Skip to content

Vanni-Fan/idmix

Repository files navigation

idmix — XID v1.1 自描述整数序列短标识符

将多个带类型的整数编码为短字符串,适用于 access_key、短 ID、令牌等场景。Go、PHP、Rust、Python、JavaScript、Java、C#、VB.NET、C/C++ 多语言实现互通,遵循同一套 XID v1.1 规范

用途

  • 类型自描述:每个整数携带原始类型(uint8~int64),解码时不依赖外部 schema
  • 极致压缩:015 的正数、-1-16 的负数仅占 1 字节;其余按最小补码宽度存储
  • 32 态多态:同一组数据可生成最多 32 种不同字符串,防猜、规避敏感词
  • 轻量自校验:2-bit 全局 XOR 校验,约 75% 的随机篡改可被即时拒绝
  • 自定义字符表:默认 62 进制(a-zA-Z0-9),字符顺序可自定义

算法概要(XID v1.1)

编码分两层:

整数序列 → [二进制层] → [文本层] → 字符串

1. 二进制层

二进制块 = 2 字节 header(小端) + 数据对象序列

Header 位域(默认配置)

位域 宽度 含义
bit[1:0] 2 check 校验位(全局 XOR 低 2 位)
bit[10:2] 9 count 对象个数(0~511)
bit[15:11] 5 variant_id 变体 ID(0~31)

数据对象(每个整数一个):

  • 内嵌模式(1 字节):无符号 015,或有符号 -1-16
  • 扩展模式(1+ 字节):其余值,最小补码 + 类型标记(otype 0~7)

变体混淆mask = (variant_id × 0x9D + 0x37) & 0xFF,对对象区逐字节 XOR(header 不参与)。

校验:对整个二进制块逐字节 XOR,低 2 位写入 header。

规范示例 uint16(5), int64(-1), uint32(40)(variant=0)的二进制块:

0F 00 22 47 B5 1F

完整协议见 arithmetic.md

2. 文本层

在二进制块前附加 2 字节大端长度前缀,整体按自定义进制(默认 62 字符表)转为字符串。

Sqids 功能对比

Sqids 是流行的短 ID 库,仅支持非负整数序列。idmix 在此基础上面向结构化 access_key 场景做了扩展。

