-- FTPPupComCool.mesa, Edit: HGM December 16, 1980  1:56 AM  

-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  FTPDefs,
  FTPPrivateDefs,
  FTPPupComDefs,
  PupDefs USING [
    AppendPupAddress, GetPupAddress, NameLookupErrorCode, PupNameTrouble,
    PupPackageDestroy, PupPackageMake, SecondsToTocks, veryLongWait],
  PupStream USING [
    CloseReason, CreatePupByteStreamListener, DestroyPupListener, PupAddress,
    PupByteStreamAbort, PupByteStreamCreate, PupListener, PupSocketID,
    RejectThisRequest, StreamClosing],
  PupTypes USING [fillInHostID, fillInNetID],
  Stream USING [Handle, TimeOut],
  MDSStorage USING [Node, Free];

FTPPupComCool: PROGRAM
  IMPORTS
    Stream, Storage: MDSStorage, PupDefs, PupStream, FTPDefs, FTPPrivateDefs,
    FTPPupComDefs
  EXPORTS FTPDefs, FTPPupComDefs
  SHARES FTPDefs =
  BEGIN OPEN FTPDefs, FTPPrivateDefs;


  PupConnection: TYPE = FTPPupComDefs.PupConnection;
  PupConnectionObject: TYPE = FTPPupComDefs.PupConnectionObject;

  -- pup port state information
  PupPort: TYPE = POINTER TO PupPortObject;
  PupPortObject: TYPE = RECORD [deactivated: EventObject, pH: PROCESS];

  -- **********************!  Constants  !***********************

  ftpsystem: POINTER TO FTPSystem = LocateFtpSystemObject[];

  communicationPrimitivesObject: CommunicationPrimitivesObject ← [
    CreateCommunicationSystem: CreateCommunicationSystem,
    DestroyCommunicationSystem: DestroyCommunicationSystem,
    OpenConnection: OpenConnection, CloseConnection: CloseConnection,
    AbortConnection: AbortConnection, ActivatePort: ActivatePort,
    DeactivatePort: DeactivatePort, SendBytes: FTPPupComDefs.SendBytes,
    ReceiveBytes: FTPPupComDefs.ReceiveBytes, SendByte: FTPPupComDefs.SendByte,
    ReceiveByte: FTPPupComDefs.ReceiveByte,
    ProduceDiscontinuity: FTPPupComDefs.ProduceDiscontinuity,
    ConsumeDiscontinuity: FTPPupComDefs.ConsumeDiscontinuity,
    ForceOutput: FTPPupComDefs.ForceOutput];

  -- **********************!  Communication Foothold Procedure  !***********************

  -- Note:  These primitives use the Pup Package in such a way as to
  --   maintain compatibility with Maxc, IFS, and Juniper file systems.

  PupCommunicationPrimitives, SomeCommunicationPrimitives: PUBLIC PROCEDURE
    RETURNS [communicationPrimitives: CommunicationPrimitives] =
    BEGIN
    -- return communication primitives
    communicationPrimitives ← @communicationPrimitivesObject;
    END;

  -- **********************!  Communication Primitives  !***********************

  CreateCommunicationSystem: PROCEDURE
    RETURNS [communicationSystem: CommunicationSystem] =
    BEGIN PupDefs.PupPackageMake[]; END;

  DestroyCommunicationSystem: PROCEDURE [
    communicationSystem: CommunicationSystem] =
    BEGIN
    -- destroy pup package
    PupDefs.PupPackageDestroy[];
    END;

  OpenConnection: PROCEDURE [
    communicationSystem: CommunicationSystem, remoteHost: STRING,
    remoteSocket: LONG INTEGER, receiveSeconds: CARDINAL]
    RETURNS [connection: Connection] =
    BEGIN
    -- local constants
    -- Note:  The transformation below assumes intimate knowledge
    --   of Mesa's LONG INTEGER implementation.
    longInteger: ARRAY {lob, hob} OF CARDINAL = LOOPHOLE[remoteSocket];
    pupRemoteSocket: PupStream.PupSocketID = [longInteger[hob], longInteger[lob]];
    -- local variables
    pupConnection: PupConnection;
    pupAddress: PupStream.PupAddress ← [
      net: PupTypes.fillInNetID, host: PupTypes.fillInHostID,
      socket: pupRemoteSocket];
    -- allocate and initialize pup connection object
    pupConnection ← Storage.Node[SIZE[PupConnectionObject]];
    pupConnection↑ ← PupConnectionObject[
      streamHandle:, thirdPartyClose: FALSE, inputDiscontinuity: FALSE,
      outputDiscontinuity: FALSE, inputDiscontinuityConsumed: FALSE,
      terminateOnEndPhysicalRecord: FALSE, mark:];
    -- locate remote server
    BEGIN
    ENABLE
      BEGIN
      PupDefs.PupNameTrouble => AbortBecauseNameLookupFailed[code, e];
      PupStream.StreamClosing => {
        string: STRING ← [40];
        IF text # NIL THEN {
          string.length ← MIN[string.maxlength, text.length];
          FOR i: CARDINAL IN [0..string.length) DO string[i] ← text[i] ENDLOOP}
        ELSE string ← NIL;
        AbortBecauseStreamClosing[why, string]};
      Stream.TimeOut => Abort[connectionTimedOut];
      UNWIND => Storage.Free[pupConnection];
      END;
    PupDefs.GetPupAddress[@pupAddress, remoteHost];
    -- create network stream to remote server
    pupConnection.streamHandle ← PupStream.PupByteStreamCreate[
      pupAddress,
      IF receiveSeconds = LAST[CARDINAL] THEN PupDefs.veryLongWait
      ELSE PupDefs.SecondsToTocks[receiveSeconds]];
    END;  -- enable
    -- return connection
    connection ← LOOPHOLE[pupConnection];
    END;

  CloseConnection: PROCEDURE [
    communicationSystem: CommunicationSystem, connection: Connection] =
    BEGIN
    -- local constants
    pupConnection: PupConnection = LOOPHOLE[connection];
    -- close network stream to remote server
    pupConnection.streamHandle.delete[pupConnection.streamHandle];
    -- release pup connection object
    IF ~pupConnection.thirdPartyClose THEN Storage.Free[pupConnection]
    ELSE pupConnection.streamHandle ← NIL;
    END;

  AbortConnection: PROCEDURE [
    communicationSystem: CommunicationSystem, connection: Connection,
    text: STRING] =
    BEGIN
    pupConnection: PupConnection = LOOPHOLE[connection];
    PupStream.PupByteStreamAbort[pupConnection.streamHandle, text];
    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
    pupPort: PupPort;
    -- allocate pup port object
    pupPort ← Storage.Node[SIZE[PupPortObject]];
    -- prepare deactivation event
    PrepareEvent[@pupPort.deactivated];
    -- fork listener process
    pupPort.pH ← FORK PupListenerProcess[
      pupPort, localSocket, serviceConnection, serviceConnectionData,
      receiveSeconds, filter, purpose];
    -- return port
    port ← LOOPHOLE[pupPort];
    END;

  PupListenerProcess: PROCEDURE [
    pupPort: PupPort, localSocket: LONG INTEGER,
    serviceConnection: PROCEDURE [UNSPECIFIED, Connection, STRING],
    serviceConnectionData: UNSPECIFIED, receiveSeconds: CARDINAL,
    filter: PROCEDURE [STRING, Purpose], purpose: Purpose] =
    BEGIN
    -- server process
    PupServerProcess: PROCEDURE [
      streamHandle: Stream.Handle, pupAddress: PupStream.PupAddress] =
      BEGIN
      -- local constants
      remoteHost: STRING = [maxStringLength];
      -- local variables
      pupConnection: PupConnection ← NIL;
      -- allocate and initialize pup connection object
      BEGIN
      ENABLE ANY => IF ftpsystem.catchUnidentifiedErrors THEN CONTINUE;
      pupConnection ← Storage.Node[SIZE[PupConnectionObject]];
      pupConnection↑ ← PupConnectionObject[
        streamHandle: streamHandle, thirdPartyClose: TRUE,
        inputDiscontinuity: FALSE, outputDiscontinuity: FALSE,
        inputDiscontinuityConsumed: FALSE, terminateOnEndPhysicalRecord: FALSE,
        mark:];
      -- identify remote host
      PupDefs.AppendPupAddress[remoteHost, pupAddress];
      -- dispatch client procedure
      serviceConnection[
        serviceConnectionData, LOOPHOLE[pupConnection], remoteHost];
      END;  -- enable
      -- close connection
      IF pupConnection # NIL THEN
        BEGIN
        -- close network stream to remote server
        IF pupConnection.streamHandle # NIL THEN
          pupConnection.streamHandle.delete[pupConnection.streamHandle];
        -- release pup connection object
        Storage.Free[pupConnection];
        END;
      END;
    -- local constants
    -- Note:  The transformation below assumes intimate knowledge
    --   of Mesa's LONG INTEGER implementation.
    longInteger: ARRAY {lob, hob} OF CARDINAL = LOOPHOLE[localSocket];
    pupLocalSocket: PupStream.PupSocketID = [longInteger[hob], longInteger[lob]];
    -- local variables
    pupListener: PupStream.PupListener;
    -- backstop all signals
    BEGIN
    ENABLE ANY => IF ftpsystem.catchUnidentifiedErrors THEN CONTINUE;
    CheckHim: PROCEDURE [pupAddress: PupStream.PupAddress] =
      BEGIN
      remoteHost: STRING = [20];
      PupDefs.AppendPupAddress[remoteHost, pupAddress];
      filter[
        remoteHost, purpose !
        RejectThisConnection => PupStream.RejectThisRequest[error]];
      END;
    pupListener ← PupStream.CreatePupByteStreamListener[
      pupLocalSocket, PupServerProcess, PupDefs.SecondsToTocks[receiveSeconds],
      CheckHim];
    -- await deactivation
    AwaitEvent[@pupPort.deactivated];
    -- deactivate listener
    PupStream.DestroyPupListener[pupListener];
    END;  -- enable

    END;

  DeactivatePort: PROCEDURE [
    communicationSystem: CommunicationSystem, port: Port] =
    BEGIN
    -- local constants
    pupPort: PupPort = LOOPHOLE[port];
    -- post deactivation event
    PostEvent[@pupPort.deactivated];
    -- join listener process
    JOIN pupPort.pH;
    -- release pup port object
    Storage.Free[pupPort];
    END;


  -- **********************!  Error Subroutines  !***********************

  AbortBecauseNameLookupFailed: PROCEDURE [
    nameLookupErrorCode: PupDefs.NameLookupErrorCode, message: LONG STRING] =
    BEGIN
    -- local constants
    ftpError: FtpError =
      SELECT nameLookupErrorCode FROM
        noRoute => noRouteToNetwork,
        noResponse => noNameLookupResponse,
        ENDCASE => noSuchHost;  -- errorFromServer
    string: STRING ← [40];
    IF message # NIL THEN {
      string.length ← MIN[string.maxlength, message.length];
      FOR i: CARDINAL IN [0..string.length) DO string[i] ← message[i] ENDLOOP;
      message ← string}
    ELSE string ← NIL;
    -- abort
    AbortWithExplanation[ftpError, string];
    END;

  AbortBecauseStreamClosing: PUBLIC PROCEDURE [
    closeReason: PupStream.CloseReason, message: STRING] =
    BEGIN
    -- local constants
    ftpError: FtpError =
      SELECT closeReason FROM
        noRouteToNetwork => noRouteToNetwork,
        transmissionTimeout => connectionTimedOut,
        remoteReject => connectionRejected,
        ENDCASE => connectionClosed;  -- localClose/remoteClose
    -- abort
    AbortWithExplanation[ftpError, message];
    END;


  -- **********************!  Main Program  !***********************

  -- no operation

  END. -- of FTPPupComCool