Reading bit-aligned data
Asked Answered
R

3

6

I have been reading the SWF format available on Adobe's site and it mentions that in order to save space, variable bits are used to store integers or floats (page 17 in the pdf)

I have always worked with byte-aligned data so have not given much thought to files that are bit-aligned, or have variable alignment where the information is stored in each byte.

So for example, you may have a struct containing four 13-bit integers stored sequentially ( rather than storing them as four 16-bit integers).

The first 13bits is the first integer, the next 13 bits is the second integer, and so on. It pads the last byte appropriate to make the struct byte-aligned with the rest of the file, so 52-bits would be padded to 56-bits, requiring 7 bytes to store those four integers as opposed to 8 bytes.

  • How do I approach this kind of problem?
  • How can I work with a stream of bytes at the bit-level?
  • Is there something I can use to help make it easier to work with this data?

I imagine the solution boils down to using bit-operations on byte arrays.

An example solution for parsing the four 13-bit integers would be nice as well to demonstrate the use of your suggested method.

Rajkot answered 16/8, 2012 at 0:38 Comment(2)
..because I can't give you a full answer, perhaps at least pointing you to BitArray will help :)Wallace
Typically the approach is keeping a buffer of bits in an uint or ulong, extracting what you need and shifting in a new byte of input when there aren't enough bits in the buffer.Olympiaolympiad
D
3

There are two ways of dealing with this that I know of. The first is to manually do it - using bit-wise operators, division, modulus etc. on byte arrays [or integer/ulong etc if you were bored]. IsBitSet Example

The other way is a BitArray - which handles most of this for you :)


It would be nice to add an example of how exactly BitArray handles getting bits 13..25 as an int, as that would be the primary operation. At a first glance I see only a loop.

Fine... I wrote a quick & dirty test proof of concept:

var rnd = new Random();
//var data = Enumerable.Range(0, 10).ToArray();
var data = Enumerable.Range(0, 10).Select(x => rnd.Next(1 << 13)).ToArray();

foreach (var n in data) Console.WriteLine(n);

Console.WriteLine(new string('-', 13));

var bits = new BitArray(data.Length * 13);

for (int i = 0; i < data.Length; i++)
{
    var intBits = new BitArray(new[] { data[i] });
    for (int b = 12; b > -1; b--)
    {
        bits[i * 13 + b] = intBits[b];
        Console.Write(intBits[b] ? 1 : 0);
    }
    Console.WriteLine();
}
Console.WriteLine(new string('-', 13));

for (int i = 0; i < bits.Length / 13; i++)
{
    int number = 0;
    for (int b = 12; b > -1; b--)
        if (bits[i * 13 + b])
            number += 1 << b;

    Console.WriteLine(number);
}
Console.ReadLine();

Which outputs:

910
3934
7326
7990
7712
1178
6380
3460
5113
7489
-------------
0001110001110
0111101011110
1110010011110
1111100110110
1111000100000
0010010011010
1100011101100
0110110000100
1001111111001
1110101000001
-------------
910
3934
7326
7990
7712
1178
6380
3460
5113
7489

The bit array doesn't do much other than simplify accessing - it's still quite manual. I expect you'd write your own classes to simply this and make it neat and reusable - for example here's another quick concept:

