Skip to content

Instantly share code, notes, and snippets.

@MikeFair
Last active February 1, 2016 10:14
Show Gist options
  • Save MikeFair/a3c6bd817ff716ce090f to your computer and use it in GitHub Desktop.
Save MikeFair/a3c6bd817ff716ce090f to your computer and use it in GitHub Desktop.
First cut at a UBJ Integer class. Initially for <LENGTH>, but can easily encodes/decodes any Unsigned Integer in Big or Little Endian by assigning to Value
using System;
using System.IO;
namespace WpfApplication1
{
public class UBJUInt
{
// Core values to track the length
private ulong m_value; // The value as an native endian ulong value for easy use as a value
private byte m_sizeBytes; // The actual number of bytes required from m_bytes for the current value of the UBJUInt
private bool m_isNull = true; // A boolean flag indicating whether or not this value is null
private byte[] m_bytes = new byte[9]; // Local storage for the encoded value
private bool m_isUpdated = false;
private static byte lowTypeCode = (byte)TypeCodes.UQuadWordLittle; // The lowest code for special types (240 or 248)
// Helpers for endianness
public enum EndianTypes { Big, Little, LocalMachine, MatchRemote }
static public EndianTypes SendEndianDefault = EndianTypes.MatchRemote; // Writable static class value to default class instances to
private EndianTypes m_endian = SendEndianDefault; // This stores the actual endian type to send the value in for this class instance
public EndianTypes SendEndian // Boolean Getter / Setter to update m_endian
{
get { return m_endian; }
set { m_isUpdated = m_isUpdated && (m_endian == value); m_endian = value; }
}
/*
Initializes the Value from the provided ulong?, when done, SizeBytes has the correct number of bytes needed to send the Value.
GetBytes() will provide a byte[] that's the right size.
*/
public UBJUInt(ulong? val = null)
: this(val, UBJUInt.SendEndianDefault)
{
}
/*
Initializes the Value from a ulong? and a boolean to set big Endian, SizeBytes has the number of bytes needed to send the Value.
GetBytes() will retrieve a byte[] that's just the right size.
*/
public UBJUInt(ulong? val, EndianTypes sendEndian)
{
SendEndian = sendEndian;
Value = val;
}
/*
Initializes the Value from a byte[], when done, SizeBytes property will have the number of bytes used from the array.
It leaves the source array untouched
*/
public UBJUInt(ref byte[] data)
{
ReadFromByteArray(ref data);
}
/*
Initializes the Value from a BinaryReader, when done, SizeBytes property will have the number of bytes consumed.
It consumes the bytes from the Stream as it Initializes
*/
public UBJUInt(BinaryReader ios)
{
ReadFromBinaryReader(ios);
}
// Value adds for doing enumerated types and easier IntelliSense references
// The valid UBJUInt transmission code values
public enum TypeCodes
{
UByte = 0, Null = 255, N255 = 254
, UWordBig = 253, UWordLittle = 252
, UDoubleWordBig = 249, UDoubleWordLittle = 248
, UQuadWordBig = 245, UQuadWordLittle = 244
//, UArrayBig = 241, UArrayLittle = 240
};
// This is the primary method of setting the value; using ulong? means it can be set to null to mean "not a value"
// The value in m_value is retained while the boolean m_isNull can be switched between true/false
public ulong? Value
{
get { return ((m_isNull) ? (ulong?)null : m_value); }
set
{
m_isNull = (value == null);
// Mark the m_bytes encoding invalid unless it's currently valid and the value isn't null and hasn't changed
// The null check above is required to prevent the m_value <-> (ulong)value test
m_isUpdated = m_isUpdated && (!m_isNull) && (m_value == (ulong)value);
if (!m_isNull) m_value = (ulong)value;
// Keep m_sizeBytes updated to the current byte requirements
// If Value is not a valid value, below the lowest type code, or == 255; m_sizeBytes = 1
if ((m_isNull) || (m_value < lowTypeCode) || (m_value == 255)) { m_sizeBytes = 1; } // Single byte values
else if (m_value <= UInt16.MaxValue) { m_sizeBytes = 3; } // >= 240 and <= 65535
else if (m_value <= UInt32.MaxValue) { m_sizeBytes = 5; } // > 65535 and <= 4294967295
else { m_sizeBytes = 9; } // Larger than a 32 bit int
}
}
// You can either set Value = null/not null; or set this bool value.
public bool IsNull
{
get { return (m_isNull); }
set { m_isNull = value; if (value) { m_sizeBytes = 1; } else { Value = m_value; /* Use the Value Setter to set m_sizeBytes */ } }
}
public int SizeBytes { get { return m_sizeBytes; } }
// FillBytes, fills an existing byte[] with the encoding of the current value and returns the number of bytes written
public int FillBytes(ref byte[] data, int index = 0)
{
UpdateValue();
Array.Copy(m_bytes, 0, data, index, m_sizeBytes);
return m_sizeBytes;
}
// GetBytes returns a new byte[] with the current encoding of the value, honoring the current SendBigEndian flag
public byte[] GetBytes()
{
UpdateValue();
byte[] buf = new byte[m_sizeBytes];
Array.Copy(m_bytes, buf, m_sizeBytes);
return buf;
}
// GetBytes returns a byte[] with the current encoding of the value, honoring the current SendBigEndian flag
public void UpdateValue()
{
// If the value is already updated, then don't recompute it
if (m_isUpdated) return;
// If m_endian is MatchRemote or LocalMachine, then set the Message Endian to the local machine's endian value
EndianTypes msg_endian;
if (m_endian == EndianTypes.MatchRemote || m_endian == EndianTypes.LocalMachine)
{
msg_endian = (BitConverter.IsLittleEndian) ? EndianTypes.Little : EndianTypes.Big;
}
else
{
msg_endian = m_endian;
}
// First handle the single byte results; m_sizeBytes should already be set properly when the value was set
if (m_isNull) // Value is not a valid value, Set TypeCode to TypeCodes.Null
{
m_bytes[0] = (byte)TypeCodes.Null;
}
else if (m_value < lowTypeCode) // value is below the lowest type code; send it directly as a single byte
{
m_bytes[0] = (byte)m_value;
}
else if (m_value == 255) // value is 255; send the special TypeCodes.N255
{
m_bytes[0] = (byte)TypeCodes.N255;
}
else if (m_value <= UInt16.MaxValue) // >= 240 and <= 65535
{
m_bytes[0] = (byte)((msg_endian == EndianTypes.Big) ? TypeCodes.UWordBig : TypeCodes.UWordLittle);
BitConverter.GetBytes(Convert.ToUInt16(m_value)).CopyTo(m_bytes, 1);
}
else if (m_value <= UInt32.MaxValue) // > 65535 and <= 4294967295
{
m_bytes[0] = (byte)((msg_endian == EndianTypes.Big) ? TypeCodes.UDoubleWordBig : TypeCodes.UDoubleWordLittle);
BitConverter.GetBytes(Convert.ToUInt32(m_value)).CopyTo(m_bytes, 1);
}
else //if (m_size > UInt32.MaxValue && m_size <= UInt64.MaxValue) // Larger than a 32 bit int
{
m_bytes[0] = (byte)((msg_endian == EndianTypes.Big) ? TypeCodes.UQuadWordBig : TypeCodes.UQuadWordLittle);
BitConverter.GetBytes(Convert.ToUInt64(m_value)).CopyTo(m_bytes, 1);
}
// The Convert function will have used the native endianness, so we reverse it as necessary
if ((m_sizeBytes > 1) && (BitConverter.IsLittleEndian != (msg_endian == EndianTypes.Little)))
{
Array.Reverse(m_bytes, 1, m_sizeBytes - 1);
}
// Done; m_bytes reflects the encoding for the current value
m_isUpdated = true;
}
// Read in from a BinaryReader; when finished SendBigEndian will match what we received
public int WriteToBinaryWriter(BinaryWriter writer)
{
UpdateValue();
writer.Write(m_bytes, 0, m_sizeBytes);
return m_sizeBytes;
}
// Read in from a BinaryReader; when finished, m_endian is MatchRemote, then m_endian will match the endian format received
public bool ReadFromBinaryReader(BinaryReader reader)
{
// Read in and then set the "Value" property (which will also set m_sizeBytes)
byte b = reader.ReadByte();
TypeCodes t = (b < lowTypeCode) ? TypeCodes.UByte : (TypeCodes)b;
switch (t)
{
case TypeCodes.Null: // Special case handling for null value
Value = null;
return false;
case TypeCodes.N255: // Special case handling for value 255
Value = 255;
return true;
case TypeCodes.UByte: // UByte values use the numeric value itself as their code value as well
Value = b;
return true;
}
// If we got here then it's a multi-byte value
// If the last bit of the type byte received == 1, then the next bytes are big endian encoded
// Set msg_endian based on whether or not the bitwise AND between the typeCode received and 0x01 == 1
EndianTypes msg_endian = (((byte)t & (byte)0x01) == 1) ? EndianTypes.Big : EndianTypes.Little;
byte[] data;
// Initialize the right sized array to receive the next bytes, use msg_endian to detect reversing them if needed, and then assign Value
switch (t)
{
case TypeCodes.UWordBig:
case TypeCodes.UWordLittle:
data = reader.ReadBytes(2);
if (BitConverter.IsLittleEndian != (msg_endian == EndianTypes.Little)) { Array.Reverse(data); }
Value = BitConverter.ToUInt16(data, 0);
break;
case TypeCodes.UDoubleWordBig:
case TypeCodes.UDoubleWordLittle:
data = reader.ReadBytes(4);
if (BitConverter.IsLittleEndian != (msg_endian == EndianTypes.Little)) { Array.Reverse(data); }
Value = BitConverter.ToUInt32(data, 0);
break;
default:
data = reader.ReadBytes(8);
if (BitConverter.IsLittleEndian != (msg_endian == EndianTypes.Little)) { Array.Reverse(data); }
Value = BitConverter.ToUInt64(data, 0);
break;
}
// Store the msg_endian as m_endian if MatchRemote was requested
m_endian = (m_endian == EndianTypes.MatchRemote) ? msg_endian : m_endian;
return true;
}
public bool ReadFromByteArray(ref byte[] data, int index = 0)
{
TypeCodes t = (data[index] < lowTypeCode) ? TypeCodes.UByte : (TypeCodes)data[index];
switch (t)
{
case TypeCodes.Null: // Special case handling for null value
Value = null;
return false;
case TypeCodes.N255: // Special case handling for value 255
Value = 255;
return true;
case TypeCodes.UByte: // SingleByte values share the same as their integer value
Value = data[index];
return true;
}
// If we got here then it's a multi-byte value
// If the last bit of the type byte received == 1, then the next bytes are big endian encoded
// Set msg_endian based on whether or not the bitwise AND between the typeCode received and 0x01 == 1
EndianTypes msg_endian = (((byte)t & (byte)0x01) == 1) ? EndianTypes.Big : EndianTypes.Little;
switch (t)
{
case TypeCodes.UWordBig:
case TypeCodes.UWordLittle:
if (BitConverter.IsLittleEndian != (msg_endian == EndianTypes.Little)) { Array.Reverse(data, index + 1, 2); }
Value = BitConverter.ToUInt16(data, index + 1);
break;
case TypeCodes.UDoubleWordBig:
case TypeCodes.UDoubleWordLittle:
if (BitConverter.IsLittleEndian != (msg_endian == EndianTypes.Little)) { Array.Reverse(data, index + 1, 4); }
Value = BitConverter.ToUInt32(data, index + 1);
break;
default:
if (BitConverter.IsLittleEndian != (msg_endian == EndianTypes.Little)) { Array.Reverse(data, index + 1, 8); }
Value = BitConverter.ToUInt64(data, index + 1);
break;
}
// Store the msg_endian as m_endian if MatchRemote was requested
m_endian = (m_endian == EndianTypes.MatchRemote) ? msg_endian : m_endian;
return true;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment