(DEFINE-FILE-INFO READTABLE "XCL" PACKAGE "RPC2")
(il:filecreated " 1-Aug-88 11:51:33" il:{erinyes}<lispusers>medley>rpcrpc.\;2 38993  

      il:|changes| il:|to:|  (il:functions define-remote-program)

      il:|previous| il:|date:| "28-Apr-88 17:26:39" il:{erinyes}<lispusers>medley>rpcrpc.\;1)


; Copyright (c) 1987, 1988 by Stanford University and Xerox Corporation.  All rights reserved.

(il:prettycomprint il:rpcrpccoms)

(il:rpaqq il:rpcrpccoms
          ((il:props (il:rpcrpc il:makefile-environment il:filetype))
           (il:variables *debug* *rpc-call* *rpc-version* *rpc-programs* *msec-until-timeout* 
                  *msec-between-tries* *internal-time-units-per-msec* *rpc-reply-stats* 
                  *rpc-accept-stats* *rpc-reject-stats* *rpc-authentication-stats* *rpc-ok-to-cache*
                  *rpc-socket-cache* *xid-count* *rpc-def-in-progress* *rpc-well-known-sockets* 
                  *rpc-protocols* *rpcstream* *rpc-pgname* *rpc-pcname*)
           

(il:* il:|;;;| "Define RPC Program")

           (il:functions define-remote-program define-remote-prog cons-up-rpc-procs 
                  clear-any-name-conflicts def-rpc-types def-rpc-inherits def-rpc-procedures 
                  def-rpc-procedure def-rpc-constants undefine-remote-program xdr-gencode-makefcn 
                  xdr-gencode-inline)
           

(il:* il:|;;;| "Remote Procedure Call")

           (il:functions remote-procedure-call setup-rpc perform-rpc rpc-resolve-host 
                  rpc-resolve-prog rpc-resolve-proc rpc-find-socket encode-rpc-args 
                  actually-do-the-rpc exchange-udp-packets exchange-tcp-packets parse-rpc-reply 
                  create-xid)
           

(il:* il:|;;;| "RPC Utility Functions")

           (il:functions get-reply-stat get-accept-stat get-reject-stat get-authentication-stat 
                  get-protocol-number find-cached-socket)
           

(il:* il:|;;;| "RPC Error Messages")

           (il:functions rpc-error-prm-mismatch rpc-error-prm-unavailable rpc-error-prc-unavailable 
                  rpc-error-garbage-args rpc-error-mismatch rpc-error-authentication)
           

(il:* il:|;;;| "Authentication")

           (il:variables *authentication-typedef* *null-authentication*)
           (il:functions create-unix-authentication encode-authentication decode-authentication)))

(il:putprops il:rpcrpc il:makefile-environment (:readtable "XCL" :package "RPC2"))

(il:putprops il:rpcrpc il:filetype :compile-file)

(defglobalparameter *debug* nil
   "T for printout, NUMBER for even more.")

(defconstant *rpc-call* 0
   "Constant 0 in packet means RPC call, 1 means reply")

(defconstant *rpc-version* 2
   "This code will only work for SUN RPC Version 2")

(defglobalvar *rpc-programs* nil
   "
A list of RPC-PROGRAM structs.

This list is consulted by various routines to find infomation about known
remote programs.

It is assumed that a given NAME field uniquely identifies a (NUMBER, VERSION, PROTOCOL).
On the other hand, there may be several NAMEs (and hence, several RPC-STRUCTs) for
a given (NUMBER, VERSION, PROTOCOL).

")

(defparameter *msec-until-timeout* 10000
   "Total time in msec before giving up on UDP exchange with remote host")

(defparameter *msec-between-tries* 1000
   "Time in msec between UDP retries")

(defconstant *internal-time-units-per-msec* (/ internal-time-units-per-second 1000)
                                                "This gets used in EXCHANGE-UDP-PACKETS.")

(defconstant *rpc-reply-stats* '((0 . accepted)
                                     (1 . rejected))
                                   "
Assoc list for internal use by PARSE-RPC-REPLY.
")

(defconstant *rpc-accept-stats* '((0 . success)
                                      (1 . program-unavailable)
                                      (2 . program-mismatch)
                                      (3 . procedure-unavailable)
                                      (4 . garbage-arguments))
                                    "
Assoc list for internal use by PARSE-RPC-REPLY.
")

(defconstant *rpc-reject-stats* '((0 . rpc-mismatch)
                                      (1 . authentication-error))
                                    "
Assoc list for internal use by PARSE-RPC-REPLY.
")

(defconstant *rpc-authentication-stats* '((1 . bad-credential)
                                              (2 . rejected-credential)
                                              (3 . bad-verifier)
                                              (4 . rejected-verifier)
                                              (5 too-weak))
                                            "NIL")

(defparameter *rpc-ok-to-cache* t
   "
If NIL, does not attempt to cache socket numbers for non-well-known sockets
")

(defvar *rpc-socket-cache* nil
   "
A list of (<iphost-address> <remote-program-name> <remote-program-version>
           <protocol> <ipsocket-number>) quintuples.")

(defvar *xid-count* 0
   "Contains the  XID stamp of the next remote procedure call")

(defvar *rpc-def-in-progress* nil
   "Used for debugging only")

(defglobalvar *rpc-well-known-sockets*
   `((* 100000 2 udp 111)
     (* 100000 2 tcp 111)
     (* 100003 2 udp 2049))
   "
List of well-known RPC programs and their sockets.
Each element is a list:
  (host-address prog-number prog-version protocol socket-number)

Host-address may be *, in which case it matches any host address.
Protocol should be either rpc2::UDP or rpc2::TCP.
")

(defvar *rpc-protocols* '((tcp . 6)
                              (udp . 17)))

(defvar *rpcstream* nil
   "This global is not used exceptin debugging.
 It holds a copy of the RPC-STREAM even after the RPC-CALL returns.")

(defglobalvar *rpc-pgname* nil
   "Name of RPC Program. Used only for *debug* printout.")

(defglobalvar *rpc-pcname* nil
   "Name of RPC Procedure. Used only for *debug* printout.")



(il:* il:|;;;| "Define RPC Program")


(defmacro define-remote-program (name number version protocol &key constants types inherits 
                                          procedures)
   "
This macro expands into code to add a new RPC-PROGRAM struct to
*RPC-PROGRAMS*. The generated code checks first to see that there
are no name conflicts with existing remote programs and then adds the new
structure to *RPC-PROGRAMS*.
"
   (let ((ename (eval name))
         (enumber (eval number))
         (eversion (eval version))
         (eprotocol (or (eval protocol)
                        'udp))
         (econstants (eval constants))
         (etypes (eval types))
         (einherits (eval inherits))
         (eprocedures (eval procedures)))
        (check-type ename symbol)
        (check-type enumber number)
        (check-type eversion number)
        (cond
           ((member eprotocol '(udp tcp))
            (if (and *use-os-networking* (eq eprotocol 'tcp))
                (error "~a is an unsupported protocol." eprotocol)
                t))
           ((equal "UDP" (string eprotocol))
            (setq eprotocol 'udp))
           ((equal "TCP" (string eprotocol))
            (if *use-os-networking*
                (error "~a is an unsupported protocol." eprotocol)
                (setq eprotocol 'tcp)))
           ((error "~a is unknown prototype." eprotocol)))
        (let ((rprog (define-remote-prog ename enumber eversion eprotocol econstants etypes einherits
                            eprocedures)))
             `(let ((dummy (format-t "Defining remote program ~a, version ~a~%" ',ename
                                  ',eversion))
                    (newprog (make-rpc-program :number ,enumber :version ,eversion :name
                                    ',ename :protocol ',eprotocol :types ',(rpc-program-types rprog)
                                    :constants
                                    ',(rpc-program-constants rprog)
                                    :inherits
                                    ',(rpc-program-inherits rprog)
                                    :procedures
                                    ,(cons-up-rpc-procs (rpc-program-procedures rprog)))))
                   (if (clear-any-name-conflicts ',ename ',enumber ',eversion ',eprotocol)
                       (progn (undefine-remote-program ',ename ',enumber ',eversion)
                              (push newprog *rpc-programs*)
                              ',ename)
                       (progn (format-t "Old RPC program not overwritten.~%")
                              nil))))))

(defun define-remote-prog (name number version protocol constants types inherits procedures)

   (il:* il:|;;| 
 "This guy does the work, so that DEFINE-REMOTE-PROGRAM can cons up the macro easily.")

   (il:* il:|;;| "An RPC-PROGRAM struct RPROG is passed back to DEFINE-REMOTE-PROGRAM. Its innards are then used by DEFINE-REMOTE-PROGRAM to build up the big cons that will cons up the proper RPC-PROGRAM later.")

   (let (rprog)
        (format-t "Building XDR routines for remote program ~a, version ~a~%" name version)
        (setq rprog (make-rpc-program :number number :version version :name name :protocol protocol)
              *rpc-def-in-progress* rprog)
        (setf (rpc-program-types rprog)
              (def-rpc-types rprog types))
        (setf (rpc-program-inherits rprog)
              (def-rpc-inherits rprog inherits))
        (setf (rpc-program-constants rprog)
              (def-rpc-constants rprog constants))
        (setf (rpc-program-procedures rprog)
              (def-rpc-procedures rprog procedures))
        rprog))

(defun cons-up-rpc-procs (procs)
   "
Given a list of RPC-PROCEDURE structs, conses up code to produce that set of
RPC-PROCEDURE structs.
"
   `(list
     ,@(map
        'list
        #'(lambda
           (proc)
           `(make-rpc-procedure
             :name
             ',(rpc-procedure-name proc)
             :procnum
             ',(rpc-procedure-procnum proc)
             :argtypes
             ,(if (rpc-procedure-argtypes proc)
                  `(list ,@(map 'list #'(lambda (fcn)
                                               (list 'function fcn))
                                (rpc-procedure-argtypes proc))))
             :resulttypes
             ,(if (rpc-procedure-resulttypes proc)
                  `(list ,@(map 'list #'(lambda (fcn)
                                               (list 'function fcn))
                                (rpc-procedure-resulttypes proc))))))
        procs)))

(defun clear-any-name-conflicts (name number version protocol)
   "
Determines whether a proposed (NAME, NUMBER, VERSION, PROTOCOL) would violate
the assumption that a NAME uniquely specifies the other three components.

If there exists a violation, the user is given a chance to remove the old program.

Returns T if no violation of assumption (or violation is resolved by removing old program),
Returns NIL if there is an unresolved violation.

"
   (let (oldrpc)
        (cond
           ((and (setq oldrpc (find-rpc-program :name name))
                 (or (/= number (rpc-program-number oldrpc))
                     (/= version (rpc-program-version oldrpc))
                     (not (eql protocol (rpc-program-protocol oldrpc)))))
            (format *query-io* "Remote program name conflict with existing program:~%   Name ~a, Protocol ~A, Number ~a, Version ~a~%"
                   name (rpc-program-protocol oldrpc)
                   (rpc-program-number oldrpc)
                   (rpc-program-version oldrpc))
            (and (yes-or-no-p "Do you want to remove the old program? ")
                 (undefine-remote-program (rpc-program-name oldrpc)
                        (rpc-program-number oldrpc)
                        (rpc-program-version oldrpc)
                        (rpc-program-protocol oldrpc))))
           (t t))))

(defun def-rpc-types (context typedefs)
   "
Essentially a no-op, as typedefs are copied directly from the DEFINE-REMOTE-PROGRAM
into the RPC-PROGRAM struct. Just prints out the name of each type as it is encountered.
"
   (if typedefs (format-t "    Types~%"))
   (dolist (i typedefs)
       (format-t "        ~A~%" (first i)))
   typedefs)

(defun def-rpc-inherits (context proglist)
   "
Checks remote program inherited by this one to make sure that it exists.
Issues a warning if it cannot find the program to be inherited.
"
   (if proglist (format-t "    Inherits~%"))
   (dolist (prg proglist proglist)
       (format-t "        ~A~%" prg)
       (if (not (and (symbolp prg)
                     (find-rpc-program :name prg)))
           (warn "Trying to inherit from remote program ~a, but ~a not found.~%" prg prg))))

(defun def-rpc-procedures (context procs)
   "Returns a list of RPC-PROCEDURE structs returned by DEF-RPC-PROCEDURE."
   (check-type procs list "A list of RPC procedure declarations")
   (if procs (format-t "    Procedures~%"))
   (map 'list #'(lambda (proc)
                       (def-rpc-procedure context proc))
        procs))

(defun def-rpc-procedure (context proc)
   "
For a procedure specified to DEFINE-REMOTE-PROGRAM's :PROCEDURES argument,
creates and returns an RPC-PROCEDURE struct. 

XDR procedure code is generated via the call to XDR-GENCODE-MAKEFCN.
"
   (check-type (first proc)
          (and symbol (not null))
          "a non-null symbol naming the RPC procedure.")
   (check-type (second proc)
          (integer 0 *)
          "a non-negative integer RPC procedure number")
   (check-type (third proc)
          list)
   (check-type (fourth proc)
          list)
   (let ((rp (make-rpc-procedure)))
        (setf (rpc-procedure-name rp)
              (first proc))
        (setf (rpc-procedure-procnum rp)
              (second proc))
        (setf (rpc-procedure-argtypes rp)
              (map 'list #'(lambda (td)
                                  (xdr-gencode-makefcn context td 'write))
                   (third proc)))
        (setf (rpc-procedure-resulttypes rp)
              (map 'list #'(lambda (td)
                                  (xdr-gencode-makefcn context td 'read))
                   (fourth proc)))
        (format-t "        ~A~%" (rpc-procedure-name rp))
        rp))

(defun def-rpc-constants (context pairs)
   "
Checks that constants specified to DEFINE-REMOTE-PROGRAM are syntactically
reasonable.
"
   (if pairs (format-t "    Constants~%"))
   (dolist (pair pairs)
       (check-type (first pair)
              (and (not null)
                   symbol))
       (check-type (second pair)
              (and (not null)
                   number))
       (format-t "        ~A~%" (first pair)))
   pairs)

(defun undefine-remote-program (name number version &optional (protocol 'udp))
   "
If finds NAME-NUMBER-VERSION-PROTOCOL match in *RPC-PROGRAMS*, deletes.
If finds NUMBER-VERSION match with NAME mismatch, asks first.
If deletes something, returns NAME of DELETED program, otherwise NIL."
                                                             (il:* il:\; "")
   (let ((rpc (find-rpc-program :number number :version version :name name :protocol protocol)))
        (if rpc
            (if (or (eql name (rpc-program-name rpc))
                    (yes-or-no-p "Do you really want to remove/overwrite RPC program ~a?"
                           (rpc-program-name rpc)))
                (progn (setq *rpc-programs* (delete rpc *rpc-programs*))
                       (rpc-program-name rpc))))))

(defun xdr-gencode-makefcn (context typedef oper &optional compilesw)
   "
Calls XDR-CODEGEN to generate an XDR function for TYPEDEF.
If COMPILESW, then compiles the function. COMPILESW is not
used anymore since DEFINE-REMOTE-PROGRAM became a macro.
"
   (let ((code (xdr-codegen context typedef oper)))
        (if compilesw
            (compile nil code)
            code)))

(defmacro xdr-gencode-inline (context typedef oper &rest vars)
   "NIL"

   (il:* il:|;;| " Note that using a NIL context is valid here. It just means that no typedefs from other Remote Program Definitions are available.")

   "NIL"
   `(funcall #',(xdr-codegen context (eval typedef)
                       (eval oper))
           ,.vars))



(il:* il:|;;;| "Remote Procedure Call")


(defun remote-procedure-call (destination program procid arglist &key (protocol 'udp)
                                        remotesocket version credentials dynamic-prognum (
                                                                                      dynamic-version
                                                                                          1)
                                        (errorflg t)
                                        leave-stream-open
                                        (msec-until-timeout *msec-until-timeout*)
                                        (msec-between-tries *msec-between-tries*)
                                        results)
   "
This is the high-level way of making a remote procedure call (PERFORM-RPC is the low-level
way).

REMOTE-PROCEDURE-CALL resolves all the arguments, creates a new RPC-STREAM, makes the call, optionally closes the RPC-STREAM, and returns the results of the call.

The resolution of arguments is designed such that all arguments may be either
unresolved (e.g., a remote host name), or already resolved (e.g., an IP address).
"
   (when (numberp *debug*)
       (format-t "Remote-Procedure-Call...~%")
       (format-t "  Destination=~A~%" destination)
       (format-t "  Program=~A~%" program)
       (format-t "  ProcID=~A~%" procid)
       (format-t "  ArgList=~A~%" arglist))
   (multiple-value-bind (destaddr destsocket rprog rproc rpcstream)
          (setup-rpc destination program procid remotesocket version dynamic-prognum dynamic-version
                 protocol)
          (setq rpcstream (open-rpcstream (rpc-program-protocol rprog)
                                 destaddr destsocket))
          (setq results (perform-rpc destaddr destsocket rprog rproc rpcstream arglist credentials 
                               :errorflg errorflg :msec-until-timeout msec-until-timeout 
                               :msec-between-tries msec-between-tries))
          (unless leave-stream-open (close-rpcstream rpcstream))
          results))

(defun setup-rpc (destination program procid &optional destsocket version dynamic-prognum 
                            dynamic-version (protocol 'udp))
   "
Resolves arguments to REMOTE-PROCEDURE-CALL. Takes arguments in more or less
any reasonable form and returns multiple values (destination-address, socket-number,
RPC-PROGRAM struct, RPC-PROCEDURE struct).
 
See individual RPC-RESOLVE-* programs for details on what inputs are acceptable.
"
   (let* ((destaddr (rpc-resolve-host destination))
          (rprog (rpc-resolve-prog program version protocol))
          (dummy                                             (il:* il:\; " This code may set RPROG")
                 (when dynamic-prognum
                     (setf rprog (copy-rpc-program rprog))
                     (setf (rpc-program-number rprog)
                           dynamic-prognum)
                     (setf (rpc-program-version rprog)
                           dynamic-version)))
          (rproc (rpc-resolve-proc rprog procid))
          (socket (or destsocket (rpc-find-socket destaddr rprog (rpc-program-protocol rprog)))))
         (values destaddr socket rprog rproc)))

(defun perform-rpc (destaddr destsocket rprog rproc stream arglist credentials &key (errorflg
                                                                                         t)
                              (msec-until-timeout *msec-until-timeout*)
                              (msec-between-tries *msec-between-tries*))
   "
The low-level remote procedure call function.
"
   (let (retvals)
        (reinitialize-rpcstream stream destaddr destsocket)
        (progn 
               (il:* il:|;;| " These are for debugging printouts only")

               (setq *rpcstream* stream)
               (setq *rpc-pgname* (rpc-program-name rprog))
               (setq *rpc-pcname* (rpc-procedure-name rproc)))
        (xdr-unsigned stream (create-xid))
        (xdr-unsigned stream *rpc-call*)
        (xdr-unsigned stream *rpc-version*)
        (xdr-unsigned stream (rpc-program-number rprog))
        (xdr-unsigned stream (rpc-program-version rprog))
        (xdr-unsigned stream (rpc-procedure-procnum rproc))
        (encode-authentication stream credentials)
        (encode-authentication stream *null-authentication*)
        (encode-rpc-args stream arglist rproc)
        (setq retvals (catch 'goforit
                          (actually-do-the-rpc stream msec-until-timeout msec-between-tries errorflg)
                          (parse-rpc-reply stream (rpc-procedure-resulttypes rproc)
                                 errorflg)))
        (when (and (numberp *debug*)
                   (> *debug* 0))
              (format-t " Values Returned by RPC: ~A~%" retvals))
        retvals))

(defun rpc-resolve-host (destination)
   "
Takes an IPADDRESS, symbol, or string and tries to find an IPADDRESS for a remote
host. Signals an error if it cannot resolve the host.
"
   (or (typecase destination
           (number destination)
           (symbol (if *use-os-networking*
                       (os-resolve-host (string destination))
                       (il:iphostaddress destination)))
           (string (if *use-os-networking*
                       (os-resolve-host destination)
                       (il:iphostaddress (intern destination))))
           (t (il:\\illegal.arg destination)))
       (error "Could not find an IP net address for DESTINATION ~A" destination)))

(defun rpc-resolve-prog (program &optional version protocol)
   "
Takes an RPC-PROGRAM, a number, a symbol, or a string along with an optional VERSION and PROTOCOL and tries to find the matching RPC-PROGRAM.
Signals an error if it cannot find the intended program.
"
   (cond
      ((typep program 'rpc-program)
       program)
      ((and (typep program 'symbol)
            (find-rpc-program :name program :version version :protocol protocol)))
      ((and (numberp program)
            (find-rpc-program :number program :version version :protocol protocol)))
      ((and (stringp program)
            (find-rpc-program :name (intern program)
                   :version version :protocol protocol)))
      (t (error "Could not find definition for program ~a~a~a.~%" program
                (if version
                    (format nil ", version ~a" version)
                    "")
                (if protocol
                    (format nil ", protocol ~a" protocol)
                    "")))))

(defun rpc-resolve-proc (rprog procid)
   "
Given an RPC-PROGRAM struct RPROG, tries to find and return an RPC-PROCEDURE in
RPROG specified by a number, string,  symbol, or RPC-PROCEDURE.

Signals an error if it cannot find the intended rpc-procedure
"
   (cond
      ((typep procid 'rpc-procedure)
       procid)
      ((and (or (numberp procid)
                (symbolp procid))
            (find-rpc-procedure (rpc-program-procedures rprog)
                   procid)))
      ((and (stringp procid)
            (find-rpc-procedure (rpc-program-procedures rprog)
                   (intern procid))))
      (t (error "Could not find definition for program ~a, procedure ~a~%" (rpc-program-name rprog)
                procid))))

(defun rpc-find-socket (destaddr prg protocol)
   "
Tries to find and return a remote socket number.

(1) Looks in *RPC-WELL-KNOWN-SOCKETS*,
(2) Looks in *RPC-SOCKET-CACHE*, but only if *RPC-OK-TO-CACHE*,
(3) Requests socket number via remote procedure call to Portmapper
on remote machine. If found and *RPC-OK-TO-CACHE*, caches the new
socket number on *RPC-SOCKET-CACHE*.
(4) If all the above have failed, signals an error.
"
   (let ((prognum (rpc-program-number prg))
         (progvers (rpc-program-version prg))
         skt)
        (cond
           ((setq skt (find-cached-socket '* prognum progvers protocol *rpc-well-known-sockets*))
            (if *debug*
                (format-t "Cached well-known socket ~a found for program ~a~%" skt (rpc-program-name
                                                                                    prg)))
            skt)
           ((and *rpc-ok-to-cache* (setq skt (find-cached-socket destaddr prognum progvers protocol 
                                                    *rpc-socket-cache*)))
            (if *debug*
                (format-t "Cached non-well-known socket ~a found for program ~a~%" skt (
                                                                                     rpc-program-name
                                                                                        prg)))
            skt)
           ((progn (if *debug*
                       (format-t "Looking up socket for program ~a on ~a.~%" (rpc-program-name prg)
                              destaddr))
                   (setq skt (first (remote-procedure-call destaddr 'portmapper 'lookup
                                           `(,(rpc-program-number prg)
                                             ,(rpc-program-version prg)
                                             ,(get-protocol-number protocol)
                                             0)
                                           :remotesocket 111)))
                   (if *debug*
                       (format-t "Socket ~a found via portampper on ~a for program ~a~%" skt destaddr
                              (rpc-program-name prg)))
                   (if (and *rpc-ok-to-cache* (> skt 0))
                       (push `(,destaddr ,prognum ,progvers ,protocol ,skt)
                             *rpc-socket-cache*)
                       skt)
                   (if (> skt 0)
                       skt)))
           ((error "Could not find remote socket number for~%~
   Host ~a, Remote Program ~a, Number ~a, Version ~a, Protocol ~a" destaddr (rpc-program-name prg)
                   prognum progvers protocol)))))

(defun encode-rpc-args (stream arglist rpc-proc)
   "
Takes a list of arguments and the corresponding list of XDR procedures and
converts the arguments into XDR, writing them into the RPC-STREAM.
"
   (when (and (numberp *debug*)
              (> *debug* 0))
         (format-t "  RPC Arguments: ~A~%" arglist))
   (do ((xdr-fns (rpc-procedure-argtypes rpc-proc)
               (rest xdr-fns))
        (args arglist (rest args)))
       ((or (null args)
            (null xdr-fns))
        (if (or xdr-fns args)
            (error "Mismatch of arguments and parameters to RPC call.~
              Number or arguments:~a, Number of parameters:~a" (length arglist)
                   (length (rpc-procedure-argtypes rpc-proc)))
            (rpc-procedure-name rpc-proc)))
     (funcall (first xdr-fns)
            stream
            (first args))))

(defun actually-do-the-rpc (stream msec-until-timeout msec-between-tries errorflg)
   "
Calls the appropriate function (for the protocol) to actually send the packets over
the net and await an answer.
"
   (ecase (rpc-stream-protocol stream)
       (udp (if *use-os-networking*
                (os-exchange-udp-packets stream msec-until-timeout msec-between-tries errorflg)
                (exchange-udp-packets stream msec-until-timeout msec-between-tries errorflg)))
       (tcp (exchange-tcp-packets stream msec-until-timeout errorflg))))

(defun exchange-udp-packets (stream msec-until-timeout msec-between-tries errorflg)
   "
Given the specified timeout and time between tries, this routine continues
to send out UDP packets until it either gets a reply or times out.
"
   (if (and (numberp *debug*)
            (> *debug* 5))
       (break "Packet ready to go from PACKET of *RPCSTREAM*"))
   (do* ((init-time (get-internal-real-time))
         (final-time (+ init-time (* msec-until-timeout *internal-time-units-per-msec*))))
        ((>= (get-internal-real-time)
             final-time)
         (case errorflg
             (:noerrors (throw 'goforit nil))
             (:returnerrors (throw 'goforit '(error timeout)))
             (otherwise (error "Timeout of RPC Call"))))
      (when *debug* (format-t "Trying RPC Call: Program ~a, Procedure ~a...~%" *rpc-pgname* 
                           *rpc-pcname*))
      (if (setf (rpc-stream-instream stream)
                (il:udp.exchange (rpc-stream-ipsocket stream)
                       (rpc-stream-outstream stream)
                       msec-between-tries))
          (progn (when *debug*
                     (format-t "It returned!~%")
                     (and (numberp *debug*)
                          (> *debug* 5)
                          (break "Reply Packet in INSTREAM of RPC-STREAM *RPCSTREAM*")))
                 (return t)))))

(defun exchange-tcp-packets (rpcstream timeout &optional errorflg)
   "
Given the specified timeout, this routine writes onto the TCP stream and 
waits until it either gets a reply or times out.
"
   (il:* il:|;;| 
 "Yes, I know EXCHANGE-TCP-PACKETS is a misnomer, but I wanted it to parallel Exchange-UDP-Packets")

   (let* ((outstring (rpc-stream-outstring rpcstream))
          (outstream (rpc-stream-outstream rpcstream))
          (instream (rpc-stream-instream rpcstream))
          (event (il:tcp.socket.event (il:tcp.stream.socket (rpc-stream-outstream rpcstream)))))
         (when (numberp *debug*)
             (inspect-string1 outstring (rpc-stream-outbyteptr rpcstream))
             (and (> *debug* 4)
                  (break "Ready to write to tcp stream")))
         (rm-forceoutput rpcstream t)
         (il:forceoutput outstream t)
         (if *debug* (format-t "Output forced out. Will wait ~a msec for reply~%" timeout))
         (il:await.event (il:tcp.socket.event (il:tcp.stream.socket (rpc-stream-outstream rpcstream))
                                )
                timeout nil)
         (if (il:readp instream)
             (progn (if *debug* (format-t "It returned!!!!~%"))
                    (rm-new-input-record rpcstream)
                    t)
             (case errorflg
                 (:noerrors (throw 'goforit nil))
                 (:returnerrors (throw 'goforit '(error timeout)))
                 (otherwise (error "Timeout of TCP Call after ~a msec.~%" timeout))))))

(defun parse-rpc-reply (rpcstream rettypes &optional errorflg)
   "
Parses a reply message. If all goes well, returns a list of the values returned (or T if RETTYPES is NIL).

If RPC was REJECTED, or ACCEPTED but with an ACCEPT-STAT other than SUCCESS,
then  (Following Courier) the response depends on the value of ERRORFLG:
	If ERRORFLG = 'NOERROR, then returns NIL
	If ERRORFLG = 'RETURNERRORS, then returns a list of the form
		(ERROR reply-stat accept-or-reject-stat otherinfo)
	If ERRORFLG = anything else, signals Lisp error.

"                                                            (il:* il:\; " ")
   (let (xid msgtype reply-stat verf accept-stat reject-stat)
        (setq xid (xdr-unsigned rpcstream))
        (setq msgtype (xdr-unsigned rpcstream))
        (if (not (eql msgtype 1))
            (error "RPC message is not a reply. MSGTYPE is ~A" msgtype))
        (case (get-reply-stat (setq reply-stat (xdr-unsigned rpcstream)))
            (accepted 
               (setq verf (decode-authentication rpcstream))
               (case (get-accept-stat (setq accept-stat (xdr-unsigned rpcstream)))
                   (success (if (null rettypes)
                                t
                                (do ((rs rettypes (cdr rs))
                                     (vals))
                                    ((null rs)
                                     (nreverse vals))
                                  (push (funcall (car rs)
                                               rpcstream)
                                        vals))))
                   (program-mismatch (rpc-error-prm-mismatch errorflg reply-stat accept-stat
                                            (xdr-unsigned rpcstream)
                                            (xdr-unsigned rpcstream)))
                   (program-unavailable (rpc-error-prm-unavailable errorflg reply-stat accept-stat))
                   (procedure-unavailable (rpc-error-prc-unavailable errorflg reply-stat accept-stat))
                   (garbage-arguments (rpc-error-garbage-args errorflg reply-stat accept-stat))))
            (rejected (case (get-reject-stat (setq reject-stat (xdr-unsigned rpcstream)))
                          (rpc-mismatch (rpc-error-mismatch errorflg reply-stat accept-stat
                                               (xdr-unsigned rpcstream)
                                               (xdr-unsigned rpcstream)))
                          (authentication-error (rpc-error-authentication errorflg reply-stat 
                                                       reject-stat (xdr-unsigned rpcstream)))
                          (otherwise (error "Unknown  RPC reply status: ~A" reply-stat)))))))

(defun create-xid ()
   "Returns a number to use as the ID of a given transmisssion."
   (setq *xid-count* (logand twoto32minusone (+ 1 *xid-count*))))



(il:* il:|;;;| "RPC Utility Functions")


(defun get-reply-stat (number)
   "Map number to corresponding reply-stat symbol of remote procedure call"
   (cdr (assoc number *rpc-reply-stats*)))

(defun get-accept-stat (number)
   "Map number to corresponding accept-stat symbol of remote procedure call"
   (cdr (assoc number *rpc-accept-stats*)))

(defun get-reject-stat (number)
   "Map number to corresponding reject-stat symbol of remote procedure call"
   (cdr (assoc number *rpc-reject-stats*)))

(defun get-authentication-stat (number)
   "Map number to corresponding authentication-stat symbol of remote procedure call"
   (cdr (assoc number *rpc-authentication-stats*)))

(defun get-protocol-number (protocol)
   "Map protocol name (e.g., RPC2::UDP) to corresponding protocol number (e.g., 17)"
   (or (cdr (assoc protocol *rpc-protocols*))
       (error "Could not find number for protocol ~a in *RPC-PROTOCOLS*" protocol)))

(defun find-cached-socket (destaddr prognum progvers protocol cache)
   "Looks up a given (DESTADDR, PROGNUM, PROGVERS, PROTOCOL) in the specified CACHE."
   (fifth (find-if #'(lambda (quint)
                            (and (eql (first quint)
                                      destaddr)
                                 (eql (second quint)
                                      prognum)
                                 (eql (third quint)
                                      progvers)
                                 (eql (fourth quint)
                                      protocol)))
                 cache)))



(il:* il:|;;;| "RPC Error Messages")


(defun rpc-error-prm-mismatch (errorflg reply-stat accept-stat low high)
   "NIL"
   (case errorflg
       (:noerrors nil)
       (:returnerrors `(error ,(get-reply-stat reply-stat)
                              ,(get-accept-stat accept-stat)
                              `(,low ,high)))
       (otherwise (error "RPC Program Mismatch: High: ~A Low: ~A" low high))))

(defun rpc-error-prm-unavailable (errorflg reply-stat accept-stat)
   "NIL"
   (case errorflg
       (:noerrors nil)
       (:returnerrors `(error ,(get-reply-stat reply-stat)
                              ,(get-accept-stat accept-stat)))
       (otherwise (error "RPC Program Unavailable"))))

(defun rpc-error-prc-unavailable (errorflg reply-stat accept-stat)
   "NIL"
   (case errorflg
       (:noerrors nil)
       (:returnerrors `(error ,(get-reply-stat reply-stat)
                              ,(get-accept-stat accept-stat)))
       (otherwise (error "RPC Procedure Unavailable"))))

(defun rpc-error-garbage-args (errorflg reply-stat accept-stat)
   "NIL"
   (case errorflg
       (:noerrors nil)
       (:returnerrors `(error ,(get-reply-stat reply-stat)
                              ,(get-accept-stat accept-stat)))
       (otherwise (error "RPC Garbage Arguments"))))

(defun rpc-error-mismatch (errorflg reply-stat reject-stat low high)
   "NIL"
   (case errorflg
       (:noerrors nil)
       (:returnerrors `(error ,(get-reply-stat reply-stat)
                              ,(get-reject-stat reject-stat)
                              `(,low ,high)))
       (otherwise (error "RPC Mismatch: High: ~A Low: ~A" low high))))

(defun rpc-error-authentication (errorflg reply-stat reject-stat authentication-stat)
   "NIL"
   (case errorflg
       (:noerrors nil)
       (:returnerrors `(error ,(get-reply-stat reply-stat)
                              ,(get-reject-stat reject-stat)
                              ,(get-authentication-stat authentication-stat)))
       (otherwise (error "Authorization Error: ~A" (get-authentication-stat authentication-stat)))))



(il:* il:|;;;| "Authentication")


(defconstant *authentication-typedef*
   '(:struct authentication (type (:enumeration (:null 0)
                                         (:unix 1)
                                         (:short 2)))
           (string :string))
   "NIL")

(defconstant *null-authentication* (make-authentication :type :null :string ""))

(defun create-unix-authentication (stamp machine-name uid gid gids)
   "
Given the fields of a Unix authentication, creates an AUTHENTICATION struct with
these fields encoded as a string.
"
   (let ((unix-auth (make-authentication))
         (tempstream (create-string-rpc-stream)))
        (xdr-unsigned tempstream stamp)
        (xdr-string tempstream machine-name)
        (xdr-unsigned tempstream uid)
        (xdr-unsigned tempstream gid)
        (xdr-gencode-inline nil '(:counted-array :unsigned)
               'write tempstream gids)
        (setf (authentication-type unix-auth)
              :unix)
        (setf (authentication-string unix-auth)
              (get-output-stream-string (rpc-stream-outstream tempstream)))
        unix-auth))

(defun encode-authentication (rpcstream auth)
   "
Given an AUTHENTICATION struct, converts the struct to its XDR encoding and writes it to
the RPC-STREAM specified.
"
   (if (null auth)
       (setq auth *null-authentication*))
   (check-type auth authentication)
   (xdr-gencode-inline nil *authentication-typedef* 'write rpcstream auth))

(defun decode-authentication (rpcstream)
   "
Reads an authentication from specified RPC-STREAM and returns it as an AUTHENTICATION
struct.
"
   (xdr-gencode-inline nil *authentication-typedef* 'read rpcstream))
(il:putprops il:rpcrpc il:copyright ("Stanford University and Xerox Corporation" 1987 1988))
(il:declare\: il:dontcopy
  (il:filemap (nil)))
il:stop
