cbor-packed 0.1.3

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
+ SHA256:
3
+ metadata.gz: 7ec7bd11d45d35c418a0fe98aa43c369b3384b2b43cb94486fd6e3463eae3232
4
+ data.tar.gz: 88efc5decfeeedb1c049df0e5a9ba126c35bdf8a661218e29db3eb836c39f9c6
5
+ SHA512:
6
+ metadata.gz: 1b6add7d575ff6ab3f239c9cf8f4f434ec8cc3c7d02e717c58b8c79a2007dd6d033ec10739c8bbac788e73d292f37501a002730303a8f58479a7131243fd04ac
7
+ data.tar.gz: efd9b83c4da71f11354398048e6421770583b2fef0e22b4e3ac1f964ba2d5f4d2d69b2cb1e6441058062313940d6fee492f6dd7579dd0e25202734229a33058b
@@ -0,0 +1,17 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "cbor-packed"
3
+ s.version = "0.1.3"
4
+ s.summary = "CBOR (Concise Binary Object Representation) packer"
5
+ s.description = %q{cbor-packed implements packed encoding for CBOR, RFC 7049 Section 3.9}
6
+ s.author = "Carsten Bormann"
7
+ s.email = "cabo@tzi.org"
8
+ s.license = "Apache-2.0"
9
+ s.homepage = "http://cbor.io/"
10
+ s.has_rdoc = false
11
+ s.test_files = Dir['test/**/*.rb']
12
+ s.files = Dir['lib/**/*.rb'] + %w(cbor-packed.gemspec) + Dir['bin/**/*.rb']
13
+ s.executables = Dir['bin/**/*.rb'].map {|x| File.basename(x)}
14
+ s.required_ruby_version = '>= 1.9.2'
15
+
16
+ s.require_paths = ["lib"]
17
+ end
@@ -0,0 +1,305 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "cbor" unless defined? CBOR
4
+ # require "pp"
5
+
6
+ $compression_hack = 0
7
+
8
+ module CBOR
9
+ PACKED_TAG = 51
10
+ REF_TAG = 6
11
+
12
+ class Packer
13
+ def self.from_item(item)
14
+ count = Hash.new(0)
15
+ item.cbor_visit do |o|
16
+ (count[o] += 1) == 1
17
+ # if the count gets > 1, we can stop visiting, so we return false in the block
18
+ end
19
+ # pp count
20
+ # count is now a Hash with all data items as keys and the number of times they occur as values
21
+
22
+ # choose those matches that are occurring > 1, make first rough estimate of saving
23
+ good_count = count.select {|k, v| v > 1}.map {|k, v| [k, v, l = k.to_cbor.length,
24
+ (v-1)*(l-1)]}
25
+ # good_count is now an array of [k, v, length, savings] tuples
26
+
27
+ # select those that potentially have savings (> 0) and sort by best saving first
28
+ better_count = good_count.to_a.select {|a| a[3] > 0}.sort_by {|a| -a[3]}
29
+ # pp better_count
30
+
31
+ # now: take the best out???; re-visit that reducing by n; re-sort and filter???
32
+ # sort by descending number of references we'll get -- the higher reference counts go first
33
+ match_array = better_count.sort_by {|a| -a[1]}.map {|a| a[0]}
34
+ # pp match_array
35
+
36
+ # XXX the below needs to be done with arrays and (hard!) maps as well
37
+ # do this on the reverse to find common suffixes
38
+ # select all strings (ignoring reference counts) and sort them
39
+ strings = count.select {|k, v| String === k}.map(&:first).sort
40
+ if strings != []
41
+ string_common = strings[1..-1].zip(strings).map{ |y, x|
42
+ l = x.chars.zip(y.chars).take_while{|a, b| a == b}.length # should be bytes
43
+ [x, l]
44
+ } << [strings[-1], 0]
45
+ # string_common: list of strings/counts of number of /bytes/ matching with next
46
+ # pp string_common
47
+ end
48
+ translate = {}
49
+ prefixes = []
50
+ if string_common
51
+ prefix_stack = [[0, false]] # sentinel
52
+ pos = 0 # mirror prefix_stack[-1][0]
53
+ tag_no = REF_TAG
54
+ string_common.each do |s, l|
55
+ if l > pos + 2 + $compression_hack
56
+ if t = prefix_stack[-1][1] # if we still have a prefix left
57
+ prefixes << CBOR::Tagged.new(t, s[pos...l])
58
+ else
59
+ prefixes << s[0...l]
60
+ end
61
+ prefix_stack << [l, tag_no]
62
+ pos = l
63
+ tag_no += 1
64
+ tag_no = 225 if tag_no == REF_TAG+1
65
+ tag_no = 28704 if tag_no == 256
66
+ end
67
+ if t = prefix_stack[-1][1] # if we still have a viable prefix left
68
+ translate[s] = CBOR::Tagged.new(t, s[pos..-1])
69
+ end
70
+ # pop the prefix stack
71
+ while l < pos
72
+ prefix_stack.pop
73
+ pos = prefix_stack[-1][0]
74
+ end
75
+ # pp prefix_stack
76
+ # pp pos
77
+ end
78
+
79
+ end
80
+ # pp translate
81
+ # XXX test replacing match_array here
82
+ match_array = match_array.map do |v|
83
+ if r = translate[v]
84
+ # puts "*** replacing #{v.inspect} by #{r.inspect}"
85
+ r
86
+ else
87
+ v
88
+ end
89
+ end
90
+ # pp [:PREFIXES, prefixes]
91
+ # pp translate
92
+ new(match_array, prefixes, [], translate)
93
+ end
94
+ def initialize(match_array, prefix_array, suffix_array, translate)
95
+ @hit = translate
96
+ # XXX: make sure we don't overwrite the existing prefix compression values!
97
+ # (this should really be done downwards, ...) 16 x 1, 160 x 2, (512-48) x 3
98
+ match_array[0...16].each_with_index do |o, i|
99
+ @hit[o] = CBOR::Simple.new(i)
100
+ end
101
+ # if m = match_array[16...128]
102
+ # m.each_with_index do |o, i|
103
+ # @hit[o] = CBOR::Simple.new(i + 128)
104
+ # end
105
+ # end
106
+ if m = match_array[16..-1]
107
+ m.each_with_index do |o, i|
108
+ @hit[o] = CBOR::Tagged.new(REF_TAG, (i >> 1) ^ -(i & 1))
109
+ end
110
+ end
111
+ # add one round of transitive matching
112
+ @hit.each do |k, v|
113
+ if r = @hit[v]
114
+ @hit[k] = r
115
+ end
116
+ end
117
+ # p @hit
118
+ @match_array = match_array
119
+ # @prefix = {} -- do that later
120
+ @prefix_array = prefix_array
121
+ @suffix_array = suffix_array
122
+ end
123
+ def has(o)
124
+ @hit[o]
125
+ end
126
+ def pack(pa)
127
+ # Don't forget to pack the match_array!
128
+ CBOR::Tagged.new(PACKED_TAG, [@match_array, @prefix_array, @suffix_array, pa])
129
+ end
130
+ end
131
+
132
+ class Unpacker
133
+ def initialize(match_array, prefix_array, suffix_array)
134
+ @simple_array = match_array[0...16]
135
+ @tagged_array = match_array[16..-1]
136
+ # index with 2i for i >= 0 or ~2i for i < 0
137
+ # no map as we need to populate in progress
138
+ # pp prefix_array
139
+ @prefix_array = []
140
+ prefix_array.each {|x| @prefix_array << x.to_unpacked_cbor1(self)}
141
+ @suffix_array = []
142
+ suffix_array.each {|x| @prefix_array << x.to_unpacked_cbor1(self)}
143
+ # XXX order? -- must do lazily!
144
+ end
145
+ def unsimple(sv)
146
+ @simple_array[sv]
147
+ end
148
+ def untag(tv)
149
+ @tagged_array[(i << 1) ^ (i >> 63)]
150
+ end
151
+ def unprefix(n)
152
+ @prefix_array[n]
153
+ end
154
+ def unsuffix(n)
155
+ @suffix_array[n]
156
+ end
157
+ end
158
+
159
+ module Packed
160
+
161
+ module Object_Packed_CBOR
162
+ def cbor_visit
163
+ yield self
164
+ end
165
+ def to_unpacked_cbor1(unpacker)
166
+ self
167
+ end
168
+ def to_packed_cbor(packer = Packer.from_item(self))
169
+ packer.pack(to_packed_cbor1(packer))
170
+ end
171
+ def to_packed_cbor1(packer = Packer.from_item(self))
172
+ if c = packer.has(self)
173
+ c
174
+ else
175
+ # Need to do the prefix dance, too
176
+ self
177
+ end
178
+ end
179
+ end
180
+ Object.send(:include, Object_Packed_CBOR)
181
+
182
+ module Simple_Packed_CBOR
183
+ def to_unpacked_cbor1(unpacker)
184
+ if v = unpacker.unsimple(value)
185
+ v.to_unpacked_cbor1(unpacker)
186
+ else
187
+ self
188
+ end
189
+ end
190
+ end
191
+ CBOR::Simple.send(:include, Simple_Packed_CBOR)
192
+
193
+ module String_Packed_CBOR
194
+ def packed_merge(other, unpacker)
195
+ # add checks
196
+ to_unpacked_cbor1(unpacker) + other.to_unpacked_cbor1(unpacker)
197
+ end
198
+ end
199
+ String.send(:include, String_Packed_CBOR)
200
+
201
+ module Array_Packed_CBOR
202
+ def cbor_visit(&b)
203
+ if yield self
204
+ each do |o|
205
+ o.cbor_visit(&b)
206
+ end
207
+ end
208
+ end
209
+ def to_unpacked_cbor1(unpacker)
210
+ map {|x| x.to_unpacked_cbor1(unpacker)}
211
+ end
212
+ def to_packed_cbor1(packer = Packer.from_item(self))
213
+ if c = packer.has(self)
214
+ c.to_unpacked_cbor1(unpacker)
215
+ else
216
+ # TODO: Find useful prefixes
217
+ map {|x| x.to_packed_cbor1(packer)}
218
+ end
219
+ end
220
+ def packed_merge(other, unpacker)
221
+ # TODO: add checks
222
+ to_unpacked_cbor1(unpacker) + other.to_unpacked_cbor1(unpacker)
223
+ end
224
+ end
225
+ Array.send(:include, Array_Packed_CBOR)
226
+
227
+ module Hash_Packed_CBOR
228
+ def cbor_visit(&b)
229
+ if yield self
230
+ each do |k, v|
231
+ k.cbor_visit(&b)
232
+ v.cbor_visit(&b)
233
+ end
234
+ end
235
+ end
236
+ def to_unpacked_cbor1(unpacker)
237
+ Hash[map {|k, v| [k.to_unpacked_cbor1(unpacker), v.to_unpacked_cbor1(unpacker)]}]
238
+ end
239
+ def to_packed_cbor1(packer = Packer.from_item(self))
240
+ if c = packer.has(self)
241
+ c.to_unpacked_cbor1(unpacker)
242
+ else
243
+ # TODO: Find useful prefixes
244
+ Hash[map {|k, v| [k.to_packed_cbor1(packer), v.to_packed_cbor1(packer)]}]
245
+ end
246
+ end
247
+ def packed_merge(other, unpacker)
248
+ # TODO: add checks
249
+ to_unpacked_cbor1(unpacker).merge other.to_unpacked_cbor1(unpacker)
250
+ end
251
+ end
252
+ Hash.send(:include, Hash_Packed_CBOR)
253
+
254
+ module Tagged_Packed_CBOR
255
+ def cbor_visit(&b)
256
+ if yield self
257
+ value.cbor_visit(&b)
258
+ end
259
+ end
260
+ def to_unpacked_cbor
261
+ if tag == PACKED_TAG
262
+ # check that this really is an array
263
+ # warn value.to_yaml
264
+ ma, pa, sa, pv = value
265
+ unpacker = Unpacker.new(ma, pa, sa)
266
+ pv.to_unpacked_cbor1(unpacker)
267
+ else
268
+ fail "error message here"
269
+ end
270
+ end
271
+ def to_unpacked_cbor1(unpacker)
272
+ case tag
273
+ when REF_TAG
274
+ if Integer === value
275
+ unpacker(untag(value))
276
+ else
277
+ unpacker.unprefix(0).packed_merge value, unpacker
278
+ end
279
+ when 225...256
280
+ unpacker.unprefix(tag-(256-32)).packed_merge value, unpacker
281
+ when 28704...32768 # (- 28704 (- 32768 4096)) == 32
282
+ unpacker.unprefix(tag-(32768-4096)).packed_merge value, unpacker
283
+ when 1879052288...2147483648 # (- 1879052288 (- 2147483648 268435456)) == 4096
284
+ unpacker.unprefix(tag-(2147483648-268435456)).packed_merge value, unpacker
285
+ when 216...224
286
+ value.packed_merge unpacker.unsuffix(tag-216), unpacker
287
+ when 27647...28672
288
+ value.packed_merge unpacker.unsuffix(tag-(28672-1024)), unpacker
289
+ when 1811940352...1879048192
290
+ value.packed_merge unpacker.unsuffix(tag-(1879048192-67108864)), unpacker
291
+ else
292
+ CBOR::Tagged.new(tag, value.to_unpacked_cbor1(unpacker))
293
+ end
294
+ end
295
+ def to_packed_cbor1(packer = Packer.from_item(self))
296
+ if c = packer.has(self)
297
+ c
298
+ else
299
+ CBOR::Tagged.new(tag, value.to_packed_cbor1(packer))
300
+ end
301
+ end
302
+ end
303
+ CBOR::Tagged.send(:include, Tagged_Packed_CBOR)
304
+ end
305
+ end
data/test/exa2.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'cbor-pure'
2
+ require 'cbor-packed'
3
+ require 'cbor-diagnostic'
4
+ require 'json'
5
+ require 'yaml'
6
+
7
+ fn = "/Users/cabo/big/wot-thing-description/test-bed/data/plugfest/2017-05-osaka/MyLED_f.jsonld"
8
+ puts fn.split("/")[4..-1].join("/")
9
+ jf = File.read(fn)
10
+ puts "JSON file: #{jf.length}"
11
+ jo = JSON.parse(jf)
12
+ jsw = JSON::generate(jo, :allow_nan => true, :max_nesting => false)
13
+ # l3 jsw, "JSON no whitespace"
14
+ File.write("/tmp/jsw", jsw)
15
+
16
+ # p jo
17
+ cb = jo.to_cbor
18
+ # l3 cb, "CBOR"
19
+ $compression_hack = 1000
20
+ pa = jo.to_packed_cbor
21
+ cbp = pa.to_cbor
22
+ # l3 cbp, "CBOR packed"
23
+ puts "CBOR packed (sharing only): #{cbp.length}"
24
+ $compression_hack = 0
25
+ pa = jo.to_packed_cbor
26
+ cbp = pa.to_cbor
27
+ # l3 cbp, "CBOR packed"
28
+ puts "CBOR packed: #{cbp.length}"
29
+ File.write("/tmp/cbp", cbp)
30
+ File.write("/tmp/cbu", cb)
31
+ pad = CBOR.decode(cbp)
32
+ up = pad.to_unpacked_cbor
33
+ pp up == jo
34
+ if up != jo
35
+ puts up.to_yaml
36
+ puts pad.to_yaml # get rid of unwanted sharing...
37
+ end
data/test/exa2r.rb ADDED
@@ -0,0 +1,59 @@
1
+ require 'cbor-pure'
2
+ require 'cbor-packed'
3
+ require 'treetop'
4
+ require 'cbor-diag-parser'
5
+ require 'cbor-diagnostic'
6
+ require 'json'
7
+ require 'yaml'
8
+
9
+ input = DATA.read
10
+
11
+
12
+ parser = CBOR_DIAGParser.new
13
+ if result = parser.parse(input)
14
+ o = result.to_rb.to_unpacked_cbor
15
+ puts o.cbor_diagnostic
16
+ else
17
+ puts "*** can't parse #{i}"
18
+ puts "*** #{parser.failure_reason}"
19
+ end
20
+
21
+ # ruby -I ../lib exa2r.rb | jq > .out1
22
+ # jq < ~/big/wot-thing-description/test-bed/data/plugfest/2017-05-osaka/MyLED_f.jsonld > .out2
23
+ # ruby .check
24
+ # => true
25
+
26
+
27
+ __END__
28
+
29
+ 51([/shared/["name", "@type", "links", "href", "mediaType",
30
+ / 0 1 2 3 4 /
31
+ "application/json", "outputData", {"valueType": {"type":
32
+ / 5 6 7 /
33
+ "number"}}, ["Property"], "writable", "valueType", "type"],
34
+ / 8 9 10 11 /
35
+ /prefix/ ["http://192.168.1.10", 6("3:8445/wot/thing"),
36
+ / 6 225 /
37
+ 225("/MyLED/"), 226("rgbValue"), "rgbValue",
38
+ / 226 227 228 /
39
+ {simple(6): simple(7), simple(9): true, simple(1): simple(8)}],
40
+ / 229 /
41
+ /suffix/ [],
42
+ /rump/ {simple(0): "MyLED",
43
+ "interactions": [
44
+ 229({simple(2): [{simple(3): 227("Red"), simple(4): simple(5)}],
45
+ simple(0): 228("Red")}),
46
+ 229({simple(2): [{simple(3): 227("Green"), simple(4): simple(5)}],
47
+ simple(0): 228("Green")}),
48
+ 229({simple(2): [{simple(3): 227("Blue"), simple(4): simple(5)}],
49
+ simple(0): 228("Blue")}),
50
+ 229({simple(2): [{simple(3): 227("White"), simple(4): simple(5)}],
51
+ simple(0): "rgbValueWhite"}),
52
+ {simple(2): [{simple(3): 226("ledOnOff"), simple(4): simple(5)}],
53
+ simple(6): {simple(10): {simple(11): "boolean"}}, simple(0):
54
+ "ledOnOff", simple(9): true, simple(1): simple(8)},
55
+ {simple(2): [{simple(3): 226("colorTemperatureChanged"),
56
+ simple(4): simple(5)}], simple(6): simple(7), simple(0):
57
+ "colorTemperatureChanged", simple(1): ["Event"]}],
58
+ simple(1): "Lamp", "id": "0", "base": 225(""),
59
+ "@context": 6("2:8444/wot/w3c-wot-td-context.jsonld")}])
@@ -0,0 +1,101 @@
1
+ require 'cbor-pure'
2
+ require 'cbor-packed'
3
+ require 'cbor-diagnostic'
4
+ require 'json'
5
+ require 'yaml'
6
+ require 'zlib'
7
+ require 'lz4-ruby'
8
+
9
+ def l3(bin, desc)
10
+ puts "#{desc}: #{bin.length}, deflate: #{Zlib.deflate(bin, Zlib::BEST_COMPRESSION).length}, lz4: #{LZ4::compress(bin).length}, lz4hc: #{LZ4::compressHC(bin).length}"
11
+ end
12
+
13
+ fn = "/Users/cabo/big/wot-thing-description/test-bed/data/plugfest/2017-05-osaka/MyLED_f.jsonld"
14
+ puts fn.split("/")[4..-1].join("/")
15
+ jf = File.read(fn)
16
+ puts "JSON file: #{jf.length}"
17
+ jo = JSON.parse(jf)
18
+ jsw = JSON::generate(jo, :allow_nan => true, :max_nesting => false)
19
+ l3 jsw, "JSON no whitespace"
20
+ File.write("/tmp/jsw", jsw)
21
+
22
+ # p jo
23
+ cb = jo.to_cbor
24
+ l3 cb, "CBOR"
25
+ $compression_hack = 1000
26
+ pa = jo.to_packed_cbor
27
+ cbp = pa.to_cbor
28
+ # l3 cbp, "CBOR packed"
29
+ puts "CBOR packed (sharing only): #{cbp.length}"
30
+ $compression_hack = 0
31
+ pa = jo.to_packed_cbor
32
+ cbp = pa.to_cbor
33
+ # l3 cbp, "CBOR packed"
34
+ puts "CBOR packed: #{cbp.length}"
35
+ File.write("/tmp/cbp", cbp)
36
+ File.write("/tmp/cbu", cb)
37
+ pad = CBOR.decode(cbp)
38
+ # puts pad.to_yaml
39
+ up = pad.to_unpacked_cbor
40
+ pp up == jo
41
+ if up != jo
42
+ puts up.to_yaml
43
+ puts pad.to_yaml # get rid of unwanted sharing...
44
+ end
45
+
46
+ # puts "#{pa.value.map {|x| x.to_cbor.length}}"
47
+ puts pa.value[1].to_json
48
+
49
+ exin = JSON.load(DATA)
50
+ exout = exin.to_packed_cbor
51
+ p CBOR.encode(exin).length, CBOR.encode(exout).length
52
+ puts exout.cbor_diagnostic
53
+ if exin != exout.to_unpacked_cbor
54
+ fail exout.inspect
55
+ end
56
+
57
+ exit
58
+
59
+ # $compression_hack = 1000
60
+
61
+ a1 = {"aaaa" => "aaaaaaaa", "aaaaaaaaaaaa" => "aaaa", "aaaaaaaa" => "aaaaaaaaaaaa"}
62
+ pa1 = a1.to_packed_cbor
63
+ puts pa1.to_yaml
64
+ a1u = pa1.to_unpacked_cbor
65
+ if a1u != a1
66
+ puts a1u.to_yaml
67
+ end
68
+
69
+ __END__
70
+
71
+ { "store": {
72
+ "book": [
73
+ { "category": "reference",
74
+ "author": "Nigel Rees",
75
+ "title": "Sayings of the Century",
76
+ "price": 8.95
77
+ },
78
+ { "category": "fiction",
79
+ "author": "Evelyn Waugh",
80
+ "title": "Sword of Honour",
81
+ "price": 12.99
82
+ },
83
+ { "category": "fiction",
84
+ "author": "Herman Melville",
85
+ "title": "Moby Dick",
86
+ "isbn": "0-553-21311-3",
87
+ "price": 8.95
88
+ },
89
+ { "category": "fiction",
90
+ "author": "J. R. R. Tolkien",
91
+ "title": "The Lord of the Rings",
92
+ "isbn": "0-395-19395-8",
93
+ "price": 22.99
94
+ }
95
+ ],
96
+ "bicycle": {
97
+ "color": "red",
98
+ "price": 19.95
99
+ }
100
+ }
101
+ }
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cbor-packed
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Carsten Bormann
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-05-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: cbor-packed implements packed encoding for CBOR, RFC 7049 Section 3.9
14
+ email: cabo@tzi.org
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - cbor-packed.gemspec
20
+ - lib/cbor-packed.rb
21
+ - test/exa2.rb
22
+ - test/exa2r.rb
23
+ - test/test-packed.rb
24
+ homepage: http://cbor.io/
25
+ licenses:
26
+ - Apache-2.0
27
+ metadata: {}
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 1.9.2
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubygems_version: 3.2.15
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: CBOR (Concise Binary Object Representation) packer
47
+ test_files:
48
+ - test/exa2.rb
49
+ - test/exa2r.rb
50
+ - test/test-packed.rb