SNMP level ASN.1 encoding

The need to encode and decode ASN.1

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.

The ASN.1 Convention

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.

The Conversions to ANS.1

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
    }

Likewise the Conversions from ANS.1

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