-- Copyright (C) 1979, 1980, 1984, 1985  by Xerox Corporation. All rights reserved. 
-- FTPUserDump.mesa, HGM, 15-Sep-85 15:45:49  


DIRECTORY
  FTPDefs,
  FTPPrivateDefs,
  Inline USING [DIVMOD],
  Mopcodes USING [zEXCH],
  String USING [AppendChar, AppendString],
  MDSStorage USING [Node, Free],
  Time USING [Append, Packed, Unpack],
  TimeExtra USING [PackedTimeFromString];

FTPUserDump: PROGRAM
  IMPORTS Inline, String, Storage: MDSStorage, Time, FTPPrivateDefs, TimeExtra
  EXPORTS FTPDefs, FTPPrivateDefs
  SHARES FTPDefs, FTPPrivateDefs =
  BEGIN OPEN FTPDefs, FTPPrivateDefs;

  BcplLongNumber: TYPE = RECORD [highbits, lowbits: WORD];
  GMTToBcplLongNumber: PROCEDURE [Time.Packed] RETURNS [BcplLongNumber] = MACHINE
    CODE BEGIN Mopcodes.zEXCH END;

  BcplToGMT: PROCEDURE [BcplLongNumber] RETURNS [Time.Packed] = MACHINE CODE
    BEGIN Mopcodes.zEXCH END;


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

  ftpsystem: POINTER TO FTPSystem = LocateFtpSystemObject[];

  -- **********************!  Dump Primitives  !***********************

  FTPInventoryDumpFile: PUBLIC PROCEDURE [
    ftpuser: FTPUser, remoteDumpFile: STRING, intent: DumpFileIntent,
    processFile: PROCEDURE [UNSPECIFIED, STRING, VirtualFilename, FileInfo],
    processFileData: UNSPECIFIED] =
    BEGIN
    -- local constants
    ftper: FTPer = ftpuser.ftper;
    propertyList: PropertyList = ftpuser.propertyList;
    bufferSize: CARDINAL = maximumDumpBlockSize;
    serverFilename: STRING = [maxStringLength];
    file: STRING = [maxStringLength];
    creationDate: STRING = [maxDateLength];
    -- local variables
    buffer: POINTER ← NIL;
    currentBlockType, byte: Byte;
    tempBPO: BytePointerObject;
    blockByteCount: CARDINAL;
    totalByteCount: LONG INTEGER;
    fileInfoObject: FileInfoObject;
    -- verify purpose and state
    VerifyPurposeAndState[ftpuser, files, connected];
    -- send retrieve command
    PutCommand[ftper, markRetrieve, 0];
    -- construct property list containing absolute and virtual filenames and credentials
    ResetPropertyList[propertyList];
    WriteFilename[
      remoteDumpFile, propertyList, NIL, NIL, ftpuser.primaryPropertyList];
    -- send property list and EOC
    PutPropertyList[ftper, propertyList];
    PutEOC[ftper];
    -- receive property list and EOC
    GetSpecificCommand[ftper, markHereIsPropertyList];
    GetPropertyList[ftper, propertyList];
    GetEOC[ftper];
    IF propertyList[serverFilename] # NIL THEN
      String.AppendString[serverFilename, propertyList[serverFilename]];
    -- request the file and await acknowledgment
    PutCommandAndEOC[ftper, markYes, 0];
    GetSpecificCommand[ftper, markHereIsFile];
    -- erase properties of dump file so they don't confuse us during retrieve
    ResetPropertyList[propertyList];
    -- alter state in anticipation of re-entry
    ftpuser.state ← inventoryingDumpFile;
    ftpuser.intent ← intent;
    ftpuser.nextBlockType ← 0;
    BEGIN
    ENABLE
      UNWIND =>
        BEGIN
        IF buffer # NIL THEN Storage.Free[buffer];
        ftpuser.state ← connected;
        END;
    -- allocate buffer for data blocks
    buffer ← Storage.Node[bufferSize];
    -- inventory dump file
    DO
      -- consume block type
      IF ftpuser.nextBlockType # 0 THEN
        BEGIN
        currentBlockType ← ftpuser.nextBlockType;
        ftpuser.nextBlockType ← 0;
        END
        -- receive block type
        -- Note:  Block type doesn't contribute to accumulated file size.

      ELSE
        BEGIN
        totalByteCount ← ftper.totalByteCount;
        currentBlockType ← ReceiveByte[ftper];
        ftper.totalByteCount ← totalByteCount;
        END;
      -- decode block type
      SELECT currentBlockType FROM
        blockName =>
          BEGIN
          -- Note:  Name doesn't contribute to accumulated file size.
          totalByteCount ← ftper.totalByteCount;
          [] ← ReceiveWord[ftper];  -- file attributes
          -- receive filename
          file.length ← creationDate.length ← 0;
          UNTIL (byte ← ReceiveByte[ftper]) = 0 DO
            String.AppendChar[file, LOOPHOLE[byte, CHARACTER]]; ENDLOOP;
          -- peek ahead for date block
          ftpuser.nextBlockType ← ReceiveByte[ftper];
          IF ftpuser.nextBlockType = blockDate THEN
            BEGIN
            date: RECORD [creation: BcplLongNumber, trash: WORD];
            temp: Time.Packed;
            ftpuser.nextBlockType ← 0;
            tempBPO ← [@date, FALSE, 6];
            ReceiveBytes[ftper, @tempBPO];
            temp ← BcplToGMT[date.creation];
            Time.Append[creationDate, Time.Unpack[temp], TRUE];
            END;
          ftper.totalByteCount ← totalByteCount;
          -- present file to caller for processing
          WriteProperty[propertyList, serverFilename, file];
          WriteProperty[propertyList, creationDate, creationDate];
          ReadFileInfo[propertyList, @fileInfoObject];
          processFile[processFileData, file, NIL, @fileInfoObject];
          END;
        blockDate =>
          BEGIN
          date: PACKED ARRAY [0..6) OF Byte;
          -- flush date
          -- Note:  Date doesn't contribute to accumulated file size.
          totalByteCount ← ftper.totalByteCount;
          tempBPO ← [@date, FALSE, 6];
          ReceiveBytes[ftper, @tempBPO];
          ftper.totalByteCount ← totalByteCount;
          END;
        blockData =>
          BEGIN
          transmittedChecksum: CARDINAL;
          checksumStateObject: ChecksumStateObject;
          -- receive block header
          -- Note:  Header doesn't contribute to accumulated file size.
          totalByteCount ← ftper.totalByteCount;
          blockByteCount ← ReceiveWord[ftper];
          transmittedChecksum ← ReceiveWord[ftper];
          ftper.totalByteCount ← totalByteCount;
          -- verify block length
          IF blockByteCount > maximumDumpBlockSize THEN
            Abort[dumpFileBlockTooLong];
          -- receive block
          tempBPO ← [buffer, FALSE, blockByteCount];
          ReceiveBytes[ftper, @tempBPO];
          -- checksum block length and block
          checksumStateObject ← [
            checksum: blockByteCount, anyExcessByte: FALSE, excessByte:];
          tempBPO ← [buffer, FALSE, blockByteCount];
          ChecksumBytes[@checksumStateObject, @tempBPO];
          IF checksumStateObject.checksum # transmittedChecksum THEN
            Abort[dumpFileCheckSumInError];
          END;
        blockError => Abort[errorBlockInDumpFile];
        blockEnd => EXIT;
        ENDCASE => Abort[unrecognizedDumpFileBlock];
      ENDLOOP;
    -- receive Yes and EOC
    WriteProperty[propertyList, serverFilename, serverFilename];
    GetSpecificCommand[ftper, markYes];
    FinishMultiFileOperation[ftpuser];
    -- release buffer and restore state

    END;  -- enable
    Storage.Free[buffer];
    ftpuser.state ← connected;
    END;

  FTPBeginDumpFile: PUBLIC PROCEDURE [ftpuser: FTPUser, remoteDumpFile: STRING] =
    BEGIN OPEN ftpuser;
    -- local variables
    fileInfoObject: FileInfoObject ← [
      fileType: binary, byteSize: 8, byteCount: 0, creationDate: NIL,
      writeDate: NIL, readDate: NIL, author: NIL];
    -- verify purpose and state
    VerifyPurposeAndState[ftpuser, files, connected];
    -- send store command
    PutCommand[ftper, markNewStore, 0];
    -- construct property list containing absolute and virtual filenames, credentials, and file information
    ResetPropertyList[propertyList];
    WriteFilename[remoteDumpFile, propertyList, NIL, NIL, primaryPropertyList];
    WriteFileInfo[propertyList, @fileInfoObject];
    -- send property list and EOC
    PutPropertyList[ftper, propertyList];
    PutEOC[ftper];
    -- sustain remote store
    GetSpecificCommand[ftper, markHereIsPropertyList];
    GetPropertyList[ftper, propertyList];
    GetEOC[ftper];
    -- signal transmission of file
    PutCommand[ftper, markHereIsFile, 0];
    -- note dump in progress
    state ← dumpFileBeingSent;
    END;

  FTPEndDumpFile: PUBLIC PROCEDURE [ftpuser: FTPUser] =
    BEGIN OPEN ftpuser;
    -- verify purpose and state
    VerifyPurposeAndState[ftpuser, files, dumpFileBeingSent];
    -- send end block
    SendByte[ftper, blockEnd];
    -- send Yes and EOC
    PutCommandAndEOC[ftper, markYes, 0];
    -- receive acknowledgment
    GetYesAndEOC[ftper];
    -- reset state
    state ← connected;
    END;

  -- **********************!  Dump Send/Receive Primitives  !***********************

  SendHeaderBlock: PUBLIC PROCEDURE [
    dumpState: DumpState, fileName, creation: STRING] =
    BEGIN
    ftper: FTPer ← dumpState.ftper;
    totalByteCount: LONG CARDINAL;
    bytePointerObject: BytePointerObject;
    totalByteCount ← ftper.totalByteCount;
    SendByte[ftper, blockName];
    SendWord[ftper, 0];  -- File attributes
    bytePointerObject ← [@fileName.text, FALSE, fileName.length];
    SendBytes[ftper, @bytePointerObject];
    SendByte[ftper, 0];
    IF creation.length # 0 THEN
      BEGIN
      date: RECORD [creation: BcplLongNumber, trash: WORD];
      date.creation ← GMTToBcplLongNumber[
        TimeExtra.PackedTimeFromString[creation]];
      date.trash ← 0;
      SendByte[ftper, blockDate];
      bytePointerObject ← [@date, FALSE, 6];
      SendBytes[ftper, @bytePointerObject];
      END;
    ftper.totalByteCount ← totalByteCount;
    END;

  DumpBlock: PUBLIC PROCEDURE [
    dumpState: DumpState, source: LONG POINTER, byteCount: CARDINAL] =
    BEGIN OPEN dumpState;
    -- Note:  byteCount=0 signifies end of file; block lengths are in the range
    --   [minimumDumpBlockSize..maximumDumpBlockSize] for Nova compatibility.
    -- local variables
    bufferBPO: BytePointerObject ← [bufferAddress, FALSE, bufferLength];
    callerBPO: BytePointerObject ← [source, FALSE, byteCount];
    tempBPO: BytePointerObject;
    availableByteCount, blockByteCount, bufferByteCount, callerByteCount:
      CARDINAL;
    checksumStateObject: ChecksumStateObject;
    totalByteCount: LONG INTEGER;
    -- send as many blocks as possible
    DO
      -- select block length
      -- Note:  The following logic assures that neither this nor subsequent blocks
      --   will violate the length constraints.
      availableByteCount ← bufferBPO.count + callerBPO.count;
      blockByteCount ←
        SELECT TRUE FROM
          (availableByteCount >= maximumDumpBlockSize + minimumDumpBlockSize) =>
            maximumDumpBlockSize,
          (byteCount = 0 AND availableByteCount <= maximumDumpBlockSize) =>
            availableByteCount,
          (byteCount = 0) => availableByteCount - minimumDumpBlockSize,
          ENDCASE => 0;
      IF blockByteCount = 0 THEN EXIT;
      bufferByteCount ← MIN[blockByteCount, bufferBPO.count];
      callerByteCount ← blockByteCount - bufferByteCount;
      -- checksum block length and block
      checksumStateObject ← [
        checksum: blockByteCount, anyExcessByte: FALSE, excessByte:];
      tempBPO ← [bufferBPO.address, bufferBPO.offset, bufferByteCount];
      ChecksumBytes[@checksumStateObject, @tempBPO];
      tempBPO ← [callerBPO.address, callerBPO.offset, callerByteCount];
      ChecksumBytes[@checksumStateObject, @tempBPO];
      -- send block header
      -- Note:  Header doesn't contribute to accumulated file size.
      totalByteCount ← ftper.totalByteCount;
      SendByte[ftper, blockData];
      SendWord[ftper, blockByteCount];
      SendWord[ftper, checksumStateObject.checksum];
      ftper.totalByteCount ← totalByteCount;
      -- send block
      tempBPO ← [bufferBPO.address, bufferBPO.offset, bufferByteCount];
      SendBytes[ftper, @tempBPO];
      tempBPO ← [callerBPO.address, callerBPO.offset, callerByteCount];
      SendBytes[ftper, @tempBPO];
      -- consume sent data
      AdvanceBytePointer[@bufferBPO, bufferByteCount];
      AdvanceBytePointer[@callerBPO, callerByteCount];
      ENDLOOP;
    -- left-adjust buffer contents
    tempBPO ← [bufferAddress, FALSE, bufferLength ← bufferBPO.count];
    IF bufferBPO # tempBPO THEN TransferBytes[@bufferBPO, @tempBPO];
    -- buffer remaining caller data
    bufferLength ← bufferLength + (tempBPO.count ← callerBPO.count);
    TransferBytes[@callerBPO, @tempBPO];
    END;

  LoadBlock: PUBLIC PROCEDURE [
    dumpState: DumpState, destination: LONG POINTER, maxWordCount: CARDINAL]
    RETURNS [actualByteCount: CARDINAL] =
    BEGIN OPEN dumpState;
    -- Note:  actualByteCount=0 signifies end of file.
    -- local variables
    bufferBPO: BytePointerObject ← [bufferAddress, FALSE, bufferLength];
    callerBPO: BytePointerObject ← [
      destination, FALSE, bytesPerWord * maxWordCount];
    tempBPO: BytePointerObject;
    blockByteCount, bufferByteCount, callerByteCount, transmittedChecksum:
      CARDINAL;
    checksumStateObject: ChecksumStateObject;
    totalByteCount: LONG INTEGER;
    date: PACKED ARRAY [0..6) OF Byte;
    -- return on end-of-file
    IF blockType = blockName OR blockType = blockEnd THEN RETURN[0];
    -- deliver buffered data to caller
    TransferBytes[@bufferBPO, @callerBPO];
    -- left-adjust buffer's remaining contents
    tempBPO ← [bufferAddress, FALSE, bufferLength ← bufferBPO.count];
    TransferBytes[@bufferBPO, @tempBPO];
    bufferBPO ← tempBPO;
    -- deliver received data to caller
    UNTIL callerBPO.count = 0 DO
      -- receive block type
      -- Note:  Block type doesn't contribute to accumulated file size.
      totalByteCount ← ftper.totalByteCount;
      blockType ← ReceiveByte[ftper];
      ftper.totalByteCount ← totalByteCount;
      -- decode block type
      SELECT blockType FROM
        blockName => EXIT;
        blockDate =>
          BEGIN
          -- flush date
          -- Note:  Date doesn't contribute to accumulated file size.
          totalByteCount ← ftper.totalByteCount;
          tempBPO ← [@date, FALSE, 6];
          ReceiveBytes[ftper, @tempBPO];
          ftper.totalByteCount ← totalByteCount;
          END;
        blockData =>
          BEGIN
          -- receive block header
          -- Note:  Header doesn't contribute to accumulated file size.
          totalByteCount ← ftper.totalByteCount;
          blockByteCount ← ReceiveWord[ftper];
          transmittedChecksum ← ReceiveWord[ftper];
          ftper.totalByteCount ← totalByteCount;
          -- select block length
          IF blockByteCount > maximumDumpBlockSize THEN
            Abort[dumpFileBlockTooLong];
          callerByteCount ← MIN[blockByteCount, callerBPO.count];
          bufferByteCount ← blockByteCount - callerByteCount;
          bufferLength ← bufferLength + bufferByteCount;
          -- receive block
          tempBPO ← [callerBPO.address, callerBPO.offset, callerByteCount];
          ReceiveBytes[ftper, @tempBPO];
          tempBPO ← [bufferBPO.address, bufferBPO.offset, bufferByteCount];
          ReceiveBytes[ftper, @tempBPO];
          -- checksum block length and block
          checksumStateObject ← [
            checksum: blockByteCount, anyExcessByte: FALSE, excessByte:];
          tempBPO ← [callerBPO.address, callerBPO.offset, callerByteCount];
          ChecksumBytes[@checksumStateObject, @tempBPO];
          tempBPO ← [bufferBPO.address, bufferBPO.offset, bufferByteCount];
          ChecksumBytes[@checksumStateObject, @tempBPO];
          IF checksumStateObject.checksum # transmittedChecksum THEN
            Abort[dumpFileCheckSumInError];
          -- produce received data
          AdvanceBytePointer[@callerBPO, callerByteCount];
          AdvanceBytePointer[@bufferBPO, bufferByteCount];
          END;
        blockError => Abort[errorBlockInDumpFile];
        blockEnd => EXIT;
        ENDCASE => Abort[unrecognizedDumpFileBlock];
      ENDLOOP;
    -- compute actual byte count for caller
    actualByteCount ← bytesPerWord * maxWordCount - callerBPO.count;
    END;

  -- **********************!  Checksum Primitive  !***********************

  ChecksumBytes: PROCEDURE [
    checksumState: ChecksumState, srcBytePointer: BytePointer] =
    BEGIN OPEN checksumState;
    -- NextByte procedure
    NextByte: PROCEDURE RETURNS [byte: Byte] =
      BEGIN
      -- consume excess byte
      IF anyExcessByte THEN BEGIN anyExcessByte ← FALSE; byte ← excessByte; END
        -- consume source byte

      ELSE byte ← LoadByte[sBP];
      -- decrement byte count
      sBP.count ← sBP.count - 1;
      END;
    -- local constants
    sBP: BytePointer = srcBytePointer;
    -- local variables
    wordCount: CARDINAL;
    dWordObject: WordObject;
    -- include excess byte in count
    IF anyExcessByte THEN sBP.count ← sBP.count + 1;
    -- checksum all but new excess byte
    UNTIL sBP.count < bytesPerWord DO
      IF ~anyExcessByte AND ~sBP.offset THEN
        BEGIN
        [wordCount, sBP.count] ← Inline.DIVMOD[sBP.count, bytesPerWord];
        THROUGH [0..wordCount) DO
          checksum ← checksum + sBP.address↑;
          sBP.address ← sBP.address + 1;
          ENDLOOP;
        END
      ELSE
        BEGIN
        dWordObject.lhByte ← NextByte[];
        dWordObject.rhByte ← NextByte[];
        checksum ← checksum + LOOPHOLE[dWordObject, CARDINAL];
        END;
      ENDLOOP;
    -- update excess byte
    IF anyExcessByte ← (sBP.count > 0) THEN excessByte ← NextByte[];
    END;

  LoadByte: PUBLIC PROCEDURE [srcBytePointer: BytePointer] RETURNS [byte: Byte] =
    INLINE
    BEGIN
    -- Note:  Doesn't check for byte pointer exhaustion.
    -- local constants
    sBP: BytePointer = srcBytePointer;
    sWord: Word = sBP.address;
    -- load byte
    byte ← IF sBP.offset THEN sWord.rhByte ELSE sWord.lhByte;
    -- advance address and offset
    IF ~(sBP.offset ← ~sBP.offset) THEN sBP.address ← sBP.address + 1;
    -- decrement byte count
    sBP.count ← sBP.count - 1;
    END;

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

  -- no operation

  END. -- of FTPUserDump