!   MODEM.BAS
!       A program to allow remote control of one SDOS system via another,
!       including file copies, etc.
!
!       This program assumes the existance of an ACIA full-duplex Virtual
!       Terminal port tied via a modem (or direct connection) to an identical
!       configuration at the far end.  Data is transmitted/recieved in binary
!       mode over these ports.  The program relies on the buffering in
!       the VT driver to store recieved data until it has a chance to fetch it.
!       Half-duplex ports are not handled.
!
!       Several layers of protocol are implemented.  The first, or electronic
!       layer, is assumed to be RS-232 connected to a full duplex modem.
!       A second layer is defined by use of ACIA chips to break the data
!       stream into "bytes".  SDLP defines the third layer, and is actually
!       implemented in this program.  The fourth layer allows arbitrary
!       SDOS SYSCALL blocks to be sent and executed remotely.  A fifth layer
!       is the user interface, and allows a user to communicate remotely with
!       another user, to list file contents and/or directories, and to copy
!       files to or from his site.
!
!       The user interface allows the following commands:
!       FILES [FROM] <pathname>          list file directory <pathname>
!       LIST [FROM] <pathname>           list contents of file
!       COPY [FROM] <pathname> TO <pathname>
!       CRC [FROM] <pathname>            compute CRC on remote file
!       EXIT                             stop MODEM program
!       * <msgtext>                      send comment to remote user
!
!       The word FROM indicates that the pathname following is remote.
!       Absence of the word FROM indicates a local pathname.
!
!       Version 1.0a 3/20/83 IDB
!       Version 1.0b 2/22/86 IDB Allow "*" for TO file name in COPY.
!                                Speed up transmission by doing blk xmit
!                                instead of byte transmit.
! ?? Method to EXIT cleanly from local keyboard, also need to kill remote.
! ?? check that all commands are implemented as documented
        Dim Version$/"MODEM 1.0b Copyright (C) 1986 Software Dynamics"/
!^L
!
Dim DebugEnabled/0/,MakeTrouble/0/
Dim Local/1/,OtherCPU/2/,SDLPchannel/3/
Dim SCGetDataCount$/:F,:E,0,:36/,SCGetFreeCount$/:F,:E,0,:35/
Dim SyscallReadB$/:B,:E,0,0/,CCActivationCk$/:E,:E,0,:1D/
Dim CCClrInput$/:E,4,0,:15/
Dim CommandLine$(100),Temp$(100),PathNamePrefix$(100)
Dim Buffer$[255],OtherCPUBuffer$(255)
!   Maxlen(Buffer$) must be equal to Maxlen(OtherCPUBuffer$)
Dim SDLPSTATE/0/,SDLPSTATEDEAD/0/,SDLPSTATEAGREEQUIT/1/,...
&   SDLPSTATEWANTQUIT/2/,SDLPSTATEINACTIVE/3/,SDLPSTATEACTIVATING/4/,...
&   SDLPSTATERESYNCHED/5/,SDLPSTATEACTIVATED/6/,SDLPSTATECONNECTEDR/7/,...
&   SDLPSTATECONNECTEDS/8/,SDLPSTATEQUIT/9/
Dim SDLPCONTROLNORMAL/0/,SDLPCONTROLRESYNCHREQ/1/,SDLPCONTROLRESYNCHED/2/,...
&   SDLPCONTROLWANTQUIT/3/,SDLPCONTROLAGREEQUIT/4/,SDLPCONTROLQUIT/5/,...
&   SDLPCONTROLRESYNCHQUIT/6/
Dim SDLPREQUEST/0/,SDLPREQUESTNONE/0/,SDLPREQUESTOPEN/1/,SDLPREQUESTTERMINATE/2/
Dim SDLPSENDDATA$(280),SDLPRCVDDATA$(300)
Dim SDLPRCVRSTATE/1/,SDLPRCVRSTATESEARCHSYN/1/
Dim SDLPCONTROL$/5,4,3,5,1,2,0,0,0,5/
Dim SDLPByte$(1),SDLPTemp$(2)
Dim SDLPCRCPolyNomial/:1021/
Dim SDLPxmitRCVRDY/1/
Dim SDLPMessageOverhead/16/,InputRingBufferSize
Dim SDLPWaitBeforeXmitDelay/500/,SDLPIdleTimeMax/25/
Dim SDLPCRCTableUpper$(256),SDLPCRCTableLower$(256)
Dim SDLPXmitMessage$(300),SDLPReceiveBuffer$(256)/0/,SDLPReceivePointer/2/
Dim SDLPTemp,SDLPTemp2

!^L
Def MatchWildCardTo(WildCard$,MatchToString$)
    ! Function to match WildCard containing "*" to MatchToString
    ! Returns TRUE or FALSE based on match result

    Let i=Find(WildCard$,"*")
    If i=0 Then Return MatchToString$=WildCard$ \ ! Wildcard has form: <text>

    ! WildCard$ has form: <text>*<moretext>
    If Find(MatchToString$,WildCard$(1,i-1))<>1
    Then Return False \ ! Prefixes don't match
    MatchToPointer=i \ ! skip past matched prefix
    WildCardPointer=1 \ ! Set up for For loop in Repeat, below
    Repeat
        ! i holds offset from WildCardPointer to next "*" in WildCard$
        For WildCardPointer=WildCardPointer+i to len(WildCard$) ...
&           Until WildCard$(WildCardPointer,1)<>"*" ...
&           Do ! Skip past matched text and possible multiple "*"s

        ! Now match something to the "*" skipped in the WildCard$
        If WildCardPointer>Len(WildCard$) Then Return True \ ! Matched !
        If MatchToPointer>Len(MatchToString$) Then Return False
        ! At this point, WildCard$ has form: <matchedtext>*<text>...
        Let i=Find(Right$(WildCard$,WildCardPointer),"*")
        If i=0
        Then
            ! Wildcard has form:  *<text>
            i=Len(WildCard$)-WildCardPointer+1 \ ! Amount to match
            If Len(MatchToString$)-MatchToPointer+1 < i
            Then Return False \ ! Tail of MatchToString$ is too short
            ! Tail is long enough, see if matches wildcard tail
            Return MatchToString$[Len(MatchToString$)-i+1,i] = ...
&                  Right$(WildCard$,WildCardPointer)
        Fi

        ! WildCard$ has form: *<text>*<moretext>
        i1=Find(Right$(MatchToString$,MatchToPointer),...
&               WildCard$[WildCardPointer,i-1])
        If i1=0 Then Return False \ ! Can't find text in MatchToString$
        Let MatchToPointer=MatchToPointer+i-1 \ ! Skip past matched text
    End
End
!^L
Subroutine SDLP
!   SDLP implements the transport layer of the link.
!   It must be called periodically (often enough to not lose data frequently)
!   It leaves a state variable, SDLPSTATE, set to the state of the link
!   This variable must be inspected to determine if the link is closed or open.
!   Another variable, SDLPREQUEST, must be set to the function next desired:
!       SDLPREQUESTOPEN or SDLPREQUESTTERMINATE
!   To send data, the data must be placed at the end of SDLPSENDDATA$.
!   As the data is sent, it will be removed from SDLPSENDDATA$.
!   SDLPSENDDATA$ must not be modified (although it may be extended with more
!   data to send).
!   Data received is added onto SDLPRCVDDATA$.  The application program must
!   extract the data it needs from the front and "shuffle" the remainder left.
!   If no room is available in SDLPRCVDDATA$, flow control will prevent the
!   transmitter from sending more until the room is available.
    Call SDLPRECEIVE \ ! See if any message has arrived
    Call SDLPTRANSMIT \ ! Go transmit a message if needed
    Return Subroutine
End

Subroutine SDLPINIT
    SDLPREQUEST=SDLPREQUESTNONE \ ! No special link operation desired
    SDLPSTATE=SDLPSTATEINACTIVE
    Syscall #SDLPChannel,CCClrInput$
    SDLPrcvrState=SDLPrcvrStateSearchSYN \ ! Search for SYN
    SDLPxmitRCVDOK=0 \ ! Number of data bytes received correctly, mod 65536
    SDLPxmitXMTBASE=0 \ ! Virtual file position of data bytes, mod 65536
    SDLPOtherCPURCVRDY=0 \ ! Assume other CPU has zero bytes free
    SDLPXmitChokedCount=0 \ ! Number of xmit tries where buffer was full
    SDLPWaitBeforeXmit=SDLPWaitBeforeXmitDelay \ ! Number of times to wait before xmit
    SDLPSENDDATA$=''
    SDLPRCVDDATA$=''
    SDLPWaitingForData=False \ ! Hint to transmit routines
    Return Subroutine
End
!^L
Subroutine SDLPPrecomputeCRC
   ! Precompute CRC table contents
   For byte=0 to 255
       word=byte**8 \ ! word is model of 16 bit crc value at moment
       for bit=0 to 7
           ! for each bit in upper byte, compute mod to word by multiple XOR's
           carry=word&:8000 \ ! compute carry if word shifted left
           word=(word&:7fff)**1
           if carry then word = word xor SDLPCRCPolyNomial
       next bit
       SDLPCRCTableUpper$(1+Byte)=word**-8 \ ! save 8 bit crc step value
       SDLPCRCTableLower$(1+Byte)=word&:FF
   Next byte
   Return Subroutine
End

Subroutine ComputeCRC(ComputeCRCNewByte)
   ! To generate CRC using table:
   !    Initially, set CRCUpperByte=0,CRCLowerByte=0
   !    Then, for each New Byte:
   i=CRCUpperByte+1
   CRCUpperByte=SDLPCRCTableUpper$(i) xor CRCLowerByte
   CRCLowerByte=SDLPCRCTableLower$(i) xor ComputeCRCNewByte
   !    Tadah!
   Return Subroutine
End
!^L
Def SDLPReceiveByte
!   Pull byte from SDLPReceiveBuffer$
!   Called only if data is known to be in receive buffer
    SDLPReceivePointer=SDLPReceivePointer+1 \ ! Advance buffer scan pointer
    Return SDLPReceiveBuffer$[SDLPReceivePointer-1]
End

Def SDLPReceiveAndCRCByte
!   Pull byte from SDLPReceiveBuffer$ and update CRC
!   Called only if data is known to be in receive buffer
    SDLPTemp=SDLPrcvrCRCUpperByte+1
    SDLPrcvrCRCUpperByte=SDLPCRCTableUpper$(SDLPTemp) xor SDLPrcvrCRCLowerByte
    SDLPrcvrCRCLowerByte=SDLPCRCTableLower$(SDLPTemp) ...
&                        xor SDLPReceiveBuffer$[SDLPReceivePointer]
    SDLPReceivePointer=SDLPReceivePointer+1 \ ! Advance buffer scan pointer
    Return SDLPReceiveBuffer$[SDLPReceivePointer-1]
End
!^L
Subroutine SDLPRECEIVE
!   Determines if a message has arrived from "other" computer.
!   Processes content of message as appropriate.
    If SDLPSTATE=SDLPSTATEDEAD
    Then Return Subroutine \ ! Lifeless
    Repeat
        ! See if any data to process
        If SDLPReceivePointer>len(SDLPReceiveBuffer$)
        Then
            Syscall #SDLPChannel,SCGetDataCount$,'',SDLPTemp$
            Let SDLPTemp=SDLPTemp$[1]**8+SDLPTemp$[2]
            If SDLPTemp=0
            Then
                ! No more recieve data to process.
                If SDLPrcvrState<>SDLPrcvrStateSearchSYN
                Then
                    SDLPrcvrIdleTime=SDLPrcvrIdleTime+1
                    If SDLPrcvrIdleTime>SDLPIdleTimeMax
                    Then
                        If DebugEnabled
                        Then
                            Print "SDLPState=";SDLPState;...
&                                 "SDLPrcvrState";SDLPrcvrState
                            Print "Timed out, fall back into sync search!"
                        Fi
                        ! Message timed out, fall back into search for sync
                        SDLPrcvrState=SDLPrcvrStateSearchSYN
                        SDLPWaitBeforeXmit=0 \ ! Transmit immediately after timeout
                        SDLPrcvrIdleTime=0 \ ! Don't time out for a long time
                    Fi
                Fi
                Return Subroutine \ ! Fast exit if nothing to do
            Else
                SDLPrcvrIdleTime=0 \ ! Just got data, line is not idle
                let len(SDLPReceiveBuffer$)=SDLPTemp \ ! record amount of data
                Read #SDLPChannel,SDLPReceiveBuffer$[1,SDLPTemp] \ ! suck into buffer
                Let SDLPReceivePointer=1 \ ! set up to scan across buffer
                If DebugEnabled
                Then
                    Print "SDLPrcvrIdleTime=";SDLPrcvrIdleTime
                    Print "Collected";SDLPTemp;"bytes:"
                    For i=1 to SDLPTemp
                        Print hex$(SDLPReceiveBuffer$[i])[4,2];" ";
                    Next i
                    Print
                Fi
            Fi
        Fi
        On SDLPrcvrState Goto ...
&          SDLPReceiveSYN,SDLPReceiveTO,SDLPReceiveFROM,SDLPReceiveCNTRL,...
&          SDLPReceiveRCVDOK1,SDLPReceiveRCVDOK2,...
&          SDLPReceiveRCVRDY1,SDLPReceiveRCVRDY2,...
&          SDLPReceiveXMTCNT1,SDLPReceiveXMTCNT2,...
&          SDLPReceiveXMTBASE1,SDLPReceiveXMTBASE2,...
&          SDLPReceiveDATA,SDLPReceiveCRC1,SDLPReceiveCRC2,SDLPReceiveETB
        Print "SDLPReceive Bug!"\Exit
!^L
SDLPReceiveSYN: ! Waiting for SYN character
        If SDLPReceiveByte<>:16
        Then SDLPReceiveWAITDATA
        Else
            !D! Print "Leading SYN found..."
            SDLPrcvrCRCUpperByte=0 \ ! Initialize CRC at message start
            SDLPrcvrCRCLowerByte=0
            Goto SDLPReceiveNextState
        Fi

SDLPReceiveTO: ! Pick up TO node number
        SDLPTemp=SDLPReceiveAndCRCByte
        If SDLPTemp<>1
        Then
            ! Message is not for us, or we are not in sync!?
            SDLPrcvrState=SDLPrcvrStateSearchSYN
            Goto SDLPReceiveWaitData
        Fi
        Goto SDLPReceiveNextState

SDLPReceiveFROM: ! Pick up FROM node number
        SDLPTemp=SDLPReceiveAndCRCByte \ ! Ignore it, message is from other guy
        Goto SDLPReceiveNextState

SDLPReceiveCNTRL: ! Pick up CNTRL byte from message
        SDLPrcvrCNTRL=SDLPReceiveAndCRCByte \ ! Save it until CRC validated
        Goto SDLPReceiveNextState

SDLPReceiveRCVDOK1: ! Pick up RCVDOK count MSB
        SDLPrcvrRCVDOK=SDLPReceiveAndCRCByte**8
        Goto SDLPReceiveNextState

SDLPReceiveRCVDOK2: ! Pick up RCVDOK count LSB
        SDLPrcvrRCVDOK=SDLPrcvrRCVDOK+SDLPReceiveAndCRCByte
        Goto SDLPReceiveNextState
!^L
SDLPReceiveRCVRDY1: ! Pick up RCVRDY count MSB
        SDLPrcvrRCVRDY=SDLPReceiveAndCRCByte**8
        Goto SDLPReceiveNextState

SDLPReceiveRCVRDY2: ! Pick up RCVRDY count LSB
        SDLPrcvrRCVRDY=SDLPrcvrRCVRDY+SDLPReceiveAndCRCByte
        Goto SDLPReceiveNextState

SDLPReceiveXMTCNT1: ! Pick up XMTCNT MSB
        SDLPrcvrXMTCNT=SDLPReceiveAndCRCByte**8
        Goto SDLPReceiveNextState

SDLPReceiveXMTCNT2: ! Pick up XMTCNT LSB
        SDLPrcvrXMTCNT=SDLPrcvrXMTCNT+SDLPReceiveAndCRCByte
        If SDLPrcvrXMTCNT=0
        Then
            ! Skip over the receive XMTBASE and data states
            SDLPrcvrState=SDLPrcvrState+3
        Fi
        Goto SDLPReceiveNextState

SDLPReceiveXMTBASE1: ! Pick up XMTBASE MSB
        !D! Print "Collecting XMTBASE..."
        SDLPrcvrXMTBASE=SDLPReceiveAndCRCByte**8
        Goto SDLPReceiveNextState

SDLPReceiveXMTBASE2: ! Pick up XMTBASE LSB
        SDLPrcvrXMTBASE=SDLPrcvrXMTBASE+SDLPReceiveAndCRCByte
        ! Note: other node's XMTBASE must always be equal or behind our RCVDOK
        !       so SDLPrcvrOffsetFromRCVDOK must always be <= 0
        SDLPrcvrOffsetFromRCVDOK=...
&           If SDLPrcvrXMTBASE<=SDLPxmitRCVDOK
                Then SDLPrcvrXMTBASE-SDLPxmitRCVDOK
                Else SDLPrcvrXMTBASE-SDLPxmitRCVDOK-65535-1 Fi
        !D! PRINT "RESULTING SDLPrcvrOffsetFromRCVDOK";SDLPrcvrOffsetFromRCVDOK
        SDLPrcvrRemainingCount=SDLPrcvrXMTCNT
        SDLPRetainedDataCount=0 \ ! Number of data bytes beyond RCVDOK we kept
        Goto SDLPReceiveNextState
!^L
SDLPReceiveDATA: ! Pick up data part of message
        ! Assert: can't get here unless buffer has some unprocessed data
        SDLPTemp2=len(SDLPReceiveBuffer$)-SDLPReceivePointer+1 \ ! buffer remaining
        If SDLPTemp2>SDLPrcvrRemainingCount
        Then SDLPTemp2=SDLPrcvrRemainingCount \ ! can't take more data than given in msg
        For i=0 to SDLPTemp2-1
            ! Adjust CRC for data we will remove from buffer
            SDLPTemp=SDLPrcvrCRCUpperByte+1
            SDLPrcvrCRCUpperByte=SDLPCRCTableUpper$(SDLPTemp) ...
&                                xor SDLPrcvrCRCLowerByte
            SDLPrcvrCRCLowerByte=SDLPCRCTableLower$(SDLPTemp) ...
&                                xor SDLPReceiveBuffer$[SDLPReceivePointer+i]
        Next i
100     If SDLPrcvrOffsetFromRCVDOK<0
        Then
            If DebugEnabled
            Then
                Print "SDLPrcvrOffset=";SDLPrcvrOffsetFromRCVDOK
                Print "SDLPTemp2=";SDLPTemp2
            Fi
            ! We are ignoring data. Ignore until ...Offset becomes positive.
            If -SDLPrcvrOffsetFromRCVDOK>SDLPTemp2
            Then
                ! More data bytes to skip than are contained in the buffer.
                SDLPReceivePointer=SDLPReceivePointer+SDLPTemp2 \ ! skip bytes
                SDLPrcvrOffsetFromRCVDOK=SDLPrcvrOffsetFromRCVDOK+SDLPTemp2
                SDLPrcvrRemainingCount=SDLPrcvrRemainingCount-SDLPTemp2
                If SDLPrcvrRemainingCount>0
                Then SDLPReceiveWaitData
                Else SDLPReceiveNextState
            Else
                ! Ignore some data bytes, and retain the rest.
                SDLPReceivePointer=SDLPReceivePointer-SDLPrcvrOffsetFromRCVDOK
                SDLPrcvrRemainingCount=SDLPrcvrRemainingCount...
&                                      + SDLPrcvrOffsetFromRCVDOK
                SDLPTemp2=SDLPTemp2+SDLPrcvrOffsetFromRCVDOK \ ! Decrease count
                SDLPrcvrOffsetFromRCVDOK=0
            Fi
        Fi
        ! Assert: SDLPrcvrOffsetFromRCVDOK>=0 here.
        ! We can keep the data if there is room in SDLPRCVDDATA$
        If SDLPTemp2<=maxlen(SDLPRCVDDATA$)-len(SDLPRCVDDATA$)
        Then i=SDLPTemp2 \ ! # data bytes to place into SDLPRCVDDATA$
        Else
            ! Can't keep more than we have room for
            i=maxlen(SDLPRCVDDATA$)-len(SDLPRCVDDATA$)
        Fi
        j=len(SDLPRCVDDATA$)+SDLPrcvrOffsetFromRCVDOK+1 \ ! where 1st byte goes
200     if j<=maxlen(SDLPRCVDDATA$)
        then
            ! It appears we can keep some of the data
            j1=j+i-1 \ ! index of last byte we desire to store in SDLPRCVDDATA$
            if j1>maxlen(SDLPRCVDDATA$)
            then j1=maxlen(SDLPRCVDDATA$) \ ! can't store beyond last byte
            let j1=j1-j+1 \ ! Number of bytes we can put into SDLPRCVDDATA$
            If SDLPrcvrOffsetFromRCVDOK<=SDLPRetainedDataCount ...
&           and SDLPrcvrOffsetFromRCVDOK+j1>SDLPRetainedDataCount
            then
                ! Remember how many bytes kept
                SDLPRetainedDataCount=SDLPrcvrOffsetFromRCVDOK+j1
            fi
            let i=len(SDLPRCVDDATA$) \ ! Remember amount of known good data
            !D! Print "SDLPTemp2";SDLPTemp2;"j";j;"j1";j1;
            !D! Print "RcvPtr";SDLPReceivePointer
            !D! Print "len(...DATA$)";len(SDLPRCVDDATA$);
            !D! Print "len(...Buf$)";len(SDLPReceiveBuffer$)
            let len(SDLPRCVDDATA$)=maxlen(SDLPRCVDDATA$) \ ! momentarily
            ! Now move the bytes into the RECEIVED DATA area
            SDLPRCVDDATA$[j,j1]=SDLPReceiveBuffer$[SDLPReceivePointer,j1]
            let len(SDLPRCVDDATA$)=i \ ! restore original known good count
            ! because we can't be sure that the CRC is correct yet!
        else ! Too bad, the data gets lost because we haven't got room for it

        ! Adjust pointers for data taken
        SDLPReceivePointer=SDLPReceivePointer+SDLPTemp2
        SDLPrcvrOffsetFromRCVDOK=SDLPrcvrOffsetFromRCVDOK+SDLPTemp2
        SDLPrcvrRemainingCount=SDLPrcvrRemainingCount-SDLPTemp2

        If SDLPrcvrRemainingCount>0
        Then SDLPReceiveWaitData
        Else SDLPReceiveNextState

SDLPReceiveCRC1: ! Pick up MSB of message CRC
        SDLPrcvrRCVDCRCUpperByte=SDLPReceiveByte
        Goto SDLPReceiveNextState

SDLPReceiveCRC2: ! Pick up LSB of message CRC
        SDLPrcvrRCVDCRCLowerByte=SDLPReceiveByte
        Goto SDLPReceiveNextState
!^L
SDLPReceiveETB: ! Eat trailing ETB byte
        SDLPrcvrState=SDLPrcvrStateSearchSYN \ ! back to hunting for preamble
        If SDLPReceiveByte<>:17 ...
&          or SDLPrcvrRCVDCRCUpperByte xor :FF <> SDLPrcvrCRCUpperByte ...
&          or SDLPrcvrRCVDCRCLowerByte xor :FF <> SDLPrcvrCRCLowerByte
        Then
            If DebugEnabled
            Then
                Print "Garbled ETB or bad CRC..."
            Fi
            Goto SDLPReceiveWaitData \ ! Ignore the message
        Else
            ! Message is good, save data and respond to CTRL field changes
            ! We already verified the TO and FROM fields
            If DebugEnabled
            Then
                PRINT "Rcvd: STATE";SDLPSTATE;"CNTRL";SDLPrcvrCNTRL;...
&                     "RCVDOK";SDLPrcvrRCVDOK;"XMTCNT";SDLPrcvrXMTCNT;...
&                     "XMTBASE";SDLPrcvrXMTBASE;"RCVRDY";SDLPrcvrRCVRDY
            Fi

            SDLPOtherCPURCVRDY=SDLPrcvrRCVRDY \ ! how many he is ready for

            If SDLPState=SDLPStateConnectedR ...
&              and len(SDLPSENDDATA$)=0 and SDLPWaitingForData=False
            Then ! Don't respond right away...
            Else
                ! Responding instantly frees other guy's buffer space,
                ! at expense of not allowing us to pack more transmit
                ! data up for next XMIT message.  Waiting a long
                ! time makes remote node suffer because he correctly sent
                ! data is now tying up his buffers, but it allows us
                ! to fill our next XMIT message with more data.
                ! So we compromise and transmit "soon", which means
                ! that data which is ready to go now is packaged and sent.
                SDLPWaitBeforeXmit=1 \ ! Transmit very soon.
                ! "1" allows SDLPTransmit to pass once, thus allowing
                ! rest of program to stuff more data in buffers before
                ! trying to SDLP again.
            Fi

            If SDLPSTATE=SDLPSTATEINACTIVE
            Then
                If SDLPrcvrCNTRL<>SDLPControlResynchReq
                Then Return Subroutine Fi \ ! and send QUIT at other node
            ElseIf SDLPSTATE=SDLPSTATEACTIVATING
            Then
                If SDLPrcvrCNTRL=SDLPCONTROLRESYNCHREQ
                Then SDLPSTATE=SDLPSTATERESYNCHED
                ElseIf SDLPrcvrCNTRL=SDLPCONTROLRESYNCHED
                Then SDLPSTATE=SDLPSTATEACTIVATED
                Goto SDLPReceiveWaitData
            Fi

            If SDLPrcvrCNTRL=SDLPCONTROLRESYNCHREQ
            Then Goto SDLPCNTRLWasResynchRequest
!^L
            ! Handle data acknowledgement
            SDLPBytesAcknowledged=...
&               If SDLPrcvrRCVDOK>=SDLPxmitXMTBASE
                Then SDLPrcvrRCVDOK-SDLPxmitXMTBASE
                Else 65535-(SDLPxmitXMTBASE-SDLPrcvrRCVDOK)+1 Fi
            !D! Print "SDLPBytesAcknowledged";SDLPBytesAcknowledged

            If SDLPBytesAcknowledged>len(SDLPSENDDATA$) or ...
&              SDLPBytesAcknowledged>32767
            Then
                Print "Other node is crazy, killing link..."
                SDLPSTATE=SDLPSTATEQUIT
                Return Subroutine
            Fi

            If SDLPBytesAcknowledged>0
            Then
                SDLPSENDDATA$=Right$(SDLPSENDDATA$,SDLPBytesAcknowledged+1)
                SDLPxmitXMTBASE=SDLPxmitXMTBASE+SDLPBytesAcknowledged
                If SDLPxmitXMTBASE>65535
                Then SDLPxmitXMTBASE=SDLPxmitXMTBASE-65536
            Fi

            !D! PRINT "SDLPrcvrXMTCNT";SDLPrcvrXMTCNT
            !D! PRINT "SDLPrcvrOffsetFromRCVDOK";SDLPrcvrOffsetFromRCVDOK

            If SDLPrcvrXMTCNT>0 and SDLPrcvrOffsetFromRCVDOK>0
            Then
                ! We collected some data bytes
                Let Len(SDLPRCVDDATA$)=...
&                       Len(SDLPRCVDDATA$)+SDLPRetainedDataCount

                ! Update value to xmit as RCVDOK
                Let SDLPxmitRCVDOK=SDLPxmitRCVDOK+SDLPRetainedDataCount
                If SDLPxmitRCVDOK>65535
                Then SDLPxmitRCVDOK=SDLPxmitRCVDOK-65536 Fi
            Fi
!^L
            On SDLPrcvrCNTRL+1 Goto SDLPCNTRLWasNormal,...
&              SDLPCNTRLWasResynchRequest,SDLPCNTRLWasResynched,...
&              SDLPCNTRLWasWantQuit,SDLPCNTRLWasAgreeQuit,...
&              SDLPCNTRLWasQuit

SDLPCNTRLWasQuit:
SDLPNewStateIsDead:
            SDLPSTATE=SDLPSTATEDEAD
            SDLPREQUEST=SDLPREQUESTNONE
            Print "Link is dead..."
            Return Subroutine \ ! don't inspect any more receive data

SDLPCNTRLWasNormal:
            If SDLPSTATE=SDLPSTATECONNECTEDR or ...
&              SDLPSTATE=SDLPSTATECONNECTEDS or ...
&              SDLPSTATE=SDLPSTATEACTIVATED or ...
&              SDLPSTATE=SDLPSTATERESYNCHED
            Then SDLPCNTRLNormal1
            ElseIf SDLPSTATE=SDLPSTATEWANTQUIT
            Then Goto SDLPReceiveWaitData
!           Else SDLPCNTRLWasAgreeQuit
SDLPCNTRLWasAgreeQuit:
            SDLPSTATE=SDLPSTATEQUIT
            Goto SDLPReceiveWaitData

SDLPCNTRLNormal1: ! Conventional message has arrived
            !D! Print "Normal1..."
            SDLPSTATE=SDLPSTATECONNECTEDR \ ! Enter receive-only state
            If Len(SDLPSENDDATA$)=0 and SDLPrcvrXMTCNT>0 ...
&           Or Len(SDLPSENDDATA$)>0
            Then SDLPSTATE=SDLPSTATECONNECTEDS \ ! We have something to send
            Goto SDLPReceiveWaitData
!^L
SDLPCNTRLWasResynchRequest:
            If SDLPSTATE<>SDLPSTATEINACTIVE and ...
&              SDLPSTATE<>SDLPSTATEACTIVATING and ...
&              SDLPSTATE<>SDLPSTATERESYNCHED
            Then SDLPNewStateIsDEAD
            Else SDLPSTATE=SDLPSTATERESYNCHED\Goto SDLPReceiveWaitData

SDLPCNTRLWasResynched:
            If SDLPSTATE=SDLPSTATEACTIVATED
            Then SDLPCNTRLNormal1 Else SDLPNewStateIsDead

SDLPCNTRLWasWantQuit:
            If SDLPSTATE<>SDLPSTATEINACTIVE
            Then SDLPSTATE=SDLPSTATEAGREEQUIT\Goto SDLPReceiveWaitData
            Else SDLPNewStateIsDead
        Fi

SDLPReceiveNextState: ! Advance state number
        SDLPrcvrState=SDLPrcvrState+1
SDLPReceiveWaitData: ! Wait for more data to arrive
        ! Loop back to top of REPEAT statement
    End
End
!^L
Subroutine SDLPStoreMsgByte(SDLPStoreMsgByteValue)
!   Insert argument into SDLPXmitMessage$
!   This routine called only if room is known to be available in xmit buffer.
    SDLPXmitMessageSize=SDLPXmitMessageSize+1 \ ! Bump size of message
    SDLPXmitMessage$[SDLPXmitMessageSize]=SDLPStoreMsgByteValue
    Return Subroutine
End

Subroutine SDLPCRCandStoreMsgByte(SDLPCRCandStoreMsgByteValue)
!   Include argument in computed CRC and insert into SDLPXmitMessage$
!   This routine called only if room is known to be available in xmit buffer.
    SDLPXmitMessageSize=SDLPXmitMessageSize+1 \ ! Bump size of message
    SDLPXmitMessage$[SDLPXmitMessageSize]=SDLPCRCandStoreMsgByteValue
    !D! Print "SDLPCRCandStoreMsgByte:";SDLPxmitCRCUpperByte;SDLPxmitCRCLowerByte
    SDLPTemp=SDLPxmitCRCUpperByte+1
    SDLPxmitCRCUpperByte=SDLPCRCTableUpper$(SDLPTemp) xor SDLPxmitCRCLowerByte
    SDLPxmitCRCLowerByte=SDLPCRCTableLower$(SDLPTemp) ...
&                        xor SDLPCRCandStoreMsgByteValue
    Return Subroutine
End

Subroutine SDLPCRCandStoreMsgWord(SDLPCRCandStoreMsgWordValue)
!   Include argument in computed CRC and insert into SDLPXmitMessage$
!   This routine called only if room is known to be available in xmit buffer.
    SDLPXmitMessageSize=SDLPXmitMessageSize+2 \ ! Bump size of message

    SDLPTemp2=(SDLPCRCandStoreMsgWordValue&:FF00)/256 \ ! upper 8 bits
    SDLPXmitMessage$[SDLPXmitMessageSize-1]=SDLPTemp2
    SDLPTemp=SDLPxmitCRCUpperByte+1
    SDLPxmitCRCUpperByte=SDLPCRCTableUpper$(SDLPTemp) xor SDLPxmitCRCLowerByte
    SDLPxmitCRCLowerByte=SDLPCRCTableLower$(SDLPTemp) xor SDLPTemp2

    SDLPTemp2=SDLPCRCandStoreMsgWordValue&:FF \ ! lower 8 bits
    SDLPXmitMessage$[SDLPXmitMessageSize]=SDLPTemp2
    SDLPTemp=SDLPxmitCRCUpperByte+1
    SDLPxmitCRCUpperByte=SDLPCRCTableUpper$(SDLPTemp) xor SDLPxmitCRCLowerByte
    SDLPxmitCRCLowerByte=SDLPCRCTableLower$(SDLPTemp) xor SDLPTemp2

    Return Subroutine
End
!^L
Subroutine SDLPTRANSMIT
!   Transmits a message to remote computer.
!   See if we need to transmit a message
!       We don't need to send if:
!          No data to send, message state is "normal" and no reply is needed.
    If SDLPSTATE=SDLPSTATEDEAD or ...
&      Len(SDLPSENDDATA$)=0 and SDLPSTATE=SDLPSTATECONNECTEDR
    Then Exit Subroutine \ ! No need to transmit
    If SDLPSTATE=SDLPSTATEINACTIVE and SDLPREQUEST=SDLPREQUESTOPEN
    Then SDLPSTATE=SDLPSTATEACTIVATING
    Syscall #SDLPChannel,SCGetFreeCount$,'',SDLPTemp$
    ! Compute amount of bufferspace available for data
    Let SDLPXmitBufferSpace=SDLPTemp$[1]**8+SDLPTemp$[2]-SDLPMessageOverhead
    If SDLPXmitBufferSpace<0
    Then
        ! Not enough transmitter buffer available
        SDLPXmitChokedCount=SDLPXmitChokedCount+1
        If SDLPXmitChokedCount>1000
        Then
            Print "Transmitter choked..."
            SDLPXmitChokedCount=0
        Fi
        Return Subroutine \ ! Not enough room to xmit
    Fi
    SDLPXmitChokedCount=0 \ ! Number of time transmit buffer was choked

    If SDLPWaitBeforeXmit>0
    Then
        SDLPWaitBeforeXmit=SDLPWaitBeforeXmit-1 \ ! down count xmit delay
        Exit Subroutine \ ! Give other guy chance to respond
    Else
        ! Wait longer than idle gap before send again
        SDLPWaitBeforeXmit=SDLPWaitBeforeXmitDelay
    Fi
    SDLPXmitMessageSize=0 \ ! Number of bytes to send in next message
    ! Compute number of data bytes we should send
    SDLPxmitXMTCNT=len(SDLPSENDDATA$) \ ! First estimate
    ! Don't wish to hang on buffer full, so hold to available space in buffer
    If SDLPxmitXMTCNT>SDLPXmitBufferSpace
    Then SDLPxmitXMTCNT=SDLPXmitBufferSpace
    ! No point in overfilling destination buffer, limit to that amount
    If SDLPxmitXMTCNT>SDLPOtherCPURCVRDY
    Then SDLPxmitXMTCNT=SDLPOtherCPURCVRDY
    ! Must limit number of data bytes sent to 32767 total unacknowledged
    ! Need code here to accomplish this...
    SDLPxmitRCVRDY=InputRingBufferSize-SDLPMessageOverhead
    ! Assert: Maxlen(SDLPXmitMessage$)>SDLPxmitRCVRDY+messageheader size
!^L
    If DebugEnabled
    Then
        PRINT "Xmit: State";SDLPSTATE;"RCVDOK";SDLPxmitRCVDOK;...
&                "RCVRDY";SDLPxmitRCVRDY;"XMTCNT";...
&                  SDLPxmitXMTCNT;"XMTBASE";SDLPxmitXMTBASE;...
&                "len(SDLPSENDDATA$)";len(SDLPSENDDATA$)
        Print "InputRingBufferSize=";InputRingBufferSize
        Print "SDLPMessageOverhead=";SDLPMessageOverhead
    Fi
    ! Send the message
    SDLPxmitCRCUpperByte=0 \ ! Initialize CRC at message start
    SDLPxmitCRCLowerByte=0
    If MakeTrouble and rnd>.9
    Then
        Print chr$(7);"MAKING TROUBLE: Drop bit in leading SYN character"
        SDLPStoreMsgByte(:14)
    Else
        SDLPStoreMsgByte(:16) \ ! Transmit leading SYN character (but don't CRC it)
    Fi
    SDLPCRCandStoreMsgByte(1) \ ! Transmit TO node number
    SDLPCRCandStoreMsgByte(1) \ ! Transmit up FROM node number
    SDLPCRCandStoreMsgByte(SDLPCONTROL$[SDLPSTATE+1]) \ ! Transmit CNTRL byte for message
    SDLPCRCandStoreMsgWord(SDLPxmitRCVDOK) \ ! Transmit RCVDOK
    SDLPCRCandStoreMsgWord(SDLPxmitRCVRDY) \ ! Transmit RCVRDY
    SDLPCRCandStoreMsgWord(SDLPxmitXMTCNT) \ ! Transmit data byte count
    If SDLPxmitXMTCNT>0
    Then
        SDLPCRCandStoreMsgWord(SDLPxmitXMTBASE) \ ! Send base for data
        ! Now insert data part of message into message.
        ! The code embedded here is logically:
        ! For i=1 to SDLPxmitXMTCNT Do SDLPCRCandStoreMsgByte(SDLPSENDDATA$(i))
        ! but is hand-optimized for speed.
        ! (no optimization is performed on the other fields because little gain)
        let len(SDLPXmitMessage$)=SDLPXmitMessageSize+SDLPxmitXMTCNT
        SDLPXmitMessage$[SDLPXmitMessageSize+1,SDLPxmitXMTCNT]=...
&           SDLPSENDDATA$[1,SDLPxmitXMTCNT] \ ! copy data bytes into message
        For i=1 to SDLPxmitXMTCNT
            ! Adjust CRC over data bytes
            SDLPTemp=SDLPxmitCRCUpperByte+1
            SDLPxmitCRCUpperByte=SDLPCRCTableUpper$(SDLPTemp) ...
&                                xor SDLPxmitCRCLowerByte
            SDLPxmitCRCLowerByte=SDLPCRCTableLower$(SDLPTemp) ...
&                                xor SDLPSENDDATA$[i]
        Next i
        Let SDLPXmitMessageSize=len(SDLPXmitMessage$)
    Fi
    !D! Print "transmitting CRC...";SDLPxmitCRCUpperByte XOR :FF;
    !D! Print SDLPxmitCRCLowerByte XOR :FF
    SDLPStoreMsgByte(SDLPxmitCRCUpperByte XOR :FF)
    SDLPStoreMsgByte(SDLPxmitCRCLowerByte XOR :FF)
    If MakeTrouble and rnd>.9
    Then
        Print chr$(7);"MAKING TROUBLE: Drop bit in trailing ETB character"
        SDLPStoreMsgByte(:15)
    Else
        SDLPStoreMsgByte(:17) \ ! Transmit ETB at end of message
    Fi
    let len(SDLPXmitMessage$)=SDLPXmitMessageSize
    Write #SDLPCHANNEL,SDLPXmitMessage$ \ ! Send constructed message
    if DebugEnabled
    Then
        Print "Transmitted message: ";
        For i=1 to SDLPXmitMessageSize
            Print hex$(SDLPXmitMessage$[i])[4,2];" ";
        Next i
        Print
    Fi
    If SDLPState=SDLPStateConnectedS
    Then SDLPState=SDLPStateConnectedR
    ElseIf SDLPState=SDLPStateQuit
    Then
        SDLPState=SDLPStateDead
        Print "Link session terminated locally."
    Fi
    Return Subroutine
End
!^L
!   SDLP Interface subroutines
Def MSB(MSBarg)=Int(MSBarg/256)
Def LSB(LSBarg)=LSBarg-MSB(LSBarg)*256

Subroutine OtherCPUTransmitHint
    If len(SDLPSENDDATA$)=0 Then Return Subroutine \ ! Nothing to send
    If DebugEnabled Then Print "Transmit Hint!"
    If SDLPState=SDLPStateConnectedR
    Then SDLPState=SDLPStateConnectedS \ ! switch to ready to send state
    SDLPWaitBeforeXmit=0
    Return Subroutine
End

Subroutine OtherCPUSendString(OtherCPUSendString$)
    ! Sends OtherCPUSendString$ to remote
    ! Assert: len(OtherCPUSendString$)<=MaxLen(SDLPSENDDATA$)
    OtherCPUSendStringCount=0 \ ! Number of bytes of string we have sent so far
    Until OtherCPUSendStringCount=Len(OtherCPUSendString$) Do
        i=MaxLen(SDLPSENDDATA$)-Len(SDLPSENDDATA$)
        If i>0
        Then
            j=Len(OtherCPUSendString$)-OtherCPUSendStringCount
            if i>j then let i=j \ ! Choose minimum
            i0=len(SDLPSENDDATA$)
            Len(SDLPSENDDATA$)=Len(SDLPSENDDATA$)+i
            SDLPSENDDATA$[i0+1,i]=...
&               OtherCPUSendString$[OtherCPUSendStringCount+1,i]
            Let OtherCPUSendStringCount=OtherCPUSendStringCount+i
            If len(SDLPSENDDATA$)=MaxLen(SDLPSENDDATA$)
            Then Call OtherCPUTransmitHint Fi \ ! Force transmit if buffer full
        Elseif SDLPState=SDLPStateDead Then Return Subroutine
        Else Call SDLP \ ! exchange messages until room for new data
    End
    Return Subroutine
End

Subroutine OtherCPUSendByteValue(OtherCPUSendByteValueArg)
    ! Send numeric value as 1 byte
    Dim OtherCPUSendByteValueArg$(1)
    OtherCPUSendByteValueArg$=Chr$(OtherCPUSendByteValueArg)
    OtherCPUSendString(OtherCPUSendByteValueArg$)
    Return Subroutine
End

Subroutine OtherCPUSendCountAndString(OtherCPUSendCountAndStringArg$)
    ! Send 1 byte containing Len(OtherCPUSendCountAndStringArg$)
    ! Then send OtherCPUSendCountAndStringArg$
    OtherCPUSendByteValue(Len(OtherCPUSendCountAndStringArg$))
    OtherCPUSendString(OtherCPUSendCountAndStringArg$)
    Return Subroutine
End
!^L
Subroutine OtherCPUWaitNBytes(OtherCPUWaitNByteCount)
    ! Wait for large enough reply
    !D! Print "Waiting for ";OtherCPUWaitNByteCount;"bytes";...
    !D! &      "len(SDLPSENDDATA$)";len(SDLPSENDDATA$);"SDLPSTATE";SDLPState
    If Len(SDLPRCVDDATA$)<OtherCPUWaitNByteCount
    Then Call OtherCPUTransmitHint
    SDLPWaitingForData=True
    Until Len(SDLPRCVDDATA$)>=OtherCPUWaitNByteCount ...
&         or SDLPState=SDLPStateDead Do SDLP End
    SDLPWaitingForData=False
    If SDLPState=SDLPStateDead Then Error 1057
    !D! Print "Bytes have arrived!"
    Return Subroutine
End

Subroutine OtherCPUWaitResponse
    ! Wait for error response from remote
    ! Propogate the error if non-zero
    OtherCPUWaitNBytes(3) \ ! Wait for error response
    If SDLPRCVDDATA$[1]<>0 Then Error 1057 \ ! Device Errored, other guy nuts
    Let OtherCPUError=SDLPRCVDDATA$[2]**8+SDLPRCVDDATA$[3]
    Let SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,4)
    If OtherCPUError=0 Then Return Subroutine
    Elseif OtherCPUError=1001 Then OtherCPUEndOfFile=TRUE\Return Subroutine
    Else
        Print "Remote CPU issued error: ";OtherCPUError
        Error OtherCPUError
    Fi
End
!^L
!
!   Logical Data Stream format
!
!   Contains series of command requests
!   References at most one open file
!
!   0   <2 byte error response to last operation requested>
!   1   <count> <count-bytes of filename to open>
!   2   <count> <count-bytes of filename to create>
!   3           specifies that remote file is to be closed
!   4   <count> <count-bytes of data to write on file>
!   5   <count>          specifies that <count> bytes are to be read from file
!   6   <4 byte position> specifies where to position file
!   7   <count> <count-bytes of message to display to operator> (note: no <CR>)
!       Note: these messages cannot be sent while remote file is open.
!   8   <count> <count-data bytes response to last read request>
!
!   9           release remote node from server duty
!   10          requires operater response to remote query
!               sends back type 8 message
!   11  <count> <count-bytes of filename to delete>
!
Subroutine DisplayOtherCPUOperatorMessage(OtherCPUOperatorMessageText$)
    SDLPREQUEST=SDLPREQUESTOPEN \ ! Open the link if not already open
    OtherCPUSendByteValue(7)
    OtherCPUSendCountAndString(OtherCPUOperatorMessageText$)
    OtherCPUTransmitHint
    ! No reply expected
    Until len(SDLPSENDDATA$)=0 or SDLPState=SDLPStateDead Do SDLP End
    Return Subroutine
End

Subroutine IssueRemoteQuery(IssueRemoteQuery$)
    DisplayOtherCPUOperatorMessage(IssueRemoteQuery$)
    OtherCPUSendByteValue(10) \ ! ask for remote input
    OtherCPUWaitNBytes(2)
    If SDLPRCVDDATA$[1]<>8 Then Error 1057 \ ! Device Errored, other guy nuts
    Let OtherCPUReadCount=SDLPRCVDDATA$[2]
    Let SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,3)
    OtherCPUWaitNBytes(OtherCPUReadCount)
    IssueRemoteQuery$=SDLPRCVDDATA$[1,OtherCPUReadCount]
    Let SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,OtherCPUReadCount+1)
    Return Subroutine
End

Subroutine ReleaseRemoteNodeFromServerFunction
    SDLPREQUEST=SDLPREQUESTOPEN \ ! Open the link if not already open
    OtherCPUSendByteValue(9)
    OtherCPUTransmitHint
    ! No reply expected
    Return Subroutine
End
!^L
Subroutine OpenOtherCPUFile(OpenOtherCPUFileName$)
    SDLPREQUEST=SDLPREQUESTOPEN \ ! Open the link if not already open
    OtherCPUSendByteValue(1)
    OtherCPUSendCountAndString(OpenOtherCPUFileName$)
    !D! Print "Waiting for remote open to complete..."
    OtherCPUWaitResponse
    !D! Print "Remote open completed."
    OtherCPUEndOfFile=False
    Return Subroutine
End

Subroutine CreateOtherCPUFile(CreateOtherCPUFileName$)
    !D! Print "Performing remote create..."
    SDLPREQUEST=SDLPREQUESTOPEN \ ! Open the link if not already open
    OtherCPUSendByteValue(2)
    OtherCPUSendCountAndString(CreateOtherCPUFileName$)
    OtherCPUWaitResponse
    !D! Print "Remote create completed."
    OtherCPUEndOfFile=False
    Return Subroutine
End

Subroutine CloseOtherCPUFile
    OtherCPUSendByteValue(3)
    OtherCPUWaitResponse
    Return Subroutine
End

Subroutine WriteOtherCPUFile(WriteOtherCPUFileString$)
    OtherCPUSendByteValue(4)
    OtherCPUSendCountAndString(WriteOtherCPUFileString$)
    OtherCPUWaitResponse
    Return Subroutine
End
!^L
Subroutine ReadOtherCPUFile(ReadOtherCPUFileTarget$)
    OtherCPUSendByteValue(5)
    OtherCPUSendByteValue(MaxLen(ReadOtherCPUFileTarget$))
    OtherCPUWaitResponse
    OtherCPUWaitNBytes(2)
    If SDLPRCVDDATA$[1]<>8 Then Error 1057 \ ! Device Errored, other guy nuts
    Let OtherCPUReadCount=SDLPRCVDDATA$[2]
    Let SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,3)
    OtherCPUWaitNBytes(OtherCPUReadCount)
    ReadOtherCPUFileTarget$=SDLPRCVDDATA$[1,OtherCPUReadCount]
    Let SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,OtherCPUReadCount+1)
    Return Subroutine
End

Subroutine PositionOtherCPUFile(OtherCPUFilePosition)
    OtherCPUSendByteValue(6)
    OtherCPUSendByteValue(MSB(MSB(MSB(OtherCPUFilePosition))))
    OtherCPUSendByteValue(LSB(MSB(MSB(OtherCPUFilePosition))))
    OtherCPUSendByteValue(LSB(MSB(OtherCPUFilePosition)))
    OtherCPUSendByteValue(LSB(OtherCPUFilePosition))
    OtherCPUWaitResponse
    Return Subroutine
End

Subroutine DeleteOtherCPUFile(DeleteOtherCPUFileName$)
    SDLPREQUEST=SDLPREQUESTOPEN \ ! Open the link if not already open
    OtherCPUSendByteValue(11)
    OtherCPUSendCountAndString(DeleteOtherCPUFileName$)
    !D! Print "Waiting for remote delete to complete..."
    OtherCPUWaitResponse
    !D! Print "Remote delete completed."
    Return Subroutine
End
!^L
Subroutine OtherCPUServeSendErrorResponse(OtherCPUServeSendErrorCode)
    ! Send error response
    OtherCPUSendByteValue(0)
    OtherCPUSendByteValue(MSB(OtherCPUServeSendErrorCode))
    OtherCPUSendByteValue(LSB(OtherCPUServeSendErrorCode))
    Return Subroutine
End

Def OtherCPUServeWaitCount
    ! Wait for count byte to arrive, return it as function result
    OtherCPUWaitNBytes(2) \ ! Wait for count byte to arrive
    i=SDLPRCVDDATA$[2]
    SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,3) \ ! Eat the request and count bytes
    Return i
End
!^L
Subroutine ProcessOtherCPUFunctions
    ! Called to play remote file server
    ! Serves remote file requests; won't touch Replies or data responses
    ! See logical requests, described above
    If Len(SDLPRCVDDATA$)<1 Then Return Subroutine \ ! Nothing to do
    On SDLPRCVDDATA$(1) ...
&      Goto OtherCPUServeOpen,OtherCPUServeCreate,OtherCPUServeClose,...
&      OtherCPUServeWrite,OtherCPUServeRead,OtherCPUServePosition,...
&      OtherCPUServeMessage,NotRemoteFunction,OtherCPUReleaseServerMode,...
&      OtherCPUServeRemoteQuery,OtherCPUServeDelete
NotRemoteFunction: ! code is not a remote function, don't fool with it
    Return Subroutine \ ! Not a remote serve request, leave it alone

OtherCPUServeOpen: ! Serve OtherCPU Open request
    !D! Print "OtherCPUServeOpen..."
    OtherCPUFileNameLength=OtherCPUServeWaitCount
    OtherCPUWaitNBytes(OtherCPUFileNameLength)
    OtherCPUBuffer$=SDLPRCVDDATA$[1,OtherCPUFileNameLength]
    SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,OtherCPUFileNameLength+1)
    If Error When Open #OtherCPU,OtherCPUBuffer$
    Then OtherCPUError=Err Else OtherCPUError=0
    OtherCPUServeSendErrorResponse(OtherCPUError)
    !D! Print "OtherCPUServeOpen completed...";len(SDLPSENDDATA$)
    OtherCPUTransmitHint
    Return Subroutine

OtherCPUServeCreate: ! Serve OtherCPU Create request
    !D! Print "OtherCPUServeCreate..."
    OtherCPUFileNameLength=OtherCPUServeWaitCount
    OtherCPUWaitNBytes(OtherCPUFileNameLength)
    OtherCPUBuffer$=SDLPRCVDDATA$[1,OtherCPUFileNameLength)
    SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,OtherCPUFileNameLength+1)
    If Error When Create #OtherCPU,OtherCPUBuffer$
    Then OtherCPUError=Err Else OtherCPUError=0
    OtherCPUServeSendErrorResponse(OtherCPUError)
    OtherCPUTransmitHint
    !D! Print "OtherCPUServeCreate done!"
    Return Subroutine
!^L
OtherCPUServeClose: ! Serve OtherCPU Close request
    SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,2) \ ! Eat the service request code
    If Error When Close #OtherCPU Then OtherCPUError=Err Else OtherCPUError=0
    OtherCPUServeSendErrorResponse(OtherCPUError)
    OtherCPUTransmitHint
    Return Subroutine

OtherCPUServeWrite: ! Serve OtherCPU Write request
    OtherCPUFileNameLength=OtherCPUServeWaitCount
    OtherCPUWaitNBytes(OtherCPUFileNameLength)
    If Error When Write #OtherCPU,SDLPRCVDDATA$[1,OtherCPUFileNameLength]
    Then OtherCPUError=Err Else OtherCPUError=0
    SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,OtherCPUFileNameLength+1)
    OtherCPUServeSendErrorResponse(OtherCPUError)
    OtherCPUTransmitHint
    Return Subroutine

OtherCPUServeRead: ! Serve OtherCPU Read request
    !D! Print "OtherCPUServeRead..."
    OtherCPUFileNameLength=OtherCPUServeWaitCount
    ! Reads for remote user
    ! Assert: len(Read Request)<=MaxLen(SDLPSENDDATA$)
    If Error When
         Syscall #OtherCPU,SyscallReadB$,'',OtherCPUBuffer$,...
&                                           OtherCPUFileNameLength
    Then OtherCPUError=Err Else OtherCPUError=0
    OtherCPUServeSendErrorResponse(OtherCPUError)
    OtherCPUSendByteValue(8) \ ! Send data response
    OtherCPUSendCountAndString(OtherCPUBuffer$)
    !D! Print "OtherCPUServeRead complete"
    OtherCPUTransmitHint
    Return Subroutine
!^L
OtherCPUServePosition: ! Serve OtherCPU Position request
    OtherCPUWaitNBytes(5) \ ! Allow positioning bytes to arrive
    If Error When
        Position #OtherCPU@((SDLPRCVDDATA$[2]*256+...
&                          SDLPRCVDDATA$[3])*256+...
&                          SDLPRCVDDATA$[4])*256+...
&                          SDLPRCVDDATA$[5]
    Then OtherCPUError=Err Else OtherCPUError=0
    SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,5+1)
    OtherCPUServeSendErrorResponse(OtherCPUError)
    OtherCPUTransmitHint
    Return Subroutine

OtherCPUServeMessage: ! Serve remote message request
    OtherCPUFileNameLength=OtherCPUServeWaitCount
    OtherCPUWaitNBytes(OtherCPUFileNameLength)
    Print SDLPRCVDDATA$[1,OtherCPUFileNameLength]
    SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,OtherCPUFileNameLength+1)
    Return Subroutine

OtherCPUServeRemoteQuery: ! Serve remote query request
    SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,2)
    Input "Reply: " OtherCPUBuffer$
    OtherCPUSendByteValue(8) \ ! Send data response
    OtherCPUSendCountAndString(OtherCPUBuffer$)
    OtherCPUTransmitHint
    Return Subroutine

OtherCPUReleaseServerMode: ! Serve release request
    ServerMode=False \ ! Boy, this one is easy!
    Return Subroutine

OtherCPUServeDelete: ! Serve OtherCPU Delete request
    !D! Print "OtherCPUServeDelete..."
    OtherCPUFileNameLength=OtherCPUServeWaitCount
    OtherCPUWaitNBytes(OtherCPUFileNameLength)
    OtherCPUBuffer$=SDLPRCVDDATA$[1,OtherCPUFileNameLength]
    SDLPRCVDDATA$=Right$(SDLPRCVDDATA$,OtherCPUFileNameLength+1)
    If Error When Delete OtherCPUBuffer$
    Then OtherCPUError=Err Else OtherCPUError=0
    OtherCPUServeSendErrorResponse(OtherCPUError)
    !D! Print "OtherCPUServeDelete completed...";len(SDLPSENDDATA$)
    OtherCPUTransmitHint
    Return Subroutine

End
!^L
Subroutine ProcessKeyBoardCommand
!   This subroutine checks for a completed line available from CONSOLE:
!   If not ready, simply returns
!   Else gets the line and executes the desired command
!
!
CheckKeyBoard:
    Input "MODEM>" CommandLine$
    Let CommandLine$=lowercase$(CommandLine$)
    If CommandLine$="" Then CheckKeyBoard
    If CommandLine$="debug" Then DebugEnabled=TRUE \ Goto CheckKeyBoard
    If CommandLine$="maketrouble" Then MakeTrouble=TRUE \ Goto CheckKeyBoard
    If CommandLine$="server"
    Then ServerMode=TRUE \ Return Subroutine
    If CommandLine$="release"
    Then ReleaseRemote
    If Find(CommandLine$,"?")=1
    Then RemoteQuery
    Let CommandLine$=LowerCase$(CommandLine$)
    If CommandLine$="exit"
    then
        SDLPREQUEST=SDLPREQUESTTERMINATE
        For i=1 to 10 Until SDLPSTATE=SDLPSTATEDEAD Do SDLP End
        exit
    Fi
    If CommandLine$="wake"
    Then
        If SDLPSTATE=SDLPSTATEDEAD
        Then Call SDLPINIT \ ! Do first-time processing
        Else Print "?? Link is already active."
        Goto CheckKeyBoard
    Fi
    If CommandLine$="die"
    Then
        If SDLPSTATE=SDLPSTATEDEAD
        Then Print "??Link is already dead."
        Else SDLPSTATE=SDLPSTATEWANTQUIT
        For i=1 to 10 Until SDLPSTATE=SDLPSTATEDEAD Do SDLP End
        Goto CheckKeyBoard
    Fi
!^L
    If CommandLine$="files"
    Then FROM=0\CommandLine$=""\PathNamePrefix$=""\Goto DoFilesCommand1
    If Find(CommandLine$,"files ")=1 Then DoFilesCommand
    If Find(CommandLine$,"list ")=1 Then DoListCommand
    If Find(CommandLine$,"copy ")=1 Then DoCopyCommand
    If Find(CommandLine$,"*")=1 Then DoMSGCommand
    If Find(CommandLine$,"crc ")=1 Then DoCRCCommand
    If Find(CommandLine$,"delete ")=1 Then DoDeleteCommand
    Print "?? Illegal MODEM command: ignored."
    Print "Try one of the following:"
Print "SERVER                           makes this node service remote"
Print "FILES [FROM] <pathname>          list file directory <pathname>"
Print "LIST [FROM] <pathname>           list contents of file"
Print "COPY [FROM] <pathname> TO <pathname> copy to/from remote file"
Print "CRC [FROM] <pathname>            compute CRC on remote file"
Print "DELETE [FROM] <pathname>         deletes a file"
Print "* <msgtext>                      send comment to remote user"
Print "? <msgtext>                      send question to remote user"
Print "EXIT                             stop MODEM program"
Print "DIE                              breaks link connection"
Print "WAKE                             reestablishes link connection"
Print "RELEASE                          releases remote node from server mode"
    Goto CheckKeyBoard
!^L
DoFilesCommand:
    Gosub CheckForFROM
    Let CommandLine$=UpperCase$(CommandLine$) \ ! So wildcards are uppercase
DoFilesCommand1:
    OtherCPUBuffer$=PathNamePrefix$ cat "DIRECTORY.SYS"
    If CommandLine$="" Then CommandLine$="*"
    If FROM Then DoFilesOtherCPU
DoFilesLocal:
    Open #Local,OtherCPUBuffer$
    Print "Local Files..."
    OtherCPUFileCount=0
DoFilesLocalLoop:
    Len(Buffer$)=32
    Read #Local,Buffer$[1,32]
    If EOF(Local)
    Then Close #Local\
         Print OtherCPUFileCount;" files listed."\Goto CheckKeyBoard
    If Buffer$[19]>0
    Then
        i=Find(Buffer$[1,16]," ")
        if i=0 then len(Buffer$)=16 else len(Buffer$)=i-1
        If MatchWildCardTo(CommandLine$,Buffer$)
        Then
            Print Buffer$
            OtherCPUFileCount=OtherCPUFileCount+1
        Fi
    Fi
    Goto DoFilesLocalLoop
