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 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 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_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 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 send_object_entrance(self, parent_id, zone_id, dc_id, do_id, dgi, has_other): resp = Datagram() resp.add_uint16(CLIENT_CREATE_OBJECT_REQUIRED_OTHER if has_other else CLIENT_CREATE_OBJECT_REQUIRED) resp.add_uint32(parent_id) resp.add_uint32(zone_id) resp.add_uint16(dc_id) resp.add_uint32(do_id) resp.add_bytes(dgi.remaining_bytes()) self.send_datagram(resp)
def save_field(self, field: AtomicField, data): if field.is_required: self.required[field.name] = data elif field.is_ram: self.ram[field.name] = data if self.db and field.is_db: dg = Datagram() dg.add_server_header([DBSERVERS_CHANNEL], self.do_id, DBSERVER_SET_STORED_VALUES) dg.add_uint32(self.do_id) dg.add_uint16(1) dg.add_uint16(field.number) dg.add_bytes(data) self.service.send_datagram(dg) self.service.log.debug(f'Object {self.do_id} saved value {data} for field {field.name} to database.')
def handle_owned_object_entrance(self, dgi, sender): do_id = dgi.get_uint32() parent_id = dgi.get_uint32() zone_id = dgi.get_uint32() dc_id = dgi.get_uint16() self.owned_objects[do_id] = ObjectInfo(do_id, dc_id, parent_id, zone_id) resp = Datagram() resp.add_uint16(CLIENT_GET_AVATAR_DETAILS_RESP) resp.add_uint32(self.avatar_id) resp.add_uint8(0) # Return code resp.add_bytes(dgi.remaining_bytes()) self.send_datagram(resp)
def activate_callback(self, dgi): context = dgi.get_uint32() do_id = dgi.get_uint32() state_server = self.service parent_id, zone_id, owner_channel, number, other_data = state_server.queries[do_id] dclass = state_server.dc_file.classes[number] del state_server.queries[do_id] required = {} ram = {} count = dgi.get_uint16() for i in range(count): field_number = dgi.get_uint16() field = state_server.dc_file.fields[field_number]() if field.is_required: required[field.name] = field.unpack_bytes(dgi) else: ram[field.name] = field.unpack_bytes(dgi) for field_number, data in other_data: field = state_server.dc_file.fields[field_number]() if field.is_required: required[field.name] = data else: ram[field.name] = data if field.is_db: dg = Datagram() dg.add_server_header([DBSERVERS_CHANNEL], do_id, DBSERVER_SET_STORED_VALUES) dg.add_uint32(do_id) dg.add_uint16(1) dg.add_uint16(field.number) dg.add_bytes(data) self.service.send_datagram(dg) self.service.log.debug(f'Activating {do_id} with required:{required}\nram:{ram}\n') obj = DistributedObject(state_server, STATESERVERS_CHANNEL, do_id, parent_id, zone_id, dclass, required, ram, owner_channel=owner_channel, db=True) state_server.database_objects.add(do_id) state_server.objects[do_id] = obj obj.send_owner_entry(owner_channel)
def test_add_data(self): s = 'hello_world'.encode('utf-8') dg = Datagram() dg.add_string16(s) other = struct.pack(f'<H{len(s)}b', len(s), *s) self.assertEqual(dg.bytes(), other) s = 'abcdefghijklmnop'.encode('utf-8') dg = Datagram() dg.add_string32(s) other = struct.pack(f'<I{len(s)}b', len(s), *s) self.assertEqual(dg.bytes(), other) dg.add_bytes(b'') self.assertEqual(dg.bytes(), other) dg = Datagram() dg.add_string16(b'') self.assertEqual(dg.bytes(), b'\x00\x00') dg = Datagram() random.seed('pydc') s = bytes(random.randint(0, 255) for _ in range((1 << 16))) with self.assertRaises(OverflowError): dg.add_string16(s) dg = Datagram() s = bytes(random.randint(0, 255) for _ in range((1 << 16))) dg.add_string32(s) s = b''.join((struct.pack('<I', len(s)), s)) self.assertEqual(dg.bytes(), s) dg = Datagram() dg.add_string32(b'') self.assertEqual(dg.bytes(), struct.pack('<I', 0)) dg = Datagram() c = chr(0x1F600).encode('utf-8') dg.add_bytes(c) self.assertEqual(dg.bytes(), c)
async def do_login(self): f = DatagramFuture(self.service.loop, DBSERVER_ACCOUNT_QUERY_RESP) self.futures.append(f) sender, dgi = await f av_del_field = self.service.dc_file.namespace['Account'][ 'ACCOUNT_AV_SET_DEL'] self.service.log.debug('Begin unpack of deleted avatars.') try: self.avs_deleted = av_del_field.unpack_value(dgi) except Exception: import traceback traceback.print_exc() return self.service.log.debug( f'Avatars deleted list for {self.account.username}: {self.avs_deleted}' ) pos = dgi.tell() avatar_info = [None] * 6 for i in range(dgi.get_uint16()): pot_av = PotentialAvatar(do_id=dgi.get_uint32(), name=dgi.get_string16(), wish_name=dgi.get_string16(), approved_name=dgi.get_string16(), rejected_name=dgi.get_string16(), dna_string=dgi.get_blob16(), index=dgi.get_uint8()) avatar_info[pot_av.index] = pot_av self.potential_avatars = avatar_info self.state = ClientState.AVATAR_CHOOSER resp = Datagram() resp.add_uint16(CLIENT_GET_AVATARS_RESP) dgi.seek(pos) resp.add_uint8(0) # Return code resp.add_bytes(dgi.remaining_bytes()) self.send_datagram(resp)
def handle_update_field(self, dgi, sender, do_id): if sender == self.channel: return if not self.object_exists(do_id): self.service.log.debug( f'Got field update for unknown object {do_id}') pos = dgi.tell() field_number = dgi.get_uint16() field = self.service.dc_file.fields[field_number]() resp = Datagram() resp.add_uint16(CLIENT_OBJECT_UPDATE_FIELD) resp.add_uint32(do_id) resp.add_uint16(field_number) resp.add_bytes(dgi.remaining_bytes()) self.send_datagram(resp)
async def create_toon(self, sender, context, dclass, disl_id, pos, fields): try: do_id = await self.backend.create_object(dclass, fields) account = await self.backend.query_object_fields(disl_id, ['ACCOUNT_AV_SET'], 'Account') temp = Datagram() temp.add_bytes(account['ACCOUNT_AV_SET']) av_set = self.dc.namespace['Account']['ACCOUNT_AV_SET'].unpack_value(temp.iterator()) print(do_id, disl_id, pos, av_set) av_set[pos] = do_id temp.seek(0) self.dc.namespace['Account']['ACCOUNT_AV_SET'].pack_value(temp, av_set) await self.backend.set_field(disl_id, 'ACCOUNT_AV_SET', temp.bytes(), 'Account') except OTPCreateFailed as e: print('creation failed', e) do_id = 0 dg = Datagram() dg.add_server_header([sender], DBSERVERS_CHANNEL, DBSERVER_CREATE_STORED_OBJECT_RESP) dg.add_uint32(context) dg.add_uint8(do_id == 0) dg.add_uint32(do_id) self.send_datagram(dg)
def handle_object_entrance(self, dgi, sender): # Before msgtype and sender pos = dgi.tell() - 10 has_other = dgi.get_uint8() do_id = dgi.get_uint32() parent_id = dgi.get_uint32() zone_id = dgi.get_uint32() dc_id = dgi.get_uint16() pending_interests = list(self.get_pending_interests( parent_id, zone_id)) if len(pending_interests): self.service.log.debug( f'Queueing object generate for {do_id} in ({parent_id} {zone_id}) {do_id in self.visible_objects}' ) pending_object = PendingObject(do_id, dc_id, parent_id, zone_id, datagrams=[]) dg = Datagram() dgi.seek(pos) dg.add_bytes(dgi.remaining_bytes()) pending_object.datagrams.append(dg) self.pending_objects[do_id] = pending_object for interest in pending_interests: interest.pending_objects.append(do_id) return if self.object_exists(do_id): return self.visible_objects[do_id] = ObjectInfo(do_id, dc_id, parent_id, zone_id) self.send_object_entrance(parent_id, zone_id, dc_id, do_id, dgi, has_other)
def append_other_data(self, dg, client_only, also_owner): if client_only: fields_dg = Datagram() count = 0 for field_name, raw_data in self.ram.items(): field = self.dclass.fields_by_name[field_name] if field.is_broadcast or field.is_clrecv or (also_owner and field.is_ownrecv): fields_dg.add_uint16(field.number) fields_dg.add_bytes(raw_data) count += 1 dg.add_uint16(count) if count: dg.add_bytes(fields_dg.bytes()) else: dg.add_uint16(len(self.ram)) for field_name, raw_data in self.ram.items(): field = self.dclass.fields_by_name[field_name] dg.add_uint16(field.number) dg.add_bytes(raw_data)
def makeNetString(self): dg = Datagram() dg.add_bytes(self.type.encode('ascii')) if self.type == 't': dg.add_uint8(toonHeadTypes.index(self.head)) dg.add_uint8(toonTorsoTypes.index(self.torso)) dg.add_uint8(toonLegTypes.index(self.legs)) if self.gender == 'm': dg.add_uint8(1) else: dg.add_uint8(0) dg.add_uint8(self.topTex) dg.add_uint8(self.topTexColor) dg.add_uint8(self.sleeveTex) dg.add_uint8(self.sleeveTexColor) dg.add_uint8(self.botTex) dg.add_uint8(self.botTexColor) dg.add_uint8(self.armColor) dg.add_uint8(self.gloveColor) dg.add_uint8(self.legColor) dg.add_uint8(self.headColor) else: raise Exception(f'unknown avatar type: {self.type}') return dg.bytes()
def handle_datagram(self, dg, dgi): pos = dgi.tell() sender = dgi.get_channel() if sender == self.channel: return msgtype = dgi.get_uint16() self.check_futures(dgi, msgtype, sender) if msgtype == STATESERVER_OBJECT_ENTERZONE_WITH_REQUIRED_OTHER: self.handle_object_entrance(dgi, sender) elif msgtype == STATESERVER_OBJECT_ENTER_OWNER_RECV: self.handle_owned_object_entrance(dgi, sender) elif msgtype == STATESERVER_OBJECT_CHANGE_ZONE: do_id = dgi.get_uint32() if self.queue_pending(do_id, dgi, pos): self.service.log.debug( f'Queued location change for pending object {do_id}.') return self.handle_location_change(dgi, sender, do_id) elif msgtype == STATESERVER_QUERY_ZONE_OBJECT_ALL_DONE: self.handle_interest_done(dgi) elif msgtype == STATESERVER_OBJECT_UPDATE_FIELD: do_id = dgi.get_uint32() if not self.object_exists(do_id): queued = self.queue_pending(do_id, dgi, pos) if queued: self.service.log.debug( f'Queued field update for pending object {do_id}.') else: self.service.log.debug( f'Got update for unknown object {do_id}.') return self.handle_update_field(dgi, sender, do_id) elif msgtype == STATESERVER_OBJECT_DELETE_RAM: do_id = dgi.get_uint32() if do_id == self.avatar_id: if sender == self.account.disl_id << 32: self.disconnect(ClientDisconnect.RELOGGED, 'redundant login') else: self.disconnect(ClientDisconnect.SHARD_DISCONNECT, 'district reset') elif not self.object_exists(do_id): self.service.log.debug( f'Queued deletion for pending object {do_id}.') self.queue_pending(do_id, dgi, pos) return else: self.send_remove_object(do_id) del self.visible_objects[do_id] elif msgtype == CLIENT_AGENT_SET_INTEREST: self.receive_add_interest(dgi, ai=True) elif msgtype == CLIENT_AGENT_REMOVE_INTEREST: self.receive_remove_interest(dgi, ai=True) elif msgtype in { CLIENT_FRIEND_ONLINE, CLIENT_FRIEND_OFFLINE, CLIENT_GET_FRIEND_LIST_RESP }: dg = Datagram() dg.add_uint16(msgtype) dg.add_bytes(dgi.remaining_bytes()) self.send_datagram(dg) else: self.service.log.debug( f'Client {self.channel} received unhandled upstream msg {msgtype}.' )
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)