Skip to content

Instantly share code, notes, and snippets.

@mieszko4
Last active December 3, 2022 22:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mieszko4/01bab59fdeba6bd09bf475dda5968808 to your computer and use it in GitHub Desktop.
Save mieszko4/01bab59fdeba6bd09bf475dda5968808 to your computer and use it in GitHub Desktop.
-- Lua uses at least 8 bytes to represent a number
-- Base64 minimum processable unit is represented by four 6-bit chunks which is 24-bits => 3 bytes
-- Hence we can safely use bit operations on number type for multiplexing and extraction
-- Define function to extract 8-bit ASCII char from 24-bit base64 unit
local function extractChar(unit, idx)
-- Skip mask for first ASCII char since shifting will destroy other two parts
if idx == 2 then
local charCode = (unit) >> (8 * idx)
return string.char(charCode)
end
-- Extract specific 8-bit
local mask = (2^8 - 1) << (8 * idx)
-- Move 8-bit to the beginning so it becomes a char code
local charCode = (unit & mask) >> (8 * idx)
-- Convert to string
return string.char(charCode)
end
-- Define mapping of base64 character to number
local mapping = {
A=0, B=1, C=2, D=3, E=4, F=5, G=6, H=7,
I=8, J=9, K=10, L=11, M=12, N=13, O=14, P=15,
Q=16, R=17, S=18, T=19, U=20, V=21, W=22, X=23,
Y=24, Z=25, a=26, b=27, c=28, d=29, e=30, f=31,
g=32, h=33, i=34, j=35, k=36, l=37, m=38, n=39,
o=40, p=41, q=42, r=43, s=44, t=45, u=46, v=47,
w=48, x=49, y=50, z=51, ["0"]=52, ["1"]=53, ["2"]=54, ["3"]=55,
["4"]=56, ["5"]=57, ["6"]=58, ["7"]=59, ["8"]=60, ["9"]=61, ["+"]=62, ["/"]=63
}
local padding = "="
local missing = ""
local function atob(encodedString)
-- Get four 6-bit chunks which forms one unit
-- There must be at least two 6-bit chunks to cover at least one 8-bit chunk
local decodedString = encodedString:gsub("(.)(.)(.?)(.?)", function(c1, c2, c3, c4)
-- Convert each 6-bit chunk to number
local b1 = mapping[c1] --24,23,22,21,20,19
local b2 = mapping[c2] --18,17,16,15,14,13
local b3 = mapping[c3] --12,11,10,09,08,07
local b4 = mapping[c4] --06,05,04,03,02,01
-- Set optional chunks to 0 to be able to multiplex
if b3 == nil then b3 = 0 end
if b4 == nil then b4 = 0 end
-- Multiplex onto 24-bit number
local muxedUnit = (b1 << (6 * 3)) + (b2 << (6 * 2)) + (b3 << (6 * 1)) + (b4 << (6 * 0))
-- Extract 8-bit chunks
-- Use empty string if unit is not full or it is padded
-- and handle A base64 characters at the end of encodedString
local r1 = extractChar(muxedUnit, 2)
-- if c3 is padding or missing then we can skip r2
local r2 = (c3 == padding or c3 == missing) and "" or extractChar(muxedUnit, 1)
-- if c4 is padding or missing then we can skip r3
local r3 = (c4 == padding or c4 == missing) and "" or extractChar(muxedUnit, 0)
-- Concatanate
return r1..r2..r3
end)
return decodedString
end
return atob
-- Test plan
assert(atob("bGlnaHQgd28=") == "light wo", "full units, single padding")
assert(atob("bGlnaHQgd28A") == "light wo\x00", "full units, explicit A")
assert(atob("bGlnaHQgd28") == "light wo", "2/3 of unit")
assert(atob("bGlnaHQgdw==") == "light w", "full unit, double padding")
assert(atob("bGlnaHQgdw") == "light w", "1/3 of unit")
assert(atob("bGlnaHQgd28=") == "light wo", "full units, single padding")
assert(atob("bGlnaHQgd29y") == "light wor", "full units, no padding")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment