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

gnrc/lorawan: add basic LoRaWAN 1.1 features #17884

Merged
merged 3 commits into from
Sep 30, 2022

Conversation

Ollrogge
Copy link
Member

@Ollrogge Ollrogge commented Mar 29, 2022

Contribution description

This PR adds basic support for LoRaWAN version 1.1. This includes joining a network and sending / receiving data.
This PR also modifies the LoRaWAN 1.0 code to use the 1.1 terms. This should not change the behavior of the 1.0 code and everything should still work.

Not implemented are ReJoin-Requests and MAC commands such as RekeyInd and RekeyConf.

For testing purposes I setup a private LoRaWAN network using the Chirpstack and was able to join the network and send data.

Testing procedure

  • tests/gnrc_lorawan_11
  • examples/gnrc_lorawan if able to setup a private LoRaWAN network

@github-actions github-actions bot added Area: cpu Area: CPU/MCU ports Area: examples Area: Example Applications Area: Kconfig Area: Kconfig integration Area: LoRa Area: LoRa radio support Area: network Area: Networking Area: sys Area: System Area: tests Area: tests and testing framework Platform: ARM Platform: This PR/issue effects ARM-based platforms labels Mar 29, 2022
@@ -76,14 +76,34 @@ static void _sleep_radio(gnrc_lorawan_t *mac)
dev->driver->set(dev, NETOPT_STATE, &state, sizeof(state));
}

void gnrc_lorawan_init(gnrc_lorawan_t *mac, uint8_t *nwkskey, uint8_t *appskey)
#if IS_ACTIVE(CONFIG_LORAWAN_1_1)
void gnrc_lorawan_init_11x(gnrc_lorawan_t *mac, uint8_t *joineui,
Copy link
Member

Choose a reason for hiding this comment

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

I think at this point it makes sense to use a common gnrc_lorawan_init function but with a "ctx" parameter. E.g

void gnrc_lorawan_init(gnrc_lorawan_t *mac, const gnrc_lorawan_ctx_t *ctx)
{
   mac->nwkskey = ctx->nwskey;
    ....
}

Since this function would be resolved at compile time (e.g user cannot switch between 1.0.3 and 1.1 on runtime), I think it will simplify things a bit

Copy link
Member Author

@Ollrogge Ollrogge Apr 4, 2022

Choose a reason for hiding this comment

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

Is there a kind of hard cut between gnrc_netif_lorawan and gnrc_lorawan ? Otherwise I could just pass in a pointer to gnrc_netif_lorawan_t without needing to declare a new ctx struct ?

Copy link
Member

Choose a reason for hiding this comment

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

yes, gnrc_netif_lorawan is the "GNRC Network Interface Implementation" for GNRC LoRaWAN. It could be possible to run GNRC LoRaWAN directly from the API without a network interface. That's why these two structures are decoupled.

If you make it const, you can e.g store that information on ROM.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay I see :) I added the gnrc_lorawan_ctx_t struct.

Another question regarding gnrc netif: Right now to configure the NwkSKey for Lorawan 1.0x one has to set CONFIG_LORAMAC_FNWKSINT_KEY_DEFAULT. As I think this is kind of confusing for someone wanting to configure a 1.0x node I think it would be better to use the old 1.0x terms in the netif layer and only switch to the new terms internally ?

Copy link
Member

Choose a reason for hiding this comment

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

hmmm good question... I usually tend to think that's better to reflect the actual key names depending on the version. For example, a user that explicitly wants to use LoRaWAN 1.1 will probably know the key names. Most LoRaWAN users will still use LoRaWAN 1.0.x, so it's probably to keep the original names there. You can always fallback to macro aliases that use the terms internally.

void gnrc_lorawan_calculate_mic(const le_uint32_t *dev_addr, uint32_t fcnt,
uint8_t dir, iolist_t *frame,
const uint8_t *nwkskey, le_uint32_t *out)
#if IS_USED(CONFIG_LORAWAN_1_1)
Copy link
Member

@jia200x jia200x Apr 5, 2022

Choose a reason for hiding this comment

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

Same here. You can merge these two functions and use a common signature. If you have different parameters, consider a structure.
You can also kind of merge the logic here: gnrc_lorawan_calculate_mic_uplink_11x is (almost) a subset of gnrc_lorawan_calculate_mic_uplink_10x.

