cbor-simple 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f0cf7690f1fb741f8a95075b8a197168704fd2bf
4
+ data.tar.gz: 2c5d4756029677dd6ee6e4601b4d059305205242
5
+ SHA512:
6
+ metadata.gz: 1e2a40952af4869b675e83ee68b42d5a2e82ec1ce30b9eb6d1f6dd4520e15ba98a361f5ec94553c97a00f5e2af63c7b835b848c361c4244d767980c9ae90ab44
7
+ data.tar.gz: 99542bf58913f7fa82b626a0d752d5c805e23f90545da64a1f63dfaef4c6cecc2a1363c3165d92d74a7a4de916fc18ba0028825d14379eee35b63aade9514484
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cbor-simple.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Lucas Clemente
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # CBOR Simple
2
+
3
+ ```ruby
4
+ gem 'cbor-simple'
5
+ ```
6
+
7
+ A basic but extensible implementation of [CBOR (RFC7049)](http://tools.ietf.org/html/rfc7049) in plain ruby.
8
+
9
+ Use just like `JSON` or `YAML`:
10
+
11
+ ```ruby
12
+ CBOR.dump(42) # => "\x18\x2a"
13
+ CBOR.load("\x18\x2a") # => 42
14
+ ```
15
+
16
+ You can add custom tags like this:
17
+
18
+ ```ruby
19
+ CBOR.register_tag 0 do
20
+ Time.iso8601(load())
21
+ end
22
+ ```
23
+
24
+ And add classes for dumping:
25
+
26
+ ```ruby
27
+ CBOR.register_class Time, 0 do |obj|
28
+ dump(obj.iso8601(6))
29
+ end
30
+ ```
31
+
32
+ Custom tags can also be given as second parameter to `load`, however this should be considered unstable and might change in the future.
33
+
34
+ ```ruby
35
+ # Invert values tagged with 0x26 (nonstandard!)
36
+ CBOR.load("\xd8\x26\xf5", {0x26 => ->{ !load }}) # => false
37
+ ```
38
+
39
+ Currently supported classes:
40
+
41
+ - (Unsigned) Integers
42
+ - Floats (single and double)
43
+ - Byte / Textstring (also symbols)
44
+ - Arrays
45
+ - Hashes
46
+ - BigDecimals
47
+ - UUIDs (if the gem `uuidtools` is visible)
48
+ - Times
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.pattern = "test/*_test.rb"
6
+ t.libs.push "test"
7
+ end
8
+
9
+ task default: :test
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cbor/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cbor-simple"
8
+ spec.version = CBOR::VERSION
9
+ spec.authors = ["Lucas Clemente"]
10
+ spec.email = ["lucas@clemente.io"]
11
+ spec.summary = %q{Basic but extensible CBOR implementation for ruby.}
12
+ spec.homepage = "https://github.com/lucas-clemente/cbor-simple"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.6"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "minitest"
23
+ spec.add_development_dependency "minitest-emoji"
24
+ spec.add_development_dependency "uuidtools"
25
+ end
data/lib/cbor.rb ADDED
@@ -0,0 +1,68 @@
1
+ require "bigdecimal"
2
+ require "stringio"
3
+
4
+ require "cbor/version"
5
+ require "cbor/consts"
6
+ require "cbor/error"
7
+ require "cbor/dumper"
8
+ require "cbor/loader"
9
+
10
+ module CBOR
11
+ def self.dump(val)
12
+ Dumper.new.dump(val)
13
+ end
14
+
15
+ # Load CBOR-encoded data.
16
+ #
17
+ # `binary` can be either a String or an IO object. In both cases the first
18
+ # encoded object will be returned.
19
+ #
20
+ # Local tags (as a hash of tag => lambda) will only be valid within this method.
21
+ def self.load(binary, local_tags = {})
22
+ if binary.is_a? String
23
+ binary = StringIO.new(binary)
24
+ elsif !binary.is_a? IO
25
+ raise CborError.new("can only load from String or IO")
26
+ end
27
+ Loader.new(binary, local_tags).load
28
+ end
29
+
30
+ def self.register_class(klass, tag, &block)
31
+ Dumper.register_class(klass, tag, &block)
32
+ end
33
+
34
+ def self.register_tag(tag, &block)
35
+ Loader.register_tag(tag, &block)
36
+ end
37
+ end
38
+
39
+ CBOR.register_class Time, CBOR::Consts::Tag::DATETIME do |val|
40
+ dump(val.iso8601(6))
41
+ end
42
+
43
+ CBOR.register_tag CBOR::Consts::Tag::DATETIME do
44
+ Time.iso8601(load)
45
+ end
46
+
47
+ CBOR.register_class BigDecimal, CBOR::Consts::Tag::DECIMAL do |val|
48
+ sign, significant_digits, base, exponent = val.split
49
+ raise CBOR::CborError.new("NaN while sending BigDecimal #{val.inspect}") if sign == 0
50
+ val = sign * significant_digits.to_i(base)
51
+ dump([exponent - significant_digits.size, val])
52
+ end
53
+
54
+ CBOR.register_tag CBOR::Consts::Tag::DECIMAL do
55
+ arr = load
56
+ raise CBOR::CborError.new("invalid decimal") if arr.length != 2
57
+ BigDecimal.new(arr[1]) * (BigDecimal.new(10) ** arr[0])
58
+ end
59
+
60
+ if defined? UUIDTools::UUID
61
+ CBOR.register_class UUIDTools::UUID, CBOR::Consts::Tag::UUID do |val|
62
+ dump val.raw.b
63
+ end
64
+
65
+ CBOR.register_tag CBOR::Consts::Tag::UUID do
66
+ UUIDTools::UUID.parse_raw(load)
67
+ end
68
+ end
@@ -0,0 +1,27 @@
1
+ module CBOR::Consts
2
+ module Major
3
+ UINT = 0
4
+ NEGINT = 1
5
+ BYTESTRING = 2
6
+ TEXTSTRING = 3
7
+ ARRAY = 4
8
+ MAP = 5
9
+ TAG = 6
10
+ SIMPLE = 7
11
+ end
12
+
13
+ module Tag
14
+ DATETIME = 0
15
+ DECIMAL = 4
16
+ UUID = 37
17
+ end
18
+
19
+ module Simple
20
+ FALSE = 20
21
+ TRUE = 21
22
+ NULL = 22
23
+ FLOAT16 = 25
24
+ FLOAT32 = 26
25
+ FLOAT64 = 27
26
+ end
27
+ end
@@ -0,0 +1,67 @@
1
+ class CBOR::Dumper
2
+ include CBOR::Consts
3
+
4
+ @@registered_classes = {}
5
+
6
+ def self.register_class(klass, tag, &block)
7
+ @@registered_classes[klass] = {
8
+ tag: tag,
9
+ block: block
10
+ }
11
+ end
12
+
13
+ def dump(val)
14
+ case val
15
+ when nil
16
+ dump_simple(Simple::NULL)
17
+ when TrueClass
18
+ dump_simple(Simple::TRUE)
19
+ when FalseClass
20
+ dump_simple(Simple::FALSE)
21
+ when Fixnum, Bignum
22
+ if val < 0
23
+ dump_uint(Major::NEGINT, -val-1)
24
+ else
25
+ dump_uint(Major::UINT, val)
26
+ end
27
+ when Float
28
+ dump_simple(Simple::FLOAT64) + [val].pack("G")
29
+ when String, Symbol
30
+ s = val.to_s.dup.force_encoding(Encoding::UTF_8)
31
+ dump_uint(s.valid_encoding? ? Major::TEXTSTRING : Major::BYTESTRING, s.bytesize) + s.b
32
+ when Array
33
+ dump_uint(Major::ARRAY, val.count) + val.map{|e| dump(e)}.join
34
+ when Hash
35
+ dump_uint(Major::MAP, val.count) + val.map{|k, v| dump(k.to_s) + dump(v)}.join
36
+ else
37
+ if h = @@registered_classes[val.class]
38
+ dump_uint(Major::TAG, h[:tag]) + instance_exec(val, &h[:block])
39
+ else
40
+ raise CBOR::CborError.new("dumping not supported for objects of type #{val.class} (#{val.inspect})")
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def dump_uint(major, val)
48
+ typeInt = (major << 5);
49
+ if val <= 23
50
+ [typeInt | val].pack("C")
51
+ elsif val < 0x100
52
+ [typeInt | 24, val].pack("CC")
53
+ elsif val < 0x10000
54
+ [typeInt | 25, val].pack("CS>")
55
+ elsif val < 0x100000000
56
+ [typeInt | 26, val].pack("CL>")
57
+ elsif val < 0x10000000000000000
58
+ [typeInt | 27, val].pack("CQ>")
59
+ else
60
+ raise CBOR::CborError.new("this cbor implementation only supports integers up to 64bit")
61
+ end
62
+ end
63
+
64
+ def dump_simple(val)
65
+ [(Major::SIMPLE << 5) | val].pack("C")
66
+ end
67
+ end
data/lib/cbor/error.rb ADDED
@@ -0,0 +1,3 @@
1
+ module CBOR
2
+ class CborError < RuntimeError; end
3
+ end
@@ -0,0 +1,94 @@
1
+ class CBOR::Loader
2
+ include CBOR::Consts
3
+
4
+ @@registered_tags = {}
5
+
6
+ def self.register_tag(tag, &block)
7
+ @@registered_tags[tag] = block
8
+ end
9
+
10
+ def initialize(io, local_tags)
11
+ @io = io
12
+ @local_tags = local_tags
13
+ end
14
+
15
+ def load
16
+ typeInt = get_bytes(1, "C")
17
+ major = (typeInt >> 5)
18
+
19
+ case major
20
+ when Major::UINT
21
+ get_uint(typeInt)
22
+ when Major::NEGINT
23
+ -get_uint(typeInt)-1
24
+ when Major::BYTESTRING
25
+ count = get_uint(typeInt)
26
+ get_bytes(count, "a*")
27
+ when Major::TEXTSTRING
28
+ count = get_uint(typeInt)
29
+ s = get_bytes(count, "a*").force_encoding(Encoding::UTF_8)
30
+ unless s.valid_encoding?
31
+ raise CBOR::CborError.new("Received non-utf8 string when expecting utf8")
32
+ end
33
+ s
34
+ when Major::ARRAY
35
+ count = get_uint(typeInt)
36
+ count.times.map { load }
37
+ when Major::MAP
38
+ count = get_uint(typeInt)
39
+ Hash[count.times.map { [load, load] }]
40
+ when Major::TAG
41
+ tag = get_uint(typeInt)
42
+ if block = @@registered_tags[tag]
43
+ instance_exec(&block)
44
+ elsif block = @local_tags[tag]
45
+ instance_exec(&block)
46
+ else
47
+ raise CBOR::CborError.new("Unknown tag #{tag}")
48
+ end
49
+ when Major::SIMPLE
50
+ case typeInt & 0x1F
51
+ when Simple::FALSE
52
+ false
53
+ when Simple::TRUE
54
+ true
55
+ when Simple::NULL
56
+ nil
57
+ when Simple::FLOAT32
58
+ get_bytes(4, "g")
59
+ when Simple::FLOAT64
60
+ get_bytes(8, "G")
61
+ else
62
+ raise CBOR::CborError.new("Unknown simple type #{simple}")
63
+ end
64
+ else
65
+ raise CBOR::CborError.new("Unknown major type #{major}")
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def get_uint(typeInt)
72
+ smallLen = typeInt & 0x1F
73
+ case smallLen
74
+ when 24
75
+ get_bytes(1, "C")
76
+ when 25
77
+ get_bytes(2, "S>")
78
+ when 26
79
+ get_bytes(4, "L>")
80
+ when 27
81
+ get_bytes(8, "Q>")
82
+ else
83
+ smallLen
84
+ end
85
+ end
86
+
87
+ def get_bytes(count, pack_opts)
88
+ s = @io.read(count)
89
+ if s.size < count
90
+ raise CBOR::CborError.new("Unexpected end of input")
91
+ end
92
+ s.unpack(pack_opts).first
93
+ end
94
+ end
@@ -0,0 +1,3 @@
1
+ module CBOR
2
+ VERSION = "1.0.0"
3
+ end
data/test/data.rb ADDED
@@ -0,0 +1,25 @@
1
+ TEST_DATA = {
2
+ "\xf6".b => nil,
3
+ "\xf5".b => true,
4
+ "\xf4".b => false,
5
+ "\x01".b => 1,
6
+ "\x18\x2a".b => 42,
7
+ "\x18\x64".b => 100,
8
+ "\x1a\x00\x0f\x42\x40".b => 1000000,
9
+ "\x1b\x00\x00\x00\xe8\xd4\xa5\x10\x00".b => 1000000000000,
10
+ "\x20".b => -1,
11
+ "\x38\x63".b => -100,
12
+ "\x39\x03\xe7".b => -1000,
13
+ "\x3b\x00\x00\x00\xe8\xd4\xa5\x0f\xff".b => -1000000000000,
14
+ "\xfb\x3f\xf1\x99\x99\x99\x99\x99\x9a".b => 1.1,
15
+ "\xfb\x7e\x37\xe4\x3c\x88\x00\x75\x9c".b => 1.0e+300,
16
+ "\x42\xc3\x28".b => "\xc3\x28".b,
17
+ "\x66foobar".b => "foobar",
18
+ "\x67f\xc3\xb6obar".b => "föobar",
19
+ "\x82\x63foo\x63bar".b => ["foo", "bar"],
20
+ "\xa2\x63foo\x01\x63bar\x02".b => {"foo" => 1, "bar" => 2},
21
+ "\xc0\x78\x1b\x32\x30\x31\x33\x2d\x30\x33\x2d\x32\x31\x54\x32\x30\x3a\x30\x34\x3a\x30\x30.000000\x5a".b => Time.iso8601("2013-03-21T20:04:00Z"),
22
+ "\xc0\x78\x1b\x32\x30\x31\x33\x2d\x30\x33\x2d\x32\x31\x54\x32\x30\x3a\x30\x34\x3a\x30\x30.000001\x5a".b => Time.iso8601("2013-03-21T20:04:00.000001Z"),
23
+ "\xc4\x82\x21\x19\x6a\xb3".b => BigDecimal.new("273.15"),
24
+ "\xd8\x25\x50\xD5\x06\x81\xAE\xB2\xC1\x49\x4B\xB2\x6E\x7F\xA4\xF7\xEE\x61\x37".b => UUIDTools::UUID.parse("D50681AE-B2C1-494B-B26E-7FA4F7EE6137"),
25
+ }
data/test/dump_test.rb ADDED
@@ -0,0 +1,27 @@
1
+ require "test_helper"
2
+ require "data"
3
+
4
+ class TestDump < Minitest::Test
5
+ def test_dumping
6
+ TEST_DATA.each do |bin, val|
7
+ assert_equal bin, CBOR.dump(val), "dumping #{val.inspect}"
8
+ end
9
+ end
10
+
11
+ class Dummy; end
12
+ def test_dump_invalid
13
+ assert_raises CBOR::CborError do
14
+ CBOR.dump(Dummy.new)
15
+ end
16
+ end
17
+
18
+ def test_dump_large_int
19
+ assert_raises CBOR::CborError do
20
+ CBOR.dump(18446744073709551616)
21
+ end
22
+ end
23
+
24
+ def test_dumps_symbols
25
+ assert_equal "\x66foobar".b, CBOR.dump(:foobar)
26
+ end
27
+ end
data/test/load_test.rb ADDED
@@ -0,0 +1,19 @@
1
+ require "test_helper"
2
+
3
+ class TestLoad < Minitest::Test
4
+ def test_loading
5
+ TEST_DATA.each do |bin, val|
6
+ assert_equal val, CBOR.load(bin), "loading #{val.inspect}"
7
+ end
8
+ end
9
+
10
+ def test_loading_from_junk
11
+ assert_raises CBOR::CborError do
12
+ CBOR.load(5)
13
+ end
14
+ end
15
+
16
+ def test_loading_local_tags
17
+ assert_equal false, CBOR.load("\xd8\x26\xf5", {0x26 => ->{ !load }})
18
+ end
19
+ end
@@ -0,0 +1,4 @@
1
+ require "minitest/autorun"
2
+ require "minitest/emoji"
3
+ require "uuidtools"
4
+ require "cbor"
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cbor-simple
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Lucas Clemente
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-emoji
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: uuidtools
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - lucas@clemente.io
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - cbor-simple.gemspec
96
+ - lib/cbor.rb
97
+ - lib/cbor/consts.rb
98
+ - lib/cbor/dumper.rb
99
+ - lib/cbor/error.rb
100
+ - lib/cbor/loader.rb
101
+ - lib/cbor/version.rb
102
+ - test/data.rb
103
+ - test/dump_test.rb
104
+ - test/load_test.rb
105
+ - test/test_helper.rb
106
+ homepage: https://github.com/lucas-clemente/cbor-simple
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.2.2
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Basic but extensible CBOR implementation for ruby.
130
+ test_files:
131
+ - test/data.rb
132
+ - test/dump_test.rb
133
+ - test/load_test.rb
134
+ - test/test_helper.rb