Remove MountPointIdentifier
[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.BigInteger;
23 import java.nio.charset.StandardCharsets;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.concurrent.ExecutionException;
27 import javax.xml.transform.dom.DOMSource;
28 import org.eclipse.jdt.annotation.NonNull;
29 import org.opendaylight.yangtools.concepts.Either;
30 import org.opendaylight.yangtools.util.xml.UntrustedXML;
31 import org.opendaylight.yangtools.yang.common.Decimal64;
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 nip)) {
168                 throw new InvalidNormalizedNodeStreamException("Invalid predicate leaf " + identifier + " in parent "
169                         + parent);
170             }
171
172             value = nip.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 = switch (mask(nodeHeader, MagnesiumNode.PREDICATE_MASK)) {
245             case MagnesiumNode.PREDICATE_ZERO -> 0;
246             case MagnesiumNode.PREDICATE_ONE -> 1;
247             case MagnesiumNode.PREDICATE_1B -> input.readUnsignedByte();
248             case MagnesiumNode.PREDICATE_4B -> input.readInt();
249             default ->
250                 // ISE on purpose: this should never ever happen
251                 throw new IllegalStateException("Failed to decode NodeIdentifierWithPredicates size from header "
252                     + nodeHeader);
253         };
254         final NodeIdentifierWithPredicates identifier = readNodeIdentifierWithPredicates(nodeId.getNodeType(), size);
255         LOG.trace("Streaming map entry node {}", identifier);
256         writer.startMapEntryNode(identifier, UNKNOWN_SIZE);
257         commonStreamContainer(writer, identifier);
258     }
259
260     private void commonStreamContainer(final NormalizedNodeStreamWriter writer, final PathArgument parent)
261             throws IOException {
262         for (byte nodeType = input.readByte(); nodeType != MagnesiumNode.NODE_END; nodeType = input.readByte()) {
263             streamNormalizedNode(writer, parent, nodeType);
264         }
265         writer.endNode();
266     }
267
268     private @NonNull NodeIdentifier decodeNodeIdentifier() throws IOException {
269         final QNameModule module = decodeQNameModule();
270         final String localName = readRefString();
271         final NodeIdentifier nodeId;
272         try {
273             nodeId = QNameFactory.getNodeIdentifier(module, localName);
274         } catch (ExecutionException e) {
275             throw new InvalidNormalizedNodeStreamException("Illegal QName module=" + module + " localName="
276                     + localName, e);
277         }
278
279         codedNodeIdentifiers.add(nodeId);
280         return nodeId;
281     }
282
283     private NodeIdentifier decodeNodeIdentifier(final byte nodeHeader) throws IOException {
284         return decodeNodeIdentifier(nodeHeader, null);
285     }
286
287     private NodeIdentifier decodeNodeIdentifier(final byte nodeHeader, final PathArgument parent) throws IOException {
288         final int index;
289         switch (nodeHeader & MagnesiumNode.ADDR_MASK) {
290             case MagnesiumNode.ADDR_DEFINE:
291                 return readNodeIdentifier();
292             case MagnesiumNode.ADDR_LOOKUP_1B:
293                 index = input.readUnsignedByte();
294                 break;
295             case MagnesiumNode.ADDR_LOOKUP_4B:
296                 index = input.readInt();
297                 break;
298             case MagnesiumNode.ADDR_PARENT:
299                 if (parent instanceof NodeIdentifier nid) {
300                     return nid;
301                 }
302                 throw new InvalidNormalizedNodeStreamException("Invalid node identifier reference to parent " + parent);
303             default:
304                 throw new InvalidNormalizedNodeStreamException("Unexpected node identifier addressing in header "
305                         + nodeHeader);
306         }
307
308         try {
309             return codedNodeIdentifiers.get(index);
310         } catch (IndexOutOfBoundsException e) {
311             throw new InvalidNormalizedNodeStreamException("Invalid QName reference " + index, e);
312         }
313     }
314
315     private AugmentationIdentifier decodeAugmentationIdentifier(final byte nodeHeader) throws IOException {
316         final int index;
317         switch (nodeHeader & MagnesiumNode.ADDR_MASK) {
318             case MagnesiumNode.ADDR_DEFINE:
319                 return readAugmentationIdentifier();
320             case MagnesiumNode.ADDR_LOOKUP_1B:
321                 index = input.readUnsignedByte();
322                 break;
323             case MagnesiumNode.ADDR_LOOKUP_4B:
324                 index = input.readInt();
325                 break;
326             default:
327                 throw new InvalidNormalizedNodeStreamException(
328                     "Unexpected augmentation identifier addressing in header " + nodeHeader);
329         }
330
331         try {
332             return codedAugments.get(index);
333         } catch (IndexOutOfBoundsException e) {
334             throw new InvalidNormalizedNodeStreamException("Invalid augmentation identifier reference " + index, e);
335         }
336     }
337
338     @Override
339     public final YangInstanceIdentifier readYangInstanceIdentifier() throws IOException {
340         final byte type = input.readByte();
341         if (type == MagnesiumValue.YIID) {
342             return readYangInstanceIdentifier(input.readInt());
343         } else if (type >= MagnesiumValue.YIID_0) {
344             // Note 'byte' is range limited, so it is always '&& type <= MagnesiumValue.YIID_31'
345             return readYangInstanceIdentifier(type - MagnesiumValue.YIID_0);
346         } else {
347             throw new InvalidNormalizedNodeStreamException("Unexpected YangInstanceIdentifier type " + type);
348         }
349     }
350
351     private @NonNull YangInstanceIdentifier readYangInstanceIdentifier(final int size) throws IOException {
352         if (size > 0) {
353             final Builder<PathArgument> builder = ImmutableList.builderWithExpectedSize(size);
354             for (int i = 0; i < size; ++i) {
355                 builder.add(readPathArgument());
356             }
357             return YangInstanceIdentifier.create(builder.build());
358         } else if (size == 0) {
359             return YangInstanceIdentifier.empty();
360         } else {
361             throw new InvalidNormalizedNodeStreamException("Invalid YangInstanceIdentifier size " + size);
362         }
363     }
364
365     @Override
366     public final QName readQName() throws IOException {
367         final byte type = input.readByte();
368         return switch (type) {
369             case MagnesiumValue.QNAME -> decodeQName();
370             case MagnesiumValue.QNAME_REF_1B -> decodeQNameRef1();
371             case MagnesiumValue.QNAME_REF_2B -> decodeQNameRef2();
372             case MagnesiumValue.QNAME_REF_4B -> decodeQNameRef4();
373             default -> throw new InvalidNormalizedNodeStreamException("Unexpected QName type " + type);
374         };
375     }
376
377     @Override
378     @Deprecated(since = "11.0.0", forRemoval = true)
379     public final Either<PathArgument, LegacyPathArgument> readLegacyPathArgument() throws IOException {
380         final byte header = input.readByte();
381         return switch (header & MagnesiumPathArgument.TYPE_MASK) {
382             case MagnesiumPathArgument.AUGMENTATION_IDENTIFIER -> Either.ofFirst(readAugmentationIdentifier(header));
383             case MagnesiumPathArgument.NODE_IDENTIFIER -> {
384                 verifyPathIdentifierOnly(header);
385                 yield Either.ofFirst(readNodeIdentifier(header));
386             }
387             case MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES ->
388                 Either.ofFirst(readNodeIdentifierWithPredicates(header));
389             case MagnesiumPathArgument.NODE_WITH_VALUE -> {
390                 verifyPathIdentifierOnly(header);
391                 yield Either.ofFirst(readNodeWithValue(header));
392             }
393             case MagnesiumPathArgument.MOUNTPOINT_IDENTIFIER -> {
394                 verifyPathIdentifierOnly(header);
395                 yield Either.ofSecond(new LegacyMountPointIdentifier(readNodeIdentifier(header).getNodeType()));
396             }
397             default -> throw new InvalidNormalizedNodeStreamException("Unexpected PathArgument header " + header);
398         };
399     }
400
401     private AugmentationIdentifier readAugmentationIdentifier() throws IOException {
402         final AugmentationIdentifier result = readAugmentationIdentifier(input.readInt());
403         codedAugments.add(result);
404         return result;
405     }
406
407     private @NonNull AugmentationIdentifier readAugmentationIdentifier(final byte header) throws IOException {
408         final byte count = mask(header, MagnesiumPathArgument.AID_COUNT_MASK);
409         return switch (count) {
410             case MagnesiumPathArgument.AID_COUNT_1B -> readAugmentationIdentifier(input.readUnsignedByte());
411             case MagnesiumPathArgument.AID_COUNT_2B -> readAugmentationIdentifier(input.readUnsignedShort());
412             case MagnesiumPathArgument.AID_COUNT_4B -> readAugmentationIdentifier(input.readInt());
413             default -> readAugmentationIdentifier(rshift(count, MagnesiumPathArgument.AID_COUNT_SHIFT));
414         };
415     }
416
417     private @NonNull AugmentationIdentifier readAugmentationIdentifier(final int size) throws IOException {
418         if (size > 0) {
419             final List<QName> qnames = new ArrayList<>(size);
420             for (int i = 0; i < size; ++i) {
421                 qnames.add(readQName());
422             }
423             return AugmentationIdentifier.create(ImmutableSet.copyOf(qnames));
424         } else if (size == 0) {
425             return EMPTY_AID;
426         } else {
427             throw new InvalidNormalizedNodeStreamException("Invalid augmentation identifier size " + size);
428         }
429     }
430
431     private @NonNull NodeIdentifier readNodeIdentifier() throws IOException {
432         return decodeNodeIdentifier();
433     }
434
435     private @NonNull NodeIdentifier readNodeIdentifier(final byte header) throws IOException {
436         return switch (header & MagnesiumPathArgument.QNAME_MASK) {
437             case MagnesiumPathArgument.QNAME_DEF -> decodeNodeIdentifier();
438             case MagnesiumPathArgument.QNAME_REF_1B -> decodeNodeIdentifierRef1();
439             case MagnesiumPathArgument.QNAME_REF_2B -> decodeNodeIdentifierRef2();
440             case MagnesiumPathArgument.QNAME_REF_4B -> decodeNodeIdentifierRef4();
441             default -> throw new InvalidNormalizedNodeStreamException("Invalid QName coding in " + header);
442         };
443     }
444
445     private @NonNull  NodeIdentifierWithPredicates readNodeIdentifierWithPredicates(final byte header)
446             throws IOException {
447         final QName qname = readNodeIdentifier(header).getNodeType();
448         return switch (mask(header, MagnesiumPathArgument.SIZE_MASK)) {
449             case MagnesiumPathArgument.SIZE_1B -> readNodeIdentifierWithPredicates(qname, input.readUnsignedByte());
450             case MagnesiumPathArgument.SIZE_2B -> readNodeIdentifierWithPredicates(qname, input.readUnsignedShort());
451             case MagnesiumPathArgument.SIZE_4B -> readNodeIdentifierWithPredicates(qname, input.readInt());
452             default -> readNodeIdentifierWithPredicates(qname, rshift(header, MagnesiumPathArgument.SIZE_SHIFT));
453         };
454     }
455
456     private @NonNull NodeIdentifierWithPredicates readNodeIdentifierWithPredicates(final QName qname, final int size)
457             throws IOException {
458         if (size == 1) {
459             return NodeIdentifierWithPredicates.of(qname, readQName(), readLeafValue());
460         } else if (size > 1) {
461             final ImmutableMap.Builder<QName, Object> builder = ImmutableMap.builderWithExpectedSize(size);
462             for (int i = 0; i < size; ++i) {
463                 builder.put(readQName(), readLeafValue());
464             }
465             return NodeIdentifierWithPredicates.of(qname, builder.build());
466         } else if (size == 0) {
467             return NodeIdentifierWithPredicates.of(qname);
468         } else {
469             throw new InvalidNormalizedNodeStreamException("Invalid predicate count " + size);
470         }
471     }
472
473     private @NonNull NodeWithValue<?> readNodeWithValue(final byte header) throws IOException {
474         final QName qname = readNodeIdentifier(header).getNodeType();
475         return new NodeWithValue<>(qname, readLeafValue());
476     }
477
478     private static void verifyPathIdentifierOnly(final byte header) throws InvalidNormalizedNodeStreamException {
479         if (mask(header, MagnesiumPathArgument.SIZE_MASK) != 0) {
480             throw new InvalidNormalizedNodeStreamException("Invalid path argument header " + header);
481         }
482     }
483
484     private @NonNull NodeIdentifier decodeNodeIdentifierRef1() throws IOException {
485         return lookupNodeIdentifier(input.readUnsignedByte());
486     }
487
488     private @NonNull NodeIdentifier decodeNodeIdentifierRef2() throws IOException {
489         return lookupNodeIdentifier(input.readUnsignedShort() + 256);
490     }
491
492     private @NonNull NodeIdentifier decodeNodeIdentifierRef4() throws IOException {
493         return lookupNodeIdentifier(input.readInt());
494     }
495
496     private @NonNull QName decodeQName() throws IOException {
497         return decodeNodeIdentifier().getNodeType();
498     }
499
500     private @NonNull QName decodeQNameRef1() throws IOException {
501         return lookupQName(input.readUnsignedByte());
502     }
503
504     private @NonNull QName decodeQNameRef2() throws IOException {
505         return lookupQName(input.readUnsignedShort() + 256);
506     }
507
508     private @NonNull QName decodeQNameRef4() throws IOException {
509         return lookupQName(input.readInt());
510     }
511
512     private @NonNull QNameModule decodeQNameModule() throws IOException {
513         final byte type = input.readByte();
514         final int index;
515         switch (type) {
516             case MagnesiumValue.MODREF_1B:
517                 index = input.readUnsignedByte();
518                 break;
519             case MagnesiumValue.MODREF_2B:
520                 index = input.readUnsignedShort() + 256;
521                 break;
522             case MagnesiumValue.MODREF_4B:
523                 index = input.readInt();
524                 break;
525             default:
526                 return decodeQNameModuleDef(type);
527         }
528
529         try {
530             return codedModules.get(index);
531         } catch (IndexOutOfBoundsException e) {
532             throw new InvalidNormalizedNodeStreamException("Invalid QNameModule reference " + index, e);
533         }
534     }
535
536     // QNameModule definition, i.e. two encoded strings
537     private @NonNull QNameModule decodeQNameModuleDef(final byte type) throws IOException {
538         final String namespace = readRefString(type);
539
540         final byte refType = input.readByte();
541         final String revision = refType == MagnesiumValue.STRING_EMPTY ? null : readRefString(refType);
542         final QNameModule module;
543         try {
544             module = QNameFactory.createModule(namespace, revision);
545         } catch (UncheckedExecutionException e) {
546             throw new InvalidNormalizedNodeStreamException("Illegal QNameModule ns=" + namespace + " rev=" + revision,
547                 e);
548         }
549
550         codedModules.add(module);
551         return module;
552     }
553
554     private @NonNull String readRefString() throws IOException {
555         return readRefString(input.readByte());
556     }
557
558     private @NonNull String readRefString(final byte type) throws IOException {
559         final String str;
560         switch (type) {
561             case MagnesiumValue.STRING_REF_1B:
562                 return lookupString(input.readUnsignedByte());
563             case MagnesiumValue.STRING_REF_2B:
564                 return lookupString(input.readUnsignedShort() + 256);
565             case MagnesiumValue.STRING_REF_4B:
566                 return lookupString(input.readInt());
567             case MagnesiumValue.STRING_EMPTY:
568                 return "";
569             case MagnesiumValue.STRING_2B:
570                 str = readString2();
571                 break;
572             case MagnesiumValue.STRING_4B:
573                 str = readString4();
574                 break;
575             case MagnesiumValue.STRING_CHARS:
576                 str = readCharsString();
577                 break;
578             case MagnesiumValue.STRING_UTF:
579                 str = input.readUTF();
580                 break;
581             default:
582                 throw new InvalidNormalizedNodeStreamException("Unexpected String type " + type);
583         }
584
585         // TODO: consider interning Strings -- that would help with bits, but otherwise it's probably not worth it
586         codedStrings.add(verifyNotNull(str));
587         return str;
588     }
589
590     private @NonNull String readString() throws IOException {
591         final byte type = input.readByte();
592         return switch (type) {
593             case MagnesiumValue.STRING_EMPTY -> "";
594             case MagnesiumValue.STRING_UTF -> input.readUTF();
595             case MagnesiumValue.STRING_2B -> readString2();
596             case MagnesiumValue.STRING_4B -> readString4();
597             case MagnesiumValue.STRING_CHARS -> readCharsString();
598             default -> throw new InvalidNormalizedNodeStreamException("Unexpected String type " + type);
599         };
600     }
601
602     private @NonNull String readString2() throws IOException {
603         return readByteString(input.readUnsignedShort());
604     }
605
606     private @NonNull String readString4() throws IOException {
607         return readByteString(input.readInt());
608     }
609
610     private @NonNull String readByteString(final int size) throws IOException {
611         if (size > 0) {
612             final byte[] bytes = new byte[size];
613             input.readFully(bytes);
614             return new String(bytes, StandardCharsets.UTF_8);
615         } else if (size == 0) {
616             return "";
617         } else {
618             throw new InvalidNormalizedNodeStreamException("Invalid String bytes length " + size);
619         }
620     }
621
622     private @NonNull String readCharsString() throws IOException {
623         final int size = input.readInt();
624         if (size > 0) {
625             final char[] chars = new char[size];
626             for (int i = 0; i < size; ++i) {
627                 chars[i] = input.readChar();
628             }
629             return String.valueOf(chars);
630         } else if (size == 0) {
631             return "";
632         } else {
633             throw new InvalidNormalizedNodeStreamException("Invalid String chars length " + size);
634         }
635     }
636
637     private @NonNull NodeIdentifier lookupNodeIdentifier(final int index) throws InvalidNormalizedNodeStreamException {
638         try {
639             return codedNodeIdentifiers.get(index);
640         } catch (IndexOutOfBoundsException e) {
641             throw new InvalidNormalizedNodeStreamException("Invalid QName reference " + index, e);
642         }
643     }
644
645     private @NonNull QName lookupQName(final int index) throws InvalidNormalizedNodeStreamException {
646         return lookupNodeIdentifier(index).getNodeType();
647     }
648
649     private @NonNull String lookupString(final int index) throws InvalidNormalizedNodeStreamException {
650         try {
651             return codedStrings.get(index);
652         } catch (IndexOutOfBoundsException e) {
653             throw new InvalidNormalizedNodeStreamException("Invalid String reference " + index, e);
654         }
655     }
656
657     private @NonNull DOMSource readDOMSource() throws IOException {
658         final String str = readString();
659         try {
660             return new DOMSource(UntrustedXML.newDocumentBuilder().parse(new InputSource(new StringReader(str)))
661                 .getDocumentElement());
662         } catch (SAXException e) {
663             throw new IOException("Error parsing XML: " + str, e);
664         }
665     }
666
667     private @NonNull Object readLeafValue() throws IOException {
668         final byte type = input.readByte();
669         switch (type) {
670             case MagnesiumValue.BOOLEAN_FALSE:
671                 return Boolean.FALSE;
672             case MagnesiumValue.BOOLEAN_TRUE:
673                 return Boolean.TRUE;
674             case MagnesiumValue.EMPTY:
675                 return Empty.value();
676             case MagnesiumValue.INT8:
677                 return input.readByte();
678             case MagnesiumValue.INT8_0:
679                 return INT8_0;
680             case MagnesiumValue.INT16:
681                 return input.readShort();
682             case MagnesiumValue.INT16_0:
683                 return INT16_0;
684             case MagnesiumValue.INT32:
685                 return input.readInt();
686             case MagnesiumValue.INT32_0:
687                 return INT32_0;
688             case MagnesiumValue.INT32_2B:
689                 return input.readShort() & 0xFFFF;
690             case MagnesiumValue.INT64:
691                 return input.readLong();
692             case MagnesiumValue.INT64_0:
693                 return INT64_0;
694             case MagnesiumValue.INT64_4B:
695                 return input.readInt() & 0xFFFFFFFFL;
696             case MagnesiumValue.UINT8:
697                 return Uint8.fromByteBits(input.readByte());
698             case MagnesiumValue.UINT8_0:
699                 return Uint8.ZERO;
700             case MagnesiumValue.UINT16:
701                 return Uint16.fromShortBits(input.readShort());
702             case MagnesiumValue.UINT16_0:
703                 return Uint16.ZERO;
704             case MagnesiumValue.UINT32:
705                 return Uint32.fromIntBits(input.readInt());
706             case MagnesiumValue.UINT32_0:
707                 return Uint32.ZERO;
708             case MagnesiumValue.UINT32_2B:
709                 return Uint32.fromIntBits(input.readShort() & 0xFFFF);
710             case MagnesiumValue.UINT64:
711                 return Uint64.fromLongBits(input.readLong());
712             case MagnesiumValue.UINT64_0:
713                 return Uint64.ZERO;
714             case MagnesiumValue.UINT64_4B:
715                 return Uint64.fromLongBits(input.readInt() & 0xFFFFFFFFL);
716             case MagnesiumValue.BIGDECIMAL:
717                 // FIXME: use string -> BigDecimal cache
718                 return Decimal64.valueOf(input.readUTF());
719             case MagnesiumValue.BIGINTEGER:
720                 return readBigInteger();
721             case MagnesiumValue.STRING_EMPTY:
722                 return "";
723             case MagnesiumValue.STRING_UTF:
724                 return input.readUTF();
725             case MagnesiumValue.STRING_2B:
726                 return readString2();
727             case MagnesiumValue.STRING_4B:
728                 return readString4();
729             case MagnesiumValue.STRING_CHARS:
730                 return readCharsString();
731             case MagnesiumValue.BINARY_0:
732                 return BINARY_0;
733             case MagnesiumValue.BINARY_1B:
734                 return readBinary(128 + input.readUnsignedByte());
735             case MagnesiumValue.BINARY_2B:
736                 return readBinary(384 + input.readUnsignedShort());
737             case MagnesiumValue.BINARY_4B:
738                 return readBinary(input.readInt());
739             case MagnesiumValue.YIID_0:
740                 return YangInstanceIdentifier.empty();
741             case MagnesiumValue.YIID:
742                 return readYangInstanceIdentifier(input.readInt());
743             case MagnesiumValue.QNAME:
744                 return decodeQName();
745             case MagnesiumValue.QNAME_REF_1B:
746                 return decodeQNameRef1();
747             case MagnesiumValue.QNAME_REF_2B:
748                 return decodeQNameRef2();
749             case MagnesiumValue.QNAME_REF_4B:
750                 return decodeQNameRef4();
751             case MagnesiumValue.BITS_0:
752                 return ImmutableSet.of();
753             case MagnesiumValue.BITS_1B:
754                 return readBits(input.readUnsignedByte() + 29);
755             case MagnesiumValue.BITS_2B:
756                 return readBits(input.readUnsignedShort() + 285);
757             case MagnesiumValue.BITS_4B:
758                 return readBits(input.readInt());
759
760             default:
761                 if (type > MagnesiumValue.BINARY_0 && type <= MagnesiumValue.BINARY_127) {
762                     return readBinary(type - MagnesiumValue.BINARY_0);
763                 } else if (type > MagnesiumValue.BITS_0 && type < MagnesiumValue.BITS_1B) {
764                     return readBits(type - MagnesiumValue.BITS_0);
765                 } else if (type > MagnesiumValue.YIID_0) {
766                     // Note 'byte' is range limited, so it is always '&& type <= MagnesiumValue.YIID_31'
767                     return readYangInstanceIdentifier(type - MagnesiumValue.YIID_0);
768                 } else {
769                     throw new InvalidNormalizedNodeStreamException("Invalid value type " + type);
770                 }
771         }
772     }
773
774     abstract @NonNull BigInteger readBigInteger() throws IOException;
775
776     private byte @NonNull [] readBinary(final int size) throws IOException {
777         if (size > 0) {
778             final byte[] ret = new byte[size];
779             input.readFully(ret);
780             return ret;
781         } else if (size == 0) {
782             return BINARY_0;
783         } else {
784             throw new InvalidNormalizedNodeStreamException("Invalid binary length " + size);
785         }
786     }
787
788     private @NonNull ImmutableSet<String> readBits(final int size) throws IOException {
789         if (size > 0) {
790             final ImmutableSet.Builder<String> builder = ImmutableSet.builder();
791             for (int i = 0; i < size; ++i) {
792                 builder.add(readRefString());
793             }
794             return builder.build();
795         } else if (size == 0) {
796             return ImmutableSet.of();
797         } else {
798             throw new InvalidNormalizedNodeStreamException("Invalid bits length " + size);
799         }
800     }
801
802     private static byte mask(final byte header, final byte mask) {
803         return (byte) (header & mask);
804     }
805
806     private static int rshift(final byte header, final byte shift) {
807         return (header & 0xFF) >>> shift;
808     }
809 }