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
17package android.net.wifi.aware;
18
19import android.annotation.Nullable;
20
21import libcore.io.Memory;
22
23import java.nio.BufferOverflowException;
24import java.nio.ByteOrder;
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.Iterator;
28import java.util.List;
29import java.util.NoSuchElementException;
30
31/**
32 * Utility class to construct and parse byte arrays using the TLV format -
33 * Type/Length/Value format. The utilities accept a configuration of the size of
34 * the Type field and the Length field. A Type field size of 0 is allowed -
35 * allowing usage for LV (no T) array formats.
36 *
37 * @hide
38 */
39public class TlvBufferUtils {
40    private TlvBufferUtils() {
41        // no reason to ever create this class
42    }
43
44    /**
45     * Utility class to construct byte arrays using the TLV format -
46     * Type/Length/Value.
47     * <p>
48     * A constructor is created specifying the size of the Type (T) and Length
49     * (L) fields. A specification of zero size T field is allowed - resulting
50     * in LV type format.
51     * <p>
52     * The byte array is either provided (using
53     * {@link TlvConstructor#wrap(byte[])}) or allocated (using
54     * {@link TlvConstructor#allocate(int)}).
55     * <p>
56     * Values are added to the structure using the {@code TlvConstructor.put*()}
57     * methods.
58     * <p>
59     * The final byte array is obtained using {@link TlvConstructor#getArray()}.
60     */
61    public static class TlvConstructor {
62        private int mTypeSize;
63        private int mLengthSize;
64
65        private byte[] mArray;
66        private int mArrayLength;
67        private int mPosition;
68
69        /**
70         * Define a TLV constructor with the specified size of the Type (T) and
71         * Length (L) fields.
72         *
73         * @param typeSize Number of bytes used for the Type (T) field. Values
74         *            of 0, 1, or 2 bytes are allowed. A specification of 0
75         *            bytes implies that the field being constructed has the LV
76         *            format rather than the TLV format.
77         * @param lengthSize Number of bytes used for the Length (L) field.
78         *            Values of 1 or 2 bytes are allowed.
79         */
80        public TlvConstructor(int typeSize, int lengthSize) {
81            if (typeSize < 0 || typeSize > 2 || lengthSize <= 0 || lengthSize > 2) {
82                throw new IllegalArgumentException(
83                        "Invalid sizes - typeSize=" + typeSize + ", lengthSize=" + lengthSize);
84            }
85            mTypeSize = typeSize;
86            mLengthSize = lengthSize;
87        }
88
89        /**
90         * Set the byte array to be used to construct the TLV.
91         *
92         * @param array Byte array to be formatted.
93         * @return The constructor to facilitate chaining
94         *         {@code ctr.putXXX(..).putXXX(..)}.
95         */
96        public TlvConstructor wrap(@Nullable byte[] array) {
97            mArray = array;
98            mArrayLength = (array == null) ? 0 : array.length;
99            return this;
100        }
101
102        /**
103         * Allocates a new byte array to be used ot construct a TLV.
104         *
105         * @param capacity The size of the byte array to be allocated.
106         * @return The constructor to facilitate chaining
107         *         {@code ctr.putXXX(..).putXXX(..)}.
108         */
109        public TlvConstructor allocate(int capacity) {
110            mArray = new byte[capacity];
111            mArrayLength = capacity;
112            return this;
113        }
114
115        /**
116         * Creates a TLV array (of the previously specified Type and Length sizes) from the input
117         * list. Allocates an array matching the contents (and required Type and Length
118         * fields), copies the contents, and set the Length fields. The Type field is set to 0.
119         *
120         * @param list A list of fields to be added to the TLV buffer.
121         * @return The constructor of the TLV.
122         */
123        public TlvConstructor allocateAndPut(@Nullable List<byte[]> list) {
124            if (list != null) {
125                int size = 0;
126                for (byte[] field : list) {
127                    size += mTypeSize + mLengthSize;
128                    if (field != null) {
129                        size += field.length;
130                    }
131                }
132                allocate(size);
133                for (byte[] field : list) {
134                    putByteArray(0, field);
135                }
136            }
137            return this;
138        }
139
140        /**
141         * Copies a byte into the TLV with the indicated type. For an LV
142         * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
143         * TlvConstructor(int, int)} ) the type field is ignored.
144         *
145         * @param type The value to be placed into the Type field.
146         * @param b The byte to be inserted into the structure.
147         * @return The constructor to facilitate chaining
148         *         {@code ctr.putXXX(..).putXXX(..)}.
149         */
150        public TlvConstructor putByte(int type, byte b) {
151            checkLength(1);
152            addHeader(type, 1);
153            mArray[mPosition++] = b;
154            return this;
155        }
156
157        /**
158         * Copies a byte array into the TLV with the indicated type. For an LV
159         * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
160         * TlvConstructor(int, int)} ) the type field is ignored.
161         *
162         * @param type The value to be placed into the Type field.
163         * @param array The array to be copied into the TLV structure.
164         * @param offset Start copying from the array at the specified offset.
165         * @param length Copy the specified number (length) of bytes from the
166         *            array.
167         * @return The constructor to facilitate chaining
168         *         {@code ctr.putXXX(..).putXXX(..)}.
169         */
170        public TlvConstructor putByteArray(int type, @Nullable byte[] array, int offset,
171                int length) {
172            checkLength(length);
173            addHeader(type, length);
174            if (length != 0) {
175                System.arraycopy(array, offset, mArray, mPosition, length);
176            }
177            mPosition += length;
178            return this;
179        }
180
181        /**
182         * Copies a byte array into the TLV with the indicated type. For an LV
183         * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
184         * TlvConstructor(int, int)} ) the type field is ignored.
185         *
186         * @param type The value to be placed into the Type field.
187         * @param array The array to be copied (in full) into the TLV structure.
188         * @return The constructor to facilitate chaining
189         *         {@code ctr.putXXX(..).putXXX(..)}.
190         */
191        public TlvConstructor putByteArray(int type, @Nullable byte[] array) {
192            return putByteArray(type, array, 0, (array == null) ? 0 : array.length);
193        }
194
195        /**
196         * Places a zero length element (i.e. Length field = 0) into the TLV.
197         * For an LV formatted structure (i.e. typeLength=0 in
198         * {@link TlvConstructor TlvConstructor(int, int)} ) the type field is
199         * ignored.
200         *
201         * @param type The value to be placed into the Type field.
202         * @return The constructor to facilitate chaining
203         *         {@code ctr.putXXX(..).putXXX(..)}.
204         */
205        public TlvConstructor putZeroLengthElement(int type) {
206            checkLength(0);
207            addHeader(type, 0);
208            return this;
209        }
210
211        /**
212         * Copies short into the TLV with the indicated type. For an LV
213         * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
214         * TlvConstructor(int, int)} ) the type field is ignored.
215         *
216         * @param type The value to be placed into the Type field.
217         * @param data The short to be inserted into the structure.
218         * @return The constructor to facilitate chaining
219         *         {@code ctr.putXXX(..).putXXX(..)}.
220         */
221        public TlvConstructor putShort(int type, short data) {
222            checkLength(2);
223            addHeader(type, 2);
224            Memory.pokeShort(mArray, mPosition, data, ByteOrder.BIG_ENDIAN);
225            mPosition += 2;
226            return this;
227        }
228
229        /**
230         * Copies integer into the TLV with the indicated type. For an LV
231         * formatted structure (i.e. typeLength=0 in {@link TlvConstructor
232         * TlvConstructor(int, int)} ) the type field is ignored.
233         *
234         * @param type The value to be placed into the Type field.
235         * @param data The integer to be inserted into the structure.
236         * @return The constructor to facilitate chaining
237         *         {@code ctr.putXXX(..).putXXX(..)}.
238         */
239        public TlvConstructor putInt(int type, int data) {
240            checkLength(4);
241            addHeader(type, 4);
242            Memory.pokeInt(mArray, mPosition, data, ByteOrder.BIG_ENDIAN);
243            mPosition += 4;
244            return this;
245        }
246
247        /**
248         * Copies a String's byte representation into the TLV with the indicated
249         * type. For an LV formatted structure (i.e. typeLength=0 in
250         * {@link TlvConstructor TlvConstructor(int, int)} ) the type field is
251         * ignored.
252         *
253         * @param type The value to be placed into the Type field.
254         * @param data The string whose bytes are to be inserted into the
255         *            structure.
256         * @return The constructor to facilitate chaining
257         *         {@code ctr.putXXX(..).putXXX(..)}.
258         */
259        public TlvConstructor putString(int type, @Nullable String data) {
260            byte[] bytes = null;
261            int length = 0;
262            if (data != null) {
263                bytes = data.getBytes();
264                length = bytes.length;
265            }
266            return putByteArray(type, bytes, 0, length);
267        }
268
269        /**
270         * Returns the constructed TLV formatted byte-array. This array is a copy of the wrapped
271         * or allocated array - truncated to just the significant bytes - i.e. those written into
272         * the (T)LV.
273         *
274         * @return The byte array containing the TLV formatted structure.
275         */
276        public byte[] getArray() {
277            return Arrays.copyOf(mArray, getActualLength());
278        }
279
280        /**
281         * Returns the size of the TLV formatted portion of the wrapped or
282         * allocated byte array. The array itself is returned with
283         * {@link TlvConstructor#getArray()}.
284         *
285         * @return The size of the TLV formatted portion of the byte array.
286         */
287        private int getActualLength() {
288            return mPosition;
289        }
290
291        private void checkLength(int dataLength) {
292            if (mPosition + mTypeSize + mLengthSize + dataLength > mArrayLength) {
293                throw new BufferOverflowException();
294            }
295        }
296
297        private void addHeader(int type, int length) {
298            if (mTypeSize == 1) {
299                mArray[mPosition] = (byte) type;
300            } else if (mTypeSize == 2) {
301                Memory.pokeShort(mArray, mPosition, (short) type, ByteOrder.BIG_ENDIAN);
302            }
303            mPosition += mTypeSize;
304
305            if (mLengthSize == 1) {
306                mArray[mPosition] = (byte) length;
307            } else if (mLengthSize == 2) {
308                Memory.pokeShort(mArray, mPosition, (short) length, ByteOrder.BIG_ENDIAN);
309            }
310            mPosition += mLengthSize;
311        }
312    }
313
314    /**
315     * Utility class used when iterating over a TLV formatted byte-array. Use
316     * {@link TlvIterable} to iterate over array. A {@link TlvElement}
317     * represents each entry in a TLV formatted byte-array.
318     */
319    public static class TlvElement {
320        /**
321         * The Type (T) field of the current TLV element. Note that for LV
322         * formatted byte-arrays (i.e. TLV whose Type/T size is 0) the value of
323         * this field is undefined.
324         */
325        public int type;
326
327        /**
328         * The Length (L) field of the current TLV element.
329         */
330        public int length;
331
332        /**
333         * The Value (V) field - a raw byte array representing the current TLV
334         * element where the entry starts at {@link TlvElement#offset}.
335         */
336        public byte[] refArray;
337
338        /**
339         * The offset to be used into {@link TlvElement#refArray} to access the
340         * raw data representing the current TLV element.
341         */
342        public int offset;
343
344        private TlvElement(int type, int length, @Nullable byte[] refArray, int offset) {
345            this.type = type;
346            this.length = length;
347            this.refArray = refArray;
348            this.offset = offset;
349
350            if (offset + length > refArray.length) {
351                throw new BufferOverflowException();
352            }
353        }
354
355        /**
356         * Utility function to return a byte representation of a TLV element of
357         * length 1. Note: an attempt to call this function on a TLV item whose
358         * {@link TlvElement#length} is != 1 will result in an exception.
359         *
360         * @return byte representation of current TLV element.
361         */
362        public byte getByte() {
363            if (length != 1) {
364                throw new IllegalArgumentException(
365                        "Accesing a byte from a TLV element of length " + length);
366            }
367            return refArray[offset];
368        }
369
370        /**
371         * Utility function to return a short representation of a TLV element of
372         * length 2. Note: an attempt to call this function on a TLV item whose
373         * {@link TlvElement#length} is != 2 will result in an exception.
374         *
375         * @return short representation of current TLV element.
376         */
377        public short getShort() {
378            if (length != 2) {
379                throw new IllegalArgumentException(
380                        "Accesing a short from a TLV element of length " + length);
381            }
382            return Memory.peekShort(refArray, offset, ByteOrder.BIG_ENDIAN);
383        }
384
385        /**
386         * Utility function to return an integer representation of a TLV element
387         * of length 4. Note: an attempt to call this function on a TLV item
388         * whose {@link TlvElement#length} is != 4 will result in an exception.
389         *
390         * @return integer representation of current TLV element.
391         */
392        public int getInt() {
393            if (length != 4) {
394                throw new IllegalArgumentException(
395                        "Accesing an int from a TLV element of length " + length);
396            }
397            return Memory.peekInt(refArray, offset, ByteOrder.BIG_ENDIAN);
398        }
399
400        /**
401         * Utility function to return a String representation of a TLV element.
402         *
403         * @return String repersentation of the current TLV element.
404         */
405        public String getString() {
406            return new String(refArray, offset, length);
407        }
408    }
409
410    /**
411     * Utility class to iterate over a TLV formatted byte-array.
412     */
413    public static class TlvIterable implements Iterable<TlvElement> {
414        private int mTypeSize;
415        private int mLengthSize;
416        private byte[] mArray;
417        private int mArrayLength;
418
419        /**
420         * Constructs a TlvIterable object - specifying the format of the TLV
421         * (the sizes of the Type and Length fields), and the byte array whose
422         * data is to be parsed.
423         *
424         * @param typeSize Number of bytes used for the Type (T) field. Valid
425         *            values are 0 (i.e. indicating the format is LV rather than
426         *            TLV), 1, and 2 bytes.
427         * @param lengthSize Number of bytes used for the Length (L) field.
428         *            Values values are 1 or 2 bytes.
429         * @param array The TLV formatted byte-array to parse.
430         */
431        public TlvIterable(int typeSize, int lengthSize, @Nullable byte[] array) {
432            if (typeSize < 0 || typeSize > 2 || lengthSize <= 0 || lengthSize > 2) {
433                throw new IllegalArgumentException(
434                        "Invalid sizes - typeSize=" + typeSize + ", lengthSize=" + lengthSize);
435            }
436            mTypeSize = typeSize;
437            mLengthSize = lengthSize;
438            mArray = array;
439            mArrayLength = (array == null) ? 0 : array.length;
440        }
441
442        /**
443         * Prints out a parsed representation of the TLV-formatted byte array.
444         * Whenever possible bytes, shorts, and integer are printed out (for
445         * fields whose length is 1, 2, or 4 respectively).
446         */
447        @Override
448        public String toString() {
449            StringBuilder builder = new StringBuilder();
450
451            builder.append("[");
452            boolean first = true;
453            for (TlvElement tlv : this) {
454                if (!first) {
455                    builder.append(",");
456                }
457                first = false;
458                builder.append(" (");
459                if (mTypeSize != 0) {
460                    builder.append("T=" + tlv.type + ",");
461                }
462                builder.append("L=" + tlv.length + ") ");
463                if (tlv.length == 0) {
464                    builder.append("<null>");
465                } else if (tlv.length == 1) {
466                    builder.append(tlv.getByte());
467                } else if (tlv.length == 2) {
468                    builder.append(tlv.getShort());
469                } else if (tlv.length == 4) {
470                    builder.append(tlv.getInt());
471                } else {
472                    builder.append("<bytes>");
473                }
474                if (tlv.length != 0) {
475                    builder.append(" (S='" + tlv.getString() + "')");
476                }
477            }
478            builder.append("]");
479
480            return builder.toString();
481        }
482
483        /**
484         * Returns a List with the raw contents (no types) of the iterator.
485         */
486        public List<byte[]> toList() {
487            List<byte[]> list = new ArrayList<>();
488            for (TlvElement tlv : this) {
489                list.add(Arrays.copyOfRange(tlv.refArray, tlv.offset, tlv.offset + tlv.length));
490            }
491
492            return list;
493        }
494
495        /**
496         * Returns an iterator to step through a TLV formatted byte-array. The
497         * individual elements returned by the iterator are {@link TlvElement}.
498         */
499        @Override
500        public Iterator<TlvElement> iterator() {
501            return new Iterator<TlvElement>() {
502                private int mOffset = 0;
503
504                @Override
505                public boolean hasNext() {
506                    return mOffset < mArrayLength;
507                }
508
509                @Override
510                public TlvElement next() {
511                    if (!hasNext()) {
512                        throw new NoSuchElementException();
513                    }
514
515                    int type = 0;
516                    if (mTypeSize == 1) {
517                        type = mArray[mOffset];
518                    } else if (mTypeSize == 2) {
519                        type = Memory.peekShort(mArray, mOffset, ByteOrder.BIG_ENDIAN);
520                    }
521                    mOffset += mTypeSize;
522
523                    int length = 0;
524                    if (mLengthSize == 1) {
525                        length = mArray[mOffset];
526                    } else if (mLengthSize == 2) {
527                        length = Memory.peekShort(mArray, mOffset, ByteOrder.BIG_ENDIAN);
528                    }
529                    mOffset += mLengthSize;
530
531                    TlvElement tlv = new TlvElement(type, length, mArray, mOffset);
532                    mOffset += length;
533                    return tlv;
534                }
535
536                @Override
537                public void remove() {
538                    throw new UnsupportedOperationException();
539                }
540            };
541        }
542    }
543
544    /**
545     * Validates that a (T)LV array is constructed correctly. I.e. that its specified Length
546     * fields correctly fill the specified length (and do not overshoot).
547     *
548     * @param array The (T)LV array to verify.
549     * @param typeSize The size (in bytes) of the type field. Valid values are 0, 1, or 2.
550     * @param lengthSize The size (in bytes) of the length field. Valid values are 1 or 2.
551     * @return A boolean indicating whether the array is valid (true) or invalid (false).
552     */
553    public static boolean isValid(@Nullable byte[] array, int typeSize, int lengthSize) {
554        if (typeSize < 0 || typeSize > 2) {
555            throw new IllegalArgumentException(
556                    "Invalid arguments - typeSize must be 0, 1, or 2: typeSize=" + typeSize);
557        }
558        if (lengthSize <= 0 || lengthSize > 2) {
559            throw new IllegalArgumentException(
560                    "Invalid arguments - lengthSize must be 1 or 2: lengthSize=" + lengthSize);
561        }
562        if (array == null) {
563            return true;
564        }
565
566        int nextTlvIndex = 0;
567        while (nextTlvIndex + typeSize + lengthSize <= array.length) {
568            nextTlvIndex += typeSize;
569            if (lengthSize == 1) {
570                nextTlvIndex += lengthSize + array[nextTlvIndex];
571            } else {
572                nextTlvIndex += lengthSize + Memory.peekShort(array, nextTlvIndex,
573                        ByteOrder.BIG_ENDIAN);
574            }
575        }
576
577        return nextTlvIndex == array.length;
578    }
579}
580