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.msgpack; 018 019import static org.apache.juneau.commons.utils.IoUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021import static org.apache.juneau.marshall.msgpack.DataType.*; 022 023import java.io.*; 024import java.math.*; 025import java.util.concurrent.atomic.*; 026 027import org.apache.juneau.marshall.serializer.*; 028 029/** 030 * Specialized output stream for serializing MessagePack streams. 031 * 032 * <h5 class='section'>Notes:</h5><ul> 033 * <li class='note'> 034 * This class is not intended for external use. 035 * </ul> 036 * 037 * <h5 class='section'>See Also:</h5><ul> 038 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/MessagePackSupport">MessagePack</a> 039 040 * </ul> 041 */ 042@SuppressWarnings({ 043 "java:S127", // The UTF-8 length/write loops advance i again to consume the low surrogate of a surrogate pair. 044 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 045 "java:S4349", // write(byte[],int,int) is intentionally omitted; MsgPack writes single structured bytes via write(int); bulk array writes are not part of this protocol API 046 "resource" // OutputStream resource managed by calling code 047}) 048public class MsgPackOutputStream extends OutputStream { 049 050 private final OutputStream os; 051 052 private static final long L2X31 = ((long)(1 << 30)) * 2; 053 054 /** 055 * Constructor. 056 * 057 * @param os The output stream being wrapped. 058 */ 059 protected MsgPackOutputStream(OutputStream os) { 060 this.os = os; 061 } 062 063 @Override /* Overridden from OutputStream */ 064 public void write(int b) { 065 try { 066 os.write(b); 067 } catch (IOException e) { 068 throw new SerializeException(e); 069 } 070 } 071 072 private static int getUtf8ByteLength(CharSequence cs) { 073 var count = 0; 074 for (int i = 0, len = cs.length(); i < len; i++) { 075 var ch = cs.charAt(i); 076 if (ch <= 0x7F) { 077 count++; 078 } else if (ch <= 0x7FF) { 079 count += 2; 080 } else if (Character.isHighSurrogate(ch) && i + 1 < len && Character.isLowSurrogate(cs.charAt(i + 1))) { 081 count += 4; 082 ++i; 083 } else { 084 // BMP char, or an unpaired surrogate emitted as U+FFFD (3 bytes) to match String.getBytes(UTF_8). 085 count += 3; 086 } 087 } 088 return count; 089 } 090 091 092 private int writeUtf8To(CharSequence in) { 093 var count = 0; 094 for (int i = 0, len = in.length(); i < len; i++) { 095 var c = (in.charAt(i) & 0xFFFF); 096 if (c <= 0x7F) { 097 write((byte)(c & 0xFF)); 098 count++; 099 } else if (c <= 0x7FF) { 100 write((byte)(0xC0 + ((c >> 6) & 0x1F))); 101 write((byte)(0x80 + (c & 0x3F))); 102 count += 2; 103 } else if (c >= 0xD800 && c <= 0xDBFF && i + 1 < len && Character.isLowSurrogate(in.charAt(i + 1))) { 104 int jchar2 = in.charAt(++i) & 0xFFFF; 105 int n = (c << 10) + jchar2 + 0xFCA02400; 106 write((byte)(0xF0 + ((n >> 18) & 0x07))); 107 write((byte)(0x80 + ((n >> 12) & 0x3F))); 108 write((byte)(0x80 + ((n >> 6) & 0x3F))); 109 write((byte)(0x80 + (n & 0x3F))); 110 count += 4; 111 } else if (c >= 0xD800 && c <= 0xDFFF) { 112 // Unpaired surrogate -> U+FFFD replacement char (matches String.getBytes(UTF_8) and the 113 // length predicted by getUtf8ByteLength). Prevents reading past the end of the input (G9). 114 write((byte)0xEF); 115 write((byte)0xBF); 116 write((byte)0xBD); 117 count += 3; 118 } else { 119 write((byte)(0xE0 + ((c >> 12) & 0x0F))); 120 write((byte)(0x80 + ((c >> 6) & 0x3F))); 121 write((byte)(0x80 + (c & 0x3F))); 122 count += 3; 123 } 124 } 125 return count; 126 } 127 128 /** 129 * Same as {@link #write(int)}. 130 */ 131 MsgPackOutputStream append(byte b) { 132 try { 133 os.write(b); 134 } catch (IOException e) { 135 throw new SerializeException(e); 136 } 137 return this; 138 } 139 140 /** 141 * Same as {@link #write(byte[])}. 142 */ 143 MsgPackOutputStream append(byte[] b) { 144 try { 145 os.write(b); 146 } catch (IOException e) { 147 throw new SerializeException(e); 148 } 149 return this; 150 } 151 152 /** 153 * Appends one byte to the stream. 154 */ 155 MsgPackOutputStream append1(int i) { 156 try { 157 os.write(i); 158 } catch (IOException e) { 159 throw new SerializeException(e); 160 } 161 return this; 162 } 163 164 /** 165 * Appends two bytes to the stream. 166 */ 167 MsgPackOutputStream append2(int i) { 168 return append1(i >> 8).append1(i); 169 } 170 171 /** 172 * Appends four bytes to the stream. 173 */ 174 MsgPackOutputStream append4(int i) { 175 return append1(i >> 24).append1(i >> 16).append1(i >> 8).append1(i); 176 } 177 178 /** 179 * Appends eight bytes to the stream. 180 */ 181 MsgPackOutputStream append8(long l) { 182 return append1((int)(l >> 56)).append1((int)(l >> 48)).append1((int)(l >> 40)).append1((int)(l >> 32)).append1((int)(l >> 24)).append1((int)(l >> 16)).append1((int)(l >> 8)).append1((int)(l)); 183 } 184 185 /** 186 * Appends a MsgPack {@code ext} field (typed binary) to the stream. 187 * 188 * <p> 189 * Selects the smallest framing that fits the payload length: fixext1/2/4/8/16 for the five 190 * exact lengths, else ext8 / ext16 / ext32 with a length prefix; the type byte and payload 191 * are appended after the framing header. 192 * 193 * @param type The {@code ext} type byte (signed; range {@code -128..127}). 194 * @param payload The {@code ext} payload bytes. 195 * @return This stream. 196 */ 197 MsgPackOutputStream writeExt(int type, byte[] payload) { 198 var n = payload.length; 199 switch (n) { 200 case 1: return append1(FIXEXT1).append1(type).append(payload); 201 case 2: return append1(FIXEXT2).append1(type).append(payload); 202 case 4: return append1(FIXEXT4).append1(type).append(payload); 203 case 8: return append1(FIXEXT8).append1(type).append(payload); 204 case 16: return append1(FIXEXT16).append1(type).append(payload); 205 default: break; 206 } 207 if (n < (1 << 8)) 208 return append1(EXT8).append1(n).append1(type).append(payload); 209 if (n < (1 << 16)) 210 return append1(EXT16).append2(n).append1(type).append(payload); 211 return append1(EXT32).append4(n).append1(type).append(payload); 212 } 213 214 /** 215 * Appends a binary field to the stream. 216 */ 217 MsgPackOutputStream appendBinary(byte[] b) { 218 // bin 8 stores a byte array whose length is up to (2^8)-1 bytes: 219 // +--------+--------+========+ 220 // | 0xc4 |XXXXXXXX| data | 221 // +--------+--------+========+ 222 // 223 // bin 16 stores a byte array whose length is up to (2^16)-1 bytes: 224 // +--------+--------+--------+========+ 225 // | 0xc5 |YYYYYYYY|YYYYYYYY| data | 226 // +--------+--------+--------+========+ 227 // 228 // bin 32 stores a byte array whose length is up to (2^32)-1 bytes: 229 // +--------+--------+--------+--------+--------+========+ 230 // | 0xc6 |ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ| data | 231 // +--------+--------+--------+--------+--------+========+ 232 // 233 // where 234 // * XXXXXXXX is a 8-bit unsigned integer which represents N 235 // * YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer which represents N 236 // * ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer which represents N 237 // * N is the length of data 238 239 if (b.length < (1 << 8)) 240 return append1(BIN8).append1(b.length).append(b); 241 if (b.length < (1 << 16)) 242 return append1(BIN16).append2(b.length).append(b); 243 return append1(BIN32).append4(b.length).append(b); 244 } 245 246 /** 247 * Appends a binary field to the stream. 248 */ 249 MsgPackOutputStream appendBinary(InputStream is) { 250 251 var baos = new ByteArrayOutputStream(); 252 pipe(is, baos, x -> { 253 throw new SerializeException(x); 254 }); 255 256 var b = baos.toByteArray(); 257 258 // bin 8 stores a byte array whose length is up to (2^8)-1 bytes: 259 // +--------+--------+========+ 260 // | 0xc4 |XXXXXXXX| data | 261 // +--------+--------+========+ 262 // 263 // bin 16 stores a byte array whose length is up to (2^16)-1 bytes: 264 // +--------+--------+--------+========+ 265 // | 0xc5 |YYYYYYYY|YYYYYYYY| data | 266 // +--------+--------+--------+========+ 267 // 268 // bin 32 stores a byte array whose length is up to (2^32)-1 bytes: 269 // +--------+--------+--------+--------+--------+========+ 270 // | 0xc6 |ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ| data | 271 // +--------+--------+--------+--------+--------+========+ 272 // 273 // where 274 // * XXXXXXXX is a 8-bit unsigned integer which represents N 275 // * YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer which represents N 276 // * ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer which represents N 277 // * N is the length of data 278 279 if (b.length < (1 << 8)) 280 return append1(BIN8).append1(b.length).append(b); 281 if (b.length < (1 << 16)) 282 return append1(BIN16).append2(b.length).append(b); 283 return append1(BIN32).append4(b.length).append(b); 284 } 285 286 /** 287 * Appends a boolean to the stream. 288 */ 289 MsgPackOutputStream appendBoolean(boolean b) { 290 return append1(b ? TRUE : FALSE); 291 } 292 293 /** 294 * Appends a double to the stream. 295 */ 296 MsgPackOutputStream appendDouble(double d) { 297 // FLOAT64 = 0xCB, // float 64 11001011 0xcb 298 return append1(FLOAT64).append8(Double.doubleToLongBits(d)); 299 } 300 301 /** 302 * Appends a float to the stream. 303 */ 304 MsgPackOutputStream appendFloat(float f) { 305 // FLOAT32 = 0xCA, // float 32 11001010 0xca 306 return append1(FLOAT32).append4(Float.floatToIntBits(f)); 307 308 } 309 310 /** 311 * Appends an integer to the stream. 312 */ 313 @SuppressWarnings({ 314 "java:S125" // MsgPack format type byte reference - documentation, not commented-out code 315 }) 316 MsgPackOutputStream appendInt(int i) { 317 // POSFIXINT_L = 0x00, pos fixint 0xxxxxxx 0x00 - 0x7f 318 // POSFIXINT_U = 0x7F 319 // UINT8 = 0xCC uint 8 11001100 0xcc 320 // UINT16 = 0xCD uint 16 11001101 0xcd 321 // UINT32 = 0xCE uint 32 11001110 0xce 322 // UINT64 = 0xCF uint 64 11001111 0xcf 323 // INT8 = 0xD0 int 8 11010000 0xd0 324 // INT16 = 0xD1 int 16 11010001 0xd1 325 // INT32 = 0xD2 int 32 11010010 0xd2 326 // INT64 = 0xD3 int 64 11010011 0xd3 327 // NEGFIXINT_L = 0xE0 neg fixint 111xxxxx 0xe0 - 0xff 328 // NEGFIXINT_U = 0xFF 329 if (i >= 0) { 330 if (i < (1 << 7)) 331 return append1(i); 332 if (i < (1 << 15)) 333 return append1(INT16).append2(i); 334 return append1(INT32).append4(i); 335 } 336 if (i > -(1 << 6)) 337 return append((byte)(0xE0 | -i)); 338 if (i > -(1 << 7)) 339 return append1(INT8).append1(i); 340 if (i > -(1 << 15)) 341 return append1(INT16).append2(i); 342 return append1(INT32).append4(i); 343 } 344 345 /** 346 * Appends a long to the stream. 347 */ 348 MsgPackOutputStream appendLong(long l) { 349 if (l < L2X31 && l > -(L2X31)) 350 return appendInt((int)l); 351 return append1(INT64).append8(l); 352 } 353 354 /** 355 * Appends a NULL flag to the stream. 356 */ 357 MsgPackOutputStream appendNull() { 358 return append1(NIL); 359 } 360 361 /** 362 * Appends a generic Number to the stream. 363 */ 364 MsgPackOutputStream appendNumber(Number n) { 365 var c = n.getClass(); 366 if (c == Integer.class || c == Short.class || c == Byte.class || c == AtomicInteger.class) 367 return appendInt(n.intValue()); 368 if (c == Long.class || c == AtomicLong.class) 369 return appendLong(n.longValue()); 370 if (c == Float.class) 371 return appendFloat(n.floatValue()); 372 if (c == Double.class) 373 return appendDouble(n.doubleValue()); 374 if (c == BigInteger.class) 375 return appendBigInteger((BigInteger)n); 376 if (c == BigDecimal.class) 377 return appendDouble(n.doubleValue()); 378 return appendInt(0); 379 } 380 381 /** 382 * Appends a {@link BigInteger} to the stream without loss of magnitude. 383 * 384 * <p> 385 * Values within signed {@code long} range are emitted via {@link #appendLong(long)}; positive values in 386 * {@code [2^63, 2^64-1]} are emitted as an unsigned 64-bit integer (UINT64). Values outside the 64-bit 387 * range cannot be represented by any MessagePack integer type and throw a {@link SerializeException} 388 * rather than silently truncating (G4). 389 * 390 * @param value The value to append. Must not be <jk>null</jk>. 391 * @return This stream. 392 */ 393 MsgPackOutputStream appendBigInteger(BigInteger value) { 394 reqnn("value", value); 395 if (value.bitLength() < 64) 396 return appendLong(value.longValue()); 397 if (value.signum() > 0 && value.bitLength() == 64) 398 return append1(UINT64).append8(value.longValue()); 399 throw new SerializeException("BigInteger value '%s' is outside the range supported by MessagePack integer types (64-bit).", value); 400 } 401 402 /** 403 * Appends a string to the stream. 404 */ 405 MsgPackOutputStream appendString(CharSequence cs) { 406 407 // fixstr stores a byte array whose length is up to 31 bytes: 408 // +--------+========+ 409 // |101XXXXX| data | 410 // +--------+========+ 411 // 412 // str 8 stores a byte array whose length is up to (2^8)-1 bytes: 413 // +--------+--------+========+ 414 // | 0xd9 |YYYYYYYY| data | 415 // +--------+--------+========+ 416 // 417 // str 16 stores a byte array whose length is up to (2^16)-1 bytes: 418 // +--------+--------+--------+========+ 419 // | 0xda |ZZZZZZZZ|ZZZZZZZZ| data | 420 // +--------+--------+--------+========+ 421 // 422 // str 32 stores a byte array whose length is up to (2^32)-1 bytes: 423 // +--------+--------+--------+--------+--------+========+ 424 // | 0xdb |AAAAAAAA|AAAAAAAA|AAAAAAAA|AAAAAAAA| data | 425 // +--------+--------+--------+--------+--------+========+ 426 // where 427 // * XXXXX is a 5-bit unsigned integer which represents N 428 // * YYYYYYYY is a 8-bit unsigned integer which represents N 429 // * ZZZZZZZZ_ZZZZZZZZ is a 16-bit big-endian unsigned integer which represents N 430 // * AAAAAAAA_AAAAAAAA_AAAAAAAA_AAAAAAAA is a 32-bit big-endian unsigned integer which represents N 431 // * N is the length of data 432 433 int length = getUtf8ByteLength(cs); 434 if (length < 32) 435 append1(0xA0 + length); 436 else if (length < (1 << 8)) 437 append1(STR8).append1(length); 438 else if (length < (1 << 16)) 439 append1(STR16).append2(length); 440 else 441 append1(STR32).append4(length); 442 443 int length2 = writeUtf8To(cs); 444 445 if (length != length2) 446 throw new SerializeException("Unexpected length. Expected=%s, Actual=%s", length, length2); 447 448 return this; 449 } 450 451 /** 452 * Appends an array data type flag to the stream. 453 */ 454 MsgPackOutputStream startArray(int size) { 455 // fixarray stores an array whose length is up to 15 elements: 456 // +--------+~~~~~~~~~~~~~~~~~+ 457 // |1001XXXX| N objects | 458 // +--------+~~~~~~~~~~~~~~~~~+ 459 // 460 // array 16 stores an array whose length is up to (2^16)-1 elements: 461 // +--------+--------+--------+~~~~~~~~~~~~~~~~~+ 462 // | 0xdc |YYYYYYYY|YYYYYYYY| N objects | 463 // +--------+--------+--------+~~~~~~~~~~~~~~~~~+ 464 // 465 // array 32 stores an array whose length is up to (2^32)-1 elements: 466 // +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+ 467 // | 0xdd |ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ| N objects | 468 // +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+ 469 // 470 // where 471 // * XXXX is a 4-bit unsigned integer which represents N 472 // * YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer which represents N 473 // * ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer which represents N 474 // N is the size of a array 475 476 if (size < 16) 477 return append1(0x90 + size); 478 if (size < (1 << 16)) 479 return append1(ARRAY16).append2(size); 480 return append1(ARRAY32).append4(size); 481 } 482 483 /** 484 * Appends a map data type flag to the stream. 485 */ 486 MsgPackOutputStream startMap(int size) { 487 // fixmap stores a map whose length is up to 15 elements 488 // +--------+~~~~~~~~~~~~~~~~~+ 489 // |1000XXXX| N*2 objects | 490 // +--------+~~~~~~~~~~~~~~~~~+ 491 // 492 // map 16 stores a map whose length is up to (2^16)-1 elements 493 // +--------+--------+--------+~~~~~~~~~~~~~~~~~+ 494 // | 0xde |YYYYYYYY|YYYYYYYY| N*2 objects | 495 // +--------+--------+--------+~~~~~~~~~~~~~~~~~+ 496 // 497 // map 32 stores a map whose length is up to (2^32)-1 elements 498 // +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+ 499 // | 0xdf |ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ|ZZZZZZZZ| N*2 objects | 500 // +--------+--------+--------+--------+--------+~~~~~~~~~~~~~~~~~+ 501 // 502 // where 503 // * XXXX is a 4-bit unsigned integer which represents N 504 // * YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer which represents N 505 // * ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer which represents N 506 // * N is the size of a map 507 // * odd elements in objects are keys of a map 508 // * the next element of a key is its associated value 509 510 if (size < 16) 511 return append1(0x80 + size); 512 if (size < (1 << 16)) 513 return append1(MAP16).append2(size); 514 return append1(MAP32).append4(size); 515 } 516}