-- File: NetworkBindingServer.mesa - last edit:
-- AOF                 19-Jun-87 12:28:34
-- kam                  3-Jun-86 10:01:49

-- Copyright (C) 1986, 1987 by Xerox Corporation. All rights reserved.
DIRECTORY
  CommPriorities USING [normal],
  Courier USING [
    Description, DeserializeParameters, EnumerateExports, Error, Exports,
    FreeEnumeration, Parameters, SerializeParameters, VersionRange],
  CourierProtocol USING [Protocol3Body, ExchWords],
  Environment USING [Block, bytesPerWord],
  Heap USING [systemZone],
  HostNumbers USING [IsMulticastID],
  MemoryStream USING [Create, IndexOutOfRange],
  NetworkBinding USING [
    Conjunct, cTRUE, RemoteProgram, dontCare, nullResponse, Predicate,
    PredicateProcedure, ResponseProc],
  NetworkBindingInternal,
  NetworkBindingProtocol,
  NSBuffer USING [Body, Buffer],
  NSTypes USING [bytesPerExchangeHeader, ExchangeID, maxDataBytesPerExchange],
  Process USING [Detach, SetPriority],
  Router USING [FindMyHostID],
  Socket USING [
    ChannelHandle, Delete, ReturnBuffer, PutPacket, SetPacketBytes,
    SwapSourceAndDestination],
  SocketInternal USING [ListenerProcType, CreateListen],
  SpecialSystem USING [HostNumber],
  Stream USING [Handle],
  System USING [
    NetworkAddress, broadcastHostNumber, SocketNumber, HostNumber,
    nullHostNumber];

NetworkBindingServer: MONITOR
  IMPORTS
    Courier, CourierProtocol, Heap, HostNumbers, MemoryStream,
    NetworkBindingInternal, Process, Router, Socket, SocketInternal
  EXPORTS NetworkBinding, NetworkBindingInternal, System =
  BEGIN

  NoBinding: PUBLIC <<NetworkBinding>> ERROR = CODE;
  DataTooLarge: PUBLIC <<NetworkBinding>> ERROR = CODE;
  HostNumber: PUBLIC <<System>> TYPE = SpecialSystem.HostNumber;

  myZone: UNCOUNTED ZONE = Heap.systemZone;
  bpw: NATURAL = Environment.bytesPerWord;
  globalExport: RECORD[
    count: NATURAL, first, last: Export, sH: Socket.ChannelHandle];

  ActiveRecord: TYPE = RECORD[
    host: System.HostNumber ← System.nullHostNumber,
    id: NSTypes.ExchangeID ← [0, 0]];
  inProgress: ARRAY NATURAL[0..2) OF ActiveRecord ← ALL[];

  Export: TYPE = LONG POINTER TO ExportObject;
  ExportObject: TYPE = RECORD[
    link: Export ← NIL,
    program: LONG CARDINAL, versionRange: Courier.VersionRange,
    predicateDescription: Courier.Description, conjunct: NetworkBinding.Conjunct,
    predicateProc: NetworkBinding.PredicateProcedure, zone: UNCOUNTED ZONE];

  me: System.HostNumber = Router.FindMyHostID[];
  null: System.HostNumber = System.nullHostNumber;
  all: System.HostNumber = System.broadcastHostNumber;
  offsetToCookie: NATURAL = NSTypes.bytesPerExchangeHeader +
    SIZE[NetworkBindingProtocol.ReturnMessage] * bpw;
  
  -- Server Stub Usage

  ClientProcess: <<DETATCHED PROCESS>> PROC[
    b: NSBuffer.Buffer, export: Export, version: CARDINAL,
    clientInfo: NetworkBindingInternal.ClientInfo] =
    BEGIN
    <<
    This task comes in three parts. First we have to deserialize the client's
    predicate data from the packet we just received (it's pointed to by the
    procdure argument 'clientInfo'). Then we call the client's predicate proc,
    passing him a procedure to call if he wants to answer in the affirmative.
    That procedure will take the client's response and serialize it back into
    the same buffer the request came in. Then we send the packet back to the
    sender. The client may elect to raise NoBinding in which case the response
    data is null (sequence length == 0). We then check to see if it is correct
    to send back a negative response (was the request broadcasted?).
    >>

    ResponseProc: NetworkBinding.ResponseProc =
      BEGIN
      block: Environment.Block;
      block.blockPointer ← LOOPHOLE[@return.body.response[0]];
      block.startIndex ← 0;
      block.stopIndexPlusOne ← NSTypes.maxDataBytesPerExchange -
        NetworkBindingInternal.fixedBytesInReturn;
      sH ← MemoryStream.Create[block];
      Courier.SerializeParameters[response, sH !
        MemoryStream.IndexOutOfRange => GOTO tooBig];
      NetworkBindingInternal.SetSequenceLength[
        @return.body.response, NATURAL[sH.getPosition[sH]] / bpw];
      sH.delete[sH];  --delete the stream
      EXITS tooBig => {sH.delete[sH]; ERROR DataTooLarge};
      END;  --ResponseProc
    
    sH: Stream.Handle;
    predicateData: LONG POINTER;
    predicate: NetworkBinding.Predicate;
    return: NetworkBindingInternal.ReturnMessage;

    Process.SetPriority[CommPriorities.normal];

    predicateData ← ExtractRequestPredicate[
      clientInfo, export.predicateDescription, export.zone !
      Courier.Error => GOTO noBinding];

    return ← LOOPHOLE[@b.ns.exchangeBody];
    predicate ← [clientInfo.remoteProgram, clientInfo.conjunct];
    NetworkBindingInternal.SetSequenceLength[@return.body.response, 0];

    export.predicateProc[predicate, predicateData, ResponseProc !
      NoBinding => GOTO noBinding];  --had nothing to say

    SendResponse[b, version, return];  --then send the answer, consuming packet
    EXITS noBinding => SendNoBind[b];  --there's no binding here
    END;  --ClientProcess

  ExtractRequestPredicate: PROC[
    clientInfo: NetworkBindingInternal.ClientInfo,
    how: Courier.Description, zone: UNCOUNTED ZONE]
    RETURNS[data: LONG POINTER] =
    BEGIN
    Describe: Courier.Description =
      {notes.noteDisjointData[notes.noteSize[SIZE[LONG POINTER]], how]};
    block: Environment.Block;
    sH: Stream.Handle;
    block.blockPointer ← LOOPHOLE[@clientInfo.param[0]];
    block.startIndex ← 0;
    block.stopIndexPlusOne ← clientInfo.param.length * bpw;
    sH ← MemoryStream.Create[block];
    Courier.DeserializeParameters[[@data, Describe], sH, zone !
      UNWIND => sH.delete[sH]];
    sH.delete[sH];  --delete the stream
    END;  --ExtractRequestPredicate
  
  DeregisterPredicate: PUBLIC <<NetworkBinding>> ENTRY PROC[
    programNumber: LONG CARDINAL,
    versionRange: Courier.VersionRange,
    conjunct: NetworkBinding.Conjunct] =
    BEGIN
    export, prev: Export ← NIL;
    FOR export ← globalExport.first, export.link UNTIL export = NIL DO
      IF export.program = programNumber
        AND export.versionRange = versionRange
	AND export.conjunct = conjunct THEN
        BEGIN
	IF prev # NIL THEN prev.link ← export.link
	ELSE globalExport.first ← export.link;
	IF globalExport.last = export THEN globalExport.last ← prev;
	IF (globalExport.count ← PRED[globalExport.count]) = 0 THEN
	  Socket.Delete[globalExport.sH];  --away it goes
	RETURN;
	END;
      ENDLOOP;
    END;  --DeregisterPredicate

  InRange: PROC[low, mid, high: System.HostNumber] RETURNS[BOOLEAN] =
    BEGIN
    OPEN
      l: LOOPHOLE[low, NetworkBindingInternal.ThreeCardinals],
      m: LOOPHOLE[mid, NetworkBindingInternal.ThreeCardinals],
      h: LOOPHOLE[high, NetworkBindingInternal.ThreeCardinals];
    RETURN[
      m.one IN[l.one..h.one] AND
      m.two IN[l.two..h.two] AND
      m.three IN[l.three..h.three]];
    END;  --InRange

  InProgress: ENTRY PROC[this: ActiveRecord] RETURNS[BOOLEAN] =
    BEGIN
    tap: CARDINAL ← LAST[CARDINAL];
    FOR i: CARDINAL IN[0..LENGTH[inProgress]) DO
      IF inProgress[i] = this THEN RETURN[TRUE];
      IF inProgress[i].host = System.nullHostNumber THEN tap ← i;
      ENDLOOP;
    IF tap # LAST[CARDINAL] THEN inProgress[tap] ← this;
    RETURN[FALSE];
    END;  --InProgress

  ListenerProc: SocketInternal.ListenerProcType =
    BEGIN
    body: NSBuffer.Body ← b.ns;
    hosts: NetworkBindingInternal.Hosts;
    enum: LONG DESCRIPTOR FOR Courier.Exports;
    locate: NetworkBindingInternal.CallMessage;
    return: NetworkBindingInternal.ReturnMessage;
    clientInfo: NetworkBindingInternal.ClientInfo;
    return ← LOOPHOLE[(locate ← LOOPHOLE[@body.exchangeBody])];

    SELECT TRUE FROM
      (body.packetType # packetExchange) => GOTO noBinding;  --bingo!
      (body.exchangeType # NetworkBindingProtocol.clientType) => GOTO noBinding;
      (locate.courierVers # [3, 3]) => GOTO noBinding;
      (locate.call.type # call) => GOTO noBinding;
      (locate.call.transaction # 0) => GOTO noBinding;
      (InProgress[[body.source.host, body.exchangeID]]) => GOTO inProgress;
      (CourierProtocol.ExchWords[locate.call.program] #
        NetworkBindingProtocol.program) => GOTO noBinding;
      (locate.call.version ~IN[
	NetworkBindingProtocol.serverVersionLow..
	NetworkBindingProtocol.serverVersionHigh]) => GOTO noBinding;
      (ProcType[locate.call.procedure] # locate) => GOTO noBinding;
        --it was all a joke, and a bad one at that
      ENDCASE;
    
    hosts ← @locate.body.hosts;
    clientInfo ← @locate.body.clientInfo +
      SIZE[NetworkBindingProtocol.HostsRecord[hosts.count]] -
      SIZE[NetworkBindingProtocol.HostsRecord[0]];

    <<Are we in the host list range?>>
    IF ~InRange[hosts[0], me, hosts[hosts.count - 1]] THEN GOTO noBinding;

    <<Are we already in the list?>>
    FOR i: NATURAL IN[0..hosts.count) DO
      IF hosts[i] = me THEN GOTO noBinding; ENDLOOP;

    <<Is there a particular program number of interest?>>
    clientInfo.remoteProgram.programNumber ← CourierProtocol.ExchWords[
      clientInfo.remoteProgram.programNumber];
    IF clientInfo.remoteProgram # NetworkBinding.dontCare THEN
      BEGIN 
      <<Does this machine export it?>>
      enum ← Courier.EnumerateExports[];
      FOR i: NATURAL IN[0..LENGTH[enum]) DO
	OPEN rp: clientInfo.remoteProgram;
	SELECT TRUE FROM
	  (enum[i].programNumber #rp.programNumber) => LOOP;
	  (enum[i].versionRange.low > rp.version) => LOOP;
	  (enum[i].versionRange.high < rp.version) => LOOP;
	  ENDCASE => EXIT;  --life is good - so far
	REPEAT FINISHED => {Courier.FreeEnumeration[enum]; GOTO noBinding};
	ENDLOOP;
      Courier.FreeEnumeration[enum];
      END;

    <<Is there a predicate to process?>>
    clientInfo.conjunct ← [CourierProtocol.ExchWords[clientInfo.conjunct]];
    IF clientInfo.conjunct # NetworkBinding.cTRUE THEN
    <<Does this machine know about the predicate?>>
      FOR export: Export ← globalExport.first, export.link
	UNTIL export = NIL DO
	IF export.program = clientInfo.remoteProgram.programNumber
	  AND export.versionRange.low <= clientInfo.remoteProgram.version
	  AND export.versionRange.high >= clientInfo.remoteProgram.version
	  AND export.conjunct = clientInfo.conjunct THEN
	  BEGIN
	  Process.Detach[
	    FORK ClientProcess[b, export, locate.call.version, clientInfo]];
	  RETURN;
	  END;
	REPEAT FINISHED => GOTO noBinding;  --we don't know about predicate
	ENDLOOP;

    NetworkBindingInternal.SetSequenceLength[@return.body.response, 0];
    SendResponse[b, locate.call.version, return];  --fills buffer - consumes 'b'

    EXITS
      noBinding => SendNoBind[b];  --consumes 'b'
      inProgress => Socket.ReturnBuffer[b];
    END;  --ListenerProc

  OutProgress: ENTRY PROC[this: ActiveRecord] =
    BEGIN
    FOR i: CARDINAL IN[0..LENGTH[inProgress]) DO
      IF inProgress[i] = this THEN
        {inProgress[i].host ← System.nullHostNumber; RETURN};
      ENDLOOP;
    END;  --OutProgress
  
  ProcType: PROCEDURE [procNumber: CARDINAL] -- reverse ORD function
    RETURNS [NetworkBindingProtocol.Procedure] = MACHINE CODE {};
  
  RegisterPredicate: PUBLIC <<NetworkBinding>> ENTRY PROC [
    programNumber: LONG CARDINAL, versionRange: Courier.VersionRange,
    conjunct: NetworkBinding.Conjunct, proc: NetworkBinding.PredicateProcedure,
    predicateDescription: Courier.Description, zone: UNCOUNTED ZONE ← NIL] =
    BEGIN
    export: Export;
    IF zone = NIL THEN zone ← myZone;  --client didn't specify zone
    export ← myZone.NEW[ExportObject ← [
      program: programNumber, versionRange: versionRange,
      conjunct: conjunct, predicateProc: proc, zone: zone,
      predicateDescription: predicateDescription]];
    IF globalExport.last # NIL THEN globalExport.last.link ← export
    ELSE globalExport.first ← export;
    globalExport.last ← export;
    IF (globalExport.count ← SUCC[globalExport.count]) = 1 THEN
      BEGIN
      globalExport.sH ← SocketInternal.CreateListen[
        socket: NetworkBindingProtocol.socket, callback: ListenerProc,
	clientData: NIL, type: packetExchange];
      END;
    END;  --RegisterPredicate

  SendNoBind: PROC[b: NSBuffer.Buffer] =
    BEGIN
    body: NSBuffer.Body ← b.ns;
    OutProgress[[body.source.host, body.exchangeID]];  --we're finished
    IF ~HostNumbers.IsMulticastID[@b.ns.destination.host] THEN
      BEGIN
      abort: NetworkBindingInternal.AbortMessage;
      Socket.SwapSourceAndDestination[b];
      abort ← LOOPHOLE[@body.exchangeBody];
      abort↑ ← [courierVers: [3, 3],
        abort: [abort[transaction: 0, abort: 1]]];
      Socket.SetPacketBytes[b, NSTypes.bytesPerExchangeHeader +
	(SIZE[NetworkBindingProtocol.AbortMessage] * bpw)];
      Socket.PutPacket[globalExport.sH, b];  --send it out (consumes buffer)
      END
    ELSE Socket.ReturnBuffer[b];
    END;  --SendNoBind

  SendResponse: PROC[
    b: NSBuffer.Buffer, version: CARDINAL,
    return: NetworkBindingInternal.ReturnMessage] =
    BEGIN
    body: NSBuffer.Body ← b.ns;
    bytes: NATURAL = IF version = NetworkBindingProtocol.serverVersionLow THEN 0
    ELSE bpw * SIZE[NetworkBindingProtocol.Cookie[return.body.response.length]];
    OutProgress[[body.source.host, body.exchangeID]];  --done with this one
    Socket.SwapSourceAndDestination[b];  --get it going back to him
    return.courierVers ← [3, 3];  --version of Courier we speak
    return.return ← [return[transaction: 0]];  --we're answering
    return.body.responder ← body.source.host;  --fixed portion of answer
    Socket.SetPacketBytes[b, NetworkBindingInternal.fixedBytesInReturn + bytes];
    Socket.PutPacket[globalExport.sH, b];  --send it out (consumes buffer)
    END;  --SendResponse 

  StartProtocol: PUBLIC <<NetworkBindingInternal>> PROC[] RETURNS[BOOLEAN] =
    BEGIN
    IF globalExport.count = 0 THEN
      RegisterPredicate[
	NetworkBinding.dontCare.programNumber,
	[NetworkBinding.dontCare.version, NetworkBinding.dontCare.version],
	NetworkBinding.cTRUE, NIL, NIL, NIL];  --starts vanilla listener
    RETURN[TRUE];  --can't fail
    END;  --StartProtocol

  StopProtocol: PUBLIC <<NetworkBindingInternal>> PROC[] RETURNS[BOOLEAN] =
    BEGIN
    IF globalExport.count # 1 THEN RETURN[FALSE];  --Too many clients to stop
    DeregisterPredicate[
      NetworkBinding.dontCare.programNumber,
      [NetworkBinding.dontCare.version, NetworkBinding.dontCare.version],
      NetworkBinding.cTRUE];
    RETURN[TRUE];
    END;  --StopProtocol

  --MAINLINE CODE--

  globalExport ← [0, NIL, NIL, ];  --get the global data started
  [] ← StartProtocol[];  --get things rolling

  END...