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
Device claiming client #1392
Conversation
@@ -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()) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 containingsession.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)
There was a problem hiding this comment.
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
, butsession.dev_addr
is specified - thesession.dev_addr
on the stored device is deleted(assumingsession.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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDEM, same as DevAddr
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
71c4366
to
0fbd63f
Compare
409b59c
to
cc818b5
Compare
55ed248
to
2f758a3
Compare
f868ecc
to
03d2b58
Compare
@rvolosatovs please review |
I think the session-related diff in NS 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", |
03d2b58
to
6f89ce3
Compare
There was a problem hiding this 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
Summary
Client for end device claiming and some necessary plumbing
References https://github.com/thethingsindustries/lorawan-stack/issues/1673
References #138
Changes
Notes for Reviewers
@rvolosatovs let me know why we need(ed) those nil-checks in NS
Release Notes
ttn-lw-cli end-device claim --help
for more informationttn-lw-cli end-device generate-qr --help
for more informationttn-lw-cli application claim authorize --help
for more information