; ICMP implementation

.include "../inc/common.inc"
.include "../inc/error.inc"

.export icmp_init
.export icmp_process
.export icmp_add_listener
.export icmp_remove_listener

.export icmp_callback

.export icmp_inp
.export icmp_outp
.exportzp icmp_type
.exportzp icmp_code
.exportzp icmp_cksum
.exportzp icmp_data

.ifdef TCP
  .export icmp_echo_ip
  .export icmp_send_echo
  .export icmp_ping
.endif

.import ip65_process
.import ip65_error

.import ip_calc_cksum
.import ip_inp
.import ip_outp
.import ip_broadcast
.import ip_send
.import ip_create_packet
.importzp ip_proto
.importzp ip_proto_icmp

.importzp ip_cksum_ptr
.importzp ip_header_cksum
.importzp ip_src
.importzp ip_dest
.importzp ip_data
.importzp ip_len

.import eth_tx
.import eth_inp
.import eth_inp_len
.import eth_outp
.import eth_outp_len
.import timer_read
.import timer_timeout


.data

icmp_cbtmp: jmp $0000           ; temporary vector - address filled in later


.bss

; argument for icmp_add_listener
icmp_callback: .res 2

; icmp callbacks
icmp_cbmax = 2
icmp_cbveclo: .res icmp_cbmax   ; table of listener vectors (lsb)
icmp_cbvechi: .res icmp_cbmax   ; table of listener vectors (msb)
icmp_cbtype:  .res icmp_cbmax   ; table of listener types
icmp_cbcount: .res 1            ; number of active listeners

; icmp packet offsets
icmp_inp   = ip_inp + ip_data   ; pointer to inbound icmp packet
icmp_outp  = ip_outp + ip_data  ; pointer to outbound icmp packet
icmp_type  = 0                  ; offset of 'type' field in icmp packet
icmp_code  = 1                  ; offset of 'code' field in icmp packet
icmp_cksum = 2                  ; offset of 'checksum' field in icmp packet
icmp_data  = 4                  ; offset of 'data' field in icmp packet

; icmp echo packet offsets
icmp_echo_id   = 4              ; offset of 'id' field in icmp echo request/echo response
icmp_echo_seq  = 6              ; offset of 'sequence' field in icmp echo request/echo response
icmp_echo_data = 8              ; offset of 'data' field in icmp echo request/echo response

; icmp type codes
icmp_msg_type_echo_reply              =  0
icmp_msg_type_destination_unreachable =  3
icmp_msg_type_source_quench           =  4
icmp_msg_type_redirect                =  5
icmp_msg_type_echo_request            =  8
icmp_msg_type_time_exceeded           = 11
icmp_msg_type_paramater_problem       = 12
icmp_msg_type_timestamp               = 13
icmp_msg_type_timestamp_reply         = 14
icmp_msg_type_information_request     = 15
icmp_msg_type_information_reply       = 16

; ping states
ping_state_request_sent      = 0
ping_state_response_received = 1


.ifdef TCP

.bss

icmp_echo_ip:  .res 4           ; destination IP address for echo request ("ping")
icmp_echo_cnt: .res 1           ; ping sequence counter
ping_state:    .res 1
ping_timer:    .res 2

.endif


.code

; initialize icmp
; inputs: none
; outputs: none
icmp_init:
  lda #0
  sta icmp_cbcount
  rts

; process incoming icmp packet
; inputs:
; eth_inp points to an ethernet frame containing an icmp packet
; outputs:
; carry flag - set on any error, clear if OK
; if inbound packet is a request (e.g. 'echo request') and an icmp listener
; has been installed, then an appropriate response message will  be
; generated and sent out (overwriting the eth_outp buffer)
icmp_process:
  lda icmp_inp + icmp_type
  cmp #icmp_msg_type_echo_request       ; ping
  beq @echo

  lda icmp_cbcount              ; any installed icmp listeners?
  beq @drop

  ldx icmp_cbcount              ; check listened types
  dex
: lda icmp_cbtype,x
  cmp icmp_inp + icmp_type
  beq @handle                   ; found a match
  dex
  bpl :-

@drop:
  sec
  rts

