! TCP module for INet process, GDMR, Feb .. May 1987
! To do: process PSH and URG
!        interpret options

%include "INet:Common_Formats.Inc"
%include "INet:Utility.Inc"
%include "INet:INet.Inc"

%externalrecord(queue fm)%spec ether outbound         queue
%externalrecord(queue fm)%spec IP outbound            queue
%externalrecord(queue fm)%spec TCP inbound            queue
%externalrecord(queue fm)%spec TCP outbound           queue
%externalrecord(queue fm)%spec TCP first              queue
%externalrecord(queue fm)%spec TCP sixth              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)

%externalintegerspec max TCP
%externalintegerspec activity count
%externalintegerspec our address

%systemroutinespec phex(%integer x)

%externalrecord(TCB table fm)%namespec TCB table

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

%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 = 63;  ! 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}
{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


! TCP 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_pending retransmit queue)
      initialise queue(t_reordering queue)
      initialise queue(t_state change queue)
      t_round trip timer = TCP initial RTT
      t_rcv wnd = TCP initial receive window
%end

%routine flush TCB queues(%record(TCB fm)%name TCB)
   %integer n
      purge queue(TCB_retransmit queue, n)
      activity count = activity count - n
      !purge queue(TCB_pending retransmit queue, n)
      !activity count = activity count - n
      purge queue(TCB_reordering queue, n)
      purge queue(TCB_state change queue, n)
      purge queue(TCB_blocked send queue, n)
%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)  ;! %if status >= 0
   flush TCB queues(TCB)
   TCP enable(TCB_slot)
   TCB = 0
%end

%externalroutine TCB time wait timeouts
   %record(TCB fm)%name TCB
   %integer i, started
      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
%end


! TCP processing proper....

%routine TCP remove acknowledged(%record(TCB fm)%name TCB)
   %record(buffer fm)%name b
   %integer diff
      !! 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
         diff = msecs timestamp - b_stamp
         TCB_round trip timer = (RTT a * TCB_round trip timer + %c
                                 (RTT a denominator - RTT a) * diff) %c
                                // RTT a denominator
         !! printstring("ACKed: ");  phex(b_seq)
         !! printstring(", diff ");  write(diff, 0)
         !! printstring(", RTT -> ");  write(TCB_round trip timer, 0)
         !! newline
         release buffer(b)
         activity count = activity count - 1
      %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
      ! (done by ether handler) 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_round trip timer << 2;  ! Crude....
      %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,
                                    our address, 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;  !<<<<<<<<<<<
         !X schedule fast timer %if activity count = 0
         activity count = activity count + 1
      %finish
      b_p_TCP packets out = b_p_TCP packets out + 1
      b_p_TCP bytes out = b_p_TCP bytes out + b_IP bytes
      enqueue buffer(b, IP outbound queue)
%end
   
%externalpredicate TCP retransmit timeouts
   %record(buffer fm)%name b
   %record(TCB fm)%name TCB
   %integer i, done one = 0, now
      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 = TCB_round trip timer << 2;  ! Still crude
                     b_p_TCP retransmits = b_p_TCP retransmits + 1
                     b_flags = requeue flag;  !<<<<<<<<<
                     enqueue buffer(b, ether outbound queue)
                     done one = 1
                  %else
                     ! Time this one out
                     !! printstring("Timed out ");  print TCB address(TCB)
                     !! newline
                     b_p_TCP retransmit timeouts = b_p_TCP retransmit timeouts + 1
                     release buffer(b)
                     activity count = activity count - 1
                     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
            %finish
         %finish
      %repeat
      %if done one = 0 %then %false %else %true
%end

