ip65 technical reference

File : ip65/tcp.s

TCP (transmission control protocol) functions
NB to use these functions, you must pass "-DTCP" to ca65 when assembling "ip.s"
otherwise inbound tcp packets won't get passed in to tcp_process 
currently only a single outbound (client) connection is supported
to use, first call "tcp_connect" to create a connection. to send data on that connection, call "tcp_send". 
whenever data arrives, a call will be made to the routine pointed at by tcp_callback.

functions

functiondescription
tcp_close
tcp_connect
make outbound tcp connection
inputs:
 tcp_connect_ip:  destination ip address (4 bytes)
 AX: destination port (2 bytes)
 tcp_callback: vector to call when data arrives on this connection
outputs:
   carry flag is set if an error occured, clear otherwise
tcp_init
 initialize tcp
called automatically by ip_init if "ip.s" was compiled with -DTCP
 inputs: none
 outputs: none
tcp_listen
listen for an inbound tcp connection
this is a 'blocking' call, i.e. it will not return until a connection has been made
inputs:
 AX: destination port (2 bytes)
 tcp_callback: vector to call when data arrives on this connection
outputs:
   carry flag is set if an error occured, clear otherwise
tcp_process
process incoming tcp packet
called automatically by ip_process if "ip.s" was compiled with -DTCP
inputs:
 eth_inp: should contain an ethernet frame encapsulating an inbound tcp packet
outputs:
 none but if connection was found, an outbound message may be created, overwriting eth_outp
 also tcp_state and other tcp variables may be modified
tcp_send
send tcp data
inputs:
   tcp connection should already be opened
   tcp_send_data_len: length of data to send (exclusive of any headers)
   AX: pointer to buffer containing data to be sent
outputs:
   carry flag is set if an error occured, clear otherwise  
tcp_send_keep_alive
send an empty ACK packet on the current connection
inputs:
   none
outputs:
   carry flag is set if an error occured, clear otherwise
tcp_send_string
send a string over the current tcp connection
inputs:
   tcp connection should already be opened
   AX: pointer to buffer - data up to (but not including)
 the first nul byte will be sent. max of 255 bytes will be sent.
outputs:
   carry flag is set if an error occured, clear otherwise

variables

variabledescriptionsize (bytes)
tcp_callbackvector to routine to be called when data is received over tcp connection 2
tcp_connect_ipip address of remote server to connect to 4
tcp_connect_remote_port2
tcp_inbound_data_lengthlength of data just received over tcp connection 2
tcp_inbound_data_ptrpointer to data just recieved over tcp connection 2
tcp_send_data_lenlength (in bytes) of data to be sent over tcp connection 2
tcp_state1

constants

constantsdescriptionvalue
tcp_remote_ip

implementation

;TCP (transmission control protocol) functions
;NB to use these functions, you must pass "-DTCP" to ca65 when assembling "ip.s"
;otherwise inbound tcp packets won't get passed in to tcp_process 
;currently only a single outbound (client) connection is supported
;to use, first call "tcp_connect" to create a connection. to send data on that connection, call "tcp_send". 
;whenever data arrives, a call will be made to the routine pointed at by tcp_callback.


MAX_TCP_PACKETS_SENT=8     ;timeout after sending 8 messages will be about 7 seconds (1+2+3+4+5+6+7+8)/4

.include "../inc/common.i"
.ifndef KPR_API_VERSION_NUMBER
  .define EQU     =
  .include "../inc/kipper_constants.i"
.endif

.import ip65_error

.export tcp_init
.export tcp_process
.export tcp_connect
.export tcp_callback
.export tcp_connect_ip
.export tcp_send_data_len
.export tcp_send
.export tcp_send_string
.export tcp_close
.export tcp_listen
.export tcp_send_keep_alive
.export tcp_connect_remote_port
.export tcp_remote_ip
.export tcp_state
.export tcp_inbound_data_ptr
.export tcp_inbound_data_length


.import ip_calc_cksum
.import ip_send
.import ip_create_packet
.import ip_inp
.import ip_outp
.import ip65_process

.import check_for_abort_key
.import timer_read
.import ip65_random_word

.importzp acc32
.importzp op32
.importzp acc16

.import add_32_32
.import add_16_32
.import cmp_32_32
.import cmp_16_16
.import sub_16_16



.importzp ip_cksum_ptr
.importzp ip_header_cksum
.importzp ip_src
.importzp ip_dest
.importzp ip_data
.importzp ip_proto
.importzp ip_proto_tcp
.importzp ip_id
.importzp ip_len

.import copymem
.importzp copy_src
.importzp copy_dest

.import cfg_ip

tcp_cxn_state_closed      = 0 
tcp_cxn_state_listening   = 1  ;(waiting for an inbound SYN)
tcp_cxn_state_syn_sent    = 2  ;(waiting for an inbound SYN/ACK)
tcp_cxn_state_established = 3  ;  

; tcp packet offsets
tcp_inp    = ip_inp + ip_data  ;pointer to tcp packet inside inbound ethernet frame
tcp_outp  = ip_outp + ip_data ;pointer to tcp packet inside outbound ethernet frame
tcp_src_port  = 0 ;offset of source port field in tcp packet
tcp_dest_port  = 2 ;offset of destination port field in tcp packet
tcp_seq    = 4 ;offset of sequence number field in tcp packet
tcp_ack  = 8 ;offset of acknowledgement field in tcp packet
tcp_header_length  = 12 ;offset of header length field in tcp packet
tcp_flags_field  = 13 ;offset of flags field in tcp packet
tcp_window_size = 14 ; offset of window size field in tcp packet
tcp_checksum = 16 ; offset of checksum field in tcp packet
tcp_urgent_pointer = 18 ; offset of urgent pointer field in tcp packet
tcp_data=20   ;offset of data in tcp packet 

; virtual header
tcp_vh    = tcp_outp - 12
tcp_vh_src  = 0
tcp_vh_dest  = 4
tcp_vh_zero  = 8
tcp_vh_proto  = 9
tcp_vh_len  = 10

;
tcp_flag_FIN  =1
tcp_flag_SYN  =2
tcp_flag_RST  =4
tcp_flag_PSH  =8
tcp_flag_ACK  =16
tcp_flag_URG  =32




.segment "TCP_VARS"
tcp_state:  .res 1
tcp_local_port: .res 2
tcp_remote_port: .res 2
tcp_remote_ip: .res 4
tcp_sequence_number: .res 4
tcp_ack_number: .res 4
tcp_data_ptr: .res 2
tcp_data_len: .res 2
tcp_send_data_ptr: .res 2
tcp_send_data_len: .res 2 ;length (in bytes) of data to be sent over tcp connection
tcp_callback: .res 2 ;vector to routine to be called when data is received over tcp connection
tcp_flags: .res 1
tcp_fin_sent: .res 1

tcp_listen_port: .res 2

tcp_inbound_data_ptr: .res 2 ;pointer to data just recieved over tcp connection
tcp_inbound_data_length: .res 2 ;length of data just received over tcp connection
;(if this is $ffff, that means "end of file", i.e. remote end has closed connection)
tcp_connect_sequence_number: .res 4   ;the seq number we will next send out
tcp_connect_expected_ack_number: .res 4 ;what we expect to see in the next inbound ack
tcp_connect_ack_number: .res 4 ;what we will next ack
tcp_connect_last_received_seq_number: .res 4 ;the seq field in the last inbound packet for this connection
tcp_connect_last_ack: .res 4 ;ack field in the last inbound packet for this connection
tcp_connect_local_port: .res 2 ;
tcp_connect_remote_port: .res 2
tcp_connect_ip: .res 4 ;ip address of remote server to connect to


tcp_timer:  .res 1
tcp_loop_count: .res 1
tcp_packet_sent_count: .res 1


.code

; initialize tcp
;called automatically by ip_init if "ip.s" was compiled with -DTCP
; inputs: none
; outputs: none
tcp_init:
  
  rts


jmp_to_callback:
  jmp (tcp_callback)

;listen for an inbound tcp connection
;this is a 'blocking' call, i.e. it will not return until a connection has been made
;inputs:
; AX: destination port (2 bytes)
; tcp_callback: vector to call when data arrives on this connection
;outputs:
;   carry flag is set if an error occured, clear otherwise
tcp_listen:
  stax  tcp_listen_port
  lda #tcp_cxn_state_listening
  sta tcp_state
  lda #0  ;reset the "packet sent" counter
  sta tcp_packet_sent_count
  sta tcp_fin_sent
  
  ;set the low word of seq number to $0000, high word to something random
  sta tcp_connect_sequence_number
  sta tcp_connect_sequence_number+1
  jsr ip65_random_word
  stax  tcp_connect_sequence_number+2
  jsr set_expected_ack;       ;due to various ugly hacks, the 'expected ack' value is now what is put into the 'SEQ' field in outbound packets 
@listen_loop:
  jsr ip65_process
  jsr check_for_abort_key
  bcc @no_abort
  lda #KPR_ERROR_ABORTED_BY_USER
  sta ip65_error
  rts
@no_abort:  
  lda #tcp_cxn_state_listening  
  cmp tcp_state
  beq @listen_loop
    
  jmp tcp_connection_established
  rts

;make outbound tcp connection
;inputs:
; tcp_connect_ip:  destination ip address (4 bytes)
; AX: destination port (2 bytes)
; tcp_callback: vector to call when data arrives on this connection
;outputs:
;   carry flag is set if an error occured, clear otherwise
tcp_connect:
  stax  tcp_connect_remote_port
  jsr ip65_random_word
  stax  tcp_connect_local_port
  lda #tcp_cxn_state_syn_sent
  sta tcp_state
  lda #0  ;reset the "packet sent" counter
  sta tcp_packet_sent_count
  sta tcp_fin_sent
  
  ;set the low word of seq number to $0000, high word to something random
  sta tcp_connect_sequence_number
  sta tcp_connect_sequence_number+1
  jsr ip65_random_word
  stax  tcp_connect_sequence_number+2
  
  
@tcp_polling_loop:

  ;create a SYN packet
  lda #tcp_flag_SYN
  sta tcp_flags
  lda  #0
  sta  tcp_data_len
  sta  tcp_data_len+1
  
  ldx #3        ; 
:  lda tcp_connect_ip,x
  sta tcp_remote_ip,x
  lda tcp_connect_sequence_number,x
  sta tcp_sequence_number,x
  dex
  bpl :-
  ldax  tcp_connect_local_port
  stax  tcp_local_port  
  ldax  tcp_connect_remote_port
  stax  tcp_remote_port
  
  jsr tcp_send_packet
  lda tcp_packet_sent_count
  adc #1
  sta tcp_loop_count       ;we wait a bit longer between each resend  
@outer_delay_loop: 
  jsr timer_read
  stx tcp_timer            ;we only care about the high byte  
@inner_delay_loop:  
  jsr ip65_process
  jsr check_for_abort_key
  bcc @no_abort
  lda #KPR_ERROR_ABORTED_BY_USER
  sta ip65_error
  rts
@no_abort:  
  lda tcp_state  
  cmp #tcp_cxn_state_syn_sent
  bne @got_a_response

  jsr timer_read
  cpx tcp_timer            ;this will tick over after about 1/4 of a second
  beq @inner_delay_loop
  
  dec tcp_loop_count
  bne @outer_delay_loop  

  
  inc tcp_packet_sent_count
  lda tcp_packet_sent_count
  cmp #MAX_TCP_PACKETS_SENT-1
  bpl @too_many_messages_sent
  jmp @tcp_polling_loop

@too_many_messages_sent:
@failed:
  lda #tcp_cxn_state_closed
  sta tcp_state
  lda #KPR_ERROR_TIMEOUT_ON_RECEIVE
  sta ip65_error  
  sec             ;signal an error
  rts
@got_a_response:
  lda tcp_state  
  cmp #tcp_cxn_state_closed
  bne @was_accepted
  sec     ;if we got here, then the other side sent a RST or FIN, so signal an error to the caller
  rts
@was_accepted:
tcp_connection_established:
;inc the sequence number to cover the SYN we have sent
  ldax  #tcp_connect_sequence_number
  stax  acc32
  ldax  #$01
  jsr add_16_32

set_expected_ack:
;set the expected ack number with current seq number
  ldx #3        ; 
:  lda tcp_connect_sequence_number,x
  sta tcp_connect_expected_ack_number,x
  dex
  bpl :-

  clc
  rts

tcp_close:
;close the current connection
;inputs:
;   none
;outputs:
;   carry flag is set if an error occured, clear otherwise


  lda tcp_state
  cmp #tcp_cxn_state_established
  beq :+
@connection_closed:  
  lda #tcp_cxn_state_closed
  sta tcp_state
  clc
  rts
:  
  ;increment the expected sequence number for the SYN we are about to send
  ldax #tcp_connect_expected_ack_number
  stax acc32
  ldax #1
  sta tcp_fin_sent
  jsr add_16_32


@send_fin_loop:
  lda #tcp_flag_FIN+tcp_flag_ACK
  sta tcp_flags
  ldax  #0
  stax  tcp_data_len
  ldx #3        ; 
:  lda tcp_connect_ip,x
  sta tcp_remote_ip,x
  lda tcp_connect_ack_number,x
  sta tcp_ack_number,x
  lda tcp_connect_sequence_number,x
  sta tcp_sequence_number,x
  dex
  bpl :-
  ldax  tcp_connect_local_port
  stax  tcp_local_port  
  ldax  tcp_connect_remote_port
  stax  tcp_remote_port  
  
  jsr tcp_send_packet

  lda tcp_packet_sent_count
  adc #1
  sta tcp_loop_count       ;we wait a bit longer between each resend  
@outer_delay_loop: 
  jsr timer_read
  stx tcp_timer            ;we only care about the high byte  
@inner_delay_loop:  
  jsr ip65_process
  lda tcp_state
  cmp #tcp_cxn_state_established
  bne @connection_closed

  jsr timer_read
  cpx tcp_timer            ;this will tick over after about 1/4 of a second
  beq @inner_delay_loop
  
  dec tcp_loop_count
  bne @outer_delay_loop  
  
  inc tcp_packet_sent_count
  lda tcp_packet_sent_count
  cmp #MAX_TCP_PACKETS_SENT-1
  bpl @too_many_messages_sent
  jmp @send_fin_loop
@too_many_messages_sent:
@failed:
  lda #tcp_cxn_state_closed
  sta tcp_state
  lda #KPR_ERROR_TIMEOUT_ON_RECEIVE
  sta ip65_error  
  sec             ;signal an error
  rts



;send a string over the current tcp connection
;inputs:
;   tcp connection should already be opened
;   AX: pointer to buffer - data up to (but not including)
; the first nul byte will be sent. max of 255 bytes will be sent.
;outputs:
;   carry flag is set if an error occured, clear otherwise
tcp_send_string:
  stax tcp_send_data_ptr
  stax copy_src
  lda #0
  tay
  sta tcp_send_data_len
  sta tcp_send_data_len+1
  lda (copy_src),y
  bne @find_end_of_string
  rts ; if the string is empty, don't send anything!
@find_end_of_string:  
  lda (copy_src),y
  beq @done  
  inc tcp_send_data_len
  iny
  bne @find_end_of_string
@done:  
  ldax tcp_send_data_ptr
  ;now we can fall through into tcp_send
  

;send tcp data
;inputs:
;   tcp connection should already be opened
;   tcp_send_data_len: length of data to send (exclusive of any headers)
;   AX: pointer to buffer containing data to be sent
;outputs:
;   carry flag is set if an error occured, clear otherwise  
tcp_send:

  stax tcp_send_data_ptr
  
  lda tcp_state
  cmp #tcp_cxn_state_established
  beq @connection_established
  lda #KPR_ERROR_CONNECTION_CLOSED
  sta ip65_error
  sec
  rts
  lda #0  ;reset the "packet sent" counter
  sta tcp_packet_sent_count

@connection_established:
  ;increment the expected sequence number
  ldax #tcp_connect_expected_ack_number
  stax acc32
  ldax tcp_send_data_len
  jsr add_16_32
  

@tcp_polling_loop:

  ;create a data packet
  lda #tcp_flag_ACK+tcp_flag_PSH
  sta tcp_flags
  ldax tcp_send_data_len
  stax tcp_data_len
  
  ldax tcp_send_data_ptr
  stax tcp_data_ptr
  
  ldx #3        ; 
:  lda tcp_connect_ip,x
  sta tcp_remote_ip,x
  lda tcp_connect_sequence_number,x
  sta tcp_sequence_number,x

  dex
  bpl :-
  ldax  tcp_connect_local_port
  stax  tcp_local_port  
  ldax  tcp_connect_remote_port
  stax  tcp_remote_port
  
  
  jsr tcp_send_packet
  lda tcp_packet_sent_count
  adc #1
  sta tcp_loop_count       ;we wait a bit longer between each resend  
@outer_delay_loop: 
  jsr timer_read
  stx tcp_timer            ;we only care about the high byte  
@inner_delay_loop:  
  jsr ip65_process
  jsr check_for_abort_key
  bcc @no_abort
  lda #KPR_ERROR_ABORTED_BY_USER
  sta ip65_error
  lda #tcp_cxn_state_closed
  sta tcp_state
  
  rts
@no_abort:  
  ldax #tcp_connect_last_ack
  stax acc32
  ldax #tcp_connect_expected_ack_number
  stax op32
  jsr cmp_32_32
  beq @got_ack

  jsr timer_read
  cpx tcp_timer            ;this will tick over after about 1/4 of a second
  beq @inner_delay_loop
  
  dec tcp_loop_count
  bne @outer_delay_loop  

  
  inc tcp_packet_sent_count
  lda tcp_packet_sent_count
  cmp #MAX_TCP_PACKETS_SENT-1
  bpl @too_many_messages_sent
  jmp @tcp_polling_loop

@too_many_messages_sent:
@failed:

  lda #tcp_cxn_state_closed
  sta tcp_state
  lda #KPR_ERROR_TIMEOUT_ON_RECEIVE
  sta ip65_error  
  sec             ;signal an error
  rts
@got_ack: 
  ;finished - now we need to advance the sequence number for the data we just sent
  ldax #tcp_connect_sequence_number
  stax acc32
  ldax tcp_send_data_len
  jsr add_16_32

  clc
  rts


;send a single tcp packet 
;inputs:
; tcp_remote_ip: IP address of destination server
; tcp_remote_port: destination tcp port 
; tcp_local_port: source tcp port
; tcp_flags: 6 bit flags
; tcp_data_ptr: pointer to data to include in this packet
; tcp_data_len: length of data pointed at by tcp_data_ptr
;outputs:
;   carry flag is set if an error occured, clear otherwise
tcp_send_packet:
  ldax  tcp_data_ptr
  stax copy_src      ; copy data to output buffer
  ldax #tcp_outp + tcp_data
  stax copy_dest
  ldax tcp_data_len
  jsr copymem

  ldx #3        ; copy virtual header addresses
:  lda tcp_remote_ip,x
  sta tcp_vh + tcp_vh_dest,x  ; set virtual header destination
  lda cfg_ip,x
  sta tcp_vh + tcp_vh_src,x  ; set virtual header source
  dex
  bpl :-

  lda tcp_local_port    ; copy source port
  sta tcp_outp + tcp_src_port + 1
  lda tcp_local_port + 1
  sta tcp_outp + tcp_src_port

  lda tcp_remote_port    ; copy destination port
  sta tcp_outp + tcp_dest_port + 1
  lda tcp_remote_port + 1
  sta tcp_outp + tcp_dest_port

  ldx #3        ; copy sequence and ack (if ACK flag set) numbers (in reverse order)
  ldy #0
:  lda tcp_sequence_number,x
  sta tcp_outp + tcp_seq,y
  lda #tcp_flag_ACK
  bit tcp_flags
  bne @ack_set 
  lda #0
  beq @sta_ack
  @ack_set:
  lda tcp_ack_number,x
  @sta_ack:
  sta tcp_outp + tcp_ack,y
  iny
  dex
  bpl :-

  lda #$50    ;4 bit header length in 32bit words + 4 bits of zero
  sta tcp_outp+tcp_header_length
  lda tcp_flags
  sta tcp_outp+tcp_flags_field
  
  lda #ip_proto_tcp
  sta tcp_vh + tcp_vh_proto

  ldax  #$0010  ;$1000 in network byte order
  stax  tcp_outp+tcp_window_size

  lda #0        ; clear checksum
  sta tcp_outp + tcp_checksum
  sta tcp_outp + tcp_checksum + 1
  sta tcp_vh + tcp_vh_zero  ; clear virtual header zero byte

  ldax #tcp_vh      ; checksum pointer to virtual header
  stax ip_cksum_ptr

  lda tcp_data_len    ; copy length + 20
  clc
  adc #20
  sta tcp_vh + tcp_vh_len + 1  ; lsb for virtual header
  tay
  lda tcp_data_len + 1
  adc #0
  sta tcp_vh + tcp_vh_len    ; msb for virtual header

  tax        ; length to A/X
  tya

  clc        ; add 12 bytes for virtual header
  adc #12
  bcc :+
  inx
:
  jsr ip_calc_cksum    ; calculate checksum
  stax tcp_outp + tcp_checksum

  ldx #3        ; copy addresses
:  lda tcp_remote_ip,x
  sta ip_outp + ip_dest,x    ; set ip destination address
  dex
  bpl :-

  jsr ip_create_packet    ; create ip packet template

  lda tcp_data_len   ; ip len = tcp data length +20 byte ip header + 20 byte tcp header
  ldx tcp_data_len +1
  clc
  adc #40 
  bcc :+
  inx
:  sta ip_outp + ip_len + 1  ; set length
  stx ip_outp + ip_len

  ldax #$1234          ; set ID
  stax ip_outp + ip_id

  lda #ip_proto_tcp    ; set protocol
  sta ip_outp + ip_proto

  jmp ip_send      ; send packet, sec on error



check_current_connection:
;see if the ip packet we just got is for a valid (non-closed) tcp connection
;inputs:
; eth_inp: should contain an ethernet frame encapsulating an inbound tcp packet
;outputs:
; carry flag clear if inbound tcp packet part of existing connection

  
  lda tcp_state
  cmp #tcp_cxn_state_closed
  bne @connection_not_closed
  sec
  rts
@connection_not_closed:  
  ldax  #ip_inp+ip_src
  stax  acc32
  ldax  #tcp_connect_ip
  stax  op32
  jsr   cmp_32_32
  beq @remote_ip_matches
  
  sec
  rts
@remote_ip_matches:
  ldax  tcp_inp+tcp_src_port
  stax  acc16
  lda   tcp_connect_remote_port+1 ;this value in reverse byte order to how it is presented in the TCP header
  ldx   tcp_connect_remote_port 
  jsr   cmp_16_16
  beq @remote_port_matches
  sec
  rts
@remote_port_matches:
  ldax  tcp_inp+tcp_dest_port
  stax  acc16
  lda   tcp_connect_local_port+1 ;this value in reverse byte order to how it is presented in the TCP header
  ldx   tcp_connect_local_port 
  jsr   cmp_16_16
  beq   @local_port_matches
  sec
  rts
@local_port_matches:
  clc
  rts
  
;process incoming tcp packet
;called automatically by ip_process if "ip.s" was compiled with -DTCP
;inputs:
; eth_inp: should contain an ethernet frame encapsulating an inbound tcp packet
;outputs:
; none but if connection was found, an outbound message may be created, overwriting eth_outp
; also tcp_state and other tcp variables may be modified
tcp_process:
  
  lda #tcp_flag_RST
  bit tcp_inp+tcp_flags_field
  beq @not_reset
  jsr check_current_connection
  bcs @not_current_connection_on_rst  
  ;for some reason, search.twitter.com is sending RSTs with ID=$1234 (i.e. echoing the inbound ID)
  ;but then keeps the connection open and ends up sending the file.
  ;so lets ignore a reset with ID=$1234
  lda ip_inp+ip_id
  cmp #$34
  bne @not_invalid_reset
  lda ip_inp+ip_id+1
  cmp #$12  
  bne @not_invalid_reset
  jmp @send_ack
@not_invalid_reset:
  ;connection has been reset so mark it as closed    
  lda #tcp_cxn_state_closed
  sta tcp_state
  lda #KPR_ERROR_CONNECTION_RESET_BY_PEER
  sta ip65_error
  
  lda #$ff
  sta tcp_inbound_data_length
  sta tcp_inbound_data_length+1
  jsr jmp_to_callback   ;let the caller see the connection has closed
  
@not_current_connection_on_rst:
  ;if we get a reset for a closed or nonexistent connection, then ignore it  
  rts
@not_reset:
  lda tcp_inp+tcp_flags_field
  cmp #tcp_flag_SYN+tcp_flag_ACK
  bne @not_syn_ack
  
  ;it's a SYN/ACK
  jsr check_current_connection
  bcc @current_connection_on_syn_ack
  ;if we get a SYN/ACK for something that aint the connection we're expecting, 
  ;terminate with extreme prejudice
  jmp @send_rst 
@current_connection_on_syn_ack:  
  lda tcp_state
  cmp #tcp_cxn_state_syn_sent
  bne @not_expecting_syn_ack
  ;this IS the syn/ack we are waiting for :-)
  ldx #3        ; copy sequence number to ack (in reverse order)
  ldy #0
:  lda tcp_inp + tcp_seq,y
  sta tcp_connect_ack_number,x
  iny
  dex
  bpl :-

  ldax #tcp_connect_ack_number
  stax acc32
  ldax  #$0001  ;
  jsr add_16_32 ;increment the ACK counter by 1, for the SYN we just received


  lda #tcp_cxn_state_established
  sta tcp_state
  
@not_expecting_syn_ack:   
;we get a SYN/ACK for the current connection,
;but we're not expecting it, it's probably
;a retransmist - just ACK it
  jmp @send_ack
  
  
@not_syn_ack:  

;is it an ACK - alone or with PSH/URGENT but not a SYN/ACK?
  lda #tcp_flag_ACK
  bit tcp_inp+tcp_flags_field
  bne @ack
  jmp @not_ack
@ack:  
  ;is this the current connection?
  jsr check_current_connection
  bcc @current_connection_on_ack
  ;if we get an ACK for something that is not the current connection
  ;we should send a RST
  jmp @send_rst
@current_connection_on_ack:
  ;if it's an ACK, then record the last ACK (reorder the bytes in the process)
  ldx #3        ; copy seq & ack fields (in reverse order)
  ldy #0
:  lda tcp_inp + tcp_ack,y
  sta tcp_connect_last_ack,x
  lda tcp_inp + tcp_seq,y
  sta tcp_connect_last_received_seq_number,x
  iny
  dex
  bpl :-
  
  ;was this the next sequence number we're waiting for?
  ldax  #tcp_connect_ack_number
  stax  acc32
  ldax  #tcp_connect_last_received_seq_number
  stax  op32
  jsr   cmp_32_32
  
  bne   @not_expected_seq_number


  
  ;what is the size of data in this packet?
  lda ip_inp+ip_len+1 ;payload length (lo byte)
  sta acc16
  lda ip_inp+ip_len ;payload length (hi byte)
  sta acc16+1
  lda tcp_inp+tcp_header_length   ;high 4 bits is header length in 32 bit words
  lsr ; A=A/2
  lsr ; A=A/2
  clc ; A now equal to tcp header length in bytes
  adc #20 ;add 20 bytes for IP header. this gives length of IP +TCP headers
  ldx #0
  sta tcp_header_length
  jsr sub_16_16
  
  ;acc16 now contains the length of data in this TCP packet
  
  lda acc16
  sta tcp_inbound_data_length
  lda acc16+1
  sta tcp_inbound_data_length+1
  bne @not_empty_packet
  lda acc16
  bne @not_empty_packet
  jmp @empty_packet  
@not_empty_packet:
  
  
  ;calculate ptr to tcp data
  clc
  lda tcp_header_length
  adc #ip_inp
  adc #0
  sta tcp_inbound_data_ptr+1
  
  ;  do a callback
  jsr jmp_to_callback
  
    
  ; move ack ptr along
  ldax #tcp_connect_ack_number
  stax acc32
  ldax tcp_inbound_data_length
  jsr add_16_32 
  
  
@not_expected_seq_number: ;send an ACK with the sequence number we expect  

  ;send the ACK for any data in this packet, then return to check for FIN flag
  jsr @send_ack 

@not_ack: 
@empty_packet:  

;is it a FIN?  
  lda #tcp_flag_FIN
  bit tcp_inp+tcp_flags_field
  bne @fin
  jmp @not_fin
@fin:  
  ;is this the current connection?
  jsr check_current_connection
  bcc :+
  jmp @send_rst ;reset if not current connection
:  
  ldx #3        ; copy seq field (in reverse order)
  ldy #0
:  lda tcp_inp + tcp_seq,y
  sta tcp_connect_last_received_seq_number,x
  iny
  dex
  bpl :-
  
  ;was this the next sequence number we're waiting for?
  ldax  #tcp_connect_ack_number
  stax  acc32
  ldax  #tcp_connect_last_received_seq_number
  stax  op32
  jsr   cmp_32_32
  
  beq :+
  rts ;bail if not expected sequence number
:  

  ;set the length to $ffff
  lda #$ff
  sta tcp_inbound_data_length
  sta tcp_inbound_data_length+1
  jsr jmp_to_callback   ;let the caller see the connection has closed   
    

  lda #tcp_cxn_state_closed
  sta tcp_state

  ;send a FIN/ACK
  ; move ack ptr along for the inbound FIN
  ldax #tcp_connect_ack_number
  stax acc32
  ldax #$01
  sta  tcp_fin_sent
  jsr add_16_32

  ;if we've already sent a FIN then just send back an ACK 
  lda tcp_fin_sent
  beq @send_fin_ack
;if we get here, we've sent a FIN, and just received an inbound FIN.
;when we sent the fin, we didn't update the sequence number, since
;we want to use the old sequence on every resend of that FIN
;now that our fin has been ACKed, we need to inc the sequence number
;and then send another ACK.

  ldax #tcp_connect_sequence_number
  stax acc32
  ldax  #$0001  ;
  jsr add_16_32 ;increment the SEQ counter by 1, for the FIN we have been sending

  lda #tcp_flag_ACK  
  jmp @send_packet
  
@send_fin_ack:  
 
  lda #tcp_flag_FIN+tcp_flag_ACK
  
  jmp @send_packet

  
@not_fin:

  lda tcp_inp+tcp_flags_field
  cmp #tcp_flag_SYN
  beq @syn 
  jmp @not_syn
