[{"data":1,"prerenderedAt":485},["ShallowReactive",2],{"developer-note-cpatch-v3":3},{"id":4,"title":5,"author":6,"body":7,"date":476,"description":477,"extension":478,"meta":479,"navigation":480,"path":481,"seo":482,"stem":483,"__hash__":484},"developerNotes\u002Fdevelopers\u002Fnotes\u002Fcpatch-v3.md","CPatch v3: Instruction Set Aware Patches","Jordan Yates",{"type":8,"value":9,"toc":463},"minimark",[10,15,19,128,132,135,138,141,157,160,165,168,178,185,196,199,203,218,225,228,232,235,244,255,264,271,283,290,293,297,300,303,351,354,357,404,408,411],[11,12,14],"h2",{"id":13},"tldr-median-patch-size-of-14","TLDR - Median Patch Size of 1.4%",[16,17,18],"p",{},"Infuse-IoT's binary diff firmware update tool has just received some major improvements:",[20,21,22,73],"table",{},[23,24,25],"thead",{},[26,27,28,32,56,59,70],"tr",{},[29,30,31],"th",{},"Firmware Type",[29,33,34,35,47,48],{},"Average Reduction",[36,37,38],"sup",{},[39,40,46],"a",{"href":41,"ariaDescribedBy":42,"dataFootnoteRef":44,"id":45},"#user-content-fn-1",[43],"footnote-label","","user-content-fnref-1","1"," ",[36,49,50],{},[39,51,55],{"href":52,"ariaDescribedBy":53,"dataFootnoteRef":44,"id":54},"#user-content-fn-2",[43],"user-content-fnref-2","2",[29,57,58],{},"Largest Reduction",[29,60,61,62],{},"Median Patch",[36,63,64],{},[39,65,69],{"href":66,"ariaDescribedBy":67,"dataFootnoteRef":44,"id":68},"#user-content-fn-3",[43],"user-content-fnref-3","3",[29,71,72],{},"How",[74,75,76,94,111],"tbody",{},[26,77,78,82,85,88,91],{},[79,80,81],"td",{},"Deployed Firmware",[79,83,84],{},"6%",[79,86,87],{},"9%",[79,89,90],{},"9.4%",[79,92,93],{},"Improved encoder",[26,95,96,99,102,105,108],{},[79,97,98],{},"Future Generic Firmware",[79,100,101],{},"31%",[79,103,104],{},"62%",[79,106,107],{},"5.2%",[79,109,110],{},"New 16-bit delta encoding instruction",[26,112,113,116,119,122,125],{},[79,114,115],{},"Future Thumb-2 (Cortex-M) Firmware",[79,117,118],{},"61%",[79,120,121],{},"96%",[79,123,124],{},"1.4%",[79,126,127],{},"Instruction-set-aware construction",[11,129,131],{"id":130},"details","Details",[16,133,134],{},"One of Infuse-IoT's great features is the ability to perform device firmware updates (DFU) without\nneeding to send the entire new firmware image to the device. Instead, we enable constructing a\nsmaller patch file which contains a set of instructions that the running application can use to\nconstruct the new firmware image, using the current firmware as a base.",[16,136,137],{},"This approach was already resulting in huge data-volume and energy-consumption wins with the original\npatch scheme, but recent improvements really surprised us. While the median patch\nsize was already only 9.8% (49 kB for a 500 kB image), our new instruction-set-aware encoding\nhas brought that down to just 1.4% (7 kB for a 500 kB image). And because that value is a median,\nhalf of all patches are even smaller than that!",[16,139,140],{},"With firmware updates that small, suddenly performing a fleet upgrade over NB-IoT NTN or LoRaWAN is\nbecoming practical for real-world deployments.",[16,142,143,144,150,151,156],{},"For more details about the design goals and original implementation of CPatch, see my presentation at\nthe 2025 Zephyr Developer Summit (",[39,145,149],{"href":146,"rel":147},"https:\u002F\u002Fstatic.sched.com\u002Fhosted_files\u002Fosseu2025\u002F9b\u002FZDS%202025%20-%20CPatch.pdf",[148],"nofollow","Slides",",\n",[39,152,155],{"href":153,"rel":154},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=cU1x0g924uY",[148],"Recording",").",[16,158,159],{},"The graphs presented below broadly cover three different application types; Bluetooth-only applications (320-400 kB),\nnRF91 LTE applications (500-600 kB), and nRF7002 Wi-Fi applications (~900 kB).",[161,162,164],"h3",{"id":163},"cpatch-v11","CPatch v1.1",[16,166,167],{},"The size gains for already deployed firmware came from one prompt and 20 minutes of spinning:",[169,170,171],"blockquote",{},[16,172,173,174],{},"Using the same patching instructions, create a more efficient diff encoder for ",[175,176,177],"code",{},"cpatch.py",[16,179,180,181,184],{},"The original encoder used a greedy approach with post-processing heuristics, while Claude Opus 5.5\ncreated a more optimal graph-based ",[175,182,183],{},"shortest-path"," encoder.",[16,186,187],{},[188,189],"img",{"alt":190,"className":191,"height":193,"src":194,"width":195},"Scatter plot of patch size as a percentage of target image size, comparing the shortest-path encoder (median 9.4%) with the original greedy encoder (median 9.8%)",[192],"rounded-lg",600,"\u002Fimages\u002Fblog\u002F20261026_shortest_path.png",800,[16,197,198],{},"This was purely an encoding-side change, so the smaller patches can be immediately used with\nalready compiled and deployed firmware.",[161,200,202],{"id":201},"cpatch-v20","CPatch v2.0",[16,204,205,206,209,210,213,214,217],{},"Future generic firmware images can take advantage of the v2.0 encoding (",[175,207,208],{},"shortest-path-delta","),\nwhich re-uses the v1 instructions and adds in a more efficient mode for encoding address shifts\nover 1 byte. While the original ",[175,211,212],{},"PATCH"," instruction is a sequence of counts to copy followed by\nsingle-byte values to replace, ",[175,215,216],{},"PATCH_DELTA"," encodes 16-bit shifts which can be cached with a\n1-byte index. This results in both less instruction overhead and less 'new' data being required,\nassuming that the address shifts are not completely random.",[16,219,220],{},[188,221],{"alt":222,"className":223,"height":193,"src":224,"width":195},"Scatter plot of patch size as a percentage of target image size, comparing the shortest-path-delta encoder (median 5.2%) with the original greedy encoder (median 9.8%)",[192],"\u002Fimages\u002Fblog\u002F20261026_shortest_path_delta.png",[16,226,227],{},"Note that achieving these gains requires an application compiled with v2.0 format support,\nso the first upgrade after updating Infuse-IoT SDK still needs to use the v1.1 format.",[161,229,231],{"id":230},"cpatch-v30","CPatch v3.0",[16,233,234],{},"Finally, a completely new patch encoding scheme was derived by Claude Opus 5.5, using the corpus\nof 300 releases as the ground truth of how real applications differ over time:",[169,236,237],{},[16,238,239,240,243],{},"Create an alternate implementation of ",[175,241,242],{},"cpatch"," that maintains the same design goals\n(linear output write, no dynamic memory, no patch compression, minimal state) but\nisn't restricted to the current instruction format. The header format should remain\nthe same.",[16,245,246,247,250,251,254],{},"After additional massaging, we ended up with an encoder that was almost magic in its efficiency.\nNo longer are we fighting in the middle of the pack for encoding efficiency versus other schemes\nlike ",[175,248,249],{},"JoJoDiff"," and ",[175,252,253],{},"bsdiff4","; we are resoundingly beating them without compromising on our design goals:",[256,257,262],"pre",{"className":258,"code":260,"language":261,"meta":44},[259],"language-text","1.10.0+d43d6563 -> 1.11.0+ca2a3746\nOriginal File: 519500 bytes\n     New File: 522228 bytes\n\nTool Comparison:\n          CPatch:  67558 12.94% (greedy, v1.0)\n          CPatch:  64369 12.33% (shortest-path, v1.1)\n          CPatch:  36107  6.91% (shortest-path-delta, v2.0)\n          CPatch:   9142  1.75% (stream-thumb2, v3.0)\n        JojoDiff:  86820 16.62%\n         bsdiff4:  35834  6.86%\n          xdelta: 119791 22.94%\n","text",[175,263,260],{"__ignoreMap":44},[16,265,266,267,270],{},"This new encoder, which we call ",[175,268,269],{},"stream-thumb2",", derives its efficiency by being instruction set aware.\nThis is a patch algorithm optimised specifically for the ARM Thumb-2 instruction set, not for generic binary\ndata. The key insight is that most changes in a firmware image are due to memory addresses shifting, for\nexample in branch instructions. But the instruction set itself constrains where these shifts can be, so\na lot more information can be implicit, if the patch applier does the work of scanning for these branches\nas it is applying the patch.",[16,272,273,274,282],{},"The end result is an encoding scheme that is much more computationally intensive to create, and\nrequires marginally more resources",[36,275,276],{},[39,277,281],{"href":278,"ariaDescribedBy":279,"dataFootnoteRef":44,"id":280},"#user-content-fn-4",[43],"user-content-fnref-4","4"," in the applier, but results in patch files so small that\nthe tradeoff is easily worth it.",[16,284,285],{},[188,286],{"alt":287,"className":288,"height":193,"src":289,"width":195},"Scatter plot of patch size as a percentage of target image size, comparing the stream-thumb2 encoder (median 1.4%) with the original greedy encoder (median 9.8%)",[192],"\u002Fimages\u002Fblog\u002F20261026_stream_thumb2.png",[16,291,292],{},"Note that achieving these gains requires an application compiled with v3.0 format support,\nso the first upgrade after updating Infuse-IoT SDK still needs to use either the v1.1 or v2.0 format.",[161,294,296],{"id":295},"hardware-test","Hardware Test",[16,298,299],{},"To see how CPatch v3 does on a realistic update, we built two versions of a small test application for the nRF54L15 DK.\nBoth are about 300 kB and talk to the host over a 115200 bps serial link. The \"original\" version runs a periodic logging task and\nblinks an LED once a second. The \"updated\" version is the kind of change a real product release brings. It logs twice\nas often and adds a SoC temperature task, which pulls in the sensor and zbus subsystems. It also adds a new source file\nthat handles a button press, and changes the LED to a double blink. None of these changes are large, but together they\nshift code and data across the whole image. Every branch and pointer that crosses a moved region changes too, which is\nwhat makes binary patches between releases bigger than the source changes suggest.",[16,301,302],{},"That moved code is what v3 is built for. When v3 copies a block from the old image, it updates the Thumb-2 branches and\npointers inside the block for their new locations, instead of sending them again as new data. The patch sizes show the\ndifference:",[20,304,305,321],{},[23,306,307],{},[26,308,309,312,315,318],{},[29,310,311],{},"Update",[29,313,314],{},"v2 patch",[29,316,317],{},"v3 patch",[29,319,320],{},"Reduction",[74,322,323,337],{},[26,324,325,328,331,334],{},[79,326,327],{},"Upgrade (original → updated)",[79,329,330],{},"14,356 B",[79,332,333],{},"2,774 B",[79,335,336],{},"81%",[26,338,339,342,345,348],{},[79,340,341],{},"Downgrade (updated → original)",[79,343,344],{},"12,817 B",[79,346,347],{},"1,331 B",[79,349,350],{},"90%",[16,352,353],{},"The v3 upgrade patch is under 1% of the new image size, five times smaller than v2. The downgrade patch is almost ten times smaller.\nThis discrepancy can be explained by noticing that the upgrade contains new code, which requires relocations and new data,\nwhile removing code is primarily about relocations.",[16,355,356],{},"Updating the relocated branches and pointers takes extra work on the device, so v3 patches take longer to apply. On the nRF54L15, the\nupgrade took 1.94 s to apply instead of 1.31 s, and the downgrade 1.50 s instead of 1.31 s. That extra time is more than made up for\nby sending less data. At the roughly 7 kB\u002Fs our serial link reached, here is the whole update, from the start of the transfer to\nrebooting into the new image:",[20,358,359,374],{},[23,360,361],{},[26,362,363,365,368,371],{},[29,364,311],{},[29,366,367],{},"v2 total",[29,369,370],{},"v3 total",[29,372,373],{},"Faster by",[74,375,376,390],{},[26,377,378,381,384,387],{},[79,379,380],{},"Upgrade",[79,382,383],{},"3.6 s",[79,385,386],{},"2.6 s",[79,388,389],{},"28%",[26,391,392,395,398,401],{},[79,393,394],{},"Downgrade",[79,396,397],{},"3.4 s",[79,399,400],{},"1.9 s",[79,402,403],{},"44%",[11,405,407],{"id":406},"conclusion","Conclusion",[16,409,410],{},"The new CPatch v3.0 encoding scheme opens up new possibilities for remote firmware management, enabling non-trivial\nfirmware changes over extremely constrained links without draining your battery or your wallet. With the EU Cyber Resilience\nAct (CRA) requiring that products can receive security updates, device firmware updates are becoming mandatory, not optional.\nInfuse-IoT can be your superpower for enabling full updates over communications technologies where you pay for every byte.",[412,413,416,421],"section",{"className":414,"dataFootnotes":44},[415],"footnotes",[11,417,420],{"className":418,"id":43},[419],"sr-only","Footnotes",[422,423,424,436,445,454],"ol",{},[425,426,428,429],"li",{"id":427},"user-content-fn-1","Mean patch size relative to original encoder ",[39,430,435],{"href":431,"ariaLabel":432,"className":433,"dataFootnoteBackref":44},"#user-content-fnref-1","Back to reference 1",[434],"data-footnote-backref","↩",[425,437,439,440],{"id":438},"user-content-fn-2","The test dataset of 300 patches contains a mix of upgrade types including routine application updates, Zephyr major version upgrades and full application type switches. ",[39,441,435],{"href":442,"ariaLabel":443,"className":444,"dataFootnoteBackref":44},"#user-content-fnref-2","Back to reference 2",[434],[425,446,448,449],{"id":447},"user-content-fn-3","Median size of patch file compared to target firmware image size ",[39,450,435],{"href":451,"ariaLabel":452,"className":453,"dataFootnoteBackref":44},"#user-content-fnref-3","Back to reference 3",[434],[425,455,457,458],{"id":456},"user-content-fn-4","The v3.0 patch applier is ~1.5 kB larger than the v2.0 version, and uses a ~1 kB\nRAM cache to reduce the number of hits to external flash for relocation information. If the patch\nfile is on internal flash, this can be tuned down with Kconfig. ",[39,459,435],{"href":460,"ariaLabel":461,"className":462,"dataFootnoteBackref":44},"#user-content-fnref-4","Back to reference 4",[434],{"title":44,"searchDepth":464,"depth":464,"links":465},2,[466,467,474,475],{"id":13,"depth":464,"text":14},{"id":130,"depth":464,"text":131,"children":468},[469,471,472,473],{"id":163,"depth":470,"text":164},3,{"id":201,"depth":470,"text":202},{"id":230,"depth":470,"text":231},{"id":295,"depth":470,"text":296},{"id":406,"depth":464,"text":407},{"id":43,"depth":464,"text":420},"2026-10-26","Reducing patch file sizes by up to 96%.","md",{},true,"\u002Fdevelopers\u002Fnotes\u002Fcpatch-v3",{"title":5,"description":477},"developers\u002Fnotes\u002Fcpatch-v3","xZKNyTZfK4CilzRa99XKNj4GtWbjm5eE-5JkjyZdIoU",1793755872603]