#! /usr/bin/python -u
'''
#-------------------------------------------------------------------------------
#
# Copyright 2015-2019 Cumulus Networks, Inc.  all rights reserved
#
# This pass_persist script handles VLAN-AWARE bridge and displays
# the dot1dBasePortTable, dot1dStp table, dot1dStpPortTable, and
# dot1dTpFdbTable in the BRIDGE-MIB (RFC-4188).
#
# It also support the dot1qBase, dot1qTpFdbTable, dot1qVlanStaticTable,
# and the dot1qPortVlanTable in the Q-BRIDGE-MIB (RFC-4363).
#
# To use this script, place the file in
# /usr/share/snmp, change the permissions (chmod 755) and edit
# /etc/snmp/snmpd.conf and add the following two lines:
#
#        view   systemonly  included   .1.3.6.1.2.1.17
#        pass_persist .1.3.6.1.2.1.17 /usr/share/snmp/bridge_pp.py
#
#-------------------------------------------------------------------------------
'''
import subprocess
import syslog
import json
import glob
import snmp_passpersist as snmp

oid_base = '.1.3.6.1.2.1.17'

syslog_already_logged = False

# some variables we will be using taken from the two RFCs (4188, 4363)
# for the BRIDGE and Q-BRIDGE MIBs.
dot1dBase = '1'
dot1dBaseBridgeAddress = '1'
dot1dBaseNumPorts = '2'
dot1dBaseType = '3'
dot1dBasePortTable = '4'
dot1dBasePortEntry = '1'
dot1dBasePort = '1'
dot1dBasePortIfIndex = '2'
dot1dBasePortCircuit = '3'

dot1dStp = '2'
dot1dStpProtocolSpecification = '1'

dot1dStpPriority = '2'
dot1dStpTimeSinceTopologyChange = '3'
dot1dStpTopChanges = '4'

dot1dStpDesignatedRoot = '5'
dot1dStpRootCost = 6
dot1dStpRootPort = 7
dot1dStpMaxAge = 8
dot1dStpHelloTime = 9
dot1dStpHoldTime = 10
dot1dStpForwardDelay = 11
dot1dStpBridgeMaxAge = 12
dot1dStpBridgeHelloTime = 13
dot1dStpBridgeForwardDelay = 14
dot1dStpPortTable = 15
dot1dStpPortEntry = 1

dot1dTp = 4
dot1dTpAgingTime = 2
dot1dTpFdbTable = 3
dot1dTpFdbEntry = 1
# the table is indexed by mac address
dot1dTpFdbAddress = 1
dot1dTpFdbPort = 2
dot1dTpFdbStatus = 3

qBridgeMIB = '7'
qBridgeMIBObjects = '1'
dot1qBase = '1'
dot1qVlanVersionNumber = '1'
dot1qMaxVlanId = '2'
dot1qMaxSupportedVlans = '3'
dot1qNumVlans = '4'
dot1qGvrpStatus = '5'

dot1qTpFdbTable = '2'
dot1qTpFdbEntry = '1'
dot1qFdbId = '1'

dot1qTpFdbAddress = '1'
dot1qTpFdbPort = '2'
dot1qTpFdbStatus = '3'

dot1qVlan = '4'
# we do not track this so we cannot show it
dot1qVlanNumDeletes = '1'
# this is the same as the static table below but is indexed with both
# dot1qVlanTimeMark (which has syntax TimeFilter (RMON2-MIB)) and
# dot1qVlanIndex. Since we cannot get TimeTick uptime value for when
# VLANs were created (bridge vlan show), we do not show this.
dot1qVlanCurrentTable = '2'
dot1qVlanCurrentEntry = '1'
# the dot1qVlanFdbId is the filtering database for a VLAN.

# we are only doing the static VLAN table, since we do not do dynamic tables.
dot1qVlanStaticTable = '3'
dot1qVlanStaticEntry = '1'

dot1qVlanStaticName = '1'
dot1qVlanStaticEgressPorts = '2'
# we do not have filtering so we are not forbidding anything
dot1qVlanForbiddenEgressPorts = '3'
dot1qVlanStaticUntaggedPorts = '4'
dot1qVlanStaticRowStatus = '5'

# we do not allow the creation of a new vlan via snmp.  You have to create
# these on the box so we do not show the next free local vlan in the
# dot1qVlanStaticTable.
# dot1qNextFreeLocalVlanIndex = '4'

