-- Copyright (C) 1981, 1982, 1983  by Xerox Corporation. All rights reserved. 
-- file: [Igor]<Emerson>STP>Private>STPsC.mesa - Simple/Stream Transfer Protocol 
-- Remote Stream stuff in here
-- Edited by:
-- SXW   	16-Jul-81 17:24:59
-- JGS    	17-Aug-81 11:32:55
-- PXK    	 9-Dec-81 18:36:06
-- SXS   	11-Sep-81 16:15:34
-- BXM  		20-Oct-81 11:24:17
-- RXJ     	16-Dec-81 17:39:42
-- AXD    	21-Sep-82 13:06:31
-- BJD    	 9-Sep-83 16:25:30

DIRECTORY
  Heap USING [systemZone],
  PupStream USING [CloseReason, StreamClosing],
  STP USING [Access, CredentialsErrors, Error, FileErrors, Type],
  STPOps USING [
    CheckConnection, ErrorIfNextNotEOC, ErrorIfNextNotYes, GetCommand,
    GenerateProtocolError, GenerateStreamClosingError, GetHereIsAndPList, Handle,
    GetSSTNop, LookAtMark, MakeRemoteName, markEOC, markHereIsFile,
    markHereIsPList, markNewStore, markNo, markRetrieve, markYes, MyGetMark,
    NameToPList, Object, ResetPList, PList, PutCommand, PutPList, RemoteObject,
    RemoteStream, SelectError, SetByteSize, SetCreateTime, SetFileType, SmashClosed,
    UserStateToPList, SetSSTNop, WaitAttentionNop],
  Stream USING [
    Block, Byte, CompletionCode, defaultObject, DeleteProcedure, EndOfStream,
    GetBlock, GetProcedure, GetSSTProcedure, GetTimeoutProcedure, Handle,
    InputOptions, InvalidOperation, Object, PutBlock, PutByte, PutProcedure, SendNow,
    SendNowProcedure, SendAttentionProcedure, SetSST, SetTimeoutProcedure, SSTChange,
    SubSequenceType, Word],
  String USING [Replace],
  Time USING [Packed];
  
STPsC: PROGRAM
  IMPORTS Heap, PupStream, String, STP, STPOps, Stream
  EXPORTS STP, STPOps =
  BEGIN OPEN PupStream, STPOps;
  
  z: UNCOUNTED ZONE = Heap.systemZone;
  
  Object: PUBLIC TYPE = STPOps.Object;
  
-- Procedures implementing STP interface 

  CreateRemoteStream: PUBLIC PROCEDURE [
    stp: STPOps.Handle, file: LONG STRING, access: STP.Access, fileType: STP.Type,
    options: Stream.InputOptions, creation: Time.Packed]
    RETURNS [stream: Stream.Handle] =
    BEGIN
    rs: STPOps.RemoteStream;
    CheckConnection[stp];
    IF stp.remoteStream # NIL THEN ERROR STP.Error[stp, accessError, "Remote Stream Exists"L];
    rs ← z.NEW[STPOps.RemoteObject];
    rs↑ ← STPOps.RemoteObject[
      access: access, state: initial, stp: stp,
      stream: [
        options: options, get: GetBlock, put: PutBlock, getByte: GetByte,
	putByte: PutByte, getWord: GetWord, putWord: PutWord,
	getSST: STPOps.GetSSTNop, setSST: STPOps.SetSSTNop,
        sendAttention: SendAttentionAbort,
	waitAttention: STPOps.WaitAttentionNop, sendNow: SendNow,
	delete: DeleteRemoteStream, getPosition: Stream.defaultObject.getPosition,
	setPosition: Stream.defaultObject.setPosition,
	getTimeout: GetTimeout, setTimeout: SetTimeout,
	clientData: NIL]]; 
    ResetPList[stp.plist];
    UserStateToPList[stp];
    NameToPList[stp.plist, file, alto];
    SetFileType[stp, fileType];
    IF access = write THEN {
      STPOps.SetCreateTime[stp, creation];
      SetByteSize[stp, fileType]};
    IF fileType = text THEN String.Replace[@stp.plist[eolConversion], "CR"L, z];
    stp.remoteStream ← rs;
    RETURN[@rs.stream]
    END;
    
  NextFileName: PUBLIC PROCEDURE [remoteStream: Stream.Handle]
    RETURNS [file: LONG STRING] =
    BEGIN
    rs: RemoteStream ← ConvertHandle[remoteStream];
    reason: PupStream.CloseReason;
    IF rs.access # read THEN ERROR STP.Error[rs.stp, accessError, NIL];
    BEGIN ENABLE PupStream.StreamClosing => {reason ← why; GOTO streamClosing}; 
    SELECT rs.state FROM
      initial => RequestRetrieve[rs];
      confirm =>
	BEGIN
	PutCommand[rs.stp, markNo, 0C, "No Thanks"L];
	IF LookAtMark[rs.stp] = markEOC THEN {[] ←  MyGetMark[rs.stp]; rs.state ← end}
	ELSE GetHereIsAndPList[rs.stp];
	END;
      data =>
        BEGIN
	ErrorIfNextNotYes[rs.stp];
	IF LookAtMark[rs.stp] = markEOC THEN {[] ←  MyGetMark[rs.stp]; rs.state ← end}
	ELSE {GetHereIsAndPList[rs.stp]; rs.state ← confirm};
	END;
      complete =>
	IF LookAtMark[rs.stp] = markEOC THEN {[] ←  MyGetMark[rs.stp]; rs.state ← end}
	ELSE {GetHereIsAndPList[rs.stp]; rs.state ← confirm};
      end => NULL;
      ENDCASE;
    EXITS
      streamClosing => GenerateStreamClosingError[rs.stp, reason];
    END;
    RETURN[IF rs.state = end THEN NIL ELSE MakeRemoteName[rs.stp.plist, alto]]
    END;
    
