1 /*
2  * Copyright 2022 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.bluetooth.bass_client;
18 
19 import android.util.Log;
20 import android.util.Pair;
21 
22 import java.nio.ByteBuffer;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.LinkedHashSet;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.MissingResourceException;
31 import java.util.Set;
32 
33 /** Helper class to parse the Broadcast Announcement BASE data */
34 class BaseData {
35     private static final String TAG = "Bassclient-BaseData";
36     private static final byte UNKNOWN_CODEC = (byte) 0xFE;
37     private static final int METADATA_LEVEL1 = 1;
38     private static final int METADATA_LEVEL2 = 2;
39     private static final int METADATA_LEVEL3 = 3;
40     private static final int METADATA_PRESENTATIONDELAY_LENGTH = 3;
41     private static final int METADATA_CODEC_LENGTH = 5;
42     private static final int METADATA_UNKNOWN_CODEC_LENGTH = 1;
43     private static final int CODEC_CONFIGURATION_SAMPLE_RATE_TYPE = 0x01;
44     private static final int CODEC_CONFIGURATION_FRAME_DURATION_TYPE = 0x02;
45     private static final int CODEC_CONFIGURATION_CHANNEL_ALLOCATION_TYPE = 0x03;
46     private static final int CODEC_CONFIGURATION_OCTETS_PER_FRAME_TYPE = 0x04;
47     private static final int METADATA_LANGUAGE_TYPE = 0x04;
48     private static final int CODEC_AUDIO_LOCATION_FRONT_LEFT = 0x01000000;
49     private static final int CODEC_AUDIO_LOCATION_FRONT_RIGHT = 0x02000000;
50     private static final int CODEC_AUDIO_SAMPLE_RATE_8K = 0x01;
51     private static final int CODEC_AUDIO_SAMPLE_RATE_16K = 0x03;
52     private static final int CODEC_AUDIO_SAMPLE_RATE_24K = 0x05;
53     private static final int CODEC_AUDIO_SAMPLE_RATE_32K = 0x06;
54     private static final int CODEC_AUDIO_SAMPLE_RATE_44P1K = 0x07;
55     private static final int CODEC_AUDIO_SAMPLE_RATE_48K = 0x08;
56     private static final int CODEC_AUDIO_FRAME_DURATION_7P5MS = 0x00;
57     private static final int CODEC_AUDIO_FRAME_DURATION_10MS = 0x01;
58 
59     private final BaseInformation mLevelOne;
60     private final ArrayList<BaseInformation> mLevelTwo;
61     private final ArrayList<BaseInformation> mLevelThree;
62 
63     private int mNumBISIndices = 0;
64 
65     public static class BaseInformation {
66         public byte[] presentationDelay = new byte[3];
67         public byte[] codecId = new byte[5];
68         public int codecConfigLength;
69         public byte[] codecConfigInfo;
70         public int metaDataLength;
71         public byte[] metaData;
72         public byte numSubGroups;
73         public byte[] bisIndices;
74         public byte index;
75         public int subGroupId;
76         public int level;
77         public LinkedHashSet<String> keyCodecCfgDiff;
78         public LinkedHashSet<String> keyMetadataDiff;
79         public String diffText;
80         public String description;
81         public byte[] consolidatedCodecId;
82         public Set<String> consolidatedMetadata;
83         public Set<String> consolidatedCodecInfo;
84         public HashMap<Integer, String> consolidatedUniqueCodecInfo;
85         public HashMap<Integer, String> consolidatedUniqueMetadata;
86 
BaseInformation()87         BaseInformation() {
88             presentationDelay = new byte[3];
89             codecId = new byte[5];
90             codecConfigLength = 0;
91             codecConfigInfo = new byte[0];
92             metaDataLength = 0;
93             metaData = new byte[0];
94             numSubGroups = 0;
95             bisIndices = null;
96             index = (byte) 0xFF;
97             level = 0;
98             keyCodecCfgDiff = new LinkedHashSet<String>();
99             keyMetadataDiff = new LinkedHashSet<String>();
100             consolidatedMetadata = new LinkedHashSet<String>();
101             consolidatedCodecInfo = new LinkedHashSet<String>();
102             consolidatedCodecId = new byte[5];
103             consolidatedUniqueMetadata = new HashMap<Integer, String>();
104             consolidatedUniqueCodecInfo = new HashMap<Integer, String>();
105             diffText = new String("");
106             description = new String("");
107             log("BaseInformation is Initialized");
108         }
109 
isCodecIdUnknown()110         boolean isCodecIdUnknown() {
111             return (codecId != null && codecId[4] == (byte) UNKNOWN_CODEC);
112         }
113 
print()114         void print() {
115             log("**BEGIN: Base Information**");
116             log("**Level: " + level + "***");
117             if (level == 1) {
118                 log("presentationDelay: " + Arrays.toString(presentationDelay));
119             }
120             if (level == 2) {
121                 log("codecId: " + Arrays.toString(codecId));
122             }
123             if (level == 2 || level == 3) {
124                 log("codecConfigLength: " + codecConfigLength);
125                 log("subGroupId: " + subGroupId);
126             }
127             if (codecConfigLength != 0) {
128                 log("codecConfigInfo: " + Arrays.toString(codecConfigInfo));
129             }
130             if (level == 2) {
131                 log("metaDataLength: " + metaDataLength);
132                 if (metaDataLength != 0) {
133                     log("metaData: " + Arrays.toString(metaData));
134                 }
135                 if (level == 1 || level == 2) {
136                     log("numSubGroups: " + numSubGroups);
137                 }
138             }
139             if (level == 2) {
140                 log("Level2: Key Metadata differentiators");
141                 if (keyMetadataDiff != null) {
142                     Iterator<String> itr = keyMetadataDiff.iterator();
143                     for (int k = 0; itr.hasNext(); k++) {
144                         log(
145                                 "keyMetadataDiff:["
146                                         + k
147                                         + "]:"
148                                         + Arrays.toString(itr.next().getBytes()));
149                     }
150                 }
151                 log("END: Level2: Key Metadata differentiators");
152                 log("Level2: Key CodecConfig differentiators");
153                 if (keyCodecCfgDiff != null) {
154                     Iterator<String> itr = keyCodecCfgDiff.iterator();
155                     for (int k = 0; itr.hasNext(); k++) {
156                         log(
157                                 "LEVEL2: keyCodecCfgDiff:["
158                                         + k
159                                         + "]:"
160                                         + Arrays.toString(itr.next().getBytes()));
161                     }
162                 }
163                 log("END: Level2: Key CodecConfig differentiators");
164                 log("LEVEL2: diffText: " + diffText);
165             }
166             if (level == 3) {
167                 log("Level3: Key CodecConfig differentiators");
168                 if (keyCodecCfgDiff != null) {
169                     Iterator<String> itr = keyCodecCfgDiff.iterator();
170                     for (int k = 0; itr.hasNext(); k++) {
171                         log(
172                                 "LEVEL3: keyCodecCfgDiff:["
173                                         + k
174                                         + "]:"
175                                         + Arrays.toString(itr.next().getBytes()));
176                     }
177                 }
178                 log("END: Level3: Key CodecConfig differentiators");
179                 log("index: " + index);
180                 log("LEVEL3: diffText: " + diffText);
181             }
182             log("**END: Base Information****");
183         }
184     }
185 
BaseData( BaseInformation levelOne, ArrayList<BaseInformation> levelTwo, ArrayList<BaseInformation> levelThree, int numOfBISIndices)186     BaseData(
187             BaseInformation levelOne,
188             ArrayList<BaseInformation> levelTwo,
189             ArrayList<BaseInformation> levelThree,
190             int numOfBISIndices) {
191         mLevelOne = levelOne;
192         mLevelTwo = levelTwo;
193         mLevelThree = levelThree;
194         mNumBISIndices = numOfBISIndices;
195     }
196 
parseBaseData(byte[] serviceData)197     static BaseData parseBaseData(byte[] serviceData) {
198         if (serviceData == null) {
199             Log.e(TAG, "Invalid service data for BaseData construction");
200             throw new IllegalArgumentException("Basedata: serviceData is null");
201         }
202         BaseInformation levelOne = new BaseInformation();
203         ArrayList<BaseInformation> levelTwo = new ArrayList<BaseInformation>();
204         ArrayList<BaseInformation> levelThree = new ArrayList<BaseInformation>();
205         int numOfBISIndices = 0;
206         log("BASE input" + Arrays.toString(serviceData));
207 
208         // Parse Level 1 base
209         levelOne.level = METADATA_LEVEL1;
210         int offset = 0;
211         System.arraycopy(serviceData, offset, levelOne.presentationDelay, 0, 3);
212         offset += METADATA_PRESENTATIONDELAY_LENGTH;
213         levelOne.numSubGroups = serviceData[offset++];
214         levelOne.print();
215         log("levelOne subgroups" + levelOne.numSubGroups);
216         for (int i = 0; i < (int) levelOne.numSubGroups; i++) {
217             Pair<BaseInformation, Integer> pair1 = parseLevelTwo(serviceData, i, offset);
218             BaseInformation node2 = pair1.first;
219             if (node2 == null) {
220                 Log.e(TAG, "Error: parsing Level 2");
221                 return null;
222             }
223             numOfBISIndices += node2.numSubGroups;
224             levelTwo.add(node2);
225             node2.print();
226             offset = pair1.second;
227             for (int k = 0; k < node2.numSubGroups; k++) {
228                 Pair<BaseInformation, Integer> pair2 = parseLevelThree(serviceData, offset);
229                 BaseInformation node3 = pair2.first;
230                 offset = pair2.second;
231                 if (node3 == null) {
232                     Log.e(TAG, "Error: parsing Level 3");
233                     return null;
234                 }
235                 levelThree.add(node3);
236                 node3.print();
237             }
238         }
239         consolidateBaseofLevelTwo(levelTwo, levelThree);
240         return new BaseData(levelOne, levelTwo, levelThree, numOfBISIndices);
241     }
242 
parseLevelTwo( byte[] serviceData, int groupIndex, int offset)243     private static Pair<BaseInformation, Integer> parseLevelTwo(
244             byte[] serviceData, int groupIndex, int offset) {
245         log("Parsing Level 2");
246         BaseInformation node = new BaseInformation();
247         node.level = METADATA_LEVEL2;
248         node.subGroupId = groupIndex;
249         node.numSubGroups = serviceData[offset++];
250         if (serviceData[offset] == (byte) UNKNOWN_CODEC) {
251             // Place It in the last byte of codecID
252             System.arraycopy(
253                     serviceData,
254                     offset,
255                     node.codecId,
256                     METADATA_CODEC_LENGTH - 1,
257                     METADATA_UNKNOWN_CODEC_LENGTH);
258             offset += METADATA_UNKNOWN_CODEC_LENGTH;
259             log("codecId is FE");
260         } else {
261             System.arraycopy(serviceData, offset, node.codecId, 0, METADATA_CODEC_LENGTH);
262             offset += METADATA_CODEC_LENGTH;
263         }
264         node.codecConfigLength = serviceData[offset++] & 0xff;
265         if (node.codecConfigLength != 0) {
266             node.codecConfigInfo = new byte[node.codecConfigLength];
267             System.arraycopy(serviceData, offset, node.codecConfigInfo, 0, node.codecConfigLength);
268             offset += node.codecConfigLength;
269         }
270         node.metaDataLength = serviceData[offset++] & 0xff;
271         if (node.metaDataLength != 0) {
272             node.metaData = new byte[node.metaDataLength];
273             System.arraycopy(serviceData, offset, node.metaData, 0, node.metaDataLength);
274             offset += node.metaDataLength;
275         }
276         return new Pair<BaseInformation, Integer>(node, offset);
277     }
278 
parseLevelThree(byte[] serviceData, int offset)279     private static Pair<BaseInformation, Integer> parseLevelThree(byte[] serviceData, int offset) {
280         log("Parsing Level 3");
281         BaseInformation node = new BaseInformation();
282         node.level = METADATA_LEVEL3;
283         node.index = serviceData[offset++];
284         node.codecConfigLength = serviceData[offset++] & 0xff;
285         if (node.codecConfigLength != 0) {
286             node.codecConfigInfo = new byte[node.codecConfigLength];
287             System.arraycopy(serviceData, offset, node.codecConfigInfo, 0, node.codecConfigLength);
288             offset += node.codecConfigLength;
289         }
290         return new Pair<BaseInformation, Integer>(node, offset);
291     }
292 
consolidateBaseofLevelTwo( ArrayList<BaseInformation> levelTwo, ArrayList<BaseInformation> levelThree)293     static void consolidateBaseofLevelTwo(
294             ArrayList<BaseInformation> levelTwo, ArrayList<BaseInformation> levelThree) {
295         int startIdx = 0;
296         int children = 0;
297         for (int i = 0; i < levelTwo.size(); i++) {
298             startIdx = startIdx + children;
299             children = children + levelTwo.get(i).numSubGroups;
300             consolidateBaseofLevelThree(
301                     levelTwo, levelThree, i, startIdx, levelTwo.get(i).numSubGroups);
302         }
303         // Eliminate Duplicates at Level 3
304         for (int i = 0; i < levelThree.size(); i++) {
305             Map<Integer, String> uniqueMds = new HashMap<Integer, String>();
306             Map<Integer, String> uniqueCcis = new HashMap<Integer, String>();
307             Set<String> Csfs = levelThree.get(i).consolidatedCodecInfo;
308             if (Csfs.size() > 0) {
309                 for (String codecInfo : Csfs) {
310                     byte[] ltvEntries = codecInfo.getBytes();
311                     int k = 0;
312                     byte length = ltvEntries[k++];
313                     byte[] ltv = new byte[length + 1];
314                     ltv[0] = length;
315                     System.arraycopy(ltvEntries, k, ltv, 1, length);
316                     int type = (int) ltv[1];
317                     String s = uniqueCcis.get(type);
318                     String ltvS = new String(ltv);
319                     if (s == null) {
320                         uniqueCcis.put(type, ltvS);
321                     } else {
322                         // if same type exists, replace
323                         uniqueCcis.replace(type, ltvS);
324                     }
325                 }
326             }
327             Set<String> Mds = levelThree.get(i).consolidatedMetadata;
328             if (Mds.size() > 0) {
329                 for (String metadata : Mds) {
330                     byte[] ltvEntries = metadata.getBytes();
331                     int k = 0;
332                     byte length = ltvEntries[k++];
333                     byte[] ltv = new byte[length + 1];
334                     ltv[0] = length;
335                     System.arraycopy(ltvEntries, k, ltv, 1, length);
336                     int type = (int) ltv[1];
337                     String s = uniqueCcis.get(type);
338                     String ltvS = new String(ltv);
339                     if (s == null) {
340                         uniqueMds.put(type, ltvS);
341                     } else {
342                         uniqueMds.replace(type, ltvS);
343                     }
344                 }
345             }
346             levelThree.get(i).consolidatedUniqueMetadata = new HashMap<Integer, String>(uniqueMds);
347             levelThree.get(i).consolidatedUniqueCodecInfo =
348                     new HashMap<Integer, String>(uniqueCcis);
349         }
350     }
351 
consolidateBaseofLevelThree( ArrayList<BaseInformation> levelTwo, ArrayList<BaseInformation> levelThree, int parentSubgroup, int startIdx, int numNodes)352     static void consolidateBaseofLevelThree(
353             ArrayList<BaseInformation> levelTwo,
354             ArrayList<BaseInformation> levelThree,
355             int parentSubgroup,
356             int startIdx,
357             int numNodes) {
358         for (int i = startIdx; i < startIdx + numNodes || i < levelThree.size(); i++) {
359             levelThree.get(i).subGroupId = levelTwo.get(parentSubgroup).subGroupId;
360             log("Copy Codec Id from Level2 Parent" + parentSubgroup);
361             System.arraycopy(
362                     levelTwo.get(parentSubgroup).consolidatedCodecId,
363                     0,
364                     levelThree.get(i).consolidatedCodecId,
365                     0,
366                     5);
367             // Metadata clone from Parent
368             levelThree.get(i).consolidatedMetadata =
369                     new LinkedHashSet<String>(levelTwo.get(parentSubgroup).consolidatedMetadata);
370             // CCI clone from Parent
371             levelThree.get(i).consolidatedCodecInfo =
372                     new LinkedHashSet<String>(levelTwo.get(parentSubgroup).consolidatedCodecInfo);
373             // Append Level 2 Codec Config
374             if (levelThree.get(i).codecConfigLength != 0) {
375                 log("append level 3 cci to level 3 cons:" + i);
376                 String s = new String(levelThree.get(i).codecConfigInfo);
377                 levelThree.get(i).consolidatedCodecInfo.add(s);
378             }
379         }
380     }
381 
getNumberOfIndices()382     public int getNumberOfIndices() {
383         return mNumBISIndices;
384     }
385 
getLevelOne()386     public BaseInformation getLevelOne() {
387         return mLevelOne;
388     }
389 
getLevelTwo()390     public ArrayList<BaseInformation> getLevelTwo() {
391         return mLevelTwo;
392     }
393 
getLevelThree()394     public ArrayList<BaseInformation> getLevelThree() {
395         return mLevelThree;
396     }
397 
getNumberOfSubgroupsofBIG()398     public byte getNumberOfSubgroupsofBIG() {
399         byte ret = 0;
400         if (mLevelOne != null) {
401             ret = mLevelOne.numSubGroups;
402         }
403         return ret;
404     }
405 
getBISIndexInfos()406     public ArrayList<BaseInformation> getBISIndexInfos() {
407         return mLevelThree;
408     }
409 
getMetadata(int subGroup)410     byte[] getMetadata(int subGroup) {
411         if (mLevelTwo != null) {
412             return mLevelTwo.get(subGroup).metaData;
413         }
414         return null;
415     }
416 
getMetadataString(byte[] metadataBytes)417     String getMetadataString(byte[] metadataBytes) {
418         String ret = "";
419         switch (metadataBytes[1]) {
420             case METADATA_LANGUAGE_TYPE:
421                 char[] lang = new char[3];
422                 System.arraycopy(metadataBytes, 1, lang, 0, 3);
423                 Locale locale = new Locale(String.valueOf(lang));
424                 try {
425                     ret = locale.getISO3Language();
426                 } catch (MissingResourceException e) {
427                     ret = "UNKNOWN LANGUAGE";
428                 }
429                 break;
430             default:
431                 ret = "UNKNOWN METADATA TYPE";
432         }
433         log("getMetadataString: " + ret);
434         return ret;
435     }
436 
getCodecParamString(byte[] csiBytes)437     String getCodecParamString(byte[] csiBytes) {
438         String ret = "";
439         switch (csiBytes[1]) {
440             case CODEC_CONFIGURATION_CHANNEL_ALLOCATION_TYPE:
441                 byte[] location = new byte[4];
442                 System.arraycopy(csiBytes, 2, location, 0, 4);
443                 ByteBuffer wrapped = ByteBuffer.wrap(location);
444                 int audioLocation = wrapped.getInt();
445                 log("audioLocation: " + audioLocation);
446                 switch (audioLocation) {
447                     case CODEC_AUDIO_LOCATION_FRONT_LEFT:
448                         ret = "LEFT";
449                         break;
450                     case CODEC_AUDIO_LOCATION_FRONT_RIGHT:
451                         ret = "RIGHT";
452                         break;
453                     case CODEC_AUDIO_LOCATION_FRONT_LEFT | CODEC_AUDIO_LOCATION_FRONT_RIGHT:
454                         ret = "LR";
455                         break;
456                 }
457                 break;
458             case CODEC_CONFIGURATION_SAMPLE_RATE_TYPE:
459                 switch (csiBytes[2]) {
460                     case CODEC_AUDIO_SAMPLE_RATE_8K:
461                         ret = "8K";
462                         break;
463                     case CODEC_AUDIO_SAMPLE_RATE_16K:
464                         ret = "16K";
465                         break;
466                     case CODEC_AUDIO_SAMPLE_RATE_24K:
467                         ret = "24K";
468                         break;
469                     case CODEC_AUDIO_SAMPLE_RATE_32K:
470                         ret = "32K";
471                         break;
472                     case CODEC_AUDIO_SAMPLE_RATE_44P1K:
473                         ret = "44.1K";
474                         break;
475                     case CODEC_AUDIO_SAMPLE_RATE_48K:
476                         ret = "48K";
477                         break;
478                 }
479                 break;
480             case CODEC_CONFIGURATION_FRAME_DURATION_TYPE:
481                 switch (csiBytes[2]) {
482                     case CODEC_AUDIO_FRAME_DURATION_7P5MS:
483                         ret = "7.5ms";
484                         break;
485                     case CODEC_AUDIO_FRAME_DURATION_10MS:
486                         ret = "10ms";
487                         break;
488                 }
489                 break;
490             case CODEC_CONFIGURATION_OCTETS_PER_FRAME_TYPE:
491                 ret = "OPF_" + String.valueOf((int) csiBytes[2]);
492                 break;
493             default:
494                 ret = "UNKNOWN PARAMETER";
495         }
496         log("getCodecParamString: " + ret);
497         return ret;
498     }
499 
print()500     void print() {
501         mLevelOne.print();
502         log("----- Level TWO BASE ----");
503         for (int i = 0; i < mLevelTwo.size(); i++) {
504             mLevelTwo.get(i).print();
505         }
506         log("----- Level THREE BASE ----");
507         for (int i = 0; i < mLevelThree.size(); i++) {
508             mLevelThree.get(i).print();
509         }
510     }
511 
log(String msg)512     static void log(String msg) {
513         Log.d(TAG, msg);
514     }
515 }
516