;;; -*- Midas -*-

subttl UDP

.begin udp

comment 

A, B, C, D, T, TT, P

Routines must be used with channels opened using UDP"OPEN since the
channel number is used as an index internally.

Switches:
	UDP"$$DST
	UDP"$$SRCPORT
	UDP"$$TTL
	UDP"$$cksum
	UDP"$$IDS
	UDP"$$CKSRC (IFN $$DST)

Routines:
	UDP"INIT
	UDP"OPEN
	UDP"SEND
	UDP"HSEND (IFN $$DST)
	UDP"RESEND
	UDP"INPUT
	UDP"INNOWT

User sets in outgoing packets:
	UDP"UD$DST
	UDP"UD$LEN
	UDP"UD$DAT (or starting from UDP"UDDATA)
	UDP"IP$DST (IFE $$DST)
	UDP"UD$SRC (IFE $$SRCPORT)
	UDP"IP$TTL (IFL $$TTL)
	UDP"IP$ID (IFE $$IDS)

User reads in incoming packets:
	UDP"UD$SRC
	UDP"UD$LEN
	UDP"UD$DAT (or starting from UDP"UDDATA)
	UDP"IP$SRC
	UDP"IP$ID

Also of interest:
	UDP"UDHEAD
	UDP"UDLEN
	UDP"MAXLEN

	;end comment

.auxil		; Don't cref me please.

.tyo6 .ifnm1
.tyo 40
.tyo6 .ifnm2
printx / included in this assembly.
/

