برای بررسی دادهٔ اندازه‌گیری، گاهی لازم است بدانیم یک مقدار کنترل دقیقاً از کدام بایت‌ها ساخته شده است. در این آموزش یک بخش TCP ساختگی با محمولهٔ حسگر می‌سازیم، checksum آن را محاسبه می‌کنیم و سپس همان بایت‌ها را مستقل می‌آزماییم. همه‌چیز در حافظه است؛ اتصال، دریافت بسته و اندازه‌گیری واقعی نداریم. مسئلهٔ اصلی، جمع مکمل یک و زمینهٔ IPv4 است.

۱. بایت‌های حسگر و واژه‌های شانزده‌بیتی

جفت‌کردن بایت‌ها در محاسبه
تصویر مفهومی: جفت‌کردن بایت‌ها در محاسبه؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

محمولهٔ تألیفی ما هفت بایت دارد: شناسهٔ کانال برابر سه و سه نمونهٔ علامت‌دارِ دوبایتی با مقدارهای ۱۲۵۰، منفی ۵۰۰ و ۳۳۰۰ میلی‌ولت. قالب !Bhhh ترتیب بایت بزرگ‌ارزش‌اول و اندازه‌های ثابت می‌دهد. واحد mV قرارداد همین مثال است؛ محاسبهٔ checksum اصلاً معنای ولتاژ را نمی‌فهمد و به کالیبراسیون یا درستی محدودهٔ حسگر رسیدگی نمی‌کند.

دنبالهٔ محموله 0304e2fe0c0ce4 می‌شود. مرز فیلدهای کاربردی الزاماً با جفت‌های checksum یکسان نیست: نخستین واژهٔ محموله از شناسه و بایت نخستِ نمونه تشکیل می‌شود. بنابراین نباید هر مقدار حسگر را جدا جمع کنیم و انتظار پاسخ یکسان داشته باشیم. ورودی محاسبه، ترتیب پیوستهٔ بایت‌هاست؛ علامت‌دار بودن نمونه نیز تفسیر این واژه‌های بدون علامت را عوض نمی‌کند.

RFC 1071، بخش‌های یک تا سه جمع واژه‌های شانزده‌بیتی و بازگرداندن نقلی به انتهای کم‌ارزش را توضیح می‌دهد. مثلاً جمع ffff و 0001 ابتدا 10000 است؛ با بازگرداندن نقلی، یک می‌شود. مکمل شانزده‌بیتیِ نتیجه fffe است. حذف سادهٔ بیت بالایی، این جواب را تولید نمی‌کند.

برای سه بایت 12 34 56، واژه‌ها 1234 و 5600 هستند و checksum برابر 97cb می‌شود. صفرِ آخر فقط تکمیل مفهومیِ محاسبه است. طول دادهٔ اصلی سه بایت باقی می‌ماند؛ به‌ویژه نباید به علت فرد بودن طول، صفر تازه‌ای به محمولهٔ ارسال‌شدنی بچسبانیم.

۲. شبه‌سرآیند IPv4 و طولی که روی سیم اضافه نمی‌شود

اطلاعات زمینهٔ مستقل از بسته
تصویر مفهومی: اطلاعات زمینهٔ مستقل از بسته؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

بخش ۳٫۱ در RFC 9293 علاوه بر سرآیند و دادهٔ TCP، شبه‌سرآیند را وارد checksum می‌کند. در IPv4 این زمینه دوازده بایت دارد: نشانی مبدأ، نشانی مقصد، بایت صفر، شمارهٔ پروتکل و طول TCP. طول، سرآیند TCP و محموله را می‌شمارد؛ خودِ شبه‌سرآیند جزو آن نیست. فیلد checksum هنگام ساخت مقدار، صفر در نظر گرفته می‌شود.

نشانی‌های نمونه 192.0.2.10 و 198.51.100.20 فقط رشته‌های ورودی محاسبه‌اند. پروتکل را شش و طول TCP را بیست‌وهفت بایت می‌گذاریم: بیست بایت سرآیند ثابت و هفت بایت داده. شبه‌سرآیند حاصل c000020ac63364140006001b است. مقدار 001b همان بیست‌وهفت است؛ نه سی‌ونه و نه بیست‌وهشت.

