Home | History | Annotate | Download | only in aware
      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.aware;
     18 
     19 import android.annotation.Nullable;
     20 
     21 import libcore.io.Memory;
     22 
     23 import java.nio.BufferOverflowException;
     24 import java.nio.ByteOrder;
     25 import java.util.ArrayList;
     26 import java.util.Arrays;
     27 import java.util.Iterator;
     28 import java.util.List;
     29 import 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  */
     39 public 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