oak 0.0.3 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,17 +1,5 @@
1
- source "http://rubygems.org"
2
- # Add dependencies required to use your gem here.
3
- # Example:
4
- # gem "activesupport", ">= 2.3.5"
1
+ source 'https://rubygems.org'
5
2
 
6
- # Add dependencies to develop your gem here.
7
- # Include everything needed to run rake, tests, features, etc.
8
- group :development do
9
- gem "shoulda", ">= 0"
10
- gem "bundler", "~> 1.0.0"
11
- gem "jeweler", "~> 1.6.4"
12
- gem "rcov", ">= 0"
13
- gem 'rdoc', "~> 3.11"
14
- end
15
-
16
- gem 'thor', "~> 0.14.6"
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
17
4
 
5
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016-2018 ProsperWorks, Inc.
4
+ Copyright (c) 2018 Copper, Inc.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,113 @@
1
+ #
2
+ # Makefile
3
+ #
4
+ # "make test" tests the external behavior of cli utilities which are
5
+ # not accessible to "rake test".
6
+ #
7
+ # author: jhw@prosperworks.com
8
+ # incept: 2019-09-25
9
+ #
10
+
11
+ .SUFFIXES:
12
+ SHELL := bash
13
+ DESTDIR := build
14
+
15
+ SOURCES := $(shell find . -type f -name '*.rb')
16
+ SOURCES += $(shell find . -type f -name 'Gemfile*')
17
+ SOURCES += $(shell find . -type f -name 'Rakefile')
18
+ SOURCES += $(shell find . -type f -name '.rubocop.yml')
19
+
20
+ .PHONY: all
21
+ .PHONY: test
22
+ all: test
23
+
24
+ .PHONY: clean
25
+ clean:
26
+ rm -rf $(DESTDIR)
27
+
28
+ # "make test-rake" performs our traditional Ruby Minitest suite.
29
+ #
30
+ .PHONY: test-rake
31
+ test test-rake: $(DESTDIR)/test-rake.ok
32
+ $(DESTDIR)/test-rake.ok: $(SOURCES)
33
+ @mkdir -p $(dir $@)
34
+ bundle exec rake test
35
+ @touch $@
36
+
37
+ # "make test-shell-basic" tests bin/oak.rb for loadability and basic behavior.
38
+ #
39
+ .PHONY: test-shell-basic
40
+ test test-shell-basic: $(DESTDIR)/test-shell-basic.ok
41
+ $(DESTDIR)/test-shell-basic.ok: $(SOURCES)
42
+ @mkdir -p $(dir $@)
43
+ @echo
44
+ @echo bin/oak is friendly and self-tests
45
+ @echo
46
+ bin/oak --self-test
47
+ rubydoctest bin/oak.rb
48
+ bin/oak --help
49
+ @echo
50
+ @echo bin/oak can decode its own output in some common cases.
51
+ @echo
52
+ set -o pipefail ; echo hello | bin/oak | bin/oak --mode decode-lines | diff <(echo hello) -
53
+ set -o pipefail ; cat Makefile | bin/oak --mode encode-file | bin/oak --mode decode-lines | diff Makefile -
54
+ set -o pipefail ; cat Makefile | bin/oak --mode encode-file | bin/oak --mode decode-file | diff Makefile -
55
+ set -o pipefail ; cat Makefile | bin/oak --mode encode-lines | bin/oak --mode decode-lines | diff -w Makefile -
56
+ set -o pipefail ; cat .git/index | bin/oak --mode encode-file | bin/oak --mode decode-file | diff .git/index -
57
+ @echo
58
+ @echo bin/oak has some kind of pointless crufty modes, too.
59
+ @echo
60
+ set -o pipefail ; echo | bin/oak --mode tests > /dev/null
61
+ set -o pipefail ; echo | bin/oak --mode crazy > /dev/null
62
+ @echo
63
+ @echo bin/oak passed all the basic tests.
64
+ @echo
65
+ @touch $@
66
+
67
+ # This keychain is only used for tests in this Makefile.
68
+ #
69
+ export OAK_TEST_KEYS=foo,bar
70
+ export OAK_TEST_KEY_foo=oak_3CNB_3725491808_52_RjFTQTMyX0qAlJNbIK4fwYY0kh5vNKF5mMpHK-ZBZkfFarRjVPxS_ok
71
+ export OAK_TEST_KEY_bar=oak_3CNB_201101230_52_RjFTQTMyXxbYlRcFH8JgiFNZMbnlFTAfUyvJCnXgCESpBmav_Etp_ok
72
+
73
+ # "make test-shell-encryption" tests the encryption features in bin/oak.
74
+ #
75
+ .PHONY: test-shell-encryption
76
+ test test-shell-encryption: $(DESTDIR)/test-shell-encryption.ok
77
+ $(DESTDIR)/test-shell-encryption.ok: $(SOURCES)
78
+ @mkdir -p $(dir $@)
79
+ set -o pipefail ; bin/oak --key-generate | grep '^oak_3[-_0-9a-zA-Z]*_ok$$'
80
+ set -o pipefail ; cat Makefile | bin/oak --mode encode-lines --key-chain OAK_TEST --key foo | bin/oak --mode decode-lines --key-chain OAK_TEST | diff -w Makefile -
81
+ set -o pipefail ; cat Makefile | bin/oak --mode encode-file --key-chain OAK_TEST --key bar | bin/oak --mode decode-file --key-chain OAK_TEST | diff Makefile -
82
+ set -o pipefail ; bin/oak --key-check | grep 'no --key-chain specified'
83
+ set -o pipefail ; bin/oak --key-check --key-chain BOGUS_KEY_CHAIN_TEST | grep 'BOGUS_KEY_CHAIN_TEST: no keys found'
84
+ set -o pipefail ; bin/oak --key-check --key-chain OAK_TEST | grep 'OAK_TEST: found keys: foo bar'
85
+ @echo
86
+ @echo We can recognize which key encrypted which OAK string:
87
+ @echo
88
+ echo Hello | bin/oak --mode encode-file --key-chain OAK_TEST --key foo | grep '^oak_4foo' > /dev/null
89
+ echo Hello | bin/oak --mode encode-file --key-chain OAK_TEST --key bar | grep '^oak_4bar' > /dev/null
90
+ @echo
91
+ @echo We can recode one from one key to another
92
+ @echo
93
+ echo Hello | bin/oak --mode encode-file --key-chain OAK_TEST --key foo | grep '^oak_4foo' > /dev/null
94
+ echo Hello | bin/oak --mode encode-file --key-chain OAK_TEST --key foo | bin/oak --mode recode-file --key-chain OAK_TEST --key bar | grep '^oak_4bar' > /dev/null
95
+ @echo
96
+ @echo Both are decodeable as the original source:
97
+ @echo
98
+ echo Hello | bin/oak --mode encode-file --key-chain OAK_TEST --key foo | bin/oak --key-chain OAK_TEST --mode decode-file | diff <(echo Hello) - > /dev/null
99
+ echo Hello | bin/oak --mode encode-file --key-chain OAK_TEST --key foo | bin/oak --mode recode-file --key-chain OAK_TEST --key bar | bin/oak --key-chain OAK_TEST --mode decode-file | diff <(echo Hello) - > /dev/null
100
+ @echo
101
+ @echo bin/oak passed all the encryption tests.
102
+ @echo
103
+ @touch $@
104
+
105
+ # "make test-rubocop" performs our rubocop checks.
106
+ #
107
+ .PHONY: test-rubocop
108
+ test test-rubocop: $(DESTDIR)/test-rubocop.ok
109
+ $(DESTDIR)/test-rubocop.ok: $(SOURCES)
110
+ @mkdir -p $(dir $@)
111
+ bundle exec rubocop --version
112
+ bundle exec rubocop --display-cop-names --display-style-guide
113
+ @touch $@
data/README.md CHANGED
@@ -1,34 +1,174 @@
1
- ## oak
1
+ # oak
2
2
 
3
- Oak is a very simple tool that assists making rails application Open Source ready.
3
+ OAK is a serialization format for Ruby primitives which supports
4
+ performance experimentation via a limited suite of built-in
5
+ compression, checksumming, and encryption options.
4
6
 
5
- **ONLY WORKS ON MAC OS X AND LINUX/BSD**
7
+ OAK supports built-in switchability between common algorithms:
6
8
 
7
- When pushlishing your rails app to a public repository, it is often neccessary to find a way protecting you secret token and other private information.
9
+ - Checksumming with ZLIB crc32(), SHA1, or none.
10
+ - Compression with LZ4, ZLIB, BZIP2k, LZMA, or none.
11
+ - ASCII armor with base64, or none.
8
12
 
9
- Oak helps you on these tasks by running
13
+ OAK also supports optional AES-256-GCM encryption with full 96-bit
14
+ random IVs and 16 byte auth tags.
10
15
 
11
- oak setup
16
+ OAK is also a compact serialization format for primitive Ruby objects
17
+ like String, Symbol, Integer, Float, Hash, and Array. OAK
18
+ serialization supports cyclical structures and distinguishes between
19
+ String and Symbol, but does not support user-defined types.
12
20
 
13
- Then your private information will be stored in a file called 'config.yml', and automaticly ignored by git master branch. And on another branch named 'deploy' (which won't be pushed to the public repository) the config.yml file is included.
14
- So you can modify your apps on master branch as usual, and call
21
+ In ProsperWorks/ALI, we use OAK at Copper, OAK has found use cases in:
15
22
 
16
- git push <production_repo> deploy:master
17
-
18
- to push your local deploy branch to your production repository's master branch.
23
+ - Volatile Redis cache entries, where dynamic control of time-space
24
+ tradeoffs shaves down hosting costs.
25
+ - Durable cold archives of larger user data.
26
+ - Encrypting runtime secrets.
19
27
 
20
- ## Contributing to oak
21
-
22
- * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
23
- * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
24
- * Fork the project
25
- * Start a feature/bugfix branch
26
- * Commit and push until you are happy with your contribution
27
- * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
28
- * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
28
+ OAK is not human-readable. It is intended to be an interchange or
29
+ archive format, not as a human interface.
29
30
 
30
- ## Copyright
31
+ Consider OAK if you are dealing with content at scale and are not
32
+ quite happy with JSON, YAML, XML, or Ruby's Marshal format. Disregard
33
+ OAK your needs are all met by JSON.
31
34
 
32
- Copyright (c) 2011 Huang Wei. See LICENSE.txt for
33
- further details.
35
+ ## Using in Ruby
34
36
 
37
+ ```
38
+ 2.1.6 :001 > require 'oak'
39
+ => true
40
+ 2.1.6 :002 > h = { 'one' => 1, 'array' => [ true, false ] }
41
+ => {"one"=>1, "array"=>[true, false]}
42
+ 2.1.6 :003 > OAK.encode(h)
43
+ => "oak_3CNB_2774455364_51_RjdIMl8xXzJfM180U1UzX29uZUkxU1U1X2FycmF5QTJfNV82dGY_ok"
44
+ 2.1.6 :004 > OAK.encode(h,format: :none)
45
+ => "oak_3CNN_2774455364_38_F7H2_1_2_3_4SU3_oneI1SU5_arrayA2_5_6tf_ok"
46
+ 2.1.6 :005 > OAK.encode(h, compression: :bzip2, force: true)
47
+ => "oak_3CBB_2774455364_106_QlpoOTFBWSZTWag9FGUAAAaPgD-AIWAKAKMBlCAgADFGjIGjTI0Ip-lPRGynomJ-qPMBxIQDw5vmY9SVFxhFj7ZLMSPxdyRThQkKg9FGUA_ok"
48
+ 2.1.6 :006 > OAK.encode(h, compression: :bzip2)
49
+ => "oak_3CNB_2774455364_51_RjdIMl8xXzJfM180U1UzX29uZUkxU1U1X2FycmF5QTJfNV82dGY_ok"
50
+ 2.1.6 :007 > OAK.decode('oak_3CNB_2774455364_51_RjdIMl8xXzJfM180U1UzX29uZUkxU1U1X2FycmF5QTJfNV82dGY_ok')
51
+ => {"one"=>1, "array"=>[true, false]}
52
+ ```
53
+
54
+ ## Using in Ruby for Encryption
55
+
56
+ ```
57
+ 2.1.6 :001 > require 'oak'
58
+ => true
59
+ 2.1.6 :002 > key_chain = OAK::KeyChain.new({ 'a' => OAK::Key.new(OAK.random_key), 'b' => OAK::Key.new(OAK.random_key) })
60
+ => #<OAK::KeyChain:0x007faa700b8458 @keys={"a"=>#<OAK::Key:0x007faa700b89a8 @key=ELIDED>, "b"=>#<OAK::Key:0x007faa700b86d8 @key=ELIDED>}>
61
+ 2.1.6 :003 > OAK.encode("Hello, World!",key_chain: key_chain, key: 'a')
62
+ => "oak_4a_B82_4iauuiFq7XQvcDCwmmXhDxL_Wp3_T765qM-S094uNJ4xtj_DxQuXGSMmqlcxPXz-_cYZdE6bFbEAQCHpiQ_ok"
63
+ 2.1.6 :004 > OAK.encode("Hello, World!",key_chain: key_chain, key: 'a')
64
+ => "oak_4a_B82_YeeAEOLPlGchlDkoSnif7G38uJAPaNQ2ozsx6Mcb7PybpsL-ljVmGa5sRbgaqFw4R5iOXNw_sOesolTB4g_ok"
65
+ 2.1.6 :005 > OAK.encode("Hello, World!",key_chain: key_chain, key: 'b')
66
+ => "oak_4b_B82_HA1O3v5UrpV81UC0fAaUQ_8tsLUxbMG6bMupcQaZNKMU3XL3Tz9zj8TaVb4nvv3s0UhxFg3q9lmFIplvnQ_ok"
67
+ 2.1.6 :006 > OAK.decode("oak_4b_B82_HA1O3v5UrpV81UC0fAaUQ_8tsLUxbMG6bMupcQaZNKMU3XL3Tz9zj8TaVb4nvv3s0UhxFg3q9lmFIplvnQ_ok",key_chain: key_chain)
68
+ => "Hello, World!
69
+ 2.1.6 :007 > OAK.decode("oak_4b_B82_HA1O3v5UrpV81UC0fAaUQ_8tsLUxbMG6bMupcQaZNKMU3XL3Tz9zj8TaVb4nvv3s0UhxFg3q9lmFIplvnQ_ok")
70
+ OAK::CantTouchThisStringError: key b but no key_chain
71
+ from ...
72
+ ```
73
+
74
+ ## Using in Shell
75
+
76
+ Non-encrypted OAK default to OAK3. Unencrypted OAK4 is available
77
+ optionally. They are generally the same size and equivalent:
78
+ unencrypted OAK4 is generally only used for debugging and getting a
79
+ peek at what happens "under the encryption".
80
+
81
+ ```
82
+ $ echo hello | bin/oak --mode encode-file
83
+ oak_3CNB_911092726_16_RjFTVTZfaGVsbG8K_ok
84
+ $ echo oak_3CNB_911092726_16_RjFTVTZfaGVsbG8K_ok | bin/oak --mode decode-file
85
+ hello
86
+ $ echo hello | bin/oak --mode encode-file --compression lz4 --force true
87
+ oak_3C4B_911092726_19_DMBGMVNVNl9oZWxsbwo_ok
88
+ $ echo oak_3C4B_911092726_19_DMBGMVNVNl9oZWxsbwo_ok | bin/oak --mode decode-file
89
+ hello
90
+ $ echo hello | bin/oak.rb --mode encode-lines --redundancy none --format none
91
+ oak_3NNN_0_11_F1SU5_hello_ok
92
+ $ echo hello | bin/oak.rb --mode encode-lines --redundancy none --format none --force-oak-4
93
+ oak_4_N15_NN0_F1SU5_hello_ok
94
+ ```
95
+
96
+ ## Using in Shell for Encryption
97
+
98
+ Encryption is supported by OAK4.
99
+
100
+ ```
101
+ $ bin/oak.rb --key-generate
102
+ oak_3CNB_2975186575_52_RjFTQTMyX00du8vD8WAikhLNgdnaOYtQV6uqyNqRz6modiEcJHOl_ok
103
+ $ bin/oak.rb --key-generate
104
+ oak_3CNB_1324948677_52_RjFTQTMyXytCueDDTpEOusKkPMANgaA9zsJuvOend5DCIJWwJdjC_ok
105
+ $ export OAK_TEST_KEYS=foo,bar
106
+ $ export OAK_TEST_KEY_foo=oak_3CNB_2975186575_52_RjFTQTMyX00du8vD8WAikhLNgdnaOYtQV6uqyNqRz6modiEcJHOl_ok
107
+ $ export OAK_TEST_KEY_bar=oak_3CNB_1324948677_52_RjFTQTMyXytCueDDTpEOusKkPMANgaA9zsJuvOend5DCIJWwJdjC_ok
108
+ $ echo hello | bin/oak.rb --mode encode-lines --redundancy none --key-chain OAK_TEST --key foo
109
+ oak_4foo_B58_PhG1qWHfosOOWDgqMhVoZlEn6F16XC6KuL_1zN1aLWMmcZZgJ2Dz5XR-ag_ok
110
+ $ echo hello | bin/oak.rb --mode encode-lines --redundancy none --key-chain OAK_TEST --key foo
111
+ oak_4foo_B58_ms11iWDHrmwFJwGpNEsWMIXYfapO96e7yvfk5r8G-F1gRzt62FS_JFQbvw_ok
112
+ $ echo hello | bin/oak.rb --mode encode-lines --redundancy none --key-chain OAK_TEST --key bar
113
+ oak_4bar_B58_kV6FIE30v6xgdKwyzdmpxVzNCU2eWjt7ZiZTWUHsQxXG3cC8u0-VoE0hmQ_ok
114
+ $ echo oak_4foo_B58_PhG1qWHfosOOWDgqMhVoZlEn6F16XC6KuL_1zN1aLWMmcZZgJ2Dz5XR-ag_ok | bin/oak.rb --mode decode-lines --key-chain OAK_TEST
115
+ hello
116
+ $ echo oak_4foo_B58_ms11iWDHrmwFJwGpNEsWMIXYfapO96e7yvfk5r8G-F1gRzt62FS_JFQbvw_ok | bin/oak.rb --mode decode-lines --key-chain OAK_TEST
117
+ hello
118
+ $ echo oak_4bar_B58_kV6FIE30v6xgdKwyzdmpxVzNCU2eWjt7ZiZTWUHsQxXG3cC8u0-VoE0hmQ_ok | bin/oak.rb --mode decode-lines --key-chain OAK_TEST
119
+ hello
120
+ ```
121
+ OAK4 supports one and only one encryption algorithm and mode of
122
+ operation:
123
+
124
+ - AES-256-GCM
125
+ - 128 bits of security
126
+ - 256-bit keys (32 bytes)
127
+ - 96-bit IVs (12 bytes)
128
+ - 128-bit auth_tags (16 bytes)
129
+ - Random IV ("Initialization Vector") used for each encryption operation.
130
+ - All headers are authenticated.
131
+ - All headers which are not required for decryption are encrypted.
132
+
133
+ This is the only encryption option supported by OAK4.
134
+
135
+ Because the GCM ([Galois/Counter Mode
136
+ ](https://en.wikipedia.org/wiki/Galois%2FCounter_Mode)) mode of
137
+ operation is an [authenticated
138
+ encryption](https://en.wikipedia.org/wiki/Authenticated_encryption),
139
+ use of `--redundancy none` with encrypted OAK strings is recommended.
140
+ The authentication of GCM is more than adequate to detect accidental
141
+ transmission errors. This recommendation may become the default in a
142
+ future version.
143
+
144
+
145
+ ## Further Reading
146
+
147
+ For more details.
148
+
149
+ - [Changelog](CHANGELOG.md)
150
+ - [Design Desiderata](DESIDERATA.md)
151
+
152
+ ## TODO: packaging
153
+
154
+ - import design docs here
155
+ - https://docs.google.com/document/d/10HVWuQzCw1Whc-czDChwsWPEZRLfyPS7F-dkjHWsIs4
156
+ - https://docs.google.com/document/d/1J7GBEJUPI3UeftJ4C-w3pbBUzk1pU0Sr6zcXufEi7NI
157
+ - https://docs.google.com/document/d/1SeOO18uqdDtHuB8tZ4-_2sql0Yiaco5J1PWdiu6gmAY
158
+ - edit comments down
159
+ - rdoc
160
+
161
+ ## Possible Future Directions for the Format
162
+
163
+ - Float representation.
164
+ - Manifest precision?
165
+ - Limited precision?
166
+ - But somehow do better than just `Float#to_s`!
167
+ - Streamability and embedability.
168
+ - Support encoders and decoders which have only fixed-size buffers.
169
+ - Portability.
170
+ - Was support for Symbols distinct from Strings too Ruby-esque?
171
+ - Native implementation.
172
+ - Error-correction coding.
173
+ - There seem to be little or no "standard" algorithms out there, at
174
+ least not as used by the Ruby community.
data/Rakefile CHANGED
@@ -1,53 +1,12 @@
1
- # encoding: utf-8
2
-
3
1
  require 'rubygems'
4
- require 'bundler'
5
- begin
6
- Bundler.setup(:default, :development)
7
- rescue Bundler::BundlerError => e
8
- $stderr.puts e.message
9
- $stderr.puts "Run `bundle install` to install missing gems"
10
- exit e.status_code
11
- end
12
- require 'rake'
13
-
14
- require 'jeweler'
15
- Jeweler::Tasks.new do |gem|
16
- # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
- gem.name = "oak"
18
- gem.homepage = "http://github.com/imonyse/oak"
19
- gem.license = "MIT"
20
- gem.summary = %Q{Oak is a tool that helps making your newly created rails app open source ready.}
21
- gem.description = %Q{When pushing your rails app to a public repository, it is often neccessary to find a way protecting you secret token and other private information. Oak helps you on these tasks and assumes you use git as the version control system}
22
- gem.email = "imonyse@gmail.com"
23
- gem.authors = ["Huang Wei"]
24
- # dependencies defined in Gemfile
25
- end
26
- Jeweler::RubygemsDotOrgTasks.new
27
-
2
+ require 'bundler/setup'
3
+ require 'bundler/gem_tasks'
28
4
  require 'rake/testtask'
29
- Rake::TestTask.new(:test) do |test|
30
- test.libs << 'lib' << 'test'
31
- test.pattern = 'test/**/test_*.rb'
32
- test.verbose = true
33
- end
34
5
 
35
- require 'rcov/rcovtask'
36
- Rcov::RcovTask.new do |test|
37
- test.libs << 'test'
38
- test.pattern = 'test/**/test_*.rb'
39
- test.verbose = true
40
- test.rcov_opts << '--exclude "gems/*"'
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/*_test.rb']
41
10
  end
42
11
 
43
12
  task :default => :test
44
-
45
- require 'rdoc/task'
46
- RDoc::Task.new do |rdoc|
47
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
-
49
- rdoc.rdoc_dir = 'rdoc'
50
- rdoc.title = "oak #{version}"
51
- rdoc.rdoc_files.include('README*')
52
- rdoc.rdoc_files.include('lib/**/*.rb')
53
- end
data/bin/oak CHANGED
@@ -1,6 +1,245 @@
1
1
  #!/usr/bin/env ruby
2
+ #
3
+ # oak.rb: cli driver for encoding strings in the OAK format.
4
+ #
5
+ # author: jhw@prosperworks.com
6
+ # incept: 2016-03-05
7
+ #
2
8
 
3
- $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
- require 'oak'
9
+ require_relative '../lib/oak.rb'
10
+ require 'optimist'
5
11
 
6
- Oak.start
12
+ OLD_ARGV = ARGV.dup # ARGV is consumed by Optimist but we use later.
13
+ OPTS = Optimist.options do
14
+ banner "#{$0} cli driver for OAK"
15
+ banner <<-OPTIMIST_EXAMPLES
16
+ Examples:
17
+ $ echo hello | bin/oak.rb
18
+ oak_3CNB_1944283675_15_RjFTVTVfaGVsbG8_ok
19
+ $ (echo hello ; echo world) | bin/oak.rb
20
+ oak_3CNB_1944283675_15_RjFTVTVfaGVsbG8_ok
21
+ oak_3CNB_2139413982_15_RjFTVTVfd29ybGQ_ok
22
+ $ (echo hello ; echo world) | bin/oak.rb --compression zlib --force
23
+ oak_3CZB_1944283675_26_eJxzMwwONY3PSM3JyQcAFF4DyA_ok
24
+ oak_3CZB_2139413982_26_eJxzMwwONY0vzy_KSQEAFNgD3A_ok
25
+ $ (echo hello ; echo world) | bin/oak.rb --format none
26
+ oak_3CNN_1944283675_11_F1SU5_hello_ok
27
+ oak_3CNN_2139413982_11_F1SU5_world_ok
28
+ $ (echo hello ; echo world) | bin/oak.rb | bin/oak.rb --mode decode-lines
29
+ hello
30
+ world
31
+ OPTIMIST_EXAMPLES
32
+ banner "Options:"
33
+ version "#{$0} #{OAK::VERSION}"
34
+ opt :redundancy, 'redundancy', :default => 'crc32'
35
+ opt :format, 'format', :default => 'base64'
36
+ opt :compression, 'compression', :default => 'none'
37
+ opt :force, 'compress even if bigger', :default => false
38
+ opt :mode, 'mode', :default => 'encode-lines'
39
+ opt :key_chain, 'key chain env name', :type => :string
40
+ opt :key, 'encrypt key name', :type => :string
41
+ opt :key_check, 'check available keys', :default => false
42
+ opt :key_generate, 'generate new key', :default => false
43
+ opt :force_oak_4, 'force OAK_4 even unencrypted', :default => false
44
+ opt :eigen, 'calc eigenratio', :type => :int
45
+ opt :self_test, 'self-test only', :default => false
46
+ opt :help, 'show this help'
47
+ end
48
+ Optimist::die :eigen, "must be non-negative" if OPTS[:eigen] && OPTS[:eigen] < 0
49
+
50
+ oak_opts = {}
51
+ oak_opts[:redundancy] = OPTS[:redundancy]
52
+ oak_opts[:compression] = OPTS[:compression]
53
+ oak_opts[:force] = OPTS[:force]
54
+ oak_opts[:format] = OPTS[:format]
55
+ oak_opts[:key_chain] = OAK.parse_env_chain(ENV,OPTS[:key_chain])
56
+ oak_opts[:key] = OPTS[:key]
57
+ oak_opts[:force_oak_4] = OPTS[:force_oak_4]
58
+
59
+ if !OAK::REDUNDANCY_2_CODE.keys.include?(oak_opts[:redundancy])
60
+ Optimist::die :redundancy, "bogus #{OPTS[:redundancy]}"
61
+ end
62
+ if !OAK::COMPRESSION_2_CODE.keys.include?(oak_opts[:compression])
63
+ Optimist::die :compression, "bogus #{OPTS[:compression]}"
64
+ end
65
+ cool_formats = OAK::FORMAT_2_CODE.keys
66
+ if !cool_formats.include?(oak_opts[:format])
67
+ Optimist::die :format, "bogus #{OPTS[:format]} not in #{cool_formats}"
68
+ end
69
+
70
+ =begin
71
+
72
+ doctest: simple transcoding
73
+ >> OAK::decode(OAK::encode([1,"2",3.000001]))
74
+ => [1,"2",3.000001]
75
+ >> OAK::decode(OAK::encode({foo: "bar"}))
76
+ => {foo: "bar"}
77
+ >> OAK::decode(OAK::encode({foo: :bar}))
78
+ => {foo: :bar}
79
+ >> OAK::decode(OAK::encode("Hello, World!"))
80
+ => "Hello, World!"
81
+ >> OAK::decode(OAK::encode("Hello, World!", format: :none, redundancy: :none))
82
+ => "Hello, World!"
83
+
84
+ doctest: stability of encoding
85
+ >> OAK::decode("oak_3NNB_0_30_RjNIMV8xXzJZQTNfZm9vU1UzX2Jhcg_ok")
86
+ => {:foo=>"bar"}
87
+ >> OAK::encode(1, format: :base64, redundancy: :none)
88
+ => "oak_3NNB_0_6_RjFJMQ_ok"
89
+ >> OAK::encode(1, format: :base64, redundancy: :crc32)
90
+ => "oak_3CNB_3405226796_6_RjFJMQ_ok"
91
+ >> OAK::encode(1, format: :none, redundancy: :crc32)
92
+ => "oak_3CNN_3405226796_4_F1I1_ok"
93
+ >> hello_utf8 = "Hello, World!".force_encoding('UTF-8')
94
+ => "Hello, World!"
95
+ >> OAK::encode(hello_utf8, format: :base64, redundancy: :none)
96
+ => "oak_3NNB_0_27_RjFTVTEzX0hlbGxvLCBXb3JsZCE_ok"
97
+ >> OAK::encode(hello_utf8, format: :none, redundancy: :crc32)
98
+ => "oak_3CNN_2351984628_20_F1SU13_Hello, World!_ok"
99
+
100
+ Note above I used force_encoding('UTF-8') after discovering that with
101
+ Ruby 2.1.6 on Mac I get Encoding.default_encoding is UTF-8, but with
102
+ Ruby 2.1.6 on Linux I get Encoding.default_encoding is US-ASCII!
103
+
104
+ =end
105
+
106
+ if __FILE__ == $0
107
+ if OPTS[:self_test]
108
+ require 'rubydoctest'
109
+ exit RubyDocTest::Runner.new(File.read(__FILE__), __FILE__).run ? 0 : 1
110
+ end
111
+ if OPTS[:key_check]
112
+ if !OPTS[:key_chain]
113
+ puts "no --key-chain specified"
114
+ else
115
+ keys = oak_opts[:key_chain].keys.keys
116
+ if 0 == keys.size
117
+ puts "#{OPTS[:key_chain]}: no keys found"
118
+ else
119
+ puts "#{OPTS[:key_chain]}: found keys: #{keys.join(' ')}"
120
+ end
121
+ end
122
+ end
123
+ if OPTS[:key_generate]
124
+ STDOUT.puts OAK.encode(OAK.random_key)
125
+ exit 0
126
+ end
127
+ if !$stdin.tty?
128
+ if OPTS[:eigen]
129
+ prev = STDIN.read
130
+ puts "input: %d" % prev.size
131
+ OPTS[:eigen].times do |i|
132
+ oak = OAK.encode(prev,oak_opts)
133
+ psize = prev.size
134
+ wsize = oak.size
135
+ ratio = 1.0 * wsize / psize
136
+ puts " iter %3d: %4d => %4d ratio %.2f" % [i,psize,wsize,ratio]
137
+ prev = oak
138
+ end
139
+ exit 0
140
+ end
141
+ unhappiness = 0
142
+ case OPTS[:mode]
143
+ when 'cat'
144
+ ARGF.each_line.map(&:strip).each do |line|
145
+ puts line
146
+ end
147
+ when 'encode-lines'
148
+ ARGF.each_line.map(&:strip).each do |line|
149
+ puts OAK.encode(line,oak_opts)
150
+ end
151
+ when 'decode-lines'
152
+ ARGF.each_line.map(&:strip).each do |line|
153
+ puts OAK.decode(line,oak_opts)
154
+ end
155
+ when 'encode-file'
156
+ puts OAK.encode(STDIN.read,oak_opts)
157
+ when 'decode-file'
158
+ STDOUT.write OAK.decode(STDIN.read.strip,oak_opts)
159
+ when 'recode-file'
160
+ puts OAK.encode(OAK.decode(STDIN.read,oak_opts),oak_opts)
161
+ when 'crazy'
162
+ #
163
+ # --mode crazy prints out a sample of OAK strings for various
164
+ # challenging cases.
165
+ #
166
+ cycle_a = ['cycle_a','TBD']
167
+ cycle_b = ['cycle_b',cycle_a]
168
+ cycle_a[1] = cycle_b
169
+ dag_c = ['dag_c']
170
+ dag_b = ['dag_b',dag_c]
171
+ dag_a = ['dag_a',dag_b,dag_c]
172
+ [
173
+ 'hello',
174
+ ['hello'] + ['hello',:hello] * 2,
175
+ {1=>'a','b'=>2,[]=>3,''=>4,{}=>5,nil=>6},
176
+ ['x','x','x','x','x','x','x','x','x','x','x','x','x'],
177
+ ['x'] * 13,
178
+ cycle_a,
179
+ dag_a,
180
+ [1,-123,0.12,-0.123,Float::NAN,-Float::INFINITY,3.14159265358979],
181
+ ].each do |obj|
182
+ oak = OAK.encode(
183
+ obj,
184
+ redundancy: :crc32,
185
+ format: :none,
186
+ compression: :none,
187
+ )
188
+ puts ""
189
+ puts "obj: #{obj}"
190
+ puts " oak: #{oak}"
191
+ begin
192
+ dec = OAK.decode(oak,oak_opts)
193
+ if dec != obj
194
+ if !dec.is_a?(Float) && !enc.is_a?(Float) && !dec.nan? && !enc.nan?
195
+ unhappiness += 1
196
+ puts " BAD: #{dec}"
197
+ end
198
+ end
199
+ rescue OAK::CantTouchThisStringError => ex
200
+ puts " BAD: #{ex.message}: #{ex.backtrace_locations[0]}"
201
+ unhappiness += 1
202
+ end
203
+ end
204
+ when 'tests'
205
+ [
206
+ [1,2,3],
207
+ {:foo=>'foo','foo'=>['x']*10},
208
+ -1,
209
+ Float::NAN,
210
+ nil,
211
+ ].each do |obj|
212
+ puts " #{obj} => ["
213
+ key_chain = OAK::KeyChain.new(
214
+ { 'l0ng3r' => OAK::Key.new('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') }
215
+ )
216
+ [
217
+ {redundancy: :none, format: :none, compression: :none },
218
+ {redundancy: :none, format: :base64,compression: :lz4, force: true},
219
+ {redundancy: :crc32,format: :base64,compression: :zlib, force: true},
220
+ {redundancy: :crc32,format: :base64,compression: :bzip2,force: true},
221
+ {redundancy: :sha1, format: :base64,compression: :lzma, force: true},
222
+ {key_chain: key_chain,force_oak_4: true,format: :none, },
223
+ {key_chain: key_chain,force_oak_4: true, },
224
+ {key_chain: key_chain,key: 'l0ng3r', },
225
+ ].each do |opts|
226
+ oak = OAK.encode(obj,opts)
227
+ puts " '#{oak}',"
228
+ dec = OAK.decode(oak,opts)
229
+ if dec != obj
230
+ if !dec.is_a?(Float) && !enc.is_a?(Float) && !dec.nan? && !enc.nan?
231
+ unhappiness += 1
232
+ end
233
+ end
234
+ end
235
+ puts " ],"
236
+ end
237
+ else
238
+ Optimist::die :mode, "bogus mode #{OPTS[:mode]}"
239
+ end
240
+ if unhappiness > 0
241
+ puts "unhappiness: #{unhappiness}"
242
+ end
243
+ exit unhappiness
244
+ end
245
+ end