Benyamin Jafari
Benyamin Jafari

Reputation: 34086

How to decode an Opaque data which has obtained by pysnmp?

I'm going to read data from an SNMP device by its OID via pysnmp library. However, I'm dealing with an error from Opaque type:

from pysnmp import hlapi

def construct_object_types(list_of_oids):
    object_types = []
    for oid in list_of_oids:
        object_types.append(hlapi.ObjectType(hlapi.ObjectIdentity(oid)))
    return object_types

def get(target, oids, credentials, port=161, engine=hlapi.SnmpEngine(),
        context=hlapi.ContextData()):
    handler = hlapi.getCmd(
        engine,
        credentials,
        hlapi.UdpTransportTarget((target, port)),
        context,
        *construct_object_types(oids)
    )
    return fetch(handler, 1)[0]

def cast(value):
    try:
        return int(value)
    except (ValueError, TypeError):
        try:
            return float(value)
        except (ValueError, TypeError):
            try:
                return str(value)
            except (ValueError, TypeError) as exc:
                print(exc)
    return value

def fetch(handler, count):
    result = []
    for i in range(count):
        (error_indication, error_status,
         error_index, var_binds) = next(handler)
        if not error_indication and not error_status:
            items = {}
            print(var_binds)
            for var_bind in var_binds:
                items[str(var_bind[0])] = cast(var_bind[1])
            result.append(items)
        else:
            raise RuntimeError(f'SNMP error: {error_indication}')
    return result


print(get("192.168.100.112", [".1.3.6.1.4.1.9839.1.2.532.0",
                              '.1.3.6.1.4.1.9839.1.2.513.0'],
          hlapi.CommunityData('public')))

Out:

[ObjectType(ObjectIdentity(<ObjectName value object, tagSet <TagSet object, tags 0:0:6>, payload [1.3.6.1.4.1.9839.1.2.532.0]>), <Opaque value object, tagSet <TagSet object, tags 64:0:4>, encoding iso-8859-1, payload [0x9f780441ccb646]>), ObjectType(ObjectIdentity(<ObjectName value object, tagSet <TagSet object, tags 0:0:6>, payload [1.3.6.1.4.1.9839.1.2.513.0]>), <Integer value object, tagSet <TagSet object, tags 0:0:2>, subtypeSpec <ConstraintsIntersection object, consts <ValueRangeConstraint object, consts -2147483648, 2147483647>>, payload [10]>)]
{'1.3.6.1.4.1.9839.1.2.532.0': '\x9fx\x04A̶F', '1.3.6.1.4.1.9839.1.2.513.0': 10}

The first OID (.1.3.6.1.4.1.9839.1.2.532.0) returns an Opaque value (\x9fx\x04A̶F) and I don't know how I can convert it to a float value. I should add that, that is a temperature value of 25.5°C.


In other words, how can I reach the following values by each other?

Upvotes: 0

Views: 1129

Answers (2)

Benyamin Jafari
Benyamin Jafari

Reputation: 34086

According to the TerhorstD's asnwer plus some changes and knowing that the Opaque frame consists of 7 bytes in which the 3 bytes of those are constant (\x9fx\x04 or 159\120\4 in decimal), I wrote the following code snippet to deal with that problem:

...

handler = get("192.168.100.112", [".1.3.6.1.4.1.9839.1.2.532.0",
                                  '.1.3.6.1.4.1.9839.1.2.513.0'],
              hlapi.CommunityData('public'))

for key, value in handler.items():
    try:
        if len(value) == 7 and value[0].encode('latin1')[0] == 159\
                and value[1].encode('latin1')[0] == 120\
                and value[2].encode('latin1')[0] == 4:

            data = value[3:]
            print(struct.unpack('>f', data.encode('latin1'))[0])
        else:
            print(value)
    except AttributeError:
        print(value)

Out:

25.589001
10

[NOTE]:

  • Opaque is a little-endian format (> in struct).

[UPDATE]:

More wisely:

for key, value in handler.items():
    try:
        unpacked = struct.unpack('>BBBf', value.encode('latin1'))
        if unpacked[:3] == (159,120,4):
            '''Checking if data Opaque or not.'''

            print(unpacked[-1])
        else:
            print(value)
    except AttributeError:
        print(value)

Upvotes: 0

TerhorstD
TerhorstD

Reputation: 295

Your value 0x9f780441ccb646 can be

  • split into two floats, of which one is 25.589001, the other part is something else, or
  • the middle is the representation of the pysnmp object (__repr__) without a known interpretation (probably MIB missing), or
  • converted to a byte representation (with iso-8859-1 encoding) which is your string '\x9fx\x04A̶F'.

So the data is there, it just needs to be extracted from the SNMP packet. The proper way would be to give the corresponding MIB entry to pysnmp.

Alternatively (answering your second question), the manual way of decoding the bytes can be done with the Python's struct module.

import struct

data = 0x9f780441ccb646         # this is what you got from pysnmp
thebytes = struct.pack("l", data)
print(thebytes.decode('latin1'))
print(thebytes)
print(struct.unpack("ff", thebytes))

gives

F¶ÌAx
b'F\xb6\xccA\x04x\x9f\x00'
(25.589000701904297, 1.4644897383138518e-38)

instead of unpacking to two floats, the MIB will tell you how the other data should be interpreted, so instead of unpack("ff",… you might want something else, check out the available format specifiers, for example "fhh" would give (25.589000701904297, 30724, 159).

EDIT:

TL;DR:

data = '\0\x9fx\x04A̶F'
print("temperature: %f°C" % struct.unpack('>ff', data.encode('latin1'))[1])
temperature: 25.589001°C

To elaborate on the string representation: The bytes you see 'A̶F' are in a reversed order than the ones in my print statement 'F¶ÌA' because of the different endianess. The byte order is already corrected in the int-converted data 0x9f780441ccb646 that you give in your output and I used in the conversion example. If you want to start from the encoded string, you first need to convert it back to the correct memory representation:

data = '\0\x9fx\x04A̶F'       # (initial '\0' is for filling the 8-bytes in correct alignment)
thebytes =  data.encode('latin1')

But that's only half of the trick, because now the endianess is still wrong. Fortunately struct has the flags to correct for that. You can unpack in both byte-orders and choose the right one

print("unpacked little-endian: ", struct.unpack("<ff", thebytes))
print("unpacked big-endian:    ", struct.unpack(">ff", thebytes))
unknown, temperature = struct.unpack(">ff", thebytes)
print("temperature: %f°C" % temperature)

giving

unpacked little-endian:  (2.9225269119838333e-36, 23398.126953125)
unpacked big-endian:     (1.4644897383138518e-38, 25.589000701904297)
temperature: 25.589001°C

The correct endianess of the opaque packet is either part of SNMP standard (then probably "network-byte order" '!' is the correct one), or should also be given in the MIB together with the correct field types which need to be given as format specifiers. If your packets are always 7-byte long, you might try a combination that adds to 7 bytes instead of 8 (ff = 4+4), then you can also omit adding the \0 padding byte.

Upvotes: 1

Related Questions