mirror of https://github.com/hyperledger/besu
When tls is on, use plain framer and handshaker to avoid double encryption (#3008)
* When tls is on, use passthrough framer and passthrough handshaker to avoid double encryption Signed-off-by: Saravana Perumal Shanmugam <perusworld@linux.com>pull/3014/head
parent
a2f517ce32
commit
bba8032883
@ -0,0 +1,67 @@ |
|||||||
|
/* |
||||||
|
* Copyright Hyperledger Besu Contributors. |
||||||
|
* |
||||||
|
* Licensed 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.p2p.plain; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; |
||||||
|
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; |
||||||
|
import org.hyperledger.besu.ethereum.rlp.RLPInput; |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf; |
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
|
||||||
|
public class MessageHandler { |
||||||
|
|
||||||
|
public static Bytes buildMessage(final PlainMessage message) { |
||||||
|
final BytesValueRLPOutput ret = new BytesValueRLPOutput(); |
||||||
|
ret.startList(); |
||||||
|
ret.writeInt(message.getMessageType().getValue()); |
||||||
|
if (MessageType.DATA.equals(message.getMessageType())) { |
||||||
|
ret.writeInt(message.getCode()); |
||||||
|
} |
||||||
|
ret.writeBytes(message.getData()); |
||||||
|
ret.endList(); |
||||||
|
return ret.encoded(); |
||||||
|
} |
||||||
|
|
||||||
|
public static Bytes buildMessage(final MessageType messageType, final Bytes data) { |
||||||
|
return buildMessage(new PlainMessage(messageType, data)); |
||||||
|
} |
||||||
|
|
||||||
|
public static Bytes buildMessage( |
||||||
|
final MessageType messageType, final int code, final Bytes data) { |
||||||
|
return buildMessage(new PlainMessage(messageType, code, data)); |
||||||
|
} |
||||||
|
|
||||||
|
public static Bytes buildMessage(final MessageType messageType, final byte[] data) { |
||||||
|
return buildMessage(new PlainMessage(messageType, data)); |
||||||
|
} |
||||||
|
|
||||||
|
public static PlainMessage parseMessage(final ByteBuf buf) { |
||||||
|
PlainMessage ret = null; |
||||||
|
final ByteBuf bufferedBytes = buf.readSlice(buf.readableBytes()); |
||||||
|
final byte[] byteArr = new byte[bufferedBytes.readableBytes()]; |
||||||
|
bufferedBytes.getBytes(0, byteArr); |
||||||
|
Bytes bytes = Bytes.wrap(byteArr); |
||||||
|
final RLPInput input = new BytesValueRLPInput(bytes, true); |
||||||
|
input.enterList(); |
||||||
|
MessageType type = MessageType.forNumber(input.readInt()); |
||||||
|
if (MessageType.DATA.equals(type)) { |
||||||
|
ret = new PlainMessage(type, input.readInt(), input.readBytes()); |
||||||
|
} else { |
||||||
|
ret = new PlainMessage(type, input.readBytes()); |
||||||
|
} |
||||||
|
return ret; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
/* |
||||||
|
* Copyright Hyperledger Besu Contributors. |
||||||
|
* |
||||||
|
* Licensed 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.p2p.plain; |
||||||
|
|
||||||
|
public enum MessageType { |
||||||
|
PING(0), |
||||||
|
PONG(1), |
||||||
|
DATA(2), |
||||||
|
UNRECOGNIZED(-1), |
||||||
|
; |
||||||
|
|
||||||
|
private final int value; |
||||||
|
|
||||||
|
private MessageType(final int value) { |
||||||
|
this.value = value; |
||||||
|
} |
||||||
|
|
||||||
|
public int getValue() { |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
public static MessageType forNumber(final int value) { |
||||||
|
switch (value) { |
||||||
|
case 0: |
||||||
|
return PING; |
||||||
|
case 1: |
||||||
|
return PONG; |
||||||
|
case 2: |
||||||
|
return DATA; |
||||||
|
default: |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
/* |
||||||
|
* Copyright Hyperledger Besu Contributors. |
||||||
|
* |
||||||
|
* Licensed 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.p2p.plain; |
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.framing.Framer; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.framing.FramingException; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.RawMessage; |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf; |
||||||
|
import org.apache.logging.log4j.LogManager; |
||||||
|
import org.apache.logging.log4j.Logger; |
||||||
|
|
||||||
|
public class PlainFramer extends Framer { |
||||||
|
private static final Logger LOG = LogManager.getLogger(); |
||||||
|
|
||||||
|
public PlainFramer() { |
||||||
|
LOG.trace("Initialising PlainFramer"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public synchronized MessageData deframe(final ByteBuf buf) throws FramingException { |
||||||
|
LOG.trace("Deframing Message"); |
||||||
|
if (buf == null || !buf.isReadable()) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
PlainMessage message = MessageHandler.parseMessage(buf); |
||||||
|
checkState( |
||||||
|
MessageType.DATA.equals(message.getMessageType()), "unexpected message: needs to be data"); |
||||||
|
return new RawMessage(message.getCode(), message.getData()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public synchronized void frame(final MessageData message, final ByteBuf output) { |
||||||
|
LOG.trace("Framing Message"); |
||||||
|
output.writeBytes( |
||||||
|
MessageHandler.buildMessage(MessageType.DATA, message.getCode(), message.getData()) |
||||||
|
.toArray()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,162 @@ |
|||||||
|
/* |
||||||
|
* Copyright Hyperledger Besu Contributors. |
||||||
|
* |
||||||
|
* Licensed 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.p2p.plain; |
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState; |
||||||
|
|
||||||
|
import org.hyperledger.besu.crypto.NodeKey; |
||||||
|
import org.hyperledger.besu.crypto.SECPPublicKey; |
||||||
|
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.HandshakeException; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.HandshakeSecrets; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.Handshaker; |
||||||
|
|
||||||
|
import java.util.Optional; |
||||||
|
import java.util.concurrent.atomic.AtomicReference; |
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf; |
||||||
|
import io.netty.buffer.Unpooled; |
||||||
|
import org.apache.logging.log4j.LogManager; |
||||||
|
import org.apache.logging.log4j.Logger; |
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
|
||||||
|
public class PlainHandshaker implements Handshaker { |
||||||
|
|
||||||
|
private static final Logger LOG = LogManager.getLogger(); |
||||||
|
|
||||||
|
private final AtomicReference<Handshaker.HandshakeStatus> status = |
||||||
|
new AtomicReference<>(Handshaker.HandshakeStatus.UNINITIALIZED); |
||||||
|
|
||||||
|
private boolean initiator; |
||||||
|
private NodeKey nodeKey; |
||||||
|
private SECPPublicKey partyPubKey; |
||||||
|
private Bytes initiatorMsg; |
||||||
|
private Bytes responderMsg; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void prepareInitiator(final NodeKey nodeKey, final SECPPublicKey theirPubKey) { |
||||||
|
checkState( |
||||||
|
status.compareAndSet( |
||||||
|
Handshaker.HandshakeStatus.UNINITIALIZED, Handshaker.HandshakeStatus.PREPARED), |
||||||
|
"handshake was already prepared"); |
||||||
|
|
||||||
|
this.initiator = true; |
||||||
|
this.nodeKey = nodeKey; |
||||||
|
this.partyPubKey = theirPubKey; |
||||||
|
LOG.trace( |
||||||
|
"Prepared plain handshake with node {}... under INITIATOR role", |
||||||
|
theirPubKey.getEncodedBytes().slice(0, 16)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void prepareResponder(final NodeKey nodeKey) { |
||||||
|
checkState( |
||||||
|
status.compareAndSet( |
||||||
|
Handshaker.HandshakeStatus.UNINITIALIZED, Handshaker.HandshakeStatus.IN_PROGRESS), |
||||||
|
"handshake was already prepared"); |
||||||
|
|
||||||
|
this.initiator = false; |
||||||
|
this.nodeKey = nodeKey; |
||||||
|
LOG.trace("Prepared plain handshake under RESPONDER role"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public ByteBuf firstMessage() throws HandshakeException { |
||||||
|
checkState(initiator, "illegal invocation of firstMessage on non-initiator end of handshake"); |
||||||
|
checkState( |
||||||
|
status.compareAndSet( |
||||||
|
Handshaker.HandshakeStatus.PREPARED, Handshaker.HandshakeStatus.IN_PROGRESS), |
||||||
|
"illegal invocation of firstMessage, handshake had already started"); |
||||||
|
initiatorMsg = |
||||||
|
MessageHandler.buildMessage(MessageType.PING, nodeKey.getPublicKey().getEncoded()); |
||||||
|
|
||||||
|
LOG.trace("First plain handshake message under INITIATOR role"); |
||||||
|
|
||||||
|
return Unpooled.wrappedBuffer(initiatorMsg.toArray()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Optional<ByteBuf> handleMessage(final ByteBuf buf) throws HandshakeException { |
||||||
|
checkState( |
||||||
|
status.get() == Handshaker.HandshakeStatus.IN_PROGRESS, |
||||||
|
"illegal invocation of onMessage on handshake that is not in progress"); |
||||||
|
|
||||||
|
PlainMessage message = MessageHandler.parseMessage(buf); |
||||||
|
|
||||||
|
Optional<Bytes> nextMsg = Optional.empty(); |
||||||
|
if (initiator) { |
||||||
|
checkState( |
||||||
|
responderMsg == null, |
||||||
|
"unexpected message: responder message had " + "already been received"); |
||||||
|
|
||||||
|
checkState( |
||||||
|
message.getMessageType().equals(MessageType.PONG), |
||||||
|
"unexpected message: needs to be a pong"); |
||||||
|
responderMsg = message.getData(); |
||||||
|
|
||||||
|
LOG.trace( |
||||||
|
"Received responder's plain handshake message from node {}...: {}", |
||||||
|
partyPubKey.getEncodedBytes().slice(0, 16), |
||||||
|
responderMsg); |
||||||
|
|
||||||
|
} else { |
||||||
|
checkState( |
||||||
|
initiatorMsg == null, |
||||||
|
"unexpected message: initiator message " + "had already been received"); |
||||||
|
checkState( |
||||||
|
message.getMessageType().equals(MessageType.PING), |
||||||
|
"unexpected message: needs to be a ping"); |
||||||
|
|
||||||
|
initiatorMsg = message.getData(); |
||||||
|
LOG.trace( |
||||||
|
"[{}] Received initiator's plain handshake message: {}", |
||||||
|
nodeKey.getPublicKey().getEncodedBytes(), |
||||||
|
initiatorMsg); |
||||||
|
|
||||||
|
partyPubKey = SignatureAlgorithmFactory.getInstance().createPublicKey(message.getData()); |
||||||
|
|
||||||
|
responderMsg = |
||||||
|
MessageHandler.buildMessage(MessageType.PONG, nodeKey.getPublicKey().getEncoded()); |
||||||
|
|
||||||
|
LOG.trace( |
||||||
|
"Generated responder's plain handshake message against peer {}...: {}", |
||||||
|
partyPubKey.getEncodedBytes().slice(0, 16), |
||||||
|
responderMsg); |
||||||
|
|
||||||
|
nextMsg = Optional.of(Bytes.wrap(responderMsg.toArray())); |
||||||
|
} |
||||||
|
|
||||||
|
status.set(Handshaker.HandshakeStatus.SUCCESS); |
||||||
|
LOG.trace("Handshake status set to {}", status.get()); |
||||||
|
return nextMsg.map(bv -> Unpooled.wrappedBuffer(bv.toArray())); |
||||||
|
} |
||||||
|
|
||||||
|
void computeSecrets() {} |
||||||
|
|
||||||
|
@Override |
||||||
|
public HandshakeStatus getStatus() { |
||||||
|
return status.get(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public HandshakeSecrets secrets() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public SECPPublicKey partyPubKey() { |
||||||
|
return partyPubKey; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,51 @@ |
|||||||
|
/* |
||||||
|
* Copyright Hyperledger Besu Contributors. |
||||||
|
* |
||||||
|
* Licensed 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.p2p.plain; |
||||||
|
|
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
|
||||||
|
public class PlainMessage { |
||||||
|
|
||||||
|
private final MessageType messageType; |
||||||
|
private final int code; |
||||||
|
private final Bytes data; |
||||||
|
|
||||||
|
public PlainMessage(final MessageType messageType, final byte[] data) { |
||||||
|
this(messageType, Bytes.wrap(data)); |
||||||
|
} |
||||||
|
|
||||||
|
public PlainMessage(final MessageType messageType, final Bytes data) { |
||||||
|
this(messageType, -1, data); |
||||||
|
} |
||||||
|
|
||||||
|
public PlainMessage(final MessageType messageType, final int code, final Bytes data) { |
||||||
|
super(); |
||||||
|
this.messageType = messageType; |
||||||
|
this.code = code; |
||||||
|
this.data = data; |
||||||
|
} |
||||||
|
|
||||||
|
public MessageType getMessageType() { |
||||||
|
return messageType; |
||||||
|
} |
||||||
|
|
||||||
|
public Bytes getData() { |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
public int getCode() { |
||||||
|
return code; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
/* |
||||||
|
* Copyright Hyperledger Besu Contributors. |
||||||
|
* |
||||||
|
* Licensed 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.p2p.rlpx.framing; |
||||||
|
|
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.HandshakeSecrets; |
||||||
|
|
||||||
|
public interface FramerProvider { |
||||||
|
|
||||||
|
public Framer buildFramer(HandshakeSecrets secrets); |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
/* |
||||||
|
* Copyright Hyperledger Besu Contributors. |
||||||
|
* |
||||||
|
* Licensed 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.p2p.rlpx.handshake; |
||||||
|
|
||||||
|
public interface HandshakerProvider { |
||||||
|
|
||||||
|
public Handshaker buildInstance(); |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
/* |
||||||
|
* Copyright Hyperledger Besu Contributors. |
||||||
|
* |
||||||
|
* Licensed 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.p2p.network; |
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.nio.file.Files; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.util.function.Supplier; |
||||||
|
import java.util.stream.Stream; |
||||||
|
|
||||||
|
public class FileBasedPasswordProvider implements Supplier<String> { |
||||||
|
private final Path passwordFile; |
||||||
|
|
||||||
|
public FileBasedPasswordProvider(final Path passwordFile) { |
||||||
|
requireNonNull(passwordFile, "Password file path cannot be null"); |
||||||
|
this.passwordFile = passwordFile; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String get() { |
||||||
|
try (final Stream<String> fileStream = Files.lines(passwordFile)) { |
||||||
|
return fileStream |
||||||
|
.findFirst() |
||||||
|
.orElseThrow( |
||||||
|
() -> |
||||||
|
new RuntimeException( |
||||||
|
String.format( |
||||||
|
"Unable to read keystore password from %s", passwordFile.toString()))); |
||||||
|
} catch (final IOException e) { |
||||||
|
throw new RuntimeException( |
||||||
|
String.format("Unable to read keystore password file %s", passwordFile.toString()), e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,444 @@ |
|||||||
|
/* |
||||||
|
* Copyright Hyperledger Besu Contributors. |
||||||
|
* |
||||||
|
* Licensed 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.p2p.network; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||||
|
import static org.mockito.ArgumentMatchers.any; |
||||||
|
import static org.mockito.ArgumentMatchers.eq; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
import org.hyperledger.besu.crypto.NodeKey; |
||||||
|
import org.hyperledger.besu.crypto.NodeKeyUtils; |
||||||
|
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.config.RlpxConfiguration; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.network.exceptions.IncompatiblePeerException; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeer; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.peers.Peer; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissions; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissionsDenylist; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.TLSConfiguration; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.SubProtocol; |
||||||
|
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; |
||||||
|
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; |
||||||
|
import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; |
||||||
|
import org.hyperledger.besu.plugin.data.EnodeURL; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.net.InetAddress; |
||||||
|
import java.nio.file.Files; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.concurrent.CompletableFuture; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
|
||||||
|
import com.google.common.base.Charsets; |
||||||
|
import io.vertx.core.Vertx; |
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
import org.assertj.core.api.Assertions; |
||||||
|
import org.junit.After; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.junit.MockitoJUnitRunner; |
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.StrictStubs.class) |
||||||
|
public class P2PPlainNetworkTest { |
||||||
|
private final Vertx vertx = Vertx.vertx(); |
||||||
|
private final NetworkingConfiguration config = |
||||||
|
NetworkingConfiguration.create() |
||||||
|
.setDiscovery(DiscoveryConfiguration.create().setActive(false)) |
||||||
|
.setRlpx(RlpxConfiguration.create().setBindPort(0).setSupportedProtocols(subProtocol())); |
||||||
|
|
||||||
|
@After |
||||||
|
public void closeVertx() { |
||||||
|
vertx.close(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void handshaking() throws Exception { |
||||||
|
final NodeKey nodeKey = NodeKeyUtils.generate(); |
||||||
|
try (final P2PNetwork listener = builder("partner1client1").nodeKey(nodeKey).build(); |
||||||
|
final P2PNetwork connector = builder("partner2client1").build()) { |
||||||
|
|
||||||
|
listener.start(); |
||||||
|
connector.start(); |
||||||
|
final EnodeURL listenerEnode = listener.getLocalEnode().get(); |
||||||
|
final Bytes listenId = listenerEnode.getNodeId(); |
||||||
|
final int listenPort = listenerEnode.getListeningPort().get(); |
||||||
|
|
||||||
|
Assertions.assertThat( |
||||||
|
connector |
||||||
|
.connect(createPeer(listenId, listenPort)) |
||||||
|
.get(30L, TimeUnit.SECONDS) |
||||||
|
.getPeerInfo() |
||||||
|
.getNodeId()) |
||||||
|
.isEqualTo(listenId); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void preventMultipleConnections() throws Exception { |
||||||
|
final NodeKey listenNodeKey = NodeKeyUtils.generate(); |
||||||
|
try (final P2PNetwork listener = builder("partner1client1").nodeKey(listenNodeKey).build(); |
||||||
|
final P2PNetwork connector = builder("partner2client1").build()) { |
||||||
|
|
||||||
|
listener.start(); |
||||||
|
connector.start(); |
||||||
|
final EnodeURL listenerEnode = listener.getLocalEnode().get(); |
||||||
|
final Bytes listenId = listenerEnode.getNodeId(); |
||||||
|
final int listenPort = listenerEnode.getListeningPort().get(); |
||||||
|
|
||||||
|
final CompletableFuture<PeerConnection> firstFuture = |
||||||
|
connector.connect(createPeer(listenId, listenPort)); |
||||||
|
final CompletableFuture<PeerConnection> secondFuture = |
||||||
|
connector.connect(createPeer(listenId, listenPort)); |
||||||
|
|
||||||
|
final PeerConnection firstConnection = firstFuture.get(30L, TimeUnit.SECONDS); |
||||||
|
final PeerConnection secondConnection = secondFuture.get(30L, TimeUnit.SECONDS); |
||||||
|
Assertions.assertThat(firstConnection.getPeerInfo().getNodeId()).isEqualTo(listenId); |
||||||
|
|
||||||
|
// Connections should reference the same instance - i.e. we shouldn't create 2 distinct
|
||||||
|
// connections
|
||||||
|
assertThat(firstConnection == secondConnection).isTrue(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests that max peers setting is honoured and inbound connections that would exceed the limit |
||||||
|
* are correctly disconnected. |
||||||
|
* |
||||||
|
* @throws Exception On Failure |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void limitMaxPeers() throws Exception { |
||||||
|
final NodeKey nodeKey = NodeKeyUtils.generate(); |
||||||
|
final int maxPeers = 1; |
||||||
|
final NetworkingConfiguration listenerConfig = |
||||||
|
NetworkingConfiguration.create() |
||||||
|
.setDiscovery(DiscoveryConfiguration.create().setActive(false)) |
||||||
|
.setRlpx( |
||||||
|
RlpxConfiguration.create() |
||||||
|
.setBindPort(0) |
||||||
|
.setMaxPeers(maxPeers) |
||||||
|
.setSupportedProtocols(subProtocol())); |
||||||
|
try (final P2PNetwork listener = |
||||||
|
builder("partner1client1").nodeKey(nodeKey).config(listenerConfig).build(); |
||||||
|
final P2PNetwork connector1 = builder("partner1client1").build(); |
||||||
|
final P2PNetwork connector2 = builder("partner2client1").build()) { |
||||||
|
|
||||||
|
// Setup listener and first connection
|
||||||
|
listener.start(); |
||||||
|
connector1.start(); |
||||||
|
final EnodeURL listenerEnode = listener.getLocalEnode().get(); |
||||||
|
final Bytes listenId = listenerEnode.getNodeId(); |
||||||
|
final int listenPort = listenerEnode.getListeningPort().get(); |
||||||
|
|
||||||
|
final Peer listeningPeer = createPeer(listenId, listenPort); |
||||||
|
Assertions.assertThat( |
||||||
|
connector1 |
||||||
|
.connect(listeningPeer) |
||||||
|
.get(30L, TimeUnit.SECONDS) |
||||||
|
.getPeerInfo() |
||||||
|
.getNodeId()) |
||||||
|
.isEqualTo(listenId); |
||||||
|
|
||||||
|
// Setup second connection and check that connection is not accepted
|
||||||
|
final CompletableFuture<PeerConnection> peerFuture = new CompletableFuture<>(); |
||||||
|
final CompletableFuture<DisconnectReason> reasonFuture = new CompletableFuture<>(); |
||||||
|
connector2.subscribeDisconnect( |
||||||
|
(peerConnection, reason, initiatedByPeer) -> { |
||||||
|
peerFuture.complete(peerConnection); |
||||||
|
reasonFuture.complete(reason); |
||||||
|
}); |
||||||
|
connector2.start(); |
||||||
|
Assertions.assertThat( |
||||||
|
connector2 |
||||||
|
.connect(listeningPeer) |
||||||
|
.get(30L, TimeUnit.SECONDS) |
||||||
|
.getPeerInfo() |
||||||
|
.getNodeId()) |
||||||
|
.isEqualTo(listenId); |
||||||
|
Assertions.assertThat(peerFuture.get(30L, TimeUnit.SECONDS).getPeerInfo().getNodeId()) |
||||||
|
.isEqualTo(listenId); |
||||||
|
assertThat(reasonFuture.get(30L, TimeUnit.SECONDS)) |
||||||
|
.isEqualByComparingTo(DisconnectReason.TOO_MANY_PEERS); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rejectPeerWithNoSharedCaps() throws Exception { |
||||||
|
final NodeKey listenerNodeKey = NodeKeyUtils.generate(); |
||||||
|
final NodeKey connectorNodeKey = NodeKeyUtils.generate(); |
||||||
|
|
||||||
|
final SubProtocol subprotocol1 = subProtocol("eth"); |
||||||
|
final Capability cap1 = Capability.create(subprotocol1.getName(), 63); |
||||||
|
final SubProtocol subprotocol2 = subProtocol("oth"); |
||||||
|
final Capability cap2 = Capability.create(subprotocol2.getName(), 63); |
||||||
|
try (final P2PNetwork listener = |
||||||
|
builder("partner1client1") |
||||||
|
.nodeKey(listenerNodeKey) |
||||||
|
.supportedCapabilities(cap1) |
||||||
|
.build(); |
||||||
|
final P2PNetwork connector = |
||||||
|
builder("partner2client1") |
||||||
|
.nodeKey(connectorNodeKey) |
||||||
|
.supportedCapabilities(cap2) |
||||||
|
.build()) { |
||||||
|
listener.start(); |
||||||
|
connector.start(); |
||||||
|
final EnodeURL listenerEnode = listener.getLocalEnode().get(); |
||||||
|
final Bytes listenId = listenerEnode.getNodeId(); |
||||||
|
final int listenPort = listenerEnode.getListeningPort().get(); |
||||||
|
|
||||||
|
final Peer listenerPeer = createPeer(listenId, listenPort); |
||||||
|
final CompletableFuture<PeerConnection> connectFuture = connector.connect(listenerPeer); |
||||||
|
assertThatThrownBy(connectFuture::get).hasCauseInstanceOf(IncompatiblePeerException.class); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rejectIncomingConnectionFromBlacklistedPeer() throws Exception { |
||||||
|
final PeerPermissionsDenylist localBlacklist = PeerPermissionsDenylist.create(); |
||||||
|
|
||||||
|
try (final P2PNetwork localNetwork = |
||||||
|
builder("partner1client1").peerPermissions(localBlacklist).build(); |
||||||
|
final P2PNetwork remoteNetwork = builder("partner2client1").build()) { |
||||||
|
|
||||||
|
localNetwork.start(); |
||||||
|
remoteNetwork.start(); |
||||||
|
|
||||||
|
final EnodeURL localEnode = localNetwork.getLocalEnode().get(); |
||||||
|
final Bytes localId = localEnode.getNodeId(); |
||||||
|
final int localPort = localEnode.getListeningPort().get(); |
||||||
|
|
||||||
|
final EnodeURL remoteEnode = remoteNetwork.getLocalEnode().get(); |
||||||
|
final Bytes remoteId = remoteEnode.getNodeId(); |
||||||
|
final int remotePort = remoteEnode.getListeningPort().get(); |
||||||
|
|
||||||
|
final Peer localPeer = createPeer(localId, localPort); |
||||||
|
final Peer remotePeer = createPeer(remoteId, remotePort); |
||||||
|
|
||||||
|
// Blacklist the remote peer
|
||||||
|
localBlacklist.add(remotePeer); |
||||||
|
|
||||||
|
// Setup disconnect listener
|
||||||
|
final CompletableFuture<PeerConnection> peerFuture = new CompletableFuture<>(); |
||||||
|
final CompletableFuture<DisconnectReason> reasonFuture = new CompletableFuture<>(); |
||||||
|
remoteNetwork.subscribeDisconnect( |
||||||
|
(peerConnection, reason, initiatedByPeer) -> { |
||||||
|
peerFuture.complete(peerConnection); |
||||||
|
reasonFuture.complete(reason); |
||||||
|
}); |
||||||
|
|
||||||
|
// Remote connect to local
|
||||||
|
final CompletableFuture<PeerConnection> connectFuture = remoteNetwork.connect(localPeer); |
||||||
|
|
||||||
|
// Check connection is made, and then a disconnect is registered at remote
|
||||||
|
Assertions.assertThat(connectFuture.get(5L, TimeUnit.SECONDS).getPeerInfo().getNodeId()) |
||||||
|
.isEqualTo(localId); |
||||||
|
Assertions.assertThat(peerFuture.get(5L, TimeUnit.SECONDS).getPeerInfo().getNodeId()) |
||||||
|
.isEqualTo(localId); |
||||||
|
assertThat(reasonFuture.get(5L, TimeUnit.SECONDS)) |
||||||
|
.isEqualByComparingTo(DisconnectReason.UNKNOWN); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void rejectIncomingConnectionFromDisallowedPeer() throws Exception { |
||||||
|
final PeerPermissions peerPermissions = mock(PeerPermissions.class); |
||||||
|
when(peerPermissions.isPermitted(any(), any(), any())).thenReturn(true); |
||||||
|
|
||||||
|
try (final P2PNetwork localNetwork = |
||||||
|
builder("partner1client1").peerPermissions(peerPermissions).build(); |
||||||
|
final P2PNetwork remoteNetwork = builder("partner2client1").build()) { |
||||||
|
|
||||||
|
localNetwork.start(); |
||||||
|
remoteNetwork.start(); |
||||||
|
|
||||||
|
final EnodeURL localEnode = localNetwork.getLocalEnode().get(); |
||||||
|
final Peer localPeer = DefaultPeer.fromEnodeURL(localEnode); |
||||||
|
final Peer remotePeer = DefaultPeer.fromEnodeURL(remoteNetwork.getLocalEnode().get()); |
||||||
|
|
||||||
|
// Deny incoming connection permissions for remotePeer
|
||||||
|
when(peerPermissions.isPermitted( |
||||||
|
eq(localPeer), |
||||||
|
eq(remotePeer), |
||||||
|
eq(PeerPermissions.Action.RLPX_ALLOW_NEW_INBOUND_CONNECTION))) |
||||||
|
.thenReturn(false); |
||||||
|
|
||||||
|
// Setup disconnect listener
|
||||||
|
final CompletableFuture<PeerConnection> peerFuture = new CompletableFuture<>(); |
||||||
|
final CompletableFuture<DisconnectReason> reasonFuture = new CompletableFuture<>(); |
||||||
|
remoteNetwork.subscribeDisconnect( |
||||||
|
(peerConnection, reason, initiatedByPeer) -> { |
||||||
|
peerFuture.complete(peerConnection); |
||||||
|
reasonFuture.complete(reason); |
||||||
|
}); |
||||||
|
|
||||||
|
// Remote connect to local
|
||||||
|
final CompletableFuture<PeerConnection> connectFuture = remoteNetwork.connect(localPeer); |
||||||
|
|
||||||
|
// Check connection is made, and then a disconnect is registered at remote
|
||||||
|
final Bytes localId = localEnode.getNodeId(); |
||||||
|
Assertions.assertThat(connectFuture.get(5L, TimeUnit.SECONDS).getPeerInfo().getNodeId()) |
||||||
|
.isEqualTo(localId); |
||||||
|
Assertions.assertThat(peerFuture.get(5L, TimeUnit.SECONDS).getPeerInfo().getNodeId()) |
||||||
|
.isEqualTo(localId); |
||||||
|
assertThat(reasonFuture.get(5L, TimeUnit.SECONDS)) |
||||||
|
.isEqualByComparingTo(DisconnectReason.UNKNOWN); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Peer createPeer(final Bytes nodeId, final int listenPort) { |
||||||
|
return DefaultPeer.fromEnodeURL( |
||||||
|
EnodeURLImpl.builder() |
||||||
|
.ipAddress(InetAddress.getLoopbackAddress().getHostAddress()) |
||||||
|
.nodeId(nodeId) |
||||||
|
.discoveryAndListeningPorts(listenPort) |
||||||
|
.build()); |
||||||
|
} |
||||||
|
|
||||||
|
private static SubProtocol subProtocol() { |
||||||
|
return subProtocol("eth"); |
||||||
|
} |
||||||
|
|
||||||
|
private static SubProtocol subProtocol(final String name) { |
||||||
|
return new SubProtocol() { |
||||||
|
@Override |
||||||
|
public String getName() { |
||||||
|
return name; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int messageSpace(final int protocolVersion) { |
||||||
|
return 8; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isValidMessageCode(final int protocolVersion, final int code) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String messageName(final int protocolVersion, final int code) { |
||||||
|
return INVALID_MESSAGE_NAME; |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
private Path initNSSConfigFile(final Path srcFilePath) { |
||||||
|
Path ret = null; |
||||||
|
try { |
||||||
|
final String content = Files.readString(srcFilePath); |
||||||
|
final String updated = |
||||||
|
content.replaceAll( |
||||||
|
"(nssSecmodDirectory\\W*)(\\.\\/.*)", |
||||||
|
"$1".concat(srcFilePath.toAbsolutePath().toString().replace("nss.cfg", "nssdb"))); |
||||||
|
final Path targetFilePath = createTemporaryFile("nsscfg"); |
||||||
|
Files.write(targetFilePath, updated.getBytes(Charsets.UTF_8)); |
||||||
|
ret = targetFilePath; |
||||||
|
} catch (IOException e) { |
||||||
|
throw new RuntimeException("Error populating nss config file", e); |
||||||
|
} |
||||||
|
return ret; |
||||||
|
} |
||||||
|
|
||||||
|
private Path createTemporaryFile(final String suffix) { |
||||||
|
final File tempFile; |
||||||
|
try { |
||||||
|
tempFile = File.createTempFile("temp", suffix); |
||||||
|
tempFile.deleteOnExit(); |
||||||
|
} catch (IOException e) { |
||||||
|
throw new RuntimeException("Error creating temporary file", e); |
||||||
|
} |
||||||
|
return tempFile.toPath(); |
||||||
|
} |
||||||
|
|
||||||
|
private static Path toPath(final String path) throws Exception { |
||||||
|
return Path.of(P2PPlainNetworkTest.class.getResource(path).toURI()); |
||||||
|
} |
||||||
|
|
||||||
|
public Optional<TLSConfiguration> p2pTLSEnabled(final String name, final String type) { |
||||||
|
final TLSConfiguration.Builder builder = TLSConfiguration.Builder.tlsConfiguration(); |
||||||
|
try { |
||||||
|
final String nsspin = "/keys/%s/nsspin.txt"; |
||||||
|
final String truststore = "/keys/%s/truststore.jks"; |
||||||
|
final String crl = "/keys/%s/crl.pem"; |
||||||
|
switch (type) { |
||||||
|
case KeyStoreWrapper.KEYSTORE_TYPE_JKS: |
||||||
|
builder |
||||||
|
.withKeyStoreType(type) |
||||||
|
.withKeyStorePath(toPath(String.format("/keys/%s/keystore.jks", name))) |
||||||
|
.withKeyStorePasswordSupplier( |
||||||
|
new FileBasedPasswordProvider(toPath(String.format(nsspin, name)))) |
||||||
|
.withKeyStorePasswordPath(toPath(String.format(nsspin, name))) |
||||||
|
.withTrustStoreType(type) |
||||||
|
.withTrustStorePath(toPath(String.format(truststore, name))) |
||||||
|
.withTrustStorePasswordSupplier( |
||||||
|
new FileBasedPasswordProvider(toPath(String.format(nsspin, name)))) |
||||||
|
.withTrustStorePasswordPath(toPath(String.format(nsspin, name))) |
||||||
|
.withCrlPath(toPath(String.format(crl, name))); |
||||||
|
break; |
||||||
|
case KeyStoreWrapper.KEYSTORE_TYPE_PKCS12: |
||||||
|
builder |
||||||
|
.withKeyStoreType(type) |
||||||
|
.withKeyStorePath(toPath(String.format("/keys/%s/keys.p12", name))) |
||||||
|
.withKeyStorePasswordSupplier( |
||||||
|
new FileBasedPasswordProvider(toPath(String.format(nsspin, name)))) |
||||||
|
.withKeyStorePasswordPath(toPath(String.format(nsspin, name))) |
||||||
|
.withTrustStoreType(KeyStoreWrapper.KEYSTORE_TYPE_JKS) |
||||||
|
.withTrustStorePath(toPath(String.format(truststore, name))) |
||||||
|
.withTrustStorePasswordSupplier( |
||||||
|
new FileBasedPasswordProvider(toPath(String.format(nsspin, name)))) |
||||||
|
.withTrustStorePasswordPath(toPath(String.format(nsspin, name))) |
||||||
|
.withCrlPath(toPath(String.format(crl, name))); |
||||||
|
break; |
||||||
|
case KeyStoreWrapper.KEYSTORE_TYPE_PKCS11: |
||||||
|
builder |
||||||
|
.withKeyStoreType(type) |
||||||
|
.withKeyStorePath(initNSSConfigFile(toPath(String.format("/keys/%s/nss.cfg", name)))) |
||||||
|
.withKeyStorePasswordSupplier( |
||||||
|
new FileBasedPasswordProvider(toPath(String.format(nsspin, name)))) |
||||||
|
.withKeyStorePasswordPath(toPath(String.format(nsspin, name))) |
||||||
|
.withCrlPath(toPath(String.format(crl, name))); |
||||||
|
break; |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RuntimeException(e); |
||||||
|
} |
||||||
|
return Optional.of(builder.build()); |
||||||
|
} |
||||||
|
|
||||||
|
private DefaultP2PNetwork.Builder builder(final String name) { |
||||||
|
return DefaultP2PNetwork.builder() |
||||||
|
.vertx(vertx) |
||||||
|
.config(config) |
||||||
|
.nodeKey(NodeKeyUtils.generate()) |
||||||
|
.p2pTLSConfiguration(p2pTLSEnabled(name, KeyStoreWrapper.KEYSTORE_TYPE_JKS)) |
||||||
|
.metricsSystem(new NoOpMetricsSystem()) |
||||||
|
.supportedCapabilities(Arrays.asList(Capability.create("eth", 63))) |
||||||
|
.storageProvider(new InMemoryKeyValueStorageProvider()) |
||||||
|
.forkIdSupplier(() -> Collections.singletonList(Bytes.EMPTY)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,65 @@ |
|||||||
|
/* |
||||||
|
* Copyright Hyperledger Besu Contributors. |
||||||
|
* |
||||||
|
* Licensed 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. |
||||||
|
* |
||||||
|
* SPDX-License-Identifier: Apache-2.0 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.ethereum.p2p.plain; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
import io.netty.buffer.Unpooled; |
||||||
|
import org.apache.tuweni.bytes.Bytes; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
public class MessageHandlerTest { |
||||||
|
|
||||||
|
private static final byte[] PING_DATA = new byte[] {0, 1, 2, 3}; |
||||||
|
|
||||||
|
private static final byte[] PONG_DATA = new byte[] {3, 2, 1, 0}; |
||||||
|
|
||||||
|
private static final byte[] MESSAGE_DATA = new byte[] {0, 1, 1, 0}; |
||||||
|
|
||||||
|
private static final Bytes MESSAGE = Bytes.wrap(MESSAGE_DATA); |
||||||
|
|
||||||
|
private static final int DATA_CODE = 1000; |
||||||
|
|
||||||
|
@Test |
||||||
|
public void buildPingMessage() throws Exception { |
||||||
|
Bytes message = MessageHandler.buildMessage(MessageType.PING, PING_DATA); |
||||||
|
assertThat(message).isNotNull(); |
||||||
|
PlainMessage parsed = MessageHandler.parseMessage(Unpooled.wrappedBuffer(message.toArray())); |
||||||
|
assertThat(parsed).isNotNull(); |
||||||
|
assertThat(parsed.getMessageType()).isEqualTo(MessageType.PING); |
||||||
|
assertThat(parsed.getData().toArray()).isEqualTo(PING_DATA); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void buildPongMessage() throws Exception { |
||||||
|
Bytes message = MessageHandler.buildMessage(MessageType.PONG, PONG_DATA); |
||||||
|
assertThat(message).isNotNull(); |
||||||
|
PlainMessage parsed = MessageHandler.parseMessage(Unpooled.wrappedBuffer(message.toArray())); |
||||||
|
assertThat(parsed).isNotNull(); |
||||||
|
assertThat(parsed.getMessageType()).isEqualTo(MessageType.PONG); |
||||||
|
assertThat(parsed.getData().toArray()).isEqualTo(PONG_DATA); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void buildDataMessage() throws Exception { |
||||||
|
Bytes message = MessageHandler.buildMessage(MessageType.DATA, DATA_CODE, MESSAGE); |
||||||
|
assertThat(message).isNotNull(); |
||||||
|
PlainMessage parsed = MessageHandler.parseMessage(Unpooled.wrappedBuffer(message.toArray())); |
||||||
|
assertThat(parsed).isNotNull(); |
||||||
|
assertThat(parsed.getMessageType()).isEqualTo(MessageType.DATA); |
||||||
|
assertThat(parsed.getCode()).isEqualTo(DATA_CODE); |
||||||
|
assertThat(parsed.getData().toArray()).isEqualTo(MESSAGE_DATA); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue