jgromes / RadioLib

Universal wireless communication library for embedded devices
https://jgromes.github.io/RadioLib/
MIT License
1.47k stars 368 forks source link

LoRaWAN: fcntUp wraps after 50 transmissions #950

Closed barnslig closed 6 months ago

barnslig commented 7 months ago

Bug description

The fcntUp wraps around after 50 transmissions, resulting in no more messages being received by the network server.

Reproduction

The bug can be reproduced as follows in god mode without sending 50 uplinks:

#include <Arduino.h>
#include <RadioLib.h>

SX1262 radio = new Module(8, 14, 12, 13);
LoRaWANNode node(&radio, &EU868);

void setup()
{
  Serial.begin(115200);

  delay(2500);

  node.wipe();

  for (unsigned int i = 0; i < 100; i += 1)
  {
    node.restoreFcntUp();

    node.fcntUp += 1;

    Serial.printf("fcntUp: %u | iteration: %u\n", node.fcntUp, i);

    node.saveFcntUp();
  }
}

void loop()
{
}

The output is as follows and shows the wrap around after 50 iterations:

Log output ``` fcntUp: 1 | iteration: 0 fcntUp: 2 | iteration: 1 fcntUp: 3 | iteration: 2 fcntUp: 4 | iteration: 3 fcntUp: 5 | iteration: 4 fcntUp: 6 | iteration: 5 fcntUp: 7 | iteration: 6 fcntUp: 8 | iteration: 7 fcntUp: 9 | iteration: 8 fcntUp: 10 | iteration: 9 fcntUp: 11 | iteration: 10 fcntUp: 12 | iteration: 11 fcntUp: 13 | iteration: 12 fcntUp: 14 | iteration: 13 fcntUp: 15 | iteration: 14 fcntUp: 16 | iteration: 15 fcntUp: 17 | iteration: 16 fcntUp: 18 | iteration: 17 fcntUp: 19 | iteration: 18 fcntUp: 20 | iteration: 19 fcntUp: 21 | iteration: 20 fcntUp: 22 | iteration: 21 fcntUp: 23 | iteration: 22 fcntUp: 24 | iteration: 23 fcntUp: 25 | iteration: 24 fcntUp: 26 | iteration: 25 fcntUp: 27 | iteration: 26 fcntUp: 28 | iteration: 27 fcntUp: 29 | iteration: 28 fcntUp: 30 | iteration: 29 fcntUp: 31 | iteration: 30 fcntUp: 32 | iteration: 31 fcntUp: 33 | iteration: 32 fcntUp: 34 | iteration: 33 fcntUp: 35 | iteration: 34 fcntUp: 36 | iteration: 35 fcntUp: 37 | iteration: 36 fcntUp: 38 | iteration: 37 fcntUp: 39 | iteration: 38 fcntUp: 40 | iteration: 39 fcntUp: 41 | iteration: 40 fcntUp: 42 | iteration: 41 fcntUp: 43 | iteration: 42 fcntUp: 44 | iteration: 43 fcntUp: 45 | iteration: 44 fcntUp: 46 | iteration: 45 fcntUp: 47 | iteration: 46 fcntUp: 48 | iteration: 47 fcntUp: 49 | iteration: 48 fcntUp: 50 | iteration: 49 fcntUp: 1 | iteration: 50 fcntUp: 2 | iteration: 51 fcntUp: 3 | iteration: 52 fcntUp: 4 | iteration: 53 fcntUp: 5 | iteration: 54 fcntUp: 6 | iteration: 55 fcntUp: 7 | iteration: 56 fcntUp: 8 | iteration: 57 fcntUp: 9 | iteration: 58 fcntUp: 10 | iteration: 59 fcntUp: 11 | iteration: 60 fcntUp: 12 | iteration: 61 fcntUp: 13 | iteration: 62 fcntUp: 14 | iteration: 63 fcntUp: 15 | iteration: 64 fcntUp: 16 | iteration: 65 fcntUp: 17 | iteration: 66 fcntUp: 18 | iteration: 67 fcntUp: 19 | iteration: 68 fcntUp: 20 | iteration: 69 fcntUp: 21 | iteration: 70 fcntUp: 22 | iteration: 71 fcntUp: 23 | iteration: 72 fcntUp: 24 | iteration: 73 fcntUp: 25 | iteration: 74 fcntUp: 26 | iteration: 75 fcntUp: 27 | iteration: 76 fcntUp: 28 | iteration: 77 fcntUp: 29 | iteration: 78 fcntUp: 30 | iteration: 79 fcntUp: 31 | iteration: 80 fcntUp: 32 | iteration: 81 fcntUp: 33 | iteration: 82 fcntUp: 34 | iteration: 83 fcntUp: 35 | iteration: 84 fcntUp: 36 | iteration: 85 fcntUp: 37 | iteration: 86 fcntUp: 38 | iteration: 87 fcntUp: 39 | iteration: 88 fcntUp: 40 | iteration: 89 fcntUp: 41 | iteration: 90 fcntUp: 42 | iteration: 91 fcntUp: 43 | iteration: 92 fcntUp: 44 | iteration: 93 fcntUp: 45 | iteration: 94 fcntUp: 46 | iteration: 95 fcntUp: 47 | iteration: 96 fcntUp: 48 | iteration: 97 fcntUp: 49 | iteration: 98 fcntUp: 50 | iteration: 99 ```

Expected behavior

The fcntUp should not wrap before the uint32 is full.

Additional info

StevenCellist commented 7 months ago

I have a suspicion, I will investigate. Good find!

StevenCellist commented 7 months ago

Got it. The node.restoreFcntUp() function was nicely implemented with 'dynamic sizing' of the NVM buffer size which got expanded from 6.3.0 to 6.4.0, but unfortunately I apparently had forgotten to also dynamicalize (inventing new words there) node.saveFcntUp().

If it bothers you personally, the fix is to change lines 818/819 to

  uint8_t fcntBuffStart = mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID);
  uint8_t fcntBuffEnd = mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID + 1);
  uint8_t buffSize = fcntBuffEnd - fcntBuffStart;
  #if RADIOLIB_STATIC_ONLY
  uint8_t fcntBuff[RADIOLIB_STATIC_ARRAY_SIZE];
  #else
  uint8_t* fcntBuff = new uint8_t[buffSize];
  #endif

  mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_EEPROM_LORAWAN_FCNT_UP_ID), fcntBuff, buffSize);

And modify the two occurrences of simply '30' in the lines 859 & 864 to buffSize. I will include this fix in a PR later, probably next week.

barnslig commented 7 months ago

@StevenCellist thank you so much for your quick investigation! I'll apply your fix locally, looking forward to your PR :)

StevenCellist commented 7 months ago

Welcome and thank you for digging this out + providing the easy code for me to run. I suspect this also solves behaviour that I thought I dreamt of where some packets went missing, but it may also help in solving #949 which will come clear soon.

@jgromes while debugging this, I noticed that PhysicalLayer.h does not expose its private members upon godmode because of the protected: at its line 491 - is this intentional, or could you move that block to below private?

jgromes commented 6 months ago

@StevenCellist good catch - no, that is not intentional. I guess I never really used god mode to acces private members from PhysicalLayer. I will fix it so that your PR can remain focused on LoRaWAN stuff :)