Given the low bandwidth of LoRa, firmware updates over LoRa will be very slow even if differential updates and compression are used. Let's put that aside for the moment and assume that the new firmware can be successfully sent to your MCU.
Since the firmware transmission is slow and unreliable, you must split your MCU's flash memory into two halves:
- 1st half: current firmware, used for executing the code
- 2nd half: next firmware version, incomplete, being received
As you can see, the flash memory needs to be at least double the size of currently used firmware.
The firmware is then received in many pieces. Most likely, a protocol is needed to ask for the re-transmission of pieces that haven't be received at all or received with errors.
Once the next firmware version has been completely received and verified (some check sum), you basically have two options:
Simple firmware update
A small firmware update routine is copied into RAM and started. It overwrites the current firmware with the next version and then resets the MCU.
This is straight-forward to implement. However, if the update fails (e.g. power hick-up), the device no longer works (colloquially "is bricked") and needs to be reprogrammed manually. So it's not excalty robust and suitable for device in the field.
Robust firmware update
The robust firmware update overwrites the vector tables with the addresses of the new firmware version and then resets the MCU. So the currently active firmware is alternately in the lower and upper half of the flash memory.
While this approach is more robust, it's more difficult to achieve. Either the firmware must be created using position-independent code or it must be specifically created with addresses either in the lower or upper half of the flash address range. None of this is supported out-of-the-box by the typical development environment.
As this is not trivial to achieve, some STM32 MCUs offer a dual bank mode to address this problem. It basically offers the robust firmware update without the hassles of a specially crafted firmware.
It of course make sense to only transmit the difference between two firmware versions. I'm however not certain if it helps a lot. Small changes to a function can lead to new addresses for variables and functions throughout the entire code, thereby introducing far more differences than you would expect.
Anyway, jDiff certainly isn't the tool of choice as it prefers speed over patch size.
Some links