فهرست مقاله
متن فارسی ممکن است در فرستنده درست باشد، اما پس از چند بار دریافت به علامتهای جایگزین تبدیل شود. یکی از علتهای قابل بررسی این است که هر قطعهٔ بایت را یک متنِ مستقل فرض کردهایم. این درس، رمزگشاییِ مرحلهای UTF-8 را با ورودیِ ساختگی و کد کامل پایتون بررسی میکند. هیچ سوکت یا شبکهای اجرا نمیشود؛ ترتیبِ بایتها و نامِ رمزگذاری از پیش معلوماند. مسئله، نگهداشتنِ وضعیتِ نویسهٔ ناتمام است، نه پیدا کردنِ مرز پیام یا تشخیصِ خودکارِ رمزگذاری.
۱. بایتهای یک نویسه ممکن است در دو قطعه برسند

بخشِ مفاهیمِ اصلی TCP سرویسِ کاربرد را جریانِ بایتیِ مرتب توصیف میکند. برای خواندنِ متن، لایهٔ دیگری لازم است. در درسِ مرزبندیِ پیامِ حسگر، محموله بایتِ کدر بود؛ اینجا فقط بخشی از مسئلهٔ تبدیلِ بایت به متن را جدا میکنیم. قطعههای آزمون را بستهٔ واقعی TCP یا نمونهٔ ثبتشده از یک اتصال معرفی نمیکنیم.
نمونهٔ اصلی متنِ Aس🙂B است: چهار نقطهٔ کدِ یونیکد و هشت بایت UTF-8. حرفِ «س» با دو بایت و شکلک با چهار بایت نمایش داده میشود. برشِ ورودی میتواند میانِ این بایتها بیفتد. بنابراین نه تعدادِ بایت با طولِ رشته یکی است و نه اندازهٔ قطعه، کاملشدنِ یک نویسه را نشان میدهد. منظور از شمارِ چهار در این مثال، نقطهٔ کد است؛ شمارشِ شکلهای نوشتاریِ مرکب، مسئلهٔ جداگانهای است.
تعریفِ UTF-8 در RFC3629 طولِ یک تا چهار بایت و دامنهٔ مجاز را توضیح میدهد. برای نمونه، دنبالهٔ هگز d8 b3 همان حرفِ «س» است. بایتِ نخست بهتنهایی هنوز متنِ کامل نیست. اگر دریافتِ نخست را مستقلاً decode کنیم، حالتِ لازم برای تکمیلِ آن در دریافتِ بعدی را نخواهیم داشت.
۲. وضعیتِ رمزگشا و پایانِ صریحِ ورودی

از رابطِ IncrementalDecoder پایتون استفاده میکنیم. یک نمونه برای یک جریان ساخته میشود؛ هر feed همان نمونه را با final=False فراخوانی میکند. خروجی میتواند رشتهٔ خالی باشد، چون هنوز بایتِ کافی برای یک نقطهٔ کد نرسیده است. کلاسِ آموزشیِ ما با getstate، پسوندِ بایتیِ ناتمام را هم برای مشاهده در گزارش میگیرد؛ این مشاهده جای قراردادِ عمومیِ همهٔ رمزگذاریها را نمیگیرد.
قطعهٔ خالی در feed یعنی این بار بایتی افزوده نشده است. پایان را با finish جدا اعلام میکنیم؛ این متد decode را با ورودیِ خالی و final=True صدا میزند. در آن مرحله، پسوندِ ناتمام باید به سیاستِ خطا برسد. اگر این فراخوانی حذف شود، برنامه ممکن است دنبالهٔ ناقصِ آخر را فقط در انتظار نگه دارد و هرگز خطای آن را گزارش نکند. در اتصالِ واقعی، لایهٔ بالاتر باید رخدادِ پایان دریافت را به همین تصمیم وصل کند؛ معنای feed خالیِ ما، تعریفِ recv واقعی نیست.
در نمونه، خروجیِ پنج feed بهترتیب A، رشتهٔ خالی، «س»، رشتهٔ خالی و 🙂B است. حالتهای ناتمامِ هگز نیز d8، دوباره d8، سپس f09f، بعد f09f99 و در پایان خالیاند. finish موفق خروجیِ تازهای ندارد و نمونه را میبندد. رد کردنِ استفادهٔ دوباره پس از finish یا خطای UnicodeDecodeError، سیاستِ پوششِ آموزشیِ ماست؛ ادعا نمیکنیم خودِ کلاسهای codecs دقیقاً همین ماشینِ حالت را تحمیل میکنند.
۳. خطای سختگیرانه و مرزِ مسئولیتِ رمزگشایی

