فهرست مقاله
در خطِ ساختِ نرمافزار، ممکن است گزارشِ تولیدشده هیچ تغییری نکرده باشد، اما فایلِ فشردهٔ تحویلی با اجرای قبلی متفاوت شود. پیش از آنکه این اختلاف را به محاسبه یا داده نسبت بدهیم، باید قراردادِ بستهبندی را هم بررسی کنیم. این درس با بایتهای تألیفی، پوششِ gzip و یک مرجعِ مستقل، تفاوتِ محتوای بازشده و بایتهای بسته را روشن میکند. نمونه هیچ گزارشِ محصول، اندازهگیریِ دستگاه یا اجرای واقعیِ خطِ انتشار نیست؛ همهٔ ساختها در حافظه انجام میشوند.
۱. بایتِ ورودی و دو معنای تکرارپذیری

دو پرسش را جدا بنویسیم: آیا پس از بازکردنِ بسته همان بایتهای ورودی به دست میآیند؟ و آیا خودِ بسته در تکرارِ همین محیط، دقیقاً همان بایتها را دارد؟ پرسشِ نخست دربارهٔ محتواست؛ دومی علاوه بر محتوا به نمایش و فراداده هم مربوط میشود. دو عضوِ معتبر میتوانند محتوای برابر و نمایشِ متفاوت داشته باشند. پس مقایسهٔ فایلِ فشرده و مقایسهٔ محتوای آن، دو معیارِ پذیرشِ متفاوتاند.
تابعِ اصلی فقط شیءِ دقیقِ bytes با طولِ صفر تا ۴۰۹۶ بایت میپذیرد. متن، bytearray، نمای حافظه و زیرکلاسِ بایت در سیاستِ خودمان رد میشوند؛ این محدودیتِ gzip عمومی نیست. ورودیِ خالی معتبر است. این سقف برای کوچک و قابل بازبینی ماندنِ مثال انتخاب شده و اندازهٔ فایلِ فشرده یا سقفِ ایمنِ بازکردنِ هر فایلِ ناشناس را تعریف نمیکند.
واحدِ طول، بایت است. رشتهٔ فارسی ابتدا با انتخابِ صریحِ UTF-8 به بایت تبدیل میشود و پایانِ خط هم بخشی از ورودی میماند. تبدیلِ LF به CRLF میتواند بسته را عوض کند، چون محتوا عوض شده است. برنامه چنین تبدیلی را پنهانی انجام نمیدهد. مسئلهٔ ثبتِ ورودی و محیط در آزمایشِ تکرارپذیرِ سیگنال هم مطرح بود؛ اینجا فقط لایهٔ بستهبندیِ یک ورودیِ ازپیشمعلوم را بررسی میکنیم.
۲. زمان و نام در پوششِ gzip

طبق مستنداتِ gzip در پایتون، زمانِ صفر، وابستگیِ خروجی به زمانِ ساخت را حذف میکند. بااینحال compress و GzipFile را یک رابط فرض نکنیم: در مستنداتِ ۳٫۱۴، پیشفرضِ اولی صفر و پیشفرضِ زمانِ سازندهٔ دومی None است. تابعِ آموزشی هر بار سطحِ نه و زمانِ صفر را صریح میدهد تا قرارداد از روی فراخوانی معلوم باشد.
شرحِ سرآیند و دنباله در RFC 1952، زمانِ صفر را نبودِ زمانِ در دسترس معرفی میکند؛ معنایش این نیست که فایلِ اصلی واقعاً در ابتدای دورانِ یونیکس ساخته شده است. این سندِ اطلاعرسانِ مهٔ ۱۹۹۶، قالب را توضیح میدهد و خبرِ تازه یا ادعای استانداردِ اینترنتیِ جدید نیست. تاریخِ مراجعه به مستندات را نیز با تاریخِ انتشارِ سند یا نسخهٔ مفسر یکی نمیگیریم.
در آزمایشِ نام، از GzipFile با دو نامِ متفاوت و یک بافرِ حافظه استفاده میکنیم. محتوا، سطح و زمان برابرند، اما نام میتواند واردِ سرآیند شود. تفاوتِ بایتها در این حالت شاهدِ تغییرِ محتوا نیست. هیچ فایلِ دیسکی ساخته نمیشود؛ نامها فقط دادهٔ فرادادهایِ مثالاند. حذفِ وابستگی به ساعت نیز بهتنهایی همهٔ علتهای اختلافِ بسته را حذف نمیکند.
پروفایلِ تولیدکنندهٔ خودمان سرآیندِ دهبایتیِ بدونِ فیلدِ اختیاری دارد. بایتِ روش، هشت و بایتِ سیستمعامل، ۲۵۵ است؛ مستندات برای compress از نسخهٔ ۳٫۱۳ مقدارِ دوم را تضمین میکنند. این انتخاب را به همهٔ gzipهای معتبر تعمیم نمیدهیم. تابعِ نمایشِ فیلدها فقط روی عضوهای معلومِ همین نمونه به کار میرود و تجزیهگر یا اعتبارسنجِ عمومیِ ورودیِ نامطمئن نیست.
۳. کدِ کامل و عضوِ مستقلِ ذخیرهای

