目录
bsdiff差分算法
Bsdiff差分算法是一种针对二进制文件的增量更新算法,由Colin Percival于2003年提出,通过精准定位新旧文件差异并生成高压缩比的补丁包。
bsdiff 算法的主要步骤
-
读取旧文件和新文件:
从输入流中读取旧文件和新文件的内容,并将其存储在内存中。 -
构建后缀数组:
使用divsufsort或divsufsort64函数构建旧文件的后缀数组。后缀数组是一个整数数组,其中每个元素表示旧文件的一个后缀的起始位置。 -
查找最长匹配前缀:
使用二分查找在后缀数组中查找新文件中的每个子串与旧文件中的最长匹配前缀。这一步通过search32或search64函数实现。 -
生成补丁:
根据最长匹配前缀的结果,生成补丁文件。补丁文件包含以下几部分:- 额外数据(即新文件中没有在旧文件中找到的部分)。
- 差异数据(即新文件中与旧文件不同的部分)。
- 匹配块的偏移量和长度。
- 新文件的大小。
后缀数组的构建可以参考下面这篇文章,写的非常好:
源代码
bsdiff源码
static int bsdiff_internal(const struct bsdiff_request req)
{
int64_t *I,*V;
int64_t scan,pos,len;
int64_t lastscan,lastpos,lastoffset;
int64_t oldscore,scsc;
int64_t s,Sf,lenf,Sb,lenb;
int64_t overlap,Ss,lens;
int64_t i;
uint8_t *buffer;
uint8_t buf[8 * 3];
if((V=req.stream->malloc((req.oldsize+1)*sizeof(int64_t)))==NULL) return -1;
I = req.I;
qsufsort(I,V,req.old,req.oldsize);
req.stream->free(V);
buffer = req.buffer;
/* Compute the differences, writing ctrl as we go */
scan=0;len=0;pos=0;
lastscan=0;lastpos=0;lastoffset=0;
while(scan<req.newsize) {
oldscore=0;
for(scsc=scan+=len;scan<req.newsize;scan++) {
len=search(I,req.old,req.oldsize,req.new+scan,req.newsize-scan,
0,req.oldsize,&pos);
for(;scsc<scan+len;scsc++)
if((scsc+lastoffset<req.oldsize) &&
(req.old[scsc+lastoffset] == req.new[scsc]))
oldscore++;
if(((len==oldscore) && (len!=0)) ||
(len>oldscore+8)) break;
if((scan+lastoffset<req.oldsize) &&
(req.old[scan+lastoffset] == req.new[scan]))
oldscore--;
};
if((len!=oldscore) || (scan==req.newsize)) {
s=0;Sf=0;lenf=0;
for(i=0;(lastscan+i<scan)&&(lastpos+i<req.oldsize);) {
if(req.old[lastpos+i]==req.new[lastscan+i]) s++;
i++;
if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; };
};
lenb=0;
if(scan<req.newsize) {
s=0;Sb=0;
for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) {
if(req.old[pos-i]==req.new[scan-i]) s++;
if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; };
};
};
if(lastscan+lenf>scan-lenb) {
overlap=(lastscan+lenf)-(scan-lenb);
s=0;Ss=0;lens=0;
for(i=0;i<overlap;i++) {
if(req.new[lastscan+lenf-overlap+i]==
req.old[lastpos+lenf-overlap+i]) s++;
if(req.new[scan-lenb+i]==
req.old[pos-lenb+i]) s--;
if(s>Ss) { Ss=s; lens=i+1; };
};
lenf+=lens-overlap;
lenb-=lens;
};
offtout(lenf,buf);
offtout((scan-lenb)-(lastscan+lenf),buf+8);
offtout((pos-lenb)-(lastpos+lenf),buf+16);
/* Write control data */
if (writedata(req.stream, buf, sizeof(buf)))
return -1;
/* Write diff data */
for(i=0;i<lenf;i++)
buffer[i]=req.new[lastscan+i]-req.old[lastpos+i];
if (writedata(req.stream, buffer, lenf))
return -1;
/* Write extra data */
for(i=0;i<(scan-lenb)-(lastscan+lenf);i++)
buffer[i]=req.new[lastscan+lenf+i];
if (writedata(req.stream, buffer, (scan-lenb)-(lastscan+lenf)))
return -1;
lastscan=scan-lenb;
lastpos=pos-lenb;
lastoffset=pos-scan;
};
};
return 0;
}
bspatch源码
int bspatch(const uint8_t* old, int64_t oldsize, uint8_t* new, int64_t newsize, struct bspatch_stream* stream)
{
uint8_t buf[8];
int64_t oldpos,newpos;
int64_t ctrl[3];
int64_t i;
oldpos=0;newpos=0;
while(newpos<newsize) {
/* Read control data */
for(i=0;i<=2;i++) {
if (stream->read(stream, buf, 8))
return -1;
ctrl[i]=offtin(buf);
};
/* Sanity-check */
if (ctrl[0]<0 || ctrl[0]>INT_MAX ||
ctrl[1]<0 || ctrl[1]>INT_MAX ||
newpos+ctrl[0]>newsize)
return -1;
/* Read diff string */
if (stream->read(stream, new + newpos, ctrl[0]))
return -1;
/* Add old data to diff string */
for(i=0;i<ctrl[0];i++)
if((oldpos+i>=0) && (oldpos+i<oldsize))
new[newpos+i]+=old[oldpos+i];
/* Adjust pointers */
newpos+=ctrl[0];
oldpos+=ctrl[0];
/* Sanity-check */
if(newpos+ctrl[1]>newsize)
return -1;
/* Read extra string */
if (stream->read(stream, new + newpos, ctrl[1]))
return -1;
/* Adjust pointers */
newpos+=ctrl[1];
oldpos+=ctrl[2];
};
return 0;
}
测试历程
在源码目录下创建build目录,用来存放生成的可执行程序

进入build目录,构建cmake编译环境

编译环境构建完成后,编译代码,生成可执行程序

生成可执行程序bsdiff、bspatch

执行可执行程序bsdiff,生成差分包patchfile

执行可执行程序bspatch,通过old_cp.bin和差分包patchfile,还原得到new_cp2.bin

比较原始new_cp.bin和new_cp2.bin,十六进制比较无差异,还原成功

差分包patchfile大小993KB,新固件new_cp.bin大小6472KB,bsdiff差分算法此次压缩率大约是15%,如果新旧固件差异很小,压缩率会更大。

想获取源码的同学在资源中下载,可以在本地操作一下压缩还原的过程,通过学习增长自己的技能。
1万+

被折叠的 条评论
为什么被折叠?



