From 9bcae469fcf3ba0ea6779c180520011eda5bd75b Mon Sep 17 00:00:00 2001 From: durch Date: Tue, 16 Dec 2025 11:26:56 +0100 Subject: [PATCH] Increase KCP fragment limit from u8 to u16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change frg field from u8 to u16 in packet header (25 bytes total) - Update encode/decode to use get_u16_le/put_u16_le - Update Segment struct frg field to u16 - Remove truncating cast in session.rs - Max message size now ~91MB (65,535 fragments × MTU) - Internal protocol only, no interop concerns Nym uses KCP for reliability and multiplexing, not standard real-time use cases. The u8 limit (255 fragments, ~355KB) was insufficient. Addresses: nym-yih9 --- .gitignore | 2 ++ common/nym-kcp/src/packet.rs | 19 ++++++++++++------- common/nym-kcp/src/session.rs | 6 +++--- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index fe7098565f..5b5393f6ce 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,5 @@ nym-api/redocly/formatted-openapi.json .beads CLAUDE.md docs +.claude +.superego \ No newline at end of file diff --git a/common/nym-kcp/src/packet.rs b/common/nym-kcp/src/packet.rs index 0ab1c3b595..fc900369af 100644 --- a/common/nym-kcp/src/packet.rs +++ b/common/nym-kcp/src/packet.rs @@ -3,7 +3,11 @@ use log::{debug, trace}; use super::error::KcpError; -pub const KCP_HEADER: usize = 24; +// Nym-KCP uses a modified header format with u16 for frg field (25 bytes total). +// Standard KCP uses u8 for frg (24 bytes). This deviation from skywind3000/kcp protocol +// supports messages up to ~91MB (65535 fragments × MTU) vs standard 355KB limit. +// This is intentional - Nym uses KCP internally for reliability/multiplexing, not interop. +pub const KCP_HEADER: usize = 25; /// Typed enumeration for KCP commands. #[repr(u8)] @@ -48,11 +52,12 @@ impl Into for KcpCommand { } /// A single KCP packet (on-wire format). +/// Note: Nym-KCP uses u16 for frg (fragment count) instead of standard u8. #[derive(Debug, Clone)] pub struct KcpPacket { conv: u32, cmd: KcpCommand, - frg: u8, + frg: u16, wnd: u16, ts: u32, sn: u32, @@ -65,7 +70,7 @@ impl KcpPacket { pub fn new( conv: u32, cmd: KcpCommand, - frg: u8, + frg: u16, wnd: u16, ts: u32, sn: u32, @@ -104,7 +109,7 @@ impl KcpPacket { self.cmd } - pub fn frg(&self) -> u8 { + pub fn frg(&self) -> u16 { self.frg } @@ -154,12 +159,12 @@ impl KcpPacket { return Ok(None); } - // Peek into the first 28 bytes + // Peek into the header (25 bytes for Nym-KCP) let mut header = &src[..KCP_HEADER]; let conv = header.get_u32_le(); let cmd_byte = header.get_u8(); - let frg = header.get_u8(); + let frg = header.get_u16_le(); let wnd = header.get_u16_le(); let ts = header.get_u32_le(); let sn = header.get_u32_le(); @@ -206,7 +211,7 @@ impl KcpPacket { dst.put_u32_le(self.conv); dst.put_u8(self.cmd.into()); // Convert enum -> u8 - dst.put_u8(self.frg); + dst.put_u16_le(self.frg); dst.put_u16_le(self.wnd); dst.put_u32_le(self.ts); dst.put_u32_le(self.sn); diff --git a/common/nym-kcp/src/session.rs b/common/nym-kcp/src/session.rs index f72cd19ef7..7312d51798 100644 --- a/common/nym-kcp/src/session.rs +++ b/common/nym-kcp/src/session.rs @@ -50,7 +50,7 @@ pub struct KcpSession { #[derive(Debug, Clone)] struct Segment { sn: u32, - frg: u8, + frg: u16, ts: u32, resendts: u32, rto: u32, @@ -60,7 +60,7 @@ struct Segment { impl Segment { #[allow(dead_code)] - fn new(sn: u32, frg: u8, data: Vec) -> Self { + fn new(sn: u32, frg: u16, data: Vec) -> Self { Segment { sn, frg, @@ -173,7 +173,7 @@ impl KcpSession { // first has frg=count-1. This allows receiver to know total count from first packet. // In KCP, `frg` is set to the remaining fragments in reverse order. // i.e., the last fragment has frg=0, the first has frg=count-1. - let frg = (count - i - 1) as u8; + let frg = (count - i - 1) as u16; let seg = Segment { sn: self.snd_nxt,