blob: 085c95c0654c97d29582fc0c08410eb3863e1f26 [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.discovery
import org.apache.tuweni.bytes.Bytes
import org.apache.tuweni.crypto.SECP256K1
import org.apache.tuweni.devp2p.EthereumNodeRecord
import org.apache.tuweni.io.Base32
import org.apache.tuweni.io.Base64URLSafe
import org.apache.tuweni.rlp.InvalidRLPEncodingException
import java.net.URI
/**
* Intermediate format to write DNS entries
*/
public interface DNSEntry {
companion object {
/**
* Read a DNS entry from a String.
* @param serialized the serialized form of a DNS entry
* @return DNS entry if found
* @throws InvalidEntryException if the record cannot be read
*/
fun readDNSEntry(serialized: String): DNSEntry {
var record = serialized
if (record[0] == '"') {
record = record.substring(1, record.length - 1)
}
if (record.startsWith("enrtree-root:")) {
return ENRTreeRoot(readKV(record))
} else if (record.startsWith("enrtree-branch:")) {
return ENRTree(record.substring("enrtree-branch:".length))
} else if (record.startsWith("enr:")) {
return ENRNode(readKV(record))
} else if (record.startsWith("enrtree:")) {
return ENRTreeLink(record)
} else {
throw InvalidEntryException("$serialized should contain enrtree-branch, enr, enrtree-root or enrtree")
}
}
private fun readKV(record: String): Map<String, String> {
return record.split(" ").map {
val equalseparator = it.indexOf("=")
if (equalseparator == -1) {
val colonseparator = it.indexOf(":")
if (colonseparator == -1) {
throw InvalidEntryException("$it could not be read")
}
val key = it.substring(0, colonseparator)
val value = it.substring(colonseparator + 1)
Pair(key, value)
} else {
val key = it.substring(0, equalseparator)
val value = it.substring(equalseparator + 1)
Pair(key, value)
}
}.toMap()
}
}
}
class ENRNode(attrs: Map<String, String>) : DNSEntry {
val nodeRecord: EthereumNodeRecord
init {
if (attrs["enr"] == null) {
throw InvalidEntryException("Missing attributes on enr entry")
}
try {
nodeRecord = EthereumNodeRecord.fromRLP(Base64URLSafe.decode(attrs["enr"]!!))
} catch (e: InvalidRLPEncodingException) {
throw InvalidEntryException(e.message)
}
}
override fun toString(): String {
return nodeRecord.toString()
}
}
class ENRTreeRoot(attrs: Map<String, String>) : DNSEntry {
val version: String
val seq: Long
val sig: SECP256K1.Signature
val enrRoot: String
val linkRoot: String
init {
if (attrs["enrtree-root"] == null || attrs["seq"] == null || attrs["sig"] == null || attrs["e"] == null ||
attrs["l"] == null) {
throw InvalidEntryException("Missing attributes on root entry")
}
version = attrs["enrtree-root"]!!
seq = attrs["seq"]!!.toLong()
val sigBytes = Base64URLSafe.decode(attrs["sig"]!!)
sig = SECP256K1.Signature.fromBytes(Bytes.concatenate(sigBytes,
Bytes.wrap(ByteArray(Math.max(0, 65 - sigBytes.size())))))
enrRoot = attrs["e"]!!
linkRoot = attrs["l"]!!
}
override fun toString(): String {
return "enrtree-root:$version e=$enrRoot " +
"l=$linkRoot seq=$seq sig=${Base64URLSafe.encode(sig.bytes())}"
}
fun signedContent(): String {
return "enrtree-root:$version e=$enrRoot l=$linkRoot seq=$seq"
}
}
class ENRTree(entriesAsString: String) : DNSEntry {
val entries: List<String>
init {
entries = entriesAsString.split(",", "\"").filter { it.length > 4 }
}
override fun toString(): String {
return "enrtree-branch:${entries.joinToString(",")}"
}
}
class ENRTreeLink(url: String) : DNSEntry {
val domainName: String
val pubKey: String
init {
val uri = URI.create(url)
domainName = uri.host
pubKey = uri.userInfo
}
fun publicKey(): SECP256K1.PublicKey {
val keyBytes = Base32.decodeBytes(pubKey)
val ecPoint = SECP256K1.Parameters.CURVE.getCurve().decodePoint(keyBytes)
return SECP256K1.PublicKey.fromBytes(Bytes.wrap(ecPoint.getEncoded(false)).slice(1))
}
override fun toString(): String {
return "enrtree://$pubKey@$domainName"
}
}
class InvalidEntryException(message: String?) : RuntimeException(message)