canoser 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: 605cb23e469f4b87cd405b7c68232bbf4370efb17a3f9ee08f5835ede36b2e2d
4
+ data.tar.gz: dc1d93b906f7d3d56346038c671e756d0e10e7887b6ca8c6f688659d11fa0e84
5
+ SHA512:
6
+ metadata.gz: c9d5d29d35b7682ac3932f3320c176b2230a427ec154a1107826820daf994d77c241bdac1e82786540b9f8e470e6c52276e727b273e2bffcafdc147afd4dac61
7
+ data.tar.gz: fd1d4aa6c2bc971dc2502b1bf264cc7065f1c703e637696c0865e494aa91f84edfee21d12daf72c9aad4c0d281024a259ce087b899df56ec3fcf658dd17940dc
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .byebug_history
10
+
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.3.1
7
+ before_install: gem install bundler -v 2.0.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in canoser.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ canoser (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ byebug (11.0.0)
10
+ minitest (5.11.3)
11
+ rake (10.5.0)
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ bundler (~> 2.0)
18
+ byebug (~> 11.0)
19
+ canoser!
20
+ minitest (~> 5.0)
21
+ rake (~> 10.0)
22
+
23
+ BUNDLED WITH
24
+ 2.0.1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 yuan xinyu
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-CN.md ADDED
@@ -0,0 +1,147 @@
1
+ # Canoser
2
+
3
+
4
+ Canoser是facebook推出的Libra网络中使用的规范序列化(canonical serialization)协议的第三方ruby实现框架。
5
+
6
+ 规范序列化可确保内存里的数据结构在序列化的时候保证字节一致性。它适用于双方想要有效比较他们独立维护的数据结构。在共识协议中,独立验证者需要就他们独立计算的状态达成一致。共识双方比较的是序列化数据的加密散列。要实现这一点,在计算时,相同数据结构的序列化必须相同。而独立验证器可能由不同的语言编写,有不同的实现代码,但是都遵循同一个规范。
7
+
8
+
9
+ ## 安装
10
+
11
+ 添加下列行到你的项目的Gemfile文件:
12
+
13
+ ```ruby
14
+ gem 'canoser'
15
+ ```
16
+
17
+ 然后执行:
18
+
19
+ $ bundle
20
+
21
+ 或者直接通过命令行安装:
22
+
23
+ $ gem install canoser
24
+
25
+ ## 使用
26
+
27
+ 首先用Canoser定义一个数据结构,也就是写一个类继承自"Canoser::Struct",然后通过"define_field"方法来定义该结构所拥有的字段。该结构自然就拥有了序列化和反序列化的能力。例如下面的AccountResource定义了一个Libra代码中的同名数据结构:
28
+ ```ruby
29
+ #ruby代码,利用canoser定义数据结构
30
+ class AccountResource < Canoser::Struct
31
+ define_field :authentication_key, [Canoser::Uint8]
32
+ define_field :balance, Canoser::Uint64
33
+ define_field :delegated_withdrawal_capability, Canoser::Bool
34
+ define_field :received_events_count, Canoser::Uint64
35
+ define_field :sent_events_count, Canoser::Uint64
36
+ define_field :sequence_number, Canoser::Uint64
37
+ end
38
+ ```
39
+
40
+ 下面是Libra中定义该数据结构以及序列化的代码:
41
+ ```rust
42
+ // Libra中的rust语言代码
43
+ // 定义数据结构
44
+ pub struct AccountResource {
45
+ balance: u64,
46
+ sequence_number: u64,
47
+ authentication_key: ByteArray,
48
+ sent_events_count: u64,
49
+ received_events_count: u64,
50
+ delegated_withdrawal_capability: bool,
51
+ }
52
+ // 实现序列化
53
+ impl CanonicalSerialize for AccountResource {
54
+ fn serialize(&self, serializer: &mut impl CanonicalSerializer) -> Result<()> {
55
+ serializer
56
+ .encode_struct(&self.authentication_key)?
57
+ .encode_u64(self.balance)?
58
+ .encode_bool(self.delegated_withdrawal_capability)?
59
+ .encode_u64(self.received_events_count)?
60
+ .encode_u64(self.sent_events_count)?
61
+ .encode_u64(self.sequence_number)?;
62
+ Ok(())
63
+ }
64
+ }
65
+ ```
66
+ 在Libra使用的rust语言中,需要手动写代码实现数据结构的序列化/反序列化,而且数据结构中的字段顺序和序列化时的顺序不一定一致。
67
+ 在Canoser中,定义好数据结构后,不需要写序列化和反序列化的代码。注意,Canoser中的数据结构顺序要按照Libra中序列化的顺序来定义。
68
+
69
+ ### 支持的数据类型
70
+
71
+ 字段支持的类型有:
72
+
73
+ | 字段类型 | 可选子类型 | 说明 |
74
+ | ------ | ------ | ------ |
75
+ | Canoser::Uint8 | | 无符号8位整数 |
76
+ | Canoser::Uint16 | | 无符号16位整数 |
77
+ | Canoser::Uint32 | | 无符号32位整数 |
78
+ | Canoser::Uint64 | | 无符号64位整数 |
79
+ | Canoser::Bool | | 布尔类型 |
80
+ | Canoser::Str | | 字符串 |
81
+ | [] | 支持 | 数组类型 |
82
+ | {} | 支持 | Map类型 |
83
+ | A Canoser::Struct Name | | 嵌套的另外一个结构(不能循环引用) |
84
+
85
+ ### 关于数组类型
86
+ 数组里的数据,如果没有定义类型,那么缺省是Uint8。下面的两个定义等价:
87
+ ```ruby
88
+ class Arr1 < Canoser::Struct
89
+ define_field :addr, []
90
+ end
91
+ class Arr2 < Canoser::Struct
92
+ define_field :addr, [Canoser::Uint8]
93
+ end
94
+ ```
95
+ 数组还可以定义长度,表示定长数据。比如Libra中的地址是256位,也就是32个字节,所以可以如下定义:
96
+ ```ruby
97
+ class Address < Canoser::Struct
98
+ define_field :addr, [Canoser::Uint8], 32
99
+ end
100
+ ```
101
+ 定长数据在序列化的时候,不写入长度信息。
102
+
103
+ ### 关于Map类型
104
+ Map里的数据,如果没有定义类型,那么缺省是字节数组。下面的两个定义等价:
105
+ ```ruby
106
+ class Map1 < Canoser::Struct
107
+ define_field :addr, {}
108
+ end
109
+ class Map2 < Canoser::Struct
110
+ define_field :addr, {[Canoser::Uint8], [Canoser::Uint8]}
111
+ end
112
+ ```
113
+
114
+ ### 结构嵌套
115
+ 下面是一个复杂的例子,包含三个数据结构:
116
+ ```ruby
117
+ class Addr < Canoser::Struct
118
+ define_field :addr, [Canoser::Uint8], 32
119
+ end
120
+
121
+ class Bar < Canoser::Struct
122
+ define_field :a, Canoser::Uint64
123
+ define_field :b, [Canoser::Uint8]
124
+ define_field :c, Addr
125
+ define_field :d, Canoser::Uint32
126
+ end
127
+
128
+ class Foo < Canoser::Struct
129
+ define_field :a, Canoser::Uint64
130
+ define_field :b, [Canoser::Uint8]
131
+ define_field :c, Bar
132
+ define_field :d, Canoser::Bool
133
+ define_field :e, {}
134
+ end
135
+ ```
136
+ 这个例子参考自libra中canonical serialization的测试代码。
137
+
138
+ ### 序列化和反序列化
139
+ 在定义好Canoser::Struct后,不需要自己实现序列化和反序列化代码,直接调用基类的默认实现即可。以AccountResource结构为例:
140
+ ```ruby
141
+ #序列化
142
+ obj = AccountResource.new(authentication_key:[...],...)
143
+ bytes = obj.serialize
144
+ #反序列化
145
+ obj = AccountResource.deserialize(bytes)
146
+ ```
147
+
data/README.md ADDED
@@ -0,0 +1,170 @@
1
+ # Canoser
2
+
3
+ [中文文档 Chinese document](/README-CN.md)
4
+
5
+ A ruby implementation of the canonical serialization for the Libra network.
6
+
7
+ Canonical serialization guarantees byte consistency when serializing an in-memory
8
+ data structure. It is useful for situations where two parties want to efficiently compare
9
+ data structures they independently maintain. It happens in consensus where
10
+ independent validators need to agree on the state they independently compute. A cryptographic
11
+ hash of the serialized data structure is what ultimately gets compared. In order for
12
+ this to work, the serialization of the same data structures must be identical when computed
13
+ by independent validators potentially running different implementations
14
+ of the same spec in different languages.
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem 'canoser'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install canoser
31
+
32
+ ## Usage
33
+
34
+ First define a data structure with Canoser, that is, write a class that inherits from "Canoser::Struct", and then define the fields owned by the structure through the "define_field" method. This structure naturally has the ability to canonical serialize and deserialize types. For example, the following AccountResource defines a data structure of the same name in the Libra code:
35
+ ```ruby
36
+ #ruby code,define canoser data structure
37
+ class AccountResource < Canoser::Struct
38
+ define_field :authentication_key, [Canoser::Uint8]
39
+ define_field :balance, Canoser::Uint64
40
+ define_field :delegated_withdrawal_capability, Canoser::Bool
41
+ define_field :received_events_count, Canoser::Uint64
42
+ define_field :sent_events_count, Canoser::Uint64
43
+ define_field :sequence_number, Canoser::Uint64
44
+ end
45
+ ```
46
+
47
+ Here is the code that defines this data structure and serialization in Libra code:
48
+ ```rust
49
+ // rust code in Libra
50
+ // define the data structure
51
+ pub struct AccountResource {
52
+ balance: u64,
53
+ sequence_number: u64,
54
+ authentication_key: ByteArray,
55
+ sent_events_count: u64,
56
+ received_events_count: u64,
57
+ delegated_withdrawal_capability: bool,
58
+ }
59
+ // serialization
60
+ impl CanonicalSerialize for AccountResource {
61
+ fn serialize(&self, serializer: &mut impl CanonicalSerializer) -> Result<()> {
62
+ serializer
63
+ .encode_struct(&self.authentication_key)?
64
+ .encode_u64(self.balance)?
65
+ .encode_bool(self.delegated_withdrawal_capability)?
66
+ .encode_u64(self.received_events_count)?
67
+ .encode_u64(self.sent_events_count)?
68
+ .encode_u64(self.sequence_number)?;
69
+ Ok(())
70
+ }
71
+ }
72
+ ```
73
+ In the rust language used by Libra, it is necessary to manually write code to serialize/deserialize the data structure, and the order of the fields in the data structure and the order of serialization are not necessarily the same.
74
+
75
+ In Canoser, after defining the data structure, you don't need to write code to implement serialization and deserialization. Note that the order of the data structures in Canoser is defined in the order in which they are serialized in Libra.
76
+
77
+ ### Supported field types
78
+
79
+ | field type | optionl sub type | description |
80
+ | ------ | ------ | ------ |
81
+ | Canoser::Uint8 | | Unsigned 8-bit integer |
82
+ | Canoser::Uint16 | | Unsigned 16-bit integer|
83
+ | Canoser::Uint32 | | Unsigned 32-bit integer |
84
+ | Canoser::Uint64 | | Unsigned 64-bit integer |
85
+ | Canoser::Bool | | Boolean |
86
+ | Canoser::Str | | String |
87
+ | [] | supported | Array Type |
88
+ | {} | supported | Map Type |
89
+ | A Canoser::Struct Name| | Another structure nested (cannot be recycled) |
90
+
91
+ ### About Array Type
92
+ The default data type (if not defined) in the array is Uint8. The following two definitions are equivalent:
93
+ ```ruby
94
+ class Arr1 < Canoser::Struct
95
+ define_field :addr, []
96
+ end
97
+ class Arr2 < Canoser::Struct
98
+ define_field :addr, [Canoser::Uint8]
99
+ end
100
+ ```
101
+ Arrays can also define lengths to represent fixed length data. For example, the address in Libra is 256 bits, which is 32 bytes, so it can be defined as follows:
102
+ ```ruby
103
+ class Address < Canoser::Struct
104
+ define_field :addr, [Canoser::Uint8], 32
105
+ end
106
+ ```
107
+ When the fixed length data is serialized, the length information is not written to the output.
108
+
109
+
110
+ ### About map type
111
+ The default data type (if not defined) in the map is an array of Uint8. The following two definitions are equivalent:
112
+ ```ruby
113
+ class Map1 < Canoser::Struct
114
+ define_field :addr, {}
115
+ end
116
+ class Map2 < Canoser::Struct
117
+ define_field :addr, {[Canoser::Uint8], [Canoser::Uint8]}
118
+ end
119
+ ```
120
+
121
+ ### Nested data structure
122
+ The following is a complex example with three data structures:
123
+ ```ruby
124
+ class Addr < Canoser::Struct
125
+ define_field :addr, [Canoser::Uint8], 32
126
+ end
127
+
128
+ class Bar < Canoser::Struct
129
+ define_field :a, Canoser::Uint64
130
+ define_field :b, [Canoser::Uint8]
131
+ define_field :c, Addr
132
+ define_field :d, Canoser::Uint32
133
+ end
134
+
135
+ class Foo < Canoser::Struct
136
+ define_field :a, Canoser::Uint64
137
+ define_field :b, [Canoser::Uint8]
138
+ define_field :c, Bar
139
+ define_field :d, Canoser::Bool
140
+ define_field :e, {}
141
+ end
142
+ ```
143
+ This example refers to the test code from canonical serialization in libra.
144
+
145
+ ### Serialization and deserialization
146
+ After defining Canoser::Struct, you don't need to implement serialization and deserialization code yourself, you can directly call the default implementation of the base class. Take the AccountResource structure as an example:
147
+
148
+ ```ruby
149
+ #序列化
150
+ obj = AccountResource.new(authentication_key:[...],...)
151
+ bytes = obj.serialize
152
+ #反序列化
153
+ obj = AccountResource.deserialize(bytes)
154
+ ```
155
+
156
+
157
+ ## Development
158
+
159
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
160
+
161
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
162
+
163
+ ## Contributing
164
+
165
+ Bug reports and pull requests are welcome on GitHub at https://github.com/yuanxinyu/canoser. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
166
+
167
+ ## License
168
+
169
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
170
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "canoser"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
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
7
+
8
+ # Do any other automated setup that you need to do here
data/canoser.gemspec ADDED
@@ -0,0 +1,44 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "canoser/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "canoser"
8
+ spec.version = Canoser::VERSION
9
+ spec.authors = ["yuan xinyu"]
10
+ spec.email = ["yuanxinyu.hangzhou@gmail.com"]
11
+
12
+ spec.summary = %q{A ruby implementation of the canonical serialization for the Libra network.}
13
+ spec.description = %q{A ruby implementation of the canonical serialization for the Libra network.}
14
+ spec.homepage = "https://github.com/yuanxinyu/canoser.git"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
21
+
22
+ spec.metadata["homepage_uri"] = spec.homepage
23
+ spec.metadata["source_code_uri"] = "https://github.com/yuanxinyu/canoser.git"
24
+ spec.metadata["changelog_uri"] = "https://github.com/yuanxinyu/canoser.git"
25
+ else
26
+ raise "RubyGems 2.0 or newer is required to protect against " \
27
+ "public gem pushes."
28
+ end
29
+
30
+ # Specify which files should be added to the gem when it is released.
31
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
32
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
33
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
34
+ end
35
+ spec.bindir = "exe"
36
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
37
+ spec.require_paths = ["lib"]
38
+
39
+ spec.add_development_dependency "bundler", "~> 2.0"
40
+ spec.add_development_dependency "rake", "~> 10.0"
41
+ spec.add_development_dependency "minitest", "~> 5.0"
42
+ spec.add_development_dependency "byebug", "~> 11.0"
43
+
44
+ end
data/lib/canoser.rb ADDED
@@ -0,0 +1,46 @@
1
+ # What is canonical serialization?
2
+ #
3
+ # Canonical serialization guarantees byte consistency when serializing an in-memory
4
+ # data structure. It is useful for situations where two parties want to efficiently compare
5
+ # data structures they independently maintain. It happens in consensus where
6
+ # independent validators need to agree on the state they independently compute. A cryptographic
7
+ # hash of the serialized data structure is what ultimately gets compared. In order for
8
+ # this to work, the serialization of the same data structures must be identical when computed
9
+ # by independent validators potentially running different implementations
10
+ # of the same spec in different languages.
11
+ #
12
+ # One design
13
+ # goal of this serialization format is to optimize for simplicity. It is not designed to be
14
+ # another full-fledged network serialization as Protobuf or Thrift. It is designed
15
+ # for doing only one thing right, which is to deterministically generate consistent bytes
16
+ # from a data structure.
17
+ #
18
+ # An extremely simple implementation of CanonicalSerializer is also provided, the encoding
19
+ # rules are:
20
+ # (All unsigned integers are encoded in little-endian representation unless specified otherwise)
21
+ #
22
+ # 1. The encoding of an unsigned 64-bit integer is defined as its little-endian representation
23
+ # in 8 bytes
24
+ #
25
+ # 2. The encoding of an item (byte array) is defined as:
26
+ # [length in bytes, represented as 4-byte integer] || [item in bytes]
27
+ #
28
+ #
29
+ # 3. The encoding of a list of items is defined as: (This is not implemented yet because
30
+ # there is no known struct that needs it yet, but can be added later easily)
31
+ # [No. of items in the list, represented as 4-byte integer] || encoding(item_0) || ....
32
+ #
33
+ # 4. The encoding of an ordered map where the keys are ordered by lexicographic order.
34
+ # Currently, we only support key and value of type Vec<u8>. The encoding is defined as:
35
+ # [No. of key value pairs in the map, represented as 4-byte integer] || encode(key1) ||
36
+ # encode(value1) || encode(key2) || encode(value2)...
37
+ # where the pairs are appended following the lexicographic order of the key
38
+ #
39
+ require "canoser/version"
40
+ require "canoser/cursor"
41
+ require "canoser/field"
42
+ require "canoser/struct"
43
+
44
+ module Canoser
45
+ class ParseError < StandardError; end
46
+ end
@@ -0,0 +1,24 @@
1
+ module Canoser
2
+ class Cursor
3
+ def initialize(bytes, offset=0)
4
+ @bytes = bytes
5
+ @offset = offset
6
+ end
7
+
8
+ def read_bytes(size)
9
+ raise ParseError.new("#{@offset+size} exceed bytes size:#{@bytes.size}") if @offset+size > @bytes.size
10
+ ret = @bytes[@offset, size]
11
+ @offset += size
12
+ ret
13
+ end
14
+
15
+ def peek_bytes(size)
16
+ raise ParseError.new("#{@offset+size} exceed bytes size:#{@bytes.size}") if @offset+size > @bytes.size
17
+ @bytes[@offset, size]
18
+ end
19
+
20
+ def finished?
21
+ @offset == @bytes.size
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,167 @@
1
+
2
+ module Canoser
3
+ class IntField
4
+ @@pack_map = {8 => "C", 16 => "S", 32 => "L", 64 => "Q"}
5
+
6
+ def initialize(int_bits)
7
+ @int_bits = int_bits
8
+ end
9
+
10
+ def inspect
11
+ "Uint#{@int_bits}"
12
+ end
13
+
14
+ def to_s
15
+ inspect
16
+ end
17
+
18
+ def pack_str
19
+ @@pack_map[@int_bits]
20
+ end
21
+
22
+ def encode(value)
23
+ [value].pack(pack_str)
24
+ end
25
+
26
+ def decode_bytes(bytes)
27
+ bytes.unpack(pack_str)[0]
28
+ end
29
+
30
+ def decode(cursor)
31
+ bytes = cursor.read_bytes(@int_bits/8)
32
+ decode_bytes(bytes)
33
+ end
34
+
35
+ def max_value
36
+ 2**@int_bits - 1
37
+ end
38
+ end
39
+
40
+ Uint8 = IntField.new(8)
41
+ Uint16 = IntField.new(16)
42
+ Uint32 = IntField.new(32)
43
+ Uint64 = IntField.new(64)
44
+
45
+ class Bool
46
+ def self.encode(value)
47
+ if value
48
+ "\1"
49
+ else
50
+ "\0"
51
+ end
52
+ end
53
+
54
+ def self.decode_bytes(bytes)
55
+ return true if bytes == "\1"
56
+ return false if bytes == "\0"
57
+ raise ParseError.new("bool should be 0 or 1.")
58
+ end
59
+
60
+ def self.decode(cursor)
61
+ bytes = cursor.read_bytes(1)
62
+ decode_bytes(bytes)
63
+ end
64
+ end
65
+
66
+
67
+ class Str
68
+ def self.encode(value)
69
+ output = ""
70
+ bytes = value.bytes
71
+ output << Uint32.encode(bytes.size)
72
+ bytes.each{|x| output << Canoser::Uint8.encode(x)}
73
+ output
74
+ end
75
+
76
+ def self.decode(cursor)
77
+ str = ""
78
+ len = Uint32.decode(cursor)
79
+ len.times do
80
+ str << Uint8.decode(cursor)
81
+ end
82
+ str
83
+ end
84
+ end
85
+
86
+
87
+ class ArrayT
88
+ attr_accessor :type, :fixed_len
89
+
90
+ def initialize(type=Uint8, fixed_len=nil)
91
+ @type = type
92
+ @fixed_len = fixed_len
93
+ end
94
+
95
+ def encode(arr)
96
+ output = ""
97
+ output << Uint32.encode(arr.size) unless @fixed_len
98
+ arr.each{|x| output << @type.encode(x)}
99
+ output
100
+ end
101
+
102
+ def decode(cursor)
103
+ arr = []
104
+ len = @fixed_len
105
+ len = Uint32.decode(cursor) unless @fixed_len
106
+ len.times do
107
+ arr << @type.decode(cursor)
108
+ end
109
+ arr
110
+ end
111
+
112
+ def ==(other)
113
+ return self.type == other.type && self.fixed_len == other.fixed_len
114
+ end
115
+
116
+ end
117
+
118
+ DEFAULT_KV = ArrayT.new(Uint8)
119
+
120
+ class HashT
121
+ attr_accessor :ktype, :vtype
122
+
123
+ def initialize(ktype=DEFAULT_KV, vtype=DEFAULT_KV)
124
+ @ktype = ktype || DEFAULT_KV
125
+ @vtype = vtype || DEFAULT_KV
126
+ end
127
+
128
+ def encode(hash)
129
+ output = ""
130
+ output << Uint32.encode(hash.size)
131
+ sorted_map = {}
132
+ hash.each do |k, v|
133
+ sorted_map[ktype.encode(k)] = vtype.encode(v)
134
+ end
135
+ sorted_map.keys.sort.each do |k|
136
+ output << k
137
+ output << sorted_map[k]
138
+ end
139
+ output
140
+ end
141
+
142
+ def decode(cursor)
143
+ hash = {}
144
+ len = Uint32.decode(cursor)
145
+ len.times do
146
+ k = ktype.decode(cursor)
147
+ v = vtype.decode(cursor)
148
+ hash[k] = v
149
+ end
150
+ hash
151
+ end
152
+
153
+ def ==(other)
154
+ return self.ktype == other.ktype && self.vtype == other.vtype
155
+ end
156
+
157
+ end
158
+
159
+
160
+ class Optional
161
+ def initialize(bool, value)
162
+ end
163
+
164
+ end
165
+
166
+
167
+ end
@@ -0,0 +1,90 @@
1
+
2
+ module Canoser
3
+
4
+ class Struct
5
+ def self.define_field(name, type, arr_len=nil)
6
+ name = ":"+name.to_sym.to_s
7
+ if type.class == Array
8
+ type = ArrayT.new(type[0], arr_len)
9
+ elsif type.class == Hash
10
+ type = HashT.new(type.keys[0], type.values[0])
11
+ else
12
+ raise "type #{type} doen't support arr_len param" if arr_len
13
+ end
14
+ str = %Q{
15
+ @@names ||= []
16
+ @@names << #{name}
17
+ @@types ||= []
18
+ @@types << type
19
+ }
20
+ class_eval str
21
+ end
22
+
23
+ def initialize(hash={})
24
+ @values = {}
25
+ hash.each do |k,v|
26
+ idx = self.class.class_variable_get("@@names").find_index{|x| x==k}
27
+ raise "#{k} is not a field of #{self}" unless idx
28
+ type = self.class.class_variable_get("@@types")[idx]
29
+ if type.class == ArrayT
30
+ len = type.fixed_len
31
+ raise "fix-length array #{k}: #{len} != #{v.size}" if len && v.size != len
32
+ end
33
+ @values[k] = v
34
+ end
35
+ end
36
+
37
+ def [](name)
38
+ @values[name.to_sym]
39
+ end
40
+
41
+ def []=(name, value)
42
+ @values[name.to_sym] = value
43
+ end
44
+
45
+ def ==(other)
46
+ return true if self.equal?(other)
47
+ self.class.class_variable_get("@@names").each_with_index do |name, idx|
48
+ unless @values[name] == other[name]
49
+ return false
50
+ end
51
+ end
52
+ true
53
+ end
54
+
55
+ def self.encode(value)
56
+ value.serialize
57
+ end
58
+
59
+ def serialize
60
+ output = ""
61
+ self.class.class_variable_get("@@names").each_with_index do |name, idx|
62
+ type = self.class.class_variable_get("@@types")[idx]
63
+ value = @values[name]
64
+ output << type.encode(value)
65
+ end
66
+ output
67
+ end
68
+
69
+ def self.deserialize(bytes)
70
+ cursor = Canoser::Cursor.new(bytes)
71
+ ret = decode(cursor)
72
+ raise ParseError.new("bytes not all consumed.") unless cursor.finished?
73
+ ret
74
+ end
75
+
76
+ def self.decode(cursor)
77
+ self.new({}).decode(cursor)
78
+ end
79
+
80
+ def decode(cursor)
81
+ self.class.class_variable_get("@@names").each_with_index do |name, idx|
82
+ type = self.class.class_variable_get("@@types")[idx]
83
+ @values[name] = type.decode(cursor)
84
+ end
85
+ self
86
+ end
87
+
88
+ end
89
+
90
+ end
@@ -0,0 +1,3 @@
1
+ module Canoser
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: canoser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - yuan xinyu
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-08-27 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: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.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: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '11.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '11.0'
69
+ description: A ruby implementation of the canonical serialization for the Libra network.
70
+ email:
71
+ - yuanxinyu.hangzhou@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".travis.yml"
78
+ - Gemfile
79
+ - Gemfile.lock
80
+ - LICENSE.txt
81
+ - README-CN.md
82
+ - README.md
83
+ - Rakefile
84
+ - bin/console
85
+ - bin/setup
86
+ - canoser.gemspec
87
+ - lib/canoser.rb
88
+ - lib/canoser/cursor.rb
89
+ - lib/canoser/field.rb
90
+ - lib/canoser/struct.rb
91
+ - lib/canoser/version.rb
92
+ homepage: https://github.com/yuanxinyu/canoser.git
93
+ licenses:
94
+ - MIT
95
+ metadata:
96
+ allowed_push_host: https://rubygems.org
97
+ homepage_uri: https://github.com/yuanxinyu/canoser.git
98
+ source_code_uri: https://github.com/yuanxinyu/canoser.git
99
+ changelog_uri: https://github.com/yuanxinyu/canoser.git
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubyforge_project:
116
+ rubygems_version: 2.7.7
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: A ruby implementation of the canonical serialization for the Libra network.
120
+ test_files: []