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; }