Search code examples
swiftobjective-cpointerspgp

Why does the same function in Obj-C return a different result in swift?


I needed to convert this c header with a function inside into a swift script. I did this but when I try both functions and compare results, they turn out to be unequal. What is going on? I think I have narrowed it down to: it's maybe about pointers being weird in swift.

C header: (header.h) (not mine. see PGPFormat)

#define CRC24_INIT 0xB704CEL
#define CRC24_POLY 0x1864CFBL

long crc_octets_1(unsigned char *octets, long len)
{
    long crc = CRC24_INIT;
    int i;
    while (len) {
        crc ^= (*octets++) << 16;
        for (i = 0; i < 8; i++) {
            crc <<= 1;
            if (crc & 0x1000000)
                crc ^= CRC24_POLY;
        }
        len-=1;
    }
    return crc & 0xFFFFFFL;
}

my swift alternative:

let CRC24_INIT_: Int = 0xB704CE
let CRC24_POLY_: Int = 0x1864CFB

func crc_octets_1( _ octets: UnsafeMutablePointer<UInt8>, _ len: Int) -> Int {
    var octets2 = octets
    var crc = CRC24_INIT;
    var l=len
    while (l != 0) {
        octets2 += 1 //i have also tried incrementing the actual value that is being pointed to. It still doesn't work. I have also tried urinary 
        crc ^= Int(octets2.pointee) << 16;
        for _ in 0..<8 {
            crc <<= 1;
            if ((crc & 0x1000000) != 0) {
                    crc ^= CRC24_POLY;
            }
        }
        l -= 1
    }
    return crc & 0xFFFFFF;
}

Final test

func test() {
    var dataBytes: [UInt8] = [1,2,3,4,5]

    let checksum1 = crc_octets_1(&dataBytes, dataBytes.count)
    let checksum2 = crc_octets_2(&dataBytes, dataBytes.count)
    
    XCTAssertEqual(checksum1, checksum2)
}

this is what I get in return: XCTAssertEqual failed: ("3153197") is not equal to ("1890961")


Solution

  • As Rob Napier pointed out, the problem is where you are incrementing octets2. The Objective-C increments the pointer after retrieving the value and the Swift version is incrementing it before.

    But I might take it a step further and eliminate the unsafe pointers (octets and octets2) altogether, as well as the len and l variables. Instead, just pass the dataBytes array directly:

    func crc(for bytes: [UInt8]) -> Int {
        var crc = CRC24_INIT
        for byte in bytes {
            crc ^= Int(byte) << 16
            for _ in 0..<8 {
                crc <<= 1
                if (crc & 0x1000000) != 0 {
                    crc ^= CRC24_POLY
                }
            }
        }
        return crc & 0xFFFFFF
    }
    

    Or, if you wanted to get fancy, you could make a generic rendition, instead, which will accept any Sequence of UInt8 (i.e., either a [UInt8] array or a Data):

    func crc<T>(for bytes: T) -> Int where T: Sequence, T.Element == UInt8 {
        var crc = CRC24_INIT
        for byte in bytes {
            crc ^= Int(byte) << 16
            for _ in 0..<8 {
                crc <<= 1
                if (crc & 0x1000000) != 0 {
                    crc ^= CRC24_POLY
                }
            }
        }
        return crc & 0xFFFFFF
    }
    

    Then you can do either:

    let dataBytes: [UInt8] = ...
    let checksum1 = crc(for: dataBytes) // 1890961
    

    or

    let data: Data = ...
    let checksum2 = crc(for: data)      // 1890961
    

    In the above, I have also removed the semicolons and used a slightly swiftier method naming convention.