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