-- File: SocketImpl.mesa - last edit:
-- AOF                  2-Feb-88 17:12:37
-- SMA                 23-May-86 16:59:42
-- MI                  18-Jul-86 16:18:01
-- Copyright (C) 1984, 1985, 1986, 1987, 1988 by Xerox Corporation. All rights reserved. 
--Function: The implementation module for Pilot NS Socket Channels. 

DIRECTORY
  Buffer USING [dataLinkReserve],
  BufferOps USING [GetSizes],
  CommFlags USING [doDebug],
  CommHeap USING [zone],
  CommUtil USING [PulsesToTicks],
  Driver USING [GetDeviceChain, Glitch, Device, PutOnGlobalDoneQueue],
  Frame USING [GetReturnFrame, ReadGlobalLink],
  HostNumbers USING [IsMulticastID],
  NSBuffer USING [
    AccessHandle, Body, DestroyPool, MakePool, Buffer, Dequeue,
    QueueInitialize, QueueCleanup, QueueObject, GetBuffer],
  NSTypes USING [bytesPerIDPHeader, PacketType, wordsPerIDPHeader],
  Process USING [
    Abort, EnableAborts, InitializeCondition, SetTimeout, DisableTimeout,
    DisableAborts, SetPriority],
  ProcessPrioritiesExtras USING [priorityPilotRealtimeSwappable],
  Protocol1 USING [GetContext],
  Router USING [AssignAddress, FindDestinationRelativeNetID, FindMyHostID],
  RouterInternal USING [
    AddSocket, BroadcastThisPacket, RemoveSocket, socketRouterLock, SendPacket],
  RoutingTable USING [NetworkContext],
  Socket USING [defaultWaitTime, SocketStatus, WaitTime],
  SocketInternal USING [
    ListenerProcType, SocketHandle, SocketObject],
  SpecialSystem USING [
    broadcastHostNumber, HostNumber, NetworkAddress, NetworkNumber, SocketNumber,
    nullSocketNumber, nullNetworkNumber, nullNetworkAddress],
  System USING [
    GetClockPulses, Pulses, NetworkAddress, MicrosecondsToPulses,
    nullHostNumber, nullSocketNumber];

SocketImpl: MONITOR LOCKS RouterInternal.socketRouterLock
  IMPORTS
    Buffer, BufferOps, CommHeap, CommUtil, Driver, Frame,
    HostNumbers, NSBuffer, Process, Protocol1, Router, RouterInternal, System
  EXPORTS Buffer, Socket, SocketInternal, System =
  BEGIN

  Device: PUBLIC <<Buffer>> TYPE = Driver.Device;
  ChannelHandle: PUBLIC TYPE = SocketInternal.SocketHandle;
  HostNumber: PUBLIC TYPE = SpecialSystem.HostNumber;
  NetworkNumber: PUBLIC TYPE = SpecialSystem.NetworkNumber;
  SocketNumber: PUBLIC TYPE = SpecialSystem.SocketNumber;
  NetworkAddress: PUBLIC TYPE = SpecialSystem.NetworkAddress;

  NormalSocket: TYPE = LONG POINTER TO normal SocketInternal.SocketObject;

  --EXPORTED Variables
  nullChannelHandle: PUBLIC ChannelHandle ← NIL;
  uniqueNetworkAddr: PUBLIC NetworkAddress ← SpecialSystem.nullNetworkAddress;
  
  --SIGNALS AND ERRORS
  TimeOut: PUBLIC ERROR = CODE;
  --GLITCH
  IllegalNSPktLength: ERROR = CODE;
  
  --the following constants are used in setting wait times, etc
  maxMsecToPulses: LONG CARDINAL = LAST[LONG CARDINAL] / 1000;

  listener: RECORD[
    process: PROCESS, condition: CONDITION,
    queue: NSBuffer.QueueObject, count: CARDINAL];

  clientAborted: CONDITION;

  --All socket are covered by one lock.

  <<
  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 PROC[cH: ChannelHandle, b: NSBuffer.Buffer] =
    BEGIN
    nsb: NSBuffer.Body = b.ns;
    nsb.source ← cH.address;
    IF nsb.source.net = SpecialSystem.nullNetworkNumber THEN
      nsb.source.net ← Router.FindDestinationRelativeNetID[
        nsb.destination.net];
    IF nsb.destination.host = System.nullHostNumber
      OR nsb.destination.socket = System.nullSocketNumber THEN
      {b.fo.status ← invalidDestAddr; Driver.PutOnGlobalDoneQueue[LOOPHOLE[b]]}
    ELSE
      BEGIN
      b.fo.status ← goodCompletion;
      SELECT TRUE FROM
	(nsb.destination.net # SpecialSystem.nullNetworkNumber) =>
	  RouterInternal.SendPacket[b];
	(~HostNumbers.IsMulticastID[@nsb.destination.host]) =>
	  RouterInternal.SendPacket[b];
	ENDCASE => RouterInternal.BroadcastThisPacket[b];
      END;
    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 thesystem via the dispatcher using b.requeueProcedure.
  >>
  BroadcastPacketToAllConnectedNets: PUBLIC PROC[
    cH: ChannelHandle, b: NSBuffer.Buffer] =
    BEGIN
    b.fo.status ← goodCompletion;
    b.ns.source ← cH.address;
    IF b.ns.destination.socket = System.nullSocketNumber THEN
      {b.fo.status ← invalidDestAddr; Driver.PutOnGlobalDoneQueue[LOOPHOLE[b]]}
    ELSE RouterInternal.BroadcastThisPacket[b];
    END;  --PutPacketToAllConnectedNets

  <<
  This procedure blocks until a buffer is on the socket's input queue.
  >> 
  GetPacket: PUBLIC ENTRY PROC[sH: ChannelHandle]
    RETURNS [b: NSBuffer.Buffer] =
    BEGIN
    startTime: System.Pulses ← System.GetClockPulses[];
    WITH cH: sH SELECT FROM
      normal =>
	WHILE (b ← NSBuffer.Dequeue[@cH.queue]) = NIL DO
	  ENABLE UNWIND => cH.getsOutstanding ← cH.getsOutstanding - 1;
	  IF cH.aborted THEN {NOTIFY clientAborted; RETURN WITH ERROR ABORTED};
	  IF (System.GetClockPulses[] - startTime) >= cH.waitTime THEN
	    RETURN WITH ERROR TimeOut;
	  cH.getsOutstanding ← cH.getsOutstanding + 1;
	  WAIT cH.newUserInput;  --propogate ERROR ABORTED
	  cH.getsOutstanding ← cH.getsOutstanding - 1;
	  ENDLOOP;
	ENDCASE => b ← NIL;
    IF CommFlags.doDebug THEN
      sH.pool.frame ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
    END;

  LocalAddressFromSocket: PUBLIC PROC[socket: SocketNumber]
    RETURNS [NetworkAddress] =
    {RETURN[
      IF socket = SpecialSystem.nullSocketNumber THEN Router.AssignAddress[]
      ELSE
	[Router.FindDestinationRelativeNetID[SpecialSystem.nullNetworkNumber],
	Router.FindMyHostID[], socket]]};
    
  BroadcastAddressFromSocket: PUBLIC PROC[socket: SocketNumber]
    RETURNS [NetworkAddress] =
    {RETURN[[Router.FindDestinationRelativeNetID[SpecialSystem.nullNetworkNumber],
      SpecialSystem.broadcastHostNumber, socket]]};

  FixupUnknownSocketNumber: PUBLIC PROC[
    address: LONG POINTER TO NetworkAddress, socket: SocketNumber] =
    BEGIN
    IF address.socket = System.nullSocketNumber THEN
      address.socket ← socket;
    END;

  <<
  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 PROC[
    socket: SocketNumber,  --will be used as source address when sending
    send, receive: CARDINAL,  --limits how many input buffers may be queued
    type: NSTypes.PacketType]  --only packets of this type
    RETURNS [sH: ChannelHandle] =
    BEGIN
    sH ← CommHeap.zone.NEW[SocketInternal.SocketObject ← [
      next: NIL, packetType: type, checksums: TRUE,
      address: LocalAddressFromSocket[socket],
      pool: NSBuffer.MakePool[send, receive, normalPool],
      flair: normal[aborted: FALSE, waitTime: , newUserInput: , queue: ]]];
    SetWaitTime[sH, Socket.defaultWaitTime];
    WITH cH: sH SELECT FROM
      normal =>
        BEGIN
	NSBuffer.QueueInitialize[@cH.queue];
	Process.InitializeCondition[@cH.newUserInput, 0];
	Process.EnableAborts[@cH.newUserInput];
	END;
      ENDCASE;
    RouterInternal.AddSocket[sH !
      UNWIND => {NSBuffer.DestroyPool[sH.pool]; CommHeap.zone.FREE[@sH]}];
    sH.pool.frame ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
    END;  --Create

  CreateListen: PUBLIC PROC[
    socket: SocketNumber,  --will be used as source address when sending
    callback: SocketInternal.ListenerProcType,  --call me when one arrives
    clientData: LONG POINTER,  --client association
    send: CARDINAL ← 0,  --listen only that wants to send??
    receive: CARDINAL ← 2, --limits how many input buffers may be queued
    type: NSTypes.PacketType ← private]  --only packets of this type
    RETURNS [ChannelHandle] =
    BEGIN
    sH: ChannelHandle ← CommHeap.zone.NEW[SocketInternal.SocketObject ← [
      next: NIL, packetType: type, checksums: TRUE,
      address: LocalAddressFromSocket[socket],
      pool: NSBuffer.MakePool[send, receive, listenPool],
      flair: listen[
        callout: callback, arrival: @listener.condition,
	clientData: clientData, queue: @listener.queue]]];
    RouterInternal.AddSocket[sH !
      UNWIND => {NSBuffer.DestroyPool[sH.pool]; CommHeap.zone.FREE[@sH]}];
    IF TestAndSetListener[1] = 1 THEN listener.process ← FORK Listener[];
    sH.pool.frame ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
    RETURN[sH];
    END;  --Create

  TestAndSetListener: PUBLIC <<SocketInternal>> ENTRY PROC[
    modify: INTEGER[-1..1]] RETURNS[CARDINAL] =
    BEGIN
    --Watch out this doesn't go large negative
    RETURN[listener.count ← listener.count + modify];
    END;  --ModifyListenerCount

  <<
  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 PROC[sH: ChannelHandle] =
    BEGIN
    AbortSocket: ENTRY PROC[cH: NormalSocket] =
      BEGIN
      cH.aborted ← TRUE; BROADCAST cH.newUserInput;
      UNTIL cH.getsOutstanding = 0 DO WAIT clientAborted; ENDLOOP;
      END;  --AbortSocket
    RouterInternal.RemoveSocket[sH];  --inhibit new input being queued
    WITH cH: sH SELECT FROM
      normal =>
       BEGIN
       AbortSocket[@cH];  --get rid of socket clients
       NSBuffer.QueueCleanup[@cH.queue];  --flush input queue
       END;
      listen =>
	IF TestAndSetListener[-1] = 0 THEN
	  BEGIN
	  Process.Abort[listener.process];
	  JOIN listener.process; listener.process ← NIL;
	  NSBuffer.QueueCleanup[@listener.queue];  --flush input queue
	  END;
      ENDCASE;
    NSBuffer.DestroyPool[sH.pool];  --free the pool
    CommHeap.zone.FREE[@sH];  --delete socket object
    END;  --Delete

  GetStatus: PUBLIC ENTRY PROC[sH: ChannelHandle]
    RETURNS [status: Socket.SocketStatus] =
    {status ← [sH.address, sH.pool.receiveInUse]};

  Listener: PROC =
    BEGIN
    ListenSocket: TYPE = LONG POINTER TO listen SocketInternal.SocketObject;
    --This is the generic listener process routine
    Dequeue: ENTRY PROC =
      BEGIN ENABLE UNWIND => NULL;
      sH: ListenSocket;
      --FOREVER-- DO
        WHILE listener.queue.length # 0 DO
	  b ← NSBuffer.Dequeue[@listener.queue];
	  sH ← NARROW[b.requeueData, ListenSocket];
	  callback ← sH.callout; clientData ← sH.clientData;
	  RETURN;  --this causes next call to start at beginning of list
	  ENDLOOP;
	WAIT listener.condition;  --Router will wake us with something to do
	ENDLOOP;
      END;  --Dequeue
    b: NSBuffer.Buffer;
    clientData: LONG POINTER;
    callback: SocketInternal.ListenerProcType;
    Process.SetPriority[ProcessPrioritiesExtras.priorityPilotRealtimeSwappable];
    DO Dequeue[ ! ABORTED => EXIT]; callback[b, clientData]; ENDLOOP;
    END;  --Listener

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

  SetWaitTime: PUBLIC ENTRY PROC[cH: ChannelHandle, time: Socket.WaitTime] =
    BEGIN
    WITH nH: cH SELECT FROM
      normal =>
        BEGIN
	nH.waitTime ← IF time > maxMsecToPulses THEN LAST[LONG CARDINAL]
	  ELSE System.MicrosecondsToPulses[time*1000];
	IF nH.waitTime = LAST[LONG CARDINAL] THEN
	  Process.DisableTimeout[@nH.newUserInput]
	ELSE Process.SetTimeout[
	  @nH.newUserInput, CommUtil.PulsesToTicks[[nH.waitTime]]];
	END;
      ENDCASE;
    END;  --SetWaitTime
      
  GetSendBuffer: PUBLIC PROC[cH: ChannelHandle] RETURNS [NSBuffer.Buffer] =
    BEGIN
    IF CommFlags.doDebug THEN
      BEGIN
      b: NSBuffer.Buffer ← NSBuffer.GetBuffer[cH.pool, send];
      b.fo.debug ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
      RETURN[b];
      END
    ELSE RETURN[NSBuffer.GetBuffer[cH.pool, send]];
    END;  --GetSendBuffer

  SwapSourceAndDestination: PUBLIC PROC[b: NSBuffer.Buffer] =
    BEGIN
    nsb: NSBuffer.Body = b.ns;
    network: Device ← LOOPHOLE[b.fo.network];
    context: RoutingTable.NetworkContext ← b.fo.context;
    temp: NetworkAddress ← nsb.source;
    nsb.source ← nsb.destination;
    nsb.destination ← temp;
    -- If the packet came from us, there will be no network object (which we
    -- use to set null source and destination nets). Try to find a network number
    -- from the networks on the device chain; if that doesn't work, let it run 
    -- with net 0. 
    IF network = NIL THEN
      FOR network ← Driver.GetDeviceChain[], network.next UNTIL network = NIL DO
	SELECT TRUE FROM
	  ((context ← Protocol1.GetContext[network, ns]) = NIL) => NULL;
	  (~network.alive) => NULL;
	  (context.netNumber # SpecialSystem.nullNetworkNumber) => EXIT;
	  ENDCASE;  --LOOP
	ENDLOOP;
    IF (nsb.source.net = SpecialSystem.nullNetworkNumber)
      AND (context # NIL) THEN nsb.source.net ← context.netNumber;
    IF (nsb.destination.net = SpecialSystem.nullNetworkNumber)
      AND (context # NIL) THEN nsb.destination.net ← context.netNumber;
    IF HostNumbers.IsMulticastID[@nsb.source.host] THEN
      nsb.source.host ← Router.FindMyHostID[];
    END;
    
  GetSource: PUBLIC PROC[b: NSBuffer.Buffer] RETURNS [NetworkAddress] =
    {RETURN[b.ns.source]};
    
  GetDestination: PUBLIC PROC[b: NSBuffer.Buffer] RETURNS [NetworkAddress] =
    {RETURN[b.ns.destination]};
    
  SetDestination: PUBLIC PROC[b: NSBuffer.Buffer, d: NetworkAddress] =
    {b.ns.destination ← d};
  
  GetPacketBytes: PUBLIC PROC[b: NSBuffer.Buffer] RETURNS [bytes: CARDINAL] =
    {RETURN[b.ns.pktLength - NSTypes.bytesPerIDPHeader]};
    
  SetPacketWords: PUBLIC PROC[b: NSBuffer.Buffer, words: CARDINAL] =
    {SetPacketBytes[b, 2*words]};
    
  SetPacketBytes: PUBLIC PROC[b: NSBuffer.Buffer, bytes: CARDINAL] =
    BEGIN
    -- Buffer.dataLinkReserve has been changed from words to bytes.
    -- That is why / 2 has been added here
    overhead: CARDINAL = Buffer.dataLinkReserve / 2 + NSTypes.wordsPerIDPHeader;
    SELECT TRUE FROM
      ~CommFlags.doDebug => NULL;  --don't bother checking
      ((bytes / 2) > (BufferOps.GetSizes[][largeBuffer] - overhead)) =>
        Driver.Glitch[IllegalNSPktLength];  --probably too late, but....
      ENDCASE;
    b.ns.pktLength ← bytes + NSTypes.bytesPerIDPHeader
    END;
    
  --This procedure returns the buffer pool for this socket.
  GetBufferPool: PUBLIC PROC[sH: ChannelHandle]
    RETURNS [NSBuffer.AccessHandle] = {RETURN[sH.pool]};
    
  --This procedure returns the full address assigned to this socket.
  GetAssignedAddress: PUBLIC PROC[sH: ChannelHandle]
    RETURNS [NetworkAddress] = {RETURN[sH.address]};

  --MAINLINE CODE
  Process.DisableAborts[@clientAborted];
  Process.SetTimeout[@clientAborted, 20];

  listener.count ← 0;
  listener.process ← NIL;
  Process.InitializeCondition[@listener.condition, 0];
  Process.DisableTimeout[@listener.condition];
  Process.EnableAborts[@listener.condition];
  NSBuffer.QueueInitialize[@listener.queue];
  END. --SocketImpl module

LOG

time - by - action
14-May-84 14:01:40  AOF  Post Klamath.
12-Mar-85  8:31:44  AOF  Replace test for broadcast with multicast.
10-Dec-85 10:23:41  AOF  Restructure listening.
13-Dec-85  9:56:51  AOF  Allowing for arbitrary sized packets.
23-May-86 10:22:32  SMA  Remove dependencies on Buffer and encapsulation.
18-Jul-86 16:17:11  MI   Devide Buffer.dataLinkReserve by two to get words count.
18-Aug-86 18:08:20  AOF  Local caching of b.ns in nsb.
10-Sep-86 17:03:26  AOF  Listener runs at priorityPilotRealtimeSwappable.
 8-Jan-87 11:39:03  AOF  More debugging code.