1#
2# This file is part of pysnmp software.
3#
4# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
5# License: http://snmplabs.com/pysnmp/license.html
6#
7import sys
8import traceback
9from pysnmp.smi.indices import OidOrderedDict
10from pysnmp.smi import exval, error
11from pysnmp.proto import rfc1902
12from pysnmp import cache, debug
13from pyasn1.type import univ
14from pyasn1.error import PyAsn1Error
15
16Integer, ObjectIdentifier = mibBuilder.importSymbols(
17    "ASN1", "Integer", "ObjectIdentifier"
18)
19
20(ConstraintsIntersection, ConstraintsUnion, SingleValueConstraint,
21 ValueRangeConstraint, ValueSizeConstraint) = mibBuilder.importSymbols(
22    "ASN1-REFINEMENT", "ConstraintsIntersection", "ConstraintsUnion",
23    "SingleValueConstraint", "ValueRangeConstraint", "ValueSizeConstraint"
24)
25
26# syntax of objects
27
28OctetString = rfc1902.OctetString
29Bits = rfc1902.Bits
30Integer32 = rfc1902.Integer32
31IpAddress = rfc1902.IpAddress
32Counter32 = rfc1902.Counter32
33Gauge32 = rfc1902.Gauge32
34Unsigned32 = rfc1902.Unsigned32
35TimeTicks = rfc1902.TimeTicks
36Opaque = rfc1902.Opaque
37Counter64 = rfc1902.Counter64
38
39
40class ExtUTCTime(OctetString):
41    subtypeSpec = OctetString.subtypeSpec + ConstraintsUnion(ValueSizeConstraint(11, 11), ValueSizeConstraint(13, 13))
42
43
44# MIB tree foundation class
45
46class MibNode(object):
47    label = ''
48
49    def __init__(self, name):
50        self.name = name
51
52    def __repr__(self):
53        return '%s(%r)' % (self.__class__.__name__, self.name)
54
55    def getName(self):
56        return self.name
57
58    def getLabel(self):
59        return self.label
60
61    def setLabel(self, label):
62        self.label = label
63        return self
64
65    def clone(self, name=None):
66        myClone = self.__class__(self.name)
67        if name is not None:
68            myClone.name = name
69        if self.label is not None:
70            myClone.label = self.label
71        return myClone
72
73
74# definitions for information modules
75
76class ModuleIdentity(MibNode):
77    status = 'current'
78    lastUpdated = ''
79    organization = ''
80    contactInfo = ''
81    description = ''
82    revisions = ()
83    revisionsDescriptions = ()
84
85    def getStatus(self):
86        return self.status
87
88    def setStatus(self, v):
89        self.status = v
90        return self
91
92    def getLastUpdated(self):
93        return self.lastUpdated
94
95    def setLastUpdated(self, v):
96        self.lastUpdated = v
97        return self
98
99    def getOrganization(self):
100        return self.organization
101
102    def setOrganization(self, v):
103        self.organization = v
104        return self
105
106    def getContactInfo(self):
107        return self.contactInfo
108
109    def setContactInfo(self, v):
110        self.contactInfo = v
111        return self
112
113    def getDescription(self):
114        return self.description
115
116    def setDescription(self, v):
117        self.description = v
118        return self
119
120    def getRevisions(self):
121        return self.revisions
122
123    def setRevisions(self, args):
124        self.revisions = args
125        return self
126
127    def getRevisionsDescriptions(self):
128        return self.revisionsDescriptions
129
130    def setRevisionsDescriptions(self, args):
131        self.revisionsDescriptions = args
132        return self
133
134    def asn1Print(self):
135        return """\
136MODULE-IDENTITY
137  LAST-UPDATED %s
138  ORGANIZATION "%s"
139  CONTACT-INFO "%s"
140  DESCRIPTION "%s"
141  %s""" % (self.getLastUpdated(),
142           self.getOrganization(),
143           self.getContactInfo(),
144           self.getDescription(),
145           ''.join(['REVISION "%s"\n' % x for x in self.getRevisions()]))
146
147
148class ObjectIdentity(MibNode):
149    status = 'current'
150    description = ''
151    reference = ''
152
153    def getStatus(self):
154        return self.status
155
156    def setStatus(self, v):
157        self.status = v
158        return self
159
160    def getDescription(self):
161        return self.description
162
163    def setDescription(self, v):
164        self.description = v
165        return self
166
167    def getReference(self):
168        return self.reference
169
170    def setReference(self, v):
171        self.reference = v
172        return self
173
174    def asn1Print(self):
175        return """\
176OBJECT-IDENTITY
177  STATUS %s
178  DESCRIPTION "%s"
179  REFERENCE "%s"
180""" % (self.getStatus(),
181       self.getDescription(),
182       self.getReference())
183
184
185# definition for objects
186
187class NotificationType(MibNode):
188    objects = ()
189    status = 'current'
190    description = ''
191    reference = ''
192    # retained for compatibility
193    revisions = ()
194
195    def getObjects(self):
196        return self.objects
197
198    def setObjects(self, *args, **kwargs):
199        if kwargs.get('append'):
200            self.objects += args
201        else:
202            self.objects = args
203        return self
204
205    def getStatus(self):
206        return self.status
207
208    def setStatus(self, v):
209        self.status = v
210        return self
211
212    def getDescription(self):
213        return self.description
214
215    def setDescription(self, v):
216        self.description = v
217        return self
218
219    def getReference(self):
220        return self.reference
221
222    def setReference(self, v):
223        self.reference = v
224        return self
225
226    # This should not be here. Retained for compatibility.
227
228    def getRevisions(self):
229        return self.revisions
230
231    def setRevisions(self, v):
232        self.revisions = v
233        return self
234
235    def asn1Print(self):
236        return """\
237NOTIFICATION-TYPE
238  OBJECTS { %s }
239  STATUS %s
240  DESCRIPTION "%s"
241  REFERENCE "%s"
242""" % (', '.join([x for x in self.getObjects()]),
243           self.getStatus(),
244           self.getDescription(),
245           self.getReference())
246
247
248class MibIdentifier(MibNode):
249    @staticmethod
250    def asn1Print():
251        return 'OBJECT IDENTIFIER'
252
253
254class ObjectType(MibNode):
255    units = ''
256    maxAccess = 'not-accessible'
257    status = 'current'
258    description = ''
259    reference = ''
260
261    def __init__(self, name, syntax=None):
262        MibNode.__init__(self, name)
263        self.syntax = syntax
264
265    # XXX
266    def __eq__(self, other):
267        return self.syntax == other
268
269    def __ne__(self, other):
270        return self.syntax != other
271
272    def __lt__(self, other):
273        return self.syntax < other
274
275    def __le__(self, other):
276        return self.syntax <= other
277
278    def __gt__(self, other):
279        return self.syntax > other
280
281    def __ge__(self, other):
282        return self.syntax >= other
283
284    def __repr__(self):
285        return '%s(%r, %r)' % (
286            self.__class__.__name__, self.name, self.syntax
287        )
288
289    def getSyntax(self):
290        return self.syntax
291
292    def setSyntax(self, v):
293        self.syntax = v
294        return self
295
296    def getUnits(self):
297        return self.units
298
299    def setUnits(self, v):
300        self.units = v
301        return self
302
303    def getMaxAccess(self):
304        return self.maxAccess
305
306    def setMaxAccess(self, v):
307        self.maxAccess = v
308        return self
309
310    def getStatus(self):
311        return self.status
312
313    def setStatus(self, v):
314        self.status = v
315        return self
316
317    def getDescription(self):
318        return self.description
319
320    def setDescription(self, v):
321        self.description = v
322        return self
323
324    def getReference(self):
325        return self.reference
326
327    def setReference(self, v):
328        self.reference = v
329        return self
330
331    def asn1Print(self):
332        return """
333OBJECT-TYPE
334  SYNTAX %s
335  UNITS "%s"
336  MAX-ACCESS %s
337  STATUS %s
338  DESCRIPTION "%s"
339  REFERENCE "%s" """ % (self.getSyntax().__class__.__name__,
340                        self.getUnits(),
341                        self.getMaxAccess(),
342                        self.getStatus(),
343                        self.getDescription(),
344                        self.getReference())
345
346
347class MibTree(ObjectType):
348    branchVersionId = 0  # cnanges on tree structure change
349    maxAccess = 'not-accessible'
350
351    def __init__(self, name, syntax=None):
352        ObjectType.__init__(self, name, syntax)
353        self._vars = OidOrderedDict()
354
355    # Subtrees registration
356
357    def registerSubtrees(self, *subTrees):
358        self.branchVersionId += 1
359        for subTree in subTrees:
360            if subTree.name in self._vars:
361                raise error.SmiError(
362                    'MIB subtree %s already registered at %s' % (subTree.name, self)
363                )
364            self._vars[subTree.name] = subTree
365
366    def unregisterSubtrees(self, *names):
367        self.branchVersionId += 1
368        for name in names:
369            # This may fail if you fill a table by exporting MibScalarInstances
370            # but later drop them through SNMP.
371            if name not in self._vars:
372                raise error.SmiError(
373                    'MIB subtree %s not registered at %s' % (name, self)
374                )
375            del self._vars[name]
376
377    #
378    # Tree traversal
379    #
380    # Missing branches are indicated by the NoSuchObjectError exception.
381    # Although subtrees may indicate their missing branches by the
382    # NoSuchInstanceError exception.
383    #
384
385    def getBranch(self, name, idx):
386        """Return a branch of this tree where the 'name' OID may reside"""
387        for keyLen in self._vars.getKeysLens():
388            subName = name[:keyLen]
389            if subName in self._vars:
390                return self._vars[subName]
391
392        raise error.NoSuchObjectError(name=name, idx=idx)
393
394    def getNextBranch(self, name, idx=None):
395        # Start from the beginning
396        if self._vars:
397            first = list(self._vars.keys())[0]
398        else:
399            first = ()
400        if self._vars and name < first:
401            return self._vars[first]
402        else:
403            try:
404                return self._vars[self._vars.nextKey(name)]
405            except KeyError:
406                raise error.NoSuchObjectError(idx=idx, name=name)
407
408    def getNode(self, name, idx=None):
409        """Return tree node found by name"""
410        if name == self.name:
411            return self
412        else:
413            return self.getBranch(name, idx).getNode(name, idx)
414
415    def getNextNode(self, name, idx=None):
416        """Return tree node next to name"""
417        try:
418            nextNode = self.getBranch(name, idx)
419        except (error.NoSuchInstanceError, error.NoSuchObjectError):
420            return self.getNextBranch(name, idx)
421        else:
422            try:
423                return nextNode.getNextNode(name, idx)
424            except (error.NoSuchInstanceError, error.NoSuchObjectError):
425                try:
426                    return self._vars[self._vars.nextKey(nextNode.name)]
427                except KeyError:
428                    raise error.NoSuchObjectError(idx=idx, name=name)
429
430    # MIB instrumentation
431
432    # Read operation
433
434    def readTest(self, name, val, idx, acInfo):
435        (acFun, acCtx) = acInfo
436        if name == self.name:
437            if acFun:
438                if self.maxAccess not in ('readonly',
439                                          'readwrite', 'readcreate') or \
440                        acFun(name, self.syntax, idx, 'read', acCtx):
441                    raise error.NoAccessError(idx=idx, name=name)
442        else:
443            try:
444                node = self.getBranch(name, idx)
445            except (error.NoSuchInstanceError, error.NoSuchObjectError):
446                return  # missing object is not an error here
447            else:
448                node.readTest(name, val, idx, acInfo)
449
450    def readGet(self, name, val, idx, acInfo):
451        try:
452            node = self.getBranch(name, idx)
453        except (error.NoSuchInstanceError, error.NoSuchObjectError):
454            return name, exval.noSuchObject
455        else:
456            return node.readGet(name, val, idx, acInfo)
457
458    # Read next operation is subtree-specific
459
460    depthFirst, breadthFirst = 0, 1
461
462    def readTestNext(self, name, val, idx, acInfo, oName=None):
463        if oName is None:
464            oName = name
465            topOfTheMib = True
466        else:
467            topOfTheMib = False
468        nextName = name
469        direction = self.depthFirst
470        while 1:  # XXX linear search here
471            if direction == self.depthFirst:
472                direction = self.breadthFirst
473                try:
474                    node = self.getBranch(nextName, idx)
475                except (error.NoSuchInstanceError, error.NoSuchObjectError):
476                    continue
477            else:
478                try:
479                    node = self.getNextBranch(nextName, idx)
480                except (error.NoSuchInstanceError, error.NoSuchObjectError):
481                    if topOfTheMib:
482                        return
483                    raise
484                direction = self.depthFirst
485                nextName = node.name
486            try:
487                return node.readTestNext(nextName, val, idx, acInfo, oName)
488            except (error.NoAccessError, error.NoSuchInstanceError, error.NoSuchObjectError):
489                pass
490
491    def readGetNext(self, name, val, idx, acInfo, oName=None):
492        if oName is None:
493            oName = name
494            topOfTheMib = True
495        else:
496            topOfTheMib = False
497        nextName = name
498        direction = self.depthFirst
499        while True:  # XXX linear search ahead!
500            if direction == self.depthFirst:
501                direction = self.breadthFirst
502                try:
503                    node = self.getBranch(nextName, idx)
504                except (error.NoSuchInstanceError, error.NoSuchObjectError):
505                    continue
506            else:
507                try:
508                    node = self.getNextBranch(nextName, idx)
509                except (error.NoSuchInstanceError, error.NoSuchObjectError):
510                    if topOfTheMib:
511                        return name, exval.endOfMib
512                    raise
513                direction = self.depthFirst
514                nextName = node.name
515            try:
516                return node.readGetNext(nextName, val, idx, acInfo, oName)
517            except (error.NoAccessError, error.NoSuchInstanceError, error.NoSuchObjectError):
518                pass
519
520    # Write operation
521
522    def writeTest(self, name, val, idx, acInfo):
523        acFun, acCtx = acInfo
524        if name == self.name:
525            # Make sure variable is writable
526            if acFun:
527                if self.maxAccess not in ('readwrite', 'readcreate') or \
528                        acFun(name, self.syntax, idx, 'write', acCtx):
529                    raise error.NotWritableError(idx=idx, name=name)
530        else:
531            node = self.getBranch(name, idx)
532            node.writeTest(name, val, idx, acInfo)
533
534    def writeCommit(self, name, val, idx, acInfo):
535        self.getBranch(name, idx).writeCommit(name, val, idx, acInfo)
536
537    def writeCleanup(self, name, val, idx, acInfo):
538        self.branchVersionId += 1
539        self.getBranch(name, idx).writeCleanup(name, val, idx, acInfo)
540
541    def writeUndo(self, name, val, idx, acInfo):
542        self.getBranch(name, idx).writeUndo(name, val, idx, acInfo)
543
544
545class MibScalar(MibTree):
546    """Scalar MIB variable. Implements access control checking."""
547    maxAccess = 'readonly'
548
549    #
550    # Subtree traversal
551    #
552    # Missing branches are indicated by the NoSuchInstanceError exception.
553    #
554
555    def getBranch(self, name, idx):
556        try:
557            return MibTree.getBranch(self, name, idx)
558        except (error.NoSuchInstanceError, error.NoSuchObjectError):
559            raise error.NoSuchInstanceError(idx=idx, name=name)
560
561    def getNextBranch(self, name, idx=None):
562        try:
563            return MibTree.getNextBranch(self, name, idx)
564        except (error.NoSuchInstanceError, error.NoSuchObjectError):
565            raise error.NoSuchInstanceError(idx=idx, name=name)
566
567    def getNode(self, name, idx=None):
568        try:
569            return MibTree.getNode(self, name, idx)
570        except (error.NoSuchInstanceError, error.NoSuchObjectError):
571            raise error.NoSuchInstanceError(idx=idx, name=name)
572
573    def getNextNode(self, name, idx=None):
574        try:
575            return MibTree.getNextNode(self, name, idx)
576        except (error.NoSuchInstanceError, error.NoSuchObjectError):
577            raise error.NoSuchInstanceError(idx=idx, name=name)
578
579    # MIB instrumentation methods
580
581    # Read operation
582
583    def readTest(self, name, val, idx, acInfo):
584        (acFun, acCtx) = acInfo
585        if name == self.name:
586            raise error.NoAccessError(idx=idx, name=name)
587        if acFun:
588            if self.maxAccess not in ('readonly', 'readwrite',
589                                      'readcreate') or \
590                    acFun(name, self.syntax, idx, 'read', acCtx):
591                raise error.NoAccessError(idx=idx, name=name)
592        MibTree.readTest(self, name, val, idx, acInfo)
593
594    def readGet(self, name, val, idx, acInfo):
595        try:
596            node = self.getBranch(name, idx)
597        except error.NoSuchInstanceError:
598            return name, exval.noSuchInstance
599        else:
600            return node.readGet(name, val, idx, acInfo)
601
602    def readTestNext(self, name, val, idx, acInfo, oName=None):
603        (acFun, acCtx) = acInfo
604        if acFun:
605            if self.maxAccess not in ('readonly', 'readwrite',
606                                      'readcreate') or \
607                    acFun(name, self.syntax, idx, 'read', acCtx):
608                raise error.NoAccessError(idx=idx, name=name)
609        MibTree.readTestNext(self, name, val, idx, acInfo, oName)
610
611    def readGetNext(self, name, val, idx, acInfo, oName=None):
612        (acFun, acCtx) = acInfo
613        # have to duplicate AC here as *Next code above treats
614        # noAccess as a noSuchObject at the Test stage, goes on
615        # to Reading
616        if acFun:
617            if self.maxAccess not in ('readonly', 'readwrite',
618                                      'readcreate') or \
619                    acFun(name, self.syntax, idx, 'read', acCtx):
620                raise error.NoAccessError(idx=idx, name=name)
621        return MibTree.readGetNext(self, name, val, idx, acInfo, oName)
622
623    # Two-phase commit implementation
624
625    def writeTest(self, name, val, idx, acInfo):
626        acFun, acCtx = acInfo
627        if name == self.name:
628            raise error.NoAccessError(idx=idx, name=name)
629        if acFun:
630            if self.maxAccess not in ('readwrite', 'readcreate') or \
631                    acFun(name, self.syntax, idx, 'write', acCtx):
632                raise error.NotWritableError(idx=idx, name=name)
633        MibTree.writeTest(self, name, val, idx, acInfo)
634
635
636class MibScalarInstance(MibTree):
637    """Scalar MIB variable instance. Implements read/write operations."""
638
639    def __init__(self, typeName, instId, syntax):
640        MibTree.__init__(self, typeName + instId, syntax)
641        self.typeName = typeName
642        self.instId = instId
643        self.__oldSyntax = None
644
645    #
646    # Managed object value access methods
647    #
648
649    # noinspection PyUnusedLocal
650    def getValue(self, name, idx):
651        debug.logger & debug.flagIns and debug.logger('getValue: returning %r for %s' % (self.syntax, self.name))
652        return self.syntax.clone()
653
654    def setValue(self, value, name, idx):
655        if value is None:
656            value = univ.noValue
657        try:
658            if hasattr(self.syntax, 'setValue'):
659                return self.syntax.setValue(value)
660            else:
661                return self.syntax.clone(value)
662        except PyAsn1Error:
663            exc_t, exc_v, exc_tb = sys.exc_info()
664            debug.logger & debug.flagIns and debug.logger('setValue: %s=%r failed with traceback %s' % (
665                self.name, value, traceback.format_exception(exc_t, exc_v, exc_tb)))
666            if isinstance(exc_v, error.TableRowManagement):
667                raise exc_v
668            else:
669                raise error.WrongValueError(idx=idx, name=name, msg=exc_v)
670
671    #
672    # Subtree traversal
673    #
674    # Missing branches are indicated by the NoSuchInstanceError exception.
675    #
676
677    def getBranch(self, name, idx):
678        try:
679            return MibTree.getBranch(self, name, idx)
680        except (error.NoSuchInstanceError, error.NoSuchObjectError):
681            raise error.NoSuchInstanceError(idx=idx, name=name)
682
683    def getNextBranch(self, name, idx=None):
684        try:
685            return MibTree.getNextBranch(self, name, idx)
686        except (error.NoSuchInstanceError, error.NoSuchObjectError):
687            raise error.NoSuchInstanceError(idx=idx, name=name)
688
689    def getNode(self, name, idx=None):
690        # Recursion terminator
691        if name == self.name:
692            return self
693        raise error.NoSuchInstanceError(idx=idx, name=name)
694
695    def getNextNode(self, name, idx=None):
696        raise error.NoSuchInstanceError(idx=idx, name=name)
697
698    # MIB instrumentation methods
699
700    # Read operation
701
702    def readTest(self, name, val, idx, acInfo):
703        if name != self.name:
704            raise error.NoSuchInstanceError(idx=idx, name=name)
705
706    def readGet(self, name, val, idx, acInfo):
707        # Return current variable (name, value)
708        if name == self.name:
709            debug.logger & debug.flagIns and debug.logger('readGet: %s=%r' % (self.name, self.syntax))
710            return self.name, self.getValue(name, idx)
711        else:
712            raise error.NoSuchInstanceError(idx=idx, name=name)
713
714    def readTestNext(self, name, val, idx, acInfo, oName=None):
715        if name != self.name or name <= oName:
716            raise error.NoSuchInstanceError(idx=idx, name=name)
717
718    def readGetNext(self, name, val, idx, acInfo, oName=None):
719        if name == self.name and name > oName:
720            debug.logger & debug.flagIns and debug.logger('readGetNext: %s=%r' % (self.name, self.syntax))
721            return self.readGet(name, val, idx, acInfo)
722        else:
723            raise error.NoSuchInstanceError(idx=idx, name=name)
724
725    # Write operation: two-phase commit
726
727    # noinspection PyAttributeOutsideInit
728    def writeTest(self, name, val, idx, acInfo):
729        # Make sure write's allowed
730        if name == self.name:
731            try:
732                self.__newSyntax = self.setValue(val, name, idx)
733            except error.MibOperationError:
734                # SMI exceptions may carry additional content
735                why = sys.exc_info()[1]
736                if 'syntax' in why:
737                    self.__newSyntax = why['syntax']
738                    raise why
739                else:
740                    raise error.WrongValueError(idx=idx, name=name, msg=sys.exc_info()[1])
741        else:
742            raise error.NoSuchInstanceError(idx=idx, name=name)
743
744    def writeCommit(self, name, val, idx, acInfo):
745        # Backup original value
746        if self.__oldSyntax is None:
747            self.__oldSyntax = self.syntax
748        # Commit new value
749        self.syntax = self.__newSyntax
750
751    # noinspection PyAttributeOutsideInit
752    def writeCleanup(self, name, val, idx, acInfo):
753        self.branchVersionId += 1
754        debug.logger & debug.flagIns and debug.logger('writeCleanup: %s=%r' % (name, val))
755        # Drop previous value
756        self.__newSyntax = self.__oldSyntax = None
757
758    # noinspection PyAttributeOutsideInit
759    def writeUndo(self, name, val, idx, acInfo):
760        # Revive previous value
761        self.syntax = self.__oldSyntax
762        self.__newSyntax = self.__oldSyntax = None
763
764    # Table column instance specifics
765
766    # Create operation
767
768    # noinspection PyUnusedLocal,PyAttributeOutsideInit
769    def createTest(self, name, val, idx, acInfo):
770        if name == self.name:
771            try:
772                self.__newSyntax = self.setValue(val, name, idx)
773            except error.MibOperationError:
774                # SMI exceptions may carry additional content
775                why = sys.exc_info()[1]
776                if 'syntax' in why:
777                    self.__newSyntax = why['syntax']
778                else:
779                    raise error.WrongValueError(idx=idx, name=name, msg=sys.exc_info()[1])
780        else:
781            raise error.NoSuchInstanceError(idx=idx, name=name)
782
783    def createCommit(self, name, val, idx, acInfo):
784        if val is not None:
785            self.writeCommit(name, val, idx, acInfo)
786
787    def createCleanup(self, name, val, idx, acInfo):
788        self.branchVersionId += 1
789        debug.logger & debug.flagIns and debug.logger('createCleanup: %s=%r' % (name, val))
790        if val is not None:
791            self.writeCleanup(name, val, idx, acInfo)
792
793    def createUndo(self, name, val, idx, acInfo):
794        if val is not None:
795            self.writeUndo(name, val, idx, acInfo)
796
797    # Destroy operation
798
799    # noinspection PyUnusedLocal,PyAttributeOutsideInit
800    def destroyTest(self, name, val, idx, acInfo):
801        if name == self.name:
802            try:
803                self.__newSyntax = self.setValue(val, name, idx)
804            except error.MibOperationError:
805                # SMI exceptions may carry additional content
806                why = sys.exc_info()[1]
807                if 'syntax' in why:
808                    self.__newSyntax = why['syntax']
809        else:
810            raise error.NoSuchInstanceError(idx=idx, name=name)
811
812    def destroyCommit(self, name, val, idx, acInfo):
813        pass
814
815    # noinspection PyUnusedLocal
816    def destroyCleanup(self, name, val, idx, acInfo):
817        self.branchVersionId += 1
818
819    def destroyUndo(self, name, val, idx, acInfo):
820        pass
821
822
823# Conceptual table classes
824
825class MibTableColumn(MibScalar):
826    """MIB table column. Manages a set of column instance variables"""
827    protoInstance = MibScalarInstance
828
829    def __init__(self, name, syntax):
830        MibScalar.__init__(self, name, syntax)
831        self.__createdInstances = {}
832        self.__destroyedInstances = {}
833        self.__rowOpWanted = {}
834
835    #
836    # Subtree traversal
837    #
838    # Missing leaves are indicated by the NoSuchInstanceError exception.
839    #
840
841    def getBranch(self, name, idx):
842        if name in self._vars:
843            return self._vars[name]
844        raise error.NoSuchInstanceError(name=name, idx=idx)
845
846    def setProtoInstance(self, protoInstance):
847        self.protoInstance = protoInstance
848
849    # Column creation (this should probably be converted into some state
850    # machine for clarity). Also, it might be a good idea to inidicate
851    # defaulted cols creation in a clearer way than just a val == None.
852
853    def createTest(self, name, val, idx, acInfo):
854        (acFun, acCtx) = acInfo
855        # Make sure creation allowed, create a new column instance but
856        # do not replace the old one
857        if name == self.name:
858            raise error.NoAccessError(idx=idx, name=name)
859        if acFun:
860            if val is not None and self.maxAccess != 'readcreate' or \
861                    acFun(name, self.syntax, idx, 'write', acCtx):
862                debug.logger & debug.flagACL and debug.logger(
863                    'createTest: %s=%r %s at %s' % (name, val, self.maxAccess, self.name))
864                raise error.NoCreationError(idx=idx, name=name)
865        # Create instances if either it does not yet exist (row creation)
866        # or a value is passed (multiple OIDs in SET PDU)
867        if val is None and name in self.__createdInstances:
868            return
869        self.__createdInstances[name] = self.protoInstance(
870            self.name, name[len(self.name):], self.syntax.clone()
871        )
872        self.__createdInstances[name].createTest(name, val, idx, acInfo)
873
874    def createCommit(self, name, val, idx, acInfo):
875        # Commit new instance value
876        if name in self._vars:  # XXX
877            if name in self.__createdInstances:
878                self._vars[name].createCommit(name, val, idx, acInfo)
879            return
880        self.__createdInstances[name].createCommit(name, val, idx, acInfo)
881        # ...commit new column instance
882        self._vars[name], self.__createdInstances[name] = \
883            self.__createdInstances[name], self._vars.get(name)
884
885    def createCleanup(self, name, val, idx, acInfo):
886        # Drop previous column instance
887        self.branchVersionId += 1
888        if name in self.__createdInstances:
889            if self.__createdInstances[name] is not None:
890                self.__createdInstances[name].createCleanup(name, val, idx,
891                                                            acInfo)
892            del self.__createdInstances[name]
893        elif name in self._vars:
894            self._vars[name].createCleanup(name, val, idx, acInfo)
895
896    def createUndo(self, name, val, idx, acInfo):
897        # Set back previous column instance, drop the new one
898        if name in self.__createdInstances:
899            self._vars[name] = self.__createdInstances[name]
900            del self.__createdInstances[name]
901            # Remove new instance on rollback
902            if self._vars[name] is None:
903                del self._vars[name]
904            else:
905                # Catch half-created instances (hackerish)
906                try:
907                    self._vars[name] == 0
908                except PyAsn1Error:
909                    del self._vars[name]
910                else:
911                    self._vars[name].createUndo(name, val, idx, acInfo)
912
913    # Column destruction
914
915    def destroyTest(self, name, val, idx, acInfo):
916        (acFun, acCtx) = acInfo
917        # Make sure destruction is allowed
918        if name == self.name:
919            raise error.NoAccessError(idx=idx, name=name)
920        if name not in self._vars:
921            return
922        if acFun:
923            if val is not None and self.maxAccess != 'readcreate' or \
924                    acFun(name, self.syntax, idx, 'write', acCtx):
925                raise error.NoAccessError(idx=idx, name=name)
926        self._vars[name].destroyTest(name, val, idx, acInfo)
927
928    def destroyCommit(self, name, val, idx, acInfo):
929        # Make a copy of column instance and take it off the tree
930        if name in self._vars:
931            self._vars[name].destroyCommit(name, val, idx, acInfo)
932            self.__destroyedInstances[name] = self._vars[name]
933            del self._vars[name]
934
935    def destroyCleanup(self, name, val, idx, acInfo):
936        # Drop instance copy
937        self.branchVersionId += 1
938        if name in self.__destroyedInstances:
939            self.__destroyedInstances[name].destroyCleanup(name, val,
940                                                           idx, acInfo)
941            debug.logger & debug.flagIns and debug.logger('destroyCleanup: %s=%r' % (name, val))
942            del self.__destroyedInstances[name]
943
944    def destroyUndo(self, name, val, idx, acInfo):
945        # Set back column instance
946        if name in self.__destroyedInstances:
947            self._vars[name] = self.__destroyedInstances[name]
948            self._vars[name].destroyUndo(name, val, idx, acInfo)
949            del self.__destroyedInstances[name]
950
951    # Set/modify column
952
953    def writeTest(self, name, val, idx, acInfo):
954        # Besides common checks, request row creation on no-instance
955        try:
956            # First try the instance
957            MibScalar.writeTest(self, name, val, idx, acInfo)
958        # ...otherwise proceed with creating new column
959        except (error.NoSuchInstanceError, error.RowCreationWanted):
960            excValue = sys.exc_info()[1]
961            if isinstance(excValue, error.RowCreationWanted):
962                self.__rowOpWanted[name] = excValue
963            else:
964                self.__rowOpWanted[name] = error.RowCreationWanted()
965            self.createTest(name, val, idx, acInfo)
966        except error.RowDestructionWanted:
967            self.__rowOpWanted[name] = error.RowDestructionWanted()
968            self.destroyTest(name, val, idx, acInfo)
969        if name in self.__rowOpWanted:
970            debug.logger & debug.flagIns and debug.logger(
971                '%s flagged by %s=%r, exception %s' % (self.__rowOpWanted[name], name, val, sys.exc_info()[1]))
972            raise self.__rowOpWanted[name]
973
974    def __delegateWrite(self, subAction, name, val, idx, acInfo):
975        if name not in self.__rowOpWanted:
976            getattr(MibScalar, 'write' + subAction)(self, name, val, idx, acInfo)
977            return
978        if isinstance(self.__rowOpWanted[name], error.RowCreationWanted):
979            getattr(self, 'create' + subAction)(name, val, idx, acInfo)
980        if isinstance(self.__rowOpWanted[name], error.RowDestructionWanted):
981            getattr(self, 'destroy' + subAction)(name, val, idx, acInfo)
982
983    def writeCommit(self, name, val, idx, acInfo):
984        self.__delegateWrite('Commit', name, val, idx, acInfo)
985        if name in self.__rowOpWanted:
986            raise self.__rowOpWanted[name]
987
988    def writeCleanup(self, name, val, idx, acInfo):
989        self.branchVersionId += 1
990        self.__delegateWrite('Cleanup', name, val, idx, acInfo)
991        if name in self.__rowOpWanted:
992            e = self.__rowOpWanted[name]
993            del self.__rowOpWanted[name]
994            debug.logger & debug.flagIns and debug.logger('%s dropped by %s=%r' % (e, name, val))
995            raise e
996
997    def writeUndo(self, name, val, idx, acInfo):
998        if name in self.__rowOpWanted:
999            self.__rowOpWanted[name] = error.RowDestructionWanted()
1000        self.__delegateWrite('Undo', name, val, idx, acInfo)
1001        if name in self.__rowOpWanted:
1002            e = self.__rowOpWanted[name]
1003            del self.__rowOpWanted[name]
1004            debug.logger & debug.flagIns and debug.logger('%s dropped by %s=%r' % (e, name, val))
1005            raise e
1006
1007
1008class MibTableRow(MibTree):
1009    """MIB table row (SMI 'Entry'). Manages a set of table columns.
1010       Implements row creation/destruction.
1011    """
1012
1013    def __init__(self, name):
1014        MibTree.__init__(self, name)
1015        self.__idToIdxCache = cache.Cache()
1016        self.__idxToIdCache = cache.Cache()
1017        self.indexNames = ()
1018        self.augmentingRows = {}
1019
1020    # Table indices resolution. Handle almost all possible rfc1902 types
1021    # explicitly rather than by means of isSuperTypeOf() method because
1022    # some subtypes may be implicitly tagged what renders base tag
1023    # unavailable.
1024
1025    __intBaseTag = Integer.tagSet.getBaseTag()
1026    __strBaseTag = OctetString.tagSet.getBaseTag()
1027    __oidBaseTag = ObjectIdentifier.tagSet.getBaseTag()
1028    __ipaddrTagSet = IpAddress.tagSet
1029    __bitsBaseTag = Bits.tagSet.getBaseTag()
1030
1031    def setFromName(self, obj, value, impliedFlag=None, parentIndices=None):
1032        if not value:
1033            raise error.SmiError('Short OID for index %r' % (obj,))
1034        if hasattr(obj, 'cloneFromName'):
1035            return obj.cloneFromName(value, impliedFlag, parentRow=self, parentIndices=parentIndices)
1036        baseTag = obj.getTagSet().getBaseTag()
1037        if baseTag == self.__intBaseTag:
1038            return obj.clone(value[0]), value[1:]
1039        elif self.__ipaddrTagSet.isSuperTagSetOf(obj.getTagSet()):
1040            return obj.clone('.'.join([str(x) for x in value[:4]])), value[4:]
1041        elif baseTag == self.__strBaseTag:
1042            # rfc1902, 7.7
1043            if impliedFlag:
1044                return obj.clone(tuple(value)), ()
1045            elif obj.isFixedLength():
1046                l = obj.getFixedLength()
1047                return obj.clone(tuple(value[:l])), value[l:]
1048            else:
1049                return obj.clone(tuple(value[1:value[0] + 1])), value[value[0] + 1:]
1050        elif baseTag == self.__oidBaseTag:
1051            if impliedFlag:
1052                return obj.clone(value), ()
1053            else:
1054                return obj.clone(value[1:value[0] + 1]), value[value[0] + 1:]
1055        # rfc2578, 7.1
1056        elif baseTag == self.__bitsBaseTag:
1057            return obj.clone(tuple(value[1:value[0] + 1])), value[value[0] + 1:]
1058        else:
1059            raise error.SmiError('Unknown value type for index %r' % (obj,))
1060
1061    def getAsName(self, obj, impliedFlag=None, parentIndices=None):
1062        if hasattr(obj, 'cloneAsName'):
1063            return obj.cloneAsName(impliedFlag, parentRow=self, parentIndices=parentIndices)
1064        baseTag = obj.getTagSet().getBaseTag()
1065        if baseTag == self.__intBaseTag:
1066            # noinspection PyRedundantParentheses
1067            return (int(obj),)
1068        elif self.__ipaddrTagSet.isSuperTagSetOf(obj.getTagSet()):
1069            return obj.asNumbers()
1070        elif baseTag == self.__strBaseTag:
1071            if impliedFlag or obj.isFixedLength():
1072                initial = ()
1073            else:
1074                initial = (len(obj),)
1075            return initial + obj.asNumbers()
1076        elif baseTag == self.__oidBaseTag:
1077            if impliedFlag:
1078                return tuple(obj)
1079            else:
1080                return (len(obj),) + tuple(obj)
1081        # rfc2578, 7.1
1082        elif baseTag == self.__bitsBaseTag:
1083            return (len(obj),) + obj.asNumbers()
1084        else:
1085            raise error.SmiError('Unknown value type for index %r' % (obj,))
1086
1087    # Fate sharing mechanics
1088
1089    def announceManagementEvent(self, action, name, val, idx, acInfo):
1090        # Convert OID suffix into index vals
1091        instId = name[len(self.name) + 1:]
1092        baseIndices = []
1093        indices = []
1094        for impliedFlag, modName, symName in self.indexNames:
1095            mibObj, = mibBuilder.importSymbols(modName, symName)
1096            syntax, instId = self.setFromName(mibObj.syntax, instId,
1097                                              impliedFlag, indices)
1098
1099            if self.name == mibObj.name[:-1]:
1100                baseIndices.append((mibObj.name, syntax))
1101
1102            indices.append(syntax)
1103
1104        if instId:
1105            raise error.SmiError('Excessive instance identifier sub-OIDs left at %s: %s' % (self, instId))
1106
1107        if not baseIndices:
1108            return
1109
1110        for modName, mibSym in self.augmentingRows.keys():
1111            mibObj, = mibBuilder.importSymbols(modName, mibSym)
1112            debug.logger & debug.flagIns and debug.logger('announceManagementEvent %s to %s' % (action, mibObj))
1113            mibObj.receiveManagementEvent(
1114                action, baseIndices, val, idx, acInfo
1115            )
1116
1117    def receiveManagementEvent(self, action, baseIndices, val, idx, acInfo):
1118        # The default implementation supports one-to-one rows dependency
1119        newSuffix = ()
1120        # Resolve indices intersection
1121        for impliedFlag, modName, symName in self.indexNames:
1122            mibObj, = mibBuilder.importSymbols(modName, symName)
1123            parentIndices = []
1124            for name, syntax in baseIndices:
1125                if name == mibObj.name:
1126                    newSuffix += self.getAsName(syntax, impliedFlag, parentIndices)
1127                parentIndices.append(syntax)
1128
1129        if newSuffix:
1130            debug.logger & debug.flagIns and debug.logger(
1131                'receiveManagementEvent %s for suffix %s' % (action, newSuffix))
1132            self.__manageColumns(action, (), newSuffix, val, idx, acInfo)
1133
1134    def registerAugmentions(self, *names):
1135        for modName, symName in names:
1136            if (modName, symName) in self.augmentingRows:
1137                raise error.SmiError(
1138                    'Row %s already augmented by %s::%s' % (self.name, modName, symName)
1139                )
1140            self.augmentingRows[(modName, symName)] = 1
1141        return self
1142
1143    def setIndexNames(self, *names):
1144        for name in names:
1145            self.indexNames += (name,)
1146        return self
1147
1148    def getIndexNames(self):
1149        return self.indexNames
1150
1151    def __manageColumns(self, action, excludeName, nameSuffix,
1152                        val, idx, acInfo):
1153        # Build a map of index names and values for automatic initialization
1154        indexVals = {}
1155        instId = nameSuffix
1156        indices = []
1157        for impliedFlag, modName, symName in self.indexNames:
1158            mibObj, = mibBuilder.importSymbols(modName, symName)
1159            syntax, instId = self.setFromName(mibObj.syntax, instId,
1160                                              impliedFlag, indices)
1161            indexVals[mibObj.name] = syntax
1162            indices.append(syntax)
1163
1164        for name, var in self._vars.items():
1165            if name == excludeName:
1166                continue
1167
1168            if name in indexVals:
1169                getattr(var, action)(name + nameSuffix, indexVals[name], idx,
1170                                     (None, None))
1171            else:
1172                getattr(var, action)(name + nameSuffix, val, idx, acInfo)
1173
1174            debug.logger & debug.flagIns and debug.logger('__manageColumns: action %s name %s suffix %s %svalue %r' % (
1175                action, name, nameSuffix, name in indexVals and "index " or "", indexVals.get(name, val)))
1176
1177    def __delegate(self, subAction, name, val, idx, acInfo):
1178        # Relay operation request to column, expect row operation request.
1179        rowIsActive = False
1180        try:
1181            getattr(self.getBranch(name, idx), 'write' + subAction)(
1182                name, val, idx, acInfo
1183            )
1184
1185        except error.RowCreationWanted:
1186            self.__manageColumns(
1187                'create' + subAction, name[:len(self.name) + 1],
1188                name[len(self.name) + 1:], None, idx, acInfo
1189            )
1190
1191            self.announceManagementEvent(
1192                'create' + subAction, name, None, idx, acInfo
1193            )
1194
1195            # watch for RowStatus == 'stActive'
1196            rowIsActive = sys.exc_info()[1].get('syntax', 0) == 1
1197
1198        except error.RowDestructionWanted:
1199            self.__manageColumns(
1200                'destroy' + subAction, name[:len(self.name) + 1],
1201                name[len(self.name) + 1:], None, idx, acInfo
1202            )
1203
1204            self.announceManagementEvent(
1205                'destroy' + subAction, name, None, idx, acInfo
1206            )
1207
1208        return rowIsActive
1209
1210    def writeTest(self, name, val, idx, acInfo):
1211        self.__delegate('Test', name, val, idx, acInfo)
1212
1213    def writeCommit(self, name, val, idx, acInfo):
1214        rowIsActive = self.__delegate('Commit', name, val, idx, acInfo)
1215        if rowIsActive:
1216            for mibNode in self._vars.values():
1217                colNode = mibNode.getNode(mibNode.name + name[len(self.name) + 1:])
1218                if not colNode.syntax.hasValue():
1219                    raise error.InconsistentValueError(msg='Row consistency check failed for %r' % colNode)
1220
1221    def writeCleanup(self, name, val, idx, acInfo):
1222        self.branchVersionId += 1
1223        self.__delegate('Cleanup', name, val, idx, acInfo)
1224
1225    def writeUndo(self, name, val, idx, acInfo):
1226        self.__delegate('Undo', name, val, idx, acInfo)
1227
1228    # Table row management
1229
1230    # Table row access by instance name
1231
1232    def getInstName(self, colId, instId):
1233        return self.name + (colId,) + instId
1234
1235    # Table index management
1236
1237    def getIndicesFromInstId(self, instId):
1238        """Return index values for instance identification"""
1239        if instId in self.__idToIdxCache:
1240            return self.__idToIdxCache[instId]
1241
1242        indices = []
1243        for impliedFlag, modName, symName in self.indexNames:
1244            mibObj, = mibBuilder.importSymbols(modName, symName)
1245            try:
1246                syntax, instId = self.setFromName(mibObj.syntax, instId, impliedFlag, indices)
1247            except PyAsn1Error:
1248                debug.logger & debug.flagIns and debug.logger('error resolving table indices at %s, %s: %s' % (self.__class__.__name__, instId, sys.exc_info()[1]))
1249                indices = [instId]
1250                instId = ()
1251                break
1252
1253            indices.append(syntax)  # to avoid cyclic refs
1254
1255        if instId:
1256            raise error.SmiError(
1257                'Excessive instance identifier sub-OIDs left at %s: %s' %
1258                (self, instId)
1259            )
1260
1261        indices = tuple(indices)
1262        self.__idToIdxCache[instId] = indices
1263
1264        return indices
1265
1266    def getInstIdFromIndices(self, *indices):
1267        """Return column instance identification from indices"""
1268        try:
1269            return self.__idxToIdCache[indices]
1270        except TypeError:
1271            cacheable = False
1272        except KeyError:
1273            cacheable = True
1274        idx = 0
1275        instId = ()
1276        parentIndices = []
1277        for impliedFlag, modName, symName in self.indexNames:
1278            if idx >= len(indices):
1279                break
1280            mibObj, = mibBuilder.importSymbols(modName, symName)
1281            syntax = mibObj.syntax.clone(indices[idx])
1282            instId += self.getAsName(syntax, impliedFlag, parentIndices)
1283            parentIndices.append(syntax)
1284            idx += 1
1285        if cacheable:
1286            self.__idxToIdCache[indices] = instId
1287        return instId
1288
1289    # Table access by index
1290
1291    def getInstNameByIndex(self, colId, *indices):
1292        """Build column instance name from components"""
1293        return self.name + (colId,) + self.getInstIdFromIndices(*indices)
1294
1295    def getInstNamesByIndex(self, *indices):
1296        """Build column instance names from indices"""
1297        instNames = []
1298        for columnName in self._vars.keys():
1299            instNames.append(
1300                self.getInstNameByIndex(*(columnName[-1],) + indices)
1301            )
1302
1303        return tuple(instNames)
1304
1305
1306class MibTable(MibTree):
1307    """MIB table. Manages a set of TableRow's"""
1308
1309    def __init__(self, name):
1310        MibTree.__init__(self, name)
1311
1312
1313zeroDotZero = ObjectIdentity((0, 0))
1314
1315# OID tree
1316itu_t = MibTree((0,)).setLabel('itu-t')
1317iso = MibTree((1,))
1318joint_iso_itu_t = MibTree((2,)).setLabel('joint-iso-itu-t')
1319org = MibIdentifier(iso.name + (3,))
1320dod = MibIdentifier(org.name + (6,))
1321internet = MibIdentifier(dod.name + (1,))
1322directory = MibIdentifier(internet.name + (1,))
1323mgmt = MibIdentifier(internet.name + (2,))
1324mib_2 = MibIdentifier(mgmt.name + (1,)).setLabel('mib-2')
1325transmission = MibIdentifier(mib_2.name + (10,))
1326experimental = MibIdentifier(internet.name + (3,))
1327private = MibIdentifier(internet.name + (4,))
1328enterprises = MibIdentifier(private.name + (1,))
1329security = MibIdentifier(internet.name + (5,))
1330snmpV2 = MibIdentifier(internet.name + (6,))
1331
1332snmpDomains = MibIdentifier(snmpV2.name + (1,))
1333snmpProxys = MibIdentifier(snmpV2.name + (2,))
1334snmpModules = MibIdentifier(snmpV2.name + (3,))
1335
1336mibBuilder.exportSymbols(
1337    'SNMPv2-SMI', MibNode=MibNode,
1338    Integer32=Integer32, Bits=Bits, IpAddress=IpAddress,
1339    Counter32=Counter32, Gauge32=Gauge32, Unsigned32=Unsigned32,
1340    TimeTicks=TimeTicks, Opaque=Opaque, Counter64=Counter64,
1341    ExtUTCTime=ExtUTCTime,
1342    ModuleIdentity=ModuleIdentity, ObjectIdentity=ObjectIdentity,
1343    NotificationType=NotificationType, MibScalar=MibScalar,
1344    MibScalarInstance=MibScalarInstance,
1345    MibIdentifier=MibIdentifier, MibTree=MibTree,
1346    MibTableColumn=MibTableColumn, MibTableRow=MibTableRow,
1347    MibTable=MibTable, zeroDotZero=zeroDotZero,
1348    itu_t=itu_t, iso=iso, joint_iso_itu_t=joint_iso_itu_t, org=org, dod=dod,
1349    internet=internet, directory=directory, mgmt=mgmt, mib_2=mib_2,
1350    transmission=transmission, experimental=experimental, private=private,
1351    enterprises=enterprises, security=security, snmpV2=snmpV2,
1352    snmpDomains=snmpDomains, snmpProxys=snmpProxys, snmpModules=snmpModules
1353)
1354
1355# XXX
1356# getAsName/setFromName goes out of MibRow?
1357# revisit getNextNode() -- needs optimization
1358