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 request 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
15import math
16
17from autotest_lib.client.cros.cellular.mbim_compliance import mbim_constants
18from autotest_lib.client.cros.cellular.mbim_compliance import mbim_errors
19from autotest_lib.client.cros.cellular.mbim_compliance import mbim_message
20
21
22class MBIMControlMessageRequest(mbim_message.MBIMControlMessage):
23    """ MBIMMessage Request Message base class. """
24    MESSAGE_TYPE = mbim_message.MESSAGE_TYPE_REQUEST
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', mbim_message.FIELD_TYPE_TRANSACTION_ID))
28
29
30class MBIMOpen(MBIMControlMessageRequest):
31    """ The class for MBIM_OPEN_MSG. """
32
33    _FIELDS = (('I', 'max_control_transfer', ''),)
34    _DEFAULTS = {'message_type': mbim_constants.MBIM_OPEN_MSG}
35
36
37class MBIMClose(MBIMControlMessageRequest):
38    """ The class for MBIM_CLOSE_MSG. """
39
40    _DEFAULTS = {'message_type': mbim_constants.MBIM_CLOSE_MSG}
41
42
43class MBIMCommandSecondary(MBIMControlMessageRequest):
44    """ The class for MBIM_COMMAND_MSG. """
45
46    _FIELDS = (('I', 'total_fragments', mbim_message.FIELD_TYPE_NUM_FRAGMENTS),
47               ('I', 'current_fragment', ''))
48
49
50class MBIMCommand(MBIMControlMessageRequest):
51    """ The class for MBIM_COMMAND_MSG. """
52
53    _FIELDS = (('I', 'total_fragments', mbim_message.FIELD_TYPE_NUM_FRAGMENTS),
54               ('I', 'current_fragment', ''),
55               ('16s', 'device_service_id', mbim_message.FIELD_TYPE_PAYLOAD_ID),
56               ('I', 'cid', mbim_message.FIELD_TYPE_PAYLOAD_ID),
57               ('I', 'command_type', ''),
58               ('I', 'information_buffer_length',
59                mbim_message.FIELD_TYPE_PAYLOAD_LEN))
60    _DEFAULTS = {'message_type': mbim_constants.MBIM_COMMAND_MSG,
61                 'total_fragments': 0x00000001,
62                 'current_fragment': 0x00000000,
63                 'information_buffer_length': 0}
64    _SECONDARY_FRAGMENT = MBIMCommandSecondary
65
66
67class MBIMHostError(MBIMControlMessageRequest):
68    """ The class for MBIM_ERROR_MSG. """
69
70    _FIELDS = (('I', 'error_status_code', ''),)
71    _DEFAULTS = {'message_type': mbim_constants.MBIM_HOST_ERROR_MSG}
72
73
74def fragment_request_packets(message, max_fragment_length):
75    """
76    Fragments request messages into a multiple fragment packets if the total
77    message length is greater than the |max_fragment_length| specified by the
78    device.
79
80    It splits the payload_buffer fields into the primary and secondary
81    fragments.
82
83    @param message: Monolithic message object.
84    @param max_fragment_length: Max length of each fragment expected by device.
85    @returns List of fragmented packets.
86
87    """
88    packets = []
89    # We may need to go up the message heirarchy level before fragmenting. So,
90    # we need to recreate the primary fragment using the parent class.
91    primary_frag_class = message.__class__.find_primary_parent_fragment()
92    secondary_frag_class = primary_frag_class.get_secondary_fragment()
93    if not secondary_frag_class:
94        mbim_errors.log_and_raise(
95                mbim_errors.MBIMComplianceControlMessageError,
96                'No secondary fragment class defined')
97    # Let's recreate the primary frag object from the raw data of the
98    # initial message.
99    raw_data = message.create_raw_data()
100    message = primary_frag_class(raw_data=raw_data)
101
102    # Calculate the number of fragments we need. We divide the |payload_bufer|
103    # between 1 primary and |num_fragments| secondary fragments.
104    primary_struct_len = primary_frag_class.get_struct_len(get_all=True)
105    secondary_struct_len = secondary_frag_class.get_struct_len(get_all=True)
106    total_length = message.get_total_len()
107    total_payload_length = message.get_payload_len()
108    num_fragments = 1
109    remaining_payload_length = total_payload_length
110    remaining_payload_buffer = message.payload_buffer
111
112    primary_frag_length = max_fragment_length
113    primary_payload_length =  primary_frag_length - primary_struct_len
114    remaining_payload_length -= primary_payload_length
115    num_fragments += int(
116            math.ceil(remaining_payload_length /
117                      float(max_fragment_length - secondary_struct_len)))
118
119    # Truncate the payload of the primary message
120    primary_message = message.copy(
121            current_fragment=0,
122            total_fragments=num_fragments,
123            message_length=primary_frag_length)
124    primary_message.payload_buffer = (
125            remaining_payload_buffer[:primary_payload_length])
126    packet = primary_message.create_raw_data()
127    remaining_payload_buffer = (
128            remaining_payload_buffer[primary_payload_length:])
129    packets.append(packet)
130
131    # Field values for secondary fragments are taken from the primary fragment
132    # field values.
133    args_list = {name : getattr(primary_message, name)
134                 for name in secondary_frag_class.get_field_names(get_all=True)}
135    del args_list['message_length']
136    args_list['total_fragments'] = num_fragments
137    for fragment_num in range(1, num_fragments):
138        secondary_frag_length = min(
139                max_fragment_length,
140                remaining_payload_length + secondary_struct_len)
141        secondary_payload_length = secondary_frag_length - secondary_struct_len
142        remaining_payload_length -= secondary_payload_length
143        args_list['current_fragment'] = fragment_num
144        args_list['payload_buffer'] = (
145                remaining_payload_buffer[:secondary_payload_length])
146        secondary_message = secondary_frag_class(**args_list)
147        packet = secondary_message.create_raw_data()
148        remaining_payload_buffer = (
149                remaining_payload_buffer[secondary_payload_length:])
150        packets.append(packet)
151    logging.debug('Fragmented request-> Fragments: %d, Total len: %d, '
152                  'Max Frag length: %d', num_fragments, total_length,
153                  max_fragment_length)
154    return packets
155
156
157def generate_request_packets(message, max_fragment_length):
158    """
159    Generates raw data corresponding to the incoming message request object.
160
161    @param message: One of the defined MBIM request messages.
162    @param max_fragment_length: Max length of each fragment expected by device.
163    @returns Tuple of (packets, message),
164            packets: List of raw byte array packets.
165
166    """
167    if message.MESSAGE_TYPE != mbim_message.MESSAGE_TYPE_REQUEST:
168        mbim_errors.log_and_raise(
169                mbim_errors.MBIMComplianceControlMessageError,
170                'Not a valid request message (%s)' % message.__name__)
171    message_class = message.__class__
172    if message.message_length < max_fragment_length:
173        packet = message.create_raw_data()
174        packets = [packet]
175    else:
176        packets = fragment_request_packets(message, max_fragment_length)
177    logging.debug("Request Message generated: %s", message)
178    return packets
179