# We do not track per port per VLAN statistics so we cannot not show
# dot1qPortVlanStatisticsTable  1.3.6.1.2.1.17.7.1.4.6
#
# The dot1qPortVlanTable table is mainly to get the PVID entries indexed by
# port number
# 1.3.6.1.2.1.17.7.1.4.5
dot1qPortVlanTable = '5'
dot1qPortVlanEntry = '1'
dot1qPvid = '1'
dot1qPortAcceptableFrameTypes = '2'
dot1qPortIngressFiltering = '3'
dot1qPortGvrpStatus = '4'
dot1qPortGvrpFailedRegistrations = '5'
dot1qPortGvrpLastPduOrigin = '6'
dot1qPortRestrictedVlanRegistration = '7'

# for dot1qPortAcceptableFrameTypes, we have admitAll = 1
# for dot1qPortGvrpFailedRegistrations, we do not count these so default to 0
# for dot1qPortGvrpLastPduOrigin, we do not track the last GVRP mac address so
# send zeros for dot1qPortRestrictedVlanRegistration, we do not restrict vlans
# so send default of false

# we do not have a mac address filtering feature
dot1dStatic = 5
qBridgeMIB = '7'
qBridgeMIBObjects = '1'
dot1qBase = '1'
dot1qTp = '2'
# we do not support the Filtering Database
# dot1qFdbTable = '1'

def get_octetstring(portlist=None):
    '''
    Given a list of port numbers, we create
    a set of hex characters as a string.  The first octet is a bitmap
    of ports 1-8, the second octet ports 9-16 and so on with port 1 as the
    most significant bit.
    For example, a list of ports [1, 5, 17] gives "88 00 80"
    or, more obvious in binary 10001000 00000000 1000000 where bits
    are set to one in positions 1, 5, and 17.
    '''
    if portlist is None:
        portlist = []
    portbin = [(1 << (i-1)) for i in portlist]
    portsum = sum(portbin)
    # now reverse the binary number, removing the "0b"
    revbin = bin(portsum)[:1:-1]
    # we might need to pad to the end with zeros to make a byte
    padding = (8 - (len(revbin) % 8)) % 8
    paddedrevbin = '%s%s' % (revbin, padding*'0')
    # take the padded binary string in chunks of 8 bits and convert it to a hex
    # string with spaces between the bytes for an snmp octet string
    paddedegressports = ' '.join([('%02x' % int(paddedrevbin[i:i+8], 2))
                                  for i in range(0, len(paddedrevbin), 8)])
    return paddedegressports

def get_json_output(commandList=None):
    '''
    This grabs the JSON output of a CLI command and returns an array.
    '''
    global syslog_already_logged
    outArray = {}
    if (not commandList) or (commandList == []):
        syslog.syslog('Error: called get_json_output with'
                      ' invalid command=%s' % \
                      commandList)
        syslog_already_logged = True
        return {}
    try:
        outArray = json.loads(subprocess.check_output((commandList),
                                                      shell=False),
                              encoding="latin-1")
    except Exception as e:
        outArray = {}
        syslog.syslog('Error: command %s EXCEPTION=%s' % \
                      (' '.join(commandList), e))
        syslog_already_logged = True
    return outArray

def is_vlan_aware(brname=None):
    if not brname:
        return False
    if open('/sys/class/net/%s/bridge/vlan_filtering' % brname,
            'r').read().strip() == '1':
        return True
    else:
        return False
    return False

def get_bridge_array():
    '''
    get_bridge_array looks in /sys/class/net for bridges and returns an array
    for that ONE bridge.  If there are both classic and vlan-aware bridge, we
    will choose the vlan-aware bridge. If there are no VLAN aware bridges,
    return nothing.
    '''
    global syslog_already_logged
    bridgeArray = {}
    # grab the bridge names from /sys/class/net/X if they exist
    allbridges = [b.split('/')[4] for b in
                  glob.glob('/sys/class/net/*/bridge/bridge_id')]
    # collect VLAN aware bridges, if any
    vlanawarebridges = [b for b in allbridges if is_vlan_aware(b)]
    # if we have vlan aware bridges, we will use them, otherwise, nothing
    if len(vlanawarebridges) > 0:
        bridges = vlanawarebridges
    else:
        bridges = []
    # find the min ifindex of the bridges we have
    # now grab all the bridge ifindices
    ifindices = [int(open('/sys/class/net/%s/ifindex' % \
                           b, 'r').read().strip()) for b in bridges]
    # in some weird (and invalid and unsupported) way, we have more then one bridge
    # find the minimum ifindex and only use that bridge since MIB only
    # handles one bridge
    if ifindices:
        brindex = min(ifindices)
        bridge = bridges[ifindices.index(brindex)]
        bridgeArray.setdefault('name', bridge)
        bridgeArray.setdefault('ifindex', brindex)
        try:
            # mib needs seconds
            aging = int(open('/sys/class/net/%s/bridge/ageing_time' % \
                             bridge, 'r').read().strip())/100
            bridgeArray.setdefault('aging', aging)
        except:
            bridgeArray.setdefault('aging', 0)
    
    if len(ifindices) == 0:
        # we have no bridge to show
        if not syslog_already_logged:
            syslog.syslog("INFO: No VLAN-AWARE bridge found.")
            syslog_already_logged = True
    if len(ifindices) > 1:
        # log to syslog once and use the lowest bridge ifindex
        # This MIB only handles one bridge.
        if not syslog_already_logged:
            syslog.syslog("INFO: More then one bridge"
                          " found. Only using %s." % bridge)
            syslog_already_logged = True

    return bridgeArray

def get_bridgeport_info(bridgeArray=None):
    '''
    Given a bridgeArray which contains the ONE bridge, this method
    builds an array indexed by bridge port name.
    It also returns brport_list.
    '''
    if not bridgeArray:
        return [],{}
    brport_list = []
    brport_array = {}
    # this is simply the list of bridge port names
    # the brportindex will be ordered based on these names
    bridge = bridgeArray.get('name', None)
    brport_list = [b.split('/')[-1] for b in \
                   glob.glob('/sys/class/net/%s/brif/*' % bridge)]
    # for every bridge port, grab the ifindex
    counter = 1
    # we well be using the brports everywhere below since this is ordered
    # by the counter
    for port in brport_list:
        brport_array.setdefault(port, {})
        brport_array[port].setdefault('brportindex', counter)
        brport_array[port].setdefault('brportname', port)
        # default all pvids to 1
        brport_array[port].setdefault('pvid', 1)
        brport_array[port].setdefault('brportifindex',
                                      int(open('/sys/class/net/%s/ifindex' \
                                               % port, 'r').read().strip()))
        brport_array[port].setdefault('brportcircuit', '0.0')
        # Now we set some defaults that are used later in dot1qPortVlanTable
        # for dot1qPortAcceptableFrameTypes, we have admitAll = 1
        admitAll = 1
        brport_array[port].setdefault('acceptableFrameTypes', admitAll)
        # for dot1qPortIngressFiltering, we do not discard incoming frames
        # so we send false
        false = 2
        brport_array[port].setdefault('ingressFiltering', false)
        # for dot1qPortGvrpStatus, we do not do any GVRP so we send disabled
        disabled = 2
        brport_array[port].setdefault('gvrpStatus', disabled)
        # for dot1qPortGvrpFailedRegistrations, we do not count these so
        # default to 0
        brport_array[port].setdefault('failedReg', 0)
        # for dot1qPortGvrpLastPduOrigin, we do not track the last GVRP mac
        # address so send zeros
        brport_array[port].setdefault('lastPDU', '00 00 00 00 00 00')
        # for dot1qPortRestrictedVlanRegistration, we do not restrict vlans
        # so send default of false
        brport_array[port].setdefault('restrictedReg', false)
        counter += 1

    return brport_list, brport_array

def get_vlan_info(vlanJson=None, brportArray=None):
    '''
    Reorganize the vlan JSON output (bridge vlan show) into an array
    indexed by VLAN id and keep track of interface names, flags, pvids, etc.
    '''
    vlan_dict = {}
    if not vlanJson:
        return {}

    ############################################################################
    # We have a vlan aware bridge.
    # For each vlan number entry is another array indexed with decimal macs
    # some vlans may exist but have no FDB entries so we should populate the
    # vlan_dict with the output of bridge -j vlan show
    # we only have ifnames if we have a vlan aware bridge
    for namelist in vlanJson:
        # this array is indexed by interface name and then lists vlans
        ifname = namelist.get('ifname')
        ifindex = int(open('/sys/class/net/%s/ifindex' % ifname,
                           'r').read().strip())
        # we only deal with ports in our bridge
        if brportArray.get(ifname, None):
            for vlanEntry in namelist.get('vlans'):
                # entry is an array with vlan and flags
                vlanid = vlanEntry.get('vlan', None)
                flags = vlanEntry.get('flags', None)
                vlan_dict.setdefault(vlanid, {})
                vlan_dict[vlanid].setdefault('ifnames', [])
                vlan_dict[vlanid].setdefault('flags', {})
                vlan_dict[vlanid]['flags'] = flags
                vlan_dict[vlanid]['ifnames'].append(ifname)
                vlan_dict[vlanid].setdefault('portindices', [])
                portindex = brportArray.get(ifname, {}).get('brportindex', 0)
                vlan_dict[vlanid]['portindices'].append(portindex)
                # there should be at most 1 PVID for each interface
                if flags and ('Egress Untagged' in flags):
                    brportArray[ifname]['pvid'] = vlanid

    # for the newly created VLANS from vlan aware bridge,
    # update some fields
    vlans = [i for i in vlan_dict.keys()]
    vlans.sort()
    for vlanid in vlans:
        myindex = vlans.index(vlanid) + 1
        # just make up a VLAN name like VLAN-1234 or default
        if myindex == 1 and vlanid == 1:
            vlan_dict[vlanid]['vlanName'] = 'default'
        else:
            vlan_dict[vlanid]['vlanName'] = 'VLAN-%s' % vlanid
        # We should update the portlists before using them
        vlan_dict[vlanid]['rowstatus'] = 1 # row status is always active
        vlan_dict[vlanid]['egressports'] = \
                  get_octetstring(vlan_dict[vlanid]['portindices'])
        vlan_dict[vlanid]['forbiddenports'] = '00'
        flags = vlan_dict[vlanid].get('flags', None)
        if flags and ('Egress Untagged' in flags):
            vlan_dict[vlanid]['untaggedports'] = \
                      get_octetstring(vlan_dict[vlanid]['portindices'])
        else:
            vlan_dict[vlanid]['untaggedports'] = '00'
    return vlan_dict

def get_fdb_info(fdbJson=None, brportArray=None):
    '''
    Create an fdbArray from the bridge fdb show JSON output and index it my
    MAC addresses.  The forwarding DB is bridge specific so we only show the
    information for ports in our bridge.
    '''
    # create an FDB array indexed by mac address
    status = {'other':1,
              'invalid':2,
              'learned':3,
              'self':4,
              'mgmt':5}
    fdbArray = {}
    for fdbEntry in fdbJson:
        machex = fdbEntry.get('mac', None) 
        device = fdbEntry.get('ifname', None)
        brport = brportArray.get(device, None)
        # we better have a port from our bridge and a mac or our
        # bridge fdb show is broken
        if brport and machex:
            fdbArray.setdefault(machex, {})
            # need dotted decimal mac for indexing later
            macdec = '.'.join([str(int(m, 16))for m in machex.split(':')])
            fdbArray[machex].setdefault('macdec', macdec)
            fdbArray[machex].setdefault('macoct', machex.replace(":", " "))
            device = fdbEntry.get('ifname', None)
            fdbArray[machex].setdefault('device', device)
            portindex = brportArray.get(device, {}).get('brportindex', 0)
            fdbArray[machex].setdefault('portindex', portindex)
            ifindex = int(open('/sys/class/net/%s/ifindex' % device,
                               'r').read().strip())
            fdbArray[machex].setdefault('ifindex', ifindex)
            fdbArray[machex].setdefault('master',
                                        fdbEntry.get('master', None))
            state = fdbEntry.get('state', None)
            if state == 'permanent':
                fdbArray[machex].setdefault('state', status['self'])
            else:
                fdbArray[machex].setdefault('state', status['learned'])
            fdbArray[machex].setdefault('vlan', fdbEntry.get('vlan', None))

    return fdbArray

def generate_dot1dBasePortTable(bridgeArray=None, brportList=None,
                                brportArray=None):
    '''
    Handle the dot1dBasePortTable. This handles any type of bridge
    since we are only showing the bridge mac and port ifindexes.
    '''
    brname = bridgeArray.get('name', None)
    if bridgeArray and brname:
        # there should be only 0 or 1 bridge at this point, grab the mac
        # address
        macString = open('/sys/class/net/%s/address' % brname,
                         'r').read().split()[0].replace(':', ' ')
        # 1.3.6.1.2.1.17.1.1
        # octets are simply hex strings separated by spaces
        pp.add_oct('%s.%s' % (dot1dBase, dot1dBaseBridgeAddress),
                   macString)

        # 1.3.6.1.2.1.17.1.2
        # just count the number of brports
        pp.add_int('%s.%s' % (dot1dBase, dot1dBaseNumPorts),
                   len(brportArray))
        # 1.3.6.1.2.1.17.1.3
        # we are only a transparent bridge
        pp.add_int('%s.%s' % (dot1dBase, dot1dBaseType), 2)

        # handle the 1.3.6.1.2.1.17.1.4.1.1, 1.4.1.2, 1.4.1.3 rows
        # and show the index of each port in bridge
        for myfunc, myobject, myindex in \
                [(pp.add_int, dot1dBasePort, 'brportindex'),
                 (pp.add_int, dot1dBasePortIfIndex, 'brportifindex'),
                 (pp.add_oid, dot1dBasePortCircuit, 'brportcircuit')]:
            for port in brportList:
                myfunc('%s.%s.%s.%s.%s' % \
                       (dot1dBase, dot1dBasePortTable,
                        dot1dBasePortEntry, myobject,
                        brportArray[port]['brportindex']),
                       brportArray[port][myindex])
        # we do not show dot1dBasePortDelayExceededDiscards(4) and
        # dot1dBasePortMtuExceededDiscards(5) because we have no way of getting
        # these numbers.  The kernel does not update the bridge statistics and
        # it is unclear which counters these might be kept in the physical ports
    return None

