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 { 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 private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN; 65 66 private byte[] mArray; 67 private int mArrayLength; 68 private int mPosition; 69 70 /** 71 * Define a TLV constructor with the specified size of the Type (T) and 72 * Length (L) fields. 73 * 74 * @param typeSize Number of bytes used for the Type (T) field. Values 75 * of 0, 1, or 2 bytes are allowed. A specification of 0 76 * bytes implies that the field being constructed has the LV 77 * format rather than the TLV format. 78 * @param lengthSize Number of bytes used for the Length (L) field. 79 * Values of 1 or 2 bytes are allowed. 80 */ TlvConstructor(int typeSize, int lengthSize)81 public TlvConstructor(int typeSize, int lengthSize) { 82 if (typeSize < 0 || typeSize > 2 || lengthSize <= 0 || lengthSize > 2) { 83 throw new IllegalArgumentException( 84 "Invalid sizes - typeSize=" + typeSize + ", lengthSize=" + lengthSize); 85 } 86 mTypeSize = typeSize; 87 mLengthSize = lengthSize; 88 mPosition = 0; 89 } 90 91 /** 92 * Configure the TLV constructor to use a particular byte order. Should be 93 * {@link ByteOrder#BIG_ENDIAN} (the default at construction) or 94 * {@link ByteOrder#LITTLE_ENDIAN}. 95 * 96 * @return The constructor to facilitate chaining 97 * {@code ctr.putXXX(..).putXXX(..)}. 98 */ setByteOrder(ByteOrder byteOrder)99 public TlvConstructor setByteOrder(ByteOrder byteOrder) { 100 mByteOrder = byteOrder; 101 return this; 102 } 103 104 /** 105 * Set the byte array to be used to construct the TLV. 106 * 107 * @param array Byte array to be formatted. 108 * @return The constructor to facilitate chaining 109 * {@code ctr.putXXX(..).putXXX(..)}. 110 */ wrap(@ullable byte[] array)111 public TlvConstructor wrap(@Nullable byte[] array) { 112 mArray = array; 113 mArrayLength = (array == null) ? 0 : array.length; 114 mPosition = 0; 115 return this; 116 } 117 118 /** 119 * Allocates a new byte array to be used ot construct a TLV. 120 * 121 * @param capacity The size of the byte array to be allocated. 122 * @return The constructor to facilitate chaining 123 * {@code ctr.putXXX(..).putXXX(..)}. 124 */ allocate(int capacity)125 public TlvConstructor allocate(int capacity) { 126 mArray = new byte[capacity]; 127 mArrayLength = capacity; 128 mPosition = 0; 129 return this; 130 } 131 132 /** 133 * Creates a TLV array (of the previously specified Type and Length sizes) from the input 134 * list. Allocates an array matching the contents (and required Type and Length 135 * fields), copies the contents, and set the Length fields. The Type field is set to 0. 136 * 137 * @param list A list of fields to be added to the TLV buffer. 138 * @return The constructor of the TLV. 139 */ allocateAndPut(@ullable List<byte[]> list)140 public TlvConstructor allocateAndPut(@Nullable List<byte[]> list) { 141 if (list != null) { 142 int size = 0; 143 for (byte[] field : list) { 144 size += mTypeSize + mLengthSize; 145 if (field != null) { 146 size += field.length; 147 } 148 } 149 allocate(size); 150 for (byte[] field : list) { 151 putByteArray(0, field); 152 } 153 } 154 return this; 155 } 156 157 /** 158 * Copies a byte 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 b The byte to be inserted into the structure. 164 * @return The constructor to facilitate chaining 165 * {@code ctr.putXXX(..).putXXX(..)}. 166 */ putByte(int type, byte b)167 public TlvConstructor putByte(int type, byte b) { 168 checkLength(1); 169 addHeader(type, 1); 170 mArray[mPosition++] = b; 171 return this; 172 } 173 174 /** 175 * Copies a raw byte into the TLV buffer - without a type or a length. 176 * 177 * @param b The byte to be inserted into the structure. 178 * @return The constructor to facilitate chaining {@code cts.putXXX(..).putXXX(..)}. 179 */ putRawByte(byte b)180 public TlvConstructor putRawByte(byte b) { 181 checkRawLength(1); 182 mArray[mPosition++] = b; 183 return this; 184 } 185 186 /** 187 * Copies a byte array into the TLV with the indicated type. For an LV 188 * formatted structure (i.e. typeLength=0 in {@link TlvConstructor 189 * TlvConstructor(int, int)} ) the type field is ignored. 190 * 191 * @param type The value to be placed into the Type field. 192 * @param array The array to be copied into the TLV structure. 193 * @param offset Start copying from the array at the specified offset. 194 * @param length Copy the specified number (length) of bytes from the 195 * array. 196 * @return The constructor to facilitate chaining 197 * {@code ctr.putXXX(..).putXXX(..)}. 198 */ putByteArray(int type, @Nullable byte[] array, int offset, int length)199 public TlvConstructor putByteArray(int type, @Nullable byte[] array, int offset, 200 int length) { 201 checkLength(length); 202 addHeader(type, length); 203 if (length != 0) { 204 System.arraycopy(array, offset, mArray, mPosition, length); 205 } 206 mPosition += length; 207 return this; 208 } 209 210 /** 211 * Copies a byte array into the TLV with the indicated type. For an LV 212 * formatted structure (i.e. typeLength=0 in {@link TlvConstructor 213 * TlvConstructor(int, int)} ) the type field is ignored. 214 * 215 * @param type The value to be placed into the Type field. 216 * @param array The array to be copied (in full) into the TLV structure. 217 * @return The constructor to facilitate chaining 218 * {@code ctr.putXXX(..).putXXX(..)}. 219 */ putByteArray(int type, @Nullable byte[] array)220 public TlvConstructor putByteArray(int type, @Nullable byte[] array) { 221 return putByteArray(type, array, 0, (array == null) ? 0 : array.length); 222 } 223 224 /** 225 * Copies a byte array into the TLV - without a type or a length. 226 * 227 * @param array The array to be copied (in full) into the TLV structure. 228 * @return The constructor to facilitate chaining 229 * {@code ctr.putXXX(..).putXXX(..)}. 230 */ putRawByteArray(@ullable byte[] array)231 public TlvConstructor putRawByteArray(@Nullable byte[] array) { 232 if (array == null) return this; 233 234 checkRawLength(array.length); 235 System.arraycopy(array, 0, mArray, mPosition, array.length); 236 mPosition += array.length; 237 return this; 238 } 239 240 /** 241 * Places a zero length element (i.e. Length field = 0) into the TLV. 242 * For an LV formatted structure (i.e. typeLength=0 in 243 * {@link TlvConstructor TlvConstructor(int, int)} ) the type field is 244 * ignored. 245 * 246 * @param type The value to be placed into the Type field. 247 * @return The constructor to facilitate chaining 248 * {@code ctr.putXXX(..).putXXX(..)}. 249 */ putZeroLengthElement(int type)250 public TlvConstructor putZeroLengthElement(int type) { 251 checkLength(0); 252 addHeader(type, 0); 253 return this; 254 } 255 256 /** 257 * Copies short into the TLV with the indicated type. For an LV 258 * formatted structure (i.e. typeLength=0 in {@link TlvConstructor 259 * TlvConstructor(int, int)} ) the type field is ignored. 260 * 261 * @param type The value to be placed into the Type field. 262 * @param data The short to be inserted into the structure. 263 * @return The constructor to facilitate chaining 264 * {@code ctr.putXXX(..).putXXX(..)}. 265 */ putShort(int type, short data)266 public TlvConstructor putShort(int type, short data) { 267 checkLength(2); 268 addHeader(type, 2); 269 Memory.pokeShort(mArray, mPosition, data, mByteOrder); 270 mPosition += 2; 271 return this; 272 } 273 274 /** 275 * Copies integer into the TLV with the indicated type. For an LV 276 * formatted structure (i.e. typeLength=0 in {@link TlvConstructor 277 * TlvConstructor(int, int)} ) the type field is ignored. 278 * 279 * @param type The value to be placed into the Type field. 280 * @param data The integer to be inserted into the structure. 281 * @return The constructor to facilitate chaining 282 * {@code ctr.putXXX(..).putXXX(..)}. 283 */ putInt(int type, int data)284 public TlvConstructor putInt(int type, int data) { 285 checkLength(4); 286 addHeader(type, 4); 287 Memory.pokeInt(mArray, mPosition, data, mByteOrder); 288 mPosition += 4; 289 return this; 290 } 291 292 /** 293 * Copies a String's byte representation into the TLV with the indicated 294 * type. For an LV formatted structure (i.e. typeLength=0 in 295 * {@link TlvConstructor TlvConstructor(int, int)} ) the type field is 296 * ignored. 297 * 298 * @param type The value to be placed into the Type field. 299 * @param data The string whose bytes are to be inserted into the 300 * structure. 301 * @return The constructor to facilitate chaining 302 * {@code ctr.putXXX(..).putXXX(..)}. 303 */ putString(int type, @Nullable String data)304 public TlvConstructor putString(int type, @Nullable String data) { 305 byte[] bytes = null; 306 int length = 0; 307 if (data != null) { 308 bytes = data.getBytes(); 309 length = bytes.length; 310 } 311 return putByteArray(type, bytes, 0, length); 312 } 313 314 /** 315 * Returns the constructed TLV formatted byte-array. This array is a copy of the wrapped 316 * or allocated array - truncated to just the significant bytes - i.e. those written into 317 * the (T)LV. 318 * 319 * @return The byte array containing the TLV formatted structure. 320 */ getArray()321 public byte[] getArray() { 322 return Arrays.copyOf(mArray, getActualLength()); 323 } 324 325 /** 326 * Returns the size of the TLV formatted portion of the wrapped or 327 * allocated byte array. The array itself is returned with 328 * {@link TlvConstructor#getArray()}. 329 * 330 * @return The size of the TLV formatted portion of the byte array. 331 */ getActualLength()332 private int getActualLength() { 333 return mPosition; 334 } 335 checkLength(int dataLength)336 private void checkLength(int dataLength) { 337 if (mPosition + mTypeSize + mLengthSize + dataLength > mArrayLength) { 338 throw new BufferOverflowException(); 339 } 340 } 341 checkRawLength(int dataLength)342 private void checkRawLength(int dataLength) { 343 if (mPosition + dataLength > mArrayLength) { 344 throw new BufferOverflowException(); 345 } 346 } 347 addHeader(int type, int length)348 private void addHeader(int type, int length) { 349 if (mTypeSize == 1) { 350 mArray[mPosition] = (byte) type; 351 } else if (mTypeSize == 2) { 352 Memory.pokeShort(mArray, mPosition, (short) type, mByteOrder); 353 } 354 mPosition += mTypeSize; 355 356 if (mLengthSize == 1) { 357 mArray[mPosition] = (byte) length; 358 } else if (mLengthSize == 2) { 359 Memory.pokeShort(mArray, mPosition, (short) length, mByteOrder); 360 } 361 mPosition += mLengthSize; 362 } 363 } 364 365 /** 366 * Utility class used when iterating over a TLV formatted byte-array. Use 367 * {@link TlvIterable} to iterate over array. A {@link TlvElement} 368 * represents each entry in a TLV formatted byte-array. 369 */ 370 public static class TlvElement { 371 /** 372 * The Type (T) field of the current TLV element. Note that for LV 373 * formatted byte-arrays (i.e. TLV whose Type/T size is 0) the value of 374 * this field is undefined. 375 */ 376 public int type; 377 378 /** 379 * The Length (L) field of the current TLV element. 380 */ 381 public int length; 382 383 /** 384 * Control of the endianess of the TLV element - true for big-endian, false for little- 385 * endian. 386 */ 387 public ByteOrder byteOrder = ByteOrder.BIG_ENDIAN; 388 389 /** 390 * The Value (V) field - a raw byte array representing the current TLV 391 * element where the entry starts at {@link TlvElement#offset}. 392 */ 393 private byte[] mRefArray; 394 395 /** 396 * The offset to be used into {@link TlvElement#mRefArray} to access the 397 * raw data representing the current TLV element. 398 */ 399 public int offset; 400 TlvElement(int type, int length, @Nullable byte[] refArray, int offset)401 private TlvElement(int type, int length, @Nullable byte[] refArray, int offset) { 402 this.type = type; 403 this.length = length; 404 mRefArray = refArray; 405 this.offset = offset; 406 407 if (offset + length > refArray.length) { 408 throw new BufferOverflowException(); 409 } 410 } 411 412 /** 413 * Return the raw byte array of the Value (V) field. 414 * 415 * @return The Value (V) field as a byte array. 416 */ getRawData()417 public byte[] getRawData() { 418 return Arrays.copyOfRange(mRefArray, offset, offset + length); 419 } 420 421 /** 422 * Utility function to return a byte representation of a TLV element of 423 * length 1. Note: an attempt to call this function on a TLV item whose 424 * {@link TlvElement#length} is != 1 will result in an exception. 425 * 426 * @return byte representation of current TLV element. 427 */ getByte()428 public byte getByte() { 429 if (length != 1) { 430 throw new IllegalArgumentException( 431 "Accesing a byte from a TLV element of length " + length); 432 } 433 return mRefArray[offset]; 434 } 435 436 /** 437 * Utility function to return a short representation of a TLV element of 438 * length 2. Note: an attempt to call this function on a TLV item whose 439 * {@link TlvElement#length} is != 2 will result in an exception. 440 * 441 * @return short representation of current TLV element. 442 */ getShort()443 public short getShort() { 444 if (length != 2) { 445 throw new IllegalArgumentException( 446 "Accesing a short from a TLV element of length " + length); 447 } 448 return Memory.peekShort(mRefArray, offset, byteOrder); 449 } 450 451 /** 452 * Utility function to return an integer representation of a TLV element 453 * of length 4. Note: an attempt to call this function on a TLV item 454 * whose {@link TlvElement#length} is != 4 will result in an exception. 455 * 456 * @return integer representation of current TLV element. 457 */ getInt()458 public int getInt() { 459 if (length != 4) { 460 throw new IllegalArgumentException( 461 "Accesing an int from a TLV element of length " + length); 462 } 463 return Memory.peekInt(mRefArray, offset, byteOrder); 464 } 465 466 /** 467 * Utility function to return a String representation of a TLV element. 468 * 469 * @return String repersentation of the current TLV element. 470 */ getString()471 public String getString() { 472 return new String(mRefArray, offset, length); 473 } 474 } 475 476 /** 477 * Utility class to iterate over a TLV formatted byte-array. 478 */ 479 public static class TlvIterable implements Iterable<TlvElement> { 480 private int mTypeSize; 481 private int mLengthSize; 482 private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN; 483 private byte[] mArray; 484 private int mArrayLength; 485 486 /** 487 * Constructs a TlvIterable object - specifying the format of the TLV 488 * (the sizes of the Type and Length fields), and the byte array whose 489 * data is to be parsed. 490 * 491 * @param typeSize Number of bytes used for the Type (T) field. Valid 492 * values are 0 (i.e. indicating the format is LV rather than 493 * TLV), 1, and 2 bytes. 494 * @param lengthSize Number of bytes used for the Length (L) field. 495 * Values values are 1 or 2 bytes. 496 * @param array The TLV formatted byte-array to parse. 497 */ TlvIterable(int typeSize, int lengthSize, @Nullable byte[] array)498 public TlvIterable(int typeSize, int lengthSize, @Nullable byte[] array) { 499 if (typeSize < 0 || typeSize > 2 || lengthSize <= 0 || lengthSize > 2) { 500 throw new IllegalArgumentException( 501 "Invalid sizes - typeSize=" + typeSize + ", lengthSize=" + lengthSize); 502 } 503 mTypeSize = typeSize; 504 mLengthSize = lengthSize; 505 mArray = array; 506 mArrayLength = (array == null) ? 0 : array.length; 507 } 508 509 /** 510 * Configure the TLV iterator to use little-endian byte ordering. 511 */ setByteOrder(ByteOrder byteOrder)512 public void setByteOrder(ByteOrder byteOrder) { 513 mByteOrder = byteOrder; 514 } 515 516 /** 517 * Prints out a parsed representation of the TLV-formatted byte array. 518 * Whenever possible bytes, shorts, and integer are printed out (for 519 * fields whose length is 1, 2, or 4 respectively). 520 */ 521 @Override toString()522 public String toString() { 523 StringBuilder builder = new StringBuilder(); 524 525 builder.append("["); 526 boolean first = true; 527 for (TlvElement tlv : this) { 528 if (!first) { 529 builder.append(","); 530 } 531 first = false; 532 builder.append(" ("); 533 if (mTypeSize != 0) { 534 builder.append("T=" + tlv.type + ","); 535 } 536 builder.append("L=" + tlv.length + ") "); 537 if (tlv.length == 0) { 538 builder.append("<null>"); 539 } else if (tlv.length == 1) { 540 builder.append(tlv.getByte()); 541 } else if (tlv.length == 2) { 542 builder.append(tlv.getShort()); 543 } else if (tlv.length == 4) { 544 builder.append(tlv.getInt()); 545 } else { 546 builder.append("<bytes>"); 547 } 548 if (tlv.length != 0) { 549 builder.append(" (S='" + tlv.getString() + "')"); 550 } 551 } 552 builder.append("]"); 553 554 return builder.toString(); 555 } 556 557 /** 558 * Returns a List with the raw contents (no types) of the iterator. 559 */ toList()560 public List<byte[]> toList() { 561 List<byte[]> list = new ArrayList<>(); 562 for (TlvElement tlv : this) { 563 list.add(Arrays.copyOfRange(tlv.mRefArray, tlv.offset, tlv.offset + tlv.length)); 564 } 565 566 return list; 567 } 568 569 /** 570 * Returns an iterator to step through a TLV formatted byte-array. The 571 * individual elements returned by the iterator are {@link TlvElement}. 572 */ 573 @Override iterator()574 public Iterator<TlvElement> iterator() { 575 return new Iterator<TlvElement>() { 576 private int mOffset = 0; 577 578 @Override 579 public boolean hasNext() { 580 return mOffset < mArrayLength; 581 } 582 583 @Override 584 public TlvElement next() { 585 if (!hasNext()) { 586 throw new NoSuchElementException(); 587 } 588 589 int type = 0; 590 if (mTypeSize == 1) { 591 type = mArray[mOffset]; 592 } else if (mTypeSize == 2) { 593 type = Memory.peekShort(mArray, mOffset, mByteOrder); 594 } 595 mOffset += mTypeSize; 596 597 int length = 0; 598 if (mLengthSize == 1) { 599 length = mArray[mOffset]; 600 } else if (mLengthSize == 2) { 601 length = Memory.peekShort(mArray, mOffset, mByteOrder); 602 } 603 mOffset += mLengthSize; 604 605 TlvElement tlv = new TlvElement(type, length, mArray, mOffset); 606 tlv.byteOrder = mByteOrder; 607 mOffset += length; 608 return tlv; 609 } 610 611 @Override 612 public void remove() { 613 throw new UnsupportedOperationException(); 614 } 615 }; 616 } 617 } 618 619 /** 620 * Validates that a (T)LV array is constructed correctly. I.e. that its specified Length 621 * fields correctly fill the specified length (and do not overshoot). Uses big-endian 622 * byte ordering. 623 * 624 * @param array The (T)LV array to verify. 625 * @param typeSize The size (in bytes) of the type field. Valid values are 0, 1, or 2. 626 * @param lengthSize The size (in bytes) of the length field. Valid values are 1 or 2. 627 * @return A boolean indicating whether the array is valid (true) or invalid (false). 628 */ isValid(@ullable byte[] array, int typeSize, int lengthSize)629 public static boolean isValid(@Nullable byte[] array, int typeSize, int lengthSize) { 630 return isValidEndian(array, typeSize, lengthSize, ByteOrder.BIG_ENDIAN); 631 } 632 633 /** 634 * Validates that a (T)LV array is constructed correctly. I.e. that its specified Length 635 * fields correctly fill the specified length (and do not overshoot). 636 * 637 * @param array The (T)LV array to verify. 638 * @param typeSize The size (in bytes) of the type field. Valid values are 0, 1, or 2. 639 * @param lengthSize The size (in bytes) of the length field. Valid values are 1 or 2. 640 * @param byteOrder The endianness of the byte array: {@link ByteOrder#BIG_ENDIAN} or 641 * {@link ByteOrder#LITTLE_ENDIAN}. 642 * @return A boolean indicating whether the array is valid (true) or invalid (false). 643 */ isValidEndian(@ullable byte[] array, int typeSize, int lengthSize, ByteOrder byteOrder)644 public static boolean isValidEndian(@Nullable byte[] array, int typeSize, int lengthSize, 645 ByteOrder byteOrder) { 646 if (typeSize < 0 || typeSize > 2) { 647 throw new IllegalArgumentException( 648 "Invalid arguments - typeSize must be 0, 1, or 2: typeSize=" + typeSize); 649 } 650 if (lengthSize <= 0 || lengthSize > 2) { 651 throw new IllegalArgumentException( 652 "Invalid arguments - lengthSize must be 1 or 2: lengthSize=" + lengthSize); 653 } 654 if (array == null) { 655 return true; 656 } 657 658 int nextTlvIndex = 0; 659 while (nextTlvIndex + typeSize + lengthSize <= array.length) { 660 nextTlvIndex += typeSize; 661 if (lengthSize == 1) { 662 nextTlvIndex += lengthSize + array[nextTlvIndex]; 663 } else { 664 nextTlvIndex += lengthSize + Memory.peekShort(array, nextTlvIndex, byteOrder); 665 } 666 } 667 668 return nextTlvIndex == array.length; 669 } 670 } 671