-- 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],
OISCP USING [
DequeueOis, uniqueAddress, unknownHostID, unknownNetID, unknownSocketID],
PrincOpsUtils USING [LowHalf],
Process USING [
InitializeCondition, MsecToTicks, SecondsToTicks, SetTimeout, Ticks],
ProcessorFace USING [GetClockPulses, microsecondsPerHundredPulses],
Router USING [
AddSocket, AssignOisAddress, BroadcastThisPacket, FindDestinationRelativeNetID,
FindMyHostID, RemoveSocket, socketRouterLock, SendPacket, XmitStatus],
Socket USING [defaultWaitTime, SocketStatus, WaitTime],
SocketInternal USING [SocketHandle, SocketObject],
NSAddress USING [HostNumber, NetworkAddress, NetworkNumber, nullNetworkAddress];

SocketImpl: MONITOR LOCKS Router.socketRouterLock
IMPORTS
BufferDefs, DriverDefs, CommUtilDefs, OISCP, PrincOpsUtils,
Process, Router, ProcessorFace
EXPORTS
CommunicationInternal, Socket, SocketInternal
SHARES BufferDefs =
BEGIN OPEN OISCP, Socket, SocketInternal;

-- EXPORTED TYPES and READONLY Variables
ChannelHandle: TYPE = REF ChannelObject;
ChannelObject: PUBLIC TYPE = SocketInternal.SocketObject;
NetworkAddress: PUBLIC TYPE = NSAddress.NetworkAddress; -- others
uniqueNetworkAddr: PUBLIC NSAddress.NetworkAddress ←
NSAddress.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: NSAddress.HostNumber;

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

-- Hot Procedures

GetPulsesIntervalTime: PROCEDURE [startTime: LONG CARDINAL]
RETURNS [LONG CARDINAL] = INLINE
BEGIN
RETURN[ProcessorFace.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: LONG CARDINAL ← ProcessorFace.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 [NSAddress.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 ← NEW[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
sH.completedUserGetQueue ← NEW[BufferDefs.QueueObject];
BufferDefs.QueueInitialize[sH.completedUserGetQueue];
Process.InitializeCondition[
CommUtilDefs.MaybeShorten[@sH.newUserInput],
Process.MsecToTicks[PrincOpsUtils.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 ← PrincOpsUtils.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 ← PrincOpsUtils.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

MilliSecondsToPulses: PROCEDURE [ms: LONG CARDINAL]
RETURNS [pulses: LONG CARDINAL] =
BEGIN
-- we must be carefull about multiplication overflow since milliSeconds must be
-- converted to microSeconds
micro: LONG CARDINAL = MIN[LAST[LONG CARDINAL]/1000,ms] * 1000;
hundreds: LONG CARDINAL = micro / ProcessorFace.microsecondsPerHundredPulses;
pulses ← MIN[LAST[LONG CARDINAL]/100,hundreds]*100;
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.