def generate_dot1dStpPortTable(mstpctlJson=None, brname=None,
                               brportList=None,
                               brportArray=None):
    '''
    If there is STP running, we should have access to the mstpctlJson output.
    Handle the 1.3.6.1.2.1.17.2.1 table.
    '''
    if mstpctlJson and brname and mstpctlJson.get(brname, None) \
           and mstpctlJson[brname].get(brname, None):
        # we only do IEEE802.1d
        pp.add_int('%s.%s'%(dot1dStp, dot1dStpProtocolSpecification), '3')

        # the priority is the first 2 bytes, remove the "." and convert
        # to decimal
        priority = int(mstpctlJson[brname][brname]
                       ["bridgeId"][0:6].replace('.', ''), 16)
        pp.add_int('%s.%s'%(dot1dStp, dot1dStpPriority), priority)

        pp.add_tt('%s.%s'%(dot1dStp, dot1dStpTimeSinceTopologyChange),
                  int(mstpctlJson[brname][brname]
                      ["timeSinceLastTopoChng"])*100)

        pp.add_cnt_32bit('%s.%s'%(dot1dStp, dot1dStpTopChanges),
                         mstpctlJson[brname][brname]["topoChngCounter"])


        # grab the root bridge and remove "." and ":", then add spaces to
        # make octet string (8)
        dsgnRoot = mstpctlJson[brname][brname]\
                   ["dsgnRoot"].replace(".", "").replace(":", "")
        dsgnRoot = ' '.join(dsgnRoot[i:i+2]
                            for i in range(0, len(dsgnRoot), 2))
        pp.add_oct('%s.%s'%(dot1dStp, dot1dStpDesignatedRoot), dsgnRoot)

        pp.add_int('%s.%s'%(dot1dStp, dot1dStpRootCost),
                   mstpctlJson[brname][brname]["pathCost"])

        rootPortName = mstpctlJson[brname][brname].get("rootPortName", None)
        rootPortIndex = brportArray.get(rootPortName, {}).get('brportindex', 0)
        pp.add_int('%s.%s'%(dot1dStp, dot1dStpRootPort), rootPortIndex)

        pp.add_int('%s.%s'%(dot1dStp, dot1dStpMaxAge),
                   mstpctlJson[brname][brname]["maxAge"]*100)

        pp.add_int('%s.%s'%(dot1dStp, dot1dStpHelloTime),
                   mstpctlJson[brname][brname]["helloTime"]*100)

        # 802.1Q 2011 it states that ieee8021SpanningTreeRstpTxHoldCount
        # is similar in value to dot1dStpTxHoldCount.
        # And in "Table 17-5-IEEE8021-SPANNING-TREE
        # MIB structure and relationship to IETF RFC 4318 and this standard
        # We can use Hold Time (8.5.3.14 of IEEE Std 802.1D, 1998 Edition)
        # or Transmission Limit
        # (TxHoldCount in 13.24.10).
        pp.add_int('%s.%s'%(dot1dStp, dot1dStpHoldTime),
                   mstpctlJson[brname][brname]["txHoldCounter"]*100)

        pp.add_int('%s.%s'%(dot1dStp, dot1dStpForwardDelay),
                   mstpctlJson[brname][brname]["fwdDelay"]*100)

        pp.add_int('%s.%s'%(dot1dStp, dot1dStpBridgeMaxAge),
                   mstpctlJson[brname][brname]["bridgeMaxAge"]*100)

        pp.add_int('%s.%s'%(dot1dStp, dot1dStpBridgeHelloTime),
                   mstpctlJson[brname][brname]["helloTime"]*100)

        pp.add_int('%s.%s'%(dot1dStp, dot1dStpBridgeForwardDelay),
                   mstpctlJson[brname][brname]["bridgeFwdDelay"]*100)

        # now show the bridge port mstpctl STP attributes
        # for some reason, mstpctl shows "discarding" when brctl shows
        # "blocking" so we make these the same
        converter = {'disabled':1,
                     'blocking':2,
                     'discarding':2,
                     'listening':3,
                     'learning':4,
                     'forwarding':5,
                     'broken':6,
                     True: 1,
                     False: 2}

        stpPortList = ['dot1dStpPort', 'dot1dStpPortPriority',
                       'dot1dStpPortState',
                       'dot1dStpPortEnable', 'dot1dStpPortPathCost',
                       'dot1dStpPortDesignatedRoot',
                       'dot1dStpPortDesignatedCost',
                       'dot1dStpPortDesignatedBridge',
                       'dot1dStpPortDesignatedPort',
                       'dot1dStpPortForwardTransitions']
        # not implementing 'dot1dStpPortPathCost32']

        brportAttrArray = {'dot1dStpPort': {'jsonName': 'dot1dStpPort',
                                            'oid': 1,
                                            'type': pp.add_int},
                           'dot1dStpPortPriority': {'jsonName':
                                                    'dot1dStpPortPriority',
                                                    'oid': 2,
                                                    'type': pp.add_int},
                           'dot1dStpPortState': {'jsonName': 'state',
                                                 'oid': 3,
                                                 'type': pp.add_int},
                           'dot1dStpPortEnable': {'jsonName': 'enabled',
                                                  'oid': 4,
                                                  'type': pp.add_int},
                           'dot1dStpPortPathCost': {'jsonName': 'extPortCost',
                                                    'oid': 5,
                                                    'type': pp.add_int},
                           'dot1dStpPortDesignatedRoot': {'jsonName':
                                                          'dsgnRegRoot',
                                                          'oid': 6,
                                                          'type': pp.add_oct},
                           'dot1dStpPortDesignatedCost': {'jsonName':
                                                          'dsgnExtCost',
                                                          'oid': 7,
                                                          'type': pp.add_int},
                           'dot1dStpPortDesignatedBridge': {'jsonName':
                                                            'dsgnBr',
                                                            'oid': 8,
                                                            'type': pp.add_oct},
                           'dot1dStpPortDesignatedPort': {'jsonName':
                                                          'dsgnPort',
                                                          'oid': 9,
                                                          'type': pp.add_oct},
                           'dot1dStpPortForwardTransitions': {'jsonName':
                                                     'numTransFwd',
                                                     'oid': 10,
                                                     'type': pp.add_cnt_32bit}
                          }
        # not sure about this last one, probably unsupported
        #'dot1dStpPortPathCost32': 'jsonName': None,
        #                           'oid': 11,
        #                           'type': pp.add_int

        # loop over all the entries
        for entryName in stpPortList:
            # now loop over the bridge ports
            for port in brportList:
                oid = brportAttrArray[entryName]['oid']
                myfunc = brportAttrArray[entryName]['type']
                jsonName = brportAttrArray[entryName]['jsonName']
                portId = mstpctlJson[brname][port].keys()[0]
                portindex = brportArray[port]['brportindex']
                mstpport = mstpctlJson[brname].get(port, {})
                if jsonName == 'dot1dStpPort':
                    value = portindex
                elif jsonName == 'dot1dStpPortPriority':
                    # the priority is the first byte of the portID
                    value = int(str(mstpport.keys()[0].split('.')[0]), 16)
                elif jsonName in ['dsgnRegRoot', 'dsgnBr', 'dsgnPort']:
                    # these octet type fields need special treatment
                    value = mstpport.get(portId, {}).get(jsonName, None)
                    value = value.replace(".", "").replace(":", "")
                    value = ' '.join(value[j:j+2]
                                     for j in range(0, len(value), 2))
                else:
                    # if the json value has no conversion, just return
                    # the number.
                    jsonvalue = mstpport.get(portId, {}).get(jsonName, None)
                    # we only convert strings, unicode and boolean values
                    if (isinstance(jsonvalue, bool) or
                            isinstance(jsonvalue, str) or
                            isinstance(jsonvalue, unicode)):
                        value = converter.get(jsonvalue, jsonvalue)
                    else:
                        value = jsonvalue

                myfunc('%s.%s.%s.%s.%s' % \
                       (dot1dStp, dot1dStpPortTable, dot1dStpPortEntry,
                        oid, portindex),
                       value)
    return None

