FANDOM


Code (Binary Packet Cont.) Edit

C# Code Edit

The following C# code demonstrates the algorithms described in the Binary Packet article. Using this code is as simple as:

BinaryPacket packet = new BinaryPacket();
packet.BeginPacket();
packet.Write...
packet.EndPacket();

For reading you have be careful. Basically read the values into variables and then act on them. Like with all networking, corrupt packets must be handled in a clean way.

type value;
if (!packet.Read...(out value)) { /* Corrupt Packet */ return; }
// Safe to use value after making sure it's legal. 
// Strings should be checked with regex and integers with ranges.

Processing messages stored in a packet is also simple:

uint eventID;
while (packet.ReadEventID(out eventID))
{ 
    switch (eventID)
    {
        case ServerEvents...:
            // Code to handle data section
            break;
        default:
            // Corrupt Packet
            break;
    }
 
}

This is just a simplified read method for messages. In a more complicated system you might have asynchronous and synchronous handlers. An example of an asynchronous read is a latency ping from the server. Asynchronous messages are normally read first and in the default statement in the switch the packet is placed in a queue so that the synchronous messages can be read later in the game loop. The nice thing about this is that the bitIndex is preserved at the last read spot so it's ready to read in the first synchronous message.

The C# code:

using System;
using System.Text;
using System.Collections.Generic;
 