功能 Sqids idmix
输入类型 非负整数序列(uint64 带类型整数序列(uint8~int64
负数支持 不支持 支持(内嵌 / 扩展模式)
类型自描述 否,解码需外部 schema 是,每个值携带原始类型
多态编码 否,同输入同输出 是,默认 32 种变体字符串
自校验 是,2-bit 全局 XOR(约 75% 拒随机串)
阻止列表(Blocklist) 支持,过滤敏感词 不支持(见下说明)
最小长度约束 支持 min_length 不支持(追求最短自然编码)
自定义字母表 支持(默认 64 字符) 支持(默认 62 字符)
单次最大元素数 无硬性上限 默认 511(可配置)

关于阻止列表:idmix 不提供 Sqids 式的 blocklist 过滤。原因是 idmix 内置 32 态变体多态——同一组数据每次编码会随机选取不同 variant_id,字符串形态天然分散,出现特定敏感词的概率极低;若偶发不满意,重新调用 Encode 即可得到另一变体。配合 2-bit 校验,随机猜测的有效率也更低。

以下为 GoRust 实现相对 sqids 的编码长度与性能对比(本机单线程,20000 次采样;idmix 长度含 32 态变体,报告 min~max)。

编码长度

场景 sqids idmix (Go) idmix (Rust)
[1, 2, 3] 6 8~8 8~8
单值 [42] 2 6~6 6~6
单值 uint32 [2000000000] 7 10~10 10~10
AccessKey [1001, 1690000000, 3] 12 16~16 16~16
小整数 [0..9] 20 17~17 17~17

sqids 在纯非负小整数场景下字符串更短;idmix 因 header、类型标记和变体开销略长,但小整数密集时反而更短(内嵌模式 1 字节/值)。

编码性能(相对 sqids 倍数)

场景 Go 编码 Rust 编码 Go 解码 Rust 解码
[1, 2, 3] 20.6× 12.4× 2.9× 5.8×
uint32 [2000000000] 12.1× 13.7× 2.2× 3.9×

idmix 编解码以位运算为主,显著快于 sqids;Go 实现整体快于 Rust(Rust 文本层使用 num-bigint)。

idmix 额外能力(sqids 不支持)

  • 带类型:uint16(5), int64(-1), uint32(40) → 9 字符
  • 负数、混合类型序列
  • 随机变体:同一输入多次编码产生不同字符串

运行对比测试:

# Go
cd golang && go test -v -run TestCompareSqids

# Rust
cd rust/lib && cargo test --test benchmark_sqids -- --nocapture

快速开始

Go

go get github.com/Vanni-Fan/idmix/golang
package main

import (
    "fmt"
    idmix "github.com/Vanni-Fan/idmix/golang"
)

func main() {
    m, _ := idmix.New()
    str, _ := m.Encode(uint16(5), int64(-1), uint32(40))
    list, _ := m.Decode(str)
    fmt.Println(str, list[0].(uint16), list[1].(int64), list[2].(uint32))
}

PHP

composer require vanni/idmix
<?php
use Vanni\Idmix\IdMix;
use Vanni\Idmix\TypedValue;

$m = IdMix::new();
$str = $m->encode(TypedValue::u16(5), TypedValue::i64(-1), TypedValue::u32(40));
$out = $m->decode($str);
// $out[0]->val === 5, $out[1]->val === -1, $out[2]->val === 40

Rust

cargo add idmix@0.2.0
use idmix::{IdMix, Value};

fn main() {
    let m = IdMix::new().unwrap();
    let values = [Value::U16(5), Value::I64(-1), Value::U32(40)];
    let encoded = m.encode(&values).unwrap();
    let decoded = m.decode(&encoded).unwrap();
    println!("{encoded:?} => {decoded:?}");
}

Python

pip install vanni-idmix
from idmix import IdMix, u16, i64, u32

m = IdMix.new()
s = m.encode(u16(5), i64(-1), u32(40))
out = m.decode(s)
# out[0].val == 5, out[1].val == -1, out[2].val == 40

JavaScript (Node.js)

npm install @vanni.fan/idmix
import { IdMix } from '@vanni.fan/idmix';

const m = IdMix.new();
// otype: 1=uint16, 7=int64, 2=uint32
const s = m.encode({ otype: 1, val: 5 }, { otype: 7, val: -1 }, { otype: 2, val: 40 });
const out = m.decode(s);

Java

Maven 依赖(Maven Central):

<dependency>
    <groupId>io.github.vanni-fan</groupId>
    <artifactId>idmix</artifactId>
    <version>0.2.0</version>
</dependency>
import io.github.vannifan.idmix.IdMix;
import io.github.vannifan.idmix.TypedValue;

IdMix m = IdMix.newDefault();
String s = m.encode(TypedValue.u16(5), TypedValue.i64(-1), TypedValue.u32(40));
var out = m.decode(s);

C#

dotnet add package Vanni.Idmix
using Vanni.Idmix;

var m = IdMix.NewDefault();
var s = m.Encode(TypedValue.U16(5), TypedValue.I64(-1), TypedValue.U32(40));
var outList = m.Decode(s);

VB.NET

dotnet add package Vanni.Idmix.Vb
Imports Vanni.Idmix

Dim m = IdMix.NewDefault()
Dim s = m.Encode(TypedValue.U16(5), TypedValue.I64(-1), TypedValue.U32(40))
Dim outList = m.Decode(s)

C++

C/C++ 没有类似 npm / crates.io 的统一官方包仓库,无需额外「发布」步骤。用户在自己的 CMake 工程里通过 FetchContent 从 GitHub 拉取源码即可(下面这段 CMake 是用户项目里写的,不是本仓库维护项):

include(FetchContent)
FetchContent_Declare(idmix
  GIT_REPOSITORY https://github.com/Vanni-Fan/idmix.git
  GIT_TAG v0.2.0
  SOURCE_SUBDIR cpp
)
FetchContent_MakeAvailable(idmix)
target_link_libraries(your_app PRIVATE idmix::idmix)

也可 clone 后本地构建:

git clone https://github.com/Vanni-Fan/idmix.git
cd idmix/cpp && cmake -B build && cmake --build build
#include "idmix/idmix.hpp"
using namespace idmix;

IdMix m = IdMix::newDefault();
auto s = m.encode({TypedValue::u16(5), TypedValue::i64(-1), TypedValue::u32(40)});
auto out = m.decode(s);

C

同样通过 FetchContent 集成(SOURCE_SUBDIR 改为 c,链接 idmix::c):

include(FetchContent)
FetchContent_Declare(idmix_c
  GIT_REPOSITORY https://github.com/Vanni-Fan/idmix.git
  GIT_TAG v0.2.0
  SOURCE_SUBDIR c
)
FetchContent_MakeAvailable(idmix_c)
target_link_libraries(your_app PRIVATE idmix::c)

或本地构建:

git clone https://github.com/Vanni-Fan/idmix.git
cd idmix/c && cmake -B build && cmake --build build
#include "idmix.h"

idmix_ctx_t* ctx = idmix_create(NULL);
idmix_value_t vals[] = {
    {IDMIX_OTYPE_UINT16, 5},
    {IDMIX_OTYPE_INT64, -1},
    {IDMIX_OTYPE_UINT32, 40},
};
char* s = NULL;
idmix_encode(ctx, vals, 3, &s);
idmix_value_t* out = NULL;
size_t n = 0;
idmix_decode(ctx, s, &out, &n);
idmix_free_string(s);
idmix_free_values(out);
idmix_destroy(ctx);

自定义字符表

// Go
m, _ := idmix.New(idmix.WithAlphabet("abcd"))
// PHP
$m = IdMix::withAlphabet('一二三四五六七八九十');
// Rust
let m = IdMix::builder().alphabet("abcd").build().unwrap();
# Python
m = IdMix.new("abcd")
// JavaScript
const m = IdMix.new('abcd');
// Java
IdMix m = new IdMix("abcd");
// C#
var m = new IdMix("abcd");
' VB.NET
Dim m As New IdMix("abcd")
// C++
IdMix m("abcd");

项目结构

idmix/
├── arithmetic.md    # XID v1.1 完整规范
├── PACKAGING.md     # 各语言包发布与安装指南
├── golang/          # Go 参考实现
├── rust/lib/        # Rust crate
├── php/             # PHP (Composer: vanni/idmix)
├── python/          # Python (pip: vanni-idmix)
├── javascript/      # JavaScript (npm: @vanni.fan/idmix)
├── java/            # Java (Maven: io.github.vanni-fan:idmix)
├── csharp/          # C# (NuGet: Vanni.Idmix)
├── vb/              # VB.NET (NuGet: Vanni.Idmix.Vb)
├── cpp/             # C++ 库
└── c/               # C 库

运行测试(详细输出)

# Go
cd golang && go test -v ./...

# Rust
cd rust/lib && cargo test -- --nocapture

# Python
cd python && python -m unittest discover -s tests -v

# JavaScript
cd javascript && npm test

# Java
cd java && mvn test

# C#
cd csharp/tests/Vanni.Idmix.Tests && dotnet test

# VB.NET
dotnet build vb/src/Vanni.Idmix.Vb/Vanni.Idmix.Vb.vbproj

# C++
cd cpp && cmake -B build && cmake --build build && build/Debug/idmix_test

# C
cd c && cmake -B build && cmake --build build && build/Debug/idmix_c_test

包安装(无需 git clone)

当前版本 0.2.0。各语言可通过包管理器直接安装;发布细节见 PACKAGING.md

语言 安装命令 包仓库
Go go get github.com/Vanni-Fan/idmix/golang pkg.go.dev
PHP composer require vanni/idmix Packagist
Rust cargo add idmix@0.2.0 crates.io
Python pip install vanni-idmix PyPI
JavaScript npm install @vanni.fan/idmix npm
Java Maven io.github.vanni-fan:idmix:0.2.0 Maven Central
C# dotnet add package Vanni.Idmix NuGet
VB.NET dotnet add package Vanni.Idmix.Vb NuGet
C/C++ 见上文 FetchContent(用户 CMake 集成) GitHub

C/C++ 说明:没有像 PyPI / NuGet 那样的统一官方包仓库,也不需要你再去某个官网「发布」。仓库里的 cpp/c/ 即为源码库;用户在自己的 CMake 工程里写 FetchContent_Declare(...) 从 GitHub 拉取即可。可选的 vcpkg 端口尚未提交上游,不影响使用。

限制

  • 单次编码最多 511 个对象(可配置)
  • 推荐用于中小整数;过大数值压缩率下降
  • 变体随机选取,同一输入多次编码字符串不同,但均可正确解码

许可证

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors