Background : I have a custom authentication mechanism on the server end that is not supported by the MPMoviePlayer. So, I decided to have a local loopback HTTP server which will take the initial request of the player and serve the HLS manifest file.
I'm at a position where the player initiates the request to my local HTTP server and after that my local HTTP server fetches manifest file from the servers and writes it back as a http response to the player. But MPMoviePlayer is not playing the video after that.
Can someone help me achieve this?
#import "QumuMediaPlayerProxy.h"
#import "GZIP.h"
#define WELCOME_MSG 0
#define ECHO_MSG 1
#define WARNING_MSG 2
#define READ_TIMEOUT 15.0
#define READ_TIMEOUT_EXTENSION 10.0
#interface QumuMediaPlayerProxy()
#property NSURL *contentURL;
#end
#implementation QumuMediaPlayerProxy
+(NSURL*)getProxyURL{
return [NSURL URLWithString:[NSString stringWithFormat:#"http://192.168.2.11:%d%#", SERVER_PORT, #"/nkm.do"]];
}
- (id)initWithURL:(NSURL*)contentURL
{
if((self = [super init]))
{
socketQueue = dispatch_queue_create("socketQueue", NULL);
listenSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:socketQueue];
connectedSockets = [[NSMutableArray alloc] initWithCapacity:1];
isRunning = NO;
self.contentURL = contentURL;
}
return self;
}
- (void)startOnPort:(int)port
{
if(!isRunning)
{
if (port < 0 || port > 65535)
{
port = 0;
}
NSError *error = nil;
if(![listenSocket acceptOnPort:port error:&error])
{
NSLog(#"Error starting QumuMediaPlayerProxy: %#", error.debugDescription);
return;
}
NSLog(#"QumuMediaPlayerProxy started on port %hu", [listenSocket localPort]);
isRunning = YES;
}
}
-(void)stop
{
if(isRunning)
{
// Stop accepting connections
[listenSocket disconnect];
// Stop any client connections
#synchronized(connectedSockets)
{
NSUInteger i;
for (i = 0; i < [connectedSockets count]; i++)
{
// Call disconnect on the socket,
// which will invoke the socketDidDisconnect: method,
// which will remove the socket from the list.
[[connectedSockets objectAtIndex:i] disconnect];
}
}
NSLog(#"Stopped QumuMediaPlayerProxy");
isRunning = false;
}
}
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
// This method is executed on the socketQueue (not the main thread)
#synchronized(connectedSockets)
{
[connectedSockets addObject:newSocket];
NSLog(#"==Accepted client==");
}
[newSocket readDataWithTimeout:-1 tag:0];
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
// This method is executed on the socketQueue (not the main thread)
if (tag == ECHO_MSG)
{
[sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0];
}
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
// This method is executed on the socketQueue (not the main thread)
dispatch_async(dispatch_get_main_queue(), ^{
#autoreleasepool {
NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length] - 2)];
NSString *msg = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding];
if (msg)
{
NSLog(#"msg===>%#",msg);
NSLog(#"contentURL===>%#",self.contentURL.absoluteString);
NSString *getStr = [msg componentsSeparatedByString:#"\n"][0];
NSString *requestedURL = [getStr substringWithRange:NSMakeRange(4, getStr.length-9)];
//NSString *host = #"http://127.0.0.1:6910/";
NSString *host = #"http://192.168.2.11:6910/";
NSURL *requestURL = self.contentURL;
if(![requestedURL containsString:#"nkm.do"]){
NSString *actualHost = [self.contentURL.absoluteString stringByReplacingOccurrencesOfString:self.contentURL.lastPathComponent withString:#""];
requestURL = [NSURL URLWithString:[actualHost stringByAppendingString:requestedURL]];
}
NSData *manifestData = [[QumuJSONHelper getInstance] fetchM3U8Playlist:requestURL];
NSString *manifestStr = [[NSString alloc] initWithData:manifestData encoding:NSUTF8StringEncoding];
NSLog(#"manifestStr===>%#",manifestStr);
/* NSArray *manifestArray = [manifestStr componentsSeparatedByString:#"\n"];
NSString *modifiedManifest = #"";
for(int i=0;i<manifestArray.count;i++){
NSString *token = manifestArray[i];
if([token containsString:#"#EXT-X-STREAM-INF"]){
NSLog(#"== Found tag EXT-X-STREAM-INF ==");
modifiedManifest = [modifiedManifest stringByAppendingString:token];
modifiedManifest = [modifiedManifest stringByAppendingString:#"\n"];
token = manifestArray[++i];
// token = [#"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/" stringByAppendingString:token];
token = [host stringByAppendingString:token];
NSLog(#"Modified URL===>%#",token);
}
modifiedManifest = [modifiedManifest stringByAppendingString:token];
modifiedManifest = [modifiedManifest stringByAppendingString:#"\n"];
}
modifiedManifest = [modifiedManifest stringByReplacingOccurrencesOfString:#"URI=\"" withString:[NSString stringWithFormat:#"URI=\"%#",host]];
NSLog(#"modifiedManifest===>%#",modifiedManifest);*/
NSString *response = #"HTTP/1.1 200 OK";
response = [response stringByAppendingString:#"\r\nContent-Type: application/vnd.apple.mpegurl;charset=UTF-8"];
response = [response stringByAppendingFormat:#"\r\nContent-Length: %ld", (unsigned long)manifestData.length];
response = [response stringByAppendingString:#"\r\nConnection: keep-alive"];
response = [response stringByAppendingString:#"\r\n\r\n"];
NSLog(#"response header ===>%#",response);
NSData *responseData = [response dataUsingEncoding:NSUTF8StringEncoding];
[sock writeData:responseData withTimeout:-1 tag:0];
[sock writeData:manifestData withTimeout:-1 tag:0];
}
else
{
NSLog(#"Error converting received data into UTF-8 String");
}
}
});
// Echo message back to client
[sock writeData:data withTimeout:-1 tag:ECHO_MSG];
}
/**
* This method is called if a read has timed out.
* It allows us to optionally extend the timeout.
* We use this method to issue a warning to the user prior to disconnecting them.
**/
- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag
elapsed:(NSTimeInterval)elapsed
bytesDone:(NSUInteger)length
{
if (elapsed <= READ_TIMEOUT)
{
NSString *warningMsg = #"Are you still there?\r\n";
NSData *warningData = [warningMsg dataUsingEncoding:NSUTF8StringEncoding];
[sock writeData:warningData withTimeout:-1 tag:WARNING_MSG];
return READ_TIMEOUT_EXTENSION;
}
return 0.0;
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
if (sock != listenSocket)
{
dispatch_async(dispatch_get_main_queue(), ^{
#autoreleasepool {
NSLog(#"Client Disconnected");
}
});
#synchronized(connectedSockets)
{
[connectedSockets removeObject:sock];
}
}
}
-(void)dealloc{
// [self stop];
}
#end
Thanks in advance.
I was writing an additional echo message back to server in
(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
Once I removed it everything started working as expected. Just mentioning this in case some one wants to use the code posted above.
Related
I have an array of objects in which this for loop iterates through. Each object is an IP Address that I test for. However, once the for loop gets to a specific object that fails, the for loop takes roughly 15seconds to a minute to move onto the next object. However, if I interact with the display on my device by tapping, the for loop moves onto the next object. I find this odd because I do call [[UIApplication sharedApplication] beginIgnoringInteractionEvents];. So A) Why is the application still detecting touch events which affect the for loop. And B) I verified that the [[UIApplication sharedApplication] beginIgnoringInteractionEvents]; does work because if I try to tap a barButtonItem nothing happens. So here's my question, how can user interaction that's ignored temporarily still cause a for loop to move onto the next object? I've verified that there's no issues with threading/GCD. I know this may be rough to understand without a code sample, however I want to know the principal behind a UI interaction event affecting a for loop.
UPDATE!
Code:
-(void)beginConnectivitySetupAndTests {
for (int i = 0; i<[extractedDataArray count]; i++) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSString *inStoreObject = [[extractedDataArray objectAtIndex:i] valueForKey:#"ConnectabilityObjectType"];
NSString *objectTitle = [[extractedDataArray objectAtIndex:i] valueForKey:#"ConnectabilityObjectTechnicalTitle"];
NSString *objectLastOctet = [[extractedDataArray objectAtIndex:i] valueForKey:#"ConnectabilityObjectLastOctet"];
NSString *isInfrastructureObject = [[extractedDataArray objectAtIndex:i] valueForKey:#"ConnectabilityObjectIsInfrastructureObject"];
NSString *needsAlternateSecondOctet = [[extractedDataArray objectAtIndex:i] valueForKey:#"ConnectablityObjectNeedsAlternateSecondOctet"];
NSLog(#"Working with: %#",objectTitle);
if ([inStoreObject isEqualToString:#"InStoreObject"]) {
if ([needsAlternateSecondOctet isEqualToString:#"YES"]) {
// self.deviceIPAddress = nil;
NSLog(#"%#", self.deviceIPAddress);
NSMutableArray <NSString *> *octets = [self.deviceIPAddress componentsSeparatedByString:#"."];
NSString *firstOctetString = [octets objectAtIndex:0];
NSString *secondOctetString = [octets objectAtIndex:1];
NSString *thirdOctetString = [octets objectAtIndex:2];
int secondInt = secondOctetString.intValue;
if (secondInt >= 40 && secondInt <= 47) {
[octets removeObjectAtIndex:1];
secondOctetString = [NSString stringWithFormat:#"%d", secondInt +40];
}
else if (secondInt >= 52 && secondInt <= 53) {
[octets removeObjectAtIndex:1];
secondOctetString = [NSString stringWithFormat:#"%d", secondInt +100];
}
else if (secondInt >= 120 && secondInt <= 121) {
[octets removeObjectAtIndex:1];
secondOctetString = [NSString stringWithFormat:#"%d", secondInt +100];
}
else if (secondInt >= 122 && secondInt <= 123) {
[octets removeObjectAtIndex:1];
secondOctetString = [NSString stringWithFormat:#"%d", secondInt +40];
}
else if (secondInt >= 124 && secondInt <= 125) {
[octets removeObjectAtIndex:1];
secondOctetString = [NSString stringWithFormat:#"%d", secondInt +40];
}
else if (secondInt >= 126 && secondInt <= 127) {
[octets removeObjectAtIndex:1];
secondOctetString = [NSString stringWithFormat:#"%d", secondInt +40];
} else {
}
NSString *finalString = [NSString stringWithFormat:#"%#.%#.%#.%#", firstOctetString, secondOctetString, thirdOctetString, objectLastOctet];
[[extractedDataArray objectAtIndex:i] setValue:[NSString stringWithFormat:#"%#",finalString]forKey:#"ConnectabilityObjectFullIPAddress"];
finalIPAddress = finalString;
octets = nil;
} else {
NSMutableArray <NSString*> *octets = [self.deviceIPAddress componentsSeparatedByString:#"."];
NSString *firstOctetString = [octets objectAtIndex:0];
NSString *secondOctetString = [octets objectAtIndex:1];
NSString *thirdOctetString = [octets objectAtIndex:2];
[octets removeObjectAtIndex:3];
NSString *lastOctet = [NSString stringWithFormat:#"%#", objectLastOctet];
NSString *constructedIPAddress = [NSString stringWithFormat:#"%#.%#.%#.%#", firstOctetString, secondOctetString, thirdOctetString, lastOctet];
finalIPAddress = constructedIPAddress;
[[extractedDataArray objectAtIndex:i] setValue:[NSString stringWithFormat:#"%#",constructedIPAddress]forKey:#"ConnectabilityObjectFullIPAddress"];
NSLog(#"%# IP: %#", objectTitle, constructedIPAddress);
}
} else {
finalIPAddress = [[extractedDataArray objectAtIndex:i] valueForKey:#"ConnectabilityObjectFullIPAddress"];
}
self.ping = [[SimplePing alloc] initWithHostName:finalIPAddress];
NSLog(#"pinging: %#",finalIPAddress);
self.ping.delegate = self;
[self.ping start];
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (self.ping != nil);
if (pingableObjectAddress) {
if (_deviceLatencyString == NULL) {
[[extractedDataArray objectAtIndex:i] setValue:[NSString stringWithFormat:#"Successful"] forKey:#"ConnectivityPingTestResultValue"];
} else {
[[extractedDataArray objectAtIndex:i] setValue:[NSString stringWithFormat:#"Successful: latency: %# ms",_deviceLatencyString] forKey:#"ConnectivityPingTestResultValue"];
}
pingableObjectAddress = nil;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
portNumberValue = [[[extractedDataArray objectAtIndex:i] valueForKey:#"ConnectabilityObjectPorts"] intValue];
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)finalIPAddress, portNumberValue, &readStream, &writeStream);
inputStream = (NSInputStream*)CFBridgingRelease(readStream);
outputStream = (NSOutputStream*)CFBridgingRelease(writeStream);
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
NSLog(#"Opening Port for %lu at IP Address %#", (unsigned long)portNumberValue, finalIPAddress);
NSString *response = #"Fly with me!";
NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
if ([[extractedDataArray objectAtIndex:i] valueForKey:#"ConnectabilityObjectPorts"] != nil ) {
if (_connectedPortNumber != 0) {
NSLog(#"Port Connection %lu succeeded for %#", (unsigned long)portNumberValue, finalIPAddress);
[[extractedDataArray objectAtIndex:i] setValue:#"Successful" forKey:#"ConnectivityPortConnectResultValue"];
if (!isInfrastructureObject) {
_portConnectionSucceeded = _portConnectionSucceeded + 1;
NSLog(#"port connect is not for infrastructure object");
}
self.connectedPortNumber = 0;
} else {
if (!isInfrastructureObject) {
_portConnectionFailed = _portConnectionFailed + 1;
}
}
}
} else {
if (!isInfrastructureObject) {
pingFailed = pingFailed + 1;
}
}
NSLog(#"%lu",(unsigned long)i);
}
}
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
// does have a valid IP address
-(void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address {
[self.ping sendPingWithData:nil];
}
// device did send packet
-(void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber address:(nonnull NSString *)address {
// NSLog(#"Sending 64 bytes to %#: icmp_seq=%hu ttl=64", address, sequenceNumber);
pingTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:PING_TIMEOUT target:self selector:#selector(timerFired) userInfo:nil repeats:NO];
_dateRef = [NSDate date];
NSLog(#"Packet Sent to: %#", address);
}
// timeout timer, fires when a ping back is never recieved
-(void)timerFired {
NSLog(#"timeout timer fired");
[pingTimeoutTimer invalidate];
//self.pingableObjectAddress = nil;
self.pingTimeoutTimer = nil;
self.ping = nil;
}
// failed to send packet error handler
-(void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error {
[self.pingTimeoutTimer invalidate];
pingTimeoutTimer = nil;
self.ping = nil;
if (error) {
NSLog(#"Ping Error: %#", error);
}
}
// device did recieve unexpected ping packet, handle this when this happens
-(void)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet {}
// did recieve response packet
-(void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber address:(NSString *)address {
[pingTimeoutTimer invalidate];
NSDate *end = [NSDate date];
double latency = [end timeIntervalSinceDate:_dateRef] *1000;
self.deviceLatencyString = [NSString stringWithFormat:#"%.f", latency];
self.pingTimeoutTimer = nil;
// NSLog(#"64 bytes sent to %#: icmp_seq=%hu ttl=64 time = %f ms", address, sequenceNumber, _latency);
self.pingableObjectAddress = address;
// NSLog(#"%#", pingableObjectAddress);
NSLog(#"did recieve packet from: %#", address);
self.ping = nil;
}
// device failed to ping for failed reason
-(void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error {
NSLog(#"Failed With Error %#", error);
assert(pinger == self.ping);
[self.ping stop];
self.ping = nil;
}
I need to read the received bytes and parse them with a custom method, which basically goes through bytes and skips every 0x7D (or a } ). But what happens now is that it does that for only one received packet, and I dont know why that is happening since im calling my method in didUpdateValueForCharacteristic :
- (void) peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error)
{
NSLog(#"Error receiving notification for characteristic %#: %#", characteristic, error);
return;
}
NSLog(#"Received data on a characteristic.");
if (characteristic == self.rxCharacteristic)
{
self.bluetoothData = [[NSMutableData alloc] initWithData:[characteristic value]];
[self unstuff1:[characteristic value]];
NSLog(#"First NSDAta %#", self.bluetoothData);
[self unstuff1:self.bluetoothData];
NSLog(#"Parsed First NSDATA %#", self.bluetoothData);
NSString *stringForPrintingOutToConsole = [[NSString alloc] initWithData:self.bluetoothData encoding:NSASCIIStringEncoding];
[self.delegate didReceiveData:stringForPrintingOutToConsole];
}
else if ([characteristic.UUID isEqual:self.class.hardwareRevisionStringUUID])
{
NSString *hwRevision = #"";
const uint8_t *bytes = characteristic.value.bytes;
for (int i = 0; i < characteristic.value.length; i++)
{
NSLog(#"%x", bytes[i]);
hwRevision = [hwRevision stringByAppendingFormat:#"0x%02x, ", bytes[i]];
}
// this one reads the hardware revision
[self.delegate didReadHardwareRevisionString:[hwRevision substringToIndex:hwRevision.length-2]];
}
}
and a custom method for parsing :
- (NSMutableData *)unstuff1:(NSMutableData *)mutableData {
NSUInteger dataLength = [self.bluetoothData length];
unsigned char *bytes = [self.bluetoothData bytes];
uint16_t i, j = 0;
for (int i = 0; i < dataLength; i++)
{
if (bytes[i] == 0x7D)
{
bytes[j] = bytes[i+1] ^ 0x20;
i++;
} else
{
bytes[j] = bytes[i];
j++;
}
}
return mutableData;
}
if anyone has a clue, i'd highly appreciate help.
We are following the raywenderlinch tutorial from http://www.raywenderlich.com/23266/in-app-purchases-in-ios-6-tutorial-consumables-and-receipt-validation
But there are some error which I am getting when import verificationController classes in our project. Right now, I am using iOS8.
Error is: Implicit declaration of function 'checkReciptSecurity' is invalid in C99
I have also search for sample code of Verification class on apple developer site ,Their page is not found.
Please give me your view to solve this or provide the link of verification classes which are update to iOS8.
In VerificationController.h put the function prototype like this :
- (void)verifyPurchase:(SKPaymentTransaction *)transaction completionHandler:(VerifyCompletionHandler)completionHandler;
BOOL checkReceiptSecurity(NSString *purchase_info_string, NSString *signature_string, CFDateRef purchaseDate);
The reason of doing this is the line number calling the function checkReceiptSecurity is before the declaration of function.
You have to modified the VerificationController.m file code.
I have put the modified code here.
#import "VerificationController.h"
#import "NSData+Base64.h"
static VerificationController *singleton;
#implementation VerificationController {
NSMutableDictionary * _completionHandlers;
}
+ (VerificationController *)sharedInstance
{
if (singleton == nil)
{
singleton = [[VerificationController alloc] init];
}
return singleton;
}
- (id)init
{
self = [super init];
if (self != nil)
{
transactionsReceiptStorageDictionary = [[NSMutableDictionary alloc] init];
_completionHandlers = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSDictionary *)dictionaryFromPlistData:(NSData *)data
{
NSError *error;
NSDictionary *dictionaryParsed = [NSPropertyListSerialization propertyListWithData:data
options:NSPropertyListImmutable
format:nil
error:&error];
if (!dictionaryParsed)
{
if (error)
{
NSLog(#"Error parsing plist");
}
return nil;
}
return dictionaryParsed;
}
- (NSDictionary *)dictionaryFromJSONData:(NSData *)data
{
NSError *error;
NSDictionary *dictionaryParsed = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
if (!dictionaryParsed)
{
if (error)
{
NSLog(#"Error parsing dictionary");
}
return nil;
}
return dictionaryParsed;
}
#pragma mark Receipt Verification
// This method should be called once a transaction gets to the SKPaymentTransactionStatePurchased or SKPaymentTransactionStateRestored state
// Call it with the SKPaymentTransaction.transactionReceipt
- (void)verifyPurchase:(SKPaymentTransaction *)transaction completionHandler:(VerifyCompletionHandler)completionHandler
{
BOOL isOk = [self isTransactionAndItsReceiptValid:transaction];
if (!isOk)
{
// There was something wrong with the transaction we got back, so no need to call verifyReceipt.
NSLog(#"Invalid transacion");
completionHandler(FALSE);
return;
}
// The transaction looks ok, so start the verify process.
// Encode the receiptData for the itms receipt verification POST request.
NSString *jsonObjectString = [self encodeBase64:(uint8_t *)transaction.transactionReceipt.bytes
length:transaction.transactionReceipt.length];
// Create the POST request payload.
NSString *payload = [NSString stringWithFormat:#"{\"receipt-data\" : \"%#\", \"password\" : \"%#\"}",
jsonObjectString, ITC_CONTENT_PROVIDER_SHARED_SECRET];
NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];
#warning Check for the correct itms verify receipt URL
// Use ITMS_SANDBOX_VERIFY_RECEIPT_URL while testing against the sandbox.
NSString *serverURL = ITMS_SANDBOX_VERIFY_RECEIPT_URL; //ITMS_PROD_VERIFY_RECEIPT_URL;
// Create the POST request to the server.
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:serverURL]];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:payloadData];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
_completionHandlers[[NSValue valueWithNonretainedObject:conn]] = completionHandler;
[conn start];
// The transation receipt has not been validated yet. That is done from the NSURLConnection callback.
}
// Check the validity of the receipt. If it checks out then also ensure the transaction is something
// we haven't seen before and then decode and save the purchaseInfo from the receipt for later receipt validation.
- (BOOL)isTransactionAndItsReceiptValid:(SKPaymentTransaction *)transaction
{
if (!(transaction && transaction.transactionReceipt && [transaction.transactionReceipt length] > 0))
{
// Transaction is not valid.
return NO;
}
// Pull the purchase-info out of the transaction receipt, decode it, and save it for later so
// it can be cross checked with the verifyReceipt.
NSDictionary *receiptDict = [self dictionaryFromPlistData:transaction.transactionReceipt];
NSString *transactionPurchaseInfo = [receiptDict objectForKey:#"purchase-info"];
NSString *decodedPurchaseInfo = [self decodeBase64:transactionPurchaseInfo length:nil];
NSDictionary *purchaseInfoDict = [self dictionaryFromPlistData:[decodedPurchaseInfo dataUsingEncoding:NSUTF8StringEncoding]];
NSString *transactionId = [purchaseInfoDict objectForKey:#"transaction-id"];
NSString *purchaseDateString = [purchaseInfoDict objectForKey:#"purchase-date"];
NSString *signature = [receiptDict objectForKey:#"signature"];
// Convert the string into a date
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss z"];
NSDate *purchaseDate = [dateFormat dateFromString:[purchaseDateString stringByReplacingOccurrencesOfString:#"Etc/" withString:#""]];
if (![self isTransactionIdUnique:transactionId])
{
// We've seen this transaction before.
// Had [transactionsReceiptStorageDictionary objectForKey:transactionId]
// Got purchaseInfoDict
return NO;
}
// Check the authenticity of the receipt response/signature etc.
BOOL result = checkReceiptSecurity(transactionPurchaseInfo, signature,
(__bridge CFDateRef)(purchaseDate));
if (!result)
{
return NO;
}
// Ensure the transaction itself is legit
if (![self doTransactionDetailsMatchPurchaseInfo:transaction withPurchaseInfo:purchaseInfoDict])
{
return NO;
}
// Make a note of the fact that we've seen the transaction id already
[self saveTransactionId:transactionId];
// Save the transaction receipt's purchaseInfo in the transactionsReceiptStorageDictionary.
[transactionsReceiptStorageDictionary setObject:purchaseInfoDict forKey:transactionId];
return YES;
}
// Make sure the transaction details actually match the purchase info
- (BOOL)doTransactionDetailsMatchPurchaseInfo:(SKPaymentTransaction *)transaction withPurchaseInfo:(NSDictionary *)purchaseInfoDict
{
if (!transaction || !purchaseInfoDict)
{
return NO;
}
int failCount = 0;
if (![transaction.payment.productIdentifier isEqualToString:[purchaseInfoDict objectForKey:#"product-id"]])
{
failCount++;
}
if (transaction.payment.quantity != [[purchaseInfoDict objectForKey:#"quantity"] intValue])
{
failCount++;
}
if (![transaction.transactionIdentifier isEqualToString:[purchaseInfoDict objectForKey:#"transaction-id"]])
{
failCount++;
}
// Optionally check the bid and bvrs match this app's current bundle ID and bundle version.
// Optionally check the requestData.
// Optionally check the dates.
if (failCount != 0)
{
return NO;
}
// The transaction and its signed content seem ok.
return YES;
}
- (BOOL)isTransactionIdUnique:(NSString *)transactionId
{
NSString *transactionDictionary = KNOWN_TRANSACTIONS_KEY;
// Save the transactionId to the standardUserDefaults so we can check against that later
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults synchronize];
if (![defaults objectForKey:transactionDictionary])
{
[defaults setObject:[[NSMutableDictionary alloc] init] forKey:transactionDictionary];
[defaults synchronize];
}
if (![[defaults objectForKey:transactionDictionary] objectForKey:transactionId])
{
return YES;
}
// The transaction already exists in the defaults.
return NO;
}
- (void)saveTransactionId:(NSString *)transactionId
{
// Save the transactionId to the standardUserDefaults so we can check against that later
// If dictionary exists already then retrieve it and add new transactionID
// Regardless save transactionID to dictionary which gets saved to NSUserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *transactionDictionary = KNOWN_TRANSACTIONS_KEY;
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithDictionary:
[defaults objectForKey:transactionDictionary]];
if (!dictionary)
{
dictionary = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:1], transactionId, nil];
} else {
[dictionary setObject:[NSNumber numberWithInt:1] forKey:transactionId];
}
[defaults setObject:dictionary forKey:transactionDictionary];
[defaults synchronize];
}
- (BOOL)doesTransactionInfoMatchReceipt:(NSString*) receiptString
{
// Convert the responseString into a dictionary and pull out the receipt data.
NSDictionary *verifiedReceiptDictionary = [self dictionaryFromJSONData:[receiptString dataUsingEncoding:NSUTF8StringEncoding]];
// Check the status of the verifyReceipt call
id status = [verifiedReceiptDictionary objectForKey:#"status"];
if (!status)
{
return NO;
}
int verifyReceiptStatus = [status integerValue];
// 21006 = This receipt is valid but the subscription has expired.
if (0 != verifyReceiptStatus && 21006 != verifyReceiptStatus)
{
return NO;
}
// The receipt is valid, so checked the receipt specifics now.
NSDictionary *verifiedReceiptReceiptDictionary = [verifiedReceiptDictionary objectForKey:#"receipt"];
NSString *verifiedReceiptUniqueIdentifier = [verifiedReceiptReceiptDictionary objectForKey:#"unique_identifier"];
NSString *transactionIdFromVerifiedReceipt = [verifiedReceiptReceiptDictionary objectForKey:#"transaction_id"];
// Get the transaction's receipt data from the transactionsReceiptStorageDictionary
NSDictionary *purchaseInfoFromTransaction = [transactionsReceiptStorageDictionary objectForKey:transactionIdFromVerifiedReceipt];
if (!purchaseInfoFromTransaction)
{
// We didn't find a receipt for this transaction.
return NO;
}
// NOTE: Instead of counting errors you could just return early.
int failCount = 0;
// Verify all the receipt specifics to ensure everything matches up as expected
if (![[verifiedReceiptReceiptDictionary objectForKey:#"bid"]
isEqualToString:[purchaseInfoFromTransaction objectForKey:#"bid"]])
{
failCount++;
}
if (![[verifiedReceiptReceiptDictionary objectForKey:#"product_id"]
isEqualToString:[purchaseInfoFromTransaction objectForKey:#"product-id"]])
{
failCount++;
}
if (![[verifiedReceiptReceiptDictionary objectForKey:#"quantity"]
isEqualToString:[purchaseInfoFromTransaction objectForKey:#"quantity"]])
{
failCount++;
}
if (![[verifiedReceiptReceiptDictionary objectForKey:#"item_id"]
isEqualToString:[purchaseInfoFromTransaction objectForKey:#"item-id"]])
{
failCount++;
}
if ([[UIDevice currentDevice] respondsToSelector:NSSelectorFromString(#"identifierForVendor")]) // iOS 6?
{
#if IS_IOS6_AWARE
// iOS 6 (or later)
NSString *localIdentifier = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
NSString *purchaseInfoUniqueVendorId = [purchaseInfoFromTransaction objectForKey:#"unique-vendor-identifier"];
NSString *verifiedReceiptVendorIdentifier = [verifiedReceiptReceiptDictionary objectForKey:#"unique_vendor_identifier"];
if(verifiedReceiptVendorIdentifier)
{
if (![purchaseInfoUniqueVendorId isEqualToString:verifiedReceiptVendorIdentifier]
|| ![purchaseInfoUniqueVendorId isEqualToString:localIdentifier])
{
// Comment this line out to test in the Simulator.
failCount++;
}
}
#endif
} else {
// Pre iOS 6
// NSString *localIdentifier = [UIDevice currentDevice].uniqueIdentifier;
// NSString *purchaseInfoUniqueId = [purchaseInfoFromTransaction objectForKey:#"unique-identifier"];
// if (![purchaseInfoUniqueId isEqualToString:verifiedReceiptUniqueIdentifier]
// || ![purchaseInfoUniqueId isEqualToString:localIdentifier])
// {
// // Comment this line out to test in the Simulator.
// failCount++;
// }
}
// Do addition time checks for the transaction and receipt.
if(failCount != 0)
{
return NO;
}
return YES;
}
#pragma mark NSURLConnectionDelegate (for the verifyReceipt connection)
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"Connection failure: %#", error);
VerifyCompletionHandler completionHandler = _completionHandlers[[NSValue valueWithNonretainedObject:connection]];
[_completionHandlers removeObjectForKey:[NSValue valueWithNonretainedObject:connection]];
completionHandler(FALSE);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
BOOL isOk = [self doesTransactionInfoMatchReceipt:responseString];
VerifyCompletionHandler completionHandler = _completionHandlers[[NSValue valueWithNonretainedObject:connection]];
[_completionHandlers removeObjectForKey:[NSValue valueWithNonretainedObject:connection]];
if (isOk)
{
//Validation suceeded. Unlock content here.
NSLog(#"Validation successful");
completionHandler(TRUE);
} else {
NSLog(#"Validation failed");
completionHandler(FALSE);
}
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([[[challenge protectionSpace] authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust])
{
SecTrustRef trust = [[challenge protectionSpace] serverTrust];
NSError *error = nil;
BOOL didUseCredential = NO;
BOOL isTrusted = [self validateTrust:trust error:&error];
if (isTrusted)
{
NSURLCredential *trust_credential = [NSURLCredential credentialForTrust:trust];
if (trust_credential)
{
[[challenge sender] useCredential:trust_credential forAuthenticationChallenge:challenge];
didUseCredential = YES;
}
}
if (!didUseCredential)
{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
} else {
[[challenge sender] performDefaultHandlingForAuthenticationChallenge:challenge];
}
}
// NOTE: These are needed for 4.x (as willSendRequestForAuthenticationChallenge: is not supported)
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return [[protectionSpace authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([[[challenge protectionSpace] authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust])
{
SecTrustRef trust = [[challenge protectionSpace] serverTrust];
NSError *error = nil;
BOOL didUseCredential = NO;
BOOL isTrusted = [self validateTrust:trust error:&error];
if (isTrusted)
{
NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
if (credential)
{
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
didUseCredential = YES;
}
}
if (! didUseCredential) {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
} else {
[[challenge sender] performDefaultHandlingForAuthenticationChallenge:challenge];
}
}
#pragma mark
#pragma mark NSURLConnection - Trust validation
- (BOOL)validateTrust:(SecTrustRef)trust error:(NSError **)error
{
// Include some Security framework SPIs
extern CFStringRef kSecTrustInfoExtendedValidationKey;
extern CFDictionaryRef SecTrustCopyInfo(SecTrustRef trust);
BOOL trusted = NO;
SecTrustResultType trust_result;
if ((noErr == SecTrustEvaluate(trust, &trust_result)) && (trust_result == kSecTrustResultUnspecified))
{
NSDictionary *trust_info = (__bridge_transfer NSDictionary *)SecTrustCopyInfo(trust);
id hasEV = [trust_info objectForKey:(__bridge NSString *)kSecTrustInfoExtendedValidationKey];
trusted = [hasEV isKindOfClass:[NSValue class]] && [hasEV boolValue];
}
if (trust)
{
if (!trusted && error)
{
*error = [NSError errorWithDomain:#"kSecTrustError" code:(NSInteger)trust_result userInfo:nil];
}
return trusted;
}
return NO;
}
#pragma mark
#pragma mark Base 64 encoding
- (NSString *)encodeBase64:(const uint8_t *)input length:(NSInteger)length
{
NSData * data = [NSData dataWithBytes:input length:length];
return [data base64EncodedString];
}
- (NSString *)decodeBase64:(NSString *)input length:(NSInteger *)length
{
NSData * data = [NSData dataFromBase64String:input];
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
char* base64_encode(const void* buf, size_t size) {
size_t outputLength;
return NewBase64Encode(buf, size, NO, &outputLength);
}
void * base64_decode(const char* s, size_t * data_len)
{
return NewBase64Decode(s, strlen(s), data_len);
}
#end
#pragma mark
#pragma mark Check Receipt signature
#include <CommonCrypto/CommonDigest.h>
#include <Security/Security.h>
#include <AssertMacros.h>
unsigned int iTS_intermediate_der_len = 1039;
unsigned char iTS_intermediate_der[] = {
put the hexacode here
};
BOOL checkReceiptSecurity(NSString *purchase_info_string, NSString *signature_string, CFDateRef purchaseDate)
{
BOOL valid = NO;
SecCertificateRef leaf = NULL, intermediate = NULL;
SecTrustRef trust = NULL;
SecPolicyRef policy = SecPolicyCreateBasicX509();
NSData *certificate_data;
NSArray *anchors;
/*
Parse inputs:
purchase_info_string and signature_string are base64 encoded JSON blobs that need to
be decoded.
*/
require([purchase_info_string canBeConvertedToEncoding:NSASCIIStringEncoding] &&
[signature_string canBeConvertedToEncoding:NSASCIIStringEncoding], outLabel);
size_t purchase_info_length;
uint8_t *purchase_info_bytes = base64_decode([purchase_info_string cStringUsingEncoding:NSASCIIStringEncoding],
&purchase_info_length);
size_t signature_length;
uint8_t *signature_bytes = base64_decode([signature_string cStringUsingEncoding:NSASCIIStringEncoding],
&signature_length);
require(purchase_info_bytes && signature_bytes, outLabel);
/*
Binary format looks as follows:
RECEIPTVERSION | SIGNATURE | CERTIFICATE SIZE | CERTIFICATE
1 byte 128 4 bytes
big endian
Extract version, signature and certificate(s).
Check receipt version == 2.
Sanity check that signature is 128 bytes.
Sanity check certificate size <= remaining payload data.
*/
#pragma pack(push, 1)
struct signature_blob {
uint8_t version;
uint8_t signature[128];
uint32_t cert_len;
uint8_t certificate[];
} *signature_blob_ptr = (struct signature_blob *)signature_bytes;
#pragma pack(pop)
uint32_t certificate_len;
/*
Make sure the signature blob is long enough to safely extract the version and
cert_len fields, then perform a sanity check on the fields.
*/
require(signature_length > offsetof(struct signature_blob, certificate), outLabel);
require(signature_blob_ptr->version == 2, outLabel);
certificate_len = ntohl(signature_blob_ptr->cert_len);
require(signature_length - offsetof(struct signature_blob, certificate) >= certificate_len, outLabel);
/*
Validate certificate chains back to valid receipt signer; policy approximation for now
set intermediate as a trust anchor; current intermediate lapses in 2016.
*/
certificate_data = [NSData dataWithBytes:signature_blob_ptr->certificate length:certificate_len];
require(leaf = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certificate_data), outLabel);
certificate_data = [NSData dataWithBytes:iTS_intermediate_der length:iTS_intermediate_der_len];
require(intermediate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certificate_data), outLabel);
anchors = [NSArray arrayWithObject:(__bridge id)intermediate];
require(anchors, outLabel);
require_noerr(SecTrustCreateWithCertificates(leaf, policy, &trust), outLabel);
require_noerr(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef) anchors), outLabel);
if (purchaseDate)
{
require_noerr(SecTrustSetVerifyDate(trust, purchaseDate), outLabel);
}
SecTrustResultType trust_result;
require_noerr(SecTrustEvaluate(trust, &trust_result), outLabel);
require(trust_result == kSecTrustResultUnspecified, outLabel);
require(2 == SecTrustGetCertificateCount(trust), outLabel);
/*
Chain is valid, use leaf key to verify signature on receipt by
calculating SHA1(version|purchaseInfo)
*/
CC_SHA1_CTX sha1_ctx;
uint8_t to_be_verified_data[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Init(&sha1_ctx);
CC_SHA1_Update(&sha1_ctx, &signature_blob_ptr->version, sizeof(signature_blob_ptr->version));
CC_SHA1_Update(&sha1_ctx, purchase_info_bytes, purchase_info_length);
CC_SHA1_Final(to_be_verified_data, &sha1_ctx);
SecKeyRef receipt_signing_key = SecTrustCopyPublicKey(trust);
require(receipt_signing_key, outLabel);
require_noerr(SecKeyRawVerify(receipt_signing_key, kSecPaddingPKCS1SHA1,
to_be_verified_data, sizeof(to_be_verified_data),
signature_blob_ptr->signature, sizeof(signature_blob_ptr->signature)),
outLabel);
/*
Optional: Verify that the receipt certificate has the 1.2.840.113635.100.6.5.1 Null OID
The signature is a 1024-bit RSA signature.
*/
valid = YES;
outLabel:
if (leaf) CFRelease(leaf);
if (intermediate) CFRelease(intermediate);
if (trust) CFRelease(trust);
if (policy) CFRelease(policy);
return valid;
}
I have implemented socket by using input and output streams. The external architecture takes care of sending one request at a time to write.
However if any request does not return no HasBytesAvailable I need to remove that request from queue and inform about request timeout.
For all other requests, I am able to send/receive data correctly, but if any one of the request time outs then after that HasSpaceAvailable never gets called.
My code is as follows :
#implementation CCCommandSocket
#synthesize connectionTimeoutTimer;
#synthesize requestTimeoutTimer;
/*
* init
*
* #params
* ipAddress :ip address of camera socket
* portNumber :port address of camera socket
*
* #return
* Object of type Socket, which will send connection request to ipAddress,portNumber
*
*/
- (id)init
{
self = [super init];
if (self)
{
ip = #"192.168.42.1";
port = 7878;
[self performSelectorOnMainThread:#selector(connectToCamera) withObject:nil waitUntilDone:YES];
bytesReceivedCondition = [[NSCondition alloc] init];
requestCompletedCondition = [[NSCondition alloc] init];
requestReadyToProcess = [[NSCondition alloc] init];
isBytesReceived = false;
isRequestCompleted = false;
isRequestReadyToProcess = false;
responseString = [[NSString alloc] init];
openBracesCount = 0;
mutex = [[NSLock alloc] init];
}
return self;
}
pragma mark-
pragma establish socket communication.
/*
* connectToCamera
*
*/
- (void) connectToCamera
{
NSString *urlStr = ip;
if (![urlStr isEqualToString:#""])
{
NSURL *website = [NSURL URLWithString:urlStr];
if (!website)
{
NSString* messageString = [NSString stringWithFormat:#"%# is not a valid URL",website];
CCLog(LOG_ERROR, messageString);
return;
}
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(urlStr), port, &readStream, &writeStream);
//cast the CFStreams to NSStreams
inputStream = (__bridge_transfer NSInputStream *)readStream;
outputStream = (__bridge_transfer NSOutputStream *)writeStream;
//set the delegate
[inputStream setDelegate:self];
[outputStream setDelegate:self];
//schedule the stream on a run loop
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
//open the stream
[inputStream open];
[outputStream open];
if(readStream==NULL)
{
CCLog(LOG_INFO, #"readstream NULL");
}
if(writeStream == NULL)
{
CCLog(LOG_INFO, #"writeStream NULL");
}
[self startConnectionTimeoutTimer];
}
}
pragma mark -
pragma getter methods
/*
* getIP
*
* #return
* Ip address to which socket is connected
*/
-(NSString *) getIP
{
return ip;
}
/*
* getPort
*
* #return
* Port number to which socket is connected
*/
-(int) getPort
{
return port;
}
pragma mark-
pragma Handle socket callbacks.
(void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
{
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:stream];
[array addObject:[NSNumber numberWithInt:eventCode]];
[self performSelectorInBackground:#selector(myStream:) withObject:array];
}
(void)myStream:(NSMutableArray*) array
{
NSNumber *number = [array objectAtIndex:1];
int eventCode = [number intValue];
switch(eventCode)
{
case NSStreamEventErrorOccurred:
{
CCLog(LOG_ERROR, #"In Command Socket NSStreamEventErrorOccurred");
//[self disconnect];
//[[ErrorDetails getInstance] reportError:NSStreamEventErrorOccurred];
break;
}
//Read from stream
case NSStreamEventHasBytesAvailable:
{
CCLog(LOG_INFO, #"In Command Socket NSStreamEventHasBytesAvailable");
[self handleCommandPortDataReceived];
break;
}
//Write to stream
case NSStreamEventHasSpaceAvailable:
{
#synchronized(self)
{
[requestReadyToProcess lock];
while (isRequestReadyToProcess == false)
{
[requestReadyToProcess wait];
}
[requestReadyToProcess unlock];
CCLog(LOG_INFO,#"In Command Socket NSStreamEventHasSpaceAvailable");
#try
{
#synchronized(requestString)
{
if(requestString != nil)
{
if(outputStream != nil)
{
int dataSent;
uint8_t* data = (uint8_t *)[requestString cStringUsingEncoding:NSUTF8StringEncoding];
responseString = #"";
//[requestReadyToProcess lock];
isRequestReadyToProcess = false;
//[requestReadyToProcess signal];
dataSent = [outputStream write:data maxLength:strlen((char*)data)];
if(dataSent != -1)
{
NSString* message = [NSString stringWithFormat:#"Bytes written %d for request\n %#",dataSent, requestString];
CCLog(LOG_REQUEST, message);
requestString = nil;
isBytesReceived = false;
[bytesReceivedCondition lock];
while (isBytesReceived ==false)
{
[bytesReceivedCondition wait];
}
[requestCompletedCondition lock];
isRequestCompleted = true;
[requestCompletedCondition signal];
[requestCompletedCondition unlock];
[bytesReceivedCondition unlock];
}
else
{
CCLog(LOG_INFO, #"Command Socket : Request not sent (dataSent == -1)");
responseString = #"{ \"rval\": -104}";
CCLog(LOG_RESPONSE, responseString);
[self removeRequestFromQueue];
}
}
else
{
CCLog(LOG_INFO, #"in else :(outputStream != nil)");
}
}
}
}
#catch (NSException *e)
{
CCLog(LOG_WARNING, e.description);
}
}
break;
}
case NSStreamEventNone:
{
CCLog(LOG_INFO, #"In Command Socket NSStreamEventNone");
break;
}
case NSStreamEventOpenCompleted:
{
CCLog(LOG_INFO, #"In Command Socket NSStreamEventOpenCompleted");
[self stopConnectionTimeoutTimer];
break;
}
case NSStreamEventEndEncountered:
{
CCLog(LOG_INFO, #"Command Socket NSStreamEventEndEncountered");
[self disconnectWithNotification:YES];
break;
}
}
}
/*
* execute
*
* #param
* request :command to be sent over socket to camera
*
* #return
* responce :response received from camera
*
*/
-(NSString *) executeRequest :(NSString *)request
{
CCLog(LOG_INFO, #"Command Socket Executing request");
[self performSelectorOnMainThread:#selector(startRequestTimeoutTimer) withObject:nil waitUntilDone:NO];
isRequestCompleted = false;
requestString = request;
responseString = #"";
[requestReadyToProcess lock];
isRequestReadyToProcess = true;
[requestReadyToProcess signal];
[requestReadyToProcess unlock];
[requestCompletedCondition lock];
while (isRequestCompleted ==false)
{
[requestCompletedCondition wait];
}
CCLog(LOG_INFO, #"Command Socket Execute request : request completed");
[requestCompletedCondition unlock];
CCLog(LOG_RESPONSE, responseString);
return responseString;
}
pragma mark-
pragma Handle connection time out
// Call this when you initiate the connection
- (void)startConnectionTimeoutTimer
{
[self stopConnectionTimeoutTimer]; // Or make sure any existing timer is stopped before this method is called
NSTimeInterval interval = 10.0; // Measured in seconds, is a double
self.connectionTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:#selector(handleConnectionTimeout)
userInfo:nil
repeats:NO];
}
(void)handleConnectionTimeout
{
responseString = #"{ \"rval\": -103}";
CCLog(LOG_RESPONSE, responseString);
[self removeRequestFromQueue];
[self disconnectWithNotification:YES];
[self stopConnectionTimeoutTimer];
}
// Call this when you initiate the connection
- (void)startRequestTimeoutTimer
{
[self stopRequestTimeoutTimer]; // Or make sure any existing timer is stopped before this method is called
NSTimeInterval interval = 20.0; // Measured in seconds, is a double
self.requestTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:#selector(handleRequestTimeout)
userInfo:nil
repeats:NO];
}
(void)handleRequestTimeout
{
responseString = #"{ \"rval\": -103}";
CCLog(LOG_RESPONSE, responseString);
[self connectToCamera];
[self stopRequestTimeoutTimer];
[self removeRequestFromQueue];
}
// Call this when you successfully connect
- (void)stopRequestTimeoutTimer
{
if (requestTimeoutTimer)
{
[requestTimeoutTimer invalidate];
requestTimeoutTimer = nil;
}
}
-(void) disconnectWithNotification:(BOOL)showNotification
{
CCLog(LOG_INFO, #"Socket Disconnected");
[inputStream close];
[inputStream setDelegate:nil];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
inputStream = nil;
[outputStream close];
[outputStream setDelegate:nil];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
outputStream = nil;
[[CCCore getInstance] disconnectWithNotification:showNotification];
}
// Call this when you successfully connect
- (void)stopConnectionTimeoutTimer
{
if (connectionTimeoutTimer)
{
[connectionTimeoutTimer invalidate];
connectionTimeoutTimer = nil;
}
if (requestTimeoutTimer)
{
[requestTimeoutTimer invalidate];
requestTimeoutTimer = nil;
}
}
-(void) handleCommandPortDataReceived
{
[mutex lock];
[self stopRequestTimeoutTimer];
#try
{
long size = 1024;
uint8_t buf[size];
unsigned int len = 0;
do
{
// read input stream into buffer
strcpy((char *)buf, "\0");
len = [inputStream read:buf maxLength:size];
//NSLog(#"Size = %ld Len = %d, Buf = %s",size, len, (char *)buf);
// Following code checks if we have received complete response by matching "{" and "}"
// from input stream. We continue to form response string unless braces are matched.
if (len > 0)
{
// Create nsdata from buffer
NSMutableData *_data = [[NSMutableData alloc] init];
[_data appendBytes:(const void *)buf length:len];
// create temporary string form nsdata
NSString* currentString = [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding];
// check the occurances of { and } in current string
int currentOpeningBraceCount = [[currentString componentsSeparatedByString:#"{"] count] - 1;
int currentClosingBraceCount = [[currentString componentsSeparatedByString:#"}"] count] - 1;
openBracesCount = (openBracesCount + currentOpeningBraceCount) - currentClosingBraceCount;
responseString = [responseString stringByAppendingString:currentString];
// NSLog(#"Total:%d currentOpen:%d currentClose:%d\n\n",openBracesCount, currentOpeningBraceCount, currentClosingBraceCount);
// NSLog(#"Current String : %#\n\n",currentString);
// NSLog(#"Final String : %#",finalString);
// NSLog(#"+++++++++++++++++++++++++++++");
}
else
break;
} while (openBracesCount != 0);
NSRange range = [responseString rangeOfString:#"get_file_complete"];
if(range.location == NSNotFound)
{
//remove it from queue
[bytesReceivedCondition lock];
isBytesReceived = true;
[bytesReceivedCondition signal];
[bytesReceivedCondition unlock];
}
//responseString = #"";
}
#catch (NSException* e)
{
[self connectToCamera];
}
[mutex unlock];
}
-(void) removeRequestFromQueue
{
//remove it from queue
requestString = nil;
[requestReadyToProcess lock];
isRequestReadyToProcess = false;
[requestReadyToProcess unlock];
[requestCompletedCondition lock];
isRequestCompleted = true;
[requestCompletedCondition signal];
[requestCompletedCondition unlock];
}
#end
Which OS version are you trying this on?? I'm having the similar issue, in 10.7 and up it is all good, but on 10.6 and below I get the very same issue you are having I'm doing some debugging but so far have not come up with a good resolution yet.
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
I have this model in one of my iPhone apps:
//
// CatapultAccount.m
// Catapult
//
// Created by Aziz Light on 4/9/13.
// Copyright (c) 2013 Catapult Technology Ltd. All rights reserved.
//
#import "CatapultAccount.h"
#interface CatapultAccount ()
- (BOOL)createAccountsTable;
- (BOOL)accountWithNameIsAlreadyAdded:(NSString *)accountName;
- (NSDictionary *)getAccountLogosForClient:(NSDictionary *)client;
- (NSString *)getAccountLogoFromURL:(NSURL *)url;
- (NSString *)saveImage:(UIImage *)image
withFileName:(NSString *)imageName
ofType:(NSString *)extension
inDirectory:(NSString *)directoryPath;
#end
#implementation CatapultAccount
- (BOOL)createAccountWithAccountID:(NSString *)accountID
{
__block BOOL operationSuccessfull;
NXOAuth2Account *account = [[NXOAuth2AccountStore sharedStore] accountWithIdentifier:accountID];
if (account == nil) {
operationSuccessfull = NO;
} else {
if ([self openDatabaseConnection]) {
if ([self createAccountsTable]) {
[NXOAuth2Request performMethod:#"GET"
onResource:[NSURL URLWithString:[NSString stringWithFormat:#"%#/users/me", kCatapultHost]]
usingParameters:nil
withAccount:account
sendProgressHandler:nil
responseHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {
if (error != nil) {
operationSuccessfull = NO;
#if DEBUG
NSLog(#"ERROR: %#", error);
#endif
_lastError = error;
} else {
NSError *jsonError;
NSDictionary *serializedResponse = [NSJSONSerialization JSONObjectWithData:responseData
options:kNilOptions
error:&jsonError];
if (jsonError != nil) {
operationSuccessfull = NO;
#if DEBUG
NSLog(#"ERROR: %#", jsonError);
#endif
_lastError = jsonError;
} else {
NSDictionary *user = [serializedResponse objectForKey:#"user"];
NSDictionary *client = [serializedResponse objectForKey:#"client"];
NSString *forename = [user objectForKey:#"forename"];
NSString *surname = [user objectForKey:#"surname"];
NSString *accountName = [client objectForKey:#"account_name"];
NSString *clientName = [client objectForKey:#"client_name"];
if ([self accountWithNameIsAlreadyAdded:accountName]) {
operationSuccessfull = NO;
_lastError = [NSError errorWithDomain:kCatapultAccountErrorDomain
code:kCatapultDuplicateAccountErrorCode
userInfo:#{#"message": #"You have already added this account"}];
#if DEBUG
NSLog(#"ERROR: %#", _lastError);
#endif
} else {
NSDictionary *logos = [self getAccountLogosForClient:client];
operationSuccessfull = [self.db executeUpdate:#"insert into accounts(account_id, forename, surname, account_name, client_name, smallest_logo, thumb_logo) values(?,?,?,?,?,?,?)",
accountID, forename, surname, accountName, clientName, logos[#"smallest_logo"], logos[#"thumb_logo"]];
}
}
}
}];
} else {
operationSuccessfull = NO;
_lastError = [NSError errorWithDomain:kCatapultDatabaseErrorDomain
code:kCatapultUnableToCreateTableErrorCode
userInfo:#{#"message": #"Unable to create the accounts table"}];
#if DEBUG
NSLog(#"ERROR: %#", _lastError);
#endif
}
[self closeDatabaseConnection];
} else {
operationSuccessfull = NO;
_lastError = [NSError errorWithDomain:kCatapultDatabaseErrorDomain
code:kCatapultUnableToOpenDatabaseConnectionErrorCode
userInfo:#{#"message": #"Unable to open database connection"}];
#if DEBUG
NSLog(#"ERROR: %#", _lastError);
#endif
}
}
return operationSuccessfull;
}
- (BOOL)createAccountsTable
{
// Accounts table schema
// id integer primary key autoincrement
// account_id varchar(36) not null - unique
// forename varchar(255) not null
// surname varchar(255) not null
// account_name varchar(255) not null - unique
// client_name varchar(255) not null
// smallest_account_logo text
// thumb_account_logo text
BOOL tableCreationWasSuccessfull = [self.db executeUpdate:#"create table if not exists accounts(id integer primary key autoincrement, account_id varchar(36) not null, forename varchar(255) not null, surname varchar(255) not null, account_name varchar(255) not null, client_name varchar(255) not null, smallest_logo text, thumb_logo text, unique(account_id, account_name) on conflict abort)"];
if (tableCreationWasSuccessfull) {
_lastError = nil;
} else {
_lastError = [self.db lastError];
#if DEBUG
NSLog(#"Failed to create users table: %#", _lastError);
#endif
}
return tableCreationWasSuccessfull;
}
- (BOOL)accountWithNameIsAlreadyAdded:(NSString *)accountName
{
FMResultSet *account = [self.db executeQuery:#"select count(*) from accounts"];
return [account next];
}
- (NSDictionary *)getAccountLogosForClient:(NSDictionary *)client
{
NSString *smallestLogoURLString = [[[client objectForKey:#"logo"] objectForKey:#"smallest"] objectForKey:#"url"];
NSString *smallestLogoPath = [self getAccountLogoFromURL:[NSURL URLWithString:smallestLogoURLString]];
NSString *thumbLogoURLString = [[[client objectForKey:#"logo"] objectForKey:#"thumb"] objectForKey:#"url"];
NSString *thumbLogoPath = [self getAccountLogoFromURL:[NSURL URLWithString:thumbLogoURLString]];
return #{#"smallest_logo": smallestLogoPath, #"thumb_logo": thumbLogoPath};
}
- (NSString *)getAccountLogoFromURL:(NSURL *)url
{
NSString *urlWithoutGETParams = [[[url absoluteString] componentsSeparatedByString:#"?"] objectAtIndex:0];
NSString *lastSegmentOfURL = [[urlWithoutGETParams componentsSeparatedByString:#"/"] lastObject];
NSString *logoName = [[lastSegmentOfURL componentsSeparatedByString:#"."] objectAtIndex:0];
NSString *logoExtension = [[lastSegmentOfURL componentsSeparatedByString:#"."] lastObject];
NSString * documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *logoPath = [documentsDirectoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", logoName, logoExtension]];
BOOL logoExists = [[NSFileManager defaultManager] fileExistsAtPath:logoPath];
if (logoExists) {
return logoPath;
} else {
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *logo = [UIImage imageWithData:data];
logoPath = [self saveImage:logo withFileName:logoName ofType:logoExtension inDirectory:documentsDirectoryPath];
return (logoPath == nil) ? nil : logoPath;
}
}
- (NSString *)saveImage:(UIImage *)image
withFileName:(NSString *)imageName
ofType:(NSString *)extension
inDirectory:(NSString *)directoryPath
{
NSData *imageRepresentation;
if ([[extension lowercaseString] isEqualToString:#"png"] || [[extension lowercaseString] isEqualToString:#"gif"]) {
imageRepresentation = UIImagePNGRepresentation(image);
} else if ([[extension lowercaseString] isEqualToString:#"jpg"] || [[extension lowercaseString] isEqualToString:#"jpeg"]) {
imageRepresentation = UIImageJPEGRepresentation(image, 1.0);
} else {
#if DEBUG
NSLog(#"Image Save Failed\nExtension: (%#) is not recognized, use (PNG/JPG/GIF)", extension);
#endif
return nil;
}
NSString *imagePath = [directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.%#", imageName, extension]];
NSError *error;
BOOL imageDidSave = [imageRepresentation writeToFile:imagePath
options:NSAtomicWrite
error:&error];
if (error != nil) {
#if DEBUG
NSLog(#"Error saving the file: %#", error);
#endif
}
return (imageDidSave) ? imagePath : nil;
}
#end
And this method in one of my view controllers:
- (void)createAccount:(NSNotification *)notification
{
NXOAuth2Account *account = [notification.userInfo objectForKey:#"NXOAuth2AccountStoreNewAccountUserInfoKey"];
if ([_accountModel createAccountWithAccountID:account.identifier]) {
// Do something
NSLog(#"Yay");
} else {
// Delete the newly created account
[[NXOAuth2AccountStore sharedStore] removeAccount:account];
UIAlertView *errorMessage = [[UIAlertView alloc] initWithTitle:#"Account Error"
message:#"Unable to add new account"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[errorMessage show];
}
[self dismissViewControllerAnimated:YES completion:nil];
}
The problem is that, following my (naive) observations, most of the methods that use the database in the model execute too slowly and the createAccountWithAccountID: method doesn't wait for the method it calls to finish. The result of that is that the record is not saved to the database but for some reason the createAccountWithAccountID method returns YES… here are the logs that illustrate what I am saying:
2013-04-09 20:07:46.261 Catapult[21004:c07] Yay
2013-04-09 20:07:46.276 Catapult[21004:c07] The FMDatabase <FMDatabase: 0x7288300> is not open.
2013-04-09 20:07:46.606 Catapult[21004:c07] The FMDatabase <FMDatabase: 0x7288300> is not open.
The record is not saved to the database because the database connection gets closed too fast…
Does anybody know how I can solve my problem please?
Most methods which use a completion block execute asynchronously. The method will return immediately and the block will be executed when the request actually completes. You need to handle it accordingly in your code. Don't release anything after you send the request, but do anything that depends on the result in the response handler block and then release it.