diff --git a/libraries/networking/README.md b/libraries/networking/README.md index 71591f717..a0bbba0b8 100644 --- a/libraries/networking/README.md +++ b/libraries/networking/README.md @@ -27,6 +27,13 @@ and snake case for the identifier. public static final NamespacedIdentifier COOKIE_CHANNEL = ChannelIdentifiers.from("example", "cookie"); ``` +In 1.13-pre2 and below, the game did not validate channel names, so Minecraft itself used names like `MC|Brand`. If you require a channel name with this convention, you can use the `fromLegacy` factory methods instead. + +```java +ChannelIdentifiers.from("example", "cookie"); // -> "example:cookie" +ChannelIdentifiers.fromLegacy("EXAMPLE", "Cookie"); // -> "EXAMPLE|Cookie" +``` + You are expected to register your channels through the `ChannelRegistry`. ```java diff --git a/libraries/networking/src/main/java/net/ornithemc/osl/networking/api/ChannelIdentifiers.java b/libraries/networking/src/main/java/net/ornithemc/osl/networking/api/ChannelIdentifiers.java index 55358b7d1..992d06a2e 100644 --- a/libraries/networking/src/main/java/net/ornithemc/osl/networking/api/ChannelIdentifiers.java +++ b/libraries/networking/src/main/java/net/ornithemc/osl/networking/api/ChannelIdentifiers.java @@ -7,6 +7,7 @@ import net.ornithemc.osl.core.api.util.NamespacedIdentifiers; import net.ornithemc.osl.core.impl.util.NamespacedIdentifierException; import net.ornithemc.osl.networking.impl.ChannelIdentifierException; +import net.ornithemc.osl.networking.impl.LegacyChannelIdentifierImpl; /** * Utility methods for creating and validating channel identifiers. @@ -45,6 +46,24 @@ public static NamespacedIdentifier from(String namespace, String identifier) { ); } + /** + * Construct a legacy channel identifier with the given identifier. + * + * @see LegacyChannelIdentifier + */ + public static LegacyChannelIdentifier fromLegacy(String identifier) { + return fromLegacy("", identifier); + } + + /** + * Construct a legacy channel identifier with the given namespace and identifier. + * + * @see LegacyChannelIdentifier + */ + public static LegacyChannelIdentifier fromLegacy(String namespace, String identifier) { + return new LegacyChannelIdentifierImpl(namespace, identifier); + } + /** * Check whether the given channel identifier is valid, or throw an exception. */ @@ -54,7 +73,7 @@ public static NamespacedIdentifier validate(NamespacedIdentifier id) { validateIdentifier(id.identifier()); return id; - } catch (ChannelIdentifierException e) { + } catch (ChannelIdentifierException | NamespacedIdentifierException e) { throw ChannelIdentifierException.invalid(id, e); } } @@ -63,15 +82,9 @@ public static NamespacedIdentifier validate(NamespacedIdentifier id) { * Check that the given namespace is valid for a channel identifier. */ public static String validateNamespace(String namespace) { - if (namespace == null || namespace.isEmpty()) { - throw ChannelIdentifierException.invalidNamespace(namespace, "null or empty"); - } if (namespace.length() > MAX_LENGTH_NAMESPACE) { throw ChannelIdentifierException.invalidNamespace(namespace, "length " + namespace.length() + " is greater than maximum allowed " + MAX_LENGTH_NAMESPACE); } - if (!namespace.chars().allMatch(chr -> chr == '-' || chr == '.' || chr == '_' || (chr >= 'a' && chr <= 'z') || (chr >= '0' && chr <= '9'))) { - throw ChannelIdentifierException.invalidNamespace(namespace, "contains illegal characters - only [a-z0-9-._] are allowed"); - } return NamespacedIdentifiers.validateNamespace(namespace); } @@ -80,15 +93,9 @@ public static String validateNamespace(String namespace) { * Check that the given identifier is valid for a channel identifier. */ public static String validateIdentifier(String identifier) { - if (identifier == null || identifier.isEmpty()) { - throw ChannelIdentifierException.invalidIdentifier(identifier, "null or empty"); - } if (identifier.length() > MAX_LENGTH_IDENTIFIER) { throw ChannelIdentifierException.invalidIdentifier(identifier, "length " + identifier.length() + " is greater than maximum allowed " + MAX_LENGTH_IDENTIFIER); } - if (!identifier.chars().allMatch(chr -> chr == '-' || chr == '.' || chr == '_' || chr == '/' || (chr >= 'a' && chr <= 'z') || (chr >= '0' && chr <= '9'))) { - throw ChannelIdentifierException.invalidIdentifier(identifier, "contains illegal characters - only [a-z0-9-._/] are allowed"); - } return NamespacedIdentifiers.validateIdentifier(identifier); } @@ -99,7 +106,7 @@ public static Set dropInvalid(Set ch .filter(channel -> { try { return ChannelIdentifiers.validate(channel) != null; - } catch (ChannelIdentifierException | NamespacedIdentifierException e) { + } catch (ChannelIdentifierException e) { return false; } }) diff --git a/libraries/networking/src/main/java/net/ornithemc/osl/networking/api/LegacyChannelIdentifier.java b/libraries/networking/src/main/java/net/ornithemc/osl/networking/api/LegacyChannelIdentifier.java new file mode 100644 index 000000000..cd7abc88c --- /dev/null +++ b/libraries/networking/src/main/java/net/ornithemc/osl/networking/api/LegacyChannelIdentifier.java @@ -0,0 +1,23 @@ +package net.ornithemc.osl.networking.api; + +import net.ornithemc.osl.core.api.util.NamespacedIdentifier; + +/** + * A {@linkplain NamespacedIdentifier} implementation that represents a packet + * channel in the legacy format used in 1.13-pre2 and below. The legacy format + * uses the vertical bar character ({@code '|'}) as the separator, but that is + * only by convention, as no format is strictly enforced, and any character in + * the UTF-8 character set is allowed. As such, strings like {@code "MC|Brand"} + * and {@code "REGISTER"} are valid channel identifiers in the legacy format. + */ +public interface LegacyChannelIdentifier extends NamespacedIdentifier { + + char SEPARATOR = '|'; + + @Override + LegacyChannelIdentifier prefixed(String prefix); + + @Override + LegacyChannelIdentifier suffixed(String suffix); + +} diff --git a/libraries/networking/src/main/java/net/ornithemc/osl/networking/api/StringChannelIdentifierParser.java b/libraries/networking/src/main/java/net/ornithemc/osl/networking/api/StringChannelIdentifierParser.java index 35b2fa805..aee6c4aaf 100644 --- a/libraries/networking/src/main/java/net/ornithemc/osl/networking/api/StringChannelIdentifierParser.java +++ b/libraries/networking/src/main/java/net/ornithemc/osl/networking/api/StringChannelIdentifierParser.java @@ -2,8 +2,8 @@ import net.ornithemc.osl.core.api.util.NamespacedIdentifier; import net.ornithemc.osl.core.impl.util.NamespacedIdentifierImpl; -import net.ornithemc.osl.networking.impl.ChannelIdentifierException; import net.ornithemc.osl.networking.impl.ChannelIdentifierParseException; +import net.ornithemc.osl.networking.impl.LegacyChannelIdentifierImpl; /** * Utility methods for converting channel identifiers from and to {@link String}s. @@ -23,15 +23,20 @@ public final class StringChannelIdentifierParser { * which may or may not be a valid identifier. */ public static NamespacedIdentifier fromString(String s) { - int i = s.indexOf('|'); + int i = s.indexOf(LegacyChannelIdentifier.SEPARATOR); - if (i < 1) { - // allow null namespaces to support channel ids that do not conform - // to OSL spec - MC did not enforce a strict spec before 1.13 - return new NamespacedIdentifierImpl("", s); - } else { - return new NamespacedIdentifierImpl(s.substring(0, i), s.substring(i + 1)); + if (i > 0) { + return new LegacyChannelIdentifierImpl(s.substring(0, i), s.substring(i + 1)); } + + i = s.indexOf(NamespacedIdentifier.SEPARATOR); + + if (i < 0) { + // allow empty namespaces since MC did not enforce a strict spec before 1.13 + return new LegacyChannelIdentifierImpl("", s); + } + + return new NamespacedIdentifierImpl(s.substring(0, i), s.substring(i + 1)); } /** @@ -45,27 +50,26 @@ public static NamespacedIdentifier fromString(String s) { * if no valid channel identifier can be parsed from the given {@code String}. */ public static NamespacedIdentifier fromStringOrThrow(String s) { - int i = s.indexOf('|'); + int i = s.indexOf(LegacyChannelIdentifier.SEPARATOR); - try { - if (i < 0) { - return ChannelIdentifiers.from(s); - } else if (i > 0) { - return ChannelIdentifiers.from(s.substring(0, i), s.substring(i + 1)); - } else { - throw ChannelIdentifierParseException.invalid(s, "badly formatted"); - } - } catch (ChannelIdentifierException e) { - throw ChannelIdentifierParseException.invalid(s, e); + if (i > 0) { + return new LegacyChannelIdentifierImpl(s.substring(0, i), s.substring(i + 1)); } + + i = s.indexOf(NamespacedIdentifier.SEPARATOR); + + if (i < 0) { + // allow empty namespaces since MC did not enforce a strict spec before 1.13 + return new LegacyChannelIdentifierImpl("", s); + } + + return ChannelIdentifiers.from(s.substring(0, i), s.substring(i + 1)); } /** * Convert the given {@code NamespacedIdentifier} to its {@code String} representation. */ public static String toString(NamespacedIdentifier id) { - return id.namespace().isEmpty() - ? id.identifier() - : id.namespace() + "|" + id.identifier(); + return id.toString(); } } diff --git a/libraries/networking/src/main/java/net/ornithemc/osl/networking/impl/LegacyChannelIdentifierImpl.java b/libraries/networking/src/main/java/net/ornithemc/osl/networking/impl/LegacyChannelIdentifierImpl.java new file mode 100644 index 000000000..427e8bdab --- /dev/null +++ b/libraries/networking/src/main/java/net/ornithemc/osl/networking/impl/LegacyChannelIdentifierImpl.java @@ -0,0 +1,57 @@ +package net.ornithemc.osl.networking.impl; + +import net.ornithemc.osl.core.api.util.NamespacedIdentifier; +import net.ornithemc.osl.core.api.util.NamespacedIdentifiers; +import net.ornithemc.osl.networking.api.LegacyChannelIdentifier; + +public final class LegacyChannelIdentifierImpl implements LegacyChannelIdentifier { + + private final String namespace; + private final String identifier; + + public LegacyChannelIdentifierImpl(String namespace, String identifier) { + this.namespace = namespace; + this.identifier = identifier; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof LegacyChannelIdentifier)) { + return false; + } + return NamespacedIdentifiers.equals(this, (LegacyChannelIdentifier) o); + } + + @Override + public int hashCode() { + return 31 * namespace.hashCode() + identifier.hashCode(); + } + + @Override + public String toString() { + return namespace.isEmpty() ? identifier : (namespace + SEPARATOR + identifier); + } + + @Override + public String namespace() { + return namespace; + } + + @Override + public String identifier() { + return identifier; + } + + @Override + public LegacyChannelIdentifier prefixed(String prefix) { + return new LegacyChannelIdentifierImpl(namespace, prefix + identifier); + } + + @Override + public LegacyChannelIdentifier suffixed(String suffix) { + return new LegacyChannelIdentifierImpl(namespace, identifier + suffix); + } +}