Reading BER-encoded data

Posted on

Problem

I’m trying to read some SNMP packets on my own. So far so good, I was able to put up some functions to write some SNMP TRAP packets as I need them. However, now I need to be able to read them and while my code works, I’m not happy with it.

I’d like to see if any of you could please tell me if there’s a better way to do and/or optimize my current code. My binary-fu isn’t as good so I’d love if I could get some proposals without bit shifting as that confuses me and could confuse other people I program with.

I’ve heard and seen SNMP libraries for .NET but it’s overkill for what I need to do. Also, I’m not allowed to use external libraries, and so far the code works, I just want to simplify it more and optimize it too, as it’ll be used very extensively under high network traffic.

This is my (sample) code:

private void ReadSNMP(byte[] buf, int size)
{
    if (size > 0)
    {
        // IS UNIVERSAL SEQUENCE?
        if (buf[0] == 0x30)
        {
            int iPos = 0;
            int pckSize = ReadSNMPInt(buf, ref iPos);
            int version = ReadSNMPInt(buf, ref iPos);
            String comm = ReadSNMPString(buf, ref iPos);
        }
    }
}

And this is a sample ReadSNMPInt function:

private int ReadSNMPInt(byte[] buf, ref int iPos)
{
    MemoryStream ms = new MemoryStream();

    iPos++;
    if (buf[iPos] > 0x80) // > 80 = BER :(
    {
        if (buf[iPos] == 0x81)
            ms.Write(new byte[] { buf[iPos + 1], 0x00, 0x00, 0x00 }, 0, 4);
        if (buf[iPos] == 0x82)
            ms.Write(new byte[] { buf[iPos + 2], buf[iPos + 1], 0x00, 0x00 }, 0, 4);
        if (buf[iPos] == 0x83)
            ms.Write(new byte[] { buf[iPos + 3], buf[iPos + 2], buf[iPos + 1], 0x00 }, 0, 4);
        if (buf[iPos] == 0x84)
            ms.Write(new byte[] { buf[iPos + 4], buf[iPos + 3], buf[iPos + 2], buf[iPos + 1] }, 0, 4);

        iPos += buf[iPos] - 0x80;
    }
    else
    {
        iPos++;
        ms.Write(new byte[] { buf[iPos], 0x00, 0x00, 0x00 }, 0, 4);
    }

    iPos++;
    return BitConverter.ToInt32(ms.ToArray(), 0);
}

It works, but I noticed that when I needed to make the ReadSNMPString function, it needed kinda the same logic but different. I’m using these docs as reference (they’re the best I’ve found, and so far their documentation helped to make the production code work). Also, replace snmp.htm with ber.htm to find out other links.

I’m trying to find out how to make a generic function to read at least one function for each datatype I need (simple though, only ints and strings) without using such ugly code.

I managed to make a new function now but I feel it can be a bit slower and/or not as optimized as I’d like.

public static class ASN1Int
{
    public static int Read(Stream s)
    {
        int preByte = s.ReadByte();
        if (preByte <= 0x79)
            return s.ReadByte();

        int bSize = preByte - 0x80;

        MemoryStream ms = new MemoryStream();
        using (BinaryWriter b = new BinaryWriter(ms))
        {
            for (int i = 0; i < 4 - bSize; i++)
                b.Write((byte)0x0);
            for (int i = 0; i < bSize; i++)
                b.Write((byte)s.ReadByte());
        }

        byte[] rv = ms.ToArray();
        Array.Reverse(rv, 0, rv.Length);
        return BitConverter.ToInt32(rv, 0);
    }
}

Any ideas are really welcome, and if someone’s gonna close this on here please do a bit more research before going trigger-happy on me.

Solution

So, based on your update it looks like you’ve switched to reading from Stream – good decision.

A bit simplified/cleaner approach for reading int is:

public static class ASN1Int
{
    public static int Read(Stream inputStream)
    {
        const int sizeMarker = 0x80; //TODO: name appropriately

        int size = inputStream.ReadByte();

        if (size < sizeMarker)
            return inputStream.ReadByte();

        size -= sizeMarker;
        //TODO: add assertion to make sure size<=4 if needed

        var result = inputStream.ReadByte();

        while (--size > 0)
            result = (result << 8) + inputStream.ReadByte();

        return result;
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *