# 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. """ All of the MBIM request message type definitions are in this file. These definitions inherit from MBIMControlMessage. Reference: [1] Universal Serial Bus Communications Class Subclass Specification for Mobile Broadband Interface Model http://www.usb.org/developers/docs/devclass_docs/ MBIM10Errata1_073013.zip """ import logging import math from autotest_lib.client.cros.cellular.mbim_compliance import mbim_constants from autotest_lib.client.cros.cellular.mbim_compliance import mbim_errors from autotest_lib.client.cros.cellular.mbim_compliance import mbim_message class MBIMControlMessageRequest(mbim_message.MBIMControlMessage): """ MBIMMessage Request Message base class. """ MESSAGE_TYPE = mbim_message.MESSAGE_TYPE_REQUEST _FIELDS = (('I', 'message_type', mbim_message.FIELD_TYPE_PAYLOAD_ID), ('I', 'message_length', mbim_message.FIELD_TYPE_TOTAL_LEN), ('I', 'transaction_id', mbim_message.FIELD_TYPE_TRANSACTION_ID)) class MBIMOpen(MBIMControlMessageRequest): """ The class for MBIM_OPEN_MSG. """ _FIELDS = (('I', 'max_control_transfer', ''),) _DEFAULTS = {'message_type': mbim_constants.MBIM_OPEN_MSG} class MBIMClose(MBIMControlMessageRequest): """ The class for MBIM_CLOSE_MSG. """ _DEFAULTS = {'message_type': mbim_constants.MBIM_CLOSE_MSG} class MBIMCommandSecondary(MBIMControlMessageRequest): """ The class for MBIM_COMMAND_MSG. """ _FIELDS = (('I', 'total_fragments', mbim_message.FIELD_TYPE_NUM_FRAGMENTS), ('I', 'current_fragment', '')) class MBIMCommand(MBIMControlMessageRequest): """ The class for MBIM_COMMAND_MSG. """ _FIELDS = (('I', 'total_fragments', mbim_message.FIELD_TYPE_NUM_FRAGMENTS), ('I', 'current_fragment', ''), ('16s', 'device_service_id', mbim_message.FIELD_TYPE_PAYLOAD_ID), ('I', 'cid', mbim_message.FIELD_TYPE_PAYLOAD_ID), ('I', 'command_type', ''), ('I', 'information_buffer_length', mbim_message.FIELD_TYPE_PAYLOAD_LEN)) _DEFAULTS = {'message_type': mbim_constants.MBIM_COMMAND_MSG, 'total_fragments': 0x00000001, 'current_fragment': 0x00000000, 'information_buffer_length': 0} _SECONDARY_FRAGMENT = MBIMCommandSecondary class MBIMHostError(MBIMControlMessageRequest): """ The class for MBIM_ERROR_MSG. """ _FIELDS = (('I', 'error_status_code', ''),) _DEFAULTS = {'message_type': mbim_constants.MBIM_HOST_ERROR_MSG} def fragment_request_packets(message, max_fragment_length): """ Fragments request messages into a multiple fragment packets if the total message length is greater than the |max_fragment_length| specified by the device. It splits the payload_buffer fields into the primary and secondary fragments. @param message: Monolithic message object. @param max_fragment_length: Max length of each fragment expected by device. @returns List of fragmented packets. """ packets = [] # We may need to go up the message heirarchy level before fragmenting. So, # we need to recreate the primary fragment using the parent class. primary_frag_class = message.__class__.find_primary_parent_fragment() secondary_frag_class = primary_frag_class.get_secondary_fragment() if not secondary_frag_class: mbim_errors.log_and_raise( mbim_errors.MBIMComplianceControlMessageError, 'No secondary fragment class defined') # Let's recreate the primary frag object from the raw data of the # initial message. raw_data = message.create_raw_data() message = primary_frag_class(raw_data=raw_data) # Calculate the number of fragments we need. We divide the |payload_bufer| # between 1 primary and |num_fragments| secondary fragments. primary_struct_len = primary_frag_class.get_struct_len(get_all=True) secondary_struct_len = secondary_frag_class.get_struct_len(get_all=True) total_length = message.get_total_len() total_payload_length = message.get_payload_len() num_fragments = 1 remaining_payload_length = total_payload_length remaining_payload_buffer = message.payload_buffer primary_frag_length = max_fragment_length primary_payload_length = primary_frag_length - primary_struct_len remaining_payload_length -= primary_payload_length num_fragments += int( math.ceil(remaining_payload_length / float(max_fragment_length - secondary_struct_len))) # Truncate the payload of the primary message primary_message = message.copy( current_fragment=0, total_fragments=num_fragments, message_length=primary_frag_length) primary_message.payload_buffer = ( remaining_payload_buffer[:primary_payload_length]) packet = primary_message.create_raw_data() remaining_payload_buffer = ( remaining_payload_buffer[primary_payload_length:]) packets.append(packet) # Field values for secondary fragments are taken from the primary fragment # field values. args_list = {name : getattr(primary_message, name) for name in secondary_frag_class.get_field_names(get_all=True)} del args_list['message_length'] args_list['total_fragments'] = num_fragments for fragment_num in range(1, num_fragments): secondary_frag_length = min( max_fragment_length, remaining_payload_length + secondary_struct_len) secondary_payload_length = secondary_frag_length - secondary_struct_len remaining_payload_length -= secondary_payload_length args_list['current_fragment'] = fragment_num args_list['payload_buffer'] = ( remaining_payload_buffer[:secondary_payload_length]) secondary_message = secondary_frag_class(**args_list) packet = secondary_message.create_raw_data() remaining_payload_buffer = ( remaining_payload_buffer[secondary_payload_length:]) packets.append(packet) logging.debug('Fragmented request-> Fragments: %d, Total len: %d, ' 'Max Frag length: %d', num_fragments, total_length, max_fragment_length) return packets def generate_request_packets(message, max_fragment_length): """ Generates raw data corresponding to the incoming message request object. @param message: One of the defined MBIM request messages. @param max_fragment_length: Max length of each fragment expected by device. @returns Tuple of (packets, message), packets: List of raw byte array packets. """ if message.MESSAGE_TYPE != mbim_message.MESSAGE_TYPE_REQUEST: mbim_errors.log_and_raise( mbim_errors.MBIMComplianceControlMessageError, 'Not a valid request message (%s)' % message.__name__) message_class = message.__class__ if message.message_length < max_fragment_length: packet = message.create_raw_data() packets = [packet] else: packets = fragment_request_packets(message, max_fragment_length) logging.debug("Request Message generated: %s", message) return packets