0

I'm attempting to implement the Remote Frame Buffer protocol using Ada's Sockets library and I'm having trouble controlling the length of the packets that I'm sending.

I'm following the RFC 6143 specification (https://tools.ietf.org/pdf/rfc6143.pdf), see comments in the code for section numbers...

          --  Section 7.1.1
          String'Write (Comms, Protocol_Version);
          Put_Line ("Server version: '"
            & Protocol_Version (1 .. 11) & "'");

          String'Read (Comms, Client_Version);
          Put_Line ("Client version: '"
            & Client_Version (1 .. 11) & "'");

          --  Section 7.1.2
          --  Server sends security types
          U8'Write (Comms, Number_Of_Security_Types);
          U8'Write (Comms, Security_Type_None);


          --  client replies by selecting a security type
          U8'Read (Comms, Client_Requested_Security_Type);
          Put_Line ("Client requested security type: "
            & Client_Requested_Security_Type'Image);

          --  Section 7.1.3
          U32'Write (Comms, Byte_Reverse (Security_Result));

          --  Section 7.3.1
          U8'Read (Comms, Client_Requested_Shared_Flag);
          Put_Line ("Client requested shared flag: "
            & Client_Requested_Shared_Flag'Image);


          Server_Init'Write (Comms, Server_Init_Rec);

The problem seems to be (according to wireshark) that my calls to the various 'Write procedures are causing bytes to queue up on the socket without getting sent.

Consequently two or more packet's worth of data are being sent as one and causing malformed packets. Sections 7.1.2 and 7.1.3 are being sent consecutively in one packet instead of being broken into two.

I had wrongly assumed that 'Reading from the socket would cause the outgoing data to be flushed out, but that does not appear to be the case.

How do I tell Ada's Sockets library "this packet is finished, send it right now"?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Disable the Nagle algorithm, but in general your expectations are misplaced. TCP is a byte-stream protocol, not a message protocol. If you want messages you have to implement them yourself. – user207421 Oct 13 '19 at 08:54
  • Can you confirm that the length of the string `Client_Version` after read is indeed exactly 12? And indicate what the value is of `Client_Requested_Security_Type` after you read it from the stream? – DeeDee Oct 13 '19 at 09:32
  • @DeeDee, Yes, Wireshark confirms it as being exactly 12 bytes including the '\n' (it all appears clearly in the hex dump). The client replies with a similar message, also correctly formatted. It all goes wrong when queueing up multiple `'Write` calls. –  Oct 13 '19 at 09:38
  • 2
    No, it all goes wrong because you aren't *reading* correctly. You can't read on the assumption that one single read will give you one entire protocol packet. It may give you anything from one byte up to the length of the buffer you supply, and that data might consist of a fraction of a packet or several, or both. You have to cope with all that at the reading end. Nothing you can possibly do at the sending end can obviate that requirement. – user207421 Oct 13 '19 at 09:43
  • @user207421. Thank you, I think I understand now. I need to create my own character buffers large enough for any "message" and manually read (or write) sequential bytes in a loop until the right number of characters is transferred. If I were writing this in C code then I'd instinctively do that kind of thing anyway. The Ada package `g-socket.ads` contains a beautiful worked example of how to use TCP and UDP but sadly neglects to mention that these buffers need to be maintained the hard way. This suggests that Ada's `String'Read (stream, str_buffer)` is BLOCKING until the buffer is full! –  Oct 13 '19 at 10:25
  • @user207421, Looks like all `'Read` calls are blocking. I don't think this is a Duplicate question because I think Ada's libraries are doing the blocking buffering on the programmer's behalf. `'Read` calls are blocking for fixed length Strings and for scalar types of known length. –  Oct 13 '19 at 10:55

1 Answers1

0

To enphasize https://stackoverflow.com/users/207421/user207421 comment:

I'm not a protocols guru, but from my own experience, the usage of TCP (see RFC793) is often misunderstood.

The problem seems to be (according to wireshark) that my calls to the various 'Write procedures are causing bytes to queue up on the socket without getting sent.

Consequently two or more packet's worth of data are being sent as one and causing malformed packets. Sections 7.1.2 and 7.1.3 are being sent consecutively in one packet instead of being broken into two.

In short, TCP is not message-oriented.

Using TCP, sending/writing to socket results only append data to the TCP stream. The socket is free to send it in one exchange or several, and if you have lengthy data to send and message oriented protocol to implement on top of TCP, you may need to handle message reconstruction. Usually, an end of message special sequence of characters is added at the end of the message.

Processes transmit data by calling on the TCP and passing buffers of data as arguments. The TCP packages the data from these buffers into segments and calls on the internet module to transmit each segment to the destination TCP. The receiving TCP places the data from a segment into the receiving user's buffer and notifies the receiving user. The TCPs include control information in the segments which they use to ensure reliable ordered data transmission.

See also https://stackoverflow.com/a/11237634/7237062, quoting:

TCP is a stream-oriented connection, not message-oriented. It has no concept of a message. When you write out your serialized string, it only sees a meaningless sequence of bytes. TCP is free to break up that stream up into multiple fragments and they will be received at the client in those fragment-sized chunks. It is up to you to reconstruct the entire message on the other end.

In your scenario, one would typically send a message length prefix. This way, the client first reads the length prefix so it can then know how large the incoming message is supposed to be.

or TCP Connection Seems to Receive Incomplete Data, quoting:

The recv function can receive as little as 1 byte, you may have to call it multiple times to get your entire payload. Because of this, you need to know how much data you're expecting. Although you can signal completion by closing the connection, that's not really a good idea.

Update:

I should also mention that the send function has the same conventions as recv: you have to call it in a loop because you cannot assume that it will send all your data. While it might always work in your development environment, that's the kind of assumption that will bite you later.

Community
  • 1
  • 1
LoneWanderer
  • 3,058
  • 1
  • 23
  • 41