-- file: FTPOisCom.mesa -- Edited by: Johnsson September 21, 1979 7:38 AM -- Edited by: Forrest July 25, 1980 8:54 PM -- Edited by: BLyon July 7, 1980 9:37 AM -- Edited by: HGM January 28, 1981 12:38 AM. DIRECTORY AddressTranslation USING [ AppendNetworkAddress, BadSyntax, StringToSystemElement, StringToNetworkAddress], FTPDefs: FROM "FTPDefs", FTPPrivateDefs: FROM "FTPPrivateDefs", Inline USING [LowHalf], NetworkStream USING [ AssignNetworkAddress, ConnectionFailed, ConnectionSuspended, Create, CreateListener, DeleteListener, FailureReason, FindAddresses, Listen, ListenerHandle, SuspendReason], Process USING [Detach], Socket, Stream USING [ Block, CompletionCode, defaultInputOptions, GetBlock, GetByte, Handle, InputOptions, PutBlock, PutByte, SendNow, SetInputOptions, SetSST, SSTChange, TimeOut], System USING [], Storage USING [Node, Free], SpecialSystem USING [NetworkAddress]; FTPOisCom: PROGRAM IMPORTS AddressTranslation, NetworkStream, Process, Stream, Storage, FTPPrivateDefs, Inline EXPORTS FTPDefs, System SHARES System, SpecialSystem, FTPDefs, FTPPrivateDefs = BEGIN OPEN FTPDefs, FTPPrivateDefs, SpecialSystem; -- **********************! Types !*********************** -- EXPORTED TYPES NetworkAddress: PUBLIC TYPE = SpecialSystem.NetworkAddress; -- ois connection state information OisConnection: TYPE = POINTER TO OisConnectionObject; OisConnectionObject: TYPE = RECORD [ streamHandle: Stream.Handle, subsequenceType: Byte, subsequenceTypeUsed: BOOLEAN, inputDiscontinuity, terminateOnEndPhysicalRecord, thirdPartyClose, closeSent, closeReceived: BOOLEAN, ftpError: FtpError]; -- ois port state information OisPort: TYPE = POINTER TO OisPortObject; OisPortObject: TYPE = RECORD [deactivated: BOOLEAN, pH: PROCESS]; -- **********************! Constants !*********************** -- subsequence types sstOne: Byte = 1; sstOther: Byte = 2; sstClose: Byte = 3; -- miscellaneous parameters checkForAbortSeconds: CARDINAL = 2; networkAddressSeparator: CHARACTER = '#; longHostSeparator: CHARACTER = '|; ftpsystem: POINTER TO FTPSystem = LocateFtpSystemObject[]; communicationPrimitivesObject: CommunicationPrimitivesObject _ [CreateCommunicationSystem: CreateCommunicationSystem, DestroyCommunicationSystem: DestroyCommunicationSystem, OpenConnection: OpenConnection, CloseConnection: CloseConnection, AbortConnection: AbortConnection, ActivatePort: ActivatePort, DeactivatePort: DeactivatePort, SendBytes: SendBytes, ReceiveBytes: ReceiveBytes, SendByte: SendByte, ReceiveByte: ReceiveByte, ProduceDiscontinuity: ProduceDiscontinuity, ConsumeDiscontinuity: ConsumeDiscontinuity, ForceOutput: ForceOutput]; -- **********************! Communication Foothold Procedure !*********************** OISCommunicationPrimitives: PUBLIC PROCEDURE RETURNS [communicationPrimitives: CommunicationPrimitives] = BEGIN -- return communication primitives communicationPrimitives _ @communicationPrimitivesObject; END; -- **********************! Communication Primitives !*********************** CreateCommunicationSystem: PROCEDURE RETURNS [communicationSystem: CommunicationSystem] = BEGIN -- no operation END; DestroyCommunicationSystem: PROCEDURE [ communicationSystem: CommunicationSystem] = BEGIN -- no operation END; OpenConnection: PROCEDURE [ communicationSystem: CommunicationSystem, remoteHost: STRING, remoteSocket: LONG INTEGER, receiveSeconds: CARDINAL] RETURNS [connection: Connection] = BEGIN -- local variables oisConnection: OisConnection; -- allocate and initialize ois connection object oisConnection _ Storage.Node[SIZE[OisConnectionObject]]; oisConnection^ _ OisConnectionObject[ streamHandle:, subsequenceType: sstOther, subsequenceTypeUsed: TRUE, inputDiscontinuity: FALSE, terminateOnEndPhysicalRecord: FALSE, thirdPartyClose: FALSE, closeSent: FALSE, closeReceived: FALSE, ftpError: ok]; -- create network stream to remote server BEGIN ENABLE BEGIN NetworkStream.ConnectionFailed => AbortBecauseConnectionFailed[oisConnection, why]; NetworkStream.ConnectionSuspended => AbortBecauseConnectionSuspended[oisConnection, why]; Stream.TimeOut => AbortBecauseConnectionTimedOut[oisConnection]; UNWIND => Storage.Free[oisConnection]; END; oisConnection.streamHandle _ NetworkStream.Create[ FabricateRemoteAddress[remoteHost, remoteSocket], IF receiveSeconds = LAST[CARDINAL] THEN LAST[LONG CARDINAL] ELSE LONG[receiveSeconds]*LONG[1000]]; END; -- enable -- return connection connection _ LOOPHOLE[oisConnection]; END; CloseConnection: PROCEDURE [ communicationSystem: CommunicationSystem, connection: Connection] = BEGIN -- Note: Called also by OisServerProcess. -- local constants oisConnection: OisConnection = LOOPHOLE[connection]; -- close user/server connection IF ~oisConnection.thirdPartyClose THEN BEGIN -- send and receive close SendClose[oisConnection]; ReceiveClose[oisConnection]; -- close network stream to remote server oisConnection.streamHandle.delete[oisConnection.streamHandle]; -- release ois connection object Storage.Free[oisConnection]; END -- interrupt server connection -- Note: There's a race condition here that must eventually be eliminated. -- The LOOPHOLE that converts connection to oisConnection prevents declaration of -- oisConnectionObject as monitored record and CloseConnection as monitor entry. ELSE SendClose[oisConnection] END; AbortConnection: PROCEDURE [ communicationSystem: CommunicationSystem, connection: Connection, text: STRING] = BEGIN END; ActivatePort: PROCEDURE [ communicationSystem: CommunicationSystem, localSocket: LONG INTEGER, serviceConnection: PROCEDURE [UNSPECIFIED, Connection, STRING], serviceConnectionData: UNSPECIFIED, receiveSeconds: CARDINAL, filter: PROCEDURE [STRING, Purpose], purpose: Purpose] RETURNS [port: Port] = BEGIN -- local variables oisPort: OisPort; -- allocate and initialize ois port object oisPort _ Storage.Node[SIZE[OisPortObject]]; oisPort^ _ OisPortObject[deactivated: FALSE, pH:]; -- fork listener process oisPort.pH _ FORK OisListenerProcess[ communicationSystem, oisPort, localSocket, serviceConnection, serviceConnectionData, receiveSeconds]; -- return port port _ LOOPHOLE[oisPort]; END; OisListenerProcess: PROCEDURE [ communicationSystem: CommunicationSystem, oisPort: OisPort, localSocket: LONG INTEGER, serviceConnection: PROCEDURE [UNSPECIFIED, Connection, STRING], serviceConnectionData: UNSPECIFIED, receiveSeconds: CARDINAL] = BEGIN -- server process OisServerProcess: PROCEDURE [ streamHandle: Stream.Handle, networkAddress: NetworkAddress] = BEGIN -- local constants host: STRING = [maxStringLength]; -- local variables oisConnection: OisConnection _ NIL; -- allocate and initialize ois connection object BEGIN ENABLE ANY => IF ftpsystem.catchUnidentifiedErrors THEN CONTINUE; oisConnection _ Storage.Node[SIZE[OisConnectionObject]]; oisConnection^ _ OisConnectionObject[ streamHandle: streamHandle, subsequenceType: sstOther, subsequenceTypeUsed: TRUE, inputDiscontinuity: FALSE, terminateOnEndPhysicalRecord: FALSE, thirdPartyClose: TRUE, closeSent: FALSE, closeReceived: FALSE, ftpError: ok]; -- dispatch client procedure VerbalizeNetworkAddress[networkAddress, host]; serviceConnection[serviceConnectionData, LOOPHOLE[oisConnection], host]; END; -- enable -- close connection IF oisConnection # NIL THEN BEGIN oisConnection.thirdPartyClose _ FALSE; CloseConnection[communicationSystem, LOOPHOLE[oisConnection]]; END; END; -- local variables listenerHandle: NetworkStream.ListenerHandle; listenerHandleObtained: BOOLEAN _ FALSE; streamHandle: Stream.Handle; networkAddress: NetworkAddress; -- backstop all signals BEGIN ENABLE BEGIN UNWIND => IF listenerHandleObtained THEN NetworkStream.DeleteListener[listenerHandle]; ANY => IF ftpsystem.catchUnidentifiedErrors THEN CONTINUE END; -- create local listener listenerHandle _ NetworkStream.CreateListener[ FabricateLocalAddress[localSocket]]; listenerHandleObtained _ TRUE; -- service local socket UNTIL oisPort.deactivated DO streamHandle _ NetworkStream.Listen[ listenerHandle, LONG[checkForAbortSeconds]*LONG[1000], LONG[receiveSeconds]*LONG[1000]]; IF streamHandle # NIL THEN BEGIN [, networkAddress] _ NetworkStream.FindAddresses[streamHandle]; Process.Detach[FORK OisServerProcess[streamHandle, networkAddress]]; END; ENDLOOP; -- destroy local listener NetworkStream.DeleteListener[listenerHandle]; END; -- enable END; DeactivatePort: PROCEDURE [ communicationSystem: CommunicationSystem, port: Port] = BEGIN -- local constants oisPort: OisPort = LOOPHOLE[port]; -- set deactivation flag oisPort.deactivated _ TRUE; -- join listener process JOIN oisPort.pH; -- release ois port object Storage.Free[oisPort]; END; SendBytes: PROCEDURE [ communicationSystem: CommunicationSystem, connection: Connection, bytePointer: BytePointer] = BEGIN -- local constants oisConnection: OisConnection = LOOPHOLE[connection]; startIndex: CARDINAL = IF bytePointer.offset THEN 1 ELSE 0; -- local variables block: Stream.Block = [blockPointer: LONG[bytePointer.address], startIndex: startIndex, stopIndexPlusOne: startIndex + bytePointer.count]; -- abort if connection troubled IF oisConnection.ftpError # ok THEN Abort[oisConnection.ftpError]; -- send bytes Stream.PutBlock[ oisConnection.streamHandle, block, FALSE ! NetworkStream.ConnectionSuspended => AbortBecauseConnectionSuspended[oisConnection, why]; Stream.TimeOut => AbortBecauseConnectionTimedOut[oisConnection]]; -- note data sent with current subsequence type IF bytePointer.count > 0 THEN oisConnection.subsequenceTypeUsed _ TRUE; -- advance caller's byte pointer AdvanceBytePointer[bytePointer, bytePointer.count]; END; ReceiveBytes: PROCEDURE [ communicationSystem: CommunicationSystem, connection: Connection, bytePointer: BytePointer, settleForLess: BOOLEAN] = BEGIN -- local constants oisConnection: OisConnection = LOOPHOLE[connection]; startIndex: CARDINAL = IF bytePointer.offset THEN 1 ELSE 0; -- local variables inputOptions: Stream.InputOptions _ Stream.defaultInputOptions; block: Stream.Block = [blockPointer: LONG[bytePointer.address], startIndex: startIndex, stopIndexPlusOne: startIndex + bytePointer.count]; bytesTransferred: CARDINAL _ 0; completionCode: Stream.CompletionCode _ normal; sst: Byte; -- return if no operation IF bytePointer.count = 0 THEN RETURN; -- abort if connection troubled IF oisConnection.ftpError # ok THEN Abort[oisConnection.ftpError]; -- abort if input discontinuity IF oisConnection.inputDiscontinuity THEN Abort[inputDiscontinuityUnexpected]; -- adjust input options if necessary IF settleForLess # oisConnection.terminateOnEndPhysicalRecord THEN BEGIN -- select and record new input option inputOptions.terminateOnEndPhysicalRecord _ oisConnection.terminateOnEndPhysicalRecord _ settleForLess; -- instate new input option Stream.SetInputOptions[oisConnection.streamHandle, inputOptions]; END; -- receive bytes [bytesTransferred, completionCode, sst] _ Stream.GetBlock[ oisConnection.streamHandle, block ! NetworkStream.ConnectionSuspended => AbortBecauseConnectionSuspended[oisConnection, why]; Stream.TimeOut => AbortBecauseConnectionTimedOut[oisConnection]]; -- note input discontinuity if any oisConnection.inputDiscontinuity _ completionCode = sstChange; -- note close if any IF completionCode = sstChange AND sst = sstClose THEN CloseReceived[oisConnection]; -- advance caller's byte pointer AdvanceBytePointer[bytePointer, bytesTransferred]; END; SendByte: PROCEDURE [ communicationSystem: CommunicationSystem, connection: Connection, byte: Byte] = BEGIN -- local constants oisConnection: OisConnection = LOOPHOLE[connection]; -- abort if connection troubled IF oisConnection.ftpError # ok THEN Abort[oisConnection.ftpError]; -- send byte Stream.PutByte[ oisConnection.streamHandle, byte ! NetworkStream.ConnectionSuspended => AbortBecauseConnectionSuspended[oisConnection, why]; Stream.TimeOut => AbortBecauseConnectionTimedOut[oisConnection]]; -- note data sent with current subsequence type oisConnection.subsequenceTypeUsed _ TRUE; END; ReceiveByte: PROCEDURE [ communicationSystem: CommunicationSystem, connection: Connection, settleForNone: BOOLEAN] RETURNS [byte: Byte, settledForNone: BOOLEAN] = BEGIN -- local constants oisConnection: OisConnection = LOOPHOLE[connection]; -- abort if connection troubled IF oisConnection.ftpError # ok THEN Abort[oisConnection.ftpError]; -- settle for none or abort if input discontinuity IF oisConnection.inputDiscontinuity THEN IF settleForNone THEN RETURN[byte, TRUE] ELSE Abort[inputDiscontinuityUnexpected]; -- receive byte settledForNone _ FALSE; byte _ Stream.GetByte[ oisConnection.streamHandle ! Stream.SSTChange => BEGIN IF ~settleForNone THEN Abort[inputDiscontinuityUnexpected]; oisConnection.inputDiscontinuity _ settledForNone _ TRUE; IF sst = sstClose THEN CloseReceived[oisConnection]; CONTINUE; END; NetworkStream.ConnectionSuspended => AbortBecauseConnectionSuspended[oisConnection, why]; Stream.TimeOut => AbortBecauseConnectionTimedOut[oisConnection]]; END; ProduceDiscontinuity: PROCEDURE [ communicationSystem: CommunicationSystem, connection: Connection] = BEGIN -- Note: Extra calls to this procedure without intervening data -- are treated as no operations. -- local constants oisConnection: OisConnection = LOOPHOLE[connection]; -- abort if connection troubled IF oisConnection.ftpError # ok THEN Abort[oisConnection.ftpError]; -- no operation if no intervening data IF ~oisConnection.subsequenceTypeUsed THEN RETURN; -- produce output discontinuity oisConnection.subsequenceType _ IF oisConnection.subsequenceType = sstOne THEN sstOther ELSE sstOne; Stream.SetSST[oisConnection.streamHandle, oisConnection.subsequenceType]; -- note no data with current subsequence type oisConnection.subsequenceTypeUsed _ FALSE; END; ConsumeDiscontinuity: PROCEDURE [ communicationSystem: CommunicationSystem, connection: Connection] = BEGIN -- Note: Extra calls to this procedure without intervening data -- are treated as no operations. -- local constants oisConnection: OisConnection = LOOPHOLE[connection]; -- abort if connection troubled IF oisConnection.ftpError # ok THEN Abort[oisConnection.ftpError]; -- consume input discontinuity oisConnection.inputDiscontinuity _ FALSE; END; ForceOutput: PROCEDURE [ communicationSystem: CommunicationSystem, connection: Connection] = BEGIN -- local constants oisConnection: OisConnection = LOOPHOLE[connection]; -- abort if connection troubled IF oisConnection.ftpError # ok THEN Abort[oisConnection.ftpError]; -- force output Stream.SendNow[ oisConnection.streamHandle ! NetworkStream.ConnectionSuspended => AbortBecauseConnectionSuspended[oisConnection, why]; Stream.TimeOut => AbortBecauseConnectionTimedOut[oisConnection]]; END; -- **********************! Host Name Primitives !*********************** FabricateLocalAddress: PROCEDURE [localSocket: LONG INTEGER] RETURNS [networkAddress: NetworkAddress] = BEGIN -- Note: Assumes localSocket representable as a CARDINAL. -- fabricate local address. We know the detailed format Hah hah! networkAddress _ NetworkStream.AssignNetworkAddress[]; networkAddress.socket _ [Shorten[localSocket]]; END; FabricateRemoteAddress: PROCEDURE [ remoteHost: STRING, defaultRemoteSocket: LONG INTEGER] RETURNS [address: NetworkAddress] = BEGIN needsTranslated: BOOLEAN _ FALSE; address _ AddressTranslation.StringToNetworkAddress[ remoteHost ! AddressTranslation.BadSyntax => BEGIN needsTranslated _ TRUE; CONTINUE; END]; IF NOT needsTranslated THEN RETURN; needsTranslated _ FALSE; address _ AddressTranslation.StringToSystemElement[ remoteHost ! AddressTranslation.BadSyntax => BEGIN needsTranslated _ TRUE; CONTINUE; END]; IF needsTranslated THEN Abort[noSuchHost]; address.socket _ [Shorten[defaultRemoteSocket]]; END; VerbalizeNetworkAddress: PROCEDURE [addr: NetworkAddress, name: STRING] = BEGIN name.length _ 0; AddressTranslation.AppendNetworkAddress[name, addr]; END; Shorten: PROCEDURE [long: LONG INTEGER] RETURNS [CARDINAL] = INLINE BEGIN RETURN[Inline.LowHalf[long]]; END; -- **********************! Close Primitives !*********************** SendClose: PROCEDURE [oisConnection: OisConnection] = BEGIN -- no operation if previously sent IF oisConnection.closeSent THEN RETURN; -- send close BEGIN ENABLE BEGIN NetworkStream.ConnectionSuspended => BEGIN oisConnection.ftpError _ SuspendReasonToSignal[why]; oisConnection.closeReceived _ TRUE; CONTINUE; END; Stream.TimeOut => BEGIN oisConnection.ftpError _ connectionTimedOut; oisConnection.closeReceived _ TRUE; CONTINUE; END; END; Stream.SetSST[oisConnection.streamHandle, sstClose]; Stream.PutByte[oisConnection.streamHandle, 0]; Stream.SendNow[oisConnection.streamHandle]; END; -- enable -- record close sent oisConnection.closeSent _ TRUE; END; ReceiveClose: PROCEDURE [oisConnection: OisConnection] = BEGIN -- receive close UNTIL oisConnection.closeReceived DO [] _ Stream.GetByte[ oisConnection.streamHandle ! Stream.SSTChange => BEGIN oisConnection.closeReceived _ sst = sstClose; EXIT; END; NetworkStream.ConnectionSuspended => BEGIN oisConnection.ftpError _ SuspendReasonToSignal[why]; oisConnection.closeSent _ oisConnection.closeReceived _ TRUE; EXIT; END; Stream.TimeOut => BEGIN oisConnection.ftpError _ connectionTimedOut; oisConnection.closeSent _ oisConnection.closeReceived _ TRUE; EXIT; END]; ENDLOOP; -- record close received oisConnection.closeReceived _ TRUE; END; CloseReceived: PROCEDURE [oisConnection: OisConnection] = BEGIN -- record close received oisConnection.closeReceived _ TRUE; -- send close IF ~oisConnection.closeSent THEN BEGIN SendClose[oisConnection]; Abort[oisConnection.ftpError _ connectionClosed]; END; END; -- **********************! Error Subroutines !*********************** AbortBecauseConnectionFailed: PROCEDURE [ oisConnection: OisConnection, failureReason: NetworkStream.FailureReason] = BEGIN -- abort Abort[ oisConnection.ftpError _ SELECT failureReason FROM timeout => connectionTimedOut, ENDCASE => noRouteToNetwork]; -- noRouteToDestination END; AbortBecauseConnectionSuspended: PROCEDURE [ oisConnection: OisConnection, suspendReason: NetworkStream.SuspendReason] = BEGIN -- avoid close protocol oisConnection.closeSent _ oisConnection.closeReceived _ TRUE; -- abort Abort[oisConnection.ftpError _ SuspendReasonToSignal[suspendReason]]; END; AbortBecauseConnectionTimedOut: PROCEDURE [oisConnection: OisConnection] = BEGIN -- abort Abort[oisConnection.ftpError _ connectionTimedOut]; END; SuspendReasonToSignal: PROCEDURE [suspendReason: NetworkStream.SuspendReason] RETURNS [ftpError: FtpError] = BEGIN ftpError _ SELECT suspendReason FROM transmissionTimeout => connectionTimedOut, noRouteToDestination => noRouteToNetwork, ENDCASE => connectionRejected; -- remoteReject END; -- **********************! Main Program !*********************** -- no operation END. -- of FTPOisCom