ifndef $$dst, $$dst==:1		; Set destination host in outgoing datagrams.
ifndef $$srcport, $$srcport==:1	; Set source port in outgoing datagrams
ifndef $$ttl, $$ttl==:255.	; Set Time To Live to this if non-negative
ifndef $$cksum, $$cksum==:1	; Generate checksums
ifndef $$ids, $$ids==:1		; Generate ID's for the user
ife $$dst, $$cksrc==:0		; (Can't check where it came from if we
				; don't know where we sent it!)
ifndef $$cksrc, $$cksrc==:1	; Check source host in incoming datagrams.

call==:pushj p,
return==:popj p,
save==:push p,
rest==:pop p,

niochn==:20		; ITS has this many channels per user.

;;; Macro to initialize several fields at the same time.  If this was
;;; really clever it would partition the fields for you!
define setup loc,bps,vals
.val.==0
.mask.==0
irp bp,,[bps]val,,[vals]
.bp.==<bp>
ife .irpcnt, .off.==.bp.&777777
ifn .off.-<.bp.&777777>, .err bps not in same word.
.val.==.dpb <val>,.bp.,.val.
.mask.==.dpb -1,.bp.,.mask.
termin
.bp.==.bp .mask.,.off.
ifn .mask.-<.dpb -1,.bp.,0>, .err bps not contiguous.
	move tt,[.ldb .bp.,.val.]
	dpb tt,[.bp. loc]
termin

udprot==:17.		; UDP is IP protocol 17 decimal.

ipvers==:4		; IP version 4 packets.

ip%ver==:740000,,	; 0 IP Version # (= 4)
ip%ihl==:036000,,	; 0 IP Header Length in 32-bit wds - at least 5
ip%tos==:001774,,	; 0 Type Of Service
ip%tol==:000003,,777760	; 0 Total Length in octets (including header)
ip%id==: 777774,,	; 1 Identification
ip%flg==:     3,,400000	; 1 Flags
  ip%fdf==:   1,,0	;	Don't-Fragment flag
  ip%fmf==:	 400000	;	More-Fragments flag
ip%frg==:     0,,377760	; 1 Fragment Offset
ip%ttl==:776000,,	; 2 Time To Live
ip%ptc==:  1774,,	; 2 Protocol
ip%cks==:     3,,777760	; 2 Header Checksum
ip%src==:777777,,777760	; 3 Source Address
ip%dst==:777777,,777760	; 4 Destination Address
			; 5 Start of options
ip$ver==:<.bp ip%ver,0>
ip$ihl==:<.bp ip%ihl,0>
ip$tos==:<.bp ip%tos,0>
ip$tol==:<.bp ip%tol,0>
ip$id==: <.bp ip%id, 1>
ip$flg==:<.bp ip%flg,1>
ip$frg==:<.bp ip%frg,1>
ip$ttl==:<.bp ip%ttl,2>
ip$ptc==:<.bp ip%ptc,2>
ip$cks==:<.bp ip%cks,2>
ip$src==:<.bp ip%src,3>
ip$dst==:<.bp ip%dst,4>
minihl==:5			; IP header is normally 5 words long.
maxihl==:15.			; But it could be this long.

;;; These guys have MINIHL built into them to make it easy to construct
;;; datagrams for output.
udhead==:minihl
ud$src==:<242000,,udhead+0>	; 0 wd 1 Source port
ud$dst==:<042000,,udhead+0>	; 0 wd 2 Dest port
ud$len==:<242000,,udhead+1>	; 1 wd 1 # octets in data
  ud$wds==:<261600,,udhead+1>	; (number of full words of data)
  ud$xtr==:<240200,,udhead+1>	; (number of extra octets of data)
ud$cks==:<042000,,udhead+1>	; 1 wd 2 UDP checksum
udlen==:2			; Total:  2 words of UDP header
uddata==:udhead+udlen		; Data then on
ud$dat==:<441000,,uddata>	; Initial ILDB pointer to data
maxlen==:maxihl+udlen		; Largest possible IP+UDP header

;;; IPQ: open mode bits:
%iqsys==:100		; Set up System Queue (0 or 1)
%iqsou==:200		; System Queue 1 if set, otherwise 0
%iqudp==:400		; Set up random queue for UDP (port # in FN1)

;;; Possible errors:
;;; 23	%ENAFL	Requested system queue not available
;;;  6	%EFLDV	All queues in use
;;; 33	%EBDRG	Neither %IQSYS nor %IQUDP set


;;; IPKIOT control bits:
;;;	Get datagram from:
%ipius==:100		; 1 = Get datagram from user space, not input queue
%ipnoc==:200		; Global input no-check flag, suppresses normal check.
			;   For Input Queue, "check" means verify IP header.
			;   For User Space, "check" means verify, set cksum.
			;   (In ITS 1539 this was still unimplemented.)
%ipnoh==:400		; Don't Hang waiting for datagram (input Q only)
%ipiqk==:1000		; Keep on queue, don't remove (only for %IPOUS)
;;;	Put datagram to:
%ipous==:0		; User space
%ipout==:1		; Output it
%ipofl==:2		; Flush it
%iporv==:3		; Re-vector to input queues past this one

;;; Possible errors:
;;; 34	%EBDDV	Not IPQ: device
;;; 30	%ETFRG	Too few args when %IPOUS or %IPIUS set
;;; 33	%EBDRG	Length (arg 3) too small or too large
;;;  2	%ENSIO	Can't Re-vector from SysOut queue (queue 1)


;;; Errors while doing I/O on an IPQ channel:
;;; 34	%EBDDV	You can't do I/O on an IPQ channel!

.scalar myaddr
ifn $$dst, .vector fhosts(niochn)
ifn $$srcport, .vector ports(niochn)

;;; CALL INIT:  Initialize UDP package.
init:	move tt,[squoze 0,IMPUS3]	; What the hell else should I do?
	.eval tt,
	 .lose
	movem tt,myaddr
	return

;;; CALL OPEN:  Open an IPQ: channel for performing UDP transactions.
;;;  Skips if successful.
;;; A (a/v):  ITS channel number.
;;; B (arg):  UDP port number for receiving packets.  -1 to generate a
;;;		unique local port number.
;;; B (val):  Resulting UDP port number.
;;; C (a/v):  Foreign host.  (IFN $$DST)
;;; TT (val):  Error code, if any.
open:	camn b,[-1]
	 .gensym b,
	andi b,177777
	.call [	setz
		sixbit /OPEN/
		moves tt
		movsi %iqudp
		movei (a)
		move [sixbit /IPQ/]
		setz b ]
	 return
ifn $$srcport, movem b,ports(a)
ifn $$dst,[
	and c,[.ldb ip$dst,-1]
	movem c,fhosts(a)
] ;end ifn $$dst
popj1:	aos (p)
cpopj:	return

;;; CALL SEND:  Output a datagram to the network.
;;; CALL HSEND:  Output a datagram to the network.  (IFN $$DST)
;;; CALL RESEND:  Retransmit a datagram to the network.
;;;  Skips if successful.
;;; A (a/v):  ITS channel number.
;;; B (a/v):  Address of datagram to output.
;;; T (arg):  Host for HSEND.
;;; TT (val):  Error code if any.
;;; Datagram will be filled in correctly for transmission first by SEND and
;;; HSEND, while RESEND will assume that it is already correct.
send:
ifn $$dst,[
	move t,fhosts(a)
hsend:	dpb t,[ip$dst (b)]
] ;end ifn $$dst
	setup (b),[ip$ver,ip$ihl,ip$tos][ipvers,minihl,0]
	setup (b),[ip$flg,ip$frg][0,0]
	setup (b),[ud$cks][0]
ifl $$ttl, setup (b),[ip$ptc,ip$cks][udprot,0]
ifge $$ttl, setup (b),[ip$ttl,ip$ptc,ip$cks][$$ttl,udprot,0]
ifn $$ids,[
	.gensym tt,
	dpb tt,[ip$id (b)]
] ;end ifn $$ids
ifn $$srcport,[
	move tt,ports(a)
	dpb tt,[ud$src (b)]
] ;end ifn $$srcport
	move tt,myaddr
	dpb tt,[ip$src (b)]
	ldb tt,[ud$len (b)]
	addi tt,4*minihl		; No options
	dpb tt,[ip$tol (b)]
ifn $$cksum,[
	call udcksm
	movei t,177777
	subi t,(tt)
	dpb t,[ud$cks (b)]
	call ipcksm
	movei t,177777
	subi t,(tt)
	dpb t,[ip$cks (b)]
] ;end ifn $$cksum
resend:	ldb t,[ip$tol (b)]
	addi t,3
	lsh t,-2
	.call [	setz
		sixbit /IPKIOT/
		moves tt
		movsi %ipius\%ipout	; User => Network
		movei (a)
		movei (b)
		setzi (t) ]
	 return
	aos (p)
	return

;;; CALL INPUT:  Input a datagram from the network.
;;; CALL INNOWT:  Input a datagram from the network, but don't wait.
;;;  Skips if successful.
;;; A (a/v):  ITS channel number.
;;; B (a/v):  Address of buffer to store datagram.
;;; C (arg):  Length of buffer.
;;; C (val):  UDP pointer.
;;; D (val):  Number of words actually transferred.  INNOWT returns 0 to
;;;		indicate no datagram was read.
;;; TT (val):  Error code if any.
input:	.call [	setz
		sixbit /IPKIOT/
		moves tt
		movsi %ipous		; Queue => User
		movei (a)
		movei (b)
		movei (c)
		setzm d]
	 return
	call inck
	jrst input

innowt:	.call [	setz
		sixbit /IPKIOT/
		moves tt
		movsi %ipous\%ipnoh	; Queue => User, no hang
		movei (a)
		movei (b)
		movei (c)
		setzm d]
	 return
	jumpe d,popj1
	call inck
	jrst innowt

inck:
ifn $$cksrc,[
	ldb tt,[ip$src (b)]
	came tt,fhosts(a)
	 return			; Bogus datagram
] ;end ifn $$cksrc
	ldb c,[ip$ihl (b)]
	addi c,-minihl(b)	; C: UDP pointer
	ldb t,[ip$tol (b)]	; T: IP length in octets
	movei tt,3(t)
	lsh tt,-2		; TT: IP length in words
	camle tt,d
	 jrst inck45		; datagram didn't fit in buffer
	ldb tt,[ud$len (c)]	; TT: UDP length in octets
	caige tt,udlen*4
	 return			; Bogus datagram
	sub t,tt		; T: IP-UDP length in octets
	ldb tt,[ip$ihl (c)]
	lsh tt,2		; TT: IP header length in octets
	camge t,tt
	 return			; Bogus datagram
	ldb tt,[ud$cks (c)]
	jumpe tt,pop1j1
	call udcksm
	jumpn tt,cpopj		; Bogus datagram
pop1j1:	aosa -1(p)
inck45:	 movei tt,%enrbf
	sub p,[1,,1]
	return

;;; CALL IPCKSM:  Compute checksum of IP header.
;;; B (a/v):  Address of datagram to checksum.
;;; TT (val):  Mod 177777 sum of all 16-bit words in IP header
;;;		(including IP header checksum word).
ipcksm:	save a
	ldb tt,[ip$ihl (b)]
	hrloi a,-1(tt)
	eqvi a,(b)
	setzi t,
	call cksum
	rest a
	return

;;; CALL UDCKSM:  Compute checksum of UDP datagram.
;;; B (a/v):  Address of datagram to checksum.
;;; TT (val):  Mod 177777 sum of all 16-bit words in UDP datagram and pseudo
;;;		header (including UDP checksum word).
udcksm:	save a
	save c
	ldb c,[ip$ihl (b)]
	addi c,-minihl(b)	; C: UDP pointer
	ldb a,[ud$wds (c)]	; A: # 32bit words
	ldb tt,[ud$xtr (c)]	; TT: # leftover octets
	move t,(tt)[	000000,,000000	; T: Mask for octets is last word
			776000,,000000
			777774,,000000
			777777,,770000 ]
	jumpe tt,udcks1		; If no extra octets, don't touch...
	movei tt,udhead(c)
	addi tt,(a)		; TT: address of last (partial) word
	and t,(tt)		; Pick up extra octets
	lsh t,-4		; and shift them over
udcks1:	hrloi a,-1(a)
	eqvi a,udhead(c)	; A: aobjn to words to sum
	ldb tt,[ip$src (b)]
	add t,tt		; T: start summing
	ldb tt,[ip$dst (b)]
	add t,tt
	ldb tt,[ip$ptc (b)]
	add t,tt
	ldb tt,[ud$len (c)]
	add t,tt
	call cksum
	rest c
	rest a
	return

;;; CALL CKSUM:  Compute checksum.
;;; A (arg):  Aobjn to words to checksum.
;;; T (arg):  Initial value of sum.
;;; TT (val):  Mod 177777 sum of all 16-bit words.
;;; We assume there is at least one word to sum.
cksum:	move tt,(a)	; (faster to LDB?)
	lsh tt,-4	; (...)
	add t,tt
	skipge t		; If overflow into sign bit,
	 sub t,[377777,,777770]	;  then reduce it.
	aobjn a,cksum
	idivi t,177777		; T >= 0 because of test above.
	return

.end udp
