Increase KCP fragment limit from u8 to u16

- 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
This commit is contained in:
durch
2025-12-16 11:26:56 +01:00
committed by Jędrzej Stuczyński
parent 65bd47f342
commit 9bcae469fc
3 changed files with 17 additions and 10 deletions
+2
View File
@@ -68,3 +68,5 @@ nym-api/redocly/formatted-openapi.json
.beads
CLAUDE.md
docs
.claude
.superego
+12 -7
View File
@@ -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<u8> 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);
+3 -3
View File
@@ -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<u8>) -> Self {
fn new(sn: u32, frg: u16, data: Vec<u8>) -> 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,