9.3 - מסגור הודעות וריבוי בקשות בפרוטוקול - פתרון
פתרון שלמות המסגרת¶
פתרון ראשון - כל נקודת קטיעה¶
הקוד הבא יכול להחליף את הפונקציה הראשית בקובץ ההרצאה. הוא בודק את כל הקידומות ולא רק חיתוך נוח בין כותרת לגוף.
fn main() {
let wire = encode(1, 9, b"payload");
for end in 0..=wire.len() {
let mut decoder = Decoder::new();
for &byte in &wire[..end] { decoder.push(byte).unwrap(); }
let valid = end == 0 || end == wire.len();
assert_eq!(decoder.finish().is_ok(), valid, "prefix length {end}");
}
let mut decoder = Decoder::new();
let mut count = 0;
for byte in encode(3, 10, b"").into_iter().chain(encode(1, 11, b"x")) {
if decoder.push(byte).unwrap().is_some() { count += 1; }
}
assert_eq!(count, 2);
decoder.finish().unwrap();
}
הקידומת הריקה אינה מכילה מסגרת חלקית ולכן תקינה מבחינת המפענח. ייתכן שהפרוטוקול העסקי דורש לפחות בקשה אחת, אבל זו דרישה של שכבה אחרת. ההבחנה מונעת מהמפענח לערבב מבנה בתים עם מדיניות שימוש.
פתרון שני - דחייה שנשארת דחייה¶
הפונקציה הבאה בודקת שהשגיאה מתרחשת בזמן קריאת הכותרת ושאין הקצאת גוף. היא נמצאת באותו מודול ולכן יכולה לבדוק את שדה הבאפר לצורך הניסוי.
fn rejects_header(mut header: Vec<u8>, change: impl FnOnce(&mut [u8])) {
change(&mut header);
let mut decoder = Decoder::new();
let mut failed = false;
for byte in header {
if decoder.push(byte).is_err() { failed = true; break; }
}
assert!(failed);
assert_eq!(decoder.body.capacity(), 0);
assert_eq!(decoder.push(0), Err("decoder already failed"));
}
fn main() {
let header = encode(1, 1, b"");
rejects_header(header.clone(), |bytes| bytes[16..20].copy_from_slice(&u32::MAX.to_be_bytes()));
rejects_header(header.clone(), |bytes| bytes[4] = 2);
rejects_header(header.clone(), |bytes| bytes[5] = 99);
rejects_header(header, |bytes| bytes[6] = 1);
}
ההקצאה מתבצעת רק אחרי בדיקות הכותרת והאורך. מצב הכשל נשמר לפני החזרת השליטה למתקשר, ולכן קריאה נוספת אינה מנסה לכתוב אחרי סוף מערך הכותרת.
פתרון שלישי - ביטול בלי מחזור מיידי¶
התוכנית הבאה משתמשת במזהים שאינם ממוחזרים. במקרה של גלישה היא מסרבת ליצור בקשה נוספת, במקום לחזור לאפס.
use std::collections::BTreeMap;
fn main() {
let mut next = 1_u64;
let mut pending = BTreeMap::new();
let first = next;
next = next.checked_add(1).unwrap();
pending.insert(first, "first");
pending.remove(&first);
let second = next;
next = next.checked_add(1).unwrap();
pending.insert(second, "second");
assert!(pending.remove(&first).is_none());
assert_eq!(pending.remove(&second), Some("second"));
assert_eq!(next, 3);
assert!(u64::MAX.checked_add(1).is_none());
}
במערכת מלאה יש להבחין בין מזהה לא מוכר בגלל ביטול מתועד לבין מזהה שמעולם לא הוקצה. אפשר לשמור רשומות ביטול מוגבלות בזמן לפי חוזה הפרוטוקול, או להגדיר שכל תשובה למזהה ישן נזרקת בתנאים ידועים. במקרה של חיבור חדש נדרשת זהות חיבור חדשה או גבול אחר שמונע התאמת הודעות מחיבור קודם.