ciborium 0.1.0-aarch64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +9 -0
  3. data/LICENSE.txt +21 -0
  4. data/NOTICE.md +26 -0
  5. data/README.md +221 -0
  6. data/Rakefile +46 -0
  7. data/lib/ciborium/3.3/ciborium_native.so +0 -0
  8. data/lib/ciborium/3.4/ciborium_native.so +0 -0
  9. data/lib/ciborium/4.0/ciborium_native.so +0 -0
  10. data/lib/ciborium/compat/buffer.rb +190 -0
  11. data/lib/ciborium/compat/errors.rb +30 -0
  12. data/lib/ciborium/compat/packer.rb +90 -0
  13. data/lib/ciborium/compat/unpacker.rb +111 -0
  14. data/lib/ciborium/compat.rb +110 -0
  15. data/lib/ciborium/decode_options.rb +61 -0
  16. data/lib/ciborium/decoder.rb +141 -0
  17. data/lib/ciborium/encode_options.rb +41 -0
  18. data/lib/ciborium/encoded.rb +10 -0
  19. data/lib/ciborium/encoder.rb +80 -0
  20. data/lib/ciborium/errors.rb +25 -0
  21. data/lib/ciborium/extensions/bignum.rb +35 -0
  22. data/lib/ciborium/extensions/regexp.rb +41 -0
  23. data/lib/ciborium/extensions/time.rb +39 -0
  24. data/lib/ciborium/extensions/uri.rb +29 -0
  25. data/lib/ciborium/extensions.rb +92 -0
  26. data/lib/ciborium/native.rb +25 -0
  27. data/lib/ciborium/replace.rb +105 -0
  28. data/lib/ciborium/simple.rb +21 -0
  29. data/lib/ciborium/tagged.rb +18 -0
  30. data/lib/ciborium/version.rb +6 -0
  31. data/lib/ciborium.rb +65 -0
  32. data/licenses/cfg-if-LICENSE-APACHE +201 -0
  33. data/licenses/cfg-if-LICENSE-MIT +25 -0
  34. data/licenses/ciborium-io-LICENSE +201 -0
  35. data/licenses/ciborium-ll-LICENSE +201 -0
  36. data/licenses/half-LICENSE-APACHE +176 -0
  37. data/licenses/half-LICENSE-MIT +19 -0
  38. data/licenses/magnus-LICENSE +21 -0
  39. data/licenses/rb-sys-LICENSE-APACHE +190 -0
  40. data/licenses/rb-sys-LICENSE-MIT +21 -0
  41. data/licenses/zerocopy-LICENSE-APACHE +202 -0
  42. data/licenses/zerocopy-LICENSE-BSD +24 -0
  43. data/licenses/zerocopy-LICENSE-MIT +26 -0
  44. data/sig/generated/ciborium/compat/buffer.rbs +95 -0
  45. data/sig/generated/ciborium/compat/errors.rbs +23 -0
  46. data/sig/generated/ciborium/compat/packer.rbs +53 -0
  47. data/sig/generated/ciborium/compat/unpacker.rbs +59 -0
  48. data/sig/generated/ciborium/compat.rbs +57 -0
  49. data/sig/generated/ciborium/decode_options.rbs +41 -0
  50. data/sig/generated/ciborium/decoder.rbs +85 -0
  51. data/sig/generated/ciborium/encode_options.rbs +33 -0
  52. data/sig/generated/ciborium/encoded.rbs +16 -0
  53. data/sig/generated/ciborium/encoder.rbs +50 -0
  54. data/sig/generated/ciborium/errors.rbs +32 -0
  55. data/sig/generated/ciborium/extensions/bignum.rbs +20 -0
  56. data/sig/generated/ciborium/extensions/regexp.rbs +30 -0
  57. data/sig/generated/ciborium/extensions/time.rbs +22 -0
  58. data/sig/generated/ciborium/extensions/uri.rbs +20 -0
  59. data/sig/generated/ciborium/extensions.rbs +35 -0
  60. data/sig/generated/ciborium/native.rbs +10 -0
  61. data/sig/generated/ciborium/replace.rbs +37 -0
  62. data/sig/generated/ciborium/version.rbs +5 -0
  63. data/sig/generated/ciborium.rbs +30 -0
  64. data/sig/manual/ciborium/extensions.rbs +12 -0
  65. data/sig/manual/ciborium/native.rbs +23 -0
  66. data/sig/manual/ciborium/tagged.rbs +25 -0
  67. metadata +115 -0
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ module Compat
6
+ # CBOR::Unpacker of the cbor gem, reading through Ciborium::Decoder. Input passes through
7
+ # buffer, which also reads from io when more is needed.
8
+ class Unpacker
9
+ # @rbs @buffer: Buffer
10
+ # @rbs @decoder: Ciborium::Decoder
11
+
12
+ attr_reader :buffer #: Buffer
13
+
14
+ # options takes symbolize_keys, and the Buffer options.
15
+ #
16
+ #: (?untyped io, ?Hash[Symbol, untyped]? options) -> void
17
+ def initialize(io = nil, options = nil)
18
+ if options.nil? && io.is_a?(Hash)
19
+ options = io
20
+ io = nil
21
+ end
22
+ @buffer = Buffer.new(io, options)
23
+ @decoder = Ciborium::Decoder.new(**Compat.decode_options, symbolize_keys: !!options&.[](:symbolize_keys))
24
+ end
25
+
26
+ #: (String data) -> self
27
+ def feed(data)
28
+ @buffer << data
29
+ self
30
+ end
31
+
32
+ #: () -> untyped
33
+ def read
34
+ with_input { @decoder.read }
35
+ end
36
+
37
+ alias_method :unpack, :read
38
+
39
+ #: () -> nil
40
+ def skip
41
+ with_input { @decoder.skip }
42
+ nil
43
+ end
44
+
45
+ # Whether the next item is null. Consumes nothing.
46
+ #
47
+ #: () -> bool
48
+ def skip_nil
49
+ with_input { @decoder.next_nil? }
50
+ end
51
+
52
+ #: () -> Integer
53
+ def read_array_header
54
+ with_input { @decoder.read_array_header }
55
+ end
56
+
57
+ #: () -> Integer
58
+ def read_map_header
59
+ with_input { @decoder.read_map_header }
60
+ end
61
+
62
+ # Yields each item until the input runs out, leaving a partial item buffered.
63
+ #
64
+ #: () { (untyped) -> void } -> nil
65
+ #: () -> Enumerator[untyped, nil]
66
+ def each
67
+ return enum_for(:each) unless block_given?
68
+
69
+ loop do
70
+ item = begin
71
+ read
72
+ rescue ::EOFError
73
+ break
74
+ end
75
+ yield item
76
+ end
77
+ nil
78
+ end
79
+
80
+ #: (String data) { (untyped) -> void } -> nil
81
+ def feed_each(data, &block)
82
+ feed(data)
83
+ each(&block)
84
+ end
85
+
86
+ #: () -> nil
87
+ def reset
88
+ @buffer.clear
89
+ @decoder.reset
90
+ nil
91
+ end
92
+
93
+ # Runs the block on the decoder, moving more input into it until the block stops raising
94
+ # IncompleteError or the input ends.
95
+ #
96
+ #: [T] () { () -> T } -> T
97
+ private def with_input
98
+ Compat.translate_errors do
99
+ @decoder.feed(@buffer.read_all(@buffer.size)) unless @buffer.empty?
100
+ yield
101
+ rescue IncompleteError
102
+ chunk = @buffer.read(Buffer::DEFAULT_IO_BUFFER_SIZE)
103
+ raise unless chunk
104
+
105
+ @decoder.feed(chunk)
106
+ retry
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative "../ciborium"
5
+ require_relative "compat/errors"
6
+ require_relative "compat/buffer"
7
+ require_relative "compat/packer"
8
+ require_relative "compat/unpacker"
9
+
10
+ module Ciborium
11
+ # The interface of the cbor gem (CBOR), implemented on top of Ciborium. Tag extensions follow
12
+ # Compat.extensions, so a tag that is not allowlisted decodes to Tagged. require
13
+ # "ciborium/replace" to install it as CBOR.
14
+ module Compat
15
+ Tagged = Ciborium::Tagged
16
+ Simple = Ciborium::Simple
17
+
18
+ # Nothing is allowlisted until the application opts in, as with Ciborium itself.
19
+ DEFAULT_EXTENSIONS = [] #: Array[extension]
20
+ DEFAULT_EXTENSIONS.freeze
21
+
22
+ # @rbs self.@extensions: Array[extension]
23
+ @extensions = DEFAULT_EXTENSIONS
24
+
25
+ # The extensions every Compat entry point uses, process-wide.
26
+ #
27
+ #: () -> Array[extension]
28
+ def self.extensions = @extensions
29
+
30
+ #: (Array[extension] extensions) -> void
31
+ def self.extensions=(extensions)
32
+ Extensions.resolve(extensions)
33
+ @extensions = extensions.dup.freeze
34
+ end
35
+
36
+ # Options for Ciborium::Encoder that reproduce the cbor gem's encoding.
37
+ #
38
+ #: () -> Hash[Symbol, untyped]
39
+ def self.encode_options
40
+ {extensions:, fallback: FALLBACK}
41
+ end
42
+
43
+ # Options for Ciborium::Decoder that reproduce the cbor gem's decoding.
44
+ #
45
+ #: () -> Hash[Symbol, untyped]
46
+ def self.decode_options
47
+ {extensions:, duplicate_keys: :last_wins}
48
+ end
49
+
50
+ # Encodes object, or writes it to io and returns nil when io is given.
51
+ #
52
+ #: (untyped object, ?untyped io) -> String?
53
+ def self.encode(object, io = nil)
54
+ bytes = Ciborium.encode(object, **encode_options)
55
+ return bytes unless io
56
+
57
+ io.write(bytes)
58
+ nil
59
+ end
60
+
61
+ # Decodes a single item from a String, or from all that an IO holds.
62
+ #
63
+ # options is {symbolize_keys: true}, or :keys_as_symbols.
64
+ #
65
+ #: (untyped source, ?(Hash[Symbol, untyped] | Symbol)? options) -> untyped
66
+ def self.decode(source, options = nil)
67
+ symbolize_keys = case options
68
+ when nil
69
+ false
70
+ when :keys_as_symbols
71
+ true
72
+ when Hash
73
+ !!options[:symbolize_keys]
74
+ else
75
+ raise ArgumentError, "expected a Hash for options, got #{options.inspect}"
76
+ end
77
+ bytes = source.respond_to?(:read) ? source.read : source
78
+ translate_errors { Ciborium.decode(bytes, **decode_options, symbolize_keys:) }
79
+ end
80
+
81
+ class << self
82
+ alias_method :pack, :encode
83
+ alias_method :dump, :encode
84
+ alias_method :unpack, :decode
85
+ alias_method :load, :decode
86
+ end
87
+
88
+ # Encodes an object Ciborium cannot represent through its own to_cbor(packer), as the cbor
89
+ # gem does.
90
+ FALLBACK = ->(object) do
91
+ unless object.respond_to?(:to_cbor) && object.method(:to_cbor).owner != CoreExt
92
+ raise EncodeError, "cannot encode an instance of #{object.class}"
93
+ end
94
+
95
+ packer = Packer.new
96
+ object.to_cbor(packer)
97
+ Encoded.new(packer.to_s)
98
+ end #: ^(untyped) -> untyped
99
+
100
+ # to_cbor for the classes the cbor gem extends. Installed by ciborium/replace.
101
+ module CoreExt
102
+ #: (?untyped packer_or_io) -> untyped
103
+ def to_cbor(packer_or_io = nil)
104
+ return packer_or_io.write(self) if packer_or_io.is_a?(Packer)
105
+
106
+ Compat.encode(self, packer_or_io)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ DEFAULT_MAX_DEPTH = 128 #: Integer
6
+ MAX_DEPTH_LIMIT = 10_000 #: Integer
7
+
8
+ # Encoding rules a profile enforces beyond well-formedness. :ctap2 is the CTAP2 canonical CBOR
9
+ # encoding form that WebAuthn requires: no tags, no indefinite lengths, arguments in their
10
+ # shortest form, and map keys ordered without duplicates.
11
+ PROFILES = [:ctap2].freeze #: Array[Symbol]
12
+
13
+ # Decoding options, validated and packed into the arguments the native decoder takes.
14
+ DecodeOptions = Data.define(
15
+ :max_depth, #: Integer
16
+ :flags, #: Integer
17
+ :handlers, #: Hash[Integer, ^(Integer, untyped) -> untyped]?
18
+ )
19
+
20
+ class DecodeOptions
21
+ # extensions lists the allowlisted extensions (see Extensions); every other tag decodes to
22
+ # Tagged. profile rejects input that breaks one of PROFILES.
23
+ #
24
+ #: (?extensions: Array[extension], ?symbolize_keys: bool, ?max_depth: Integer, ?duplicate_keys: Symbol, ?profile: Symbol?) -> DecodeOptions
25
+ def self.build(extensions: [], symbolize_keys: false, max_depth: DEFAULT_MAX_DEPTH, duplicate_keys: :error,
26
+ profile: nil)
27
+ unless max_depth.is_a?(Integer) && max_depth.between?(1, MAX_DEPTH_LIMIT)
28
+ raise ArgumentError, "max_depth must be an Integer in 1..#{MAX_DEPTH_LIMIT}, got #{max_depth.inspect}"
29
+ end
30
+
31
+ flags = 0
32
+ flags |= Native::FLAG_SYMBOLIZE_KEYS if symbolize_keys
33
+ case duplicate_keys
34
+ when :error
35
+ # rejected by default
36
+ when :last_wins
37
+ flags |= Native::FLAG_LAST_WINS
38
+ else
39
+ raise ArgumentError, "duplicate_keys must be :error or :last_wins, got #{duplicate_keys.inspect}"
40
+ end
41
+ flags |= Native::FLAG_CTAP2 if DecodeOptions.check_profile(profile, extensions)
42
+ new(max_depth:, flags:, handlers: Extensions.tag_handlers(Extensions.resolve(extensions)))
43
+ end
44
+
45
+ # Returns whether profile is :ctap2.
46
+ #
47
+ #: (Symbol? profile, untyped extensions) -> bool
48
+ def self.check_profile(profile, extensions)
49
+ case profile
50
+ when nil
51
+ return false
52
+ when *PROFILES
53
+ raise ArgumentError, "the #{profile} profile allows no tags, so takes no extensions" unless extensions.empty?
54
+ else
55
+ raise ArgumentError, "profile must be one of #{PROFILES.inspect}, got #{profile.inspect}"
56
+ end
57
+
58
+ true
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ # Decodes data items from input that arrives in parts. Nothing is consumed until a whole item
6
+ # has arrived, so reads that raise IncompleteError can be retried after feeding more.
7
+ class Decoder
8
+ include Enumerable #[untyped]
9
+
10
+ # Consumed bytes are dropped from the buffer once they exceed this size.
11
+ COMPACT_THRESHOLD = 4096 #: Integer
12
+
13
+ # @rbs @options: DecodeOptions
14
+ # @rbs @buffer: String
15
+ # @rbs @pos: Integer
16
+ # @rbs @scan_state: Native::ScanState
17
+
18
+ # Options are those of DecodeOptions.build.
19
+ #
20
+ #: (**untyped options) -> void
21
+ def initialize(**options)
22
+ @options = DecodeOptions.build(**options)
23
+ @buffer = "".b
24
+ @pos = 0
25
+ @scan_state = Native::ScanState.new
26
+ end
27
+
28
+ #: (String bytes) -> self
29
+ def feed(bytes)
30
+ compact
31
+ @buffer << bytes.b
32
+ self
33
+ end
34
+
35
+ # Decodes the next item. Raises IncompleteError when it has not fully arrived.
36
+ #
37
+ #: () -> untyped
38
+ def read
39
+ item_end
40
+ value, pos = Native.decode(@buffer, @pos, @options.max_depth, @options.flags, @options.handlers)
41
+ advance(pos)
42
+ value
43
+ end
44
+
45
+ # Yields each item that has fully arrived, leaving a partial one buffered.
46
+ #
47
+ #: () { (untyped) -> void } -> self
48
+ #: () -> Enumerator[untyped, self]
49
+ def each
50
+ return enum_for(:each) unless block_given?
51
+
52
+ yield read while buffered_bytes > 0 && Native.scan(@buffer, @pos, @options.max_depth, @scan_state)
53
+ self
54
+ end
55
+
56
+ # Skips the next item without decoding it.
57
+ #
58
+ #: () -> self
59
+ def skip
60
+ advance(item_end)
61
+ self
62
+ end
63
+
64
+ # Reads the head of a definite-length array and returns its length, leaving the elements to
65
+ # be read one by one.
66
+ #
67
+ #: () -> Integer
68
+ def read_array_header
69
+ read_container_head(4, "array")
70
+ end
71
+
72
+ # Reads the head of a definite-length map and returns its number of pairs.
73
+ #
74
+ #: () -> Integer
75
+ def read_map_header
76
+ read_container_head(5, "map")
77
+ end
78
+
79
+ # Whether the next item is null. Consumes nothing.
80
+ #
81
+ #: () -> bool
82
+ def next_nil?
83
+ byte = @buffer.getbyte(@pos)
84
+ raise IncompleteError, "unexpected end of input" unless byte
85
+
86
+ byte == 0xf6
87
+ end
88
+
89
+ #: () -> Integer
90
+ def buffered_bytes
91
+ @buffer.bytesize - @pos
92
+ end
93
+
94
+ # Drops all buffered input.
95
+ #
96
+ #: () -> self
97
+ def reset
98
+ @buffer = "".b
99
+ advance(0)
100
+ self
101
+ end
102
+
103
+ #: () -> Integer
104
+ private def item_end
105
+ Native.scan(@buffer, @pos, @options.max_depth, @scan_state) or
106
+ raise IncompleteError, "unexpected end of input"
107
+ end
108
+
109
+ # Moves to the next item, whose scan starts afresh.
110
+ #
111
+ #: (Integer pos) -> void
112
+ private def advance(pos)
113
+ @pos = pos
114
+ @scan_state.clear
115
+ end
116
+
117
+ #: (Integer major, String name) -> Integer
118
+ private def read_container_head(major, name)
119
+ head = Native.read_head(@buffer, @pos)
120
+ raise IncompleteError, "unexpected end of input" unless head
121
+
122
+ actual, length, pos = head
123
+ unless actual == major && length
124
+ raise UnexpectedTypeError, "expected a definite-length #{name}, got major type #{actual}"
125
+ end
126
+
127
+ advance(pos)
128
+ length
129
+ end
130
+
131
+ # The scan state is relative to @pos, so it stays valid.
132
+ #
133
+ #: () -> void
134
+ private def compact
135
+ return if @pos < COMPACT_THRESHOLD
136
+
137
+ @buffer = @buffer.byteslice(@pos..) || "".b
138
+ @pos = 0
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ # Encoding options, validated into the arguments the native encoder takes.
6
+ EncodeOptions = Data.define(
7
+ :max_depth, #: Integer
8
+ :flags, #: Integer
9
+ :fallback, #: Proc?
10
+ :class_handlers, #: Array[[Module, ^(untyped) -> untyped]]?
11
+ )
12
+
13
+ class EncodeOptions
14
+ # extensions lists the allowlisted extensions (see Extensions). fallback is called with each
15
+ # object neither the encoder nor an extension can represent, and returns an object to encode
16
+ # in its place. profile makes the output follow one of PROFILES, sorting map keys.
17
+ #
18
+ #: (?extensions: Array[extension], ?max_depth: Integer, ?fallback: (^(untyped) -> untyped)?, ?profile: Symbol?) -> EncodeOptions
19
+ def self.build(extensions: [], max_depth: DEFAULT_MAX_DEPTH, fallback: nil, profile: nil)
20
+ unless max_depth.is_a?(Integer) && max_depth.between?(1, MAX_DEPTH_LIMIT)
21
+ raise ArgumentError, "max_depth must be an Integer in 1..#{MAX_DEPTH_LIMIT}, got #{max_depth.inspect}"
22
+ end
23
+
24
+ new(
25
+ max_depth:,
26
+ flags: DecodeOptions.check_profile(profile, extensions) ? Native::FLAG_CTAP2 : 0,
27
+ fallback: fallback && wrap_fallback(fallback),
28
+ class_handlers: Extensions.class_handlers(Extensions.resolve(extensions)),
29
+ )
30
+ end
31
+
32
+ #: (^(untyped) -> untyped fallback) -> Proc
33
+ private_class_method def self.wrap_fallback(fallback)
34
+ ->(object) do
35
+ fallback.call(object)
36
+ rescue StandardError
37
+ raise EncodeError, "fallback failed for an instance of #{object.class}"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ # A data item that is already encoded, written out as-is by the encoder. The encoder checks
6
+ # that bytes hold exactly one well-formed item.
7
+ Encoded = Data.define(
8
+ :bytes, #: String
9
+ )
10
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ # Writes data items one after another into a buffer, for output built in parts.
6
+ class Encoder
7
+ # @rbs @options: EncodeOptions
8
+ # @rbs @buffer: String
9
+
10
+ # Options are those of EncodeOptions.build.
11
+ #
12
+ #: (**untyped options) -> void
13
+ def initialize(**options)
14
+ @options = EncodeOptions.build(**options)
15
+ @buffer = "".b
16
+ end
17
+
18
+ #: (untyped object) -> self
19
+ def write(object)
20
+ @buffer << Native.encode(object, @options.max_depth, @options.flags, @options.fallback, @options.class_handlers)
21
+ self
22
+ end
23
+
24
+ # Writes the head of an array whose length items the caller writes next.
25
+ #
26
+ #: (Integer length) -> self
27
+ def write_array_header(length)
28
+ write_head(4, length)
29
+ end
30
+
31
+ # Writes the head of a map whose length key-value pairs the caller writes next.
32
+ #
33
+ #: (Integer length) -> self
34
+ def write_map_header(length)
35
+ write_head(5, length)
36
+ end
37
+
38
+ # Writes a tag head; the caller writes the tag content next.
39
+ #
40
+ #: (Integer tag) -> self
41
+ def write_tag(tag)
42
+ raise EncodeError, "tags are not allowed by the ctap2 profile" if @options.flags & Native::FLAG_CTAP2 != 0
43
+
44
+ write_head(6, tag)
45
+ end
46
+
47
+ # A copy of everything written so far.
48
+ #
49
+ #: () -> String
50
+ def bytes
51
+ @buffer.dup
52
+ end
53
+
54
+ #: () -> Integer
55
+ def size
56
+ @buffer.bytesize
57
+ end
58
+
59
+ #: () -> bool
60
+ def empty?
61
+ @buffer.empty?
62
+ end
63
+
64
+ #: () -> self
65
+ def clear
66
+ @buffer.clear
67
+ self
68
+ end
69
+
70
+ #: (Integer major, Integer argument) -> self
71
+ private def write_head(major, argument)
72
+ unless argument.is_a?(Integer) && argument.between?(0, Tagged::MAX_TAG)
73
+ raise ArgumentError, "argument must be an Integer in 0..2**64-1, got #{argument.inspect}"
74
+ end
75
+
76
+ @buffer << Native.encode_head(major, argument)
77
+ self
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ class Error < StandardError; end
6
+
7
+ class DecodeError < Error; end
8
+
9
+ # The input is not well-formed CBOR, or breaks a rule the decoder enforces.
10
+ class MalformedError < DecodeError; end
11
+
12
+ # The input ends in the middle of a data item.
13
+ class IncompleteError < DecodeError; end
14
+
15
+ # Containers and tags are nested deeper than max_depth.
16
+ class DepthError < DecodeError; end
17
+
18
+ # An allowlisted extension failed to decode a tag's content. The cause holds the original error.
19
+ class ExtensionError < DecodeError; end
20
+
21
+ # A Decoder head reader found an item of another type.
22
+ class UnexpectedTypeError < DecodeError; end
23
+
24
+ class EncodeError < Error; end
25
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ module Extensions
6
+ # Tags 2 and 3 (RFC 8949 section 3.4.3): integers outside -2**64..2**64-1 as byte strings.
7
+ class BigInteger
8
+ #: () -> Array[Integer]
9
+ def tags = [2, 3]
10
+
11
+ #: () -> Array[Module]
12
+ def classes = [Integer]
13
+
14
+ #: (Integer tag, untyped value) -> Integer
15
+ def decode(tag, value)
16
+ unless value.is_a?(String) && value.encoding == Encoding::BINARY
17
+ raise ArgumentError, "tag #{tag} content must be a byte string"
18
+ end
19
+
20
+ # unpack1 is typed as any directive's result; "H*" always yields a String.
21
+ hex = value.unpack1("H*") #: String
22
+ n = hex.empty? ? 0 : hex.to_i(16)
23
+ tag == 2 ? n : -1 - n
24
+ end
25
+
26
+ #: (Integer object) -> Tagged
27
+ def encode(object)
28
+ tag, n = object.negative? ? [3, -1 - object] : [2, object]
29
+ hex = n.to_s(16)
30
+ hex = "0#{hex}" if hex.size.odd?
31
+ Tagged.new(tag, [hex].pack("H*"))
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Ciborium
5
+ module Extensions
6
+ # Tag 35 as Regexp. Compiling a pattern from untrusted input risks ReDoS, so pass a timeout
7
+ # unless Regexp.timeout is set.
8
+ class Regexp
9
+ FLAGS = ::Regexp::IGNORECASE | ::Regexp::EXTENDED | ::Regexp::MULTILINE #: Integer
10
+
11
+ # @rbs @timeout: Float?
12
+
13
+ #: (?timeout: Float?) -> void
14
+ def initialize(timeout: nil)
15
+ @timeout = timeout
16
+ end
17
+
18
+ #: () -> Array[Integer]
19
+ def tags = [35]
20
+
21
+ #: () -> Array[Module]
22
+ def classes = [::Regexp]
23
+
24
+ #: (Integer tag, untyped value) -> ::Regexp
25
+ def decode(tag, value)
26
+ raise ArgumentError, "tag #{tag} content must be text" unless value.is_a?(String)
27
+
28
+ ::Regexp.new(value, timeout: @timeout)
29
+ end
30
+
31
+ # Tag 35 has no place for flags, so a Regexp with any is refused rather than changed.
32
+ #
33
+ #: (::Regexp object) -> Tagged
34
+ def encode(object)
35
+ raise ArgumentError, "cannot encode #{object.inspect}: tag 35 carries no flags" if object.options & FLAGS != 0
36
+
37
+ Tagged.new(35, object.source)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "time"
5
+
6
+ module Ciborium
7
+ module Extensions
8
+ # Tag 0 (RFC 3339 text) and tag 1 (seconds since the epoch) as Time.
9
+ class Time
10
+ #: () -> Array[Integer]
11
+ def tags = [0, 1]
12
+
13
+ #: () -> Array[Module]
14
+ def classes = [::Time]
15
+
16
+ #: (Integer tag, untyped value) -> ::Time
17
+ def decode(tag, value)
18
+ if tag == 0
19
+ raise ArgumentError, "tag 0 content must be text" unless value.is_a?(String)
20
+
21
+ return ::Time.iso8601(value)
22
+ end
23
+
24
+ unless value.is_a?(Integer) || (value.is_a?(Float) && value.finite?)
25
+ raise ArgumentError, "tag 1 content must be an Integer or a finite Float"
26
+ end
27
+
28
+ ::Time.at(value)
29
+ end
30
+
31
+ # Whole seconds are written as an Integer, anything finer as a Float.
32
+ #
33
+ #: (::Time object) -> Tagged
34
+ def encode(object)
35
+ Tagged.new(1, object.subsec.zero? ? object.to_i : object.to_f)
36
+ end
37
+ end
38
+ end
39
+ end