721 changed files with 27054 additions and 10060 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,71 @@ |
|||
-- |
|||
-- Copyright © 2016-2021 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. |
|||
-- |
|||
|
|||
CREATE TABLE IF NOT EXISTS entity_alarm ( |
|||
tenant_id uuid NOT NULL, |
|||
entity_type varchar(32), |
|||
entity_id uuid NOT NULL, |
|||
created_time bigint NOT NULL, |
|||
alarm_type varchar(255) NOT NULL, |
|||
customer_id uuid, |
|||
alarm_id uuid, |
|||
CONSTRAINT entity_alarm_pkey PRIMARY KEY (entity_id, alarm_id), |
|||
CONSTRAINT fk_entity_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE |
|||
); |
|||
|
|||
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_status_created_time ON alarm(tenant_id, status, created_time DESC); |
|||
CREATE INDEX IF NOT EXISTS idx_entity_alarm_created_time ON entity_alarm(tenant_id, entity_id, created_time DESC); |
|||
CREATE INDEX IF NOT EXISTS idx_entity_alarm_alarm_id ON entity_alarm(alarm_id); |
|||
|
|||
INSERT INTO entity_alarm(tenant_id, entity_type, entity_id, created_time, alarm_type, customer_id, alarm_id) |
|||
SELECT tenant_id, |
|||
CASE |
|||
WHEN originator_type = 0 THEN 'TENANT' |
|||
WHEN originator_type = 1 THEN 'CUSTOMER' |
|||
WHEN originator_type = 2 THEN 'USER' |
|||
WHEN originator_type = 3 THEN 'DASHBOARD' |
|||
WHEN originator_type = 4 THEN 'ASSET' |
|||
WHEN originator_type = 5 THEN 'DEVICE' |
|||
WHEN originator_type = 6 THEN 'ALARM' |
|||
WHEN originator_type = 7 THEN 'RULE_CHAIN' |
|||
WHEN originator_type = 8 THEN 'RULE_NODE' |
|||
WHEN originator_type = 9 THEN 'ENTITY_VIEW' |
|||
WHEN originator_type = 10 THEN 'WIDGETS_BUNDLE' |
|||
WHEN originator_type = 11 THEN 'WIDGET_TYPE' |
|||
WHEN originator_type = 12 THEN 'TENANT_PROFILE' |
|||
WHEN originator_type = 13 THEN 'DEVICE_PROFILE' |
|||
WHEN originator_type = 14 THEN 'API_USAGE_STATE' |
|||
WHEN originator_type = 15 THEN 'TB_RESOURCE' |
|||
WHEN originator_type = 16 THEN 'OTA_PACKAGE' |
|||
WHEN originator_type = 17 THEN 'EDGE' |
|||
WHEN originator_type = 18 THEN 'RPC' |
|||
else 'UNKNOWN' |
|||
END, |
|||
originator_id, |
|||
created_time, |
|||
type, |
|||
customer_id, |
|||
id |
|||
FROM alarm |
|||
ON CONFLICT DO NOTHING; |
|||
|
|||
INSERT INTO entity_alarm(tenant_id, entity_type, entity_id, created_time, alarm_type, customer_id, alarm_id) |
|||
SELECT a.tenant_id, r.from_type, r.from_id, created_time, type, customer_id, id |
|||
FROM alarm a |
|||
INNER JOIN relation r ON r.relation_type_group = 'ALARM' and r.relation_type = 'ANY' and a.id = r.to_id |
|||
ON CONFLICT DO NOTHING; |
|||
|
|||
DELETE FROM relation r WHERE r.relation_type_group = 'ALARM'; |
|||
@ -0,0 +1,213 @@ |
|||
-- |
|||
-- Copyright © 2016-2021 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. |
|||
-- |
|||
|
|||
|
|||
CREATE OR REPLACE PROCEDURE update_profile_bootstrap() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
|
|||
BEGIN |
|||
|
|||
UPDATE device_profile |
|||
SET profile_data = jsonb_set( |
|||
profile_data, |
|||
'{transportConfiguration}', |
|||
get_bootstrap( |
|||
profile_data::jsonb #> '{transportConfiguration}', |
|||
subquery.publickey_bs, |
|||
subquery.publickey_lw, |
|||
profile_data::json #>> '{transportConfiguration, bootstrap, bootstrapServer, securityMode}', |
|||
profile_data::json #>> '{transportConfiguration, bootstrap, lwm2mServer, securityMode}'), |
|||
true) |
|||
FROM ( |
|||
SELECT id, |
|||
encode( |
|||
decode(profile_data::json #> '{transportConfiguration,bootstrap,bootstrapServer}' ->> |
|||
'serverPublicKey', 'hex')::bytea, 'base64') AS publickey_bs, |
|||
encode( |
|||
decode(profile_data::json #> '{transportConfiguration,bootstrap,lwm2mServer}' ->> |
|||
'serverPublicKey', 'hex')::bytea, 'base64') AS publickey_lw |
|||
FROM device_profile |
|||
WHERE transport_type = 'LWM2M' |
|||
) AS subquery |
|||
WHERE device_profile.id = subquery.id |
|||
AND subquery.publickey_bs IS NOT NULL |
|||
AND subquery.publickey_lw IS NOT NULL; |
|||
|
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION get_bootstrap(transport_configuration_in jsonb, publickey_bs text, |
|||
publickey_lw text, security_mode_bs text, |
|||
security_mode_lw text) RETURNS jsonb AS |
|||
$$ |
|||
|
|||
DECLARE |
|||
bootstrap_new jsonb; |
|||
bootstrap_in jsonb; |
|||
|
|||
BEGIN |
|||
|
|||
IF security_mode_lw IS NULL THEN |
|||
security_mode_lw := 'NO_SEC'; |
|||
END IF; |
|||
|
|||
IF security_mode_bs IS NULL THEN |
|||
security_mode_bs := 'NO_SEC'; |
|||
END IF; |
|||
|
|||
bootstrap_in := transport_configuration_in::jsonb #> '{bootstrap}'; |
|||
bootstrap_new := json_build_array( |
|||
json_build_object('shortServerId', bootstrap_in::json #> '{bootstrapServer}' -> 'serverId', |
|||
'securityMode', security_mode_bs, |
|||
'binding', bootstrap_in::json #> '{servers}' ->> 'binding', |
|||
'lifetime', bootstrap_in::json #> '{servers}' -> 'lifetime', |
|||
'notifIfDisabled', bootstrap_in::json #> '{servers}' -> 'notifIfDisabled', |
|||
'defaultMinPeriod', bootstrap_in::json #> '{servers}' -> 'defaultMinPeriod', |
|||
'host', bootstrap_in::json #> '{bootstrapServer}' ->> 'host', |
|||
'port', bootstrap_in::json #> '{bootstrapServer}' -> 'port', |
|||
'serverPublicKey', publickey_bs, |
|||
'bootstrapServerIs', true, |
|||
'clientHoldOffTime', bootstrap_in::json #> '{bootstrapServer}' -> 'clientHoldOffTime', |
|||
'bootstrapServerAccountTimeout', |
|||
bootstrap_in::json #> '{bootstrapServer}' -> 'bootstrapServerAccountTimeout' |
|||
), |
|||
json_build_object('shortServerId', bootstrap_in::json #> '{lwm2mServer}' -> 'serverId', |
|||
'securityMode', security_mode_lw, |
|||
'binding', bootstrap_in::json #> '{servers}' ->> 'binding', |
|||
'lifetime', bootstrap_in::json #> '{servers}' -> 'lifetime', |
|||
'notifIfDisabled', bootstrap_in::json #> '{servers}' -> 'notifIfDisabled', |
|||
'defaultMinPeriod', bootstrap_in::json #> '{servers}' -> 'defaultMinPeriod', |
|||
'host', bootstrap_in::json #> '{lwm2mServer}' ->> 'host', |
|||
'port', bootstrap_in::json #> '{lwm2mServer}' -> 'port', |
|||
'serverPublicKey', publickey_lw, |
|||
'bootstrapServerIs', false, |
|||
'clientHoldOffTime', bootstrap_in::json #> '{lwm2mServer}' -> 'clientHoldOffTime', |
|||
'bootstrapServerAccountTimeout', |
|||
bootstrap_in::json #> '{lwm2mServer}' -> 'bootstrapServerAccountTimeout' |
|||
) |
|||
); |
|||
RETURN jsonb_set( |
|||
transport_configuration_in, |
|||
'{bootstrap}', |
|||
bootstrap_new, |
|||
true) || '{"bootstrapServerUpdateEnable": true}'; |
|||
|
|||
END; |
|||
$$ LANGUAGE plpgsql; |
|||
|
|||
CREATE OR REPLACE PROCEDURE update_device_credentials_to_base64_and_bootstrap() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
|
|||
BEGIN |
|||
|
|||
UPDATE device_credentials |
|||
SET credentials_value = get_device_and_bootstrap(credentials_value::text) |
|||
WHERE credentials_type = 'LWM2M_CREDENTIALS'; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION get_device_and_bootstrap(IN credentials_value text, OUT credentials_value_new text) |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
client_secret_key text; |
|||
client_public_key_or_id text; |
|||
client_key_value_object jsonb; |
|||
client_bootstrap_server_value_object jsonb; |
|||
client_bootstrap_server_object jsonb; |
|||
client_bootstrap_object jsonb; |
|||
|
|||
BEGIN |
|||
credentials_value_new := credentials_value; |
|||
IF credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode' = 'RPK' AND |
|||
NULLIF((credentials_value::jsonb #> '{client}' ->> 'key' ~ '^[0-9a-fA-F]+$')::text, 'false') = 'true' THEN |
|||
client_public_key_or_id := encode(decode(credentials_value::jsonb #> '{client}' ->> 'key', 'hex')::bytea, 'base64'); |
|||
client_key_value_object := json_build_object( |
|||
'endpoint', credentials_value::jsonb #> '{client}' ->> 'endpoint', |
|||
'securityConfigClientMode', credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode', |
|||
'key', client_public_key_or_id); |
|||
credentials_value_new := |
|||
credentials_value_new::jsonb || json_build_object('client', client_key_value_object)::jsonb; |
|||
END IF; |
|||
IF credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode' = 'X509' AND |
|||
NULLIF((credentials_value::jsonb #> '{client}' ->> 'cert' ~ '^[0-9a-fA-F]+$')::text, 'false') = 'true' THEN |
|||
client_public_key_or_id := |
|||
encode(decode(credentials_value::jsonb #> '{client}' ->> 'cert', 'hex')::bytea, 'base64'); |
|||
client_key_value_object := json_build_object( |
|||
'endpoint', credentials_value::jsonb #> '{client}' ->> 'endpoint', |
|||
'securityConfigClientMode', credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode', |
|||
'cert', client_public_key_or_id); |
|||
credentials_value_new := |
|||
credentials_value_new::jsonb || json_build_object('client', client_key_value_object)::jsonb; |
|||
END IF; |
|||
|
|||
IF credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode' = 'RPK' OR |
|||
credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode' = 'X509' THEN |
|||
IF NULLIF((credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientSecretKey' ~ '^[0-9a-fA-F]+$')::text, |
|||
'false') = 'true' AND |
|||
NULLIF( |
|||
(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientPublicKeyOrId' ~ '^[0-9a-fA-F]+$')::text, |
|||
'false') = 'true' THEN |
|||
client_secret_key := |
|||
encode(decode(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientSecretKey', 'hex')::bytea, |
|||
'base64'); |
|||
client_public_key_or_id := encode( |
|||
decode(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientPublicKeyOrId', 'hex')::bytea, |
|||
'base64'); |
|||
client_bootstrap_server_value_object := jsonb_build_object( |
|||
'securityMode', credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode', |
|||
'clientPublicKeyOrId', client_public_key_or_id, |
|||
'clientSecretKey', client_secret_key |
|||
); |
|||
client_bootstrap_server_object := jsonb_build_object('lwm2mServer', client_bootstrap_server_value_object::jsonb); |
|||
client_bootstrap_object := credentials_value_new::jsonb #> '{bootstrap}' || client_bootstrap_server_object::jsonb; |
|||
credentials_value_new := |
|||
jsonb_set(credentials_value_new::jsonb, '{bootstrap}', client_bootstrap_object::jsonb, false)::jsonb; |
|||
END IF; |
|||
END IF; |
|||
|
|||
IF credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode' = 'RPK' OR |
|||
credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode' = 'X509' THEN |
|||
IF NULLIF( |
|||
(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientSecretKey' ~ '^[0-9a-fA-F]+$')::text, |
|||
'false') = 'true' AND |
|||
NULLIF( |
|||
(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientPublicKeyOrId' ~ '^[0-9a-fA-F]+$')::text, |
|||
'false') = 'true' THEN |
|||
client_secret_key := |
|||
encode( |
|||
decode(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientSecretKey', 'hex')::bytea, |
|||
'base64'); |
|||
client_public_key_or_id := encode( |
|||
decode(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientPublicKeyOrId', 'hex')::bytea, |
|||
'base64'); |
|||
client_bootstrap_server_value_object := jsonb_build_object( |
|||
'securityMode', credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode', |
|||
'clientPublicKeyOrId', client_public_key_or_id, |
|||
'clientSecretKey', client_secret_key |
|||
); |
|||
client_bootstrap_server_object := |
|||
jsonb_build_object('bootstrapServer', client_bootstrap_server_value_object::jsonb); |
|||
client_bootstrap_object := credentials_value_new::jsonb #> '{bootstrap}' || client_bootstrap_server_object::jsonb; |
|||
credentials_value_new := |
|||
jsonb_set(credentials_value_new::jsonb, '{bootstrap}', client_bootstrap_object::jsonb, false)::jsonb; |
|||
END IF; |
|||
END IF; |
|||
|
|||
END; |
|||
$$; |
|||
@ -1,70 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.service.lwm2m; |
|||
|
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.core.util.Hex; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; |
|||
import org.thingsboard.server.common.transport.config.ssl.SslCredentials; |
|||
import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig; |
|||
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; |
|||
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
@RequiredArgsConstructor |
|||
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") |
|||
public class LwM2MServerSecurityInfoRepository { |
|||
|
|||
private final LwM2MTransportServerConfig serverConfig; |
|||
private final LwM2MTransportBootstrapConfig bootstrapConfig; |
|||
|
|||
public ServerSecurityConfig getServerSecurityInfo(boolean bootstrapServer) { |
|||
ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig); |
|||
result.setBootstrapServerIs(bootstrapServer); |
|||
return result; |
|||
} |
|||
|
|||
private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig) { |
|||
ServerSecurityConfig bsServ = new ServerSecurityConfig(); |
|||
bsServ.setServerId(serverConfig.getId()); |
|||
bsServ.setHost(serverConfig.getHost()); |
|||
bsServ.setPort(serverConfig.getPort()); |
|||
bsServ.setSecurityHost(serverConfig.getSecureHost()); |
|||
bsServ.setSecurityPort(serverConfig.getSecurePort()); |
|||
bsServ.setServerPublicKey(getPublicKey(serverConfig)); |
|||
return bsServ; |
|||
} |
|||
|
|||
private String getPublicKey(LwM2MSecureServerConfig config) { |
|||
try { |
|||
SslCredentials sslCredentials = config.getSslCredentials(); |
|||
if (sslCredentials != null) { |
|||
return Hex.encodeHexString(sslCredentials.getPublicKey().getEncoded()); |
|||
} |
|||
} catch (Exception e) { |
|||
log.trace("Failed to fetch public key from key store!", e); |
|||
|
|||
} |
|||
return ""; |
|||
} |
|||
|
|||
} |
|||
|
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.service.lwm2m; |
|||
|
|||
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault; |
|||
|
|||
public interface LwM2MService { |
|||
|
|||
LwM2MServerSecurityConfigDefault getServerSecurityInfo(boolean bootstrapServer); |
|||
|
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.service.lwm2m; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.codec.binary.Base64; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault; |
|||
import org.thingsboard.server.common.transport.config.ssl.SslCredentials; |
|||
import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig; |
|||
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; |
|||
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
@RequiredArgsConstructor |
|||
@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true'") |
|||
public class LwM2MServiceImpl implements LwM2MService { |
|||
|
|||
private final LwM2MTransportServerConfig serverConfig; |
|||
private final Optional<LwM2MTransportBootstrapConfig> bootstrapConfig; |
|||
|
|||
@Override |
|||
public LwM2MServerSecurityConfigDefault getServerSecurityInfo(boolean bootstrapServer) { |
|||
LwM2MSecureServerConfig bsServerConfig = bootstrapServer ? bootstrapConfig.orElse(null) : serverConfig; |
|||
if (bsServerConfig!= null) { |
|||
LwM2MServerSecurityConfigDefault result = getServerSecurityConfig(bsServerConfig); |
|||
result.setBootstrapServerIs(bootstrapServer); |
|||
return result; |
|||
} |
|||
else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private LwM2MServerSecurityConfigDefault getServerSecurityConfig(LwM2MSecureServerConfig bsServerConfig) { |
|||
LwM2MServerSecurityConfigDefault bsServ = new LwM2MServerSecurityConfigDefault(); |
|||
bsServ.setShortServerId(bsServerConfig.getId()); |
|||
bsServ.setHost(bsServerConfig.getHost()); |
|||
bsServ.setPort(bsServerConfig.getPort()); |
|||
bsServ.setSecurityHost(bsServerConfig.getSecureHost()); |
|||
bsServ.setSecurityPort(bsServerConfig.getSecurePort()); |
|||
byte[] publicKeyBase64 = getPublicKey(bsServerConfig); |
|||
if (publicKeyBase64 == null) { |
|||
bsServ.setServerPublicKey(""); |
|||
} else { |
|||
bsServ.setServerPublicKey(Base64.encodeBase64String(publicKeyBase64)); |
|||
} |
|||
byte[] certificateBase64 = getCertificate(bsServerConfig); |
|||
if (certificateBase64 == null) { |
|||
bsServ.setServerCertificate(""); |
|||
} else { |
|||
bsServ.setServerCertificate(Base64.encodeBase64String(certificateBase64)); |
|||
} |
|||
return bsServ; |
|||
} |
|||
|
|||
private byte[] getPublicKey(LwM2MSecureServerConfig config) { |
|||
try { |
|||
SslCredentials sslCredentials = config.getSslCredentials(); |
|||
if (sslCredentials != null) { |
|||
return sslCredentials.getPublicKey().getEncoded(); |
|||
} |
|||
} catch (Exception e) { |
|||
log.trace("Failed to fetch public key from key store!", e); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
private byte[] getCertificate(LwM2MSecureServerConfig config) { |
|||
try { |
|||
SslCredentials sslCredentials = config.getSslCredentials(); |
|||
if (sslCredentials != null) { |
|||
return sslCredentials.getCertificateChain()[0].getEncoded(); |
|||
} |
|||
} catch (Exception e) { |
|||
log.trace("Failed to fetch certificate from key store!", e); |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,257 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.actors; |
|||
|
|||
import org.junit.jupiter.api.Test; |
|||
import org.junit.jupiter.api.extension.ExtendWith; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.context.properties.EnableConfigurationProperties; |
|||
import org.springframework.boot.test.mock.mockito.MockBean; |
|||
import org.springframework.data.redis.core.RedisTemplate; |
|||
import org.springframework.test.context.ContextConfiguration; |
|||
import org.springframework.test.context.TestPropertySource; |
|||
import org.springframework.test.context.junit.jupiter.SpringExtension; |
|||
import org.thingsboard.rule.engine.api.MailService; |
|||
import org.thingsboard.rule.engine.api.SmsService; |
|||
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; |
|||
import org.thingsboard.server.actors.service.ActorService; |
|||
import org.thingsboard.server.cluster.TbClusterService; |
|||
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; |
|||
import org.thingsboard.server.dao.asset.AssetService; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.audit.AuditLogService; |
|||
import org.thingsboard.server.dao.cassandra.CassandraCluster; |
|||
import org.thingsboard.server.dao.customer.CustomerService; |
|||
import org.thingsboard.server.dao.dashboard.DashboardService; |
|||
import org.thingsboard.server.dao.device.ClaimDevicesService; |
|||
import org.thingsboard.server.dao.device.DeviceService; |
|||
import org.thingsboard.server.dao.edge.EdgeEventService; |
|||
import org.thingsboard.server.dao.edge.EdgeService; |
|||
import org.thingsboard.server.dao.entityview.EntityViewService; |
|||
import org.thingsboard.server.dao.event.EventService; |
|||
import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; |
|||
import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; |
|||
import org.thingsboard.server.dao.ota.OtaPackageService; |
|||
import org.thingsboard.server.dao.relation.RelationService; |
|||
import org.thingsboard.server.dao.resource.ResourceService; |
|||
import org.thingsboard.server.dao.rule.RuleChainService; |
|||
import org.thingsboard.server.dao.rule.RuleNodeStateService; |
|||
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; |
|||
import org.thingsboard.server.dao.tenant.TenantProfileService; |
|||
import org.thingsboard.server.dao.tenant.TenantService; |
|||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|||
import org.thingsboard.server.dao.user.UserService; |
|||
import org.thingsboard.server.queue.discovery.PartitionService; |
|||
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
|||
import org.thingsboard.server.queue.usagestats.TbApiUsageClient; |
|||
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; |
|||
import org.thingsboard.server.service.component.ComponentDiscoveryService; |
|||
import org.thingsboard.server.service.edge.rpc.EdgeRpcService; |
|||
import org.thingsboard.server.service.executors.DbCallbackExecutorService; |
|||
import org.thingsboard.server.service.executors.ExternalCallExecutorService; |
|||
import org.thingsboard.server.service.executors.SharedEventLoopGroupService; |
|||
import org.thingsboard.server.service.mail.MailExecutorService; |
|||
import org.thingsboard.server.service.profile.TbDeviceProfileCache; |
|||
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; |
|||
import org.thingsboard.server.service.rpc.TbRpcService; |
|||
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; |
|||
import org.thingsboard.server.service.script.JsInvokeService; |
|||
import org.thingsboard.server.service.session.DeviceSessionCacheService; |
|||
import org.thingsboard.server.service.sms.SmsExecutorService; |
|||
import org.thingsboard.server.service.state.DeviceStateService; |
|||
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; |
|||
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; |
|||
import org.thingsboard.server.service.transport.TbCoreToTransportService; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
|
|||
@ExtendWith(SpringExtension.class) |
|||
@ContextConfiguration(classes = ActorSystemContext.class) |
|||
@EnableConfigurationProperties |
|||
@TestPropertySource(properties = { |
|||
"cache.type=caffeine", |
|||
}) |
|||
public class ActorSystemContextTest { |
|||
|
|||
@Autowired |
|||
ActorSystemContext ctx; |
|||
|
|||
@MockBean |
|||
private TbApiUsageStateService apiUsageStateService; |
|||
|
|||
@MockBean |
|||
private TbApiUsageClient apiUsageClient; |
|||
|
|||
@MockBean |
|||
private TbServiceInfoProvider serviceInfoProvider; |
|||
|
|||
@MockBean |
|||
private ActorService actorService; |
|||
|
|||
@MockBean |
|||
private ComponentDiscoveryService componentService; |
|||
|
|||
@MockBean |
|||
private DataDecodingEncodingService encodingService; |
|||
|
|||
@MockBean |
|||
private DeviceService deviceService; |
|||
|
|||
@MockBean |
|||
private TbTenantProfileCache tenantProfileCache; |
|||
|
|||
@MockBean |
|||
private TbDeviceProfileCache deviceProfileCache; |
|||
|
|||
@MockBean |
|||
private AssetService assetService; |
|||
|
|||
@MockBean |
|||
private DashboardService dashboardService; |
|||
|
|||
@MockBean |
|||
private TenantService tenantService; |
|||
|
|||
@MockBean |
|||
private TenantProfileService tenantProfileService; |
|||
|
|||
@MockBean |
|||
private CustomerService customerService; |
|||
|
|||
@MockBean |
|||
private UserService userService; |
|||
|
|||
@MockBean |
|||
private RuleChainService ruleChainService; |
|||
|
|||
@MockBean |
|||
private RuleNodeStateService ruleNodeStateService; |
|||
|
|||
@MockBean |
|||
private PartitionService partitionService; |
|||
|
|||
@MockBean |
|||
private TbClusterService clusterService; |
|||
|
|||
@MockBean |
|||
private TimeseriesService tsService; |
|||
|
|||
@MockBean |
|||
private AttributesService attributesService; |
|||
|
|||
@MockBean |
|||
private EventService eventService; |
|||
|
|||
@MockBean |
|||
private RelationService relationService; |
|||
|
|||
@MockBean |
|||
private AuditLogService auditLogService; |
|||
|
|||
@MockBean |
|||
private EntityViewService entityViewService; |
|||
|
|||
@MockBean |
|||
private TelemetrySubscriptionService tsSubService; |
|||
|
|||
@MockBean |
|||
private AlarmSubscriptionService alarmService; |
|||
|
|||
@MockBean |
|||
private JsInvokeService jsSandbox; |
|||
|
|||
@MockBean |
|||
private MailExecutorService mailExecutor; |
|||
|
|||
@MockBean |
|||
private SmsExecutorService smsExecutor; |
|||
|
|||
@MockBean |
|||
private DbCallbackExecutorService dbCallbackExecutor; |
|||
|
|||
@MockBean |
|||
private ExternalCallExecutorService externalCallExecutorService; |
|||
|
|||
@MockBean |
|||
private SharedEventLoopGroupService sharedEventLoopGroupService; |
|||
|
|||
@MockBean |
|||
private MailService mailService; |
|||
|
|||
@MockBean |
|||
private SmsService smsService; |
|||
|
|||
@MockBean |
|||
private SmsSenderFactory smsSenderFactory; |
|||
|
|||
@MockBean |
|||
private ClaimDevicesService claimDevicesService; |
|||
|
|||
@MockBean |
|||
private JsInvokeStats jsInvokeStats; |
|||
|
|||
@MockBean |
|||
private DeviceStateService deviceStateService; |
|||
|
|||
@MockBean |
|||
private DeviceSessionCacheService deviceSessionCacheService; |
|||
|
|||
@MockBean |
|||
private TbCoreToTransportService tbCoreToTransportService; |
|||
|
|||
@MockBean |
|||
private TbRuleEngineDeviceRpcService tbRuleEngineDeviceRpcService; |
|||
|
|||
@MockBean |
|||
private TbCoreDeviceRpcService tbCoreDeviceRpcService; |
|||
|
|||
@MockBean |
|||
private EdgeService edgeService; |
|||
|
|||
@MockBean |
|||
private EdgeEventService edgeEventService; |
|||
|
|||
@MockBean |
|||
private EdgeRpcService edgeRpcService; |
|||
|
|||
@MockBean |
|||
private ResourceService resourceService; |
|||
|
|||
@MockBean |
|||
private OtaPackageService otaPackageService; |
|||
|
|||
@MockBean |
|||
private TbRpcService tbRpcService; |
|||
|
|||
@MockBean |
|||
private CassandraCluster cassandraCluster; |
|||
|
|||
@MockBean |
|||
private CassandraBufferedRateReadExecutor cassandraBufferedRateReadExecutor; |
|||
|
|||
@MockBean |
|||
private CassandraBufferedRateWriteExecutor cassandraBufferedRateWriteExecutor; |
|||
|
|||
@MockBean |
|||
private RedisTemplate<String, Object> redisTemplate; |
|||
|
|||
@Test |
|||
void givenCaffeineCache_whenInit_thenIsLocalCacheTrue() { |
|||
assertThat(ctx.getCacheType()).isEqualTo("caffeine"); |
|||
assertThat(ctx.isLocalCacheType()).as("caffeine is the local cache type").isTrue(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.controller; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.After; |
|||
import org.junit.Before; |
|||
import org.thingsboard.server.queue.memory.InMemoryStorage; |
|||
|
|||
@Slf4j |
|||
public abstract class AbstractInMemoryStorageTest { |
|||
|
|||
@Before |
|||
public void setUpInMemoryStorage() { |
|||
log.info("set up InMemoryStorage"); |
|||
cleanupInMemStorage(); |
|||
} |
|||
|
|||
@After |
|||
public void tearDownInMemoryStorage() { |
|||
log.info("tear down InMemoryStorage"); |
|||
cleanupInMemStorage(); |
|||
} |
|||
|
|||
public static void cleanupInMemStorage() { |
|||
InMemoryStorage.getInstance().cleanup(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,196 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.controller; |
|||
|
|||
import com.fasterxml.jackson.core.type.TypeReference; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.junit.After; |
|||
import org.junit.Assert; |
|||
import org.junit.Before; |
|||
import org.junit.Test; |
|||
import org.springframework.test.web.servlet.MvcResult; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.*; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.rpc.Rpc; |
|||
import org.thingsboard.server.common.data.rpc.RpcStatus; |
|||
import org.thingsboard.server.common.data.security.Authority; |
|||
|
|||
import java.util.List; |
|||
|
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
public abstract class BaseRpcControllerTest extends AbstractControllerTest { |
|||
|
|||
private Tenant savedTenant; |
|||
private User tenantAdmin; |
|||
|
|||
@Before |
|||
public void beforeTest() throws Exception { |
|||
loginSysAdmin(); |
|||
|
|||
Tenant tenant = new Tenant(); |
|||
tenant.setTitle("My tenant"); |
|||
savedTenant = doPost("/api/tenant", tenant, Tenant.class); |
|||
Assert.assertNotNull(savedTenant); |
|||
|
|||
tenantAdmin = new User(); |
|||
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); |
|||
tenantAdmin.setTenantId(savedTenant.getId()); |
|||
tenantAdmin.setEmail("tenant2@thingsboard.org"); |
|||
tenantAdmin.setFirstName("Joe"); |
|||
tenantAdmin.setLastName("Downs"); |
|||
|
|||
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); |
|||
} |
|||
|
|||
@After |
|||
public void afterTest() throws Exception { |
|||
loginSysAdmin(); |
|||
|
|||
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) |
|||
.andExpect(status().isOk()); |
|||
} |
|||
|
|||
private Device createDefaultDevice() { |
|||
Device device = new Device(); |
|||
device.setName("My device"); |
|||
device.setType("default"); |
|||
|
|||
return device; |
|||
} |
|||
|
|||
private ObjectNode createDefaultRpc() { |
|||
ObjectNode rpc = JacksonUtil.newObjectNode(); |
|||
rpc.put("method", "setGpio"); |
|||
|
|||
ObjectNode params = JacksonUtil.newObjectNode(); |
|||
|
|||
params.put("pin", 7); |
|||
params.put("value", 1); |
|||
|
|||
rpc.set("params", params); |
|||
rpc.put("persistent", true); |
|||
rpc.put("timeout", 5000); |
|||
|
|||
return rpc; |
|||
} |
|||
|
|||
private Rpc getRpcById(String rpcId) throws Exception { |
|||
return doGet("/api/rpc/persistent/" + rpcId, Rpc.class); |
|||
} |
|||
|
|||
private MvcResult removeRpcById(String rpcId) throws Exception { |
|||
return doDelete("/api/rpc/persistent/" + rpcId).andReturn(); |
|||
} |
|||
|
|||
@Test |
|||
public void testSaveRpc() throws Exception { |
|||
Device device = createDefaultDevice(); |
|||
Device savedDevice = doPost("/api/device", device, Device.class); |
|||
|
|||
ObjectNode rpc = createDefaultRpc(); |
|||
String result = doPostAsync( |
|||
"/api/rpc/oneway/" + savedDevice.getId().getId().toString(), |
|||
JacksonUtil.toString(rpc), |
|||
String.class, |
|||
status().isOk() |
|||
); |
|||
String rpcId = JacksonUtil.fromString(result, JsonNode.class) |
|||
.get("rpcId") |
|||
.asText(); |
|||
Rpc savedRpc = getRpcById(rpcId); |
|||
|
|||
Assert.assertNotNull(savedRpc); |
|||
Assert.assertEquals(savedDevice.getId(), savedRpc.getDeviceId()); |
|||
} |
|||
|
|||
@Test |
|||
public void testDeleteRpc() throws Exception { |
|||
Device device = createDefaultDevice(); |
|||
Device savedDevice = doPost("/api/device", device, Device.class); |
|||
|
|||
ObjectNode rpc = createDefaultRpc(); |
|||
String result = doPostAsync( |
|||
"/api/rpc/oneway/" + savedDevice.getId().getId().toString(), |
|||
JacksonUtil.toString(rpc), |
|||
String.class, |
|||
status().isOk() |
|||
); |
|||
String rpcId = JacksonUtil.fromString(result, JsonNode.class) |
|||
.get("rpcId") |
|||
.asText(); |
|||
Rpc savedRpc = getRpcById(rpcId); |
|||
|
|||
MvcResult mvcResult = removeRpcById(savedRpc.getId().getId().toString()); |
|||
MvcResult res = doGet("/api/rpc/persistent/" + rpcId) |
|||
.andExpect(status().isNotFound()) |
|||
.andReturn(); |
|||
|
|||
JsonNode deleteResponse = JacksonUtil.fromString(res.getResponse().getContentAsString(), JsonNode.class); |
|||
Assert.assertEquals(404, deleteResponse.get("status").asInt()); |
|||
|
|||
String url = "/api/rpc/persistent/device/" + savedDevice.getUuidId().toString() |
|||
+ "?" + "page=0" + "&" + |
|||
"pageSize=" + Integer.MAX_VALUE + "&" + |
|||
"rpcStatus=" + RpcStatus.DELETED.name(); |
|||
MvcResult byDeviceResult = doGet(url).andReturn(); |
|||
JsonNode byDeviceResponse = JacksonUtil.fromString(byDeviceResult.getResponse().getContentAsString(), JsonNode.class); |
|||
|
|||
Assert.assertEquals(500, byDeviceResponse.get("status").asInt()); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetRpcsByDeviceId() throws Exception { |
|||
Device device = createDefaultDevice(); |
|||
Device savedDevice = doPost("/api/device", device, Device.class); |
|||
|
|||
ObjectNode rpc = createDefaultRpc(); |
|||
|
|||
String result = doPostAsync( |
|||
"/api/rpc/oneway/" + savedDevice.getId().getId().toString(), |
|||
JacksonUtil.toString(rpc), |
|||
String.class, |
|||
status().isOk() |
|||
); |
|||
String rpcId = JacksonUtil.fromString(result, JsonNode.class) |
|||
.get("rpcId") |
|||
.asText(); |
|||
|
|||
String url = "/api/rpc/persistent/device/" + savedDevice.getId().getId() |
|||
+ "?" + "page=0" + "&" + |
|||
"pageSize=" + Integer.MAX_VALUE + "&" + |
|||
"rpcStatus=" + RpcStatus.QUEUED; |
|||
|
|||
MvcResult byDeviceResult = doGetAsync(url).andReturn(); |
|||
|
|||
List<Rpc> byDeviceRpcs = JacksonUtil.fromString( |
|||
byDeviceResult |
|||
.getResponse() |
|||
.getContentAsString(), |
|||
new TypeReference<PageData<Rpc>>() {} |
|||
).getData(); |
|||
|
|||
|
|||
boolean found = byDeviceRpcs.stream().anyMatch(r -> |
|||
r.getUuidId().toString().equals(rpcId) |
|||
&& r.getDeviceId().equals(savedDevice.getId()) |
|||
); |
|||
|
|||
Assert.assertTrue(found); |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.controller.sql; |
|||
|
|||
import org.thingsboard.server.controller.BaseRpcControllerTest; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
@DaoSqlTest |
|||
public class RpcControllerTest extends BaseRpcControllerTest { |
|||
} |
|||
@ -0,0 +1,207 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.service.sql; |
|||
|
|||
import com.google.gson.JsonObject; |
|||
import com.google.gson.JsonParser; |
|||
import org.jetbrains.annotations.NotNull; |
|||
import org.junit.After; |
|||
import org.junit.Assert; |
|||
import org.junit.Before; |
|||
import org.junit.Test; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode; |
|||
import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNodeConfiguration; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.Tenant; |
|||
import org.thingsboard.server.common.data.User; |
|||
import org.thingsboard.server.common.data.asset.Asset; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
|||
import org.thingsboard.server.common.data.kv.JsonDataEntry; |
|||
import org.thingsboard.server.common.data.kv.LongDataEntry; |
|||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|||
import org.thingsboard.server.common.data.security.Authority; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.TbMsgDataType; |
|||
import org.thingsboard.server.common.msg.TbMsgMetaData; |
|||
import org.thingsboard.server.common.msg.session.SessionMsgType; |
|||
import org.thingsboard.server.controller.AbstractControllerTest; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
import java.util.concurrent.ExecutionException; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.concurrent.TimeoutException; |
|||
|
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
@DaoSqlTest |
|||
public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest { |
|||
|
|||
static final int TIMEOUT = 30; |
|||
|
|||
final String TOTALIZER = "Totalizer"; |
|||
final int TTL = 99999; |
|||
final String GENERIC_CUMULATIVE_OBJ = "genericCumulativeObj"; |
|||
final List<Long> ts = List.of(10L, 20L, 30L, 40L, 60L, 70L, 50L, 80L); |
|||
final List<Long> msgValue = List.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L); |
|||
|
|||
@Autowired |
|||
TimeseriesService timeseriesService; |
|||
|
|||
TbMsgTimeseriesNodeConfiguration configuration; |
|||
Tenant savedTenant; |
|||
User tenantAdmin; |
|||
|
|||
@Before |
|||
public void beforeTest() throws Exception { |
|||
configuration = new TbMsgTimeseriesNodeConfiguration(); |
|||
configuration.setUseServerTs(true); |
|||
|
|||
loginSysAdmin(); |
|||
|
|||
Tenant tenant = new Tenant(); |
|||
tenant.setTitle("My tenant"); |
|||
savedTenant = doPost("/api/tenant", tenant, Tenant.class); |
|||
Assert.assertNotNull(savedTenant); |
|||
|
|||
tenantAdmin = new User(); |
|||
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); |
|||
tenantAdmin.setTenantId(savedTenant.getId()); |
|||
tenantAdmin.setEmail("tenant2@thingsboard.org"); |
|||
tenantAdmin.setFirstName("Joe"); |
|||
tenantAdmin.setLastName("Downs"); |
|||
|
|||
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); |
|||
} |
|||
|
|||
@After |
|||
public void afterTest() throws Exception { |
|||
loginSysAdmin(); |
|||
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk()); |
|||
} |
|||
|
|||
@Test |
|||
public void testSequentialTimeseriesPersistence() throws Exception { |
|||
Asset asset = saveAsset("Asset"); |
|||
|
|||
Device deviceA = saveDevice("Device A"); |
|||
Device deviceB = saveDevice("Device B"); |
|||
Device deviceC = saveDevice("Device C"); |
|||
Device deviceD = saveDevice("Device D"); |
|||
List<Device> devices = List.of(deviceA, deviceB, deviceC, deviceD); |
|||
|
|||
for (int i = 0; i < 2; i++) { |
|||
int idx = i * devices.size(); |
|||
saveLatestTsForAssetAndDevice(devices, asset, idx); |
|||
checkDiffBetweenLatestTsForDevicesAndAsset(devices, asset); |
|||
} |
|||
} |
|||
|
|||
Device saveDevice(String name) throws Exception { |
|||
Device device = new Device(); |
|||
device.setName(name); |
|||
device.setType("default"); |
|||
Device savedDevice = doPost("/api/device", device, Device.class); |
|||
Assert.assertNotNull(savedDevice); |
|||
return savedDevice; |
|||
} |
|||
|
|||
Asset saveAsset(String name) throws Exception { |
|||
Asset asset = new Asset(); |
|||
asset.setName(name); |
|||
asset.setType("default"); |
|||
Asset savedAsset = doPost("/api/asset", asset, Asset.class); |
|||
Assert.assertNotNull(savedAsset); |
|||
return savedAsset; |
|||
} |
|||
|
|||
void saveLatestTsForAssetAndDevice(List<Device> devices, Asset asset, int idx) throws ExecutionException, InterruptedException, TimeoutException { |
|||
for (Device device : devices) { |
|||
TbMsg tbMsg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), |
|||
device.getId(), |
|||
getTbMsgMetadata(device.getName(), ts.get(idx)), |
|||
TbMsgDataType.JSON, |
|||
getTbMsgData(msgValue.get(idx))); |
|||
saveDeviceTsEntry(device.getId(), tbMsg, msgValue.get(idx)); |
|||
saveAssetTsEntry(asset, device.getName(), msgValue.get(idx), TbMsgTimeseriesNode.computeTs(tbMsg, configuration.isUseServerTs())); |
|||
idx++; |
|||
} |
|||
} |
|||
|
|||
void checkDiffBetweenLatestTsForDevicesAndAsset(List<Device> devices, Asset asset) throws ExecutionException, InterruptedException, TimeoutException { |
|||
TsKvEntry assetTsKvEntry = getTsKvLatest(asset.getId(), GENERIC_CUMULATIVE_OBJ); |
|||
Assert.assertTrue(assetTsKvEntry.getJsonValue().isPresent()); |
|||
JsonObject assetJsonObject = new JsonParser().parse(assetTsKvEntry.getJsonValue().get()).getAsJsonObject(); |
|||
for (Device device : devices) { |
|||
Long assetValue = assetJsonObject.get(device.getName()).getAsLong(); |
|||
TsKvEntry deviceLatest = getTsKvLatest(device.getId(), TOTALIZER); |
|||
Assert.assertTrue(deviceLatest.getLongValue().isPresent()); |
|||
Long deviceValue = deviceLatest.getLongValue().get(); |
|||
Assert.assertEquals(assetValue, deviceValue); |
|||
} |
|||
} |
|||
|
|||
String getTbMsgData(long value) { |
|||
return "{\"Totalizer\": " + value + "}"; |
|||
} |
|||
|
|||
TbMsgMetaData getTbMsgMetadata(String name, long ts) { |
|||
Map<String, String> metadata = new HashMap<>(); |
|||
metadata.put("deviceName", name); |
|||
metadata.put("ts", String.valueOf(ts)); |
|||
return new TbMsgMetaData(metadata); |
|||
} |
|||
|
|||
void saveDeviceTsEntry(EntityId entityId, TbMsg tbMsg, long value) throws ExecutionException, InterruptedException, TimeoutException { |
|||
TsKvEntry tsKvEntry = new BasicTsKvEntry(TbMsgTimeseriesNode.computeTs(tbMsg, configuration.isUseServerTs()), new LongDataEntry(TOTALIZER, value)); |
|||
saveTimeseries(entityId, tsKvEntry); |
|||
} |
|||
|
|||
void saveAssetTsEntry(Asset asset, String key, long value, long ts) throws ExecutionException, InterruptedException, TimeoutException { |
|||
Optional<String> tsKvEntryOpt = getTsKvLatest(asset.getId(), GENERIC_CUMULATIVE_OBJ).getJsonValue(); |
|||
TsKvEntry saveTsKvEntry = new BasicTsKvEntry(ts, new JsonDataEntry(GENERIC_CUMULATIVE_OBJ, getJsonObject(key, value, tsKvEntryOpt).toString())); |
|||
saveTimeseries(asset.getId(), saveTsKvEntry); |
|||
} |
|||
|
|||
@NotNull |
|||
JsonObject getJsonObject(String key, long value, Optional<String> tsKvEntryOpt) { |
|||
JsonObject jsonObject = new JsonObject(); |
|||
if (tsKvEntryOpt.isPresent()) { |
|||
jsonObject = new JsonParser().parse(tsKvEntryOpt.get()).getAsJsonObject(); |
|||
} |
|||
jsonObject.addProperty(key, value); |
|||
return jsonObject; |
|||
} |
|||
|
|||
void saveTimeseries(EntityId entityId, TsKvEntry saveTsKvEntry) throws InterruptedException, ExecutionException, TimeoutException { |
|||
timeseriesService.save(savedTenant.getId(), entityId, List.of(saveTsKvEntry), TTL).get(TIMEOUT, TimeUnit.SECONDS); |
|||
} |
|||
|
|||
TsKvEntry getTsKvLatest(EntityId entityId, String key) throws InterruptedException, ExecutionException, TimeoutException { |
|||
List<TsKvEntry> tsKvEntries = timeseriesService.findLatest( |
|||
savedTenant.getTenantId(), |
|||
entityId, |
|||
List.of(key)).get(TIMEOUT, TimeUnit.SECONDS); |
|||
Assert.assertEquals(1, tsKvEntries.size()); |
|||
return tsKvEntries.get(0); |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m; |
|||
|
|||
import org.eclipse.californium.core.network.config.NetworkConfig; |
|||
import org.eclipse.leshan.client.object.Security; |
|||
|
|||
import static org.eclipse.leshan.client.object.Security.noSec; |
|||
|
|||
public class Lwm2mTestHelper { |
|||
|
|||
// Server
|
|||
public static final int SECURE_PORT = 5686; |
|||
public static final int SECURE_PORT_BS = 5688; |
|||
public static final int PORT = 5685; |
|||
public static final int PORT_BS = 5687; |
|||
public static final String HOST = "localhost"; |
|||
public static final String HOST_BS = "localhost"; |
|||
public static final int SHORT_SERVER_ID = 123; |
|||
public static final int SHORT_SERVER_ID_BS = 111; |
|||
|
|||
public static final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT)); |
|||
public static final String SECURE_URI = "coaps://" + HOST + ":" + SECURE_PORT; |
|||
public static final Security SECURITY = noSec("coap://"+ HOST +":" + PORT, SHORT_SERVER_ID); |
|||
public static final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT)); |
|||
|
|||
// Models
|
|||
public static final String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml"}; |
|||
public static final int BINARY_APP_DATA_CONTAINER = 19; |
|||
public static final int TEMPERATURE_SENSOR = 3303; |
|||
|
|||
// Ids in Client
|
|||
public static final int OBJECT_ID_0 = 0; |
|||
public static final int OBJECT_INSTANCE_ID_0 = 0; |
|||
public static final int OBJECT_INSTANCE_ID_1 = 1; |
|||
public static final int OBJECT_INSTANCE_ID_2 = 2; |
|||
public static final int OBJECT_INSTANCE_ID_12 = 12; |
|||
public static final int RESOURCE_ID_0 = 0; |
|||
public static final int RESOURCE_ID_1 = 1; |
|||
public static final int RESOURCE_ID_2 = 2; |
|||
public static final int RESOURCE_ID_3 = 3; |
|||
public static final int RESOURCE_ID_4 = 4; |
|||
public static final int RESOURCE_ID_7 = 7; |
|||
public static final int RESOURCE_ID_8 = 8; |
|||
public static final int RESOURCE_ID_9 = 9; |
|||
public static final int RESOURCE_ID_11 = 11; |
|||
public static final int RESOURCE_ID_14 = 14; |
|||
public static final int RESOURCE_ID_15 = 15; |
|||
public static final int RESOURCE_INSTANCE_ID_2 = 2; |
|||
|
|||
public static final String RESOURCE_ID_NAME_3_9 = "batteryLevel"; |
|||
public static final String RESOURCE_ID_NAME_3_14 = "UtfOffset"; |
|||
public static final String RESOURCE_ID_NAME_19_0_0 = "dataRead"; |
|||
public static final String RESOURCE_ID_NAME_19_1_0 = "dataWrite"; |
|||
} |
|||
@ -0,0 +1,222 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.client; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.client.resource.BaseInstanceEnabler; |
|||
import org.eclipse.leshan.client.servers.ServerIdentity; |
|||
import org.eclipse.leshan.core.model.ObjectModel; |
|||
import org.eclipse.leshan.core.model.ResourceModel; |
|||
import org.eclipse.leshan.core.node.LwM2mMultipleResource; |
|||
import org.eclipse.leshan.core.node.LwM2mResource; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
import org.eclipse.leshan.core.response.WriteResponse; |
|||
|
|||
import javax.security.auth.Destroyable; |
|||
import java.sql.Time; |
|||
import java.util.Arrays; |
|||
import java.util.Date; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
|
|||
@Slf4j |
|||
public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements Destroyable { |
|||
|
|||
/** |
|||
* id = 0 |
|||
* Multiple |
|||
* base64 |
|||
*/ |
|||
|
|||
/** |
|||
* Example1: |
|||
* InNlcnZpY2VJZCI6Ik1ldGVyIiwNCiJzZXJ2aWNlRGF0YSI6ew0KImN1cnJlbnRSZWFka |
|||
* W5nIjoiNDYuMyIsDQoic2lnbmFsU3RyZW5ndGgiOjE2LA0KImRhaWx5QWN0aXZpdHlUaW1lIjo1NzA2DQo= |
|||
* "serviceId":"Meter", |
|||
* "serviceData":{ |
|||
* "currentReading":"46.3", |
|||
* "signalStrength":16, |
|||
* "dailyActivityTime":5706 |
|||
*/ |
|||
|
|||
/** |
|||
* Example2: |
|||
* InNlcnZpY2VJZCI6IldhdGVyTWV0ZXIiLA0KImNtZCI6IlNFVF9URU1QRVJBVFVSRV9SRUFEX |
|||
* 1BFUklPRCIsDQoicGFyYXMiOnsNCiJ2YWx1ZSI6NA0KICAgIH0sDQoNCg0K |
|||
* "serviceId":"WaterMeter", |
|||
* "cmd":"SET_TEMPERATURE_READ_PERIOD", |
|||
* "paras":{ |
|||
* "value":4 |
|||
* }, |
|||
*/ |
|||
|
|||
Map<Integer, byte[]> data; |
|||
private Integer priority = 0; |
|||
private Time timestamp; |
|||
private String description; |
|||
private String dataFormat; |
|||
private Integer appID = -1; |
|||
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 4, 5); |
|||
|
|||
public LwM2mBinaryAppDataContainer() { |
|||
} |
|||
|
|||
public LwM2mBinaryAppDataContainer(ScheduledExecutorService executorService, Integer id) { |
|||
try { |
|||
if (id != null) this.setId(id); |
|||
executorService.scheduleWithFixedDelay(() -> |
|||
fireResourcesChange(0, 2), 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
|
|||
} catch (Throwable e) { |
|||
log.error("[{}]Throwable", e.toString()); |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public ReadResponse read(ServerIdentity identity, int resourceId) { |
|||
try { |
|||
switch (resourceId) { |
|||
case 0: |
|||
ReadResponse response = ReadResponse.success(resourceId, getData(), ResourceModel.Type.OPAQUE); |
|||
return response; |
|||
case 1: |
|||
return ReadResponse.success(resourceId, getPriority()); |
|||
case 2: |
|||
return ReadResponse.success(resourceId, getTimestamp()); |
|||
case 3: |
|||
return ReadResponse.success(resourceId, getDescription()); |
|||
case 4: |
|||
return ReadResponse.success(resourceId, getDataFormat()); |
|||
case 5: |
|||
return ReadResponse.success(resourceId, getAppID()); |
|||
default: |
|||
return super.read(identity, resourceId); |
|||
} |
|||
} catch (Exception e) { |
|||
return ReadResponse.badRequest(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { |
|||
log.info("Write on Device resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); |
|||
switch (resourceId) { |
|||
case 0: |
|||
if (setData(value, replace)) { |
|||
return WriteResponse.success(); |
|||
} else { |
|||
WriteResponse.badRequest("Invalidate value ..."); |
|||
} |
|||
case 1: |
|||
setPriority((Integer) (value.getValue() instanceof Long ? ((Long) value.getValue()).intValue() : value.getValue())); |
|||
fireResourcesChange(resourceId); |
|||
return WriteResponse.success(); |
|||
case 2: |
|||
setTimestamp(((Date) value.getValue()).getTime()); |
|||
fireResourcesChange(resourceId); |
|||
return WriteResponse.success(); |
|||
case 3: |
|||
setDescription((String) value.getValue()); |
|||
fireResourcesChange(resourceId); |
|||
return WriteResponse.success(); |
|||
case 4: |
|||
setDataFormat((String) value.getValue()); |
|||
fireResourcesChange(resourceId); |
|||
return WriteResponse.success(); |
|||
case 5: |
|||
setAppID((Integer) value.getValue()); |
|||
fireResourcesChange(resourceId); |
|||
return WriteResponse.success(); |
|||
default: |
|||
return super.write(identity, replace, resourceId, value); |
|||
} |
|||
} |
|||
|
|||
private Integer getAppID() { |
|||
return this.appID; |
|||
} |
|||
|
|||
private void setAppID(Integer appId) { |
|||
this.appID = appId; |
|||
} |
|||
|
|||
private void setDataFormat(String value) { |
|||
this.dataFormat = value; |
|||
} |
|||
|
|||
private String getDataFormat() { |
|||
return this.dataFormat == null ? "OPAQUE" : this.dataFormat; |
|||
} |
|||
|
|||
private void setDescription(String value) { |
|||
this.description = value; |
|||
} |
|||
|
|||
private String getDescription() { |
|||
return this.description == null ? "meter reading" : this.description; |
|||
} |
|||
|
|||
private void setTimestamp(long time) { |
|||
this.timestamp = new Time(time); |
|||
} |
|||
|
|||
private Time getTimestamp() { |
|||
return this.timestamp != null ? this.timestamp : new Time(new Date().getTime()); |
|||
} |
|||
|
|||
private boolean setData(LwM2mResource value, boolean replace) { |
|||
try { |
|||
if (value instanceof LwM2mMultipleResource) { |
|||
if (replace || this.data == null) { |
|||
this.data = new HashMap<>(); |
|||
} |
|||
value.getInstances().values().forEach(v -> { |
|||
this.data.put(v.getId(), (byte[]) v.getValue()); |
|||
}); |
|||
return true; |
|||
} else { |
|||
return false; |
|||
} |
|||
} catch (Exception e) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
private Map<Integer, byte[]> getData() { |
|||
return data; |
|||
} |
|||
|
|||
@Override |
|||
public List<Integer> getAvailableResourceIds(ObjectModel model) { |
|||
return supportedResources; |
|||
} |
|||
|
|||
@Override |
|||
public void destroy() { |
|||
} |
|||
|
|||
private int getPriority() { |
|||
return this.priority; |
|||
} |
|||
|
|||
private void setPriority(int value) { |
|||
this.priority = value; |
|||
} |
|||
} |
|||
@ -0,0 +1,146 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.client; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.client.resource.BaseInstanceEnabler; |
|||
import org.eclipse.leshan.client.servers.ServerIdentity; |
|||
import org.eclipse.leshan.core.model.ObjectModel; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
|
|||
import javax.security.auth.Destroyable; |
|||
import java.util.Arrays; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
import java.util.Random; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Slf4j |
|||
public class LwM2mLocation extends BaseInstanceEnabler implements Destroyable { |
|||
|
|||
private float latitude; |
|||
private float longitude; |
|||
private float scaleFactor; |
|||
private Date timestamp; |
|||
protected static final Random RANDOM = new Random(); |
|||
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 5); |
|||
|
|||
public LwM2mLocation() { |
|||
this(null, null, 1.0f); |
|||
} |
|||
|
|||
public LwM2mLocation(Float latitude, Float longitude, float scaleFactor) { |
|||
|
|||
if (latitude != null) { |
|||
this.latitude = latitude + 90f; |
|||
} else { |
|||
this.latitude = RANDOM.nextInt(180); |
|||
} |
|||
if (longitude != null) { |
|||
this.longitude = longitude + 180f; |
|||
} else { |
|||
this.longitude = RANDOM.nextInt(360); |
|||
} |
|||
this.scaleFactor = scaleFactor; |
|||
timestamp = new Date(); |
|||
} |
|||
|
|||
public LwM2mLocation(Float latitude, Float longitude, float scaleFactor, ScheduledExecutorService executorService, Integer id) { |
|||
try { |
|||
if (id != null) this.setId(id); |
|||
if (latitude != null) { |
|||
this.latitude = latitude + 90f; |
|||
} else { |
|||
this.latitude = RANDOM.nextInt(180); |
|||
} |
|||
if (longitude != null) { |
|||
this.longitude = longitude + 180f; |
|||
} else { |
|||
this.longitude = RANDOM.nextInt(360); |
|||
} |
|||
this.scaleFactor = scaleFactor; |
|||
timestamp = new Date(); |
|||
executorService.scheduleWithFixedDelay(() -> |
|||
fireResourcesChange(0, 1), 10000, 10000, TimeUnit.MILLISECONDS); |
|||
} catch (Throwable e) { |
|||
log.error("[{}]Throwable", e.toString()); |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public ReadResponse read(ServerIdentity identity, int resourceId) { |
|||
log.info("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); |
|||
switch (resourceId) { |
|||
case 0: |
|||
return ReadResponse.success(resourceId, getLatitude()); |
|||
case 1: |
|||
return ReadResponse.success(resourceId, getLongitude()); |
|||
case 5: |
|||
return ReadResponse.success(resourceId, getTimestamp()); |
|||
default: |
|||
return super.read(identity, resourceId); |
|||
} |
|||
} |
|||
|
|||
public void moveLocation(String nextMove) { |
|||
switch (nextMove.charAt(0)) { |
|||
case 'w': |
|||
moveLatitude(1.0f); |
|||
break; |
|||
case 'a': |
|||
moveLongitude(-1.0f); |
|||
break; |
|||
case 's': |
|||
moveLatitude(-1.0f); |
|||
break; |
|||
case 'd': |
|||
moveLongitude(1.0f); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
private void moveLatitude(float delta) { |
|||
latitude = latitude + delta * scaleFactor; |
|||
timestamp = new Date(); |
|||
fireResourcesChange(0, 5); |
|||
} |
|||
|
|||
private void moveLongitude(float delta) { |
|||
longitude = longitude + delta * scaleFactor; |
|||
timestamp = new Date(); |
|||
fireResourcesChange(1, 5); |
|||
} |
|||
|
|||
public float getLatitude() { |
|||
return latitude - 90.0f; |
|||
} |
|||
|
|||
public float getLongitude() { |
|||
return longitude - 180.f; |
|||
} |
|||
|
|||
public Date getTimestamp() { |
|||
return timestamp; |
|||
} |
|||
|
|||
@Override |
|||
public List<Integer> getAvailableResourceIds(ObjectModel model) { |
|||
return supportedResources; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,131 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.client; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.client.resource.BaseInstanceEnabler; |
|||
import org.eclipse.leshan.client.servers.ServerIdentity; |
|||
import org.eclipse.leshan.core.model.ObjectModel; |
|||
import org.eclipse.leshan.core.response.ExecuteResponse; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
|
|||
import javax.security.auth.Destroyable; |
|||
import java.math.BigDecimal; |
|||
import java.math.RoundingMode; |
|||
import java.util.Arrays; |
|||
import java.util.List; |
|||
import java.util.Random; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Slf4j |
|||
public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destroyable { |
|||
|
|||
private static final String UNIT_CELSIUS = "cel"; |
|||
private double currentTemp = 20d; |
|||
private double minMeasuredValue = currentTemp; |
|||
private double maxMeasuredValue = currentTemp; |
|||
protected static final Random RANDOM = new Random(); |
|||
private static final List<Integer> supportedResources = Arrays.asList(5601, 5602, 5700, 5701); |
|||
|
|||
public LwM2mTemperatureSensor() { |
|||
|
|||
} |
|||
|
|||
public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) { |
|||
try { |
|||
if (id != null) this.setId(id); |
|||
executorService.scheduleWithFixedDelay(this::adjustTemperature, 2000, 2000, TimeUnit.MILLISECONDS); |
|||
} catch (Throwable e) { |
|||
log.error("[{}]Throwable", e.toString()); |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public synchronized ReadResponse read(ServerIdentity identity, int resourceId) { |
|||
log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); |
|||
switch (resourceId) { |
|||
case 5601: |
|||
return ReadResponse.success(resourceId, getTwoDigitValue(minMeasuredValue)); |
|||
case 5602: |
|||
return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue)); |
|||
case 5700: |
|||
return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); |
|||
case 5701: |
|||
return ReadResponse.success(resourceId, UNIT_CELSIUS); |
|||
default: |
|||
return super.read(identity, resourceId); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public synchronized ExecuteResponse execute(ServerIdentity identity, int resourceId, String params) { |
|||
log.info("Execute on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); |
|||
switch (resourceId) { |
|||
case 5605: |
|||
resetMinMaxMeasuredValues(); |
|||
return ExecuteResponse.success(); |
|||
default: |
|||
return super.execute(identity, resourceId, params); |
|||
} |
|||
} |
|||
|
|||
private double getTwoDigitValue(double value) { |
|||
BigDecimal toBeTruncated = BigDecimal.valueOf(value); |
|||
return toBeTruncated.setScale(2, RoundingMode.HALF_UP).doubleValue(); |
|||
} |
|||
|
|||
private void adjustTemperature() { |
|||
float delta = (RANDOM.nextInt(20) - 10) / 10f; |
|||
currentTemp += delta; |
|||
Integer changedResource = adjustMinMaxMeasuredValue(currentTemp); |
|||
if (changedResource != null) { |
|||
fireResourcesChange(5700, changedResource); |
|||
} else { |
|||
fireResourcesChange(5700); |
|||
} |
|||
} |
|||
|
|||
private synchronized Integer adjustMinMaxMeasuredValue(double newTemperature) { |
|||
if (newTemperature > maxMeasuredValue) { |
|||
maxMeasuredValue = newTemperature; |
|||
return 5602; |
|||
} else if (newTemperature < minMeasuredValue) { |
|||
minMeasuredValue = newTemperature; |
|||
return 5601; |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private void resetMinMaxMeasuredValues() { |
|||
minMeasuredValue = currentTemp; |
|||
maxMeasuredValue = currentTemp; |
|||
} |
|||
|
|||
@Override |
|||
public List<Integer> getAvailableResourceIds(ObjectModel model) { |
|||
return supportedResources; |
|||
} |
|||
|
|||
@Override |
|||
public void destroy() { |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.ota; |
|||
|
|||
import org.springframework.mock.web.MockMultipartFile; |
|||
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; |
|||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; |
|||
import org.thingsboard.server.common.data.OtaPackageInfo; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; |
|||
|
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; |
|||
|
|||
@DaoSqlTest |
|||
public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
|||
|
|||
private final String[] RESOURCES_OTA = new String[]{"3.xml", "5.xml", "9.xml"}; |
|||
protected static final String CLIENT_ENDPOINT_WITHOUT_FW_INFO = "WithoutFirmwareInfoDevice"; |
|||
protected static final String CLIENT_ENDPOINT_OTA5 = "Ota5_Device"; |
|||
protected static final String CLIENT_ENDPOINT_OTA9 = "Ota9_Device"; |
|||
|
|||
public AbstractOtaLwM2MIntegrationTest() { |
|||
setResources(this.RESOURCES_OTA); |
|||
} |
|||
|
|||
protected OtaPackageInfo createFirmware() throws Exception { |
|||
String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; |
|||
|
|||
OtaPackageInfo firmwareInfo = new OtaPackageInfo(); |
|||
firmwareInfo.setDeviceProfileId(deviceProfile.getId()); |
|||
firmwareInfo.setType(FIRMWARE); |
|||
firmwareInfo.setTitle("My firmware"); |
|||
firmwareInfo.setVersion("v1.0"); |
|||
|
|||
OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); |
|||
|
|||
MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1}); |
|||
|
|||
return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); |
|||
} |
|||
|
|||
protected OtaPackageInfo createSoftware() throws Exception { |
|||
String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; |
|||
|
|||
OtaPackageInfo swInfo = new OtaPackageInfo(); |
|||
swInfo.setDeviceProfileId(deviceProfile.getId()); |
|||
swInfo.setType(SOFTWARE); |
|||
swInfo.setTitle("My sw"); |
|||
swInfo.setVersion("v1.0"); |
|||
|
|||
OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", swInfo, OtaPackageInfo.class); |
|||
|
|||
MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1}); |
|||
|
|||
return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); |
|||
} |
|||
|
|||
protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { |
|||
MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); |
|||
postRequest.file(content); |
|||
setJwtToken(postRequest); |
|||
return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); |
|||
} |
|||
} |
|||
@ -0,0 +1,203 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.rpc; |
|||
|
|||
import org.junit.Before; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import java.util.function.Predicate; |
|||
|
|||
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; |
|||
import static org.eclipse.leshan.core.LwM2mId.DEVICE; |
|||
import static org.eclipse.leshan.core.LwM2mId.FIRMWARE; |
|||
import static org.eclipse.leshan.core.LwM2mId.SERVER; |
|||
import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; |
|||
|
|||
@DaoSqlTest |
|||
public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
|||
|
|||
protected String RPC_TRANSPORT_CONFIGURATION; |
|||
|
|||
protected String deviceId; |
|||
public Set expectedObjects; |
|||
public Set expectedObjectIdVers; |
|||
public Set expectedInstances; |
|||
public Set expectedObjectIdVerInstances; |
|||
|
|||
protected String objectInstanceIdVer_1; |
|||
protected String objectIdVer_0; |
|||
protected String objectIdVer_2; |
|||
private static final Predicate PREDICATE_3 = path -> (!((String) path).contains("/" + TEMPERATURE_SENSOR) && ((String) path).contains("/" + DEVICE)); |
|||
protected String objectIdVer_3; |
|||
protected String objectInstanceIdVer_3; |
|||
protected String objectInstanceIdVer_5; |
|||
protected String objectInstanceIdVer_9; |
|||
protected String objectIdVer_19; |
|||
protected final String OBJECT_ID_VER_50 = "/50"; |
|||
protected String objectIdVer_3303; |
|||
protected static AtomicInteger endpointSequence = new AtomicInteger(); |
|||
protected static String DEVICE_ENDPOINT_RPC_PREF = "deviceEndpointRpc"; |
|||
|
|||
public AbstractRpcLwM2MIntegrationTest(){ |
|||
setResources(resources); |
|||
} |
|||
|
|||
@Before |
|||
public void beforeTest() throws Exception { |
|||
String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet(); |
|||
init(); |
|||
createNewClient (SECURITY, COAP_CONFIG, true, endpoint); |
|||
|
|||
expectedObjects = ConcurrentHashMap.newKeySet(); |
|||
expectedObjectIdVers = ConcurrentHashMap.newKeySet(); |
|||
expectedInstances = ConcurrentHashMap.newKeySet(); |
|||
expectedObjectIdVerInstances = ConcurrentHashMap.newKeySet(); |
|||
client.getClient().getObjectTree().getObjectEnablers().forEach((key, val) -> { |
|||
if (key > 0) { |
|||
String objectVerId = "/" + key; |
|||
if (!val.getObjectModel().version.equals("1.0")) { |
|||
objectVerId += ("_" + val.getObjectModel().version); |
|||
} |
|||
expectedObjects.add("/" + key); |
|||
expectedObjectIdVers.add(objectVerId); |
|||
String finalObjectVerId = objectVerId; |
|||
val.getAvailableInstanceIds().forEach(inststanceId -> { |
|||
expectedInstances.add("/" + key + "/" + inststanceId); |
|||
expectedObjectIdVerInstances.add(finalObjectVerId + "/" + inststanceId); |
|||
}); |
|||
} |
|||
}); |
|||
String ver_Id_0 = client.getClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_0).version; |
|||
if ("1.0".equals(ver_Id_0)) { |
|||
objectIdVer_0 = "/" + OBJECT_ID_0; |
|||
} |
|||
else { |
|||
objectIdVer_0 = "/" + OBJECT_ID_0 + "_" + ver_Id_0; |
|||
} |
|||
objectIdVer_2 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + ACCESS_CONTROL)).findFirst().get(); |
|||
objectIdVer_3 = (String) expectedObjects.stream().filter(PREDICATE_3).findFirst().get(); |
|||
objectIdVer_19 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get(); |
|||
objectIdVer_3303 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + TEMPERATURE_SENSOR)).findFirst().get(); |
|||
objectInstanceIdVer_1 = (String) expectedObjectIdVerInstances.stream().filter(path -> (!((String) path).contains("/" + BINARY_APP_DATA_CONTAINER) && ((String) path).contains("/" + SERVER))).findFirst().get(); |
|||
objectInstanceIdVer_3 = (String) expectedObjectIdVerInstances.stream().filter(PREDICATE_3).findFirst().get(); |
|||
objectInstanceIdVer_5 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).contains("/" + FIRMWARE)).findFirst().get(); |
|||
objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).contains("/" + SOFTWARE_MANAGEMENT)).findFirst().get(); |
|||
|
|||
RPC_TRANSPORT_CONFIGURATION = "{\n" + |
|||
" \"type\": \"LWM2M\",\n" + |
|||
" \"observeAttr\": {\n" + |
|||
" \"keyName\": {\n" + |
|||
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" + |
|||
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" + |
|||
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" + |
|||
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\"\n" + |
|||
" },\n" + |
|||
" \"observe\": [\n" + |
|||
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9 + "\",\n" + |
|||
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "\"\n" + |
|||
" ],\n" + |
|||
" \"attribute\": [\n" + |
|||
" ],\n" + |
|||
" \"telemetry\": [\n" + |
|||
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9 + "\",\n" + |
|||
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\",\n" + |
|||
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "\",\n" + |
|||
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\"\n" + |
|||
" ],\n" + |
|||
" \"attributeLwm2m\": {}\n" + |
|||
" },\n" + |
|||
" \"bootstrapServerUpdateEnable\": true,\n" + |
|||
" \"bootstrap\": [\n" + |
|||
" {\n" + |
|||
" \"host\": \"0.0.0.0\",\n" + |
|||
" \"port\": 5687,\n" + |
|||
" \"binding\": \"U\",\n" + |
|||
" \"lifetime\": 300,\n" + |
|||
" \"securityMode\": \"NO_SEC\",\n" + |
|||
" \"shortServerId\": 111,\n" + |
|||
" \"notifIfDisabled\": true,\n" + |
|||
" \"serverPublicKey\": \"\",\n" + |
|||
" \"defaultMinPeriod\": 1,\n" + |
|||
" \"bootstrapServerIs\": true,\n" + |
|||
" \"clientHoldOffTime\": 1,\n" + |
|||
" \"bootstrapServerAccountTimeout\": 0\n" + |
|||
" },\n" + |
|||
" {\n" + |
|||
" \"host\": \"0.0.0.0\",\n" + |
|||
" \"port\": 5685,\n" + |
|||
" \"binding\": \"U\",\n" + |
|||
" \"lifetime\": 300,\n" + |
|||
" \"securityMode\": \"NO_SEC\",\n" + |
|||
" \"shortServerId\": 123,\n" + |
|||
" \"notifIfDisabled\": true,\n" + |
|||
" \"serverPublicKey\": \"\",\n" + |
|||
" \"defaultMinPeriod\": 1,\n" + |
|||
" \"bootstrapServerIs\": false,\n" + |
|||
" \"clientHoldOffTime\": 1,\n" + |
|||
" \"bootstrapServerAccountTimeout\": 0\n" + |
|||
" }\n" + |
|||
" ],\n" + |
|||
" \"clientLwM2mSettings\": {\n" + |
|||
" \"edrxCycle\": null,\n" + |
|||
" \"powerMode\": \"DRX\",\n" + |
|||
" \"fwUpdateResource\": null,\n" + |
|||
" \"fwUpdateStrategy\": 1,\n" + |
|||
" \"psmActivityTimer\": null,\n" + |
|||
" \"swUpdateResource\": null,\n" + |
|||
" \"swUpdateStrategy\": 1,\n" + |
|||
" \"pagingTransmissionWindow\": null,\n" + |
|||
" \"clientOnlyObserveAfterConnect\": 1\n" + |
|||
" }\n" + |
|||
"}"; |
|||
createDeviceProfile(RPC_TRANSPORT_CONFIGURATION); |
|||
|
|||
NoSecClientCredential credentials = createNoSecClientCredentials(endpoint); |
|||
final Device device = createDevice(credentials); |
|||
deviceId = device.getId().getId().toString(); |
|||
|
|||
client.start(); |
|||
} |
|||
|
|||
protected String pathIdVerToObjectId(String pathIdVer) { |
|||
if (pathIdVer.contains("_")){ |
|||
String [] objVer = pathIdVer.split("/"); |
|||
objVer[1] = objVer[1].split("_")[0]; |
|||
return String.join("/", objVer); |
|||
} |
|||
return pathIdVer; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.rpc.sql; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.eclipse.leshan.core.ResponseCode; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.junit.Test; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertTrue; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; |
|||
|
|||
|
|||
public class RpcLwm2mIntegrationCreateTest extends AbstractRpcLwM2MIntegrationTest { |
|||
|
|||
/** |
|||
* Create {"id":"/19_1.1","value":{"0":{"0":"00AC"}, "1":1}} |
|||
* |
|||
* create_2_instances_in_object |
|||
* new ObjectInstance if Object is Multiple & Resource Single |
|||
* Create {"id":"/19_1.1/12","value":{"0":{"0":"00AC", "1":1}}} |
|||
* {"{"result":"CREATED"}"} |
|||
*/ |
|||
@Test |
|||
public void testCreateObjectInstanceWithInstanceIdByIdKey_Result_CREATED() throws Exception { |
|||
String expectedPath = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_12; |
|||
String expectedValue = "{\"" + RESOURCE_ID_0 + "\":{\"0\":\"00AC\"}, \"1\":1}"; |
|||
String actualResult = sendRPCreateById(expectedPath, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CREATED.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
/** |
|||
* Create {"id":"/19_1.1","value":{"0":{"0":"00AC"}, "1":1}} |
|||
* |
|||
* create_2_instances_in_object |
|||
* new ObjectInstance if Object is Multiple & Resource Single |
|||
* Create {"id":"/19_1.1/0","value":{"0":{"0":"00AC", "1":1}}} |
|||
* {"result":"BAD_REQUEST","error":"instance 0 already exists"} |
|||
*/ |
|||
@Test |
|||
public void testCreateObjectInstanceWithInstanceIdAlreadyExistsById_Result_BAD_REQUEST() throws Exception { |
|||
String expectedPath = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0; |
|||
String expectedValue = "{\"" + RESOURCE_ID_0 + "\":{\"0\":\"00AC\"}, \"1\":1}"; |
|||
String actualResult = sendRPCreateById(expectedPath, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String expected = "instance " + OBJECT_INSTANCE_ID_0 + " already exists"; |
|||
String actual = rpcActualResult.get("error").asText(); |
|||
assertTrue(actual.equals(expected)); |
|||
} |
|||
|
|||
/** |
|||
* failed: cannot_create_mandatory_single_object |
|||
* Create {"id":"/3/1,"value":{"0":"00AC"}} |
|||
* {"result":"BAD_REQUEST","error":"Path /3/1. Object must be Multiple !"} |
|||
*/ |
|||
@Test |
|||
public void testCreateObjectInstanceWithInstanceIdMandatorySingleObjectById_Result_BAD_REQUEST() throws Exception { |
|||
String expectedPath = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_1; |
|||
String expectedValue = "{\"" + RESOURCE_ID_0 + "\":{\"0\":\"00AC\"}}"; |
|||
String actualResult = sendRPCreateById(expectedPath, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String expected = "Path " + expectedPath + ". Object must be Multiple !"; |
|||
String actual = rpcActualResult.get("error").asText(); |
|||
assertTrue(actual.equals(expected)); |
|||
} |
|||
|
|||
/** |
|||
* failed: cannot_create_instance_of_security_object |
|||
* Create {"id":"/0/2","value":{"2":4}} |
|||
* {"result":"BAD_REQUEST","error":"Specified object id 0 absent in the list supported objects of the client or is security object!"} |
|||
*/ |
|||
@Test |
|||
public void testCreateObjectInstanceWithInstanceIdSecurityObjectById_Result_BAD_REQUEST() throws Exception { |
|||
String expectedPath = objectIdVer_0 + "/" + OBJECT_INSTANCE_ID_1; |
|||
String expectedValue = "{\"" + RESOURCE_ID_0 + "\":{\"2\":4}}"; |
|||
String actualResult = sendRPCreateById(expectedPath, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedObjectId = pathIdVerToObjectId((String) expectedPath); |
|||
LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); |
|||
String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!"; |
|||
String actual = rpcActualResult.get("error").asText(); |
|||
assertTrue(actual.equals(expected)); |
|||
} |
|||
|
|||
/** |
|||
* failed: cannot_create_instance_of_absent_object |
|||
* Create {"id":"/50/1","value":{"0":"00AC"}} |
|||
* {"result":"BAD_REQUEST","error":"Specified object id 50 absent in the list supported objects of the client or is security object!"} |
|||
*/ |
|||
@Test |
|||
public void testCreateObjectInstanceWithInstanceIdAbsentObjectById_Result_BAD_REQUEST() throws Exception { |
|||
String expectedPath = OBJECT_ID_VER_50 + "/" + OBJECT_INSTANCE_ID_1; |
|||
String expectedValue = "{\"" + RESOURCE_ID_0 + "\":{\"0\":\"00AC\"}}"; |
|||
String actualResult = sendRPCreateById(expectedPath, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedObjectId = pathIdVerToObjectId((String) expectedPath); |
|||
LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); |
|||
String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!"; |
|||
String actual = rpcActualResult.get("error").asText(); |
|||
assertTrue(actual.equals(expected)); |
|||
} |
|||
|
|||
private String sendRPCreateById(String path, String value) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"Create\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.rpc.sql; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.eclipse.leshan.core.ResponseCode; |
|||
import org.junit.Test; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertTrue; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_7; |
|||
|
|||
|
|||
public class RpcLwm2mIntegrationDeleteTest extends AbstractRpcLwM2MIntegrationTest { |
|||
|
|||
/** |
|||
* if there is such an instance |
|||
* Delete {"id":"/3303/12"} |
|||
* {"result":"DELETE"} |
|||
*/ |
|||
@Test |
|||
public void testDeleteObjectInstanceIsSuchByIdKey_Result_DELETED() throws Exception { |
|||
String expectedPath = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12; |
|||
String actualResult = sendRPCDeleteById(expectedPath); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.DELETED.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
/** |
|||
* if there is no such instance |
|||
* Delete {"id":"/19/12"} |
|||
* {"result":"NOT_FOUND"} |
|||
*/ |
|||
@Test |
|||
public void testDeleteObjectInstanceIsNotSuchByIdKey_Result_NOT_FOUND() throws Exception { |
|||
String expectedPath = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_12; |
|||
String actualResult = sendRPCDeleteById(expectedPath); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
/** |
|||
* delete object |
|||
* Delete {"id":"/19_1.1"} |
|||
* {"result":"BAD_REQUEST","error":"Invalid path /19 : Only object instances can be delete"} |
|||
*/ |
|||
@Test |
|||
public void testDeleteObjectByIdKey_Result_BAD_REQUEST() throws Exception { |
|||
String expectedPath = objectIdVer_19; |
|||
String actualResult = sendRPCDeleteById(expectedPath); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String expected = "Invalid path " + pathIdVerToObjectId((String) expectedPath) + " : Only object instances can be delete"; |
|||
String actual = rpcActualResult.get("error").asText(); |
|||
assertTrue(actual.equals(expected)); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* delete resource |
|||
* Delete {"id":"/3/0/7"} |
|||
* {"result":"METHOD_NOT_ALLOWED"} |
|||
*/ |
|||
@Test |
|||
public void testDeleteResourceByIdKey_Result_METHOD_NOT_ALLOWED() throws Exception { |
|||
String expectedPath = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + RESOURCE_ID_7; |
|||
String actualResult = sendRPCDeleteById(expectedPath); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
|
|||
private String sendRPCDeleteById(String path) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"Delete\", \"params\": {\"id\": \"" + path + "\"}}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,165 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.rpc.sql; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.eclipse.leshan.core.ResponseCode; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.junit.Test; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; |
|||
|
|||
import java.util.Arrays; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertTrue; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; |
|||
|
|||
|
|||
public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegrationTest { |
|||
|
|||
/** |
|||
* DiscoverAll |
|||
* |
|||
* @throws Exception |
|||
*/ |
|||
@Test |
|||
public void testDiscoverAll_Return_CONTENT_LinksAllObjectsAllInstancesOfClient() throws Exception { |
|||
String setRpcRequest = "{\"method\":\"DiscoverAll\"}"; |
|||
String actualResult = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
JsonNode rpcActualValue = JacksonUtil.toJsonNode(rpcActualResult.get("value").asText()); |
|||
Set actualObjects = ConcurrentHashMap.newKeySet(); |
|||
Set actualInstances = ConcurrentHashMap.newKeySet(); |
|||
rpcActualValue.forEach(node -> { |
|||
if (!node.get("url").asText().equals("/")) { |
|||
LwM2mPath path = new LwM2mPath(node.get("url").asText()); |
|||
actualObjects.add("/" + path.getObjectId()); |
|||
if (path.isObjectInstance()) { |
|||
actualInstances.add("/" + path.getObjectId() + "/" + path.getObjectInstanceId()); |
|||
} |
|||
} |
|||
}); |
|||
assertEquals(expectedInstances, actualInstances); |
|||
assertEquals(expectedObjects, actualObjects); |
|||
} |
|||
|
|||
/** |
|||
* Discover {"id":"/3"} |
|||
* |
|||
* @throws Exception |
|||
*/ |
|||
@Test |
|||
public void testDiscoverObject_Return_CONTENT_LinksInstancesAndResourcesOnLyExpectedObject() { |
|||
expectedObjectIdVers.forEach(expected -> { |
|||
try { |
|||
String actualResult = sendDiscover((String) expected); |
|||
String expectedObjectId = pathIdVerToObjectId((String) expected); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String[] actualValues = rpcActualResult.get("value").asText().split(","); |
|||
assertTrue(actualValues.length > 0); |
|||
assertEquals(0, Arrays.stream(actualValues).filter(path -> !path.contains(expectedObjectId)).collect(Collectors.toList()).size()); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Discover {"id":"3/0"} |
|||
* If WriteAttributes not implemented: |
|||
* {"result":"CONTENT","value":"</3/0>,</3/0/0>,</3/0/1>,</3/0/2>,</3/0/3>,</3/0/4>,</3/0/5>,</3/0/6>,</3/0/7>,</3/0/8>,</3/0/9>,</3/0/10>,</3/0/11>,</3/0/12>,</3/0/13>,</3/0/14>,</3/0/15>,</3/0/16>,</3/0/1 |
|||
* 7>,</3/0/18>,</3/0/19>,</3/0/20>,</3/0/21>,</3/0/22>"} |
|||
* If WriteAttributes implemented and WriteAttributes saved |
|||
* Discover {"id":"19/0"} |
|||
* {"result":"CONTENT","value":"[</19/0>;dim=2;pmin=10;pmax=60;gt=50;lt=42.2,</19/0/0>;pmax=120, </19/0/1>, </19/0/2>, </19/0/3>, </19/0/4>, </19/0/5>;lt=45]"} |
|||
*/ |
|||
@Test |
|||
public void testDiscoverInstance_Return_CONTENT_LinksResourcesOnLyExpectedInstance() throws Exception { |
|||
String expected = (String) expectedObjectIdVerInstances.stream().findAny().get(); |
|||
String actualResult = sendDiscover(expected); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedObjectInstanceId = pathIdVerToObjectId(expected); |
|||
String[] actualValues = rpcActualResult.get("value").asText().split(","); |
|||
assertTrue(actualValues.length > 0); |
|||
assertEquals(0, Arrays.stream(actualValues).filter(path -> !path.contains(expectedObjectInstanceId)).collect(Collectors.toList()).size()); |
|||
} |
|||
|
|||
/** |
|||
* Discover {"id":"3/0/14"} |
|||
* If WriteAttributes implemented: |
|||
* {"result":"CONTENT","value":"</3/0/14>;pmax=100, "pmin":10, "ver"=1.0"} |
|||
* If WriteAttributes not implemented: |
|||
* {"result":"CONTENT","value":"</3/0/14>"} |
|||
* Discover {"id":"19_1.1/0/0"} |
|||
* If WriteAttributes implemented: |
|||
* {"result":"CONTENT","value":"</19/0/0>;pmax=100, "pmin":10, "ver"=1.1"} |
|||
* If WriteAttributes not implemented: |
|||
* {"result":"CONTENT","value":"</19/0/0>"} |
|||
*/ |
|||
@Test |
|||
public void testDiscoverResource_Return_CONTENT_LinksResourceOnLyExpectedResource() throws Exception { |
|||
String expectedInstance = (String) expectedInstances.stream().findFirst().get(); |
|||
String expectedObjectInstanceId = pathIdVerToObjectId(expectedInstance); |
|||
LwM2mPath expectedPath = new LwM2mPath(expectedObjectInstanceId); |
|||
int expectedResource = client.getClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey(); |
|||
String expected = expectedInstance + "/" + expectedResource; |
|||
String actualResult = sendDiscover(expected); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedResourceId = "<" + expectedObjectInstanceId + "/" + expectedResource + ">"; |
|||
String actualValue = rpcActualResult.get("value").asText(); |
|||
assertEquals(expectedResourceId, actualValue ); |
|||
|
|||
} |
|||
|
|||
/** |
|||
* Discover {"id":"2/0"} |
|||
*{"result":"NOT_FOUND"} |
|||
*/ |
|||
@Test |
|||
public void testDiscoverObjectInstanceAbsentInObject_Return_NOT_FOUND() throws Exception { |
|||
String expected = objectIdVer_2 + "/" + OBJECT_INSTANCE_ID_0; |
|||
String actualResult = sendDiscover(expected); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
/** |
|||
* Discover {"id":"2/0/2"} |
|||
* {"result":"NOT_FOUND"} |
|||
*/ |
|||
@Test |
|||
public void testDiscoverResourceAbsentInObject_Return_NOT_FOUND() throws Exception { |
|||
String expected = objectIdVer_2 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2; |
|||
String actualResult = sendDiscover(expected); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
private String sendDiscover(String path) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
} |
|||
@ -0,0 +1,186 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.rpc.sql; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.eclipse.leshan.core.ResponseCode; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.junit.Test; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertTrue; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_4; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_8; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; |
|||
|
|||
|
|||
public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationTest { |
|||
|
|||
|
|||
/** |
|||
* Update FW |
|||
* Execute {"id":"5/0/2"} |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testExecuteUpdateFWById_Result_CHANGED() throws Exception { |
|||
String expectedPath = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; |
|||
String actualResult = sendRPCExecuteById(expectedPath); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
/** |
|||
* Update SW |
|||
* Execute {"id":"9/0/4"} |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testExecuteUpdateSWById_Result_CHANGED() throws Exception { |
|||
String expectedPath = objectInstanceIdVer_9 + "/" + RESOURCE_ID_4; |
|||
String actualResult = sendRPCExecuteById(expectedPath); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
/** |
|||
* Reboot |
|||
* Execute {"id":"3/0/4"} |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testExecuteRebootById_Result_CHANGED() throws Exception { |
|||
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_4; |
|||
String actualResult = sendRPCExecuteById(expectedPath); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
/** |
|||
* Registration Update Trigger |
|||
* Execute {"id":"1/0/8"} |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testExecuteRegistrationUpdateTriggerById_Result_CHANGED() throws Exception { |
|||
String expectedPath = objectInstanceIdVer_1 + "/" + RESOURCE_ID_8; |
|||
String actualResult = sendRPCExecuteById(expectedPath); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* execute_resource_with_parameters (execute reboot after 60 seconds on device) |
|||
* Execute {"id":"3/0/4","value":60} |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testExecuteResourceWithParametersById_Result_CHANGED() throws Exception { |
|||
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_4; |
|||
Object expectedValue = 60; |
|||
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
/** |
|||
* Bootstrap-Request Trigger |
|||
* Execute {"id":"1/0/9"} |
|||
* {"result":"BAD_REQUEST","error":"probably no bootstrap server configured"} |
|||
*/ |
|||
@Test |
|||
public void testExecuteBootstrapRequestTriggerById_Result_BAD_REQUEST_Error_NoBootstrapServerConfigured() throws Exception { |
|||
String expectedPath = objectInstanceIdVer_1 + "/" + RESOURCE_ID_9; |
|||
String actualResult = sendRPCExecuteById(expectedPath); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String expected = "probably no bootstrap server configured"; |
|||
String actual = rpcActualResult.get("error").asText(); |
|||
assertTrue(actual.equals(expected)); |
|||
} |
|||
|
|||
/** |
|||
* bad: resource operation not "E" |
|||
* Execute {"id":"5/0/3"} |
|||
* {"result":"BAD_REQUEST","error":"Resource with /5/0/3 is not executable."} |
|||
*/ |
|||
@Test |
|||
public void testExecuteResourceWithOperationNotExecuteById_Result_METHOD_NOT_ALLOWED() throws Exception { |
|||
String expectedPath = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; |
|||
String actualResult = sendRPCExecuteById(expectedPath); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedObjectId = pathIdVerToObjectId((String) expectedPath); |
|||
String expected = "Resource with " + expectedObjectId + " is not executable."; |
|||
String actual = rpcActualResult.get("error").asText(); |
|||
assertTrue(actual.equals(expected)); |
|||
} |
|||
|
|||
/** |
|||
* bad: execute_non_existing_resource_on_non_existing_object |
|||
* Execute {"id":"50/0/3"} |
|||
* {"result":"BAD_REQUEST","error":"Specified object id 50 absent in the list supported objects of the client or is security object!"} |
|||
*/ |
|||
@Test |
|||
public void testExecuteNonExistingResourceOnNonExistingObjectById_Result_BAD_REQUEST() throws Exception { |
|||
String expectedPath = OBJECT_ID_VER_50 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3; |
|||
String actualResult = sendRPCExecuteById(expectedPath); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedObjectId = pathIdVerToObjectId((String) expectedPath); |
|||
LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); |
|||
String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!"; |
|||
String actual = rpcActualResult.get("error").asText(); |
|||
assertTrue(actual.equals(expected)); |
|||
} |
|||
|
|||
/** |
|||
* bad: execute security object |
|||
* Execute {"id":"0/0/3"} |
|||
* {"result":"BAD_REQUEST","error":"Specified object id 0 absent in the list supported objects of the client or is security object!"} |
|||
*/ |
|||
@Test |
|||
public void testExecuteSecurityObjectById_Result_NOT_FOUND() throws Exception { |
|||
String expectedPath = objectIdVer_0 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3; |
|||
String actualResult = sendRPCExecuteById(expectedPath); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedObjectId = pathIdVerToObjectId((String) expectedPath); |
|||
LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); |
|||
String expected = "Specified object id " + expectedPathId.getObjectId() + " absent in the list supported objects of the client or is security object!"; |
|||
String actual = rpcActualResult.get("error").asText(); |
|||
assertTrue(actual.equals(expected)); |
|||
} |
|||
|
|||
|
|||
private String sendRPCExecuteById(String path) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\"}}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
private String sendRPCExecuteWithValueById(String path, Object value) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,190 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.rpc.sql; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.eclipse.leshan.core.ResponseCode; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.junit.Test; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; |
|||
|
|||
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; |
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertTrue; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; |
|||
|
|||
public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest { |
|||
|
|||
/** |
|||
* ObserveReadAll |
|||
* @throws Exception |
|||
*/ |
|||
@Test |
|||
public void testObserveReadAllNothingObservation_Result_CONTENT_Value_Count_0() throws Exception { |
|||
String actualResult = sendObserve("ObserveCancelAll", null); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
actualResult = sendObserve("ObserveReadAll", null); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
assertEquals("[]", rpcActualResult.get("value").asText()); |
|||
} |
|||
|
|||
/** |
|||
* Observe {"id":"/3/0/9"} |
|||
* @throws Exception |
|||
*/ |
|||
@Test |
|||
public void testObserveSingleResource_Result_CONTENT_Value_SingleResource() throws Exception { |
|||
String expectedIdVer = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; |
|||
String actualResult = sendObserve("Observe", expectedIdVer); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource")); |
|||
} |
|||
|
|||
/** |
|||
* Observe {"id":"/3_1.1/0/13"} |
|||
* @throws Exception |
|||
*/ |
|||
@Test |
|||
public void testObserveWithBadVersion_Result_BadRequest_ErrorMsg_BadVersionMustBe1_0() throws Exception { |
|||
String expectedInstance = (String) expectedInstances.stream().filter(path -> !((String)path).contains("_")).findFirst().get(); |
|||
LwM2mPath expectedPath = new LwM2mPath(expectedInstance); |
|||
int expectedResource = client.getClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey(); |
|||
String expectedId = "/" + expectedPath.getObjectId() + "_1.2" + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource; |
|||
String actualResult = sendObserve("Observe", expectedId); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String expected = "Specified resource id " + expectedId +" is not valid version! Must be version: 1.0"; |
|||
assertEquals(expected, rpcActualResult.get("error").asText()); |
|||
} |
|||
|
|||
/** |
|||
* Not implemented Instance |
|||
* Observe {"id":"/2/0"} |
|||
* @throws Exception |
|||
*/ |
|||
@Test |
|||
public void testObserveNoImplementedInstanceOnDevice_Result_NotFound() throws Exception { |
|||
String objectInstanceIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + ACCESS_CONTROL)).findFirst().get(); |
|||
String expected = objectInstanceIdVer + "/" + OBJECT_INSTANCE_ID_0; |
|||
String actualResult = sendObserve("Observe", expected); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
/** |
|||
* Not implemented Resource |
|||
* Observe {"id":"/19_1.1/0/0"} |
|||
* @throws Exception |
|||
*/ |
|||
@Test |
|||
public void testObserveNoImplementedResourceOnDeviceValueNull_Result_BadRequest() throws Exception { |
|||
String objectIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get(); |
|||
String expected = objectIdVer + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; |
|||
String actualResult = sendObserve("Observe", expected); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
String expectedValue = "values MUST NOT be null"; |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
assertEquals(expectedValue, rpcActualResult.get("error").asText()); |
|||
} |
|||
|
|||
/** |
|||
* Repeated request on Observe |
|||
* Observe {"id":"/5/0/0"} |
|||
* @throws Exception |
|||
*/ |
|||
@Test |
|||
public void testObserveRSourceNotRead_Result_METHOD_NOT_ALLOWED() throws Exception { |
|||
String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_0; |
|||
sendObserve("Observe", expectedId); |
|||
String actualResult = sendObserve("Observe", expectedId); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
/** |
|||
* Repeated request on Observe |
|||
* Observe {"id":"/3/0/0"} |
|||
* @throws Exception |
|||
*/ |
|||
@Test |
|||
public void testObserveRepeatedRequestObserveOnDevice_Result_BAD_REQUEST_ErrorMsg_AlreadyRegistered() throws Exception { |
|||
String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; |
|||
sendObserve("Observe", expectedId); |
|||
String actualResult = sendObserve("Observe", expectedId); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String expected = "Observation is already registered!"; |
|||
assertEquals(expected, rpcActualResult.get("error").asText()); |
|||
} |
|||
|
|||
/** |
|||
* ObserveReadAll |
|||
* @throws Exception |
|||
*/ |
|||
@Test |
|||
public void testObserveReadAll_Result_CONTENT_Value_Contains_Paths_Count_ObserveAll() throws Exception { |
|||
sendObserve("ObserveCancelAll", null); |
|||
String expectedId_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; |
|||
String expectedId_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; |
|||
sendObserve("Observe", expectedId_0); |
|||
sendObserve("Observe", expectedId_9); |
|||
String actualResult = sendObserve("ObserveReadAll", null); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String actualValues = rpcActualResult.get("value").asText(); |
|||
assertTrue(actualValues.contains(expectedId_0)); |
|||
assertTrue(actualValues.contains(expectedId_9)); |
|||
assertEquals(2, actualValues.split(",").length); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* ObserveCancel {"id":"/3/0/3"} |
|||
* ObserveCancel {"id":"/5/0/3"} |
|||
*/ |
|||
@Test |
|||
public void testObserveCancelOneResource_Result_CONTENT_Value_Count_1() throws Exception { |
|||
sendObserve("ObserveCancelAll", null); |
|||
String expectedId_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; |
|||
String expectedId_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; |
|||
sendObserve("Observe", expectedId_0); |
|||
sendObserve("Observe", expectedId_3); |
|||
String actualResult = sendObserve("ObserveCancel", expectedId_0); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
assertEquals("1", rpcActualResult.get("value").asText()); |
|||
} |
|||
|
|||
private String sendObserve(String method, String params) throws Exception { |
|||
String sendRpcRequest; |
|||
if (params == null) { |
|||
sendRpcRequest = "{\"method\": \"" + method + "\"}"; |
|||
} |
|||
else { |
|||
sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}"; |
|||
} |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk()); |
|||
} |
|||
} |
|||
@ -0,0 +1,226 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.rpc.sql; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.eclipse.leshan.core.ResponseCode; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.junit.Test; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; |
|||
|
|||
import static org.eclipse.leshan.core.LwM2mId.SERVER; |
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertTrue; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_1; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_11; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; |
|||
|
|||
|
|||
public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest { |
|||
|
|||
/** |
|||
* Read {"id":"/3"} |
|||
* Read {"id":"/6"}... |
|||
*/ |
|||
@Test |
|||
public void testReadAllObjectsInClientById_Result_CONTENT_Value_IsLwM2mObject_IsInstances() throws Exception { |
|||
expectedObjectIdVers.forEach(expected -> { |
|||
try { |
|||
String actualResult = sendRPCById((String) expected); |
|||
String expectedObjectId = pathIdVerToObjectId((String) expected); |
|||
LwM2mPath expectedPath = new LwM2mPath(expectedObjectId); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedObjectInstances = "LwM2mObject [id=" + expectedPath.getObjectId() + ", instances={0=LwM2mObjectInstance [id=0, resources="; |
|||
if (expectedPath.getObjectId() == 2) { |
|||
expectedObjectInstances = "LwM2mObject [id=2, instances={}]"; |
|||
} |
|||
assertTrue(rpcActualResult.get("value").asText().contains(expectedObjectInstances)); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Read {"id":"/5/0"} |
|||
* |
|||
* @throws Exception |
|||
*/ |
|||
@Test |
|||
public void testReadAllInstancesInClientById_Result_CONTENT_Value_IsInstances_IsResources() throws Exception{ |
|||
expectedObjectIdVerInstances.forEach(expected -> { |
|||
try { |
|||
String actualResult = sendRPCById((String) expected); |
|||
String expectedObjectId = pathIdVerToObjectId((String) expected); |
|||
LwM2mPath expectedPath = new LwM2mPath(expectedObjectId); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedObjectInstances = "LwM2mObjectInstance [id=" + expectedPath.getObjectInstanceId() + ", resources={"; |
|||
assertTrue(rpcActualResult.get("value").asText().contains(expectedObjectInstances)); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Read {"id":"/19/1/0"} |
|||
* |
|||
* @throws Exception |
|||
*/ |
|||
@Test |
|||
public void testReadMultipleResourceById_Result_CONTENT_Value_IsLwM2mMultipleResource() throws Exception { |
|||
String expectedIdVer = objectInstanceIdVer_3 +"/" + RESOURCE_ID_11; |
|||
String actualResult = sendRPCById(expectedIdVer); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String expected = "LwM2mMultipleResource [id=" + RESOURCE_ID_11 + ", values={"; |
|||
assertTrue(rpcActualResult.get("value").asText().contains(expected)); |
|||
} |
|||
|
|||
/** |
|||
* Read {"id":"/3/0/14"} |
|||
*/ |
|||
@Test |
|||
public void testReadSingleResourceById_Result_CONTENT_Value_IsLwM2mSingleResource() throws Exception { |
|||
String expectedIdVer = objectInstanceIdVer_3 +"/" + RESOURCE_ID_14; |
|||
String actualResult = sendRPCById(expectedIdVer); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value="; |
|||
assertTrue(rpcActualResult.get("value").asText().contains(expected)); |
|||
} |
|||
|
|||
/** |
|||
* Read {"key":"UtfOffset"} |
|||
*/ |
|||
@Test |
|||
public void testReadSingleResourceByKey_Result_CONTENT_Value_IsLwM2mSingleResource() throws Exception { |
|||
String expectedKey = RESOURCE_ID_NAME_3_14; |
|||
String actualResult = sendRPCByKey(expectedKey); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value="; |
|||
assertTrue(rpcActualResult.get("value").asText().contains(expected)); |
|||
} |
|||
|
|||
/** |
|||
* ReadComposite {"ids":["/1_1.2", "/3_1.0/0/1", "/3_1.0/0/11"]} |
|||
*/ |
|||
@Test |
|||
public void testReadCompositeSingleResourceByIds_Result_CONTENT_Value_IsObjectIsLwM2mSingleResourceIsLwM2mMultipleResource() throws Exception { |
|||
String expectedIdVer_1 = (String) expectedObjectIdVers.stream().filter(path -> (!((String)path).contains("/" + BINARY_APP_DATA_CONTAINER) && ((String)path).contains("/" + SERVER))).findFirst().get(); |
|||
String objectId_1 = pathIdVerToObjectId(expectedIdVer_1); |
|||
String expectedIdVer3_0_1 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_1; |
|||
String expectedIdVer3_0_11 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_11; |
|||
String objectInstanceId_3 = pathIdVerToObjectId(objectInstanceIdVer_3); |
|||
String expectedIds = "[\"" + expectedIdVer_1 + "\", \"" + expectedIdVer3_0_1 + "\", \"" + expectedIdVer3_0_11 + "\"]"; |
|||
String actualResult = sendCompositeRPCByIds(expectedIds); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String expected1 = objectId_1 + "=LwM2mObject [id=" + new LwM2mPath(objectId_1).getObjectId() + ", instances={"; |
|||
String expected3_0_1 = objectInstanceId_3 + "/" + RESOURCE_ID_1 + "=LwM2mSingleResource [id=" + RESOURCE_ID_1 + ", value="; |
|||
String expected3_0_11 = objectInstanceId_3 + "/" + RESOURCE_ID_11 + "=LwM2mMultipleResource [id=" + RESOURCE_ID_11 + ", values={"; |
|||
String actualValues = rpcActualResult.get("value").asText(); |
|||
assertTrue(actualValues.contains(expected1)); |
|||
assertTrue(actualValues.contains(expected3_0_1)); |
|||
assertTrue(actualValues.contains(expected3_0_11)); |
|||
} |
|||
|
|||
/** |
|||
* ReadComposite {"ids":["/1_1.2/0/1", "/1_1.2/0/2", "/3_1.0/0"]} |
|||
*/ |
|||
@Test |
|||
public void testReadCompositeSingleResourceByIds_Result_CONTENT_Value_IsObjectInstanceIsLwM2mSingleResource() throws Exception { |
|||
String expectedIdVer3_0 = objectInstanceIdVer_3; |
|||
String expectedIdVer1_0_1 = objectInstanceIdVer_1 + "/" + RESOURCE_ID_1; |
|||
String expectedIdVer1_0_2 = objectInstanceIdVer_1 + "/" + RESOURCE_ID_2; |
|||
String expectedIds = "[\"" + expectedIdVer1_0_1 + "\", \"" + expectedIdVer1_0_2 + "\", \"" + expectedIdVer3_0 + "\"]"; |
|||
String actualResult = sendCompositeRPCByIds(expectedIds); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String objectInstanceId_3 = pathIdVerToObjectId(objectInstanceIdVer_3); |
|||
LwM2mPath path = new LwM2mPath(objectInstanceId_3); |
|||
String expected3_0 = objectInstanceId_3 + "=LwM2mObjectInstance [id=" + path.getObjectInstanceId() + ", resources={"; |
|||
String objectInstanceId_1 = pathIdVerToObjectId(objectInstanceIdVer_1); |
|||
String expected1_0_1 = objectInstanceId_1 + "/" + RESOURCE_ID_1 + "=LwM2mSingleResource [id=" + RESOURCE_ID_1 + ", value="; |
|||
String expected1_0_2 = objectInstanceId_1 + "/" + RESOURCE_ID_2 + "=null"; |
|||
String actualValues = rpcActualResult.get("value").asText(); |
|||
assertTrue(actualValues.contains(expected3_0)); |
|||
assertTrue(actualValues.contains(expected1_0_1)); |
|||
assertTrue(actualValues.contains(expected1_0_2)); |
|||
} |
|||
|
|||
/** |
|||
* ReadComposite {"keys":["batteryLevel", "UtfOffset", "dataRead", "dataWrite"]} |
|||
*/ |
|||
@Test |
|||
public void testReadCompositeSingleResourceByKeys_Result_CONTENT_Value_3_0_IsLwM2mSingleResource_19_0_0_AND_19_0_1_Null() throws Exception { |
|||
String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; |
|||
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; |
|||
String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0; |
|||
String expectedKey19_1_0 = RESOURCE_ID_NAME_19_1_0; |
|||
String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\"]"; |
|||
String actualResult = sendCompositeRPCByKeys(expectedKeys); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String objectInstanceId_3 = pathIdVerToObjectId(objectInstanceIdVer_3); |
|||
String objectId_19 = pathIdVerToObjectId(objectIdVer_19); |
|||
String expected3_0_9 = objectInstanceId_3 + "/" + RESOURCE_ID_9 + "=LwM2mSingleResource [id=" + RESOURCE_ID_9 + ", value="; |
|||
String expected3_0_14 = objectInstanceId_3 + "/" + RESOURCE_ID_14 + "=LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value="; |
|||
String expected19_0_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "=null"; |
|||
String expected19_1_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "=null"; |
|||
String actualValues = rpcActualResult.get("value").asText(); |
|||
assertTrue(actualValues.contains(expected3_0_9)); |
|||
assertTrue(actualValues.contains(expected3_0_14)); |
|||
assertTrue(actualValues.contains(expected19_0_0)); |
|||
assertTrue(actualValues.contains(expected19_1_0)); |
|||
} |
|||
|
|||
|
|||
private String sendRPCById(String path) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
private String sendRPCByKey(String key) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"key\": \"" + key + "\"}}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
private String sendCompositeRPCByIds(String paths) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"ReadComposite\", \"params\": {\"ids\":" + paths + "}}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
private String sendCompositeRPCByKeys(String keys) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"ReadComposite\", \"params\": {\"keys\":" + keys + "}}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.rpc.sql; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.eclipse.leshan.core.ResponseCode; |
|||
import org.junit.Test; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertTrue; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; |
|||
|
|||
|
|||
public class RpcLwm2mIntegrationWriteAttributesTest extends AbstractRpcLwM2MIntegrationTest { |
|||
|
|||
/** |
|||
* WriteAttributes {"id":"/3/0/14","attributes":{"pmax":100, "pmin":10}} |
|||
* if not implemented: |
|||
* {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} |
|||
* if implemented: |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testWriteAttributesResourceWithParametersById_Result_INTERNAL_SERVER_ERROR() throws Exception { |
|||
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; |
|||
String expectedValue = "{\"pmax\":100, \"pmin\":10}"; |
|||
String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); |
|||
String expected = "not implemented"; |
|||
String actual = rpcActualResult.get("error").asText(); |
|||
assertTrue(actual.equals(expected)); |
|||
} |
|||
|
|||
private String sendRPCExecuteWithValueById(String path, String value) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"WriteAttributes\", \"params\": {\"id\": \"" + path + "\", \"attributes\": " + value + " }}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,334 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.rpc.sql; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.eclipse.leshan.core.ResponseCode; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.junit.Test; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertTrue; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_2; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_15; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_INSTANCE_ID_2; |
|||
|
|||
public class RpcLwm2mIntegrationWriteTest extends AbstractRpcLwM2MIntegrationTest { |
|||
|
|||
|
|||
/** |
|||
* update SingleResource: |
|||
* WriteReplace {"id":"3/0/14","value":"+12"} |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testWriteReplaceValueSingleResourceById_Result_CHANGED() throws Exception { |
|||
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; |
|||
String expectedValue = "+12"; |
|||
String actualResult = sendRPCWriteStringById("WriteReplace", expectedPath, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); |
|||
actualResult = sendRPCReadById(expectedPath); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
String actualValues = rpcActualResult.get("value").asText(); |
|||
String expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=" + expectedValue + ", type=STRING]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
} |
|||
|
|||
/** |
|||
* key |
|||
* WriteReplace {"key":"timezone","value":"+10"} |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testWriteReplaceValueSingleResourceByKey_Result_CHANGED() throws Exception { |
|||
String expectedKey = RESOURCE_ID_NAME_3_14; |
|||
String expectedValue = "+09"; |
|||
String actualResult = sendRPCWriteByKey("WriteReplace", expectedKey, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); |
|||
actualResult = sendRPCReadByKey(expectedKey); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
String actualValues = rpcActualResult.get("value").asText(); |
|||
String expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=" + expectedValue + ", type=STRING]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* id |
|||
* WriteReplace {"id": "/19_1.1/0/0","value": {"0":"0000ad45675600", "15":"1525ad45675600cdef"}} |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testWriteReplaceValueMultipleResource_Result_CHANGED_Value_Multi_Instance_Resource_must_in_Json_format() throws Exception { |
|||
String expectedPath = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; |
|||
int resourceInstanceId0 = 0; |
|||
int resourceInstanceId15 = 15; |
|||
String expectedValue0 = "0000ad45675600"; |
|||
String expectedValue15 = "1525ad45675600cdef"; |
|||
String expectedValue = "{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId15 + "\":\"" + expectedValue15 + "\"}"; |
|||
String actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedPath0 = expectedPath + "/" + resourceInstanceId0; |
|||
String expectedPath15 = expectedPath + "/" + resourceInstanceId15; |
|||
actualResult = sendRPCReadById(expectedPath0); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
String actualValues = rpcActualResult.get("value").asText(); |
|||
String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
actualResult = sendRPCReadById(expectedPath15); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
actualValues = rpcActualResult.get("value").asText(); |
|||
expected = "LwM2mResourceInstance [id=" + resourceInstanceId15 + ", value=" + expectedValue15.length()/2 + "Bytes, type=OPAQUE]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
} |
|||
|
|||
/** |
|||
* bad: singleResource, operation="R" - only read |
|||
* WriteReplace {"id":"/3/0/9","value":90} |
|||
* {"result":"METHOD_NOT_ALLOWED"} |
|||
*/ |
|||
@Test |
|||
public void testWriteReplaceValueSingleResourceR_ById_Result_CHANGED() throws Exception { |
|||
String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; |
|||
Integer expectedValue = 90; |
|||
String actualResult = sendRPCWriteObjectById("WriteReplace", expectedPath, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText()); |
|||
} |
|||
|
|||
/** |
|||
* ids |
|||
* WriteUpdate {"id":"/3/0","value":{"14":"+5","15":"Kiyv/Europe"}} |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testWriteUpdateValueSingleResourceById_Result_CHANGED() throws Exception { |
|||
String expectedPath = objectInstanceIdVer_3; |
|||
String expectedValue14 = "+5"; |
|||
String expectedValue15 = "Kiyv/Europe"; |
|||
String expectedValue = "{\"" + RESOURCE_ID_14 + "\":\"" + expectedValue14 + "\",\"" + RESOURCE_ID_15 + "\":\"" + expectedValue15 + "\"}"; |
|||
String actualResult = sendRPCWriteObjectById("WriteUpdate", expectedPath, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedPath14 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; |
|||
String expectedPath15 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_15; |
|||
actualResult = sendRPCReadById(expectedPath14); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
String actualValues = rpcActualResult.get("value").asText(); |
|||
String expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=" + expectedValue14 + ", type=STRING]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
actualResult = sendRPCReadById(expectedPath15); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
actualValues = rpcActualResult.get("value").asText(); |
|||
expected = "LwM2mSingleResource [id=" + RESOURCE_ID_15 + ", value=" + expectedValue15 + ", type=STRING]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
} |
|||
|
|||
/** |
|||
* id |
|||
* WriteUpdate {"id": "/19_1.1/0","value": {"0":{"0":"00ad456756", "25":"25ad456756"}}} |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testWriteUpdateValueMultipleResourceById_Result_CHANGED() throws Exception { |
|||
String expectedPath = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0; |
|||
int resourceInstanceId0 = 0; |
|||
int resourceInstanceId25 = 25; |
|||
String expectedValue0 = "00ad45675600"; |
|||
String expectedValue25 = "25ad45675600cdef"; |
|||
String expectedValue = "{\"" + RESOURCE_ID_0 + "\":{\"" + resourceInstanceId0 + "\":\"" + expectedValue0 + "\", \"" + resourceInstanceId25 + "\":\"" + expectedValue25 + "\"}}"; |
|||
String actualResult = sendRPCWriteObjectById("WriteUpdate", expectedPath, expectedValue); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedPath0 = expectedPath + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId0; |
|||
String expectedPath25 =expectedPath + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId25; |
|||
actualResult = sendRPCReadById(expectedPath0); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
String actualValues = rpcActualResult.get("value").asText(); |
|||
String expected = "LwM2mResourceInstance [id=" + resourceInstanceId0 + ", value=" + expectedValue0.length()/2 + "Bytes, type=OPAQUE]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
actualResult = sendRPCReadById(expectedPath25); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
actualValues = rpcActualResult.get("value").asText(); |
|||
expected = "LwM2mResourceInstance [id=" + resourceInstanceId25 + ", value=" + expectedValue25.length()/2 + "Bytes, type=OPAQUE]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
} |
|||
|
|||
/** |
|||
* ResourceInstance + KeySingleResource + IdSingleResource |
|||
* WriteComposite {"nodes":{"/19/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}} |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testWriteCompositeValueSingleResourceResourceInstanceByIdKey_Result_CHANGED() throws Exception { |
|||
int resourceInstanceId2 = 2; |
|||
String expectedPath19_1_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + resourceInstanceId2; |
|||
String expectedValue19_1_0_2 = "00001234"; |
|||
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; |
|||
String expectedValue3_0_14 = "+04"; |
|||
String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_15; |
|||
String expectedValue3_0_15 = "Kiyv/Europe"; |
|||
String nodes = "{\"" + expectedPath19_1_0_2 + "\":\"" + expectedValue19_1_0_2 + "\", \"" + expectedKey3_0_14 + |
|||
"\":\"" + expectedValue3_0_14 + "\", \"" + expectedPath3_0_15 + "\":\"" + expectedValue3_0_15 + "\"}"; |
|||
String actualResult = sendCompositeRPC(nodes); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); |
|||
actualResult = sendRPCReadById(expectedPath19_1_0_2); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
String actualValues = rpcActualResult.get("value").asText(); |
|||
String expected = "LwM2mResourceInstance [id=" + resourceInstanceId2 + ", value=" + expectedValue19_1_0_2.length()/2 + "Bytes, type=OPAQUE]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
actualResult = sendRPCReadByKey(expectedKey3_0_14); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
actualValues = rpcActualResult.get("value").asText(); |
|||
expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=" + expectedValue3_0_14 + ", type=STRING]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
actualResult = sendRPCReadById(expectedPath3_0_15); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
actualValues = rpcActualResult.get("value").asText(); |
|||
expected = "LwM2mSingleResource [id=" + RESOURCE_ID_15 + ", value=" + expectedValue3_0_15 + ", type=STRING]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
} |
|||
|
|||
/** |
|||
* multipleResource == error |
|||
* bad - cannot be used for value Json, only primitive: SingleResource, ResourceInstance (for Json: WriteUpdate, WriteReplace) |
|||
* WriteComposite {"nodes":{"/19/0/0":{"0":"abcd5678", "10":"abcd5678"}}} |
|||
*/ |
|||
@Test |
|||
public void testWriteCompositeValueSingleMultipleResourceByIdKey_Result_BAD_REQUEST_WriteComposite_operation_for_SingleResources_or_and_ResourceInstance() throws Exception { |
|||
String nodes = "{\"/19/0/0\":{\"0\":\"abcd5678\", \"10\":\"abcd5678\"}}"; |
|||
String actualResult = sendCompositeRPC(nodes); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String actualValues = rpcActualResult.get("error").asText(); |
|||
String expectedNodes = nodes.replaceAll("\"", "").replaceAll(":", "="); |
|||
String expected = String.format("nodes: %s is not validate value. " + |
|||
"The WriteComposite operation is only used for SingleResources or/and ResourceInstance.", expectedNodes); |
|||
assertEquals(expected, actualValues); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* update_resourceInstances&update_singleResource |
|||
* new ResourceInstance if Resource is Multiple & Resource Single |
|||
* - WriteReplace {"id":"/19_1.2/1/0","value":{"2":ddff12"}} |
|||
* - WriteReplace {"key":"UtfOffset","value":"+04"} |
|||
* - WriteReplace {"id":"/3/0/15","value":"Kiyv/Europe"} |
|||
* WriteComposite {"nodes":{"/19_1.1/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}}} |
|||
* {"result":"CHANGED"} |
|||
*/ |
|||
@Test |
|||
public void testWriteCompositeCreateResourceInstanceUpdateSingleResourceByIdKey_Result_CHANGED() throws Exception { |
|||
String expectedPath19_1_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_2; |
|||
String expectedValue19_1_0_2 = "00001234"; |
|||
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; |
|||
String expectedValue3_0_14 = "+04"; |
|||
String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_15; |
|||
String expectedValue3_0_15 = "Kiyv/Europe"; |
|||
String nodes = "{\"" + expectedPath19_1_0_2 + "\":\"" + expectedValue19_1_0_2 + "\", \"" + expectedKey3_0_14 + |
|||
"\":\"" + expectedValue3_0_14 + "\", \"" + expectedPath3_0_15 + "\":\"" + expectedValue3_0_15 + "\"}"; |
|||
String actualResult = sendCompositeRPC(nodes); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); |
|||
actualResult = sendRPCReadById(expectedPath19_1_0_2); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
String actualValues = rpcActualResult.get("value").asText(); |
|||
String expected = "LwM2mResourceInstance [id=" + RESOURCE_INSTANCE_ID_2 + ", value=" + expectedValue19_1_0_2.length()/2 + "Bytes, type=OPAQUE]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
actualResult = sendRPCReadByKey(expectedKey3_0_14); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
actualValues = rpcActualResult.get("value").asText(); |
|||
expected = "LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=" + expectedValue3_0_14 + ", type=STRING]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
actualResult = sendRPCReadById(expectedPath3_0_15); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
actualValues = rpcActualResult.get("value").asText(); |
|||
expected = "LwM2mSingleResource [id=" + RESOURCE_ID_15 + ", value=" + expectedValue3_0_15 + ", type=STRING]"; |
|||
assertTrue(actualValues.contains(expected)); |
|||
} |
|||
|
|||
/** |
|||
* composite_not created_new_instance... |
|||
* new ObjectInstance if Object is Multiple - bad |
|||
* - WriteReplace {"id":"/19_1.2/2/0","value":{"2":ddff12"}} |
|||
* - WriteReplace {"key":"UtfOffset","value":"+04"} |
|||
* - WriteReplace {"id":"/3/0/15","value":"Kiyv/Europe"} |
|||
* WriteComposite {"nodes":{"/19_1.1/1/0/2":"00001234", "UtfOffset":"+04", "/3/0/15":"Kiyv/Europe"}}} |
|||
* {"result":"BAD_REQUEST","error":"object instance /19/2 not found"} |
|||
*/ |
|||
@Test |
|||
public void testWriteCompositeCreateObjectInstanceUpdateSingleResourceByIdKey_Result_BAD_REQUEST() throws Exception { |
|||
String expectedPath19_1_2_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_2 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_2; |
|||
String expectedValue19_1_0_2 = "00001234"; |
|||
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; |
|||
String expectedValue3_0_14 = "+04"; |
|||
String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_15; |
|||
String expectedValue3_0_15 = "Kiyv/Europe"; |
|||
String nodes = "{\"" + expectedPath19_1_2_2 + "\":\"" + expectedValue19_1_0_2 + "\", \"" + expectedKey3_0_14 + |
|||
"\":\"" + expectedValue3_0_14 + "\", \"" + expectedPath3_0_15 + "\":\"" + expectedValue3_0_15 + "\"}"; |
|||
String actualResult = sendCompositeRPC(nodes); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); |
|||
String expectedObjectId = pathIdVerToObjectId((String) expectedPath19_1_2_2); |
|||
LwM2mPath expectedPathId = new LwM2mPath(expectedObjectId); |
|||
String expected = "object instance " + "/" + expectedPathId.getObjectId() + "/" + expectedPathId.getObjectInstanceId() + " not found"; |
|||
String actual = rpcActualResult.get("error").asText(); |
|||
assertTrue(actual.equals(expected)); |
|||
} |
|||
|
|||
private String sendRPCWriteStringById(String method, String path, String value) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + path + "\", \"value\": \"" + value + "\" }}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
private String sendRPCWriteObjectById(String method, String path, Object value) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + path + "\", \"value\": " + value + " }}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
private String sendRPCReadById(String id) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + id + "\"}}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
private String sendRPCWriteByKey(String method, String key, String value) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"key\": \"" + key + "\", \"value\": \"" + value + "\" }}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
private String sendRPCReadByKey(String key) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"key\": \"" + key + "\"}}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
|
|||
private String sendCompositeRPC(String nodes) throws Exception { |
|||
String setRpcRequest = "{\"method\": \"WriteComposite\", \"params\": {\"nodes\":" + nodes + "}}"; |
|||
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.security; |
|||
|
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; |
|||
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; |
|||
|
|||
import java.io.IOException; |
|||
import java.io.InputStream; |
|||
import java.security.GeneralSecurityException; |
|||
import java.security.KeyStore; |
|||
import java.security.PrivateKey; |
|||
import java.security.PublicKey; |
|||
import java.security.cert.X509Certificate; |
|||
|
|||
@DaoSqlTest |
|||
public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
|||
|
|||
protected final String CREDENTIALS_PATH = "lwm2m/credentials/"; // client public key or id used for PSK
|
|||
// Get keys PSK
|
|||
protected final String CLIENT_PSK_IDENTITY = "SOME_PSK_ID"; // client public key or id used for PSK
|
|||
protected final String CLIENT_PSK_KEY = "73656372657450534b73656372657450"; // client private/secret key used for PSK
|
|||
|
|||
// Server
|
|||
protected static final String SERVER_JKS_FOR_TEST = "lwm2mserver"; |
|||
protected static final String SERVER_STORE_PWD = "server_ks_password"; |
|||
protected static final String SERVER_CERT_ALIAS = "server"; |
|||
protected final X509Certificate serverX509Cert; // server certificate signed by rootCA
|
|||
protected final PublicKey serverPublicKeyFromCert; // server public key used for RPK
|
|||
|
|||
// Client
|
|||
protected LwM2MTestClient client; |
|||
protected static final String CLIENT_ENDPOINT_NO_SEC = "LwNoSec00000000"; |
|||
protected static final String CLIENT_ENDPOINT_PSK = "LwPsk00000000"; |
|||
protected static final String CLIENT_ENDPOINT_RPK = "LwRpk00000000"; |
|||
protected static final String CLIENT_ENDPOINT_X509_TRUST = "LwX50900000000"; |
|||
protected static final String CLIENT_ENDPOINT_X509_TRUST_NO = "LwX509TrustNo"; |
|||
protected static final String CLIENT_JKS_FOR_TEST = "lwm2mclient"; |
|||
protected static final String CLIENT_STORE_PWD = "client_ks_password"; |
|||
protected static final String CLIENT_ALIAS_CERT_TRUST = "client_alias_00000000"; |
|||
protected static final String CLIENT_ALIAS_CERT_TRUST_NO = "client_alias_trust_no"; |
|||
|
|||
protected final X509Certificate clientX509CertTrust; // client certificate signed by intermediate, rootCA with a good CN ("host name")
|
|||
protected final PrivateKey clientPrivateKeyFromCertTrust; // client private key used for X509 and RPK
|
|||
protected final PublicKey clientPublicKeyFromCertTrust; // client public key used for RPK
|
|||
protected final X509Certificate clientX509CertTrustNo; // client certificate signed by intermediate, rootCA with a good CN ("host name")
|
|||
protected final PrivateKey clientPrivateKeyFromCertTrustNo; // client private key used for X509 and RPK
|
|||
protected final PublicKey clientPublicKeyFromCertTrustNo; // client public key used for RPK
|
|||
private final String[] RESOURCES_SECURITY = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"}; |
|||
|
|||
|
|||
private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials; |
|||
|
|||
|
|||
|
|||
public AbstractSecurityLwM2MIntegrationTest() { |
|||
// create client credentials
|
|||
setResources(this.RESOURCES_SECURITY); |
|||
try { |
|||
// Get certificates from key store
|
|||
char[] clientKeyStorePwd = CLIENT_STORE_PWD.toCharArray(); |
|||
KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); |
|||
try (InputStream clientKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream(CREDENTIALS_PATH + CLIENT_JKS_FOR_TEST + ".jks")) { |
|||
clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd); |
|||
} |
|||
// Trust
|
|||
clientPrivateKeyFromCertTrust = (PrivateKey) clientKeyStore.getKey(CLIENT_ALIAS_CERT_TRUST, clientKeyStorePwd); |
|||
clientX509CertTrust = (X509Certificate) clientKeyStore.getCertificate(CLIENT_ALIAS_CERT_TRUST); |
|||
clientPublicKeyFromCertTrust = clientX509CertTrust != null ? clientX509CertTrust.getPublicKey() : null; |
|||
// No trust
|
|||
clientPrivateKeyFromCertTrustNo = (PrivateKey) clientKeyStore.getKey(CLIENT_ALIAS_CERT_TRUST_NO, clientKeyStorePwd); |
|||
clientX509CertTrustNo = (X509Certificate) clientKeyStore.getCertificate(CLIENT_ALIAS_CERT_TRUST_NO); |
|||
clientPublicKeyFromCertTrustNo = clientX509CertTrustNo != null ? clientX509CertTrustNo.getPublicKey() : null; |
|||
|
|||
} catch (GeneralSecurityException | IOException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
|
|||
// create server credentials
|
|||
try { |
|||
// Get certificates from key store
|
|||
char[] serverKeyStorePwd = SERVER_STORE_PWD.toCharArray(); |
|||
KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); |
|||
try (InputStream serverKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream(CREDENTIALS_PATH + SERVER_JKS_FOR_TEST + ".jks")) { |
|||
serverKeyStore.load(serverKeyStoreFile, serverKeyStorePwd); |
|||
} |
|||
|
|||
serverX509Cert = (X509Certificate) serverKeyStore.getCertificate(SERVER_CERT_ALIAS); |
|||
serverPublicKeyFromCert = serverX509Cert.getPublicKey(); |
|||
} catch (GeneralSecurityException | IOException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
|
|||
defaultBootstrapCredentials = new LwM2MBootstrapClientCredentials(); |
|||
|
|||
NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential(); |
|||
|
|||
defaultBootstrapCredentials.setBootstrapServer(serverCredentials); |
|||
defaultBootstrapCredentials.setLwm2mServer(serverCredentials); |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* Copyright © 2016-2021 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.lwm2m.security.sql; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.Test; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; |
|||
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; |
|||
|
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY; |
|||
|
|||
@Slf4j |
|||
public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { |
|||
|
|||
@Test |
|||
public void testConnectAndObserveTelemetry() throws Exception { |
|||
NoSecClientCredential clientCredentials = createNoSecClientCredentials(CLIENT_ENDPOINT_NO_SEC); |
|||
super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, CLIENT_ENDPOINT_NO_SEC); |
|||
} |
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue