-- (c) William Welch 2004 -- -- This software is provided 'as-is', without any express or implied -- warranty. In no event will the authors be held liable for any damages -- arising from the use of this software. -- -- Permission is granted to anyone to use this software for any purpose, -- including commercial applications, and to alter it and redistribute it -- freely, subject to the following restrictions: -- -- 1. The origin of this software must not be misrepresented; -- you must not claim that you wrote the original software. -- If you use this software in a product, an acknowledgment in -- the product documentation would be appreciated but is not required. -- -- 2. Altered source versions must be plainly marked as such, and must -- not be misrepresented as being the original software. -- -- 3. This notice may not be removed or altered from any source distribution. -- -- This license is commonly known as the zlib/libpng License. -- -- -- tcp.jal bvwelch 26 may 2004 -- revised 19 march 2005 -- See RFC 793 for details -- states for tcp const tcp_closed = 0 const tcp_listen = 1 const tcp_synrcv = 2 const tcp_estab = 3 const tcp_fin = 4 -- "connection" variables. If you change this, -- you *MUST* adjust "TCP_CONN_SZ" in ram.jal const conn_retran_hi = 0 const conn_retran_lo = 1 const conn_txfid_hi = 2 const conn_txfid_lo = 3 const conn_snd_una = 4 const conn_snd_nxt = 8 const conn_rcv_nxt = 12 const conn_peer_ip = 16 const conn_peer_mac = 20 const conn_peer_port = 26 const conn_my_port = 28 const conn_state = 30 const conn_retran_cnt = 31 const conn_rxlen = 32 const conn_txok = 34 procedure tcp_init is var byte n, txfid_hi, txfid_lo -- force an initial "passive open" MemWrEx ( telnet_conn , conn_state, tcp_listen) MemWrEx ( telnet_conn , conn_txok, 0 ) MemWrEx ( telnet_conn , conn_retran_hi, 0) MemWrEx ( telnet_conn , conn_retran_lo, 0) MemWrEx ( telnet_conn , conn_retran_cnt, 0) MemWrEx ( telnet_conn , conn_my_port, ( telnet_port >> 8 ) ) MemWrEx ( telnet_conn , ( conn_my_port + 1 ), ( telnet_port & 0xff ) ) -- force an initial "passive open" MemWrEx ( http_conn , conn_state, tcp_listen) MemWrEx ( http_conn , conn_txok, 0 ) MemWrEx ( http_conn , conn_retran_hi, 0) MemWrEx ( http_conn , conn_retran_lo, 0) MemWrEx ( http_conn , conn_retran_cnt, 0) MemWrEx ( http_conn , conn_my_port, ( http_port >> 8 ) ) MemWrEx ( http_conn , ( conn_my_port + 1 ), ( http_port & 0xff ) ) -- some number, random would be better. I32_LDL ( tcp_clk , 0 , 0 , 0x12 , 0x34 ) -- allocate the retry buffers net_alloc ( txfid_hi , txfid_lo ) MemWrEx ( telnet_conn , conn_txfid_hi, txfid_hi) MemWrEx ( telnet_conn , conn_txfid_lo, txfid_lo) net_alloc ( txfid_hi , txfid_lo ) MemWrEx ( http_conn , conn_txfid_hi, txfid_hi) MemWrEx ( http_conn , conn_txfid_lo, txfid_lo) end procedure -- This routine will fill-in the ethernet and IP headers completely. -- The fixed portion of the TCP header will also be filled in by this routine. procedure tcp_txpkt(byte in conn_off, byte in tcp_hdrsize, byte in out_flags, byte in txlen_hi, byte in txlen_lo) is var byte n, hi, lo, txfid_hi, txfid_lo SetPointerRel ( RCV_NXT, conn_off , conn_rcv_nxt ) SetPointerRel ( SND_NXT, conn_off , conn_snd_nxt ) -- build ethernet hdr MemCpyEx ( PKT , 0 , conn_off , conn_peer_mac , 6 ) MemCpyEx ( PKT , 6 , MYMAC , 0 , 6 ) MemWrEx ( PKT , 12 , 8 ) MemNext = 0 -- build IP hdr. MemSet( ip_off, 0 , ip_hdrsize ) MemWrEx( ip_off , 0, 0x45) MemCpyEx ( ip_off , 4, tcp_clk , 0 , 2 ) ntohsEx ( ip_off , 4 ) MemWrEx( ip_off , 8, 64) MemWrEx( ip_off , 9, 6) MemCpyEx ( IP_OFF , 12 , MYIP , 0 , 4 ) MemCpyEx ( IP_OFF , 16 , conn_off , conn_peer_ip , 4) -- update the length fields in the IP header. note byte swap. I16_LDL ( L16 , txlen_hi , txlen_lo ) I16_ADD8 ( L16, ( ip_hdrsize + tcp_hdrsize ) ) I16_SVL ( L16 , hi , lo ) I16_LDL ( IP_LEN , lo , hi) -- build TCP hdr. MemSet( tcp_off, 0 , 20 ) MemCpyEx ( tcp_off, 0 , conn_off , conn_my_port , 2 ) MemCpyEx ( tcp_off, 2 , conn_off , conn_peer_port , 2 ) n = tcp_hdrsize << 2 -- copy our seq # and ack to outgoing tcp header MemCpy ( seg_seq , snd_nxt , 4 ) MemCpy ( seg_ack , rcv_nxt , 4 ) htonl ( seg_seq ) htonl ( seg_ack ) -- advance our seq number. if we send SYN or FIN, count them also. I32_ADD16 ( snd_nxt , txlen_hi , txlen_lo ) if ( out_flags & 1 ) != 0 then I32_INC( snd_nxt ) end if if ( out_flags & 2 ) != 0 then I32_INC( snd_nxt ) end if MemWrEx( tcp_off , 12, n ) MemWrEx( tcp_off , 13, out_flags ) MemWrEx ( tcp_off , 14, ( tcp_window >> 8 ) ) MemNext = ( tcp_window & 0xff ) MemSetEx ( tcp_off , 18, 0x00 , 2 ) -- do the tcp checksum I16_SUB8 ( L16 , ip_hdrsize ) I16_SVL ( L16 , hi , lo) chksum_tcp(hi , lo) -- now do the IP chksum n = ( MemRd(ip_off) & 0x0F ) << 2 chksum_write(ip_off, 0, 10, 0, n) -- setup to send packet. calculate the length. -- round up, if odd length I16_ADD8 ( L16, ( ip_hdrsize + 14 ) ) if I16_BTT ( L16 , 0 ) then I16_INC ( L16 ) end if I16_SVL ( L16 , hi , lo) MemWrEx( conn_off , conn_retran_hi, hi) MemWrEx( conn_off , conn_retran_lo, lo) MemWrEx( conn_off , conn_retran_cnt, 0) -- zero out the "timeout clocks" if conn_off == http_conn then net_http_clk = 0 else net_telnet_clk = 0 end if -- check for SYN or FIN on outgoing packet. out_flags = out_flags & 3 -- if no data, no SYN, no FIN, then just send the packet. no re-tran is required. if (txlen_hi == 0) & (txlen_lo == 0) & ( out_flags == 0 ) then MemWrEx( conn_off , conn_retran_hi, 0) MemWrEx( conn_off , conn_retran_lo, 0) net_txpkt( hi , lo ) return end if -- save a copy of the packet for possible retry, then send the packet. txfid_hi = MemRdEx ( conn_off , conn_txfid_hi ) txfid_lo = MemRdEx ( conn_off , conn_txfid_lo ) net_putbuf ( txfid_hi , txfid_lo , 0, PKT , hi , lo ) net_txpkt( hi , lo ) end procedure -- Main function to implement a tcp server's "passive open". procedure tcp_state_listen(byte in conn_off) is var byte in_flags, out_flags if debug_mode then putc = "[" putc = "L" putc = "]" end if in_flags = MemRdEx(tcp_off , 13) & 0x3F -- if RST or ACK bit, ignore the contents of the packet. if ( in_flags & 0x14) != 0 then if debug_mode then putc = "D" putc = "L" end if return end if -- We are looking for a new connection, so the SYN flag must be set. if ( in_flags & 2) == 0 then if debug_mode then putc = "D" putc = "L" end if return end if -- OK. This is the first segment of the 3-way handshake -- copy incoming seq number to our ack field, and increment it by 1. (SYN counts as 1) ntohl ( seg_seq ) MemCpy(rcv_nxt, seg_seq, 4) I32_INC(rcv_nxt) -- initialize our seq # to a "random" value from the "ISN" clock. MemCpy(snd_nxt, tcp_clk, 4) MemCpyEx(conn_off, conn_snd_una, snd_nxt, 0, 4) -- we will use a 24 byte header to allow room for our MSS info. MemWrEx( tcp_off , 20, 0x02 ) MemNext = 0x04 MemNext = ( tcp_maxsegsize >> 8) MemNext = ( tcp_maxsegsize & 0xff) -- SYN + ACK flags. note that SYN counts as 1 data byte. out_flags = 0x12 -- advance to state SYN-RCV MemWrEx( conn_off , conn_state, tcp_synrcv) -- remember who is "calling". MemCpyEx( conn_off , conn_peer_mac, PKT , 6, 6) MemCpyEx( conn_off , conn_peer_ip, ip_off , 12, 4) MemCpyEx( conn_off , conn_peer_port, tcp_off, 0, 2) -- send our reply, which is the 2nd step of the 3-way handshake. tcp_txpkt(conn_off, 24, out_flags, 0, 0) end procedure procedure tcp_state_synrcv(byte in conn_off) is var byte in_flags, tmp if debug_mode then putc = "[" putc = "S" putc = "]" end if -- check IP addr and source port number if MemCompEx( ip_off , 12, conn_off , conn_peer_ip , 4 ) == false then if debug_mode then putc = "a" end if return end if if MemCompEx( tcp_off , 0, conn_off , conn_peer_port , 2 ) == false then if debug_mode then putc = "b" end if return end if in_flags = MemRdEx(tcp_off , 13) & 0x3F -- ACK? ignore this segment if it is "not acceptable". ntohl ( seg_seq ) if ( in_flags & 0x10) != 0 then if MemComp(seg_seq, rcv_nxt, 4 ) == false then if debug_mode then putc = "D" putc = "S" end if return end if end if -- if SYN, or RST, just ignore. if ( in_flags & 6) != 0 then if debug_mode then putc = "D" putc = "S" end if return end if -- make sure ACK flag is set. if (in_flags & 0x10) == 0 then if debug_mode then putc = "D" putc = "S" end if return end if -- make sure seq_ack is correct ntohl ( seg_ack ) if MemComp(seg_ack, snd_nxt, 4 ) == false then if debug_mode then putc = "D" putc = "S" end if return end if -- we have completed the last step of 3-way handshake MemWrEx( conn_off , conn_state, tcp_estab) MemCpyEx( conn_off , conn_snd_una, snd_nxt, 0, 4) MemWrEx( conn_off , conn_retran_hi, 0) MemWrEx( conn_off , conn_retran_lo, 0) if debug_mode then putc = "[" putc = "E" putc = "]" end if end procedure -- handle incoming tcp packet. reply with an ACK if necessary. procedure tcp_state_estab(byte in conn_off) is var byte in_flags, out_flags, tcp_hdrsize, hi, lo var bit reply reply = false -- check IP addr and source port number if MemCompEx( ip_off , 12, conn_off , conn_peer_ip , 4 ) == false then if debug_mode then putc = "a" end if return end if if MemCompEx( tcp_off , 0, conn_off , conn_peer_port , 2 ) == false then if debug_mode then putc = "b" end if return end if in_flags = MemRdEx(tcp_off , 13) & 0x3F -- if no ACK, ignore this packet. if ( in_flags & 0x10) == 0 then return end if -- ignore this segment if it is "not acceptable". For now, -- I hope it will always be the exact segment we are expecting next. -- TODO: in theory, we might get the packet out of order... ntohl ( seg_seq ) if MemComp( seg_seq , rcv_nxt , 4 ) == false then if debug_mode then putc = "D" putc = "E" end if return end if -- if RST bit, ignore this packet if ( in_flags & 4) != 0 then if debug_mode then putc = "d" end if return end if -- figure out where the payload starts, if any. and its size. tcp_hdrsize = ( MemRdEx(tcp_off , 12) & 0xF0 ) >> 2 SetPointerRel ( TCP_APP_OFF, TCP_OFF , tcp_hdrsize ) MemCpy ( L16 , IP_LEN , 2 ) ntohs ( L16 ) I16_SUB8 ( L16, ip_hdrsize ) I16_SUB8 ( L16, tcp_hdrsize ) I16_SVL ( L16, hi, lo ) I32_ADD16 ( rcv_nxt , hi , lo ) if ( hi != 0 ) | ( lo != 0 ) then pkt_owner = conn_off reply = true end if MemCpyEx( conn_off , conn_rxlen, L16, 0, 2) -- Is this ACK the one we are looking for? If so, we can -- enable tx of tcp packets. ntohl ( seg_ack ) if MemComp(seg_ack, snd_nxt, 4 ) then MemWrEx( conn_off , conn_retran_hi, 0) MemWrEx( conn_off , conn_retran_lo, 0) MemCpyEx( conn_off , conn_snd_una, snd_nxt, 0, 4) MemWrEx( conn_off , conn_txok, 1 ) else MemWrEx( conn_off , conn_txok, 0 ) end if -- incoming FIN? closing the connection? if ( in_flags & 1) != 0 then MemWrEx( conn_off , conn_state, tcp_closed) I32_INC( rcv_nxt ) reply = true end if if reply == false then return end if -- set ACK bit. out_flags = 0x10 -- send our reply if debug_mode then putc = "e" end if tcp_txpkt(conn_off, 20, out_flags, 0, 0) end procedure procedure tcp_rx_event_processor(byte in conn_off) is var byte state, txlen_hi , txlen_lo SetPointerRel ( RCV_NXT, conn_off , conn_rcv_nxt ) SetPointerRel ( SND_NXT, conn_off , conn_snd_nxt ) i32_inc(tcp_clk) state = MemRdEx(conn_off , conn_state) if state == tcp_listen then tcp_state_listen(conn_off) elsif state == tcp_synrcv then tcp_state_synrcv(conn_off) elsif state == tcp_estab then tcp_state_estab(conn_off) end if end procedure var byte old_state old_state = 0 procedure tcp_timeout(byte in conn_off) is var byte n, cnt, tmo_clk, txfid_hi, txfid_lo, hi, lo SetPointerRel ( RCV_NXT, conn_off , conn_rcv_nxt ) SetPointerRel ( SND_NXT, conn_off , conn_snd_nxt ) if conn_off == http_conn then tmo_clk = net_http_clk else tmo_clk = net_telnet_clk end if -- temporary debugging stuff for http only var byte state state = MemRdEx(conn_off , conn_state) if conn_off == http_conn then if state != old_state then if debug_mode then putc = "|" puthex(state) putc = "|" end if old_state = state end if end if I32_inc(tcp_clk) -- if n != 0, we have sent a message, and have not yet seen the acknowledgement for it. n = 0 if MemRdEx ( conn_off , conn_retran_hi ) != 0 then n = 1 end if if MemRdEx ( conn_off , conn_retran_lo ) != 0 then n = 1 end if -- I can see now why the RFC has an extra state to deal with this situation... oh well. -- First, see if we might be trying to re-transmit a packet. this might even be due -- to our trying to close the connection... So be careful. -- If not, then after the connection is closed, wait around a few seconds before -- allowing/listening again. This is recommended in the RFC and helps avoid confusion. if n == 0 then if MemRdEx(conn_off , conn_state) == tcp_closed then if tmo_clk > 5 then MemWrEx( conn_off , conn_retran_hi, 0) MemWrEx( conn_off , conn_retran_lo, 0) MemWrEx( conn_off , conn_retran_cnt, 0) MemWrEx( conn_off , conn_state, tcp_listen) if debug_mode then putc = "[" putc = "L" putc = "T" putc = "1" putc = "]" end if return end if end if end if -- this is a work-around. sometimes we seem to hang forever in the estab state. -- For any state other than "listen", we will enforce a timeout. -- we will use a bigger timeout here than the "packet acknowledgement" timeout. -- In fact, for telnet, we will use a very big timeout, since perhaps the human is -- just paused, thinking, and does not really want, or need, a timeout. if n == 0 then if MemRdEx(conn_off , conn_state) != tcp_listen then var byte tmo_val if ( conn_off == telnet_conn ) then tmo_val = 60 else tmo_val = 10 end if if tmo_clk > tmo_val then MemWrEx( conn_off , conn_retran_hi, 0) MemWrEx( conn_off , conn_retran_lo, 0) MemWrEx( conn_off , conn_retran_cnt, 0) MemWrEx( conn_off , conn_state, tcp_listen) if debug_mode then putc = "[" putc = "L" putc = "T" putc = "2" putc = "]" end if return end if end if end if if n == 0 then return end if -- we know that we've sent a message, but we haven't seen it's "ack" yet. -- Is it time to re-transmit? if tmo_clk < 5 then return end if if debug_mode then putc = "[" putc = "T" putc = "]" end if cnt = MemRdEx(conn_off , conn_retran_cnt) cnt = cnt + 1 MemWrEx( conn_off , conn_retran_cnt, cnt) -- too many re-tries ? If so, just give up. -- TODO: what is a good value for this? if cnt > 10 then MemWrEx( conn_off , conn_state, tcp_closed) MemWrEx( conn_off , conn_retran_hi, 0) MemWrEx( conn_off , conn_retran_lo, 0) MemWrEx( conn_off , conn_retran_cnt, 0) return end if -- re-transmit the packet if conn_off == telnet_conn then net_telnet_clk = 0 elsif conn_off == http_conn then net_http_clk = 0 end if txfid_hi = MemRdEx ( conn_off , conn_txfid_hi ) txfid_lo = MemRdEx ( conn_off , conn_txfid_lo ) hi = MemRdEx( conn_off , conn_retran_hi ) lo = MemRdEx( conn_off , conn_retran_lo ) net_getbuf ( txfid_hi, txfid_lo, 0, PKT, hi , lo ) net_txpkt( hi , lo ) end procedure function tcp_http_for_me return bit is -- Make sure this is a TCP packet. if MemRdEx(ip_off , 9) != 6 then return false end if -- is this http ? (byte order inverted) if ! MemComp16Ex ( tcp_off , 2 , ( http_port & 0xff ) , ( http_port >> 8 ) ) then return false end if return true end function function tcp_telnet_for_me return bit is -- Make sure this is a TCP packet. if MemRdEx(ip_off , 9) != 6 then return false end if -- is this telnet ? if (MemRdEx(tcp_off , 2) != ( telnet_port >> 8 ) ) | (MemRdEx(tcp_off , 3) != ( telnet_port & 0xff ) ) then return false end if return true end function procedure tcp_yield is if net_rxpoll then toggle_led if arp_is_for_me then arp_reply elsif etype_802_3 & ip_is_for_me then if icmp_is_echo then icmp_echo_reply elsif tcp_telnet_for_me then tcp_rx_event_processor(telnet_conn) elsif tcp_http_for_me then tcp_rx_event_processor(http_conn) else udp_is_for_me end if else putc = "{" putc = "E" putc = "T" putc = " " puthex( MemRdEx(PKT , 12) ) puthex( MemRdEx(PKT , 13) ) putc = "}" end if end if tcp_timeout(telnet_conn) tcp_timeout(http_conn) end procedure