-- file: STPsA.mesa - Simple/Stream Transfer Protocol
-- Initialization and Error stuff in here
--  Edited by:
-- Smokey on: 12-Mar-81 13:23:33
-- Karlton on: Oct 26, 1980 4:07 PM
-- Evans on: Nov 14, 1980 9:35 AM

DIRECTORY
  Format USING [Octal, StringProc],
  HeapString USING [AppendString],
  STP USING [ defaultOptions, Error, ErrorCode, FileInfoObject],
  STPOps USING [
    CollectCode, CollectString, ErrorIfNextNotEOC, GetCommand, GetServerType,
    Handle, markComment, markIAmVersion, markNo, maxStringLength, Object,
    PList, PListArray, ProtocolError, PutCommand, UserProperties, ValidProperties],
  STPReplyCode,
  PupStream USING [
    CloseReason, GetPupAddress, PupAddress, PupByteStreamCreate, PupNameTrouble,
    PupPackageDestroy, PupPackageMake, StreamClosing, veryLongWait],
  PupTypes USING [fillInHostID, fillInNetID],
  Stream USING [
    GetProcedure, Handle, InputOptions, PutProcedure, SendAttentionProcedure,
    SetSSTProcedure, SubSequenceType, WaitAttentionProcedure],
  Storage USING [
    EmptyString, Free, FreeNodeNil, FreeString, FreeStringNil, Node, String, StringLength];
    
STPsA: PROGRAM
  IMPORTS Format, HeapString, PupStream, Storage, STP, STPOps
  EXPORTS STP, STPOps =
  
  BEGIN OPEN PupStream, STPOps;
  
  -- Global Data
  
  Error: PUBLIC SIGNAL [
    code: STP.ErrorCode, error: STRING, reply: CHARACTER ← 0C] = CODE;
  Object: PUBLIC TYPE = STPOps.Object;
  
  -- 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[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];
    stp.info ← Storage.FreeNodeNil[stp.info];
    stp.remoteString ← Storage.FreeStringNil[stp.remoteString];
    IF stp.byteStream # NIL THEN
      BEGIN
      stp.byteStream.delete[stp.byteStream ! PupStream.StreamClosing => CONTINUE];
      stp.byteStream ← NIL;
      END;
    END;
    
  Create: PUBLIC PROCEDURE RETURNS [stp: STPOps.Handle] =
    BEGIN OPEN PupTypes;
    ENABLE UNWIND => Storage.Free[stp];
    stp ← Storage.Node[SIZE[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];
    stp.host ← Storage.FreeStringNil[stp.host];
    FOR i: STPOps.UserProperties IN STPOps.UserProperties DO
      stp.userState[i] ← Storage.FreeStringNil[stp.userState[i]]; ENDLOOP;
    Storage.Free[stp];
    PupStream.PupPackageDestroy[];
    RETURN[NIL];
    END;
    
  Open: PUBLIC PROCEDURE [stp: STPOps.Handle, host: STRING]
    RETURNS [herald: STRING] =
    BEGIN
    reason: PupStream.CloseReason;
    IF stp = NIL THEN RETURN[NIL];
    IF stp.byteStream # NIL THEN
      ERROR Error[alreadyAConnection, "You already have a connection?"L];
    BEGIN
    server: PupStream.PupAddress ← [PupTypes.fillInNetID, PupTypes.fillInHostID, [0, 3]];
    server.socket ← [0, 3];
    PupStream.GetPupAddress[
      @server, host !
      PupStream.PupNameTrouble => GenerateErrorString[noSuchHost, e]];
    stp.byteStream ← PupStream.PupByteStreamCreate[
      server, PupStream.veryLongWait !
      PupStream.StreamClosing => {reason ← why; GOTO streamClosing}];
    stp.byteStream.options ← STP.defaultOptions;
    stp.remoteString ← Storage.String[maxStringLength];
    stp.plist ← MakePList[];
    stp.info ← Storage.Node[SIZE[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 ← Storage.String[80];
    BEGIN ENABLE UNWIND => Storage.Free[herald];
    code: CHARACTER;
    mark: Stream.SubSequenceType;
    [mark, code] ← GetCommand[stp, @herald];
    IF mark # markIAmVersion THEN GenerateProtocolError[badVersion, mark, code];
    ErrorIfNextNotEOC[stp];
    END;
    RETURN[herald];
    EXITS streamClosing => {CloseInternal[stp]; GenerateStreamClosingError[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 Storage.FreeString[plist[i]]; ENDLOOP;
      Storage.Free[plist];
      END;
    RETURN[NIL]
    END;
    
  MakePList: PUBLIC PROCEDURE RETURNS [plist: PList] =
    BEGIN
    i: STPOps.ValidProperties;
    plist ← Storage.Node[SIZE[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] = {OPEN STPReplyCode;
    RETURN[SELECT code 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 [
    errorCode: STP.ErrorCode, string: STRING, code: CHARACTER ← 0C] =
    BEGIN
    ERROR Error[
      ErrorCodeToSTPErrorCode[errorCode, code],
      IF Storage.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 [why: PupStream.CloseReason] =
    BEGIN
    GenerateErrorString[
      SELECT why FROM
	localClose, remoteClose => connectionClosed,
	noRouteToNetwork => noRouteToNetwork,
	transmissionTimeout => connectionTimedOut,
	remoteReject => connectionRejected,
	ENDCASE => ERROR, NIL];
    END;
    
  GenerateProtocolError: PUBLIC PROCEDURE [
    type: ProtocolError, mark: Stream.SubSequenceType, code: CHARACTER ← 0C] =
    BEGIN
    string: STRING ← NIL;
    MyAppend: Format.StringProc =
      BEGIN HeapString.AppendString[to: @string, from: s]; END;
    HeapString.AppendString[
      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];
    HeapString.AppendString[to: @string, from: ", mark ="L];
    Format.Octal[mark, MyAppend];
    HeapString.AppendString[to: @string, from: ", code ="L];
    Format.Octal[code, MyAppend];
    ERROR Error[
      protocolError, string, code ! UNWIND => Storage.FreeString[string]];
    END;
    
  SelectError: PUBLIC PROCEDURE [
    stp: STPOps.Handle, s: 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];
      ErrorIfNextNotEOC[stp];
      GenerateErrorString[
	requestRefused,
	IF Storage.EmptyString[stp.remoteString] THEN s ELSE stp.remoteString,
	code];
      END
    ELSE GenerateProtocolError[badMark, mark, code];
    END;
    
  -- NOP and ERROR Stream routines
  
  GetError: PUBLIC Stream.GetProcedure = {
    ERROR STP.Error[accessError, "Attempt to Get from a store stream"L]};
    
  PutError: PUBLIC Stream.PutProcedure = {
    ERROR STP.Error[accessError, "Attempt to Put on a retrieve stream"L]};
    
  SetSSTNop: PUBLIC Stream.SetSSTProcedure = BEGIN END;
  
  SendAttentionNop: PUBLIC Stream.SendAttentionProcedure = BEGIN END;
  
  WaitAttentionNop: PUBLIC Stream.WaitAttentionProcedure = BEGIN RETURN[0B] END;
  
  END. -- of STPsA