Last active
February 1, 2016 10:14
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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