Source/Tx.Network/Snmp/Asn1DecoderExtensions.cs (250 lines of code) (raw):
namespace Tx.Network.Snmp
{
using System;
using System.Linq;
using System.Text;
/// <summary>
/// Internal class that provides extension methods for Asn1 decoder
/// </summary>
internal static class Asn1DecoderExtensions
{
/// <summary>
/// Decodes the type of to class construct.
/// </summary>
/// <param name="byteToDecode">The byte to decode.</param>
/// <returns></returns>
public static Asn1TagInfo DecodeToClassConstructType(this byte byteToDecode)
{
return new Asn1TagInfo((byteToDecode & 0xC0) >> 6, (byteToDecode & 0x20) >> 5, byteToDecode & 0x1F);
}
/// <summary>
/// Reads the unsigned integer.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="offset">The offset.</param>
/// <param name="length">The length.</param>
/// <returns>uint</returns>
public static uint ReadUnsignedInteger(this byte[] bytes, int offset, int length)
{
uint value = 0;
int endOfContentIndex = offset + length;
for (int i = offset; i < endOfContentIndex; i++)
{
value = (value << 8) | bytes[i];
}
return value;
}
/// <summary>
/// Reads the integer.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="offset">The offset.</param>
/// <param name="length">The length.</param>
/// <returns>int</returns>
public static int ReadInteger(this byte[] bytes, int offset, int length)
{
int value = 0;
int endOfContentIndex = offset + length;
for (int i = offset; i < endOfContentIndex; i++)
{
value = (value << 8) | bytes[i];
}
return value;
}
/// <summary>
/// Reads the long integer.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="offset">The offset.</param>
/// <param name="length">The length.</param>
/// <returns>long</returns>
public static long ReadLongInteger(this byte[] bytes, int offset, int length)
{
long value = 0;
int endOfContentIndex = offset + length;
for (int i = offset; i < endOfContentIndex; i++)
{
value = (value << 8) | bytes[i];
}
return value;
}
/// <summary>
/// Reads the unsigned long integer.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="offset">The offset.</param>
/// <param name="length">The length.</param>
/// <returns>ulong</returns>
private static ulong ReadUnsignedLong(byte[] bytes, int offset, int length)
{
ulong value = 0;
int endOfContentIndex = offset + length;
for (int i = offset; i < endOfContentIndex; i++)
{
value = (value << 8) | bytes[i];
}
return value;
}
/// <summary>
/// Reads the octet string.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="offset">The offset.</param>
/// <param name="length">The length.</param>
/// <returns>string</returns>
internal static string ReadOctetString(this byte[] bytes, int offset, int length)
{
var stringValue = Encoding.UTF8.GetString(bytes, offset, length);
// If there are non-printable char, it has hex value.
if (stringValue.Any(char.IsControl))
{
// hex value
stringValue = BitConverter.ToString(bytes, offset, length);
}
return stringValue;
}
/// <summary>
/// Reads the ip address.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="startOffset">The start offset.</param>
/// <returns>IPAddress</returns>
private static System.Net.IPAddress ReadIPAddress(byte[] bytes, int startOffset)
{
return new System.Net.IPAddress(new byte[]{bytes[startOffset], bytes[startOffset +1], bytes[startOffset+2],bytes[startOffset+3]});
}
/// <summary>
/// Reads the variable binds.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="offset">The offset.</param>
/// <param name="bytesLength">Length of the bytes.</param>
/// <returns>
/// VarBinds
/// </returns>
/// <exception cref="System.DataMisalignedException">Bad Data/Mulformated Asn1/Snmp data</exception>
/// <exception cref="InvalidOperationException">Malformed datagram/Out of sequemce data</exception>
public static VarBind[] ReadVarBinds(this byte[] bytes, int offset, int bytesLength)
{
VarBind[] values = new VarBind[100];
int varbindCount = 0;
bytesLength += offset;
while (offset < bytesLength)
{
Asn1TagInfo cct = bytes[offset++].DecodeToClassConstructType();
int length;
offset = ReadLength(bytes, offset, out length);
if (ConstructType.Primitive != cct.Asn1ConstructType)
{
continue;
}
if (cct.Asn1TagType == Asn1Tag.NotAsn1Data || cct.Asn1TagType != Asn1Tag.ObjectIdentifier)
{
throw new DataMisalignedException("Bad Data/Mulformated Asn1/Snmp data");
}
uint[] key = ReadOids(bytes, offset, length);
offset += length;
// Get [TLV] type, length, value
cct = bytes[offset++].DecodeToClassConstructType();
offset = ReadLength(bytes, offset, out length);
object value;
offset = GetVarBindValue(bytes, offset, length, cct, out value);
//Make varbind
MakeVarBinds(ref values, key, value, cct, varbindCount++);
}
if (varbindCount != values.Length)
{
Array.Resize(ref values, varbindCount);
}
return values;
}
/// <summary>
/// Gets the variable bind value.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="offset">The offset.</param>
/// <param name="length">The length.</param>
/// <param name="tagInfo">The tag information.</param>
/// <param name="value">The value.</param>
/// <returns>offset int</returns>
private static int GetVarBindValue(byte[] bytes, int offset, int length, Asn1TagInfo tagInfo, out object value)
{
if (tagInfo.Asn1TagType == Asn1Tag.NotAsn1Data)
{
switch (tagInfo.Asn1SnmpTagType)
{
case Asn1SnmpTag.IpAddress:
{
value = ReadIPAddress(bytes, offset);
break;
}
case Asn1SnmpTag.Counter:
{
value = ReadUnsignedInteger(bytes, offset, length);
break;
}
case Asn1SnmpTag.Counter64:
{
value = ReadUnsignedLong(bytes, offset, length);
break;
}
case Asn1SnmpTag.Gauge:
{
value = ReadUnsignedInteger(bytes, offset, length);
break;
}
case Asn1SnmpTag.UInt32:
{
value = ReadUnsignedInteger(bytes, offset, length);
break;
}
case Asn1SnmpTag.TimeTicks:
{
value = ReadUnsignedInteger(bytes, offset, length);
break;
}
default:
{
value = "NYI: " + tagInfo.Asn1SnmpTagType.ToString();
break;
}
}
}
else
{
switch (tagInfo.Asn1TagType)
{
case Asn1Tag.Null:
{
value = null;
break;
}
case Asn1Tag.Integer:
{
value = ReadLongInteger(bytes, offset, length);
break;
}
case Asn1Tag.OctetString:
{
value = ReadOctetString(bytes, offset, length);
break;
}
case Asn1Tag.ObjectIdentifier:
{
value = new ObjectIdentifier(bytes.ReadOids(offset, length));
break;
}
default:
{
value = "NYI: " + tagInfo.Asn1TagType.ToString();
break;
}
}
}
return offset + length;
}
/// <summary>
/// Makes the variable binds.
/// </summary>
/// <param name="varBinds">Varbind value.</param>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <param name="tag">The asn1 tag.</param>
/// <param name="varbindCount">The varbind count.</param>
private static void MakeVarBinds(ref VarBind[] varBinds, uint[] key, object value, Asn1TagInfo tag, int varbindCount)
{
int length = varBinds.Length;
if (varbindCount >= length)
{
Array.Resize(ref varBinds, length + 100);
}
varBinds[varbindCount] = new VarBind(new ObjectIdentifier(key), value, tag);
}
/// <summary>
/// Reads the oids.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="offset">The offset.</param>
/// <param name="length">The length.</param>
/// <returns>uint array</returns>
public static uint[] ReadOids(this byte[] bytes, int offset, int length)
{
uint subId;
uint[] oids = new uint[length];
int count = 2;
int endOfContentIndex = offset + length - 1;
offset = DecodeSubID(bytes, offset, out subId);
if (subId < 40)
{
oids[0] = 0;
oids[1] = subId;
}
else if (subId < 80)
{
oids[0] = 1;
oids[1] = (subId - 40);
}
else
{
oids[0] = 2;
oids[1] = (subId - 80);
}
while (offset <= endOfContentIndex)
{
offset = DecodeSubID(bytes, offset, out subId);
if (length > count)
{
oids[count++] = subId;
}
else
{
Array.Resize(ref oids, count + 1);
oids[count++] = subId;
}
}
if (oids.Length != count)
{
Array.Resize(ref oids, count);
}
return oids;
}
/// <summary>
/// Decodes the sub identifier.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <param name="subID">The sub identifier.</param>
/// <returns></returns>
private static int DecodeSubID(byte[] data, int offset, out uint subID)
{
subID = 0;
byte b;
do
{
b = data[offset++];
subID <<= 7;
subID |= (uint)(b & (byte)0x7F);
} while ((b & 0x80) == 0x80);
return offset;
}
/// <summary>
/// Reads the length.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <param name="value">The value.</param>
/// <returns></returns>
/// <exception cref="NotSupportedException">Indefinite lengths are not implemented.</exception>
internal static int ReadLength(this byte[] data, int offset, out int value)
{
int length = data[offset++];
if (length == 0x80)
throw new NotSupportedException("Indefinite lengths are not implemented.");
if (length <= 127)
{
value = length;
return offset;
}
int numLengthBytes = (length & 0x7F);
value = ReadInteger(data, offset, numLengthBytes);
return offset + numLengthBytes;
}
}
}