|
|
|
@ -65,6 +65,7 @@ import java.util.concurrent.ConcurrentHashMap; |
|
|
|
import java.util.concurrent.ConcurrentMap; |
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
import java.util.concurrent.atomic.AtomicInteger; |
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
@Slf4j |
|
|
|
public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
@ -76,9 +77,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
private static final int REQUEST_ID_POSITION_CERTIFICATE_REQUEST = 4; |
|
|
|
private static final String DTLS_SESSION_ID_KEY = "DTLS_SESSION_ID"; |
|
|
|
|
|
|
|
private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionInfoMap = new ConcurrentHashMap<>(); |
|
|
|
private final ConcurrentMap<String, AtomicInteger> tokenToObserveNotificationSeqMap = new ConcurrentHashMap<>(); |
|
|
|
private final ConcurrentMap<TransportProtos.SessionInfoProto, ObserveRelation> sessionInfoToObserveRelationMap = new ConcurrentHashMap<>(); |
|
|
|
private final ConcurrentMap<String, CoapObserveSessionInfo> tokenToCoapSessionInfoMap = new ConcurrentHashMap<>(); |
|
|
|
private final ConcurrentMap<CoapObserveSessionInfo, ObserveRelation> sessionInfoToObserveRelationMap = new ConcurrentHashMap<>(); |
|
|
|
private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet(); |
|
|
|
private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet(); |
|
|
|
|
|
|
|
@ -94,7 +94,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
this.timeout = coapServerService.getTimeout(); |
|
|
|
this.sessionReportTimeout = ctx.getSessionReportTimeout(); |
|
|
|
ctx.getScheduler().scheduleAtFixedRate(() -> { |
|
|
|
Set<TransportProtos.SessionInfoProto> observeSessions = sessionInfoToObserveRelationMap.keySet(); |
|
|
|
Set<CoapObserveSessionInfo> coapObserveSessionInfos = sessionInfoToObserveRelationMap.keySet(); |
|
|
|
Set<TransportProtos.SessionInfoProto> observeSessions = coapObserveSessionInfos |
|
|
|
.stream() |
|
|
|
.map(CoapObserveSessionInfo::getSessionInfoProto) |
|
|
|
.collect(Collectors.toSet()); |
|
|
|
observeSessions.forEach(this::reportActivity); |
|
|
|
}, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); |
|
|
|
} |
|
|
|
@ -112,17 +116,17 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
relation.setEstablished(); |
|
|
|
addObserveRelation(relation); |
|
|
|
} |
|
|
|
AtomicInteger notificationCounter = tokenToObserveNotificationSeqMap.computeIfAbsent(token, s -> new AtomicInteger(0)); |
|
|
|
response.getOptions().setObserve(notificationCounter.getAndIncrement()); |
|
|
|
AtomicInteger observeNotificationCounter = tokenToCoapSessionInfoMap.get(token).getObserveNotificationCounter(); |
|
|
|
response.getOptions().setObserve(observeNotificationCounter.getAndIncrement()); |
|
|
|
} // ObserveLayer takes care of the else case
|
|
|
|
} |
|
|
|
|
|
|
|
public void clearAndNotifyObserveRelation(ObserveRelation relation, CoAP.ResponseCode code) { |
|
|
|
private void clearAndNotifyObserveRelation(ObserveRelation relation, CoAP.ResponseCode code) { |
|
|
|
relation.cancel(); |
|
|
|
relation.getExchange().sendResponse(new Response(code)); |
|
|
|
} |
|
|
|
|
|
|
|
public Map<TransportProtos.SessionInfoProto, ObserveRelation> getSessionInfoToObserveRelationMap() { |
|
|
|
private Map<CoapObserveSessionInfo, ObserveRelation> getCoapSessionInfoToObserveRelationMap() { |
|
|
|
return sessionInfoToObserveRelationMap; |
|
|
|
} |
|
|
|
|
|
|
|
@ -278,8 +282,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); |
|
|
|
break; |
|
|
|
case SUBSCRIBE_ATTRIBUTES_REQUEST: |
|
|
|
TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionInfoMap.get(getTokenFromRequest(request)); |
|
|
|
if (currentAttrSession == null) { |
|
|
|
CoapObserveSessionInfo currentCoapObserveAttrSessionInfo = tokenToCoapSessionInfoMap.get(getTokenFromRequest(request)); |
|
|
|
if (currentCoapObserveAttrSessionInfo == null) { |
|
|
|
attributeSubscriptions.add(sessionId); |
|
|
|
registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, |
|
|
|
transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), getTokenFromRequest(request)); |
|
|
|
@ -291,20 +295,20 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
} |
|
|
|
break; |
|
|
|
case UNSUBSCRIBE_ATTRIBUTES_REQUEST: |
|
|
|
TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(getTokenFromRequest(request)); |
|
|
|
if (attrSession != null) { |
|
|
|
CoapObserveSessionInfo coapObserveAttrSessionInfo = lookupAsyncSessionInfo(getTokenFromRequest(request)); |
|
|
|
if (coapObserveAttrSessionInfo != null) { |
|
|
|
TransportProtos.SessionInfoProto attrSession = coapObserveAttrSessionInfo.getSessionInfoProto(); |
|
|
|
UUID attrSessionId = toSessionId(attrSession); |
|
|
|
attributeSubscriptions.remove(attrSessionId); |
|
|
|
sessionInfoToObserveRelationMap.remove(attrSession); |
|
|
|
transportService.process(attrSession, |
|
|
|
TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), |
|
|
|
new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); |
|
|
|
closeAndDeregister(sessionInfo); |
|
|
|
new CoapNoOpCallback(exchange)); |
|
|
|
} |
|
|
|
closeAndDeregister(sessionInfo); |
|
|
|
break; |
|
|
|
case SUBSCRIBE_RPC_COMMANDS_REQUEST: |
|
|
|
TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionInfoMap.get(getTokenFromRequest(request)); |
|
|
|
if (currentRpcSession == null) { |
|
|
|
CoapObserveSessionInfo currentCoapObserveRpcSessionInfo = tokenToCoapSessionInfoMap.get(getTokenFromRequest(request)); |
|
|
|
if (currentCoapObserveRpcSessionInfo == null) { |
|
|
|
rpcSubscriptions.add(sessionId); |
|
|
|
registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, |
|
|
|
transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), getTokenFromRequest(request)); |
|
|
|
@ -315,16 +319,16 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
} |
|
|
|
break; |
|
|
|
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: |
|
|
|
TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(getTokenFromRequest(request)); |
|
|
|
if (rpcSession != null) { |
|
|
|
CoapObserveSessionInfo coapObserveRpcSessionInfo = lookupAsyncSessionInfo(getTokenFromRequest(request)); |
|
|
|
if (coapObserveRpcSessionInfo != null) { |
|
|
|
TransportProtos.SessionInfoProto rpcSession = coapObserveRpcSessionInfo.getSessionInfoProto(); |
|
|
|
UUID rpcSessionId = toSessionId(rpcSession); |
|
|
|
rpcSubscriptions.remove(rpcSessionId); |
|
|
|
sessionInfoToObserveRelationMap.remove(rpcSession); |
|
|
|
transportService.process(rpcSession, |
|
|
|
TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), |
|
|
|
new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); |
|
|
|
closeAndDeregister(sessionInfo); |
|
|
|
} |
|
|
|
closeAndDeregister(sessionInfo); |
|
|
|
break; |
|
|
|
case TO_DEVICE_RPC_RESPONSE: |
|
|
|
transportService.process(sessionInfo, |
|
|
|
@ -356,13 +360,12 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
return new UUID(sessionInfoProto.getSessionIdMSB(), sessionInfoProto.getSessionIdLSB()); |
|
|
|
} |
|
|
|
|
|
|
|
private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { |
|
|
|
tokenToObserveNotificationSeqMap.remove(token); |
|
|
|
return tokenToSessionInfoMap.remove(token); |
|
|
|
private CoapObserveSessionInfo lookupAsyncSessionInfo(String token) { |
|
|
|
return tokenToCoapSessionInfoMap.remove(token); |
|
|
|
} |
|
|
|
|
|
|
|
private void registerAsyncCoapSession(CoapExchange exchange, TransportProtos.SessionInfoProto sessionInfo, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder, String token) { |
|
|
|
tokenToSessionInfoMap.putIfAbsent(token, sessionInfo); |
|
|
|
tokenToCoapSessionInfoMap.putIfAbsent(token, new CoapObserveSessionInfo(sessionInfo)); |
|
|
|
transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder, sessionInfo)); |
|
|
|
transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); |
|
|
|
} |
|
|
|
@ -477,45 +480,36 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg) { |
|
|
|
public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg msg) { |
|
|
|
log.trace("[{}] Received attributes update notification to device", sessionId); |
|
|
|
try { |
|
|
|
exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg)); |
|
|
|
} catch (AdaptorException e) { |
|
|
|
log.trace("Failed to reply due to error", e); |
|
|
|
exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); |
|
|
|
closeObserveRelationAndNotify(sessionId, CoAP.ResponseCode.INTERNAL_SERVER_ERROR); |
|
|
|
closeAndDeregister(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { |
|
|
|
log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); |
|
|
|
Map<TransportProtos.SessionInfoProto, ObserveRelation> sessionToObserveRelationMap = coapTransportResource.getSessionInfoToObserveRelationMap(); |
|
|
|
if (coapTransportResource.getObserverCount() > 0 && !CollectionUtils.isEmpty(sessionToObserveRelationMap)) { |
|
|
|
Set<TransportProtos.SessionInfoProto> observeSessions = sessionToObserveRelationMap.keySet(); |
|
|
|
Optional<TransportProtos.SessionInfoProto> observeSessionToClose = observeSessions.stream().filter(sessionInfoProto -> { |
|
|
|
UUID observeSessionId = new UUID(sessionInfoProto.getSessionIdMSB(), sessionInfoProto.getSessionIdLSB()); |
|
|
|
return observeSessionId.equals(sessionId); |
|
|
|
}).findFirst(); |
|
|
|
if (observeSessionToClose.isPresent()) { |
|
|
|
TransportProtos.SessionInfoProto sessionInfoProto = observeSessionToClose.get(); |
|
|
|
ObserveRelation observeRelation = sessionToObserveRelationMap.get(sessionInfoProto); |
|
|
|
coapTransportResource.clearAndNotifyObserveRelation(observeRelation, CoAP.ResponseCode.SERVICE_UNAVAILABLE); |
|
|
|
} |
|
|
|
} |
|
|
|
closeObserveRelationAndNotify(sessionId, CoAP.ResponseCode.SERVICE_UNAVAILABLE); |
|
|
|
closeAndDeregister(); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg msg) { |
|
|
|
boolean successful; |
|
|
|
public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg msg) { |
|
|
|
log.trace("[{}] Received RPC command to device", sessionId); |
|
|
|
boolean successful = true; |
|
|
|
try { |
|
|
|
exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg, rpcRequestDynamicMessageBuilder)); |
|
|
|
successful = true; |
|
|
|
} catch (AdaptorException e) { |
|
|
|
log.trace("Failed to reply due to error", e); |
|
|
|
exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); |
|
|
|
closeObserveRelationAndNotify(sessionId, CoAP.ResponseCode.INTERNAL_SERVER_ERROR); |
|
|
|
successful = false; |
|
|
|
} |
|
|
|
if (msg.getPersisted()) { |
|
|
|
} finally { |
|
|
|
if (msg.getPersisted()) { |
|
|
|
RpcStatus status; |
|
|
|
if (!successful) { |
|
|
|
status = RpcStatus.FAILED; |
|
|
|
@ -531,6 +525,10 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
.setStatus(status.name()) |
|
|
|
.build(); |
|
|
|
coapTransportResource.transportService.process(sessionInfo, responseMsg, TransportServiceCallback.EMPTY); |
|
|
|
} |
|
|
|
if (!successful) { |
|
|
|
closeAndDeregister(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -547,6 +545,30 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
private boolean isConRequest() { |
|
|
|
return exchange.advanced().getRequest().isConfirmable(); |
|
|
|
} |
|
|
|
|
|
|
|
private void closeObserveRelationAndNotify(UUID sessionId, CoAP.ResponseCode responseCode) { |
|
|
|
Map<CoapObserveSessionInfo, ObserveRelation> sessionToObserveRelationMap = coapTransportResource.getCoapSessionInfoToObserveRelationMap(); |
|
|
|
if (coapTransportResource.getObserverCount() > 0 && !CollectionUtils.isEmpty(sessionToObserveRelationMap)) { |
|
|
|
Optional<CoapObserveSessionInfo> observeSessionToClose = sessionToObserveRelationMap.keySet().stream().filter(coapObserveSessionInfo -> { |
|
|
|
TransportProtos.SessionInfoProto sessionToDelete = coapObserveSessionInfo.getSessionInfoProto(); |
|
|
|
UUID observeSessionId = new UUID(sessionToDelete.getSessionIdMSB(), sessionToDelete.getSessionIdLSB()); |
|
|
|
return observeSessionId.equals(sessionId); |
|
|
|
}).findFirst(); |
|
|
|
if (observeSessionToClose.isPresent()) { |
|
|
|
CoapObserveSessionInfo coapObserveSessionInfo = observeSessionToClose.get(); |
|
|
|
ObserveRelation observeRelation = sessionToObserveRelationMap.get(coapObserveSessionInfo); |
|
|
|
coapTransportResource.clearAndNotifyObserveRelation(observeRelation, responseCode); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void closeAndDeregister() { |
|
|
|
Request request = exchange.advanced().getRequest(); |
|
|
|
String token = coapTransportResource.getTokenFromRequest(request); |
|
|
|
CoapObserveSessionInfo deleted = coapTransportResource.lookupAsyncSessionInfo(token); |
|
|
|
coapTransportResource.closeAndDeregister(deleted.getSessionInfoProto()); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
public class CoapResourceObserver implements ResourceObserver { |
|
|
|
@ -571,7 +593,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
public void addedObserveRelation(ObserveRelation relation) { |
|
|
|
Request request = relation.getExchange().getRequest(); |
|
|
|
String token = getTokenFromRequest(request); |
|
|
|
sessionInfoToObserveRelationMap.putIfAbsent(tokenToSessionInfoMap.get(token), relation); |
|
|
|
sessionInfoToObserveRelationMap.putIfAbsent(tokenToCoapSessionInfoMap.get(token), relation); |
|
|
|
log.trace("Added Observe relation for token: {}", token); |
|
|
|
} |
|
|
|
|
|
|
|
@ -579,8 +601,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
public void removedObserveRelation(ObserveRelation relation) { |
|
|
|
Request request = relation.getExchange().getRequest(); |
|
|
|
String token = getTokenFromRequest(request); |
|
|
|
TransportProtos.SessionInfoProto session = tokenToSessionInfoMap.get(token); |
|
|
|
sessionInfoToObserveRelationMap.remove(session); |
|
|
|
sessionInfoToObserveRelationMap.remove(tokenToCoapSessionInfoMap.get(token)); |
|
|
|
log.trace("Relation removed for token: {}", token); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -591,7 +612,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
transportService.deregisterSession(session); |
|
|
|
rpcSubscriptions.remove(sessionId); |
|
|
|
attributeSubscriptions.remove(sessionId); |
|
|
|
sessionInfoToObserveRelationMap.remove(session); |
|
|
|
} |
|
|
|
|
|
|
|
private TransportConfigurationContainer getTransportConfigurationContainer(DeviceProfile deviceProfile) throws AdaptorException { |
|
|
|
@ -657,4 +677,17 @@ public class CoapTransportResource extends AbstractCoapTransportResource { |
|
|
|
this.jsonPayload = jsonPayload; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Data |
|
|
|
private static class CoapObserveSessionInfo { |
|
|
|
|
|
|
|
private final TransportProtos.SessionInfoProto sessionInfoProto; |
|
|
|
private final AtomicInteger observeNotificationCounter; |
|
|
|
|
|
|
|
private CoapObserveSessionInfo(TransportProtos.SessionInfoProto sessionInfoProto) { |
|
|
|
this.sessionInfoProto = sessionInfoProto; |
|
|
|
this.observeNotificationCounter = new AtomicInteger(0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|