Как преобразовать метод Obj-C с байтами NSData с помощью побитовых операторов в Swift

Я пытаюсь преобразовать метод Objective C в Swift, но для этого мне нужна помощь в понимании того, что происходит.

Для контекста входные данные для этого метода начинаются как NSString, который затем преобразуется в объект NSData с кодировкой utf8. Затем байты этого объекта данных передаются в метод (параметр message).

Возвращаемое значение этого метода затем отправляется через writeData на определенную характеристику CBPeripheral.

Вот что я понимаю (с точки зрения Swift).

  • Переданное сообщение представляет собой массив из UInt8 байтов: [UInt8]
  • 3 переменные создаются как UInt32 значений; crcVal, byteVal и mask
  • Затем crcVal устанавливается на максимальное значение типа UInt32
  • каждый байт из переданного массива UInt8 байтов затем зацикливается, и выполняется какая-то операция для создания и добавления к конечному результату значения crcVal, которое в конечном итоге используется для отправки через команду CoreBluetooth writeData в CBPeripheral.

Я не очень уверен в побитовых операторах или в том, почему приведенный ниже метод делает то, что делает, поскольку нет документации. Может ли кто-нибудь помочь прояснить некоторые или все части этого? Я надеюсь написать версию этого метода, эквивалентную Swift. Спасибо.

- (uint32_t) computeCRC:(uint8_t *)message len:(NSUInteger)msgLen {

    uint32_t crcVal = 0, byteVal, mask;

    crcVal = 0xFFFFFFFF;
    for (int i=0; i<msgLen; i++) {
        byteVal = message[i];
        crcVal ^= byteVal;
        for (int j=7; j>=0; j--) {
            mask = -(crcVal&1);
            crcVal = (crcVal>>1)^(0xEDB88320 & mask);
        }
    }

    return ~crcVal;
}

person Aaron    schedule 01.06.2018    source источник
comment
Вы можете попробовать это здесь: objectivec2swift.com/#/converter   -  person DonMag    schedule 01.06.2018
comment
почему бы вам не использовать эту функцию crc32 в библиотеке zlib, которая уже есть?   -  person Sulthan    schedule 01.06.2018


Ответы (2)


Вместо того, чтобы писать реализацию с нуля, почему бы вам не использовать zlib-реализацию crc32?:

import zlib

func crc32(from data: Data) -> UInt {
    return data.withUnsafeBytes { (buffer: UnsafePointer<Bytef>) -> UInt in
       return zlib.crc32(0, buffer, uInt(data.count))
    }
}

Однако, чтобы помочь вам понять побитовые операции:

func computeCRC(message: [UInt8]) -> UInt32 {
    var crc: UInt32 = 0xFFFFFFFF

    for byte in message {
        crc ^= UInt32(byte)
        for _ in 0 ..< 8 {
            // negation using 2-complement: -x = ~x + 1
            // use &+ addition with overflow
            let mask = ~(crc & 1) &+ 1
            crc = (crc >> 1) ^ (0xEDB88320 & mask)
        }
    }

    return ~crc
}

Обратите внимание, что нам не нужно передавать количество байтов в Swift. Реализации имеют разные сигнатуры и возвращают немного другой тип, но обе они дают одинаковый результат.

person Sulthan    schedule 01.06.2018
comment
Я не знал, что CRC был обычным явлением. Я думал, что это какой-то проприетарный криптографический метод. Теперь, когда я знаю, что он запекается, я обязательно воспользуюсь им. Спасибо! - person Aaron; 02.06.2018

Для расчета CRC16 вы можете использовать

import Foundation

extension Data {

    enum CRCType {
        case MODBUS
        case ARC
    }

    static func crc16(_ data: [UInt8], type: CRCType) -> UInt16? {
        if data.isEmpty {
            return nil
        }
        let polynomial: UInt16 = 0xA001 // A001 is the bit reverse of 8005
        var accumulator: UInt16
        // set the accumulator initial value based on CRC type
        if type == .ARC {
            accumulator = 0
        } else {
            // default to MODBUS
            accumulator = 0xFFFF
        }
        // main computation loop
        for byte in data {
            var tempByte = UInt16(byte)
            for _ in 0 ..< 8 {
                let temp1 = accumulator & 0x0001
                accumulator = accumulator >> 1
                let temp2 = tempByte & 0x0001
                tempByte = tempByte >> 1
                if (temp1 ^ temp2) == 1 {
                    accumulator = accumulator ^ polynomial
                }
            }
        }
        return accumulator
    }
}

Источник здесь

person hbk    schedule 02.06.2018
comment
если у вас есть enum, используйте switch, тогда вам не понадобятся комментарии, такие как default to MODBUS, когда нет фактического значения по умолчанию, только второе значение. Зачем возвращать nil для пустых данных? - person Sulthan; 02.06.2018