use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class AbstractVertxBasedMqttProtocolAdapter method uploadCommandResponseMessage.
/**
* Uploads a command response message.
*
* @param ctx The context in which the MQTT message has been published.
* @param targetAddress The address that the response should be forwarded to.
* @return A future indicating the outcome of the operation.
* <p>
* The future will succeed if the message has been uploaded successfully.
* Otherwise, the future will fail with a {@link ServiceInvocationException}.
* @throws NullPointerException if any of the parameters are {@code null}.
*/
public final Future<Void> uploadCommandResponseMessage(final MqttContext ctx, final ResourceIdentifier targetAddress) {
Objects.requireNonNull(ctx);
Objects.requireNonNull(targetAddress);
final String[] addressPath = targetAddress.getResourcePath();
Integer status = null;
String reqId = null;
final Future<CommandResponse> commandResponseTracker;
if (addressPath.length <= CommandConstants.TOPIC_POSITION_RESPONSE_STATUS) {
commandResponseTracker = Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "command response topic has too few segments"));
} else {
try {
status = Integer.parseInt(addressPath[CommandConstants.TOPIC_POSITION_RESPONSE_STATUS]);
} catch (final NumberFormatException e) {
log.trace("got invalid status code [{}] [tenant-id: {}, device-id: {}]", addressPath[CommandConstants.TOPIC_POSITION_RESPONSE_STATUS], targetAddress.getTenantId(), targetAddress.getResourceId());
}
if (status != null) {
reqId = addressPath[CommandConstants.TOPIC_POSITION_RESPONSE_REQ_ID];
final CommandResponse commandResponse = CommandResponse.fromRequestId(reqId, targetAddress.getTenantId(), targetAddress.getResourceId(), ctx.message().payload(), ctx.contentType(), status);
commandResponseTracker = commandResponse != null ? Future.succeededFuture(commandResponse) : Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "command response topic contains invalid data"));
} else {
// status code could not be parsed
commandResponseTracker = Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "invalid status code"));
}
}
final Span currentSpan = TracingHelper.buildChildSpan(tracer, ctx.getTracingContext(), "upload Command response", getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, targetAddress.getTenantId()).withTag(TracingHelper.TAG_DEVICE_ID, targetAddress.getResourceId()).withTag(Constants.HEADER_COMMAND_RESPONSE_STATUS, status).withTag(Constants.HEADER_COMMAND_REQUEST_ID, reqId).withTag(TracingHelper.TAG_AUTHENTICATED.getKey(), ctx.authenticatedDevice() != null).start();
final int payloadSize = Optional.ofNullable(ctx.message().payload()).map(Buffer::length).orElse(0);
final Future<TenantObject> tenantTracker = getTenantConfiguration(targetAddress.getTenantId(), ctx.getTracingContext());
return CompositeFuture.all(tenantTracker, commandResponseTracker).compose(success -> {
final Future<RegistrationAssertion> deviceRegistrationTracker = getRegistrationAssertion(targetAddress.getTenantId(), targetAddress.getResourceId(), ctx.authenticatedDevice(), currentSpan.context());
final Future<Void> tenantValidationTracker = CompositeFuture.all(isAdapterEnabled(tenantTracker.result()), checkMessageLimit(tenantTracker.result(), payloadSize, currentSpan.context())).mapEmpty();
return CompositeFuture.all(deviceRegistrationTracker, tenantValidationTracker).compose(ok -> sendCommandResponse(tenantTracker.result(), deviceRegistrationTracker.result(), commandResponseTracker.result(), currentSpan.context()));
}).compose(delivery -> {
log.trace("successfully forwarded command response from device [tenant-id: {}, device-id: {}]", targetAddress.getTenantId(), targetAddress.getResourceId());
metrics.reportCommand(Direction.RESPONSE, targetAddress.getTenantId(), tenantTracker.result(), ProcessingOutcome.FORWARDED, payloadSize, ctx.getTimer());
// check that the remote MQTT client is still connected before sending PUBACK
if (ctx.isAtLeastOnce() && ctx.deviceEndpoint().isConnected()) {
currentSpan.log(EVENT_SENDING_PUBACK);
ctx.acknowledge();
}
currentSpan.finish();
return Future.<Void>succeededFuture();
}).recover(t -> {
TracingHelper.logError(currentSpan, t);
currentSpan.finish();
metrics.reportCommand(Direction.RESPONSE, targetAddress.getTenantId(), tenantTracker.result(), ProcessingOutcome.from(t), payloadSize, ctx.getTimer());
return Future.failedFuture(t);
});
}
use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class ErrorSubscription method validateTopic.
private static ResourceIdentifier validateTopic(final String topic) {
Objects.requireNonNull(topic);
if (!ResourceIdentifier.isValid(topic)) {
throw new IllegalArgumentException("topic filter or its first segment must not be empty");
}
final ResourceIdentifier resource = ResourceIdentifier.fromString(topic);
if (resource.length() != 4 || !"#".equals(resource.elementAt(resource.length() - 1))) {
throw new IllegalArgumentException("invalid topic filter: must have 4 segments and end with '#'");
}
if (!isErrorEndpoint(resource.getEndpoint())) {
throw new IllegalArgumentException("the endpoint needs to be '" + ERROR_ENDPOINT + "' or '" + ERROR_ENDPOINT_SHORT + "'");
}
return resource;
}
use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class CommandSubscription method fromTopic.
/**
* Creates a command subscription object for the given topic.
* <p>
* If the authenticated device is given, it is used to either validate the tenant and device-id
* given via the topic or, if the topic doesn't contain these values, the authenticated device
* is used to provide tenant and device-id for the created command subscription object.
*
* @param topic The topic to subscribe for commands.
* @param qos The quality-of-service level for the subscription.
* @param authenticatedDevice The authenticated device or {@code null}.
* @return The CommandSubscription object or {@code null} if the topic does not match the rules.
* @throws NullPointerException if topic or qos is {@code null}.
*/
public static CommandSubscription fromTopic(final String topic, final MqttQoS qos, final Device authenticatedDevice) {
Objects.requireNonNull(topic);
Objects.requireNonNull(qos);
try {
final ResourceIdentifier topicResource = validateTopic(topic);
return new CommandSubscription(topicResource, authenticatedDevice, qos);
} catch (final IllegalArgumentException e) {
LOG.debug(e.getMessage());
return null;
}
}
use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class VertxBasedMqttProtocolAdapter method mapTopic.
Future<ResourceIdentifier> mapTopic(final MqttContext context) {
final Promise<ResourceIdentifier> result = Promise.promise();
final ResourceIdentifier topic = context.topic();
final MqttQoS qos = context.message().qosLevel();
switch(MetricsTags.EndpointType.fromString(topic.getEndpoint())) {
case TELEMETRY:
if (MqttQoS.EXACTLY_ONCE.equals(qos)) {
// client tries to send telemetry message using QoS 2
result.fail(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "QoS 2 not supported for telemetry messages"));
} else {
result.complete(topic);
}
break;
case EVENT:
if (MqttQoS.AT_LEAST_ONCE.equals(qos)) {
result.complete(topic);
} else {
// client tries to send event message using QoS 0 or 2
result.fail(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "Only QoS 1 supported for event messages"));
}
break;
case COMMAND:
if (MqttQoS.EXACTLY_ONCE.equals(qos)) {
// client tries to send control message using QoS 2
result.fail(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, "QoS 2 not supported for command response messages"));
} else {
result.complete(topic);
}
break;
default:
// MQTT client is trying to publish on a not supported endpoint
log.debug("no such endpoint [{}]", topic.getEndpoint());
result.fail(new ClientErrorException(HttpURLConnection.HTTP_NOT_FOUND, "no such endpoint"));
}
return result.future();
}
use of org.eclipse.hono.util.ResourceIdentifier in project hono by eclipse.
the class DelegatingTenantAmqpEndpoint method isAuthorized.
/**
* Checks if the client is authorized to invoke an operation.
* <p>
* If the request does not include a <em>tenant_id</em> application property
* then the request is authorized by default. This behavior allows clients to
* invoke operations that do not require a tenant ID as a parameter.
* <p>
* If the request does contain a tenant ID parameter in its application properties
* then this tenant ID is used for the authorization check together with the
* endpoint and operation name.
*
* @param clientPrincipal The client.
* @param resource The resource the operation belongs to.
* @param request The message for which the authorization shall be checked.
* @return The outcome of the check.
* @throws NullPointerException if any of the parameters is {@code null}.
*/
@Override
protected Future<Boolean> isAuthorized(final HonoUser clientPrincipal, final ResourceIdentifier resource, final Message request) {
Objects.requireNonNull(request);
final String tenantId = MessageHelper.getTenantId(request);
if (tenantId == null) {
// delegate authorization check to filterResource operation
return Future.succeededFuture(Boolean.TRUE);
} else {
final ResourceIdentifier specificTenantAddress = ResourceIdentifier.fromPath(new String[] { resource.getEndpoint(), tenantId });
return getAuthorizationService().isAuthorized(clientPrincipal, specificTenantAddress, request.getSubject());
}
}
Aggregations