Files
tbf/NfcC7_DLL/NfcHanler/Utils/FirmwareImage.cs
T

468 lines
16 KiB
C#

using System.Collections.ObjectModel;
using System.ComponentModel;
using Sensus;
namespace NfcC7_DLL.NfcHanler.Utils
{
public enum SegmentType { Standard = 0, Extended = 1 }
public class FirmwareVersion : IComparable<FirmwareVersion>, IComparable
{
public FirmwareVersion() { }
public FirmwareVersion(Byte major, Byte minor)
{
if (major > 0x0f || minor > 0x0f)
throw new ArgumentException("Invalid firmware version");
Major = major;
Minor = minor;
}
public Byte Major { get; protected set; }
public Byte Minor { get; protected set; }
public override bool Equals(object obj)
{
if (!(obj is FirmwareVersion))
return false;
if ((object)this == obj) return true;
FirmwareVersion other = (FirmwareVersion)obj;
return other.GetHashCode() == this.GetHashCode();
}
public virtual ushort ToShort()
{
return (ushort)(((Major & 0x0F) << 12) | ((Minor & 0x0F) << 8));
}
public override int GetHashCode()
{
return (Major << 4) | Minor;
}
public override string ToString()
{
return String.Format("{0:X}.{1:X} (hex)", Major, Minor);
}
public virtual string ToShortString()
{
return String.Format("{0:X}.{1:X}", Major, Minor);
}
#region IComparable<FirmwareVersion> Members
public int CompareTo(FirmwareVersion other)
{
if (this.Equals(other)) return 0; // all fields are same
return this.GetHashCode() - other.GetHashCode();
}
#endregion
#region IComparable Members
public int CompareTo(object obj)
{
if (obj.GetType() == this.GetType()) return CompareTo((FirmwareVersion)obj);
throw new ArgumentException();
}
#endregion
public static bool operator <(FirmwareVersion a, FirmwareVersion b) { return (a.CompareTo(b) < 0); }
public static bool operator >(FirmwareVersion a, FirmwareVersion b) { return (a.CompareTo(b) > 0); }
public static bool operator <=(FirmwareVersion a, FirmwareVersion b) { return (a < b) || (a.Equals(b)); }
public static bool operator >=(FirmwareVersion a, FirmwareVersion b) { return (a > b) || (a.Equals(b)); }
}
public class FlexNetFirmwareVersion : FirmwareVersion
{
public FlexNetFirmwareVersion() { }
public FlexNetFirmwareVersion(Byte major, Byte minor, Boolean beta)
: base(major, minor)
{
if (major > 0x07)
throw new ArgumentException("Invalid Firmware Version");
IsBeta = beta;
}
public Boolean IsBeta { get; protected set; }
public override ushort ToShort()
{
if (!IsBeta)
return base.ToShort();
else
return (ushort)(base.ToShort() | 0x8000);
}
public override int GetHashCode()
{
return base.GetHashCode() | ((IsBeta ? 0 : 1) << 7);
}
public override string ToString()
{
return String.Format("{0:X}.{1:X}{2} (hex)", Major, Minor, ((IsBeta) ? " beta" : String.Empty));
}
public override string ToShortString()
{
return String.Format("{2}{0:X}.{1:X}", Major, Minor, ((IsBeta) ? "b" : String.Empty));
}
}
public class NA2WFirmwareVersion : FlexNetFirmwareVersion
{
public NA2WFirmwareVersion() { }
public NA2WFirmwareVersion(Byte major, Byte minor, Byte patch, Boolean beta)
: base(major, minor, beta)
{
Patch = patch;
}
public static NA2WFirmwareVersion FromUInt16(UInt16 version)
{
return new NA2WFirmwareVersion(
(byte)((version >> 12) & 7),
(byte)((version & 0x0f00) >> 8),
(byte)(version & 0xff),
(version >> 15) == 1);
}
public override ushort ToShort()
{
return (ushort)(base.ToShort() | (Patch & 0xFF));
}
public static NA2WFirmwareVersion Parse(String s)
{
Boolean beta = false;
Byte major, minor, patch;
if (s[0] == 'B')
{
beta = true;
s = s.Substring(1);
}
else if (s[0] == 'R')
{
s = s.Substring(1);
}
var versions = s.Split('.');
if (versions.Count() != 3)
throw new FormatException("Not a recognized NA2W firmware version format");
major = Byte.Parse(versions[0], System.Globalization.NumberStyles.HexNumber);
minor = Byte.Parse(versions[1], System.Globalization.NumberStyles.HexNumber);
patch = Byte.Parse(versions[2], System.Globalization.NumberStyles.HexNumber);
return new NA2WFirmwareVersion(major, minor, patch, beta);
}
public Byte Patch { get; protected set; }
public override int GetHashCode()
{
return (((((Major << 4) | Minor) << 8) | Patch) << 1) | (IsBeta ? 0 : 1);
}
public override string ToString()
{
return String.Format("{0:X}.{1:X}.{2:X2}{3} (hex)", Major, Minor, Patch, ((IsBeta) ? " beta" : String.Empty));
}
public override string ToShortString()
{
return String.Format("{3}{0:X}.{1:X}.{2:X2}", Major, Minor, Patch, ((IsBeta) ? "b" : String.Empty));
}
}
public class SegmentDescriptor
{
public static SegmentDescriptor FromReader(BinaryReader reader, SegmentType type)
{
return new SegmentDescriptor(reader, type);
}
public SegmentType Type { get; set; }
public UInt32 Address { get; set; }
public UInt16 BlockCount { get; set; }
public SegmentDescriptor()
{
}
protected SegmentDescriptor(BinaryReader reader, SegmentType type)
{
Type = type;
switch (type)
{
case SegmentType.Extended:
Address = (UInt32)(reader.ReadByte() | (reader.ReadByte() << 8) | (reader.ReadByte() << 16));
break;
case SegmentType.Standard:
Address = (UInt32)(reader.ReadByte() | (reader.ReadByte() << 8));
break;
default:
throw new InvalidDataException("Unrecognized segment type");
}
BlockCount = (UInt16)(reader.ReadByte() | (reader.ReadByte() << 8));
}
}
public class FirmwareBlock : ReadOnlyCollection<byte>
{
public static FirmwareBlock FromReader(BinaryReader reader)
{
return new FirmwareBlock(reader);
}
public FirmwareBlock(UInt16 id, byte[] data, int offset)
: base(new byte[19])
{
ID = id;
Array.Copy(data, offset, (byte[])Items, 0, Math.Min(19, data.Length - offset));
}
public UInt16 ID { get; set; }
protected FirmwareBlock(BinaryReader reader)
: base(new byte[19])
{
ID = (UInt16)(reader.ReadByte() | (reader.ReadByte() << 8));
Array.Copy(reader.ReadBytes(19), (byte[])Items, 19);
}
}
/// <summary>
/// Contents of a Sensus .upg file.
/// </summary>
public class FirmwareImage
{
/// <summary>
/// Creates a FirmwareImage given the path to a .upg file.
/// Non-NA2W UPG files must be named with the version number appended
/// to the end of the file name after a _. For example: iConA_B1.2.3.4.upg
/// NA2W-format UPG files have the firmware version embedded inside the file
/// after the device type.
/// </summary>
/// <param name="path">path to the .upg file.</param>
/// <returns></returns>
public FirmwareImage(string upgPath)
{
switch (Path.GetExtension(upgPath).ToLower())
{
case ".upg":
Filename = Path.GetFileName(upgPath);
try
{
ProcessUPG(upgPath, SegmentType.Extended);
}
catch
{
ProcessUPG(upgPath, SegmentType.Standard);
}
break;
case ".gft":
Filename = Path.GetFileName(upgPath);
ProcessGft(upgPath);
break;
default:
throw new InvalidOperationException("invalid file extension for firmware file");
}
}
public string Filename { get; private set; }
public byte DeviceType { get; private set; }
/// <summary>
/// Version of the firmware that will be installed if instance is loaded onto a device.
/// This property does not indicate file format version of the .upg file.
/// </summary>
public FirmwareVersion Version { get; set; }
/// <summary>
/// Compatibility byte from UPG file -- in NA2W devices, this is
/// also known as the "Product type."
/// </summary>
public byte Compatibility { get; private set; }
/// <summary>
/// Number of 19-byte blocks in the image
/// </summary>
public UInt16 BlockCount { get; private set; }
/// <summary>
/// Size of the firmware image in bytes.
/// </summary>
public UInt32 ImageSize { get; private set; }
/// <summary>
/// Number of segments in the image.
/// </summary>
public byte SegmentCount { get; private set; }
/// <summary>
/// CRC32 of the data in all of the blocks
/// </summary>
public UInt32 CRC { get; private set; }
/// <summary>
/// Description of all the segments in the firmware image.
/// </summary>
public IList<SegmentDescriptor> Segments { get; private set; }
/// <summary>
/// The new firmware image in 19-byte blocks.
/// </summary>
[Serialize(false)]
public IList<FirmwareBlock> Blocks { get; private set; }
/// <summary>
/// Data from the firmware image as an array.
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public byte[] Data
{
get
{
return Blocks.SelectMany(b => b.ToList()).ToArray();
}
set
{
ushort id = 0;
Blocks.Clear();
for (int offset = 0; offset < value.Length; offset += 19)
Blocks.Add(new FirmwareBlock(id++, value, offset));
}
}
public byte[] GetSegmentDescriptorBlock()
{
byte[] result = new byte[19];
int addrSize = Segments.First().Type == SegmentType.Extended ? 3 : 2;
int segmentSize = addrSize + 2;
for (int i = 0; i < Segments.Count; i++)
{
result.Write(Segments[i].Address, (uint)(((i * segmentSize) + 0) * 8), (uint)(addrSize * 8), ByteOrder.LittleEndian);
result.Write(Segments[i].BlockCount, (uint)(((i * segmentSize) + addrSize) * 8), 16, ByteOrder.LittleEndian);
}
return result;
}
public UInt32 GetCRCWithSegmentDescriptorBlock()
{
return ~CRC32.Calculate(~CRC, GetSegmentDescriptorBlock());
}
private void ProcessUPG(string path, SegmentType segmentType)
{
using (BinaryReader reader = new BinaryReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
Segments = new List<SegmentDescriptor>();
Blocks = new List<FirmwareBlock>();
DeviceType = reader.ReadByte();
Version = NA2WFirmwareVersion.FromUInt16((UInt16)((reader.ReadByte() << 8) | reader.ReadByte()));
Compatibility = reader.ReadByte();
BlockCount = (UInt16)(reader.ReadByte() | (reader.ReadByte() << 8));
SegmentCount = reader.ReadByte();
for (int i = 0; i < SegmentCount; i++)
Segments.Add(SegmentDescriptor.FromReader(reader, segmentType));
CRC = (UInt32)(reader.ReadByte() | (reader.ReadByte() << 8) | (reader.ReadByte() << 16) | (reader.ReadByte() << 24));
ImageSize = (UInt32)(reader.ReadByte() | (reader.ReadByte() << 8) | (reader.ReadByte() << 16) | (reader.ReadByte() << 24));
if (ImageSize == 0)
ImageSize = (uint)(BlockCount * 19);
uint minimumBlocks = ImageSize / 19;
uint maximumBlocks = minimumBlocks + 1; // file is zero-padded to nearest block boundary
if (!(minimumBlocks <= BlockCount && BlockCount <= maximumBlocks))
throw new InvalidDataException();
for (int i = 0; i < BlockCount; i++)
Blocks.Add(FirmwareBlock.FromReader(reader));
UInt32 calculatedCRC = ~Blocks.Aggregate(CRC32.Seed, CRC32.Calculate);
if (CRC != calculatedCRC)
throw new InvalidDataException(String.Format("Invalid CRC. Calculated CRC = {0}, file CRC = {1}", calculatedCRC, CRC));
}
}
private void ProcessGft(string path)
{
Gft.parseGFTfile(path);
if (
(!Gft.transfChunk.getTransferType().Equals("FWDL"))
|| (Gft.parsedGFTfile.isElectric)
)
{
throw new InvalidDataException("GFT not NA2W FWDL.");
}
Log.Write("GFT Verification (" + Filename + ")\n");
Log.Write(" Inner signature status: " + Gft.parsedGFTfile.BlockSignatureStatus + "\n");
Log.Write(" File SHA-256 status: " + Gft.parsedGFTfile.FileSHA256Status + "\n");
Log.Write(" File signature status: " + Gft.parsedGFTfile.FileSignatureStatus + "\n");
// Populate Firmware Image fields
DeviceType = Gft.transfChunk.devType;
Compatibility = Gft.na2wfwChunk.compatibilityId;
CRC = Gft.na2wfwChunk.crc32;
// NA2W Chunk fileBytes and controlBytes should be multiple of 19 bytes.
// NA2W Chunk verificationBytes is not a multiple of 19 bytes. Need to apply a ceiling logic.
uint fileBlocks = Gft.na2wfwChunk.fileBytes / 19;
uint controlBlocks = Gft.na2wfwChunk.controlBytes / 19;
uint verificationBlocks = (uint)Math.Ceiling(((decimal)Gft.na2wfwChunk.verificationBytes / 19));
BlockCount = (ushort)(fileBlocks + controlBlocks + verificationBlocks);
uint imageSizeBytes = (uint)(BlockCount * 19);
ImageSize = imageSizeBytes;
Version = NA2WFirmwareVersion.FromUInt16(Gft.na2wfwChunk.fwVersion);
// GFT format is identified with signal NULL descriptor. Segment address is
// load block number of start of control block
SegmentCount = 1;
Segments = new List<SegmentDescriptor>();
Segments.Add(
new SegmentDescriptor()
{
Type = SegmentType.Extended,
Address = fileBlocks,
BlockCount = 0
}
);
// Build load blocks
Blocks = new List<FirmwareBlock>();
for (ushort i = 0; i < BlockCount; i++)
{
Blocks.Add(new FirmwareBlock(i, Gft.blockData, i * 19));
}
}
}
}