Some observations on SIMD performance on A7
static inline v64x2_t vmull(v32x2_t x, v32x2_t y){ v64x2_t v; v.r = x.r * (uint64_t)y.r; v.i = x.i * (uint64_t)y.i; return v; }
static inline v64x2_t vmull(v32x2_t x, v32x2_t y){ v64x2_t v; v.r = x.r * (uint64_t)y.r; v.i = x.i * (uint64_t)y.i; return v; }
size_t i;
OCTET_STRING_t *iap[2];
int iap_cnt=0;
for (i = 0; i < payload->list.count; i++) {
ReceiptAttribute_t *entry;
entry = payload->list.array[i];
switch (entry->type) {
case 2:
bundle_id = &entry->value;
break;
case 3:
bundle_version = &entry->value;
break;
case 4:
opaque = &entry->value;
break;
case 5:
hash = &entry->value;
break;
case 17:
iap[iap_cnt]=&entry->value;
iap_cnt ++ ;
break;
}
}
The iap[] array should be large enough to hold references to all possible in-app purchases, i.e. the total number of products configured for the app. Each of the OCTET_STRING_t references now hold a new ASN.1 structure that holds the information on the in-app purchase. It is important that the free_struct call is not called before these new structures have been parsed. To parse the structures, a function like the following can be used:
static inline void getIAP(OCTET_STRING_t **iap, int iap_cnt){
Payload_t *payload=NULL;
asn_dec_rval_t rval;
int i,n;
for (n=0; n < iap_cnt; n++){
OCTET_STRING_t *this_iap = iap[n];
payload = NULL;
rval = asn_DEF_Payload.ber_decoder(NULL,&asn_DEF_Payload,(void **)&payload, this_iap->buf,this_iap->size,0);
if (rval.code != RC_OK){
NSLog(@"Failed to decode payload at index %d",n);
asn_DEF_Payload.free_struct(&asn_DEF_Payload,payload,0);
continue;
}
for (i=0; i < payload->list.count; i++){
ReceiptAttribute_t *entry;
entry = payload->list.array[i];
switch (entry->type) {
case AR_IAP_PRODUCT_ID:{
const char *id1 = "com.yourdomain.yourproduct1";
const char *id2 = "com.yourdomain.yourproduct2";
if (strcmp(id1, (char *)entry->value.buf+2)==0){
enableFeature(0);
}
if (strcmp(id2, (char *)entry->value.buf+2)==0){
enableFeature(1);
}
}
break;
default:
break;
}
}
asn_DEF_Payload.free_struct(&asn_DEF_Payload,payload,0);
}
}
It is important that the payload is set to NULL for each iteration - otherwise it is not possible to free the payload in each iteration. Note that the OCTET_STRINGs should be parsed from offset 0, while the product IDs are found from offset 2. After this function is called, the full app receipt structure can be freed.
With iOS7, Apple has introduced the app receipt to allow a developer to check if the app is authorized to run on the device it is running, and to track non-consumable in-app purchases. The last part is interesting because it solves a lot of problems in persisting the presence of in-app purchases in a secure way. Also, it automatically transfers in-app purchases to new devices if an App is downloaded there.
Unfortunately, the handling and validation of the receipt is not very well documented - important bits are missing from the documentation. However please read the Receipt Validation Programming Guide as this post refers a lot to that . This is my attempt at clearing up some of the points. The goal for this blog post is not to provide complete code, but to clear out some of the questions one has after reading the documentation
A note before we start: REMEMBER to log out of the iTunes store before running this code, and to log in using a test account. You can set up a test account in iTunes connect. And do this every time you run your app until the app is in the iTunes store. Also - check each and every step before continuing - it is very hard to debug the code in big-bang fashion.
The receipt can be loaded from the app bundle by the following code:
NSURL *url = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *data = [NSData dataWithContentsOfURL:url];
If data ends up being nil, the receipt is missing. This should never happen in a production environment, but will happen in the sandbox environment used during development.
In a development environment, the receipt can be fetched by code akin to the following code:
- (void) getReceipt
{
self.recreq = [[SKReceiptRefreshRequest alloc] init];
self.recreq.delegate = self;
[self.recreq start];
}
- (void)requestDidFinish:(SKRequest *)request
{
if ([request isEqual:self.recreq]){
NSLog(@"Got receipt");
}
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
NSLog(@"Did not get receipt");
}
The class containing this code must conform to SKRequestDelegate.
NSData *cert = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"AppleIncRootCertificate" withExtension:@"cer"]];
This allows you to fill an important missing piece in the receipt validation code, namely the loading of the Apple certificate.
Note: In general it may not be a good idea to use objective-c in the code that validates the receipt, as it is much easier to disassemble. If you are paranoid enough, you should stick to core foundation. The same code to get the data is shown here using core foundation.
CFBundleRef bundle = CFBundleGetMainBundle();
const char *resname = "AppleIncRootCertificate";
const char *resext = "cer";
CFStringRef rname = CFStringCreateWithCString(NULL, resname, kCFStringEncodingASCII);
CFStringRef rext = CFStringCreateWithCString(NULL, resext, kCFStringEncodingASCII);
CFURLRef url = CFBundleCopyResourceURL(bundle, rname, rext, NULL);
unsigned char fname[1024];
CFURLGetFileSystemRepresentation(url, 0, fname, 1024);
uint8_t certdata[2048];
int certlen;
int fid = open((const char*)fname, O_RDONLY);
certlen=read(fid, certdata, 2048);
close(fid);
CFRelease(url);
CFRelease(rext);
CFRelease(rname);
And then thank your favorite celestial being that Objective-c was invented.
#import <openssl/pkcs7.h>
#import <openssl/x509.h>
#import <openssl/bio.h>
and how to initialize the p7 and Apple variables (note that the Apple variable is initialized differently here than what Apple suggests):
b_p7 = BIO_new_mem_buf(receiptdata, receiptlen);
...
const unsigned char *cdata = certdata;
Apple = d2i_X509(NULL, &cdata, certlen);
If you have not worked with OpenSSL before, you will be bitten by this one: Remember to start your use of the library with OpenSSL_add_all_algorithms() and end it with EVP_cleanup();
The final step is to get the payload out from the receipt. The payload is found in the b_out BIO. As far as I know there is no way of knowing how big it is, except it is smaller than the receipt itself. Hence we use the following construct to get the data out:
uint8_t *pld = malloc(receiptlen);
int pld_sz = BIO_read(b_out, payload, receiptlen);
const char *ref = "1.1";
const char *tst = (const char *)bundle_version->buf+2;
if (strcmp(ref,tst)){FAIL};
Since the name and version are hardcoded, remember to get it right.
NSUUID *vendorID = [[UIDevice currentDevice] identifierForVendor];
uuid_t vendorIDBytes;
[vendorID getUUIDBytes:vendorIDBytes];
uuid_t is a pointer to 16 bytes, so that makes it easy to complete listing 1-7.
When doing the comparison, there is no offset of the value, so the correct comparison is
if (memcmp(hash->buf,digest)){FAIL}
asn_DEF_Payload.free_struct(&asn_DEF_Payload, payload,0);
In the next installment I will go through how to check the status of the in-app purchases using the receipt.
Many places on the web there are questions about how to get hold of the frame buffer on an iOS device. The answer is always the same: "There is no frame buffer". Usually the question reflects the wish to do fast blit'ing of bitmaps generated on the fly to the screen. That variant of the question is usually answered with a "Quartz is too slow for that" followed by some muttering about OpenGL, textures and quads while people wander off. I have not seen any working code coming from these answers.
While it is true that there is no direct access to the frame buffer on an iOS device one can get pretty darn close without resorting to OpenGL, and also with fairly good results (60 fps can be upheld provided your generator code is fast enough). Here is how:
QuartzCore has the ability to generate a bitmap object backed by user-accessible memory. So for off-screen purposes there is a way to generate a "frame buffer" in the form of a bitmap object. The creation function is documented here, and the following is an example of a function that creates this given a CGRect with width and height:
CGColorSpaceRef csp = CGColorSpaceCreateDeviceRGB();
CGContextRef _context = CGBitmapContextCreate(NULL,
(size_t)frame.size.width, (size_t)frame.size.height,
8, 4*(size_t)frame.size.width, csp,
kCGImageAlphaNoneSkipFirst);
CGColorSpaceRelease(csp);
NULL means that Quartz will allocate the memory. This frees you from memory management and also allows Quartz to optimize the location. The last argument means that the bitmap is stored ARGB in memory (byte 0 is alpha, 1 is red, 2 is green and 3 is blue etc. etc). and that the alpha-component is ignored. This is done to improve performance, but it means that the image will always be opaque. Using the following code
uint32_t *framebuffer = CGBitmapContextGetData(_context);
you get access to the color components (4 bytes each) of the bitmap. the frame buffer is an array which is width*height in size, and where each row is following each other (i.e. the index to the array is row*width+column for a given pixel at location (row,column)). The array starts in upper-left corner.
The bitmap context is the first element of the frame buffer emulation. This can be converted to an image by:
CGImageRef img = CGBitmapContextCreateImage(_context);
The documentation will tell you that you can draw this image using CGContextDrawImage() to draw it in a UIView drawRect: method. This is where Quartz gets its reputation for being slow from - because that is not a very fast operation.
The key to a fast frame buffer-like object on iOS is to use a CALayer. This is the basic building block in Quartz on iOS, and is nothing more than a cached CGImageRef that the graphics subsystem can render (you can probably already see where this is heading). The documentation is not very clear on this but it is possible (and very easy) to access a CALayers backing store. It is done using the contents property. To assign a new CGImage to a CALayer you simply do this:
CGImageRef img = CGBitmapContextCreateImage(_context);
layer.contents = (__bridge id)img;
CGImageRelease(img);
The first line obtains a CGImage from the bitmap as described above. Second line assigns this to a CALayer content property. This property is of type id but any CFType (which CGImage is) can be cast to id. the __bridge indicates that the memory management must be handled by Objective-c from this point, hence the image must be released again (last line).
That is pretty much it. The key to keeping the speed up is to not allocate a new bitmap over and over, but only obtain a new img every time the bitmap is updated on-screen and then change the backing store in-between. CGBitmapContextCreateImage() is a low-overhead operation whereas CGBitmapContextCreate() is not. The content property can be updated from anywhere in your program and the layer will update on-screen accordingly next time the screen is rendered.
Last part of this is to add the CALayer that you use for your bitmap to some view using
[view.layer addSublayer:layer]
All this can be wrapped up in a small CALayer subclass ().
#import <QuartzCore/QuartzCore.h>
#import <stdint.h>
@interface RSFrameBufferLayer : CALayer
// Class method to create a new layer with an underlying
// bitmap. Both will have the size set by the frame
+ (RSFrameBufferLayer *)layerWithFrame:(CGRect)frame;
// Same as above
- (id)initWithFrame:(CGRect)frame;
// Draw bitmap to screen
- (void)blit;
// Get the underlying context to use for higher-level
// drawing operations in Quartz
@property(readonly) CGContextRef context;
// Get the raw "frame buffer"
@property(readonly) uint32_t *framebuffer;
@end
The implementation is very simple:
#import "RSFrameBufferLayer.h"
@implementation RSFrameBufferLayer
@synthesize context = _context;
+ (RSFrameBufferLayer *)layerWithFrame:(CGRect)frame
{
return [[[RSFrameBufferLayer alloc] initWithFrame:frame]
autorelease];
}
- (id)initWithFrame:(CGRect)frame
{
if (self=[super init]){
self.opaque = YES;
self.frame=frame;
}
return self;
}
- (void)dealloc
{
CGContextRelease(_context);
[super dealloc];
}
- (void)blit
{
CGImageRef img = CGBitmapContextCreateImage(_context);
Self.contents = (__bridge id)img;
CGImageRelease(img);
}
-(void)setFrame:(CGRect)frame
{
CGRect oldframe = self.frame;
[super setFrame:frame];
if (frame.size.width != oldframe.size.width ||
frame.size.height != oldframe.size.height){
if (_context){
CGContextRelease(_context);
}
CGColorSpaceRef csp = CGColorSpaceCreateDeviceRGB();
_context = CGBitmapContextCreate(NULL,
(size_t)frame.size.width,
(size_t)frame.size.height, 8,
4*(size_t)frame.size.width, csp,
kCGImageAlphaNoneSkipFirst);
CGColorSpaceRelease(csp);
}
}
-(uint32_t *)framebuffer
{
return CGBitmapContextGetData(_context);
}
@end