Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-05-19

OSX Receipt validation in Swift, part 5: The Device GUID

During receipt verification there is a point where we need the device GUID, a unique identifier for the Mac.On OS-X this is the MAC address of the primary network card. You can read about this here: https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW14
If that link is stale, try searching for "validating receipts locally" that should get you to the necessary article.

In that article there is a code example that shows you how to read the GUID. There is IMO little sense in translating it to Swift, so I suggest that you grab the code from Apple and create an Objective-C header/implementation pair of files as follows:

The Objective-C header file:

//
//  deviceGuid.h
//

#ifndef MyGreatApp_deviceGuid_h
#define MyGreatApp_deviceGuid_h

#import <Foundation/Foundation.h>

CFDataRef copy_mac_address(void);

#endif

The Objective-C implementation file:

//
//  deviceGuid.m
//

#import "deviceGuid.h"

#import <IOKit/IOKitLib.h>


// Returns a CFData object, containing the computer's GUID.
CFDataRef copy_mac_address(void)
{
    kern_return_t             kernResult;
    mach_port_t               master_port;
    CFMutableDictionaryRef    matchingDict;
    io_iterator_t             iterator;
    io_object_t               service;
    CFDataRef                 macAddress = nil;
    
    kernResult = IOMasterPort(MACH_PORT_NULL, &master_port);
    if (kernResult != KERN_SUCCESS) {
        // printf("IOMasterPort returned %d\n", kernResult);
        return nil;
    }
    
    matchingDict = IOBSDNameMatching(master_port, 0, "en0");
    if (!matchingDict) {
        // printf("IOBSDNameMatching returned empty dictionary\n");
        return nil;
    }
    
    kernResult = IOServiceGetMatchingServices(master_port, matchingDict, &iterator);
    if (kernResult != KERN_SUCCESS) {
        // printf("IOServiceGetMatchingServices returned %d\n", kernResult);
        return nil;
    }
    
    while((service = IOIteratorNext(iterator)) != 0) {
        io_object_t parentService;
        
        kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane,
                                                   &parentService);
        if (kernResult == KERN_SUCCESS) {
            if (macAddress) CFRelease(macAddress);
            
            macAddress = (CFDataRef) IORegistryEntryCreateCFProperty(parentService,
                                                                     CFSTR("IOMACAddress"), kCFAllocatorDefault, 0);
            IOObjectRelease(parentService);
        } /* else {
            printf("IORegistryEntryGetParentEntry returned %d\n", kernResult);
        }*/
        
        IOObjectRelease(service);
    }
    IOObjectRelease(iterator);
    
    return macAddress;

}

As you can see, I removed the printing parts as I don't want that.

Since the Foundation headers are needed, the above code must be placed in Objective-C files and not as C files. If C files are used, the compiler will complain about "Could not build module Foundation".

The above operation returns either 'nil' or a pointer to the appropriate CFData object. In Swift this can be used as follows:

let guidDataOrNil = copy_mac_address()
if guidDataOrNil == nil {
    println("Could not retrieve the device GUID")
    exit(errorCode)
}
let guidData: NSData = guidDataOrNil.takeRetainedValue()

log.atLevelDebug(id: 0, source: "Main", message: "Device GUID retrieved OK")

Note the "takeRetainedValue". In Xcode the editor shows that the function returns the type "Unmanaged<CFData>" (in accordance with the Apple approach that an object returned by a "Create" API has a retain count of 0). The CFData is toll-free bridged to a NSData, but we need to retain the object so that ARC can delete it when it is no longer needed.

Next up, part 6: Example Swift code for receipt validation & verification

Happy Coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment