emailler/ip65/tcp.s
Oliver Schmidt 6a296b2058 Improved URL selector handling.
parse_url stores the URL selector in the output_buffer - which is currently 520 bytes. A new entry point called parse_url_buffer was added which instead stores the URL selector in a buffer provided by the user.

url_download now calls the new parse_url_buffer instead of parse_url. The buffer for the URL selector is simply the download_buffer. So the download_buffer is used twice: First to hold the URL selector to be sent as request to the server and then to hold the response received from the server.

However, the URL selector still can't exceed the MSS (aka 1460 bytes).

Note: The User-Agent string was shortened by two bytes as that allows a "default" URL (incl. 'http://' but without port number) of exactly 1400 bytes to end up as 1460 bytes URL selector.
2018-11-13 13:19:47 +01:00

1037 lines
27 KiB
ArmAsm

; 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 "zeropage.inc"
.include "../inc/common.inc"
.include "../inc/error.inc"
.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
.bss
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 #IP65_ERROR_ABORTED_BY_USER
sta ip65_error
rts
@no_abort:
lda #tcp_cxn_state_listening
cmp tcp_state
beq @listen_loop
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 :-
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 #IP65_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 #IP65_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:
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 #IP65_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.
; outputs:
; carry flag is set if an error occured, clear otherwise
tcp_send_string:
stax tcp_send_data_ptr
stax ptr1
lda #0
tay
sta tcp_send_data_len
sta tcp_send_data_len+1
lda (ptr1),y
bne @find_end_of_string
rts ; if the string is empty, don't send anything!
@find_end_of_string:
lda (ptr1),y
beq @done
inc tcp_send_data_len
iny
bne @find_end_of_string
inc tcp_send_data_len+1
inc ptr1+1
jmp @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 (up to 1460 bytes)
; 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 #IP65_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 #IP65_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 #IP65_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 #$b405 ; $05b4 (1460) 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
; 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
check_current_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 #IP65_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
jsr tcp_connection_established
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
sta tcp_inbound_data_ptr
lda #>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
; send an ACK with the sequence number we expect
@not_expected_seq_number:
; 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
@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 --