سیاستِ strict را صریح انتخاب کردهایم. دنبالهٔ بیشازحد بلند مانند c080، کدگذاریِ surrogate و مقدارِ بالاتر از U+10FFFF پذیرفته نمیشوند. مرجعِ بایتیِ مستقل نیز قواعدِ کاملِ بخشِ چهارم RFC3629 را برای همین شاهدهای محدود به کار میبرد. این آزمونها جای بررسیِ امنیتِ یک سرویس یا همهٔ مسیرهای ورودِ داده را نمیگیرند.
سیاستهای خطای codecs معنای replace و ignore را جدا مشخص میکنند. اگر d8 و b3 را جداگانه با replace بخوانیم، بهجای یک «س»، دو علامتِ جایگزین میگیریم. همچنین ff و fe، با وجود تفاوتِ بایتی، میتوانند به همان علامت برسند؛ ignore هر دو را حذف میکند. پس نبودِ استثنا در این حالتها، حفظِ متنِ اصلی را ثابت نمیکند.
رمزگذاریِ این درس plain utf-8 است. در شاهدِ آغازینِ efbbbf، مقدار U+FEFF حفظ میشود؛ هیچ انتخابِ utf-8-sig یا حذفِ امضا نداریم. NUL نیز از نظر رمزگشاییِ UTF-8 یک مقدارِ مجاز است، ولی کاربرد میتواند آن را جداگانه نپذیرد. همین جدایی برای یک حرفِ ترکیبی مهم است: decode، دنبالهٔ e و اکسنت را خودکار به یک نقطهٔ کدِ آماده تبدیل نمیکند و نرمالسازی یا بخشبندیِ گرافیم انجام نمیدهد.
اگر feed قبلی A را برگردانده و فراخوانیِ بعدی خطا کند، A پس گرفته نمیشود. اگر یک فراخوانی هم پیشوندِ معتبر و هم بایتِ خراب داشته باشد، استثنا میدهد و خروجیِ همان فراخوانی برنمیگردد. تابعِ دستهایِ decode_chunks نتیجه را فقط پس از موفقیتِ کل ورودی تحویل میدهد؛ این رفتار را نباید با بازگردانیِ عمومیِ اثراتِ مصرفکننده یکی گرفت.
۴. کدِ کامل و شاهدِ مستقل از قواعد UTF-8

