6c3f2c59eb3245e51fa15d8f74ea9fdba6f53664
[yangtools.git] / codec / yang-data-codec-binfmt / src / main / java / org / opendaylight / yangtools / yang / data / codec / binfmt / AbstractMagnesiumDataInput.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.data.codec.binfmt;
9
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
13
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.ImmutableList.Builder;
16 import com.google.common.collect.ImmutableMap;
17 import com.google.common.collect.ImmutableSet;
18 import com.google.common.util.concurrent.UncheckedExecutionException;
19 import java.io.DataInput;
20 import java.io.IOException;
21 import java.io.StringReader;
22 import java.math.BigDecimal;
23 import java.math.BigInteger;
24 import java.nio.charset.StandardCharsets;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.concurrent.ExecutionException;
28 import javax.xml.transform.dom.DOMSource;
29 import org.eclipse.jdt.annotation.NonNull;
30 import org.opendaylight.yangtools.rfc8528.data.api.MountPointIdentifier;
31 import org.opendaylight.yangtools.util.xml.UntrustedXML;
32 import org.opendaylight.yangtools.yang.common.Empty;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.common.QNameModule;
35 import org.opendaylight.yangtools.yang.common.Uint16;
36 import org.opendaylight.yangtools.yang.common.Uint32;
37 import org.opendaylight.yangtools.yang.common.Uint64;
38 import org.opendaylight.yangtools.yang.common.Uint8;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
45 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48 import org.xml.sax.InputSource;
49 import org.xml.sax.SAXException;
50
51 /**
52  * Abstract base class for NormalizedNodeDataInput based on {@link MagnesiumNode}, {@link MagnesiumPathArgument} and
53  * {@link MagnesiumValue}.
54  */
55 abstract class AbstractMagnesiumDataInput extends AbstractNormalizedNodeDataInput {
56     private static final Logger LOG = LoggerFactory.getLogger(AbstractMagnesiumDataInput.class);
57
58     // Known singleton objects
59     private static final @NonNull Byte INT8_0 = 0;
60     private static final @NonNull Short INT16_0 = 0;
61     private static final @NonNull Integer INT32_0 = 0;
62     private static final @NonNull Long INT64_0 = 0L;
63     private static final byte @NonNull[] BINARY_0 = new byte[0];
64     private static final @NonNull AugmentationIdentifier EMPTY_AID = AugmentationIdentifier.create(ImmutableSet.of());
65
66     private final List<AugmentationIdentifier> codedAugments = new ArrayList<>();
67     private final List<NodeIdentifier> codedNodeIdentifiers = new ArrayList<>();
68     private final List<QNameModule> codedModules = new ArrayList<>();
69     private final List<String> codedStrings = new ArrayList<>();
70
71     AbstractMagnesiumDataInput(final DataInput input) {
72         super(input);
73     }
74
75     @Override
76     public final void streamNormalizedNode(final NormalizedNodeStreamWriter writer) throws IOException {
77         streamNormalizedNode(requireNonNull(writer), null, input.readByte());
78     }
79
80     private void streamNormalizedNode(final NormalizedNodeStreamWriter writer, final PathArgument parent,
81             final byte nodeHeader) throws IOException {
82         switch (nodeHeader & MagnesiumNode.TYPE_MASK) {
83             case MagnesiumNode.NODE_LEAF:
84                 streamLeaf(writer, parent, nodeHeader);
85                 break;
86             case MagnesiumNode.NODE_CONTAINER:
87                 streamContainer(writer, nodeHeader);
88                 break;
89             case MagnesiumNode.NODE_LIST:
90                 streamList(writer, nodeHeader);
91                 break;
92             case MagnesiumNode.NODE_MAP:
93                 streamMap(writer, nodeHeader);
94                 break;
95             case MagnesiumNode.NODE_MAP_ORDERED:
96                 streamMapOrdered(writer, nodeHeader);
97                 break;
98             case MagnesiumNode.NODE_LEAFSET:
99                 streamLeafset(writer, nodeHeader);
100                 break;
101             case MagnesiumNode.NODE_LEAFSET_ORDERED:
102                 streamLeafsetOrdered(writer, nodeHeader);
103                 break;
104             case MagnesiumNode.NODE_CHOICE:
105                 streamChoice(writer, nodeHeader);
106                 break;
107             case MagnesiumNode.NODE_AUGMENTATION:
108                 streamAugmentation(writer, nodeHeader);
109                 break;
110             case MagnesiumNode.NODE_ANYXML:
111                 streamAnyxml(writer, nodeHeader);
112                 break;
113             case MagnesiumNode.NODE_LIST_ENTRY:
114                 streamListEntry(writer, parent, nodeHeader);
115                 break;
116             case MagnesiumNode.NODE_LEAFSET_ENTRY:
117                 streamLeafsetEntry(writer, parent, nodeHeader);
118                 break;
119             case MagnesiumNode.NODE_MAP_ENTRY:
120                 streamMapEntry(writer, parent, nodeHeader);
121                 break;
122             default:
123                 throw new InvalidNormalizedNodeStreamException("Unexpected node header " + nodeHeader);
124         }
125     }
126
127     private void streamAnyxml(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
128         final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
129         LOG.trace("Streaming anyxml node {}", identifier);
130
131         final DOMSource value = readDOMSource();
132         if (writer.startAnyxmlNode(identifier, DOMSource.class)) {
133             writer.domSourceValue(value);
134             writer.endNode();
135         }
136     }
137
138     private void streamAugmentation(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
139         final AugmentationIdentifier augIdentifier = decodeAugmentationIdentifier(nodeHeader);
140         LOG.trace("Streaming augmentation node {}", augIdentifier);
141         writer.startAugmentationNode(augIdentifier);
142         commonStreamContainer(writer, augIdentifier);
143     }
144
145     private void streamChoice(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
146         final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
147         LOG.trace("Streaming choice node {}", identifier);
148         writer.startChoiceNode(identifier, UNKNOWN_SIZE);
149         commonStreamContainer(writer, identifier);
150     }
151
152     private void streamContainer(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
153         final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
154         LOG.trace("Streaming container node {}", identifier);
155         writer.startContainerNode(identifier, UNKNOWN_SIZE);
156         commonStreamContainer(writer, identifier);
157     }
158
159     private void streamLeaf(final NormalizedNodeStreamWriter writer, final PathArgument parent, final byte nodeHeader)
160             throws IOException {
161         final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
162         LOG.trace("Streaming leaf node {}", identifier);
163         writer.startLeafNode(identifier);
164
165         final Object value;
166         if ((nodeHeader & MagnesiumNode.PREDICATE_ONE) == MagnesiumNode.PREDICATE_ONE) {
167             if (!(parent instanceof NodeIdentifierWithPredicates)) {
168                 throw new InvalidNormalizedNodeStreamException("Invalid predicate leaf " + identifier + " in parent "
169                         + parent);
170             }
171
172             value = ((NodeIdentifierWithPredicates) parent).getValue(identifier.getNodeType());
173             if (value == null) {
174                 throw new InvalidNormalizedNodeStreamException("Failed to find predicate leaf " + identifier
175                     + " in parent " + parent);
176             }
177         } else {
178             value = readLeafValue();
179         }
180
181         writer.scalarValue(value);
182         writer.endNode();
183     }
184
185     private void streamLeafset(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
186         final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
187         LOG.trace("Streaming leaf set node {}", identifier);
188         writer.startLeafSet(identifier, UNKNOWN_SIZE);
189         commonStreamContainer(writer, identifier);
190     }
191
192     private void streamLeafsetOrdered(final NormalizedNodeStreamWriter writer, final byte nodeHeader)
193             throws IOException {
194         final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
195         LOG.trace("Streaming ordered leaf set node {}", identifier);
196         writer.startOrderedLeafSet(identifier, UNKNOWN_SIZE);
197
198         commonStreamContainer(writer, identifier);
199     }
200
201     private void streamLeafsetEntry(final NormalizedNodeStreamWriter writer, final PathArgument parent,
202             final byte nodeHeader) throws IOException {
203         final NodeIdentifier nodeId = decodeNodeIdentifier(nodeHeader, parent);
204         final Object value = readLeafValue();
205         final NodeWithValue<Object> leafIdentifier = new NodeWithValue<>(nodeId.getNodeType(), value);
206         LOG.trace("Streaming leaf set entry node {}", leafIdentifier);
207         writer.startLeafSetEntryNode(leafIdentifier);
208         writer.scalarValue(value);
209         writer.endNode();
210     }
211
212     private void streamList(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
213         final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
214         writer.startUnkeyedList(identifier, UNKNOWN_SIZE);
215         commonStreamContainer(writer, identifier);
216     }
217
218     private void streamListEntry(final NormalizedNodeStreamWriter writer, final PathArgument parent,
219             final byte nodeHeader) throws IOException {
220         final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader, parent);
221         LOG.trace("Streaming unkeyed list item node {}", identifier);
222         writer.startUnkeyedListItem(identifier, UNKNOWN_SIZE);
223         commonStreamContainer(writer, identifier);
224     }
225
226     private void streamMap(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
227         final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
228         LOG.trace("Streaming map node {}", identifier);
229         writer.startMapNode(identifier, UNKNOWN_SIZE);
230         commonStreamContainer(writer, identifier);
231     }
232
233     private void streamMapOrdered(final NormalizedNodeStreamWriter writer, final byte nodeHeader) throws IOException {
234         final NodeIdentifier identifier = decodeNodeIdentifier(nodeHeader);
235         LOG.trace("Streaming ordered map node {}", identifier);
236         writer.startOrderedMapNode(identifier, UNKNOWN_SIZE);
237         commonStreamContainer(writer, identifier);
238     }
239
240     private void streamMapEntry(final NormalizedNodeStreamWriter writer, final PathArgument parent,
241             final byte nodeHeader) throws IOException {
242         final NodeIdentifier nodeId = decodeNodeIdentifier(nodeHeader, parent);
243
244         final int size;
245         switch (mask(nodeHeader, MagnesiumNode.PREDICATE_MASK)) {
246             case MagnesiumNode.PREDICATE_ZERO:
247                 size = 0;
248                 break;
249             case MagnesiumNode.PREDICATE_ONE:
250                 size = 1;
251                 break;
252             case MagnesiumNode.PREDICATE_1B:
253                 size = input.readUnsignedByte();
254                 break;
255             case MagnesiumNode.PREDICATE_4B:
256                 size = input.readInt();
257                 break;
258             default:
259                 // ISE on purpose: this should never ever happen
260                 throw new IllegalStateException("Failed to decode NodeIdentifierWithPredicates size from header "
261                         + nodeHeader);
262         }
263
264         final NodeIdentifierWithPredicates identifier = readNodeIdentifierWithPredicates(nodeId.getNodeType(), size);
265         LOG.trace("Streaming map entry node {}", identifier);
266         writer.startMapEntryNode(identifier, UNKNOWN_SIZE);
267         commonStreamContainer(writer, identifier);
268     }
269
270     private void commonStreamContainer(final NormalizedNodeStreamWriter writer, final PathArgument parent)
271             throws IOException {
272         for (byte nodeType = input.readByte(); nodeType != MagnesiumNode.NODE_END; nodeType = input.readByte()) {
273             streamNormalizedNode(writer, parent, nodeType);
274         }
275         writer.endNode();
276     }
277
278     private @NonNull NodeIdentifier decodeNodeIdentifier() throws IOException {
279         final QNameModule module = decodeQNameModule();
280         final String localName = readRefString();
281         final NodeIdentifier nodeId;
282         try {
283             nodeId = QNameFactory.getNodeIdentifier(module, localName);
284         } catch (ExecutionException e) {
285             throw new InvalidNormalizedNodeStreamException("Illegal QName module=" + module + " localName="
286                     + localName, e);
287         }
288
289         codedNodeIdentifiers.add(nodeId);
290         return nodeId;
291     }
292
293     private NodeIdentifier decodeNodeIdentifier(final byte nodeHeader) throws IOException {
294         return decodeNodeIdentifier(nodeHeader, null);
295     }
296
297     private NodeIdentifier decodeNodeIdentifier(final byte nodeHeader, final PathArgument parent) throws IOException {
298         final int index;
299         switch (nodeHeader & MagnesiumNode.ADDR_MASK) {
300             case MagnesiumNode.ADDR_DEFINE:
301                 return readNodeIdentifier();
302             case MagnesiumNode.ADDR_LOOKUP_1B:
303                 index = input.readUnsignedByte();
304                 break;
305             case MagnesiumNode.ADDR_LOOKUP_4B:
306                 index = input.readInt();
307                 break;
308             case MagnesiumNode.ADDR_PARENT:
309                 if (parent instanceof NodeIdentifier) {
310                     return (NodeIdentifier) parent;
311                 }
312                 throw new InvalidNormalizedNodeStreamException("Invalid node identifier reference to parent " + parent);
313             default:
314                 throw new InvalidNormalizedNodeStreamException("Unexpected node identifier addressing in header "
315                         + nodeHeader);
316         }
317
318         try {
319             return codedNodeIdentifiers.get(index);
320         } catch (IndexOutOfBoundsException e) {
321             throw new InvalidNormalizedNodeStreamException("Invalid QName reference " + index, e);
322         }
323     }
324
325     private AugmentationIdentifier decodeAugmentationIdentifier(final byte nodeHeader) throws IOException {
326         final int index;
327         switch (nodeHeader & MagnesiumNode.ADDR_MASK) {
328             case MagnesiumNode.ADDR_DEFINE:
329                 return readAugmentationIdentifier();
330             case MagnesiumNode.ADDR_LOOKUP_1B:
331                 index = input.readUnsignedByte();
332                 break;
333             case MagnesiumNode.ADDR_LOOKUP_4B:
334                 index = input.readInt();
335                 break;
336             default:
337                 throw new InvalidNormalizedNodeStreamException(
338                     "Unexpected augmentation identifier addressing in header " + nodeHeader);
339         }
340
341         try {
342             return codedAugments.get(index);
343         } catch (IndexOutOfBoundsException e) {
344             throw new InvalidNormalizedNodeStreamException("Invalid augmentation identifier reference " + index, e);
345         }
346     }
347
348     @Override
349     public final YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
350         final byte type = input.readByte();
351         if (type == MagnesiumValue.YIID) {
352             return readYangInstanceIdentifier(input.readInt());
353         } else if (type >= MagnesiumValue.YIID_0) {
354             // Note 'byte' is range limited, so it is always '&& type <= MagnesiumValue.YIID_31'
355             return readYangInstanceIdentifier(type - MagnesiumValue.YIID_0);
356         } else {
357             throw new InvalidNormalizedNodeStreamException("Unexpected YangInstanceIdentifier type " + type);
358         }
359     }
360
361     private @NonNull YangInstanceIdentifier readYangInstanceIdentifier(final int size) throws IOException {
362         if (size > 0) {
363             final Builder<PathArgument> builder = ImmutableList.builderWithExpectedSize(size);
364             for (int i = 0; i < size; ++i) {
365                 builder.add(readPathArgument());
366             }
367             return YangInstanceIdentifier.create(builder.build());
368         } else if (size == 0) {
369             return YangInstanceIdentifier.empty();
370         } else {
371             throw new InvalidNormalizedNodeStreamException("Invalid YangInstanceIdentifier size " + size);
372         }
373     }
374
375     @Override
376     public final QName readQName() throws IOException {
377         final byte type = input.readByte();
378         switch (type) {
379             case MagnesiumValue.QNAME:
380                 return decodeQName();
381             case MagnesiumValue.QNAME_REF_1B:
382                 return decodeQNameRef1();
383             case MagnesiumValue.QNAME_REF_2B:
384                 return decodeQNameRef2();
385             case MagnesiumValue.QNAME_REF_4B:
386                 return decodeQNameRef4();
387             default:
388                 throw new InvalidNormalizedNodeStreamException("Unexpected QName type " + type);
389         }
390     }
391
392     @Override
393     public final PathArgument readPathArgument() throws IOException {
394         final byte header = input.readByte();
395         switch (header & MagnesiumPathArgument.TYPE_MASK) {
396             case MagnesiumPathArgument.AUGMENTATION_IDENTIFIER:
397                 return readAugmentationIdentifier(header);
398             case MagnesiumPathArgument.NODE_IDENTIFIER:
399                 verifyPathIdentifierOnly(header);
400                 return readNodeIdentifier(header);
401             case MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES:
402                 return readNodeIdentifierWithPredicates(header);
403             case MagnesiumPathArgument.NODE_WITH_VALUE:
404                 verifyPathIdentifierOnly(header);
405                 return readNodeWithValue(header);
406             case MagnesiumPathArgument.MOUNTPOINT_IDENTIFIER:
407                 verifyPathIdentifierOnly(header);
408                 return MountPointIdentifier.create(readNodeIdentifier(header).getNodeType());
409             default:
410                 throw new InvalidNormalizedNodeStreamException("Unexpected PathArgument header " + header);
411         }
412     }
413
414     private AugmentationIdentifier readAugmentationIdentifier() throws IOException {
415         final AugmentationIdentifier result = readAugmentationIdentifier(input.readInt());
416         codedAugments.add(result);
417         return result;
418     }
419
420     private AugmentationIdentifier readAugmentationIdentifier(final byte header) throws IOException {
421         final byte count = mask(header, MagnesiumPathArgument.AID_COUNT_MASK);
422         switch (count) {
423             case MagnesiumPathArgument.AID_COUNT_1B:
424                 return readAugmentationIdentifier(input.readUnsignedByte());
425             case MagnesiumPathArgument.AID_COUNT_2B:
426                 return readAugmentationIdentifier(input.readUnsignedShort());
427             case MagnesiumPathArgument.AID_COUNT_4B:
428                 return readAugmentationIdentifier(input.readInt());
429             default:
430                 return readAugmentationIdentifier(rshift(count, MagnesiumPathArgument.AID_COUNT_SHIFT));
431         }
432     }
433
434     private AugmentationIdentifier readAugmentationIdentifier(final int size) throws IOException {
435         if (size > 0) {
436             final List<QName> qnames = new ArrayList<>(size);
437             for (int i = 0; i < size; ++i) {
438                 qnames.add(readQName());
439             }
440             return AugmentationIdentifier.create(ImmutableSet.copyOf(qnames));
441         } else if (size == 0) {
442             return EMPTY_AID;
443         } else {
444             throw new InvalidNormalizedNodeStreamException("Invalid augmentation identifier size " + size);
445         }
446     }
447
448     private NodeIdentifier readNodeIdentifier() throws IOException {
449         return decodeNodeIdentifier();
450     }
451
452     private NodeIdentifier readNodeIdentifier(final byte header) throws IOException {
453         switch (header & MagnesiumPathArgument.QNAME_MASK) {
454             case MagnesiumPathArgument.QNAME_DEF:
455                 return decodeNodeIdentifier();
456             case MagnesiumPathArgument.QNAME_REF_1B:
457                 return decodeNodeIdentifierRef1();
458             case MagnesiumPathArgument.QNAME_REF_2B:
459                 return decodeNodeIdentifierRef2();
460             case MagnesiumPathArgument.QNAME_REF_4B:
461                 return decodeNodeIdentifierRef4();
462             default:
463                 throw new InvalidNormalizedNodeStreamException("Invalid QName coding in " + header);
464         }
465     }
466
467     private NodeIdentifierWithPredicates readNodeIdentifierWithPredicates(final byte header) throws IOException {
468         final QName qname = readNodeIdentifier(header).getNodeType();
469         switch (mask(header, MagnesiumPathArgument.SIZE_MASK)) {
470             case MagnesiumPathArgument.SIZE_1B:
471                 return readNodeIdentifierWithPredicates(qname, input.readUnsignedByte());
472             case MagnesiumPathArgument.SIZE_2B:
473                 return readNodeIdentifierWithPredicates(qname, input.readUnsignedShort());
474             case MagnesiumPathArgument.SIZE_4B:
475                 return readNodeIdentifierWithPredicates(qname, input.readInt());
476             default:
477                 return readNodeIdentifierWithPredicates(qname, rshift(header, MagnesiumPathArgument.SIZE_SHIFT));
478         }
479     }
480
481     private NodeIdentifierWithPredicates readNodeIdentifierWithPredicates(final QName qname, final int size)
482             throws IOException {
483         if (size == 1) {
484             return NodeIdentifierWithPredicates.of(qname, readQName(), readLeafValue());
485         } else if (size > 1) {
486             final ImmutableMap.Builder<QName, Object> builder = ImmutableMap.builderWithExpectedSize(size);
487             for (int i = 0; i < size; ++i) {
488                 builder.put(readQName(), readLeafValue());
489             }
490             return NodeIdentifierWithPredicates.of(qname, builder.build());
491         } else if (size == 0) {
492             return NodeIdentifierWithPredicates.of(qname);
493         } else {
494             throw new InvalidNormalizedNodeStreamException("Invalid predicate count " + size);
495         }
496     }
497
498     private NodeWithValue<?> readNodeWithValue(final byte header) throws IOException {
499         final QName qname = readNodeIdentifier(header).getNodeType();
500         return new NodeWithValue<>(qname, readLeafValue());
501     }
502
503     private static void verifyPathIdentifierOnly(final byte header) throws InvalidNormalizedNodeStreamException {
504         if (mask(header, MagnesiumPathArgument.SIZE_MASK) != 0) {
505             throw new InvalidNormalizedNodeStreamException("Invalid path argument header " + header);
506         }
507     }
508
509     private @NonNull NodeIdentifier decodeNodeIdentifierRef1() throws IOException {
510         return lookupNodeIdentifier(input.readUnsignedByte());
511     }
512
513     private @NonNull NodeIdentifier decodeNodeIdentifierRef2() throws IOException {
514         return lookupNodeIdentifier(input.readUnsignedShort() + 256);
515     }
516
517     private @NonNull NodeIdentifier decodeNodeIdentifierRef4() throws IOException {
518         return lookupNodeIdentifier(input.readInt());
519     }
520
521     private @NonNull QName decodeQName() throws IOException {
522         return decodeNodeIdentifier().getNodeType();
523     }
524
525     private @NonNull QName decodeQNameRef1() throws IOException {
526         return lookupQName(input.readUnsignedByte());
527     }
528
529     private @NonNull QName decodeQNameRef2() throws IOException {
530         return lookupQName(input.readUnsignedShort() + 256);
531     }
532
533     private @NonNull QName decodeQNameRef4() throws IOException {
534         return lookupQName(input.readInt());
535     }
536
537     private @NonNull QNameModule decodeQNameModule() throws IOException {
538         final byte type = input.readByte();
539         final int index;
540         switch (type) {
541             case MagnesiumValue.MODREF_1B:
542                 index = input.readUnsignedByte();
543                 break;
544             case MagnesiumValue.MODREF_2B:
545                 index = input.readUnsignedShort() + 256;
546                 break;
547             case MagnesiumValue.MODREF_4B:
548                 index = input.readInt();
549                 break;
550             default:
551                 return decodeQNameModuleDef(type);
552         }
553
554         try {
555             return codedModules.get(index);
556         } catch (IndexOutOfBoundsException e) {
557             throw new InvalidNormalizedNodeStreamException("Invalid QNameModule reference " + index, e);
558         }
559     }
560
561     // QNameModule definition, i.e. two encoded strings
562     private @NonNull QNameModule decodeQNameModuleDef(final byte type) throws IOException {
563         final String namespace = readRefString(type);
564
565         final byte refType = input.readByte();
566         final String revision = refType == MagnesiumValue.STRING_EMPTY ? null : readRefString(refType);
567         final QNameModule module;
568         try {
569             module = QNameFactory.createModule(namespace, revision);
570         } catch (UncheckedExecutionException e) {
571             throw new InvalidNormalizedNodeStreamException("Illegal QNameModule ns=" + namespace + " rev=" + revision,
572                 e);
573         }
574
575         codedModules.add(module);
576         return module;
577     }
578
579     private @NonNull String readRefString() throws IOException {
580         return readRefString(input.readByte());
581     }
582
583     private @NonNull String readRefString(final byte type) throws IOException {
584         final String str;
585         switch (type) {
586             case MagnesiumValue.STRING_REF_1B:
587                 return lookupString(input.readUnsignedByte());
588             case MagnesiumValue.STRING_REF_2B:
589                 return lookupString(input.readUnsignedShort() + 256);
590             case MagnesiumValue.STRING_REF_4B:
591                 return lookupString(input.readInt());
592             case MagnesiumValue.STRING_EMPTY:
593                 return "";
594             case MagnesiumValue.STRING_2B:
595                 str = readString2();
596                 break;
597             case MagnesiumValue.STRING_4B:
598                 str = readString4();
599                 break;
600             case MagnesiumValue.STRING_CHARS:
601                 str = readCharsString();
602                 break;
603             case MagnesiumValue.STRING_UTF:
604                 str = input.readUTF();
605                 break;
606             default:
607                 throw new InvalidNormalizedNodeStreamException("Unexpected String type " + type);
608         }
609
610         // TODO: consider interning Strings -- that would help with bits, but otherwise it's probably not worth it
611         codedStrings.add(verifyNotNull(str));
612         return str;
613     }
614
615     private @NonNull String readString() throws IOException {
616         final byte type = input.readByte();
617         switch (type) {
618             case MagnesiumValue.STRING_EMPTY:
619                 return "";
620             case MagnesiumValue.STRING_UTF:
621                 return input.readUTF();
622             case MagnesiumValue.STRING_2B:
623                 return readString2();
624             case MagnesiumValue.STRING_4B:
625                 return readString4();
626             case MagnesiumValue.STRING_CHARS:
627                 return readCharsString();
628             default:
629                 throw new InvalidNormalizedNodeStreamException("Unexpected String type " + type);
630         }
631     }
632
633     private @NonNull String readString2() throws IOException {
634         return readByteString(input.readUnsignedShort());
635     }
636
637     private @NonNull String readString4() throws IOException {
638         return readByteString(input.readInt());
639     }
640
641     private @NonNull String readByteString(final int size) throws IOException {
642         if (size > 0) {
643             final byte[] bytes = new byte[size];
644             input.readFully(bytes);
645             return new String(bytes, StandardCharsets.UTF_8);
646         } else if (size == 0) {
647             return "";
648         } else {
649             throw new InvalidNormalizedNodeStreamException("Invalid String bytes length " + size);
650         }
651     }
652
653     private @NonNull String readCharsString() throws IOException {
654         final int size = input.readInt();
655         if (size > 0) {
656             final char[] chars = new char[size];
657             for (int i = 0; i < size; ++i) {
658                 chars[i] = input.readChar();
659             }
660             return String.valueOf(chars);
661         } else if (size == 0) {
662             return "";
663         } else {
664             throw new InvalidNormalizedNodeStreamException("Invalid String chars length " + size);
665         }
666     }
667
668     private @NonNull NodeIdentifier lookupNodeIdentifier(final int index) throws InvalidNormalizedNodeStreamException {
669         try {
670             return codedNodeIdentifiers.get(index);
671         } catch (IndexOutOfBoundsException e) {
672             throw new InvalidNormalizedNodeStreamException("Invalid QName reference " + index, e);
673         }
674     }
675
676     private @NonNull QName lookupQName(final int index) throws InvalidNormalizedNodeStreamException {
677         return lookupNodeIdentifier(index).getNodeType();
678     }
679
680     private @NonNull String lookupString(final int index) throws InvalidNormalizedNodeStreamException {
681         try {
682             return codedStrings.get(index);
683         } catch (IndexOutOfBoundsException e) {
684             throw new InvalidNormalizedNodeStreamException("Invalid String reference " + index, e);
685         }
686     }
687
688     private @NonNull DOMSource readDOMSource() throws IOException {
689         final String str = readString();
690         try {
691             return new DOMSource(UntrustedXML.newDocumentBuilder().parse(new InputSource(new StringReader(str)))
692                 .getDocumentElement());
693         } catch (SAXException e) {
694             throw new IOException("Error parsing XML: " + str, e);
695         }
696     }
697
698     private @NonNull Object readLeafValue() throws IOException {
699         final byte type = input.readByte();
700         switch (type) {
701             case MagnesiumValue.BOOLEAN_FALSE:
702                 return Boolean.FALSE;
703             case MagnesiumValue.BOOLEAN_TRUE:
704                 return Boolean.TRUE;
705             case MagnesiumValue.EMPTY:
706                 return Empty.value();
707             case MagnesiumValue.INT8:
708                 return input.readByte();
709             case MagnesiumValue.INT8_0:
710                 return INT8_0;
711             case MagnesiumValue.INT16:
712                 return input.readShort();
713             case MagnesiumValue.INT16_0:
714                 return INT16_0;
715             case MagnesiumValue.INT32:
716                 return input.readInt();
717             case MagnesiumValue.INT32_0:
718                 return INT32_0;
719             case MagnesiumValue.INT32_2B:
720                 return input.readShort() & 0xFFFF;
721             case MagnesiumValue.INT64:
722                 return input.readLong();
723             case MagnesiumValue.INT64_0:
724                 return INT64_0;
725             case MagnesiumValue.INT64_4B:
726                 return input.readInt() & 0xFFFFFFFFL;
727             case MagnesiumValue.UINT8:
728                 return Uint8.fromByteBits(input.readByte());
729             case MagnesiumValue.UINT8_0:
730                 return Uint8.ZERO;
731             case MagnesiumValue.UINT16:
732                 return Uint16.fromShortBits(input.readShort());
733             case MagnesiumValue.UINT16_0:
734                 return Uint16.ZERO;
735             case MagnesiumValue.UINT32:
736                 return Uint32.fromIntBits(input.readInt());
737             case MagnesiumValue.UINT32_0:
738                 return Uint32.ZERO;
739             case MagnesiumValue.UINT32_2B:
740                 return Uint32.fromIntBits(input.readShort() & 0xFFFF);
741             case MagnesiumValue.UINT64:
742                 return Uint64.fromLongBits(input.readLong());
743             case MagnesiumValue.UINT64_0:
744                 return Uint64.ZERO;
745             case MagnesiumValue.UINT64_4B:
746                 return Uint64.fromLongBits(input.readInt() & 0xFFFFFFFFL);
747             case MagnesiumValue.BIGDECIMAL:
748                 // FIXME: use string -> BigDecimal cache
749                 return new BigDecimal(input.readUTF());
750             case MagnesiumValue.BIGINTEGER:
751                 return readBigInteger();
752             case MagnesiumValue.STRING_EMPTY:
753                 return "";
754             case MagnesiumValue.STRING_UTF:
755                 return input.readUTF();
756             case MagnesiumValue.STRING_2B:
757                 return readString2();
758             case MagnesiumValue.STRING_4B:
759                 return readString4();
760             case MagnesiumValue.STRING_CHARS:
761                 return readCharsString();
762             case MagnesiumValue.BINARY_0:
763                 return BINARY_0;
764             case MagnesiumValue.BINARY_1B:
765                 return readBinary(128 + input.readUnsignedByte());
766             case MagnesiumValue.BINARY_2B:
767                 return readBinary(384 + input.readUnsignedShort());
768             case MagnesiumValue.BINARY_4B:
769                 return readBinary(input.readInt());
770             case MagnesiumValue.YIID_0:
771                 return YangInstanceIdentifier.empty();
772             case MagnesiumValue.YIID:
773                 return readYangInstanceIdentifier(input.readInt());
774             case MagnesiumValue.QNAME:
775                 return decodeQName();
776             case MagnesiumValue.QNAME_REF_1B:
777                 return decodeQNameRef1();
778             case MagnesiumValue.QNAME_REF_2B:
779                 return decodeQNameRef2();
780             case MagnesiumValue.QNAME_REF_4B:
781                 return decodeQNameRef4();
782             case MagnesiumValue.BITS_0:
783                 return ImmutableSet.of();
784             case MagnesiumValue.BITS_1B:
785                 return readBits(input.readUnsignedByte() + 29);
786             case MagnesiumValue.BITS_2B:
787                 return readBits(input.readUnsignedShort() + 285);
788             case MagnesiumValue.BITS_4B:
789                 return readBits(input.readInt());
790
791             default:
792                 if (type > MagnesiumValue.BINARY_0 && type <= MagnesiumValue.BINARY_127) {
793                     return readBinary(type - MagnesiumValue.BINARY_0);
794                 } else if (type > MagnesiumValue.BITS_0 && type < MagnesiumValue.BITS_1B) {
795                     return readBits(type - MagnesiumValue.BITS_0);
796                 } else if (type > MagnesiumValue.YIID_0) {
797                     // Note 'byte' is range limited, so it is always '&& type <= MagnesiumValue.YIID_31'
798                     return readYangInstanceIdentifier(type - MagnesiumValue.YIID_0);
799                 } else {
800                     throw new InvalidNormalizedNodeStreamException("Invalid value type " + type);
801                 }
802         }
803     }
804
805     abstract @NonNull BigInteger readBigInteger() throws IOException;
806
807     private byte @NonNull [] readBinary(final int size) throws IOException {
808         if (size > 0) {
809             final byte[] ret = new byte[size];
810             input.readFully(ret);
811             return ret;
812         } else if (size == 0) {
813             return BINARY_0;
814         } else {
815             throw new InvalidNormalizedNodeStreamException("Invalid binary length " + size);
816         }
817     }
818
819     private @NonNull ImmutableSet<String> readBits(final int size) throws IOException {
820         if (size > 0) {
821             final ImmutableSet.Builder<String> builder = ImmutableSet.builder();
822             for (int i = 0; i < size; ++i) {
823                 builder.add(readRefString());
824             }
825             return builder.build();
826         } else if (size == 0) {
827             return ImmutableSet.of();
828         } else {
829             throw new InvalidNormalizedNodeStreamException("Invalid bits length " + size);
830         }
831     }
832
833     private static byte mask(final byte header, final byte mask) {
834         return (byte) (header & mask);
835     }
836
837     private static int rshift(final byte header, final byte shift) {
838         return (header & 0xFF) >>> shift;
839     }
840 }