001package jmri.jmrit; 002 003import java.io.BufferedReader; 004import java.io.File; 005import java.io.FileInputStream; 006import java.io.FileNotFoundException; 007import java.io.IOException; 008import java.io.InputStreamReader; 009import java.io.Writer; 010import java.util.ArrayList; 011import jmri.util.StringUtil; 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015/** 016 * Models (and provides utility functions for) board memory as expressed in .hex 017 * files and .DMF files. 018 * <p> 019 * Provides mechanisms to read and interpret firmware update files into an 020 * internal data structure. Provides mechanisms to in create firmware update 021 * files from an internal data structure. Provides mechanisms to allow other 022 * agents to access the data in the internal data structures for the purpose of 023 * sending the data to the device to be updated. Supports the Intel "I8HEX" file 024 * format and a derivative ".dmf" file format created by Digitrax. 025 * <p> 026 * Support for the Intel "I8HEX" format includes support for record types "00" 027 * and "01". The "I8HEX" format implements records with a LOAD OFFSET field of 028 * 16 bits. To support the full 24-bit addressing range provided by the LocoNet 029 * messaging protocol for firmware updates, this class is able to interpret 030 * record type "04" (Extended Linear Address) records for input files with 031 * 16-bit LOAD OFFSET fields. Record type "04" are typically found in the Intel 032 * "I32HEX" 32-bit addressing format. Because the class supports only 24 bits of 033 * address, interpretation of the "04" record type requires that the upper 8 034 * bits of the 16-bit data field be 0. 035 * <p> 036 * Support for some .hex files emitted by some tool-sets requires support for 037 * the Extended Segment Address record type (record type "02"), which may be 038 * used in I16HEX format files. This version of the {@link #readHex} method 039 * supports the Extended Segment Address record type ONLY when the segment 040 * specified in the data field is 0x0000. 041 * <p> 042 * Support for the Digitrax ".DMF" format is an extension to the "I8HEX" 043 * support. This extension supports interpretation of the 24-bit LOAD OFFSET 044 * fields used in .DFM files. The class does not allow files with 24-bit LOAD 045 * OFFSET fields to use the "04" (Extended Linear Address) record type unless 046 * its data field is 0x0000. 047 * <p> 048 * Support for the ".DMF" format allows capture of Key/Value pairs which may be 049 * embedded in special comments within a .DMF file. This support is enabled for 050 * I8HEX files. 051 * <p> 052 * The class treats the information within a file's records as having 053 * "big-endian" address values in the record LOAD OFFSET field. The INFO or DATA 054 * field information is interpreted as 8-bit values, with the left-most value in 055 * the INFO or DATA field corresponding to the address specified by the record's 056 * LOAD OFFSET field plus the influence of the most recent previous Extended 057 * Linear Address record, if any. 058 * <p> 059 * The INFO or DATA field for Extended Linear Address records is interpreted as 060 * a big-endian value, where bits 7 thru 0 of the data field value are used as 061 * bits 23 thru 16 of the effective address, while bits 15 thru 0 of the 062 * effective address are from the 16-bit LOAD OFFSET of each data record. Bits 063 * 15 thru 8 of the Extended Linear Address record INFO or DATA field must be 0 064 * because of the 24-bit address limitation of this implementation. 065 * <p> 066 * The class does not have to know anything about filenames or filename 067 * extensions. Instead, to read a file, an instantiating method will create a 068 * {@link File} object and pass that object to {@link #readHex}. 069 * Similarly, when writing the contents of data storage to a file, the 070 * instantiating method will create a {@link File} and an associated 071 * {@link Writer} and pass the {@link Writer} object to 072 * {@link #writeHex}. The mechanisms implemented within this class do not 073 * know about or care about the filename or its extension and do not use that 074 * information as part of its file interpretation or file creation. 075 * <p> 076 * The class is implemented with a maximum of 24 bits of address space, with up 077 * to 256 pages of up to 65536 bytes per page. A "sparse" implementation of 078 * memory is modeled, where only occupied pages are allocated within the Java 079 * system's memory. 080 * <hr> 081 * The Intel "Hexadecimal Object File Format File Format Specification" 082 * uses the following terms for the fields of the record: 083 * <dl> 084 * <dt>RECORD MARK</dt><dd>first character of a record. ':'</dd> 085 * 086 * <dt>RECLEN</dt><dd>a two-character specifier of the number of bytes of information 087 * in the "INFO or DATA" field. Immediately follows the RECORD 088 * MARK charcter. Since each byte within the "INFO or DATA" field is 089 * represented by two ASCII characters, the data field contains twice 090 * the RECLEN value number of ASCII characters.</dd> 091 * 092 * <dt>LOAD OFFSET</dt><dd>specifies the 16-bit starting load offset of the data bytes. 093 * This applies only to "Data" records, so this class requires that 094 * this field must encode 0x0000 for all other record types. The LOAD 095 * OFFSET field immediately follows the RECLEN field. 096 * <p> 097 * Note that for the 24-bit addressing format used with ".DMF" 098 * files, this field is a 24-bit starting load offset, represented by 099 * six ASCII characters, rather than the four ASCII characters 100 * specified in the Intel specification.</dd> 101 * 102 * <dt>RECTYP</dt><dd>RECord TYPe - indicates the record type for this record. The 103 * RECTYPE field immediately follows the LOAD OFFSET field.</dd> 104 * 105 * <dt>INFO or DATA</dt><dd>(Optional) field containing information or data which is 106 * appropriate to the RECTYP. Immediately follows the RECTYP field. 107 * contains RECLEN times 2 characters, where consecutive pairs of 108 * characters represent one byte of info or data.</dd> 109 * 110 * <dt>CHKSUM</dt><dd>8-bit Checksum, computed using the hexadecimal byte values represented 111 * by the character pairs in RECLEN, LOAD OFFSET, RECTYP, and INFO 112 * or DATA fields, such that the computed sum, when added to the 113 * CKSUM value, sums to an 8-bit value of 0x00.</dd> 114 * </dl> 115 * This information based on the Intel document "Hexadecimal Object File Format 116 * Specification", Revision A, January 6, 1988. 117 * <p> 118 * Mnemonically, a properly formatted record would appear as: 119 * <pre> 120 * :lloooott{dd}cc 121 * where: 122 * ':' is the RECORD MARK 123 * "ll" is the RECLEN 124 * "oooo" is the 16-bit LOAD OFFSET 125 * "tt" is the RECTYP 126 * "{dd}" is the INFO or DATA field, containing zero or more pairs of 127 * characters of Info or Data associated with the record 128 * "cc" is the CHKSUM 129 * </pre> 130 * <p> 131 * and a few examples of complaint records would be: 132 * <ul> 133 * <li>:02041000FADE07 134 * <li>:020000024010AC 135 * <li>:00000001FF 136 * </ul> 137 * 138 * @author Bob Jacobsen Copyright (C) 2005, 2008 139 * @author B. Milhaupt Copyright (C) 2014, 2017 140 */ 141public class MemoryContents { 142 143 // Class (static) variables 144 145 /* For convenience, a page of local storage of data is sized to equal one 146 * "segment" within an input file. As such, the terms "page" and "segment" 147 * are used interchangeably throughout here. 148 * 149 * The number of pages is chosen to match the 24-bit address space. 150 */ 151 private static final int DEFAULT_MEM_VALUE = -1; 152 private static final int PAGESIZE = 0x10000; 153 private static final int PAGES = 256; 154 155 private static final int RECTYP_DATA_RECORD = 0; 156 private static final String STRING_DATA_RECTYP = StringUtil.twoHexFromInt(RECTYP_DATA_RECORD); 157 private static final int RECTYP_EXTENDED_SEGMENT_ADDRESS_RECORD = 2; 158 private static final int RECTYP_EXTENDED_LINEAR_ADDRESS_RECORD = 4; 159 private static final int RECTYP_EOF_RECORD = 1; 160 private static final int CHARS_IN_RECORD_MARK = 1; 161 private static final int CHARS_IN_RECORD_LENGTH = 2; 162 private static final int CHARS_IN_RECORD_TYPE = 2; 163 private static final int CHARS_IN_EACH_DATA_BYTE = 2; 164 private static final int CHARS_IN_CHECKSUM = 2; 165 private static final int CHARS_IN_24_BIT_ADDRESS = 6; 166 private static final int CHARS_IN_16_BIT_ADDRESS = 4; 167 168 private static final char LEADING_CHAR_COMMENT = '#'; // NOI18N 169 private static final char LEADING_CHAR_KEY_VALUE = '!'; // NOI18N 170 private static final char LEADING_CHAR_RECORD_MARK = ':'; // NOI18N 171 172 // Instance variables 173 /** 174 * Firmware data storage 175 * 176 * Implemented as a two-dimensional array where the first dimension 177 * represents the "page" number, and the second dimension represents the 178 * byte within the page of {@link #PAGESIZE} bytes. 179 */ 180 private final int[][] pageArray; 181 private int currentPage; 182 private int lineNum; 183 private boolean hasData; 184 private int curExtLinAddr; 185 private int curExtSegAddr; 186 187 /** 188 * Storage for Key/Value comment information extracted from key/value 189 * comments within a .DMF or .hex file 190 */ 191 private ArrayList<String> keyValComments = new ArrayList<String>(1); 192 193 /** 194 * Defines the LOAD OFFSET field type used/expected for records in "I8HEX" 195 * and ".DMF" file formats. 196 * <p> 197 * When reading a file using the {@link #readHex} method, the value is 198 * inferred from the first record and then used to validate the remaining 199 * records in the file. 200 * <p> 201 * This value must be properly set before invoking the {@link #writeHex} 202 * method. 203 */ 204 private LoadOffsetFieldType loadOffsetFieldType = LoadOffsetFieldType.UNDEFINED; 205 206 /** 207 */ 208 public MemoryContents() { 209 pageArray = new int[PAGES][]; 210 currentPage = -1; 211 hasData = false; 212 curExtLinAddr = 0; 213 curExtSegAddr = 0; 214 keyValComments = new ArrayList<String>(1); 215 } 216 217 private boolean isPageInitialized(int page) { 218 return (pageArray[page] != null); 219 } 220 221 /** 222 * Initialize a single page of data storage, if and only if the page has not 223 * been initialized already. 224 * 225 */ 226 private void initPage(int page) { 227 if (pageArray[page] != null) { 228 if (log.isDebugEnabled()) { 229 log.debug("Method initPage was previously invoked for page {}", page); 230 } 231 return; 232 } 233 234 int[] largeArray = new int[PAGESIZE]; 235 for (int i = 0; i < PAGESIZE; i++) { 236 largeArray[i] = DEFAULT_MEM_VALUE; // default contents 237 } 238 pageArray[page] = largeArray; 239 } 240 241 /** 242 * Perform a read of a .hex file information into JAVA memory. Assumes that 243 * the file is of the Intel "I8HEX" format or the similar Digitrax ".DMF" 244 * format. Automatically infers the file type. Performs various checks upon 245 * the incoming data to help ensure proper interpretation of the file and to 246 * help detect corrupted files. Extracts "key/value" pair information from 247 * comments for use by the invoking method. 248 * <p> 249 * Integrity checks include: 250 * <ul> 251 * <li>Identification of LOAD OFFSET field type from first record 252 * <li>Verification that all subsequent records use the same LOAD OFFSET 253 * field type 254 * <li>Verification of checksum found at the end of each record 255 * <li>Verification of supported record types 256 * <li>Flagging of lines which are neither comment lines or records 257 * <li>Identification of a missing EOF record 258 * <li>Identification of any record after an EOF record 259 * <li>Identification of a file without any data record 260 * <li>Identification of any records which have extra characters after the 261 * checksum 262 * </ul> 263 * <p> 264 * When reading the file, {@link #readHex} infers the addressing format 265 * from the first record found in the file, and future records are 266 * interpreted using that addressing format. It is not necessary to 267 * pre-configure the addressing format before reading the file. This is a 268 * departure from previous versions of this method. 269 * <p> 270 * Blank lines are allowed and are ignored. 271 * <p> 272 * This code supports reading of files containing comments. Comment lines 273 * which begin with '#' are ignored. 274 * <p> 275 * Comment lines which * begin with '!' may encode Key/Value pair 276 * information. Such Key/Value pair information is used within the .DMF 277 * format to provide configuration information for firmware update 278 * mechanism. This class also extracts key/value pair comments "I8HEX" 279 * format files. After successful completion of the {@link #readHex} call, 280 * then the {@link #extractValueOfKey(String keyName)} method may be used to inspect individual key values. 281 * <p> 282 * Key/Value pair definition comment lines are of the format: 283 * <p> 284 * {@code ! KeyName: Value} 285 * 286 * @param filename string containing complete filename with path 287 * @throws FileNotFoundException if the file does not exist 288 * @throws MemoryFileRecordLengthException if a record line is too long 289 * or short 290 * @throws MemoryFileChecksumException if a record checksum does not 291 * match the computed record 292 * checksum 293 * @throws MemoryFileUnknownRecordType if a record contains an 294 * unsupported record type 295 * @throws MemoryFileRecordContentException if a record contains 296 * inappropriate characters 297 * @throws MemoryFileNoEOFRecordException if a file does not contain an 298 * EOF record 299 * @throws MemoryFileNoDataRecordsException if a file does not contain 300 * any data records 301 * @throws MemoryFileRecordFoundAfterEOFRecord if a file contains records 302 * after the EOF record 303 * @throws MemoryFileAddressingRangeException if a file contains an 304 * Extended Linear Address 305 * record outside of the 306 * supported address range 307 * @throws IOException if a file cannot be opened 308 * via newBufferedReader 309 */ 310 public void readHex(String filename) throws FileNotFoundException, 311 MemoryFileRecordLengthException, MemoryFileChecksumException, 312 MemoryFileUnknownRecordType, MemoryFileRecordContentException, 313 MemoryFileNoDataRecordsException, MemoryFileNoEOFRecordException, 314 MemoryFileRecordFoundAfterEOFRecord, MemoryFileAddressingRangeException, 315 IOException { 316 readHex(new File(filename)); 317 } 318 319 /** 320 * Perform a read of a .hex file information into JAVA memory. Assumes that 321 * the file is of the Intel "I8HEX" format or the similar Digitrax ".DMF" 322 * format. Automatically infers the file type. Performs various checks upon 323 * the incoming data to help ensure proper interpretation of the file and to 324 * help detect corrupted files. Extracts "key/value" pair information from 325 * comments for use by the invoking method. 326 * <p> 327 * Integrity checks include: 328 * <ul> 329 * <li>Identification of LOAD OFFSET field type from first record 330 * <li>Verification that all subsequent records use the same LOAD OFFSET 331 * field type 332 * <li>Verification of checksum found at the end of each record 333 * <li>Verification of supported record types 334 * <li>Flagging of lines which are neither comment lines or records 335 * <li>Identification of a missing EOF record 336 * <li>Identification of any record after an EOF record 337 * <li>Identification of a file without any data record 338 * <li>Identification of any records which have extra characters after the 339 * checksum 340 * </ul><p> 341 * When reading the file, {@link #readHex} infers the addressing format 342 * from the first record found in the file, and future records are 343 * interpreted using that addressing format. It is not necessary to 344 * pre-configure the addressing format before reading the file. This is a 345 * departure from previous versions of this method. 346 * <p> 347 * Blank lines are allowed and are ignored. 348 * <p> 349 * This code supports reading of files containing comments. Comment lines 350 * which begin with '#' are ignored. 351 * <p> 352 * Comment lines which * begin with '!' may encode Key/Value pair 353 * information. Such Key/Value pair information is used within the .DMF 354 * format to provide configuration information for firmware update 355 * mechanism. This class also extracts key/value pair comments "I8HEX" 356 * format files. After successful completion of this method, 357 * then the {@code #extractValueOfKey(String keyName)} method may be used to inspect individual key values. 358 * <p> 359 * Key/Value pair definition comment lines are of the format: 360 * <p> 361 * {@code ! KeyName: Value} 362 * 363 * @param file file to read 364 * @throws FileNotFoundException if the file does not exist 365 * @throws MemoryFileRecordLengthException if a record line is too long 366 * or short 367 * @throws MemoryFileChecksumException if a record checksum does not 368 * match the computed record 369 * checksum 370 * @throws MemoryFileUnknownRecordType if a record contains an 371 * unsupported record type 372 * @throws MemoryFileRecordContentException if a record contains 373 * inappropriate characters 374 * @throws MemoryFileNoEOFRecordException if a file does not contain an 375 * EOF record 376 * @throws MemoryFileNoDataRecordsException if a file does not contain 377 * any data records 378 * @throws MemoryFileRecordFoundAfterEOFRecord if a file contains records 379 * after the EOF record 380 * @throws MemoryFileAddressingRangeException if a file contains an 381 * Extended Linear Address 382 * record outside of the 383 * supported address range 384 * @throws IOException if a file cannot be opened 385 * via newBufferedReader 386 */ 387 public void readHex(File file) throws FileNotFoundException, 388 MemoryFileRecordLengthException, MemoryFileChecksumException, 389 MemoryFileUnknownRecordType, MemoryFileRecordContentException, 390 MemoryFileNoDataRecordsException, MemoryFileNoEOFRecordException, 391 MemoryFileRecordFoundAfterEOFRecord, MemoryFileAddressingRangeException, 392 IOException { 393 BufferedReader fileStream; 394 try { 395 fileStream = new BufferedReader(new InputStreamReader(new FileInputStream(file))); 396 } catch (IOException ex) { 397 throw new FileNotFoundException(ex.toString()); 398 } 399 400 this.clear(); // Ensure that the information storage is clear of any 401 // previous contents 402 currentPage = 0; 403 loadOffsetFieldType = LoadOffsetFieldType.UNDEFINED; 404 boolean foundDataRecords = false; 405 boolean foundEOFRecord = false; 406 407 keyValComments.clear(); // ensure that no key/value pair values are retained 408 //from a previous invocation. 409 410 lineNum = 0; 411 // begin reading the file 412 try { 413 //byte bval; 414 int ival; 415 String line; 416 while ((line = fileStream.readLine()) != null) { 417 // this loop reads one line per turn 418 lineNum++; 419 420 // decode line type 421 int len = line.length(); 422 if (len < 1) { 423 continue; // skip empty lines 424 } 425 if (line.charAt(0) == LEADING_CHAR_COMMENT) { 426 // human comment. Ignore it. 427 } else if (line.charAt(0) == LEADING_CHAR_KEY_VALUE) { 428 // machine comment; store it to allow for key/value extraction 429 keyValComments.add(line); 430 } else if (line.charAt(0) == LEADING_CHAR_RECORD_MARK) { 431 // hex file record - determine LOAD OFFSET field type (if not yet 432 // then interpret the record based on its RECTYP 433 434 int indexOfLastAddressCharacter; 435 if (loadOffsetFieldType == LoadOffsetFieldType.UNDEFINED) { 436 // Infer the file's LOAD OFFSET field type from the first record. 437 // It is sufficient to infer the LOAD OFFSET field type once, then 438 // interpret all future records as the same type without 439 // checking the type again, because the checksum verfication 440 // uses the LOAD OFFSET field type as part of the 441 // checksum verification. 442 443 loadOffsetFieldType = inferRecordAddressType(line); 444 445 if ((isLoadOffsetType16Bits()) 446 && (isLoadOffsetType24Bits())) { 447 // could not infer a valid addressing type. 448 String message = "Could not infer addressing type from" // NOI18N 449 + " line " + lineNum + "."; // NOI18N 450 logError(message); 451 throw new MemoryFileRecordContentException(message); 452 } 453 } 454 455 // Determine the index of the last character of the line which 456 // contains LOAD OFFSET field info 457 indexOfLastAddressCharacter = charsInAddress() + 2; 458 if (indexOfLastAddressCharacter < 0) { 459 // unknown LOAD OFFSET field type - cannot continue. 460 String message = "Fell thru with unknown loadOffsetFieldType value " // NOI18N 461 + loadOffsetFieldType + " for line" + lineNum + "."; // NOI18N 462 logError(message); 463 throw new MemoryFileAddressingRangeException(message); 464 } 465 466 // extract the RECTYP. 467 int recordType = Integer.valueOf(line.substring(indexOfLastAddressCharacter + 1, 468 indexOfLastAddressCharacter + 3), 16).intValue(); 469 if (log.isDebugEnabled()) { 470 log.debug("RECTYP = 0x{}", Integer.toHexString(recordType)); 471 } 472 473 // verify record character count 474 int count = extractRecLen(line); 475 if (len != CHARS_IN_RECORD_MARK + CHARS_IN_RECORD_LENGTH 476 + charsInAddress() 477 + CHARS_IN_RECORD_TYPE 478 + (count * CHARS_IN_EACH_DATA_BYTE) + CHARS_IN_CHECKSUM) { 479 // line length error - invalid record or invalid data 480 // length byte or incorrect LOAD OFFSET field type 481 String message 482 = "Data record line length is incorrect for " // NOI18N 483 + "inferred addressing type and for data " // NOI18N 484 + "count field in line " + lineNum;// NOI18N 485 logError(message); 486 throw new MemoryFileRecordLengthException(message); 487 } 488 489 // verify the checksum now that we know the RECTYP. 490 // Do this by calculating the checksum of all characters on 491 //line (except the ':' record mark), which should result in 492 // a computed checksum value of 0 493 int computedChecksum = calculate8BitChecksum(line.substring(CHARS_IN_RECORD_MARK)); 494 if (computedChecksum != 0x00) { 495 // line's checksum is incorrect. Find checksum of 496 // all but the checksum bytes 497 computedChecksum = calculate8BitChecksum( 498 line.substring( 499 CHARS_IN_RECORD_MARK, 500 line.length() 501 - CHARS_IN_RECORD_MARK 502 - CHARS_IN_CHECKSUM + 1) 503 ); 504 int expectedChecksum = Integer.parseInt(line.substring(line.length() - 2), 16); 505 String message = "Record checksum error in line " // NOI18N 506 + lineNum 507 + " - computed checksum = 0x" // NOI18N 508 + Integer.toHexString(computedChecksum) 509 + ", expected checksum = 0x" // NOI18N 510 + Integer.toHexString(expectedChecksum) 511 + "."; // NOI18N 512 logError(message); 513 throw new MemoryFileChecksumException(message); 514 } 515 516 if (recordType == RECTYP_DATA_RECORD) { 517 // Record Type 0x00 518 if (foundEOFRecord) { 519 // problem - data record happened after an EOF record was parsed 520 String message = "Found a Data record in line " // NOI18N 521 + lineNum + " after the EOF record"; // NOI18N 522 logError(message); 523 throw new MemoryFileRecordFoundAfterEOFRecord(message); 524 } 525 526 int recordAddress = extractLoadOffset(line); 527 528 recordAddress &= (isLoadOffsetType24Bits()) 529 ? 0x00FFFFFF : 0x0000FFFF; 530 531 // compute effective address (assumes cannot have 532 // non-zero values in both curExtLinAddr and 533 // curExtSegAddr) 534 int effectiveAddress = recordAddress + curExtLinAddr + curExtSegAddr; 535 536 if (addressAndCountIsOk(effectiveAddress, count) == false) { 537 // data crosses memory boundary that can be mis-interpreted. 538 // So refuse the file. 539 String message = "Data crosses boundary which could lead to " // NOI18N 540 + " mis-interpretation. Aborting read at line " // NOI18N 541 + line; 542 logError(message); 543 throw new MemoryFileAddressingRangeException(message); 544 } 545 546 int effectivePage = effectiveAddress / PAGESIZE; 547 if (!isPageInitialized(effectivePage)) { 548 initPage(effectivePage); 549 log.debug("effective address 0x{} is causing change to segment 0x{}", // NOI18N 550 Integer.toHexString(effectiveAddress), 551 Integer.toHexString(effectivePage)); 552 } 553 int effectiveOffset = effectiveAddress % PAGESIZE; 554 555 log.debug("Effective address 0x{}, effective page 0x{}, effective offset 0x{}", 556 Integer.toHexString(effectiveAddress), 557 Integer.toHexString(effectivePage), 558 Integer.toHexString(effectiveOffset)); 559 for (int i = 0; i < count; ++i) { 560 int startIndex = indexOfLastAddressCharacter + 3 + (i * 2); 561 // parse as hex into integer, then convert to byte 562 ival = Integer.valueOf(line.substring(startIndex, startIndex + 2), 16).intValue(); 563 pageArray[effectivePage][effectiveOffset++] = ival; 564 hasData = true; 565 } 566 foundDataRecords = true; 567 568 } else if (recordType == RECTYP_EXTENDED_SEGMENT_ADDRESS_RECORD) { 569 // parse Extended Segment Address record to check for 570 // validity 571 if (foundEOFRecord) { 572 String message 573 = "Found a Extended Segment Address record in line " // NOI18N 574 + lineNum 575 + " after the EOF record"; // NOI18N 576 logError(message); 577 throw new MemoryFileRecordFoundAfterEOFRecord(message); 578 } 579 580 int datacount = extractRecLen(line); 581 if (datacount != 2) { 582 String message = "Extended Segment Address record " // NOI18N 583 + "did not have 16 bits of data content." // NOI18N 584 + lineNum; 585 logError(message); 586 throw new MemoryFileRecordContentException(message); 587 } 588 int startpoint = indexOfLastAddressCharacter + 3; 589 // compute page number from '20-bit segment address' in record 590 int newPage = 16 * Integer.valueOf(line.substring(startpoint, 591 (startpoint + 2 * datacount)), 16).intValue(); 592 593 // check for an allowed segment value 594 if (newPage != 0) { 595 String message = "Unsupported Extended Segment Address " // NOI18N 596 + "Record data value 0x" // NOI18N 597 + Integer.toHexString(newPage) 598 + " in line " + lineNum; // NOI18N 599 logError(message); 600 throw new MemoryFileAddressingRangeException(message); 601 } 602 curExtLinAddr = 0; 603 curExtSegAddr = newPage; 604 if (newPage != currentPage) { 605 currentPage = newPage; 606 initPage(currentPage); 607 } 608 609 } else if (recordType == RECTYP_EXTENDED_LINEAR_ADDRESS_RECORD) { 610 // Record Type 0x04 611 if (foundEOFRecord) { 612 String message 613 = "Found a Extended Linear Address record in line " // NOI18N 614 + lineNum 615 + " after the EOF record"; // NOI18N 616 logError(message); 617 throw new MemoryFileRecordFoundAfterEOFRecord(message); 618 } 619 620 // validate that LOAD OFFSET field of record is all zeros. 621 if (extractLoadOffset(line) != 0) { 622 String message = "Extended Linear Address record has " // NOI18N 623 + "non-zero LOAD OFFSET field." // NOI18N 624 + lineNum; 625 logError(message); 626 throw new MemoryFileRecordContentException(message); 627 } 628 629 // Allow non-zero Extended Linear Address value ONLY if 16-bit addressing! 630 int datacount = extractRecLen(line); 631 if (datacount != 2) { 632 String message = "Expect data payload length of 2, " // NOI18N 633 + "found RECLEN value of " + // NOI18N 634 +extractRecLen(line) 635 + " in line " + lineNum; // NOI18N 636 logError(message); 637 throw new MemoryFileRecordContentException(message); 638 } 639 int startpoint = indexOfLastAddressCharacter + 3; 640 int tempPage = Integer.valueOf(line.substring(startpoint, 641 (startpoint + 2 * datacount)), 16).intValue(); 642 643 if ((tempPage != 0) && (isLoadOffsetType24Bits())) { 644 // disallow non-zero extended linear address if 24-bit addressing 645 String message = "Extended Linear Address record with non-zero" // NOI18N 646 + "data field in line " // NOI18N 647 + lineNum 648 + " is not allowed in files using " // NOI18N 649 + "24-bit LOAD OFFSET field."; // NOI18N 650 logError(message); // NOI18N 651 throw new MemoryFileRecordContentException(message); 652 } else if (tempPage < PAGES) { 653 curExtLinAddr = tempPage * 65536; 654 curExtSegAddr = 0; 655 currentPage = tempPage; 656 initPage(currentPage); 657 if (log.isDebugEnabled()) { 658 log.debug("New page 0x{}", Integer.toHexString(currentPage)); // NOI18N 659 } // NOI18N 660 } else { 661 String message = "Page number 0x" // NOI18N 662 + Integer.toHexString(tempPage) 663 + " specified in line number " // NOI18N 664 + lineNum 665 + " is beyond the supported 24-bit address range."; // NOI18N; 666 logError(message); 667 throw new MemoryFileAddressingRangeException(message); 668 } 669 670 } else if (recordType == RECTYP_EOF_RECORD) { 671 if ((extractRecLen(line) != 0) 672 || (extractLoadOffset(line) != 0)) { 673 String message = "Illegal EOF record form in line " // NOI18N 674 + lineNum; 675 logError(message); 676 throw new MemoryFileRecordContentException(message); 677 } 678 679 foundEOFRecord = true; 680 continue; // not record we need to handle 681 } else { 682 String message = "Unknown RECTYP 0x" // NOI18N 683 + Integer.toHexString(recordType) 684 + " was found in line " // NOI18N 685 + lineNum + ". Aborting file read."; // NOI18N 686 logError(message); 687 throw new MemoryFileUnknownRecordType(message); 688 } 689 // end parsing hex file record 690 } else { 691 String message = "Unknown line type in line " + lineNum + "."; // NOI18N 692 logError(message); 693 throw new MemoryFileUnknownRecordType(message); 694 } 695 } 696 } catch (IOException e) { 697 log.error("Exception reading file", e); 698 } // NOI18N 699 finally { 700 try { 701 fileStream.close(); 702 } catch (IOException e2) { 703 log.error("Exception closing file", e2); 704 } // NOI18N 705 } 706 if (!foundDataRecords) { 707 String message = "No Data Records found in file - aborting."; // NOI18N 708 logError(message); 709 throw new MemoryFileNoDataRecordsException(message); 710 } else if (!foundEOFRecord) { // found Data Records, but no EOF 711 String message = "No EOF Record found in file - aborting."; // NOI18N 712 logError(message); 713 throw new MemoryFileNoEOFRecordException(message); 714 } 715 } 716 717 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST", 718 justification="pass Error String directly.") 719 private void logError(String errorToLog) { 720 log.error(errorToLog); 721 } 722 723 /** 724 * Sends a character stream of an image of a programmatic representation of 725 * memory in the Intel "I8HEX" file format to a Writer. 726 * <p> 727 * Number of bytes of data per data record is fixed at 16. Does not write 728 * any comment information to the file. 729 * <p> 730 * This method generates only RECTYPs "00" and "01", and does not generate 731 * any comment lines in its output. 732 * 733 * @param w Writer to which the character stream is sent 734 * @throws IOException upon file access problem 735 * @throws MemoryFileAddressingFormatException if unsupported addressing 736 * format 737 */ 738 public void writeHex(Writer w) throws IOException, MemoryFileAddressingFormatException { 739 writeHex(w, 16); 740 } 741 742 /** 743 * Sends a character stream of key/value pairs (if requested) and an image 744 * of a programmatic representation of memory in either the Intel "I8HEX" or 745 * Digitrax ".DMF" file format to a Writer. 746 * <p> 747 * When selected for writing, the key/value pairs are provided at the 748 * beginning of the character stream. Note that comments of the key/value 749 * format implemented here is not in compliance with the "I8HEX" format. 750 * <p> 751 * The "I8HEX" format is used when the {@link #loadOffsetFieldType} is 752 * configured for 16-bit addresses in the record LOAD OFFSET field. The 753 * ".DMF" format is used when the {@link #loadOffsetFieldType} is 754 * configured for 24-bit addresses in the record LOAD OFFSET field. 755 * <p> 756 * The method generates only RECTYPs "00" and "01", and does not generate 757 * any comment lines in its output. 758 * 759 * @param writer Writer to which the character stream is sent 760 * @param writeKeyVals determines whether key/value pairs (if any) are 761 * written at the beginning of the stream 762 * @param blockSize is the maximum number of bytes defined in a data 763 * record 764 * @throws IOException upon file access problem 765 * @throws MemoryFileAddressingFormatException if unsupported addressing 766 * format 767 */ 768 public void writeHex(Writer writer, boolean writeKeyVals, int blockSize) 769 throws IOException, MemoryFileAddressingFormatException { 770 if (writeKeyVals) { 771 writeComments(writer); 772 } 773 writeHex(writer, blockSize); 774 } 775 776 /** 777 * Sends a character stream of an image of a programmatic representation of 778 * memory in either the Intel "I8HEX" or Digitrax ".DMF" file format to a 779 * Writer. 780 * <p> 781 * The "I8HEX" format is used when the{@link #loadOffsetFieldType} is 782 * configured for 16-bit addresses in the record LOAD OFFSET field. The 783 * ".DMF" format is used when the {@link #loadOffsetFieldType} is 784 * configured for 24-bit addresses in the record LOAD OFFSET field. 785 * <p> 786 * The method generates only RECTYPs "00" and "01", and does not generate 787 * any comment lines in its output. 788 * 789 * @param writer Writer to which the character stream is sent 790 * @param blockSize is the maximum number of bytes defined in a data record 791 * @throws IOException upon file access problem 792 * @throws MemoryFileAddressingFormatException if unsupported addressing 793 * format 794 */ 795 private void writeHex(Writer writer, int blockSize) 796 throws IOException, MemoryFileAddressingFormatException { 797 int blocksize = blockSize; // number of bytes per record in .hex file 798 // validate Address format selection 799 if ((!isLoadOffsetType16Bits()) 800 && (!isLoadOffsetType24Bits())) { 801 String message = "Invalid loadOffsetFieldType at writeHex invocation"; // NOI18N 802 log.error(message); 803 throw new MemoryFileAddressingFormatException(message); 804 } 805 806 for (int segment = 0; segment < PAGES; ++segment) { 807 if (pageArray[segment] != null) { 808 if ((segment != 0) && (isLoadOffsetType16Bits())) { 809 // write an extended linear address record for 16-bit LOAD OFFSET field size files only 810 StringBuffer output = new StringBuffer(":0200000400"); // NOI18N 811 output.append(StringUtil.twoHexFromInt(segment)); 812 813 int checksum = calculate8BitChecksum(output.substring(CHARS_IN_RECORD_MARK)); 814 output.append(StringUtil.twoHexFromInt(checksum)); 815 output.append("\n"); // NOI18N 816 817 writer.write(output.toString()); 818 } 819 for (int i = 0; i < pageArray[segment].length - blocksize + 1; i += blocksize) { 820 if (log.isDebugEnabled()) { 821 log.debug("write at 0x{}", Integer.toHexString(i)); // NOI18N 822 } 823 // see if need to write the current block 824 boolean write = false; 825 int startOffset = -1; 826 827 // Avoid producing a record which spans the natural alignment of 828 // addresses with respect to blocksize. In other words, do not produce 829 // a data record that spans both sides of an Address which is a natural 830 // mulitple of blocksize. 831 for (int j = i; j < (i + blocksize) - ((i + blocksize) % blocksize); j++) { 832 if (pageArray[segment][j] >= 0) { 833 write = true; 834 if (startOffset < 0) { 835 startOffset = j; 836 if (log.isDebugEnabled()) { 837 log.debug("startOffset = 0x{}", Integer.toHexString(startOffset)); // NOI18N 838 } 839 } 840 } 841 if (((write == true) && (j == i + (blocksize - 1))) 842 || ((write == true) && (pageArray[segment][j] < 0))) { 843 // got to end of block size, or got a gap in the data 844 // need to write out at least a partial block of data 845 int addressForAddressField = startOffset; 846 if (isLoadOffsetType24Bits()) { 847 addressForAddressField += segment * PAGESIZE; 848 } 849 int addrMostSByte = (addressForAddressField) / 65536; 850 int addrMidSByte = ((addressForAddressField) - (65536 * addrMostSByte)) / 256; 851 int addrLeastSByte = (addressForAddressField) - (256 * addrMidSByte) - (65536 * addrMostSByte); 852 int count = j - startOffset; 853 if ( j == i + (blocksize - 1) ) { 854 count++; 855 } 856 if (log.isDebugEnabled()) { 857 log.debug("Writing Address {} ({}bit Address) count {}", startOffset, isLoadOffsetType24Bits() ? "24" : "16", count); 858 } 859 860 StringBuffer output = new StringBuffer(":"); // NOI18N 861 output.append(StringUtil.twoHexFromInt(count)); 862 if (isLoadOffsetType24Bits()) { 863 output.append(StringUtil.twoHexFromInt(addrMostSByte)); 864 } 865 output.append(StringUtil.twoHexFromInt(addrMidSByte)); 866 output.append(StringUtil.twoHexFromInt(addrLeastSByte)); 867 output.append(STRING_DATA_RECTYP); 868 869 for (int k = 0; k < count; ++k) { 870 int val = pageArray[segment][startOffset + k]; 871 output.append(StringUtil.twoHexFromInt(val)); 872 } 873 int checksum = calculate8BitChecksum(output.substring(CHARS_IN_RECORD_MARK)); 874 output.append(StringUtil.twoHexFromInt(checksum)); 875 output.append("\n"); // NOI18N 876 writer.write(output.toString()); 877 write = false; 878 startOffset = -1; 879 } 880 } 881 if (!write) { 882 continue; // no, we don't 883 } 884 } 885 } 886 } 887 // write last line & close 888 writer.write((isLoadOffsetType24Bits()) ? ":0000000001FF\n" : ":00000001FF\n"); // NOI18N 889 writer.flush(); 890 } 891 892 /** 893 * Return the address of the next location containing data, including the 894 * location in the argument 895 * 896 * @param location indicates the address from which the next location is 897 * determined 898 * @return the next location 899 */ 900 public int nextContent(int location) { 901 currentPage = location / PAGESIZE; 902 int offset = location % PAGESIZE; 903 for (; currentPage < PAGES; currentPage++) { 904 if (pageArray[currentPage] != null) { 905 for (; offset < pageArray[currentPage].length; offset++) { 906 if (pageArray[currentPage][offset] != DEFAULT_MEM_VALUE) { 907 return offset + currentPage * PAGESIZE; 908 } 909 } 910 } 911 offset = 0; 912 } 913 return -1; 914 } 915 916 /** 917 * Modifies the programmatic representation of memory to reflect a specified 918 * value. 919 * 920 * @param location location within programmatic representation of memory to 921 * modify 922 * @param value value to be placed at location within programmatic 923 * representation of memory 924 */ 925 public void setLocation(int location, int value) { 926 currentPage = location / PAGESIZE; 927 928 pageArray[currentPage][location % PAGESIZE] = value; 929 hasData = true; 930 } 931 932 /** 933 * Queries the programmatic representation of memory to determine if 934 * location is represented. 935 * 936 * @param location location within programmatic representation of memory to 937 * inspect 938 * @return true if location exists within programmatic representation of 939 * memory 940 */ 941 public boolean locationInUse(int location) { 942 currentPage = location / PAGESIZE; 943 if (pageArray[currentPage] == null) { 944 return false; 945 } 946 try { 947 return pageArray[currentPage][location % PAGESIZE] != DEFAULT_MEM_VALUE; 948 } catch (Exception e) { 949 log.error("error in locationInUse {} {}", currentPage, location, e); // NOI18N 950 return false; 951 } 952 } 953 954 /** 955 * Returns the value from the programmatic representation of memory for the 956 * specified location. Returns -1 if the specified location is not currently 957 * represented in the programmatic representation of memory. 958 * 959 * @param location location within programmatic representation of memory to 960 * report 961 * @return value found at the specified location. 962 */ 963 public int getLocation(int location) { 964 currentPage = location / PAGESIZE; 965 if (pageArray[currentPage] == null) { 966 log.error("Error in getLocation(0x{}): accessed uninitialized page {}", Integer.toHexString(location), currentPage); 967 return DEFAULT_MEM_VALUE; 968 } 969 try { 970 return pageArray[currentPage][location % PAGESIZE]; 971 } catch (Exception e) { 972 log.error("Error in getLocation(0x{}); computed (current page 0x{}): exception ", Integer.toHexString(location), Integer.toHexString(currentPage), e); // NOI18N 973 return 0; 974 } 975 } 976 977 /** 978 * Reports whether the object has not been initialized with any data. 979 * 980 * @return false if object contains data, true if no data stored in object. 981 */ 982 public boolean isEmpty() { 983 return !hasData; 984 } 985 986 /** 987 * Infers addressing type from contents of string containing a record. 988 * <p> 989 * Returns ADDRESSFIELDSIZEUNKNOWN if 990 * <ul> 991 * <li>the recordString does not begin with ':' 992 * <li>the length of recordString is not appropriate to define an integral 993 * number of bytes 994 * <li>the recordString checksum does not match a checksum computed for the 995 * recordString 996 * <li>if the record type extracted after inferring the addressing type is 997 * an unsupported record type 998 * <li>if the length of recordString did not match the length expected for 999 * the inferred addressing type. 1000 * <ul> 1001 * 1002 * @param recordString the ASCII record, including the leading ':' 1003 * @return the inferred addressing type, or ADDRESSFIELDSIZEUNKNOWN if the 1004 * addressing type cannot be inferred 1005 */ 1006 private LoadOffsetFieldType inferRecordAddressType(String recordString) { 1007 if (recordString.charAt(0) != LEADING_CHAR_RECORD_MARK) { 1008 log.error("Cannot infer record addressing type because line {} is not a record.", lineNum); // NOI18N 1009 return LoadOffsetFieldType.ADDRESSFIELDSIZEUNKNOWN; 1010 } 1011 String r = recordString.substring(CHARS_IN_RECORD_MARK); // create a string without the leading ':' 1012 int len = r.length(); 1013 if (((len + 1) / 2) != (len / 2)) { 1014 // Not an even number of characters in the line (after removing the ':' 1015 // character), so must be a bad record. 1016 log.error("Cannot infer record addressing type because line {} does not have the correct number of characters.", lineNum); // NOI18N 1017 return LoadOffsetFieldType.ADDRESSFIELDSIZEUNKNOWN; 1018 } 1019 1020 int datalen = Integer.parseInt(r.substring(0, 2), 16); 1021 int checksumInRecord = Integer.parseInt(r.substring(len - 2, len), 16); 1022 1023 // Compute the checksum of the record 1024 int calculatedChecksum = calculate8BitChecksum(recordString.substring(CHARS_IN_RECORD_MARK, 1025 recordString.length() - CHARS_IN_CHECKSUM)); 1026 1027 // Return if record checksum value does not match calculated checksum 1028 if (calculatedChecksum != checksumInRecord) { 1029 log.error("Cannot infer record addressing type because line {} does not have the correct checksum (expect 0x{}, found CHKSUM = 0x{})", lineNum, Integer.toHexString(calculatedChecksum), Integer.toHexString(checksumInRecord)); // NOI18N 1030 return LoadOffsetFieldType.ADDRESSFIELDSIZEUNKNOWN; 1031 } 1032 1033 // Checksum is ok, so can check length of line versus address size. 1034 // Compute expected line lengths based on possible address sizes 1035 int computedLenIf16Bit = 2 + 4 + 2 + (datalen * 2) + 2; 1036 int computedLenIf24Bit = computedLenIf16Bit + 2; 1037 1038 // Determine if record line length matches any of the expected line lengths 1039 if (computedLenIf16Bit == len) { 1040 //inferred 16-bit addressing based on length. Check the record type. 1041 if (isSupportedRecordType(Integer.parseInt(r.substring(6, 8), 16))) { 1042 return LoadOffsetFieldType.ADDRESSFIELDSIZE16BITS; 1043 } else { 1044 log.error("Cannot infer record addressing type in line {} because record type is an unsupported record type.", lineNum); // NOI18N 1045 return LoadOffsetFieldType.ADDRESSFIELDSIZEUNKNOWN; 1046 } 1047 } 1048 1049 if (computedLenIf24Bit == len) { 1050 //inferred 24-bit addressing based on length. Check the record type. 1051 if (isSupportedRecordType(Integer.parseInt(r.substring(8, 10), 16))) { 1052 return LoadOffsetFieldType.ADDRESSFIELDSIZE24BITS; 1053 } else { 1054 log.error("Cannot infer record addressing type in line {} because record type is an unsupported record type.", lineNum); // NOI18N 1055 return LoadOffsetFieldType.ADDRESSFIELDSIZEUNKNOWN; 1056 } 1057 } 1058 1059 // Record length did not match a calculated line length for any supported 1060 // addressing type. Report unknown record addressing type. 1061 return LoadOffsetFieldType.ADDRESSFIELDSIZEUNKNOWN; 1062 } 1063 1064 /** 1065 * Calculates an 8-bit checksum value from a string which uses sequential 1066 * pairs of ASCII characters to encode the hexadecimal values of a sequence 1067 * of bytes. 1068 * <p> 1069 * When used to calculate the checksum of a record in I8HEX or similar 1070 * format, the infoString parameter is expected to include only those 1071 * characters which are used for calculation of the checksum. The "record 1072 * mark" at the beginning of a record should not be included in the 1073 * infoString. Similarly, the checksum at the end of a record should 1074 * generally not be included in the infoString. 1075 * <p> 1076 * An example infoString value might be: 020000040010 1077 * <p> 1078 * In case of an invalid infoString, the returned checksum is -1. 1079 * <p> 1080 * If using this method to verify the checksum of a record, the infoString 1081 * should include the record Checksum characters. Then the invoking method 1082 * may check for a non-zero return value to indicate a checksum error. 1083 * 1084 * @param infoString a string of characters for which the checksum is 1085 * calculated 1086 * @return the calculated 8-bit checksum, or -1 if not a valid infoString 1087 */ 1088 private int calculate8BitChecksum(String infoString) { 1089 // check length of record content for an even number of characters 1090 int len = infoString.length(); 1091 if (((len + 1) / 2) != (len / 2)) { 1092 return -1; 1093 } 1094 1095 // Compute the checksum of the record, omitting the last two characters. 1096 int calculatedChecksum = 0; 1097 for (int i = 0; i < len; i += 2) { 1098 calculatedChecksum += Integer.parseInt(infoString.substring(i, i + 2), 16); 1099 } 1100 // Safely remove extraneous bits from the calculated checksum to create an 1101 // 8-bit result. 1102 return (0xFF & (0x100 - (calculatedChecksum & 0xFF))); 1103 } 1104 1105 /** 1106 * Determines if a given amount of data will pass a segment boundary when 1107 * added to the memory image beginning at a given address. 1108 * 1109 * @param addr address for begin of a sequence of bytes 1110 * @param count number of bytes 1111 * @return true if string of bytes will not cross into another page, else 1112 * false. 1113 */ 1114 private boolean addressAndCountIsOk(int addr, int count) { 1115 int beginPage = addr / PAGESIZE; 1116 int endPage = ((addr + count - 1) / PAGESIZE); 1117 log.debug("Effective Record Addr = 0x{} count = {} BeginPage = {} endpage = {}", Integer.toHexString(addr), count, beginPage, endPage); // NOI18N 1118 return (beginPage == endPage); 1119 } 1120 1121 /** 1122 * Finds the Value for a specified Key if that Key is found in the list of 1123 * Key/Value pair comment lines. The list of Key/Value pair comment lines is 1124 * created while the input file is processed. 1125 * <p> 1126 * Key/value pair information is extractable only from comments of the form: 1127 * <p> 1128 * {@code ! Key/Value} 1129 * 1130 * @param keyName Key/value comment line, including the leading "! " 1131 * @return String containing Key name 1132 */ 1133 public String extractValueOfKey(String keyName) { 1134 for (int i = 0; i < keyValComments.size(); i++) { 1135 String t = keyValComments.get(i); 1136 String targetedKey = "! " + keyName + ": "; // NOI18N 1137 if (t.startsWith(targetedKey)) { 1138 int f = t.indexOf(": "); // NOI18N 1139 String value = t.substring(f + 2, t.length()); 1140 if (log.isDebugEnabled()) { 1141 log.debug("Key {} was found in firmware image with value '{}'", keyName, value); // NOI18N 1142 } 1143 return value; 1144 } 1145 } 1146 if (log.isDebugEnabled()) { 1147 log.debug("Key {} is not defined in firmware image", keyName); // NOI18N 1148 } 1149 return null; 1150 1151 } 1152 1153 /** 1154 * Finds the index of the specified key within the array containing 1155 * key/value comments 1156 * 1157 * @param keyName Key to search for in the internal storage 1158 * @return index in the arraylist for the specified key, or -1 if the key is 1159 * not found in the list 1160 */ 1161 private int findKeyCommentIndex(String keyName) { 1162 for (int i = 0; i < keyValComments.size(); i++) { 1163 String t = keyValComments.get(i); 1164 String targetedKey = "! " + keyName + ": "; // NOI18N 1165 if (t.startsWith(targetedKey)) { 1166 return i; 1167 } 1168 } 1169 if (log.isDebugEnabled()) { 1170 log.debug("Did not find key {}", keyName); // NOI18N 1171 } 1172 return -1; 1173 } 1174 1175 /** 1176 * Updates the internal key/value storage to reflect the parameters. If the 1177 * key already exists, its value is updated based on the parameter. If the 1178 * key does not exist, a new key/value pair comment is added to the 1179 * key/value storage list. 1180 * 1181 * @param keyName key to use 1182 * @param value value to store 1183 */ 1184 public void addKeyValueComment(String keyName, String value) { 1185 int keyIndex; 1186 if ((keyIndex = findKeyCommentIndex(keyName)) < 0) { 1187 // key does not already exist. Can simply add the key/value comment 1188 keyValComments.add("! " + keyName + ": " + value + "\n"); // NOI18N 1189 return; 1190 } 1191 log.warn("Key {} already exists in key/value set. Overriding previous value!", keyName); // NOI18N 1192 keyValComments.set(keyIndex, "! " + keyName + ": " + value + "\n"); // NOI18N 1193 } 1194 1195 public enum LoadOffsetFieldType { 1196 1197 UNDEFINED, 1198 ADDRESSFIELDSIZE16BITS, 1199 ADDRESSFIELDSIZE24BITS, 1200 ADDRESSFIELDSIZEUNKNOWN 1201 } 1202 1203 /** 1204 * Configures the Addressing format used in the LOAD OFFSET field when 1205 * writing to a .hex file using the {@link #writeHex} method. 1206 * <p> 1207 * Note that the {@link #readHex} method infers the addressing format 1208 * from the first record in the file and updates the stored address format 1209 * based on the format found in the file. 1210 * 1211 * @param addressingType addressing type to use 1212 */ 1213 public void setAddressFormat(LoadOffsetFieldType addressingType) { 1214 loadOffsetFieldType = addressingType; 1215 } 1216 1217 /** 1218 * Returns the current addressing format setting. The current setting is 1219 * established by the last occurrence of the {@link #setAddressFormat} 1220 * method or {@link #readHex} method invocation. 1221 * 1222 * @return the current Addressing format setting 1223 */ 1224 public LoadOffsetFieldType getCurrentAddressFormat() { 1225 return loadOffsetFieldType; 1226 } 1227 1228 /** 1229 * Writes key/data pair information to an output file 1230 * <p> 1231 * Since the key/value metadata is typically presented at the beginning of a 1232 * firmware file, the method would typically be invoked before invocation of 1233 * the writeHex method. 1234 * @param writer Writer to which the character stream is sent 1235 * @throws IOException if problems writing data to file 1236 */ 1237 public void writeComments(Writer writer) throws IOException { 1238 for (String s : keyValComments) { 1239 writer.write(s); 1240 } 1241 } 1242 1243 private boolean isLoadOffsetType24Bits() { 1244 return loadOffsetFieldType == LoadOffsetFieldType.ADDRESSFIELDSIZE24BITS; 1245 } 1246 1247 private boolean isLoadOffsetType16Bits() { 1248 return loadOffsetFieldType == LoadOffsetFieldType.ADDRESSFIELDSIZE16BITS; 1249 } 1250 1251 private boolean isSupportedRecordType(int recordType) { 1252 switch (recordType) { 1253 case RECTYP_DATA_RECORD: 1254 case RECTYP_EXTENDED_LINEAR_ADDRESS_RECORD: 1255 case RECTYP_EOF_RECORD: 1256 case RECTYP_EXTENDED_SEGMENT_ADDRESS_RECORD: 1257 return true; 1258 default: 1259 return false; 1260 } 1261 } 1262 1263 private int extractRecLen(String line) { 1264 return Integer.valueOf(line.substring(CHARS_IN_RECORD_MARK, 1265 CHARS_IN_RECORD_MARK + CHARS_IN_RECORD_LENGTH), 16).intValue(); 1266 } 1267 1268 private int charsInAddress() { 1269 if (isLoadOffsetType24Bits()) { 1270 return CHARS_IN_24_BIT_ADDRESS; 1271 } else if (isLoadOffsetType16Bits()) { 1272 return CHARS_IN_16_BIT_ADDRESS; 1273 } else { 1274 return -999; 1275 } 1276 } 1277 1278 private int extractLoadOffset(String line) { 1279 return Integer.parseInt( 1280 line.substring(CHARS_IN_RECORD_MARK + CHARS_IN_RECORD_LENGTH, 1281 CHARS_IN_RECORD_MARK + CHARS_IN_RECORD_LENGTH + charsInAddress()), 16); 1282 } 1283 1284 /** 1285 * Generalized class from which detailed exceptions are derived. 1286 */ 1287 public class MemoryFileException extends jmri.JmriException { 1288 1289 public MemoryFileException() { 1290 super(); 1291 } 1292 1293 public MemoryFileException(String s) { 1294 super(s); 1295 } 1296 } 1297 1298 /** 1299 * An exception for a record which has incorrect checksum. 1300 */ 1301 public class MemoryFileChecksumException extends MemoryFileException { 1302 1303 public MemoryFileChecksumException() { 1304 super(); 1305 } 1306 1307 public MemoryFileChecksumException(String s) { 1308 super(s); 1309 } 1310 } 1311 1312 /** 1313 * An exception for a record containing a record type which is not 1314 * supported. 1315 */ 1316 public class MemoryFileUnknownRecordType extends MemoryFileException { 1317 1318 public MemoryFileUnknownRecordType() { 1319 super(); 1320 } 1321 1322 public MemoryFileUnknownRecordType(String s) { 1323 super(s); 1324 } 1325 } 1326 1327 /** 1328 * An exception for a record which has content which cannot be parsed. 1329 * <p> 1330 * Possible examples may include records which include characters other than 1331 * ASCII characters associated with hexadecimal digits and the initial ':' 1332 * character, trailing spaces, etc. 1333 */ 1334 public class MemoryFileRecordContentException extends MemoryFileException { 1335 1336 public MemoryFileRecordContentException() { 1337 super(); 1338 } 1339 1340 public MemoryFileRecordContentException(String s) { 1341 super(s); 1342 } 1343 } 1344 1345 /** 1346 * An exception for a data record where there are too many or too few 1347 * characters versus the number of characters expected based on the record 1348 * type field, LOAD OFFSET field size, and data count field. 1349 */ 1350 public class MemoryFileRecordLengthException extends MemoryFileException { 1351 1352 public MemoryFileRecordLengthException() { 1353 super(); 1354 } 1355 1356 public MemoryFileRecordLengthException(String s) { 1357 super(s); 1358 } 1359 } 1360 1361 /** 1362 * An exception for an unsupported addressing format 1363 */ 1364 public class MemoryFileAddressingFormatException extends MemoryFileException { 1365 1366 public MemoryFileAddressingFormatException() { 1367 super(); 1368 } 1369 1370 public MemoryFileAddressingFormatException(String s) { 1371 super(s); 1372 } 1373 } 1374 1375 /** 1376 * An exception for an address outside of the supported range 1377 */ 1378 public class MemoryFileAddressingRangeException extends MemoryFileException { 1379 1380 public MemoryFileAddressingRangeException() { 1381 super(); 1382 } 1383 1384 public MemoryFileAddressingRangeException(String s) { 1385 super(s); 1386 } 1387 } 1388 1389 /** 1390 * An exception for a file with no data records 1391 */ 1392 public class MemoryFileNoDataRecordsException extends MemoryFileException { 1393 1394 public MemoryFileNoDataRecordsException() { 1395 super(); 1396 } 1397 1398 public MemoryFileNoDataRecordsException(String s) { 1399 super(s); 1400 } 1401 } 1402 1403 /** 1404 * An exception for a file without an end-of-file record 1405 */ 1406 public class MemoryFileNoEOFRecordException extends MemoryFileException { 1407 1408 public MemoryFileNoEOFRecordException() { 1409 super(); 1410 } 1411 1412 public MemoryFileNoEOFRecordException(String s) { 1413 super(s); 1414 } 1415 } 1416 1417 /** 1418 * An exception for a file containing at least one record after the EOF 1419 * record 1420 */ 1421 public class MemoryFileRecordFoundAfterEOFRecord extends MemoryFileException { 1422 1423 public MemoryFileRecordFoundAfterEOFRecord() { 1424 super(); 1425 } 1426 1427 public MemoryFileRecordFoundAfterEOFRecord(String s) { 1428 super(s); 1429 } 1430 } 1431 1432 /** 1433 * Summarize contents 1434 */ 1435 @Override 1436 public String toString() { 1437 StringBuffer retval = new StringBuffer("Pages occupied: "); // NOI18N 1438 for (int page=0; page<PAGES; page++) { 1439 if (isPageInitialized(page)) { 1440 retval.append(page); 1441 retval.append(" "); 1442 } 1443 } 1444 return new String(retval); 1445 } 1446 1447 /** 1448 * Clear out an imported Firmware File. 1449 * 1450 * This may be used, when the instantiating object has evaluated the contents of 1451 * a firmware file and found it to be inappropriate for updating to a device, 1452 * to clear out the firmware image so that there is no chance that it can be 1453 * updated to the device. 1454 * 1455 */ 1456 public void clear() { 1457 log.info("Clearing a MemoryContents object by program request."); 1458 currentPage = -1; 1459 hasData = false; 1460 curExtLinAddr = 0; 1461 curExtSegAddr = 0; 1462 keyValComments = new ArrayList<String>(1); 1463 for (int i = 0 ; i < pageArray.length; ++i) { 1464 pageArray[i] = null; 1465 } 1466 1467 } 1468 1469 private final static Logger log = LoggerFactory.getLogger(MemoryContents.class); 1470}