Source/Tx.Network/Snmp/Asn1EncoderExtensions.cs (327 lines of code) (raw):
namespace Tx.Network.Snmp
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Net;
using System.Text;
/// <summary>
/// Internal class which provides Asn1 Encoding extension methods
/// </summary>
internal static class Asn1EncoderExtensions
{
private const uint uintMask = 0xFF800000u;
private const ulong ulongMask = 0xFF80000000000000;
private const int intShiftSize = 8 * (sizeof(int) - 1);
private const int uintShiftSize = 8 * (sizeof(uint) - 1);
private const int ulongShiftSize = 8 * (sizeof(ulong) - 1);
/// <summary>
/// Encodes the type of the class construct.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <param name="asn1Class">The asn1 class.</param>
/// <param name="constructType">Type of the construct.</param>
/// <param name="tag">The tag.</param>
/// <returns>new offset value in int</returns>
public static int EncodeClassConstructType(this byte[] data, int offset, Asn1Class asn1Class, ConstructType constructType, byte tag)
{
data[offset] = (byte)(((byte)asn1Class << 6) | ((byte)constructType << 5) | tag);
return offset + 1;
}
/// <summary>
/// Gets the length of the integer for encoding.
/// </summary>
/// <param name="value">The integer value whose length need to be found.</param>
/// <returns>int</returns>
public static int GetIntegerLength(this int value)
{
uint mask = 0xFF800000u;
int size = sizeof(int);
while (((value & mask) == 0 || (value & mask) == mask) && size > 1)
{
size--;
value <<= 8;
}
return size;
}
/// <summary>
/// Encodes the length.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <param name="lengthIntegerToEncode">The length integer to encode.</param>
/// <returns>encoded length</returns>
public static int EncodeLength(this byte[] data, int offset, int lengthIntegerToEncode)
{
if (lengthIntegerToEncode <= 127)
{
data[offset] = (byte)lengthIntegerToEncode;
return offset + 1;
}
int lengthOfLength = 0;
uint mask = 0xFF000000;
bool foundNonZero = false;
for (int i = 1; i <= sizeof(int); i++)
{
if (foundNonZero || (mask & lengthIntegerToEncode) != 0)
{
lengthOfLength++;
data[offset + lengthOfLength] = (byte)((lengthIntegerToEncode & mask) >> ((sizeof(int) - i) * 8));
foundNonZero = true;
}
mask >>= 8;
}
data[offset] = (byte)(0x80 + lengthOfLength);
return offset + lengthOfLength + 1;
}
/// <summary>
/// Encodes the ip address.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="offset">The offset.</param>
/// <param name="ipAddress">The ip address.</param>
/// <returns>offset as int</returns>
private static int EncodeIPAddress(this byte[] bytes, int offset, System.Net.IPAddress ipAddress)
{
offset = bytes.EncodeClassConstructType(offset, Asn1Class.Application, ConstructType.Primitive, (byte)Asn1SnmpTag.IpAddress);
offset = bytes.EncodeLength(offset, 4);
byte[] bytesEncoded = ipAddress.GetAddressBytes();
Array.Copy(bytesEncoded, 0, bytes, offset, bytesEncoded.Length);
return offset + 4;
}
/// <summary>
/// Gets the length of the string.
/// </summary>
/// <param name="octetString">The octet string.</param>
/// <returns></returns>
private static int GetStringLength(string octetString)
{
if (octetString == null)
{
return 0;
}
// does not support long string
if (octetString.Length > 75)
{
return 75;
}
return octetString.Length;
}
/// <summary>
/// Encodes the null.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <returns>offset as int</returns>
public static int EncodeNull(this byte[] data, int offset)
{
offset = data.EncodeClassConstructType(offset, Asn1Class.Universal, ConstructType.Primitive, (byte)Asn1Tag.Null);
return data.EncodeLength(offset, 0);
}
/// <summary>
/// Encodes the variable binds.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <param name="varBinds">The variable binds.</param>
/// <returns>int offset</returns>
public static int EncodeVarBinds(this byte[] data, int offset, IReadOnlyCollection<VarBind> varBinds)
{
int length = offset;
foreach (var item in varBinds)
{
offset = data.EncodeClassConstructType(offset, Asn1Class.Universal, ConstructType.Constructed, (byte)Asn1Tag.Sequence);
int tempOffset = data.EncodeOid(offset + 1, item.Oid.Oids);
if (item.Asn1TypeInfo.Asn1SnmpTagType == Asn1SnmpTag.NotSnmpData)
{
switch (item.Asn1TypeInfo.Asn1TagType)
{
case Asn1Tag.Integer:
{
tempOffset = data.EncodeLongInteger(tempOffset, Convert.ToInt64(item.Value));
break;
}
case Asn1Tag.Null:
{
tempOffset = data.EncodeNull(tempOffset);
break;
}
case Asn1Tag.OctetString:
{
tempOffset = data.EncodeOctetString(tempOffset, (string)item.Value);
break;
}
case Asn1Tag.ObjectIdentifier:
{
tempOffset = data.EncodeOid(tempOffset, ((ObjectIdentifier)item.Value).Oids);
break;
}
default:
{
tempOffset = data.EncodeNull(tempOffset);
break;
}
}
}
else
{
switch (item.Asn1TypeInfo.Asn1SnmpTagType)
{
case Asn1SnmpTag.Counter:
{
tempOffset = data.EncodeUnsignedInteger(tempOffset, (uint)item.Value, (byte)Asn1SnmpTag.Counter);
break;
}
case Asn1SnmpTag.IpAddress:
{
tempOffset = data.EncodeIPAddress(tempOffset, (System.Net.IPAddress)item.Value);
break;
}
case Asn1SnmpTag.Counter64:
{
tempOffset = data.EncodeUnsignedLong(tempOffset, (ulong)item.Value, (byte)Asn1SnmpTag.Counter64);
break;
}
case Asn1SnmpTag.Gauge:
{
tempOffset = data.EncodeUnsignedInteger(tempOffset, (uint)item.Value, (byte)Asn1SnmpTag.Gauge);
break;
}
case Asn1SnmpTag.TimeTicks:
{
tempOffset = data.EncodeUnsignedInteger(tempOffset, (uint)item.Value, (byte)Asn1SnmpTag.TimeTicks);
break;
}
case Asn1SnmpTag.UInt32:
{
tempOffset = data.EncodeUnsignedInteger(tempOffset, (uint)item.Value, (byte)Asn1SnmpTag.UInt32);
break;
}
default:
{
tempOffset = data.EncodeNull(tempOffset);
break;
}
}
}
data.EncodeLength(offset, tempOffset - offset);
offset = tempOffset;
}
return offset - length;
}
/// <summary>
/// Encodes the integer.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <param name="value">The value.</param>
/// <returns>offset as int</returns>
public static int EncodeInteger(this byte[] data, int offset, int value)
{
offset = data.EncodeClassConstructType(offset, Asn1Class.Universal, ConstructType.Primitive, (byte)Asn1Tag.Integer);
int size = sizeof(int);
while (((value & uintMask) == 0 || (value & uintMask) == uintMask) && size > 1)
{
size--;
value <<= 8;
}
offset = data.EncodeLength(offset, size);
while (size-- > 0)
{
data[offset++] = (byte)((value & uintMask) >> intShiftSize);
value <<= 8;
}
return offset;
}
/// <summary>
/// Encodes the long integer.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <param name="value">The value.</param>
/// <returns>int offset</returns>
public static int EncodeLongInteger(this byte[] data, int offset, long value)
{
byte[] buffer = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(value));
offset = data.EncodeClassConstructType(offset, Asn1Class.Universal, ConstructType.Primitive, (byte)Asn1Tag.Integer);
offset = data.EncodeLength(offset, buffer.Length);
for (int i = 0; i < buffer.Length; i++)
{
data[offset++] = buffer[i];
}
return offset;
}
/// <summary>
/// Encodes the unsigned integer.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <param name="value">The value.</param>
/// <param name="tag">The tag.</param>
/// <returns>int offset</returns>
private static int EncodeUnsignedInteger(this byte[] data, int offset, uint value, byte tag)
{
offset = data.EncodeClassConstructType(offset, Asn1Class.Application, ConstructType.Primitive, tag);
int size = sizeof(uint);
while (((value & uintMask) == 0 || (value & uintMask) == uintMask) && size > 1)
{
size--;
value <<= 8;
}
offset = data.EncodeLength(offset, size);
while (size-- > 0)
{
data[offset++] = (byte)((value & uintMask) >> uintShiftSize);
value <<= 8;
}
return offset;
}
/// <summary>
/// Encodes the unsigned long.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <param name="value">The value.</param>
/// <param name="tag">The tag.</param>
/// <returns>int offset</returns>
private static int EncodeUnsignedLong(this byte[] data, int offset, ulong value, byte tag)
{
offset = data.EncodeClassConstructType(offset, Asn1Class.Application, ConstructType.Primitive, tag);
int size = sizeof(ulong);
while (((value & ulongMask) == 0 || (value & ulongMask) == ulongMask) && size > 1)
{
size--;
value <<= 8;
}
offset = data.EncodeLength(offset, size);
while (size-- > 0)
{
data[offset++] = (byte)((value & ulongMask) >> ulongShiftSize);
value <<= 8;
}
return offset;
}
/// <summary>
/// Sizes the of sub identifier.
/// </summary>
/// <param name="subid">The subid.</param>
/// <returns>int size of SubID</returns>
private static int SizeOfSubID(uint subid)
{
if (subid < 0x80u)
{
return 1;
}
else if (subid < 0x4000u)
{
return 2;
}
else if (subid < 0x200000u)
{
return 3;
}
else if (subid < 0x10000000u)
{
return 4;
}
else
{
return 5;
}
}
/// <summary>
/// Gets the length of the oid.
/// </summary>
/// <param name="oid">The oid.</param>
/// <returns>int length</returns>
private static int GetOidLength(ReadOnlyCollection<uint> oid)
{
if (oid.Count <= 0)
{
return 0;
}
if (oid.Count == 1)
{
return 1;
}
uint subId = (oid[0] * 40) + oid[1];
int size = SizeOfSubID(subId);
for (int i = 2; i < oid.Count; i++)
{
size += SizeOfSubID(oid[i]);
}
return size;
}
/// <summary>
/// Encodes the octet string.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <param name="octetString">The octet string.</param>
/// <returns>int offset</returns>
public static int EncodeOctetString(this byte[] data, int offset, string octetString)
{
int len = GetStringLength(octetString);
offset = data.EncodeClassConstructType(offset, Asn1Class.Universal, ConstructType.Primitive, (byte)Asn1Tag.OctetString);
offset = data.EncodeLength(offset, len);
for (int i = 0; i < len; i++)
{
data[offset++] = (byte)octetString[i];
}
return offset;
}
/// <summary>
/// Encodes the oid.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <param name="subOids">The sub oids.</param>
/// <returns>int offset</returns>
/// <exception cref="System.ArgumentNullException">data</exception>
private static int EncodeOid(this byte[] data, int offset, ReadOnlyCollection<uint> subOids)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
offset = data.EncodeClassConstructType(offset, Asn1Class.Universal, ConstructType.Primitive, (byte)Asn1Tag.ObjectIdentifier);
offset = data.EncodeLength(offset, GetOidLength(subOids));
offset = EncodeSubID((subOids[0] * 40) + subOids[1], data, offset);
for (int i = 2; i < subOids.Count; i++)
{
offset = EncodeSubID(subOids[i], data, offset);
}
return offset;
}
/// <summary>
/// Encodes the sub identifier.
/// </summary>
/// <param name="subid">The subid.</param>
/// <param name="data">The data.</param>
/// <param name="offset">The offset.</param>
/// <returns>int offset</returns>
private static int EncodeSubID(uint subid, byte[] data, int offset)
{
if (subid < 0x80u)
{ // Size = 1
data[offset++] = (byte)subid;
}
else if (subid < 0x4000u)
{ // Size = 2
data[offset++] = (byte)((subid >> 7) | 0x80);
data[offset++] = (byte)(subid & 0x7F);
}
else if (subid < 0x200000u)
{ // Size = 3
data[offset++] = (byte)((subid >> 14) | 0x80);
data[offset++] = (byte)(((subid >> 7) & 0x7F) | 0x80);
data[offset++] = (byte)(subid & 0x7F);
}
else if (subid < 0x10000000u)
{ // Size = 4
data[offset++] = (byte)((subid >> 21) | 0x80);
data[offset++] = (byte)(((subid >> 14) & 0x7F) | 0x80);
data[offset++] = (byte)(((subid >> 7) & 0x7F) | 0x80);
data[offset++] = (byte)(subid & 0x7F);
}
else
{ // Size = 5
data[offset++] = (byte)((subid >> 28) | 0x80);
data[offset++] = (byte)(((subid >> 21) & 0x7F) | 0x80);
data[offset++] = (byte)(((subid >> 14) & 0x7F) | 0x80);
data[offset++] = (byte)(((subid >> 7) & 0x7F) | 0x80);
data[offset++] = (byte)(subid & 0x7F);
}
return offset;
}
}
}