468 lines
16 KiB
C#
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));
|
|
}
|
|
|
|
}
|
|
}
|
|
} |