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