Skip to content

Instantly share code, notes, and snippets.

@mieszko4
Last active December 3, 2022 22:16
Show Gist options
  • Save mieszko4/01bab59fdeba6bd09bf475dda5968808 to your computer and use it in GitHub Desktop.
Save mieszko4/01bab59fdeba6bd09bf475dda5968808 to your computer and use it in GitHub Desktop.

Revisions

  1. mieszko4 revised this gist Dec 3, 2022. No changes.
  2. mieszko4 revised this gist Dec 3, 2022. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions atob.lua
    Original file line number Diff line number Diff line change
    @@ -6,9 +6,9 @@
    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)
    local charCode = (unit) >> (8 * idx)

    return string.char(charCode)
    return string.char(charCode)
    end

    -- Extract specific 8-bit
  3. mieszko4 revised this gist Dec 3, 2022. 1 changed file with 7 additions and 0 deletions.
    7 changes: 7 additions & 0 deletions atob.lua
    Original file line number Diff line number Diff line change
    @@ -4,6 +4,13 @@

    -- 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)

  4. mieszko4 revised this gist Dec 2, 2022. 1 changed file with 16 additions and 13 deletions.
    29 changes: 16 additions & 13 deletions atob.lua
    Original file line number Diff line number Diff line change
    @@ -6,19 +6,14 @@
    local function extractChar(unit, idx)
    -- 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)

    -- Return empty string in case of padding
    if charCode == 0 then
    return ""
    end

    -- 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,
    @@ -30,9 +25,11 @@ local function extractChar(unit, idx)
    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
    -- 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
    @@ -44,14 +41,18 @@ local function extractChar(unit, idx)
    -- 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)
    local r2 = extractChar(muxedUnit, 1)
    local r3 = extractChar(muxedUnit, 0)
    -- 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
    @@ -62,7 +63,9 @@ 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")
  5. mieszko4 created this gist Dec 2, 2022.
    70 changes: 70 additions & 0 deletions atob.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,70 @@
    -- 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)
    -- 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)

    -- Return empty string in case of padding
    if charCode == 0 then
    return ""
    end

    -- 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 function atob(encodedString)
    -- Get four 6-bit chunks
    -- 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
    local r1 = extractChar(muxedUnit, 2)
    local r2 = extractChar(muxedUnit, 1)
    local r3 = extractChar(muxedUnit, 0)

    -- Concatanate
    return r1..r2..r3
    end)

    return decodedString
    end

    return atob

    assert(atob("bGlnaHQgd28=") == "light wo", "full units, single padding")
    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")