yogshototh/Yog-Shot-Oth/libs/cocoslive/CLScoreServerPost.m
Yann Esposito (Yogsototh) 5601e1a32d Initial Commit
2011-09-24 09:15:59 +02:00

335 lines
10 KiB
Objective-C

/*
* cocos2d for iPhone: http://www.cocos2d-iphone.org
*
* Copyright (c) 2008-2010 Ricardo Quesada
* Copyright (c) 2011 Zynga Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#import "CLScoreServerPost.h"
#import "ccMacros.h"
// free function used to sort
NSInteger alphabeticSort(id string1, id string2, void *reverse);
NSInteger alphabeticSort(id string1, id string2, void *reverse)
{
if ((NSInteger *)reverse == NO)
return [string2 localizedCaseInsensitiveCompare:string1];
return [string1 localizedCaseInsensitiveCompare:string2];
}
@interface CLScoreServerPost (Private)
-(void) addValue:(NSString*)value key:(NSString*)key;
-(void) calculateHashAndAddValue:(id)value key:(NSString*)key;
-(NSString*) getHashForData;
-(NSData*) getBodyValues;
-(NSString*) encodeData:(NSString*)data;
-(NSMutableURLRequest *) scoreServerRequestWithURLString:(NSString *)url;
-(BOOL) submitScore:(NSDictionary*)dict forUpdate:(BOOL)isUpdate;
@end
@implementation CLScoreServerPost
@synthesize postStatus = postStatus_;
@synthesize ranking = ranking_;
@synthesize scoreDidUpdate = scoreDidUpdate_;
@synthesize connection = connection_;
+(id) serverWithGameName:(NSString*) name gameKey:(NSString*) key delegate:(id) delegate
{
return [[[self alloc] initWithGameName:name gameKey:key delegate:delegate] autorelease];
}
-(id) initWithGameName:(NSString*) name gameKey:(NSString*) key delegate:(id)aDelegate
{
self = [super init];
if( self ) {
gameKey = [key retain];
gameName = [name retain];
bodyValues = [[NSMutableArray arrayWithCapacity:5] retain];
delegate = [aDelegate retain];
receivedData = [[NSMutableData data] retain];
ranking_ = kServerPostInvalidRanking;
}
return self;
}
-(void) dealloc
{
CCLOGINFO(@"deallocing %@", self);
[delegate release];
[gameKey release];
[gameName release];
[bodyValues release];
[receivedData release];
[connection_ release];
[super dealloc];
}
#pragma mark ScoreServer send scores
-(BOOL) sendScore: (NSDictionary*) dict
{
return [self submitScore:dict forUpdate:NO];
}
-(BOOL) updateScore: (NSDictionary*) dict
{
if (![dict objectForKey:@"cc_playername"]) {
// fail. cc_playername + cc_device_id are needed to update an score
[NSException raise:@"cocosLive:updateScore" format:@"cc_playername not found"];
}
return [self submitScore:dict forUpdate:YES];
}
-(BOOL) submitScore: (NSDictionary*)dict forUpdate:(BOOL)isUpdate
{
[receivedData setLength:0];
[bodyValues removeAllObjects];
// reset status
postStatus_ = kPostStatusOK;
// create the request
NSMutableURLRequest *post = [self scoreServerRequestWithURLString:(isUpdate ? SCORE_SERVER_UPDATE_URL : SCORE_SERVER_SEND_URL)];
CC_MD5_Init( &md5Ctx);
// hash SHALL be calculated in certain order
NSArray *keys = [dict allKeys];
int reverseSort = NO;
NSArray *sortedKeys = [keys sortedArrayUsingFunction:alphabeticSort context:&reverseSort];
for( id key in sortedKeys )
[self calculateHashAndAddValue:[dict objectForKey:key] key:key];
// device id is hashed to prevent spoofing this same score from different devices
// one way to prevent a replay attack is to send cc_id & cc_time and use it as primary keys
[self addValue:[[UIDevice currentDevice] uniqueIdentifier] key:@"cc_device_id"];
[self addValue:gameName key:@"cc_gamename"];
[self addValue:[self getHashForData] key:@"cc_hash"];
[self addValue:SCORE_SERVER_PROTOCOL_VERSION key:@"cc_prot_ver"];
[post setHTTPBody: [self getBodyValues] ];
// create the connection with the request
// and start loading the data
self.connection=[NSURLConnection connectionWithRequest:post delegate:self];
if ( ! connection_)
return NO;
return YES;
}
-(NSMutableURLRequest *) scoreServerRequestWithURLString:(NSString *)url {
NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:[NSURL URLWithString: url]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
[request setHTTPMethod: @"POST"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
return request;
}
-(void) calculateHashAndAddValue:(id) value key:(NSString*) key
{
NSString *val;
// value shall be a string or nsnumber
if( [value respondsToSelector:@selector(stringValue)] )
val = [value stringValue];
else if( [value isKindOfClass:[NSString class]] )
val = value;
else
[NSException raise:@"Invalid format for value" format:@"Invalid format for value. addValue"];
[self addValue:val key:key];
const char * data = [val UTF8String];
CC_MD5_Update( &md5Ctx, data, strlen(data) );
}
-(void) addValue:(NSString*)value key:(NSString*) key
{
NSString *encodedValue = [self encodeData:value];
NSString *encodedKey = [self encodeData:key];
[bodyValues addObject: [NSString stringWithFormat:@"%@=%@", encodedKey, encodedValue] ];
}
-(NSData*) getBodyValues {
NSMutableData *data = [[NSMutableData alloc] init];
BOOL first=YES;
for( NSString *s in bodyValues ) {
if( !first)
[data appendBytes:"&" length:1];
[data appendBytes:[s UTF8String] length:[s length]];
first = NO;
}
return [data autorelease];
}
-(NSString*) getHashForData
{
NSString *ret;
unsigned char pTempKey[16];
// update the hash with the secret key
const char *data = [gameKey UTF8String];
CC_MD5_Update(&md5Ctx, data, strlen(data));
// then get the hash
CC_MD5_Final( pTempKey, &md5Ctx);
// NSData *nsdata = [NSData dataWithBytes:pTempKey length:16];
ret = [NSString stringWithString:@""];
for( int i=0;i<16;i++) {
ret = [NSString stringWithFormat:@"%@%02x", ret, pTempKey[i] ];
}
return ret;
}
-(NSString*) encodeData:(NSString*) data
{
NSString *newData;
newData = [data stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// '&' and '=' should be encoded manually
newData = [newData stringByReplacingOccurrencesOfString:@"&" withString:@"%26"];
newData = [newData stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"];
return newData;
}
#pragma mark NSURLConnection Delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// this method is called when the server has determined that it
// has enough information to create the NSURLResponse
// it can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is declared as a method instance elsewhere
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// append the new data to the receivedData
// receivedData is declared as a method instance elsewhere
[receivedData appendData:data];
// NSString *dataString = [NSString stringWithCString:[data bytes] length: [data length]];
// CCLOG( @"data: %@", dataString);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
CCLOG(@"Connection failed");
// wifi problems ?
postStatus_ = kPostStatusConnectionFailed;
// release the connection
self.connection = nil;
if( [delegate respondsToSelector:@selector(scorePostFail:) ] )
[delegate scorePostFail:self];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// release the connection
self.connection = nil;
// NSString *dataString = [NSString stringWithCString:[receivedData bytes] length: [receivedData length]];
NSString *dataString = [NSString stringWithCString:[receivedData bytes] encoding: NSASCIIStringEncoding];
if( [dataString hasPrefix:@"OK:"] ) {
// parse ranking and other possible answers
NSArray *values = [dataString componentsSeparatedByString:@":"];
NSArray *answer = [ [values objectAtIndex:1] componentsSeparatedByString:@","];
NSEnumerator *nse = [answer objectEnumerator];
// Create a holder for each line we are going to work with
NSString *line;
// Loop through all the lines in the lines array processing each one
while( (line = [nse nextObject]) ) {
NSArray *keyvalue = [line componentsSeparatedByString:@"="];
// NSLog(@"%@",keyvalue);
if( [[keyvalue objectAtIndex:0] isEqual:@"ranking"] ) {
ranking_ = [[keyvalue objectAtIndex:1] intValue];
} else if( [[keyvalue objectAtIndex:0] isEqual:@"score_updated"] ) {
scoreDidUpdate_ = [[keyvalue objectAtIndex:1] boolValue];
}
}
if( [delegate respondsToSelector:@selector(scorePostOk:) ] )
[delegate scorePostOk:self];
} else if( [dataString hasPrefix: @"OK"] ) {
// Ok
postStatus_ = kPostStatusOK;
if( [delegate respondsToSelector:@selector(scorePostOk:) ] )
[delegate scorePostOk:self];
} else {
NSLog(@"cocoslive: Post Score failed. Reason: %@", dataString);
// Error parsing answer
postStatus_ = kPostStatusPostFailed;
if( [delegate respondsToSelector:@selector(scorePostFail:) ] )
[delegate scorePostFail:self];
}
}
-(NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
{
NSURLRequest *newRequest=request;
if (redirectResponse) {
newRequest=nil;
}
return newRequest;
}
@end