//Improved to take sign into account.
//Sign is in addition to bits allocated for storage in this version.
//Stored as {sign}{bits}
//E.g.  -5, stored in 3 bits signed is:
//       1 101
//E.g.   5, stored in 3 bits [with sign turned on]
//       0 101
//E.g.   5, stored in 3 bits no sign
//         101  
//This may differ from your exiting format - e.g. you may use two's compliments.
static void Main(string[] args)
{
    int bitsPerInt = 13;

    //Create your data
    var rnd = new Random();
    //var data = Enumerable.Range(-5, 10).ToArray();
    var data = Enumerable.Range(0, 10).Select(x => rnd.Next(-(1 << bitsPerInt), 1 << bitsPerInt)).ToArray();

    var bits = new BitSerlializer();

    //Add length header
    bits.AddInt(data.Length, 8, false);
    foreach (var n in data)
    {
        bits.AddInt(n, bitsPerInt);
        Console.WriteLine(n);
    }

    //Serialize to bytes for network transfer etc.
    var bytes = bits.ToBytes();

    Console.WriteLine(new string('-', 10));
    foreach (var b in bytes) Console.WriteLine(Convert.ToString(b, 2).PadLeft(8, '0'));
    Console.WriteLine(new string('-', 10));

    //Deserialize
    bits = new BitSerlializer(bytes);
    //Get Length Header
    var count = bits.ReadInt(8, false);
    for (int i = 0; i < count; i++)
        Console.WriteLine(bits.ReadInt(bitsPerInt));

    Console.ReadLine();
}

public class BitSerlializer
{
    List<byte> bytes;
    int Position { get; set; }

    public BitSerlializer(byte[] initialData = null)
    {
        if (initialData == null)
            bytes = new List<byte>();
        else
            bytes = new List<byte>(initialData);
    }

    public byte[] ToBytes() { return bytes.ToArray(); }

    public void Addbit(bool val)
    {
        if (Position % 8 == 0) bytes.Add(0);
        if (val) bytes[Position / 8] += (byte)(128 >> (Position % 8));
        Position++;
    }

    public void AddInt(int i, int length, bool isSigned = true)
    {
        if (isSigned) Addbit(i < 0);
        if (i < 0) i = -i;

        for (int pos = --length; pos >= 0; pos--)
        {
            var val = (i & (1 << pos)) != 0;
            Addbit(val);
        }
    }

    public bool ReadBit()
    {
        var val = (bytes[Position / 8] & (128 >> (Position % 8))) != 0;
        ++Position;
        return val;
    }

    public int ReadInt(int length, bool isSigned = true)
    {
        var val = 0;
        var sign = isSigned && ReadBit() ? -1 : 1;

        for (int pos = --length; pos >= 0; pos--)
            if (ReadBit())
                val += 1 << pos;

        return val * sign;
    }
}
Daughterinlaw answered 16/8, 2012 at 0:53 Comment(3)
It would be nice to add an example of how exactly BitArray handles getting bits 13..25 as an int, as that would be the primary operation. At a first glance I see only a loop.Doley
Whoa! I'm impressed. I believe this does not address the sign and its extension, also not sure if performance is also of consideration - Keikoku did not mention it, so probably not. Otherwise an excellent example of an extensive answer. That's +1.Doley
@EugeneRyabtsev - good catch, added in the sign in my second version.Daughterinlaw
D
3

On the other hand, byte-array-based approach could go like this:

    int extend(uint raw, int bits)
    {
        int sh = 32 - bits;
        int x = (int)raw << sh; // puts your sign bit in the highest bit.
        return x >> sh;  // since x is signed this is an arithmatic signed shift
    }

    int read(byte[] data, int pos, int bits, bool signed)
    {
        int fbi = pos / 8; // first byte index
        int lbi = (pos + bits - 1) / 8; // last byte index
        int cnt = lbi - fbi + 1; // bytes spanned
        if (cnt > 3 || lbi >= data.Length) { throw new ArgumentException(); }

        uint raw = (uint)(
            (data[fbi] << (24 + pos % 8)) + 
            (cnt < 2 ? 0 : data[fbi + 1] << (16 + pos % 8)) + 
            (cnt < 3 ? 0 : data[fbi + 2] << (8 + pos % 8))
            ) >> (32 - bits);
        return signed ? extend(raw, bits) : (int)raw;
    }

Test for this:

    byte[] test = { 0x55, 0xAA, 0x10 };

    string s = "";
    s += read(test, 0, 8, false) + "\r\n";
    s += read(test, 0, 8, true) + "\r\n";
    s += read(test, 8, 8, false) + "\r\n";
    s += read(test, 8, 8, true) + "\r\n";
    s += read(test, 4, 8, false) + "\r\n";
    s += read(test, 7, 9, true) + "\r\n";
    s += read(test, 7, 10, true) + "\r\n";
    s += read(test, 7, 11, true) + "\r\n";
    s += read(test, 7, 12, true) + "\r\n";
    s += read(test, 7, 13, true) + "\r\n";
    s += read(test, 7, 14, true) + "\r\n";
    s += read(test, 7, 15, true) + "\r\n";
    s += read(test, 7, 16, true) + "\r\n";
    s += read(test, 7, 17, true) + "\r\n";
    s += read(test, 18, 2, true) + "\r\n";
    s += read(test, 18, 3, true) + "\r\n";
    s += read(test, 23, 1, true) + "\r\n";
    s += read(test, 23, 2, true) + "\r\n";

The test builds the string like the following:

    85
    85
    170
    -86
    90
    -86
    -172
    -344
    -688
    -1375
    -2750
    -5500
    -11000
    -22000
    1
    2
    0

then throws an exception on the last line.