-- Procedures for Remote streams  

  conversionFudge: LONG CARDINAL = LOOPHOLE[@LOOPHOLE[LONG[0], RemoteStream].stream];
  
  ConvertHandle: PROCEDURE [h: Stream.Handle] RETURNS [rs: RemoteStream] = INLINE
    BEGIN RETURN[LOOPHOLE[h - conversionFudge]] END;
    
  DeleteRemoteStream: PUBLIC Stream.DeleteProcedure =
    BEGIN
    rs: RemoteStream ← ConvertHandle[sH];
    stp: STPOps.Handle = rs.stp;
    {ENABLE UNWIND => {stp.remoteStream ← NIL; z.FREE[@rs]};
    IF rs.access = read THEN {IF rs.state # end THEN SmashClosed[stp]} -- in mid-transfer
    ELSE SELECT rs.state FROM
      initial => NULL;
      data => {
	PutCommand[rs.stp, markYes, 0C, "Transfer Completed"L
	  ! PupStream.StreamClosing => CONTINUE]; -- OK, if closed in advance
	ErrorIfNextNotYes[rs.stp];
	ErrorIfNextNotEOC[rs.stp]};
      confirm, complete, end => NULL; -- worry about later
      ENDCASE;
    stp.remoteStream ← NIL; z.FREE[@rs]};
    END;
    
  SendAttentionAbort: PUBLIC Stream.SendAttentionProcedure = 
    BEGIN
 -- To be called by client when a store is aborted, followed by DeleteRemoteStream; 
    rs: RemoteStream ← ConvertHandle[sH];
    stp: STPOps.Handle = rs.stp;
    IF rs.access = write THEN {
      ENABLE PupStream.StreamClosing => GOTO return; -- OK, if closed in advance
      SELECT rs.state FROM
	data => {
          string: LONG STRING ← NIL;
	  mark: Stream.SubSequenceType;
	  code: CHAR ← 0C;
	  PutCommand[rs.stp, markNo, 106C, "Transfer Aborted"L];
	  [mark, code] ← STPOps.GetCommand[stp, @string];
	  z.FREE[@string];
	  IF mark # markNo THEN
	    STPOps.GenerateProtocolError[stp, badMark, mark, code];
	  ErrorIfNextNotEOC[rs.stp]};
	initial, confirm, complete, end => NULL;
	ENDCASE;
      rs.state ← end
      EXITS
        return => NULL}
    END;
  
  GetByte: PUBLIC PROCEDURE [sH: Stream.Handle] RETURNS [byte: Stream.Byte] =
    BEGIN
    options: Stream.InputOptions = [FALSE, FALSE, FALSE, TRUE, TRUE, TRUE];
    rs: RemoteStream ← ConvertHandle[sH];
    bytesTransferred: CARDINAL;
    charArray: PACKED ARRAY [0..1] OF Stream.Byte;
    block: Stream.Block ← [@charArray, 0, 1];
    [bytesTransferred,,] ← GetBlock[sH, block, options];
    RETURN[IF bytesTransferred # 0 THEN charArray[0] ELSE 0]
    END;
    
  GetWord: PUBLIC PROCEDURE [sH: Stream.Handle] RETURNS [word: Stream.Word] =
    BEGIN
    t: UNSPECIFIED = CARDINAL[GetByte[sH]]*256;
    RETURN[t + GetByte[sH]];
    END;
    
  GetBlock: PUBLIC Stream.GetProcedure =
    BEGIN
    rs: RemoteStream ← ConvertHandle[sH];
    reason: PupStream.CloseReason;
    BEGIN
    ENABLE PupStream.StreamClosing => {reason ← why; GOTO streamClosing};
    IF rs.state # data THEN
      BEGIN
      StartRemote[rs, read];
      IF rs.state = end THEN GOTO done;
      END;
    [bytesTransferred, why, sst] ← Stream.GetBlock[ rs.stp.byteStream, block !
      Stream.EndOfStream => {bytesTransferred ← nextIndex; GOTO done};
      Stream.SSTChange => {
	rs.stp.mark ← sst;
	rs.stp.gotMark ← TRUE;
	bytesTransferred ← nextIndex - block.startIndex;
	CONTINUE}];
    IF rs.stp.gotMark THEN {SetupForNextOrEnd[rs.stp]; GOTO done};
    EXITS
      streamClosing => {rs.state ← end; GenerateStreamClosingError[rs.stp, reason]};
      done =>
	BEGIN
	why ← endOfStream;
	IF options.signalEndOfStream THEN
	  Stream.EndOfStream[nextIndex: bytesTransferred];
	END;
    END;
    END;
    
  PutByte: PUBLIC PROCEDURE [sH: Stream.Handle, byte: Stream.Byte] =
    BEGIN
    rs: RemoteStream ← ConvertHandle[sH];
    reason: PupStream.CloseReason;
    BEGIN
    ENABLE PupStream.StreamClosing => {reason ← why; GOTO streamClosing};
    IF rs.state # data THEN StartRemote[rs, write];
    Stream.PutByte[rs.stp.byteStream, byte];
    EXITS streamClosing => {rs.state ← end; GenerateStreamClosingError[rs.stp, reason]};
    END;
    END;
    
  PutWord: PUBLIC PROCEDURE [sH: Stream.Handle, word: Stream.Word] =
    BEGIN
    PutByte[sH, CARDINAL[word]/256];
    PutByte[sH, CARDINAL[word] MOD 256];
    END;
    
  PutBlock: PUBLIC Stream.PutProcedure =
    BEGIN
    rs: RemoteStream ← ConvertHandle[sH];
    reason: PupStream.CloseReason;
    BEGIN
    ENABLE PupStream.StreamClosing => {reason ← why; GOTO streamClosing};
    IF rs.state # data THEN StartRemote[rs, write];
    Stream.PutBlock[rs.stp.byteStream, block, FALSE];
    EXITS streamClosing => {rs.state ← end; GenerateStreamClosingError[rs.stp, reason]};
    END;
    END;
    
  SendNow: PUBLIC Stream.SendNowProcedure =
    BEGIN
    rs: RemoteStream ← ConvertHandle[sH];
    reason: PupStream.CloseReason;
    BEGIN
    ENABLE PupStream.StreamClosing => {reason ← why; GOTO streamClosing};
    IF rs.state # data THEN StartRemote[rs, write];
    Stream.SendNow[rs.stp.byteStream, TRUE];
    EXITS streamClosing => {rs.state ← end; GenerateStreamClosingError[rs.stp, reason]};
    END;
    END;
    
  GetTimeout: PUBLIC Stream.GetTimeoutProcedure =
    BEGIN
    ERROR Stream.InvalidOperation
    END;
    
  SetTimeout: PUBLIC Stream.SetTimeoutProcedure =
    BEGIN
    ERROR Stream.InvalidOperation
    END;
    
    
    
    
-- Utilities  

  SetupForNextOrEnd: PROCEDURE [stp: STPOps.Handle] =
    BEGIN
    ErrorIfNextNotYes[stp];
    SELECT LookAtMark[stp] FROM
      markEOC => {[] ← MyGetMark[stp]; stp.remoteStream.state ← end};
      markHereIsPList => stp.remoteStream.state ← complete;
      ENDCASE => GenerateProtocolError[stp, badMark, MyGetMark[stp]];
    END;
    
  RequestRetrieve: PROCEDURE [rs: RemoteStream] =
    BEGIN
    PutPList[rs.stp, markRetrieve];
    GetHereIsAndPList[rs.stp ! STP.Error =>
      SELECT code FROM
	IN STP.CredentialsErrors, IN STP.FileErrors  => rs.state ← end;
	ENDCASE];
    rs.state ← confirm;
    END;
    
  StartRemote: PROCEDURE [rs: RemoteStream, access: STP.Access] =
    BEGIN
    reason: PupStream.CloseReason;
    IF rs.access # access THEN
      ERROR STP.Error[rs.stp, accessError, "Remote stream access Error"L];
    BEGIN
    ENABLE PupStream.StreamClosing => {reason ← why; GOTO streamClosing};
    SELECT access FROM
      read =>
	BEGIN
	IF rs.state = initial THEN RequestRetrieve[rs]; 
	IF rs.state = complete THEN {
	  IF LookAtMark[rs.stp] = markEOC THEN {rs.state ← end; RETURN}
	  ELSE GetHereIsAndPList[rs.stp]; rs.state ← confirm};
	IF rs.state = confirm THEN 
	  BEGIN
	  mark: Stream.SubSequenceType;
	  PutCommand[rs.stp, markYes, 0C, "Yes, please"L];
	  SELECT (mark ← MyGetMark[rs.stp]) FROM
	    markHereIsFile => NULL;
	    markNo => {rs.state ← complete; SelectError[rs.stp, "He says NO"L, mark]};
	    ENDCASE => SelectError[rs.stp, "HereIsFile Expected"L, mark];
	  END;
	END;
      write =>
	BEGIN
	PutPList[rs.stp, markNewStore];
	GetHereIsAndPList[rs.stp];
	Stream.SetSST[rs.stp.byteStream, markHereIsFile];
	END;
      ENDCASE => ERROR;
    rs.state ← data;
    EXITS streamClosing => GenerateStreamClosingError[rs.stp, reason];
    END;
    END;
    
  END. of STPsC