رفتوبرگشتِ تابعِ فشردهساز و بازکننده، بهتنهایی مرجعِ مستقل نمیسازد. برای شاهدِ جدا، یک عضوِ کوچک را بدونِ فراخوانیِ فشردهسازیِ کتابخانه میسازیم: سرآیندِ ثابت، یک بلوکِ ذخیرهای، خودِ بایتهای ورودی و دنباله. در این مرجع، CRC نیز با حلقهٔ بیتیِ جدا محاسبه میشود و از تابعِ CRC در zlib استفاده نمیکنیم. این سازنده فقط fixtureهای محدودِ درس را پوشش میدهد.
بلوکِ بدونِ فشردهسازی در RFC 1951، طول و مکملِ یکِ آن را پیش از دادهٔ لفظی قرار میدهد. برای یک بلوکِ نهایی، بیتِ پایان یک و نوع صفرصفر است؛ پس پس از همترازی، طولِ دوبایتی و مکملش میآیند. سقفِ ۴۰۹۶ ما زیرِ حدِ ۶۵۵۳۵ بایتیِ این بلوک است. کد، جدولِ Huffman، ارجاعِ عقبرو یا رمزگشای کاملِ DEFLATE نمیسازد.
نمونهٔ دستی، نه بایتِ ASCII از یک تا نه است. طولِ بلوک نه و مکملِ شانزدهبیتیِ آن fff6 است. مقدارِ CRC در قراردادِ این قالب cbf43926 میشود؛ در دنباله، بایتِ کمارزش زودتر میآید. اندازهٔ عضوِ ذخیرهای ۳۲ بایت است: ده سرآیند، پنج سربارِ بلوک، نه داده و هشت دنباله. برابریِ عضوِ مرجع با یک رشتهٔ کاملِ دستی جدا کنترل میشود.
"""Bounded gzip artifact metadata; replay evidence only in this runtime.
No filesystem/network/device input. The independent stored-member producer
never calls a compression library or its CRC implementation.
"""
import gzip
import io
from itertools import product
import json
import platform
import sys
import zlib
MAX_PAYLOAD_BYTES = 4096
NATIVE_HEADER = bytes.fromhex('1f8b08000000000002ff')
STORED_HEADER = bytes.fromhex('1f8b08000000000000ff')
def artifact(payload):
"""Our narrow exact-bytes producer, not a general gzip validation API."""
if type(payload) is not bytes or len(payload) > MAX_PAYLOAD_BYTES:
raise ValueError('exact bytes payload length 0..4096 required')
return gzip.compress(payload, compresslevel=9, mtime=0)
def little(value, width):
return bytes((value >> (8 * index)) & 255 for index in range(width))
def crc32_bitwise(payload):
"""Table-free reflected CRC; independent of gzip/zlib CRC code."""
value = 0xffffffff
for octet in payload:
value ^= octet
for _ in range(8):
value = (value >> 1) ^ (0xedb88320 if value & 1 else 0)
return value ^ 0xffffffff
def stored_reference(payload):
"""Trusted bounded fixtures: one BFINAL=1, BTYPE=00 literal block."""
assert type(payload) is bytes and len(payload) <= 4096
n = len(payload)
block = b'\x01' + little(n, 2) + little(n ^ 0xffff, 2) + payload
trailer = little(crc32_bitwise(payload), 4) + little(n, 4)
return STORED_HEADER + block + trailer
def own_fields(member):
"""Only own known single members, not arbitrary gzip parsing/validation."""
return {'header_hex': member[:10].hex(),
'mtime_seconds': int.from_bytes(member[4:8], 'little'),
'flags': member[3], 'extra_flags': member[8], 'os_byte': member[9],
'crc32_hex': format(int.from_bytes(member[-8:-4], 'little'), '08x'),
'isize_bytes_mod_2_32': int.from_bytes(member[-4:], 'little')}
def named_fixture(payload, name):
"""Only a metadata counterexample; writes a BytesIO, never a disk file."""
buffer = io.BytesIO()
with gzip.GzipFile(filename=name, mode='wb', compresslevel=9,
fileobj=buffer, mtime=0) as output:
output.write(payload)
return buffer.getvalue()
def invalid_payload(value):
try:
artifact(value)
except ValueError:
return True
return False
def invalid_gzip(member):
try:
gzip.decompress(member)
except (gzip.BadGzipFile, EOFError, zlib.error):
return True
return False
def run():
checks = {}
def check(name, value):
assert type(value) is bool, name
checks[name] = value
assert value, name
class BytesSubclass(bytes):
pass
pulled = []
def arbitrary_source():
pulled.append('consumed')
yield 7
source = arbitrary_source()
bad = ('text', True, None, 7, bytearray(b'a'), memoryview(b'a'),
BytesSubclass(b'a'), source)
check('strict_exact_bytes_policy_and_no_iterator_consumption',
all(invalid_payload(x) for x in bad) and pulled == [])
boundary = b'x' * MAX_PAYLOAD_BYTES
check('own_payload_byte_cap_inclusive_4096',
gzip.decompress(artifact(boundary)) == boundary
and invalid_payload(boundary + b'x') and artifact(b'')[:10] == NATIVE_HEADER)
payload = b'channel=voltage\nunit=mV\nsamples=12,13,12\n'
native = artifact(payload)
fields = own_fields(native)
check('explicit_level9_mtime0_no_name_profile_header',
native[:10] == NATIVE_HEADER and fields['mtime_seconds'] == 0
and fields['flags'] == 0 and fields['os_byte'] == 255)
digits = b'123456789'
literal_stored = bytes.fromhex(
'1f8b08000000000000ff' '010900f6ff' '313233343536373839'
'2639f4cb09000000')
digits_native = artifact(digits)
check('manual_crc_size_and_complete_stored_member_fixture',
crc32_bitwise(b'') == 0 and crc32_bitwise(digits) == 0xcbf43926
and digits_native[-8:] == bytes.fromhex('2639f4cb09000000')
and stored_reference(digits) == literal_stored
and gzip.decompress(literal_stored) == digits)
check('whole_artifact_repeat_only_same_actual_environment',
artifact(payload) == native == artifact(bytes(bytearray(payload))))
dated_a = gzip.compress(payload, compresslevel=9, mtime=1)
dated_b = gzip.compress(payload, compresslevel=9, mtime=2)
check('mtime_changes_artifact_without_changing_payload',
dated_a != dated_b and dated_a[4:8] == b'\x01\x00\x00\x00'
and dated_b[4:8] == b'\x02\x00\x00\x00'
and gzip.decompress(dated_a) == gzip.decompress(dated_b) == payload)
named_a, named_b = named_fixture(payload, 'run-A.json'), named_fixture(payload, 'run-B.json')
check('original_filename_metadata_changes_bytes_in_memory',
named_a != named_b and named_a[3] & 8 == named_b[3] & 8 == 8
and gzip.decompress(named_a) == gzip.decompress(named_b) == payload)
text = '\u0648\u0644\u062a\u0627\u0698\n'
utf8_lf = text.encode('utf-8')
utf8_crlf = text.replace('\n', '\r\n').encode('utf-8')
check('UTF8_payload_and_line_endings_are_exact_bytes',
utf8_lf != utf8_crlf and artifact(utf8_lf) != artifact(utf8_crlf)
and gzip.decompress(artifact(utf8_lf)) == utf8_lf
and gzip.decompress(artifact(utf8_crlf)) == utf8_crlf)
empty_reference = bytes.fromhex('1f8b08000000000000ff' '010000ffff' '0000000000000000')
check('empty_payload_has_a_valid_nonempty_member',
stored_reference(b'') == empty_reference and len(empty_reference) == 23
and gzip.decompress(artifact(b'')) == gzip.decompress(empty_reference) == b'')
binary = bytes(range(256))
check('all_256_byte_values_are_binary_payload_not_text',
gzip.decompress(artifact(binary)) == gzip.decompress(stored_reference(binary)) == binary)
corrupt_crc = native[:-8] + bytes([native[-8] ^ 1]) + native[-7:]
check('chosen_CRC_trailer_corruption_is_rejected', invalid_gzip(corrupt_crc))
corrupt_size = native[:-1] + bytes([native[-1] ^ 1])
check('chosen_ISIZE_trailer_corruption_is_rejected', invalid_gzip(corrupt_size))
combined = artifact(b'left:') + stored_reference(b'right')
check('valid_multiple_members_decode_as_concatenated_content',
gzip.decompress(combined) == b'left:right')
cases = 0
for length in range(7):
for values in product((0, 255), repeat=length):
data = bytes(values)
independent = stored_reference(data)
produced = artifact(data)
assert independent[:10] == STORED_HEADER
assert independent[10:15] == b'\x01' + little(length, 2) + little(length ^ 0xffff, 2)
assert independent[15:-8] == data and len(independent) == length + 23
assert gzip.decompress(independent) == gzip.decompress(produced) == data
assert produced[:10] == NATIVE_HEADER and produced != independent
assert int.from_bytes(produced[-8:-4], 'little') == crc32_bitwise(data)
assert int.from_bytes(produced[-4:], 'little') == length
cases += 1
check('independent_stored_members_127_payloads_ONE_group', cases == 127)
assert len(checks) == 14
return {'python': platform.python_version(), 'implementation': platform.python_implementation(),
'dependencies': 'stdlib gzip/io/itertools/json/platform/sys/zlib only',
'zlib_build_version': zlib.ZLIB_VERSION, 'zlib_runtime_version': zlib.ZLIB_RUNTIME_VERSION,
'payload_unit': 'byte', 'own_payload_max_bytes': MAX_PAYLOAD_BYTES,
'producer_policy': {'compresslevel': 9, 'mtime_seconds': 0, 'original_filename': None},
'profile_scope': 'Own gzip.compress single-member outputs only; own_fields is not a general parser',
'replay_scope': 'Only actual same environment and exact input/parameters; no cross-version byte guarantee',
'example_payload_hex': payload.hex(), 'example_payload_bytes': len(payload),
'example_compressed_bytes': len(native), 'example_header_and_trailer': fields,
'manual_digits_payload_bytes': 9, 'manual_digits_CRC32_hex': 'cbf43926',
'manual_stored_digits_member_hex': literal_stored.hex(), 'manual_stored_digits_member_bytes': len(literal_stored),
'same_content_different_valid_encoding': native != stored_reference(payload),
'mtime_pair_seconds': [1, 2], 'same_content_different_mtime_artifact': dated_a != dated_b,
'same_content_different_name_artifact': named_a != named_b,
'empty_stored_member_bytes': len(empty_reference),
'independent_stored_members_in_ONE_group': cases,
'oracle_scope': 'Own literal stored-block producer and table-free CRC, no compression-library calls in oracle',
'new_file_network_device_or_benchmark_operations': 0, 'named_check_groups': len(checks), 'checks': checks}
if __name__ == '__main__':
sys.stdout.buffer.write((json.dumps(run(), ensure_ascii=True, indent=2, allow_nan=False) + '\n').encode('utf-8'))
کد فقط کتابخانهٔ استاندارد میخواهد و هیچ ساعتسنجی یا مقایسهٔ سرعتی ندارد. نسخهٔ پایتون و نسخهٔ زمانِ ساخت و اجرای zlib در گزارش ثبت میشوند. گزارش از کانالِ بایتی با UTF-8 و LF نوشته میشود. خودِ ورودیها باینریاند؛ متنِ نمایشدادهشدهٔ برنامه یا تصویرِ مفهومی، جانشینِ بایتهای ثبتشده نیست.
۴. آزمونِ مرجع و مرزِ نتیجه

مرجع، ۱۲۷ ورودیِ کامل با طولِ صفر تا شش و الفبای بایتیِ صفر و ۲۵۵ میسازد. برای هر ورودی، عضوِ دستساز باز میشود و با همان ورودی مقایسه میگردد؛ سپس CRC و اندازهٔ دنبالهٔ خروجیِ تولیدکننده نیز با مرجع سنجیده میشوند. این خانواده یک گروهِ آزمون است. تفاوتِ بایتهای عضوِ ذخیرهای و عضوِ فشردهٔ کتابخانه، خطا محسوب نمیشود؛ محتوای بازشده باید برابر باشد.
چهارده گروهِ نامدار، علاوه بر آن خانواده، مرزِ طول، نوعِ ورودی، عضوِ خالی، همهٔ مقدارهای یک بایت، زمان، نام و پایانِ خط را بررسی میکنند. دو دستکاریِ مشخص در CRC و اندازهٔ دنباله باید با خانوادهٔ خطاهای مستند رد شوند. از این دو مثال نتیجه نمیگیریم که هر تغییرِ ممکن حتماً تشخیص داده میشود یا پیام و نوعِ خطا در همهٔ نسخهها یکسان خواهد بود.
{
"python": "3.14.5",
"implementation": "CPython",
"dependencies": "stdlib gzip/io/itertools/json/platform/sys/zlib only",
"zlib_build_version": "1.3.1.zlib-ng",
"zlib_runtime_version": "1.3.1.zlib-ng",
"payload_unit": "byte",
"own_payload_max_bytes": 4096,
"producer_policy": {
"compresslevel": 9,
"mtime_seconds": 0,
"original_filename": null
},
"profile_scope": "Own gzip.compress single-member outputs only; own_fields is not a general parser",
"replay_scope": "Only actual same environment and exact input/parameters; no cross-version byte guarantee",
"example_payload_hex": "6368616e6e656c3d766f6c746167650a756e69743d6d560a73616d706c65733d31322c31332c31320a",
"example_payload_bytes": 41,
"example_compressed_bytes": 61,
"example_header_and_trailer": {
"header_hex": "1f8b08000000000002ff",
"mtime_seconds": 0,
"flags": 0,
"extra_flags": 2,
"os_byte": 255,
"crc32_hex": "a775abb9",
"isize_bytes_mod_2_32": 41
},
"manual_digits_payload_bytes": 9,
"manual_digits_CRC32_hex": "cbf43926",
"manual_stored_digits_member_hex": "1f8b08000000000000ff010900f6ff3132333435363738392639f4cb09000000",
"manual_stored_digits_member_bytes": 32,
"same_content_different_valid_encoding": true,
"mtime_pair_seconds": [
1,
2
],
"same_content_different_mtime_artifact": true,
"same_content_different_name_artifact": true,
"empty_stored_member_bytes": 23,
"independent_stored_members_in_ONE_group": 127,
"oracle_scope": "Own literal stored-block producer and table-free CRC, no compression-library calls in oracle",
"new_file_network_device_or_benchmark_operations": 0,
"named_check_groups": 14,
"checks": {
"strict_exact_bytes_policy_and_no_iterator_consumption": true,
"own_payload_byte_cap_inclusive_4096": true,
"explicit_level9_mtime0_no_name_profile_header": true,
"manual_crc_size_and_complete_stored_member_fixture": true,
"whole_artifact_repeat_only_same_actual_environment": true,
"mtime_changes_artifact_without_changing_payload": true,
"original_filename_metadata_changes_bytes_in_memory": true,
"UTF8_payload_and_line_endings_are_exact_bytes": true,
"empty_payload_has_a_valid_nonempty_member": true,
"all_256_byte_values_are_binary_payload_not_text": true,
"chosen_CRC_trailer_corruption_is_rejected": true,
"chosen_ISIZE_trailer_corruption_is_rejected": true,
"valid_multiple_members_decode_as_concatenated_content": true,
"independent_stored_members_127_payloads_ONE_group": true
}
}
CRC مربوط به بایتهای بازشده و اندازهٔ دنباله، طولِ ورودی به پیمانهٔ دو به توانِ ۳۲ است؛ با سقفِ کوچکِ ما دورزدنِ اندازه رخ نمیدهد. این بررسیها احرازِ هویت نیستند. همچنین کنارهمگذاشتنِ دو عضوِ معتبر میتواند محتوای پیوستهٔ آن دو را بدهد. تولیدِ یک عضو در رابطِ ما، دلیلی برای نامعتبر خواندنِ همهٔ فایلهای چندعضوی نیست.
در درسِ کدِ پیشوندیِ Huffman، هزینهٔ بیتِ منطقی از قالبِ کامل جدا بود. اینجا عضوِ واقعیِ gzip داریم، اما ادعای بهینهبودنِ اندازه، سازگاریِ همهٔ پیادهسازیها یا تکرارِ بایتها در نسخههای آینده نداریم. تکرارِ برابر فقط شاهدِ همین محیط، همین ورودی و همین پارامترهاست؛ رفتارِ فشردهساز میتواند با تغییرِ محیط عوض شود.
۵. چهار پرسش برای نگهداریِ بستهٔ خروجی

