Compare commits
10 commits
2d6fedf749
...
b8ebc57bb7
| Author | SHA1 | Date | |
|---|---|---|---|
| b8ebc57bb7 | |||
| 4ac38a7598 | |||
| 94f47dd728 | |||
| 5f838d36cc | |||
|
|
3ab21c9616 | ||
|
|
21356c8bcf | ||
|
|
e05b209ed3 | ||
|
|
2e76c0b6d3 | ||
|
|
d7a11f5b43 | ||
|
|
529ec14773 |
15 changed files with 148 additions and 46 deletions
22
CHANGELOG.md
22
CHANGELOG.md
|
|
@ -1,4 +1,5 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
|
|
@ -6,7 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.2.1] - 2020-09-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Bug when passing an `IO` to `to_cbor`
|
||||
|
||||
## [0.2.0] - 2020-06-18
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgraded to Crystal 0.35.0
|
||||
|
||||
## [0.1.1] - 2020-06-02
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix encoding of CBOR tags and as a consequence, the encoding of `Time`
|
||||
|
||||
## [0.1.0] - 2020-06-01
|
||||
|
||||
* Initial Release
|
||||
|
||||
- Initial Release
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
name: cbor
|
||||
version: 0.1.0
|
||||
version: 0.2.1
|
||||
|
||||
authors:
|
||||
- Alberto Restifo <alberto@restifo.dev>
|
||||
|
||||
description: "A CBOR library in pure Crystal, impleming RFC7049"
|
||||
description: "A CBOR library in pure Crystal, implementing RFC7049"
|
||||
|
||||
crystal: 0.34.0
|
||||
crystal: 0.35.0
|
||||
|
||||
license: MIT
|
||||
|
||||
repository: https://git.sr.ht/~arestifo/crystal-cbor
|
||||
homepage: https://git.sr.ht/~arestifo/crystal-cbor
|
||||
homepage: https://sr.ht/~arestifo/crystal-cbor/
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ describe CBOR::Encoder do
|
|||
bytes_arr = hex_string.split.map(&.to_u8(16))
|
||||
want_bytes = Bytes.new(bytes_arr.to_unsafe, bytes_arr.size)
|
||||
|
||||
it "econdes #{value.to_s} to #{want_bytes.hexstring}" do
|
||||
it "encodes #{value} to #{want_bytes.hexstring}" do
|
||||
res = IO::Memory.new
|
||||
|
||||
encoder = CBOR::Encoder.new(res)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ describe CBOR::Lexer do
|
|||
]
|
||||
|
||||
tests.each do |tt|
|
||||
it "reads #{tt[:bytes].hexstring} as #{tt[:value].to_s}" do
|
||||
it "reads #{tt[:bytes].hexstring} as #{tt[:value]}" do
|
||||
lexer = CBOR::Lexer.new(tt[:bytes])
|
||||
|
||||
token = lexer.next_token
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ end
|
|||
|
||||
describe CBOR::Serializable do
|
||||
describe "rfc examples" do
|
||||
describe %(example {_ "a": 1, "b": [_ 2, 3]}) do
|
||||
describe %(example {"a": 1, "b": [2, 3]}) do
|
||||
it "decodes from cbor" do
|
||||
result = ExampleA.from_cbor(Bytes[0xbf, 0x61, 0x61, 0x01, 0x61, 0x62, 0x9f, 0x02, 0x03, 0xff, 0xff])
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ describe CBOR::Serializable do
|
|||
end
|
||||
end
|
||||
|
||||
describe %(example {_ "Fun": true, "Amt": -2}) do
|
||||
describe %(example {"Fun": true, "Amt": -2}) do
|
||||
it "decodes from cbor" do
|
||||
result = ExampleB.from_cbor(Bytes[0xbf, 0x63, 0x46, 0x75, 0x6e, 0xf5, 0x63, 0x41, 0x6d, 0x74, 0x21, 0xff])
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ describe CBOR::Serializable do
|
|||
end
|
||||
end
|
||||
|
||||
describe %(example ["a", {_ "b": "c"}]) do
|
||||
describe %(example ["a", {"b": "c"}]) do
|
||||
it "decodes from cbor" do
|
||||
result = Array(String | ExampleC).from_cbor(Bytes[0x82, 0x61, 0x61, 0xbf, 0x61, 0x62, 0x61, 0x63, 0xff])
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ describe CBOR::Serializable do
|
|||
|
||||
it "encodes to CBOR" do
|
||||
cbor = House.from_cbor(bytes).to_cbor
|
||||
CBOR::Diagnostic.to_s(cbor).should eq(%({_ "address": "Crystal Road 1234", "location": {_ "lat": 12.3, "lng": 34.5}}))
|
||||
CBOR::Diagnostic.to_s(cbor).should eq(%({"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}}))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -168,7 +168,7 @@ describe CBOR::Serializable do
|
|||
it "encodes to CBOR" do
|
||||
cbor = Array(House).from_cbor(bytes).to_cbor
|
||||
|
||||
CBOR::Diagnostic.to_s(cbor).should eq(%([{_ "address": "Crystal Road 1234", "location": {_ "lat": 12.3, "lng": 34.5}}]))
|
||||
CBOR::Diagnostic.to_s(cbor).should eq(%([{"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}}]))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ describe CBOR::Serializable do
|
|||
res.a.should eq(1)
|
||||
res.cbor_unmapped.should eq({"b" => 2})
|
||||
|
||||
CBOR::Diagnostic.to_s(res.to_cbor).should eq(%({_ "a": 1, "b": 2}))
|
||||
CBOR::Diagnostic.to_s(res.to_cbor).should eq(%({"a": 1, "b": 2}))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ describe "to_cbor" do
|
|||
tests.each do |tt|
|
||||
type, bytes, value = tt
|
||||
|
||||
it "encodes #{value.inspect} of type #{type.to_s}" do
|
||||
it "encodes #{value.inspect} of type #{type}" do
|
||||
res = value.to_cbor
|
||||
res.hexdump.should eq(bytes.hexdump)
|
||||
end
|
||||
|
|
@ -58,4 +58,13 @@ describe "to_cbor" do
|
|||
encoder.to_slice.hexdump.should eq(Bytes[0xc1, 0x1a, 0x51, 0x4b, 0x67, 0xb0].hexdump)
|
||||
end
|
||||
end
|
||||
|
||||
describe "encodes to an IO" do
|
||||
it "encodes a string" do
|
||||
io = IO::Memory.new
|
||||
"a".to_cbor(io)
|
||||
|
||||
io.to_slice.should eq(Bytes[0x61, 0x61])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@ class CBOR::Decoder
|
|||
@lexer : Lexer
|
||||
getter current_token : Token::T?
|
||||
|
||||
def reset
|
||||
@lexer.reset
|
||||
@current_token = @lexer.next_token
|
||||
end
|
||||
|
||||
def initialize(input)
|
||||
@lexer = Lexer.new(input)
|
||||
@current_token = @lexer.next_token
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Reads a CBOR input into a diagnostic string.
|
||||
# This consumes the IO and is mostly usedful to tests again the example
|
||||
# This consumes the IO and is mostly useful to tests again the example
|
||||
# provided in the RFC and ensuring a correct functioning of the `CBOR::Lexer`.
|
||||
class CBOR::Diagnostic
|
||||
@lexer : Lexer
|
||||
|
|
@ -63,7 +63,7 @@ class CBOR::Diagnostic
|
|||
when Tag::NegativeBigNum
|
||||
read_big_int(negative: true)
|
||||
else
|
||||
"#{token.value.value.to_s}(#{next_value})"
|
||||
"#{token.value.value}(#{next_value})"
|
||||
end
|
||||
when Token::FloatT
|
||||
return "NaN" if token.value.nan?
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class CBOR::Encoder
|
|||
return write(value.to_u64) if value >= 0
|
||||
|
||||
# When it's negative, transform it into a positive value and write the
|
||||
# resulting unsigled int with an offset
|
||||
# resulting unsigned int with an offset
|
||||
positive_value = -(value + 1)
|
||||
write(positive_value.to_u64, 0x20)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ def Object.from_cbor(string_or_io)
|
|||
new(parser)
|
||||
end
|
||||
|
||||
def Object.from_cbor(parser : CBOR::Decoder)
|
||||
new(parser)
|
||||
end
|
||||
|
||||
def String.new(decoder : CBOR::Decoder)
|
||||
decoder.read_string
|
||||
end
|
||||
|
|
@ -180,7 +184,7 @@ def BigInt.new(decoder : CBOR::Decoder)
|
|||
end
|
||||
|
||||
# Reads the CBOR value as a BigDecimal.
|
||||
# If the next token is a flaot, then it'll be transformed to a BigDecimal,
|
||||
# If the next token is a float, then it'll be transformed to a BigDecimal,
|
||||
# otherwhise the value must be correctly tagged with value 4 (decimal fraction)
|
||||
# or 5 (big float).
|
||||
def BigDecimal.new(decoder : CBOR::Decoder)
|
||||
|
|
@ -231,7 +235,7 @@ def Union.new(decoder : CBOR::Decoder)
|
|||
return {{type}}.new(decoder)
|
||||
{% end %}
|
||||
else
|
||||
# This case check is non-exaustive on purpose
|
||||
# This case check is non-exhaustive on purpose
|
||||
end
|
||||
{% end %}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ class CBOR::Lexer
|
|||
def initialize(@io : IO)
|
||||
end
|
||||
|
||||
def reset
|
||||
@io.seek 0
|
||||
@eof = false
|
||||
end
|
||||
|
||||
def next_token : Token::T?
|
||||
return nil if @eof
|
||||
|
||||
|
|
@ -164,7 +169,7 @@ class CBOR::Lexer
|
|||
end
|
||||
|
||||
private def consume_simple_value(id) : Token::SimpleValueT
|
||||
raise ParseError.new("Invalid simple value #{id.to_s}") if id > 255
|
||||
raise ParseError.new("Invalid simple value #{id}") if id > 255
|
||||
Token::SimpleValueT.new(value: SimpleValue.new(id.to_u8))
|
||||
end
|
||||
|
||||
|
|
@ -176,7 +181,7 @@ class CBOR::Lexer
|
|||
{% conv = %w(to_i8 to_i16 to_i32 to_i64 to_i128) %}
|
||||
|
||||
{% for uint, index in uints %}
|
||||
# Reads the `{{uint.id}}` as a negative integer, returning the samllest
|
||||
# Reads the `{{uint.id}}` as a negative integer, returning the smallest
|
||||
# integer capable of containing the value.
|
||||
def to_negative_int(value : {{uint.id}})
|
||||
int = begin
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ module CBOR
|
|||
end
|
||||
|
||||
private def self.new_from_cbor_decoder(decoder : ::CBOR::Decoder)
|
||||
# puts "self.new_from_cbor_decoder"
|
||||
instance = allocate
|
||||
instance.initialize(__decoder_for_cbor_serializable: decoder)
|
||||
GC.add_finalizer(instance) if instance.responds_to?(:finalize)
|
||||
|
|
@ -143,7 +144,7 @@ module CBOR
|
|||
end
|
||||
|
||||
# When the type is inherited, carry over the `new`
|
||||
# so it can compete with other possible intializes
|
||||
# so it can compete with other possible initializes
|
||||
|
||||
macro inherited
|
||||
def self.new(decoder : ::CBOR::Decoder)
|
||||
|
|
@ -243,6 +244,10 @@ module CBOR
|
|||
raise ::CBOR::SerializationError.new("Unknown CBOR attribute: #{key}", self.class.to_s, nil)
|
||||
end
|
||||
|
||||
protected def get_cbor_unmapped
|
||||
{} of String => ::CBOR::Type
|
||||
end
|
||||
|
||||
protected def on_to_cbor(cbor : ::CBOR::Encoder)
|
||||
end
|
||||
|
||||
|
|
@ -268,9 +273,28 @@ module CBOR
|
|||
{% end %}
|
||||
{% end %}
|
||||
|
||||
cbor.object do
|
||||
{% for name, value in properties %}
|
||||
# Compute the size of the final list of properties to serialize.
|
||||
# This allows a more compact encoding, and a faster decoding.
|
||||
nb_properties_to_serialize = 0
|
||||
{% for name, value in properties %}
|
||||
_{{name}} = @{{name}}
|
||||
{% unless value[:emit_null] %}
|
||||
unless _{{name}}.nil?
|
||||
nb_properties_to_serialize += 1
|
||||
end
|
||||
{% else %}
|
||||
nb_properties_to_serialize += 1
|
||||
{% end %} # macro unless value[:emit_null]
|
||||
{% end %} # macro for properties
|
||||
|
||||
nb_properties_to_serialize += get_cbor_unmapped.size
|
||||
|
||||
|
||||
{% if properties.size > 0 %}
|
||||
cbor.write_object_start nb_properties_to_serialize
|
||||
|
||||
{% for name, value in properties %}
|
||||
_{{name}} = @{{name}}
|
||||
|
||||
{% unless value[:emit_null] %}
|
||||
unless _{{name}}.nil?
|
||||
|
|
@ -279,23 +303,23 @@ module CBOR
|
|||
# Write the key of the map
|
||||
cbor.write({{value[:key]}})
|
||||
|
||||
{% if value[:converter] %}
|
||||
if _{{name}}
|
||||
{{ value[:converter] }}.to_cbor(_{{name}}, cbor)
|
||||
else
|
||||
cbor.write(nil, use_undefined: value[:nil_as_undefined])
|
||||
end
|
||||
{% else %}
|
||||
_{{name}}.to_cbor(cbor)
|
||||
{% end %}
|
||||
{% if value[:converter] %}
|
||||
if _{{name}}
|
||||
{{ value[:converter] }}.to_cbor(_{{name}}, cbor)
|
||||
else
|
||||
cbor.write(nil, use_undefined: value[:nil_as_undefined])
|
||||
end
|
||||
{% else %} # macro if value[:converter]
|
||||
_{{name}}.to_cbor(cbor)
|
||||
{% end %} # macro if value[:converter]
|
||||
|
||||
{% unless value[:emit_null] %}
|
||||
end
|
||||
{% end %}
|
||||
{% end %}
|
||||
end # unless _{{name}}.nil?
|
||||
{% end %} # macro unless value[:emit_null]
|
||||
{% end %} # macro for properties
|
||||
on_to_cbor(cbor)
|
||||
end
|
||||
{% end %}
|
||||
{% end %} # macro if properties.size > 0
|
||||
{% end %} # begin
|
||||
end
|
||||
|
||||
module Unmapped
|
||||
|
|
@ -310,6 +334,10 @@ module CBOR
|
|||
end
|
||||
end
|
||||
|
||||
protected def get_cbor_unmapped
|
||||
cbor_unmapped
|
||||
end
|
||||
|
||||
protected def on_to_cbor(cbor : ::CBOR::Encoder)
|
||||
cbor_unmapped.each do |key, value|
|
||||
cbor.write(key)
|
||||
|
|
@ -318,6 +346,35 @@ module CBOR
|
|||
end
|
||||
end
|
||||
|
||||
macro use_cbor_discriminator(field, mapping)
|
||||
{% unless mapping.is_a?(HashLiteral) || mapping.is_a?(NamedTupleLiteral) %}
|
||||
{% mapping.raise "mapping argument must be a HashLiteral or a NamedTupleLiteral, not #{mapping.class_name.id}" %}
|
||||
{% end %}
|
||||
|
||||
# SLOW. Read everything, get the type, read everything again.
|
||||
def self.new(decoder : ::CBOR::Decoder)
|
||||
if v = decoder.read_value
|
||||
decoder.reset
|
||||
case v
|
||||
when Hash(CBOR::Type, CBOR::Type)
|
||||
discriminator_value = v[{{field.id.stringify}}]?
|
||||
case discriminator_value
|
||||
{% for key, value in mapping %}
|
||||
when {{key.id.stringify}}
|
||||
return {{value.id}}.from_cbor(decoder)
|
||||
{% end %}
|
||||
else
|
||||
raise "Unknown '{{field.id}}' discriminator value: #{discriminator_value.inspect}"
|
||||
end
|
||||
else
|
||||
raise "cannot get cbor discriminator #{ {{ field.id.stringify }} }"
|
||||
end
|
||||
else
|
||||
raise "cannot decode cbor value"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Tells this class to decode CBOR by using a field as a discriminator.
|
||||
#
|
||||
# - *field* must be the field name to use as a discriminator
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ enum CBOR::SimpleValue : UInt8
|
|||
when Undefined
|
||||
"undefined"
|
||||
else
|
||||
"simple(#{self.value.to_s})"
|
||||
"simple(#{self.value})"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ enum CBOR::Tag : UInt32
|
|||
Decimal
|
||||
BigFloat
|
||||
|
||||
CSOEEnCrypt = 16
|
||||
CSOEMac
|
||||
CSOESign
|
||||
COSE_Encrypt0 = 16 # COSE Single Recipient Encrypted Data Object
|
||||
COSE_Mac0 = 17 # COSE Mac w/o Recipients Object
|
||||
COSE_Sign1 = 18 # COSE Single Signer Data Object
|
||||
|
||||
ExpectBase64URL = 21
|
||||
ExpectBase64
|
||||
|
|
@ -26,5 +26,9 @@ enum CBOR::Tag : UInt32
|
|||
|
||||
CBORWebToken = 61
|
||||
|
||||
COSE_Encrypt = 96 # COSE Encrypted Data Object
|
||||
COSE_Mac = 97 # COSE MACed Data Object
|
||||
COSE_Sign = 98 # COSE Signed Data Object
|
||||
|
||||
SelfDescribeCBOR = 55799
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ class Object
|
|||
end
|
||||
|
||||
def to_cbor(io : IO)
|
||||
encoder = CBOR::Encoder.new
|
||||
encoder = CBOR::Encoder.new(io)
|
||||
to_cbor(encoder)
|
||||
self
|
||||
end
|
||||
|
|
@ -79,7 +79,7 @@ module Time::Format::RFC_3339
|
|||
end
|
||||
|
||||
module Time::EpochConverter
|
||||
# Emits the time as a tagged unix timestamp, asp specified by
|
||||
# Emits the time as a tagged unix timestamp, as specified by
|
||||
# [RFC 7049 section 2.4.1](https://tools.ietf.org/html/rfc7049#section-2.4.1).
|
||||
#
|
||||
def self.to_cbor(value : Time, encoder : CBOR::Encoder)
|
||||
|
|
@ -108,7 +108,7 @@ struct Time
|
|||
end
|
||||
|
||||
# struct BigInt
|
||||
# # Encodes the value a bytes arrya tagged with the CBOR tag 2 or 3, as specified
|
||||
# # Encodes the value a bytes array tagged with the CBOR tag 2 or 3, as specified
|
||||
# # in [RFC 7049 Section 2.4.2](https://tools.ietf.org/html/rfc7049#section-2.4.2).
|
||||
# def to_cbor(encoder : CBOR::Encoder)
|
||||
# encoded_value = BigInt.new(self)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue