blob: f14455109049f78573e8b7f1129e985fa8cdc5b9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tuweni.devp2p
import org.apache.tuweni.rlp.RLPException
import org.apache.tuweni.rlp.RLPReader
import org.apache.tuweni.rlp.RLPWriter
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.UnknownHostException
private fun parseInetAddress(address: String): InetAddress {
require(Character.digit(address[0], 16) != -1 || address[0] == ':') {
"address should be a literal IP address, got $address"
}
return InetAddress.getByName(address)
}
/**
* An Ethereum node endpoint.
*
* @constructor Create a new endpoint.
* @param address the InetAddress
* @param udpPort the UDP port for the endpoint
* @param tcpPort the TCP port for the endpoint or `null` if no TCP port is known
* @throws IllegalArgumentException if either port is out of range
*/
data class Endpoint(
val address: InetAddress,
val udpPort: Int = DEFAULT_PORT,
val tcpPort: Int? = null
) {
/**
* Create a new endpoint.
*
* @param address the IP string literal
* @param udpPort the UDP port for the endpoint
* @param tcpPort the TCP port for the endpoint or `null` if no TCP port is known
* @throws IllegalArgumentException if the address isn't an IP address, or either port is out of range
*/
constructor(address: String, udpPort: Int = DEFAULT_PORT, tcpPort: Int? = null) :
this(parseInetAddress(address), udpPort, tcpPort)
/**
* Create a new endpoint.
*
* @param address an InetSocketAddress, containing the IP address the UDP port
*/
constructor(address: InetSocketAddress, tcpPort: Int? = null) : this(address.address, address.port, tcpPort)
companion object {
/**
* The default port used by Ethereum DevP2P.
*/
const val DEFAULT_PORT = 30303
/**
* Create an Endpoint by reading fields from the RLP input stream.
*
* If the fields are wrapped into an RLP list, use `reader.readList` to unwrap before calling this method.
*
* @param reader the RLP input stream from which to read
* @return the decoded endpoint
* @throws RLPException if the RLP source does not decode to a valid endpoint
*/
fun readFrom(reader: RLPReader): Endpoint {
val addr: InetAddress
try {
addr = InetAddress.getByAddress(reader.readValue().toArrayUnsafe())
} catch (e: UnknownHostException) {
throw RLPException(e)
}
val udpPort = reader.readInt()
// Some implementations seem to send packets that either do not have the TCP port field, or to have an
// RLP NULL value for it.
var tcpPort: Int? = null
if (!reader.isComplete) {
tcpPort = reader.readInt()
if (tcpPort == 0) {
tcpPort = null
}
}
return Endpoint(addr.hostAddress, udpPort, tcpPort)
}
}
init {
require(udpPort in 1..65535) { "udpPort should be between 1 and 65535, got $udpPort" }
require(tcpPort == null || tcpPort in 1..65535) { "tcpPort should be between 1 and 65535, got $tcpPort" }
}
val udpSocketAddress: InetSocketAddress = InetSocketAddress(address, udpPort)
val tcpSocketAddress: InetSocketAddress? = if (tcpPort != null) InetSocketAddress(address, tcpPort) else null
/**
* Write this endpoint to an RLP output.
*
* @param writer the RLP writer
*/
internal fun writeTo(writer: RLPWriter) {
writer.writeByteArray(address.address)
writer.writeInt(udpPort)
writer.writeInt(tcpPort ?: 0)
}
// rough over-estimate, assuming maximum size encoding for the port numbers
internal fun rlpSize(): Int = 1 + address.address.size + 2 * (1 + 2)
}