پرسش اول: زمانِ صفر چه تاریخی را اثبات میکند؟ پاسخ: هیچ تاریخِ ساختِ واقعی را اثبات نمیکند؛ در سرآیند یعنی زمان در دسترس نیست. مزیتِ قراردادیِ آن در این مثال، حذفِ ساعتِ ساخت از خروجی است. برای تاریخِ گزارش باید فیلدی جدا با معنای روشن داشته باشیم، نه آنکه از صفر، روزِ ساختهشدنِ داده را حدس بزنیم.
پرسش دوم: چرا با محتوای برابر، دو خروجیِ نامدار متفاوتاند؟ پاسخ: نامِ اصلی میتواند در پوشش ثبت شود. برنامه برابریِ محتوای بازشده را جدا از نابرابریِ بستهها میسنجد. پیش از تغییرِ معیارِ پذیرش، باید تصمیم بگیریم هویتِ موردِ نیاز، محتوای گزارش است یا کلِ فایلِ تحویلی؛ ثابتکردنِ نام و زمان، بخشی از قراردادِ دوم است.
پرسش سوم: چرا مرجعِ ۳۲بایتی باید با خروجیِ کتابخانه متفاوت باشد؟ پاسخ: مرجع، دادهٔ نهبایتی را لفظی در یک بلوکِ ذخیرهای قرار میدهد و پرچمِ اضافیِ سرآیندش صفر است؛ پروفایلِ سطحِ نهٔ ما پرچمِ دو دارد. هدف، برابریِ محتوای بازشده و درستیِ ساختِ مستقل است، نه تحمیلِ نمایشِ ذخیرهای به فشردهساز. تفاوتِ معتبر، نادرستی نیست.
پرسش چهارم: آیا قبولیِ این آزمون، خروجیِ همیشگیِ یکسان را تضمین میکند؟ پاسخ: خیر. ورودیِ دقیق، پارامترها و نسخههای محیط را نگه دارید و پس از ارتقا دوباره قرارداد را بسنجید. محتوای بازشده و بستهٔ فشرده را جدا مقایسه کنید. شواهدِ فعلی، چهارده گروهِ همین اجرای محدودند؛ دربارهٔ دوامِ فایل، امنیت، دادهٔ فیزیکی یا کاراییِ محصول حکمی نمیدهند.
منابع و مطالعهٔ بیشتر
- Python؛ gzip، زمان و نامِ سرآیند
- RFC1952؛ عضوِ gzip و سرآیند و دنباله
- RFC1951؛ بلوکِ ذخیرهایِ بدونِ فشردهسازی
بررسی منابع: ۲۸ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

