cbor-packed 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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