Wednesday, May 8, 2019

Kubernetes Follow-On (Part 2)


Kubernetes Follow-On (Part 2)

In the previous post I described sending messages directly between components via TCP/IP (Transmission Control Protocol/Internet Protocol) via Microsoft WinSock rather than between Applications and then delivering the message to the applicable component.  This was done by assigning receive/client and transmit/server ports to the component and using the port along with the IP address of the computer.  At that time the example had one component in each of two applications.

Since then I have extended the examples to have two components within the first application to communicate with each other as well as one of its components communicating with the component of the second application as before.  Then, what proved to be much more difficult to for me to figure out, the addition of a second component to the second app and having both communicate with a component of a third app to be run on a second computer.  Hence using a different IP address.

During this time I was also cleaning up my WinSock package code from the time at the beginning of the decade when I first used it.  And, making some changes that I thought would improve the ability of the WinSock code to connect to its remote application's components whether on the same computer or a different one.  Some of these "improvements" proved to be an impediment to say the least.  But at least one change was necessary. 

That is, the Receive/Client and Transmit/Server WinSock subpackages (Recv and Xmit) have their Install procedure similar to components.  As with components there is the Install procedure that runs in the launch thread of the application and their callback procedure that runs in the thread assigned by the Threads package.  But unlike components there is one pair of callback procedures (one Recv and one Xmit) per pair of component partners.  Whereas in components static data can be maintained in the package body that is used and updated by the callback thread, doing so in the Recv and Xmit packages caused a problem since there was only one Recv and one Xmit Install but multiple threads to access and update the data.  Hence, the second Install would clobber selected data of the first and the callback procedures wouldn't find the expected values.  This was fixed by adding additional fields to the Comm Data and Link array records to maintain this data.  That is, where there were separate records in the Data and Link array to be accessed by each thread.  Thus removing the data it from the static data area of the Recv and Xmit subpackages and placing it in the static data of the WinSock package itself.

Also, where possible in some instances other data was moved to the thread callback procedure where the data would be on the stack of the particular thread.  That is, data that was only used by the individual callback procedure.

Delivery.dat

I also rearranged and extended the Delivery.dat file records to require more extensive searching to find the partners of the components.  That is, allowing the creation and maintenance of the Delivery.dat file to be more flexible.  This ended up as
1|Component1|192.xxx.y.c1|10.0.0.1|8001|8002|
6|RemoteComponent|192.xxx.y.c2|10.1.1.1|8006|8005|
2|Component2|192.xxx.y.c1|10.0.0.2|8002|8001|
2|Component2|192.xxx.y.c1|10.0.0.2|8009|8010|
6|RemoteComponent|192.xxx.y.c2|10.1.1.2|8010|8009|
1|Component1|192.xxx.y.c1|10.0.0.1|8003|8004|
8|Component8|192.xxx.y.c1|10.0.0.4|8007|8008|
4|NewComponent|192.xxx.y.c1|10.0.0.2|8004|8003|
5|ExComponent|192.xxx.y.c1|10.0.0.3|8005|8006|
where xxx.y.c1 and xxx.y.c2 are the other three bytes of the IP address of the first and second computers.

As before, the extra IP addresses such as 10.0.0.1 have been left in the file but are unused since the ports are sufficient to identify the component of the TCP/IP communications.

In the file the '|' character is used as a field separator.  The first field is the numeric identifier of the component and the second its name.  The third field is the IP address of the computer where the application is executed.  The fourth field is the addition IP address that would be assigned if the ports weren't used.  The fifth and sixth fields are the pair of ports assigned to the component – one for the client and one for the server. 

The ports are reversed for the partner instance of the communicating component.  For instance Component1 of the first record is to communicate with Component2 of the third record.  And Component1 of the sixth record is to communicate with NewComponent of the eighth record.  This would be more obvious if the pair of communicating components were placed next to each other in the Delivery.dat file as I had in the first post but I wanted to mix them up to be sure the code could still find the pairs.

Debugging

I did debugging by outputting Ada Text_IO to the "console".  I would use separate Put statements of text and integers to build up a line and then do a Put_Line to end the sequence.  This caused confusion when another thread would suspend the first thread and do or start its own "line" causing data of the initial output to become separated when the initial thread resumed.

Therefore, I created a new Text IO package to more easily combine strings and integers so that the write of the line could be done all at once.  I added functions to concatenate a string and an integer and to concatenate two strings while inserting a space between them in each instance.  The new TextIO package Concat of an integer to a string also removes any leading spaces that the Ada Int_IO caused so that the string and the converted integer are immediately adjacent except for the inserted space.  These two functions can be used to build up a set of such values to form a string of up to 80 characters that can be output via the new TextIO Put_Line procedure.

For instance,
      declare
        Text : Itf.V_80_String_Type;
        IPAddr : Itf.ByteArray(1..4);
        for IPAddr use at Comm.Link(I).Transmit.Socket.Data.SIn_Addr'address;
      begin
        Text.Count := 8;
        Text.Data(1..8) := "SIn_Addr";
        Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(IPAddr(1)) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(IPAddr(2)) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(IPAddr(3)) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(IPAddr(4)) );
        TextIO.Put_Line( Text );
      end;
to output the IP address as four bytes as it is stored in Comm.Link(I).Transmit.Socket.Data.

Some of the original Text_IO and Int_IO output remains.  Mostly where the output is being done while in the launch thread so other threads can not suspend it.

One Mystery Solved

Not until April 30 did the light finally dawn to me.

After not getting the one PC to communicate via TCP/IP using the WinSock of Microsoft with a second PC for some time and looking back at my work of 10 years ago when I know that I was able to communicate between two computers, I finally saw what had been in plain sight but hadn't revealed itself to me.  That is, as I "install" the components with WinSock I output text.  Included in this text was the IP address such as
                 Id  Name                                    Partner Location in table
          1 Component1                    3
          6 RemoteComponent               9
          2 Component2                    1
          2 Component2                    5
          6 RemoteComponent               4
          1 Component1                    8
          8 Component8                    0
          4 NewComponent                  6
          5 ExComponent                   2
Thread item Name Component2
EventName Component2         384
Items added - Possible_Pairs 1 1 2 Comm.Data 2 1 4265706 ß A
Items added - Possible_Pairs 2 2 6 Comm.Data 2 6 4265706 ß B
Comm.Link for Receive Socket 1 SIn_Port 16671 SIn_Addr 1124182208 Addr 4893340
SIn_Addr 192 xxx y c1
Comm.Link for Transmit Socket 1 SIn_Port 16927 SIn_Addr 1124182208 Addr 4893392
SIn_Addr 192 xxx y c1 ß B'
Comm.Link for Receive Socket 2 SIn_Port 18975 SIn_Addr 1124182208 Addr 4893444 ß D
SIn_Addr 192 xxx y c1
Comm.Link for Transmit Socket 2 SIn_Port 18719 SIn_Addr 1124182208 Addr 4893496
SIn_Addr 192 xxx y c1
Thread item Name ExComponent
EventName ExComponent         388
Items added - Possible_Pairs 3 5 6 Comm.Data 5 6 4266298 ß C
Comm.Link for Receive Socket 3 SIn_Port 17951 SIn_Addr 1124182208 Addr 4893548
SIn_Addr 192 xxx y c1
Comm.Link for Transmit Socket 3 SIn_Port 17695 SIn_Addr 1124182208 Addr 4893600
SIn_Addr 192 xxx y c1 ß C'
for communicating between the components Component2 and ExComponent of App2 of the one PC and the RemoteComponent of App3 running on the other PC.  In this output the line marked with A is between the component pair (1,2) where Component1 with an id of 1 (in the "table" at the top) is a component in App1 that runs on the same PC.  The line marked with B is for the component pair (2,6) and its Receive and Transmit Socket data is in the four lines starting at D.  The line marked with C and the four below it are for the component pair (5,6).

As can be seen all the SIn_Addr lines show the same IP address – that of the PC on which App2 (and App1) are running with their components where all of the components are executed except RemoteComponent which runs on the second PC.

I was used to this output since I had been using just App1 to communicate between its two components (Component1 and NewComponent) and Component1 communicating with Component2 – that is, two components of one application in the first instance and two components of two different applications in the second.

