This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new a3154ced05 Add binary-native opt-in token surface for CBOR and MsgPack.
a3154ced05 is described below

commit a3154ced05f944cbfc6103f4d812414dd4df14dc
Author: James Bognar <[email protected]>
AuthorDate: Tue Jun 16 08:13:13 2026 -0400

    Add binary-native opt-in token surface for CBOR and MsgPack.
    
    Exposes the binary-native value types of CBOR (semantic tags + simple 
values) and
    MsgPack (`ext`) as opt-in token-level metadata, while the default 
parseTokens /
    serializeTokens cursors stay byte-for-byte unchanged.
    
    - New `marshall.stream` role interfaces + `BinaryNativeKind` discriminant
      (NONE / CBOR_TAG / CBOR_SIMPLE / MSGPACK_EXT); 
`BinaryNativeTokenReader`/`Writer`
      with per-format flavor markers and narrowing cursor return types.
    - Explicit `parseNativeTokens(...)` / `serializeNativeTokens(...)` 
factories (not a flag
      on the default factories); native info surfaced via metadata accessors, 
no new TokenType.
    - Low-level `CborOutputStream.writeSimple` / `MsgPackOutputStream.writeExt` 
primitives plus
      flag-gated reader/writer support in both formats.
    - Tests: new CBOR/MsgPack native-token-stream suites + invariant + 
generic-consumer tests;
      `@SuppressWarnings("resource")` on the fluent output-stream test helpers 
(the discarded
      fluent return is the try-with-resources-managed stream, not a leak).
    
    Co-authored-by: Cursor <[email protected]>
---
 .../juneau/marshall/cbor/CborOutputStream.java     |  16 ++
 .../apache/juneau/marshall/cbor/CborParser.java    |  66 +++++
 .../juneau/marshall/cbor/CborParserSession.java    |   8 +-
 .../juneau/marshall/cbor/CborTokenReader.java      |  79 +++++-
 .../juneau/marshall/cbor/CborTokenWriter.java      |  19 ++
 .../marshall/msgpack/MsgPackOutputStream.java      |  29 +++
 .../juneau/marshall/msgpack/MsgPackParser.java     |  66 ++++-
 .../marshall/msgpack/MsgPackParserSession.java     |   8 +-
 .../marshall/msgpack/MsgPackTokenReader.java       |  61 ++++-
 .../marshall/msgpack/MsgPackTokenWriter.java       |  11 +
 .../juneau/marshall/stream/BinaryNativeKind.java   |  63 +++++
 .../apache/juneau/marshall/stream/TokenReader.java |  62 +++++
 .../apache/juneau/marshall/stream/TokenWriter.java |  55 +++++
 .../marshall/cbor/CborNativeTokenStream_Test.java  | 267 +++++++++++++++++++++
 .../marshall/cbor/CborOutputStream_Test.java       |  19 ++
 .../msgpack/MsgPackNativeTokenStream_Test.java     | 193 +++++++++++++++
 .../marshall/msgpack/MsgPackOutputStream_Test.java |  93 +++++++
 .../BinaryNativeKind_GenericConsumer_Test.java     | 119 +++++++++
 .../stream/FormatStreamingCapability_Test.java     |   1 +
 19 files changed, 1230 insertions(+), 5 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborOutputStream.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborOutputStream.java
index 2b747ebdbd..dffe7f686e 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborOutputStream.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborOutputStream.java
@@ -398,4 +398,20 @@ public class CborOutputStream extends OutputStream {
                writeHead(6, tagNumber);
                return this;
        }
+
+       /**
+        * Writes a CBOR simple value (major type 7).
+        *
+        * <p>
+        * Caller responsibility: reserved encodings (20-23 = 
bool/null/undefined; 25-27 =
+        * float16/32/64; 31 = break) collide with native scalar/structural 
emits and must not be used
+        * here.
+        *
+        * @param value The simple value (range {@code 0..255}; reserved values 
noted above).
+        * @return This stream.
+        */
+       CborOutputStream writeSimple(int value) {
+               writeHead(7, value);
+               return this;
+       }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborParser.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborParser.java
index 128b74f5cc..607e362ed8 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborParser.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborParser.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.marshall.cbor;
 
 import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.io.*;
 import java.math.*;
@@ -78,6 +79,9 @@ public class CborParser extends InputStreamParser implements 
CborMetaProvider, T
        // Argument name constants for assertArgNotNull
        private static final String ARG_copyFrom = "copyFrom";
 
+       // Property name constants
+       private static final String PROP_nativeMode = "nativeMode";
+
        /** Default parser, string input encoded as BASE64. */
        public static class Base64 extends CborParser {
 
@@ -98,11 +102,14 @@ public class CborParser extends InputStreamParser 
implements CborMetaProvider, T
 
                private static final Cache<HashKey,CborParser> CACHE = 
Cache.of(HashKey.class, CborParser.class).build();
 
+               private boolean nativeMode;
+
                /**
                 * Constructor, default settings.
                 */
                protected Builder() {
                        consumes("application/cbor");
+                       nativeMode = env("CborParser.nativeMode", false);
                }
 
                /**
@@ -112,6 +119,7 @@ public class CborParser extends InputStreamParser 
implements CborMetaProvider, T
                 */
                protected Builder(Builder copyFrom) {
                        super(assertArgNotNull(ARG_copyFrom, copyFrom));
+                       nativeMode = copyFrom.nativeMode;
                }
 
                /**
@@ -121,6 +129,39 @@ public class CborParser extends InputStreamParser 
implements CborMetaProvider, T
                 */
                protected Builder(CborParser copyFrom) {
                        super(assertArgNotNull(ARG_copyFrom, copyFrom));
+                       nativeMode = copyFrom.nativeMode;
+               }
+
+               /**
+                * Surfaces CBOR semantic tags and simple values as token-level 
metadata on
+                * {@link TokenReader} cursors returned by {@link 
CborParserSession#parseTokens(Object)}.
+                *
+                * <p>
+                * When enabled, tags accumulate on the cursor's tag stack 
(visible via
+                * {@link TokenReader#getTagCount()} / {@link 
TokenReader#getTag(int)}) and simple values
+                * surface their int via {@link TokenReader#getSimpleValue()} on
+                * {@link TokenType#VALUE_NULL} tokens.  When disabled 
(default), tags are silently
+                * unwrapped and simple values collapse to {@link 
TokenType#VALUE_NULL} with no metadata.
+                *
+                * <p>
+                * This is a token-cursor-level setting and does not affect the 
high-level POJO databind
+                * path ({@link CborParser#parse(Object, Class)}); that path 
always discards tags.
+                *
+                * @return This object.
+                */
+               public Builder nativeMode() {
+                       return nativeMode(true);
+               }
+
+               /**
+                * Same as {@link #nativeMode()} but allows you to explicitly 
specify the value.
+                *
+                * @param value The value for this setting.
+                * @return This object.
+                */
+               public Builder nativeMode(boolean value) {
+                       nativeMode = value;
+                       return this;
                }
 
                @Override /* Overridden from Context.Builder<?> */
@@ -132,6 +173,11 @@ public class CborParser extends InputStreamParser 
implements CborMetaProvider, T
                public Builder copy() {
                        return new Builder(this);
                }
+
+               @Override /* Overridden from Context.Builder<?> */
+               public HashKey hashKey() {
+                       return HashKey.of(super.hashKey(), nativeMode);
+               }
        }
 
        /** Default parser, string input encoded as spaced-hex. */
@@ -149,6 +195,8 @@ public class CborParser extends InputStreamParser 
implements CborMetaProvider, T
 
        /** Default parser, all default settings. */
        public static final CborParser DEFAULT = new CborParser(create());
+       /** Default parser with binary-native opt-in mode enabled (CBOR tags / 
simple values surface as token-level metadata). */
+       public static final CborParser DEFAULT_NATIVE = new 
CborParser(create().nativeMode());
        /** Default parser, string input encoded as spaced-hex. */
        public static final CborParser DEFAULT_SPACED_HEX = new 
SpacedHex(create());
        /** Default parser, string input encoded as BASE64. */
@@ -163,6 +211,8 @@ public class CborParser extends InputStreamParser 
implements CborMetaProvider, T
                return new Builder();
        }
 
+       protected final boolean nativeMode;
+
        private final Map<ClassMeta<?>,CborClassMeta> cborClassMetas = new 
ConcurrentHashMap<>();
        private final Map<BeanPropertyMeta,CborBeanPropertyMeta> 
cborBeanPropertyMetas = new ConcurrentHashMap<>();
 
