integration test of upd handshake
diff --git a/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultNodeDiscoveryService.kt b/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultNodeDiscoveryService.kt
index 972429e..8575452 100644
--- a/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultNodeDiscoveryService.kt
+++ b/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultNodeDiscoveryService.kt
@@ -22,7 +22,6 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import org.apache.tuweni.bytes.Bytes
-import org.apache.tuweni.crypto.Hash
 import org.apache.tuweni.crypto.SECP256K1
 import org.apache.tuweni.devp2p.EthereumNodeRecord
 import org.apache.tuweni.devp2p.v5.NodeDiscoveryService
@@ -47,8 +46,7 @@
     null,
     bindAddress.port
   ),
-  private val nodeId: Bytes = Hash.sha2_256(selfENR),
-  private val connector: UdpConnector = DefaultUdpConnector(nodeId, bindAddress, keyPair, selfENR),
+  private val connector: UdpConnector = DefaultUdpConnector(bindAddress, keyPair, selfENR),
   override val coroutineContext: CoroutineContext = Dispatchers.Default
 ) : NodeDiscoveryService, CoroutineScope {
 
diff --git a/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultPacketCodec.kt b/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultPacketCodec.kt
index 868243a..6dbab0f 100644
--- a/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultPacketCodec.kt
+++ b/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultPacketCodec.kt
@@ -17,6 +17,7 @@
 package org.apache.tuweni.devp2p.v5.internal
 
 import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.crypto.Hash
 import org.apache.tuweni.crypto.SECP256K1
 import org.apache.tuweni.devp2p.v5.AuthenticationProvider
 import org.apache.tuweni.devp2p.v5.PacketCodec
@@ -33,9 +34,9 @@
 import kotlin.IllegalArgumentException
 
 class DefaultPacketCodec(
-  private val nodeId: Bytes,
   private val keyPair: SECP256K1.KeyPair,
   private val enr: Bytes,
+  private val nodeId: Bytes = Hash.sha2_256(enr),
   private val authenticationProvider: AuthenticationProvider = DefaultAuthenticationProvider(keyPair, enr)
 ) : PacketCodec {
 
diff --git a/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultUdpConnector.kt b/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultUdpConnector.kt
index 3bd3e73..ebb4eb8 100644
--- a/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultUdpConnector.kt
+++ b/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultUdpConnector.kt
@@ -21,6 +21,7 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.crypto.Hash
 import org.apache.tuweni.crypto.SECP256K1
 import org.apache.tuweni.devp2p.v5.MessageHandler
 import org.apache.tuweni.devp2p.v5.PacketCodec
@@ -39,13 +40,13 @@
 import kotlin.coroutines.CoroutineContext
 
 class DefaultUdpConnector(
-  private val nodeId: Bytes,
   private val bindAddress: InetSocketAddress,
   private val keyPair: SECP256K1.KeyPair,
   private val selfEnr: Bytes,
+  private val nodeId: Bytes = Hash.sha2_256(selfEnr),
   private val receiveChannel: CoroutineDatagramChannel = CoroutineDatagramChannel.open(),
   private val sendChannel: CoroutineDatagramChannel = CoroutineDatagramChannel.open(),
-  private val packetCodec: PacketCodec = DefaultPacketCodec(nodeId, keyPair, selfEnr),
+  private val packetCodec: PacketCodec = DefaultPacketCodec(keyPair, selfEnr),
   override val coroutineContext: CoroutineContext = Dispatchers.IO
 ) : UdpConnector, CoroutineScope {
 
@@ -106,8 +107,12 @@
 
   override fun getNodeKeyPair(): SECP256K1.KeyPair = keyPair
 
-  override fun getPendingNodeIdByAddress(address: InetSocketAddress): Bytes = authenticatingPeers[address]
-    ?: throw IllegalArgumentException("Authenticated peer not found with address ${address.hostName}:${address.port}")
+  override fun getPendingNodeIdByAddress(address: InetSocketAddress): Bytes {
+    val result = authenticatingPeers[address]
+      ?: throw IllegalArgumentException("Authenticated peer not found with address ${address.hostName}:${address.port}")
+    authenticatingPeers.remove(address)
+    return result
+  }
 
   private fun processDatagram(datagram: ByteBuffer, address: InetSocketAddress) {
     val messageBytes = Bytes.wrapByteBuffer(datagram)
@@ -116,9 +121,8 @@
     when (message) {
       is RandomMessage -> randomMessageHandler.handle(message, address, decodeResult.srcNodeId, this)
       is WhoAreYouMessage -> whoAreYouMessageHandler.handle(message, address, decodeResult.srcNodeId, this)
-      is FindNodeMessage -> {  } //TODO: response with NODES message
+      is FindNodeMessage -> { } //TODO: response with NODES message
       else -> throw IllegalArgumentException("Unexpected message has been received - ${message::class.java.simpleName}")
     }
   }
-
 }
diff --git a/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/handler/WhoAreYouMessageHandler.kt b/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/handler/WhoAreYouMessageHandler.kt
index 17e28e8..754ceae 100644
--- a/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/handler/WhoAreYouMessageHandler.kt
+++ b/devp2p/src/main/kotlin/org/apache/tuweni/devp2p/v5/internal/handler/WhoAreYouMessageHandler.kt
@@ -17,6 +17,7 @@
 package org.apache.tuweni.devp2p.v5.internal.handler
 
 import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.crypto.Hash
 import org.apache.tuweni.devp2p.v5.MessageHandler
 import org.apache.tuweni.devp2p.v5.UdpConnector
 import org.apache.tuweni.devp2p.v5.packet.FindNodeMessage
@@ -37,8 +38,9 @@
     // Retrieve enr
     val destRlp = connector.getPendingNodeIdByAddress(address)
     val handshakeParams = HandshakeInitParameters(message.idNonce, message.authTag, destRlp)
+    val destNodeId = Hash.sha2_256(destRlp)
 
     val findNodeMessage = FindNodeMessage()
-    connector.send(address, findNodeMessage, srcNodeId, handshakeParams)
+    connector.send(address, findNodeMessage, destNodeId, handshakeParams)
   }
 }
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/HandshakeIntegrationTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/HandshakeIntegrationTest.kt
new file mode 100644
index 0000000..ebbad34
--- /dev/null
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/HandshakeIntegrationTest.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.v5
+
+import kotlinx.coroutines.runBlocking
+import org.apache.tuweni.bytes.Bytes
+import org.apache.tuweni.crypto.Hash
+import org.apache.tuweni.crypto.SECP256K1
+import org.apache.tuweni.devp2p.EthereumNodeRecord
+import org.apache.tuweni.devp2p.v5.internal.DefaultAuthenticationProvider
+import org.apache.tuweni.devp2p.v5.internal.DefaultNodeDiscoveryService
+import org.apache.tuweni.devp2p.v5.internal.DefaultPacketCodec
+import org.apache.tuweni.devp2p.v5.internal.DefaultUdpConnector
+import org.apache.tuweni.devp2p.v5.packet.FindNodeMessage
+import org.apache.tuweni.devp2p.v5.packet.RandomMessage
+import org.apache.tuweni.devp2p.v5.packet.UdpMessage
+import org.apache.tuweni.devp2p.v5.packet.WhoAreYouMessage
+import org.apache.tuweni.io.Base64URLSafe
+import org.apache.tuweni.junit.BouncyCastleExtension
+import org.apache.tuweni.net.coroutines.CoroutineDatagramChannel
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import java.net.InetAddress
+import java.net.InetSocketAddress
+import java.nio.ByteBuffer
+
+@ExtendWith(BouncyCastleExtension::class)
+class HandshakeIntegrationTest {
+
+  private val keyPair: SECP256K1.KeyPair = SECP256K1.KeyPair.random()
+  private val enr: Bytes = EthereumNodeRecord.toRLP(keyPair, ip = InetAddress.getLocalHost(), udp = SERVICE_PORT)
+  private val address: InetSocketAddress = InetSocketAddress(InetAddress.getLocalHost(), SERVICE_PORT)
+  private val connector: UdpConnector = DefaultUdpConnector(address, keyPair, enr)
+
+  private val clientKeyPair = SECP256K1.KeyPair.random()
+  private val clientEnr = EthereumNodeRecord.toRLP(clientKeyPair, ip = InetAddress.getLocalHost(), udp = CLIENT_PORT)
+  private val authProvider = DefaultAuthenticationProvider(clientKeyPair, clientEnr)
+  private val clientCodec = DefaultPacketCodec(clientKeyPair, clientEnr, authenticationProvider = authProvider)
+  private val socket = CoroutineDatagramChannel.open()
+
+  private val clientNodeId: Bytes = Hash.sha2_256(clientEnr)
+
+  private val bootList = listOf("enr:${Base64URLSafe.encode(clientEnr)}")
+  private val service: NodeDiscoveryService =
+    DefaultNodeDiscoveryService(keyPair, SERVICE_PORT, bootstrapENRList = bootList, connector = connector)
+
+  @BeforeEach
+  fun init() {
+    socket.bind(InetSocketAddress(9091))
+    service.start()
+  }
+
+  @Test
+  fun discv5HandshakeTest() {
+    runBlocking {
+      val buffer = ByteBuffer.allocate(UdpMessage.MAX_UDP_MESSAGE_SIZE)
+      socket.receive(buffer)
+      buffer.flip()
+
+      var content = Bytes.wrapByteBuffer(buffer)
+      var decodingResult = clientCodec.decode(content)
+      assert(decodingResult.message is RandomMessage)
+      buffer.clear()
+
+      sendWhoAreYou()
+
+      socket.receive(buffer)
+      buffer.flip()
+      content = Bytes.wrapByteBuffer(buffer)
+      decodingResult = clientCodec.decode(content)
+      assert(decodingResult.message is FindNodeMessage)
+      assert(null != authProvider.findSessionKey(Hash.sha2_256(enr).toHexString()))
+
+      val message = decodingResult.message as FindNodeMessage
+
+      assert(message.distance == 0L)
+      assert(message.requestId.size() == UdpMessage.REQUEST_ID_LENGTH)
+    }
+  }
+
+  @AfterEach
+  fun tearDown() {
+    service.terminate(true)
+    socket.close()
+  }
+
+  private suspend fun sendWhoAreYou() {
+    val message = WhoAreYouMessage()
+    val bytes = clientCodec.encode(message, clientNodeId)
+    val buffer = ByteBuffer.wrap(bytes.toArray())
+    socket.send(buffer, address)
+  }
+
+  companion object {
+    private const val SERVICE_PORT: Int = 9090
+    private const val CLIENT_PORT: Int = 9091
+  }
+}
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/encrypt/AES128GCMTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/encrypt/AES128GCMTest.kt
index f91ad02..4146d05 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/encrypt/AES128GCMTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/encrypt/AES128GCMTest.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.v5.encrypt
 
 import org.apache.tuweni.bytes.Bytes