@syn:  
  
  ;is this the port we are listening on?
  lda tcp_inp+tcp_dest_port+1
  cmp tcp_listen_port
  bne @decline_syn_with_reset
  lda tcp_inp+tcp_dest_port
  cmp tcp_listen_port+1
  bne @decline_syn_with_reset
  
  ;it's the right port - are we actually waiting for a connecting?
  lda #tcp_cxn_state_listening  
  cmp tcp_state  
  beq @this_is_connection_we_are_waiting_for
  ;is this the current connection? that would mean our ACK got lost, so resend
  jsr check_current_connection
  bcc @this_is_connection_we_are_waiting_for
  
  rts ;if we've currently got a connection open, then ignore any new requests
      ;the sender will timeout and resend the SYN, by which time we may be
      ;ready to accept it again.
  
@this_is_connection_we_are_waiting_for:

  ; copy sequence number to ack (in reverse order) and remote IP
  ldx #3        
  ldy #0
:  lda tcp_inp + tcp_seq,y
  sta tcp_connect_ack_number,x
  lda ip_inp+ip_src,x
  sta tcp_connect_ip,x
  iny
  dex
  bpl :-

  ;copy ports
  ldax tcp_listen_port
  stax tcp_connect_local_port
  
  lda tcp_inp+tcp_src_port+1
  sta tcp_connect_remote_port
  lda tcp_inp+tcp_src_port
  sta tcp_connect_remote_port+1

  lda #tcp_cxn_state_established
  sta tcp_state

  ldax #tcp_connect_ack_number 
  stax acc32
  ldax  #$0001  ;
  jsr add_16_32 ;increment the ACK counter by 1, for the SYN we just received
  lda #tcp_flag_SYN+tcp_flag_ACK
  jmp @send_packet
  
@decline_syn_with_reset:
;create a RST packet
  ldx #3        ; copy sequence number to ack (in reverse order)
  ldy #0
:  lda tcp_inp + tcp_seq,y
  sta tcp_ack_number,x
  iny
  dex
  bpl :-

  ldax #tcp_ack_number 
  stax acc32
  ldax  #$0001  ;
  jsr add_16_32 ;increment the ACK counter by 1, for the SYN we just received
  
@send_rst:
  
  lda #tcp_flag_RST+tcp_flag_ACK
  sta tcp_flags
  ldax  #0
  stax  tcp_data_len
  ldx #3        ; 
:  lda ip_inp+ip_src,x
  sta tcp_remote_ip,x
  dex
  bpl :-
  
  ;copy src/dest ports in inverted byte order
  lda tcp_inp+tcp_src_port
  sta tcp_remote_port+1
  lda tcp_inp+tcp_src_port+1
  sta tcp_remote_port
  
  lda tcp_inp+tcp_dest_port
  sta tcp_local_port+1
  lda tcp_inp+tcp_dest_port+1
  sta tcp_local_port
  
  jsr tcp_send_packet
  rts

@not_syn:
  rts

@send_ack:

;create an ACK packet
  lda #tcp_flag_ACK
  
@send_packet:  
  sta tcp_flags
  ldax  #0
  stax  tcp_data_len
  ldx #3        ; 
:  lda tcp_connect_ip,x
  sta tcp_remote_ip,x
  lda tcp_connect_ack_number,x
  sta tcp_ack_number,x
;if we have just sent a packet out, we may not yet have updated tcp_connect_sequence_number yet
;so use current value of tcp_connect_expected_ack_number as outbound sequence number instead
  lda tcp_connect_expected_ack_number,x   
  sta tcp_sequence_number,x
  dex
  bpl :-
  ldax  tcp_connect_local_port
  stax  tcp_local_port  
  ldax  tcp_connect_remote_port
  stax  tcp_remote_port
  
  
  jmp tcp_send_packet


;send an empty ACK packet on the current connection
;inputs:
;   none
;outputs:
;   carry flag is set if an error occured, clear otherwise
tcp_send_keep_alive=@send_ack

;-- LICENSE FOR tcp.s --
; The contents of this file are subject to the Mozilla Public License
; Version 1.1 (the "License"); you may not use this file except in
; compliance with the License. You may obtain a copy of the License at
; http://www.mozilla.org/MPL/
; 
; Software distributed under the License is distributed on an "AS IS"
; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
; License for the specific language governing rights and limitations
; under the License.
; 
; The Original Code is ip65.
; 
; The Initial Developer of the Original Code is Jonno Downes,
; jonno@jamtronix.com.
; Portions created by the Initial Developer are Copyright (C) 2009
; Jonno Downes. All Rights Reserved.  
; -- LICENSE END --