1919import static java .nio .ByteOrder .LITTLE_ENDIAN ;
2020
2121import androidx .annotation .Nullable ;
22+ import androidx .annotation .VisibleForTesting ;
2223import com .google .common .collect .ImmutableSet ;
2324import com .google .common .primitives .Chars ;
2425import com .google .common .primitives .Ints ;
3031import java .nio .charset .Charset ;
3132import java .nio .charset .StandardCharsets ;
3233import java .util .Arrays ;
34+ import java .util .concurrent .atomic .AtomicBoolean ;
3335
3436/**
3537 * Wraps a byte array, providing a set of methods for parsing data from it. Numerical values are
@@ -52,6 +54,9 @@ public final class ParsableByteArray {
5254 StandardCharsets .UTF_16BE ,
5355 StandardCharsets .UTF_16LE );
5456
57+ // TODO: b/147657250 - Flip this to true
58+ private static final AtomicBoolean shouldEnforceLimitOnLegacyMethods = new AtomicBoolean ();
59+
5560 private byte [] data ;
5661 private int position ;
5762 // TODO(internal b/147657250): Enforce this limit on all read methods.
@@ -227,6 +232,7 @@ public void readBytes(ParsableBitArray bitArray, int length) {
227232 * @param length The number of bytes to read.
228233 */
229234 public void readBytes (byte [] buffer , int offset , int length ) {
235+ maybeAssertAtLeastBytesLeftForLegacyMethod (length );
230236 System .arraycopy (data , position , buffer , offset , length );
231237 position += length ;
232238 }
@@ -239,12 +245,14 @@ public void readBytes(byte[] buffer, int offset, int length) {
239245 * @param length The number of bytes to read.
240246 */
241247 public void readBytes (ByteBuffer buffer , int length ) {
248+ maybeAssertAtLeastBytesLeftForLegacyMethod (length );
242249 buffer .put (data , position , length );
243250 position += length ;
244251 }
245252
246253 /** Peeks at the next byte as an unsigned value. */
247254 public int peekUnsignedByte () {
255+ maybeAssertAtLeastBytesLeftForLegacyMethod (1 );
248256 return (data [position ] & 0xFF );
249257 }
250258
@@ -280,6 +288,7 @@ public char peekChar(Charset charset) {
280288
281289 /** Peek the UTF-16 char at {@link #position}{@code + offset}. */
282290 private char peekChar (ByteOrder byteOrder , int offset ) {
291+ maybeAssertAtLeastBytesLeftForLegacyMethod (2 );
283292 return byteOrder == BIG_ENDIAN
284293 ? Chars .fromBytes (data [position + offset ], data [position + offset + 1 ])
285294 : Chars .fromBytes (data [position + offset + 1 ], data [position + offset ]);
@@ -320,59 +329,69 @@ public int peekCodePoint(Charset charset) {
320329
321330 /** Reads the next byte as an unsigned value. */
322331 public int readUnsignedByte () {
332+ maybeAssertAtLeastBytesLeftForLegacyMethod (1 );
323333 return (data [position ++] & 0xFF );
324334 }
325335
326336 /** Reads the next two bytes as an unsigned value. */
327337 public int readUnsignedShort () {
338+ maybeAssertAtLeastBytesLeftForLegacyMethod (2 );
328339 return (data [position ++] & 0xFF ) << 8 | (data [position ++] & 0xFF );
329340 }
330341
331342 /** Reads the next two bytes as an unsigned value. */
332343 public int readLittleEndianUnsignedShort () {
344+ maybeAssertAtLeastBytesLeftForLegacyMethod (2 );
333345 return (data [position ++] & 0xFF ) | (data [position ++] & 0xFF ) << 8 ;
334346 }
335347
336348 /** Reads the next two bytes as a signed value. */
337349 public short readShort () {
350+ maybeAssertAtLeastBytesLeftForLegacyMethod (2 );
338351 return (short ) ((data [position ++] & 0xFF ) << 8 | (data [position ++] & 0xFF ));
339352 }
340353
341354 /** Reads the next two bytes as a signed value. */
342355 public short readLittleEndianShort () {
356+ maybeAssertAtLeastBytesLeftForLegacyMethod (2 );
343357 return (short ) ((data [position ++] & 0xFF ) | (data [position ++] & 0xFF ) << 8 );
344358 }
345359
346360 /** Reads the next three bytes as an unsigned value. */
347361 public int readUnsignedInt24 () {
362+ maybeAssertAtLeastBytesLeftForLegacyMethod (3 );
348363 return (data [position ++] & 0xFF ) << 16
349364 | (data [position ++] & 0xFF ) << 8
350365 | (data [position ++] & 0xFF );
351366 }
352367
353368 /** Reads the next three bytes as a signed value. */
354369 public int readInt24 () {
370+ maybeAssertAtLeastBytesLeftForLegacyMethod (3 );
355371 return ((data [position ++] & 0xFF ) << 24 ) >> 8
356372 | (data [position ++] & 0xFF ) << 8
357373 | (data [position ++] & 0xFF );
358374 }
359375
360376 /** Reads the next three bytes as a signed value in little endian order. */
361377 public int readLittleEndianInt24 () {
378+ maybeAssertAtLeastBytesLeftForLegacyMethod (3 );
362379 return (data [position ++] & 0xFF )
363380 | (data [position ++] & 0xFF ) << 8
364381 | (data [position ++] & 0xFF ) << 16 ;
365382 }
366383
367384 /** Reads the next three bytes as an unsigned value in little endian order. */
368385 public int readLittleEndianUnsignedInt24 () {
386+ maybeAssertAtLeastBytesLeftForLegacyMethod (3 );
369387 return (data [position ++] & 0xFF )
370388 | (data [position ++] & 0xFF ) << 8
371389 | (data [position ++] & 0xFF ) << 16 ;
372390 }
373391
374392 /** Reads the next four bytes as an unsigned value. */
375393 public long readUnsignedInt () {
394+ maybeAssertAtLeastBytesLeftForLegacyMethod (4 );
376395 return (data [position ++] & 0xFFL ) << 24
377396 | (data [position ++] & 0xFFL ) << 16
378397 | (data [position ++] & 0xFFL ) << 8
@@ -381,6 +400,7 @@ public long readUnsignedInt() {
381400
382401 /** Reads the next four bytes as an unsigned value in little endian order. */
383402 public long readLittleEndianUnsignedInt () {
403+ maybeAssertAtLeastBytesLeftForLegacyMethod (4 );
384404 return (data [position ++] & 0xFFL )
385405 | (data [position ++] & 0xFFL ) << 8
386406 | (data [position ++] & 0xFFL ) << 16
@@ -389,6 +409,7 @@ public long readLittleEndianUnsignedInt() {
389409
390410 /** Reads the next four bytes as a signed value */
391411 public int readInt () {
412+ maybeAssertAtLeastBytesLeftForLegacyMethod (4 );
392413 return (data [position ++] & 0xFF ) << 24
393414 | (data [position ++] & 0xFF ) << 16
394415 | (data [position ++] & 0xFF ) << 8
@@ -397,6 +418,7 @@ public int readInt() {
397418
398419 /** Reads the next four bytes as a signed value in little endian order. */
399420 public int readLittleEndianInt () {
421+ maybeAssertAtLeastBytesLeftForLegacyMethod (4 );
400422 return (data [position ++] & 0xFF )
401423 | (data [position ++] & 0xFF ) << 8
402424 | (data [position ++] & 0xFF ) << 16
@@ -405,6 +427,7 @@ public int readLittleEndianInt() {
405427
406428 /** Reads the next eight bytes as a signed value. */
407429 public long readLong () {
430+ maybeAssertAtLeastBytesLeftForLegacyMethod (8 );
408431 return (data [position ++] & 0xFFL ) << 56
409432 | (data [position ++] & 0xFFL ) << 48
410433 | (data [position ++] & 0xFFL ) << 40
@@ -417,6 +440,7 @@ public long readLong() {
417440
418441 /** Reads the next eight bytes as a signed value in little endian order. */
419442 public long readLittleEndianLong () {
443+ maybeAssertAtLeastBytesLeftForLegacyMethod (8 );
420444 return (data [position ++] & 0xFFL )
421445 | (data [position ++] & 0xFFL ) << 8
422446 | (data [position ++] & 0xFFL ) << 16
@@ -429,6 +453,7 @@ public long readLittleEndianLong() {
429453
430454 /** Reads the next four bytes, returning the integer portion of the fixed point 16.16 integer. */
431455 public int readUnsignedFixedPoint1616 () {
456+ maybeAssertAtLeastBytesLeftForLegacyMethod (4 );
432457 int result = (data [position ++] & 0xFF ) << 8 | (data [position ++] & 0xFF );
433458 position += 2 ; // Skip the non-integer portion.
434459 return result ;
@@ -518,6 +543,7 @@ public String readString(int length) {
518543 * @return The string encoded by the bytes in the specified character set.
519544 */
520545 public String readString (int length , Charset charset ) {
546+ maybeAssertAtLeastBytesLeftForLegacyMethod (length );
521547 String result = new String (data , position , length , charset );
522548 position += length ;
523549 return result ;
@@ -531,6 +557,7 @@ public String readString(int length, Charset charset) {
531557 * @return The string, not including any terminating NUL byte.
532558 */
533559 public String readNullTerminatedString (int length ) {
560+ maybeAssertAtLeastBytesLeftForLegacyMethod (length );
534561 if (length == 0 ) {
535562 return "" ;
536563 }
@@ -630,6 +657,7 @@ public String readLine(Charset charset) {
630657 * @return Decoded long value
631658 */
632659 public long readUtf8EncodedLong () {
660+ maybeAssertAtLeastBytesLeftForLegacyMethod (1 );
633661 int length = 0 ;
634662 long value = data [position ];
635663 // find the high most 0 bit
@@ -647,6 +675,7 @@ public long readUtf8EncodedLong() {
647675 if (length == 0 ) {
648676 throw new NumberFormatException ("Invalid UTF-8 sequence first byte: " + value );
649677 }
678+ maybeAssertAtLeastBytesLeftForLegacyMethod (length );
650679 for (int i = 1 ; i < length ; i ++) {
651680 int x = data [position + i ];
652681 if ((x & 0xC0 ) != 0x80 ) { // if the high most 0 bit not 7th
@@ -724,6 +753,23 @@ public Charset readUtfCharsetFromBom() {
724753 return null ;
725754 }
726755
756+ /**
757+ * Sets whether all read/peek methods should enforce that {@link #getPosition()} never exceeds
758+ * {@link #limit()}.
759+ *
760+ * <p>Setting this to {@code true} in tests can help catch cases of accidentally reading beyond
761+ * {@link #limit()} but still within the bounds of the underlying {@link #getData()}.
762+ *
763+ * <p>Some (newer) methods will always enforce the invariant, even when this is set to {@code
764+ * false}.
765+ *
766+ * <p>Defaults to false (this may change in a later release).
767+ */
768+ @ VisibleForTesting
769+ public static void setShouldEnforceLimitOnLegacyMethods (boolean enforceLimit ) {
770+ ParsableByteArray .shouldEnforceLimitOnLegacyMethods .set (enforceLimit );
771+ }
772+
727773 /**
728774 * Returns the index of the next occurrence of '\n' or '\r', or {@link #limit} if none is found.
729775 */
@@ -898,6 +944,22 @@ && isUtf8ContinuationByte(data[position + 3])) {
898944 }
899945 }
900946
947+ /**
948+ * Enforces that {@link #bytesLeft()} is at least {@code bytesNeeded} if {@link
949+ * #shouldEnforceLimitOnLegacyMethods} is set to {@code true}.
950+ *
951+ * <p>This should only be called from methods that previously didn't enforce the limit. All new
952+ * methods added to this class should unconditionally enforce the limit.
953+ */
954+ private void maybeAssertAtLeastBytesLeftForLegacyMethod (int bytesNeeded ) {
955+ if (shouldEnforceLimitOnLegacyMethods .get ()) {
956+ if (bytesLeft () < bytesNeeded ) {
957+ throw new IndexOutOfBoundsException (
958+ "bytesNeeded= " + bytesNeeded + ", bytesLeft=" + bytesLeft ());
959+ }
960+ }
961+ }
962+
901963 private static boolean isUtf8ContinuationByte (byte b ) {
902964 return (b & 0xC0 ) == 0x80 ;
903965 }
0 commit comments