From f800893ac8feec67afdcdaecd0bc3a9bd02292fc Mon Sep 17 00:00:00 2001
From: andreas <andreas@wellenvogel.de>
Date: Sat, 9 Sep 2023 17:11:47 +0200
Subject: [PATCH] use chip id for all image checks

---
 extra_script.py              |  6 +--
 tools/flashtool/flashtool.py | 96 +++++++++++++++++++++++-------------
 web/index.js                 | 27 +++++-----
 webinstall/install.js        | 30 ++++-------
 webinstall/installUtil.js    | 22 +++++----
 5 files changed, 99 insertions(+), 82 deletions(-)

diff --git a/extra_script.py b/extra_script.py
index a91c147..8a50062 100644
--- a/extra_script.py
+++ b/extra_script.py
@@ -268,10 +268,7 @@ def prebuild(env):
     genereateUserTasks(os.path.join(outPath(), TASK_INCLUDE))
     generateFile(os.path.join(basePath(),XDR_FILE),os.path.join(outPath(),XDR_INCLUDE),generateXdrMappings)
     version="dev"+datetime.now().strftime("%Y%m%d")
-    env.Append(CPPDEFINES=[
-        ('GWDEVVERSION',version),
-        ('PIO_ENV_BOARD',env.get('BOARD_MCU'))
-        ])
+    env.Append(CPPDEFINES=[('GWDEVVERSION',version)])
 
 def cleangenerated(source, target, env):
     od=outPath()
@@ -293,4 +290,3 @@ env.Append(
 )
 #script does not run on clean yet - maybe in the future
 env.AddPostAction("clean",cleangenerated)
-#print(env.Dump())
diff --git a/tools/flashtool/flashtool.py b/tools/flashtool/flashtool.py
index 3a30162..92fef05 100755
--- a/tools/flashtool/flashtool.py
+++ b/tools/flashtool/flashtool.py
@@ -72,7 +72,7 @@ def main():
             tk.Label(frame,textvariable=self.fileInfo).grid(row=row,column=0,columnspan=2,sticky="ew")
             row+=1
             self.flashInfo=tk.StringVar()
-            self.flashInfo.set("Address 0x1000")
+            self.flashInfo.set("Full Flash")
             tk.Label(frame,textvariable=self.flashInfo).grid(row=row,column=0,columnspan=2,sticky='ew',pady=10)
             row+=1
             btFrame=tk.Frame(frame)
@@ -96,7 +96,7 @@ def main():
         def updateFlashInfo(self):
             if self.mode.get() == 1:
                 #full
-                self.flashInfo.set("Address 0x1000")
+                self.flashInfo.set("Full Flash")
             else:
                 self.flashInfo.set("Erase(otadata): 0xe000...0xffff, Address 0x10000")
         def changeMode(self):
@@ -141,50 +141,67 @@ def main():
                 self.interrupt=False
                 raise Exception("User cancel")
 
-        FULLOFFSET=61440
+        UPDATE_ADDR = 0x10000
         HDROFFSET = 288
         VERSIONOFFSET = 16
         NAMEOFFSET = 48
+        IDOFFSET=12 #2 byte chipid
         MINSIZE = HDROFFSET + NAMEOFFSET + 32
         CHECKBYTES = {
-            0: 0xe9,  # image magic
             288: 0x32,  # app header magic
             289: 0x54,
             290: 0xcd,
             291: 0xab
         }
+        #flash addresses for full images based on chip id
+        FLASH_ADDR={
+            0: 0x1000,
+            9: 0
+        }
 
         def getString(self,buffer, offset, len):
             return buffer[offset:offset + len].rstrip(b'\0').decode('utf-8')
 
