use of org.eclipse.hono.auth.Device in project hono by eclipse.
the class AbstractVertxBasedMqttProtocolAdapterTest method testDeviceConnectionIsClosedOnTenantDisabledNotification.
/**
* Verifies that the adapter closes the connection to an authenticated device when a notification
* has been received that the tenant of the device has been disabled.
*
* @param ctx The vert.x test context.
*/
@Test
public void testDeviceConnectionIsClosedOnTenantDisabledNotification(final VertxTestContext ctx) {
final Device device = new Device("tenant", "deviceId");
testDeviceConnectionIsClosedOnDeviceOrTenantChangeNotification(ctx, device, new TenantChangeNotification(LifecycleChange.UPDATE, "tenant", Instant.now(), false));
}
use of org.eclipse.hono.auth.Device in project hono by eclipse.
the class AbstractVertxBasedMqttProtocolAdapterTest method testUploadTelemetryMessageIncludesRetainAnnotation.
/**
* Verifies that the adapter includes a message annotation in a downstream message if the device publishes a message
* with its <em>retain</em> flag set.
*
* @param ctx The vert.x test context.
*/
@Test
public void testUploadTelemetryMessageIncludesRetainAnnotation(final VertxTestContext ctx) {
// GIVEN an adapter with a downstream telemetry consumer
givenAnAdapter(properties);
givenATelemetrySenderForAnyTenant();
// WHEN a device publishes a message with its retain flag set
final MqttEndpoint endpoint = mockEndpoint();
when(endpoint.isConnected()).thenReturn(Boolean.TRUE);
final Buffer payload = Buffer.buffer("hello");
final MqttPublishMessage messageFromDevice = mock(MqttPublishMessage.class);
when(messageFromDevice.qosLevel()).thenReturn(MqttQoS.AT_LEAST_ONCE);
when(messageFromDevice.messageId()).thenReturn(5555555);
when(messageFromDevice.topicName()).thenReturn("t/my-tenant/4712");
when(messageFromDevice.isRetain()).thenReturn(Boolean.TRUE);
when(messageFromDevice.payload()).thenReturn(payload);
final MqttContext context = newMqttContext(messageFromDevice, endpoint, span);
adapter.uploadTelemetryMessage(context, "my-tenant", "4712", payload).onComplete(ctx.succeeding(ok -> {
ctx.verify(() -> {
// THEN the device has received a PUBACK
verify(endpoint).publishAcknowledge(5555555);
// and the message has been sent downstream
// including the "retain" annotation
verify(telemetrySender).sendTelemetry(argThat(tenant -> tenant.getTenantId().equals("my-tenant")), argThat(assertion -> assertion.getDeviceId().equals("4712")), eq(QoS.AT_LEAST_ONCE), any(), any(), argThat(props -> props.get(MessageHelper.ANNOTATION_X_OPT_RETAIN).equals(Boolean.TRUE)), any());
verify(metrics).reportTelemetry(eq(MetricsTags.EndpointType.TELEMETRY), eq("my-tenant"), any(), eq(MetricsTags.ProcessingOutcome.FORWARDED), eq(MetricsTags.QoS.AT_LEAST_ONCE), eq(payload.length()), any());
});
ctx.completeNow();
}));
}
use of org.eclipse.hono.auth.Device in project hono by eclipse.
the class AbstractVertxBasedMqttProtocolAdapter method uploadMessage.
private Future<Void> uploadMessage(final MqttContext ctx, final TenantObject tenantObject, final String deviceId, final Buffer payload, final MetricsTags.EndpointType endpoint) {
if (!isPayloadOfIndicatedType(payload, ctx.contentType())) {
return Future.failedFuture(new ClientErrorException(HttpURLConnection.HTTP_BAD_REQUEST, String.format("Content-Type %s does not match payload", ctx.contentType())));
}
final Span currentSpan = TracingHelper.buildChildSpan(tracer, ctx.getTracingContext(), "upload " + endpoint, getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenantObject.getTenantId()).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).withTag(TracingHelper.TAG_AUTHENTICATED.getKey(), ctx.authenticatedDevice() != null).start();
final Future<RegistrationAssertion> tokenTracker = getRegistrationAssertion(tenantObject.getTenantId(), deviceId, ctx.authenticatedDevice(), currentSpan.context());
final Future<TenantObject> tenantValidationTracker = CompositeFuture.all(isAdapterEnabled(tenantObject), checkMessageLimit(tenantObject, payload.length(), currentSpan.context())).map(tenantObject);
return CompositeFuture.all(tokenTracker, tenantValidationTracker).compose(ok -> {
final Map<String, Object> props = getDownstreamMessageProperties(ctx);
props.put(MessageHelper.APP_PROPERTY_QOS, ctx.getRequestedQos().ordinal());
addRetainAnnotation(ctx, props, currentSpan);
customizeDownstreamMessageProperties(props, ctx);
if (endpoint == EndpointType.EVENT) {
return getEventSender(tenantObject).sendEvent(tenantObject, tokenTracker.result(), ctx.contentType(), payload, props, currentSpan.context());
} else {
return getTelemetrySender(tenantObject).sendTelemetry(tenantObject, tokenTracker.result(), ctx.getRequestedQos(), ctx.contentType(), payload, props, currentSpan.context());
}
}).map(ok -> {
log.trace("successfully processed message [topic: {}, QoS: {}] from device [tenantId: {}, deviceId: {}]", ctx.message().topicName(), ctx.message().qosLevel(), tenantObject.getTenantId(), deviceId);
// 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 ok;
}).recover(t -> {
if (ClientErrorException.class.isInstance(t)) {
final ClientErrorException e = (ClientErrorException) t;
log.debug("cannot process message [endpoint: {}] from device [tenantId: {}, deviceId: {}]: {} - {}", endpoint, tenantObject.getTenantId(), deviceId, e.getErrorCode(), e.getMessage());
} else {
log.debug("cannot process message [endpoint: {}] from device [tenantId: {}, deviceId: {}]", endpoint, tenantObject.getTenantId(), deviceId, t);
}
TracingHelper.logError(currentSpan, t);
currentSpan.finish();
return Future.failedFuture(t);
});
}
use of org.eclipse.hono.auth.Device 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.auth.Device in project hono by eclipse.
the class LoraProtocolAdapter method handleProviderRoute.
void handleProviderRoute(final HttpContext ctx, final LoraProvider provider) {
if (LOG.isDebugEnabled()) {
LOG.debug("processing request from provider [name: {}, URI: {}]", provider.getProviderName(), ctx.getRoutingContext().normalizedPath());
}
final Span currentSpan = TracingHelper.buildServerChildSpan(tracer, TracingHandler.serverSpanContext(ctx.getRoutingContext()), SPAN_NAME_PROCESS_MESSAGE, getClass().getSimpleName()).start();
TAG_LORA_PROVIDER.set(currentSpan, provider.getProviderName());
ctx.put(LoraConstants.APP_PROPERTY_ORIG_LORA_PROVIDER, provider.getProviderName());
if (!ctx.isDeviceAuthenticated()) {
logUnsupportedUserType(ctx.getRoutingContext(), currentSpan);
currentSpan.finish();
handle401(ctx.getRoutingContext());
return;
}
final Device gatewayDevice = ctx.getAuthenticatedDevice();
TracingHelper.setDeviceTags(currentSpan, gatewayDevice.getTenantId(), gatewayDevice.getDeviceId());
try {
final LoraMessage loraMessage = provider.getMessage(ctx.getRoutingContext());
final LoraMessageType type = loraMessage.getType();
currentSpan.log(Map.of("message type", type));
final String deviceId = loraMessage.getDevEUIAsString();
currentSpan.setTag(TAG_LORA_DEVICE_ID, deviceId);
switch(type) {
case UPLINK:
final UplinkLoraMessage uplinkMessage = (UplinkLoraMessage) loraMessage;
final Buffer payload = uplinkMessage.getPayload();
Optional.ofNullable(uplinkMessage.getMetaData()).ifPresent(metaData -> ctx.put(LoraConstants.APP_PROPERTY_META_DATA, metaData));
Optional.ofNullable(uplinkMessage.getAdditionalData()).ifPresent(additionalData -> ctx.put(LoraConstants.APP_PROPERTY_ADDITIONAL_DATA, additionalData));
final String contentType = payload.length() > 0 ? LoraConstants.CONTENT_TYPE_LORA_BASE + provider.getProviderName() : EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION;
// uploadTelemetryMessage will finish the root span, therefore finish child span here already
currentSpan.finish();
uploadTelemetryMessage(ctx, gatewayDevice.getTenantId(), deviceId, payload, contentType);
registerCommandConsumerIfNeeded(provider, gatewayDevice, currentSpan.context());
break;
default:
LOG.debug("discarding message of unsupported type [tenant: {}, device-id: {}, type: {}]", gatewayDevice.getTenantId(), deviceId, type);
currentSpan.log("discarding message of unsupported type");
currentSpan.finish();
// discard the message but return 202 to not cause errors on the LoRa provider side
handle202(ctx.getRoutingContext());
}
} catch (final LoraProviderMalformedPayloadException e) {
LOG.debug("error processing request from provider [name: {}]", provider.getProviderName(), e);
TracingHelper.logError(currentSpan, "error processing request", e);
currentSpan.finish();
handle400(ctx.getRoutingContext(), ERROR_MSG_INVALID_PAYLOAD);
}
}
Aggregations