@handle:
  lda icmp_cbveclo,x            ; copy vector
  sta icmp_cbtmp + 1
  lda icmp_cbvechi,x
  sta icmp_cbtmp + 2
  jsr icmp_cbtmp                ; call listener
  clc
  rts

@echo:
  lda ip_broadcast              ; check if packet is broadcast
  beq @notbc
  sec                           ; don't reply to broadcast pings
  rts
@notbc:
  ldx #5
: lda eth_inp,x                 ; swap dest and src mac
  sta eth_outp + 6,x
  lda eth_inp + 6,x
  sta eth_outp,x
  dex
  bpl :-

  ldx #12                       ; copy the packet
: lda eth_inp,x
  sta eth_outp,x
  inx
  cpx eth_inp_len
  bne :-

  ldx #3
: lda ip_inp + ip_src,x         ; swap dest and src ip
  sta ip_outp + ip_dest,x
  lda ip_inp + ip_dest,x
  sta ip_outp + ip_src,x
  dex
  bpl :-

  lda #0                        ; change type to reply
  sta icmp_outp + icmp_type

  lda icmp_inp + icmp_cksum     ; recalc checksum
  clc
  adc #8
  sta icmp_outp + icmp_cksum
  bcc :+
  inc icmp_outp + icmp_cksum + 1
: lda eth_inp_len               ; copy length
  sta eth_outp_len
  lda eth_inp_len + 1
  sta eth_outp_len + 1

  lda #0                        ; clear checksum
  sta ip_outp + ip_header_cksum
  sta ip_outp + ip_header_cksum + 1
  ldax #ip_outp                 ; calculate ip header checksum
  stax ip_cksum_ptr
  ldax #20
  jsr ip_calc_cksum
  stax ip_outp + ip_header_cksum

  jsr eth_tx                    ; send packet
  clc
  rts

; add an icmp listener
; inputs:
; A = icmp type
; icmp_callback: vector to call when an icmp packet of specified type arrives
; outputs:
; carry flag - set if error, clear if no error
icmp_add_listener:
  ldx icmp_cbcount              ; any listeners at all?
  beq @add
  cpx #icmp_cbmax               ; max?
  beq @full
  ldx #0
: cmp icmp_cbtype,x             ; check if type is already listened
  beq @busy
  inx
  cpx icmp_cbcount
  bne :-
@add:
  inc icmp_cbcount              ; increase counter
  sta icmp_cbtype,x             ; add type
  lda icmp_callback             ; and vector
  sta icmp_cbveclo,x
  lda icmp_callback + 1
  sta icmp_cbvechi,x
  clc
  rts

@full:
@busy:
  sec
  rts

; remove an icmp listener
; inputs:
; A = icmp type
; outputs:
; carry flag - set if error (i.e. no listner for this type exists),
; clear if no error
icmp_remove_listener:
  ldx icmp_cbcount              ; any listeners installed?
  beq @notfound
  dex
: cmp icmp_cbtype,x             ; check if type is listened
  beq @remove
  dex
  bpl :-
@notfound:
  sec
  rts
@remove:
  txa                           ; number of listeners below
  eor #$ff
  clc
  adc icmp_cbcount
  beq @done
@move:
  tay                           ; number of items to move
: lda icmp_cbtype + 1,x         ; move type
  sta icmp_cbtype,x
  lda icmp_cbveclo + 1,x        ; move vector lsb
  sta icmp_cbveclo,x
  lda icmp_cbvechi + 1,x        ; move vector msb
  sta icmp_cbvechi,x
  inx
  dey
  bne :-
@done:
  dec icmp_cbcount              ; decrement counter
  clc
  rts

.ifdef TCP

; icmp_send_echo was contributed by Glenn Holmer (ShadowM)
; send an ICMP echo ("ping") request
; inputs:
; icmp_echo_ip: destination IP address
; outputs:
; carry flag - set if error, clear if no error
icmp_send_echo:
  ldy #3
