swift3 - Error creating UnsafeMutablePointer<UInt8>

Getting an error trying to create UnsafeMutablePointer<UInt8> for the CFDataCreate below.



let serverIP:String = addressInfo[0]

let serverIPCString = serverIP.cString(using: String.Encoding.ascii)

let binaryAddress:UnsafeMutablePointer<in_addr_t> = UnsafeMutablePointer<in_addr_t>.allocate(capacity: 1)

inet_pton(AF_INET, serverIPCString!, binaryAddress)

serverSocketAddrPointer.pointee.sin_len = __uint8_t(MemoryLayout<sockaddr_in>.size)

serverSocketAddrPointer.pointee.sin_family = sa_family_t(AF_INET)

serverSocketAddrPointer.pointee.sin_port = in_port_t(serverPort.bigEndian)

serverSocketAddrPointer.pointee.sin_zero = (0,0,0,0,0,0,0,0)

serverSocketAddrPointer.pointee.sin_addr = in_addr(s_addr: binaryAddress.pointee)


let connectServerAddrPointer:UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>(serverSocketAddrPointer)

error: ‘init’ is unavailable: use ‘withMemoryRebound(to:capacity:_)’ to temporarily view memory as another layout-compatible type.


let connectAddr = CFDataCreate(kCFAllocatorDefault, connectServerAddrPointer, MemoryLayout<sockaddr_in>.size)


How can I correct this for Swift3?

Replies

What are you actually trying to do with this code? Specifically, what data are you starting with? And what data are you trying to end up with?

There’s an obvious solution here (rebind the address to a new type, as suggested by the fix it) but there’s a more likely to be a better solution (once that’s both easier to write and does not hard code IPv4).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I am trying to initiate a UDP connection to network devices for monitoring. For this connection, user input of an IP or host name would be passed thru my CFHost function for DNS resolution returning an array of address(es). The first IPv4 address in that array would then be passed to udpConnection below.



Original code that worked on Swift2.3:

var isConnected:Bool = false

var socketRef:CFSocketRef?

var serverPort = UInt16(161)

var serverSocketAddrPointer:UnsafeMutablePointer<sockaddr_in> = UnsafeMutablePointer<sockaddr_in>.alloc(1)

var connectError = ""


func udpConnection(addressInfo: [String], timeout: Double) -> (isConnected: Bool, error: String) {

if false == isConnected {

socketRef = CFSocketCreate(kCFAllocatorDefault, AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0, nil, nil)

if (socketRef != nil) {

let serverIP:String = addressInfo[0]

let serverIPCString = serverIP.cStringUsingEncoding(NSASCIIStringEncoding)

let binaryAddress:UnsafeMutablePointer<in_addr_t> = UnsafeMutablePointer<in_addr_t>.alloc(1)

inet_pton(AF_INET, serverIPCString!, binaryAddress)

serverSocketAddrPointer.memory.sin_len = __uint8_t(sizeof(sockaddr_in))

serverSocketAddrPointer.memory.sin_family = sa_family_t(AF_INET)

serverSocketAddrPointer.memory.sin_port = in_port_t(serverPort.bigEndian)

serverSocketAddrPointer.memory.sin_zero = (0,0,0,0,0,0,0,0)

serverSocketAddrPointer.memory.sin_addr = in_addr(s_addr: binaryAddress.memory)

let connectServerAddrPointer:UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>(serverSocketAddrPointer)

let connectAddr = CFDataCreate(kCFAllocatorDefault, connectServerAddrPointer, sizeof(sockaddr_in))!

let connectionResult = CFSocketConnectToAddress(socketRef, connectAddr, CFTimeInterval(timeout))

if connectionResult == CFSocketError.Success {

isConnected = true

connectError = "NoError"

} else if connectionResult == CFSocketError.Error {

connectError = "An Error has occurred"

} else if connectionResult == CFSocketError.Timeout {

connectError = "Connection Attempt Timed Out"

} else {

connectError = "Unable to connect socket"

}

}

} else {

if (socketRef != nil) {

CFSocketIsValid(socketRef)

}

}

return (isConnected, connectError)

}

CFHost gives you back an array of CFDatas, each containing a

sockaddr
.
CFSocketConnectToAddress
takes a CFData containing a
sockaddr
. Gluing these two together should be relatively straightforward, and (mostly) does not require you to grovel around inside the
sockaddr_***
types.

Consider this.

let host: CFHost = … a resolved CFHost …
let a1: Unmanaged<CFArray>? = CFHostGetAddressing(host, nil)
guard let a2: Unmanaged<CFArray> = a1 else {
    … handle error …
}
let a3: CFArray = a2.takeUnretainedValue()
let addresses: [CFData] = a3 as! [CFData]
for address in addresses {
    let family = CFDataGetBytePtr(address)!.withMemoryRebound(to: sockaddr.self, capacity: 1) {
        return $0.pointee.sa_family
    }
    guard let s = CFSocketCreate(nil, Int32(family), SOCK_DGRAM, 0, 0, nil, nil) else {
        … handle error …
    }

    let err = CFSocketConnectToAddress(s, address, 0.0)
    switch err {
        case .success:
            … handle success …
        case .error:
            … handle error …
        case .timeout:
            … handle timeout …
    }
}

There’s a couple of weird things here:

  • Lines 2 through 7 are complex because

    CFHostGetAddressing
    is quite unpleasant to use from Swift. I recommend you file a bug requesting that it be made more Swift friendly. For the moment, you could write your own wrapper function that abstracts this away.
  • Lines 9 through 11 is the only place where I have to grovel around within the CFData, to extract the

    sa_family
    value.

Finally, this code will support IPv6 out of the box; if you don’t want that, you can filter the addresses based on the ‘family’ value.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

How would I include that I want to connect via UDP port 161?

var serverPort = UInt16(161)

in_port_t(serverPort.bigEndian)

Ah, yeah, that’s a pain. With the standard BSD Sockets resolver,

getaddrinfo
, you can specify a port via its ‘service name’ parameter. Unfortunately CFHost has no such facility. Without that, you have to write the annoying address-family-specific code that I was trying to avoid. To wit:
let address: CFData = … from the previous example …
let addressWithPort = CFDataCreateMutableCopy(nil, 0, address)!
CFDataGetMutableBytePtr(addressWithPort)!.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in
    switch Int32(sa.pointee.sa_family) {
        case AF_INET:
            sa.withMemoryRebound(to: sockaddr_in.self, capacity: 1) { sin in
                sin.pointee.sin_port = (161 as UInt16).bigEndian
            }
        case AF_INET6:
            sa.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) { sin6 in
                sin6.pointee.sin6_port = (161 as UInt16).bigEndian
            }
        default:
            fatalError()
    }
}

Honestly, in your shoes I might just switch to

getaddrinfo
. If you do make that switch, however, remember that
getaddrinfo
is a synchronous blocking networking call, and thus can’t be made on the main thread.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"