001package jmri.jmrix.can.cbus.swing.bootloader;
002
003import java.io.IOException;
004import java.util.Arrays;
005
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Class to encapsulate a hex record as used by Microchip tools.
011 * 
012 * @author Andrew Crosland Copyright (C) 2020
013 */
014public class HexRecord {
015
016    // Record types
017    static final byte EXT_ADDR = 4;
018    static final byte TYPE_DATA = 0;
019    static final byte END = 1;
020    // Maximum data length
021    static final int MAX_LEN = 256;
022    // Offsets of fields within the record
023
024    // Record property members
025    protected int len;
026    protected int addrh;
027    protected int addrl;
028    protected int address;
029    protected int type;
030    protected int checksum;
031    protected boolean valid;
032    protected byte [] data;
033    protected int lineNo;
034    
035    
036    /**
037     * Create an empty record with unprogrammed data and invalid status.
038     */
039    public HexRecord() {
040        len = 0;
041        addrh = 0;
042        addrl = 0;
043        type = END;
044        checksum = 0;
045        valid = false;
046        data = new byte[MAX_LEN];
047        Arrays.fill(data, (byte)0xFF);
048        lineNo = -1;
049    }
050    
051    
052    /**
053     * Read a new record from a file.
054     * 
055     * @param f hex file to read from
056     * @throws IOException from underlying read operations
057     */
058    public HexRecord(HexFile f) throws IOException {
059        this();
060        readRecord(f);
061    }
062    
063    
064    /**
065     * Set the line number where the record was found in the file.
066     * 
067     * @param l the line number
068     */
069    protected void setLineNo(int l) {
070        lineNo = l;
071    }
072    
073    
074    /**
075     * Get the data array from a hex record.
076     * 
077     * @return the data
078     */
079    protected byte [] getData() {
080        return data;
081    }
082    
083    
084    /**
085     * Get a data element from a hex record.
086     * 
087     * @param i index of the element to get
088     * @return the data
089     */
090    protected byte getData(int i) {
091        return data[i];
092    }
093    
094    
095    /**
096     * Get current address from a hex record.
097     * <p>
098     * Returns 16 bit address from a normal hex record. Extended address records
099     * are handled elsewhere.
100     * 
101     * @return the address
102     */
103    protected int getAddress() {
104        return address;
105    }
106    
107    
108    /**
109     * Look for the start of a new record.
110     * 
111     * @param f Input hex file
112     */
113    private void startRecord(HexFile f) throws IOException {
114        int c;
115        
116        checksum = 0;
117        // Skip newline and look for ':'
118        while (((c = f.readChar()) == 0xd) || (c == 0xa)) {  }
119        if (c != ':') {
120            log.error("No colon at start of line {}", f.getLineNo());
121            throw new IOException("No colon at start of line "+f.getLineNo());
122        }
123    }
124    
125    
126    /**
127     * Read hex record header.
128     * 
129     * @param f Input hex file
130     * @throws IOException 
131     */
132    private void readHeader(HexFile f) throws IOException {
133        // length of data
134        len = f.rdHexByte();
135        checksum += len;
136        // High address
137        addrh = f.rdHexByte();
138        checksum += addrh;
139        // Low address
140        addrl = f.rdHexByte();
141        checksum += addrl;
142        // record type
143        type = f.rdHexByte();
144        checksum += type;
145        if (type != EXT_ADDR) {
146            // update address, extended address should be handled by caller
147            address = addrh * 256 + addrl;
148        }
149    }
150    
151    
152    /**
153     * Read the data bytes.
154     * 
155     * @param f Input hex file
156     * @throws IOException from underlying read operations
157     */
158    void readData(HexFile f) throws IOException {
159        if (type != END) {
160            for (int i = 0; i < len; i++) {
161                data[i] = (byte)(f.rdHexByte() & 0xFF);
162                checksum += data[i];
163            }
164        }
165    }
166    
167    
168    /**
169     * Verify the record checksum.
170     * 
171     * @param f Input hex file
172     * @throws IOException 
173     */
174    private void checkRecord(HexFile f) throws IOException {
175        int fileCheck = f.rdHexByte();
176        if (((checksum + fileCheck) & 0xff) != 0) {
177            log.error("Bad checksum at {}", Integer.toHexString(address));
178            throw new IOException("Bad checksum");
179        }
180    }
181    
182    
183    /**
184     * Read a record from a hex file and verify the checksum.
185     * 
186     * @param f the hex file
187     * @throws IOException 
188     */
189    private void readRecord(HexFile f) throws IOException {
190        try {
191            startRecord(f);
192            readHeader(f);
193            readData(f);
194            checkRecord(f);
195        } catch (IOException e) {
196            throw new IOException(e);
197        }
198        valid = true;
199    }
200
201    
202    private static final Logger log = LoggerFactory.getLogger(HexRecord.class);
203    
204}