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