|
5 | 5 | package org.ethereum.beacon.discovery.schema; |
6 | 6 |
|
7 | 7 | import static org.assertj.core.api.Assertions.assertThat; |
| 8 | +import static org.mockito.ArgumentMatchers.any; |
8 | 9 | import static org.mockito.ArgumentMatchers.eq; |
9 | 10 | import static org.mockito.Mockito.doReturn; |
10 | 11 | import static org.mockito.Mockito.mock; |
| 12 | +import static org.mockito.Mockito.never; |
11 | 13 | import static org.mockito.Mockito.spy; |
| 14 | +import static org.mockito.Mockito.times; |
12 | 15 | import static org.mockito.Mockito.verify; |
13 | 16 | import static org.mockito.Mockito.when; |
14 | 17 |
|
|
22 | 25 | import java.util.function.Consumer; |
23 | 26 | import org.apache.tuweni.bytes.Bytes; |
24 | 27 | import org.apache.tuweni.bytes.Bytes32; |
| 28 | +import org.apache.tuweni.units.bigints.UInt64; |
25 | 29 | import org.ethereum.beacon.discovery.SimpleIdentitySchemaInterpreter; |
26 | 30 | import org.ethereum.beacon.discovery.crypto.DefaultSigner; |
27 | 31 | import org.ethereum.beacon.discovery.crypto.Signer; |
28 | 32 | import org.ethereum.beacon.discovery.message.V5Message; |
29 | 33 | import org.ethereum.beacon.discovery.network.NetworkParcel; |
| 34 | +import org.ethereum.beacon.discovery.network.NetworkParcelV5; |
| 35 | +import org.ethereum.beacon.discovery.packet.Header; |
| 36 | +import org.ethereum.beacon.discovery.packet.WhoAreYouPacket; |
| 37 | +import org.ethereum.beacon.discovery.packet.WhoAreYouPacket.WhoAreYouAuthData; |
30 | 38 | import org.ethereum.beacon.discovery.pipeline.handler.NodeSessionManager; |
31 | 39 | import org.ethereum.beacon.discovery.pipeline.info.Request; |
32 | 40 | import org.ethereum.beacon.discovery.pipeline.info.RequestInfo; |
|
36 | 44 | import org.ethereum.beacon.discovery.storage.LocalNodeRecordStore; |
37 | 45 | import org.ethereum.beacon.discovery.storage.NewAddressHandler; |
38 | 46 | import org.ethereum.beacon.discovery.storage.NodeRecordListener; |
| 47 | +import org.ethereum.beacon.discovery.type.Bytes12; |
| 48 | +import org.ethereum.beacon.discovery.type.Bytes16; |
39 | 49 | import org.ethereum.beacon.discovery.util.Functions; |
40 | 50 | import org.junit.jupiter.api.Test; |
41 | 51 | import org.junit.jupiter.params.ParameterizedTest; |
@@ -187,6 +197,73 @@ void createNextRequest_shouldNotResetAuthenticatedStatesWhenRequestTimesOut() { |
187 | 197 | assertThat(session.getState()).isEqualTo(SessionState.AUTHENTICATED); |
188 | 198 | } |
189 | 199 |
|
| 200 | + @Test |
| 201 | + void resendOutgoingWhoAreYou_shouldSendPacketWhenPendingPacketExists() { |
| 202 | + session.sendOutgoingWhoAreYou(createWhoAreYouPacket(Bytes12.wrap(Bytes.random(12)))); |
| 203 | + |
| 204 | + final ArgumentCaptor<NetworkParcelV5> firstCaptor = |
| 205 | + ArgumentCaptor.forClass(NetworkParcelV5.class); |
| 206 | + verify(outgoingPipeline).accept(firstCaptor.capture()); |
| 207 | + |
| 208 | + session.resendOutgoingWhoAreYou(); |
| 209 | + |
| 210 | + final ArgumentCaptor<NetworkParcelV5> secondCaptor = |
| 211 | + ArgumentCaptor.forClass(NetworkParcelV5.class); |
| 212 | + verify(outgoingPipeline, times(2)).accept(secondCaptor.capture()); |
| 213 | + // A packet must actually be sent on resend. |
| 214 | + assertThat(secondCaptor.getAllValues()).hasSize(2); |
| 215 | + } |
| 216 | + |
| 217 | + @Test |
| 218 | + void resendOutgoingWhoAreYou_shouldDoNothingWhenNoPendingPacket() { |
| 219 | + session.resendOutgoingWhoAreYou(); |
| 220 | + |
| 221 | + verify(outgoingPipeline, never()).accept(any()); |
| 222 | + } |
| 223 | + |
| 224 | + @Test |
| 225 | + void resendOutgoingWhoAreYou_shouldDoNothingAfterHandshakeStateReset() { |
| 226 | + final Request<?> request = createRequestMock(); |
| 227 | + final RequestInfo requestInfo = session.createNextRequest(request); |
| 228 | + |
| 229 | + final ArgumentCaptor<Runnable> timeoutHandlerCaptor = ArgumentCaptor.forClass(Runnable.class); |
| 230 | + verify(expirationScheduler).put(eq(requestInfo.getRequestId()), timeoutHandlerCaptor.capture()); |
| 231 | + |
| 232 | + session.sendOutgoingWhoAreYou(createWhoAreYouPacket(Bytes12.wrap(Bytes.random(12)))); |
| 233 | + session.setState(SessionState.WHOAREYOU_SENT); |
| 234 | + |
| 235 | + // Simulate request timeout which resets the handshake state. |
| 236 | + timeoutHandlerCaptor.getValue().run(); |
| 237 | + assertThat(session.getState()).isEqualTo(SessionState.INITIAL); |
| 238 | + |
| 239 | + session.resendOutgoingWhoAreYou(); |
| 240 | + |
| 241 | + // sendOutgoingWhoAreYou was called once above; resend should not add another send. |
| 242 | + verify(outgoingPipeline, times(1)).accept(any()); |
| 243 | + } |
| 244 | + |
| 245 | + @Test |
| 246 | + void resendOutgoingWhoAreYou_shouldPreserveOriginalNonce() { |
| 247 | + final Bytes12 originalNonce = Bytes12.wrap(Bytes.random(12)); |
| 248 | + final WhoAreYouPacket originalPacket = createWhoAreYouPacket(originalNonce); |
| 249 | + session.sendOutgoingWhoAreYou(originalPacket); |
| 250 | + |
| 251 | + final Bytes challengeAfterSend = session.getWhoAreYouChallenge().orElseThrow(); |
| 252 | + |
| 253 | + session.resendOutgoingWhoAreYou(); |
| 254 | + |
| 255 | + // Challenge must be unchanged after resend so a handshake signed against the original |
| 256 | + // challenge remains valid. |
| 257 | + assertThat(session.getWhoAreYouChallenge()).contains(challengeAfterSend); |
| 258 | + } |
| 259 | + |
| 260 | + private static WhoAreYouPacket createWhoAreYouPacket(final Bytes12 nonce) { |
| 261 | + final Bytes16 idNonce = Bytes16.wrap(Bytes.random(16)); |
| 262 | + final Header<WhoAreYouAuthData> header = |
| 263 | + Header.createWhoAreYouHeader(nonce, idNonce, UInt64.ZERO); |
| 264 | + return WhoAreYouPacket.create(header); |
| 265 | + } |
| 266 | + |
190 | 267 | private Request<?> createRequestMock() { |
191 | 268 | final Request<?> request = mock(Request.class); |
192 | 269 | when(request.getResultPromise()).thenReturn(new CompletableFuture<>()); |
|
0 commit comments