-- SocketImpl.mesa (last edited by: BLyon on: March 26, 1981  10:13 AM)
-- Function: The implementation module for Pilot OISCP Socket Channels. 

DIRECTORY
  BufferDefs USING [
    BufferAccessHandle, FreeBufferPool, MakeBufferPool, OisBuffer, QueueInitialize,
    QueueCleanup, QueueObject],
  CommunicationInternal USING [],
  CommUtilDefs USING [MaybeShorten, EnableAborts],
  DriverDefs USING [PutOnGlobalDoneQueue],
  Heap USING [MakeNode, FreeNode],
  Inline USING [LowHalf],
  OISCP USING [
    DequeueOis, uniqueAddress, unknownHostID, unknownNetID, unknownSocketID],
  Process USING [
    InitializeCondition, MsecToTicks, SecondsToTicks, SetTimeout, Ticks],
  Router USING [
    AddSocket, AssignOisAddress, BroadcastThisPacket, FindDestinationRelativeNetID,
    FindMyHostID, RemoveSocket, socketRouterLock, SendPacket, XmitStatus],
  Socket USING [defaultWaitTime, SocketStatus, WaitTime],
  SocketInternal USING [SocketHandle, SocketObject],
  SpecialSystem USING [HostNumber, NetworkAddress, NetworkNumber, nullNetworkAddress],
  System USING [GetClockPulses, Pulses, NetworkAddress, MicrosecondsToPulses];

