2

I have this two structs:

struct pcap_hdr_s {
    UInt32 magic_number;
    UInt16 version_major;
    UInt16 version_minor;
    int32_t thiszone;
    UInt32 sigfigs;
    UInt32 snaplen;
    UInt32 network;
};
//packet header
struct pcaprec_hdr_s {
    UInt32 ts_sec;
    UInt32 ts_usec;
    UInt32 incl_len;
    UInt32 orig_len;
};

which are initialised as follows(for example):

    let pcapHeader : pcap_hdr_s = pcap_hdr_s(magic_number: 0xa1b2c3d4,
                                            version_major: 2, 
                                            version_minor: 4, 
                                            thiszone: 0, 
                                            sigfigs: 0,
                                            snaplen: 
                                            pcap_record_size, 
                                            network: LINKTYPE_ETHERNET)

    let pcapRecHeader : pcaprec_hdr_s = pcaprec_hdr_s(ts_sec: UInt32(ts.tv_sec),
                                        ts_usec: UInt32(ts.tv_nsec), 
                                        incl_len: plen, 
                                        orig_len: length) 

I tried to create Data/NSData objects of the structs like this:

            //write pcap header
            let pcapHeaderData : NSData = NSData(bytes: pcapHeader, length: sizeofValue(pcapHeader))
            //write pcaprec header
            let pcapRecHeaderData : NSData = NSData(bytes: pcapRecHeader, length: sizeofValue(pcapRecHeader))

but I always get this error for each line:

"Connot convert value if type 'pcap_hdr_s' to expected arguemnt type 'UsafeRawPointer?'"

I had a look at the documentation of UnsafeRawPointers in Swift, but I don't get it enough as for now, to create the NSData object from the structs. Am I on the right way or is there a better one to accomplish my intend?

If this Data initialisation would work, my next steps would be

  • Append pcapRecHeaderData to pcapHeaderData
  • write pcapHeaderData atomically to file/url with the provided function of Data/NSData

EDIT:

//packet ethernet header
struct ethernet_hdr_s {
    let dhost : [UInt8]
    let shost : [UInt8]
    let type : UInt16
};

let src_mac : [UInt8] = [0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB]
let dest_mac : [UInt8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55]

let ethernetHeader : ethernet_hdr_s = ethernet_hdr_s(dhost: dest_mac, shost: src_mac, type: 0x0800)

EDIT 2:

let payloadSize = packet.payload.count
            let plen = (payloadSize < Int(pcap_record_size) ? payloadSize : Int(pcap_record_size));

            bytesWritten = withUnsafePointer(to: &(packet.payload)) {
                $0.withMemoryRebound(to: UInt8.self, capacity: Int(plen)) {
                    ostream.write($0, maxLength: Int(plen))
                }
            }
            if bytesWritten != (Int(plen)) {
                // Could not write all bytes, report error ...
                NSLog("error in Writting packet payload, not all Bytes written: bytesWritten: %d|plen: %d", bytesWritten, Int(plen))
            }
student96
  • 245
  • 3
  • 12

2 Answers2

2

You can write arbitrary data to an InputStream without creating a (NS)Data object first. The "challenge" is how to convert the pointer to the struct to an UInt8 pointer as expected by the write method:

let ostream = OutputStream(url: url, append: false)! // Add error checking here!
ostream.open()

var pcapHeader = pcap_hdr_s(...)
let headerSize = MemoryLayout.size(ofValue: pcapHeader)

let bytesWritten = withUnsafePointer(to: &pcapHeader) {
        $0.withMemoryRebound(to: UInt8.self, capacity: headerSize) {
        ostream.write($0, maxLength: headerSize)
    }
}
if bytesWritten != headerSize {
    // Could not write all bytes, report error ...
}

In the same way you can read data from in InputStream:

let istream = InputStream(url: url)! // Add error checking here!
istream.open()

let bytesRead = withUnsafeMutablePointer(to: &pcapHeader) {
    $0.withMemoryRebound(to: UInt8.self, capacity: headerSize) {
        istream.read($0, maxLength: headerSize)
    }
}
if bytesRead != headerSize {
    // Could not read all bytes, report error ...
}

