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.cbor;
018
019import static org.apache.juneau.commons.utils.IoUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021import static org.apache.juneau.marshall.cbor.DataType.*;
022
023import java.io.*;
024import java.math.*;
025
026import org.apache.juneau.marshall.parser.*;
027
028/**
029 * Specialized input stream for parsing CBOR streams (RFC 8949).
030 *
031 * <p>
032 * Reads CBOR-encoded binary data, extracting major types and additional information from
033 * initial bytes and decoding values according to RFC 8949.
034 *
035 * <h5 class='section'>Notes:</h5><ul>
036 *    <li class='note'>This class is not intended for external use.
037 *    <li class='note'>Indefinite-length encoding (RFC 8949 §3.2) is supported: arrays/maps decode via the
038 *       BREAK-aware container loop, and chunked text/byte strings (heads <c>0x7F</c>/<c>0x5F</c>) are
039 *       reassembled from their definite-length pieces.
040 * </ul>
041 *
042 * <h5 class='section'>See Also:</h5><ul>
043 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Cbor">CBOR</a>
044 *    <li class='link'><a class="doclink" href="https://www.rfc-editor.org/rfc/rfc8949.html">RFC 8949</a>
045 * </ul>
046 */
047public class CborInputStream extends ParserInputStream {
048
049   /** Default cap (16 MiB) for wire-declared lengths, mirroring {@code BsonInputStream}. */
050   static final int DEFAULT_MAX_LENGTH = 16 * 1024 * 1024;
051
052   // checkLength's "what" label for byte/text-string length checks (definite-length and reassembled indefinite-length).
053   private static final String WHAT_STRING = "string";
054
055   private long length;
056   private int lastInitialByte;
057   private DataType lastDataType;
058   // Single-byte pushback for indefinite-length container loops that need to peek for BREAK
059   // without consuming the next data item.  -1 means no pushback present.
060   private int pushedBackByte = -1;
061   // True when the last-read data item carried additional-info 31 (indefinite-length array/map/string).
062   // Tracked separately from length so a definite value whose argument happens to be -1 (an 8-byte
063   // 0xFFFFFFFFFFFFFFFF) is NOT mistaken for an indefinite item.
064   private boolean indefinite;
065
066   /**
067    * Constructor.
068    *
069    * @param pipe The parser input.
070    * @throws IOException Thrown by underlying stream.
071    */
072   protected CborInputStream(ParserPipe pipe) throws IOException {
073      super(pipe);
074   }
075
076   @Override /* InputStream */
077   public int read() throws IOException {
078      if (pushedBackByte != -1) {
079         int b = pushedBackByte;
080         pushedBackByte = -1;
081         return b;
082      }
083      return super.read();
084   }
085
086   /**
087    * Peeks one byte and returns whether it is the CBOR BREAK marker (0xFF).  Non-BREAK bytes
088    * are pushed back so the next {@link #read()} sees them.
089    *
090    * <p>
091    * Used by definite/indefinite-length container loops to detect the end of an indefinite
092    * container without consuming the next data item.
093    *
094    * @return <jk>true</jk> if the next byte is BREAK; <jk>false</jk> otherwise (or at EOF).
095    * @throws IOException If the underlying stream fails.
096    */
097   boolean peekBreak() throws IOException {
098      int b = read();
099      if (b == -1)
100         return false;
101      if (b == 0xFF) {
102         lastDataType = BREAK;
103         length = 0;
104         return true;
105      }
106      pushedBackByte = b;
107      return false;
108   }
109
110   /**
111    * Reads the initial byte and returns it.
112    *
113    * @return The initial byte (0-255), or -1 on EOF.
114    * @throws IOException If read fails.
115    */
116   int readInitialByte() throws IOException {
117      int b = read();
118      if (b == -1)
119         throw ioex("Unexpected end of CBOR input");
120      return b & 0xFF;
121   }
122
123   /**
124    * Extracts the major type from an initial byte (high 3 bits).
125    *
126    * @param initialByte The initial byte.
127    * @return Major type 0-7.
128    */
129   static int getMajorType(int initialByte) {
130      return (initialByte >> 5) & 0x07;
131   }
132
133   /**
134    * Extracts the additional information from an initial byte (low 5 bits).
135    *
136    * @param initialByte The initial byte.
137    * @return Additional info 0-31.
138    */
139   static int getAdditionalInfo(int initialByte) {
140      return initialByte & 0x1F;
141   }
142
143   /**
144    * Reads the argument based on additional info.
145    * For 0-23, returns additionalInfo; for 24-27, reads 1/2/4/8 bytes big-endian.
146    *
147    * @param additionalInfo The additional info from the initial byte.
148    * @return The argument value.
149    * @throws IOException If read fails.
150    */
151   long readArgument(int additionalInfo) throws IOException {
152      if (additionalInfo <= 23)
153         return additionalInfo;
154      if (additionalInfo == 24)
155         return readUInt1();
156      if (additionalInfo == 25)
157         return readUInt2();
158      if (additionalInfo == 26)
159         return readUInt4();
160      if (additionalInfo == 27)
161         return readUInt8();
162      if (additionalInfo == 31)
163         throw ioex("Indefinite-length CBOR encoding not supported");
164      throw ioex("Reserved additional info value: %s", additionalInfo);
165   }
166
167   /**
168    * Reads 1 unsigned byte.
169    */
170   @SuppressWarnings({
171      "java:S4144" // Identical to readInitialByte by design; each method has distinct semantic purpose
172   })
173   int readUInt1() throws IOException {
174      int b = read();
175      if (b == -1)
176         throw ioex("Unexpected end of CBOR input");
177      return b & 0xFF;
178   }
179
180   /**
181    * Reads 2 bytes big-endian as unsigned integer.
182    */
183   int readUInt2() throws IOException {
184      return (readUInt1() << 8) | readUInt1();
185   }
186
187   /**
188    * Reads 4 bytes big-endian as unsigned long.
189    */
190   long readUInt4() throws IOException {
191      long l = readUInt1();
192      l = (l << 8) | readUInt1();
193      l = (l << 8) | readUInt1();
194      l = (l << 8) | readUInt1();
195      return l;
196   }
197
198   /**
199    * Reads 8 bytes big-endian as unsigned long.
200    */
201   long readUInt8() throws IOException {
202      long l = readUInt1();
203      l = (l << 8) | readUInt1();
204      l = (l << 8) | readUInt1();
205      l = (l << 8) | readUInt1();
206      l = (l << 8) | readUInt1();
207      l = (l << 8) | readUInt1();
208      l = (l << 8) | readUInt1();
209      l = (l << 8) | readUInt1();
210      return l;
211   }
212
213   /**
214    * Reads the next CBOR data item's type and length/argument, returning the DataType.
215    * The argument is stored and can be retrieved via {@link #readLength()}.
216    *
217    * @return The DataType of the next item.
218    * @throws IOException If read fails.
219    */
220   @SuppressWarnings({
221      "java:S3776" // Cognitive complexity acceptable for CBOR data type reading
222   })
223   DataType readDataType() throws IOException {
224      int ib = readInitialByte();
225      lastInitialByte = ib;
226      int majorType = getMajorType(ib);
227      int additionalInfo = getAdditionalInfo(ib);
228
229      DataType dt;
230      switch (majorType) {
231         case 0 -> dt = UINT;
232         case 1 -> dt = NINT;
233         case 2 -> dt = BINARY;
234         case 3 -> dt = STRING;
235         case 4 -> dt = ARRAY;
236         case 5 -> dt = MAP;
237         case 6 -> dt = TAG;
238         case 7 -> {
239            switch (additionalInfo) {
240               case 20 -> dt = BOOLEAN;
241               case 21 -> dt = BOOLEAN;
242               case 22 -> dt = NULL;
243               case 23 -> dt = UNDEFINED;
244               case 25, 26, 27 -> dt = FLOAT;
245               case 31 -> dt = BREAK;
246               default -> dt = SIMPLE;
247            }
248         }
249         default -> throw ioex("Invalid major type: %s", majorType);
250      }
251
252      lastDataType = dt;
253      indefinite = false;
254      if (dt == FLOAT) {
255         if (additionalInfo == 25)
256            length = 2;
257         else if (additionalInfo == 26)
258            length = 4;
259         else
260            length = 8;
261      } else if ((dt == ARRAY || dt == MAP || dt == STRING || dt == BINARY) && additionalInfo == 31) {
262         // Indefinite-length container or string (RFC 8949 §3.2).  Flagged via 'indefinite' rather than
263         // overloading length so callers distinguish it from a definite item whose 8-byte argument is
264         // 0xFFFFFFFFFFFFFFFF.  Containers are driven by the BREAK-aware loop (shouldContinueContainer /
265         // peekBreak); indefinite text/byte strings are reassembled from their chunks in readBinary.
266         indefinite = true;
267         length = 0;
268      } else if (dt != BOOLEAN && dt != NULL && dt != UNDEFINED && dt != BREAK) {
269         // NOTE: element-COUNT caps for arrays/maps are intentionally NOT applied here.  This method is
270         // shared by the O(1)-memory streaming cursor (CborTokenReader), which must accept large
271         // definite-length containers.  The count cap is enforced only on the databind path
272         // (CborParserSession), where elements actually accumulate into a collection.  Byte/text-string
273         // LENGTH caps remain active on every path via readBinary/readIndefiniteString.
274         length = readArgument(additionalInfo);
275      } else
276         length = dt == BOOLEAN ? -1 : 0;
277
278      return dt;
279   }
280
281   /**
282    * Returns whether the last-read data item used indefinite-length encoding (additional-info 31).
283    *
284    * @return <jk>true</jk> if the last {@link #readDataType()} returned an indefinite-length array, map, or string.
285    */
286   boolean isIndefinite() {
287      return indefinite;
288   }
289
290   /**
291    * Returns the length/argument for the last read data type.
292    * For strings/binary: byte count. For arrays: element count. For maps: pair count. For TAG: tag number.
293    */
294   long readLength() {
295      return length;
296   }
297
298   /**
299    * Reads a boolean (must have been identified as BOOLEAN by readDataType).
300    */
301   boolean readBoolean() {
302      return lastInitialByte == 0xF5;
303   }
304
305   /**
306    * Reads an unsigned integer value (UINT) as long.
307    * The value was already consumed by readDataType/readArgument.
308    */
309   long readUnsignedLong() {
310      return length;
311   }
312
313   /**
314    * Reads a signed integer. For UINT, returns the value. For NINT, returns -1 - value.
315    */
316   long readSignedLong() {
317      if (lastDataType == UINT)
318         return readUnsignedLong();
319      if (lastDataType == NINT)
320         return -1 - readUnsignedLong();
321      throw isex("Expected integer type, got %s", lastDataType);
322   }
323
324   /**
325    * Reads the last integer (UINT/NINT) as a {@link BigInteger}, preserving the full unsigned
326    * 64-bit magnitude.
327    *
328    * <p>
329    * Used when the target Java type is {@link BigInteger} so that values beyond the signed
330    * {@code long} range (CBOR major type 0 values {@code >= 2^63}, and major type 1 values
331    * {@code < -2^63}) widen losslessly instead of surfacing as a sign-wrapped {@code long}.
332    *
333    * @return The integer value as a {@link BigInteger}.
334    */
335   BigInteger readBigInteger() {
336      var magnitude = new BigInteger(Long.toUnsignedString(length));
337      if (lastDataType == UINT)
338         return magnitude;
339      if (lastDataType == NINT)
340         return BigInteger.valueOf(-1).subtract(magnitude);
341      throw isex("Expected integer type, got %s", lastDataType);
342   }
343
344   /**
345    * Reads an int (for UINT/NINT when value fits in int).
346    */
347   int readInt() {
348      return (int)readSignedLong();
349   }
350
351   /**
352    * Reads a long (for UINT/NINT).
353    */
354   long readLong() {
355      return readSignedLong();
356   }
357
358   /**
359    * Reads a float. Handles half (0xF9), single (0xFA), and double (0xFB) precision.
360    */
361   float readFloat() throws IOException {
362      if (lastDataType == FLOAT) {
363         if (lastInitialByte == 0xF9) {
364            int half = (readUInt1() << 8) | readUInt1();
365            return halfFloatToFloat(half);
366         }
367         if (lastInitialByte == 0xFA)
368            return Float.intBitsToFloat((int)readUInt4());
369         if (lastInitialByte == 0xFB)
370            return (float)Double.longBitsToDouble(readUInt8());
371      }
372      throw isex("Expected float type, got %s", lastDataType);
373   }
374
375   /**
376    * Reads a double. Handles half, single, and double precision.
377    */
378   double readDouble() throws IOException {
379      if (lastDataType == FLOAT) {
380         if (lastInitialByte == 0xF9)
381            return halfFloatToFloat((readUInt1() << 8) | readUInt1());
382         if (lastInitialByte == 0xFA)
383            return Float.intBitsToFloat((int)readUInt4());
384         if (lastInitialByte == 0xFB)
385            return Double.longBitsToDouble(readUInt8());
386      }
387      throw isex("Expected float type, got %s", lastDataType);
388   }
389
390   private static float halfFloatToFloat(int half) {
391      int exp = (half >> 10) & 0x1F;
392      int mant = half & 0x3FF;
393      int sign = (half & 0x8000) << 16;
394      if (exp == 0) {
395         // Zero (mant == 0) or subnormal (mant != 0).  A half subnormal is mant * 2^-24; the
396         // previous (mant << 13) form reinterpreted the bits as a float32 subnormal (mant * 2^-149),
397         // which is effectively zero.  Compute mant * 2^-24 directly (exact for mant <= 1023) and
398         // re-apply the sign bit.
399         var v = mant / 16777216f;  // 16777216 == 2^24
400         return Float.intBitsToFloat(Float.floatToRawIntBits(v) | sign);
401      }
402      if (exp == 31)
403         return mant == 0 ? Float.intBitsToFloat(sign | 0x7F800000)
404            : Float.NaN;
405      return Float.intBitsToFloat(sign | ((exp + 112) << 23) | (mant << 13));
406   }
407
408   /**
409    * Reads a UTF-8 text string.
410    */
411   String readString() throws IOException {
412      byte[] b = readBinary();
413      return new String(b, UTF8);
414   }
415
416   /**
417    * Reads a byte string (or text string's raw bytes).
418    *
419    * <p>
420    * Handles both definite-length strings and indefinite-length strings (RFC 8949 §3.2.3): when the
421    * head carried additional-info 31 ({@link #isIndefinite()}) the chunked definite-length pieces are
422    * reassembled until the BREAK marker.  A definite-length string whose declared length is out of
423    * bounds (negative, e.g. an 8-byte 0xFFFFFFFFFFFFFFFF argument, or beyond the configured maximum)
424    * is rejected here via {@link #checkLength(long, String)}.
425    */
426   byte[] readBinary() throws IOException {
427      if (indefinite)
428         return readIndefiniteString();
429      var b = new byte[checkLength(length, WHAT_STRING)];
430      var bytesRead = read(b);
431      if (bytesRead != b.length)
432         throw ioex("Expected to read %s bytes but only read %s", b.length, bytesRead);
433      return b;
434   }
435
436   /**
437    * Reassembles an indefinite-length text/byte string (RFC 8949 §3.2.3) from its definite-length
438    * chunks, terminated by the BREAK marker (0xFF).
439    *
440    * <p>
441    * Per §3.2.3 every chunk must be a definite-length string of the same major type as the
442    * enclosing indefinite string, and nested indefinite chunks are not permitted.  Malformed input
443    * surfaces as a clean {@link IOException} rather than a {@link NegativeArraySizeException}.
444    */
445   private byte[] readIndefiniteString() throws IOException {
446      var expectedMajorType = getMajorType(lastInitialByte);
447      var baos = new ByteArrayOutputStream();
448      var total = 0L;
449      while (true) {
450         int ib = read();
451         if (ib == -1)
452            throw ioex("Unexpected end of CBOR input in indefinite-length string");
453         ib &= 0xFF;
454         if (ib == 0xFF)  // BREAK
455            break;
456         var chunkMajorType = getMajorType(ib);
457         var chunkInfo = getAdditionalInfo(ib);
458         if (chunkMajorType != expectedMajorType)
459            throw ioex("Invalid chunk major type %s in indefinite-length string (expected %s)", chunkMajorType, expectedMajorType);
460         if (chunkInfo == 31)
461            throw ioex("Nested indefinite-length string chunk not allowed");
462         var chunkLen = checkLength(readArgument(chunkInfo), WHAT_STRING);
463         // Bound the reassembled aggregate, not just each chunk, so the returned byte[] cannot exceed
464         // the configured maximum regardless of how the payload is split across chunks.
465         total += chunkLen;
466         checkLength(total, WHAT_STRING);
467         var chunk = new byte[chunkLen];
468         var bytesRead = read(chunk);
469         if (bytesRead != chunk.length)
470            throw ioex("Expected to read %s bytes but only read %s", chunk.length, bytesRead);
471         baos.write(chunk, 0, chunk.length);
472      }
473      return baos.toByteArray();
474   }
475}