001package jmri.jmrix.sprog.update; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.io.BufferedInputStream; 006import java.io.BufferedOutputStream; 007import java.io.File; 008import java.io.FileInputStream; 009import java.io.FileOutputStream; 010import java.io.IOException; 011import java.util.Arrays; 012 013import jmri.util.swing.JmriJOptionPane; 014 015/** 016 * Class to encapsulate an intel format hex file and methods to manipulate it. 017 * Intended use is as an input format for new program code to be sent to a 018 * hardware device via some bootloading process. 019 * 020 * @author Andrew Crosland Copyright (C) 2010 021 */ 022public class SprogHexFile extends jmri.util.JmriJFrame { 023 024 private File file; 025 private FileInputStream in; 026 private BufferedInputStream buffIn; 027 private FileOutputStream out; 028 private BufferedOutputStream buffOut; 029 private int address = 0; 030 private int type; 031 private int len; 032 private int data[]; 033 private boolean read; 034 private int lineNo = 0; 035 private int charIn; 036 private String name; 037 038 // Hex file record types 039 static final byte EXT_ADDR = 4; 040 static final byte DATA = 0; 041 static final byte END = 1; 042 // Maximum record length 043 private static final int MAX_LEN = (255 + 1 + 2 + 1 + 1) * 2; 044 // Offsets of fields within the record 045 private static final int LEN = 0; 046 private static final int ADDRH = 1; 047 private static final int ADDRL = 2; 048 private static final int TYPE = 3; 049 050 public SprogHexFile(String fileName) { 051 name = fileName; 052 file = new File(fileName); 053 } 054 055 /** 056 * @return name of the open file 057 */ 058 @Override 059 public String getName() { 060 return name; 061 } 062 063 /** 064 * Open hex file for reading. 065 * 066 * @return boolean true if successful 067 */ 068 public boolean openRd() { 069 read = true; 070 try { 071 // Create an input reader based on the file, so we can read its data. 072 in = new FileInputStream(file); 073 buffIn = new BufferedInputStream(in); 074 // Assume addresses start at 0 for hex files that do not have an initial type 4 record 075 address = 0; 076 //line = new StringBuffer(""); 077 return true; 078 } catch (IOException e) { 079 return false; 080 } 081 } 082 083 /** 084 * Open file for writing. 085 * 086 * @return boolean true if successful 087 */ 088 public boolean openWr() { 089 read = false; 090 try { 091 // Create an output writer based on the file, so we can write. 092 out = new FileOutputStream(file); 093 buffOut = new BufferedOutputStream(out); 094 return true; 095 } catch (IOException e) { 096 return false; 097 } 098 } 099 100 /** 101 * Close the currently open file. 102 */ 103 public void close() { 104 try { 105 if (read) { 106 buffIn.close(); 107 in.close(); 108 } else { 109 buffOut.flush(); 110 buffOut.close(); 111 out.close(); 112 } 113 name = null; 114 } catch (IOException e) { 115 116 } 117 } 118 119 /** 120 * Read a record (line) from the hex file. 121 * <p> 122 * If it's an extended address record then update the address 123 * and read the next line. Returns the data length. 124 * 125 * @return int the data length of the record, or 0 if no data 126 */ 127 @SuppressFBWarnings(value = "DLS_DEAD_LOCAL_STORE") 128 // False positive 129 public int read() { 130 // Make space for the maximum size record to be read 131 int record[] = new int[MAX_LEN]; 132 do { 133 record = readLine(); 134 if (type == EXT_ADDR) { 135 // Get new extended address and read next line 136 address = address & 0xffff 137 + record[4] * 256 * 65536 + record[5] * 65536; 138 record = readLine(); 139 } 140 } while ((type != DATA) && (type != END)); 141 if (type == END) { 142 return 0; 143 } 144 data = new int[len]; 145 for (int i = 0; i < len; i++) { 146 data[i] = record[TYPE + 1 + i]; 147 } 148 return len; 149 } 150 151 /** 152 * Read a line from the hex file and verify the checksum. 153 * 154 * @return int[] the array of bytes read from the file 155 */ 156 public int[] readLine() { 157 // Make space for the maximum size record to be read 158 int record[] = new int[MAX_LEN]; 159 int checksum = 0; 160 // Read ":" 161 try { 162 while (((charIn = buffIn.read()) == 0xd) 163 || (charIn == 0xa)) { 164 // skip 165 } 166 if (charIn != ':') { 167 if (log.isDebugEnabled()) { 168 log.debug("HexFile.readLine no colon at start of line {}", lineNo); 169 } 170 return new int[]{-1}; 171 } 172 } catch (IOException e) { 173 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("IoErrorReadingHexFile"), 174 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 175 if (log.isDebugEnabled()) { 176 log.debug("I/O Error reading hex file!{}", e.toString()); 177 } 178 } 179 // length of data 180 record[LEN] = rdHexByte(); 181 checksum += record[LEN]; 182 // High address 183 record[ADDRH] = rdHexByte(); 184 checksum += record[ADDRH]; 185 // Low address 186 record[ADDRL] = rdHexByte(); 187 checksum += record[ADDRL]; 188 // record type 189 record[TYPE] = rdHexByte(); 190 checksum += record[TYPE]; 191 // update address 192 address = (address & 0xffff0000) + record[ADDRH] * 256 + record[ADDRL]; 193 type = record[TYPE]; 194 if (type != END) { 195 len = record[LEN]; 196 for (int i = 1; i <= len; i++) { 197 record[TYPE + i] = rdHexByte(); 198 checksum += record[TYPE + i]; 199 } 200 } 201 int fileCheck = rdHexByte(); 202 if (((checksum + fileCheck) & 0xff) != 0) { 203 log.error("HexFile.readLine bad checksum at line {}", lineNo); 204 } 205 lineNo++; 206 return record; 207 } 208 209 /** 210 * Read a hex byte. 211 * 212 * @return byte the byte that was read 213 */ 214 private int rdHexByte() { 215 int hi = rdHexDigit(); 216 int lo = rdHexDigit(); 217 if ((hi < 16) && (lo < 16)) { 218 return (hi * 16 + lo); 219 } else { 220 return 0; 221 } 222 } 223 224 /** 225 * Read a single hex digit. 226 * 227 * @return 16 if digit is invalid. byte low nibble contains the hex digit read. 228 * high nibble set if error. 229 */ 230 private int rdHexDigit() { 231 int b = 0; 232 try { 233 b = buffIn.read(); 234 if ((b >= '0') && (b <= '9')) { 235 b = b - '0'; 236 } else if ((b >= 'A') && (b <= 'F')) { 237 b = b - 'A' + 10; 238 } else if ((b >= 'a') && (b <= 'f')) { 239 b = b - 'a' + 10; 240 } else { 241 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("InvalidHexDigitAtLine", lineNo), 242 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 243 log.error("Format Error! Invalid hex digit at line {}", lineNo); 244 b = 16; 245 } 246 } catch (IOException e) { 247 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("IoErrorReadingHexFile"), 248 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 249 log.error("I/O Error reading hex file!{}", e.toString()); 250 } 251 return (byte) b; 252 } 253 254 /** 255 * Write a line to the hex file. 256 * 257 * @param addr int the starting address of the data 258 * @param type byte the type of data record being written 259 * @param data byte[] the array of bytes to be written 260 */ 261 public void write(int addr, byte type, byte[] data) { 262 // Make space for the record to be written 263 byte record[] = new byte[data.length + 1 + 2 + 1]; 264 if (addr / 0x10000 != address / 0x10000) { 265 // write an extended address record 266 byte[] extAddr = { 267 2, 0, 0, EXT_ADDR, 0, (byte) (addr / 0x10000)}; 268 writeLine(extAddr); 269 } 270 // update current address 271 address = addr; 272 // save length, address and record type 273 record[LEN] = (byte) (data.length); 274 record[ADDRH] = (byte) (address / 0x100); 275 record[ADDRL] = (byte) (address & 0xff); 276 record[TYPE] = type; 277 // copy the data 278 for (int i = 0; i < data.length; i++) { 279 record[TYPE + 1 + i] = data[i]; 280 } 281 // write the record 282 writeLine(record); 283 } 284 285 /** 286 * Write an extended address record. 287 * 288 * @param addr the extended address 289 */ 290 public void wrExtAddr(int addr) { 291 write(0, EXT_ADDR, new byte[]{(byte) (addr / 256), (byte) (addr & 0xff)}); 292 } 293 294 /** 295 * Write an end of file record. 296 * 297 */ 298 public void wrEof() { 299 writeLine(new byte[]{0, 0, 0, END}); 300 } 301 302 /** 303 * Get the type of the last record read from the hex file. 304 * 305 * @return byte the record type 306 */ 307 public int getRecordType() { 308 return type; 309 } 310 311 /** 312 * Get the length of the last record read from the hex file. 313 * 314 * @return byte the length 315 */ 316 public int getLen() { 317 return len; 318 } 319 320 /** 321 * Get current address. 322 * 323 * @return int the current address 324 */ 325 public int getAddress() { 326 return address; 327 } 328 329 /** 330 * Get lower byte of current address. 331 * 332 * @return byte the lower byte of the address 333 */ 334 public byte getAddressL() { 335 return (byte) (address & 0xff); 336 } 337 338 /** 339 * Get high (middle) byte of current address. 340 * 341 * @return byte the high (middle) byte of the address 342 */ 343 public byte getAddressH() { 344 return (byte) ((address / 0x100) & 0xff); 345 } 346 347 /** 348 * Get upper byte of current address. 349 * 350 * @return byte the upper byte of the address 351 */ 352 public byte getAddressU() { 353 return (byte) (address / 0x10000); 354 } 355 356 /** 357 * Get data from last record read. 358 * 359 * @return byte[] array of data bytes 360 */ 361 public int[] getData() { 362 return Arrays.copyOf(data, data.length); 363 } 364 365 /** 366 * Write a byte array to the hex file, prepending ":" and appending checksum 367 * and carriage return. 368 * 369 * @param data byte[] array of data bytes top be written 370 */ 371 private void writeLine(byte[] data) { 372 int checksum = 0; 373 try { 374 buffOut.write(':'); 375 for (int i = 0; i < data.length; i++) { 376 writeHexByte(data[i]); 377 checksum += data[i]; 378 } 379 checksum = checksum & 0xff; 380 if (checksum > 0) { 381 checksum = 256 - checksum; 382 } 383 writeHexByte((byte) checksum); 384 buffOut.write('\n'); 385 } catch (IOException e) { 386 387 } 388 } 389 390 /** 391 * Write a byte as two hex characters. 392 * 393 * @param b byte the byte to be written 394 */ 395 private void writeHexByte(byte b) { 396 int i = b; 397 // correct for byte being -128 to +127 398 if (b < 0) { 399 i = 256 + b; 400 } 401 writeHexDigit((byte) (i / 16)); 402 writeHexDigit((byte) (i & 0xf)); 403 } 404 405 /** 406 * Write a single hex digit. 407 * 408 * @param b byte low nibble contains the hex digit to be written 409 */ 410 private void writeHexDigit(byte b) { 411 try { 412 if (b > 9) { 413 buffOut.write(b - 9 + 0x40); 414 } else { 415 buffOut.write(b + 0x30); 416 } 417 } catch (IOException e) { 418 } 419 } 420 421 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SprogHexFile.class); 422 423}