سرآیند ثابتِ ما گزینه ندارد؛ Data Offset برابر پنج واژهٔ چهاربایتی است. شماره‌های توالی، تأیید، پرچم‌ها و پنجره عمداً ثابت‌اند تا ورودی آزمون خوانا باشد. از این انتخاب، برقراری اتصال یا معتبر بودن وضعیت یک نشست نتیجه نمی‌شود. کد هیچ بستهٔ کامل IP نمی‌سازد و این مثال، پیاده‌سازی ماشین حالت TCP نیست.

محاسبهٔ حاضر checksum سرآیند IPv4 نیست. از زمینهٔ IPv4 فقط فیلدهای مشخص شبه‌سرآیند را می‌گیریم؛ کل سرآیند IP ورودی این تابع نیست. همچنین این محاسبه را نباید روی هر قطعهٔ دلخواهِ خوانده‌شده از سوکت اعمال کرد: ورودی ما از ابتدا یک بخش TCP کامل و ازپیش‌جداشده است، با نشانی‌های متناظرِ معلوم.

۳. کد کاملِ ساخت و بررسی checksum

ساخت بسته و وارسیِ دوباره
تصویر مفهومی: ساخت بسته و وارسیِ دوباره؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

کد را با نام tcp_checksum.py ذخیره و با python tcp_checksum.py اجرا کنید. اجرای ثبت‌شده Python 3.14.5 و فقط کتابخانهٔ استاندارد دارد. تابع اصلی جفت‌ها را به ترتیب شبکه می‌خواند، نقلی هر جمع را برمی‌گرداند و در پایان همهٔ شانزده بیت را معکوس می‌کند. Python عدد صحیح با پهنای دلخواه دارد؛ سرریز ماشینی در این جمع رخ نمی‌دهد.

"""Offline IPv4 TCP checksum lesson; no sockets or actual packets are sent."""
import ipaddress
import json
import platform
import struct

MAX_TCP_BYTES = 65535 - 20  # Ordinary IPv4, assumed 20-byte IP header.


def exact_bytes(data):
    if type(data) is not bytes:
        raise TypeError('exact bytes required')
    return data


def checksum(data):
    """16-bit network-order Internet checksum, conceptual odd-byte padding."""
    exact_bytes(data)
    total = 0
    for offset in range(0, len(data), 2):
        word = data[offset] << 8
        if offset + 1 < len(data):
            word |= data[offset + 1]
        total += word
        total = (total & 0xffff) + (total >> 16)
    return total ^ 0xffff


def modular_oracle(data):
    """Independent byte-weighted integer arithmetic, no carry-fold loop."""
    weighted = sum(byte * (256 if index % 2 == 0 else 1)
                   for index, byte in enumerate(data))
    residue = weighted % 65535
    folded = 65535 if weighted and residue == 0 else residue
    return folded ^ 65535


def ipv4(address):
    if type(address) is not str:
        raise TypeError('IPv4 string required')
    return ipaddress.IPv4Address(address).packed


def pseudoheader(source, destination, length):
    if type(length) is not int or not 20 <= length <= MAX_TCP_BYTES:
        raise ValueError('TCP length must fit ordinary IPv4 with 20-byte IP header')
    return ipv4(source) + ipv4(destination) + struct.pack('!BBH', 0, 6, length)


def build_segment(source, destination, payload, source_port=41000, destination_port=9000):
    exact_bytes(payload)
    for port in (source_port, destination_port):
        if type(port) is not int or not 0 <= port <= 65535:
            raise ValueError('port must be an exact unsigned 16-bit integer')
    # Fixed educational header, no options and no connection-state semantics.
    header = struct.pack('!HHIIHHHH', source_port, destination_port,
                         1234, 5678, (5 << 12) | 0x18, 4096, 0, 0)
    segment = header + payload
    context = pseudoheader(source, destination, len(segment))
    value = checksum(context + segment)
    return segment[:16] + value.to_bytes(2, 'big') + segment[18:]


def accepted(source, destination, segment):
    """Checksum check only, with minimal length/offset guard for this lesson."""
    exact_bytes(segment)
    context = pseudoheader(source, destination, len(segment))
    if segment[12] >> 4 != 5:
        raise ValueError('only 20-byte TCP headers accepted by this lesson')
    return checksum(context + segment) == 0


def rejects(call):
    try:
        call()
    except (TypeError, ValueError):
        return True
    return False


def run_example():
    src, dst = '192.0.2.10', '198.51.100.20'
    # Channel identifier then three signed millivolt samples; exactly 7 bytes.
    payload = struct.pack('!Bhhh', 3, 1250, -500, 3300)
    segment = build_segment(src, dst, payload)
    context = pseudoheader(src, dst, len(segment))
    check_value = int.from_bytes(segment[16:18], 'big')
    zeroed = segment[:16] + b'\0\0' + segment[18:]
    checks = {}
    checks['empty_and_zero_representations'] = (
        checksum(b'') == 0xffff and checksum(b'\0\0') == 0xffff
        and checksum(b'\xff\xff') == 0)
    checks['end_around_carry_hand_calculation'] = checksum(bytes.fromhex('ffff0001')) == 0xfffe
    checks['rfc1071_section3_known_sum'] = checksum(bytes.fromhex('0001f203f4f5f6f7')) == 0x220d
    checks['odd_byte_hand_and_no_mutation'] = checksum(b'\x12\x34\x56') == 0x97cb and payload == bytes.fromhex('0304e2fe0c0ce4')
    checks['exact_context_bytes_and_length'] = context == bytes.fromhex('c000020ac63364140006001b') and len(context) == 12
    checks['header_and_payload_wire_length'] = len(segment) == 27 and segment[20:] == payload and segment[12] >> 4 == 5
    checks['independent_oracle_for_authored_segment'] = check_value == modular_oracle(context + zeroed)
    checks['verify_includes_stored_checksum'] = accepted(src, dst, segment) and modular_oracle(context + segment) == 0
    # All 216 individual bits of this fixed segment are flipped separately.
    corruptions = []
    for index in range(len(segment)):
        for bit in range(8):
            altered = bytearray(segment); altered[index] ^= 1 << bit
            try:
                rejected = not accepted(src, dst, bytes(altered))
            except ValueError:  # Data-offset guard also rejects some flips.
                rejected = True
            corruptions.append(rejected)
    checks['single_bit_corruptions_in_this_fixture'] = all(corruptions)
    checks['context_address_and_length_change'] = (
        not accepted(src, '198.51.100.21', segment)
        and not accepted(src, dst, segment + b'\0'))
    # Swap two complete payload words: a concrete unchanged-sum collision.
    collision_payload = bytes.fromhex('010203040506')
    collision_segment = build_segment(src, dst, collision_payload)
    rearranged = collision_segment[:20] + bytes.fromhex('030401020506')
    checks['distinct_payload_collision_is_visible'] = (
        rearranged != collision_segment and accepted(src, dst, rearranged))
    oracle_cases = 0
    oracle_ok = True
    for length in range(65):
        for seed in range(11):
            data = bytes((index * 73 + seed * 41 + length) % 256 for index in range(length))
            oracle_ok &= checksum(data) == modular_oracle(data)
            oracle_cases += 1
    checks['715_byte_weighted_modular_oracle_cases'] = bool(oracle_ok)
    largest = build_segment(src, dst, b'\0' * (MAX_TCP_BYTES - 20))
    checks['ordinary_ipv4_size_boundary'] = (
        len(largest) == MAX_TCP_BYTES and accepted(src, dst, largest)
        and rejects(lambda: build_segment(src, dst, b'\0' * (MAX_TCP_BYTES - 19))))
    checks['input_contract_and_limited_header_scope'] = all([
        rejects(lambda: checksum(bytearray(b'a'))),
        rejects(lambda: build_segment(src, dst, 'data')),
        rejects(lambda: build_segment(src, dst, b'', True)),
        rejects(lambda: build_segment(src, dst, b'', -1)),
        rejects(lambda: build_segment(src, dst, b'', 65536)),
        rejects(lambda: build_segment('::1', dst, b'')),
        rejects(lambda: build_segment(123, dst, b'')),
        rejects(lambda: pseudoheader(src, dst, True)),
        rejects(lambda: accepted(src, dst, b'\0' * 19)),
        rejects(lambda: accepted(src, dst, segment[:12] + b'\x60' + segment[13:])),
    ])
    assert all(type(v) is bool and v for v in checks.values()), checks
    return {
        'python_version': platform.python_version(), 'stdlib_only': True,
        'scope': 'offline checksum bytes only; no socket, IP packet or TCP stack',
        'sensor_payload_hex': payload.hex(), 'sample_unit': 'mV',
        'tcp_length_bytes': len(segment), 'pseudoheader_hex': context.hex(),
        'checksum_hex': f'{check_value:04x}', 'tcp_segment_hex': segment.hex(),
        'verification_residue_hex': f'{checksum(context + segment):04x}',
        'collision_original_payload_hex': collision_payload.hex(),
        'collision_changed_payload_hex': rearranged[20:].hex(),
        'collision_accepted': accepted(src, dst, rearranged),
        'independent_oracle_cases_within_one_group': oracle_cases,
        'single_bit_fixture_cases_within_one_group': len(corruptions),
        'checks': checks,
    }


