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}