use of io.vertx.proton.ProtonQoS in project hono by eclipse.
the class AmqpUploadTestBase method testUploadMessagesUsingSaslExternal.
/**
* Verifies that a number of messages uploaded to the AMQP adapter using client certificate
* based authentication can be successfully consumed via the AMQP Messaging Network.
*
* @param senderQos The delivery semantics used by the device for uploading messages.
* @throws InterruptedException if test execution is interrupted.
*/
@ParameterizedTest(name = IntegrationTestSupport.PARAMETERIZED_TEST_NAME_PATTERN)
@MethodSource("senderQoSTypes")
public void testUploadMessagesUsingSaslExternal(final ProtonQoS senderQos) throws InterruptedException {
final String tenantId = helper.getRandomTenantId();
final String deviceId = helper.getRandomDeviceId(tenantId);
final SelfSignedCertificate deviceCert = SelfSignedCertificate.create(deviceId + ".iot.eclipse.org");
final VertxTestContext setup = new VertxTestContext();
helper.getCertificate(deviceCert.certificatePath()).compose(cert -> {
final var tenant = Tenants.createTenantForTrustAnchor(cert);
prepareTenantConfig(tenant);
return helper.registry.addDeviceForTenant(tenantId, tenant, deviceId, cert);
}).compose(ok -> connectToAdapter(deviceCert)).compose(con -> createProducer(null, senderQos)).onComplete(setup.succeeding(s -> {
setup.verify(() -> {
final UnsignedLong maxMessageSize = s.getRemoteMaxMessageSize();
assertWithMessage("max-message-size included in adapter's attach frame").that(maxMessageSize).isNotNull();
assertWithMessage("max-message-size").that(maxMessageSize.longValue()).isGreaterThan(0);
});
sender = s;
setup.completeNow();
}));
assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
assertWithMessage("adapter connection failure occurred").that(setup.failed()).isFalse();
testUploadMessages(tenantId, senderQos);
}
use of io.vertx.proton.ProtonQoS in project hono by eclipse.
the class HonoConnectionImpl method createReceiver.
@Override
public Future<ProtonReceiver> createReceiver(final String sourceAddress, final ProtonQoS qos, final ProtonMessageHandler messageHandler, final int preFetchSize, final boolean autoAccept, final Handler<String> remoteCloseHook) {
Objects.requireNonNull(sourceAddress);
Objects.requireNonNull(qos);
Objects.requireNonNull(messageHandler);
if (preFetchSize < 0) {
throw new IllegalArgumentException("pre-fetch size must be >= 0");
}
return executeOnContext(result -> {
checkConnected().compose(v -> {
final Promise<ProtonReceiver> receiverPromise = Promise.promise();
final ProtonReceiver receiver = session.createReceiver(sourceAddress);
if (clientConfigProperties.getMaxMessageSize() > ClientConfigProperties.MAX_MESSAGE_SIZE_UNLIMITED) {
receiver.setMaxMessageSize(new UnsignedLong(clientConfigProperties.getMaxMessageSize()));
}
receiver.setAutoAccept(autoAccept);
receiver.setQoS(qos);
receiver.setPrefetch(preFetchSize);
receiver.handler((delivery, message) -> {
HonoProtonHelper.onReceivedMessageDeliveryUpdatedFromRemote(delivery, d -> log.debug("got unexpected disposition update for received message [remote state: {}]", delivery.getRemoteState()));
try {
messageHandler.handle(delivery, message);
if (log.isTraceEnabled()) {
final int remainingCredits = receiver.getCredit() - receiver.getQueued();
log.trace("handling message [remotely settled: {}, queued messages: {}, remaining credit: {}]", delivery.remotelySettled(), receiver.getQueued(), remainingCredits);
}
} catch (final Exception ex) {
log.warn("error handling message", ex);
ProtonHelper.released(delivery, true);
}
});
final DisconnectListener<HonoConnection> disconnectBeforeOpenListener = (con) -> {
log.debug("opening receiver [{}] failed: got disconnected", sourceAddress);
receiverPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "not connected"));
};
oneTimeDisconnectListeners.add(disconnectBeforeOpenListener);
receiver.openHandler(recvOpen -> {
oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
// the result future may have already been completed here in case of a link establishment timeout
if (receiverPromise.future().isComplete()) {
log.debug("ignoring server response for opening receiver [{}]: receiver creation already timed out", sourceAddress);
} else if (recvOpen.failed()) {
// this means that we have received the peer's attach
// and the subsequent detach frame in one TCP read
final ErrorCondition error = receiver.getRemoteCondition();
if (error == null) {
log.debug("opening receiver [{}] failed", sourceAddress, recvOpen.cause());
receiverPromise.tryFail(new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND, "cannot open receiver", recvOpen.cause()));
} else {
log.debug("opening receiver [{}] failed: {} - {}", sourceAddress, error.getCondition(), error.getDescription());
receiverPromise.tryFail(StatusCodeMapper.fromAttachError(error));
}
} else if (HonoProtonHelper.isLinkEstablished(receiver)) {
log.debug("receiver open [source: {}]", sourceAddress);
receiverPromise.tryComplete(recvOpen.result());
} else {
// this means that the peer did not create a local terminus for the link
// and will send a detach frame for closing the link very shortly
// see AMQP 1.0 spec section 2.6.3
log.debug("peer did not create terminus for source [{}] and will detach the link", sourceAddress);
receiverPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE));
}
});
HonoProtonHelper.setDetachHandler(receiver, remoteDetached -> onRemoteDetach(receiver, connection.getRemoteContainer(), false, remoteCloseHook));
HonoProtonHelper.setCloseHandler(receiver, remoteClosed -> onRemoteDetach(receiver, connection.getRemoteContainer(), true, remoteCloseHook));
receiver.open();
vertx.setTimer(clientConfigProperties.getLinkEstablishmentTimeout(), tid -> {
final boolean notOpenedAndNotDisconnectedYet = oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
if (notOpenedAndNotDisconnectedYet) {
onLinkEstablishmentTimeout(receiver, clientConfigProperties, receiverPromise);
}
});
return receiverPromise.future();
}).onComplete(result);
});
}
use of io.vertx.proton.ProtonQoS in project hono by eclipse.
the class HonoConnectionImpl method createSender.
/**
* Creates a sender link.
*
* @param targetAddress The target address of the link. If the address is {@code null}, the
* sender link will be established to the 'anonymous relay' and each
* message must specify its destination address.
* @param qos The quality of service to use for the link.
* @param closeHook The handler to invoke when the link is closed by the peer (may be {@code null}).
* @return A future for the created link. The future will be completed once the link is open.
* The future will fail with a {@link ServiceInvocationException} if the link cannot be opened.
* @throws NullPointerException if qos is {@code null}.
*/
@Override
public final Future<ProtonSender> createSender(final String targetAddress, final ProtonQoS qos, final Handler<String> closeHook) {
Objects.requireNonNull(qos);
return executeOnContext(result -> {
checkConnected().compose(v -> {
if (targetAddress == null && !supportsCapability(Constants.CAP_ANONYMOUS_RELAY)) {
// before a client can use anonymous terminus
return Future.failedFuture(new ServerErrorException(HttpURLConnection.HTTP_NOT_IMPLEMENTED, "server does not support anonymous terminus"));
}
final Promise<ProtonSender> senderPromise = Promise.promise();
final ProtonSender sender = session.createSender(targetAddress);
sender.setQoS(qos);
sender.setAutoSettle(true);
final DisconnectListener<HonoConnection> disconnectBeforeOpenListener = (con) -> {
log.debug("opening sender [{}] failed: got disconnected", targetAddress);
senderPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE, "not connected"));
};
oneTimeDisconnectListeners.add(disconnectBeforeOpenListener);
sender.openHandler(senderOpen -> {
oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
// the result future may have already been completed here in case of a link establishment timeout
if (senderPromise.future().isComplete()) {
log.debug("ignoring server response for opening sender [{}]: sender creation already timed out", targetAddress);
} else if (senderOpen.failed()) {
// this means that we have received the peer's attach
// and the subsequent detach frame in one TCP read
final ErrorCondition error = sender.getRemoteCondition();
if (error == null) {
log.debug("opening sender [{}] failed", targetAddress, senderOpen.cause());
senderPromise.tryFail(new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND, "cannot open sender", senderOpen.cause()));
} else {
log.debug("opening sender [{}] failed: {} - {}", targetAddress, error.getCondition(), error.getDescription());
senderPromise.tryFail(StatusCodeMapper.fromAttachError(error));
}
} else if (HonoProtonHelper.isLinkEstablished(sender)) {
log.debug("sender open [target: {}, sendQueueFull: {}, remote max-message-size: {}]", targetAddress, sender.sendQueueFull(), sender.getRemoteMaxMessageSize());
final long remoteMaxMessageSize = Optional.ofNullable(sender.getRemoteMaxMessageSize()).map(UnsignedLong::longValue).orElse(0L);
if (remoteMaxMessageSize > 0 && remoteMaxMessageSize < clientConfigProperties.getMinMaxMessageSize()) {
// peer won't accept our (biggest) messages
sender.close();
final String msg = String.format("peer does not support minimum max-message-size [required: %d, supported: %d", clientConfigProperties.getMinMaxMessageSize(), remoteMaxMessageSize);
log.debug(msg);
senderPromise.tryFail(new ClientErrorException(HttpURLConnection.HTTP_PRECON_FAILED, msg));
} else if (sender.getCredit() <= 0) {
// wait on credits a little time, if not already given
final long waitOnCreditsTimerId = vertx.setTimer(clientConfigProperties.getFlowLatency(), timerID -> {
log.debug("sender [target: {}] has {} credits after grace period of {}ms", targetAddress, sender.getCredit(), clientConfigProperties.getFlowLatency());
sender.sendQueueDrainHandler(null);
senderPromise.tryComplete(sender);
});
sender.sendQueueDrainHandler(replenishedSender -> {
log.debug("sender [target: {}] has received {} initial credits", targetAddress, replenishedSender.getCredit());
if (vertx.cancelTimer(waitOnCreditsTimerId)) {
result.tryComplete(replenishedSender);
replenishedSender.sendQueueDrainHandler(null);
}
// otherwise the timer has already completed the future and cleaned up
// sendQueueDrainHandler
});
} else {
senderPromise.tryComplete(sender);
}
} else {
// this means that the peer did not create a local terminus for the link
// and will send a detach frame for closing the link very shortly
// see AMQP 1.0 spec section 2.6.3
log.debug("peer did not create terminus for target [{}] and will detach the link", targetAddress);
senderPromise.tryFail(new ServerErrorException(HttpURLConnection.HTTP_UNAVAILABLE));
}
});
HonoProtonHelper.setDetachHandler(sender, remoteDetached -> onRemoteDetach(sender, connection.getRemoteContainer(), false, closeHook));
HonoProtonHelper.setCloseHandler(sender, remoteClosed -> onRemoteDetach(sender, connection.getRemoteContainer(), true, closeHook));
sender.open();
vertx.setTimer(clientConfigProperties.getLinkEstablishmentTimeout(), tid -> {
final boolean notOpenedAndNotDisconnectedYet = oneTimeDisconnectListeners.remove(disconnectBeforeOpenListener);
if (notOpenedAndNotDisconnectedYet) {
onLinkEstablishmentTimeout(sender, clientConfigProperties, senderPromise);
}
});
return senderPromise.future();
}).onComplete(result);
});
}
use of io.vertx.proton.ProtonQoS in project hono by eclipse.
the class AmqpUploadTestBase method testUploadMessagesUsingSaslPlain.
/**
* Verifies that a number of messages published through the AMQP adapter can be successfully consumed by
* applications connected to the AMQP messaging network.
*
* @param senderQos The delivery semantics to use for the device.
* @throws InterruptedException if test is interrupted while running.
*/
@ParameterizedTest(name = IntegrationTestSupport.PARAMETERIZED_TEST_NAME_PATTERN)
@MethodSource("senderQoSTypes")
public void testUploadMessagesUsingSaslPlain(final ProtonQoS senderQos) throws InterruptedException {
final String tenantId = helper.getRandomTenantId();
final String deviceId = helper.getRandomDeviceId(tenantId);
final Tenant tenantConfig = new Tenant();
prepareTenantConfig(tenantConfig);
final VertxTestContext setup = new VertxTestContext();
setupProtocolAdapter(tenantId, tenantConfig, deviceId, senderQos).onComplete(setup.succeeding(s -> {
setup.verify(() -> {
final UnsignedLong maxMessageSize = s.getRemoteMaxMessageSize();
assertWithMessage("max-message-size included in adapter's attach frame").that(maxMessageSize).isNotNull();
assertWithMessage("max-message-size").that(maxMessageSize.longValue()).isGreaterThan(0);
});
sender = s;
setup.completeNow();
}));
assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
assertWithMessage("adapter connection failure occurred").that(setup.failed()).isFalse();
testUploadMessages(tenantId, senderQos);
}
use of io.vertx.proton.ProtonQoS in project hono by eclipse.
the class AmqpUploadTestBase method testUploadMessages.
// ------------------------------------------< private methods >---
private void testUploadMessages(final String tenantId, final ProtonQoS senderQoS) throws InterruptedException {
final VertxTestContext messageSending = new VertxTestContext();
final Function<Handler<Void>, Future<Void>> receiver = callback -> {
return createConsumer(tenantId, msg -> {
if (log.isTraceEnabled()) {
log.trace("received message [{}]: {}", msg.getContentType(), msg.getPayload().toString());
}
messageSending.verify(() -> {
DownstreamMessageAssertions.assertTelemetryMessageProperties(msg, tenantId);
assertThat(msg.getQos()).isEqualTo(AmqpUploadTestBase.getQoS(senderQoS));
assertAdditionalMessageProperties(msg);
callback.handle(null);
});
}).mapEmpty();
};
doUploadMessages(messageSending, receiver, payload -> {
final Message msg = ProtonHelper.message();
MessageHelper.setPayload(msg, "opaque/binary", payload);
msg.setAddress(getEndpointName());
final Promise<Void> sendingComplete = Promise.promise();
final Handler<ProtonSender> sendMsgHandler = replenishedSender -> {
replenishedSender.sendQueueDrainHandler(null);
switch(senderQoS) {
case AT_LEAST_ONCE:
replenishedSender.send(msg, delivery -> {
if (Accepted.class.isInstance(delivery.getRemoteState())) {
sendingComplete.complete();
} else {
sendingComplete.fail(AmqpErrorException.from(delivery.getRemoteState()));
}
});
break;
case AT_MOST_ONCE:
replenishedSender.send(msg);
sendingComplete.complete();
break;
}
};
context.runOnContext(go -> {
if (sender.getCredit() <= 0) {
log.trace("wait for credit ...");
sender.sendQueueDrainHandler(sendMsgHandler);
} else {
sendMsgHandler.handle(sender);
}
});
return sendingComplete.future();
});
}
Aggregations