! TCP (RFC 793) module for INet process, GDMR, Feb .. May 1987
! Revised version Feb. 1988 includes (will include):
!     deferred ACK, in case it can be piggy-backed
!     RTT as per Jacobson
! ?   congestion avoidance, as per Jacobson
! To do: process PSH and URG

! Optional features:
!     byte-swapping for network order ({N}/!N!)
!     segment tracing ({T}/!T!)

%option "-NonStandard-NoCheck-NoTrace-NoDiag-NoStack-NoLine"

%include "INet:Formats.Inc"
%include "INet:Utility.Inc"
%include "INet:INet.Inc"
%include "INet:Stats.Inc"
%include "INet:Lights.Inc"

%externalrecord(INet statistics fm)%spec stats

%externalroutinespec IP outbound(%record(buffer fm)%name b) 

%externalrecord(queue fm)%spec TCP inbound            queue
%externalrecord(queue fm)%spec TCP outbound           queue
%externalrecord(queue fm)%spec TCP packet delivered   queue

%externalpredicatespec TCP send to user(%record(buffer fm)%name b)
%externalroutinespec TCP send close(%integer unit)
%externalroutinespec TCP open response(%integer unit, error, ra, rp, lp)
%externalroutinespec TCP claim response(%integer unit, error, allocated)
%externalroutinespec TCP enable(%integer unit)
%externalroutinespec TCP disable(%integer unit, error)
%externalroutinespec TCP block(%integer unit)
%externalroutinespec TCP unblock(%integer unit)
%externalpredicatespec TCP interpret user request(%record(buffer fm)%name b)

%externalintegerfnspec IP source for(%integer d)

%externalintegerspec max TCP

%systemroutinespec phex(%integer x)

%externalrecord(TCB table fm)%namespec TCB table

%constinteger ACK threshold = 8
%constinteger TCP retransmit limit = 8

%constinteger TCP privileged limit = 1024

%owninteger TCP last privileged    = TCP privileged limit
%owninteger TCP last unprivileged  = TCP privileged limit

%owninteger TCP tag = 0

%routine print TCB address(%record(TCB fm)%name t)
   print inet address(t_remote address)
   print symbol('.');  write(t_remote port, 0)
   print symbol('/');  write(t_local port, 0)
%end

!! %routine show TCP header(%record(TCP header fm)%name h,
!!                          %integer n, %string(15) s, %integer tag)
!!    printstring("TCP ");  printstring(s);  space;  write(tag, 0)
!!    printstring(" -- source: ");  write(h_source, 0)
!!    printstring(", dest: ");  write(h_destination, 0)
!!    printstring(", seq: ");  phex(h_seq)
!!    printstring(", ack: ");  phex(h_ack)
!!    printstring(" FIN") %if h_flags & FIN bit # 0
!!    printstring(" SYN") %if h_flags & SYN bit # 0
!!    printstring(" RST") %if h_flags & RST bit # 0
!!    printstring(" PSH") %if h_flags & PSH bit # 0
!!    printstring(" ACK") %if h_flags & ACK bit # 0
!!    printstring(" URG") %if h_flags & URG bit # 0
!!    %if n # 0 %start
!!       printstring(" + ");  write(n, 0)
!!    %finish
!!    newline
!!    printstring("Window: ");  write(h_window, 0)
!!    printstring(", urgent: ") %and write(h_urgent, 0) %if h_flags & URG bit # 0
!!    newline
!!    %if (h_data offset >> 4) & 15 > 5 %start
!!       ! Options
!!       printstring("Options: ");  phex(h_options);  newline
!!    %finish
!! %end

!! %routine show TCB(%record(TCB fm)%name TCB)
!!    %integer now, offset
!!       %return %if TCB == nil
!!       %if TCB_local port = 0 %start
!!          printstring("*free*");  newline
!!          %return
!!       %finish
!!       print inet address(TCB_remote address);  print symbol('.')
!!       write(TCB_remote port, 0);  printstring(" <--> ")
!!       write(TCB_local port, 0);  newline
!!       printstring("State ");  printstring(TCP state name(TCB_state))
!!       printstring(", from ");  printstring(TCP state name(TCB_previous state))
!!       now = msecs timestamp
!!       offset = now - TCB_state change stamp
!!       printstring(", changed ")
!!       %if offset >= 0 %start
!!          write(offset, 0);  printstring(" msec")
!!          print symbol('s') %if offset # 1
!!       %else
!!          printstring("ages")
!!       %finish
!!       printstring(" ago");  newline
!!       printstring("Snd nxt: ");  phex(TCB_snd nxt)
!!       printstring(", ISS: ");  phex(TCB_iss)
!!       printstring(", sent: ");  write(TCB_snd nxt - TCB_iss, 0)
!!       printstring(", una: ");  phex(TCB_snd una)
!!       printstring(", window: ");  write(TCB_snd wnd, 0);  newline
!!       printstring("Rcv nxt: ");  phex(TCB_rcv nxt)
!!       printstring(", IRS: ");  phex(TCB_irs)
!!       printstring(", received: ");  write(TCB_rcv nxt - TCB_irs, 0)
!!       printstring(", window: ");  write(TCB_rcv wnd, 0);  newline
!! %end


! TCP tracing.  Maintain a circular list of the last "few" TCP
! operations as an aid to debugging.

!T!   %constinteger max TCP trace = 127;  ! Suitable for masking
!T!   %conststring(31) TCP trace name = "INET__TCP_TRACE_BUFFER"
!T!
!T!   %constinteger TCP trace data  = 96
!T!   %constinteger TCP trace in    = 1
!T!   %constinteger TCP trace out   = 2
!T!   %constinteger TCP trace reTX  = 3
!T!   %constinteger TCP trace check = 4
!T!   %constinteger TCP trace ACK   = 5
!T!
!T!   %recordformat TCP trace fm(%integer inout, data bytes,
!T!                              %record(TCB fm) TCB,
!T!                              %record(TCP header fm) TCP header,
!T!                              %bytearray data(1 : TCP trace data))
!T!   %constinteger TCP trace size = 4 + 4 + TCB size + %c
!T!                                  TCP header size + 4 {options} + TCP trace data
!T!
!T!   %recordformat TCP trace buffer fm(%integer next,
!T!                                     %record(TCP trace fm)%array t(0 : max TCP trace))
!T!
!T!   %ownrecord(TCP trace buffer fm)%name TCP trace buffer == nil
!T!
!T!   %routine TCP trace(%record(buffer fm)%name b, %integer inout)
!T!      ! Buffer is sufficient, as we can find everything else of interest
!T!      ! from there.
!T!      %record(TCP trace fm)%name t
!T!      %integer data copy, i
!T!      %bytename x
!T!         %if TCP trace buffer == nil %start
!T!            ! Must initialise
!T!            TCP trace buffer == %c
!T!               named heap get(TCP trace size * (max TCP trace + 1) + 4,
!T!                              TCP trace name)
!T!            !! printstring("Trace buffer initialised at ")
!T!            !! phex(addr(TCP trace buffer));  newline
!T!         %finish
!T!         !! printstring("TCP trace: ");  phex(addr(b));  newline
!T!         t == TCP trace buffer_t(TCP trace buffer_next & max TCP trace)
!T!         TCP trace buffer_next = TCP trace buffer_next + 1
!T!         t_inout = inout
!T!         %if b_TCB == nil %then t_TCB = 0 %else t_TCB = b_TCB
!T!         t_TCP header = b_TCP header
!T!         t_data bytes = b_data bytes
!T!         data copy = b_data bytes
!T!         data copy = TCP trace data %if data copy > TCP trace data
!T!         %if data copy > 0 %start
!T!            x == b_data start
!T!            %for i = 1, 1, data copy %cycle
!T!               t_data(i) = x
!T!               x == x [1]
!T!            %repeat
!T!         %finish
!T!   %end