So it was only after many days, after yet another attempt to duplicate the work of years ago by different code, that I realized that the IP addresses shouldn't all be alike now that RemoteComponent was involved which ran on the other PC.  That the C_Accept WinSock function should be providing the IP address of the PC to which it should be transmitting.  And it wasn't available to do so.  (I assumed at that time that the Accept was the Windows call that should use the other PC's IP address – that is, the IP address to which the Server would be sending the message.  More about that later.)

Thus the other IP address was not available in the tables that WinSock Install had created from which the above text was output.  However, it is available in the Delivery.dat file that is used to provide some of the information in the nine lines at the beginning of the sample output as shown in the Delivery.dat file above.  In the Delivery file the two instances of component id 6 (the second line and the fifth) contain the IP address of the second PC.  Therefore, in building the transmit IP address (the lines marked with B' and C') the IP address of the RemoteComponent needed to be supplied.  (Or so I assumed at the time.  That is, I assumed that the transmit would need the IP address of the computer to which it was sending the message.)

The mystery is why did it take me so long to recognize that this address wasn't being made available for the C_Accept function to use.

The Second Mystery Solved

On May 4th, after making adjustments to use the other PC's IP address as the transmit entry, as mentioned above, the connections still weren't being made.  As I went through the Microsoft WinSock material again I realized that the other PC's IP address was supposed to be for the Client – my Receive – component and not the Server – my Transmit – entry.  So I switched the usage.

What with other changes I had made in the meantime, connections still weren't being made.  But I reverted one change back to how I had had it – that is, doing the assignment of the address family, type, and protocol to the socket; the bind; and the listen back to the Xmit Install and out of the particular Xmit thread of the component where I had moved it.  This caused it to happen prior to the threads running for each of the components.  And therefore in advance of the assignment of the address family, type, and protocol followed by the connect function invocation for the client/receive that remained as the initial activity of each of the Recv threads.

I then got a very brief connection (that luckily I just happened to see) buried in the text output of the remote component of the third application that was running on the other PC.  There were apparently two causes for this only brief appearance of messages being received.  One was the security software that quickly didn't like the strange new request for service.  And which refused to believe an operator (myself) that would respond to always allow the request.  The other was that after getting WSA errors, WinSock would refuse to continue.

Success!!

However, trying often enough seemed to satisfy the security software and starting and running all three applications at close to the same time avoided the WinSock non-connection problems and hence WSA errors.  Under these conditions the components of all three applications communicated – sent and received messages – with each other and didn't stop until the applications were terminated.

Therefore, components within a particular application can communicate, components within different applications on the same computer can communicate, and components in applications on different computers can communicate.  That is, as long as all the applications of the configuration's component pairs specified in the Delivery.dat file are run at the same time.

Sample Output

App1 message sample
Component1 received a message: Component2 message  <-- 1
Xmit Callback loop 2 2 True
index of 2
Xmit after C_Accept 2
Component1 wait for event
Component1 after end of wait
Component1 sending to Component2
Transmit Index 1 DeliverTo 2
Transmit sent using socket port 16927
Component1 sending to component4
Transmit Index 2 DeliverTo 4
NewComponent received a message: Component1 message for 4 <-- 1
Transmit sent using socket port 17439
NewComponent in forever loop
Transmit Index 3 DeliverTo 1
Transmit 3 not connected, returning
Xmit Callback loop 3 3 True
index of 3
Xmit after C_Accept 3
Component1 received a message: Component2 message <-- 2
Component1 wait for event
Component1 after end of wait
Component1 sending to Component2
Transmit Index 1 DeliverTo 2
Transmit sent using socket port 16927
Component1 sending to component4
Transmit Index 2 DeliverTo 4
NewComponent received a message: Component1 message for 4 <-- 2
Transmit sent using socket port 17439
NewComponent in forever loop
Transmit Index 3 DeliverTo 1
Component1 received a message: Component4 message   3 <-- 1
Transmit sent using socket port 17183
Component1 received a message: Component2 message  <-- 3

This sample shows messages received by Component1 of App1 from Component2 of App2 and those received by the intra-App1 components – Component1 from NewComponent (also identified as Component4) and by NewComponent from Component1.  Three messages received by Component1 from Component2 of App2; two messages received by NewComponent from Component1; and one message received by Component1 from NewComponent (identified as Component4 in the message) in the sample shown.

This next sample shows messages received by Component2 of App2.
Component2 received a message: RemoteComponent message -> 1
ExComponent wait to transmit to 6
Transmit Index 2 DeliverTo 6
Transmit 2 not connected, returning
Component2 to send to Component1
Transmit Index 1 DeliverTo 1
Transmit 1 not connected, returning
Component2 to send to RemoteComponent
Transmit Index 2 DeliverTo 6
Transmit 2 not connected, returning
Xmit Callback loop 2 2 True
index of 2
Xmit after C_Accept 2
Xmit after C_Accept 1
ExComponent wait to transmit to 6
Transmit Index 2 DeliverTo 6
Transmit sent using socket port 18975
Xmit Callback loop 3 3 True
index of 3
Xmit Callback initial loop end
Component2 received a message: RemoteComponent message -> 2
Component2 to send to Component1
Transmit Index 1 DeliverTo 1
Transmit sent using socket port 16671
Component2 to send to RemoteComponent
Transmit Index 2 DeliverTo 6
Transmit sent using socket port 18975
ExComponent wait to transmit to 6
Transmit Index 2 DeliverTo 6
Transmit sent using socket port 18975
Component2 received a message: Component1 message for 2 -> 1
Component2 received a message: RemoteComponent message  -> 3
Xmit Callback loop 3 3 True
index of 3
Xmit Callback initial loop end
ExComponent wait to transmit to 6
Transmit Index 2 DeliverTo 6
Transmit sent using socket port 18975
Component2 to send to Component1
Transmit Index 1 DeliverTo 1
Transmit sent using socket port 16671
Component2 to send to RemoteComponent
Transmit Index 2 DeliverTo 6
Transmit sent using socket port 18975
Component2 received a message: Component1 message for 2 <-- 2
Component2 received a message: RemoteComponent message  <-- 4

It shows four messages received and delivered to Component2 from RemoteComponent of App3 running on the other PC and two messages received by Component2 from Component1 of App1 running on the same PC.

The third sample illustrates the messages received by the RemoteComponent of App3 running on the other PC.  This sample shows three messages received from Component2 of App2 and four messages received from the ExComponent of App2.
RemoteComponent sending to component5
RemoteComponent received a message: ExComponent message ß 1
RemoteComponent sending to component2
Transmit Index 1 DeliverTo 2
Transmit sent using socket port 18719
RemoteComponent received a message: Component2 message to 6 ß 1
RemoteComponent sending to component5
RemoteComponent received a message: ExComponent message ß 2
RemoteComponent sending to component2
Transmit Index 1 DeliverTo 2
Transmit sent using socket port 18719
RemoteComponent received a message: ExComponent message ß 3
RemoteComponent received a message: Component2 message to 6 ß 2
RemoteComponent sending to component5
RemoteComponent sending to component2
Transmit Index 1 DeliverTo 2
Transmit sent using socket port 18719
RemoteComponent received a message: ExComponent message ß 4
RemoteComponent received a message: Component2 message to 6 ß 3
RemoteComponent sending to component5

The three samples illustrate components of the same application communicating with each other, components of two different applications running on the same PC communicating with each other, and components running on two different PCs communicating with each other.  In each case just using the TCP/IP IP address and the WinSock ports assigned to the components.

Follow-On

The next problem to be solved is how to continue communicating between the running applications when not all those of the configuration are being run.

Sample output of the failure to communicate follows.

ERROR: WSALastError      10061 ç
Client Socket 3 NOT Connected
ERROR: Client Connect 3 FAILED: ç
WinSock Receive 03                 3
Receive NOT Connected 3 17695
Component2 to send to Component1
Transmit Index 1 DeliverTo 1
Transmit sent using socket port 16671
Component2 to send to RemoteComponent
Transmit Index 2 DeliverTo 6
Transmit 2 not connected, returning
ReceiveCreate Index 3
ExComponent wait to transmit to 6
Transmit Index 2 DeliverTo 6
Transmit 2 not connected, returning
Component2 received a message: Component1 message for 2 ß
ERROR: WSALastError      10061 ç
ERROR: WinSock Receive failed 2
ERROR: WSALastError          0
ERROR: WinSock Receive failed 1
ERROR: WSALastError          0Xmit after C_Accept 2

ERROR: Server Client Socket NOT accepted
ERROR: WSALastError          0
Xmit Callback initial loop end
Client Socket 3 NOT Connected
ERROR: Client Connect 3 FAILED:
WinSock Receive 03                 3
Receive NOT Connected 3 17695
ReceiveCreate Index 3
ERROR: WSALastError      10093 ç

There is the initial WSA error of 10061 for a failure between App2 and App3, then one message received for Component2 from Component1, followed by another 10061 error.  This is then followed by 10093 errors of which the first is shown.

A Microsoft on-line document says that error 10061 is for Connection refused – that the target computer actively refused the connection. That, it states, this condition usually results from trying to connect to a service that is inactive on the foreign host—that is, one with no server application running.  10093 has the comment Successful WSAStartup not yet performed or WSACleanup has been called too many times.  Since the WSAStartup was done the error would seem to be for WSACleanup called too many times.  Since there were two 10061 errors, too many times would seem to be mean more than once.

Code

Other than the new components which are clones of those presented in the first post and the changes that had to be made to Threads to allow for more threads as more components were added, which are just an extension of the previously presented code, the code not shown before is that of the TextIO package and the body of the WinSock package and its sub packages.

TextIO code

The specification
with Itf;

package TextIO is

  function Concat
  ( S1 : String;
    I2 : Integer
  ) return Itf.V_80_String_Type;

  function Concat
  ( S1 : String;
    S2 : String
  ) return Itf.V_80_String_Type;

  procedure Put_Line
  ( Text : in Itf.V_80_String_Type
  );

end TextIO;

The implementation
with Text_IO;

package body TextIO is

  function toString
  ( Int : in Natural
  ) return Itf.V_String_Type is

    Start
    -- Index of first non-zero digit
    : Integer;

    Temp1
    -- Temporary string
    : Itf.V_String_Type
    := ( Count => 0,
         Data  => ( others => '0' ) );
    Temp2
    -- Return string
    : Itf.V_String_Type
    := ( Count => 0,
         Data  => ( others => ' ' ) );

    Work
    -- Working integer
    : Integer := Int;

  begin -- toString

    for I in reverse 1..12 loop

      Temp1.Data(I) := Character'Val( Character'Pos( '0' ) + ( Work mod 10 ) );
      Temp1.Count := Temp1.Count + 1;

      Work := Work / 10;
      exit when Work = 0;

    end loop;

    if Work /= 0 then
      Text_IO.Put_Line("ERROR: Conversion Error in TextIO toString");
    end if;

    -- Remove leading 0s
    Start := 12;
    for I in 1..11 loop
      if Temp1.Data(I) /= '0' then
        Start := I;
        exit;
      end if;
      Temp1.Data(I) := ' ';
    end loop;

    -- Move digits to the beginning and into the string to be returned
    Temp2.Count := 0;
    for I in Start..12 loop
      Temp2.Count := Temp2.Count + 1;
      Temp2.Data(Temp2.Count) := Temp1.Data(I);
    end loop;

    return Temp2;

  end toString;

  function Concat -- leaving separating space
  ( S1 : String;
    I2 : Integer
  ) return Itf.V_80_String_Type is

    Temp1 : Itf.V_80_String_Type;
    Temp2 : Itf.V_String_Type;

  begin -- Concat

    Temp1.Data := ( others => ' ' );

    Temp2 := toString(abs(I2));
    Temp1.Count := S1'Length;
    Temp1.Data(1..S1'Length) := S1;
    -- Insert separating space after input string
    Temp1.Count := Temp1.Count + 1;
    Temp1.Data(Temp1.Count) := ' ';
    -- Concatenate string of converted integer
    Temp1.Data(Temp1.Count+1..Temp1.Count+Temp2.Count) :=
      Temp2.Data(1..Temp2.Count);
    Temp1.Count := Temp1.Count+Temp2.Count;

    return Temp1;

  end Concat;

  function Concat
  ( S1 : String;
    S2 : String
  ) return Itf.V_80_String_Type is

    Count : Integer;

    Temp1 : Itf.V_80_String_Type;

  begin -- Concat

    Temp1.Count := S1'Length;
    Temp1.Data(1..Temp1.Count) := S1;
    Temp1.Count := Temp1.Count + 1;
    Temp1.Data(Temp1.Count) := ' '; -- Insert separating space
    Count := Temp1.Count + S2'Length;
    Temp1.Data(Temp1.Count+1..Count) := S2;
    Temp1.Count := Count;

    return Temp1;

  end Concat;

  procedure Put_Line
  ( Text : in Itf.V_80_String_Type
  ) is
  begin -- Put_Line
    Text_IO.Put_Line(Text.Data(1..Text.Count));
  end Put_Line;

end TextIO;

WinSock Package code

No doubt the WinSock package can still use some improvements.  Here are its current packages and separates.

The specification
with ExecItf;
with Itf;
with System;

package WinSock is

  subtype Component_Ids_Type
  -- Identifier of the hosted components.
  -- Notes:
  --   This allows for a configuration with a maximum of 63 components.
  is Integer range 0..63;

  subtype Component_Name_Type
  is String(1..25);

  type ReceiveCallbackType
  -- Callback to return received message to its component
  is access procedure( Message : in String );

  -- Do overall initialization of arrays
  procedure Initialize;

  -- Add to the tables for particular component
  procedure Install
  ( ComponentId  : in Component_Ids_Type;
    Component    : in String;
    RecvCallback : in ReceiveCallbackType
  );

  -- Finalize the Comm arrays and invoke the Recv and Xmit for each pair
  procedure Finalize;

  -- Send a message to its DeliverTo component
  procedure Transmit
  ( DeliverTo : in Component_Ids_Type;
    Count     : in Itf.Message_Size_Type;
    Message   : in System.Address
  );

end WinSock;

The implementation (without the code separates)

with Text_IO;
with Unchecked_Conversion;

package body WinSock is

  package Int_IO is new Text_IO.Integer_IO( Integer );

  type Communication_Direction_Type
  is ( Receive,
       Transmit );

  type Possible_Pairs_Count_Type
  -- Range of possible component pairs that can inter-communicate
  is range 0..45;

  subtype Possible_Pairs_Index_Type
  is Possible_Pairs_Count_Type range 1..Possible_Pairs_Count_Type'Last;

  type Connection_Count_Type
  -- Number of allowed different connections that can be treated at one time
  is new Integer range 0..10;

  subtype Connection_Index_Type
  -- Index into Connection Data array
  is Connection_Count_Type range 1..Connection_Count_Type'last;

  WSAData
  -- Windows structure that contains the information on the configuration of
  -- the WinSock DLL, including the highest version available.  This structure
  -- is a record that contains
  --   wVersion      : Exec_Itf.WORD;
  --   wHighVersion  : Exec_Itf.WORD;
  --   szDescription : Exec_Itf.WSA_CHAR_Array(0..WSADESCRIPTION_LEN);
  --   szSystemStatus: Exec_Itf.WSA_CHAR_Array(0..WSASYS_STATUS_LEN);
  --   iMaxSockets   : Exec_Itf.USHORT;
  --   iMaxUdpDg     : Exec_Itf.USHORT;
  --   lpVendorInfo  : Exec_Itf.PSTR;
  : ExecItf.WSADATA;

  function to_LPWSADATA -- convert address to ExecItf.WinSock pointer
  is new Unchecked_Conversion( Source => System.Address,
                               Target => ExecItf.LPWSADATA );

  lpWSAData
  -- Pointer to Windows WSADATA structure that contains the information on the
  -- configuration of the WinSock DLL, including the highest version available
  : constant ExecItf.LPWSADATA
  := to_LPWSADATA(WSAData'address);

  type SockAddr_In
  is record
    SIn_Family : ExecItf.SHORT;   -- Internet protocol (16 bits)
    SIn_Port   : ExecItf.USHORT;  -- Address port (16 bits)
    SIn_Addr   : ExecItf.ULONG;   -- IP address (32 bits)
    SIn_Zero   : Itf.ByteArray(1..8);
  end record;
  for SockAddr_In
  use record
    SIn_Family at 0 range 0 .. 15;
    SIn_Port   at 2 range 0 .. 15;
    SIn_Addr   at 4 range 0 .. 31;
    SIn_Zero   at 8 range 0 .. 63;
  end record;
  for SockAddr_In'size use 16*8; -- bits

  type Socket_Info_Type
  –- its application to act as a WinSock server or client
  is record
    Socket : ExecItf.Socket;
    -- Handle to be supplied to accept function to obtain client socket when
    -- client application connect is accepted by this application acting as a
    -- WinSock server or handle to be supplied to connect function to obtain
    -- a connection to a remote server when this application is acting as a
    -- WinSock client
    Data : SockAddr_In;
    -- SA_family, port and IP address of the Server Socket
    Addr   : ExecItf.PSOCKADDR;
    -- Pointer to description of local address of Server Socket.  The SOCKADDR
    -- to which it points is a record that contains
    --   SA_family : u_short;
    --   SA_data   : ExecItf.WSA_CHAR_Array(0..13);
  end record;

  Null_Socket_Info
  : constant Socket_Info_Type
  := ( Socket => ExecItf.Invalid_Socket,
       Data   => ( SIn_Family => 0,
                   SIn_Port   => 0,
                   SIn_Addr   => 0,
                   SIn_Zero   => ( others => 0 ) ),
       Addr   => null );

  type Communication_Connection_Data_Type
  -- Data about server/client sockets that have requested a connection with a
  -- client or server.
  --
  -- In this peer-to-peer implementation, each application contains components
  -- and each component is both a server to other components and their client.
  -- As such each component of the pair can be connected to the other via a
  -- pair of Window socket ports. One connected as if a particular component is
  -- the WinSock server and the other as if it is the client. 
  --
  -- Along with the IP address each socket is identified via a port number;
  -- one port for the server and one for the client.
  --
  -- Since the client, as the reader, has to wait for data from its server, the
  -- client connect attempt with its receive are implemented in its own thread;
  -- one thread for each component to connect as the client.
  --
  -- Likewise, the server portion of the application has to attempt to accept
  -- connections from each of the other applications.  Therefore, the accept
  -- request must be in a loop where the accept will return when a particular
  -- client connect request is accepted.  The accept returns the new socket
  -- to be used and the IP address with the port.  Since one or more remote
  -- components may be in an application(s) that may not be running, the accept
  -- can block waiting for components to request to connect that aren't initially
  -- running.  Therefore, each server also has its own accept connection thread
  -- so the rest of the application can run while the accepts are taking place.
  --
  -- Note: The accept function returns a new socket that replaces that of the
  -- original bind and listen.
  --
  -- With both components of a communication pair acting as a server and a
  -- client of the other, each will attempt to connect to the other as a
  -- client and each will attempt to accept the other's request to connect.
  -- Therefore, each shall have a socket it obtained for the client connect
  -- request and one that it obtained for the server bind and listen and
  -- passed to its accept request (although the accept request can otherwise
  -- specify null).  For each accept a client socket will be returned to use
  -- in the communications.
  --
  -- Thus, the transmit to the other component will use the socket returned
  -- by the accept no matter what application it happens to be in.  The
  -- receive from the other application will use the socket returned by the
  -- receive thread.
  is record
    Supported : Boolean;
    -- True if Apps Configuration indicates that both applications of the
    -- connection pair support WinSock communications
    Connected : Boolean;
    -- True if this application acting as a WinSock client has connected
    -- with its server
    Created   : Boolean;
    -- True if this application acting as a WinSock server has created
    -- the Socket
    Name      : Component_Name_Type;
    -- Name by which to register the Driver Receive or Transmit components
    Socket    : Socket_Info_Type;
    -- Socket handle and IP address to attempt to connect to remote application
  end record;

  Null_Communication_Connection_Data
  : constant Communication_Connection_Data_Type
  := ( Supported => False,
       Connected => False,
       Created   => False,
       Name      => ( others => ' ' ),
       Socket    => Null_Socket_Info );

  type Communication_Connection_Type
  -- Similar data for the WinSock client and server
  is record
    Receive  : Communication_Connection_Data_Type;
    Transmit : Communication_Connection_Data_Type;
  end record;

  type Communication_Connection_Link_Type
  is array( Connection_Index_Type )
  of Communication_Connection_Type;

  type Component_Id_Pair_Type
  -- Pair of application identifiers to identify applications that can communicate
  is array( 1..2 ) of Component_Ids_Type;

  type Delivery_Table_Count_Type
  -- Range of Delivery Table entries
  is new Integer range 0..16;

  type Delivery_Table_Positions_Type
  -- Locations of the matched pair in the Delivery_Table
  is array (1..2) of Delivery_Table_Count_Type;

  type Communication_Data_Type
  is record
    Available         : Boolean;
    -- True if remote application is available in the configuration
    Bound             : Boolean;
    -- True if the Bind for the Transmit socket has succeeded
    Pair              : Component_Id_Pair_Type;
    -- Identifiers of the application pair; always lower number first
    Local_Com         : Component_Ids_Type;
    -- Identifier of running component
    Remote_Com        : Component_Ids_Type;
    -- Identifier of other (i.e., remote) component of the pair
    Receive_Wait      : ExecItf.HANDLE;
    -- Wait event handle of receive thread
    Transmit_Wait     : ExecItf.HANDLE;
    -- Wait event handle of transmit thread
    Receive_Callback  : ReceiveCallbackType;
    -- Callback to component's procedure to receive messages
    DeliveryId        : Component_Ids_Type;
    -- Delivery_Table Component Identifier for transmit
    DeliveryPosition  : Delivery_Table_Positions_Type;
    -- Pair of indexes into Delivery_Table of the matched pair of components
  end record;

  Null_Data_Info
  : constant Communication_Data_Type
  := ( Available        => False,
       Bound            => False,
       Pair             => ( 0, 0 ),
       Local_Com        => 0,
       Remote_Com       => 0,
       Receive_Wait     => System.Null_Address,
       Transmit_Wait    => System.Null_Address,
       Receive_Callback => null,
       DeliveryId       => 0,
       DeliveryPosition => ( 0, 0 )
     );

  type Communication_Data_Array_Type
  is array( Connection_Index_Type ) of Communication_Data_Type;

  type Communication_Type
  –- that receive messages from other components and servers that transmit to other
  -- components. That is, any of the components can be a server that send messages
  –- to other components as well as a client that receives messages.
  is record
    Count : Connection_Count_Type;
    -- Number of entries in the Data and Link arrays
    Data  : Communication_Data_Array_Type;
    -- Data to be used in conjunction with WinSock threads
    Link  : Communication_Connection_Link_Type;
    -- Data to be used by this application to receive from / transmit to the
    -- other components of this or other applications
  end record;

  Comm
  -- Information about threads and Microsoft Windows connections
  -- for each application
  : Communication_Type;
  pragma Volatile( Comm ); -- since accessed by multiple threads


  type Possible_Pairs_Type
  is array( Possible_Pairs_Index_Type ) of Component_Id_Pair_Type;

  Possible_Pairs
  --| Possible pairs of component ids in the Delivery.dat file
  : Possible_Pairs_Type;

  Possible_Pair_Indexes
  --| Possible pairs of applications with lower numbered indexes first
  : constant Possible_Pairs_Type
  := (  1 => ( 1, 2 ),
        2 => ( 1, 3 ),
        3 => ( 1, 4 ),
        4 => ( 1, 5 ),
        5 => ( 1, 6 ),
        6 => ( 1, 7 ),
        7 => ( 1, 8 ),
        8 => ( 1, 9 ),
        9 => ( 1, 10 ),
       10 => ( 2, 3 ),
       11 => ( 2, 4 ),
       12 => ( 2, 5 ),
       13 => ( 2, 6 ),
       14 => ( 2, 7 ),
       15 => ( 2, 8 ),
       16 => ( 2, 9 ),
       17 => ( 2, 10 ),
       18 => ( 3, 4 ),
       19 => ( 3, 5 ),
       20 => ( 3, 6 ),
       21 => ( 3, 7 ),
       22 => ( 3, 8 ),
       23 => ( 3, 9 ),
       24 => ( 3, 10 ),
       25 => ( 4, 5 ),
       26 => ( 4, 6 ),
       27 => ( 4, 7 ),
       28 => ( 4, 8 ),
       29 => ( 4, 9 ),
       30 => ( 4, 10 ),
       31 => ( 5, 6 ),
       32 => ( 5, 7 ),
       33 => ( 5, 8 ),
       34 => ( 5, 9 ),
       35 => ( 5, 10 ),
       36 => ( 6, 7 ),
       37 => ( 6, 8 ),
       38 => ( 6, 9 ),
       39 => ( 6, 10 ),
       40 => ( 7, 8 ),
       41 => ( 7, 9 ),
       42 => ( 7, 10 ),
       43 => ( 8, 9 ),
       44 => ( 8, 10 ),
       45 => ( 9, 10 )
     );

  type File_Type
  -- Delivery name and handle
  is record
    Name   : ExecItf.Config_File_Name_Type;
    -- Name of delivery data file for applications
    Handle : ExecItf.File_Handle;
    -- Handle of delivery data file after created
  end record;

  Delivery_File
  -- Name and Handle of Delivery.dat file
  : File_Type;

  type DeliveryBytesType
  is record
    Count : Integer; -- number of bytes in message
    Bytes : Itf.ByteArray(1..15);
  end record;

  type ComponentNameType
  is record
    Count : Integer; -- number of characters in name
    Value : String(1..20);
  end record;

  type Delivery_Table_Data_Type
  is record
    ComId      : Component_Ids_Type;
    -- Identifier of component of the table entry
    ComName    : ComponentNameType;
    -- Name of component of the table entry
    PCAddress  : DeliveryBytesType;
    -- IP address of PC of the component
    ComRoute   : DeliveryBytesType;
    -- IP address of the individual component
    PortServer : Integer;
    -- Identifier of the server/transmit port
    PortClient : Integer;
    -- Identifier of the client/receive port
    Partner    : Delivery_Table_Count_Type;
    -- Index of the component with the opposite ports
  end record;

  type Delivery_Table_Data_Array_Type
  is array (1..Delivery_Table_Count_Type'Last) of Delivery_Table_Data_Type;

  type Delivery_Table_Type
  -- Table of the contents of the Delivery.dat file
  is record
    Count : Delivery_Table_Count_Type;
    -- Number of valid entries in the table
    Last  : Delivery_Table_Count_Type;
    -- Last table location matched to ComponentId of component being Installed
    List  : Delivery_Table_Data_Array_Type;
    -- Space for the maximum number of entries
  end record;

  Delivery_Error
  -- Whether an error occurred in Install
  : Boolean := False;

  Delivery_Table
  -- Parsed contents of Delivery.dat file
  : Delivery_Table_Type;

  NameIndex
  -- Index of component in Delivery_Table
  : Integer := 0;

  function to_ac_SOCKADDR_t -- convert address to ExecItf.WinSock pointer
  is new Unchecked_Conversion( Source => System.Address,
                               Target => ExecItf.PSOCKADDR );

  function FindDeliveryFile
  return File_Type;

  procedure MatchComId
  ( Start : in out Possible_Pairs_Count_Type;
    ComId : in Component_Ids_Type
  );

  function MatchName
  ( Start : in Possible_Pairs_Count_Type;
    Name  : in String
  ) return Possible_Pairs_Count_Type;

  -- Parse the Delivery.dat file to create the Delivery_Table
  procedure ParseDelivery;

  -- Lookup the component and check that the table entries cross reference
  procedure DeliveryLookup
  ( ComId   : in Component_Ids_Type;
    -- Current component's id
    Pair    : in Component_Id_Pair_Type;
    -- Pair of component ids to be looked up
    Matched : out Boolean;
    -- Whether the pair is in the Delivery table
    OtherId : out Component_Ids_Type;
    -- Other component id of the matched pair
    Indexes : out Delivery_Table_Positions_Type
    -- Locations of matched pair in the table
  );

  -- Validate the Delivery_Table; return True if not invalid
  function ValidateDelivery
  return Boolean;

  function to_Digit
  ( Number : in Integer
  ) return Character;

  package Recv is
  -- Receive message from a particular component

    procedure Install
    ( Id : in Connection_Count_Type
      -- Identifier of the component
    );

  end Recv;

  package Xmit is
  -- Transmit message to a particular component

    procedure Install
    ( Id : in Connection_Count_Type
      -- Identifier of the component
    );

  end Xmit;


  -- Separate declarations

  procedure Finalize is separate;

  function FindDeliveryFile
  return File_Type is separate;

  procedure Initialize is separate;

  procedure Install
  ( ComponentId  : in Component_Ids_Type;
    Component    : in String;
    RecvCallback : in ReceiveCallbackType
  ) is separate;

  procedure MatchComId
  ( Start : in out Possible_Pairs_Count_Type;
    ComId : in Component_Ids_Type
  ) is separate;

  function MatchName
  ( Start : in Possible_Pairs_Count_Type;
    Name  : in String
  ) return Possible_Pairs_Count_Type is separate;

  procedure ParseDelivery is separate;

  function ValidateDelivery
  return Boolean is separate;

  procedure DeliveryLookup
  ( ComId   : in Component_Ids_Type;
    Pair    : in Component_Id_Pair_Type;
    Matched : out Boolean;
    OtherId : out Component_Ids_Type;
    Indexes : out Delivery_Table_Positions_Type
  ) is separate;

  function to_Digit
  ( Number : in Integer
  ) return Character is
  -- Convert number from 1 thru 9 to a alpha digit.

  begin -- to_Digit

    case Number is
      when 1 => return '1';
      when 2 => return '2';
      when 3 => return '3';
      when 4 => return '4';
      when 5 => return '5';
      when 6 => return '6';
      when 7 => return '7';
      when 8 => return '8';
      when 9 => return '9';
      when others =>
        Text_IO.Put("ERROR: to_Digit for Number not 1 thru 0");
        Int_IO.Put(Number);
        Text_IO.Put_Line(" ");
        return '0';
    end case;

  end to_Digit;

  procedure Transmit
  ( DeliverTo : in Component_Ids_Type;
    Count     : in Itf.Message_Size_Type;
    Message   : in System.Address
  ) is separate;

  package body Recv is separate;

  package body Xmit is separate;

end WinSock;

The Delivery.dat file read/parse and table procedures/functions code

with CStrings;
with Directory;
with GNAT.IO;
with Text_IO;

separate( WinSock )

function FindDeliveryFile
return File_Type is
-- Notes: If running via GPS the folder that contains the gpr file seems to be
--        the current directory.  If running from a DOS window of the Build
--        folder, that is the current directory.  If run the exe file while
--        in the folder of .dat file, that's the current directory.

  package Int_IO is new Text_IO.Integer_IO( Integer );

  DeliveryFile
  : Itf.V_Long_String_Type;

  Last
  : String(1..5);

  Path
  : Itf.V_Long_String_Type;

  Result
  -- Create result
  : Integer;

  use type ExecItf.File_Handle;

begin -- FindDeliveryFile

  -- Get the current directory/folder.
  Path := Directory.GetCurrentDirectory;

  -- Attempt to open "Delivery.dat" file containing the current WinSock data
  -- to deliver messages.
  DeliveryFile.Data(1..Path.Count) := Path.Data(1..Path.Count);
  DeliveryFile.Data(Path.Count+1..Path.Count+12) := "Delivery.dat";
  DeliveryFile.Count := Path.Count+12;

  Delivery_File := ( Name   => ( others => ASCII.NUL ),
                     Handle => ExecItf.Invalid_File_Handle );
  Delivery_File.Name(1..DeliveryFile.Count) :=
    DeliveryFile.Data(1..DeliveryFile.Count);

  Delivery_File.Handle := ExecItf.Open_Read( Name => Delivery_File.Name );

  if Delivery_File.Handle = ExecItf.Invalid_File_Handle then
    Result := Integer(ExecItf.GetLastError);
    Text_IO.Put("Delivery file doesn't exist");
    Int_IO.Put(Integer(Result));
    Text_IO.Put_Line(" ");

    -- Not in current directory.  Try previous directories.
    WhileLoop:
    while Delivery_File.Handle = ExecItf.Invalid_File_Handle loop
      for I in reverse 1..Path.Count-1 loop
        -- Find the previous backslash.
        if Path.Data(I) = '\' then
          DeliveryFile.Data(1..I) := Path.Data(1..I);
          DeliveryFile.Data(I+1..I+12) := "Delivery.dat";
          DeliveryFile.Count := I+12;
          Text_IO.Put_Line(DeliveryFile.Data(1..I+12));
          Path.Count := I; -- where '\' was found
          Text_IO.Put("next path that will be searched ");
          Text_IO.Put_Line(Path.Data(1..Path.Count));

          Delivery_File := ( Name   => ( others => ASCII.NUL ),
                             Handle => ExecItf.Invalid_File_Handle );
          Delivery_File.Name(1..DeliveryFile.Count) :=
            DeliveryFile.Data(1..DeliveryFile.Count);

          Delivery_File.Handle := ExecItf.Open_Read
                                  ( Name => Delivery_File.Name );
          if Delivery_File.Handle = ExecItf.Invalid_File_Handle then
            if I < 5 then
              exit WhileLoop; -- not going to be found in the path
            end if;
          else
            exit WhileLoop;
          end if;
        end if;
      end loop;
    end loop WhileLoop;

    if Delivery_File.Handle = ExecItf.Invalid_File_Handle then
      -- Not in previous directories.  Prompt for the Path.
      Text_IO.Put("Enter the path to the Delivery.dat file: ");
      GNAT.IO.Get_Line( DeliveryFile.Data, DeliveryFile.Count );
      -- Check whether the .dat file was included
      Last(1..4) := DeliveryFile.Data
                    (DeliveryFile.Count-3..DeliveryFile.Count);
      Last(5) := ASCII.NUL;
      declare
        Dat : String(1..5) := ".dat ";
      begin
        Dat(5) := ASCII.NUL;
        if (CStrings.Compare(Last'Address,Dat'Address,true) = 0) then
          -- Check whether the trailing \ was entered
          if DeliveryFile.Data(DeliveryFile.Count) /= '\' then
            DeliveryFile.Count := DeliveryFile.Count + 1;
            DeliveryFile.Data(DeliveryFile.Count) := '\';
          end if;
          -- Append the file name
          DeliveryFile.Data(DeliveryFile.Count+1..DeliveryFile.Count+12) :=
            "Delivery.dat";
          Delivery_File.Name := ( others => ASCII.NUL );
          Delivery_File.Name(1..DeliveryFile.Count+12) :=
            DeliveryFile.Data(1..DeliveryFile.Count+12);
          Text_IO.Put("New path ");
          Text_IO.Put_Line(DeliveryFile.Data(1..DeliveryFile.Count+12));
          -- Attempt to open the file
          Delivery_File.Handle := ExecItf.Open_Read( Name => Delivery_File.Name );
          if Delivery_File.Handle = ExecItf.Invalid_File_Handle then
            Result := Integer(ExecItf.GetLastError);
            Text_IO.Put("Entered Configuration file of ");
            Text_IO.Put(Delivery_File.Name(1..DeliveryFile.Count));
            Text_IO.Put_Line(" doesn't exist");
          end if;
        end if;
      end;
    end if;

  end if;

  return Delivery_File;

end FindDeliveryFile;


with CStrings;

separate( WinSock )

procedure ParseDelivery is

  package Int_IO is new Text_IO.Integer_IO( Integer );

  Max_File_Size
  : constant Integer := 1000;

  type FileDataType is new String(1..Max_File_Size);

  CR1         : Itf.Byte := 16#0D#; --'\r'
  CR          : Character;
  for CR use at CR1'Address;
  NL1         : Itf.Byte := 16#0A#; -- '\n'
  NL          : Character;
  for NL use at NL1'Address;
  Delimiter   : Character := '|';

  Bytes_Read
  -- Number of bytes read from Delivery.dat file
  : Integer := 0;

  Delivery_Data
  -- Data read from Delivery.dat file
  : FileDataType;

  Field
  -- Field of record being parsed
  : Integer := 0;

  I
  -- Index into Delivery_Data
  : Integer := 0;

  Index
  -- Index into Temp
  : Integer := 0;

  Result
  -- last error result
  : Integer;

  Success
  -- ReadFile return
  : Boolean;

  Temp
  : String(1..40);

  use type ExecItf.File_Handle;
 
begin -- ParseDelivery

  Delivery_Table.Count := 0;
  Delivery_Error := False;

  -- Obtain the IP address associated with the PC and that assigned to this
  -- component.

  -- First, obtain the path of the delivery file containing what should be a
  -- representation of the PC's static route table and open it.
  Delivery_File := FindDeliveryFile;

  -- Return if Configuration File not opened
  if Delivery_File.Handle = ExecItf.Invalid_File_Handle then
    Text_IO.Put_Line("ERROR: Delivery file not found");
    Delivery_Error := True;
    return;
  end if;
 
  -- Fill-in the Delivery_Table from the Delivery_File
  Bytes_Read := ExecItf.Read_File
                ( File => Delivery_File.Handle,  -- handle of disk file
                  Addr => Delivery_Data'address, -- buffer to receive data
                  Num  => Max_File_Size );       -- size of the buffer
  if Bytes_Read <= 0 then
    Result := Integer(ExecItf.GetLastError);
    Delivery_Error := True;
    return;
  end if;

  -- Close the file
  Success := ExecItf.Close_File( Handle => Delivery_File.Handle );

  -- Parse the delivery file data.
  Field := 0;
  I := 0;
  while I < Bytes_Read loop
    I := I + 1;
    if Field = 6 then
      -- Bypass end of line characters
      if Delivery_Data(I) = CR or else Delivery_Data(I) = NL then
        null;
      else
        Index := Index + 1;
        Temp(Index) := Delivery_Data(I); -- retain character for next phase
        Field := 0; -- start over for next application
       end if;
    else -- parse within the record
      if Delivery_Data(I) /= Delimiter then
        Index := Index + 1;
        Temp(Index) := Delivery_Data(I); -- retain byte
      else -- treat field prior to delimiter
        if Field = 0 then -- First get component id
          declare
            Success : Boolean;
          begin
            Delivery_Table.Count := Delivery_Table.Count + 1;
            Temp(Index+1) := ASCII.NUL; -- append trailing NUL
            CStrings.TryParse( From    => Temp'Address,
                               Size    => Index,
                               Result  => Delivery_Table.List(Delivery_Table.Count).ComId,
                               Success => Success );
          end;
          Index := 0;
         
        elsif Field = 1 then -- Next get component name
          declare
            StrData : String(1..Index);
            for StrData use at Temp'Address;
          begin
            Delivery_Table.List(Delivery_Table.Count).ComName.Value :=
              ( others => ' ' );
            Delivery_Table.List(Delivery_Table.Count).ComName.Count := Index;
            for J in 1..Index loop
              Delivery_Table.List(Delivery_Table.Count).ComName.Value(J) :=
                StrData(J);
            end loop;
          end;
          Index := 0;
        elsif Field = 2 then -- IP address of PC
          declare
            ByteData : Itf.ByteArray(1..Index);
            for ByteData use at Temp'Address;
          begin
            Delivery_Table.List(Delivery_Table.Count).PCAddress.Bytes :=
              ( others => 0 );
            Delivery_Table.List(Delivery_Table.Count).PCAddress.Count := Index;
            Delivery_Table.List(Delivery_Table.Count).PCAddress.Bytes(1..Index) :=
              ByteData(1..Index);
          end;
          Index := 0;
        elsif Field = 3 then -- Route Table IP address of component
          declare
            ByteData : Itf.ByteArray(1..Index);
            for ByteData use at Temp'Address;
          begin
            Delivery_Table.List(Delivery_Table.Count).ComRoute.Bytes :=
              ( others => 0 );
            Delivery_Table.List(Delivery_Table.Count).ComRoute.Count := Index;
            Delivery_Table.List(Delivery_Table.Count).ComRoute.Bytes(1..Index) :=
              ByteData(1..Index);
          end;
          Index := 0;
        elsif Field = 4 then -- Port to use for Server
          declare
            Success : Boolean;
          begin
            Temp(Index+1) := ASCII.NUL; -- append trailing NUL
            CStrings.TryParse
            ( From    => Temp'Address,
              Size    => Index,
              Result  => Delivery_Table.List(Delivery_Table.Count).PortServer,
              Success => Success );
            if not Success then
              Text_IO.Put_Line
                ("ERROR: Delivery.dat contains non-numeric value for Server Port");
            end if;
          end;
          Index := 0;
        else -- Port to use for Client
          declare
            Success : Boolean;
          begin
            Temp(Index+1) := ASCII.NUL; -- append trailing NUL
            CStrings.TryParse
            ( From    => Temp'Address,
              Size    => Index,
              Result  => Delivery_Table.List(Delivery_Table.Count).PortClient,
              Success => Success );
            if not Success then
              Text_IO.Put_Line
                ("ERROR: Delivery.dat contains non-numeric value for Client Port");
            end if;
          end;
          Index := 0;
        end if;
        Field := Field + 1;
      end if;
    end if;
  end loop;

end ParseDelivery;


separate( WinSock )

procedure DeliveryLookup
( ComId   : in Component_Ids_Type;
  Pair    : in Component_Id_Pair_Type;
  Matched : out Boolean;
  OtherId : out Component_Ids_Type;
  Indexes : out Delivery_Table_Positions_Type
) is

  OtherComponent : Component_Ids_Type := 0;

  Partner1 : Delivery_Table_Count_Type;
  Partner2 : Delivery_Table_Count_Type;
 
begin -- DeliveryLookup

  -- Determine if one component of the pair is the current component
  if Pair(1) = ComId then
    OtherComponent := Pair(2);
  elsif Pair(2) = ComId then
    OtherComponent := Pair(1);
  end if;
  -- return if Pair doesn't contain the component's identifier
  if OtherComponent = 0 then -- No match of ComId in pair
    Matched := False;
    OtherId := 0;
    Indexes := (0,0);
    return;
  end if;

  -- Determine if each component of the Pair cross references the other
  for I in Delivery_Table.Last+1..Delivery_Table.Count loop
    -- Does the Delivery Table location contain one of the pair?
    if Delivery_Table.List(I).ComId = ComId then
      Partner1 := Delivery_Table.List(I).Partner;
      if Partner1 > I then -- not already examined
        -- Find any matching partner
        for J in I+1..Delivery_Table.Count loop
          Partner2 := Delivery_Table.List(J).Partner;
          if Delivery_Table.List(Partner1).ComId = OtherComponent and then
             Delivery_Table.List(Partner1).Partner = Partner2
          then -- matched
            Matched := True;
            OtherId := OtherComponent;
            Indexes(1) := I;
            Indexes(2) := J;
            Delivery_Table.Last := I;
            return;
          end if;
        end loop;
      end if;
    elsif Delivery_Table.List(I).ComId = OtherComponent then
      Partner1 := Delivery_Table.List(I).Partner;
      if Partner1 > I then -- not already examined
        -- Find any matching partner
        for J in I+1..Delivery_Table.Count loop
          Partner2 := Delivery_Table.List(J).Partner;
          if Delivery_Table.List(Partner1).ComId = ComId and then
             Delivery_Table.List(Partner1).Partner = Partner2
          then -- matched
            Matched := True;
            OtherId := OtherComponent;
            Delivery_Table.Last := I;
            Indexes(1) := J;
            Indexes(2) := I;
            return;
          end if;
        end loop;
      end if;
    end if;
  end loop;

  -- No match of component partners in pair
  Matched := False;
  OtherId := 0;
  Indexes := (0,0);
  return;

end DeliveryLookup;


separate( WinSock )

function ValidateDelivery
return Boolean is

  Count   : Integer := 0;
  Failure : Boolean := False;

begin -- ValidateDelivery

  for I in 1..Delivery_Table.Count loop

    Delivery_Table.List(I).Partner := 0;

    -- Check that ComId is within range
    if Delivery_Table.List(I).ComId > 0 and then
      Delivery_Table.List(I).ComId <=
        Component_Ids_Type(Delivery_Table_Count_Type'Last)
    then
      null;
    else
      Text_IO.Put_Line("ERROR: Delivery.dat ComId is out-of-range");
    end if;
     
    -- Check that an entry with a duplicate ComId has the same ComName
    for J in I+1..Delivery_Table.Count loop
      if Delivery_Table.List(J).ComId = Delivery_Table.List(I).ComId then
        if Delivery_Table.List(J).ComName /= Delivery_Table.List(I).ComName then
          Text_IO.Put
            ("WARNING: ComponentName mismatch between Delivery.dat records at");
          Int_IO.Put(Integer(I));
          Text_IO.Put(" and");
          Int_IO.Put(Integer(J));
          Text_IO.Put_Line(" ");
        end if;
      end if;
    end loop;

    -- Check that IP addresses are in dot notation
    Count := Delivery_Table.List(I).PCAddress.Count;
    declare
      Dots  : Integer := 0;
      Previous : Integer := 1;
      AsString : String(1..Count);
      for AsString use at Delivery_Table.List(I).PCAddress.Bytes(1)'address;
    begin
      CheckDot:
      for S in Previous..Count loop
        if AsString(S) = '.' then -- dot found
          Dots := Dots + 1;
          for D in Previous..S-1 loop
            if AsString(D) not in '0'..'9' then
              Text_IO.Put(AsString);
              Text_IO.Put(" contains invalid dotted IP formatting at record");
              Int_IO.Put(Integer(I));
              Text_IO.Put_Line(" ");
              exit CheckDot; -- loop
            end if;
          end loop;
          Previous := S+1;
          if Dots = 3 then
            for D in Previous..Count loop
              if AsString(D) not in '0'..'9' then
                Text_IO.Put(AsString);
                Text_IO.Put(" contains invalid dotted IP formatting at record");
                Int_IO.Put(Integer(I));
                Text_IO.Put_Line(" ");
                exit CheckDot; -- loop
              end if;
            end loop;
            exit CheckDot; -- finished with all the bytes
          end if;
        end if;
      end loop CheckDot;
    end;

-- add check the other IP field
-- add check format and values of PCAddress

    -- Check PortServer and PortClient for some range of values
    if (Delivery_Table.List(I).PortServer < 8000 or else
        Delivery_Table.List(I).PortServer > 9999) or else
       (Delivery_Table.List(I).PortClient < 8000 or else
        Delivery_Table.List(I).PortClient > 9999)
    then
      Text_IO.Put_Line
        ("ERROR: Server or Client Port not within selected range of 8000-9999");
    end if;
   
-- add check that another record doesn't have the same PortServer or the same PortClient

    -- Find component partner of this entry
    for J in 1..Delivery_Table.Count loop
      if I /= J then -- avoid current entry
        if Delivery_Table.List(I).PortServer =
           Delivery_Table.List(J).PortClient and then
           Delivery_Table.List(I).PortClient =
           Delivery_Table.List(J).PortServer
        then
          Delivery_Table.List(I).Partner := J;
          exit; -- inner loop; can't be more than one partner
        end if;
      end if;
    end loop;

  end loop; -- for I

-- add check that there are no entries without a Partner?? or just
-- issue a warning.

  -- Issue a warning if entry has no partner.
  for I in 1..Delivery_Table.Count loop
    if Delivery_Table.List(I).Partner = 0 then
      declare
        ComName : String(1..Delivery_Table.List(I).ComName.Count);
        for ComName use at Delivery_Table.List(I).ComName.Value'Address;
      begin
        Text_IO.Put("WARNING: Delivery.dat lacks a partner component for");
        Int_IO.Put(Delivery_Table.List(I).ComId);
        Text_IO.Put(" ");
        Text_IO.Put_Line(ComName);
      end;
    end if;
  end loop;

  return not Failure;

end ValidateDelivery;

The Initialize, Install, and Finalize procedures of my WinSock package.

with CStrings;
with GNAT.OS_Lib;
with Text_IO;

separate( WinSock )

procedure Initialize is

  Win_Status
  -- Result of WSAStartup call
  : ExecItf.INT;

  use type ExecItf.INT;

begin -- Initialize

  --   Do the Windows sockets initialization.
  Win_Status := ExecItf.WSAStartup( VersionRequired => 16#0202#, -- version 2.2
                                    WSAData         => lpWSAData );
  if Win_Status /= 0 then
    Text_IO.Put("ERROR: WinSock WSAStartup failed");
    Int_IO.Put(Integer(Win_Status));
    Text_IO.Put_Line(" ");
    return;
  end if;

  -- Initialize communication array.
  -- Notes:
  --   The array has been set to null when declared.  The Comm.Link Transmit
  --   Socket will have its last value filled in when a connection is accepted.

  Comm.Count := 0;
  for I in Connection_Index_Type loop
    Comm.Data(I) := Null_Data_Info;
    Comm.Link(I).Receive  := Null_Communication_Connection_Data;
    Comm.Link(I).Transmit := Null_Communication_Connection_Data;
  end loop;

  -- Build Delivery_Table from Delivery.dat file
  ParseDelivery;
  -- Validate that table doesn't contain extraneous entries
  if not ValidateDelivery then
    -- quit if Delivery.dat built an invalid table
    GNAT.OS_Lib.OS_Exit(0);
  end if;

  Delivery_Table.Last := 0;
  for I in 1..Delivery_Table.Count loop
    Int_IO.Put(Integer(Delivery_Table.List(I).ComId));
    Text_IO.Put(" ");
    Text_IO.Put(String(Delivery_Table.List(I).ComName.Value));
    Int_IO.Put(Integer(Delivery_Table.List(I).Partner));
    Text_IO.Put_Line(" ");
  end loop;

  –- Initialize Possible Pairs
  for I in Possible_Pairs_Index_Type'range loop
    Possible_Pairs(I) := ( 0, 0 );
  end loop;

  -- Look up name of host PC and display
  declare
    HostLen  : Integer := 0;
    HostName : String(1..25) := ( others => ASCII.Nul );
    Prefix   : String(1..11) := "Host Name  ";
    ResultStr: String(1..40);
    Result   : CStrings.SubStringType;
    function to_PSTR is new Unchecked_Conversion( Source => System.Address,
                                                  Target => ExecItf.PSTR );
  begin
    Win_Status := ExecItf.GetHostName( Name    => to_PSTR(HostName'address),
                                       NameLen => 20 );
    if Win_Status = 0 then
      for I in 1..25 loop
        if HostName(I) = ASCII.Nul then
          HostLen := I - 1;
          exit;
        end if;
      end loop;
      Prefix(11) := ASCII.NUL;
      if HostLen > 0 then
        Result := (40,ResultStr'Address);
        CStrings.Append(Prefix'Address, HostName'Address, Result);
        Text_IO.Put_Line(ResultStr(1..Result.Length));
      end if;
    end if;
  end;

end Initialize;


with CStrings;
with TextIO;
with Threads;

separate( WinSock )

procedure Install
( ComponentId  : in Component_Ids_Type;
  Component    : in String;
  RecvCallback : in ReceiveCallbackType
) is

  package Int_IO is new Text_IO.Integer_IO( Integer );

  Digit
  -- Digit of remote component identifier ('1', '2', '3', '4', ...)
  : Character;

  IC
  -- Index into Comm arrays
  : Connection_Count_Type;

  InitialCommCount
  -- Comm.Count upon entry
  : Connection_Count_Type;

  Index
  -- Index into Temp
  : Integer := 0;

  PCAddress
  -- Server IP address
  : DeliveryBytesType;

  Port
  -- Server port id number
  : Natural;

  Default_WinSock_Receive_Component_Name
  -- WinSock receive name without digit identifying client application
  : constant Component_Name_Type
  := "WinSock Receive 00       ";

  Default_WinSock_Server_Accept_Component_Name
  -- WinSock server accept connection name without digit identifying client application
  : constant Component_Name_Type
  := "WinSock Server Accept 00 ";

  use type ExecItf.INT;
  use type ExecItf.File_Handle;

  function to_Integer is new Unchecked_Conversion -- for debug
                             ( Source => ExecItf.PSOCKADDR,
                               Target => Integer );
  function to_Ptr is new Unchecked_Conversion
                         ( Source => System.Address,
                           Target => ExecItf.PCSTR );
  function to_Callback is new Unchecked_Conversion
                              ( Source => System.Address,
                                Target => Threads.CallbackType );
  function callback_toInt is new Unchecked_Conversion
                                 ( Source => ReceiveCallbackType,
                                   Target => Integer );

begin -- Install

  -- Reinitialize for a new search for matching components.
  Delivery_Table.Last := 0;
  InitialCommCount := Comm.Count;

  -- Fill in server addresses to allow each remote client that supports
  -- WinSock to connect to a known server if the local app supports WinSock.
  declare

    OtherComponent : Component_Ids_Type;
    -- The other component of the matched Pair
    ComId      : Possible_Pairs_Count_Type;
    Index      : Possible_Pairs_Count_Type;
    Indexes    : Delivery_Table_Positions_Type;
    ItemAdded  : Boolean;
    Matched    : Boolean; -- True if Pair matched to Delivery_Table entries
    Pair       : Component_Id_Pair_Type;

  begin

    ComId      := Possible_Pairs_Count_Type(ComponentId);
    Index      := 1;

    FindPair:
    for I in Possible_Pairs_Index_Type'range loop
      ItemAdded := False;
      Pair := Possible_Pair_Indexes(I);
      DeliveryLookup( ComId   => ComponentId,
                      Pair    => Pair,
                      Matched => Matched,
                      OtherId => OtherComponent,
                      Indexes => Indexes);
      if Matched then
        IC := Connection_Count_Type(Index) + InitialCommCount;
        Comm.Data(IC).Pair := Pair;
        Comm.Data(IC).Local_Com := ComponentId;
        if Pair(1) = ComponentId then
          Comm.Data(IC).Receive_Callback := RecvCallback;
          Comm.Data(IC).Remote_Com := Pair(2);
        else -- Pair(2) = ComponentId
            Comm.Data(IC).Receive_Callback := RecvCallback;
            Comm.Data(IC).Remote_Com := Pair(1);
        end if;
        Comm.Data(IC).DeliveryPosition := Indexes;

        -- display possible pair
        declare
          Text : Itf.V_80_String_Type;
        begin
          Text.Data(1..28) := "Items added - Possible_Pairs";
          Text := TextIO.Concat( Text.Data(1..28), Integer(IC) );
          Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(Pair(1)) );
          Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(Pair(2)) );
          Text := TextIO.Concat( Text.Data(1..Text.Count), "Comm.Data" );
          Text := TextIO.Concat( Text.Data(1..Text.Count),
                                 Integer(Comm.Data(IC).Local_Com) );
          Text := TextIO.Concat( Text.Data(1..Text.Count),
                                 Integer(Comm.Data(IC).Remote_Com) );
          Text := TextIO.Concat( Text.Data(1..Text.Count),
                                 callback_toInt(Comm.Data(IC).Receive_Callback) );
          TextIO.Put_Line(Text);
        end;

        ItemAdded := True;
        Comm.Count := Comm.Count + 1;

      end if; -- Matched

      exit when Index = Possible_Pairs_Index_Type'last or else
                ( Possible_Pair_Indexes(I)(1) > ComponentId and then
                  Possible_Pair_Indexes(I)(2) > ComponentId );
      if ItemAdded then
        Index := Index + 1;
      end if;
    end loop FindPair; -- for I in range
  end;

  -- Finish initialize of communication array.
  -- Notes:
  --   The array has been set to null when declared.  The Comm.Link Transmit
  --   Socket will be filled in when a connection is accepted.

  declare
    CIndex : Possible_Pairs_Count_Type; -- Client/Receive index
    SIndex : Possible_Pairs_Count_Type; -- Server/Transmit index
  begin
    for I in InitialCommCount+1..Comm.Count loop
      CIndex := Possible_Pairs_Count_Type(
                  Comm.Data(Connection_Count_Type(I)).DeliveryPosition(2));
      SIndex := Possible_Pairs_Count_Type(
                  Comm.Data(Connection_Count_Type(I)).DeliveryPosition(1));
      Port := Delivery_Table.List(Delivery_Table_Count_Type(CIndex)).PortClient;
      PCAddress := Delivery_Table.List(Delivery_Table_Count_Type(CIndex)).PCAddress;
      Comm.Link(I).Receive.Socket.Data.SIn_Family :=
        ExecItf.AF_INET; -- Internet address family
      Comm.Link(I).Receive.Socket.Data.SIn_Port :=
        ExecItf.htons(ExecItf.USHORT(Port));
      Comm.Link(I).Receive.Socket.Data.SIn_Addr :=
        ExecItf.inet_addr(to_Ptr(PCAddress.Bytes'Address));
      Comm.Link(I).Receive.Socket.Addr :=
        to_ac_SOCKADDR_t(Comm.Link(I).Receive.Socket.Data'address);
      -- Note: The Server/Transmit index is that of the 2nd DeliveryPosition.
      --       However, setting the Port continues to use the Client/Receive
      --       index since it uses the .PortServer selection.  That is, the
      --       other half of the pair of ports that are reversed in the Delivery
      --       file since the first of the pair is the Client/Receive port and
      --       the second is the Server/Transmit port.
      -- Note: The Transmit socket will be modified when the connection is accepted.
      Port := Delivery_Table.List(Delivery_Table_Count_Type(CIndex)).PortServer;
      PCAddress := Delivery_Table.List(Delivery_Table_Count_Type(SIndex)).PCAddress;
      Comm.Link(I).Transmit.Socket.Data.SIn_Family :=
        ExecItf.AF_INET; -- Internet address family
      Comm.Link(I).Transmit.Socket.Data.SIn_Port :=
        ExecItf.htons(ExecItf.USHORT(Port));
      Comm.Link(I).Transmit.Socket.Data.SIn_Addr :=
        ExecItf.inet_addr(to_Ptr(PCAddress.Bytes'Address));
      Comm.Link(I).Transmit.Socket.Addr :=
        to_ac_SOCKADDR_t(Comm.Link(I).Transmit.Socket.Data'address);
      declare
        Text : Itf.V_80_String_Type;
      begin
        Text.Data(1..28) := "Comm.Link for Receive Socket";
        Text := TextIO.Concat( Text.Data(1..28), Integer(I) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), "SIn_Port" );
        Text := TextIO.Concat( Text.Data(1..Text.Count),
                               Integer(Comm.Link(I).Receive.Socket.Data.SIn_Port) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), "SIn_Addr" );
        Text := TextIO.Concat( Text.Data(1..Text.Count),
                               Integer(Comm.Link(I).Receive.Socket.Data.SIn_Addr) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), "Addr" );
        Text := TextIO.Concat( Text.Data(1..Text.Count),
                               to_Integer(Comm.Link(I).Receive.Socket.Addr) );
        TextIO.Put_Line( Text );
      end;
      declare
        Text : Itf.V_80_String_Type;
        IPAddr : Itf.ByteArray(1..4);
        for IPAddr use at Comm.Link(I).Receive.Socket.Data.SIn_Addr'address;
      begin
        Text.Count := 8;
        Text.Data(1..8) := "SIn_Addr";
        Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(IPAddr(1)) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(IPAddr(2)) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(IPAddr(3)) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(IPAddr(4)) );
        TextIO.Put_Line( Text );
      end;
      declare
        Text : Itf.V_80_String_Type;
      begin
        Text.Data(1..29) := "Comm.Link for Transmit Socket";
           --              1234567890123456789012345678
        Text := TextIO.Concat( Text.Data(1..29), Integer(I) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), "SIn_Port" );
        Text := TextIO.Concat( Text.Data(1..Text.Count),
                               Integer(Comm.Link(I).Transmit.Socket.Data.SIn_Port) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), "SIn_Addr" );
        Text := TextIO.Concat( Text.Data(1..Text.Count),
                               Integer(Comm.Link(I).Transmit.Socket.Data.SIn_Addr) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), "Addr" );
        Text := TextIO.Concat( Text.Data(1..Text.Count),
                               to_Integer(Comm.Link(I).Transmit.Socket.Addr) );
        TextIO.Put_Line( Text );
      end;
      declare
        Text : Itf.V_80_String_Type;
        IPAddr : Itf.ByteArray(1..4);
        for IPAddr use at Comm.Link(I).Transmit.Socket.Data.SIn_Addr'address;
      begin
        Text.Count := 8;
        Text.Data(1..8) := "SIn_Addr";
        Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(IPAddr(1)) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(IPAddr(2)) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(IPAddr(3)) );
        Text := TextIO.Concat( Text.Data(1..Text.Count), Integer(IPAddr(4)) );
        TextIO.Put_Line( Text );
      end;
      declare
        Text : Itf.V_80_String_Type;
        Byte_Data : Itf.ByteArray(1..24);
        for Byte_Data use at Comm.Link(I).Transmit.Socket'address;
      begin
        Text.Count := 1;
        Text.Data(1..1) := " ";
        for B in 1..12 loop
          Text := TextIO.Concat( Text.Data(1..Text.Count),  Integer(Byte_Data(B)) );
        end loop;
        for B in 13..24 loop
          Text := TextIO.Concat( Text.Data(1..Text.Count),  Integer(Byte_Data(B)) );
        end loop;
        TextIO.Put_Line( Text );
      end;

      Digit := to_Digit(Integer(I));
      Comm.Link(I).Receive.Name := Default_WinSock_Receive_Component_Name;
      Comm.Link(I).Receive.Name(18) := Digit;
      Comm.Link(I).Transmit.Name := Default_WinSock_Server_Accept_Component_Name;
      Comm.Link(I).Transmit.Name(24) := Digit;

    end loop;

  end;

end Install;


separate( WinSock )

procedure Finalize is

begin -- Finalize

  -- Set the Receive_Index and Receive and Transmit Supported
  for I in 1..Comm.Count loop
    FindRemoteCom:
    for J in 1..Comm.Count loop
      if Comm.Data(J).Local_Com = Comm.Data(I).Local_Com and then
         Comm.Data(J).DeliveryId = 0 -- not set as yet set
      then
        for K in 1..Comm.Count loop
          if Comm.Data(J).Remote_Com = Comm.Data(K).Remote_Com then
            Comm.Link(I).Receive.Supported := True;
            Comm.Link(I).Transmit.Supported := True;
            Comm.Data(J).DeliveryId :=
              Component_Ids_Type(Comm.Data(K).Remote_Com);
            exit FindRemoteCom; -- loop
          end if;
        end loop;
      end if;
    end loop FindRemoteCom;
  end loop;

  -- Install threads for receive "components".
  for I in 1..Comm.Count loop
    Text_IO.Put("WinSock-Install Recv Index");
    Int_IO.Put(Integer(I));
    if Comm.Link(I).Transmit.Supported then
      Text_IO.Put_Line(" Transmit.Supported");
    else
      Text_IO.Put_Line(" ");
    end if;
    if Comm.Link(I).Transmit.Supported then
      Recv.Install( Id => I );
      -- Install threads for transmit "component".
      Text_IO.Put("WinSock-Install Xmit Index");
      Int_IO.Put(Integer(I));
      Text_IO.Put_Line(" ");
      Xmit.Install( Id => I );
    end if;
  end loop;

end Finalize;

The Transmit procedure is to be used by components to send a message to another component.  It uses the client socket of the accept of the Xmit Callback that replaces the original value stored in the Comm.Link(Index).Transmit.Socket.Socket location.  Hence it doesn't execute the Send until Connected is indicated.  Therefore, it can be invoked directly by a component to transmit a message but the request will be ignored until there is a connection to the other component.

Since this procedure can be invoked from multiple component threads the only static data that it uses is that of the Comm arrays for the particular component.  The other variables are on the stack.  Therefore, if one component thread suspends another, it will use its own stack and not overwrite the variables of the suspended thread.

with CStrings;
with TextIO;

separate( WinSock )

procedure Transmit
( DeliverTo : in Component_Ids_Type;
  Count     : in Itf.Message_Size_Type;
  Message   : in System.Address
) is

  Bytes_Written
  -- Number of bytes sent
  : ExecItf.INT;

  Index
  -- Index into Comm array
  : Connection_Count_Type := 0;

  function to_PCSTR is new Unchecked_Conversion( Source => System.Address,
                                                 Target => ExecItf.PCSTR );

  use type ExecItf.INT;

begin -- Transmit

  -- Find location of pair for remote application and set Index into Comm.
  for I in 1..Comm.Count loop
    if Comm.Data(I).DeliveryId = DeliverTo then
      Index := I;

      declare
        Text : String(1..28) := "Transmit Index N DeliverTo D";
      begin
        Text(16) := to_Digit(Integer(Index));
        Text(28) := to_Digit(Integer(DeliverTo));
        Text_IO.Put_Line(text);     
      end;

      exit;
    end if;
  end loop;

  -- Return if remote component doesn't exist for this configuration.
  if Index = 0 then
    return;
  end if;

  -- Return if the select socket isn't available; i.e., connected.
  if not Comm.Link(Index).Transmit.Connected then
    if Index = 1 then
      Text_IO.Put_Line("Transmit 1 not connected, returning");
    elsif Index = 2 then
      Text_IO.Put_Line("Transmit 2 not connected, returning");
    else
      Text_IO.Put_Line("Transmit 3 not connected, returning");
    end if;
    return;
  end if;
   
  Bytes_Written :=
    ExecItf.Send( S     => Comm.Link(Index).Transmit.Socket.Socket,
                  Buf   => to_PCSTR(Message),
                  Len   => ExecItf.INT(Count),
                  Flags => 0 );
  if Bytes_Written /= ExecItf.INT(Count) then
    Text_IO.Put("ERROR: WinSock Message Send failed");
    Int_IO.Put(Integer(Bytes_Written));
    Text_IO.Put(" ");
    Text_IO.Put(String(Comm.Link(Index).Transmit.Name(1..25)));
    Int_IO.Put(Integer(Index));
    Int_IO.Put(Integer(Comm.Link(Index).Transmit.Socket.Data.SIn_Port));
    Text_IO.Put_Line(" ");
    ExecItf.Display_Last_WSA_Error;
  else
    declare
      Text : Itf.V_80_String_Type;
    begin
      Text := TextIO.Concat
              ( "Transmit sent using socket port",
                Integer(Comm.Link(Index).Transmit.Socket.Data.SIn_Port) );
      TextIO.Put_Line( Text );
    end;
  end if;

end Transmit;

The Xmit subpackage with its Install and callbacks for separate threads

with CStrings;
with Interfaces.C;
with TextIO;
with Threads;

separate( WinSock )

package body Xmit is

  procedure Callback
  ( Id : in Integer
  );

  -– Bind the socket
  function SocketBind
  ( Index : in Connection_Count_Type
  ) return Boolean;

  function SocketListen
  ( Index : in Connection_Count_Type
  ) return Boolean;

  procedure Install
  ( Id : in Connection_Count_Type
  ) is
  -- This procedure runs in the startup thread.

    Name : String(1..25);

    Result : Boolean;

    TransmitResult
    -- Result of Install of Transmit with Threads
    : Threads.RegisterResult;

    use type ExecItf.SOCKET;
    use type Interfaces.C.int;
    use type Threads.InstallResult;

    function to_Callback is new Unchecked_Conversion
                                ( Source => System.Address,
                                  Target => Threads.CallbackType );
  begin -- Install

    Name := Comm.Link(Id).Transmit.Name;

    -- Install thread for the server
    TransmitResult := Threads.Install
                      ( Name     => Name,
                        Index    => Integer(Id),
                        Priority => Threads.NORMAL,
                        Callback => to_Callback(Callback'Address) );
    if TransmitResult.Status = Threads.Valid then
      Comm.Data(Id).Transmit_Wait := TransmitResult.Event;
      Text_IO.Put("Install Xmit ");
      Text_IO.Put(Name);
      Int_IO.Put(Integer(Id));
      Text_IO.Put_Line(" ");
    end if;

    -- Create the socket for Transmit.
    -- Note:
    --  remote/other component.  After a connection has been established
    --  with the other component, the Transmit procedure can send messages
    --  in the particular thread associated with the component pair in
    --  via the callback.

    Result := SocketBind( Id );

    if Result then
      Result := SocketListen( Id );
    end if;

  end Install;

  function SocketBind
  ( Index : in Connection_Count_Type
  ) return Boolean is

    Status
    -- 0 means function was successful; -1 otherwise
    : ExecItf.INT;

    use type ExecItf.SOCKET;
    use type Interfaces.C.int;

  begin -- SocketBind

    Comm.Link(Index).Transmit.Socket.Socket :=
      ExecItf.Socket_Func( AF       => ExecItf.PF_INET,       -- address family
                           C_Type   => ExecItf.SOCK_STREAM,   -- connection-oriented
                           Protocol => ExecItf.IPPROTO_TCP ); -- for TCP
    if Comm.Link(Index).Transmit.Socket.Socket = ExecItf.INVALID_SOCKET then

      Text_IO.Put_Line("ERROR: Server Socket NOT created");
      ExecItf.Display_Last_WSA_Error;

      Status := ExecItf.WSACleanup;

      return False;

    end if;

    if Index = 1 then
      Text_IO.Put("Xmit 1 TransmitCreate");
    elsif Index = 2 then
      Text_IO.Put("Xmit 2 TransmitCreate");
    else
      Text_IO.Put("Xmit 3 TransmitCreate");
    end if;
    Int_IO.Put(integer(Comm.Link(Index).Transmit.Socket.Socket));
    Text_IO.Put_Line(" ");

    -- Bind server socket and indicate socket created if bind successful.
    Status :=
      ExecItf.Bind
      ( S       => Comm.Link(Index).Transmit.Socket.Socket,
        Addr    => Comm.Link(Index).Transmit.Socket.Addr,
        NameLen => ExecItf.INT(Comm.Link(Index).Transmit.Socket.Data'size/8) );

    if Status /= 0 then

      ExecItf.Display_Last_WSA_Error;
      Text_IO.Put("ERROR: Server created socket but Bind FAILED" );
      Int_IO.Put(Integer(Status));
      Text_IO.Put_Line(" ");

      Status := ExecItf.CloseSocket( S => Comm.Link(Index).Transmit.Socket.Socket );
      Comm.Link(Index).Transmit.Socket.Socket := ExecItf.INVALID_SOCKET;
      Status := ExecItf.WSACleanup;
      return False;

    else

      Comm.Data(Index).Bound := True;

      return True;

    end if;

  end SocketBind;

  function SocketListen
  ( Index : in Connection_Count_Type
  ) return Boolean is

    Status
    -- 0 means function was successful; -1 otherwise
    : ExecItf.INT;

    use type Interfaces.C.int;

  begin -- SocketListen

    if ExecItf.Listen( S       => Comm.Link(Index).Transmit.Socket.Socket,
                       Backlog => 1 ) < 0 -- only allow one connection per remote client
    then

      Comm.Link(Index).Transmit.Created := False;
      Text_IO.Put_Line("ERROR: Server bound socket but Listen FAILED" );
      ExecItf.Display_Last_WSA_Error;

      Status := ExecItf.CloseSocket
                ( S => Comm.Link(Index).Transmit.Socket.Socket );
      Comm.Link(Index).Transmit.Socket.Socket := ExecItf.INVALID_SOCKET;
      Status := ExecItf.WSACleanup;

      return False;

    else

      Comm.Link(Index).Transmit.Created := True;

      if Index = 1 then
        Text_IO.Put_line("Xmit 1 Transmit Created");
      elsif Index = 2 then
        Text_IO.Put_line("Xmit 2 Transmit Created");
      else
        Text_IO.Put_line("Xmit 3 Transmit Created");
      end if;

    end if;

    return True;

  end SocketListen;

  -- Forever loop as initiated by Threads
  procedure Callback
  ( Id : in Integer
  ) is
  -- This procedure runs in the particular thread assigned to accept the
  -- connection for a component.

    Client_Socket
    -- Accepted client socket
    : ExecItf.SOCKET := ExecItf.INVALID_SOCKET;

    type Int_Ptr_Type is access ExecItf.INT;

    use type Interfaces.C.int;

    Index
    -- Index for Component for Comm.Link
    : Connection_Count_Type
    := Connection_Count_Type(Id);

    Client_Address_Size
    -- Size of socket address structure
    : ExecItf.INT
    := Comm.Link(Index).Transmit.Socket.Data'size/8;

    use type ExecItf.SOCKET;

    function to_Int_Ptr is new Unchecked_Conversion( Source => System.Address,
                                                     Target => Int_Ptr_Type );
    function to_Integer is new Unchecked_Conversion -- for debug
                               ( Source => ExecItf.PSOCKADDR,
                                 Target => Integer );

  begin -- Callback

    Connect:
    Loop

      declare
        Text : String(1..28);
      begin
        Text(1..19) := "Xmit Callback loop ";
        Text(20) := to_Digit(Integer(Id));
        Text(21) := ' ';
        Text(22) := to_Digit(Integer(Index));
        if Comm.Link(Index).Transmit.Created then
          Text(23..28) := " True ";
        else
          Text(23..28) := " False";
        end if;
        Text_IO.Put_Line(Text);
      end;
      if Index = 1 then
        Text_IO.Put_Line("index of 1");
      elsif Index = 2 then
        Text_IO.Put_Line("index of 2");
      else
        Text_IO.Put_Line("index of 3");
      end if;

      if Comm.Link(Index).Transmit.Created and then
         Comm.Link(Index).Receive.Connected and then
         not Comm.Link(Index).Transmit.Connected
      then

        -- Accept a client connection.
        Client_Socket :=
          ExecItf.C_Accept( S       => Comm.Link(Index).Transmit.Socket.Socket,
                            Addr    => null,
                            AddrLen => null );

        declare
          Text : Itf.V_80_String_Type;
        begin
          Text := TextIO.Concat( "Xmit after C_Accept", Integer(Index) );
          TextIO.Put_Line(Text);
        end;

        if Client_Socket = ExecItf.INVALID_SOCKET then

          Text_IO.Put_Line("ERROR: Server Client Socket NOT accepted");
          ExecItf.Display_Last_WSA_Error;

        else -- Accepted

          Comm.Link(Index).Transmit.Connected := True;
          Comm.Link(Index).Transmit.Socket.Socket := Client_Socket;

          exit Connect; -- loop

        end if; -- invalid Client_Socket

      end if; -- Comm.Link(Index).Transmit.Created

      Text_IO.Put_Line("Xmit Callback initial loop end");

      delay(1.0*Duration(Index)); -- seconds

    end loop Connect;

    -- Nothing else for the thread to do.
    Forever:
    loop

      delay 3.0; -- seconds

    end loop Forever;

  end Callback;

end Xmit;

The Recv subpackage with its Install, Create, and callbacks for the separate threads.  Unlike the Server create that is done from the Install procedure, the Client create (here named ReceiveCreate) is done from the beginning of the particular thread for the client.

separate( WinSock.Recv )

procedure ReceiveCreate
( Index : in Connection_Count_Type
) is

  Status
  -- 0 means function was successful; -1 otherwise
  : ExecItf.INT;

  use type ExecItf.SOCKET;
  use type ExecItf.INT;

begin -- ReceiveCreate

  -- Ignore create attempt if configuration doesn't specify that the
  -- method is supported for the remote application.
  -- Notes:
  --   Transmit Supported is whether the remote component supports transmit
  --   via WinSock and hence this application should be able to receive from it.

  if not Comm.Link(Index).Transmit.Supported then
    Text_IO.Put("ReceiveCreate not supported");
    return;
  end if;

  declare
    Text : String(1..21);
  begin
    Text(1..20) := "ReceiveCreate Index ";
    Text(21) := to_Digit(Integer(Index));
    Text_IO.Put_Line(Text);
  end;

  -- Create socket.
  -- Notes:
  --   The socket client receives messages from the server.  The socket
  --   server sends messages to the client.  Therefore, the running
  --   application component acts as the socket client for a particular
  --   connection and the remote application component acts as the server
  --   and transmits the message.  There is a connection pair for each
  --   component with the running application supporting the component that
  --   is acting as the client for the other side of the component pair that
  --   is used to send messages to this application's component.

  Comm.Link(Index).Receive.Socket.Socket :=
     ExecItf.Socket_Func( AF       => ExecItf.PF_INET,       -- address family
                          C_Type   => ExecItf.SOCK_STREAM,   -- connection-oriented
                          Protocol => ExecItf.IPPROTO_TCP ); -- for TCP
  if Comm.Link(Index).Receive.Socket.Socket = ExecItf.INVALID_SOCKET then

    ExecItf.Display_Last_WSA_Error;
    Status := ExecItf.WSACleanup;

    declare
      Text : String(1..28) := "Client Socket NOT created: x";
    begin
      Text(28) := to_Digit(Integer(Index));
      Text_IO.Put_Line(Text);
    end;

  else -- valid

    -- Connect to server.

    Status :=
      ExecItf.Connect( S       => Comm.Link(Index).Receive.Socket.Socket,
                       Name    => Comm.Link(Index).Receive.Socket.Addr,
                       NameLen => ExecItf.Int(Comm.Link(Index).Receive.Socket.Data'size/8) );

    if Status = 0 then

      Comm.Link(Index).Receive.Connected := True;

      -- Indicate that there is a remote component that can be used by one of
      -- the Client Receive threads in one of the instances of the Recv package.

      Comm.Data(Index).Available := True;

      if Index = 1 then
        Text_IO.Put_Line("Client Socket 1 Connected");
        Text_IO.Put_Line("Comm.Data(1) Available for Client ");
      elsif Index = 2 then
        Text_IO.Put_Line("Client Socket 2 Connected");
        Text_IO.Put_Line("Comm.Data(2) Available for Client ");
      else
        Text_IO.Put_Line("Client Socket 3 Connected");
        Text_IO.Put_Line("Comm.Data(3) Available for Client ");
      end if;

    else

      ExecItf.Display_Last_WSA_Error;
      Status := ExecItf.WSACleanup;

      Status := ExecItf.CloseSocket( S => Comm.Link(Index).Receive.Socket.Socket );
      Comm.Link(Index).Receive.Socket.Socket := ExecItf.INVALID_SOCKET;

      if Index = 1 then
        Text_IO.Put_Line("Client Socket 1 NOT Connected");
        Text_IO.Put_line("ERROR: Client Connect 1 FAILED: ");
      elsif Index = 2 then
        Text_IO.Put_Line("Client Socket 2 NOT Connected");
        Text_IO.Put_line("ERROR: Client Connect 2 FAILED: ");
      else
        Text_IO.Put_Line("Client Socket 3 NOT Connected");
        Text_IO.Put_line("ERROR: Client Connect 3 FAILED: ");
      end if;
      Text_IO.Put(String(Comm.Link(Index).Receive.Name));
      Int_IO.Put(Integer(Index));
      Text_IO.Put_Line(" ");

    end if;

  end if;

  declare
    Txt  : String(1..23);
    Text : Itf.V_80_String_Type;
  begin
    if Comm.Link(Index).Receive.Connected then
      Txt(1..18) := "Receive Connected ";
      Text.Count := 18;
    else
      Txt(1..22) := "Receive NOT Connected ";
      Text.Count := 22;
    end if;
    Text.Count := Text.Count + 1;
    Txt(Text.Count) := To_Digit(Integer(Index));
    Text := TextIO.Concat
            ( Txt(1..Text.Count),
              Integer(Comm.Link(Index).Receive.Socket.Data.SIn_Port) );
    TextIO.Put_Line(Text);
  end;
  if Comm.Link(Index).Receive.Socket.Socket /= ExecItf.INVALID_SOCKET then
    Text_IO.Put_Line("valid socket");
  end if;

end ReceiveCreate;


with CStrings;
with Interfaces.C;
with TextIO;
with Threads;

separate( WinSock )

package body Recv is

  type Received_Message_Connection_Type
  -- Connection of received message
  is record
    Remote : Connection_Count_Type;
    -- Remote connection of received message
    Length : Integer;
    -- Length of received message
  end record;

  function to_Ptr is new Unchecked_Conversion
                         ( Source => System.Address,
                           Target => ExecItf.PSTR );
   
  procedure Callback
  ( Id : in Integer
  );

  procedure ReceiveCreate
  ( Index : in Connection_Count_Type
  );
 
  procedure Install
  ( Id : in Connection_Count_Type
  ) is
  -- This procedure runs in the startup thread.

    Name  : String(1..25);

    ReceiveResult
    -- Result of Install of Receive with Threads
    : Threads.RegisterResult;

    use type Threads.InstallResult;

    function to_Callback is new Unchecked_Conversion
                                ( Source => System.Address,
                                  Target => Threads.CallbackType );

  begin -- Install

    Name := Comm.Link(Id).Receive.Name;
    Text_IO.Put("Install Recv ");
    Text_IO.Put(Name);
    Int_IO.Put(Integer(Id));
    Text_IO.Put_Line(" ");

    -- Install client thread for receive.
    ReceiveResult := Threads.Install
                     ( Name     => Name,
                       Index    => Integer(Id),
                       Priority => Threads.HIGH,
                       Callback => to_Callback(Callback'Address) );
    if ReceiveResult.Status = Threads.Valid then
      Comm.Data(Id).Receive_Wait := ReceiveResult.Event;
    end if;

  end Install;

  -- Forever loop as initiated by Threads to connect to the remote component
  -- and then receive messages from it.
  -- Notes:
  --   o The data for the socket to be used between the local and remote components
  --     is stored in the WinSock table available by the Index of Id.
  --   o This procedure is that of one of the threads.  Therefore, there is not a
  --     calling procedure that can be returned to.  Hence, there cannot be a
  --     return from this procedure.
  procedure Callback
  ( Id : in Integer
  ) is
  -- This procedure runs in the particular thread to wait for a message for
  -- the component of the Id.  The Id is passed by the Threads package as passed
  -- to it via the Install.

    Index
    -- Index into Comm.Link as passed in from Threads
    : Connection_Count_Type;

    Message
    -- Message as read from socket
    : Itf.Message_Buffer_Type;

    Received_Size
    -- Size of received message
    : ExecItf.INT;

    Result
    -- Return value for Close
    : ExecItf.INT;

    use type ExecItf.SOCKET;
    use type Interfaces.C.Int;

  begin -- Callback

    declare
      Text : String(1..26);
    begin
      Text(1..25) := "in WinSock Recv callback ";
      Text(26) := to_Digit(Integer(Id));
      Text_IO.Put_Line(Text);
    end;
    Index := Connection_Count_Type(Id);

    Forever:
    loop

      -- If the receive socket has not been connected, do so.
      if not Comm.Link(Index).Receive.Connected then
        ReceiveCreate( Index => Index );
      end if;

      -- Read from the socket and treat it.  Avoid the read if the socket
      -- has been closed.
      -- Notes:
      --   Since this is a separate process/thread, there can be no return
      --   from it when the socket is closed.

      if Comm.Link(Index).Receive.Connected and then
         Comm.Link(Index).Receive.Socket.Socket /= ExecItf.INVALID_SOCKET
      then

        declare
          function to_Int is new Unchecked_Conversion
                                 ( Source => System.Address,
                                   Target => Integer );
        begin
          Received_Size :=
            ExecItf.Recv( S     => Comm.Link(Index).Receive.Socket.Socket,
                          Buf   => to_Ptr(Message'address),
                          Len   => ExecItf.INT(Message'size/8),
                          Flags => 0 );
        end;

        if Received_Size < 0 then
          declare
            Text : Itf.V_80_String_Type;
          begin
            Text.Data(1..29) := "ERROR: WinSock Receive failed";
            Text := TextIO.Concat( Text.Data(1..29),
                                   Integer(Index) );
            TextIO.Put_Line(Text);
          end;
          ExecItf.Display_Last_WSA_Error;
          Result := ExecItf.CloseSocket
                    ( S => Comm.Link(Index).Receive.Socket.Socket );
          Comm.Link(Index).Receive.Socket.Socket := ExecItf.INVALID_SOCKET;
        elsif Received_Size = 0 then
          Text_IO.Put_Line("ERROR: WinSock Receive of 0 bytes");
        else

          -- Pass the message to its associated component
          declare
            Msg : String(1..Integer(Received_Size));
            for Msg use at Message'Address;
            function callback_toInt is new Unchecked_Conversion
                                           ( Source => ReceiveCallbackType,
                                             Target => Integer );
          begin
            Comm.Data(Index).Receive_Callback(Msg);
          end;

        end if; -- Received_Size < 0

      else -- not Comm(Index)(Receive).Socket_Connected

        delay 0.5; -- seconds and then return to try once more

      end if; -- Comm(Index)(Receive).Socket_Connected

    end loop Forever;

  end Callback;

  procedure ReceiveCreate
  ( Index : in Connection_Count_Type
  ) is separate;

end Recv;

A sample component.  The others are very similar.  Their only purpose is to send and receive messages via TCP/IP of Microsoft WinSock as interfaced via my WinSock Ada package.  Actual components would, of course, have unique responsibilities and are used to isolate the component from other dependences and allow them to be moved from one application to another without the necessity of modifying the applications.  (Except for the references in the main procedure.)

Each sample component declares the messages it is going to request to be transmitted, installs itself with the Threads package to obtain its thread to run in its callback and then along with the WinSock package to which it identifies itself and specifies the callback by which it will be notified of received messages.  In its callback thread it can perform its tasks (of which none are provided since the reason for supplying the components is only to illustrate messaging) and send messages to other components of the configuration reporting results or requesting information.

with ExecItf;

package Component2 is

  -- Return component's wakeup event handle
  function WakeupEvent
  return ExecItf.HANDLE;

  procedure Install;

end Component2;

with System;
with Text_IO;
with Threads;
with Unchecked_Conversion;
with WinSock;

package body Component2 is

  package Int_IO is new Text_IO.Integer_IO( Integer );

  ComponentWakeup
  -- Wakeup Event handle of the component
  : ExecItf.HANDLE;

  Message
  : String(1..18)
  := "Component2 message";

  MessageTo6
  : String(1..23)
  := "Component2 message to 6";

  procedure Callback
  ( Id : in Integer
  );

  procedure ReceiveCallback
  ( Message : in String --Itf.Message_Buffer_Type
  );

  procedure Install is

    Result : Threads.RegisterResult;

    use type Threads.InstallResult;

    function to_Callback is new Unchecked_Conversion
                                ( Source => System.Address,
                                  Target => Threads.CallbackType );
    function to_RecvCallback is new Unchecked_Conversion
                                    ( Source => System.Address,
                                      Target => WinSock.ReceiveCallbackType );

  begin -- Install

    -- Install the component into the Threads package.
    Result := Threads.Install
              ( Name     => "Component2",
                Index    => 0, -- value doesn't matter
                Priority => Threads.NORMAL,
                Callback => to_Callback(Callback'Address)
              );
    if Result.Status = Threads.VALID then
      ComponentWakeup := Result.Event; -- make visible to WinSock via function

      -- Do the Windows sockets initialization and install its threads.
      WinSock.Install( ComponentId => 2,
                       Component   => "Component2",
                       RecvCallback => to_RecvCallback(ReceiveCallback'Address)
                     );

    end if;

  end Install;

  -- Return component's wakeup event handle
  function WakeupEvent
  return ExecItf.HANDLE is
  begin -- WakeupEvent
    return ComponentWakeup;
  end WakeupEvent;

  -- Received message from WinSock Recv
  procedure ReceiveCallback
  ( Message : in String --Itf.Message_Buffer_Type
  ) is

  begin -- ReceiveCallback

    Text_IO.Put("Component2 received a message: ");
    declare
      Msg : String(1..Message'Length);
      for Msg use at Message'Address;
    begin
      Text_IO.Put_Line(Msg);
    end;

  end ReceiveCallback;

  -- Forever loop as initiated by Threads
  procedure Callback
  ( Id : in Integer
  ) is

  begin -- Callback

    Text_IO.Put("in Component2 callback");
    Int_IO.Put(Id);
    Text_IO.Put_Line(" ");

    loop -- forever

      Text_IO.Put_Line("Component2 to send to Component1");
      WinSock.Transmit( DeliverTo => 1,
                        Count     => 18,
                        Message   => Message'address );

      Text_IO.Put_Line("Component2 to send to RemoteComponent");
      WinSock.Transmit( DeliverTo => 6,
                        Count     => 23,
                        Message   => MessageTo6'address );
      Delay(2.0);

    end loop;

  end Callback;

end Component2;

A sample application main procedure.  The others are very similar. 

Their only purpose is to Initialize and Finalize the WinSock package that interfaces with Microsoft WinSock, install the components that are to run within the application, and then invoke Threads Create to create all the component and WinSock Recv and Xmit threads and cause their callback forever loops to be entered – each in its own thread. At that point, the launch thread ceases to execute any code and only the code in the forever loops executes.

with Component2;
with ExComponent;
with Text_IO;
with Threads;
with WinSock;

procedure App2 is

begin -- App2

  -- Initialize certain WinSock tables and input and parse the Delivery.dat file
  WinSock.Initialize;

  -- Install the components of App2
  Component2.Install;
  ExComponent.Install;

  -- Complete the WinSock tables and install the WinSock Recv and Xmit for each
  -- pair of components
  WinSock.Finalize;

  Text_IO.Put_Line("calling Threads Create");

  -- Create the threads for the thread table objects and enter the callbacks
  Threads.Create;

end App2;

The Itf package can be found in older posts.  Nothing new except that
  type V_80_String_Type
  is record
    Count : Integer;
    Data  : String(1..80);
  end record;
is declared.

No comments: