tlv
Version, currently 2.0.03 versions
- 2.0.0latestSep 12, 2026
- 1.1.0not indexedSep 12, 2026
- master branchFeb 12, 2026
github.com/Crystal-Matter/tlv
Matter TLV encoder/decoder
Installation
# Add this to your shard.yml
dependencies:
tlv:
github: Crystal-Matter/tlv
version: ~> 2.0.0Then run:
shards installshard.yml
- Crystal
- no constraint declared
- License
- MIT
- Author
- Stephen von Takach
Dependencies
Runtime Dependencies
- bindata>= 3.2github: spider-gazelle/bindata
README
TLV
Matter TLV encoder/decoder
Installation
-
Add the dependency to your
shard.yml:dependencies: tlv: github: spider-gazelle/tlv -
Run
shards install
Usage
require "tlv"
class User
include TLV::Serializable
@[TLV::Field(tag: 1)]
property first_name : String
@[TLV::Field(tag: 2)]
property last_name : String
end
class Packet
include TLV::Serializable
@[TLV::Field(tag: 1)]
property id : UInt8
@[TLV::Field(tag: 2)]
property port : UInt16
@[TLV::Field(tag: 3)]
property? duplex : Bool
@[TLV::Field(tag: 4)]
property message : String
@[TLV::Field(tag: 5)]
property encoded_message : Bytes
# arrays
@[TLV::Field(tag: 6)]
property array : Array(TLV::Value)
# lists
@[TLV::Field(tag: 1)]
property list : Tuple(UInt8, String, UInt16)
# Tuple serialized as TLV Array (homogeneous format)
@[TLV::Field(tag: 1, container: :array)]
property items : Tuple(UInt8, UInt8, UInt8)
# Array serialized as TLV List (heterogeneous format)
@[TLV::Field(tag: 1, container: :list)]
property items : Array(UInt8)
# Nested structures
@[TLV::Field(tag: 7)]
property user : User
@[TLV::Field(tag: 8)]
property optional_field : String?
# nilable required field
@[TLV::Field(tag: 9, optional: false)]
property not_optional_field : String?
# Common Profile Tag
@[TLV::Field(tag: {0x235A, 42})]
property common : UInt32
# Vendor Profile Tag
@[TLV::Field(tag: {0xFFFF, 0x235A, 42})]
property vendor : UInt32
end
io = IO::Memory.new # bytes from network etc
packet = io.read_bytes(Packet)
packet.to_slice
There is also a TLV::Any type which should really only be used externally for payloads with an anonymous type. (i.e. no wrapping structure)
require "tlv"
io = IO::Memory.new(Bytes[0x05, 0xF1, 0xFF]) # Anonymous UInt16 value 65521
any = io.read_bytes(TLV::Any)
any.header.element_type # => TLV::ElementType::UnsignedInt16
any.as_u16 # => 65521_u16
Custom field types
A type that includes TLV::Serializable encodes as a structure. For a value that
is a bare scalar on the wire, such as a wrapper around an identifier, register a
pair of overloads instead and the type can then be used as a field, including as
a nilable field or a member of a union.
struct NodeId
getter id : UInt64
def initialize(@id : UInt64)
end
def to_tlv(outer_tag : TLV::TagId? = nil) : TLV::Any
TLV::Any.new(@id, outer_tag)
end
def self.from_tlv(any : TLV::Any) : NodeId
new(TLV::Serializable.deserialize_value(any, UInt64))
end
end
module TLV::Serializable
def self.serialize_value(value : NodeId, tag, fixed_size : Bool = false) : TLV::Any
value.to_tlv(tag)
end
def self.deserialize_value(any : TLV::Any, type : NodeId.class) : NodeId
type.from_tlv(any)
end
end
Errors
Malformed input raises TLV::DeserializationError, carrying the structure and
field it failed on. A value that cannot be written raises
TLV::SerializationError, which is what an unregistered field type produces.
Contributing
- Fork it (https://github.com/spider-gazelle/tlv/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
Contributors
- Stephen von Takach - creator and maintainer
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This release
- Version
2.0.0- Tagged
- Sep 12, 2026
- Commit
9171c9a37667- Indexed
- yes
Dependents
Repository
github.com/Crystal-Matter/tlv
Metadata
- Created
- Aug 12, 2026
- Updated
- Sep 20, 2026
- Synced
- Sep 20, 2026
- Versions
- 3