RandomBits
RandomBits

Reputation: 4284

Swift 4 Int32 to [UInt8]

I am trying to get the bytes from an integer into an [UInt8] to send them over a wire protocol. While I found answers that work for Swift 2/3, none of the solutions work for Swift 4.

The following snippet works to encode a message for small message sizes (just the raw string data prepended with a network byte order Int32 size):

func send(message: String) {
    let messageSize = message.utf8.count
    let encodedMessageSize = Int32(messageSize).bigEndian
    let frameSize = messageSize + 4

    var buffer: [UInt8] = Array()
    buffer.append(0)
    buffer.append(0)
    buffer.append(0)
    buffer.append(UInt8(messageSize))
    buffer.append(contentsOf: message.utf8)
    outputStream.write(buffer, maxLength: frameSize)
}

I have also tried using raw pointers directly, but cannot get anything to work for Swift 4 along that avenue either.

The overall tasks is to encode and frame messages that consist of integers and strings. The encoding converts everything to strings and adds a null at the end of each string. The framing simply prepends the message with a network byte order Int32 size. I cannot change the protocol, but am willing to consider other approaches to achieving this end.

cheers,

[EDIT] Updated code using @MartinR's code (with @Hamish's suggestion). Also made some progress of the overall task in the mean time.

func encodeMessagePart(_ message: String) -> [UInt8] {
    var buffer: [UInt8] = Array(message.utf8)
    buffer.append(0)
    return buffer
}

func encodeMessagePart(_ message: Int) -> [UInt8] {
    return encodeMessagePart("\(message)")
}

func frameMessage(_ buffer: [UInt8]) -> [UInt8] {
    let bufferSize = buffer.count
    var encodedBufferSize = Int32(bufferSize).bigEndian
    let encodedBufferSizeData = withUnsafeBytes(of: &encodedBufferSize) { Data($0) }

    var frame: [UInt8] = Array()
    frame.append(contentsOf: encodedBufferSizeData)
    frame.append(contentsOf: buffer)
    return frame
}

func sendMessage(_ buffer: [UInt8]) {
    let frame = frameMessage(buffer)
    outputStream.write(frame, maxLength: frame.count)
}

func sendMessage(_ message: String) {
    let encodedPart = encodeMessagePart(message)
    sendMessage(encodedPart)
}

//    func sendMessage(_ messages: Encodable...) {
//        var buffer: [UInt8] = Array()
//        for message in messages {
//            let b = encodeMessagePart(message)
//            buffer.append(contentsOf: b)
//        }
//        sendMessage(buffer)
//    }

Upvotes: 1

Views: 568

Answers (1)

Martin R
Martin R

Reputation: 539745

You can create a Data value from the integer with

let encodedMessageSize = Int32(messageSize).bigEndian
let data = withUnsafeBytes(of: encodedMessageSize) { Data($0) }

(In Swift versions before 4.2 you'll have to write

var encodedMessageSize = Int32(messageSize).bigEndian
let data = withUnsafeBytes(of: &encodedMessageSize) { Data($0) }

instead.)

The data can then be appended to the array with

buffer.append(contentsOf: data)

Alternatively you can use a data buffer instead of an array:

func send(message: String) {
    let messageSize = message.utf8.count
    let encodedMessageSize = Int32(messageSize).bigEndian

    var data = withUnsafeBytes(of: encodedMessageSize) { Data($0) }
    data.append(Data(message.utf8))

    let amountWritten = data.withUnsafeBytes { [count = data.count] in
        outputStream.write($0, maxLength: count)
    }
}

Finally note that that the write() method might write less bytes than provided (e.g. on network connections), so you should always check the return value.

Upvotes: 2

Related Questions