zser 0.0.0 → 0.0.1
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 +4 -4
- data/.gitignore +1 -0
- data/.rspec +3 -1
- data/.rubocop.yml +16 -7
- data/.ruby-version +1 -0
- data/Gemfile +5 -1
- data/Guardfile +18 -0
- data/README.md +50 -11
- data/Rakefile +15 -1
- data/lib/zser.rb +18 -0
- data/lib/zser/decoder.rb +43 -0
- data/lib/zser/exceptions.rb +27 -0
- data/lib/zser/object.rb +53 -0
- data/lib/zser/parser.rb +89 -0
- data/lib/zser/varint.rb +99 -0
- data/lib/zser/version.rb +3 -1
- data/zser.gemspec +4 -2
- metadata +11 -7
- data/.travis.yml +0 -5
- data/bin/console +0 -14
- data/bin/setup +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4ff5c747cc1a2d7be12df6224a14c098a1a06a5
|
4
|
+
data.tar.gz: 331c618c62205dea46544daa16a2ebbdebd59a3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55f3a70cc8e4e67c19adea81d7f32172970168b0b710e182b5e95a5e1c7cdde8d53d1642508b3c6e6a649eab4597899c30e9211d8bfa7daa9c4fc4523e745e85
|
7
|
+
data.tar.gz: d95178c9ad6d06c2ed2de519c7a3d0749963161ee9f15b7cb30163e715e3d7d43cfcc0ef2ce77ea99dd5dc56378d229400a2d5b4496fa9958752aac4e8d1180e
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.rubocop.yml
CHANGED
@@ -5,8 +5,11 @@ AllCops:
|
|
5
5
|
# Style
|
6
6
|
#
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
Style/ModuleFunction:
|
9
|
+
Enabled: false
|
10
|
+
|
11
|
+
Style/NumericPredicate:
|
12
|
+
Enabled: false
|
10
13
|
|
11
14
|
Style/StringLiterals:
|
12
15
|
EnforcedStyle: double_quotes
|
@@ -16,16 +19,22 @@ Style/StringLiterals:
|
|
16
19
|
#
|
17
20
|
|
18
21
|
Metrics/AbcSize:
|
19
|
-
|
20
|
-
|
21
|
-
Metrics/CyclomaticComplexity:
|
22
|
-
Enabled: false
|
22
|
+
Max: 25
|
23
23
|
|
24
|
-
Metrics/
|
24
|
+
Metrics/BlockLength:
|
25
25
|
Enabled: false
|
26
26
|
|
27
27
|
Metrics/ClassLength:
|
28
28
|
Max: 100
|
29
29
|
|
30
|
+
Metrics/CyclomaticComplexity:
|
31
|
+
Max: 25
|
32
|
+
|
33
|
+
Metrics/LineLength:
|
34
|
+
Max: 128
|
35
|
+
|
30
36
|
Metrics/MethodLength:
|
31
37
|
Max: 25
|
38
|
+
|
39
|
+
Metrics/PerceivedComplexity:
|
40
|
+
Max: 25
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.4.1
|
data/Gemfile
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
|
+
ruby RUBY_VERSION
|
4
5
|
|
5
6
|
gemspec
|
6
7
|
|
7
8
|
group :development, :test do
|
9
|
+
gem "benchmark-ips"
|
10
|
+
gem "guard-rspec"
|
8
11
|
gem "rake"
|
9
12
|
gem "rspec", "~> 3.5"
|
10
|
-
gem "rubocop", "0.
|
13
|
+
gem "rubocop", "0.48.1"
|
14
|
+
gem "tjson", "~> 0.5"
|
11
15
|
end
|
data/Guardfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# More info at https://github.com/guard/guard#readme
|
4
|
+
|
5
|
+
guard :rspec, cmd: "GUARD_RSPEC=1 bundle exec rspec --no-profile" do
|
6
|
+
require "guard/rspec/dsl"
|
7
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
8
|
+
|
9
|
+
# RSpec files
|
10
|
+
rspec = dsl.rspec
|
11
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
12
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
13
|
+
watch(rspec.spec_files)
|
14
|
+
|
15
|
+
# Ruby files
|
16
|
+
ruby = dsl.ruby
|
17
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
18
|
+
end
|
data/README.md
CHANGED
@@ -1,15 +1,46 @@
|
|
1
|
-
#
|
1
|
+
# zser.rb [![Latest Version][gem-shield]][gem-link] [![Build Status][build-image]][build-link] [![MIT licensed][license-image]][license-link]
|
2
2
|
|
3
|
-
|
3
|
+
[gem-shield]: https://badge.fury.io/rb/zser.svg
|
4
|
+
[gem-link]: https://rubygems.org/gems/zser
|
5
|
+
[build-image]: https://secure.travis-ci.org/zcred/zser.svg?branch=master
|
6
|
+
[build-link]: http://travis-ci.org/zcred/zser
|
7
|
+
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg
|
8
|
+
[license-link]: https://github.com/zcred/zser/blob/master/LICENSE.txt
|
4
9
|
|
5
|
-
|
10
|
+
Ruby implementation of **zser**: a security-oriented serialization format
|
11
|
+
with novel authentication properties based on "Merkleized" data structures.
|
12
|
+
|
13
|
+
For more information, see the [toplevel README.md].
|
14
|
+
|
15
|
+
[toplevel README.md]: https://github.com/zcred/zser/blob/master/README.md
|
16
|
+
|
17
|
+
## Help and Discussion
|
18
|
+
|
19
|
+
Have questions? Want to suggest a feature or change?
|
20
|
+
|
21
|
+
* [Gitter]: web-based chat about zcred projects including **zser**
|
22
|
+
* [Google Group]: join via web or email ([zcred+subscribe@googlegroups.com])
|
23
|
+
|
24
|
+
[Gitter]: https://gitter.im/zcred/Lobby
|
25
|
+
[Google Group]: https://groups.google.com/forum/#!forum/zcred
|
26
|
+
[zcred+subscribe@googlegroups.com]: mailto:zcred+subscribe@googlegroups.com
|
27
|
+
|
28
|
+
## Requirements
|
29
|
+
|
30
|
+
This library is tested against the following MRI versions:
|
31
|
+
|
32
|
+
- 2.2
|
33
|
+
- 2.3
|
34
|
+
- 2.4
|
35
|
+
|
36
|
+
Other Ruby versions may work, but are not officially supported.
|
6
37
|
|
7
38
|
## Installation
|
8
39
|
|
9
40
|
Add this line to your application's Gemfile:
|
10
41
|
|
11
42
|
```ruby
|
12
|
-
gem
|
43
|
+
gem "zser"
|
13
44
|
```
|
14
45
|
|
15
46
|
And then execute:
|
@@ -20,17 +51,25 @@ Or install it yourself as:
|
|
20
51
|
|
21
52
|
$ gem install zser
|
22
53
|
|
23
|
-
##
|
54
|
+
## API
|
24
55
|
|
25
|
-
|
56
|
+
### Zser.parse
|
26
57
|
|
27
|
-
|
58
|
+
To parse a **zser** message, use the `Zser.parse` method:
|
28
59
|
|
29
|
-
|
30
|
-
|
31
|
-
|
60
|
+
```ruby
|
61
|
+
>> Zser.parse("\x15\x07\x02\x03\x55".b)
|
62
|
+
=> {1=>{24=>42}}
|
63
|
+
```
|
32
64
|
|
33
65
|
## Contributing
|
34
66
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
67
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/zcred/zser
|
68
|
+
|
69
|
+
## Copyright
|
70
|
+
|
71
|
+
Copyright (c) 2017 [The Zcred Developers][AUTHORS].
|
72
|
+
See [LICENSE.txt] for further details.
|
36
73
|
|
74
|
+
[AUTHORS]: https://github.com/zcred/zcred/blob/master/AUTHORS.md
|
75
|
+
[LICENSE.txt]: https://github.com/zcred/zser/blob/master/LICENSE.txt
|
data/Rakefile
CHANGED
@@ -8,4 +8,18 @@ RSpec::Core::RakeTask.new
|
|
8
8
|
require "rubocop/rake_task"
|
9
9
|
RuboCop::RakeTask.new
|
10
10
|
|
11
|
-
task default: %w
|
11
|
+
task default: %w[spec rubocop]
|
12
|
+
|
13
|
+
task :bench do
|
14
|
+
require "benchmark/ips"
|
15
|
+
require "zser"
|
16
|
+
|
17
|
+
Benchmark.ips do |b|
|
18
|
+
input = "\xE9\xF4\x81\x80\x80\x80@".dup.force_encoding("BINARY").freeze
|
19
|
+
|
20
|
+
b.report("zsint encode") { Zser::Varint.encode(281_474_976_741_993) }
|
21
|
+
b.report("zsint decode") { Zser::Varint.decode(input) }
|
22
|
+
|
23
|
+
b.compare!
|
24
|
+
end
|
25
|
+
end
|
data/lib/zser.rb
CHANGED
@@ -1,5 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "zser/version"
|
4
|
+
require "zser/exceptions"
|
5
|
+
|
6
|
+
require "zser/decoder"
|
7
|
+
require "zser/object"
|
8
|
+
require "zser/parser"
|
9
|
+
require "zser/varint"
|
2
10
|
|
3
11
|
# zcred serialization format
|
4
12
|
module Zser
|
13
|
+
# Parse the given self-describing zser message
|
14
|
+
#
|
15
|
+
# @param message [String] binary encoded zser message
|
16
|
+
#
|
17
|
+
# @return [Zser::Object] Hash-like object representing message
|
18
|
+
def self.parse(message)
|
19
|
+
parser = Zser::Parser.new(Zser::Decoder.new)
|
20
|
+
parser.parse(message)
|
21
|
+
parser.finish
|
22
|
+
end
|
5
23
|
end
|
data/lib/zser/decoder.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zser
|
4
|
+
# Build Zser::Objects from zser's self-describing form
|
5
|
+
class Decoder
|
6
|
+
# Create a new decoder object which will construct a Zser::Object tree
|
7
|
+
def initialize
|
8
|
+
@stack = [Zser::Object.new]
|
9
|
+
end
|
10
|
+
|
11
|
+
# Add a uint64 to the current object
|
12
|
+
def uint64(id, value)
|
13
|
+
raise TypeError, "expected Integer, got #{value.class}" unless value.is_a?(Integer)
|
14
|
+
@stack.last[id] = value
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add binary data to the current object
|
18
|
+
def binary(id, value)
|
19
|
+
raise TypeError, "expected String, got #{value.class}" unless value.is_a?(String)
|
20
|
+
raise EncodingError, "expected BINARY encoding, got #{value.encoding}" unless value.encoding == Encoding::BINARY
|
21
|
+
@stack.last[id] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
# Push down the internal stack, constructing a new Zser::Object
|
25
|
+
def begin_nested
|
26
|
+
@stack << Zser::Object.new
|
27
|
+
end
|
28
|
+
|
29
|
+
# Complete the pushdown, adding the newly constructed object to the next one in the stack
|
30
|
+
def end_nested(id)
|
31
|
+
value = @stack.pop
|
32
|
+
raise StateError, "not inside a nested message" if @stack.empty?
|
33
|
+
@stack.last[id] = value
|
34
|
+
end
|
35
|
+
|
36
|
+
# Finish decoding, returning the parent Zser::Object
|
37
|
+
def finish
|
38
|
+
result = @stack.pop
|
39
|
+
raise StateError, "objects remaining in stack" unless @stack.empty?
|
40
|
+
result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zser
|
4
|
+
# Base class of all Zser errors
|
5
|
+
Error = Class.new(StandardError)
|
6
|
+
|
7
|
+
# Generic parse error
|
8
|
+
ParseError = Class.new(Error)
|
9
|
+
|
10
|
+
# Data is not in the correct character encoding
|
11
|
+
EncodingError = Class.new(ParseError)
|
12
|
+
|
13
|
+
# Unexpected end of input
|
14
|
+
EOFError = Class.new(ParseError)
|
15
|
+
|
16
|
+
# Message is larger than our maximum configured size
|
17
|
+
OversizeMessageError = Class.new(ParseError)
|
18
|
+
|
19
|
+
# Nested message structure is too deep
|
20
|
+
DepthError = Class.new(ParseError)
|
21
|
+
|
22
|
+
# Parser is in the wrong state to perform the given task
|
23
|
+
StateError = Class.new(ParseError)
|
24
|
+
|
25
|
+
# Field repeated in message
|
26
|
+
DuplicateFieldError = Class.new(ParseError)
|
27
|
+
end
|
data/lib/zser/object.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zser
|
4
|
+
# Key/value pairs ala JSON objects or Protobuf messages
|
5
|
+
class Object
|
6
|
+
# Create a new Zser::Object
|
7
|
+
#
|
8
|
+
# @return [Zser::Object]
|
9
|
+
def initialize
|
10
|
+
@fields = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# Retrieve the value associated with a field identifier in a Zser::Object
|
14
|
+
#
|
15
|
+
# @param key [Integer] field identifier
|
16
|
+
#
|
17
|
+
# @return [Object] value associated with this key
|
18
|
+
def [](key)
|
19
|
+
@fields[key]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the value associated with a field identifier
|
23
|
+
#
|
24
|
+
# @param key [Integer] field identifier
|
25
|
+
# @param value [Object] value associated with the given key
|
26
|
+
#
|
27
|
+
# @raise [TypeError] non-Integer key given
|
28
|
+
# @raise [Zser::DuplicateFieldError] attempt to set field that's already been set
|
29
|
+
#
|
30
|
+
# @return [Object] newly set value
|
31
|
+
def []=(key, value)
|
32
|
+
raise TypeError, "key must be an integer: #{key.inspect}" unless key.is_a?(Integer)
|
33
|
+
raise RangeError, "key must be positive: #{key.inspect}" if key < 0
|
34
|
+
raise DuplicateFieldError, "duplicate field ID: #{key}" if @fields.key?(key)
|
35
|
+
|
36
|
+
@fields[key] = value
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return a hash representation of this object (and its children).
|
40
|
+
# This is akin to an `#as_json` method as seen in e.g. Rails.
|
41
|
+
#
|
42
|
+
# @return [Hash] a hash representation of this object
|
43
|
+
def to_h
|
44
|
+
result = {}
|
45
|
+
|
46
|
+
@fields.each do |k, v|
|
47
|
+
result[k.to_s] = v.is_a?(self.class) ? v.to_h : v
|
48
|
+
end
|
49
|
+
|
50
|
+
result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/zser/parser.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zser
|
4
|
+
# Parses encoded zser messages, invoking callbacks in the given handler
|
5
|
+
# (i.e. this is a "push parser" which supports different backends)
|
6
|
+
class Parser
|
7
|
+
# Default maximum length of a zser message. This is a conservative choice
|
8
|
+
# as zser's main intended use is a credential format.
|
9
|
+
MAX_LENGTH = 1024
|
10
|
+
|
11
|
+
# Default maximum depth (i.e. number of levels of child objects)
|
12
|
+
MAX_DEPTH = 8
|
13
|
+
|
14
|
+
# Create a new message parser with the given parse event handler
|
15
|
+
def initialize(handler, max_length = MAX_LENGTH, max_depth = MAX_DEPTH)
|
16
|
+
@handler = handler
|
17
|
+
@max_length = max_length
|
18
|
+
@max_depth = max_depth
|
19
|
+
@remaining = []
|
20
|
+
end
|
21
|
+
|
22
|
+
# Parse the given zser message, invoking callbacks as necessary
|
23
|
+
def parse(msg)
|
24
|
+
raise OversizeMessageError, "length #{msg.length} exceeds max of #{@max_length}" if msg.length > @max_length
|
25
|
+
raise EncodingError, "expected BINARY encoding, got #{msg.encoding}" unless msg.encoding == Encoding::BINARY
|
26
|
+
@remaining << msg
|
27
|
+
|
28
|
+
raise DepthError, "exceeded max depth of #{@max_depth}" if @remaining.size > @max_depth
|
29
|
+
|
30
|
+
until @remaining.last.empty?
|
31
|
+
id, wiretype = parse_field_prefix
|
32
|
+
|
33
|
+
case wiretype
|
34
|
+
when 0 then parse_uint64(id)
|
35
|
+
when 2 then parse_message(id)
|
36
|
+
when 3 then parse_binary(id)
|
37
|
+
else raise ParseError, "unknown wiretype: #{wiretype.inspect}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
@remaining.pop
|
42
|
+
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
# Finish parsing, returning the resulting object produced by the builder
|
47
|
+
def finish
|
48
|
+
@handler.finish
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Parse a varint which also stores a wiretype
|
54
|
+
def parse_field_prefix
|
55
|
+
result, remaining = Zser::Varint.decode(@remaining.pop)
|
56
|
+
@remaining << remaining
|
57
|
+
wiretype = result & 0x7
|
58
|
+
[result >> 3, wiretype]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Parse an unsigned 64-bit integer
|
62
|
+
def parse_uint64(id)
|
63
|
+
value, remaining = Zser::Varint.decode(@remaining.pop)
|
64
|
+
@remaining << remaining
|
65
|
+
@handler.uint64(id, value)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Parse a data type stored with a length prefix
|
69
|
+
def parse_length_prefixed_data
|
70
|
+
length, remaining = Zser::Varint.decode(@remaining.pop)
|
71
|
+
raise EOFError, "not enough bytes remaining in input" if remaining.bytesize < length
|
72
|
+
data = remaining.byteslice(0, length)
|
73
|
+
@remaining << remaining.byteslice(length, remaining.bytesize - length)
|
74
|
+
data
|
75
|
+
end
|
76
|
+
|
77
|
+
# Parse a nested message
|
78
|
+
def parse_message(id)
|
79
|
+
@handler.begin_nested
|
80
|
+
parse(parse_length_prefixed_data)
|
81
|
+
@handler.end_nested(id)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Parse length-prefixed binary data
|
85
|
+
def parse_binary(id)
|
86
|
+
@handler.binary(id, parse_length_prefixed_data)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/zser/varint.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Zser
|
5
|
+
# zsint: Little Endian 64-bit Unsigned Prefix Varints
|
6
|
+
module Varint
|
7
|
+
# Maximum value we can encode as a zsuint64
|
8
|
+
MAX = (2**64) - 1
|
9
|
+
|
10
|
+
# :nodoc: Lookup table for the number of trailing zeroes in a byte
|
11
|
+
CTZ_TABLE = [8, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
12
|
+
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
13
|
+
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
14
|
+
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
15
|
+
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
16
|
+
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
17
|
+
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
18
|
+
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
19
|
+
7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
20
|
+
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
21
|
+
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
22
|
+
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
23
|
+
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
24
|
+
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
25
|
+
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
|
26
|
+
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0].freeze
|
27
|
+
|
28
|
+
# Encode the given integer value as a zsuint64
|
29
|
+
#
|
30
|
+
# @param value [Integer] unsigned integer value to encode as a zsuint64
|
31
|
+
#
|
32
|
+
# @raise [TypeError] non-integer value given
|
33
|
+
# @raise [ArgumentError] value outside the unsigned 64-bit integer range
|
34
|
+
#
|
35
|
+
# @return [String] serialized zsuint64 value
|
36
|
+
def self.encode(value)
|
37
|
+
raise TypeError, "value must be an Integer" unless value.is_a?(Integer)
|
38
|
+
raise ArgumentError, "value must be zero or greater" if value < 0
|
39
|
+
raise ArgumentError, "value must be in the 64-bit unsigned range" if value > MAX
|
40
|
+
|
41
|
+
length = 1
|
42
|
+
result = (value << 1) | 1
|
43
|
+
max = 1 << 7
|
44
|
+
|
45
|
+
while value >= max
|
46
|
+
# 9-byte special case
|
47
|
+
return [0, value].pack("CQ<") if max == 1 << 63
|
48
|
+
|
49
|
+
result <<= 1
|
50
|
+
max <<= 7
|
51
|
+
length += 1
|
52
|
+
end
|
53
|
+
|
54
|
+
[result].pack("Q<")[0, length].force_encoding(Encoding::BINARY)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Decode a zsuint64-serialized value into an integer
|
58
|
+
#
|
59
|
+
# @param input [String] serialized zsuint64 to decode
|
60
|
+
#
|
61
|
+
# @raise [TypeError] non-String input given
|
62
|
+
# @raise [ArgumentError] empty input given
|
63
|
+
#
|
64
|
+
# @return [Array<Integer, String>] decoded integer and remaining data
|
65
|
+
def self.decode(input)
|
66
|
+
raise TypeError, "input must be a String" unless input.is_a?(String)
|
67
|
+
raise ArgumentError, "input cannot be empty" if input.empty?
|
68
|
+
|
69
|
+
prefix = input.getbyte(0)
|
70
|
+
input_len = input.bytesize
|
71
|
+
|
72
|
+
# 9-byte special case
|
73
|
+
if prefix.zero?
|
74
|
+
raise EOFError, "not enough bytes to decode varint" if input_len < 9
|
75
|
+
[decode_le64(input[1, 8]), input.byteslice(9, input_len - 9)]
|
76
|
+
else
|
77
|
+
# Count trailing zeroes
|
78
|
+
length = CTZ_TABLE[prefix] + 1
|
79
|
+
raise EOFError, "not enough bytes to decode varint" if input_len < length
|
80
|
+
[decode_le64(input[0, length]) >> length, input.byteslice(length, input_len - length)]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class << self
|
85
|
+
private
|
86
|
+
|
87
|
+
# Decode a little endian integer (without allocating memory, unlike pack)
|
88
|
+
def decode_le64(bytes)
|
89
|
+
result = 0
|
90
|
+
|
91
|
+
(bytes.bytesize - 1).downto(0) do |i|
|
92
|
+
result = (result << 8) | bytes.getbyte(i)
|
93
|
+
end
|
94
|
+
|
95
|
+
result
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/zser/version.rb
CHANGED
data/zser.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
lib = File.expand_path("../lib", __FILE__)
|
3
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
6
|
require "zser/version"
|
@@ -16,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
16
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
17
19
|
spec.require_paths = ["lib"]
|
18
20
|
|
19
|
-
spec.required_ruby_version = ">= 2.
|
21
|
+
spec.required_ruby_version = ">= 2.2.2"
|
20
22
|
|
21
23
|
spec.add_development_dependency "bundler", "~> 1.14"
|
22
24
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Arcieri
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -35,13 +35,17 @@ files:
|
|
35
35
|
- ".gitignore"
|
36
36
|
- ".rspec"
|
37
37
|
- ".rubocop.yml"
|
38
|
-
- ".
|
38
|
+
- ".ruby-version"
|
39
39
|
- Gemfile
|
40
|
+
- Guardfile
|
40
41
|
- README.md
|
41
42
|
- Rakefile
|
42
|
-
- bin/console
|
43
|
-
- bin/setup
|
44
43
|
- lib/zser.rb
|
44
|
+
- lib/zser/decoder.rb
|
45
|
+
- lib/zser/exceptions.rb
|
46
|
+
- lib/zser/object.rb
|
47
|
+
- lib/zser/parser.rb
|
48
|
+
- lib/zser/varint.rb
|
45
49
|
- lib/zser/version.rb
|
46
50
|
- zser.gemspec
|
47
51
|
homepage: https://github.com/zcred/zser/tree/master/ruby/
|
@@ -55,7 +59,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
55
59
|
requirements:
|
56
60
|
- - ">="
|
57
61
|
- !ruby/object:Gem::Version
|
58
|
-
version:
|
62
|
+
version: 2.2.2
|
59
63
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
64
|
requirements:
|
61
65
|
- - ">="
|
@@ -63,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
67
|
version: '0'
|
64
68
|
requirements: []
|
65
69
|
rubyforge_project:
|
66
|
-
rubygems_version: 2.6.
|
70
|
+
rubygems_version: 2.6.11
|
67
71
|
signing_key:
|
68
72
|
specification_version: 4
|
69
73
|
summary: zcred serialization format
|
data/.travis.yml
DELETED
data/bin/console
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "bundler/setup"
|
4
|
-
require "zser"
|
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__)
|