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.Shorts.*;
020import static org.apache.juneau.commons.utils.SystemUtils.*;
021
022import java.io.*;
023import java.math.*;
024import java.util.*;
025import java.util.concurrent.*;
026
027import org.apache.juneau.commons.bean.*;
028import org.apache.juneau.commons.collections.*;
029import org.apache.juneau.marshall.*;
030import org.apache.juneau.marshall.parser.*;
031import org.apache.juneau.marshall.stream.*;
032
033/**
034 * Parses CBOR streams (RFC 8949) into POJO models.
035 *
036 * <h5 class='topic'>Media types</h5>
037 *
038 * Handles <c>Content-Type</c> types:  <bc>application/cbor</bc>
039 *
040 * <h5 class='topic'>Description</h5>
041 * <p>
042 * CBOR-to-Java type mapping:
043 * <ul class='spaced-list'>
044 *    <li>CBOR map (major type 5) → bean or {@link Map}
045 *    <li>CBOR array (major type 4) → {@link Collection} or Java array
046 *    <li>CBOR text string (major type 3) → {@link String}; coerced to types parseable from string
047 *    <li>CBOR unsigned/negative integer (major types 0/1) → {@link Integer} or {@link Long}
048 *    <li>CBOR float (major type 7, 0xFA/0xFB) → {@link Float} or {@link Double}
049 *    <li>CBOR half-precision float (major type 7, 0xF9) → {@link Float} (converted)
050 *    <li>CBOR boolean (major type 7, 0xF4/0xF5) → {@link Boolean}
051 *    <li>CBOR null (major type 7, 0xF6) → {@code null}
052 *    <li>CBOR byte string (major type 2) → {@code byte[]}
053 *    <li>CBOR semantic tag (major type 6) → tag ignored; tagged value parsed normally
054 * </ul>
055 *
056 * <h5 class='section'>Round-trip notes:</h5>
057 * <ul class='spaced-list'>
058 *    <li>Integers (major types 0/1) surface by target type: a {@link BigInteger} field carries the full
059 *       unsigned-64-bit magnitude; any other numeric type keeps the raw 64-bit bits as a {@code long}.
060 *    <li>{@link BigInteger} / {@link BigDecimal} round-trip losslessly (big integers decode from native
061 *       CBOR integers or, beyond {@code ±2^64}, from their decimal string; {@link BigDecimal} decodes
062 *       from its decimal string).
063 *    <li>Indefinite-length CBOR (RFC 8949 §3.2) is supported: arrays, maps, and chunked text/byte
064 *       strings all decode.
065 *    <li>Semantic tags are read and discarded on the databind path; tag number does not affect parsed
066 *       type.  Enable {@link Builder#nativeMode() nativeMode} to surface tags/simple values on the
067 *       {@link TokenReader} cursor.
068 * </ul>
069 *
070 * <h5 class='section'>Notes:</h5><ul>
071 *    <li class='note'>This class is thread safe and reusable.
072 * </ul>
073 *
074 * <h5 class='section'>See Also:</h5><ul>
075 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Cbor">CBOR</a>
076 *    <li class='link'><a class="doclink" href="https://www.rfc-editor.org/rfc/rfc8949.html">RFC 8949</a>
077 * </ul>
078 */
079@SuppressWarnings({
080   "java:S110", // Inheritance depth acceptable for this class hierarchy
081   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
082   "java:S9149", // Per-format static factories intentionally shadow the parent's.
083   "resource" // Closeable resources are owned by the caller's parser session; Eclipse JDT @Owning warning is by design.
084})
085public class CborParser extends InputStreamParser implements CborMetaProvider, TokenReadable, ArrayRecordReadable {
086
087   private static final int DEFAULT_MAX_LENGTH = CborInputStream.DEFAULT_MAX_LENGTH;
088
089   /** Default parser, string input encoded as BASE64. */
090   public static class Base64 extends CborParser {
091
092      /**
093       * Constructor.
094       *
095       * @param builder The builder for this object.
096       */
097      public Base64(Builder builder) {
098         super(builder.binaryFormat(BinaryFormat.BASE64));
099      }
100   }
101
102   /**
103    * Builder class.
104    */
105   public static class Builder extends InputStreamParser.Builder<Builder> {
106
107      private static final Cache<HashKey,CborParser> CACHE = Cache.of(HashKey.class, CborParser.class).build();
108
109      private boolean nativeMode;
110      private int maxLength = DEFAULT_MAX_LENGTH;
111
112      /**
113       * Constructor, default settings.
114       */
115      protected Builder() {
116         consumes("application/cbor");
117         nativeMode = env("CborParser.nativeMode", false);
118         maxLength = env("CborParser.maxLength", DEFAULT_MAX_LENGTH);
119      }
120
121      /**
122       * Copy constructor.
123       *
124       * @param copyFrom The builder to copy from.
125       */
126      protected Builder(Builder copyFrom) {
127         super(reqnn("copyFrom", copyFrom));
128         nativeMode = copyFrom.nativeMode;
129         maxLength = copyFrom.maxLength;
130      }
131
132      /**
133       * Copy constructor.
134       *
135       * @param copyFrom The parser to copy from.
136       */
137      protected Builder(CborParser copyFrom) {
138         super(reqnn("copyFrom", copyFrom));
139         nativeMode = copyFrom.nativeMode;
140         maxLength = copyFrom.maxLength;
141      }
142
143      /**
144       * Surfaces CBOR semantic tags and simple values as token-level metadata on
145       * {@link TokenReader} cursors returned by {@link CborParserSession#readTokens(Object)}.
146       *
147       * <p>
148       * When enabled, tags accumulate on the cursor's tag stack (visible via
149       * {@link TokenReader#getTagCount()} / {@link TokenReader#getTag(int)}) and simple values
150       * surface their int via {@link TokenReader#getSimpleValue()} on
151       * {@link TokenType#VALUE_NULL} tokens.  When disabled (default), tags are silently
152       * unwrapped and simple values collapse to {@link TokenType#VALUE_NULL} with no metadata.
153       *
154       * <p>
155       * This is a token-cursor-level setting and does not affect the high-level POJO databind
156       * path ({@link CborParser#read(Object, Class)}); that path always discards tags.
157       *
158       * @return This object.
159       */
160      public Builder nativeMode() {
161         return nativeMode(true);
162      }
163
164      /**
165       * Same as {@link #nativeMode()} but allows you to explicitly specify the value.
166       *
167       * @param value The value for this setting.
168       * @return This object.
169       */
170      public Builder nativeMode(boolean value) {
171         nativeMode = value;
172         return this;
173      }
174
175      /**
176       * The maximum allowed wire-declared length for CBOR byte/text strings and array/map element counts.
177       *
178       * <p>
179       * Guards against malformed or adversarial input where a small payload declares a huge length or
180       * element count, which would otherwise trigger {@link OutOfMemoryError}.  Malformed lengths are
181       * reported as a clean parse error instead.
182       *
183       * @param value The maximum length.  Default is 16 MiB.  Values &le; 0 disable the cap.
184       * @return This object.
185       */
186      public Builder maxLength(int value) {
187         maxLength = value;
188         return this;
189      }
190
191      @Override /* Overridden from Context.Builder<?> */
192      public CborParser build() {
193         return cache(CACHE).build(CborParser.class);
194      }
195
196      @Override /* Overridden from Context.Builder<?> */
197      public Builder copy() {
198         return new Builder(this);
199      }
200
201      @Override /* Overridden from Context.Builder<?> */
202      public HashKey hashKey() {
203         return HashKey.of(super.hashKey(), nativeMode, maxLength);
204      }
205   }
206
207   /** Default parser, string input encoded as spaced-hex. */
208   public static class SpacedHex extends CborParser {
209
210      /**
211       * Constructor.
212       *
213       * @param builder The builder for this object.
214       */
215      public SpacedHex(Builder builder) {
216         super(builder.binaryFormat(BinaryFormat.SPACED_HEX));
217      }
218   }
219
220   /** Default parser, all default settings. */
221   public static final CborParser DEFAULT = new CborParser(create());
222   /** Default parser with binary-native opt-in mode enabled (CBOR tags / simple values surface as token-level metadata). */
223   public static final CborParser DEFAULT_NATIVE = new CborParser(create().nativeMode());
224   /** Default parser, string input encoded as spaced-hex. */
225   public static final CborParser DEFAULT_SPACED_HEX = new SpacedHex(create());
226   /** Default parser, string input encoded as BASE64. */
227   public static final CborParser DEFAULT_BASE64 = new Base64(create());
228
229   /**
230    * Creates a new builder for this object.
231    *
232    * @return A new builder.
233    */
234   public static Builder create() {
235      return new Builder();
236   }
237
238   protected final boolean nativeMode;
239
240   private final int maxLength;
241
242   private final Map<ClassMeta<?>,CborClassMeta> cborClassMetas = new ConcurrentHashMap<>();
243   private final Map<BeanPropertyMeta,CborBeanPropertyMeta> cborBeanPropertyMetas = new ConcurrentHashMap<>();
244
245   /**
246    * Constructor.
247    *
248    * @param builder The builder for this object.
249    */
250   public CborParser(Builder builder) {
251      super(builder);
252      this.nativeMode = builder.nativeMode;
253      this.maxLength = builder.maxLength;
254   }
255
256   /**
257    * Returns <jk>true</jk> if token-cursor native mode is enabled.
258    *
259    * @return <jk>true</jk> if native mode is enabled, else <jk>false</jk>.
260    */
261   public boolean isNativeMode() {
262      return nativeMode;
263   }
264
265   /**
266    * Returns the maximum allowed wire-declared length for byte/text strings and array/map element counts.
267    *
268    * @return The maximum length.
269    */
270   public int getMaxLength() {
271      return maxLength;
272   }
273
274   @Override /* Overridden from Context */
275   public Builder copy() {
276      return new Builder(this);
277   }
278
279   @Override /* Overridden from Context */
280   public CborParserSession.Builder createSession() {
281      return CborParserSession.create(this);
282   }
283
284   @Override /* Overridden from CborMetaProvider */
285   public CborBeanPropertyMeta getCborBeanPropertyMeta(BeanPropertyMeta bpm) {
286      if (bpm == null)
287         return CborBeanPropertyMeta.DEFAULT;
288      return cborBeanPropertyMetas.computeIfAbsent(bpm, k -> new CborBeanPropertyMeta(k.getDelegateFor(), this));
289   }
290
291   @Override /* Overridden from CborMetaProvider */
292   public CborClassMeta getCborClassMeta(ClassMeta<?> cm) {
293      return cborClassMetas.computeIfAbsent(cm, k -> new CborClassMeta(k, this));
294   }
295
296   @Override /* Overridden from Context */
297   public CborParserSession getSession() {
298      return createSession().build();
299   }
300
301   /**
302    * Convenience delegator that opens a {@link CborTokenReader} over the input using
303    * <b>default session arguments</b> (mirrors {@link #read(Object, Class)}).
304    *
305    * <p>
306    * The real implementation lives on {@link CborParserSession#readTokens(Object)}.  Callers
307    * that need request-derived configuration (locale, timezone, schema, swaps) should call
308    * {@link #createSession()} and invoke {@link CborParserSession#readTokens(Object)} on the
309    * built session instead.
310    *
311    * @param input The input.
312    * @return A new {@link CborTokenReader}.
313    * @throws IOException If a problem occurred opening the underlying input.
314    */
315   @Override /* TokenReadable */
316   public TokenReader readTokens(Object input) throws IOException {
317      return getSession().readTokens(input);
318   }
319
320   /**
321    * Convenience delegator for the streaming array-element {@link RecordReader} (uses default
322    * session args; see {@link #readTokens(Object)}).  Real impl on
323    * {@link CborParserSession#readArrayRecords(Object)}.
324    *
325    * @param input The input.
326    * @return A new element-streamed {@link RecordReader}.
327    * @throws IOException If a problem occurred opening the underlying input.
328    */
329   @Override /* ArrayRecordReadable */
330   public RecordReader readArrayRecords(Object input) throws IOException {
331      return getSession().readArrayRecords(input);
332   }
333
334   @Override /* Overridden from InputStreamParser */
335   protected FluentMap<String,Object> properties() {
336      return super.properties()
337         .a("nativeMode", nativeMode);
338   }
339}