-- Copyright (C) 1981, 1984  by Xerox Corporation. All rights reserved. 
-- file: [Igor]<Emerson>STP>STPsA.mesa - Simple/Stream Transfer Protocol
-- Initialization and Error stuff in here
--  Edited by:
-- SXW   ,	16-Jul-81 14:54:32
-- PXK    ,	 9-Dec-81 18:23:36
-- SXE  ,	Nov 14, 1980 9:35 AM
-- BJD    ,	26-Mar-84 11:42:39
-- AOF, 	17-Jan-85  9:04:42

DIRECTORY
  Format USING [Octal, StringProc],
  Heap USING [systemZone],
  String USING [AppendString, AppendStringAndGrow, EmptyString, StringLength],
  STP USING [Error, ErrorCode, FileInfoObject],
  STPOps USING [
    CollectCode, CollectString, ErrorIfNextNotEOC, GetCommand, GetServerType,
    Handle, markComment, markIAmVersion, markNo, maxStringLength, Object,
    PList, PListArray, ProtocolError, PutCommand, UserProperties, ValidProperties],
  STPReplyCode USING [ReplyCode],
  PupStream USING [
    CloseReason, GetPupAddress, PupAddress, PupByteStreamCreate, PupNameTrouble,
    PupPackageDestroy, PupPackageMake, StreamClosing, veryLongWait],
  PupTypes USING [fillInHostID, fillInNetID],
  Stream USING [
    GetProcedure, GetSSTProcedure, Handle, InputOptions, PutProcedure,
    SetSSTProcedure, SubSequenceType, WaitAttentionProcedure];
    
