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