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