@@ -173,6 +223,16 @@ public class CborParser extends InputStreamParser 
implements CborMetaProvider, T
         */
        public CborParser(Builder builder) {
                super(builder);
+               this.nativeMode = builder.nativeMode;
+       }
+
+       /**
+        * Returns <jk>true</jk> if token-cursor native mode is enabled.
+        *
+        * @return <jk>true</jk> if native mode is enabled, else <jk>false</jk>.
+        */
+       public boolean isNativeMode() {
+               return nativeMode;
        }
 
        @Override /* Overridden from Context */
@@ -234,4 +294,10 @@ public class CborParser extends InputStreamParser 
implements CborMetaProvider, T
        public RecordReader parseArrayRecords(Object input) throws IOException {
                return getSession().parseArrayRecords(input);
        }
+
+       @Override /* Overridden from InputStreamParser */
+       protected FluentMap<String,Object> properties() {
+               return super.properties()
+                       .a(PROP_nativeMode, nativeMode);
+       }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborParserSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborParserSession.java
index cfa6abb944..5631681fdc 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborParserSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborParserSession.java
@@ -57,6 +57,8 @@ public class CborParserSession extends 
InputStreamParserSession implements Token
         */
        public static class Builder extends 
InputStreamParserSession.Builder<Builder> {
 
+               final boolean nativeMode;
+
                /**
                 * Constructor
                 *
@@ -65,6 +67,7 @@ public class CborParserSession extends 
InputStreamParserSession implements Token
                 */
                protected Builder(CborParser ctx) {
                        super(assertArgNotNull(ARG_ctx, ctx));
+                       this.nativeMode = ctx.isNativeMode();
                }
 
                @Override
@@ -85,6 +88,8 @@ public class CborParserSession extends 
InputStreamParserSession implements Token
                return new Builder(assertArgNotNull(ARG_ctx, ctx));
        }
 
+       private final boolean nativeMode;
+
        /**
         * Constructor.
         *
@@ -92,6 +97,7 @@ public class CborParserSession extends 
InputStreamParserSession implements Token
         */
        protected CborParserSession(Builder builder) {
                super(builder);
+               this.nativeMode = builder.nativeMode;
        }
 
        /**
@@ -119,7 +125,7 @@ public class CborParserSession extends 
InputStreamParserSession implements Token
        @Override /* TokenReadable */
        public TokenReader parseTokens(Object input) throws IOException {
                var pipe = new ParserPipe(input, isDebug(), 
isAutoCloseStreams(), isUnbuffered(), null);
-               return new CborTokenReader(pipe, this);
+               return new CborTokenReader(pipe, 
this).setNativeMode(nativeMode);
        }
 
        /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborTokenReader.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborTokenReader.java
index 6ebc3d02cd..15b33bcdf0 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborTokenReader.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborTokenReader.java
@@ -86,6 +86,17 @@ public class CborTokenReader implements TokenReader {
        private boolean currentBoolean;
        private byte[] currentBinary;
 
+       // Binary-native opt-in state (175ad).  Defaults preserve the 
normalize-by-default contract:
+       // nativeMode == false ⇒ tag numbers are dropped, simple values surface 
as VALUE_NULL with no
+       // metadata.  When nativeMode is true, the TAG branch accumulates tag 
numbers into 'tags' as
+       // it recurses, the SIMPLE branch captures the simple int into 
'simpleValue', and 'nativeKind'
+       // classifies the resulting token for format-agnostic consumers.
+       private boolean nativeMode;
+       private BinaryNativeKind nativeKind = BinaryNativeKind.NONE;
+       private long[] tags = new long[4];
+       private int tagCount;
+       private int simpleValue;
+
        /**
         * Constructor with default settings.
         *
@@ -128,6 +139,24 @@ public class CborTokenReader implements TokenReader {
                return this;
        }
 
+       /**
+        * Enables or disables binary-native opt-in mode.
+        *
+        * <p>
+        * When enabled, CBOR semantic tags are captured on the tag stack 
rather than discarded
+        * (surface them via {@link #getTagCount()} / {@link #getTag(int)}), 
and CBOR simple values
+        * surface their int via {@link #getSimpleValue()}.  When disabled 
(default), tags are
+        * silently unwrapped and simple values collapse to {@link 
TokenType#VALUE_NULL} with no
+        * metadata &mdash; identical to the pre-native-mode behavior.
+        *
+        * @param value <jk>true</jk> to enable native mode.
+        * @return This object.
+        */
+       public CborTokenReader setNativeMode(boolean value) {
+               nativeMode = value;
+               return this;
+       }
+
        // 
==============================================================================================
        // State-machine summary.  CBOR is already a token-shaped wire format 
(RFC 8949 major types), so
        // next() reads the next data-type tag via 
CborInputStream.readDataType() and dispatches based
@@ -136,7 +165,7 @@ public class CborTokenReader implements TokenReader {
        // END_*), and (3) within a map, whether we're at a key position or 
value position.
        //
        // Per-level container state lives on parallel stacks (stackRemaining + 
stackIsMap +
-       // stackAwaitingKey, indexed by depth).  A stackRemaining value of -1 
means indefinite-length;
+       // stackAwaitingKey, indexed by depth).  A stackRemaining value of -1 
means indefinite-length
        // in that case the next() loop relies on CborInputStream returning 
DataType.BREAK instead of
        // counting elements.
        // 
==============================================================================================
@@ -154,6 +183,12 @@ public class CborTokenReader implements TokenReader {
                currentNumberLexeme = null;
                currentNumber = null;
                currentBinary = null;
+               // Native-mode metadata: only reset at outermost entry — 
recursive tag unwraps preserve the
+               // accumulated tag stack so nested tag(tag(value)) round-trips 
losslessly.
+               if (tagNestingDepth == 0) {
+                       nativeKind = BinaryNativeKind.NONE;
+                       tagCount = 0;
+               }
 
                // Inside a definite-length container at element-count zero: 
emit END_*.
                if (depth > 0 && stackRemaining[depth - 1] == 0) {
@@ -241,7 +276,16 @@ public class CborTokenReader implements TokenReader {
                        case TAG, SIMPLE -> {
                                // Q3: by default normalize to common scalars.  
TAG: skip the tag and read the
                                // next item as the wrapped value.  SIMPLE: 
emit as VALUE_NULL (best-effort).
+                               // In native mode (175ad): TAG numbers 
accumulate on the tag stack across
+                               // recursion; SIMPLE captures the simple int as 
metadata on the VALUE_NULL token.
                                if (dt == DataType.TAG) {
+                                       var n = is.readLength();
+                                       if (nativeMode) {
+                                               if (tagCount == tags.length)
+                                                       tags = 
java.util.Arrays.copyOf(tags, tags.length * 2);
+                                               tags[tagCount++] = n;
+                                               nativeKind = 
BinaryNativeKind.CBOR_TAG;
+                                       }
                                        // Guard against pathologically deep 
tag chains blowing the JVM stack.
                                        if (tagNestingDepth >= 
maxTagNestingDepth)
                                                throw new ParseException(
@@ -254,6 +298,10 @@ public class CborTokenReader implements TokenReader {
                                                tagNestingDepth--;
                                        }
                                }
+                               if (nativeMode) {
+                                       simpleValue = (int) is.readLength();
+                                       nativeKind = 
BinaryNativeKind.CBOR_SIMPLE;
+                               }
                                currentToken = TokenType.VALUE_NULL;
                                consumedOneElement();
                        }
@@ -446,4 +494,33 @@ public class CborTokenReader implements TokenReader {
        public void close() throws IOException {
                pipe.close();
        }
+
+       @Override /* TokenReader */
+       public BinaryNativeKind getNativeKind() {
+               return nativeKind;
+       }
+
+       @Override /* TokenReader */
+       public int getTagCount() {
+               return tagCount;
+       }
+
+       @Override /* TokenReader */
+       public long getTag(int index) {
+               if (index < 0 || index >= tagCount)
+                       throw new IndexOutOfBoundsException("Tag index " + 
index + " out of bounds for tagCount " + tagCount);
+               return tags[index];
+       }
+
+       @Override /* TokenReader */
+       public int getExtType() {
+               throw new IllegalStateException("CBOR cursor has no MsgPack ext 
type.");
+       }
+
+       @Override /* TokenReader */
+       public int getSimpleValue() {
+               if (nativeKind != BinaryNativeKind.CBOR_SIMPLE)
+                       throw new IllegalStateException("Current token is not a 
CBOR simple value (nativeKind=" + nativeKind + ")");
+               return simpleValue;
+       }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborTokenWriter.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborTokenWriter.java
index 99fb06ada8..d1a4a46045 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborTokenWriter.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/cbor/CborTokenWriter.java
@@ -265,6 +265,25 @@ public class CborTokenWriter implements TokenWriter {
                return this;
        }
 
+       @Override /* TokenWriter */
+       public CborTokenWriter writeTag(long tagNumber) throws IOException {
+               assertOpen();
+               // A tag is a prefix on the next value emit; it does NOT 
consume a map-key/value or
+               // array-element slot.  Skip preValueWrite()/afterValue() — the 
wrapped value emit that
+               // follows owns the state transition.
+               out.writeTag(tagNumber);
+               return this;
+       }
+
+       @Override /* TokenWriter */
+       public CborTokenWriter writeSimple(int value) throws IOException {
+               assertOpen();
+               preValueWrite();
+               out.writeSimple(value);
+               afterValue();
+               return this;
+       }
+
        @Override /* TokenWriter */
        public TokenWriter object(Object value) throws IOException {
                assertOpen();
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackOutputStream.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackOutputStream.java
index 5723c59e7b..db30edda13 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackOutputStream.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackOutputStream.java
@@ -177,6 +177,35 @@ public class MsgPackOutputStream extends OutputStream {
                return append1((int)(l >> 56)).append1((int)(l >> 
48)).append1((int)(l >> 40)).append1((int)(l >> 32)).append1((int)(l >> 
24)).append1((int)(l >> 16)).append1((int)(l >> 8)).append1((int)(l));
        }
 
+       /**
+        * Appends a MsgPack {@code ext} field (typed binary) to the stream.
+        *
+        * <p>
+        * Selects the smallest framing that fits the payload length: 
fixext1/2/4/8/16 for the five
+        * exact lengths, else ext8 / ext16 / ext32 with a length prefix; the 
type byte and payload
+        * are appended after the framing header.
+        *
+        * @param type The {@code ext} type byte (signed; range {@code 
-128..127}).
+        * @param payload The {@code ext} payload bytes.
+        * @return This stream.
+        */
+       MsgPackOutputStream writeExt(int type, byte[] payload) {
+               var n = payload.length;
+               switch (n) {
+                       case 1: return 
append1(FIXEXT1).append1(type).append(payload);
+                       case 2: return 
append1(FIXEXT2).append1(type).append(payload);
+                       case 4: return 
append1(FIXEXT4).append1(type).append(payload);
+                       case 8: return 
append1(FIXEXT8).append1(type).append(payload);
+                       case 16: return 
append1(FIXEXT16).append1(type).append(payload);
+                       default: break;
+               }
+               if (n < (1 << 8))
+                       return 
append1(EXT8).append1(n).append1(type).append(payload);
+               if (n < (1 << 16))
+                       return 
append1(EXT16).append2(n).append1(type).append(payload);
+               return append1(EXT32).append4(n).append1(type).append(payload);
+       }
+
        /**
         * Appends a binary field to the stream.
         */
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackParser.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackParser.java
index e0435cb244..44871948f0 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackParser.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackParser.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.marshall.msgpack;
 
 import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.io.*;
 import java.util.*;
@@ -73,6 +74,9 @@ public class MsgPackParser extends InputStreamParser 
implements MsgPackMetaProvi
        // Argument name constants for assertArgNotNull
        private static final String ARG_copyFrom = "copyFrom";
 
+       // Property name constants
+       private static final String PROP_nativeMode = "nativeMode";
+
        /** Default parser, string input encoded as BASE64. */
        public static class Base64 extends MsgPackParser {
 
@@ -93,11 +97,14 @@ public class MsgPackParser extends InputStreamParser 
implements MsgPackMetaProvi
 
                private static final Cache<HashKey,MsgPackParser> CACHE = 
Cache.of(HashKey.class, MsgPackParser.class).build();
 
+               private boolean nativeMode;
+
                /**
                 * Constructor, default settings.
                 */
                protected Builder() {
                        consumes("application/msgpack,octal/msgpack");
+                       nativeMode = env("MsgPackParser.nativeMode", false);
                }
 
                /**
@@ -108,6 +115,7 @@ public class MsgPackParser extends InputStreamParser 
implements MsgPackMetaProvi
                 */
                protected Builder(Builder copyFrom) {
                        super(assertArgNotNull(ARG_copyFrom, copyFrom));
+                       nativeMode = copyFrom.nativeMode;
                }
 
                /**
@@ -118,6 +126,39 @@ public class MsgPackParser extends InputStreamParser 
implements MsgPackMetaProvi
                 */
                protected Builder(MsgPackParser copyFrom) {
                        super(assertArgNotNull(ARG_copyFrom, copyFrom));
+                       nativeMode = copyFrom.nativeMode;
+               }
+
+               /**
+                * Surfaces the MsgPack {@code ext} type byte as token-level 
metadata on
+                * {@link TokenReader} cursors returned by {@link 
MsgPackParserSession#parseTokens(Object)}.
+                *
+                * <p>
+                * When enabled, ext tokens carry the signed type byte (visible 
via
+                * {@link TokenReader#getExtType()}) on {@link 
TokenType#VALUE_BINARY} tokens.  When
+                * disabled (default), the payload still surfaces as {@link 
TokenType#VALUE_BINARY} but
+                * the type byte is dropped.
+                *
+                * <p>
+                * This is a token-cursor-level setting and does not affect the 
high-level POJO databind
+                * path ({@link MsgPackParser#parse(Object, Class)}); that path 
always discards the type
+                * byte.
+                *
+                * @return This object.
+                */
+               public Builder nativeMode() {
+                       return nativeMode(true);
+               }
+
+               /**
+                * Same as {@link #nativeMode()} but allows you to explicitly 
specify the value.
+                *
+                * @param value The value for this setting.
+                * @return This object.
+                */
+               public Builder nativeMode(boolean value) {
+                       nativeMode = value;
+                       return this;
                }
 
                @Override /* Overridden from Context.Builder<?> */
@@ -130,7 +171,10 @@ public class MsgPackParser extends InputStreamParser 
implements MsgPackMetaProvi
                        return new Builder(this);
                }
 
-
+               @Override /* Overridden from Context.Builder<?> */
+               public HashKey hashKey() {
+                       return HashKey.of(super.hashKey(), nativeMode);
+               }
        }
 
        /** Default parser, string input encoded as spaced-hex. */
@@ -148,6 +192,8 @@ public class MsgPackParser extends InputStreamParser 
implements MsgPackMetaProvi
 
        /** Default parser, all default settings.*/
        public static final MsgPackParser DEFAULT = new MsgPackParser(create());
+       /** Default parser with binary-native opt-in mode enabled (MsgPack 
{@code ext} type byte surfaces as token-level metadata). */
+       public static final MsgPackParser DEFAULT_NATIVE = new 
MsgPackParser(create().nativeMode());
        /** Default parser, all default settings, string input encoded as 
spaced-hex.*/
        public static final MsgPackParser DEFAULT_SPACED_HEX = new 
SpacedHex(create());
 
@@ -163,6 +209,8 @@ public class MsgPackParser extends InputStreamParser 
implements MsgPackMetaProvi
                return new Builder();
        }
 
+       protected final boolean nativeMode;
+
        private final Map<ClassMeta<?>,MsgPackClassMeta> msgPackClassMetas = 
new ConcurrentHashMap<>();
        private final Map<BeanPropertyMeta,MsgPackBeanPropertyMeta> 
msgPackBeanPropertyMetas = new ConcurrentHashMap<>();
 
@@ -173,6 +221,16 @@ public class MsgPackParser extends InputStreamParser 
implements MsgPackMetaProvi
         */
        public MsgPackParser(Builder builder) {
                super(builder);
+               this.nativeMode = builder.nativeMode;
+       }
+
+       /**
+        * Returns <jk>true</jk> if token-cursor native mode is enabled.
+        *
+        * @return <jk>true</jk> if native mode is enabled, else <jk>false</jk>.
+        */
+       public boolean isNativeMode() {
+               return nativeMode;
        }
 
        @Override /* Overridden from Context */
@@ -232,4 +290,10 @@ public class MsgPackParser extends InputStreamParser 
implements MsgPackMetaProvi
        public RecordReader parseArrayRecords(Object input) throws IOException {
                return getSession().parseArrayRecords(input);
        }
+
+       @Override /* Overridden from InputStreamParser */
+       protected FluentMap<String,Object> properties() {
+               return super.properties()
+                       .a(PROP_nativeMode, nativeMode);
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackParserSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackParserSession.java
index 881e778f2c..491b4766bb 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackParserSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackParserSession.java
@@ -58,6 +58,8 @@ public class MsgPackParserSession extends 
InputStreamParserSession implements To
         */
        public static class Builder extends 
InputStreamParserSession.Builder<Builder> {
 
+               final boolean nativeMode;
+
                /**
                 * Constructor
                 *
@@ -66,6 +68,7 @@ public class MsgPackParserSession extends 
InputStreamParserSession implements To
                 */
                protected Builder(MsgPackParser ctx) {
                        super(assertArgNotNull(ARG_ctx, ctx));
+                       this.nativeMode = ctx.isNativeMode();
                }
 
                @Override
@@ -86,6 +89,8 @@ public class MsgPackParserSession extends 
InputStreamParserSession implements To
                return new Builder(assertArgNotNull(ARG_ctx, ctx));
        }
 
+       private final boolean nativeMode;
+
        /**
         * Constructor.
         *
@@ -93,6 +98,7 @@ public class MsgPackParserSession extends 
InputStreamParserSession implements To
         */
        protected MsgPackParserSession(Builder builder) {
                super(builder);
+               this.nativeMode = builder.nativeMode;
        }
 
        /**
@@ -114,7 +120,7 @@ public class MsgPackParserSession extends 
InputStreamParserSession implements To
        @Override /* TokenReadable */
        public TokenReader parseTokens(Object input) throws IOException {
                var pipe = new ParserPipe(input, isDebug(), 
isAutoCloseStreams(), isUnbuffered(), null);
-               return new MsgPackTokenReader(pipe, this);
+               return new MsgPackTokenReader(pipe, 
this).setNativeMode(nativeMode);
        }
 
        /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackTokenReader.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackTokenReader.java
index 040d3e2535..ea78ae019b 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackTokenReader.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackTokenReader.java
@@ -61,6 +61,14 @@ public class MsgPackTokenReader implements TokenReader {
        private boolean currentBoolean;
        private byte[] currentBinary;
 
+       // Binary-native opt-in state (175ad).  Defaults preserve the 
normalize-by-default contract:
+       // EXT payloads surface as VALUE_BINARY with no metadata.  When 
nativeMode is true, the EXT
+       // branch additionally captures the signed type byte into 'extType' and 
sets
+       // 'nativeKind' = MSGPACK_EXT.
+       private boolean nativeMode;
+       private BinaryNativeKind nativeKind = BinaryNativeKind.NONE;
+       private int extType;
+
        /**
         * Constructor with default settings.
         *
@@ -86,6 +94,23 @@ public class MsgPackTokenReader implements TokenReader {
                this.session = session;
        }
 
+       /**
+        * Enables or disables binary-native opt-in mode.
+        *
+        * <p>
+        * When enabled, MsgPack {@code ext} tokens carry the signed type byte 
via
+        * {@link #getExtType()}.  When disabled (default), the payload still 
surfaces as
+        * {@link TokenType#VALUE_BINARY} but the type byte is dropped &mdash; 
identical to the
+        * pre-native-mode behavior.
+        *
+        * @param value <jk>true</jk> to enable native mode.
+        * @return This object.
+        */
+       public MsgPackTokenReader setNativeMode(boolean value) {
+               nativeMode = value;
+               return this;
+       }
+
        // 
==============================================================================================
        // State-machine summary.  MsgPack containers are length-prefixed (no 
indefinite-length
        // encoding), so next() reads the data-type tag via 
MsgPackInputStream.readDataType() and
@@ -107,6 +132,7 @@ public class MsgPackTokenReader implements TokenReader {
                currentNumberLexeme = null;
                currentNumber = null;
                currentBinary = null;
+               nativeKind = BinaryNativeKind.NONE;
 
                // Inside a container at element-count zero: emit END_*.
                if (depth > 0 && stackRemaining[depth - 1] == 0) {
@@ -192,7 +218,13 @@ public class MsgPackTokenReader implements TokenReader {
                                consumedOneElement();
                        }
                        case EXT -> {
-                               // Q3 default: normalize to common scalars; 
skip the extension payload as binary.
+                               // Q3 default: normalize to common scalars; the 
extension payload surfaces as
+                               // VALUE_BINARY.  In native mode (175ad), the 
signed ext type byte is also
+                               // captured for retrieval via getExtType().
+                               if (nativeMode) {
+                                       extType = (byte) is.getExtType();
+                                       nativeKind = 
BinaryNativeKind.MSGPACK_EXT;
+                               }
                                currentBinary = is.readBinary();
                                currentToken = TokenType.VALUE_BINARY;
                                consumedOneElement();
@@ -364,4 +396,31 @@ public class MsgPackTokenReader implements TokenReader {
 
        @Override /* TokenReader */
        public void close() throws IOException { pipe.close(); }
+
+       @Override /* TokenReader */
+       public BinaryNativeKind getNativeKind() {
+               return nativeKind;
+       }
+
+       @Override /* TokenReader */
+       public int getTagCount() {
+               return 0;
+       }
+
+       @Override /* TokenReader */
+       public long getTag(int index) {
+               throw new IndexOutOfBoundsException("Tag index " + index + " 
out of bounds for tagCount 0");
+       }
+
+       @Override /* TokenReader */
+       public int getExtType() {
+               if (nativeKind != BinaryNativeKind.MSGPACK_EXT)
+                       throw new IllegalStateException("Current token is not a 
MsgPack ext (nativeKind=" + nativeKind + ")");
+               return extType;
+       }
+
+       @Override /* TokenReader */
+       public int getSimpleValue() {
+               throw new IllegalStateException("MsgPack cursor has no CBOR 
simple value.");
+       }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackTokenWriter.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackTokenWriter.java
index bb15173a3a..44f50a23f8 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackTokenWriter.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/msgpack/MsgPackTokenWriter.java
@@ -287,6 +287,17 @@ public class MsgPackTokenWriter implements TokenWriter {
                return this;
        }
 
+       @Override /* TokenWriter */
+       public MsgPackTokenWriter writeExt(int type, byte[] payload) throws 
IOException {
+               assertOpen();
+               if (payload == null)
+                       throw new IllegalArgumentException("ext payload must 
not be null");
+               preValueCheck();
+               new MsgPackOutputStream(activeOut()).writeExt(type, payload);
+               afterValue();
+               return this;
+       }
+
        @Override /* TokenWriter */
        public TokenWriter object(Object value) throws IOException {
                assertOpen();
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/stream/BinaryNativeKind.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/stream/BinaryNativeKind.java
new file mode 100644
index 0000000000..15e179f325
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/stream/BinaryNativeKind.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.marshall.stream;
+
+/**
+ * Discriminant returned by {@link TokenReader#getNativeKind()} that 
classifies the
+ * binary-native metadata (if any) carried by the cursor's current token.
+ *
+ * <p>
+ * A {@link TokenReader} surfaces format-native value types as <b>metadata on 
the existing
+ * token vocabulary</b> rather than as new top-level events.  A native wrapper 
rides on whatever
+ * token the wrapped value emits ({@code VALUE_*} or {@code START_*}); this 
enum lets a
+ * format-agnostic consumer branch on the kind of native metadata without 
having to test the
+ * cursor's runtime type.
+ *
+ * <p>
+ * Each enum value names the concrete native concept it represents.  New 
values may be added in
+ * future minor releases as additional formats expose native opt-in metadata.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li>{@link TokenReader} &mdash; the cursor that exposes this 
discriminant.
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/SerializersAndParsers";>Serializers 
and Parsers</a>
+ * </ul>
+ */
+public enum BinaryNativeKind {
+
+       /** The current token carries no binary-native metadata. */
+       NONE,
+
+       /**
+        * The current token is wrapped by one or more semantic tags (CBOR 
major type 6).  Surfaced
+        * via {@link TokenReader#getTagCount()} / {@link 
TokenReader#getTag(int)}.
+        */
+       CBOR_TAG,
+
+       /**
+        * The current token represents a CBOR simple value (major type 7).  
Token type is
+        * {@link TokenType#VALUE_NULL}; the simple int is surfaced via
+        * {@link TokenReader#getSimpleValue()}.
+        */
+       CBOR_SIMPLE,
+
+       /**
+        * The current token represents a MsgPack {@code ext} value (signed 
type byte + binary
+        * payload).  Token type is {@link TokenType#VALUE_BINARY}; the type 
byte is surfaced via
+        * {@link TokenReader#getExtType()}.
+        */
+       MSGPACK_EXT
+}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/stream/TokenReader.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/stream/TokenReader.java
index 1870b7b84d..9cb27d5ce4 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/stream/TokenReader.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/stream/TokenReader.java
@@ -301,4 +301,66 @@ public interface TokenReader extends RecordReader {
         */
        @Override
        void close() throws IOException;
+
+       // 
=================================================================================
+       // Binary-native opt-in metadata accessors.  Default cursors and 
formats without
+       // native concepts inherit the safe defaults below: getNativeKind() 
returns NONE,
+       // getTagCount() returns 0, and the value accessors throw.  
Format-specific cursors
+       // override the relevant subset (CBOR overrides 
getNativeKind/getTagCount/getTag/
+       // getSimpleValue; MsgPack overrides getNativeKind/getExtType).  See
+       // {@link BinaryNativeKind} for the catalog of native concepts.
+       // 
=================================================================================
+
+       /**
+        * Returns the binary-native classification of the current token.
+        *
+        * @return The native kind, or {@link BinaryNativeKind#NONE NONE} when 
the current token
+        *      carries no binary-native metadata.  Never {@code null}.
+        */
+       default BinaryNativeKind getNativeKind() {
+               return BinaryNativeKind.NONE;
+       }
+
+       /**
+        * Returns the number of semantic tags wrapping the current token.
+        *
+        * @return The tag count (0 when {@link #getNativeKind()} is not
+        *      {@link BinaryNativeKind#CBOR_TAG CBOR_TAG}).
+        */
+       default int getTagCount() {
+               return 0;
+       }
+
+       /**
+        * Returns the semantic tag at the specified position in the tag stack.
+        *
+        * @param index The tag position; outermost tag is {@code 0}.
+        * @return The tag number.
+        * @throws IndexOutOfBoundsException If {@code index} is outside {@code 
[0, getTagCount())}.
+        */
+       default long getTag(int index) {
+               throw new IndexOutOfBoundsException("Tag index " + index + " 
out of bounds for tagCount " + getTagCount());
+       }
+
+       /**
+        * Returns the typed-binary type byte for the current token.
+        *
+        * @return The signed type byte (range {@code -128..127}).
+        * @throws IllegalStateException If {@link #getNativeKind()} is not
+        *      {@link BinaryNativeKind#MSGPACK_EXT MSGPACK_EXT}.
+        */
+       default int getExtType() {
+               throw new IllegalStateException("Current token is not a 
typed-binary (ext) value (nativeKind=" + getNativeKind() + ")");
+       }
+
+       /**
+        * Returns the opaque simple value for the current token.
+        *
+        * @return The simple int.
+        * @throws IllegalStateException If {@link #getNativeKind()} is not
+        *      {@link BinaryNativeKind#CBOR_SIMPLE CBOR_SIMPLE}.
+        */
+       default int getSimpleValue() {
+               throw new IllegalStateException("Current token is not a simple 
value (nativeKind=" + getNativeKind() + ")");
+       }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/stream/TokenWriter.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/stream/TokenWriter.java
index 5f842f206d..0b486912c8 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/stream/TokenWriter.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/stream/TokenWriter.java
@@ -288,4 +288,59 @@ public interface TokenWriter extends RecordWriter {
         */
        @Override
        void close() throws IOException;
+
+       // 
=================================================================================
+       // Binary-native opt-in emits.  These default to 
UnsupportedOperationException so
+       // formats override only the subset they support (CBOR overrides 
writeTag and
+       // writeSimple; MsgPack overrides writeExt; text formats inherit the 
throwing
+       // defaults).  See {@link BinaryNativeKind} for the catalog of native 
concepts.
+       // 
=================================================================================
+
+       /**
+        * Emits a semantic tag prefixing the next value emit.
+        *
+        * <p>
+        * Tags compose: {@code writeTag(1).writeTag(2).number(5)} writes 
{@code tag(1)(tag(2)(5))}.
+        * The tag header is written immediately and does NOT consume a 
map-key/value or array-element
+        * slot; that bookkeeping belongs to the wrapped value emit that 
follows.
+        *
+        * @param tagNumber The unsigned tag number (carried as a {@code long} 
for the full unsigned
+        *      64-bit range).
+        * @return This object.
+        * @throws IOException If a problem occurred writing to the underlying 
stream.
+        * @throws UnsupportedOperationException If the format does not support 
semantic tags.
+        */
+       default TokenWriter writeTag(long tagNumber) throws IOException {
+               throw new UnsupportedOperationException("writeTag is not 
supported by this writer.");
+       }
+
+       /**
+        * Emits an opaque simple value.  The corresponding read-side token is
+        * {@link TokenType#VALUE_NULL} with {@link 
TokenReader#getSimpleValue()}.
+        *
+        * @param value The simple value.  Format-specific reserved encodings 
(e.g. CBOR major-7
+        *      codepoints 20-23, 25-27, 31 collide with 
bool/null/undefined/float/break) must not be
+        *      used.
+        * @return This object.
+        * @throws IOException If a problem occurred writing to the underlying 
stream.
+        * @throws UnsupportedOperationException If the format does not support 
opaque simple values.
+        */
+       default TokenWriter writeSimple(int value) throws IOException {
+               throw new UnsupportedOperationException("writeSimple is not 
supported by this writer.");
+       }
+
+       /**
+        * Emits a typed-binary value (signed type byte + binary payload).  The 
corresponding
+        * read-side token is {@link TokenType#VALUE_BINARY} with {@link 
TokenReader#getExtType()}
+        * and {@link TokenReader#getBinary()}.
+        *
+        * @param type The signed type byte (range {@code -128..127}).
+        * @param payload The payload bytes.  Must not be {@code null}.
+        * @return This object.
+        * @throws IOException If a problem occurred writing to the underlying 
stream.
+        * @throws UnsupportedOperationException If the format does not support 
typed-binary values.
+        */
+       default TokenWriter writeExt(int type, byte[] payload) throws 
IOException {
+               throw new UnsupportedOperationException("writeExt is not 
supported by this writer.");
+       }
 }
diff --git 
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/cbor/CborNativeTokenStream_Test.java
 
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/cbor/CborNativeTokenStream_Test.java
new file mode 100644
index 0000000000..c1d59d99ff
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/cbor/CborNativeTokenStream_Test.java
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.marshall.cbor;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.marshall.stream.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Unit tests for the CBOR opt-in binary-native token surface (175ad).  
Verifies that
+ * {@link CborParserSession#parseNativeTokens(Object) parseNativeTokens} 
surfaces semantic tags
+ * and simple values as token-level metadata, and that the default
+ * {@link CborParserSession#parseTokens(Object) parseTokens} path still 
normalizes them away.
+ */
+@SuppressWarnings({
+       "resource" // Token readers are closed via try-with-resources; JDT's 
flow analysis over chained factory calls yields false-positive leak reports.
+})
+class CborNativeTokenStream_Test extends TestBase {
+
+       private static byte[] bytes(int... b) {
+               var r = new byte[b.length];
+               for (var i = 0; i < b.length; i++) r[i] = (byte) b[i];
+               return r;
+       }
+
+       // 
=================================================================================
+       // A. Reader native-mode (parseNativeTokens)
+       // 
=================================================================================
+
+       @Test void a01_singleTag() throws Exception {
+               // 0xC0 = tag(0); 0x61 = string len 1; 'a' = 0x61 — tag(0) 
wraps "a".
+               var data = bytes(0xC0, 0x61, 0x61);
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_STRING, r.next());
+                       assertEquals(BinaryNativeKind.CBOR_TAG, 
r.getNativeKind());
+                       assertEquals(1, r.getTagCount());
+                       assertEquals(0L, r.getTag(0));
+                       assertEquals("a", r.getString());
+                       assertEquals(TokenType.END_OF_STREAM, r.next());
+               }
+       }
+
+       @Test void a02_nestedTags() throws Exception {
+               // tag(1)(tag(2)(uint 5)).  tag-1 = 0xC1, tag-2 = 0xC2, uint 5 
= 0x05.
+               var data = bytes(0xC1, 0xC2, 0x05);
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_NUMBER, r.next());
+                       assertEquals(BinaryNativeKind.CBOR_TAG, 
r.getNativeKind());
+                       assertEquals(2, r.getTagCount());
+                       assertEquals(1L, r.getTag(0)); // outermost first
+                       assertEquals(2L, r.getTag(1));
+                       assertEquals(5L, r.getNumber().longValue());
+               }
+       }
+
+       @Test void a03_taggedContainer() throws Exception {
+               // tag(4) wrapping an empty array: 0xC4 0x80
+               var data = bytes(0xC4, 0x80);
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.START_ARRAY, r.next());
+                       assertEquals(BinaryNativeKind.CBOR_TAG, 
r.getNativeKind());
+                       assertEquals(1, r.getTagCount());
+                       assertEquals(4L, r.getTag(0));
+                       // Subsequent END_ARRAY is not tagged.
+                       assertEquals(TokenType.END_ARRAY, r.next());
+                       assertEquals(BinaryNativeKind.NONE, r.getNativeKind());
+                       assertEquals(0, r.getTagCount());
+               }
+       }
+
+       @Test void a04_simpleValue() throws Exception {
+               // Major-7 simple value 16: 0xF0 (initial byte = 0xE0 | 16).
+               var data = bytes(0xF0);
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_NULL, r.next());
+                       assertEquals(BinaryNativeKind.CBOR_SIMPLE, 
r.getNativeKind());
+                       assertEquals(16, r.getSimpleValue());
+               }
+       }
+
+       @Test void a05_defaultStillNormalizes() throws Exception {
+               // Same inputs via plain parseTokens: tags are silently 
unwrapped, simple → VALUE_NULL.
+               var taggedData = bytes(0xC1, 0xC2, 0x05);
+               try (var r = CborParser.DEFAULT.parseTokens(taggedData)) {
+                       assertEquals(TokenType.VALUE_NUMBER, r.next());
+                       assertEquals(5L, r.getNumber().longValue());
+               }
+               var simpleData = bytes(0xF0);
+               try (var r = CborParser.DEFAULT.parseTokens(simpleData)) {
+                       assertEquals(TokenType.VALUE_NULL, r.next());
+               }
+       }
+
+       @Test void a06_extTypeAccessThrowsOnCbor() throws Exception {
+               var data = bytes(0xC0, 0x61, 0x61);
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_STRING, r.next());
+                       assertThrows(IllegalStateException.class, 
r::getExtType);
+               }
+       }
+
+       @Test void a07_simpleValueOutOfStateThrows() throws Exception {
+               // A plain string token has no simple value: getSimpleValue 
must throw.
+               var data = bytes(0x61, 0x61);
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_STRING, r.next());
+                       assertThrows(IllegalStateException.class, 
r::getSimpleValue);
+               }
+       }
+
+       @Test void a08_getTagOutOfBounds() throws Exception {
+               var data = bytes(0xC0, 0x61, 0x61);
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_STRING, r.next());
+                       assertThrows(IndexOutOfBoundsException.class, () -> 
r.getTag(1));
+                       assertThrows(IndexOutOfBoundsException.class, () -> 
r.getTag(-1));
+               }
+       }
+
+       @Test void a10_tagStackGrows() throws Exception {
+               // Five nested tags exceeds the initial tags[] array (size 4) 
and must trigger growth.
+               // 0xC0 0xC0 0xC0 0xC0 0xC0 0x05 — 5 tags wrapping uint 5.
+               var data = bytes(0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x05);
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_NUMBER, r.next());
+                       assertEquals(5, r.getTagCount());
+                       for (var i = 0; i < 5; i++)
+                               assertEquals(0L, r.getTag(i));
+               }
+       }
+
+       // 
=================================================================================
+       // B. Writer native-mode (serializeTokens) — round-trip through reader
+       // 
=================================================================================
+
+       @Test void b01_writeTaggedString() throws Exception {
+               var bos = new ByteArrayOutputStream();
+               try (var w = 
CborSerializer.DEFAULT.getSession().serializeTokens(bos)) {
+                       w.writeTag(0);
+                       w.string("hello");
+               }
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(bos.toByteArray())) {
+                       assertEquals(TokenType.VALUE_STRING, r.next());
+                       assertEquals(BinaryNativeKind.CBOR_TAG, 
r.getNativeKind());
+                       assertEquals(1, r.getTagCount());
+                       assertEquals(0L, r.getTag(0));
+                       assertEquals("hello", r.getString());
+               }
+       }
+
+       @Test void b02_writeNestedTags() throws Exception {
+               var bos = new ByteArrayOutputStream();
+               try (var w = 
CborSerializer.DEFAULT.getSession().serializeTokens(bos)) {
+                       w.writeTag(1);
+                       w.writeTag(2);
+                       w.number(5L);
+               }
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(bos.toByteArray())) {
+                       assertEquals(TokenType.VALUE_NUMBER, r.next());
+                       assertEquals(2, r.getTagCount());
+                       assertEquals(1L, r.getTag(0));
+                       assertEquals(2L, r.getTag(1));
+                       assertEquals(5L, r.getNumber().longValue());
+               }
+       }
+
+       @Test void b03_writeSimple() throws Exception {
+               var bos = new ByteArrayOutputStream();
+               try (var w = 
CborSerializer.DEFAULT.getSession().serializeTokens(bos)) {
+                       w.writeSimple(16);
+               }
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(bos.toByteArray())) {
+                       assertEquals(TokenType.VALUE_NULL, r.next());
+                       assertEquals(BinaryNativeKind.CBOR_SIMPLE, 
r.getNativeKind());
+                       assertEquals(16, r.getSimpleValue());
+               }
+       }
+
+       @Test void b04_taggedInsideMap() throws Exception {
+               // Tag a map value: { "k": tag(0)("v") }.
+               var bos = new ByteArrayOutputStream();
+               try (var w = 
CborSerializer.DEFAULT.getSession().serializeTokens(bos)) {
+                       w.startObject();
+                       w.fieldName("k");
+                       w.writeTag(0);
+                       w.string("v");
+                       w.endObject();
+               }
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(bos.toByteArray())) {
+                       assertEquals(TokenType.START_OBJECT, r.next());
+                       assertEquals(TokenType.FIELD_NAME, r.next());
+                       assertEquals("k", r.getFieldName());
+                       assertEquals(TokenType.VALUE_STRING, r.next());
+                       assertEquals(BinaryNativeKind.CBOR_TAG, 
r.getNativeKind());
+                       assertEquals(1, r.getTagCount());
+                       assertEquals(0L, r.getTag(0));
+                       assertEquals("v", r.getString());
+                       assertEquals(TokenType.END_OBJECT, r.next());
+               }
+       }
+
+       @Test void b05_taggedInsideArray() throws Exception {
+               // Tag an array element: [ tag(7)(42) ].
+               var bos = new ByteArrayOutputStream();
+               try (var w = 
CborSerializer.DEFAULT.getSession().serializeTokens(bos)) {
+                       w.startArray();
+                       w.writeTag(7);
+                       w.number(42L);
+                       w.endArray();
+               }
+               try (var r = 
CborParser.DEFAULT_NATIVE.getSession().parseTokens(bos.toByteArray())) {
+                       assertEquals(TokenType.START_ARRAY, r.next());
+                       assertEquals(TokenType.VALUE_NUMBER, r.next());
+                       assertEquals(7L, r.getTag(0));
+                       assertEquals(42L, r.getNumber().longValue());
+                       assertEquals(TokenType.END_ARRAY, r.next());
+               }
+       }
+
+       @Test void b06_extWriteUnsupported() throws Exception {
+               // CBOR doesn't support MsgPack ext; the inherited 
default-throwing writeExt should fire.
+               var bos = new ByteArrayOutputStream();
+               try (var w = 
CborSerializer.DEFAULT.getSession().serializeTokens(bos)) {
+                       assertThrows(UnsupportedOperationException.class, () -> 
w.writeExt(0, new byte[]{1}));
+               }
+       }
+
+       // 
=================================================================================
+       // C. Convenience-class delegation (CborParser/CborSerializer DEFAULT)
+       // 
=================================================================================
+
+       @Test void c01_convenienceDelegation() throws Exception {
+               // CborParser.DEFAULT_NATIVE.parseTokens(...) must yield the 
same shape as session-level.
+               var data = bytes(0xC0, 0x05);
+               try (var r = CborParser.DEFAULT_NATIVE.parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_NUMBER, r.next());
+                       assertEquals(BinaryNativeKind.CBOR_TAG, 
r.getNativeKind());
+                       assertEquals(1, r.getTagCount());
+                       assertEquals(0L, r.getTag(0));
+               }
+               // And the writer side: serializeTokens via DEFAULT.
+               var bos = new ByteArrayOutputStream();
+               try (var w = CborSerializer.DEFAULT.serializeTokens(bos)) {
+                       w.writeTag(0);
+                       w.number(5L);
+               }
+               assertArrayEquals(data, bos.toByteArray());
+       }
+}
diff --git 
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/cbor/CborOutputStream_Test.java
 
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/cbor/CborOutputStream_Test.java
index 12bb8db764..91c09de380 100644
--- 
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/cbor/CborOutputStream_Test.java
+++ 
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/cbor/CborOutputStream_Test.java
@@ -235,4 +235,23 @@ class CborOutputStream_Test extends TestBase {
                var b = CborSerializer.DEFAULT.serialize(m);
                assertTrue(toSpacedHex(b).startsWith("B8 18"));
        }
+
+       @Test
+       @SuppressWarnings("resource") // writeSimple returns the stream (fluent 
this); the discarded value is os, closed by the try-with-resources.
+       void a32_writeSimple() throws Exception {
+               // Major type 7 with the additional info encoding the simple 
value.
+               // Inline (0..23): one byte, 0xE0 | value.
+               var bos = new java.io.ByteArrayOutputStream();
+               try (var os = new CborOutputStream(bos)) {
+                       os.writeSimple(0);
+               }
+               assertEquals("E0", toSpacedHex(bos.toByteArray()));
+
+               // Two-byte encoding (24..255): 0xF8 then the value.
+               bos = new java.io.ByteArrayOutputStream();
+               try (var os = new CborOutputStream(bos)) {
+                       os.writeSimple(255);
+               }
+               assertEquals("F8 FF", toSpacedHex(bos.toByteArray()));
+       }
 }
diff --git 
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/msgpack/MsgPackNativeTokenStream_Test.java
 
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/msgpack/MsgPackNativeTokenStream_Test.java
new file mode 100644
index 0000000000..815e2a0674
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/msgpack/MsgPackNativeTokenStream_Test.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.marshall.msgpack;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.marshall.stream.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Unit tests for the MsgPack opt-in binary-native token surface (175ad).  
Verifies that
+ * {@link MsgPackParserSession#parseNativeTokens(Object) parseNativeTokens} 
surfaces the {@code ext}
+ * type byte as token-level metadata, and that the default
+ * {@link MsgPackParserSession#parseTokens(Object) parseTokens} path still 
drops it.
+ */
+@SuppressWarnings({
+       "resource" // Token readers are closed via try-with-resources; JDT's 
flow analysis over chained factory calls yields false-positive leak reports.
+})
+class MsgPackNativeTokenStream_Test extends TestBase {
+
+       private static byte[] ext(int type, byte... payload) throws IOException 
{
+               var bos = new ByteArrayOutputStream();
+               try (var os = new MsgPackOutputStream(bos)) {
+                       os.writeExt(type, payload);
+               }
+               return bos.toByteArray();
+       }
+
+       // 
=================================================================================
+       // A. Reader native-mode (parseNativeTokens)
+       // 
=================================================================================
+
+       @Test void a01_ext() throws Exception {
+               // fixext4 with type 5 and 4-byte payload.
+               var data = ext(5, (byte) 1, (byte) 2, (byte) 3, (byte) 4);
+               try (var r = 
MsgPackParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_BINARY, r.next());
+                       assertEquals(BinaryNativeKind.MSGPACK_EXT, 
r.getNativeKind());
+                       assertEquals(5, r.getExtType());
+                       assertArrayEquals(new byte[]{1, 2, 3, 4}, 
r.getBinary());
+               }
+       }
+
+       @Test void a02_negativeExtType() throws Exception {
+               // Negative ext type (-1) — must round-trip as signed int8.
+               var data = ext(-1, (byte) 0x7F);
+               try (var r = 
MsgPackParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_BINARY, r.next());
+                       assertEquals(-1, r.getExtType());
+               }
+       }
+
+       @Test void a03_defaultStillNormalizes() throws Exception {
+               // Same input via plain parseTokens: VALUE_BINARY with no 
native metadata, type byte dropped.
+               var data = ext(5, (byte) 1, (byte) 2, (byte) 3, (byte) 4);
+               try (var r = MsgPackParser.DEFAULT.parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_BINARY, r.next());
+                       assertArrayEquals(new byte[]{1, 2, 3, 4}, 
r.getBinary());
+               }
+       }
+
+       @Test void a04_tagAccessOnMsgPack() throws Exception {
+               var data = ext(5, (byte) 0);
+               try (var r = 
MsgPackParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_BINARY, r.next());
+                       // MsgPack has no tags.
+                       assertEquals(0, r.getTagCount());
+                       assertThrows(IndexOutOfBoundsException.class, () -> 
r.getTag(0));
+               }
+       }
+
+       @Test void a05_simpleValueOnMsgPack() throws Exception {
+               var data = ext(5, (byte) 0);
+               try (var r = 
MsgPackParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_BINARY, r.next());
+                       // MsgPack has no CBOR simple values.
+                       assertThrows(IllegalStateException.class, 
r::getSimpleValue);
+               }
+       }
+
+       @Test void a06_extTypeOutOfStateThrows() throws Exception {
+               // A plain string token has no ext: getExtType must throw.
+               var data = new byte[]{(byte) 0xA1, (byte) 'a'};  // fixstr len 
1, "a"
+               try (var r = 
MsgPackParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_STRING, r.next());
+                       assertThrows(IllegalStateException.class, 
r::getExtType);
+               }
+       }
+
+       @Test void a08_largeExt() throws Exception {
+               // 257 bytes uses ext16 framing internally; native-mode must 
surface the type/payload.
+               var p = new byte[257];
+               for (var i = 0; i < 257; i++) p[i] = (byte) i;
+               var data = ext(127, p);
+               try (var r = 
MsgPackParser.DEFAULT_NATIVE.getSession().parseTokens(data)) {
+                       assertEquals(TokenType.VALUE_BINARY, r.next());
+                       assertEquals(127, r.getExtType());
+                       assertArrayEquals(p, r.getBinary());
+               }
+       }
+
+       // 
=================================================================================
+       // B. Writer native-mode (serializeTokens) — round-trip through reader
+       // 
=================================================================================
+
+       @Test void b01_writeExt() throws Exception {
+               var bos = new ByteArrayOutputStream();
+               try (var w = 
MsgPackSerializer.DEFAULT.getSession().serializeTokens(bos)) {
+                       w.writeExt(5, new byte[]{1, 2, 3, 4});
+               }
+               try (var r = 
MsgPackParser.DEFAULT_NATIVE.getSession().parseTokens(bos.toByteArray())) {
+                       assertEquals(TokenType.VALUE_BINARY, r.next());
+                       assertEquals(BinaryNativeKind.MSGPACK_EXT, 
r.getNativeKind());
+                       assertEquals(5, r.getExtType());
+                       assertArrayEquals(new byte[]{1, 2, 3, 4}, 
r.getBinary());
+               }
+       }
+
+       @Test void b02_extInsideArray() throws Exception {
+               // [ ext(5, [1,2,3,4]) ]
+               var bos = new ByteArrayOutputStream();
+               try (var w = 
MsgPackSerializer.DEFAULT.getSession().serializeTokens(bos)) {
+                       w.startArray();
+                       w.writeExt(5, new byte[]{1, 2, 3, 4});
+                       w.endArray();
+               }
+               try (var r = 
MsgPackParser.DEFAULT_NATIVE.getSession().parseTokens(bos.toByteArray())) {
+                       assertEquals(TokenType.START_ARRAY, r.next());
+                       assertEquals(TokenType.VALUE_BINARY, r.next());
+                       assertEquals(5, r.getExtType());
+                       assertArrayEquals(new byte[]{1, 2, 3, 4}, 
r.getBinary());
+                       assertEquals(TokenType.END_ARRAY, r.next());
+               }
+       }
+
+       @Test void b03_extInsideMap() throws Exception {
+               // { "k": ext(5, [9]) }
+               var bos = new ByteArrayOutputStream();
+               try (var w = 
MsgPackSerializer.DEFAULT.getSession().serializeTokens(bos)) {
+                       w.startObject();
+                       w.fieldName("k");
+                       w.writeExt(5, new byte[]{9});
+                       w.endObject();
+               }
+               try (var r = 
MsgPackParser.DEFAULT_NATIVE.getSession().parseTokens(bos.toByteArray())) {
+                       assertEquals(TokenType.START_OBJECT, r.next());
+                       assertEquals(TokenType.FIELD_NAME, r.next());
+                       assertEquals("k", r.getFieldName());
+                       assertEquals(TokenType.VALUE_BINARY, r.next());
+                       assertEquals(5, r.getExtType());
+                       assertArrayEquals(new byte[]{9}, r.getBinary());
+                       assertEquals(TokenType.END_OBJECT, r.next());
+               }
+       }
+
+       @Test void b04_negativeType() throws Exception {
+               var bos = new ByteArrayOutputStream();
+               try (var w = 
MsgPackSerializer.DEFAULT.getSession().serializeTokens(bos)) {
+                       w.writeExt(-1, new byte[]{0});
+               }
+               try (var r = 
MsgPackParser.DEFAULT_NATIVE.getSession().parseTokens(bos.toByteArray())) {
+                       assertEquals(TokenType.VALUE_BINARY, r.next());
+                       assertEquals(-1, r.getExtType());
+               }
+       }
+
+       @Test void b05_writeTagUnsupported() throws Exception {
+               // MsgPack doesn't support CBOR tags; the inherited 
default-throwing writeTag/writeSimple
+               // should fire.
+               var bos = new ByteArrayOutputStream();
+               try (var w = 
MsgPackSerializer.DEFAULT.getSession().serializeTokens(bos)) {
+                       assertThrows(UnsupportedOperationException.class, () -> 
w.writeTag(0));
+                       assertThrows(UnsupportedOperationException.class, () -> 
w.writeSimple(16));
+               }
+       }
+}
diff --git 
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/msgpack/MsgPackOutputStream_Test.java
 
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/msgpack/MsgPackOutputStream_Test.java
new file mode 100644
index 0000000000..04e62da511
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/msgpack/MsgPackOutputStream_Test.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.marshall.msgpack;
+
+import static org.apache.juneau.commons.utils.StringUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Low-level MsgPack {@code writeExt} framing verification (fixext / ext8 / 
ext16 / ext32 by length).
+ */
+class MsgPackOutputStream_Test extends TestBase {
+
+       @SuppressWarnings("resource") // writeExt returns the stream (fluent 
this); the discarded value is os, closed by the try-with-resources.
+       private static String enc(int type, byte[] payload) throws IOException {
+               var bos = new ByteArrayOutputStream();
+               try (var os = new MsgPackOutputStream(bos)) {
+                       os.writeExt(type, payload);
+               }
+               return toSpacedHex(bos.toByteArray());
+       }
+
+       @Test
+       void a01_fixext1() throws Exception {
+               assertEquals("D4 05 7F", enc(5, new byte[]{0x7F}));
+       }
+
+       @Test
+       void a02_fixext2() throws Exception {
+               assertEquals("D5 05 01 02", enc(5, new byte[]{1, 2}));
+       }
+
+       @Test
+       void a03_fixext4() throws Exception {
+               assertEquals("D6 05 01 02 03 04", enc(5, new byte[]{1, 2, 3, 
4}));
+       }
+
+       @Test
+       void a04_fixext8() throws Exception {
+               assertEquals("D7 05 01 02 03 04 05 06 07 08", enc(5, new 
byte[]{1, 2, 3, 4, 5, 6, 7, 8}));
+       }
+
+       @Test
+       void a05_fixext16() throws Exception {
+               var p = new byte[16];
+               for (var i = 0; i < 16; i++) p[i] = (byte) i;
+               assertTrue(enc(5, p).startsWith("D8 05 "));
+       }
+
+       @Test
+       void a06_ext8_threeBytes() throws Exception {
+               // 3 bytes is not a fixext length, so ext8 framing applies.
+               assertEquals("C7 03 05 01 02 03", enc(5, new byte[]{1, 2, 3}));
+       }
+
+       @Test
+       void a07_ext16_257Bytes() throws Exception {
+               var p = new byte[257];
+               var hex = enc(5, p);
+               // C8 = ext16, length is 0x0101, type 0x05, then 257 zero bytes.
+               assertTrue(hex.startsWith("C8 01 01 05 "));
+       }
+
+       @Test
+       void a08_negativeExtType() throws Exception {
+               // Type byte is signed; -1 must serialize as 0xFF.
+               assertEquals("D4 FF 7F", enc(-1, new byte[]{0x7F}));
+       }
+
+       @Test
+       void a09_largeNegativeExtType() throws Exception {
+               // -128 must serialize as 0x80.
+               assertEquals("D4 80 00", enc(-128, new byte[]{0x00}));
+       }
+}
diff --git 
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/stream/BinaryNativeKind_GenericConsumer_Test.java
 
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/stream/BinaryNativeKind_GenericConsumer_Test.java
new file mode 100644
index 0000000000..0869005595
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/stream/BinaryNativeKind_GenericConsumer_Test.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.marshall.stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.marshall.cbor.*;
+import org.apache.juneau.marshall.msgpack.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Demonstrates that a single format-agnostic consumer can branch on
+ * {@link BinaryNativeKind} alone (without per-format {@code instanceof} on 
the cursor) and
+ * extract the native metadata from both CBOR and MsgPack opt-in cursors.  
This is the
+ * 175ad base-role abstraction that lets a generic native-aware consumer work 
uniformly across
+ * formats.
+ */
+@SuppressWarnings({
+       "resource" // Token readers are closed via try-with-resources; JDT's 
flow analysis over chained factory calls yields false-positive leak reports.
+})
+class BinaryNativeKind_GenericConsumer_Test extends TestBase {
+
+       /**
+        * A format-agnostic consumer that walks a {@link TokenReader} in 
native mode and renders
+        * each value-bearing token as a string with its native metadata, 
branching purely on
+        * {@link BinaryNativeKind}.
+        */
+       private static String render(TokenReader r) throws Exception {
+               var sb = new StringBuilder();
+               while (true) {
+                       var t = r.next();
+                       if (t == TokenType.END_OF_STREAM)
+                               break;
+                       switch (t) {
+                               case VALUE_NUMBER:
+                               case VALUE_STRING:
+                               case VALUE_BINARY:
+                               case VALUE_NULL:
+                               case VALUE_BOOLEAN:
+                                       sb.append(t).append('(');
+                                       switch (r.getNativeKind()) {
+                                               case CBOR_TAG:
+                                                       sb.append("tagged:");
+                                                       for (var i = 0; i < 
r.getTagCount(); i++) {
+                                                               if (i > 0) 
sb.append(',');
+                                                               
sb.append(r.getTag(i));
+                                                       }
+                                                       sb.append(';');
+                                                       break;
+                                               case CBOR_SIMPLE:
+                                                       
sb.append("simple:").append(r.getSimpleValue()).append(';');
+                                                       break;
+                                               case MSGPACK_EXT:
+                                                       
sb.append("ext:").append(r.getExtType()).append(';');
+                                                       break;
+                                               case NONE:
+                                                       break;
+                                       }
+                                       sb.append(')');
+                                       break;
+                               default:
+                                       sb.append(t);
+                       }
+                       sb.append(' ');
+               }
+               return sb.toString().trim();
+       }
+
+       @Test void a01_cborTaggedString() throws Exception {
+               // CBOR: tag(0) wrapping "hi"
+               var bos = new ByteArrayOutputStream();
+               try (var w = CborSerializer.DEFAULT.serializeTokens(bos)) {
+                       w.writeTag(0);
+                       w.string("hi");
+               }
+               try (var r = 
CborParser.DEFAULT_NATIVE.parseTokens(bos.toByteArray())) {
+                       assertEquals("VALUE_STRING(tagged:0;)", render(r));
+               }
+       }
+
+       @Test void a02_cborSimple() throws Exception {
+               // Use simple value 16 (within the valid 0..19 range; 20-23 are 
reserved for
+               // false/true/null/undefined and 25-27 for float16/32/64).
+               var bos = new ByteArrayOutputStream();
+               try (var w = CborSerializer.DEFAULT.serializeTokens(bos)) {
+                       w.writeSimple(16);
+               }
+               try (var r = 
CborParser.DEFAULT_NATIVE.parseTokens(bos.toByteArray())) {
+                       assertEquals("VALUE_NULL(simple:16;)", render(r));
+               }
+       }
+
+       @Test void a03_msgpackExt() throws Exception {
+               var bos = new ByteArrayOutputStream();
+               try (var w = MsgPackSerializer.DEFAULT.serializeTokens(bos)) {
+                       w.writeExt(5, new byte[]{1, 2, 3, 4});
+               }
+               try (var r = 
MsgPackParser.DEFAULT_NATIVE.parseTokens(bos.toByteArray())) {
+                       assertEquals("VALUE_BINARY(ext:5;)", render(r));
+               }
+       }
+}
diff --git 
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/stream/FormatStreamingCapability_Test.java
 
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/stream/FormatStreamingCapability_Test.java
index fd040e6125..ea5709e9d1 100644
--- 
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/stream/FormatStreamingCapability_Test.java
+++ 
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/stream/FormatStreamingCapability_Test.java
@@ -132,4 +132,5 @@ class FormatStreamingCapability_Test extends TestBase {
                }
                assertTrue(checked > 0, "expected at least one format to expose 
an array-record writer");
        }
+
 }

Reply via email to