فایلِ incremental_utf8_stream.py را ذخیره و با پایتون اجرا کنید. اجرای ثبتشده CPython 3.14.5 و فقط کتابخانهٔ استاندارد است؛ مستنداتِ خواندهشده نسخهٔ 3.14.8 هستند. تابعِ دستهای دقیقاً tuple با حداکثر سیودو قطعه و مجموعِ حداکثر ۲۵۶ بایت میپذیرد و پیش از ساختِ رمزگشا، شکلِ ورودی را بررسی میکند. این سقفها برای مثالاند و بودجهٔ حافظهٔ کلِ فرایند نیستند؛ هستهٔ feed چنین سقفِ تجمعی ندارد.
"""Known UTF-8 on supplied ordered chunks; no socket, framing or normalization."""
import codecs
import itertools
import json
import platform
class UTF8Stream:
"""Strict decoder with our own terminal-failure/one-finish wrapper policy."""
def __init__(self):
self._decoder = codecs.getincrementaldecoder('utf-8')('strict')
self.state = 'open'
def _open(self):
if self.state != 'open':
raise RuntimeError('Create a new stream after failure or finish')
def _decode(self, chunk, final):
try:
text = self._decoder.decode(chunk, final=final)
except UnicodeDecodeError:
self.state = 'failed'
self._decoder.reset()
raise
if final:
self.state = 'finished'
return text
def feed(self, chunk):
self._open()
if type(chunk) is not bytes:
raise TypeError('Expected exact bytes, including optional empty chunk')
return self._decode(chunk, final=False)
def finish(self):
self._open()
return self._decode(b'', final=True)
def pending_bytes(self):
return self._decoder.getstate()[0]
def decode_chunks(chunks):
"""Bounded batch helper; returns only after the entire stream is valid."""
if type(chunks) is not tuple:
raise TypeError('Expected exact tuple of chunks')
if len(chunks) > 32:
raise ValueError('At most32 chunks in this example')
if any(type(chunk) is not bytes for chunk in chunks):
raise TypeError('Each chunk must be exact bytes')
if sum(map(len, chunks)) > 256:
raise ValueError('At most256 input bytes in this example')
stream = UTF8Stream()
parts = [stream.feed(chunk) for chunk in chunks]
parts.append(stream.finish())
return ''.join(parts)
def scalar_oracle(data):
"""Independent whole-byte RFC3629 grammar; does not call any text codec."""
result, index = [], 0
while index < len(data):
first = data[index]
if first <= 0x7f:
count, low, high, value = 1, 0, 0, first
elif 0xc2 <= first <= 0xdf:
count, low, high, value = 2, 0x80, 0xbf, first & 0x1f
elif 0xe0 <= first <= 0xef:
count = 3
low = 0xa0 if first == 0xe0 else 0x80
high = 0x9f if first == 0xed else 0xbf
value = first & 0x0f
elif 0xf0 <= first <= 0xf4:
count = 4
low = 0x90 if first == 0xf0 else 0x80
high = 0x8f if first == 0xf4 else 0xbf
value = first & 0x07
else:
raise ValueError('Invalid leading byte')
if index + count > len(data):
raise ValueError('Incomplete scalar')
for offset in range(1, count):
octet = data[index + offset]
minimum, maximum = (low, high) if offset == 1 else (0x80, 0xbf)
if not minimum <= octet <= maximum:
raise ValueError('Invalid continuation or scalar range')
value = (value << 6) | (octet & 0x3f)
result.append(value)
index += count
return tuple(result)
def partitions(data):
if not data:
yield ()
return
for mask in range(1 << (len(data) - 1)):
cuts = [0] + [i for i in range(1, len(data)) if mask & (1 << (i - 1))] + [len(data)]
yield tuple(data[a:b] for a, b in zip(cuts, cuts[1:]))
def raises(expected, function, *args):
try:
function(*args)
except expected:
return True
return False
def run_example():
# Literal code points and wire bytes are specified independently of encode().
wire = bytes.fromhex('41d8b3f09f998242')
expected_points = (0x41, 0x633, 0x1f642, 0x42)
expected_text = 'A\u0633\U0001f642B'
chunks = (b'A\xd8', b'', b'\xb3\xf0\x9f', b'\x99', b'\x82B')
stream = UTF8Stream()
outputs, pending = [], []
for chunk in chunks:
outputs.append(stream.feed(chunk))
pending.append(stream.pending_bytes().hex())
final_output = stream.finish()
literal_boundaries = (
(0x0, '00'), (0x7f, '7f'), (0x80, 'c280'), (0x7ff, 'dfbf'),
(0x800, 'e0a080'), (0xd7ff, 'ed9fbf'), (0xe000, 'ee8080'),
(0xffff, 'efbfbf'), (0x10000, 'f0908080'), (0x10ffff, 'f48fbfbf'))
boundary_matches = []
for point, hex_bytes in literal_boundaries:
data = bytes.fromhex(hex_bytes)
decoded = decode_chunks(tuple(data[i:i+1] for i in range(len(data))))
boundary_matches.append(tuple(map(ord, decoded)) == scalar_oracle(data) == (point,))
partition_matches = []
for pieces in partitions(wire):
partition_matches.append(decode_chunks(pieces) == expected_text)
with_empty = (b'',) + tuple(part for piece in pieces for part in (piece, b''))
partition_matches.append(decode_chunks(with_empty) == expected_text)
malformed = ('80', 'ff', 'c080', 'e08080', 'eda080', 'f0808080',
'f4908080', 'f880808080', 'c241', 'e2ffac')
malformed_matches = []
for hex_bytes in malformed:
data = bytes.fromhex(hex_bytes)
for cut in range(len(data) + 1):
malformed_matches.append(raises(UnicodeDecodeError, decode_chunks,
(data[:cut], data[cut:])))
assert raises(ValueError, scalar_oracle, data)
truncated = []
for data in (b'\xd8', b'\xe2\x82', b'\xf0\x9f\x99'):
sample = UTF8Stream()
before = sample.feed(data)
waiting = sample.pending_bytes()
failed_at_end = raises(UnicodeDecodeError, sample.finish)
truncated.append(before == '' and waiting == data and failed_at_end
and sample.state == 'failed' and sample.pending_bytes() == b'')
tiny_matches = []
alphabet = (0x41, 0x80, 0xc2, 0xe0, 0xa0, 0xf0)
for length in range(5):
for word in itertools.product(alphabet, repeat=length):
data = bytes(word)
try:
oracle = scalar_oracle(data)
except ValueError:
tiny_matches.append(raises(UnicodeDecodeError, decode_chunks, (data,)))
else:
tiny_matches.append(tuple(map(ord, decode_chunks((data,)))) == oracle)
empty = UTF8Stream()
empty_reads = [empty.feed(b''), empty.feed(b'')]
empty_was_open = empty.state == 'open'
empty_finished = empty.finish()
invalid_type = UTF8Stream()
invalid_types = [raises(TypeError, invalid_type.feed, x)
for x in ('text', bytearray(b'A'), memoryview(b'A'), None)]
valid_after_type_error = invalid_type.feed(b'A')
invalid_type.finish()
prior = UTF8Stream()
delivered = prior.feed(b'A')
prior_failed = raises(UnicodeDecodeError, prior.feed, b'\xff')
mixed = UTF8Stream()
mixed_failed = raises(UnicodeDecodeError, mixed.feed, b'A\xff')
bad = UTF8Stream()
bad.feed(b'\xd8')
bad_failed = raises(UnicodeDecodeError, bad.feed, b'A')
bad_later_rejected = raises(RuntimeError, bad.feed, b'\xb3') and raises(RuntimeError, bad.finish)
original_tuple = (b'A', b'\xd8\xb3')
before_tuple = original_tuple
first_pass = decode_chunks(original_tuple)
second_pass = decode_chunks(original_tuple)
batch_invalids = [raises(TypeError, decode_chunks, x)
for x in ([b'A'], iter((b'A',)), b'A', None)]
batch_invalids += [raises((TypeError, ValueError), decode_chunks, x)
for x in ((b'A', 'bad'), tuple(b'' for _ in range(33)), (b'A' * 257,))]
replacement = b'\xd8'.decode('utf-8', errors='replace') + b'\xb3'.decode('utf-8', errors='replace')
loss_a, loss_b = b'\xff'.decode('utf-8', errors='replace'), b'\xfe'.decode('utf-8', errors='replace')
decomposed = decode_chunks((b'e\xcc', b'\x81'))
composed = decode_chunks((b'\xc3\xa9',))
checks = {
'hand_wire_scalar_sequence_and_step_outputs':
scalar_oracle(wire) == expected_points == tuple(map(ord, expected_text))
and outputs == ['A', '', '\u0633', '', '\U0001f642B']
and ''.join(outputs) + final_output == expected_text,
'hand_pending_suffix_and_explicit_finish_state':
pending == ['d8', 'd8', 'f09f', 'f09f99', '']
and final_output == '' and stream.state == 'finished',
'all_literal_scalar_range_boundaries_match_independent_hex': all(boundary_matches),
'all_byte_partitions_and_empty_insertions_preserve_known_text':
len(partition_matches) == 256 and all(partition_matches),
'malformed_sequences_reject_at_every_single_cut': all(malformed_matches),
'incomplete_suffix_only_rejects_when_final_end_is_declared': all(truncated),
'independent_small_alphabet_whole_byte_grammar_family':
len(tiny_matches) == 1555 and all(tiny_matches),
'empty_feed_is_not_end_and_empty_stream_finishes':
empty_reads == ['', ''] and empty_was_open and empty_finished == ''
and empty.state == 'finished' and decode_chunks(()) == '',
'finished_or_failed_wrapper_is_terminal_by_own_policy':
raises(RuntimeError, stream.finish) and raises(RuntimeError, stream.feed, b'A')
and bad_failed and bad_later_rejected and bad.pending_bytes() == b'',
'type_errors_leave_decoder_usable_without_consuming_text':
all(invalid_types) and valid_after_type_error == 'A',
'strict_chunk_error_has_no_same_call_return_or_previous_output_rollback':
delivered == 'A' and prior_failed and prior.state == 'failed'
and mixed_failed and mixed.state == 'failed',
'replacement_and_ignore_can_hide_distinct_invalid_bytes':
replacement == '\ufffd\ufffd' and replacement != '\u0633'
and loss_a == loss_b == '\ufffd'
and b'\xff'.decode('utf-8', errors='ignore') == b'\xfe'.decode('utf-8', errors='ignore') == '',
'plain_utf8_keeps_initial_and_embedded_feff_without_sniffing':
decode_chunks((b'\xef', b'\xbb\xbfA')) == '\ufeffA'
and decode_chunks((b'A\xef\xbb', b'\xbfB')) == 'A\ufeffB',
'nul_is_scalar_and_decode_does_not_normalize_combining_text':
decode_chunks((b'A\x00B',)) == 'A\x00B'
and tuple(map(ord, decomposed)) == (0x65, 0x301)
and tuple(map(ord, composed)) == (0xe9,) and decomposed != composed,
'bounded_batch_validation_accepts_exact_limits_and_rejects_wrong_profile':
all(batch_invalids) and decode_chunks((b'A' * 256,)) == 'A' * 256
and decode_chunks(tuple(b'' for _ in range(32))) == '',
'independent_stream_instances_repeatability_and_input_preservation':
first_pass == second_pass == 'A\u0633' and original_tuple == before_tuple
and decode_chunks((b'\xd8', b'\xb3')) == '\u0633'
and raises(UnicodeDecodeError, decode_chunks, (b'\xb3',)),
}
assert all(type(value) is bool and value for value in checks.values()), checks
return {
'runtime': {'python': platform.python_version(), 'dependencies': 'Python standard library only'},
'contract': {'encoding': 'known plain utf-8, strict', 'core': 'supplied exact bytes; no socket or framing',
'wrapper_failure': 'terminal after UnicodeDecodeError; reset pending bytes, create new wrapper',
'end': 'finish once explicitly; empty feed is not EOF',
'batch_bounds': {'exact_tuple_max_chunks': 32, 'total_input_bytes': 256},
'partial_output': 'previous feed results stay delivered; batch helper returns only on whole success'},
'main': {'wire_hex': wire.hex(), 'wire_bytes': len(wire),
'expected_codepoints_hex': [hex(x) for x in expected_points],
'chunk_hex': [part.hex() for part in chunks], 'feed_output': outputs,
'pending_hex_after_each_feed': pending, 'finish_output': final_output,
'joined_text': ''.join(outputs) + final_output, 'codepoint_count': len(expected_text)},
'reference_families': {'literal_scalar_boundaries': len(boundary_matches),
'partition_profiles_including_empty_insertions': len(partition_matches),
'malformed_single_cut_profiles': len(malformed_matches),
'small_alphabet_strings': len(tiny_matches),
'cases_are_not_separate_named_groups': True},
'loss_witness': {'separate_chunk_replace': replacement, 'whole_valid_character': '\u0633',
'two_distinct_invalid_bytes_replace_identically': loss_a == loss_b},
'meaningful_named_groups': len(checks), 'checks': checks,
'limits': ['No charset detection, UTF-8 signature stripping, normalization or grapheme segmentation.',
'No application message parsing, JSON validation, network execution or transport diagnosis.',
'Finite authored fixtures/oracle families do not prove production security or all Unicode behavior.',
'Core feed output and caller-held inputs are not covered by the bounded batch input limit.']}
if __name__ == '__main__':
print(json.dumps(run_example(), ensure_ascii=True, indent=2))
مرجعِ مستقل از decode استفاده نمیکند: بایتِ آغاز، بازهٔ بایتِ دوم و ادامهها را میخواند و شمارهٔ نقطهٔ کد را با عملیاتِ صحیح میسازد. جدولِ ده شاهدِ هگز، مرزهای طول و دو سویِ بازهٔ surrogate را جدا کنترل میکند. نمونههای بدساخت و پایانِ ناقص نیز فقط با همان پیادهسازیِ اصلی برای انتظار ساخته نشدهاند.
هشت بایتِ نمونه، ۱۲۸ تقسیم به قطعهٔ غیرخالی دارند؛ با نسخهٔ دارای قطعههای خالی، ۲۵۶ الگو بررسی میشوند. همهٔ آنها یک خانوادهٔ بررسی هستند. ۱۵۵۵ رشته از الفبای کوچکِ ششبایتی، خانوادهٔ دیگری برای مرجعِ دستورِ بایتیاند. گزارش شانزده گروهِ نامدار دارد؛ شمارِ حالتهای درونِ یک خانواده بهعنوان گروههای مستقل اضافه نشده است.
{
"runtime": {
"python": "3.14.5",
"dependencies": "Python standard library only"
},
"contract": {
"encoding": "known plain utf-8, strict",
"core": "supplied exact bytes; no socket or framing",
"wrapper_failure": "terminal after UnicodeDecodeError; reset pending bytes, create new wrapper",
"end": "finish once explicitly; empty feed is not EOF",
"batch_bounds": {
"exact_tuple_max_chunks": 32,
"total_input_bytes": 256
},
"partial_output": "previous feed results stay delivered; batch helper returns only on whole success"
},
"main": {
"wire_hex": "41d8b3f09f998242",
"wire_bytes": 8,
"expected_codepoints_hex": [
"0x41",
"0x633",
"0x1f642",
"0x42"
],
"chunk_hex": [
"41d8",
"",
"b3f09f",
"99",
"8242"
],
"feed_output": [
"A",
"",
"س",
"",
"🙂B"
],
"pending_hex_after_each_feed": [
"d8",
"d8",
"f09f",
"f09f99",
""
],
"finish_output": "",
"joined_text": "Aس🙂B",
"codepoint_count": 4
},
"reference_families": {
"literal_scalar_boundaries": 10,
"partition_profiles_including_empty_insertions": 256,
"malformed_single_cut_profiles": 38,
"small_alphabet_strings": 1555,
"cases_are_not_separate_named_groups": true
},
"loss_witness": {
"separate_chunk_replace": "��",
"whole_valid_character": "س",
"two_distinct_invalid_bytes_replace_identically": true
},
"meaningful_named_groups": 16,
"checks": {
"hand_wire_scalar_sequence_and_step_outputs": true,
"hand_pending_suffix_and_explicit_finish_state": true,
"all_literal_scalar_range_boundaries_match_independent_hex": true,
"all_byte_partitions_and_empty_insertions_preserve_known_text": true,
"malformed_sequences_reject_at_every_single_cut": true,
"incomplete_suffix_only_rejects_when_final_end_is_declared": true,
"independent_small_alphabet_whole_byte_grammar_family": true,
"empty_feed_is_not_end_and_empty_stream_finishes": true,
"finished_or_failed_wrapper_is_terminal_by_own_policy": true,
"type_errors_leave_decoder_usable_without_consuming_text": true,
"strict_chunk_error_has_no_same_call_return_or_previous_output_rollback": true,
"replacement_and_ignore_can_hide_distinct_invalid_bytes": true,
"plain_utf8_keeps_initial_and_embedded_feff_without_sniffing": true,
"nul_is_scalar_and_decode_does_not_normalize_combining_text": true,
"bounded_batch_validation_accepts_exact_limits_and_rejects_wrong_profile": true,
"independent_stream_instances_repeatability_and_input_preservation": true
},
"limits": [
"No charset detection, UTF-8 signature stripping, normalization or grapheme segmentation.",
"No application message parsing, JSON validation, network execution or transport diagnosis.",
"Finite authored fixtures/oracle families do not prove production security or all Unicode behavior.",
"Core feed output and caller-held inputs are not covered by the bounded batch input limit."
]
}
آزمونِ نوعِ ورودی، خطای برنامهنویسی را از خطای متن جدا میکند؛ پس از TypeError هنوز میتوان bytes درست فرستاد. پس از خطای متن، پوششِ ما وضعیت را پایانی و بافرِ ناتمام را پاک میکند. برای ادامه، نمونهٔ تازه لازم است؛ حدسزدنِ محلِ همگامسازی یا ترمیمِ خودکارِ متن در این درس پیاده نشده است. دادههای خروجی و ورودیِ نگهداشتهشده توسطِ فراخواننده همچنان حافظه مصرف میکنند.
۵. چهار پرسش دربارهٔ دریافتِ متن

پرسش اول: اگر f09f99 برسد و سپس ورودی پایان یابد، چه رخ میدهد؟ پاسخ: feed هنوز منتظرِ بایتِ چهارم میماند، اما finish با final=True خطای دنبالهٔ ناقص میدهد. حذفِ finish، همین بررسیِ پایان را حذف میکند؛ رشتهٔ خالیِ برگشتی شاهدِ کامل بودن نیست.
پرسش دوم: آیا replace راهِ حفظِ حرفِ شکسته است؟ پاسخ: خیر؛ در برشِ d8 و b3، دو جایگزین میتواند جای یک «س» را بگیرد. برای حفظِ جریانِ معتبر باید وضعیتِ یک رمزگشای مشترک باقی بماند. سیاستِ جایگزینی تصمیمی دربارهٔ ادامهٔ پردازش است، نه بازیابیِ اطلاعاتِ ازبینرفته.
پرسش سوم: آیا UTF-8 معتبر یعنی رکوردِ حسگر معتبر است؟ پاسخ: خیر؛ متن میتواند JSON بدساخت، واحدِ نامناسب یا نامِ تکراری داشته باشد. درسِ قراردادِ JSON حسگر از str رمزگشاییشده آغاز میکند و آن مسئولیت را جدا میسنجد. این کد هیچ محتوای مهندسی را از روی بایتها تأیید نمیکند.
پرسش چهارم: آیا decode امضای آغازین و شکلِ نوشتاری را اصلاح میکند؟ پاسخ: در انتخابِ plain utf-8 ما، U+FEFF حفظ میشود و نرمالسازی هم انجام نمیشود. حذفِ امضا، تغییرِ شکلِ یونیکد و یافتنِ مرزِ پیام باید با قراردادهای جدا طراحی شوند. منابع در ۲۸ مهر ۱۴۰۵ خوانده شدهاند؛ تاریخِ انتشارِ RFCها نوامبرِ ۲۰۰۳ و اوتِ ۲۰۲۲ است و این درس، خبرِ تازه یا آزمایشِ شبکه نیست.
منابع و مطالعهٔ بیشتر
- Python3.14؛ رمزگشای مرحلهای و سیاستِ خطا
- RFC3629؛ تعریف و دستورِ بایتهای UTF-8
- RFC9293؛ سرویسِ جریانِ بایتیِ مرتب
بررسی منابع: ۲۸ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

