Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Device claiming client #1392

Merged
merged 13 commits into from Oct 7, 2019
Merged

Device claiming client #1392

merged 13 commits into from Oct 7, 2019

Conversation

johanstokking
Copy link
Member

@johanstokking johanstokking commented Sep 26, 2019

Summary

Client for end device claiming and some necessary plumbing

References https://github.com/thethingsindustries/lorawan-stack/issues/1673
References #138

Changes

  • Add CLI commands for device claiming, including application authorization
  • Remove nil-checks in NS device registry
  • Change claim authentication code to string, also fixed for TR005 QR code parsing

Notes for Reviewers

@rvolosatovs let me know why we need(ed) those nil-checks in NS

Release Notes

  • Added support for claiming end devices through CLI. See ttn-lw-cli end-device claim --help for more information
  • Added support generating QR codes for claiming. See ttn-lw-cli end-device generate-qr --help for more information
  • Added support for authorizing device claiming on application level through CLI. See ttn-lw-cli application claim authorize --help for more information

@johanstokking johanstokking added c/network server This is related to the Network Server ui/cli This is related to ttn-lw-cli compat/api This could affect API compatibility labels Sep 26, 2019
@johanstokking johanstokking added this to the September 2019 milestone Sep 26, 2019
@johanstokking johanstokking self-assigned this Sep 26, 2019
@coveralls
Copy link

coveralls commented Sep 26, 2019

Coverage Status

Coverage increased (+0.03%) to 73.199% when pulling 03d2b58 on feature/end-device-claiming into ae469dc on master.

@@ -134,22 +134,6 @@ func validABPSessionKey(key *ttnpb.KeyEnvelope) bool {

// Set implements NsEndDeviceRegistryServer.
func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest) (*ttnpb.EndDevice, error) {
if ttnpb.HasAnyField(req.FieldMask.Paths, "session.dev_addr") && (req.EndDevice.Session == nil || req.EndDevice.Session.DevAddr.IsZero()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason why someone would want to set the session.dev_addr to zero value is to make the device model corrupted. I don't think we should allow this.
The set transaction should fail anyway due to invalid dev_addr, but we can(and should) just directly send a clear error to the user without even hitting the stores or doing anything else

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The field mask indicate what the caller knows about the entity being passed.

If session == nil, a field mask containing session.dev_addr is not wrong. It should be ineffective.

Sure, NS should avoid invalid state. And a session.dev_addr that is zero is invalid state, so that should be checked, but there's no need to return an error if session == nil.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you mean session == nil on the stored device. The solution is simple though - we should get the session if session.dev_addr is requested and do different checks in update and create functionality

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you mean session == nil on the stored device.

No, on the given end device. Please see revised change in 76ad7a7

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If session == nil, a field mask containing session.dev_addr is not wrong. It should be ineffective.

That is not true - if session == nil, but session.dev_addr is specified - the session.dev_addr on the stored device is deleted(assuming session.dev_addr is stored)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not true - if session == nil, but session.dev_addr is specified - the session.dev_addr on the stored device is deleted(assuming session.dev_addr is stored)

Ineffective was specifically for create. For update, again, the field mask specifies what the caller knows. I.e. the caller knows session.dev_addr and session == nil, so the entire session should be reset.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not how fieldmasks work, right?
If you specify a.b, a.c must not change.
From https://developers.google.com/protocol-buffers/docs/reference/java/com/google/protobuf/FieldMask:

A field mask in update operations specifies which fields of the
targeted resource are going to be updated. The API is required
to only change the values of the fields as specified in the mask
and leave the others untouched. If a resource is passed in to
describe the updated values, the API ignores the values of all
fields not covered by the mask.

Also, please note that the registry does not validate session.dev_addr set by the user (at least now) - that happens in this RPC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, then make it ineffective. session will also be part of the field mask anyway.

if ttnpb.HasAnyField(req.FieldMask.Paths, "session.dev_addr") && (req.EndDevice.Session == nil || req.EndDevice.Session.DevAddr.IsZero()) {
return nil, errInvalidFieldValue.WithAttributes("field", "session.dev_addr")
}
if ttnpb.HasAnyField(req.FieldMask.Paths, "session.keys.session_key_id") && (req.EndDevice.Session == nil || len(req.EndDevice.Session.SessionKeys.GetSessionKeyID()) == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDEM, same as DevAddr

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The length check, yes, but not the session existence check

if ttnpb.HasAnyField(req.FieldMask.Paths, "session.keys.session_key_id") && (req.EndDevice.Session == nil || len(req.EndDevice.Session.SessionKeys.GetSessionKeyID()) == 0) {
return nil, errInvalidFieldValue.WithAttributes("field", "session.keys.session_key_id")
}
if ttnpb.HasAnyField(req.FieldMask.Paths, "session.keys.f_nwk_s_int_key.key") && (req.EndDevice.Session == nil || req.EndDevice.Session.SessionKeys.GetFNwkSIntKey().GetKey().IsZero()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDEM, removing this field results in a corrupted session, which NS cannot use. We should not allow doing that - the user should just remove the session

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, will recover the zero-check

pkg/networkserver/grpc_deviceregistry_test.go Show resolved Hide resolved
pkg/networkserver/grpc_deviceregistry_test.go Outdated Show resolved Hide resolved
@johanstokking johanstokking force-pushed the feature/end-device-claiming branch 2 times, most recently from 409b59c to cc818b5 Compare September 30, 2019 08:21
@johanstokking johanstokking marked this pull request as ready for review September 30, 2019 08:24
@johanstokking johanstokking added the blocked This can't continue until another issue or pull request is done label Oct 2, 2019
@johanstokking johanstokking force-pushed the feature/end-device-claiming branch 2 times, most recently from f868ecc to 03d2b58 Compare October 2, 2019 12:14
@johanstokking
Copy link
Member Author

@rvolosatovs please review pkg/networkserver only

@rvolosatovs
Copy link
Contributor

rvolosatovs commented Oct 3, 2019

I think the session-related diff in NS grpc_deviceregistry.go should look something like this:

diff --git a/pkg/networkserver/grpc_deviceregistry.go b/pkg/networkserver/grpc_deviceregistry.go
index a79778834..894e2162d 100644
--- a/pkg/networkserver/grpc_deviceregistry.go
+++ b/pkg/networkserver/grpc_deviceregistry.go
@@ -128,28 +128,16 @@ func (ns *NetworkServer) Get(ctx context.Context, req *ttnpb.GetEndDeviceRequest
 	return dev, nil
 }
 
+func sessionKeyIsSet(key *ttnpb.KeyEnvelope) bool {
+	return key != nil && (len(key.EncryptedKey) > 0 || !key.Key.IsZero())
+}
+
 func validABPSessionKey(key *ttnpb.KeyEnvelope) bool {
 	return key != nil && key.KEKLabel == "" && !key.Key.IsZero()
 }
 
 // Set implements NsEndDeviceRegistryServer.
 func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest) (*ttnpb.EndDevice, error) {
-	if ttnpb.HasAnyField(req.FieldMask.Paths, "session.dev_addr") && (req.EndDevice.Session == nil || req.EndDevice.Session.DevAddr.IsZero()) {
-		return nil, errInvalidFieldValue.WithAttributes("field", "session.dev_addr")
-	}
-	if ttnpb.HasAnyField(req.FieldMask.Paths, "session.keys.session_key_id") && (req.EndDevice.Session == nil || len(req.EndDevice.Session.SessionKeys.GetSessionKeyID()) == 0) {
-		return nil, errInvalidFieldValue.WithAttributes("field", "session.keys.session_key_id")
-	}
-	if ttnpb.HasAnyField(req.FieldMask.Paths, "session.keys.f_nwk_s_int_key.key") && (req.EndDevice.Session == nil || req.EndDevice.Session.SessionKeys.GetFNwkSIntKey().GetKey().IsZero()) {
-		return nil, errInvalidFieldValue.WithAttributes("field", "session.keys.f_nwk_s_int_key.key")
-	}
-	if ttnpb.HasAnyField(req.FieldMask.Paths, "session.keys.s_nwk_s_int_key.key") && (req.EndDevice.Session == nil || req.EndDevice.Session.SessionKeys.GetSNwkSIntKey().GetKey().IsZero()) {
-		return nil, errInvalidFieldValue.WithAttributes("field", "session.keys.s_nwk_s_int_key.key")
-	}
-	if ttnpb.HasAnyField(req.FieldMask.Paths, "session.keys.nwk_s_enc_key.key") && (req.EndDevice.Session == nil || req.EndDevice.Session.SessionKeys.GetNwkSEncKey().GetKey().IsZero()) {
-		return nil, errInvalidFieldValue.WithAttributes("field", "session.keys.nwk_s_enc_key.key")
-	}
-
 	if ttnpb.HasAnyField(req.FieldMask.Paths, "frequency_plan_id") && req.EndDevice.FrequencyPlanID == "" {
 		return nil, errInvalidFieldValue.WithAttributes("field", "frequency_plan_id")
 	}
@@ -184,6 +172,14 @@ func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest
 			break
 		}
 	}
+	for _, p := range req.FieldMask.Paths {
+		if strings.HasPrefix(p, "session.") {
+			gets = append(gets,
+				"session",
+			)
+			break
+		}
+	}
 
 	var evt events.Event
 	var addDownlinkTask bool
@@ -204,7 +200,26 @@ func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest
 			); err != nil {
 				return nil, nil, errInvalidFieldMask.WithCause(err)
 			}
-			if ttnpb.HasAnyField(req.FieldMask.Paths, "session.dev_addr") {
+
+			if dev.Session != nil {
+				if !dev.Session.DevAddr.IsZero() && ttnpb.HasAnyField(sets, "session.dev_addr") && (req.EndDevice.Session == nil || req.EndDevice.Session.DevAddr.IsZero()) {
+					return nil, nil, errInvalidFieldValue.WithAttributes("field", "session.dev_addr")
+				}
+				if len(dev.Session.SessionKeyID) > 0 && ttnpb.HasAnyField(sets, "session.keys.session_key_id") && (req.EndDevice.Session == nil || len(req.EndDevice.Session.SessionKeys.GetSessionKeyID()) == 0) {
+					return nil, nil, errInvalidFieldValue.WithAttributes("field", "session.keys.session_key_id")
+				}
+				if sessionKeyIsSet(dev.Session.SessionKeys.GetFNwkSIntKey()) && ttnpb.HasAnyField(sets, "session.keys.f_nwk_s_int_key.key") && (req.EndDevice.Session == nil || req.EndDevice.Session.SessionKeys.GetFNwkSIntKey().GetKey().IsZero()) {
+					return nil, nil, errInvalidFieldValue.WithAttributes("field", "session.keys.f_nwk_s_int_key.key")
+				}
+				if sessionKeyIsSet(dev.Session.SessionKeys.GetSNwkSIntKey()) && ttnpb.HasAnyField(sets, "session.keys.s_nwk_s_int_key.key") && (req.EndDevice.Session == nil || req.EndDevice.Session.SessionKeys.GetSNwkSIntKey().GetKey().IsZero()) {
+					return nil, nil, errInvalidFieldValue.WithAttributes("field", "session.keys.s_nwk_s_int_key.key")
+				}
+				if sessionKeyIsSet(dev.Session.SessionKeys.GetNwkSEncKey()) && ttnpb.HasAnyField(sets, "session.keys.nwk_s_enc_key.key") && (req.EndDevice.Session == nil || req.EndDevice.Session.SessionKeys.GetNwkSEncKey().GetKey().IsZero()) {
+					return nil, nil, errInvalidFieldValue.WithAttributes("field", "session.keys.nwk_s_enc_key.key")
+				}
+			}
+
+			if ttnpb.HasAnyField(sets, "session.dev_addr") && req.EndDevice.Session != nil && !req.EndDevice.Session.DevAddr.IsZero() {
 				req.EndDevice.DevAddr = &req.EndDevice.Session.DevAddr
 				sets = append(sets, "ids.dev_addr")
 			}
@@ -333,7 +348,7 @@ func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest
 			)
 		}
 		if req.EndDevice.DevAddr != nil {
-			if !ttnpb.HasAnyField(req.FieldMask.Paths, "session.dev_addr") || !req.EndDevice.DevAddr.Equal(req.EndDevice.Session.DevAddr) {
+			if !ttnpb.HasAnyField(req.FieldMask.Paths, "session.dev_addr") || req.EndDevice.Session == nil || !req.EndDevice.DevAddr.Equal(req.EndDevice.Session.DevAddr) {
 				return nil, nil, errInvalidFieldValue.WithAttributes("field", "ids.dev_addr")
 			}
 		}
@@ -345,7 +360,7 @@ func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest
 			if req.EndDevice.DevEUI == nil {
 				return nil, nil, errNoDevEUI
 			}
-			if !ttnpb.HasAnyField([]string{"session"}, sets...) {
+			if !ttnpb.HasAnyField([]string{"session"}, sets...) || req.EndDevice.Session == nil {
 				return &req.EndDevice, sets, nil
 			}
 		}
@@ -356,6 +371,9 @@ func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest
 		); err != nil {
 			return nil, nil, errInvalidFieldMask.WithCause(err)
 		}
+		if req.EndDevice.Session == nil || req.EndDevice.Session.DevAddr.IsZero() {
+			return nil, nil, errInvalidFieldValue.WithAttributes("field", "session.dev_addr")
+		}
 		req.EndDevice.EndDeviceIdentifiers.DevAddr = &req.EndDevice.Session.DevAddr
 		sets = append(sets,
 			"ids.dev_addr",
@@ -381,11 +399,11 @@ func (ns *NetworkServer) Set(ctx context.Context, req *ttnpb.SetEndDeviceRequest
 				return nil, nil, errInvalidNwkSEncKey
 			}
 		} else {
-			if err := ttnpb.ProhibitFields(sets,
-				"session.keys.nwk_s_enc_key.key",
-				"session.keys.s_nwk_s_int_key.key",
-			); err != nil {
-				return nil, nil, errInvalidFieldMask.WithCause(err)
+			if ttnpb.HasAnyField([]string{"session.keys.nwk_s_enc_key"}, sets...) && req.EndDevice.Session.SessionKeys.NwkSEncKey != nil {
+				return nil, nil, errInvalidFieldValue.WithAttributes("field", "session.keys.nwk_s_enc_key")
+			}
+			if ttnpb.HasAnyField([]string{"session.keys.s_nwk_s_int_key"}, sets...) && req.EndDevice.Session.SessionKeys.SNwkSIntKey != nil {
+				return nil, nil, errInvalidFieldValue.WithAttributes("field", "session.keys.s_nwk_s_int_key")
 			}
 			// TODO: Encrypt (https://github.com/TheThingsIndustries/lorawan-stack/issues/1562)
 			req.EndDevice.Session.SNwkSIntKey = req.EndDevice.Session.FNwkSIntKey
diff --git a/pkg/networkserver/grpc_deviceregistry_test.go b/pkg/networkserver/grpc_deviceregistry_test.go
index 3da9cf614..4524f4ef7 100644
--- a/pkg/networkserver/grpc_deviceregistry_test.go
+++ b/pkg/networkserver/grpc_deviceregistry_test.go
@@ -464,6 +464,7 @@ func TestDeviceRegistrySet(t *testing.T) {
 					"lorawan_version",
 					"mac_settings.supports_32_bit_f_cnt",
 					"mac_settings.use_adr",
+					"session",
 					"session.dev_addr",
 					"session.keys.f_nwk_s_int_key.key",
 					"session.last_f_cnt_up",
@@ -653,6 +654,7 @@ func TestDeviceRegistrySet(t *testing.T) {
 					"lorawan_version",
 					"mac_settings.supports_32_bit_f_cnt",
 					"mac_settings.use_adr",
+					"session",
 					"session.dev_addr",
 					"session.keys.f_nwk_s_int_key.key",
 					"session.last_f_cnt_up",
@@ -832,6 +834,7 @@ func TestDeviceRegistrySet(t *testing.T) {
 					"lorawan_version",
 					"mac_settings.supports_32_bit_f_cnt",
 					"mac_settings.use_adr",
+					"session",
 					"session.dev_addr",
 					"session.keys.f_nwk_s_int_key.key",
 					"session.last_f_cnt_up",

@johanstokking johanstokking removed the blocked This can't continue until another issue or pull request is done label Oct 3, 2019
Copy link
Contributor

@rvolosatovs rvolosatovs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new NS changes look good

@johanstokking johanstokking merged commit 131cb27 into master Oct 7, 2019
@johanstokking johanstokking deleted the feature/end-device-claiming branch October 7, 2019 09:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c/network server This is related to the Network Server compat/api This could affect API compatibility ui/cli This is related to ttn-lw-cli
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants