Skip to content

Commit 001d279

Browse files
committed
init
1 parent a153b5c commit 001d279

File tree

5 files changed

+508
-0
lines changed

5 files changed

+508
-0
lines changed

.github/workflows/release.yml

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
defaults:
9+
run:
10+
shell: bash
11+
12+
permissions:
13+
contents: write
14+
actions: write
15+
16+
jobs:
17+
cleanup:
18+
runs-on: ubuntu-latest
19+
permissions:
20+
actions: write
21+
steps:
22+
- name: Cleanup artifacts
23+
uses: actions/github-script@v7
24+
with:
25+
script: |
26+
const artifacts = await github.paginate(github.rest.actions.listArtifactsForRepo, {
27+
owner: context.repo.owner,
28+
repo: context.repo.repo,
29+
per_page: 100,
30+
});
31+
const toDelete = artifacts.filter((artifact) => artifact.name.startsWith("wheels-"));
32+
for (const artifact of toDelete) {
33+
await github.rest.actions.deleteArtifact({
34+
owner: context.repo.owner,
35+
repo: context.repo.repo,
36+
artifact_id: artifact.id,
37+
});
38+
}
39+
40+
build:
41+
runs-on: ${{ matrix.os }}
42+
needs: cleanup
43+
strategy:
44+
fail-fast: false
45+
matrix:
46+
os:
47+
- ubuntu-latest
48+
- windows-latest
49+
- macos-latest
50+
python-version:
51+
- "3.13"
52+
53+
steps:
54+
- name: Checkout
55+
uses: actions/checkout@v4
56+
57+
- name: Set up Python
58+
uses: actions/setup-python@v5
59+
with:
60+
python-version: ${{ matrix.python-version }}
61+
62+
- name: Set up Rust
63+
uses: dtolnay/rust-toolchain@stable
64+
65+
- uses: Swatinem/rust-cache@v2
66+
67+
- name: Create venv (Unix)
68+
if: runner.os != 'Windows'
69+
run: |
70+
python -m venv .venv
71+
echo "PYTHON_EXEC=$PWD/.venv/bin/python" >> $GITHUB_ENV
72+
73+
- name: Create venv (Windows)
74+
if: runner.os == 'Windows'
75+
run: |
76+
python -m venv .venv
77+
echo "PYTHON_EXEC=$(pwd)/.venv/Scripts/python.exe" >> $GITHUB_ENV
78+
79+
- name: Install maturin
80+
run: $PYTHON_EXEC -m pip install --upgrade pip maturin
81+
82+
- name: Build wheels
83+
run: $PYTHON_EXEC -m maturin build --release --out dist
84+
85+
- name: Install wheel
86+
run: $PYTHON_EXEC -m pip install dist/*.whl
87+
88+
- name: Run Tests
89+
run: |
90+
$PYTHON_EXEC -m unittest discover tests
91+
92+
- name: Upload wheels
93+
uses: actions/upload-artifact@v4
94+
with:
95+
name: wheels-${{ matrix.os }}-py${{ matrix.python-version }}
96+
path: dist/*
97+
retention-days: 3
98+
99+
release:
100+
runs-on: ubuntu-latest
101+
needs: build
102+
permissions:
103+
contents: write
104+
steps:
105+
- name: Cleanup latest release assets
106+
uses: actions/github-script@v7
107+
with:
108+
script: |
109+
try {
110+
const release = await github.rest.repos.getReleaseByTag({
111+
owner: context.repo.owner,
112+
repo: context.repo.repo,
113+
tag: "latest",
114+
});
115+
const assets = release.data.assets || [];
116+
for (const asset of assets) {
117+
await github.rest.repos.deleteReleaseAsset({
118+
owner: context.repo.owner,
119+
repo: context.repo.repo,
120+
asset_id: asset.id,
121+
});
122+
}
123+
} catch (error) {
124+
if (error.status !== 404) {
125+
throw error;
126+
}
127+
}
128+
129+
- name: Download wheels
130+
uses: actions/download-artifact@v4
131+
with:
132+
path: dist
133+
merge-multiple: true
134+
135+
- name: Publish latest release
136+
uses: softprops/action-gh-release@v2
137+
with:
138+
tag_name: latest
139+
name: latest
140+
generate_release_notes: true
141+
files: dist/*

Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "poptrie"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
name = "poptrie"
8+
crate-type = ["cdylib"]
9+
10+
[dependencies]
11+
memmap2 = "0.9.9"
12+
rayon = "1.11.0"
13+
14+
[dependencies.pyo3]
15+
version = "0.27.2"
16+
features = ["extension-module", "abi3-py38"]
17+
18+
[profile.release]
19+
opt-level = 3 # 确保开启最高等级优化
20+
lto = "fat" # 开启全模块链接优化,消除跨 crate 的性能开销
21+
codegen-units = 1 # 牺牲编译速度换取更好的机器码生成
22+
panic = "abort" # 减小二进制体积,并略微提升性能(不再需要展开堆栈)
23+
strip = "symbols" # 自动移除调试符号,减小库文件体积

build_bin.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import socket
2+
import struct
3+
4+
5+
class BinBuilder:
6+
NODE_SIZE = 68 # 32字节Child_BM + 32字节Leaf_BM + 4字节Base_Offset
7+
8+
def __init__(self):
9+
# 根节点:{ 'children': {byte: node_dict}, 'is_leaf': bool }
10+
self.root = {'children': {}, 'is_leaf': False}
11+
12+
def add_cidr(self, cidr):
13+
"""添加并自动合并子网"""
14+
ip_part, mask_part = cidr.split('/')
15+
mask = int(mask_part)
16+
family = socket.AF_INET6 if ':' in ip_part else socket.AF_INET
17+
ip_bytes = socket.inet_pton(family, ip_part)
18+
19+
curr = self.root
20+
steps = mask // 8
21+
remaining = mask % 8
22+
23+
# 1. 沿路径向下寻址
24+
for i in range(steps):
25+
# 如果路径中已经存在叶子节点,说明当前 CIDR 已被更大网段覆盖,直接返回
26+
if curr['is_leaf']:
27+
return
28+
29+
byte = ip_bytes[i]
30+
if byte not in curr['children']:
31+
curr['children'][byte] = {'children': {}, 'is_leaf': False}
32+
curr = curr['children'][byte]
33+
34+
# 2. 处理剩余位或结束位
35+
if remaining == 0:
36+
# 正好整除,标记为叶子,并清理其下的所有子节点(因为大网覆盖小网)
37+
curr['is_leaf'] = True
38+
curr['children'] = {}
39+
else:
40+
# 处理非对齐掩码,如 /18 在第 3 字节有 2 位
41+
shift = 8 - remaining
42+
start_byte = ip_bytes[steps] & (0xFF << shift)
43+
end_byte = start_byte | (0xFF >> remaining)
44+
45+
for b in range(start_byte, end_byte + 1):
46+
# 如果这个范围内的某个字节已经存在,递归处理它
47+
if b not in curr['children']:
48+
curr['children'][b] = {'children': {}, 'is_leaf': True}
49+
else:
50+
# 如果已存在,将其标记为叶子,并剪枝其子树
51+
curr['children'][b]['is_leaf'] = True
52+
curr['children'][b]['children'] = {}
53+
54+
def _prune(self, node):
55+
"""递归剪枝:如果 256 个子节点全是叶子,合并为父节点叶子"""
56+
if not node['children']:
57+
return node['is_leaf']
58+
59+
# 先递归剪枝子节点
60+
all_children_are_leaf = len(node['children']) == 256
61+
for b in list(node['children'].keys()):
62+
child_is_leaf = self._prune(node['children'][b])
63+
if not child_is_leaf:
64+
all_children_are_leaf = False
65+
66+
# 如果 256 个子节点全满且都是叶子,则向上合并
67+
if all_children_are_leaf:
68+
node['is_leaf'] = True
69+
node['children'] = {}
70+
return True
71+
72+
return node['is_leaf']
73+
74+
def save(self, output_path):
75+
"""执行剪枝并序列化为 BFS 结构的二进制文件"""
76+
# 1. 先进行全局剪枝优化压缩率
77+
self._prune(self.root)
78+
79+
final_data = bytearray()
80+
current_layer = [self.root]
81+
# 下一层节点在文件中的起始偏移(根节点之后)
82+
next_layer_start_offset = self.NODE_SIZE
83+
84+
while current_layer:
85+
next_layer = []
86+
for node in current_layer:
87+
child_bm = bytearray(32)
88+
leaf_bm = bytearray(32)
89+
90+
# 获取 0-255 的排序键
91+
sorted_keys = sorted(node['children'].keys())
92+
93+
# 收集本节点中有子树的节点,它们将排在 next_layer
94+
nodes_with_children = []
95+
96+
for k in sorted_keys:
97+
child_node = node['children'][k]
98+
99+
# 标记 Leaf Bitmap: 只要这个字节是终点
100+
if child_node['is_leaf']:
101+
leaf_bm[k >> 3] |= (1 << (7 - (k % 8)))
102+
103+
# 标记 Child Bitmap: 只有还有子树的才标记,用于跳转
104+
if child_node['children']:
105+
child_bm[k >> 3] |= (1 << (7 - (k % 8)))
106+
nodes_with_children.append(child_node)
107+
108+
# 写入节点数据
109+
final_data.extend(child_bm)
110+
final_data.extend(leaf_bm)
111+
112+
if nodes_with_children:
113+
# 记录跳转到下一层这些子节点的起始偏移
114+
final_data.extend(struct.pack("<I", next_layer_start_offset))
115+
next_layer.extend(nodes_with_children)
116+
next_layer_start_offset += len(nodes_with_children) * self.NODE_SIZE
117+
else:
118+
# 没有子节点可跳转
119+
final_data.extend(struct.pack("<I", 0))
120+
121+
current_layer = next_layer
122+
123+
with open(output_path, "wb") as f:
124+
f.write(final_data)
125+
print(f"构建完成!压缩后大小: {len(final_data) / 1024:.2f} KB")
126+
127+
128+
# --- 使用方式 ---
129+
builder = BinBuilder()
130+
# 这里放入你收集的中国 IP CIDR 列表
131+
china_cidrs = ["1.0.1.0/24", "110.16.0.0/12", "240e::/18"]
132+
for c in china_cidrs:
133+
builder.add_cidr(c)
134+
135+
builder.save("china_ip.bin")

0 commit comments

Comments
 (0)