! TCB module

%record(TCB fm)%map find TCB(%integer remote address, remote port, local port)
   ! Try for an exact match if possible.  If not, try for a wild match.
   %record(TCB fm)%name t, possible == nil
   %integer i
      !! printstring("Looking for TCB for ");  print inet address(remote address)
      !! write(remote port, 1);  write(local port, 1);  newline
      %for i = 1, 1, max TCP %cycle
         t == TCB table_TCB(i)
         !! printstring("Trying ");  print inet address(t_remote address)
         !! write(t_remote port, 1);  write(t_local port, 1);  newline
         %if t_local port = local port %start
            t_slot = i;  ! Just in case!
            %result == t %if t_remote address = remote address %c
                        %and t_remote port = remote port
            possible == t %if t_remote address = 0
         %finish
      %repeat
      %result == possible
%end

%routine setup TCB(%record(TCB fm)%name t)
   %integer slot
      %signal 13, t_slot %if t_local port # 0;  ! Disaster 
      slot = t_slot;  t = 0;  t_slot = slot
      initialise queue(t_blocked send queue)
      initialise queue(t_retransmit queue)
      initialise queue(t_reordering queue)
      initialise queue(t_state change queue)
      t_avg = TCP initial RTT << 3
      t_mdev = TCP initial RTT;  !??
      t_rto = TCP initial RTT
      t_rcv wnd = TCP initial receive window
%end

%routine flush TCB queues(%record(TCB fm)%name TCB)
   purge queue(TCB_retransmit queue)
   purge queue(TCB_reordering queue)
   purge queue(TCB_state change queue)
   purge queue(TCB_blocked send queue)
%end

%routine TCP new state(%record(TCB fm)%name t, %integer new state)
   %record(buffer fm)%name b
      %signal 13, new state %unless 0 < new state <= TCP time wait
      !! printstring("TCP state change: ");  printstring(TCP state name(t_state))
      !! printstring(" -> ");  printstring(TCP state name(new state));  newline
      t_previous state = t_state
      t_state = new state
      t_state change stamp = msecs timestamp
      %cycle
         b == dequeue buffer(t_state change queue)
         %exit %if b == nil
         %signal 13 %if b_next queue == nil
         enqueue buffer(b, b_next queue)
      %repeat
%end

%routine TCP print state(%integer state)
   %if TCP listen <= state <= TCP time wait %start
      printstring(TCP state name(state))
   %else
      write(state, 0)
   %finish
%end

%routine delete TCB(%record(TCB fm)%name TCB, %integer status)
   %if TCB_local port = 0 %start
      ! Not opened.  Must have been a packet still in transit?
      pdate
      printstring("Attempting to delete non-open TCB at ")
      phex(addr(TCB))
      printstring(", user status would be ")
      write(status, 0);  printstring(" (")
      phex(status);  print symbol(')');  newline
      %return
   %finish
   !! printstring("Delete TCB: state ");  TCP print state(TCB_state);  newline
   TCP disable(TCB_slot, status)
   flush TCB queues(TCB)
   TCP enable(TCB_slot)
   TCB = 0
%end


! TCP processing proper....

%routine TCP remove acknowledged(%record(TCB fm)%name TCB)
   %record(buffer fm)%name b
   %integer meas
      !! printstring("Remove acknowledged: ");  phex(TCB_snd una);  newline
      %cycle
         b == dequeue buffer(TCB_retransmit queue)
         %return %if b == nil;  ! No more pending
         %if b_seq - TCB_snd una > 0 %start
            ! Not ACKed yet -- put it back again
            !! printstring("Not ACKed: ");  phex(b_seq);  newline
            requeue buffer(b, TCB_retransmit queue)
            %return
         %finish
         ! RTT calculation, as per Jacobsen
         meas = msecs timestamp - b_stamp
         meas = meas - (TCB_avg >> 3)
         TCB_avg = TCB_avg + meas
         meas = -meas %if meas < 0
         meas = meas - (TCB_mdev >> 2)
         TCB_mdev = TCB_mdev + meas
         TCB_rto = (TCB_avg >> 3) + (TCB_mdev >> 1)
         ! End RTT calculation
   !T!   TCP trace(b, TCP trace ACK)
         release buffer(b)
      %repeat
%end

