Пример #1
0
 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)
Пример #2
0
    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
Пример #3
0
    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}.')
Пример #4
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)
Пример #5
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)
Пример #6
0
 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)
Пример #7
0
    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)
Пример #8
0
 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)
Пример #9
0
    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)
Пример #10
0
    def handle_query_all(self, dgi, sender):
        other = dgi.get_uint8()
        context = dgi.get_uint32()

        resp = Datagram()
        resp.add_server_header([sender], self.do_id, STATESERVER_QUERY_OBJECT_ALL_RESP)
        resp.add_uint32(self.do_id)
        resp.add_uint16(context)
        self.append_required_data(resp, False, True)
        self.service.send_datagram(resp)
Пример #11
0
 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)
Пример #12
0
    def test_get(self):
        dg = Datagram()
        dg.add_uint16(25)
        dg.add_int64(-354843598374)
        dg.add_string32('datagram iterator test'.encode('utf-8'))

        dgi = dg.iterator()
        self.assertEqual(dgi.get_uint16(), 25)
        self.assertEqual(dgi.get_int64(), -354843598374)
        self.assertEqual(dgi.get_string32(), 'datagram iterator test')
Пример #13
0
    def ai_format_update(self, do_id, to_id, from_id, args):
        dg = Datagram()

        dg.add_uint8(1)
        dg.add_channel(to_id)
        dg.add_channel(from_id)
        dg.add_uint16(STATESERVER_OBJECT_UPDATE_FIELD)
        dg.add_uint32(do_id)
        dg.add_uint16(self.number)
        self.pack_value(dg, args)
        return dg
Пример #14
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)
Пример #15
0
    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'))
Пример #16
0
    def receive_get_avatars(self, dgi):
        query = Datagram()
        query.add_server_header([
            DBSERVERS_CHANNEL,
        ], self.channel, DBSERVER_ACCOUNT_QUERY)

        disl_id = self.account.disl_id
        query.add_uint32(disl_id)
        field_number = self.service.avatars_field.number
        query.add_uint16(field_number)
        self.service.send_datagram(query)

        self.tasks.append(self.service.loop.create_task(self.do_login()))
Пример #17
0
 def disconnect(self, booted_index, booted_text):
     for task in self.tasks:
         task.cancel()
     del self.tasks[:]
     resp = Datagram()
     resp.add_uint16(CLIENT_GO_GET_LOST)
     resp.add_uint16(booted_index)
     resp.add_string16(booted_text.encode('utf-8'))
     self.transport.write(len(resp).to_bytes(2, byteorder='little'))
     self.transport.write(resp.bytes())
     self.transport.close()
     self.service.log.debug(
         f'Booted client {self.channel} with index {booted_index} and text: "{booted_text}"'
     )
Пример #18
0
    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)
Пример #19
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.')
Пример #20
0
    def test_copy_datagram(self):
        dg = Datagram()
        dg.add_string32('testing copy'.encode('utf-8'))

        dg2 = dg.copy()

        self.assertEqual(dg.bytes(), dg2.bytes())

        dg.add_uint16(65200)

        self.assertNotEqual(dg.bytes(), dg2.bytes())

        data = dg2.bytes()
        del dg
        self.assertEqual(dg2.bytes(), data)
Пример #21
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)
Пример #22
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)
Пример #23
0
    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()))
Пример #24
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)
Пример #25
0
    def database_generate_context(self, obj, context_id, parent_id, zone_id, owner_channel, database_server_id, from_channel_id):
        dg = Datagram()
        dg.add_uint8(1)
        dg.add_channel(database_server_id)
        dg.add_channel(from_channel_id)
        dg.add_uint16(STATESERVER_OBJECT_CREATE_WITH_REQUIRED_CONTEXT)
        dg.add_uint32(parent_id)
        dg.add_uint32(zone_id)
        dg.add_channel(owner_channel)
        dg.add_uint16(self.number)
        dg.add_uint32(context_id)

        for field in self.inherited_fields:
            if not isinstance(field, MolecularField) and field.is_required:
                self.pack_required_field(dg, obj, field)

        return dg
Пример #26
0
    def handle_interest_done(self, dgi):
        handle = dgi.get_uint16()
        context = dgi.get_uint32()
        self.service.log.debug(
            f'sending interest done for handle {handle} context {context}')

        interest = None

        for _interest in self.interests:
            if _interest.handle == handle and _interest.context == context:
                interest = _interest
                break

        if not interest:
            self.service.log.debug(
                f'Got interest done for unknown interest: {handle} {context}')
            return

        if interest.done:
            self.service.log.debug('Received duplicate interest done...')
            return

        interest.done = True

        pending = [
            self.pending_objects.pop(do_id)
            for do_id in interest.pending_objects
            if do_id in self.pending_objects
        ]
        # Need this sorting.
        pending.sort(key=lambda p: p.dc_id)
        del interest.pending_objects[:]

        self.service.log.debug(
            f'Replaying datagrams for {[p.do_id for p in pending]}')

        for pending_object in pending:
            for datagram in pending_object.datagrams:
                self.handle_datagram(datagram, datagram.iterator())

        if not interest.ai:
            resp = Datagram()
            resp.add_uint16(CLIENT_DONE_INTEREST_RESP)
            resp.add_uint16(handle)
            resp.add_uint32(context)
            self.send_datagram(resp)
Пример #27
0
    def test_overwrite(self):
        dg = Datagram()
        dg.add_uint32(2828)

        pos = dg.tell()
        self.assertEqual(pos, 4)

        dg.add_uint16(24)
        dg.add_int64(-352793)
        dg.seek(pos)
        dg.add_uint16(5000)
        dg.seek(len(dg))
        dg.add_string32(b'overwrite')

        dgi = dg.iterator()
        self.assertEqual(dgi.get_uint32(), 2828)
        self.assertEqual(dgi.get_uint16(), 5000)
        self.assertEqual(dgi.get_int64(), -352793)
        self.assertEqual(dgi.get_string32(), 'overwrite')
Пример #28
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)
Пример #29
0
    def receive_get_friend_list(self, dgi):
        self.service.log.debug(
            f'Friend list query received from {self.channel}')
        error = 0

        count = 0

        # Friend Structure
        # uint32 do_id
        # string name
        # string dna_string
        # uint32 pet_id

        resp = Datagram()
        resp.add_uint16(CLIENT_GET_FRIEND_LIST_RESP)
        resp.add_uint8(error)
        resp.add_uint16(count)

        self.send_datagram(resp)
Пример #30
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)