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