@@ -31,5 +47,4 @@
 
     assert(result == expectedResult)
   }
-
 }
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/encrypt/SessionKeyGeneratorTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/encrypt/SessionKeyGeneratorTest.kt
index 530b73f..f517ed1 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/encrypt/SessionKeyGeneratorTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/encrypt/SessionKeyGeneratorTest.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.v5.encrypt
 
 import org.apache.tuweni.bytes.Bytes
@@ -22,5 +38,4 @@
     assert(result.initiatorKey == expectedInitiatorKey)
     assert(result.recipientKey == expectedRecipientKey)
   }
-
 }
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultAuthenticationProviderTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultAuthenticationProviderTest.kt
index 357e9e5..cebe167 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultAuthenticationProviderTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultAuthenticationProviderTest.kt
@@ -91,5 +91,4 @@
 
     assert(result != null)
   }
-
 }
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultNodeDiscoveryServiceTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultNodeDiscoveryServiceTest.kt
index 9c65d90..895db0b 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultNodeDiscoveryServiceTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultNodeDiscoveryServiceTest.kt
@@ -18,7 +18,6 @@
 
 import kotlinx.coroutines.runBlocking
 import org.apache.tuweni.bytes.Bytes
-import org.apache.tuweni.crypto.Hash
 import org.apache.tuweni.crypto.SECP256K1
 import org.apache.tuweni.devp2p.EthereumNodeRecord
 import org.apache.tuweni.devp2p.v5.NodeDiscoveryService
@@ -56,11 +55,10 @@
     null,
     bindAddress.port
   )
-  private val nodeId: Bytes = Hash.sha2_256(selfENR)
-  private val connector: UdpConnector = DefaultUdpConnector(nodeId, bindAddress, keyPair, selfENR)
+  private val connector: UdpConnector = DefaultUdpConnector(bindAddress, keyPair, selfENR)
 
   private val nodeDiscoveryService: NodeDiscoveryService =
-    DefaultNodeDiscoveryService(keyPair, localPort, bindAddress, bootstrapENRList, enrSeq, selfENR, nodeId, connector)
+    DefaultNodeDiscoveryService(keyPair, localPort, bindAddress, bootstrapENRList, enrSeq, selfENR, connector)
 
   @Test
   fun startInitializesConnectorAndBootstraps() {
@@ -96,5 +94,4 @@
 
     assert(!connector.available())
   }
-
 }
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultPacketCodecTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultPacketCodecTest.kt
index ba03bb3..85a08dd 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultPacketCodecTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultPacketCodecTest.kt
@@ -42,7 +42,7 @@
   private val nodeId: Bytes = Hash.sha2_256(enr)
   private val authenticationProvider: AuthenticationProvider = DefaultAuthenticationProvider(keyPair, enr)
 
-  private val codec: PacketCodec = DefaultPacketCodec(nodeId, keyPair, enr, authenticationProvider)
+  private val codec: PacketCodec = DefaultPacketCodec(keyPair, enr, nodeId, authenticationProvider)
 
   private val destNodeId: Bytes = Bytes.random(32)
 
@@ -144,5 +144,4 @@
     assert(result!!.requestId == message.requestId)
     assert(result.distance == message.distance)
   }
-
 }
diff --git a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultUdpConnectorTest.kt b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultUdpConnectorTest.kt
index 002af30..33f84eb 100644
--- a/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultUdpConnectorTest.kt
+++ b/devp2p/src/test/kotlin/org/apache/tuweni/devp2p/v5/internal/DefaultUdpConnectorTest.kt
@@ -44,11 +44,11 @@
   private val data: Bytes = UdpMessage.randomData()
   private val message: RandomMessage = RandomMessage(data)
 
-  private var connector: UdpConnector = DefaultUdpConnector(nodeId, address, keyPair, selfEnr)
+  private var connector: UdpConnector = DefaultUdpConnector(address, keyPair, selfEnr, nodeId)
 
   @BeforeEach
   fun setUp() {
-    connector = DefaultUdpConnector(nodeId, address, keyPair, selfEnr)
+    connector = DefaultUdpConnector(address, keyPair, selfEnr, nodeId)
   }
 
   @AfterEach