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