More tags of the registry¶
Beyond the tags RFC 8949 defines itself, the library implements a good part of the IANA registry: the COSE and CWT structures, calendar dates, network addresses, the typed arrays and their shapes, and twenty more. All of them are registered in the decoder by default.
COSE and CWT¶
The six COSE structures of RFC 9052 and the CBOR Web Token tag of RFC 8392 that wraps them. Each one describes the shape of the structure and gives access to its parts; verifying a signature or a MAC is not done here and needs a COSE implementation such as web-auth/cose-lib.
| Tag | Structure | Class | Content |
|---|---|---|---|
| 16 | COSE_Encrypt0 | CoseEncrypt0Tag |
[protected, unprotected, ciphertext] |
| 17 | COSE_Mac0 | CoseMac0Tag |
[protected, unprotected, payload, tag] |
| 18 | COSE_Sign1 | CoseSign1Tag |
[protected, unprotected, payload, signature] |
| 61 | CWT | CwtTag |
a COSE structure, tagged or not, or a bare claims set |
| 96 | COSE_Encrypt | CoseEncryptTag |
[protected, unprotected, ciphertext, recipients] |
| 97 | COSE_Mac | CoseMacTag |
[protected, unprotected, payload, tag, recipients] |
| 98 | COSE_Sign | CoseSignTag |
[protected, unprotected, payload, signatures] |
use CBOR\Decoder;
use CBOR\StringStream;
use CBOR\Tag\CoseSign1Tag;
$object = Decoder::create()->decode(StringStream::create($encoded));
if ($object instanceof CoseSign1Tag) {
// The protected header is signed as it was written, so it travels wrapped in a byte string.
$algorithm = $object->getProtectedHeaderAsMap()->normalize()[1] ?? null;
$payload = $object->getPayload(); // ByteStringObject, or NullObject when detached
$signature = $object->getSignature();
}
Two shapes a stricter reading would turn away are accepted on purpose:
- a detached payload, which COSE writes as
null: the content travels out of band and only the signature comes with the structure; - an absent protected header, which COSE writes as an empty byte string:
getProtectedHeaderAsMap()answers an emptyMapObjectrather than failing to parse zero bytes.
To build one, createFromComponents() takes the parts and serializes the protected header for you:
$tag = CoseSign1Tag::createFromComponents(
$protectedHeader, // MapObject; encoded into the byte string COSE signs
$unprotectedHeader, // MapObject
ByteStringObject::create($payload),
ByteStringObject::create($signature)
);
Calendar dates¶
A calendar day is not an instant: a birthday or an expiry date is the same day everywhere, and pinning it to a moment moves it across a day boundary depending on where it is read. RFC 8943 gives it two tags of its own.
| Tag | Description | Class | Accepts |
|---|---|---|---|
| 100 | Days since 1970-01-01 | DateTag |
UnsignedIntegerObject, NegativeIntegerObject |
| 1004 | RFC 3339 full-date, YYYY-MM-DD |
DateStringTag |
TextStringObject |
use CBOR\Tag\DateTag;
use CBOR\UnsignedIntegerObject;
$tag = DateTag::create(UnsignedIntegerObject::create(19737));
echo $tag->normalize()->format('Y-m-d'); // 2024-01-15
PHP has no date-only type, so both normalize to midnight UTC — the reading that keeps the day intact —
and neither is affected by the ambient time zone. A day that does not exist, 2013-02-30, is rejected
rather than rolled over into the next month.
Network addresses¶
| Tag | Description | Class | Accepts |
|---|---|---|---|
| 52 | IPv4 address, prefix or zoned address | Ipv4Tag |
ByteStringObject, or a two-item ListObject |
| 54 | IPv6 address, prefix or zoned address | Ipv6Tag |
ByteStringObject, or a two-item ListObject |
| 260 | Network address: IPv4, IPv6 or MAC | NetworkAddressTag |
a 4, 6 or 16 byte ByteStringObject |
| 261 | Network address prefix | NetworkAddressPrefixTag |
a single-entry MapObject |
Tags 52 and 54 come from RFC 9164 and carry three shapes
under one number, told apart by their first item: a byte string is an address, [length, bytes] is a
prefix whose trailing zero bytes may be left out, and [bytes, zone] is an address with the zone that
scopes it.
use CBOR\Tag\Ipv6Tag;
echo Ipv6Tag::createFromAddress('2001:db8::1')->normalize(); // 2001:db8::1
// A prefix normalizes with its length, a zoned address with its zone:
// "2001:db8::/32", "fe80::1%eth0"
Tags 260 and 261 are the earlier form, which tells the family from the length of the byte string. RFC 9164 supersedes them: prefer 52 and 54 for anything new.
Typed arrays¶
RFC 8746 packs an array of numbers of one fixed type into a single byte string: a megabyte of samples travels as a megabyte instead of as three. The tag number gives the element type, so the only thing the content has to satisfy is being a whole number of elements long.
| Tags | Element | Classes, in CBOR\Tag\TypedArray |
|---|---|---|
| 64, 68 | uint8, uint8 clamped | Uint8ArrayTag, Uint8ClampedArrayTag |
| 65, 69 | uint16 big, little endian | Uint16BigEndianArrayTag, Uint16LittleEndianArrayTag |
| 66, 70 | uint32 big, little endian | Uint32BigEndianArrayTag, Uint32LittleEndianArrayTag |
| 67, 71 | uint64 big, little endian | Uint64BigEndianArrayTag, Uint64LittleEndianArrayTag |
| 72 | sint8 | Sint8ArrayTag |
| 73, 77 | sint16 big, little endian | Sint16BigEndianArrayTag, Sint16LittleEndianArrayTag |
| 74, 78 | sint32 big, little endian | Sint32BigEndianArrayTag, Sint32LittleEndianArrayTag |
| 75, 79 | sint64 big, little endian | Sint64BigEndianArrayTag, Sint64LittleEndianArrayTag |
| 80, 84 | binary16 big, little endian | Float16BigEndianArrayTag, Float16LittleEndianArrayTag |
| 81, 85 | binary32 big, little endian | Float32BigEndianArrayTag, Float32LittleEndianArrayTag |
| 82, 86 | binary64 big, little endian | Float64BigEndianArrayTag, Float64LittleEndianArrayTag |
| 83, 87 | binary128 big, little endian | Float128BigEndianArrayTag, Float128LittleEndianArrayTag |
use CBOR\ByteStringObject;
use CBOR\Tag\TypedArray\Uint16BigEndianArrayTag;
$tag = Uint16BigEndianArrayTag::create(ByteStringObject::create("\x00\x01\x00\x02"));
count($tag); // 2
$tag->normalize(); // [1, 2]
Normalized, they give a list of int or float. A uint64 above PHP_INT_MAX comes back as a decimal
string rather than as a negative integer.
binary128 is the exception
PHP has no quadruple precision float, and returning a binary64 would quietly drop fifteen digits, so
tags 83 and 87 do not implement Normalizable. Use getChunks(), which hands back the 16 bytes of
each element.
Arrays with a shape¶
The same RFC folds a flat array — a plain one or a typed array — into a shape, as [dimensions, values].
| Tag | Order | Class |
|---|---|---|
| 40 | Row-major, last index varies fastest | RowMajorMultiDimensionalArrayTag |
| 1040 | Column-major, first index varies fastest | ColumnMajorMultiDimensionalArrayTag |
// 40([[2, 3], [1, 2, 3, 4, 5, 6]])
$tag->getDimensions(); // [2, 3]
$tag->normalize(); // [[1, 2, 3], [4, 5, 6]]
// The same values under tag 1040 fold the other way: [[1, 3, 5], [2, 4, 6]]
normalize() rejects a document whose dimensions do not account for exactly the number of values it
carries.
Tag 41, HomogeneousArrayTag, belongs to the same RFC. It is a hint that every item of an array has the
same type; the array normalizes as it would untagged, and homogeneity is not checked, since what counts as
"the same type" is the application's data model rather than CBOR's major types.
The rest¶
| Tag | Description | Class | Accepts |
|---|---|---|---|
| 25 | String reference | StringReferenceTag |
UnsignedIntegerObject |
| 26 | Serialised Perl object | PerlObjectTag |
ListObject opening with a text string |
| 27 | Serialised language-independent object | LanguageIndependentObjectTag |
ListObject opening with a text string |
| 28 | Shareable value | ShareableTag |
any object |
| 29 | Shared value reference | SharedReferenceTag |
UnsignedIntegerObject |
| 30 | Rational number | RationalNumberTag |
two-item ListObject, [numerator, denominator] |
| 35 | Regular expression | RegexpTag |
TextStringObject |
| 37 | Binary UUID | UuidTag |
a 16 byte ByteStringObject |
| 38 | Language-tagged string | LanguageTaggedStringTag |
two-item ListObject, [language, text] |
| 39 | Identifier | IdentifierTag |
any object |
| 42 | IPLD content identifier, a CID | IpldContentIdentifierTag |
ByteStringObject |
| 63 | Encoded CBOR sequence | CBORSequenceTag |
ByteStringObject |
| 256 | String reference namespace | StringReferenceNamespaceTag |
any object |
| 257 | Binary MIME message | BinaryMimeTag |
ByteStringObject |
| 258 | Mathematical finite set | SetTag |
ListObject |
| 259 | Explicit map | ExplicitMapTag |
MapObject |
| 1001 | Extended time | ExtendedTimeTag |
MapObject |
| 1002 | Duration | DurationTag |
MapObject |
| 1003 | Period | PeriodTag |
ListObject |
A few of them are worth a word.
Tag 37, a UUID, normalizes to the canonical 8-4-4-4-12 form and can be built from it:
use CBOR\Tag\UuidTag;
$tag = UuidTag::createFromUuidString('01234567-89ab-cdef-0123-456789abcdef');
echo $tag->normalize(); // 01234567-89ab-cdef-0123-456789abcdef
Tag 30, a rational number, normalizes to lowest terms, as "numerator/denominator" — or as the
numerator alone when the denominator reduces to 1. Either part may be a bignum
(tags 2 and 3), which is the point of the tag: a ratio such as ⅓ has no exact
decimal or binary floating point form.
Tag 63, a CBOR sequence, is to a sequence what
tag 24 is to a single item. getSequence() decodes the
byte string into the items it holds:
Tag 258, a set, returns its items in the order they were written. Nothing in CBOR keeps a duplicate out of an array, so a repeated item is left for the application to notice — and, most often, to reject the document over, since a set that carries one is not what it claims to be.
Tags 25, 28, 29 and 256, string references and value sharing, are recorded rather than resolved. The table a reference points into is built while the whole document is read, which is outside the scope of any single tag, so the index is returned as it stands and the marked value normalizes as it would untagged.
Tags 1001, 1002 and 1003, RFC 9581, are returned as
the map or array they are. The components they allow — a leap second, a time scale that is not UTC, a
resolution below the microsecond — do not all fit DateTimeImmutable or DateInterval, and flattening
them into one would drop whichever does not.