;;; -*- Mode:LISP; Package:UDP; Readtable:CL; Base:10 -*-

#|

  Copyright LISP Machine, Inc. 1987
   See filename "Copyright.Text" for
  licensing and release information.

|#

(export '( *udp-stream*
	   setup-udp
	   describe-udp
	   make-udp-socket
	   using-udp-socket
	   multiple-connection-listen
	   multiple-connection-kill-gauges))

;;;UDP's interface to IP
(defstream udp-ip-transport-protocol
	   (ip-transport-protocol)
	   "UDP-"
  (user-socket-alist nil)			;user sockets
  (next-local-port 256)				;next local port to assign
  (checksum-failures 0)				;bad checksum packets
  (local-checksums nil)				;T if calculate checksums for looped-back packets
  )

(defop (udp-ip-transport-protocol :peek-normal-fields) (tp)
  (ncons (tv:scroll-parse-item
	   `(:function udp-next-local-port (,tp) NIL ("  Next local port assigned = ~D"))
	   `(:function udp-checksum-failures (,tp) NIL ("  Checksum failures ~D"))
	   `(:mouse-item
	      (nil :buttons
		   ,(make-list 3 :initial-element
			       `(nil :eval (setf (udp-local-checksums tp)
						 (not (udp-local-checksums tp)))
				     :bindings ((tp ',tp))))
		   :DOCUMENTATION
		   "Click to toggle local checksums"
		   :BINDINGS
		   ((tp ',tp)))
	      :function udp-local-checksums (,tp) NIL (" Local checksums: ~A")))))

(defop (udp-ip-transport-protocol :peek-final-fields) (tp)
  (ncons (TV:SCROLL-MAINTAIN-LIST
	   #'(LAMBDA () (udp-user-socket-alist tp))
	   'peek-udp-socket)))

(defun peek-udp-socket (elt)
  (let ((socket (cdr elt)))
    (send socket :peek-item elt)))

(defvar *udp-stream* nil "The ip-transport-protocol for UDP")
(defvar *udp-receives-out* 8 "Number of outstanding receives")

(defconstant udp-protocol 17 "IP protocol type field for UDP")

(defun setup-udp ()
  (setq *udp-stream* (make-udp-ip-transport-protocol :keyword :udp
						     :type udp-protocol
						     :gauge-name "UDP"
						     :reset-function 'udp-reset
						     :close-function 'udp-close
						     :receive-function 'receive-udp-packet
						     :icmp-notification-function 'udp-icmp
						     :broadcast-allowed-p t
						     ))

  (send *udp-stream* :open)
  (send *udp-stream* :enable) 
  (dotimes (i *udp-receives-out*)		;and give IP some read buffers
    (send *udp-stream* :receive (get-udp-buffer)))
  t)

(defvar *free-udp-buffers* nil "List of free UDP message buffers")

(defun get-udp-buffer ()
  "returns an array to hold a UDP message."
  (if *free-udp-buffers*
      (pop *free-udp-buffers*)
    (zl:make-array 1480				; + 20 IP = 1500 bytes
		   :element-type '(unsigned-byte 8)
		   :fill-pointer 0
		   :leader-length 2
		   :named-structure-symbol 'udp-buffer)))

(defun free-udp-buffer (udp)
  (push (original-array udp) *free-udp-buffers*)
  nil)

(defun udp-buffer-p (buffer)
  (eq (named-structure-p buffer) 'udp-buffer))

(defvar *free-udp-headers* nil "List of free UDP headers")

(defun get-udp-header ()
  "returns an array to hold a UDP header."
  (if *free-udp-headers*
      (pop *free-udp-headers*)
    (make-array 8 :element-type '(unsigned-byte 8))))

(defun free-udp-header (udp)
  (push udp *free-udp-headers*)
  nil)

;;;UDP Header Definitions

(defmacro udp-source-port (udp)
  `(dpb (aref ,udp 0) (byte 8 8) (aref ,udp 1)))
(defun set-udp-source-port (udp val)
  (setf (aref udp 0) (ldb (byte 8 8) val))
  (setf (aref udp 1) (ldb (byte 8 0) val))
  val)
(defsetf udp-source-port set-udp-source-port)

(defmacro udp-destination-port (udp)
  `(dpb (aref ,udp 2) (byte 8 8) (aref ,udp 3)))
(defun set-udp-destination-port (udp val)
  (setf (aref udp 2) (ldb (byte 8 8) val))
  (setf (aref udp 3) (ldb (byte 8 0) val))
  val)
(defsetf udp-destination-port set-udp-destination-port)

(defmacro udp-length (udp)
  `(dpb (aref ,udp 4) (byte 8 8) (aref ,udp 5)))
(defun set-udp-length (udp val)
  (setf (aref udp 4) (ldb (byte 8 8) val))
  (setf (aref udp 5) (ldb (byte 8 0) val))
  val)
(defsetf udp-length set-udp-length)

(defmacro udp-checksum (udp)
  `(dpb (aref ,udp 6) (byte 8 8) (aref ,udp 7)))
(defun set-udp-checksum (udp val)
  (setf (aref udp 6) (ldb (byte 8 8) val))
  (setf (aref udp 7) (ldb (byte 8 0) val))
  val)
(defsetf udp-checksum set-udp-checksum)

(defconstant udp-first-data-byte 8 "Offset of first data byte in udp buffer")

(defun describe-udp (udp &optional display-data-p)
  "Given an art-8b array, displays the UDP message in it"
  (format t "~&source-port: ~20t~D" (udp-source-port udp))
  (format t "~&destination-port: ~20t~D" (udp-destination-port udp))
  (format t "~&checksum: ~20t~D" (udp-checksum udp))
  (format t "~&length: ~20t~D" (udp-length udp))

  (when (and display-data-p (> (udp-length udp) 8))
    (if (numberp display-data-p)
	(format t "~&~D bytes of data:" display-data-p)
      (format t "~&rest of data:"))
    (do* ((offset 8 (1+ offset))
	  (length (- (udp-length udp) offset))
	  (count (if (numberp display-data-p) (min length display-data-p) length))
	  (index 0 (1+ index)))
	 ((= index count) nil)
      (if (zerop (mod index 32))
	  (format t "~&"))
      (format t "~16,2,'0r " (aref udp offset))))

  udp)

(defun check-udp-checksum (udp ip local-p)
  ;;"An all-zero transmitted checksum value means that the transmitter generated no checksum" -- RFC768 p2
  (when (or (zerop (udp-checksum udp))
	    (and (not (udp-local-checksums *udp-stream*)) local-p))
    (return-from check-udp-checksum (values t 0)))
  (let* ((count (udp-length udp))
	 (sum (checksum-1 udp (pseudo-header-checksum ip count) count)))
    (values (zerop sum) sum)))

(defun store-udp-checksum (buffers ip local-p)
  (unless (consp buffers)
    (setq buffers (ncons buffers)))
  (setf (udp-checksum (first buffers)) 0)
  (when (and (not (udp-local-checksums *udp-stream*)) local-p)
    (return-from store-udp-checksum t))
  (let ((sum (checksum buffers (pseudo-header-checksum ip (udp-length (first buffers))))))
    ;;"If the computed checksum is zero, it is transmitted as all ones" -- RFC768 p2
    (setf (udp-checksum (first buffers)) (if (zerop sum) #xffff sum))))

;;;User interface to UDP

(defstream udp-socket
	   ()
	   "UDP-USER-"
  (keyword nil)					; Keyword to identify this UDP user
  (local-port nil)				; Local Port number of this UDP user
  (remote-port nil)				; Remote Port number, if specified
  (remote-address nil)				; Remote Address, if specified
  (receive-buffers nil)				; list of buffers to receive network packets
  (packet-list nil)				; list of received packets
  (statistics-block (make-statistics-block))	; Network statistics block maintained at clock level
  (packets-sent-discarded 0)			; statistic: sent packets discarded (couldn't route)
  (packets-received-discarded 0)		; statistic: received packets discarded (no rcv buffer)
  (bytes-sent-discarded 0)			; statistic: sent bytes discarded
  (bytes-received-discarded 0)			; statistic: received bytes discarded
  (active-gauges nil)				; list of gauges in control panel
  (inactive-gauges nil)				; list of gauges not currently in control panel
  (gauge-name nil)				; the name of this protocol for its gauges
  )

(defsubst udp-user-packets-sent (uu)
  (aref (udp-user-statistics-block uu) STAT-PS STAT-CURR))
(defsubst udp-user-packets-received (uu)
  (aref (udp-user-statistics-block uu) STAT-PR STAT-CURR))
(defsubst udp-user-bytes-sent (uu)
  (aref (udp-user-statistics-block uu) STAT-BS STAT-CURR))
(defsubst udp-user-bytes-received (uu)
  (aref (udp-user-statistics-block uu) STAT-BR STAT-CURR))

(defsubst udp-user-enabled (socket)
  (rassoc socket (udp-user-socket-alist *udp-stream*)))

;;;Peek item for a TCP socket

(defop (udp-socket :peek-item) (elt)
  (let ((socket (cdr elt)))
    (list `(:PRE-PROCESS-FUNCTION udp-socket-insert-special-fields :socket ,socket)
	  (tv:scroll-parse-item
	    :leader 2
	    `(:MOUSE-ITEM
	       (NIL :MENU-CHOOSE
		    ("UDP socket operations"
		     ("Close" :EVAL  (when (tv:mouse-y-or-n-p "Close this UDP socket")
				       (funcall socket :close))
		      :DOCUMENTATION
		      "Click left to close this UDP socket.")
		     ("Inspect" :EVAL (send tv:selected-window :force-kbd-input `(inspect ,socket))
		      :DOCUMENTATION
		      "Click left to INSPECT this UDP socket.")
		     ("Describe" :EVAL (send tv:selected-window :force-kbd-input `(describe ,socket))
		      :DOCUMENTATION
		      "Click left to DESCRIBE this UDP socket.")
		     ("Brief" :eval (udp-socket-level tv:item :brief)
		      :documentation
		      "Click left to choose brief display for this UDP socket.")
		     ("Normal" :eval (udp-socket-level tv:item :normal)
		      :documentation
		      "Click left to choose normal display for this UDP socket."))
		    :DOCUMENTATION
		    "Menu of things to do to this UDP socket."
		    :BINDINGS
		    ((socket ',socket)))
	       :FUNCTION udp-user-keyword (,socket) NIL ("   UDP socket ~D:"))
	    `(:function udp-user-local-port (,socket) NIL (" ~D"))
	    `(:function udp-user-remote-port (,socket) NIL (" -> ~D"))
	    `(:function ,#'(lambda (socket)
			     (if (udp-user-remote-address socket)
				 (ip:canonical-ip (udp-user-remote-address socket))
			       nil)) (,socket) NIL (" [~A] "))
	    `(:mouse-item
	       (nil :buttons
		    ((nil :eval (funcall socket
					 (if (udp-user-active-gauges socket) :kill-gauges :make-gauges))
			  :bindings ((socket ',socket)))
		     (nil :eval (ignore))
		     (nil :eval (net:gauge-menu socket (udp-user-active-gauges socket))
			  :bindings ((socket ',socket))))
		    :documentation
		    "Click left for default, right for menu"
		    ((socket ',socket)))
	       :function ,#'(lambda (n) (mapcar 'car (udp-user-active-gauges n))) (,socket) NIL ("Gauges: ~A"))))))

(defun udp-socket-level (item level)
  (when (null (array-leader item (1+ tv:scroll-item-leader-offset)))
    (setf (array-leader item (1+ tv:scroll-item-leader-offset)) :brief))
  (unless (eq level (array-leader item (1+ tv:scroll-item-leader-offset)))
    (setf (array-leader item tv:scroll-item-leader-offset) nil)
    (setf (array-leader item (1+ tv:scroll-item-leader-offset)) level)))

(defun udp-socket-insert-special-fields (item &aux socket level)
  "A pre-process function to insert udp-socket specific fields in the display"
  (let ((first-item (first (tv:scroll-item-component-items item))))
    (unless (array-leader first-item tv:scroll-item-leader-offset)
      (setq level (array-leader first-item (1+ tv:scroll-item-leader-offset)))
      (setq socket (getf (tv:scroll-item-plist item) :socket))
      (setf (tv:scroll-item-component-items item)
	    (nconc (list (first (tv:scroll-item-component-items item)))
		   (if (member level '(:normal)) (udp-socket-normal-fields socket))))
      (setf (array-leader first-item tv:scroll-item-leader-offset) t))))

(defun udp-socket-normal-fields (socket)
  (list
    (net:sent-statistics (udp-user-statistics-block socket) "    ")
    (net:rcvd-statistics (udp-user-statistics-block socket) "    ")
    (TV:SCROLL-PARSE-ITEM
      `(:function udp-user-packets-sent-discarded (,socket) NIL ("    Packets sent/disc ~D"))
      `(:function udp-user-packets-received-discarded (,socket) NIL (" rcvd/disc ~D"))
      `(:function udp-user-bytes-sent-discarded (,socket) NIL (" Bytes sent/disc ~D"))
      `(:function udp-user-bytes-received-discarded (,socket) NIL (" rcvd/disc ~D")))
    ))

(defop (udp-socket :open) (&key local-port remote-port remote-address (initial-gauges nil ig-p)
				&aux parsed-remote-address)
  (unless (udp-user-enabled self)
    (when (udp-enabled *udp-stream*)
      (assert (or (setq parsed-remote-address nil)	;This branch always fails, but reinitializes local
		  (cond ((null remote-port)
			 ;;If no remote-port, must be no remote-address
			 (null remote-address))
			((setq parsed-remote-address (parse-internet-address remote-address))
			 ;;If have a remote-port, remote-address must be valid AND remote-port must be valid
			 (typep remote-port '(unsigned-byte 16)))
			(t
			 ;;If have a remote-port and remote-address invalid, error
			 nil)))
	      (remote-port remote-address)
	      "~[REMOTE-PORT ~D out of range~;REMOTE-ADDRESS ~S unknown~;REMOTE-PORT and REMOTE-ADDRESS must be both nil or both non-nil~]"
	      (cond ((null remote-port) 2)
		    (parsed-remote-address 0)
		    (t 1))
	      (if parsed-remote-address remote-port remote-address))
      (if local-port
	  (assert (and (typep local-port '(unsigned-byte 16))
		       (unused-local-port local-port remote-port))
		  (local-port)
		  "LOCAL-PORT ~D is ~:[out of range~;already in use~]"
		  local-port
		  (typep local-port '(unsigned-byte 16)))
	(setq local-port (assign-local-port)))
      (setf (udp-user-local-port self) local-port)
      (setf (udp-user-remote-port self) remote-port)
      (setf (udp-user-remote-address self) parsed-remote-address)
      (setf (udp-user-packet-list self) (make-fifo))
      (array-initialize (udp-user-statistics-block self) 0)
      (setf (udp-user-gauge-name self) (format nil "UDP ~D" (udp-user-local-port self)))
      (dolist (g (udp-user-inactive-gauges self))
	(send (cdr g) :set-margin-name (udp-user-gauge-name self)))
      (when ig-p
	;;If initial-gauges specified, NIL means no gauges, T means default, anything else specifies gauges
	;;The reason to add no gauges is to have the statistics block looked at by the clock function
	(if (eq initial-gauges t)
	    (setq initial-gauges '(:apr :aps :abr :abs)))
	(set-gauges (udp-user-statistics-block self)
		    (locf (udp-user-active-gauges self))
		    (locf (udp-user-inactive-gauges self))
		    (udp-user-gauge-name self)
		    initial-gauges)
	(add-network-statistics-block (udp-user-statistics-block self)))
      (do ((blip (pop-fifo (udp-user-packet-list self))
		 (pop-fifo (udp-user-packet-list self))))
	  ((null blip))
	(unless (eq (first blip) :close)
	  (free-ip-header (second blip)))
	(if (eq (first blip) :data)
	    (free-udp-buffer (third blip))))
      (push (cons local-port self) (udp-user-socket-alist *udp-stream*))
      local-port)))

(defop (udp-socket :bind) (&optional remote-port remote-address &aux parsed-remote-address)
  (when (and (udp-enabled *udp-stream*) (udp-user-enabled self))
    (assert (or (setq parsed-remote-address nil)	;This branch always fails, but reinitializes local
		(cond ((null remote-port)
		       ;;If no remote-port, must be no remote-address
		       (null remote-address))
		      ((setq parsed-remote-address (parse-internet-address remote-address))
		       ;;If have a remote-port, remote-address must be valid AND remote-port must be valid
		       (typep remote-port '(unsigned-byte 16)))
		      (t
		       ;;If have a remote-port and remote-address invalid, error
		       nil)))
	    (remote-port remote-address)
	    "~[REMOTE-PORT ~D out of range~;REMOTE-ADDRESS ~S unknown~;REMOTE-PORT and REMOTE-ADDRESS must be both nil or both non-nil~]"
	    (cond ((null remote-port) 2)
		  (parsed-remote-address 0)
		  (t 1))
	    (if parsed-remote-address remote-port remote-address))
    (when (and (null remote-port) (null remote-address))
      (unless (fifo-empty-p (udp-user-packet-list self))
	(let ((blip (first (fifo-as-list (udp-user-packet-list self)))))
	  ;;Look at packet at head of packet-list to find remote address and remote port
	  (when (eq (first blip) :data)
	    (setq remote-port (fourth blip))
	    (setq remote-address (ip:ih-source-address (second blip)))))))
    (setf (udp-user-remote-port self) remote-port)
    (setf (udp-user-remote-address self) remote-address)
    t))

(defop (udp-socket :remote-port) ()
  (and (udp-enabled *udp-stream*)
       (udp-user-enabled self)
       (udp-user-remote-port self)))

(defop (udp-socket :remote-address) ()
  (and (udp-enabled *udp-stream*)
       (udp-user-enabled self)
       (udp-user-remote-address self)))

(defop (udp-socket :local-port) ()
  (and (udp-enabled *udp-stream*)
       (udp-user-enabled self)
       (udp-user-local-port self)))

(defun unused-local-port (local-port remote-port)
  (do ((list (udp-user-socket-alist *udp-stream*)))
      ((null list) t)
    (setq list (member local-port list :key #'car))
    (if (and list (eql remote-port (udp-user-remote-port (cdar list))))
	(return nil)
      (setq list (cdr list)))))

(defun assign-local-port ()
  (do ((port (udp-next-local-port *udp-stream*) (1+ port)))
      ((unused-local-port port nil)
       (setf (udp-next-local-port *udp-stream*) (1+ port))
       (if (= (udp-next-local-port *udp-stream*) 65536)
	   (setf (udp-next-local-port *udp-stream*) 256))
       port)))

(defop (udp-socket :close) (&optional abort)
  (let ((item (udp-user-enabled self)))
    (when item
      (setf (udp-user-socket-alist *udp-stream*) (remove item (udp-user-socket-alist *udp-stream*)))
      (delete-network-statistics-block (udp-user-statistics-block self))
      (send self :kill-gauges)
      (do ((buffer (pop (udp-user-receive-buffers self))
		   (pop (udp-user-receive-buffers self))))
	  ((null buffer))
	(free-udp-buffer buffer))
      (if abort
	  (do ((blip (pop-fifo (udp-user-packet-list self))
		     (pop-fifo (udp-user-packet-list self))))
	      ((null blip))
	    (free-ip-header (second blip))
	    (if (eq (first blip) :data)
		(free-udp-buffer (original-array (third blip))))))
      (push-fifo (ncons :close) (udp-user-packet-list self))
      t)))

(defop (udp-socket :receive) (&optional buffer)
  (when (udp-user-enabled self)
    (when buffer
      (setq buffer (original-array buffer)))
    (if buffer
	(check-type buffer (satisfies udp-buffer-p))
      (setq buffer (get-udp-buffer)))
    (push buffer (udp-user-receive-buffers self))
    t))

(defop (udp-socket :allocate) ()
  (get-udp-buffer))

(defop (udp-socket :free) (buffer)
  (setq buffer (original-array buffer))
  (check-type buffer (satisfies udp-buffer-p))
  (free-udp-buffer buffer))

(defop (udp-socket :write-packet) (buffers ip-header &optional remote-port (checksum t))
  (unless (consp buffers)
    (setq buffers (ncons buffers)))
  (dolist (b buffers)
    (check-type b (satisfies byte-array-or-string-p)))
  (if (udp-user-remote-port self)
      (setq remote-port (udp-user-remote-port self)))
  (check-type ip-header (satisfies ip-header-p))
  (check-type remote-port (unsigned-byte 16))
  (if (udp-user-remote-address self)
      (set-destination-address ip-header (udp-user-remote-address self)))
  (setf (ip:ih-protocol ip-header) udp-protocol)
  (let ((udp-header (get-udp-header)))
    (unwind-protect
	(let ((result nil)
	      (buffer-length (apply '+ (mapcar 'length buffers))))
	  (push udp-header buffers)
	  (setf (udp-source-port udp-header) (udp-user-local-port self))
	  (setf (udp-destination-port udp-header) remote-port)
	  (setf (udp-length udp-header) (+ 8 buffer-length))
	  (if checksum
	      (store-udp-checksum buffers ip-header (local-host-p (ip::ih-dest-address ip-header)))
	    (setf (udp-checksum udp-header) 0))
	  (setq result (multiple-value-list (send *udp-stream* :send buffers ip-header)))
	  (cond ((or (null (car result)) (zerop (car result)))
		 (incf (udp-user-packets-sent-discarded self))
		 (incf (udp-user-bytes-sent-discarded self) buffer-length))
		(t
		 (incf (udp-user-packets-sent self))
		 (incf (udp-user-bytes-sent self) buffer-length)))
	  (values-list result))
      (free-udp-header udp-header))))

(defop (udp-socket :broadcast-packet) (buffers ip-header remote-port &optional remote-network (checksum t))
  (when (udp-user-remote-address self)
    (error "Can't broadcast from a fully specified socket"))
  (unless (consp buffers)
    (setq buffers (ncons buffers)))
  (dolist (b buffers)
    (check-type b (satisfies byte-array-or-string-p)))
  (check-type ip-header (satisfies ip-header-p))
  (check-type remote-port (unsigned-byte 16))
  (when remote-network
    (check-type remote-network (unsigned-byte 32)))
  (setf (ip:ih-protocol ip-header) udp-protocol)
  (let ((udp-header (get-udp-header)))
    (unwind-protect
	(let ((result nil)
	      (buffer-length (apply '+ (mapcar 'length buffers))))
	  (push udp-header buffers)
	  (setf (udp-source-port udp-header) (udp-user-local-port self))
	  (setf (udp-destination-port udp-header) remote-port)
	  (setf (udp-length udp-header) (+ 8 buffer-length))
	  (setq result
		(multiple-value-list
		  (send *udp-stream* :broadcast buffers ip-header remote-network
				     #'(lambda (header buffers)
					 (if checksum
					     (store-udp-checksum buffers header (local-host-p (ip:ih-dest-address header)))
					   (setf (udp-checksum udp-header) 0))))))
	  (cond ((or (null (car result)) (zerop (car result)))
		 (incf (udp-user-packets-sent-discarded self))
		 (incf (udp-user-bytes-sent-discarded self) buffer-length))
		(t
		 (incf (udp-user-packets-sent self))
		 (incf (udp-user-bytes-sent self) buffer-length)))
	  (values-list result))
      (free-udp-header udp-header))))

(defop (udp-socket :listen) ()
  (not (fifo-empty-p (udp-user-packet-list self))))

(defun multiple-connection-listen (list)
  "Given a LIST of sockets, return the first stream that has data waiting"
  (dolist (socket list)
    (when (not (fifo-empty-p (udp-user-packet-list socket)))
      (return socket))))

(defun multiple-connection-kill-gauges (list)
  (let ((gauges nil))
    (without-interrupts
      (dolist (socket list)
	(do ((gauge (pop (udp-user-active-gauges socket)) (pop (udp-user-active-gauges socket))))
	    ((null gauge))
	  (push (cdr gauge) gauges)
	  (push gauge (udp-user-inactive-gauges socket)))))
    (when gauges
      (apply 'tv:delete-network-gauges gauges))))

(defop (udp-socket :read-packet) ()
  (pop-fifo (udp-user-packet-list self)))

(defop (udp-socket :print-self) (stream &optional ignore ignore)
  (si:printing-random-object (self stream :type :no-pointer)
    (format stream
	    "(~:[closed~;open~]) ~D~:[~; -> ~:*~D [~A]~]"
	    (and *udp-stream*
		(rassoc self (udp-user-socket-alist *udp-stream*)))
	    (udp-user-local-port self)
	    (udp-user-remote-port self)
	    (if (udp-user-remote-address self)
		(canonical-ip (udp-user-remote-address self))))))

(defop (udp-socket :set-gauges) (gauge-list)
  "Set the active gauges for this udp user socket"
  (when (udp-user-enabled self)
    (add-network-statistics-block (udp-user-statistics-block self))
    (set-gauges (udp-user-statistics-block self)
		(locf (udp-user-active-gauges self))
		(locf (udp-user-inactive-gauges self))
		(udp-user-gauge-name self)
		gauge-list)))

(defop (udp-socket :make-gauges) (&optional (gauge-list '(:apr :aps :abr :abs)))
  "Add selected gauges for this udp user socket"
  (when (udp-user-enabled self)
    (add-network-statistics-block (udp-user-statistics-block self))
    (add-gauges (udp-user-statistics-block self)
		(locf (udp-user-active-gauges self))
		(locf (udp-user-inactive-gauges self))
		(udp-user-gauge-name self)
		gauge-list)))

(defop (udp-socket :kill-gauges) (&optional (gauge-list '(:ipr :apr :ips :aps :ibr :abr :ibs :abs)))
  "Delete selected gauges from a udp user socket.  Default is to delete all gauges"
  (delete-gauges (locf (udp-user-active-gauges self))
		 (locf (udp-user-inactive-gauges self))
		 gauge-list))

(defun using-udp-socket (host port keyword receiver &optional (couldnt-open #'false) local-port &aux header)
  (declare (zwei:indentation 3 2))
  (if (ip-header-p host)
      (setq host (ip:ih-dest-address (setq header host)))
    (let ((original-host host))
      (assert (numberp (setq host (parse-internet-address (setq original-host host))))
	      (host)
	      "~S is not a valid Internet host specification"
	      original-host)))
  (let ((user-socket (make-udp-socket :keyword keyword)))
    (unwind-protect
	(if (send user-socket :open :remote-port port :remote-address host :local-port local-port)
	    (funcall receiver
		     user-socket
		     (or header (make-ip-header :destination host)))
	  (funcall couldnt-open))
      (send user-socket :close))))

;;;IP interface

(defun udp-close (stream &optional (abort t))
  (do ()
      ((null (udp-receive-buffers stream)))
    (free-udp-buffer (pop (udp-receive-buffers stream))))
  (close-udp-sockets stream abort))

(defun udp-reset (stream)
  (close-udp-sockets stream :abort))
 
(defun close-udp-sockets (stream abort)
  (dolist (user (udp-user-socket-alist stream))
    (send (cdr user) :close abort)))

(defun match-udp-msg-with-socket (local-port remote-port remote-address)
  (do ((list (udp-user-socket-alist *udp-stream*) (cdr list))
       (generic-handler nil))
      ((null list) generic-handler)
    (when (= (caar list) local-port)
      (let* ((match (cdar list))
	     (rport (udp-user-remote-port match)))
	(if (null rport)
	    (setq generic-handler match)
	  (and (= remote-port rport)
	       (= remote-address (udp-user-remote-address match))
	       (return match)))))))

(defun udp-icmp (type header buffer)
  (let ((user-socket (match-udp-msg-with-socket (udp-source-port buffer)
						(udp-destination-port buffer)
						(ip:ih-dest-address header))))
    (if user-socket
	(push-fifo (list type header buffer) (udp-user-packet-list user-socket))
      (free-ip-header header))))

(defun receive-udp-packet (stream buffer header local-p broadcast-p interface &aux user-socket)
  (declare (ignore interface))
  (unwind-protect
      (cond ((not (check-udp-checksum buffer header local-p))
	     (incf (udp-checksum-failures stream))
	     (free-ip-header header))
	    ((setq user-socket (match-udp-msg-with-socket (udp-destination-port buffer)
							  (udp-source-port buffer)
							  (ip:ih-source-address header)))
	     (cond ((null (udp-user-receive-buffers user-socket))
		    (incf (udp-user-packets-received-discarded user-socket))
		    (incf (udp-user-bytes-received-discarded user-socket) (- (length buffer) 8))
		    (unless broadcast-p
		      (icmp:icmp :source-quench (list header buffer)))
		    (free-ip-header header))
		   (t
		    (push-fifo (list :data
				     header
				     (let ((length (- (fill-pointer buffer) udp-first-data-byte)))
				       (make-array length
						   :element-type '(unsigned-byte 8)
						   :displaced-to buffer
						   :displaced-index-offset udp-first-data-byte))
				     (udp-source-port buffer)
				     broadcast-p)
			       (udp-user-packet-list user-socket))
		    (incf (udp-user-packets-received user-socket))
		    (incf (udp-user-bytes-received user-socket) (- (length buffer) 8))
		    (setq buffer (pop (udp-user-receive-buffers user-socket))))))
	    (t					;(null user-socket)
	     (incf (net:tp-packets-received-discarded stream))
	     (incf (net:tp-bytes-received-discarded stream) (fill-pointer buffer))
	     (unless broadcast-p
	       (icmp:icmp :destination-unreachable (list header buffer) icmp:icmp-port-unreachable))
	     (free-ip-header header)))
    (send stream :receive buffer)))