: lda icmp_echo_ip,y
  sta ip_outp + ip_dest,y
  dey
  bpl :-

  lda #icmp_msg_type_echo_request
  sta icmp_outp + icmp_type
  lda #0                        ; not used for echo packets
  sta icmp_outp + icmp_code
  sta icmp_outp + icmp_cksum    ; clear checksum
  sta icmp_outp + icmp_cksum + 1
  sta icmp_outp + icmp_echo_id  ; set id to 0
  sta icmp_outp + icmp_echo_id + 1
  inc icmp_echo_cnt + 1         ; big-endian
  bne :+
  inc icmp_echo_cnt
: ldax icmp_echo_cnt
  stax icmp_outp + icmp_echo_seq

  ldy #0
: lda ip65_msg,y
  beq @set_ip_len
  sta icmp_outp + icmp_echo_data,y
  iny
  bne :-
@set_ip_len:
  tya
  clc
  adc #28                       ; IP header + ICMP type, code, cksum, id, seq
  sta ip_outp + ip_len + 1      ; high byte first
  lda #0  ; will never be >256
  sta ip_outp + ip_len

  ldax #icmp_outp               ; start of ICMP packet
  stax ip_cksum_ptr
  tya
  clc
  adc #8                        ; ICMP type, code, cksum, id, seq
  ldx #0                        ; AX = length of ICMP data
  jsr ip_calc_cksum
  stax icmp_outp + icmp_cksum
  lda #ip_proto_icmp
  sta ip_outp + ip_proto
  jsr ip_create_packet
  jmp ip_send

; send a ping (ICMP echo request) to a remote host, and wait for a response
; inputs:
; icmp_echo_ip: destination IP address
; outputs:
; carry flag - set if no response, otherwise AX is time (in miliseconds) for host to respond
icmp_ping:
  lda #0                        ; reset the "packet sent" counter
  sta icmp_echo_cnt
@send_one_message:
  jsr icmp_send_echo
  bcc @message_sent_ok
  ; we couldn't send the message - most likely we needed to do an ARP lookup.
  ; so wait a bit, and retry

  jsr timer_read                ; read current timer value
  stax ping_timer
@loop_during_arp_lookup:
  jsr ip65_process
  ldax ping_timer
  adc #50                       ; set timeout to now + 50 ms
  bcc :+
  inx
: jsr timer_timeout
  bcs @loop_during_arp_lookup
  jsr icmp_send_echo
  bcc @message_sent_ok
  ; still can't send? then give up
  lda #IP65_ERROR_TRANSMIT_FAILED
  sta ip65_error
  rts
@message_sent_ok:
  jsr timer_read                ; read current timer value
  stax ping_timer
  ldax #icmp_ping_callback
  stax icmp_callback
  lda #icmp_msg_type_echo_reply
  jsr icmp_add_listener
  lda #ping_state_request_sent
  sta ping_state
@loop_till_get_ping_response:
  jsr ip65_process

  lda ping_state
  cmp #ping_state_response_received
  beq @got_reply
  ldax ping_timer
  inx                           ; x rolls over about 4 times per second
  inx                           ; so we will timeout after about 2 seconds
  inx
  inx
  inx
  inx
  inx
  inx

  jsr timer_timeout
  bcs @loop_till_get_ping_response
  lda #IP65_ERROR_TIMEOUT_ON_RECEIVE
  sta ip65_error
  lda #icmp_msg_type_echo_reply
  jsr icmp_remove_listener
  sec
  rts
@got_reply:
  lda #icmp_msg_type_echo_reply
  jsr icmp_remove_listener
  jsr timer_read
  sec
  sbc ping_timer
  pha
  txa
  sbc ping_timer+1
  tax
  pla
  clc
  rts

icmp_ping_callback:
  lda icmp_inp + icmp_echo_seq
  cmp icmp_echo_cnt
  bne @not_what_we_were_waiting_for
  lda #ping_state_response_received
  sta ping_state
@not_what_we_were_waiting_for:
  rts

ip65_msg:
  .byte "ip65 - the 6502 IP stack",0

.endif



; -- LICENSE FOR icmp.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 Per Olofsson,
; MagerValp@gmail.com.
; Portions created by the Initial Developer are Copyright (C) 2009
; Per Olofsson. All Rights Reserved.
;
; Contributor(s): Jonno Downes, Glenn Holmer
; -- LICENSE END --
