How to receive data from APNS feedback server in objective c - ios

Hello I'm trying to to something rather simple I think.
I have made an cocoa application that sends data using APNS, getting the tokens from my database, everything is set up and running perfect.
Now I want to check the APNS feedback server and remove any tokens received from my database.
I have found dozens of examples in php, javascript and so forth, but nothing in Objective C. I have read the programming guide from apple but can't figure out how to do it.
I am establishing a connection to APNS feedback but I don't know how to read the data.
I'm new to cocoa so please explain in detail :)
This is how I connect to the feedback server, it's the same way I connect when sending, just using another host.
- (void)connectToFeedBackServer
{
if(self.certificate == nil)
{
return;
}
NSString *feedBackHost = #"feedback.push.apple.com";
const char *cHost = [feedBackHost UTF8String];
NSLog(#"The size of cHost is: %lu", strlen(cHost));
NSLog(#"Host is: %s", cHost);
// Define result variable.
OSStatus result;
// Establish connection to server.
PeerSpec peer;
result = MakeServerConnection(cHost, 2196, &socket, &peer);
//NSLog(#"MakeServerConnection(): %d", result);
// Create new SSL context.
result = SSLNewContext(false, &context); //NSLog(#"SSLNewContext(): %d", result);
// Set callback functions for SSL context.
result = SSLSetIOFuncs(context, SocketRead, SocketWrite);
// NSLog(#"SSLSetIOFuncs(): %d", result);
// Set SSL context connection.
result = SSLSetConnection(context, socket);
// NSLog(#"SSLSetConnection(): %d", result);
// Set server domain name.
//result = SSLSetPeerDomainName(context, cHost, sizeof(cHost));
NSLog(#"SSLSetPeerDomainName(): %d", result);
result = SSLSetPeerDomainName(context, cHost, strlen(cHost));
result = SecIdentityCopyCertificate(_theIdentity, &(certificate));
// Set client certificate.
CFArrayRef certificates = CFArrayCreate(NULL, (const void **)&_theIdentity, 1, NULL);
result = SSLSetCertificate(context, certificates);// NSLog(#"SSLSetCertificate(): %d", result);
CFRelease(certificates);
// Perform SSL handshake.
do
{
result = SSLHandshake(context); NSLog(#"SSLHandshake(): %d", result);
} while(result == errSSLWouldBlock);
}
And how I try to read the data and save the received the tokens in an array
- (NSMutableArray *)CheckFeedBackServer
{
char feedback[38];
size_t feedBackSize = sizeof(feedback);
size_t processed = 0;
NSMutableData *feedbackData = [[NSMutableData alloc]init];
NSString *token = [[NSString alloc]init];
NSMutableArray *tokenArray = [[NSMutableArray alloc]init];
[self connectToFeedBackServer];
while ([self getSSLContext])
{
int bytesLength = SSLRead([self getSSLContext], &feedback, feedBackSize, &processed);
[feedbackData appendBytes:feedback length:bytesLength];
while ([feedbackData length] > 38)
{
NSData *deviceToken = [NSData dataWithBytes:[feedbackData bytes] + 6 length:32];
token = [self deviceTokenToString:deviceToken];
[tokenArray addObject:token];
[feedbackData replaceBytesInRange: NSMakeRange(0, 38) withBytes: "" length: 0];
}
}
return tokenArray;
}
- (NSString *)deviceTokenToString: (NSData *)deviceToken;
{
NSString *tmpToken = [NSString stringWithFormat:#"%#", deviceToken];
NSUInteger loc_begin = [tmpToken rangeOfString: #"<"].location+1;
NSUInteger loc_end = [tmpToken rangeOfString: #">"].location-1;
return [tmpToken substringWithRange: NSMakeRange(loc_begin, loc_end)];
}

Just if anyone need to do something similar I solved my problem like this.
I use Apples ioSock class, and I have set the certificate in my code by calling the keychain
First I connect to the feedback server with this code
- (void)connectToFeedBackServer
{
if(self.certificate == nil)
{
return;
}
// Get the global variable feedbackHost and make it to a char
const char *cHost = [feedbackHost UTF8String];
NSLog(#"The size of cHost is: %lu", strlen(cHost));
NSLog(#"Host is: %s", cHost);
// Define result variable.
OSStatus result;
// Establish connection to server.
PeerSpec peer;
result = MakeServerConnection(cHost, 2196, &socket, &peer);
// Create new SSL context.
result = SSLNewContext(false, &context);
// Set callback functions for SSL context.
result = SSLSetIOFuncs(context, SocketRead, SocketWrite);
// Set SSL context connection.
result = SSLSetConnection(context, socket);
// Set server domain name.
result = SSLSetPeerDomainName(context, cHost, strlen(cHost));
result = SecIdentityCopyCertificate(_theIdentity, &(certificate));
// Set client certificate.
CFArrayRef certificates = CFArrayCreate(NULL, (const void **)&_theIdentity, 1, NULL);
result = SSLSetCertificate(context, certificates);
CFRelease(certificates);
do
{
result = SSLHandshake(context); NSLog(#"SSLHandshake(): %d", result);
} while(result == errSSLWouldBlock);
}
And then I read the feedback data and add the tokens to an array, like this
- (NSMutableArray *)CheckFeedBackServer
{
OSStatus result;
NSMutableArray *feedbackTokens = [[NSMutableArray alloc]init];
// Retrieve message from SSL.
size_t processed = 0;
char buffer[38];
do
{
// Fetch the next item
result = SSLRead(context, buffer, 38, &processed);
if (result) break;
char *b = buffer;
// Recover Device ID
NSMutableString *deviceID = [NSMutableString string];
b += 6;
for (int i = 0; i < 32; i++)
{
[deviceID appendFormat:#"%02x", (unsigned char)b[i]];
}
[feedbackTokens addObject:deviceID];
} while (processed > 0);
return feedbackTokens;
}

Related

cfhoststartinforesolution fails. get device name from IP address on LAN

As a part of assignment I have to scan my LAN and get the names of devices connected to wifi. I went through apples documentation this and many other S.O links like this. However resolution is not happening, some error occurs. I am not a networking guy and hence most of it sounded greek to me. an almost same question has been asked here
This is my code. I have written down the error that is occurring in comments.
An alternate answer would be helpful too.
+ (NSArray *)hostnamesForAddress:(NSString *)address {
// Get the host reference for the given address.
CFStreamError streamError;
struct addrinfo hints;
struct addrinfo *result = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
int errorStatus = getaddrinfo([address cStringUsingEncoding:NSASCIIStringEncoding], NULL, &hints, &result);
if (errorStatus != 0) return nil;
CFDataRef addressRef = CFDataCreate(NULL, (UInt8 *)result->ai_addr, result->ai_addrlen);
if (addressRef == nil) return nil;
freeaddrinfo(result);
CFHostRef hostRef = CFHostCreateWithAddress(kCFAllocatorDefault, addressRef);
if (hostRef == nil) return nil;
CFRelease(addressRef);
BOOL isSuccess = CFHostStartInfoResolution(hostRef, kCFHostNames, &streamError);
// always false
if (!isSuccess){
NSLog(#"error:%#",[self convertCFStreamErrorIntoNSError:streamError]);
// error:Error Domain=kCFErrorDomainCFNetwork Code=2 "The operation couldn’t be completed. (kCFErrorDomainCFNetwork error 2.)" UserInfo=0x1cd29190 {kCFGetAddrInfoFailureKey=8}
return nil;
}
// Get the hostnames for the host reference.
CFArrayRef hostnamesRef = CFHostGetNames(hostRef, NULL);
NSMutableArray *hostnames = [NSMutableArray array];
for (int currentIndex = 0; currentIndex < [(__bridge NSArray *)hostnamesRef count]; currentIndex++) {
[hostnames addObject:[(__bridge NSArray *)hostnamesRef objectAtIndex:currentIndex]];
}
return hostnames[0];
}

in iOS: SecKeyDecrypt OSStaus return error code -9809

I have googling a lot, but any answer help me with this problem:
Code:
MAIN DECRYPT in XRSA.m
- (NSData *) decryptWithString:(NSString *)content {
return [self RSADecryptData:[content dataUsingEncoding:NSUTF8StringEncoding]];
}
LOAD PRIVATE KEY .p12 in XRSA.m
#pragma mark - Private Key (.p12)
-(BOOL)setPrivateKey:(NSString *)privateKeyPath withPassphrase:(NSString *)password{
NSData *pkcs12key = [NSData dataWithContentsOfFile:privateKeyPath];
NSDictionary* options = NULL;
CFArrayRef importedItems = NULL;
if (password) {
options = [NSDictionary dictionaryWithObjectsAndKeys: password, kSecImportExportPassphrase, nil];
}
OSStatus returnCode = SecPKCS12Import((__bridge CFDataRef) pkcs12key,
(__bridge CFDictionaryRef) options,
&importedItems);
if (returnCode != 0) {
NSLog(#"SecPKCS12Import fail");
return FALSE;
}
NSDictionary* item = (NSDictionary*) CFArrayGetValueAtIndex(importedItems, 0);
SecIdentityRef identity = (__bridge SecIdentityRef) [item objectForKey:(__bridge NSString *) kSecImportItemIdentity];
SecIdentityCopyPrivateKey(identity, &privateKey);
if (privateKey == nil) {
NSLog(#"SecIdentityCopyPrivateKey fail");
return FALSE;
}
return TRUE;
}
Decrypt message in XRSA.m
#pragma mark - RSA Decryption
-(NSData *)RSADecryptData:(NSData *)content{
NSAssert(privateKey != nil,#"Private key can not be nil");
size_t cipherLen = content.length;
void *cipher = malloc(cipherLen);
[content getBytes:cipher length:cipherLen];
size_t plainLen = SecKeyGetBlockSize(privateKey) - 12;
void *plain = malloc(plainLen);
//SecKeyDecrypt(<#SecKeyRef key#>, <#SecPadding padding#>, <#const uint8_t *cipherText#>, <#size_t cipherTextLen#>, <#uint8_t *plainText#>, <#size_t *plainTextLen#>)
OSStatus returnCode = SecKeyDecrypt(privateKey, kSecPaddingPKCS1, cipher,cipherLen, plain, &plainLen);
NSData *result = nil;
if (returnCode != 0) {
NSLog(#"SecKeyDecrypt fail. Error Code: %d", (int)returnCode);
}
else {
result = [NSData dataWithBytes:plain
length:plainLen];
}
free(plain);
free(cipher);
return result;
}
in ViewControler.m:
NSString *privatekeyPath = [[NSBundle mainBundle] pathForResource:#"private_key" ofType:#"p12"];
XRSA *rsa2 = [XRSA alloc];
if([rsa2 setPrivateKey:privatekeyPath withPassphrase:#"Xs23tg"]){
NSString *data = #"UKFpmRmyu1TUZLqcgHmCEGnHaT7+0j5fAaf57xzVR2/j/Qe0j+b5Lez7wya3jlARfzRuHSSZctsGs4gK2JX2LEqHmQLX2zRhLSSzyMlLnYPF8X4pjbDY5agjPlWf4FpFJnmwGr2XjdqRJzPZ9NvEJAns5dNKAh0lQ3nc3kDppfg=";
[rsa2 decryptWithString:data];
}
else{
}
In RSADecryptData fuction, OSStaus is always return error code -9809.
Any ideas?
Thanks for your time.
There are a couple of possibilities:
In the line [content getBytes:cipher length:cipherLen]; you are not assigning that result to anything. Perhaps assign it to a const uint8_t * and pass into the SecKeyDecrypt function instead of content.
You should check to ensure that the cipherLen is less than the plainLen value. You didn't mention your key length, but that could be the cause of the failure. If you need to support larger message, you will need to decrypt in smaller chunk and iterate over your cipher.

Message Sent To Deallocated Instantce

I'm using game center to send data between two players. For some reason I keep getting a deallocated instance message. Here's the code:
- (void)sendGameMove:(uint32_t)i andj:(NSString *)j {
MessageMove message;
message.message.messageType = kMessageTypeMove;
message.i = 1;
message.j = #"Testing 1 2 3s";
NSData *data = [NSData dataWithBytes:&message length:sizeof(MessageMove)];
MessageMove * messageMove = (MessageMove *) [data bytes];
NSLog(#"Message I: %i", messageMove->i);
NSLog(#"Message J: %#", messageMove->j);
[self sendData:data];
}
(I Filled in the i and j arguments for what i'm passing). In this method the NSLog statements both log what they're supposed to after creating the NSData object but when I sent that NSData object to the method [self sendData:data]:
- (void)sendData:(NSData *)data {
MessageMove * messageMove = (MessageMove *) [data bytes];
NSLog(#"Message I: %i", messageMove->i);
NSLog(#"Message J: %#", messageMove->j);
NSError *error;
BOOL success = [[GCHelper sharedInstance].match sendDataToAllPlayers:data withDataMode:GKMatchSendDataReliable error:&error];
if (!success) {
[self matchEnded];
}
}
In the NSLog statement the first one works fine so I get:
"Message I: 1"
in the console but for the second log statement I get :
"*** -[ respondsToSelector:]: message sent to deallocated instance"
the code to break down the data object is the exact same in the second method as in the first. Any ideas?
I'm guessing that MessageMove is a struct like:
typedef struct {
int i;
NSString *j;
} MessageMove;
The problem is that you're sending only the contents of the struct itself. In memory and on the network, it'd look something like this:
01000000 07FCA700
-----------------
i j
When the second device receives the message and tries to read the j pointer, it crashes because there's nothing there: that pointer was only valid on the origin device. The struct didn't even contain the contents of the string at all.
To fix this, you need to actually send the string in the message. Flexible array members are one way of storing a string directly in the struct:
typedef struct {
int32_t i; // explicit integer sizes are a good idea for network protocols
int32_t j_length;
char j[]; // the flexible array member
} MessageMove;
Send:
NSString *s = #"Testing 1 2 3s";
NSData *d = [s dataUsingEncoding:NSUTF8StringEncoding];
size_t messageSize = sizeof(MessageMove) + [d length];
MessageMove *mm = calloc(1, messageSize);
mm->i = 1;
mm->j_length = [d length];
memcpy(mm->j, [d bytes], [d length]);
[self sendData:[NSData dataWithBytes:mm length:messageSize]];
Receive:
MessageMove *mm = (MessageMove *)[data bytes];
assert(mm->j_length == [data length] - offsetof(MessageMove, j));
NSString *j = [[NSString alloc] initWithBytes:mm->j length:mm->j_length encoding:NSUTF8StringEncoding];
The assertion prevents reading past the end of the data buffer. offsetof is a macro from <stddef.h>.
For anything more complicated than this, I'd recommend serializing to and sending plist or JSON instead. They handle all the string copying details for you and let you send arrays and dictionary too.

Unable to write data into hardware peripheral

I am using CoreBlueTooth API to write data into a peripheral we have received from some hardware manufacturing company. As per the specs they have given us a bunch of characteristics UUIDs to write data into. Once we want to finish we need to write 0 in one of the characteristics. Now, the problem is that when I am trying to send String/Integer and converting them into NSData then its not working. I think I need to send byte stream in those writable characteristics. Can someone help me as how can I convert my NSString & NSNumber data into byte stream before sending them. Below is my conversion code I tried with:
- (void)writeCharactersticData:(NSDictionary *)iData toPeripheral:(CBPeripheral *)iPeripheral {
NSArray *charactersticsIDs = [NSArray arrayWithArray:iData.allKeys];
self.writeCharactersticsCount = charactersticsIDs.count;
for (CBUUID *uuid in charactersticsIDs) {
if (self.peripheralCharacterstics[uuid]) {
NSData *payload;
if ([iData[uuid] isKindOfClass:[NSNumber class]]) {
NSInteger data = ((NSNumber *)iData[uuid]).integerValue;
// int integerSize = sizeof(data);
//
// uint8_t bytes[integerSize];
//
//
// NSLog(#"Integer data = %d", data);
//
// int8_t tx = (int8_t)data;
// bytes[0] = tx;
// payload = [NSData dataWithBytes:bytes length:sizeof(data)];
payload = [NSData dataWithBytes:&data length:sizeof(data)];
} else if ([iData[uuid] isKindOfClass:[NSString class]]) {
int stringSize = sizeof(iData[uuid]);
uint8_t bytes[stringSize];
NSScanner *scanner = [NSScanner scannerWithString:iData[uuid]];
for (int i=0; i<stringSize; i++) {
unsigned int value;
[scanner scanHexInt:&value];
bytes[i] = (uint8_t)value;
}
payload = [NSData dataWithBytes:bytes length:stringSize];
// payload = [iData[uuid] dataUsingEncoding:NSUTF8StringEncoding];
}
[self.discoveredPeripheral writeValue:payload forCharacteristic:self.peripheralCharacterstics[uuid] type:CBCharacteristicWriteWithResponse];
}
}
}
Here is the fix, It works for me
//Integer to NSData
+(NSData *) IntToNSData:(NSInteger)data
{
Byte *byteData = (Byte*)malloc(4);
byteData[3] = data & 0xff;
byteData[2] = (data & 0xff00) >> 8;
byteData[1] = (data & 0xff0000) >> 16;
byteData[0] = (data & 0xff000000) >> 24;
NSData * result = [NSData dataWithBytes:byteData length:4];
NSLog(#"result=%#",result);
return (NSData*)result;
}
//NSData to Integer
+(NSInteger) NSDataToInt:(NSData *)data
{
unsigned char bytes[4];
[data getBytes:bytes length:4];
NSInteger n = (int)bytes[0] << 24;
n |= (int)bytes[1] << 16;
n |= (int)bytes[2] << 8;
n |= (int)bytes[3];
return n;
}
Thanks to http://coding-snippet.blogspot.in/2013/05/blog-post.html
This is not a Core Bluetooth based problem you have here.
For debugging, you could use
NSLog(#"%#", payload);
For your string to NSData conversion, your approach seems very complicated. I would suggest something simple like
NSData* payload = [iData[uuid] dataUsingEncoding:NSUTF8StringEncoding];
if (payload.length > 20)
{
// handle error. most LE peripherals don't support longer values.
}
Another error could be that you mix ASCII 0 '0' with a binary zero '\0' when writing your value.
Just use
https://github.com/LGBluetooth/LGBluetooth It will make your job 100x easier

Private key signature different on iOS and MacOSX

I implemented a category method on the NSData class which returns a signature of the data using an SHA-1 hash and subsequent encryption with a private key as follows:
- (NSData *)signatureWithKey:(SecKeyRef)keyRef {
if (keyRef == NULL) {
return nil;
}
NSData *sha1Digest = [self dataWithSHA1Digest];
size_t maxLength = SecKeyGetBlockSize(keyRef) - 11;
if ([sha1Digest length] > maxLength) {
NSString *reason = [NSString stringWithFormat:#"Digest is too long to sign with this key, max length is %ld and actual length is %ld", maxLength, (unsigned long)[self length]];
NSException *ex = [NSException exceptionWithName:#"BMInvalidArgumentException" reason:reason userInfo:nil];
#throw ex;
}
#if TARGET_OS_IPHONE
OSStatus status = noErr;
uint8_t *plainBuffer = (uint8_t *)[sha1Digest bytes];
size_t plainBufferSize = [sha1Digest length];
size_t cipherBufferSize = SecKeyGetBlockSize(keyRef);
uint8_t *cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
status = SecKeyRawSign(keyRef,
kSecPaddingPKCS1SHA1,
plainBuffer,
plainBufferSize,
&cipherBuffer[0],
&cipherBufferSize
);
if (status == noErr) {
return [NSData dataWithBytesNoCopy:cipherBuffer length:cipherBufferSize freeWhenDone:YES];
}
free(cipherBuffer);
return nil;
#else
CFErrorRef error = NULL;
SecTransformRef signer = NULL;
CFTypeRef signature = NULL;
if ((signer = SecSignTransformCreate(keyRef, &error))) {
if (SecTransformSetAttribute(
signer,
kSecTransformInputAttributeName,
(CFDataRef)sha1Digest,
&error)) {
signature = SecTransformExecute(signer, &error);
}
}
if (error) {
LogWarn(#"Could not sign: %#", error);
CFRelease(error);
}
if (signer) {
CFRelease(signer);
}
if (signature) {
NSData *data = [NSData dataWithData:(NSData *)signature];
CFRelease(signature);
return data;
} else {
return nil;
}
#endif
}
Now the strange thing is that with the same private key (loaded from a p12 file) I get two different results for iOS and MacOSX when signing the same data. I am completely puzzled by this. You may notice the method above uses a different implementation for MacOSX using security transforms, but even if I use the iOS implementation on MacOSX (which gives a compile warning but works fine) I get the same result.
The method used for loading the private key from file is below:
+ (SecKeyRef)newPrivateKeyRefWithPassword:(NSString *)password fromData:(NSData *)data {
NSMutableDictionary * options = [[NSMutableDictionary alloc] init];
SecKeyRef privateKeyRef = NULL;
// Set the public key query dictionary
//change to your .pfx password here
[options setObject:password forKey:(id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((CFDataRef)data,
(CFDictionaryRef)options, &items);
if (securityError == noErr && CFArrayGetCount(items) > 0) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identityApp =
(SecIdentityRef)CFDictionaryGetValue(identityDict,
kSecImportItemIdentity);
securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
if (securityError != noErr) {
privateKeyRef = NULL;
}
}
[options release];
if (items) CFRelease(items);
return privateKeyRef;
}
And this is the test case I use. Notice that two different strings are printed on iOS and MacOSX:
NSString *test = #"bla";
NSData *testData = [test dataUsingEncoding:NSUTF8StringEncoding];
NSString *p12Path= [[NSBundle mainBundle] pathForResource:#"private_key" ofType:#"p12"];
NSData *p12Data = [NSData dataWithContentsOfFile:p12Path];
SecKeyRef keyRef = [BMSecurityHelper newPrivateKeyRefWithPassword:#"xxxxxxxx" fromData:p12Data];
NSData *signatureData = [testData signatureWithKey:keyRef];
NSString *signatureString = [BMEncodingHelper base64EncodedStringForData:signatureData withLineLength:0];
if (keyRef) CFRelease(keyRef);
NSLog(#"signatureString: %#", signatureString);
It's always nice if you can answer your own question. I missed the following: under MacOSX the security transform also calculates the SHA-1 hash automatically, in contrast with the iOS implementation.
I fixed the problem by adding the following in the MacOSX implementation:
SecTransformSetAttribute(signer, kSecInputIsAttributeName, kSecInputIsDigest, &error)

Resources