-- FTPPupComCool.mesa, Edit: HGM July 31, 1980  5:49 PM  

-- Copyright  Xerox Corporation 1979, 1980

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

FTPPupComCool: PROGRAM
  IMPORTS PupStream, Stream, Storage, 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,
  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
    PupStream.PupPackageMake[];
  END;

DestroyCommunicationSystem: PROCEDURE [communicationSystem: CommunicationSystem] =
  BEGIN
  -- destroy pup package
    PupStream.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
      PupStream.PupNameTrouble => AbortBecauseNameLookupFailed[code, e];
      PupStream.StreamClosing    => AbortBecauseStreamClosing[why, text];
      Stream.TimeOut               => Abort[connectionTimedOut];
      UNWIND                         => Storage.Free[pupConnection];
      END; 
    PupStream.GetPupAddress[@pupAddress, remoteHost];
  -- create network stream to remote server
    pupConnection.streamHandle ←
      PupStream.PupByteStreamCreate[
        pupAddress,
        IF receiveSeconds = LAST[CARDINAL] THEN PupStream.veryLongWait
        ELSE PupStream.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;

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
          PupStream.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];
        PupStream.AppendPupAddress[remoteHost, pupAddress];
        filter[remoteHost, purpose !
            RejectThisConnection => PupStream.RejectThisRequest[error] ];
        END;
      pupListener ←
        PupStream.CreatePupByteStreamListener[pupLocalSocket, PupServerProcess,
          PupStream.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: STRING] =
  BEGIN
  -- local constants
    ftpError: FtpError = SELECT nameLookupErrorCode FROM
      noRoute     => noRouteToNetwork,
      noResponse => noNameLookupResponse,
      ENDCASE   => noSuchHost; -- errorFromServer
  -- abort
    AbortWithExplanation[ftpError, message];
  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