From cdd26098c525b23714c111ae3db11da84079b786 Mon Sep 17 00:00:00 2001
From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
Date: Sun, 24 Mar 2019 22:15:52 +0900
Subject: [PATCH] Fix broken stream ID counting issue

---
 examples/client.cc           |   4 +-
 examples/server.cc           |   4 +-
 lib/includes/ngtcp2/ngtcp2.h |  28 ++----
 lib/ngtcp2_conn.c            | 190 +++++++++++++++--------------------
 lib/ngtcp2_conn.h            |  35 ++++---
 lib/ngtcp2_conv.c            |   4 +
 lib/ngtcp2_conv.h            |   5 +
 tests/ngtcp2_conn_test.c     |  28 ++----
 8 files changed, 131 insertions(+), 167 deletions(-)

diff --git a/examples/client.cc b/examples/client.cc
index 6f6f5fad..d6aa4833 100644
--- a/examples/client.cc
+++ b/examples/client.cc
@@ -931,8 +931,8 @@ int Client::init(int fd, const Address &local_addr, const Address &remote_addr,
       path_validation,
       ::select_preferred_address,
       nullptr, // stream_reset
-      nullptr, // max_remote_stream_id_bidi,
-      nullptr, // max_remote_stream_id_uni,
+      nullptr, // extend_max_remote_streams_bidi,
+      nullptr, // extend_max_remote_streams_uni,
   };
 
   auto dis = std::uniform_int_distribution<uint8_t>(
diff --git a/examples/server.cc b/examples/server.cc
index 67aa4dae..9009fd07 100644
--- a/examples/server.cc
+++ b/examples/server.cc
@@ -1066,8 +1066,8 @@ int Handler::init(const Endpoint &ep, const sockaddr *sa, socklen_t salen,
       path_validation,
       nullptr, // select_preferred_addr
       nullptr, // stream_reset
-      nullptr, // max_remote_stream_id_bidi,
-      nullptr, // max_remote_stream_id_uni,
+      nullptr, // extend_max_remote_streams_bidi,
+      nullptr, // extend_max_remote_streams_uni,
   };
 
   ngtcp2_settings settings;
diff --git a/lib/includes/ngtcp2/ngtcp2.h b/lib/includes/ngtcp2/ngtcp2.h
index 8fe27627..e04fad32 100644
--- a/lib/includes/ngtcp2/ngtcp2.h
+++ b/lib/includes/ngtcp2/ngtcp2.h
@@ -1336,8 +1336,8 @@ typedef int (*ngtcp2_recv_stateless_reset)(ngtcp2_conn *conn,
  *
  * :type:`ngtcp2_extend_max_streams` is a callback function which is
  * called every time max stream ID is strictly extended.
- * |max_streams| is the cumulative number of streams which a local
- * endpoint can open.
+ * |max_streams| is the cumulative number of streams which an endpoint
+ * can open.
  *
  * The callback function must return 0 if it succeeds.  Returning
  * :enum:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
@@ -1451,22 +1451,6 @@ typedef int (*ngtcp2_select_preferred_addr)(ngtcp2_conn *conn,
                                             const ngtcp2_preferred_addr *paddr,
                                             void *user_data);
 
-/**
- * @functypedef
- *
- * :type:`ngtcp2_max_remote_stream_id` is a callback function which is
- * invoked when the maximum stream ID which remote endpoint can open
- * is extended.  |max_stream_id| is the maximum stream ID which remote
- * endpoint can initiate.
- *
- * The callback function must return 0 if it succeeds.  Returning
- * :enum:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return
- * immediately.
- */
-typedef int (*ngtcp2_max_remote_stream_id)(ngtcp2_conn *conn,
-                                           int64_t max_stream_id,
-                                           void *user_data);
-
 typedef struct {
   ngtcp2_client_initial client_initial;
   ngtcp2_recv_client_initial recv_client_initial;
@@ -1510,8 +1494,8 @@ typedef struct {
   ngtcp2_stream_close stream_close;
   ngtcp2_recv_stateless_reset recv_stateless_reset;
   ngtcp2_recv_retry recv_retry;
-  ngtcp2_extend_max_streams extend_max_streams_bidi;
-  ngtcp2_extend_max_streams extend_max_streams_uni;
+  ngtcp2_extend_max_streams extend_max_local_streams_bidi;
+  ngtcp2_extend_max_streams extend_max_local_streams_uni;
   ngtcp2_rand rand;
   ngtcp2_get_new_connection_id get_new_connection_id;
   ngtcp2_remove_connection_id remove_connection_id;
@@ -1519,8 +1503,8 @@ typedef struct {
   ngtcp2_path_validation path_validation;
   ngtcp2_select_preferred_addr select_preferred_addr;
   ngtcp2_stream_reset stream_reset;
-  ngtcp2_max_remote_stream_id max_remote_stream_id_bidi;
-  ngtcp2_max_remote_stream_id max_remote_stream_id_uni;
+  ngtcp2_extend_max_streams extend_max_remote_streams_bidi;
+  ngtcp2_extend_max_streams extend_max_remote_streams_uni;
 } ngtcp2_conn_callbacks;
 
 /*
diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c
index 2367f754..10269f71 100644
--- a/lib/ngtcp2_conn.c
+++ b/lib/ngtcp2_conn.c
@@ -169,16 +169,16 @@ static int conn_call_stream_reset(ngtcp2_conn *conn, int64_t stream_id,
   return 0;
 }
 
-static int conn_call_extend_max_streams_bidi(ngtcp2_conn *conn,
-                                             uint64_t max_streams) {
+static int conn_call_extend_max_local_streams_bidi(ngtcp2_conn *conn,
+                                                   uint64_t max_streams) {
   int rv;
 
-  if (!conn->callbacks.extend_max_streams_bidi) {
+  if (!conn->callbacks.extend_max_local_streams_bidi) {
     return 0;
   }
 
-  rv = conn->callbacks.extend_max_streams_bidi(conn, max_streams,
-                                               conn->user_data);
+  rv = conn->callbacks.extend_max_local_streams_bidi(conn, max_streams,
+                                                     conn->user_data);
   if (rv != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
@@ -186,16 +186,16 @@ static int conn_call_extend_max_streams_bidi(ngtcp2_conn *conn,
   return 0;
 }
 
-static int conn_call_extend_max_streams_uni(ngtcp2_conn *conn,
-                                            uint64_t max_streams) {
+static int conn_call_extend_max_local_streams_uni(ngtcp2_conn *conn,
+                                                  uint64_t max_streams) {
   int rv;
 
-  if (!conn->callbacks.extend_max_streams_uni) {
+  if (!conn->callbacks.extend_max_local_streams_uni) {
     return 0;
   }
 
-  rv = conn->callbacks.extend_max_streams_uni(conn, max_streams,
-                                              conn->user_data);
+  rv = conn->callbacks.extend_max_local_streams_uni(conn, max_streams,
+                                                    conn->user_data);
   if (rv != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
@@ -266,16 +266,16 @@ static int conn_call_select_preferred_addr(ngtcp2_conn *conn,
   return 0;
 }
 
-static int conn_call_max_remote_stream_id_bidi(ngtcp2_conn *conn,
-                                               int64_t max_stream_id) {
+static int conn_call_extend_max_remote_streams_bidi(ngtcp2_conn *conn,
+                                                    uint64_t max_streams) {
   int rv;
 
-  if (!conn->callbacks.max_remote_stream_id_bidi) {
+  if (!conn->callbacks.extend_max_remote_streams_bidi) {
     return 0;
   }
 
-  rv = conn->callbacks.max_remote_stream_id_bidi(conn, max_stream_id,
-                                                 conn->user_data);
+  rv = conn->callbacks.extend_max_remote_streams_bidi(conn, max_streams,
+                                                      conn->user_data);
   if (rv != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
@@ -283,16 +283,16 @@ static int conn_call_max_remote_stream_id_bidi(ngtcp2_conn *conn,
   return 0;
 }
 
-static int conn_call_max_remote_stream_id_uni(ngtcp2_conn *conn,
-                                              int64_t max_stream_id) {
+static int conn_call_extend_max_remote_streams_uni(ngtcp2_conn *conn,
+                                                   uint64_t max_streams) {
   int rv;
 
-  if (!conn->callbacks.max_remote_stream_id_uni) {
+  if (!conn->callbacks.extend_max_remote_streams_uni) {
     return 0;
   }
 
-  rv = conn->callbacks.max_remote_stream_id_uni(conn, max_stream_id,
-                                                conn->user_data);
+  rv = conn->callbacks.extend_max_remote_streams_uni(conn, max_streams,
+                                                     conn->user_data);
   if (rv != 0) {
     return NGTCP2_ERR_CALLBACK_FAILURE;
   }
@@ -639,13 +639,11 @@ int ngtcp2_conn_client_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid,
     return rv;
   }
   (*pconn)->rcid = *dcid;
-  (*pconn)->remote.bidi.unsent_max_stream_id =
-      (*pconn)->remote.bidi.max_stream_id =
-          ngtcp2_nth_server_bidi_id(settings->max_streams_bidi);
+  (*pconn)->remote.bidi.unsent_max_streams = (*pconn)->remote.bidi.max_streams =
+      settings->max_streams_bidi;
 
-  (*pconn)->remote.uni.unsent_max_stream_id =
-      (*pconn)->remote.uni.max_stream_id =
-          ngtcp2_nth_server_uni_id(settings->max_streams_uni);
+  (*pconn)->remote.uni.unsent_max_streams = (*pconn)->remote.uni.max_streams =
+      settings->max_streams_uni;
 
   (*pconn)->state = NGTCP2_CS_CLIENT_INITIAL;
   (*pconn)->local.bidi.next_stream_id = 0;
@@ -665,13 +663,11 @@ int ngtcp2_conn_server_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid,
     return rv;
   }
   (*pconn)->server = 1;
-  (*pconn)->remote.bidi.unsent_max_stream_id =
-      (*pconn)->remote.bidi.max_stream_id =
-          ngtcp2_nth_client_bidi_id(settings->max_streams_bidi);
+  (*pconn)->remote.bidi.unsent_max_streams = (*pconn)->remote.bidi.max_streams =
+      settings->max_streams_bidi;
 
-  (*pconn)->remote.uni.unsent_max_stream_id =
-      (*pconn)->remote.uni.max_stream_id =
-          ngtcp2_nth_client_uni_id(settings->max_streams_uni);
+  (*pconn)->remote.uni.unsent_max_streams = (*pconn)->remote.uni.max_streams =
+      settings->max_streams_uni;
 
   (*pconn)->state = NGTCP2_CS_SERVER_INITIAL;
   (*pconn)->local.bidi.next_stream_id = 1;
@@ -2171,8 +2167,7 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
       assert(0);
       break;
     case NGTCP2_FRAME_MAX_STREAMS_BIDI:
-      if ((*pfrc)->fr.max_streams.max_streams <
-          (uint64_t)(conn->remote.bidi.max_stream_id >> 2)) {
+      if ((*pfrc)->fr.max_streams.max_streams < conn->remote.bidi.max_streams) {
         frc = *pfrc;
         *pfrc = (*pfrc)->next;
         ngtcp2_frame_chain_del(frc, conn->mem);
@@ -2180,8 +2175,7 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
       }
       break;
     case NGTCP2_FRAME_MAX_STREAMS_UNI:
-      if ((*pfrc)->fr.max_streams.max_streams <
-          (uint64_t)(conn->remote.uni.max_stream_id >> 2)) {
+      if ((*pfrc)->fr.max_streams.max_streams < conn->remote.uni.max_streams) {
         frc = *pfrc;
         *pfrc = (*pfrc)->next;
         ngtcp2_frame_chain_del(frc, conn->mem);
@@ -2261,10 +2255,9 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
   /* Write MAX_STREAM_ID after RESET_STREAM so that we can extend stream
      ID space in one packet. */
   if (rv != NGTCP2_ERR_NOBUF && *pfrc == NULL &&
-      conn->remote.bidi.unsent_max_stream_id >
-          conn->remote.bidi.max_stream_id) {
-    rv = conn_call_max_remote_stream_id_bidi(
-        conn, conn->remote.bidi.unsent_max_stream_id);
+      conn->remote.bidi.unsent_max_streams > conn->remote.bidi.max_streams) {
+    rv = conn_call_extend_max_remote_streams_bidi(
+        conn, conn->remote.bidi.unsent_max_streams);
     if (rv != 0) {
       assert(ngtcp2_err_is_fatal(rv));
       return rv;
@@ -2276,11 +2269,10 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
       return rv;
     }
     nfrc->fr.type = NGTCP2_FRAME_MAX_STREAMS_BIDI;
-    nfrc->fr.max_streams.max_streams =
-        (uint64_t)(conn->remote.bidi.unsent_max_stream_id >> 2);
+    nfrc->fr.max_streams.max_streams = conn->remote.bidi.unsent_max_streams;
     *pfrc = nfrc;
 
-    conn->remote.bidi.max_stream_id = conn->remote.bidi.unsent_max_stream_id;
+    conn->remote.bidi.max_streams = conn->remote.bidi.unsent_max_streams;
 
     rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &(*pfrc)->fr);
     if (rv != 0) {
@@ -2293,10 +2285,9 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
   }
 
   if (rv != NGTCP2_ERR_NOBUF && *pfrc == NULL) {
-    if (conn->remote.uni.unsent_max_stream_id >
-        conn->remote.uni.max_stream_id) {
-      rv = conn_call_max_remote_stream_id_uni(
-          conn, conn->remote.uni.unsent_max_stream_id);
+    if (conn->remote.uni.unsent_max_streams > conn->remote.uni.max_streams) {
+      rv = conn_call_extend_max_remote_streams_uni(
+          conn, conn->remote.uni.unsent_max_streams);
       if (rv != 0) {
         assert(ngtcp2_err_is_fatal(rv));
         return rv;
@@ -2308,11 +2299,10 @@ static ssize_t conn_write_pkt(ngtcp2_conn *conn, uint8_t *dest, size_t destlen,
         return rv;
       }
       nfrc->fr.type = NGTCP2_FRAME_MAX_STREAMS_UNI;
-      nfrc->fr.max_streams.max_streams =
-          (uint64_t)(conn->remote.uni.unsent_max_stream_id >> 2);
+      nfrc->fr.max_streams.max_streams = conn->remote.uni.unsent_max_streams;
       *pfrc = nfrc;
 
-      conn->remote.uni.max_stream_id = conn->remote.uni.unsent_max_stream_id;
+      conn->remote.uni.max_streams = conn->remote.uni.unsent_max_streams;
 
       rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd,
                                        &(*pfrc)->fr);
@@ -3654,7 +3644,8 @@ static int conn_recv_max_stream_data(ngtcp2_conn *conn,
       if (conn->local.bidi.next_stream_id <= fr->stream_id) {
         return NGTCP2_ERR_STREAM_STATE;
       }
-    } else if (conn->remote.bidi.max_stream_id < fr->stream_id) {
+    } else if (conn->remote.bidi.max_streams <
+               ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }
 
@@ -4868,7 +4859,8 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) {
       if (conn->local.bidi.next_stream_id <= fr->stream_id) {
         return NGTCP2_ERR_STREAM_STATE;
       }
-    } else if (conn->remote.bidi.max_stream_id < fr->stream_id) {
+    } else if (conn->remote.bidi.max_streams <
+               ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }
 
@@ -4877,7 +4869,7 @@ static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) {
     if (local_stream) {
       return NGTCP2_ERR_STREAM_STATE;
     }
-    if (conn->remote.uni.max_stream_id < fr->stream_id) {
+    if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }
 
@@ -5099,13 +5091,13 @@ static int conn_stop_sending(ngtcp2_conn *conn, ngtcp2_strm *strm,
 }
 
 /*
- * handle_remote_stream_id_extension extends
- * |*punsent_max_remote_stream_id| if a condition allows it.
+ * handle_max_remote_streams_extension extends
+ * |*punsent_max_remote_streams| if a condition allows it.
  */
 static void
-handle_remote_stream_id_extension(int64_t *punsent_max_remote_stream_id) {
-  if (*punsent_max_remote_stream_id <= (int64_t)(NGTCP2_MAX_VARINT - 4)) {
-    *punsent_max_remote_stream_id += 4;
+handle_max_remote_streams_extension(uint64_t *punsent_max_remote_streams) {
+  if (*punsent_max_remote_streams < NGTCP2_MAX_STREAMS) {
+    ++(*punsent_max_remote_streams);
   }
 }
 
@@ -5149,7 +5141,8 @@ static int conn_recv_reset_stream(ngtcp2_conn *conn,
       if (conn->local.bidi.next_stream_id <= fr->stream_id) {
         return NGTCP2_ERR_STREAM_STATE;
       }
-    } else if (fr->stream_id > conn->remote.bidi.max_stream_id) {
+    } else if (conn->remote.bidi.max_streams <
+               ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }
 
@@ -5158,7 +5151,7 @@ static int conn_recv_reset_stream(ngtcp2_conn *conn,
     if (local_stream) {
       return NGTCP2_ERR_PROTO;
     }
-    if (fr->stream_id > conn->remote.uni.max_stream_id) {
+    if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }
 
@@ -5197,10 +5190,10 @@ static int conn_recv_reset_stream(ngtcp2_conn *conn,
        RESET_STREAM and don't write stream data any further.  This
        effectively allows another new stream for peer. */
     if (bidi) {
-      handle_remote_stream_id_extension(
-          &conn->remote.bidi.unsent_max_stream_id);
+      handle_max_remote_streams_extension(
+          &conn->remote.bidi.unsent_max_streams);
     } else {
-      handle_remote_stream_id_extension(&conn->remote.uni.unsent_max_stream_id);
+      handle_max_remote_streams_extension(&conn->remote.uni.unsent_max_streams);
     }
 
     return 0;
@@ -5268,7 +5261,8 @@ static int conn_recv_stop_sending(ngtcp2_conn *conn,
       if (conn->local.bidi.next_stream_id <= fr->stream_id) {
         return NGTCP2_ERR_STREAM_STATE;
       }
-    } else if (fr->stream_id > conn->remote.bidi.max_stream_id) {
+    } else if (conn->remote.bidi.max_streams <
+               ngtcp2_ord_stream_id(fr->stream_id)) {
       return NGTCP2_ERR_STREAM_LIMIT;
     }
 
@@ -5513,32 +5507,19 @@ static int conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn,
  */
 static int conn_recv_max_streams(ngtcp2_conn *conn,
                                  const ngtcp2_max_streams *fr) {
-  int64_t n;
+  uint64_t n = ngtcp2_min(fr->max_streams, NGTCP2_MAX_STREAMS);
+
   if (fr->type == NGTCP2_FRAME_MAX_STREAMS_BIDI) {
-    if (conn->server) {
-      n = ngtcp2_nth_server_bidi_id(fr->max_streams);
-      n = ngtcp2_min(n, NGTCP2_MAX_SERVER_STREAM_ID_BIDI);
-    } else {
-      n = ngtcp2_nth_client_bidi_id(fr->max_streams);
-      n = ngtcp2_min(n, NGTCP2_MAX_CLIENT_STREAM_ID_BIDI);
-    }
-    if (n > conn->local.bidi.max_stream_id) {
-      conn->local.bidi.max_stream_id = n;
-      return conn_call_extend_max_streams_bidi(conn, (uint64_t)(n >> 2));
+    if (conn->local.bidi.max_streams < n) {
+      conn->local.bidi.max_streams = n;
+      return conn_call_extend_max_local_streams_bidi(conn, n);
     }
     return 0;
   }
 
-  if (conn->server) {
-    n = ngtcp2_nth_server_uni_id(fr->max_streams);
-    n = ngtcp2_min(n, NGTCP2_MAX_SERVER_STREAM_ID_UNI);
-  } else {
-    n = ngtcp2_nth_client_uni_id(fr->max_streams);
-    n = ngtcp2_min(n, NGTCP2_MAX_CLIENT_STREAM_ID_UNI);
-  }
-  if (n > conn->local.uni.max_stream_id) {
-    conn->local.uni.max_stream_id = n;
-    return conn_call_extend_max_streams_uni(conn, (uint64_t)(n >> 2));
+  if (conn->local.uni.max_streams < n) {
+    conn->local.uni.max_streams = n;
+    return conn_call_extend_max_local_streams_uni(conn, n);
   }
   return 0;
 }
@@ -6417,16 +6398,16 @@ static int conn_handshake_completed(ngtcp2_conn *conn) {
     return rv;
   }
 
-  if (conn->local.bidi.max_stream_id > 0) {
-    rv = conn_call_extend_max_streams_bidi(
-        conn, (uint64_t)(conn->local.bidi.max_stream_id >> 2));
+  if (conn->local.bidi.max_streams > 0) {
+    rv = conn_call_extend_max_local_streams_bidi(conn,
+                                                 conn->local.bidi.max_streams);
     if (rv != 0) {
       return rv;
     }
   }
-  if (conn->local.uni.max_stream_id > 0) {
-    rv = conn_call_extend_max_streams_uni(
-        conn, (uint64_t)(conn->local.uni.max_stream_id >> 2));
+  if (conn->local.uni.max_streams > 0) {
+    rv = conn_call_extend_max_local_streams_uni(conn,
+                                                conn->local.uni.max_streams);
     if (rv != 0) {
       return rv;
     }
@@ -7617,19 +7598,8 @@ conn_client_validate_transport_params(ngtcp2_conn *conn,
 }
 
 static void conn_sync_stream_id_limit(ngtcp2_conn *conn) {
-  if (conn->server) {
-    conn->local.bidi.max_stream_id =
-        ngtcp2_nth_server_bidi_id(conn->remote.settings.max_streams_bidi);
-
-    conn->local.uni.max_stream_id =
-        ngtcp2_nth_server_uni_id(conn->remote.settings.max_streams_uni);
-  } else {
-    conn->local.bidi.max_stream_id =
-        ngtcp2_nth_client_bidi_id(conn->remote.settings.max_streams_bidi);
-
-    conn->local.uni.max_stream_id =
-        ngtcp2_nth_client_uni_id(conn->remote.settings.max_streams_uni);
-  }
+  conn->local.bidi.max_streams = conn->remote.settings.max_streams_bidi;
+  conn->local.uni.max_streams = conn->remote.settings.max_streams_uni;
 }
 
 int ngtcp2_conn_set_remote_transport_params(
@@ -7723,7 +7693,8 @@ int ngtcp2_conn_open_bidi_stream(ngtcp2_conn *conn, int64_t *pstream_id,
   int rv;
   ngtcp2_strm *strm;
 
-  if (conn->local.bidi.next_stream_id > conn->local.bidi.max_stream_id) {
+  if (ngtcp2_ord_stream_id(conn->local.bidi.next_stream_id) >
+      conn->local.bidi.max_streams) {
     return NGTCP2_ERR_STREAM_ID_BLOCKED;
   }
 
@@ -7750,7 +7721,8 @@ int ngtcp2_conn_open_uni_stream(ngtcp2_conn *conn, int64_t *pstream_id,
   int rv;
   ngtcp2_strm *strm;
 
-  if (conn->local.uni.next_stream_id > conn->local.uni.max_stream_id) {
+  if (ngtcp2_ord_stream_id(conn->local.uni.next_stream_id) >
+      conn->local.uni.max_streams) {
     return NGTCP2_ERR_STREAM_ID_BLOCKED;
   }
 
@@ -8043,10 +8015,10 @@ int ngtcp2_conn_close_stream(ngtcp2_conn *conn, ngtcp2_strm *strm,
 
   if (!conn_local_stream(conn, strm->stream_id)) {
     if (bidi_stream(strm->stream_id)) {
-      handle_remote_stream_id_extension(
-          &conn->remote.bidi.unsent_max_stream_id);
+      handle_max_remote_streams_extension(
+          &conn->remote.bidi.unsent_max_streams);
     } else {
-      handle_remote_stream_id_extension(&conn->remote.uni.unsent_max_stream_id);
+      handle_max_remote_streams_extension(&conn->remote.uni.unsent_max_streams);
     }
   }
 
diff --git a/lib/ngtcp2_conn.h b/lib/ngtcp2_conn.h
index 1ff6af99..31674bf0 100644
--- a/lib/ngtcp2_conn.h
+++ b/lib/ngtcp2_conn.h
@@ -62,6 +62,9 @@ typedef enum {
   NGTCP2_CS_DRAINING,
 } ngtcp2_conn_state;
 
+/* NGTCP2_MAX_STREAMS is the maximum number of streams. */
+#define NGTCP2_MAX_STREAMS (((1LL << 60) - 1) << 2)
+
 /* NGTCP2_MAX_NUM_BUFFED_RX_PKTS is the maximum number of buffered
    reordered packets. */
 #define NGTCP2_MAX_NUM_BUFFED_RX_PKTS 16
@@ -356,18 +359,18 @@ struct ngtcp2_conn {
   struct {
     ngtcp2_settings settings;
     struct {
-      /* max_stream_id is the maximum bidirectional stream ID which
+      /* max_streams is the maximum number of bidirectional streams which
          the local endpoint can open. */
-      int64_t max_stream_id;
+      uint64_t max_streams;
       /* next_stream_id is the bidirectional stream ID which the local
          endpoint opens next. */
       int64_t next_stream_id;
     } bidi;
 
     struct {
-      /* max_stream_id is the maximum unidirectional stream ID which
-         the local endpoint can open. */
-      int64_t max_stream_id;
+      /* max_streams is the maximum number of unidirectional streams
+         which the local endpoint can open. */
+      uint64_t max_streams;
       /* next_stream_id is the unidirectional stream ID which the
          local endpoint opens next. */
       int64_t next_stream_id;
@@ -378,26 +381,28 @@ struct ngtcp2_conn {
     ngtcp2_settings settings;
     struct {
       ngtcp2_idtr idtr;
-      /* unsent_max_stream_id is the maximum stream ID of peer
+      /* unsent_max_streams is the maximum number of streams of peer
          initiated bidirectional stream which the local endpoint can
          accept.  This limit is not yet notified to the remote
          endpoint. */
-      int64_t unsent_max_stream_id;
-      /* max_stream_id is the maximum stream ID of peer initiated
-         bidirectional stream which the local endpoint can accept. */
-      int64_t max_stream_id;
+      uint64_t unsent_max_streams;
+      /* max_streams is the maximum number of streams of peer
+         initiated bidirectional stream which the local endpoint can
+         accept. */
+      uint64_t max_streams;
     } bidi;
 
     struct {
       ngtcp2_idtr idtr;
-      /* unsent_max_stream_id is the maximum stream ID of peer
+      /* unsent_max_streams is the maximum number of streams of peer
          initiated unidirectional stream which the local endpoint can
          accept.  This limit is not yet notified to the remote
          endpoint. */
-      int64_t unsent_max_stream_id;
-      /* max_stream_id is the maximum stream ID of peer initiated
-         unidirectional stream which the local endpoint can accept. */
-      int64_t max_stream_id;
+      uint64_t unsent_max_streams;
+      /* max_streams is the maximum number of streams of peer
+         initiated unidirectional stream which the local endpoint can
+         accept. */
+      uint64_t max_streams;
     } uni;
   } remote;
 
diff --git a/lib/ngtcp2_conv.c b/lib/ngtcp2_conv.c
index 89c0a1d0..319dd14a 100644
--- a/lib/ngtcp2_conv.c
+++ b/lib/ngtcp2_conv.c
@@ -246,3 +246,7 @@ int64_t ngtcp2_nth_client_uni_id(uint64_t n) {
 
   return (int64_t)(((n - 1) << 2) | 0x02);
 }
+
+uint64_t ngtcp2_ord_stream_id(int64_t stream_id) {
+  return (uint64_t)(stream_id >> 2) + 1;
+}
diff --git a/lib/ngtcp2_conv.h b/lib/ngtcp2_conv.h
index e421cd2f..c70d42cf 100644
--- a/lib/ngtcp2_conv.h
+++ b/lib/ngtcp2_conv.h
@@ -189,4 +189,9 @@ int64_t ngtcp2_nth_server_uni_id(uint64_t n);
  */
 int64_t ngtcp2_nth_client_uni_id(uint64_t n);
 
+/*
+ * ngtcp2_ord_stream_id returns the ordinal number of |stream_id|.
+ */
+uint64_t ngtcp2_ord_stream_id(int64_t stream_id);
+
 #endif /* NGTCP2_CONV_H */
diff --git a/tests/ngtcp2_conn_test.c b/tests/ngtcp2_conn_test.c
index 2d33de6f..af6408f8 100644
--- a/tests/ngtcp2_conn_test.c
+++ b/tests/ngtcp2_conn_test.c
@@ -388,10 +388,8 @@ static void setup_default_server(ngtcp2_conn **pconn) {
   (*pconn)->remote.settings.max_streams_bidi = 0;
   (*pconn)->remote.settings.max_streams_uni = 1;
   (*pconn)->remote.settings.max_data = 64 * 1024;
-  (*pconn)->local.bidi.max_stream_id =
-      ngtcp2_nth_server_bidi_id((*pconn)->remote.settings.max_streams_bidi);
-  (*pconn)->local.uni.max_stream_id =
-      ngtcp2_nth_server_uni_id((*pconn)->remote.settings.max_streams_uni);
+  (*pconn)->local.bidi.max_streams = (*pconn)->remote.settings.max_streams_bidi;
+  (*pconn)->local.uni.max_streams = (*pconn)->remote.settings.max_streams_uni;
   (*pconn)->tx.max_offset = (*pconn)->remote.settings.max_data;
   (*pconn)->odcid = dcid;
 }
@@ -439,10 +437,8 @@ static void setup_default_client(ngtcp2_conn **pconn) {
   (*pconn)->remote.settings.max_streams_bidi = 1;
   (*pconn)->remote.settings.max_streams_uni = 1;
   (*pconn)->remote.settings.max_data = 64 * 1024;
-  (*pconn)->local.bidi.max_stream_id =
-      ngtcp2_nth_client_bidi_id((*pconn)->remote.settings.max_streams_bidi);
-  (*pconn)->local.uni.max_stream_id =
-      ngtcp2_nth_client_uni_id((*pconn)->remote.settings.max_streams_uni);
+  (*pconn)->local.bidi.max_streams = (*pconn)->remote.settings.max_streams_bidi;
+  (*pconn)->local.uni.max_streams = (*pconn)->remote.settings.max_streams_uni;
   (*pconn)->tx.max_offset = (*pconn)->remote.settings.max_data;
   (*pconn)->odcid = dcid;
 }
@@ -547,10 +543,8 @@ static void setup_early_server(ngtcp2_conn **pconn) {
   (*pconn)->remote.settings.max_streams_bidi = 0;
   (*pconn)->remote.settings.max_streams_uni = 1;
   (*pconn)->remote.settings.max_data = 64 * 1024;
-  (*pconn)->local.bidi.max_stream_id =
-      ngtcp2_nth_server_bidi_id((*pconn)->remote.settings.max_streams_bidi);
-  (*pconn)->local.uni.max_stream_id =
-      ngtcp2_nth_server_uni_id((*pconn)->remote.settings.max_streams_uni);
+  (*pconn)->local.bidi.max_streams = (*pconn)->remote.settings.max_streams_bidi;
+  (*pconn)->local.uni.max_streams = (*pconn)->remote.settings.max_streams_uni;
   (*pconn)->tx.max_offset = (*pconn)->remote.settings.max_data;
 }
 
@@ -1434,7 +1428,7 @@ void test_ngtcp2_conn_recv_reset_stream(void) {
 
   CU_ASSERT(0 == rv);
   CU_ASSERT(NULL == ngtcp2_conn_find_stream(conn, 0));
-  CU_ASSERT(12 == conn->remote.bidi.unsent_max_stream_id);
+  CU_ASSERT(4 == conn->remote.bidi.unsent_max_streams);
 
   ngtcp2_conn_del(conn);
 
@@ -2301,7 +2295,7 @@ void test_ngtcp2_conn_recv_max_streams(void) {
   rv = ngtcp2_conn_read_pkt(conn, &null_path, buf, pktlen, 1);
 
   CU_ASSERT(0 == rv);
-  CU_ASSERT(((999 - 1) << 2) + 0x02 == conn->local.uni.max_stream_id);
+  CU_ASSERT(999 == conn->local.uni.max_streams);
 
   fr.type = NGTCP2_FRAME_MAX_STREAMS_BIDI;
   fr.max_streams.max_streams = 997;
@@ -2310,7 +2304,7 @@ void test_ngtcp2_conn_recv_max_streams(void) {
   rv = ngtcp2_conn_read_pkt(conn, &null_path, buf, pktlen, 2);
 
   CU_ASSERT(0 == rv);
-  CU_ASSERT(((997 - 1) << 2) == conn->local.bidi.max_stream_id);
+  CU_ASSERT(997 == conn->local.bidi.max_streams);
 
   ngtcp2_conn_del(conn);
 }
@@ -2526,7 +2520,7 @@ void test_ngtcp2_conn_retransmit_protected(void) {
 
   /* Retransmission takes place per frame basis. */
   setup_default_client(&conn);
-  conn->local.bidi.max_stream_id = 8;
+  conn->local.bidi.max_streams = 3;
 
   ngtcp2_conn_open_bidi_stream(conn, &stream_id_a, NULL);
   ngtcp2_conn_open_bidi_stream(conn, &stream_id_b, NULL);
@@ -2914,7 +2908,7 @@ void test_ngtcp2_conn_recv_stream_data(void) {
   /* Receive an unidirectional stream which is beyond the limit. */
   setup_default_server(&conn);
   conn->callbacks.recv_stream_data = recv_stream_data;
-  conn->remote.uni.max_stream_id = 0;
+  conn->remote.uni.max_streams = 0;
   conn->user_data = &ud;
 
   fr.type = NGTCP2_FRAME_STREAM;
-- 
GitLab