From 58636ad489a2e1d52e013ee92f392acc816d718b Mon Sep 17 00:00:00 2001 From: Didactic Drunk <1479616+didactic-drunk@users.noreply.github.com> Date: Fri, 30 Aug 2019 22:03:24 -0700 Subject: [PATCH 1/4] Add spec for NUL bytes. --- spec/base32_spec.cr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/base32_spec.cr b/spec/base32_spec.cr index f7536f8..5b2037a 100644 --- a/spec/base32_spec.cr +++ b/spec/base32_spec.cr @@ -89,4 +89,10 @@ describe Base32 do Base32.hex_decode_string("CPNMUOJ1").should eq("fooba") Base32.hex_decode_string("CPNMUOJ1E8").should eq("foobar") end + + it "encodes/decodes with NUL bytes" do + str = "\0foo\0bar\0" + str2 = Base32.decode_string(Base32.encode(str)) + str2.should eq str + end end From 4729f01f0f60c772699adb99519b6ea893b047c7 Mon Sep 17 00:00:00 2001 From: Joakim Repomaa Date: Mon, 30 Sep 2019 17:25:02 +0900 Subject: [PATCH 2/4] fix floor division issue with crystal 0.31.0 --- src/base32.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base32.cr b/src/base32.cr index 8533885..b23802b 100644 --- a/src/base32.cr +++ b/src/base32.cr @@ -32,7 +32,7 @@ module Base32 # :nodoc: private def to_base32(data, pad : Bool, chars : Array(Char)) : String - mio = IO::Memory.new((data.bytesize / 5) * 8) + mio = IO::Memory.new((data.bytesize // 5) * 8) data.to_slice.each_slice(5) do |slice| bits = 0_u64 @@ -69,7 +69,7 @@ module Base32 # :nodoc: private def from_base32(data, map : Hash(Char, Int)) : Slice(UInt8) - mio = IO::Memory.new((data.bytesize / 8) * 5) + mio = IO::Memory.new((data.bytesize // 8) * 5) data.to_slice.select { |s| !['\n'.ord, '\r'.ord, '='.ord].includes?(s) }.each_slice(8) do |slice| bits = 0_u64 From c8187af4fd832c7c87476da0747ff13a2da83a2a Mon Sep 17 00:00:00 2001 From: Didactic Drunk <1479616+didactic-drunk@users.noreply.github.com> Date: Sat, 12 Jun 2021 20:05:14 -0700 Subject: [PATCH 3/4] Change maintainer in README.md --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b1e33c2..ab9174c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ # base32 - -[![Build Status](https://travis-ci.org/noumar/base32.svg?branch=master)](https://travis-ci.org/noumar/base32) -[![Docs](http://docrystal.org/badge.svg?style=round)](http://docrystal.org/github.com/noumar/base32) +[![Crystal CI](https://github.com/didactic-drunk/base32/actions/workflows/crystal.yml/badge.svg)](https://github.com/didactic-drunk/base32/actions/workflows/crystal.yml) +[![GitHub release](https://img.shields.io/github/release/didactic-drunk/base32.svg)](https://github.com/didactic-drunk/base32/releases) +![GitHub commits since latest release (by date) for a branch](https://img.shields.io/github/commits-since/didactic-drunk/base32/latest) +[![Docs](https://img.shields.io/badge/docs-available-brightgreen.svg)](https://didactic-drunk.github.io/base32/master) Provides encoding and decoding of base32 and base32hex as defined in RFC 4648. +Maintained here temporarily (or permanently) until @noumar returns. + ## Installation Add this to your application's `shard.yml`: @@ -12,7 +15,7 @@ Add this to your application's `shard.yml`: ```yaml dependencies: base32: - github: noumar/base32 + github: didactic-drunk/base32 ``` ## Usage @@ -33,7 +36,7 @@ TODO: Write development instructions here ## Contributing -1. Fork it ( https://github.com/noumar/base32/fork ) +1. Fork it ( https://github.com/didactic-drunk/base32/fork ) 2. Create your feature branch (git checkout -b my-new-feature) 3. Commit your changes (git commit -am 'Add some feature') 4. Push to the branch (git push origin my-new-feature) @@ -41,4 +44,5 @@ TODO: Write development instructions here ## Contributors -- [noumar](https://github.com/noumar) / Mikael Karlsson - creator, maintainer +- [noumar](https://github.com/noumar) / Mikael Karlsson - creator +- [didactic-drunk](https://github.com/didactic-drunk) - current maintainer From eb6457ab6837c11b0e8459831f422a02678795ed Mon Sep 17 00:00:00 2001 From: Didactic Drunk <1479616+didactic-drunk@users.noreply.github.com> Date: Wed, 12 Jun 2019 20:05:28 -0700 Subject: [PATCH 4/4] Use String.build instead of IO::Memory. New faster implementation of Base32.encode. Correctly handle NUL bytes. Add NUL spec. --- spec/base32_spec.cr | 16 ++++++++-- src/base32.cr | 74 ++++++++++++++++++++------------------------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/spec/base32_spec.cr b/spec/base32_spec.cr index 5b2037a..5e3bcac 100644 --- a/spec/base32_spec.cr +++ b/spec/base32_spec.cr @@ -12,6 +12,9 @@ describe Base32 do Base32.encode("foobar").should eq("MZXW6YTBOI======") Base32.encode("Hello World!").should eq("JBSWY3DPEBLW64TMMQQQ====") + + # Compared with Ruby version + Base32.encode(Bytes.new(4)).should eq("AAAAAAA=") end it "encodes to base32 (w/o padding)" do @@ -90,9 +93,16 @@ describe Base32 do Base32.hex_decode_string("CPNMUOJ1E8").should eq("foobar") end - it "encodes/decodes with NUL bytes" do + describe "NUL byte" do str = "\0foo\0bar\0" - str2 = Base32.decode_string(Base32.encode(str)) - str2.should eq str + bstr = "ABTG63YAMJQXEAA=" + + it "encode" do + Base32.encode(str).should eq bstr + end + + it "decode" do + Base32.decode_string(bstr).should eq str + end end end diff --git a/src/base32.cr b/src/base32.cr index b23802b..70f62e7 100644 --- a/src/base32.cr +++ b/src/base32.cr @@ -32,65 +32,57 @@ module Base32 # :nodoc: private def to_base32(data, pad : Bool, chars : Array(Char)) : String - mio = IO::Memory.new((data.bytesize // 5) * 8) - - data.to_slice.each_slice(5) do |slice| - bits = 0_u64 - 0.to(slice.size - 1) do |j| - bits = bits | (slice[j].to_u64 << (4 - j)*8) + ssize = data.bytesize * 8 // 5 + ssize += 7 if pad + String.build(ssize) do |sb| + bits = 0 + b = 0 + data.to_slice.each do |c| + b = (b << 8) | c + bits += 8 + while bits >= 5 + bits -= 5 + sb << chars[b >> bits] + mask = (1 << bits) - 1 + b &= mask + end end - mask = 0x1F_u64 << 35 - 7.to(0) do |i| - num = (bits & mask) >> i*5 - mio << chars[num] - mask = (mask >> 5) + if bits > 0 + sb << chars[b << (5 - bits)] end - end - # Exclude empty trailing chars - while mio.buffer[mio.pos - 1] == chars[0].ord - mio.pos -= 1 - end - - # Fill with padding - until (mio.pos % 8 == 0) - mio << PAD + if pad + rem = sb.bytesize % 8 + (8 - rem).times { sb << PAD } if rem > 0 + end end - - # Exclude padding if not wanted - sl = mio.to_slice - while sl[-1] == PAD.ord - sl = sl[0, sl.size - 1] - end if sl.size > 0 && pad == false - - String.new(sl) end + IGNORE_CHARS = ['\n'.ord, '\r'.ord, '='.ord] + # :nodoc: - private def from_base32(data, map : Hash(Char, Int)) : Slice(UInt8) + private def from_base32(data, map : Hash(Char, Int)) : Bytes mio = IO::Memory.new((data.bytesize // 8) * 5) - data.to_slice.select { |s| !['\n'.ord, '\r'.ord, '='.ord].includes?(s) }.each_slice(8) do |slice| + data.to_slice.select { |s| !IGNORE_CHARS.includes?(s) }.each_slice(8) do |slice| bits = 0_u64 - 0.to(slice.size - 1) do |j| - bits = bits | (map[slice[j].chr].to_u64 << (7 - j)*5) + slice.each_with_index do |b, j| + bits = bits | (map[b.chr].to_u64 << (7 - j)*5) end mask = 0xFF_u64 << 32 - 4.to(0) do |i| + + rem_bytes = slice.size * 5 // 8 + + 4.downto(5 - rem_bytes) do |i| num = (bits & mask) >> i*8 mio.write_byte(num.to_u8) mask = (mask >> 8) end end - # Exclude trailing zero bytes - sl = mio.to_slice - while sl[-1] == 0 - sl = sl[0, sl.size - 1] - end if sl.size > 0 - sl + mio.to_slice end # Encode data as base32 with padding, or without if `pad` = false @@ -99,7 +91,7 @@ module Base32 end # Decode base32 data, regardless if padded or not - def decode(data) : Slice(UInt8) + def decode(data) : Bytes from_base32(data, DEC_STD) end @@ -114,7 +106,7 @@ module Base32 end # Decode base32hex data, regardless if padded or not - def hex_decode(data) : Slice(UInt8) + def hex_decode(data) : Bytes from_base32(data, DEC_HEX) end