Doley answered 16/8, 2012 at 9:43 Comment(1)
Interesting system. Missing write capability... and should be generalized to deal with arbitrary length bit strings [so you can easily write functions to serialize more than int's].Daughterinlaw
P
0

I once similarly needed to read and write bit-aligned data in C# - and this is the class I came up with then:

using System;
using System.Text;

namespace bytes
{
    /// <summary>
    /// With the methods of this class you can write/read 1, 2, 4 or 8 byte long signed/unsigned
    /// integers with big/little endianness from a byte array at any offset.
    /// </summary>
    public class Bytes
    {
        /// <summary>
        /// Writes a multi-byte integer in little-endian byte order into a byte array
        /// at the given offset.
        /// </summary>
        /// <param name="value">A multi-byte integer to be written.</param>
        /// <param name="data">Byte array where the value will be written to.</param>
        /// <param name="offset">Offset within the byte array where the value will be written.</param>
        /// <param name="count">Number of bytes to be written.</param>
        public static void PutLE(long value, byte[] data, int offset, int count)
        {
            for (int i = 0; i < count; i++)
            {
                data[offset] = (byte)(value & 0xFF);
                offset++;
                value >>= 8;
            }
        }

        /// <summary>
        /// Reads a multi-byte integer in little-endian byte order from a byte array
        /// at the given offset.
        /// </summary>
        /// <param name="data">Byte array where the value will be read from.</param>
        /// <param name="offset">Offset within the byte array where the value will be read.</param>
        /// <param name="count">Number of bytes to be written.</param>
        /// <param name="signed">Indicates whether the value should preserve the sign.</param>
        /// <returns>
        /// A multi-byte integer read.
        /// </returns>
        public static long GetLE(byte[] data, int offset, int count, bool signed)
        {
            long value = 0;
            for (int i = 0; i < count; i++)
            {
                value = (long)((ulong)value >> 8);
                value |= ((long)data[offset]) << 56;
                offset++;
            }
            if (signed)
            {
                value >>= (8 - count) * 8;
            }
            else
            {
                value = (long)((ulong)value >> ((8 - count) * 8));
            }
            return (value);
        }

        /// <summary>
        /// Writes a multi-byte integer in big-endian byte order into a byte array
        /// at the given offset.
        /// </summary>
        /// <param name="value">A multi-byte integer to be written.</param>
        /// <param name="data">Byte array where the value will be written to.</param>
        /// <param name="offset">Offset within the byte array where the value will be written.</param>
        /// <param name="count">Number of bytes to be written.</param>
        public static void PutBE(long value, byte[] data, int offset, int count)
        {
            offset += count - 1;
            for (int i = 0; i < count; i++)
            {
                data[offset] = (byte)(value & 0xFF);
                offset--;
                value >>= 8;
            }
        }

        /// <summary>
        /// Reads a multi-byte integer in big-endian byte order from a byte array
        /// at the given offset.
        /// </summary>
        /// <param name="data">Byte array where the value will be read from.</param>
        /// <param name="offset">Offset within the byte array where the value will be read.</param>
        /// <param name="count">Number of bytes to be written.</param>
        /// <param name="signed">Indicates whether the value should preserve the sign.</param>
        /// <returns>
        /// A multi-byte integer read.
        /// </returns>
        public static long GetBE(byte[] data, int offset, int count, bool signed)
        {
            offset += count - 1;
            long value = 0;
            for (int i = 0; i < count; i++)
            {
                value = (long)((ulong)value >> 8);
                value |= ((long)data[offset]) << 56;
                offset--;
            }
            if (signed)
            {
                value >>= (8 - count) * 8;
            }
            else
            {
                value = (long)((ulong)value >> ((8 - count) * 8));
            }
            return (value);
        }

        public static string ByteArrayToString(byte[] bytes)
        {
            StringBuilder hex = new StringBuilder(bytes.Length * 2);
            foreach (byte octet in bytes)
            {
                hex.AppendFormat("{0:x2}", octet);
            }
            return hex.ToString();
        }

        public static byte[] StringToByteArray(string hex)
        {
            int length = hex.Length;
            byte[] bytes = new byte[length / 2];
            for (int i = 0; i < length; i += 2)
            {
                bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
            }
            return bytes;
        }

        public static void Main(string[] args)
        {
            if (args.Length < 1)
            {
                System.Console.WriteLine("ERROR - First argument must be one of: 'pl', 'gl', 'pb', 'gb'");
                return;
            }
            if (args.Length < 5)
            {
                System.Console.WriteLine("ERROR - There must be five arguments");
                return;
            }
            switch (args[0])
            {
                case "pl":
                    {
                        long value = Int64.Parse(args[1]);
                        byte[] data = StringToByteArray(args[2]);
                        int offset = Int32.Parse(args[3]);
                        int count = Int32.Parse(args[4]);
                        PutLE(value, data, offset, count);
                        System.Console.WriteLine(ByteArrayToString(data));
                    }
                    break;
                case "gl":
                    {
                        byte[] data = StringToByteArray(args[1]);
                        int offset = Int32.Parse(args[2]);
                        int count = Int32.Parse(args[3]);
                        bool signed = Boolean.Parse(args[4]);
                        long result = GetLE(data, offset, count, signed);
                        System.Console.WriteLine("Hex: {0:X}", result);
                    }
                    break;
                case "pb":
                    {
                        long value = Int64.Parse(args[1]);
                        byte[] data = StringToByteArray(args[2]);
                        int offset = Int32.Parse(args[3]);
                        int count = Int32.Parse(args[4]);
                        PutBE(value, data, offset, count);
                        System.Console.WriteLine(ByteArrayToString(data));
                    }
                    break;
                case "gb":
                    {
                        byte[] data = StringToByteArray(args[1]);
                        int offset = Int32.Parse(args[2]);
                        int count = Int32.Parse(args[3]);
                        bool signed = Boolean.Parse(args[4]);
                        long result = GetLE(data, offset, count, signed);
                        System.Console.WriteLine("Hex: {0:X}", result);
                    }
                    break;
                default:
                    System.Console.WriteLine("ERROR - First argument must be one of: 'pl', 'gl', 'pb', 'gb'");
                    break;
            }
        }
    }
}

Another related class I created provides an approach where the values are byte-aligned but the encoding/decoding structure can be specified:

namespace Explorer
{
    using System;
    using System.Buffers.Binary;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Text;
    using Microsoft.Extensions.Logging;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    /// <summary>
    /// Codec class of the application.
    /// </summary>
    public sealed class Codec
    {
        private readonly Endian endianness;
        private readonly Type[] types;
        private readonly string[] fields;
        private readonly int size;

        /// <summary>
        /// Initializes a new instance of the <see cref="Codec"/> class.
        /// </summary>
        /// <param name="endianness">Endianness.</param>
        /// <param name="types">Field types.</param>
        /// <param name="fields">Field names.</param>
        public Codec(Endian endianness, Type[] types, string[] fields)
        {
            Debug.Assert(types.Length == fields.Length, "Cardinality of types and fields must be the same");
            this.endianness = endianness;
            this.types = types;
            this.fields = fields;
            this.size = this.CalculateSize();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Codec"/> class.
        /// </summary>
        /// <param name="codec">Codec definition in JSON.</param>
        public Codec(JObject codec)
        {
            this.endianness = EndianFromName(codec["endian"].ToString().ToLower());
            this.types = TypesFromNames((JArray)codec["types"]);
            this.fields = codec["fields"].ToObject<List<string>>().ToArray();
            this.size = this.CalculateSize();
        }

        /// <summary>
        /// Endianness.
        /// </summary>
        public enum Endian : int
        {
            /// <summary>
            /// Represents little-endian byte order.
            /// </summary>
            Little,

            /// <summary>
            /// Represents big-endian byte order.
            /// </summary>
            Big,

            /// <summary>
            /// Represents unknown endian byte order.
            /// </summary>
            Unknown,
        }

        /// <summary>
        /// Field type.
        /// </summary>
        public enum Type : int
        {
            /// <summary>
            /// Represents 8-bit unsigned integer.
            /// </summary>
            U8,

            /// <summary>
            /// Represents 8-bit signed integer.
            /// </summary>
            S8,

            /// <summary>
            /// Represents 16-bit unsigned integer.
            /// </summary>
            U16,

            /// <summary>
            /// Represents 16-bit signed integer.
            /// </summary>
            S16,

            /// <summary>
            /// Represents 32-bit unsigned integer.
            /// </summary>
            U32,

            /// <summary>
            /// Represents 32-bit signed integer.
            /// </summary>
            S32,

            /// <summary>
            /// Represents 64-bit unsigned integer.
            /// </summary>
            U64,

            /// <summary>
            /// Represents 64-bit signed integer.
            /// </summary>
            S64,

            /// <summary>
            /// Represents 32-bit float.
            /// </summary>
            F32,

            /// <summary>
            /// Represents 64-bit float.
            /// </summary>
            F64,

            /// <summary>
            /// Represents boolean.
            /// </summary>
            B,

            /// <summary>
            /// Represents an unknown type.
            /// </summary>
            Unknown,
        }

        /// <summary>
        /// Logs hex outcome.
        /// </summary>
        /// <param name="argument">Command argument.</param>
        /// <param name="logger">Logger.</param>
        public static void ConvertToHex(string argument, ILogger logger)
        {
            logger.LogInformation(Hex(Encoding.UTF8.GetBytes(argument)));
        }

        /// <summary>
        /// Logs codec outcome.
        /// </summary>
        /// <param name="argument">Command argument.</param>
        /// <param name="logger">Logger.</param>
        public static void LogCodec(string argument, ILogger logger)
        {
            JObject batch = JObject.Parse(argument);
            Codec codec = new Codec((JObject)batch["codec"]);
            string operation = batch["operation"].ToString().ToLower();

            if ("encode".Equals(operation))
            {
                byte[] octets = new byte[codec.size];
                codec.Encode((JObject)batch["content"], octets);
                logger.LogInformation(Hex(octets));
            }

            if ("decode".Equals(operation))
            {
                byte[] octets = batch["octets"].ToObject<List<byte>>().ToArray();
                JObject content = new JObject();
                codec.Decode(octets, content);
                logger.LogInformation(JsonConvert.SerializeObject(content));
            }
        }

        private static Endian EndianFromName(string endianName)
        {
            if ("little".Equals(endianName))
            {
                return Endian.Little;
            }

            if ("big".Equals(endianName))
            {
                return Endian.Big;
            }

            return Endian.Unknown;
        }

        private static Type[] TypesFromNames(JArray typeNames)
        {
            List<Type> types = new List<Type>();
            foreach (string typeRaw in typeNames)
            {
                string typeName = typeRaw.ToUpper();
                Type type = Type.Unknown;
                if ("U8".Equals(typeName))
                {
                    type = Type.U8;
                }

                if ("S8".Equals(typeName))
                {
                    type = Type.S8;
                }

                if ("U16".Equals(typeName))
                {
                    type = Type.U16;
                }

                if ("S16".Equals(typeName))
                {
                    type = Type.S16;
                }

                if ("U32".Equals(typeName))
                {
                    type = Type.U32;
                }

                if ("S32".Equals(typeName))
                {
                    type = Type.S32;
                }

                if ("U64".Equals(typeName))
                {
                    type = Type.U64;
                }

                if ("S64".Equals(typeName))
                {
                    type = Type.S64;
                }

                if ("F32".Equals(typeName))
                {
                    type = Type.F32;
                }

                if ("F64".Equals(typeName))
                {
                    type = Type.F64;
                }

                if ("B".Equals(typeName))
                {
                    type = Type.B;
                }

                types.Add(type);
            }

            return types.ToArray();
        }

        private static int TypeToSize(Type type)
        {
            switch (type)
            {
                case Type.U8:
                case Type.S8:
                case Type.B:
                    return 1;
                case Type.U16:
                case Type.S16:
                    return 2;
                case Type.U32:
                case Type.S32:
                case Type.F32:
                    return 4;
                case Type.U64:
                case Type.S64:
                case Type.F64:
                    return 8;
                default:
                    return 0;
            }
        }

        private static bool IsCompatible(Type type, JToken token)
        {
            switch (type)
            {
                case Type.U8:
                case Type.S8:
                case Type.U16:
                case Type.S16:
                case Type.U32:
                case Type.S32:
                case Type.U64:
                case Type.S64:
                    return token.Type == JTokenType.Integer;
                case Type.F32:
                case Type.F64:
                    return token.Type == JTokenType.Float;
                case Type.B:
                    return token.Type == JTokenType.Boolean;
                default:
                    return false;
            }
        }

        private static string Hex(Span<byte> octets) => BitConverter.ToString(octets.ToArray()).Replace("-", string.Empty);

        private int CalculateSize()
        {
            int size = 0;
            foreach (Type type in this.types)
            {
                size += TypeToSize(type);
            }

            return size;
        }

        private bool IsCompatible(JObject content)
        {
            for (int i = 0; i < this.types.Length; i++)
            {
                if (!IsCompatible(this.types[i], content[this.fields[i]]))
                {
                    return false;
                }
            }

            return true;
        }

        private void Decode(Span<byte> octets, JObject content)
        {
            switch (this.endianness)
            {
                case Endian.Little:
                    this.DecodeLittle(octets, content);
                    break;
                case Endian.Big:
                    this.DecodeBig(octets, content);
                    break;
            }
        }

        private void DecodeLittle(Span<byte> octets, JObject content)
        {
            for (int i = 0, offset = 0; i < this.types.Length; i++)
            {
                switch (this.types[i])
                {
                    case Type.U8:
                        content[this.fields[i]] = (byte)octets[offset];
                        break;
                    case Type.S8:
                        content[this.fields[i]] = (sbyte)octets[offset];
                        break;
                    case Type.U16:
                        content[this.fields[i]] = BinaryPrimitives.ReadUInt16LittleEndian(octets.Slice(offset));
                        break;
                    case Type.S16:
                        content[this.fields[i]] = BinaryPrimitives.ReadInt16LittleEndian(octets.Slice(offset));
                        break;
                    case Type.U32:
                        content[this.fields[i]] = BinaryPrimitives.ReadUInt32LittleEndian(octets.Slice(offset));
                        break;
                    case Type.S32:
                        content[this.fields[i]] = BinaryPrimitives.ReadInt32LittleEndian(octets.Slice(offset));
                        break;
                    case Type.U64:
                        content[this.fields[i]] = BinaryPrimitives.ReadUInt64LittleEndian(octets.Slice(offset));
                        break;
                    case Type.S64:
                        content[this.fields[i]] = BinaryPrimitives.ReadInt64LittleEndian(octets.Slice(offset));
                        break;
                    case Type.F32:
                        int f32 = BinaryPrimitives.ReadInt32LittleEndian(octets.Slice(offset));
                        if (!BitConverter.IsLittleEndian)
                        {
                            f32 = BinaryPrimitives.ReverseEndianness(f32);
                        }

                        content[this.fields[i]] = BitConverter.Int32BitsToSingle(f32);
                        break;
                    case Type.F64:
                        long f64 = BinaryPrimitives.ReadInt64LittleEndian(octets.Slice(offset));
                        if (!BitConverter.IsLittleEndian)
                        {
                            f64 = BinaryPrimitives.ReverseEndianness(f64);
                        }

                        content[this.fields[i]] = BitConverter.Int64BitsToDouble(f64);
                        break;
                    case Type.B:
                        content[this.fields[i]] = octets[offset] == 1;
                        break;
                }

                offset += TypeToSize(this.types[i]);
            }
        }

        private void DecodeBig(Span<byte> octets, JObject content)
        {
            for (int i = 0, offset = 0; i < this.types.Length; i++)
            {
                switch (this.types[i])
                {
                    case Type.U8:
                        content[this.fields[i]] = (byte)octets[offset];
                        break;
                    case Type.S8:
                        content[this.fields[i]] = (sbyte)octets[offset];
                        break;
                    case Type.U16:
                        content[this.fields[i]] = BinaryPrimitives.ReadUInt16BigEndian(octets.Slice(offset));
                        break;
                    case Type.S16:
                        content[this.fields[i]] = BinaryPrimitives.ReadInt16BigEndian(octets.Slice(offset));
                        break;
                    case Type.U32:
                        content[this.fields[i]] = BinaryPrimitives.ReadUInt32BigEndian(octets.Slice(offset));
                        break;
                    case Type.S32:
                        content[this.fields[i]] = BinaryPrimitives.ReadInt32BigEndian(octets.Slice(offset));
                        break;
                    case Type.U64:
                        content[this.fields[i]] = BinaryPrimitives.ReadUInt64BigEndian(octets.Slice(offset));
                        break;
                    case Type.S64:
                        content[this.fields[i]] = BinaryPrimitives.ReadInt64BigEndian(octets.Slice(offset));
                        break;
                    case Type.F32:
                        int f32 = BinaryPrimitives.ReadInt32BigEndian(octets.Slice(offset));
                        if (BitConverter.IsLittleEndian)
                        {
                            f32 = BinaryPrimitives.ReverseEndianness(f32);
                        }

                        content[this.fields[i]] = BitConverter.Int32BitsToSingle(f32);
                        break;
                    case Type.F64:
                        long f64 = BinaryPrimitives.ReadInt64BigEndian(octets.Slice(offset));
                        if (BitConverter.IsLittleEndian)
                        {
                            f64 = BinaryPrimitives.ReverseEndianness(f64);
                        }

                        content[this.fields[i]] = BitConverter.Int64BitsToDouble(f64);
                        break;
                    case Type.B:
                        content[this.fields[i]] = octets[offset] == 1;
                        break;
                }

                offset += TypeToSize(this.types[i]);
            }
        }

        private void Encode(JObject content, Span<byte> octets)
        {
            switch (this.endianness)
            {
                case Endian.Little:
                    this.EncodeLittle(content, octets);
                    break;
                case Endian.Big:
                    this.EncodeBig(content, octets);
                    break;
            }
        }

        private void EncodeLittle(JObject content, Span<byte> octets)
        {
            for (int i = 0, offset = 0; i < this.types.Length; i++)
            {
                switch (this.types[i])
                {
                    case Type.U8:
                    case Type.S8:
                        octets[offset] = (byte)content[this.fields[i]];
                        break;
                    case Type.U16:
                        BinaryPrimitives.WriteUInt16LittleEndian(octets.Slice(offset), (ushort)content[this.fields[i]]);
                        break;
                    case Type.S16:
                        BinaryPrimitives.WriteInt16LittleEndian(octets.Slice(offset), (short)content[this.fields[i]]);
                        break;
                    case Type.U32:
                        BinaryPrimitives.WriteUInt32LittleEndian(octets.Slice(offset), (uint)content[this.fields[i]]);
                        break;
                    case Type.S32:
                        BinaryPrimitives.WriteInt32LittleEndian(octets.Slice(offset), (int)content[this.fields[i]]);
                        break;
                    case Type.U64:
                        BinaryPrimitives.WriteUInt64LittleEndian(octets.Slice(offset), (ulong)content[this.fields[i]]);
                        break;
                    case Type.S64:
                        BinaryPrimitives.WriteInt64LittleEndian(octets.Slice(offset), (long)content[this.fields[i]]);
                        break;
                    case Type.F32:
                        int f32 = BitConverter.SingleToInt32Bits((float)content[this.fields[i]]);
                        if (!BitConverter.IsLittleEndian)
                        {
                            f32 = BinaryPrimitives.ReverseEndianness(f32);
                        }

                        BinaryPrimitives.WriteInt32LittleEndian(octets.Slice(offset), f32);
                        break;
                    case Type.F64:
                        long f64 = BitConverter.DoubleToInt64Bits((double)content[this.fields[i]]);
                        if (!BitConverter.IsLittleEndian)
                        {
                            f64 = BinaryPrimitives.ReverseEndianness(f64);
                        }

                        BinaryPrimitives.WriteInt64LittleEndian(octets.Slice(offset), f64);
                        break;
                    case Type.B:
                        octets[offset] = ((bool)content[this.fields[i]]) ? (byte)1 : (byte)0;
                        break;
                }

                offset += Codec.TypeToSize(this.types[i]);
            }
        }

        private void EncodeBig(JObject content, Span<byte> octets)
        {
            for (int i = 0, offset = 0; i < this.types.Length; i++)
            {
                switch (this.types[i])
                {
                    case Type.U8:
                    case Type.S8:
                        octets[offset] = (byte)content[this.fields[i]];
                        break;
                    case Type.U16:
                        BinaryPrimitives.WriteUInt16BigEndian(octets.Slice(offset), (ushort)content[this.fields[i]]);
                        break;
                    case Type.S16:
                        BinaryPrimitives.WriteInt16BigEndian(octets.Slice(offset), (short)content[this.fields[i]]);
                        break;
                    case Type.U32:
                        BinaryPrimitives.WriteUInt32BigEndian(octets.Slice(offset), (uint)content[this.fields[i]]);
                        break;
                    case Type.S32:
                        BinaryPrimitives.WriteInt32BigEndian(octets.Slice(offset), (int)content[this.fields[i]]);
                        break;
                    case Type.U64:
                        BinaryPrimitives.WriteUInt64BigEndian(octets.Slice(offset), (ulong)content[this.fields[i]]);
                        break;
                    case Type.S64:
                        BinaryPrimitives.WriteInt64BigEndian(octets.Slice(offset), (long)content[this.fields[i]]);
                        break;
                    case Type.F32:
                        int f32 = BitConverter.SingleToInt32Bits((float)content[this.fields[i]]);
                        if (BitConverter.IsLittleEndian)
                        {
                            f32 = BinaryPrimitives.ReverseEndianness(f32);
                        }

                        BinaryPrimitives.WriteInt32BigEndian(octets.Slice(offset), f32);
                        break;
                    case Type.F64:
                        long f64 = BitConverter.DoubleToInt64Bits((double)content[this.fields[i]]);
                        if (BitConverter.IsLittleEndian)
                        {
                            f64 = BinaryPrimitives.ReverseEndianness(f64);
                        }

                        BinaryPrimitives.WriteInt64BigEndian(octets.Slice(offset), f64);
                        break;
                    case Type.B:
                        octets[offset] = ((bool)content[this.fields[i]]) ? (byte)1 : (byte)0;
                        break;
                }

                offset += TypeToSize(this.types[i]);
            }
        }
    }
}
Panoptic answered 15/2 at 16:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.