001package jmri.jmrix.can.cbus.swing.bootloader; 002 003import java.io.*; 004import java.util.Optional; 005 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * Class to encapsulate an intel format hex file for a CBUS PIC. 011 * 012 * Assumes hex record addresses are 8-byte aligned and that addresses increase 013 * monotonically. 014 * 015 * @author Andrew Crosland Copyright (C) 2020, 2022 016 */ 017public class HexFile { 018 019 protected String name; 020 protected File file; 021 protected FileInputStream in; 022 protected BufferedInputStream buffIn; 023 // Number of hex records 024 protected static final int MAX_HEX_SIZE = 10000; 025 026 protected int address = 0; 027 protected boolean read; 028 protected int lineNo = 0; 029 private int progStart = 99999999; 030 private int progEnd = 0; 031 032 // Storage for raw program data 033 protected HexRecord [] hexRecords; 034 protected int readIndex = 0; 035 protected int endRecord = 0; 036 037 038 /** 039 * Create a new HexFile object and initialize data to unprogrammed state. 040 * 041 * @param fileName file name to use for the hex file 042 */ 043 public HexFile(String fileName) { 044 name = fileName; 045 file = new File(fileName); 046 047 hexRecords = new HexRecord[MAX_HEX_SIZE]; 048 } 049 050 051 /** 052 * @return name of the open file 053 */ 054 public String getName() { 055 return name; 056 } 057 058 059 /** 060 * Open hex file for reading. 061 * 062 * @throws FileNotFoundException if pre-defined file can't be opened 063 */ 064 public void openRd() throws FileNotFoundException { 065 read = true; 066 // Create an input reader based on the file, so we can read its data. 067 in = new FileInputStream(file); 068 buffIn = new BufferedInputStream(in); 069 address = 0; 070 } 071 072 073 /** 074 * Close the currently open file. 075 * <p> 076 * Safe to call if the file was never opened, or has already been closed. 077 * The records already read remain available in memory. 078 */ 079 public void close() { 080 try { 081 if (buffIn != null) { 082 buffIn.close(); 083 } else if (in != null) { 084 in.close(); 085 } 086 } catch (IOException e) { 087 log.warn("Exception closing hex file", e); 088 } 089 buffIn = null; 090 in = null; 091 read = false; 092 name = null; 093 } 094 095 096 /** 097 * DProcess record if required 098 * @param r hex record 099 */ 100 protected void checkRecord(HexRecord r) { 101 102 } 103 104 /** 105 * Read one hex record from the file 106 * 107 * @return the hex record 108 * @throws java.io.IOException on read error. 109 */ 110 protected HexRecord readOneRecord() throws IOException { 111 HexRecord r; 112 try { 113 r = new HexRecord(this); 114 } catch (IOException e) { 115 log.error("Exception reading hex record", e); 116 throw new IOException(e); 117 } 118 119 return r; 120 } 121 122 /** 123 * Read a hex file. 124 * <p> 125 * Read hex records and store TYPE_DATA records in the array. 126 * <p> 127 * Only data, end of file and extended linear address records are 128 * understood. Any other record type causes the file to be rejected. 129 * 130 * @throws IOException on read error, or if the file contains an 131 * unsupported record type, or too many records 132 */ 133 public void read() throws IOException { 134 HexRecord r; 135 136 lineNo = 0; 137 endRecord = 0; 138 139 do { 140 if (lineNo >= hexRecords.length) { 141 log.error("Too many records in hex file, maximum is {}", hexRecords.length); 142 throw new IOException("Too many records in hex file, maximum is " + hexRecords.length); 143 } 144 r = readOneRecord(); 145 if ((r.type != HexRecord.TYPE_DATA) && (r.type != HexRecord.END) && (r.type != HexRecord.EXT_ADDR)) { 146 // Segment address, start address or unknown record types are not supported 147 log.error("Unsupported hex record type {} at line {}", r.type, lineNo); 148 throw new IOException("Unsupported hex record type " + r.type + " at line " + lineNo); 149 } 150 r.setLineNo(lineNo); 151 hexRecords[lineNo] = r; 152 if (r.type == HexRecord.EXT_ADDR) { 153 // Extended address record so update the record address. 154 // Not kept in the array (lineNo is not incremented) so the 155 // next record overwrites it, only data and end records are stored. 156 address = (r.data[0]& 0xff) * 256 * 65536 + (r.data[1] & 0xff) * 65536; 157// hexRecords[lineNo].address = address; 158 log.debug("Found extended adress record for address {}", Integer.toHexString(address)); 159 continue; 160 } 161 if (r.type == HexRecord.TYPE_DATA) { 162 address = (address & 0xffff0000) + r.getAddress(); 163 hexRecords[lineNo].address = address; 164 log.debug("Hex record for address {}", Integer.toHexString(hexRecords[lineNo].address)); 165 // Keep track of start and end addresses that have been read 166 if (address < progStart) { 167 progStart = address; 168 } 169 if ((address + r.len) > progEnd) { 170 progEnd = address + r.len; 171 } 172 } 173 if (r.type == HexRecord.END) { 174 endRecord = lineNo; 175 } 176 // Process the record now that its full address is known 177 checkRecord(r); 178 lineNo++; 179 } while (r.type != HexRecord.END); 180 log.debug("Done reading hex file"); 181 } 182 183 184 /** 185 * Read a character from the hex file 186 * @return the character 187 * @throws IOException from the underlying read operation 188 */ 189 public int readChar() throws IOException { 190 return buffIn.read(); 191 } 192 193 194 /** 195 * Read a hex byte. 196 * 197 * @return the byte 198 * @throws IOException from the underlying read operation 199 */ 200 public int rdHexByte() throws IOException { 201 int hi = rdHexDigit(); 202 int lo = rdHexDigit(); 203 return hi * 16 + lo; 204 } 205 206 207 /** 208 * Read a single hex digit. 209 * 210 * @return int representing a single hex digit 0 - f. 211 * @throws IOException from the underlying read operation or if there's an invalid hex digit 212 */ 213 private int rdHexDigit() throws IOException { 214 int b = buffIn.read(); 215 if ((b >= '0') && (b <= '9')) { 216 b = b - '0'; 217 } else if ((b >= 'A') && (b <= 'F')) { 218 b = b - 'A' + 10; 219 } else if ((b >= 'a') && (b <= 'f')) { 220 b = b - 'a' + 10; 221 } else { 222 log.error("Invalid hex digit {}", b); 223 throw new IOException(Bundle.getMessage("HexInvalidDigit")); 224 } 225 return (byte) b; 226 } 227 228 229 /** 230 * Get current address. 231 * 232 * @return int the current address 233 */ 234 public int getAddress() { 235 return address; 236 } 237 238 239 /** 240 * Get current file line number 241 * 242 * @return the file number 243 */ 244 public int getLineNo() { 245 return lineNo; 246 } 247 248 249 /** 250 * Return the next TYPE_DATA record from the file 251 * <p> 252 * Once the records read from the file are exhausted, an END record is 253 * returned so that callers stop cleanly. 254 * 255 * @return the next hex record 256 */ 257 public HexRecord getNextRecord() { 258 if ((readIndex >= 0) && (readIndex <= endRecord) && (readIndex < hexRecords.length) 259 && (hexRecords[readIndex] != null)) { 260 return hexRecords[readIndex++]; 261 } 262 log.warn("No more hex records to read at index {}", readIndex); 263 readIndex = endRecord + 1; 264 return new HexRecord(); 265 } 266 267 268 /** 269 * Get the hex record for a given address 270 * 271 * Expected that the address will be the start address of a record but will 272 * return the first record that encompasses the address and increment the 273 * index to point at the next record. 274 * <p> 275 * Only the records actually read from the file are searched. 276 * 277 * @param addr The address 278 * @return the hex record, or empty if no data record contains the address 279 */ 280 public Optional<HexRecord> getRecordForAddress(int addr) { 281 readIndex = 0; 282 283 for (int i = 0; (i <= endRecord) && (i < hexRecords.length); i++) { 284 HexRecord r = hexRecords[i]; 285 if (r == null) { 286 break; 287 } 288 if ((r.type == HexRecord.TYPE_DATA) 289 && (addr >= r.address) && (addr < (r.address + r.len))) { 290 readIndex = i + 1; 291 return Optional.of(r); 292 } 293 } 294 // Not found, leave the index pointing at the END record 295 readIndex = endRecord; 296 return Optional.empty(); 297 } 298 299 300 /** 301 * Get the file parameters 302 * 303 * Create an invalid parameter set of necessary. Override in hardware specific 304 * implementations. 305 * 306 * @return CBUS parameters from the file 307 */ 308 public CbusParameters getParams() { 309 return new CbusParameters(); 310 } 311 312 313 /** 314 * Return the lowest address read from the hex file 315 * 316 * @return the highest address 317 */ 318 public int getProgStart() { 319 return progStart; 320 } 321 322 323 /** 324 * Return the highest address read from the hex file 325 * 326 * @return the highest address 327 */ 328 public int getProgEnd() { 329 return progEnd; 330 } 331 332 333 /** 334 * close the open file 335 */ 336 public void dispose() { 337 close(); 338 } 339 340 341 private static final Logger log = LoggerFactory.getLogger(HexFile.class); 342 343}