namespace NetworkBinaryPacket
{
    public class BinaryPacket
    {
        private List<byte> buffer;
        private int bitIndex = 0;
        private int maxBitIndex = 0;
 
 
        /// <summary>
        /// Constructor.
        /// </summary>
        public BinaryPacket()
        {
            buffer = new List<byte>();
        }
 
 
        /// <summary>
        /// Fills the buffer with the requested bytes to work with.
        /// </summary>
        /// <param name="buffer">The bytes to load into the buffer</param>
        public BinaryPacket(byte[] buffer)
        {
            this.buffer = new List<byte>(buffer);
        }
 
 
        /// <summary>
        /// Gets or sets the bit index.
        /// </summary>
        public int BitIndex
        {
            get
            {
                return bitIndex;
            }
            set
            {
                if (value < 0 || (value + 7) / 8 > buffer.Count) throw new ArgumentOutOfRangeException("Unable to set the bit index outside of the buffer size.");
                bitIndex = value;
            }
        }
 
 
        /// <summary>
        /// Gets or Sets the max bit index.
        /// </summary>
        public int MaxBitIndex
        {
            get
            {
                return maxBitIndex;
            }
            set
            {
                if (value < 0 || (value + 7) / 8 > buffer.Count) throw new ArgumentOutOfRangeException("Unable to set the max bit index outside of the buffer size.");
                maxBitIndex = value;
            }
        }
 
 
        /// <summary>
        /// Gets the header size for the length in bytes.
        /// </summary>
        public int HeaderSize
        {
            get
            {
                return sizeof(uint);
            }
        }
 
 
        /// <summary>
        /// Gets the header size for the length in bits.
        /// </summary>
        public int HeaderSizeInBits
        {
            get
            {
                return sizeof(uint) * 8;
            }
        }
 
 
        /// <summary>
        /// Returns the buffer length in bytes.
        /// </summary>
        public int Length
        {
            get
            {
                return buffer.Count;
            }
        }
 
 
        /// <summary>
        /// Returns the buffer as an array of bytes.
        /// </summary>
        public byte[] Buffer
        {
            get
            {
                return buffer.ToArray();
            }
        }
 
 
        /// <summary>
        /// Returns a string of 0s and 1s representing the bits in the buffer. Good for debugging.
        /// Places a space between nibbles and two spaces between bytes.
        /// </summary>
        /// <returns></returns>
        public string Trace()
        {
            string s = string.Empty;
            for (int copyBits = 0; copyBits < buffer.Count * 8; ++copyBits)
            {
                s += ((buffer[copyBits / 8] >> (7 - copyBits % 8)) & 0x1) == 0 ? "0" : "1";
                if ((copyBits + 1) % 4 == 0 && copyBits != 0)
                {
                    s += " ";
                    if ((copyBits + 1) % 8 == 0)
                    {
                        s += " ";
                    }
                }
            }
            return s;
        }
 
 
        /// <summary>
        /// Expands the buffer size appending bytes so that the write functions don't overflow.
        /// Records the furthest write in the maxBitIndex
        /// </summary>
        /// <param name="bits">The number of bits to allocate and record.</param>
        private void ExpandBuffer(int bits)
        {
            if (bits < 1) throw new ArgumentOutOfRangeException("bits must be greater than 0");
            while ((bitIndex + bits + 7) / 8 > buffer.Count) buffer.Add(new byte());
            maxBitIndex = Math.Max(maxBitIndex, bitIndex + bits);
        }
 
 
        /// <summary>
        /// Rounds the bitIndex up to a byte.
        /// </summary>
        public void RoundUpToByte()
        {
            bitIndex = (bitIndex + 7) / 8 * 8;
        }
 
 
        /// <summary>
        /// Reads the length stored in the header. 
        /// BeginPacket and EndPacket should have been called to make this.
        /// Note: It's expected 4 bytes are in the buffer before this is called.
        /// </summary>
        public void ReadLength()
        {
            int oldBitIndex = bitIndex;
            bitIndex = 0;
            maxBitIndex = 0;
            uint length;
            if (ReadUInt32(out length))
            {
                maxBitIndex = (int)length;
            }
            bitIndex = oldBitIndex;
        }
 
 
        //WRITE METHODS
 
 
        /// <summary>
        /// Writes a begin packet header of 32 bits.
        /// </summary>
        public void BeginPacket()
        {
            WriteUInt32(0);
        }
 
 
        /// <summary>
        /// Writes the maxbits of the packet to the beginning of the packet.
        /// </summary>
        public void EndPacket()
        {
            int oldBitIndex = bitIndex;
            bitIndex = 0;
            WriteUInt32((uint)maxBitIndex);
            bitIndex = oldBitIndex;
        }
 
 
        /// <summary>
        /// Writes an Event ID.
        /// </summary>
        /// <param name="value">The event ID normally stored in an enumeration.</param>
        public void WriteEventID(uint value)
        {
            WriteDynamicUInt(value, 6);
        }
 
 
        /// <summary>
        /// Writes a single bit either 0 or 1 into the buffer.
        /// </summary>
        /// <param name="value">The boolean value to write.</param>
        public void WriteBool(bool value)
        {
            ExpandBuffer(1);
            if (value) buffer[bitIndex / 8] |= (byte)(1 << (7 - bitIndex % 8));
            ++bitIndex;
        }
 
 
        /// <summary>
        /// Writes an 8 bit unsigned byte into the buffer.
        /// </summary>
        /// <param name="value">The unsigned byte value to write.</param>
        public void WriteByte(byte value)
        {
            ExpandBuffer(8);
            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> offset);
            if (offset != 0)
            {
                buffer[bitIndex / 8 + 1] |= (byte)(value << 8 - offset);
            }
            bitIndex += 8;
        }
 
 
        /// <summary>
        /// Writes an 8 bit signed byte into the buffer.
        /// </summary>
        /// <param name="value">The signed byte value to write.</param>
        public void WriteSByte(sbyte value)
        {
            ExpandBuffer(8);
            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> offset);
            if (offset != 0)
            {
                buffer[bitIndex / 8 + 1] |= (byte)(value << 8 - offset);
            }
            bitIndex += 8;
        }
 
 
        /// <summary>
        /// Writes a 16 bit unsigned short into the buffer.
        /// </summary>
        /// <param name="value">The unsigned short value to write.</param>
        public void WriteUInt16(ushort value)
        {
            ExpandBuffer(16);
            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> 8 + offset);
            buffer[bitIndex / 8 + 1] |= (byte)(value >> offset);
            if (offset != 0)
            {
                buffer[bitIndex / 8 + 2] |= (byte)(value << 8 - offset);
            }
            bitIndex += 16;
        }
 
 
        /// <summary>
        /// Writes a 16 bit signed short into the buffer.
        /// </summary>
        /// <param name="value">The signed short value to write.</param>
        public void WriteInt16(short value)
        {
            ExpandBuffer(16);
            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> 8 + offset);
            buffer[bitIndex / 8 + 1] |= (byte)(value >> offset);
            if (offset != 0)
            {
                buffer[bitIndex / 8 + 2] |= (byte)(value << 8 - offset);
            }
            bitIndex += 16;
        }
 
 
        /// <summary>
        /// Writes a 32 bit unsigned integer into the buffer.
        /// </summary>
        /// <param name="value">The unsigned integer value to write.</param>
        public void WriteUInt32(uint value)
        {
            ExpandBuffer(32);
            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> 24 + offset);
            buffer[bitIndex / 8 + 1] |= (byte)(value >> 16 + offset);
            buffer[bitIndex / 8 + 2] |= (byte)(value >> 8 + offset);
            buffer[bitIndex / 8 + 3] |= (byte)(value >> offset);
            if (offset != 0)
            {
                buffer[bitIndex / 8 + 4] |= (byte)(value << 8 - offset);
            }
            bitIndex += 32;
        }
 
 
        /// <summary>
        /// Writes a 32 bit signed integer into the buffer.
        /// </summary>
        /// <param name="value">The signed integer value to write.</param>
        public void WriteInt32(int value)
        {
            ExpandBuffer(32);
            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> 24 + offset);
            buffer[bitIndex / 8 + 1] |= (byte)(value >> 16 + offset);
            buffer[bitIndex / 8 + 2] |= (byte)(value >> 8 + offset);
            buffer[bitIndex / 8 + 3] |= (byte)(value >> offset);
            if (offset != 0)
            {
                buffer[bitIndex / 8 + 4] |= (byte)(value << 8 - offset);
            }
            bitIndex += 32;
        }
 
 
        /// <summary>
        /// Writes an n bit unsigned integer into the buffer.
        /// </summary>
        /// <param name="value">The unsigned integer value to write.</param>
        /// <param name="bits">The number of bits to use.</param>
        public void WriteUInt(uint value, int bits)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");
            if (bits != 32 && value > (0x1 << bits) - 1) throw new ArgumentOutOfRangeException("Value does not fit into " + bits.ToString() + " bits.");
 
 
            ExpandBuffer(bits);
 
 
            value <<= 32 - bits;
 
 
            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> 24 + offset);
            if (offset + bits > 8)
            {
                buffer[bitIndex / 8 + 1] |= (byte)(value >> 16 + offset);
                if (offset + bits > 16)
                {
                    buffer[bitIndex / 8 + 2] |= (byte)(value >> 8 + offset);
                    if (offset + bits > 24)
                    {
                        buffer[bitIndex / 8 + 3] |= (byte)(value >> offset);
                        if (offset + bits > 32)
                        {
                            buffer[bitIndex / 8 + 4] |= (byte)(value << 8 - offset);
                        }
                    }
                }
            }
            bitIndex += bits;
        }
 
 
        /// <summary>
        /// Writes an n bit signed integer into the buffer.
        /// </summary>
        /// <param name="value">The signed integer value to write.</param>
        /// <param name="bits">The number of bits to use.</param>
        public void WriteInt(int value, int bits)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");
            if (bits != 32 && (value < -(0x1 << (bits - 1)) || value >= 0x1 << (bits - 1))) throw new ArgumentOutOfRangeException("Value does not fit into " + bits.ToString() + " bits.");
 
 
            ExpandBuffer(bits);
 
 
            value <<= 32 - bits;
            uint uvalue = (uint)value;
 
 
            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(uvalue >> 24 + offset);
            if (offset + bits > 8)
            {
                buffer[bitIndex / 8 + 1] |= (byte)(uvalue >> 16 + offset);
                if (offset + bits > 16)
                {
                    buffer[bitIndex / 8 + 2] |= (byte)(uvalue >> 8 + offset);
                    if (offset + bits > 24)
                    {
                        buffer[bitIndex / 8 + 3] |= (byte)(uvalue >> offset);
                        if (offset + bits > 32)
                        {
                            buffer[bitIndex / 8 + 4] |= (byte)(uvalue << 8 - offset);
                        }
                    }
                }
            }
            bitIndex += bits;
        }
 
 
        /// <summary>
        /// Writes a 32 bit single into the buffer.
        /// </summary>
        /// <param name="value">The single value to write.</param>
        public void WriteSingle(float value)
        {
            WriteUInt32(BitConverter.ToUInt32(BitConverter.GetBytes(value), 0));
        }
 
 
        /// <summary>
        /// Writes a 64 bit double into the buffer.
        /// </summary>
        /// <param name="value">The double value to write.</param>
        public void WriteDouble(double value)
        {
            byte[] bytes = BitConverter.GetBytes(value);
            WriteUInt32(BitConverter.ToUInt32(bytes, 0));
            WriteUInt32(BitConverter.ToUInt32(bytes, 4));
        }
 
 
        /// <summary>
        /// Writes an integer using a variable width encoding of bits. Choose a bits value that represents the number of bits to hold the average value.
        /// </summary>
        /// <param name="value">The unsigned integer value to write.</param>
        /// <param name="bits">The number of bits to use for the sequence.</param>
        public void WriteDynamicUInt(uint value, int bits)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");
            int shift = bits;
            // Stop when our value can fit inside
            for (; shift < 32 && value >= (0x1 << shift); shift += bits)
            {
                WriteBool(true); // Write a 1 for a continuation bit signifying one more interval is needed
            }
            if (shift < 32)
            {
                WriteBool(false);    // Write a 0 for a continuation bit signifying the end
            }
            WriteUInt(value, shift >= 32 ? 32 : shift);
        }
 
 
        /// <summary>
        /// Default 4 bits.
        /// </summary>
        /// <param name="value">The unsigned integer value to write.</param>
        public void WriteDynamicUInt(uint value)
        {
            WriteDynamicUInt(value, 4);
        }
 
 
        /// <summary>
        /// Writes an integer using a variable length of bits. Choose a bits value that represents the number of bits to hold the average value.
        /// </summary>
        /// <param name="value">The signed integer value to write.</param>
        /// <param name="bits">The number of bits to use for the sequence.</param>
        public void WriteDynamicInt(int value, int bits)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");
            int shift = bits;
            // Stop when our value can fit inside
            for (; shift < 32 && (value < -(0x1 << (shift - 1)) || value >= 0x1 << (shift - 1)); shift += bits)
            {
                WriteBool(true); // Write a 1 for a continuation bit signifying one more interval is needed
            }
            if (shift < 32)
            {
                WriteBool(false);    // Write a 0 for a continuation bit signifying the end
            }
            WriteInt(value, shift >= 32 ? 32 : shift);
        }
 
 
        /// <summary>
        /// Default 4 bits.
        /// </summary>
        /// <param name="value">The signed integer value to write.</param>
        public void WriteDynamicInt(int value)
        {
            WriteDynamicInt(value, 4);
        }
 
 
        /// <summary>
        /// Creates a value for the ratio: value / (2 ^ bitResolution - 1) in relationship to value / (max - min).
        /// This allows a floating point to have a resolution.
        /// </summary>
        /// <param name="value">The floating point value in the range.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="bitResolution">The number of bits to use for the ratio.</param>
        public void WriteCustomResolutionSingle(float value, float min, float max, int bitResolution)
        {
            if (bitResolution < 1 || bitResolution > 31) throw new ArgumentOutOfRangeException("bitResolution must be in the range (0, 32).");
            if (min > max) throw new ArgumentOutOfRangeException("min argument must be less than the max argument.");
            if (value < min || value > max) throw new ArgumentOutOfRangeException("The value must be on the interval [min, max]");
            uint uValue;
            if (min < 0 && max > 0)
            {
                uValue = value == 0 ? 0 : (uint)Math.Round((value - min) / (max - min) * (float)((0x1 << bitResolution) - 2)) + 1;
            }
            else
            {
                uValue = (uint)Math.Round((value - min) / (max - min) * (float)((0x1 << bitResolution) - 1));
            }
            WriteUInt(uValue, bitResolution);
        }
 
 
        /// <summary>
        /// Creates a value for the ratio: value / (2 ^ bitResolution - 1) in relationship to value / (max - min).
        /// This allows a floating point to have a resolution.
        /// </summary>
        /// <param name="value">The double value in the range.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="bitResolution">The number of bits to use for the ratio.</param>
        public void WriteCustomResolutionDouble(double value, double min, double max, int bitResolution)
        {
            if (bitResolution < 1 || bitResolution > 31) throw new ArgumentOutOfRangeException("bitResolution must be in the range (0, 32).");
            if (min > max) throw new ArgumentOutOfRangeException("min argument must be less than the max argument.");
            if (value < min || value > max) throw new ArgumentOutOfRangeException("The value must be on the interval [min, max]");
            uint uValue;
            if (min < 0 && max > 0)
            {
                uValue = value == 0 ? 0 : (uint)Math.Round((value - min) / (max - min) * (double)((0x1 << bitResolution) - 2)) + 1;
            }
            else
            {
                uValue = (uint)Math.Round((value - min) / (max - min) * (double)((0x1 << bitResolution) - 1));
            }
            WriteUInt(uValue, bitResolution);
        }
 
 
        /// <summary>
        /// Writes the length of the string using WriteDynamicUnsignedInteger and then writes a boolean, true for unicode, false for ascii. 
        /// ASCII uses a compression step using either 6 or 7 bits per character.
        /// </summary>
        /// <param name="value">The string value to write.</param>
        /// <param name="bits">The number of bits for the length written using an unsigned dynamic integer.</param>
        public void WriteString(string value, int bits)
        {
            if (bits < 1 || bits > 31) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32).");
            uint size = (uint)value.Length;
            WriteDynamicUInt(size, bits);
            throw new NotImplementedException();
        }
 
 
        /// <summary>
        /// Defaults to 4 arrayBits. Read the overloaded function for a description.
        /// </summary>
        /// <param name="value">The string value to Write.</param>
        public void WriteString(string value)
        {
            WriteString(value, 4);
        }
 
 
        /// <summary>
        /// Appends a binary packet to the buffer.
        /// </summary>
        /// <param name="value">The binary packet to write.</param>
        public void WriteBinaryPacket(BinaryPacket value)
        {
            int oldBitIndex = value.bitIndex;
            value.bitIndex = 0;
            int valueMaxBitIndex = value.MaxBitIndex;
            WriteDynamicUInt((uint)valueMaxBitIndex);
            for (uint copyBytes = 0; copyBytes < valueMaxBitIndex / 8; ++copyBytes)
            {
                byte valueByte;
                value.ReadByte(out valueByte);
                WriteByte(valueByte);
            }
            for (uint copyBits = 0; copyBits < valueMaxBitIndex % 8; ++copyBits)
            {
                bool valueBit;
                value.ReadBool(out valueBit);
                WriteBool(valueBit);
            }
            bitIndex += valueMaxBitIndex;
            value.bitIndex = oldBitIndex;
        }
 
 
        //READ METHODS
 
 
        /// <summary>
        /// Reads an Event ID.
        /// </summary>
        /// <param name="value">The event ID normally stored in an enumeration.</param>
        /// <returns>false on error.</returns>
        public bool ReadEventID(out uint value)
        {
            return ReadDynamicUInt(out value, 6);
        }
 
 
        /// <summary>
        /// Reads one bit from the buffer.
        /// </summary>
        /// <param name="value">Boolean.</param>
        /// <returns>false on error.</returns>
        public bool ReadBool(out bool value)
        {
            value = false;
            if ((bitIndex + 1 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            value = ((buffer[bitIndex / 8] >> (7 - bitIndex % 8)) & 0x1) == 1;
            ++bitIndex;
            return true;
        }
 
 
        /// <summary>
        /// Reads an 8 bits unsigned byte from the buffer.
        /// </summary>
        /// <param name="value">Unsigned Byte.</param>
        /// <returns>false on error.</returns>
        public bool ReadByte(out byte value)
        {
            value = 0;
            if ((bitIndex + 8 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (byte)(buffer[bitIndex / 8] << offset);
            if (offset != 0)
            {
                value |= (byte)(buffer[bitIndex / 8 + 1] >> 8 - offset);
            }
            bitIndex += 8;
            return true;
        }
 
 
        /// <summary>
        /// Reads an 8 bits signed byte from the buffer.
        /// </summary>
        /// <param name="value">Signed Byte.</param>
        /// <returns>false on error.</returns>
        public bool ReadSByte(out sbyte value)
        {
            value = 0;
            if ((bitIndex + 8 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (sbyte)(buffer[bitIndex / 8] << offset);
            if (offset != 0)
            {
                value |= (sbyte)(buffer[bitIndex / 8 + 1] >> 8 - offset);
            }
            bitIndex += 8;
            return true;
        }
 
 
        /// <summary>
        /// Reads a 16 bit unsigned short from the buffer.
        /// </summary>
        /// <param name="value">Unsigned Short.</param>
        /// <returns>false on error.</returns>
        public bool ReadUInt16(out ushort value)
        {
            value = 0;
            if ((bitIndex + 16 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (ushort)(buffer[bitIndex / 8] << 8 + offset);
            value |= (ushort)(buffer[bitIndex / 8 + 1] << offset);
            if (offset != 0)
            {
                value |= (ushort)(buffer[bitIndex / 8 + 2] >> 8 - offset);
            }
            bitIndex += 16;
            return true;
        }
 
 
        /// <summary>
        /// Reads a 16 bit signed short from the buffer.
        /// </summary>
        /// <param name="value">Signed Short.</param>
        /// <returns>false on error.</returns>
        public bool ReadInt16(out short value)
        {
            value = 0;
            if ((bitIndex + 16 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (short)(buffer[bitIndex / 8] << 8 + offset);
            value |= (short)(buffer[bitIndex / 8 + 1] << offset);
            if (offset != 0)
            {
                value |= (short)(buffer[bitIndex / 8 + 2] >> 8 - offset);
            }
            bitIndex += 16;
            return true;
        }
 
 
        /// <summary>
        /// Reads a 32 bit unsigned integer from the buffer.
        /// </summary>
        /// <param name="value">Unsigned Integer.</param>
        /// <returns>false on error.</returns>
        public bool ReadUInt32(out uint value)
        {
            value = 0;
            if ((bitIndex + 32 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (uint)(buffer[bitIndex / 8] << 24 + offset);
            value |= (uint)(buffer[bitIndex / 8 + 1] << 16 + offset);
            value |= (uint)(buffer[bitIndex / 8 + 2] << 8 + offset);
            value |= (uint)(buffer[bitIndex / 8 + 3] << offset);
            if (offset != 0)
            {
                value |= (uint)(buffer[bitIndex / 8 + 4] >> 8 - offset);
            }
            bitIndex += 32;
            return true;
        }
 
 
        /// <summary>
        /// Reads a 32 bit signed integer from the buffer.
        /// </summary>
        /// <param name="value">Signed Integer.</param>
        /// <returns>false on error.</returns>
        public bool ReadInt32(out int value)
        {
            value = 0;
            if ((bitIndex + 32 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (int)(buffer[bitIndex / 8] << 24 + offset);
            value |= (int)(buffer[bitIndex / 8 + 1] << 16 + offset);
            value |= (int)(buffer[bitIndex / 8 + 2] << 8 + offset);
            value |= (int)(buffer[bitIndex / 8 + 3] << offset);
            if (offset != 0)
            {
                value |= (int)(buffer[bitIndex / 8 + 4] >> 8 - offset);
            }
            bitIndex += 32;
            return true;
        }
 
 
        /// <summary>
        /// Reads an n bit custom unsigned integer from the buffer.
        /// </summary>
        /// <param name="value">Unsigned Integer.</param>
        /// <param name="bits">The number of bits used to write.</param>
        /// <returns>false on error.</returns>
        public bool ReadUInt(out uint value, int bits)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");
 
            value = 0;
            if ((bitIndex + bits + 7) / 8 > buffer.Count)
            {
                return false;
            }
 
 
            int offset = bitIndex % 8;
            value = (uint)buffer[bitIndex / 8] << 24 + offset;
            if (offset + bits > 8)
            {
                value |= (uint)buffer[bitIndex / 8 + 1] << 16 + offset;
                if (offset + bits > 16)
                {
                    value |= (uint)buffer[bitIndex / 8 + 2] << 8 + offset;
                    if (offset + bits > 24)
                    {
                        value |= (uint)buffer[bitIndex / 8 + 3] << offset;
                        if (offset + bits > 32)
                        {
                            value |= (uint)buffer[bitIndex / 8 + 4] >> 8 - offset;
                        }
                    }
                }
            }
 
 
            value >>= 32 - bits;
            bitIndex += bits;
            return true;
        }
 
 
        /// <summary>
        /// Reads an n bit custom signed integer from the buffer.
        /// </summary>
        /// <param name="value">Signed Integer.</param>
        /// <param name="bits">The number of bits used to write.</param>
        /// <returns>false on error.</returns>
        public bool ReadInt(out int value, int bits)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");
 
            value = 0;
            if ((bitIndex + bits + 7) / 8 > buffer.Count)
            {
                return false;
            }
 
 
            int offset = bitIndex % 8;
            value = buffer[bitIndex / 8] << 24 + offset;
            if (offset + bits > 8)
            {
                value |= buffer[bitIndex / 8 + 1] << 16 + offset;
                if (offset + bits > 16)
                {
                    value |= buffer[bitIndex / 8 + 2] << 8 + offset;
                    if (offset + bits > 24)
                    {
                        value |= buffer[bitIndex / 8 + 3] << offset;
                        if (offset + bits > 32)
                        {
                            value |= buffer[bitIndex / 8 + 4] >> 8 - offset;
                        }
                    }
                }
            }
 
 
            value >>= 32 - bits;
            bitIndex += bits;
            return true;
        }
 
 
        /// <summary>
        /// Reads a 32 bit single from the buffer.
        /// </summary>
        /// <param name="value">Single.</param>
        /// <returns>false on error.</returns>
        public bool ReadSingle(out float value)
        {
            value = 0;
            if ((bitIndex + 32 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            uint uValue;
            if (!ReadUInt32(out uValue)) return false;
            value = BitConverter.ToSingle(BitConverter.GetBytes(uValue), 0);
            return true;
        }
 
 
        /// <summary>
        /// Reads a 64 bit double from the buffer.
        /// </summary>
        /// <param name="value">Double.</param>
        /// <returns>false on error.</returns>
        public bool ReadDouble(out double value)
        {
            value = 0;
            if ((bitIndex + 64 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            byte[] bytes = new byte[8];
            uint uValue1;
            if (!ReadUInt32(out uValue1)) return false;
            byte[] first4bytes = BitConverter.GetBytes(uValue1);
            uint uValue2;
            if (!ReadUInt32(out uValue2)) return false;
            byte[] last4bytes = BitConverter.GetBytes(uValue2);
 
 
            for (uint copyBytes = 0; copyBytes < 4; ++copyBytes)
            {
                bytes[copyBytes] = first4bytes[copyBytes];
            }
            for (uint copyBytes = 4; copyBytes < 8; ++copyBytes)
            {
                bytes[copyBytes] = last4bytes[copyBytes - 4];
            }
            value = BitConverter.ToDouble(bytes, 0);
            return true;
        }
 
 
        /// <summary>
        /// Reads the dynamic unsigned integer.
        /// </summary>
        /// <param name="value">Unsigned Integer.</param>
        /// <param name="bits">The bit size of the sequence used to write.</param>
        /// <returns>false on error.</returns>
        public bool ReadDynamicUInt(out uint value, int bits)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");
            value = 0;
            int valueBitCount = bits;
            bool continuationBitValue = true;
            do
            {
                if (!ReadBool(out continuationBitValue)) return false;
                if (continuationBitValue) valueBitCount += bits;
            }
            while (continuationBitValue && valueBitCount < 32);
            return ReadUInt(out value, valueBitCount >= 32 ? 32 : valueBitCount);
        }
 
 
        /// <summary>
        /// Default 4 bits.
        /// </summary>
        /// <param name="value">Unsigned Integer.</param>
        /// <returns>false on error.</returns>
        public bool ReadDynamicUInt(out uint value)
        {
            return ReadDynamicUInt(out value, 4);
        }
 
 
        /// <summary>
        /// Reads the dynamic unsigned integer.
        /// </summary>
        /// <param name="value">Unsigned Integer.</param>
        /// <param name="bits">The bit size of the sequence used to write.</param>
        /// <returns>false on error.</returns>
        public bool ReadDynamicInt(out int value, int bits)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");
            value = 0;
            int valueBitCount = bits;
            bool continuationBitValue = true;
            do
            {
                if (!ReadBool(out continuationBitValue)) return false;
                if (continuationBitValue) valueBitCount += bits;
            }
            while (continuationBitValue && valueBitCount < 32);
            return ReadInt(out value, valueBitCount >= 32 ? 32 : valueBitCount);
        }
 
 
        /// <summary>
        /// Default 4 bits.
        /// </summary>
        /// <param name="value">Signed Integer.</param>
        /// <returns>false on error.</returns>
        public bool ReadDynamicSignedInteger(out int value)
        {
            return ReadDynamicInt(out value, 4);
        }
 
 
        /// <summary>
        /// Reads a custom resolution single.
        /// </summary>
        /// <param name="value">Single.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="bitResolution">The number of bits written for the ratio.</param>
        /// <returns>false on error.</returns>
        public bool ReadCustomResolutionSingle(out float value, float min, float max, int bitResolution)
        {
            if (bitResolution < 1 || bitResolution > 31) throw new ArgumentOutOfRangeException("bitResolution must be in the range (0, 32).");
            if (min > max) throw new ArgumentOutOfRangeException("min argument must be less than the max argument.");
            value = 0;
            uint uValue;
            if (!ReadUInt(out uValue, bitResolution)) return false;
            if (min < 0 && max > 0)
            {
                value = uValue == 0 ? 0 : (uValue - 1) / (float)((0x1 << bitResolution) - 2) * (max - min) + min;
            }
            else
            {
                value = uValue / (float)((0x1 << bitResolution) - 1) * (max - min) + min;
            }
            return true;
        }
 
 
        /// <summary>
        /// Reads a custom resolution double
        /// </summary>
        /// <param name="value">Double.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="bitResolution">The number of bits written for the ratio.</param>
        /// <returns>false on error.</returns>
        public bool ReadCustomResolutionDouble(out double value, double min, double max, int bitResolution)
        {
            if (bitResolution < 1 || bitResolution > 31) throw new ArgumentOutOfRangeException("bitResolution must be in the range (0, 32).");
            if (min > max) throw new ArgumentOutOfRangeException("min argument must be less than the max argument.");
            value = 0;
            uint uValue;
            if (!ReadUInt(out uValue, bitResolution)) return false;
            if (min < 0 && max > 0)
            {
                value = uValue == 0 ? 0 : (uValue - 1) / (double)((0x1 << bitResolution) - 2) * (max - min) + min;
            }
            else
            {
                value = uValue / (double)((0x1 << bitResolution) - 1) * (max - min) + min;
            }
            return true;
        }
 
 
        /// <summary>
        /// Reads a string
        /// </summary>
        /// <param name="value">String.</param>
        /// <param name="bits">The number of bits for the length written using an unsigned dynamic integer.</param>
        /// <returns>false on error.</returns>
        public bool ReadString(out string value, int bits)
        {
            if (bits < 1 || bits > 31) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32).");
            value = string.Empty;
            uint size;
            if (!ReadDynamicUInt(out size, bits)) return false;
            throw new NotImplementedException();
            // return true;
        }
 
 
        /// <summary>
        /// Default 4 bits.
        /// </summary>
        /// <param name="value">String.</param>
        /// <returns>false on error.</returns>
        public bool ReadString(out string value)
        {
            return ReadString(out value, 4);
        }
 
 
        /// <summary>
        /// Reads a binary packet that has been written to the buffer.
        /// </summary>
        /// <param name="value">A binary packet.</param>
        /// <returns>false on error.</returns>
        public bool ReadBinaryPacket(out BinaryPacket value)
        {
            value = new BinaryPacket();
            uint valueMaxBitIndex;
            if (!ReadDynamicUInt(out valueMaxBitIndex)) return false;
            for (uint copyBytes = 0; copyBytes < valueMaxBitIndex / 8; ++copyBytes)
            {
                byte valueByte;
                if (!ReadByte(out valueByte)) return false;
                value.WriteByte(valueByte);
            }
            for (uint copyBits = 0; copyBits < valueMaxBitIndex % 8; ++copyBits)
            {
                bool valueBit;
                if (!ReadBool(out valueBit)) return false;
                value.WriteBool(valueBit);
            }
            bitIndex += (int)valueMaxBitIndex;
            value.bitIndex = 0;
            return true;
        }
 
 
    }
 
}

C++ Code Edit

This code is simply a quick port of the C# version. It's missing constants and general C++ optimizations that might exist. However, it's made to be very readable so that the concepts can be understood. Still performs well though when profiled as most compilers can optimize it very well.

Read the C# notes above on possible usage.

Header Edit

The C++ code: BinaryPacket.hpp


#ifndef BINARY_PACKET_HPP
#define BINARY_PACKET_HPP
#include <vector>
#include <sstream>
#include <stdexcept>
#include <boost/cstdint.hpp>
 
 
// Binary Writer/Reader
// bool - 1 byte
// [unsigned] char - 1 byte
// [unsigned] short - 2 bytes
// [unsigned] int - 4 bytes
// float - 4 bytes
// double - 8 bytes
// String - No unicode support. C++ has none. Choose a library and implement it if you need it.
class BinaryPacket
{
private:
std::vector<uint8_t> buffer;
int32_t bitIndex;
int32_t maxBitIndex;
 
// Expands the buffer size appending bytes so that the write functions don't overflow.
// Records the furthest write in the maxBitIndex
// bits: The number of bits to allocate and record.
void ExpandBuffer(int32_t bits);
 
public:
// Constructor.
BinaryPacket();
 
// Fills the buffer with the requested bytes to work with.
// buffer: The bytes to load into the buffer.
BinaryPacket(uint8_t* buffer, int32_t maxBitIndex);
 
// Gets the bit index.
int32_t GetBitIndex() const;
 
// Sets the bit index.
void SetBitIndex(int32_t value) throw(std::out_of_range);
 
// Gets the max bit index.
int32_t GetMaxBitIndex() const;
 
// Sets the max bit index.
// value: the value to set the max bit index to.
void SetMaxBitIndex(int32_t value) throw(std::out_of_range);
 
// Gets the header size for the length in bytes.
int32_t GetHeaderSize() const;
 
// Gets the header size for the length in bits.
int32_t GetHeaderSizeInBits() const;
 
// Returns the buffer length in bytes.
int32_t GetLength() const;
 
// Returns the buffer as an array of bytes.
const char* GetBuffer();
 
// Returns a string of 0s and 1s representing the bits in the buffer. Good for debugging.
// Places a space between nibbles and two spaces between bytes.
std::string Trace() const;
 
// Rounds the bitIndex up to a byte.
void RoundUpToByte();
 
// Reads the length stored in the header. 
// BeginPacket and EndPacket should have been called to make this.
// Note: It's expected 4 bytes are in the buffer before this is called.
void ReadLength();
 
// WRITE METHODS
 
// Writes a begin packet header of 32 bits.
void BeginPacket();
 
// Writes maxBitIndex to the packet header created with BeginPacket.
void EndPacket();
 
// Writes an Event ID.
// value: The event ID normally stored in an enumeration.
void WriteEventID(uint32_t value);
 
// Writes a single bit either 0 or 1 into the buffer.
// value: The boolean value to write.
void WriteBool(bool value);
 
// Writes an 8 bit unsigned byte into the buffer.
// value: The unsigned byte value to write.
void WriteByte(uint8_t value);
 
// Writes an 8 bit signed byte into the buffer.
// value: The signed byte value to write.
void WriteSByte(int8_t value);
 
// Writes a 16 bit unsigned short into the buffer.
// value: The unsigned short value to write.
void WriteUInt16(uint16_t value);
 
// Writes a 16 bit signed short into the buffer.
// value: The signed short value to write.
void WriteInt16(int16_t value);
 
// Writes a 32 bit unsigned integer into the buffer.
// value: The unsigned integer value to write.
void WriteUInt32(uint32_t value);
 
// Writes a 32 bit signed integer into the buffer.
// value: The signed integer value to write.
void WriteInt32(int32_t value);
 
// Writes an n bit unsigned integer into the buffer.
// value: The unsigned integer value to write.
// bits: The number of bits to use.
void WriteUInt(uint32_t value, int32_t bits) throw(std::out_of_range);
 
// Writes an n bit signed integer into the buffer.
// value: The signed integer value to write.
// bits: The number of bits to use.
void WriteInt(int32_t value, int32_t bits) throw(std::out_of_range);
 
// Writes a 32 bit single into the buffer.
// value: The single value to write.
void WriteSingle(float value);
 
// Writes a 64 bit double into the buffer. 
// value: The double value to write.
void WriteDouble(double value);
 
// Writes an integer using a variable width encoding of bits. Choose a bits value that represents the number of bits to hold the average value.
// value: The unsigned integer value to write.
// bits: The number of bits to use for the sequence.
void WriteDynamicUInt(uint32_t value, int32_t bits) throw(std::out_of_range);
 
// Default 4 bits.
// value: The unsigned integer value to write.
void WriteDynamicUInt(uint32_t value);
 
// Writes an integer using a variable length of bits. Choose a bits value that represents the number of bits to hold the average value.
// value: The signed integer value to write.
// bits: The number of bits to use for the sequence.
void WriteDynamicInt(int32_t value, int32_t bits) throw(std::out_of_range);
 
// Default 4 bits.
// value: The signed integer value to write.
void WriteDynamicInt(int32_t value);
 
// Creates a value for the ratio: value / (2 ^ bitResolution - 1) in relationship to value / (max - min).
// This allows a floating point to have a resolution.
// value: The floating point value in the range.
// min: The minimum value in the range.
// max: The maximum value in the range.
// bitResolution: The number of bits to use for the ratio.
void WriteCustomResolutionSingle(float value, float min, float max, int32_t bitResolution) throw(std::out_of_range);
 
// Creates a value for the ratio: value / (2 ^ bitResolution - 1) in relationship to value / (max - min).
// This allows a floating point to have a resolution.
// value">The double value in the range.
// min">The minimum value in the range.
// max">The maximum value in the range.
// bitResolution">The number of bits to use for the ratio.
void WriteCustomResolutionDouble(double value, double min, double max, int32_t bitResolution) throw(std::out_of_range);
 
// Writes the length of the string using WriteDynamicUnsignedInteger and then writes a boolean, true for unicode, false for ascii. 
// ASCII uses a compression step using either 6 or 7 bits per character.
// value: The string value to write.
// bits: The number of bits for the length written using an unsigned dynamic integer.
void WriteString(const std::string& value, int32_t bits) throw(std::out_of_range);
 
// Defaults to 4 arrayBits. Read the overloaded function for a description.
// value: The string value to Write.
void WriteString(const std::string& value);
 
// Appends a binary packet to the buffer.
// value: The binary packet to write.
void WriteBinaryPacket(const BinaryPacket& value);
 
// READ METHODS
 
// Reads an Event ID.
// value: The event ID normally stored in an enumeration.
// returns: false on error.
bool ReadEventID(uint32_t& value);
 
// Reads one bit from the buffer.
// value: Boolean.
// returns: false on error.
bool ReadBool(bool& value);
 
// Reads an 8 bits unsigned byte from the buffer.
// value: Unsigned Byte.
// returns: false on error.
bool ReadByte(uint8_t& value);
 
// Reads an 8 bits signed byte from the buffer.
// value: Signed Byte.
// returns: false on error.
bool ReadSByte(int8_t& value);
 
// Reads a 16 bit unsigned short from the buffer.
// value: Unsigned Short.
// returns: false on error.
bool ReadUInt16(uint16_t& value);
 
// Reads a 16 bit signed short from the buffer.
// value: Signed Short.
// returns: false on error.
bool ReadInt16(int16_t& value);
 
// Reads a 32 bit unsigned integer from the buffer.
// value: Unsigned Integer.
// returns: false on error.
bool ReadUInt32(uint32_t& value);
 
// Reads a 32 bit signed integer from the buffer.
// value: Signed Integer.
// returns: false on error.
bool ReadInt32(int32_t& value);
 
// Reads an n bit custom unsigned integer from the buffer.
// value: Unsigned Integer.
// bits: The number of bits used to write.
// returns: false on error.
bool ReadUInt(uint32_t& value, int32_t bits) throw(std::out_of_range);
 
// Reads an n bit custom signed integer from the buffer.
// value: Signed Integer.
// bits: The number of bits used to write.
// returns: false on error.
bool ReadInt(int32_t& value, int32_t bits) throw(std::out_of_range);
 
// Reads a 32 bit single from the buffer.
// value: Single.
// returns: false on error.
bool ReadSingle(float& value);
 
// Reads a 64 bit double from the buffer.
// value: Double.
// returns: false on error.
bool ReadDouble(double& value);
 
// Reads the dynamic unsigned integer.
// value: Unsigned Integer.
// bits: The bit size of the sequence used to write.
// returns: false on error.
bool ReadDynamicUInt(uint32_t& value, int32_t bits) throw(std::out_of_range);
 
// Default 4 bits.
// value: Unsigned Integer.
// returns: false on error.
bool ReadDynamicUInt(uint32_t& value);
 
// Reads the dynamic unsigned integer.
// value: Unsigned Integer.
// bits: The bit size of the sequence used to write.
// returns: false on error.
bool ReadDynamicInt(int32_t& value, int32_t bits) throw(std::out_of_range);
 
// Default 4 bits.
// value: Signed Integer.
// returns: false on error.
bool ReadDynamicInt(int32_t& value);
 
// Reads a custom resolution single.
// value: Single. 
// min: The minimum value in the range.
// max: The maximum value in the range.
// bitResolution: The number of bits written for the ratio.
// returns: false on error.
bool ReadCustomResolutionSingle(float& value, float min, float max, int32_t bitResolution) throw(std::out_of_range);
 
// Reads a custom resolution double
// value: Double.
// min: The minimum value in the range.
// max: The maximum value in the range.
// bitResolution: The number of bits written for the ratio.
// returns: false on error.
bool ReadCustomResolutionDouble(double& value, double min, double max, int32_t bitResolution) throw(std::out_of_range);
 
// Reads a string
// value: String.
// bits: The number of bits for the length written using an unsigned dynamic integer.
// returns: false on error.
bool ReadString(std::string& value, int32_t bits) throw(std::out_of_range);
 
// Default 4 bits.
// value: String.
// returns: false on error.
bool ReadString(std::string& value);
 
// Reads a binary packet that has been written to the buffer.
// value: A binary packet.
// returns: false on error.
bool ReadBinaryPacket(BinaryPacket& value);
};
 
 
 
 
#endif /* BINARY_PACKET_HPP */



Source Edit

BinaryPacket.cpp


#include "BinaryPacket.hpp"
 
 
 
void BinaryPacket::ExpandBuffer(int32_t bits)
{
if (bits < 1) throw std::out_of_range("bits must be greater than 0");
while ((bitIndex + bits + 7) / 8 > static_cast<int>(buffer.size())) buffer.push_back(0);
maxBitIndex = std::max<int32_t>(maxBitIndex, bitIndex + bits);
}
 
BinaryPacket::BinaryPacket()
{
bitIndex = 0;
maxBitIndex = 0;
}
 
BinaryPacket::BinaryPacket(uint8_t* buffer, int32_t maxBitIndex)
{
bitIndex = 0;
this->maxBitIndex = maxBitIndex;
this->buffer.resize((maxBitIndex + 7) / 8);
memcpy(&this->buffer[0], buffer, (maxBitIndex + 7) / 8);
}
 
int BinaryPacket::GetBitIndex() const
{
return bitIndex;
}
 
void BinaryPacket::SetBitIndex(int32_t value) throw(std::out_of_range)
{
if (value < 0 || (value + 7) / 8 > static_cast<int>(buffer.size())) throw std::out_of_range("Unable to set the bit index outside of the buffer size.");
bitIndex = value;
}
 
int32_t BinaryPacket::GetMaxBitIndex() const
{
return maxBitIndex;
}
 
void BinaryPacket::SetMaxBitIndex(int32_t value) throw(std::out_of_range)
{
if (value < 0 || (value + 7) / 8 > static_cast<int>(buffer.size())) throw std::out_of_range("Unable to set the max bit index outside of the buffer size.");
maxBitIndex = value;
}
 
int32_t BinaryPacket::GetHeaderSize() const
{
return sizeof(uint32_t);
}
 
int32_t BinaryPacket::GetHeaderSizeInBits() const
{
return sizeof(uint32_t) * 8;
}
 
int32_t BinaryPacket::GetLength() const
{
return static_cast<int32_t>(buffer.size());
}
 
const char* BinaryPacket::GetBuffer()
{
return reinterpret_cast<char*>(&buffer[0]);
}
 
std::string BinaryPacket::Trace() const
{
std::string s = "";
for (int32_t copyBits = 0; copyBits < static_cast<int32_t>(buffer.size() * 8); ++copyBits)
{
s += ((buffer[copyBits / 8] >> (7 - copyBits % 8)) & 0x1) == 0 ? "0" : "1";
if ((copyBits + 1) % 4 == 0 && copyBits != 0)
{
s += " ";
if ((copyBits + 1) % 8 == 0)
{
s += " ";
}
}
}
return s;
}
 
void BinaryPacket::RoundUpToByte()
{
bitIndex = (bitIndex + 7) / 8 * 8;
}
 
void BinaryPacket::ReadLength()
{
int oldBitIndex = bitIndex;
bitIndex = 0;
maxBitIndex = 0;
uint32_t length;
if (ReadUInt32(length))
{
maxBitIndex = static_cast<int32_t>(length);
}
bitIndex = oldBitIndex;
}
 
void BinaryPacket::BeginPacket()
{
WriteUInt32(0);
}
 
void BinaryPacket::EndPacket()
{
int oldBitIndex = bitIndex;
bitIndex = 0;
WriteUInt32(static_cast<uint32_t>(maxBitIndex));
bitIndex = oldBitIndex;
}
 
// Write Methods
 
void BinaryPacket::WriteEventID(uint32_t value)
{
WriteDynamicUInt(value, 6);
}
 
void BinaryPacket::WriteBool(bool value)
{
ExpandBuffer(1);
if (value) buffer[bitIndex / 8] |= static_cast<uint8_t>(1 << (7 - bitIndex % 8));
++bitIndex;
}
 
void BinaryPacket::WriteByte(uint8_t value)
{
ExpandBuffer(8);
int32_t offset = bitIndex % 8;
buffer[bitIndex / 8] |= value >> offset;
if (offset != 0)
{
buffer[bitIndex / 8 + 1] |= value << (8 - offset);
}
bitIndex += 8;
}
 
void BinaryPacket::WriteSByte(int8_t value)
{
ExpandBuffer(8);
int32_t offset = bitIndex % 8;
buffer[bitIndex / 8] |= value >> offset;
if (offset != 0)
{
buffer[bitIndex / 8 + 1] |= value << (8 - offset);
}
bitIndex += 8;
}
 
void BinaryPacket::WriteUInt16(uint16_t value)
{
ExpandBuffer(16);
int32_t offset = bitIndex % 8;
buffer[bitIndex / 8] |= value >> (8 + offset);
buffer[bitIndex / 8 + 1] |= value >> offset;
if (offset != 0)
{
buffer[bitIndex / 8 + 2] |= value << (8 - offset);
}
bitIndex += 16;
}
 
void BinaryPacket::WriteInt16(int16_t value)
{
ExpandBuffer(16);
int32_t offset = bitIndex % 8;
buffer[bitIndex / 8] |= value >> (8 + offset);
buffer[bitIndex / 8 + 1] |= value >> offset;
if (offset != 0)
{
buffer[bitIndex / 8 + 2] |= value << (8 - offset);
}
bitIndex += 16;
}
 
void BinaryPacket::WriteUInt32(uint32_t value)
{
ExpandBuffer(32);
int32_t offset = bitIndex % 8;
buffer[bitIndex / 8] |= value >> (24 + offset);
buffer[bitIndex / 8 + 1] |= value >> (16 + offset);
buffer[bitIndex / 8 + 2] |= value >> (8 + offset);
buffer[bitIndex / 8 + 3] |= value >> offset;
if (offset != 0)
{
buffer[bitIndex / 8 + 4] |= value << (8 - offset);
}
bitIndex += 32;
}
 
void BinaryPacket::WriteInt32(int32_t value)
{
ExpandBuffer(32);
int32_t offset = bitIndex % 8;
buffer[bitIndex / 8] |= value >> (24 + offset);
buffer[bitIndex / 8 + 1] |= value >> (16 + offset);
buffer[bitIndex / 8 + 2] |= value >> (8 + offset);
buffer[bitIndex / 8 + 3] |= value >> offset;
if (offset != 0)
{
buffer[bitIndex / 8 + 4] |= value << (8 - offset);
}
bitIndex += 32;
}
 
void BinaryPacket::WriteUInt(uint32_t value, int32_t bits) throw(std::out_of_range)
{
if (bits < 1 || bits > 32) throw std::out_of_range("bits must be in the range (0, 32].");
if (bits != 32 && value > static_cast<uint32_t>((0x1 << bits) - 1))
{
std::ostringstream exceptionStr;
exceptionStr << "Value does not fit into " << bits << " bits.";
throw std::out_of_range(exceptionStr.str());
}
ExpandBuffer(bits);
value <<= 32 - bits;
int32_t offset = bitIndex % 8;
buffer[bitIndex / 8] |= value >> (24 + offset);
if (offset + bits > 8)
{
buffer[bitIndex / 8 + 1] |= value >> (16 + offset);
if (offset + bits > 16)
{
buffer[bitIndex / 8 + 2] |= value >> (8 + offset);
if (offset + bits > 24)
{
buffer[bitIndex / 8 + 3] |= value >> offset;
if (offset + bits > 32)
{
buffer[bitIndex / 8 + 4] |= value << (8 - offset);
}
}
}
}
bitIndex += bits;
}
 
void BinaryPacket::WriteInt(int32_t value, int32_t bits) throw(std::out_of_range)
{
if (bits < 1 || bits > 32) throw std::out_of_range("bits must be in the range (0, 32].");
if (bits != 32 && (value < -(0x1 << (bits - 1)) || value >= 0x1 << (bits - 1)))
{
std::ostringstream exceptionStr;
exceptionStr << "Value does not fit into " << bits << " bits.";
throw std::out_of_range(exceptionStr.str());
}
ExpandBuffer(bits);
value <<= 32 - bits;
uint32_t uvalue = *reinterpret_cast<uint32_t*>(&value);
int offset = bitIndex % 8;
buffer[bitIndex / 8] |= uvalue >> (24 + offset);
if (offset + bits > 8)
{
buffer[bitIndex / 8 + 1] |= uvalue >> (16 + offset);
if (offset + bits > 16)
{
buffer[bitIndex / 8 + 2] |= uvalue >> (8 + offset);
if (offset + bits > 24)
{
buffer[bitIndex / 8 + 3] |= uvalue >> offset;
if (offset + bits > 32)
{
buffer[bitIndex / 8 + 4] |= uvalue << (8 - offset);
}
}
}
}
bitIndex += bits;
}
 
void BinaryPacket::WriteSingle(float value)
{
WriteUInt32(*reinterpret_cast<uint32_t*>(&value));
}
 
void BinaryPacket::WriteDouble(double value)
{
uint32_t* ints = reinterpret_cast<uint32_t*>(&value);
WriteUInt32(ints[0]);
WriteUInt32(ints[1]);
}
 
void BinaryPacket::WriteDynamicUInt(uint32_t value, int32_t bits) throw(std::out_of_range)
{
if (bits < 1 || bits > 32) throw std::out_of_range("bits must be in the range (0, 32].");
int32_t shift = bits;
// Stop when our value can fit inside
for (; shift < 32 && value >= static_cast<uint32_t>(0x1 << shift); shift += bits)
{
WriteBool(true); // Write a 1 for a continuation bit signifying one more interval is needed
}
if (shift < 32)
{
WriteBool(false); // Write a 0 for a continuation bit signifying the end
}
WriteUInt(value, shift >= 32 ? 32 : shift);
}
 
void BinaryPacket::WriteDynamicUInt(uint32_t value)
{
WriteDynamicUInt(value, 4);
}
 
void BinaryPacket::WriteDynamicInt(int32_t value, int32_t bits) throw(std::out_of_range)
{
if (bits < 1 || bits > 32) throw std::out_of_range("bits must be in the range (0, 32].");
int32_t shift = bits;
// Stop when our value can fit inside
for (; shift < 32 && (value < -(0x1 << (shift - 1)) || value >= 0x1 << (shift - 1)); shift += bits)
{
WriteBool(true); // Write a 1 for a continuation bit signifying one more interval is needed
}
if (shift < 32)
{
WriteBool(false); // Write a 0 for a continuation bit signifying the end
}
WriteInt(value, shift >= 32 ? 32 : shift);
}
 
void BinaryPacket::WriteDynamicInt(int32_t value)
{
WriteDynamicInt(value, 4);
}
 
void BinaryPacket::WriteCustomResolutionSingle(float value, float min, float max, int bitResolution) throw(std::out_of_range)
{
if (bitResolution < 1 || bitResolution > 31) throw std::out_of_range("bitResolution must be in the range (0, 32).");
if (min > max) throw std::out_of_range("min argument must be less than the max argument.");
if (value < min || value > max) throw std::out_of_range("The value must be on the interval [min, max]");
uint32_t uValue;
if (min < 0 && max > 0)
{
uValue = value == 0 ? 0 : static_cast<uint32_t>((value - min) / (max - min) * static_cast<float>((0x1 << bitResolution) - 2) + 0.5) + 1;
}
else
{
uValue = static_cast<uint32_t>((value - min) / (max - min) * static_cast<float>((0x1 << bitResolution) - 1) + 0.5);
}
WriteUInt(uValue, bitResolution);
}
 
void BinaryPacket::WriteCustomResolutionDouble(double value, double min, double max, int bitResolution) throw(std::out_of_range)
{
if (bitResolution < 1 || bitResolution > 31) throw std::out_of_range("bitResolution must be in the range (0, 32).");
if (min > max) throw std::out_of_range("min argument must be less than the max argument.");
if (value < min || value > max) throw std::out_of_range("The value must be on the interval [min, max]");
uint32_t uValue;
if (min < 0 && max > 0)
{
uValue = value == 0 ? 0 : static_cast<uint32_t>((value - min) / (max - min) * static_cast<double>((0x1 << bitResolution) - 2) + 0.5) + 1;
}
else
{
uValue = static_cast<uint32_t>((value - min) / (max - min) * static_cast<double>((0x1 << bitResolution) - 1) + 0.5);
}
WriteUInt(uValue, bitResolution);
}
 
void BinaryPacket::WriteString(const std::string& value, int bits) throw(std::out_of_range)
{
if (bits < 1 || bits > 31) throw std::out_of_range("bits must be in the range (0, 32).");
uint32_t size = static_cast<uint32_t>(value.length());
WriteDynamicUInt(size, bits);
for (std::string::const_iterator strItr = value.begin(); strItr != value.end(); ++strItr)
{
    		WriteByte(*strItr);
 
}
}
 
void BinaryPacket::WriteString(const std::string& value)
{
WriteString(value, 4);
}
 
void BinaryPacket::WriteBinaryPacket(const BinaryPacket& value)
{
int valueBitIndex = 0;
int valueMaxBitIndex = value.maxBitIndex;
WriteDynamicUInt(static_cast<uint32_t>(valueMaxBitIndex));
for (int copyBytes = 0; copyBytes < valueMaxBitIndex / 8; ++copyBytes)
{
uint8_t valueByte = 0;
if ((valueBitIndex + 8 + 7) / 8 >= static_cast<int>(value.buffer.size()))
{
int offset = valueBitIndex % 8;
valueByte |= value.buffer[valueBitIndex / 8] << offset;
if (offset != 0)
{
valueByte |= value.buffer[valueBitIndex / 8 + 1] >> (8 - offset);
}
}
        	valueBitIndex += 8;
 
WriteByte(valueByte);
}
for (int copyBits = 0; copyBits < valueMaxBitIndex % 8; ++copyBits)
{
bool valueBit = false;
if ((valueBitIndex + 1 + 7) / 8 <= static_cast<int>(value.buffer.size()))
{
valueBit = ((value.buffer[valueBitIndex / 8] >> (7 - valueBitIndex % 8)) & 0x1) == 1;
}
++valueBitIndex;
WriteBool(valueBit);
}
bitIndex += valueMaxBitIndex;
}
 
// Read Methods
 
bool BinaryPacket::ReadEventID(uint32_t& value)
{
return ReadDynamicUInt(value, 6);
}
 
bool BinaryPacket::ReadBool(bool& value)
{
value = false;
if ((bitIndex + 1 + 7) / 8 > static_cast<int>(buffer.size()))
{
return false;
}
value = ((buffer[bitIndex / 8] >> (7 - bitIndex % 8)) & 0x1) == 1;
++bitIndex;
return true;
}
 
bool BinaryPacket::ReadByte(uint8_t& value)
{
value = 0;
if ((bitIndex + 8 + 7) / 8 > static_cast<int>(buffer.size()))
{
return false;
}
int offset = bitIndex % 8;
value |= buffer[bitIndex / 8] << offset;
if (offset != 0)
{
value |= buffer[bitIndex / 8 + 1] >> (8 - offset);
}
bitIndex += 8;
return true;
}
 
bool BinaryPacket::ReadSByte(int8_t& value)
{
value = 0;
if ((bitIndex + 8 + 7) / 8 > static_cast<int>(buffer.size()))
{
return false;
}
int offset = bitIndex % 8;
value |= buffer[bitIndex / 8] << offset;
if (offset != 0)
{
value |= buffer[bitIndex / 8 + 1] >> (8 - offset);
}
bitIndex += 8;
return true;
}
 
bool BinaryPacket::ReadUInt16(uint16_t& value)
{
value = 0;
if ((bitIndex + 16 + 7) / 8 > static_cast<int>(buffer.size()))
{
return false;
}
int offset = bitIndex % 8;
value |= buffer[bitIndex / 8] << (8 + offset);
value |= buffer[bitIndex / 8 + 1] << offset;
if (offset != 0)
{
value |= buffer[bitIndex / 8 + 2] >> (8 - offset);
}
bitIndex += 16;
return true;
}
 
bool BinaryPacket::ReadInt16(int16_t& value)
{
value = 0;
if ((bitIndex + 16 + 7) / 8 > static_cast<int>(buffer.size()))
{
return false;
}
int offset = bitIndex % 8;
value |= buffer[bitIndex / 8] << (8 + offset);
value |= buffer[bitIndex / 8 + 1] << offset;
if (offset != 0)
{
value |= buffer[bitIndex / 8 + 2] >> (8 - offset);
}
bitIndex += 16;
return true;
}
 
bool BinaryPacket::ReadUInt32(uint32_t& value)
{
value = 0;
if ((bitIndex + 32 + 7) / 8 > static_cast<int>(buffer.size()))
{
return false;
}
int offset = bitIndex % 8;
value |= buffer[bitIndex / 8] << (24 + offset);
value |= buffer[bitIndex / 8 + 1] << (16 + offset);
value |= buffer[bitIndex / 8 + 2] << (8 + offset);
value |= buffer[bitIndex / 8 + 3] << offset;
if (offset != 0)
{
value |= buffer[bitIndex / 8 + 4] >> (8 - offset);
}
bitIndex += 32;
return true;
}
 
bool BinaryPacket::ReadInt32(int32_t& value)
{
value = 0;
if ((bitIndex + 32 + 7) / 8 > static_cast<int>(buffer.size()))
{
return false;
}
int offset = bitIndex % 8;
value |= buffer[bitIndex / 8] << (24 + offset);
value |= buffer[bitIndex / 8 + 1] << (16 + offset);
value |= buffer[bitIndex / 8 + 2] << (8 + offset);
value |= buffer[bitIndex / 8 + 3] << offset;
if (offset != 0)
{
value |= buffer[bitIndex / 8 + 4] >> (8 - offset);
}
bitIndex += 32;
return true;
}
 
bool BinaryPacket::ReadUInt(uint32_t& value, int bits) throw(std::out_of_range)
{
if (bits < 1 || bits > 32) throw std::out_of_range("bits must be in the range (0, 32].");
value = 0;
if ((bitIndex + bits + 7) / 8 > static_cast<int>(buffer.size()))
{
return false;
}
int offset = bitIndex % 8;
value = buffer[bitIndex / 8] << (24 + offset);
if (offset + bits > 8)
{
value |= buffer[bitIndex / 8 + 1] << (16 + offset);
if (offset + bits > 16)
{
value |= buffer[bitIndex / 8 + 2] << (8 + offset);
if (offset + bits > 24)
{
value |= buffer[bitIndex / 8 + 3] << offset;
if (offset + bits > 32)
{
value |= buffer[bitIndex / 8 + 4] >> (8 - offset);
}
}
}
}
value >>= 32 - bits;
bitIndex += bits;
return true;
}
 
bool BinaryPacket::ReadInt(int32_t& value, int bits) throw(std::out_of_range)
{
if (bits < 1 || bits > 32) throw std::out_of_range("bits must be in the range (0, 32].");
value = 0;
if ((bitIndex + bits + 7) / 8 > static_cast<int>(buffer.size()))
{
return false;
}
int offset = bitIndex % 8;
value = buffer[bitIndex / 8] << (24 + offset);
if (offset + bits > 8)
{
value |= buffer[bitIndex / 8 + 1] << (16 + offset);
if (offset + bits > 16)
{
value |= buffer[bitIndex / 8 + 2] << (8 + offset);
if (offset + bits > 24)
{
value |= buffer[bitIndex / 8 + 3] << offset;
if (offset + bits > 32)
{
value |= buffer[bitIndex / 8 + 4] >> (8 - offset);
}
}
}
}
value >>= 32 - bits;
bitIndex += bits;
return true;
}
 
bool BinaryPacket::ReadSingle(float& value)
{
value = 0;
if ((bitIndex + 32 + 7) / 8 > (int)buffer.size())
{
return false;
}
uint32_t uValue;
if (!ReadUInt32(uValue)) return false;
value = *reinterpret_cast<float*>(&uValue);
return true;
}
 
bool BinaryPacket::ReadDouble(double& value)
{
value = 0;
if ((bitIndex + 64 + 7) / 8 > (int)buffer.size())
{
return false;
}
uint32_t ints[2];
if (!ReadUInt32(ints[0])) return false;
if (!ReadUInt32(ints[1])) return false;
value = *reinterpret_cast<double*>(ints);
return true;
}
 
bool BinaryPacket::ReadDynamicUInt(uint32_t& value, int bits) throw(std::out_of_range)
{
if (bits < 1 || bits > 32) throw std::out_of_range("bits must be in the range (0, 32].");
value = 0;
int valueBitCount = bits;
bool continuationBitValue = true;
do
{
if (!ReadBool(continuationBitValue)) return false;
if (continuationBitValue) valueBitCount += bits;
}
while (continuationBitValue && valueBitCount < 32);
return ReadUInt(value, valueBitCount >= 32 ? 32 : valueBitCount);
}
 
bool BinaryPacket::ReadDynamicUInt(uint32_t& value)
{
return ReadDynamicUInt(value, 4);
}
 
bool BinaryPacket::ReadDynamicInt(int32_t& value, int bits) throw(std::out_of_range)
{
if (bits < 1 || bits > 32) throw std::out_of_range("bits must be in the range (0, 32].");
value = 0;
int valueBitCount = bits;
bool continuationBitValue = true;
do
{
if (!ReadBool(continuationBitValue)) return false;
if (continuationBitValue) valueBitCount += bits;
}
while (continuationBitValue && valueBitCount < 32);
return ReadInt(value, valueBitCount >= 32 ? 32 : valueBitCount);
}
 
bool BinaryPacket::ReadDynamicInt(int32_t& value)
{
return ReadDynamicInt(value, 4);
}
 
bool BinaryPacket::ReadCustomResolutionSingle(float& value, float min, float max, int bitResolution) throw(std::out_of_range)
{
if (bitResolution < 1 || bitResolution > 31) throw std::out_of_range("bitResolution must be in the range (0, 32).");
if (min > max) throw std::out_of_range("min argument must be less than the max argument.");
value = 0;
uint32_t uValue;
if (!ReadUInt(uValue, bitResolution)) return false;
if (min < 0 && max > 0)
{
value = uValue == 0 ? 0 : (uValue - 1) / static_cast<float>((0x1 << bitResolution) - 2) * (max - min) + min;
}
else
{
value = uValue / static_cast<float>((0x1 << bitResolution) - 1) * (max - min) + min;
}
return true;
}
 
bool BinaryPacket::ReadCustomResolutionDouble(double& value, double min, double max, int bitResolution) throw(std::out_of_range)
{
if (bitResolution < 1 || bitResolution > 31) throw std::out_of_range("bitResolution must be in the range (0, 32).");
if (min > max) throw std::out_of_range("min argument must be less than the max argument.");
value = 0;
uint32_t uValue;
if (!ReadUInt(uValue, bitResolution)) return false;
if (min < 0 && max > 0)
{
value = uValue == 0 ? 0 : (uValue - 1) / static_cast<double>((0x1 << bitResolution) - 2) * (max - min) + min;
}
else
{
value = uValue / static_cast<double>((0x1 << bitResolution) - 1) * (max - min) + min;
}
return true;
}
 
bool BinaryPacket::ReadString(std::string& value, int bits) throw(std::out_of_range)
{
if (bits < 1 || bits > 31) throw std::out_of_range("bits must be in the range (0, 32).");
value = "";
uint32_t size;
if (!ReadDynamicUInt(size, bits)) return false;
for (uint32_t strItr = 0; strItr < size; ++strItr)
{
uint8_t c;
if (!ReadByte(c)) return false;
value += c;
}
return true;
}
 
bool BinaryPacket::ReadString(std::string& value)
{
return ReadString(value, 4);
}
 
bool BinaryPacket::ReadBinaryPacket(BinaryPacket& value)
{
uint32_t valueMaxBitIndex;
if (!ReadDynamicUInt(valueMaxBitIndex)) return false;
for (uint32_t copyBytes = 0; copyBytes < valueMaxBitIndex / 8; ++copyBytes)
{
uint8_t valueByte;
if (!ReadByte(valueByte)) return false;
value.WriteByte(valueByte);
}
for (uint32_t copyBits = 0; copyBits < valueMaxBitIndex % 8; ++copyBits)
{
bool valueBit;
if (!ReadBool(valueBit)) return false;
value.WriteBool(valueBit);
}
bitIndex += (int)valueMaxBitIndex;
value.bitIndex = 0;
return true;
}