%routine TCP return reset(%record(buffer fm)%name b, %integer zero)
   ! If zero = 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 zero = 0 %start
         ack value = b_TCP header_seq
         ack value = ack value + 1 %if b_TCP header_flags & (FIN bit ! SYN 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_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_p == find peer info(b_IP peer)
      release buffer(b) %and %return %if b_p == nil;  ! Unknown??  Throw it away
      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
         b_p == TCB_peer info
         b_p_TCP data bytes sent = b_p_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(buffer fm)%name x
   %integer c, header size
   %short ss
      !! printstring("TCP inbound");  newline
      !! dump(byteinteger(addr(b_TCP header)), b_IP bytes)
      b_next queue == nil
      %unless b_IP header_destination = our address %start
         ! Probably a broadcast, since everything else will have been
         ! filtered out in the IP processing...
         !! printstring("TCP broadcast from ");  print inet address(b_IP peer)
         !! newline
         b_p_TCP bogus addresses = b_p_TCP bogus addresses + 1
         release buffer(b)
         %return
      %finish
      c = calculate pseudo checksum(b_TCP header, b_IP bytes,
                                    b_IP peer, our address,
                                    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)
         b_p_TCP checksum errors = b_p_TCP checksum errors + 1
         release buffer(b)
         %return
      %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
      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)
      b_p_TCP packets in = b_p_TCP packets in + 1
      b_p_TCP bytes in = b_p_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
         b_p_TCP for closed = b_p_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
         %return
      %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
            b_p_TCP resets received = b_p_TCP resets received + 1
            release buffer(b)
            %return
         %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
            b_p_TCP junk received = b_p_TCP junk received + 1
            TCP return reset(b, 0)
            %return
         %finish
         ! Third, check for SYN
         %if b_TCP header_flags & SYN bit # 0 %start
            ! Should check security/precedence here.
            b_p_TCP SYNs received = b_p_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
               enqueue buffer(b, TCP first queue)
            %else
               ! No data, so just use the original
               x == b
            %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_peer info == x_p;  ! Connection defined, so note our peer
            x_TCB_remote address = x_IP peer
            x_TCB_remote port = x_TCP header_source
            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_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_TCP header_options = TCP define max segment
            x_data bytes = 0
            TCP transmit segment(x, 1, 1)
            %return
         %finish
         ! Else, something odd
         !! pdate
         !! printstring("TCP (listen): odd packet from ")
         !! print inet address(b_IP peer);  newline
         b_p_TCP junk received = b_p_TCP junk received + 1
         release buffer(b)
         %return
      %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
               b_p_TCP dud ACKs received = b_p_TCP dud ACKs received + 1
               %if b_TCP header_flags & RST bit = 0 %start
                  TCP return reset(b, 1)
               %else
                  release buffer(b)
               %finish
               %return
            %finish
            %if b_TCB_snd una - b_TCP header_ack <= 0 %c
                  %and b_TCP header_ack - b_TCB_snd nxt <= 0 %start
               b_p_TCP good ACKs received = b_p_TCP good ACKs received + 1
               b_acceptable = 1
            %finish
         %finish
         ! Second, check the RST bit
         %if b_TCP header_flags & RST bit # 0 %start
            b_p_TCP resets received = b_p_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
            %return
         %finish
         ! Third, check security/precedence (but we don't bother)
         ! Fourth, check the SYN bit
         %if b_TCP header_flags & SYN bit # 0 %start
            b_p_TCP SYNs received = b_p_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
               b_p_TCP connections established = %c
                  b_p_TCP connections established + 1
               ! Update the send window
               b_p_TCP window updates = b_p_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)
               TCP send ACK(b_TCB)
               ! Process anything else below...
               enqueue buffer(b, TCP sixth queue)
               %return
            %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_next queue == TCP first queue
            enqueue buffer(b, b_TCB_state change queue)
            %return
         %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
         b_p_TCP junk received = b_p_TCP junk received + 1
         release buffer(b)
         %return
      %finish
      enqueue buffer(b, TCP first queue)
%end

%externalroutine TCP first(%record(buffer fm)%name b)
   %record(TCB fm)%name TCB
   %integer length, middle, rhs, diff
   %switch second(TCP listen : TCP time wait)
      !! printstring("TCP first: ");  write(b_tag, 0);  newline
      !! show TCB(b_TCB)
      release buffer(b) %and %return %if b_TCB_local port = 0;  ! Aborted?
      b_next queue == nil
      -> 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.
            b_data bytes = 0
            %if b_TCP header_seq = b_TCB_rcv nxt %start
               TCP send ACK(b_TCB) %if b_TCP header_flags & RST bit = 0
               b_acceptable = 1
            %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
         b_p_TCP unacceptable segments = b_p_TCP unacceptable segments + 1
         !! printstring("unacceptable");  newline
         TCP send ACK(b_TCB) %if b_TCP header_flags & RST bit = 0
         release buffer(b)
         %return
      %else 
         b_p_TCP acceptable segments = b_p_TCP acceptable segments + 1
         !! printstring("acceptable");  newline
      %finish

      %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
         b_p_TCP ahead segments = b_p_TCP ahead segments + 1
      !? b_TCB_ack pending = b_TCB_ack pending + 1
         b_next queue == TCP first queue
         enqueue buffer(b, b_TCB_reordering queue)
         %return
      %finish
      %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
         b_p_TCP duplicate bytes = b_p_TCP duplicate bytes - diff
         b_data bytes = b_data bytes + diff
         %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

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
         b_p_TCP resets received = b_p_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
            b_p_TCP connections reset = b_p_TCP connections reset + 1
            release buffer(b)
            delete TCB(TCB, reset error)
         %finish
         %return
      %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
         b_p_TCP resets received = b_p_TCP resets received + 1
         b_p_TCP connections reset = b_p_TCP connections reset + 1
         delete TCB(b_TCB, reset error)
         release buffer(b)
         %return
      %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
         b_p_TCP resets received = b_p_TCP resets received + 1
         delete TCB(b_TCB, 0)
         release buffer(b)
         %return
      %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
         TCP return reset(b, b_TCP header_flags & ACK bit)
         b_p_TCP connections reset = b_p_TCP connections reset + 1
         delete TCB(b_TCB, reset error)
         ! Note, b is used to return the RST.
         %return
      %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)
         %return
      %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
            b_p_TCP good ACKs received = b_p_TCP good ACKs received + 1
            delete TCB(b_TCB, 0)
            release buffer(b)
            %return
            ! 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)
         %return
      %else %if b_TCB_state = TCP time wait
         b_TCB_ack pending = b_TCB_ack pending + 1
         TCP send ACK(b_TCB) %if b_TCB_ack pending >= ACK threshold
         !* Restart the 2MSL timer here....
         release buffer(b)
         %return
      %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
               b_p_TCP good ACKs received = b_p_TCP good ACKs received + 1
               ! Update the send window
               b_p_TCP window updates = b_p_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
               b_p_TCP dud ACKs received = b_p_TCP dud ACKs received + 1
               TCP return reset(b, 0)
               %return
            %finish
         %finish
         %if b_TCP header_ack - b_TCB_snd una < 0 %start
            ! Old segment, ignore the ACK
            b_p_TCP old ACKs received = b_p_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
            b_p_TCP dud ACKs received = b_p_TCP dud ACKs received + 1
            !! printstring("TCP ACK for something we haven't sent yet");  newline
            TCP send ACK(b_TCB)
            release buffer(b)
            %return
         %else
            ! Else ACK is new
            !! printstring("New TCP ACK");  newline
            b_p_TCP good ACKs received = b_p_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
               b_p_TCP window updates = b_p_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
             ! b_p_TCP good ACKs received = b_p_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
               b_p_TCP good ACKs received = b_p_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:
      enqueue buffer(b, TCP sixth queue)
