; UDP (user datagram protocol) functions

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

.import ip65_error

.export udp_init
.export udp_process
.export udp_add_listener
.export udp_remove_listener
.export udp_send
.export udp_send_internal

.export udp_callback

.export udp_inp
.export udp_outp

.exportzp udp_src_port
.exportzp udp_dest_port
.exportzp udp_len
.exportzp udp_cksum
.exportzp udp_data

.export udp_send_dest
.export udp_send_src_port
.export udp_send_dest_port
.export udp_send_len

.import ip_calc_cksum
.import ip_send
.import ip_create_packet
.import ip_inp
.import ip_outp
.importzp ip_cksum_ptr
.importzp ip_header_cksum
.importzp ip_src
.importzp ip_dest
.importzp ip_data
.importzp ip_proto
.importzp ip_proto_udp
.importzp ip_id
.importzp ip_len

.import copymem
.importzp copy_src
.importzp copy_dest

.import cfg_ip


.data

udp_cbtmp: jmp $ffff            ; temporary vector - gets filled in later


.bss

; argument for udp_add_listener
udp_callback: .res 2            ; vector to routine to be called when a udp packet arrives

; arguments for udp_send
udp_send_dest:      .res 4      ; set to ip address that udp packet will be sent to
udp_send_src_port:  .res 2      ; set to port that udp packet will be sent from
udp_send_dest_port: .res 2      ; set to port that udp packet will be sent to
udp_send_len:       .res 2      ; set to length of data to be sent in udp packet (excluding ethernet,ip & udp headers)

; udp listener callbacks
udp_cbmax = 4
udp_cbveclo:  .res udp_cbmax    ; table of listener vectors (lsb)
udp_cbvechi:  .res udp_cbmax    ; table of listener vectors (msb)
udp_cbportlo: .res udp_cbmax    ; table of ports (lsb)
udp_cbporthi: .res udp_cbmax    ; table of ports (msb)
udp_cbcount:  .res 1            ; number of active listeners

; udp packet offsets
udp_inp  = ip_inp + ip_data     ; pointer to udp packet inside inbound ethernet frame
udp_outp = ip_outp + ip_data    ; pointer to udp packet inside outbound ethernet frame
udp_src_port  = 0               ; offset of source port field in udp packet
udp_dest_port = 2               ; offset of destination port field in udp packet
udp_len       = 4               ; offset of length field in udp packet
udp_cksum     = 6               ; offset of checksum field in udp packet
udp_data      = 8               ; offset of data in udp packet

; virtual header
udp_vh       = udp_outp - 12
udp_vh_src   =  0
udp_vh_dest  =  4
udp_vh_zero  =  8
udp_vh_proto =  9
udp_vh_len   = 10

; temp for port comparison
port: .res 2


.code

; initialize udp
; inputs: none
; outputs: none
udp_init:
  lda #0
  sta udp_cbcount
  rts

; process incoming udp packet
; inputs:
; eth_inp: should contain an ethernet frame encapsulating an inbound udp packet
; outputs:
; carry flag set if any error occured (including if no handler for specified port
; was found)
; carry flag clear if no error
; if handler was found, an outbound message may be created, overwriting eth_outp
udp_process:
  lda udp_cbcount               ; any installed udp listeners?
  beq @drop
  tax                           ; check ports
  dex
@checkport:
  lda udp_cbportlo,x
  cmp udp_inp + udp_dest_port + 1
  bne :+
  lda udp_cbporthi,x
  cmp udp_inp + udp_dest_port
  beq @handle
: dex
  bpl @checkport

@drop:
  sec
  rts

@handle:
  lda udp_cbveclo,x             ; copy vector
  sta udp_cbtmp + 1
  lda udp_cbvechi,x
  sta udp_cbtmp + 2
  jsr udp_cbtmp                 ; call listener
  clc
  rts

; add a udp listener
; inputs:
; udp_callback: vector to call when udp packet arrives on specified port
; AX: set to udp port to listen on
; outputs:
; carry flag set if too may listeners already installed, clear otherwise
udp_add_listener:
  sta port
  stx port + 1

  ldy udp_cbcount               ; any listeners at all?
  beq @add
  cpy #udp_cbmax                ; max?
  beq @full
  ldy #0
@check:
  lda udp_cbportlo,y            ; check if port is already handled
  cmp port
  bne :+
  lda udp_cbporthi,y
  cmp port + 1
  beq @busy
: iny
  cpy udp_cbcount
  bne @check
@add:
  inc udp_cbcount               ; increase counter
  sta udp_cbportlo,y            ; add port
  txa
  sta udp_cbporthi,y            ; add port
  lda udp_callback              ; and vector
  sta udp_cbveclo,y
  lda udp_callback + 1
  sta udp_cbvechi,y
  clc
  rts

@full:
@busy:
  lda #IP65_ERROR_LISTENER_NOT_AVAILABLE
  sta ip65_error
  sec
  rts

; remove an udp listener
; inputs:
; AX = port to stop listening on
; outputs:
; carry flag clear of handler found and removed
; carry flag set if handler for specified port not found
udp_remove_listener:
  sta port
  stx port + 1

  ldy udp_cbcount               ; any listeners installed?
  beq @notfound
  dey
@check:
  lda udp_cbportlo,y            ; check if port is handled
  cmp port
  bne :+
  lda udp_cbporthi,y
  cmp port + 1
  beq @remove
: dey
  bpl @check
@notfound:
  sec
  rts
@remove:
  tya                           ; number of listeners below
  eor #$ff
  clc
  adc udp_cbcount
  beq @done
@move:
  tax                           ; number of items to move
: lda udp_cbportlo + 1,y        ; move ports
  sta udp_cbportlo,y
  lda udp_cbporthi + 1,y
  sta udp_cbporthi,y
  lda udp_cbveclo + 1,y         ; move vectors
  sta udp_cbveclo,y
  lda udp_cbvechi + 1,y
  sta udp_cbvechi,y
  iny
  dex
  bne :-
@done:
  dec udp_cbcount               ; decrement counter
  clc
  rts

; send udp packet
; inputs:
; udp_send_dest:  destination ip address (4 bytes)
; udp_send_dest_port: destination port (2 bytes)
; udp_send_src_port: source port (2 bytes)
; udp_send_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
udp_send:
  stax copy_src                 ; copy data to output buffer
  ldax #udp_outp + udp_data
  stax copy_dest
  ldax udp_send_len
  jsr copymem
  ; now we can fall through into udp_send_internal

; send udp packet with data at (udp_outp + udp_data)
; inputs:
; udp_send_dest:  destination ip address (4 bytes)
; udp_send_dest_port: destination port (2 bytes)
; udp_send_src_port: source port (2 bytes)
; udp_send_len: length of data to send (exclusive of any headers)
; outputs:
; carry flag is set if an error occured, clear otherwise
udp_send_internal:
  ldx #3                        ; copy virtual header addresses
: lda udp_send_dest,x
  sta udp_vh + udp_vh_dest,x    ; set virtual header destination
  lda cfg_ip,x
  sta udp_vh + udp_vh_src,x     ; set virtual header source
  dex
  bpl :-

  lda udp_send_src_port         ; copy source port
  sta udp_outp + udp_src_port + 1
  lda udp_send_src_port + 1
  sta udp_outp + udp_src_port

  lda udp_send_dest_port        ; copy destination port
  sta udp_outp + udp_dest_port + 1
  lda udp_send_dest_port + 1
  sta udp_outp + udp_dest_port

  lda #ip_proto_udp
  sta udp_vh + udp_vh_proto

  lda #0                        ; clear checksum
  sta udp_outp + udp_cksum
  sta udp_outp + udp_cksum + 1
  sta udp_vh + udp_vh_zero      ; clear virtual header zero byte

  ldax #udp_vh                  ; checksum pointer to virtual header
  stax ip_cksum_ptr

  lda udp_send_len              ; copy length + 8
  clc
  adc #8
  sta udp_outp + udp_len + 1    ; lsb for udp header
  sta udp_vh + udp_vh_len + 1   ; lsb for virtual header
  tay
  lda udp_send_len + 1
  adc #0
  sta udp_outp + udp_len        ; msb for udp header
  sta udp_vh + udp_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 udp_outp + udp_cksum

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

  jsr ip_create_packet          ; create ip packet template

  lda udp_outp + udp_len + 1    ; ip len = udp len + 20
  ldx udp_outp + udp_len
  clc
  adc #20
  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_udp             ; set protocol
  sta ip_outp + ip_proto

  jmp ip_send                   ; send packet, sec on error



; -- LICENSE FOR udp.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.
; -- LICENSE END --