STPsA: PROGRAM
  IMPORTS Format, Heap, PupStream, String, STP, STPOps
  EXPORTS STP, STPOps =
  
  BEGIN OPEN PupStream, STPOps;
  
  -- Global Data
  
  Error: PUBLIC SIGNAL [
  stp: STPOps.Handle, code: STP.ErrorCode, error: LONG STRING, reply: CHARACTER ← 0C] = CODE;
  Object: PUBLIC TYPE = STPOps.Object;
  z: UNCOUNTED ZONE = Heap.systemZone;
  
  -- Public Interface Routines 
  
  Close: PUBLIC PROCEDURE [stp: STPOps.Handle] =
    BEGIN
    nilByteStream: BOOLEAN;
    IF stp = NIL THEN RETURN;
    nilByteStream ← stp.byteStream = NIL;
    CloseInternal[stp];
    IF nilByteStream THEN
      ERROR Error[stp: stp, code: noConnection, error: "Attempt to Close a NIL connection"L];
    END;
    
  CloseInternal: PUBLIC PROCEDURE [stp: STPOps.Handle] =
    BEGIN
    IF stp = NIL THEN RETURN;
    stp.plist ← DestroyPList[stp.plist];
    z.FREE[@stp.info];
    z.FREE[@stp.remoteString];
    IF stp.byteStream # NIL THEN
      BEGIN
      s: Stream.Handle = stp.byteStream;
      stp.byteStream ← NIL;
      s.delete[s ! PupStream.StreamClosing => CONTINUE];
      END;
    END;
    
  Create: PUBLIC PROCEDURE RETURNS [stp: STPOps.Handle] =
    BEGIN OPEN PupTypes;
    ENABLE UNWIND => z.FREE[@stp];
    stp ← z.NEW[STPOps.Object];
    [] ← PupStream.PupPackageMake[];
    stp↑ ← STPOps.Object[];
    RETURN[stp];
    END;
    
  Destroy: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [Handle] =
    BEGIN
    IF stp = NIL THEN RETURN[NIL];
    IF stp.byteStream # NIL THEN Close[stp];
    z.FREE[@stp.host];
    FOR i: STPOps.UserProperties IN STPOps.UserProperties DO
      z.FREE[@stp.userState[i]]; ENDLOOP;
    z.FREE[@stp];
    PupStream.PupPackageDestroy[];
    RETURN[NIL];
    END;
    
  Open: PUBLIC PROCEDURE [stp: STPOps.Handle, host: LONG STRING]
    RETURNS [herald: LONG STRING] =
    BEGIN
    reason: PupStream.CloseReason;
    IF stp = NIL THEN RETURN[NIL];
    IF stp.byteStream # NIL THEN
      ERROR Error[stp, alreadyAConnection, "You already have a connection?"L];
    BEGIN
    shortHost: STRING ← [40];
    server: PupStream.PupAddress ← [PupTypes.fillInNetID, PupTypes.fillInHostID, [0, 3]];
    stpOptions: Stream.InputOptions = [
      signalAttention: TRUE,
      terminateOnEndRecord: FALSE,
      signalLongBlock: FALSE,
      signalShortBlock: FALSE,
      signalSSTChange: TRUE,
      signalEndOfStream: TRUE];
    server.socket ← [0, 3];
    String.AppendString[shortHost, host];
    PupStream.GetPupAddress[
      @server, shortHost !
      PupStream.PupNameTrouble => GenerateErrorString[stp, noSuchHost, e]];
    stp.byteStream ← PupStream.PupByteStreamCreate[
      server, PupStream.veryLongWait !
      PupStream.StreamClosing => {reason ← why; GOTO streamClosing}];
    stp.byteStream.options ← stpOptions;
    stp.remoteString ← z.NEW[StringBody[maxStringLength]];
    stp.plist ← MakePList[];
    stp.info ← z.NEW[STP.FileInfoObject];
    stp.info↑ ← [];
    stp.gotMark ← FALSE;
    stp.serverType ← GetServerType[host];
    PutCommand[stp, markIAmVersion, 1C, "STP calling"L !
      PupStream.StreamClosing => {reason ← why; GOTO streamClosing}];
    herald ← z.NEW[StringBody[80]];
    BEGIN ENABLE UNWIND => z.FREE[@herald];
    code: CHARACTER;
    mark: Stream.SubSequenceType;
    [mark, code] ← GetCommand[stp, @herald !
      PupStream.StreamClosing => {reason ← why; GOTO streamClosing}];
    IF mark # markIAmVersion THEN GenerateProtocolError[stp, badVersion, mark, code];
    ErrorIfNextNotEOC[stp];
    END;
    RETURN[herald];
    EXITS streamClosing => {CloseInternal[stp]; GenerateStreamClosingError[stp, reason]};
    END;
    END;
    
  -- PList Utilities
  
  DestroyPList: PUBLIC PROCEDURE [plist: PList] RETURNS [PList] =
    BEGIN
    i: STPOps.ValidProperties;
    IF plist # NIL THEN
      BEGIN
      FOR i IN STPOps.ValidProperties DO z.FREE[@plist[i]]; ENDLOOP;
      z.FREE[@plist];
      END;
    RETURN[NIL]
    END;
    
  MakePList: PUBLIC PROCEDURE RETURNS [plist: PList] =
    BEGIN
    i: STPOps.ValidProperties;
    plist ← z.NEW[PListArray];
    FOR i IN STPOps.ValidProperties DO plist[i] ← NIL; ENDLOOP;
    END;
    
  -- Error generation routines
  
  ErrorCodeToSTPErrorCode: PUBLIC PROCEDURE [
    errorCode: STP.ErrorCode, code: CHARACTER]
    RETURNS [STP.ErrorCode] = {
    replyCode: STPReplyCode.ReplyCode = LOOPHOLE[code];
    RETURN[SELECT replyCode FROM
      null => errorCode,
      badCommand => protocolError,
      noUserName => illegalUserName,
      illegalCommand => protocolError,
      badPList => protocolError,
      illegalServerFilename => illegalFileName,
      illegalDirectory => illegalFileName,
      illegalNameBody => illegalFileName,
      illegalVersion => illegalFileName,
      illegalType => accessError,
      illegalCharacterSize => accessError,
      illegalEOLConversion => accessError,
      illegalUserName => illegalUserName,
      illegalUserPassword => illegalUserPassword,
      illegalUserAccount => illegalUserAccount,
      illegalConnectName => illegalConnectName,
      illegalConnectPassword => illegalConnectPassword,
      illegalCreationDate => illegalFileName,
      illegalWriteDate => illegalFileName,
      illegalReadDate => illegalFileName,
      illegalAuthor => illegalFileName,
      illegalDevice => illegalFileName,
      fileNotFound => noSuchFile,
      accessDenied => accessDenied,
      inconsistent => protocolError,
      fileDataError => errorCode,
      tooLong => errorCode,
      dontSend => errorCode,
      notCompleted => errorCode,
      transientError => errorCode,
      permanentError => errorCode,
      fileBusy => errorCode,
      ENDCASE => errorCode]  -- can't do any better--};
      
  GenerateErrorString: PUBLIC PROCEDURE [
    stp: STPOps.Handle, errorCode: STP.ErrorCode, string: LONG STRING , code: CHARACTER ← 0C] =
    BEGIN
    ERROR Error[stp, 
      ErrorCodeToSTPErrorCode[errorCode, code],
      IF String.StringLength[string] # 0 THEN string
      ELSE
	SELECT errorCode FROM
	  noSuchHost => "No such host"L,
	  noRouteToNetwork => "No route to network"L,
	  noNameLookupResponse => "Name lookup server is not responding"L,
	  alreadyAConnection => "You already have a connection"L,
	  noConnection => "Please open a connection"L,
	  connectionClosed => "Connection closed (local or remote)"L,
	  connectionRejected => "Connection rejected by remote host"L,
	  connectionTimedOut => "Connection timed out"L,
	  accessDenied => "Access denied by remote server"L,
	  illegalUserName => "Invalid or illegal UserName"L,
	  illegalUserPassword => "Invalid or illegal UserPassword"L,
	  illegalUserAccount => "Invalid or illegal UserAccount"L,
	  illegalConnectName => "Invalid or illegal ConnectName"L,
	  illegalConnectPassword => "Invalid or illegal ConnectPassword"L,
	  credentailsMissing => "Name and/or Password not supplied"L,
	  protocolError => "Internal FTP protocol error"L,
	  illegalFileName => "Illegal filename"L,
	  noSuchFile => "File not found"L,
	  requestRefused => "Request refused by remote host"L,
	  accessError => "Illegal access attempt on remote stream"L,
	  undefinedError => "Undefined error"L,
	  ENDCASE => ERROR, code];
	  
    END;
    
  GenerateStreamClosingError: PUBLIC PROCEDURE [stp: STPOps.Handle, why: PupStream.CloseReason] =
    BEGIN
    GenerateErrorString[stp, 
      SELECT why FROM
	localClose, remoteClose => connectionClosed,
	noRouteToNetwork => noRouteToNetwork,
	transmissionTimeout => connectionTimedOut,
	remoteReject => connectionRejected,
	ENDCASE => ERROR, NIL];
    END;
    
  GenerateProtocolError: PUBLIC PROCEDURE [
    stp: STPOps.Handle, type: ProtocolError, mark: Stream.SubSequenceType, code: CHARACTER ← 0C] =
    BEGIN
    string: LONG STRING  ← NIL;
    MyAppend: Format.StringProc =
      BEGIN String.AppendStringAndGrow[to: @string, from: s, z: z]; END;
    String.AppendStringAndGrow[
      to: @string,
      from:
      SELECT type FROM
	badVersion => "Incompatable protocol version"L,
	badMark => "Invalid or undefined mark byte"L,
	badPList => "Invalid or malformed property list"L,
	eocExpected => "End-Of-Command mark byte expected"L,
	noCode => "error code is required after error mark byte"L,
	ENDCASE => ERROR,
       z: z];
    String.AppendStringAndGrow[to: @string, from: ", mark ="L, z: z];
    Format.Octal[MyAppend, mark];
    String.AppendStringAndGrow[to: @string, from: ", code ="L, z: z];
    Format.Octal[MyAppend, code];
    ERROR Error[stp, 
      protocolError, string, code ! UNWIND => z.FREE[@string]];
    END;
    
  SelectError: PUBLIC PROCEDURE [
    stp: STPOps.Handle, s: LONG STRING , mark: Stream.SubSequenceType] =
    BEGIN
    code: CHARACTER ← 0C;
    IF mark = markNo OR mark = markComment THEN
      BEGIN
      IF mark # markComment THEN code ← CollectCode[stp];
      CollectString[stp, @stp.remoteString];
      GenerateErrorString[
	stp, requestRefused,
	IF String.EmptyString[stp.remoteString] THEN s ELSE stp.remoteString,
	code];
      END
    ELSE GenerateProtocolError[stp, badMark, mark, code];
    END;
    
  -- NOP and ERROR Stream routines
  
  GetError: PUBLIC Stream.GetProcedure = {
    ERROR STP.Error[NIL, accessError, "Attempt to Get from a store stream"L]};
    
  PutError: PUBLIC Stream.PutProcedure = {
    ERROR STP.Error[NIL, accessError, "Attempt to Put on a retrieve stream"L]};
    
  SetSSTNop: PUBLIC Stream.SetSSTProcedure = BEGIN END;
  
  GetSSTNop: PUBLIC Stream.GetSSTProcedure = BEGIN RETURN[0B] END;
  
  WaitAttentionNop: PUBLIC Stream.WaitAttentionProcedure = BEGIN RETURN[0B] END;
  
  END. -- of STPsA