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