%routine TCP transmit segment(%record(buffer fm)%name b,
                              %integer options, retransmit)
   ! Calculate the checksum, convert to net order, forward to IP
   %integer c, header size
      b_seq <- b_TCP header_seq + b_data bytes
      b_seq <- b_seq + 1 %if b_TCP header_flags & SYN bit # 0
      b_seq <- b_seq + 1 %if b_TCP header_flags & FIN bit # 0
      ! ACKed when snd una >= _seq
      b_stamp = msecs timestamp
      %if b_TCB == nil %start
         ! No context at this end, use default
         b_interval = TCP initial RTT << 2
         ! (it probably won't be used anyway!)
      %else
         b_interval = b_TCB_rto
         %if b_interval < TCP reTX lbound %start
            b_interval = TCP reTX lbound
         %else %if b_interval > TCP reTX ubound
            b_interval = TCP reTX ubound
         %finish
      %finish
      b_life = 1
      %if options = 0 %then header size = 16_50 %else header size = 16_60
      b_TCP header_data offset = header size
      b_IP bytes = b_data bytes + 4 * (header size >> 4)
      b_protocol = IP TCP protocol
!T!   TCP trace(b, TCP trace out)
      !! show TCP header(b_TCP header, b_data bytes, "transmit", b_tag)
!N!   net order short(b_TCP header_source)
!N!   net order short(b_TCP header_destination)
!N!   net order long(b_TCP header_seq)
!N!   net order long(b_TCP header_ack)
!N!   net order short(b_TCP header_window)
!N!   net order short(b_TCP header_checksum)
!N!   net order short(b_TCP header_urgent)
!N!   ! Options (if any) assumed to be already in net order
      b_TCP header_checksum = 0;  ! Initially
      c = calculate pseudo checksum(b_TCP header, b_IP bytes,
                                    IP source for(b_IP peer), b_IP peer,
                                    IP TCP protocol, b_IP bytes)
      b_TCP header_checksum <- \c
      b_TCP header_checksum <- 16_FFFF %if b_TCP header_checksum = 0;  ! -0 safer
      %if retransmit = 0 %start
         ! Don't want to retransmit this one
         !! printstring("Don't retransmit: ");  phex(b_seq);  newline
         b_next queue == nil
      %else
         ! Must retransmit this one
         !! printstring("-> retransmit queue: ");  phex(b_seq);  newline
         b_next queue == b_TCB_retransmit queue
         b_flags = 0;  !<<<<<<<<<<<
      %finish
      stats_TCP packets out = stats_TCP packets out + 1
      stats_TCP bytes out = stats_TCP bytes out + b_IP bytes
      IP outbound(b)
%end
   
%routine TCP return reset(%record(buffer fm)%name b, %integer ACK in)
   ! If ACK in = 0 we send 0 as seq, together with a reasonable ACK,
   ! otherwise we echo ACK as seq and send no ACK
   %integer ack value
   %short s
      s = b_TCP header_source;  b_TCP header_source = b_TCP header_destination
      b_TCP header_destination = s
      %if ACK in = 0 %start
         ack value <- b_TCP header_seq + b_data bytes
         ack value <- ack value + 1 %if b_TCP header_flags & SYN bit # 0
         ack value <- ack value + 1 %if b_TCP header_flags & FIN bit # 0
         b_TCP header_ack = ack value
         b_TCP header_seq = 0
         b_TCP header_flags = RST bit ! ACK bit
      %else
         b_TCP header_seq = b_TCP header_ack
         b_TCP header_ACK = 0
         b_TCP header_flags = RST bit
      %finish
      b_data bytes = 0
      TCP transmit segment(b, 0, 0)
%end

%routine TCP send ACK(%record(TCB fm)%name TCB)
   %record(buffer fm)%name b
      !! printstring("Send ACK ");  phex(TCB_rcv nxt)
      !! printstring(" for TCB ");  phex(addr(TCB));  newline
      b == claim buffer
      b_IP peer = TCB_remote address
      b_TCB == TCB
      b_data bytes = 0
      b_TCP header == record(addr(b_data(64)))
      b_TCP header_source = TCB_local port
      b_TCP header_destination = TCB_remote port
      b_TCP header_seq <- TCB_snd nxt
      b_TCP header_ack <- TCB_rcv nxt
      b_TCP header_flags = ACK bit
      b_TCP header_window = TCB_rcv wnd
      TCP transmit segment(b, 0, 0)
      TCB_ACK pending = 0
%end

%routine TCP send window updated(%record(TCB fm)%name TCB)
   %record(buffer fm)%name b
      !! printstring("Send window updated: now ")
      !! write(TCB_snd wnd, 0);  newline
      %cycle
         ! This will probably only ever go round the loop once.  However, it
         ! is just possible that the user will send us another bufferload
         ! before we got round to inhibiting the unit, in which case we
         ! might just have several buffers queued here....  Note that we don't
         ! dequeue the buffer until we're sure it can be sent.
         b == record(addr(TCB_blocked send queue_forward))
         %exit %if b == record(addr(TCB_blocked send queue));  ! Empty
         ! Is the window open enough?
         ! (snd nxt + data <= snd una + snd wnd)
         %return %if TCB_snd nxt + b_data bytes - TCB_snd una - TCB_snd wnd > 0
         !! printstring("Send window opened for ")
         !! write(b_data bytes, 0);  newline
         b == dequeue buffer(TCB_blocked send queue)
         b_IP peer = TCB_remote address
         stats_TCP data bytes sent = stats_TCP data bytes sent + b_data bytes
         b_TCP header == record(addr(b_data start) - TCP header size)
         b_TCP header_source = TCB_local port
         b_TCP header_destination = TCB_remote port
         b_TCP header_seq <- TCB_snd nxt
         b_TCP header_ack <- TCB_rcv nxt
         b_TCP header_window = TCB_rcv wnd
         TCB_ACK pending = 0
         %if b_code = TCP send request %start
            b_TCP header_flags = ACK bit ! PSH bit
            TCB_snd nxt <- TCB_snd nxt + b_data bytes
            TCB_snd wnd = TCB_snd wnd - b_data bytes
         %else
            b_TCP header_flags = ACK bit ! FIN bit
            b_data bytes = 0;  ! Unfrig
            TCB_FIN seq <- b_TCP header_seq
            TCB_snd nxt <- TCB_snd nxt + 1
            TCB_snd wnd = TCB_snd wnd - 1
            %if TCB_state = TCP close wait %then TCP new state(TCB, TCP last ack) %c
                                           %else TCP new state(TCB, TCP fin wait 1)
         %finish
         TCP transmit segment(b, 0, 1)
      %repeat
      %if TCB_blocked send # 0 %start
         !! printstring("Unblock unit ");  write(TCB_slot, 0);  newline
         TCP unblock(TCB_slot)
         TCB_blocked send = 0
      %finish
%end

%externalroutine TCP inbound(%record(buffer fm)%name b)
   ! Initial TCP segment validation and preliminary processing
   %record(TCB fm)%name TCB
   %record(buffer fm)%name x
   %integer length, middle, rhs, diff, c, header size
   %short ss
   %switch second(TCP listen : TCP time wait)

!L!   lights or A(TCP light)
      b_next queue == nil
      -> sixth %if b_flags & do sixth # 0
      -> first %if b_flags & do first # 0
      !! printstring("TCP inbound");  newline
      !! dump(byteinteger(addr(b_TCP header)), b_IP bytes)

      c = calculate pseudo checksum(b_TCP header, b_IP bytes,
                                    b_IP peer, IP source for(b_IP peer),
                                    IP TCP protocol, b_IP bytes)
      %if 0 # c # 16_FFFF %start
         ! Invalid checksum (not +/- 0)
         !! pdate
         !! printstring("Invalid TCP checksum ")
         !! write(c, 0);  space;  phex(c);  printstring(" from ")
         !! print inet address(b_IP peer);  newline
         !! phex(b_IP bytes);  space;  phex(b_IP peer)
         !! space;  phex(our address);  space;  phex(IP TCP protocol);  newline
         !! dump(byteinteger(addr(b_TCP header)), b_IP bytes)
         stats_TCP checksum errors = stats_TCP checksum errors + 1
         b_TCB == nil
!T!      TCP trace(b, TCP trace check)
         release buffer(b)
         -> out
      %finish
!N!   net order short(b_TCP header_source)
!N!   net order short(b_TCP header_destination)
!N!   net order long(b_TCP header_seq)
!N!   net order long(b_TCP header_ack)
!N!   net order short(b_TCP header_window)
!N!   net order short(b_TCP header_checksum)
!N!   net order short(b_TCP header_urgent)
      header size = b_TCP header_data offset >> 2;  ! >> 4, then << 2
      b_data bytes = b_IP bytes - header size
      %if b_data bytes < 0 %start
         ! Something very wrong with this packet.  Drop it
         release buffer(b)
         -> out
      %finish
      b_data start == byteinteger(addr(b_TCP header) + header size)
      !! printstring("Header size ");  write(header size, 0)
      !! printstring(", data bytes ");  write(b_data bytes, 0);  newline
      TCP tag <- TCP tag + 1;  b_tag = TCP tag
      !! show TCP header(b_TCP header, b_data bytes, "inbound", b_tag)
      stats_TCP packets in = stats_TCP packets in + 1
      stats_TCP bytes in = stats_TCP bytes in + b_IP bytes
      b_TCB == find TCB(b_IP peer, b_TCP header_source, b_TCP header_destination)
!T!   TCP trace(b, TCP trace in)
      %if b_TCB == nil %start
         !! printstring("TCP for closed socket: from ")
         !! print inet address(b_IP peer)
         !! print symbol('.');  write(b_TCP header_source, 0)
         !! printstring(" for ");  write(b_TCP header_destination, 0)
         !! newline
         stats_TCP for closed = stats_TCP for closed + 1
         %if b_TCP header_flags & RST bit = 0 %start
            TCP return reset(b, b_TCP header_flags & ACK bit)
         %else
            release buffer(b)
         %finish
         -> out
      %finish
      !! show TCB(b_TCB)
      %if b_TCB_state = TCP listen %start
         ! First, check for RST
         %if b_TCP header_flags & RST bit # 0 %start
            !! printstring("TCP (listen): ignoring reset from ")
            !! print inet address(b_IP peer);  newline
            stats_TCP resets received = stats_TCP resets received + 1
            release buffer(b)
            -> out
         %finish
         ! Second, check for ACK
         %if b_TCP header_flags & ACK bit # 0 %start
            !! printstring("TCP (listen): resetting ACK from ")
            !! print inet address(b_IP peer);  newline
            stats_TCP junk received = stats_TCP junk received + 1
            TCP return reset(b, 1)
            -> out
         %finish
         ! Third, check for SYN
         %if b_TCP header_flags & SYN bit # 0 %start
            ! Should check security/precedence here.
            stats_TCP SYNs received = stats_TCP SYNs received + 1
            b_TCB_rcv nxt <- b_TCP header_seq + 1
            b_TCB_IRS <- b_TCP header_seq
            %if b_data bytes > 0 %start
               ! Some data included with this segment
{>>>>>>>}      x == claim buffer;  copy headers(b, x);  x_data bytes = 0
               ! Now queue anything else in b for later
               b_TCP header_seq <- b_TCP header_seq + 1
               b_TCP header_flags = b_TCP header_flags & (\ SYN bit)
               b_flags = ignore seq ! ignore SYN ! ignore ACK ! do first
            %else
               ! No data, so just use the original
               x == b;  b == nil
            %finish
            ! Select an ISS and send a SYN ACK
            x_TCB_ISS <- generate ISS
            x_TCB_snd nxt <- x_TCB_ISS + 1
            x_TCB_snd una <- x_TCB_ISS
            !! printstring("ISS ");  phex(x_TCB_ISS)
            !! printstring(" for ");  print inet address(x_IP peer)
            !! print symbol('.');  write(x_TCP header_source, 0)
            !! print symbol('/');  write(x_TCP header_destination, 0);  newline
            TCP new state(x_TCB, TCP syn received)
            x_TCB_remote address = x_IP peer
            x_TCB_remote port = x_TCP header_source
            x_TCP header_source = x_TCP header_destination
            x_TCP header_destination = x_TCB_remote port
            x_TCP header_seq <- x_TCB_ISS
            x_TCP header_ack <- x_TCB_rcv nxt
            x_TCP header_window = x_TCB_rcv wnd
            x_TCP header_flags = SYN bit ! ACK bit
            byteinteger (addr(x_TCP header_options)    ) = 2;  ! Define max segment
            byteinteger (addr(x_TCP header_options) + 1) = 4;  ! Size of ^
            shortinteger(addr(x_TCP header_options) + 2) <- x_TCB_mrss
      !N!   net order short(shortinteger(addr(x_TCP header_options) + 2))
            x_data bytes = 0
            TCP transmit segment(x, 1, 1)
            ! Now process the segment data, if any....
            TCP inbound(b) %if b ## nil   
            -> out
         %finish
         ! Else, something odd
         !! pdate
         !! printstring("TCP (listen): odd packet from ")
         !! print inet address(b_IP peer);  newline
         stats_TCP junk received = stats_TCP junk received + 1
         release buffer(b)
         -> out
      %else %if b_TCB_state = TCP syn sent
         ! First, check the ACK
         %if b_TCP header_flags & ACK bit # 0 %start
            %if b_TCP header_ack - b_TCB_ISS <= 0 %c
                  %or b_TCP header_ack - b_TCB_snd nxt > 0 %start
               stats_TCP dud ACKs received = stats_TCP dud ACKs received + 1
               %if b_TCP header_flags & RST bit = 0 %start
                  TCP return reset(b, 1)
               %else
                  release buffer(b)
               %finish
               -> out
            %finish
            %if b_TCB_snd una - b_TCP header_ack <= 0 %c
                  %and b_TCP header_ack - b_TCB_snd nxt <= 0 %start
               stats_TCP good ACKs received = stats_TCP good ACKs received + 1
               b_acceptable = 1
            %finish
         %finish
         ! Second, check the RST bit
         %if b_TCP header_flags & RST bit # 0 %start
            stats_TCP resets received = stats_TCP resets received + 1
            %if b_acceptable # 0 %start
               delete TCB(b_TCB, reset error)
               release buffer(b)
            %else
               !! pdate
               !! printstring("TCP (syn sent): unacceptable ACK from ")
               !! print TCB address(b_TCB);  newline
               release buffer(b)
            %finish
            -> out
         %finish
         ! Third, check security/precedence (but we don't bother)
         ! Fourth, check the SYN bit
         %if b_TCP header_flags & SYN bit # 0 %start
            stats_TCP SYNs received = stats_TCP SYNs received + 1
            b_TCB_rcv nxt <- b_TCP header_seq + 1
            b_TCB_IRS <- b_TCP header_seq
            %if b_TCP header_flags & ACK bit # 0 %start
               b_TCB_snd una <- b_TCP header_ack
               TCP remove acknowledged(b_TCB)
            %finish
            %if b_TCB_snd una - b_TCB_ISS > 0 %start
               ! Our SYN has been ACKed
               stats_TCP connections established = %c
                  stats_TCP connections established + 1
               ! Update the send window
               stats_TCP window updates = stats_TCP window updates + 1
               b_TCB_snd wnd <- b_TCP header_window
               b_TCB_snd wl1 <- b_TCP header_seq
               b_TCB_snd wl2 <- b_TCP header_ack
               TCP new state(b_TCB, TCP established)
               TCP open response(b_TCB_slot, 0,
                                 b_TCB_remote address, b_TCB_remote port,
                                 b_TCB_local port)
               b_TCB_ACK pending = 1 %if b_TCB_ACK pending = 0
               ! Process anything else below...
               -> sixth
            %finish
            ! Else no ACK
            TCP new state(b_TCB, TCP syn received)
{>>>>>>>}   x == claim buffer;  copy headers(b, x);  x_data bytes = 0
            ss = x_TCP header_source;  x_TCP header_source = x_TCP header_destination
            x_TCP header_destination = ss
            x_TCP header_seq <- x_TCB_ISS
            x_TCP header_ack <- x_TCB_rcv nxt
            x_TCP header_flags = SYN bit ! ACK bit
            byteinteger (addr(x_TCP header_options)    ) = 2;  ! Define max segment
            byteinteger (addr(x_TCP header_options) + 1) = 4;  ! Size of ^
            shortinteger(addr(x_TCP header_options) + 2) <- x_TCB_mrss
      !N!   net order short(shortinteger(addr(x_TCP header_options) + 2))
            x_data bytes = 0
            TCP transmit segment(x, 1, 1)
            b_flags = b_flags ! do first
            b_next queue == TCP inbound queue
            enqueue buffer(b, b_TCB_state change queue)
            -> out
         %finish
         ! Fifth, no SYN or RST, so dump it
         !! pdate
         !! printstring("TCP (syn sent): no SYN or RST in packet from ")
         !! print TCB address(b_TCB);  newline
         stats_TCP junk received = stats_TCP junk received + 1
         release buffer(b)
         -> out
      %finish

first:-> seq ignored %if b_flags & ignore seq # 0
      ! Not closed, listen or syn sent, so start by checking the sequence number
      ! This applies to all remaining states.
      length = b_data bytes
      length = length + 1 %if b_TCP header_flags & SYN bit # 0
      length = length + 1 %if b_TCP header_flags & FIN bit # 0
      %if length = 0 %start
         %if b_TCB_rcv wnd = 0 %start
            !! printstring("Case 0, 0 ")
            %if b_TCP header_seq = b_TCB_rcv nxt %start
               b_acceptable = 1
            %else
               b_acceptable = 0
            %finish
         %else
            !! printstring("Case 0, >0 ")
            %if b_TCB_rcv nxt - b_TCP header_seq <= 0 %c
                  %and b_TCP header_seq - b_TCB_rcv nxt - b_TCB_rcv wnd <= 0 %start
               b_acceptable = 1
            %else
               b_acceptable = 0
            %finish
         %finish
      %else
         %if b_TCB_rcv wnd = 0 %start
            !! printstring("Case >0, 0 ")
            ! Strictly, nothing should be acceptable here.  However, we'll
            ! pretend that the segment really had no data in it at all and
            ! carry on -- in that case it's only acceptable if its SEQ is
            ! (exactly) what we're expecting, as per "Case 0, 0" above.
            ! Regardless, we send an ACK.
            b_data bytes = 0
            b_TCP header_flags = b_TCP header_flags & (\ (SYN bit ! FIN bit))
            %if b_TCP header_seq = b_TCB_rcv nxt %start
               %if b_TCP header_flags & RST bit = 0 %start
                  b_TCB_ACK pending = 1 %if b_TCB_ACK pending = 0
                  b_acceptable = 1
               %else
                  ! RST set, drop the segment
                  release buffer(b)
                  -> out
               %finish
            %else
               b_acceptable = 0
            %finish
         %else
            !! printstring("Case >0, >0 ")
            rhs <- b_TCB_rcv nxt + b_TCB_rcv wnd
            middle <- b_TCP header_seq + length - 1
            %if (b_TCB_rcv nxt - b_TCP header_seq <= 0 %c
                 %and b_TCP header_seq - rhs < 0) %c
            %or (b_TCB_rcv nxt - middle <= 0 %and middle - rhs < 0) %start
               b_acceptable = 1
            %else
               b_acceptable = 0
            %finish
         %finish
      %finish
      %if b_acceptable = 0 %start
         stats_TCP unacceptable segments = stats_TCP unacceptable segments + 1
         !! printstring("unacceptable");  newline
         b_TCB_ACK pending = 1 %c
             %if b_TCP header_flags & RST bit = 0 %and b_TCB_ACK pending = 0
         release buffer(b)
         -> out
      %else 
         stats_TCP acceptable segments = stats_TCP acceptable segments + 1
         !! printstring("acceptable");  newline
      %finish

      ! The code which checks whether the segment is what we're expecting next
      ! has been moved to just before we (should) check the URG bit.  This
      ! appears not to be what the spec says, but it makes more sense just
      ! to get on and check RST, SYN, ACK as these aren't really related to
      ! the data content of the segment.  We check the segment's seq field
      ! just before we (would) start to process data.

seq ignored:
      ! Second stage, check the RST bit
      -> second(b_TCB_state)

second(TCP syn received):
      %if b_TCP header_flags & RST bit # 0 %start
         !! printstring("RST received");  newline
         stats_TCP resets received = stats_TCP resets received + 1
         %if b_TCB_previous state = TCP listen %start
            !! printstring("Returning to listen");  newline
            TCB == b_TCB
            release buffer(b)
            TCP new state(TCB, TCP listen)
            TCB_remote address = 0
            TCB_remote port = 0
            flush TCB queues(TCB)
         %else
            TCB == b_TCB
            stats_TCP connections reset = stats_TCP connections reset + 1
            release buffer(b)
            delete TCB(TCB, reset error)
         %finish
         -> out
      %finish
      -> third

second(TCP established):
second(TCP fin wait 1):
second(TCP fin wait 2):
second(TCP close wait):
      %if b_TCP header_flags & RST bit # 0 %start
         !! printstring("RST received");  newline
         stats_TCP resets received = stats_TCP resets received + 1
         stats_TCP connections reset = stats_TCP connections reset + 1
         delete TCB(b_TCB, reset error)
         release buffer(b)
         -> out
      %finish
      -> third

second(TCP closing):
second(TCP last ack):
second(TCP time wait):
      %if b_TCP header_flags & RST bit # 0 %start
         !! printstring("RST received");  newline
         stats_TCP resets received = stats_TCP resets received + 1
         delete TCB(b_TCB, reset error)
         release buffer(b)
         -> out
      %finish

third:
      ! Third, we should check security & precedence (but we don't bother)

      ! Fourth (all states), we check the SYN bit
      %if b_flags & ignore SYN = 0 %and b_TCP header_flags & SYN bit # 0 %start
         !! printstring("Bogus SYN -- resetting");  newline
         stats_TCP connections reset = stats_TCP connections reset + 1
         delete TCB(b_TCB, reset error);  b_TCB == nil
         TCP return reset(b, b_TCP header_flags & ACK bit)
         -> out
      %finish

      ! Fifth, check the ACK bit
      -> ACK ignored %if b_flags & ignore ACK # 0
      %if b_TCP header_flags & ACK bit = 0 %start
         !! printstring("NO ACK -- dropping...");  newline
         release buffer(b)
         -> out
      %finish
      %if b_TCB_state = TCP last ack %start
         %if b_TCB_FIN seq - b_TCP header_ack < 0 %start
            ! Our FIN has been ACKed
            !! printstring("Our FIN ACKed");  newline
            stats_TCP good ACKs received = stats_TCP good ACKs received + 1
            delete TCB(b_TCB, 0)
            release buffer(b)
            -> out
            ! Shouldn't this have gone to time-wait ??
         %finish
         ! Nothing else should arrive now.  If it does just throw it away
         !! printstring("Spurious segment in last ack");  newline
         release buffer(b)
         -> out
      %else %if b_TCB_state = TCP time wait
         b_TCB_ACK pending = 1 %if b_TCB_ACK pending = 0
         release buffer(b)
         -> out
      %else
         %if b_TCB_state = TCP syn received %start
            %if b_TCB_snd una - b_TCP header_ack <= 0 %c
                  %and b_TCP header_ack - b_TCB_snd nxt <= 0 %start
               stats_TCP good ACKs received = stats_TCP good ACKs received + 1
               ! Update the send window
               stats_TCP window updates = stats_TCP window updates + 1
               b_TCB_snd wnd <- b_TCP header_window
               b_TCB_snd wl1 <- b_TCP header_seq
               b_TCB_snd wl2 <- b_TCP header_ack
               TCP new state(b_TCB, TCP established)
               TCP open response(b_TCB_slot, 0,
                                 b_TCB_remote address, b_TCB_remote port,
                                 b_TCB_local port)
            %else
               stats_TCP dud ACKs received = stats_TCP dud ACKs received + 1
               TCP return reset(b, 1)
               -> out
            %finish
         %finish
         %if b_TCP header_ack - b_TCB_snd una < 0 %start
            ! Old segment, ignore the ACK
            stats_TCP old ACKs received = stats_TCP old ACKs received + 1
            !! printstring("Old TCP segment, ignoring ACK");  newline
         %else %if b_TCP header_ack - b_TCB_snd nxt > 0
            ! ACK for something we haven't sent yet
            stats_TCP dud ACKs received = stats_TCP dud ACKs received + 1
            !! printstring("TCP ACK for something we haven't sent yet");  newline
            b_TCB_ACK pending = 1 %if b_TCB_ACK pending = 0
            release buffer(b)
            -> out
         %else
            ! Else ACK is new
            !! printstring("New TCP ACK");  newline
            stats_TCP good ACKs received = stats_TCP good ACKs received + 1
            b_TCB_snd una <- b_TCP header_ack
            TCP remove acknowledged(b_TCB)
            %if b_TCB_snd wl1 - b_TCP header_seq < 0 %c
            %or (b_TCB_snd wl1 = b_TCP header_seq %c
                 %and b_TCB_snd wl2 - b_TCP header_ack <= 0) %start
               ! Update the send window
               stats_TCP window updates = stats_TCP window updates + 1
               b_TCB_snd wnd <- b_TCP header_window
               b_TCB_snd wl1 <- b_TCP header_seq
               b_TCB_snd wl2 <- b_TCP header_ack
               TCP send window updated(b_TCB)
            %finish
         %finish
         %if b_TCB_state = TCP fin wait 1 %start
            %if b_TCB_FIN seq - b_TCP header_ack < 0 %start
               ! Our FIN has been ACKed
             ! stats_TCP good ACKs received = stats_TCP good ACKs received + 1
               TCP new state(b_TCB, TCP fin wait 2)
            %finish
         %else %if b_TCB_state = TCP closing
            %if b_TCB_FIN seq - b_TCP header_ack < 0 %start
               ! Our FIN has been ACKed
               stats_TCP good ACKs received = stats_TCP good ACKs received + 1
               TCP new state(b_TCB, TCP time wait)
               TCP disable(b_TCB_slot, 0)
               ! Should start the timer here, probably....
            %finish
         %finish
      %finish

ACK ignored:

sixth:! The following sequence-checking code used to come at the end of the
      ! first stage, which is what the RFC appears to suggest.  It was
      ! moved here as this seems to be a more sensible place to put it...

      ! Now see if the segment is what we're expecting next.  If it has too
      ! high a sequence number we assume that we must have missed something:
      ! in that case we queue the segment on the pending queue, hoping that
      ! the other end will retransmit the missing one.  If it has too low a
      ! sequence number we trim off the data (and controls) at the front so
      ! as to coerce it to what we're expecting.  Note that we don't bother
      ! trimming it down if it's too long: instead we just accept the extra
      ! data, though of course we have to be careful with the window
      ! adjustment later....
      %if b_TCP header_seq - b_TCB_rcv nxt > 0 %start
         ! Not what we're expecting next
         !! printstring("Sent ahead -- holding: ");  write(b_tag, 0);  space
         !! phex(b_TCP header_seq); space;  phex(b_TCB_rcv nxt);  newline
         stats_TCP ahead segments = stats_TCP ahead segments + 1
         b_TCB_ACK pending = 1 %if b_TCB_ACK pending = 0
         b_flags = b_flags ! do first
         b_next queue == TCP inbound queue
         enqueue buffer(b, b_TCB_reordering queue)
         -> out
      %finish
      ! Could be old data.  Try to trim off a SYN first....
      %if b_TCP header_flags & SYN bit # 0 %start
         %if b_TCP header_seq - b_TCB_rcv nxt < 0 %start
            ! Overlaps our window, so trim off the SYN
            !! printstring("Trim SYN");  newline
            b_TCP header_flags = b_TCP header_flags & (\ SYN bit)
            b_TCP header_seq <- b_TCP header_seq + 1
         %finish
      %finish
      diff <- b_TCP header_seq - b_TCB_rcv nxt
      %if diff < 0 %start
         ! (Still) overlaps our window, so trim off some data.  Note that
         ! diff is -ve, so subtracts become adds, & v.v.
         !! printstring("Trim ");  write(-diff, 0);  newline
         stats_TCP duplicate bytes = stats_TCP duplicate bytes - diff
         b_TCP header_seq <- b_TCP header_seq - diff;  ! Move start
         b_data bytes = b_data bytes + diff;           ! Shorten to compensate
         %signal 13, 98, b_data bytes %if b_data bytes < 0;  ! Whole segment is old
         b_data start == byteinteger(addr(b_data start) - diff)
      %finish

      ! Sixth, check the URG bit

      ! Seventh, proccess the segment text
      %if b_data bytes > 0 %start
         %if b_TCB_state = TCP established %c
         %or b_TCB_state = TCP fin wait 1 %or b_TCB_state = TCP fin wait 2 %start
            ! Deliver the data, mark as to be put on delivered queue
            !! printstring("Forward ");  write(b_data bytes, 0)
            !! printstring(" from seq ");  phex(b_TCP header_seq)
            !! printstring(" to ");  write(b_TCB_slot, 0);  newline
            !! dump(b_data start, b_data bytes)
            stats_TCP data bytes received = stats_TCP data bytes received + b_data bytes
            b_next queue == TCP packet delivered queue
            %if TCP send to user(b) %start
               ! Packet has been successfully queued to the user, so update
               ! our receive window and ACK it
               b_TCB_rcv wnd = b_TCB_rcv wnd - b_data bytes
               b_TCB_rcv wnd = 0 %if b_TCB_rcv wnd < 0;  ! Naughty??
               b_TCB_rcv nxt <- b_TCP header_seq + b_data bytes
               b_TCB_ACK pending = 1 %if b_TCB_ACK pending = 0
               ! Recycle the queue of segments pending in case one of them
               ! has now become current.
               %cycle
                  x == dequeue buffer(b_TCB_reordering queue)
                  %exit %if x == nil
                  !! printstring("Recycle ");  write(x_tag, 0);  newline
                  enqueue buffer(x, x_next queue)
               %repeat
            %else
               ! Packet wasn't deliverable for some reason.  Drop it and
               ! hope that when it's retransmitted it will get through....
               b_TCB_ACK pending = 1 %if b_TCB_ACK pending = 0
               release buffer(b)
            %finish
            %return
         %else
            ! Else ignore the segment text
            b_data bytes = 0;  ! To avoid pointer update later
         %finish
      %else %if b_TCP header_flags & (FIN bit ! SYN bit) # 0
         ! No data, but control: ACK it.
         b_TCB_ACK pending = 1 %if b_TCB_ACK pending = 0
      %finish
      enqueue buffer(b, TCP packet delivered queue) %if b_next queue == nil
      ! Else it will end up there anyway...
out:
!L!   lights and A(\TCP light)
%end 

%externalroutine TCP packet delivered(%record(buffer fm)%name b)
   ! Called after the packet delivery QIO has completed.  Update our
   ! receive window, tell the other end, and proccess any FIN.
!L!lights or A(TCP light)
   !! printstring("TCP packet delivered: ");  write(b_tag, 0);  space
   !! write(b_data bytes, 0);  newline
   !! show TCB(b_TCB)
   release buffer(b) %and %return %if b_TCB_local port = 0;  ! Aborted?
   b_next queue == nil
   %if b_data bytes > 0 %start
      ! The other side has actually taken delivery of the packet, so we
      ! can update our receive window again.  Send an ack if we have
      ! just opened our window (??).
      b_TCB_rcv wnd = b_TCB_rcv wnd + b_data bytes
      b_TCB_ACK pending = 1 %c
         %if {b_TCB_rcv wnd = b_data bytes %and} b_TCB_ACK pending = 0
   %finish
   ! Now check for a FIN bit in the last segment
   !! show TCP header(b_TCP header, b_data bytes, "check FIN", b_tag)
   !! printstring("Check FIN state: ");  TCP print state(b_TCB_state);  newline
   %if b_TCP header_flags & FIN bit # 0 %and %c
         b_TCB_state # TCP listen %and b_TCB_state # TCP syn sent %start
      !! printstring("FIN received");  newline
      stats_TCP FINs received = stats_TCP FINs received + 1
      b_TCB_rcv nxt <- b_TCB_rcv nxt + 1
      b_TCB_ACK pending = 1 %if b_TCB_ACK pending = 0
      %if b_TCB_state = TCP syn received %or b_TCB_state = TCP established %start
         TCP new state(b_TCB, TCP close wait)
      %else %if b_TCB_state = TCP fin wait 1
         %if b_TCB_FIN seq - b_TCP header_ack < 0 %start
            ! Our FIN has been ACKed
            TCP new state(b_TCB, TCP time wait)
         %else
            ! We haven't been ACKed yet
            TCP new state(b_TCB, TCP closing)
         %finish
      %else %if b_TCB_state = TCP fin wait 2
         TCP new state(b_TCB, TCP time wait)
      %finish
      TCP send close(b_TCB_slot)
   %finish
   ! Else, remain in the current state
   release buffer(b)
!L!lights and A(\TCP light)
%end


%routine TCP abort TCB(%record(TCB fm)%name TCB)
   %record(buffer fm)%name b
   %switch state(TCP listen : TCP time wait)
      !! printstring("Abort TCB ");  print TCB address(TCB);  newline
      %if TCB_state = TCP closed %start
         ! Not open yet
         !! printstring("TCB is closed");  newline
         ! We have to turn the UCB on again, as otherwise we'll eventually
         ! run out of slave units....
         %if TCB_slot = 0 %start
            pdate
            printstring("Aborting: slot = 0 ??");  newline
         %else
            TCP disable(TCB_slot, reset error)
            TCP enable(TCB_slot)
         %finish
         %return
      %finish
      -> state(TCB_state) %if TCP listen <= TCB_state <= TCP time wait
      printstring("Bad state ");  write(TCB_state, 0);  printstring(" in TCB ")
      print TCB address(TCB);  newline
      delete TCB(TCB, bugcheck error)
      %return

state(TCP syn received):
state(TCP established):
state(TCP fin wait 1):
state(TCP fin wait 2):
state(TCP close wait):
      b == claim buffer
      b_data bytes = 0
      b_IP peer = TCB_remote address
      b_TCB == TCB
      b_TCP header == record(addr(b_data(64)))
      b_TCP header_source = TCB_local port
      b_TCP header_destination = TCB_remote port
      b_TCP header_seq <- TCB_snd nxt
      b_TCP header_ack = 0
      b_TCP header_flags = RST bit
      TCP transmit segment(b, 0, 0)
      ! Fall throuth to delete the TCB

state(TCP listen):
state(TCP syn sent):
      delete TCB(TCB, reset error)
      %return

state(TCP closing):
state(TCP last ack):
state(TCP time wait):
      ! Turn off the slave unit -- the time-wait timeout
      ! will turn it on again.
      TCP disable(TCB_slot, 0)
%end


%externalroutine TCP outbound(%record(buffer fm)%name b)
   %switch op(TCP first request : TCP last request)
   %record(TCB fm)%name TCB
   %recordformat connect fm(%integer remote address, remote port, local port,
                            %integer mss)
   %record(connect fm)%name connect
   %integer i, which
!L!   lights or B(TCP light)
      TCP tag <- TCP tag + 1;  b_tag = TCP tag
      !! printstring("TCP outbound: ");  write(b_tag, 0);  newline
      -> op(b_code) %if TCP interpret user request(b)
      !! printstring("Unknown code ");  write(b_code, 0);  newline
      release buffer(b);  ! Unknown, throw it away
      -> out

op(TCP open request):
      connect == record(addr(b_data start))
      !! printstring("Open request from ");  write(b_TCB_slot, 0)
      !! printstring(" for ");  print inet address(connect_remote address)
      !! print symbol('.');  write(connect_remote port, 0)
      !! print symbol('/');  write(connect_local port, 0);  newline
      %if connect_local port <= 0 %start
         !! printstring("Dud local port ");  write(connect_local port, 0)
         !! newline
         TCP open response(b_TCB_slot, reset error, 0, 0, 0)
         release buffer(b)
         -> out
      %finish
      %if connect_local port <= TCP privileged limit %and b_privilege = 0 %start
         TCP open response(b_TCB_slot, privilege error, 0, 0, 0)
         release buffer(b)
         -> out
      %finish
      %if b_TCB_local port # 0 %start
         ! This TCB is already in use
         TCP open response(b_TCB_slot, exists error, 0, 0, 0)
         release buffer(b)
         -> out
      %finish
      TCB == find TCB(connect_remote address, connect_remote port,
                      connect_local port)
      %if TCB ## nil %and TCB_remote address # 0 %start
         ! Someone else is already using that connection
         !! printstring("Socket is already in use: ")
         !! print inet address(TCB_remote address)
         !! print symbol('.');  write(TCB_remote port, 0)
         !! print symbol('/');  write(TCB_local port, 0);  newline
         TCP open response(b_TCB_slot, exists error, 0, 0, 0)
         release buffer(b)
         -> out
      %finish
      setup TCB(b_TCB)
      %if b_data bytes < 16 %start
         ! Short connect data -- default the rest
         b_TCB_mrss = TCP unspecified mrss
      %else %if connect_mss <= 0
         ! Use default
         b_TCB_mrss = TCP default mrss
      %else
         ! Use specified limit
         b_TCB_mrss = connect_mss
      %finish
      b_TCB_remote address = connect_remote address
      b_TCB_remote port = connect_remote port
      b_TCB_local port = connect_local port
      b_TCB_rcv wnd = TCP initial receive window
      b_TCB_state = TCP listen
      b_TCB_state change stamp = msecs timestamp
      %if connect_remote address = 0 %start
         ! Passive open, remain in listen
         !! printstring("Passive open: ")
         !! print TCB address(b_TCB);  newline
         release buffer(b)
         -> out
      %finish
      b_IP peer = connect_remote address
      b_TCB_rcv wnd = TCP initial receive window
      b_TCB_ISS = generate ISS
      !! printstring("Actively opening ");  print TCB address(b_TCB)
      !! printstring(", ISS ");  phex(b_TCB_ISS);  newline
      b_TCP header == record(addr(b_data start) - TCP header size - 4)
      b_TCP header_source = b_TCB_local port
      b_TCP header_destination = b_TCB_remote port
      b_TCP header_seq = b_TCB_ISS
      b_TCP header_ack = 0
      b_TCP header_flags = SYN bit
      b_TCP header_window = 0
      byteinteger (addr(b_TCP header_options)    ) = 2;  ! Define max segment
      byteinteger (addr(b_TCP header_options) + 1) = 4;  ! Size of ^
      shortinteger(addr(b_TCP header_options) + 2) <- b_TCB_mrss
!N!   net order short(shortinteger(addr(b_TCP header_options) + 2))
      b_TCB_snd una <- b_TCB_ISS
      b_TCB_snd nxt <- b_TCB_ISS + 1
      TCP new state(b_TCB, TCP syn sent)
      b_data bytes = 0
      TCP transmit segment(b, 1, 1)
      -> out

op(TCP send request):
      !! printstring("Send request from ");  write(b_TCB_slot, 0);  newline
      !! dump(b_data start, b_data bytes)
      %if b_TCB_state = TCP established %or b_TCB_state = TCP close wait %start
         ! Is the window open enough?
         ! (snd nxt + data <= snd una + snd wnd)
         %if b_TCB_blocked send = 0 %and %c
               (b_TCB_snd nxt + b_data bytes %c
                - b_TCB_snd una - b_TCB_snd wnd <= 0) %start
            ! OK to send
            b_IP peer = b_TCB_remote address
            stats_TCP data bytes sent = stats_TCP data bytes sent + b_data bytes
            b_TCP header == record(addr(b_data start) - TCP header size)
            b_TCP header_source = b_TCB_local port
            b_TCP header_destination = b_TCB_remote port
            b_TCP header_seq <- b_TCB_snd nxt
            b_TCP header_ack <- b_TCB_rcv nxt
            b_TCP header_window = b_TCB_rcv wnd
            b_TCB_ACK pending = 0
            b_TCP header_flags = ACK bit ! PSH bit
            b_TCB_snd nxt <- b_TCB_snd nxt + b_data bytes
            TCP transmit segment(b, 0, 1)
         %else
must block: ! Send window is too small
            !! printstring("Must block: ");  write(b_data bytes, 0)
            !! space;  phex(b_TCB_snd nxt);  space;  write(b_TCB_snd wnd, 0)
            !! space;  phex(b_TCB_snd una);  write(b_TCB_blocked send, 1)
            !! newline
            TCP block(b_TCB_slot)
            enqueue buffer(b, b_TCB_blocked send queue)
            b_TCB_blocked send = 1
         %finish
      %else
         release buffer(b);  ! Throw it away **meantime**
      %finish
      -> out

op(TCP close request):
      !! printstring("Close request from ");  write(b_TCB_slot, 0);  newline
      %if b_TCB_state = TCP listen %or b_TCB_state = TCP syn sent %start
         ! Shouldn't be here!
         TCB == b_TCB
         release buffer(b)
         delete TCB(TCB, bugcheck error)
      %else %if b_TCB_state = TCP syn received %c
            %or b_TCB_state = TCP established  %c
            %or b_TCB_state = TCP close wait
         %if b_TCB_blocked send # 0 {%or b_TCB_snd wnd = 0} %start
            b_data bytes = 1;  ! Frig
            -> must block
         %finish
         b_data bytes = 0
         b_IP peer = b_TCB_remote address
         b_TCP header == record(addr(b_data start) - TCP header size)
         b_TCP header_source = b_TCB_local port
         b_TCP header_destination = b_TCB_remote port
         b_TCP header_seq <- b_TCB_snd nxt
         b_TCP header_ack <- b_TCB_rcv nxt
         b_TCP header_window = b_TCB_rcv wnd
         b_TCB_ACK pending = 0
         b_TCP header_flags = ACK bit ! FIN bit
         b_TCB_FIN seq <- b_TCP header_seq
         b_TCB_snd nxt <- b_TCB_snd nxt + 1
         %if b_TCB_state = TCP close wait %start
            ! P.61 says "closing", P.23 says "last ack"
            TCP new state(b_TCB, TCP closing)
         %else
            TCP new state(b_TCB, TCP fin wait 1)
         %finish
         TCP transmit segment(b, 0, 1)
      %else
         ! fin wait 1, fin wait 2, closing, last ack, time wait
         release buffer(b);  !**Meantime**
      %finish
      -> out

op(TCP abort request):
      !! printstring("Abort request from ");  write(b_TCB_slot, 0);  newline
      TCP abort TCB(b_TCB)
      release buffer(b)
      -> out

op(TCP claim request):
op(TCP claim priv request):
      !! printstring("Claim port request from ");  write(b_TCB_slot, 0);  newline
try claim port again:
      %if b_code = TCP claim priv request %start
         %if b_privilege = 0 %start
            TCP claim response(b_TCB_slot, privilege error, 0)
            release buffer(b)
            -> out
         %finish
         TCP last privileged = TCP last privileged - 1
         TCP last privileged = 1023 %if TCP last privileged < 768
         which = TCP last privileged
         !! printstring("Looking for privileged, trying ")
         !! write(which, 0);  newline
      %else
         TCP last unprivileged = TCP last unprivileged + 1
         TCP last unprivileged = 1025 %if TCP last unprivileged > 2048
         which = TCP last unprivileged
         !! printstring("Looking for unprivileged, trying ")
         !! write(which, 0);  newline
      %finish
      %for i = 1, 1, max TCP %cycle
         -> try claim port again %if TCB table_TCB(i)_local port = which
      %repeat
      ! If we fall through here then the port was free...
      !! printstring("Allocated ");  write(which, 0);  newline
      TCP claim response(b_TCB_slot, 0, which)
      release buffer(b)

out:
!L!   lights and B(\TCP light)
%end

%externalroutine TCP fast timer
   %record(buffer fm)%name b
   %record(TCB fm)%name TCB
   %integer i, now
!L!   lights or B(TCP light)
      now = msecs timestamp
      !! printstring("Retransmit timeouts: ");  phex(now);  newline
      %for i = 1, 1, max TCP %cycle
         TCB == TCB table_TCB(i)
         %if TCB_local port # 0 %start
            ! In use
            b == dequeue buffer(TCB_retransmit queue)
            %if b ## nil %start
               ! Something pending
               %if now - b_stamp - b_interval >= 0 %start
                  ! This one has expired
                  b_life = b_life + 1
                  %if b_life <= TCP retransmit limit %start
                     ! Retransmit required here
                     !! printstring("Retransmitting ");  print TCB address(TCB)
                     !! printstring(" at ");  phex(now);  newline
                     b_interval = b_interval << 1;  ! Double it
                     %if b_interval < TCP reTX lbound %start
                        b_interval = TCP reTX lbound
                     %else %if b_interval > TCP reTX ubound
                        b_interval = TCP reTX ubound
                     %finish
                     stats_TCP retransmits = stats_TCP retransmits + 1
                     b_flags = requeue flag;  !<<<<<<<<<
!T!                  TCP trace(b, TCP trace reTX)
                     IP outbound(b)
                  %else
                     ! Time this one out
                     !! printstring("Timed out ");  print TCB address(TCB)
                     !! newline
                     stats_TCP retransmit timeouts = stats_TCP retransmit timeouts + 1
                     release buffer(b)
                     delete TCB(TCB, timeout error)
                  %finish
               %else
                  ! Put it back to wait for next time
                  !! printstring("Requeue ");  print TCB address(TCB);  newline
                  requeue buffer(b, TCB_retransmit queue)
               %finish
            %else %if TCB_ACK pending > 0
               ! Pending ACK in the last clock interval.  Defer it for
               ! another tick.
               TCB_ACK pending = -1
            %else %if TCB_ACK pending < 0
               ! Deferred pending ACK.  Send it this time.
               TCP send ACK(TCB)
            %finish
         %finish
      %repeat
!L!   lights and B(\TCP light)
%end

%externalroutine TCP slow timer
   %record(TCB fm)%name TCB
   %integer i, started
!L!   lights or B(TCP light)
      started <- msecs timestamp - time wait timeout
      %for i = 1, 1, max TCP %cycle
         TCB == TCB table_TCB(i)
         delete TCB(TCB, 0) %if TCB_state >= TCP closing %c
            %and TCB_state change stamp - started <= 0
      %repeat
!L!   lights and B(\TCP light)
%end

%end %of %file
