バイナリプロトコル設計では、まず受信側がバイト列から明確で有限かつ検証可能なメッセージを取得する方法を定義する必要があります。コンパクトなエンコーディングはその一部に過ぎず、フィールドの意味、重複処理、未知の値、長さの上限、状態遷移、およびエラー動作を規定することも必要です。テキストにも厳格なフレーミングが存在し得ますし、バイナリの方が特定のテキストよりも大きくなることもあり、形式だけで品質を推測することはできません。
レイヤー 明確にするべき内容 よくある混乱
伝送 TCP バイトストリーム、UDP データグラム、または既存のストリームプロトコル 1回の read を1メッセージとみなす
フレーミング メッセージの開始位置、完了タイミング、最大長 ペイロードスキーマのみがあり、メッセージ境界がない
フィールドエンコーディング タイプ、バイトオーダー、長さ、整数表現 メモリ構造と直接同一視する
セマンティクス 単位、範囲、デフォルト値、状態、エラー バイト列が解析できればリクエスト実行可能とみなす
進化 未知フィールド、能力、クリティカル拡張のルール 未知の内容を無条件に無視する
フレーミング:長さプレフィックスにも完全な契約が必要
TCP はアプリケーションの書き込み境界を保持しません。2回の send が1回の read で発生することがあり、1回の send が複数の read に分割されることもあります。UDP はデータグラム境界を保持しますが、それでもアプリケーションメッセージが完全かどうか、断片化や複数レコードを許可するかどうか、受信バッファによる切り捨てが発生しないかどうかを判断する必要があります。
方法 利点 補完すべきルール
固定長 境界が直接計算可能 バージョン変更、パディング、無駄
長さプレフィックス 任意のペイロードバイトに対応可能 長さにヘッダーが含まれるか、上限、オーバーフロー、切り捨て
区切り文字 ストリーミングテキストやレコードに適している エスケープ処理、read 跨ぎのマッチング、最長レコード
チャンク/終了フレーム 総長を事前に知る必要がない チャンク上限、終了状態、総量とタイムアウト
HTTP ヘッダーの空行はヘッダーの終了のみを示し、レスポンスボディ全体の長さを独立して決定するものではありません。SMTP DATA も補完的なドットエスケープに依存します。これらの局所的なメカニズムを、他の状態を必要としない汎用フレーミングとして扱うことはできません。
長さフィールドは信頼できるメモリ割り当て指示ではありません。受信側はまず固定ヘッダーと長さの上限を検証し、その後逐步的にデータを受信すべきです。長接続の場合、未完了メッセージの数、総バッファサイズ、組立時間、展開後のサイズを制限する必要があります。単一フレームを 1 MiB に制限しても、1万の接続がそれぞれ半分のフレームを保持し続けることを防げません。
導出可能な教育用プロトコル
以下に定義するのは、パーサーの境界を説明するためのみの小規模プロトコルであり、既存の標準ではありません。これは信頼性の高い順序付きバイトストリーム上で動作し、12 バイトの固定ヘッダーと TLV ボディを使用します。空のボディは許可され、すべてのマルチバイト整数はビッグエンディアンです。
教育用プロトコル:12 B 固定ヘッダー + 有界 TLV ボディ
Magic
2 B
Version
1 B
Flags
1 B
Body length
4 B
Request ID
4 B
Type 1 B
Length 2 B
Value:Length B
すべてのマルチバイト整数はビッグエンディアンを使用します。body length は固定ヘッダーを含みません。TLV length は value のみを計算します。
フィールド 定義
Magic 2バイト KB。フォーマットの識別用であり、認証ではない
Version 現在バージョン 1 のみを許可。それ以外は失敗
Flags 現在 0 である必要あり。未知のクリティカルなセマンティクスを黙って無視しないため
Body length uint32。単位はバイト。固定ヘッダーを除く。最大 4096
Request ID uint32。この接続内でのリクエストの関連付け用。本来的にビジネスの冪等キーではない
TLV uint8 type + uint16 length + value
TLV type の最上位ビットはクリティカルフィールドを示し、下位 7 ビットがフィールド ID です。この例では、既知の ID 1 は厳密な UTF-8 名、ID 2 は 4 バイトの uint32 数量です。既知の ID の重複は許可されません。他のオプションフィールドはスキップされ、未知のクリティカルフィールドは拒否されます。既知のフィールドの欠如は構文レベルでは許可されますが、ビジネス操作では必須として要求される場合があります。
この「クリティカルビット」は、この文書のプロトコルが能動的に定義したルールであり、すべての TLV フォーマットに共通するものではありません。読者は長さがわかればオプション値をスキップできることを理解できますが、それによってセマンティクスを理解できるわけではなく、破棄しても正しく転送できることが保証されるわけではありません。
増分パーサー:待機、失敗、正常な完了を分離する
増分パーサー:データ不足は待機、フォーマット不正は失敗
固定ヘッダーを待機
12 B 未満:保持
ヘッダーと長さの検証
不正な値を先に拒否し、その後ボディを待機
完全なボディを解析
1フレーム消費し、次のフレームへ
EOF:バッファが空の場合のみ正常終了。残りがあれば切り捨て
長さの上限、接続の総予算、組立タイムアウトがリソースを制限する。宣言された長さのまま無限に割り当ててはならない。
以下のパーサーはボディの長さを制限し、フレームごとに TLV を検証します。呼び出し元から提供されるチャンクを受け取り、上位層が有界な読み込みで供給することを要求し、接続レベルの予算とタイムアウトを管理します。この例は完全なサーバーではありません。
import struct
HEADER = struct . Struct ( ' >2sBBII' )
MAX_BODY = 4096
def parse_tlvs ( body ) :
pos , values = 0 , { }
while pos < len ( body ) :
if len ( body ) - pos < 3 :
raise ValueError ( ' truncated TLV header' )
kind , size = struct . unpack_from ( ' >BH' , body , pos )
pos += 3
if size > len ( body ) - pos :
raise ValueError ( ' TLV exceeds body' )
value = body [ pos : pos + size ]
pos += size
field_id = kind & 0x 7f
if field_id not in ( 1 , 2 ) :
if kind & 0x 80:
raise ValueError ( ' unknown critical field' )
continue
if field_id in values :
raise ValueError ( ' duplicate known field' )
if field_id == 1 :
values [ 1 ] = value . decode ( ' utf-8' , errors = ' strict' )
else :
if size != 4 :
raise ValueError ( ' quantity must be uint32' )
values [ 2 ] = struct . unpack ( ' >I' , value ) [ 0 ]
return values
class Decoder :
def __init__ ( self ) :
self . buffer = bytearray ( )
def feed ( self , chunk ) :
self . buffer . extend ( chunk )
messages = [ ]
while len ( self . buffer ) >= HEADER . size :
magic , version , flags , size , request_id = HEADER . unpack_from ( self . buffer )
if magic != b ' KB' or version != 1 or flags != 0 or size > MAX_BODY :
raise ValueError ( ' invalid header' )
end = HEADER . size + size
if len ( self . buffer ) < end :
break
fields = parse_tlvs ( bytes ( self . buffer [ HEADER . size : end ] ) )
del self . buffer [ : end ]
messages . append ( ( request_id , fields ) )
return messages
def eof ( self ) :
if self . buffer :
raise ValueError ( ' truncated frame' )
def tlv ( kind , value ) :
return struct . pack ( ' >BH' , kind , len ( value ) ) + value
def frame ( body , request_id = 7 ) :
return HEADER . pack ( b ' KB' , 1 , 0 , len ( body ) , request_id ) + body
body = tlv ( 1 , ' 温度' . encode ( ) ) + tlv ( 2 , struct . pack ( ' >I' , 300 ) )
wire = frame ( body )
assert len ( body ) == 16 and len ( wire ) == 28
expected = [ ( 7 , { 1 : ' 温度' , 2 : 300 } ) ]
for split in range ( len ( wire ) + 1 ) :
decoder = Decoder ( )
assert decoder . feed ( wire [ : split ] ) + decoder . feed ( wire [ split : ] ) == expected
decoder . eof ( )
assert Decoder ( ) . feed ( wire + wire ) == expected + expected
assert Decoder ( ) . feed ( frame ( body + tlv ( 3 , b ' optional' ) ) ) == expected
bad = [
frame ( body + tlv ( 0x 83, b ' critical' ) ) ,
frame ( body + tlv ( 1 , b ' duplicate' ) ) ,
frame ( tlv ( 1 , b ' \xff ' ) ) ,
frame ( tlv ( 2 , b ' \x01 ' ) ) ,
frame ( b ' \x01 \x00 \x04 x' ) ,
HEADER . pack ( b ' KB' , 1 , 0 , MAX_BODY + 1 , 7 ) ,
wire [ : - 1 ] ,
]
for candidate in bad :
decoder = Decoder ( )
try :
decoder . feed ( candidate )
decoder . eof ( )
except ValueError :
pass
else :
raise AssertionError ( ' invalid input accepted' )
print ( ' 28 B メッセージ:すべてのバイナリ境界、フレーム結合、オプションフィールド、7種類の拒否が通過' )
解析エラーが発生した後、この例では呼び出し元にストリームのデコードを中止するよう要求します。次の KB がフレームヘッダーではないかと推測し続けるのではなく、このバイト列がペイロード内にも合法に出現する可能性があるためです。ノイズからの再同期が本当に必要な場合は、同期、完全性、および回復ルールを別途設計する必要があります。
本番環境の実装では、バイト単位のバッファ移動のコスト、例外発生前に解析済みのメッセージの配信方法、メモリ所有権、および非同期キャンセルを考慮する必要があります。ゼロコピーでもライフサイクルの制約から逃れることはできません。小さなビューを保持していると、大きなバッファ全体が解放できなくなる可能性があります。
ヘッダーの半分を受け取った場合、待機すべきかエラーとするか?
11バイト目で EOF に切り替えると、待機は切り捨てになります。17バイト目で最初のフレームは完全に完成しており、2番目のフレームがまだ到着していなくても配信できます。さらに宣言された長さが上限を超えるの場合、12バイト目で拒否すべきです。4097バイト待ってエラーを確定させるべきではありません。
図を準備しています 再読み込み
ヘッダーの半分を受け取った場合、待機すべきかエラーとするか? ストリームが終了していない間はデータ不足で待機できるが、EOF 後は切り捨てとなる。長さが上限を超えている場合、ヘッダーが完成次第拒否する。巨大なボディを待ったり、事前に割り当てたりしてはならない。
{"id":"binary-incremental-framing","title":"ヘッダーの半分を受け取った場合、待機すべきかエラーとするか?","summary":"ストリームが終了していない間はデータ不足で待機できるが、EOF 後は切り捨てとなる。長さが上限を超えている場合、ヘッダーが完成次第拒否する。巨大なボディを待ったり、事前に割り当てたりしてはならない。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"mode\"><button data-mode=\"good\" data-i18n=\"good\"></button><button data-mode=\"oversize\" data-i18n=\"oversize\"></button></div><div class=\"control\"><label for=\"available\" data-i18n=\"available\"></label><output for=\"available\" id=\"available-value\"></output><input id=\"available\" type=\"range\" min=\"0\" max=\"29\" value=\"11\" step=\"1\"></div><label class=\"check\"><input type=\"checkbox\" id=\"eof\"><span data-i18n=\"eof\"></span></label><div id=\"bytes\" class=\"rangegrid\"></div><div class=\"metrics\"><div><span data-i18n=\"frames\"></span><strong id=\"frames\"></strong></div><div><span data-i18n=\"status\"></span><strong id=\"status\"></strong></div></div><p id=\"trace\" class=\"message\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.rangegrid{grid-template-columns:repeat(10,minmax(0,1fr))}.metrics strong{font-size:17px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let mode='good';function draw(){const data=new Uint8Array(29);data.set([75,66,1,0,0,0,0,5,0,0,0,7,1,0,2,76,105],0);data.set([75,66,1,0,0,0,0,0,0,0,0,8],17);if(mode==='oversize'){data[6]=16;data[7]=1}const n=+$('#available').value,eof=$('#eof').checked;let pos=0,frames=0,error='',trace=[];while(n-pos>=12){const len=new DataView(data.buffer).getUint32(pos+4);trace.push('at '+pos+': body '+len);if(len>4096){error='REJECT LENGTH';break}if(n-pos<12+len)break;pos+=12+len;frames++}const status=error||((n>pos)?(eof?'TRUNCATED':'WAIT'):eof?'EOF CLEAN':'BOUNDARY');set('available-value',n);cells('bytes',Array.from({length:30},(_,i)=>({text:i<29?data[i].toString(16).padStart(2,'0'):'·',cls:i>=n?'empty':i<pos?'on':'gold'})));set('frames',frames);set('status',status);set('trace',trace.join('\\n')+'\\nconsumed '+pos+'; buffered '+(n-pos));$('#status').dataset.snapshot=JSON.stringify({frames,pos,status});pressed(mode);}document.querySelectorAll('input').forEach(e=>e.oninput=draw);document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{mode=b.dataset.mode;draw()});draw();","audio":false,"strings":{"available":"到達バイト数","eof":"ここでストリームは終了 EOF","frames":"完全に解析されたフレーム数","good":"2つの有効なフレーム","heading":"ヘッダーの半分を受け取った場合、待機すべきかエラーとするか?","intro":"到達したバイト数を進め、EOF を個別に宣言します。2つの結合フレームの境界を確認します。","mode":"入力モード","oversize":"最初のフレームが 4097 B を宣言","reset":"リセット","scope":"本文の KB プロトコル 12 B ヘッダーを継承。最初のフレームのボディは有効な TLV name=Li で 5 B、2番目のフレームは空のボディで、合計 29 B。固定ヘッダー/フレーミングのみを検証。TLV セマンティクスは本文のパーサーによって検証される。","scopeLabel":"モデル範囲と規約","status":"解析ステータス"}}
整数エンコーディング:Protobuf varint と QUIC varint は異なる
エンコーディング 境界ルール 数値 300
uint32 ビッグエンディアン 固定 4 B。上位バイトが先 00 00 01 2c
uint32 リトルエンディアン 固定 4 B。下位バイトが先 2c 01 00 00
Protobuf 符号なし varint 1バイトあたり 7 ビットの値。最上位ビットが後続バイトを示す ac 02
QUIC 可変長整数 最初のバイトの上位 2 ビットが総長 1/2/4/8 B を決定する 41 2c
Protobuf varint は下位グループが先に現れ、uint64 で最大 10 バイトです。QUIC は長さマーカーの後にネットワークバイトオーダーでエンコーディングされ、最大 62 ビットです。QUIC の一般的な整数エンコーディングでは、最短表現でない某些の表現が許可されていますが、frame type などの特定のフィールドには最短エンコーディングの要件があり、「すべての varint が最短でなければならない」と一般化することはできません。QUIC §16
def uvarint ( n ) :
if not 0 <= n < 1 < < 64 :
raise ValueError ( ' not uint64' )
result = bytearray ( )
while n >= 128 :
result . append ( ( n & 127 ) | 128 )
n >>= 7
result . append ( n )
return bytes ( result )
def quic_integer ( n ) :
for length , bits , prefix in [ ( 1 , 6 , 0 ) , ( 2 , 14 , 1 ) , ( 4 , 30 , 2 ) , ( 8 , 62 , 3 ) ] :
if 0 <= n < 1 < < bits :
return ( n | ( prefix < < ( 8 * length - 2 ) ) ) . to_bytes ( length , ' big' )
raise ValueError ( ' not uint62' )
assert uvarint ( 300 ) . hex ( ) == ' ac02'
assert quic_integer ( 300 ) . hex ( ) == ' 412c'
assert len ( uvarint ( 127 ) ) == 1 and len ( uvarint ( 128 ) ) == 2
assert len ( quic_integer ( 63 ) ) == 1 and len ( quic_integer ( 64 ) ) == 2
assert len ( uvarint ( 2 * * 64 - 1 ) ) == 10
assert len ( quic_integer ( 2 * * 62 - 1 ) ) == 8
zigzag = lambda n : 2 * n if n >= 0 else - 2 * n - 1
assert [ zigzag ( n ) for n in [ 0 , - 1 , 1 , - 2 ] ] == [ 0 , 1 , 2 , 3 ]
assert uvarint ( zigzag ( - 2 ) ) == b ' \x03 '
print ( ' 2種類の varint、境界、ZigZag マッピングが通過' )
ZigZag は値のマッピングであり、その後 varint エンコーディングが行われます。Protobuf の sint32/sint64 はこれを使用します。int32/int64 の負数は別のルールを採用しており、負数は 10 バイトになる可能性があります。ワイヤータイプがどちらも 0 だからといって、互いに交換可能と宣言してはいけません。
同じ 300 なのに、なぜ 2 つの varint は全く異なるのか?
まず 63/64 付近で QUIC が 2 バイトに増える様子を観察し、次に 127/128 付近で Protobuf の変化を観察します。これらは同じ「最上位ビットが長さを示す」というルールではありません。300 に移動し、ac02 と 412c のビットグループ順序を照らし合わせます。
図を準備しています 再読み込み
同じ 300 なのに、なぜ 2 つの varint は全く異なるのか? エンコーディングのグループ順序と長さのシグナルが異なる。Protobuf は下位 7 ビットグループを先に送り、QUIC は総長を先に宣言し、ビッグエンディアンの数値順序を保持する。
{"id":"integer-wire-boundaries","title":"同じ 300 なのに、なぜ 2 つの varint は全く異なるのか?","summary":"エンコーディングのグループ順序と長さのシグナルが異なる。Protobuf は下位 7 ビットグループを先に送り、QUIC は総長を先に宣言し、ビッグエンディアンの数値順序を保持する。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"value\" data-i18n=\"value\"></label><output for=\"value\" id=\"value-value\"></output><input id=\"value\" type=\"range\" min=\"0\" max=\"65535\" value=\"300\" step=\"1\"></div><section class=\"panel\"><h3 data-i18n=\"wire\"></h3><div id=\"wire\"></div></section><p id=\"equation\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#wire .tr{grid-template-columns:1fr 1.6fr .5fr}.tr>*{min-height:4.8em;font-family:var(--viz-font-ui)}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function draw(){const n=+$('#value').value;let x=n,p=[];do{const low=x%128;x=Math.floor(x/128);p.push(low+(x?128:0))}while(x);const len=n<64?1:n<16384?2:4,q=Array.from({length:len},(_,i)=>Math.floor(n/256**(len-1-i))%256);q[0]|=({1:0,2:64,4:128})[len];const be=[0,0,n>>8,n&255],hex=b=>b.map(x=>x.toString(16).padStart(2,'0')).join(' ');set('value-value',n);table('wire',[['encoding','bytes','B'],['uint32 BE',hex(be),4],['Protobuf',hex(p),p.length],['QUIC',hex(q),q.length]]);set('equation','Protobuf thresholds: 128, 16384\\nQUIC thresholds: 64, 16384\\n'+n+' = '+(n%128)+' + 128×'+Math.floor(n/128));$('#wire').dataset.values=JSON.stringify({be,p,q});}$('#value').oninput=draw;draw();","audio":false,"strings":{"heading":"同じ 300 なのに、なぜ 2 つの varint は全く異なるのか?","intro":"整数を変更し、固定ビッグエンディアン、7ビット継続、QUIC 長さプレフィックスの実際のバイトを比較する。","reset":"リセット","scope":"符号なし 0…65535。QUIC は最短エンコーディングを使用。Protobuf フィールドタグは含まない。完全な uint64 と QUIC 62 ビットの境界は本文を参照。","scopeLabel":"モデル範囲と規約","value":"エンコード対象の整数","wire":"実際のバイト(16進数)"}}
Protobuf:wire type が境界を与え、スキーマがセマンティクスを与える
syntax = "proto3";
message Person {
string name = 1;
uint32 age = 2;
}
name="Li"、age=300 のエンコーディングの 1 つは 0a 02 4c 69 10 ac 02 です。フィールド 1 のタグは (1 << 3) | 2 = 0a で、その後長さが 2、UTF-8 が続きます。フィールド 2 のタグは 10 で、その後 varint ac 02 が続きます。フィールド名はワイヤー上に現れず、年齢の単位や制約もタグ自体によって解釈されません。
Wire type 境界 考えられるセマンティクス
0 varint 終了ビット int、uint、sint、bool、enum
1 8 B fixed64、double など
2 長さプレフィックス string、bytes、ネストされたメッセージ、packed repeated
5 4 B fixed32、float など
3 / 4 古い group の開始/終了 廃止された group 表現
したがって、Protobuf はすべてのフィールドが独立した長さを持つ統一された 3 部構成の TLV を明示的に携带しているわけではありません。fixed32/fixed64 などはリトルエンディアンを使用し、TCP/IP ヘッダーで一般的なビッグエンディアンとは異なります。可変長整数は、独自のビットグループルールに従って理解する必要があります。Protobuf Encoding
未知フィールドの保持、フィールドの存在、および JSON への変換動作は、具体的なランタイムとインターフェースに依存します。バイナリメッセージをオブジェクトとして読み取り、既知のフィールドのみを手動でコピーすると、本来保持可能だった未知の内容が失われる可能性があります。ワイヤー互換性があるからといって、すべての中間処理が无损であるわけではありません。Proto3 未知フィールド
バイトオーダー、正規化、完全性はそれぞれが決定する
新しいプロトコルでは、フィールドの幅、符号、バイトオーダー、浮動小数点表現、テキストエンコーディング、単位を明文で規定すべきであり、「CPU のネイティブ形式を選べばよい」というわけではありません。C struct をそのまま送信すると、パディング、アラインメント、ABI の差異が持ち込まれます。CPU が未アラインアクセスを許可していても、それが相手の言語やハードウェアでも同様に許可されるわけではありません。
署名、ハッシュ、キャッシュキーについては、生バイト列にバインドするのか、正規化されたセマンティクスにバインドするのかを定義する必要があります。2つのオブジェクトが等価であっても、それらのシリアライズされたバイト列が一意であるとは限りません。一意な表現が必要な場合は、フィールドの順序、重複キー、浮動小数点 NaN、数字の最短エンコーディングなどのルールを明確にする必要があります。CBOR の決定論的エンコーディングは、これらの要件を明示的に書き出した例であり、「すべての CBOR が本来的に正規化されている」わけではありません。CBOR 決定論的エンコーディング
CRC やチェックサムは、適用可能な故障モデルの下での予期せぬ損傷を検出するために主に使用され、メッセージが信頼できる相手から来たことを証明するものではありません。認証には MAC、AEAD、または署名と鍵の信頼が必要です。圧縮についても展開後の上限を規定する必要があり、元のフレームが小さくても、展開後のリソースオーバーヘッドが小さいとは限りません。
デザインが完了したかどうかの基準は、失敗入力に対しても確定的な動作があるかどうか
入力または状態 規定すべき結果
ヘッダーの半分、ボディの半分 待機。EOF またはタイムアウト後に明確な失敗
上限を超える長さの宣言、整数オーバーフロー 割り当てまたは加算のオーバーフロー前に拒否
未知のオプション/クリティカルフィールド 明確なルールに従ってスキップまたは失敗
重複フィールド 拒否、マージ、または優先ルールは一意でなければならない
不正な UTF-8、深いネスト 受け入れ範囲と予算を明確にする
現在の状態では許可されないメッセージ プロトコルエラーまたは規定された回復
同じビジネスリクエストの再試行 ビジネスの冪等契約によって効果が決定され、フレーミングによって保証されるものではない
切り捨て、結合、境界長、未知のクリティカルフィールド、重複値、およびクロス言語ベクトルを優先してテストし、その後ファジングテストとリソース予算テストを行います。フォールトトレランスは、明文で定義された相互運用動作であり、各実装が異なる入力を推測して受け入れるものではありません。Maintaining Robust Protocols