1# Copyright 2015 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""
5All of the MBIM response message type definitions are in this file. These
6definitions inherit from MBIMControlMessage.
7
8Reference:
9    [1] Universal Serial Bus Communications Class Subclass Specification for
10        Mobile Broadband Interface Model
11        http://www.usb.org/developers/docs/devclass_docs/
12        MBIM10Errata1_073013.zip
13"""
14import logging
15
16from autotest_lib.client.cros.cellular.mbim_compliance import mbim_constants
17from autotest_lib.client.cros.cellular.mbim_compliance import mbim_errors
18from autotest_lib.client.cros.cellular.mbim_compliance import mbim_message
19
20
21class MBIMControlMessageResponse(mbim_message.MBIMControlMessage):
22    """ MBIMMessage Response Message base class. """
23
24    MESSAGE_TYPE = mbim_message.MESSAGE_TYPE_RESPONSE
25    _FIELDS = (('I', 'message_type', mbim_message.FIELD_TYPE_PAYLOAD_ID),
26               ('I', 'message_length', mbim_message.FIELD_TYPE_TOTAL_LEN),
27               ('I', 'transaction_id', ''))
28
29
30class MBIMOpenDone(MBIMControlMessageResponse):
31    """ The class for MBIM_OPEN_DONE. """
32
33    _FIELDS = (('I', 'status_codes', ''),)
34    _IDENTIFIERS = {'message_type': mbim_constants.MBIM_OPEN_DONE}
35
36
37class MBIMCloseDone(MBIMControlMessageResponse):
38    """ The class for MBIM_CLOSE_DONE. """
39
40    _FIELDS = (('I', 'status_codes', ''),)
41    _IDENTIFIERS = {'message_type': mbim_constants.MBIM_CLOSE_DONE}
42
43
44class MBIMCommandDoneSecondary(MBIMControlMessageResponse):
45    """ The class for MBIM_COMMAND_DONE. """
46
47    _FIELDS = (('I', 'total_fragments', mbim_message.FIELD_TYPE_NUM_FRAGMENTS),
48               ('I', 'current_fragment', ''))
49
50
51class MBIMCommandDone(MBIMControlMessageResponse):
52    """ The class for MBIM_COMMAND_DONE. """
53
54    _FIELDS = (('I', 'total_fragments', mbim_message.FIELD_TYPE_NUM_FRAGMENTS),
55               ('I', 'current_fragment', ''),
56               ('16s', 'device_service_id', mbim_message.FIELD_TYPE_PAYLOAD_ID),
57               ('I', 'cid', mbim_message.FIELD_TYPE_PAYLOAD_ID),
58               ('I', 'status_codes', ''),
59               ('I', 'information_buffer_length',
60                mbim_message.FIELD_TYPE_PAYLOAD_LEN))
61    _IDENTIFIERS = {'message_type': mbim_constants.MBIM_COMMAND_DONE}
62    _SECONDARY_FRAGMENT = MBIMCommandDoneSecondary
63
64
65class MBIMIndicateStatusSecondary(MBIMControlMessageResponse):
66    """ The class for MBIM_INDICATE_STATUS_MSG. """
67
68    _FIELDS = (('I', 'total_fragments', mbim_message.FIELD_TYPE_NUM_FRAGMENTS),
69               ('I', 'current_fragment', ''))
70
71
72class MBIMIndicateStatus(MBIMControlMessageResponse):
73    """ The class for MBIM_INDICATE_STATUS_MSG. """
74
75    _FIELDS = (('I', 'total_fragments', mbim_message.FIELD_TYPE_NUM_FRAGMENTS),
76               ('I', 'current_fragment', ''),
77               ('16s', 'device_service_id', mbim_message.FIELD_TYPE_PAYLOAD_ID),
78               ('I', 'cid', mbim_message.FIELD_TYPE_PAYLOAD_ID),
79               ('I', 'information_buffer_length',
80                mbim_message.FIELD_TYPE_PAYLOAD_LEN))
81    _IDENTIFIERS = {'message_type': mbim_constants.MBIM_INDICATE_STATUS_MSG}
82    _SECONDARY_FRAGMENT = MBIMIndicateStatusSecondary
83
84
85class MBIMFunctionError(MBIMControlMessageResponse):
86    """ The class for MBIM_FUNCTION_ERROR_MSG. """
87
88    _FIELDS = (('I', 'error_status_code', ''),)
89    _IDENTIFIERS = {'message_type': mbim_constants.MBIM_FUNCTION_ERROR_MSG}
90
91
92def reassemble_response_packets(primary_fragment, secondary_packets):
93    """
94    Reassembles fragmented response messages into a single object.
95
96    It parses all the secondary fragments as |secondary_frag_class| and
97    merges all the payload_buffer fields into the primary fragment.
98
99    @param primary_fragment: Primary fragment message object.
100    @param secondary_packets: Array of the raw byte array response received
101                               from device.
102    @returns Reassembled Response Message object.
103
104    """
105    secondary_frag_class = primary_fragment.get_secondary_fragment()
106    # Check if we can reassemble at this tree level or not. If there is
107    # no associated _SECONDARY_FRAG_CLASS, we need to go down the tree further
108    # to reassemble.
109    if not secondary_frag_class:
110        return None
111
112    for packet in secondary_packets:
113        secondary_fragment = secondary_frag_class(raw_data=packet)
114        primary_fragment.payload_buffer.extend(
115            secondary_fragment.payload_buffer)
116
117    payload_len = primary_fragment.get_payload_len()
118    num_fragments = primary_fragment.get_num_fragments()
119    if ((num_fragments != len(secondary_packets) + 1) or
120        (payload_len != len(primary_fragment.payload_buffer))):
121        mbim_errors.log_and_raise(mbim_errors.MBIMComplianceAssertionError,
122                                  'mbim1.0:9.2')
123    total_length = primary_fragment.calculate_total_len()
124    primary_fragment =  primary_fragment.copy(message_length=total_length)
125    logging.debug('Reassembled response-> Fragments: %d, Payload length: %d',
126                  num_fragments, payload_len)
127    return primary_fragment
128
129
130def parse_response_packets(packets):
131    """
132    Parses the incoming raw data |packets| into corresponding message response
133    object.
134
135    The function starts the at the root of the message hierarchy tree
136    and then goes down the root to find the exact leaf node message class. If
137    there are multiple frgaments expected at any level, it will reassemble the
138    secondary fragments before proceeding.
139
140    @param packets: Array of the raw byte array response received from device.
141    @returns Response Message object.
142
143    """
144    # Start with the root class for all responses and then go down the tree.
145    message_class = MBIMControlMessageResponse
146    parse_packets = packets
147
148    while message_class is not None:
149        first_packet = parse_packets[0]
150        message = message_class(raw_data=first_packet)
151        # If there are secondary fragments expected at this level,
152        # let's reassemble the payload together before traversing down the
153        # message heirarchy.
154        if len(parse_packets) > 1:
155            reassembled_message = reassemble_response_packets(message,
156                                                              parse_packets[1:])
157            if reassembled_message is not None:
158                message = reassembled_message
159                reassembled_packet = message.create_raw_data()
160                parse_packets = [reassembled_packet]
161        message_class = message.find_payload_class()
162    logging.debug("Response Message parsed: %s", message)
163    return message
164