nstrct 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e2087a496c196012c63735b96ff04f1ab6d51c79
4
+ data.tar.gz: 669624331f0b1a78b122e926ddc1d703ddaa614f
5
+ SHA512:
6
+ metadata.gz: ff6574cb663aca80b4bee655b3e7a93a9a60be152b8213636402c39b33ccbcc3e14c8116a5dab061f820167f6a450d28c40ed4c683ff5a8ecf4096ecef57fd1f
7
+ data.tar.gz: 1929493e6b70b29c39d86bdc1e75e91758f123d445e634f462f7ab7c9b44ab5ae1cdc93b811e569a073cd6ee47a1910af4e093d45a72586fd9c070c0f6da99b3
@@ -0,0 +1 @@
1
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
3
+
4
+ group :test do
5
+ gem 'rspec'
6
+ end
@@ -0,0 +1,26 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ nstrct (0.0.3)
5
+ digest-crc
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.2.5)
11
+ digest-crc (0.4.0)
12
+ rspec (2.14.1)
13
+ rspec-core (~> 2.14.0)
14
+ rspec-expectations (~> 2.14.0)
15
+ rspec-mocks (~> 2.14.0)
16
+ rspec-core (2.14.7)
17
+ rspec-expectations (2.14.4)
18
+ diff-lcs (>= 1.1.3, < 2.0)
19
+ rspec-mocks (2.14.4)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ nstrct!
26
+ rspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 ElectricFeel Mobility Systems GmbH, Joël Gähwiler
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.
@@ -0,0 +1,40 @@
1
+ # nstrct-ruby
2
+
3
+ **a multi-purpose binary protocol for instruction interchange**
4
+
5
+ Interchange formats like json or xml are great to keep data visible, but due to their parse and pack complexity they aren't used in embedded applications. There are alternatives like msgpack or Google's protocol buffer, which allow a more binary representation of data, but these protcols are still heavy and developers tend to rather implement their own 'simple' binary protocols instead of porting or using the big ones.
6
+
7
+ The protcol **nstrct** is designed to be an alternative in those situations where a tiny and versatile protocol is needed. The main transportable unit in nstrct are **instructions** which carry a dynamic amount of data in the form of **arguments**. Each instruction is identified by a code between 0-65535 which can be used by two communicating applications to identify the blueprint of the message. Arguments support all standard types of integers(boolean, int8-64, uint8-64, float32/64), strings, and arrays of integers or strings. There is no support for hashes by design to keep the pack and unpacking functions as small as possible. [Check the main repository for the binary layout of the instructions](http://github.com/nstrct/nstrct).
8
+
9
+ **Start using nstrct by getting the library for your favorite programming language:**
10
+
11
+ * [C/C++ (nstrct-c)](http://github.com/nstrct/nstrct-c)
12
+ * [Ruby (nstrct-ruby)](http://github.com/nstrct/nstrct-ruby)
13
+ * [Obj-C (nstrct-objc)](http://github.com/nstrct/nstrct-objc)
14
+ * [Java (nstrct-c)](http://github.com/nstrct/nstrct-java)
15
+
16
+ _This software has been open sourced by [ElectricFeel Mobility Systems Gmbh](http://electricfeel.com) and is further maintained by [Joël Gähwiler](http://github.com/256dpi)._
17
+
18
+ ## Instruction Composing
19
+
20
+ A simple example:
21
+
22
+ ```ruby
23
+ instruction = Nstrct::Instruction.new(382)
24
+ bytes = instruction.pack
25
+ ```
26
+
27
+ A more complex example:
28
+
29
+ ```ruby
30
+ instruction = Nstrct::Instruction.build_instruction(232, [:boolean, true], [:int8, 2], [:float32, 1.0], [[:uint16], [54, 23, 1973]])
31
+ instruction.pack
32
+ ```
33
+
34
+ ## Instruction Processing
35
+
36
+ ```ruby
37
+ instruction = Nstrct::Instruction.parse(bytes)
38
+ instruction.code
39
+ instruction.arguments[0].datatype
40
+ ```
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+
5
+ $:.unshift File.expand_path('../lib', __FILE__)
6
+ require 'nstrct'
7
+
8
+ mode = nil
9
+
10
+ MIN = {
11
+ int8: (2**7)*-1,
12
+ int16: (2**15)*-1,
13
+ int32: (2**31)*-1,
14
+ int64: (2**63)*-1
15
+ }
16
+
17
+ MAX = {
18
+ uint8: (2**8)-1,
19
+ uint16: (2**16)-1,
20
+ uint32: (2**32)-1,
21
+ uint64: (2**64)-1,
22
+ float32: 3.4028234663852886*(10**38),
23
+ float64: 1.7976931348623157*(10**308)
24
+ }
25
+
26
+ def assert test, exp
27
+ unless exp
28
+ puts test
29
+ exit!
30
+ end
31
+ end
32
+
33
+ OptionParser.new do |opts|
34
+ opts.banner = 'Usage: cross_platform_test [options]'
35
+ opts.on('-p', '--process', 'process bytes at STDIN and return result') do |p|
36
+ mode = :process
37
+ end
38
+ opts.on('-g', '--generate', 'generate bytes and write to STDOUT') do |g|
39
+ mode = :generate
40
+ end
41
+ end.parse!
42
+
43
+ if mode == :generate
44
+ i = Nstrct::Instruction.build MAX[:uint16],
45
+ [:boolean, false],
46
+ [:int8, MIN[:int8]],
47
+ [:int16, MIN[:int16]],
48
+ [:int32, MIN[:int32]],
49
+ [:int64, MIN[:int64]],
50
+ [:uint8, MAX[:uint8]],
51
+ [:uint16, MAX[:uint16]],
52
+ [:uint32, MAX[:uint32]],
53
+ [:uint64, MAX[:uint64]],
54
+ [:float32, MAX[:float32]],
55
+ [:float64, MAX[:float64]],
56
+ [:string, 'hello world'],
57
+ [:string, ''],
58
+ [[:uint16], [2443, 3443]]
59
+ buf = Nstrct::Frame.new(i).pack
60
+ STDOUT.write [buf.size].pack('L>')
61
+ STDOUT.write buf
62
+ elsif mode == :process
63
+ length = STDIN.read(4).unpack('L>')[0]
64
+ i = Nstrct::Frame.parse(STDIN.read(length)).instruction
65
+ assert ' boolean value error', !i.arguments[0].value
66
+ assert ' int8 value error', i.arguments[1].value == MIN[:int8]
67
+ assert ' int16 value error', i.arguments[2].value == MIN[:int16]
68
+ assert ' int32 value error', i.arguments[3].value == MIN[:int32]
69
+ assert ' int64 value error', i.arguments[4].value == MIN[:int64]
70
+ assert ' uint8 value error', i.arguments[5].value == MAX[:uint8]
71
+ assert ' uint16 value error', i.arguments[6].value == MAX[:uint16]
72
+ assert ' uint32 value error', i.arguments[7].value == MAX[:uint32]
73
+ assert ' uint64 value error', i.arguments[8].value == MAX[:uint64]
74
+ assert ' float32 value error', i.arguments[9].value == MAX[:float32]
75
+ assert ' float64 value error', i.arguments[10].value == MAX[:float64]
76
+ assert ' string value error', i.arguments[11].value.eql?('hello world')
77
+ assert ' empty string value error', i.arguments[12].value.eql?('')
78
+ assert ' array uint16 value 1 error', i.arguments[13].value[0] == 2443
79
+ assert ' array uint16 value 2 error', i.arguments[13].value[1] == 3443
80
+ puts ' tests passed'
81
+ else
82
+ puts 'please specify a run mode'
83
+ end
@@ -0,0 +1,3 @@
1
+ require 'nstrct/argument'
2
+ require 'nstrct/instruction'
3
+ require 'nstrct/frame'
@@ -0,0 +1,149 @@
1
+ module Nstrct
2
+
3
+ class Argument
4
+
5
+ # Available datatypes and their argument code
6
+ DATATYPES = {
7
+ boolean: 1,
8
+ int8: 10,
9
+ int16: 11,
10
+ int32: 12,
11
+ int64: 13,
12
+ uint8: 14,
13
+ uint16: 15,
14
+ uint32: 16,
15
+ uint64: 17,
16
+ float32: 21,
17
+ float64: 22,
18
+ string: 31,
19
+ array: 32
20
+ }
21
+
22
+ # Get the datatype of a argument code
23
+ def self.datatype_by_for_argument_code code
24
+ DATATYPES.detect{ |k,v| v == code }[0]
25
+ end
26
+
27
+ # Parse a single value of a buffer
28
+ def self.parse_value datatype, data
29
+ case datatype
30
+ when :boolean
31
+ return data.slice!(0).unpack('C')[0] == 1
32
+ when :int8
33
+ return data.slice!(0).unpack('c')[0]
34
+ when :int16
35
+ return data.slice!(0..1).unpack('s>')[0]
36
+ when :int32
37
+ return data.slice!(0..3).unpack('l>')[0]
38
+ when :int64
39
+ return data.slice!(0..7).unpack('q>')[0]
40
+ when :uint8
41
+ return data.slice!(0).unpack('C')[0]
42
+ when :uint16
43
+ return data.slice!(0..1).unpack('S>')[0]
44
+ when :uint32
45
+ return data.slice!(0..3).unpack('L>')[0]
46
+ when :uint64
47
+ return data.slice!(0..7).unpack('Q>')[0]
48
+ when :float32
49
+ return data.slice!(0..3).unpack('g')[0]
50
+ when :float64
51
+ return data.slice!(0..7).unpack('G')[0]
52
+ when :string
53
+ length = data.slice!(0).unpack('C')[0]
54
+ return length > 0 ? data.slice!(0..length-1) : ''
55
+ when :array
56
+ raise 'cannot parse array value directly'
57
+ else
58
+ raise "datatype '#{datatype}' not recognized"
59
+ end
60
+ end
61
+
62
+ # Parse a single argument from a buffer
63
+ def self.parse data
64
+ datatype = self.datatype_by_for_argument_code(data.slice!(0).unpack('C')[0])
65
+ if datatype == :array
66
+ array_datatype = self.datatype_by_for_argument_code(data.slice!(0).unpack('C')[0])
67
+ array_num_elements = data.slice!(0).unpack('C')[0]
68
+ values = []
69
+ array_num_elements.times do
70
+ values << self.parse_value(array_datatype, data)
71
+ end
72
+ return self.new(array_datatype, values, true)
73
+ else
74
+ return self.new(datatype, self.parse_value(datatype, data), false)
75
+ end
76
+ end
77
+
78
+ attr_reader :datatype, :value, :array
79
+
80
+ # Instantiate a new Argument providing its datatype, value and arrayness
81
+ def initialize datatype, value, array
82
+ @datatype, @value, @array = datatype, value, array
83
+ end
84
+
85
+ # Pack a single value in a buffer
86
+ def pack_value datatype, value, data
87
+ case datatype
88
+ when :boolean
89
+ data += [value ? 1 : 0].pack('C')
90
+ when :int8
91
+ data += [value].pack('c')
92
+ when :int16
93
+ data += [value].pack('s>')
94
+ when :int32
95
+ data += [value].pack('l>')
96
+ when :int64
97
+ data += [value].pack('q>')
98
+ when :uint8
99
+ data += [value].pack('C')
100
+ when :uint16
101
+ data += [value].pack('S>')
102
+ when :uint32
103
+ data += [value].pack('L>')
104
+ when :uint64
105
+ data += [value].pack('Q>')
106
+ when :float32
107
+ data += [value].pack('g')
108
+ when :float64
109
+ data += [value].pack('G')
110
+ when :string
111
+ data += [value.size].pack('C')
112
+ data += value
113
+ when :array
114
+ raise 'cannot pack array value directly'
115
+ else
116
+ raise "datatype '#{datatype}' not recognized"
117
+ end
118
+ data
119
+ end
120
+
121
+ # Pack a single argument in a buffer
122
+ def pack
123
+ data = ''
124
+ if @array
125
+ data += [DATATYPES[:array]].pack('C')
126
+ data += [DATATYPES[@datatype]].pack('C')
127
+ data += [@value.size].pack('C')
128
+ @value.each do |val|
129
+ data = pack_value(@datatype, val, data)
130
+ end
131
+ else
132
+ data += [DATATYPES[datatype]].pack('C')
133
+ data = pack_value(@datatype, @value, data)
134
+ end
135
+ data
136
+ end
137
+
138
+ # Return comparable inspection
139
+ def inspect
140
+ if @array
141
+ "[#{@datatype.inspect}]=#{@value}"
142
+ else
143
+ "#{@datatype.inspect}=#{@value}"
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+ end
@@ -0,0 +1,62 @@
1
+ require 'digest/crc32'
2
+
3
+ module Nstrct
4
+
5
+ class Frame
6
+
7
+ FRAME_OVERHEAD = 8
8
+ FRAME_START = 0x55
9
+ FRAME_END = 0xAA
10
+
11
+ def self.crc32 buffer
12
+ Digest::CRC32.checksum buffer
13
+ end
14
+
15
+ def self.available? buffer
16
+ if buffer.size >= 1
17
+ raise 'start of frame invalid' unless buffer.slice(0).unpack('C')[0] == FRAME_START
18
+ if buffer.size >= 3
19
+ payload_length = buffer.slice(1..2).unpack('S>')[0]
20
+ if buffer.size >= (payload_length+FRAME_OVERHEAD)
21
+ raise 'end of frame invalid' unless buffer.slice(payload_length+FRAME_OVERHEAD-1).unpack('C')[0] == FRAME_END
22
+ return true
23
+ end
24
+ end
25
+ end
26
+ false
27
+ end
28
+
29
+ def self.parse buffer
30
+ raise 'no frame available' unless self.available?(buffer)
31
+ buffer.slice!(0) # remove start of frame
32
+ length = buffer.slice!(0..1).unpack('S>')[0]
33
+ payload = buffer.slice!(0..length-1)
34
+ checksum = buffer.slice!(0..3).unpack('L>')[0]
35
+ buffer.slice!(0) # remove end of frame
36
+ raise 'checksum invalid' unless checksum == crc32(payload)
37
+ self.new(Instruction.parse(payload))
38
+ end
39
+
40
+ attr_accessor :instruction
41
+
42
+ def initialize instruction
43
+ @instruction = instruction
44
+ end
45
+
46
+ def pack
47
+ payload = @instruction.pack
48
+ frame = [FRAME_START].pack('C')
49
+ frame += [payload.size].pack('S>')
50
+ frame += payload
51
+ frame += [Frame.crc32(payload)].pack('L>')
52
+ frame + [FRAME_END].pack('C')
53
+ end
54
+
55
+ # Return a comparable inspection
56
+ def inspect
57
+ "#<Nstrct::Frame instruction=#{@instruction.inspect}>"
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,68 @@
1
+ module Nstrct
2
+
3
+ class Instruction
4
+
5
+ attr_accessor :code, :arguments
6
+
7
+ # Parse one message from the data stream.
8
+ def self.parse data
9
+ code = data.slice!(0..1).unpack('s>')[0]
10
+ num_arguments = data.slice!(0).unpack('C')[0]
11
+ data.slice!(0..1) # num_array_elements
12
+ arguments = []
13
+ num_arguments.times do
14
+ arguments << Nstrct::Argument.parse(data)
15
+ end
16
+ self.new(code, arguments)
17
+ end
18
+
19
+ # Build an instruction for a code and some arguments.
20
+ #
21
+ # Message.build_instruction 54, [ :boolean, true], [[:int8], [7, 8, 9]] ]
22
+ #
23
+ def self.build code, *args
24
+ arguments = []
25
+ args.each do |arg|
26
+ if arg[0].is_a?(Array)
27
+ arguments << Nstrct::Argument.new(arg[0][0], arg[1], true)
28
+ else
29
+ arguments << Nstrct::Argument.new(arg[0], arg[1], false)
30
+ end
31
+ end
32
+ self.new(code, arguments)
33
+ end
34
+
35
+ # Instantiate a new instruction by its code and alist of arguments
36
+ def initialize code, arguments=[]
37
+ @code, @arguments = code, arguments
38
+ end
39
+
40
+ # Get all the arguments values
41
+ def argument_values
42
+ @arguments.map{ |arg| arg.value }
43
+ end
44
+
45
+ # get all elements in arrays
46
+ def array_elements
47
+ @arguments.inject(0){ |sum, a| sum + (a.array ? a.value.size : 0) }
48
+ end
49
+
50
+ # Pack a single instruction in a buffer
51
+ def pack
52
+ message = [@code].pack('s>')
53
+ message += [@arguments.size].pack('C')
54
+ message += [array_elements].pack('s>')
55
+ @arguments.each do |arg|
56
+ message += arg.pack
57
+ end
58
+ message
59
+ end
60
+
61
+ # Return a comparable inspection
62
+ def inspect
63
+ "#<Nstrct::Instructon code=#{@code.inspect}, arguments=#{@arguments.inspect}>"
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,3 @@
1
+ module Nstrct
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,21 @@
1
+ $:.push File.expand_path('../lib', __FILE__)
2
+ require 'nstrct/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'nstrct'
6
+ s.version = Nstrct::VERSION
7
+
8
+ s.summary = 'a multi-purpose binary protocol for instruction interchange'
9
+ s.authors = 'Joël Gähwiler'
10
+ s.email = 'nstrct@256dpi.ch'
11
+ s.homepage = 'http://github.com/nstrct'
12
+
13
+ s.description = "Interchange formats like json or xml are great to keep data visible, but due to their parse and pack complexity they aren't used in embedded applications. There are alternatives like msgpack or Google's protocol buffer, which allow a more binary representation of data, but these protcols are still heavy and developers tend to rather implement their own 'simple' binary protocols instead of porting or using the big ones."
14
+
15
+ s.license = 'MIT'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+
20
+ s.add_runtime_dependency 'digest-crc'
21
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Nstrct::Instruction do
4
+
5
+ it 'should pack and unpack an empty instruction' do
6
+ instruction1 = Nstrct::Instruction.new(382, [])
7
+ instruction2 = Nstrct::Instruction.parse(instruction1.pack)
8
+ instruction1.inspect.should == instruction2.inspect
9
+ end
10
+
11
+ it 'should pack and unpack an instructionw with arguments' do
12
+ instruction1 = Nstrct::Instruction.build(232, [:float32, 1.0 ], [:boolean, true], [:int8, 2], [:float32, 1.0], [[:uint16], [54, 23, 1973]])
13
+ instruction2 = Nstrct::Instruction.parse(instruction1.pack)
14
+ instruction1.inspect.should == instruction2.inspect
15
+ end
16
+
17
+ end
18
+
19
+ describe Nstrct::Frame do
20
+
21
+ it 'should pack and unpack a frame' do
22
+ frame1 = Nstrct::Frame.new(Nstrct::Instruction.new(132, []))
23
+ frame2 = Nstrct::Frame.parse(frame1.pack)
24
+ frame1.inspect.should == frame2.inspect
25
+ end
26
+
27
+ end
@@ -0,0 +1 @@
1
+ require 'nstrct'
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nstrct
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Joël Gähwiler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: digest-crc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Interchange formats like json or xml are great to keep data visible,
28
+ but due to their parse and pack complexity they aren't used in embedded applications.
29
+ There are alternatives like msgpack or Google's protocol buffer, which allow a more
30
+ binary representation of data, but these protcols are still heavy and developers
31
+ tend to rather implement their own 'simple' binary protocols instead of porting
32
+ or using the big ones.
33
+ email: nstrct@256dpi.ch
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - Gemfile
40
+ - Gemfile.lock
41
+ - LICENSE.txt
42
+ - README.md
43
+ - cross_platform_test
44
+ - lib/nstrct.rb
45
+ - lib/nstrct/argument.rb
46
+ - lib/nstrct/frame.rb
47
+ - lib/nstrct/instruction.rb
48
+ - lib/nstrct/version.rb
49
+ - nstrct.gemspec
50
+ - spec/protocol_spec.rb
51
+ - spec/spec_helper.rb
52
+ homepage: http://github.com/nstrct
53
+ licenses:
54
+ - MIT
55
+ metadata: {}
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 2.0.3
73
+ signing_key:
74
+ specification_version: 4
75
+ summary: a multi-purpose binary protocol for instruction interchange
76
+ test_files:
77
+ - spec/protocol_spec.rb
78
+ - spec/spec_helper.rb