def generate_hash(self, hash_gen): hash_gen.add_string(self.identifier) self.dtype.generate_hash(hash_gen) hash_gen.add_int(len(self.cases)) for case in self.cases: try: dg = Datagram() pack_functions[DCTypes[self.dtype]](dg, case.value) except KeyError: dg = Datagram() self.dtype.pack_value(dg, case.value) hash_gen.add_bytes(dg.bytes()) hash_gen.add_int(len(case.parameters) + 1) self.dtype.generate_hash(hash_gen) for parameter in case.parameters: parameter.generate_hash(hash_gen) if self.default_case is not None: hash_gen.add_int(len(self.default_case.parameters) + 1) self.dtype.generate_hash(hash_gen) for parameter in self.default_case.parameters: parameter.generate_hash(hash_gen)
def test_initialization(self): dg = Datagram(b'\x01\x02\x03') self.assertEqual(dg.bytes(), b'\x01\x02\x03') random.seed('test_initialization') data = os.urandom(32) dg = Datagram(data) self.assertEqual(dg.bytes(), data)
def createObjects(self): self.registerForChannel(self.ourChannel) from .Objects import ToontownDistrictAI, ToontownDistrictStatsAI, DistributedInGameNewsMgrAI, NewsManagerAI, FriendManagerAI from .TimeManagerAI import TimeManagerAI self.district = ToontownDistrictAI(self) self.district.name = 'Nutty River' self.generateWithRequired(self.district, OTP_DO_ID_TOONTOWN, OTP_ZONE_ID_DISTRICTS) post_remove = Datagram() post_remove.add_server_control_header(CONTROL_ADD_POST_REMOVE) post_remove.add_server_header([ STATESERVERS_CHANNEL, ], self.ourChannel, STATESERVER_SHARD_REST) post_remove.add_channel(self.ourChannel) self.send(post_remove) dg = Datagram() dg.add_server_header([STATESERVERS_CHANNEL], self.ourChannel, STATESERVER_ADD_AI_RECV) dg.add_uint32(self.district.do_id) dg.add_channel(self.ourChannel) self.send(dg) stats = ToontownDistrictStatsAI(self) stats.settoontownDistrictId(self.district.do_id) self.generateWithRequired(stats, OTP_DO_ID_TOONTOWN, OTP_ZONE_ID_DISTRICTS_STATS) dg = Datagram() dg.add_server_header([STATESERVERS_CHANNEL], self.ourChannel, STATESERVER_ADD_AI_RECV) dg.add_uint32(stats.do_id) dg.add_channel(self.ourChannel) self.send(dg) self.timeManager = TimeManagerAI(self) self.timeManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) self.ingameNewsMgr = DistributedInGameNewsMgrAI(self) self.ingameNewsMgr.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) self.newsManager = NewsManagerAI(self) self.newsManager.generateWithRequired(OTP_ZONE_ID_MANAGEMENT) self.friendManager = FriendManagerAI(self) self.friendManager.generateGlobalObject(OTP_ZONE_ID_MANAGEMENT) self.loadZones() self.district.b_setAvailable(True)
def test_add_datagram(self): dg1 = Datagram() dg1.add_uint16(32) dg2 = Datagram() dg2.add_string16(b'hello') dg1.add_datagram(dg2) self.assertEqual(dg1.bytes(), struct.pack('<HH5B', 32, 5, *b'hello')) del dg2 self.assertEqual(dg1.bytes(), struct.pack('<HH5B', 32, 5, *b'hello'))
def receive_delete_avatar(self, dgi): av_id = dgi.get_uint32() av = self.get_potential_avatar(av_id) if not av: return self.potential_avatars[av.index] = None avatars = [ pot_av.do_id if pot_av else 0 for pot_av in self.potential_avatars ] self.avs_deleted.append((av_id, int(time.time()))) field = self.service.dc_file.namespace['Account']['ACCOUNT_AV_SET'] del_field = self.service.dc_file.namespace['Account'][ 'ACCOUNT_AV_SET_DEL'] dg = Datagram() dg.add_server_header([DBSERVERS_CHANNEL], self.channel, DBSERVER_SET_STORED_VALUES) dg.add_uint32(self.account.disl_id) dg.add_uint16(2) dg.add_uint16(field.number) field.pack_value(dg, avatars) dg.add_uint16(del_field.number) del_field.pack_value(dg, self.avs_deleted) self.service.send_datagram(dg) resp = Datagram() resp.add_uint16(CLIENT_DELETE_AVATAR_RESP) resp.add_uint8(0) # Return code av_count = sum( (1 if pot_av else 0 for pot_av in self.potential_avatars)) dg.add_uint16(av_count) for pot_av in self.potential_avatars: if not pot_av: continue dg.add_uint32(pot_av.do_id) dg.add_string16(pot_av.name.encode('utf-8')) dg.add_string16(pot_av.wish_name.encode('utf-8')) dg.add_string16(pot_av.approved_name.encode('utf-8')) dg.add_string16(pot_av.rejected_name.encode('utf-8')) dg.add_string16(pot_av.dna_string.encode('utf-8')) dg.add_uint8(pot_av.index) self.send_datagram(resp)
def ai_format_generate(self, obj, do_id, parent_id, zone_id, district_channel_id, from_channel_id, optional_fields): dg = Datagram() dg.add_uint8(1) dg.add_channel(district_channel_id) dg.add_channel(from_channel_id) if optional_fields: dg.add_uint16(STATESERVER_OBJECT_GENERATE_WITH_REQUIRED_OTHER) else: dg.add_uint16(STATESERVER_OBJECT_GENERATE_WITH_REQUIRED) #if parent_id: dg.add_uint32(parent_id) dg.add_uint32(zone_id) dg.add_uint16(self.number) dg.add_uint32(do_id) for field in self.inherited_fields: if not isinstance(field, MolecularField) and field.is_required: self.pack_field(dg, obj, field) if optional_fields: dg.add_uint16(len(optional_fields)) for field_name in optional_fields: field = self.fields_by_name[field_name] dg.add_uint16(field.number) self.pack_field(dg, obj, field) return dg
def unsubscribe_channel(self, channel): dg = Datagram() dg.add_uint8(1) dg.add_channel(CONTROL_MESSAGE) dg.add_uint16(CONTROL_REMOVE_CHANNEL) dg.add_channel(channel) self.send_datagram(dg)
async def created_avatar(self): f = DatagramFuture(self.service.loop, DBSERVER_CREATE_STORED_OBJECT_RESP) self.futures.append(f) sender, dgi = await f context = dgi.get_uint32() return_code = dgi.get_uint8() av_id = dgi.get_uint32() av = self.potential_avatar av.do_id = av_id self.potential_avatars[av.index] = av self.potential_avatar = None resp = Datagram() resp.add_uint16(CLIENT_CREATE_AVATAR_RESP) resp.add_uint16(0) # Context resp.add_uint8(return_code) # Return Code resp.add_uint32(av_id) # av_id self.send_datagram(resp) self.created_av_id = av_id self.service.log.debug( f'New avatar {av_id} created for client {self.channel}.')
def send_object_location(self, do_id, new_parent, new_zone): resp = Datagram() resp.add_uint16(CLIENT_OBJECT_LOCATION) resp.add_uint32(do_id) resp.add_uint32(new_parent) resp.add_uint32(new_zone) self.send_datagram(resp)
def test_remaining(self): dg = Datagram() dg.add_uint16(25) dg.add_int64(-354843598374) dgi = dg.iterator() self.assertEqual(dgi.remaining(), 10) s = 'remaining unit test' dg.add_string32(s.encode('utf-8')) self.assertEqual(dgi.remaining(), 10 + 4 + len(s)) self.assertEqual(dgi.get_uint16(), 25) self.assertEqual(dgi.remaining(), 8 + 4 + len(s)) dgi.seek(dg.get_length()) self.assertEqual(dgi.remaining(), 0) dgi.seek(0) self.assertEqual(dgi.remaining(), 10 + 4 + len(s)) dgi.seek(14) self.assertEqual(dgi.remaining(), len(s)) dgi.seek(999) self.assertEqual(dgi.remaining(), 0)
async def get_stored_values(self, sender, context, do_id, fields): try: field_dict = await self.backend.query_object_fields(do_id, [field.name for field in fields]) except OTPQueryNotFound: field_dict = None self.log.debug(f'Received query request from {sender} with context {context} for do_id: {do_id}.') dg = Datagram() dg.add_server_header([sender], DBSERVERS_CHANNEL, DBSERVER_GET_STORED_VALUES_RESP) dg.add_uint32(context) dg.add_uint32(do_id) pos = dg.tell() dg.add_uint16(0) if field_dict is None: print('object not found... %s' % do_id, sender, context) self.send_datagram(dg) return counter = 0 for field in fields: if field.name not in field_dict: continue if field_dict[field.name] is None: continue dg.add_uint16(field.number) dg.add_bytes(field_dict[field.name]) counter += 1 dg.seek(pos) dg.add_uint16(counter) self.send_datagram(dg)
async def handle_datagrams(self): # TODO: run this tight loop in a seperate process, maybe proccess pool for CA and MD expected = 0 while True: if expected: if len(self.buf) < expected: await asyncio.sleep(0.01) continue else: try: dg = Datagram() dg.add_bytes(self.buf[:expected]) self.receive_datagram(dg) del self.buf[:expected] expected = 0 continue except Exception as e: import traceback traceback.print_exc() continue elif len(self.buf) > 2: expected = struct.unpack('H', self.buf[:2])[0] del self.buf[:2] continue else: await asyncio.sleep(0.01)
def receive_datagram(self, dg): dgi = dg.iterator() recipient_count = dgi.get_uint8() if recipient_count == 1 and dgi.get_channel() == CONTROL_MESSAGE: # Control message. msg_type = dgi.get_uint16() if msg_type == CONTROL_SET_CHANNEL: channel = dgi.get_channel() self.subscribe_channel(channel) elif msg_type == CONTROL_REMOVE_CHANNEL: channel = dgi.get_channel() self.unsubscribe_channel(channel) elif msg_type == CONTROL_ADD_RANGE: low = dgi.get_channel() high = dgi.get_channel() for channel in range(low, high, 1): self.channels.add(channel) elif msg_type == CONTROL_REMOVE_RANGE: low = dgi.get_channel() high = dgi.get_channel() for channel in range(low, high, 1): if channel in self.channels: self.channels.remove(channel) elif msg_type == CONTROL_ADD_POST_REMOVE: post_dg = Datagram() post_dg.add_bytes(dgi.get_bytes(dgi.remaining())) self.service.log.debug(f'Received post remove:{post_dg.bytes()}') self.post_removes.append(post_dg) elif msg_type == CONTROL_CLEAR_POST_REMOVE: del self.post_removes[:] else: self.service.q.put_nowait((None, dg))
def receive_update_field(self, dgi, do_id=None): if do_id is None: do_id = dgi.get_uint32() field_number = dgi.get_uint16() field = self.service.dc_file.fields[field_number]() sendable = False if field.is_ownsend and do_id in self.owned_objects: sendable = True elif field.is_clsend: sendable = True if not sendable: self.disconnect(ClientDisconnect.INTERNAL_ERROR, 'Tried to send nonsendable field to object.') self.service.log.warn( f'Client {self.channel} tried to update {do_id} with nonsendable field {field.name}. ' f'DCField keywords: {field.keywords}') return pos = dgi.tell() field.unpack_bytes(dgi) dgi.seek(pos) resp = Datagram() resp.add_server_header([do_id], self.channel, STATESERVER_OBJECT_UPDATE_FIELD) resp.add_uint32(do_id) resp.add_uint16(field_number) resp.add_bytes(dgi.remaining_bytes()) self.service.send_datagram(resp)
def removeInterest(self, client_channel, handle, context): dg = Datagram() dg.add_server_header([client_channel], self.ourChannel, CLIENT_AGENT_REMOVE_INTEREST) dg.add_uint16(handle) dg.add_uint32(context) self.send(dg)
def handle_one_update(self, dgi, sender): field_id = dgi.get_uint16() field = self.dclass.dcfile().fields[field_id]() pos = dgi.tell() data = field.unpack_bytes(dgi) if isinstance(field, MolecularField): dgi.seek(pos) self.save_molecular(field, dgi) else: self.save_field(field, data) targets = [] if field.is_broadcast: targets.append(location_as_channel(self.parent_id, self.zone_id)) if field.is_airecv and self.ai_channel and self.ai_channel != sender: targets.append(self.ai_channel) if field.is_ownrecv and self.owner_channel and self.owner_channel != sender: targets.append(self.owner_channel) if targets: dg = Datagram() dg.add_server_header(targets, sender, STATESERVER_OBJECT_UPDATE_FIELD) dg.add_uint32(self.do_id) dg.add_uint16(field_id) dg.add_bytes(data) self.service.send_datagram(dg)
async def query_account(self, sender, do_id): dclass = self.dc.namespace['Account'] toon_dclass = self.dc.namespace['DistributedToon'] field_dict = await self.backend.query_object_all(do_id, dclass.name) temp = Datagram() temp.add_bytes(field_dict['ACCOUNT_AV_SET']) av_ids = dclass['ACCOUNT_AV_SET'].unpack_value(temp.iterator()) dg = Datagram() dg.add_server_header([sender], DBSERVERS_CHANNEL, DBSERVER_ACCOUNT_QUERY_RESP) dg.add_bytes(field_dict['ACCOUNT_AV_SET_DEL']) av_count = sum((1 if av_id else 0 for av_id in av_ids)) self.log.debug(f'Account query for {do_id} from {sender}: {field_dict}') dg.add_uint16(av_count) # Av count for av_id in av_ids: if not av_id: continue toon_fields = await self.backend.query_object_fields(av_id, ['setName', 'WishNameState', 'WishName', 'setDNAString'], 'DistributedToon') wish_name = toon_fields['WishName'] temp = Datagram() temp.add_bytes(toon_fields['WishNameState']) name_state = toon_dclass['WishNameState'].unpack_value(temp.iterator()) dg.add_uint32(av_id) dg.add_bytes(toon_fields['setName']) pending_name = b'\x00\x00' approved_name = b'\x00\x00' rejected_name = b'\x00\x00' if name_state == 'APPROVED': approved_name = wish_name elif name_state == 'REJECTED': rejected_name = wish_name else: pending_name = wish_name dg.add_bytes(pending_name) dg.add_bytes(approved_name) dg.add_bytes(rejected_name) dg.add_bytes(toon_fields['setDNAString']) dg.add_uint8(av_ids.index(av_id)) self.send_datagram(dg)
def test_get_remaining(self): dg = Datagram() dg.add_string16(b'test string') dg.add_uint32(2525) dgi = dg.iterator() self.assertEqual(dgi.remaining(), 2 + len('test string') + 4) self.assertEqual(dgi.get_remaining(), dg.get_message().tobytes())
def send_location_entry(self, location): dg = Datagram() dg.add_server_header([location], self.do_id, STATESERVER_OBJECT_ENTERZONE_WITH_REQUIRED_OTHER) dg.add_uint8(bool(self.ram)) self.append_required_data(dg, True, False) if self.ram: self.append_other_data(dg, True, False) self.service.send_datagram(dg)
def sendFriendOnline(self, avId, otherAvId): # Need this delay so that `setFriendsList` is set first to avoid # the online whisper message. dg = Datagram() dg.add_server_header([getPuppetChannel(avId)], self.air.ourChannel, CLIENT_FRIEND_ONLINE) dg.add_uint32(otherAvId) self.air.send(dg)
def queue_pending(self, do_id, dgi, pos): if do_id in self.pending_objects: dgi.seek(pos) dg = Datagram() dg.add_bytes(dgi.remaining_bytes()) self.pending_objects[do_id].datagrams.append(dg) return True return False
def receive_remove_interest(self, dgi, ai=False): handle = dgi.get_uint16() if dgi.remaining(): context = dgi.get_uint32() else: context = None interest = None for _interest in self.interests: if _interest.handle == handle: interest = _interest break if not interest: self.service.log.debug( f'Got unexpected interest removal from client {self.channel} for interest handle ' f'{handle} with context {context}') return self.service.log.debug( f'Got remove interest request from client {self.channel} for interest handle ' f'{handle} with context {context}') parent_id = interest.parent_id uninterested_zones = [] for zone in interest.zones: if len(self.lookup_interest(parent_id, zone)) == 1: uninterested_zones.append(zone) to_remove = [] for do_id in self.visible_objects: do = self.visible_objects[do_id] if do.parent_id == parent_id and do.zone_id in uninterested_zones: self.service.log.debug( f'Object {do_id} killed by interest remove.') self.send_remove_object(do_id) to_remove.append(do_id) for do_id in to_remove: del self.visible_objects[do_id] for zone in uninterested_zones: self.unsubscribe_channel(location_as_channel(parent_id, zone)) self.interests.remove(interest) if not ai: resp = Datagram() resp.add_uint16(CLIENT_DONE_INTEREST_RESP) resp.add_uint16(handle) resp.add_uint32(context) self.send_datagram(resp)
def handle_location_change(self, new_parent, new_zone, sender): old_parent = self.parent_id old_zone = self.zone_id targets = list() if self.ai_channel is not None: targets.append(self.ai_channel) if self.owner_channel is not None: targets.append(self.owner_channel) if new_parent == self.do_id: raise Exception('Object cannot be parented to itself.\n') if new_parent != old_parent: if old_parent: self.unsubscribe_channel(parent_to_children(old_parent)) targets.append(old_parent) targets.append(location_as_channel(old_parent, old_zone)) self.parent_id = new_parent self.zone_id = new_zone if new_parent: self.subscribe_channel(parent_to_children(new_parent)) if not self.ai_explicitly_set: new_ai_channel = self.service.resolve_ai_channel(new_parent) if new_ai_channel != self.ai_channel: self.ai_channel = new_ai_channel self.send_ai_entry(new_ai_channel) targets.append(new_parent) elif new_zone != old_zone: self.zone_id = new_zone targets.append(self.parent_id) targets.append(location_as_channel(self.parent_id, old_zone)) else: # Not changing zones. return dg = Datagram() dg.add_server_header(targets, sender, STATESERVER_OBJECT_CHANGE_ZONE) dg.add_uint32(self.do_id) dg.add_uint32(new_parent) dg.add_uint32(new_zone) dg.add_uint32(old_parent) dg.add_uint32(old_zone) self.service.send_datagram(dg) self.parent_synced = False if new_parent: self.send_location_entry(location_as_channel(new_parent, new_zone))
def unregisterForChannel(self, channel): if channel not in self._registedChannels: return self._registedChannels.remove(channel) dg = Datagram() dg.add_server_control_header(CONTROL_REMOVE_CHANNEL) dg.add_channel(channel) self.send(dg)
def registerForChannel(self, channel): if channel in self._registedChannels: return self._registedChannels.add(channel) dg = Datagram() dg.add_server_control_header(CONTROL_SET_CHANNEL) dg.add_channel(channel) self.send(dg)
def send_ai_entry(self, location): dg = Datagram() dg.add_server_header([location], self.do_id, STATESERVER_OBJECT_ENTER_AI_RECV) self.append_required_data(dg, False, False) if self.ram: self.append_other_data(dg, False, False) self.service.send_datagram(dg)
def test_add_uint32(self): dg = Datagram() dg.add_uint32(1 << 31) other = struct.pack('<I', 1 << 31) self.assertEqual(dg.bytes(), other) with self.assertRaises(OverflowError): dg.add_uint32(1 << 32)
def receive_create_avatar(self, dgi): _ = dgi.get_uint16() dna = dgi.get_blob16() pos = dgi.get_uint8() self.service.log.debug( f'Client {self.channel} requesting avatar creation with dna {dna} and pos {pos}.' ) if not 0 <= pos < 6 or self.potential_avatars[pos] is not None: self.service.log.debug( f'Client {self.channel} tried creating avatar in invalid position.' ) return self.potential_avatar = PotentialAvatar(do_id=0, name='Toon', wish_name='', approved_name='', rejected_name='', dna_string=dna, index=pos) dclass = self.service.dc_file.namespace['DistributedToon'] dg = Datagram() dg.add_server_header([DBSERVERS_CHANNEL], self.channel, DBSERVER_CREATE_STORED_OBJECT) dg.add_uint32(0) dg.add_uint16(dclass.number) dg.add_uint32(self.account.disl_id) dg.add_uint8(pos) pos = dg.tell() dg.add_uint16(0) default_toon = dict(DEFAULT_TOON) default_toon['setDNAString'] = (dna, ) default_toon['setDISLid'] = (self.account.disl_id, ) default_toon['WishName'] = ('', ) default_toon['WishNameState'] = ('CLOSED', ) count = 0 for field in dclass.inherited_fields: if not isinstance(field, MolecularField) and field.is_db: if field.name == 'DcObjectType': continue dg.add_uint16(field.number) field.pack_value(dg, default_toon[field.name]) count += 1 dg.seek(pos) dg.add_uint16(count) self.state = ClientState.CREATING_AVATAR self.service.send_datagram(dg) self.tasks.append(self.service.loop.create_task(self.created_avatar()))
def setInterest(self, client_channel, handle, context, parent_id, zones): dg = Datagram() dg.add_server_header([client_channel], self.ourChannel, CLIENT_AGENT_SET_INTEREST) dg.add_uint16(handle) dg.add_uint32(context) dg.add_uint32(parent_id) for zone in zones: dg.add_uint32(zone) self.send(dg)
def sendLocation(self, do_id, old_parent: int, old_zone: int, new_parent: int, new_zone: int): dg = Datagram() dg.add_server_header([do_id], self.ourChannel, STATESERVER_OBJECT_SET_ZONE) dg.add_uint32(new_parent) dg.add_uint32(new_zone) dg.add_uint32(old_parent) dg.add_uint32(old_zone) self.send(dg)