# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import logging
import traceback

import common
from autotest_lib.client.common_lib import error


class MBIMComplianceError(error.TestFail):
    """ Base class for all errors overtly raised in the suite. """
    pass


class MBIMComplianceFrameworkError(MBIMComplianceError):
    """
    Errors raised by any of the framework code.

    These errors are raised by code that is not part of a test / sequence /
    assertion.

    """
    pass


class MBIMComplianceChannelError(MBIMComplianceError):
    """ Errors raised in the MBIM communication channel. """
    pass


class MBIMComplianceControlMessageError(MBIMComplianceError):
    """ Errors raised in the MBIM control module. """
    pass


class MBIMComplianceDataTransferError(MBIMComplianceError):
    """ Errors raised in the MBIM data transfer module. """
    pass


class MBIMComplianceNtbError(MBIMComplianceError):
    """ Errors raised in the MBIM NTB module. """
    pass


class MBIMComplianceTestError(MBIMComplianceError):
    """ Errors raised by compliance suite tests. """
    pass


class MBIMComplianceSequenceError(MBIMComplianceError):
    """ Errors raised by compliance suite sequences. """
    pass


class MBIMComplianceAssertionError(MBIMComplianceError):
    """ Errors raised by compliance suite assertions. """

    MBIM_ASSERTIONS = {
            # This key should not be used directly.
            # Raise |MBIMComplianceGenericAssertionError| instead.
            'no_code': '',

            # Assertion group: 3.x.x#x
            'mbim1.0:3.2.1#1': 'Functions that implement both NCM 1.0 and MBIM '
                               'shall provide two alternate settings for the '
                               'Communication Interface.',
            'mbim1.0:3.2.1#2': 'For alternate setting 0 of the Communication '
                               'Interface of an NCM/MBIM function: interface, '
                               'functional and endpoint descriptors shall be '
                               'constructed according to the rules given in '
                               '[USBNCM10].',
            'mbim1.0:3.2.1#3': 'For alternate setting 1 of the Communication '
                               'Interface of an NCM/MBIM function: interface, '
                               'functional and endpoint descriptors shall be '
                               'constructed according to the rules given in '
                               '[MBIM 1.0] section 6.',
            'mbim1.0:3.2.1#4': 'When alternate setting 0 of the Communiation'
                               'Interface of an NCM/MBIM function is selected, '
                               'the function shall operator according to the '
                               'NCM rules given in [USBNCM10].',
            'mbim1.0:3.2.1#5': 'When alternate setting 1 of the Communiation'
                               'Interface of an NCM/MBIM function is selected, '
                               'the function shall operator according to the '
                               'MBIM rules given in [MBIM1.0].',
            'mbim1.0:3.2.2.1#1': 'If an Interface Association Descriptor is '
                                 'used to form an NCM/MBIM function, its '
                                 'interface class, subclass, and protocol '
                                 'codes shall match those given in alternate '
                                 'setting 0 of the Communication Interface. ',
            'mbim1.0:3.2.2.4#1': 'Functions that implement both NCM 1.0 and '
                                 'MBIM (an "NCM/MBIM function") shall provide '
                                 'three alternate settings for the Data '
                                 'Interface.',
            'mbim1.0:3.2.2.4#2': 'For an NCM/MBIM function, the Data Interface '
                                 'descriptors for alternate settings 0 and 1 '
                                 'must have bInterfaceSubClass == 00h, and '
                                 'bInterfaceProtocol == 01h.',
            'mbim1.0:3.2.2.4#3': 'For an NCM/MBIM function, the Data Interface '
                                 'descriptor for alternate setting 2 must have '
                                 'bInterfaceSubClass == 00h, and '
                                 'bInterfaceProtocol == 02h.',
            'mbim1.0:3.2.2.4#4': 'For an NCM/MBIM function there must be no '
                                 'endpoints for alternate setting 0 of the '
                                 'Data Interface. For each of the other two '
                                 'alternate settings (1 and 2) there must be '
                                 'exactly two endpoints: one Bulk IN and one '
                                 'Bulk OUT.',

            # Assertion group: 6.x#x
            'mbim1.0:6.1#1': 'If an Interface Association Descriptor (IAD) is '
                             'provided for the MBIM function, the IAD and the '
                             'mandatory CDC Union Functional Descriptor '
                             'specified for the MBIM function shall group '
                             'together the same interfaces.',
            'mbim1.0:6.1#2': 'If an Interface Association Descriptor (IAD) is '
                             'provided for the MBIM only function, its '
                             'interface class, subclass, and protocol codes '
                             'shall match those given in the Communication '
                             'Interface descriptor.',
            'mbim1.0:6.3#1': 'The descriptor for alternate setting 0 of the '
                             'Communication Interface of an MBIM only function '
                             'shall have bInterfaceClass == 02h, '
                             'bInterfaceSubClass == 0Eh, and '
                             'bInterfaceProtocol == 00h.',
            'mbim1.0:6.3#2': 'MBIM Communication Interface description shall '
                             'include the following functional descriptors: '
                             'CDC Header Functional Descriptor, CDC Union '
                             'Functional Descriptor, and MBIM Functional '
                             'Descriptor. Refer to Table 6.2 of [USBMBIM10].',
            'mbim1.0:6.3#3': 'CDC Header Functional Descriptor shall appear '
                             'before CDC Union Functional Descriptor and '
                             'before MBIM Functional Descriptor.',
            'mbim1.0:6.3#4': 'CDC Union Functional Descriptor for an MBIM '
                             'function shall group together the MBIM '
                             'Communication Interface and the MBIM Data '
                             'Interface.',
            'mbim1.0:6.3#5': 'The class-specific descriptors must be followed '
                             'by an Interrupt IN endpoint descriptor.',
            'mbim1.0:6.4#1': 'Field wMaxControlMessage of MBIM Functional '
                             'Descriptor must not be smaller than 64.',
            'mbim1.0:6.4#2': 'Field bNumberFilters of MBIM Functional '
                             'Descriptor must not be smaller than 16.',
            'mbim1.0:6.4#3': 'Field bMaxFilterSize of MBIM Functional '
                             'Descriptor must not exceed 192.',
            'mbim1.0:6.4#4': 'Field wMaxSegmentSize of MBIM Functional '
                             'Descriptor must not be smaller than 2048.',
            'mbim1.0:6.4#5': 'Field bFunctionLength of MBIM Functional '
                             'Descriptor must be 12 representing the size of '
                             'the descriptor.',
            'mbim1.0:6.4#6': 'Field bcdMBIMVersion of MBIM Functional '
                             'Descriptor must be 0x0100 in little endian '
                             'format.',
            'mbim1.0:6.4#7': 'Field bmNetworkCapabilities of MBIM Functional '
                             'Descriptor should have the following bits set to '
                             'zero: D0, D1, D2, D4, D6 and D7.',
            'mbim1.0:6.5#1': 'If MBIM Extended Functional Descriptor is '
                             'provided, it must appear after MBIM Functional '
                             'Descriptor.',
            'mbim1.0:6.5#2': 'Field bFunctionLength of MBIM Extended '
                             'Functional Descriptor must be 8 representing the '
                             'size of the descriptor.',
            'mbim1.0:6.5#3': 'Field bcdMBIMEFDVersion of MBIM Extended '
                             'Functional Descriptor must be 0x0100 in little '
                             'endian format.',
            'mbim1.0:6.5#4': 'Field bMaxOutstandingCommandMessages of MBIM '
                             'Extended Functional Descriptor shall be greater '
                             'than 0.',
            'mbim1.0:6.6#1': 'The Data Interface for an MBIM only function '
                             'shall provide two alternate settings.',
            'mbim1.0:6.6#2': 'The first alternate setting for the Data '
                             'Interface of an MBIM only function (the default '
                             'interface setting, alternate setting 0) shall '
                             'include no endpoints.',
            'mbim1.0:6.6#3': 'The second alternate setting for the Data '
                             'Interface of an MBIM only function (alternate '
                             'setting 1) is used for normal operation, and '
                             'shall include one Bulk IN endpoint and one Bulk '
                             'OUT endpoint.',
            'mbim1.0:6.6#4': 'For an MBIM only function the Data Interface '
                             'descriptors for alternate settings 0 and 1 must '
                             'have bInterfaceSubClass == 00h, and '
                             'bInterfaceProtocol == 02h. Refer to Table 6.4 of '
                             '[USBMBIM10].',

            # Assertion Groups: 7.x.x#x
            'mbim1.0:7#1':   'To distinguish among the data streams, the last '
                             'character of the dwSignature in the NDP16 header '
                             'shall be coded with the index SessionId specified'
                             ' by the host in the MBIM_CID_CONNECT. The first '
                             'three symbols are encoded as ASCII characters in '
                             'little-endian form plus a last byte in HEX '
                             '(binary) format: "IPS"<SessionId>.',
            'mbim1.0:7#3':   'To distinguish among the data streams, the last '
                             'character of the dwSignature in the NDP32 header '
                             'shall be coded with the index SessionId specified'
                             ' by the host in the MBIM_CID_CONNECT. The first '
                             'three symbols are encoded as ASCII characters in '
                             'little-endian form plus a last byte in HEX '
                             '(binary) format: "ips"<SessionId>.',

            # Assertion Groups: 8.x.x#x
            'mbim1.0:8.1.2#2': 'The function must use a separate '
                               'GET_ENCAPSULATED_RESPONSE transfer for each '
                               'control message it has to send to the host.',
            'mbim1.0:8.1.2#3': 'The function must send a RESPONSE_AVAILABLE '
                               'notification for each available fragment of '
                               'ENCAPSULATED_RESPONSE to be read from the '
                               'default pipe.',

            # Assertion Groups: 9.x#x, 9.x.x and 9.x.x#x
            'mbim1.0:9.1#1':   'For notifications, the TransactionId must be '
                               'set to 0 by the function.',
            'mbim1.0:9.1#2':   'MessageLength in MBIM_MESSAGE_HEADER must be >='
                               ' 0x0C.',
            'mbim1.0:9.2':     'Function should fragment responses based on '
                               'MaxControlTransfer value from MBIM_OPEN_MSG.',
            'mbim1.0:9.3.1#1': 'In case MBIM_OPEN_MSG message is sent to a '
                               'function that is already opened, the function '
                               'shall interpret this as that the host and the '
                               'function are out of synchronization. The '
                               'function shall then perform the actions '
                               'dictated by the MBIM_CLOSE_MSG before it '
                               'performs the actions dictated by this '
                               'command.The function shall not send the '
                               'MBIM_CLOSE_DONE when the transition to the '
                               'Closed state has been completed. Only the '
                               ' MBIM_OPEN_DONE message is sent upon '
                               'successful completion of this message.',
            'mbim1.0:9.3.2#1': 'Between the host\'s sending MBIM_CLOSE_MSG '
                               'message and the function\'s completing the '
                               'request (acknowledged with MBIM_CLOSE_DONE), '
                               'the function shall ignore any MBIM control '
                               'messages it receives on the control plane or '
                               'the data on the bulk pipes.',
            'mbim1.0:9.3.2#2': 'The function shall not send any MBIM control '
                               'messages on the control plane or data on the '
                               'bulk pipes after completing '
                               'MBIM_CLOSE_MSG message (acknowledging it with '
                               'the MBIM_CLOSE_DONE message) with one '
                               'exception and that is MBIM_ERROR_NOT_OPENED.',
            'mbim1.0:9.3.2#3': 'On MBIM_CLOSE_MSG, any active context between '
                               'the function and the host shall be terminated ',
            'mbim1.0:9.3.4#2': 'An MBIM_FUNCTION_ERROR_MSG shall not make use '
                               'of a DataBuffer, so it cannot send any data '
                               'payload.',
            'mbim1.0:9.3.4#3': 'MBIM_ERROR_FRAGMENT_OUT_OF_SEQUENCE shall be '
                               'sent by the function if it detects a fragmented'
                               ' message out of sequence.',
            'mbim1.0:9.3.4.2#2': 'For MBIM_ERROR_FRAGMENT_OUT_OF_SEQUENCE, the'
                               ' TransactionId of the responding message must '
                               'match the TransactionId in the faulty '
                               'fragmented sequence.',
            'mbim1.0:9.3.4.2#3': 'In case of an out of a sequence error, the '
                               'function shall discard all the packets with '
                               'the same TransactionId as the faulty message '
                               'sequence.',
            'mbim1.0:9.3.4.2#4': 'If the function gets one more message that '
                               'is out of order for the same TransactionId, it '
                               'shall send a new error message with the same '
                               'TransactionId once more.',
            'mbim1.0:9.4.1#1': 'The function shall respond to the '
                               'MBIM_OPEN_MSG message with an MBIM_OPEN_DONE '
                               'message in which the TransactionId must match '
                               'the TransactionId in the MBIM_OPEN_MSG.',
            'mbim1.0:9.4.1#2': 'The Status field of MBIM_OPEN_DONE shall be '
                               'set to MBIM_STATUS_SUCCESS if the function '
                               'initialized successfully.',
            'mbim1.0:9.4.2#1': 'The function shall respond to the '
                               'MBIM_CLOSE_MSG message with an '
                               'MBIM_CLOSE_DONE message in which the '
                               'TransactionId must match the TransactionId in '
                               'the MBIM_CLOSE_MSG.',
            'mbim1.0:9.4.2#2': 'The Status field of MBIM_CLOSE_DONE shall '
                               'always be set to MBIM_STATUS_SUCCESS.',
            'mbim1.0:9.4.3': 'The function shall respond to '
                             'the MBIM_COMMAND_MSG message with an '
                             'MBIM_COMMAND_DONE message in which the '
                             'TransactionId must match the TransactionId in '
                             'the MBIM_COMMAND_MSG.',
            'mbim1.0:9.4.5#1': 'If the CID is successful, the function shall '
                               'set the Status field to MBIM_STATUS_SUCCESS '
                               'in the MBIM_COMMAND_DONE.',
            'mbim1.0:9.4.5#2': 'If the function does not implement the CID, '
                               'then the function shall fail the request with '
                               'MBIM_STATUS_NO_DEVICE_SUPPORT.',
            'mbim1.0:9.4.5#3': 'If the Status field returned to the host is '
                               'not equal to MBIM_STATUS_SUCCESS, the function '
                               'must set the Information BufferLength to 0, '
                               'indicating an empty InformationBuffer except '
                               'the following CIDs: MBIM_CID_REGISTER_STATE, '
                               'MBIM_CID_PACKET_SERVICE, MBIM_CID_CONNECT, '
                               'MBIM_CID_SERVICE_ACTIVATION.',
            'mbim1.0:9.5#1':   'Function should transmit fragmented message to '
                               'host without intermixing fragments from other '
                               'messages.',
            'mbim1.0:10.3#2':  'The function shall reject incoming messages '
                               'that dont follow the rules for variable-length'
                               ' encoding by setting '
                               'MBIM_STATUS_INVALID_PARAMETERS as the status '
                               'code in the MBIM_COMMAND_DONE message.',
            'mbim1.0:10.5.1.3#1': 'Functions that support CDMA must specify '
                               'MBIMCtrlCapsCdmaMobileIP or '
                               'MBIMCtrlCapsCdmaSimpleIP or both flags to '
                               'inform the host about the type of IP that the '
                               'function supports.',

            # NCM Assertion group: 3.x.x#x
            'ncm1.0:3.2.1#1':  'The first four bytes in NTH16 shall be '
                               '0x484D434E in little-endian format ("NCMH").',
            'ncm1.0:3.2.1#2':  'wHeaderLength value in NTH16 shall be 0x000C.',
            'ncm1.0:3.2.1#3':  'wSequence in NTH16 shall be set to zero by the '
                               'function in the first NTB transferred after '
                               'every "function reset" event.',
            'ncm1.0:3.2.1#4':  'wSequence value in NTH16 shall be incremented '
                               'for every NTB subsequent transfer.',
            'ncm1.0:3.2.1#5':  'NTB size (IN) shall not exceed dwNtbInMaxSize.',
            'ncm1.0:3.2.1#6':  'wNdpIndex value in NTH16 must be a multiple of '
                               '4, and must be >= 0x000C, in little endian.',
            'ncm1.0:3.2.2#1':  'The first four bytes in NTH32 shall be '
                               '0x686D636E in little-endian format ("ncmh").',
            'ncm1.0:3.2.2#2':  'wHeaderLength value in NTH32 shall be 0x0010.',
            'ncm1.0:3.2.2#3':  'wSequence in NTH32 shall be set to zero by the '
                               'function in the first NTB transferred after '
                               'every "function reset" event.',
            'ncm1.0:3.2.2#4':  'wSequence value in NTH32 shall be incremented '
                               'for every NTB subsequent transfer.',
            'ncm1.0:3.2.2#5':  'NTB size (IN) shall not exceed dwNtbInMaxSize.',
            'ncm1.0:3.2.2#6':  'dwNdpIndex value in NTH32 must be a multiple of'
                               ' 4, and must be >= 0x0010, in little endian.',
            'ncm1.0:3.3.1#1':  'wLength value in the NDP16 must be a multiple '
                               'of 4, and must be at least 16d (0x0010).',
            'ncm1.0:3.3.1#2':  'wDatagramIndex[0] value in NDP16 must be >= '
                               '0x000C (because it must point past the NTH16).',
            'ncm1.0:3.3.1#3':  'wDatagramLength[0] value in NDP16 must be >= '
                               '20d if datagram payload is IPv4 and >= 40d if '
                               'datagram payload is IPv6.',
            'ncm1.0:3.3.1#4':  'wDatagramIndex[(wLength-8)/4 - 1] value in '
                               'NDP16 must be zero.',
            'ncm1.0:3.3.1#5':  'wDatagramLength[(wLength-8)/4 - 1] value in '
                               'NDP16 must be zero.',
            'ncm1.0:3.3.2#1':  'wLength value in the NDP32 must be a multiple '
                               'of 8, and must be at least 16d (0x0020).',
            'ncm1.0:3.3.2#2':  'dwDatagramIndex[0] value in NDP32 must be >= '
                               '0x0010 (because it must point past the NTH32).',
            'ncm1.0:3.3.2#3':  'dwDatagramLength[0] value in NDP32 must be >= '
                               '20d if datagram payload is IPv4 and >= 40d if '
                               'datagram payload is IPv6.',
            'ncm1.0:3.3.2#4':  'dwDatagramIndex[(wLength-8)/4 - 1] value in '
                               'NDP32 must be zero.',
            'ncm1.0:3.3.2#5':  'dwDatagramLength[(wLength-8)/4 - 1] value in '
                               'NDP32 must be zero.',
    }

    def __init__(self, assertion_id, error_string=None):
        """
        @param assertion_id: A str that must be a key in the MBIM_ASSERTIONS map
                defined in this class.
        @param error_string: An optional str to be appended to the error
                description.

        For example,
            MBIMComplianceAssertionError('mbim1.0:3.2.1#1')
            raises an error associated with assertion [MBIM 1.0]-3.2.1#1

        """
        if assertion_id not in self.MBIM_ASSERTIONS:
            log_and_raise(MBIMComplianceFrameworkError,
                          'Unknown assertion id "%s"' % assertion_id)

        message = '[%s]: %s' % (assertion_id,
                                self.MBIM_ASSERTIONS[assertion_id])
        if error_string:
            message += ': %s' % error_string

        super(MBIMComplianceAssertionError, self).__init__(message)


class MBIMComplianceGenericAssertionError(MBIMComplianceAssertionError):
    """ Assertion errors that don't map directly to an MBIM assertion. """
    def __init__(self, error_string):
        """
        @param error_string: A description of the error.
        """
        super(MBIMComplianceGenericAssertionError, self).__init__(
                'no_code',
                error_string)


def log_and_raise(error_class, *args):
    """
    Log and raise an error.

    This function should be used to raise all errors.

    @param error_class: An Exception subclass to raise.
    @param *args: Arguments to be passed to the error class constructor.
    @raises: |error_class|.

    """
    error_object = error_class(*args)
    logging.error(error_object)
    trace = traceback.format_stack()
    # Get rid of the current frame from trace
    trace = trace[:len(trace)-1]
    logging.error('Traceback:\n' + ''.join(trace))
    raise error_object