swiftbit-manipulationswift3base32

How to take N bits from a UInt8 variable in Swift?


I'm trying to implement a Base32 decoding for an app as I teach myself Swift, but I cannot seem to figure out how to go below the byte level in this language. It would be convenient if I could truncate a UInt8 into 5 bits and append that to a Data object which I could then work with.

I have this function written in Python:

def base32_decode(secret):
    b32alphabet = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")
    b32v = [b32alphabet.index(x) for x in secret if x != '=']
    t1 = ["{0:0>5}".format(bin(v)[2:]) for v in b32v]
    t2 = ''.join(t1)
    t3 = textwrap.wrap(t2,8)
    t4 = [int(v, 2) for v in t3 if len(v) == 8]
    t5 = ''.join(["{0:0>2}".format(hex(v)[2:]) for v in t4])

Which works to output the hex representation of the data in base32. I wanted to replicate this in Swift (although not the converting to hex part). However, I got this far:

func base32decode(string: String) -> Data
{
    let b32a: Array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "2", "3", "4", "5", "6", "7"]
    let complete: NSMutableData = NSMutableData()

    var b32v: Array<UInt8> = []

    for c in string.characters
    {
        let index  = b32a.index(of: String(c))!
        b32v.append(UInt8(index)) // Need to append only the 5 LSB
    }

    // Return b32v as base 32 decoded data
...

Is there an easy way to do this? I can't find anything through Google.


Solution

  • Swift has bit manipulation operators (|, &, <<, >>) which can be used to extract portions of a byte (and whether that is "an easy way" or not is certainly opinion-based).

    Your Python code apparently creates a string of all binary digits first, which is then partitioned into portions of 8 bits and converted to hexadecimal values.

    The following is a possible implementation which does not use an intermediate string. Instead the decoded bits are accumulated in an integer, and as soon as 8 bits are collected, these are appended to the result array.

    func base32decode(string: String) -> Data {
        let b32a = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".characters)
        var b32v: [UInt8] = []
        var accum = 0
        var bits = 0 // # of valid bits in `accum`
        for c in string.characters {
            if let index = b32a.index(of: c) {
                accum = (accum << 5) | index
                bits += 5
                if bits >= 8 {
                    b32v.append(UInt8(truncatingBitPattern: accum >> (bits - 8)))
                    bits -= 8
                }
            }
        }
        return Data(bytes: b32v)
    }
    

    Example:

    print(base32decode(string: "JBSWY3DPEB3W64TMMQQQ") as NSData)
    // <48656c6c 6f20776f 726c6421>
    

    (which is "Hello World!").

    The interesting parts in that function are

      accum = (accum << 5) | index
    

    which moves all bits in accum by 5 positions to the left and sets the lowest 5 bits to index, and

      b32v.append(UInt8(truncatingBitPattern: accum >> (bits - 8)))
    

    which appends the left-most 8 valid bits in accum to the array.