committed by
GitHub
224 changed files with 7247 additions and 2091 deletions
@ -0,0 +1,200 @@ |
|||
-- |
|||
-- 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), |
|||
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) RETURNS jsonb AS |
|||
$$ |
|||
|
|||
DECLARE |
|||
bootstrap_new jsonb; |
|||
bootstrap_in jsonb; |
|||
|
|||
BEGIN |
|||
bootstrap_in := transport_configuration_in::jsonb #> '{bootstrap}'; |
|||
bootstrap_new := json_build_array( |
|||
json_build_object('shortServerId', bootstrap_in::json #> '{bootstrapServer}' -> 'serverId', |
|||
'securityMode', bootstrap_in::json #> '{bootstrapServer}' ->> 'securityMode', |
|||
'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', bootstrap_in::json #> '{lwm2mServer}' ->> 'securityMode', |
|||
'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,81 @@ |
|||
/** |
|||
* 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)); |
|||
} |
|||
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; |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,70 @@ |
|||
/** |
|||
* 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 String HOST = "localhost"; |
|||
public static final String HOST_BS = "localhost"; |
|||
public static final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT)); |
|||
public static final String ENDPOINT_SECURITY = "deviceAEndpoint"; |
|||
public static final String SECURE_URI = "coaps://localhost:" + SECURE_PORT; |
|||
|
|||
public static final int PORT = 5685; |
|||
public static final int PORT_BS = 5687; |
|||
public static final int SHORT_SERVER_ID = 123; |
|||
public static final int SHORT_SERVER_ID_BS = 111; |
|||
|
|||
public static final Security SECURITY = noSec("coap://localhost:" + 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 objectId_0 = 0; |
|||
public static final int objectInstanceId_0 = 0; |
|||
public static final int objectInstanceId_1 = 1; |
|||
public static final int objectInstanceId_2 = 2; |
|||
public static final int objectInstanceId_12 = 12; |
|||
public static final int resourceId_0 = 0; |
|||
public static final int resourceId_1 = 1; |
|||
public static final int resourceId_2 = 2; |
|||
public static final int resourceId_3 = 3; |
|||
public static final int resourceId_4 = 4; |
|||
public static final int resourceId_8 = 8; |
|||
public static final int resourceId_9 = 9; |
|||
public static final int resourceId_11 = 11; |
|||
public static final int resourceId_14 = 14; |
|||
public static final int resourceId_15= 15; |
|||
public static final int resourceInstanceId_2 = 2; |
|||
|
|||
public static final String resourceIdName_3_9 = "batteryLevel"; |
|||
public static final String resourceIdName_3_14 = "UtfOffset"; |
|||
public static final String resourceIdName_19_0_0 = "dataRead"; |
|||
public static final String resourceIdName_19_1_0 = "dataWrite"; |
|||
|
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* 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.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
@Slf4j |
|||
@Data |
|||
public class LwM2MLocationParams { |
|||
|
|||
private Float latitude; |
|||
private Float longitude; |
|||
private Float scaleFactor = 1.0F; |
|||
private final String locationPos = "50.4501:30.5234"; |
|||
private final Float locationScaleFactor = 1.0F; |
|||
|
|||
|
|||
protected void getPos() { |
|||
this.latitude = null; |
|||
this.longitude = null; |
|||
int c = locationPos.indexOf(':'); |
|||
this.latitude = Float.valueOf(locationPos.substring(0, c)); |
|||
this.longitude = Float.valueOf(locationPos.substring(c + 1)); |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,231 @@ |
|||
/** |
|||
* 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 |
|||
* }, |
|||
*/ |
|||
// private String data = "InNlcnZpY2VJZCI6Ik1ldGVyIiwNCiJzZXJ2aWNlRGF0YSI6ew0KImN1cnJlbnRSZWFkaW5nIjoiNDYuMyIsDQoic2lnbmFsU3RyZW5ndGgiOjE2LA0KImRhaWx5QWN0aXZpdHlUaW1lIjo1NzA2DQo=";
|
|||
// private byte[] data;
|
|||
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), 5000, 5000, TimeUnit.MILLISECONDS);
|
|||
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) { |
|||
// log.warn("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
|
|||
try { |
|||
switch (resourceId) { |
|||
case 0: |
|||
// log.warn("Read on Location resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId);
|
|||
ReadResponse response = ReadResponse.success(resourceId, getData(), ResourceModel.Type.OPAQUE); |
|||
// log.warn("Response [{}]", response);
|
|||
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 ? "base64" : this.dataFormat;
|
|||
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()); |
|||
} |
|||
|
|||
// fireResourcesChange(resourceId);
|
|||
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() { |
|||
// this.data.put(23, new byte[]{0,0, 2,3});
|
|||
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,150 @@ |
|||
/** |
|||
* 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); |
|||
// log.info("Move to North [{}]/[{}]", getLatitude(), getLongitude());
|
|||
break; |
|||
case 'a': |
|||
moveLongitude(-1.0f); |
|||
// log.info("Move to East [{}]/[{}]", getLatitude(), getLongitude());
|
|||
break; |
|||
case 's': |
|||
moveLatitude(-1.0f); |
|||
// log.info("Move to South [{}]/[{}]", getLatitude(), getLongitude());
|
|||
break; |
|||
case 'd': |
|||
moveLongitude(1.0f); |
|||
// log.info("Move to West [{}]/[{}]", getLatitude(), getLongitude());
|
|||
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,76 @@ |
|||
/** |
|||
* 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 = new String[]{"3.xml", "5.xml", "9.xml"}; |
|||
|
|||
public AbstractOtaLwM2MIntegrationTest() { |
|||
setResources(this.resources); |
|||
} |
|||
|
|||
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,206 @@ |
|||
/** |
|||
* 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.controller.TbTestWebSocketClient; |
|||
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.ScheduledExecutorService; |
|||
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.objectId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_0_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_1_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_9; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; |
|||
|
|||
@DaoSqlTest |
|||
public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
|||
|
|||
protected String RPC_TRANSPORT_CONFIGURATION; |
|||
|
|||
protected ScheduledExecutorService executor; |
|||
protected TbTestWebSocketClient wsClient; |
|||
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 String objectIdVer_50 = "/50"; |
|||
protected String objectIdVer_3303; |
|||
protected static AtomicInteger endpointSequence = new AtomicInteger(); |
|||
|
|||
public AbstractRpcLwM2MIntegrationTest(){ |
|||
setResources(resources); |
|||
} |
|||
|
|||
@Before |
|||
public void beforeTest() throws Exception { |
|||
setEndpoint("deviceEndpointRpc" + endpointSequence.incrementAndGet()); |
|||
init(); |
|||
createNewClient (SECURITY, COAP_CONFIG, true); |
|||
|
|||
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(objectId_0).version; |
|||
if ("1.0".equals(ver_Id_0)) { |
|||
objectIdVer_0 = "/" + objectId_0; |
|||
} |
|||
else { |
|||
objectIdVer_0 = "/" + objectId_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 + "/" + objectInstanceId_0 + "/" + resourceId_9 + "\": \"" + resourceIdName_3_9 + "\",\n" + |
|||
" \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_14 + "\": \"" + resourceIdName_3_14 + "\",\n" + |
|||
" \"" + objectIdVer_19 + "/" + objectInstanceId_0 + "/" + resourceId_0 + "\": \"" + resourceIdName_19_0_0 + "\",\n" + |
|||
" \"" + objectIdVer_19 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "\": \"" + resourceIdName_19_1_0 + "\"\n" + |
|||
" },\n" + |
|||
" \"observe\": [\n" + |
|||
" \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_9 + "\",\n" + |
|||
" \"" + objectIdVer_19 + "/" + objectInstanceId_0 + "/" + resourceId_0 + "\"\n" + |
|||
" ],\n" + |
|||
" \"attribute\": [\n" + |
|||
" ],\n" + |
|||
" \"telemetry\": [\n" + |
|||
" \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_9 + "\",\n" + |
|||
" \"" + objectIdVer_3 + "/" + objectInstanceId_0 + "/" + resourceId_14 + "\",\n" + |
|||
" \"" + objectIdVer_19 + "/" + objectInstanceId_0 + "/" + resourceId_0 + "\",\n" + |
|||
" \"" + objectIdVer_19 + "/" + objectInstanceId_1 + "/" + resourceId_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.objectInstanceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_12; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_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 + "/" + objectInstanceId_12; |
|||
String expectedValue = "{\"" + resourceId_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 + "/" + objectInstanceId_0; |
|||
String expectedValue = "{\"" + resourceId_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 " + objectInstanceId_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 + "/" + objectInstanceId_1; |
|||
String expectedValue = "{\"" + resourceId_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 + "/" + objectInstanceId_1; |
|||
String expectedValue = "{\"" + resourceId_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 = objectIdVer_50+ "/" + objectInstanceId_1; |
|||
String expectedValue = "{\"" + resourceId_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.objectInstanceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_12; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9; |
|||
|
|||
|
|||
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 + "/" + objectInstanceId_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 + "/" + objectInstanceId_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/9"} |
|||
* {"result":"METHOD_NOT_ALLOWED"} |
|||
*/ |
|||
@Test |
|||
public void testDeleteResourceByIdKey_Result_METHOD_NOT_ALLOWED() throws Exception { |
|||
String expectedPath = objectIdVer_3 + "/" + objectInstanceId_0 + resourceId_9; |
|||
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.objectInstanceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_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 + "/" + objectInstanceId_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 + "/" + objectInstanceId_0 + "/" + resourceId_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.objectInstanceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_2; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_3; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_4; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_8; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_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 + "/" + resourceId_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 + "/" + resourceId_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 + "/" + resourceId_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 + "/" + resourceId_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 + "/" + resourceId_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 + "/" + resourceId_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 + "/" + resourceId_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 = objectIdVer_50 + "/" + objectInstanceId_0 + "/" + resourceId_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 + "/" + objectInstanceId_0 + "/" + resourceId_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.objectInstanceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_3; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_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 + "/" + resourceId_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 + "/" + objectInstanceId_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 + "/" + objectInstanceId_0 + "/" + resourceId_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 + "/" + resourceId_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 + "/" + resourceId_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 + "/" + resourceId_0; |
|||
String expectedId_9 = objectInstanceIdVer_3 + "/" + resourceId_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 + "/" + resourceId_0; |
|||
String expectedId_3 = objectInstanceIdVer_5 + "/" + resourceId_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.objectInstanceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_0_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_19_1_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_9; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_1; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_11; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_2; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_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 +"/" + resourceId_11 ; |
|||
String actualResult = sendRPCById(expectedIdVer); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String expected = "LwM2mMultipleResource [id=" + resourceId_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 +"/" + resourceId_14 ; |
|||
String actualResult = sendRPCById(expectedIdVer); |
|||
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); |
|||
String expected = "LwM2mSingleResource [id=" + resourceId_14 + ", value="; |
|||
assertTrue(rpcActualResult.get("value").asText().contains(expected)); |
|||
} |
|||
|
|||
/** |
|||
* Read {"key":"UtfOffset"} |
|||
*/ |
|||
@Test |
|||
public void testReadSingleResourceByKey_Result_CONTENT_Value_IsLwM2mSingleResource() throws Exception { |
|||
String expectedKey = resourceIdName_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=" + resourceId_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 + "/" + resourceId_1; |
|||
String expectedIdVer3_0_11 = objectInstanceIdVer_3 + "/" + resourceId_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 + "/" + resourceId_1 + "=LwM2mSingleResource [id=" + resourceId_1 + ", value="; |
|||
String expected3_0_11 = objectInstanceId_3 + "/" + resourceId_11 + "=LwM2mMultipleResource [id=" + resourceId_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 + "/" + resourceId_1; |
|||
String expectedIdVer1_0_2 = objectInstanceIdVer_1 + "/" + resourceId_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 + "/" + resourceId_1 + "=LwM2mSingleResource [id=" + resourceId_1 + ", value="; |
|||
String expected1_0_2 = objectInstanceId_1 + "/" + resourceId_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 = resourceIdName_3_9; |
|||
String expectedKey3_0_14 = resourceIdName_3_14; |
|||
String expectedKey19_0_0 = resourceIdName_19_0_0; |
|||
String expectedKey19_1_0 = resourceIdName_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 + "/" + resourceId_9 + "=LwM2mSingleResource [id=" + resourceId_9 + ", value="; |
|||
String expected3_0_14 = objectInstanceId_3 + "/" + resourceId_14 + "=LwM2mSingleResource [id=" + resourceId_14 + ", value="; |
|||
String expected19_0_0 = objectId_19 + "/" + objectInstanceId_0 + "/" + resourceId_0 + "=null"; |
|||
String expected19_1_0 = objectId_19 + "/" + objectInstanceId_1 + "/" + resourceId_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,63 @@ |
|||
/** |
|||
* 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.objectInstanceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_2; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_3; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_4; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_8; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9; |
|||
|
|||
|
|||
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 + "/" + resourceId_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,335 @@ |
|||
/** |
|||
* 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.BINARY_APP_DATA_CONTAINER; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_1; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.objectInstanceId_2; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceIdName_3_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_0; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_14; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_15; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceId_9; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resourceInstanceId_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 + "/" + resourceId_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=" + resourceId_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 = resourceIdName_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=" + resourceId_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 + "/" + objectInstanceId_0 + "/" + resourceId_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 + "/" + resourceId_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 = "{\"" + resourceId_14 + "\":\"" + expectedValue14 + "\",\"" + resourceId_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 + "/" + resourceId_14; |
|||
String expectedPath15 = objectInstanceIdVer_3 + "/" + resourceId_15; |
|||
actualResult = sendRPCReadById(expectedPath14); |
|||
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); |
|||
String actualValues = rpcActualResult.get("value").asText(); |
|||
String expected = "LwM2mSingleResource [id=" + resourceId_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=" + resourceId_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 + "/" + objectInstanceId_0; |
|||
int resourceInstanceId0 = 0; |
|||
int resourceInstanceId25 = 25; |
|||
String expectedValue0 = "00ad45675600"; |
|||
String expectedValue25 = "25ad45675600cdef"; |
|||
String expectedValue = "{\"" + resourceId_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 + "/" + resourceId_0 + "/" + resourceInstanceId0; |
|||
String expectedPath25 =expectedPath + "/" + resourceId_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 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "/" + resourceInstanceId2; |
|||
String expectedValue19_1_0_2 = "00001234"; |
|||
String expectedKey3_0_14 = resourceIdName_3_14; |
|||
String expectedValue3_0_14 = "+04"; |
|||
String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + resourceId_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=" + resourceId_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=" + resourceId_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 + "/" + objectInstanceId_1 + "/" + resourceId_0 + "/" + resourceInstanceId_2; |
|||
String expectedValue19_1_0_2 = "00001234"; |
|||
String expectedKey3_0_14 = resourceIdName_3_14; |
|||
String expectedValue3_0_14 = "+04"; |
|||
String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + resourceId_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=" + resourceInstanceId_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=" + resourceId_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=" + resourceId_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 + "/" + objectInstanceId_2 + "/" + resourceId_0 + "/" + resourceInstanceId_2; |
|||
String expectedValue19_1_0_2 = "00001234"; |
|||
String expectedKey3_0_14 = resourceIdName_3_14; |
|||
String expectedValue3_0_14 = "+04"; |
|||
String expectedPath3_0_15 = objectInstanceIdVer_3 + "/" + resourceId_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,176 @@ |
|||
/** |
|||
* 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.eclipse.leshan.core.util.Hex; |
|||
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.math.BigInteger; |
|||
import java.security.AlgorithmParameters; |
|||
import java.security.GeneralSecurityException; |
|||
import java.security.KeyFactory; |
|||
import java.security.KeyStore; |
|||
import java.security.PrivateKey; |
|||
import java.security.PublicKey; |
|||
import java.security.cert.Certificate; |
|||
import java.security.cert.X509Certificate; |
|||
import java.security.spec.ECGenParameterSpec; |
|||
import java.security.spec.ECParameterSpec; |
|||
import java.security.spec.ECPoint; |
|||
import java.security.spec.ECPrivateKeySpec; |
|||
import java.security.spec.ECPublicKeySpec; |
|||
import java.security.spec.KeySpec; |
|||
|
|||
@DaoSqlTest |
|||
public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
|||
|
|||
protected final String pskIdentity; // client public key or id used for PSK
|
|||
protected final String pskKey; // client private/secret key used for PSK
|
|||
protected final PublicKey clientPublicKey; // client public key used for RPK
|
|||
protected final PrivateKey clientPrivateKey; // client private key used for RPK
|
|||
protected final PublicKey serverPublicKey; // server public key used for RPK
|
|||
protected final PrivateKey serverPrivateKey; // server private key used for RPK
|
|||
|
|||
// client private key used for X509
|
|||
protected final PrivateKey clientPrivateKeyFromCert; |
|||
// server private key used for X509
|
|||
protected final PrivateKey serverPrivateKeyFromCert; |
|||
// client certificate signed by rootCA with a good CN (CN start by leshan_integration_test)
|
|||
protected final X509Certificate clientX509Cert; |
|||
// client certificate signed by rootCA but with bad CN (CN does not start by leshan_integration_test)
|
|||
protected final X509Certificate clientX509CertWithBadCN; |
|||
// client certificate self-signed with a good CN (CN start by leshan_integration_test)
|
|||
protected final X509Certificate clientX509CertSelfSigned; |
|||
// client certificate signed by another CA (not rootCA) with a good CN (CN start by leshan_integration_test)
|
|||
protected final X509Certificate clientX509CertNotTrusted; |
|||
// server certificate signed by rootCA
|
|||
protected final X509Certificate serverX509Cert; |
|||
// self-signed server certificate
|
|||
protected final X509Certificate serverX509CertSelfSigned; |
|||
// rootCA used by the server
|
|||
protected final X509Certificate rootCAX509Cert; |
|||
// certificates trustedby the server (should contain rootCA)
|
|||
protected final Certificate[] trustedCertificates = new Certificate[1]; |
|||
|
|||
protected static final String ENDPOINT = "deviceAEndpoint"; |
|||
|
|||
protected LwM2MTestClient client; |
|||
|
|||
private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials; |
|||
|
|||
private final String[] resources = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"}; |
|||
|
|||
public AbstractSecurityLwM2MIntegrationTest() { |
|||
// create client credentials
|
|||
setResources(this.resources); |
|||
setEndpoint(ENDPOINT); |
|||
try { |
|||
// Get keys PSK
|
|||
this.pskIdentity = "SOME_PSK_ID"; |
|||
this.pskKey = "73656372657450534b73656372657450"; |
|||
|
|||
// Get point values
|
|||
byte[] publicX = Hex |
|||
.decodeHex("89c048261979208666f2bfb188be1968fc9021c416ce12828c06f4e314c167b5".toCharArray()); |
|||
byte[] publicY = Hex |
|||
.decodeHex("cbf1eb7587f08e01688d9ada4be859137ca49f79394bad9179326b3090967b68".toCharArray()); |
|||
byte[] privateS = Hex |
|||
.decodeHex("e67b68d2aaeb6550f19d98cade3ad62b39532e02e6b422e1f7ea189dabaea5d2".toCharArray()); |
|||
|
|||
// Get Elliptic Curve Parameter spec for secp256r1
|
|||
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); |
|||
algoParameters.init(new ECGenParameterSpec("secp256r1")); |
|||
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); |
|||
|
|||
// Create key specs
|
|||
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), |
|||
parameterSpec); |
|||
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); |
|||
|
|||
// Get keys RPK
|
|||
clientPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); |
|||
clientPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); |
|||
|
|||
// Get certificates from key store
|
|||
char[] clientKeyStorePwd = "client".toCharArray(); |
|||
KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); |
|||
try (InputStream clientKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/clientKeyStore.jks")) { |
|||
clientKeyStore.load(clientKeyStoreFile, clientKeyStorePwd); |
|||
} |
|||
|
|||
clientPrivateKeyFromCert = (PrivateKey) clientKeyStore.getKey("client", clientKeyStorePwd); |
|||
clientX509Cert = (X509Certificate) clientKeyStore.getCertificate("client"); |
|||
clientX509CertWithBadCN = (X509Certificate) clientKeyStore.getCertificate("client_bad_cn"); |
|||
clientX509CertSelfSigned = (X509Certificate) clientKeyStore.getCertificate("client_self_signed"); |
|||
clientX509CertNotTrusted = (X509Certificate) clientKeyStore.getCertificate("client_not_trusted"); |
|||
} catch (GeneralSecurityException | IOException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
|
|||
// create server credentials
|
|||
try { |
|||
// Get point values
|
|||
byte[] publicX = Hex |
|||
.decodeHex("fcc28728c123b155be410fc1c0651da374fc6ebe7f96606e90d927d188894a73".toCharArray()); |
|||
byte[] publicY = Hex |
|||
.decodeHex("d2ffaa73957d76984633fc1cc54d0b763ca0559a9dff9706e9f4557dacc3f52a".toCharArray()); |
|||
byte[] privateS = Hex |
|||
.decodeHex("1dae121ba406802ef07c193c1ee4df91115aabd79c1ed7f4c0ef7ef6a5449400".toCharArray()); |
|||
|
|||
// Get Elliptic Curve Parameter spec for secp256r1
|
|||
AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); |
|||
algoParameters.init(new ECGenParameterSpec("secp256r1")); |
|||
ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); |
|||
|
|||
// Create key specs
|
|||
KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), |
|||
parameterSpec); |
|||
KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); |
|||
|
|||
// Get keys
|
|||
serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); |
|||
serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); |
|||
|
|||
// Get certificates from key store
|
|||
char[] serverKeyStorePwd = "server".toCharArray(); |
|||
KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); |
|||
try (InputStream serverKeyStoreFile = this.getClass().getClassLoader().getResourceAsStream("lwm2m/credentials/serverKeyStore.jks")) { |
|||
serverKeyStore.load(serverKeyStoreFile, serverKeyStorePwd); |
|||
} |
|||
|
|||
serverPrivateKeyFromCert = (PrivateKey) serverKeyStore.getKey("server", serverKeyStorePwd); |
|||
rootCAX509Cert = (X509Certificate) serverKeyStore.getCertificate("rootCA"); |
|||
serverX509Cert = (X509Certificate) serverKeyStore.getCertificate("server"); |
|||
serverX509CertSelfSigned = (X509Certificate) serverKeyStore.getCertificate("server_self_signed"); |
|||
trustedCertificates[0] = serverX509Cert; |
|||
} 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(ENDPOINT); |
|||
super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, ENDPOINT); |
|||
} |
|||
|
|||
} |
|||
@ -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.transport.lwm2m.security.sql; |
|||
|
|||
import org.eclipse.leshan.client.object.Security; |
|||
import org.junit.Test; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential; |
|||
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; |
|||
|
|||
import static org.eclipse.leshan.client.object.Security.x509; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI; |
|||
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID; |
|||
|
|||
public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { |
|||
|
|||
@Test |
|||
public void testConnectAndObserveTelemetry() throws Exception { |
|||
X509ClientCredential credentials = new X509ClientCredential(); |
|||
credentials.setEndpoint(ENDPOINT); |
|||
Security security = x509(SECURE_URI, |
|||
SHORT_SERVER_ID, |
|||
clientX509Cert.getEncoded(), |
|||
clientPrivateKeyFromCert.getEncoded(), |
|||
serverX509Cert.getEncoded()); |
|||
super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,144 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!-- |
|||
FILE INFORMATION |
|||
|
|||
OMA Permanent Document |
|||
File: OMA-SUP-LwM2M_BinaryAppDataContainer-V1_0_1-20190221-A |
|||
Type: xml |
|||
|
|||
Public Reachable Information |
|||
Path: http://www.openmobilealliance.org/tech/profiles |
|||
Name: LwM2M_BinaryAppDataContainer-v1_0_1.xml |
|||
|
|||
NORMATIVE INFORMATION |
|||
|
|||
Information about this file can be found in the latest revision of |
|||
|
|||
OMA-TS-LWM2M_BinaryAppDataContainer-V1_0_1 |
|||
|
|||
This is available at http://www.openmobilealliance.org/ |
|||
|
|||
Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues |
|||
|
|||
CHANGE HISTORY |
|||
|
|||
15062018 Status changed to Approved by DM, Doc Ref # OMA-DM&SE-2018-0061-INP_LWM2M_APPDATA_V1_0_ERP_for_final_Approval |
|||
21022019 Status changed to Approved by IPSO, Doc Ref # OMA-IPSO-2019-0025-INP_LwM2M_Object_App_Data_Container_1_0_1_for_Final_Approval |
|||
|
|||
LEGAL DISCLAIMER |
|||
|
|||
Copyright 2019 Open Mobile Alliance. |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions |
|||
are met: |
|||
|
|||
1. Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
2. Redistributions in binary form must reproduce the above copyright |
|||
notice, this list of conditions and the following disclaimer in the |
|||
documentation and/or other materials provided with the distribution. |
|||
3. Neither the name of the copyright holder nor the names of its |
|||
contributors may be used to endorse or promote products derived |
|||
from this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
|||
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
POSSIBILITY OF SUCH DAMAGE. |
|||
|
|||
The above license is used as a license under copyright only. Please |
|||
reference the OMA IPR Policy for patent licensing terms: |
|||
https://www.omaspecworks.org/about/intellectual-property-rights/ |
|||
|
|||
--> |
|||
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd"> |
|||
<Object ObjectType="MODefinition"> |
|||
<Name>BinaryAppDataContainer</Name> |
|||
<Description1><![CDATA[This LwM2M Objects provides the application service data related to a LwM2M Server, eg. Water meter data. |
|||
There are several methods to create instance to indicate the message direction based on the negotiation between Application and LwM2M. The Client and Server should negotiate the instance(s) used to exchange the data. For example: |
|||
- Using a single instance for both directions communication, from Client to Server and from Server to Client. |
|||
- Using an instance for communication from Client to Server and another one for communication from Server to Client |
|||
- Using several instances |
|||
]]></Description1> |
|||
<ObjectID>19</ObjectID> |
|||
<ObjectURN>urn:oma:lwm2m:oma:19:1.1</ObjectURN> |
|||
<LWM2MVersion>1.1</LWM2MVersion> |
|||
<ObjectVersion>1.1</ObjectVersion> |
|||
<MultipleInstances>Multiple</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Resources> |
|||
<Item ID="0"><Name>Data</Name> |
|||
<Operations>RW</Operations> |
|||
<MultipleInstances>Multiple</MultipleInstances> |
|||
<Mandatory>Mandatory</Mandatory> |
|||
<Type>Opaque</Type> |
|||
<RangeEnumeration /> |
|||
<Units /> |
|||
<Description><![CDATA[Indicates the application data content.]]></Description> |
|||
</Item> |
|||
<Item ID="1"><Name>Data Priority</Name> |
|||
<Operations>RW</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Integer</Type> |
|||
<RangeEnumeration>1 bytes</RangeEnumeration> |
|||
<Units /> |
|||
<Description><![CDATA[Indicates the Application data priority: |
|||
0:Immediate |
|||
1:BestEffort |
|||
2:Latest |
|||
3-100: Reserved for future use. |
|||
101-254: Proprietary mode.]]></Description> |
|||
</Item> |
|||
<Item ID="2"><Name>Data Creation Time</Name> |
|||
<Operations>RW</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Time</Type> |
|||
<RangeEnumeration /> |
|||
<Units /> |
|||
<Description><![CDATA[Indicates the Data instance creation timestamp.]]></Description> |
|||
</Item> |
|||
<Item ID="3"><Name>Data Description</Name> |
|||
<Operations>RW</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>String</Type> |
|||
<RangeEnumeration>32 bytes</RangeEnumeration> |
|||
<Units /> |
|||
<Description><![CDATA[Indicates the data description. |
|||
e.g. "meter reading".]]></Description> |
|||
</Item> |
|||
<Item ID="4"><Name>Data Format</Name> |
|||
<Operations>RW</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>String</Type> |
|||
<RangeEnumeration>32 bytes</RangeEnumeration> |
|||
<Units /> |
|||
<Description><![CDATA[Indicates the format of the Application Data. |
|||
e.g. YG-Meter-Water-Reading |
|||
UTF8-string |
|||
]]></Description> |
|||
</Item> |
|||
<Item ID="5"><Name>App ID</Name> |
|||
<Operations>RW</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Integer</Type> |
|||
<RangeEnumeration>2 bytes</RangeEnumeration> |
|||
<Units /> |
|||
<Description><![CDATA[Indicates the destination Application ID.]]></Description> |
|||
</Item></Resources> |
|||
<Description2><![CDATA[]]></Description2> |
|||
</Object> |
|||
</LWM2M> |
|||
@ -0,0 +1,103 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!-- |
|||
|
|||
Copyright © 2016-2018 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://openmobilealliance.org/tech/profiles/LWM2M.xsd"> |
|||
<Object ObjectType="MODefinition"> |
|||
<Name>Temperature</Name> |
|||
<Description1>This IPSO object should be used with a temperature sensor to report a temperature measurement. It also provides resources for minimum/maximum measured values and the minimum/maximum range that can be measured by the temperature sensor. An example measurement unit is degrees Celsius.</Description1> |
|||
<ObjectID>3303</ObjectID> |
|||
<ObjectURN>urn:oma:lwm2m:ext:3303</ObjectURN> |
|||
<LWM2MVersion>1.0</LWM2MVersion> |
|||
<ObjectVersion>1.0</ObjectVersion> |
|||
<MultipleInstances>Multiple</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Resources> |
|||
<Item ID="5700"> |
|||
<Name>Sensor Value</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Mandatory</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>Last or Current Measured Value from the Sensor</Description> |
|||
</Item> |
|||
<Item ID="5601"> |
|||
<Name>Min Measured Value</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>The minimum value measured by the sensor since power ON or reset</Description> |
|||
</Item> |
|||
<Item ID="5602"> |
|||
<Name>Max Measured Value</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>The maximum value measured by the sensor since power ON or reset</Description> |
|||
</Item> |
|||
<Item ID="5603"> |
|||
<Name>Min Range Value</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>The minimum value that can be measured by the sensor</Description> |
|||
</Item> |
|||
<Item ID="5604"> |
|||
<Name>Max Range Value</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>The maximum value that can be measured by the sensor</Description> |
|||
</Item> |
|||
<Item ID="5701"> |
|||
<Name>Sensor Units</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>String</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>Measurement Units Definition.</Description> |
|||
</Item> |
|||
<Item ID="5605"> |
|||
<Name>Reset Min and Max Measured Values</Name> |
|||
<Operations>E</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type></Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description>Reset the Min and Max Measured Values to Current Value</Description> |
|||
</Item> |
|||
</Resources> |
|||
<Description2></Description2> |
|||
</Object> |
|||
</LWM2M> |
|||
@ -0,0 +1,143 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
|
|||
<!-- |
|||
FILE INFORMATION |
|||
|
|||
OMA Permanent Document |
|||
File: OMA-SUP-XML_6-V1_0-20201110-A.xml |
|||
http://www.openmobilealliance.org/release/ObjLwM2M_Location/ |
|||
|
|||
OMNA LwM2M Registry |
|||
Path: https://github.com/OpenMobileAlliance/lwm2m-registry |
|||
Name: 6.xml |
|||
|
|||
NORMATIVE INFORMATION |
|||
|
|||
Information about this file can be found in the latest revision of |
|||
|
|||
OMA-TS-LightweightM2M-V1_0_2 |
|||
OMA-TS-LightweightM2M_Core-V1_1_1 |
|||
|
|||
This is available at http://www.openmobilealliance.org/release/LightweightM2M/ |
|||
|
|||
Send comments to https://github.com/OpenMobileAlliance/OMA_LwM2M_for_Developers/issues |
|||
|
|||
LEGAL DISCLAIMER |
|||
|
|||
Copyright 2020 Open Mobile Alliance. |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions |
|||
are met: |
|||
|
|||
1. Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
2. Redistributions in binary form must reproduce the above copyright |
|||
notice, this list of conditions and the following disclaimer in the |
|||
documentation and/or other materials provided with the distribution. |
|||
3. Neither the name of the copyright holder nor the names of its |
|||
contributors may be used to endorse or promote products derived |
|||
from this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
|||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
|||
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
|||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
|||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
|||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
POSSIBILITY OF SUCH DAMAGE. |
|||
|
|||
The above license is used as a license under copyright only. Please |
|||
reference the OMA IPR Policy for patent licensing terms: |
|||
https://www.omaspecworks.org/about/intellectual-property-rights/ |
|||
|
|||
--> |
|||
|
|||
<LWM2M xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.openmobilealliance.org/tech/profiles/LWM2M.xsd"> |
|||
<Object ObjectType="MODefinition"> |
|||
<Name>Location</Name> |
|||
<Description1><![CDATA[This LwM2M Object provides a range of location telemetry related information which can be queried by the LwM2M Server.]]></Description1> |
|||
<ObjectID>6</ObjectID> |
|||
<ObjectURN>urn:oma:lwm2m:oma:6</ObjectURN> |
|||
<LWM2MVersion>1.0</LWM2MVersion> |
|||
<ObjectVersion>1.0</ObjectVersion> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Resources> |
|||
<Item ID="0"> |
|||
<Name>Latitude</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Mandatory</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units>lat</Units> |
|||
<Description><![CDATA[The decimal notation of latitude, e.g. -43.5723 [World Geodetic System 1984].]]></Description> |
|||
</Item> |
|||
<Item ID="1"> |
|||
<Name>Longitude</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Mandatory</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units>lon</Units> |
|||
<Description><![CDATA[The decimal notation of longitude, e.g. 153.21760 [World Geodetic System 1984].]]></Description> |
|||
</Item> |
|||
<Item ID="2"> |
|||
<Name>Altitude</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units>m</Units> |
|||
<Description><![CDATA[The decimal notation of altitude in meters above sea level.]]></Description> |
|||
</Item> |
|||
<Item ID="3"> |
|||
<Name>Radius</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units>m</Units> |
|||
<Description><![CDATA[The value in this resource indicates the radius of a circular area in meters. The circular area is used to describe uncertainty about a point for coordinates in a two-dimensional coordinate reference systems (CRS). The center point of a circular area is specified by using the Latitude and the Longitude Resources.]]></Description> |
|||
</Item> |
|||
<Item ID="4"> |
|||
<Name>Velocity</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Opaque</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description><![CDATA[The velocity of the LwM2M Client, as defined in [3GPP-TS_23.032].]]></Description> |
|||
</Item> |
|||
<Item ID="5"> |
|||
<Name>Timestamp</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Mandatory</Mandatory> |
|||
<Type>Time</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units></Units> |
|||
<Description><![CDATA[The timestamp of when the location measurement was performed.]]></Description> |
|||
</Item> |
|||
<Item ID="6"><Name>Speed</Name> |
|||
<Operations>R</Operations> |
|||
<MultipleInstances>Single</MultipleInstances> |
|||
<Mandatory>Optional</Mandatory> |
|||
<Type>Float</Type> |
|||
<RangeEnumeration></RangeEnumeration> |
|||
<Units>m/s</Units> |
|||
<Description><![CDATA[Speed is the time rate of change in position of a LwM2M Client without regard for direction: the scalar component of velocity.]]></Description> |
|||
</Item></Resources> |
|||
<Description2></Description2> |
|||
</Object> |
|||
</LWM2M> |
|||
@ -0,0 +1,40 @@ |
|||
/** |
|||
* 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.common.data.device.credentials.lwm2m; |
|||
|
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import org.apache.commons.codec.DecoderException; |
|||
import org.apache.commons.codec.binary.Hex; |
|||
|
|||
@Getter |
|||
@Setter |
|||
public class PSKClientCredential extends AbstractLwM2MClientSecurityCredential { |
|||
private String identity; |
|||
|
|||
@Override |
|||
public LwM2MSecurityMode getSecurityConfigClientMode() { |
|||
return LwM2MSecurityMode.PSK; |
|||
} |
|||
|
|||
@Override |
|||
public byte[] getDecoded() throws IllegalArgumentException, DecoderException { |
|||
if (securityInBytes == null) { |
|||
securityInBytes = Hex.decodeHex(key.toLowerCase().toCharArray()); |
|||
} |
|||
return securityInBytes; |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/** |
|||
* 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.common.data.device.profile.lwm2m.bootstrap; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import com.fasterxml.jackson.annotation.JsonSubTypes; |
|||
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; |
|||
|
|||
@JsonTypeInfo( |
|||
use = JsonTypeInfo.Id.NAME, |
|||
property = "securityMode") |
|||
@JsonSubTypes({ |
|||
@JsonSubTypes.Type(value = NoSecLwM2MBootstrapServerCredential.class, name = "NO_SEC"), |
|||
@JsonSubTypes.Type(value = PSKLwM2MBootstrapServerCredential.class, name = "PSK"), |
|||
@JsonSubTypes.Type(value = RPKLwM2MBootstrapServerCredential.class, name = "RPK"), |
|||
@JsonSubTypes.Type(value = X509LwM2MBootstrapServerCredential.class, name = "X509") |
|||
}) |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public interface LwM2MBootstrapServerCredential { |
|||
@JsonIgnore |
|||
LwM2MSecurityMode getSecurityMode(); |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
/** |
|||
* 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.common.data.device.profile.lwm2m.bootstrap; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Data |
|||
public class LwM2MBootstrapServersConfiguration { |
|||
|
|||
List<LwM2MBootstrapServerCredential> bootstrap; |
|||
|
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
/** |
|||
* 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.common.data.device.profile.lwm2m.bootstrap; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
@ApiModel |
|||
@Data |
|||
public class LwM2MServerSecurityConfig { |
|||
|
|||
@ApiModelProperty(position = 1, value = "Server short Id. Used as link to associate server Object Instance. This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. " + |
|||
"This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'. " + |
|||
"The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.", example = "123", readOnly = true) |
|||
protected Integer shortServerId = 123; |
|||
/** Security -> ObjectId = 0 'LWM2M Security' */ |
|||
@ApiModelProperty(position = 2, value = "Is Bootstrap Server or Lwm2m Server. " + |
|||
"The LwM2M Client MAY be configured to use one or more LwM2M Server Account(s). " + |
|||
"The LwM2M Client MUST have at most one LwM2M Bootstrap-Server Account. " + |
|||
"(*) The LwM2M client MUST have at least one LwM2M server account after completing the boot sequence specified.", example = "true or false", readOnly = true) |
|||
protected boolean bootstrapServerIs = false; |
|||
@ApiModelProperty(position = 3, value = "Host for 'No Security' mode", example = "0.0.0.0", readOnly = true) |
|||
protected String host; |
|||
@ApiModelProperty(position = 4, value = "Port for Lwm2m Server: 'No Security' mode: Lwm2m Server or Bootstrap Server", example = "'5685' or '5687'", readOnly = true) |
|||
protected Integer port; |
|||
@ApiModelProperty(position = 7, value = "Client Hold Off Time. The number of seconds to wait before initiating a Client Initiated Bootstrap once the LwM2M Client has determined it should initiate this bootstrap mode. (This information is relevant for use with a Bootstrap-Server only.)", example = "1", readOnly = true) |
|||
protected Integer clientHoldOffTime = 1; |
|||
@ApiModelProperty(position = 8, value = "Server Public Key for 'Security' mode (DTLS): RPK or X509. Format: base64 encoded", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" + |
|||
"+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", readOnly = true) |
|||
protected String serverPublicKey; |
|||
@ApiModelProperty(position = 9, value = "Bootstrap Server Account Timeout (If the value is set to 0, or if this resource is not instantiated, the Bootstrap-Server Account lifetime is infinite.)", example = "0", readOnly = true) |
|||
Integer bootstrapServerAccountTimeout = 0; |
|||
|
|||
/** Config -> ObjectId = 1 'LwM2M Server' */ |
|||
@ApiModelProperty(position = 10, value = "Specify the lifetime of the registration in seconds.", example = "300", readOnly = true) |
|||
private Integer lifetime = 300; |
|||
@ApiModelProperty(position = 11, value = "The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation. " + |
|||
"If this Resource doesn’t exist, the default value is 0.", example = "1", readOnly = true) |
|||
private Integer defaultMinPeriod = 1; |
|||
/** ResourceID=6 'Notification Storing When Disabled or Offline' */ |
|||
@ApiModelProperty(position = 12, value = "If true, the LwM2M Client stores “Notify” operations to the LwM2M Server while the LwM2M Server account is disabled or the LwM2M Client is offline. After the LwM2M Server account is enabled or the LwM2M Client is online, the LwM2M Client reports the stored “Notify” operations to the Server. " + |
|||
"If false, the LwM2M Client discards all the “Notify” operations or temporarily disables the Observe function while the LwM2M Server is disabled or the LwM2M Client is offline. " + |
|||
"The default value is true.", example = "true", readOnly = true) |
|||
private boolean notifIfDisabled = true; |
|||
@ApiModelProperty(position = 14, value = "This Resource defines the transport binding configured for the LwM2M Client. " + |
|||
"If the LwM2M Client supports the binding specified in this Resource, the LwM2M Client MUST use that transport for the Current Binding Mode.", example = "U", readOnly = true) |
|||
private String binding = "U"; |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* 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.common.data.device.profile.lwm2m.bootstrap; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
@ApiModel |
|||
@Data |
|||
public class LwM2MServerSecurityConfigDefault extends LwM2MServerSecurityConfig { |
|||
@ApiModelProperty(position = 5, value = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", readOnly = true) |
|||
protected String securityHost; |
|||
@ApiModelProperty(position = 6, value = "Port for 'Security' mode (DTLS): Lwm2m Server or Bootstrap Server", example = "5686 or 5688", readOnly = true) |
|||
protected Integer securityPort; |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* 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.common.data.device.profile.lwm2m.bootstrap; |
|||
|
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; |
|||
|
|||
public class NoSecLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { |
|||
@Override |
|||
public LwM2MSecurityMode getSecurityMode() { |
|||
return LwM2MSecurityMode.NO_SEC; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* 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.common.data.device.profile.lwm2m.bootstrap; |
|||
|
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; |
|||
|
|||
public class PSKLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { |
|||
@Override |
|||
public LwM2MSecurityMode getSecurityMode() { |
|||
return LwM2MSecurityMode.PSK; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* 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.common.data.device.profile.lwm2m.bootstrap; |
|||
|
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; |
|||
|
|||
public class RPKLwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { |
|||
@Override |
|||
public LwM2MSecurityMode getSecurityMode() { |
|||
return LwM2MSecurityMode.RPK; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* 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.common.data.device.profile.lwm2m.bootstrap; |
|||
|
|||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; |
|||
|
|||
public class X509LwM2MBootstrapServerCredential extends AbstractLwM2MBootstrapServerCredential { |
|||
@Override |
|||
public LwM2MSecurityMode getSecurityMode() { |
|||
return LwM2MSecurityMode.X509; |
|||
} |
|||
} |
|||
@ -1,44 +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.common.data.lwm2m; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
@ApiModel |
|||
@Data |
|||
public class ServerSecurityConfig { |
|||
@ApiModelProperty(position = 1, value = "Is Bootstrap Server", example = "true", readOnly = true) |
|||
boolean bootstrapServerIs = true; |
|||
@ApiModelProperty(position = 2, value = "Host for 'No Security' mode", example = "0.0.0.0", readOnly = true) |
|||
String host; |
|||
@ApiModelProperty(position = 3, value = "Port for 'No Security' mode", example = "5687", readOnly = true) |
|||
Integer port; |
|||
@ApiModelProperty(position = 4, value = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", readOnly = true) |
|||
String securityHost; |
|||
@ApiModelProperty(position = 5, value = "Port for 'Security' mode (DTLS)", example = "5688", readOnly = true) |
|||
Integer securityPort; |
|||
@ApiModelProperty(position = 5, value = "Server short Id", example = "111", readOnly = true) |
|||
Integer serverId = 111; |
|||
@ApiModelProperty(position = 7, value = "Client Hold Off Time", example = "1", readOnly = true) |
|||
Integer clientHoldOffTime = 1; |
|||
@ApiModelProperty(position = 8, value = "Server Public Key (base64 encoded)", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" + |
|||
"+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", readOnly = true) |
|||
String serverPublicKey; |
|||
@ApiModelProperty(position = 9, value = "Bootstrap Server Account Timeout", example = "0", readOnly = true) |
|||
Integer bootstrapServerAccountTimeout = 0; |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* 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.queue.util; |
|||
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
|||
|
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
|
|||
@Retention(RetentionPolicy.RUNTIME) |
|||
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.lwm2m.enabled}'=='true')) && '${transport.lwm2m.bootstrap.enabled:false}'=='true'") |
|||
public @interface TbLwM2mBootstrapTransportComponent { |
|||
} |
|||
@ -1,27 +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.transport.lwm2m.bootstrap.secure; |
|||
|
|||
import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; |
|||
import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStoreAdapter; |
|||
|
|||
public class LwM2MInMemoryBootstrapConfigurationAdapter extends BootstrapConfigurationStoreAdapter { |
|||
|
|||
public LwM2MInMemoryBootstrapConfigurationAdapter(BootstrapConfigStore store) { |
|||
super(store); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,284 @@ |
|||
/** |
|||
* 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.bootstrap.store; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.core.Link; |
|||
import org.eclipse.leshan.core.node.LwM2mObject; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.eclipse.leshan.core.request.BootstrapDeleteRequest; |
|||
import org.eclipse.leshan.core.request.BootstrapDiscoverRequest; |
|||
import org.eclipse.leshan.core.request.BootstrapDownlinkRequest; |
|||
import org.eclipse.leshan.core.request.BootstrapReadRequest; |
|||
import org.eclipse.leshan.core.request.ContentFormat; |
|||
import org.eclipse.leshan.core.response.BootstrapDiscoverResponse; |
|||
import org.eclipse.leshan.core.response.BootstrapReadResponse; |
|||
import org.eclipse.leshan.core.response.LwM2mResponse; |
|||
import org.eclipse.leshan.server.bootstrap.BootstrapConfig; |
|||
import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; |
|||
import org.eclipse.leshan.server.bootstrap.BootstrapSession; |
|||
import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider; |
|||
import org.eclipse.leshan.server.bootstrap.BootstrapUtil; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.HashMap; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.TreeMap; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.eclipse.leshan.server.bootstrap.BootstrapUtil.toWriteRequest; |
|||
|
|||
@Slf4j |
|||
public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvider { |
|||
|
|||
private BootstrapConfigStore store; |
|||
|
|||
private Map<Integer, String> supportedObjects; |
|||
|
|||
/** |
|||
* Map<serverId, InstanceId> |
|||
*/ |
|||
protected Map<Integer, Integer> securityInstances; |
|||
protected Map<Integer, Integer> serverInstances; |
|||
protected Integer bootstrapServerIdOld; |
|||
protected Integer bootstrapServerIdNew; |
|||
|
|||
public LwM2MBootstrapConfigStoreTaskProvider(BootstrapConfigStore store) { |
|||
this.store = store; |
|||
} |
|||
|
|||
@Override |
|||
public Tasks getTasks(BootstrapSession session, List<LwM2mResponse> previousResponse) { |
|||
BootstrapConfig config = store.get(session.getEndpoint(), session.getIdentity(), session); |
|||
if (config == null) { |
|||
return null; |
|||
} |
|||
if (previousResponse == null && shouldStartWithDiscover(config)) { |
|||
Tasks tasks = new Tasks(); |
|||
tasks.requestsToSend = new ArrayList<>(1); |
|||
tasks.requestsToSend.add(new BootstrapDiscoverRequest()); |
|||
tasks.last = false; |
|||
return tasks; |
|||
} else { |
|||
Tasks tasks = new Tasks(); |
|||
if (this.supportedObjects == null) { |
|||
initSupportedObjectsDefault(); |
|||
} |
|||
// add supportedObjects
|
|||
tasks.supportedObjects = this.supportedObjects; |
|||
// handle bootstrap discover response
|
|||
if (previousResponse != null) { |
|||
if (previousResponse.get(0) instanceof BootstrapDiscoverResponse) { |
|||
BootstrapDiscoverResponse discoverResponse = (BootstrapDiscoverResponse) previousResponse.get(0); |
|||
if (discoverResponse.isSuccess()) { |
|||
this.initAfterBootstrapDiscover(discoverResponse); |
|||
findSecurityInstanceId(discoverResponse.getObjectLinks()); |
|||
} else { |
|||
log.warn( |
|||
"Bootstrap Discover return error {} : to continue bootstrap session without autoIdForSecurityObject mode. {}", |
|||
discoverResponse, session); |
|||
} |
|||
if (this.securityInstances.get(0) == null) { |
|||
log.error( |
|||
"Unable to find bootstrap server instance in Security Object (0) in response {}: unable to continue bootstrap session with autoIdForSecurityObject mode. {}", |
|||
discoverResponse, session); |
|||
return null; |
|||
} |
|||
tasks.requestsToSend = new ArrayList<>(1); |
|||
tasks.requestsToSend.add(new BootstrapReadRequest("/1")); |
|||
tasks.last = false; |
|||
return tasks; |
|||
} |
|||
BootstrapReadResponse readResponse = (BootstrapReadResponse) previousResponse.get(0); |
|||
if (readResponse.isSuccess()) { |
|||
findServerInstanceId(readResponse); |
|||
} else { |
|||
log.warn( |
|||
"Bootstrap ReadResponse return error {} : to continue bootstrap session without find Server Instance Id. {}", |
|||
readResponse, session); |
|||
} |
|||
// create requests from config
|
|||
tasks.requestsToSend = this.toRequests(config, |
|||
config.contentFormat != null ? config.contentFormat : session.getContentFormat()); |
|||
} else { |
|||
// create requests from config
|
|||
tasks.requestsToSend = BootstrapUtil.toRequests(config, |
|||
config.contentFormat != null ? config.contentFormat : session.getContentFormat()); |
|||
} |
|||
return tasks; |
|||
} |
|||
} |
|||
|
|||
protected boolean shouldStartWithDiscover(BootstrapConfig config) { |
|||
return config.autoIdForSecurityObject; |
|||
} |
|||
|
|||
protected void findSecurityInstanceId(Link[] objectLinks) { |
|||
log.info("Object after discover: [{}]", objectLinks); |
|||
this.securityInstances = new HashMap<>(); |
|||
for (Link link : objectLinks) { |
|||
if (link.getUrl().startsWith("/0/")) { |
|||
try { |
|||
LwM2mPath path = new LwM2mPath(link.getUrl()); |
|||
if (path.isObjectInstance()) { |
|||
if (link.getAttributes().containsKey("ssid")) { |
|||
int serverId = Integer.parseInt(link.getAttributes().get("ssid")); |
|||
if (!this.securityInstances.containsKey(serverId)) { |
|||
this.securityInstances.put(serverId, path.getObjectInstanceId()); |
|||
} else { |
|||
log.error("Invalid lwm2mSecurityInstance by [{}]", path.getObjectInstanceId()); |
|||
} |
|||
this.securityInstances.put(Integer.valueOf(link.getAttributes().get("ssid")), path.getObjectInstanceId()); |
|||
} else { |
|||
if (!this.securityInstances.containsKey(0)) { |
|||
this.securityInstances.put(0, path.getObjectInstanceId()); |
|||
} else { |
|||
log.error("Invalid bootstrapSecurityInstance by [{}]", path.getObjectInstanceId()); |
|||
} |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
// ignore if this is not a LWM2M path
|
|||
log.error("Invalid LwM2MPath starting by \"/0/\""); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected void findServerInstanceId(BootstrapReadResponse readResponse) { |
|||
this.serverInstances = new HashMap<>(); |
|||
((LwM2mObject) readResponse.getContent()).getInstances().values().forEach(instance -> { |
|||
serverInstances.put(((Long) instance.getResource(0).getValue()).intValue(), instance.getId()); |
|||
}); |
|||
if (this.securityInstances != null && this.securityInstances.size() > 0 && this.serverInstances != null && this.serverInstances.size() > 0) { |
|||
this.findBootstrapServerId(); |
|||
} |
|||
} |
|||
|
|||
protected void findBootstrapServerId() { |
|||
Map<Integer, Integer> filteredMap = this.serverInstances.entrySet() |
|||
.stream().filter(x -> !this.securityInstances.containsKey(x.getKey())) |
|||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |
|||
if (filteredMap.size() > 0) { |
|||
this.bootstrapServerIdOld = filteredMap.keySet().stream().findFirst().get(); |
|||
} |
|||
} |
|||
|
|||
public BootstrapConfigStore getStore() { |
|||
return this.store; |
|||
} |
|||
|
|||
private void initAfterBootstrapDiscover(BootstrapDiscoverResponse response) { |
|||
Link[] links = response.getObjectLinks(); |
|||
Arrays.stream(links).forEach(link -> { |
|||
LwM2mPath path = new LwM2mPath(link.getUrl()); |
|||
if (!path.isRoot() && path.getObjectId() < 3) { |
|||
if (path.isObject()) { |
|||
String ver = link.getAttributes().get("ver") != null ? link.getAttributes().get("ver") : "1.0"; |
|||
this.supportedObjects.put(path.getObjectId(), ver); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
|
|||
public List<BootstrapDownlinkRequest<? extends LwM2mResponse>> toRequests(BootstrapConfig bootstrapConfig, |
|||
ContentFormat contentFormat) { |
|||
List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requests = new ArrayList<>(); |
|||
Set<String> pathsDelete = new HashSet<>(); |
|||
List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requestsWrite = new ArrayList<>(); |
|||
boolean isBsServer = false; |
|||
boolean isLwServer = false; |
|||
/** Map<serverId, InstanceId> */ |
|||
Map<Integer, Integer> instances = new HashMap<>(); |
|||
// handle security
|
|||
int id = 0; |
|||
for (BootstrapConfig.ServerSecurity security : new TreeMap<>(bootstrapConfig.security).values()) { |
|||
if (security.bootstrapServer) { |
|||
requestsWrite.add(toWriteRequest(this.securityInstances.get(0), security, contentFormat)); |
|||
isBsServer = true; |
|||
this.bootstrapServerIdNew = security.serverId; |
|||
instances.put(security.serverId, this.securityInstances.get(0)); |
|||
} else { |
|||
if (id == this.securityInstances.get(0)) { |
|||
id++; |
|||
} |
|||
requestsWrite.add(toWriteRequest(id, security, contentFormat)); |
|||
instances.put(security.serverId, id); |
|||
isLwServer = true; |
|||
if (!isBsServer && this.securityInstances.containsKey(security.serverId) && id != this.securityInstances.get(security.serverId)) { |
|||
pathsDelete.add("/0/" + this.securityInstances.get(security.serverId)); |
|||
} |
|||
/** |
|||
* If there is an instance in the serverInstances with serverId which we replace in the securityInstances |
|||
*/ |
|||
// find serverId in securityInstances by id (instance)
|
|||
Integer serverIdOld = null; |
|||
for (Map.Entry<Integer, Integer> entry : this.securityInstances.entrySet()) { |
|||
if (entry.getValue().equals(id)) { |
|||
serverIdOld = entry.getKey(); |
|||
} |
|||
} |
|||
if (!isBsServer && serverIdOld != null && this.serverInstances.containsKey(serverIdOld)) { |
|||
pathsDelete.add("/1/" + this.serverInstances.get(serverIdOld)); |
|||
} |
|||
id++; |
|||
} |
|||
} |
|||
// handle server
|
|||
for (Map.Entry<Integer, BootstrapConfig.ServerConfig> server : bootstrapConfig.servers.entrySet()) { |
|||
int securityInstanceId = instances.get(server.getValue().shortId); |
|||
requestsWrite.add(toWriteRequest(securityInstanceId, server.getValue(), contentFormat)); |
|||
if (!isBsServer) { |
|||
/** Delete instance if bootstrapServerIdNew not equals bootstrapServerIdOld or securityInstanceBsIdNew not equals serverInstanceBsIdOld */ |
|||
if (this.bootstrapServerIdNew != null && server.getValue().shortId == this.bootstrapServerIdNew && |
|||
(this.bootstrapServerIdNew != this.bootstrapServerIdOld || securityInstanceId != this.serverInstances.get(this.bootstrapServerIdOld))) { |
|||
pathsDelete.add("/1/" + this.serverInstances.get(this.bootstrapServerIdOld)); |
|||
/** Delete instance if serverIdNew is present in serverInstances and securityInstanceIdOld by serverIdNew not equals serverInstanceIdOld */ |
|||
} else if (this.serverInstances.containsKey(server.getValue().shortId) && securityInstanceId != this.serverInstances.get(server.getValue().shortId)) { |
|||
pathsDelete.add("/1/" + this.serverInstances.get(server.getValue().shortId)); |
|||
} |
|||
} |
|||
} |
|||
// handle acl
|
|||
for (Map.Entry<Integer, BootstrapConfig.ACLConfig> acl : bootstrapConfig.acls.entrySet()) { |
|||
requestsWrite.add(toWriteRequest(acl.getKey(), acl.getValue(), contentFormat)); |
|||
} |
|||
// handle delete
|
|||
if (isBsServer && isLwServer) { |
|||
requests.add(new BootstrapDeleteRequest("/0")); |
|||
requests.add(new BootstrapDeleteRequest("/1")); |
|||
} else { |
|||
pathsDelete.forEach(pathDelete -> requests.add(new BootstrapDeleteRequest(pathDelete))); |
|||
} |
|||
// handle write
|
|||
if (requestsWrite.size() > 0) { |
|||
requests.addAll(requestsWrite); |
|||
} |
|||
return (requests); |
|||
} |
|||
|
|||
private void initSupportedObjectsDefault() { |
|||
this.supportedObjects = new HashMap<>(); |
|||
this.supportedObjects.put(0, "1.1"); |
|||
this.supportedObjects.put(1, "1.1"); |
|||
this.supportedObjects.put(2, "1.0"); |
|||
} |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
/** |
|||
* 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.bootstrap.store; |
|||
|
|||
import org.eclipse.leshan.server.bootstrap.BootstrapConfig; |
|||
import org.eclipse.leshan.server.bootstrap.ConfigurationChecker; |
|||
import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; |
|||
import java.util.Map; |
|||
|
|||
public class LwM2MConfigurationChecker extends ConfigurationChecker { |
|||
|
|||
@Override |
|||
public void verify(BootstrapConfig config) throws InvalidConfigurationException { |
|||
// check security configurations
|
|||
for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> e : config.security.entrySet()) { |
|||
BootstrapConfig.ServerSecurity sec = e.getValue(); |
|||
|
|||
// checks security config
|
|||
switch (sec.securityMode) { |
|||
case NO_SEC: |
|||
checkNoSec(sec); |
|||
break; |
|||
case PSK: |
|||
checkPSK(sec); |
|||
break; |
|||
case RPK: |
|||
checkRPK(sec); |
|||
break; |
|||
case X509: |
|||
checkX509(sec); |
|||
break; |
|||
case EST: |
|||
throw new InvalidConfigurationException("EST is not currently supported.", e); |
|||
} |
|||
|
|||
validateMandatoryField(sec); |
|||
} |
|||
|
|||
// does each server have a corresponding security entry?
|
|||
validateOneSecurityByServer(config); |
|||
} |
|||
|
|||
protected void validateOneSecurityByServer(BootstrapConfig config) throws InvalidConfigurationException { |
|||
for (Map.Entry<Integer, BootstrapConfig.ServerConfig> e : config.servers.entrySet()) { |
|||
BootstrapConfig.ServerConfig srvCfg = e.getValue(); |
|||
|
|||
// shortId checks
|
|||
if (srvCfg.shortId == 0) { |
|||
throw new InvalidConfigurationException("short ID must not be 0"); |
|||
} |
|||
|
|||
// look for security entry
|
|||
BootstrapConfig.ServerSecurity security = getSecurityEntry(config, srvCfg.shortId); |
|||
|
|||
if (security == null) { |
|||
throw new InvalidConfigurationException("no security entry for server instance: " + e.getKey()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected static BootstrapConfig.ServerSecurity getSecurityEntry(BootstrapConfig config, int shortId) { |
|||
for (Map.Entry<Integer, BootstrapConfig.ServerSecurity> es : config.security.entrySet()) { |
|||
if (es.getValue().serverId == shortId) { |
|||
return es.getValue(); |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue