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