base85 0.1.0 → 0.3.0
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/.rubocop.yml +12 -0
- data/CHANGELOG.md +10 -1
- data/LICENSE.txt +1 -1
- data/README.md +62 -6
- data/Rakefile +10 -0
- data/exe/base85 +6 -0
- data/lib/base85/ascii85.rb +57 -0
- data/lib/base85/cli.rb +196 -0
- data/lib/base85/module_methods.rb +86 -0
- data/lib/base85/rfc1924.rb +38 -0
- data/lib/base85/standard.rb +18 -70
- data/lib/base85/version.rb +2 -1
- data/lib/base85/z85.rb +37 -0
- data/lib/base85.rb +27 -1
- metadata +13 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cbae229dec12dca96d21882da5944e4370e6f4d3096e85dad9ce0183a21c18d6
|
|
4
|
+
data.tar.gz: 8bfb829e5a5b61d5f6169940a55d74cc1534699350a2c563d0fe3feb51373d67
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 894e13e4725732b656aa9b97194c7e81ef0fc9b87d4fa7d255e23762586a88b0396da955004de092677c5423de6384f7faf23fcc6b4c093c6fd5e82a5ada6cdd
|
|
7
|
+
data.tar.gz: f10eb21ea204e722f99b6b2c8f08fc7147bb48737b77b91c64cd9db95b72ad8925cd61f4ca380aedba01aa0390c556a521c61de54786049564fc99cd65da1aab
|
data/.rubocop.yml
CHANGED
|
@@ -10,9 +10,21 @@ AllCops:
|
|
|
10
10
|
Layout/LineLength:
|
|
11
11
|
Max: 120
|
|
12
12
|
|
|
13
|
+
Metrics/AbcSize:
|
|
14
|
+
Max: 18
|
|
15
|
+
|
|
16
|
+
Metrics/ClassLength:
|
|
17
|
+
Enabled: false
|
|
18
|
+
|
|
19
|
+
Metrics/MethodLength:
|
|
20
|
+
Max: 20
|
|
21
|
+
|
|
13
22
|
RSpec/DescribedClass:
|
|
14
23
|
Enabled: false
|
|
15
24
|
|
|
25
|
+
RSpec/MultipleExpectations:
|
|
26
|
+
Max: 2
|
|
27
|
+
|
|
16
28
|
Style/RaiseArgs:
|
|
17
29
|
Enabled: false
|
|
18
30
|
|
data/CHANGELOG.md
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
[](https://badge.fury.io/rb/base85)
|
|
2
|
+
|
|
1
3
|
# Base85
|
|
2
4
|
|
|
3
|
-
Base85 is a pure Ruby gem to encode/decode data using Base85
|
|
5
|
+
Base85 is a pure Ruby gem to encode/decode data using Base85 encodings.
|
|
6
|
+
|
|
7
|
+
It handles mulitple alphabets:
|
|
8
|
+
|
|
9
|
+
* standard (original) one,
|
|
10
|
+
* Ascii85 (Adobe version, very similar to standard one but with start and end markers, and a special 'z' character for 4 null bytes),
|
|
11
|
+
* Z85 from [ZeroMQ](https://github.com/zeromq/libzmq),
|
|
12
|
+
* [RFC1924](https://www.rfc-editor.org/rfc/rfc1924).
|
|
4
13
|
|
|
5
14
|
## Installation
|
|
6
15
|
|
|
@@ -14,12 +23,50 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
|
14
23
|
|
|
15
24
|
## Usage
|
|
16
25
|
|
|
26
|
+
### CLI
|
|
27
|
+
|
|
28
|
+
`base85` command line tool may be used to decode/encode base85 dialects on command line.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Encode data from STDIN to STDOUT
|
|
32
|
+
echo "text to encode" | base85
|
|
33
|
+
# Encode using given alphabet (standard (default one), z85, ascii85 or rfc1924)
|
|
34
|
+
echo "text to encode" | base85 -a z85
|
|
35
|
+
# Output data is wrapped to 72 columns by default (as do base64)
|
|
36
|
+
# To change this value, use -w option (0 to deactivate wrapping)
|
|
37
|
+
echo "text to encode" | base85 -a rfc1924 -w 0
|
|
38
|
+
|
|
39
|
+
# Encode file to STDOUT
|
|
40
|
+
base85 -a ascii85 -w 40 myfile
|
|
41
|
+
|
|
42
|
+
# Decode data from STDIN to STDOUT
|
|
43
|
+
echo "aaaaa" | base85 -d
|
|
44
|
+
# Encode using given alphabet (standard (default one), z85, ascii85 or rfc1924)
|
|
45
|
+
echo "aaaaa" | base85 -d -a z85
|
|
46
|
+
|
|
47
|
+
# Decode file to STDOUT
|
|
48
|
+
base85 -d -a rfc1924 myfile
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Ruby library
|
|
52
|
+
|
|
17
53
|
To encode a string:
|
|
18
54
|
|
|
19
55
|
```ruby
|
|
20
56
|
require "base85"
|
|
21
57
|
|
|
58
|
+
# Use standard alphabet
|
|
22
59
|
Base85.encode("test1") #=> "FCfN80`"
|
|
60
|
+
Base85.encode("test1", alphabet: :standard) #=> "FCfN80`"
|
|
61
|
+
|
|
62
|
+
# Use Ascii85 alphabet
|
|
63
|
+
Base85.encode("test1", alphabet: :ascii85) #=> "<~FCfN80`~>"
|
|
64
|
+
|
|
65
|
+
# Use Z85 alphabet
|
|
66
|
+
Base85.encode("test1", alphabet: :z85) #=> "By/Jnf-"
|
|
67
|
+
|
|
68
|
+
# Use RFC1924 alphabet
|
|
69
|
+
Base85.encode("test1", alphabet: :rfc1924) #=> "bY*jNF#"
|
|
23
70
|
```
|
|
24
71
|
|
|
25
72
|
To decode a string:
|
|
@@ -27,14 +74,23 @@ To decode a string:
|
|
|
27
74
|
```ruby
|
|
28
75
|
require "base85"
|
|
29
76
|
|
|
30
|
-
|
|
31
|
-
|
|
77
|
+
# Use standard alphabet
|
|
78
|
+
Base85.decode("ASu!rA7]9") #=> "encoded"
|
|
79
|
+
Base85.decode("ASu!rA7]9", alphabet: :standard) #=> "encoded"
|
|
32
80
|
|
|
33
|
-
|
|
81
|
+
# Use Ascii85 alphabet
|
|
82
|
+
Base85.decode("<~ASu!rA7]9~>", alphabet: :ascii85) #=> "encoded"
|
|
83
|
+
# Special z character
|
|
84
|
+
Base85.decode("<~z~>", alphabet: :ascii85) #=> "\u0000\u0000\u0000\u0000"
|
|
85
|
+
# Also accept string without markers
|
|
86
|
+
Base85.decode("ASu!rA7]9", alphabet: :ascii85) #=> "encoded"
|
|
34
87
|
|
|
35
|
-
|
|
88
|
+
# Use Z85 alphabet
|
|
89
|
+
Base85.decode("wO#0@wmYo", alphabet: :z85) #=> "encoded"
|
|
36
90
|
|
|
37
|
-
|
|
91
|
+
# Use RFC1924 alphabet
|
|
92
|
+
Base85.decode("Wo~0{WMyO", alphabet: :rfc1924) #=> "encoded"
|
|
93
|
+
```
|
|
38
94
|
|
|
39
95
|
## Contributing
|
|
40
96
|
|
data/Rakefile
CHANGED
|
@@ -10,3 +10,13 @@ require "rubocop/rake_task"
|
|
|
10
10
|
RuboCop::RakeTask.new
|
|
11
11
|
|
|
12
12
|
task default: %i[spec rubocop]
|
|
13
|
+
|
|
14
|
+
begin
|
|
15
|
+
require "yard"
|
|
16
|
+
|
|
17
|
+
YARD::Rake::YardocTask.new do |t|
|
|
18
|
+
t.files = ["lib/**/*.rb", "-", "README.md", "LICENSE.txt"]
|
|
19
|
+
t.options = %w[--no-private]
|
|
20
|
+
end
|
|
21
|
+
rescue LoadError # rubocop:disable Lint/SuppressedException
|
|
22
|
+
end
|
data/exe/base85
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Base85
|
|
4
|
+
# Ascii85 base85 encoding. This encoding uses +!+ to +u+ ASCII characters to encode data. +z+ is used to represent
|
|
5
|
+
# 32-bit 0 value. The encoded string is surrounded by +<~+ and +~>+ markers.
|
|
6
|
+
# @author sd77
|
|
7
|
+
module Ascii85
|
|
8
|
+
extend ModuleMethods
|
|
9
|
+
|
|
10
|
+
# ASCII value for character +!+
|
|
11
|
+
BASE_ASCII_VALUE = "!".ord
|
|
12
|
+
# Ascii85 defines +z+ as a special character
|
|
13
|
+
SPECIAL_Z = true
|
|
14
|
+
|
|
15
|
+
# ASCII85 start marker
|
|
16
|
+
START_MARKER = "<~"
|
|
17
|
+
# ASCII85 end marker
|
|
18
|
+
END_MARKER = "~>"
|
|
19
|
+
|
|
20
|
+
# Encode a value (0..84 integer) to a Ascii85 character
|
|
21
|
+
# @param [Integer] value
|
|
22
|
+
# @return [String]
|
|
23
|
+
def self.encode_value(value)
|
|
24
|
+
(BASE_ASCII_VALUE + value).chr
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Encode data using Ascii85 encoding
|
|
28
|
+
# @param [String] data
|
|
29
|
+
# @return [String] encoded data, surrounded by +<~+ and +~>+ markers
|
|
30
|
+
def self.encode(data)
|
|
31
|
+
"#{START_MARKER}#{super}#{END_MARKER}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Decode a character from Ascii85 and give its index in alphabet
|
|
35
|
+
# @param [String] char a acharacter from Ascii85 alphabet
|
|
36
|
+
# @return [Integer] index of this character in alphabet
|
|
37
|
+
# @raise DecodeError +char+ is not known in alphabet
|
|
38
|
+
def self.decode_char(char)
|
|
39
|
+
case char
|
|
40
|
+
when "!".."u" # rubocop:disable Lint/MixedCaseRange
|
|
41
|
+
char.ord - BASE_ASCII_VALUE
|
|
42
|
+
else
|
|
43
|
+
raise DecodeError, "unknown character '#{char}'"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Decode data using Ascii85 encoding
|
|
48
|
+
# @param [String] data Encoded string, may be prefixed by +<~+ and suffixed by +~>+
|
|
49
|
+
# @return [String] Decoded data
|
|
50
|
+
# @raise [DecodeError] unknown character or invalid tuple
|
|
51
|
+
def self.decode(data)
|
|
52
|
+
start = data.start_with?(START_MARKER) ? 2 : 0
|
|
53
|
+
stop = data.end_with?(END_MARKER) ? -3 : -1
|
|
54
|
+
super(data[start..stop])
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/base85/cli.rb
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require_relative "../base85"
|
|
5
|
+
|
|
6
|
+
module Base85
|
|
7
|
+
# Base85 comman line interface
|
|
8
|
+
# @author sd77
|
|
9
|
+
class CLI
|
|
10
|
+
# Default wrap column count
|
|
11
|
+
DEFAULT_WRAP = 76
|
|
12
|
+
# @private
|
|
13
|
+
# Max size to read from input at once
|
|
14
|
+
READ_MAX_SIZE = 4096
|
|
15
|
+
# Known alphabet codes for --alphabet option
|
|
16
|
+
ALPHABETS = Base85::SUPPORTED_ALPHABETS.map(&:to_s).freeze
|
|
17
|
+
|
|
18
|
+
# Decode (+true+) or encode (+false+)
|
|
19
|
+
# @return [Boolean]
|
|
20
|
+
attr_accessor :decode
|
|
21
|
+
# Alpahbet to use
|
|
22
|
+
# @param alphabet [:standard, :ascii85 :z85 :rfc1924]
|
|
23
|
+
attr_writer :alphabet
|
|
24
|
+
# column count after which output is wrapped
|
|
25
|
+
# @return [Integer]
|
|
26
|
+
attr_accessor :wrap
|
|
27
|
+
|
|
28
|
+
# Create a CLI object and parse arguments
|
|
29
|
+
# @param [Array[String]] argv
|
|
30
|
+
# @return [CLI]
|
|
31
|
+
def self.start(argv)
|
|
32
|
+
new.start(argv)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [self]
|
|
36
|
+
def initialize
|
|
37
|
+
@decode = false
|
|
38
|
+
@alphabet = :standard
|
|
39
|
+
@wrap = DEFAULT_WRAP
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Alpahbet to use
|
|
43
|
+
# @return [:standard, :ascii85 :z85 :rfc1924]
|
|
44
|
+
def alphabet
|
|
45
|
+
@alphabet.to_sym
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Parse arguments
|
|
49
|
+
# @param [Array[String]] argv
|
|
50
|
+
# @return [self]
|
|
51
|
+
def start(argv)
|
|
52
|
+
parser = create_option_parser
|
|
53
|
+
parser.parse!(argv)
|
|
54
|
+
|
|
55
|
+
abort("---wrap may only be used when encoding") if decode && (wrap != DEFAULT_WRAP)
|
|
56
|
+
|
|
57
|
+
if argv.empty?
|
|
58
|
+
process($stdin)
|
|
59
|
+
elsif argv.size == 1
|
|
60
|
+
File.open(argv.shift) do |file|
|
|
61
|
+
process(file)
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
parser.abort("Too much operands")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Process +input+
|
|
71
|
+
# @param [IO] input
|
|
72
|
+
# @return [void]
|
|
73
|
+
def process(input)
|
|
74
|
+
if decode
|
|
75
|
+
decode_input(input)
|
|
76
|
+
else
|
|
77
|
+
encode_input(input)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Encode +input+ and print it to stdout
|
|
82
|
+
# @param [IO] input
|
|
83
|
+
# @return [void]
|
|
84
|
+
def encode_input(input)
|
|
85
|
+
rest = +""
|
|
86
|
+
current_col = handle_ascii85_start
|
|
87
|
+
until input.eof?
|
|
88
|
+
data = rest << input.read(READ_MAX_SIZE)
|
|
89
|
+
mod = data.size % 4
|
|
90
|
+
rest = mod.zero? ? +"" : data.slice!(-mod..)
|
|
91
|
+
current_col = print_wrapped(encode_data(data), current_col)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
encoded_rest = handle_ascii85_end(encode_data(rest))
|
|
95
|
+
print_wrapped(encoded_rest, current_col)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @param [String] data
|
|
99
|
+
# @return [String] encoded data
|
|
100
|
+
def encode_data(data)
|
|
101
|
+
encoded = Base85.encode(data, alphabet: alphabet)
|
|
102
|
+
ascii85_remove_markers(encoded)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Decode +input+ and print it to stdout
|
|
106
|
+
# @param [IO] input
|
|
107
|
+
# @return [void]
|
|
108
|
+
def decode_input(input)
|
|
109
|
+
rest = +""
|
|
110
|
+
until input.eof?
|
|
111
|
+
data = rest << input.read(READ_MAX_SIZE)
|
|
112
|
+
ascii85_remove_markers(data)
|
|
113
|
+
mod = data.size % 5
|
|
114
|
+
rest = mod.zero? ? +"" : data.slice!(-mod..)
|
|
115
|
+
print decode_data(data)
|
|
116
|
+
end
|
|
117
|
+
print decode_data(rest)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @param [String] data
|
|
121
|
+
# @return [String] decoded data
|
|
122
|
+
def decode_data(data)
|
|
123
|
+
Base85.decode(data, alphabet: alphabet)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def create_option_parser
|
|
129
|
+
OptionParser.new do |parser|
|
|
130
|
+
prepare_banner(parser)
|
|
131
|
+
parser.on("-d", "--decode", "Decode data.") { self.decode = true }
|
|
132
|
+
parser.on("-a", "--alphabet ALPHABET", ALPHABETS,
|
|
133
|
+
"Alphabet to use: #{ALPHABETS.join(", ")}\n",
|
|
134
|
+
"(default standard).") { |alphabet| self.alphabet = alphabet }
|
|
135
|
+
parser.on("-w", "--wrap COLS", Integer,
|
|
136
|
+
"Wrap encoded lines after WRAP characters (default #{DEFAULT_WRAP}).\n",
|
|
137
|
+
"Use 0 to disable wrapping.") { |cols| self.wrap = cols }
|
|
138
|
+
parser.on("--version", "Output version information and exit.") do
|
|
139
|
+
show_version
|
|
140
|
+
exit
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def prepare_banner(parser)
|
|
146
|
+
parser.banner = "Usage: #{$PROGRAM_NAME} [options] [FILE]\n" \
|
|
147
|
+
"Encode or decode FILE or standard input to standard output."
|
|
148
|
+
parser.separator ""
|
|
149
|
+
parser.separator "With no FILE, or when FILE is -, read standard input."
|
|
150
|
+
parser.separator ""
|
|
151
|
+
parser.separator "Options:"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def show_version
|
|
155
|
+
puts "base85 #{Base85::VERSION}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def ascii85?
|
|
159
|
+
alphabet == :ascii85
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def ascii85_remove_markers(data)
|
|
163
|
+
return data unless ascii85?
|
|
164
|
+
|
|
165
|
+
data.delete_prefix!("<~")
|
|
166
|
+
data.delete_suffix!("~>")
|
|
167
|
+
data
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def handle_ascii85_start
|
|
171
|
+
if ascii85?
|
|
172
|
+
print Ascii85::START_MARKER
|
|
173
|
+
Ascii85::START_MARKER.size
|
|
174
|
+
else
|
|
175
|
+
0
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def handle_ascii85_end(data)
|
|
180
|
+
data << Ascii85::END_MARKER if ascii85?
|
|
181
|
+
data
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def print_wrapped(data, current_col)
|
|
185
|
+
if wrap.zero?
|
|
186
|
+
print data
|
|
187
|
+
return 0
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
puts data.slice!(0, wrap - current_col) if (data.size + current_col) > wrap
|
|
191
|
+
puts data.slice!(0, wrap) while data.size > wrap
|
|
192
|
+
print data
|
|
193
|
+
data.size
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Base85
|
|
4
|
+
# Module handling common module methods to all encodings
|
|
5
|
+
# @author sd77
|
|
6
|
+
module ModuleMethods
|
|
7
|
+
# @private
|
|
8
|
+
LUT = (0..4).map { |count| 85**(4 - count) }.freeze
|
|
9
|
+
|
|
10
|
+
# @private
|
|
11
|
+
# Encode a single word
|
|
12
|
+
# @param [Integer] word
|
|
13
|
+
# @param [Integer] byte_count byte count of word. Must be in range 1..4
|
|
14
|
+
def encode_word(word, byte_count: 4)
|
|
15
|
+
return "z" if const_defined?(:SPECIAL_Z) && word.zero? && (byte_count == 4)
|
|
16
|
+
|
|
17
|
+
encoded = "~" * 5
|
|
18
|
+
5.times do |i|
|
|
19
|
+
word, r = word.divmod(85)
|
|
20
|
+
encoded[-i - 1] = encode_value(r)
|
|
21
|
+
end
|
|
22
|
+
encoded[..byte_count]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Encode data in base85
|
|
26
|
+
# @param [String] data data to encode
|
|
27
|
+
# @return [String] base85 encoded string
|
|
28
|
+
def encode(data)
|
|
29
|
+
result = +""
|
|
30
|
+
data.unpack("N*").each do |word|
|
|
31
|
+
result << encode_word(word)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
last_count = data.length % 4
|
|
35
|
+
return result if last_count.zero?
|
|
36
|
+
|
|
37
|
+
last_chars = data[-last_count..] << ("\x00" * (4 - last_count))
|
|
38
|
+
result << encode_word(last_chars.unpack1("N"), byte_count: last_count)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Decode data from base85
|
|
42
|
+
# @param [String] data base85 encoded data
|
|
43
|
+
# @return [String] decoded data
|
|
44
|
+
# @raise [DecodeError] unknown character or invalid tuple
|
|
45
|
+
def decode(data)
|
|
46
|
+
result = +""
|
|
47
|
+
word = 0
|
|
48
|
+
count = 0
|
|
49
|
+
data.each_char do |c|
|
|
50
|
+
if c == "z" && const_defined?(:SPECIAL_Z)
|
|
51
|
+
raise DecodeError, "'z' cannot be in the middle of a tuple" if count.positive?
|
|
52
|
+
|
|
53
|
+
result << "\0\0\0\0"
|
|
54
|
+
next
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
idx = decode_char(c)
|
|
58
|
+
|
|
59
|
+
word += idx * LUT[count]
|
|
60
|
+
count += 1
|
|
61
|
+
|
|
62
|
+
if (count == 5) && (word > 0xffffffff)
|
|
63
|
+
raise DecodeError, "invalid tuple"
|
|
64
|
+
elsif count == 5
|
|
65
|
+
wordbuf = " " * 4
|
|
66
|
+
3.downto(0) do |i|
|
|
67
|
+
wordbuf.setbyte(i, word & 0xff)
|
|
68
|
+
word >>= 8
|
|
69
|
+
end
|
|
70
|
+
result << wordbuf
|
|
71
|
+
count = 0
|
|
72
|
+
word = 0
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
return result if count.zero?
|
|
77
|
+
|
|
78
|
+
count -= 1
|
|
79
|
+
word += LUT[count]
|
|
80
|
+
result << ((word >> 24) & 0xff).chr if count >= 1
|
|
81
|
+
result << ((word >> 16) & 0xff).chr if count >= 2
|
|
82
|
+
result << ((word >> 8) & 0xff).chr if count == 3
|
|
83
|
+
result
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Base85
|
|
4
|
+
# RFC 1924 base85 encoding
|
|
5
|
+
# @author sd77
|
|
6
|
+
module Rfc1924
|
|
7
|
+
extend ModuleMethods
|
|
8
|
+
|
|
9
|
+
# RFC1924 alphabet
|
|
10
|
+
ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"
|
|
11
|
+
|
|
12
|
+
# Encode a value (0..84 integer) to a RFC 1924 character
|
|
13
|
+
# @param [Integer] value
|
|
14
|
+
# @return [String]
|
|
15
|
+
def self.encode_value(value)
|
|
16
|
+
ALPHABET[value]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Decode a character from RFC 1924 alphabet and give its index in alphabet
|
|
20
|
+
# @param [String] char a acharacter from base85 alphabet
|
|
21
|
+
# @return [Integer] index of this character in alphabet
|
|
22
|
+
# @raise DecodeError +char+ is not known in alphabet
|
|
23
|
+
def self.decode_char(char)
|
|
24
|
+
case char
|
|
25
|
+
when "0".."9"
|
|
26
|
+
char.ord - 48
|
|
27
|
+
when "A".."Z"
|
|
28
|
+
char.ord - 55 # - 65 + 10
|
|
29
|
+
when "a".."z"
|
|
30
|
+
char.ord - 61 # - 97 + 36
|
|
31
|
+
when "!", "#".."&", "(".."+", "-", ";".."@", "^".."`", "{".."~"
|
|
32
|
+
ALPHABET.index(char)
|
|
33
|
+
else
|
|
34
|
+
raise DecodeError, "unknown character '#{char}'"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/base85/standard.rb
CHANGED
|
@@ -1,84 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Base85
|
|
4
|
-
# Standard base85 encoding. This encoding uses
|
|
4
|
+
# Standard base85 encoding. This encoding uses +!+ to +u+ ASCII characters to encode data.
|
|
5
5
|
# @author sd77
|
|
6
6
|
module Standard
|
|
7
|
-
|
|
8
|
-
BASE_ASCII_VALUE = "!".ord
|
|
9
|
-
# @private
|
|
10
|
-
LUT = (0..4).map { |count| 85**(4 - count) }.freeze
|
|
7
|
+
extend ModuleMethods
|
|
11
8
|
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
# @param [Integer] word
|
|
15
|
-
# @param [Integer] byte_count byte count of word. Must be in range 1..4
|
|
16
|
-
def self.encode_word(word, byte_count: 4)
|
|
17
|
-
encoded = "~" * 5
|
|
18
|
-
5.times do |i|
|
|
19
|
-
word, r = word.divmod(85)
|
|
20
|
-
encoded[-i - 1] = (BASE_ASCII_VALUE + r).chr
|
|
21
|
-
end
|
|
22
|
-
encoded[..byte_count]
|
|
23
|
-
end
|
|
9
|
+
# ASCII value for character +!+
|
|
10
|
+
BASE_ASCII_VALUE = "!".ord
|
|
24
11
|
|
|
25
|
-
# Encode
|
|
26
|
-
# @param [
|
|
12
|
+
# Encode a value (0..84 integer) to a standard base85 character
|
|
13
|
+
# @param [Integer] value
|
|
27
14
|
# @return [String]
|
|
28
|
-
def self.
|
|
29
|
-
|
|
30
|
-
data.unpack("N*").each do |word|
|
|
31
|
-
result << encode_word(word)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
last_count = data.length % 4
|
|
35
|
-
return result if last_count.zero?
|
|
36
|
-
|
|
37
|
-
last_chars = data[-last_count..] << ("\x00" * (4 - last_count))
|
|
38
|
-
result << encode_word(last_chars.unpack1("N"), byte_count: last_count)
|
|
15
|
+
def self.encode_value(value)
|
|
16
|
+
(BASE_ASCII_VALUE + value).chr
|
|
39
17
|
end
|
|
40
18
|
|
|
41
|
-
# Decode
|
|
42
|
-
# @param [String]
|
|
43
|
-
# @return [
|
|
44
|
-
# @raise
|
|
45
|
-
def self.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
when "!".."u" # rubocop:disable Lint/MixedCaseRange
|
|
52
|
-
idx = c.ord - BASE_ASCII_VALUE
|
|
53
|
-
else
|
|
54
|
-
raise DecodeError, "unknown character '#{c}'"
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
word += idx * LUT[count]
|
|
58
|
-
count += 1
|
|
59
|
-
|
|
60
|
-
if (count == 5) && (word > 0xffffffff)
|
|
61
|
-
raise DecodeError, "invalid tuple"
|
|
62
|
-
elsif count == 5
|
|
63
|
-
wordbuf = " " * 4
|
|
64
|
-
3.downto(0) do |i|
|
|
65
|
-
wordbuf.setbyte(i, word & 0xff)
|
|
66
|
-
word >>= 8
|
|
67
|
-
end
|
|
68
|
-
result << wordbuf
|
|
69
|
-
count = 0
|
|
70
|
-
word = 0
|
|
71
|
-
end
|
|
19
|
+
# Decode a character from standard base85 and give its index in alphabet
|
|
20
|
+
# @param [String] char a character from base85 alphabet
|
|
21
|
+
# @return [Integer] index of this character in alphabet
|
|
22
|
+
# @raise DecodeError +char+ is not known in alphabet
|
|
23
|
+
def self.decode_char(char)
|
|
24
|
+
case char
|
|
25
|
+
when "!".."u" # rubocop:disable Lint/MixedCaseRange
|
|
26
|
+
char.ord - BASE_ASCII_VALUE
|
|
27
|
+
else
|
|
28
|
+
raise DecodeError, "unknown character '#{char}'"
|
|
72
29
|
end
|
|
73
|
-
|
|
74
|
-
return result if count.zero?
|
|
75
|
-
|
|
76
|
-
count -= 1
|
|
77
|
-
word += LUT[count]
|
|
78
|
-
result << ((word >> 24) & 0xff).chr if count >= 1
|
|
79
|
-
result << ((word >> 16) & 0xff).chr if count >= 2
|
|
80
|
-
result << ((word >> 8) & 0xff).chr if count == 3
|
|
81
|
-
result
|
|
82
30
|
end
|
|
83
31
|
end
|
|
84
32
|
end
|
data/lib/base85/version.rb
CHANGED
data/lib/base85/z85.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Base85
|
|
4
|
+
# ZeroMQ (Z85) encoding
|
|
5
|
+
module Z85
|
|
6
|
+
extend ModuleMethods
|
|
7
|
+
|
|
8
|
+
# Z85 alphabet
|
|
9
|
+
ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#"
|
|
10
|
+
|
|
11
|
+
# Encode a value (0..84 integer) to a Z85 character
|
|
12
|
+
# @param [Integer] value
|
|
13
|
+
# @return [String]
|
|
14
|
+
def self.encode_value(value)
|
|
15
|
+
ALPHABET[value]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Decode a character from Z85 and give its index in alphabet
|
|
19
|
+
# @param [String] char a acharacter from base85 alphabet
|
|
20
|
+
# @return [Integer] index of this character in alphabet
|
|
21
|
+
# @raise DecodeError +char+ is not known in alphabet
|
|
22
|
+
def self.decode_char(char)
|
|
23
|
+
case char
|
|
24
|
+
when "0".."9"
|
|
25
|
+
char.ord - 48
|
|
26
|
+
when "a".."z"
|
|
27
|
+
char.ord - 87 # - 97 + 10
|
|
28
|
+
when "A".."Z"
|
|
29
|
+
char.ord - 29 # - 65 + 26
|
|
30
|
+
when "!", "#".."&", "(".."+", "-".."/", ":", "<".."@", "[", "]", "^", "{".."}"
|
|
31
|
+
ALPHABET.index(char)
|
|
32
|
+
else
|
|
33
|
+
raise DecodeError, "unknown character '#{char}'"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/base85.rb
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "base85/version"
|
|
4
|
+
require_relative "base85/module_methods"
|
|
4
5
|
require_relative "base85/standard"
|
|
6
|
+
require_relative "base85/z85"
|
|
7
|
+
require_relative "base85/rfc1924"
|
|
8
|
+
require_relative "base85/ascii85"
|
|
5
9
|
|
|
6
10
|
# Provide methods to encode/decode Base85 formatted data
|
|
7
11
|
# @author sd77
|
|
@@ -14,32 +18,54 @@ module Base85
|
|
|
14
18
|
|
|
15
19
|
# Unknown alphabet exception
|
|
16
20
|
class UnknownAlphabetError < Error
|
|
21
|
+
# @param [Symbol] name alphabet name
|
|
17
22
|
def initialize(name)
|
|
18
23
|
super
|
|
19
24
|
@name = name
|
|
20
25
|
end
|
|
21
26
|
|
|
27
|
+
# @return [String]
|
|
22
28
|
def message
|
|
23
29
|
"Uknown alphabet '#{@name}'"
|
|
24
30
|
end
|
|
25
31
|
end
|
|
26
32
|
|
|
27
33
|
# Supported Base85 alphabets
|
|
28
|
-
SUPPORTED_ALPHABETS = %i[standard z85 rfc1924].freeze
|
|
34
|
+
SUPPORTED_ALPHABETS = %i[standard ascii85 z85 rfc1924].freeze
|
|
29
35
|
|
|
36
|
+
# Encode +data+ using given +alphabet+
|
|
37
|
+
# @param [String] data data to encode
|
|
38
|
+
# @param [:standard,:ascii85,:z85,:rfc1924] alphabet alphabet to use to encode
|
|
39
|
+
# @return [String] encoded string
|
|
30
40
|
def self.encode(data, alphabet: :standard)
|
|
31
41
|
case alphabet
|
|
32
42
|
when :standard
|
|
33
43
|
Standard.encode(data)
|
|
44
|
+
when :ascii85
|
|
45
|
+
Ascii85.encode(data)
|
|
46
|
+
when :z85
|
|
47
|
+
Z85.encode(data)
|
|
48
|
+
when :rfc1924
|
|
49
|
+
Rfc1924.encode(data)
|
|
34
50
|
else
|
|
35
51
|
raise UnknownAlphabetError.new(alphabet)
|
|
36
52
|
end
|
|
37
53
|
end
|
|
38
54
|
|
|
55
|
+
# Decode +data+ using given +alphabet+
|
|
56
|
+
# @param [String] data data to decode
|
|
57
|
+
# @param [:standard,:ascii85,:z85,:rfc1924] alphabet alphabet to use to decode
|
|
58
|
+
# @return [String] decoded string
|
|
39
59
|
def self.decode(data, alphabet: :standard)
|
|
40
60
|
case alphabet
|
|
41
61
|
when :standard
|
|
42
62
|
Standard.decode(data)
|
|
63
|
+
when :ascii85
|
|
64
|
+
Ascii85.decode(data)
|
|
65
|
+
when :z85
|
|
66
|
+
Z85.decode(data)
|
|
67
|
+
when :rfc1924
|
|
68
|
+
Rfc1924.decode(data)
|
|
43
69
|
else
|
|
44
70
|
raise UnknownAlphabetError.new(alphabet)
|
|
45
71
|
end
|
metadata
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: base85
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- sd77
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-06-
|
|
11
|
+
date: 2025-06-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
|
-
description:
|
|
14
|
-
|
|
13
|
+
description: |
|
|
14
|
+
Encode and decode base85 data. Multiple alphabets are supported (standard,
|
|
15
|
+
Ascii85, Z85 or RFC 1924).
|
|
15
16
|
email:
|
|
16
17
|
- sd@ld77.eu
|
|
17
|
-
executables:
|
|
18
|
+
executables:
|
|
19
|
+
- base85
|
|
18
20
|
extensions: []
|
|
19
21
|
extra_rdoc_files: []
|
|
20
22
|
files:
|
|
@@ -24,9 +26,15 @@ files:
|
|
|
24
26
|
- LICENSE.txt
|
|
25
27
|
- README.md
|
|
26
28
|
- Rakefile
|
|
29
|
+
- exe/base85
|
|
27
30
|
- lib/base85.rb
|
|
31
|
+
- lib/base85/ascii85.rb
|
|
32
|
+
- lib/base85/cli.rb
|
|
33
|
+
- lib/base85/module_methods.rb
|
|
34
|
+
- lib/base85/rfc1924.rb
|
|
28
35
|
- lib/base85/standard.rb
|
|
29
36
|
- lib/base85/version.rb
|
|
37
|
+
- lib/base85/z85.rb
|
|
30
38
|
- sig/base85.rbs
|
|
31
39
|
homepage: https://codeberg.org/sd77/base85
|
|
32
40
|
licenses:
|