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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +27 -0
  3. data/.editorconfig +7 -0
  4. data/.gitignore +13 -0
  5. data/.rspec +2 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +35 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +63 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +7 -0
  12. data/bin/generate_fixtures +262 -0
  13. data/bin/setup +8 -0
  14. data/cborb.gemspec +27 -0
  15. data/lib/cborb.rb +52 -0
  16. data/lib/cborb/decoding/decoder.rb +19 -0
  17. data/lib/cborb/decoding/ib_jump_table.rb +47 -0
  18. data/lib/cborb/decoding/simple_buffer.rb +24 -0
  19. data/lib/cborb/decoding/state.rb +130 -0
  20. data/lib/cborb/decoding/tagged_value.rb +3 -0
  21. data/lib/cborb/decoding/types/array.rb +20 -0
  22. data/lib/cborb/decoding/types/break.rb +12 -0
  23. data/lib/cborb/decoding/types/byte_string.rb +12 -0
  24. data/lib/cborb/decoding/types/floating_point.rb +18 -0
  25. data/lib/cborb/decoding/types/half_precision_floating_point.rb +28 -0
  26. data/lib/cborb/decoding/types/indefinite_array.rb +23 -0
  27. data/lib/cborb/decoding/types/indefinite_byte_string.rb +25 -0
  28. data/lib/cborb/decoding/types/indefinite_map.rb +24 -0
  29. data/lib/cborb/decoding/types/indefinite_text_string.rb +25 -0
  30. data/lib/cborb/decoding/types/integer.rb +12 -0
  31. data/lib/cborb/decoding/types/integer_decodable.rb +24 -0
  32. data/lib/cborb/decoding/types/map.rb +24 -0
  33. data/lib/cborb/decoding/types/negative_integer.rb +12 -0
  34. data/lib/cborb/decoding/types/root.rb +7 -0
  35. data/lib/cborb/decoding/types/simple_value.rb +17 -0
  36. data/lib/cborb/decoding/types/tag.rb +17 -0
  37. data/lib/cborb/decoding/types/text_string.rb +13 -0
  38. data/lib/cborb/decoding/types/type.rb +24 -0
  39. data/lib/cborb/decoding/types/unassigned_simple_value.rb +13 -0
  40. data/lib/cborb/decoding/types/unknown.rb +8 -0
  41. data/lib/cborb/decoding/unassigned_simple_value.rb +3 -0
  42. data/lib/cborb/errors.rb +11 -0
  43. data/lib/cborb/version.rb +3 -0
  44. metadata +128 -0
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
7
+
8
+ ruby bin/generate_fixtures
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,3 @@
1
+ module Cborb::Decoding
2
+ TaggedValue = Struct.new(:tag, :value)
3
+ 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