package com.punchthrough.bean.sdk.upload; import android.os.Parcelable; import com.punchthrough.bean.sdk.internal.exception.HexParsingException; import com.punchthrough.bean.sdk.internal.exception.NoEnumFoundException; import com.punchthrough.bean.sdk.internal.intelhex.Line; import com.punchthrough.bean.sdk.internal.intelhex.LineRecordType; import com.punchthrough.bean.sdk.internal.utility.Chunk; import com.punchthrough.bean.sdk.internal.utility.Constants; import com.punchthrough.bean.sdk.internal.utility.EnumParse; import org.apache.commons.codec.DecoderException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.ListIterator; import auto.parcel.AutoParcel; import static com.punchthrough.bean.sdk.internal.utility.Convert.asciiHexToBytes; import static com.punchthrough.bean.sdk.internal.utility.Convert.bytesToInt; /** * Represents a Sketch (Arduino code snippet) in hex form. */ @AutoParcel public abstract class SketchHex implements Parcelable, Chunk.Chunkable { public abstract String sketchName(); public abstract byte[] bytes(); @Override public byte[] getChunkableData() { return bytes(); } /** * Initialize a SketchHex object with no data. */ public static SketchHex create(String sketchName) throws HexParsingException { return SketchHex.create(sketchName, ""); } /** * Initialize a SketchHex object with a string of Intel Hex data. * * @param sketchName The name of the sketch. * @param hexString The Intel Hex data as a string * @return The new SketchHex object * @throws com.punchthrough.bean.sdk.internal.exception.HexParsingException * if the string data being parsed is not valid Intel Hex */ public static SketchHex create(String sketchName, String hexString) throws HexParsingException { if (sketchName.length() > Constants.MAX_SKETCH_NAME_LENGTH) { sketchName = sketchName.substring(0, Constants.MAX_SKETCH_NAME_LENGTH); } List<Line> lines = parseHexStringToLines(hexString); byte[] bytes = convertLinesToBytes(lines); return new AutoParcel_SketchHex(sketchName, bytes); } /** * Parse a string of Intel Hex data to a list of * {@link com.punchthrough.bean.sdk.internal.intelhex.Line} objects. * * @param hexString The Intel Hex data as a string * @return A list of {@link com.punchthrough.bean.sdk.internal.intelhex.Line} objects * representing the Intel Hex data * @throws HexParsingException If the Intel Hex could not be parsed */ private static List<Line> parseHexStringToLines(String hexString) throws HexParsingException { List<String> rawLines = Arrays.asList(hexString.split("\n")); ListIterator<String> iterator = rawLines.listIterator(); List<Line> lines = new ArrayList<>(); while (iterator.hasNext()) { int rawLineNum = iterator.nextIndex(); String rawLine = iterator.next(); if (rawLine.length() < 11) continue; if ( ! rawLine.startsWith(":") ) { throw new HexParsingException(String.format( "Couldn't parse hex: line %d did not start with ':'", rawLineNum)); } rawLine = rawLine.replaceAll("\r", ""); // rawBytes: bytes of the string without the leading colon, parsed into hex byte[] rawBytes; try { rawBytes = asciiHexToBytes(rawLine.substring(1)); } catch (DecoderException e) { throw new HexParsingException(String.format( "Couldn't parse hex: line %d ASCII could not be parsed to byte array: %s", rawLineNum, e.getLocalizedMessage())); } Line line = new Line(); line.setByteCount(rawBytes[0]); line.setAddress(bytesToInt(rawBytes[1], rawBytes[2])); byte rawRecordType = rawBytes[3]; LineRecordType recordType; try { recordType = EnumParse.enumWithRawValue(LineRecordType.class, rawRecordType); } catch (NoEnumFoundException e) { throw new HexParsingException(String.format( "Couldn't parse hex: line %d had invalid record type %d", rawLineNum, rawRecordType)); } line.setRecordType(recordType); if (line.getByteCount() > 0) { line.setData(Arrays.copyOfRange(rawBytes, 4, 4 + line.getByteCount())); } line.setChecksum(rawBytes[4 + line.getByteCount()]); lines.add(line); } return lines; } /** * Convert a list of Intel Hex {@link com.punchthrough.bean.sdk.internal.intelhex.Line} objects * to an array of the raw bytes they represent. * * @param lines The List of {@link com.punchthrough.bean.sdk.internal.intelhex.Line} objects * @return An array of raw bytes represented by the * {@link com.punchthrough.bean.sdk.internal.intelhex.Line} objects */ private static byte[] convertLinesToBytes(List<Line> lines) { List<Line> dataLines = new ArrayList<>(); int byteCount = 0; for (Line line : lines) { if (line.getRecordType() == LineRecordType.DATA) { dataLines.add(line); byteCount += line.getByteCount(); } } byte[] bytes = new byte[byteCount]; int i = 0; for (Line line : dataLines) { for (byte b : line.getData()) { bytes[i] = b; i++; } } return bytes; } }