if __name__ == '__main__':
    print(json.dumps(run_example(), indent=2, ensure_ascii=False))

ورودی محموله و تابع checksum باید دقیقاً bytes باشد؛ رشته و bytearray تبدیل ضمنی نمی‌شوند. درگاه‌ها int دقیقِ بدون علامت و شانزده‌بیتی‌اند؛ bool نیز رد می‌شود. نشانی باید رشتهٔ IPv4 باشد و IPv6 پذیرفته نیست. این‌ها تصمیم‌های رابط همین درس‌اند؛ دربارهٔ همهٔ انواع قابل‌قبولِ رابط‌های شبکه حکم عمومی نمی‌دهند.

سقف طول TCP را ۶۵۵۱۵ بایت گذاشته‌ایم، با فرض IPv4 معمول و سرآیند IP بیست‌بایتی. بنابراین بزرگ‌ترین محمولهٔ سازنده ۶۵۴۹۵ بایت است. این سقف، اندازهٔ توصیه‌شدهٔ بسته یا MTU مسیر نیست. گزینه‌های IP، قطعه‌قطعه‌سازی و بازسازی، و ساختار گزینه‌های TCP در مدل وجود ندارند. تابع accepted فقط طول و Data Offset همین قرارداد را محدود و checksum را بررسی می‌کند؛ نام آن به معنی پذیرش کامل پروتکلی نیست.

پس از محاسبه، دو بایت مقدار در جایگاه شانزده و هفدهٔ سرآیند قرار می‌گیرند. برای بررسی، آن فیلد را دوباره صفر نمی‌کنیم: کل زمینه و بخشِ دارای checksum باید خروجی صفر بدهد. در زبان جمع مکمل یک، جمع پیش از معکوس‌سازی تماماً یک شده است. این تفاوتِ مرحلهٔ ساخت و مرحلهٔ بررسی، جلوی یک آزمون رفت‌وبرگشتِ گمراه‌کننده را می‌گیرد.

۴. مرجع پیمانه‌ای، خرابی و شاهدِ برخورد

مرجع مستقل و محدودیتِ تشخیص خرابی
تصویر مفهومی: مرجع مستقل و محدودیتِ تشخیص خرابی؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

خروجی واقعی برای بخش اصلی، مقدار ff12 و طول بیست‌وهفت بایت است. صفر مفهومیِ انتهای محاسبه در رشتهٔ بخش ذخیره نشده است. تغییر مقصد به 198.51.100.21 با حفظ بقیهٔ داده‌ها، بررسی را ناموفق می‌کند. افزودن یک بایت صفر نیز طولِ واردشده در شبه‌سرآیند را عوض می‌کند و در همین نمونه رد می‌شود.

{
  "python_version": "3.14.5",
  "stdlib_only": true,
  "scope": "offline checksum bytes only; no socket, IP packet or TCP stack",
  "sensor_payload_hex": "0304e2fe0c0ce4",
  "sample_unit": "mV",
  "tcp_length_bytes": 27,
  "pseudoheader_hex": "c000020ac63364140006001b",
  "checksum_hex": "ff12",
  "tcp_segment_hex": "a0282328000004d20000162e50181000ff1200000304e2fe0c0ce4",
  "verification_residue_hex": "0000",
  "collision_original_payload_hex": "010203040506",
  "collision_changed_payload_hex": "030401020506",
  "collision_accepted": true,
  "independent_oracle_cases_within_one_group": 715,
  "single_bit_fixture_cases_within_one_group": 216,
  "checks": {
    "empty_and_zero_representations": true,
    "end_around_carry_hand_calculation": true,
    "rfc1071_section3_known_sum": true,
    "odd_byte_hand_and_no_mutation": true,
    "exact_context_bytes_and_length": true,
    "header_and_payload_wire_length": true,
    "independent_oracle_for_authored_segment": true,
    "verify_includes_stored_checksum": true,
    "single_bit_corruptions_in_this_fixture": true,
    "context_address_and_length_change": true,
    "distinct_payload_collision_is_visible": true,
    "715_byte_weighted_modular_oracle_cases": true,
    "ordinary_ipv4_size_boundary": true,
    "input_contract_and_limited_header_scope": true
  }
}

