| package co.nstant.in.cbor; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Objects; |
| |
| import co.nstant.in.cbor.decoder.ArrayDecoder; |
| import co.nstant.in.cbor.decoder.ByteStringDecoder; |
| import co.nstant.in.cbor.decoder.MapDecoder; |
| import co.nstant.in.cbor.decoder.NegativeIntegerDecoder; |
| import co.nstant.in.cbor.decoder.SpecialDecoder; |
| import co.nstant.in.cbor.decoder.TagDecoder; |
| import co.nstant.in.cbor.decoder.UnicodeStringDecoder; |
| import co.nstant.in.cbor.decoder.UnsignedIntegerDecoder; |
| import co.nstant.in.cbor.model.Array; |
| import co.nstant.in.cbor.model.DataItem; |
| import co.nstant.in.cbor.model.LanguageTaggedString; |
| import co.nstant.in.cbor.model.MajorType; |
| import co.nstant.in.cbor.model.Number; |
| import co.nstant.in.cbor.model.RationalNumber; |
| import co.nstant.in.cbor.model.Tag; |
| import co.nstant.in.cbor.model.UnicodeString; |
| |
| /** |
| * Decoder for the CBOR format based. |
| */ |
| public class CborDecoder { |
| |
| private final InputStream inputStream; |
| private final UnsignedIntegerDecoder unsignedIntegerDecoder; |
| private final NegativeIntegerDecoder negativeIntegerDecoder; |
| private final ByteStringDecoder byteStringDecoder; |
| private final UnicodeStringDecoder unicodeStringDecoder; |
| private final ArrayDecoder arrayDecoder; |
| private final MapDecoder mapDecoder; |
| private final TagDecoder tagDecoder; |
| private final SpecialDecoder specialDecoder; |
| |
| private boolean autoDecodeInfinitiveArrays = true; |
| private boolean autoDecodeInfinitiveMaps = true; |
| private boolean autoDecodeInfinitiveByteStrings = true; |
| private boolean autoDecodeInfinitiveUnicodeStrings = true; |
| private boolean autoDecodeRationalNumbers = true; |
| private boolean autoDecodeLanguageTaggedStrings = true; |
| private boolean rejectDuplicateKeys = false; |
| |
| /** |
| * Initialize a new decoder which reads the binary encoded data from an |
| * {@link InputStream}. |
| */ |
| public CborDecoder(InputStream inputStream) { |
| Objects.requireNonNull(inputStream); |
| this.inputStream = inputStream; |
| unsignedIntegerDecoder = new UnsignedIntegerDecoder(this, inputStream); |
| negativeIntegerDecoder = new NegativeIntegerDecoder(this, inputStream); |
| byteStringDecoder = new ByteStringDecoder(this, inputStream); |
| unicodeStringDecoder = new UnicodeStringDecoder(this, inputStream); |
| arrayDecoder = new ArrayDecoder(this, inputStream); |
| mapDecoder = new MapDecoder(this, inputStream); |
| tagDecoder = new TagDecoder(this, inputStream); |
| specialDecoder = new SpecialDecoder(this, inputStream); |
| } |
| |
| /** |
| * Convenience method to decode a byte array directly. |
| * |
| * @param bytes |
| * the CBOR encoded data |
| * @return a list of {@link DataItem}s |
| * @throws CborException |
| * if decoding failed |
| */ |
| public static List<DataItem> decode(byte[] bytes) throws CborException { |
| return new CborDecoder(new ByteArrayInputStream(bytes)).decode(); |
| } |
| |
| /** |
| * Decode the {@link InputStream} to a list of {@link DataItem}s. |
| * |
| * @return the list of {@link DataItem}s |
| * @throws CborException |
| * if decoding failed |
| */ |
| public List<DataItem> decode() throws CborException { |
| List<DataItem> dataItems = new LinkedList<>(); |
| DataItem dataItem; |
| while ((dataItem = decodeNext()) != null) { |
| dataItems.add(dataItem); |
| } |
| return dataItems; |
| } |
| |
| /** |
| * Streaming decoding of an input stream. On each decoded DataItem, the |
| * callback listener is invoked. |
| * |
| * @param dataItemListener |
| * the callback listener |
| * @throws CborException |
| * if decoding failed |
| */ |
| public void decode(DataItemListener dataItemListener) throws CborException { |
| Objects.requireNonNull(dataItemListener); |
| DataItem dataItem = decodeNext(); |
| while (dataItem != null) { |
| dataItemListener.onDataItem(dataItem); |
| dataItem = decodeNext(); |
| } |
| } |
| |
| /** |
| * Decodes exactly one DataItem from the input stream. |
| * |
| * @return a {@link DataItem} or null if end of stream has reached. |
| * @throws CborException |
| * if decoding failed |
| */ |
| public DataItem decodeNext() throws CborException { |
| int symbol; |
| try { |
| symbol = inputStream.read(); |
| } catch (IOException ioException) { |
| throw new CborException(ioException); |
| } |
| if (symbol == -1) { |
| return null; |
| } |
| switch (MajorType.ofByte(symbol)) { |
| case ARRAY: |
| return arrayDecoder.decode(symbol); |
| case BYTE_STRING: |
| return byteStringDecoder.decode(symbol); |
| case MAP: |
| return mapDecoder.decode(symbol); |
| case NEGATIVE_INTEGER: |
| return negativeIntegerDecoder.decode(symbol); |
| case UNICODE_STRING: |
| return unicodeStringDecoder.decode(symbol); |
| case UNSIGNED_INTEGER: |
| return unsignedIntegerDecoder.decode(symbol); |
| case SPECIAL: |
| return specialDecoder.decode(symbol); |
| case TAG: |
| Tag tag = tagDecoder.decode(symbol); |
| DataItem next = decodeNext(); |
| if (next == null) { |
| throw new CborException("Unexpected end of stream: tag without following data item."); |
| } else { |
| if (autoDecodeRationalNumbers && tag.getValue() == 30) { |
| return decodeRationalNumber(next); |
| } else if (autoDecodeLanguageTaggedStrings && tag.getValue() == 38) { |
| return decodeLanguageTaggedString(next); |
| } else { |
| DataItem itemToTag = next; |
| while (itemToTag.hasTag()) |
| itemToTag = itemToTag.getTag(); |
| itemToTag.setTag(tag); |
| return next; |
| } |
| } |
| case INVALID: |
| default: |
| throw new CborException("Not implemented major type " + symbol); |
| } |
| } |
| |
| private DataItem decodeLanguageTaggedString(DataItem dataItem) throws CborException { |
| if (!(dataItem instanceof Array)) { |
| throw new CborException("Error decoding LanguageTaggedString: not an array"); |
| } |
| |
| Array array = (Array) dataItem; |
| |
| if (array.getDataItems().size() != 2) { |
| throw new CborException("Error decoding LanguageTaggedString: array size is not 2"); |
| } |
| |
| DataItem languageDataItem = array.getDataItems().get(0); |
| |
| if (!(languageDataItem instanceof UnicodeString)) { |
| throw new CborException("Error decoding LanguageTaggedString: first data item is not an UnicodeString"); |
| } |
| |
| DataItem stringDataItem = array.getDataItems().get(1); |
| |
| if (!(stringDataItem instanceof UnicodeString)) { |
| throw new CborException("Error decoding LanguageTaggedString: second data item is not an UnicodeString"); |
| } |
| |
| UnicodeString language = (UnicodeString) languageDataItem; |
| UnicodeString string = (UnicodeString) stringDataItem; |
| |
| return new LanguageTaggedString(language, string); |
| } |
| |
| private DataItem decodeRationalNumber(DataItem dataItem) throws CborException { |
| if (!(dataItem instanceof Array)) { |
| throw new CborException("Error decoding RationalNumber: not an array"); |
| } |
| |
| Array array = (Array) dataItem; |
| |
| if (array.getDataItems().size() != 2) { |
| throw new CborException("Error decoding RationalNumber: array size is not 2"); |
| } |
| |
| DataItem numeratorDataItem = array.getDataItems().get(0); |
| |
| if (!(numeratorDataItem instanceof Number)) { |
| throw new CborException("Error decoding RationalNumber: first data item is not a number"); |
| } |
| |
| DataItem denominatorDataItem = array.getDataItems().get(1); |
| |
| if (!(denominatorDataItem instanceof Number)) { |
| throw new CborException("Error decoding RationalNumber: second data item is not a number"); |
| } |
| |
| Number numerator = (Number) numeratorDataItem; |
| Number denominator = (Number) denominatorDataItem; |
| |
| return new RationalNumber(numerator, denominator); |
| } |
| |
| public boolean isAutoDecodeInfinitiveArrays() { |
| return autoDecodeInfinitiveArrays; |
| } |
| |
| public void setAutoDecodeInfinitiveArrays(boolean autoDecodeInfinitiveArrays) { |
| this.autoDecodeInfinitiveArrays = autoDecodeInfinitiveArrays; |
| } |
| |
| public boolean isAutoDecodeInfinitiveMaps() { |
| return autoDecodeInfinitiveMaps; |
| } |
| |
| public void setAutoDecodeInfinitiveMaps(boolean autoDecodeInfinitiveMaps) { |
| this.autoDecodeInfinitiveMaps = autoDecodeInfinitiveMaps; |
| } |
| |
| public boolean isAutoDecodeInfinitiveByteStrings() { |
| return autoDecodeInfinitiveByteStrings; |
| } |
| |
| public void setAutoDecodeInfinitiveByteStrings( |
| boolean autoDecodeInfinitiveByteStrings) { |
| this.autoDecodeInfinitiveByteStrings = autoDecodeInfinitiveByteStrings; |
| } |
| |
| public boolean isAutoDecodeInfinitiveUnicodeStrings() { |
| return autoDecodeInfinitiveUnicodeStrings; |
| } |
| |
| public void setAutoDecodeInfinitiveUnicodeStrings( |
| boolean autoDecodeInfinitiveUnicodeStrings) { |
| this.autoDecodeInfinitiveUnicodeStrings = autoDecodeInfinitiveUnicodeStrings; |
| } |
| |
| public boolean isAutoDecodeRationalNumbers() { |
| return autoDecodeRationalNumbers; |
| } |
| |
| public void setAutoDecodeRationalNumbers( |
| boolean autoDecodeRationalNumbers) { |
| this.autoDecodeRationalNumbers = autoDecodeRationalNumbers; |
| } |
| |
| public boolean isAutoDecodeLanguageTaggedStrings() { |
| return autoDecodeLanguageTaggedStrings; |
| } |
| |
| public void setAutoDecodeLanguageTaggedStrings( |
| boolean autoDecodeLanguageTaggedStrings) { |
| this.autoDecodeLanguageTaggedStrings = autoDecodeLanguageTaggedStrings; |
| } |
| |
| public boolean isRejectDuplicateKeys() { |
| return rejectDuplicateKeys; |
| } |
| |
| public void setRejectDuplicateKeys(boolean rejectDuplicateKeys) { |
| this.rejectDuplicateKeys = rejectDuplicateKeys; |
| } |
| |
| } |