UEL 0.1.0

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: 20137fd4dcf93bec9b07837ec1258036ebce587ed3bf449441207c0b6e8cdd2a
4
+ data.tar.gz: 76291fb545ee2b209e5788f5390d3ea0e3175f54d839fe93826670692a407137
5
+ SHA512:
6
+ metadata.gz: eecdc2920f87a00afdc867d36dcce4d00bc1e768a1647ee241d9f421bcd1356e59115326a6b1a28f8f26f7109ddefe37a2717dd8862bb09c2616bdfc7dbda8fe
7
+ data.tar.gz: 7587e3602d1858a2e7de9a29a22efb5771957f392d48f2aa98e4f064ca07a104c992b9c2db89cab30b88bdcdcda3b22cbdb4d12b5802bebf60d1813d730a6d88
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ Gemfile.lock
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
14
+
15
+ *.so
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7
6
+ - 2.6
7
+ - 2.5
8
+ - 2.4
9
+ before_install: gem install bundler -v 2.1.4
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at matthew.b.carey@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in untitled_etf_lib.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Matthew Carey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # UEL (Untitled ETF Lib)
2
+
3
+ The other ETF lib I could find is pretty slow, so here's this one. I couldn't think of a good name.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'uel'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install uel
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ require 'uel'
25
+
26
+ UEL.encode(some_object)
27
+ UEL.decode(some_string_of_etf_data)
28
+ ```
29
+
30
+ ## Contributing
31
+
32
+ Bug reports and pull requests are welcome on GitHub at https://github.com/swarley/uel. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/swarley/uel/blob/master/CODE_OF_CONDUCT.md).
33
+
34
+
35
+ ## License
36
+
37
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
38
+
39
+ ## Code of Conduct
40
+
41
+ Everyone interacting in the UEL project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/swarley/uel/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "rake/extensiontask"
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => [:compile, :spec]
8
+
9
+ Rake::ExtensionTask.new("uel") do |ext|
10
+ ext.name = "uel"
11
+ ext.lib_dir = "lib/"
12
+ ext.ext_dir = "ext/uel"
13
+ ext.gem_spec = Gem::Specification.load('uel.gemspec')
14
+ end
data/benchmark.rb ADDED
@@ -0,0 +1,97 @@
1
+ require 'bert'
2
+ require 'erlang-etf'
3
+ require 'oj'
4
+ require 'untitled_etf_lib'
5
+ require 'json'
6
+ require 'benchmark/ips'
7
+
8
+ Benchmark.ips do |x|
9
+ etf_data = "\x83b\x00\x0009"
10
+ json_data = "12345"
11
+ raise "OJ FAIL" unless Oj.load(json_data) == 12345
12
+ raise "UEL FAIL" unless UEL.decode(etf_data) == 12345
13
+ raise "EE FAIL" unless Erlang.binary_to_term(etf_data) == 12345
14
+
15
+ #x.report("oj integer") { Oj.load json_data }
16
+ #x.report("json integer") { JSON.load json_data }
17
+ x.report("uel integer") { UEL.decode(etf_data) }
18
+ x.report("ee integer") { Erlang.binary_to_term(etf_data) }
19
+
20
+ x.compare!
21
+ end
22
+
23
+ Benchmark.ips do |x|
24
+ etf_data = "\x83FA\x9do4T~ku"
25
+ json_data = "123456789.123456789"
26
+ raise "OJ FAIL" unless Oj.load(json_data).to_f == 123456789.123456789
27
+ raise "UEL FAIL" unless UEL.decode(etf_data) == 123456789.123456789
28
+ raise "EE FAIL" unless Erlang.binary_to_term(etf_data) == 123456789.123456789
29
+
30
+ #x.report("oj float") { Oj.load json_data }
31
+ #x.report("json float") { JSON.load json_data }
32
+ x.report("uel float") { UEL.decode(etf_data) }
33
+ x.report("ee float") { Erlang.binary_to_term(etf_data) }
34
+
35
+ x.compare!
36
+ end
37
+
38
+ Benchmark.ips do |x|
39
+ etf_data = "\x83j"
40
+ json_data = "[]"
41
+
42
+ raise "OJ FAIL" unless Oj.load(json_data) == []
43
+ raise "UEL FAIL" unless UEL.decode(etf_data) == []
44
+ raise "BERT FAIL" unless Erlang.binary_to_term(etf_data) == []
45
+
46
+ #x.report("oj []") { Oj.load json_data }
47
+ #x.report("json []") { JSON.load json_data }
48
+ x.report("uel nil") { UEL.decode(etf_data) }
49
+ x.report("ee nil") { Erlang.binary_to_term(etf_data) }
50
+ x.compare!
51
+ end
52
+
53
+ Benchmark.ips do |x|
54
+ etf_data = "\x83n\x08\x00\xff\xff\xff\xff\xff\xff\xff\xff"
55
+ json_data = 0xFFFFFFFFFFFFFFFF.to_s
56
+
57
+ raise "OJ FAIL" unless Oj.load(json_data) == 0xFFFFFFFFFFFFFFFF
58
+ raise "UEL FAIL" unless UEL.decode(etf_data) == 0xFFFFFFFFFFFFFFFF
59
+ raise "BERT FAIL" unless Erlang.binary_to_term(etf_data) == 0xFFFFFFFFFFFFFFFF
60
+
61
+ #x.report("oj big_num") { Oj.load json_data }
62
+ #x.report("json big_num") { JSON.load json_data }
63
+ x.report("uel big_num") { UEL.decode(etf_data) }
64
+ x.report("ee big_num") { Erlang.binary_to_term(etf_data) }
65
+ x.compare!
66
+ end
67
+
68
+ Benchmark.ips do |x|
69
+ etf_data = "\x83l\x00\x00\x00\x03a\x01F?\xf1\x99\x99\x99\x99\x99\x9an\x06\x00\x00\xa0rN\x18\tj"
70
+ json_data = "[1, 1.1, 10000000000000]"
71
+
72
+ raise "OJ FAIL" unless Oj.load(json_data) == [1, 1.1, 10000000000000]
73
+ raise "UEL FAIL" unless UEL.decode(etf_data) == [1, 1.1, 10000000000000]
74
+ raise "EE FAIL" unless Erlang.binary_to_term(etf_data) == [1, 1.1, 10000000000000]
75
+
76
+ x.report("oj list") { Oj.load json_data }
77
+ x.report("json list") { JSON.load json_data }
78
+ x.report("uel list") { UEL.decode(etf_data) }
79
+ x.report("ee list") { Erlang.binary_to_term(etf_data) }
80
+ x.compare!
81
+ end
82
+
83
+ Benchmark.ips do |x|
84
+ etf_data = "\x83t\x00\x00\x00\x02m\x00\x00\x00\x03fooa\x02m\x00\x00\x00\x03barl\x00\x00\x00\x02a\x04a\x05j"
85
+ json_data = '{"foo": 2, "bar": [4, 5]}'
86
+
87
+
88
+ raise "OJ FAIL" unless Oj.load(json_data) == {"foo" => 2, "bar" => [4, 5]}
89
+ raise "UEL FAIL" unless UEL.decode(etf_data) == {"foo" => 2, "bar" => [4, 5]}
90
+ raise "EE FAIL" unless Erlang.binary_to_term(etf_data) == {"foo" => 2, "bar" => [4, 5]}
91
+
92
+ x.report("oj map") { Oj.load json_data }
93
+ x.report("json map") { JSON.load json_data }
94
+ x.report("uel map") { UEL.decode(etf_data) }
95
+ x.report("ee map") { Erlang.binary_to_term(etf_data) }
96
+ x.compare!
97
+ end
data/ext/uel/decode.c ADDED
@@ -0,0 +1,288 @@
1
+ #include <netinet/in.h>
2
+ #include <endian.h>
3
+ #include <ruby.h>
4
+ #include <ruby/encoding.h>
5
+ #include <string.h>
6
+ #include <stdlib.h>
7
+ #include "./extconf.h"
8
+ #include "./uel.h"
9
+
10
+ #if HAVE_ZLIB_H
11
+ #include <zlib.h>
12
+ #endif
13
+
14
+ VALUE uel_decode(VALUE self, VALUE data) {
15
+ struct uel_bert_data bert_data;
16
+
17
+ Check_Type(data, T_STRING);
18
+
19
+ bert_data.buff = (uint8_t *)RSTRING_PTR(data);
20
+ bert_data.end = (uint8_t *)(bert_data.buff + RSTRING_LEN(data));
21
+
22
+ if (uel_read8(&bert_data) != UEL_ETF_VERSION_NUMBER)
23
+ rb_raise(rb_eNotImpError, "Unknown ETF version");
24
+
25
+ return uel_decode_term(&bert_data);
26
+ }
27
+
28
+ VALUE uel_decode_term(struct uel_bert_data *data) {
29
+ switch(uel_read8(data)) {
30
+ case UEL_ETF_SMALL_INTEGER_ID:
31
+ return uel_read_small_integer(data);
32
+ case UEL_ETF_INTEGER_ID:
33
+ return uel_read_integer(data);
34
+ case UEL_ETF_FLOAT_ID:
35
+ return uel_read_float(data);
36
+ case UEL_ETF_SMALL_BIGNUM_ID:
37
+ return uel_read_small_bignum(data);
38
+ case UEL_ETF_LARGE_BIGNUM_ID:
39
+ return uel_read_large_bignum(data);
40
+ case UEL_ETF_NEW_FLOAT_ID:
41
+ return uel_read_new_float(data);
42
+ case UEL_ETF_NIL_ID:
43
+ return uel_read_nil(data);
44
+ case UEL_ETF_LIST_ID:
45
+ return uel_read_list(data);
46
+ case UEL_ETF_MAP_ID:
47
+ return uel_read_map(data);
48
+ case UEL_ETF_BINARY_ID:
49
+ return uel_read_binary(data);
50
+ case UEL_ETF_SMALL_TUPLE_ID:
51
+ return uel_read_small_tuple(data);
52
+ case UEL_ETF_LARGE_TUPLE_ID:
53
+ return uel_read_large_tuple(data);
54
+ case UEL_ETF_ATOM_ID:
55
+ case UEL_ETF_ATOM_UTF8_ID:
56
+ return uel_read_atom(data);
57
+ case UEL_ETF_SMALL_ATOM_ID:
58
+ case UEL_ETF_SMALL_ATOM_UTF8_ID:
59
+ return uel_read_small_atom(data);
60
+ #ifdef HAVE_ZLIB_H
61
+ case UEL_ETF_ZLIB_ID:
62
+ return uel_read_zlib(data);
63
+ #endif
64
+ default:
65
+ rb_raise(rb_eNotImpError, "Unable to process data type: %hu", *(data->buff - 1));
66
+ return Qnil;
67
+ }
68
+ }
69
+
70
+ inline void uel_check_bounds(struct uel_bert_data *data, size_t size) {
71
+ if(data->buff + size > data->end)
72
+ rb_raise(rb_eRangeError, "end of buffer while taking %li", size);
73
+ }
74
+
75
+ inline uint8_t uel_read8(struct uel_bert_data *data) {
76
+ uel_check_bounds(data, 1);
77
+ return *data->buff++;
78
+ }
79
+
80
+ inline uint16_t uel_read16(struct uel_bert_data *data) {
81
+ uint16_t value;
82
+
83
+ uel_check_bounds(data, 2);
84
+ value = *(uint16_t *)data->buff;
85
+ data->buff += sizeof(uint16_t);
86
+ return ntohs(value);
87
+ }
88
+
89
+ inline uint32_t uel_read32(struct uel_bert_data *data) {
90
+ uint32_t value;
91
+
92
+ uel_check_bounds(data, 4);
93
+ value = *(uint32_t *)data->buff;
94
+ data->buff += sizeof(uint32_t);
95
+ return ntohl(value);
96
+ }
97
+
98
+ inline VALUE uel_read_small_integer(struct uel_bert_data *data) {
99
+ return UINT2NUM((uint8_t) uel_read8(data));
100
+ }
101
+
102
+ inline VALUE uel_read_integer(struct uel_bert_data *data) {
103
+ return INT2NUM(uel_read32(data));
104
+ }
105
+
106
+ VALUE uel_read_float(struct uel_bert_data *data) {
107
+ double value;
108
+
109
+ uel_check_bounds(data, 31);
110
+ sscanf((char *)data->buff, "%31lf", &value);
111
+ data->buff += 31;
112
+ return DBL2NUM(value);
113
+ }
114
+
115
+ inline VALUE uel_read_nil(struct uel_bert_data *data) {
116
+ return rb_ary_new();
117
+ }
118
+
119
+ VALUE uel_read_small_bignum(struct uel_bert_data *data) {
120
+ uint8_t size;
121
+ uint8_t sign;
122
+
123
+ size = uel_read8(data);
124
+ sign = uel_read8(data);
125
+
126
+ uel_check_bounds(data, size);
127
+ data->buff += size;
128
+
129
+ return rb_integer_unpack(data->buff - size, size, 1, 0, INTEGER_PACK_LITTLE_ENDIAN | (sign * INTEGER_PACK_NEGATIVE));
130
+ }
131
+
132
+ VALUE uel_read_large_bignum(struct uel_bert_data *data) {
133
+ uint32_t size;
134
+ uint8_t sign;
135
+
136
+ size = uel_read32(data);
137
+ sign = uel_read8(data);
138
+
139
+ uel_check_bounds(data, size);
140
+ data->buff += size;
141
+ return rb_integer_unpack(data->buff - size, size, 1, 0, INTEGER_PACK_LITTLE_ENDIAN | (sign * INTEGER_PACK_NEGATIVE));
142
+ }
143
+
144
+ VALUE uel_read_new_float(struct uel_bert_data *data) {
145
+ DOUBLE_SWAPPER tmp;
146
+
147
+ memcpy(tmp.buff, data->buff, sizeof(double));
148
+ data->buff += sizeof(double);
149
+ tmp.u = be64toh(tmp.u);
150
+
151
+ return DBL2NUM(tmp.d);
152
+ }
153
+
154
+ inline void uel_read_n_terms(struct uel_bert_data *data, VALUE *values, uint32_t count) {
155
+ for(uint32_t index = 0; index < count; index++) {
156
+ values[index] = uel_decode_term(data);
157
+ }
158
+ }
159
+
160
+ VALUE uel_read_list(struct uel_bert_data *data) {
161
+ uint32_t length;
162
+ VALUE* values;
163
+
164
+ length = uel_read32(data);
165
+ // Extra value to account for Tail
166
+ values = (VALUE *)malloc(sizeof(VALUE) * (length + 1));
167
+
168
+ uel_read_n_terms(data, values, length);
169
+
170
+ uel_check_bounds(data, 1);
171
+ if (*data->buff == UEL_ETF_NIL_ID) {
172
+ data->buff += 1;
173
+ return rb_ary_new_from_values(length, values);
174
+ }
175
+ else {
176
+ values[length] = uel_decode_term(data);
177
+ return rb_ary_new_from_values(length + 1, values);
178
+ }
179
+
180
+ }
181
+
182
+ VALUE uel_read_map(struct uel_bert_data *data) {
183
+ uint32_t arity;
184
+ VALUE hash;
185
+ VALUE key;
186
+ VALUE value;
187
+
188
+ arity = uel_read32(data);
189
+ hash = rb_hash_new();
190
+ for (uint32_t index = 0; index < arity; index++) {
191
+ key = uel_decode_term(data);
192
+ value = uel_decode_term(data);
193
+ rb_hash_aset(hash, key, value);
194
+ }
195
+
196
+ return hash;
197
+ }
198
+
199
+ VALUE uel_read_binary(struct uel_bert_data *data) {
200
+ uint32_t length;
201
+
202
+ length = uel_read32(data);
203
+ uel_check_bounds(data, length);
204
+ data->buff += length;
205
+ return rb_str_new((const char *)data->buff - length, length);
206
+ }
207
+
208
+ VALUE uel_read_small_tuple(struct uel_bert_data *data) {
209
+ uint8_t length;
210
+ VALUE *values;
211
+
212
+ length = uel_read8(data);
213
+ values = (VALUE *)malloc(sizeof(VALUE) * length);
214
+
215
+ uel_read_n_terms(data, values, length);
216
+
217
+ return rb_ary_new_from_values(length, values);
218
+ }
219
+
220
+ VALUE uel_read_large_tuple(struct uel_bert_data *data) {
221
+ uint32_t length;
222
+ VALUE *values;
223
+
224
+ length = uel_read32(data);
225
+ values = (VALUE *)malloc(sizeof(VALUE) * length);
226
+
227
+ uel_read_n_terms(data, values, length);
228
+
229
+ return rb_ary_new_from_values(length, values);
230
+ }
231
+
232
+ VALUE uel_read_symbol(struct uel_bert_data *data, uint16_t length) {
233
+ VALUE string;
234
+
235
+ string = rb_enc_str_new((const char *)data->buff, length, rb_utf8_encoding());
236
+ data->buff += length;
237
+ return rb_to_symbol(string);
238
+ }
239
+
240
+ VALUE uel_read_atom(struct uel_bert_data *data) {
241
+ return uel_read_symbol(data, uel_read16(data));
242
+ }
243
+
244
+ VALUE uel_read_small_atom(struct uel_bert_data *data) {
245
+ return uel_read_symbol(data, uel_read8(data));
246
+ }
247
+
248
+ /*
249
+ TODO:
250
+ SMALL_TUPLE
251
+ LARGE_TUPLE
252
+ STRING
253
+ ATOM_SHIT
254
+ */
255
+
256
+ #if HAVE_ZLIB_H
257
+ VALUE uel_read_zlib(struct uel_bert_data *data) {
258
+ uint32_t inflate_length;
259
+ int inflate_status_code;
260
+ z_stream strm;
261
+ unsigned char *out;
262
+ struct uel_bert_data new_data;
263
+
264
+ inflate_length = uel_read32(data);
265
+ out = malloc(inflate_length * sizeof(uint8_t));
266
+
267
+ strm.zalloc = Z_NULL;
268
+ strm.zfree = Z_NULL;
269
+ strm.opaque = Z_NULL;
270
+ strm.avail_in = (uint32_t)UEL_DATA_LEN(data);
271
+ strm.next_in = data->buff;
272
+ strm.avail_out = inflate_length;
273
+ strm.next_out = out;
274
+
275
+ inflateInit(&strm);
276
+ inflate(&strm, Z_NO_FLUSH);
277
+ inflate_status_code = inflateEnd(&strm);
278
+
279
+ new_data.buff = out;
280
+ new_data.end = out + inflate_length;
281
+
282
+ if (inflate_status_code != Z_OK) {
283
+ rb_raise(rb_eTypeError, "Error inflating compressed data: (%i) %s", inflate_status_code, strm.msg);
284
+ }
285
+
286
+ return uel_decode_term(&new_data);
287
+ }
288
+ #endif
data/ext/uel/encode.c ADDED
@@ -0,0 +1,282 @@
1
+ #include <netinet/in.h>
2
+ #include <endian.h>
3
+ #include <ruby.h>
4
+ #include <ruby/encoding.h>
5
+ #include <string.h>
6
+ #include <stdlib.h>
7
+ #include "./extconf.h"
8
+ #include "./uel.h"
9
+
10
+ VALUE uel_encode(VALUE self, VALUE value) {
11
+ uint8_t *buff;
12
+ VALUE ret_val;
13
+ struct uel_bert_data *term;
14
+ uint32_t term_len;
15
+
16
+ term = uel_encode_term(value);
17
+ if (term == NULL) {
18
+ rb_raise(rb_eStandardError, "Error decoding term");
19
+ return Qnil;
20
+ }
21
+
22
+
23
+ term_len = UEL_DATA_LEN(term);
24
+ buff = malloc(sizeof(uint8_t) * (term_len + 1));
25
+ buff[0] = 131;
26
+ memcpy(&buff[1], term->buff, term_len);
27
+ ret_val = rb_str_new((const char *)buff, (term_len + 1));
28
+
29
+ free(buff);
30
+ uel_destroy_bert_data(term);
31
+
32
+ return ret_val;
33
+ }
34
+
35
+
36
+ struct uel_bert_data *uel_encode_term(VALUE value) {
37
+ switch (TYPE(value)) {
38
+ case T_FIXNUM:
39
+ if (FIX2LONG(value) <= 0xFFFFFFFF)
40
+ return uel_encode_fixnum(value);
41
+ else
42
+ return uel_encode_bignum(value);
43
+ case T_FLOAT:
44
+ return uel_encode_float(value);
45
+ case T_BIGNUM:
46
+ return uel_encode_bignum(value);
47
+ case T_NIL:
48
+ return uel_encode_ptr_small_atom("nil", 3);
49
+ case T_TRUE:
50
+ return uel_encode_ptr_small_atom("true", 4);
51
+ case T_FALSE:
52
+ return uel_encode_ptr_small_atom("false", 5);
53
+ case T_SYMBOL:
54
+ return uel_encode_symbol(value);
55
+ case T_ARRAY:
56
+ return uel_encode_array(value);
57
+ case T_HASH:
58
+ return uel_encode_hash(value);
59
+ case T_STRING:
60
+ return uel_encode_string(value);
61
+ default:
62
+ /* TODO: Include class name
63
+ rb_raise(rb_eNotImpError, "Type not implemented %s", rb_class2name(RBASIC_CLASS(value)));
64
+ */
65
+ return NULL;
66
+ }
67
+ }
68
+
69
+ inline void uel_destroy_bert_data(struct uel_bert_data* data) {
70
+ free(data->buff);
71
+ free(data);
72
+ }
73
+
74
+ struct uel_bert_data *uel_encode_fixnum(VALUE fixnum) {
75
+ long l;
76
+ struct uel_bert_data *data = NULL;
77
+
78
+ l = FIX2LONG(fixnum);
79
+ if (l <= 0xFF) {
80
+ data = malloc(sizeof(struct uel_bert_data));
81
+ data->buff = malloc(sizeof(uint8_t) * 2);
82
+ data->buff[0] = UEL_ETF_SMALL_INTEGER_ID;
83
+ data->buff[1] = (uint8_t)l;
84
+ UEL_DATA_SET_LEN(data, 2);
85
+ } else {
86
+ data = malloc(sizeof(struct uel_bert_data));
87
+ data->buff = malloc(sizeof(uint8_t) * 5);
88
+ data->buff[0] = UEL_ETF_INTEGER_ID;
89
+
90
+ UEL_SET_INT32(data->buff + 1, l);
91
+ UEL_DATA_SET_LEN(data, 5);
92
+ }
93
+
94
+ return data;
95
+ }
96
+
97
+ struct uel_bert_data *uel_encode_float(VALUE value) {
98
+ struct uel_bert_data *data = malloc(sizeof(struct uel_bert_data));
99
+ DOUBLE_SWAPPER swapper;
100
+
101
+ data->buff = (uint8_t *)malloc(sizeof(uint8_t) * 9);
102
+ UEL_DATA_SET_LEN(data, 9);
103
+
104
+ swapper.d = NUM2DBL(value);
105
+ swapper.u = htobe64(swapper.u);
106
+
107
+ *data->buff = UEL_ETF_NEW_FLOAT_ID;
108
+ memcpy(data->buff + 1, swapper.buff, sizeof(double));
109
+
110
+ return data;
111
+ }
112
+
113
+ struct uel_bert_data *uel_encode_bignum(VALUE value) {
114
+ size_t byte_count;
115
+ struct uel_bert_data *data = NULL;
116
+
117
+ data = malloc(sizeof(struct uel_bert_data));
118
+ byte_count = rb_absint_size(value, NULL);
119
+ if (byte_count <= 0xFF) {
120
+ // id byte | n byte | sign byte | data
121
+ data->buff = malloc(sizeof(uint8_t) * (3 + byte_count));
122
+ data->buff[0] = UEL_ETF_SMALL_BIGNUM_ID;
123
+ data->buff[1] = (uint8_t)byte_count;
124
+ data->buff[2] = FIX2LONG(value) >= 0 ? 0 : 1;
125
+ UEL_DATA_SET_LEN(data, 3 + byte_count);
126
+
127
+ rb_integer_pack(value, data->buff + 3, byte_count, sizeof(uint8_t), 0, INTEGER_PACK_LITTLE_ENDIAN);
128
+ }
129
+ else {
130
+ // id byte | 4 byte n | sign byte | data
131
+ data->buff = malloc(sizeof(uint8_t) * (2 + byte_count) + sizeof(uint32_t));
132
+ data->buff[0] = UEL_ETF_LARGE_BIGNUM_ID;
133
+ UEL_SET_INT32(data->buff + 1, byte_count);
134
+ data->buff[5] = RBIGNUM_SIGN(value) ? 0 : 1;
135
+ UEL_DATA_SET_LEN(data, 6 + byte_count);
136
+
137
+ rb_integer_pack(value, data->buff + 6, byte_count, sizeof(uint8_t), 0, INTEGER_PACK_LITTLE_ENDIAN);
138
+ }
139
+ return data;
140
+ }
141
+
142
+ struct uel_bert_data *uel_encode_ptr_atom(const char *name, uint16_t len) {
143
+ struct uel_bert_data *data = malloc(sizeof(struct uel_bert_data));
144
+
145
+ data->buff = malloc(sizeof(uint8_t) * (len + 3));
146
+ data->buff[0] = UEL_ETF_ATOM_UTF8_ID;
147
+ UEL_SET_UINT16(data->buff + 1, len);
148
+ memcpy(data->buff + 3, name, len);
149
+ UEL_DATA_SET_LEN(data, 3 + len);
150
+
151
+ return data;
152
+ }
153
+
154
+ struct uel_bert_data *uel_encode_ptr_small_atom(const char *name, uint8_t len) {
155
+ struct uel_bert_data *data = malloc(sizeof(struct uel_bert_data));
156
+
157
+ data->buff = malloc(sizeof(uint8_t) * (len + 2));
158
+ data->buff[0] = UEL_ETF_SMALL_ATOM_UTF8_ID;
159
+ data->buff[1] = len;
160
+ memcpy(data->buff + 2, name, len);
161
+ UEL_DATA_SET_LEN(data, 2 + len);
162
+
163
+ return data;
164
+ }
165
+
166
+ struct uel_bert_data *uel_encode_symbol(VALUE value) {
167
+ const char *name;
168
+ uint16_t len;
169
+
170
+ name = rb_id2name(rb_to_id(value));
171
+ len = strlen(name);
172
+
173
+ if (len > 0xFF)
174
+ return uel_encode_ptr_atom(name, len);
175
+ else
176
+ return uel_encode_ptr_small_atom(name, len);
177
+ }
178
+
179
+ struct uel_bert_data *uel_encode_array(VALUE value) {
180
+ uint32_t data_size = 0;
181
+ // Account for id tag and length
182
+ uint32_t offset = 5;
183
+ uint32_t elem_count = RARRAY_LEN(value);
184
+ struct uel_bert_data **data_ary = malloc(sizeof(struct uel_bert_data *) * elem_count);
185
+ struct uel_bert_data *element;
186
+ struct uel_bert_data *ret_data;
187
+
188
+ for (uint32_t i = 0; i < elem_count; i++) {
189
+ element = uel_encode_term(rb_ary_entry(value, i));
190
+ if (element == NULL) {
191
+ for (uint32_t j = 0; j <= i; j++)
192
+ uel_destroy_bert_data(data_ary[j]);
193
+ free(data_ary);
194
+ return NULL;
195
+ }
196
+ data_size += UEL_DATA_LEN(element);
197
+ data_ary[i] = element;
198
+ }
199
+
200
+ ret_data = malloc(sizeof(struct uel_bert_data));
201
+ ret_data->buff = malloc(sizeof(uint8_t) * (data_size + 6));
202
+ ret_data->buff[0] = UEL_ETF_LIST_ID;
203
+ UEL_SET_UINT32(ret_data->buff + 1, elem_count);
204
+ UEL_DATA_SET_LEN(ret_data, data_size + 6);
205
+
206
+ for (uint32_t i = 0; i < elem_count; i++) {
207
+ memcpy(ret_data->buff + offset, data_ary[i]->buff, UEL_DATA_LEN(data_ary[i]));
208
+ offset += UEL_DATA_LEN(data_ary[i]);
209
+ uel_destroy_bert_data(data_ary[i]);
210
+ }
211
+ ret_data->buff[offset] = UEL_ETF_NIL_ID;
212
+ free(data_ary);
213
+
214
+ return ret_data;
215
+ }
216
+
217
+ struct uel_bert_data *uel_encode_string(VALUE value) {
218
+ struct uel_bert_data *data;
219
+ size_t len = RSTRING_LEN(value);
220
+
221
+ data = malloc(sizeof(struct uel_bert_data));
222
+ data->buff = malloc(sizeof(uint8_t) * (len + 5));
223
+ UEL_DATA_SET_LEN(data, len + 5);
224
+
225
+ data->buff[0] = UEL_ETF_BINARY_ID;
226
+ UEL_SET_UINT32(data->buff + 1, len);
227
+ memcpy(data->buff + 5, StringValuePtr(value), len);
228
+
229
+ return data;
230
+ }
231
+
232
+ struct uel_bert_data *uel_encode_hash(VALUE value) {
233
+ struct uel_bert_data *data;
234
+ struct uel_bert_data **data_ary;
235
+ uint32_t hash_size;
236
+ int32_t data_index = 0;
237
+ uint32_t key_index = 0;
238
+ uint32_t data_size = 0;
239
+ uint32_t offset;
240
+ VALUE keys;
241
+ VALUE key;
242
+
243
+ hash_size = rb_hash_size_num(value);
244
+ data = malloc(sizeof(struct uel_bert_data));
245
+ data_ary = malloc(sizeof(struct uel_bert_data *) * (hash_size * 2));
246
+ keys = rb_hash_keys(value);
247
+
248
+ for (; key_index < hash_size; key_index++) {
249
+ key = rb_ary_entry(keys, key_index);
250
+ data_ary[data_index++] = uel_encode_term(key);
251
+ data_ary[data_index++] = uel_encode_term(rb_hash_lookup(value, key));
252
+
253
+ if (data_ary[data_index - 1] == NULL || data_ary[data_index - 2] == NULL) {
254
+ while(--data_index >= 0) {
255
+ if (data_ary[data_index] != NULL)
256
+ uel_destroy_bert_data(data_ary[data_index]);
257
+ }
258
+ free(data);
259
+ free(data_ary);
260
+ return NULL;
261
+ }
262
+
263
+ data_size += UEL_DATA_LEN(data_ary[data_index - 1]);
264
+ data_size += UEL_DATA_LEN(data_ary[data_index - 2]);
265
+ }
266
+
267
+ data->buff = (uint8_t *)malloc(sizeof(uint8_t) * (5 + data_size));
268
+ data->buff[0] = UEL_ETF_MAP_ID;
269
+ UEL_SET_UINT32(data->buff + 1, hash_size);
270
+ UEL_DATA_SET_LEN(data, 5 + data_size);
271
+
272
+ offset = 0;
273
+ for (int index = 0; index < data_index; index++) {
274
+ memcpy(data->buff + 5 + offset, data_ary[index]->buff, UEL_DATA_LEN(data_ary[index]));
275
+ offset += UEL_DATA_LEN(data_ary[index]);
276
+ uel_destroy_bert_data(data_ary[index]);
277
+ }
278
+
279
+ free(data_ary);
280
+
281
+ return data;
282
+ }
@@ -0,0 +1,14 @@
1
+ require 'mkmf'
2
+
3
+ extension_name = 'uel'
4
+ dir_config(extension_name)
5
+ find_header('ruby.h')
6
+ find_header('endian.h')
7
+ find_header('stdlib.h')
8
+
9
+ if have_library('z')
10
+ have_header('zlib.h')
11
+ end
12
+
13
+ create_header
14
+ create_makefile(extension_name)
data/ext/uel/uel.c ADDED
@@ -0,0 +1,19 @@
1
+ #include <netinet/in.h>
2
+ #include <endian.h>
3
+ #include <ruby.h>
4
+ #include <ruby/encoding.h>
5
+ #include <string.h>
6
+ #include <stdlib.h>
7
+ #include "./extconf.h"
8
+ #include "./uel.h"
9
+
10
+
11
+
12
+ VALUE UELModule = Qnil;
13
+
14
+ void Init_uel() {
15
+ UELModule = rb_define_module("UEL");
16
+ rb_define_module_function(UELModule, "decode", uel_decode, 1);
17
+ rb_define_module_function(UELModule, "encode", uel_encode, 1);
18
+ }
19
+
data/ext/uel/uel.h ADDED
@@ -0,0 +1,110 @@
1
+ #ifndef UNTITLED_ETF_LIB_H
2
+ #define UNTITLED_ETF_LIB_H
3
+
4
+ #define UEL_ETF_VERSION_NUMBER 131
5
+
6
+ /* Supported Terms */
7
+ #define UEL_ETF_NEW_FLOAT_ID 70
8
+ #define UEL_ETF_BIT_BINARY_ID 77
9
+ #define UEL_ETF_ZLIB_ID 80
10
+ #define UEL_ETF_SMALL_INTEGER_ID 97
11
+ #define UEL_ETF_INTEGER_ID 98
12
+ #define UEL_ETF_FLOAT_ID 99
13
+ #define UEL_ETF_ATOM_ID 100
14
+ #define UEL_ETF_SMALL_TUPLE_ID 104
15
+ #define UEL_ETF_LARGE_TUPLE_ID 105
16
+ #define UEL_ETF_NIL_ID 106
17
+ #define UEL_ETF_STRING_ID 107
18
+ #define UEL_ETF_LIST_ID 108
19
+ #define UEL_ETF_BINARY_ID 109
20
+ #define UEL_ETF_SMALL_BIGNUM_ID 110
21
+ #define UEL_ETF_LARGE_BIGNUM_ID 111
22
+ #define UEL_ETF_SMALL_ATOM_ID 115
23
+ #define UEL_ETF_MAP_ID 116
24
+ #define UEL_ETF_ATOM_UTF8_ID 118
25
+ #define UEL_ETF_SMALL_ATOM_UTF8_ID 119
26
+
27
+
28
+ /* Unsupported terms */
29
+ #define ETF_ATOM_CACHE_REF_ID 82
30
+ #define ETF_NEW_PID_ID 88
31
+ #define ETF_NEW_PORT_ID 89
32
+ #define ETF_NEWER_REFERENCE_ID 90
33
+ #define ETF_REFERENCE_ID 101
34
+ #define ETF_PORT_ID 102
35
+ #define ETF_PID_ID 103
36
+ #define ETF_NEW_FUN_ID 112
37
+ #define ETF_EXPORT_ID 113
38
+ #define ETF_NEW_REFERENCE_ID 114
39
+ #define ETF_FUN_ID 117
40
+
41
+
42
+ struct uel_bert_data {
43
+ uint8_t *buff;
44
+ uint8_t *end;
45
+ uint32_t length;
46
+ };
47
+
48
+ #define UEL_DATA_LEN(etf_data) (etf_data->end - etf_data->buff)
49
+ #define UEL_DATA_SET_LEN(etf_data, len) (etf_data->end = etf_data->buff + (len))
50
+ #define UEL_SET_CAST(type, data, value) *((type *)(data)) = (type)value;
51
+ #define UEL_SET_INT32(data, value) UEL_SET_CAST(int32_t, (data), htobe32(value))
52
+ #define UEL_SET_INT16(data, value) UEL_SET_CAST(int16_t, (data), htobe16(value))
53
+ #define UEL_SET_UINT32(data, value) UEL_SET_CAST(uint32_t, (data), htobe32(value))
54
+ #define UEL_SET_UINT16(data, value) UEL_SET_CAST(uint16_t, (data), htobe16(value))
55
+
56
+
57
+ typedef union {
58
+ double d;
59
+ uint64_t u;
60
+ char buff[8];
61
+ } DOUBLE_SWAPPER;
62
+
63
+ void Init_uel();
64
+
65
+ void uel_destroy_bert_data(struct uel_bert_data*data);
66
+
67
+ /* decode */
68
+ VALUE uel_decode(VALUE self, VALUE data);
69
+ VALUE uel_decode_term(struct uel_bert_data *data);
70
+ void uel_check_bounds(struct uel_bert_data *data, size_t size);
71
+ uint8_t uel_read8(struct uel_bert_data *data);
72
+ uint16_t uel_read16(struct uel_bert_data *data);
73
+ uint32_t uel_read32(struct uel_bert_data *data);
74
+ VALUE uel_read_small_integer(struct uel_bert_data *data);
75
+ VALUE uel_read_integer(struct uel_bert_data *data);
76
+ VALUE uel_read_float(struct uel_bert_data *data);
77
+ VALUE uel_read_nil(struct uel_bert_data *data);
78
+ VALUE uel_read_small_bignum(struct uel_bert_data *data);
79
+ VALUE uel_read_large_bignum(struct uel_bert_data *data);
80
+ VALUE uel_read_new_float(struct uel_bert_data *data);
81
+ VALUE uel_read_list(struct uel_bert_data *data);
82
+ VALUE uel_read_map(struct uel_bert_data *data);
83
+ VALUE uel_read_binary(struct uel_bert_data *data);
84
+ VALUE uel_read_zlib(struct uel_bert_data *data);
85
+ VALUE uel_read_small_tuple(struct uel_bert_data *data);
86
+ VALUE uel_read_large_tuple(struct uel_bert_data *data);
87
+ VALUE uel_read_atom(struct uel_bert_data *data);
88
+ VALUE uel_read_small_atom(struct uel_bert_data *data);
89
+ VALUE uel_read_atom_utf8(struct uel_bert_data *data);
90
+ VALUE uel_read_small_atom_utf8(struct uel_bert_data *data);
91
+
92
+ /* encode */
93
+ VALUE uel_encode(VALUE self, VALUE value);
94
+ struct uel_bert_data *uel_encode_term(VALUE value);
95
+ struct uel_bert_data *uel_encode_fixnum(VALUE value);
96
+ struct uel_bert_data *uel_encode_float(VALUE value);
97
+ struct uel_bert_data *uel_encode_bignum(VALUE value);
98
+ struct uel_bert_data *uel_encode_array(VALUE value);
99
+ struct uel_bert_data *uel_encode_hash(VALUE value);
100
+ struct uel_bert_data *uel_encode_struct(VALUE value);
101
+ struct uel_bert_data *uel_encode_array(VALUE value);
102
+ struct uel_bert_data *uel_encode_symbol(VALUE value);
103
+ struct uel_bert_data *uel_encode_string(VALUE value);
104
+ struct uel_bert_data *uel_encode_ptr_atom(const char *name, uint16_t len);
105
+ struct uel_bert_data *uel_encode_ptr_small_atom(const char *name, uint8_t len);
106
+
107
+ /* ruby symbols */
108
+ VALUE rb_hash_keys(VALUE hash);
109
+
110
+ #endif
data/uel.gemspec ADDED
@@ -0,0 +1,29 @@
1
+
2
+ Gem::Specification.new do |spec|
3
+ spec.name = "UEL"
4
+ spec.version = "0.1.0"
5
+ spec.authors = ["Matthew Carey"]
6
+ spec.email = ["matthew.b.carey@gmail.com"]
7
+
8
+ spec.summary = %q{untitled etf lib}
9
+ spec.description = %q{untitled etf lib}
10
+ spec.homepage = "https://github.com/swarley/uel"
11
+ spec.license = "MIT"
12
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
+
14
+ spec.metadata["homepage_uri"] = spec.homepage
15
+ spec.metadata["source_code_uri"] = spec.homepage
16
+ spec.metadata["changelog_uri"] = spec.homepage
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+ spec.extensions << "ext/uel/extconf.rb"
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_development_dependency "rake-compiler"
29
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: UEL
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Carey
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-05-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake-compiler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: untitled etf lib
28
+ email:
29
+ - matthew.b.carey@gmail.com
30
+ executables: []
31
+ extensions:
32
+ - ext/uel/extconf.rb
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".gitignore"
36
+ - ".rspec"
37
+ - ".travis.yml"
38
+ - CODE_OF_CONDUCT.md
39
+ - Gemfile
40
+ - LICENSE.txt
41
+ - README.md
42
+ - Rakefile
43
+ - benchmark.rb
44
+ - ext/uel/decode.c
45
+ - ext/uel/encode.c
46
+ - ext/uel/extconf.rb
47
+ - ext/uel/uel.c
48
+ - ext/uel/uel.h
49
+ - uel.gemspec
50
+ homepage: https://github.com/swarley/uel
51
+ licenses:
52
+ - MIT
53
+ metadata:
54
+ homepage_uri: https://github.com/swarley/uel
55
+ source_code_uri: https://github.com/swarley/uel
56
+ changelog_uri: https://github.com/swarley/uel
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: 2.3.0
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubygems_version: 3.0.3
73
+ signing_key:
74
+ specification_version: 4
75
+ summary: untitled etf lib
76
+ test_files: []