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