%end

%externalroutine TCP sixth(%record(buffer fm)%name b)
   %record(buffer fm)%name x

      release buffer(b) %and %return %if b_TCB_local port = 0;  ! Aborted?
      b_next queue == nil
      !! printstring("TCP sixth: ");  write(b_tag, 0);  newline
      !! show TCB(b_TCB)
      ! 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)
            b_p_TCP data bytes received = b_p_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 = b_TCB_ACK pending + 1
               TCP send ACK(b_TCB) %if b_TCB_ack pending >= ACK threshold
               ! 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 = b_TCB_ACK pending + 1
               release buffer(b)
            %finish
            %return
         %else
            ! Else ignore the segment text
            b_data bytes = 0;  ! To avoid pointer update later
         %finish
      %finish
      enqueue buffer(b, TCP packet delivered queue) %if b_next queue == nil
      ! Else it will end up there anyway...
%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.
   !! 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
      TCP send ACK(b_TCB) %if b_TCB_rcv wnd = b_data bytes
   %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
      b_p_TCP FINs received = b_p_TCP FINs received + 1
      b_TCB_rcv nxt <- b_TCB_rcv nxt + 1
      TCP send ACK(b_TCB);  ! Unconditionally
      %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)
%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_p == TCB_peer info
      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
      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
      %return

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)
         %return
      %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)
         %return
      %finish
      %if connect_remote address # 0 %Start
         ! Not passive.  Get our peer's data
         b_p == find peer info(connect_remote address)
         b_p == new peer info(connect_remote address) %if b_p == nil
         %if b_p == nil %start
            TCP open response(b_TCB_slot, reset error, 0, 0, 0)
            release buffer(b)
            %return
         %finish
      %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)
         %return
      %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)
         %return
      %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)
         %return
      %finish
      b_TCB_peer info == b_p
      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)
      %return

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
            b_p == b_TCB_peer info
            b_p_TCP data bytes sent = b_p_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
      %return

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_p == b_TCB_peer info
         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 %then TCP new state(b_TCB, TCP last ack) %c
                                          %else TCP new state(b_TCB, TCP fin wait 1)
         TCP transmit segment(b, 0, 1)
      %else
         ! fin wait 1, fin wait 2, closing, last ack, time wait
         release buffer(b);  !**Meantime**
      %finish
      %return

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

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)
            %return
         %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)
%end

%externalpredicate TCP pending acks
   %record(TCB fm)%name TCB
   %integer i, sent some = 0
      %for i = 1, 1, max TCP %cycle
         TCB == TCB table_TCB(i)
         %if TCB ## nil %and TCB_ack pending > 0 %start
            !! printstring("Send pending ACK for ");  print TCB address(TCB)
            !! newline
            TCP send ACK(TCB)
            sent some = 1
         %finish
      %repeat
      %if sent some = 0 %then %false %else %true
%end

%end %of %file
