001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.marshall.bson; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.io.*; 022import java.math.*; 023import java.nio.charset.*; 024import java.util.*; 025 026import org.apache.juneau.marshall.parser.*; 027 028/** 029 * Specialized input stream for parsing BSON (Binary JSON) streams. 030 * 031 * <p> 032 * BSON uses little-endian byte order for all multi-byte values. 033 * 034 * <h5 class='section'>Notes:</h5><ul> 035 * <li class='note'> 036 * This class is not intended for external use. 037 * </ul> 038 * 039 * <h5 class='section'>See Also:</h5><ul> 040 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Bson">BSON</a> 041 * </ul> 042 */ 043public class BsonInputStream extends ParserInputStream { 044 045 private static final Charset UTF8 = StandardCharsets.UTF_8; 046 private static final String UNEXPECTED_END_OF_BSON_STREAM = "Unexpected end of BSON stream"; 047 048 /** Default cap (16 MiB, the conventional BSON document-size limit) for wire-declared lengths. */ 049 static final int DEFAULT_MAX_LENGTH = 16 * 1024 * 1024; 050 051 private int pushback = -1; 052 053 // Stack of remaining-byte counts, one per currently-open document/array (innermost on top). Every 054 // physical byte pulled off the wire decrements *all* active levels, since bytes consumed by a nested 055 // document/array also count against its enclosing document's declared length. This is what stops a 056 // forged-short document length plus an unterminated cstring from growing past the declared bound. 057 private final Deque<int[]> bounds = new ArrayDeque<>(); 058 059 /** 060 * Constructor. 061 * 062 * @param pipe The parser input. 063 * @throws IOException Thrown by underlying stream. 064 */ 065 protected BsonInputStream(ParserPipe pipe) throws IOException { 066 super(pipe); 067 } 068 069 @Override 070 public int read() throws IOException { 071 if (pushback >= 0) { 072 var b = pushback; 073 pushback = -1; 074 return b; 075 } 076 checkBoundAvailable(); 077 var b = super.read(); 078 if (b >= 0) 079 decrementBounds(); 080 return b; 081 } 082 083 /** 084 * Pushes back one byte for the next read. 085 */ 086 private void pushBack(int b) { 087 pushback = b & 0xFF; 088 } 089 090 /** 091 * Verifies every currently-open document/array bound still has at least one byte of budget left, 092 * before a further physical byte is pulled off the wire. 093 */ 094 private void checkBoundAvailable() throws IOException { 095 for (var bound : bounds) 096 if (bound[0] <= 0) 097 throw ioex("BSON document declared length exceeded"); 098 } 099 100 /** 101 * Charges one byte against every currently-open document/array bound. 102 */ 103 private void decrementBounds() { 104 for (var bound : bounds) 105 bound[0]--; 106 } 107 108 /** 109 * Opens a length-bounded scope for a document/array body being entered, so cstrings and nested values 110 * cannot be read past its declared length regardless of what the enclosing bound(s) still allow. 111 * 112 * @param remaining The number of bytes remaining in the body (declared size minus the 4-byte length 113 * header already consumed). 114 */ 115 private void pushDocumentBound(int remaining) { 116 bounds.push(new int[]{remaining}); 117 } 118 119 /** 120 * Closes the innermost document/array bound after its terminator has been consumed. 121 * 122 * @throws IOException If the declared length was not fully consumed (unconsumed trailing bytes). 123 */ 124 private void popDocumentBound() throws IOException { 125 if (bounds.isEmpty()) 126 return; 127 var remaining = bounds.pop()[0]; 128 if (remaining != 0) 129 throw ioex("BSON document declared length not fully consumed (%s byte(s) remaining)", remaining); 130 } 131 132 /** 133 * Reads a 4-byte little-endian integer. 134 * 135 * @return The 32-bit value. 136 * @throws IOException If the stream ends prematurely. 137 */ 138 public int readLE4() throws IOException { 139 var b1 = read(); 140 var b2 = read(); 141 var b3 = read(); 142 var b4 = read(); 143 if (b4 < 0) 144 throw ioex(UNEXPECTED_END_OF_BSON_STREAM); 145 return (b1 & 0xFF) | ((b2 & 0xFF) << 8) | ((b3 & 0xFF) << 16) | ((b4 & 0xFF) << 24); 146 } 147 148 /** 149 * Reads an 8-byte little-endian long. 150 * 151 * @return The 64-bit value. 152 * @throws IOException If the stream ends prematurely. 153 */ 154 public long readLE8() throws IOException { 155 var lo = readLE4() & 0xFFFFFFFFL; 156 var hi = readLE4() & 0xFFFFFFFFL; 157 return lo | (hi << 32); 158 } 159 160 /** 161 * Reads the document size (int32) from the stream and opens a length-bounded scope for the document 162 * body that follows, so a forged-short declared length cannot be bypassed by traversal that otherwise 163 * relies solely on finding a {@code 0x00} terminator. 164 * 165 * @return The document size in bytes. 166 * @throws IOException If the stream ends prematurely. 167 */ 168 public int readDocumentSize() throws IOException { 169 var size = checkLength(readLE4(), "document"); 170 pushDocumentBound(size - 4); 171 return size; 172 } 173 174 /** 175 * Reads the 1-byte element type. 176 * 177 * @return The BSON type byte (0x01-0x13, 0x7F, 0xFF). 178 * @throws IOException If the stream ends prematurely. 179 */ 180 public int readElementType() throws IOException { 181 var b = read(); 182 if (b < 0) 183 throw ioex(UNEXPECTED_END_OF_BSON_STREAM); 184 return b & 0xFF; 185 } 186 187 /** 188 * Reads a cstring (null-terminated UTF-8). 189 * 190 * @return The element name. 191 * @throws IOException If the stream ends prematurely. 192 */ 193 public String readElementName() throws IOException { 194 return readCString(); 195 } 196 197 /** 198 * Reads a cstring (UTF-8 bytes until 0x00). 199 * 200 * <p> 201 * Growth is capped by whichever is tighter of the innermost open document/array bound (enforced by 202 * {@link #read()}) or the configured {@link #setMaxLength(int) maxLength}, so an unterminated cstring 203 * cannot grow the backing buffer without limit. 204 * 205 * @return The string value. 206 * @throws IOException If the stream ends prematurely or the cstring exceeds the configured maximum length. 207 */ 208 public String readCString() throws IOException { 209 var baos = new ByteArrayOutputStream(); 210 int b; 211 while ((b = read()) >= 0 && b != 0) { 212 checkLength(baos.size() + 1L, "cstring"); 213 baos.write(b); 214 } 215 if (b < 0) 216 throw ioex(UNEXPECTED_END_OF_BSON_STREAM); 217 return new String(baos.toByteArray(), UTF8); 218 } 219 220 /** 221 * Returns whether the next byte is the document terminator (0x00). 222 * 223 * @return <jk>true</jk> if next byte is 0x00, <jk>false</jk> otherwise. 224 * @throws IOException If the stream ends prematurely. 225 */ 226 public boolean isDocumentEnd() throws IOException { 227 var b = read(); 228 if (b >= 0) 229 pushBack(b); 230 return b == 0x00; 231 } 232 233 /** 234 * Consumes the document terminator byte (0x00) and closes the length-bounded scope opened by the 235 * matching {@link #readDocumentSize()}, rejecting trailing bytes left unconsumed within the declared 236 * length. 237 * 238 * @throws IOException If the stream ends prematurely, the byte is not 0x00, or the declared document 239 * length was not fully consumed. 240 */ 241 public void readDocumentTerminator() throws IOException { 242 var b = read(); 243 if (b != 0x00) 244 throw ioex("Expected document terminator, got %s", b); 245 popDocumentBound(); 246 } 247 248 /** 249 * Reads an IEEE 754 double (8 bytes, little-endian). 250 * 251 * @return The double value. 252 * @throws IOException If the stream ends prematurely. 253 */ 254 public double readDouble() throws IOException { 255 return Double.longBitsToDouble(readLE8()); 256 } 257 258 /** 259 * Reads a 32-bit integer (4 bytes, little-endian). 260 * 261 * @return The integer value. 262 * @throws IOException If the stream ends prematurely. 263 */ 264 public int readInt32() throws IOException { 265 return readLE4(); 266 } 267 268 /** 269 * Reads a 64-bit integer (8 bytes, little-endian). 270 * 271 * @return The long value. 272 * @throws IOException If the stream ends prematurely. 273 */ 274 public long readInt64() throws IOException { 275 return readLE8(); 276 } 277 278 /** 279 * Reads a boolean (1 byte: 0x00=false, 0x01=true). 280 * 281 * @return The boolean value. 282 * @throws IOException If the stream ends prematurely. 283 */ 284 public boolean readBoolean() throws IOException { 285 var b = read(); 286 if (b < 0) 287 throw ioex(UNEXPECTED_END_OF_BSON_STREAM); 288 return b != 0; 289 } 290 291 /** 292 * Reads a BSON string (int32 length + UTF-8 + 0x00). 293 * 294 * @return The string value. 295 * @throws IOException If the stream ends prematurely. 296 */ 297 public String readString() throws IOException { 298 var len = readLE4(); 299 if (len <= 0) 300 throw ioex("Invalid BSON string length: %s", len); 301 checkLength(len, "string"); 302 var bytes = new byte[len - 1]; // length includes null 303 for (var i = 0; i < bytes.length; i++) { 304 var b = read(); 305 if (b < 0) 306 throw ioex(UNEXPECTED_END_OF_BSON_STREAM); 307 bytes[i] = (byte)b; 308 } 309 var term = read(); 310 if (term != 0x00) 311 throw ioex("Expected string terminator, got %s", term); 312 return new String(bytes, UTF8); 313 } 314 315 /** 316 * Reads BSON binary (int32 length + subtype + bytes). 317 * 318 * @return The binary data. 319 * @throws IOException If the stream ends prematurely. 320 */ 321 public byte[] readBinary() throws IOException { 322 var len = checkLength(readLE4(), "binary"); 323 var subtype = read(); 324 if (subtype < 0) 325 throw ioex(UNEXPECTED_END_OF_BSON_STREAM); 326 var bytes = new byte[len]; 327 for (var i = 0; i < len; i++) { 328 var b = read(); 329 if (b < 0) 330 throw ioex(UNEXPECTED_END_OF_BSON_STREAM); 331 bytes[i] = (byte)b; 332 } 333 return bytes; 334 } 335 336 /** 337 * Reads BSON datetime (int64, UTC millis since epoch). 338 * 339 * @return The UTC millis since epoch. 340 * @throws IOException If the stream ends prematurely. 341 */ 342 public long readDateTime() throws IOException { 343 return readLE8(); 344 } 345 346 /** 347 * Reads ObjectId (12 bytes) as 24-character hex string. 348 * 349 * @return The ObjectId as hex string. 350 * @throws IOException If the stream ends prematurely. 351 */ 352 public String readObjectId() throws IOException { 353 var bytes = new byte[12]; 354 for (var i = 0; i < 12; i++) { 355 var b = read(); 356 if (b < 0) 357 throw ioex(UNEXPECTED_END_OF_BSON_STREAM); 358 bytes[i] = (byte)b; 359 } 360 var sb = new StringBuilder(24); 361 for (var b : bytes) 362 sb.append(f("%02x", b & 0xFF)); 363 return sb.toString(); 364 } 365 366 /** 367 * Reads BSON decimal128 (16 bytes, IEEE 754-2008) as BigDecimal. 368 * 369 * @return The decimal value, or <jk>null</jk> if the wire value is NaN or ±Infinity (which have no BigDecimal form). 370 * @throws IOException If the stream ends prematurely. 371 */ 372 public BigDecimal readDecimal128() throws IOException { 373 var bytes = new byte[16]; 374 for (var i = 0; i < 16; i++) { 375 var b = read(); 376 if (b < 0) 377 throw ioex(UNEXPECTED_END_OF_BSON_STREAM); 378 bytes[i] = (byte)b; 379 } 380 return decodeDecimal128(bytes); 381 } 382 383 /** 384 * Skips a value of the given BSON type. 385 * 386 * @param type The BSON type byte (0x01-0x13, 0x7F, 0xFF). 387 * @throws IOException If the stream ends prematurely. 388 */ 389 public void skipValue(int type) throws IOException { 390 switch (type) { 391 case 0x01 -> readLE8(); 392 case 0x02 -> { 393 var len = readLE4(); 394 skip(len); 395 } 396 case 0x03, 0x04 -> { 397 var size = readLE4(); 398 skip(size - 4); 399 } 400 case 0x05 -> { 401 var len = readLE4(); 402 read(); 403 skip(len); 404 } 405 case 0x06 -> { /* BSON Undefined (deprecated) - zero bytes, intentionally ignored */ } 406 case 0x07 -> skip(12); 407 case 0x08 -> read(); 408 case 0x09 -> readLE8(); 409 case 0x0A -> { /* BSON Null - zero bytes, no payload to consume */ } 410 case 0x0B -> { 411 readCString(); 412 readCString(); 413 } 414 case 0x0C -> { 415 readString(); 416 skip(12); 417 } 418 case 0x0D -> readString(); 419 case 0x0E -> readString(); 420 case 0x0F -> { 421 var len = readLE4(); 422 skip(len - 4); 423 } 424 case 0x10 -> readLE4(); 425 case 0x11 -> readLE8(); 426 case 0x12 -> readLE8(); 427 case 0x13 -> skip(16); 428 case 0x7F, 0xFF -> { /* BSON MinKey/MaxKey - zero bytes, marker types with no payload to consume */ } 429 default -> throw ioex("Unknown BSON type: %s", type); 430 } 431 } 432 433 private void skip(int n) throws IOException { 434 for (var i = 0; i < n; i++) 435 if (read() < 0) 436 throw ioex(UNEXPECTED_END_OF_BSON_STREAM); 437 } 438 439 /** 440 * Decodes IEEE 754-2008 decimal128 bytes to BigDecimal. 441 * 442 * <p> 443 * Bytes are little-endian: bytes 0-7 = low 64 bits, bytes 8-15 = high 64 bits. 444 * Matches {@link BsonOutputStream#writeDecimal128} output. 445 */ 446 private static BigDecimal decodeDecimal128(byte[] bytes) { 447 if (bytes.length != 16) 448 throw new IllegalArgumentException("Decimal128 must be 16 bytes"); 449 var low = readLE8From(bytes, 0); 450 var high = readLE8From(bytes, 8); 451 // NaN/Infinity are valid Decimal128 wire values (MongoDB emits them) but have no BigDecimal form; normalize 452 // them to null instead of throwing an unchecked ArithmeticException that would abort the whole parse. 453 return BsonDecimal128.fromIEEE754BIDEncoding(high, low).toBigDecimalOrNull(); 454 } 455 456 private static long readLE8From(byte[] bytes, int offset) { 457 return ((long)(bytes[offset + 7] & 0xFF) << 56) | ((long)(bytes[offset + 6] & 0xFF) << 48) 458 | ((long)(bytes[offset + 5] & 0xFF) << 40) | ((long)(bytes[offset + 4] & 0xFF) << 32) 459 | ((long)(bytes[offset + 3] & 0xFF) << 24) | ((long)(bytes[offset + 2] & 0xFF) << 16) 460 | ((long)(bytes[offset + 1] & 0xFF) << 8) | (bytes[offset] & 0xFF); 461 } 462}