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