Skip to content

Commit b69aeea

Browse files
Add suffix format; Support IPv6 (#371)
Closes #371 Co-authored-by: FlorianMichael <florian.michael07@gmail.com>
1 parent f966367 commit b69aeea

File tree

49 files changed

+1175
-172
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1175
-172
lines changed

README.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,22 @@ servers in client-side mode.*
146146

147147
### How can I set the version for specific servers?:
148148

149-
- Append ._v(VERSION).viafabric.
150-
- Examples: ``minigame.example.com._v1_8.viafabric``, ``native.example.com._v-1.viafabric``
151-
, ``auto.example.com._v-2.viafabric``
149+
#### Suffix Style
150+
151+
- Append ``._v(VERSION).viafabric`` after the host, before any port.
152+
- All dots (``.``) in a given version must be replaced with underscores (``_``).
153+
- Examples:
154+
- ``minigame.example.com._v1_8.viafabric``
155+
- ``native.example.com._v-1.viafabric``
156+
- ``auto.example.com._v-2.viafabric``
157+
158+
#### Prefix Style
159+
160+
- Add ``viafabric.v(VERSION);`` before the address.
161+
- Examples:
162+
- ``viafabric.v1.8;minigame.example.com``
163+
- ``viafabric.v-1;native.example.com``
164+
- ``viafabric.v-2;auto.example.com``
152165

153166
## ViaFabricPlus
154167

build.gradle.kts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,21 @@ dependencies {
121121

122122
includeJ8("com.viaversion:viaversion:${rootProject.extra["viaver_version"]}")
123123
include("io.github.cottonmc:cotton-client-commands:1.1.0+1.15.2")
124+
125+
testImplementation("org.testng:testng:6.13.1")
124126
}
125127

126-
tasks.remapJar.configure {
127-
nestedJars.from(includeJ8)
128-
subprojects.forEach { subproject ->
129-
subproject.tasks.matching { it.name == "remapJar" }.configureEach {
130-
nestedJars.from(this)
128+
tasks {
129+
test {
130+
useTestNG()
131+
}
132+
133+
remapJar.configure {
134+
nestedJars.from(includeJ8)
135+
subprojects.forEach { subproject ->
136+
subproject.tasks.matching { it.name == "remapJar" }.configureEach {
137+
nestedJars.from(this)
138+
}
131139
}
132140
}
133141
}

src/main/java/com/viaversion/fabric/common/AddressParser.java

Lines changed: 236 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,84 +20,286 @@
2020
import com.google.common.collect.Lists;
2121
import com.google.common.primitives.Ints;
2222
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
23+
import com.viaversion.viaversion.libs.fastutil.ints.IntArrayList;
24+
import com.viaversion.viaversion.libs.fastutil.ints.IntImmutableList;
25+
import com.viaversion.viaversion.libs.fastutil.ints.IntList;
26+
import java.net.InetAddress;
27+
import java.net.InetSocketAddress;
28+
import java.net.UnknownHostException;
2329
import java.util.ArrayList;
2430
import java.util.Arrays;
2531
import java.util.List;
2632
import java.util.regex.Pattern;
33+
import java.util.stream.Collectors;
2734
import org.apache.commons.lang3.StringUtils;
35+
import org.jetbrains.annotations.NotNull;
36+
import org.jetbrains.annotations.Nullable;
37+
import org.jetbrains.annotations.VisibleForTesting;
2838

2939
// Based on VIAaaS parser
3040
public class AddressParser {
31-
public Integer protocol;
32-
public String viaSuffix;
33-
public String serverAddress;
34-
public String viaOptions;
41+
private static final String VIA = "viafabric";
42+
private static final Pattern DOT_SUFFIX = Pattern.compile("\\.$");
43+
private static final int MAX_PORT = 65535;
3544

36-
public AddressParser parse(String address) {
37-
return parse(address, "viafabric");
45+
// Retaining a list for now; not exposing it to the end consumer.
46+
// An idea tossed about is to retry the server for the given protocols.
47+
@Nullable
48+
@VisibleForTesting
49+
final IntList protocols;
50+
@NotNull
51+
private final String serverAddress;
52+
@Nullable
53+
private final Integer port;
54+
55+
private AddressParser(@NotNull String address, @Nullable Integer port) {
56+
this(address, null, port);
3857
}
3958

40-
public String getSuffixWithOptions() {
41-
if (viaOptions != null && !viaOptions.isEmpty()) {
42-
return viaOptions + "." + viaSuffix;
43-
}
44-
return viaSuffix;
59+
private AddressParser(@NotNull String address, @Nullable IntList protocols, @Nullable Integer port) {
60+
this.serverAddress = address;
61+
this.port = port;
62+
this.protocols = protocols;
63+
}
64+
65+
@NotNull
66+
public static AddressParser parse(String address) {
67+
return parse(address, VIA);
4568
}
4669

47-
public AddressParser parse(String address, String viaHostName) {
70+
@NotNull
71+
private static AddressParser parse(String address, String viaHostName) {
72+
int portIndex = address.lastIndexOf(':');
73+
Integer port = portIndex >= 0 ? Ints.tryParse(address.substring(portIndex + 1)) : null;
74+
75+
if (port != null && port >= 0 && port < MAX_PORT + 1) {
76+
if (address.charAt(portIndex - 1) == '.') {
77+
// Let's not allocate an intermediate string.
78+
portIndex -= 1;
79+
} else if (port < 10000 &&
80+
// I don't like this but there's not really a better way of doing this
81+
address.lastIndexOf(':', portIndex - 1) > Math.max(
82+
address.lastIndexOf(']', portIndex - 1),
83+
address.lastIndexOf('.', portIndex - 1)
84+
)) {
85+
// We parsed an IPv6, a port isn't ideal here.
86+
port = null;
87+
}
88+
89+
// Keeping a sane flow control.
90+
if (port != null) {
91+
// Truncate the port off, as that interferes.
92+
address = address.substring(0, portIndex);
93+
}
94+
}
95+
4896
address = StringUtils.removeEnd(address, ".");
49-
String suffixRemoved = StringUtils.removeEnd(address, "." + viaHostName);
5097

51-
if (suffixRemoved.equals(address)) {
52-
serverAddress = address;
53-
return this;
98+
String truncated = StringUtils.removeEnd(address, '.' + viaHostName);
99+
if (!address.equals(truncated)) {
100+
return parseSuffix(truncated, port);
101+
}
102+
103+
truncated = StringUtils.removeStart(address, viaHostName + '.');
104+
if (!address.equals(truncated)) {
105+
final AddressParser addr = parsePrefix(truncated, port);
106+
if (addr != null) {
107+
return addr;
108+
}
54109
}
55110

111+
return new AddressParser(address, port);
112+
}
113+
114+
@NotNull
115+
private static AddressParser parseSuffix(String address, Integer port) {
56116
boolean stopOptions = false;
57-
List<String> optionsParts = new ArrayList<>();
117+
IntList protocolParts = new IntArrayList();
58118
List<String> serverParts = new ArrayList<>();
59119

60-
for (String part : Lists.reverse(Arrays.asList(suffixRemoved.split(Pattern.quote("."))))) {
61-
if (!stopOptions && parseOption(part)) {
62-
optionsParts.add(part);
120+
Integer protocol;
121+
for (String part : Lists.reverse(Arrays.asList(address.split("\\.")))) {
122+
if (!stopOptions && (protocol = parseSuffixOption(part)) != null) {
123+
protocolParts.add(protocol.intValue());
63124
continue;
64125
}
65126
stopOptions = true;
66127
serverParts.add(part);
67128
}
68129

69-
serverAddress = String.join(".", Lists.reverse(serverParts));
70-
viaOptions = String.join(".", Lists.reverse(optionsParts));
71-
viaSuffix = viaHostName;
130+
return new AddressParser(
131+
String.join(".", Lists.reverse(serverParts)),
132+
new IntImmutableList(Lists.reverse(protocolParts)),
133+
port
134+
);
135+
}
136+
137+
// Fail condition = returns null; caller must fall through.
138+
@Nullable
139+
private static AddressParser parsePrefix(String address, Integer port) {
140+
IntList protocols = new IntArrayList();
141+
int index = 0, lastIndex, colonIndex = address.indexOf(';');
142+
143+
if (colonIndex < 0) {
144+
return null;
145+
}
146+
147+
while ((index = address.indexOf('+', lastIndex = index)) >= 0 && index < colonIndex) {
148+
parseAndAdd(address.substring(lastIndex, index), protocols);
149+
index++;
150+
}
151+
152+
parseAndAdd(address.substring(lastIndex, colonIndex), protocols);
72153

73-
return this;
154+
return new AddressParser(
155+
address.substring(colonIndex + 1),
156+
new IntImmutableList(protocols),
157+
port
158+
);
74159
}
75160

76-
public boolean parseOption(String part) {
161+
private static void parseAndAdd(String part, IntList protocols) {
162+
final Integer protocol = parseSchemeOption(part);
163+
if (protocol != null) {
164+
protocols.add(protocol.intValue());
165+
}
166+
}
167+
168+
private static Integer parseSuffixOption(String part) {
77169
String option;
78170
if (part.length() < 2) {
79-
return false;
171+
return null;
80172
} else if (part.startsWith("_")) {
81173
option = String.valueOf(part.charAt(1));
82174
} else if (part.charAt(1) == '_') {
83175
option = String.valueOf(part.charAt(0));
84176
} else {
85-
return false;
177+
return null;
86178
}
87179

88180
String arg = part.substring(2);
89181
if ("v".equals(option)) {
90-
parseProtocol(arg);
182+
return parseProtocol(arg);
183+
}
184+
185+
return null;
186+
}
187+
188+
private static Integer parseSchemeOption(String part) {
189+
if (part.length() < 2) {
190+
return null;
191+
}
192+
if (!part.startsWith("v")) {
193+
return null;
194+
}
195+
return parseProtocol(part.substring(1));
196+
}
197+
198+
private static Integer parseProtocol(String arg) {
199+
final Integer protocol = Ints.tryParse(arg);
200+
if (protocol != null) {
201+
return protocol;
202+
}
203+
ProtocolVersion ver = ProtocolVersion.getClosest(arg.replace('_', '.'));
204+
if (ver != null) {
205+
return ver.getVersion();
91206
}
207+
return null;
208+
}
209+
210+
private static String toProtocolName(int protocol) {
211+
if (protocol < 0 || !ProtocolVersion.isRegistered(protocol)) {
212+
return Integer.toString(protocol);
213+
}
214+
return ProtocolVersion.getProtocol(protocol).getIncludedVersions().iterator().next();
215+
}
216+
217+
public String getSuffixWithOptions() {
218+
if (protocols == null) {
219+
return "";
220+
}
221+
if (protocols.isEmpty()) {
222+
return VIA;
223+
}
224+
return protocols.intStream()
225+
.mapToObj(AddressParser::toProtocolName)
226+
.map(str -> str.replace('.', '_'))
227+
.collect(Collectors.joining("._v", "_v", "." + VIA));
228+
}
229+
230+
public String getPrefixWithOptions() {
231+
if (protocols == null) {
232+
return "";
233+
}
234+
if (protocols.isEmpty()) {
235+
return VIA;
236+
}
237+
return protocols.intStream()
238+
.mapToObj(AddressParser::toProtocolName)
239+
.collect(Collectors.joining("+v", VIA + ".v", ""));
240+
}
241+
242+
public boolean hasViaMetadata() {
243+
return protocols != null;
244+
}
245+
246+
public boolean hasProtocol() {
247+
return protocols != null && !protocols.isEmpty();
248+
}
249+
250+
public Integer protocol() {
251+
if (protocols != null && !protocols.isEmpty()) {
252+
return protocols.getInt(0);
253+
}
254+
return null;
255+
}
256+
257+
@NotNull
258+
public String serverAddress() {
259+
return this.serverAddress;
260+
}
261+
262+
@Nullable
263+
public Integer port() {
264+
return this.port;
265+
}
266+
267+
public String toAddress() {
268+
if (port != null) {
269+
return serverAddress + ':' + port;
270+
}
271+
return serverAddress;
272+
}
92273

93-
return true;
274+
public String toSuffixedViaAddress() {
275+
final String address = this.addAddressSuffix(serverAddress);
276+
if (port != null) {
277+
return address + ':' + port;
278+
}
279+
return address;
280+
}
281+
282+
public InetAddress resolve() throws UnknownHostException {
283+
return this.addAddressSuffix(InetAddress.getByName(serverAddress));
284+
}
285+
286+
public InetSocketAddress addAddressSuffix(InetSocketAddress address) throws UnknownHostException {
287+
return new InetSocketAddress(this.addAddressSuffix(address.getAddress()), address.getPort());
94288
}
95289

96-
public void parseProtocol(String arg) {
97-
protocol = Ints.tryParse(arg);
98-
if (protocol == null) {
99-
ProtocolVersion ver = ProtocolVersion.getClosest(arg.replace("_", "."));
100-
if (ver != null) protocol = ver.getVersion();
290+
public InetAddress addAddressSuffix(InetAddress address) throws UnknownHostException {
291+
return InetAddress.getByAddress(this.addAddressSuffix(address.getHostName()), address.getAddress());
292+
}
293+
294+
public String addAddressSuffix(String input) {
295+
if (!this.hasViaMetadata()) {
296+
return input;
101297
}
298+
return DOT_SUFFIX.matcher(input).replaceAll("") + '.' + this.getSuffixWithOptions();
299+
}
300+
301+
@Override
302+
public String toString() {
303+
return "AddressParser{" + this.toSuffixedViaAddress() + '}';
102304
}
103305
}

src/main/java/com/viaversion/fabric/common/protocol/HostnameParserProtocol.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ protected void register() {
3838
map(Types.STRING, new ValueTransformer<String, String>(Types.STRING) {
3939
@Override
4040
public String transform(PacketWrapper packetWrapper, String s) {
41-
return new AddressParser().parse(s).serverAddress;
41+
return AddressParser.parse(s).serverAddress();
4242
}
4343
});
4444
}

src/main/java/com/viaversion/fabric/common/provider/AbstractFabricVersionProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ public ProtocolVersion getClosestServerProtocol(UserConnection connection) throw
9494
SocketAddress addr = connection.getChannel().remoteAddress();
9595

9696
if (addr instanceof InetSocketAddress) {
97-
AddressParser parser = new AddressParser();
98-
Integer addrVersion = parser.parse(((InetSocketAddress) addr).getHostName()).protocol;
97+
AddressParser parser = AddressParser.parse(((InetSocketAddress) addr).getHostName());
98+
Integer addrVersion = parser.protocol();
9999
if (addrVersion != null) {
100100
serverVer = addrVersion;
101101
}

0 commit comments

Comments
 (0)