AI 驱动招聘变革:从流程电子化到决策智能化的跨越 在数字化浪潮席卷各行各业的今天,人力资源领域的数字化转型早已不是新鲜话题。ERP系统的普及、自动化流程的搭建,让企业招聘摆脱了纯粹的纸质化办公,迈入了“流程电子化”的新阶段。然而,这种看似便捷的数字化,实则暗藏诸多局限——简历筛选仍停留在关键词匹配的浅层阶段,面试评价难逃主观偏见的桎梏,企业往往在海量信息中耗费大量精力,却仍难避免错失核心人才的遗憾,“伪数字化”的标签始终难以摘除。 生成式 AI 的崛起,为招聘行业带来了真正的颠覆性力量,它打破了传统工具的被动属性,以主动洞察、智能交互的姿态,重构了人才甄选的全流程。这一变革的核心,在于将招聘从“事务性操作”升级为“战略性决策”,精准破解了长期困扰行业的低效、主观、高成本三大痛点。 在效率与精准度的双重突破上,AI 面试智能体成为无可替代的核心引擎。通过严格的心理学效度与信度检验,其评估结果与资深面试官形成高度契合,为招聘决策提供了可量化的科学依据。不同于传统简历筛选的片面化,AI 能够深度解析候选人履历,精准定位核心成就与信息疑点,构建层层递进的提问逻辑,既实现了信息核实的严谨性,又能深度挖掘候选人的潜在能力。更值得关注的是,单一智能问题即可同步测评多项核心胜任力,无缝衔接初筛与复试环节,使整体评估效率提升超五成,不仅解放了 HR 从海量简历中“淘金”的时间,更让业务面试官摆脱了初试阶段的重复劳动,将精力聚焦于核心人才的深度沟通。同时,针对编程、财务、工程等不同专业领域,AI 可实现精准化测评,确保人才筛选与岗位需求的高度匹配。 而在候选人体验与雇主品牌传递上,AI 招聘系统也实现了质的飞跃。告别了传统 AI 面试的生硬机械,新一代系统具备了敏锐的情绪感知能力,能够捕捉候选人的语速、语调变化,以专业的引导方式帮助候选人放松心态,充分展现真实水平,避免因紧张导致的评价失真。音画同步技术的应用,让虚拟面试官的表情、口型与语音节奏完美契合,赋予交互满满的温度,彻底摆脱“纸片人”式的疏离感。全程无需手动操作启停,语音自动识别功能让问答流转如真人交谈般自然流畅,极大提升了面试的沉浸感。此外,候选人可随时就职位详情、团队文化、发展路径等问题发起咨询,AI 基于企业知识库提供即时、一致的专业解答,在完成人才评估的同时,实现了雇主价值的高效传递,让每一次面试都成为雇主品牌的加分项。 AI 驱动的招聘变革,绝非对传统招聘逻辑的否定与取代,而是以技术赋能的方式,实现了流程优化与价值升级。它让招聘摆脱了“伪数字化”的束缚,从“流程电子化”真正迈向“决策智能化”,为企业在日趋激烈的人才竞争中搭建起核心优势。未来,随着 AI 技术的持续迭代,招聘行业将进一步突破时空限制,实现更精准的人才匹配、更高效的流程运转、更优质的双向体验,成为企业吸引并留住核心人才的战略支撑,为企业的长远发展注入源源不断的人才活力。
classChainOfVerification:
def__init__(self, llm):
self.llm=llm
defrun(self, query):
# Step 1: Baseline Generation
# Let the model hallucinate freely here.
draft_prompt=f"Question: {query}\nAnswer:"
draft=self.llm.generate(draft_prompt)
print(f"--- DRAFT ---\n{draft}\n")
# Step 2: Plan Verifications
# Ask the model to identify what needs checking.
plan_prompt=f"""
Context: {query}
Draft: {draft}
Task: Create a list of 3-5 verification questions to check the facts
in the draft. Output ONLY the questions.
"""
plan_text=self.llm.generate(plan_prompt)
questions=self.parse_questions(plan_text)
print(f"--- QUESTIONS ---\n{questions}\n")
# Step 3: Factored Verification (The Key Step)
verification_results= []
forqinquestions:
# CRITICAL: Do NOT include 'draft' in this prompt context.
# We want the raw model weights to answer this, uninfluenced by the previous lie.
verify_prompt=f"Question: {q}\nAnswer:"
# Low temperature is crucial here for factual retrieval
answer=self.llm.generate(verify_prompt, temperature=0)
verification_results.append((q, answer))
# Step 4: Final Synthesis
# Now we bring it all together.
verification_context=self.format_pairs(verification_results)
synthesis_prompt=f"""
Original Query: {query}
Draft Response: {draft}
Verification Data:
{verification_context}
Task: Rewrite the Draft Response to be fully accurate.
Remove any details contradicted by the Verification Data.
"""
final_response=self.llm.generate(synthesis_prompt)
returnfinal_response
defparse_questions(self, text):
return [line.strip() forlineintext.split('\n') if'?'inline]
defformat_pairs(self, pairs):
return"\n".join([f"Q: {q}\nA: {a}"forq, ainpairs])
import distributedDeviceManager from '@ohos.distributedDeviceManager';
import featureAbility from '@ohos.ability.featureAbility';
const BUNDLE_NAME = 'com.example.distributeddemo';
let deviceManager = distributedDeviceManager.createDeviceManager(BUNDLE_NAME);
function startRemotePage() {
let devices = deviceManager.getTrustedDeviceListSync();
devices.forEach(device => {
if (device.deviceType === 2) { // 假设 2 表示平板
let want = {
bundleName: BUNDLE_NAME,
abilityName: 'RemotePageAbility',
deviceId: device.deviceId
};
featureAbility.startAbility(want);
}
});
}
代码说明
createDeviceManager:创建设备管理器
getTrustedDeviceListSync:获取可信设备列表
deviceType:用于简单区分设备类型
startAbility:指定 deviceId 后,Ability 会在远端设备启动
整个过程不需要你关心远端设备的进程、生命周期,系统会处理。
核心实现方式二:分布式 Service 执行任务
适合什么场景
这种方式更适合:
计算密集型任务
后台处理
不需要 UI 的逻辑
比如: 手机采集数据,交给性能更强的设备做分析。
Demo:连接远端计算 Service
import featureAbility from '@ohos.ability.featureAbility';
function connectRemoteService(remoteDeviceId: string) {
let want = {
bundleName: 'com.example.distributeddemo',
abilityName: 'ComputeServiceAbility',
deviceId: remoteDeviceId
};
featureAbility.connectAbility(want, {
onConnect(elementName, remote) {
console.log('远程 Service 已连接');
remote.sendMessage({
command: 'startCompute',
data: [1, 2, 3, 4]
});
},
onDisconnect() {
console.log('远程 Service 已断开');
}
});
}
代码说明
Service 在远端设备运行
本地通过 IPC 的方式和远端通信
计算逻辑完全在远端执行
本地只负责发请求、收结果
这种方式非常适合“重计算、轻交互”的任务。
典型应用场景分析与示例
场景一:手机 + 平板的学习展示系统
场景说明
手机负责控制、翻页
平板负责展示课件内容
实现思路
手机发现平板
在平板启动展示 Ability
通过分布式数据同步当前页码
import distributedData from '@ohos.data.distributedData';
async function syncPage(page: number) {
let kvManager = distributedData.createKVManager();
let store = await kvManager.getKVStore('pageStore');
await store.put('current_page', page);
}
在 Hexacon 2024 上关注到了这么一个议题 《Exploiting File Writes in Hardened Environments - From HTTP Request to ROP Chain in Node.js 》, 同时该作者发了一个简单的 Blog 讲述了下这个原理以及部分细节。[1] 这里简单快速复现一下。
do { r = read(loop->signal_pipefd[0], buf + bytes, sizeof(buf) - bytes);
if (r == -1 && errno == EINTR) continue; ... /* `end` is rounded down to a multiple of sizeof(uv__signal_msg_t). */ end = (bytes / sizeof(uv__signal_msg_t)) * sizeof(uv__signal_msg_t);
for (i = 0; i < end; i += sizeof(uv__signal_msg_t)) { msg = (uv__signal_msg_t*) (buf + i); handle = msg->handle;
由于 FROM node:18@sha256:f910225c96b0f77b0149f350a3184568a9ba6cddba2a7c7805cc125a50591605 我们这个方式拉取的 node 程序本身是没有开PIE的
1 2 3 4 5 6 7 8 9
osboxes@osboxes:~$ checksec node [*] '/home/osboxes/node' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No Debuginfo: Yes
defread_mem(addr, size): if0x0000000000400000< addr< 0x0000000004ff1000: base = 0x0000000000400000 data = mem1[addr-base: addr+size-base] elif0x00000000051f1000 < addr < 0x00000000051f4000: base = 0x00000000051f1000 data = mem2[addr-base: addr+size-base] elif0x00000000051f4000 < addr < 0x000000000520f000: base = 0x00000000051f4000 data = mem3[addr-base: addr+size-base] else: returnNone return data
defis_useful_gadget(out): dis_list = out.split('\n') for n, x inenumerate(dis_list): if x == 'ret': for _ inrange(0, n): if'bad'in dis_list[_] : returnFalse returnTrue returnFalse
CVE-2024-41592 是 forescout 一篇为 《Breaking Into DrayTekRouters Before Threat Actors Do It Again》[1]的漏洞报告其中的一个漏洞。
漏洞产生于 GetCGI() 函数中, 在该函数中处理字符串参数会造成越界导致栈溢出。
漏洞分析
固件解压和调试准备
这里以Draytek 3910的 4.3.1 的版本作为调试 测试版本,进行展开分析。固件的解密和解压不展开赘述,可以参考之前 《HEXACON2022 - Emulate it until you make it! Pwning a DrayTek Router by Philippe Laulheret》 [2]slide 或者其他研究员的文章。
解压后能在 rootfs/firmware/vqemu/sohod64.bin 目录下找到主程序, Draytek 3910 采用了奇葩的 Linux + Qemu + RTOS 的奇葩架构,即在 arm linux操作系统上使用qemu 运行 drayos 的RTOS 操作系统。这里的调试方式采用的是使用编译 Draytek 开源的qemu代码进行编译,然后就可以正常调试。
Although this seems straightforward, challenges exist. Consider the “FreeCtrlName()” function called when a CGI handler returns (Figure 13). This function “frees” all the POST/GET request data structures, including the query string buffer. It simply iterates over the 32-bit pointers located in the lower 4 bytes of the stack 21 DRAY:BREAK - BREAKING INTO DRAYTEK ROUTERS BEFORE THREAT ACTORS DO IT AGAIN addresses and frees them, zeroing out the pointer values as well. Oddly, the higher 4-byte addresses (e.g., pointers to query string parameters values) are never freed
FreeCtrlName 函数伪代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
__int64 __fastcall FreeCtrlName(__int64 result) { int v1; // [xsp+1Ch] [xbp+1Ch] int i; // [xsp+2Ch] [xbp+2Ch]
这个函数的 free 逻辑是, 遍历栈上的指针, 一直free 直到为 0 为止, 因此我们需要找到一个函数可以在栈上写一个 0 , 这样就能避免这个问题。在原文[1] 甚至后来 12月在 Blackhat EU 《When (Remote) Shells Fall Into The Same Hole: Rooting DrayTekRouters Before Attackers Can Do It Again》[3]的slide 上都没有提及这个所谓的 [vulnerable-cgi-page].cgi 是什么。
1.《Breaking Into DrayTekRouters Before Threat Actors Do It Again》https://www.forescout.com/resources/draybreak-draytek-research/↩
2.《HEXACON2022 - Emulate it until you make it! Pwning a DrayTek Router by Philippe Laulheret》https://www.youtube.com/watch?v=CD8HfjdDeuM↩
3.《When (Remote) Shells Fall Into The Same Hole: Rooting DrayTekRouters Before Attackers Can Do It Again》 https://i.blackhat.com/EU-24/Presentations/EU24-Dashevskyi-When-Remote-Shells-Fall-Into-The-Same-Hole.pdf↩
──(root㉿kali)-[/home/kali/Desktop] └─# lvdisplay --- Logical volume --- LV Path /dev/groupA/home LV Name home VG Name groupA LV UUID vPWDHH-AlTq-GvBS-UAnf-orT1-yT2d-TdbWyK LV Write Access read/write LV Creation host, time (none), 2025-01-09 17:28:21 -0500 LV Status NOT available LV Size <4.87 GiB Current LE 1246 Segments 1 Allocation inherit Read ahead sectors auto
--- Logical volume --- LV Path /dev/groupA/runtime LV Name runtime VG Name groupA LV UUID dFDVOl-kYQR-J3N5-3HNC-toXc-9947-sj0yzc LV Write Access read/write LV Creation host, time (none), 2025-01-09 17:28:39 -0500 LV Status NOT available LV Size <19.46 GiB Current LE 4981 Segments 2 Allocation inherit Read ahead sectors auto
--- Logical volume --- LV Path /dev/groupZ/home LV Name home VG Name groupZ LV UUID cOTBS1-oaYw-PlAt-puTS-Uvq5-6C91-pK6QHK LV Write Access read/write LV Creation host, time (none), 2024-10-07 06:47:49 -0400 LV Status NOT available LV Size 6.72 GiB Current LE 1721 Segments 1 Allocation inherit Read ahead sectors auto
# swing @ sw in ~/Dropbox/Attachments/SafetyEquipment/VPN/ivc/2.3 [17:53:53] $ file out2.bak out2.bak: gzip compressed data, last modified: Sat Oct 5 17:32:45 2024, max compression, from Unix, original size modulo 2^32 118361088
$ ./openconnect 172.16.64.222 --protocol=pulse --dump-http-traffic -vvv Attempting to connect to server 172.16.64.222:443 Connected to 172.16.64.222:443 SSL negotiation with 172.16.64.222 Server certificate verify failed: signer not found
Asus, as a leading consumer electronics manufacturer, offers a wide range of IoT devices, but its router products have historically faced significant challenges in security, including critical vulnerabilities such as the cfgserver issue in the Tianfu Cup and the httpd authentication bypass vulnerability. These incidents reveal potential shortcomings in the security design of ASUS router products.
This presentation will provide a systematic attack surface analysis of ASUS router devices, focusing on a review of some key historical vulnerabilities and a deep dive into the lighttpd component within the aicloud service to identify potential security risks. Our analysis will cover multiple vulnerabilities and their associated remote code execution (RCE) vulnerability chains, assess their impact scope and potential consequences, and offer recommendations for future improvements.
for (int i = 0; i < NSS_DATABASE_COUNT; ++i) if (staging->services[i] == NULL) { ok = nss_database_select_default (&cache, i, &staging->services[i]); if (!ok) break; }
staticbool nss_database_check_reload_and_get (struct nss_database_state *local, nss_action_list *result, enum nss_database database_index) { struct __stat64_t64str; /* Acquire MO is needed because the thread that sets reload_disabled may have loaded the configuration first, so synchronize with the Release MO store there. */ if (atomic_load_acquire (&local->data.reload_disabled)) { *result = local->data.services[database_index]; /* No reload, so there is no error. */ returntrue; } structfile_change_detectioninitial; if (!__file_change_detection_for_path (&initial, _PATH_NSSWITCH_CONF)) returnfalse; __libc_lock_lock (local->lock); if (__file_is_unchanged (&initial, &local->data.nsswitch_conf)) { /* Configuration is up-to-date. Read it and return it to the caller. */ *result = local->data.services[database_index]; __libc_lock_unlock (local->lock); returntrue; } int stat_rv = __stat64_time64 ("/", &str); if (local->data.services[database_index] != NULL) { /* Before we reload, verify that "/" hasn't changed. We assume that errors here are very unlikely, but the chance that we're entering a container is also very unlikely, so we err on the side of both very unlikely things not happening at the same time. */ if (stat_rv != 0 || (local->root_ino != 0 && (str.st_ino != local->root_ino || str.st_dev != local->root_dev))) { /* Change detected; disable reloading and return current state. */ atomic_store_release (&local->data.reload_disabled, 1); *result = local->data.services[database_index]; __libc_lock_unlock (local->lock); returntrue; } } if (stat_rv == 0) { local->root_ino = str.st_ino; local->root_dev = str.st_dev; } __libc_lock_unlock (local->lock); /* Avoid overwriting the global configuration until we have loaded everything successfully. Otherwise, if the file change information changes back to what is in the global configuration, the lookups would use the partially-written configuration. */ structnss_database_datastaging = { .initialized = true, }; bool ok = nss_database_reload (&staging, &initial); if (ok) { __libc_lock_lock (local->lock); /* See above for memory order. */ if (!atomic_load_acquire (&local->data.reload_disabled)) /* This may go back in time if another thread beats this thread with the update, but in this case, a reload happens on the next NSS call. */ local->data = staging; *result = local->data.services[database_index]; __libc_lock_unlock (local->lock); } return ok; }
if (atomic_load_acquire (&local->data.reload_disabled)) { *result = local->data.services[database_index]; /* No reload, so there is no error. */ returntrue; }
然后是判断/etc/nsswitch.conf文件是否修改:
1 2 3 4 5 6 7 8 9 10 11 12
structfile_change_detectioninitial; if (!__file_change_detection_for_path (&initial, _PATH_NSSWITCH_CONF)) returnfalse; __libc_lock_lock (local->lock); if (__file_is_unchanged (&initial, &local->data.nsswitch_conf)) { /* Configuration is up-to-date. Read it and return it to the caller. */ *result = local->data.services[database_index]; __libc_lock_unlock (local->lock); returntrue; }
if (local->data.services[database_index] != NULL) { /* Before we reload, verify that "/" hasn't changed. We assume that errors here are very unlikely, but the chance that we're entering a container is also very unlikely, so we err on the side of both very unlikely things not happening at the same time. */ if (stat_rv != 0 || (local->root_ino != 0 && (str.st_ino != local->root_ino || str.st_dev != local->root_dev))) { /* Change detected; disable reloading and return current state. */ atomic_store_release (&local->data.reload_disabled, 1); *result = local->data.services[database_index]; __libc_lock_unlock (local->lock); returntrue; } }
>end (gdb) i b Num Type Disp Enb Address What 3 breakpoint keep y <MULTIPLE> 3.1 y 0x00007ffff7d2b050 in pivot_root at ../sysdeps/unix/syscall-template.S:120 3.2 y 0x00007ffff7eb59b0 in pivot_root at ./pivot.c:39 4 breakpoint keep y 0x00007ffff7eb5b00 in unpivot_root at ./pivot.c:64 5 breakpoint keep y 0x00007ffff7d52300 in nss_database_check_reload_and_get at ./nss/nss_database.c:396 i r rdx c (gdb)
Breakpoint 3.2, pivot_root (new_root=0x5555555a701c "woot", state=0x7fffffffcc38) at ./pivot.c:39 39 { (gdb) c Continuing. Download failed: Invalid argument. Continuing without source file ./nss/./nss/nss_database.c.
Breakpoint 5, nss_database_check_reload_and_get (local=0x5555555a1ad0, result=0x7fffffffc510, database_index=nss_database_initgroups) at ./nss/nss_database.c:396 warning: 396 ./nss/nss_database.c: No such file or directory rdx 0x6 6
Breakpoint 5, nss_database_check_reload_and_get (local=0x5555555a1ad0, result=0x7fffffffc510, database_index=nss_database_group) at ./nss/nss_database.c:396 396 in ./nss/nss_database.c rdx 0x2 2
Breakpoint 4, unpivot_root (state=state@entry=0x7fffffffcc38) at ./pivot.c:64 64 { (gdb) c Continuing. Download failed: Invalid argument. Continuing without source file ./nss/./nss/nss_database.c.
Breakpoint 5, nss_database_check_reload_and_get (local=0x5555555a1ad0, result=0x7ffff7e10b68 <__nss_group_database>, database_index=nss_database_group) at ./nss/nss_database.c:396 warning: 396 ./nss/nss_database.c: No such file or directory rdx 0x2 2
Breakpoint 5, nss_database_check_reload_and_get (local=0x5555555a1ad0, result=0x7ffff7e10b68 <__nss_group_database>, database_index=nss_database_group) at ./nss/nss_database.c:396 396 in ./nss/nss_database.c rdx 0x2 2
Breakpoint 5, nss_database_check_reload_and_get (local=0x5555555a1ad0, result=0x7ffff7e10b00 <__nss_shadow_database>, database_index=nss_database_shadow) at ./nss/nss_database.c:396 396 in ./nss/nss_database.c rdx 0xf 15 Downloading separate debug info for libnss_/woot1337.so.2 Download failed: Invalid argument. Continuing without source file ./nss/./nss/nss_database.c.
当走到 if (local->data.services[database_index] != NULL) 判断的时候
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
if (local->data.services[database_index] != NULL) { /* Before we reload, verify that "/" hasn't changed. We assume that errors here are very unlikely, but the chance that we're entering a container is also very unlikely, so we err on the side of both very unlikely things not happening at the same time. */ if (stat_rv != 0 || (local->root_ino != 0 && (str.st_ino != local->root_ino || str.st_dev != local->root_dev))) { /* Change detected; disable reloading and return current state. */ atomic_store_release (&local->data.reload_disabled, 1); *result = local->data.services[database_index]; __libc_lock_unlock (local->lock); returntrue; } }
由于 local->data.services[database_index] 不为空, 因此会进入 if 的逻辑。 且此时
/* Internal implementation of __nss_module_load. */ staticbool module_load (struct nss_module *module) { if (strcmp (module->name, "files") == 0) return module_load_nss_files (module); if (strcmp (module->name, "dns") == 0) return module_load_nss_dns (module); void *handle; { char *shlib_name; if (__asprintf (&shlib_name, "libnss_%s.so%s", module->name, __nss_shlib_revision) < 0) /* This is definitely a temporary failure. Do not update module->state. This will trigger another attempt at the next call. */ returnfalse; handle = __libc_dlopen (shlib_name); free (shlib_name); } /* Failing to load the module can be caused by several different scenarios. One such scenario is that the module has been removed from the disk. In which case the in-memory version is all that we have, and if the module->state indidates it is loaded then we can use it. */ if (handle == NULL) { /* dlopen failure. We do not know if this a temporary or permanent error. See bug 22041. Update the state using the double-checked locking idiom. */ __libc_lock_lock (nss_module_list_lock); bool result = result; switch ((enum nss_module_state) atomic_load_acquire (&module->state)) { case nss_module_uninitialized: atomic_store_release (&module->state, nss_module_failed); result = false; break; case nss_module_loaded: result = true; break; case nss_module_failed: result = false; break; } __libc_lock_unlock (nss_module_list_lock); return result; } nss_module_functions_untyped pointers; /* Look up and store locally all the function pointers we may need later. Doing this now means the data will not change in the future. */ for (size_t idx = 0; idx < array_length (nss_function_name_array); ++idx) { char *function_name; if (__asprintf (&function_name, "_nss_%s_%s", module->name, nss_function_name_array[idx]) < 0) { /* Definitely a temporary error. */ __libc_dlclose (handle); returnfalse; } pointers[idx] = __libc_dlsym (handle, function_name); free (function_name); PTR_MANGLE (pointers[idx]); }
/* PFMERGE dest src1 src2 src3 ... srcN => OK */ voidpfmergeCommand(client *c){ uint8_t max[HLL_REGISTERS]; structhllhdr *hdr; int j; int use_dense = 0; /* Use dense representation as target? */
/* Compute an HLL with M[i] = MAX(M[i]_j). * We store the maximum into the max array of registers. We'll write * it to the target variable later. */ memset(max,0,sizeof(max)); for (j = 1; j < c->argc; j++) { ... /* Merge with this HLL with our 'max' HLL by setting max[i] * to MAX(max[i],hll[i]). */ if (hllMerge(max,o) == C_ERR) { // hllMerge [1] stack oob write ... } }
/* Convert the destination object to dense representation if at least * one of the inputs was dense. */ if (use_dense && hllSparseToDense(o) == C_ERR) { // hllSparseToDense [2] heap oob write ... }
/* If the representation is already the right one return ASAP. */ hdr = (struct hllhdr*) sparse; if (hdr->encoding == HLL_DENSE) return C_OK;
/* Create a string of the right size filled with zero bytes. * Note that the cached cardinality is set to 0 as a side effect * that is exactly the cardinality of an empty HLL. */ dense = sdsnewlen(NULL,HLL_DENSE_SIZE); hdr = (struct hllhdr*) dense; *hdr = *oldhdr; /* This will copy the magic and cached cardinality. */ hdr->encoding = HLL_DENSE;
/* Now read the sparse representation and set non-zero registers * accordingly. */ p += HLL_HDR_SIZE; while(p < end) { if (HLL_SPARSE_IS_ZERO(p)) { runlen = HLL_SPARSE_ZERO_LEN(p); idx += runlen; p++; } elseif (HLL_SPARSE_IS_XZERO(p)) { runlen = HLL_SPARSE_XZERO_LEN(p); idx += runlen; p += 2; } else { runlen = HLL_SPARSE_VAL_LEN(p); regval = HLL_SPARSE_VAL_VALUE(p); if ((runlen + idx) > HLL_REGISTERS) break; /* Overflow. */ while(runlen--) { HLL_DENSE_SET_REGISTER(hdr->registers,idx,regval); idx++; } p++; } }
/* If the sparse representation was valid, we expect to find idx * set to HLL_REGISTERS. */ if (idx != HLL_REGISTERS) { sdsfree(dense); return C_ERR; }
/* Free the old representation and set the new one. */ sdsfree(o->ptr); o->ptr = dense; return C_OK; }
while 循环之前是对 HLL 数据的的部分 header 解析,之后是一个转换过程。 HLL 数据是一种 SDS [2]字符串的表示。 我们可以用 set 命令来伪造一个 HLL 数据。
// 1. HLL 总体结构 structhllhdr { char magic[4]; /* "HYLL" */ uint8_t encoding; /* HLL_DENSE or HLL_SPARSE. */ uint8_t notused[3]; /* Reserved for future use, must be zero. */ uint8_t card[8]; /* Cached cardinality, little endian. */ uint8_t registers[]; /* Data bytes. */ };
#define HLL_P 14 /* The greater is P, the smaller the error. */ #define HLL_REGISTERS (1<<HLL_P) /* With P=14, 16384 registers. */ #define HLL_DENSE_SIZE (HLL_HDR_SIZE+((HLL_REGISTERS*HLL_BITS+7)/8))