1 /*
2  * Copyright (C) 2023 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 
17 package com.android.server.usb;
18 
19 import static org.junit.Assert.assertEquals;
20 
21 import androidx.test.filters.SmallTest;
22 import androidx.test.runner.AndroidJUnit4;
23 
24 import org.junit.Test;
25 import org.junit.runner.RunWith;
26 
27 import java.util.Arrays;
28 import java.util.Random;
29 
30 /**
31  * Unit tests for com.android.server.usb.UsbMidiPacketConverter.
32  */
33 @SmallTest
34 @RunWith(AndroidJUnit4.class)
35 public class UsbMidiPacketConverterTest {
generateRandomByteStream(Random rnd, int size)36     private byte[] generateRandomByteStream(Random rnd, int size) {
37         byte[] output = new byte[size];
38         rnd.nextBytes(output);
39         return output;
40     }
41 
compareByteArrays(byte[] expectedArray, byte[] outputArray)42     private void compareByteArrays(byte[] expectedArray, byte[] outputArray) {
43         assertEquals(expectedArray.length, outputArray.length);
44         for (int i = 0; i < outputArray.length; i++) {
45             assertEquals(expectedArray[i], outputArray[i]);
46         }
47     }
48 
49     @Test
testDecoderSinglePacket()50     public void testDecoderSinglePacket() {
51         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
52         usbMidiPacketConverter.createDecoders(2);
53         byte[] input = new byte[] {0x19 /* Cable 1 Note-On */, (byte) 0x91, 0x33, 0x66};
54         byte[] expectedOutputCable0 = new byte[] {};
55         byte[] expectedOutputCable1 = new byte[] {(byte) 0x91, 0x33, 0x66};
56         usbMidiPacketConverter.decodeMidiPackets(input, input.length);
57         byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
58         byte[] actualOutputCable1 = usbMidiPacketConverter.pullDecodedMidiPackets(1);
59         compareByteArrays(expectedOutputCable0, actualOutputCable0);
60         compareByteArrays(expectedOutputCable1, actualOutputCable1);
61     }
62 
63     @Test
testDecoderMultiplePackets()64     public void testDecoderMultiplePackets() {
65         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
66         usbMidiPacketConverter.createDecoders(4);
67         byte[] input = new byte[] {
68                 0x1B /* Cable 1 Control Change */, (byte) 0xB4, 0x55, 0x6E,
69                 0x35 /* Cable 3 Single byte SysEx */, (byte) 0xF8, 0x00, 0x00,
70                 0x02 /* Cable 0 Two byte System Common */, (byte) 0xF3, 0x12, 0x00};
71         byte[] expectedOutputCable0 = new byte[] {(byte) 0xF3, 0x12};
72         byte[] expectedOutputCable1 = new byte[] {(byte) 0xB4, 0x55, 0x6E};
73         byte[] expectedOutputCable2 = new byte[] {};
74         byte[] expectedOutputCable3 = new byte[] {(byte) 0xF8};
75         usbMidiPacketConverter.decodeMidiPackets(input, input.length);
76         byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
77         byte[] actualOutputCable1 = usbMidiPacketConverter.pullDecodedMidiPackets(1);
78         byte[] actualOutputCable2 = usbMidiPacketConverter.pullDecodedMidiPackets(2);
79         byte[] actualOutputCable3 = usbMidiPacketConverter.pullDecodedMidiPackets(3);
80         compareByteArrays(expectedOutputCable0, actualOutputCable0);
81         compareByteArrays(expectedOutputCable1, actualOutputCable1);
82         compareByteArrays(expectedOutputCable2, actualOutputCable2);
83         compareByteArrays(expectedOutputCable3, actualOutputCable3);
84     }
85 
86     @Test
testDecoderSysExEndFirstByte()87     public void testDecoderSysExEndFirstByte() {
88         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
89         usbMidiPacketConverter.createDecoders(2);
90         byte[] input = new byte[] {
91                 0x14 /* Cable 1 SysEx Start */, (byte) 0xF0, 0x00, 0x01,
92                 0x15 /* Cable 1 Single byte SysEx End */, (byte) 0xF7, 0x00, 0x00};
93         byte[] expectedOutputCable0 = new byte[] {};
94         byte[] expectedOutputCable1 = new byte[] {
95                 (byte) 0xF0, 0x00, 0x01,
96                 (byte) 0xF7};
97         usbMidiPacketConverter.decodeMidiPackets(input, input.length);
98         byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
99         byte[] actualOutputCable1 = usbMidiPacketConverter.pullDecodedMidiPackets(1);
100         compareByteArrays(expectedOutputCable0, actualOutputCable0);
101         compareByteArrays(expectedOutputCable1, actualOutputCable1);
102     }
103 
104     @Test
testDecoderSysExEndSecondByte()105     public void testDecoderSysExEndSecondByte() {
106         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
107         usbMidiPacketConverter.createDecoders(1);
108         byte[] input = new byte[] {
109                 0x04 /* Cable 0 SysEx Start */, (byte) 0xF0, 0x00, 0x01,
110                 0x06 /* Cable 0 Two byte SysEx End */, 0x02, (byte) 0xF7, 0x00};
111         byte[] expectedOutputCable0 = new byte[] {
112                 (byte) 0xF0, 0x00, 0x01,
113                 0x02, (byte) 0xF7};
114         usbMidiPacketConverter.decodeMidiPackets(input, input.length);
115         byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
116         compareByteArrays(expectedOutputCable0, actualOutputCable0);
117     }
118 
119     @Test
testDecoderSysExEndThirdByte()120     public void testDecoderSysExEndThirdByte() {
121         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
122         byte[] input = new byte[] {
123                 0x04 /* Cable 0 SysEx Start */, (byte) 0xF0, 0x00, 0x01,
124                 0x07 /* Cable 0 Three byte SysEx End */, 0x02, 0x03, (byte) 0xF7};
125         usbMidiPacketConverter.createDecoders(1);
126         byte[] expectedOutputCable0 = new byte[] {
127                 (byte) 0xF0, 0x00, 0x01,
128                 0x02, 0x03, (byte) 0xF7};
129         usbMidiPacketConverter.decodeMidiPackets(input, input.length);
130         byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
131         compareByteArrays(expectedOutputCable0, actualOutputCable0);
132     }
133 
134     @Test
testDecoderSysExStartEnd()135     public void testDecoderSysExStartEnd() {
136         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
137         byte[] input = new byte[] {
138                 0x06 /* Cable 0 Two byte SysEx End */, (byte) 0xF0, (byte) 0xF7, 0x00};
139         usbMidiPacketConverter.createDecoders(1);
140         byte[] expectedOutputCable0 = new byte[] {
141                 (byte) 0xF0, (byte) 0xF7};
142         usbMidiPacketConverter.decodeMidiPackets(input, input.length);
143         byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
144         compareByteArrays(expectedOutputCable0, actualOutputCable0);
145     }
146 
147     @Test
testDecoderSysExStartByteEnd()148     public void testDecoderSysExStartByteEnd() {
149         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
150         byte[] input = new byte[] {
151                 0x07 /* Cable 0 Three byte SysEx End */, (byte) 0xF0, 0x44, (byte) 0xF7};
152         usbMidiPacketConverter.createDecoders(1);
153         byte[] expectedOutputCable0 = new byte[] {
154                 (byte) 0xF0, 0x44, (byte) 0xF7};
155         usbMidiPacketConverter.decodeMidiPackets(input, input.length);
156         byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
157         compareByteArrays(expectedOutputCable0, actualOutputCable0);
158     }
159 
160     @Test
testDecoderDefaultToFirstCable()161     public void testDecoderDefaultToFirstCable() {
162         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
163         byte[] input = new byte[] {0x49 /* Cable 4 Note-On */, (byte) 0x91, 0x22, 0x33};
164         usbMidiPacketConverter.createDecoders(1);
165         byte[] expectedOutputCable0 = new byte[] {
166                 (byte) 0x91, 0x22, 0x33};
167         usbMidiPacketConverter.decodeMidiPackets(input, input.length);
168         byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0);
169         compareByteArrays(expectedOutputCable0, actualOutputCable0);
170     }
171 
172     @Test
testDecoderLargePacketDoesNotCrash()173     public void testDecoderLargePacketDoesNotCrash() {
174         for (long seed = 1001; seed < 5000; seed += 777) {
175             UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
176             usbMidiPacketConverter.createDecoders(3);
177             Random rnd = new Random(seed);
178             byte[] input = generateRandomByteStream(rnd, 1003 /* arbitrary large size */);
179             usbMidiPacketConverter.decodeMidiPackets(input, input.length);
180             usbMidiPacketConverter.pullDecodedMidiPackets(0);
181             usbMidiPacketConverter.pullDecodedMidiPackets(1);
182             usbMidiPacketConverter.pullDecodedMidiPackets(2);
183         }
184     }
185 
186     @Test
testEncoderBasic()187     public void testEncoderBasic() {
188         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
189         usbMidiPacketConverter.createEncoders(1);
190         byte[] input = new byte[] {(byte) 0x91 /* Note-On */, 0x33, 0x66};
191         byte[] expectedOutput = new byte[] {
192                 0x09 /* Cable 0 Note-On */, (byte) 0x91, 0x33, 0x66};
193         usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
194         byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
195         compareByteArrays(expectedOutput, output);
196     }
197 
198     @Test
testEncoderMultiplePackets()199     public void testEncoderMultiplePackets() {
200         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
201         usbMidiPacketConverter.createEncoders(3);
202         byte[] inputCable2 = new byte[] {
203                 (byte) 0xB4 /* Control Change */, 0x55, 0x6E};
204         byte[] inputCable1 = new byte[] {
205                 (byte) 0xF8 /* Timing Clock (Single Byte) */,
206                 (byte) 0xF3 /* Song Select (Two Bytes) */, 0x12};
207         byte[] expectedOutput = new byte[] {
208             0x2B /* Cable 2 Control Change */, (byte) 0xB4, 0x55, 0x6E,
209             0x15 /* Cable 1 Timing Clock */, (byte) 0xF8, 0x00, 0x00,
210             0x12 /* Cable 1 Two Byte System Common */, (byte) 0xF3, 0x12, 0x00};
211         usbMidiPacketConverter.encodeMidiPackets(inputCable2, inputCable2.length, 2);
212         usbMidiPacketConverter.encodeMidiPackets(inputCable1, inputCable1.length, 1);
213         byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
214         compareByteArrays(expectedOutput, output);
215     }
216 
217     @Test
testEncoderWeavePackets()218     public void testEncoderWeavePackets() {
219         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
220         usbMidiPacketConverter.createEncoders(2);
221         byte[] inputCable1Msg1 = new byte[] {
222                 (byte) 0x93 /* Note-On */, 0x23, 0x43};
223         byte[] inputCable0Msg = new byte[] {
224                 (byte) 0xB4 /* Control Change */, 0x65, 0x26};
225         byte[] inputCable1Msg2 = new byte[] {
226                 (byte) 0xA4 /* Poly-KeyPress */, 0x52, 0x76};
227         byte[] expectedOutput = new byte[] {
228                 0x19 /* Cable 1 Note-On */, (byte) 0x93, 0x23, 0x43,
229                 0x0B /* Cable 0 Control Change */, (byte) 0xB4, 0x65, 0x26,
230                 0x1A /* Cable 1 Poly-KeyPress */, (byte) 0xA4, 0x52, 0x76};
231         usbMidiPacketConverter.encodeMidiPackets(inputCable1Msg1, inputCable1Msg1.length, 1);
232         usbMidiPacketConverter.encodeMidiPackets(inputCable0Msg, inputCable0Msg.length, 0);
233         usbMidiPacketConverter.encodeMidiPackets(inputCable1Msg2, inputCable1Msg2.length, 1);
234         byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
235         compareByteArrays(expectedOutput, output);
236     }
237 
238     @Test
testEncoderSysExEndFirstByte()239     public void testEncoderSysExEndFirstByte() {
240         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
241         usbMidiPacketConverter.createEncoders(1);
242         byte[] input = new byte[] {
243                 (byte) 0xF0 /* SysEx Start */, 0x00, 0x01,
244                 (byte) 0xF7 /* SysEx End */};
245         byte[] expectedOutput = new byte[] {
246                 0x04 /* Cable 0 Three Byte SysEx Start */, (byte) 0xF0, 0x00, 0x01,
247                 0x05 /* Cable 0 One Byte SysEx End */, (byte) 0xF7, 0x00, 0x00};
248         usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
249         byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
250         compareByteArrays(expectedOutput, output);
251     }
252 
253     @Test
testEncoderSysExEndSecondByte()254     public void testEncoderSysExEndSecondByte() {
255         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
256         usbMidiPacketConverter.createEncoders(1);
257         byte[] input = new byte[] {
258                 (byte) 0xF0 /* SysEx Start */, 0x00, 0x01,
259                 0x02, (byte) 0xF7 /* SysEx End */};
260         byte[] expectedOutput = new byte[] {
261                 0x04 /* Cable 0 Three Byte SysEx Start */, (byte) 0xF0, 0x00, 0x01,
262                 0x06 /* Cable 0 Two Byte SysEx End */, 0x02, (byte) 0xF7, 0x00};
263         usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
264         byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
265         compareByteArrays(expectedOutput, output);
266     }
267 
268     @Test
testEncoderSysExEndThirdByte()269     public void testEncoderSysExEndThirdByte() {
270         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
271         usbMidiPacketConverter.createEncoders(1);
272         byte[] input = new byte[] {
273                 (byte) 0xF0 /* SysEx Start */, 0x00, 0x01,
274                 0x02, 0x03, (byte) 0xF7 /* SysEx End */};
275         byte[] expectedOutput = new byte[] {
276                 0x04 /* Cable 0 Three Byte SysEx Start */, (byte) 0xF0, 0x00, 0x01,
277                 0x07 /* Cable 0 Three Byte SysEx End */, 0x02, 0x03, (byte) 0xF7};
278         usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
279         byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
280         compareByteArrays(expectedOutput, output);
281     }
282 
283     @Test
testEncoderSysExStartEnd()284     public void testEncoderSysExStartEnd() {
285         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
286         usbMidiPacketConverter.createEncoders(1);
287         byte[] input = new byte[] {
288                 (byte) 0xF0 /* SysEx Start */, (byte) 0xF7 /* SysEx End */};
289         byte[] expectedOutput = new byte[] {
290                 0x06 /* Cable 0 Two Byte SysEx End */, (byte) 0xF0, (byte) 0xF7, 0x00};
291         usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
292         byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
293         compareByteArrays(expectedOutput, output);
294     }
295 
296     @Test
testEncoderSysExStartByteEnd()297     public void testEncoderSysExStartByteEnd() {
298         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
299         usbMidiPacketConverter.createEncoders(1);
300         byte[] input = new byte[] {
301                 (byte) 0xF0 /* SysEx Start */, 0x44, (byte) 0xF7 /* SysEx End */};
302         byte[] expectedOutput = new byte[] {
303                 0x07 /* Cable 0 Three Byte SysEx End */, (byte) 0xF0, 0x44, (byte) 0xF7};
304         usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
305         byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
306         compareByteArrays(expectedOutput, output);
307     }
308 
309     @Test
testEncoderMultiplePulls()310     public void testEncoderMultiplePulls() {
311         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
312         usbMidiPacketConverter.createEncoders(1);
313 
314         byte[] input = new byte[] {
315                 (byte) 0xF0 /* SysEx Start */, 0x44, 0x55,
316                 0x66, 0x77}; // 0x66 and 0x77 will not be pulled the first time
317         byte[] expectedOutput = new byte[] {
318                 0x04 /* SysEx Start */, (byte) 0xF0, 0x44, 0x55};
319         usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
320         byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
321         compareByteArrays(expectedOutput, output);
322 
323         input = new byte[] {
324                 0x11, // Combined with 0x66 and 0x77 above
325                 0x22, (byte) 0xF7 /* SysEx End */};
326         expectedOutput = new byte[] {
327                 0x04 /* Cable 0 SysEx Continue */, 0x66, 0x77, 0x11,
328                 0x06 /* Cable 0 Two Byte SysEx End */, 0x22, (byte) 0xF7, 0x00};
329         usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
330         output = usbMidiPacketConverter.pullEncodedMidiPackets();
331         compareByteArrays(expectedOutput, output);
332 
333         input = new byte[] {
334                 (byte) 0xF0 /* SysEx Start */, (byte) 0xF7 /* SysEx End */};
335         expectedOutput = new byte[] {
336                 0x06 /* Cable 0 Two Byte SysEx End */, (byte) 0xF0, (byte) 0xF7, 0x00};
337         usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0);
338         output = usbMidiPacketConverter.pullEncodedMidiPackets();
339         compareByteArrays(expectedOutput, output);
340     }
341 
342     @Test
testEncoderDefaultToFirstCable()343     public void testEncoderDefaultToFirstCable() {
344         UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
345         usbMidiPacketConverter.createEncoders(2);
346         byte[] input = new byte[] {(byte) 0x91 /* Note-On */, 0x22, 0x33};
347         byte[] expectedOutput = new byte[] {
348                 0x09 /* Cable 0 Note-On */, (byte) 0x91, 0x22, 0x33};
349         usbMidiPacketConverter.encodeMidiPackets(input, input.length, 4);
350         byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets();
351         compareByteArrays(expectedOutput, output);
352     }
353 
354     @Test
testEncoderLargePacketDoesNotCrash()355     public void testEncoderLargePacketDoesNotCrash() {
356         for (long seed = 234; seed < 4000; seed += 666) {
357             Random rnd = new Random(seed);
358             UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
359             usbMidiPacketConverter.createEncoders(4);
360             for (int cableNumber = 0; cableNumber < 4; cableNumber++) {
361                 byte[] input = generateRandomByteStream(rnd, 1003 /* arbitrary large size */);
362                 usbMidiPacketConverter.encodeMidiPackets(input, input.length, cableNumber);
363             }
364             usbMidiPacketConverter.pullEncodedMidiPackets();
365         }
366     }
367 
368     @Test
testEncodeDecode()369     public void testEncodeDecode() {
370         final int bufferSize = 30;
371         final int numCables = 16;
372         final int bytesToEncodePerEncoding = 10;
373         byte[][] rawMidi = new byte[numCables][bufferSize];
374         for (long seed = 45; seed < 3000; seed += 300) {
375             Random rnd = new Random(seed);
376             for (int cableNumber = 0; cableNumber < numCables; cableNumber++) {
377                 rawMidi[cableNumber] =  generateRandomByteStream(rnd, bufferSize);
378 
379                 // Change the last byte to SysEx End.
380                 // This way the encoder is guaranteed to flush all packets.
381                 rawMidi[cableNumber][bufferSize - 1] = (byte) 0xF7;
382             }
383             UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter();
384             usbMidiPacketConverter.createEncoders(numCables);
385             // Encode packets and interweave them
386             for (int startByte = 0; startByte < bufferSize;
387                     startByte += bytesToEncodePerEncoding) {
388                 for (int cableNumber = 0; cableNumber < numCables; cableNumber++) {
389                     byte[] bytesToEncode = Arrays.copyOfRange(rawMidi[cableNumber], startByte,
390                             startByte + bytesToEncodePerEncoding);
391                     usbMidiPacketConverter.encodeMidiPackets(bytesToEncode, bytesToEncode.length,
392                             cableNumber);
393                 }
394             }
395             byte[] usbMidi = usbMidiPacketConverter.pullEncodedMidiPackets();
396 
397             usbMidiPacketConverter.createDecoders(numCables);
398 
399             // Now decode the MIDI packets to check if they are the same as the original
400             usbMidiPacketConverter.decodeMidiPackets(usbMidi, usbMidi.length);
401             for (int cableNumber = 0; cableNumber < numCables; cableNumber++) {
402                 byte[] decodedRawMidi = usbMidiPacketConverter.pullDecodedMidiPackets(cableNumber);
403                 compareByteArrays(rawMidi[cableNumber], decodedRawMidi);
404             }
405         }
406     }
407 }
408