mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-23 09:00:24 +02:00
Fix third netcode review
This commit is contained in:
1 parent
0d548798e5
commit
4f3ce00a8e
19 files changed
+698
-241
No files matched your search
+17
-13
@@ -34,9 +34,11 @@ with ideas from VALORANT's netcode articles, keeping the 30 Hz tick:
|
||||
that with where it had the player at that tick, not now, and moves the
|
||||
player by the difference, keeping what they did since. The host takes a
|
||||
client's player where the client says within a tolerance of its own,
|
||||
and no further from where it last took them than a player moves in the
|
||||
client's ticks since (so the tolerance is not gained again each tick);
|
||||
a teleporter, which moves the host's own copy too, starts afresh.
|
||||
and no further from an anchor (one it took, a newer once a second) than
|
||||
a player moves in the client's ticks since, those no more than its own
|
||||
since and a little jitter (so the tolerance and the jitter are not gained
|
||||
again each tick); a teleporter, which moves the host's own copy too,
|
||||
starts afresh.
|
||||
- **Shooter's hits.** A client reports what its own players hit; the host
|
||||
checks the report (the player's, a weapon they carry, the target where
|
||||
the host had it when the shooter saw it, no faster than weapons fire) and
|
||||
@@ -124,10 +126,11 @@ a pregame keep-alive every five seconds from the host
|
||||
only that client is told;
|
||||
- twice a second and with every kill, the players' statistics that
|
||||
changed (once a second a few more, round them all, as the message is
|
||||
unreliable); when it changes (looked at five times a second) and once a
|
||||
second, the game type's state (scores, the flags, the balls and their
|
||||
carriers, the king's hill, the players' speeds) and whether the game is
|
||||
over. A machine that has loaded is sent all of both at once. It counts
|
||||
unreliable); when it changes (looked at five times a second, sent at
|
||||
most every other look unless the game ended; reliably, so never
|
||||
again unchanged), the game type's state (scores, the flags, the balls
|
||||
and their carriers, the king's hill, the players' speeds) and whether
|
||||
the game is over. A machine that has loaded is sent all of both at once. It counts
|
||||
the game type's events (captures, grabs and returns of the flags,
|
||||
laps, the balls reset), and a client announces those it has not had,
|
||||
as the host does its own; a client runs the rest of the game type's
|
||||
@@ -173,11 +176,12 @@ a pregame keep-alive every five seconds from the host
|
||||
drawn fading over a few ticks (`render_interpolation.c`) instead of a
|
||||
jump. A client drives its own player's vehicle and sends where it is,
|
||||
which the host takes within a tolerance, as it does its own player's
|
||||
unit, and no further from where it last took it than the vehicle moves
|
||||
in the client's ticks since (its speed, the host's copy's or twice the
|
||||
tag's top speed, whichever is more, no more than 3 world units a tick,
|
||||
and a tenth more a tick, with the blend distance; a teleporter falls
|
||||
back to the tolerance). The host sends the client its own vehicle
|
||||
unit, and no further from an anchor (one it took, a newer once a
|
||||
second) than the vehicle moves in the client's ticks since, those no
|
||||
more than its own since and a little jitter (its speed, the host's
|
||||
copy's or twice the tag's top speed, whichever is more, no more than 3
|
||||
world units a tick, and a tenth more a tick, with the blend distance; a
|
||||
teleporter falls back to the tolerance). The host sends the client its own vehicle
|
||||
every third tick, with the client's tick it took the vehicle at
|
||||
(`_distributed_object_predicted_bit` and a 16-bit time in the object's
|
||||
state); the client compares that with where it had the vehicle at that
|
||||
@@ -271,7 +275,7 @@ Unity's Netcode for Entities, lightyear, netfox and the Ares source):
|
||||
round all of them, four a tick; the players' statistics when
|
||||
they change (with every kill, twice a second), with sixteen more
|
||||
players' once a second round them all; the game type's state when it
|
||||
changes, and once a second.
|
||||
changes, at most two and a half times a second.
|
||||
- **The players each client needs, when it needs them.** The host sends a
|
||||
client every player's unit and input every tick when they are within 25
|
||||
world units of the client's own players, every second tick within 60,
|
||||
|
||||
@@ -355,6 +355,10 @@ Only machines with the invite can find the game:
|
||||
(`src/p2p_signal.c`, `src/p2p_crypto.c`). The host authenticates its answer
|
||||
with a key that only it and the player can calculate. Its public key must
|
||||
agree with the identifier in the link.
|
||||
- Then the player shows in the same way that it has the private key of its
|
||||
public key. Only then does the host make a session for the player. Thus
|
||||
other machines with the invite cannot make sessions in the name of a
|
||||
player (such a session would keep the player out).
|
||||
- Each two machines get the keys of their packets from their key pairs and
|
||||
a random number from each. The keys do not go through the brokers. Thus
|
||||
other machines with the invite cannot read or change the packets.
|
||||
|
||||
@@ -452,7 +452,8 @@ void network_damage_aftermath(
|
||||
short body_part,
|
||||
short node_index,
|
||||
short region_index,
|
||||
short material_index)
|
||||
short material_index,
|
||||
long victim_player_index)
|
||||
{
|
||||
struct distributed_damage_event *event;
|
||||
struct unit_datum *unit;
|
||||
@@ -464,8 +465,9 @@ void network_damage_aftermath(
|
||||
unit = (struct unit_datum *)object_try_and_get_and_verify_type(object_index, _object_mask_unit);
|
||||
if (!unit)
|
||||
return;
|
||||
/* (the last events kept for killing blows) */
|
||||
kill = TEST_FLAG(being_damaged_flags, _object_being_damaged_body_depleted_bit) && unit->unit.player_index != NONE;
|
||||
/* (the last events kept for killing blows: of a player's unit, whose
|
||||
player the blow's aftermath has already taken from it, unit_died) */
|
||||
kill = TEST_FLAG(being_damaged_flags, _object_being_damaged_body_depleted_bit) && victim_player_index != NONE;
|
||||
if (damage_event_count >= MAXIMUM_DAMAGE_EVENTS_PER_TICK - (kill ? 0 : RESERVED_KILL_EVENTS))
|
||||
return;
|
||||
event = &damage_events[damage_event_count++];
|
||||
@@ -490,7 +492,7 @@ void network_damage_aftermath(
|
||||
|
||||
event->kind = _damage_event_kill;
|
||||
/* (no killer when the game noted none: a death it did not score) */
|
||||
if (!distributed_get_death((short)DATUM_INDEX_TO_ABSOLUTE_INDEX(unit->unit.player_index),
|
||||
if (!distributed_get_death((short)DATUM_INDEX_TO_ABSOLUTE_INDEX(victim_player_index),
|
||||
&event->player_index, &friendly_fire, &killed_by_vehicle))
|
||||
{
|
||||
event->player_index = NO_PLAYER;
|
||||
@@ -1234,8 +1236,30 @@ static void distributed_note_event_destinations(
|
||||
}
|
||||
/* (a player's body, living or dead) */
|
||||
unit = (struct unit_datum *)object_try_and_get_and_verify_type(event->object_index, _object_mask_unit);
|
||||
if (unit && unit->unit.player_index != NONE)
|
||||
player_indices[(*count)++] = (short)DATUM_INDEX_TO_ABSOLUTE_INDEX(unit->unit.player_index);
|
||||
if (unit)
|
||||
{
|
||||
long unit_player_index = unit->unit.player_index;
|
||||
|
||||
/* (a body's player is no longer its unit's, but has it as their
|
||||
dead one, player_died) */
|
||||
if (unit_player_index == NONE)
|
||||
{
|
||||
struct data_iterator iterator;
|
||||
struct player_datum *player;
|
||||
|
||||
data_iterator_new(&iterator, player_data);
|
||||
while ((player = (struct player_datum *)data_iterator_next(&iterator)) != NULL)
|
||||
{
|
||||
if (player->dead_unit_index == event->object_index)
|
||||
{
|
||||
unit_player_index = iterator.datum_index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unit_player_index != NONE)
|
||||
player_indices[(*count)++] = (short)DATUM_INDEX_TO_ABSOLUTE_INDEX(unit_player_index);
|
||||
}
|
||||
if (tick->time != game_time_get())
|
||||
continue;
|
||||
for (player_index = 0; player_index < MAXIMUM_TRACKED_PLAYERS; player_index++)
|
||||
|
||||
@@ -90,7 +90,10 @@ enum
|
||||
STATISTICS_REFRESH_PLAYERS = 16,
|
||||
/* the game type's state looked at, and sent unchanged */
|
||||
GAME_STATE_INTERVAL_TICKS = 6,
|
||||
GAME_STATE_REFRESH_TICKS = TICKS_PER_SECOND,
|
||||
/* ... when it changes (reliably: no refresh), no more often than this
|
||||
(the king's and the ball's scores change every tick), but the game's end
|
||||
at once */
|
||||
GAME_STATE_MINIMUM_TICKS = 2 * GAME_STATE_INTERVAL_TICKS,
|
||||
MAXIMUM_GAME_STATE_SIZE = 0xF00,
|
||||
MAXIMUM_STATISTICS_PER_MESSAGE = 64,
|
||||
MAXIMUM_PICKUPS_PER_TICK = 64,
|
||||
@@ -113,9 +116,13 @@ enum
|
||||
/* the host: the ticks after it took a client's player's position that it
|
||||
still tells the client which of its ticks it has them at */
|
||||
PREDICTION_ECHO_TICKS = 3,
|
||||
/* ... and the client's ticks it takes as run between two of its own
|
||||
beyond the host's (its messages delayed, then bunched) */
|
||||
/* ... and the client's ticks it takes as run since the anchor beyond
|
||||
the host's (its messages delayed, then bunched) */
|
||||
PREDICTION_JITTER_TICKS = 6,
|
||||
/* ... how long it measures predictions from one it took, the anchor,
|
||||
before it takes a newer as the anchor (the jitter and the blend granted
|
||||
once a second, not once a tick) */
|
||||
PREDICTION_ANCHOR_TICKS = TICKS_PER_SECOND,
|
||||
};
|
||||
|
||||
/* struct distributed_unit_state flags */
|
||||
@@ -383,9 +390,10 @@ static struct
|
||||
long taken_time;
|
||||
long taken_host_time;
|
||||
} distributed_predictions[MAXIMUM_TRACKED_PLAYERS];
|
||||
/* the host: where it last took each client's player to be (the next may
|
||||
be no further from it than the player moves in the client's ticks since),
|
||||
at which of the client's ticks and its own, and where that left its unit */
|
||||
/* the host: the anchor, where it took each client's player to be (one
|
||||
taken, a newer once a second: the next may be no further from it than the
|
||||
player moves in the client's ticks since), at which of the client's ticks
|
||||
and its own; and when it last took one, and where that left its unit */
|
||||
static struct
|
||||
{
|
||||
boolean valid;
|
||||
@@ -393,7 +401,8 @@ static struct
|
||||
long time;
|
||||
long host_time;
|
||||
real_point3d position;
|
||||
real_point3d host_position;
|
||||
long taken_host_time;
|
||||
real_point3d taken_host_position;
|
||||
} distributed_accepted[MAXIMUM_TRACKED_PLAYERS];
|
||||
/* a client: where each of its own players' units was at its last ticks,
|
||||
and how long the host takes to have them (ticks) */
|
||||
@@ -450,8 +459,10 @@ static long distributed_player_machines[MAXIMUM_TRACKED_PLAYERS];
|
||||
and once a second a few players' whatever they are, round them all) */
|
||||
static unsigned long distributed_sent_statistics[MAXIMUM_TRACKED_PLAYERS];
|
||||
static short distributed_statistics_cursor;
|
||||
/* the host: the game type's state as last sent, and when */
|
||||
/* the host: the game type's state as last sent (its checksum and its
|
||||
leading postgame state), and when */
|
||||
static unsigned long distributed_game_state_checksum;
|
||||
static long distributed_game_state_postgame;
|
||||
static long distributed_game_state_time;
|
||||
|
||||
/* the latest tick of each kind of unreliable message had from each sender
|
||||
@@ -1529,11 +1540,12 @@ static void distributed_handle_predictions(
|
||||
little off closed by half, more put there. Each accepted moves the host's
|
||||
unit, and the next is measured from there, so a client could take its
|
||||
player HOST_ACCEPT_TOLERANCE further each tick: a prediction is also no
|
||||
further from the last one taken than the player moves in the client's
|
||||
ticks since (MAXIMUM_PREDICTED_SPEED), those no more than the host's since
|
||||
and a little jitter. A teleporter moves the host's own unit too, further
|
||||
than that since the last one taken: it is measured from where the host has
|
||||
it only (as it is after a new life, or a ride). */
|
||||
further from the anchor (one taken, a newer once a second) than the player
|
||||
moves in the client's ticks since (MAXIMUM_PREDICTED_SPEED), those no more
|
||||
than the host's since and a little jitter. A teleporter moves the host's
|
||||
own unit too, further than it moves since the last taken: it is measured
|
||||
from where the host has it only, a new anchor (as it is after a new life,
|
||||
or a ride). */
|
||||
static void distributed_apply_predictions(
|
||||
void)
|
||||
{
|
||||
@@ -1568,16 +1580,17 @@ static void distributed_apply_predictions(
|
||||
continue;
|
||||
if (distributed_accepted[player_index].valid)
|
||||
{
|
||||
long host_ticks = now - distributed_accepted[player_index].host_time;
|
||||
long ticks = MIN(distributed_predictions[player_index].time - distributed_accepted[player_index].time,
|
||||
host_ticks + PREDICTION_JITTER_TICKS);
|
||||
real reach = MAXIMUM_PREDICTED_SPEED * (real)host_ticks + HOST_BLEND_DISTANCE;
|
||||
now - distributed_accepted[player_index].host_time + PREDICTION_JITTER_TICKS);
|
||||
real reach = MAXIMUM_PREDICTED_SPEED * (real)(now - distributed_accepted[player_index].taken_host_time) +
|
||||
HOST_BLEND_DISTANCE;
|
||||
|
||||
dx = object->object.position.x - distributed_accepted[player_index].host_position.x;
|
||||
dy = object->object.position.y - distributed_accepted[player_index].host_position.y;
|
||||
dz = object->object.position.z - distributed_accepted[player_index].host_position.z;
|
||||
/* (the host's own unit moved further than a unit moves: taken
|
||||
from where it is) */
|
||||
dx = object->object.position.x - distributed_accepted[player_index].taken_host_position.x;
|
||||
dy = object->object.position.y - distributed_accepted[player_index].taken_host_position.y;
|
||||
dz = object->object.position.z - distributed_accepted[player_index].taken_host_position.z;
|
||||
/* (the host's own unit moved further than a unit moves since
|
||||
the last taken, which only the host moves it by: taken from
|
||||
where it is, a new anchor) */
|
||||
if (!(dx * dx + dy * dy + dz * dz <= reach * reach))
|
||||
distributed_accepted[player_index].valid = FALSE;
|
||||
else
|
||||
@@ -1596,12 +1609,18 @@ static void distributed_apply_predictions(
|
||||
{
|
||||
distributed_predictions[player_index].taken_time = distributed_predictions[player_index].time;
|
||||
distributed_predictions[player_index].taken_host_time = now;
|
||||
distributed_accepted[player_index].valid = TRUE;
|
||||
distributed_accepted[player_index].unit_index = unit_index;
|
||||
distributed_accepted[player_index].time = distributed_predictions[player_index].time;
|
||||
distributed_accepted[player_index].host_time = now;
|
||||
distributed_accepted[player_index].position = state->position;
|
||||
distributed_accepted[player_index].host_position = object->object.position;
|
||||
/* (a new anchor: the first, one a second on, or after a jump) */
|
||||
if (!distributed_accepted[player_index].valid ||
|
||||
now - distributed_accepted[player_index].host_time >= PREDICTION_ANCHOR_TICKS)
|
||||
{
|
||||
distributed_accepted[player_index].valid = TRUE;
|
||||
distributed_accepted[player_index].unit_index = unit_index;
|
||||
distributed_accepted[player_index].time = distributed_predictions[player_index].time;
|
||||
distributed_accepted[player_index].host_time = now;
|
||||
distributed_accepted[player_index].position = state->position;
|
||||
}
|
||||
distributed_accepted[player_index].taken_host_time = now;
|
||||
distributed_accepted[player_index].taken_host_position = object->object.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2527,8 +2546,8 @@ static void distributed_send_all_statistics(
|
||||
/* ---------- the game type's state */
|
||||
|
||||
/* the game type's state (larger than a datagram: reliably) to a machine
|
||||
that has loaded, or (NONE) to every client when it changed or it has not
|
||||
been sent for a second */
|
||||
that has loaded, or (NONE) to every client when it changed (at most every
|
||||
other look, unless the game ended) */
|
||||
static void distributed_send_game_state(
|
||||
long machine_index)
|
||||
{
|
||||
@@ -2549,11 +2568,16 @@ static void distributed_send_game_state(
|
||||
return;
|
||||
}
|
||||
checksum = distributed_checksum(message.data, size);
|
||||
if (checksum == distributed_game_state_checksum && distributed_game_state_time != NONE &&
|
||||
game_time_get() - distributed_game_state_time < GAME_STATE_REFRESH_TICKS)
|
||||
if (distributed_game_state_time != NONE &&
|
||||
(checksum == distributed_game_state_checksum ||
|
||||
(game_time_get() - distributed_game_state_time < GAME_STATE_MINIMUM_TICKS &&
|
||||
(size < (long)sizeof(long) ||
|
||||
csmemcmp(message.data, &distributed_game_state_postgame, sizeof(long)) == 0))))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (size >= (long)sizeof(long))
|
||||
csmemcpy(&distributed_game_state_postgame, message.data, sizeof(long));
|
||||
distributed_game_state_checksum = checksum;
|
||||
distributed_game_state_time = game_time_get();
|
||||
distributed_send(&message, _distributed_message_game_state, 0, (word)(sizeof(message.header) + size),
|
||||
@@ -2584,6 +2608,7 @@ void network_distributed_new_game(
|
||||
csmemset(distributed_sent_statistics, 0, sizeof(distributed_sent_statistics));
|
||||
distributed_statistics_cursor = 0;
|
||||
distributed_game_state_checksum = 0;
|
||||
distributed_game_state_postgame = 0;
|
||||
distributed_game_state_time = NONE;
|
||||
distributed_host_update_number = NONE;
|
||||
for (player_index = 0; player_index < MAXIMUM_TRACKED_PLAYERS; player_index++)
|
||||
|
||||
@@ -100,9 +100,9 @@ enum
|
||||
up to the most */
|
||||
CLIENT_READY_INTERVAL_TICKS = TICKS_PER_SECOND,
|
||||
CLIENT_READY_MAXIMUM_INTERVAL_TICKS = 4 * TICKS_PER_SECOND,
|
||||
/* (a machine that does not say whether it asks again: network_objects_
|
||||
client_ready) the host tells it all its objects at most once in this
|
||||
long (asked again sooner, they are on their way) */
|
||||
/* the host tells a machine all its objects again at most once in this
|
||||
long, asked again (a client that failed to make one: asked again sooner,
|
||||
they are on their way; a flood of asking is not answered in full) */
|
||||
HOST_OBJECTS_RESEND_TICKS = 10 * TICKS_PER_SECOND,
|
||||
/* a client that failed to make one of the host's objects asks for them
|
||||
all again this long after, twice as long each time it fails again up to
|
||||
@@ -126,15 +126,17 @@ enum
|
||||
/* a client: where the vehicles its own players drive were, the last
|
||||
ticks (a power of two, more than the longest round trip) */
|
||||
OWN_VEHICLE_POSITION_TICKS = 64,
|
||||
/* the host: the ticks after it took a client's vehicle that it still
|
||||
tells the client which of its ticks it has it at */
|
||||
VEHICLE_PREDICTION_ECHO_TICKS = 3,
|
||||
/* ... and the client's ticks it takes as run between two it took beyond
|
||||
the host's (its messages delayed, then bunched) */
|
||||
/* the host: the client's ticks it takes as run since a client's vehicle's
|
||||
anchor beyond its own (its messages delayed, then bunched) */
|
||||
VEHICLE_PREDICTION_JITTER_TICKS = 6,
|
||||
/* ... and how long it measures the next from the last it took (longer,
|
||||
from where it has the vehicle only: a ride begun again) */
|
||||
VEHICLE_PREDICTION_REFERENCE_TICKS = TICKS_PER_SECOND,
|
||||
/* ... how long it measures predictions from one it took, the anchor,
|
||||
before it takes a newer as the anchor (the jitter and the blend granted
|
||||
once a second, not once a tick) */
|
||||
VEHICLE_PREDICTION_ANCHOR_TICKS = TICKS_PER_SECOND,
|
||||
/* ... and how long, with none taken, it keeps the anchor, and tells the
|
||||
client which of its ticks it has the vehicle at (longer: from where it
|
||||
has the vehicle only, a ride begun again) */
|
||||
VEHICLE_PREDICTION_REFERENCE_TICKS = 2 * TICKS_PER_SECOND,
|
||||
/* a client: its own player's ammunition and grenades are the host's once
|
||||
the host has had this long past a round trip to see what it did with
|
||||
them (the host's inventories go every INVENTORY_INTERVAL_TICKS) */
|
||||
@@ -312,20 +314,22 @@ static struct
|
||||
long player_indices[MAXIMUM_LOCAL_PLAYERS];
|
||||
} objects_host_machines[HALO_PORT_MAXIMUM_NETWORK_MACHINES];
|
||||
/* ... each client's player's latest vehicle prediction, taken at the next
|
||||
tick; whether it took the latest it had; the vehicle it last took (NONE:
|
||||
none), at which of the client's ticks and its own, where the client had it
|
||||
and where that left the host's */
|
||||
tick; the vehicle it last took (NONE: none), at which of the client's ticks
|
||||
and its own, and where that left the host's copy; and the anchor
|
||||
predictions are measured from: one it took, at which of the client's ticks
|
||||
and its own, and where the client had it */
|
||||
static struct
|
||||
{
|
||||
boolean valid;
|
||||
boolean echo;
|
||||
long machine_index;
|
||||
struct distributed_object_state state;
|
||||
long accepted_vehicle_index;
|
||||
word accepted_time;
|
||||
long accepted_host_time;
|
||||
real_point3d accepted_position;
|
||||
real_point3d accepted_host_position;
|
||||
word anchor_time;
|
||||
long anchor_host_time;
|
||||
real_point3d anchor_position;
|
||||
} objects_host_vehicle_predictions[MAXIMUM_TRACKED_PLAYERS];
|
||||
/* a client: the host's objects it has, by absolute index */
|
||||
static long objects_client_has[MAXIMUM_TRACKED_OBJECTS];
|
||||
@@ -820,7 +824,8 @@ void network_objects_client_asked(
|
||||
return;
|
||||
player_list = machine_get_player_list(machine_index);
|
||||
/* (another machine in its place has other players) */
|
||||
if (again || objects_host_machines[machine_index].time == NONE ||
|
||||
if ((again && game_time_get() - objects_host_machines[machine_index].time >= HOST_OBJECTS_RESEND_TICKS) ||
|
||||
objects_host_machines[machine_index].time == NONE ||
|
||||
csmemcmp(objects_host_machines[machine_index].player_indices, player_list,
|
||||
sizeof(objects_host_machines[machine_index].player_indices)) != 0)
|
||||
{
|
||||
@@ -1050,11 +1055,12 @@ static void distributed_host_send_states(
|
||||
{
|
||||
short player_index = objects_host_viewers.machines[machine_number].vehicle_player_indices[own_vehicle];
|
||||
|
||||
/* (the client's clock against the host's, which a gap or a
|
||||
prediction refused since leaves as it was) */
|
||||
if (player_index >= 0 && player_index < MAXIMUM_TRACKED_PLAYERS &&
|
||||
objects_host_vehicle_predictions[player_index].echo &&
|
||||
objects_host_vehicle_predictions[player_index].accepted_vehicle_index == state->object_index &&
|
||||
game_time_get() - objects_host_vehicle_predictions[player_index].accepted_host_time <
|
||||
VEHICLE_PREDICTION_ECHO_TICKS)
|
||||
VEHICLE_PREDICTION_REFERENCE_TICKS)
|
||||
{
|
||||
SET_FLAG(state->flags, _distributed_object_predicted_bit, TRUE);
|
||||
state->time = (short)(word)(objects_host_vehicle_predictions[player_index].accepted_time +
|
||||
@@ -1307,13 +1313,14 @@ void network_objects_handle_vehicle_prediction(
|
||||
still drives it: a little off closed by half, more put there. Each taken
|
||||
moves the host's copy, and the next is measured from there, so a client
|
||||
could take its vehicle HOST_VEHICLE_ACCEPT_TOLERANCE further each tick: a
|
||||
prediction is also no further from the last one taken than the vehicle goes
|
||||
in the client's ticks since (those no more than the host's since and a
|
||||
little jitter), and no faster: as fast as twice its tag's top speed, or as
|
||||
the host's copy goes (falling, thrown), whichever is faster, with a margin.
|
||||
A teleporter moves the host's copy too, further than that since the last one
|
||||
taken: it is measured from where the host has it only. Which of the
|
||||
client's ticks the host has it at is noted, to tell the client. */
|
||||
prediction is also no further from the anchor (one taken, a newer once a
|
||||
second) than the vehicle goes in the client's ticks since (after it, and no
|
||||
more than the host's since and a little jitter), and no faster: as fast as
|
||||
twice its tag's top speed, or as the host's copy goes (falling, thrown),
|
||||
whichever is faster, with a margin. A teleporter moves the host's copy too,
|
||||
further than it goes since the last taken: it is measured from where the
|
||||
host has it only, a new anchor. Which of the client's ticks the host has it at is noted, to tell
|
||||
the client. */
|
||||
void network_objects_apply_vehicle_predictions(
|
||||
void)
|
||||
{
|
||||
@@ -1328,11 +1335,11 @@ void network_objects_apply_vehicle_predictions(
|
||||
real_vector3d forward, up, velocity, angular_velocity;
|
||||
real dx, dy, dz;
|
||||
real speed;
|
||||
boolean anchored;
|
||||
|
||||
if (!objects_host_vehicle_predictions[player_index].valid)
|
||||
continue;
|
||||
objects_host_vehicle_predictions[player_index].valid = FALSE;
|
||||
objects_host_vehicle_predictions[player_index].echo = FALSE;
|
||||
vehicle = (struct unit_datum *)object_try_and_get_and_verify_type(state->object_index, _object_mask_vehicle);
|
||||
if (!vehicle || vehicle->unit.driver_object_index == NONE || vehicle->object.parent_object_index != NONE)
|
||||
continue;
|
||||
@@ -1363,27 +1370,36 @@ void network_objects_apply_vehicle_predictions(
|
||||
speed = top_speed;
|
||||
speed = MIN(speed, MAXIMUM_PREDICTED_VEHICLE_SPEED);
|
||||
}
|
||||
if (objects_host_vehicle_predictions[player_index].accepted_vehicle_index == state->object_index &&
|
||||
now - objects_host_vehicle_predictions[player_index].accepted_host_time < VEHICLE_PREDICTION_REFERENCE_TICKS)
|
||||
anchored = objects_host_vehicle_predictions[player_index].accepted_vehicle_index == state->object_index &&
|
||||
now - objects_host_vehicle_predictions[player_index].anchor_host_time < VEHICLE_PREDICTION_REFERENCE_TICKS;
|
||||
if (anchored)
|
||||
{
|
||||
long host_ticks = now - objects_host_vehicle_predictions[player_index].accepted_host_time;
|
||||
long ticks = MIN((long)(short)(word)((word)state->time - objects_host_vehicle_predictions[player_index].accepted_time),
|
||||
host_ticks + VEHICLE_PREDICTION_JITTER_TICKS);
|
||||
real reach = (speed + PREDICTED_VEHICLE_SPEED_MARGIN) * (real)host_ticks + HOST_VEHICLE_BLEND_DISTANCE;
|
||||
real_point3d const *accepted = &objects_host_vehicle_predictions[player_index].accepted_host_position;
|
||||
real_point3d const *from = &objects_host_vehicle_predictions[player_index].accepted_host_position;
|
||||
|
||||
dx = vehicle->object.position.x - accepted->x;
|
||||
dy = vehicle->object.position.y - accepted->y;
|
||||
dz = vehicle->object.position.z - accepted->z;
|
||||
/* (the host's copy moved further than it goes: taken from where
|
||||
it is) */
|
||||
if (dx * dx + dy * dy + dz * dz <= reach * reach)
|
||||
dx = vehicle->object.position.x - from->x;
|
||||
dy = vehicle->object.position.y - from->y;
|
||||
dz = vehicle->object.position.z - from->z;
|
||||
/* (the host's copy moved further than it goes since the last
|
||||
taken, which only the host moves it by: taken from where it is,
|
||||
a new anchor) */
|
||||
if (!(dx * dx + dy * dy + dz * dz <= reach * reach))
|
||||
{
|
||||
anchored = FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* (a client's clock that jumped counts no more than the
|
||||
host's ticks since and the jitter) */
|
||||
long ticks = MIN((long)(short)(word)((word)state->time - objects_host_vehicle_predictions[player_index].anchor_time),
|
||||
now - objects_host_vehicle_predictions[player_index].anchor_host_time + VEHICLE_PREDICTION_JITTER_TICKS);
|
||||
|
||||
reach = (speed + PREDICTED_VEHICLE_SPEED_MARGIN) * (real)ticks + HOST_VEHICLE_BLEND_DISTANCE;
|
||||
accepted = &objects_host_vehicle_predictions[player_index].accepted_position;
|
||||
dx = state->position.x - accepted->x;
|
||||
dy = state->position.y - accepted->y;
|
||||
dz = state->position.z - accepted->z;
|
||||
from = &objects_host_vehicle_predictions[player_index].anchor_position;
|
||||
dx = state->position.x - from->x;
|
||||
dy = state->position.y - from->y;
|
||||
dz = state->position.z - from->z;
|
||||
if (ticks <= 0 || !(dx * dx + dy * dy + dz * dz <= reach * reach))
|
||||
continue;
|
||||
}
|
||||
@@ -1396,12 +1412,18 @@ void network_objects_apply_vehicle_predictions(
|
||||
continue;
|
||||
network_objects_reconcile(state->object_index, &state->position, &forward, &up, &velocity, &angular_velocity,
|
||||
HOST_VEHICLE_BLEND_DISTANCE);
|
||||
objects_host_vehicle_predictions[player_index].echo = TRUE;
|
||||
objects_host_vehicle_predictions[player_index].accepted_vehicle_index = state->object_index;
|
||||
objects_host_vehicle_predictions[player_index].accepted_time = (word)state->time;
|
||||
objects_host_vehicle_predictions[player_index].accepted_host_time = now;
|
||||
objects_host_vehicle_predictions[player_index].accepted_position = state->position;
|
||||
objects_host_vehicle_predictions[player_index].accepted_host_position = vehicle->object.position;
|
||||
/* (a new anchor: the first, one a second on, or after a jump) */
|
||||
if (!anchored ||
|
||||
now - objects_host_vehicle_predictions[player_index].anchor_host_time >= VEHICLE_PREDICTION_ANCHOR_TICKS)
|
||||
{
|
||||
objects_host_vehicle_predictions[player_index].anchor_time = (word)state->time;
|
||||
objects_host_vehicle_predictions[player_index].anchor_host_time = now;
|
||||
objects_host_vehicle_predictions[player_index].anchor_position = state->position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-4
@@ -13,9 +13,10 @@ link games as if they were on one LAN, without a server of this project's.
|
||||
- Signalling (p2p_signal.c) goes through public MQTT brokers, on topics
|
||||
that are hashes of the token, with messages sealed with a key derived
|
||||
from it (p2p_crypto.c). A joiner offers its public key and the addresses
|
||||
it can be reached at; the host answers with its own. The secret of their
|
||||
session comes from the two keys (X25519) and a nonce of each, and never
|
||||
travels.
|
||||
it can be reached at; the host answers with its own, and makes the
|
||||
session once the joiner, answered, proves that it holds its key. The
|
||||
secret of their session comes from the two keys (X25519) and a nonce of
|
||||
each, and never travels.
|
||||
- The tunnel is one UDP socket. Each machine learns its public address from
|
||||
public STUN servers, and both then send to each other's addresses until
|
||||
packets get through (hole punching). There is no relay: two machines whose
|
||||
@@ -1213,7 +1214,8 @@ static int find_closed(int stream, unsigned long *address, unsigned short *port)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* a stand-in that opens has a port no closed one stands for now */
|
||||
/* a stand-in, or a socket of the game's, that has the port now: no closed
|
||||
stand-in stands for it */
|
||||
static void forget_closed(int stream, unsigned short local_port)
|
||||
{
|
||||
int index;
|
||||
@@ -1570,6 +1572,17 @@ void p2p_socket_port(int socket, int stream, int listening, unsigned short port)
|
||||
/* the game listens for connections while it hosts */
|
||||
if (stream && listening)
|
||||
p2p.hosting_socket = socket;
|
||||
/* (and no stand-in that closed has the port now) */
|
||||
forget_closed(stream != 0, port);
|
||||
pthread_mutex_unlock(&p2p_lock);
|
||||
}
|
||||
|
||||
void p2p_port_taken(int stream, unsigned short port)
|
||||
{
|
||||
if (!p2p.running || !port)
|
||||
return;
|
||||
pthread_mutex_lock(&p2p_lock);
|
||||
forget_closed(stream != 0, port);
|
||||
pthread_mutex_unlock(&p2p_lock);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,9 @@ int p2p_broadcast_datagram(unsigned short source_port, unsigned short port, cons
|
||||
(stream and listening: it is hosting). Peers reach only these ports (a
|
||||
stream's only while it listens), and datagram ports it sent them from */
|
||||
void p2p_socket_port(int socket, int stream, int listening, unsigned short port);
|
||||
/* a socket that peers are not to reach (bound to 127.0.0.1 alone) has this
|
||||
local port: traffic from it is not a peer's whose stand-in had the port */
|
||||
void p2p_port_taken(int stream, unsigned short port);
|
||||
/* the game closes a socket */
|
||||
void p2p_socket_closed(int socket);
|
||||
|
||||
|
||||
+307
-106
@@ -10,27 +10,40 @@ Everything that passes through them is sealed with a key derived from the
|
||||
invite's token, and goes to topics that are hashes of it, so the brokers
|
||||
(and anyone watching them) learn nothing and can join nothing:
|
||||
|
||||
- the host listens on hceu/2/<HMAC(token, "host" | host)>, where a joiner
|
||||
- the host listens on hceu/3/<HMAC(token, "host" | host)>, where a joiner
|
||||
sends JOIN: its public key (its identifier is the key's hash), a nonce,
|
||||
and its addresses;
|
||||
- the joiner listens on hceu/2/<HMAC(token, "joiner" | joiner)>, where the
|
||||
- the joiner listens on hceu/3/<HMAC(token, "joiner" | joiner)>, where the
|
||||
host answers ACCEPT: its public key, the joiner's nonce, one of its own,
|
||||
its addresses, and a tag that only the two of them can make (from their
|
||||
keys).
|
||||
keys);
|
||||
- the joiner then repeats its JOIN with the host's nonce and a tag of its
|
||||
own, made the same way, which shows that it holds the key it gave. Only
|
||||
then does the host make a session.
|
||||
|
||||
Their tunnel's keys come from their X25519 shared secret and the two
|
||||
nonces, and never travel: another holder of the invite reads the messages
|
||||
but cannot work them out, and cannot answer as the host (whose key must
|
||||
hash to the identifier in the invite). It can send a JOIN in another
|
||||
machine's name, which gets it nothing: the tunnel takes that machine's key,
|
||||
and a session with a machine is not replaced while it lives (p2p.c).
|
||||
machine's name, but cannot prove it: the host answers it (to that machine)
|
||||
and makes no session of it. A session no one could complete would keep that
|
||||
machine out while it lived (a session with a machine is not replaced while
|
||||
it lives, p2p.c), and a JOIN every so often would keep it out for good.
|
||||
|
||||
The host's nonce is a hash of the request with a key of the host's and the
|
||||
time (it changes every HOST_NONCE_PERIOD), so the host need not remember
|
||||
what it answered: anyone with the invite can ask in a machine's name as
|
||||
often as it likes, pushing out whatever the host remembered. What it does
|
||||
keep of requests not proven yet (the work of their keys) only saves work.
|
||||
|
||||
A joiner repeats its JOIN until the tunnel reaches the host. The host makes
|
||||
one session of a JOIN (its public key and nonce) at most: anyone watching
|
||||
the brokers could send it again after the session ended, and have the host
|
||||
reach for the joiner's old addresses in its name, keeping it out. So a
|
||||
joiner asks with a new nonce when its session with the host ends before the
|
||||
tunnel reached it, or when the host has not answered it in a while.
|
||||
one session of a request (a public key and nonce) at most: anyone watching
|
||||
the brokers could send the proven JOIN again after the session ended, and
|
||||
have the host reach for the joiner's old addresses in its name, keeping it
|
||||
out (with the same keys again). It remembers the request at least while its
|
||||
host nonce lasts. So a joiner asks with a new nonce when its session with
|
||||
the host ends before the tunnel reached it, or when the host has not
|
||||
answered it in a while.
|
||||
*/
|
||||
|
||||
#include "platform.h"
|
||||
@@ -45,16 +58,22 @@ tunnel reached it, or when the host has not answered it in a while.
|
||||
enum
|
||||
{
|
||||
MAXIMUM_BROKERS = 4,
|
||||
/* the joiners a host remembers, so a repeated request gets the same
|
||||
session: as many as it takes */
|
||||
/* the joiners a host made sessions for, so a repeated request gets the
|
||||
same session: as many as it takes */
|
||||
MAXIMUM_JOINERS = P2P_MAXIMUM_PEERS + 1,
|
||||
/* the requests not proven yet whose keys' work a host keeps; only to
|
||||
save work (a flood of requests pushes them out) */
|
||||
MAXIMUM_ASKERS = 16,
|
||||
/* the requests a session was made from, which make none again (a
|
||||
host takes at most a few new players a minute: this is hours of them) */
|
||||
host takes at most a few new players a minute: this is hours of them;
|
||||
one is not forgotten while its host nonce lasts: USED_REQUEST_TIME) */
|
||||
MAXIMUM_USED_REQUESTS = 1024,
|
||||
TOPIC_SIZE = 7 + 32 + 1,
|
||||
NONCE_SIZE = 8,
|
||||
/* an ACCEPT's tag: the first half of an HMAC-SHA256 */
|
||||
/* an ACCEPT's or a proven JOIN's tag: the first half of an HMAC-SHA256 */
|
||||
TAG_SIZE = 16,
|
||||
/* a proven JOIN's end: the host's nonce, and the tag */
|
||||
PROOF_SIZE = NONCE_SIZE + TAG_SIZE,
|
||||
BUFFER_SIZE = 4096,
|
||||
MAXIMUM_MESSAGE_SIZE = 256,
|
||||
|
||||
@@ -67,9 +86,12 @@ enum
|
||||
JOIN_INTERVAL = 2000,
|
||||
ANSWER_INTERVAL = 1000,
|
||||
/* a joiner the host has not answered in this long asks anew, with a
|
||||
new nonce (the host may have made a session from its request whose
|
||||
answer was lost, and makes no other from it) */
|
||||
new nonce (a fresh start: the host makes nothing of a request it did
|
||||
not answer, as it makes a session only of a proven one) */
|
||||
UNANSWERED_TIME = 20000,
|
||||
/* a host nonce is taken in the period it is made in and the next */
|
||||
HOST_NONCE_PERIOD = 30000,
|
||||
USED_REQUEST_TIME = 2 * HOST_NONCE_PERIOD,
|
||||
};
|
||||
|
||||
enum
|
||||
@@ -84,7 +106,8 @@ enum
|
||||
{
|
||||
_message_join = 'J',
|
||||
_message_accept = 'A',
|
||||
MESSAGE_VERSION = 2,
|
||||
/* 3: a JOIN proves its key before the host makes a session */
|
||||
MESSAGE_VERSION = 3,
|
||||
};
|
||||
|
||||
struct broker
|
||||
@@ -109,6 +132,8 @@ struct broker
|
||||
int output_size;
|
||||
};
|
||||
|
||||
/* a request: of a session made (joiners), or not proven yet (askers, which
|
||||
have no host nonce or secret) */
|
||||
struct joiner
|
||||
{
|
||||
unsigned char identifier[P2P_IDENTIFIER_SIZE];
|
||||
@@ -123,6 +148,13 @@ struct joiner
|
||||
int used;
|
||||
};
|
||||
|
||||
struct used_request
|
||||
{
|
||||
/* the joiner's identifier and nonce */
|
||||
unsigned char request[P2P_IDENTIFIER_SIZE + NONCE_SIZE];
|
||||
unsigned long time;
|
||||
};
|
||||
|
||||
static struct
|
||||
{
|
||||
int started;
|
||||
@@ -137,9 +169,14 @@ static struct
|
||||
char host_topic[TOPIC_SIZE];
|
||||
struct joiner joiners[MAXIMUM_JOINERS];
|
||||
int next_joiner;
|
||||
/* the joiner's identifier and nonce of each request a session was made
|
||||
from (kept while hosting stops and starts: an invite lasts the run) */
|
||||
unsigned char used_requests[MAXIMUM_USED_REQUESTS][P2P_IDENTIFIER_SIZE + NONCE_SIZE];
|
||||
struct joiner askers[MAXIMUM_ASKERS];
|
||||
int next_asker;
|
||||
/* the key of the host's nonces (host_nonce_for), for the run */
|
||||
int has_nonce_key;
|
||||
unsigned char nonce_key[P2P_SHA256_SIZE];
|
||||
/* each request a session was made from (kept while hosting stops and
|
||||
starts: an invite lasts the run) */
|
||||
struct used_request used_requests[MAXIMUM_USED_REQUESTS];
|
||||
int used_request_count;
|
||||
int used_request_next;
|
||||
|
||||
@@ -155,9 +192,11 @@ static struct
|
||||
char join_host_topic[TOPIC_SIZE];
|
||||
char join_topic[TOPIC_SIZE];
|
||||
unsigned long join_sent_time;
|
||||
/* when join_nonce was made, and whether the host answered it */
|
||||
/* when join_nonce was made, and whether the host answered it (with
|
||||
join_host_nonce, which its proof carries) */
|
||||
unsigned long join_nonce_time;
|
||||
int join_answered;
|
||||
unsigned char join_host_nonce[NONCE_SIZE];
|
||||
} signalling;
|
||||
|
||||
static int elapsed(unsigned long since, unsigned long time)
|
||||
@@ -196,7 +235,7 @@ static void make_topic(const unsigned char *token, const char *label, const unsi
|
||||
|
||||
derive(token, label, identifier, digest);
|
||||
p2p_hex(digest, 16, text);
|
||||
snprintf(topic, TOPIC_SIZE, "hceu/2/%s", text);
|
||||
snprintf(topic, TOPIC_SIZE, "hceu/3/%s", text);
|
||||
}
|
||||
|
||||
/* ---------- MQTT */
|
||||
@@ -463,18 +502,63 @@ static void session_secret(const unsigned char *base, const unsigned char *nonce
|
||||
p2p_hmac_sha256(base, P2P_SHA256_SIZE, data, sizeof(data), secret);
|
||||
}
|
||||
|
||||
/* an ACCEPT's tag, over what precedes it */
|
||||
static void accept_tag(const unsigned char *base, const unsigned char *message, int size, unsigned char *tag)
|
||||
/* an ACCEPT's tag (label "accept") or a proven JOIN's ("join"), over what
|
||||
precedes it */
|
||||
static void message_tag(const unsigned char *base, const char *label, const unsigned char *message, int size,
|
||||
unsigned char *tag)
|
||||
{
|
||||
unsigned char data[6 + MAXIMUM_MESSAGE_SIZE];
|
||||
unsigned char digest[P2P_SHA256_SIZE];
|
||||
int label_size = (int)strlen(label);
|
||||
|
||||
memcpy(data, "accept", 6);
|
||||
memcpy(data + 6, message, (size_t)size);
|
||||
p2p_hmac_sha256(base, P2P_SHA256_SIZE, data, 6 + size, digest);
|
||||
memcpy(data, label, (size_t)label_size);
|
||||
memcpy(data + label_size, message, (size_t)size);
|
||||
p2p_hmac_sha256(base, P2P_SHA256_SIZE, data, label_size + size, digest);
|
||||
memcpy(tag, digest, TAG_SIZE);
|
||||
}
|
||||
|
||||
/* whether a message's tag (its last TAG_SIZE bytes) is right */
|
||||
static int tag_right(const unsigned char *base, const char *label, const unsigned char *message, int size)
|
||||
{
|
||||
unsigned char tag[TAG_SIZE];
|
||||
|
||||
message_tag(base, label, message, size - TAG_SIZE, tag);
|
||||
return p2p_equal(tag, message + size - TAG_SIZE, TAG_SIZE);
|
||||
}
|
||||
|
||||
/* the host's nonce for a request (a joiner's public key and nonce) in a
|
||||
period of HOST_NONCE_PERIOD */
|
||||
static void host_nonce_for(const unsigned char *public_key, const unsigned char *nonce, unsigned long period,
|
||||
unsigned char *host_nonce)
|
||||
{
|
||||
unsigned char data[4 + P2P_KEY_SIZE + NONCE_SIZE];
|
||||
unsigned char digest[P2P_SHA256_SIZE];
|
||||
int index;
|
||||
|
||||
for (index = 0; index < 4; index++)
|
||||
data[index] = (unsigned char)(period >> (index * 8));
|
||||
memcpy(data + 4, public_key, P2P_KEY_SIZE);
|
||||
memcpy(data + 4 + P2P_KEY_SIZE, nonce, NONCE_SIZE);
|
||||
p2p_hmac_sha256(signalling.nonce_key, P2P_SHA256_SIZE, data, sizeof(data), digest);
|
||||
memcpy(host_nonce, digest, NONCE_SIZE);
|
||||
}
|
||||
|
||||
/* whether the host answered a request with this nonce lately: in this
|
||||
period, or the last */
|
||||
static int host_nonce_current(const unsigned char *public_key, const unsigned char *nonce,
|
||||
const unsigned char *host_nonce)
|
||||
{
|
||||
unsigned long period = p2p_now() / HOST_NONCE_PERIOD;
|
||||
unsigned char expected[NONCE_SIZE];
|
||||
|
||||
host_nonce_for(public_key, nonce, period, expected);
|
||||
if (p2p_equal(expected, host_nonce, NONCE_SIZE))
|
||||
return 1;
|
||||
/* (the last before the clock wraps, before the first) */
|
||||
host_nonce_for(public_key, nonce, period ? period - 1 : 0xFFFFFFFFUL / HOST_NONCE_PERIOD, expected);
|
||||
return p2p_equal(expected, host_nonce, NONCE_SIZE);
|
||||
}
|
||||
|
||||
/* ---------- the messages */
|
||||
|
||||
static void send_join(void)
|
||||
@@ -490,107 +574,208 @@ static void send_join(void)
|
||||
memcpy(message + size, signalling.join_nonce, NONCE_SIZE);
|
||||
size += NONCE_SIZE;
|
||||
size += put_candidates(message + size);
|
||||
/* answered: the proof that this machine holds its key, of which the host
|
||||
makes the session */
|
||||
if (signalling.join_answered)
|
||||
{
|
||||
memcpy(message + size, signalling.join_host_nonce, NONCE_SIZE);
|
||||
size += NONCE_SIZE;
|
||||
message_tag(signalling.join_base, "join", message, size, message + size);
|
||||
size += TAG_SIZE;
|
||||
}
|
||||
size = p2p_seal(signalling.join_key, message, size, sealed);
|
||||
publish_everywhere(signalling.join_host_topic, sealed, size);
|
||||
signalling.join_sent_time = p2p_now();
|
||||
}
|
||||
|
||||
/* the host: a joiner asked */
|
||||
static struct joiner *find_joiner(struct joiner *list, int count, const unsigned char *public_key,
|
||||
const unsigned char *nonce)
|
||||
{
|
||||
int index;
|
||||
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
if (list[index].used && !memcmp(list[index].public_key, public_key, P2P_KEY_SIZE) &&
|
||||
!memcmp(list[index].nonce, nonce, NONCE_SIZE))
|
||||
{
|
||||
return &list[index];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* pair_base with a joiner: kept from a request of its, else worked out */
|
||||
static int joiner_base(const unsigned char *public_key, unsigned char *base)
|
||||
{
|
||||
int index;
|
||||
|
||||
for (index = 0; index < MAXIMUM_JOINERS + MAXIMUM_ASKERS; index++)
|
||||
{
|
||||
struct joiner const *joiner = index < MAXIMUM_JOINERS ? &signalling.joiners[index] :
|
||||
&signalling.askers[index - MAXIMUM_JOINERS];
|
||||
|
||||
if (joiner->used && !memcmp(joiner->public_key, public_key, P2P_KEY_SIZE))
|
||||
{
|
||||
memcpy(base, joiner->base, P2P_SHA256_SIZE);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return pair_base(public_key, p2p_public_key(), public_key, base);
|
||||
}
|
||||
|
||||
static int request_used(const unsigned char *request)
|
||||
{
|
||||
int index;
|
||||
|
||||
for (index = 0; index < signalling.used_request_count; index++)
|
||||
{
|
||||
if (!memcmp(signalling.used_requests[index].request, request, P2P_IDENTIFIER_SIZE + NONCE_SIZE))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* the host's answer to a request */
|
||||
static void send_accept(const unsigned char *identifier, const unsigned char *nonce, const unsigned char *host_nonce,
|
||||
const unsigned char *base)
|
||||
{
|
||||
unsigned char answer[MAXIMUM_MESSAGE_SIZE];
|
||||
unsigned char sealed[MAXIMUM_MESSAGE_SIZE + P2P_SEAL_OVERHEAD];
|
||||
char topic[TOPIC_SIZE];
|
||||
int size = 0;
|
||||
|
||||
answer[size++] = _message_accept;
|
||||
answer[size++] = MESSAGE_VERSION;
|
||||
memcpy(answer + size, p2p_public_key(), P2P_KEY_SIZE);
|
||||
size += P2P_KEY_SIZE;
|
||||
memcpy(answer + size, nonce, NONCE_SIZE);
|
||||
size += NONCE_SIZE;
|
||||
memcpy(answer + size, host_nonce, NONCE_SIZE);
|
||||
size += NONCE_SIZE;
|
||||
size += put_candidates(answer + size);
|
||||
message_tag(base, "accept", answer, size, answer + size);
|
||||
size += TAG_SIZE;
|
||||
size = p2p_seal(signalling.host_key, answer, size, sealed);
|
||||
make_topic(signalling.host_token, "joiner", identifier, topic);
|
||||
publish_everywhere(topic, sealed, size);
|
||||
}
|
||||
|
||||
/* the host: a joiner asked; with a proof (the host's nonce, and a tag) once
|
||||
the host answered it */
|
||||
static void join_received(const unsigned char *message, int size)
|
||||
{
|
||||
struct p2p_candidate candidates[P2P_MAXIMUM_CANDIDATES];
|
||||
unsigned char answer[MAXIMUM_MESSAGE_SIZE];
|
||||
unsigned char sealed[MAXIMUM_MESSAGE_SIZE + P2P_SEAL_OVERHEAD];
|
||||
unsigned char identifier[P2P_IDENTIFIER_SIZE];
|
||||
unsigned char request[P2P_IDENTIFIER_SIZE + NONCE_SIZE];
|
||||
unsigned char host_nonce[NONCE_SIZE];
|
||||
unsigned char base[P2P_SHA256_SIZE];
|
||||
unsigned char secret[P2P_SHA256_SIZE];
|
||||
const unsigned char *public_key = message + 2;
|
||||
const unsigned char *nonce = public_key + P2P_KEY_SIZE;
|
||||
int fixed = 2 + P2P_KEY_SIZE + NONCE_SIZE;
|
||||
struct joiner *joiner = NULL;
|
||||
char topic[TOPIC_SIZE];
|
||||
int answered = 0;
|
||||
struct joiner *joiner;
|
||||
struct used_request *used;
|
||||
int proven;
|
||||
int count;
|
||||
int index;
|
||||
int answer_size = 0;
|
||||
|
||||
if (size < fixed + 1)
|
||||
return;
|
||||
count = get_candidates(message + fixed, size - fixed, candidates);
|
||||
if (count < 0)
|
||||
return;
|
||||
proven = size - fixed - 1 - count * 6;
|
||||
if (proven != 0 && proven != PROOF_SIZE)
|
||||
return;
|
||||
p2p_identifier_for(public_key, identifier);
|
||||
for (index = 0; index < MAXIMUM_JOINERS; index++)
|
||||
joiner = find_joiner(signalling.joiners, MAXIMUM_JOINERS, public_key, nonce);
|
||||
if (joiner)
|
||||
{
|
||||
if (signalling.joiners[index].used &&
|
||||
!memcmp(signalling.joiners[index].identifier, identifier, P2P_IDENTIFIER_SIZE))
|
||||
/* a request a session was made from, again (through another broker,
|
||||
or repeated until the tunnel reaches the host): that session's
|
||||
answer, while it lasts; never another. The addresses only from a
|
||||
proof (anyone can send the rest) */
|
||||
if (proven && (memcmp(message + size - PROOF_SIZE, joiner->host_nonce, NONCE_SIZE) ||
|
||||
!tag_right(joiner->base, "join", message, size)))
|
||||
{
|
||||
joiner = &signalling.joiners[index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (joiner && !memcmp(joiner->public_key, public_key, P2P_KEY_SIZE) && !memcmp(joiner->nonce, nonce, NONCE_SIZE))
|
||||
{
|
||||
/* the same request again, through another broker or repeated: the
|
||||
same session, while it lasts; never another (anyone watching the
|
||||
brokers can send it again once the session ends, and a session in
|
||||
the joiner's name would keep the joiner out while it lasts: a joiner
|
||||
asks again with a new nonce) */
|
||||
if (!elapsed(joiner->answered_time, ANSWER_INTERVAL) ||
|
||||
!p2p_peer_reoffered(identifier, joiner->secret, candidates, count))
|
||||
return;
|
||||
answered = 1;
|
||||
}
|
||||
if (!answered)
|
||||
{
|
||||
/* a new session (p2p.c turns it away while another with that machine
|
||||
lives), from a request no session was made from */
|
||||
unsigned char base[P2P_SHA256_SIZE], host_nonce[NONCE_SIZE], secret[P2P_SHA256_SIZE];
|
||||
unsigned char request[P2P_IDENTIFIER_SIZE + NONCE_SIZE];
|
||||
|
||||
memcpy(request, identifier, P2P_IDENTIFIER_SIZE);
|
||||
memcpy(request + P2P_IDENTIFIER_SIZE, nonce, NONCE_SIZE);
|
||||
for (index = 0; index < signalling.used_request_count; index++)
|
||||
{
|
||||
if (!memcmp(signalling.used_requests[index], request, sizeof(request)))
|
||||
return;
|
||||
}
|
||||
if (!elapsed(joiner->answered_time, ANSWER_INTERVAL) ||
|
||||
!p2p_peer_reoffered(identifier, joiner->secret, candidates, proven ? count : 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
joiner->answered_time = p2p_now();
|
||||
send_accept(identifier, joiner->nonce, joiner->host_nonce, joiner->base);
|
||||
return;
|
||||
}
|
||||
memcpy(request, identifier, P2P_IDENTIFIER_SIZE);
|
||||
memcpy(request + P2P_IDENTIFIER_SIZE, nonce, NONCE_SIZE);
|
||||
if (request_used(request))
|
||||
return;
|
||||
if (!proven)
|
||||
{
|
||||
/* an answer (to the machine whose key it is, which alone can prove
|
||||
the request), and nothing else: the host keeps nothing of it that it
|
||||
needs */
|
||||
struct joiner *asker = find_joiner(signalling.askers, MAXIMUM_ASKERS, public_key, nonce);
|
||||
|
||||
if (asker && !elapsed(asker->answered_time, ANSWER_INTERVAL))
|
||||
return;
|
||||
/* (checked before the work of the keys, which anyone with the invite
|
||||
can ask for as often as they like) */
|
||||
if (p2p_peer_turned_away(identifier, 0) || !pair_base(public_key, p2p_public_key(), public_key, base))
|
||||
if (p2p_peer_turned_away(identifier, 0))
|
||||
return;
|
||||
posix_random_bytes(host_nonce, NONCE_SIZE);
|
||||
session_secret(base, nonce, host_nonce, secret);
|
||||
if (!p2p_peer_offered(identifier, secret, candidates, count, 0))
|
||||
return;
|
||||
memcpy(signalling.used_requests[signalling.used_request_next], request, sizeof(request));
|
||||
signalling.used_request_next = (signalling.used_request_next + 1) % MAXIMUM_USED_REQUESTS;
|
||||
if (signalling.used_request_count < MAXIMUM_USED_REQUESTS)
|
||||
signalling.used_request_count++;
|
||||
if (!joiner)
|
||||
joiner = &signalling.joiners[signalling.next_joiner++ % MAXIMUM_JOINERS];
|
||||
memcpy(joiner->identifier, identifier, P2P_IDENTIFIER_SIZE);
|
||||
memcpy(joiner->public_key, public_key, P2P_KEY_SIZE);
|
||||
memcpy(joiner->nonce, nonce, NONCE_SIZE);
|
||||
memcpy(joiner->host_nonce, host_nonce, NONCE_SIZE);
|
||||
memcpy(joiner->base, base, P2P_SHA256_SIZE);
|
||||
memcpy(joiner->secret, secret, P2P_SHA256_SIZE);
|
||||
joiner->used = 1;
|
||||
if (!asker)
|
||||
{
|
||||
if (!joiner_base(public_key, base))
|
||||
return;
|
||||
asker = &signalling.askers[signalling.next_asker];
|
||||
signalling.next_asker = (signalling.next_asker + 1) % MAXIMUM_ASKERS;
|
||||
memset(asker, 0, sizeof(*asker));
|
||||
memcpy(asker->identifier, identifier, P2P_IDENTIFIER_SIZE);
|
||||
memcpy(asker->public_key, public_key, P2P_KEY_SIZE);
|
||||
memcpy(asker->nonce, nonce, NONCE_SIZE);
|
||||
memcpy(asker->base, base, P2P_SHA256_SIZE);
|
||||
asker->used = 1;
|
||||
}
|
||||
asker->answered_time = p2p_now();
|
||||
host_nonce_for(public_key, nonce, p2p_now() / HOST_NONCE_PERIOD, host_nonce);
|
||||
send_accept(identifier, nonce, host_nonce, asker->base);
|
||||
return;
|
||||
}
|
||||
/* proven: with a nonce the host answered the request with lately, and a
|
||||
tag only the key's holder can make. A new session (p2p.c turns it away
|
||||
while another with that machine lives) */
|
||||
memcpy(host_nonce, message + size - PROOF_SIZE, NONCE_SIZE);
|
||||
if (!host_nonce_current(public_key, nonce, host_nonce) || p2p_peer_turned_away(identifier, 0) ||
|
||||
!joiner_base(public_key, base) || !tag_right(base, "join", message, size))
|
||||
{
|
||||
return;
|
||||
}
|
||||
/* (a request is remembered while its proof lasts, at least: a copy of it
|
||||
would make the session again, with the same keys) */
|
||||
used = &signalling.used_requests[signalling.used_request_next];
|
||||
if (signalling.used_request_count == MAXIMUM_USED_REQUESTS && !elapsed(used->time, USED_REQUEST_TIME))
|
||||
return;
|
||||
session_secret(base, nonce, host_nonce, secret);
|
||||
if (!p2p_peer_offered(identifier, secret, candidates, count, 0))
|
||||
return;
|
||||
memcpy(used->request, request, sizeof(request));
|
||||
used->time = p2p_now();
|
||||
signalling.used_request_next = (signalling.used_request_next + 1) % MAXIMUM_USED_REQUESTS;
|
||||
if (signalling.used_request_count < MAXIMUM_USED_REQUESTS)
|
||||
signalling.used_request_count++;
|
||||
joiner = &signalling.joiners[signalling.next_joiner];
|
||||
signalling.next_joiner = (signalling.next_joiner + 1) % MAXIMUM_JOINERS;
|
||||
memcpy(joiner->identifier, identifier, P2P_IDENTIFIER_SIZE);
|
||||
memcpy(joiner->public_key, public_key, P2P_KEY_SIZE);
|
||||
memcpy(joiner->nonce, nonce, NONCE_SIZE);
|
||||
memcpy(joiner->host_nonce, host_nonce, NONCE_SIZE);
|
||||
memcpy(joiner->base, base, P2P_SHA256_SIZE);
|
||||
memcpy(joiner->secret, secret, P2P_SHA256_SIZE);
|
||||
joiner->answered_time = p2p_now();
|
||||
|
||||
answer[answer_size++] = _message_accept;
|
||||
answer[answer_size++] = MESSAGE_VERSION;
|
||||
memcpy(answer + answer_size, p2p_public_key(), P2P_KEY_SIZE);
|
||||
answer_size += P2P_KEY_SIZE;
|
||||
memcpy(answer + answer_size, joiner->nonce, NONCE_SIZE);
|
||||
answer_size += NONCE_SIZE;
|
||||
memcpy(answer + answer_size, joiner->host_nonce, NONCE_SIZE);
|
||||
answer_size += NONCE_SIZE;
|
||||
answer_size += put_candidates(answer + answer_size);
|
||||
accept_tag(joiner->base, answer, answer_size, answer + answer_size);
|
||||
answer_size += TAG_SIZE;
|
||||
answer_size = p2p_seal(signalling.host_key, answer, answer_size, sealed);
|
||||
make_topic(signalling.host_token, "joiner", identifier, topic);
|
||||
publish_everywhere(topic, sealed, answer_size);
|
||||
joiner->used = 1;
|
||||
send_accept(identifier, nonce, host_nonce, base);
|
||||
}
|
||||
|
||||
/* the joiner: the host answered */
|
||||
@@ -598,7 +783,6 @@ static void accept_received(const unsigned char *message, int size)
|
||||
{
|
||||
struct p2p_candidate candidates[P2P_MAXIMUM_CANDIDATES];
|
||||
unsigned char identifier[P2P_IDENTIFIER_SIZE];
|
||||
unsigned char tag[TAG_SIZE];
|
||||
unsigned char secret[P2P_SHA256_SIZE];
|
||||
const unsigned char *host_public = message + 2;
|
||||
const unsigned char *nonce = host_public + P2P_KEY_SIZE;
|
||||
@@ -608,6 +792,13 @@ static void accept_received(const unsigned char *message, int size)
|
||||
|
||||
if (size < fixed + 1 + TAG_SIZE || memcmp(nonce, signalling.join_nonce, NONCE_SIZE))
|
||||
return;
|
||||
/* the first answer to the request holds: the proof carries its host
|
||||
nonce (the host's changes every HOST_NONCE_PERIOD) */
|
||||
if (signalling.join_answered && (memcmp(host_public, signalling.join_host_public, P2P_KEY_SIZE) ||
|
||||
memcmp(host_nonce, signalling.join_host_nonce, NONCE_SIZE)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
/* the invite's host: its key hashes to the identifier in the invite */
|
||||
p2p_identifier_for(host_public, identifier);
|
||||
if (memcmp(identifier, signalling.join_host, P2P_IDENTIFIER_SIZE))
|
||||
@@ -620,15 +811,18 @@ static void accept_received(const unsigned char *message, int size)
|
||||
signalling.join_has_base = 1;
|
||||
}
|
||||
/* and the answer is its */
|
||||
accept_tag(signalling.join_base, message, size - TAG_SIZE, tag);
|
||||
if (!p2p_equal(tag, message + size - TAG_SIZE, TAG_SIZE))
|
||||
if (!tag_right(signalling.join_base, "accept", message, size))
|
||||
return;
|
||||
count = get_candidates(message + fixed, size - TAG_SIZE - fixed, candidates);
|
||||
if (count < 0)
|
||||
return;
|
||||
signalling.join_answered = 1;
|
||||
session_secret(signalling.join_base, nonce, host_nonce, secret);
|
||||
p2p_peer_offered(signalling.join_host, secret, candidates, count, 1);
|
||||
if (!p2p_peer_offered(signalling.join_host, secret, candidates, count, 1) || signalling.join_answered)
|
||||
return;
|
||||
memcpy(signalling.join_host_nonce, host_nonce, NONCE_SIZE);
|
||||
signalling.join_answered = 1;
|
||||
/* the proof, at once */
|
||||
send_join();
|
||||
}
|
||||
|
||||
static void publish_received(const char *topic, const unsigned char *payload, int size)
|
||||
@@ -721,10 +915,11 @@ static void broker_parse(struct broker *broker)
|
||||
publish_received(topic, body + offset, remaining - offset);
|
||||
}
|
||||
}
|
||||
memmove(broker->input, broker->input + total, (size_t)(broker->input_size - total));
|
||||
broker->input_size -= total;
|
||||
/* (what it sent in answer may have closed it, emptying input) */
|
||||
if (broker->socket < 0)
|
||||
return;
|
||||
memmove(broker->input, broker->input + total, (size_t)(broker->input_size - total));
|
||||
broker->input_size -= total;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -909,6 +1104,11 @@ static void sync_all_topics(void)
|
||||
|
||||
void p2p_signal_host(const unsigned char *token)
|
||||
{
|
||||
if (!signalling.has_nonce_key)
|
||||
{
|
||||
posix_random_bytes(signalling.nonce_key, P2P_SHA256_SIZE);
|
||||
signalling.has_nonce_key = 1;
|
||||
}
|
||||
memcpy(signalling.host_token, token, P2P_TOKEN_SIZE);
|
||||
derive(token, "seal", NULL, signalling.host_key);
|
||||
make_topic(token, "host", p2p_identifier(), signalling.host_topic);
|
||||
@@ -920,6 +1120,7 @@ void p2p_signal_stop_hosting(void)
|
||||
{
|
||||
signalling.hosting = 0;
|
||||
memset(signalling.joiners, 0, sizeof(signalling.joiners));
|
||||
memset(signalling.askers, 0, sizeof(signalling.askers));
|
||||
sync_all_topics();
|
||||
}
|
||||
|
||||
|
||||
@@ -333,9 +333,9 @@ int posix_socket_select(int *read, int *read_count, int *write, int *write_count
|
||||
/* poll, which takes any descriptor (select none from FD_SETSIZE on,
|
||||
which a process allowed more files has), with select's readiness: read
|
||||
for data, the end or an error, write for room or an error, error for
|
||||
urgent data */
|
||||
urgent data or (as Winsock's) a connect that failed */
|
||||
static const short events[3] = { POLLIN, POLLOUT, POLLPRI };
|
||||
static const short ready[3] = { POLLIN | POLLHUP | POLLERR, POLLOUT | POLLERR, POLLPRI };
|
||||
static const short ready[3] = { POLLIN | POLLHUP | POLLERR, POLLOUT | POLLERR, POLLPRI | POLLERR };
|
||||
/* (larger sets, as internet play's thread waits on, in a buffer each
|
||||
thread keeps: not one allocation each time) */
|
||||
static __thread struct pollfd *buffer;
|
||||
|
||||
+44
-14
@@ -240,8 +240,8 @@ static int peer_outgoing_address(int stream, int connecting, const struct sockad
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* tells internet play the local port the game's socket has (bound, or
|
||||
listening) */
|
||||
/* tells internet play the local port the game's socket has (bound, given
|
||||
one by a connection or a send, or listening) */
|
||||
static void note_socket_port(SOCKET socket, int listening)
|
||||
{
|
||||
struct sockaddr_in bound;
|
||||
@@ -249,15 +249,18 @@ static void note_socket_port(SOCKET socket, int listening)
|
||||
int type = SOCK_DGRAM;
|
||||
int type_length = sizeof(type);
|
||||
|
||||
/* (not one bound to this machine alone: the telnet console's, which no
|
||||
peer is to reach, and which is not hosting) */
|
||||
if (posix_socket_getsockname((int)socket, &bound, &length) < 0 || bound.sin_family != AF_INET ||
|
||||
bound.sin_addr.s_addr == loopback_address())
|
||||
!bound.sin_port)
|
||||
{
|
||||
return;
|
||||
}
|
||||
posix_socket_getsockopt((int)socket, SOL_SOCKET, SO_TYPE, &type, &type_length);
|
||||
p2p_socket_port((int)socket, type == SOCK_STREAM, listening, bound.sin_port);
|
||||
/* (one bound to this machine alone, as the telnet console's, is not for
|
||||
peers to reach, and is not hosting; its port is still no stand-in's) */
|
||||
if (bound.sin_addr.s_addr == loopback_address())
|
||||
p2p_port_taken(type == SOCK_STREAM, bound.sin_port);
|
||||
else
|
||||
p2p_socket_port((int)socket, type == SOCK_STREAM, listening, bound.sin_port);
|
||||
}
|
||||
|
||||
/* traffic from an internet play peer's stand-in comes from the peer, and
|
||||
@@ -406,6 +409,8 @@ int WSAAPI halo_ws_connect(SOCKET socket, const struct sockaddr *address, int ad
|
||||
unsigned long override;
|
||||
int type = SOCK_STREAM;
|
||||
int type_length = sizeof(type);
|
||||
int result;
|
||||
int error;
|
||||
|
||||
/* an internet play peer's TCP or UDP port */
|
||||
if (address && address->sa_family == AF_INET &&
|
||||
@@ -437,7 +442,18 @@ int WSAAPI halo_ws_connect(SOCKET socket, const struct sockaddr *address, int ad
|
||||
posix_socket_bind((int)socket, &bound, sizeof(bound));
|
||||
}
|
||||
}
|
||||
return winsock_result(posix_socket_connect((int)socket, address, address_length));
|
||||
result = posix_socket_connect((int)socket, address, address_length);
|
||||
error = result < 0 ? posix_socket_last_error() : 0;
|
||||
/* the port the system gave it (a connection under way has one too, and
|
||||
a datagram socket's that failed keeps its: a stream socket's that failed
|
||||
has none) */
|
||||
note_socket_port(socket, 0);
|
||||
if (result < 0)
|
||||
{
|
||||
WSASetLastError(error);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int WSAAPI halo_ws_listen(SOCKET socket, int backlog)
|
||||
@@ -474,6 +490,10 @@ int WSAAPI halo_ws_sendto(SOCKET socket, const char *buffer, int length, int fla
|
||||
{
|
||||
/* the rewritten destination: it must outlive the send */
|
||||
struct sockaddr_in target;
|
||||
/* 0: not bound yet, which the first send binds it */
|
||||
unsigned short source_port = socket_port(socket);
|
||||
int result;
|
||||
int send_error;
|
||||
|
||||
if (address && address->sa_family == AF_INET && address_length >= (int)sizeof(struct sockaddr_in) &&
|
||||
((const struct sockaddr_in *)address)->sin_addr.s_addr == INADDR_BROADCAST)
|
||||
@@ -481,11 +501,8 @@ int WSAAPI halo_ws_sendto(SOCKET socket, const char *buffer, int length, int fla
|
||||
unsigned long targets[MAXIMUM_BROADCAST_TARGETS];
|
||||
unsigned short ports[P2P_BROADCAST_PEERS];
|
||||
int target_count = broadcast_targets(targets, MAXIMUM_BROADCAST_TARGETS);
|
||||
unsigned short source_port;
|
||||
int peer_count;
|
||||
int send_error;
|
||||
int index;
|
||||
int result;
|
||||
|
||||
/* one datagram per target; the broadcast counts as sent if any is */
|
||||
memcpy(&target, address, sizeof(target));
|
||||
@@ -511,7 +528,12 @@ int WSAAPI halo_ws_sendto(SOCKET socket, const char *buffer, int length, int fla
|
||||
/* and to every internet play peer (after the send above, which binds
|
||||
the socket if it was not): onto the tunnel at once, else through the
|
||||
stand-ins */
|
||||
source_port = socket_port(socket);
|
||||
if (!source_port)
|
||||
{
|
||||
source_port = socket_port(socket);
|
||||
if (source_port)
|
||||
note_socket_port(socket, 0);
|
||||
}
|
||||
if (source_port)
|
||||
{
|
||||
if (p2p_broadcast_datagram(source_port, ((const struct sockaddr_in *)address)->sin_port, buffer,
|
||||
@@ -551,8 +573,6 @@ int WSAAPI halo_ws_sendto(SOCKET socket, const char *buffer, int length, int fla
|
||||
if (address && address->sa_family == AF_INET && address_length >= (int)sizeof(struct sockaddr_in) &&
|
||||
(halo_ws_ntohl(((const struct sockaddr_in *)address)->sin_addr.s_addr) & 0xFFC00000) == 0x64400000)
|
||||
{
|
||||
unsigned short source_port = socket_port(socket);
|
||||
|
||||
switch (source_port ? p2p_send_datagram(source_port, ((const struct sockaddr_in *)address)->sin_addr.s_addr,
|
||||
((const struct sockaddr_in *)address)->sin_port, buffer, length) : 0)
|
||||
{
|
||||
@@ -571,7 +591,17 @@ int WSAAPI halo_ws_sendto(SOCKET socket, const char *buffer, int length, int fla
|
||||
address = outgoing_address(address, address_length, &target);
|
||||
break;
|
||||
}
|
||||
return winsock_result(posix_socket_sendto((int)socket, buffer, length, flags, address, address_length));
|
||||
result = posix_socket_sendto((int)socket, buffer, length, flags, address, address_length);
|
||||
send_error = result < 0 ? posix_socket_last_error() : 0;
|
||||
/* the port the send gave it (one that failed binds it too) */
|
||||
if (!source_port)
|
||||
note_socket_port(socket, 0);
|
||||
if (result < 0)
|
||||
{
|
||||
WSASetLastError(send_error);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* debug.network_latency and debug.network_loss: what this machine receives
|
||||
|
||||
@@ -1228,30 +1228,35 @@ short connect_endpoint(
|
||||
do
|
||||
{
|
||||
fd_set writeable;
|
||||
fd_set failed;
|
||||
|
||||
writeable.fd_array[0] = ep->socket;
|
||||
writeable.fd_count = 1;
|
||||
failed.fd_array[0] = ep->socket;
|
||||
failed.fd_count = 1;
|
||||
|
||||
if (select(1, NULL, &writeable, NULL, &timeval) == 1)
|
||||
/* port: a connect that failed is in the error set (not
|
||||
writeable, and its would-block left as it was) */
|
||||
if (select(1, NULL, &writeable, &failed, &timeval) > 0)
|
||||
{
|
||||
error = 0;
|
||||
error = failed.fd_count ? WSAECONNREFUSED : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
error = WSAGetLastError();
|
||||
}
|
||||
|
||||
if (system_milliseconds() > timeout)
|
||||
/* port: a select that ends with the connection still being
|
||||
made leaves the connect's would-block: waited on, as in
|
||||
progress is, until the timeout (a connection made after a
|
||||
SYN sent again takes a second or three) */
|
||||
if ((error == WSAEINPROGRESS || error == WSAEWOULDBLOCK) &&
|
||||
(long)(system_milliseconds() - timeout) > 0)
|
||||
{
|
||||
error = WSAEINPROGRESS;
|
||||
closesocket(ep->socket);
|
||||
/* port: closed here, not again when the endpoint is (its
|
||||
number may be another socket's by then) */
|
||||
ep->socket = INVALID_SOCKET;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (error == WSAEINPROGRESS);
|
||||
while (error == WSAEINPROGRESS || error == WSAEWOULDBLOCK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1259,6 +1264,12 @@ short connect_endpoint(
|
||||
{
|
||||
winsock_error_to_string(error);
|
||||
result = _transport_error_connect_failed;
|
||||
/* port: none of the attempt kept (its socket half made and not
|
||||
blocking, which the next would reuse): the next makes a new one,
|
||||
and its number is not closed again when the endpoint is */
|
||||
closesocket(ep->socket);
|
||||
ep->socket = INVALID_SOCKET;
|
||||
SET_FLAG(ep->flags, _transport_endpoint_nonblocking_bit, FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -3825,6 +3825,13 @@ void game_engine_player_killed(
|
||||
killer (port/linux/game/network_distributed.c) */
|
||||
network_distributed_player_killed(&killing_player_index, &killing_object_index, dead_player_index,
|
||||
&friendly_fire);
|
||||
/* the host's kill of a player who quit, ahead of this client's clock
|
||||
(game_update_quit_players has not come to its time yet) */
|
||||
if (network_game_distributed_client() && dead_player->quit_out_of_game_time != NONE &&
|
||||
killing_player_index == dead_player_index)
|
||||
{
|
||||
dead_player->quit_out_of_game = TRUE;
|
||||
}
|
||||
dead_player->death_time = game_time_get();
|
||||
if (game_engine->player_killed_player)
|
||||
{
|
||||
|
||||
+10
-2
@@ -3850,10 +3850,15 @@ void players_update_after_game(
|
||||
telefrag_ticks = player->telefrag_timeout;
|
||||
if (telefrag_ticks >= 90)
|
||||
{
|
||||
/* a client's view of who blocks is a latency late: the
|
||||
host's kill arrives with its damage events (the message
|
||||
shown once) */
|
||||
boolean client = network_game_distributed_client();
|
||||
|
||||
if (player->unit_index != NONE)
|
||||
{
|
||||
unit = unit_get(player->unit_index);
|
||||
if (!TEST_FLAG(unit->object.damage_flags, _object_die_act_of_god_bit))
|
||||
if (client ? telefrag_ticks == 90 : !TEST_FLAG(unit->object.damage_flags, _object_die_act_of_god_bit))
|
||||
{
|
||||
if (player->local_player_index != NONE)
|
||||
{
|
||||
@@ -3870,9 +3875,12 @@ void players_update_after_game(
|
||||
}
|
||||
|
||||
player_telefrag_effect_stop(iterator.datum_index);
|
||||
unit_kill(player->unit_index);
|
||||
if (!client)
|
||||
unit_kill(player->unit_index);
|
||||
}
|
||||
}
|
||||
if (client)
|
||||
player_telefrag_effect_stop(iterator.datum_index);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -2312,11 +2312,12 @@ static struct widget_instance *ui_widget_launch_widget(
|
||||
struct widget_instance *new_widget;
|
||||
short local_player_index;
|
||||
|
||||
/* port: the multiplayer menus open only on maps of a build that plays
|
||||
multiplayer with the others (cache_files.c, cache_files_multiplayer_region);
|
||||
otherwise the player is told why, and the main menu stays */
|
||||
/* port: the menus of multiplayer with other machines (not split screen's
|
||||
or co-op's) open only on maps of a build that plays multiplayer with the
|
||||
others (cache_files.c, cache_files_multiplayer_region); otherwise the
|
||||
player is told why, and the menu stays */
|
||||
{
|
||||
static char const multiplayer_menus[] = "ui\\shell\\main_menu\\multiplayer_type_select\\";
|
||||
static char const multiplayer_menus[] = "ui\\shell\\main_menu\\multiplayer_type_select\\connected\\";
|
||||
char const *name = tag_get_name(new_widget_tag_index);
|
||||
char build[0x20];
|
||||
|
||||
|
||||
@@ -5551,7 +5551,8 @@ static boolean multiplayer_level_select(
|
||||
{
|
||||
char build[0x20];
|
||||
|
||||
if (global_network_game_server_get() && !cache_files_map_plays_multiplayer(map_name, build))
|
||||
if (global_network_game_server_get() && !network_game_is_splitscreen_local() &&
|
||||
!cache_files_map_plays_multiplayer(map_name, build))
|
||||
{
|
||||
cache_files_show_multiplayer_unavailable(map_name, build);
|
||||
return FALSE;
|
||||
|
||||
@@ -1261,9 +1261,13 @@ boolean network_game_client_game_settings_updated(
|
||||
|
||||
/* port: a map of a build this version does not play with others
|
||||
(its objects would not be the host's): said, and the game left */
|
||||
if (!cache_files_map_plays_multiplayer(message_packet->map.name, build))
|
||||
if (!network_game_is_splitscreen_local() &&
|
||||
!cache_files_map_plays_multiplayer(message_packet->map.name, build))
|
||||
{
|
||||
cache_files_show_multiplayer_unavailable(message_packet->map.name, build);
|
||||
/* (the menu's error the join's, not the connection lost that
|
||||
the failure would otherwise give) */
|
||||
display_error_when_main_menu_loaded(_error_network_failed_to_join_game);
|
||||
return FALSE;
|
||||
}
|
||||
network_event("precaching map '%s'...", message_packet->map.name);
|
||||
|
||||
@@ -1896,10 +1896,13 @@ static void network_connection_flush_reliable_last(
|
||||
{
|
||||
unsigned long start_time = system_milliseconds();
|
||||
|
||||
/* (and not at all for a peer that has taken nothing lately: gone, its
|
||||
connection dropped for it) */
|
||||
while (connection->reliable_outgoing_queue && circular_queue_size(connection->reliable_outgoing_queue) > 0 &&
|
||||
network_connection_flush_reliable(connection) &&
|
||||
circular_queue_size(connection->reliable_outgoing_queue) > 0 &&
|
||||
system_milliseconds() - start_time < 500)
|
||||
system_milliseconds() - start_time < 500 &&
|
||||
system_milliseconds() - connection->reliable_outgoing_time < 100)
|
||||
{
|
||||
Sleep(5);
|
||||
}
|
||||
|
||||
@@ -779,6 +779,71 @@ static long network_game_server_frequent_updates_until[MAXIMUM_NETWORK_MACHINE_C
|
||||
network_game_server_client_machine_may_slow_countdown) */
|
||||
static boolean network_game_server_countdown_slowed[MAXIMUM_NETWORK_MACHINE_COUNT];
|
||||
|
||||
/* port: the players added to the game in progress from each client
|
||||
machine's address (a machine joins from one address, and joining again
|
||||
drops the old: network_game_server_accept_client_machine_into_game), no
|
||||
more than MAXIMUM_INGAME_ADDITIONS_PER_ADDRESS a game, so that players
|
||||
added and removed, or a machine joining again and again, do not take
|
||||
every player of the game (a player gone stays for its scores) */
|
||||
enum
|
||||
{
|
||||
MAXIMUM_INGAME_ADDITIONS_PER_ADDRESS = 2 * MAXIMUM_PLAYERS_PER_MACHINE,
|
||||
};
|
||||
static struct
|
||||
{
|
||||
unsigned long address;
|
||||
short count;
|
||||
} network_game_server_ingame_additions[MAXIMUM_NETWORK_PLAYER_COUNT];
|
||||
|
||||
static short *network_game_server_ingame_addition_count(
|
||||
unsigned long address,
|
||||
boolean create)
|
||||
{
|
||||
long index;
|
||||
|
||||
if (!address)
|
||||
return NULL;
|
||||
for (index = 0; index < (long)NUMBEROF(network_game_server_ingame_additions); index++)
|
||||
{
|
||||
if (network_game_server_ingame_additions[index].address == address)
|
||||
return &network_game_server_ingame_additions[index].count;
|
||||
}
|
||||
/* (more addresses than players: the game is full anyway) */
|
||||
for (index = 0; create && index < (long)NUMBEROF(network_game_server_ingame_additions); index++)
|
||||
{
|
||||
if (!network_game_server_ingame_additions[index].address)
|
||||
{
|
||||
network_game_server_ingame_additions[index].address = address;
|
||||
network_game_server_ingame_additions[index].count = 0;
|
||||
return &network_game_server_ingame_additions[index].count;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* (a client machine's player queued to add in game: one refused is as one
|
||||
the game has no room for) */
|
||||
static boolean network_game_server_machine_may_add_player_ingame(
|
||||
struct network_game_server *server,
|
||||
long machine_index)
|
||||
{
|
||||
short *count;
|
||||
|
||||
if (!VALID_INDEX(machine_index, MAXIMUM_NETWORK_MACHINE_COUNT) ||
|
||||
network_game_server_client_machine_is_local(server, &server->client_machines[machine_index]))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
count = network_game_server_ingame_addition_count(network_game_server_client_machine_addresses[machine_index], FALSE);
|
||||
if (count && *count >= MAXIMUM_INGAME_ADDITIONS_PER_ADDRESS)
|
||||
{
|
||||
network_event("client machine #%ld added too many players in game", machine_index);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ---------- public code */
|
||||
|
||||
struct network_game_server *network_game_server_create(
|
||||
@@ -963,11 +1028,23 @@ static boolean network_game_server_network_lost(
|
||||
{
|
||||
static unsigned long down_time;
|
||||
static unsigned long checked_time;
|
||||
static boolean asked;
|
||||
static boolean available_asked;
|
||||
static unsigned long asked_time;
|
||||
unsigned long now = system_milliseconds();
|
||||
boolean in_game = server->state == _network_game_server_state_ingame ||
|
||||
server->state == _network_game_server_state_postgame;
|
||||
boolean available = transport_network_available();
|
||||
boolean recent = checked_time && now - checked_time <= 2000;
|
||||
boolean available;
|
||||
|
||||
/* (asked once a second: each asking lists the machine's interfaces) */
|
||||
if (!asked || now - asked_time >= 1000)
|
||||
{
|
||||
available_asked = transport_network_available();
|
||||
asked_time = now;
|
||||
asked = TRUE;
|
||||
}
|
||||
available = available_asked;
|
||||
|
||||
checked_time = now;
|
||||
if (available)
|
||||
@@ -1969,11 +2046,20 @@ void network_game_server_update_ticks(
|
||||
}
|
||||
|
||||
if (client_machine &&
|
||||
network_game_server_machine_may_add_player_ingame(server, client_machine_index) &&
|
||||
network_game_server_add_player_to_game(
|
||||
server,
|
||||
client_machine,
|
||||
&server->queued_player))
|
||||
{
|
||||
if (!network_game_server_client_machine_is_local(server, client_machine))
|
||||
{
|
||||
short *count = network_game_server_ingame_addition_count(
|
||||
network_game_server_client_machine_addresses[client_machine_index], TRUE);
|
||||
|
||||
if (count)
|
||||
(*count)++;
|
||||
}
|
||||
if (!network_game_server_send_player_joined_info_ingame(
|
||||
server,
|
||||
&server->queued_player))
|
||||
@@ -3941,6 +4027,7 @@ boolean network_game_server_reset_to_pregame(
|
||||
server->sent_start_game_message = FALSE;
|
||||
server->queued_player_valid = FALSE;
|
||||
server->waiting_player_count = 0;
|
||||
csmemset(network_game_server_ingame_additions, 0, sizeof(network_game_server_ingame_additions));
|
||||
/* Preserve January's 32-bit wrap without overflowing signed arithmetic.
|
||||
* VC7 converts the unsigned result back to the same signed bit pattern.
|
||||
*/
|
||||
|
||||
+11
-2
@@ -147,7 +147,7 @@ boolean network_damage_replaying_kill(void);
|
||||
void network_damage_player_effect(long player_index, struct damage_data const *damage, real total_damage);
|
||||
void network_damage_aftermath(long object_index, struct damage_data const *damage, unsigned long being_damaged_flags,
|
||||
real shield_damage, real body_damage, real body_damage_multiplier, short body_part, short node_index,
|
||||
short region_index, short material_index);
|
||||
short region_index, short material_index, long victim_player_index);
|
||||
|
||||
/* set while a distributed client carries out a kill it does not decide (an
|
||||
act of god: the host's word, network_distributed.c, or the world's) */
|
||||
@@ -1373,6 +1373,7 @@ void object_cause_damage(
|
||||
struct object_datum *current_object;
|
||||
struct damage_resistance_material const *damage_material;
|
||||
long current_object_index;
|
||||
long victim_player_index;
|
||||
unsigned long being_damaged_flags;
|
||||
real shield_damage;
|
||||
real body_damage;
|
||||
@@ -1790,6 +1791,13 @@ void object_cause_damage(
|
||||
}
|
||||
}
|
||||
|
||||
/* (the player of the unit, which a killing blow's aftermath
|
||||
takes from it, unit_died) */
|
||||
{
|
||||
struct unit_datum *victim = unit_try_and_get(current_object_index);
|
||||
|
||||
victim_player_index = victim ? victim->unit.player_index : NONE;
|
||||
}
|
||||
object_damage_aftermath(
|
||||
current_object_index,
|
||||
damage,
|
||||
@@ -1809,7 +1817,8 @@ void object_cause_damage(
|
||||
body_part,
|
||||
current_object_index == object_index ? node_index : NONE,
|
||||
current_object_index == object_index ? region_index : NONE,
|
||||
current_object_index == object_index ? material_index : NONE);
|
||||
current_object_index == object_index ? material_index : NONE,
|
||||
victim_player_index);
|
||||
if (TEST_FLAG(
|
||||
being_damaged_flags,
|
||||
_object_being_damaged_body_destroyed_bit))
|
||||
|
||||
Reference in new issue
Block a user