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.net.wifi.nan;
18 
19 import libcore.io.Memory;
20 
21 import java.nio.BufferOverflowException;
22 import java.nio.ByteOrder;
23 import 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  */
33 public class TlvBufferUtils {
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          */
TlvConstructor(int typeSize, int lengthSize)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          */
wrap(byte[] array)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          */
allocate(int capacity)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          */
putByte(int type, byte b)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          */
putByteArray(int type, byte[] array, int offset, int length)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          */
putByteArray(int type, byte[] array)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          */
putZeroLengthElement(int type)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          */
putShort(int type, short data)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          */
putInt(int type, int data)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          */
putString(int type, String data)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          */
getArray()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          */
getActualLength()252         public int getActualLength() {
253             return mPosition;
254         }
255 
checkLength(int dataLength)256         private void checkLength(int dataLength) {
257             if (mPosition + mTypeSize + mLengthSize + dataLength > mArrayLength) {
258                 throw new BufferOverflowException();
259             }
260         }
261 
addHeader(int type, int length)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 
TlvElement(int type, int length, byte[] refArray, int offset)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          */
getByte()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          */
getShort()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          */
getInt()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          */
getString()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          */
TlvIterable(int typeSize, int lengthSize, byte[] array, int length)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
toString()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
iterator()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