Binary parser analysis protocol for serial data

I am revising the communication protocol parser constructor for a byte stream (serial data received 1 byte at a time).

Package structure (cannot be changed):

|| Start Delimiter (1 byte) | Message ID (1 byte) | Length (1 byte) | Payload (n bytes) | Checksum (1 byte) || 

In the past, I implemented such systems in a state procedural computer. As each byte of data arrives, the state machine starts to see where / if the incoming data fits into the actual packet byte at a time, and as soon as the entire packet has been collected, the switch statement based on the Message Identifier executes the corresponding message handler. In some implementations, the machine / syntax message handler / processor loop sits in its own stream so as not to burden the serial data handler and is triggered by a semaphore indicating that the bytes have been read.

I am wondering if there is a more elegant solution to this common problem using some of the more modern language features of C # and OO design. Any design patterns that could solve this problem? Event Driven vs. Poll vs. Combination?

I am interested to hear your ideas. Thanks.

Prembo

+6
c # design-patterns protocols serial-port communication
source share
3 answers

First of all, I would separate the packet parser from the data stream reader (so that I could write tests without accessing the stream). Then consider a base class that provides a method for reading in a package and one for writing a package.

In addition, I would create a dictionary (once, and then reuse it for future calls) as follows:

 class Program { static void Main(string[] args) { var assembly = Assembly.GetExecutingAssembly(); IDictionary<byte, Func<Message>> messages = assembly .GetTypes() .Where(t => typeof(Message).IsAssignableFrom(t) && !t.IsAbstract) .Select(t => new { Keys = t.GetCustomAttributes(typeof(AcceptsAttribute), true) .Cast<AcceptsAttribute>().Select(attr => attr.MessageId), Value = (Func<Message>)Expression.Lambda( Expression.Convert(Expression.New(t), typeof(Message))) .Compile() }) .SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value })) .ToDictionary(o => o.Key, v => v.Value); //will give you a runtime error when created if more //than one class accepts the same message id, <= useful test case? var m = messages[5](); // consider a TryGetValue here instead m.Accept(new Packet()); Console.ReadKey(); } } [Accepts(5)] public class FooMessage : Message { public override void Accept(Packet packet) { Console.WriteLine("here"); } } //turned off for the moment by not accepting any message ids public class BarMessage : Message { public override void Accept(Packet packet) { Console.WriteLine("here2"); } } public class Packet {} public class AcceptsAttribute : Attribute { public AcceptsAttribute(byte messageId) { MessageId = messageId; } public byte MessageId { get; private set; } } public abstract class Message { public abstract void Accept(Packet packet); public virtual Packet Create() { return new Packet(); } } 

Edit: some explanation of what is going on here:

At first:

 [Accepts(5)] 

This line is a C # attribute (defined by AcceptsAttribute ), says the FooMessage class accepts message id 5.

Secondly:

Yes, a dictionary is built at runtime through reflection. You only need to do this once (I would put it in a singleton class that you can put a test case on it that you can run to ensure the dictionary is correctly built).

Third:

 var m = messages[5](); 

This line gets the following compiled lambda expression from the dictionary and executes it:

 ()=>(Message)new FooMessage(); 

(The cast is necessary in .NET 3.5, but not in 4.0 due to covariant changes in delagates, in 4.0 an object of type Func<FooMessage> can be assigned to an object of type Func<Message> .)

This lambda expression is built on the line of assignment of values ​​when creating a dictionary:

 Value = (Func<Message>)Expression.Lambda(Expression.Convert(Expression.New(t), typeof(Message))).Compile() 

(The cast here requires the compiled lambda expression to be Func<Message> .)

I did it like this because I already have the type available to me at that moment. You can also use:

 Value = ()=>(Message)Activator.CreateInstance(t) 

But I think this will be slower (and the cast here needs to change Func<object> to Func<Message> ).

Fourth:

 .SelectMany(o => o.Keys.Select(key => new { Key = key, o.Value })) 

This was done because I felt that you might have a value when placing AcceptsAttribute more than once in a class (to accept more than one message identifier for each class). It also has a good effect on ignoring message classes that do not have a message identifier attribute (otherwise, the Where method would have to be difficult to determine if the attribute is present).

+4
source share

I'm a little late to the party, but I wrote a framework that I think can do it. Without knowing more about your protocol, it is difficult for me to write an object model, but I think it will not be too difficult. Take a look at binaryserializer.com .

+2
source share

What I usually do is define an abstract base message class and retrieve private messages from this class. Then you have a message parser object that contains a state machine for interpreting bytes and constructing the corresponding message object. The message parser object has only a way (to pass it to incoming bytes) and, possibly, an event (called when a full message arrives).

You have two options for handling actual messages:

  • Define an abstract method in the base message class, redefining it in each of the received message classes. Ask the message analyzer to call this method after the message has arrived completely.
  • The second option is less object-oriented, but easier to work with: leave message classes just data. When the message is completed, send it through an event that will take the abstract base message class as a parameter. Instead of the switch statement, the as handler usually passes them to derived types.

Both of these options are useful in different scenarios.

+1
source share

All Articles