Update PeerAddress.java

* Added validation for ports with universal fixed range between 1 and 65535.
* Detailed exception messages improve debugging when invalid data is provided.
* Simplified and enforced consistent bracket handling for IPv6 addresses.
* Throws a clear UnknownHostException if the address cannot be resolved.
* Simplified the equality logic while ensuring proper null checks.
* Improve logging logic to handle better dubugs.
This commit is contained in:
cwd.systems | 0KN 2024-11-27 17:18:04 +06:00 committed by GitHub
parent 8ffb0625a1
commit 4f2c71b82a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -9,107 +9,116 @@ import javax.xml.bind.annotation.XmlAccessorType;
import java.net.*; import java.net.*;
/** /**
* Convenience class for encapsulating/parsing/rendering/converting peer addresses * Encapsulates parsing, rendering, and resolving peer addresses,
* including late-stage resolving before actual use by a socket. * including late-stage resolving before actual use by a socket.
*/ */
// All properties to be converted to JSON via JAXB
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
public class PeerAddress { public class PeerAddress {
// Properties // Properties
private String host; private String host; // Hostname or IP address (bracketed if IPv6)
private int port; private int port;
// Private constructor to enforce factory usage
private PeerAddress(String host, int port) { private PeerAddress(String host, int port) {
if (host == null || host.isEmpty()) {
throw new IllegalArgumentException("Host cannot be null or empty");
}
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("Port must be between 1 and 65535");
}
this.host = host; this.host = host;
this.port = port; this.port = port;
} }
// Constructors // Default constructor for JAXB
// For JAXB
protected PeerAddress() { protected PeerAddress() {
} }
/** Constructs new PeerAddress using remote address from passed connected socket. */ // Factory Methods
/**
* Constructs a PeerAddress from a connected socket.
*
* @param socket the connected socket
* @return the PeerAddress
*/
public static PeerAddress fromSocket(Socket socket) { public static PeerAddress fromSocket(Socket socket) {
InetSocketAddress socketAddress = (InetSocketAddress) socket.getRemoteSocketAddress(); InetSocketAddress socketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
InetAddress address = socketAddress.getAddress(); InetAddress address = socketAddress.getAddress();
String host = InetAddresses.toAddrString(address); String host = InetAddresses.toAddrString(address);
// Make sure we encapsulate IPv6 addresses in brackets // Ensure IPv6 addresses are properly bracketed
if (address instanceof Inet6Address) if (address instanceof Inet6Address) {
host = "[" + host + "]"; host = "[" + host + "]";
}
return new PeerAddress(host, socketAddress.getPort()); return new PeerAddress(host, socketAddress.getPort());
} }
/** /**
* Constructs new PeerAddress using hostname or literal IP address and optional port.<br> * Constructs a PeerAddress from a string containing hostname/IP and optional port.
* Literal IPv6 addresses must be enclosed within square brackets. * IPv6 addresses must be enclosed in brackets.
* <p> *
* Examples: * @param addressString the address string
* <ul> * @return the PeerAddress
* <li>peer.example.com * @throws IllegalArgumentException if the input is invalid
* <li>peer.example.com:9084
* <li>192.0.2.1
* <li>192.0.2.1:9084
* <li>[2001:db8::1]
* <li>[2001:db8::1]:9084
* </ul>
* <p>
* Not allowed:
* <ul>
* <li>2001:db8::1
* <li>2001:db8::1:9084
* </ul>
*/ */
public static PeerAddress fromString(String addressString) throws IllegalArgumentException { public static PeerAddress fromString(String addressString) {
boolean isBracketed = addressString.startsWith("["); boolean isBracketed = addressString.startsWith("[");
// Attempt to parse string into host and port // Parse the host and port
HostAndPort hostAndPort = HostAndPort.fromString(addressString).withDefaultPort(Settings.getInstance().getDefaultListenPort()).requireBracketsForIPv6(); HostAndPort hostAndPort = HostAndPort.fromString(addressString)
.withDefaultPort(Settings.getInstance().getDefaultListenPort())
.requireBracketsForIPv6();
String host = hostAndPort.getHost(); String host = hostAndPort.getHost();
if (host.isEmpty())
throw new IllegalArgumentException("Empty host part");
// Validate IP literals by attempting to convert to InetAddress, without DNS lookups // Validate host as IP literal or hostname
if (host.contains(":") || host.matches("[0-9.]+")) if (host.contains(":") || host.matches("[0-9.]+")) {
InetAddresses.forString(host); try {
InetAddresses.forString(host); // Validate IP literal
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid IP literal: " + host, e);
}
}
// If we've reached this far then we have a valid address // Enforce IPv6 brackets for consistency
if (isBracketed) {
// Make sure we encapsulate IPv6 addresses in brackets
if (isBracketed)
host = "[" + host + "]"; host = "[" + host + "]";
}
return new PeerAddress(host, hostAndPort.getPort()); return new PeerAddress(host, hostAndPort.getPort());
} }
// Getters // Getters
/** Returns hostname or literal IP address, bracketed if IPv6 */ /** @return the hostname or IP address (bracketed if IPv6) */
public String getHost() { public String getHost() {
return this.host; return this.host;
} }
/** @return the port number */
public int getPort() { public int getPort() {
return this.port; return this.port;
} }
// Conversions // Conversions
/** Returns InetSocketAddress for use with Socket.connect(), or throws UnknownHostException if address could not be resolved by DNS lookup. */ /**
* Converts this PeerAddress to an InetSocketAddress, performing DNS resolution if necessary.
*
* @return the InetSocketAddress
* @throws UnknownHostException if the host cannot be resolved
*/
public InetSocketAddress toSocketAddress() throws UnknownHostException { public InetSocketAddress toSocketAddress() throws UnknownHostException {
// Attempt to construct new InetSocketAddress with DNS lookups.
// There's no control here over whether IPv6 or IPv4 will be used.
InetSocketAddress socketAddress = new InetSocketAddress(this.host, this.port); InetSocketAddress socketAddress = new InetSocketAddress(this.host, this.port);
// If we couldn't resolve then return null if (socketAddress.isUnresolved()) {
if (socketAddress.isUnresolved()) throw new UnknownHostException("Unable to resolve host: " + this.host);
throw new UnknownHostException(); }
return socketAddress; return socketAddress;
} }
@ -121,14 +130,18 @@ public class PeerAddress {
// Utilities // Utilities
/** Returns true if other PeerAddress has same port and same case-insensitive host part, without DNS lookups */ /**
* Checks if another PeerAddress is equal to this one.
*
* @param other the other PeerAddress
* @return true if they are equal, false otherwise
*/
public boolean equals(PeerAddress other) { public boolean equals(PeerAddress other) {
// Ports must match if (other == null) {
if (this.port != other.port)
return false; return false;
// Compare host parts but without DNS lookups
return this.host.equalsIgnoreCase(other.host);
} }
// Ports must match, and hostnames/IPs must be case-insensitively equal
return this.port == other.port && this.host.equalsIgnoreCase(other.host);
}
} }