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}