cborb 0.1.0
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.
- checksums.yaml +7 -0
- data/.circleci/config.yml +27 -0
- data/.editorconfig +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +21 -0
- data/README.md +63 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/generate_fixtures +262 -0
- data/bin/setup +8 -0
- data/cborb.gemspec +27 -0
- data/lib/cborb.rb +52 -0
- data/lib/cborb/decoding/decoder.rb +19 -0
- data/lib/cborb/decoding/ib_jump_table.rb +47 -0
- data/lib/cborb/decoding/simple_buffer.rb +24 -0
- data/lib/cborb/decoding/state.rb +130 -0
- data/lib/cborb/decoding/tagged_value.rb +3 -0
- data/lib/cborb/decoding/types/array.rb +20 -0
- data/lib/cborb/decoding/types/break.rb +12 -0
- data/lib/cborb/decoding/types/byte_string.rb +12 -0
- data/lib/cborb/decoding/types/floating_point.rb +18 -0
- data/lib/cborb/decoding/types/half_precision_floating_point.rb +28 -0
- data/lib/cborb/decoding/types/indefinite_array.rb +23 -0
- data/lib/cborb/decoding/types/indefinite_byte_string.rb +25 -0
- data/lib/cborb/decoding/types/indefinite_map.rb +24 -0
- data/lib/cborb/decoding/types/indefinite_text_string.rb +25 -0
- data/lib/cborb/decoding/types/integer.rb +12 -0
- data/lib/cborb/decoding/types/integer_decodable.rb +24 -0
- data/lib/cborb/decoding/types/map.rb +24 -0
- data/lib/cborb/decoding/types/negative_integer.rb +12 -0
- data/lib/cborb/decoding/types/root.rb +7 -0
- data/lib/cborb/decoding/types/simple_value.rb +17 -0
- data/lib/cborb/decoding/types/tag.rb +17 -0
- data/lib/cborb/decoding/types/text_string.rb +13 -0
- data/lib/cborb/decoding/types/type.rb +24 -0
- data/lib/cborb/decoding/types/unassigned_simple_value.rb +13 -0
- data/lib/cborb/decoding/types/unknown.rb +8 -0
- data/lib/cborb/decoding/unassigned_simple_value.rb +3 -0
- data/lib/cborb/errors.rb +11 -0
- data/lib/cborb/version.rb +3 -0
- metadata +128 -0
data/bin/setup
ADDED
data/cborb.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "cborb/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cborb"
|
8
|
+
spec.version = Cborb::VERSION
|
9
|
+
spec.authors = ["murakmii"]
|
10
|
+
spec.email = ["bonono.jp@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "Cborb is a pure ruby decoder for CBOR(RFC 7049)"
|
13
|
+
spec.description = "Cborb is a pure ruby decoder for CBOR(RFC 7049)"
|
14
|
+
spec.homepage = "https://github.com/murakmii/cborb"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
27
|
+
end
|
data/lib/cborb.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "stringio"
|
3
|
+
|
4
|
+
require "cborb/version"
|
5
|
+
require "cborb/errors"
|
6
|
+
|
7
|
+
require "cborb/decoding/types/type"
|
8
|
+
require "cborb/decoding/types/unknown"
|
9
|
+
require "cborb/decoding/types/root"
|
10
|
+
require "cborb/decoding/types/integer_decodable"
|
11
|
+
require "cborb/decoding/types/integer"
|
12
|
+
require "cborb/decoding/types/negative_integer"
|
13
|
+
require "cborb/decoding/types/byte_string"
|
14
|
+
require "cborb/decoding/types/indefinite_byte_string"
|
15
|
+
require "cborb/decoding/types/text_string"
|
16
|
+
require "cborb/decoding/types/indefinite_text_string"
|
17
|
+
require "cborb/decoding/types/array"
|
18
|
+
require "cborb/decoding/types/indefinite_array"
|
19
|
+
require "cborb/decoding/types/map"
|
20
|
+
require "cborb/decoding/types/indefinite_map"
|
21
|
+
require "cborb/decoding/types/tag"
|
22
|
+
require "cborb/decoding/types/simple_value"
|
23
|
+
require "cborb/decoding/types/unassigned_simple_value"
|
24
|
+
require "cborb/decoding/types/half_precision_floating_point"
|
25
|
+
require "cborb/decoding/types/floating_point"
|
26
|
+
require "cborb/decoding/types/break"
|
27
|
+
|
28
|
+
require "cborb/decoding/ib_jump_table"
|
29
|
+
require "cborb/decoding/tagged_value"
|
30
|
+
require "cborb/decoding/unassigned_simple_value"
|
31
|
+
require "cborb/decoding/simple_buffer"
|
32
|
+
require "cborb/decoding/state"
|
33
|
+
require "cborb/decoding/decoder"
|
34
|
+
|
35
|
+
module Cborb
|
36
|
+
# The shorthand to decode CBOR
|
37
|
+
#
|
38
|
+
# @param [String] cbor
|
39
|
+
# @return [Object] decoded data(Array, Hash, etc...)
|
40
|
+
def decode(cbor)
|
41
|
+
decoder = Decoding::Decoder.new
|
42
|
+
decoder.decode(cbor)
|
43
|
+
|
44
|
+
if decoder.finished?
|
45
|
+
decoder.result
|
46
|
+
else
|
47
|
+
raise Cborb::InvalidByteSequenceError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module_function :decode
|
52
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Cborb::Decoding
|
2
|
+
class Decoder
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators :@state, :result, :finished?
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@state = Cborb::Decoding::State.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def decode(cbor)
|
12
|
+
@state << cbor.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"#<#{self.class}:#{object_id} finished=#{finished?} result=#{finished? ? result : "nil"}>"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Cborb::Decoding
|
2
|
+
# IB = Initial Byte
|
3
|
+
# @see https://tools.ietf.org/html/rfc7049#appendix-B
|
4
|
+
IB_JUMP_TABLE =
|
5
|
+
Array.new(256) do |ib|
|
6
|
+
case ib
|
7
|
+
when 0x00..0x1B
|
8
|
+
Types::Integer
|
9
|
+
when 0x20..0x3B
|
10
|
+
Types::NegativeInteger
|
11
|
+
when 0x40..0x5B
|
12
|
+
Types::ByteString
|
13
|
+
when 0x5F
|
14
|
+
Types::IndefiniteByteString
|
15
|
+
when 0x60..0x7B
|
16
|
+
Types::TextString
|
17
|
+
when 0x7F
|
18
|
+
Types::IndefiniteTextString
|
19
|
+
when 0x80..0x9B
|
20
|
+
Types::Array
|
21
|
+
when 0x9F
|
22
|
+
Types::IndefiniteArray
|
23
|
+
when 0xA0..0xBB
|
24
|
+
Types::Map
|
25
|
+
when 0xBF
|
26
|
+
Types::IndefiniteMap
|
27
|
+
when 0xC0..0xDB
|
28
|
+
Types::Tag
|
29
|
+
when 0xE0..0xF3
|
30
|
+
Types::UnassignedSimpleValue
|
31
|
+
when 0xF4..0xF7
|
32
|
+
Types::SimpleValue
|
33
|
+
when 0xF8
|
34
|
+
Types::UnassignedSimpleValue
|
35
|
+
when 0xF9
|
36
|
+
Types::HalfPrecisionFloatingPoint
|
37
|
+
when 0xFA, 0xFB
|
38
|
+
Types::FloatingPoint
|
39
|
+
when 0xFC..0xFE
|
40
|
+
Types::UnassignedSimpleValue
|
41
|
+
when 0xFF
|
42
|
+
Types::Break
|
43
|
+
else
|
44
|
+
Types::Unknown
|
45
|
+
end
|
46
|
+
end.freeze
|
47
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Cborb::Decoding
|
2
|
+
class SimpleBuffer
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators :@buffer, :read, :getbyte, :eof?
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@buffer = StringIO.new
|
9
|
+
@buffer.set_encoding(Encoding::ASCII_8BIT)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [String] data
|
13
|
+
def write(data)
|
14
|
+
pos = @buffer.pos
|
15
|
+
@buffer << data
|
16
|
+
@buffer.pos = pos
|
17
|
+
end
|
18
|
+
|
19
|
+
def reset!
|
20
|
+
@buffer.rewind
|
21
|
+
@buffer.truncate(0)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Cborb::Decoding
|
2
|
+
# State of processing to decode
|
3
|
+
class State
|
4
|
+
CONTINUE = Object.new
|
5
|
+
NO_RESULT = Object.new
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@buffer = Cborb::Decoding::SimpleBuffer.new
|
9
|
+
@stack = [[Cborb::Decoding::Types::Root, nil]]
|
10
|
+
@result = NO_RESULT
|
11
|
+
|
12
|
+
# This fiber decodes CBOR.
|
13
|
+
# If buffer becomes empty, this fiber is stopped(#consume)
|
14
|
+
# When new CBOR data is buffered(#<<), that is resumed.
|
15
|
+
@decoding_fiber = Fiber.new { loop_consuming }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Buffering new CBOR data
|
19
|
+
#
|
20
|
+
# @param [String] cbor
|
21
|
+
def <<(cbor)
|
22
|
+
@buffer.write(cbor)
|
23
|
+
@decoding_fiber.resume
|
24
|
+
|
25
|
+
rescue FiberError => e
|
26
|
+
msg = e.message
|
27
|
+
|
28
|
+
# umm...
|
29
|
+
if msg.include?("dead")
|
30
|
+
raise Cborb::InvalidByteSequenceError
|
31
|
+
elsif msg.include?("threads")
|
32
|
+
raise Cborb::DecodingError, "Can't decode across threads"
|
33
|
+
else
|
34
|
+
raise
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Consume CBOR data.
|
39
|
+
# This method will be called only in fiber.
|
40
|
+
#
|
41
|
+
# @param [Integer] size Size to consume
|
42
|
+
# @return [String]
|
43
|
+
def consume(size)
|
44
|
+
data = @buffer.read(size).to_s
|
45
|
+
|
46
|
+
# If buffered data is not enought, yield fiber until new data will be buffered.
|
47
|
+
if data.size < size
|
48
|
+
@buffer.reset!
|
49
|
+
|
50
|
+
while data.size != size
|
51
|
+
Fiber.yield
|
52
|
+
data += @buffer.read(size - data.size)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
data
|
57
|
+
end
|
58
|
+
|
59
|
+
# Consume 1 byte(uses #getbyte instead of #read)
|
60
|
+
#
|
61
|
+
# @return [Integer]
|
62
|
+
def consume_byte
|
63
|
+
ib = @buffer.getbyte
|
64
|
+
|
65
|
+
if ib.nil?
|
66
|
+
@buffer.reset!
|
67
|
+
|
68
|
+
while ib.nil?
|
69
|
+
Fiber.yield
|
70
|
+
ib = @buffer.getbyte
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
ib
|
75
|
+
end
|
76
|
+
|
77
|
+
# Push type that has following data(e.g. Array) to stack
|
78
|
+
#
|
79
|
+
# @param [Class] type Class constant that inherits Cborb::Decoding::Types::Type
|
80
|
+
# @param [Object] im_data depends on type
|
81
|
+
def push_stack(type, im_data = nil)
|
82
|
+
@stack << [type, im_data]
|
83
|
+
end
|
84
|
+
|
85
|
+
# @param [Class] type Class constant that inherits Cborb::Decoding::Types::Type
|
86
|
+
# @param [Object] value
|
87
|
+
def accept_value(type, value)
|
88
|
+
loop do
|
89
|
+
stacked_type, im_data = @stack.last
|
90
|
+
value = stacked_type.accept(im_data, type, value)
|
91
|
+
type = stacked_type
|
92
|
+
|
93
|
+
if value.eql?(CONTINUE)
|
94
|
+
break
|
95
|
+
else
|
96
|
+
@stack.pop
|
97
|
+
if @stack.empty?
|
98
|
+
raise Cborb::InvalidByteSequenceError unless @buffer.eof?
|
99
|
+
@result = value
|
100
|
+
break
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# @return [Class] Class constant that inherits Cborb::Decoding::Types::Type on top of stack.
|
107
|
+
def stack_top
|
108
|
+
@stack.last.first
|
109
|
+
end
|
110
|
+
|
111
|
+
# @return [Boolean]
|
112
|
+
def finished?
|
113
|
+
!@result.eql?(NO_RESULT)
|
114
|
+
end
|
115
|
+
|
116
|
+
# @return [Object]
|
117
|
+
def result
|
118
|
+
finished? ? @result : nil
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def loop_consuming
|
124
|
+
until finished? do
|
125
|
+
ib = consume_byte
|
126
|
+
Cborb::Decoding::IB_JUMP_TABLE[ib].decode(self, ib & 0x1F)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Cborb::Decoding::Types
|
2
|
+
# To represent major type: 4(definite-length)
|
3
|
+
#
|
4
|
+
# @see https://tools.ietf.org/html/rfc7049#section-2.1
|
5
|
+
class Array < Type
|
6
|
+
extend Cborb::Decoding::Types::IntegerDecodable
|
7
|
+
|
8
|
+
Intermediate = Struct.new(:size, :array)
|
9
|
+
|
10
|
+
def self.decode(state, additional_info)
|
11
|
+
im = Intermediate.new(consume_as_integer(state, additional_info), [])
|
12
|
+
state.push_stack(self, im)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.accept(im_data, type, value)
|
16
|
+
im_data.array << value
|
17
|
+
im_data.size == im_data.array.size ? im_data.array : Cborb::Decoding::State::CONTINUE
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Cborb::Decoding::Types
|
2
|
+
# To represent "break" stop code
|
3
|
+
class Break < Type
|
4
|
+
def self.decode(state, additional_info)
|
5
|
+
unless state.stack_top.indefinite?
|
6
|
+
raise Cborb::DecodingError, 'Unexpected "break" stop code'
|
7
|
+
end
|
8
|
+
|
9
|
+
state.accept_value(self, self)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Cborb::Decoding::Types
|
2
|
+
# To represent major type: 2(definite-length)
|
3
|
+
#
|
4
|
+
# @see https://tools.ietf.org/html/rfc7049#section-2.1
|
5
|
+
class ByteString < Type
|
6
|
+
extend Cborb::Decoding::Types::IntegerDecodable
|
7
|
+
|
8
|
+
def self.decode(state, additional_info)
|
9
|
+
state.accept_value(self, state.consume(consume_as_integer(state, additional_info)))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Cborb::Decoding::Types
|
2
|
+
# To represent part of major type: 7
|
3
|
+
#
|
4
|
+
# @see https://tools.ietf.org/html/rfc7049#section-2.3
|
5
|
+
class FloatingPoint < Type
|
6
|
+
UNPACK_TEMPLATES = [
|
7
|
+
"g".freeze,
|
8
|
+
"G".freeze,
|
9
|
+
].freeze
|
10
|
+
|
11
|
+
def self.decode(state, additional_info)
|
12
|
+
index = additional_info - 26
|
13
|
+
bytesize = 4 * (index + 1)
|
14
|
+
|
15
|
+
state.accept_value(self, state.consume(bytesize).unpack(UNPACK_TEMPLATES[index]).first)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Cborb::Decoding::Types
|
2
|
+
# To represent part of major type: 7
|
3
|
+
#
|
4
|
+
# @see https://tools.ietf.org/html/rfc7049#section-2.3
|
5
|
+
# @see https://tools.ietf.org/html/rfc7049#appendix-D
|
6
|
+
class HalfPrecisionFloatingPoint < Type
|
7
|
+
class << self
|
8
|
+
def decode(state, additional_info)
|
9
|
+
bits = state.consume(2).unpack("n".freeze).first
|
10
|
+
bits = (bits & 0x7FFF) << 13 | (bits & 0x8000) << 16
|
11
|
+
fp =
|
12
|
+
if (bits & 0x7C00) != 0x7C00
|
13
|
+
Math.ldexp(to_single(bits), 112)
|
14
|
+
else
|
15
|
+
to_single(bits | 0x7F800000)
|
16
|
+
end
|
17
|
+
|
18
|
+
state.accept_value(self, fp)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def to_single(bits)
|
24
|
+
[bits].pack("N".freeze).unpack("g".freeze).first
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Cborb::Decoding::Types
|
2
|
+
# To represent major type: 4(indefinite-length)
|
3
|
+
#
|
4
|
+
# @see https://tools.ietf.org/html/rfc7049#section-2.2.1
|
5
|
+
class IndefiniteArray < Type
|
6
|
+
def self.indefinite?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.decode(state, additional_info)
|
11
|
+
state.push_stack(self, [])
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.accept(im_data, type, value)
|
15
|
+
if type == Cborb::Decoding::Types::Break
|
16
|
+
im_data
|
17
|
+
else
|
18
|
+
im_data << value
|
19
|
+
Cborb::Decoding::State::CONTINUE
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|