2 * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.data.codec.binfmt;
10 import static com.google.common.base.Preconditions.checkArgument;
12 import java.io.DataOutput;
13 import java.io.IOException;
14 import java.io.StringWriter;
15 import java.math.BigDecimal;
16 import java.math.BigInteger;
17 import java.nio.charset.StandardCharsets;
18 import java.util.ArrayDeque;
19 import java.util.Deque;
20 import java.util.HashMap;
21 import java.util.List;
23 import java.util.Map.Entry;
24 import java.util.Optional;
26 import javax.xml.transform.TransformerException;
27 import javax.xml.transform.TransformerFactory;
28 import javax.xml.transform.dom.DOMSource;
29 import javax.xml.transform.stream.StreamResult;
30 import org.eclipse.jdt.annotation.NonNull;
31 import org.opendaylight.yangtools.rfc8528.data.api.MountPointIdentifier;
32 import org.opendaylight.yangtools.yang.common.Decimal64;
33 import org.opendaylight.yangtools.yang.common.Empty;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.common.QNameModule;
36 import org.opendaylight.yangtools.yang.common.Revision;
37 import org.opendaylight.yangtools.yang.common.Uint16;
38 import org.opendaylight.yangtools.yang.common.Uint32;
39 import org.opendaylight.yangtools.yang.common.Uint64;
40 import org.opendaylight.yangtools.yang.common.Uint8;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
46 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * Abstract base class for NormalizedNodeDataOutput based on {@link MagnesiumNode}, {@link MagnesiumPathArgument} and
52 * {@link MagnesiumValue}.
54 abstract class AbstractMagnesiumDataOutput extends AbstractNormalizedNodeDataOutput {
55 private static final Logger LOG = LoggerFactory.getLogger(AbstractMagnesiumDataOutput.class);
57 // Marker for encoding state when we have entered startLeafNode() within a startMapEntry() and that leaf corresponds
58 // to a key carried within NodeIdentifierWithPredicates.
59 private static final Object KEY_LEAF_STATE = new Object();
60 // Marker for nodes which have simple content and do not use END_NODE marker to terminate
61 private static final Object NO_ENDNODE_STATE = new Object();
63 private static final TransformerFactory TF = TransformerFactory.newInstance();
66 * Stack tracking encoding state. In general we track the node identifier of the currently-open element, but there
67 * are a few other circumstances where we push other objects. See {@link #KEY_LEAF_STATE} and
68 * {@link #NO_ENDNODE_STATE}.
70 private final Deque<Object> stack = new ArrayDeque<>();
73 private final Map<AugmentationIdentifier, Integer> aidCodeMap = new HashMap<>();
74 private final Map<QNameModule, Integer> moduleCodeMap = new HashMap<>();
75 private final Map<String, Integer> stringCodeMap = new HashMap<>();
76 private final Map<QName, Integer> qnameCodeMap = new HashMap<>();
78 AbstractMagnesiumDataOutput(final DataOutput output) {
83 public final void startLeafNode(final NodeIdentifier name) throws IOException {
84 final Object current = stack.peek();
85 if (current instanceof NodeIdentifierWithPredicates) {
86 final QName qname = name.getNodeType();
87 if (((NodeIdentifierWithPredicates) current).containsKey(qname)) {
88 writeQNameNode(MagnesiumNode.NODE_LEAF | MagnesiumNode.PREDICATE_ONE, qname);
89 stack.push(KEY_LEAF_STATE);
94 startSimpleNode(MagnesiumNode.NODE_LEAF, name);
98 public final void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
99 startQNameNode(MagnesiumNode.NODE_LEAFSET, name);
103 public final void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
104 startQNameNode(MagnesiumNode.NODE_LEAFSET_ORDERED, name);
108 public final void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
109 if (matchesParentQName(name.getNodeType())) {
110 output.writeByte(MagnesiumNode.NODE_LEAFSET_ENTRY);
111 stack.push(NO_ENDNODE_STATE);
113 startSimpleNode(MagnesiumNode.NODE_LEAFSET_ENTRY, name);
118 public final void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
119 startQNameNode(MagnesiumNode.NODE_CONTAINER, name);
123 public final void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
124 startQNameNode(MagnesiumNode.NODE_LIST, name);
128 public final void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
129 startInheritedNode(MagnesiumNode.NODE_LIST_ENTRY, name);
133 public final void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
134 startQNameNode(MagnesiumNode.NODE_MAP, name);
138 public final void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
140 final int size = identifier.size();
142 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_ONE), identifier);
143 } else if (size == 0) {
144 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_ZERO), identifier);
145 } else if (size < 256) {
146 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_1B), identifier);
147 output.writeByte(size);
149 startInheritedNode((byte) (MagnesiumNode.NODE_MAP_ENTRY | MagnesiumNode.PREDICATE_4B), identifier);
150 output.writeInt(size);
153 writePredicates(identifier);
157 public final void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
158 startQNameNode(MagnesiumNode.NODE_MAP_ORDERED, name);
162 public final void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
163 startQNameNode(MagnesiumNode.NODE_CHOICE, name);
167 public final void startAugmentationNode(final AugmentationIdentifier identifier) throws IOException {
168 final Integer code = aidCodeMap.get(identifier);
170 aidCodeMap.put(identifier, aidCodeMap.size());
171 output.writeByte(MagnesiumNode.NODE_AUGMENTATION | MagnesiumNode.ADDR_DEFINE);
172 final Set<QName> qnames = identifier.getPossibleChildNames();
173 output.writeInt(qnames.size());
174 for (QName qname : qnames) {
175 writeQNameInternal(qname);
178 writeNodeType(MagnesiumNode.NODE_AUGMENTATION, code);
180 stack.push(identifier);
184 public final boolean startAnyxmlNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
185 if (DOMSource.class.isAssignableFrom(objectModel)) {
186 startSimpleNode(MagnesiumNode.NODE_ANYXML, name);
193 public final void domSourceValue(final DOMSource value) throws IOException {
194 final StringWriter writer = new StringWriter();
196 TF.newTransformer().transform(value, new StreamResult(writer));
197 } catch (TransformerException e) {
198 throw new IOException("Error writing anyXml", e);
200 writeValue(writer.toString());
204 public final void endNode() throws IOException {
205 if (stack.pop() instanceof PathArgument) {
206 output.writeByte(MagnesiumNode.NODE_END);
211 public final void scalarValue(final Object value) throws IOException {
212 if (KEY_LEAF_STATE.equals(stack.peek())) {
213 LOG.trace("Inside a map entry key leaf, not emitting value {}", value);
220 final void writeQNameInternal(final QName qname) throws IOException {
221 final Integer code = qnameCodeMap.get(qname);
223 output.writeByte(MagnesiumValue.QNAME);
231 final void writePathArgumentInternal(final PathArgument pathArgument) throws IOException {
232 if (pathArgument instanceof NodeIdentifier) {
233 writeNodeIdentifier((NodeIdentifier) pathArgument);
234 } else if (pathArgument instanceof NodeIdentifierWithPredicates) {
235 writeNodeIdentifierWithPredicates((NodeIdentifierWithPredicates) pathArgument);
236 } else if (pathArgument instanceof AugmentationIdentifier) {
237 writeAugmentationIdentifier((AugmentationIdentifier) pathArgument);
238 } else if (pathArgument instanceof NodeWithValue) {
239 writeNodeWithValue((NodeWithValue<?>) pathArgument);
240 } else if (pathArgument instanceof MountPointIdentifier) {
241 writeMountPointIdentifier((MountPointIdentifier) pathArgument);
243 throw new IOException("Unhandled PathArgument " + pathArgument);
247 private void writeAugmentationIdentifier(final AugmentationIdentifier identifier) throws IOException {
248 final Set<QName> qnames = identifier.getPossibleChildNames();
249 final int size = qnames.size();
251 output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER
252 | size << MagnesiumPathArgument.AID_COUNT_SHIFT);
253 } else if (size < 256) {
254 output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_1B);
255 output.writeByte(size);
256 } else if (size < 65536) {
257 output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_2B);
258 output.writeShort(size);
260 output.writeByte(MagnesiumPathArgument.AUGMENTATION_IDENTIFIER | MagnesiumPathArgument.AID_COUNT_4B);
261 output.writeInt(size);
264 for (QName qname : qnames) {
265 writeQNameInternal(qname);
269 private void writeNodeIdentifier(final NodeIdentifier identifier) throws IOException {
270 writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_IDENTIFIER);
273 private void writeMountPointIdentifier(final MountPointIdentifier identifier) throws IOException {
274 writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.MOUNTPOINT_IDENTIFIER);
277 private void writeNodeIdentifierWithPredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
278 final int size = identifier.size();
280 writePathArgumentQName(identifier.getNodeType(),
281 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES
282 | size << MagnesiumPathArgument.SIZE_SHIFT));
283 } else if (size < 256) {
284 writePathArgumentQName(identifier.getNodeType(),
285 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_1B));
286 output.writeByte(size);
287 } else if (size < 65536) {
288 writePathArgumentQName(identifier.getNodeType(),
289 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_2B));
290 output.writeShort(size);
292 writePathArgumentQName(identifier.getNodeType(),
293 (byte) (MagnesiumPathArgument.NODE_IDENTIFIER_WITH_PREDICATES | MagnesiumPathArgument.SIZE_4B));
294 output.writeInt(size);
297 writePredicates(identifier);
300 private void writePredicates(final NodeIdentifierWithPredicates identifier) throws IOException {
301 for (Entry<QName, Object> e : identifier.entrySet()) {
302 writeQNameInternal(e.getKey());
303 writeObject(e.getValue());
307 private void writeNodeWithValue(final NodeWithValue<?> identifier) throws IOException {
308 writePathArgumentQName(identifier.getNodeType(), MagnesiumPathArgument.NODE_WITH_VALUE);
309 writeObject(identifier.getValue());
312 private void writePathArgumentQName(final QName qname, final byte typeHeader) throws IOException {
313 final Integer code = qnameCodeMap.get(qname);
315 final int val = code;
317 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_1B);
318 output.writeByte(val);
319 } else if (val < 65792) {
320 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_2B);
321 output.writeShort(val - 256);
323 output.writeByte(typeHeader | MagnesiumPathArgument.QNAME_REF_4B);
324 output.writeInt(val);
327 // implied '| MagnesiumPathArgument.QNAME_DEF'
328 output.writeByte(typeHeader);
334 final void writeYangInstanceIdentifierInternal(final YangInstanceIdentifier identifier) throws IOException {
335 writeValue(identifier);
338 private void writeObject(final @NonNull Object value) throws IOException {
339 if (value instanceof String) {
340 writeValue((String) value);
341 } else if (value instanceof Boolean) {
342 writeValue((Boolean) value);
343 } else if (value instanceof Byte) {
344 writeValue((Byte) value);
345 } else if (value instanceof Short) {
346 writeValue((Short) value);
347 } else if (value instanceof Integer) {
348 writeValue((Integer) value);
349 } else if (value instanceof Long) {
350 writeValue((Long) value);
351 } else if (value instanceof Uint8) {
352 writeValue((Uint8) value);
353 } else if (value instanceof Uint16) {
354 writeValue((Uint16) value);
355 } else if (value instanceof Uint32) {
356 writeValue((Uint32) value);
357 } else if (value instanceof Uint64) {
358 writeValue((Uint64) value);
359 } else if (value instanceof QName) {
360 writeQNameInternal((QName) value);
361 } else if (value instanceof YangInstanceIdentifier) {
362 writeValue((YangInstanceIdentifier) value);
363 } else if (value instanceof byte[]) {
364 writeValue((byte[]) value);
365 } else if (value instanceof Empty) {
366 output.writeByte(MagnesiumValue.EMPTY);
367 } else if (value instanceof Set) {
368 writeValue((Set<?>) value);
369 } else if (value instanceof BigDecimal || value instanceof Decimal64) {
370 output.writeByte(MagnesiumValue.BIGDECIMAL);
371 output.writeUTF(value.toString());
372 } else if (value instanceof BigInteger) {
373 writeValue((BigInteger) value);
375 throw new IOException("Unhandled value type " + value.getClass());
379 private void writeValue(final boolean value) throws IOException {
380 output.writeByte(value ? MagnesiumValue.BOOLEAN_TRUE : MagnesiumValue.BOOLEAN_FALSE);
383 private void writeValue(final byte value) throws IOException {
385 output.writeByte(MagnesiumValue.INT8);
386 output.writeByte(value);
388 output.writeByte(MagnesiumValue.INT8_0);
392 private void writeValue(final short value) throws IOException {
394 output.writeByte(MagnesiumValue.INT16);
395 output.writeShort(value);
397 output.writeByte(MagnesiumValue.INT16_0);
401 private void writeValue(final int value) throws IOException {
402 if ((value & 0xFFFF0000) != 0) {
403 output.writeByte(MagnesiumValue.INT32);
404 output.writeInt(value);
405 } else if (value != 0) {
406 output.writeByte(MagnesiumValue.INT32_2B);
407 output.writeShort(value);
409 output.writeByte(MagnesiumValue.INT32_0);
413 private void writeValue(final long value) throws IOException {
414 if ((value & 0xFFFFFFFF00000000L) != 0) {
415 output.writeByte(MagnesiumValue.INT64);
416 output.writeLong(value);
417 } else if (value != 0) {
418 output.writeByte(MagnesiumValue.INT64_4B);
419 output.writeInt((int) value);
421 output.writeByte(MagnesiumValue.INT64_0);
425 private void writeValue(final Uint8 value) throws IOException {
426 final byte b = value.byteValue();
428 output.writeByte(MagnesiumValue.UINT8);
431 output.writeByte(MagnesiumValue.UINT8_0);
435 private void writeValue(final Uint16 value) throws IOException {
436 final short s = value.shortValue();
438 output.writeByte(MagnesiumValue.UINT16);
439 output.writeShort(s);
441 output.writeByte(MagnesiumValue.UINT16_0);
445 private void writeValue(final Uint32 value) throws IOException {
446 final int i = value.intValue();
447 if ((i & 0xFFFF0000) != 0) {
448 output.writeByte(MagnesiumValue.UINT32);
451 output.writeByte(MagnesiumValue.UINT32_2B);
452 output.writeShort(i);
454 output.writeByte(MagnesiumValue.UINT32_0);
458 private void writeValue(final Uint64 value) throws IOException {
459 final long l = value.longValue();
460 if ((l & 0xFFFFFFFF00000000L) != 0) {
461 output.writeByte(MagnesiumValue.UINT64);
464 output.writeByte(MagnesiumValue.UINT64_4B);
465 output.writeInt((int) l);
467 output.writeByte(MagnesiumValue.UINT64_0);
471 abstract void writeValue(BigInteger value) throws IOException;
473 private void writeValue(final String value) throws IOException {
474 if (value.isEmpty()) {
475 output.writeByte(MagnesiumValue.STRING_EMPTY);
476 } else if (value.length() <= Short.MAX_VALUE / 2) {
477 output.writeByte(MagnesiumValue.STRING_UTF);
478 output.writeUTF(value);
479 } else if (value.length() <= 1048576) {
480 final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
481 if (bytes.length < 65536) {
482 output.writeByte(MagnesiumValue.STRING_2B);
483 output.writeShort(bytes.length);
485 output.writeByte(MagnesiumValue.STRING_4B);
486 output.writeInt(bytes.length);
490 output.writeByte(MagnesiumValue.STRING_CHARS);
491 output.writeInt(value.length());
492 output.writeChars(value);
496 private void writeValue(final byte[] value) throws IOException {
497 if (value.length < 128) {
498 output.writeByte(MagnesiumValue.BINARY_0 + value.length);
499 } else if (value.length < 384) {
500 output.writeByte(MagnesiumValue.BINARY_1B);
501 output.writeByte(value.length - 128);
502 } else if (value.length < 65920) {
503 output.writeByte(MagnesiumValue.BINARY_2B);
504 output.writeShort(value.length - 384);
506 output.writeByte(MagnesiumValue.BINARY_4B);
507 output.writeInt(value.length);
512 private void writeValue(final YangInstanceIdentifier value) throws IOException {
513 final List<PathArgument> args = value.getPathArguments();
514 final int size = args.size();
516 output.writeByte(MagnesiumValue.YIID);
517 output.writeInt(size);
519 output.writeByte(MagnesiumValue.YIID_0 + size);
521 for (PathArgument arg : args) {
522 writePathArgumentInternal(arg);
526 private void writeValue(final Set<?> value) throws IOException {
527 final int size = value.size();
529 output.writeByte(MagnesiumValue.BITS_0 + size);
530 } else if (size < 285) {
531 output.writeByte(MagnesiumValue.BITS_1B);
532 output.writeByte(size - 29);
533 } else if (size < 65821) {
534 output.writeByte(MagnesiumValue.BITS_2B);
535 output.writeShort(size - 285);
537 output.writeByte(MagnesiumValue.BITS_4B);
538 output.writeInt(size);
541 for (Object bit : value) {
542 checkArgument(bit instanceof String, "Expected value type to be String but was %s", bit);
543 encodeString((String) bit);
547 // Check if the proposed QName matches the parent. This is only effective if the parent is identified by
548 // NodeIdentifier -- which is typically true
549 private boolean matchesParentQName(final QName qname) {
550 final Object current = stack.peek();
551 return current instanceof NodeIdentifier && qname.equals(((NodeIdentifier) current).getNodeType());
554 // Start an END_NODE-terminated node, which typically has a QName matching the parent. If that is the case we emit
555 // a parent reference instead of an explicit QName reference -- saving at least one byte
556 private void startInheritedNode(final byte type, final PathArgument name) throws IOException {
557 final QName qname = name.getNodeType();
558 if (matchesParentQName(qname)) {
561 writeQNameNode(type, qname);
566 // Start an END_NODE-terminated node, which needs its QName encoded
567 private void startQNameNode(final byte type, final PathArgument name) throws IOException {
568 writeQNameNode(type, name.getNodeType());
572 // Start a simple node, which is not terminated through END_NODE and encode its QName
573 private void startSimpleNode(final byte type, final PathArgument name) throws IOException {
574 writeQNameNode(type, name.getNodeType());
575 stack.push(NO_ENDNODE_STATE);
578 // Encode a QName-based (i.e. NodeIdentifier*) node with a particular QName. This will either result in a QName
579 // definition, or a reference, where this is encoded along with the node type.
580 private void writeQNameNode(final int type, final @NonNull QName qname) throws IOException {
581 final Integer code = qnameCodeMap.get(qname);
583 output.writeByte(type | MagnesiumNode.ADDR_DEFINE);
586 writeNodeType(type, code);
590 // Write a node type + lookup
591 private void writeNodeType(final int type, final int code) throws IOException {
593 output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_1B);
594 output.writeByte(code);
596 output.writeByte(type | MagnesiumNode.ADDR_LOOKUP_4B);
597 output.writeInt(code);
601 // Encode a QName using lookup tables, resuling either in a reference to an existing entry, or emitting two
603 private void encodeQName(final @NonNull QName qname) throws IOException {
604 final Integer prev = qnameCodeMap.put(qname, qnameCodeMap.size());
606 throw new IOException("Internal coding error: attempted to re-encode " + qname + "%s already encoded as "
610 final QNameModule module = qname.getModule();
611 final Integer code = moduleCodeMap.get(module);
613 moduleCodeMap.put(module, moduleCodeMap.size());
614 encodeString(module.getNamespace().toString());
615 final Optional<Revision> rev = module.getRevision();
616 if (rev.isPresent()) {
617 encodeString(rev.get().toString());
619 output.writeByte(MagnesiumValue.STRING_EMPTY);
622 writeModuleRef(code);
624 encodeString(qname.getLocalName());
627 // Encode a String using lookup tables, resulting either in a reference to an existing entry, or emitting as
629 private void encodeString(final @NonNull String str) throws IOException {
630 final Integer code = stringCodeMap.get(str);
634 stringCodeMap.put(str, stringCodeMap.size());
639 // Write a QName with a lookup table reference. This is a combination of asserting the value is a QName plus
640 // the effects of writeRef()
641 private void writeQNameRef(final int code) throws IOException {
642 final int val = code;
644 output.writeByte(MagnesiumValue.QNAME_REF_1B);
645 output.writeByte(val);
646 } else if (val < 65792) {
647 output.writeByte(MagnesiumValue.QNAME_REF_2B);
648 output.writeShort(val - 256);
650 output.writeByte(MagnesiumValue.QNAME_REF_4B);
651 output.writeInt(val);
655 // Write a lookup table reference, which table is being referenced is implied by the caller
656 private void writeRef(final int code) throws IOException {
657 final int val = code;
659 output.writeByte(MagnesiumValue.STRING_REF_1B);
660 output.writeByte(val);
661 } else if (val < 65792) {
662 output.writeByte(MagnesiumValue.STRING_REF_2B);
663 output.writeShort(val - 256);
665 output.writeByte(MagnesiumValue.STRING_REF_4B);
666 output.writeInt(val);
670 // Write a lookup module table reference, which table is being referenced is implied by the caller
671 private void writeModuleRef(final int code) throws IOException {
672 final int val = code;
674 output.writeByte(MagnesiumValue.MODREF_1B);
675 output.writeByte(val);
676 } else if (val < 65792) {
677 output.writeByte(MagnesiumValue.MODREF_2B);
678 output.writeShort(val - 256);
680 output.writeByte(MagnesiumValue.MODREF_4B);
681 output.writeInt(val);