1#
2# This file is part of pysmi software.
3#
4# Copyright (c) 2015-2019, Ilya Etingof <etingof@gmail.com>
5# License: http://snmplabs.com/pysmi/license.html
6#
7import sys
8import re
9from time import strptime, strftime
10from keyword import iskeyword
11from pysmi.mibinfo import MibInfo
12from pysmi.codegen.base import AbstractCodeGen, dorepr
13from pysmi import error
14from pysmi import debug
15
16
17if sys.version_info[0] > 2:
18    # noinspection PyShadowingBuiltins
19    unicode = str
20    # noinspection PyShadowingBuiltins
21    long = int
22
23
24class PySnmpCodeGen(AbstractCodeGen):
25    """Builds PySNMP-specific Python code representing MIB module supplied
26       in form of an Abstract Syntax Tree on input.
27
28       Instance of this class is supposed to be passed to *MibCompiler*,
29       the rest is internal to *MibCompiler*.
30    """
31    defaultMibPackages = ('pysnmp.smi.mibs', 'pysnmp_mibs')
32
33    symsTable = {
34        'MODULE-IDENTITY': ('ModuleIdentity',),
35        'OBJECT-TYPE': ('MibScalar', 'MibTable', 'MibTableRow', 'MibTableColumn'),
36        'NOTIFICATION-TYPE': ('NotificationType',),
37        'TEXTUAL-CONVENTION': ('TextualConvention',),
38        'MODULE-COMPLIANCE': ('ModuleCompliance',),
39        'OBJECT-GROUP': ('ObjectGroup',),
40        'NOTIFICATION-GROUP': ('NotificationGroup',),
41        'AGENT-CAPABILITIES': ('AgentCapabilities',),
42        'OBJECT-IDENTITY': ('ObjectIdentity',),
43        'TRAP-TYPE': ('NotificationType',),  # smidump always uses NotificationType
44        'BITS': ('Bits',),
45    }
46
47    constImports = {
48        'ASN1': ('Integer', 'OctetString', 'ObjectIdentifier'),
49        'ASN1-ENUMERATION': ('NamedValues',),
50        'ASN1-REFINEMENT': ('ConstraintsUnion', 'ConstraintsIntersection', 'SingleValueConstraint',
51                            'ValueRangeConstraint', 'ValueSizeConstraint'),
52        'SNMPv2-SMI': ('iso',
53                       'Bits',  # XXX
54                       'Integer32',  # XXX
55                       'TimeTicks',  # bug in some IETF MIBs
56                       'Counter32',  # bug in some IETF MIBs (e.g. DSA-MIB)
57                       'Counter64',  # bug in some MIBs (e.g.A3COM-HUAWEI-LswINF-MIB)
58                       'NOTIFICATION-TYPE',  # bug in some MIBs (e.g. A3COM-HUAWEI-DHCPSNOOP-MIB)
59                       'Gauge32',  # bug in some IETF MIBs (e.g. DSA-MIB)
60                       'MODULE-IDENTITY', 'OBJECT-TYPE', 'OBJECT-IDENTITY', 'Unsigned32', 'IpAddress',  # XXX
61                       'MibIdentifier'),  # OBJECT IDENTIFIER
62        'SNMPv2-TC': ('DisplayString', 'TEXTUAL-CONVENTION',),  # XXX
63        'SNMPv2-CONF': ('MODULE-COMPLIANCE', 'NOTIFICATION-GROUP',),  # XXX
64    }
65
66    # never compile these, they either:
67    # - define MACROs (implementation supplies them)
68    # - or carry conflicting OIDs (so that all IMPORT's of them will be rewritten)
69    # - or have manual fixes
70    # - or import base ASN.1 types from implementation-specific MIBs
71    fakeMibs = ('ASN1',
72                'ASN1-ENUMERATION',
73                'ASN1-REFINEMENT')
74    baseMibs = ('PYSNMP-USM-MIB',
75                'SNMP-FRAMEWORK-MIB',
76                'SNMP-TARGET-MIB',
77                'TRANSPORT-ADDRESS-MIB',
78                'INET-ADDRESS-MIB') + AbstractCodeGen.baseMibs
79
80    baseTypes = ['Integer', 'Integer32', 'Bits', 'ObjectIdentifier', 'OctetString']
81
82    typeClasses = {
83        'COUNTER32': 'Counter32',
84        'COUNTER64': 'Counter64',
85        'GAUGE32': 'Gauge32',
86        'INTEGER': 'Integer32',  # XXX
87        'INTEGER32': 'Integer32',
88        'IPADDRESS': 'IpAddress',
89        'NETWORKADDRESS': 'IpAddress',
90        'OBJECT IDENTIFIER': 'ObjectIdentifier',
91        'OCTET STRING': 'OctetString',
92        'OPAQUE': 'Opaque',
93        'TIMETICKS': 'TimeTicks',
94        'UNSIGNED32': 'Unsigned32',
95        'Counter': 'Counter32',
96        'Gauge': 'Gauge32',
97        'NetworkAddress': 'IpAddress',  # RFC1065-SMI, RFC1155-SMI -> SNMPv2-SMI
98        'nullSpecific': 'zeroDotZero',  # RFC1158-MIB -> SNMPv2-SMI
99        'ipRoutingTable': 'ipRouteTable',  # RFC1158-MIB -> RFC1213-MIB
100        'snmpEnableAuthTraps': 'snmpEnableAuthenTraps'  # RFC1158-MIB -> SNMPv2-MIB
101    }
102
103    smiv1IdxTypes = ['INTEGER', 'OCTET STRING', 'IPADDRESS', 'NETWORKADDRESS']
104
105    ifTextStr = 'if mibBuilder.loadTexts: '
106    indent = ' ' * 4
107    fakeidx = 1000  # starting index for fake symbols
108
109    def __init__(self):
110        self._snmpTypes = set(self.typeClasses.values())
111        self._snmpTypes.add('Bits')
112        self._rows = set()
113        self._cols = {}  # k, v = name, datatype
114        self._exports = set()
115        self._seenSyms = set()
116        self._importMap = {}
117        self._out = {}  # k, v = name, generated code
118        self._moduleIdentityOid = None
119        self._moduleRevision = None
120        self.moduleName = ['DUMMY']
121        self.genRules = {'text': True}
122        self.symbolTable = {}
123
124    def symTrans(self, symbol):
125        if symbol in self.symsTable:
126            return self.symsTable[symbol]
127
128        return symbol,
129
130    @staticmethod
131    def transOpers(symbol):
132        if iskeyword(symbol):
133            symbol = 'pysmi_' + symbol
134
135        return symbol.replace('-', '_')
136
137    def prepData(self, pdata, classmode=False):
138        data = []
139
140        for el in pdata:
141            if not isinstance(el, tuple):
142                data.append(el)
143
144            elif len(el) == 1:
145                data.append(el[0])
146
147            else:
148                data.append(
149                    self.handlersTable[el[0]](self, self.prepData(el[1:], classmode=classmode), classmode=classmode)
150                )
151
152        return data
153
154    def genImports(self, imports):
155        outStr = ''
156
157        # conversion to SNMPv2
158        toDel = []
159        for module in list(imports):
160
161            if module in self.convertImportv2:
162
163                for symbol in imports[module]:
164
165                    if symbol in self.convertImportv2[module]:
166                        toDel.append((module, symbol))
167
168                        for newImport in self.convertImportv2[module][symbol]:
169                            newModule, newSymbol = newImport
170
171                            if newModule in imports:
172                                imports[newModule].append(newSymbol)
173                            else:
174                                imports[newModule] = [newSymbol]
175
176        # removing converted symbols
177        for d in toDel:
178            imports[d[0]].remove(d[1])
179
180        # merging mib and constant imports
181        for module in self.constImports:
182            if module in imports:
183                imports[module] += self.constImports[module]
184            else:
185                imports[module] = self.constImports[module]
186
187        for module in sorted(imports):
188            symbols = ()
189
190            for symbol in set(imports[module]):
191                symbols += self.symTrans(symbol)
192
193            if symbols:
194                self._seenSyms.update([self.transOpers(s) for s in symbols])
195                self._importMap.update([(self.transOpers(s), module) for s in symbols])
196
197                outStr += ', '.join([self.transOpers(s) for s in symbols])
198                if len(symbols) < 2:
199                    outStr += ','
200                outStr += ' = mibBuilder.importSymbols("%s")\n' % ('", "'.join((module,) + symbols))
201
202        return outStr, tuple(sorted(imports))
203
204    def genExports(self, ):
205        exports = list(self._exports)
206        if not exports:
207            return ''
208
209        numFuncCalls = len(exports) // 254 + 1
210
211        outStr = ''
212
213        for idx in range(numFuncCalls):
214            outStr += 'mibBuilder.exportSymbols("' + self.moduleName[0] + '", '
215            outStr += ', '.join(exports[254 * idx:254 * (idx + 1)]) + ')\n'
216
217        return outStr
218
219    # noinspection PyMethodMayBeStatic
220    def genLabel(self, symbol, classmode=False):
221        if '-' in symbol or iskeyword(symbol):
222            return classmode and 'label = "' + symbol + '"\n' or '.setLabel("' + symbol + '")'
223
224        return ''
225
226    def addToExports(self, symbol, moduleIdentity=0):
227        if moduleIdentity:
228            self._exports.add('PYSNMP_MODULE_ID=%s' % symbol)
229
230        self._exports.add('%s=%s' % (symbol, symbol))
231        self._seenSyms.add(symbol)
232
233    # noinspection PyUnusedLocal
234    def regSym(self, symbol, outStr, oidStr=None, moduleIdentity=False):
235        if symbol in self._seenSyms and symbol not in self._importMap:
236            raise error.PySmiSemanticError('Duplicate symbol found: %s' % symbol)
237
238        self.addToExports(symbol, moduleIdentity)
239        self._out[symbol] = outStr
240
241        if moduleIdentity:
242            if self._moduleIdentityOid:
243                raise error.PySmiSemanticError('Duplicate module identity')
244            # TODO: turning literal tuple into a string - hackerish
245            self._moduleIdentityOid = '.'.join(oidStr.split(', '))[1:-1]
246
247    def genNumericOid(self, oid):
248        numericOid = ()
249
250        for part in oid:
251            if isinstance(part, tuple):
252                parent, module = part
253
254                if parent == 'iso':
255                    numericOid += (1,)
256                    continue
257
258                if module not in self.symbolTable:
259                    # XXX do getname for possible future borrowed mibs
260                    raise error.PySmiSemanticError('no module "%s" in symbolTable' % module)
261
262                if parent not in self.symbolTable[module]:
263                    raise error.PySmiSemanticError('no symbol "%s" in module "%s"' % (parent, module))
264
265                numericOid += self.genNumericOid(self.symbolTable[module][parent]['oid'])
266
267            else:
268                numericOid += (part,)
269
270        return numericOid
271
272    def getBaseType(self, symName, module):
273        if module not in self.symbolTable:
274            raise error.PySmiSemanticError('no module "%s" in symbolTable' % module)
275
276        if symName not in self.symbolTable[module]:
277            raise error.PySmiSemanticError('no symbol "%s" in module "%s"' % (symName, module))
278
279        symType, symSubtype = self.symbolTable[module][symName].get('syntax', (('', ''), ''))
280
281        if not symType[0]:
282            raise error.PySmiSemanticError('unknown type for symbol "%s"' % symName)
283
284        if symType[0] in self.baseTypes:
285            return symType, symSubtype
286
287        else:
288            baseSymType, baseSymSubtype = self.getBaseType(*symType)
289
290            if isinstance(baseSymSubtype, list):
291                if isinstance(symSubtype, list):
292                    symSubtype += baseSymSubtype
293                else:
294                    symSubtype = baseSymSubtype
295
296            return baseSymType, symSubtype
297
298    # Clause generation functions
299
300    # noinspection PyUnusedLocal
301    def genAgentCapabilities(self, data, classmode=False):
302        name, productRelease, status, description, reference, oid = data
303
304        label = self.genLabel(name)
305        name = self.transOpers(name)
306
307        oidStr, parentOid = oid
308        outStr = name + ' = AgentCapabilities(' + oidStr + ')' + label + '\n'
309
310        if productRelease:
311            outStr += """\
312if getattr(mibBuilder, 'version', (0, 0, 0)) > (4, 4, 0):
313    %(name)s = %(name)s%(productRelease)s
314""" % dict(name=name, productRelease=productRelease)
315
316        if status:
317            outStr += """\
318if getattr(mibBuilder, 'version', (0, 0, 0)) > (4, 4, 0):
319    %(name)s = %(name)s%(status)s
320""" % dict(name=name, status=status)
321
322        if self.genRules['text'] and description:
323            outStr += self.ifTextStr + name + description + '\n'
324
325        if self.genRules['text'] and reference:
326            outStr += name + reference + '\n'
327
328        self.regSym(name, outStr, oidStr)
329
330        return outStr
331
332    # noinspection PyUnusedLocal
333    def genModuleIdentity(self, data, classmode=False):
334        name, lastUpdated, organization, contactInfo, description, revisionsAndDescrs, oid = data
335
336        label = self.genLabel(name)
337        name = self.transOpers(name)
338
339        oidStr, parentOid = oid
340
341        outStr = name + ' = ModuleIdentity(' + oidStr + ')' + label + '\n'
342
343        if revisionsAndDescrs:
344            last_revision, revisions, descriptions = revisionsAndDescrs
345
346            self._moduleRevision = last_revision
347
348            if revisions:
349                outStr += name + revisions + '\n'
350
351            if self.genRules['text'] and descriptions:
352                outStr += """
353if getattr(mibBuilder, 'version', (0, 0, 0)) > (4, 4, 0):
354    %(ifTextStr)s%(name)s%(descriptions)s
355""" % dict(ifTextStr=self.ifTextStr, name=name, descriptions=descriptions)
356
357        if lastUpdated:
358            outStr += self.ifTextStr + name + lastUpdated + '\n'
359
360        if organization:
361            outStr += self.ifTextStr + name + organization + '\n'
362
363        if self.genRules['text'] and contactInfo:
364            outStr += self.ifTextStr + name + contactInfo + '\n'
365
366        if self.genRules['text'] and description:
367            outStr += self.ifTextStr + name + description + '\n'
368
369        self.regSym(name, outStr, oidStr, moduleIdentity=True)
370
371        return outStr
372
373    # noinspection PyUnusedLocal
374    def genModuleCompliance(self, data, classmode=False):
375        name, status, description, reference, compliances, oid = data
376
377        label = self.genLabel(name)
378        name = self.transOpers(name)
379
380        oidStr, parentOid = oid
381        outStr = name + ' = ModuleCompliance(' + oidStr + ')' + label
382        outStr += compliances + '\n'
383
384        if status:
385            outStr += """\
386if getattr(mibBuilder, 'version', (0, 0, 0)) > (4, 4, 0):
387    %(name)s = %(name)s%(status)s
388""" % dict(name=name, status=status)
389
390        if self.genRules['text'] and description:
391            outStr += self.ifTextStr + name + description + '\n'
392
393        if self.genRules['text'] and reference:
394            outStr += self.ifTextStr + name + reference + '\n'
395
396        self.regSym(name, outStr, oidStr)
397
398        return outStr
399
400    # noinspection PyUnusedLocal
401    def genNotificationGroup(self, data, classmode=False):
402        name, objects, status, description, reference, oid = data
403
404        label = self.genLabel(name)
405        name = self.transOpers(name)
406
407        oidStr, parentOid = oid
408
409        outStr = name + ' = NotificationGroup(' + oidStr + ')' + label
410
411        if objects:
412            objects = ['("' + self._importMap.get(obj, self.moduleName[0]) + '", "' + self.transOpers(obj) + '")'
413                       for obj in objects]
414
415            numFuncCalls = len(objects) // 255 + 1
416
417            if numFuncCalls > 1:
418                objStrParts = []
419
420                for idx in range(numFuncCalls):
421                    objStrParts.append('[' + ', '.join(objects[255 * idx:255 * (idx + 1)]) + ']')
422
423                outStr += """
424for _%(name)s_obj in [%(objects)s]:
425    if getattr(mibBuilder, 'version', 0) < (4, 4, 2):
426        # WARNING: leading objects get lost here! Upgrade your pysnmp version!
427        %(name)s = %(name)s.setObjects(*_%(name)s_obj)
428    else:
429        %(name)s = %(name)s.setObjects(*_%(name)s_obj, **dict(append=True))\
430""" % dict(name=name, objects=', '.join(objStrParts))
431
432            else:
433                outStr += '.setObjects(' + ', '.join(objects) + ')'
434
435        outStr += '\n'
436
437        if status:
438            outStr += """\
439if getattr(mibBuilder, 'version', (0, 0, 0)) > (4, 4, 0):
440    %(name)s = %(name)s%(status)s
441""" % dict(name=name, status=status)
442
443        if self.genRules['text'] and description:
444            outStr += self.ifTextStr + name + description + '\n'
445
446        if self.genRules['text'] and reference:
447            outStr += name + reference + '\n'
448
449        self.regSym(name, outStr, oidStr)
450
451        return outStr
452
453    # noinspection PyUnusedLocal
454    def genNotificationType(self, data, classmode=False):
455        name, objects, status, description, reference, oid = data
456
457        label = self.genLabel(name)
458        name = self.transOpers(name)
459
460        oidStr, parentOid = oid
461
462        outStr = name + ' = NotificationType(' + oidStr + ')' + label
463
464        if objects:
465            objects = ['("' + self._importMap.get(obj, self.moduleName[0]) + '", "' + self.transOpers(obj) + '")'
466                       for obj in objects]
467
468            numFuncCalls = len(objects) // 255 + 1
469
470            if numFuncCalls > 1:
471                objStrParts = []
472
473                for idx in range(numFuncCalls):
474                    objStrParts.append('[' + ', '.join(objects[255 * idx:255 * (idx + 1)]) + ']')
475
476                outStr += """
477for _%(name)s_obj in [%(objects)s]:
478    if getattr(mibBuilder, 'version', 0) < (4, 4, 2):
479        # WARNING: leading objects get lost here! Upgrade your pysnmp version!
480        %(name)s = %(name)s.setObjects(*_%(name)s_obj)
481    else:
482        %(name)s = %(name)s.setObjects(*_%(name)s_obj, **dict(append=True))\
483""" % dict(name=name, objects=', '.join(objStrParts))
484
485            else:
486                outStr += '.setObjects(' + ', '.join(objects) + ')'
487
488        outStr += '\n'
489
490        if status:
491            outStr += self.ifTextStr + name + status + '\n'
492
493        if self.genRules['text'] and description:
494            outStr += self.ifTextStr + name + description + '\n'
495
496        if self.genRules['text'] and reference:
497            outStr += self.ifTextStr + name + reference + '\n'
498
499        self.regSym(name, outStr, oidStr)
500
501        return outStr
502
503    # noinspection PyUnusedLocal
504    def genObjectGroup(self, data, classmode=False):
505        name, objects, status, description, reference, oid = data
506
507        label = self.genLabel(name)
508        name = self.transOpers(name)
509
510        oidStr, parentOid = oid
511
512        outStr = name + ' = ObjectGroup(' + oidStr + ')' + label
513
514        if objects:
515            objects = ['("' + self._importMap.get(obj, self.moduleName[0]) + '", "' + self.transOpers(obj) + '")'
516                       for obj in objects]
517
518            numFuncCalls = len(objects) // 255 + 1
519
520            if numFuncCalls > 1:
521                objStrParts = []
522
523                for idx in range(numFuncCalls):
524                    objStrParts.append('[' + ', '.join(objects[255 * idx:255 * (idx + 1)]) + ']')
525
526                outStr += """
527for _%(name)s_obj in [%(objects)s]:
528    if getattr(mibBuilder, 'version', 0) < (4, 4, 2):
529        # WARNING: leading objects get lost here!
530        %(name)s = %(name)s.setObjects(*_%(name)s_obj)
531    else:
532        %(name)s = %(name)s.setObjects(*_%(name)s_obj, **dict(append=True))\
533""" % dict(name=name, objects=', '.join(objStrParts))
534
535            else:
536                outStr += '.setObjects(' + ', '.join(objects) + ')'
537
538        outStr += '\n'
539
540        if status:
541            outStr += """\
542if getattr(mibBuilder, 'version', (0, 0, 0)) > (4, 4, 0):
543    %(name)s = %(name)s%(status)s
544""" % dict(name=name, status=status)
545
546        if self.genRules['text'] and description:
547            outStr += self.ifTextStr + name + description + '\n'
548
549        if self.genRules['text'] and reference:
550            outStr += self.ifTextStr + name + reference + '\n'
551
552        self.regSym(name, outStr, oidStr)
553
554        return outStr
555
556    # noinspection PyUnusedLocal
557    def genObjectIdentity(self, data, classmode=False):
558        name, status, description, reference, oid = data
559
560        label = self.genLabel(name)
561        name = self.transOpers(name)
562
563        oidStr, parentOid = oid
564        outStr = name + ' = ObjectIdentity(' + oidStr + ')' + label + '\n'
565
566        if status:
567            outStr += self.ifTextStr + name + status + '\n'
568
569        if self.genRules['text'] and description:
570            outStr += self.ifTextStr + name + description + '\n'
571
572        if self.genRules['text'] and reference:
573            outStr += self.ifTextStr + name + reference + '\n'
574
575        self.regSym(name, outStr, oidStr)
576
577        return outStr
578
579    # noinspection PyUnusedLocal
580    def genObjectType(self, data, classmode=False):
581        name, syntax, units, maxaccess, status, description, reference, augmention, index, defval, oid = data
582
583        label = self.genLabel(name)
584        name = self.transOpers(name)
585
586        oidStr, parentOid = oid
587
588        indexStr, fakeStrlist, fakeSyms = index or ('', '', [])
589        subtype = syntax[0] == 'Bits' and 'Bits()' + syntax[1] or syntax[1]  # Bits hack #1
590
591        classtype = self.typeClasses.get(syntax[0], syntax[0])
592        classtype = self.transOpers(classtype)
593        classtype = syntax[0] == 'Bits' and 'MibScalar' or classtype  # Bits hack #2
594        classtype = name in self.symbolTable[self.moduleName[0]]['_symtable_cols'] and 'MibTableColumn' or classtype
595
596        defval = self.genDefVal(defval, objname=name)
597
598        outStr = name + ' = ' + classtype + '(' + oidStr + ', ' + subtype + (defval or '') + ')' + label
599        outStr += units or ''
600        outStr += maxaccess or ''
601        outStr += indexStr or ''
602        outStr += '\n'
603
604        if self.genRules['text'] and reference:
605            outStr += self.ifTextStr + name + reference + '\n'
606
607        if augmention:
608            augmention = self.transOpers(augmention)
609            outStr += augmention + '.registerAugmentions(("' + self._importMap.get(name, self.moduleName[0]) + '", "' + name + '"))\n'
610            outStr += name + '.setIndexNames(*' + augmention + '.getIndexNames())\n'
611
612        if status:
613            outStr += self.ifTextStr + name + status + '\n'
614
615        if self.genRules['text'] and description:
616            outStr += self.ifTextStr + name + description + '\n'
617
618        self.regSym(name, outStr, parentOid)
619
620        if fakeSyms:  # fake symbols for INDEX to support SMIv1
621            for idx, fakeSym in enumerate(fakeSyms):
622                fakeOutStr = fakeStrlist[idx] % oidStr
623                self.regSym(fakeSym, fakeOutStr, oidStr)
624
625        return outStr
626
627    # noinspection PyUnusedLocal
628    def genTrapType(self, data, classmode=False):
629        name, enterprise, objects, description, reference, value = data
630
631        label = self.genLabel(name)
632        name = self.transOpers(name)
633
634        enterpriseStr, parentOid = enterprise
635
636        outStr = name + ' = NotificationType(' + enterpriseStr + ' + (0,' + str(value) + '))' + label
637
638        if objects:
639            objects = ['("' + self._importMap.get(obj, self.moduleName[0]) + '", "' + self.transOpers(obj) + '")'
640                       for obj in objects]
641
642            numFuncCalls = len(objects) // 255 + 1
643
644            if numFuncCalls > 1:
645                objStrParts = []
646
647                for idx in range(numFuncCalls):
648                    objStrParts.append('[' + ', '.join(objects[255 * idx:255 * (idx + 1)]) + ']')
649
650                outStr += """
651for _%(name)s_obj in [%(objects)s]:
652    if getattr(mibBuilder, 'version', 0) < (4, 4, 2):
653        # WARNING: leading objects get lost here! Upgrade your pysnmp version!
654        %(name)s = %(name)s.setObjects(*_%(name)s_obj)
655    else:
656        %(name)s = %(name)s.setObjects(*_%(name)s_obj, **dict(append=True))\
657""" % dict(name=name, objects=', '.join(objStrParts))
658
659            else:
660                outStr += '.setObjects(' + ', '.join(objects) + ')'
661
662        outStr += '\n'
663
664        if self.genRules['text'] and description:
665            outStr += self.ifTextStr + name + description + '\n'
666
667        if self.genRules['text'] and reference:
668            outStr += self.ifTextStr + name + reference + '\n'
669
670        self.regSym(name, outStr, enterpriseStr)
671
672        return outStr
673
674    # noinspection PyUnusedLocal
675    def genTypeDeclaration(self, data, classmode=False):
676        outStr = ''
677
678        name, declaration = data
679
680        if declaration:
681            parentType, attrs = declaration
682            if parentType:  # skipping SEQUENCE case
683                name = self.transOpers(name)
684                outStr = 'class ' + name + '(' + parentType + '):\n' + attrs + '\n'
685                self.regSym(name, outStr)
686
687        return outStr
688
689    # noinspection PyUnusedLocal
690    def genValueDeclaration(self, data, classmode=False):
691        name, oid = data
692
693        label = self.genLabel(name)
694        name = self.transOpers(name)
695
696        oidStr, parentOid = oid
697        outStr = name + ' = MibIdentifier(' + oidStr + ')' + label + '\n'
698
699        self.regSym(name, outStr, oidStr)
700
701        return outStr
702
703    # Subparts generation functions
704
705    # noinspection PyMethodMayBeStatic,PyUnusedLocal
706    def ftNames(self, data, classmode=False):
707        names = data[0]
708        return names
709
710    def genBitNames(self, data, classmode=False):
711        names = data[0]
712        return names
713
714    def genBits(self, data, classmode=False):
715        bits = data[0]
716
717        namedval = ['("' + bit[0] + '", ' + str(bit[1]) + ')' for bit in bits]
718
719        numFuncCalls = len(namedval) // 255 + 1
720
721        funcCalls = ''
722        for idx in range(numFuncCalls):
723            funcCalls += 'NamedValues(' + ', '.join(namedval[255 * idx:255 * (idx + 1)]) + ') + '
724
725        funcCalls = funcCalls[:-3]
726
727        outStr = classmode and self.indent + 'namedValues = ' + funcCalls + '\n' or '.clone(namedValues=' + funcCalls + ')'
728
729        return 'Bits', outStr
730
731    # noinspection PyUnusedLocal
732    def genCompliances(self, data, classmode=False):
733        if not data[0]:
734            return ''
735
736        objects = []
737
738        for complianceModule in data[0]:
739            name = complianceModule[0] or self.moduleName[0]
740            objects += ['("' + name + '", "' + self.transOpers(compl) + '")' for compl in complianceModule[1]]
741
742        outStr = ''
743
744        numFuncCalls = len(objects) // 255 + 1
745
746        if numFuncCalls > 1:
747            objStrParts = []
748
749            for idx in range(numFuncCalls):
750                objStrParts.append('[' + ', '.join(objects[255 * idx:255 * (idx + 1)]) + ']')
751
752            outStr += """
753for _%(name)s_obj in [%(objects)s]:
754    if getattr(mibBuilder, 'version', 0) < (4, 4, 2):
755        # WARNING: leading objects get lost here! Upgrade your pysnmp version!
756        %(name)s = %(name)s.setObjects(*_%(name)s_obj)
757    else:
758        %(name)s = %(name)s.setObjects(*_%(name)s_obj, **dict(append=True))
759
760""" % dict(name=name, objects=', '.join(objStrParts))
761
762        else:
763            outStr += '.setObjects(' + ', '.join(objects) + ')\n'
764
765        return outStr
766
767    # noinspection PyUnusedLocal
768    def genConceptualTable(self, data, classmode=False):
769        row = data[0]
770        if row[1] and row[1][-2:] == '()':
771            row = row[1][:-2]
772            self._rows.add(row)
773
774        return 'MibTable', ''
775
776    # noinspection PyMethodMayBeStatic,PyUnusedLocal
777    def genContactInfo(self, data, classmode=False):
778        text = self.textFilter('contact-info', data[0])
779        return '.setContactInfo(' + dorepr(text) + ')'
780
781    # noinspection PyUnusedLocal
782    def genDisplayHint(self, data, classmode=False):
783        return self.indent + 'displayHint = ' + dorepr(data[0]) + '\n'
784
785    # noinspection PyUnusedLocal
786    def genDefVal(self, data, classmode=False, objname=None):
787        if not data:
788            return ''
789
790        if not objname:
791            return data
792
793        defval = data[0]
794        defvalType = self.getBaseType(objname, self.moduleName[0])
795
796        if isinstance(defval, (int, long)):  # number
797            val = str(defval)
798
799        elif self.isHex(defval):  # hex
800            if defvalType[0][0] in ('Integer32', 'Integer'):  # common bug in MIBs
801                val = str(int(defval[1:-2], 16))
802            else:
803                val = 'hexValue="' + defval[1:-2] + '"'
804
805        elif self.isBinary(defval):  # binary
806            binval = defval[1:-2]
807            if defvalType[0][0] in ('Integer32', 'Integer'):  # common bug in MIBs
808                val = str(int(binval or '0', 2))
809            else:
810                hexval = binval and hex(int(binval, 2))[2:] or ''
811                val = 'hexValue="' + hexval + '"'
812
813        elif defval[0] == defval[-1] and defval[0] == '"':  # quoted string
814            if defval[1:-1] == '' and defvalType != 'OctetString':  # common bug
815                # a warning should be here
816                return False  # we will set no default value
817
818            val = dorepr(defval[1:-1])
819
820        else:  # symbol (oid as defval) or name for enumeration member
821            if (defvalType[0][0] == 'ObjectIdentifier' and
822                    (defval in self.symbolTable[self.moduleName[0]] or defval in self._importMap)):  # oid
823                module = self._importMap.get(defval, self.moduleName[0])
824
825                try:
826                    val = str(self.genNumericOid(self.symbolTable[module][defval]['oid']))
827                except:
828                    # or no module if it will be borrowed later
829                    raise error.PySmiSemanticError('no symbol "%s" in module "%s"' % (defval, module))
830
831            # enumeration
832            elif defvalType[0][0] in ('Integer32', 'Integer') and isinstance(defvalType[1], list):
833                if isinstance(defval, list):  # buggy MIB: DEFVAL { { ... } }
834                    defval = [dv for dv in defval if dv in dict(defvalType[1])]
835                    val = defval and dorepr(defval[0]) or ''
836                elif defval in dict(defvalType[1]):  # good MIB: DEFVAL { ... }
837                    val = dorepr(defval)
838                else:
839                    val = ''
840
841            elif defvalType[0][0] == 'Bits':
842                defvalBits = []
843                bits = dict(defvalType[1])
844
845                for bit in defval:
846                    bitValue = bits.get(bit, None)
847                    if bitValue is not None:
848                        defvalBits.append((bit, bitValue))
849                    else:
850                        raise error.PySmiSemanticError('no such bit as "%s" for symbol "%s"' % (bit, objname))
851
852                return self.genBits([defvalBits])[1]
853
854            else:
855                raise error.PySmiSemanticError(
856                    'unknown type "%s" for defval "%s" of symbol "%s"' % (defvalType, defval, objname))
857
858        return '.clone(' + val + ')'
859
860    # noinspection PyMethodMayBeStatic,PyUnusedLocal
861    def genDescription(self, data, classmode=False):
862        text = self.textFilter('description', data[0])
863        return classmode and self.indent + 'description = ' + dorepr(text) + '\n' or '.setDescription(' + dorepr(text) + ')'
864
865    # noinspection PyMethodMayBeStatic
866    def genReference(self, data, classmode=False):
867        text = self.textFilter('reference', data[0])
868        return classmode and self.indent + 'reference = ' + dorepr(text) + '\n' or '.setReference(' + dorepr(text) + ')'
869
870    # noinspection PyMethodMayBeStatic
871    def genStatus(self, data, classmode=False):
872        text = data[0]
873        return classmode and self.indent + 'status = ' + dorepr(text) + '\n' or '.setStatus(' + dorepr(text) + ')'
874
875    # noinspection PyMethodMayBeStatic
876    def genProductRelease(self, data, classmode=False):
877        text = data[0]
878        return classmode and self.indent + 'productRelease = ' + dorepr(text) + '\n' or '.setProductRelease(' + dorepr(text) + ')'
879
880    def genEnumSpec(self, data, classmode=False):
881        items = data[0]
882        singleval = [str(item[1]) for item in items]
883        outStr = classmode and self.indent + 'subtypeSpec = %s.subtypeSpec + ' or '.subtype(subtypeSpec='
884        numFuncCalls = len(singleval) / 255 + 1
885        singleCall = numFuncCalls == 1
886        funcCalls = ''
887
888        outStr += not singleCall and 'ConstraintsUnion(' or ''
889
890        for idx in range(int(numFuncCalls)):
891            if funcCalls:
892                funcCalls += ', '
893            funcCalls += 'SingleValueConstraint(' + ', '.join(singleval[255 * idx:255 * (idx + 1)]) + ')'
894
895        outStr += funcCalls
896        outStr += not singleCall and (classmode and ')\n' or '))') or (not classmode and ')' or '\n')
897        outStr += self.genBits(data, classmode=classmode)[1]
898
899        return outStr
900
901    # noinspection PyUnusedLocal
902    def genTableIndex(self, data, classmode=False):
903        def genFakeSyms(fakeidx, idxType):
904            fakeSymName = 'pysmiFakeCol%s' % fakeidx
905
906            objType = self.typeClasses.get(idxType, idxType)
907            objType = self.transOpers(objType)
908
909            return (fakeSymName + ' = MibTableColumn(%s + (' + str(fakeidx) +
910                    ', ), ' + objType + '())\n',  # stub for parentOid
911                    fakeSymName)
912
913        indexes = data[0]
914        idxStrlist, fakeSyms, fakeStrlist = [], [], []
915        for idx in indexes:
916            idxName = idx[1]
917            if idxName in self.smiv1IdxTypes:  # SMIv1 support
918                idxType = idxName
919
920                fakeSymStr, idxName = genFakeSyms(self.fakeidx, idxType)
921                fakeStrlist.append(fakeSymStr)
922                fakeSyms.append(idxName)
923                self.fakeidx += 1
924
925            idxStrlist.append('(' + str(idx[0]) + ', "' +
926                              self._importMap.get(idxName, self.moduleName[0]) +
927                              '", "' + idxName + '")')
928
929        return '.setIndexNames(' + ', '.join(idxStrlist) + ')', fakeStrlist, fakeSyms
930
931    def genIntegerSubType(self, data, classmode=False):
932        singleRange = len(data[0]) == 1
933
934        outStr = classmode and self.indent + 'subtypeSpec = %s.subtypeSpec + ' or '.subtype(subtypeSpec='
935        outStr += not singleRange and 'ConstraintsUnion(' or ''
936
937        for rng in data[0]:
938            vmin, vmax = len(rng) == 1 and (rng[0], rng[0]) or rng
939            vmin, vmax = str(self.str2int(vmin)), str(self.str2int(vmax))
940            outStr += 'ValueRangeConstraint(' + vmin + ', ' + vmax + ')' + (not singleRange and ', ' or '')
941
942        outStr += not singleRange and (classmode and ')' or '))') or (not classmode and ')' or '\n')
943
944        return outStr
945
946    # noinspection PyMethodMayBeStatic,PyUnusedLocal
947    def genMaxAccess(self, data, classmode=False):
948        access = data[0].replace('-', '')
949        return access != 'notaccessible' and '.setMaxAccess("' + access + '")' or ''
950
951    def genOctetStringSubType(self, data, classmode=False):
952        singleRange = len(data[0]) == 1
953
954        outStr = classmode and self.indent + 'subtypeSpec = %s.subtypeSpec + ' or '.subtype(subtypeSpec='
955        outStr += not singleRange and 'ConstraintsUnion(' or ''
956
957        for rng in data[0]:
958            vmin, vmax = len(rng) == 1 and (rng[0], rng[0]) or rng
959            vmin, vmax = str(self.str2int(vmin)), str(self.str2int(vmax))
960            outStr += ('ValueSizeConstraint(' + vmin + ', ' + vmax + ')' +
961                       (not singleRange and ', ' or ''))
962
963        outStr += not singleRange and (classmode and ')' or '))') or (not classmode and ')' or '\n')
964
965        if data[0]:
966            # noinspection PyUnboundLocalVariable
967            outStr += (singleRange
968                       and vmin == vmax
969                       and (classmode and self.indent + 'fixedLength = ' + vmin + '\n' or '.setFixedLength(' + vmin + ')') or '')
970
971        return outStr
972
973    # noinspection PyUnusedLocal
974    def genOid(self, data, classmode=False):
975        out = ()
976        parent = ''
977        for el in data[0]:
978            if isinstance(el, (str, unicode)):
979                parent = self.transOpers(el)
980                out += ((parent, self._importMap.get(parent, self.moduleName[0])),)
981
982            elif isinstance(el, (int, long)):
983                out += (el,)
984
985            elif isinstance(el, tuple):
986                out += (el[1],)  # XXX Do we need to create a new object el[0]?
987
988            else:
989                raise error.PySmiSemanticError('unknown datatype for OID: %s' % el)
990
991        return str(self.genNumericOid(out)), parent
992
993    # noinspection PyUnusedLocal
994    def genObjects(self, data, classmode=False):
995        if data[0]:
996            return [self.transOpers(obj) for obj in data[0]]  # XXX self.transOpers or not??
997        return []
998
999    # noinspection PyMethodMayBeStatic,PyUnusedLocal
1000    def genTime(self, data, classmode=False):
1001        times = []
1002        for timeStr in data:
1003            if len(timeStr) == 11:
1004                timeStr = '19' + timeStr
1005            # XXX raise in strict mode
1006            # elif lenTimeStr != 13:
1007            #  raise error.PySmiSemanticError("Invalid date %s" % t)
1008            try:
1009                times.append(strftime('%Y-%m-%d %H:%M', strptime(timeStr, '%Y%m%d%H%MZ')))
1010
1011            except ValueError:
1012                # XXX raise in strict mode
1013                # raise error.PySmiSemanticError("Invalid date %s: %s" % (t, sys.exc_info()[1]))
1014                timeStr = '197001010000Z'  # dummy date for dates with typos
1015                times.append(strftime('%Y-%m-%d %H:%M', strptime(timeStr, '%Y%m%d%H%MZ')))
1016
1017        return times
1018
1019    # noinspection PyMethodMayBeStatic,PyUnusedLocal
1020    def genLastUpdated(self, data, classmode=False):
1021        text = data[0]
1022        return '.setLastUpdated(' + dorepr(text) + ')'
1023
1024    # noinspection PyMethodMayBeStatic,PyUnusedLocal
1025    def genOrganization(self, data, classmode=False):
1026        text = self.textFilter('organization', data[0])
1027        return '.setOrganization(' + dorepr(text) + ')'
1028
1029    # noinspection PyUnusedLocal
1030    def genRevisions(self, data, classmode=False):
1031        times = self.genTime([x[0] for x in data[0]])
1032        times = [dorepr(x) for x in times]
1033
1034        revisions = '.setRevisions((%s,))' % ', '.join(times)
1035
1036        descriptions = '.setRevisionsDescriptions((%s,))' % ', '.join(
1037            [dorepr(self.textFilter('description', x[1][1])) for x in data[0]]
1038        )
1039
1040        lastRevision = data[0][0][0]
1041
1042        return lastRevision, revisions, descriptions
1043
1044    def genRow(self, data, classmode=False):
1045        row = data[0]
1046        row = self.transOpers(row)
1047        return row in self.symbolTable[self.moduleName[0]]['_symtable_rows'] and (
1048             'MibTableRow', '') or self.genSimpleSyntax(data, classmode=classmode)
1049
1050    # noinspection PyUnusedLocal
1051    def genSequence(self, data, classmode=False):
1052        cols = data[0]
1053        self._cols.update(cols)
1054        return '', ''
1055
1056    def genSimpleSyntax(self, data, classmode=False):
1057        objType = data[0]
1058        objType = self.typeClasses.get(objType, objType)
1059        objType = self.transOpers(objType)
1060
1061        subtype = len(data) == 2 and data[1] or ''
1062
1063        if classmode:
1064            subtype = '%s' in subtype and subtype % objType or subtype  # XXX hack?
1065            return objType, subtype
1066
1067        outStr = objType + '()' + subtype
1068
1069        return 'MibScalar', outStr
1070
1071    # noinspection PyUnusedLocal
1072    def genTypeDeclarationRHS(self, data, classmode=False):
1073        if len(data) == 1:
1074            parentType, attrs = data[0]  # just syntax
1075
1076        else:
1077            # Textual convention
1078            display, status, description, reference, syntax = data
1079            parentType, attrs = syntax
1080
1081            if parentType in self._snmpTypes:
1082                parentType = 'TextualConvention, ' + parentType
1083
1084            if display:
1085                attrs = display + attrs
1086
1087            if status:
1088                attrs = status + attrs
1089
1090            if self.genRules['text'] and description:
1091                attrs = description + attrs
1092
1093            if reference:
1094                attrs = reference + attrs
1095
1096        attrs = attrs or self.indent + 'pass\n'
1097
1098        return parentType, attrs
1099
1100    # noinspection PyMethodMayBeStatic,PyUnusedLocal
1101    def genUnits(self, data, classmode=False):
1102        text = data[0]
1103        return '.setUnits(' + dorepr(self.textFilter('units', text)) + ')'
1104
1105    handlersTable = {
1106        'agentCapabilitiesClause': genAgentCapabilities,
1107        'moduleIdentityClause': genModuleIdentity,
1108        'moduleComplianceClause': genModuleCompliance,
1109        'notificationGroupClause': genNotificationGroup,
1110        'notificationTypeClause': genNotificationType,
1111        'objectGroupClause': genObjectGroup,
1112        'objectIdentityClause': genObjectIdentity,
1113        'objectTypeClause': genObjectType,
1114        'trapTypeClause': genTrapType,
1115        'typeDeclaration': genTypeDeclaration,
1116        'valueDeclaration': genValueDeclaration,
1117
1118        'ApplicationSyntax': genSimpleSyntax,
1119        'BitNames': genBitNames,
1120        'BITS': genBits,
1121        'ComplianceModules': genCompliances,
1122        'conceptualTable': genConceptualTable,
1123        'CONTACT-INFO': genContactInfo,
1124        'DISPLAY-HINT': genDisplayHint,
1125        'DEFVAL': genDefVal,
1126        'DESCRIPTION': genDescription,
1127        'REFERENCE': genReference,
1128        'Status': genStatus,
1129        'PRODUCT-RELEASE': genProductRelease,
1130        'enumSpec': genEnumSpec,
1131        'INDEX': genTableIndex,
1132        'integerSubType': genIntegerSubType,
1133        'MaxAccessPart': genMaxAccess,
1134        'Notifications': genObjects,
1135        'octetStringSubType': genOctetStringSubType,
1136        'objectIdentifier': genOid,
1137        'Objects': genObjects,
1138        'LAST-UPDATED': genLastUpdated,
1139        'ORGANIZATION': genOrganization,
1140        'Revisions': genRevisions,
1141        'row': genRow,
1142        'SEQUENCE': genSequence,
1143        'SimpleSyntax': genSimpleSyntax,
1144        'typeDeclarationRHS': genTypeDeclarationRHS,
1145        'UNITS': genUnits,
1146        'VarTypes': genObjects,
1147        # 'a': lambda x: genXXX(x, 'CONSTRAINT')
1148    }
1149
1150    def genCode(self, ast, symbolTable, **kwargs):
1151        self.genRules['text'] = kwargs.get('genTexts', False)
1152        self.textFilter = kwargs.get('textFilter') or (lambda symbol, text: re.sub('\s+', ' ', text))
1153        self.symbolTable = symbolTable
1154        self._rows.clear()
1155        self._cols.clear()
1156        self._exports.clear()
1157        self._seenSyms.clear()
1158        self._importMap.clear()
1159        self._out.clear()
1160        self._moduleIdentityOid = None
1161        self.moduleName[0], moduleOid, imports, declarations = ast
1162
1163        out, importedModules = self.genImports(imports or {})
1164
1165        for declr in declarations or []:
1166            if declr:
1167                clausetype = declr[0]
1168                classmode = clausetype == 'typeDeclaration'
1169                self.handlersTable[declr[0]](self, self.prepData(declr[1:], classmode), classmode)
1170
1171        for sym in self.symbolTable[self.moduleName[0]]['_symtable_order']:
1172            if sym not in self._out:
1173                raise error.PySmiCodegenError('No generated code for symbol %s' % sym)
1174            out += self._out[sym]
1175
1176        out += self.genExports()
1177
1178        if 'comments' in kwargs:
1179            out = ''.join(['# %s\n' % x for x in kwargs['comments']]) + '#\n' + out
1180            out = '#\n# PySNMP MIB module %s (http://snmplabs.com/pysmi)\n' % self.moduleName[0] + out
1181
1182        debug.logger & debug.flagCodegen and debug.logger(
1183            'canonical MIB name %s (%s), imported MIB(s) %s, Python code size %s bytes' % (
1184                self.moduleName[0], moduleOid, ','.join(importedModules) or '<none>', len(out)))
1185
1186        return MibInfo(oid=moduleOid,
1187                       identity=self._moduleIdentityOid,
1188                       name=self.moduleName[0],
1189                       revision=self._moduleRevision,
1190                       oids=[],
1191                       enterprise=None,
1192                       compliance=[],
1193                       imported=tuple([x for x in importedModules if x not in self.fakeMibs])), out
1194
1195    def genIndex(self, processed, **kwargs):
1196        out = '\nfrom pysnmp.proto.rfc1902 import ObjectName\n\noidToMibMap = {\n'
1197        count = 0
1198        for module, status in processed.items():
1199            value = getattr(status, 'oid', None)
1200            if value:
1201                out += 'ObjectName("%s"): "%s",\n' % (value, module)
1202                count += 1
1203        out += '}\n'
1204
1205        if 'comments' in kwargs:
1206            out = ''.join(['# %s\n' % x for x in kwargs['comments']]) + '#\n' + out
1207            out = '#\n# PySNMP MIB indices (http://snmplabs.com/pysmi)\n' + out
1208
1209        debug.logger & debug.flagCodegen and debug.logger(
1210            'OID->MIB index built, %s entries, %s bytes' % (count, len(out)))
1211
1212        return out
1213
1214# backward compatibility
1215baseMibs = PySnmpCodeGen.baseMibs
1216fakeMibs = PySnmpCodeGen.fakeMibs
1217