-- 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