xref: /aosp_15_r20/cts/tests/tests/security/src/android/security/cts/EffectBundleTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2016 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 android.security.cts;
18 
19 import static org.junit.Assert.*;
20 
21 import android.media.MediaPlayer;
22 import android.media.audiofx.AudioEffect;
23 import android.media.audiofx.EnvironmentalReverb;
24 import android.media.audiofx.Equalizer;
25 import android.media.audiofx.PresetReverb;
26 import android.platform.test.annotations.AsbSecurityTest;
27 import android.util.Log;
28 
29 import androidx.test.runner.AndroidJUnit4;
30 
31 import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
32 
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 
36 import java.nio.ByteBuffer;
37 import java.nio.ByteOrder;
38 import java.nio.charset.StandardCharsets;
39 import java.util.Arrays;
40 import java.util.UUID;
41 
42 
43 @RunWith(AndroidJUnit4.class)
44 public class EffectBundleTest extends StsExtraBusinessLogicTestCase {
45     private static final String TAG = "EffectBundleTest";
46     private static final int[] INVALID_BAND_ARRAY = {Integer.MIN_VALUE, -10000, -100, -2, -1};
47     private static final int mValue0 = 9999; //unlikely values. Should not change
48     private static final int mValue1 = 13877;
49     private static final int PRESET_CUSTOM = -1; //keep in sync AudioEqualizer.h
50     private static final int GET_PARAM_FAILED = -1;
51 
52     private static final int MEDIA_SHORT = 0;
53     private static final int MEDIA_LONG = 1;
54 
55     // should match audio_effect.h (native)
56     private static final int EFFECT_CMD_SET_PARAM = 5;
57 
58     private static final int intSize = 4;
59 
60     //Testing security bug: 32436341
61     @AsbSecurityTest(cveBugId = 32436341)
62     @Test
testEqualizer_getParamCenterFreq()63     public void testEqualizer_getParamCenterFreq() throws Exception {
64         if (!hasEqualizer()) {
65             return;
66         }
67         testGetParam(MEDIA_SHORT, Equalizer.PARAM_CENTER_FREQ, INVALID_BAND_ARRAY, mValue0,
68                 mValue1);
69     }
70 
71     //Testing security bug: 32588352
72     @AsbSecurityTest(cveBugId = 32588352)
73     @Test
testEqualizer_getParamCenterFreq_long()74     public void testEqualizer_getParamCenterFreq_long() throws Exception {
75         if (!hasEqualizer()) {
76             return;
77         }
78         testGetParam(MEDIA_LONG, Equalizer.PARAM_CENTER_FREQ, INVALID_BAND_ARRAY, mValue0, mValue1);
79     }
80 
81     //Testing security bug: 32438598
82     @AsbSecurityTest(cveBugId = 32438598)
83     @Test
testEqualizer_getParamBandLevel()84     public void testEqualizer_getParamBandLevel() throws Exception {
85         if (!hasEqualizer()) {
86             return;
87         }
88         testGetParam(MEDIA_SHORT, Equalizer.PARAM_BAND_LEVEL, INVALID_BAND_ARRAY, mValue0, mValue1);
89     }
90 
91     //Testing security bug: 32584034
92     @AsbSecurityTest(cveBugId = 32584034)
93     @Test
testEqualizer_getParamBandLevel_long()94     public void testEqualizer_getParamBandLevel_long() throws Exception {
95         if (!hasEqualizer()) {
96             return;
97         }
98         testGetParam(MEDIA_LONG, Equalizer.PARAM_BAND_LEVEL, INVALID_BAND_ARRAY, mValue0, mValue1);
99     }
100 
101     //Testing security bug: 32247948
102     @AsbSecurityTest(cveBugId = 32247948)
103     @Test
testEqualizer_getParamFreqRange()104     public void testEqualizer_getParamFreqRange() throws Exception {
105         if (!hasEqualizer()) {
106             return;
107         }
108         testGetParam(MEDIA_SHORT, Equalizer.PARAM_BAND_FREQ_RANGE, INVALID_BAND_ARRAY, mValue0,
109                 mValue1);
110     }
111 
112     //Testing security bug: 32588756
113     @AsbSecurityTest(cveBugId = 32588756)
114     @Test
testEqualizer_getParamFreqRange_long()115     public void testEqualizer_getParamFreqRange_long() throws Exception {
116         if (!hasEqualizer()) {
117             return;
118         }
119         testGetParam(MEDIA_LONG, Equalizer.PARAM_BAND_FREQ_RANGE, INVALID_BAND_ARRAY, mValue0,
120                 mValue1);
121     }
122 
123     //Testing security bug: 32448258
124     @AsbSecurityTest(cveBugId = 32448258)
125     @Test
testEqualizer_getParamPresetName()126     public void testEqualizer_getParamPresetName() throws Exception {
127         if (!hasEqualizer()) {
128             return;
129         }
130         testParamPresetName(MEDIA_SHORT);
131     }
132 
133     //Testing security bug: 32588016
134     @AsbSecurityTest(cveBugId = 32588016)
135     @Test
testEqualizer_getParamPresetName_long()136     public void testEqualizer_getParamPresetName_long() throws Exception {
137         if (!hasEqualizer()) {
138             return;
139         }
140         testParamPresetName(MEDIA_LONG);
141     }
142 
testParamPresetName(int media)143     private void testParamPresetName(int media) {
144         final int command = Equalizer.PARAM_GET_PRESET_NAME;
145         for (int invalidBand : INVALID_BAND_ARRAY)
146         {
147             final byte testValue = 7;
148             byte reply[] = new byte[Equalizer.PARAM_STRING_SIZE_MAX];
149             Arrays.fill(reply, testValue);
150             int length = eqGetParam(media, command, invalidBand, reply);
151             //Compare
152             if (invalidBand == PRESET_CUSTOM) {
153                 final String expectedName = "Custom";
154                 try {
155                     // remove the '\0' at the end if it exist (HIDL audio effect hal)
156                     if (reply[length - 1] == '\0') {
157                         length = length - 1;
158                     }
159                     final String presetName =  new String(reply, 0, length,
160                             StandardCharsets.ISO_8859_1.name());
161                     assertEquals("getPresetName custom preset name failed", expectedName,
162                             presetName);
163                 } catch (Exception e) {
164                     Log.w(TAG, "Problem creating reply string.");
165                 }
166             } else {
167                 assertTrue("getPresetName with invalid preset index should fail", length < 0);
168             }
169         }
170     }
171 
172     //testing security bug: 32095626
173     @AsbSecurityTest(cveBugId = 32095626)
174     @Test
testEqualizer_setParamBandLevel()175     public void testEqualizer_setParamBandLevel() throws Exception {
176         if (!hasEqualizer()) {
177             return;
178         }
179         final int command = Equalizer.PARAM_BAND_LEVEL;
180         short[] value = { 1000 };
181         for (int invalidBand : INVALID_BAND_ARRAY)
182         {
183             if (!eqSetParam(MEDIA_SHORT, command, invalidBand, value)) {
184                 fail("setParam PARAM_BAND_LEVEL did not complete successfully");
185             }
186         }
187     }
188 
189     //testing security bug: 32585400
190     @AsbSecurityTest(cveBugId = 32585400)
191     @Test
testEqualizer_setParamBandLevel_long()192     public void testEqualizer_setParamBandLevel_long() throws Exception {
193         if (!hasEqualizer()) {
194             return;
195         }
196         final int command = Equalizer.PARAM_BAND_LEVEL;
197         short[] value = { 1000 };
198         for (int invalidBand : INVALID_BAND_ARRAY)
199         {
200             if (!eqSetParam(MEDIA_LONG, command, invalidBand, value)) {
201                 fail("setParam PARAM_BAND_LEVEL did not complete successfully");
202             }
203         }
204     }
205 
206     //testing security bug: 32705438
207     @AsbSecurityTest(cveBugId = 32705438)
208     @Test
testEqualizer_getParamFreqRangeCommand_short()209     public void testEqualizer_getParamFreqRangeCommand_short() throws Exception {
210         if (!hasEqualizer()) {
211             return;
212         }
213         assertTrue("testEqualizer_getParamFreqRangeCommand_short did not complete successfully",
214                 eqGetParamFreqRangeCommand(MEDIA_SHORT));
215     }
216 
217     //testing security bug: 32703959
218     @AsbSecurityTest(cveBugId = 32703959)
219     @Test
testEqualizer_getParamFreqRangeCommand_long()220     public void testEqualizer_getParamFreqRangeCommand_long() throws Exception {
221         if (!hasEqualizer()) {
222             return;
223         }
224         assertTrue("testEqualizer_getParamFreqRangeCommand_long did not complete successfully",
225                 eqGetParamFreqRangeCommand(MEDIA_LONG));
226     }
227 
228     //testing security bug: 37563371 (short media)
229     @AsbSecurityTest(cveBugId = 37563371)
230     @Test
testEqualizer_setParamProperties_short()231     public void testEqualizer_setParamProperties_short() throws Exception {
232         if (!hasEqualizer()) {
233             return;
234         }
235         assertTrue("testEqualizer_setParamProperties_long did not complete successfully",
236                 eqSetParamProperties(MEDIA_SHORT));
237     }
238 
239     //testing security bug: 37563371 (long media)
240     @AsbSecurityTest(cveBugId = 37563371)
241     @Test
testEqualizer_setParamProperties_long()242     public void testEqualizer_setParamProperties_long() throws Exception {
243         if (!hasEqualizer()) {
244             return;
245         }
246         assertTrue("testEqualizer_setParamProperties_long did not complete successfully",
247                 eqSetParamProperties(MEDIA_LONG));
248     }
249 
250     //Testing security bug: 63662938
251     @AsbSecurityTest(cveBugId = 63662938)
252     @Test
testDownmix_setParameter()253     public void testDownmix_setParameter() throws Exception {
254         verifyZeroPVSizeRejectedForSetParameter(
255                 EFFECT_TYPE_DOWNMIX, new int[] { DOWNMIX_PARAM_TYPE });
256     }
257 
258     /**
259      * Definitions for the downmix effect. Taken from
260      * system/media/audio/include/system/audio_effects/effect_downmix.h
261      * This effect is normally not exposed to applications.
262      */
263     private static final UUID EFFECT_TYPE_DOWNMIX = UUID
264             .fromString("381e49cc-a858-4aa2-87f6-e8388e7601b2");
265     private static final int DOWNMIX_PARAM_TYPE = 0;
266 
267     //Testing security bug: 63526567
268     @AsbSecurityTest(cveBugId = 63526567)
269     @Test
testEnvironmentalReverb_setParameter()270     public void testEnvironmentalReverb_setParameter() throws Exception {
271         verifyZeroPVSizeRejectedForSetParameter(
272                 AudioEffect.EFFECT_TYPE_ENV_REVERB, new int[] {
273                   EnvironmentalReverb.PARAM_ROOM_LEVEL,
274                   EnvironmentalReverb.PARAM_ROOM_HF_LEVEL,
275                   EnvironmentalReverb.PARAM_DECAY_TIME,
276                   EnvironmentalReverb.PARAM_DECAY_HF_RATIO,
277                   EnvironmentalReverb.PARAM_REFLECTIONS_LEVEL,
278                   EnvironmentalReverb.PARAM_REFLECTIONS_DELAY,
279                   EnvironmentalReverb.PARAM_REVERB_LEVEL,
280                   EnvironmentalReverb.PARAM_REVERB_DELAY,
281                   EnvironmentalReverb.PARAM_DIFFUSION,
282                   EnvironmentalReverb.PARAM_DENSITY,
283                   10 // EnvironmentalReverb.PARAM_PROPERTIES
284                 }
285         );
286     }
287 
288     //Testing security bug: 67647856
289     @AsbSecurityTest(cveBugId = 67647856)
290     @Test
testPresetReverb_setParameter()291     public void testPresetReverb_setParameter() throws Exception {
292         verifyZeroPVSizeRejectedForSetParameter(
293                 AudioEffect.EFFECT_TYPE_PRESET_REVERB, new int[] {
294                   PresetReverb.PARAM_PRESET
295                 }
296         );
297     }
298 
eqSetParamProperties(int media)299     private boolean eqSetParamProperties(int media) {
300         MediaPlayer mp = null;
301         Equalizer eq = null;
302         boolean status = false;
303         try {
304             mp = MediaPlayer.create(getInstrumentation().getContext(),  getMediaId(media));
305             eq = new Equalizer(0 /*priority*/, mp.getAudioSessionId());
306 
307             int shortSize = 2; //bytes
308 
309             int cmdCode = EFFECT_CMD_SET_PARAM;
310             byte command[] = concatArrays(/*status*/ intToByteArray(0),
311                     /*psize*/ intToByteArray(1 * intSize),
312                     /*vsize*/ intToByteArray(2 * shortSize),
313                     /*data[0]*/ intToByteArray((int) 9 /*EQ_PARAM_PROPERTIES*/),
314                     /*data[4]*/ shortToByteArray((short)-1 /*preset*/),
315                     /*data[6]*/ shortToByteArray((short)5 /*FIVEBAND_NUMBANDS*/));
316             byte reply[] = new byte[ 4 /*command.length*/];
317 
318             AudioEffect af = eq;
319             Object o = AudioEffect.class.getDeclaredMethod("command", int.class, byte[].class,
320                     byte[].class).invoke(af, cmdCode, command, reply);
321             int retStatus = (int) o;
322 
323             int replyValue = byteArrayToInt(reply, 0 /*offset*/);
324             if (replyValue >= 0) {
325                 Log.w(TAG, "Reply Value: " + replyValue);
326             }
327             assertTrue("Negative replyValue was expected ", retStatus !=  0);
328             status = true;
329         } catch (Exception e) {
330             Log.w(TAG, "Problem setting parameter in equalizer");
331         } finally {
332             if (eq != null) {
333                 eq.release();
334             }
335             if (mp != null) {
336                 mp.release();
337             }
338         }
339         return status;
340     }
341 
eqGetParamFreqRangeCommand(int media)342     private boolean eqGetParamFreqRangeCommand(int media) {
343         MediaPlayer mp = null;
344         Equalizer eq = null;
345         boolean status = false;
346         try {
347             mp = MediaPlayer.create(getInstrumentation().getContext(), getMediaId(media));
348             eq = new Equalizer(0 /*priority*/, mp.getAudioSessionId());
349 
350             short band = 2;
351 
352             //baseline
353             int cmdCode = 8; // EFFECT_CMD_GET_PARAM
354             byte command[] = concatArrays(/*status*/ intToByteArray(0),
355                     /*psize*/ intToByteArray(2 * intSize),
356                     /*vsize*/ intToByteArray(2 * intSize),
357                     /*data[0]*/ intToByteArray(Equalizer.PARAM_BAND_FREQ_RANGE),
358                     /*data[1]*/ intToByteArray((int) band));
359 
360             byte reply[] = new byte[command.length];
361 
362             AudioEffect af = eq;
363             Object o = AudioEffect.class.getDeclaredMethod("command", int.class, byte[].class,
364                     byte[].class).invoke(af, cmdCode, command, reply);
365 
366             int methodStatus = AudioEffect.ERROR;
367             if (o != null) {
368                 methodStatus = Integer.valueOf(o.toString()).intValue();
369             }
370 
371             assertTrue("Command expected to fail", methodStatus <= 0);
372             int sum = 0;
373             for (int i = 0; i < reply.length; i++) {
374                 sum += Math.abs(reply[i]);
375             }
376 
377             assertEquals("reply expected to be all zeros", sum, 0);
378             status = true;
379         } catch (Exception e) {
380             Log.w(TAG, "Problem testing eqGetParamFreqRangeCommand");
381             status = false;
382         } finally {
383             if (eq != null) {
384                 eq.release();
385             }
386             if (mp != null) {
387                 mp.release();
388             }
389         }
390         return status;
391     }
392 
eqGetParam(int media, int command, int band, byte[] reply)393     private int eqGetParam(int media, int command, int band, byte[] reply) {
394         MediaPlayer mp = null;
395         Equalizer eq = null;
396         int length = GET_PARAM_FAILED;
397         try {
398             mp = MediaPlayer.create(getInstrumentation().getContext(), getMediaId(media));
399             eq = new Equalizer(0 /*priority*/, mp.getAudioSessionId());
400 
401             AudioEffect af = eq;
402             int cmd[] = {command, band};
403 
404             Object o = AudioEffect.class.getDeclaredMethod("getParameter", int[].class,
405                     byte[].class).invoke(af, cmd, reply);
406             length = (int) o;
407         } catch (Exception e) {
408             Log.w(TAG, "Problem getting parameter from equalizer");
409         } finally {
410             if (eq != null) {
411                 eq.release();
412             }
413             if (mp != null) {
414                 mp.release();
415             }
416         }
417         return length;
418     }
419 
eqGetParam(int media, int command, int band, int[] reply)420     private int eqGetParam(int media, int command, int band, int[] reply) {
421         MediaPlayer mp = null;
422         Equalizer eq = null;
423         int length = GET_PARAM_FAILED;
424         try {
425             mp = MediaPlayer.create(getInstrumentation().getContext(), getMediaId(media));
426             eq = new Equalizer(0 /*priority*/, mp.getAudioSessionId());
427 
428             AudioEffect af = eq;
429             int cmd[] = {command, band};
430 
431             Object o = AudioEffect.class.getDeclaredMethod("getParameter", int[].class,
432                     int[].class).invoke(af, cmd, reply);
433             length = (int) o;
434         } catch (Exception e) {
435             Log.w(TAG, "Problem getting parameter from equalizer");
436         } finally {
437             if (eq != null) {
438                 eq.release();
439             }
440             if (mp != null) {
441                 mp.release();
442             }
443         }
444         return length;
445     }
446 
testGetParam(int media, int command, int[] bandArray, int value0, int value1)447     private void testGetParam(int media, int command, int[] bandArray, int value0, int value1) {
448         int reply[] = {value0, value1};
449         for (int invalidBand : INVALID_BAND_ARRAY)
450         {
451             final int length = eqGetParam(media, command, invalidBand, reply);
452             assertTrue("getParam with invalid bands should fail", length < 0);
453             assertEquals("getParam should not change value0", value0, reply[0]);
454             assertEquals("getParam should not change value1", value1, reply[1]);
455         }
456     }
457 
eqSetParam(int media, int command, int band, short[] value)458     private boolean eqSetParam(int media, int command, int band, short[] value) {
459         MediaPlayer mp = null;
460         Equalizer eq = null;
461         boolean status = false;
462         try {
463             mp = MediaPlayer.create(getInstrumentation().getContext(),  getMediaId(media));
464             eq = new Equalizer(0 /*priority*/, mp.getAudioSessionId());
465 
466             AudioEffect af = eq;
467             int cmd[] = {command, band};
468 
469             AudioEffect.class.getDeclaredMethod("setParameter", int[].class,
470                     short[].class).invoke(af, cmd, value);
471             status = true;
472         } catch (Exception e) {
473             Log.w(TAG, "Problem setting parameter in equalizer");
474             status = false;
475         } finally {
476             if (eq != null) {
477                 eq.release();
478             }
479             if (mp != null) {
480                 mp.release();
481             }
482         }
483         return status;
484     }
485 
getMediaId(int media)486     private int getMediaId(int media) {
487         switch (media) {
488             default:
489             case MEDIA_SHORT:
490                 return R.raw.good;
491             case MEDIA_LONG:
492                 return R.raw.onekhzsine_90sec;
493         }
494     }
495 
496     // Verifies that for all the effects of the specified type
497     // an attempt to pass psize = 0 or vsize = 0 to 'set parameter' command
498     // is rejected by the effect.
verifyZeroPVSizeRejectedForSetParameter( UUID effectType, final int paramCodes[])499     private void verifyZeroPVSizeRejectedForSetParameter(
500             UUID effectType, final int paramCodes[]) throws Exception {
501 
502         boolean effectFound = false;
503         AudioEffect.Descriptor[] descriptors = AudioEffect.queryEffects();
504         if (descriptors != null) {
505             for (AudioEffect.Descriptor descriptor : descriptors) {
506                 if (descriptor.type.compareTo(effectType) != 0) continue;
507 
508                 effectFound = true;
509                 AudioEffect ae = null;
510                 MediaPlayer mp = null;
511                 try {
512                     mp = MediaPlayer.create(getInstrumentation().getContext(), R.raw.good);
513                     java.lang.reflect.Constructor ct = AudioEffect.class.getConstructor(
514                             UUID.class, UUID.class, int.class, int.class);
515                     try {
516                         ae = (AudioEffect) ct.newInstance(descriptor.type, descriptor.uuid,
517                                 /*priority*/ 0, mp.getAudioSessionId());
518                     } catch (Exception e) {
519                         // Not every effect can be instantiated by apps.
520                         Log.w(TAG, "Failed to create effect " + descriptor.uuid);
521                         continue;
522                     }
523                     java.lang.reflect.Method command = AudioEffect.class.getDeclaredMethod(
524                             "command", int.class, byte[].class, byte[].class);
525                     for (int paramCode : paramCodes) {
526                         executeSetParameter(ae, command, intSize, 0, paramCode);
527                         executeSetParameter(ae, command, 0, intSize, paramCode);
528                     }
529                 } finally {
530                     if (ae != null) {
531                         ae.release();
532                     }
533                     if (mp != null) {
534                         mp.release();
535                     }
536                 }
537             }
538         }
539 
540         if (!effectFound) {
541             Log.w(TAG, "No effect with type " + effectType + " was found");
542         }
543     }
544 
executeSetParameter(AudioEffect ae, java.lang.reflect.Method command, int paramSize, int valueSize, int paramCode)545     private void executeSetParameter(AudioEffect ae, java.lang.reflect.Method command,
546             int paramSize, int valueSize, int paramCode) throws Exception {
547         byte cmdBuf[] = concatArrays(/*status*/ intToByteArray(0),
548                 /*psize*/ intToByteArray(paramSize),
549                 /*vsize*/ intToByteArray(valueSize),
550                 /*data[0]*/ intToByteArray(paramCode));
551         byte reply[] = new byte[intSize];
552         Integer ret = (Integer)command.invoke(ae, EFFECT_CMD_SET_PARAM, cmdBuf, reply);
553         if (ret >= 0) {
554             int val = byteArrayToInt(reply, 0 /*offset*/);
555             assertTrue("Negative reply value expected, effect " + ae.getDescriptor().uuid +
556                     ", parameter " + paramCode + ", reply value " + val,
557                     val < 0);
558         } else {
559             // Some effect implementations detect this condition at the command dispatch level,
560             // and reject command execution. That's also OK, but log a message so the test
561             // author can double check if 'paramCode' is correct.
562             Log.w(TAG, "\"Set parameter\" command rejected for effect " + ae.getDescriptor().uuid +
563                     ", parameter " + paramCode + ", return code " + ret);
564         }
565     }
566 
hasEqualizer()567     private boolean hasEqualizer() {
568         boolean result = false;
569         try {
570             MediaPlayer mp = MediaPlayer.create(getInstrumentation().getContext(), R.raw.good);
571             new Equalizer(0 /*priority*/, mp.getAudioSessionId());
572             result = true;
573         } catch (Exception e) {
574             Log.d(TAG, "Cannot create equalizer");
575         }
576         return result;
577     }
578 
intToByteArray(int value)579     private static byte[] intToByteArray(int value) {
580         ByteBuffer converter = ByteBuffer.allocate(4);
581         converter.order(ByteOrder.nativeOrder());
582         converter.putInt(value);
583         return converter.array();
584     }
585 
byteArrayToInt(byte[] valueBuf, int offset)586     public static int byteArrayToInt(byte[] valueBuf, int offset) {
587         ByteBuffer converter = ByteBuffer.wrap(valueBuf);
588         converter.order(ByteOrder.nativeOrder());
589         return converter.getInt(offset);
590     }
591 
shortToByteArray(short value)592     private static byte[] shortToByteArray(short value) {
593         ByteBuffer converter = ByteBuffer.allocate(2);
594         converter.order(ByteOrder.nativeOrder());
595         short sValue = (short) value;
596         converter.putShort(sValue);
597         return converter.array();
598     }
599 
concatArrays(byte[]... arrays)600     private static  byte[] concatArrays(byte[]... arrays) {
601         int len = 0;
602         for (byte[] a : arrays) {
603             len += a.length;
604         }
605         byte[] b = new byte[len];
606 
607         int offs = 0;
608         for (byte[] a : arrays) {
609             System.arraycopy(a, 0, b, offs, a.length);
610             offs += a.length;
611         }
612         return b;
613     }
614 }
615