I am currently implementing a library in Rust that implements a proprietary serial protocol.

The protocol specifies several enum values, that mostly are returned by the hardware as u8s (bytes), but not any value that such a byte can take is a valid enum value, e.g.:

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Ord, PartialOrd, FromPrimitive, ToPrimitive)]
pub enum Type {
    UnknownDevice = 0x00,
    Coordinator = 0x01,
    Router = 0x02,
    EndDevice = 0x03,
    SleepyEndDevice = 0x04,
}

My question now is, how I should implement the deserialization of these enum values?
Should I use recoverable errors, i.e. Result<Type, Error> and return appropriate errors if a byte that does not match a valid enum value is returned by the hardware, or should I use unrecoverable errors, i.e. let the program panic in such cases?

2

The general rule when implementing a network protocol is to be strict in the messages you send, but to be lax in what you accept.

The reasoning here is that you don´t want to cause troubles by sending invalid messages, but you want your implementation also to be robust against incorrectly received data.

Stopping the complete application with a panic because you received a byte that you don´t know how to interpret is the opposite of being robust. Incorrect data can very easily occur due to interference during the transport, or because an attacker tries to bring down your service.

The better option is to report a recoverable error and to ignore the entire message that could not be parsed and, depending on the protocol, report the failure to the other party so they can initiate a retry.

You must expect that your code might be given any sequence of bytes at all. Either by mistake, or as an attack. And there are many byte sequences that are absolutely legitimate and that you accept. If you receive something that isn’t, you have two choices:

a. You don’t accept it. You asked me say for details about customer X, over an internet connection. The connection might fail, you need to handle that. The data may be unacceptable and not be accepted, you need to handle that in a very similar way. You asked for data and you are told (by the parser reading your data) that you won’t get anything.

b. You accept it. You write a deterministic algorithm that accepts any byte sequence as input and produces some result. For example, if all you expect is a UTF-8 string, you can just ignore any bytes that come in that are not valid UTF-8. “abc def” returns a string “abcdef”. There is no security risk, as long as whatever you pass on is something you might have passed on with valid input. Like “abcdef” is what would have happened if I sent you “abcdef” So you might for example say that any unknown enum value is taken to mean “UnknownDevice” and see how far that gets you. Important thing is that the end result is the same as what you could have received from a correct message. And that you touch the original data only in that one place; so in the string example you will always think there are six characters, and never count invalid characters.

So your usual path would be: Correct data => correct object, and no internet connection => no object. Your choice is to either state invalid data => no object, or invalid data => an object that could have been created with correct data and would have been accepted. For the second case no further action is needed in your code. Exceept it is possible that you get an object with unusual but still valid data. But an attacker could have done that anyway.

1