Also, you can use a regular if with IS_ACTIVEor IS_USED in some parts. The compiler will remove branches that are never reached. Of course, you cannot do that with struct members that don't exist on compile time (those who appear only with LoRaWAN 1.1).

For instance, you could write these functions as:

void gnrc_lorawan_calculate_mic_uplink(const le_uint32_t *dev_addr, uint32_t fcnt,
                                           ... lorawan_mic_t *ctx, ...
                                           le_uint32_t *out)
{
    orawan_block0_t block0 = { 0 };

    block0.fb = MIC_B0_START;
    block0.dir = 0x00;
        
    memcpy(&block0.dev_addr, dev_addr, sizeof(le_uint32_t));

    block0.fcnt = byteorder_htoll(fcnt);
    block0.len = iolist_size(frame);

    /* cmacF = aes128_cmac(FNwkSIntKey, B0 | msg) */
    cmac_init(&CmacContext, fnwksintkey, LORAMAC_FNWKSINTKEY_LEN);
    cmac_update(&CmacContext, &block0, sizeof(block0));
    for (iolist_t *io = frame; io != NULL; io = io->iol_next) {
        cmac_update(&CmacContext, io->iol_base, io->iol_len);
    }
    cmac_final(&CmacContext, digest);
  
    if (IS_ACTIVE(GNRC_LORAWAN_1_1)) {
        /* Do 1.1 specific stuff an get specific keys from `ctx`*/
    }
}

Copy link
Member Author

Choose a reason for hiding this comment

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

When exactly can I use if with this macro ? I tried to e.g. use here and it didn't work.
I find it kinda weird to depend on compiler optimizations to make my code compile ...

Copy link
Member

Choose a reason for hiding this comment

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

it probably failed because _appkey exists only when you set LoRaWAN 1.1. Since the "topology" of the file is the same, the variables should always exist.

You can always use if with this macro, since always resolves to 0 or 1.

For example:

/* foo */
if (IS_ACTIVE(MY_MODULE)) {
    /* bar */
}

In this example the snippets "foo" and "bar" are compiled like regular C code. But in case "MY_MODULE" is not used, the compiler will optimize out "bar". The compiler will still find syntax errors in "bar", since the compiler parses the snippet anyway.

Copy link
Member

Choose a reason for hiding this comment

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

I find it kinda weird to depend on compiler optimizations to make my code compile ...

What do you mean with that? You don't depend on compiler optimizations to compile your code (if you turn off compiler optimizations it will still compile without issues). Could you elaborate more?

Copy link
Member Author

Choose a reason for hiding this comment

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

it probably failed because _appkey exists only when you set LoRaWAN 1.1. Since the "topology" of the file is the same, the variables should always exist.

You can always use if with this macro, since always resolves to 0 or 1.

For example:

/* foo */
if (IS_ACTIVE(MY_MODULE)) {
    /* bar */
}

In this example the snippets "foo" and "bar" are compiled like regular C code. But in case "MY_MODULE" is not used, the compiler will optimize out "bar". The compiler will still find syntax errors in "bar", since the compiler parses the snippet anyway.

Having _appkey exist all the time fixed the issue. Thank you !
I misunderstood how using if (IS_USED(...) works. Your example and explanation cleared things up !

@@ -106,6 +107,8 @@ uint32_t gnrc_lorawan_pick_channel(gnrc_lorawan_t *mac)
for (int i = 0; i < pos + 1; i++) {
state = bitarithm_test_and_clear(state, &index);
}

mac->last_chan_idx = index;
Copy link
Member

Choose a reason for hiding this comment

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

since this function only picks a random channel, I would be in favor of maintaining "mac->last_chan_idx" outside of this function. It makes it easier to maintain as well

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 am now managing it inside gnrc_lorawan_mcps_request by just looping through the channels and looking for the correct one. With this change I had removed the call to gnrc_lorawan_pick_channel from gnrc_lorawan_event_retrans_timeout which I had added because otherwise the last_chan_idx wont be correct.

I am wondering if this is bad since gnrc_lorawan_event_retrans_timeout might wan't to use a new channel ?

Copy link
Member

Choose a reason for hiding this comment

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

I am wondering if this is bad since gnrc_lorawan_event_retrans_timeout might wan't to use a new channel ?

Hmmmm yes, the retransmssion should follow the same rules of any transmission. So it should choose a different channel.

Copy link
Member Author

Choose a reason for hiding this comment

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

Due to this I now moved the channel picking + the calculation of the uplink mic into the _transmit_pkt function. This is the cleanest solution I could find.

static uint8_t _appskey[LORAMAC_APPSKEY_LEN];
#if IS_USED(CONFIG_LORAWAN_1_1)
static uint8_t _appkey[LORAMAC_APPKEY_LEN];
Copy link
Member

Choose a reason for hiding this comment

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

why is _appkey not required in LoRaWAN 1.0.3?

Copy link
Member Author

@Ollrogge Ollrogge Apr 5, 2022

Choose a reason for hiding this comment

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

It is but its name has changed. The 1.1 spec uses the NwkKey in place of the AppKey.

Copy link
Member

Choose a reason for hiding this comment

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

ok, I see. But maybe you could try to use the same variable names internally. Otherwise you cannot use regular if (IS_ACTIVE()) stuff

@jia200x
Copy link
Member

jia200x commented Apr 5, 2022

Overall the code looks nice and the changes make sense. I left some comments there and plan to run some tests when they are addressed.

@Ollrogge
Copy link
Member Author

@jia200x I implement the Rekey MAC command and successfully tested it with TTN. Please test again if you can :)

image

@jia200x
Copy link
Member

jia200x commented Jun 28, 2022

Hi. It works now!
However, according to this, there should be also backward compatibility between a v1.1 device and a 1.0.3 network server.
When I configure that on TTN, it throws invalid MIC. Does it work for you on Chirpstack?

@Ollrogge
Copy link
Member Author

Hi. It works now! However, according to this, there should be also backward compatibility between a v1.1 device and a 1.0.3 network server. When I configure that on TTN, it throws invalid MIC. Does it work for you on Chirpstack?

Should be fixed as well now !

@jia200x
Copy link
Member

jia200x commented Aug 8, 2022

I can confirm this still works as expected when compiling LoRaWAN 1.0.3. I will proceed with all tests for LoRaWAN 1.1

@jia200x
Copy link
Member

jia200x commented Aug 23, 2022

ok, everything seems to work now

Copy link
Member

@jia200x jia200x left a comment

Choose a reason for hiding this comment

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

Tested ACK. Please squash commits to make them more semantic (e.g "add support for GNRC LoRaWAN 1.1", "Adapt test". etc...)

@jia200x jia200x added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Aug 24, 2022
@Ollrogge
Copy link
Member Author

@jia200x Tests for some boards fail because they don't implement they flashpage API which I need for LoRaWAN 1.1.
How should I handle this ?

@github-actions github-actions bot removed Area: cpu Area: CPU/MCU ports Platform: ARM Platform: This PR/issue effects ARM-based platforms labels Sep 16, 2022
@Ollrogge
Copy link
Member Author

@jia200x Can you explain to me the doc and Murdock failures ? Neither seem to be related to my code ...

@jia200x jia200x added CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR and removed CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Sep 22, 2022
@jia200x
Copy link
Member

jia200x commented Sep 22, 2022

There seemed to be an unrelated error. I retriggered the CI. Let's see what happens

@Ollrogge
Copy link
Member Author

Looks like the same issue :(

@jia200x
Copy link
Member

jia200x commented Sep 27, 2022

Looks like the same issue :(

I just realized that the Murdock problem was because you added NETOPTs without updating tests/unittests/tests-netopt. You have to add the new NETOPTs there to make the test pass.
Let's try to fix that one first and then see what went on with the documentation

@Ollrogge
Copy link
Member Author

Looks like the same issue :(

I just realized that the Murdock problem was because you added NETOPTs without updating tests/unittests/tests-netopt. You have to add the new NETOPTs there to make the test pass. Let's try to fix that one first and then see what went on with the documentation

Thanks for this ! I should have checked the error message more closely... All green now !

@jia200x jia200x merged commit f022ac3 into RIOT-OS:master Sep 30, 2022
@jia200x
Copy link
Member

jia200x commented Sep 30, 2022

finally! congrats @Ollrogge 🎉 🎉 🎉

@Ollrogge
Copy link
Member Author

finally! congrats @Ollrogge tada tada tada

Yaaayy :) Thank you for all the help !!

@maribu maribu added this to the Release 2022.10 milestone Oct 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: build system Area: Build system Area: examples Area: Example Applications Area: LoRa Area: LoRa radio support Area: network Area: Networking Area: sys Area: System Area: tests Area: tests and testing framework CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants