;;; -*- Mode:LISP; Package:CHAOS; Base:10; Readtable:CL -*-
;;;	** (c) Copyright 1981 Massachusetts Institute of Technology **
;;;
;;; This is SYS: NETWORK; CHAOS; CHUSE
;;; Very high-level CHAOSnet functions.
;;; The NCP and low level functions in SYS: NETWORK; CHAOS; CHSNCP

;;; This returns a connection to frob, and the host name
(defun establish-connection (real-address contact-name timeout window-size &aux conn host-name)
  (assure-enabled)
  (setq conn (open-connection real-address contact-name window-size))
  (setq host-name
	(or (si:get-host-from-address real-address :chaos)
	    real-address))
  (let ((wait-completed nil))
    (unwind-protect
	(progn
	  (wait conn 'rfc-sent-state timeout
		(format nil "Net Connect: ~A"
			(if (typep host-name 'instance)
			    (send host-name :short-name)
			  host-name)))
	  (setq wait-completed t))
      (unless wait-completed
	(remove-conn conn)))
    (values (and wait-completed conn) host-name)))

;;; This does a full "ICP": it sends an RFC, waits for a reply or timeout,
;;; and returns a string to get an error, or else the CONN to indicate that
;;; the foreign host sent an OPN and we are connected.
;;; The first argument gets parsed as an address.
(DEFUN CONNECT (ADDRESS CONTACT-NAME &OPTIONAL (WINDOW-SIZE DEFAULT-WINDOW-SIZE)
					       (TIMEOUT (* 10. 60.))
			      &AUX CONN REAL-ADDRESS HOST-NAME)
  "Establish a chaosnet connection and return the connection object.
ADDRESS is a host name or number.  CONTACT-NAME is a string containing
the contact name and optional additional data for the other host.
WINDOW-SIZE is the number of packets that can be in transit from
the other side, on this connection.
TIMEOUT is how long to wait before giving up (in 60'ths of a second).

If the connection fails, an error is signaled."
  (DO-FOREVER
    (CATCH-ERROR-RESTART-EXPLICIT-IF T (SYS:REMOTE-NETWORK-ERROR
					 :RETRY-CONNECTION "Try the connection again.")
      (COND ((NULL (SETQ REAL-ADDRESS (ADDRESS-PARSE ADDRESS)))
	     (FERROR 'SYS:UNKNOWN-ADDRESS "~S is not a valid Chaosnet address." ADDRESS))
	    (T
	     (multiple-value (conn host-name)
	       (establish-connection real-address contact-name timeout window-size))
	     (CASE (STATE CONN)
	       (OPEN-STATE (RETURN CONN))
	       (RFC-SENT-STATE (CLOSE-CONN CONN)
			       (funcall *chaos-stream* :flush-address (conn-foreign-address conn))
			       (FERROR 'SYS:HOST-NOT-RESPONDING-DURING-CONNECTION
				       "Host ~1@*~A not responding."
				       CONN HOST-NAME))
	       (ANSWERED-STATE (CLOSE-CONN CONN)
			       (FERROR 'SYS:CONNECTION-ERROR-1
				       "Received an ANS instead of an OPN."
				       CONN))
	       (CLS-RECEIVED-STATE
		(LET* ((PKT (GET-NEXT-PKT CONN))
		       (STRING (STRING-APPEND (PKT-STRING PKT))))
		  (RETURN-PKT PKT)
		  (CLOSE-CONN CONN)
		  (IF (EQUAL STRING "")
		      (FERROR 'SYS:CONNECTION-REFUSED
			      "Connection to ~1@*~A rejected without explanation."
			      CONN HOST-NAME)
		    (FERROR 'SYS:CONNECTION-REFUSED	
			    "Connection to ~1@*~A refused: ~A."
			    CONN HOST-NAME STRING))))
	       (OTHERWISE (UNWIND-PROTECT
			      (FERROR 'SYS:CONNECTION-ERROR-1
				      "Bad state in ~S: ~A~@[, ~A~]"
				      CONN
				      (STATE CONN) 
				      (AND (READ-PKTS CONN) (PKT-STRING (READ-PKTS CONN))))
			    (REMOVE-CONN CONN)))))))
    ;; The second time, wait a long time.
    (SETQ TIMEOUT (* 2 TIMEOUT))))

;;; Takes anything anyone might use as a ChaosNet address, and tries to return
;;; the corresponding host number.  If it fails, returns NIL.
(DEFUN ADDRESS-PARSE (ADDRESS &AUX HOST)
  "Coerce the argument to a chaosnet address.
The argument can be a host name or host object, or an address."
  (DECLARE (VALUES ADDRESS HOST-OBJECT))
  (CONDITION-CASE (ERROR)
      (LET ((ADDRESS (COND ((INTEGERP ADDRESS)
			    ADDRESS)
			   ((AND (TYPEP ADDRESS 'INSTANCE)
				 (SEND (SETQ HOST ADDRESS) :SEND-IF-HANDLES :network-ADDRESS :chaos)))
			   ((AND (SETQ HOST (SI:PARSE-HOST ADDRESS T))
				 (SEND HOST :network-address :CHAOS)))
			   ((AND (STRINGP ADDRESS)
				 (PARSE-NUMBER ADDRESS 0 NIL 8))))))
	(IF ADDRESS (VALUES ADDRESS (OR HOST (SI:GET-HOST-FROM-ADDRESS ADDRESS :CHAOS)))))
    (SYS:UNCLAIMED-MESSAGE NIL)))

;;; This is used to perform a "simple connection".  An RFC is sent to the
;;; specified address, expecting an ANS.  Returns a string if there was an
;;; error, in which case the string is an ASCII explanation.  Otherwise
;;; returns the ANS.  When you are done perusing the ANS, RETURN-PKT the PKT.
(DEFUN SIMPLE (ADDRESS CONTACT-NAME &OPTIONAL (TIMEOUT (* 10. 60.))
	       &AUX CONN REAL-ADDRESS HOST-NAME)
  "Send a message to CONTACT-NAME at ADDRESS, expecting one ANS packet in return.
No connection is established; if the other host tries to create a connection,
it is considered an error.
If successful, the ANS packet object is returned.
Otherwise, a string describing the reasons for failure is returned.
TIMEOUT is how long to wait before giving up, in 60'ths of a second."
  (DO-FOREVER
    (CATCH-ERROR-RESTART-EXPLICIT-IF T (SYS:REMOTE-NETWORK-ERROR :RETRY-CONNECTION
					 "Try the transaction again.")
      (COND ((NULL (SETQ REAL-ADDRESS (ADDRESS-PARSE ADDRESS)))
	     (FERROR 'SYS:UNKNOWN-ADDRESS "~S is not a valid Chaosnet address." ADDRESS))
	    (T (multiple-value (conn host-name)
		 (establish-connection real-address contact-name timeout 5))
	       (CASE (STATE CONN)
		 (RFC-SENT-STATE
		  (REMOVE-CONN CONN)
		  (funcall *chaos-stream* :flush-address (conn-foreign-address conn))
		  (FERROR 'SYS:HOST-NOT-RESPONDING-DURING-CONNECTION
			  "Host ~1@*~A not responding."
			  CONN HOST-NAME))
		 (CLS-RECEIVED-STATE
		  (LET* ((PKT (GET-NEXT-PKT CONN))
			 (STRING (STRING-APPEND (PKT-STRING PKT))))
		    (RETURN-PKT PKT)
		    (REMOVE-CONN CONN)
		    (IF (EQUAL STRING "")
			(FERROR 'SYS:CONNECTION-REFUSED
				"Simple transaction to ~1@*~S rejected without explanation."
				CONN HOST-NAME)
		        (FERROR 'SYS:CONNECTION-REFUSED
				"Simple transaction to ~1@*~S refused: ~A."
				CONN HOST-NAME STRING))))
		 (OPEN-STATE
		  (CLOSE-CONN CONN "I expected an ANS, not an OPN.")
		  (FERROR 'SYS:CONNECTION-ERROR-1
			  "Received an OPN instead of an ANS."
			  CONN))
		 (ANSWERED-STATE
		  (RETURN (PROG1 (GET-NEXT-PKT CONN)
				 (CLOSE-CONN CONN))))
		 (OTHERWISE (UNWIND-PROTECT
			      (FERROR 'SYS:CONNECTION-ERROR-1
				      "Bad state in ~S: ~A~@[, ~A~]"
				      CONN
				      (STATE CONN) 
				      (AND (READ-PKTS CONN) (PKT-STRING (READ-PKTS CONN))))
			      (REMOVE-CONN CONN)))))))
    (SETQ TIMEOUT (* 2 TIMEOUT))))

(DEFMACRO VALID-ADDRESS? (ADDRESS)
  `(TYPEP ,ADDRESS '(INTEGER 0 #o177777)))

;;;; USER FUNCTIONS: Functions for the user side of a connection.

;;; This is called as the first step in opening a connection.  Note the
;;; CONNECT function, which is a higher-level frob (like NETWRK's ICP routine)
;;; which you may want to use instead.
;;;   The first arg is the address of the foreign host.  Next is the contact name.
;;; Optionally following are the one-way flag and window size.
(DEFUN OPEN-CONNECTION (ADDRESS CONTACT-NAME &OPTIONAL (WINDOW-SIZE DEFAULT-WINDOW-SIZE)
		      &AUX PKT CONN)
    (CHECK-TYPE ADDRESS (SATISFIES VALID-ADDRESS?) "an address")
    (CHECK-ARG CONTACT-NAME
               (AND (STRINGP CONTACT-NAME) ( (LENGTH CONTACT-NAME) MAX-DATA-BYTES-PER-PKT))
               "a string")
    (CHECK-TYPE WINDOW-SIZE NUMBER "a number")
    (SETQ CONN (MAKE-CONNECTION))
    (SETF (LOCAL-WINDOW-SIZE CONN) (MAX 1 (MIN WINDOW-SIZE MAXIMUM-WINDOW-SIZE)))
    (SETF (FOREIGN-ADDRESS CONN) ADDRESS)
    (SETF (GETF (CONN-PLIST CONN) 'RFC-CONTACT-NAME) CONTACT-NAME)

    (UNWIND-PROTECT
      (PROGN
	(SETQ PKT (ALLOCATE-PKT))
	(SETF (PKT-OPCODE PKT) RFC-OP)
	(SET-PKT-STRING PKT CONTACT-NAME)
	(SETF (PKT-LINK PKT) NIL)
	(WITHOUT-INTERRUPTS
	  (SETF (WINDOW-AVAILABLE CONN) 1)
	  (SETF (TIME-LAST-RECEIVED CONN) (zl:TIME))
	  (SETF (STATE CONN) 'RFC-SENT-STATE))
	(TRANSMIT-NORMAL-PKT CONN PKT (PKT-NUM-SENT CONN))
	;; Must not put on lists before calling TRANSMIT-NORMAL-PKT, which fills in
	;; important information    
	(WITHOUT-INTERRUPTS
	  (SETF (SEND-PKTS CONN) PKT)
	  (SETF (SEND-PKTS-LAST CONN) PKT)
	  (SETF (SEND-PKTS-LENGTH CONN) 1) 
	  (SETQ RETRANSMISSION-NEEDED T)
	  (SETQ PKT NIL)))
      (AND PKT (FREE-PKT PKT)))
    CONN)

#| This stuff can't work yet -- RpK
;;; Open up a connection for use with foreign protocols
(DEFUN OPEN-FOREIGN-CONNECTION (FOREIGN-HOST FOREIGN-INDEX
				&OPTIONAL (PKT-ALLOCATION 10.) DISTINGUISHED-PORT
				&AUX CONN)
  (CHECK-TYPE FOREIGN-HOST (SATISFIES VALID-ADDRESS?) "an address")
  (SETQ CONN (MAKE-CONNECTION))
  (SETF (LOCAL-WINDOW-SIZE CONN) (MAX 1 (MIN PKT-ALLOCATION MAXIMUM-WINDOW-SIZE)))
  (SETF (FOREIGN-ADDRESS CONN) FOREIGN-HOST)
  (SETF (FOREIGN-INDEX-NUM CONN) FOREIGN-INDEX)
  (SETF (STATE CONN) 'FOREIGN-STATE)
  (WHEN DISTINGUISHED-PORT
    (SETF (AREF INDEX-CONN (LDB MAXIMUM-INDEX-LOG-2-MINUS-1 (LOCAL-INDEX-NUM CONN))) NIL)
    (SETF (LOCAL-INDEX-NUM CONN) DISTINGUISHED-PORT)
    (PUSH (CONS DISTINGUISHED-PORT CONN) DISTINGUISHED-PORT-CONN-TABLE))
  CONN)

(DEFVAR *ALL-SUBNET-BIT-MAP* (MAKE-ARRAY 32. :element-type '(mod #o400) :INITIAL-ELEMENT #o377))

(DEFUN SUBNET-BIT-MAP (SUBNETS)
  "SUBNETS may be a list of subnet numbers, or the symbol :ALL"
  (DECLARE (VALUES BIT-MAP BIT-MAP-LENGTH))
  (COND ((EQ SUBNETS :ALL) (VALUES *ALL-SUBNET-BIT-MAP* 32.))
	((NULL SUBNETS) (VALUES (MAKE-ARRAY 4. :ELEMENT-TYPE 'string-char :INITIAL-ELEMENT 0) 4))
	(T
	 (LET* ((BIT-MAP-LENGTH (* (CEILING (CEILING (1+ (APPLY #'MAX SUBNETS)) 8.) 4) 4))
		(BIT-MAP (MAKE-ARRAY BIT-MAP-LENGTH :element-TYPE '(mod #o400) :INITIAL-ELEMENT 0)))
	   (DOLIST (SUBNET SUBNETS)
	     (MULTIPLE-VALUE-BIND (BYTE BIT) (TRUNCATE SUBNET 8)
	       (SETF (AREF BIT-MAP BYTE) (LOGIOR (AREF BIT-MAP BYTE) (LSH 1 BIT)))))
	   (VALUES BIT-MAP BIT-MAP-LENGTH)))))
  
(DEFUN OPEN-BROADCAST-CONNECTION (SUBNETS CONTACT-NAME &OPTIONAL (PKT-ALLOCATION 10.)
				  &AUX SUBNET-BIT-MAP SUBNET-BIT-MAP-LENGTH)
  "Broadcast a service request from CONTACT-NAME over certain subnets.
PKT-ALLOCATION is the buffering size for unread requests as they come over the net.
The connection returned is in the CHAOS:BROADCAST-SENT-STATE."
  (MULTIPLE-VALUE (SUBNET-BIT-MAP SUBNET-BIT-MAP-LENGTH) (SUBNET-BIT-MAP SUBNETS))
  (LET ((CONN (MAKE-CONNECTION)))
    (SETF (LOCAL-WINDOW-SIZE CONN) (MAX 1 (MIN PKT-ALLOCATION MAXIMUM-WINDOW-SIZE)))
    (SETF (FOREIGN-ADDRESS CONN) 0) ; seems ok
    ; (SETF (FOREIGN-INDEX-NUM CONN) FOREIGN-INDEX) ; not sure about this
    (LET ((PKT NIL))
      (UNWIND-PROTECT
	  (PROGN
	    (SETQ PKT (ALLOCATE-PKT))
	    (SETF (PKT-ACK-NUM PKT) SUBNET-BIT-MAP-LENGTH)
	    (SETF (PKT-OPCODE PKT) BRD-OP)
	    (SETF (PKT-LINK PKT) NIL)
	    (SETF (PKT-DEST-ADDRESS PKT) 0)
	    (SETF (PKT-DEST-INDEX-NUM PKT) 0)
	    (SETF (PKT-SOURCE-ADDRESS PKT) MY-ADDRESS)
	    (SETF (PKT-SOURCE-INDEX-NUM PKT) (LOCAL-INDEX-NUM CONN))
	    (SETF (GETF (CONN-PLIST CONN) 'BROADCAST-CONNECTION) T)
	    (SETF (GETF (CONN-PLIST CONN) 'SUBNET-BIT-MAP) SUBNET-BIT-MAP)
	    (SETF (GETF (CONN-PLIST CONN) 'SUBNET-BIT-MAP-LENGTH) SUBNET-BIT-MAP-LENGTH)
	    (SETF (GETF (CONN-PLIST CONN) 'CONTACT-NAME) CONTACT-NAME)
	    (SET-PKT-STRING PKT SUBNET-BIT-MAP CONTACT-NAME)
	    (WITHOUT-INTERRUPTS
	      (SETF (WINDOW-AVAILABLE CONN) 1)
	      (SETF (TIME-LAST-RECEIVED CONN) (zl:TIME))
	      (SETF (STATE CONN) 'BROADCAST-SENT-STATE))
	    (TRANSMIT-PKT PKT ()))
	(AND PKT (FREE-PKT PKT)))
    CONN)))

(DEFMACRO ASSURE-BROADCAST-CONNECTION (CONN)
  `(IF (NOT (GETF (CONN-PLIST ,CONN) 'BROADCAST-CONNECTION))
       (FERROR 'SYS:LOCAL-NETWORK-ERROR
	       :FORMAT-STRING "~A was not opened in broadcast mode"
	       :FORMAT-ARGS (LIST ,CONN))))

(DEFUN RETRANSMIT-BRD-PACKET (CONN)
  "Send out another request for service, if CONN was opened in broadcast mode."
  (ASSURE-BROADCAST-CONNECTION CONN)
  (LET ((PKT (ALLOCATE-PKT))
	(SUBNET-BIT-MAP-LENGTH (GET (LOCF (CONN-PLIST CONN)) 'SUBNET-BIT-MAP-LENGTH)))
    (UNWIND-PROTECT
	(PROGN
	  (SETF (PKT-ACK-NUM PKT) SUBNET-BIT-MAP-LENGTH)
	  (SETF (PKT-OPCODE PKT) BRD-OP)
	  (SETF (PKT-DEST-ADDRESS PKT) 0)
	  (SETF (PKT-DEST-INDEX-NUM PKT) 0)
	  (SETF (PKT-SOURCE-ADDRESS PKT) MY-ADDRESS)
	  (SETF (PKT-SOURCE-INDEX-NUM PKT) (LOCAL-INDEX-NUM CONN))
	  (SET-PKT-STRING PKT (GET (LOCF (CONN-PLIST CONN)) 'SUBNET-BIT-MAP)
			  (GET (LOCF (CONN-PLIST CONN)) 'CONTACT-NAME))
	  (TRANSMIT-PKT PKT ()))
      (FREE-PKT PKT))))

(DEFUN READ-BROADCAST-PKT (CONN &KEY NO-HANG-P (RESET-STATE-P :ANS) (WHOSTATE "BRD In"))
  "Returns a PKT or NIL, like GET-NEXT-PKT.
This function will do nasty things if not called on a broadcast CONN.
RESET-STATE-P can be
 :ANS only if an ANS was received
 :ALWAYS if any type of packet was received
 NIL never"
  (ASSURE-BROADCAST-CONNECTION CONN)
  (LET ((PKT (GET-NEXT-PKT CONN NO-HANG-P WHOSTATE)))
    (UNLESS (NULL PKT)
      (IF (OR (EQ RESET-STATE-P :ALWAYS)
	      (AND (EQ (STATE CONN) 'ANSWERED-STATE) (EQ RESET-STATE-P :ANS)))
	  (SETF (STATE CONN) 'BROADCAST-SEND-STATE))
      PKT)))
|#  

;;;; SERVER FUNCTIONS: Functions used by the server side of a connection only.

(DEFUN LISTEN (CONTACT-NAME &OPTIONAL (WINDOW-SIZE DEFAULT-WINDOW-SIZE) (WAIT-FOR-RFC T)
		     &AUX CONN)
    "Listen for an incoming RFC to CONTACT-NAME.
Returns the connection-object, ready to have CHAOS:ACCEPT,
CHAOS:REJECT, CHAOS:ANSWER, or CHAOS:FORWARD done to it.
A server function on SERVER-ALIST can call LISTEN to respond to
the request which caused the server to be run.
If WAIT-FOR-RFC is NIL, doesn't wait for the RFC to arrive, just sets up a queue.
WINDOW-SIZE specifies how many packets can be in transit at once from the
other side of the connection to this one, once the connection is established."
    (CHECK-TYPE CONTACT-NAME STRING)
    (CHECK-TYPE WINDOW-SIZE NUMBER)
    ;; Make a connection.  If table full, wait a little while and try again.
    (DO-FOREVER
      (CONDITION-CASE ()
          (SETQ CONN (MAKE-CONNECTION))
	(SYS:NETWORK-RESOURCES-EXHAUSTED
	  (PROCESS-SLEEP 30.))
	(:NO-ERROR (RETURN))))
    (SETF (GETF (CONN-PLIST CONN) 'LISTEN-CONTACT-NAME) CONTACT-NAME)
    (SETF (LOCAL-WINDOW-SIZE CONN) (MAX 1 (MIN WINDOW-SIZE MAXIMUM-WINDOW-SIZE)))
    (PROG LISTEN ()
      (WITHOUT-INTERRUPTS			;First try to pick up a pending RFC
	(DO ((PKT PENDING-RFC-PKTS (PKT-LINK PKT))
	     (PREV NIL PKT))
	    ((NULL PKT))
	  (COND ((STRING-EQUAL (CONTACT-NAME-FROM-RFC PKT) CONTACT-NAME)
		 (COND ((NULL PREV) (SETQ PENDING-RFC-PKTS (PKT-LINK PKT)))
		       (T (SETF (PKT-LINK PREV) (PKT-LINK PKT))))
		 (RFC-MEETS-LSN CONN PKT)
		 (RETURN-FROM LISTEN CONN))))
	(SETF (STATE CONN) 'LISTENING-STATE)	;No RFC, let listen pend
	(PUSH (CONS CONTACT-NAME CONN) PENDING-LISTENS))
      (COND (WAIT-FOR-RFC
	     (PROCESS-WAIT "Net Listen"
			   #'(LAMBDA (CONN) (NEQ (STATE CONN) 'LISTENING-STATE))
			   CONN)
	     (OR (EQ (STATE CONN) 'RFC-RECEIVED-STATE)
		 (FERROR 'SYS:BAD-CONNECTION-STATE-1
			 "Listening connection ~S entered bad state ~S"
			 CONN (STATE CONN)))))
      (RETURN CONN)))

;;; If you have done a LISTEN and the state has changed to RFC-RECEIVED, you
;;; call one of the following four functions.

;;; Send an OPN, and leave conn in OPEN-STATE.
;;; Note that when this returns the other end has not yet acknowledged
;;; the OPN, and the window size is still 0.  Transmitting the first packet
;;; will wait.
(DEFUN ACCEPT (CONN &AUX PKT)
  "Accept a request for a connection, received on connection-object CONN.
CONN should have been returned by a previous call to LISTEN.
Note that the connection is not completely established
until the other side replies to the packet we send."
    (OR (EQ (STATE CONN) 'RFC-RECEIVED-STATE)
        (FERROR 'SYS:BAD-CONNECTION-STATE-1
		"Attempt to accept ~S, which was in ~A, not RFC-RECEIVED-STATE"
		CONN (STATE CONN)))
    (SETQ PKT (READ-PKTS CONN))
    (COND (PKT					;In case the user has not read the RFC
	   (SETF (PKT-NUM-RECEIVED CONN) (PKT-NUM PKT))
	   (SETF (READ-PKTS CONN) (PKT-LINK PKT))
	   (OR (READ-PKTS CONN)
	       (SETF (READ-PKTS-LAST CONN) NIL))
	   (FREE-PKT PKT)))
    (SETQ PKT (ALLOCATE-PKT))
    (SETF (PKT-OPCODE PKT) OPN-OP)
    (SETF (PKT-NBYTES-on-write PKT) 4)
    (SETF (PKT-SECOND-DATA-WORD PKT) (LOCAL-WINDOW-SIZE CONN))
    (SETF (PKT-FIRST-DATA-WORD PKT) (PKT-NUM-READ CONN))
    (WITHOUT-INTERRUPTS
      (SETF (PKT-LINK PKT) NIL)
      (SETF (WINDOW-AVAILABLE CONN) 0)
      (SETF (TIME-LAST-RECEIVED CONN) (zl:TIME))
      (SETF (STATE CONN) 'OPEN-STATE))  ;Set this -before- telling other end it's open!
    (TRANSMIT-NORMAL-PKT CONN PKT T)
    (WITHOUT-INTERRUPTS
      ;; TRANSMIT-NORMAL-PKT fills in fields that must be filled before packet
      ;; can be put on transmit list
      (SETF (SEND-PKTS CONN) PKT)
      (SETF (SEND-PKTS-LAST CONN) PKT)
      (SETF (SEND-PKTS-LENGTH CONN) 1)
      (SETQ RETRANSMISSION-NEEDED T))
    T)

;;; Send a CLS and leave conn INACTIVE.
(DEFUN REJECT (CONN REASON)
  "Reject a request for a connection, received on connection-object CONN.
CONN should have been returned by a previous call to LISTEN.
REASON is a string to be sent to the requestor and returned from
his call to CONNECT."
    (OR (EQ (STATE CONN) 'RFC-RECEIVED-STATE)
        (FERROR 'SYS:BAD-CONNECTION-STATE-1
		"Attempt to reject ~S, which was in ~A, not RFC-RECEIVED-STATE"
		CONN (STATE CONN)))
    (CLOSE-CONN CONN REASON)
    T)

;; Send an ANS, and leave conn INACTIVE.
;; The caller passes in a PKT with data and NBYTES set up.
(DEFUN ANSWER (CONN PKT)
  "Reply to a simple transaction received on connection-object CONN.
PKT should be a packet with ANS as its opcode and the data and nbytes fields set up.
This is the proper way to answer when the requestor has used the function CHAOS:SIMPLE.
Note that there is no guarantee that the requestor will receive the answer;
he will just repeat the request if he does not.
See also CHAOS:ANSWER-STRING."
  (WHEN (EQ (STATE CONN) 'RFC-RECEIVED-STATE)
    (SETF (PKT-OPCODE PKT) ANS-OP)
    (TRANSMIT-NORMAL-PKT CONN PKT))
  (RETURN-PKT PKT)
  (REMOVE-CONN CONN)
  T)

(DEFUN ANSWER-STRING (CONN STRING)
  "Reply to a simple transaction received on connection-object CONN.
STRING specifies the answer to send.
This is the proper way to answer when the requestor has used the function CHAOS:SIMPLE.
Note that there is no guarantee that the requestor will receive the answer;
he will just repeat the request if he does not.
See also CHAOS:ANSWER, a lower level way of answering."
  (LET ((PKT (GET-PKT)))
    (SETF (PKT-NBYTES-on-write PKT) (MIN (STRING-LENGTH STRING) MAX-DATA-BYTES-PER-PKT))
    (COPY-ARRAY-CONTENTS STRING (PKT-STRING PKT))
    (ANSWER CONN PKT)))

;;; Minimal-consing simple-transaction answerer.
;;; Returns T if succeeds, NIL if fails, although you probably don't care, since
;;; a value of T does not assure that the ANS really reached the requestor.
(DEFUN FAST-ANSWER-STRING (CONTACT-NAME STRING)
  "Reply to a simple transaction requested on CONTACT-NAME, with answer STRING.
This is like (ANSWER-STRING (LISTEN contact-name) string) but conses less."
  (PROG ((PREV NIL) RFC PKT PSTR)
    (WITHOUT-INTERRUPTS
      (SETQ RFC (DO PKT PENDING-RFC-PKTS (PKT-LINK PKT) (NULL PKT)
		    (AND (STRING-EQUAL (CONTACT-NAME-FROM-RFC PKT) CONTACT-NAME)
			 (RETURN PKT))
		    (SETQ PREV PKT)))
      (IF (NULL RFC) (RETURN NIL)
	  (IF (NULL PREV) (SETQ PENDING-RFC-PKTS (PKT-LINK RFC))
	      (SETF (PKT-LINK PREV) (PKT-LINK RFC)))))
    (setq pkt (net:allocate-packet))
    (SETF (PKT-NBYTES-on-write PKT) (MIN (STRING-LENGTH STRING) MAX-DATA-BYTES-PER-PKT))
    (SETQ PSTR	     ;Create indirect array to reference as a string
	  (MAKE-STRING MAX-DATA-BYTES-PER-PKT :FILL-POINTER 0
		       			      :DISPLACED-TO PKT
					      :DISPLACED-INDEX-OFFSET 16.))
    (COPY-ARRAY-CONTENTS STRING PSTR)
    ;(RETURN-ARRAY (PROG1 PSTR (SETQ PSTR NIL)))
    (SETF (PKT-SOURCE-ADDRESS PKT) MY-ADDRESS)
    (SETF (PKT-SOURCE-INDEX-NUM PKT) 0)
    (SETF (PKT-DEST-ADDRESS PKT) (PKT-SOURCE-ADDRESS RFC))
    (SETF (PKT-DEST-INDEX-NUM PKT) (PKT-SOURCE-INDEX-NUM RFC))
    (SETF (PKT-OPCODE PKT) ANS-OP)
    (SETF (PKT-NUM PKT) 0)
    (SETF (PKT-ACK-NUM PKT) 0)
    (TRANSMIT-INT-PKT PKT)
    (SETF (PKT-STATUS RFC) NIL)
    (FREE-PKT RFC)
    (RETURN T)))

(DEFUN FORWARD (CONN PKT HOST)
  "Forward a request for a connection to some other host and//or contact name.
CONN should be a connection object returned by LISTEN on which a
request has been received.  PKT should have opcode CHAOS:FWD-OP and its
data (and PKT-NBYTES) set to the new contact name to forward to.
HOST should specify the host to forward to."
  (OR (EQ (STATE CONN) 'RFC-RECEIVED-STATE)
      (FERROR 'SYS:BAD-CONNECTION-STATE-1
	      "Attempt to forward ~S, which was in ~A, not RFC-RECEIVED-STATE"
	      CONN (STATE CONN)))
  (SETF (PKT-OPCODE PKT) FWD-OP)
  (TRANSMIT-NORMAL-PKT CONN PKT 0 HOST)
  (RETURN-PKT PKT)
  (REMOVE-CONN CONN)
  T)

(DEFUN FORWARD-ALL (CONTACT-NAME HOST)
  "Tell all requests for chaosnet connections to CONTACT-NAME to try host HOST instead."
  (SETQ HOST (ADDRESS-PARSE HOST))
  (PUSH (LIST CONTACT-NAME
	      `(PROG (CONN)
		     (SETQ CONN (LISTEN ,CONTACT-NAME))
		     (FORWARD CONN (GET-NEXT-PKT CONN) ,HOST)))
	SERVER-ALIST)
  NIL)


;;;; Control operations used by both users and servers.

;;; If CONN has received a close, free it up.
;;; If CONN is inactive, do nothing.
;;; If CONN is open, send a CLS containing the reason, leaving CONN inactive.
(DEFUN CLOSE-CONN (CONN &OPTIONAL (REASON "") &AUX PKT)
  "Close a chaosnet connection, given connection-object CONN.
REASON is a string telling the other side why; but don't rely
on its being received."
    (CASE (STATE CONN)
      ((CLS-RECEIVED-STATE ANSWERED-STATE)
       (REMOVE-CONN CONN)
       NIL)
      (INACTIVE-STATE
       (SETQ CONN-LIST (DELQ CONN CONN-LIST))
       NIL)
      ((OPEN-STATE RFC-RECEIVED-STATE)
       (SETQ PKT (ALLOCATE-PKT))
       (SETF (PKT-OPCODE PKT) CLS-OP)
       (SET-PKT-STRING PKT REASON)
       (TRANSMIT-NORMAL-PKT CONN PKT)
       (FREE-PKT PKT)
       (REMOVE-CONN CONN)
       NIL)
      ((LOS-RECEIVED-STATE HOST-DOWN-STATE LISTENING-STATE RFC-SENT-STATE)
       (REMOVE-CONN CONN)
       NIL)
      (OTHERWISE
       (FERROR 'SYS:BAD-CONNECTION-STATE-1
	       "Attempt to close ~S, which was in ~S, not an acceptable state"
	       CONN (STATE CONN)))))

(DEFF CLOSE 'CLOSE-CONN)
(DEFF CHAOS-CLOSE 'CLOSE-CONN)
(MAKE-OBSOLETE CLOSE "use CHAOS:CLOSE-CONN")
(MAKE-OBSOLETE CHAOS-CLOSE "use CHAOS:CLOSE-CONN")

;;; Wait until either:
;;;  the state of CONN is not STATE  (return T), or
;;;  over TIMEOUT 60ths of a second happen (return NIL).
(DEFUN WAIT (CONN STATE TIMEOUT &OPTIONAL (WHOSTATE "Chaosnet Wait") &AUX START-TIME)
  "Wait for chaosnet connection CONN to be in a state other than STATE.
Alternatively, waiting ends after TIMEOUT time (measured in 60'ths).
Returns non-NIL iff the connection's state has changed.
WHOSTATE is a string to tell the user what you are waiting for."
   (SETQ START-TIME (zl:TIME))
   (DO () (NIL)
     (OR (EQ STATE (STATE CONN))
	 (RETURN T))
     (OR (< (TIME-DIFFERENCE (zl:TIME) START-TIME) TIMEOUT)
	 (RETURN NIL))
     (PROCESS-WAIT WHOSTATE
		   (FUNCTION (LAMBDA (CONN STATE START-TIME TIMEOUT)
				     (OR (NEQ (STATE CONN) STATE)
					 ( (TIME-DIFFERENCE (zl:TIME) START-TIME) TIMEOUT))))
		   CONN
		   STATE
		   START-TIME
		   TIMEOUT)))

;;; Send the specied format string, and eof and close
(DEFUN FORMAT-AND-EOF (CONN &REST FORMAT-ARGS)
  (CONDITION-CASE ()
      (PROGN
	(ACCEPT CONN)
	(WITH-OPEN-STREAM (STREAM (MAKE-STREAM CONN))
	  (APPLY #'FORMAT STREAM FORMAT-ARGS)))
    (SYS:REMOTE-NETWORK-ERROR NIL)))


;;;; Streams
;;; This is included in all chaosnet streams, input or output
(DEFFLAVOR BASIC-STREAM
	((CONNECTION NIL))
	()
  (:INCLUDED-FLAVORS SI:STREAM)
  (:INITABLE-INSTANCE-VARIABLES CONNECTION)
  (:GETTABLE-INSTANCE-VARIABLES CONNECTION))


;;; To find out what chaos host a stream is open to.
(DEFMETHOD (BASIC-STREAM :FOREIGN-HOST) ()
  (SI:GET-HOST-FROM-ADDRESS (FOREIGN-ADDRESS CONNECTION) :CHAOS))

(DEFMETHOD (BASIC-STREAM :CONTACT-NAME) () (CONTACT-NAME CONNECTION))

(DEFMETHOD (BASIC-STREAM :CLOSE) (&OPTIONAL ABORT-P)
  (WHEN CONNECTION				;Allowed to keep doing this
    (CLOSE-CONN CONNECTION (IF ABORT-P "Aborted" ""))
    (REMOVE-CONN (PROG1 CONNECTION (SETQ CONNECTION NIL)))))

(DEFMETHOD (BASIC-STREAM :ACCEPT) ()
  (ACCEPT CONNECTION))

(DEFMETHOD (BASIC-STREAM :REJECT) (&OPTIONAL REASON)
  (REJECT CONNECTION (OR REASON "")))

;;; These are new operations for the coming network system
(DEFMETHOD (BASIC-STREAM :ADD-AS-SERVER) (NAME &OPTIONAL (PROCESS CURRENT-PROCESS))
  (SEND TV:WHO-LINE-FILE-STATE-SHEET :ADD-SERVER CONNECTION NAME PROCESS))

(DEFMETHOD (BASIC-STREAM :DELETE-AS-SERVER) ()
  (SEND TV:WHO-LINE-FILE-STATE-SHEET :DELETE-SERVER CONNECTION))

(DEFMETHOD (BASIC-STREAM :NETWORK) () :CHAOS)

(DEFVAR *SECURITY-FUNCTION* NIL "If T, a predicate called with the chaos address")
(DEFUN SECURE-P-INTERNAL (CONNECTION)
  (IF *SECURITY-FUNCTION* (FUNCALL *SECURITY-FUNCTION* (FOREIGN-ADDRESS CONNECTION)) T))

(DEFMETHOD (BASIC-STREAM :SECURE-P) () (SECURE-P-INTERNAL CONNECTION))

;;; This is included in all chaosnet input streams, character and binary
(DEFFLAVOR INPUT-STREAM-MIXIN
	((INPUT-PACKET nil))
	()
  (:INCLUDED-FLAVORS SI:BASIC-BUFFERED-INPUT-STREAM))

(DEFMETHOD (INPUT-STREAM-MIXIN :DISCARD-INPUT-BUFFER) (IGNORE)
  (when input-packet
    (RETURN-PKT INPUT-PACKET)
    (setq input-packet nil)))

(defmethod (input-stream-mixin :before :close) (&rest ignore)
  (when input-packet
    (return-pkt input-packet)
    (setq input-packet nil)))

;;; This is included in all chaosnet output streams, character and binary
(DEFFLAVOR OUTPUT-STREAM-MIXIN
	(OUTPUT-PACKET)
	()
  (:INCLUDED-FLAVORS SI:BASIC-BUFFERED-OUTPUT-STREAM))

(DEFMETHOD (OUTPUT-STREAM-MIXIN :DISCARD-OUTPUT-BUFFER) (IGNORE)
  (RETURN-PKT OUTPUT-PACKET)
  (SETQ OUTPUT-PACKET NIL))

;;; This is included in simple chaosnet input streams, but not file streams, where certain
;;; opcodes have special meaning.
(DEFFLAVOR BASIC-INPUT-STREAM
	((INPUT-PACKET NIL))
	(INPUT-STREAM-MIXIN BASIC-STREAM))

(DEFMETHOD (BASIC-INPUT-STREAM :GET-NEXT-INPUT-PKT) (NO-HANG-P &AUX OP)
  (COND ((AND INPUT-PACKET
	      (OR (= (SETQ OP (PKT-OPCODE INPUT-PACKET)) EOF-OP)
		  (= OP CLS-OP)))
	 NIL)
	((NULL (SETQ INPUT-PACKET (GET-NEXT-PKT CONNECTION NO-HANG-P "Chaosnet Input" T)))
	 NIL)
	((OR (= (SETQ OP (PKT-OPCODE INPUT-PACKET)) EOF-OP)
	     (= OP CLS-OP))
	 NIL)
	(( OP DAT-OP)
	 T)
	(T
	 (FERROR NIL "Unknown opcode ~O in packet ~S received from connection ~S"
		 OP INPUT-PACKET CONNECTION))))

(DEFMETHOD (BASIC-INPUT-STREAM :CLEAR-EOF) ()
  (COND ((AND INPUT-PACKET (= (PKT-OPCODE INPUT-PACKET) EOF-OP))
	 (RETURN-PKT INPUT-PACKET)
	 (SETQ INPUT-PACKET NIL))))

;;; This is included in simple chaosnet output streams, but not file streams, where a
;;; connection is maintained for longer.
(DEFFLAVOR BASIC-OUTPUT-STREAM
	()
	(OUTPUT-STREAM-MIXIN BASIC-STREAM)
  (:INCLUDED-FLAVORS SI:BASIC-BUFFERED-OUTPUT-STREAM))

(DEFMETHOD (BASIC-OUTPUT-STREAM :EOF) ()
  (SEND SELF :FORCE-OUTPUT)
  (SEND-PKT CONNECTION (GET-PKT) EOF-OP)
  (FINISH-CONN CONNECTION))

(DEFMETHOD (BASIC-OUTPUT-STREAM :FINISH) ()
  (FINISH-CONN CONNECTION))

(DEFMETHOD (BASIC-OUTPUT-STREAM :BEFORE :CLOSE) (&OPTIONAL ABORT-P)
  (AND CONNECTION (NOT ABORT-P)
       (EQ (STATE CONNECTION) 'OPEN-STATE)
       (SEND SELF :EOF)))

(DEFFLAVOR CHARACTER-INPUT-STREAM-MIXIN
	(INPUT-PACKET)
	(INPUT-STREAM-MIXIN)
  (:INCLUDED-FLAVORS BASIC-STREAM SI:BASIC-BUFFERED-INPUT-STREAM)
  ;; :GET-NEXT-INPUT-PKT returns T if INPUT-PACKET is a valid packet
  (:REQUIRED-METHODS :GET-NEXT-INPUT-PKT))

(DEFMETHOD (CHARACTER-INPUT-STREAM-MIXIN :ELEMENT-TYPE) () 'STRING-CHAR)

(DEFMETHOD (CHARACTER-INPUT-STREAM-MIXIN :NEXT-INPUT-BUFFER) (&OPTIONAL NO-HANG-P)
  (AND (SEND SELF :GET-NEXT-INPUT-PKT NO-HANG-P)
       (VALUES (PKT-STRING INPUT-PACKET)
	       0
	       (PKT-NBYTES INPUT-PACKET))))

(DEFFLAVOR BINARY-INPUT-STREAM-MIXIN
	(INPUT-PACKET)
	(INPUT-STREAM-MIXIN)
  (:INCLUDED-FLAVORS BASIC-STREAM SI:BASIC-BUFFERED-INPUT-STREAM)
  (:REQUIRED-METHODS :GET-NEXT-INPUT-PKT))

(DEFMETHOD (BINARY-INPUT-STREAM-MIXIN :ELEMENT-TYPE) () '(UNSIGNED-BYTE 8))

(DEFMETHOD (BINARY-INPUT-STREAM-MIXIN :NEXT-INPUT-BUFFER) (&OPTIONAL NO-HANG-P)
  (AND (SEND SELF :GET-NEXT-INPUT-PKT NO-HANG-P)
       (LET ((ET (SEND-IF-HANDLES SELF :ELEMENT-TYPE)))
	 (COND ((AND (CONSP ET)
		     (MEMQ (CAR ET) '(UNSIGNED-BYTE SIGNED-BYTE))
		     (EQ 8 (CADR ET)))
		(VALUES (PKT-STRING INPUT-PACKET)
			0
			(PKT-NBYTES INPUT-PACKET)))
	       ('ELSE
		(VALUES INPUT-PACKET
			FIRST-DATA-WORD-IN-PKT
			(+ FIRST-DATA-WORD-IN-PKT (TRUNCATE (PKT-NBYTES INPUT-PACKET) 2))))))))

(DEFFLAVOR CHARACTER-OUTPUT-STREAM-MIXIN
	(OUTPUT-PACKET)
	(OUTPUT-STREAM-MIXIN)
  (:INCLUDED-FLAVORS BASIC-STREAM SI:BASIC-BUFFERED-OUTPUT-STREAM))

(DEFMETHOD (CHARACTER-OUTPUT-STREAM-MIXIN :ELEMENT-TYPE) () 'STRING-CHAR)

(DEFMETHOD (CHARACTER-OUTPUT-STREAM-MIXIN :NEW-OUTPUT-BUFFER) ()
  (SETQ OUTPUT-PACKET (GET-PKT))
  (VALUES (PKT-STRING OUTPUT-PACKET) 0 MAX-DATA-BYTES-PER-PKT))

(DEFMETHOD (CHARACTER-OUTPUT-STREAM-MIXIN :SEND-OUTPUT-BUFFER) SEND-CHARACTER-PKT)

(DECLARE-FLAVOR-INSTANCE-VARIABLES (CHARACTER-OUTPUT-STREAM-MIXIN)
(DEFUN SEND-CHARACTER-PKT (IGNORE IGNORE LENGTH)
  (SETF (PKT-NBYTES-on-write OUTPUT-PACKET) LENGTH)
  (SEND-PKT CONNECTION OUTPUT-PACKET)
  (SETQ OUTPUT-PACKET NIL)))

(DEFFLAVOR BINARY-OUTPUT-STREAM-MIXIN
	(OUTPUT-PACKET)
	(OUTPUT-STREAM-MIXIN)
  (:INCLUDED-FLAVORS BASIC-STREAM SI:BASIC-BUFFERED-OUTPUT-STREAM))

;due to unfortunate history, binary implies a default byte size of 16.
(DEFMETHOD (BINARY-OUTPUT-STREAM-MIXIN :ELEMENT-TYPE) () '(UNSIGNED-BYTE 16))

(DEFMETHOD (BINARY-OUTPUT-STREAM-MIXIN :NEW-OUTPUT-BUFFER) ()
  (SETQ OUTPUT-PACKET (GET-PKT))
  (LET ((ET (SEND-IF-HANDLES SELF :ELEMENT-TYPE)))
    (COND ((AND (CONSP ET)
		(MEMQ (CAR ET) '(UNSIGNED-BYTE SIGNED-BYTE))
		(EQ 8 (CADR ET)))
	   (VALUES (PKT-STRING OUTPUT-PACKET)
		   0
		   MAX-DATA-BYTES-PER-PKT))
	  ('ELSE
	   (VALUES OUTPUT-PACKET
		   FIRST-DATA-WORD-IN-PKT
		   (+ FIRST-DATA-WORD-IN-PKT (TRUNCATE MAX-DATA-BYTES-PER-PKT 2)))))))

(DEFMETHOD (BINARY-OUTPUT-STREAM-MIXIN :SEND-OUTPUT-BUFFER) (ARRAY END)
  (COND ((EQ ARRAY OUTPUT-PACKET)
	 (SEND-BINARY-PKT NIL NIL END))
	('ELSE
	 (SETF (PKT-NBYTES-ON-WRITE OUTPUT-PACKET) END)
	 (SEND-PKT CONNECTION OUTPUT-PACKET #o300)
	 (SETQ OUTPUT-PACKET NIL))))

(DECLARE-FLAVOR-INSTANCE-VARIABLES (BINARY-OUTPUT-STREAM-MIXIN)
(DEFUN SEND-BINARY-PKT (IGNORE IGNORE LENGTH)
  (SETF (PKT-NBYTES-on-write OUTPUT-PACKET) (* (- LENGTH FIRST-DATA-WORD-IN-PKT) 2))
  (SEND-PKT CONNECTION OUTPUT-PACKET #o300)
  (SETQ OUTPUT-PACKET NIL)))


(DECLARE-FLAVOR-INSTANCE-VARIABLES (BINARY-OUTPUT-STREAM-MIXIN)
(DEFUN SEND-BINARY-PKT-8 (IGNORE IGNORE LENGTH)
  (SETF (PKT-NBYTES-ON-WRITE OUTPUT-PACKET) LENGTH)
  (SEND-PKT CONNECTION OUTPUT-PACKET #o300)
  (SETQ OUTPUT-PACKET NIL)))


(DECLARE-FLAVOR-INSTANCE-VARIABLES (BINARY-OUTPUT-STREAM-MIXIN)
(DEFUN SEND-BINARY-PKT-ANY (IGNORE ARRAY LENGTH)
  (COND ((EQ ARRAY CHAOS:OUTPUT-PACKET)
	 (CHAOS:SEND-BINARY-PKT NIL NIL LENGTH))
	('ELSE
	 (CHAOS:SEND-BINARY-PKT-8 NIL NIL LENGTH)))))




;;; Now the instantiatable flavors
(DEFFLAVOR INPUT-CHARACTER-STREAM
	()
	(CHARACTER-INPUT-STREAM-MIXIN BASIC-INPUT-STREAM SI:BUFFERED-INPUT-CHARACTER-STREAM))

(DEFFLAVOR OUTPUT-CHARACTER-STREAM
	()
	(CHARACTER-OUTPUT-STREAM-MIXIN BASIC-OUTPUT-STREAM
	 SI:BUFFERED-OUTPUT-CHARACTER-STREAM))

(DEFFLAVOR CHARACTER-STREAM
	()
	(CHARACTER-INPUT-STREAM-MIXIN CHARACTER-OUTPUT-STREAM-MIXIN
	 BASIC-INPUT-STREAM BASIC-OUTPUT-STREAM SI:BUFFERED-CHARACTER-STREAM))

;;; This is to make the EVAL server work
(DEFMETHOD (CHARACTER-STREAM :BEEP) (&OPTIONAL IGNORE)
  )

(COMPILE-FLAVOR-METHODS INPUT-CHARACTER-STREAM OUTPUT-CHARACTER-STREAM CHARACTER-STREAM )

(DEFFLAVOR INPUT-BINARY-STREAM
	()
	(BINARY-INPUT-STREAM-MIXIN BASIC-INPUT-STREAM
	 SI:BUFFERED-INPUT-STREAM))

(DEFFLAVOR OUTPUT-BINARY-STREAM
	()
	(BINARY-OUTPUT-STREAM-MIXIN BASIC-OUTPUT-STREAM
	 SI:BUFFERED-OUTPUT-STREAM))

(DEFFLAVOR BINARY-STREAM
	()
	(BINARY-INPUT-STREAM-MIXIN BINARY-OUTPUT-STREAM-MIXIN
	 BASIC-INPUT-STREAM BASIC-OUTPUT-STREAM SI:BUFFERED-STREAM))

(COMPILE-FLAVOR-METHODS INPUT-BINARY-STREAM OUTPUT-BINARY-STREAM BINARY-STREAM)

(DEFFLAVOR ASCII-TRANSLATING-INPUT-CHARACTER-STREAM
	()
	(SI:ASCII-TRANSLATING-INPUT-STREAM-MIXIN
	 CHARACTER-INPUT-STREAM-MIXIN BASIC-INPUT-STREAM
	 SI:BUFFERED-TYI-INPUT-STREAM))

(DEFFLAVOR ASCII-TRANSLATING-OUTPUT-CHARACTER-STREAM
	()
	(SI:ASCII-TRANSLATING-OUTPUT-STREAM-MIXIN
	 CHARACTER-OUTPUT-STREAM-MIXIN BASIC-OUTPUT-STREAM
	 SI:BUFFERED-TYO-OUTPUT-STREAM))

(DEFFLAVOR ASCII-TRANSLATING-CHARACTER-STREAM
	()
	(SI:ASCII-TRANSLATING-INPUT-STREAM-MIXIN SI:ASCII-TRANSLATING-OUTPUT-STREAM-MIXIN
	 CHARACTER-INPUT-STREAM-MIXIN CHARACTER-OUTPUT-STREAM-MIXIN
	 BASIC-INPUT-STREAM BASIC-OUTPUT-STREAM SI:BUFFERED-TYI-TYO-STREAM))

(COMPILE-FLAVOR-METHODS ASCII-TRANSLATING-INPUT-CHARACTER-STREAM
			ASCII-TRANSLATING-OUTPUT-CHARACTER-STREAM
			ASCII-TRANSLATING-CHARACTER-STREAM)

(DEFUN OPEN-STREAM (HOST CONTACT-NAME &KEY &OPTIONAL (WINDOW-SIZE DEFAULT-WINDOW-SIZE)
						     (TIMEOUT (* 10. 60.))
						     (DIRECTION :BIDIRECTIONAL)
						     (ERROR T)
						     (CHARACTERS T)
						     (ASCII-TRANSLATION NIL)
				      &AUX CONN)
  "Open a chaosnet connection and return a stream that does i//o to it.
HOST is the host to connect to; CONTACT-NAME is the contact name at that host.
The keyword arguments are:
:WINDOW-SIZE - number of packets to allow in transit to this host over the connection.
:TIMEOUT - how long to wait before assuming the host is down.
:ASCII-TRANSLATION - if non-NIL, assume the data on the connection is in ASCII
 and translate to and from the Lisp machine character set as appropriate.
:DIRECTION, :CHARACTERS, :ERROR - as in OPEN.  :DIRECTION defaults to :BIDIRECTIONAL."
  (CONDITION-CASE-IF (NOT ERROR) (ERROR-OBJECT)
        (SETQ CONN (IF HOST
		       (CONNECT HOST CONTACT-NAME WINDOW-SIZE TIMEOUT)
		       (LISTEN CONTACT-NAME WINDOW-SIZE)))
    (SYS:REMOTE-NETWORK-ERROR ERROR-OBJECT)
    (:NO-ERROR 
      (MAKE-STREAM CONN :DIRECTION DIRECTION
		   	:CHARACTERS CHARACTERS
			:ASCII-TRANSLATION ASCII-TRANSLATION))))

(DEFUN MAKE-STREAM (CONNECTION &KEY &OPTIONAL (DIRECTION :BIDIRECTIONAL)
					      (CHARACTERS T)
					      (ASCII-TRANSLATION NIL))
  "Return a stream that does I//O to an already established chaos connection.
:ASCII-TRANSLATION - if non-NIL, assume the data on the connection is in ASCII
 and translate to and from the Lisp machine character set as appropriate.
:DIRECTION, :CHARACTERS - as in OPEN.  :DIRECTION defaults to :BIDIRECTIONAL."
  (MAKE-INSTANCE (CASE DIRECTION
		   (:INPUT
		    (COND (ASCII-TRANSLATION 'ASCII-TRANSLATING-INPUT-CHARACTER-STREAM)
			  (CHARACTERS 'INPUT-CHARACTER-STREAM)
			  (T 'INPUT-BINARY-STREAM)))
		   (:OUTPUT
		    (COND (ASCII-TRANSLATION 'ASCII-TRANSLATING-OUTPUT-CHARACTER-STREAM)
			  (CHARACTERS 'OUTPUT-CHARACTER-STREAM)
			  (T 'OUTPUT-BINARY-STREAM)))
		   (:BIDIRECTIONAL
		    (COND (ASCII-TRANSLATION 'ASCII-TRANSLATING-CHARACTER-STREAM)
			  (CHARACTERS 'CHARACTER-STREAM)
			  (T 'BINARY-STREAM))))
		 :CONNECTION CONNECTION))

(DEFF STREAM 'MAKE-STREAM)
(MAKE-OBSOLETE STREAM "use MAKE-STREAM")

;;;; Useful information gatherers

;;; HOST-DATA: returns information about a specified host.  Currently,
;;; returns name of machine as primary value and host number as second value
(DEFUN HOST-DATA (&OPTIONAL (HOST MY-ADDRESS) &AUX HOST-ADDRESS HOST-NAME TEM)
  "Return the long name and chaos address of a host."
  (DECLARE (RETURN-LIST HOST-NAME HOST-ADDRESS))
  (OR (SETQ HOST-ADDRESS (ADDRESS-PARSE HOST))
      (FERROR NIL "~S is an illegal host specification" HOST))
  (IF (AND (SETQ HOST-NAME (SI:GET-HOST-FROM-ADDRESS HOST-ADDRESS :CHAOS))
	   (SETQ HOST-NAME (SEND HOST-NAME :NAME)))
      (AND (SETQ TEM (ASSOC-EQUALP HOST-NAME SI:MACHINE-LOCATION-ALIST))
	   (SETQ HOST-NAME (SECOND TEM)))
    (IF (SETQ TEM (GET-HOST-STATUS-PACKET HOST-ADDRESS))
	(LET ((STRING (PKT-STRING TEM)))
	  (SETQ HOST-NAME (SUBSTRING STRING 0
				     (MIN (PKT-NBYTES TEM) 32.
					  (OR (STRING-SEARCH-CHAR 0 STRING) 32.)))))
      (SETQ HOST-NAME "Unknown")))
  (VALUES HOST-NAME HOST-ADDRESS))

;;; If given a number, this always returns something that ADDRESS-PARSE would make into that
;;; number.
(DEFUN HOST-SHORT-NAME (HOST &AUX HOST1)
  "Return a brief name for the specified host."
  (COND ((NOT (NUMBERP HOST))
	 (SEND (SI:PARSE-HOST HOST) :SHORT-NAME))
	((SETQ HOST1 (SI:GET-HOST-FROM-ADDRESS HOST :CHAOS))
	 (SEND HOST1 :SHORT-NAME))
	(T (FORMAT NIL "~O" HOST))))

;(FSET 'HOST-SYSTEM-TYPE 'SI:HOST-SYSTEM-TYPE)

(DEFUN GET-HOST-STATUS-PACKET (HOST &AUX CONNECTION PKT ADR)
  "Returns a STATUS packet from the specified host or NIL if couldn't get the packet"
  (ASSURE-ENABLED)
  (SETQ ADR (OR (ADDRESS-PARSE HOST)
		(FERROR NIL "Not a known Chaos address: ~S" HOST)))
  (SETQ CONNECTION (OPEN-CONNECTION ADR "STATUS" 1))
  (DO () ((NULL CONNECTION))
      (PROCESS-SLEEP 10.)			;Take a few chaos net interrupts
      (CASE (STATE CONNECTION)
	(RFC-SENT-STATE
	  (COND (( (TIME-DIFFERENCE (zl:TIME) (TIME-LAST-RECEIVED CONNECTION))
		    300.)		;5-second timeout
		 (REMOVE-CONN CONNECTION)
		 (RETURN NIL))))
	(ANSWERED-STATE				;This is what we want
	  (SETQ PKT (GET-NEXT-PKT CONNECTION))
	  (CLOSE-CONN CONNECTION)
	  (RETURN PKT))
	(CLS-RECEIVED-STATE (CLOSE-CONN CONNECTION) (RETURN NIL))
	(OPEN-STATE
	  (CLOSE-CONN CONNECTION "I expected an ANS, not an OPN.")
	  (RETURN NIL))
	(LOS-RECEIVED-STATE
	  (CLOSE-CONN CONNECTION)
	  (RETURN NIL))
	(OTHERWISE
	  (CLOSE-CONN CONNECTION)
	  (RETURN NIL)))))

(DEFUN ON-CHAOSNET-P (HOST)
  "Return T if HOST has a chaosnet address."
  (SEND (SI:PARSE-HOST HOST) :NETWORK-TYPEP :CHAOS))

(DEFINE-SITE-VARIABLE USUAL-LM-NAME-PREFIX :USUAL-LM-NAME-PREFIX)

(DEFUN GET-SHORT-LM-NAME (LM &AUX (PL (STRING-LENGTH USUAL-LM-NAME-PREFIX)))
  (IF USUAL-LM-NAME-PREFIX
      (DO ((L (SEND LM :HOST-NAMES) (CDR L)))
	  ((NULL L) (SEND LM :SHORT-NAME))
	(AND (STRING-EQUAL USUAL-LM-NAME-PREFIX (CAR L) :END1 PL :END2 PL)
	     (RETURN (CAR L))))
    (SEND LM :SHORT-NAME)))


;;; This isn't DEFINE-SITE-HOST-LIST because this file is loaded too early,
;;; as is the SITE file itself.
(DEFINE-SITE-VARIABLE TIME-SERVER-HOSTS :CHAOS-TIME-SERVER-HOSTS)

(SETQ TIME:*NETWORK-TIME-FUNCTION* 'HOST-TIME)

;;; Returns universal time from host over the net, as a 32-bit number
;;; or if it can't get the time, returns a string which is the reason why not.
;;; This applies each host for the time at a rate of one per second.
;;; As soon as one of them replies, it returns the time that host gave.
;;; 2nd value is host from which time was gotten.
(DEFUN HOST-TIME (&OPTIONAL (HOSTS TIME-SERVER-HOSTS) &AUX CONNECTIONS LAST-HOST)
  (ASSURE-ENABLED)
  (AND (NLISTP HOSTS)
       (NOT (NULL HOSTS))
       (SETQ HOSTS (LIST HOSTS)))
  (SETQ LAST-HOST (CAR (LAST HOSTS)))
  (UNWIND-PROTECT
    (LOOP NAMED HOST-TIME
	  FOR HOST IN HOSTS
	  AS ADDRESS = (ADDRESS-PARSE HOST)
	  WHEN (AND ADDRESS ( ADDRESS MY-ADDRESS))
	    DO (PUSH (OPEN-CONNECTION (ADDRESS-PARSE HOST) "TIME" 5) CONNECTIONS)
	       (COND ((PROCESS-WAIT-WITH-TIMEOUT "Ask the Time"
			(IF (EQ HOST LAST-HOST) 300. 60.)
			#'(LAMBDA (CONNECTIONS)
			    (LOOP FOR CONNECTION IN CONNECTIONS
				  WHEN (EQ (STATE CONNECTION) 'ANSWERED-STATE)
				  RETURN T))
			CONNECTIONS)
		      (LOOP WITH PKT
			    FOR CONNECTION IN CONNECTIONS
			    WHEN (EQ (STATE CONNECTION) 'ANSWERED-STATE)
			    DO (RETURN-FROM HOST-TIME
				 (VALUES
				   (PROG2 (SETQ PKT (GET-NEXT-PKT CONNECTION))
					  (DECODE-CANONICAL-TIME-PACKET PKT)
					  (RETURN-PKT PKT))
				   (SI:GET-HOST-FROM-ADDRESS
				     (FOREIGN-ADDRESS CONNECTION) :CHAOS))))))
	  ELSE UNLESS ADDRESS DO
	  (FORMAT *ERROR-OUTPUT* "~&Invalid host given to HOST-TIME by ~S" HOST)
	  FINALLY (RETURN "No hosts responded."))
    (MAPC 'CLOSE-CONN CONNECTIONS)))


;; Copied from LAD: RELEASE-3.NETWORK.CHAOS; CHUSE.LISP#28 on 2-Oct-86 17:22:45
(network:define-network-function (network:get-host-time :chaos) (host)
  (multiple-value-bind (time ahost)
      (host-time (list host))
    (if ahost time)))
  

(DEFUN CHAOS-UNKNOWN-HOST-FUNCTION (NAME)
  (DOLIST (HOST (SI:GET-SITE-OPTION :CHAOS-HOST-TABLE-SERVER-HOSTS))
    (AND (SI:PARSE-HOST HOST T ())		; prevent infinite recursion
	 (WITH-OPEN-STREAM (STREAM (OPEN-STREAM HOST "HOSTAB" :ERROR NIL))
	   (SETQ NAME (STRING NAME))
	   (UNLESS (ERRORP STREAM)
	     (SEND STREAM :LINE-OUT NAME)
	     (SEND STREAM :FORCE-OUTPUT)
	     (DO ((LIST NIL) (RESULT) (DONE)
		  (LINE) (EOF)
		  (LEN) (SP) (PROP))
		 (DONE RESULT)
	       (MULTIPLE-VALUE (LINE EOF) (SEND STREAM :LINE-IN))
	       (cond (EOF
		      (SETQ RESULT (WHEN LIST
				     (PUTPROP LIST (STABLE-SORT (GET LIST :HOST-NAMES)
								#'(LAMBDA (X Y)
								    (< (STRING-LENGTH X)
								       (STRING-LENGTH Y))))
					      :HOST-NAMES)
				     (APPLY #'SI:DEFINE-HOST LIST))
			    DONE T))
		     (t
		      (SETQ LEN (STRING-LENGTH LINE)
			    SP (STRING-SEARCH-CHAR #\SP LINE 0 LEN))
		      (SETQ PROP (INTERN (SUBSTRING LINE 0 SP) ""))
		      (INCF SP)
		      (CASE PROP
			(:ERROR (SETQ DONE T))
			(:NAME
			 (LET ((NAME (SUBSTRING LINE SP LEN)))
			   (OR LIST (SETQ LIST (NCONS NAME)))
			   (PUSH NAME (GET LIST :HOST-NAMES))))
			((:SYSTEM-TYPE :MACHINE-TYPE)
			 (PUTPROP LIST (INTERN (SUBSTRING LINE SP LEN) "") PROP))
			(OTHERWISE
			 (LET ((FUNCTION (GET PROP 'NET:ADDRESS-PARSER)))
			   (OR FUNCTION (SETQ FUNCTION (GET :CHAOS 'NET:ADDRESS-PARSER)))
			   (PUSH (FUNCALL FUNCTION PROP LINE SP LEN)
				 (GET LIST PROP))))))))
	     (RETURN T))))))

(SETQ SI:UNKNOWN-HOST-FUNCTION 'CHAOS-UNKNOWN-HOST-FUNCTION)

(DEFUN NEW-HOST-VALIDATION-FUNCTION (HOST SYSTEM-TYPE ADDRESS)
  (COND ((NOT (STRINGP HOST))
	 (AND ADDRESS
	      (NOT (MEMQ ADDRESS (SEND HOST :CHAOS-ADDRESSES)))
	      (FERROR NIL "~O is not a valid chaosnet address for ~A" ADDRESS HOST))
	 HOST)
	(T
	 (LET ((STATUS-PKT (GET-HOST-STATUS-PACKET ADDRESS)))
	   (OR STATUS-PKT (FERROR NIL "Cannot connect to ~A at ~O" HOST ADDRESS))
	   (LET ((STRING (PKT-STRING STATUS-PKT)))
	     (OR (FQUERY NIL "Host is ~A, ok? "
			 (SUBSTRING STRING 0 (MIN (STRING-LENGTH STRING) 32.
						  (OR (STRING-SEARCH-SET '(#o200 0) STRING)
						      32.))))
		 (FERROR NIL "Incorrect host specified"))))
	 (SI:DEFINE-HOST HOST :HOST-NAMES `(,HOST)
			      :SYSTEM-TYPE SYSTEM-TYPE
			      :CHAOS `(,ADDRESS))
	 (SETQ HOST (SI:PARSE-HOST HOST))
	 (AND (EQ CHAOS:MY-ADDRESS ADDRESS) (SETQ SI:LOCAL-HOST HOST))
	 HOST)))

(setf (get 'si:new-host-validation-function :chaos) 'chaos:new-host-validation-function)