def generate_dot1dTpFdbTable(fdbArray=None):
    '''
    Handle the Transparent bridge table.
    -- ---------------------------------------------------------- --
    --  The Forwarding Database for Transparent Bridges
    -- ---------------------------------------------------------- --
    1.3.6.1.2.1.17.4.3.1.1, 4.3.1.2, 4.3.1.3, etc.
    '''
    macs = fdbArray.keys()
    macs.sort()
    for myfunc, myindex, mystring in [(pp.add_oct, dot1dTpFdbAddress, "macoct"),
                                      (pp.add_int, dot1dTpFdbPort, "portindex"),
                                      (pp.add_int, dot1dTpFdbStatus, "state")]:
        # loop over all mac address for each row in the table
        for mac in macs:
            myfunc('%s.%s.%s.%s.%s' % (dot1dTp, dot1dTpFdbTable,
                                       dot1dTpFdbEntry,
                                       myindex, fdbArray[mac]['macdec']),
                   fdbArray[mac][mystring])
    return None

def generate_dot1qBase(vlanArray=None):
    '''
    The start of the Q-BRIDGE-MIB set of objects (dot1aBase) are generated here.
    '''
    numVlans = str(len(vlanArray.keys()))
    # 1.3.6.1.2.1.17.7.1.1, 7.1.2
    pp.add_int('%s.%s.%s.%s.0' % (qBridgeMIB, qBridgeMIBObjects, dot1qBase,
                                  dot1qVlanVersionNumber), '1')
    pp.add_int('%s.%s.%s.%s.0' % (qBridgeMIB, qBridgeMIBObjects, dot1qBase,
                                  dot1qMaxVlanId), '4094')
    pp.add_gau('%s.%s.%s.%s.0' % (qBridgeMIB, qBridgeMIBObjects, dot1qBase,
                                  dot1qMaxSupportedVlans), 4094)
    # since linux always shows 0 as a vlan, we need to count that too
    pp.add_gau('%s.%s.%s.%s.0' % (qBridgeMIB, qBridgeMIBObjects, dot1qBase,
                                  dot1qNumVlans), numVlans)
    # since we are not doing gvrp, we say it is disabled value is 2 7.1.1.5.0
    pp.add_int('%s.%s.%s.%s.0' % (qBridgeMIB, qBridgeMIBObjects, dot1qBase,
                                  dot1qGvrpStatus), '2')

    return None

def generate_dot1qTpFdbTable(fdbArray=None):
    '''
    Handle the set of tables for the forwarding database.  There are the
    following: 1.3.6.1.2.1.17.7.1.2.2.1.1,    7.1.2.2.1.2
    '''
    # we do support the forwarding databases
    macs = fdbArray.keys()
    macs.sort()
    # reuse the sorted mac list from just above
    for myfunc, myindex, mystring in [(pp.add_oct, dot1qTpFdbAddress, "macoct"),
                                      (pp.add_int, dot1qTpFdbPort, "portindex"),
                                      (pp.add_int, dot1qTpFdbStatus, "state")]:
        # loop over all mac address for each row in the table
        # Note: just before the dotted decimal mac you need an FDB ID of 1
        for mac in macs:
            myfunc('%s.%s.%s.%s.%s.%s.%s.%s' % \
                   (qBridgeMIB, qBridgeMIBObjects,
                    dot1qTp, dot1qTpFdbTable,
                    dot1qTpFdbEntry, myindex,
                    dot1qFdbId, fdbArray[mac]['macdec']),
                   fdbArray[mac][mystring])
    return None

def generate_dot1qVlanStaticTable(vlanArray=None):
    '''
    Handle the dot1qVlanStaticTable which is essentially these:
    1.3.6.1.2.1.17.7.1.4.3.1.1,  7.1.4.3.1.2, etc.
    '''
    vlans = [i for i in vlanArray.keys()]
    vlans.sort()
    for myfunc, myobject, myfield in \
            [(pp.add_str, dot1qVlanStaticName, 'vlanName'),
             (pp.add_oct, dot1qVlanStaticEgressPorts, 'egressports'),
             (pp.add_oct, dot1qVlanForbiddenEgressPorts, 'forbiddenports'),
             (pp.add_oct, dot1qVlanStaticUntaggedPorts, 'untaggedports'),
             (pp.add_int, dot1qVlanStaticRowStatus, 'rowstatus')]:
        for vlanid in vlans:
            myfunc('%s.%s.%s.%s.%s.%s.%s' % \
                   (qBridgeMIB, qBridgeMIBObjects,
                    dot1qVlan, dot1qVlanStaticTable, dot1qVlanStaticEntry,
                    myobject, vlanid), vlanArray[vlanid][myfield])
    return None

def generate_dot1qPortVlanTable(bridgeArray=None,
                                brportList=None,
                                brportArray=None):
    '''
    Handle the dot1qPortVlanTable which is indexed by interface port:
    1.3.6.1.2.1.17.7.1.4.5
    '''
    brname = bridgeArray.get('name', None)
    if bridgeArray and brname and brportList:
        for myfunc, myobject, myfield in \
                [(pp.add_gau, dot1qPvid, 'pvid'),
                 (pp.add_int, dot1qPortAcceptableFrameTypes,
                  'acceptableFrameTypes'),
                 (pp.add_int, dot1qPortIngressFiltering, 'ingressFiltering'),
                 (pp.add_int, dot1qPortGvrpStatus, 'gvrpStatus'),
                 (pp.add_cnt_32bit, dot1qPortGvrpFailedRegistrations,
                  'failedReg'),
                 (pp.add_oct, dot1qPortGvrpLastPduOrigin, 'lastPDU'),
                 (pp.add_int, dot1qPortRestrictedVlanRegistration,
                  'restrictedReg')]:
            for port in brportList:
                myfunc('%s.%s.%s.%s.%s.%s.%s' % \
                       (qBridgeMIB, qBridgeMIBObjects,
                        dot1qVlan, dot1qPortVlanTable,
                        dot1qPortVlanEntry,
                        myobject, brportArray[port]['brportindex']),
                       brportArray[port].get(myfield, None))
    return None

def update():
    '''
    This is the main method that will be called periodically.  This grabs the
    output of various subprocess commands and provides the agent with various
    tables.
    '''
    # grab the array output to be used for these mibs
    mstpctlJson = get_json_output(['/sbin/mstpctl', 'showall', 'json'])
    fdbJson = get_json_output(['/sbin/bridge', '-j', 'fdb', 'show'])
    # this only gives us VLANs for VLAN aware bridge
    vlanJson = get_json_output(['/sbin/bridge', '-j', 'vlan', 'show'])

    # create our bridge array
    bridgeArray = get_bridge_array()
    if not bridgeArray:
        # we only support vlan aware bridges, if found
        return

    bridgeName = bridgeArray.get('name', None)
    # the bridge ports are kept in a list of arrays
    brportList, brportArray = get_bridgeport_info(bridgeArray=bridgeArray)
    # we pass in the brportArray since this method will add the pvid
    vlanArray = get_vlan_info(vlanJson=vlanJson, brportArray=brportArray)
    fdbArray = get_fdb_info(fdbJson=fdbJson, brportArray=brportArray)

    # handle the 1.3.6.1.2.1.17.1.4.1.1, X.1.4.1.2, X.1.4.1.3 rows
    generate_dot1dBasePortTable(bridgeArray=bridgeArray, brportList=brportList,
                                brportArray=brportArray)

    # mostly 1.3.6.1.2.1.17.2.1
    generate_dot1dStpPortTable(mstpctlJson=mstpctlJson, brname=bridgeName,
                               brportList=brportList, brportArray=brportArray)

    # we do not handle Source Route Bridging
    # dot1dSr = 3
    # we should handle the Transparent Bridging tables if STP is off
    # dot1dTp = 4
    # this is the counter for macs discarded because of lack of space
    # not sure how to get this
    # dot1dTpLearnedEntryDiscards = 1
    # grab the aging time for the bridge
    # 1.3.6.1.2.1.17.4.2.0
    # dot1dTpAgingTime = 2
    pp.add_int('%s.%s.0' % (dot1dTp, dot1dTpAgingTime),
               bridgeArray.get('aging', 0))

    # handle 1.3.6.1.2.1.17.4.3.1.1, 4.3.1.2, 4.3.1.3, etc.
    generate_dot1dTpFdbTable(fdbArray=fdbArray)

    #-- ---------------------------------------------------------- --
    #-- The Static (Destination-Address Filtering) Database
    #-- ---------------------------------------------------------- --
    #-- Implementation of this subtree is optional.
    #-- ---------------------------------------------------------- --
    # we do not have a mac address filtering feature
    # dot1dStatic = 5

    ############################################################################
    # Now we handle some of the Q-BRIDGE-MIB Tables.
    ############################################################################
    generate_dot1qBase(vlanArray=vlanArray)

    generate_dot1qTpFdbTable(fdbArray=fdbArray)

    generate_dot1qVlanStaticTable(vlanArray=vlanArray)

    generate_dot1qPortVlanTable(bridgeArray=bridgeArray,
                                brportList=brportList,
                                brportArray=brportArray)

    return
################################################################################
pp = snmp.PassPersist(oid_base)
pp.debug = False

if pp.debug:
    # just run the update script once and print debugs to stdout
    update()
else:
    pp.start(update, 60)
