# View-based Access Control Model
from pysnmp.smi.error import NoSuchObjectError
from pysnmp.proto import errind, error
from pysnmp import debug

accessModelID = 3

__powOfTwoSeq = [128, 64, 32, 16, 8, 4, 2, 1]

# 3.2
def isAccessAllowed(
    snmpEngine,
    securityModel,
    securityName,
    securityLevel,
    viewType,
    contextName,
    variableName):
    mibInstrumController = snmpEngine.msgAndPduDsp.mibInstrumController

    debug.logger & debug.flagACL and debug.logger('isAccessAllowed: viewType %s for variableName %s' % (viewType, variableName))

    # 3.2.1
    vacmContextEntry, = mibInstrumController.mibBuilder.importSymbols('SNMP-VIEW-BASED-ACM-MIB', 'vacmContextEntry')
    tblIdx = vacmContextEntry.getInstIdFromIndices(contextName)
    try:
        vacmContextName = vacmContextEntry.getNode(
            vacmContextEntry.name + (1,) + tblIdx
            ).syntax
    except NoSuchObjectError:
        raise error.StatusInformation(errorIndication=errind.noSuchContext)    

    # 3.2.2
    vacmSecurityToGroupEntry, = mibInstrumController.mibBuilder.importSymbols(
        'SNMP-VIEW-BASED-ACM-MIB', 'vacmSecurityToGroupEntry'
        )
    tblIdx = vacmSecurityToGroupEntry.getInstIdFromIndices(
        securityModel, securityName
        )
    try:
        vacmGroupName = vacmSecurityToGroupEntry.getNode(
            vacmSecurityToGroupEntry.name + (3,) + tblIdx
            ).syntax
    except NoSuchObjectError:
        raise error.StatusInformation(errorIndication=errind.noGroupName)

    # 3.2.3
    vacmAccessEntry, = mibInstrumController.mibBuilder.importSymbols(
        'SNMP-VIEW-BASED-ACM-MIB', 'vacmAccessEntry'
        )
    # XXX partial context name match
    tblIdx = vacmAccessEntry.getInstIdFromIndices(
        vacmGroupName, contextName, securityModel, securityLevel
        )

    # 3.2.4
    if viewType == 'read':
        entryIdx = vacmAccessEntry.name + (5,) + tblIdx
    elif viewType == 'write':
        entryIdx = vacmAccessEntry.name + (6,) + tblIdx
    elif viewType == 'notify':
        entryIdx = vacmAccessEntry.name + (7,) + tblIdx
    else:
        raise error.ProtocolError('Unknown view type %s' % viewType)

    try:
        viewName = vacmAccessEntry.getNode(entryIdx).syntax
    except NoSuchObjectError:
        raise error.StatusInformation(errorIndication=errind.noAccessEntry)
    if not len(viewName):
        raise error.StatusInformation(errorIndication=errind.noSuchView)

    # XXX split onto object & instance ?
    
    # 3.2.5a
    vacmViewTreeFamilyEntry, = mibInstrumController.mibBuilder.importSymbols('SNMP-VIEW-BASED-ACM-MIB', 'vacmViewTreeFamilyEntry')
    tblIdx = vacmViewTreeFamilyEntry.getInstIdFromIndices(viewName)

    # Walk over entries
    initialTreeName = treeName = vacmViewTreeFamilyEntry.name + (2,) + tblIdx
    maskName = vacmViewTreeFamilyEntry.name + (3,) + tblIdx
    while 1:
        vacmViewTreeFamilySubtree = vacmViewTreeFamilyEntry.getNextNode(
            treeName
            )
        vacmViewTreeFamilyMask = vacmViewTreeFamilyEntry.getNextNode(
            maskName
            )
        treeName = vacmViewTreeFamilySubtree.name
        maskName = vacmViewTreeFamilyMask.name
        if initialTreeName != treeName[:len(initialTreeName)]:
            # 3.2.5b
            raise error.StatusInformation(errorIndication=errind.notInView)
        l = len(vacmViewTreeFamilySubtree.syntax)
        if l > len(variableName):
            continue
        if vacmViewTreeFamilyMask.syntax:
            mask = []
            for c in vacmViewTreeFamilyMask.syntax.asNumbers():
                mask = mask + [ b&c for b in __powOfTwoSeq ]
            m = len(mask)-1
            idx = l-1
            while idx:
                if idx > m or mask[idx] and \
                   vacmViewTreeFamilySubtree.syntax[idx] != variableName[idx]:
                    break
                idx = idx - 1
            if idx: continue # no match
        else: # no mask
            if vacmViewTreeFamilySubtree.syntax != variableName[:l]:
                continue # no match
        # 3.2.5c
        return error.StatusInformation(errorIndication=errind.accessAllowed)
