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