47 changed files with 1719 additions and 1067 deletions
@ -0,0 +1,88 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2018 The Thingsboard Authors |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<parent> |
||||
|
<groupId>org.thingsboard.common</groupId> |
||||
|
<version>2.2.0-SNAPSHOT</version> |
||||
|
<artifactId>transport</artifactId> |
||||
|
</parent> |
||||
|
<groupId>org.thingsboard.common.transport</groupId> |
||||
|
<artifactId>coap</artifactId> |
||||
|
<packaging>jar</packaging> |
||||
|
|
||||
|
<name>Thingsboard CoAP Transport Common</name> |
||||
|
<url>https://thingsboard.io</url> |
||||
|
|
||||
|
<properties> |
||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
|
<main.dir>${basedir}/../../..</main.dir> |
||||
|
</properties> |
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>org.thingsboard.common.transport</groupId> |
||||
|
<artifactId>transport-api</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.eclipse.californium</groupId> |
||||
|
<artifactId>californium-core</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework</groupId> |
||||
|
<artifactId>spring-context-support</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework</groupId> |
||||
|
<artifactId>spring-context</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.slf4j</groupId> |
||||
|
<artifactId>slf4j-api</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.slf4j</groupId> |
||||
|
<artifactId>log4j-over-slf4j</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>ch.qos.logback</groupId> |
||||
|
<artifactId>logback-core</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>ch.qos.logback</groupId> |
||||
|
<artifactId>logback-classic</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-test</artifactId> |
||||
|
<scope>test</scope> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>junit</groupId> |
||||
|
<artifactId>junit</artifactId> |
||||
|
<scope>test</scope> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.mockito</groupId> |
||||
|
<artifactId>mockito-all</artifactId> |
||||
|
<scope>test</scope> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
</project> |
||||
@ -0,0 +1,51 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.coap; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.common.transport.TransportContext; |
||||
|
import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 18.10.18. |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.coap.enabled}'=='true')") |
||||
|
@Component |
||||
|
public class CoapTransportContext extends TransportContext { |
||||
|
|
||||
|
@Getter |
||||
|
@Value("${transport.coap.bind_address}") |
||||
|
private String host; |
||||
|
|
||||
|
@Getter |
||||
|
@Value("${transport.coap.bind_port}") |
||||
|
private Integer port; |
||||
|
|
||||
|
@Getter |
||||
|
@Value("${transport.coap.timeout}") |
||||
|
private Long timeout; |
||||
|
|
||||
|
@Getter |
||||
|
@Autowired |
||||
|
private CoapTransportAdaptor adaptor; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,445 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.coap; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.eclipse.californium.core.CoapResource; |
||||
|
import org.eclipse.californium.core.coap.CoAP.ResponseCode; |
||||
|
import org.eclipse.californium.core.coap.Request; |
||||
|
import org.eclipse.californium.core.coap.Response; |
||||
|
import org.eclipse.californium.core.network.Exchange; |
||||
|
import org.eclipse.californium.core.network.ExchangeObserver; |
||||
|
import org.eclipse.californium.core.server.resources.CoapExchange; |
||||
|
import org.eclipse.californium.core.server.resources.Resource; |
||||
|
import org.springframework.util.ReflectionUtils; |
||||
|
import org.thingsboard.server.common.data.security.DeviceTokenCredentials; |
||||
|
import org.thingsboard.server.common.msg.session.FeatureType; |
||||
|
import org.thingsboard.server.common.msg.session.SessionMsgType; |
||||
|
import org.thingsboard.server.common.transport.SessionMsgListener; |
||||
|
import org.thingsboard.server.common.transport.TransportContext; |
||||
|
import org.thingsboard.server.common.transport.TransportService; |
||||
|
import org.thingsboard.server.common.transport.TransportServiceCallback; |
||||
|
import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos; |
||||
|
|
||||
|
import java.lang.reflect.Field; |
||||
|
import java.util.List; |
||||
|
import java.util.Optional; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.concurrent.ConcurrentMap; |
||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
import java.util.function.Consumer; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class CoapTransportResource extends CoapResource { |
||||
|
// coap://localhost:port/api/v1/DEVICE_TOKEN/[attributes|telemetry|rpc[/requestId]]
|
||||
|
private static final int ACCESS_TOKEN_POSITION = 3; |
||||
|
private static final int FEATURE_TYPE_POSITION = 4; |
||||
|
private static final int REQUEST_ID_POSITION = 5; |
||||
|
|
||||
|
private final CoapTransportContext transportContext; |
||||
|
private final TransportService transportService; |
||||
|
private final Field observerField; |
||||
|
private final long timeout; |
||||
|
private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
public CoapTransportResource(CoapTransportContext context, String name) { |
||||
|
super(name); |
||||
|
this.transportContext = context; |
||||
|
this.transportService = context.getTransportService(); |
||||
|
this.timeout = context.getTimeout(); |
||||
|
// This is important to turn off existing observable logic in
|
||||
|
// CoapResource. We will have our own observe monitoring due to 1:1
|
||||
|
// observe relationship.
|
||||
|
this.setObservable(false); |
||||
|
observerField = ReflectionUtils.findField(Exchange.class, "observer"); |
||||
|
observerField.setAccessible(true); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void handleGET(CoapExchange exchange) { |
||||
|
if (transportContext.getQuotaService().isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) { |
||||
|
log.warn("CoAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort()); |
||||
|
exchange.respond(ResponseCode.BAD_REQUEST); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest()); |
||||
|
if (!featureType.isPresent()) { |
||||
|
log.trace("Missing feature type parameter"); |
||||
|
exchange.respond(ResponseCode.BAD_REQUEST); |
||||
|
} else if (featureType.get() == FeatureType.TELEMETRY) { |
||||
|
log.trace("Can't fetch/subscribe to timeseries updates"); |
||||
|
exchange.respond(ResponseCode.BAD_REQUEST); |
||||
|
} else if (exchange.getRequestOptions().hasObserve()) { |
||||
|
processExchangeGetRequest(exchange, featureType.get()); |
||||
|
} else if (featureType.get() == FeatureType.ATTRIBUTES) { |
||||
|
processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); |
||||
|
} else { |
||||
|
log.trace("Invalid feature type parameter"); |
||||
|
exchange.respond(ResponseCode.BAD_REQUEST); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void processExchangeGetRequest(CoapExchange exchange, FeatureType featureType) { |
||||
|
boolean unsubscribe = exchange.getRequestOptions().getObserve() == 1; |
||||
|
SessionMsgType sessionMsgType; |
||||
|
if (featureType == FeatureType.RPC) { |
||||
|
sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; |
||||
|
} else { |
||||
|
sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; |
||||
|
} |
||||
|
processRequest(exchange, sessionMsgType); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void handlePOST(CoapExchange exchange) { |
||||
|
if (transportContext.getQuotaService().isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) { |
||||
|
log.warn("CoAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort()); |
||||
|
exchange.respond(ResponseCode.BAD_REQUEST); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest()); |
||||
|
if (!featureType.isPresent()) { |
||||
|
log.trace("Missing feature type parameter"); |
||||
|
exchange.respond(ResponseCode.BAD_REQUEST); |
||||
|
} else { |
||||
|
switch (featureType.get()) { |
||||
|
case ATTRIBUTES: |
||||
|
processRequest(exchange, SessionMsgType.POST_ATTRIBUTES_REQUEST); |
||||
|
break; |
||||
|
case TELEMETRY: |
||||
|
processRequest(exchange, SessionMsgType.POST_TELEMETRY_REQUEST); |
||||
|
break; |
||||
|
case RPC: |
||||
|
Optional<Integer> requestId = getRequestId(exchange.advanced().getRequest()); |
||||
|
if (requestId.isPresent()) { |
||||
|
processRequest(exchange, SessionMsgType.TO_DEVICE_RPC_RESPONSE); |
||||
|
} else { |
||||
|
processRequest(exchange, SessionMsgType.TO_SERVER_RPC_REQUEST); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void processRequest(CoapExchange exchange, SessionMsgType type) { |
||||
|
log.trace("Processing {}", exchange.advanced().getRequest()); |
||||
|
exchange.accept(); |
||||
|
Exchange advanced = exchange.advanced(); |
||||
|
Request request = advanced.getRequest(); |
||||
|
|
||||
|
Optional<DeviceTokenCredentials> credentials = decodeCredentials(request); |
||||
|
if (!credentials.isPresent()) { |
||||
|
exchange.respond(ResponseCode.BAD_REQUEST); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
transportService.process(TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), |
||||
|
new DeviceAuthCallback(transportContext, exchange, sessionInfo -> { |
||||
|
UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); |
||||
|
try { |
||||
|
switch (type) { |
||||
|
case POST_ATTRIBUTES_REQUEST: |
||||
|
transportService.process(sessionInfo, |
||||
|
transportContext.getAdaptor().convertToPostAttributes(sessionId, request), |
||||
|
new CoapOkCallback(exchange)); |
||||
|
break; |
||||
|
case POST_TELEMETRY_REQUEST: |
||||
|
transportService.process(sessionInfo, |
||||
|
transportContext.getAdaptor().convertToPostTelemetry(sessionId, request), |
||||
|
new CoapOkCallback(exchange)); |
||||
|
break; |
||||
|
case SUBSCRIBE_ATTRIBUTES_REQUEST: |
||||
|
advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced), |
||||
|
registerAsyncCoapSession(exchange, request, sessionInfo, sessionId))); |
||||
|
transportService.process(sessionInfo, |
||||
|
TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), |
||||
|
new CoapNoOpCallback(exchange)); |
||||
|
break; |
||||
|
case UNSUBSCRIBE_ATTRIBUTES_REQUEST: |
||||
|
TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(request); |
||||
|
if (attrSession != null) { |
||||
|
transportService.process(attrSession, |
||||
|
TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), |
||||
|
new CoapOkCallback(exchange)); |
||||
|
closeAndDeregister(sessionInfo); |
||||
|
} |
||||
|
break; |
||||
|
case SUBSCRIBE_RPC_COMMANDS_REQUEST: |
||||
|
advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced), |
||||
|
registerAsyncCoapSession(exchange, request, sessionInfo, sessionId))); |
||||
|
transportService.process(sessionInfo, |
||||
|
TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), |
||||
|
new CoapNoOpCallback(exchange)); |
||||
|
break; |
||||
|
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: |
||||
|
TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(request); |
||||
|
if (rpcSession != null) { |
||||
|
transportService.process(rpcSession, |
||||
|
TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), |
||||
|
new CoapOkCallback(exchange)); |
||||
|
transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); |
||||
|
transportService.deregisterSession(rpcSession); |
||||
|
} |
||||
|
break; |
||||
|
case TO_DEVICE_RPC_RESPONSE: |
||||
|
transportService.process(sessionInfo, |
||||
|
transportContext.getAdaptor().convertToDeviceRpcResponse(sessionId, request), |
||||
|
new CoapOkCallback(exchange)); |
||||
|
break; |
||||
|
case TO_SERVER_RPC_REQUEST: |
||||
|
transportService.process(sessionInfo, |
||||
|
transportContext.getAdaptor().convertToServerRpcRequest(sessionId, request), |
||||
|
new CoapNoOpCallback(exchange)); |
||||
|
break; |
||||
|
case GET_ATTRIBUTES_REQUEST: |
||||
|
transportService.registerSyncSession(sessionInfo, new CoapSessionListener(sessionId, exchange), transportContext.getTimeout()); |
||||
|
transportService.process(sessionInfo, |
||||
|
transportContext.getAdaptor().convertToGetAttributes(sessionId, request), |
||||
|
new CoapNoOpCallback(exchange)); |
||||
|
break; |
||||
|
} |
||||
|
} catch (AdaptorException e) { |
||||
|
log.trace("[{}] Failed to decode message: ", sessionId, e); |
||||
|
exchange.respond(ResponseCode.BAD_REQUEST); |
||||
|
} catch (IllegalAccessException e) { |
||||
|
log.trace("[{}] Failed to process message: ", sessionId, e); |
||||
|
exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); |
||||
|
} |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(Request request) { |
||||
|
String token = request.getSource().getHostAddress() + ":" + request.getSourcePort() + ":" + request.getTokenString(); |
||||
|
return tokenToSessionIdMap.remove(token); |
||||
|
} |
||||
|
|
||||
|
private String registerAsyncCoapSession(CoapExchange exchange, Request request, TransportProtos.SessionInfoProto sessionInfo, UUID sessionId) { |
||||
|
String token = request.getSource().getHostAddress() + ":" + request.getSourcePort() + ":" + request.getTokenString(); |
||||
|
tokenToSessionIdMap.putIfAbsent(token, sessionInfo); |
||||
|
CoapSessionListener attrListener = new CoapSessionListener(sessionId, exchange); |
||||
|
transportService.registerAsyncSession(sessionInfo, attrListener); |
||||
|
transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); |
||||
|
return token; |
||||
|
} |
||||
|
|
||||
|
private static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) { |
||||
|
return TransportProtos.SessionEventMsg.newBuilder() |
||||
|
.setSessionType(TransportProtos.SessionType.ASYNC) |
||||
|
.setEvent(event).build(); |
||||
|
} |
||||
|
|
||||
|
private Optional<DeviceTokenCredentials> decodeCredentials(Request request) { |
||||
|
List<String> uriPath = request.getOptions().getUriPath(); |
||||
|
if (uriPath.size() >= ACCESS_TOKEN_POSITION) { |
||||
|
return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1))); |
||||
|
} else { |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Optional<FeatureType> getFeatureType(Request request) { |
||||
|
List<String> uriPath = request.getOptions().getUriPath(); |
||||
|
try { |
||||
|
if (uriPath.size() >= FEATURE_TYPE_POSITION) { |
||||
|
return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); |
||||
|
} |
||||
|
} catch (RuntimeException e) { |
||||
|
log.warn("Failed to decode feature type: {}", uriPath); |
||||
|
} |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
|
||||
|
public static Optional<Integer> getRequestId(Request request) { |
||||
|
List<String> uriPath = request.getOptions().getUriPath(); |
||||
|
try { |
||||
|
if (uriPath.size() >= REQUEST_ID_POSITION) { |
||||
|
return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1))); |
||||
|
} |
||||
|
} catch (RuntimeException e) { |
||||
|
log.warn("Failed to decode feature type: {}", uriPath); |
||||
|
} |
||||
|
return Optional.empty(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Resource getChild(String name) { |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
private static class DeviceAuthCallback implements TransportServiceCallback<TransportProtos.ValidateDeviceCredentialsResponseMsg> { |
||||
|
private final TransportContext transportContext; |
||||
|
private final CoapExchange exchange; |
||||
|
private final Consumer<TransportProtos.SessionInfoProto> onSuccess; |
||||
|
|
||||
|
DeviceAuthCallback(TransportContext transportContext, CoapExchange exchange, Consumer<TransportProtos.SessionInfoProto> onSuccess) { |
||||
|
this.transportContext = transportContext; |
||||
|
this.exchange = exchange; |
||||
|
this.onSuccess = onSuccess; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onSuccess(TransportProtos.ValidateDeviceCredentialsResponseMsg msg) { |
||||
|
if (msg.hasDeviceInfo()) { |
||||
|
UUID sessionId = UUID.randomUUID(); |
||||
|
TransportProtos.DeviceInfoProto deviceInfoProto = msg.getDeviceInfo(); |
||||
|
TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() |
||||
|
.setNodeId(transportContext.getNodeId()) |
||||
|
.setTenantIdMSB(deviceInfoProto.getTenantIdMSB()) |
||||
|
.setTenantIdLSB(deviceInfoProto.getTenantIdLSB()) |
||||
|
.setDeviceIdMSB(deviceInfoProto.getDeviceIdMSB()) |
||||
|
.setDeviceIdLSB(deviceInfoProto.getDeviceIdLSB()) |
||||
|
.setSessionIdMSB(sessionId.getMostSignificantBits()) |
||||
|
.setSessionIdLSB(sessionId.getLeastSignificantBits()) |
||||
|
.build(); |
||||
|
onSuccess.accept(sessionInfo); |
||||
|
} else { |
||||
|
exchange.respond(ResponseCode.UNAUTHORIZED); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onError(Throwable e) { |
||||
|
log.warn("Failed to process request", e); |
||||
|
exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class CoapOkCallback implements TransportServiceCallback<Void> { |
||||
|
private final CoapExchange exchange; |
||||
|
|
||||
|
CoapOkCallback(CoapExchange exchange) { |
||||
|
this.exchange = exchange; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onSuccess(Void msg) { |
||||
|
exchange.respond(ResponseCode.VALID); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onError(Throwable e) { |
||||
|
exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class CoapNoOpCallback implements TransportServiceCallback<Void> { |
||||
|
private final CoapExchange exchange; |
||||
|
|
||||
|
CoapNoOpCallback(CoapExchange exchange) { |
||||
|
this.exchange = exchange; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onSuccess(Void msg) { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onError(Throwable e) { |
||||
|
exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class CoapSessionListener implements SessionMsgListener { |
||||
|
|
||||
|
private final CoapExchange exchange; |
||||
|
private final AtomicInteger seqNumber = new AtomicInteger(2); |
||||
|
|
||||
|
CoapSessionListener(UUID sessionId, CoapExchange exchange) { |
||||
|
this.exchange = exchange; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg msg) { |
||||
|
try { |
||||
|
exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); |
||||
|
} catch (AdaptorException e) { |
||||
|
log.trace("Failed to reply due to error", e); |
||||
|
exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg) { |
||||
|
try { |
||||
|
exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); |
||||
|
} catch (AdaptorException e) { |
||||
|
log.trace("Failed to reply due to error", e); |
||||
|
exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { |
||||
|
exchange.respond(ResponseCode.SERVICE_UNAVAILABLE); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg msg) { |
||||
|
try { |
||||
|
exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); |
||||
|
} catch (AdaptorException e) { |
||||
|
log.trace("Failed to reply due to error", e); |
||||
|
exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg msg) { |
||||
|
try { |
||||
|
exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); |
||||
|
} catch (AdaptorException e) { |
||||
|
log.trace("Failed to reply due to error", e); |
||||
|
exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public int getNextSeqNumber() { |
||||
|
return seqNumber.getAndIncrement(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class CoapExchangeObserverProxy implements ExchangeObserver { |
||||
|
|
||||
|
private final ExchangeObserver proxy; |
||||
|
private final String token; |
||||
|
|
||||
|
CoapExchangeObserverProxy(ExchangeObserver proxy, String token) { |
||||
|
super(); |
||||
|
this.proxy = proxy; |
||||
|
this.token = token; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void completed(Exchange exchange) { |
||||
|
proxy.completed(exchange); |
||||
|
TransportProtos.SessionInfoProto session = tokenToSessionIdMap.remove(token); |
||||
|
if (session != null) { |
||||
|
closeAndDeregister(session); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void closeAndDeregister(TransportProtos.SessionInfoProto session) { |
||||
|
transportService.process(session, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); |
||||
|
transportService.deregisterSession(session); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.coap.adaptors; |
||||
|
|
||||
|
import org.eclipse.californium.core.coap.Request; |
||||
|
import org.eclipse.californium.core.coap.Response; |
||||
|
import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos; |
||||
|
import org.thingsboard.server.transport.coap.CoapTransportResource; |
||||
|
|
||||
|
import java.util.UUID; |
||||
|
import java.util.Optional; |
||||
|
|
||||
|
public interface CoapTransportAdaptor { |
||||
|
|
||||
|
TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound) throws AdaptorException; |
||||
|
|
||||
|
TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound) throws AdaptorException; |
||||
|
|
||||
|
TransportProtos.GetAttributeRequestMsg convertToGetAttributes(UUID sessionId, Request inbound) throws AdaptorException; |
||||
|
|
||||
|
TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException; |
||||
|
|
||||
|
TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(UUID sessionId, Request inbound) throws AdaptorException; |
||||
|
|
||||
|
Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException; |
||||
|
|
||||
|
Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException; |
||||
|
|
||||
|
Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException; |
||||
|
|
||||
|
Response convertToPublish(CoapTransportResource.CoapSessionListener coapSessionListener, TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,155 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.transport.coap.adaptors; |
||||
|
|
||||
|
import java.util.*; |
||||
|
|
||||
|
import com.google.gson.JsonElement; |
||||
|
import com.google.gson.JsonObject; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.eclipse.californium.core.coap.CoAP; |
||||
|
import org.eclipse.californium.core.coap.Request; |
||||
|
import org.eclipse.californium.core.coap.Response; |
||||
|
import org.springframework.util.StringUtils; |
||||
|
import org.thingsboard.server.common.msg.kv.AttributesKVMsg; |
||||
|
import org.thingsboard.server.common.msg.session.SessionContext; |
||||
|
import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
||||
|
import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import com.google.gson.JsonParser; |
||||
|
import com.google.gson.JsonSyntaxException; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos; |
||||
|
import org.thingsboard.server.transport.coap.CoapTransportResource; |
||||
|
|
||||
|
@Component("JsonCoapAdaptor") |
||||
|
@Slf4j |
||||
|
public class JsonCoapAdaptor implements CoapTransportAdaptor { |
||||
|
|
||||
|
@Override |
||||
|
public TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound) throws AdaptorException { |
||||
|
String payload = validatePayload(sessionId, inbound); |
||||
|
try { |
||||
|
return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload)); |
||||
|
} catch (IllegalStateException | JsonSyntaxException ex) { |
||||
|
throw new AdaptorException(ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound) throws AdaptorException { |
||||
|
String payload = validatePayload(sessionId, inbound); |
||||
|
try { |
||||
|
return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload)); |
||||
|
} catch (IllegalStateException | JsonSyntaxException ex) { |
||||
|
throw new AdaptorException(ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(UUID sessionId, Request inbound) throws AdaptorException { |
||||
|
List<String> queryElements = inbound.getOptions().getUriQuery(); |
||||
|
TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder(); |
||||
|
if (queryElements != null && queryElements.size() > 0) { |
||||
|
Set<String> clientKeys = toKeys(queryElements, "clientKeys"); |
||||
|
Set<String> sharedKeys = toKeys(queryElements, "sharedKeys"); |
||||
|
if (clientKeys != null) { |
||||
|
result.addAllClientAttributeNames(clientKeys); |
||||
|
} |
||||
|
if (sharedKeys != null) { |
||||
|
result.addAllSharedAttributeNames(sharedKeys); |
||||
|
} |
||||
|
} |
||||
|
return result.build(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException { |
||||
|
Optional<Integer> requestId = CoapTransportResource.getRequestId(inbound); |
||||
|
String payload = validatePayload(sessionId, inbound); |
||||
|
JsonObject response = new JsonParser().parse(payload).getAsJsonObject(); |
||||
|
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId.orElseThrow(() -> new AdaptorException("Request id is missing!"))) |
||||
|
.setPayload(response.toString()).build(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(UUID sessionId, Request inbound) throws AdaptorException { |
||||
|
String payload = validatePayload(sessionId, inbound); |
||||
|
return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), 0); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.AttributeUpdateNotificationMsg msg) throws AdaptorException { |
||||
|
return getObserveNotification(session.getNextSeqNumber(), JsonConverter.toJson(msg)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.ToDeviceRpcRequestMsg msg) throws AdaptorException { |
||||
|
return getObserveNotification(session.getNextSeqNumber(), JsonConverter.toJson(msg, true)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Response convertToPublish(CoapTransportResource.CoapSessionListener coapSessionListener, TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException { |
||||
|
Response response = new Response(CoAP.ResponseCode.CONTENT); |
||||
|
JsonElement result = JsonConverter.toJson(msg); |
||||
|
response.setPayload(result.toString()); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException { |
||||
|
if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0 && msg.getDeletedAttributeKeysCount() == 0) { |
||||
|
return new Response(CoAP.ResponseCode.NOT_FOUND); |
||||
|
} else { |
||||
|
Response response = new Response(CoAP.ResponseCode.CONTENT); |
||||
|
JsonObject result = JsonConverter.toJson(msg); |
||||
|
response.setPayload(result.toString()); |
||||
|
return response; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Response getObserveNotification(int seqNumber, JsonElement json) { |
||||
|
Response response = new Response(CoAP.ResponseCode.CONTENT); |
||||
|
response.getOptions().setObserve(seqNumber); |
||||
|
response.setPayload(json.toString()); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
private String validatePayload(UUID sessionId, Request inbound) throws AdaptorException { |
||||
|
String payload = inbound.getPayloadString(); |
||||
|
if (payload == null) { |
||||
|
log.warn("[{}] Payload is empty!", sessionId); |
||||
|
throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); |
||||
|
} |
||||
|
return payload; |
||||
|
} |
||||
|
|
||||
|
private Set<String> toKeys(List<String> queryElements, String attributeName) throws AdaptorException { |
||||
|
String keys = null; |
||||
|
for (String queryElement : queryElements) { |
||||
|
String[] queryItem = queryElement.split("="); |
||||
|
if (queryItem.length == 2 && queryItem[0].equals(attributeName)) { |
||||
|
keys = queryItem[1]; |
||||
|
} |
||||
|
} |
||||
|
if (keys != null && !StringUtils.isEmpty(keys)) { |
||||
|
return new HashSet<>(Arrays.asList(keys.split(","))); |
||||
|
} else { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,140 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
import org.apache.tools.ant.filters.ReplaceTokens |
||||
|
|
||||
|
buildscript { |
||||
|
ext { |
||||
|
osPackageVersion = "3.8.0" |
||||
|
} |
||||
|
repositories { |
||||
|
jcenter() |
||||
|
} |
||||
|
dependencies { |
||||
|
classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
apply plugin: "nebula.ospackage" |
||||
|
|
||||
|
buildDir = projectBuildDir |
||||
|
version = projectVersion |
||||
|
distsDirName = "./" |
||||
|
|
||||
|
// OS Package plugin configuration |
||||
|
ospackage { |
||||
|
packageName = pkgName |
||||
|
version = "${project.version}" |
||||
|
release = 1 |
||||
|
os = LINUX |
||||
|
type = BINARY |
||||
|
|
||||
|
into pkgInstallFolder |
||||
|
|
||||
|
user pkgName |
||||
|
permissionGroup pkgName |
||||
|
|
||||
|
// Copy the actual .jar file |
||||
|
from(mainJar) { |
||||
|
// Strip the version from the jar filename |
||||
|
rename { String fileName -> |
||||
|
"${pkgName}.jar" |
||||
|
} |
||||
|
fileMode 0500 |
||||
|
into "bin" |
||||
|
} |
||||
|
|
||||
|
// Copy the config files |
||||
|
from("target/conf") { |
||||
|
exclude "${pkgName}.conf" |
||||
|
fileType CONFIG | NOREPLACE |
||||
|
fileMode 0754 |
||||
|
into "conf" |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// Configure our RPM build task |
||||
|
buildRpm { |
||||
|
|
||||
|
arch = NOARCH |
||||
|
|
||||
|
version = projectVersion.replace('-', '') |
||||
|
archiveName = "${pkgName}.rpm" |
||||
|
|
||||
|
requires("java-1.8.0") |
||||
|
|
||||
|
from("target/conf") { |
||||
|
include "${pkgName}.conf" |
||||
|
filter(ReplaceTokens, tokens: ['pkg.platform': 'rpm']) |
||||
|
fileType CONFIG | NOREPLACE |
||||
|
fileMode 0754 |
||||
|
into "${pkgInstallFolder}/conf" |
||||
|
} |
||||
|
|
||||
|
preInstall file("${buildDir}/control/rpm/preinst") |
||||
|
postInstall file("${buildDir}/control/rpm/postinst") |
||||
|
preUninstall file("${buildDir}/control/rpm/prerm") |
||||
|
postUninstall file("${buildDir}/control/rpm/postrm") |
||||
|
|
||||
|
user pkgName |
||||
|
permissionGroup pkgName |
||||
|
|
||||
|
// Copy the system unit files |
||||
|
from("${buildDir}/control/${pkgName}.service") { |
||||
|
addParentDirs = false |
||||
|
fileMode 0644 |
||||
|
into "/usr/lib/systemd/system" |
||||
|
} |
||||
|
|
||||
|
directory(pkgLogFolder, 0755) |
||||
|
link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") |
||||
|
link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") |
||||
|
} |
||||
|
|
||||
|
// Same as the buildRpm task |
||||
|
buildDeb { |
||||
|
|
||||
|
arch = "all" |
||||
|
|
||||
|
archiveName = "${pkgName}.deb" |
||||
|
|
||||
|
requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer").or("openjdk-8-jre-headless") |
||||
|
|
||||
|
from("target/conf") { |
||||
|
include "${pkgName}.conf" |
||||
|
filter(ReplaceTokens, tokens: ['pkg.platform': 'deb']) |
||||
|
fileType CONFIG | NOREPLACE |
||||
|
fileMode 0754 |
||||
|
into "${pkgInstallFolder}/conf" |
||||
|
} |
||||
|
|
||||
|
configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf") |
||||
|
configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml") |
||||
|
configurationFile("${pkgInstallFolder}/conf/logback.xml") |
||||
|
|
||||
|
preInstall file("${buildDir}/control/deb/preinst") |
||||
|
postInstall file("${buildDir}/control/deb/postinst") |
||||
|
preUninstall file("${buildDir}/control/deb/prerm") |
||||
|
postUninstall file("${buildDir}/control/deb/postrm") |
||||
|
|
||||
|
user pkgName |
||||
|
permissionGroup pkgName |
||||
|
|
||||
|
directory(pkgLogFolder, 0755) |
||||
|
link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar") |
||||
|
link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") |
||||
|
link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") |
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2018 The Thingsboard Authors |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd"> |
||||
|
<id>windows</id> |
||||
|
|
||||
|
<formats> |
||||
|
<format>zip</format> |
||||
|
</formats> |
||||
|
|
||||
|
<!-- Workaround to create logs directory --> |
||||
|
<fileSets> |
||||
|
<fileSet> |
||||
|
<directory>${pkg.win.dist}</directory> |
||||
|
<outputDirectory>logs</outputDirectory> |
||||
|
<excludes> |
||||
|
<exclude>*/**</exclude> |
||||
|
</excludes> |
||||
|
</fileSet> |
||||
|
<fileSet> |
||||
|
<directory>${pkg.win.dist}/conf</directory> |
||||
|
<outputDirectory>conf</outputDirectory> |
||||
|
<lineEnding>windows</lineEnding> |
||||
|
</fileSet> |
||||
|
</fileSets> |
||||
|
|
||||
|
<files> |
||||
|
<file> |
||||
|
<source>${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</source> |
||||
|
<outputDirectory>lib</outputDirectory> |
||||
|
<destName>${pkg.name}.jar</destName> |
||||
|
</file> |
||||
|
<file> |
||||
|
<source>${pkg.win.dist}/service.exe</source> |
||||
|
<outputDirectory/> |
||||
|
<destName>${pkg.name}.exe</destName> |
||||
|
</file> |
||||
|
<file> |
||||
|
<source>${pkg.win.dist}/service.xml</source> |
||||
|
<outputDirectory/> |
||||
|
<destName>${pkg.name}.xml</destName> |
||||
|
<lineEnding>windows</lineEnding> |
||||
|
</file> |
||||
|
<file> |
||||
|
<source>${pkg.win.dist}/install.bat</source> |
||||
|
<outputDirectory/> |
||||
|
<lineEnding>windows</lineEnding> |
||||
|
</file> |
||||
|
<file> |
||||
|
<source>${pkg.win.dist}/uninstall.bat</source> |
||||
|
<outputDirectory/> |
||||
|
<lineEnding>windows</lineEnding> |
||||
|
</file> |
||||
|
</files> |
||||
|
</assembly> |
||||
@ -0,0 +1,43 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2018 The Thingsboard Authors |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<!DOCTYPE configuration> |
||||
|
<configuration> |
||||
|
|
||||
|
<appender name="fileLogAppender" |
||||
|
class="ch.qos.logback.core.rolling.RollingFileAppender"> |
||||
|
<file>${pkg.logFolder}/${pkg.name}.log</file> |
||||
|
<rollingPolicy |
||||
|
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
||||
|
<fileNamePattern>${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
||||
|
<maxFileSize>100MB</maxFileSize> |
||||
|
<maxHistory>30</maxHistory> |
||||
|
<totalSizeCap>3GB</totalSizeCap> |
||||
|
</rollingPolicy> |
||||
|
<encoder> |
||||
|
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> |
||||
|
</encoder> |
||||
|
</appender> |
||||
|
|
||||
|
<logger name="org.thingsboard.server" level="INFO" /> |
||||
|
|
||||
|
<root level="INFO"> |
||||
|
<appender-ref ref="fileLogAppender"/> |
||||
|
</root> |
||||
|
|
||||
|
</configuration> |
||||
@ -0,0 +1,23 @@ |
|||||
|
# |
||||
|
# Copyright © 2016-2018 The Thingsboard Authors |
||||
|
# |
||||
|
# 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. |
||||
|
# |
||||
|
|
||||
|
export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" |
||||
|
export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" |
||||
|
export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" |
||||
|
export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" |
||||
|
export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly" |
||||
|
export LOG_FILENAME=${pkg.name}.out |
||||
|
export LOADER_PATH=${pkg.installFolder}/conf |
||||
@ -0,0 +1 @@ |
|||||
|
pkg.logFolder=${pkg.unixLogFolder} |
||||
@ -0,0 +1,2 @@ |
|||||
|
pkg.logFolder=${BASE}\\logs |
||||
|
pkg.winWrapperLogFolder=%BASE%\\logs |
||||
@ -0,0 +1,48 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.coap; |
||||
|
|
||||
|
import org.springframework.boot.SpringApplication; |
||||
|
import org.springframework.boot.SpringBootConfiguration; |
||||
|
import org.springframework.context.annotation.ComponentScan; |
||||
|
import org.springframework.scheduling.annotation.EnableAsync; |
||||
|
import org.springframework.scheduling.annotation.EnableScheduling; |
||||
|
|
||||
|
import java.util.Arrays; |
||||
|
|
||||
|
@SpringBootConfiguration |
||||
|
@EnableAsync |
||||
|
@EnableScheduling |
||||
|
@ComponentScan({"org.thingsboard.server.coap", "org.thingsboard.server.common", "org.thingsboard.server.transport.coap", "org.thingsboard.server.kafka"}) |
||||
|
public class ThingsboardCoapTransportApplication { |
||||
|
|
||||
|
private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; |
||||
|
private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "tb-coap-transport"; |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
SpringApplication.run(ThingsboardCoapTransportApplication.class, updateArguments(args)); |
||||
|
} |
||||
|
|
||||
|
private static String[] updateArguments(String[] args) { |
||||
|
if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) { |
||||
|
String[] modifiedArgs = new String[args.length + 1]; |
||||
|
System.arraycopy(args, 0, modifiedArgs, 0, args.length); |
||||
|
modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM; |
||||
|
return modifiedArgs; |
||||
|
} |
||||
|
return args; |
||||
|
} |
||||
|
} |
||||
@ -1,242 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.transport.coap; |
|
||||
|
|
||||
import java.lang.reflect.Field; |
|
||||
import java.util.List; |
|
||||
import java.util.Optional; |
|
||||
|
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.eclipse.californium.core.CoapResource; |
|
||||
import org.eclipse.californium.core.coap.CoAP.ResponseCode; |
|
||||
import org.eclipse.californium.core.coap.Request; |
|
||||
import org.eclipse.californium.core.network.Exchange; |
|
||||
import org.eclipse.californium.core.network.ExchangeObserver; |
|
||||
import org.eclipse.californium.core.server.resources.CoapExchange; |
|
||||
import org.eclipse.californium.core.server.resources.Resource; |
|
||||
import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; |
|
||||
import org.thingsboard.server.common.data.security.DeviceTokenCredentials; |
|
||||
import org.thingsboard.server.common.msg.session.*; |
|
||||
import org.thingsboard.server.common.transport.SessionMsgProcessor; |
|
||||
import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
|
||||
import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
|
||||
import org.thingsboard.server.common.transport.quota.QuotaService; |
|
||||
import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; |
|
||||
import org.thingsboard.server.transport.coap.session.CoapExchangeObserverProxy; |
|
||||
import org.thingsboard.server.transport.coap.session.CoapSessionCtx; |
|
||||
import org.springframework.util.ReflectionUtils; |
|
||||
|
|
||||
@Slf4j |
|
||||
public class CoapTransportResource extends CoapResource { |
|
||||
// coap://localhost:port/api/v1/DEVICE_TOKEN/[attributes|telemetry|rpc[/requestId]]
|
|
||||
private static final int ACCESS_TOKEN_POSITION = 3; |
|
||||
private static final int FEATURE_TYPE_POSITION = 4; |
|
||||
private static final int REQUEST_ID_POSITION = 5; |
|
||||
|
|
||||
private final CoapTransportAdaptor adaptor; |
|
||||
private final SessionMsgProcessor processor; |
|
||||
private final DeviceAuthService authService; |
|
||||
private final QuotaService quotaService; |
|
||||
private final Field observerField; |
|
||||
private final long timeout; |
|
||||
|
|
||||
public CoapTransportResource(SessionMsgProcessor processor, DeviceAuthService authService, CoapTransportAdaptor adaptor, String name, |
|
||||
long timeout, QuotaService quotaService) { |
|
||||
super(name); |
|
||||
this.processor = processor; |
|
||||
this.authService = authService; |
|
||||
this.quotaService = quotaService; |
|
||||
this.adaptor = adaptor; |
|
||||
this.timeout = timeout; |
|
||||
// This is important to turn off existing observable logic in
|
|
||||
// CoapResource. We will have our own observe monitoring due to 1:1
|
|
||||
// observe relationship.
|
|
||||
this.setObservable(false); |
|
||||
observerField = ReflectionUtils.findField(Exchange.class, "observer"); |
|
||||
observerField.setAccessible(true); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void handleGET(CoapExchange exchange) { |
|
||||
if(quotaService.isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) { |
|
||||
log.warn("COAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort()); |
|
||||
exchange.respond(ResponseCode.BAD_REQUEST); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest()); |
|
||||
if (!featureType.isPresent()) { |
|
||||
log.trace("Missing feature type parameter"); |
|
||||
exchange.respond(ResponseCode.BAD_REQUEST); |
|
||||
} else if (featureType.get() == FeatureType.TELEMETRY) { |
|
||||
log.trace("Can't fetch/subscribe to timeseries updates"); |
|
||||
exchange.respond(ResponseCode.BAD_REQUEST); |
|
||||
} else if (exchange.getRequestOptions().hasObserve()) { |
|
||||
processExchangeGetRequest(exchange, featureType.get()); |
|
||||
} else if (featureType.get() == FeatureType.ATTRIBUTES) { |
|
||||
processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); |
|
||||
} else { |
|
||||
log.trace("Invalid feature type parameter"); |
|
||||
exchange.respond(ResponseCode.BAD_REQUEST); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void processExchangeGetRequest(CoapExchange exchange, FeatureType featureType) { |
|
||||
boolean unsubscribe = exchange.getRequestOptions().getObserve() == 1; |
|
||||
SessionMsgType sessionMsgType; |
|
||||
if (featureType == FeatureType.RPC) { |
|
||||
sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; |
|
||||
} else { |
|
||||
sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; |
|
||||
} |
|
||||
Optional<SessionId> sessionId = processRequest(exchange, sessionMsgType); |
|
||||
if (sessionId.isPresent()) { |
|
||||
if (exchange.getRequestOptions().getObserve() == 1) { |
|
||||
exchange.respond(ResponseCode.VALID); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void handlePOST(CoapExchange exchange) { |
|
||||
if(quotaService.isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) { |
|
||||
log.warn("COAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort()); |
|
||||
exchange.respond(ResponseCode.BAD_REQUEST); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest()); |
|
||||
if (!featureType.isPresent()) { |
|
||||
log.trace("Missing feature type parameter"); |
|
||||
exchange.respond(ResponseCode.BAD_REQUEST); |
|
||||
} else { |
|
||||
switch (featureType.get()) { |
|
||||
case ATTRIBUTES: |
|
||||
processRequest(exchange, SessionMsgType.POST_ATTRIBUTES_REQUEST); |
|
||||
break; |
|
||||
case TELEMETRY: |
|
||||
processRequest(exchange, SessionMsgType.POST_TELEMETRY_REQUEST); |
|
||||
break; |
|
||||
case RPC: |
|
||||
Optional<Integer> requestId = getRequestId(exchange.advanced().getRequest()); |
|
||||
if (requestId.isPresent()) { |
|
||||
processRequest(exchange, SessionMsgType.TO_DEVICE_RPC_RESPONSE); |
|
||||
} else { |
|
||||
processRequest(exchange, SessionMsgType.TO_SERVER_RPC_REQUEST); |
|
||||
} |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private Optional<SessionId> processRequest(CoapExchange exchange, SessionMsgType type) { |
|
||||
log.trace("Processing {}", exchange.advanced().getRequest()); |
|
||||
exchange.accept(); |
|
||||
Exchange advanced = exchange.advanced(); |
|
||||
Request request = advanced.getRequest(); |
|
||||
|
|
||||
Optional<DeviceCredentialsFilter> credentials = decodeCredentials(request); |
|
||||
if (!credentials.isPresent()) { |
|
||||
exchange.respond(ResponseCode.BAD_REQUEST); |
|
||||
return Optional.empty(); |
|
||||
} |
|
||||
|
|
||||
CoapSessionCtx ctx = new CoapSessionCtx(exchange, adaptor, processor, authService, timeout); |
|
||||
|
|
||||
// if (!ctx.login(credentials.get())) {
|
|
||||
// exchange.respond(ResponseCode.UNAUTHORIZED);
|
|
||||
// return Optional.empty();
|
|
||||
// }
|
|
||||
|
|
||||
AdaptorToSessionActorMsg msg; |
|
||||
try { |
|
||||
switch (type) { |
|
||||
case GET_ATTRIBUTES_REQUEST: |
|
||||
case POST_ATTRIBUTES_REQUEST: |
|
||||
case POST_TELEMETRY_REQUEST: |
|
||||
case TO_DEVICE_RPC_RESPONSE: |
|
||||
case TO_SERVER_RPC_REQUEST: |
|
||||
ctx.setSessionType(SessionType.SYNC); |
|
||||
msg = adaptor.convertToActorMsg(ctx, type, request); |
|
||||
break; |
|
||||
case SUBSCRIBE_ATTRIBUTES_REQUEST: |
|
||||
case SUBSCRIBE_RPC_COMMANDS_REQUEST: |
|
||||
ExchangeObserver systemObserver = (ExchangeObserver) observerField.get(advanced); |
|
||||
advanced.setObserver(new CoapExchangeObserverProxy(systemObserver, ctx)); |
|
||||
ctx.setSessionType(SessionType.ASYNC); |
|
||||
msg = adaptor.convertToActorMsg(ctx, type, request); |
|
||||
break; |
|
||||
case UNSUBSCRIBE_ATTRIBUTES_REQUEST: |
|
||||
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: |
|
||||
ctx.setSessionType(SessionType.ASYNC); |
|
||||
msg = adaptor.convertToActorMsg(ctx, type, request); |
|
||||
break; |
|
||||
default: |
|
||||
log.trace("[{}] Unsupported msg type: {}", ctx.getSessionId(), type); |
|
||||
throw new IllegalArgumentException("Unsupported msg type: " + type); |
|
||||
} |
|
||||
log.trace("Processing msg: {}", msg); |
|
||||
// processor.process(new BasicTransportToDeviceSessionActorMsg(ctx.getDevice(), msg));
|
|
||||
} catch (AdaptorException e) { |
|
||||
log.debug("Failed to decode payload {}", e); |
|
||||
exchange.respond(ResponseCode.BAD_REQUEST, e.getMessage()); |
|
||||
return Optional.empty(); |
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) { |
|
||||
log.debug("Failed to process payload {}", e); |
|
||||
exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR, e.getMessage()); |
|
||||
} |
|
||||
return Optional.of(ctx.getSessionId()); |
|
||||
} |
|
||||
|
|
||||
private Optional<DeviceCredentialsFilter> decodeCredentials(Request request) { |
|
||||
List<String> uriPath = request.getOptions().getUriPath(); |
|
||||
DeviceCredentialsFilter credentials = null; |
|
||||
if (uriPath.size() >= ACCESS_TOKEN_POSITION) { |
|
||||
credentials = new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1)); |
|
||||
} |
|
||||
return Optional.ofNullable(credentials); |
|
||||
} |
|
||||
|
|
||||
private Optional<FeatureType> getFeatureType(Request request) { |
|
||||
List<String> uriPath = request.getOptions().getUriPath(); |
|
||||
try { |
|
||||
if (uriPath.size() >= FEATURE_TYPE_POSITION) { |
|
||||
return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); |
|
||||
} |
|
||||
} catch (RuntimeException e) { |
|
||||
log.warn("Failed to decode feature type: {}", uriPath); |
|
||||
} |
|
||||
return Optional.empty(); |
|
||||
} |
|
||||
|
|
||||
public static Optional<Integer> getRequestId(Request request) { |
|
||||
List<String> uriPath = request.getOptions().getUriPath(); |
|
||||
try { |
|
||||
if (uriPath.size() >= REQUEST_ID_POSITION) { |
|
||||
return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1))); |
|
||||
} |
|
||||
} catch (RuntimeException e) { |
|
||||
log.warn("Failed to decode feature type: {}", uriPath); |
|
||||
} |
|
||||
return Optional.empty(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Resource getChild(String name) { |
|
||||
return this; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,25 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.transport.coap.adaptors; |
|
||||
|
|
||||
import org.eclipse.californium.core.coap.Request; |
|
||||
import org.eclipse.californium.core.coap.Response; |
|
||||
import org.thingsboard.server.common.transport.TransportAdaptor; |
|
||||
import org.thingsboard.server.transport.coap.session.CoapSessionCtx; |
|
||||
|
|
||||
public interface CoapTransportAdaptor extends TransportAdaptor<CoapSessionCtx, Request, Response> { |
|
||||
|
|
||||
} |
|
||||
@ -1,260 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.transport.coap.adaptors; |
|
||||
|
|
||||
import java.util.*; |
|
||||
|
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.eclipse.californium.core.coap.CoAP.ResponseCode; |
|
||||
import org.eclipse.californium.core.coap.Request; |
|
||||
import org.eclipse.californium.core.coap.Response; |
|
||||
import org.springframework.util.StringUtils; |
|
||||
import org.thingsboard.server.common.msg.core.*; |
|
||||
import org.thingsboard.server.common.msg.kv.AttributesKVMsg; |
|
||||
import org.thingsboard.server.common.msg.session.SessionMsgType; |
|
||||
import org.thingsboard.server.common.msg.session.SessionContext; |
|
||||
import org.thingsboard.server.common.msg.session.ex.ProcessingTimeoutException; |
|
||||
import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
|
||||
import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
|
||||
import org.springframework.stereotype.Component; |
|
||||
|
|
||||
import com.google.gson.JsonObject; |
|
||||
import com.google.gson.JsonParser; |
|
||||
import com.google.gson.JsonSyntaxException; |
|
||||
import org.thingsboard.server.transport.coap.CoapTransportResource; |
|
||||
import org.thingsboard.server.transport.coap.session.CoapSessionCtx; |
|
||||
|
|
||||
@Component("JsonCoapAdaptor") |
|
||||
@Slf4j |
|
||||
public class JsonCoapAdaptor implements CoapTransportAdaptor { |
|
||||
|
|
||||
@Override |
|
||||
public AdaptorToSessionActorMsg convertToActorMsg(CoapSessionCtx ctx, SessionMsgType type, Request inbound) throws AdaptorException { |
|
||||
FromDeviceMsg msg = null; |
|
||||
switch (type) { |
|
||||
case POST_TELEMETRY_REQUEST: |
|
||||
msg = convertToTelemetryUploadRequest(ctx, inbound); |
|
||||
break; |
|
||||
case POST_ATTRIBUTES_REQUEST: |
|
||||
msg = convertToUpdateAttributesRequest(ctx, inbound); |
|
||||
break; |
|
||||
case GET_ATTRIBUTES_REQUEST: |
|
||||
msg = convertToGetAttributesRequest(ctx, inbound); |
|
||||
break; |
|
||||
case SUBSCRIBE_RPC_COMMANDS_REQUEST: |
|
||||
msg = new RpcSubscribeMsg(); |
|
||||
break; |
|
||||
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: |
|
||||
msg = new RpcUnsubscribeMsg(); |
|
||||
break; |
|
||||
case TO_DEVICE_RPC_RESPONSE: |
|
||||
msg = convertToDeviceRpcResponse(ctx, inbound); |
|
||||
break; |
|
||||
case TO_SERVER_RPC_REQUEST: |
|
||||
msg = convertToServerRpcRequest(ctx, inbound); |
|
||||
break; |
|
||||
case SUBSCRIBE_ATTRIBUTES_REQUEST: |
|
||||
msg = new AttributesSubscribeMsg(); |
|
||||
break; |
|
||||
case UNSUBSCRIBE_ATTRIBUTES_REQUEST: |
|
||||
msg = new AttributesUnsubscribeMsg(); |
|
||||
break; |
|
||||
default: |
|
||||
log.warn("[{}] Unsupported msg type: {}!", ctx.getSessionId(), type); |
|
||||
throw new AdaptorException(new IllegalArgumentException("Unsupported msg type: " + type + "!")); |
|
||||
} |
|
||||
return new BasicAdaptorToSessionActorMsg(ctx, msg); |
|
||||
} |
|
||||
|
|
||||
private FromDeviceMsg convertToDeviceRpcResponse(CoapSessionCtx ctx, Request inbound) throws AdaptorException { |
|
||||
Optional<Integer> requestId = CoapTransportResource.getRequestId(inbound); |
|
||||
String payload = validatePayload(ctx, inbound); |
|
||||
JsonObject response = new JsonParser().parse(payload).getAsJsonObject(); |
|
||||
return new ToDeviceRpcResponseMsg( |
|
||||
requestId.orElseThrow(() -> new AdaptorException("Request id is missing!")), |
|
||||
response.get("response").toString()); |
|
||||
} |
|
||||
|
|
||||
private FromDeviceMsg convertToServerRpcRequest(CoapSessionCtx ctx, Request inbound) throws AdaptorException { |
|
||||
|
|
||||
String payload = validatePayload(ctx, inbound); |
|
||||
|
|
||||
// return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), 0);
|
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Optional<Response> convertToAdaptorMsg(CoapSessionCtx ctx, SessionActorToAdaptorMsg source) throws AdaptorException { |
|
||||
ToDeviceMsg msg = source.getMsg(); |
|
||||
switch (msg.getSessionMsgType()) { |
|
||||
case STATUS_CODE_RESPONSE: |
|
||||
case TO_DEVICE_RPC_RESPONSE_ACK: |
|
||||
return Optional.of(convertStatusCodeResponse((StatusCodeResponse) msg)); |
|
||||
case GET_ATTRIBUTES_RESPONSE: |
|
||||
return Optional.of(convertGetAttributesResponse((GetAttributesResponse) msg)); |
|
||||
case ATTRIBUTES_UPDATE_NOTIFICATION: |
|
||||
return Optional.of(convertNotificationResponse(ctx, (AttributesUpdateNotification) msg)); |
|
||||
case TO_DEVICE_RPC_REQUEST: |
|
||||
return Optional.of(convertToDeviceRpcRequest(ctx, (ToDeviceRpcRequestMsg) msg)); |
|
||||
case TO_SERVER_RPC_RESPONSE: |
|
||||
return Optional.of(convertToServerRpcResponse(ctx, (ToServerRpcResponseMsg) msg)); |
|
||||
case RULE_ENGINE_ERROR: |
|
||||
return Optional.of(convertToRuleEngineErrorResponse(ctx, (RuleEngineErrorMsg) msg)); |
|
||||
default: |
|
||||
log.warn("[{}] Unsupported msg type: {}!", source.getSessionId(), msg.getSessionMsgType()); |
|
||||
throw new AdaptorException(new IllegalArgumentException("Unsupported msg type: " + msg.getSessionMsgType() + "!")); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private Response convertToRuleEngineErrorResponse(CoapSessionCtx ctx, RuleEngineErrorMsg msg) { |
|
||||
ResponseCode status = ResponseCode.INTERNAL_SERVER_ERROR; |
|
||||
switch (msg.getError()) { |
|
||||
case QUEUE_PUT_TIMEOUT: |
|
||||
status = ResponseCode.GATEWAY_TIMEOUT; |
|
||||
break; |
|
||||
default: |
|
||||
if (msg.getInSessionMsgType() == SessionMsgType.TO_SERVER_RPC_REQUEST) { |
|
||||
status = ResponseCode.BAD_REQUEST; |
|
||||
} |
|
||||
break; |
|
||||
} |
|
||||
Response response = new Response(status); |
|
||||
response.setPayload(JsonConverter.toErrorJson(msg.getErrorMsg()).toString()); |
|
||||
return response; |
|
||||
} |
|
||||
|
|
||||
private Response convertNotificationResponse(CoapSessionCtx ctx, AttributesUpdateNotification msg) { |
|
||||
return getObserveNotification(ctx, JsonConverter.toJson(msg.getData(), false)); |
|
||||
} |
|
||||
|
|
||||
private Response convertToDeviceRpcRequest(CoapSessionCtx ctx, ToDeviceRpcRequestMsg msg) { |
|
||||
return getObserveNotification(ctx, JsonConverter.toJson(msg, true)); |
|
||||
} |
|
||||
|
|
||||
private Response getObserveNotification(CoapSessionCtx ctx, JsonObject json) { |
|
||||
Response response = new Response(ResponseCode.CONTENT); |
|
||||
response.getOptions().setObserve(ctx.nextSeqNumber()); |
|
||||
response.setPayload(json.toString()); |
|
||||
return response; |
|
||||
} |
|
||||
|
|
||||
private AttributesUpdateRequest convertToUpdateAttributesRequest(SessionContext ctx, Request inbound) throws AdaptorException { |
|
||||
String payload = validatePayload(ctx, inbound); |
|
||||
try { |
|
||||
return JsonConverter.convertToAttributes(new JsonParser().parse(payload)); |
|
||||
} catch (IllegalStateException | JsonSyntaxException ex) { |
|
||||
throw new AdaptorException(ex); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private FromDeviceMsg convertToGetAttributesRequest(SessionContext ctx, Request inbound) throws AdaptorException { |
|
||||
List<String> queryElements = inbound.getOptions().getUriQuery(); |
|
||||
if (queryElements != null && queryElements.size() > 0) { |
|
||||
Set<String> clientKeys = toKeys(ctx, queryElements, "clientKeys"); |
|
||||
Set<String> sharedKeys = toKeys(ctx, queryElements, "sharedKeys"); |
|
||||
return new BasicGetAttributesRequest(0, clientKeys, sharedKeys); |
|
||||
} else { |
|
||||
return new BasicGetAttributesRequest(0); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private Set<String> toKeys(SessionContext ctx, List<String> queryElements, String attributeName) throws AdaptorException { |
|
||||
String keys = null; |
|
||||
for (String queryElement : queryElements) { |
|
||||
String[] queryItem = queryElement.split("="); |
|
||||
if (queryItem.length == 2 && queryItem[0].equals(attributeName)) { |
|
||||
keys = queryItem[1]; |
|
||||
} |
|
||||
} |
|
||||
if (keys != null && !StringUtils.isEmpty(keys)) { |
|
||||
return new HashSet<>(Arrays.asList(keys.split(","))); |
|
||||
} else { |
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private TelemetryUploadRequest convertToTelemetryUploadRequest(SessionContext ctx, Request inbound) throws AdaptorException { |
|
||||
String payload = validatePayload(ctx, inbound); |
|
||||
try { |
|
||||
return JsonConverter.convertToTelemetry(new JsonParser().parse(payload)); |
|
||||
} catch (IllegalStateException | JsonSyntaxException ex) { |
|
||||
throw new AdaptorException(ex); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private Response convertStatusCodeResponse(StatusCodeResponse msg) { |
|
||||
if (msg.isSuccess()) { |
|
||||
Optional<Integer> code = msg.getData(); |
|
||||
if (code.isPresent() && code.get() == 200) { |
|
||||
return new Response(ResponseCode.VALID); |
|
||||
} else { |
|
||||
return new Response(ResponseCode.CREATED); |
|
||||
} |
|
||||
} else { |
|
||||
return convertError(msg.getError()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private String validatePayload(SessionContext ctx, Request inbound) throws AdaptorException { |
|
||||
String payload = inbound.getPayloadString(); |
|
||||
if (payload == null) { |
|
||||
log.warn("[{}] Payload is empty!", ctx.getSessionId()); |
|
||||
throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); |
|
||||
} |
|
||||
return payload; |
|
||||
} |
|
||||
|
|
||||
private Response convertToServerRpcResponse(SessionContext ctx, ToServerRpcResponseMsg msg) { |
|
||||
if (msg.isSuccess()) { |
|
||||
Response response = new Response(ResponseCode.CONTENT); |
|
||||
// JsonElement result = JsonConverter.toJson(msg);
|
|
||||
// response.setPayload(result.toString());
|
|
||||
return response; |
|
||||
} else { |
|
||||
return convertError(Optional.of(new RuntimeException("Server RPC response is empty!"))); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private Response convertGetAttributesResponse(GetAttributesResponse msg) { |
|
||||
if (msg.isSuccess()) { |
|
||||
Optional<AttributesKVMsg> payload = msg.getData(); |
|
||||
if (!payload.isPresent() || (payload.get().getClientAttributes().isEmpty() && payload.get().getSharedAttributes().isEmpty())) { |
|
||||
return new Response(ResponseCode.NOT_FOUND); |
|
||||
} else { |
|
||||
Response response = new Response(ResponseCode.CONTENT); |
|
||||
JsonObject result = JsonConverter.toJson(payload.get(), false); |
|
||||
response.setPayload(result.toString()); |
|
||||
return response; |
|
||||
} |
|
||||
} else { |
|
||||
return convertError(msg.getError()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private Response convertError(Optional<Exception> exception) { |
|
||||
if (exception.isPresent()) { |
|
||||
log.warn("Converting exception: {}", exception.get().getMessage(), exception.get()); |
|
||||
if (exception.get() instanceof ProcessingTimeoutException) { |
|
||||
return new Response(ResponseCode.SERVICE_UNAVAILABLE); |
|
||||
} else { |
|
||||
return new Response(ResponseCode.INTERNAL_SERVER_ERROR); |
|
||||
} |
|
||||
} else { |
|
||||
return new Response(ResponseCode.INTERNAL_SERVER_ERROR); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,38 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.transport.coap.session; |
|
||||
|
|
||||
import org.eclipse.californium.core.network.Exchange; |
|
||||
import org.eclipse.californium.core.network.ExchangeObserver; |
|
||||
|
|
||||
public class CoapExchangeObserverProxy implements ExchangeObserver { |
|
||||
|
|
||||
private final ExchangeObserver proxy; |
|
||||
private final CoapSessionCtx ctx; |
|
||||
|
|
||||
public CoapExchangeObserverProxy(ExchangeObserver proxy, CoapSessionCtx ctx) { |
|
||||
super(); |
|
||||
this.proxy = proxy; |
|
||||
this.ctx = ctx; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void completed(Exchange exchange) { |
|
||||
proxy.completed(exchange); |
|
||||
ctx.close(); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,127 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.transport.coap.session; |
|
||||
|
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.eclipse.californium.core.coap.CoAP.ResponseCode; |
|
||||
import org.eclipse.californium.core.coap.Request; |
|
||||
import org.eclipse.californium.core.coap.Response; |
|
||||
import org.eclipse.californium.core.server.resources.CoapExchange; |
|
||||
import org.thingsboard.server.common.msg.session.ex.SessionException; |
|
||||
import org.thingsboard.server.common.transport.SessionMsgProcessor; |
|
||||
import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
|
||||
import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
|
||||
import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; |
|
||||
import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; |
|
||||
|
|
||||
import java.util.concurrent.atomic.AtomicInteger; |
|
||||
|
|
||||
@Slf4j |
|
||||
public class CoapSessionCtx extends DeviceAwareSessionContext { |
|
||||
|
|
||||
private final SessionId sessionId; |
|
||||
private final CoapExchange exchange; |
|
||||
private final CoapTransportAdaptor adaptor; |
|
||||
private final String token; |
|
||||
private final long timeout; |
|
||||
private SessionType sessionType; |
|
||||
private final AtomicInteger seqNumber = new AtomicInteger(2); |
|
||||
|
|
||||
public CoapSessionCtx(CoapExchange exchange, CoapTransportAdaptor adaptor, SessionMsgProcessor processor, DeviceAuthService authService, long timeout) { |
|
||||
super(); |
|
||||
Request request = exchange.advanced().getRequest(); |
|
||||
this.token = request.getTokenString(); |
|
||||
this.sessionId = new CoapSessionId(request.getSource().getHostAddress(), request.getSourcePort(), this.token); |
|
||||
this.exchange = exchange; |
|
||||
this.adaptor = adaptor; |
|
||||
this.timeout = timeout; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
@Override |
|
||||
public void onMsg(SessionActorToAdaptorMsg msg) throws SessionException { |
|
||||
try { |
|
||||
adaptor.convertToAdaptorMsg(this, msg).ifPresent(this::pushToNetwork); |
|
||||
} catch (AdaptorException e) { |
|
||||
logAndWrap(e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void pushToNetwork(Response response) { |
|
||||
exchange.respond(response); |
|
||||
} |
|
||||
|
|
||||
private void logAndWrap(AdaptorException e) throws SessionException { |
|
||||
log.warn("Failed to convert msg: {}", e.getMessage(), e); |
|
||||
throw new SessionException(e); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onMsg(SessionCtrlMsg msg) throws SessionException { |
|
||||
log.debug("[{}] onCtrl: {}", sessionId, msg); |
|
||||
if (msg instanceof SessionCloseMsg) { |
|
||||
onSessionClose((SessionCloseMsg) msg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void onSessionClose(SessionCloseMsg msg) { |
|
||||
if (msg.isTimeout()) { |
|
||||
exchange.respond(ResponseCode.SERVICE_UNAVAILABLE); |
|
||||
} else if (msg.isCredentialsRevoked()) { |
|
||||
exchange.respond(ResponseCode.UNAUTHORIZED); |
|
||||
} else { |
|
||||
exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public SessionId getSessionId() { |
|
||||
return sessionId; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public String toString() { |
|
||||
return "CoapSessionCtx [sessionId=" + sessionId + "]"; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public boolean isClosed() { |
|
||||
return exchange.advanced().isComplete() || exchange.advanced().isTimedOut(); |
|
||||
} |
|
||||
|
|
||||
public void close() { |
|
||||
log.info("[{}] Closing processing context. Timeout: {}", sessionId, exchange.advanced().isTimedOut()); |
|
||||
// processor.process(exchange.advanced().isTimedOut() ? SessionCloseMsg.onTimeout(sessionId) : SessionCloseMsg.onError(sessionId));
|
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public long getTimeout() { |
|
||||
return timeout; |
|
||||
} |
|
||||
|
|
||||
public void setSessionType(SessionType sessionType) { |
|
||||
this.sessionType = sessionType; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public SessionType getSessionType() { |
|
||||
return sessionType; |
|
||||
} |
|
||||
|
|
||||
public int nextSeqNumber() { |
|
||||
return seqNumber.getAndIncrement(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,75 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.transport.coap.session; |
|
||||
|
|
||||
public final class CoapSessionId implements SessionId { |
|
||||
|
|
||||
private final String clientAddress; |
|
||||
private final int clientPort; |
|
||||
private final String token; |
|
||||
|
|
||||
public CoapSessionId(String host, int port, String token) { |
|
||||
super(); |
|
||||
this.clientAddress = host; |
|
||||
this.clientPort = port; |
|
||||
this.token = token; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public int hashCode() { |
|
||||
final int prime = 31; |
|
||||
int result = 1; |
|
||||
result = prime * result + ((clientAddress == null) ? 0 : clientAddress.hashCode()); |
|
||||
result = prime * result + clientPort; |
|
||||
result = prime * result + ((token == null) ? 0 : token.hashCode()); |
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public boolean equals(Object obj) { |
|
||||
if (this == obj) |
|
||||
return true; |
|
||||
if (obj == null) |
|
||||
return false; |
|
||||
if (getClass() != obj.getClass()) |
|
||||
return false; |
|
||||
CoapSessionId other = (CoapSessionId) obj; |
|
||||
if (clientAddress == null) { |
|
||||
if (other.clientAddress != null) |
|
||||
return false; |
|
||||
} else if (!clientAddress.equals(other.clientAddress)) |
|
||||
return false; |
|
||||
if (clientPort != other.clientPort) |
|
||||
return false; |
|
||||
if (token == null) { |
|
||||
if (other.token != null) |
|
||||
return false; |
|
||||
} else if (!token.equals(other.token)) |
|
||||
return false; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public String toString() { |
|
||||
return "CoapSessionId [clientAddress=" + clientAddress + ", clientPort=" + clientPort + ", token=" + token + "]"; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public String toUidStr() { |
|
||||
return clientAddress + ":" + clientPort + ":" + token; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,34 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2018 The Thingsboard Authors |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<!DOCTYPE configuration> |
||||
|
<configuration scan="true" scanPeriod="10 seconds"> |
||||
|
|
||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> |
||||
|
<encoder> |
||||
|
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> |
||||
|
</encoder> |
||||
|
</appender> |
||||
|
|
||||
|
<logger name="org.thingsboard.server" level="TRACE" /> |
||||
|
|
||||
|
<root level="INFO"> |
||||
|
<appender-ref ref="STDOUT"/> |
||||
|
</root> |
||||
|
|
||||
|
</configuration> |
||||
@ -0,0 +1,68 @@ |
|||||
|
# |
||||
|
# Copyright © 2016-2018 The Thingsboard Authors |
||||
|
# |
||||
|
# 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. |
||||
|
# |
||||
|
|
||||
|
spring.main.web-environment: false |
||||
|
spring.main.web-application-type: none |
||||
|
|
||||
|
# MQTT server parameters |
||||
|
transport: |
||||
|
coap: |
||||
|
bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" |
||||
|
bind_port: "${COAP_BIND_PORT:5683}" |
||||
|
timeout: "${COAP_TIMEOUT:10000}" |
||||
|
|
||||
|
#Quota parameters |
||||
|
quota: |
||||
|
host: |
||||
|
# Max allowed number of API requests in interval for single host |
||||
|
limit: "${QUOTA_HOST_LIMIT:10000}" |
||||
|
# Interval duration |
||||
|
intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}" |
||||
|
# Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs |
||||
|
ttlMs: "${QUOTA_HOST_TTL_MS:60000}" |
||||
|
# Interval for scheduled task that cleans expired records. TTL is used for expiring |
||||
|
cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}" |
||||
|
# Enable Host API Limits |
||||
|
enabled: "${QUOTA_HOST_ENABLED:true}" |
||||
|
# Array of whitelist hosts |
||||
|
whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}" |
||||
|
# Array of blacklist hosts |
||||
|
blacklist: "${QUOTA_HOST_BLACKLIST:}" |
||||
|
log: |
||||
|
topSize: 10 |
||||
|
intervalMin: 2 |
||||
|
|
||||
|
kafka: |
||||
|
enabled: true |
||||
|
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" |
||||
|
acks: "${TB_KAFKA_ACKS:all}" |
||||
|
retries: "${TB_KAFKA_RETRIES:1}" |
||||
|
batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" |
||||
|
linger.ms: "${TB_KAFKA_LINGER_MS:1}" |
||||
|
buffer.memory: "${TB_BUFFER_MEMORY:33554432}" |
||||
|
transport_api: |
||||
|
requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}" |
||||
|
responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}" |
||||
|
max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}" |
||||
|
max_requests_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" |
||||
|
response_poll_interval: "${TB_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}" |
||||
|
response_auto_commit_interval: "${TB_TRANSPORT_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}" |
||||
|
rule_engine: |
||||
|
topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}" |
||||
|
notifications: |
||||
|
topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" |
||||
|
poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" |
||||
|
auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}" |
||||
@ -0,0 +1,6 @@ |
|||||
|
#!/bin/sh |
||||
|
|
||||
|
chown -R ${pkg.name}: ${pkg.logFolder} |
||||
|
chown -R ${pkg.name}: ${pkg.installFolder} |
||||
|
update-rc.d ${pkg.name} defaults |
||||
|
|
||||
@ -0,0 +1,3 @@ |
|||||
|
#!/bin/sh |
||||
|
|
||||
|
update-rc.d -f ${pkg.name} remove |
||||
@ -0,0 +1,18 @@ |
|||||
|
#!/bin/sh |
||||
|
|
||||
|
if ! getent group ${pkg.name} >/dev/null; then |
||||
|
addgroup --system ${pkg.name} |
||||
|
fi |
||||
|
|
||||
|
if ! getent passwd ${pkg.name} >/dev/null; then |
||||
|
adduser --quiet \ |
||||
|
--system \ |
||||
|
--ingroup ${pkg.name} \ |
||||
|
--quiet \ |
||||
|
--disabled-login \ |
||||
|
--disabled-password \ |
||||
|
--home ${pkg.installFolder} \ |
||||
|
--no-create-home \ |
||||
|
-gecos "Thingsboard application" \ |
||||
|
${pkg.name} |
||||
|
fi |
||||
@ -0,0 +1,5 @@ |
|||||
|
#!/bin/sh |
||||
|
|
||||
|
if [ -e /var/run/${pkg.name}/${pkg.name}.pid ]; then |
||||
|
service ${pkg.name} stop |
||||
|
fi |
||||
@ -0,0 +1,9 @@ |
|||||
|
#!/bin/sh |
||||
|
|
||||
|
chown -R ${pkg.name}: ${pkg.logFolder} |
||||
|
chown -R ${pkg.name}: ${pkg.installFolder} |
||||
|
|
||||
|
if [ $1 -eq 1 ] ; then |
||||
|
# Initial installation |
||||
|
systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || : |
||||
|
fi |
||||
@ -0,0 +1,6 @@ |
|||||
|
#!/bin/sh |
||||
|
|
||||
|
if [ $1 -ge 1 ] ; then |
||||
|
# Package upgrade, not uninstall |
||||
|
systemctl try-restart ${pkg.name}.service >/dev/null 2>&1 || : |
||||
|
fi |
||||
@ -0,0 +1,6 @@ |
|||||
|
#!/bin/sh |
||||
|
|
||||
|
getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name} |
||||
|
getent passwd ${pkg.name} >/dev/null || \ |
||||
|
useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \ |
||||
|
-c "Thingsboard application" |
||||
@ -0,0 +1,6 @@ |
|||||
|
#!/bin/sh |
||||
|
|
||||
|
if [ $1 -eq 0 ] ; then |
||||
|
# Package removal, not upgrade |
||||
|
systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || : |
||||
|
fi |
||||
@ -0,0 +1,11 @@ |
|||||
|
[Unit] |
||||
|
Description=${pkg.name} |
||||
|
After=syslog.target |
||||
|
|
||||
|
[Service] |
||||
|
User=${pkg.name} |
||||
|
ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar |
||||
|
SuccessExitStatus=143 |
||||
|
|
||||
|
[Install] |
||||
|
WantedBy=multi-user.target |
||||
@ -0,0 +1,87 @@ |
|||||
|
@ECHO OFF |
||||
|
|
||||
|
setlocal ENABLEEXTENSIONS |
||||
|
|
||||
|
@ECHO Detecting Java version installed. |
||||
|
:CHECK_JAVA_64 |
||||
|
@ECHO Detecting if it is 64 bit machine |
||||
|
set KEY_NAME="HKEY_LOCAL_MACHINE\Software\Wow6432Node\JavaSoft\Java Runtime Environment" |
||||
|
set VALUE_NAME=CurrentVersion |
||||
|
|
||||
|
FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( |
||||
|
set ValueName=%%A |
||||
|
set ValueType=%%B |
||||
|
set ValueValue=%%C |
||||
|
) |
||||
|
@ECHO CurrentVersion %ValueValue% |
||||
|
|
||||
|
SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%" |
||||
|
SET VALUE_NAME=JavaHome |
||||
|
|
||||
|
if defined ValueName ( |
||||
|
FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( |
||||
|
set ValueName2=%%A |
||||
|
set ValueType2=%%B |
||||
|
set JRE_PATH2=%%C |
||||
|
|
||||
|
if defined ValueName2 ( |
||||
|
set ValueName = %ValueName2% |
||||
|
set ValueType = %ValueType2% |
||||
|
set ValueValue = %JRE_PATH2% |
||||
|
) |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
IF NOT "%JRE_PATH2%" == "" GOTO JAVA_INSTALLED |
||||
|
|
||||
|
:CHECK_JAVA_32 |
||||
|
@ECHO Detecting if it is 32 bit machine |
||||
|
set KEY_NAME="HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment" |
||||
|
set VALUE_NAME=CurrentVersion |
||||
|
|
||||
|
FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( |
||||
|
set ValueName=%%A |
||||
|
set ValueType=%%B |
||||
|
set ValueValue=%%C |
||||
|
) |
||||
|
@ECHO CurrentVersion %ValueValue% |
||||
|
|
||||
|
SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%" |
||||
|
SET VALUE_NAME=JavaHome |
||||
|
|
||||
|
if defined ValueName ( |
||||
|
FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( |
||||
|
set ValueName2=%%A |
||||
|
set ValueType2=%%B |
||||
|
set JRE_PATH2=%%C |
||||
|
|
||||
|
if defined ValueName2 ( |
||||
|
set ValueName = %ValueName2% |
||||
|
set ValueType = %ValueType2% |
||||
|
set ValueValue = %JRE_PATH2% |
||||
|
) |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
IF "%JRE_PATH2%" == "" GOTO JAVA_NOT_INSTALLED |
||||
|
|
||||
|
:JAVA_INSTALLED |
||||
|
|
||||
|
@ECHO Java 1.8 found! |
||||
|
@ECHO Installing ${pkg.name} ... |
||||
|
|
||||
|
%BASE%${pkg.name}.exe install |
||||
|
|
||||
|
@ECHO ${pkg.name} installed successfully! |
||||
|
|
||||
|
GOTO END |
||||
|
|
||||
|
:JAVA_NOT_INSTALLED |
||||
|
@ECHO Java 1.8 or above is not installed |
||||
|
@ECHO Please go to https://java.com/ and install Java. Then retry installation. |
||||
|
PAUSE |
||||
|
GOTO END |
||||
|
|
||||
|
:END |
||||
|
|
||||
|
|
||||
@ -0,0 +1,36 @@ |
|||||
|
<service> |
||||
|
<id>${pkg.name}</id> |
||||
|
<name>${project.name}</name> |
||||
|
<description>${project.description}</description> |
||||
|
<workingdirectory>%BASE%\conf</workingdirectory> |
||||
|
<logpath>${pkg.winWrapperLogFolder}</logpath> |
||||
|
<logmode>rotate</logmode> |
||||
|
<env name="LOADER_PATH" value="%BASE%\conf" /> |
||||
|
<executable>java</executable> |
||||
|
<startargument>-Xloggc:%BASE%\logs\gc.log</startargument> |
||||
|
<startargument>-XX:+HeapDumpOnOutOfMemoryError</startargument> |
||||
|
<startargument>-XX:+PrintGCDetails</startargument> |
||||
|
<startargument>-XX:+PrintGCDateStamps</startargument> |
||||
|
<startargument>-XX:+PrintHeapAtGC</startargument> |
||||
|
<startargument>-XX:+PrintTenuringDistribution</startargument> |
||||
|
<startargument>-XX:+PrintGCApplicationStoppedTime</startargument> |
||||
|
<startargument>-XX:+UseGCLogFileRotation</startargument> |
||||
|
<startargument>-XX:NumberOfGCLogFiles=10</startargument> |
||||
|
<startargument>-XX:GCLogFileSize=10M</startargument> |
||||
|
<startargument>-XX:-UseBiasedLocking</startargument> |
||||
|
<startargument>-XX:+UseTLAB</startargument> |
||||
|
<startargument>-XX:+ResizeTLAB</startargument> |
||||
|
<startargument>-XX:+PerfDisableSharedMem</startargument> |
||||
|
<startargument>-XX:+UseCondCardMark</startargument> |
||||
|
<startargument>-XX:CMSWaitDuration=10000</startargument> |
||||
|
<startargument>-XX:+UseParNewGC</startargument> |
||||
|
<startargument>-XX:+UseConcMarkSweepGC</startargument> |
||||
|
<startargument>-XX:+CMSParallelRemarkEnabled</startargument> |
||||
|
<startargument>-XX:+CMSParallelInitialMarkEnabled</startargument> |
||||
|
<startargument>-XX:+CMSEdenChunksRecordAlways</startargument> |
||||
|
<startargument>-XX:CMSInitiatingOccupancyFraction=75</startargument> |
||||
|
<startargument>-XX:+UseCMSInitiatingOccupancyOnly</startargument> |
||||
|
<startargument>-jar</startargument> |
||||
|
<startargument>%BASE%\lib\${pkg.name}.jar</startargument> |
||||
|
|
||||
|
</service> |
||||
@ -0,0 +1,9 @@ |
|||||
|
@ECHO OFF |
||||
|
|
||||
|
@ECHO Stopping ${pkg.name} ... |
||||
|
net stop ${pkg.name} |
||||
|
|
||||
|
@ECHO Uninstalling ${pkg.name} ... |
||||
|
%~dp0${pkg.name}.exe uninstall |
||||
|
|
||||
|
@ECHO DONE. |
||||
@ -1,197 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.transport.coap; |
|
||||
|
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.eclipse.californium.core.CoapClient; |
|
||||
import org.eclipse.californium.core.CoapResponse; |
|
||||
import org.eclipse.californium.core.coap.CoAP.ResponseCode; |
|
||||
import org.eclipse.californium.core.coap.MediaTypeRegistry; |
|
||||
import org.junit.Assert; |
|
||||
import org.junit.Before; |
|
||||
import org.junit.Test; |
|
||||
import org.junit.runner.RunWith; |
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||
import org.springframework.context.annotation.Bean; |
|
||||
import org.springframework.context.annotation.Configuration; |
|
||||
import org.springframework.test.annotation.DirtiesContext; |
|
||||
import org.springframework.test.annotation.DirtiesContext.ClassMode; |
|
||||
import org.springframework.test.context.junit4.SpringRunner; |
|
||||
import org.thingsboard.server.common.data.Device; |
|
||||
import org.thingsboard.server.common.data.id.CustomerId; |
|
||||
import org.thingsboard.server.common.data.id.DeviceId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; |
|
||||
import org.thingsboard.server.common.data.security.DeviceCredentialsType; |
|
||||
import org.thingsboard.server.common.data.security.DeviceTokenCredentials; |
|
||||
import org.thingsboard.server.common.msg.session.*; |
|
||||
import org.thingsboard.server.common.transport.SessionMsgProcessor; |
|
||||
import org.thingsboard.server.common.transport.auth.DeviceAuthResult; |
|
||||
import org.thingsboard.server.common.transport.auth.DeviceAuthService; |
|
||||
import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService; |
|
||||
|
|
||||
import java.util.Optional; |
|
||||
import java.util.UUID; |
|
||||
|
|
||||
@RunWith(SpringRunner.class) |
|
||||
@DirtiesContext(classMode = ClassMode.BEFORE_CLASS) |
|
||||
@Slf4j |
|
||||
public class CoapServerTest { |
|
||||
|
|
||||
private static final int TEST_PORT = 5555; |
|
||||
private static final String TELEMETRY_POST_MESSAGE = "[{\"key1\":\"value1\"}]"; |
|
||||
private static final String TEST_ATTRIBUTES_RESPONSE = "{\"key1\":\"value1\",\"key2\":42}"; |
|
||||
private static final String DEVICE1_TOKEN = "Device1Token"; |
|
||||
|
|
||||
@Configuration |
|
||||
public static class EchoCoapServerITConfiguration extends CoapServerTestConfiguration { |
|
||||
|
|
||||
@Bean |
|
||||
public static DeviceAuthService authService() { |
|
||||
return new DeviceAuthService() { |
|
||||
|
|
||||
private final DeviceId devId = new DeviceId(UUID.randomUUID()); |
|
||||
|
|
||||
@Override |
|
||||
public DeviceAuthResult process(DeviceCredentialsFilter credentials) { |
|
||||
if (credentials != null && credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { |
|
||||
DeviceTokenCredentials tokenCredentials = (DeviceTokenCredentials) credentials; |
|
||||
if (tokenCredentials.getCredentialsId().equals(DEVICE1_TOKEN)) { |
|
||||
return DeviceAuthResult.of(devId); |
|
||||
} |
|
||||
} |
|
||||
return DeviceAuthResult.of("Credentials are invalid!"); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Optional<Device> findDeviceById(DeviceId deviceId) { |
|
||||
if (deviceId.equals(devId)) { |
|
||||
Device dev = new Device(); |
|
||||
dev.setId(devId); |
|
||||
dev.setTenantId(new TenantId(UUID.randomUUID())); |
|
||||
dev.setCustomerId(new CustomerId(UUID.randomUUID())); |
|
||||
return Optional.of(dev); |
|
||||
} else { |
|
||||
return Optional.empty(); |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
@Bean |
|
||||
public static SessionMsgProcessor sessionMsgProcessor() { |
|
||||
return new SessionMsgProcessor() { |
|
||||
|
|
||||
// @Override
|
|
||||
// public void process(SessionAwareMsg toActorMsg) {
|
|
||||
// if (toActorMsg instanceof TransportToDeviceSessionActorMsg) {
|
|
||||
// AdaptorToSessionActorMsg sessionMsg = ((TransportToDeviceSessionActorMsg) toActorMsg).getSessionMsg();
|
|
||||
// try {
|
|
||||
// FromDeviceMsg deviceMsg = sessionMsg.getMsg();
|
|
||||
// ToDeviceMsg toDeviceMsg = null;
|
|
||||
// if (deviceMsg.getMsgType() == SessionMsgType.POST_TELEMETRY_REQUEST) {
|
|
||||
// toDeviceMsg = BasicStatusCodeResponse.onSuccess(deviceMsg.getMsgType(), BasicRequest.DEFAULT_REQUEST_ID);
|
|
||||
// } else if (deviceMsg.getMsgType() == SessionMsgType.GET_ATTRIBUTES_REQUEST) {
|
|
||||
// List<AttributeKvEntry> data = new ArrayList<>();
|
|
||||
// data.add(new BaseAttributeKvEntry(new StringDataEntry("key1", "value1"), System.currentTimeMillis()));
|
|
||||
// data.add(new BaseAttributeKvEntry(new LongDataEntry("key2", 42L), System.currentTimeMillis()));
|
|
||||
// BasicAttributeKVMsg kv = BasicAttributeKVMsg.fromClient(data);
|
|
||||
// toDeviceMsg = BasicGetAttributesResponse.onSuccess(deviceMsg.getMsgType(), BasicRequest.DEFAULT_REQUEST_ID, kv);
|
|
||||
// }
|
|
||||
// if (toDeviceMsg != null) {
|
|
||||
// sessionMsg.getSessionContext().onMsg(new BasicSessionActorToAdaptorMsg(sessionMsg.getSessionContext(), toDeviceMsg));
|
|
||||
// }
|
|
||||
// } catch (Exception e) {
|
|
||||
// e.printStackTrace();
|
|
||||
// }
|
|
||||
// }
|
|
||||
// }
|
|
||||
|
|
||||
@Override |
|
||||
public void onDeviceAdded(Device device) { |
|
||||
|
|
||||
} |
|
||||
|
|
||||
|
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
@Bean |
|
||||
public static HostRequestsQuotaService quotaService() { |
|
||||
return new HostRequestsQuotaService(null, null, null, null, false); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Autowired |
|
||||
private CoapTransportService service; |
|
||||
|
|
||||
@Before |
|
||||
public void beforeTest() { |
|
||||
log.info("Service info: {}", service.toString()); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void testBadJsonTelemetryPostRequest() { |
|
||||
CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.TELEMETRY.name().toLowerCase()); |
|
||||
CoapResponse response = client.setTimeout(6000).post("test", MediaTypeRegistry.APPLICATION_JSON); |
|
||||
Assert.assertEquals(ResponseCode.BAD_REQUEST, response.getCode()); |
|
||||
log.info("Response: {}, {}", response.getCode(), response.getResponseText()); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void testNoCredentialsPostRequest() { |
|
||||
CoapClient client = new CoapClient(getBaseTestUrl()); |
|
||||
CoapResponse response = client.setTimeout(6000).post(TELEMETRY_POST_MESSAGE, MediaTypeRegistry.APPLICATION_JSON); |
|
||||
Assert.assertEquals(ResponseCode.BAD_REQUEST, response.getCode()); |
|
||||
log.info("Response: {}, {}", response.getCode(), response.getResponseText()); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void testValidJsonTelemetryPostRequest() { |
|
||||
CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.TELEMETRY.name().toLowerCase()); |
|
||||
CoapResponse response = client.setTimeout(6000).post(TELEMETRY_POST_MESSAGE, MediaTypeRegistry.APPLICATION_JSON); |
|
||||
Assert.assertEquals(ResponseCode.CREATED, response.getCode()); |
|
||||
log.info("Response: {}, {}", response.getCode(), response.getResponseText()); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void testNoCredentialsAttributesGetRequest() { |
|
||||
CoapClient client = new CoapClient("coap://localhost:5555/api/v1?keys=key1,key2"); |
|
||||
CoapResponse response = client.setTimeout(6000).get(); |
|
||||
Assert.assertEquals(ResponseCode.BAD_REQUEST, response.getCode()); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void testNoKeysAttributesGetRequest() { |
|
||||
CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.ATTRIBUTES.name().toLowerCase() + "?data=key1,key2"); |
|
||||
CoapResponse response = client.setTimeout(6000).get(); |
|
||||
Assert.assertEquals(ResponseCode.CONTENT, response.getCode()); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void testValidAttributesGetRequest() { |
|
||||
CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.ATTRIBUTES.name().toLowerCase() + "?clientKeys=key1,key2"); |
|
||||
CoapResponse response = client.setTimeout(6000).get(); |
|
||||
Assert.assertEquals(ResponseCode.CONTENT, response.getCode()); |
|
||||
Assert.assertEquals(TEST_ATTRIBUTES_RESPONSE, response.getResponseText()); |
|
||||
log.info("Response: {}, {}", response.getCode(), response.getResponseText()); |
|
||||
} |
|
||||
|
|
||||
private String getBaseTestUrl() { |
|
||||
return "coap://localhost:" + TEST_PORT + "/api/v1/"; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,35 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* 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. |
|
||||
*/ |
|
||||
package org.thingsboard.server.transport.coap; |
|
||||
|
|
||||
import org.springframework.context.annotation.Bean; |
|
||||
import org.springframework.context.annotation.ComponentScan; |
|
||||
import org.springframework.context.annotation.Configuration; |
|
||||
import org.springframework.context.annotation.PropertySource; |
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; |
|
||||
import org.springframework.test.context.TestPropertySource; |
|
||||
|
|
||||
@Configuration |
|
||||
@ComponentScan({ "org.thingsboard.server.transport.coap" }) |
|
||||
@PropertySource("classpath:coap-transport-test.properties") |
|
||||
public class CoapServerTestConfiguration { |
|
||||
|
|
||||
@Bean |
|
||||
public static PropertySourcesPlaceholderConfigurer propertyConfig() { |
|
||||
return new PropertySourcesPlaceholderConfigurer(); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,4 +0,0 @@ |
|||||
coap.bind_address=0.0.0.0 |
|
||||
coap.bind_port=5555 |
|
||||
coap.adaptor=JsonCoapAdaptor |
|
||||
coap.timeout=10000 |
|
||||
Loading…
Reference in new issue