مرجع مستقل به جای حلقهٔ نقلی، هر بایت را با وزن ۲۵۶ یا یک وارد جمع صحیح می‌کند و با پیمانهٔ ۶۵۵۳۵ به جواب می‌رسد. جمع مثبت با باقیماندهٔ صفر باید به نمایش ffff برگردد؛ جمع واقعاً صفر نمایش دیگری دارد. این تمایز، مورد تمام‌صفر و تمام‌یک را پوشش می‌دهد. هفتصد و پانزده ورودیِ محدود، یک گروه مقایسه با این مرجع هستند.

بردار بیرونی بخش سوم RFC 1071، مستقل از محمولهٔ ما، checksum برابر 220d می‌دهد. جدا از آن، هر یک از ۲۱۶ بیت بخشِ تألیفی یک‌بار عوض می‌شود. همهٔ نسخه‌ها رد می‌شوند؛ بعضی تغییرهای Data Offset با محدودیت قالب هم برخورد می‌کنند. این مجموعه یک گروه آزمون است و نرخ خرابی یا احتمالِ تشخیص در شبکه را اندازه نمی‌گیرد. تعداد واقعی گروه‌های نام‌دار چهارده است.

یک شاهدِ محدودیت نیز عمداً می‌سازیم: محمولهٔ 010203040506 را به 030401020506 عوض می‌کنیم. دو واژهٔ کامل جابه‌جا شده‌اند و بررسی همچنان موفق است. داده‌ها متفاوت‌اند، ولی مجموعشان یکسان است. پس checksum رمزنگاری، امضای فرستنده یا تضمین عدم‌دستکاری نیست؛ همین شاهد برای رد استنتاجِ «بررسی موفق یعنی داده قطعاً همان دادهٔ اصلی است» کافی است.

درس مرزبندی پیام حسگر روی جریان TCP مرز پیامِ کاربرد را بررسی می‌کند؛ مسئلهٔ اینجا بایت‌های یک بخشِ آماده است. همچنین آزمون CRC بستهٔ حسگر قرارداد چندجمله‌ای و ترتیب FCS متفاوتی دارد. هیچ‌کدام جای اعتبار محتوای اندازه‌گیری، احراز هویت یا سیاست مقابله با بازپخش را نمی‌گیرند.

۵. چهار پرسش پاسخ‌دار و مرزِ اعتماد

تمامیت محاسباتی و مرزِ اعتماد
تصویر مفهومی: تمامیت محاسباتی و مرزِ اعتماد؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

پرسش اول: برای محمولهٔ هفت‌بایتی، طول TCP در شبه‌سرآیند چند است؟ پاسخ: با سرآیند ثابت بیست‌بایتی، بیست‌وهفت؛ دوازده بایت شبه‌سرآیند و صفر مفهومی شمرده نمی‌شوند. رشتهٔ بخش نیز بیست‌وهفت بایت می‌ماند.

پرسش دوم: هنگام بررسی، فیلد checksum را صفر کنیم؟ پاسخ: خیر؛ صفرکردن مربوط به ساخت مقدار است. بررسی حاضر مقدار ذخیره‌شده را وارد می‌کند و خروجی صفر می‌خواهد؛ همان نمونهٔ اصلی این شرط را دارد.

پرسش سوم: چرا عوض‌کردن نشانی مقصد اهمیت دارد؟ پاسخ: نشانی در شبه‌سرآیند وارد جمع می‌شود. در تغییرِ تک‌واحدیِ این مثال، بخش قدیمی دیگر بررسی موفق ندارد؛ این مشاهده، ادعای تشخیص همهٔ تغییرهای هم‌زمان چند فیلد نیست.

پرسش چهارم: موفقیت شاهد جابه‌جایی واژه‌ها چه می‌گوید؟ پاسخ: این مقدار کنترل برخورد دارد و اصالت ایجاد نمی‌کند. منابع در ۲۶ مهر ۱۴۰۵ خوانده شدند؛ تاریخ انتشار RFC 9293 اوت ۲۰۲۲ و RFC 1071 سپتامبر ۱۹۸۸ است. این نتیجه، آزمونِ محاسبهٔ آفلاین است و به انطباق کامل پشتهٔ شبکه تعمیم ندارد.

منابع و مطالعهٔ بیشتر

بررسی منابع: ۲۶ مهر ۱۴۰۵. تحلیل‌ها و پیشنهادهای اجرایی، جمع‌بندی نویسنده‌اند.