SocketImpl: MONITOR LOCKS Router.socketRouterLock
  IMPORTS
    BufferDefs, DriverDefs, CommUtilDefs, Heap, Inline, OISCP,
    Process, Router, System
  EXPORTS
    CommunicationInternal, Socket, SocketInternal,
    -- others --System
  SHARES BufferDefs =
  BEGIN OPEN OISCP, Socket, SocketInternal;

  -- EXPORTED TYPES and READONLY Variables
  ChannelHandle: PUBLIC TYPE = SocketInternal.SocketHandle;
  NetworkAddress: PUBLIC TYPE = SpecialSystem.NetworkAddress; -- others
  uniqueNetworkAddr: PUBLIC System.NetworkAddress ←
    SpecialSystem.nullNetworkAddress;

  -- All socket are covered by one lock.  We must be careful when we go to multiple
  -- MDSs as we must make sure to specify where the lock really is, i.e. in outerspace.

  -- constants for various kinds of buffer pools
  -- local copies for speed
  myHostID: SpecialSystem.HostNumber;

  -- Signals and Errors
  TimeOut: PUBLIC ERROR = CODE;
  ChannelAborted: PUBLIC ERROR = CODE;
  ChannelError: PUBLIC ERROR = CODE;

  -- Hot Procedures

  GetPulsesIntervalTime: PROCEDURE [startTime: System.Pulses]
    RETURNS [LONG CARDINAL] = INLINE
    BEGIN
    RETURN[System.GetClockPulses[] - startTime];
    END; -- GetPulsesIntervalTime

  -- This procedure puts the buffer containing a packet out from this socket channel.
  -- The send is asynchronous;   the caller owns the buffer  and gets it back from the 
  -- system via the dispatcher using b.requeueProcedure.
  PutPacket: PUBLIC PROCEDURE [cH: ChannelHandle, b: BufferDefs.OisBuffer] =
    BEGIN

    PutLocked: ENTRY PROCEDURE = INLINE
      BEGIN
      IF cH.channelState # active THEN RETURN WITH ERROR ChannelAborted;
      b.status ← LOOPHOLE[Router.XmitStatus[goodCompletion]];
      -- assume all will go OK
      END; -- PutLocked

    -- fix up the buffer
    PutLocked[];
    b.ois.source ← cH.localAddr;
    IF b.ois.source.net = unknownNetID THEN
      b.ois.source.net ← Router.FindDestinationRelativeNetID[b.ois.destination.net];
    IF b.ois.destination.host = unknownHostID OR
      b.ois.destination.socket = unknownSocketID THEN
      BEGIN
      b.status ← LOOPHOLE[Router.XmitStatus[invalidDestAddr]]; -- this is safe!
      DriverDefs.PutOnGlobalDoneQueue[b];
      RETURN;
      END;
    Router.SendPacket[b];
    END; -- PutPacket

  -- This procedure puts the buffer containing a packet out from this socket channel.
  -- This packet will go out to all  hosts on all connected networks.
  -- The send is asynchronous;  the caller owns the buffer  and gets it back from the 
  -- system via the dispatcher using b.requeueProcedure.
  PutPacketToAllConnectedNets: PUBLIC PROCEDURE [cH: ChannelHandle, b: BufferDefs.OisBuffer] =
    BEGIN

    PutLocked: ENTRY PROCEDURE = INLINE
      BEGIN
      IF cH.channelState # active THEN RETURN WITH ERROR ChannelAborted;
      b.status ← LOOPHOLE[Router.XmitStatus[goodCompletion]];
      -- assume all will go OK
      END; -- PutLocked

    -- fix up the buffer
    PutLocked[];
    b.ois.source ← cH.localAddr;
    IF b.ois.destination.socket = unknownSocketID THEN
      BEGIN
      b.status ← LOOPHOLE[Router.XmitStatus[invalidDestAddr]]; -- this is safe!
      DriverDefs.PutOnGlobalDoneQueue[b];
      RETURN;
      END;
    Router.BroadcastThisPacket[b];
    END; -- PutPacketToAllConnectedNets

  -- This procedure blocks until a buffer appears the socket's completedUserGetQueue. 

  GetPacket: PUBLIC ENTRY PROCEDURE [sH: ChannelHandle] RETURNS [b: BufferDefs.OisBuffer] =
    BEGIN ENABLE UNWIND => NULL;
    startTime: System.Pulses ← System.GetClockPulses[];
    IF (sH.channelState = aborted) THEN RETURN WITH ERROR ChannelAborted;
    WHILE (b ← DequeueOis[@sH.completedUserGetQueue]) = NIL DO
      IF GetPulsesIntervalTime[startTime] > sH.waitTime THEN
	RETURN WITH ERROR TimeOut;
      WAIT sH.newUserInput; -- propogate ERROR ABORTED
      IF (sH.channelState = aborted) THEN RETURN WITH ERROR ChannelAborted;
      ENDLOOP;
    END;

  --Cool Procedures

  -- This procedure assigns a temporary socket number in a NetworkAddress.

  AssignNetworkAddress: PUBLIC PROCEDURE RETURNS [System.NetworkAddress] =
    BEGIN RETURN[Router.AssignOisAddress[]]; END; -- AssignNetworkAddress

  -- This procedure makes a socket channel with the specified socket number,
  -- and makes the right kind of buffer pool for the socket.  If the value of local is
  -- uniqueAddress then an unused address is assigned to the socket.
  -- Some day we will check to see that the socket number requested is valid.

  Create: PUBLIC PROCEDURE [
    local: NetworkAddress, send, receive, reserve: CARDINAL,
    privateBuffers: BOOLEAN ← TRUE] RETURNS [sH: SocketHandle] =
    BEGIN
    totalBuffers: CARDINAL ←
      IF privateBuffers THEN send + receive + reserve ELSE 0;
    sH ← Heap.MakeNode[n: SIZE[SocketObject]];
    -- fill in all the specific fields of the socket object appropriately,
    -- make the appropriate buffer pool, which can be the the spp buffer pool per socket,
    -- the listener buffer pool per socket, or just a pool of small buffers called crates.
    sH.pool ← BufferDefs.MakeBufferPool[
      totalBuffers, send, receive, reserve, ~privateBuffers];
    sH.localAddr ←
      IF local = OISCP.uniqueAddress THEN Router.AssignOisAddress[]
      ELSE local;
    sH.channelState ← active;
    sH.waitTime ← MilliSecondsToPulses[Socket.defaultWaitTime];
    -- initialize the condition variables and queues
    BufferDefs.QueueInitialize[@sH.completedUserGetQueue];
    Process.InitializeCondition[
      CommUtilDefs.MaybeShorten[@sH.newUserInput],
      Process.MsecToTicks[Inline.LowHalf[Socket.defaultWaitTime]] + 1];
    CommUtilDefs.EnableAborts[CommUtilDefs.MaybeShorten[@sH.newUserInput]];
    -- tell the router of this socket
    Router.AddSocket[sH];
    END; -- Create


  -- This procedure deletes this socket channel by deleting the socket from the router's
  -- tables, and cleans up the data structures associated with this socket.

  Delete: PUBLIC PROCEDURE [sH: ChannelHandle] =
    BEGIN

    DeleteLocked: ENTRY PROCEDURE = INLINE
      BEGIN
      ENABLE UNWIND => NULL;
      BufferDefs.QueueCleanup[@sH.completedUserGetQueue];
      BufferDefs.FreeBufferPool[sH.pool];
      END; -- DeleteLocked

    -- This procedure is a bit tricky to avoid any monitor problems with socket handles.
    -- We want to make sure that the process that from the
    -- router always has a valid socket handle, and that there are no deadlocks.
    Router.RemoveSocket[sH];
    DeleteLocked[];
    Heap.FreeNode[p: sH];
    END; -- 

  -- This procedure aborts I/O on this socket channel.

  Abort: PUBLIC ENTRY PROCEDURE [sH: ChannelHandle] =
    BEGIN
    temp: CONDITION;
    sH.channelState ← aborted;
    BROADCAST sH.newUserInput; -- tell all potential waiters
    Process.InitializeCondition[@temp, Process.MsecToTicks[10]];
    WAIT temp;  -- Yield to let waiter leave the MONITOR
    END; -- Abort

  Reset: PUBLIC ENTRY PROCEDURE [sH: ChannelHandle] =
    BEGIN sH.channelState ← active; END;

  -- This procedure gets the status of this socket channel.

  GetStatus: PUBLIC ENTRY PROCEDURE [sH: ChannelHandle]
    RETURNS [status: SocketStatus] =
    BEGIN
    status ←
      [localAddr: sH.localAddr, state: sH.channelState,
	incompleteGets: sH.completedUserGetQueue.length];
    END; -- GetStatus

  -- This procedure sets the wait time for this socket channel.

  SetWaitTime: PUBLIC ENTRY PROCEDURE [cH: ChannelHandle, time: WaitTime] =
    BEGIN
    ticks: Process.Ticks;
    condTime: CARDINAL ← Inline.LowHalf[time];
    lastCard: LONG CARDINAL = LAST[CARDINAL]; -- no, not last LONG CARDINAL !!
    cH.waitTime ← MilliSecondsToPulses[time];
    -- fix the condition variables to reflect this change
    -- this is a bit hairy because ticks are (short) CARDINALs
    IF time > lastCard THEN
      BEGIN
      timeInSeconds: LONG CARDINAL ← time/1000;
      IF timeInSeconds > lastCard THEN condTime ← LAST[CARDINAL] - 1
	-- still too big !!!

      ELSE condTime ← Inline.LowHalf[timeInSeconds];
      ticks ← Process.SecondsToTicks[condTime];
      END
    ELSE ticks ← Process.MsecToTicks[condTime] + 1;
    Process.SetTimeout[CommUtilDefs.MaybeShorten[@cH.newUserInput], ticks];
    Process.SetTimeout[CommUtilDefs.MaybeShorten[@cH.newUserInput], ticks];
    END; -- SetWaitTime

  pulsesPerMilliSecond: LONG CARDINAL ← System.MicrosecondsToPulses[1000];
  MilliSecondsToPulses: PROCEDURE [ms: LONG CARDINAL]
    RETURNS [pulses: LONG CARDINAL] =
    BEGIN
    -- we must be carefull about multiplication overflow since milliSeconds must be
    -- converted to microSeconds
    IF ms >= LAST[LONG CARDINAL]/1000 -- overflow condition
       THEN
      IF ms >= LAST[LONG CARDINAL]/pulsesPerMilliSecond -- ms is out of range 
	 THEN pulses ← LAST[LONG CARDINAL]
      ELSE pulses ← ms*pulsesPerMilliSecond -- close guestimation

    ELSE pulses ← System.MicrosecondsToPulses[1000*ms];
    END; -- end MilliSecondsToPulses


  -- This procedure returns the buffer pool for this socket.  It needn't lock the monitor.

  GetBufferPool: PUBLIC PROCEDURE [sH: SocketHandle]
    RETURNS [BufferDefs.BufferAccessHandle] = BEGIN RETURN[sH.pool]; END;
  -- GetBufferPool

  -- Cold Procedures

  -- This procedure turns this module on.  The Communication software is written with the
  -- idea of eventually turning the code on and off during execution, and so we should
  -- get the latest value the hostID when being turned on by the Router.

  SocketOn: PUBLIC PROCEDURE =
    BEGIN
    -- find  myHostID from the Router
    myHostID ← Router.FindMyHostID[];
    END; -- SocketOn

  -- initialization (Cold)

  END. -- SocketImpl module

