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 */ 076 public void close() { 077 try { 078 if (read) { 079 buffIn.close(); 080 in.close(); 081 } 082 name = null; 083 } catch (IOException e) { 084 log.warn("Exception closing hex file", e); 085 name = null; 086 } 087 } 088 089 090 /** 091 * DProcess record if required 092 * @param r hex record 093 */ 094 protected void checkRecord(HexRecord r) { 095 096 } 097 098 /** 099 * Read one hex record from the file 100 * 101 * @return the hex record 102 * @throws java.io.IOException on read error. 103 */ 104 protected HexRecord readOneRecord() throws IOException { 105 HexRecord r; 106 try { 107 r = new HexRecord(this); 108 } catch (IOException e) { 109 log.error("Exception reading hex record", e); 110 throw new IOException(e); 111 } 112 113 checkRecord(r); 114 115 return r; 116 } 117 118 /** 119 * Read a hex file. 120 * <p> 121 * Read hex records and store TYPE_DATA records in the array. 122 * 123 * @throws IOException on read error 124 */ 125 public void read() throws IOException { 126 HexRecord r; 127 128 lineNo = 0; 129 endRecord = 0; 130 131 do { 132 r = readOneRecord(); 133 r.setLineNo(lineNo); 134 hexRecords[lineNo] = r; 135 if (r.type == HexRecord.EXT_ADDR) { 136 // Extended address record so update the record address 137 address = (r.data[0]& 0xff) * 256 * 65536 + (r.data[1] & 0xff) * 65536; 138// hexRecords[lineNo].address = address; 139 log.debug("Found extended adress record for address {}", Integer.toHexString(address)); 140 continue; 141 } 142 if (r.type == HexRecord.TYPE_DATA) { 143 address = (address & 0xffff0000) + r.getAddress(); 144 hexRecords[lineNo].address = address; 145 log.debug("Hex record for address {}", Integer.toHexString(hexRecords[lineNo].address)); 146 // Keep track of start and end addresses that have been read 147 if (address < progStart) { 148 progStart = address; 149 } 150 if ((address + r.len) > progEnd) { 151 progEnd = address + r.len; 152 } 153 } 154 if (r.type == HexRecord.END) { 155 endRecord = lineNo; 156 } 157 lineNo++; 158 } while (r.type != HexRecord.END); 159 log.debug("Done reading hex file"); 160 } 161 162 163 /** 164 * Read a character from the hex file 165 * @return the character 166 * @throws IOException from the underlying read operation 167 */ 168 public int readChar() throws IOException { 169 return buffIn.read(); 170 } 171 172 173 /** 174 * Read a hex byte. 175 * 176 * @return the byte 177 * @throws IOException from the underlying read operation 178 */ 179 public int rdHexByte() throws IOException { 180 int hi = rdHexDigit(); 181 int lo = rdHexDigit(); 182 return hi * 16 + lo; 183 } 184 185 186 /** 187 * Read a single hex digit. 188 * 189 * @return int representing a single hex digit 0 - f. 190 * @throws IOException from the underlying read operation or if there's an invalid hex digit 191 */ 192 private int rdHexDigit() throws IOException { 193 int b = buffIn.read(); 194 if ((b >= '0') && (b <= '9')) { 195 b = b - '0'; 196 } else if ((b >= 'A') && (b <= 'F')) { 197 b = b - 'A' + 10; 198 } else if ((b >= 'a') && (b <= 'f')) { 199 b = b - 'a' + 10; 200 } else { 201 log.error("Invalid hex digit {}", b); 202 throw new IOException(Bundle.getMessage("HexInvalidDigit")); 203 } 204 return (byte) b; 205 } 206 207 208 /** 209 * Get current address. 210 * 211 * @return int the current address 212 */ 213 public int getAddress() { 214 return address; 215 } 216 217 218 /** 219 * Get current file line number 220 * 221 * @return the file number 222 */ 223 public int getLineNo() { 224 return lineNo; 225 } 226 227 228 /** 229 * Return the next TYPE_DATA record from the file 230 * 231 * @return the next hex record 232 * @throws ArrayIndexOutOfBoundsException when needed 233 */ 234 public HexRecord getNextRecord() throws ArrayIndexOutOfBoundsException { 235 return hexRecords[readIndex++]; 236 } 237 238 239 /** 240 * Get the hex record for a given address 241 * 242 * Expected that the address will be the start address of a record but will 243 * return the first record that encompasses the address and increment the 244 * index to point at the next record. 245 * 246 * @param addr The address 247 * @return the hex record 248 * @throws ArrayIndexOutOfBoundsException when needed 249 */ 250 public Optional<HexRecord> getRecordForAddress(int addr) throws ArrayIndexOutOfBoundsException { 251 HexRecord r; 252 readIndex = 0; 253 254 while (true) { 255 try { 256 r = hexRecords[readIndex++]; 257 if ((r.type == HexRecord.TYPE_DATA) 258 && (addr >= r.address) && (addr < (r.address + r.len))) { 259 return Optional.of(r); 260 } 261 } catch (ArrayIndexOutOfBoundsException ex) { 262 return Optional.empty(); 263 } 264 } 265 } 266 267 268 /** 269 * Get the file parameters 270 * 271 * Create an invalid parameter set of necessary. Override in hardware specific 272 * implementations. 273 * 274 * @return CBUS parameters from the file 275 */ 276 public CbusParameters getParams() { 277 return new CbusParameters(); 278 } 279 280 281 /** 282 * Return the lowest address read from the hex file 283 * 284 * @return the highest address 285 */ 286 public int getProgStart() { 287 return progStart; 288 } 289 290 291 /** 292 * Return the highest address read from the hex file 293 * 294 * @return the highest address 295 */ 296 public int getProgEnd() { 297 return progEnd; 298 } 299 300 301 /** 302 * close the open file 303 */ 304 public void dispose() { 305 close(); 306 } 307 308 309 private final static Logger log = LoggerFactory.getLogger(HexFile.class); 310 311}