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