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