Skip to content
Browse files

Properly handle offset. Remove return type hinting due to a bug in cl…

…ojure. Add some perf improvements. Add tests.
  • Loading branch information...
1 parent 4270516 commit ab8ceed3b1d4f676d7b033b424bb1e4b4c1bcc02 Alexander Taggart committed Oct 10, 2011
Showing with 108 additions and 95 deletions.
  1. +95 −92 src/main/clojure/clojure/data/codec/base64.clj
  2. +13 −3 src/test/clojure/clojure/data/codec/test_base64.clj
View
187 src/main/clojure/clojure/data/codec/base64.clj
@@ -1,8 +1,8 @@
(ns clojure.data.codec.base64)
-(def ^:private enc-bytes
- (byte-array
- (map (comp byte int)
+(def ^:private ^"[B" enc-bytes
+ (byte-array
+ (map (comp byte int)
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")))
(defn enc-length
@@ -13,94 +13,97 @@
(quot 3)
(unchecked-multiply 4)))
+(defn encode!
+ "Reads from the input byte array for the specified length starting at the offset
+ index, and base64 encodes into the output array starting at index 0."
+ [^bytes input ^long offset ^long length ^bytes output]
+ (let [tail-len (unchecked-remainder-int length 3)
+ loop-lim (unchecked-subtract (unchecked-add offset length) tail-len)
+ end (unchecked-dec (unchecked-add offset length))]
+ (loop [i offset j 0]
+ (when (< i loop-lim)
+ (let [x (long (aget input i)) ; can only bind long/double prims, and no widening conversion
+ y (long (aget input (unchecked-inc i)))
+ z (long (aget input (unchecked-add 2 i)))
+ a (-> x
+ (bit-shift-right 2)
+ (bit-and 0x3F))
+ b1 (-> x
+ (bit-and 0x3)
+ (bit-shift-left 4))
+ b2 (-> y
+ (bit-shift-right 4)
+ (bit-and 0xF))
+ b (bit-or b1 b2)
+ c1 (-> y
+ (bit-and 0xF)
+ (bit-shift-left 2))
+ c2 (-> z
+ (bit-shift-right 6)
+ (bit-and 0x3))
+ c (bit-or c1 c2)
+ d (bit-and z 0x3F)]
+ (aset output j (aget enc-bytes a))
+ (aset output (unchecked-inc j) (aget enc-bytes b))
+ (aset output (unchecked-add 2 j) (aget enc-bytes c))
+ (aset output (unchecked-add 3 j) (aget enc-bytes d)))
+ (recur (unchecked-add 3 i) (unchecked-add 4 j))))
+ ; add padding:
+ (case tail-len
+ 0 output
+ 1 (do
+ (aset output
+ (unchecked-subtract (alength output) 4)
+ (aget enc-bytes
+ (-> (aget input end)
+ int
+ (bit-shift-right 2)
+ (bit-and 0x3F))))
+ (aset output
+ (unchecked-subtract (alength output) 3)
+ (aget enc-bytes
+ (-> (aget input end)
+ int
+ (bit-and 0x3)
+ (bit-shift-left 4))))
+ (aset output (unchecked-subtract (alength output) 2) (byte 61))
+ (aset output (unchecked-dec (alength output)) (byte 61))
+ output)
+ 2 (do
+ (aset output
+ (unchecked-subtract (alength output) 4)
+ (aget enc-bytes
+ (-> (aget input (unchecked-dec end))
+ int
+ (bit-shift-right 2)
+ (bit-and 0x3F))))
+ (aset output
+ (unchecked-subtract (alength output) 3)
+ (aget enc-bytes
+ (-> (aget input (unchecked-dec end))
+ int
+ (bit-and 0x3)
+ (bit-shift-left 4)
+ (bit-or (-> (aget input end)
+ int
+ (bit-shift-right 4)
+ (bit-and 0xF))))))
+ (aset output
+ (unchecked-subtract (alength output) 2)
+ (aget enc-bytes
+ (-> (aget input end)
+ int
+ (bit-and 0xF)
+ (bit-shift-left 2))))
+ (aset output (unchecked-dec (alength output)) (byte 61))
+ nil))))
+
(defn encode
"Returns a base64 encoded byte array."
- (^bytes [^bytes ba]
- (encode ba 0 (alength ba)))
- (^bytes [^bytes ba offset length]
- (let [^bytes enc-bytes enc-bytes ; can't type-hint var
- _ (if (> length 1610612733)
- (throw (IllegalArgumentException.
- "Encoded length would exceed max array size.")))
- ca (byte-array (enc-length length))
- lim (case (mod length 3)
- 0 length
- 1 (unchecked-dec length)
- 2 (unchecked-subtract length 2))]
- (loop [i offset j 0]
- (when (< i lim)
- (let [x (int (aget ba i)) ; can only bind long/double prims, but no widening, and can't cast from byte to long without boxing
- y (int (aget ba (unchecked-inc i)))
- z (int (aget ba (unchecked-add 2 i)))
- a (-> x
- (bit-shift-right 2)
- (bit-and 0x3F))
- b1 (-> x
- (bit-and 0x3)
- (bit-shift-left 4))
- b2 (-> y
- (bit-shift-right 4)
- (bit-and 0xF))
- b (bit-or b1 b2)
- c1 (-> y
- (bit-and 0xF)
- (bit-shift-left 2))
- c2 (-> z
- (bit-shift-right 6)
- (bit-and 0x3))
- c (bit-or c1 c2)
- d (bit-and z 0x3F)]
- (aset ca j (aget enc-bytes a))
- (aset ca (unchecked-inc j) (aget enc-bytes b))
- (aset ca (unchecked-add 2 j) (aget enc-bytes c))
- (aset ca (unchecked-add 3 j) (aget enc-bytes d)))
- (recur (unchecked-add 3 i) (unchecked-add 4 j))))
- ; add padding:
- (case (mod length 3)
- 0 ca
- 1 (do
- (aset ca
- (unchecked-subtract (alength ca) 4)
- (aget enc-bytes
- (-> (aget ba (unchecked-dec length))
- int
- (bit-shift-right 2)
- (bit-and 0x3F))))
- (aset ca
- (unchecked-subtract (alength ca) 3)
- (aget enc-bytes
- (-> (aget ba (unchecked-dec length))
- int
- (bit-and 0x3)
- (bit-shift-left 4))))
- (aset ca (unchecked-subtract (alength ca) 2) (byte 61))
- (aset ca (unchecked-dec (alength ca)) (byte 61))
- ca)
- 2 (do
- (aset ca
- (unchecked-subtract (alength ca) 4)
- (aget enc-bytes
- (-> (aget ba (unchecked-subtract length 2))
- int
- (bit-shift-right 2)
- (bit-and 0x3F))))
- (aset ca
- (unchecked-subtract (alength ca) 3)
- (aget enc-bytes
- (-> (aget ba (unchecked-subtract length 2))
- int
- (bit-and 0x3)
- (bit-shift-left 4)
- (bit-or (-> (aget ba (unchecked-dec length))
- int
- (bit-shift-right 4)
- (bit-and 0xF))))))
- (aset ca
- (unchecked-subtract (alength ca) 2)
- (aget enc-bytes
- (-> (aget ba (unchecked-dec length))
- int
- (bit-and 0xF)
- (bit-shift-left 2))))
- (aset ca (unchecked-dec (alength ca)) (byte 61))
- ca)))))
+ ([^bytes input]
+ (encode input 0 (alength input)))
+ ([^bytes input ^long offset ^long length]
+ (let [dest (byte-array (enc-length length))]
+ (encode! input offset length dest)
+ dest)))
+
View
16 src/test/clojure/clojure/data/codec/test_base64.clj
@@ -1,5 +1,4 @@
(ns clojure.data.codec.test-base64
- (:refer-clojure :exclude [time])
(:import org.apache.commons.codec.binary.Base64)
(:use clojure.test
clojure.data.codec.base64))
@@ -11,8 +10,19 @@
(byte-array)))
(deftest correctness
- (doseq [n (range 1 129)]
+ (doseq [n (range 1 100)]
(is (let [input (rand-bytes n)
a1 (encode input)
a2 (Base64/encodeBase64 input)]
- (= (into [] a1) (into [] a2))))))
+ (= (into [] a1) (into [] a2))))))
+
+(deftest offset-correctness
+ (doseq [n (range 1 100)]
+ (doseq [off (range 1 n)]
+ (is (let [input (rand-bytes n)
+ len (- n off)
+ a1 (encode input off len)
+ input2 (byte-array len)
+ _ (System/arraycopy input off input2 0 len)
+ a2 (Base64/encodeBase64 input2)]
+ (= (into [] a1) (into [] a2)))))))

0 comments on commit ab8ceed

Please sign in to comment.
Something went wrong with that request. Please try again.