So it's early and I always have anxiety over things like this, so I was hoping someone could take a quick look and make sure this would actually work.

I've got a socket program that will transmit a Data object over the wire. I just want to make sure that my bytes are in order and nothing obviously wrong is going on. I haven't gotten far enough into it to test it, I just wanted to head off any errors at the pass.

This is the method that converts a object of type Data to a single byte array to be transmitted:

/// <summary>
        /// Prepares a Data object for transmission over the wire.
        /// </summary>
        /// <param name="data">The Data object to prepare</param>
        /// <returns>A byte array to send over the wire</returns>
        public byte[] PrepareForWire(Data data)
        {
            using (MemoryStream DataArray = new MemoryStream())
            {
                // convert the data to byte arrays
                byte[] dIsSecret = BitConverter.GetBytes(data.DIsSecret);
                byte[] dCommand = BitConverter.GetBytes((int)data.DCommand);

                // write the arrays to the memory stream
                //skip the first 4 bytes, this is where the message length goes
                DataArray.Seek(4, 0);
                DataArray.Write(dCommand, 0, 4);
                DataArray.Write(dIsSecret, 0, 1);
                DataArray.Write(data.DMessage, 0, data.DMessage.Length);
                
                // find the length of our message
                int TransmissionLength = DataArray.ToArray().Length;

                // write the length of the message minus 4 bytes to the start of the memory stream
                DataArray.Seek(0, 0);
                DataArray.Write(BitConverter.GetBytes(TransmissionLength - 4), 0, 4);

                return DataArray.ToArray();
            }
        }

And here is a Data object and it's constructors:

/// <summary>
    /// Data object
    /// </summary>
    public class Data
    {
        /// <summary>
        /// The command requested
        /// </summary>
        public Command DCommand;

        /// <summary>
        /// Flag indicating if the message is secret or not
        /// </summary>
        public bool DIsSecret;

        /// <summary>
        /// The message
        /// </summary>
        public byte[] DMessage;

        /// <summary>
        /// Creates a new Data object
        /// </summary>
        public Data()
        {
            DCommand = Command.GoodBye;
            DIsSecret = true;
        }

        /// <summary>
        /// Creates a new Data object
        /// </summary>
        /// <param name="dCommand">Command to perform</param>
        /// <param name="dMessage">The message</param>
        public Data(Command dCommand, byte[] dMessage)
        {
            DCommand = dCommand;
            DIsSecret = true;
            DMessage = dMessage;
        }

        /// <summary>
        /// Creates a new Data object
        /// </summary>
        /// <param name="dCommand">Command to perform</param>
        /// <param name="dIsSecret">Flag indicating if the message is secret or not</param>
        /// <param name="dMessage">The message</param>
        public Data(Command dCommand, bool dIsSecret, byte[] dMessage)
        {
            DCommand = dCommand;
            DIsSecret = dIsSecret;
            DMessage = dMessage;
        }

The array that the method returns should look like this:

| Position  | Length    | Data Type | Description                                                  |
----------------------------------------------------------------------------------------------------
| 0         | 4         | Int32     | The length of the following message                          |
| 4         | 4         | Int32     | The command to perform (See: Command Enumeration)            |
| 8         | 1         | bool      | Flag indicating if the receiver should use his private key   |
|           |           |           |   when reading the message                                   |
| 9         | Varies    | string    | If the IsSecret flag is "False" this is unicode text.        |
|           |           |           |   If the IsSecret flag is "True" this is an encrypted object,|
|           |           |           |   the type of which is determined by the command.            |
----------------------------------------------------------------------------------------------------

Dani AI

Generated

Nice start, . A few things to tighten up before you ship bytes over the wire:

  • Endianness: BitConverter returns bytes in the host machine’s endianness. Pick a fixed wire order (big-endian is conventional for network protocols) and convert explicitly when writing/reading. On .NET Framework use IPAddress.HostToNetworkOrder; on modern .NET you can use System.Buffers.Binary.BinaryPrimitives. (learn.microsoft.com)
  • Framing: you already prefix the message length; just be sure your receiver does a looped read until that many bytes arrive (sockets often return partial reads).
  • Booleans and future-proofing: instead of a bool, consider a one-byte flags field where bit 0 means secret and other bits are reserved for future options or a tiny version.
  • Payload: guard against null and be explicit about text encoding (e.g., always UTF-8) rather than saying “unicode.”

If you want the same layout you described but without streams or hardcoded magic numbers, pre-size a buffer and write in network order:

using System;
using System.Buffers.Binary;

byte[] Encode(Data d)
{
    var payload = d.DMessage ?? Array.Empty<byte>();
    var buf = new byte[4 + 4 + 1 + payload.Length];
    BinaryPrimitives.WriteInt32BigEndian(buf.AsSpan(0), 4 + 1 + payload.Length); // length of following bytes
    BinaryPrimitives.WriteInt32BigEndian(buf.AsSpan(4), (int)d.DCommand);
    buf[8] = d.DIsSecret ? (byte)1 : (byte)0; // consider a flags byte later
    Buffer.BlockCopy(payload, 0, buf, 9, payload.Length);
    return buf;
}

On decoding, reverse the process and validate lengths before allocating.

Re: ’s serialization note: avoid BinaryFormatter for network payloads; it is obsolete/disabled and considered insecure. Use a stable binary protocol (your own, protobuf, MessagePack) or JSON if human-readability beats size. (video2.skills-academy.com)

And +1 to : prefer sizeof(int) over literals if you do keep any size math in code.

Recommended Answers

All 4 Replies

Normally I'd see this done with serialization in the C# world, this looks like C code :) The only issue I have is the hard coded lengths in lines 17/18, but you have to have them for something like this.

Momerath,

Actually I guess I don't really HAVE to hard code them. I could use the bit converter for them as well, but I figured it was an unnecessary call. I always figured that when it came to a communication protocol (of any kind, network or between threads etc...) it's better to be "tough" and have a tight leash on things. One extra byte or two could accidentally change the entire meaning of the communication because someone didn't follow the protocol correctly.

Course I guess the same could happen here, but I dunno.

One thing that occurs to me now though is that if I need to modify the protocol for some reason (add something or remove something) or maybe change the type of data like from bool to an integer, I would have to go back and change the lengths I hard-coded by hand. If I used the bit converter and found the byte length I wouldn't have to do that, I would just have to modify the methods that encode and decode the stream.

Hmmm...Actually I would have to do that anyway, regardless of the setup...I think.

Wow, anyway, so no glaring issues? Seems like it would encode in the correct order?

I must say I did actually consider serialization, but I was worried that if I serialized the entire class that the recipient, if it was running a slightly older or newer version might run into major issues and they might not be able to communicate at all.

Is that a valid concern? Or am I just too paranoid? This whole application development gig is going to kill me! I just know it!

Momerath,

Were you talking about something like this:

/// <summary>
        /// Prepares a Data object for transmission over the wire.
        /// </summary>
        /// <param name="data">The Data object to prepare</param>
        /// <returns>A byte array to send over the wire</returns>
        public byte[] PrepareForWire(Data data)
        {
            using (MemoryStream DataArray = new MemoryStream())
            {
                BinaryFormatter DataSerializer = new BinaryFormatter();
                DataSerializer.Serialize(DataArray, data);

                return DataArray.ToArray();
            }
        }

Does that cause the size of the array to become bloated?

Looks like that will work. Instead of using hardcoded values, maybe consider using the sizeof(int/etc) keyword. It will give you the size of a datatype relative to the current system architecture.

This got me to thinking...there must be a way to make a generic class serializer that isn't as bloated as using the built in using System.Runtime.Serialization.Formatters.Binary and flagging a class [Serializable]

After a quick google search I found some interesting little methods in the System.Reflection library. For example:

public static Dictionary<string, object> GetFields(object ob)
        {
            Dictionary<string, object> rData = new Dictionary<string, object>();
            Type objectType = ob.GetType();

            foreach (System.Reflection.FieldInfo mInfo in objectType.GetFields())
                rData.Add(mInfo.Name, mInfo.GetValue(ob));

            return rData;
        }

I think I might keep dablling away at this until I have a useful serialization class. It's kind of reinventing the wheel, but it's a good learning exercise to understand how serialization works.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.