1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.usb.descriptors;
17 
18 import android.annotation.NonNull;
19 import android.hardware.usb.UsbConstants;
20 import android.hardware.usb.UsbDeviceConnection;
21 import android.service.usb.UsbGroupTerminalBlockProto;
22 import android.service.usb.UsbMidiBlockParserProto;
23 import android.util.Log;
24 
25 import com.android.internal.util.dump.DualDumpOutputStream;
26 
27 import java.util.ArrayList;
28 
29 /**
30  * @hide
31  * A class to parse Block Descriptors
32  * see midi20.pdf section 5.4
33  */
34 public class UsbMidiBlockParser {
35     private static final String TAG = "UsbMidiBlockParser";
36 
37     // Block header size
38     public static final int MIDI_BLOCK_HEADER_SIZE = 5;
39     public static final int MIDI_BLOCK_SIZE = 13;
40     public static final int REQ_GET_DESCRIPTOR = 0x06;
41     public static final int CS_GR_TRM_BLOCK = 0x26;     // Class-specific GR_TRM_BLK
42     public static final int GR_TRM_BLOCK_HEADER = 0x01; // Group block header
43     public static final int REQ_TIMEOUT_MS = 2000;      // 2 second timeout
44     public static final int DEFAULT_MIDI_TYPE = 1;      // Default MIDI type
45 
46     protected int mHeaderLength;            // 0:1 Size of header descriptor
47     protected int mHeaderDescriptorType;    // 1:1 Descriptor Type
48     protected int mHeaderDescriptorSubtype; // 2:1 Descriptor Subtype
49     protected int mTotalLength;             // 3:2 Total Length of header and blocks
50 
51     static class GroupTerminalBlock {
52         protected int mLength;                  // 0:1 Size of descriptor
53         protected int mDescriptorType;          // 1:1 Descriptor Type
54         protected int mDescriptorSubtype;       // 2:1 Descriptor Subtype
55         protected int mGroupBlockId;            // 3:1 Id of Group Terminal Block
56         protected int mGroupTerminalBlockType;  // 4:1 bi-directional, IN, or OUT
57         protected int mGroupTerminal;           // 5:1 Group Terminal Number
58         protected int mNumGroupTerminals;       // 6:1 Number of Group Terminals
59         protected int mBlockItem;               // 7:1 ID of STRING descriptor of Block item
60         protected int mMidiProtocol;            // 8:1 MIDI protocol
61         protected int mMaxInputBandwidth;       // 9:2 Max Input Bandwidth
62         protected int mMaxOutputBandwidth;      // 11:2 Max Output Bandwidth
63 
parseRawDescriptors(ByteStream stream)64         public int parseRawDescriptors(ByteStream stream) {
65             mLength = stream.getUnsignedByte();
66             mDescriptorType = stream.getUnsignedByte();
67             mDescriptorSubtype = stream.getUnsignedByte();
68             mGroupBlockId = stream.getUnsignedByte();
69             mGroupTerminalBlockType = stream.getUnsignedByte();
70             mGroupTerminal = stream.getUnsignedByte();
71             mNumGroupTerminals = stream.getUnsignedByte();
72             mBlockItem = stream.getUnsignedByte();
73             mMidiProtocol = stream.getUnsignedByte();
74             mMaxInputBandwidth = stream.unpackUsbShort();
75             mMaxOutputBandwidth = stream.unpackUsbShort();
76             return mLength;
77         }
78 
79         /**
80          * Write the state of the block to a dump stream.
81          */
dump(@onNull DualDumpOutputStream dump, @NonNull String idName, long id)82         public void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) {
83             long token = dump.start(idName, id);
84 
85             dump.write("length", UsbGroupTerminalBlockProto.LENGTH, mLength);
86             dump.write("descriptor_type", UsbGroupTerminalBlockProto.DESCRIPTOR_TYPE,
87                     mDescriptorType);
88             dump.write("descriptor_subtype", UsbGroupTerminalBlockProto.DESCRIPTOR_SUBTYPE,
89                     mDescriptorSubtype);
90             dump.write("group_block_id", UsbGroupTerminalBlockProto.GROUP_BLOCK_ID, mGroupBlockId);
91             dump.write("group_terminal_block_type",
92                     UsbGroupTerminalBlockProto.GROUP_TERMINAL_BLOCK_TYPE, mGroupTerminalBlockType);
93             dump.write("group_terminal", UsbGroupTerminalBlockProto.GROUP_TERMINAL,
94                     mGroupTerminal);
95             dump.write("num_group_terminals", UsbGroupTerminalBlockProto.NUM_GROUP_TERMINALS,
96                     mNumGroupTerminals);
97             dump.write("block_item", UsbGroupTerminalBlockProto.BLOCK_ITEM, mBlockItem);
98             dump.write("midi_protocol", UsbGroupTerminalBlockProto.MIDI_PROTOCOL, mMidiProtocol);
99             dump.write("max_input_bandwidth", UsbGroupTerminalBlockProto.MAX_INPUT_BANDWIDTH,
100                     mMaxInputBandwidth);
101             dump.write("max_output_bandwidth", UsbGroupTerminalBlockProto.MAX_OUTPUT_BANDWIDTH,
102                     mMaxOutputBandwidth);
103 
104             dump.end(token);
105         }
106     }
107 
108     private ArrayList<GroupTerminalBlock> mGroupTerminalBlocks =
109             new ArrayList<GroupTerminalBlock>();
110 
UsbMidiBlockParser()111     public UsbMidiBlockParser() {
112     }
113 
114     /**
115      * Parses a raw ByteStream into a block terminal descriptor.
116      * The header is parsed before each block is parsed.
117      * @param   stream  ByteStream to parse
118      * @return          The total length that has been parsed.
119      */
parseRawDescriptors(ByteStream stream)120     public int parseRawDescriptors(ByteStream stream) {
121         mHeaderLength = stream.getUnsignedByte();
122         mHeaderDescriptorType = stream.getUnsignedByte();
123         mHeaderDescriptorSubtype = stream.getUnsignedByte();
124         mTotalLength = stream.unpackUsbShort();
125 
126         while (stream.available() >= MIDI_BLOCK_SIZE) {
127             GroupTerminalBlock block = new GroupTerminalBlock();
128             block.parseRawDescriptors(stream);
129             mGroupTerminalBlocks.add(block);
130         }
131 
132         return mTotalLength;
133     }
134 
135     /**
136      * Calculates the MIDI type through querying the device twice, once for the size
137      * of the block descriptor and once for the block descriptor. This descriptor is
138      * then parsed to return the MIDI type.
139      * See the MIDI 2.0 USB doc for more info.
140      * @param  connection               UsbDeviceConnection to send the request
141      * @param  interfaceNumber          The interface number to query
142      * @param  alternateInterfaceNumber The alternate interface of the interface
143      * @return                          The MIDI type as an int.
144      */
calculateMidiType(UsbDeviceConnection connection, int interfaceNumber, int alternateInterfaceNumber)145     public int calculateMidiType(UsbDeviceConnection connection, int interfaceNumber,
146             int alternateInterfaceNumber) {
147         byte[] byteArray = new byte[MIDI_BLOCK_HEADER_SIZE];
148         try {
149             // This first request is simply to get the full size of the descriptor.
150             // This info is stored in the last two bytes of the header.
151             int rdo = connection.controlTransfer(
152                     UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD
153                             | UsbConstants.USB_CLASS_AUDIO,
154                     REQ_GET_DESCRIPTOR,
155                     (CS_GR_TRM_BLOCK << 8) + alternateInterfaceNumber,
156                     interfaceNumber,
157                     byteArray,
158                     MIDI_BLOCK_HEADER_SIZE,
159                     REQ_TIMEOUT_MS);
160             if (rdo > 0) {
161                 if (byteArray[1] != CS_GR_TRM_BLOCK) {
162                     Log.e(TAG, "Incorrect descriptor type: " + byteArray[1]);
163                     return DEFAULT_MIDI_TYPE;
164                 }
165                 if (byteArray[2] != GR_TRM_BLOCK_HEADER) {
166                     Log.e(TAG, "Incorrect descriptor subtype: " + byteArray[2]);
167                     return DEFAULT_MIDI_TYPE;
168                 }
169                 int newSize = (((int) byteArray[3]) & (0xff))
170                         + ((((int) byteArray[4]) & (0xff)) << 8);
171                 if (newSize <= 0) {
172                     Log.e(TAG, "Parsed a non-positive block terminal size: " + newSize);
173                     return DEFAULT_MIDI_TYPE;
174                 }
175                 byteArray = new byte[newSize];
176                 rdo = connection.controlTransfer(
177                         UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD
178                                 | UsbConstants.USB_CLASS_AUDIO,
179                         REQ_GET_DESCRIPTOR,
180                         (CS_GR_TRM_BLOCK << 8) + alternateInterfaceNumber,
181                         interfaceNumber,
182                         byteArray,
183                         newSize,
184                         REQ_TIMEOUT_MS);
185                 if (rdo > 0) {
186                     ByteStream stream = new ByteStream(byteArray);
187                     parseRawDescriptors(stream);
188                     if (mGroupTerminalBlocks.isEmpty()) {
189                         Log.e(TAG, "Group Terminal Blocks failed parsing: " + DEFAULT_MIDI_TYPE);
190                         return DEFAULT_MIDI_TYPE;
191                     } else {
192                         Log.d(TAG, "MIDI protocol: " + mGroupTerminalBlocks.get(0).mMidiProtocol);
193                         return mGroupTerminalBlocks.get(0).mMidiProtocol;
194                     }
195                 } else {
196                     Log.e(TAG, "second transfer failed: " + rdo);
197                 }
198             } else {
199                 Log.e(TAG, "first transfer failed: " + rdo);
200             }
201         } catch (Exception e) {
202             Log.e(TAG, "Can not communicate with USB device", e);
203         }
204         return DEFAULT_MIDI_TYPE;
205     }
206 
207     /**
208      * Write the state of the parser to a dump stream.
209      */
dump(@onNull DualDumpOutputStream dump, @NonNull String idName, long id)210     public void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) {
211         long token = dump.start(idName, id);
212 
213         dump.write("length", UsbMidiBlockParserProto.LENGTH, mHeaderLength);
214         dump.write("descriptor_type", UsbMidiBlockParserProto.DESCRIPTOR_TYPE,
215                 mHeaderDescriptorType);
216         dump.write("descriptor_subtype", UsbMidiBlockParserProto.DESCRIPTOR_SUBTYPE,
217                 mHeaderDescriptorSubtype);
218         dump.write("total_length", UsbMidiBlockParserProto.TOTAL_LENGTH, mTotalLength);
219         for (GroupTerminalBlock groupTerminalBlock : mGroupTerminalBlocks) {
220             groupTerminalBlock.dump(dump, "block", UsbMidiBlockParserProto.BLOCK);
221         }
222 
223         dump.end(token);
224     }
225 
226 }
227