#include "licensedata.h"
|
#include <QDataStream>
|
#include <QCryptographicHash>
|
#include <QRandomGenerator>
|
#include <QtDebug>
|
|
LicenseData::LicenseData()
|
: productId(0), verMajor(0), verMinor(0), activeState(0)
|
{
|
}
|
|
QByteArray LicenseData::toBinary() const
|
{
|
QByteArray data;
|
data.reserve(64);
|
QDataStream stream(&data, QIODevice::WriteOnly);
|
stream.setByteOrder(QDataStream::BigEndian);
|
|
// 1. 主板UUID:固定16字节
|
QByteArray uuid = boardUuid.left(16);
|
uuid.append(QByteArray(16 - uuid.size(), '\0'));
|
stream.writeRawData(uuid.constData(), 16);
|
|
// 2. CPU序列号:固定8字节
|
QByteArray cpu = cpuSerial.left(8);
|
cpu.append(QByteArray(8 - cpu.size(), '\0'));
|
stream.writeRawData(cpu.constData(), 8);
|
|
// 3. 用户名:长度前缀(1B) + UTF-8数据
|
QByteArray userBytes = userName.toUtf8();
|
if (userBytes.size() > 255) {
|
userBytes = userBytes.left(255);
|
}
|
stream << static_cast<quint8>(userBytes.size());
|
stream.writeRawData(userBytes.constData(), userBytes.size());
|
|
// 4. 失效日期:2字节(距2000-01-01的天数)
|
stream << dateToDays(expireDate);
|
|
// 5. 产品号:1字节
|
stream << productId;
|
|
// 6. 版本号:主版本 + 次版本,各1字节
|
stream << verMajor << verMinor;
|
|
// 7. 激活状态:1字节
|
stream << activeState;
|
|
return data;
|
}
|
|
bool LicenseData::fromBinary(const QByteArray &data)
|
{
|
if (data.size() < 30) {
|
qWarning() << "LicenseData::fromBinary: data too short, need at least 30 bytes";
|
return false;
|
}
|
|
QDataStream stream(data);
|
stream.setByteOrder(QDataStream::BigEndian);
|
|
// 1. UUID
|
boardUuid.resize(16);
|
stream.readRawData(boardUuid.data(), 16);
|
|
// 2. CPU序列号
|
cpuSerial.resize(8);
|
stream.readRawData(cpuSerial.data(), 8);
|
|
// 3. 用户名
|
quint8 nameLen = 0;
|
stream >> nameLen;
|
if (data.size() < 25 + nameLen + 5) {
|
qWarning() << "LicenseData::fromBinary: insufficient data for remaining fields";
|
return false;
|
}
|
QByteArray userBytes(nameLen, '\0');
|
stream.readRawData(userBytes.data(), nameLen);
|
userName = QString::fromUtf8(userBytes);
|
|
// 4. 日期
|
quint16 days = 0;
|
stream >> days;
|
expireDate = daysToDate(days);
|
|
// 5. 产品号
|
stream >> productId;
|
|
// 6. 版本
|
stream >> verMajor >> verMinor;
|
|
// 7. 激活状态
|
stream >> activeState;
|
|
return true;
|
}
|
|
QString LicenseData::toDebugString() const
|
{
|
return QString("{UUID:%1, CPU:%2, User:%3, Expire:%4, Prod:%5, Ver:%6.%7, State:%8}")
|
.arg(uuidBinaryToString(boardUuid))
|
.arg(binaryToHexString(cpuSerial))
|
.arg(userName)
|
.arg(expireDate.toString("yyyy-MM-dd"))
|
.arg(productId)
|
.arg(verMajor).arg(verMinor)
|
.arg(activeState);
|
}
|
|
// ===== V3 紧凑格式(20位许可密钥用)=====
|
|
QByteArray LicenseData::machineFingerprint(const QByteArray &uuid, const QByteArray &cpu, const QByteArray &key)
|
{
|
// 与toBinary相同的定长规整,保证授权端和验证端对同一台机器算出同一指纹
|
QByteArray u = uuid.left(16);
|
u.append(QByteArray(16 - u.size(), '\0'));
|
QByteArray c = cpu.left(8);
|
c.append(QByteArray(8 - c.size(), '\0'));
|
|
QCryptographicHash hash(QCryptographicHash::Sha256);
|
hash.addData(key);
|
hash.addData(u);
|
hash.addData(c);
|
return hash.result().left(8);
|
}
|
|
quint16 LicenseData::crc16(const QByteArray &data)
|
{
|
// CRC16-CCITT (0xFFFF 初值,0x1021 多项式)
|
quint16 crc = 0xFFFF;
|
for (int i = 0; i < data.size(); ++i) {
|
crc ^= static_cast<quint16>(static_cast<unsigned char>(data.at(i))) << 8;
|
for (int b = 0; b < 8; ++b) {
|
crc = (crc & 0x8000) ? static_cast<quint16>((crc << 1) ^ 0x1021)
|
: static_cast<quint16>(crc << 1);
|
}
|
}
|
return crc;
|
}
|
|
QByteArray LicenseData::toBinaryV3(const QByteArray &key) const
|
{
|
QByteArray data(16, '\0');
|
|
// [0..7] 机器指纹
|
QByteArray fp = machineFingerprint(boardUuid, cpuSerial, key);
|
memcpy(data.data(), fp.constData(), 8);
|
|
// [8..9] 失效日期(与V2同一2000-01-01基准)
|
quint16 days = dateToDays(expireDate);
|
data[8] = static_cast<char>((days >> 8) & 0xFF);
|
data[9] = static_cast<char>(days & 0xFF);
|
|
// [10] 产品号
|
data[10] = static_cast<char>(productId);
|
|
// [11] 版本:高4位主版本 | 低4位次版本
|
data[11] = static_cast<char>(((verMajor & 0x0F) << 4) | (verMinor & 0x0F));
|
|
// [12] 标志:高4位格式版本(1),低4位激活状态
|
data[12] = static_cast<char>(0x10 | (activeState & 0x0F));
|
|
// [13..14] CRC16(覆盖[0..12],随机盐不参与)
|
quint16 crc = crc16(data.left(13));
|
data[13] = static_cast<char>((crc >> 8) & 0xFF);
|
data[14] = static_cast<char>(crc & 0xFF);
|
|
// [15] 随机盐
|
data[15] = static_cast<char>(QRandomGenerator::global()->generate() & 0xFF);
|
|
return data;
|
}
|
|
bool LicenseData::fromBinaryV3(const QByteArray &data)
|
{
|
if (data.size() != 16) {
|
return false;
|
}
|
|
quint8 flags = static_cast<quint8>(data.at(12));
|
if ((flags >> 4) != 0x01) {
|
qWarning() << "LicenseData::fromBinaryV3: not a V3 payload, format version ="
|
<< (flags >> 4);
|
return false;
|
}
|
|
quint16 expectCrc = static_cast<quint16>((static_cast<quint8>(data.at(13)) << 8)
|
| static_cast<quint8>(data.at(14)));
|
if (crc16(data.left(13)) != expectCrc) {
|
qWarning() << "LicenseData::fromBinaryV3: CRC check failed, wrong key or corrupted data";
|
return false;
|
}
|
|
machineFp = data.left(8);
|
|
quint16 days = static_cast<quint16>((static_cast<quint8>(data.at(8)) << 8)
|
| static_cast<quint8>(data.at(9)));
|
expireDate = daysToDate(days);
|
|
productId = static_cast<quint8>(data.at(10));
|
|
quint8 ver = static_cast<quint8>(data.at(11));
|
verMajor = ver >> 4;
|
verMinor = ver & 0x0F;
|
|
activeState = flags & 0x0F;
|
return true;
|
}
|
|
QString LicenseData::toDebugStringV3() const
|
{
|
return QString("{FP:%1, Expire:%2, Prod:%3, Ver:%4.%5, State:%6}")
|
.arg(binaryToHexString(machineFp))
|
.arg(expireDate.toString("yyyy-MM-dd"))
|
.arg(productId)
|
.arg(verMajor).arg(verMinor)
|
.arg(activeState);
|
}
|
|
quint16 LicenseData::dateToDays(const QDate &date) const
|
{
|
QDate base(BASE_YEAR, BASE_MONTH, BASE_DAY);
|
if (!date.isValid() || date < base) {
|
return 0;
|
}
|
return static_cast<quint16>(base.daysTo(date));
|
}
|
|
QDate LicenseData::daysToDate(quint16 days) const
|
{
|
QDate base(BASE_YEAR, BASE_MONTH, BASE_DAY);
|
return base.addDays(days);
|
}
|
|
QByteArray LicenseData::uuidStringToBinary(const QString &uuidStr)
|
{
|
QString clean = uuidStr;
|
clean.remove('-');
|
clean.remove('{');
|
clean.remove('}');
|
clean = clean.toUpper();
|
if (clean.size() != 32) {
|
return QByteArray();
|
}
|
return hexStringToBinary(clean);
|
}
|
|
QString LicenseData::uuidBinaryToString(const QByteArray &bin)
|
{
|
if (bin.size() != 16) {
|
return QString();
|
}
|
QString hex = binaryToHexString(bin);
|
return QString("%1-%2-%3-%4-%5")
|
.arg(hex.mid(0, 8))
|
.arg(hex.mid(8, 4))
|
.arg(hex.mid(12, 4))
|
.arg(hex.mid(16, 4))
|
.arg(hex.mid(20, 12));
|
}
|
|
QByteArray LicenseData::hexStringToBinary(const QString &hexStr)
|
{
|
QString clean = hexStr;
|
clean.remove(' ');
|
QByteArray result;
|
result.reserve(clean.size() / 2);
|
for (int i = 0; i + 1 < clean.size(); i += 2) {
|
bool ok;
|
result.append(static_cast<char>(clean.mid(i, 2).toUInt(&ok, 16)));
|
}
|
return result;
|
}
|
|
QString LicenseData::binaryToHexString(const QByteArray &bin)
|
{
|
return QString::fromLatin1(bin.toHex()).toUpper();
|
}
|