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.*; 020 021import java.io.*; 022import java.math.*; 023import java.util.concurrent.atomic.*; 024 025import org.apache.juneau.marshall.serializer.*; 026 027/** 028 * Specialized output stream for serializing CBOR streams (RFC 8949). 029 * 030 * <p> 031 * CBOR uses a self-describing binary format where each data item starts with an initial byte 032 * containing a 3-bit major type and 5-bit additional information. Integers use compact encoding; 033 * strings, arrays, and maps use definite-length encoding. 034 * 035 * <h5 class='section'>Notes:</h5><ul> 036 * <li class='note'>This class is not intended for external use. 037 * <li class='note'>This class is not thread-safe; each serialization session uses its own instance. 038 * </ul> 039 * 040 * <h5 class='section'>See Also:</h5><ul> 041 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Cbor">CBOR</a> 042 * <li class='link'><a class="doclink" href="https://www.rfc-editor.org/rfc/rfc8949.html">RFC 8949</a> 043 * </ul> 044 */ 045@SuppressWarnings({ 046 "java:S127", // getUtf8ByteLength and writeUtf8To advance i past the low surrogate of a surrogate pair inside the loop 047 "resource" // appendXxx(...) methods return this stream for chaining; Eclipse JDT @Owning warning is by design. 048}) 049public class CborOutputStream extends OutputStream { 050 051 private final OutputStream os; 052 053 /** 054 * Constructor. 055 * 056 * @param os The output stream being wrapped. 057 */ 058 protected CborOutputStream(OutputStream os) { 059 this.os = os; 060 } 061 062 @Override /* Overridden from OutputStream */ 063 public void write(int b) { 064 try { 065 os.write(b); 066 } catch (IOException e) { 067 throw new SerializeException(e); 068 } 069 } 070 071 @Override /* Overridden from OutputStream */ 072 public void write(byte[] b, int off, int len) { 073 try { 074 os.write(b, off, len); 075 } catch (IOException e) { 076 throw new SerializeException(e); 077 } 078 } 079 080 /** 081 * Writes a single byte to the stream. 082 * 083 * @param b The byte to write (lower 8 bits used). 084 */ 085 void write1(int b) { 086 try { 087 os.write(b & 0xFF); 088 } catch (IOException e) { 089 throw new SerializeException(e); 090 } 091 } 092 093 /** 094 * Writes a 2-byte big-endian unsigned integer. 095 * 096 * @param value The value to write. 097 */ 098 void write2(int value) { 099 write1(value >> 8); 100 write1(value); 101 } 102 103 /** 104 * Writes a 4-byte big-endian unsigned integer. 105 * 106 * @param value The value to write. 107 */ 108 void write4(int value) { 109 write1(value >> 24); 110 write1(value >> 16); 111 write1(value >> 8); 112 write1(value); 113 } 114 115 /** 116 * Writes an 8-byte big-endian unsigned integer. 117 * 118 * @param value The value to write. 119 */ 120 void write8(long value) { 121 write1((int)(value >> 56)); 122 write1((int)(value >> 48)); 123 write1((int)(value >> 40)); 124 write1((int)(value >> 32)); 125 write1((int)(value >> 24)); 126 write1((int)(value >> 16)); 127 write1((int)(value >> 8)); 128 write1((int)(value)); 129 } 130 131 /** 132 * Encodes and writes a CBOR initial byte with major type and argument. 133 * 134 * <p> 135 * Argument encoding per RFC 8949: 136 * <ul> 137 * <li>0-23: Single byte (majorType << 5 | argument) 138 * <li>24-255: 2 bytes (type | 24, 1-byte argument) 139 * <li>256-65535: 3 bytes (type | 25, 2-byte argument) 140 * <li>65536-2^32-1: 5 bytes (type | 26, 4-byte argument) 141 * <li>2^32 and above: 9 bytes (type | 27, 8-byte argument) 142 * </ul> 143 * 144 * @param majorType The CBOR major type (0-7). 145 * @param argument The additional information or length value. 146 */ 147 void writeHead(int majorType, long argument) { 148 int typeBits = (majorType & 0x07) << 5; 149 if (argument <= 23) 150 write1(typeBits | (int)argument); 151 else if (argument <= 0xFF) { 152 write1(typeBits | 24); 153 write1((int)argument); 154 } else if (argument <= 0xFFFF) { 155 write1(typeBits | 25); 156 write2((int)argument); 157 } else if (argument <= 0xFFFFFFFFL) { 158 write1(typeBits | 26); 159 write4((int)argument); 160 } else { 161 write1(typeBits | 27); 162 write8(argument); 163 } 164 } 165 166 /** 167 * Appends CBOR null (0xF6). 168 * 169 * @return This stream. 170 */ 171 CborOutputStream appendNull() { 172 write1(0xF6); 173 return this; 174 } 175 176 /** 177 * Appends a boolean value (0xF4 for false, 0xF5 for true). 178 * 179 * @param value The boolean value. 180 * @return This stream. 181 */ 182 CborOutputStream appendBoolean(boolean value) { 183 write1(value ? 0xF5 : 0xF4); 184 return this; 185 } 186 187 /** 188 * Appends an integer using CBOR compact encoding. 189 * Positive values use major type 0; negative values use major type 1 with argument -1-value. 190 * 191 * @param value The integer value. 192 * @return This stream. 193 */ 194 CborOutputStream appendInt(int value) { 195 if (value >= 0) 196 writeHead(0, value & 0xFFFFFFFFL); 197 else 198 writeHead(1, -1L - value); 199 return this; 200 } 201 202 /** 203 * Appends a long integer using CBOR compact encoding. 204 * 205 * @param value The long value. 206 * @return This stream. 207 */ 208 CborOutputStream appendLong(long value) { 209 if (value >= 0) 210 writeHead(0, value); 211 else 212 writeHead(1, -1L - value); 213 return this; 214 } 215 216 /** 217 * Appends a float as IEEE 754 single-precision (0xFA + 4 bytes). 218 * 219 * @param value The float value. 220 * @return This stream. 221 */ 222 CborOutputStream appendFloat(float value) { 223 write1(0xFA); 224 write4(Float.floatToRawIntBits(value)); 225 return this; 226 } 227 228 /** 229 * Appends a double as IEEE 754 double-precision (0xFB + 8 bytes). 230 * 231 * @param value The double value. 232 * @return This stream. 233 */ 234 CborOutputStream appendDouble(double value) { 235 write1(0xFB); 236 write8(Double.doubleToRawLongBits(value)); 237 return this; 238 } 239 240 /** 241 * Appends a generic Number, dispatching to the appropriate append method. 242 * 243 * @param n The number. 244 * @return This stream. 245 */ 246 CborOutputStream appendNumber(Number n) { 247 var c = n.getClass(); 248 if (c == Integer.class || c == Short.class || c == Byte.class || c == AtomicInteger.class) 249 return appendInt(n.intValue()); 250 if (c == Long.class || c == AtomicLong.class) 251 return appendLong(n.longValue()); 252 if (c == Float.class) 253 return appendFloat(n.floatValue()); 254 if (c == Double.class) 255 return appendDouble(n.doubleValue()); 256 if (c == BigInteger.class) 257 return appendBigInteger((BigInteger)n); 258 if (c == BigDecimal.class) 259 return appendString(n.toString()); 260 return appendInt(0); 261 } 262 263 /** 264 * Appends a {@link BigInteger} losslessly. 265 * 266 * <p> 267 * Values within the signed {@code long} range use the compact integer encoding. Values that 268 * still fit CBOR's native integer range (unsigned 64-bit for positives via major type 0, down to 269 * {@code -2^64} for negatives via major type 1) are emitted with the full 64-bit magnitude rather 270 * than truncating. Values beyond {@code ±2^64} (which have no native CBOR integer head) fall back 271 * to a lossless decimal string so no magnitude is lost. 272 * 273 * @param value The value. 274 * @return This stream. 275 */ 276 CborOutputStream appendBigInteger(BigInteger value) { 277 if (value.bitLength() <= 63) 278 return appendLong(value.longValueExact()); 279 if (value.signum() > 0) { 280 if (value.bitLength() <= 64) { 281 write1(0x1B); // major type 0 | additional info 27 (8-byte argument) 282 write8(value.longValue()); // low 64 bits == full unsigned magnitude here 283 return this; 284 } 285 } else { 286 var arg = value.negate().subtract(BigInteger.ONE); // CBOR major type 1 argument == -1 - value 287 if (arg.bitLength() <= 64) { 288 write1(0x3B); // major type 1 | additional info 27 (8-byte argument) 289 write8(arg.longValue()); 290 return this; 291 } 292 } 293 // Beyond ±2^64: no native CBOR integer head exists; emit a lossless decimal string. 294 return appendString(value.toString()); 295 } 296 297 private static int getUtf8ByteLength(CharSequence cs) { 298 var count = 0; 299 for (int i = 0, len = cs.length(); i < len; i++) { 300 var ch = cs.charAt(i); 301 if (ch <= 0x7F) 302 count++; 303 else if (ch <= 0x7FF) 304 count += 2; 305 else if (Character.isHighSurrogate(ch) && i + 1 < len && Character.isLowSurrogate(cs.charAt(i + 1))) { 306 count += 4; 307 ++i; 308 } else 309 // BMP char, or an unpaired surrogate which is emitted as the 3-byte U+FFFD replacement. 310 count += 3; 311 } 312 return count; 313 } 314 315 private int writeUtf8To(CharSequence in) { 316 var count = 0; 317 for (int i = 0, len = in.length(); i < len; i++) { 318 var c = (in.charAt(i) & 0xFFFF); 319 if (c <= 0x7F) { 320 write((byte)(c & 0xFF)); 321 count++; 322 } else if (c <= 0x7FF) { 323 write((byte)(0xC0 + ((c >> 6) & 0x1F))); 324 write((byte)(0x80 + (c & 0x3F))); 325 count += 2; 326 } else if (c >= 0xD800 && c <= 0xDBFF && i + 1 < len && Character.isLowSurrogate(in.charAt(i + 1))) { 327 int jchar2 = in.charAt(++i) & 0xFFFF; 328 int n = (c << 10) + jchar2 + 0xFCA02400; 329 write((byte)(0xF0 + ((n >> 18) & 0x07))); 330 write((byte)(0x80 + ((n >> 12) & 0x3F))); 331 write((byte)(0x80 + ((n >> 6) & 0x3F))); 332 write((byte)(0x80 + (n & 0x3F))); 333 count += 4; 334 } else { 335 // BMP char, or an unpaired surrogate. An unpaired surrogate is not a valid Unicode 336 // scalar value, so emit the U+FFFD replacement character rather than crashing or 337 // producing malformed UTF-8. 338 if (c >= 0xD800 && c <= 0xDFFF) 339 c = 0xFFFD; 340 write((byte)(0xE0 + ((c >> 12) & 0x0F))); 341 write((byte)(0x80 + ((c >> 6) & 0x3F))); 342 write((byte)(0x80 + (c & 0x3F))); 343 count += 3; 344 } 345 } 346 return count; 347 } 348 349 /** 350 * Appends a UTF-8 text string (major type 3). 351 * 352 * @param s The string. 353 * @return This stream. 354 */ 355 CborOutputStream appendString(CharSequence s) { 356 int length = getUtf8ByteLength(s); 357 writeHead(3, length); 358 int length2 = writeUtf8To(s); 359 if (length != length2) 360 throw new SerializeException("Unexpected length. Expected=%s, Actual=%s", length, length2); 361 return this; 362 } 363 364 /** 365 * Appends a byte string (major type 2). 366 * 367 * @param data The bytes. 368 * @return This stream. 369 */ 370 CborOutputStream appendBinary(byte[] data) { 371 writeHead(2, data.length); 372 try { 373 os.write(data); 374 } catch (IOException e) { 375 throw new SerializeException(e); 376 } 377 return this; 378 } 379 380 /** 381 * Appends a byte string from an input stream. 382 * 383 * @param data The input stream. 384 * @return This stream. 385 */ 386 CborOutputStream appendBinary(InputStream data) { 387 var baos = new ByteArrayOutputStream(); 388 pipe(data, baos, x -> { 389 throw new SerializeException(x); 390 }); 391 return appendBinary(baos.toByteArray()); 392 } 393 394 /** 395 * Starts a definite-length array (major type 4). 396 * 397 * @param size The number of elements. 398 * @return This stream. 399 */ 400 CborOutputStream startArray(int size) { 401 writeHead(4, size); 402 return this; 403 } 404 405 /** 406 * Starts a definite-length map (major type 5). 407 * 408 * @param size The number of key-value pairs. 409 * @return This stream. 410 */ 411 CborOutputStream startMap(int size) { 412 writeHead(5, size); 413 return this; 414 } 415 416 /** 417 * Writes a CBOR semantic tag (major type 6). 418 * 419 * @param tagNumber The tag number. 420 * @return This stream. 421 */ 422 CborOutputStream writeTag(long tagNumber) { 423 writeHead(6, tagNumber); 424 return this; 425 } 426 427 /** 428 * Writes a CBOR simple value (major type 7). 429 * 430 * <p> 431 * Caller responsibility: reserved encodings (20-23 = bool/null/undefined; 25-27 = 432 * float16/32/64; 31 = break) collide with native scalar/structural emits and must not be used 433 * here. 434 * 435 * @param value The simple value (range {@code 0..255}; reserved values noted above). 436 * @return This stream. 437 */ 438 CborOutputStream writeSimple(int value) { 439 writeHead(7, value); 440 return this; 441 } 442}