Skip to content
This repository was archived by the owner on Jun 3, 2024. It is now read-only.

Commit e7bc269

Browse files
committed
Big refactor to network version negotiation
1 parent c9545a4 commit e7bc269

5 files changed

Lines changed: 234 additions & 148 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.patchworkmc.api.networking;
2+
3+
import net.minecraft.util.Identifier;
4+
5+
public interface Channel {
6+
Identifier getChannelName();
7+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package com.patchworkmc.impl.networking;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collection;
5+
import java.util.Collections;
6+
import java.util.HashMap;
7+
import java.util.HashSet;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Set;
11+
import java.util.function.BiPredicate;
12+
import java.util.function.Function;
13+
import java.util.stream.Collectors;
14+
import java.util.stream.StreamSupport;
15+
16+
import net.minecraftforge.fml.network.NetworkInstance;
17+
import net.minecraftforge.fml.network.NetworkRegistry;
18+
import org.apache.commons.lang3.tuple.Pair;
19+
import org.apache.logging.log4j.LogManager;
20+
import org.apache.logging.log4j.Logger;
21+
import org.apache.logging.log4j.Marker;
22+
import org.apache.logging.log4j.MarkerManager;
23+
24+
import net.minecraft.util.Identifier;
25+
26+
public final class NetworkVersionManager {
27+
private static final Logger LOGGER = LogManager.getLogger();
28+
private static final Marker NETREGISTRY = MarkerManager.getMarker("NETREGISTRY");
29+
public static final String ABSENT = "ABSENT \uD83E\uDD14";
30+
public static final String ACCEPTVANILLA = "ALLOWVANILLA \uD83D\uDC93\uD83D\uDC93\uD83D\uDC93";
31+
32+
Iterable<NetworkInstance> channels;
33+
34+
public NetworkVersionManager(Collection<NetworkInstance> channels) {
35+
this.channels = channels;
36+
}
37+
38+
/**
39+
* Construct the Map representation of the channel list, for use during login handshaking.
40+
*
41+
* @see FMLHandshakeMessages.S2CModList
42+
* @see FMLHandshakeMessages.C2SModListReply
43+
*/
44+
public Map<Identifier, String> buildChannelVersions() {
45+
Map<Identifier, String> channelVersions = new HashMap<>();
46+
47+
for (VersionedChannel channel: channels) {
48+
channelVersions.put(channel.getChannelName(), channel.getNetworkProtocolVersion());
49+
}
50+
51+
return channelVersions;
52+
}
53+
54+
/**
55+
* Construct the Map representation of the channel list, for the client to check against during list ping.
56+
*
57+
* @see FMLHandshakeMessages.S2CModList
58+
* @see FMLHandshakeMessages.C2SModListReply
59+
*/
60+
public Map<Identifier, Pair<String, Boolean>> buildChannelVersionsForListPing() {
61+
Map<Identifier, Pair<String, Boolean>> channelVersions = new HashMap<>();
62+
63+
for (VersionedChannel channel: channels) {
64+
Identifier name = channel.getChannelName();
65+
66+
if(name.getNamespace().equals("fml")) {
67+
continue;
68+
}
69+
70+
Pair<String, Boolean> version = Pair.of(channel.getNetworkProtocolVersion(), channel.tryClientVersionOnServer(NetworkRegistry.ABSENT));
71+
72+
channelVersions.put(name, version);
73+
}
74+
75+
return channelVersions;
76+
}
77+
78+
public List<String> getServerNonVanillaNetworkMods() {
79+
return validateChannels(identifier -> NetworkRegistry.ACCEPTVANILLA, Origin.VANILLA, VersionedChannel::tryClientVersionOnServer);
80+
}
81+
82+
public List<String> getClientNonVanillaNetworkMods() {
83+
return validateChannels(identifier -> NetworkRegistry.ACCEPTVANILLA, Origin.VANILLA, VersionedChannel::tryServerVersionOnClient);
84+
}
85+
86+
/**
87+
* Validate the channels from the server on the client. Tests the client predicates against the server
88+
* supplied network protocol version.
89+
*
90+
* @param channels An @{@link Map} of name->version pairs for testing
91+
* @return true if all channels accept themselves
92+
*/
93+
public boolean validateClientChannels(final Map<Identifier, String> channels) {
94+
return validateChannels(channels::get, Origin.SERVER, VersionedChannel::tryServerVersionOnClient).isEmpty();
95+
}
96+
97+
/**
98+
* Validate the channels from the client on the server. Tests the server predicates against the client
99+
* supplied network protocol version.
100+
* @param channels An @{@link Map} of name->version pairs for testing
101+
* @return true if all channels accept themselves
102+
*/
103+
public boolean validateServerChannels(final Map<Identifier, String> channels) {
104+
return validateChannels(channels::get, Origin.CLIENT, VersionedChannel::tryClientVersionOnServer).isEmpty();
105+
}
106+
107+
/**
108+
* Tests if the map matches with the supplied predicate tester.
109+
*
110+
* @param incoming An @{@link Function} of name->version pairs for testing. It should return null for missing versions.
111+
* @param origin A label for use in logging (where the version pairs came from)
112+
* @param predicate A predicate to test whether a version satisfies the requirement
113+
* @return a list of the channels that rejected the version check
114+
*/
115+
private List<String> validateChannels(final Function<Identifier, String> incoming, final Origin origin, BiPredicate<VersionedChannel, String> predicate) {
116+
List<String> rejected = new ArrayList<>();
117+
118+
for (VersionedChannel channel: channels) {
119+
final String incomingVersion = incoming.apply(channel.getChannelName());
120+
final boolean accepted = predicate.test(channel, incomingVersion != null ? incomingVersion : NetworkRegistry.ABSENT);
121+
122+
if(origin == Origin.VANILLA) {
123+
LOGGER.debug(NETREGISTRY, "Channel '{}' : Vanilla acceptance test: {}", channel.getChannelName(), accepted ? "ACCEPTED" : "REJECTED");
124+
} else {
125+
LOGGER.debug(NETREGISTRY, "Channel '{}' : Version test of '{}' from {} : {}", channel.getChannelName(), incomingVersion, origin, accepted ? "ACCEPTED" : "REJECTED");
126+
}
127+
128+
if(!accepted) {
129+
rejected.add(channel.getChannelName().toString());
130+
}
131+
}
132+
133+
if (!rejected.isEmpty()) {
134+
if(origin == Origin.VANILLA) {
135+
LOGGER.error(NETREGISTRY, "Channels {} rejected vanilla connections", rejected);
136+
} else {
137+
LOGGER.error(NETREGISTRY, "Channels {} rejected their {} version number", rejected, origin);
138+
}
139+
140+
return rejected;
141+
}
142+
143+
LOGGER.debug(NETREGISTRY, "Accepting channel list from {}", origin);
144+
145+
return Collections.emptyList();
146+
}
147+
148+
public boolean checkListPingCompatibilityForClient(Map<Identifier, Pair<String, Boolean>> incoming) {
149+
Set<Identifier> handled = new HashSet<>();
150+
final List<Pair<Identifier, Boolean>> results = StreamSupport.stream(channels.spliterator(), false)
151+
.filter(p -> !p.getChannelName().getNamespace().equals("fml"))
152+
.map(ni -> {
153+
final Pair<String, Boolean> incomingVersion = incoming.getOrDefault(ni.getChannelName(), Pair.of(ABSENT, true));
154+
final boolean test = ni.tryServerVersionOnClient(incomingVersion.getLeft());
155+
handled.add(ni.getChannelName());
156+
LOGGER.debug(NETREGISTRY, "Channel '{}' : Version test of '{}' during listping : {}", ni.getChannelName(), incomingVersion, test ? "ACCEPTED" : "REJECTED");
157+
return Pair.of(ni.getChannelName(), test);
158+
}).filter(p -> !p.getRight()).collect(Collectors.toList());
159+
final List<Identifier> missingButRequired = incoming.entrySet().stream()
160+
.filter(p -> !p.getKey().getNamespace().equals("fml"))
161+
.filter(p -> !p.getValue().getRight())
162+
.filter(p -> !handled.contains(p.getKey()))
163+
.map(Map.Entry::getKey)
164+
.collect(Collectors.toList());
165+
166+
if (!results.isEmpty()) {
167+
LOGGER.error(NETREGISTRY, "Channels [{}] rejected their server side version number during listping",
168+
results.stream().map(Pair::getLeft).map(Object::toString).collect(Collectors.joining(",")));
169+
return false;
170+
}
171+
172+
if (!missingButRequired.isEmpty()) {
173+
LOGGER.error(NETREGISTRY, "The server is likely to require channel [{}] to be present, yet we don't have it",
174+
missingButRequired);
175+
return false;
176+
}
177+
178+
LOGGER.debug(NETREGISTRY, "Accepting channel list during listping");
179+
return true;
180+
}
181+
182+
public enum Origin {
183+
CLIENT, SERVER, VANILLA
184+
}
185+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.patchworkmc.impl.networking;
2+
3+
import com.patchworkmc.api.networking.Channel;
4+
5+
public interface VersionedChannel extends Channel {
6+
String getNetworkProtocolVersion();
7+
boolean tryServerVersionOnClient(final String serverVersion);
8+
boolean tryClientVersionOnServer(final String clientVersion);
9+
}

patchwork-networking/src/main/java/net/minecraftforge/fml/network/NetworkInstance.java

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@
3232
import net.minecraft.network.ClientConnection;
3333
import net.minecraft.util.Identifier;
3434

35-
public class NetworkInstance {
35+
import com.patchworkmc.api.networking.Channel;
36+
import com.patchworkmc.impl.networking.VersionedChannel;
37+
38+
public class NetworkInstance implements Channel, VersionedChannel {
3639
private final Identifier channelName;
3740
private final String networkProtocolVersion;
3841
private final Predicate<String> clientAcceptedVersions;
@@ -47,6 +50,7 @@ public class NetworkInstance {
4750
this.networkEventBus = BusBuilder.builder().setExceptionHandler(this::handleError).build();
4851
}
4952

53+
@Override
5054
public Identifier getChannelName() {
5155
return channelName;
5256
}
@@ -80,27 +84,32 @@ boolean dispatch(final ICustomPacket<?> packet, final ClientConnection connectio
8084
return context.getPacketHandled();
8185
}
8286

83-
String getNetworkProtocolVersion() {
84-
return networkProtocolVersion;
87+
void dispatchGatherLogin(final List<NetworkRegistry.LoginPayload> loginPayloadList, boolean isLocal) {
88+
this.networkEventBus.post(new NetworkEvent.GatherLoginPayloadsEvent(loginPayloadList, isLocal));
8589
}
8690

87-
boolean tryServerVersionOnClient(final String serverVersion) {
88-
return this.clientAcceptedVersions.test(serverVersion);
91+
void dispatchLoginPacket(final NetworkEvent.LoginPayloadEvent loginPayloadEvent) {
92+
this.networkEventBus.post(loginPayloadEvent);
8993
}
9094

91-
boolean tryClientVersionOnServer(final String clientVersion) {
92-
return this.serverAcceptedVersions.test(clientVersion);
95+
void dispatchChannelRegistrationChange(final NetworkEvent.ChannelRegistrationChangeEvent registrationChangeEvent) {
96+
this.networkEventBus.post(registrationChangeEvent);
9397
}
9498

95-
void dispatchGatherLogin(final List<NetworkRegistry.LoginPayload> loginPayloadList, boolean isLocal) {
96-
this.networkEventBus.post(new NetworkEvent.GatherLoginPayloadsEvent(loginPayloadList, isLocal));
99+
// VersionedChannel implementations
100+
101+
@Override
102+
public String getNetworkProtocolVersion() {
103+
return networkProtocolVersion;
97104
}
98105

99-
void dispatchLoginPacket(final NetworkEvent.LoginPayloadEvent loginPayloadEvent) {
100-
this.networkEventBus.post(loginPayloadEvent);
106+
@Override
107+
public boolean tryServerVersionOnClient(final String serverVersion) {
108+
return this.clientAcceptedVersions.test(serverVersion);
101109
}
102110

103-
void dispatchEvent(final NetworkEvent networkEvent) {
104-
this.networkEventBus.post(networkEvent);
111+
@Override
112+
public boolean tryClientVersionOnServer(final String clientVersion) {
113+
return this.serverAcceptedVersions.test(clientVersion);
105114
}
106115
}

0 commit comments

Comments
 (0)