If the file was possibly created on a different platform with a different byte order then you can check the "magic" and swap bytes if necessary (as described on https://wiki.wireshark.org/Development/LibpcapFileFormat):

switch pcapHeader.magic_number {
case 0xa1b2c3d4:
    break // Already in host byte order
case 0xd4c3b2a1:
    pcapHeader.version_major = pcapHeader.version_major.byteSwapped
    pcapHeader.version_minor = pcapHeader.version_minor.byteSwapped
    // ...
default:
    // Unknown magic, report error ...
}

To simplify the task of writing and reading structs one can define custom extension methods, e.g.

extension OutputStream {

    enum ValueWriteError: Error {
        case incompleteWrite
        case unknownError
    }

    func write<T>(value: T) throws {
        var value = value
        let size = MemoryLayout.size(ofValue: value)
        let bytesWritten = withUnsafePointer(to: &value) {
            $0.withMemoryRebound(to: UInt8.self, capacity: size) {
                write($0, maxLength: size)
            }
        }
        if bytesWritten == -1 {
            throw streamError ?? ValueWriteError.unknownError
        } else if bytesWritten != size {
            throw ValueWriteError.incompleteWrite
        }
    }
} 

extension InputStream {

    enum ValueReadError: Error {
        case incompleteRead
        case unknownError
    }

    func read<T>(value: inout T) throws {
        let size = MemoryLayout.size(ofValue: value)
        let bytesRead = withUnsafeMutablePointer(to: &value) {
            $0.withMemoryRebound(to: UInt8.self, capacity: size) {
                read($0, maxLength: size)
            }
        }
        if bytesRead == -1 {
            throw streamError ?? ValueReadError.unknownError
        } else if bytesRead != size {
            throw ValueReadError.incompleteRead
        }
    }
}

Now you can write and read simply with

try ostream.write(value: pcapHeader)
try istream.read(value: &pcapHeader) 

Of course this works only with "self-contained" structs like your pcap_hdr_s and pcaprec_hdr_s.

Martin R
  • 488,667
  • 78
  • 1,132
  • 1,248
  • Thanks for this great answer, but I got stuck at the point where I pass the OutputStream the file url. Has the file to be created before passing the url to the Outputstream or is it created by the stream itself? – student96 Jun 05 '17 at 09:05
  • @student96: I assume that has been clarified now :) – Martin R Jun 05 '17 at 10:26
  • Yes, thanks for the other answer too. I though it is not the same issue so I created a new thread for this ;) – student96 Jun 05 '17 at 10:34
  • Do you have a clue why the ethernet_hdr_s struct, which is initialised like in the Edit part of my question, has a length of 10 instead of 14 in this line: let ethernetheaderSize = MemoryLayout.size(ofValue: packet.ethernetHeader) – student96 Jun 05 '17 at 23:34
  • @student96: `[UInt8]` is a Swift array: a fixed-sized struct with (opaque) pointers to the element storage. A C array is imported into Swift as a tuple: `(UInt8, ..., UInt8)`. – But actually that is quite a different question now :) – Martin R Jun 06 '17 at 05:21
  • I'm not sure I get what you want to say with that, but my calculation gives me 14 bytes, because dhost is initialised with 6 UInt8's, which is equivalent to 6 bytes, plus 6 for shost und 2 for the 16 bit (= 2byte) type.The problem is that Memorysize of dhost or shost return a value of 4 instead of 6. Do you know why this is? Or should I create a new thread for this issue? – student96 Jun 06 '17 at 08:35
  • @student96: A Swift array is not the same as a C array. To get the same memory layout, define the variable as a 6-element tuple: `dhost: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)` or better, define the struct as C struct and import it into Swift. – Martin R Jun 06 '17 at 08:48
  • You are right again. One last question: If I want to write a Data object after the two structs, should I use the same way, but instead of using MemoryLayout.size() take the object.count value as size? (see Edit 2) – student96 Jun 06 '17 at 09:31
  • @student96: Sorry, your question is not clear to me. Why don't you try it yourself? Otherwise I would suggest to start a new thread. – Martin R Jun 06 '17 at 09:45
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/145974/discussion-between-student96-and-martin-r). – student96 Jun 06 '17 at 12:58
1

You can convert pcap_hdr_s to Data and vice versa in Swift 3 with

  • pcap_hdr_s -> Data

    var pcapHeader : pcap_hdr_s = pcap_hdr_s(magic_number ...
    
    let data = withUnsafePointer(to: &pcapHeader) {
         Data(bytes: UnsafePointer($0), count: MemoryLayout.size(ofValue: pcapHeader))
    }
    
  • Data -> pcap_hdr_s

    let header: pcap_hdr_s = data.withUnsafeBytes { $0.pointee }
    

Reference: round trip Swift number types to/from Data

vadian
  • 232,468
  • 27
  • 273
  • 287
  • Saving data like that will break if (well, when) the ABI changes – Alexander Jun 04 '17 at 17:54
  • @Alexander What is `ABI` ? – vadian Jun 04 '17 at 17:57
  • ABI is "Application binary interface". – @Alexander: The pcap_hdr_s structure (which is documented at https://wiki.wireshark.org/Development/LibpcapFileFormat) has a "magic" and version numbers which should be sufficient to protect against reading incompatible data. – Martin R Jun 04 '17 at 18:17