From 517615d3a883ddcb43bc747db7e8b98563980875 Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Mon, 28 Sep 2026 00:03:16 -0700 Subject: [PATCH 1/3] Send a same-flow pure ACK heading a TX glom on its own (AmiNetXDuo #89) In gloms of three or more frames headed by two pure ACKs of one TCP flow, the head ACK never reached the air (A11: 21 of 21 missing; UDP head pairs 0 of 153). SendGlomDataPacket now sends that head in its own transfer and the rest as one glom, as before. It splits at most once per glom, so a glom costs at most one extra SDIO transfer; nothing else changes. Pure ACK: IPv4 (not a fragment) or IPv6 with TCP as the first next header, no TCP payload, ACK set, SYN/FIN/RST clear; TCP options such as timestamps and SACK count as header, not payload. Same flow: equal addresses and ports. The decision is wifipi_glom_head_split() in src/glomsplit.h, pure C with no Amiga headers; tests/test_glomsplit.c runs it on the host (33 checks). Co-Authored-By: Claude Opus 5.5 --- src/glomsplit.h | 74 ++++++++++++++++++ src/packet.c | 40 +++++++++- tests/test_glomsplit.c | 174 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 src/glomsplit.h create mode 100644 tests/test_glomsplit.c diff --git a/src/glomsplit.h b/src/glomsplit.h new file mode 100644 index 0000000..5681f03 --- /dev/null +++ b/src/glomsplit.h @@ -0,0 +1,74 @@ +/* + * TX glom head split (AmiNetXDuo #89). Pure: no Amiga headers, so the host + * test in tests/test_glomsplit.c builds it with cc. + * + * In a glom of three or more frames whose first two are pure ACKs of one TCP + * flow, the firmware drops the head ACK. The driver then sends the head on + * its own and batches the rest as before. + * + * Pure ACK: IPv4 (not a fragment, options allowed) or IPv6 (TCP as the first + * next header), TCP with no payload, ACK set, SYN, FIN and RST clear. TCP + * options (timestamps, SACK) do not count as payload. Same flow: equal + * source and destination addresses and ports. + */ +#ifndef WIFIPI_GLOMSPLIT_H +#define WIFIPI_GLOMSPLIT_H + +/* frame: from the Ethernet header, len bytes; *key and *keyLen give the + address pair, *ports the port pair */ +static inline int wifipi_pure_ack(const unsigned char *f, unsigned long len, + const unsigned char **key, unsigned int *keyLen, + const unsigned char **ports) +{ + const unsigned char *ip = f + 14, *tcp; + unsigned long hl, iplen, thl; + unsigned int type; + + if (f == 0 || len < 14 + 20) return 0; + type = (f[12] << 8) | f[13]; + + if (type == 0x0800) + { + if ((ip[0] >> 4) != 4 || ip[9] != 6) return 0; + if ((ip[6] & 0x3f) | ip[7]) return 0; /* MF or offset */ + hl = (ip[0] & 15) * 4; + iplen = (ip[2] << 8) | ip[3]; + if (hl < 20) return 0; + *key = ip + 12; *keyLen = 8; + } + else if (type == 0x86dd) + { + if (len < 14 + 40 || (ip[0] >> 4) != 6 || ip[6] != 6) return 0; + hl = 40; + iplen = 40 + ((ip[4] << 8) | ip[5]); + *key = ip + 8; *keyLen = 32; + } + else return 0; + + if (iplen < hl + 20 || 14 + iplen > len) return 0; + tcp = ip + hl; + thl = (tcp[12] >> 4) * 4; + if (thl < 20 || hl + thl != iplen) return 0; /* payload */ + if ((tcp[13] & 0x17) != 0x10) return 0; /* ACK; no FIN/SYN/RST */ + *ports = tcp; + return 1; +} + +/* frames[0..1] and lens[0..1]: the head of a glom of count frames */ +static inline int wifipi_glom_head_split(const unsigned char *const *frames, + const unsigned long *lens, + unsigned long count) +{ + const unsigned char *k0, *k1, *p0, *p1; + unsigned int n0, n1, i; + + if (count < 3) return 0; + if (!wifipi_pure_ack(frames[0], lens[0], &k0, &n0, &p0)) return 0; + if (!wifipi_pure_ack(frames[1], lens[1], &k1, &n1, &p1)) return 0; + if (n0 != n1) return 0; + for (i = 0; i < n0; i++) if (k0[i] != k1[i]) return 0; + for (i = 0; i < 4; i++) if (p0[i] != p1[i]) return 0; + return 1; +} + +#endif diff --git a/src/packet.c b/src/packet.c index 5c2797e..80fe0fa 100644 --- a/src/packet.c +++ b/src/packet.c @@ -20,6 +20,7 @@ #include "wifipi.h" #include "packet.h" #include "brcm_wifi.h" +#include "glomsplit.h" #include #ifndef PAD @@ -1857,7 +1858,8 @@ void ProcessDataPacket(struct SDIO *sdio, UBYTE *packet, ULONG packetLength) * Byte 6~7: Reserved */ -int SendGlomDataPacket(struct SDIO *sdio, struct IOSana2Req **ioList, UBYTE count) +/* one transfer from the head of ioList; returns the frames it took */ +static UBYTE SendGlomOnce(struct SDIO *sdio, struct IOSana2Req **ioList, UBYTE count, BOOL maySplit) { struct WiFiBase *WiFiBase = sdio->s_WiFiBase; struct ExecBase *SysBase = sdio->s_SysBase; @@ -1867,6 +1869,8 @@ int SendGlomDataPacket(struct SDIO *sdio, struct IOSana2Req **ioList, UBYTE coun struct PacketHeaderHW *pktBase = sdio->s_TXBuffer; UBYTE *byteBuffer = sdio->s_TXBuffer; struct GlomHeader *lastGh = NULL; + const UBYTE *head[2]; + ULONG headLen[2]; for (UBYTE i = 0; i < count; i++) { @@ -1915,6 +1919,12 @@ int SendGlomDataPacket(struct SDIO *sdio, struct IOSana2Req **ioList, UBYTE coun *ptr++ = 0; *ptr++ = 0; + if (i < 2) + { + head[i] = ptr; + headLen[i] = packetLength - sizeof(struct Packet) - sizeof(struct GlomHeader) - 4; + } + if ((io->ios2_Req.io_Flags & SANA2IOF_RAW) == 0) { // Copy destination @@ -1956,6 +1966,18 @@ int SendGlomDataPacket(struct SDIO *sdio, struct IOSana2Req **ioList, UBYTE coun if (packetLength % 16 != 0) bug("\n"); } #endif + /* two pure ACKs of one flow at the head of 3+: the firmware drops + the first, so it goes alone; the rest is one glom as before (#89) */ + if (i == 1 && maySplit && wifipi_glom_head_split(head, headLen, count)) + { + struct GlomHeader *firstGh = (APTR)(byteBuffer + sizeof(struct PacketHeaderHW)); + + sdio->s_TXSeq--; + firstGh->gh_LastItem = 1; + count = 1; + break; + } + // Increase total length by packet length (aligned) totalLength += (packetLength + 3) & ~3; } @@ -1998,6 +2020,22 @@ int SendGlomDataPacket(struct SDIO *sdio, struct IOSana2Req **ioList, UBYTE coun unit->wu_Stats.PacketsSent++; } + return count; +} + +int SendGlomDataPacket(struct SDIO *sdio, struct IOSana2Req **ioList, UBYTE count) +{ + BOOL maySplit = TRUE; + + while (count) + { + UBYTE n = SendGlomOnce(sdio, ioList, count, maySplit); + + maySplit = FALSE; + ioList += n; + count -= n; + } + return 1; } diff --git a/tests/test_glomsplit.c b/tests/test_glomsplit.c new file mode 100644 index 0000000..5f24680 --- /dev/null +++ b/tests/test_glomsplit.c @@ -0,0 +1,174 @@ +/* + * TX glom head split decision (#89), on the host: + * cc -O2 -Wall -Wextra -o test_glomsplit tests/test_glomsplit.c && ./test_glomsplit + * Prints RESULT pass=N fail=M; exit status 0 only when nothing failed. + */ +#include +#include + +#include "../src/glomsplit.h" + +static int checks, failures; + +static void expect(int got, int want, const char *what) +{ + checks++; + if (got == want) return; + failures++; + printf("FAIL %s: got %d, want %d\n", what, got, want); +} + +#define ACK 0x10 +#define PSH 0x08 +#define FIN 0x01 +#define SYN 0x02 +#define RST 0x04 + +/* Ethernet + IPv4 + TCP; optlen bytes of TCP options, dlen of payload */ +static unsigned long v4(unsigned char *f, unsigned int src, unsigned int sport, + unsigned int dport, unsigned int flags, unsigned int optlen, + unsigned int dlen) +{ + unsigned char *ip = f + 14, *t = ip + 20; + unsigned int thl = 20 + optlen, iplen = 20 + thl + dlen; + + memset(f, 0, 14 + iplen + 16); + f[12] = 0x08; f[13] = 0x00; + ip[0] = 0x45; ip[2] = iplen >> 8; ip[3] = iplen; + ip[6] = 0x40; /* DF */ + ip[8] = 64; ip[9] = 6; + ip[12] = 192; ip[13] = 168; ip[14] = 1; ip[15] = src; + ip[16] = 192; ip[17] = 168; ip[18] = 1; ip[19] = 2; + t[0] = sport >> 8; t[1] = sport; t[2] = dport >> 8; t[3] = dport; + t[12] = (thl / 4) << 4; t[13] = flags; + return 14 + iplen; +} + +/* Ethernet + IPv6 + TCP */ +static unsigned long v6(unsigned char *f, unsigned int src, unsigned int nh, + unsigned int sport, unsigned int flags, unsigned int dlen) +{ + unsigned char *ip = f + 14, *t = ip + 40; + unsigned int plen = 20 + dlen; + + memset(f, 0, 14 + 40 + plen + 16); + f[12] = 0x86; f[13] = 0xdd; + ip[0] = 0x60; ip[4] = plen >> 8; ip[5] = plen; ip[6] = nh; ip[7] = 64; + ip[8] = 0xfe; ip[9] = 0x80; ip[23] = src; + ip[24] = 0xfe; ip[25] = 0x80; ip[39] = 2; + t[0] = sport >> 8; t[1] = sport; t[2] = 0x14; t[3] = 0x51; + t[12] = 5 << 4; t[13] = flags; + return 14 + 40 + plen; +} + +static unsigned char a[1600], b[1600]; +static const unsigned char *fr[2] = { a, b }; +static unsigned long ln[2]; + +static int split(unsigned long count) { return wifipi_glom_head_split(fr, ln, count); } + +int main(void) +{ + /* two same-flow pure ACKs heading 3 and 32 frames: split */ + ln[0] = v4(a, 10, 5001, 50000, ACK, 0, 0); + ln[1] = v4(b, 10, 5001, 50000, ACK, 0, 0); + expect(split(3), 1, "A40,A40 count 3"); + expect(split(32), 1, "A40,A40 count 32"); + /* two frames, or one: no split */ + expect(split(2), 0, "A40,A40 count 2"); + expect(split(1), 0, "count 1"); + /* the Ethernet frame may be longer than the IP datagram */ + ln[0] += 6; + expect(split(3), 1, "Ethernet tail beyond IP length"); + /* a truncated frame is not read past its end */ + ln[0] = 14 + 39; + expect(split(3), 0, "truncated head"); + + /* timestamps and SACK options: still pure ACKs */ + ln[0] = v4(a, 10, 5001, 50000, ACK, 12, 0); + ln[1] = v4(b, 10, 5001, 50000, ACK, 20, 0); + expect(split(3), 1, "ACKs with options"); + /* PSH on an empty segment: still pure */ + ln[0] = v4(a, 10, 5001, 50000, ACK | PSH, 0, 0); + expect(split(3), 1, "ACK|PSH no payload"); + + /* different flows: no split */ + ln[0] = v4(a, 10, 5001, 50000, ACK, 0, 0); + ln[1] = v4(b, 10, 5002, 50000, ACK, 0, 0); + expect(split(3), 0, "different source port"); + ln[1] = v4(b, 10, 5001, 50001, ACK, 0, 0); + expect(split(3), 0, "different destination port"); + ln[1] = v4(b, 11, 5001, 50000, ACK, 0, 0); + expect(split(3), 0, "different source address"); + ln[1] = v4(b, 10, 5001, 50000, ACK, 0, 0); + b[14 + 19] = 3; + expect(split(3), 0, "different destination address"); + + /* data at the head, or second: no split */ + ln[0] = v4(a, 10, 5001, 50000, ACK, 0, 1); + ln[1] = v4(b, 10, 5001, 50000, ACK, 0, 0); + expect(split(3), 0, "data segment at the head"); + ln[0] = v4(a, 10, 5001, 50000, ACK | PSH, 0, 1460); + expect(split(32), 0, "bulk data head"); + ln[0] = v4(a, 10, 5001, 50000, ACK, 0, 0); + ln[1] = v4(b, 10, 5001, 50000, ACK, 0, 1460); + expect(split(32), 0, "data segment second"); + + /* FIN, SYN, RST, no ACK: no split */ + ln[1] = v4(b, 10, 5001, 50000, ACK, 0, 0); + ln[0] = v4(a, 10, 5001, 50000, ACK | FIN, 0, 0); + expect(split(3), 0, "ACK|FIN at the head"); + ln[0] = v4(a, 10, 5001, 50000, ACK, 0, 0); + ln[1] = v4(b, 10, 5001, 50000, ACK | FIN, 0, 0); + expect(split(3), 0, "ACK|FIN second"); + ln[0] = v4(a, 10, 5001, 50000, ACK | SYN, 0, 0); + ln[1] = v4(b, 10, 5001, 50000, ACK, 0, 0); + expect(split(3), 0, "SYN|ACK at the head"); + ln[0] = v4(a, 10, 5001, 50000, ACK | RST, 0, 0); + expect(split(3), 0, "RST|ACK at the head"); + ln[0] = v4(a, 10, 5001, 50000, 0, 0, 0); + expect(split(3), 0, "no ACK flag"); + + /* UDP, ARP, IPv4 fragment: no split */ + ln[0] = v4(a, 10, 5001, 50000, ACK, 0, 0); + a[14 + 9] = 17; + expect(split(3), 0, "UDP head"); + ln[0] = v4(a, 10, 5001, 50000, ACK, 0, 0); + a[12] = 0x08; a[13] = 0x06; + expect(split(3), 0, "ARP head"); + ln[0] = v4(a, 10, 5001, 50000, ACK, 0, 0); + a[14 + 6] = 0x20; + expect(split(3), 0, "MF fragment head"); + ln[0] = v4(a, 10, 5001, 50000, ACK, 0, 0); + a[14 + 20 + 12] = 4 << 4; /* TCP data offset 16 */ + expect(split(3), 0, "bad TCP data offset"); + /* IPv4 header options: still pure */ + ln[0] = v4(a, 10, 5001, 50000, ACK, 0, 0); + memmove(a + 14 + 24, a + 14 + 20, 20); + memset(a + 14 + 20, 1, 4); + a[14] = 0x46; a[14 + 3] = 44; ln[0] += 4; + expect(split(3), 1, "IPv4 header options"); + + /* IPv6 */ + ln[0] = v6(a, 10, 6, 5001, ACK, 0); + ln[1] = v6(b, 10, 6, 5001, ACK, 0); + expect(split(3), 1, "IPv6 ACK,ACK count 3"); + expect(split(2), 0, "IPv6 ACK,ACK count 2"); + ln[1] = v6(b, 11, 6, 5001, ACK, 0); + expect(split(3), 0, "IPv6 different source address"); + ln[1] = v6(b, 10, 6, 5002, ACK, 0); + expect(split(3), 0, "IPv6 different port"); + ln[1] = v6(b, 10, 6, 5001, ACK, 100); + expect(split(3), 0, "IPv6 data second"); + ln[1] = v6(b, 10, 6, 5001, ACK | FIN, 0); + expect(split(3), 0, "IPv6 ACK|FIN second"); + ln[1] = v6(b, 10, 0, 5001, ACK, 0); + expect(split(3), 0, "IPv6 extension header"); + /* IPv4 then IPv6: no split */ + ln[0] = v4(a, 10, 5001, 50000, ACK, 0, 0); + ln[1] = v6(b, 10, 6, 5001, ACK, 0); + expect(split(3), 0, "IPv4 then IPv6"); + + printf("RESULT pass=%d fail=%d\n", checks - failures, failures); + return failures != 0; +} From 3f9aff0fed546b9b3368dfd44a3ac7a2f584d792 Mon Sep 17 00:00:00 2001 From: claudecode Date: Mon, 28 Sep 2026 00:16:23 -0700 Subject: [PATCH 2/3] glomsplit.h: the head ACK is absent from the peer capture, loss site open A peer capture shows the head ACK missing after CMD53; it does not show whether the firmware or the air lost it. Comment only, no change in behaviour. Co-Authored-By: Claude Opus 5.5 --- src/glomsplit.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/glomsplit.h b/src/glomsplit.h index 5681f03..4f04e8b 100644 --- a/src/glomsplit.h +++ b/src/glomsplit.h @@ -3,8 +3,9 @@ * test in tests/test_glomsplit.c builds it with cc. * * In a glom of three or more frames whose first two are pure ACKs of one TCP - * flow, the firmware drops the head ACK. The driver then sends the head on - * its own and batches the rest as before. + * flow, the head ACK was absent from the peer's capture (A11: 21 of 21); + * where it is lost after CMD53 is not established. The driver sends the + * head on its own and batches the rest as before. * * Pure ACK: IPv4 (not a fragment, options allowed) or IPv6 (TCP as the first * next header), TCP with no payload, ACK set, SYN, FIN and RST clear. TCP From 29283c42ec98e9918e49b7cb31aa643f469ffc18 Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Mon, 28 Sep 2026 00:27:22 -0700 Subject: [PATCH 3/3] Send same-flow pure ACK runs in pairs, never at the head of 3+ (AmiNetXDuo #89) 517615d split only once per glom, so in a run of same-flow pure ACKs the remainder was again a glom of 3+ headed by a same-flow pair (291 of 306 changed download gloms). SendGlomDataPacket now ends a glom after its first two frames whenever they are same-flow pure ACKs and it would hold 3 or more, then starts the next glom from the third frame, so no glom of 3+ leaves with such a head pair. A run of k same-flow ACKs goes as pairs. Two-frame gloms of such a pair lost no head ACK in the peer captures (A11 0 of 44, G2 0 of 54), unlike three or more (A11 21 of 21). wifipi_glom_take() gives the frames the next transfer takes; tests/test_glomsplit.c now emits queues of 3, 4, 8 and 32 ACKs, with and without trailing data, and checks every glom (45 checks). Co-Authored-By: Claude Opus 5.5 --- src/glomsplit.h | 15 ++++++-- src/packet.c | 21 ++++------- tests/test_glomsplit.c | 79 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 17 deletions(-) diff --git a/src/glomsplit.h b/src/glomsplit.h index 4f04e8b..3271986 100644 --- a/src/glomsplit.h +++ b/src/glomsplit.h @@ -3,9 +3,10 @@ * test in tests/test_glomsplit.c builds it with cc. * * In a glom of three or more frames whose first two are pure ACKs of one TCP - * flow, the head ACK was absent from the peer's capture (A11: 21 of 21); - * where it is lost after CMD53 is not established. The driver sends the - * head on its own and batches the rest as before. + * flow, the head ACK was absent from the peer's capture (A11: 21 of 21); in + * two-frame gloms of such a pair it was not (A11 0 of 44, G2 0 of 54). Where + * it is lost after CMD53 is not established. The driver never sends such a + * glom: it ends the glom after the pair, so a same-flow ACK run goes in pairs. * * Pure ACK: IPv4 (not a fragment, options allowed) or IPv6 (TCP as the first * next header), TCP with no payload, ACK set, SYN, FIN and RST clear. TCP @@ -72,4 +73,12 @@ static inline int wifipi_glom_head_split(const unsigned char *const *frames, return 1; } +/* frames the next transfer takes from a queue of count */ +static inline unsigned long wifipi_glom_take(const unsigned char *const *frames, + const unsigned long *lens, + unsigned long count) +{ + return wifipi_glom_head_split(frames, lens, count) ? 2 : count; +} + #endif diff --git a/src/packet.c b/src/packet.c index 80fe0fa..a54d098 100644 --- a/src/packet.c +++ b/src/packet.c @@ -1859,7 +1859,7 @@ void ProcessDataPacket(struct SDIO *sdio, UBYTE *packet, ULONG packetLength) */ /* one transfer from the head of ioList; returns the frames it took */ -static UBYTE SendGlomOnce(struct SDIO *sdio, struct IOSana2Req **ioList, UBYTE count, BOOL maySplit) +static UBYTE SendGlomOnce(struct SDIO *sdio, struct IOSana2Req **ioList, UBYTE count) { struct WiFiBase *WiFiBase = sdio->s_WiFiBase; struct ExecBase *SysBase = sdio->s_SysBase; @@ -1966,16 +1966,12 @@ static UBYTE SendGlomOnce(struct SDIO *sdio, struct IOSana2Req **ioList, UBYTE c if (packetLength % 16 != 0) bug("\n"); } #endif - /* two pure ACKs of one flow at the head of 3+: the firmware drops - the first, so it goes alone; the rest is one glom as before (#89) */ - if (i == 1 && maySplit && wifipi_glom_head_split(head, headLen, count)) + /* two pure ACKs of one flow never head 3+: end the glom here, as + wifipi_glom_take() says (#89) */ + if (i == 1 && wifipi_glom_head_split(head, headLen, count)) { - struct GlomHeader *firstGh = (APTR)(byteBuffer + sizeof(struct PacketHeaderHW)); - - sdio->s_TXSeq--; - firstGh->gh_LastItem = 1; - count = 1; - break; + gh->gh_LastItem = 1; + count = 2; } // Increase total length by packet length (aligned) @@ -2025,13 +2021,10 @@ static UBYTE SendGlomOnce(struct SDIO *sdio, struct IOSana2Req **ioList, UBYTE c int SendGlomDataPacket(struct SDIO *sdio, struct IOSana2Req **ioList, UBYTE count) { - BOOL maySplit = TRUE; - while (count) { - UBYTE n = SendGlomOnce(sdio, ioList, count, maySplit); + UBYTE n = SendGlomOnce(sdio, ioList, count); - maySplit = FALSE; ioList += n; count -= n; } diff --git a/tests/test_glomsplit.c b/tests/test_glomsplit.c index 5f24680..f0e4dde 100644 --- a/tests/test_glomsplit.c +++ b/tests/test_glomsplit.c @@ -67,6 +67,83 @@ static unsigned long ln[2]; static int split(unsigned long count) { return wifipi_glom_head_split(fr, ln, count); } +/* a queue as the driver sends it: glom sizes into sizes[], returns the count */ +#define MAXQ 33 +static unsigned char q[MAXQ][192]; +static const unsigned char *qf[MAXQ]; +static unsigned long ql[MAXQ]; + +static int emit(unsigned long n, unsigned long *sizes) +{ + unsigned long h = 0; + int k = 0; + + while (h < n) + { + unsigned long take = wifipi_glom_take(qf + h, ql + h, n - h); + + /* nothing of 3+ leaves with a same-flow pure-ACK head pair */ + if (take >= 3 && wifipi_glom_head_split(qf + h, ql + h, take)) return -1; + sizes[k++] = take; + h += take; + } + return k; +} + +/* acks same-flow pure ACKs, then tail frames of data (1) or UDP (2) */ +static void queue(unsigned long acks, unsigned long tail, int kind) +{ + unsigned long i; + + for (i = 0; i < acks + tail; i++) + { + ql[i] = v4(q[i], 10, 5001, 50000, ACK, 0, i < acks ? 0 : 100); + if (i >= acks && kind == 2) q[i][14 + 9] = 17; + qf[i] = q[i]; + } +} + +static void expect_emit(unsigned long n, const unsigned long *want, int wantk, const char *what) +{ + unsigned long got[MAXQ]; + int k = emit(n, got), i, same = k == wantk; + + for (i = 0; same && i < k; i++) same = got[i] == want[i]; + expect(same, 1, what); + if (!same) + { + printf(" got"); + for (i = 0; i < k; i++) printf(" %lu", got[i]); + printf(" (k=%d)\n", k); + } +} + +static void runs(void) +{ + static const unsigned long r3[] = { 2, 1 }, r4[] = { 2, 2 }, r8[] = { 2, 2, 2, 2 }; + static const unsigned long r3d[] = { 2, 2 }, r4d[] = { 2, 2, 1 }, r2d[] = { 2, 1 }; + static const unsigned long d32[] = { 32 }, one2[] = { 2 }, a1d[] = { 3 }; + unsigned long r32[16], r32d[17], mix[] = { 2, 31 }; + int i; + + for (i = 0; i < 16; i++) r32[i] = r32d[i] = 2; + r32d[16] = 1; + + queue(3, 0, 0); expect_emit(3, r3, 2, "run 3: 2+1"); + queue(4, 0, 0); expect_emit(4, r4, 2, "run 4: 2+2"); + queue(8, 0, 0); expect_emit(8, r8, 4, "run 8: 4 pairs"); + queue(32, 0, 0); expect_emit(32, r32, 16, "run 32: 16 pairs"); + queue(3, 1, 1); expect_emit(4, r3d, 2, "run 3 + data: 2+2"); + queue(4, 1, 1); expect_emit(5, r4d, 3, "run 4 + data: 2+2+1"); + queue(32, 1, 1); expect_emit(33, r32d, 17, "run 32 + data: 16 pairs + 1"); + queue(2, 1, 2); expect_emit(3, r2d, 2, "2 ACKs + UDP: 2+1"); + queue(2, 0, 0); expect_emit(2, one2, 1, "2 ACKs: one glom of 2"); + queue(1, 2, 1); expect_emit(3, a1d, 1, "ACK + 2 data: one glom of 3"); + queue(0, 32, 1); expect_emit(32, d32, 1, "32 data frames: one glom of 32"); + /* 2 ACKs, then 31 data frames: the pair goes alone, the bulk stays whole */ + queue(2, 31, 1); expect_emit(33, mix, 2, "2 ACKs + 31 data: 2+31"); +} + int main(void) { /* two same-flow pure ACKs heading 3 and 32 frames: split */ @@ -169,6 +246,8 @@ int main(void) ln[1] = v6(b, 10, 6, 5001, ACK, 0); expect(split(3), 0, "IPv4 then IPv6"); + runs(); + printf("RESULT pass=%d fail=%d\n", checks - failures, failures); return failures != 0; }