gotcpnetwork-programmingprotocol-buffersprotobuf-go

Receive protobuf encoded messages that can be partially written?


I am trying to send and receive protobuff encoded messages in GoLang over TCP, where the sender can cancel the write() halfway through the operation, and the receiver can correctly receive partial messages.

Note that I use a single TCP connection to send messages of different user defined types, infinitely (this is not a per connection message case)

To explain my question concretely, first I will present how I implement the send/receive without partial writes.

In my program, there are multiple types of messages, defined in a .proto file. I will explain the mechanism for one such message type.

message MessageType {
  int64 sender = 1;
  int64 receiver = 2;
  int64 operation = 3;
  string message = 4;
}

Then I use Golang Protobuf plugin to generate the stubs.

Then in the sender side, the following is how I send.

func send(w *bufio.Writer, code uint8, oriMsg MessageType) {
    err := w.WriteByte(code)
    data, err := proto.Marshal(oriMsg)
    lengthWritten := len(data)
    var b [8]byte
    bs := b[:8]
    binary.LittleEndian.PutUint64(bs, uint64(lengthWritten))
    _, err = w.Write(bs)
    _, err = w.Write(data)
    w.flush()
}

Then in the receiver side, the following is how I receive.

reader *bufio.Reader
for true {
        if msgType, err = reader.ReadByte(); err != nil {
            panic()
        }
        if msgType == 1 || msgType == 2{
            var b [8]byte
            bs := b[:8]

            _, err := io.ReadFull(reader, bs) 
            numBytes := binary.LittleEndian.Uint64(bs)
            data := make([]byte, numBytes)
            length, err := io.ReadFull(reader, data) 
            msg *MessageType = new(GenericConsensus) // an empty message 
            err = proto.Unmarshal(data[:length], msg) 
            // do something with the message 
            
        } else {
            // unknown message type handler
        }
    }

Now my question is, what if the sender aborts his writes in the middle: more concretely,

  1. Case 1: what if the sender writes the message type byte, and then abort? In this case the receiver will read the message type byte, and waits to receive an 8 byte message length, but the sender doesn't send it.

  2. Case 2: This is an extended version of case 1 where the sender first sends only the message type byte, and the aborts sending the message length and marshaled message, and then send the next message: the type byte, the length and encoded message. Now in the receiver side, everything goes wrong because the order of messages (type, length and encoded message) is violated.

So my question is, how can I modify the receiver such that it can continue to operate despite the sender violating the pre-agreed order of type:length:encoded-message?

Thanks


Solution

  • Why would the sender abort a message, but then send another message? You mean it's a fully byzantine sender? Or are you preparing for fuzzy-testing?

    If your API contract says that the sender always needs to send a correct message, then the receiver can simply ignore wrong messages, or even close the connection if it sees a violation of the API contract.

    If you really need it, here some ideas of how you could make it work:

    Also, as the code is currently, it is quite easy to crash by sending a size with the maximum of 64 bits. So you should also check for the size to be in a useful range. I would limit it to 32 bits...