BUG27209: handle trimming of segments when out of window or out of
order properly
diff --git a/CHANGELOG b/CHANGELOG
index 18b7d6c..a457650 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -115,6 +115,9 @@
 
 
   ++ Bugfixes:
+  2009-08-12 Kieran Mansley
+  * tcp_in.c, tcp.c: Fix bug #27209: handle trimming of segments when
+    out of window or out of order properly
 
   2009-08-12 Kieran Mansley
   * tcp_in.c: Fix bug #27199: use snd_wl2 instead of snd_wl1
diff --git a/src/core/tcp.c b/src/core/tcp.c
index bd16545..0f3fd41 100644
--- a/src/core/tcp.c
+++ b/src/core/tcp.c
@@ -49,6 +49,7 @@
 #include "lwip/memp.h"
 #include "lwip/snmp.h"
 #include "lwip/tcp.h"
+#include "lwip/debug.h"
 
 #include <string.h>
 
@@ -423,6 +424,9 @@
 {
   int wnd_inflation;
 
+  LWIP_ASSERT("tcp_recved: len would wrap rcv_wnd\n",
+              len <= 0xffff - pcb->rcv_wnd );
+
   pcb->rcv_wnd += len;
   if (pcb->rcv_wnd > TCP_WND)
     pcb->rcv_wnd = TCP_WND;
diff --git a/src/core/tcp_in.c b/src/core/tcp_in.c
index 235a68a..362a4a6 100644
--- a/src/core/tcp_in.c
+++ b/src/core/tcp_in.c
@@ -1055,35 +1055,73 @@
         /* The incoming segment is the next in sequence. We check if
            we have to trim the end of the segment and update rcv_nxt
            and pass the data to the application. */
+        tcplen = TCP_TCPLEN(&inseg);
+
+        if (tcplen > pcb->rcv_wnd) {
+          LWIP_DEBUGF(TCP_INPUT_DEBUG, 
+                      ("tcp_receive: other end overran receive window"
+                       "seqno %"U32_F" len %"U32_F" right edge %"U32_F"\n",
+                       seqno, tcplen, pcb->rcv_nxt + pcb->rcv_wnd));
+          if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {
+            /* Must remove the FIN from the header as we're trimming 
+             * that byte of sequence-space from the packet */
+            TCPH_FLAGS_SET(inseg.tcphdr, TCPH_FLAGS(inseg.tcphdr) &~ TCP_FIN);
+          }
+          /* Adjust length of segment to fit in the window. */
+          inseg.len = pcb->rcv_wnd;
+          if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) {
+            inseg.len -= 1;
+          }
+          pbuf_realloc(inseg.p, inseg.len);
+          tcplen = TCP_TCPLEN(&inseg);
+          LWIP_ASSERT("tcp_receive: segment not trimmed correctly to rcv_wnd\n",
+                      (seqno + tcplen) == (pcb->rcv_nxt + pcb->rcv_wnd));
+        }
 #if TCP_QUEUE_OOSEQ
-        if (pcb->ooseq != NULL &&
-                TCP_SEQ_LEQ(pcb->ooseq->tcphdr->seqno, seqno + inseg.len)) {
-          if (pcb->ooseq->len > 0) {
-            /* We have to trim the second edge of the incoming
-               segment. */
-            inseg.len = (u16_t)(pcb->ooseq->tcphdr->seqno - seqno);
-            pbuf_realloc(inseg.p, inseg.len);
-          } else {
-            /* does the ooseq segment contain only flags that are in inseg also? */
-            if ((TCPH_FLAGS(inseg.tcphdr) & (TCP_FIN|TCP_SYN)) ==
-                (TCPH_FLAGS(pcb->ooseq->tcphdr) & (TCP_FIN|TCP_SYN))) {
+        if (pcb->ooseq != NULL) {
+          if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {
+            LWIP_DEBUGF(TCP_INPUT_DEBUG, 
+                        ("tcp_receive: received in-order FIN, binning ooseq queue\n"));
+            /* Received in-order FIN means anything that was received
+             * out of order must now have been received in-order, so
+             * bin the ooseq queue */
+            while (pcb->ooseq != NULL) {
               struct tcp_seg *old_ooseq = pcb->ooseq;
               pcb->ooseq = pcb->ooseq->next;
               memp_free(MEMP_TCP_SEG, old_ooseq);
+            }               
+          } else if (TCP_SEQ_LEQ(pcb->ooseq->tcphdr->seqno, seqno + tcplen)) {
+            if (pcb->ooseq->len > 0) {
+              /* We have to trim the second edge of the incoming segment. */
+              LWIP_ASSERT("tcp_receive: trimmed segment would have zero length\n",
+                          TCP_SEQ_GT(pcb->ooseq->tcphdr->seqno, seqno));
+              /* FIN in inseg already handled by dropping whole ooseq queue */
+              inseg.len = (u16_t)(pcb->ooseq->tcphdr->seqno - seqno);
+              if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) {
+                inseg.len -= 1;
+              }
+              pbuf_realloc(inseg.p, inseg.len);
+              tcplen = TCP_TCPLEN(&inseg);
+              LWIP_ASSERT("tcp_receive: segment not trimmed correctly to ooseq queue\n",
+                          (seqno + tcplen) == pcb->ooseq->tcphdr->seqno);
+            } else {
+              /* does the ooseq segment contain only flags that are in inseg also? */
+              if ((TCPH_FLAGS(inseg.tcphdr) & (TCP_FIN|TCP_SYN)) ==
+                  (TCPH_FLAGS(pcb->ooseq->tcphdr) & (TCP_FIN|TCP_SYN))) {
+                struct tcp_seg *old_ooseq = pcb->ooseq;
+                pcb->ooseq = pcb->ooseq->next;
+                memp_free(MEMP_TCP_SEG, old_ooseq);
+              }
             }
           }
         }
 #endif /* TCP_QUEUE_OOSEQ */
 
-        tcplen = TCP_TCPLEN(&inseg);
         pcb->rcv_nxt = seqno + tcplen;
 
         /* Update the receiver's (our) window. */
-        if (pcb->rcv_wnd < tcplen) {
-          pcb->rcv_wnd = 0;
-        } else {
-          pcb->rcv_wnd -= tcplen;
-        }
+        LWIP_ASSERT("tcp_receive: tcplen > rcv_wnd\n", pcb->rcv_wnd >= tcplen);
+        pcb->rcv_wnd -= tcplen;
 
         tcp_update_rcv_ann_wnd(pcb);
 
@@ -1118,11 +1156,9 @@
           seqno = pcb->ooseq->tcphdr->seqno;
 
           pcb->rcv_nxt += TCP_TCPLEN(cseg);
-          if (pcb->rcv_wnd < TCP_TCPLEN(cseg)) {
-            pcb->rcv_wnd = 0;
-          } else {
-            pcb->rcv_wnd -= TCP_TCPLEN(cseg);
-          }
+          LWIP_ASSERT("tcp_receive: ooseq tcplen > rcv_wnd\n",
+                      pcb->rcv_wnd >= TCP_TCPLEN(cseg));
+          pcb->rcv_wnd -= TCP_TCPLEN(cseg);
 
           tcp_update_rcv_ann_wnd(pcb);