You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
5.3 KiB
172 lines
5.3 KiB
package license
|
|
|
|
import (
|
|
cryptorand "crypto/rand"
|
|
"fmt"
|
|
"math/rand"
|
|
"testing"
|
|
)
|
|
|
|
// 生产库里的真实 v1 授权码样本
|
|
const legacySampleCode = "0D82F1CCD83D72B78FA9"
|
|
|
|
// generateV1 复刻 comm.GenerateLicense 的 v1 编码,仅供测试构造老码。
|
|
// 生产代码不再生成 v1,只解析。
|
|
func generateV1(productid uint16, batch byte, serial uint32) (string, error) {
|
|
if batch < 1 {
|
|
return "", fmt.Errorf("批次必须 >= 1")
|
|
}
|
|
if serial > 0xFFFFF {
|
|
return "", fmt.Errorf("v1 序号上限 0xFFFFF")
|
|
}
|
|
seedBytes := make([]byte, 2)
|
|
if _, err := cryptorand.Read(seedBytes); err != nil {
|
|
return "", err
|
|
}
|
|
seed := int(seedBytes[0])<<8 | int(seedBytes[1])
|
|
rng := rand.New(rand.NewSource(int64(seed)))
|
|
|
|
confusion := make([]int, 4)
|
|
for i := range confusion {
|
|
confusion[i] = rng.Intn(65536)
|
|
}
|
|
confusion[0] ^= int(productid)
|
|
confusion[1] ^= (int(batch) << 8) | (int(serial>>12) & 0xFF)
|
|
confusion[2] ^= int(serial) & 0xFFF
|
|
confusion[3] ^= rng.Intn(65536)
|
|
|
|
code := fmt.Sprintf("%04X", seed)
|
|
for _, c := range confusion {
|
|
code += fmt.Sprintf("%04X", c)
|
|
}
|
|
return code, nil
|
|
}
|
|
|
|
func TestParse_LegacySample(t *testing.T) {
|
|
info, err := Parse(legacySampleCode)
|
|
if err != nil {
|
|
t.Fatalf("解析真实老码失败: %v", err)
|
|
}
|
|
if info.Version != V1 {
|
|
t.Errorf("版本应为 v1, 实际 v%d", info.Version)
|
|
}
|
|
if info.ProductID != 45069 || info.Batch != 1 || info.Serial != 260 {
|
|
t.Errorf("解析结果不符: pid=%d batch=%d serial=%d, 期望 45069/1/260",
|
|
info.ProductID, info.Batch, info.Serial)
|
|
}
|
|
}
|
|
|
|
// v2 往返:序号必须能占满 uint32
|
|
func TestGenerateV2_RoundTrip(t *testing.T) {
|
|
pids := []uint16{0, 1, 45058, 45069, 0xFFFF}
|
|
serials := []uint32{
|
|
0, 1, 15, 16, 0xFFF, 0x1000, 0xFFFFF, 0x100000,
|
|
0xFFFFFFF, 0x10000000, 0x12345678, 0xFFFFFFFE, 0xFFFFFFFF,
|
|
}
|
|
for _, pid := range pids {
|
|
for _, serial := range serials {
|
|
code, err := GenerateV2(pid, serial)
|
|
if err != nil {
|
|
t.Fatalf("生成失败 pid=%d serial=%d: %v", pid, serial, err)
|
|
}
|
|
if len(code) != totalSegments*segmentLength {
|
|
t.Fatalf("码长变了: %d, 期望 %d", len(code), totalSegments*segmentLength)
|
|
}
|
|
info, err := Parse(code)
|
|
if err != nil {
|
|
t.Fatalf("解析失败 %s: %v", code, err)
|
|
}
|
|
if info.Version != V2 {
|
|
t.Errorf("%s 应识别为 v2, 实际 v%d", code, info.Version)
|
|
}
|
|
if info.ProductID != pid || info.Serial != serial {
|
|
t.Errorf("往返不一致: 写入 pid=%d serial=%d, 读出 pid=%d serial=%d",
|
|
pid, serial, info.ProductID, info.Serial)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// probatch < 240 的 v1 码不能被误判成 v2,且 batch/serial 必须原样解出
|
|
func TestParse_V1NotMisdetected(t *testing.T) {
|
|
const pid = 45069
|
|
serials := []uint32{0, 1, 4095, 4096, 65535, 0xFFFFF}
|
|
for batch := 1; batch < V1ProbatchLimit; batch++ {
|
|
for _, serial := range serials {
|
|
code, err := generateV1(pid, byte(batch), serial)
|
|
if err != nil {
|
|
t.Fatalf("v1 生成失败 batch=%d serial=%d: %v", batch, serial, err)
|
|
}
|
|
info, err := Parse(code)
|
|
if err != nil {
|
|
t.Fatalf("v1 解析失败 batch=%d serial=%d: %v", batch, serial, err)
|
|
}
|
|
if info.Version != V1 {
|
|
t.Fatalf("batch=%d serial=%d 的 v1 码被误判为 v%d", batch, serial, info.Version)
|
|
}
|
|
if info.ProductID != pid || info.Batch != byte(batch) || info.Serial != serial {
|
|
t.Fatalf("v1 解析不一致 batch=%d serial=%d -> pid=%d batch=%d serial=%d",
|
|
batch, serial, info.ProductID, info.Batch, info.Serial)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 文档化约束:batch >= 240 的 v1 码必然被误判为 v2(判别位 = batch>>4 == 0xF)。
|
|
// 迁移脚本必须先验证 MAX(probatch) < V1ProbatchLimit,否则老码序号会解出垃圾。
|
|
// 本测试若开始失败,说明判别位挪了位置,前提校验需要跟着改。
|
|
func TestParse_V1OverLimitIsMisdetected(t *testing.T) {
|
|
const pid = 45069
|
|
for batch := V1ProbatchLimit; batch <= 0xFF; batch++ {
|
|
code, err := generateV1(pid, byte(batch), 1)
|
|
if err != nil {
|
|
t.Fatalf("v1 生成失败 batch=%d: %v", batch, err)
|
|
}
|
|
info, err := Parse(code)
|
|
if err != nil {
|
|
t.Fatalf("解析失败 batch=%d: %v", batch, err)
|
|
}
|
|
if info.Version != V2 {
|
|
t.Errorf("batch=%d 的 v1 码应当被误判为 v2(证明前提校验必要),实际 v%d", batch, info.Version)
|
|
}
|
|
}
|
|
}
|
|
|
|
// productid 的提取与版本无关,也不受 v1 批次异常影响
|
|
func TestProductID_VersionAgnostic(t *testing.T) {
|
|
const pid = 45069
|
|
|
|
v1, err := generateV1(pid, 3, 1234)
|
|
if err != nil {
|
|
t.Fatalf("v1 生成失败: %v", err)
|
|
}
|
|
v2, err := GenerateV2(pid, 0xDEADBEEF)
|
|
if err != nil {
|
|
t.Fatalf("v2 生成失败: %v", err)
|
|
}
|
|
for _, code := range []string{v1, v2, legacySampleCode} {
|
|
got, err := ProductID(code)
|
|
if err != nil {
|
|
t.Fatalf("%s 提取 pid 失败: %v", code, err)
|
|
}
|
|
if got != pid {
|
|
t.Errorf("%s 提取 pid=%d, 期望 %d", code, got, pid)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 同产品下连续序号生成的码互不相同(code <-> (seed, pid, serial) 双射)
|
|
func TestGenerateV2_Uniqueness(t *testing.T) {
|
|
const pid = 45069
|
|
seen := make(map[string]uint32, 20000)
|
|
for serial := uint32(1); serial <= 20000; serial++ {
|
|
code, err := GenerateV2(pid, serial)
|
|
if err != nil {
|
|
t.Fatalf("生成失败 serial=%d: %v", serial, err)
|
|
}
|
|
if prev, dup := seen[code]; dup {
|
|
t.Fatalf("授权码碰撞: serial=%d 与 serial=%d 都生成 %s", serial, prev, code)
|
|
}
|
|
seen[code] = serial
|
|
}
|
|
}
|
|
|