!^L
DoFilesOtherCPU:
    OpenOtherCPUFile(OtherCPUBuffer$)
    Print "Remote Files..."
    OtherCPUFileCount=0
DoOtherCPUFilesLoop:
    Len(Buffer$)=32
    ReadOtherCPUFile(Buffer$[1,32])
    If OtherCPUEndOfFile
    Then Call CloseOtherCPUFile\
         Print OtherCPUFileCount;" files listed."\Goto CheckKeyBoard
    If Buffer$[19]>0
    Then
        i=Find(Buffer$[1,16]," ")
        if i=0 then len(Buffer$)=16 else len(Buffer$)=i-1
        If MatchWildCardTo(CommandLine$,Buffer$)
        Then
            Print Buffer$
            OtherCPUFileCount=OtherCPUFileCount+1
        Fi
    Fi
    Goto DoOtherCPUFilesLoop
!^L
DoListCommand:
    Gosub CheckForFROM
    OtherCPUBuffer$=PathNamePrefix$ cat CommandLine$
    If FROM Then DoListOtherCPU
DoListLocal:
    Open #Local,OtherCPUBuffer$
DoListLocalLoop:
    Read #Local,Buffer$
    Print Buffer$;
    If EOF(Local)
    Then Close #Local\Goto CheckKeyBoard
    Goto DoListLocalLoop

DoListOtherCPU:
    OpenOtherCPUFile(OtherCPUBuffer$)
DoListOtherCPULoop:
    !D! Print "Reading another bufferful..."
    ReadOtherCPUFile(Buffer$)
    Print Buffer$;
    If OtherCPUEndOfFile
    Then Call CloseOtherCPUFile\Goto CheckKeyBoard
    Goto DoListOtherCPULoop
!^L
DoCopyCommand:
    Gosub CheckForFROM
    i=find(CommandLine$," to ")
    if i=0 then print "?? missing TO: command aborted."\Goto CheckKeyBoard
    OtherCPUBuffer$=PathNamePrefix$ cat CommandLine$[1,i-1]
    If Right$(CommandLine$,i+4)="*"
    Then
        ! User specified "*" as destination file name
        ! Use source file name for destination file name.
        CommandLine$=CommandLine$[1,i-1]
    ElseIf CommandLine$[Len(CommandLine$)-1,2]=":*"
    Then
        ! User specified "device:*" as destination file name
        ! Use "device:sourcefile" for destination file name.
        CommandLine$=CommandLine$[i+4,len(CommandLine$)-1-(i+4)+1] ...
&                    cat CommandLine$[1,i-1]
    Else
        ! User specified both source and destination file names
        CommandLine$=Right$(CommandLine$,i+4)
    Fi
    If FROM then DoCopyFromOtherCPU

DoCopyFromLocal: ! want to copy from local file to remote
    !D! Print "DoCopyFromLocal"
    CreateOtherCPUFile(CommandLine$)
    !D! Print "RemoteFileCreated"
    Open #Local,OtherCPUBuffer$
DoCopyFromLocalLoop:
    Read #Local,Buffer$
    !D! Print "Sending block of data to copy"
    WriteOtherCPUFile(Buffer$)
    !D! Print "Block of data sent"
    If EOF(Local)
    Then Call CloseOtherCPUFile\Close #Local\Goto CheckKeyBoard
    Goto DoCopyFromLocalLoop

DoCopyFromOtherCPU: ! wants to copy from from remote to local
    ! Print "DoCopyFromOtherCPU"
    OpenOtherCPUFile(OtherCPUBuffer$)
    Create #Local,CommandLine$
DoCopyFromOtherCPULoop:
    ReadOtherCPUFile(Buffer$)
    Write #Local,Buffer$
    If OtherCPUEndOfFile
    Then Call CloseOtherCPUFile\Close #Local\Goto CheckKeyBoard
    Goto DoCopyFromOtherCPULoop
!^L
DoCRCCommand:
    Let CRCUpperByte=0\CRCLowerByte=0
    Gosub CheckForFROM
    OtherCPUBuffer$=PathNamePrefix$ cat CommandLine$
    If FROM then DoCRCOtherCPU
DoCRCLocal:
    Open #Local,OtherCPUBuffer$
DoCRCLocalLoop:
    Read #Local,Buffer$
    For j=1 to len(Buffer$) do ComputeCRC(Buffer$(j))
    If EOF(Local) Then Close #Local\Goto DoCRCDisplay
    Goto DoCRCLocalLoop

DoCRCOtherCPU:
    OpenOtherCPUFile(OtherCPUBuffer$)
DoCRCOtherCPULoop:
    !D! Print "DoCRCOtherCPULoop"
    ReadOtherCPUFile(Buffer$)
    !D! Print "Read buffer..."
    For j=1 to len(Buffer$) do ComputeCRC(Buffer$(j))
    !D! Print "Buffer is CRC'd..."
    If OtherCPUEndOfFile
    Then Call CloseOtherCPUFile\Goto DoCRCDisplay
    Goto DoCRCOtherCPULoop

DoCRCDisplay:
    Print "CRC=";Hex$(CRCUpperByte**8+CRCLowerByte)\Goto CheckKeyBoard
    Goto CheckKeyBoard
!^L
DoDeleteCommand:
    Gosub CheckForFROM
    OtherCPUBuffer$=PathNamePrefix$ cat CommandLine$
    If FROM
    Then
        !D! Print "Starting Remote Delete"
        DeleteOtherCPUFile(OtherCPUBuffer$)
    Else
        Delete OtherCPUBuffer$
    Fi
    Goto CheckKeyBoard

DoMSGCommand:
    DisplayOtherCPUOperatorMessage(CommandLine$)
    Goto CheckKeyBoard

ReleaseRemote:
    ReleaseRemoteNodeFromServerFunction
    Goto CheckKeyBoard

RemoteQuery:
    IssueRemoteQuery(CommandLine$)
    Print "Answer: ";CommandLine$
    Goto CheckKeyBoard
!^L
CheckForFROM: ! Assert: there exists a blank in commandline$
    ! Sets FROM to "TRUE" if word FROM is present at front of line
    ! Sets PathNamePrefix$ to device name specified, or to DISK:
    ! Leaves filename in CommandLine$

    FROM=find(CommandLine$," ")
    CommandLine$=Right$(CommandLine$,FROM+1)
    FROM=find(CommandLine$,"from ")
    If FROM=1
    Then CommandLine$=Right$(CommandLine$,6)
    i=0\i0=0
    imax=Find(CommandLine$," ")
    if imax=0 then imax=len(CommandLine$)+1
    Repeat
       i=i+i0
       i0=Find(CommandLine$[i+1,imax-i-1],":")
    When i0>0 End
    Let PathNamePrefix$=CommandLine$[1,i]
    Let CommandLine$=Right$(CommandLine$,i+1)
    Return

End
!^L
    Print Version$

    ! Get port address
    If Error When Open #Local,"MODEM.INI"
    Then
        If Err<>1011 then Error
        Input "Name of port to use: " CommandLine$
        Input "Size of input ring buffer: " InputRingBufferSize
        Print "For 9600 baud, 2Mhz CPU:"
        Print "    use Wait before Transmit Delay = 500"
        Print "        Idle Time Max = 25"
        Input "Wait before Transmit delay " SDLPWaitBeforeXmitDelay
        Input "Idle Time Max (time between bytes)" SDLPIdleTimeMax
        Input "Save in MODEM.INI ? " Temp$
        If Find(UpperCase$(Temp$),"Y")=1
        Then
            Create #Local,"MODEM.INI"
            Print #Local,CommandLine$
            Print #Local,InputRingBufferSize
            Print #Local,SDLPWaitBeforeXmitDelay
            Print #Local,SDLPIdleTimeMax
            Close #Local
        Fi
    Else
        Input #Local,CommandLine$
        Input #Local,InputRingBufferSize
        Input #Local,SDLPWaitBeforeXmitDelay
        Input #Local,SDLPIdleTimeMax
        Close #Local
    Fi
    If Error When Open #SDLPChannel,CommandLine$
    Then Print "Can't Open SDLP channel..."\Error

    ServerMode=False \ ! Until told otherwise
    Call SDLPINIT \ ! Do first-time processing
    Call SDLPPrecomputeCRC \ ! Set up fast-CRC tables

MainLoop:
    ProcessKeyBoardCommand
    Repeat
        SDLP \ ! Allow comm a chance
        If SDLPState=SDLPStateDead
        Then
            Call SDLPINIT \ ! Reinitialize link for transmission
            If Error When Close #Local Then ! Ignore it
            If Error When Close #OtherCPU Then ! Ignore this, too
        Fi
        ProcessOtherCPUFunctions
        !D! Print "MainLoop: len(SDLPSENDDATA$)";len(SDLPSENDDATA$)
    When ServerMode End
    Goto MainLoop
END
