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}