LOG

Time: January 7, 1980  3:11 PM  By: Dalal  Action: converted to new SocketInternal.
Time: January 21, 1980  5:47 PM  By: Dalal  Action: all sockets under one lock.
Time: March 12, 1980  5:32 PM  By: BLyon  Action: added send, receive to CreateInternal.
Time: March 18, 1980  12:07 PM  By: BLyon  Action: Added Reset, Getpacket, PutPacket (body identicle to old Put), modified Put. Modified TransferWait & TransferWaitAny to abort like PutPacket. Modified Abort.
Time: June 3, 1980  3:43 PM  By: BLyon  Action: Replace SimpleHeap references with Heap. 
Time: June 3, 1980  3:43 PM  By: HGM  Action: Add MaybeShortens. 
Time: June 18, 1980  2:44 PM  By: BLyon  Action: Added EXPORTED TYPES. 
Time: September 18, 1980  3:45 PM  By: BLyon  Action: flushed primaryNetID concept. 
Time: October 8, 1980  9:10 AM  By: BLyon  Action: Time units are now pulses and not milliseconds. 
Time: November 19, 1980  5:12 PM  By: BLyon  Action: Post Mokelumne rework.
Time: January 23, 1981  4:27 PM  By: BLyon  Action: Added ability to abort GetPacket.