# -*- coding: utf-8 -*-
# Common utility functions
# SPDX-License-Identifier: BSD-2-Clause
from __future__ import print_function
import struct
import ntp.poly
from ntp.util import slicedata
internetPrefix = (1, 3, 6, 1) # Used by the prefix option of OID headers
prefixCount = len(internetPrefix)
# ==========================================================================
#
# Callables fall into the following categories:
# Data type encoder/decoders
# Packet classes/encoders
# Packet body decoders
# Glue/Utility/Misc functions
#
# To encode a packet, create an instance of that packet type's class, then
# call the encode() method.
#
# To decode a packet, call the decode_packet function, which will select
# the correct decoder for that packet type.
#
# ==========================================================================
# ==========================================================================
#
# Error classes
#
# ==========================================================================
class ParseError(Exception):
def __init__(self, message, header=None, packetData="", remainingData=""):
Exception.__init__(self)
self.message = message
self.header = header
self.packetData = packetData
self.remainingData = remainingData
class ParseDataLengthError(ParseError):
pass
class ParseVersionError(ParseError):
pass
class ParsePDUTypeError(ParseError):
pass
# ==========================================================================
#
# Packet encoders / decoders
#
# Encoders are class methods which output a string version of the
# packet, ready for transmission.
#
# Decoders take just the body sliced away from everything else,
# and a dict containing the already parsed information from the header.
# They return the relevant class instance for the packet in question,
# they do not return extra data as they are never supposed to receive it.
#
# Decoders are not meant to be called directly by external code,
# only by decode_packet().
#
# ==========================================================================
class AgentXPDU:
def __init__(self, pduType, bigEndian, sID, tactID, pktID,
hascontext=False, context=None):
self.pduType = pduType
self.bigEndian = bigEndian
self.sessionID = sID
self.transactionID = tactID
self.packetID = pktID
self.context = context
self._hascontext = hascontext
def packetVars(self):
"Assembles a list of class variables that it is desirable to print"
pktvars = {}
names = dir(self)
names.remove("context")
for vname in names:
if vname[0] == "_": # ignores specials, and what we want ignored
continue
var = getattr(self, vname)
if callable(var): # ignores methods
# *might* have to rethink this if contents get classified
continue
pktvars[vname] = var
if self._hascontext:
# context is always present, not always used
pktvars["context"] = self.context
return pktvars
def __repr__(self):
s = self.__class__.__name__ + "("
v = []
myvars = self.packetVars()
keys = list(myvars.keys())
keys.sort() # they will always be in the same order: testable
for name in keys:
value = myvars[name]
v.append("%s=%s" % (name, repr(value)))
s += ", ".join(v) + ")"
return s
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
if self.pduType != other.pduType:
return False
if self.bigEndian != other.bigEndian:
return False
if self.sessionID != other.sessionID:
return False
if self.transactionID != other.transactionID:
return False
if self.packetID != other.packetID:
return False
if self._hascontext != other._hascontext:
return False
if self._hascontext:
if self.context != other.context:
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def decode_OpenPDU(data, header):
flags = header["flags"]
temp, data = slicedata(data, 4)
timeout = struct.unpack("Bxxx", ntp.poly.polybytes(temp))[0]
oid, data = decode_OID(data, header)
description = decode_octetstr(data, header)[0]
result = OpenPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
timeout, oid, description)
return result
class OpenPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID,
timeout, oid, description):
AgentXPDU.__init__(self, PDU_OPEN, bigEndian, sID, tactID, pktID)
self.timeout = timeout
self.oid = classifyOID(oid)
self.description = description
def __eq__(self, other):
if AgentXPDU.__eq__(self, other) is not True:
return False
if self.timeout != other.timeout:
return False
if self.oid != other.oid:
return False
if self.description != other.description:
return False
return True
def encode(self):
payload = struct.pack("Bxxx", self.timeout)
payload += self.oid.encode(self.bigEndian)
payload += encode_octetstr(self.bigEndian, self.description)
header = encode_pduheader(self.pduType,
False, False, False, False, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, len(payload))
return header + payload
def decode_ClosePDU(data, header):
flags = header["flags"]
reason = data[0] # Bxxx
if isinstance(reason, str):
reason = ord(reason)
result = ClosePDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
reason)
return result
class ClosePDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID, reason):
AgentXPDU.__init__(self, PDU_CLOSE, bigEndian, sID, tactID, pktID)
if reason not in definedReasons: # pragma: no cover
raise ValueError("Close reason %s not in defined types" % reason)
self.reason = reason
def __eq__(self, other):
if AgentXPDU.__eq__(self, other) is not True:
return False
if self.reason != other.reason:
return False
return True
def encode(self):
payload = struct.pack("Bxxx", self.reason)
header = encode_pduheader(self.pduType,
False, False, False, False, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, len(payload))
return header + payload
def decode_xRegisterPDU(data, header):
flags = header["flags"]
endianToken = getendian(flags["bigEndian"])
context, data = decode_context(data, header)
temp, data = slicedata(data, 4)
timeout, priority, rangeSubid = struct.unpack(endianToken + "BBBx",
ntp.poly.polybytes(temp))
oid, data = decode_OID(data, header)
if rangeSubid != 0:
temp, data = slicedata(data, 4)
upperBound = struct.unpack(endianToken + "I",
ntp.poly.polybytes(temp))[0]
else:
upperBound = None
if header["type"] == PDU_REGISTER:
result = RegisterPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
timeout, priority, oid,
rangeSubid, upperBound, context)
else:
result = UnregisterPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
priority, oid, rangeSubid, upperBound, context)
return result
class RegisterPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID,
timeout, priority, subtree,
rangeSubid=0, upperBound=None, context=None):
AgentXPDU.__init__(self, PDU_REGISTER,
bigEndian, sID, tactID, pktID, True, context)
self.timeout = timeout
self.priority = priority
self.subtree = classifyOID(subtree)
self.rangeSubid = rangeSubid
self.upperBound = upperBound
self._instReg = True # so we don't have to write two encode()s
def __eq__(self, other):
if AgentXPDU.__eq__(self, other) is not True:
return False
if hasattr(self, "timeout"): # Relevant to UnregisterPDU subclass
if self.timeout != other.timeout:
return False
if self.priority != other.priority:
return False
if self.subtree != other.subtree:
return False
if self.rangeSubid != other.rangeSubid:
return False
if self.upperBound != other.upperBound:
return False
return True
def encode(self):
endianToken = getendian(self.bigEndian)
contextP, payload = encode_context(self.bigEndian, self.context)
if self.pduType == PDU_REGISTER:
payload += struct.pack(endianToken + "BBBx", self.timeout,
self.priority, self.rangeSubid)
else:
payload += struct.pack(endianToken + "xBBx",
self.priority, self.rangeSubid)
payload += self.subtree.encode(self.bigEndian)
if self.rangeSubid != 0:
if self.upperBound is None:
raise ValueError("upperBound must be set if rangeSubid is set")
payload += struct.pack(endianToken + "I", self.upperBound)
header = encode_pduheader(self.pduType, self._instReg, False, False,
contextP, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, len(payload))
packet = header + payload
return packet
class UnregisterPDU(RegisterPDU):
def __init__(self, bigEndian, sID, tactID, pktID, priority, subtree,
rangeSubid=0, upperBound=None, context=None):
RegisterPDU.__init__(self, bigEndian, sID, tactID, pktID,
None, priority, subtree,
rangeSubid, upperBound, context)
self.pduType = PDU_UNREGISTER
del self.timeout # Unregister doesn't have a timeout
self._instReg = False
def decode_xGetPDU(data, header):
flags = header["flags"]
context, data = decode_context(data, header)
oidranges = decode_searchrange_list(data, header)
if header["type"] == PDU_GET_NEXT:
result = GetNextPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
oidranges, context)
else:
result = GetPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
oidranges, context)
return result
class GetPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID, oidranges, context=None):
AgentXPDU.__init__(self, PDU_GET,
bigEndian, sID, tactID, pktID, True, context)
self.oidranges = oidranges
def __eq__(self, other):
if AgentXPDU.__eq__(self, other) is not True:
return False
if self.oidranges != other.oidranges:
return False
return True
def encode(self):
contextP, payload = encode_context(self.bigEndian, self.context)
payload += encode_searchrange_list(self.bigEndian, self.oidranges)
header = encode_pduheader(self.pduType, False, False, False,
contextP, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, len(payload))
return header + payload
class GetNextPDU(GetPDU):
def __init__(self, bigEndian, sID, tactID, pktID, oidranges, context=None):
GetPDU.__init__(self, bigEndian, sID, tactID, pktID,
oidranges, context)
self.pduType = PDU_GET_NEXT
def decode_GetBulkPDU(data, header):
flags = header["flags"]
endianToken = getendian(flags["bigEndian"])
context, data = decode_context(data, header)
temp, data = slicedata(data, 4)
nonReps, maxReps = struct.unpack(endianToken + "HH",
ntp.poly.polybytes(temp))
oidranges = decode_searchrange_list(data, header)
result = GetBulkPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
nonReps, maxReps, oidranges, context)
return result
class GetBulkPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID,
nonReps, maxReps, oidranges, context=None):
AgentXPDU.__init__(self, PDU_GET_BULK,
bigEndian, sID, tactID, pktID, True, context)
self.nonReps = nonReps
self.maxReps = maxReps
self.oidranges = oidranges
def __eq__(self, other):
if AgentXPDU.__eq__(self, other) is not True:
return False
if self.nonReps != other.nonReps:
return False
if self.maxReps != other.maxReps:
return False
if self.oidranges != other.oidranges:
return False
return True
def encode(self):
endianToken = getendian(self.bigEndian)
contextP, payload = encode_context(self.bigEndian, self.context)
payload += struct.pack(endianToken + "HH", self.nonReps, self.maxReps)
payload += encode_searchrange_list(self.bigEndian, self.oidranges)
header = encode_pduheader(self.pduType, False, False, False,
contextP, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, len(payload))
return header + payload
def decode_TestSetPDU(data, header):
flags = header["flags"]
context, data = decode_context(data, header)
varbinds = decode_varbindlist(data, header)
result = TestSetPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
varbinds, context)
return result
class TestSetPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID, varbinds, context=None):
AgentXPDU.__init__(self, PDU_TEST_SET,
bigEndian, sID, tactID, pktID, True, context)
self.varbinds = varbinds
def __eq__(self, other):
if AgentXPDU.__eq__(self, other) is not True:
return False
if self.varbinds != other.varbinds:
return False
return True
def encode(self):
contextP, payload = encode_context(self.bigEndian, self.context)
payload += encode_varbindlist(self.bigEndian, self.varbinds)
header = encode_pduheader(self.pduType, False, False, False,
contextP, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, len(payload))
return header + payload
def decode_CommitSetPDU(data, header):
flags = header["flags"]
result = CommitSetPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"])
return result
class CommitSetPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID):
AgentXPDU.__init__(self, PDU_COMMIT_SET,
bigEndian, sID, tactID, pktID)
def encode(self):
header = encode_pduheader(self.pduType,
False, False, False, False, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, 0)
return header
def decode_UndoSetPDU(data, header):
flags = header["flags"]
result = UndoSetPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"])
return result
class UndoSetPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID):
AgentXPDU.__init__(self, PDU_UNDO_SET,
bigEndian, sID, tactID, pktID)
def encode(self):
header = encode_pduheader(self.pduType,
False, False, False, False, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, 0)
return header
def decode_CleanupSetPDU(data, header):
flags = header["flags"]
result = CleanupSetPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"])
return result
class CleanupSetPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID):
AgentXPDU.__init__(self, PDU_CLEANUP_SET,
bigEndian, sID, tactID, pktID)
def encode(self):
header = encode_pduheader(self.pduType,
False, False, False, False, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, 0)
return header
def decode_PingPDU(data, header):
flags = header["flags"]
context, data = decode_context(data, header)
result = PingPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
context)
return result
class PingPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID, context=None):
AgentXPDU.__init__(self, PDU_PING,
bigEndian, sID, tactID, pktID, True, context)
def encode(self):
contextP, payload = encode_context(self.bigEndian, self.context)
header = encode_pduheader(self.pduType, False, False, False,
contextP, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, len(payload))
return header + payload
def decode_NotifyPDU(data, header):
flags = header["flags"]
context, data = decode_context(data, header)
varbinds = decode_varbindlist(data, header)
result = NotifyPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
varbinds, context)
return result
class NotifyPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID, varbinds, context=None):
AgentXPDU.__init__(self, PDU_NOTIFY,
bigEndian, sID, tactID, pktID, True, context)
self.varbinds = varbinds
def __eq__(self, other):
if AgentXPDU.__eq__(self, other) is not True:
return False
if self.varbinds != other.varbinds:
return False
return True
def encode(self):
contextP, payload = encode_context(self.bigEndian, self.context)
payload += encode_varbindlist(self.bigEndian, self.varbinds)
header = encode_pduheader(self.pduType, False, False, False,
contextP, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, len(payload))
return header + payload
def decode_xIndexAllocPDU(data, header):
flags = header["flags"]
context, data = decode_context(data, header)
varbinds = decode_varbindlist(data, header)
isalloc = (header["type"] == PDU_INDEX_ALLOC)
pdu = IndexAllocPDU if isalloc else IndexDeallocPDU
result = pdu(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
flags["newIndex"], flags["anyIndex"],
varbinds, context)
return result
class IndexAllocPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID,
newIndex, anyIndex, varbinds, context=None):
AgentXPDU.__init__(self, PDU_INDEX_ALLOC,
bigEndian, sID, tactID, pktID, True, context)
self.newIndex = newIndex
self.anyIndex = anyIndex
self.varbinds = varbinds
def __eq__(self, other):
if AgentXPDU.__eq__(self, other) is not True:
return False
if self.newIndex != other.newIndex:
return False
if self.anyIndex != other.anyIndex:
return False
if self.varbinds != other.varbinds:
return False
return True
def encode(self):
contextP, payload = encode_context(self.bigEndian, self.context)
payload += encode_varbindlist(self.bigEndian, self.varbinds)
header = encode_pduheader(self.pduType,
False, self.newIndex, self.anyIndex,
contextP, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, len(payload))
return header + payload
class IndexDeallocPDU(IndexAllocPDU):
def __init__(self, bigEndian, sID, tactID, pktID,
newIndex, anyIndex, varbinds, context=None):
IndexAllocPDU.__init__(self, bigEndian, sID, tactID, pktID,
newIndex, anyIndex, varbinds, context)
self.pduType = PDU_INDEX_DEALLOC
def decode_AddAgentCapsPDU(data, header):
flags = header["flags"]
context, data = decode_context(data, header)
oid, data = decode_OID(data, header)
descr = decode_octetstr(data, header)[0]
result = AddAgentCapsPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
oid, descr, context)
return result
class AddAgentCapsPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID,
oid, description, context=None):
AgentXPDU.__init__(self, PDU_ADD_AGENT_CAPS,
bigEndian, sID, tactID, pktID, True, context)
self.oid = classifyOID(oid)
self.description = description
def __eq__(self, other):
if AgentXPDU.__eq__(self, other) is not True:
return False
if self.oid != other.oid:
return False
if self.description != other.description:
return False
return True
def encode(self):
contextP, payload = encode_context(self.bigEndian, self.context)
payload += self.oid.encode(self.bigEndian)
payload += encode_octetstr(self.bigEndian, self.description)
header = encode_pduheader(self.pduType, False, False, False,
contextP, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, len(payload))
return header + payload
def decode_RMAgentCapsPDU(data, header):
flags = header["flags"]
context, data = decode_context(data, header)
oid, data = decode_OID(data, header)
result = RMAgentCapsPDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
oid, context)
return result
class RMAgentCapsPDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID, oid, context=None):
AgentXPDU.__init__(self, PDU_RM_AGENT_CAPS,
bigEndian, sID, tactID, pktID, True, context)
self.oid = classifyOID(oid)
def __eq__(self, other):
if AgentXPDU.__eq__(self, other) is not True:
return False
if self.oid != other.oid:
return False
return True
def encode(self):
contextP, payload = encode_context(self.bigEndian, self.context)
payload += self.oid.encode(self.bigEndian)
header = encode_pduheader(self.pduType, False, False, False,
contextP, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, len(payload))
return header + payload
def decode_ResponsePDU(data, header):
flags = header["flags"]
endianToken = getendian(flags["bigEndian"])
temp, data = slicedata(data, 8)
sysUptime, resError, resIndex = struct.unpack(endianToken + "IHH",
ntp.poly.polybytes(temp))
if data:
varbinds = decode_varbindlist(data, header)
else:
varbinds = None
result = ResponsePDU(flags["bigEndian"], header["session_id"],
header["transaction_id"], header["packet_id"],
sysUptime, resError, resIndex, varbinds)
return result
class ResponsePDU(AgentXPDU):
def __init__(self, bigEndian, sID, tactID, pktID,
sysUptime, resError, resIndex, varbinds=None):
AgentXPDU.__init__(self, PDU_RESPONSE, bigEndian, sID, tactID, pktID)
self.sysUptime = sysUptime
self.resError = resError
self.resIndex = resIndex
self.varbinds = varbinds
def __eq__(self, other):
if AgentXPDU.__eq__(self, other) is not True:
return False
if self.sysUptime != other.sysUptime:
return False
if self.resError != other.resError:
return False
if self.resIndex != other.resIndex:
return False
if self.varbinds != other.varbinds:
return False
return True
def encode(self):
endianToken = getendian(self.bigEndian)
payload = struct.pack(endianToken + "IHH", self.sysUptime,
self.resError, self.resIndex)
if self.varbinds is not None:
payload += encode_varbindlist(self.bigEndian, self.varbinds)
header = encode_pduheader(self.pduType,
False, False, False, False, self.bigEndian,
self.sessionID, self.transactionID,
self.packetID, len(payload))
return header + payload
# ========================================================================
#
# Data type encoders / decoders
#
# Encoders take data relevant to the type, return encoded string.
#
# Decoders take encoded string, return a tuple of (value, restOfData).
#
# ========================================================================
def classifyOID(oid):
"Utility function to allow the user to send a bare tuple for some cases"
if isinstance(oid, OID):
return oid
return OID(oid, False)
def decode_OID(data, header):
flags = header["flags"]
# Need to split off the header to get the subid count
header, data = slicedata(data, 4)
n_subid, prefix, include = struct.unpack("BBBx", ntp.poly.polybytes(header))
if prefix != 0:
subids = internetPrefix + (prefix,)
else:
subids = ()
totalLen = len(subids) + n_subid
if not (0 <= totalLen <= 128):
raise ValueError("OID has too many subids")
include = bool(include) # could be anything, force bool
# Now have the count, need to split the subids from the rest of the packet
byteCount = n_subid * 4
data, rest = slicedata(data, byteCount)
endianToken = getendian(flags["bigEndian"])
formatString = endianToken + ("I" * n_subid)
subids += struct.unpack(formatString, ntp.poly.polybytes(data))
result = OID(subids, include)
return (result, rest)
class OID:
def __init__(self, subids, include=False):
self.subids = subids
self.include = include
self.sanity()
def __eq__(self, other):
if not isinstance(other, OID):
return False
if not (self.subids == other.subids):
return False
elif self.include != other.include:
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __lt__(self, other):
return self.compareOID(other) == -1
def __le__(self, other):
return self.compareOID(other) in (-1, 0)
def __gt__(self, other):
return self.compareOID(other) == 1
def __ge__(self, other):
return self.compareOID(other) in (0, 1)
def compareOID(self, other):
if self.subids == other.subids:
return 0
lself = len(self.subids)
lother = len(other.subids)
if lself > lother:
x = other.subids
y = self.subids
lx = lother
flipped = True
else:
x = self.subids
y = other.subids
lx = lself
flipped = False
for i in range(lx):
if x[i] == y[i]:
continue
else:
# this is the Py3 version of c = cmp(x[i], y[i])
c = (x[i] > y[i]) - (x[i] < y[i])
c = -c if flipped else c
return c
# Only reach this if shorter, and each index is equal
if flipped:
return 1
else:
return -1
def __repr__(self):
return "OID(%s, %s)" % (repr(self.subids), repr(self.include))
def sanity(self):
if not isinstance(self.subids, (tuple, list)):
raise TypeError
if len(self.subids) > 128:
raise ValueError
def isNull(self):
if not self.subids and not self.include:
return True
return False
def encode(self, bigEndian):
subids = self.subids[:] # we may need to modify the copy
numsubs = len(subids)
if not (0 <= numsubs <= 128): # OIDs can have a maximum of 128 subs
raise ValueError("OID has too many subids")
if subids[:prefixCount] == internetPrefix:
if numsubs > prefixCount:
prefix = subids[prefixCount] # the number after the prefix
subids = subids[prefixCount + 1:] # +1 to strip prefix arg
else: # Have a prefix but nothing else, leave it as is.
prefix = 0
else:
prefix = 0
n_subid = len(subids)
include = int(bool(self.include)) # force integer bool
endianToken = getendian(bigEndian)
body = struct.pack(endianToken + "BBBx", n_subid, prefix, include)
for subid in subids:
body += struct.pack(endianToken + "I", subid)
return body
def encode_octetstr(bigEndian, octets):
numoctets = len(octets)
endianToken = getendian(bigEndian)
header = struct.pack(endianToken + "I", numoctets)
pad = (numoctets % 4)
if pad > 0: # Pad out the data to word boundary
pad = 4 - pad
pad = b"\x00" * pad
if type(octets) is str:
octets = ntp.poly.polybytes(octets)
data = header + octets + pad
else:
fmt = "B" * numoctets
data = struct.pack(fmt, *octets)
data = header + data + pad
return data
def decode_octetstr(data, header):
flags = header["flags"]
header, data = slicedata(data, 4)
endianToken = getendian(flags["bigEndian"])
numoctets = struct.unpack(endianToken + "I", ntp.poly.polybytes(header))[0]
if len(data) < numoctets:
raise ValueError("Octet string shorter than length")
pad = numoctets % 4
if pad > 0: # Pad out the data to word boundary
pad = 4 - pad
return ntp.poly.polystr(data[:numoctets]), data[numoctets + pad:]
def sanity_octetstr(data):
if isinstance(data, str):
return
if isinstance(data, (list, tuple)):
for c in data:
if not (0 <= c < 256):
raise ValueError
return
raise TypeError
def decode_Varbind(data, header):
flags = header["flags"]
bindheader, data = slicedata(data, 4)
endianToken = getendian(flags["bigEndian"])
valType = struct.unpack(endianToken + "Hxx",
ntp.poly.polybytes(bindheader))[0]
name, data = decode_OID(data, header)
if valType not in definedValueTypes.keys():
raise ValueError("Value type %s not in defined types" % valType)
handlers = definedValueTypes[valType]
payload, data = handlers[2](data, header)
result = Varbind(valType, name, payload)
return result, data
class Varbind:
def __init__(self, vtype, oid, payload=None):
self.valueType = vtype
self.oid = classifyOID(oid)
self.payload = payload # payload=None exists for Null types
self.sanity()
def __eq__(self, other):
if self.valueType != other.valueType:
return False
if self.oid != other.oid:
return False
if self.payload != other.payload:
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
fmt = "Varbind(vtype=%s, oid=%s, payload=%s)"
r = fmt % (repr(self.valueType), repr(self.oid), repr(self.payload))
return r
def sanity(self):
self.oid.sanity()
vt = self.valueType
if vt not in definedValueTypes.keys():
raise ValueError("Value type %s not in defined types" % vt)
sanifyer, encoder, decoder = definedValueTypes[vt]
if sanifyer is None: # it is a class
self.payload.sanity()
else:
sanifyer(self.payload)
def encode(self, bigEndian):
endianToken = getendian(bigEndian)
header = struct.pack(endianToken + "Hxx", self.valueType)
name = self.oid.encode(bigEndian)
handlers = definedValueTypes[self.valueType]
sanifyer, encoder, decoder = handlers
if sanifyer is None: # Classes have the .sanity() method
data = header + name + self.payload.encode(bigEndian)
else: # Non-classes have an associated sanity_<vartype>() function
data = header + name + encoder(bigEndian, self.payload)
return data
def encode_integer32(bigEndian, num):
endianToken = getendian(bigEndian)
return struct.pack(endianToken + "i", num)
def decode_integer32(data, header):
flags = header["flags"]
endianToken = getendian(flags["bigEndian"])
num, data = slicedata(data, 4)
num = struct.unpack(endianToken + "i", num)[0]
return (num, data)
def sanity_integer32(data):
if type(data) is not int:
raise TypeError("%s is not integer" % repr(data))
def encode_unsigned32(bigEndian, num):
endianToken = getendian(bigEndian)
return struct.pack(endianToken + "I", num)
def decode_unsigned32(data, header):
flags = header["flags"]
endianToken = getendian(flags["bigEndian"])
num, data = slicedata(data, 4)
num = struct.unpack(endianToken + "I", num)[0]
return (num, data)
def sanity_unsigned32(data): # pragma: no cover
if data != (data & 0xFFFFFFFF):
raise ValueError
def encode_nullvalue(bigEndian, data):
return b""
def decode_nullvalue(data, header):
return (None, data)
def sanity_nullvalue(data):
pass # Seriously?
def encode_integer64(bigEndian, num):
endianToken = getendian(bigEndian)
return struct.pack(endianToken + "Q", num)
def decode_integer64(data, header):
flags = header["flags"]
endianToken = getendian(flags["bigEndian"])
num, data = slicedata(data, 8)
num = struct.unpack(endianToken + "Q", ntp.poly.polybytes(num))[0]
return (num, data)
def sanity_integer64(data):
if data != (data & 0xFFFFFFFFFFFFFFFF):
raise ValueError
def encode_ipaddr(bigEndian, octets):
sanity_ipaddr(octets)
return encode_octetstr(bigEndian, octets)
def decode_ipaddr(data, header):
addr, data = decode_octetstr(data, header)
addr = struct.unpack("BBBB", ntp.poly.polybytes(addr))
return addr, data
def sanity_ipaddr(data):
if len(data) not in (4, 16):
raise ValueError
def decode_SearchRange(data, header):
startOID, data = decode_OID(data, header)
endOID, data = decode_OID(data, header)
result = SearchRange(startOID, endOID)
return result, data
class SearchRange:
def __init__(self, start, end, include=None):
self.start = classifyOID(start)
self.end = classifyOID(end)
self.end.include = False # sanify
if include is not None:
# if the user does not provide include it defaults to whatever
# start is already set to
self.start.include = include
self.sanity()
def __eq__(self, other):
if self.start != other.start:
return False
if self.end != other.end:
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
r = "SearchRange(%s, %s)" % (repr(self.start), repr(self.end))
return r
def sanity(self):
self.start.sanity()
self.end.sanity()
if self.end.include:
raise ValueError
def encode(self, bigEndian):
startOIDstr = self.start.encode(bigEndian)
endOIDstr = self.end.encode(bigEndian)
return startOIDstr + endOIDstr
def encode_searchrange_list(bigEndian, searchranges):
encoded = []
for sran in searchranges:
encoded.append(sran.encode(bigEndian))
encoded = b"".join(encoded)
return encoded
def decode_searchrange_list(data, header): # Cannot handle extra data
oidranges = []
while data:
oids, data = decode_SearchRange(data, header)
oidranges.append(oids)
return tuple(oidranges)
def encode_varbindlist(bigEndian, varbinds):
payload = b""
for varbind in varbinds:
payload += varbind.encode(bigEndian)
return payload
def decode_varbindlist(data, header):
if data:
varbinds = []
while data:
vb, data = decode_Varbind(data, header)
varbinds.append(vb)
varbinds = tuple(varbinds)
else:
varbinds = None
return varbinds
def encode_flagbyte(flags):
flagbyte = 0
flagbyte |= flags["instReg"]
flagbyte |= flags["newIndex"] << 1
flagbyte |= flags["anyIndex"] << 2
flagbyte |= flags["contextP"] << 3 # nonDefaultContext
flagbyte |= flags["bigEndian"] << 4
return flagbyte
def decode_flagbyte(flags):
flagDict = makeflags(bool(flags & 0x1), bool(flags & 0x2),
bool(flags & 0x4), bool(flags & 0x8),
bool(flags & 0x10))
return flagDict
# =========================================
# Utilities, glue, and misc
# =========================================
def makeflags(iR, nI, aI, cP, bE):
return {"instReg": iR,
"newIndex": nI,
"anyIndex": aI,
"contextP": cP,
"bigEndian": bE}
def getendian(bigEndian):
return ">" if bigEndian else "<"
def encode_pduheader(pduType, instanceRegistration, newIndex,
anyIndex, nonDefaultContext, bigEndian,
sessionID, transactionID, packetID, payloadLength):
# version type flags reserved
# sessionID
# transactionID
# packetID
# payload_length
if pduType not in definedPDUTypes:
raise ValueError("PDU type %s not in defined types" % pduType)
flags = encode_flagbyte(makeflags(instanceRegistration, newIndex,
anyIndex, nonDefaultContext,
bigEndian))
# Yes, this is a kluge, it is less problematic then the next best kluge
endianToken = getendian(bigEndian)
data = struct.pack(endianToken + "BBBxIIII", 1, pduType, flags,
sessionID, transactionID,
packetID, payloadLength)
return data
def decode_pduheader(data): # Endianness is controlled from the PDU header
lineone, data = slicedata(data, 4)
version, pduType, flags = struct.unpack(">BBBx",
ntp.poly.polybytes(lineone))
# Slice up the flags
flagDict = decode_flagbyte(flags)
# Chop the remaining parts of the header from the rest of the datastream
# then parse them
fmt = getendian(flagDict["bigEndian"]) + "IIII"
linen, data = slicedata(data, 16) # 4 x 4-byte variables
sID, tactionID, pktID, dataLen = struct.unpack(fmt,
ntp.poly.polybytes(linen))
result = {"version": version, "type": pduType, "flags": flagDict,
"session_id": sID, "transaction_id": tactionID,
"packet_id": pktID, "length": dataLen}
return result
def encode_context(bigEndian, context):
if context is not None:
contextP = True
payload = encode_octetstr(bigEndian, context)
else:
contextP = False
payload = b""
return (contextP, payload)
def decode_context(data, header):
flags = header["flags"]
if flags["contextP"]:
context, data = decode_octetstr(data, header)
else:
context = None
return (context, data)
def decode_packet(data):
if len(data) < 20:
raise ParseDataLengthError("Data too short for header")
header, newData = slicedata(data, 20)
header = decode_pduheader(header)
if header["length"] > len(newData):
return None, False, data # pkt, isFull?, buffer
packetSlice, newData = slicedata(newData, header["length"])
if header["version"] != 1:
raise ParseVersionError("Unknown packet version %i" %
header["version"],
header, packetSlice, newData)
pktType = header["type"]
if pktType not in definedPDUTypes.keys():
raise ParsePDUTypeError("PDU type %s not in defined types" % pktType,
header, packetSlice, newData)
decoder = definedPDUTypes[pktType]
try:
parsedPkt = decoder(packetSlice, header)
except Exception: # pragma: no cover
err = ParseError("Body parsing error", header, packetSlice, newData)
raise err
return parsedPkt, True, newData # pkt, isFull?, buffer
def bits2Bools(bitString, cropLength=None):
bits = []
for octet in bitString:
octet = ord(octet)
bits.append(bool(octet & 0x80)) # Yes, these are backwards, that is
bits.append(bool(octet & 0x40)) # how SNMP wants them. It does make
bits.append(bool(octet & 0x20)) # sense if you think about it as a
bits.append(bool(octet & 0x10)) # stream of bits instead of octets.
bits.append(bool(octet & 0x08))
bits.append(bool(octet & 0x04)) # If you don't like it go yell at
bits.append(bool(octet & 0x02)) # the SNMP designers.
bits.append(bool(octet & 0x01))
if cropLength is not None: # used when a bitfield is not a multiple of 8
bits = bits[:cropLength]
return bits
def bools2Bits(bits):
bitCounter = 0
octets = []
current = 0
for bit in bits:
current += (int(bit) << (7 - bitCounter))
bitCounter += 1
if bitCounter >= 8: # end of byte
bitCounter = 0
octets.append(chr(current))
current = 0
else:
if bitCounter != 0:
octets.append(chr(current))
octets = "".join(octets)
return octets
# Value types
VALUE_INTEGER = 2
VALUE_OCTET_STR = 4
VALUE_NULL = 5
VALUE_OID = 6
VALUE_IP_ADDR = 64
VALUE_COUNTER32 = 65
VALUE_GAUGE32 = 66
VALUE_TIME_TICKS = 67
VALUE_OPAQUE = 68
VALUE_COUNTER64 = 70
VALUE_NO_SUCH_OBJECT = 128
VALUE_NO_SUCH_INSTANCE = 129
VALUE_END_OF_MIB_VIEW = 130
# Sub-tuple format: (sanityChecker, encoder/class, decoder)
# if the sanityChecker is None it means the data type is held in
# a class, which has it's own .sanity() method
definedValueTypes = { # Used by the varbind functions
VALUE_INTEGER: (sanity_integer32,
encode_integer32,
decode_integer32),
VALUE_OCTET_STR: (sanity_octetstr,
encode_octetstr,
decode_octetstr),
VALUE_NULL: (sanity_nullvalue,
encode_nullvalue,
decode_nullvalue),
VALUE_OID: (None, OID, decode_OID),
VALUE_IP_ADDR: (sanity_ipaddr,
encode_ipaddr,
decode_ipaddr),
VALUE_COUNTER32: (sanity_unsigned32,
encode_unsigned32,
decode_unsigned32),
VALUE_GAUGE32: (sanity_unsigned32,
encode_unsigned32,
decode_unsigned32),
VALUE_TIME_TICKS: (sanity_unsigned32,
encode_unsigned32,
decode_unsigned32),
VALUE_OPAQUE: (sanity_octetstr,
encode_octetstr,
decode_octetstr),
VALUE_COUNTER64: (sanity_integer64,
encode_integer64,
decode_integer64),
VALUE_NO_SUCH_OBJECT: (sanity_nullvalue,
encode_nullvalue,
decode_nullvalue),
VALUE_NO_SUCH_INSTANCE: (sanity_nullvalue,
encode_nullvalue,
decode_nullvalue),
VALUE_END_OF_MIB_VIEW: (sanity_nullvalue,
encode_nullvalue,
decode_nullvalue)}
# PDU types
PDU_OPEN = 1
PDU_CLOSE = 2
PDU_REGISTER = 3
PDU_UNREGISTER = 4
PDU_GET = 5
PDU_GET_NEXT = 6
PDU_GET_BULK = 7
PDU_TEST_SET = 8
PDU_COMMIT_SET = 9
PDU_UNDO_SET = 10
PDU_CLEANUP_SET = 11
PDU_NOTIFY = 12
PDU_PING = 13
PDU_INDEX_ALLOC = 14
PDU_INDEX_DEALLOC = 15
PDU_ADD_AGENT_CAPS = 16
PDU_RM_AGENT_CAPS = 17
PDU_RESPONSE = 18
definedPDUTypes = { # Used by the decode_packet function
PDU_OPEN: decode_OpenPDU,
PDU_CLOSE: decode_ClosePDU,
PDU_REGISTER: decode_xRegisterPDU,
PDU_UNREGISTER: decode_xRegisterPDU,
PDU_GET: decode_xGetPDU,
PDU_GET_NEXT: decode_xGetPDU,
PDU_GET_BULK: decode_GetBulkPDU,
PDU_TEST_SET: decode_TestSetPDU,
PDU_COMMIT_SET: decode_CommitSetPDU,
PDU_UNDO_SET: decode_UndoSetPDU,
PDU_CLEANUP_SET: decode_CleanupSetPDU,
PDU_NOTIFY: decode_NotifyPDU,
PDU_PING: decode_PingPDU,
PDU_INDEX_ALLOC: decode_xIndexAllocPDU,
PDU_INDEX_DEALLOC: decode_xIndexAllocPDU,
PDU_ADD_AGENT_CAPS: decode_AddAgentCapsPDU,
PDU_RM_AGENT_CAPS: decode_RMAgentCapsPDU,
PDU_RESPONSE: decode_ResponsePDU}
# Closing reasons
RSN_OTHER = 1
RSN_PARSE_ERROR = 2
RSN_PROTOCOL_ERROR = 3
RSN_TIMEOUT = 4
RSN_SHUTDOWN = 5
RSN_BY_MANAGER = 6
definedReasons = (RSN_OTHER, RSN_PARSE_ERROR, RSN_PROTOCOL_ERROR,
RSN_TIMEOUT, RSN_SHUTDOWN, RSN_BY_MANAGER)
# Error reasons
ERR_NOERROR = 0
ERR_GENERR = 5
ERR_NO_ACCESS = 6
ERR_WRONG_TYPE = 7
ERR_WRONG_LEN = 8
ERR_WRONG_ENCODING = 9
ERR_WRONG_VALUE = 10
ERR_NO_CREATION = 11
ERR_INCONSISTENT_VALUE = 12
ERR_RESOURCE_UNAVAILABLE = 13
ERR_COMMIT_FAILED = 14
ERR_UNDO_FAILED = 15
ERR_NOT_WRITABLE = 17
ERR_INCONSISTENT_NAME = 18
definedErrors = (ERR_NOERROR, ERR_GENERR, ERR_NO_ACCESS, ERR_WRONG_TYPE,
ERR_WRONG_LEN, ERR_WRONG_ENCODING, ERR_WRONG_VALUE,
ERR_NO_CREATION, ERR_INCONSISTENT_VALUE,
ERR_RESOURCE_UNAVAILABLE, ERR_NOT_WRITABLE,
ERR_INCONSISTENT_NAME)
RSPERR_NO_AGENTX = 0
RSPERR_OPEN_FAILED = 265
RSPERR_NOT_OPEN = 257
RSPERR_INDEX_WRONG_TYPE = 258
RSPERR_INDEX_ALREADY_ALLOCATED = 259
RSPERR_INDEX_NONE_AVAILABLE = 260
RSPERR_INDEX_NOT_ALLOCATED = 261
RSPERR_UNSUPPORTED_CONTEXT = 262
RSPERR_DUPLICATE_REGISTRATION = 263
RSPERR_UNKNOWN_REGISTRATION = 264
RSPERR_UNKNOWN_AGENT_CAPS = 265
RSPERR_PARSE_ERROR = 266
RSPERR_REQUEST_DENIED = 267
RSPERR_PROCESSING_ERROR = 268
responseErrors = (RSPERR_NO_AGENTX, RSPERR_OPEN_FAILED, RSPERR_NOT_OPEN,
RSPERR_INDEX_WRONG_TYPE, RSPERR_INDEX_ALREADY_ALLOCATED,
RSPERR_INDEX_NONE_AVAILABLE, RSPERR_INDEX_NOT_ALLOCATED,
RSPERR_UNSUPPORTED_CONTEXT, RSPERR_DUPLICATE_REGISTRATION,
RSPERR_UNKNOWN_REGISTRATION, RSPERR_UNKNOWN_AGENT_CAPS,
RSPERR_PARSE_ERROR, RSPERR_REQUEST_DENIED,
RSPERR_PROCESSING_ERROR)
|