Exemple #1
0
    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))
Exemple #2
0
    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)
Exemple #3
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)
Exemple #4
0
    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)
Exemple #5
0
    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)
Exemple #6
0
 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
Exemple #7
0
 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)
Exemple #8
0
    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.')
Exemple #9
0
    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)
Exemple #10
0
    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)
Exemple #11
0
    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)
Exemple #12
0
    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)
Exemple #13
0
    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)
Exemple #14
0
    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)
Exemple #15
0
    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)
Exemple #16
0
    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)
Exemple #17
0
 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()
Exemple #18
0
    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}.'
            )
Exemple #19
0
    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)