In order to have a neutral inter-communication between systems all data transfer is encoded using ASN.1 (Abstract Syntax Notation One). For details see the relevent specifications because here, for the purpose of SNMP, a much reduced implementation is used.
Using the BER (Basic Encoding Rules) both the Primative and Constructed encoding with defined length data generates an ANS.1 "frame" with three parts. Identifier Octets + Length Octets + Content Octets. Examples given later.
The Identifier Octets are a tag indicating the type of data in the Contents Octets: Universal, Application, Context-Specific, Private.
The Length Octets give the length of the Contents in one of two forms. Short Form: for data lengths 0 - 127 bytes and Long Form: for lengths > 127 bytes.
Here are a number of conversions from a data type to an ANS.1 "byte array."
// Create a snmp integer
public Byte[] SNMPInteger(long Val)
{
System.Collections.ArrayList B = new System.Collections.ArrayList();
do
{
B.Insert(0, (byte)(Val & 0xFF));
Val >>= 8;
} while (Val != 0);
AddSNMPLength(ref B, B.Count);
byte[] Result = new byte[1 + B.Count];
int Pos = 0;
Result[Pos++] = (byte)SnmpType.Integer;
for (int i = 0; i < B.Count; i++) Result[Pos++] = (byte)B[i];
return Result;
}
// Create a snmp octet string
public byte[] SNMPString(string Val)
{
System.Collections.ArrayList B = new System.Collections.ArrayList();
AddSNMPLength(ref B, Val.Length);
byte[] Result = new byte[1 + B.Count + Val.Length];
int Pos = 0;
Result[Pos++] = (byte)SnmpType.OctetString;
for (int i = 0; i < B.Count; i++) Result[Pos++] = (byte)B[i];
System.Buffer.BlockCopy(System.Text.Encoding.ASCII.GetBytes(Val), 0, Result, Pos, Val.Length);
return Result;
}
// Create a snmp null
private byte[] SNMPNull()
{
byte[] Result = new byte[2];
Result[0] = (byte)SnmpType.Null;
Result[1] = 0x00; // length is zero
return Result;
}
// Create a snmp OID
private Byte[] SNMPObjectID(String Val)
{
if (Val.StartsWith("1.3")) Val = "43" + Val.Substring(3);
System.Collections.ArrayList B = new System.Collections.ArrayList();
String[] A = Val.Split('.');
Int32 Len = 0;
for (int i = 0; i < A.Length; i++)
{
Int32 V = System.Convert.ToInt32(A[i]);
B.Insert(Len, (byte)(V & 0x7F));
V >>= 7;
while (V != 0)
{
B.Insert(Len, (byte)(V & 0x7F | 0x80));
V >>= 7;
}
Len = B.Count;
}
AddSNMPLength(ref B, B.Count);
Byte[] Result = new Byte[1 + B.Count];
Int32 Pos = 0;
Result[Pos++] = (Byte)SnmpType.ObjectIdentifier;
for (Int32 i = 0; i < B.Count; i++) Result[Pos++] = (Byte)B[i];
return Result;
}
// Create a snmp sequence header
private byte[] SNMPSequence(long Tag, int Len)
{
System.Collections.ArrayList B = new System.Collections.ArrayList();
AddSNMPLength(ref B, Len);
byte[] Result = new byte[1 + B.Count];
int Pos = 0;
Result[Pos++] = (byte)Tag;
for (int i = 0; i < B.Count; i++) Result[Pos++] = (byte)B[i];
return Result;
}
// Add variable length in front of byte list
private void AddSNMPLength(ref System.Collections.ArrayList B, int Len)
{
int Pos = 0;
Boolean bExcess = false;
// We assume 0x00 to 0xFE. However when length > 0x7F 2 bytes are required
// It also seem 0xFF is not permitted
if ((Len > 0x7F) && (Len < 0xFF)) bExcess = true;
B.Insert(Pos++, (byte)(Len & 0xFF));
Len >>= 8;
while (Len != 0)
{
B.Insert(Pos++, (byte)(Len & 0xFF));
Len >>= 8;
}
if ((Pos > 1) || (bExcess)) B.Insert(0, (byte)(B.Count | 0x80));
}
The TAG codes.
public enum SnmpType
{
Boolean = 0x01,
Integer = 0x02,
BitString = 0x03,
OctetString = 0x04,
Null = 0x05,
ObjectIdentifier = 0x06,
Sequence = 0x30,
IPAddress = 0x40,
Counter32 = 0x41,
Gauge = 0x42,
TimeTicks = 0x43,
Opaque = 0x44,
NetAddress = 0x45,
Counter64 = 0x46,
UInt32 = 0x47,
GetRequestPDU = 0xa0,
GetNextRequestPDU = 0xa1,
GetResponsePDU = 0xa2,
SetRequestPDU = 0xa3,
TrapPDUv1 = 0xa4,
GetBulkRequest = 0xa5,
InformRequest = 0xa6,
TrapPDUv2 = 0xa7,
VersionV1 = 0x00,
VersionV2c = 0x01
}
Here are a number of conversions from a data type to an ANS.1 "byte array."
// Get snmp integer
private Int32 GetSNMPInteger(ref Byte[] Packet, ref Int32 Pos)
{
if (Packet[Pos++] != (byte)SnmpType.Integer) throw new System.Exception("ASN.1 Integer expected.");
Int32 Len = GetSNMPLength(ref Packet, ref Pos);
Int32 Val = 0;
for (Int16 i = 0; i < Len; i++) Val = (Val << 8) + Packet[Pos++];
return Val;
}
// Get snmp variable as integer (used for booleans, integers of all sizes, counters, etc.)
private Int32 GetSNMPInteger(ref Byte[] Packet, ref Int32 Pos, Int32 Tag)
{
if (Packet[Pos++] != Tag) throw new System.Exception("ASN.1 Integer " + System.String.Format("0x{0:x}", Tag) + " expected.");
Int32 Len = GetSNMPLength(ref Packet, ref Pos);
Int32 Val = 0;
for (Int16 i = 0; i < Len; i++) Val = (Val << 8) + Packet[Pos++];
return Val;
}
// get snmp null
private string GetSNMPNull(ref byte[] Packet, ref int Pos)
{
if (Packet[Pos++] != (byte)SnmpType.Null) throw new System.Exception("ASN.1 Null expected.");
Pos++;
return ";
}
// get snmp OID
private string GetSNMPObjectID(ref byte[] Packet, ref int Pos)
{
if (Packet[Pos++] != (byte)SnmpType.ObjectIdentifier) throw new System.Exception("ASN.1 ObjectID expected.");
int Len = GetSNMPLength(ref Packet, ref Pos);
string Result = ";
for (int i = Pos; i < Pos + Len; i++)
{
int Val = 0;
int V;
do
{
V = Packet[i];
Val = (Val << 7) + (V & 0x7F);
if (V > 127) i++; else break;
} while (true);
if (Result != ") Result += ".";
Result += Val;
}
if (Result.StartsWith("43")) Result = "1.3" + Result.Substring(2);
Pos += Len;
return Result;
}
// get snmp sequence length (used for Sequence, GetResponsePDU, etc)
private int GetSNMPSequence(ref byte[] Packet, ref int Pos, int Tag)
{
if (Packet[Pos++] != (byte)Tag)
{
throw new System.Exception("ASN.1 " + System.String.Format("0x{0:x}", Tag) + " expected.");
}
return GetSNMPLength(ref Packet, ref Pos);
}
// get snmp ipaddress
private string GetSNMPIPAddress(ref byte[] Packet, ref int Pos)
{
if (Packet[Pos++] != (byte)SnmpType.IPAddress) throw new System.Exception("ASN.1 IPAddress expected.");
int Len = GetSNMPLength(ref Packet, ref Pos);
string Result = ";
for (int i = 0; i < Len; i++)
{
if (Result != ") Result += ".";
Result += Packet[Pos++];
}
return Result;
}
// get binary snmp data (used for unknown data types)
private string GetSNMPBinary(ref byte[] Packet, ref int Pos)
{
Pos++;
int Len = GetSNMPLength(ref Packet, ref Pos);
string Result = System.BitConverter.ToString(Packet, Pos, Len);
Pos += Len;
return Result;
}
// get snmp octet string (detect dates and numeric mac addresses by function encapsulation)
private string GetSNMPString(ref byte[] Packet, ref int Pos)
{
if (Packet[Pos++] != (byte)SnmpType.OctetString) throw new System.Exception("ASN.1 String expected.");
int Len = GetSNMPLength(ref Packet, ref Pos);
String Result = ";
if (Result == ")
{
// Problem in copy data raw since char can be > 0x7F
Byte[] a = new Byte[Len];
Boolean bNonASCII = false;
for (Int16 ia = 0; ia < Len; ia++)
{
a[ia] = Packet[ia + Pos];
if (a[ia] > 0x7F) bNonASCII = true;
}
if (bNonASCII == false) Result = System.Text.Encoding.ASCII.GetString(Packet, Pos, Len);
else for (Int16 ib = 0; ib < Len; ib++) Result += (char)a[ib];
}
Pos += Len;
return Result;
}
The helpers.
// Get snmp variable length
private Int32 GetSNMPLength(ref Byte[] Packet, ref Int32 Pos)
{
int Len = Packet[Pos++];
if (Len > 128)
{
int L = Len & 0x7F;
Len = 0;
for (int i = 0; i < L; i++) Len = (Len << 8) + Packet[Pos++];
}
return Len;
}
// Get a packet type without advance of position or not
private Byte GetSNMPType(ref Byte[] Packet, ref Int32 Pos, Boolean bAdvance)
{
Byte Val = Packet[Pos];
if (bAdvance) Pos++;
return Val;
}