Feature #446 ยป 0001-Support-complex-field-types-in-network-packets.patch
client/clinet.c | ||
---|---|---|
if (NULL != packet) {
|
||
client_packet_input(packet, type);
|
||
free(packet);
|
||
packet_destroy(packet, type);
|
||
} else {
|
||
break;
|
||
}
|
||
... | ... | |
}
|
||
client_packet_input(packet, type);
|
||
free(packet);
|
||
packet_destroy(packet, type);
|
||
if (type == PACKET_PROCESSING_FINISHED) {
|
||
log_debug("ifstrgp: expect=%d, seen=%d",
|
common/generate_packets.py | ||
---|---|---|
foldable: bool = False
|
||
"""Whether a field of this type can be folded into the packet header"""
|
||
complex: bool = False
|
||
"""Whether a field of this type needs special handling when initializing,
|
||
copying or destroying the packet struct"""
|
||
@cache
|
||
def array(self, size: SizeInfo) -> "FieldType":
|
||
"""Construct a FieldType for an array with element type self and the
|
||
... | ... | |
See also self.get_code_handle_param()"""
|
||
return str(location)
|
||
@abstractmethod
|
||
def get_code_init(self, location: Location) -> str:
|
||
"""Generate a code snippet initializing a field of this type in the
|
||
packet struct, after the struct has already been zeroed.
|
||
Subclasses must override this if self.complex is True"""
|
||
if self.complex:
|
||
raise ValueError(f"default get_code_init implementation called for field {location.name} with complex type {self!r}")
|
||
return f"""\
|
||
/* no work needed for {location} */
|
||
"""
|
||
def get_code_copy(self, location: Location, dest: str, src: str) -> str:
|
||
"""Generate a code snippet deep-copying a field of this type from
|
||
one packet struct to another that has already been initialized.
|
||
Subclasses must override this if self.complex is True"""
|
||
if self.complex:
|
||
raise ValueError(f"default get_code_copy implementation called for field {location.name} with complex type {self!r}")
|
||
return f"""\
|
||
{dest}->{location} = {src}->{location};
|
||
"""
|
||
def get_code_fill(self, location: Location) -> str:
|
||
"""Generate a code snippet moving a value of this type from dsend
|
||
arguments into a packet struct."""
|
||
raise NotImplementedError
|
||
"""Generate a code snippet shallow-copying a value of this type from
|
||
dsend arguments into a packet struct."""
|
||
return f"""\
|
||
real_packet->{location} = {location};
|
||
"""
|
||
def get_code_free(self, location: Location) -> str:
|
||
"""Generate a code snippet deinitializing a field of this type in
|
||
the packet struct before it gets destroyed.
|
||
Subclasses must override this if self.complex is True"""
|
||
if self.complex:
|
||
raise ValueError(f"default get_code_free implementation called for field {location.name} with complex type {self!r}")
|
||
return f"""\
|
||
/* no work needed for {location} */
|
||
"""
|
||
@abstractmethod
|
||
def get_code_hash(self, location: Location) -> str:
|
||
... | ... | |
def get_code_handle_param(self, location: Location) -> str:
|
||
return f"{self.public_type} {location}"
|
||
def get_code_fill(self, location: Location) -> str:
|
||
return f"""\
|
||
real_packet->{location} = {location};
|
||
"""
|
||
def get_code_hash(self, location: Location) -> str:
|
||
raise ValueError(f"hash not supported for type {self} in field {location.name}")
|
||
... | ... | |
super().__init__(dataio_type, public_type)
|
||
def get_code_copy(self, location: Location, dest: str, src: str) -> str:
|
||
return f"""\
|
||
worklist_copy(&{dest}->{location}, &{src}->{location});
|
||
"""
|
||
def get_code_fill(self, location: Location) -> str:
|
||
return f"""\
|
||
worklist_copy(&real_packet->{location}, {location});
|
||
... | ... | |
def get_code_fill(self, location: Location) -> str:
|
||
return super().get_code_fill(location)
|
||
@abstractmethod
|
||
def get_code_copy(self, location: Location, dest: str, src: str) -> str:
|
||
return super().get_code_copy(location, dest, src)
|
||
def __str__(self) -> str:
|
||
return f"{super().__str__()}[{self.size}]"
|
||
... | ... | |
def get_code_fill(self, location: Location) -> str:
|
||
return f"""\
|
||
sz_strlcpy(real_packet->{location}, {location});
|
||
"""
|
||
def get_code_copy(self, location: Location, dest: str, src: str) -> str:
|
||
return f"""\
|
||
sz_strlcpy({dest}->{location}, {src}->{location});
|
||
"""
|
||
def get_code_cmp(self, location: Location) -> str:
|
||
... | ... | |
def get_code_fill(self, location: Location) -> str:
|
||
raise NotImplementedError("fill not supported for memory-type fields")
|
||
def get_code_copy(self, location: Location, dest: str, src: str) -> str:
|
||
return f"""\
|
||
memcpy({dest}->{location}, {src}->{location}, {self.size.actual_for(src)});
|
||
"""
|
||
def get_code_cmp(self, location: Location) -> str:
|
||
if self.size.constant:
|
||
return f"""\
|
||
... | ... | |
self.elem = elem
|
||
self.size = size
|
||
@property
|
||
def complex(self) -> bool:
|
||
return self.elem.complex
|
||
def get_code_declaration(self, location: Location) -> str:
|
||
return self.elem.get_code_declaration(
|
||
location.deeper(f"{location}[{self.size.declared}]")
|
||
... | ... | |
pre = "" if location.depth else "const "
|
||
return pre + self.elem.get_code_handle_param(location.deeper(f"*{location}"))
|
||
def get_code_init(self, location: Location) -> str:
|
||
if not self.complex:
|
||
return super().get_code_init(location)
|
||
inner_init = prefix(" ", self.elem.get_code_init(location.sub))
|
||
# Note: we're initializing and destroying *all* elements of the array,
|
||
# not just those up to the actual size; otherwise we'd have to
|
||
# dynamically initialize and destroy elements as the actual size changes
|
||
return f"""\
|
||
{{
|
||
int {location.index};
|
||
for ({location.index} = 0; {location.index} < {self.size.declared}; {location.index}++) {{
|
||
{inner_init}\
|
||
}}
|
||
}}
|
||
"""
|
||
def get_code_copy(self, location: Location, dest: str, src: str) -> str:
|
||
# can't use direct assignment to bit-copy a raw array,
|
||
# even if our type is not complex
|
||
inner_copy = prefix(" ", self.elem.get_code_copy(location.sub, dest, src))
|
||
# FIXME: can't use self.size.real; have to use actual_for(src context)
|
||
return f"""\
|
||
{{
|
||
int {location.index};
|
||
for ({location.index} = 0; {location.index} < {self.size.actual_for(src)}; {location.index}++) {{
|
||
{inner_copy}\
|
||
}}
|
||
}}
|
||
"""
|
||
def get_code_fill(self, location: Location) -> str:
|
||
inner_fill = prefix(" ", self.elem.get_code_fill(location.sub))
|
||
return f"""\
|
||
... | ... | |
{inner_fill}\
|
||
}}
|
||
}}
|
||
"""
|
||
def get_code_free(self, location: Location) -> str:
|
||
if not self.complex:
|
||
return super().get_code_free(location)
|
||
inner_free = prefix(" ", self.elem.get_code_free(location.sub))
|
||
# Note: we're initializing and destroying *all* elements of the array,
|
||
# not just those up to the actual size; otherwise we'd have to
|
||
# dynamically initialize and destroy elements as the actual size changes
|
||
return f"""\
|
||
{{
|
||
int {location.index};
|
||
for ({location.index} = 0; {location.index} < {self.size.declared}; {location.index}++) {{
|
||
{inner_free}\
|
||
}}
|
||
}}
|
||
"""
|
||
def get_code_hash(self, location: Location) -> str:
|
||
... | ... | |
"""Set of all capabilities affecting this field"""
|
||
return self.flags.add_caps | self.flags.remove_caps
|
||
@property
|
||
def complex(self) -> bool:
|
||
"""Whether this field's type requires special handling;
|
||
see FieldType.complex"""
|
||
return self.type_info.complex
|
||
def present_with_caps(self, caps: typing.Container[str]) -> bool:
|
||
"""Determine whether this field should be part of a variant with the
|
||
given capabilities"""
|
||
... | ... | |
packet_arrow + self.name,
|
||
))
|
||
def get_init(self) -> str:
|
||
"""Generate code initializing this field in the packet struct, after
|
||
the struct has already been zeroed."""
|
||
return self.type_info.get_code_init(Location(self.name))
|
||
def get_copy(self, dest: str, src: str) -> str:
|
||
"""Generate code deep-copying this field from *src to *dest."""
|
||
return self.type_info.get_code_copy(Location(self.name), dest, src)
|
||
def get_fill(self) -> str:
|
||
"""Generate code moving this field from the dsend arguments into
|
||
the packet struct."""
|
||
"""Generate code shallow-copying this field from the dsend arguments
|
||
into the packet struct."""
|
||
return self.type_info.get_code_fill(Location(self.name))
|
||
def get_free(self) -> str:
|
||
"""Generate code deinitializing this field in the packet struct
|
||
before destroying the packet."""
|
||
return self.type_info.get_code_free(Location(self.name))
|
||
def get_hash(self) -> str:
|
||
"""Generate code factoring this field into a hash computation."""
|
||
assert self.is_key
|
||
... | ... | |
See Packet.cancel"""
|
||
return self.packet.cancel
|
||
@property
|
||
def complex(self) -> bool:
|
||
"""Whether this packet's struct requires special handling for
|
||
initialization, copying, and destruction.
|
||
Note that this is still True even if the complex-typed fields
|
||
of the packet are excluded from this Variant."""
|
||
return self.packet.complex
|
||
@property
|
||
def differ_used(self) -> bool:
|
||
"""Whether the send function needs a `differ` boolean.
|
||
... | ... | |
phandlers->receive[{self.type}] = (void *(*)(struct connection *)) receive_{self.name};
|
||
"""
|
||
def get_copy(self, dest: str, src: str) -> str:
|
||
"""Generate code deep-copying the fields relevant to this variant
|
||
from *src to *dest"""
|
||
if not self.complex:
|
||
return f"""\
|
||
*{dest} = *{src};
|
||
"""
|
||
return "".join(
|
||
field.get_copy(dest, src)
|
||
for field in self.fields
|
||
)
|
||
def get_stats(self) -> str:
|
||
"""Generate the declaration of the delta stats counters associated
|
||
with this packet variant"""
|
||
... | ... | |
log=""
|
||
if self.no_packet:
|
||
# empty packet, don't need anything
|
||
main_header = ""
|
||
else:
|
||
if self.packet.want_pre_send:
|
||
main_header = f"""\
|
||
after_header = ""
|
||
before_return = ""
|
||
elif not self.packet.want_pre_send:
|
||
# no pre-send, don't need to copy the packet
|
||
main_header = f"""\
|
||
const struct {self.packet_name} *real_packet = packet;
|
||
int e;
|
||
"""
|
||
after_header = ""
|
||
before_return = ""
|
||
elif not self.complex:
|
||
# bit-copy the packet
|
||
main_header = f"""\
|
||
/* copy packet for pre-send */
|
||
struct {self.packet_name} packet_buf = *packet;
|
||
const struct {self.packet_name} *real_packet = &packet_buf;
|
||
int e;
|
||
"""
|
||
else:
|
||
main_header = f"""\
|
||
const struct {self.packet_name} *real_packet = packet;
|
||
"""
|
||
main_header += """\
|
||
after_header = ""
|
||
before_return = ""
|
||
else:
|
||
# deep-copy the packet for pre-send, have to destroy the copy
|
||
copy = prefix(" ", self.get_copy("(&packet_buf)", "packet"))
|
||
main_header = f"""\
|
||
/* buffer to hold packet copy for pre-send */
|
||
struct {self.packet_name} packet_buf;
|
||
const struct {self.packet_name} *real_packet = &packet_buf;
|
||
int e;
|
||
"""
|
||
after_header = f"""\
|
||
init_{self.packet_name}(&packet_buf);
|
||
{copy}\
|
||
"""
|
||
before_return = f"""\
|
||
free_{self.packet_name}(&packet_buf);
|
||
"""
|
||
if not self.packet.want_pre_send:
|
||
pre = ""
|
||
... | ... | |
delta_header += """\
|
||
#endif /* FREECIV_DELTA_PROTOCOL */
|
||
"""
|
||
body = prefix(" ", self.get_delta_send_body()) + """\
|
||
body = prefix(" ", self.get_delta_send_body(before_return)) + """\
|
||
#ifndef FREECIV_DELTA_PROTOCOL
|
||
"""
|
||
else:
|
||
... | ... | |
SEND_PACKET_START({self.type});
|
||
""",
|
||
faddr,
|
||
after_header,
|
||
log,
|
||
report,
|
||
pre,
|
||
body,
|
||
post,
|
||
before_return,
|
||
f"""\
|
||
SEND_PACKET_END({self.type});
|
||
}}
|
||
... | ... | |
#ifdef FREECIV_DELTA_PROTOCOL
|
||
if (nullptr == *hash) {{
|
||
*hash = genhash_new_full(hash_{self.name}, cmp_{self.name},
|
||
nullptr, nullptr, nullptr, free);
|
||
nullptr, nullptr, nullptr, destroy_{self.packet_name});
|
||
}}
|
||
BV_CLR_ALL(fields);
|
||
if (!genhash_lookup(*hash, real_packet, (void **) &old)) {{
|
||
old = fc_malloc(sizeof(*old));
|
||
/* temporary bitcopy just to insert correctly */
|
||
*old = *real_packet;
|
||
genhash_insert(*hash, old, old);
|
||
memset(old, 0, sizeof(*old));
|
||
init_{self.packet_name}(old);
|
||
"""
|
||
if self.is_info != "no":
|
||
intro += """\
|
||
... | ... | |
field.get_put_wrapper(self, i, True)
|
||
for i, field in enumerate(self.other_fields)
|
||
)
|
||
body += """\
|
||
*old = *real_packet;
|
||
"""
|
||
body += "\n"
|
||
body += self.get_copy("old", "real_packet")
|
||
# Cancel some is-info packets.
|
||
for i in self.cancel:
|
||
... | ... | |
f"""\
|
||
{self.receive_prototype}
|
||
{{
|
||
#define FREE_PACKET_STRUCT(_packet) free_{self.packet_name}(_packet)
|
||
""",
|
||
delta_header,
|
||
f"""\
|
||
... | ... | |
post,
|
||
"""\
|
||
RECEIVE_PACKET_END(real_packet);
|
||
#undef FREE_PACKET_STRUCT
|
||
}
|
||
""",
|
||
... | ... | |
"""Helper for get_receive(). Generate the part of the receive
|
||
function responsible for recreating the full packet from the
|
||
received delta and the last cached packet."""
|
||
if self.key_fields:
|
||
# bit-copy the values, since we're moving (not cloning)
|
||
# the key fields
|
||
# FIXME: might not work for arrays
|
||
backup_key = "".join(
|
||
prefix(" ", field.get_declar())
|
||
for field in self.key_fields
|
||
) + "\n"+ "".join(
|
||
f"""\
|
||
{field.name} = real_packet->{field.name};
|
||
"""
|
||
for field in self.key_fields
|
||
) + "\n"
|
||
restore_key = "\n" + "".join(
|
||
f"""\
|
||
real_packet->{field.name} = {field.name};
|
||
"""
|
||
for field in self.key_fields
|
||
)
|
||
else:
|
||
backup_key = restore_key = ""
|
||
if self.gen_log:
|
||
fl = f"""\
|
||
{self.log_macro}(" no old info");
|
||
"""
|
||
else:
|
||
fl=""
|
||
copy_from_old = prefix(" ", self.get_copy("real_packet", "old"))
|
||
body = f"""\
|
||
#ifdef FREECIV_DELTA_PROTOCOL
|
||
if (nullptr == *hash) {{
|
||
*hash = genhash_new_full(hash_{self.name}, cmp_{self.name},
|
||
nullptr, nullptr, nullptr, free);
|
||
nullptr, nullptr, nullptr, destroy_{self.packet_name});
|
||
}}
|
||
if (genhash_lookup(*hash, real_packet, (void **) &old)) {{
|
||
*real_packet = *old;
|
||
{copy_from_old}\
|
||
}} else {{
|
||
{backup_key}\
|
||
/* packet is already initialized empty */
|
||
{fl}\
|
||
memset(real_packet, 0, sizeof(*real_packet));
|
||
{restore_key}\
|
||
}}
|
||
"""
|
||
... | ... | |
for i, field in enumerate(self.other_fields)
|
||
)
|
||
extro = """\
|
||
copy_to_old = prefix(" ", self.get_copy("old", "real_packet"))
|
||
extro = f"""\
|
||
if (nullptr == old) {
|
||
if (nullptr == old) {{
|
||
old = fc_malloc(sizeof(*old));
|
||
*old = *real_packet;
|
||
init_{self.packet_name}(old);
|
||
{copy_to_old}\
|
||
genhash_insert(*hash, old, old);
|
||
} else {
|
||
*old = *real_packet;
|
||
}
|
||
}} else {{
|
||
{copy_to_old}\
|
||
}}
|
||
"""
|
||
# Cancel some is-info packets.
|
||
... | ... | |
"""Set of all capabilities affecting this packet"""
|
||
return {cap for field in self.fields for cap in field.all_caps}
|
||
@property
|
||
def complex(self) -> bool:
|
||
"""Whether this packet's struct requires special handling for
|
||
initialization, copying, and destruction."""
|
||
return any(field.complex for field in self.fields)
|
||
def get_struct(self) -> str:
|
||
"""Generate the struct definition for this packet"""
|
||
... | ... | |
PacketsDefinition.code_delta_stats_reset"""
|
||
return "\n".join(v.get_reset_part() for v in self.variants)
|
||
def get_init(self) -> str:
|
||
"""Generate this packet's init function, which initializes the
|
||
packet struct so its complex-typed fields are useable, and sets
|
||
all fields to the empty default state used for computing deltas"""
|
||
if self.complex:
|
||
field_parts = "\n" + "".join(
|
||
prefix(" ", field.get_init())
|
||
for field in self.fields
|
||
)
|
||
else:
|
||
field_parts = ""
|
||
return f"""\
|
||
static inline void init_{self.name}(struct {self.name} *packet)
|
||
{{
|
||
memset(packet, 0, sizeof(*packet));
|
||
{field_parts}\
|
||
}}
|
||
"""
|
||
def get_free_destroy(self) -> str:
|
||
"""Generate this packet's free and destroy functions, which free
|
||
memory associated with complex-typed fields of this packet, and
|
||
optionally the allocation of the packet itself (destroy)."""
|
||
if not self.complex:
|
||
return f"""\
|
||
#define free_{self.name}(_packet) (void) 0
|
||
#define destroy_{self.name} free
|
||
"""
|
||
# drop fields in reverse order, in case later fields depend on
|
||
# earlier fields (e.g. for actual array sizes)
|
||
field_parts = "".join(
|
||
prefix(" ", field.get_free())
|
||
for field in reversed(self.fields)
|
||
)
|
||
# NB: destroy_*() takes void* to avoid casts
|
||
return f"""\
|
||
static inline void free_{self.name}(struct {self.name} *packet)
|
||
{{
|
||
{field_parts}\
|
||
}}
|
||
static inline void destroy_{self.name}(void *packet)
|
||
{{
|
||
free_{self.name}((struct {self.name} *) packet);
|
||
free(packet);
|
||
}}
|
||
"""
|
||
def get_send(self) -> str:
|
||
"""Generate the implementation of the send function, which sends a
|
||
given packet to a given connection."""
|
||
... | ... | |
"""Generate the implementation of the dsend function, which directly
|
||
takes packet fields instead of a packet struct."""
|
||
if not self.want_dsend: return ""
|
||
# safety: fill just borrows the given values; no init/free necessary
|
||
fill = "".join(
|
||
prefix(" ", field.get_fill())
|
||
for field in self.fields
|
||
... | ... | |
See self.get_dsend() and self.get_lsend()"""
|
||
if not (self.want_lsend and self.want_dsend): return ""
|
||
# safety: fill just borrows the given values; no init/free necessary
|
||
fill = "".join(
|
||
prefix(" ", field.get_fill())
|
||
for field in self.fields
|
||
... | ... | |
"""
|
||
return intro + body + extro
|
||
@property
|
||
def code_packet_destroy(self) -> str:
|
||
"""Code fragment implementing the packet_destroy() function"""
|
||
# NB: missing packet IDs are empty-initialized, i.e. set to nullptr by default
|
||
handlers = "".join(
|
||
f"""\
|
||
[{packet.type}] = destroy_{packet.name},
|
||
"""
|
||
for packet in self
|
||
)
|
||
return f"""\
|
||
void packet_destroy(void *packet, enum packet_type type)
|
||
{{
|
||
static void (*const destroy_handlers[PACKET_LAST])(void *packet) = {{
|
||
{handlers}\
|
||
}};
|
||
void (*handler)(void *packet) = (type < PACKET_LAST ? destroy_handlers[type] : nullptr);
|
||
fc_assert_action_msg(handler != nullptr, handler = free,
|
||
"packet_destroy(): invalid packet type %d", type);
|
||
handler(packet);
|
||
}}
|
||
"""
|
||
@property
|
||
def code_enum_packet(self) -> str:
|
||
"""Code fragment declaring the packet_type enum"""
|
||
... | ... | |
# write hash, cmp, send, receive
|
||
for p in packets:
|
||
output_c.write(p.get_init())
|
||
output_c.write(p.get_free_destroy())
|
||
output_c.write(p.get_variants())
|
||
output_c.write(p.get_send())
|
||
output_c.write(p.get_lsend())
|
||
... | ... | |
output_c.write(packets.code_packet_handlers_fill_initial)
|
||
output_c.write(packets.code_packet_handlers_fill_capability)
|
||
output_c.write(packets.code_packet_destroy)
|
||
def write_server_header(path: "str | Path | None", packets: PacketsDefinition):
|
||
"""Write contents for server/hand_gen.h to the given path"""
|
common/networking/packets.h | ||
---|---|---|
const char *capability);
|
||
const char *packet_name(enum packet_type type);
|
||
bool packet_has_game_info_flag(enum packet_type type);
|
||
void packet_destroy(void *packet, enum packet_type type);
|
||
void packet_header_init(struct packet_header *packet_header);
|
||
void post_send_packet_server_join_reply(struct connection *pconn,
|
||
... | ... | |
struct data_in din; \
|
||
struct packet_type packet_buf, *result = &packet_buf; \
|
||
\
|
||
init_ ##packet_type (&packet_buf); \
|
||
dio_input_init(&din, pc->buffer->data, \
|
||
data_type_size(pc->packet_header.length)); \
|
||
{ \
|
||
... | ... | |
#define RECEIVE_PACKET_END(result) \
|
||
if (!packet_check(&din, pc)) { \
|
||
FREE_PACKET_STRUCT(&packet_buf); \
|
||
return NULL; \
|
||
} \
|
||
remove_packet_from_buffer(pc->buffer); \
|
||
... | ... | |
#define RECEIVE_PACKET_FIELD_ERROR(field, ...) \
|
||
log_packet("Error on field '" #field "'" __VA_ARGS__); \
|
||
FREE_PACKET_STRUCT(&packet_buf); \
|
||
return NULL
|
||
#endif /* FREECIV_JSON_PROTOCOL */
|
common/networking/packets_json.h | ||
---|---|---|
#define RECEIVE_PACKET_START(packet_type, result) \
|
||
struct packet_type packet_buf, *result = &packet_buf; \
|
||
struct data_in din; \
|
||
init_ ##packet_type (&packet_buf); \
|
||
if (!pc->json_mode) { \
|
||
dio_input_init(&din, pc->buffer->data, \
|
||
data_type_size(pc->packet_header.length)); \
|
||
... | ... | |
return result; \
|
||
} else { \
|
||
if (!packet_check(&din, pc)) { \
|
||
FREE_PACKET_STRUCT(&packet_buf); \
|
||
return NULL; \
|
||
} \
|
||
remove_packet_from_buffer(pc->buffer); \
|
||
... | ... | |
#define RECEIVE_PACKET_FIELD_ERROR(field, ...) \
|
||
log_packet("Error on field '" #field "'" __VA_ARGS__); \
|
||
FREE_PACKET_STRUCT(&packet_buf); \
|
||
return NULL;
|
||
/* Utilities to exchange strings and string vectors. */
|
server/sernet.c | ||
---|---|---|
start_processing_request(pconn, pconn->server.last_request_id_seen);
|
||
command_ok = server_packet_input(pconn, packet.data, packet.type);
|
||
free(packet.data);
|
||
packet_destroy(packet.data, packet.type);
|
||
finish_processing_request(pconn);
|
||
connection_do_unbuffer(pconn);
|