def sessionTokenCall(sessionToken, playerId, methodName, *args): playerSession = getPlayerSession(playerId) if playerSession is None: return getErrorResponse(NO_ACTIVE_SESSION_ERROR) if playerSession.method != 'token' or playerSession.token is None: return getErrorResponse(TOKEN_METHOD_ERROR); if sessionToken == playerSession.token: if playerSession.isExpired(): deletePlayerSession(playerId) return getErrorResponse(SESSION_EXPIRED_ERROR) method = _getMethodFromName(methodName) if method: if args is None or len(args) == 0: return method(playerId) else: return method(playerId, *args) else: return getErrorResponse(UNKNOW_SERVICE_CALL_ERROR) else: return getErrorResponse(INVALID_SESSION_TOKEN_ERROR)
def reviewScore(playerId, score, adminMode=False): if adminMode: admin = getAdmin() try: admin.playerList.index(playerId) except ValueError: return getErrorResponse(ADMIN_ONLY) scoreValue = score['score'] scoreTime = score['time'] playerKey = Key.from_path('Player', playerId) reviewSession = ReviewSession.get_by_key_name('reviewSession', parent=playerKey) if reviewSession is None: return {'result': {'message': 'nothing to review'}} scoreToReviewKey = ReviewSession.currentScoreToReview.get_value_for_datastore( reviewSession) # We are done with it reviewSession.delete() # The above could have potentially be put in a transaction but since there is only one player concerned, it should not matter def _increaseNumScoreReviewed(): playerRecord = Record.get_by_key_name('record', parent=playerKey) playerRecord.numScoreReviewed += 1 playerRecord.put() db.run_in_transaction(_increaseNumScoreReviewed) try: cheaters = db.run_in_transaction(_checkConflicts, scoreToReviewKey, scoreValue, scoreTime, playerId, adminMode) except TransactionFailedError: return getErrorResponse(CHECKCONFLICT_TRANSACTION_FAILURE, 0) if cheaters: def _cheaterUpdate(cheaterKey): cheaterRecord = Record.get_by_key_name('record', parent=cheaterKey) cheaterRecord.numCheat += 1 cheaterRecord.put() for cheaterKey in cheaters: db.run_in_transaction(_cheaterUpdate, cheaterKey) #TODO : check TRANSACTION ERROR return {'result': {'message': 'review submited'}}
def start(playerId): def _start(): playerKey = Key.from_path('Player', playerId) rand = random.SystemRandom() seed = struct.pack("4L", rand.randint(1, MAX_INT32_VALUE), rand.randint(1, MAX_INT32_VALUE), rand.randint(1, MAX_INT32_VALUE), rand.randint(1, MAX_INT32_VALUE)) playSession = PlaySession(key_name='playSession', seed=seed, version=config.currentGameMechanicVersion, seedDateTime=datetime.datetime.now(), parent=playerKey) playSession.put() today = datetime.date.today() playerRecord = Record.get_by_key_name('record', parent=playerKey) if playerRecord.lastDayPlayed != today: playerRecord.numDaysPlayed += 1 playerRecord.lastDayPlayed = today playerRecord.put() seedList = struct.unpack("4L", playSession.seed) return {'result' : { 'seed' : seedList, 'version': playSession.version } } try: return db.run_in_transaction(_start) except TransactionFailedError: return getErrorResponse(START_TRANSACTION_FAILURE, 0)
def start(playerId): def _start(): playerKey = Key.from_path('Player', playerId) rand = random.SystemRandom() seed = struct.pack("4L", rand.randint(1, MAX_INT32_VALUE), rand.randint(1, MAX_INT32_VALUE), rand.randint(1, MAX_INT32_VALUE), rand.randint(1, MAX_INT32_VALUE)) playSession = PlaySession(key_name='playSession', seed=seed, version=config.currentGameMechanicVersion, seedDateTime=datetime.datetime.now(), parent=playerKey) playSession.put() today = datetime.date.today() playerRecord = Record.get_by_key_name('record', parent=playerKey) if playerRecord.lastDayPlayed != today: playerRecord.numDaysPlayed += 1 playerRecord.lastDayPlayed = today playerRecord.put() seedList = struct.unpack("4L", playSession.seed) return {'result': {'seed': seedList, 'version': playSession.version}} try: return db.run_in_transaction(_start) except TransactionFailedError: return getErrorResponse(START_TRANSACTION_FAILURE, 0)
def _setScore(): playSession.delete() verifiedScore = None verifiedScoreWrapper = VerifiedScoreWrapper.get_by_key_name( "verifiedScore", parent=playerKey) if verifiedScoreWrapper is not None: verifiedScore = verifiedScoreWrapper.verified if verifiedScore is None or scoreValue > verifiedScore.value or ( scoreValue == verifiedScore.value and scoreTime < verifiedScore.time): pendingScore = PendingScore.get_by_key_name("pendingScore", parent=playerKey) if pendingScore is not None: nonVerifiedScore = pendingScore.nonVerified else: nonVerifiedScore = None if nonVerifiedScore is None or scoreValue > nonVerifiedScore.value or ( scoreValue == nonVerifiedScore.value and scoreTime < nonVerifiedScore.time): try: proofBlob = db.Blob(proof) except Exception: proofText = str(proof) proofBlob = db.Blob(proofText) nonVerifiedScore = NonVerifiedScore(value=scoreValue, time=scoreTime, proof=proofBlob, seed=seed, version=version, parent=playerKey) nonVerifiedScore.put() if pendingScore is None: pendingScore = PendingScore(key_name='pendingScore', parent=playerKey, nonVerified=nonVerifiedScore) else: conflicts = ReviewConflict.gql( "WHERE ANCESTOR IS :score", score=pendingScore.nonVerified.key()).fetch( 100) # shoud not be more than 2 for conflict in conflicts: conflict.delete() pendingScore.nonVerified.delete() pendingScore.nonVerified = nonVerifiedScore pendingScore.put() return {'result': {'message': 'success'}} else: pass # TODO : are you trying to cheat? else: pass # TODO : are you trying to cheat? return getErrorResponse(SCORE_TOO_SMALL)
def reviewScore(playerId, score, adminMode=False): if adminMode: admin = getAdmin() try: admin.playerList.index(playerId) except ValueError: return getErrorResponse(ADMIN_ONLY) scoreValue = score['score'] scoreTime = score['time'] playerKey = Key.from_path('Player', playerId) reviewSession = ReviewSession.get_by_key_name('reviewSession', parent=playerKey) if reviewSession is None: return {'result' : {'message' : 'nothing to review'} } scoreToReviewKey = ReviewSession.currentScoreToReview.get_value_for_datastore(reviewSession) # We are done with it reviewSession.delete() # The above could have potentially be put in a transaction but since there is only one player concerned, it should not matter def _increaseNumScoreReviewed(): playerRecord = Record.get_by_key_name('record', parent=playerKey) playerRecord.numScoreReviewed += 1 playerRecord.put() db.run_in_transaction(_increaseNumScoreReviewed) try: cheaters = db.run_in_transaction(_checkConflicts, scoreToReviewKey, scoreValue, scoreTime, playerId, adminMode) except TransactionFailedError: return getErrorResponse(CHECKCONFLICT_TRANSACTION_FAILURE, 0) if cheaters: def _cheaterUpdate(cheaterKey): cheaterRecord = Record.get_by_key_name('record', parent=cheaterKey) cheaterRecord.numCheat+=1 cheaterRecord.put() for cheaterKey in cheaters: db.run_in_transaction(_cheaterUpdate,cheaterKey) #TODO : check TRANSACTION ERROR return {'result' : {'message' : 'review submited'} }
def forceReviewTimeUnit(playerId, value): admin = getAdmin() try: admin.playerList.index(playerId) except ValueError: return getErrorResponse(ADMIN_ONLY) oldReviewTimeUnit = getReviewTimeUnit() setReviewTimeUnit(value) return {'result': {'oldReviewTimeUnit': oldReviewTimeUnit}}
def forceReviewTimeUnit(playerId, value): admin = getAdmin() try: admin.playerList.index(playerId) except ValueError: return getErrorResponse(ADMIN_ONLY) oldReviewTimeUnit = getReviewTimeUnit() setReviewTimeUnit(value) return {'result' : { 'oldReviewTimeUnit' : oldReviewTimeUnit} }
def signedRequestCall(signedRequest): signature, payload = decode_signedRequest(signedRequest) data = json.loads(base64_url_decode(payload)) playerId = data['playerId'] methodName = data['methodName'] if 'args' in data: args = data['args'] else: args = None playerSession = getPlayerSession(playerId) if playerSession is None: return getErrorResponse(NO_ACTIVE_SESSION_ERROR) if playerSession.method != 'signedRequest' or playerSession.secret is None: return getErrorResponse(SIGNED_REQUEST_METHOD_ERROR) verified = verifySignature(signature, payload, playerSession.secret, 'SHA1') # TODO decide which algorithm to use (haxe need to support SHA256 if not verified: return getErrorResponse(INVALID_SIGNATURE_ERROR) if playerSession.isExpired(): deletePlayerSession(playerId) return getErrorResponse(SESSION_EXPIRED_ERROR) method = _getMethodFromName(methodName) if method: if args is None or len(args) == 0: return method(playerId) else: return method(playerId, *args) else: return getErrorResponse(UNKNOW_SERVICE_CALL_ERROR)
def _setScore(): playSession.delete() verifiedScore = None verifiedScoreWrapper = VerifiedScoreWrapper.get_by_key_name("verifiedScore", parent=playerKey) if verifiedScoreWrapper is not None: verifiedScore = verifiedScoreWrapper.verified if verifiedScore is None or scoreValue > verifiedScore.value or (scoreValue == verifiedScore.value and scoreTime < verifiedScore.time): pendingScore = PendingScore.get_by_key_name("pendingScore", parent=playerKey) if pendingScore is not None: nonVerifiedScore = pendingScore.nonVerified else: nonVerifiedScore = None if nonVerifiedScore is None or scoreValue > nonVerifiedScore.value or (scoreValue == nonVerifiedScore.value and scoreTime < nonVerifiedScore.time): try: proofBlob = db.Blob(proof) except Exception: proofText = str(proof) proofBlob = db.Blob(proofText) nonVerifiedScore = NonVerifiedScore(value=scoreValue,time=scoreTime,proof=proofBlob,seed=seed,version=version, parent=playerKey) nonVerifiedScore.put() if pendingScore is None: pendingScore = PendingScore(key_name='pendingScore', parent=playerKey, nonVerified=nonVerifiedScore) else: conflicts = ReviewConflict.gql("WHERE ANCESTOR IS :score", score=pendingScore.nonVerified.key()).fetch(100) # shoud not be more than 2 for conflict in conflicts: conflict.delete() pendingScore.nonVerified.delete() pendingScore.nonVerified = nonVerifiedScore pendingScore.put() return {'result' : {'message' : 'success'} } else: pass # TODO : are you trying to cheat? else: pass # TODO : are you trying to cheat? return getErrorResponse(SCORE_TOO_SMALL)
def getHighestNonApprovedScore(playerId): admin = getAdmin() try: admin.playerList.index(playerId) except ValueError: return getErrorResponse(ADMIN_ONLY) scoreToApprove = db.GqlQuery("SELECT * FROM VerifiedScore WHERE approvedByAdmin = False ORDER BY value DESC").get() if scoreToApprove is not None: playerKey = Key.from_path('Player', playerId) approveSession = ApproveSession(key_name='approveSession', currentScoreToApprove=scoreToApprove, parent=playerKey) approveSession.put() seedList = struct.unpack("4L", scoreToApprove.seed) return {'result' : { 'proof' : scoreToApprove.proof, 'seed' : seedList} } return {'result' : { 'message' : 'Nothing to approve for now'} }
def getHighestNonApprovedScore(playerId): admin = getAdmin() try: admin.playerList.index(playerId) except ValueError: return getErrorResponse(ADMIN_ONLY) scoreToApprove = db.GqlQuery( "SELECT * FROM VerifiedScore WHERE approvedByAdmin = False ORDER BY value DESC" ).get() if scoreToApprove is not None: playerKey = Key.from_path('Player', playerId) approveSession = ApproveSession(key_name='approveSession', currentScoreToApprove=scoreToApprove, parent=playerKey) approveSession.put() seedList = struct.unpack("4L", scoreToApprove.seed) return {'result': {'proof': scoreToApprove.proof, 'seed': seedList}} return {'result': {'message': 'Nothing to approve for now'}}
def getOwnHighScore(playerId): def _getOwnHighScore(): score = None playerKey = Key.from_path('Player', playerId) bestScore = PendingScore.get_by_key_name("pendingScore", parent=playerKey) if bestScore is None: bestScore = VerifiedScoreWrapper.get_by_key_name("verifiedScore", parent=playerKey) if bestScore is not None: score = bestScore.verified else: score = bestScore.nonVerified if score is None: return {'result' : { 'score' : 0, 'time' : 0} } return {'result' : { 'score' : score.value, 'time' : score.time} } try: return db.run_in_transaction(_getOwnHighScore) except TransactionFailedError: return getErrorResponse(OWNHIGHSCORE_TRANSACTION_FAILURE, 0)
def getOwnHighScore(playerId): def _getOwnHighScore(): score = None playerKey = Key.from_path('Player', playerId) bestScore = PendingScore.get_by_key_name("pendingScore", parent=playerKey) if bestScore is None: bestScore = VerifiedScoreWrapper.get_by_key_name("verifiedScore", parent=playerKey) if bestScore is not None: score = bestScore.verified else: score = bestScore.nonVerified if score is None: return {'result': {'score': 0, 'time': 0}} return {'result': {'score': score.value, 'time': score.time}} try: return db.run_in_transaction(_getOwnHighScore) except TransactionFailedError: return getErrorResponse(OWNHIGHSCORE_TRANSACTION_FAILURE, 0)
def setScore(playerId, score): scoreValue = score['score'] scoreTime = score['time'] proof = score['proof'] playerKey = Key.from_path('Player', playerId) playSession = PlaySession.get_by_key_name('playSession', parent=playerKey) if playSession is None: return getErrorResponse(NO_PLAYER_SESSION) seed = playSession.seed version = playSession.version seedDateTime = playSession.seedDateTime now = datetime.datetime.now() # TODO : investigate: should we consider the player as cheater for this two exception ? if seedDateTime + datetime.timedelta(milliseconds=scoreTime) > now: return getErrorResponse(NOT_ENOUGH_TIME) maxScoreTime = scoreTime + MINIMUM_TIME if seedDateTime + datetime.timedelta(milliseconds=maxScoreTime) < now: return getErrorResponse(TOO_MUCH_TIME) def _setScore(): playSession.delete() verifiedScore = None verifiedScoreWrapper = VerifiedScoreWrapper.get_by_key_name("verifiedScore", parent=playerKey) if verifiedScoreWrapper is not None: verifiedScore = verifiedScoreWrapper.verified if verifiedScore is None or scoreValue > verifiedScore.value or (scoreValue == verifiedScore.value and scoreTime < verifiedScore.time): pendingScore = PendingScore.get_by_key_name("pendingScore", parent=playerKey) if pendingScore is not None: nonVerifiedScore = pendingScore.nonVerified else: nonVerifiedScore = None if nonVerifiedScore is None or scoreValue > nonVerifiedScore.value or (scoreValue == nonVerifiedScore.value and scoreTime < nonVerifiedScore.time): try: proofBlob = db.Blob(proof) except Exception: proofText = str(proof) proofBlob = db.Blob(proofText) nonVerifiedScore = NonVerifiedScore(value=scoreValue,time=scoreTime,proof=proofBlob,seed=seed,version=version, parent=playerKey) nonVerifiedScore.put() if pendingScore is None: pendingScore = PendingScore(key_name='pendingScore', parent=playerKey, nonVerified=nonVerifiedScore) else: conflicts = ReviewConflict.gql("WHERE ANCESTOR IS :score", score=pendingScore.nonVerified.key()).fetch(100) # shoud not be more than 2 for conflict in conflicts: conflict.delete() pendingScore.nonVerified.delete() pendingScore.nonVerified = nonVerifiedScore pendingScore.put() return {'result' : {'message' : 'success'} } else: pass # TODO : are you trying to cheat? else: pass # TODO : are you trying to cheat? return getErrorResponse(SCORE_TOO_SMALL) try: return db.run_in_transaction(_setScore) except TransactionFailedError: return getErrorResponse(SETSCORE_TRANSACTION_FAILURE, 0)
def approveScore(playerId, score): admin = getAdmin() try: admin.playerList.index(playerId) except ValueError: return getErrorResponse(ADMIN_ONLY) scoreValue = score['score'] scoreTime = score['time'] playerKey = Key.from_path('Player', playerId) approveSession = ApproveSession.get_by_key_name('approveSession', parent=playerKey) if approveSession is None: return getErrorResponse(NOTHING_TO_REVIEW) scoreToApproveKey = ApproveSession.currentScoreToApprove.get_value_for_datastore(approveSession) # We are done with it approveSession.delete() def _approveScore(): scoreToApprove = db.get(scoreToApproveKey) if scoreToApprove is None: return {'cheater' : None, 'nonCheaters': []} approvedPlayerKey = scoreToApprove.parent_key() cheater = None nonCheaters = [] if scoreToApprove.value == scoreValue and scoreToApprove.time == scoreTime: scoreToApprove.approvedByAdmin = True scoreToApprove.put() else: approvedPlayerRecord = Record.get_by_key_name('record', parent=approvedPlayerKey) approvedPlayerRecord.numCheat += 1 approvedPlayerRecord.put() cheater = Key.from_path('Player', scoreToApprove.verifier) for nonCheaterId in scoreToApprove.conflictingReviewers: nonCheaters.append(Key.from_path('Player', nonCheaterId)) scoreToApprove.delete() db.delete(Key.from_path('VerifiedScoreWrapper', 'verifiedScore', parent = approvedPlayerKey)) return {'cheater' : cheater, 'nonCheaters': nonCheaters} try: result = db.run_in_transaction(_approveScore) except TransactionFailedError: return getErrorResponse(APPROVESCORE_TRANSACTION_FAILURE, 0) cheater = result['cheater'] nonCheaters = result['nonCheaters'] if cheater is not None: cheaterRecord = Record.get_by_key_name('record', parent=cheater) cheaterRecord.numCheat+=1 cheaterRecord.put() if nonCheaters: def _nonCheaterUpdate(nonCheaterKey): cheaterRecord = Record.get_by_key_name('record', parent=nonCheaterKey) cheaterRecord.numCheat-=1 cheaterRecord.put() for nonCheaterKey in nonCheaters: db.run_in_transaction(_nonCheaterUpdate,nonCheaterKey) return {'result' : { 'message' : 'approvement submited'} }
def setScore(playerId, score): scoreValue = score['score'] scoreTime = score['time'] proof = score['proof'] playerKey = Key.from_path('Player', playerId) playSession = PlaySession.get_by_key_name('playSession', parent=playerKey) if playSession is None: return getErrorResponse(NO_PLAYER_SESSION) seed = playSession.seed version = playSession.version seedDateTime = playSession.seedDateTime now = datetime.datetime.now() # TODO : investigate: should we consider the player as cheater for this two exception ? if seedDateTime + datetime.timedelta(milliseconds=scoreTime) > now: return getErrorResponse(NOT_ENOUGH_TIME) maxScoreTime = scoreTime + MINIMUM_TIME if seedDateTime + datetime.timedelta(milliseconds=maxScoreTime) < now: return getErrorResponse(TOO_MUCH_TIME) def _setScore(): playSession.delete() verifiedScore = None verifiedScoreWrapper = VerifiedScoreWrapper.get_by_key_name( "verifiedScore", parent=playerKey) if verifiedScoreWrapper is not None: verifiedScore = verifiedScoreWrapper.verified if verifiedScore is None or scoreValue > verifiedScore.value or ( scoreValue == verifiedScore.value and scoreTime < verifiedScore.time): pendingScore = PendingScore.get_by_key_name("pendingScore", parent=playerKey) if pendingScore is not None: nonVerifiedScore = pendingScore.nonVerified else: nonVerifiedScore = None if nonVerifiedScore is None or scoreValue > nonVerifiedScore.value or ( scoreValue == nonVerifiedScore.value and scoreTime < nonVerifiedScore.time): try: proofBlob = db.Blob(proof) except Exception: proofText = str(proof) proofBlob = db.Blob(proofText) nonVerifiedScore = NonVerifiedScore(value=scoreValue, time=scoreTime, proof=proofBlob, seed=seed, version=version, parent=playerKey) nonVerifiedScore.put() if pendingScore is None: pendingScore = PendingScore(key_name='pendingScore', parent=playerKey, nonVerified=nonVerifiedScore) else: conflicts = ReviewConflict.gql( "WHERE ANCESTOR IS :score", score=pendingScore.nonVerified.key()).fetch( 100) # shoud not be more than 2 for conflict in conflicts: conflict.delete() pendingScore.nonVerified.delete() pendingScore.nonVerified = nonVerifiedScore pendingScore.put() return {'result': {'message': 'success'}} else: pass # TODO : are you trying to cheat? else: pass # TODO : are you trying to cheat? return getErrorResponse(SCORE_TOO_SMALL) try: return db.run_in_transaction(_setScore) except TransactionFailedError: return getErrorResponse(SETSCORE_TRANSACTION_FAILURE, 0)
def getRandomScore(playerId): playerKey = Key.from_path('Player', playerId) playerRecord = Record.get_by_key_name('record', parent=playerKey) # do not review if you are a cheater if playerRecord.numCheat > 0: return getErrorResponse(CHEATER_BLOCKED) reviewTimeUnitMilliseconds = getReviewTimeUnit() reviewTimeUnit = datetime.timedelta(milliseconds=reviewTimeUnitMilliseconds) now =datetime.datetime.now() oldEnoughTime = now - reviewTimeUnit delay = 2000 + random.random() * 5000 + ceil(reviewTimeUnitMilliseconds * (1 + random.random() * 2)) if delay > MAX_INT32_VALUE: delay = MAX_INT32_VALUE def _updateLastReviewAttemptDateTime(): if playerRecord.lastReviewAttemptDateTime is not None and playerRecord.lastReviewAttemptDateTime > oldEnoughTime: # TODO : check whethe rthis randomize stuff is good or not: return {'result' : {'message' : 'You already posted enough reviews, retry later', 'retry' : delay } } # could be 2 * reviewTimeUnit / config.nbPlayerPerTimeUnit playerRecord.lastReviewAttemptDateTime = datetime.datetime.now() playerRecord.put() return None reviewSession = ReviewSession.get_by_key_name('reviewSession', parent=playerKey) if reviewSession is None: try: result = db.run_in_transaction(_updateLastReviewAttemptDateTime) except TransactionFailedError: result = getErrorResponse(LASTREVIEWUPDATE_TRANSACTION_FAILURE, 0) if result is not None: return result # do not allow reviewer to jump on a just posted review. basically the reviewer should have lots of potential review to take from and other reviewer shoudl compete with potentialScoresToReview = db.GqlQuery("SELECT * FROM NonVerifiedScore WHERE dateTime < :oldEnoughTime ORDER BY dateTime ASC", oldEnoughTime=oldEnoughTime).fetch(5) scoreToReview = None for score in potentialScoresToReview: if score.parent_key() != playerKey: try: score.conflictingReviewers.index(playerId) except ValueError: # the current reviewer did not review this score yet scoreToReview = score break if scoreToReview is None: return {'result' : {'message' : 'Nothing to review for now', 'retry' : delay } } reviewSession = ReviewSession(key_name='reviewSession', currentScoreToReview=scoreToReview, parent=playerKey) reviewSession.put() else: try: scoreToReview = reviewSession.currentScoreToReview except ReferencePropertyResolveError: scoreToReview = None # in case score has been approved just now, it could have been removed if scoreToReview is not None: seedList = struct.unpack("4L", scoreToReview.seed) return {'result' : { 'proof' : scoreToReview.proof, 'seed' : seedList, 'version' : scoreToReview.version} } return {'result' : {'message' : 'Nothing to review for now (just done)', 'retry' : delay} }