BLE Bond info is lost when Bangle.JS 2 is turned off

Posted on
  • I've been wanting to use the whitelist with my Bangle as I don't particularly like leaving my devices unsecured. I am aware of the pin option but I would like to make the whitelist work. I've noticed people mention having issues on Android devices due to the apparently randomized MAC addresses. I have found the proper way to add these addresses to the whitelist but the bond info seems to be lost when the watch is turned off (through Settings>Utils>Turn Off)

    My Android uses private resolvable addresses when connecting with Bangle.JS Gadgetbridge (nRF Connect seems to use my real public address, so the privacy option seems to be per app?). Private resolvable addresses can be resolved by bonded devices to obtain the public address.

    If you bond to the Bangle while the whitelist is OFF, the Bangle can now resolve the addresses. You can now add your device to the whitelist by disconnecting (NOT removing the bond), selecting "Add Device", and connecting by tapping the Bangle card in Gadgetbridge.

    Tapping the new whitelist entry you should see "XX:XX:XX:XX:XX:XX public (resolved)" with your unchanging public address. You can now connect and disconnect from the Bangle any number of times with the whitelist on as long as you don't turn it off.

    Adding to the whitelist without bonding first will result in "XX:XX:XX:XX:XX:XX private-resolvable" being added to the list with a randomized address, as the Bangle does not have the IRK needed to resolve the public address.

    I usually turn my Bangle off when it's off my wrist. I confirmed that the Bangle is unable to resolve addresses after a reboot by writing down and trying to resolve one of my phone's generated public-resolvable addresses using NRF.resolveAddress(). Before the reboot it resolves fine, but after the reboot it returns undefined meaning the address cannot be resolved.

    I am not entirely sure, but this seems like it could be a firmware issue with the Nordic Peer Manager not successfully storing or loading bond keys and information from flash.

  • Hi, thanks for digging into this - which firmware version do you have in your Bangle at the moment?

    Just to check though, you are releasing the button on the Bangle as soon as the screen lights up, and not keeping holding it down? I know we have some code which deletes all bonded peers if you boot while holding the button: https://github.com/espruino/Espruino/blob/master/targets/nrf5x/bluetooth.c#L2239-L2260

    That's done so that you can't get in the position where your Bangle.js is permanently unconnectable.

  • My bangle is on 2v19. I released the button as short as possible for it to still register a power on, releasing right as the "CHECK STORAGE" and "BOOTING" messages appear and the bond is still forgotten.

    I am not exactly sure when this code is executed and the buttons are polled for the reset, but judging from the comments and macros this was designed to check if ALL buttons are held down, in this case the Bangle 2 only has 1 so it fulfills that condition.

    I know this is Espruino code not specific to the Bangle, but the bangle also has the Recovery menu that appears to be triggered by holding the button during startup. I think that would be a good place to add a "Clear Bonds" option. I also think it would be good to add some granular control over bonds to the NRF class such as listing and removing individual ones, and then later on adding them to the bluetooth menu in settings app so regular users can manage all the devices their watch is bonded to.

    I would like to help contribute these features to make the watch a bit more secure for everyone, I have some embedded experience, but I've never written an interpreter before and am not sure what files I should be looking at.

  • Hi - well, that is a strange one. As long as you release the button when you're on the blocky text screen (the bootloader) before or about the same time as BOOTING appears you should be basically ok though.

    As you say adding an option into the recovery menu instead seems like a smarter idea - but that menu only got added in the 2v19 last week so it's pretty new.

    PRs for this kind of stuff would be great! The fact it's an interpreter shouldn't be a big deal as you won't really go near that code. The code itself is at https://github.com/espruino/Espruino and you can build with https://github.com/espruino/Espruino/blob/master/README_Building.md

    There's some info on extending Espruino at https://www.espruino.com/Extending+Espruino+1#add-function-source-in-c - you don't need to make a new file, just add to jswrap_bluetooth.c

    And you can see how existing stuff works if you head to the reference at https://www.espruino.com/Reference#NRF then find a function you're interested in. You can then click the ⇒ link in the title and it'll bring you to where the function is defined in the code

  • I did it again and can confirm I definitely released the button before BOOTING appears. Something I thought of is that somehow the way Espruino is using the flash for file storage could be overlapping with the nRF Flash Data Storage module that the Peer Manager uses for bond data storage as I've noticed Espruino seems to access the flash in a different way. But this is completely speculation.

    I will try and contribute some more wrapped bond management functions but I am a little scared to flash my bangle as I do use it as my watch.

    Would I just use BLE DFU mode to test my builds on it? Would the bootloader be reflashed as well or does it just stay in place as other regions of flash are overwritten? I am a bit worried about wearing out the flash memory or getting it into an unusable state and having to open it up to reflash.

  • If you talk about Bangle 2 then there is no need to open it to reflash, SWD is right there on the back of the watch.

  • As for FDS - it is used. Normally there are 2 blocks reserved for it, it is the number 2 in this line
    https://github.com/espruino/Espruino/blob/master/boards/BANGLEJS2.py#L115
    but bangle2 has Storage in external SPI flash so this does not matter and more flash is free (and it should not overlap with anything) but still 2 blocks should be used - defined via FDS_VIRTUAL_PAGES define here).

    And the location is right below the bootloader, here

  • As mentioned, on Bangle.js 2 we use external flash so we don't touch internal, but even when we do for other boards there is no overlap.

    It might be worth checking the contents of the FD pages - 2x 4096 byte pages, starting at (1024*1024)-(10*4096) and see if they actually change after reboot (I don't think they will). If not it might be that we need to load the info out of them at boot time somehow?

    However I just tested and bonding isn't reset for me...

    • Connect to a Bangle with nRF Connect on Android
    • Tell it to Bond
    • Disconnect/connect - still bonded
    • Disconnect - hard reboot the Bangle
    • Connect - still bonded

    So I think something else must be broken in your case?

  • (1024*1024)-(10*4096)

    the bootloader is at 0xf7000 not 0xf8(?) https://github.com/espruino/Espruino/blob/master/targetlibs/nrf5x_15/nrf5x_linkers/secure_bootloader_gcc_nrf52.ld#L8
    so FDS should start at at 0xf5000 = (1024*1024)-((9+2)*4096)
    or peek32(0x10001014)-2*4096

    we need to load the info out of them at boot time somehow?

    Was expecting that the code here does it
    https://github.com/espruino/Espruino/blob/1f80e6e69b0435d5d960ac11e9f05c46158c34f6/targets/nrf5x/bluetooth.c#L2264
    at least that line is failing when FDS storage is full/corrupted and it clears it and retries to continue

  • ahh sorry, forgot about that - so yes, once page further behind...

  • If it was retaining the bonds the whitelist would be able to resolve the addresses after a reboot and it cannot.
    It appears to be re-learning the bond information after a reboot if there is no whitelist in place to stop it. Doing the steps you described with the whitelist off works the exact same way for me, the "Bonded" text never goes away on nRF Connect because Android retains the bond info irrespective of the watch.

    Please try this:

    • Clear whitelist and disable if on
    • Connect and Bond with Gadgetbridge (If you already have the watch added to gadgetbridge you can bond using Bluetooth settings in android)
    • Disconnect
    • Enable Whitelist, Add Device
    • Connect using Gadgetbridge while Add Device is open (important)
    • Confirm that added address in whitelist is suffixed with public (resolved) and not public or private-resolvable
    • Disconnect and Reconnect a few times in Gadgetbridge to see that you can while the whitelist is enabled
    • Power Off Bangle from settings and turn back on
    • Try to connect with Gadgetbridge, it won't work

    EDIT: Would this code be correct for dumping the 2 pages?

    f = require("Flash");
    
    function dump() {
      pagesize = 4096*2;
      chunksize = 16;
    
      for (let i = 0; i <= pagesize; i += chunksize) {
        fds = f.read(chunksize, (0xf5000 + i));
        hex = [];
        fds.forEach((b) => hex.push(b.toString(16)));
        console.log(hex.join(' '));
      }
    }
    
    setTimeout(dump, 20);
    

    EDIT 2:
    Android seems to stop and start using address privacy seemingly at random, I thought at first nRF Connect disabled it to make development easier, but the real public address (not public (resolved)) started showing up while trying to add to the whitelist with Gadgetbridge.

    If I've done the code above correctly, I can confirm that the FDS pages don't get cleared on reboot which is what should be happening according to Gordon. Something else must be the problem, perhaps somewhere in the whitelist code. I will post more findings soon, I really want to get to the bottom of this.

  • Reboot is almost like power off regarding starting of the firmware but power off turns off also the the RAM (most probably, it is configurable). So if something regarding bond info is stored in RAM it is lost. However since static data defined is C source that goes to RAM is reinitialized and bss section is cleared every startup it must be something else - like special linker section just for this. Maybe this can be tested by keeping the RAM on when doing 'power off'

    just checked and there are actually two methods -off and softOff not sure which one is used
    https://github.com/espruino/Espruino/blob/eccac25814d50e31e613ac682e56ab439eb64575/libs/banglejs/jswrap_bangle.c#L5281
    can you try to call one or another to see if it makes a difference?

    Has the bonding info some time validity so it can be invalid because time gets reset to 1970?

  • And btw the softOff would be enough for your habit of turning it off so often, the real off makes sense if putting it away for several months.

    Maybe the RAM can be kept on by poking the registers RAM[x].POWER directly by poke32 before turning it off (unless they are protected), more info
    https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.nrf52832.ps.v1.1/power.html?cp=5_2_0_17_1#unique_2047546596

    EDIT: they are protected, and RAM is indeed set to be turned off too peek32(0x40000000+0x524).toString(16), you can try to run this to change it

    setTimeout(function() { NRF.restart(function(){
    poke32(0x40000554,3|3<<16)
    poke32(0x40000524,3|3<<16)
    }) }, 2000);NRF.disconnect();
    

    to enable RAM retention in OFF mode and then you can try to power it off if it makes a difference.

    Copy paste (ctrl+v) whole block when connected to watch via IDE https://www.espruino.com/ide/
    It will disconnect. Then reconnect in few seconds and check via peek32 if the value changed.

  • Oh, I just tried Bangle.off() vs Bangle.softOff() and with off the time is lost and with softOff() it is not however when I try Settings->Utils->Turn Off the time is not lost after wakeup so the softOff() is most probably already used by default?

  • Sorry for a lot of noise. Maybe you said that not only 'turn off' but every reboot actually breaks it? Then it is not the RAM. Or it is, but application startup clears it too.

    Maybe I see something similar or related. In Chrome on Linux when I try to connect via webide it works and device is remembered in the list. Then I can disconnect and reconnect many times just fine. However if I reboot the watch the remembered item in the connect window stops working and I need to search for the watch again via first Web Bluetooth item. Then I see the device in the list and it has the '- paired' suffix there and when clicking connect it works. However I guess this is not related to bonding but it is interesting, why reboot would break that? Doing just NRF.restart() which restarts the SoftDevice does not break it.

    EDIT: Chrome in Linux or the Bluez stack probably adds some of its own issues so it is not good example or benchmark. When trying more it has issues even when i turn BLE off in watch settings, then when turning BLE back on I see 'Bluetooth Device is no longer in range.' and it does not come back, I need to search again. The error message I mentioned earlier that happens after reboot is reported as "NetworkError: Connection Error: Connection attempt failed.'

  • Anyway so far that was all without bonding. I tried 'pairing' the device in linux in Bluetooth devices panel and this seem to create bond.

    Here is the interesting part stored in the page below bootloader. I dump everything via
    for (i=0;i<8192;i+=256) print(btoa(require("Flash").read(256,0xf5000+i))). Before any pairing the second page started as

    3sCt3v4BHvH//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////w==
    

    The first page is very similar and does not change.
    Then I did pairing from linux side and the second page changed into

    3sCt3v4BHvEHwBQAAMAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAZrqpYvGRPZthHF+yDRG8k87z/U1JjQABagnjvAQDN7I+xxRW4Bvy4O/wo7gT5+57B+UAA9GT7P0A1vgYD2gbAAQAAwAAAAgAAAAEAAAAIwAQAAMAAAAMAAAADAAAACAAQAAIAAQDNygAA/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////w==
    

    Also I noticed security status shows

    NRF.getSecurityStatus()
    ={ advertising: false, connected: true, encrypted: true, mitm_protected: false,
      bonded: true,
      connected_addr: "64:bc:58:aa:ae:19 public"
     }
    

    so there is encrypted true, bonded true. Then I rebooted device and reconnected and it changed into

    3sCt3v4BHvEHwBQAAMAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAZrqpYvGRPZthHF+yDRG8k87z/U1JjQABagnjvAQDN7I+xxRW4Bvy4O/wo7gT5+57B+UAA9GT7P0A1vgYD2gbAAQAAwAAAAgAAAAEAAAAAAAQAAMAAAAMAAAADAAAACAAQAAIAAQDNygAAAAAEAADAAAAEAAAAAwAAAAgAEAACAAEAzcoAAAjABAAAwAAABQAAAAMAAAAIABAAAgABAM3KAAD//////////////////////////////////////////////////////////////////////////w==
    

    I still get bonded, encrypted connection but there is another short record added.
    Then I noticed that each time I disconnect Bangle from linux side (so that blue icon on bangle turns into grey) there is another such record added after each next connection, otherwise everything works. here are few lines with added records

    3sCt3v4BHvEHwBQAAMAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAZrqpYvGRPZthHF+yDRG8k87z/U1JjQABagnjvAQDN7I+xxRW4Bvy4O/wo7gT5+57B+UAA9GT7P0A1vgYD2gbAAQAAwAAAAgAAAAEAAAAAAAQAAMAAAAMAAAADAAAACAAQAAIAAQDNygAAAAAEAADAAAAEAAAAAwAAAAgAEAACAAEAzcoAAAAABAAAwAAABQAAAAMAAAAIABAAAgABAM3KAAAIwAQAAMAAAAYAAAADAAAACAAQAAIAAQDNygAA/////////////////////////////////////w==
    3sCt3v4BHvEHwBQAAMAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAZrqpYvGRPZthHF+yDRG8k87z/U1JjQABagnjvAQDN7I+xxRW4Bvy4O/wo7gT5+57B+UAA9GT7P0A1vgYD2gbAAQAAwAAAAgAAAAEAAAAAAAQAAMAAAAMAAAADAAAACAAQAAIAAQDNygAAAAAEAADAAAAEAAAAAwAAAAgAEAACAAEAzcoAAAAABAAAwAAABQAAAAMAAAAIABAAAgABAM3KAAAAAAQAAMAAAAYAAAADAAAACAAQAAIAAQDNygAACMAEAADAAAAHAAAAAwAAAAgAEAACAAEAzcoAAA==
    3sCt3v4BHvEHwBQAAMAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAZrqpYvGRPZthHF+yDRG8k87z/U1JjQABagnjvAQDN7I+xxRW4Bvy4O/wo7gT5+57B+UAA9GT7P0A1vgYD2gbAAQAAwAAAAgAAAAEAAAAAAAQAAMAAAAMAAAADAAAACAAQAAIAAQDNygAAAAAEAADAAAAEAAAAAwAAAAgAEAACAAEAzcoAAAAABAAAwAAABQAAAAMAAAAIABAAAgABAM3KAAAAAAQAAMAAAAYAAAADAAAACAAQAAIAAQDNygAAAAAEAADAAAAHAAAAAwAAAAgAEAACAAEAzcoAAA==
    CMAEAADAAAAIAAAAAwAAAAgAEAACAAEAzcoAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////w==
    

    Then I removed device on linux side so the bonding got lost on linux side and after reconnection the stored data were still the same however the encryption was turned off

    NRF.getSecurityStatus()
    ={ advertising: false, connected: true, encrypted: false, mitm_protected: false,
      bonded: true,
      connected_addr: "64:bc:58:aa:ae:19 public"
     }
    

    no record was added on this new connection. It still says bonded.
    Then I did the pairing from linux side again and it added another long bonding record and turned encryption to true so it looks like this

    3sCt3v4BHvEAABQAAMAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAAAZrqpYvGRPZthHF+yDRG8k87z/U1JjQABagnjvAQDN7I+xxRW4Bvy4O/wo7gT5+57B+UAA9GT7P0A1vgYD2gbAAQAAwAAAAgAAAAEAAAAAAAQAAMAAAAMAAAADAAAACAAQAAIAAQDNygAAAAAEAADAAAAEAAAAAwAAAAgAEAACAAEAzcoAAAAABAAAwAAABQAAAAMAAAAIABAAAgABAM3KAAAAAAQAAMAAAAYAAAADAAAACAAQAAIAAQDNygAAAAAEAADAAAAHAAAAAwAAAAgAEAACAAEAzcoAAA==
    AAAEAADAAAAIAAAAAwAAAAgAEAACAAEAzcoAAAfAFAAAwAAACQAAAAEAAAAAAAAAAAAAAAAAAAAAABmuqli8ZBhGCfWMrObG4It5lSB38yJAANI2qOvqjzATvQ8Furn7uxrS8kakk1e6b5EtQADt6Vtdj5PQBlvLCMAEAADAAAAKAAAAAwAAAAgAEAACAAEAzcoAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////w==
    

    So for me reboot does not clear anything, only every new connection add some extra record there (maybe connection encryption keys?). Also clearing bonding from linux side does not change bonding info in Bangle at all. New bonding invalidates previous - it can be seen previous records are modified when next is written so new bonding clears something in the first one.

    When it works the connection is encrypted and rebooting watch does not break this, reconnecting after reboot keeps it still encrypted.

  • Ok, so I'd be pretty sure that actually all the bonding info stays in place after a reboot. @user156416 so you actually checked what I said, and Android reported it was bonded?

    Because I'd have thought that even if Android remembered it was bonded, if the bonds were erased on the Bangle then it would refuse to connect.

    So I think the issue is probably the very recently added NRF.resolveAddress which tries to resolve the address: https://www.espruino.com/Reference#l_NRF_resolveAddress

    (code for it is at https://github.com/espruino/Espruino/blob/cfbc4040dac6a7881e3465f60adc4dd8a1e45b85/targets/nrf5x/bluetooth.c#L3654-L3673)

    Maybe you could try calling it from the IDE along with NRF.getSecurityStatus() after a reboot (you can do remote access from a desktop via the App Loader in Gadgetbridge). I would imagine that if bonded, bonded would still stay true after a reboot but maybe resolveAddress(NRF.getSecurityStatus().connected_addr) might not work for you?

    If so, perhaps while the peer info is all saved to storage, maybe the peer manager doesn't actually load it on boot, so pm_next_peer_id_get ends up not iterating over all saved peers?

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

BLE Bond info is lost when Bangle.JS 2 is turned off

Posted by Avatar for user156416 @user156416

Actions