-        def getFirmwareInfo(self,ih,imageFile,offset):
-            buffer = ih.read(self.MINSIZE)
-            if len(buffer) != self.MINSIZE:
-                return self.setErr("invalid image file %s, to short"%imageFile)
-            for k, v in self.CHECKBYTES.items():
-                if buffer[k] != v:
-                    return self.setErr("invalid magic at %d, expected %d got %d"
+        def getFirmwareInfo(self,filename,isFull):
+            with open(filename,"rb") as ih:
+                buffer = ih.read(self.MINSIZE)
+                if len(buffer) != self.MINSIZE:
+                    return self.setErr("invalid image file %s, to short"%filename)
+                if buffer[0] != 0xe9:
+                    return self.setErr("invalid magic in file, expected 0xe9 got 0x%02x"%buffer[0])
+                chipid= buffer[self.IDOFFSET]+256*buffer[self.IDOFFSET+1]
+                flashoffset=self.FLASH_ADDR.get(chipid)
+                if flashoffset is None:
+                    return self.setErr("unknown chip id in image %d",chipid);
+                if isFull:
+                    offset=self.UPDATE_ADDR-flashoffset;
+                    offset-=self.MINSIZE
+                    ih.seek(offset,os.SEEK_CUR)
+                    buffer=ih.read(self.MINSIZE)
+                    if len(buffer) != self.MINSIZE:
+                        return self.setErr("invalid image file %s, to short"%filename)
+                    if buffer[0] != 0xe9:
+                        return self.setErr("invalid magic in file, expected 0xe9 got 0x%02x"%buffer[0])
+                for k, v in self.CHECKBYTES.items():
+                    if buffer[k] != v:
+                        return self.setErr("invalid magic at %d, expected %d got %d"
                                     % (k+offset, v, buffer[k]))
-            name = self.getString(buffer, self.HDROFFSET + self.NAMEOFFSET, 32)
-            version = self.getString(buffer, self.HDROFFSET + self.VERSIONOFFSET, 32)
-            return {'error':False,'info':"%s:%s"%(name,version)}
+                name = self.getString(buffer, self.HDROFFSET + self.NAMEOFFSET, 32)
+                version = self.getString(buffer, self.HDROFFSET + self.VERSIONOFFSET, 32)
+                chipid= buffer[self.IDOFFSET]+256*buffer[self.IDOFFSET+1]
+                return {
+                    'error':False,
+                    'info':"%s:%s"%(name,version),
+                    'chipid':chipid,
+                    'flashbase':flashoffset if isFull else self.UPDATE_ADDR
+                }
 
         def setErr(self,err):
             return {'error':True,'info':err}
         def checkImageFile(self,filename,isFull):
             if not os.path.exists(filename):
                 return self.setErr("file %s not found"%filename)
-            with open(filename,"rb") as fh:
-                offset=0
-                if isFull:
-                    b=fh.read(1)
-                    if len(b) != 1:
-                        return self.setErr("unable to read header")
-                    if b[0] != 0xe9:
-                        return self.setErr("invalid magic in file, expected 0xe9 got 0x%02x"%b[0])
-                    st=fh.seek(self.FULLOFFSET)
-                    offset=self.FULLOFFSET
-                return self.getFirmwareInfo(fh,filename,offset)
+            return self.getFirmwareInfo(filename,isFull)
 
         def runCheck(self):
             self.text_widget.delete("1.0", "end")
@@ -202,7 +219,7 @@ def main():
             if info['error']:
                 print("ERROR: %s" % info['info'])
                 return
-            return {'port':port,'isFull':isFull}
+            return {'port':port,'isFull':isFull,'info':info}
 
         def runEspTool(self,command):
             for b in self.actionButtons:
@@ -219,25 +236,36 @@ def main():
             for b in self.actionButtons:
                 b.configure(state=tk.NORMAL)
             self.cancelButton.configure(state=tk.DISABLED)
-        def buttonCheck(self):
+        def verifyChip(self):
             param = self.runCheck()
             if not param:
+                print("check failed")
                 return
-            print("Settings OK")
-            command = ['--chip', 'ESP32', '--port', param['port'], 'chip_id']
-            self.runEspTool(command)
+            imageChipId=param['info']['chipid']
+            chip=esptool.ESPLoader.detect_chip(param['port'])
+            print("Detected chip %s, id=%d"%(chip.CHIP_NAME,chip.IMAGE_CHIP_ID))
+            if (chip.IMAGE_CHIP_ID != imageChipId):
+                print("##Error: chip id in image %d does not match detected chip"%imageChipId)
+                return
+            print("Checks OK")
+            param['chipname']=chip.CHIP_NAME
+            return param
+        def buttonCheck(self):
+            param = self.verifyChip()
+            
+            
 
         def buttonFlash(self):
-            param=self.runCheck()
+            param=self.verifyChip()
             if not param:
                 return
             if param['isFull']:
-                command=['--chip','ESP32','--port',param['port'],'write_flash','0x1000',self.filename.get()]
+                command=['--chip',param['chipname'],'--port',param['port'],'write_flash',str(param['info']['flashbase']),self.filename.get()]
                 self.runEspTool(command)
             else:
-                command=['--chip','ESP32','--port',param['port'],'erase_region','0xe000','0x2000']
+                command=['--chip',param['chipname'],'--port',param['port'],'erase_region','0xe000','0x2000']
                 self.runEspTool(command)
-                command = ['--chip', 'ESP32', '--port', param['port'], 'write_flash', '0x10000', self.filename.get()]
+                command = ['--chip', param['chipname'], '--port', param['port'], 'write_flash', str(param['info']['flashbase']), self.filename.get()]
                 self.runEspTool(command)
 
 
diff --git a/web/index.js b/web/index.js
index 699a82e..4c49f9c 100644
--- a/web/index.js
+++ b/web/index.js
@@ -1564,14 +1564,14 @@ function uploadBin(ev){
         .then(function (result) {
             let currentType;
             let currentVersion;
-            let chiptype;
+            let chipid;
             forEl('.status-version', function (el) { currentVersion = el.textContent });
             forEl('.status-fwtype', function (el) { currentType = el.textContent });
-            forEl('.status-chiptype', function (el) { chiptype = el.textContent });
+            forEl('.status-chipid', function (el) { chipid = el.textContent });
             let confirmText = 'Ready to update firmware?\n';
-            if (result.chip.match(/^@@/) && (result.chip.substr(2) != chiptype)){
-                confirmText += "WARNING: the chiptype in the image ("+result.chip.substr(2);
-                confirmText +=") does not match the current chip type ("+chiptype+").\n";
+            if (result.chipId != chipid){
+                confirmText += "WARNING: the chipid in the image ("+result.chipId;
+                confirmText +=") does not match the current chip id ("+chipid+").\n";
             }
             if (currentType != result.fwtype) {
                 confirmText += "WARNING: image has different type: " + result.fwtype + "\n";
@@ -1649,8 +1649,8 @@ function uploadBin(ev){
 let HDROFFSET=288;
 let VERSIONOFFSET=16;
 let NAMEOFFSET=48;
-let CHIPOFFSET=NAMEOFFSET+64;
-let MINSIZE = HDROFFSET + CHIPOFFSET + 32;
+let MINSIZE = HDROFFSET + NAMEOFFSET + 32;
+let CHIPIDOFFSET=12; //2 bytes chip id here
 let imageCheckBytes={
     0: 0xe9, //image magic
     288: 0x32, //app header magic
@@ -1669,6 +1669,10 @@ function decodeFromBuffer(buffer,start,length){
             start+length));
     return rt;        
 }
+function getChipId(buffer){
+    if (buffer.length < CHIPIDOFFSET+2) return -1;
+    return buffer[CHIPIDOFFSET]+256*buffer[CHIPIDOFFSET+1];
+}
 function checkImageFile(file){
     return new Promise(function(resolve,reject){
         if (! file) reject("no file");
@@ -1685,11 +1689,11 @@ function checkImageFile(file){
         }
         let version=decodeFromBuffer(content,HDROFFSET+VERSIONOFFSET,32);
         let fwtype=decodeFromBuffer(content,HDROFFSET+NAMEOFFSET,32);
-        let chip=decodeFromBuffer(content,HDROFFSET+CHIPOFFSET,32);
+        let chipId=getChipId(content);
         let rt={
             fwtype:fwtype,
             version: version,
-            chip:chip
+            chipId:chipId
         };
         resolve(rt);    
     });
@@ -1751,10 +1755,7 @@ window.addEventListener('load', function () {
             checkImageFile(file)
                 .then(function(res){
                     forEl('#imageProperties',function(iel){
-                        let txt="";
-                        if (res.chip.match(/^@@/)){
-                            txt=res.chip.substr(2)+", "
-                        }
+                        let txt="["+res.chipId+"] ";
                         txt+=res.fwtype+", "+res.version;
                         iel.textContent=txt;
                         iel.classList.remove("error");
diff --git a/webinstall/install.js b/webinstall/install.js
index 6db2cb1..fb84eeb 100644
--- a/webinstall/install.js
+++ b/webinstall/install.js
@@ -10,8 +10,7 @@ import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm";
     const HDROFFSET = 288;
     const VERSIONOFFSET = 16;
     const NAMEOFFSET = 48;
-    const CHIPOFFSET=NAMEOFFSET+64;
-    const MINSIZE = HDROFFSET + CHIPOFFSET + 32;
+    const MINSIZE = HDROFFSET + NAMEOFFSET + 32;
     const CHIPIDOFFSET=12; //2 bytes chip id here
     const imageMagic=0xe9; //at byte 0
     const imageCheckBytes = {
@@ -22,6 +21,7 @@ import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm";
     };
     /**
      * map of our known chip ids to flash starts for full images
+     * 0 - esp32 - starts at 0x1000
      * 9 - esp32s3 - starts at 0
      */
     const FLASHSTART={
@@ -47,8 +47,8 @@ import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm";
         let prfx=isFull?"full":"update";
         let startOffset=0;
         let flashStart=UPDATE_START;
+        let chipId=getChipId(content);
         if (isFull){
-            let chipId=getChipId(content);
             if (chipId < 0) throw new Error(prfx+"image: no valid chip id found");
             let flashStart=FLASHSTART[chipId];
             if (flashStart === undefined) throw new Error(prfx+"image: unknown chip id "+chipId);
@@ -69,11 +69,10 @@ import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm";
         }
         let version = decodeFromBuffer(content, startOffset+ HDROFFSET + VERSIONOFFSET, 32);
         let fwtype = decodeFromBuffer(content, startOffset+ HDROFFSET + NAMEOFFSET, 32);
-        let chip=decodeFromBuffer(content,startOffset+HDROFFSET+CHIPOFFSET,32);
         let rt = {
             fwtype: fwtype,
             version: version,
-            chip:chip,
+            chipId:chipId,
             flashStart: flashStart
         };
         return rt;
@@ -133,22 +132,13 @@ import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm";
         hFrame.textContent='';
         let h=addEl('h2',undefined,hFrame,`ESP32 Install ${info}`)
     }
-    const checkChip= async (chipFamily,data,isFull)=>{
+    const checkChip= async (chipId,data,isFull)=>{
         let info=checkImage(data,isFull);
-        if (info.chip && info.chip.match(/^@@/)){
-            let chip=info.chip.substr(2);
-            let compare=chipFamily.toLowerCase().replace(/[^a-z0-9]*/g,'');
-            if (compare !== chip){
-                let res=confirm("different chip signatures - image("+chip+"), chip ("+compare+")\nUse this image any way?");
-                if (! res) throw new Error("user abort");
-            }
-            return;
+        if (info.chipId != chipId){
+            let res=confirm("different chip signatures - image("+chipId+"), chip ("+info.chipId+")\nUse this image any way?");
+            if (! res) throw new Error("user abort");
         }
-        //for now only ESP32/ESP32-S3
-        if (chipFamily != "ESP32" && chipFamily != "ESP32-S3"){
-            throw new Error(`unexpected chip family ${chipFamily}, expected ESP32/ESP32-S3`);
-        }
-        return;
+        return info;
     }
     const baudRates=[1200,
         2400,
@@ -339,7 +329,6 @@ import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm";
                 await espInstaller.runFlash(
                     true,
                     fullData,
-                    vinfo.flashStart,
                     version,
                     checkChip
                 )
@@ -353,7 +342,6 @@ import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm";
                 await espInstaller.runFlash(
                     false,
                     updateData,
-                    uinfo.flashStart,
                     version,
                     checkChip
                 )
diff --git a/webinstall/installUtil.js b/webinstall/installUtil.js
index bb0eedd..c46f2af 100644
--- a/webinstall/installUtil.js
+++ b/webinstall/installUtil.js
@@ -61,6 +61,7 @@ class ESPInstaller{
         this.base=import.meta.url.replace(/[^/]*$/,"install.php");
         this.consoleDevice=undefined;
         this.consoleReader=undefined;
+        this.imageChipId=undefined;
     }
     /**
      * get an URL query parameter
@@ -133,6 +134,7 @@ class ESPInstaller{
             this.espLoaderTerminal.writeLine(`chip: ${foundChip}`);
             await this.esploader.flash_id();
             this.chipFamily = this.esploader.chip.CHIP_NAME;
+            this.imageChipId = this.esploader.chip.IMAGE_CHIP_ID;
             this.espLoaderTerminal.writeLine(`chipFamily: ${this.chipFamily}`);
         } catch (e) {
             this.disconnect();
@@ -185,6 +187,10 @@ class ESPInstaller{
         this.checkConnected();
         return this.chipFamily;
     }
+    getChipId(){
+        this.checkConnected();
+        return this.imageChipId;
+    }
     /**
      * flass the device
      * @param {*} fileList : an array of entries {data:blob,address:number}
@@ -236,7 +242,7 @@ class ESPInstaller{
      * @param {*} repo 
      * @param {*} version 
      * @param {*} assetName
-     * @param {*} checkChip will be called with the found chip and the data and the isFull flag
+     * @param {*} checkChip will be called with the found chipId and the data and the isFull flag
      *            must return an info with the flashStart being set
      * @returns 
      */
@@ -247,7 +253,7 @@ class ESPInstaller{
             if (!imageData || imageData.length == 0) {
                 throw new Error(`no image data fetched`);
             }
-            let info=await checkChip(this.getChipFamily(),imageData,isFull);
+            let info=await checkChip(this.getChipId(),imageData,isFull);
             let fileList = [
                 { data: imageData, address: info.flashAddress }
             ];
@@ -267,20 +273,18 @@ class ESPInstaller{
     /**
      * directly run the flash
      * @param {*} isFull 
-     * @param {*} address 
      * @param {*} imageData the data to be flashed
      * @param {*} version the info shown in the dialog
-     * @param {*} checkChip will be called with the found chip and the data
+     * @param {*} checkChip will be called with the found chipId and the data
+     *            must return an info with flashAddress
      * @returns 
      */
-    async runFlash(isFull,imageData,address,version,checkChip){
+    async runFlash(isFull,imageData,version,checkChip){
         try {
             await this.connect();
-            if (checkChip) {
-                await checkChip(this.getChipFamily(),imageData,isFull); //just check
-            }
+            let info= await checkChip(this.getChipId(),imageData,isFull); //just check
             let fileList = [
-                { data: imageData, address: address }
+                { data: imageData, address: info.flashAddress }
             ];
             let txt = isFull ? "baseImage (all data will be erased)" : "update";
             if (!confirm(`ready to install ${version}\n${txt}`)) {