From 25ed2813d57dea2c396e517dcf197474ae59278b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 11:20:38 +0000 Subject: [PATCH 01/35] Add plan for refactoring byte parsing onto seq[byte] views Describes how to replace slice+permission-based ghost parsing functions (Timestamp, BytesToIO_HF, CurrSeg, absPkt, ...) with permission-free pure functions over seq[byte], obtained from a new opaque View function in the slices package. Includes the strengthened slice-lemma specs, the list of offset-equality/widening lemmas that become redundant, the new lemmas required (view extensionality, write-effect), and a bottom-up migration order. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- .../byte-slice-parsing-refactor.md | 428 ++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 doc/verification/byte-slice-parsing-refactor.md diff --git a/doc/verification/byte-slice-parsing-refactor.md b/doc/verification/byte-slice-parsing-refactor.md new file mode 100644 index 000000000..76895100d --- /dev/null +++ b/doc/verification/byte-slice-parsing-refactor.md @@ -0,0 +1,428 @@ +# Plan: parsing packet bytes through `seq[byte]` views instead of slices + permissions + +## Problem + +Ghost pure functions that parse packet fields currently take a `[]byte` plus offsets and +require `sl.Bytes(raw, 0, len(raw))` (or elementwise `acc(&raw[i])`) in their preconditions. +For example, `Timestamp` in `pkg/slayers/path/infofield_spec.gobra`: + +```gobra +ghost +requires 0 <= currINF && 0 <= headerOffset +requires InfoFieldOffset(currINF, headerOffset) + InfoLen < len(raw) +requires sl.Bytes(raw, 0, len(raw)) +decreases +pure func Timestamp(raw []byte, currINF int, headerOffset int) io.Ainfo +``` + +Because a pure function cannot perform ghost operations (`SplitRange_Bytes`, +`Reslice_Bytes`, ...), a caller holding `sl.Bytes(raw, 0, len(raw))` cannot manufacture the +predicate instance for a sub-range inside a pure context. The consequence is that *every* +function in the parsing chain must take the entire buffer plus offsets, and every +relationship between "parsed from the full buffer" and "parsed from a subslice" needs a +bespoke lemma (`BytesToAbsInfoFieldOffsetEq`, `WidenBytesHopField`, `WidenCurrSeg`, +`absIO_valWidenLemma`, `ValidPktMetaHdrSublice`, `IsSupportedPktSubslice`, +`AbsPktToSubSliceAbsPkt`, ...). These lemmas are pure permission plumbing: they unfold two +predicate instances, re-prove pointer overlap with `sl.AssertSliceOverlap`, and re-fold. + +The fix: separate *heap access* from *parsing*. Heap access is concentrated in a single +heap-dependent, opaque function (`View`) that abstracts a `sl.Bytes` range into a +mathematical `seq[byte]`. All parsing functions become permission-free pure functions over +`seq[byte]`, taking exactly the bytes of the field they parse. Sub-ranging then happens at +the sequence level (`v[a:b]`), where it is definitional, instead of at the permission level, +where it requires ghost statements. + +The codebase already anticipates this in places: +- `seqs.ToSeqByte(ub []byte) seq[byte]` (`verification/utils/seqs/seqs.gobra:48`) is an + abstract view function, characterized elementwise via `sl.GetByte`. +- `slayers.IsSupportedRawPkt(raw seq[byte])` (`pkg/slayers/scion_spec.gobra`) is a parsing + function already written over `seq[byte]`, used through `SerializeBuffer.View()`. +- `raw_spec.gobra:378` carries a `// TODO: rename this to View()` on `absPkt`. +- `binary.BigEndian.Uint16Spec/Uint32Spec` are already permission-free byte-level + functions, so no changes to the `encoding/binary` stubs are needed for reading. + +## Step 1 — `View` in `verification/utils/slices` + +Add to `verification/utils/slices/slices.gobra`: + +```gobra +ghost +opaque +requires acc(Bytes(s, start, end), _) +ensures len(res) == end - start +ensures forall i int :: { res[i] } 0 <= i && i < end - start ==> + res[i] == GetByte(s, start, end, start + i) +decreases +pure func View(s []byte, start int, end int) (res seq[byte]) { + return unfolding acc(Bytes(s, start, end), _) in ViewAux(s, start, end) +} + +// Helper over raw quantified permissions (same style as BytesToAbsInfoFieldHelper), +// so that the recursion does not need per-range predicate instances. +ghost +requires 0 <= i && i <= end +requires forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], _) +ensures len(res) == end - i +ensures forall k int :: { res[k] } 0 <= k && k < end - i ==> res[k] == s[i + k] +decreases end - i +pure func ViewAux(s []byte, i int, end int) (res seq[byte]) { + return i == end ? seq[byte]{} : seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) +} +``` + +Notes: +- The postconditions of an `opaque` function remain visible, so the elementwise + characterization (`View(s, start, end)[i] == GetByte(s, start, end, start+i)`) is + available everywhere without `reveal`. In practice callers should almost never need + `reveal View(...)`. +- The wildcard permission amount (`acc(Bytes(...), _)`) lets the function be applied under + any fraction (`R55`, etc.) without threading a `perm` argument. This matches + `seqs.ToSeqByte`. +- `View` is deliberately indexed by the *predicate instance* `Bytes(s, start, end)`. A + caller holding `Bytes(s, 0, len(s))` writes `View(s, 0, len(s))[a:b]` for a sub-range — + a pure sequence operation — instead of splitting predicates. This is the key move that + eliminates the ghost-operation-in-pure-context problem. +- Framing is by the heap: as long as the caller's fraction of `Bytes(s, start, end)` is + preserved (not unfolded and modified), `View(s, start, end)` is automatically known to + be unchanged. +- If the recursive body turns out to be brittle in verification, fall back to declaring + `View` abstract (bodyless) with the same postconditions — this is exactly the (already + trusted) `seqs.ToSeqByte` pattern, generalized with `start`/`end`. In either variant, + `seqs.ToSeqByte(ub)` becomes a deprecated alias for `View(ub, 0, len(ub))` and should be + removed at the end of the migration (its only current use is + `gopacket.SerializeBuffer.View()` in + `verification/dependencies/github.com/google/gopacket/writer.gobra`, which should be + re-specified in terms of `sl.View`). + +Additionally, add one extensionality lemma that converts elementwise knowledge into +sequence equality (needed after buffer writes, see Step 4): + +```gobra +ghost +requires acc(Bytes(s, start, end), _) +requires len(other) == end - start +requires forall i int :: { other[i] } 0 <= i && i < end - start ==> + other[i] == GetByte(s, start, end, start + i) +ensures View(s, start, end) == other +decreases +func ViewEqFromElements(s []byte, start int, end int, other seq[byte]) +``` + +## Step 2 — strengthen the specs of the `slices` package + +Every ghost operation that transfers permissions between predicate instances must now also +state what happens to the views, so that clients never lose contents information when they +split/combine/reslice. Concretely, extend the postconditions: + +```gobra +// SplitByIndex_Bytes gains: +ensures View(s, start, idx) == old(View(s, start, end))[:idx - start] +ensures View(s, idx, end) == old(View(s, start, end))[idx - start:] + +// CombineAtIndex_Bytes gains: +ensures View(s, start, end) == old(View(s, start, idx)) ++ old(View(s, idx, end)) + +// Reslice_Bytes gains: +ensures View(s[start:end], 0, end - start) == old(View(s, start, end)) + +// Unslice_Bytes gains: +ensures View(s, start, end) == old(View(s[start:end], 0, end - start)) + +// SplitRange_Bytes gains: +ensures View(s[start:end], 0, end - start) == old(View(s, 0, len(s)))[start:end] +ensures View(s, 0, start) == old(View(s, 0, len(s)))[:start] +ensures View(s, end, len(s)) == old(View(s, 0, len(s)))[end:] + +// CombineRange_Bytes gains: +ensures View(s, 0, len(s)) == + old(View(s, 0, start)) ++ old(View(s[start:end], 0, end - start)) ++ old(View(s, end, len(s))) +``` + +All of these follow from the elementwise postcondition of `View` plus the pointer-identity +facts the lemma bodies already establish (`&s[start:end][i] == &s[start+i]`); where the +prover needs help, close with `ViewEqFromElements`. + +With these in place, the generic fact that today is re-proved once per parsing function +(every `*OffsetEq` / `Widen*` / `*Subslice` lemma) is available once and for all: +**the view of a subslice is the subsequence of the view.** + +## Step 3 — refactor the parsing functions to take `seq[byte]` + +General shape: each parser takes exactly the bytes of the thing it parses, with a length +precondition instead of offset arithmetic and permissions. Offsets survive only in the +*callers*, as sequence slicing. + +### 3a. Leaf parsers — `pkg/slayers/path` + +`infofield_spec.gobra` (`InfoLen == 8`): + +```gobra +ghost +requires len(raw) == InfoLen +decreases +pure func ConsDir(raw seq[byte]) bool { return raw[0] & 0x1 == 0x1 } + +ghost +requires len(raw) == InfoLen +decreases +pure func Peer(raw seq[byte]) bool { return raw[0] & 0x2 == 0x2 } + +ghost +requires len(raw) == InfoLen +decreases +pure func Timestamp(raw seq[byte]) io.Ainfo { + return io.Ainfo{uint(binary.BigEndian.Uint32Spec(raw[4], raw[5], raw[6], raw[7]))} +} + +ghost +requires len(raw) == InfoLen +decreases +pure func AbsUinfo(raw seq[byte]) set[io.MsgTerm] { + return AbsUInfoFromUint16(binary.BigEndian.Uint16Spec(raw[2], raw[3])) +} + +ghost +opaque +requires len(raw) == InfoLen +decreases +pure func BytesToAbsInfoField(raw seq[byte]) io.AbsInfoField { + return io.AbsInfoField { + AInfo: Timestamp(raw), + UInfo: AbsUinfo(raw), + ConsDir: ConsDir(raw), + Peer: Peer(raw), + } +} +``` + +Note the switch from `binary.BigEndian.Uint32` (slice + permissions) to +`binary.BigEndian.Uint32Spec` (byte values): the `unfolding`, the `AssertSliceOverlap` +calls, and `BytesToAbsInfoFieldHelper` all disappear. `InfoFieldOffset` stays — callers +still use it to *select* the sub-sequence. + +`hopfield_spec.gobra` (`HopLen == 12`, `MacLen == 6`): + +```gobra +ghost +requires len(raw) == HopLen +decreases +pure func BytesToIO_HF(raw seq[byte]) io.HF { + return let inif2 := binary.BigEndian.Uint16Spec(raw[2], raw[3]) in + let egif2 := binary.BigEndian.Uint16Spec(raw[4], raw[5]) in + io.HF { + InIF2: ifsToIO_ifs(inif2), + EgIF2: ifsToIO_ifs(egif2), + HVF: AbsMac(FromSeqToMacArray(raw[6:6+MacLen])), + } +} +``` + +with a new permission-free companion to `FromSliceToMacArray` in `io_msgterm_spec.gobra`: + +```gobra +ghost +requires len(mac) == MacLen +ensures forall i int :: { res[i] } 0 <= i && i < MacLen ==> mac[i] == res[i] +decreases +pure func FromSeqToMacArray(mac seq[byte]) (res [MacLen]byte) { + return [MacLen]byte{ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] } +} +``` + +### 3b. Mid-level parsers — `pkg/slayers/path/scion/raw_spec.gobra` + +These functions parse *several* fields, so they take the view of the region they cover +(for the top-level ones: the whole packet) and slice it. All permissions vanish; offset +parameters survive only where they select sub-sequences. + +```gobra +ghost +requires 0 <= currHfIdx && currHfIdx <= segLen +requires len(hfBytes) == segLen * path.HopLen +ensures len(res) == segLen - currHfIdx +decreases segLen - currHfIdx +pure func hopFields(hfBytes seq[byte], currHfIdx int, segLen int) (res seq[io.HF]) { + return currHfIdx == segLen ? seq[io.HF]{} : + seq[io.HF]{path.BytesToIO_HF(hfBytes[currHfIdx*path.HopLen:(currHfIdx+1)*path.HopLen])} ++ + hopFields(hfBytes, currHfIdx + 1, segLen) +} +``` + +`segment` takes `hfBytes seq[byte]` the same way. `CurrSeg` merges with the +`CurrSegWithInfo` family from `info_hop_setter_lemmas.gobra`: since the info field is now +parsed independently of permissions, there is no reason to keep two variants ("parse info +from raw" vs. "info passed as value"): + +```gobra +ghost +opaque +requires 0 < segLen +requires 0 <= currHfIdx && currHfIdx <= segLen +requires len(infoBytes) == path.InfoLen +requires len(hfBytes) == segLen * path.HopLen +decreases +pure func CurrSeg(infoBytes seq[byte], hfBytes seq[byte], currHfIdx int, segLen int) io.Seg { + return segment(hfBytes, currHfIdx, + path.Timestamp(infoBytes), path.AbsUinfo(infoBytes), + path.ConsDir(infoBytes), path.Peer(infoBytes), segLen) +} +``` + +(If keeping the packet-relative signature is preferred for fewer call-site changes, +`CurrSeg(raw seq[byte], offset, currInfIdx, currHfIdx, segLen, headerOffset)` with +`raw[...]` slicing inside is also permission-free; the field-exact variant above is the +end state the refactor aims for, and `CurrSegWithInfo`'s existence shows it is the shape +proofs actually want.) + +`LeftSeg` / `RightSeg` / `MidSeg` / `absPkt` / `RawBytesToMetaHdr` / `RawBytesToBase` / +`validPktMetaHdr` take `raw seq[byte]` (the whole packet view) and compute the +`infoBytes`/`hfBytes` arguments by pure slicing with the existing offset functions +(`path.InfoFieldOffset`, `HopFieldOffset`). E.g.: + +```gobra +ghost +requires MetaLen <= len(raw) +decreases +pure func RawBytesToMetaHdr(raw seq[byte]) MetaHdr { + return DecodedFrom(binary.BigEndian.Uint32Spec(raw[0], raw[1], raw[2], raw[3])) +} +``` + +Method specs keep the buffer argument for the *predicate*, and pass views to the abstract +functions: `(s *Raw) absPkt(ub []byte)` becomes `(s *Raw) absPkt(raw seq[byte])` and call +sites use `s.absPkt(sl.View(ub, 0, len(ub)))`. Where an `ub` and its prefix `ub[:length]` +both appear today, both are now expressed from one view (`V := sl.View(ub, 0, len(ub))`; +prefix is `V[:length]`) — this is what kills the widening lemmas. + +The `CorrectlyDecodedInf/Hf(WithIdx)` family keeps its `(ub []byte)` receiver-style +signature at the boundary or moves to `seq[byte]`; either way its body compares against +`BytesToAbsInfoField(V[infOffset:infOffset+path.InfoLen])` with `V := sl.View(ub, 0, len(ub))`. + +### 3c. Header-level parsers — `pkg/slayers/scion_spec.gobra` and `router/io-spec.gobra` + +- `ValidPktMetaHdr(ub []byte)`, `IsSupportedPkt(ub []byte)`, `GetAddressOffset*`, + `GetLength*`, `GetPathType`, `GetNextHdr` → take `raw seq[byte]`. `IsSupportedPkt` is + simply deleted in favor of the already existing `IsSupportedRawPkt(raw seq[byte])` + (rename it to `IsSupportedPkt` once the slice version is gone). +- `router/io-spec.gobra`: `absPkt(raw seq[byte]) io.Pkt`, + `absIO_val(raw seq[byte], ingressID uint16) io.Val`, + `absValUnsupported` loses its `sl.Bytes` precondition entirely. + `MsgToAbsVal` remains heap-dependent (it looks inside `msg.Mem()`) and becomes the/an + explicit boundary: it extracts the buffer, applies `sl.View`, and calls `absIO_val`. +- Loop invariants and contracts in `router/dataplane.go` that currently say + `absIO_val(rawPkt, ingressID)` become `absIO_val(sl.View(rawPkt, 0, len(rawPkt)), ingressID)`. + This is the bulk of the (mechanical) call-site churn: ~150 references in + `router/dataplane.go`, plus `pkg/slayers/path/scion/raw.go`, `decoded.go`, `scion.go`. + +## Step 4 — lemma cleanup: deletions and additions + +### Deleted (made redundant by seq-level sub-ranging) + +| Lemma | File | +|---|---| +| `BytesToAbsInfoFieldOffsetEq`, `BytesToAbsInfoFieldHelper` | `pkg/slayers/path/infofield_spec.gobra` | +| `WidenBytesHopField`, `BytesToAbsHopFieldOffsetEq` | `pkg/slayers/path/hopfield_spec.gobra` | +| `WidenCurrSeg`, `WidenLeftSeg`, `WidenRightSeg`, `WidenMidSeg` | `pkg/slayers/path/scion/widen-lemma.gobra` (whole file) | +| `CurrSegEquality`, `LeftSegEquality(Spec)`, `RightSegEquality(Spec)`, `MidSegEquality(Spec)`, and the `CurrSegWithInfo`/`LeftSegWithInfo`/`RightSegWithInfo`/`MidSegWithInfo` duplicates | `pkg/slayers/path/scion/info_hop_setter_lemmas.gobra` (family merges into `CurrSeg` et al.) | +| `ValidPktMetaHdrSublice` | `pkg/slayers/path/scion/raw_spec.gobra` | +| `IsSupportedPktSubslice`, `ValidHeaderOffsetToSubSliceLemma`, `ValidHeaderOffsetFromSubSliceLemma` | `pkg/slayers/scion_spec.gobra` | +| `absIO_valWidenLemma`, `ValidPktMetaHdrWidenLemma`, `IsSupportedPktWidenLemma`, `absPktWidenLemma` | `router/widen-lemma.gobra` (whole file) | +| `AbsPktToSubSliceAbsPkt`, `SubSliceAbsPktToAbsPkt` | `router/io-spec-lemmas.gobra` | + +Where an opaque function is involved (e.g. `absPkt` on `V` vs. `V[:length]`), a residual +lemma may still be wanted so that call sites don't need `reveal`; such lemmas shrink to a +`reveal` + sequence reasoning with **no** permission manipulation, no `unfold`/`fold`, and +no `AssertSliceOverlap`. Expectation: `router/widen-lemma.gobra` either disappears or +becomes a ~20-line file. + +`sl.AssertSliceOverlap` keeps its remaining uses in executable-code proofs (where real +subslices are taken), but disappears from all pure parsing definitions. + +### Added + +1. `View`, `ViewAux`, `ViewEqFromElements` in `verification/utils/slices` (Step 1). +2. The strengthened postconditions of Step 2 (same package). +3. **Write-effect lemmas**: today, setter proofs (`SetInfoField`, `SetHopField`, + `info_hop_setter_lemmas.gobra`) argue field-by-field which parsing functions survive a + buffer write. With views, this becomes one generic pattern: after code unfolds + `Bytes(ub, 0, len(ub))`, writes bytes `[a, b)`, and refolds, + + ```gobra + sl.View(ub, 0, len(ub)) == + old(sl.View(ub, 0, len(ub)))[:a] ++ written ++ old(sl.View(ub, 0, len(ub)))[b:] + ``` + + which follows from `ViewEqFromElements`. To make this convenient, specify the ghost + update boundary once, e.g. as a lemma in the `slices` package: + + ```gobra + ghost + requires ... // Bytes held, old elementwise facts for [0,a) and [b,len), new bytes for [a,b) + ensures View(ub, 0, len(ub)) == oldView[:a] ++ written ++ oldView[b:] + func ViewAfterUpdate(ub []byte, a int, b int, written seq[byte], oldView seq[byte]) + ``` + + The existing `binary.BigEndian.PutUint16/PutUint32` postconditions + (`PutUint16Spec(b[0], b[1], v)`) already give the elementwise facts needed to + instantiate `written`. +4. **Seq-index congruence helpers** only if the SMT solver needs nudging, e.g. + `SubSeqIndex(v seq[byte], a, b, i)` asserting `v[a:b][i] == v[a+i]`. Gobra encodes + these definitionally for sequences, so start without them and add on demand. +5. Optional convenience: `binary.BigEndian.Uint32SeqSpec(bs seq[byte])` / + `Uint16SeqSpec(bs seq[byte])` wrappers (`requires len(bs) == 4/2`) to avoid spelling + out four indices at every use. Pure sugar over `Uint32Spec`. + +## Migration strategy + +Verification is per-package and expensive, so migrate bottom-up, keeping the build green: + +1. **`verification/utils/slices`** — add `View` + Step-2 postconditions; verify the + package standalone (plus `slices_test.gobra`). +2. **`verification/utils/seqs` / gopacket stubs** — define `ToSeqByte` as + `View(ub, 0, len(ub))` (or delete it and re-specify `SerializeBuffer.View()` against + `sl.View`). +3. **`pkg/slayers/path`** — convert leaf parsers (3a). During the transition, if needed, + keep the old slice-based functions temporarily with bodies delegating to the new ones + (`Timestamp_old(raw, i, h) == Timestamp(sl.View(raw,0,len(raw))[idx:idx+InfoLen])`), so + dependent packages keep verifying; delete them at the end. Update + `hopfield.go`/`infofield.go` proof annotations (`DecodeFromBytes`/`SerializeTo` relate + the struct to `BytesToIO_HF(sl.View(...)...)`). +4. **`pkg/slayers/path/scion`** — convert `raw_spec.gobra` (3b), merge the `*WithInfo` + family, delete `widen-lemma.gobra`, rewrite `info_hop_setter_lemmas.gobra` on top of the + write-effect lemma; update `raw.go`, `base.go`, `decoded.go` annotations. +5. **`pkg/slayers`** — convert `scion_spec.gobra` (3c), delete the subslice lemmas, update + `scion.go` annotations. +6. **`router`** — convert `io-spec.gobra`, `io-spec-lemmas.gobra`, delete + `widen-lemma.gobra`, update `dataplane.go` invariants/contracts (mechanical + `f(raw, ...)` → `f(sl.View(raw, 0, len(raw)), ...)` rewriting, then removal of now-dead + lemma calls and `SplitRange/CombineRange` choreography that existed only to feed the + old preconditions). +7. Sweep for dead code: `AssertSliceOverlap` uses in pure functions, unused `Offset` + helper parameters (`BytesToIO_HF`'s `start`/`end`), stale comments, and the `absPkt` + TODO at `raw_spec.gobra:378`. + +Each step ends with running the package's Gobra verification (CI targets / `Makefile`, +including the chopper configuration re-enabled in #420) before moving up. + +## Risks / points of attention + +- **Trigger hygiene.** The elementwise postcondition of `View` uses `{ res[i] }` as + trigger; sequence-heavy proofs can be slower or flakier than pointwise permission + reasoning in pathological cases. Mitigation: keep big parsers `opaque` (as today) and + prove equalities via the small set of lemmas rather than raw quantifier reasoning; + measure verification times per package as part of each migration step. +- **Predicate-instance discipline.** `View(s, a, b)` requires exactly the instance + `Bytes(s, a, b)`. The convention must be: take the view of the instance you hold, then + slice the sequence. Code that today holds `Bytes(ub, start, end)` mid-split (e.g. inside + `DecodeFromBytes` choreography) uses `View(ub, start, end)` at that point and the Step-2 + postconditions to relate it to the outer view when recombining. +- **`decreases` measures.** The recursive `ViewAux` and the seq-based `hopFields` need + adjusted termination measures (straightforward: `end - i`, `segLen - currHfIdx`). +- **Churn volume.** `router/dataplane.go` alone has ~150 references to `absPkt`/ + `absIO_val`-family functions. The rewrite is mechanical but broad; the temporary-alias + trick in migration step 3 keeps intermediate states verifiable. +- **What does *not* change.** The `Bytes` predicate itself, the executable code, the + `encoding/binary` trusted stubs, the IO-spec types (`io.Pkt`, `io.Seg`, + `io.AbsInfoField`, ...), and the abstract transition relations + (`router/io-spec-abstract-transitions.gobra`) are untouched — the refactor only moves + the boundary where bytes stop being heap and become math. From 972e808a4642965a2139b85c5deff47d93ef998a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 11:42:57 +0000 Subject: [PATCH 02/35] Introduce View abstraction for slice contents in slices package Add an opaque ghost pure function View(s, start, end) that abstracts the contents of a Bytes predicate instance into a seq[byte], characterized elementwise via GetByte in its postcondition. Strengthen the specs of SplitByIndex/CombineAtIndex/Reslice/Unslice/SplitRange/CombineRange to relate the views of the predicate instances they exchange, add the extensionality lemma ViewEqFromElements and the subslice lemma ViewOfSubslice, and define seqs.ToSeqByte as an alias of View. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- verification/utils/seqs/seqs.gobra | 8 +- verification/utils/slices/slices.gobra | 145 ++++++++++++++++++++ verification/utils/slices/slices_test.gobra | 22 +++ 3 files changed, 173 insertions(+), 2 deletions(-) diff --git a/verification/utils/seqs/seqs.gobra b/verification/utils/seqs/seqs.gobra index e2c336a80..d4e96b033 100644 --- a/verification/utils/seqs/seqs.gobra +++ b/verification/utils/seqs/seqs.gobra @@ -39,10 +39,14 @@ ensures forall i int :: { res[i] } 0 <= i && i < size ==> res[i] == nil decreases _ pure func NewSeqByteSlice(size int) (res seq[[]byte]) +// Deprecated: use sl.View directly. ToSeqByte is kept as an alias for +// the view of a whole slice while its remaining clients are migrated. ghost requires acc(sl.Bytes(ub, 0, len(ub)), _) ensures len(res) == len(ub) ensures forall i int :: { res[i] } 0 <= i && i < len(ub) ==> res[i] == sl.GetByte(ub, 0, len(ub), i) -decreases _ -pure func ToSeqByte(ub []byte) (res seq[byte]) \ No newline at end of file +decreases +pure func ToSeqByte(ub []byte) (res seq[byte]) { + return sl.View(ub, 0, len(ub)) +} \ No newline at end of file diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index f8c21ed66..b1e1514c4 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -39,17 +39,116 @@ pure func GetByte(s []byte, start int, end int, i int) byte { return unfolding Bytes(s, start, end) in s[i] } +// View abstracts the contents of the range [start, end) of s, as +// guarded by the predicate instance Bytes(s, start, end), into a +// mathematical sequence of bytes. All content-related reasoning +// should be performed on views: given a view of the predicate +// instance one holds, the bytes of any subrange are obtained by +// (pure) sequence slicing, instead of by manipulating predicate +// instances, which cannot be done in pure contexts. The elementwise +// characterization below is exposed as a postcondition, so clients +// never need to reveal View. +ghost +opaque +requires acc(Bytes(s, start, end), _) +ensures len(res) == end - start +ensures forall i int :: { res[i] } 0 <= i && i < end - start ==> + res[i] == GetByte(s, start, end, start + i) +decreases +pure func View(s []byte, start int, end int) (res seq[byte]) { + return unfolding acc(Bytes(s, start, end), _) in ViewAux(s, start, end) +} + +// ViewAux computes the contents of the range [i, end) of s directly +// from the elementwise access permissions, so that the recursion does +// not require a predicate instance per subrange. +ghost +requires 0 <= i && i <= end && end <= cap(s) +requires forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], _) +ensures len(res) == end - i +ensures forall k int :: { res[k] } 0 <= k && k < end - i ==> + res[k] == s[i + k] +decreases end - i +pure func ViewAux(s []byte, i int, end int) (res seq[byte]) { + return i == end ? seq[byte]{} : + seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) +} + +// ViewEqFromElements derives an equality between View(s, start, end) +// and a sequence known to be elementwise equal to it. This is the +// extensionality principle used to reconstruct views after predicate +// instances are folded, e.g., after writes to the underlying buffer. +ghost +requires 0 < p +requires acc(Bytes(s, start, end), p) +requires len(other) == end - start +requires forall i int :: { other[i] } 0 <= i && i < end - start ==> + other[i] == GetByte(s, start, end, start + i) +ensures acc(Bytes(s, start, end), p) +ensures View(s, start, end) == other +decreases +func ViewEqFromElements(s []byte, start int, end int, other seq[byte], p perm) { + v := View(s, start, end) + assert forall i int :: { v[i] } 0 <= i && i < end - start ==> + v[i] == GetByte(s, start, end, start + i) + assert forall i int :: { v[i] } 0 <= i && i < end - start ==> + v[i] == other[i] + assert v == other +} + +// ViewOfSubslice relates the view of a subslice, guarded by its own +// predicate instance, to the view of the original slice. Together +// with the view postconditions of the lemmas below, it makes all +// per-function "offset equality" and "widening" lemmas unnecessary: +// the view of a subslice is the subsequence of the view. +ghost +requires 0 < p +requires 0 <= start && start <= end && end <= len(s) +preserves acc(Bytes(s, 0, len(s)), p) +preserves acc(Bytes(s[start:end], 0, end - start), p) +ensures View(s[start:end], 0, end - start) == View(s, 0, len(s))[start:end] +decreases +func ViewOfSubslice(s []byte, start int, end int, p perm) { + vFull := View(s, 0, len(s)) + vSub := View(s[start:end], 0, end - start) + unfold acc(Bytes(s, 0, len(s)), p/2) + unfold acc(Bytes(s[start:end], 0, end - start), p/2) + AssertSliceOverlap(s, start, end) + assert forall i int :: { &s[start:end][i] } 0 <= i && i < end - start ==> + s[start:end][i] == s[start + i] + assert forall i int :: { &s[start:end][i] } 0 <= i && i < end - start ==> + vSub[i] == s[start:end][i] + assert forall i int :: { &s[i] } start <= i && i < end ==> + vFull[i] == s[i] + fold acc(Bytes(s[start:end], 0, end - start), p/2) + fold acc(Bytes(s, 0, len(s)), p/2) + assert forall i int :: { vSub[i] } 0 <= i && i < end - start ==> + vSub[i] == vFull[start + i] + assert vSub == vFull[start:end] +} + ghost requires 0 < p requires acc(Bytes(s, start, end), p) requires start <= idx && idx <= end ensures acc(Bytes(s, start, idx), p) ensures acc(Bytes(s, idx, end), p) +ensures View(s, start, idx) == old(View(s, start, end))[:idx - start] +ensures View(s, idx, end) == old(View(s, start, end))[idx - start:] decreases func SplitByIndex_Bytes(s []byte, start int, end int, idx int, p perm) { + v := View(s, start, end) unfold acc(Bytes(s, start, end), p) + assert forall i int :: { &s[i] } start <= i && i < end ==> + s[i] == v[i - start] fold acc(Bytes(s, start, idx), p) fold acc(Bytes(s, idx, end), p) + assert forall i int :: { GetByte(s, start, idx, i) } start <= i && i < idx ==> + GetByte(s, start, idx, i) == v[i - start] + assert forall i int :: { GetByte(s, idx, end, i) } idx <= i && i < end ==> + GetByte(s, idx, end, i) == v[i - start] + ViewEqFromElements(s, start, idx, v[:idx - start], p) + ViewEqFromElements(s, idx, end, v[idx - start:], p) } ghost @@ -57,11 +156,24 @@ requires 0 < p requires acc(Bytes(s, start, idx), p) requires acc(Bytes(s, idx, end), p) ensures acc(Bytes(s, start, end), p) +ensures View(s, start, end) == + old(View(s, start, idx)) ++ old(View(s, idx, end)) decreases func CombineAtIndex_Bytes(s []byte, start int, end int, idx int, p perm) { + v1 := View(s, start, idx) + v2 := View(s, idx, end) unfold acc(Bytes(s, start, idx), p) unfold acc(Bytes(s, idx, end), p) + assert forall i int :: { &s[i] } start <= i && i < idx ==> + s[i] == v1[i - start] + assert forall i int :: { &s[i] } idx <= i && i < end ==> + s[i] == v2[i - idx] fold acc(Bytes(s, start, end), p) + assert forall i int :: { GetByte(s, start, end, i) } start <= i && i < end ==> + GetByte(s, start, end, i) == (i < idx ? v1[i - start] : v2[i - idx]) + assert forall i int :: { (v1 ++ v2)[i] } 0 <= i && i < end - start ==> + (v1 ++ v2)[i] == (i < idx - start ? v1[i] : v2[i - (idx - start)]) + ViewEqFromElements(s, start, end, v1 ++ v2, p) } ghost @@ -71,11 +183,20 @@ requires acc(Bytes(s, start, end), p) // the slice operation is well-formed requires unfolding acc(Bytes(s, start, end), p) in true ensures acc(Bytes(s[start:end], 0, len(s[start:end])), p) +ensures View(s[start:end], 0, end - start) == old(View(s, start, end)) decreases func Reslice_Bytes(s []byte, start int, end int, p perm) { + v := View(s, start, end) unfold acc(Bytes(s, start, end), p) assert forall i int :: { &s[start:end][i] }{ &s[start + i] } 0 <= i && i < (end-start) ==> &s[start:end][i] == &s[start + i] + assert forall i int :: { &s[start + i] } 0 <= i && i < (end-start) ==> + s[start + i] == v[i] + assert forall i int :: { &s[start:end][i] } 0 <= i && i < (end-start) ==> + s[start:end][i] == v[i] fold acc(Bytes(s[start:end], 0, len(s[start:end])), p) + assert forall i int :: { GetByte(s[start:end], 0, end - start, i) } 0 <= i && i < (end-start) ==> + GetByte(s[start:end], 0, end - start, i) == v[i] + ViewEqFromElements(s[start:end], 0, end - start, v, p) } ghost @@ -83,25 +204,34 @@ requires 0 < p requires 0 <= start && start <= end && end <= cap(s) requires acc(Bytes(s[start:end], 0, len(s[start:end])), p) ensures acc(Bytes(s, start, end), p) +ensures View(s, start, end) == old(View(s[start:end], 0, end - start)) decreases func Unslice_Bytes(s []byte, start int, end int, p perm) { + v := View(s[start:end], 0, end - start) unfold acc(Bytes(s[start:end], 0, len(s[start:end])), p) assert 0 <= start && start <= end && end <= cap(s) assert forall i int :: { &s[start:end][i] } 0 <= i && i < len(s[start:end]) ==> acc(&s[start:end][i], p) assert forall i int :: { &s[start:end][i] }{ &s[start + i] } 0 <= i && i < len(s[start:end]) ==> &s[start:end][i] == &s[start + i] + assert forall i int :: { &s[start:end][i] } 0 <= i && i < len(s[start:end]) ==> s[start:end][i] == v[i] invariant 0 <= j && j <= len(s[start:end]) invariant forall i int :: { &s[start:end][i] } j <= i && i < len(s[start:end]) ==> acc(&s[start:end][i], p) invariant forall i int :: { &s[start:end][i] }{ &s[start + i] } 0 <= i && i < len(s[start:end]) ==> &s[start:end][i] == &s[start + i] + invariant forall i int :: { &s[start:end][i] } j <= i && i < len(s[start:end]) ==> s[start:end][i] == v[i] invariant forall i int :: { &s[i] } start <= i && i < start+j ==> acc(&s[i], p) + invariant forall i int :: { &s[i] } start <= i && i < start+j ==> s[i] == v[i - start] decreases len(s[start:end]) - j for j := 0; j < len(s[start:end]); j++ { assert forall i int :: { &s[i] } start <= i && i < start+j ==> acc(&s[i], p) assert &s[start:end][j] == &s[start + j] assert acc(&s[start + j], p) + assert s[start + j] == v[j] assert forall i int :: { &s[i] } start <= i && i <= start+j ==> acc(&s[i], p) } fold acc(Bytes(s, start, end), p) + assert forall i int :: { GetByte(s, start, end, i) } start <= i && i < end ==> + GetByte(s, start, end, i) == v[i - start] + ViewEqFromElements(s, start, end, v, p) } ghost @@ -111,10 +241,18 @@ requires acc(Bytes(s, 0, len(s)), p) ensures acc(Bytes(s[start:end], 0, end-start), p) ensures acc(Bytes(s, 0, start), p) ensures acc(Bytes(s, end, len(s)), p) +ensures View(s[start:end], 0, end-start) == old(View(s, 0, len(s)))[start:end] +ensures View(s, 0, start) == old(View(s, 0, len(s)))[:start] +ensures View(s, end, len(s)) == old(View(s, 0, len(s)))[end:] decreases func SplitRange_Bytes(s []byte, start int, end int, p perm) { + v := View(s, 0, len(s)) SplitByIndex_Bytes(s, 0, len(s), start, p) SplitByIndex_Bytes(s, start, len(s), end, p) + assert View(s, start, end) == v[start:][:end - start] + assert v[start:][:end - start] == v[start:end] + assert View(s, end, len(s)) == v[start:][end - start:] + assert v[start:][end - start:] == v[end:] Reslice_Bytes(s, start, end, p) } @@ -125,11 +263,18 @@ requires acc(Bytes(s[start:end], 0, end-start), p) requires acc(Bytes(s, 0, start), p) requires acc(Bytes(s, end, len(s)), p) ensures acc(Bytes(s, 0, len(s)), p) +ensures View(s, 0, len(s)) == old(View(s, 0, start)) ++ + old(View(s[start:end], 0, end-start)) ++ old(View(s, end, len(s))) decreases func CombineRange_Bytes(s []byte, start int, end int, p perm) { + vLeft := View(s, 0, start) + vMid := View(s[start:end], 0, end-start) + vRight := View(s, end, len(s)) Unslice_Bytes(s, start, end, p) CombineAtIndex_Bytes(s, start, len(s), end, p) CombineAtIndex_Bytes(s, 0, len(s), start, p) + assert View(s, 0, len(s)) == vLeft ++ (vMid ++ vRight) + assert vLeft ++ (vMid ++ vRight) == vLeft ++ vMid ++ vRight } ghost diff --git a/verification/utils/slices/slices_test.gobra b/verification/utils/slices/slices_test.gobra index 399161928..6a1b505eb 100644 --- a/verification/utils/slices/slices_test.gobra +++ b/verification/utils/slices/slices_test.gobra @@ -22,4 +22,26 @@ func Bytes_test() { s := make([]byte, 10) fold Bytes(s, 0, 10) // assert false // fails +} + +func View_test() { + s := make([]byte, 10) + fold Bytes(s, 0, 10) + v := View(s, 0, 10) + assert len(v) == 10 + assert v[3] == GetByte(s, 0, 10, 3) + + // splitting preserves the contents of the views + SplitByIndex_Bytes(s, 0, 10, 4, writePerm) + assert View(s, 0, 4) == v[:4] + assert View(s, 4, 10) == v[4:] + assert View(s, 0, 4)[2] == v[2] + CombineAtIndex_Bytes(s, 0, 10, 4, writePerm) + assert View(s, 0, 10) == v + + // reslicing preserves the contents of the views + SplitRange_Bytes(s, 2, 8, writePerm) + assert View(s[2:8], 0, 6) == v[2:8] + CombineRange_Bytes(s, 2, 8, writePerm) + assert View(s, 0, 10) == v } \ No newline at end of file From 906288f3230a8f7615220ea8b662e57c2c9178bb Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 11:49:04 +0000 Subject: [PATCH 03/35] Convert path leaf parsers to permission-free seq[byte] functions ConsDir, Peer, Timestamp, AbsUinfo, BytesToAbsInfoField and BytesToIO_HF now take exactly the bytes of the field they parse as a seq[byte], computed from a buffer with sl.View, and no longer require offsets or sl.Bytes permissions. They read multi-byte fields with the byte-level binary.BigEndian.*Spec functions, which removes the unfolding and AssertSliceOverlap boilerplate. BytesToAbsInfoFieldHelper, the slice-based MAC helpers and the OffsetEq/Widen lemmas are removed; a seq-based FromSeqToMacArray/EqualSeqImplyEqualMac replaces the latter. The DecodeFromBytes/SerializeTo proofs relate the buffers to the parsers through views. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/hopfield.go | 35 ++++++-- pkg/slayers/path/hopfield_spec.gobra | 70 +++------------- pkg/slayers/path/infofield.go | 39 +++++---- pkg/slayers/path/infofield_spec.gobra | 109 ++++++------------------- pkg/slayers/path/io_msgterm_spec.gobra | 22 ++--- 5 files changed, 97 insertions(+), 178 deletions(-) diff --git a/pkg/slayers/path/hopfield.go b/pkg/slayers/path/hopfield.go index 03299dbb4..8b467c5aa 100644 --- a/pkg/slayers/path/hopfield.go +++ b/pkg/slayers/path/hopfield.go @@ -79,13 +79,14 @@ type HopField struct { // @ preserves acc(sl.Bytes(raw, 0, HopLen), R45) // @ ensures h.Mem() // @ ensures err == nil -// @ ensures BytesToIO_HF(raw, 0, 0, HopLen) == +// @ ensures BytesToIO_HF(sl.View(raw, 0, HopLen)) == // @ unfolding acc(h.Mem(), R10) in h.Abs() // @ decreases func (h *HopField) DecodeFromBytes(raw []byte) (err error) { if len(raw) < HopLen { return serrors.New("HopField raw too short", "expected", HopLen, "actual", len(raw)) } + //@ ghost v := sl.View(raw, 0, HopLen) //@ unfold acc(sl.Bytes(raw, 0, HopLen), R46) h.EgressRouterAlert = raw[0]&0x1 == 0x1 h.IngressRouterAlert = raw[0]&0x2 == 0x2 @@ -100,8 +101,14 @@ func (h *HopField) DecodeFromBytes(raw []byte) (err error) { copy(h.Mac[:], raw[6:6+MacLen] /*@ , R47 @*/) //@ assert forall i int :: {&h.Mac[:][i]} 0 <= i && i < MacLen ==> h.Mac[:][i] == raw[6:6+MacLen][i] //@ assert forall i int :: {&h.Mac[i]} 0 <= i && i < MacLen ==> h.Mac[:][i] == h.Mac[i] - //@ EqualBytesImplyEqualMac(raw[6:6+MacLen], h.Mac) - //@ assert BytesToIO_HF(raw, 0, 0, HopLen) == h.Abs() + //@ assert v[2] == raw[2] && v[3] == raw[3] && v[4] == raw[4] && v[5] == raw[5] + //@ assert forall i int :: { v[6:6+MacLen][i] } 0 <= i && i < MacLen ==> + //@ v[6:6+MacLen][i] == v[6+i] + //@ assert forall i int :: { &raw[6+i] } 0 <= i && i < MacLen ==> v[6+i] == raw[6+i] + //@ assert forall i int :: { h.Mac[i] } 0 <= i && i < MacLen ==> + //@ v[6:6+MacLen][i] == h.Mac[i] + //@ EqualSeqImplyEqualMac(v[6:6+MacLen], h.Mac) + //@ assert BytesToIO_HF(v) == h.Abs() //@ fold acc(sl.Bytes(raw, 0, HopLen), R46) //@ fold h.Mem() return nil @@ -113,7 +120,7 @@ func (h *HopField) DecodeFromBytes(raw []byte) (err error) { // @ preserves acc(h.Mem(), R10) // @ preserves sl.Bytes(b, 0, HopLen) // @ ensures err == nil -// @ ensures BytesToIO_HF(b, 0, 0, HopLen) == +// @ ensures BytesToIO_HF(sl.View(b, 0, HopLen)) == // @ unfolding acc(h.Mem(), R10) in h.Abs() // @ decreases func (h *HopField) SerializeTo(b []byte) (err error) { @@ -134,6 +141,8 @@ func (h *HopField) SerializeTo(b []byte) (err error) { binary.BigEndian.PutUint16(b[2:4], h.ConsIngress) //@ assert &b[4:6][0] == &b[4] && &b[4:6][1] == &b[5] binary.BigEndian.PutUint16(b[4:6], h.ConsEgress) + //@ assert binary.BigEndian.Uint16Spec(b[2], b[3]) == h.ConsIngress + //@ assert binary.BigEndian.Uint16Spec(b[4], b[5]) == h.ConsEgress //@ assert forall i int :: { &b[i] } 0 <= i && i < HopLen ==> acc(&b[i]) //@ assert forall i int :: { &h.Mac[:][i] } 0 <= i && i < len(h.Mac) ==> //@ &h.Mac[i] == &h.Mac[:][i] @@ -141,9 +150,23 @@ func (h *HopField) SerializeTo(b []byte) (err error) { copy(b[6:6+MacLen], h.Mac[:] /*@, R47 @*/) //@ assert forall i int :: {&h.Mac[:][i]} 0 <= i && i < MacLen ==> h.Mac[:][i] == b[6:6+MacLen][i] //@ assert forall i int :: {&h.Mac[i]} 0 <= i && i < MacLen ==> h.Mac[:][i] == h.Mac[i] - //@ EqualBytesImplyEqualMac(b[6:6+MacLen], h.Mac) + //@ assert forall i int :: { &b[6+i] } 0 <= i && i < MacLen ==> b[6:6+MacLen][i] == b[6+i] + //@ ghost b2 := b[2] + //@ ghost b3 := b[3] + //@ ghost b4 := b[4] + //@ ghost b5 := b[5] + //@ ghost mac := seq[byte]{b[6], b[7], b[8], b[9], b[10], b[11]} + //@ assert forall i int :: { mac[i] } 0 <= i && i < MacLen ==> mac[i] == h.Mac[i] //@ fold sl.Bytes(b, 0, HopLen) - //@ assert h.Abs() == BytesToIO_HF(b, 0, 0, HopLen) + //@ ghost v := sl.View(b, 0, HopLen) + //@ assert v[2] == b2 && v[3] == b3 && v[4] == b4 && v[5] == b5 + //@ assert forall j int :: { v[j] } 6 <= j && j < 6+MacLen ==> v[j] == mac[j-6] + //@ assert forall i int :: { v[6:6+MacLen][i] } 0 <= i && i < MacLen ==> + //@ v[6:6+MacLen][i] == v[6+i] + //@ assert forall i int :: { h.Mac[i] } 0 <= i && i < MacLen ==> + //@ v[6:6+MacLen][i] == h.Mac[i] + //@ EqualSeqImplyEqualMac(v[6:6+MacLen], h.Mac) + //@ assert h.Abs() == BytesToIO_HF(v) //@ fold acc(h.Mem(), R11) return nil } diff --git a/pkg/slayers/path/hopfield_spec.gobra b/pkg/slayers/path/hopfield_spec.gobra index 816768b22..8a0702072 100644 --- a/pkg/slayers/path/hopfield_spec.gobra +++ b/pkg/slayers/path/hopfield_spec.gobra @@ -18,9 +18,7 @@ package path import ( "verification/io" - sl "verification/utils/slices" "verification/dependencies/encoding/binary" - . "verification/utils/definitions" ) pred (h *HopField) Mem() { @@ -40,72 +38,24 @@ pure func IO_ifsToIfs(ifs option[io.Ifs]) uint16 { return ifs == none[io.Ifs] ? 0 : uint16(get(ifs).V) } +// BytesToIO_HF takes the raw bytes of exactly one hop field (obtained +// by slicing the view of a buffer) and computes its abstract +// representation. It requires no permissions. ghost -requires 0 <= start && start <= middle -requires middle + HopLen <= end && end <= len(raw) -requires sl.Bytes(raw, start, end) +requires len(raw) == HopLen decreases -pure func BytesToIO_HF(raw [] byte, start int, middle int, end int) (io.HF) { - return let _ := sl.AssertSliceOverlap(raw, middle+2, middle+4) in - let _ := sl.AssertSliceOverlap(raw, middle+4, middle+6) in - let _ := sl.AssertSliceOverlap(raw, middle+6, middle+6+MacLen) in - unfolding sl.Bytes(raw, start, end) in - let inif2 := binary.BigEndian.Uint16(raw[middle+2:middle+4]) in - let egif2 := binary.BigEndian.Uint16(raw[middle+4:middle+6]) in - let op_inif2 := ifsToIO_ifs(inif2) in - let op_egif2 := ifsToIO_ifs(egif2) in +pure func BytesToIO_HF(raw seq[byte]) (io.HF) { + return let inif2 := binary.BigEndian.Uint16Spec(raw[2], raw[3]) in + let egif2 := binary.BigEndian.Uint16Spec(raw[4], raw[5]) in + let op_inif2 := ifsToIO_ifs(inif2) in + let op_egif2 := ifsToIO_ifs(egif2) in io.HF { InIF2: op_inif2, EgIF2: op_egif2, - HVF: AbsMac(FromSliceToMacArray(raw[middle+6:middle+6+MacLen])), + HVF: AbsMac(FromSeqToMacArray(raw[6:6+MacLen])), } } -// WidenBytesHopField shows the equality between the HF computed -// from raw bytes in slice 'raw' starting at position `offset` with -// the HF obtained from the slice 'raw[start:end]`. -ghost -requires 0 <= start && start <= offset -requires offset + HopLen <= end -requires end <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), R55) -preserves acc(sl.Bytes(raw[start:end], 0, len(raw[start:end])), R55) -ensures BytesToIO_HF(raw, 0, offset, len(raw)) == - BytesToIO_HF(raw[start:end], 0, offset-start, end-start) -decreases -func WidenBytesHopField(raw []byte, offset int, start int, end int) { - unfold acc(sl.Bytes(raw, 0, len(raw)), R56) - unfold acc(sl.Bytes(raw[start:end], 0, len(raw[start:end])), R56) - hfBytes1 := BytesToIO_HF(raw, 0, offset, len(raw)) - hfBytes2 := BytesToIO_HF(raw[start:end], 0, offset-start, end-start) - - sl.AssertSliceOverlap(raw, start, end) - sl.AssertSliceOverlap(raw[start:end], offset-start+2, offset-start+4) - assert hfBytes1.InIF2 == hfBytes2.InIF2 - sl.AssertSliceOverlap(raw[start:end], offset-start+4, offset-start+6) - assert hfBytes1.EgIF2 == hfBytes2.EgIF2 - sl.AssertSliceOverlap(raw[start:end], offset-start+6, offset-start+6+MacLen) - - fold acc(sl.Bytes(raw, 0, len(raw)), R56) - fold acc(sl.Bytes(raw[start:end], 0, len(raw[start:end])), R56) -} - -// WidenBytesHopField shows the equality between the HF computed -// from raw bytes in slice 'raw' starting at position `offset` with -// the HF obtained from the slice 'raw[offset:offset+HopLen]`. -// It is a special case of `WidenBytesHopField`. -ghost -requires 0 <= offset -requires offset+HopLen <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), R55) -preserves acc(sl.Bytes(raw[offset:offset+HopLen], 0, HopLen), R55) -ensures BytesToIO_HF(raw, 0, offset, len(raw)) == - BytesToIO_HF(raw[offset:offset+HopLen], 0, 0, HopLen) -decreases -func BytesToAbsHopFieldOffsetEq(raw [] byte, offset int) { - WidenBytesHopField(raw, offset, offset, offset+HopLen) -} - ghost decreases pure func (h HopField) Abs() (io.HF) { diff --git a/pkg/slayers/path/infofield.go b/pkg/slayers/path/infofield.go index 6f9e616aa..a25ce7e56 100644 --- a/pkg/slayers/path/infofield.go +++ b/pkg/slayers/path/infofield.go @@ -64,13 +64,14 @@ type InfoField struct { // @ preserves acc(inf) // @ preserves acc(slices.Bytes(raw, 0, len(raw)), R45) // @ ensures err == nil -// @ ensures BytesToAbsInfoField(raw, 0) == +// @ ensures BytesToAbsInfoField(slices.View(raw, 0, len(raw))[:InfoLen]) == // @ inf.ToAbsInfoField() // @ decreases func (inf *InfoField) DecodeFromBytes(raw []byte) (err error) { if len(raw) < InfoLen { return serrors.New("InfoField raw too short", "expected", InfoLen, "actual", len(raw)) } + //@ ghost v := slices.View(raw, 0, len(raw)) //@ unfold acc(slices.Bytes(raw, 0, len(raw)), R50) inf.ConsDir = raw[0]&0x1 == 0x1 inf.Peer = raw[0]&0x2 == 0x2 @@ -79,8 +80,10 @@ func (inf *InfoField) DecodeFromBytes(raw []byte) (err error) { //@ assert &raw[4:8][0] == &raw[4] && &raw[4:8][1] == &raw[5] //@ assert &raw[4:8][2] == &raw[6] && &raw[4:8][3] == &raw[7] inf.Timestamp = binary.BigEndian.Uint32(raw[4:8]) + //@ assert v[0] == raw[0] && v[2] == raw[2] && v[3] == raw[3] + //@ assert v[4] == raw[4] && v[5] == raw[5] && v[6] == raw[6] && v[7] == raw[7] //@ fold acc(slices.Bytes(raw, 0, len(raw)), R50) - //@ assert reveal BytesToAbsInfoField(raw, 0) == + //@ assert reveal BytesToAbsInfoField(v[:InfoLen]) == //@ inf.ToAbsInfoField() return nil } @@ -92,7 +95,7 @@ func (inf *InfoField) DecodeFromBytes(raw []byte) (err error) { // @ preserves slices.Bytes(b, 0, len(b)) // @ ensures err == nil // @ ensures inf.ToAbsInfoField() == -// @ BytesToAbsInfoField(b, 0) +// @ BytesToAbsInfoField(slices.View(b, 0, len(b))[:InfoLen]) // @ decreases func (inf *InfoField) SerializeTo(b []byte) (err error) { if len(b) < InfoLen { @@ -105,32 +108,36 @@ func (inf *InfoField) SerializeTo(b []byte) (err error) { if inf.ConsDir { b[0] |= 0x1 } - //@ ghost tmpInfo1 := BytesToAbsInfoFieldHelper(b, 0) //@ bits.InfoFieldFirstByteSerializationLemmas() - //@ assert tmpInfo1.ConsDir == targetAbsInfo.ConsDir + //@ assert (b[0] & 0x1 == 0x1) == targetAbsInfo.ConsDir //@ ghost firstByte := b[0] if inf.Peer { b[0] |= 0x2 } - //@ tmpInfo2 := BytesToAbsInfoFieldHelper(b, 0) - //@ assert tmpInfo2.Peer == (b[0] & 0x2 == 0x2) - //@ assert tmpInfo2.ConsDir == (b[0] & 0x1 == 0x1) - //@ assert tmpInfo2.Peer == targetAbsInfo.Peer - //@ assert tmpInfo2.ConsDir == tmpInfo1.ConsDir - //@ assert tmpInfo2.ConsDir == targetAbsInfo.ConsDir + //@ assert (b[0] & 0x2 == 0x2) == targetAbsInfo.Peer + //@ assert (b[0] & 0x1 == 0x1) == (firstByte & 0x1 == 0x1) + //@ assert (b[0] & 0x1 == 0x1) == targetAbsInfo.ConsDir b[1] = 0 // reserved //@ assert &b[2:4][0] == &b[2] && &b[2:4][1] == &b[3] binary.BigEndian.PutUint16(b[2:4], inf.SegID) - //@ ghost tmpInfo3 := BytesToAbsInfoFieldHelper(b, 0) - //@ assert tmpInfo3.UInfo == targetAbsInfo.UInfo + //@ assert binary.BigEndian.Uint16Spec(b[2], b[3]) == inf.SegID //@ assert &b[4:8][0] == &b[4] && &b[4:8][1] == &b[5] //@ assert &b[4:8][2] == &b[6] && &b[4:8][3] == &b[7] binary.BigEndian.PutUint32(b[4:8], inf.Timestamp) - //@ ghost tmpInfo4 := BytesToAbsInfoFieldHelper(b, 0) - //@ assert tmpInfo4.AInfo == targetAbsInfo.AInfo + //@ assert binary.BigEndian.Uint32Spec(b[4], b[5], b[6], b[7]) == inf.Timestamp + //@ ghost b0 := b[0] + //@ ghost b2 := b[2] + //@ ghost b3 := b[3] + //@ ghost b4 := b[4] + //@ ghost b5 := b[5] + //@ ghost b6 := b[6] + //@ ghost b7 := b[7] //@ fold slices.Bytes(b, 0, len(b)) + //@ ghost v := slices.View(b, 0, len(b)) + //@ assert v[0] == b0 && v[2] == b2 && v[3] == b3 + //@ assert v[4] == b4 && v[5] == b5 && v[6] == b6 && v[7] == b7 //@ assert inf.ToAbsInfoField() == - //@ reveal BytesToAbsInfoField(b, 0) + //@ reveal BytesToAbsInfoField(v[:InfoLen]) return nil } diff --git a/pkg/slayers/path/infofield_spec.gobra b/pkg/slayers/path/infofield_spec.gobra index f29c8099b..83f39a215 100644 --- a/pkg/slayers/path/infofield_spec.gobra +++ b/pkg/slayers/path/infofield_spec.gobra @@ -18,9 +18,7 @@ package path import ( "verification/io" - sl "verification/utils/slices" "verification/dependencies/encoding/binary" - . "verification/utils/definitions" ) ghost @@ -29,105 +27,50 @@ pure func InfoFieldOffset(currINF, headerOffset int) int { return headerOffset + InfoLen * currINF } -ghost -requires 0 <= currINF && 0 <= headerOffset -requires InfoFieldOffset(currINF, headerOffset) < len(raw) -requires sl.Bytes(raw, 0, len(raw)) -decreases -pure func ConsDir(raw []byte, currINF int, headerOffset int) bool { - return unfolding sl.Bytes(raw, 0, len(raw)) in - raw[InfoFieldOffset(currINF, headerOffset)] & 0x1 == 0x1 -} - -ghost -requires 0 <= currINF && 0 <= headerOffset -requires InfoFieldOffset(currINF, headerOffset) < len(raw) -requires sl.Bytes(raw, 0, len(raw)) -decreases -pure func Peer(raw []byte, currINF int, headerOffset int) bool { - return unfolding sl.Bytes(raw, 0, len(raw)) in - raw[InfoFieldOffset(currINF, headerOffset)] & 0x2 == 0x2 -} +// The parsing functions below take the raw bytes of exactly one info +// field (obtained by slicing the view of a buffer) and require no +// permissions. Callers select the relevant bytes with, e.g., +// sl.View(ub, 0, len(ub))[off:off+InfoLen]. ghost -requires 0 <= currINF && 0 <= headerOffset -requires InfoFieldOffset(currINF, headerOffset) + InfoLen < len(raw) -requires sl.Bytes(raw, 0, len(raw)) +requires len(raw) == InfoLen decreases -pure func Timestamp(raw []byte, currINF int, headerOffset int) io.Ainfo { - return let idx := InfoFieldOffset(currINF, headerOffset)+4 in - unfolding sl.Bytes(raw, 0, len(raw)) in - let _ := sl.AssertSliceOverlap(raw, idx, idx+4) in - io.Ainfo{uint(binary.BigEndian.Uint32(raw[idx:idx+4]))} +pure func ConsDir(raw seq[byte]) bool { + return raw[0] & 0x1 == 0x1 } ghost -requires 0 <= currINF && 0 <= headerOffset -requires InfoFieldOffset(currINF, headerOffset) + InfoLen < len(raw) -requires sl.Bytes(raw, 0, len(raw)) +requires len(raw) == InfoLen decreases -pure func AbsUinfo(raw []byte, currINF int, headerOffset int) set[io.MsgTerm] { - return let idx := InfoFieldOffset(currINF, headerOffset)+2 in - unfolding sl.Bytes(raw, 0, len(raw)) in - let _ := sl.AssertSliceOverlap(raw, idx, idx+2) in - AbsUInfoFromUint16(binary.BigEndian.Uint16(raw[idx:idx+2])) +pure func Peer(raw seq[byte]) bool { + return raw[0] & 0x2 == 0x2 } ghost -opaque -requires 0 <= middle -requires middle+InfoLen <= len(raw) -requires sl.Bytes(raw, 0, len(raw)) +requires len(raw) == InfoLen decreases -pure func BytesToAbsInfoField(raw [] byte, middle int) (io.AbsInfoField) { - return unfolding sl.Bytes(raw, 0, len(raw)) in - BytesToAbsInfoFieldHelper(raw, middle) +pure func Timestamp(raw seq[byte]) io.Ainfo { + return io.Ainfo{uint(binary.BigEndian.Uint32Spec(raw[4], raw[5], raw[6], raw[7]))} } ghost -requires 0 <= middle -requires middle+InfoLen <= len(raw) -requires forall i int :: { &raw[i] } middle <= i && i < len(raw) ==> - acc(&raw[i]) +requires len(raw) == InfoLen decreases -pure func BytesToAbsInfoFieldHelper(raw [] byte, middle int) (io.AbsInfoField) { - return let _ := sl.AssertSliceOverlap(raw, middle+2, middle+4) in - let _ := sl.AssertSliceOverlap(raw, middle+4, middle+8) in - io.AbsInfoField { - AInfo: io.Ainfo{uint(binary.BigEndian.Uint32(raw[middle+4:middle+8]))}, - UInfo: AbsUInfoFromUint16(binary.BigEndian.Uint16(raw[middle+2:middle+4])), - ConsDir: raw[middle] & 0x1 == 0x1, - Peer: raw[middle] & 0x2 == 0x2, - } +pure func AbsUinfo(raw seq[byte]) set[io.MsgTerm] { + return AbsUInfoFromUint16(binary.BigEndian.Uint16Spec(raw[2], raw[3])) } ghost -requires 0 <= middle -requires middle+InfoLen <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), R55) -preserves acc(sl.Bytes(raw[middle:middle+InfoLen], 0, InfoLen), R55) -ensures BytesToAbsInfoField(raw, middle) == - BytesToAbsInfoField(raw[middle:middle+InfoLen], 0) +opaque +requires len(raw) == InfoLen decreases -func BytesToAbsInfoFieldOffsetEq(raw [] byte, middle int) { - start := middle - end := middle+InfoLen - unfold acc(sl.Bytes(raw, 0, len(raw)), R56) - unfold acc(sl.Bytes(raw[start:end], 0, InfoLen), R56) - absInfo1 := reveal BytesToAbsInfoField(raw, start) - absInfo2 := reveal BytesToAbsInfoField(raw[start:end], 0) - - assert absInfo1.ConsDir == absInfo2.ConsDir - assert absInfo1.Peer == absInfo2.Peer - sl.AssertSliceOverlap(raw, start, end) - sl.AssertSliceOverlap(raw[start:end], 4, 8) - assert absInfo1.AInfo == absInfo2.AInfo - sl.AssertSliceOverlap(raw[start:end], 2, 4) - assert absInfo1.UInfo == absInfo2.UInfo - assert absInfo1 == absInfo2 - - fold acc(sl.Bytes(raw, 0, len(raw)), R56) - fold acc(sl.Bytes(raw[start:end], 0, InfoLen), R56) +pure func BytesToAbsInfoField(raw seq[byte]) (io.AbsInfoField) { + return io.AbsInfoField { + AInfo: Timestamp(raw), + UInfo: AbsUinfo(raw), + ConsDir: ConsDir(raw), + Peer: Peer(raw), + } } ghost @@ -139,4 +82,4 @@ pure func (inf InfoField) ToAbsInfoField() (io.AbsInfoField) { ConsDir: inf.ConsDir, Peer: inf.Peer, } -} \ No newline at end of file +} diff --git a/pkg/slayers/path/io_msgterm_spec.gobra b/pkg/slayers/path/io_msgterm_spec.gobra index 7331673bc..2acc07949 100644 --- a/pkg/slayers/path/io_msgterm_spec.gobra +++ b/pkg/slayers/path/io_msgterm_spec.gobra @@ -18,7 +18,6 @@ package path import ( "verification/io" - . "verification/utils/definitions" ) // At the moment, we assume that all cryptographic operations performed at the code level @@ -34,28 +33,25 @@ ghost decreases pure func AbsMac(mac [MacLen]byte) (io.MsgTerm) -// The following function converts a slice with at least `MacLen` elements into -// an (exclusive) array containing the mac. Note that there are no permissions -// involved for accessing exclusive arrays. +// The following function converts a sequence with exactly `MacLen` elements +// into an (exclusive) array containing the mac. Note that there are no +// permissions involved for accessing exclusive arrays or sequences. ghost -requires MacLen <= len(mac) -requires forall i int :: { &mac[i] } 0 <= i && i < MacLen ==> acc(&mac[i]) +requires len(mac) == MacLen ensures len(res) == MacLen ensures forall i int :: { res[i] } 0 <= i && i < MacLen ==> mac[i] == res[i] decreases -pure func FromSliceToMacArray(mac []byte) (res [MacLen]byte) { +pure func FromSeqToMacArray(mac seq[byte]) (res [MacLen]byte) { return [MacLen]byte{ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] } } ghost requires len(mac1) == MacLen -requires forall i int :: { &mac1[i] } 0 <= i && i < MacLen ==> acc(&mac1[i], R50) -requires forall i int :: { &mac1[i] } 0 <= i && i < MacLen ==> mac1[i] == mac2[i] -ensures forall i int :: { &mac1[i] } 0 <= i && i < MacLen ==> acc(&mac1[i], R50) -ensures AbsMac(FromSliceToMacArray(mac1)) == AbsMac(mac2) +requires forall i int :: { mac2[i] } 0 <= i && i < MacLen ==> mac1[i] == mac2[i] +ensures AbsMac(FromSeqToMacArray(mac1)) == AbsMac(mac2) decreases -func EqualBytesImplyEqualMac(mac1 []byte, mac2 [MacLen]byte) { - mac1Arr := FromSliceToMacArray(mac1) +func EqualSeqImplyEqualMac(mac1 seq[byte], mac2 [MacLen]byte) { + mac1Arr := FromSeqToMacArray(mac1) assert mac1Arr == mac2 assert mac1Arr[0] == mac2[0] && mac1Arr[1] == mac2[1] && From 8f7344f60ab7cf01dfa9c234f801972ea24ad59f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 12:15:03 +0000 Subject: [PATCH 04/35] Convert scion path abstraction functions to seq[byte] views CurrSeg/LeftSeg/RightSeg/MidSeg/hopFields/segment, RawBytesToMetaHdr, RawBytesToBase, validPktMetaHdr, absPkt, the *WithInfo family and all lemmas relating them (Widen*, *SegEquality, Xover*, IncCurrSeg, HopsFrom*, Align*) now operate on mathematical sequences obtained with sl.View instead of byte slices guarded by sl.Bytes. This removes all permission threading from those lemmas, including the recursive fraction-halving tricks; boundary lemmas and CorrectlyDecoded* keep their buffer arguments and take views internally. The SetInfoField and SetHopField proofs are restructured around a single SplitRange/ CombineRange pair over the written field, replacing the previous decomposition of the buffer into per-infofield and per-segment predicate instances. Base/MetaHdr specs (EqAbsHeader, DecodeFromBytesSpec, SerializeToSpec) are expressed over views as well. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/scion/base.go | 22 +- pkg/slayers/path/scion/base_spec.gobra | 61 +-- .../path/scion/info_hop_setter_lemmas.gobra | 438 ++++++++++-------- pkg/slayers/path/scion/raw.go | 302 +++++++----- pkg/slayers/path/scion/raw_spec.gobra | 352 ++++++-------- pkg/slayers/path/scion/widen-lemma.gobra | 144 +++--- verification/utils/seqs/seqs.gobra | 21 + 7 files changed, 675 insertions(+), 665 deletions(-) diff --git a/pkg/slayers/path/scion/base.go b/pkg/slayers/path/scion/base.go index 1e0b48d53..cd58ec1f9 100644 --- a/pkg/slayers/path/scion/base.go +++ b/pkg/slayers/path/scion/base.go @@ -83,10 +83,10 @@ type Base struct { // @ ensures r == nil ==> // @ s.Mem() && // @ s.GetBase().WeaklyValid() && -// @ s.DecodeFromBytesSpec(data) +// @ s.DecodeFromBytesSpec(sl.View(data, 0, len(data))) // @ ensures len(data) < MetaLen ==> r != nil // posts for IO: -// @ ensures r == nil ==> s.GetBase().EqAbsHeader(data) +// @ ensures r == nil ==> s.GetBase().EqAbsHeader(sl.View(data, 0, len(data))) // @ decreases func (s *Base) DecodeFromBytes(data []byte) (r error) { // PathMeta takes care of bounds check. @@ -149,8 +149,8 @@ func (s *Base) DecodeFromBytes(data []byte) (r error) { //@ defer fold s.NonInitMem() return serrors.New("NumHops too large", "NumHops", s.NumHops, "Maximum", MaxHops) } - //@ assert s.PathMeta.EqAbsHeader(data) - //@ assert s.EqAbsHeader(data) + //@ assert s.PathMeta.EqAbsHeader(sl.View(data, 0, len(data))) + //@ assert s.EqAbsHeader(sl.View(data, 0, len(data))) //@ fold s.Mem() return nil } @@ -253,7 +253,7 @@ type MetaHdr struct { // @ preserves acc(sl.Bytes(raw, 0, len(raw)), R50) // @ ensures (len(raw) >= MetaLen) == (e == nil) // @ ensures e == nil ==> m.InBounds() -// @ ensures e == nil ==> m.DecodeFromBytesSpec(raw) +// @ ensures e == nil ==> m.DecodeFromBytesSpec(sl.View(raw, 0, len(raw))) // @ ensures e != nil ==> e.ErrorMem() // @ decreases func (m *MetaHdr) DecodeFromBytes(raw []byte) (e error) { @@ -261,6 +261,7 @@ func (m *MetaHdr) DecodeFromBytes(raw []byte) (e error) { // (VerifiedSCION) added cast, otherwise Gobra cannot verify call return serrors.New("MetaHdr raw too short", "expected", int(MetaLen), "actual", int(len(raw))) } + //@ ghost v := sl.View(raw, 0, len(raw)) //@ unfold acc(sl.Bytes(raw, 0, len(raw)), R50) line := binary.BigEndian.Uint32(raw) m.CurrINF = uint8(line >> 30) @@ -273,7 +274,9 @@ func (m *MetaHdr) DecodeFromBytes(raw []byte) (e error) { //@ bit.And3fAtMost64(uint8(line>>12)) //@ bit.And3fAtMost64(uint8(line>>6)) //@ bit.And3fAtMost64(uint8(line)) + //@ assert v[0] == raw[0] && v[1] == raw[1] && v[2] == raw[2] && v[3] == raw[3] //@ fold acc(sl.Bytes(raw, 0, len(raw)), R50) + //@ assert line == binary.BigEndian.Uint32Spec(v[0], v[1], v[2], v[3]) return nil } @@ -283,7 +286,7 @@ func (m *MetaHdr) DecodeFromBytes(raw []byte) (e error) { // @ preserves acc(m, R50) // @ preserves sl.Bytes(b, 0, len(b)) // @ ensures e == nil -// @ ensures m.SerializeToSpec(b) +// @ ensures m.SerializeToSpec(sl.View(b, 0, len(b))) // @ decreases func (m *MetaHdr) SerializeTo(b []byte) (e error) { if len(b) < MetaLen { @@ -296,7 +299,14 @@ func (m *MetaHdr) SerializeTo(b []byte) (e error) { line |= uint32(m.SegLen[2] & 0x3F) //@ unfold acc(sl.Bytes(b, 0, len(b))) binary.BigEndian.PutUint32(b, line) + //@ ghost b0 := b[0] + //@ ghost b1 := b[1] + //@ ghost b2 := b[2] + //@ ghost b3 := b[3] //@ fold acc(sl.Bytes(b, 0, len(b))) + //@ ghost v := sl.View(b, 0, len(b)) + //@ assert v[0] == b0 && v[1] == b1 && v[2] == b2 && v[3] == b3 + //@ assert binary.BigEndian.PutUint32Spec(v[0], v[1], v[2], v[3], line) return nil } diff --git a/pkg/slayers/path/scion/base_spec.gobra b/pkg/slayers/path/scion/base_spec.gobra index f99b1fc47..de76cc0db 100644 --- a/pkg/slayers/path/scion/base_spec.gobra +++ b/pkg/slayers/path/scion/base_spec.gobra @@ -18,10 +18,6 @@ package scion import ( "encoding/binary" - "github.com/scionproto/scion/pkg/slayers/path" - sl "github.com/scionproto/scion/verification/utils/slices" - - . "github.com/scionproto/scion/verification/utils/definitions" ) pred (b *Base) NonInitMem() { @@ -244,29 +240,23 @@ pure func DecodedFrom(line uint32) MetaHdr { } ghost -requires sl.Bytes(b, 0, len(b)) +requires MetaLen <= len(raw) decreases -pure func (m MetaHdr) DecodeFromBytesSpec(b []byte) bool { - return MetaLen <= len(b) && - 0 <= m.CurrINF && m.CurrINF <= 3 && +pure func (m MetaHdr) DecodeFromBytesSpec(raw seq[byte]) bool { + return 0 <= m.CurrINF && m.CurrINF <= 3 && 0 <= m.CurrHF && m.CurrHF < 64 && m.SegsInBounds() && - let lenR := len(b) in - let b0 := sl.GetByte(b, 0, lenR, 0) in - let b1 := sl.GetByte(b, 0, lenR, 1) in - let b2 := sl.GetByte(b, 0, lenR, 2) in - let b3 := sl.GetByte(b, 0, lenR, 3) in - let line := binary.BigEndian.Uint32Spec(b0, b1, b2, b3) in + let line := binary.BigEndian.Uint32Spec(raw[0], raw[1], raw[2], raw[3]) in DecodedFrom(line) == m } ghost requires s.Mem() -requires sl.Bytes(b, 0, len(b)) +requires MetaLen <= len(raw) decreases -pure func (s *Base) DecodeFromBytesSpec(b []byte) bool { +pure func (s *Base) DecodeFromBytesSpec(raw seq[byte]) bool { return unfolding s.Mem() in - s.PathMeta.DecodeFromBytesSpec(b) + s.PathMeta.DecodeFromBytesSpec(raw) } ghost @@ -296,38 +286,29 @@ pure func (m MetaHdr) SerializedToLine() uint32 { } ghost -requires sl.Bytes(b, 0, len(b)) +requires MetaLen <= len(raw) decreases -pure func (m MetaHdr) SerializeToSpec(b []byte) bool { - return MetaLen <= len(b) && - let lenR := len(b) in - let b0 := sl.GetByte(b, 0, lenR, 0) in - let b1 := sl.GetByte(b, 0, lenR, 1) in - let b2 := sl.GetByte(b, 0, lenR, 2) in - let b3 := sl.GetByte(b, 0, lenR, 3) in - let v := m.SerializedToLine() in - binary.BigEndian.PutUint32Spec(b0, b1, b2, b3, v) +pure func (m MetaHdr) SerializeToSpec(raw seq[byte]) bool { + return let v := m.SerializedToLine() in + binary.BigEndian.PutUint32Spec(raw[0], raw[1], raw[2], raw[3], v) } ghost -requires sl.Bytes(ub, 0, len(ub)) decreases -pure func (s Base) EqAbsHeader(ub []byte) bool { - // we compute the sublice ub[:MetaLen] inside this function instead - // of expecting the correct subslice to be passed, otherwise this function - // becomes too cumbersome to use in calls from (*Raw).EqAbsHeader due to the - // lack of a folding expression. Same goes for MetaHdr.EqAbsHeader. - return MetaLen <= len(ub) && - s == RawBytesToBase(ub) +pure func (s Base) EqAbsHeader(raw seq[byte]) bool { + // we take the whole view of the packet instead of expecting the + // subsequence raw[:MetaLen] to be passed, otherwise this function + // becomes too cumbersome to use in calls from (*Raw).EqAbsHeader. + // Same goes for MetaHdr.EqAbsHeader. + return MetaLen <= len(raw) && + s == RawBytesToBase(raw) } ghost -requires sl.Bytes(ub, 0, len(ub)) decreases -pure func (s MetaHdr) EqAbsHeader(ub []byte) bool { - return MetaLen <= len(ub) && - unfolding sl.Bytes(ub, 0, len(ub)) in - s == DecodedFrom(binary.BigEndian.Uint32(ub[:MetaLen])) +pure func (s MetaHdr) EqAbsHeader(raw seq[byte]) bool { + return MetaLen <= len(raw) && + s == DecodedFrom(binary.BigEndian.Uint32Spec(raw[0], raw[1], raw[2], raw[3])) } /** Lemma proven in /VerifiedSCION/verification/utils/bitwise/proofs.dfy **/ diff --git a/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra b/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra index 886d6c3d2..7ae16a79e 100644 --- a/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra +++ b/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra @@ -18,18 +18,17 @@ package scion import ( "github.com/scionproto/scion/pkg/slayers/path" - . "verification/utils/definitions" sl "verification/utils/slices" "verification/io" ) /*** This file contains helpful lemmas for proving SetInfoField and SetHopfield. ***/ // Our abstract translation functions (CurrSeg, LeftSeg, RightSeg, MidSeg) are defined based on the -// entire byte slice of the concrete packet. This approach makes proving updates to the bytes very difficult. -// In this file, we introduce new translation functions that rely only on the hopfields byte slice and -// the infofield of a segment. We prove that these new functions are equivalent to the original ones -// and can be translated to each other. With these new functions, the proofs for SetInfoField and SetHopfield -// are greatly simplified. +// view of the entire packet. This approach makes proving updates to the bytes very difficult. +// In this file, we introduce new translation functions that rely only on the view of the hopfields +// of a segment and the infofield of that segment. We prove that these new functions are equivalent +// to the original ones and can be translated to each other. With these new functions, the proofs +// for SetInfoField and SetHopfield are greatly simplified. // InfofieldByteSlice returns the byte slice of the infofield corresponding to the @@ -48,6 +47,21 @@ pure func InfofieldByteSlice(raw []byte, currInfIdx int) ([]byte) { raw[infOffset:infOffset + path.InfoLen] } +// InfofieldByteSeq is the view-level analogue of InfofieldByteSlice: it returns +// the bytes of the infofield corresponding to the specified currInfIdx argument +// in the view of the packet. +ghost +requires 0 <= currInfIdx +requires path.InfoFieldOffset(currInfIdx, MetaLen) + path.InfoLen <= len(raw) +ensures len(res) == path.InfoLen +decreases +pure func InfofieldByteSeq(raw seq[byte], currInfIdx int) (res seq[byte]) { + return let infOffset := currInfIdx == 4 ? + path.InfoFieldOffset(0, MetaLen) : + path.InfoFieldOffset(currInfIdx, MetaLen) in + raw[infOffset:infOffset + path.InfoLen] +} + // HopfieldsStartIdx returns index of the first byte of the hopfields of a segment // specified by the currInfIdx argument. Although a packet can have only three segments, // we use currInfIdx == 4 to represent the first segment in our translation from @@ -65,7 +79,7 @@ pure func HopfieldsStartIdx(currInfIdx int, segs io.SegLens) int { infOffset + (segs.Seg1Len + segs.Seg2Len) * path.HopLen } -// HopfieldsStartIdx returns index of the last byte of the hopfields of a segment +// HopfieldsEndIdx returns index of the last byte of the hopfields of a segment // specified by the currInfIdx argument. Although a packet can have only three segments, // we use currInfIdx == 4 to represent the first segment in our translation from // concrete packets to abstract packets. This requires the special case that @@ -82,7 +96,7 @@ pure func HopfieldsEndIdx(currInfIdx int, segs io.SegLens) int { infOffset + (segs.Seg1Len + segs.Seg2Len + segs.Seg3Len) * path.HopLen } -// HopfieldsStartIdx returns returns the byte slice of the hopfields of a segment +// HopfieldsByteSlice returns the byte slice of the hopfields of a segment // specified by the currInfIdx argument. Although a packet can have only three segments, // we use currInfIdx == 4 to represent the first segment in our translation from // concrete packets to abstract packets. This requires the special case that @@ -93,9 +107,22 @@ requires 0 <= currInfIdx requires PktLen(segs, MetaLen) <= len(raw) decreases pure func HopfieldsByteSlice(raw []byte, currInfIdx int, segs io.SegLens) ([]byte) { - return let numInf := segs.NumInfoFields() in - let infOffset := path.InfoFieldOffset(numInf, MetaLen) in - let start := HopfieldsStartIdx(currInfIdx, segs) in + return let start := HopfieldsStartIdx(currInfIdx, segs) in + let end := HopfieldsEndIdx(currInfIdx, segs) in + raw[start:end] +} + +// HopfieldsByteSeq is the view-level analogue of HopfieldsByteSlice: it returns +// the bytes of the hopfields of the segment specified by the currInfIdx argument +// in the view of the packet. +ghost +requires segs.Valid() +requires 0 <= currInfIdx +requires PktLen(segs, MetaLen) <= len(raw) +ensures len(res) == HopfieldsEndIdx(currInfIdx, segs) - HopfieldsStartIdx(currInfIdx, segs) +decreases +pure func HopfieldsByteSeq(raw seq[byte], currInfIdx int, segs io.SegLens) (res seq[byte]) { + return let start := HopfieldsStartIdx(currInfIdx, segs) in let end := HopfieldsEndIdx(currInfIdx, segs) in raw[start:end] } @@ -111,17 +138,39 @@ ensures acc(sl.Bytes(HopfieldsByteSlice(raw, 0, segs), 0, segs.Seg1Len * path.H ensures acc(sl.Bytes(HopfieldsByteSlice(raw, 1, segs), 0, segs.Seg2Len * path.HopLen), p) ensures acc(sl.Bytes(HopfieldsByteSlice(raw, 2, segs), 0, segs.Seg3Len * path.HopLen), p) ensures acc(sl.Bytes(raw[HopfieldsEndIdx(2, segs):], 0, len(raw[HopfieldsEndIdx(2, segs):])), p) +ensures sl.View(raw[:HopfieldsStartIdx(0, segs)], 0, HopfieldsStartIdx(0, segs)) == + old(sl.View(raw, 0, len(raw)))[:HopfieldsStartIdx(0, segs)] +ensures sl.View(HopfieldsByteSlice(raw, 0, segs), 0, segs.Seg1Len * path.HopLen) == + old(sl.View(raw, 0, len(raw)))[HopfieldsStartIdx(0, segs):HopfieldsEndIdx(0, segs)] +ensures sl.View(HopfieldsByteSlice(raw, 1, segs), 0, segs.Seg2Len * path.HopLen) == + old(sl.View(raw, 0, len(raw)))[HopfieldsStartIdx(1, segs):HopfieldsEndIdx(1, segs)] +ensures sl.View(HopfieldsByteSlice(raw, 2, segs), 0, segs.Seg3Len * path.HopLen) == + old(sl.View(raw, 0, len(raw)))[HopfieldsStartIdx(2, segs):HopfieldsEndIdx(2, segs)] +ensures sl.View(raw[HopfieldsEndIdx(2, segs):], 0, len(raw) - HopfieldsEndIdx(2, segs)) == + old(sl.View(raw, 0, len(raw)))[HopfieldsEndIdx(2, segs):] decreases func SliceBytesIntoSegments(raw []byte, segs io.SegLens, p perm) { - sl.SplitByIndex_Bytes(raw, 0, len(raw), HopfieldsStartIdx(0, segs), p) - sl.SplitByIndex_Bytes(raw, HopfieldsStartIdx(0, segs), len(raw), HopfieldsEndIdx(0, segs), p) - sl.SplitByIndex_Bytes(raw, HopfieldsStartIdx(1, segs), len(raw), HopfieldsEndIdx(1, segs), p) - sl.SplitByIndex_Bytes(raw, HopfieldsStartIdx(2, segs), len(raw), HopfieldsEndIdx(2, segs), p) - sl.Reslice_Bytes(raw, 0, HopfieldsStartIdx(0, segs), p) - sl.Reslice_Bytes(raw, HopfieldsStartIdx(0, segs), HopfieldsEndIdx(0, segs), p) - sl.Reslice_Bytes(raw, HopfieldsStartIdx(1, segs), HopfieldsEndIdx(1, segs), p) - sl.Reslice_Bytes(raw, HopfieldsStartIdx(2, segs), HopfieldsEndIdx(2, segs), p) - sl.Reslice_Bytes(raw, HopfieldsEndIdx(2, segs), len(raw), p) + v := sl.View(raw, 0, len(raw)) + hs0 := HopfieldsStartIdx(0, segs) + he0 := HopfieldsEndIdx(0, segs) + he1 := HopfieldsEndIdx(1, segs) + he2 := HopfieldsEndIdx(2, segs) + sl.SplitByIndex_Bytes(raw, 0, len(raw), hs0, p) + assert sl.View(raw, hs0, len(raw)) == v[hs0:] + sl.SplitByIndex_Bytes(raw, hs0, len(raw), he0, p) + assert v[hs0:][:he0 - hs0] == v[hs0:he0] + assert v[hs0:][he0 - hs0:] == v[he0:] + sl.SplitByIndex_Bytes(raw, he0, len(raw), he1, p) + assert v[he0:][:he1 - he0] == v[he0:he1] + assert v[he0:][he1 - he0:] == v[he1:] + sl.SplitByIndex_Bytes(raw, he1, len(raw), he2, p) + assert v[he1:][:he2 - he1] == v[he1:he2] + assert v[he1:][he2 - he1:] == v[he2:] + sl.Reslice_Bytes(raw, 0, hs0, p) + sl.Reslice_Bytes(raw, hs0, he0, p) + sl.Reslice_Bytes(raw, he0, he1, p) + sl.Reslice_Bytes(raw, he1, he2, p) + sl.Reslice_Bytes(raw, he2, len(raw), p) } // CombineBytesFromSegments combines the three hopfield segments of a packet into a single slice of bytes. @@ -135,17 +184,37 @@ requires acc(sl.Bytes(HopfieldsByteSlice(raw, 1, segs), 0, segs.Seg2Len*path.Hop requires acc(sl.Bytes(HopfieldsByteSlice(raw, 2, segs), 0, segs.Seg3Len*path.HopLen), p) requires acc(sl.Bytes(raw[HopfieldsEndIdx(2, segs):], 0, len(raw[HopfieldsEndIdx(2, segs):])), p) ensures acc(sl.Bytes(raw, 0, len(raw)), p) +ensures sl.View(raw, 0, len(raw))[:HopfieldsStartIdx(0, segs)] == + old(sl.View(raw[:HopfieldsStartIdx(0, segs)], 0, HopfieldsStartIdx(0, segs))) +ensures sl.View(raw, 0, len(raw))[HopfieldsStartIdx(0, segs):HopfieldsEndIdx(0, segs)] == + old(sl.View(HopfieldsByteSlice(raw, 0, segs), 0, segs.Seg1Len*path.HopLen)) +ensures sl.View(raw, 0, len(raw))[HopfieldsStartIdx(1, segs):HopfieldsEndIdx(1, segs)] == + old(sl.View(HopfieldsByteSlice(raw, 1, segs), 0, segs.Seg2Len*path.HopLen)) +ensures sl.View(raw, 0, len(raw))[HopfieldsStartIdx(2, segs):HopfieldsEndIdx(2, segs)] == + old(sl.View(HopfieldsByteSlice(raw, 2, segs), 0, segs.Seg3Len*path.HopLen)) +ensures sl.View(raw, 0, len(raw))[HopfieldsEndIdx(2, segs):] == + old(sl.View(raw[HopfieldsEndIdx(2, segs):], 0, len(raw) - HopfieldsEndIdx(2, segs))) decreases func CombineBytesFromSegments(raw []byte, segs io.SegLens, p perm) { - sl.Unslice_Bytes(raw, HopfieldsEndIdx(2, segs), len(raw), p) - sl.Unslice_Bytes(raw, HopfieldsStartIdx(2, segs), HopfieldsEndIdx(2, segs), p) - sl.Unslice_Bytes(raw, HopfieldsStartIdx(1, segs), HopfieldsEndIdx(1, segs), p) - sl.Unslice_Bytes(raw, HopfieldsStartIdx(0, segs), HopfieldsEndIdx(0, segs), p) - sl.Unslice_Bytes(raw, 0, HopfieldsStartIdx(0, segs), p) - sl.CombineAtIndex_Bytes(raw, HopfieldsStartIdx(2, segs), len(raw), HopfieldsEndIdx(2, segs), p) - sl.CombineAtIndex_Bytes(raw, HopfieldsStartIdx(1, segs), len(raw), HopfieldsEndIdx(1, segs), p) - sl.CombineAtIndex_Bytes(raw, HopfieldsStartIdx(0, segs), len(raw), HopfieldsEndIdx(0, segs), p) - sl.CombineAtIndex_Bytes(raw, 0, len(raw), HopfieldsStartIdx(0, segs), p) + hs0 := HopfieldsStartIdx(0, segs) + he0 := HopfieldsEndIdx(0, segs) + he1 := HopfieldsEndIdx(1, segs) + he2 := HopfieldsEndIdx(2, segs) + sl.Unslice_Bytes(raw, he2, len(raw), p) + sl.Unslice_Bytes(raw, he1, he2, p) + sl.Unslice_Bytes(raw, he0, he1, p) + sl.Unslice_Bytes(raw, hs0, he0, p) + sl.Unslice_Bytes(raw, 0, hs0, p) + sl.CombineAtIndex_Bytes(raw, he1, len(raw), he2, p) + sl.CombineAtIndex_Bytes(raw, he0, len(raw), he1, p) + sl.CombineAtIndex_Bytes(raw, hs0, len(raw), he0, p) + sl.CombineAtIndex_Bytes(raw, 0, len(raw), hs0, p) + v := sl.View(raw, 0, len(raw)) + assert v[:hs0] == old(sl.View(raw[:hs0], 0, hs0)) + assert v[hs0:he0] == old(sl.View(raw[hs0:he0], 0, he0 - hs0)) + assert v[he0:he1] == old(sl.View(raw[he0:he1], 0, he1 - he0)) + assert v[he1:he2] == old(sl.View(raw[he1:he2], 0, he2 - he1)) + assert v[he2:] == old(sl.View(raw[he2:], 0, len(raw) - he2)) } // SliceBytesIntoInfoFields splits the raw bytes of a packet into its infofields @@ -160,8 +229,18 @@ ensures acc(sl.Bytes(InfofieldByteSlice(raw, 0), 0, path.InfoLen), p) ensures 1 < numInf ==> acc(sl.Bytes(InfofieldByteSlice(raw, 1), 0, path.InfoLen), p) ensures 2 < numInf ==> acc(sl.Bytes(InfofieldByteSlice(raw, 2), 0, path.InfoLen), p) ensures acc(sl.Bytes(raw[HopfieldsStartIdx(0, segs):], 0, len(raw[HopfieldsStartIdx(0, segs):])), p) +ensures sl.View(raw[:MetaLen], 0, MetaLen) == old(sl.View(raw, 0, len(raw)))[:MetaLen] +ensures sl.View(InfofieldByteSlice(raw, 0), 0, path.InfoLen) == + old(sl.View(raw, 0, len(raw)))[path.InfoFieldOffset(0, MetaLen):path.InfoFieldOffset(0, MetaLen) + path.InfoLen] +ensures 1 < numInf ==> sl.View(InfofieldByteSlice(raw, 1), 0, path.InfoLen) == + old(sl.View(raw, 0, len(raw)))[path.InfoFieldOffset(1, MetaLen):path.InfoFieldOffset(1, MetaLen) + path.InfoLen] +ensures 2 < numInf ==> sl.View(InfofieldByteSlice(raw, 2), 0, path.InfoLen) == + old(sl.View(raw, 0, len(raw)))[path.InfoFieldOffset(2, MetaLen):path.InfoFieldOffset(2, MetaLen) + path.InfoLen] +ensures sl.View(raw[HopfieldsStartIdx(0, segs):], 0, len(raw) - HopfieldsStartIdx(0, segs)) == + old(sl.View(raw, 0, len(raw)))[HopfieldsStartIdx(0, segs):] decreases func SliceBytesIntoInfoFields(raw []byte, numInf int, segs io.SegLens, p perm) { + v := sl.View(raw, 0, len(raw)) sl.SplitByIndex_Bytes(raw, 0, len(raw), MetaLen, p) sl.SplitByIndex_Bytes(raw, MetaLen, len(raw), path.InfoFieldOffset(1, MetaLen), p) sl.Reslice_Bytes(raw, 0, MetaLen, p) @@ -192,6 +271,15 @@ requires 1 < numInf ==> acc(sl.Bytes(InfofieldByteSlice(raw, 1), 0, path.InfoLen requires 2 < numInf ==> acc(sl.Bytes(InfofieldByteSlice(raw, 2), 0, path.InfoLen), p) requires acc(sl.Bytes(raw[HopfieldsStartIdx(0, segs):], 0, len(raw[HopfieldsStartIdx(0, segs):])), p) ensures acc(sl.Bytes(raw, 0, len(raw)), p) +ensures sl.View(raw, 0, len(raw))[:MetaLen] == old(sl.View(raw[:MetaLen], 0, MetaLen)) +ensures sl.View(raw, 0, len(raw))[path.InfoFieldOffset(0, MetaLen):path.InfoFieldOffset(0, MetaLen) + path.InfoLen] == + old(sl.View(InfofieldByteSlice(raw, 0), 0, path.InfoLen)) +ensures 1 < numInf ==> sl.View(raw, 0, len(raw))[path.InfoFieldOffset(1, MetaLen):path.InfoFieldOffset(1, MetaLen) + path.InfoLen] == + old(sl.View(InfofieldByteSlice(raw, 1), 0, path.InfoLen)) +ensures 2 < numInf ==> sl.View(raw, 0, len(raw))[path.InfoFieldOffset(2, MetaLen):path.InfoFieldOffset(2, MetaLen) + path.InfoLen] == + old(sl.View(InfofieldByteSlice(raw, 2), 0, path.InfoLen)) +ensures sl.View(raw, 0, len(raw))[HopfieldsStartIdx(0, segs):] == + old(sl.View(raw[HopfieldsStartIdx(0, segs):], 0, len(raw) - HopfieldsStartIdx(0, segs))) decreases func CombineBytesFromInfoFields(raw []byte, numInf int, segs io.SegLens, p perm) { sl.Unslice_Bytes(raw, HopfieldsStartIdx(0, segs), len(raw), p) @@ -213,25 +301,25 @@ func CombineBytesFromInfoFields(raw []byte, numInf int, segs io.SegLens, p perm) } // CurrSegWithInfo returns the abstract representation of the current segment of a packet. -// Unlike CurrSeg, it relies solely on the hopfield byte slice and an infofield instead of -// the entire raw bytes of the packet. This approach simplifies the verification of changes -// within a segment after updates to the packet's raw bytes. +// Unlike CurrSeg, it relies solely on the view of the hopfields of the segment and an +// infofield instead of the view of the entire packet. This approach simplifies the +// verification of changes within a segment after updates to the packet's raw bytes. ghost opaque requires 0 < SegLen requires 0 <= currHfIdx && currHfIdx <= SegLen requires SegLen * path.HopLen == len(hopfields) -requires sl.Bytes(hopfields, 0, len(hopfields)) decreases -pure func CurrSegWithInfo(hopfields []byte, currHfIdx int, SegLen int, inf io.AbsInfoField) io.Seg { +pure func CurrSegWithInfo(hopfields seq[byte], currHfIdx int, SegLen int, inf io.AbsInfoField) io.Seg { return segment(hopfields, 0, currHfIdx, inf.AInfo, inf.UInfo, inf.ConsDir, inf.Peer, SegLen) } // LeftSegWithInfo returns the abstract representation of the next segment of a packet. -// Unlike LeftSeg, it relies solely on the hopfields byte slice and an infofield instead of -// the entire bytes of the packet. Whenever the return value is not none, LeftSegWithInfo -// requires permissions to the hopfields byte slice of the segment specified by currInfIdx. +// Unlike LeftSeg, it relies solely on the view of the hopfields of the segment and an +// infofield instead of the view of the entire packet. Whenever the return value is not +// none, LeftSegWithInfo requires the bytes of the hopfields of the segment specified +// by currInfIdx. ghost opaque requires segs.Valid() @@ -240,11 +328,10 @@ requires (currInfIdx == 1 && segs.Seg2Len > 0) || let start := HopfieldsStartIdx(currInfIdx, segs) in let end := HopfieldsEndIdx(currInfIdx, segs) in inf != none[io.AbsInfoField] && - len(hopfields) == end - start && - sl.Bytes(hopfields, 0, len(hopfields)) + len(hopfields) == end - start decreases pure func LeftSegWithInfo( - hopfields []byte, + hopfields seq[byte], currInfIdx int, segs io.SegLens, inf option[io.AbsInfoField]) option[io.Seg] { @@ -256,9 +343,10 @@ pure func LeftSegWithInfo( } // RightSegWithInfo returns the abstract representation of the previous segment of a packet. -// Unlike RightSeg, it relies solely on the hopfields byte slice and an infofield instead of -// the entire bytes of the packet. Whenever the return value is not none, RightSegWithInfo -// requires permissions to the hopfields byte slice of the segment specified by currInfIdx. +// Unlike RightSeg, it relies solely on the view of the hopfields of the segment and an +// infofield instead of the view of the entire packet. Whenever the return value is not +// none, RightSegWithInfo requires the bytes of the hopfields of the segment specified +// by currInfIdx. ghost opaque requires segs.Valid() @@ -267,11 +355,10 @@ requires (currInfIdx == 0 && segs.Seg2Len > 0) || let start := HopfieldsStartIdx(currInfIdx, segs) in let end := HopfieldsEndIdx(currInfIdx, segs) in inf != none[io.AbsInfoField] && - len(hopfields) == end - start && - sl.Bytes(hopfields, 0, len(hopfields)) + len(hopfields) == end - start decreases pure func RightSegWithInfo( - hopfields []byte, + hopfields seq[byte], currInfIdx int, segs io.SegLens, inf option[io.AbsInfoField]) option[io.Seg] { @@ -283,9 +370,10 @@ pure func RightSegWithInfo( } // MidSegWithInfo returns the abstract representation of the last or first segment of a packet. -// Unlike MidSeg, it relies solely on the hopfields byte slice and an infofield instead of -// the entire bytes of the packet. Whenever the return value is not none, MidSegWithInfo -// requires permissions to the hopfields byte slice of the segment specified by currInfIdx. +// Unlike MidSeg, it relies solely on the view of the hopfields of the segment and an +// infofield instead of the view of the entire packet. Whenever the return value is not +// none, MidSegWithInfo requires the bytes of the hopfields of the segment specified +// by currInfIdx. ghost opaque requires segs.Valid() @@ -294,11 +382,10 @@ requires (segs.Seg2Len > 0 && segs.Seg3Len > 0 && let start := HopfieldsStartIdx(currInfIdx, segs) in let end := HopfieldsEndIdx(currInfIdx, segs) in inf != none[io.AbsInfoField] && - len(hopfields) == end - start && - sl.Bytes(hopfields, 0, len(hopfields)) + len(hopfields) == end - start decreases pure func MidSegWithInfo( - hopfields []byte, + hopfields seq[byte], currInfIdx int, segs io.SegLens, inf option[io.AbsInfoField]) option[io.Seg] { @@ -312,46 +399,32 @@ pure func MidSegWithInfo( // CurrSegEquality ensures that the two definitions of abstract segments, CurrSegWithInfo(..) // and CurrSeg(..), represent the same abstract segment. ghost -requires path.InfoFieldOffset(currInfIdx, MetaLen) + path.InfoLen <= offset -requires 0 < SegLen -requires offset + path.HopLen * SegLen <= len(raw) -requires 0 <= currHfIdx && currHfIdx <= SegLen -requires 0 <= currInfIdx && currInfIdx < 3 -preserves acc(sl.Bytes(raw, 0, len(raw)), R50) -preserves acc(sl.Bytes(raw[offset:offset + SegLen * path.HopLen], 0, SegLen * path.HopLen), R50) -preserves acc(sl.Bytes(InfofieldByteSlice(raw, currInfIdx), 0, path.InfoLen), R50) -ensures let inf := path.BytesToAbsInfoField(InfofieldByteSlice(raw, currInfIdx), 0) in +requires path.InfoFieldOffset(currInfIdx, MetaLen) + path.InfoLen <= offset +requires 0 < SegLen +requires offset + path.HopLen * SegLen <= len(raw) +requires 0 <= currHfIdx && currHfIdx <= SegLen +requires 0 <= currInfIdx && currInfIdx < 3 +ensures let inf := path.BytesToAbsInfoField(InfofieldByteSeq(raw, currInfIdx)) in CurrSegWithInfo(raw[offset:offset + SegLen * path.HopLen], currHfIdx, SegLen, inf) == CurrSeg(raw, offset, currInfIdx, currHfIdx, SegLen, MetaLen) decreases -func CurrSegEquality(raw []byte, offset int, currInfIdx int, currHfIdx int, SegLen int) { - infoBytes := InfofieldByteSlice(raw, currInfIdx) - inf := reveal path.BytesToAbsInfoField(infoBytes, 0) +func CurrSegEquality(raw seq[byte], offset int, currInfIdx int, currHfIdx int, SegLen int) { + infoBytes := InfofieldByteSeq(raw, currInfIdx) + inf := reveal path.BytesToAbsInfoField(infoBytes) infOffset := path.InfoFieldOffset(currInfIdx, MetaLen) - unfold acc(sl.Bytes(raw, 0, len(raw)), R56) - unfold acc(sl.Bytes(infoBytes, 0, path.InfoLen), R56) - path.BytesToAbsInfoFieldOffsetEq(raw, infOffset) - assert path.BytesToAbsInfoField(raw, infOffset) == - path.BytesToAbsInfoField(infoBytes, 0) - sl.AssertSliceOverlap(raw, infOffset, offset + SegLen * path.HopLen) + assert infoBytes == InfofieldBytes(raw, currInfIdx, MetaLen) currseg1 := reveal CurrSeg(raw, offset, currInfIdx, currHfIdx, SegLen, MetaLen) currseg2 := reveal CurrSegWithInfo(raw[offset:offset + SegLen * path.HopLen], currHfIdx, SegLen, inf) - // Establish equality of AInfo - sl.AssertSliceOverlap(raw, infOffset+2, infOffset+4) - sl.AssertSliceOverlap(raw, infOffset+4, infOffset+8) - _ := reveal path.BytesToAbsInfoField(raw, infOffset) - assert currseg1.AInfo == path.Timestamp(raw, currInfIdx, MetaLen) + // Establish equality of the infofield-derived fields + assert currseg1.AInfo == path.Timestamp(infoBytes) assert currseg2.AInfo == inf.AInfo assert currseg1.AInfo == currseg2.AInfo - - // Establish equality of Peer - assert currseg1.Peer == path.Peer(raw, currInfIdx, MetaLen) + assert currseg1.Peer == path.Peer(infoBytes) assert currseg2.Peer == inf.Peer assert currseg1.Peer == currseg2.Peer - fold acc(sl.Bytes(raw, 0, len(raw)), R56) - fold acc(sl.Bytes(infoBytes, 0, path.InfoLen), R56) + // Establish equality of the hopfields widenSegment(raw, offset, currHfIdx, inf.AInfo, inf.UInfo, inf.ConsDir, inf.Peer, SegLen, offset, offset + SegLen * path.HopLen) } @@ -359,14 +432,13 @@ func CurrSegEquality(raw []byte, offset int, currInfIdx int, currHfIdx int, SegL // UpdateCurrSegInfo proves that updating the infofield from inf1 to inf2 does not alter the hopfields // of the current segment. ghost -requires 0 < SegLen -requires 0 <= currHfIdx && currHfIdx <= SegLen -requires SegLen * path.HopLen == len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), R50) -ensures CurrSegWithInfo(raw, currHfIdx, SegLen, inf1).UpdateCurrSeg(inf2) == +requires 0 < SegLen +requires 0 <= currHfIdx && currHfIdx <= SegLen +requires SegLen * path.HopLen == len(raw) +ensures CurrSegWithInfo(raw, currHfIdx, SegLen, inf1).UpdateCurrSeg(inf2) == CurrSegWithInfo(raw, currHfIdx, SegLen, inf2) decreases -func UpdateCurrSegInfo(raw []byte, currHfIdx int, SegLen int, +func UpdateCurrSegInfo(raw seq[byte], currHfIdx int, SegLen int, inf1 io.AbsInfoField, inf2 io.AbsInfoField) { seg1 := reveal CurrSegWithInfo(raw, currHfIdx, SegLen, inf1) seg2 := reveal CurrSegWithInfo(raw, currHfIdx, SegLen, inf2) @@ -379,58 +451,42 @@ ghost requires segs.Valid() requires PktLen(segs, MetaLen) <= len(raw) requires 1 <= currInfIdx && currInfIdx < 4 -requires sl.Bytes(raw, 0, len(raw)) -requires (currInfIdx == 1 && segs.Seg2Len > 0) || - (currInfIdx == 2 && segs.Seg2Len > 0 && segs.Seg3Len > 0) ==> - let infoBytes := InfofieldByteSlice(raw, currInfIdx) in - let hopBytes := HopfieldsByteSlice(raw, currInfIdx, segs) in - sl.Bytes(infoBytes, 0, path.InfoLen) && - sl.Bytes(hopBytes, 0, len(hopBytes)) decreases -pure func LeftSegEqualitySpec(raw []byte, currInfIdx int, segs io.SegLens) bool { +pure func LeftSegEqualitySpec(raw seq[byte], currInfIdx int, segs io.SegLens) bool { return (currInfIdx == 1 && segs.Seg2Len > 0) || (currInfIdx == 2 && segs.Seg2Len > 0 && segs.Seg3Len > 0) ? - let infoBytes := InfofieldByteSlice(raw, currInfIdx) in - let hopBytes := HopfieldsByteSlice(raw, currInfIdx, segs) in - let inf := some(path.BytesToAbsInfoField(infoBytes, 0)) in + let infoBytes := InfofieldByteSeq(raw, currInfIdx) in + let hopBytes := HopfieldsByteSeq(raw, currInfIdx, segs) in + let inf := some(path.BytesToAbsInfoField(infoBytes)) in LeftSeg(raw, currInfIdx, segs, MetaLen) == LeftSegWithInfo(hopBytes, currInfIdx, segs, inf) : LeftSeg(raw, currInfIdx, segs, MetaLen) == - LeftSegWithInfo(nil, currInfIdx, segs, none[io.AbsInfoField]) + LeftSegWithInfo(seq[byte]{}, currInfIdx, segs, none[io.AbsInfoField]) } // LeftSegEquality ensures that the two definitions of abstract segments, LeftSegWithInfo(..) // and LeftSeg(..), represent the same abstract segment. // The left segment corresponds to different segments of the packet depending on the currInfIdx. -// To address this, we need to consider all possible cases of currInfIdx. This results in fairly -// complex preconditions and postconditions because, for every currInfIdx, we need an offset for -// its infofield and one for its hopfields. +// To address this, we need to consider all possible cases of currInfIdx. ghost -requires segs.Valid() -requires PktLen(segs, MetaLen) <= len(raw) -requires 1 <= currInfIdx && currInfIdx < 4 -preserves acc(sl.Bytes(raw, 0, len(raw)), R49) -preserves (currInfIdx == 1 && segs.Seg2Len > 0) || - (currInfIdx == 2 && segs.Seg2Len > 0 && segs.Seg3Len > 0) ==> - let infoBytes := InfofieldByteSlice(raw, currInfIdx) in - let hopBytes := HopfieldsByteSlice(raw, currInfIdx, segs) in - acc(sl.Bytes(infoBytes, 0, path.InfoLen), R49) && - acc(sl.Bytes(hopBytes, 0, len(hopBytes)), R49) -ensures LeftSegEqualitySpec(raw, currInfIdx, segs) +requires segs.Valid() +requires PktLen(segs, MetaLen) <= len(raw) +requires 1 <= currInfIdx && currInfIdx < 4 +ensures LeftSegEqualitySpec(raw, currInfIdx, segs) decreases -func LeftSegEquality(raw []byte, currInfIdx int, segs io.SegLens) { +func LeftSegEquality(raw seq[byte], currInfIdx int, segs io.SegLens) { reveal LeftSeg(raw, currInfIdx, segs, MetaLen) if ((currInfIdx == 1 && segs.Seg2Len > 0) || (currInfIdx == 2 && segs.Seg2Len > 0 && segs.Seg3Len > 0)) { - infoBytes := InfofieldByteSlice(raw, currInfIdx) - hopBytes := HopfieldsByteSlice(raw, currInfIdx, segs) - inf := some(reveal path.BytesToAbsInfoField(infoBytes, 0)) + infoBytes := InfofieldByteSeq(raw, currInfIdx) + hopBytes := HopfieldsByteSeq(raw, currInfIdx, segs) + inf := some(reveal path.BytesToAbsInfoField(infoBytes)) offset := HopfieldsStartIdx(currInfIdx, segs) segLen := currInfIdx == 1 ? segs.Seg2Len : segs.Seg3Len reveal LeftSegWithInfo(hopBytes, currInfIdx, segs, inf) CurrSegEquality(raw, offset, currInfIdx, 0, segLen) } else { - reveal LeftSegWithInfo(nil, currInfIdx, segs, none[io.AbsInfoField]) + reveal LeftSegWithInfo(seq[byte]{}, currInfIdx, segs, none[io.AbsInfoField]) } } @@ -440,58 +496,42 @@ ghost requires segs.Valid() requires PktLen(segs, MetaLen) <= len(raw) requires -1 <= currInfIdx && currInfIdx < 2 -requires sl.Bytes(raw, 0, len(raw)) -requires (currInfIdx == 0 && segs.Seg2Len > 0) || - (currInfIdx == 1 && segs.Seg2Len > 0 && segs.Seg3Len > 0) ==> - let infoBytes := InfofieldByteSlice(raw, currInfIdx) in - let hopBytes := HopfieldsByteSlice(raw, currInfIdx, segs) in - sl.Bytes(infoBytes, 0, path.InfoLen) && - sl.Bytes(hopBytes, 0, len(hopBytes)) decreases -pure func RightSegEqualitySpec(raw []byte, currInfIdx int, segs io.SegLens) bool { +pure func RightSegEqualitySpec(raw seq[byte], currInfIdx int, segs io.SegLens) bool { return (currInfIdx == 0 && segs.Seg2Len > 0) || (currInfIdx == 1 && segs.Seg2Len > 0 && segs.Seg3Len > 0) ? - let infoBytes := InfofieldByteSlice(raw, currInfIdx) in - let hopBytes := HopfieldsByteSlice(raw, currInfIdx, segs) in - let inf := some(path.BytesToAbsInfoField(infoBytes, 0)) in + let infoBytes := InfofieldByteSeq(raw, currInfIdx) in + let hopBytes := HopfieldsByteSeq(raw, currInfIdx, segs) in + let inf := some(path.BytesToAbsInfoField(infoBytes)) in RightSeg(raw, currInfIdx, segs, MetaLen) == RightSegWithInfo(hopBytes, currInfIdx, segs, inf) : RightSeg(raw, currInfIdx, segs, MetaLen) == - RightSegWithInfo(nil, currInfIdx, segs, none[io.AbsInfoField]) + RightSegWithInfo(seq[byte]{}, currInfIdx, segs, none[io.AbsInfoField]) } // RightSegEquality ensures that the two definitions of abstract segments, RightSegWithInfo(..) // and RightSeg(..), represent the same abstract segment. // The right segment corresponds to different segments of the packet depending on the currInfIdx. -// To address this, we need to consider all possible cases of currInfIdx. This results in fairly -// complex preconditions and postconditions because, for every currInfIdx, we need an offset for -// its infofield and one for its hopfields. +// To address this, we need to consider all possible cases of currInfIdx. ghost -requires segs.Valid() -requires PktLen(segs, MetaLen) <= len(raw) -requires -1 <= currInfIdx && currInfIdx < 2 -preserves acc(sl.Bytes(raw, 0, len(raw)), R49) -preserves (currInfIdx == 0 && segs.Seg2Len > 0) || - (currInfIdx == 1 && segs.Seg2Len > 0 && segs.Seg3Len > 0) ==> - let infoBytes := InfofieldByteSlice(raw, currInfIdx) in - let hopBytes := HopfieldsByteSlice(raw, currInfIdx, segs) in - acc(sl.Bytes(infoBytes, 0, path.InfoLen), R49) && - acc(sl.Bytes(hopBytes, 0, len(hopBytes)), R49) -ensures RightSegEqualitySpec(raw, currInfIdx, segs) +requires segs.Valid() +requires PktLen(segs, MetaLen) <= len(raw) +requires -1 <= currInfIdx && currInfIdx < 2 +ensures RightSegEqualitySpec(raw, currInfIdx, segs) decreases -func RightSegEquality(raw []byte, currInfIdx int, segs io.SegLens) { +func RightSegEquality(raw seq[byte], currInfIdx int, segs io.SegLens) { reveal RightSeg(raw, currInfIdx, segs, MetaLen) if ((currInfIdx == 0 && segs.Seg2Len > 0) || (currInfIdx == 1 && segs.Seg2Len > 0 && segs.Seg3Len > 0)) { - infoBytes := InfofieldByteSlice(raw, currInfIdx) - hopBytes := HopfieldsByteSlice(raw, currInfIdx, segs) - inf := some(reveal path.BytesToAbsInfoField(infoBytes, 0)) + infoBytes := InfofieldByteSeq(raw, currInfIdx) + hopBytes := HopfieldsByteSeq(raw, currInfIdx, segs) + inf := some(reveal path.BytesToAbsInfoField(infoBytes)) offset := HopfieldsStartIdx(currInfIdx, segs) segLen := currInfIdx == 0 ? segs.Seg1Len : segs.Seg2Len reveal RightSegWithInfo(hopBytes, currInfIdx, segs, inf) CurrSegEquality(raw, offset, currInfIdx, segLen, segLen) } else { - reveal RightSegWithInfo(nil, currInfIdx, segs, none[io.AbsInfoField]) + reveal RightSegWithInfo(seq[byte]{}, currInfIdx, segs, none[io.AbsInfoField]) } } @@ -501,63 +541,47 @@ ghost requires segs.Valid() requires PktLen(segs, MetaLen) <= len(raw) requires 2 <= currInfIdx && currInfIdx < 5 -requires sl.Bytes(raw, 0, len(raw)) -requires (segs.Seg2Len > 0 && segs.Seg3Len > 0 && - (currInfIdx == 2 || currInfIdx == 4)) ==> - let infoBytes := InfofieldByteSlice(raw, currInfIdx) in - let hopBytes := HopfieldsByteSlice(raw, currInfIdx, segs) in - sl.Bytes(infoBytes, 0, path.InfoLen) && - sl.Bytes(hopBytes, 0, len(hopBytes)) decreases -pure func MidSegEqualitySpec(raw []byte, currInfIdx int, segs io.SegLens) bool { +pure func MidSegEqualitySpec(raw seq[byte], currInfIdx int, segs io.SegLens) bool { return (segs.Seg2Len > 0 && segs.Seg3Len > 0 && (currInfIdx == 2 || currInfIdx == 4)) ? - let infoBytes := InfofieldByteSlice(raw, currInfIdx) in - let hopBytes := HopfieldsByteSlice(raw, currInfIdx, segs) in - let inf := some(path.BytesToAbsInfoField(infoBytes, 0)) in + let infoBytes := InfofieldByteSeq(raw, currInfIdx) in + let hopBytes := HopfieldsByteSeq(raw, currInfIdx, segs) in + let inf := some(path.BytesToAbsInfoField(infoBytes)) in MidSeg(raw, currInfIdx, segs, MetaLen) == MidSegWithInfo(hopBytes, currInfIdx, segs, inf) : MidSeg(raw, currInfIdx, segs, MetaLen) == - MidSegWithInfo(nil, currInfIdx, segs, none[io.AbsInfoField]) + MidSegWithInfo(seq[byte]{}, currInfIdx, segs, none[io.AbsInfoField]) } // MidSegEquality ensures that the two definitions of abstract segments, MidSegWithInfo(..) // and MidSeg(..), represent the same abstract segment. // The mid segment corresponds to different segments of the packet depending on the currInfIdx. -// To address this, we need to consider all possible cases of currInfIdx. This results in fairly -// complex preconditions and postconditions because, for every currInfIdx, we need an offset for -// its infofield and one for its hopfields. +// To address this, we need to consider all possible cases of currInfIdx. ghost -requires segs.Valid() -requires PktLen(segs, MetaLen) <= len(raw) -requires 2 <= currInfIdx && currInfIdx < 5 -preserves acc(sl.Bytes(raw, 0, len(raw)), R49) -preserves (segs.Seg2Len > 0 && segs.Seg3Len > 0 && - (currInfIdx == 2 || currInfIdx == 4)) ==> - let infoBytes := InfofieldByteSlice(raw, currInfIdx) in - let hopBytes := HopfieldsByteSlice(raw, currInfIdx, segs) in - acc(sl.Bytes(infoBytes, 0, path.InfoLen), R49) && - acc(sl.Bytes(hopBytes, 0, len(hopBytes)), R49) -ensures MidSegEqualitySpec(raw, currInfIdx, segs) +requires segs.Valid() +requires PktLen(segs, MetaLen) <= len(raw) +requires 2 <= currInfIdx && currInfIdx < 5 +ensures MidSegEqualitySpec(raw, currInfIdx, segs) decreases -func MidSegEquality(raw []byte, currInfIdx int, segs io.SegLens) { +func MidSegEquality(raw seq[byte], currInfIdx int, segs io.SegLens) { reveal MidSeg(raw, currInfIdx, segs, MetaLen) if (currInfIdx == 4 && segs.Seg2Len > 0 && segs.Seg3Len > 0) { - infoBytes := InfofieldByteSlice(raw, 0) - hopBytes := HopfieldsByteSlice(raw, 0, segs) - inf := some(reveal path.BytesToAbsInfoField(infoBytes, 0)) + infoBytes := InfofieldByteSeq(raw, 0) + hopBytes := HopfieldsByteSeq(raw, 0, segs) + inf := some(reveal path.BytesToAbsInfoField(infoBytes)) offset := HopfieldsStartIdx(currInfIdx, segs) reveal MidSegWithInfo(hopBytes, currInfIdx, segs, inf) CurrSegEquality(raw, offset, 0, segs.Seg1Len, segs.Seg1Len) } else if (currInfIdx == 2 && segs.Seg2Len > 0 && segs.Seg3Len > 0) { - infoBytes := InfofieldByteSlice(raw, currInfIdx) - hopBytes := HopfieldsByteSlice(raw, currInfIdx, segs) - inf := some(reveal path.BytesToAbsInfoField(infoBytes, 0)) + infoBytes := InfofieldByteSeq(raw, currInfIdx) + hopBytes := HopfieldsByteSeq(raw, currInfIdx, segs) + inf := some(reveal path.BytesToAbsInfoField(infoBytes)) offset := HopfieldsStartIdx(currInfIdx, segs) reveal MidSegWithInfo(hopBytes, currInfIdx, segs, inf) CurrSegEquality(raw, offset, currInfIdx, 0, segs.Seg3Len) } else { - reveal MidSegWithInfo(nil, currInfIdx, segs, none[io.AbsInfoField]) + reveal MidSegWithInfo(seq[byte]{}, currInfIdx, segs, none[io.AbsInfoField]) } } @@ -567,45 +591,33 @@ func MidSegEquality(raw []byte, currInfIdx int, segs io.SegLens) { ghost requires 0 <= currHfIdx && currHfIdx < segLen requires segLen * path.HopLen == len(hopfields) -requires sl.Bytes(hopfields, 0, len(hopfields)) -requires let currHfStart := currHfIdx * path.HopLen in - let currHfEnd := currHfStart + path.HopLen in - sl.Bytes(hopfields[:currHfStart], 0, currHfStart) && - sl.Bytes(hopfields[currHfStart:currHfEnd], 0, path.HopLen) && - sl.Bytes(hopfields[currHfEnd:], 0, (segLen - currHfIdx - 1) * path.HopLen) decreases -pure func BytesStoreCurrSeg(hopfields []byte, currHfIdx int, segLen int, inf io.AbsInfoField) bool { +pure func BytesStoreCurrSeg(hopfields seq[byte], currHfIdx int, segLen int, inf io.AbsInfoField) bool { return let currseg := CurrSegWithInfo(hopfields, currHfIdx, segLen, inf) in let currHfStart := currHfIdx * path.HopLen in let currHfEnd := currHfStart + path.HopLen in len(currseg.Future) > 0 && - currseg.Future[0] == path.BytesToIO_HF(hopfields[currHfStart:currHfEnd], 0, 0, path.HopLen) && - currseg.Future[1:] == hopFields(hopfields[currHfEnd:], 0, 0, (segLen - currHfIdx - 1)) && - currseg.Past == segPast(hopFields(hopfields[:currHfStart], 0, 0, currHfIdx)) && - currseg.History == segHistory(hopFields(hopfields[:currHfStart], 0, 0, currHfIdx)) && + currseg.Future[0] == path.BytesToIO_HF(hopfields[currHfStart:currHfEnd]) && + currseg.Future[1:] == hopFields(hopfields[currHfEnd:], 0, 0, (segLen - currHfIdx - 1)) && + currseg.Past == segPast(hopFields(hopfields[:currHfStart], 0, 0, currHfIdx)) && + currseg.History == segHistory(hopFields(hopfields[:currHfStart], 0, 0, currHfIdx)) && currseg.AInfo == inf.AInfo && currseg.UInfo == inf.UInfo && currseg.ConsDir == inf.ConsDir && currseg.Peer == inf.Peer } -// `EstablishBytesStoreCurrSeg` shows that the raw bytes containing all hopfields -// can be split into three slices, one that exclusively contains all past hopfields, one +// `EstablishBytesStoreCurrSeg` shows that the sequence of bytes containing all hopfields +// can be split into three subsequences, one that exclusively contains all past hopfields, one // that exclusively contains all future ones and another one for the current hopfield. // This helps in proving that the future and past hopfields remain unchanged when the // current hopfield is modified. ghost requires 0 <= currHfIdx && currHfIdx < segLen requires segLen * path.HopLen == len(hopfields) -preserves acc(sl.Bytes(hopfields, 0, len(hopfields)), R49) -preserves let currHfStart := currHfIdx * path.HopLen in - let currHfEnd := currHfStart + path.HopLen in - acc(sl.Bytes(hopfields[:currHfStart], 0, currHfStart), R49) && - acc(sl.Bytes(hopfields[currHfStart:currHfEnd], 0, path.HopLen), R49) && - acc(sl.Bytes(hopfields[currHfEnd:], 0, (segLen - currHfIdx - 1) * path.HopLen), R49) ensures BytesStoreCurrSeg(hopfields, currHfIdx, segLen, inf) decreases -func EstablishBytesStoreCurrSeg(hopfields []byte, currHfIdx int, segLen int, inf io.AbsInfoField) { +func EstablishBytesStoreCurrSeg(hopfields seq[byte], currHfIdx int, segLen int, inf io.AbsInfoField) { currseg := reveal CurrSegWithInfo(hopfields, currHfIdx, segLen, inf) currHfStart := currHfIdx * path.HopLen currHfEnd := currHfStart + path.HopLen @@ -614,9 +626,9 @@ func EstablishBytesStoreCurrSeg(hopfields []byte, currHfIdx int, segLen int, inf AlignHopsOfRawWithOffsetAndIndex(hopfields, 0, currHfIdx + 1, segLen, currHfIdx + 1) HopsFromPrefixOfRawMatchPrefixOfHops(hopfields, 0, 0, segLen, currHfIdx + 1) HopsFromPrefixOfRawMatchPrefixOfHops(hopfields, 0, 0, segLen, currHfIdx) - widenHopFields(hopfields, 0, 0, currHfIdx, 0, currHfStart, R52) - widenHopFields(hopfields, currHfEnd, 0, segLen - currHfIdx - 1, currHfEnd, segLen * path.HopLen, R52) - widenHopFields(hopfields, currHfStart, 0, 1, currHfStart, currHfEnd, R52) + widenHopFields(hopfields, 0, 0, currHfIdx, 0, currHfStart) + widenHopFields(hopfields, currHfEnd, 0, segLen - currHfIdx - 1, currHfEnd, segLen * path.HopLen) + widenHopFields(hopfields, currHfStart, 0, 1, currHfStart, currHfEnd) } // `SplitHopfields` splits the permission to the raw bytes of a segment into the permission @@ -632,6 +644,14 @@ ensures let currHfStart := currHfIdx * path.HopLen in acc(sl.Bytes(hopfields[:currHfStart], 0, currHfStart), p) && acc(sl.Bytes(hopfields[currHfStart:currHfEnd], 0, path.HopLen), p) && acc(sl.Bytes(hopfields[currHfEnd:], 0, (segLen - currHfIdx - 1) * path.HopLen), p) +ensures let currHfStart := currHfIdx * path.HopLen in + let currHfEnd := currHfStart + path.HopLen in + sl.View(hopfields[:currHfStart], 0, currHfStart) == + old(sl.View(hopfields, 0, len(hopfields)))[:currHfStart] && + sl.View(hopfields[currHfStart:currHfEnd], 0, path.HopLen) == + old(sl.View(hopfields, 0, len(hopfields)))[currHfStart:currHfEnd] && + sl.View(hopfields[currHfEnd:], 0, (segLen - currHfIdx - 1) * path.HopLen) == + old(sl.View(hopfields, 0, len(hopfields)))[currHfEnd:] decreases func SplitHopfields(hopfields []byte, currHfIdx int, segLen int, p perm) { currHfStart := currHfIdx * path.HopLen @@ -656,6 +676,12 @@ requires let currHfStart := currHfIdx * path.HopLen in acc(sl.Bytes(hopfields[currHfStart:currHfEnd], 0, path.HopLen), p) && acc(sl.Bytes(hopfields[currHfEnd:], 0, (segLen - currHfIdx - 1) * path.HopLen), p) ensures acc(sl.Bytes(hopfields, 0, len(hopfields)), p) +ensures let currHfStart := currHfIdx * path.HopLen in + let currHfEnd := currHfStart + path.HopLen in + sl.View(hopfields, 0, len(hopfields)) == + old(sl.View(hopfields[:currHfStart], 0, currHfStart)) ++ + old(sl.View(hopfields[currHfStart:currHfEnd], 0, path.HopLen)) ++ + old(sl.View(hopfields[currHfEnd:], 0, (segLen - currHfIdx - 1) * path.HopLen)) decreases func CombineHopfields(hopfields []byte, currHfIdx int, segLen int, p perm) { currHfStart := currHfIdx * path.HopLen @@ -665,4 +691,4 @@ func CombineHopfields(hopfields []byte, currHfIdx int, segLen int, p perm) { sl.Unslice_Bytes(hopfields, 0, currHfStart, p) sl.CombineAtIndex_Bytes(hopfields, currHfStart, len(hopfields), currHfEnd, p) sl.CombineAtIndex_Bytes(hopfields, 0, len(hopfields), currHfStart, p) -} \ No newline at end of file +} diff --git a/pkg/slayers/path/scion/raw.go b/pkg/slayers/path/scion/raw.go index 9947c338e..efbf569b5 100644 --- a/pkg/slayers/path/scion/raw.go +++ b/pkg/slayers/path/scion/raw.go @@ -39,7 +39,7 @@ type Raw struct { // @ ensures res == nil ==> s.Mem(data) // @ ensures res == nil ==> // @ s.GetBase(data).WeaklyValid() && -// @ s.GetBase(data).EqAbsHeader(data) +// @ s.GetBase(data).EqAbsHeader(sl.View(data, 0, len(data))) // @ ensures res != nil ==> (s.NonInitMem() && res.ErrorMem()) // @ decreases func (s *Raw) DecodeFromBytes(data []byte) (res error) { @@ -222,11 +222,11 @@ func (s *Raw) ToDecoded( /*@ ghost ubuf []byte @*/ ) (d *Decoded, err error) { // @ requires s.Mem(ubuf) // @ requires sl.Bytes(ubuf, 0, len(ubuf)) // pres for IO: -// @ requires s.GetBase(ubuf).EqAbsHeader(ubuf) -// @ requires validPktMetaHdr(ubuf) -// @ requires s.absPkt(ubuf).PathNotFullyTraversed() +// @ requires s.GetBase(ubuf).EqAbsHeader(sl.View(ubuf, 0, len(ubuf))) +// @ requires validPktMetaHdr(sl.View(ubuf, 0, len(ubuf))) +// @ requires s.absPkt(sl.View(ubuf, 0, len(ubuf))).PathNotFullyTraversed() // @ requires s.GetBase(ubuf).IsXoverSpec() ==> -// @ s.absPkt(ubuf).LeftSeg != none[io.Seg] +// @ s.absPkt(sl.View(ubuf, 0, len(ubuf))).LeftSeg != none[io.Seg] // @ ensures sl.Bytes(ubuf, 0, len(ubuf)) // @ ensures old(unfolding s.Mem(ubuf) in unfolding // @ s.Base.Mem() in (s.NumINF <= 0 || int(s.PathMeta.CurrHF) >= s.NumHops-1)) ==> r != nil @@ -235,11 +235,12 @@ func (s *Raw) ToDecoded( /*@ ghost ubuf []byte @*/ ) (d *Decoded, err error) { // @ ensures r != nil ==> r.ErrorMem() // post for IO: // @ ensures r == nil ==> -// @ s.GetBase(ubuf).EqAbsHeader(ubuf) && validPktMetaHdr(ubuf) +// @ s.GetBase(ubuf).EqAbsHeader(sl.View(ubuf, 0, len(ubuf))) && +// @ validPktMetaHdr(sl.View(ubuf, 0, len(ubuf))) // @ ensures r == nil && old(s.GetBase(ubuf).IsXoverSpec()) ==> -// @ s.absPkt(ubuf) == AbsXover(old(s.absPkt(ubuf))) +// @ s.absPkt(sl.View(ubuf, 0, len(ubuf))) == AbsXover(old(s.absPkt(sl.View(ubuf, 0, len(ubuf))))) // @ ensures r == nil && !old(s.GetBase(ubuf).IsXoverSpec()) ==> -// @ s.absPkt(ubuf) == AbsIncPath(old(s.absPkt(ubuf))) +// @ s.absPkt(sl.View(ubuf, 0, len(ubuf))) == AbsIncPath(old(s.absPkt(sl.View(ubuf, 0, len(ubuf))))) // (VerifiedSCION) the following post is technically redundant, // as it conveys information that could, in principle, be conveyed // with the previous posts. We should at some point revisit all @@ -248,8 +249,10 @@ func (s *Raw) ToDecoded( /*@ ghost ubuf []byte @*/ ) (d *Decoded, err error) { // @ s.GetBase(ubuf) == old(s.GetBase(ubuf).IncPathSpec()) // @ decreases func (s *Raw) IncPath( /*@ ghost ubuf []byte @*/ ) (r error) { + //@ ghost oldView := sl.View(ubuf, 0, len(ubuf)) //@ unfold s.Mem(ubuf) - //@ reveal validPktMetaHdr(ubuf) + //@ reveal validPktMetaHdr(oldView) + //@ ghost tail := oldView[MetaLen:] //@ unfold acc(s.Base.Mem(), R56) //@ oldCurrInfIdx := int(s.PathMeta.CurrINF) //@ oldCurrHfIdx := int(s.PathMeta.CurrHF) @@ -266,47 +269,49 @@ func (s *Raw) IncPath( /*@ ghost ubuf []byte @*/ ) (r error) { return err } //@ fold acc(s.Mem(ubuf), HalfPerm) - //@ sl.SplitRange_Bytes(ubuf, 0, MetaLen, HalfPerm) - //@ ValidPktMetaHdrSublice(ubuf, MetaLen) - //@ sl.Reslice_Bytes(ubuf, MetaLen, len(ubuf), HalfPerm) - //@ tail := ubuf[MetaLen:] - //@ unfold acc(sl.Bytes(tail, 0, len(tail)), R50) //@ oldHfIdxSeg := oldCurrHfIdx-oldPrevSegLen - //@ WidenCurrSeg(ubuf, oldOffset + MetaLen, oldCurrInfIdx, oldHfIdxSeg, oldSegLen, MetaLen, MetaLen, len(ubuf)) - //@ WidenLeftSeg(ubuf, oldCurrInfIdx + 1, oldSegs, MetaLen, MetaLen, len(ubuf)) - //@ WidenMidSeg(ubuf, oldCurrInfIdx + 2, oldSegs, MetaLen, MetaLen, len(ubuf)) - //@ WidenRightSeg(ubuf, oldCurrInfIdx - 1, oldSegs, MetaLen, MetaLen, len(ubuf)) + //@ WidenCurrSeg(oldView, oldOffset + MetaLen, oldCurrInfIdx, oldHfIdxSeg, oldSegLen, MetaLen, MetaLen, len(oldView)) + //@ WidenLeftSeg(oldView, oldCurrInfIdx + 1, oldSegs, MetaLen, MetaLen, len(oldView)) + //@ WidenMidSeg(oldView, oldCurrInfIdx + 2, oldSegs, MetaLen, MetaLen, len(oldView)) + //@ WidenRightSeg(oldView, oldCurrInfIdx - 1, oldSegs, MetaLen, MetaLen, len(oldView)) + //@ assert oldView[MetaLen:len(oldView)] == tail //@ LenCurrSeg(tail, oldOffset, oldCurrInfIdx, oldHfIdxSeg, oldSegLen) - //@ oldAbsPkt := reveal s.absPkt(ubuf) - //@ sl.SplitRange_Bytes(ubuf, 0, MetaLen, HalfPerm) + //@ oldAbsPkt := reveal s.absPkt(oldView) + //@ sl.SplitRange_Bytes(ubuf, 0, MetaLen, writePerm) //@ unfold acc(s.Base.Mem(), R2) err := s.PathMeta.SerializeTo(s.Raw[:MetaLen]) //@ assert s.Base.Valid() //@ assert s.PathMeta.InBounds() - //@ v := s.Raw[:MetaLen] - //@ b0 := sl.GetByte(v, 0, MetaLen, 0) - //@ b1 := sl.GetByte(v, 0, MetaLen, 1) - //@ b2 := sl.GetByte(v, 0, MetaLen, 2) - //@ b3 := sl.GetByte(v, 0, MetaLen, 3) + //@ ghost metaView := sl.View(s.Raw[:MetaLen], 0, MetaLen) + //@ b0 := metaView[0] + //@ b1 := metaView[1] + //@ b2 := metaView[2] + //@ b3 := metaView[3] //@ s.PathMeta.SerializeAndDeserializeLemma(b0, b1, b2, b3) - //@ assert s.PathMeta.EqAbsHeader(v) - //@ assert RawBytesToBase(v).Valid() - //@ sl.CombineRange_Bytes(ubuf, 0, MetaLen, HalfPerm) - //@ ValidPktMetaHdrSublice(ubuf, MetaLen) - //@ assert s.EqAbsHeader(ubuf) == s.PathMeta.EqAbsHeader(ubuf) - //@ assert reveal validPktMetaHdr(ubuf) + //@ assert s.PathMeta.EqAbsHeader(metaView) + //@ sl.CombineRange_Bytes(ubuf, 0, MetaLen, writePerm) + //@ ghost newView := sl.View(ubuf, 0, len(ubuf)) + //@ assert newView[:MetaLen] == metaView + //@ assert newView[MetaLen:] == tail + //@ assert newView[0] == metaView[0] && newView[1] == metaView[1] + //@ assert newView[2] == metaView[2] && newView[3] == metaView[3] + //@ assert s.PathMeta.EqAbsHeader(newView) + //@ assert RawBytesToBase(newView).Valid() + //@ assert s.EqAbsHeader(newView) == s.PathMeta.EqAbsHeader(newView) + //@ assert reveal validPktMetaHdr(newView) //@ currInfIdx := int(s.PathMeta.CurrINF) //@ currHfIdx := int(s.PathMeta.CurrHF) //@ assert currHfIdx == oldCurrHfIdx + 1 //@ ghost if(currInfIdx == oldCurrInfIdx) { //@ IncCurrSeg(tail, oldOffset, oldCurrInfIdx, oldHfIdxSeg, oldSegLen) - //@ WidenCurrSeg(ubuf, oldOffset + MetaLen, oldCurrInfIdx, oldHfIdxSeg + 1, - //@ oldSegLen, MetaLen, MetaLen, len(ubuf)) - //@ WidenLeftSeg(ubuf, oldCurrInfIdx + 1, oldSegs, MetaLen, MetaLen, len(ubuf)) - //@ WidenMidSeg(ubuf, oldCurrInfIdx + 2, oldSegs, MetaLen, MetaLen, len(ubuf)) - //@ WidenRightSeg(ubuf, oldCurrInfIdx - 1, oldSegs, MetaLen, MetaLen, len(ubuf)) - //@ assert reveal s.absPkt(ubuf) == AbsIncPath(oldAbsPkt) + //@ WidenCurrSeg(newView, oldOffset + MetaLen, oldCurrInfIdx, oldHfIdxSeg + 1, + //@ oldSegLen, MetaLen, MetaLen, len(newView)) + //@ WidenLeftSeg(newView, oldCurrInfIdx + 1, oldSegs, MetaLen, MetaLen, len(newView)) + //@ WidenMidSeg(newView, oldCurrInfIdx + 2, oldSegs, MetaLen, MetaLen, len(newView)) + //@ WidenRightSeg(newView, oldCurrInfIdx - 1, oldSegs, MetaLen, MetaLen, len(newView)) + //@ assert newView[MetaLen:len(newView)] == tail + //@ assert reveal s.absPkt(newView) == AbsIncPath(oldAbsPkt) //@ } else { //@ segLen := oldSegs.LengthOfCurrSeg(currHfIdx) //@ prevSegLen := oldSegs.LengthOfPrevSeg(currHfIdx) @@ -317,16 +322,14 @@ func (s *Raw) IncPath( /*@ ghost ubuf []byte @*/ ) (r error) { //@ XoverLeftSeg(tail, oldCurrInfIdx + 2, oldSegs) //@ XoverMidSeg(tail, oldCurrInfIdx - 1, oldSegs) //@ XoverRightSeg(tail, oldCurrInfIdx, oldCurrHfIdx, oldSegs) - //@ WidenCurrSeg(ubuf, offsetWithHops, currInfIdx, hfIdxSeg, segLen, MetaLen, MetaLen, len(ubuf)) - //@ WidenLeftSeg(ubuf, currInfIdx + 1, oldSegs, MetaLen, MetaLen, len(ubuf)) - //@ WidenMidSeg(ubuf, currInfIdx + 2, oldSegs, MetaLen, MetaLen, len(ubuf)) - //@ WidenRightSeg(ubuf, currInfIdx - 1, oldSegs, MetaLen, MetaLen, len(ubuf)) - //@ assert reveal s.absPkt(ubuf) == AbsXover(oldAbsPkt) + //@ WidenCurrSeg(newView, offsetWithHops, currInfIdx, hfIdxSeg, segLen, MetaLen, MetaLen, len(newView)) + //@ WidenLeftSeg(newView, currInfIdx + 1, oldSegs, MetaLen, MetaLen, len(newView)) + //@ WidenMidSeg(newView, currInfIdx + 2, oldSegs, MetaLen, MetaLen, len(newView)) + //@ WidenRightSeg(newView, currInfIdx - 1, oldSegs, MetaLen, MetaLen, len(newView)) + //@ assert newView[MetaLen:len(newView)] == tail + //@ assert reveal s.absPkt(newView) == AbsXover(oldAbsPkt) //@ } - //@ fold acc(sl.Bytes(tail, 0, len(tail)), R50) - //@ sl.Unslice_Bytes(ubuf, MetaLen, len(ubuf), HalfPerm) - //@ sl.CombineRange_Bytes(ubuf, 0, MetaLen, HalfPerm) //@ fold acc(s.Base.Mem(), R2) //@ fold acc(s.Mem(ubuf), HalfPerm) return err @@ -352,14 +355,17 @@ func (s *Raw) GetInfoField(idx int /*@, ghost ubuf []byte @*/) (ifield path.Info //@ fold acc(s.Base.Mem(), R12) infOffset := MetaLen + idx*path.InfoLen info /*@@@*/ := path.InfoField{} + //@ ghost v := sl.View(ubuf, 0, len(ubuf)) //@ sl.SplitRange_Bytes(ubuf, infOffset, infOffset+path.InfoLen, R20) if err := info.DecodeFromBytes(s.Raw[infOffset : infOffset+path.InfoLen]); err != nil { //@ Unreachable() return path.InfoField{}, err } - //@ sl.CombineRange_Bytes(ubuf, infOffset, infOffset+path.InfoLen, R21) - //@ path.BytesToAbsInfoFieldOffsetEq(ubuf, infOffset) - //@ sl.CombineRange_Bytes(ubuf, infOffset, infOffset+path.InfoLen, R21) + //@ ghost infoView := sl.View(s.Raw[infOffset:infOffset+path.InfoLen], 0, path.InfoLen) + //@ assert infoView[:path.InfoLen] == infoView + //@ assert infoView == v[infOffset:infOffset+path.InfoLen] + //@ sl.CombineRange_Bytes(ubuf, infOffset, infOffset+path.InfoLen, R20) + //@ assert sl.View(ubuf, 0, len(ubuf)) == v //@ fold acc(s.Mem(ubuf), R11) //@ assert reveal s.CorrectlyDecodedInfWithIdx(ubuf, idx, info) return info, nil @@ -392,23 +398,25 @@ func (s *Raw) GetCurrentInfoField( /*@ ghost ubuf []byte @*/ ) (res path.InfoFie // @ requires sl.Bytes(ubuf, 0, len(ubuf)) // @ requires acc(s.Mem(ubuf), R20) // pres for IO: -// @ requires validPktMetaHdr(ubuf) -// @ requires s.GetBase(ubuf).EqAbsHeader(ubuf) +// @ requires validPktMetaHdr(sl.View(ubuf, 0, len(ubuf))) +// @ requires s.GetBase(ubuf).EqAbsHeader(sl.View(ubuf, 0, len(ubuf))) // @ ensures acc(s.Mem(ubuf), R20) // @ ensures sl.Bytes(ubuf, 0, len(ubuf)) // @ ensures r != nil ==> r.ErrorMem() // posts for IO: // @ ensures r == nil ==> -// @ validPktMetaHdr(ubuf) && s.GetBase(ubuf).EqAbsHeader(ubuf) +// @ validPktMetaHdr(sl.View(ubuf, 0, len(ubuf))) && +// @ s.GetBase(ubuf).EqAbsHeader(sl.View(ubuf, 0, len(ubuf))) // @ ensures r == nil && idx == int(old(s.GetCurrINF(ubuf))) ==> -// @ let oldPkt := old(s.absPkt(ubuf)) in +// @ let oldPkt := old(s.absPkt(sl.View(ubuf, 0, len(ubuf)))) in // @ let newPkt := oldPkt.UpdateInfoField(info.ToAbsInfoField()) in -// @ s.absPkt(ubuf) == newPkt +// @ s.absPkt(sl.View(ubuf, 0, len(ubuf))) == newPkt // @ decreases // @ #backend[exhaleMode(1)] func (s *Raw) SetInfoField(info path.InfoField, idx int /*@, ghost ubuf []byte @*/) (r error) { //@ share info - //@ reveal validPktMetaHdr(ubuf) + //@ ghost oldView := sl.View(ubuf, 0, len(ubuf)) + //@ reveal validPktMetaHdr(oldView) //@ unfold acc(s.Mem(ubuf), R50) //@ unfold acc(s.Base.Mem(), R50) //@ currInfIdx := int(s.PathMeta.CurrINF) @@ -429,38 +437,63 @@ func (s *Raw) SetInfoField(info path.InfoField, idx int /*@, ghost ubuf []byte @ } infOffset := MetaLen + idx*path.InfoLen - //@ SliceBytesIntoInfoFields(ubuf, s.NumINF, segLens, HalfPerm) - //@ SliceBytesIntoSegments(ubuf, segLens, R40) - - //@ ValidPktMetaHdrSublice(ubuf, MetaLen) - //@ oldInfo := path.BytesToAbsInfoField(ubuf[infOffset : infOffset+path.InfoLen], 0) + //@ oldInfo := path.BytesToAbsInfoField(oldView[infOffset : infOffset+path.InfoLen]) //@ newInfo := info.ToAbsInfoField() //@ hfIdxSeg := currHfIdx-prevSegLen - //@ hopfields := ubuf[offset:offset + segLen*path.HopLen] + //@ ghost hopfields := oldView[offset:offset + segLen*path.HopLen] //@ ghost if idx == currInfIdx { - //@ CurrSegEquality(ubuf, offset, currInfIdx, hfIdxSeg, segLen) - //@ LeftSegEquality(ubuf, currInfIdx+1, segLens) - //@ MidSegEquality(ubuf, currInfIdx+2, segLens) - //@ RightSegEquality(ubuf, currInfIdx-1, segLens) + //@ CurrSegEquality(oldView, offset, currInfIdx, hfIdxSeg, segLen) + //@ LeftSegEquality(oldView, currInfIdx+1, segLens) + //@ MidSegEquality(oldView, currInfIdx+2, segLens) + //@ RightSegEquality(oldView, currInfIdx-1, segLens) //@ } - //@ reveal s.absPkt(ubuf) - //@ sl.SplitRange_Bytes(ubuf[:hopfieldOffset], infOffset, infOffset+path.InfoLen, R40) - //@ sl.SplitRange_Bytes(ubuf, infOffset, infOffset+path.InfoLen, HalfPerm-R40) + //@ reveal s.absPkt(oldView) + //@ sl.SplitRange_Bytes(ubuf, infOffset, infOffset+path.InfoLen, writePerm) ret := info.SerializeTo(s.Raw[infOffset : infOffset+path.InfoLen]) - //@ sl.CombineRange_Bytes(ubuf[:hopfieldOffset], infOffset, infOffset+path.InfoLen, R40) - //@ sl.CombineRange_Bytes(ubuf, infOffset, infOffset+path.InfoLen, HalfPerm-R40) - //@ ValidPktMetaHdrSublice(ubuf, MetaLen) - //@ assert reveal validPktMetaHdr(ubuf) + //@ ghost newInfoView := sl.View(s.Raw[infOffset:infOffset+path.InfoLen], 0, path.InfoLen) + //@ assert newInfoView[:path.InfoLen] == newInfoView + //@ assert path.BytesToAbsInfoField(newInfoView) == newInfo + //@ sl.CombineRange_Bytes(ubuf, infOffset, infOffset+path.InfoLen, writePerm) + //@ ghost newView := sl.View(ubuf, 0, len(ubuf)) + //@ assert newView[:infOffset] == oldView[:infOffset] + //@ assert newView[infOffset:infOffset+path.InfoLen] == newInfoView + //@ assert newView[infOffset+path.InfoLen:] == oldView[infOffset+path.InfoLen:] + //@ assert forall i int :: { newView[i] } 0 <= i && i < infOffset ==> + //@ newView[i] == oldView[i] + //@ assert forall i int :: { newView[i] } infOffset+path.InfoLen <= i && i < len(newView) ==> + //@ newView[i] == oldView[i] + //@ assert newView[0] == oldView[0] && newView[1] == oldView[1] && + //@ newView[2] == oldView[2] && newView[3] == oldView[3] + //@ assert RawBytesToMetaHdr(newView) == RawBytesToMetaHdr(oldView) + //@ assert RawBytesToBase(newView) == RawBytesToBase(oldView) + //@ assert reveal validPktMetaHdr(newView) //@ ghost if idx == currInfIdx { - //@ CurrSegEquality(ubuf, offset, currInfIdx, hfIdxSeg, segLen) + //@ assert newView[offset:offset + segLen*path.HopLen] == hopfields + //@ CurrSegEquality(newView, offset, currInfIdx, hfIdxSeg, segLen) //@ UpdateCurrSegInfo(hopfields, hfIdxSeg, segLen, oldInfo, newInfo) - //@ LeftSegEquality(ubuf, currInfIdx+1, segLens) - //@ MidSegEquality(ubuf, currInfIdx+2, segLens) - //@ RightSegEquality(ubuf, currInfIdx-1, segLens) - //@ reveal s.absPkt(ubuf) + //@ if (currInfIdx+1 == 1 && segLens.Seg2Len > 0) || + //@ (currInfIdx+1 == 2 && segLens.Seg2Len > 0 && segLens.Seg3Len > 0) { + //@ assert InfofieldByteSeq(newView, currInfIdx+1) == InfofieldByteSeq(oldView, currInfIdx+1) + //@ assert HopfieldsByteSeq(newView, currInfIdx+1, segLens) == HopfieldsByteSeq(oldView, currInfIdx+1, segLens) + //@ } + //@ if segLens.Seg2Len > 0 && segLens.Seg3Len > 0 && + //@ (currInfIdx+2 == 2 || currInfIdx+2 == 4) { + //@ assert InfofieldByteSeq(newView, currInfIdx+2) == InfofieldByteSeq(oldView, currInfIdx+2) + //@ assert HopfieldsByteSeq(newView, currInfIdx+2, segLens) == HopfieldsByteSeq(oldView, currInfIdx+2, segLens) + //@ } + //@ if (currInfIdx-1 == 0 && segLens.Seg2Len > 0) || + //@ (currInfIdx-1 == 1 && segLens.Seg2Len > 0 && segLens.Seg3Len > 0) { + //@ assert InfofieldByteSeq(newView, currInfIdx-1) == InfofieldByteSeq(oldView, currInfIdx-1) + //@ assert HopfieldsByteSeq(newView, currInfIdx-1, segLens) == HopfieldsByteSeq(oldView, currInfIdx-1, segLens) + //@ } + //@ LeftSegEquality(oldView, currInfIdx+1, segLens) + //@ MidSegEquality(oldView, currInfIdx+2, segLens) + //@ RightSegEquality(oldView, currInfIdx-1, segLens) + //@ LeftSegEquality(newView, currInfIdx+1, segLens) + //@ MidSegEquality(newView, currInfIdx+2, segLens) + //@ RightSegEquality(newView, currInfIdx-1, segLens) + //@ reveal s.absPkt(newView) //@ } - //@ CombineBytesFromSegments(ubuf, segLens, R40) - //@ CombineBytesFromInfoFields(ubuf, s.NumINF, segLens, HalfPerm) //@ fold acc(s.Base.Mem(), R50) //@ fold acc(s.Mem(ubuf), R50) return ret @@ -486,15 +519,17 @@ func (s *Raw) GetHopField(idx int /*@, ghost ubuf []byte @*/) (res path.HopField hopOffset := MetaLen + s.NumINF*path.InfoLen + idx*path.HopLen //@ fold acc(s.Base.Mem(), R12) hop /*@@@*/ := path.HopField{} + //@ ghost v := sl.View(ubuf, 0, len(ubuf)) //@ sl.SplitRange_Bytes(ubuf, hopOffset, hopOffset+path.HopLen, R20) if err := hop.DecodeFromBytes(s.Raw[hopOffset : hopOffset+path.HopLen]); err != nil { //@ Unreachable() return path.HopField{}, err } //@ unfold hop.Mem() - //@ sl.CombineRange_Bytes(ubuf, hopOffset, hopOffset+path.HopLen, R21) - //@ path.BytesToAbsHopFieldOffsetEq(ubuf, hopOffset) - //@ sl.CombineRange_Bytes(ubuf, hopOffset, hopOffset+path.HopLen, R21) + //@ ghost hopView := sl.View(s.Raw[hopOffset:hopOffset+path.HopLen], 0, path.HopLen) + //@ assert hopView == v[hopOffset:hopOffset+path.HopLen] + //@ sl.CombineRange_Bytes(ubuf, hopOffset, hopOffset+path.HopLen, R20) + //@ assert sl.View(ubuf, 0, len(ubuf)) == v //@ fold acc(s.Mem(ubuf), R11) //@ assert reveal s.CorrectlyDecodedHfWithIdx(ubuf, idx, hop) return hop, nil @@ -527,20 +562,20 @@ func (s *Raw) GetCurrentHopField( /*@ ghost ubuf []byte @*/ ) (res path.HopField // @ requires acc(s.Mem(ubuf), R20) // @ requires sl.Bytes(ubuf, 0, len(ubuf)) // pres for IO: -// @ requires validPktMetaHdr(ubuf) -// @ requires s.GetBase(ubuf).EqAbsHeader(ubuf) -// @ requires s.absPkt(ubuf).PathNotFullyTraversed() +// @ requires validPktMetaHdr(sl.View(ubuf, 0, len(ubuf))) +// @ requires s.GetBase(ubuf).EqAbsHeader(sl.View(ubuf, 0, len(ubuf))) +// @ requires s.absPkt(sl.View(ubuf, 0, len(ubuf))).PathNotFullyTraversed() // @ ensures acc(s.Mem(ubuf), R20) // @ ensures sl.Bytes(ubuf, 0, len(ubuf)) // @ ensures r != nil ==> r.ErrorMem() // posts for IO: // @ ensures r == nil ==> -// @ validPktMetaHdr(ubuf) && -// @ s.GetBase(ubuf).EqAbsHeader(ubuf) +// @ validPktMetaHdr(sl.View(ubuf, 0, len(ubuf))) && +// @ s.GetBase(ubuf).EqAbsHeader(sl.View(ubuf, 0, len(ubuf))) // @ ensures r == nil && idx == int(old(s.GetCurrHF(ubuf))) ==> -// @ let oldPkt := old(s.absPkt(ubuf)) in +// @ let oldPkt := old(s.absPkt(sl.View(ubuf, 0, len(ubuf)))) in // @ let newPkt := oldPkt.UpdateHopField(hop.Abs()) in -// @ s.absPkt(ubuf) == newPkt +// @ s.absPkt(sl.View(ubuf, 0, len(ubuf))) == newPkt // @ decreases // @ #backend[exhaleMode(1)] func (s *Raw) SetHopField(hop path.HopField, idx int /*@, ghost ubuf []byte @*/) (r error) { @@ -552,7 +587,8 @@ func (s *Raw) SetHopField(hop path.HopField, idx int /*@, ghost ubuf []byte @*/) // https://github.com/viperproject/gobra/issues/192 //@ assume 0 <= tmpHopField.ConsIngress && 0 <= tmpHopField.ConsEgress //@ fold acc(tmpHopField.Mem(), R9) - //@ reveal validPktMetaHdr(ubuf) + //@ ghost oldView := sl.View(ubuf, 0, len(ubuf)) + //@ reveal validPktMetaHdr(oldView) //@ unfold acc(s.Mem(ubuf), R50) //@ unfold acc(s.Base.Mem(), R50) //@ ghost currInfIdx := int(s.PathMeta.CurrINF) @@ -574,48 +610,66 @@ func (s *Raw) SetHopField(hop path.HopField, idx int /*@, ghost ubuf []byte @*/) } hopOffset := MetaLen + s.NumINF*path.InfoLen + idx*path.HopLen - //@ SliceBytesIntoSegments(ubuf, segLens, HalfPerm) - //@ SliceBytesIntoInfoFields(ubuf[:hopfieldOffset], s.NumINF, segLens, R40) - - //@ ValidPktMetaHdrSublice(ubuf, MetaLen) - //@ ghost inf := path.BytesToAbsInfoField(InfofieldByteSlice(ubuf, currInfIdx), 0) + //@ ghost inf := path.BytesToAbsInfoField(InfofieldByteSeq(oldView, currInfIdx)) //@ ghost hfIdxSeg := idx-prevSegLen - //@ ghost currHopfields := HopfieldsByteSlice(ubuf, currInfIdx, segLens) + //@ ghost currHopfields := HopfieldsByteSeq(oldView, currInfIdx, segLens) //@ ghost if idx == currHfIdx { - //@ CurrSegEquality(ubuf, offset, currInfIdx, hfIdxSeg, segLen) - //@ LeftSegEquality(ubuf, currInfIdx+1, segLens) - //@ MidSegEquality(ubuf, currInfIdx+2, segLens) - //@ RightSegEquality(ubuf, currInfIdx-1, segLens) - //@ reveal s.absPkt(ubuf) - //@ SplitHopfields(currHopfields, hfIdxSeg, segLen, R0) + //@ CurrSegEquality(oldView, offset, currInfIdx, hfIdxSeg, segLen) + //@ LeftSegEquality(oldView, currInfIdx+1, segLens) + //@ MidSegEquality(oldView, currInfIdx+2, segLens) + //@ RightSegEquality(oldView, currInfIdx-1, segLens) + //@ reveal s.absPkt(oldView) + //@ assert oldView[offset:offset+segLen*path.HopLen] == currHopfields //@ EstablishBytesStoreCurrSeg(currHopfields, hfIdxSeg, segLen, inf) - //@ SplitHopfields(currHopfields, hfIdxSeg, segLen, R0) - //@ } else { - //@ sl.SplitRange_Bytes(ubuf[offset:offset+segLen*path.HopLen], hfIdxSeg*path.HopLen, - //@ (hfIdxSeg+1)*path.HopLen, HalfPerm) //@ } - //@ sl.SplitRange_Bytes(ubuf, hopOffset, hopOffset+path.HopLen, HalfPerm) + //@ sl.SplitRange_Bytes(ubuf, hopOffset, hopOffset+path.HopLen, writePerm) ret := tmpHopField.SerializeTo(s.Raw[hopOffset : hopOffset+path.HopLen]) - //@ sl.CombineRange_Bytes(ubuf, hopOffset, hopOffset+path.HopLen, HalfPerm) - //@ ValidPktMetaHdrSublice(ubuf, MetaLen) - //@ assert reveal validPktMetaHdr(ubuf) + //@ ghost newHopView := sl.View(s.Raw[hopOffset:hopOffset+path.HopLen], 0, path.HopLen) + //@ assert path.BytesToIO_HF(newHopView) == tmpHopField.Abs() + //@ sl.CombineRange_Bytes(ubuf, hopOffset, hopOffset+path.HopLen, writePerm) + //@ ghost newView := sl.View(ubuf, 0, len(ubuf)) + //@ assert newView[:hopOffset] == oldView[:hopOffset] + //@ assert newView[hopOffset:hopOffset+path.HopLen] == newHopView + //@ assert newView[hopOffset+path.HopLen:] == oldView[hopOffset+path.HopLen:] + //@ assert forall i int :: { newView[i] } 0 <= i && i < hopOffset ==> + //@ newView[i] == oldView[i] + //@ assert forall i int :: { newView[i] } hopOffset+path.HopLen <= i && i < len(newView) ==> + //@ newView[i] == oldView[i] + //@ assert newView[0] == oldView[0] && newView[1] == oldView[1] && + //@ newView[2] == oldView[2] && newView[3] == oldView[3] + //@ assert RawBytesToMetaHdr(newView) == RawBytesToMetaHdr(oldView) + //@ assert RawBytesToBase(newView) == RawBytesToBase(oldView) + //@ assert reveal validPktMetaHdr(newView) //@ ghost if idx == currHfIdx { - //@ CombineHopfields(currHopfields, hfIdxSeg, segLen, R0) - //@ EstablishBytesStoreCurrSeg(currHopfields, hfIdxSeg, segLen, inf) - //@ CombineHopfields(currHopfields, hfIdxSeg, segLen, R0) - //@ CurrSegEquality(ubuf, offset, currInfIdx, hfIdxSeg, segLen) - //@ LeftSegEquality(ubuf, currInfIdx+1, segLens) - //@ MidSegEquality(ubuf, currInfIdx+2, segLens) - //@ RightSegEquality(ubuf, currInfIdx-1, segLens) - //@ reveal s.absPkt(ubuf) - //@ assert s.absPkt(ubuf).CurrSeg.Future == - //@ seq[io.HF]{tmpHopField.Abs()} ++ old(s.absPkt(ubuf).CurrSeg.Future[1:]) - //@ } else { - //@ sl.CombineRange_Bytes(ubuf[offset:offset+segLen*path.HopLen], hfIdxSeg*path.HopLen, - //@ (hfIdxSeg+1)*path.HopLen, HalfPerm) + //@ ghost newHopfields := HopfieldsByteSeq(newView, currInfIdx, segLens) + //@ assert InfofieldByteSeq(newView, currInfIdx) == InfofieldByteSeq(oldView, currInfIdx) + //@ assert newHopfields[:hfIdxSeg*path.HopLen] == currHopfields[:hfIdxSeg*path.HopLen] + //@ assert newHopfields[hfIdxSeg*path.HopLen:(hfIdxSeg+1)*path.HopLen] == newHopView + //@ assert newHopfields[(hfIdxSeg+1)*path.HopLen:] == currHopfields[(hfIdxSeg+1)*path.HopLen:] + //@ CurrSegEquality(newView, offset, currInfIdx, hfIdxSeg, segLen) + //@ EstablishBytesStoreCurrSeg(newHopfields, hfIdxSeg, segLen, inf) + //@ if (currInfIdx+1 == 1 && segLens.Seg2Len > 0) || + //@ (currInfIdx+1 == 2 && segLens.Seg2Len > 0 && segLens.Seg3Len > 0) { + //@ assert InfofieldByteSeq(newView, currInfIdx+1) == InfofieldByteSeq(oldView, currInfIdx+1) + //@ assert HopfieldsByteSeq(newView, currInfIdx+1, segLens) == HopfieldsByteSeq(oldView, currInfIdx+1, segLens) + //@ } + //@ if segLens.Seg2Len > 0 && segLens.Seg3Len > 0 && + //@ (currInfIdx+2 == 2 || currInfIdx+2 == 4) { + //@ assert InfofieldByteSeq(newView, currInfIdx+2) == InfofieldByteSeq(oldView, currInfIdx+2) + //@ assert HopfieldsByteSeq(newView, currInfIdx+2, segLens) == HopfieldsByteSeq(oldView, currInfIdx+2, segLens) + //@ } + //@ if (currInfIdx-1 == 0 && segLens.Seg2Len > 0) || + //@ (currInfIdx-1 == 1 && segLens.Seg2Len > 0 && segLens.Seg3Len > 0) { + //@ assert InfofieldByteSeq(newView, currInfIdx-1) == InfofieldByteSeq(oldView, currInfIdx-1) + //@ assert HopfieldsByteSeq(newView, currInfIdx-1, segLens) == HopfieldsByteSeq(oldView, currInfIdx-1, segLens) + //@ } + //@ LeftSegEquality(newView, currInfIdx+1, segLens) + //@ MidSegEquality(newView, currInfIdx+2, segLens) + //@ RightSegEquality(newView, currInfIdx-1, segLens) + //@ reveal s.absPkt(newView) + //@ assert s.absPkt(newView).CurrSeg.Future == + //@ seq[io.HF]{tmpHopField.Abs()} ++ old(s.absPkt(sl.View(ubuf, 0, len(ubuf))).CurrSeg.Future[1:]) //@ } - //@ CombineBytesFromInfoFields(ubuf[:hopfieldOffset], s.NumINF, segLens, R40) - //@ CombineBytesFromSegments(ubuf, segLens, HalfPerm) //@ fold acc(s.Base.Mem(), R50) //@ fold acc(s.Mem(ubuf), R50) return ret diff --git a/pkg/slayers/path/scion/raw_spec.gobra b/pkg/slayers/path/scion/raw_spec.gobra index 218a6a38e..a752e3fd4 100644 --- a/pkg/slayers/path/scion/raw_spec.gobra +++ b/pkg/slayers/path/scion/raw_spec.gobra @@ -47,7 +47,7 @@ requires sl.Bytes(buf, 0, len(buf)) decreases pure func (s *Raw) IsValidResultOfDecoding(buf []byte) bool { return let base := s.GetBase(buf) in - base.EqAbsHeader(buf) && + base.EqAbsHeader(sl.View(buf, 0, len(buf))) && base.WeaklyValid() } @@ -216,20 +216,32 @@ pure func PktLen(segs io.SegLens, headerOffset int) int { path.HopLen * segs.TotalHops() } +// InfofieldBytes returns the bytes of the infofield with index currInfIdx +// in the view of the packet raw bytes. +ghost +requires 0 <= currInfIdx && 0 <= headerOffset +requires path.InfoFieldOffset(currInfIdx, headerOffset) + path.InfoLen <= len(raw) +ensures len(res) == path.InfoLen +decreases +pure func InfofieldBytes(raw seq[byte], currInfIdx int, headerOffset int) (res seq[byte]) { + return let infOffset := path.InfoFieldOffset(currInfIdx, headerOffset) in + raw[infOffset:infOffset + path.InfoLen] +} + ghost requires 0 <= offset requires 0 <= currHfIdx && currHfIdx <= segLen requires offset + path.HopLen * segLen <= len(raw) -requires sl.Bytes(raw, 0, len(raw)) ensures len(res) == segLen - currHfIdx decreases segLen - currHfIdx pure func hopFields( - raw []byte, + raw seq[byte], offset int, currHfIdx int, segLen int) (res seq[io.HF]) { return currHfIdx == segLen ? seq[io.HF]{} : - let hf := path.BytesToIO_HF(raw, 0, offset + path.HopLen * currHfIdx, len(raw)) in + let start := offset + path.HopLen * currHfIdx in + let hf := path.BytesToIO_HF(raw[start:start + path.HopLen]) in seq[io.HF]{hf} ++ hopFields(raw, offset, currHfIdx + 1, segLen) } @@ -252,7 +264,6 @@ pure func segHistory(hopfields seq[io.HF]) (res seq[io.AHI]) { } ghost -requires sl.Bytes(raw, 0, len(raw)) requires 0 <= offset requires 0 < segLen requires 0 <= currHfIdx && currHfIdx <= segLen @@ -261,7 +272,7 @@ ensures len(res.Future) == segLen - currHfIdx ensures len(res.History) == currHfIdx ensures len(res.Past) == currHfIdx decreases -pure func segment(raw []byte, +pure func segment(raw seq[byte], offset int, currHfIdx int, ainfo io.Ainfo, @@ -283,7 +294,6 @@ pure func segment(raw []byte, ghost opaque -requires sl.Bytes(raw, 0, len(raw)) requires 0 <= headerOffset requires path.InfoFieldOffset(currInfIdx, headerOffset) + path.InfoLen <= offset requires 0 < segLen @@ -294,29 +304,29 @@ ensures len(res.Future) == segLen - currHfIdx ensures len(res.History) == currHfIdx ensures len(res.Past) == currHfIdx decreases -pure func CurrSeg(raw []byte, +pure func CurrSeg(raw seq[byte], offset int, currInfIdx int, currHfIdx int, segLen int, headerOffset int) (res io.Seg) { - return let ainfo := path.Timestamp(raw, currInfIdx, headerOffset) in - let consDir := path.ConsDir(raw, currInfIdx, headerOffset) in - let peer := path.Peer(raw, currInfIdx, headerOffset) in - let uinfo := path.AbsUinfo(raw, currInfIdx, headerOffset) in + return let infoBytes := InfofieldBytes(raw, currInfIdx, headerOffset) in + let ainfo := path.Timestamp(infoBytes) in + let consDir := path.ConsDir(infoBytes) in + let peer := path.Peer(infoBytes) in + let uinfo := path.AbsUinfo(infoBytes) in segment(raw, offset, currHfIdx, ainfo, uinfo, consDir, peer, segLen) } ghost opaque -requires sl.Bytes(raw, 0, len(raw)) requires 0 <= headerOffset requires segs.Valid() requires PktLen(segs, headerOffset) <= len(raw) requires 1 <= currInfIdx && currInfIdx < 4 decreases pure func LeftSeg( - raw []byte, + raw seq[byte], currInfIdx int, segs io.SegLens, headerOffset int) option[io.Seg] { @@ -330,14 +340,13 @@ pure func LeftSeg( ghost opaque -requires sl.Bytes(raw, 0, len(raw)) requires 0 <= headerOffset requires segs.Valid() requires PktLen(segs, headerOffset) <= len(raw) requires -1 <= currInfIdx && currInfIdx < 2 decreases pure func RightSeg( - raw []byte, + raw seq[byte], currInfIdx int, segs io.SegLens, headerOffset int) option[io.Seg] { @@ -351,14 +360,13 @@ pure func RightSeg( ghost opaque -requires sl.Bytes(raw, 0, len(raw)) requires 0 <= headerOffset requires segs.Valid() requires PktLen(segs, headerOffset) <= len(raw) requires 2 <= currInfIdx && currInfIdx < 5 decreases pure func MidSeg( - raw []byte, + raw seq[byte], currInfIdx int, segs io.SegLens, headerOffset int) option[io.Seg] { @@ -372,11 +380,9 @@ pure func MidSeg( ghost opaque -requires sl.Bytes(raw, 0, len(raw)) requires validPktMetaHdr(raw) decreases -// TODO: rename this to View() -pure func (s *Raw) absPkt(raw []byte) (res io.Pkt) { +pure func (s *Raw) absPkt(raw seq[byte]) (res io.Pkt) { return let _ := reveal validPktMetaHdr(raw) in let metaHdr := RawBytesToMetaHdr(raw) in let currInfIdx := int(metaHdr.CurrINF) in @@ -399,19 +405,16 @@ pure func (s *Raw) absPkt(raw []byte) (res io.Pkt) { ghost requires MetaLen <= len(raw) -requires sl.Bytes(raw, 0, len(raw)) decreases -pure func RawBytesToMetaHdr(raw []byte) MetaHdr { - return unfolding sl.Bytes(raw, 0, len(raw)) in - let hdr := binary.BigEndian.Uint32(raw[:MetaLen]) in +pure func RawBytesToMetaHdr(raw seq[byte]) MetaHdr { + return let hdr := binary.BigEndian.Uint32Spec(raw[0], raw[1], raw[2], raw[3]) in DecodedFrom(hdr) } ghost requires MetaLen <= len(raw) -requires sl.Bytes(raw, 0, len(raw)) decreases -pure func RawBytesToBase(raw []byte) Base { +pure func RawBytesToBase(raw seq[byte]) Base { return let metaHdr := RawBytesToMetaHdr(raw) in let seg1 := int(metaHdr.SegLen[0]) in let seg2 := int(metaHdr.SegLen[1]) in @@ -422,9 +425,8 @@ pure func RawBytesToBase(raw []byte) Base { ghost opaque -requires sl.Bytes(raw, 0, len(raw)) decreases -pure func validPktMetaHdr(raw []byte) bool { +pure func validPktMetaHdr(raw seq[byte]) bool { return MetaLen <= len(raw) && let metaHdr := RawBytesToMetaHdr(raw) in let seg1 := int(metaHdr.SegLen[0]) in @@ -437,39 +439,36 @@ pure func validPktMetaHdr(raw []byte) bool { PktLen(segs, MetaLen) <= len(raw) } +// The meta header of a packet only depends on its first MetaLen bytes. ghost -requires MetaLen <= idx && idx <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), R56) -preserves acc(sl.Bytes(raw[:idx], 0, idx), R56) -ensures RawBytesToMetaHdr(raw) == RawBytesToMetaHdr(raw[:idx]) -ensures RawBytesToBase(raw) == RawBytesToBase(raw[:idx]) +requires MetaLen <= idx && idx <= len(raw) +ensures RawBytesToMetaHdr(raw) == RawBytesToMetaHdr(raw[:idx]) +ensures RawBytesToBase(raw) == RawBytesToBase(raw[:idx]) decreases -func ValidPktMetaHdrSublice(raw []byte, idx int) { - reveal validPktMetaHdr(raw) - reveal validPktMetaHdr(raw[:idx]) - unfold acc(sl.Bytes(raw, 0, len(raw)), R56) - unfold acc(sl.Bytes(raw[:idx], 0, idx), R56) - assert forall i int :: { &raw[:MetaLen][i] } 0 <= i && i < MetaLen ==> - &raw[:MetaLen][i] == &raw[:idx][:MetaLen][i] - fold acc(sl.Bytes(raw, 0, len(raw)), R56) - fold acc(sl.Bytes(raw[:idx], 0, idx), R56) +func ValidPktMetaHdrSublice(raw seq[byte], idx int) { + assert raw[:idx][0] == raw[0] && + raw[:idx][1] == raw[1] && + raw[:idx][2] == raw[2] && + raw[:idx][3] == raw[3] } ghost requires acc(s.Mem(ub), R54) requires acc(sl.Bytes(ub, 0, len(ub)), R55) requires s.GetBase(ub).Valid() -requires s.GetBase(ub).EqAbsHeader(ub) +requires s.GetBase(ub).EqAbsHeader(sl.View(ub, 0, len(ub))) ensures acc(sl.Bytes(ub, 0, len(ub)), R55) ensures acc(s.Mem(ub), R54) -ensures validPktMetaHdr(ub) -ensures s.GetBase(ub).EqAbsHeader(ub) +ensures validPktMetaHdr(sl.View(ub, 0, len(ub))) +ensures s.GetBase(ub).EqAbsHeader(sl.View(ub, 0, len(ub))) decreases func (s *Raw) EstablishValidPktMetaHdr(ghost ub []byte) { + v := sl.View(ub, 0, len(ub)) + assert len(v) == len(ub) unfold acc(s.Mem(ub), R55) unfold acc(s.Base.Mem(), R56) assert MetaLen <= len(ub) - assert s.Base.GetBase() == RawBytesToBase(ub) + assert s.Base.GetBase() == RawBytesToBase(v) seg1 := int(s.Base.PathMeta.SegLen[0]) seg2 := int(s.Base.PathMeta.SegLen[1]) seg3 := int(s.Base.PathMeta.SegLen[2]) @@ -477,7 +476,7 @@ func (s *Raw) EstablishValidPktMetaHdr(ghost ub []byte) { assert 0 < seg1 assert s.GetBase(ub).NumsCompatibleWithSegLen() assert PktLen(segs, MetaLen) <= len(ub) - assert reveal validPktMetaHdr(ub) + assert reveal validPktMetaHdr(v) fold acc(s.Base.Mem(), R56) fold acc(s.Mem(ub), R55) } @@ -543,7 +542,8 @@ pure func (s *Raw) CorrectlyDecodedInfWithIdx(ub []byte, idx int, info path.Info let infOffset := MetaLen + idx*path.InfoLen in infOffset + path.InfoLen <= len(ub) && info.ToAbsInfoField() == - reveal path.BytesToAbsInfoField(ub, infOffset) + reveal path.BytesToAbsInfoField( + sl.View(ub, 0, len(ub))[infOffset:infOffset + path.InfoLen]) } ghost @@ -558,7 +558,8 @@ pure func (s *Raw) CorrectlyDecodedInf(ub []byte, info path.InfoField) bool { let infOffset := MetaLen + int(s.Base.PathMeta.CurrINF) * path.InfoLen in infOffset + path.InfoLen <= len(ub) && info.ToAbsInfoField() == - reveal path.BytesToAbsInfoField(ub, infOffset) + reveal path.BytesToAbsInfoField( + sl.View(ub, 0, len(ub))[infOffset:infOffset + path.InfoLen]) } ghost @@ -572,7 +573,8 @@ pure func (s *Raw) CorrectlyDecodedHfWithIdx(ub []byte, idx int, hop path.HopFie unfolding s.Base.Mem() in let hopOffset := MetaLen + int(s.NumINF) * path.InfoLen + idx * path.HopLen in hopOffset + path.HopLen <= len(ub) && - hop.Abs() == path.BytesToIO_HF(ub, 0, hopOffset, len(ub)) + hop.Abs() == path.BytesToIO_HF( + sl.View(ub, 0, len(ub))[hopOffset:hopOffset + path.HopLen]) } ghost @@ -587,20 +589,22 @@ pure func (s *Raw) CorrectlyDecodedHf(ub []byte, hop path.HopField) bool { let hopOffset := MetaLen + int(s.NumINF) * path.InfoLen + int(s.Base.PathMeta.CurrHF) * path.HopLen in hopOffset + path.HopLen <= len(ub) && - hop.Abs() == path.BytesToIO_HF(ub, 0, hopOffset, len(ub)) + hop.Abs() == path.BytesToIO_HF( + sl.View(ub, 0, len(ub))[hopOffset:hopOffset + path.HopLen]) } ghost preserves acc(s.Mem(ubuf), R55) preserves s.IsLastHopSpec(ubuf) preserves acc(sl.Bytes(ubuf, 0, len(ubuf)), R56) -preserves validPktMetaHdr(ubuf) -preserves s.GetBase(ubuf).EqAbsHeader(ubuf) -ensures len(s.absPkt(ubuf).CurrSeg.Future) == 1 +preserves validPktMetaHdr(sl.View(ubuf, 0, len(ubuf))) +preserves s.GetBase(ubuf).EqAbsHeader(sl.View(ubuf, 0, len(ubuf))) +ensures len(s.absPkt(sl.View(ubuf, 0, len(ubuf))).CurrSeg.Future) == 1 decreases func (s *Raw) LastHopLemma(ubuf []byte) { - reveal validPktMetaHdr(ubuf) - metaHdr := RawBytesToMetaHdr(ubuf) + v := sl.View(ubuf, 0, len(ubuf)) + reveal validPktMetaHdr(v) + metaHdr := RawBytesToMetaHdr(v) currInfIdx := int(metaHdr.CurrINF) currHfIdx := int(metaHdr.CurrHF) seg1Len := int(metaHdr.SegLen[0]) @@ -611,8 +615,8 @@ func (s *Raw) LastHopLemma(ubuf []byte) { prevSegLen := segs.LengthOfPrevSeg(currHfIdx) numINF := segs.NumInfoFields() offset := HopFieldOffset(numINF, prevSegLen, MetaLen) - pkt := reveal s.absPkt(ubuf) - assert pkt.CurrSeg == reveal CurrSeg(ubuf, offset, currInfIdx, currHfIdx - prevSegLen, segLen, MetaLen) + pkt := reveal s.absPkt(v) + assert pkt.CurrSeg == reveal CurrSeg(v, offset, currInfIdx, currHfIdx - prevSegLen, segLen, MetaLen) assert len(pkt.CurrSeg.Future) == 1 } @@ -620,16 +624,17 @@ ghost preserves acc(s.Mem(ubuf), R55) preserves s.GetBase(ubuf).IsXoverSpec() preserves acc(sl.Bytes(ubuf, 0, len(ubuf)), R56) -preserves validPktMetaHdr(ubuf) -preserves s.GetBase(ubuf).EqAbsHeader(ubuf) -ensures s.absPkt(ubuf).LeftSeg != none[io.Seg] -ensures len(s.absPkt(ubuf).CurrSeg.Future) == 1 -ensures len(get(s.absPkt(ubuf).LeftSeg).Future) > 0 -ensures len(get(s.absPkt(ubuf).LeftSeg).History) == 0 +preserves validPktMetaHdr(sl.View(ubuf, 0, len(ubuf))) +preserves s.GetBase(ubuf).EqAbsHeader(sl.View(ubuf, 0, len(ubuf))) +ensures s.absPkt(sl.View(ubuf, 0, len(ubuf))).LeftSeg != none[io.Seg] +ensures len(s.absPkt(sl.View(ubuf, 0, len(ubuf))).CurrSeg.Future) == 1 +ensures len(get(s.absPkt(sl.View(ubuf, 0, len(ubuf))).LeftSeg).Future) > 0 +ensures len(get(s.absPkt(sl.View(ubuf, 0, len(ubuf))).LeftSeg).History) == 0 decreases func (s *Raw) XoverLemma(ubuf []byte) { - reveal validPktMetaHdr(ubuf) - metaHdr := RawBytesToMetaHdr(ubuf) + v := sl.View(ubuf, 0, len(ubuf)) + reveal validPktMetaHdr(v) + metaHdr := RawBytesToMetaHdr(v) currInfIdx := int(metaHdr.CurrINF) currHfIdx := int(metaHdr.CurrHF) seg1Len := int(metaHdr.SegLen[0]) @@ -640,12 +645,12 @@ func (s *Raw) XoverLemma(ubuf []byte) { prevSegLen := segs.LengthOfPrevSeg(currHfIdx) numINF := segs.NumInfoFields() offset := HopFieldOffset(numINF, prevSegLen, MetaLen) - pkt := reveal s.absPkt(ubuf) - assert pkt.CurrSeg == reveal CurrSeg(ubuf, offset, currInfIdx, currHfIdx - prevSegLen, segLen, MetaLen) - assert pkt.LeftSeg == reveal LeftSeg(ubuf, currInfIdx + 1, segs, MetaLen) + pkt := reveal s.absPkt(v) + assert pkt.CurrSeg == reveal CurrSeg(v, offset, currInfIdx, currHfIdx - prevSegLen, segLen, MetaLen) + assert pkt.LeftSeg == reveal LeftSeg(v, currInfIdx + 1, segs, MetaLen) assert len(pkt.CurrSeg.Future) == 1 assert pkt.LeftSeg != none[io.Seg] - assert len(get(s.absPkt(ubuf).LeftSeg).History) == 0 + assert len(get(s.absPkt(v).LeftSeg).History) == 0 assert len(get(pkt.LeftSeg).Future) > 0 } @@ -672,19 +677,20 @@ pure func (s *Raw) EqAbsInfoField(pkt io.Pkt, info io.AbsInfoField) bool { ghost preserves acc(s.Mem(ubuf), R53) preserves acc(sl.Bytes(ubuf, 0, len(ubuf)), R53) -preserves validPktMetaHdr(ubuf) -preserves s.GetBase(ubuf).EqAbsHeader(ubuf) -preserves s.absPkt(ubuf).PathNotFullyTraversed() +preserves validPktMetaHdr(sl.View(ubuf, 0, len(ubuf))) +preserves s.GetBase(ubuf).EqAbsHeader(sl.View(ubuf, 0, len(ubuf))) +preserves s.absPkt(sl.View(ubuf, 0, len(ubuf))).PathNotFullyTraversed() preserves s.GetBase(ubuf).ValidCurrInfSpec() preserves s.GetBase(ubuf).ValidCurrHfSpec() preserves s.CorrectlyDecodedInf(ubuf, info) preserves s.CorrectlyDecodedHf(ubuf, hop) -ensures s.EqAbsInfoField(s.absPkt(ubuf), info.ToAbsInfoField()) -ensures s.EqAbsHopField(s.absPkt(ubuf), hop.Abs()) +ensures s.EqAbsInfoField(s.absPkt(sl.View(ubuf, 0, len(ubuf))), info.ToAbsInfoField()) +ensures s.EqAbsHopField(s.absPkt(sl.View(ubuf, 0, len(ubuf))), hop.Abs()) decreases func (s *Raw) DecodingLemma(ubuf []byte, info path.InfoField, hop path.HopField) { - assert reveal validPktMetaHdr(ubuf) - metaHdr := RawBytesToMetaHdr(ubuf) + v := sl.View(ubuf, 0, len(ubuf)) + assert reveal validPktMetaHdr(v) + metaHdr := RawBytesToMetaHdr(v) currInfIdx := int(metaHdr.CurrINF) currHfIdx := int(metaHdr.CurrHF) seg1Len := int(metaHdr.SegLen[0]) @@ -699,23 +705,22 @@ func (s *Raw) DecodingLemma(ubuf []byte, info path.InfoField, hop path.HopField) assert reveal s.CorrectlyDecodedInf(ubuf, info) assert reveal s.CorrectlyDecodedHf(ubuf, hop) - currSeg := reveal CurrSeg(ubuf, offset, currInfIdx, hfIdxSeg, segLen, MetaLen) - HopsFromPrefixOfRawMatchPrefixOfHops(ubuf, offset, 0, segLen, hfIdxSeg) + currSeg := reveal CurrSeg(v, offset, currInfIdx, hfIdxSeg, segLen, MetaLen) + HopsFromPrefixOfRawMatchPrefixOfHops(v, offset, 0, segLen, hfIdxSeg) - pktView := reveal s.absPkt(ubuf) + pktView := reveal s.absPkt(v) infoView := info.ToAbsInfoField() // assertions for proving s.EqAbsInfoField(pktView, infoView) { + infOffset := MetaLen + currInfIdx * path.InfoLen + infoBytes := InfofieldBytes(v, currInfIdx, MetaLen) // equality of Peer assert pktView.CurrSeg.Peer == infoView.Peer // equality of AInfo - assert currSeg.AInfo == path.Timestamp(ubuf, currInfIdx, MetaLen) - infOffset := MetaLen + currInfIdx * path.InfoLen - sl.AssertSliceOverlap(ubuf, infOffset+4, infOffset+8) + assert currSeg.AInfo == path.Timestamp(infoBytes) assert pktView.CurrSeg.AInfo == infoView.AInfo // equality of UInfo - sl.AssertSliceOverlap(ubuf, infOffset+2, infOffset+4) assert pktView.CurrSeg.UInfo == infoView.UInfo } @@ -724,45 +729,42 @@ func (s *Raw) DecodingLemma(ubuf []byte, info path.InfoField, hop path.HopField) } ghost -requires path.InfoFieldOffset(currInfIdx, 0) + path.InfoLen <= offset -requires 0 < segLen -requires offset + path.HopLen * segLen <= len(raw) -requires 0 <= currHfIdx && currHfIdx < segLen -requires 0 <= currInfIdx && currInfIdx < 3 -preserves acc(sl.Bytes(raw, 0, len(raw)), R56) -ensures len(CurrSeg(raw, offset, currInfIdx, currHfIdx, segLen, 0).Future) > 0 +requires path.InfoFieldOffset(currInfIdx, 0) + path.InfoLen <= offset +requires 0 < segLen +requires offset + path.HopLen * segLen <= len(raw) +requires 0 <= currHfIdx && currHfIdx < segLen +requires 0 <= currInfIdx && currInfIdx < 3 +ensures len(CurrSeg(raw, offset, currInfIdx, currHfIdx, segLen, 0).Future) > 0 decreases -func LenCurrSeg(raw []byte, offset int, currInfIdx int, currHfIdx int, segLen int) { +func LenCurrSeg(raw seq[byte], offset int, currInfIdx int, currHfIdx int, segLen int) { reveal CurrSeg(raw, offset, currInfIdx, currHfIdx, segLen, 0) } ghost -requires segs.Valid() -requires 0 < segs.Seg2Len -requires PktLen(segs, 0) <= len(raw) -requires 0 <= currInfIdx && currInfIdx < 2 -requires 1 <= currInfIdx ==> 0 < segs.Seg3Len -preserves acc(sl.Bytes(raw, 0, len(raw)), R56) -ensures LeftSeg(raw, currInfIdx + 1, segs, 0) != none[io.Seg] -ensures RightSeg(raw, currInfIdx, segs, 0) != none[io.Seg] -decreases -func XoverSegNotNone(raw []byte, currInfIdx int, segs io.SegLens) { +requires segs.Valid() +requires 0 < segs.Seg2Len +requires PktLen(segs, 0) <= len(raw) +requires 0 <= currInfIdx && currInfIdx < 2 +requires 1 <= currInfIdx ==> 0 < segs.Seg3Len +ensures LeftSeg(raw, currInfIdx + 1, segs, 0) != none[io.Seg] +ensures RightSeg(raw, currInfIdx, segs, 0) != none[io.Seg] +decreases +func XoverSegNotNone(raw seq[byte], currInfIdx int, segs io.SegLens) { reveal LeftSeg(raw, currInfIdx + 1, segs, 0) reveal RightSeg(raw, currInfIdx, segs, 0) } ghost -requires path.InfoFieldOffset(currInfIdx, 0) + path.InfoLen <= offset -requires 0 < segLen -requires offset + path.HopLen * segLen <= len(raw) -requires 0 <= currHfIdx && currHfIdx < segLen -requires 0 <= currInfIdx && currInfIdx < 3 -preserves acc(sl.Bytes(raw, 0, len(raw)), R56) -preserves len(CurrSeg(raw, offset, currInfIdx, currHfIdx, segLen, 0).Future) > 0 -ensures CurrSeg(raw, offset, currInfIdx, currHfIdx + 1, segLen, 0) == +requires path.InfoFieldOffset(currInfIdx, 0) + path.InfoLen <= offset +requires 0 < segLen +requires offset + path.HopLen * segLen <= len(raw) +requires 0 <= currHfIdx && currHfIdx < segLen +requires 0 <= currInfIdx && currInfIdx < 3 +requires len(CurrSeg(raw, offset, currInfIdx, currHfIdx, segLen, 0).Future) > 0 +ensures CurrSeg(raw, offset, currInfIdx, currHfIdx + 1, segLen, 0) == absIncPathSeg(CurrSeg(raw, offset, currInfIdx, currHfIdx, segLen, 0)) decreases -func IncCurrSeg(raw []byte, offset int, currInfIdx int, currHfIdx int, segLen int) { +func IncCurrSeg(raw seq[byte], offset int, currInfIdx int, currHfIdx int, segLen int) { currseg := reveal CurrSeg(raw, offset, currInfIdx, currHfIdx, segLen, 0) incseg := reveal CurrSeg(raw, offset, currInfIdx, currHfIdx + 1, segLen, 0) hf := hopFields(raw, offset, 0, segLen) @@ -779,15 +781,14 @@ func IncCurrSeg(raw []byte, offset int, currInfIdx int, currHfIdx int, segLen in } ghost -requires segs.Valid() -requires 0 < segs.Seg2Len -requires PktLen(segs, 0) <= len(raw) -requires 1 <= currInfIdx && currInfIdx < 3 -requires 1 == currInfIdx ==> currHfIdx + 1 == segs.Seg1Len -requires 2 == currInfIdx ==> 0 < segs.Seg3Len && currHfIdx + 1 == segs.Seg1Len + segs.Seg2Len -requires PktLen(segs, 0) <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), R56) -preserves LeftSeg(raw, currInfIdx, segs, 0) != none[io.Seg] +requires segs.Valid() +requires 0 < segs.Seg2Len +requires PktLen(segs, 0) <= len(raw) +requires 1 <= currInfIdx && currInfIdx < 3 +requires 1 == currInfIdx ==> currHfIdx + 1 == segs.Seg1Len +requires 2 == currInfIdx ==> 0 < segs.Seg3Len && currHfIdx + 1 == segs.Seg1Len + segs.Seg2Len +requires LeftSeg(raw, currInfIdx, segs, 0) != none[io.Seg] +ensures LeftSeg(raw, currInfIdx, segs, 0) != none[io.Seg] ensures let prevSegLen := segs.LengthOfPrevSeg(currHfIdx + 1) in let segLen := segs.LengthOfCurrSeg(currHfIdx + 1) in @@ -796,7 +797,7 @@ ensures CurrSeg(raw, offset, currInfIdx, currHfIdx - prevSegLen + 1, segLen, 0) == get(LeftSeg(raw, currInfIdx, segs, 0)) decreases -func XoverCurrSeg(raw []byte, currInfIdx int, currHfIdx int, segs io.SegLens) { +func XoverCurrSeg(raw seq[byte], currInfIdx int, currHfIdx int, segs io.SegLens) { prevSegLen := segs.LengthOfPrevSeg(currHfIdx + 1) segLen := segs.LengthOfCurrSeg(currHfIdx + 1) numInf := segs.NumInfoFields() @@ -807,45 +808,42 @@ func XoverCurrSeg(raw []byte, currInfIdx int, currHfIdx int, segs io.SegLens) { } ghost -requires segs.Valid() -requires PktLen(segs, 0) <= len(raw) -requires 2 <= currInfIdx && currInfIdx < 4 -preserves acc(sl.Bytes(raw, 0, len(raw)), R56) -ensures LeftSeg(raw, currInfIdx, segs, 0) == +requires segs.Valid() +requires PktLen(segs, 0) <= len(raw) +requires 2 <= currInfIdx && currInfIdx < 4 +ensures LeftSeg(raw, currInfIdx, segs, 0) == MidSeg(raw, currInfIdx, segs, 0) decreases -func XoverLeftSeg(raw []byte, currInfIdx int, segs io.SegLens) { +func XoverLeftSeg(raw seq[byte], currInfIdx int, segs io.SegLens) { leftseg := reveal LeftSeg(raw, currInfIdx, segs, 0) midseg := reveal MidSeg(raw, currInfIdx, segs, 0) assert leftseg == midseg } ghost -requires segs.Valid() -requires 0 < segs.Seg2Len -requires PktLen(segs, 0) <= len(raw) -requires -1 <= currInfIdx && currInfIdx < 1 -requires 0 == currInfIdx ==> 0 < segs.Seg3Len -preserves acc(sl.Bytes(raw, 0, len(raw)), R56) -ensures MidSeg(raw, currInfIdx + 4, segs, 0) == +requires segs.Valid() +requires 0 < segs.Seg2Len +requires PktLen(segs, 0) <= len(raw) +requires -1 <= currInfIdx && currInfIdx < 1 +requires 0 == currInfIdx ==> 0 < segs.Seg3Len +ensures MidSeg(raw, currInfIdx + 4, segs, 0) == RightSeg(raw, currInfIdx, segs, 0) decreases -func XoverMidSeg(raw []byte, currInfIdx int, segs io.SegLens) { +func XoverMidSeg(raw seq[byte], currInfIdx int, segs io.SegLens) { midseg := reveal MidSeg(raw, currInfIdx + 4, segs, 0) rightseg := reveal RightSeg(raw, currInfIdx, segs, 0) assert midseg == rightseg } ghost -requires segs.Valid() -requires 0 < segs.Seg2Len -requires PktLen(segs, 0) <= len(raw) -requires 0 <= currInfIdx && currInfIdx < 2 -requires 0 == currInfIdx ==> currHfIdx + 1 == segs.Seg1Len -requires 1 == currInfIdx ==> 0 < segs.Seg3Len && currHfIdx + 1 == segs.Seg1Len + segs.Seg2Len -requires PktLen(segs, 0) <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), R56) -preserves RightSeg(raw, currInfIdx, segs, 0) != none[io.Seg] +requires segs.Valid() +requires 0 < segs.Seg2Len +requires PktLen(segs, 0) <= len(raw) +requires 0 <= currInfIdx && currInfIdx < 2 +requires 0 == currInfIdx ==> currHfIdx + 1 == segs.Seg1Len +requires 1 == currInfIdx ==> 0 < segs.Seg3Len && currHfIdx + 1 == segs.Seg1Len + segs.Seg2Len +requires RightSeg(raw, currInfIdx, segs, 0) != none[io.Seg] +ensures RightSeg(raw, currInfIdx, segs, 0) != none[io.Seg] ensures let prevSegLen := segs.LengthOfPrevSeg(currHfIdx) in let segLen := segs.LengthOfCurrSeg(currHfIdx) in @@ -855,7 +853,7 @@ ensures len(currseg.Future) > 0 && get(RightSeg(raw, currInfIdx, segs, 0)) == absIncPathSeg(currseg) decreases -func XoverRightSeg(raw []byte, currInfIdx int, currHfIdx int, segs io.SegLens) { +func XoverRightSeg(raw seq[byte], currInfIdx int, currHfIdx int, segs io.SegLens) { prevSegLen := segs.LengthOfPrevSeg(currHfIdx) segLen := segs.LengthOfCurrSeg(currHfIdx) numInf := segs.NumInfoFields() @@ -875,28 +873,12 @@ requires 0 <= offset requires 0 <= currHfIdx && currHfIdx <= end requires end <= segLen requires offset + path.HopLen * segLen <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), R54) -ensures hopFields(raw, offset, currHfIdx, segLen)[:end - currHfIdx] == - hopFields(raw, offset, currHfIdx, end) -decreases -func HopsFromSuffixOfRawMatchSuffixOfHops(raw []byte, offset int, currHfIdx int, segLen int, end int) { - hopsFromSuffixOfRawMatchSuffixOfHops(raw, offset, currHfIdx, segLen, end, R54) -} - -ghost -requires R55 < p -requires 0 <= offset -requires 0 <= currHfIdx && currHfIdx <= end -requires end <= segLen -requires offset + path.HopLen * segLen <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), p) ensures hopFields(raw, offset, currHfIdx, segLen)[:end - currHfIdx] == hopFields(raw, offset, currHfIdx, end) decreases end - currHfIdx -func hopsFromSuffixOfRawMatchSuffixOfHops(raw []byte, offset int, currHfIdx int, segLen int, end int, p perm) { +func HopsFromSuffixOfRawMatchSuffixOfHops(raw seq[byte], offset int, currHfIdx int, segLen int, end int) { if (currHfIdx != end) { - newP := (p + R55)/2 - hopsFromSuffixOfRawMatchSuffixOfHops(raw, offset, currHfIdx + 1, segLen, end, newP) + HopsFromSuffixOfRawMatchSuffixOfHops(raw, offset, currHfIdx + 1, segLen, end) } } @@ -905,28 +887,12 @@ requires 0 <= offset requires 0 <= start requires 0 <= currHfIdx && currHfIdx <= segLen - start requires offset + path.HopLen * segLen <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), R54) -ensures hopFields(raw, offset, currHfIdx, segLen)[start:] == - hopFields(raw, offset, currHfIdx + start, segLen) -decreases -func HopsFromPrefixOfRawMatchPrefixOfHops(raw []byte, offset int, currHfIdx int, segLen int, start int) { - hopsFromPrefixOfRawMatchPrefixOfHops(raw, offset, currHfIdx, segLen, start, R54) -} - -ghost -requires R55 < p -requires 0 <= offset -requires 0 <= start -requires 0 <= currHfIdx && currHfIdx <= segLen - start -requires offset + path.HopLen * segLen <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), p) ensures hopFields(raw, offset, currHfIdx, segLen)[start:] == hopFields(raw, offset, currHfIdx + start, segLen) decreases start -func hopsFromPrefixOfRawMatchPrefixOfHops(raw []byte, offset int, currHfIdx int, segLen int, start int, p perm) { +func HopsFromPrefixOfRawMatchPrefixOfHops(raw seq[byte], offset int, currHfIdx int, segLen int, start int) { if (start != 0) { - newP := (p + R55)/2 - hopsFromPrefixOfRawMatchPrefixOfHops(raw, offset, currHfIdx, segLen, start - 1, newP) + HopsFromPrefixOfRawMatchPrefixOfHops(raw, offset, currHfIdx, segLen, start - 1) } } @@ -935,27 +901,11 @@ requires 0 <= offset requires 0 <= start && start <= currHfIdx requires 0 <= currHfIdx && currHfIdx <= segLen requires offset + path.HopLen * segLen <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), R54) -ensures hopFields(raw, offset, currHfIdx, segLen) == - hopFields(raw, offset + start * path.HopLen, currHfIdx - start, segLen - start) -decreases -func AlignHopsOfRawWithOffsetAndIndex(raw []byte, offset int, currHfIdx int, segLen int, start int) { - alignHopsOfRawWithOffsetAndIndex(raw, offset, currHfIdx, segLen, start, R54) -} - -ghost -requires R55 < p -requires 0 <= offset -requires 0 <= start && start <= currHfIdx -requires 0 <= currHfIdx && currHfIdx <= segLen -requires offset + path.HopLen * segLen <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), p) ensures hopFields(raw, offset, currHfIdx, segLen) == hopFields(raw, offset + start * path.HopLen, currHfIdx - start, segLen - start) decreases segLen - currHfIdx -func alignHopsOfRawWithOffsetAndIndex(raw []byte, offset int, currHfIdx int, segLen int, start int, p perm) { +func AlignHopsOfRawWithOffsetAndIndex(raw seq[byte], offset int, currHfIdx int, segLen int, start int) { if (currHfIdx != segLen) { - newP := (p + R55)/2 - alignHopsOfRawWithOffsetAndIndex(raw, offset, currHfIdx + 1, segLen, start, newP) + AlignHopsOfRawWithOffsetAndIndex(raw, offset, currHfIdx + 1, segLen, start) } -} \ No newline at end of file +} diff --git a/pkg/slayers/path/scion/widen-lemma.gobra b/pkg/slayers/path/scion/widen-lemma.gobra index 9eb8eb06f..76c5dbc78 100644 --- a/pkg/slayers/path/scion/widen-lemma.gobra +++ b/pkg/slayers/path/scion/widen-lemma.gobra @@ -16,28 +16,30 @@ package scion +// The lemmas in this file relate the abstract segments parsed from the +// view of a packet buffer to the ones parsed from the view of a prefix +// (or subrange) of that buffer. Since views are mathematical sequences, +// these lemmas require no permissions at all: subslicing a view is +// ordinary sequence slicing. + import ( - sl "verification/utils/slices" "verification/io" - . "verification/utils/definitions" - "verification/dependencies/encoding/binary" + "verification/utils/seqs" "github.com/scionproto/scion/pkg/slayers/path" ) ghost -requires 0 <= start && start <= headerOffset -requires path.InfoFieldOffset(currInfIdx, headerOffset) + path.InfoLen <= offset -requires 0 < segLen -requires offset + path.HopLen * segLen <= length -requires length <= len(raw) -requires 0 <= currHfIdx && currHfIdx <= segLen -requires 0 <= currInfIdx && currInfIdx < 3 -preserves acc(sl.Bytes(raw, 0, len(raw)), R51) -preserves acc(sl.Bytes(raw[start:length], 0, len(raw[start:length])), R51) -ensures CurrSeg(raw, offset, currInfIdx, currHfIdx, segLen, headerOffset) == +requires 0 <= start && start <= headerOffset +requires path.InfoFieldOffset(currInfIdx, headerOffset) + path.InfoLen <= offset +requires 0 < segLen +requires offset + path.HopLen * segLen <= length +requires length <= len(raw) +requires 0 <= currHfIdx && currHfIdx <= segLen +requires 0 <= currInfIdx && currInfIdx < 3 +ensures CurrSeg(raw, offset, currInfIdx, currHfIdx, segLen, headerOffset) == CurrSeg(raw[start:length], offset-start, currInfIdx, currHfIdx, segLen, headerOffset-start) decreases -func WidenCurrSeg(raw []byte, +func WidenCurrSeg(raw seq[byte], offset int, currInfIdx int, currHfIdx int, @@ -45,35 +47,13 @@ func WidenCurrSeg(raw []byte, headerOffset int, start int, length int) { - unfold acc(sl.Bytes(raw, 0, len(raw)), R53) - unfold acc(sl.Bytes(raw[start:length], 0, len(raw[start:length])), R53) - - ainfo1 := path.Timestamp(raw, currInfIdx, headerOffset) - ainfo2 := path.Timestamp(raw[start:length], currInfIdx, headerOffset-start) - sl.AssertSliceOverlap(raw, start, length) - idxTimestamp := path.InfoFieldOffset(currInfIdx, headerOffset-start)+4 - sl.AssertSliceOverlap(raw[start:length], idxTimestamp, idxTimestamp+4) - assert ainfo1 == ainfo2 - - uinfo1 := path.AbsUinfo(raw, currInfIdx, headerOffset) - uinfo2 := path.AbsUinfo(raw[start:length], currInfIdx, headerOffset-start) - idxUinfo := path.InfoFieldOffset(currInfIdx, headerOffset-start)+2 - sl.AssertSliceOverlap(raw[start:length], idxUinfo, idxUinfo+2) - assert uinfo1 == uinfo2 - - consDir1 := path.ConsDir(raw, currInfIdx, headerOffset) - consDir2 := path.ConsDir(raw[start:length], currInfIdx, headerOffset-start) - assert consDir1 == consDir2 - - peer1 := path.Peer(raw, currInfIdx, headerOffset) - peer2 := path.Peer(raw[start:length], currInfIdx, headerOffset-start) - assert peer1 == peer2 - - widenSegment(raw, offset, currHfIdx, ainfo1, uinfo1, consDir1, peer1, segLen, start, length) + infOffset := path.InfoFieldOffset(currInfIdx, headerOffset) + seqs.SubSeqOfSubSeq(raw, infOffset, infOffset + path.InfoLen, start, length) + assert InfofieldBytes(raw, currInfIdx, headerOffset) == + InfofieldBytes(raw[start:length], currInfIdx, headerOffset-start) + widenHopFields(raw, offset, 0, segLen, start, length) reveal CurrSeg(raw, offset, currInfIdx, currHfIdx, segLen, headerOffset) reveal CurrSeg(raw[start:length], offset-start, currInfIdx, currHfIdx, segLen, headerOffset-start) - fold acc(sl.Bytes(raw, 0, len(raw)), R53) - fold acc(sl.Bytes(raw[start:length], 0, len(raw[start:length])), R53) } ghost @@ -82,12 +62,10 @@ requires 0 < segLen requires 0 <= currHfIdx && currHfIdx <= segLen requires length <= len(raw) requires offset + path.HopLen * segLen <= length -preserves acc(sl.Bytes(raw, 0, len(raw)), R52) -preserves acc(sl.Bytes(raw[start:length], 0, len(raw[start:length])), R52) ensures segment(raw, offset, currHfIdx, ainfo, uinfo, consDir, peer, segLen) == segment(raw[start:length], offset-start, currHfIdx, ainfo, uinfo, consDir, peer, segLen) decreases -func widenSegment(raw []byte, +func widenSegment(raw seq[byte], offset int, currHfIdx int, ainfo io.Ainfo, @@ -97,43 +75,37 @@ func widenSegment(raw []byte, segLen int, start int, length int) { - newP := (R52 + R53)/2 - widenHopFields(raw, offset, 0, segLen, start, length, newP) + widenHopFields(raw, offset, 0, segLen, start, length) } ghost -requires R53 < p -requires 0 <= start && start <= offset -requires 0 <= currHfIdx && currHfIdx <= segLen -requires offset + path.HopLen * segLen <= length -requires length <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), p) -preserves acc(sl.Bytes(raw[start:length], 0, len(raw[start:length])), p) -ensures hopFields(raw, offset, currHfIdx, segLen) == +requires 0 <= start && start <= offset +requires 0 <= currHfIdx && currHfIdx <= segLen +requires offset + path.HopLen * segLen <= length +requires length <= len(raw) +ensures hopFields(raw, offset, currHfIdx, segLen) == hopFields(raw[start:length], offset-start, currHfIdx, segLen) decreases segLen - currHfIdx -func widenHopFields(raw []byte, offset int, currHfIdx int, segLen int, start int, length int, p perm) { +func widenHopFields(raw seq[byte], offset int, currHfIdx int, segLen int, start int, length int) { if (currHfIdx != segLen) { - path.WidenBytesHopField(raw, offset + path.HopLen * currHfIdx, start, length) - hf1 := path.BytesToIO_HF(raw, 0, offset + path.HopLen * currHfIdx, len(raw)) - hf2 := path.BytesToIO_HF(raw[start:length], 0, offset + path.HopLen * currHfIdx - start, length - start) - newP := (p + R53)/2 - widenHopFields(raw, offset, currHfIdx + 1, segLen, start, length, newP) + hfStart := offset + path.HopLen * currHfIdx + seqs.SubSeqOfSubSeq(raw, hfStart, hfStart + path.HopLen, start, length) + assert raw[hfStart:hfStart + path.HopLen] == + raw[start:length][hfStart - start:hfStart - start + path.HopLen] + widenHopFields(raw, offset, currHfIdx + 1, segLen, start, length) } } ghost -requires 0 <= start && start <= headerOffset -requires segs.Valid() -requires 0 <= length && length <= len(raw) -requires PktLen(segs, headerOffset) <= length -requires 1 <= currInfIdx && currInfIdx < 4 -preserves acc(sl.Bytes(raw, 0, len(raw)), R51) -preserves acc(sl.Bytes(raw[start:length], 0, len(raw[start:length])), R51) -ensures LeftSeg(raw, currInfIdx, segs, headerOffset) == +requires 0 <= start && start <= headerOffset +requires segs.Valid() +requires 0 <= length && length <= len(raw) +requires PktLen(segs, headerOffset) <= length +requires 1 <= currInfIdx && currInfIdx < 4 +ensures LeftSeg(raw, currInfIdx, segs, headerOffset) == LeftSeg(raw[start:length], currInfIdx, segs, headerOffset-start) decreases -func WidenLeftSeg(raw []byte, +func WidenLeftSeg(raw seq[byte], currInfIdx int, segs io.SegLens, headerOffset int, @@ -152,17 +124,15 @@ func WidenLeftSeg(raw []byte, } ghost -requires 0 <= start && start <= headerOffset -requires segs.Valid() -requires 0 <= length && length <= len(raw) -requires PktLen(segs, headerOffset) <= length -requires -1 <= currInfIdx && currInfIdx < 2 -preserves acc(sl.Bytes(raw, 0, len(raw)), R51) -preserves acc(sl.Bytes(raw[start:length], 0, len(raw[start:length])), R51) -ensures RightSeg(raw, currInfIdx, segs, headerOffset) == +requires 0 <= start && start <= headerOffset +requires segs.Valid() +requires 0 <= length && length <= len(raw) +requires PktLen(segs, headerOffset) <= length +requires -1 <= currInfIdx && currInfIdx < 2 +ensures RightSeg(raw, currInfIdx, segs, headerOffset) == RightSeg(raw[start:length], currInfIdx, segs, headerOffset-start) decreases -func WidenRightSeg(raw []byte, +func WidenRightSeg(raw seq[byte], currInfIdx int, segs io.SegLens, headerOffset int, @@ -180,17 +150,15 @@ func WidenRightSeg(raw []byte, } ghost -requires 0 <= start && start <= headerOffset -requires segs.Valid() -requires 2 <= currInfIdx && currInfIdx < 5 -requires 0 <= length && length <= len(raw) -requires PktLen(segs, headerOffset) <= length -preserves acc(sl.Bytes(raw, 0, len(raw)), R51) -preserves acc(sl.Bytes(raw[start:length], 0, len(raw[start:length])), R51) -ensures MidSeg(raw, currInfIdx, segs, headerOffset) == +requires 0 <= start && start <= headerOffset +requires segs.Valid() +requires 2 <= currInfIdx && currInfIdx < 5 +requires 0 <= length && length <= len(raw) +requires PktLen(segs, headerOffset) <= length +ensures MidSeg(raw, currInfIdx, segs, headerOffset) == MidSeg(raw[start:length], currInfIdx, segs, headerOffset - start) decreases -func WidenMidSeg(raw []byte, +func WidenMidSeg(raw seq[byte], currInfIdx int, segs io.SegLens, headerOffset int, @@ -205,4 +173,4 @@ func WidenMidSeg(raw []byte, } reveal MidSeg(raw, currInfIdx, segs, headerOffset) reveal MidSeg(raw[start:length], currInfIdx, segs, headerOffset - start) -} \ No newline at end of file +} diff --git a/verification/utils/seqs/seqs.gobra b/verification/utils/seqs/seqs.gobra index d4e96b033..edddfd72e 100644 --- a/verification/utils/seqs/seqs.gobra +++ b/verification/utils/seqs/seqs.gobra @@ -39,6 +39,27 @@ ensures forall i int :: { res[i] } 0 <= i && i < size ==> res[i] == nil decreases _ pure func NewSeqByteSlice(size int) (res seq[[]byte]) +// SubSeqOfSubSeq proves that reslicing a subsequence yields the same +// sequence as directly slicing the original sequence with the composed +// indices. +ghost +requires 0 <= start && start <= a +requires a <= b && b <= length && length <= len(raw) +ensures raw[a:b] == raw[start:length][a - start : b - start] +decreases +func SubSeqOfSubSeq(raw seq[byte], a int, b int, start int, length int) { + s1 := raw[a:b] + s2 := raw[start:length][a - start : b - start] + assert len(s1) == len(s2) + assert forall i int :: { s1[i] } 0 <= i && i < b - a ==> + s1[i] == raw[a + i] + assert forall i int :: { s2[i] } 0 <= i && i < b - a ==> + s2[i] == raw[start:length][a - start + i] + assert forall i int :: { s2[i] } 0 <= i && i < b - a ==> + s2[i] == raw[a + i] + assert s1 == s2 +} + // Deprecated: use sl.View directly. ToSeqByte is kept as an alias for // the view of a whole slice while its remaining clients are migrated. ghost From 2241d6eb8f781375a5f5de76d68b01dbafc4501a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 12:22:02 +0000 Subject: [PATCH 05/35] Convert slayers header spec functions to seq[byte] views GetAddressOffset, GetLength, GetPathType, GetNextHdr, ValidPktMetaHdr, IsSupportedPkt and EqPathTypeWithBuffer now take the view of the packet instead of a slice with a sl.Bytes predicate, which merges the *WithinLength variants into the plain getters and the slice-based IsSupportedPkt into the former IsSupportedRawPkt. ValidHeaderOffset takes the view explicitly and its To/FromSubSlice lemmas become pure sequence lemmas; IsSupportedPktSubslice likewise. EqAbsHeader keeps its buffer signature but is defined through the view and scion.RawBytesToBase. The SCION SerializeTo/DecodeFromBytes proofs are restructured around views, dropping the reslicing choreography that only served the old heap-dependent lemmas. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/scion.go | 74 ++++++++-------- pkg/slayers/scion_spec.gobra | 167 ++++++++++------------------------- 2 files changed, 83 insertions(+), 158 deletions(-) diff --git a/pkg/slayers/scion.go b/pkg/slayers/scion.go index 3b8794d3a..f4057801b 100644 --- a/pkg/slayers/scion.go +++ b/pkg/slayers/scion.go @@ -223,7 +223,7 @@ func (s *SCION) NetworkFlow() (res gopacket.Flow) { // @ ensures e != nil ==> e.ErrorMem() // post for IO: // @ ensures e == nil && old(s.EqPathType(ubuf)) ==> -// @ IsSupportedRawPkt(b.View()) == old(IsSupportedPkt(ubuf)) +// @ IsSupportedPkt(b.View()) == old(IsSupportedPkt(sl.View(ubuf, 0, len(ubuf)))) // @ decreases func (s *SCION) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions /* @ , ghost ubuf []byte @*/) (e error) { // @ unfold acc(s.Mem(ubuf), R1) @@ -263,55 +263,52 @@ func (s *SCION) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeO buf[9] = uint8(s.DstAddrType&0x7)<<4 | uint8(s.SrcAddrType&0x7) // @ assert &buf[10:12][0] == &buf[10] && &buf[10:12][1] == &buf[11] binary.BigEndian.PutUint16(buf[10:12], 0) + // @ assert &buf[4] == &uSerBufN[4] && &buf[8] == &uSerBufN[8] + // @ ghost b4 := buf[4] + // @ ghost b8 := buf[8] // @ fold acc(sl.Bytes(uSerBufN, 0, len(uSerBufN)), writePerm) + // @ ghost vSer0 := sl.View(uSerBufN, 0, len(uSerBufN)) + // @ assert vSer0[4] == b4 && vSer0[8] == b8 // @ ghost if s.EqPathType(ubuf) { - // @ assert reveal s.EqPathTypeWithBuffer(ubuf, uSerBufN) - // @ s.IsSupportedPktLemma(ubuf, uSerBufN) + // @ assert reveal s.EqPathTypeWithBuffer(ubuf, vSer0) + // @ s.IsSupportedPktLemma(ubuf, vSer0) // @ } + // @ IsSupportedPktSubslice(vSer0, CmnHdrLen) // Serialize address header. - // @ sl.SplitRange_Bytes(uSerBufN, CmnHdrLen, scnLen, HalfPerm) - // @ sl.Reslice_Bytes(uSerBufN, 0, CmnHdrLen, R54) - // @ IsSupportedPktSubslice(uSerBufN, CmnHdrLen) - // @ sl.SplitRange_Bytes(uSerBufN, CmnHdrLen, scnLen, HalfPerm) + // @ sl.SplitRange_Bytes(uSerBufN, CmnHdrLen, scnLen, writePerm) // @ sl.SplitRange_Bytes(ubuf, CmnHdrLen, len(ubuf), R10) if err := s.SerializeAddrHdr(buf[CmnHdrLen:] /*@ , ubuf[CmnHdrLen:] @*/); err != nil { - // @ sl.Unslice_Bytes(uSerBufN, 0, CmnHdrLen, R54) // @ sl.CombineRange_Bytes(uSerBufN, CmnHdrLen, scnLen, writePerm) // @ sl.CombineRange_Bytes(ubuf, CmnHdrLen, len(ubuf), R10) return err } offset := CmnHdrLen + s.AddrHdrLen( /*@ nil, true @*/ ) - // @ sl.CombineRange_Bytes(uSerBufN, CmnHdrLen, scnLen, HalfPerm) + // @ sl.CombineRange_Bytes(uSerBufN, CmnHdrLen, scnLen, writePerm) // @ sl.CombineRange_Bytes(ubuf, CmnHdrLen, len(ubuf), R10) - // @ IsSupportedPktSubslice(uSerBufN, CmnHdrLen) - // @ sl.Unslice_Bytes(uSerBufN, 0, CmnHdrLen, R54) - // @ sl.CombineRange_Bytes(uSerBufN, CmnHdrLen, scnLen, HalfPerm) + // @ ghost vSerA := sl.View(uSerBufN, 0, len(uSerBufN)) + // @ assert vSerA[:CmnHdrLen] == vSer0[:CmnHdrLen] + // @ IsSupportedPktSubslice(vSerA, CmnHdrLen) + // @ assert IsSupportedPkt(vSerA) == IsSupportedPkt(vSer0) // Serialize path header. // @ ghost startP := int(CmnHdrLen+s.AddrHdrLenSpecInternal()) // @ ghost endP := int(s.HdrLen*LineLen) // @ ghost pathSlice := ubuf[startP : endP] - // @ sl.SplitRange_Bytes(uSerBufN, offset, scnLen, HalfPerm) - // @ sl.SplitRange_Bytes(ubuf, startP, endP, HalfPerm) - // @ sl.Reslice_Bytes(uSerBufN, 0, offset, R54) - // @ sl.Reslice_Bytes(ubuf, 0, startP, R54) - // @ IsSupportedPktSubslice(uSerBufN, offset) - // @ IsSupportedPktSubslice(ubuf, startP) - // @ sl.SplitRange_Bytes(uSerBufN, offset, scnLen, HalfPerm) - // @ sl.SplitRange_Bytes(ubuf, startP, endP, HalfPerm) + // @ IsSupportedPktSubslice(vSerA, offset) + // @ sl.SplitRange_Bytes(uSerBufN, offset, scnLen, writePerm) + // @ sl.SplitRange_Bytes(ubuf, startP, endP, writePerm) tmp := s.Path.SerializeTo(buf[offset:] /*@, pathSlice @*/) - // @ sl.CombineRange_Bytes(uSerBufN, offset, scnLen, HalfPerm) - // @ sl.CombineRange_Bytes(ubuf, startP, endP, HalfPerm) - // @ IsSupportedPktSubslice(uSerBufN, offset) - // @ IsSupportedPktSubslice(ubuf, startP) - // @ sl.Unslice_Bytes(uSerBufN, 0, offset, R54) - // @ sl.Unslice_Bytes(ubuf, 0, startP, R54) - // @ sl.CombineRange_Bytes(uSerBufN, offset, scnLen, HalfPerm) - // @ sl.CombineRange_Bytes(ubuf, startP, endP, HalfPerm) - // @ reveal IsSupportedPkt(uSerBufN) - // @ reveal IsSupportedRawPkt(b.View()) + // @ sl.CombineRange_Bytes(uSerBufN, offset, scnLen, writePerm) + // @ sl.CombineRange_Bytes(ubuf, startP, endP, writePerm) + // @ ghost vSer1 := sl.View(uSerBufN, 0, len(uSerBufN)) + // @ assert vSer1[:offset] == vSerA[:offset] + // @ assert vSer1[:CmnHdrLen] == vSerA[:CmnHdrLen] + // @ IsSupportedPktSubslice(vSer1, offset) + // @ assert IsSupportedPkt(vSer1) == IsSupportedPkt(vSerA) + // @ assert b.View() == vSer1 + // @ reveal IsSupportedPkt(b.View()) return tmp } @@ -353,13 +350,14 @@ func (s *SCION) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) (res er // @ preserves CmnHdrLen <= len(data) && acc(sl.Bytes(data, 0, len(data)), R41) // @ ensures s.DstAddrType.Has3Bits() && s.SrcAddrType.Has3Bits() // @ ensures 0 <= s.PathType && s.PathType < 256 - // @ ensures path.Type(GetPathType(data)) == s.PathType - // @ ensures L4ProtocolType(GetNextHdr(data)) == s.NextHdr - // @ ensures GetLength(data) == int(s.HdrLen * LineLen) - // @ ensures GetAddressOffset(data) == + // @ ensures path.Type(GetPathType(sl.View(data, 0, len(data)))) == s.PathType + // @ ensures L4ProtocolType(GetNextHdr(sl.View(data, 0, len(data)))) == s.NextHdr + // @ ensures GetLength(sl.View(data, 0, len(data))) == int(s.HdrLen * LineLen) + // @ ensures GetAddressOffset(sl.View(data, 0, len(data))) == // @ CmnHdrLen + 2*addr.IABytes + s.DstAddrType.Length() + s.SrcAddrType.Length() // @ decreases // @ outline( + // @ ghost v := sl.View(data, 0, len(data)) // @ unfold acc(sl.Bytes(data, 0, len(data)), R41) s.NextHdr = L4ProtocolType(data[4]) s.HdrLen = data[5] @@ -372,6 +370,7 @@ func (s *SCION) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) (res er // @ assert int(s.DstAddrType) == b.BitAnd7(int(data[9] >> 4)) s.SrcAddrType = AddrType(data[9] & 0x7) // @ assert int(s.SrcAddrType) == b.BitAnd7(int(data[9])) + // @ assert v[4] == data[4] && v[5] == data[5] && v[8] == data[8] && v[9] == data[9] // @ fold acc(sl.Bytes(data, 0, len(data)), R41) // @ ) // Decode address header. @@ -433,14 +432,13 @@ func (s *SCION) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) (res er s.Payload = data[hdrBytes:] // @ fold acc(s.Mem(data), R54) // @ ghost if(typeOf(s.GetPath(data)) == (*scion.Raw)) { - // @ unfold acc(sl.Bytes(data, 0, len(data)), R56) - // @ unfold acc(sl.Bytes(data[offset : offset+pathLen], 0, len(data[offset : offset+pathLen])), R56) + // @ sl.ViewOfSubslice(data, offset, offset+pathLen, R56) + // @ assert sl.View(data[offset : offset+pathLen], 0, pathLen) == + // @ sl.View(data, 0, len(data))[offset : offset+pathLen] // @ unfold acc(s.Path.(*scion.Raw).Mem(data[offset : offset+pathLen]), R55) // @ assert reveal s.EqAbsHeader(data) // @ assert reveal s.ValidScionInitSpec(data) // @ fold acc(s.Path.Mem(data[offset : offset+pathLen]), R55) - // @ fold acc(sl.Bytes(data, 0, len(data)), R56) - // @ fold acc(sl.Bytes(data[offset : offset+pathLen], 0, len(data[offset : offset+pathLen])), R56) // @ } // @ sl.CombineRange_Bytes(data, offset, offset+pathLen, R41) // @ assert typeOf(s.GetPath(data)) == *scion.Raw ==> s.EqAbsHeader(data) && s.ValidScionInitSpec(data) diff --git a/pkg/slayers/scion_spec.gobra b/pkg/slayers/scion_spec.gobra index 636713ec1..07205fc03 100644 --- a/pkg/slayers/scion_spec.gobra +++ b/pkg/slayers/scion_spec.gobra @@ -31,7 +31,6 @@ import ( sl "verification/utils/slices" "verification/io" "encoding/binary" - "verification/utils/seqs" ) pred PathPoolMem(pathPool []path.Path, pathPoolRaw path.Path) { @@ -354,52 +353,37 @@ pure func (s *SCION) GetPath(ub []byte) path.Path { ghost opaque requires s.Mem(ub) -requires sl.Bytes(ub, 0, length) -requires CmnHdrLen <= length +requires CmnHdrLen <= len(raw) decreases -pure func (s *SCION) ValidHeaderOffset(ub []byte, length int) bool { - return GetAddressOffsetWithinLength(ub, length) == s.PathStartIdx(ub) && - GetLengthWithinLength(ub,length) == s.PathEndIdx(ub) +pure func (s *SCION) ValidHeaderOffset(ub []byte, raw seq[byte]) bool { + return GetAddressOffset(raw) == s.PathStartIdx(ub) && + GetLength(raw) == s.PathEndIdx(ub) } ghost requires acc(s.Mem(ub), R56) -requires acc(sl.Bytes(ub, 0, len(ub)), R55) -requires acc(sl.Bytes(ub, 0, length), R55) -requires CmnHdrLen <= length && length <= len(ub) -requires s.ValidHeaderOffset(ub, len(ub)) +requires CmnHdrLen <= length && length <= len(raw) +requires s.ValidHeaderOffset(ub, raw) ensures acc(s.Mem(ub), R56) -ensures acc(sl.Bytes(ub, 0, len(ub)), R55) -ensures acc(sl.Bytes(ub, 0, length), R55) -ensures s.ValidHeaderOffset(ub, length) +ensures s.ValidHeaderOffset(ub, raw[:length]) decreases -func (s *SCION) ValidHeaderOffsetToSubSliceLemma(ub []byte, length int) { - reveal s.ValidHeaderOffset(ub, len(ub)) - unfold acc(sl.Bytes(ub, 0, len(ub)), R56) - unfold acc(sl.Bytes(ub, 0, length), R56) - assert reveal s.ValidHeaderOffset(ub, length) - fold acc(sl.Bytes(ub, 0, len(ub)), R56) - fold acc(sl.Bytes(ub, 0, length), R56) +func (s *SCION) ValidHeaderOffsetToSubSliceLemma(ub []byte, raw seq[byte], length int) { + reveal s.ValidHeaderOffset(ub, raw) + assert raw[:length][5] == raw[5] && raw[:length][9] == raw[9] + assert reveal s.ValidHeaderOffset(ub, raw[:length]) } ghost requires acc(s.Mem(ub), R56) -requires acc(sl.Bytes(ub, 0, len(ub)), R55) -requires acc(sl.Bytes(ub, 0, length), R55) -requires CmnHdrLen <= length && length <= len(ub) -requires s.ValidHeaderOffset(ub, length) +requires CmnHdrLen <= length && length <= len(raw) +requires s.ValidHeaderOffset(ub, raw[:length]) ensures acc(s.Mem(ub), R56) -ensures acc(sl.Bytes(ub, 0, len(ub)), R55) -ensures acc(sl.Bytes(ub, 0, length), R55) -ensures s.ValidHeaderOffset(ub, len(ub)) +ensures s.ValidHeaderOffset(ub, raw) decreases -func (s *SCION) ValidHeaderOffsetFromSubSliceLemma(ub []byte, length int) { - reveal s.ValidHeaderOffset(ub, len(ub)) - unfold acc(sl.Bytes(ub, 0, len(ub)), R56) - unfold acc(sl.Bytes(ub, 0, length), R56) - assert reveal s.ValidHeaderOffset(ub, length) - fold acc(sl.Bytes(ub, 0, len(ub)), R56) - fold acc(sl.Bytes(ub, 0, length), R56) +func (s *SCION) ValidHeaderOffsetFromSubSliceLemma(ub []byte, raw seq[byte], length int) { + reveal s.ValidHeaderOffset(ub, raw[:length]) + assert raw[:length][5] == raw[5] && raw[:length][9] == raw[9] + assert reveal s.ValidHeaderOffset(ub, raw) } ghost @@ -413,24 +397,15 @@ pure func (s *SCION) EqAbsHeader(ub []byte) bool { // parsing errors (since the SIF PR in Gobra, low is a keyword). let low_ := CmnHdrLen+s.AddrHdrLenSpecInternal() in let high := int(s.HdrLen) * LineLen in - GetAddressOffset(ub) == low_ && - GetLength(ub) == int(high) && + let v := sl.View(ub, 0, len(ub)) in + GetAddressOffset(v) == low_ && + GetLength(v) == int(high) && // Might be worth introducing EqAbsHeader as an interface method on Path // to avoid doing these casts, especially when we add support for EPIC. typeOf(s.Path) == (*scion.Raw) && unfolding s.Path.Mem(ub[low_:high]) in - unfolding sl.Bytes(ub, 0, len(ub)) in - let _ := Asserting(forall k int :: {&ub[low_:high][k]} 0 <= k && k < high ==> - &ub[low_:high][k] == &ub[low_ + k]) in - let _ := Asserting(forall k int :: {&ub[low_:high][:scion.MetaLen][k]} 0 <= k && k < scion.MetaLen ==> - &ub[low_:high][:scion.MetaLen][k] == &ub[low_:high][k]) in - let metaHdr := scion.DecodedFrom(binary.BigEndian.Uint32(ub[low_:high][:scion.MetaLen])) in - let seg1 := int(metaHdr.SegLen[0]) in - let seg2 := int(metaHdr.SegLen[1]) in - let seg3 := int(metaHdr.SegLen[2]) in - let segs := io.CombineSegLens(seg1, seg2, seg3) in s.Path.(*scion.Raw).Base.GetBase() == - scion.Base{metaHdr, segs.NumInfoFields(), segs.TotalHops()} + scion.RawBytesToBase(v[low_:high]) } // Describes a SCION packet that was successfully decoded by `DecodeFromBytes`. @@ -451,19 +426,15 @@ pure func (s *SCION) ValidScionInitSpec(ub []byte) bool { // Checks if the common path header is valid in the serialized scion packet. ghost opaque -requires sl.Bytes(raw, 0, len(raw)) decreases -pure func ValidPktMetaHdr(raw []byte) bool { +pure func ValidPktMetaHdr(raw seq[byte]) bool { return CmnHdrLen <= len(raw) && let start := GetAddressOffset(raw) in let end := start+scion.MetaLen in 0 <= start && end <= len(raw) && - let rawHdr := raw[start:end] in let length := GetLength(raw) in length <= len(raw) && - unfolding sl.Bytes(raw, 0, len(raw)) in - let _ := Asserting(forall k int :: {&rawHdr[k]} 0 <= k && k < scion.MetaLen ==> &rawHdr[k] == &raw[start + k]) in - let hdr := binary.BigEndian.Uint32(rawHdr) in + let hdr := binary.BigEndian.Uint32Spec(raw[start], raw[start+1], raw[start+2], raw[start+3]) in let metaHdr := scion.DecodedFrom(hdr) in let seg1 := int(metaHdr.SegLen[0]) in let seg2 := int(metaHdr.SegLen[1]) in @@ -477,109 +448,66 @@ pure func ValidPktMetaHdr(raw []byte) bool { ghost opaque -requires sl.Bytes(raw, 0, len(raw)) decreases -pure func IsSupportedPkt(raw []byte) bool { +pure func IsSupportedPkt(raw seq[byte]) bool { return CmnHdrLen <= len(raw) && - let pathType := path.Type(GetPathType(raw)) in - let nextHdr := L4ProtocolType(GetNextHdr(raw)) in - pathType == scion.PathType && - nextHdr != L4SCMP -} - -ghost -opaque -decreases -pure func IsSupportedRawPkt(raw seq[byte]) bool { - return CmnHdrLen <= len(raw) && - let pathType := path.Type(raw[8]) in + let pathType := path.Type(raw[8]) in let nextHdr := L4ProtocolType(raw[4]) in pathType == scion.PathType && nextHdr != L4SCMP } ghost -requires CmnHdrLen <= idx && idx <= len(raw) -preserves acc(sl.Bytes(raw, 0, len(raw)), R55) -preserves acc(sl.Bytes(raw[:idx], 0, idx), R55) -ensures IsSupportedPkt(raw) == IsSupportedPkt(raw[:idx]) +requires CmnHdrLen <= idx && idx <= len(raw) +ensures IsSupportedPkt(raw) == IsSupportedPkt(raw[:idx]) decreases -func IsSupportedPktSubslice(raw []byte, idx int) { - unfold acc(sl.Bytes(raw, 0, len(raw)), R56) - unfold acc(sl.Bytes(raw[:idx], 0, idx), R56) +func IsSupportedPktSubslice(raw seq[byte], idx int) { reveal IsSupportedPkt(raw) reveal IsSupportedPkt(raw[:idx]) - fold acc(sl.Bytes(raw, 0, len(raw)), R56) - fold acc(sl.Bytes(raw[:idx], 0, idx), R56) } ghost preserves acc(s.Mem(ub), R55) preserves acc(sl.Bytes(ub, 0, len(ub)), R55) -preserves acc(sl.Bytes(buffer, 0, len(buffer)), R55) preserves s.EqPathType(ub) preserves s.EqPathTypeWithBuffer(ub, buffer) -ensures IsSupportedPkt(ub) == IsSupportedPkt(buffer) +ensures IsSupportedPkt(sl.View(ub, 0, len(ub))) == IsSupportedPkt(buffer) decreases -func (s *SCION) IsSupportedPktLemma(ub []byte, buffer []byte) { +func (s *SCION) IsSupportedPktLemma(ub []byte, buffer seq[byte]) { reveal s.EqPathType(ub) reveal s.EqPathTypeWithBuffer(ub, buffer) - reveal IsSupportedPkt(ub) + reveal IsSupportedPkt(sl.View(ub, 0, len(ub))) reveal IsSupportedPkt(buffer) } ghost -requires sl.Bytes(ub, 0, len(ub)) -requires CmnHdrLen <= len(ub) +requires CmnHdrLen <= len(raw) decreases -pure func GetAddressOffset(ub []byte) int { - return GetAddressOffsetWithinLength(ub, len(ub)) -} - -ghost -requires sl.Bytes(ub, 0, length) -requires CmnHdrLen <= length -decreases -pure func GetAddressOffsetWithinLength(ub []byte, length int) int { - return unfolding sl.Bytes(ub, 0, length) in - let dstAddrLen := AddrType(ub[9] >> 4 & 0x7).Length() in - let srcAddrLen := AddrType(ub[9] & 0x7).Length() in +pure func GetAddressOffset(raw seq[byte]) int { + return let dstAddrLen := AddrType(raw[9] >> 4 & 0x7).Length() in + let srcAddrLen := AddrType(raw[9] & 0x7).Length() in CmnHdrLen + 2*addr.IABytes + dstAddrLen + srcAddrLen } ghost -requires sl.Bytes(ub, 0, len(ub)) -requires CmnHdrLen <= len(ub) -decreases -pure func GetLength(ub []byte) int { - return GetLengthWithinLength(ub, len(ub)) -} - -ghost -requires sl.Bytes(ub, 0, length) -requires CmnHdrLen <= length +requires CmnHdrLen <= len(raw) decreases -pure func GetLengthWithinLength(ub []byte, length int) int { - return unfolding sl.Bytes(ub, 0, length) in - int(ub[5]) * LineLen +pure func GetLength(raw seq[byte]) int { + return int(raw[5]) * LineLen } ghost -requires sl.Bytes(ub, 0, len(ub)) -requires CmnHdrLen <= len(ub) +requires CmnHdrLen <= len(raw) decreases -pure func GetPathType(ub []byte) int { - return unfolding sl.Bytes(ub, 0, len(ub)) in - int(ub[8]) +pure func GetPathType(raw seq[byte]) int { + return int(raw[8]) } ghost -requires sl.Bytes(ub, 0, len(ub)) -requires CmnHdrLen <= len(ub) +requires CmnHdrLen <= len(raw) decreases -pure func GetNextHdr(ub []byte) int { - return unfolding sl.Bytes(ub, 0, len(ub)) in - int(ub[4]) +pure func GetNextHdr(raw seq[byte]) int { + return int(raw[4]) } ghost @@ -588,15 +516,14 @@ requires s.Mem(ub) requires sl.Bytes(ub, 0, len(ub)) decreases pure func (s *SCION) EqPathType(ub []byte) bool { - return reveal s.EqPathTypeWithBuffer(ub, ub) + return reveal s.EqPathTypeWithBuffer(ub, sl.View(ub, 0, len(ub))) } ghost opaque requires s.Mem(ub) -requires sl.Bytes(buffer, 0, len(buffer)) decreases -pure func (s *SCION) EqPathTypeWithBuffer(ub []byte, buffer []byte) bool { +pure func (s *SCION) EqPathTypeWithBuffer(ub []byte, buffer seq[byte]) bool { return unfolding s.Mem(ub) in CmnHdrLen <= len(buffer) && path.Type(GetPathType(buffer)) == s.PathType && From d7cb2ecbbd1de1972eb4d7a7242aeb37235f5412 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 19:07:30 +0000 Subject: [PATCH 06/35] Convert router IO spec to seq[byte] views absPkt and absIO_val now abstract packets from the view of the buffer rather than from a slice guarded by sl.Bytes; MsgToAbsVal and absReturnErr take the view at the boundary. The widening lemmas (absIO_valWidenLemma and helpers) and the AbsPktToSubSliceAbsPkt/ SubSliceAbsPktToAbsPkt bridging lemmas become permission-free sequence lemmas built on sl.ViewOfSubslice and the seq-based scion.Widen* lemmas. All dataplane.go contracts and proof annotations are rewritten to pass views to the converted functions. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- router/dataplane.go | 530 ++++++++++++++++++------------------ router/io-spec-lemmas.gobra | 129 ++++----- router/io-spec.gobra | 21 +- router/widen-lemma.gobra | 55 ++-- 4 files changed, 349 insertions(+), 386 deletions(-) diff --git a/router/dataplane.go b/router/dataplane.go index b70db752e..b33a74dbf 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -1049,10 +1049,10 @@ func (d *DataPlane) Run(ctx context.Context /*@, ghost place io.Place, ghost sta // @ assert p.N <= len(p.Buffers[0]) // @ sl.SplitRange_Bytes(p.Buffers[0], 0, p.N, HalfPerm) tmpBuf := p.Buffers[0][:p.N] - // @ ghost absPktTmpBuf := absIO_val(tmpBuf, ingressID) - // @ ghost absPktBuf0 := absIO_val(msgs[i0].Buffers[0], ingressID) + // @ ghost absPktTmpBuf := absIO_val(sl.View(tmpBuf, 0, len(tmpBuf)), ingressID) + // @ ghost absPktBuf0 := absIO_val(sl.View(msgs[i0].Buffers[0], 0, len(msgs[i0].Buffers[0])), ingressID) // @ assert msgs[i0] === p - // @ absIO_valWidenLemma(p.Buffers[0], ingressID, p.N) + // @ absIO_valWidenLemma(sl.View(p.Buffers[0], 0, len(p.Buffers[0])), ingressID, p.N) // @ assert absPktTmpBuf.isValPkt ==> absPktTmpBuf === absPktBuf0 // @ MultiElemWitnessStep(ioSharedArg.IBufY, ioIngressID, ioValSeq, i0) // @ assert ioValSeq[i0].isValPkt ==> @@ -1137,10 +1137,10 @@ func (d *DataPlane) Run(ctx context.Context /*@, ghost place io.Place, ghost sta writeMsgs[0].Addr = result.OutAddr } // @ sl.NilAcc_Bytes() - // @ assert absIO_val(result.OutPkt, result.EgressID) == - // @ absIO_val(writeMsgs[0].Buffers[0], result.EgressID) + // @ assert absIO_val(sl.View(result.OutPkt, 0, len(result.OutPkt)), result.EgressID) == + // @ absIO_val(sl.View(writeMsgs[0].Buffers[0], 0, len(writeMsgs[0].Buffers[0])), result.EgressID) // @ assert result.OutPkt != nil ==> newAbsPkt == - // @ absIO_val(writeMsgs[0].Buffers[0], result.EgressID) + // @ absIO_val(sl.View(writeMsgs[0].Buffers[0], 0, len(writeMsgs[0].Buffers[0])), result.EgressID) // @ fold acc(writeMsgs[0].Mem(), R50) // @ ghost ioLock.Lock() // @ unfold SharedInv{dp, ioSharedArg}() @@ -1518,10 +1518,10 @@ func (p *scionPacketProcessor) reset() (err error) { // @ requires dp.Valid() // @ requires acc(ioLock.LockP(), _) // @ requires ioLock.LockInv() == SharedInv{dp, ioSharedArg} -// @ requires let absPkt := absIO_val(rawPkt, p.getIngressID()) in +// @ requires let absPkt := absIO_val(sl.View(rawPkt, 0, len(rawPkt)), p.getIngressID()) in // @ absPkt.isValPkt ==> ElemWitness(ioSharedArg.IBufY, path.ifsToIO_ifs(p.getIngressID()), absPkt.ValPkt_2) // @ ensures respr.OutPkt != nil ==> -// @ newAbsPkt == absIO_val(respr.OutPkt, respr.EgressID) +// @ newAbsPkt == absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID) // @ ensures (respr.OutPkt == nil) == (newAbsPkt == io.ValUnit{}) // @ ensures newAbsPkt.isValPkt ==> // @ ElemWitness(ioSharedArg.OBufY, newAbsPkt.ValPkt_1, newAbsPkt.ValPkt_2) @@ -1637,7 +1637,7 @@ func (p *scionPacketProcessor) processPkt(rawPkt []byte, // @ assert sl.Bytes(p.rawPkt, 0, len(p.rawPkt)) // @ unfold acc(p.d.Mem(), _) // @ assert reveal p.scionLayer.EqPathType(p.rawPkt) - // @ assert !(reveal slayers.IsSupportedPkt(p.rawPkt)) + // @ assert !(reveal slayers.IsSupportedPkt(sl.View(p.rawPkt, 0, len(p.rawPkt)))) v1, v2 /*@, aliasesPkt, newAbsPkt @*/ := p.processOHP() // @ ResetDecodingLayers(&p.scionLayer, &p.hbhLayer, &p.e2eLayer, ubScionLayer, ubHbhLayer, ubE2eLayer, true, hasHbhLayer, hasE2eLayer) // @ fold p.sInit() @@ -1827,12 +1827,12 @@ func (p *scionPacketProcessor) processIntraBFD(data []byte) (res error) { // @ requires p.scionLayer.EqPathType(ub) // @ requires acc(ioLock.LockP(), _) // @ requires ioLock.LockInv() == SharedInv{dp, ioSharedArg} -// @ requires let absPkt := absIO_val(p.rawPkt, p.ingressID) in +// @ requires let absPkt := absIO_val(sl.View(p.rawPkt, 0, len(p.rawPkt)), p.ingressID) in // @ absPkt.isValPkt ==> ElemWitness(ioSharedArg.IBufY, path.ifsToIO_ifs(p.ingressID), absPkt.ValPkt_2) // @ ensures reserr == nil && newAbsPkt.isValPkt ==> // @ ElemWitness(ioSharedArg.OBufY, newAbsPkt.ValPkt_1, newAbsPkt.ValPkt_2) // @ ensures respr.OutPkt != nil ==> -// @ newAbsPkt == absIO_val(respr.OutPkt, respr.EgressID) +// @ newAbsPkt == absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID) // @ ensures reserr != nil && respr.OutPkt != nil ==> // @ newAbsPkt.isValUnsupported // @ ensures (respr.OutPkt == nil) == (newAbsPkt == io.ValUnit{}) @@ -1981,7 +1981,7 @@ type macBuffersT struct { // @ respr.OutPkt === p.buffer.UBuf() // @ ensures reserr != nil && reserr.ErrorMem() // @ ensures respr.OutPkt != nil ==> -// @ !slayers.IsSupportedPkt(respr.OutPkt) +// @ !slayers.IsSupportedPkt(sl.View(respr.OutPkt, 0, len(respr.OutPkt))) // @ decreases func (p *scionPacketProcessor) packSCMP( typ slayers.SCMPType, @@ -2064,13 +2064,13 @@ func (p *scionPacketProcessor) packSCMP( // @ ensures reserr != nil ==> reserr.ErrorMem() // Postconditions for IO: // @ ensures reserr == nil ==> -// @ slayers.ValidPktMetaHdr(ub) && +// @ slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && // @ p.scionLayer.EqAbsHeader(ub) && // @ p.scionLayer.ValidPathMetaData(ub) -// @ ensures reserr == nil ==> absPkt(ub).PathNotFullyTraversed() -// @ ensures reserr == nil ==> p.EqAbsHopField(absPkt(ub)) -// @ ensures reserr == nil ==> p.EqAbsInfoField(absPkt(ub)) -// @ ensures old(slayers.IsSupportedPkt(ub)) == slayers.IsSupportedPkt(ub) +// @ ensures reserr == nil ==> absPkt(sl.View(ub, 0, len(ub))).PathNotFullyTraversed() +// @ ensures reserr == nil ==> p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures reserr == nil ==> p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures old(slayers.IsSupportedPkt(sl.View(ub, 0, len(ub)))) == slayers.IsSupportedPkt(sl.View(ub, 0, len(ub))) // @ ensures respr.OutPkt == nil // @ decreases func (p *scionPacketProcessor) parsePath( /*@ ghost ub []byte @*/ ) (respr processResult, reserr error) { @@ -2122,15 +2122,15 @@ func (p *scionPacketProcessor) parsePath( /*@ ghost ub []byte @*/ ) (respr proce // @ p.EstablishEqAbsHeader(ub, startP, endP) // @ p.path.EstablishValidPktMetaHdr(ubPath) // @ p.SubSliceAbsPktToAbsPkt(ub, startP, endP) - // @ absPktFutureLemma(ub) + // @ absPktFutureLemma(sl.View(ub, 0, len(ub))) // @ p.path.DecodingLemma(ubPath, p.infoField, p.hopField) - // @ assert reveal p.path.EqAbsInfoField(p.path.absPkt(ubPath), + // @ assert reveal p.path.EqAbsInfoField(p.path.absPkt(sl.View(ubPath, 0, len(ubPath))), // @ p.infoField.ToAbsInfoField()) - // @ assert reveal p.path.EqAbsHopField(p.path.absPkt(ubPath), + // @ assert reveal p.path.EqAbsHopField(p.path.absPkt(sl.View(ubPath, 0, len(ubPath))), // @ p.hopField.Abs()) - // @ assert reveal p.EqAbsHopField(absPkt(ub)) - // @ assert reveal p.EqAbsInfoField(absPkt(ub)) - // @ assert old(reveal slayers.IsSupportedPkt(ub)) == reveal slayers.IsSupportedPkt(ub) + // @ assert reveal p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) + // @ assert reveal p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) + // @ assert old(reveal slayers.IsSupportedPkt(sl.View(ub, 0, len(ub)))) == reveal slayers.IsSupportedPkt(sl.View(ub, 0, len(ub))) return processResult{}, nil } @@ -2144,8 +2144,8 @@ func (p *scionPacketProcessor) parsePath( /*@ ghost ub []byte @*/ ) (respr proce // @ requires acc(&p.buffer, R50) && p.buffer != nil && p.buffer.Mem() // @ requires sl.Bytes(p.buffer.UBuf(), 0, len(p.buffer.UBuf())) // pres for IO: -// @ requires slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ requires absPkt(ubScionL).PathNotFullyTraversed() +// @ requires slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ requires absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() // @ preserves ubLL == nil || ubLL === ubScionL[startLL:endLL] // @ preserves acc(&p.lastLayer, R55) && p.lastLayer != nil // @ preserves &p.scionLayer !== p.lastLayer ==> @@ -2168,12 +2168,12 @@ func (p *scionPacketProcessor) parsePath( /*@ ghost ub []byte @*/ ) (respr proce // @ ensures reserr == nil ==> // @ respr === processResult{} // posts for IO: -// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ ensures reserr == nil ==> absPkt(ubScionL).PathNotFullyTraversed() -// @ ensures reserr == nil ==> absPkt(ubScionL) == old(absPkt(ubScionL)) -// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(ubScionL)) == slayers.IsSupportedPkt(ubScionL) +// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))) == old(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL)))) == slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL))) // @ ensures reserr != nil && respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases func (p *scionPacketProcessor) validateHopExpiry( /*@ ghost ubScionL []byte, ghost ubLL []byte, ghost startLL int, ghost endLL int @*/ ) (respr processResult, reserr error) { expiration := util.SecsToTime(p.infoField.Timestamp). @@ -2212,7 +2212,7 @@ func (p *scionPacketProcessor) validateHopExpiry( /*@ ghost ubScionL []byte, gho /*@ ubScionL, ubLL, startLL, endLL, @*/ ) // @ ghost if tmpErr != nil && tmpRes.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(tmpRes.OutPkt, tmpRes.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmpRes.OutPkt, 0, len(tmpRes.OutPkt)), tmpRes.EgressID) // @ } return tmpRes, tmpErr } @@ -2255,17 +2255,17 @@ func (p *scionPacketProcessor) validateHopExpiry( /*@ ghost ubScionL []byte, gho // @ ensures reserr == nil ==> // @ respr === processResult{} // contracts for IO-spec -// @ requires slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ requires absPkt(ubScionL).PathNotFullyTraversed() -// @ requires p.EqAbsHopField(absPkt(ubScionL)) -// @ requires p.EqAbsInfoField(absPkt(ubScionL)) -// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ ensures reserr == nil ==> absPkt(ubScionL) == old(absPkt(ubScionL)) -// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(ubScionL)) == slayers.IsSupportedPkt(ubScionL) +// @ requires slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ requires absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() +// @ requires p.EqAbsHopField(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ requires p.EqAbsInfoField(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))) == old(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL)))) == slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL))) // @ ensures reserr == nil ==> -// @ AbsValidateIngressIDConstraint(absPkt(ubScionL), path.ifsToIO_ifs(p.ingressID)) +// @ AbsValidateIngressIDConstraint(absPkt(sl.View(ubScionL, 0, len(ubScionL))), path.ifsToIO_ifs(p.ingressID)) // @ ensures reserr != nil && respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases func (p *scionPacketProcessor) validateIngressID( /*@ ghost ubScionL []byte, ghost ubLL []byte, ghost startLL int, ghost endLL int@*/ ) (respr processResult, reserr error) { pktIngressID := p.hopField.ConsIngress @@ -2284,11 +2284,11 @@ func (p *scionPacketProcessor) validateIngressID( /*@ ghost ubScionL []byte, gho /*@ ubScionL, ubLL, startLL, endLL, @*/ ) // @ ghost if tmpErr != nil && tmpRes.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(tmpRes.OutPkt, tmpRes.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmpRes.OutPkt, 0, len(tmpRes.OutPkt)), tmpRes.EgressID) // @ } return tmpRes, tmpErr } - // @ ghost oldPkt := absPkt(ubScionL) + // @ ghost oldPkt := absPkt(sl.View(ubScionL, 0, len(ubScionL))) // @ reveal p.EqAbsHopField(oldPkt) // @ reveal p.EqAbsInfoField(oldPkt) // @ assert reveal AbsValidateIngressIDConstraint(oldPkt, path.ifsToIO_ifs(p.ingressID)) @@ -2325,16 +2325,16 @@ func (p *scionPacketProcessor) validateIngressID( /*@ ghost ubScionL []byte, gho // @ ensures reserr == nil ==> // @ respr === processResult{} // contracts for IO-spec -// @ requires slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ requires absPkt(ubScionL).PathNotFullyTraversed() -// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) +// @ requires slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ requires absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() +// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) // @ ensures reserr == nil ==> p.DstIsLocalIngressID(ubScionL) // @ ensures reserr == nil ==> p.LastHopLen(ubScionL) -// @ ensures reserr == nil ==> absPkt(ubScionL).PathNotFullyTraversed() -// @ ensures reserr == nil ==> absPkt(ubScionL) == old(absPkt(ubScionL)) -// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(ubScionL)) == slayers.IsSupportedPkt(ubScionL) +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))) == old(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL)))) == slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL))) // @ ensures reserr != nil && respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases func (p *scionPacketProcessor) validateSrcDstIA( /*@ ghost ubScionL []byte, ghost ubLL []byte, ghost startLL int, ghost endLL int @*/ ) (respr processResult, reserr error) { // @ unfold acc(p.scionLayer.Mem(ubScionL), R20) @@ -2344,7 +2344,7 @@ func (p *scionPacketProcessor) validateSrcDstIA( /*@ ghost ubScionL []byte, ghos // @ ghost ubPath := ubScionL[startP:endP] // @ sl.SplitRange_Bytes(ubScionL, startP, endP, R50) // @ p.AbsPktToSubSliceAbsPkt(ubScionL, startP, endP) - // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ubScionL, startP) + // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ubScionL, sl.View(ubScionL, 0, len(ubScionL)), startP) // @ unfold acc(p.scionLayer.HeaderMem(ubScionL[slayers.CmnHdrLen:]), R20) // @ defer fold acc(p.scionLayer.HeaderMem(ubScionL[slayers.CmnHdrLen:]), R20) // @ p.d.getLocalIA() @@ -2375,7 +2375,7 @@ func (p *scionPacketProcessor) validateSrcDstIA( /*@ ghost ubScionL []byte, ghos } // @ ghost if(p.path.IsLastHopSpec(ubPath)) { // @ p.path.LastHopLemma(ubPath) - // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ubScionL, startP) + // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ubScionL, sl.View(ubScionL, 0, len(ubScionL)), startP) // @ p.SubSliceAbsPktToAbsPkt(ubScionL, startP, endP) // @ } } @@ -2421,7 +2421,7 @@ func (p *scionPacketProcessor) validateSrcDstIA( /*@ ghost ubScionL []byte, ghos // @ respr.OutPkt === p.buffer.UBuf() // @ ensures reserr != nil && reserr.ErrorMem() // @ ensures respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases func (p *scionPacketProcessor) invalidSrcIA( // @ ghost ub []byte, @@ -2438,7 +2438,7 @@ func (p *scionPacketProcessor) invalidSrcIA( /*@ ub , ubLL, startLL, endLL, @*/ ) // @ ghost if tmpErr != nil && tmpRes.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(tmpRes.OutPkt, tmpRes.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmpRes.OutPkt, 0, len(tmpRes.OutPkt)), tmpRes.EgressID) // @ } return tmpRes, tmpErr } @@ -2471,7 +2471,7 @@ func (p *scionPacketProcessor) invalidSrcIA( // @ respr.OutPkt === p.buffer.UBuf() // @ ensures reserr != nil && reserr.ErrorMem() // @ ensures respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases func (p *scionPacketProcessor) invalidDstIA( // @ ghost ub []byte, @@ -2488,7 +2488,7 @@ func (p *scionPacketProcessor) invalidDstIA( /*@ ub , ubLL, startLL, endLL, @*/ ) // @ ghost if tmpErr != nil && tmpRes.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(tmpRes.OutPkt, tmpRes.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmpRes.OutPkt, 0, len(tmpRes.OutPkt)), tmpRes.EgressID) // @ } return tmpRes, tmpErr } @@ -2597,31 +2597,31 @@ func (p *scionPacketProcessor) validateTransitUnderlaySrc( /*@ ghost ub []byte @ // @ requires dp.Valid() // @ requires p.d.WellConfigured() // @ requires p.d.DpAgreesWithSpec(dp) -// @ requires slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ requires absPkt(ubScionL).PathNotFullyTraversed() -// @ requires p.EqAbsHopField(absPkt(ubScionL)) -// @ requires p.EqAbsInfoField(absPkt(ubScionL)) +// @ requires slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ requires absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() +// @ requires p.EqAbsHopField(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ requires p.EqAbsInfoField(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) // @ requires p.segmentChange ==> -// @ absPkt(ubScionL).RightSeg != none[io.Seg] && len(get(absPkt(ubScionL).RightSeg).Past) > 0 +// @ absPkt(sl.View(ubScionL, 0, len(ubScionL))).RightSeg != none[io.Seg] && len(get(absPkt(sl.View(ubScionL, 0, len(ubScionL))).RightSeg).Past) > 0 // @ requires !p.segmentChange ==> -// @ AbsValidateIngressIDConstraint(absPkt(ubScionL), path.ifsToIO_ifs(p.ingressID)) +// @ AbsValidateIngressIDConstraint(absPkt(sl.View(ubScionL, 0, len(ubScionL))), path.ifsToIO_ifs(p.ingressID)) // @ requires p.segmentChange ==> -// @ AbsValidateIngressIDConstraintXover(absPkt(ubScionL), path.ifsToIO_ifs(p.ingressID)) -// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ ensures reserr == nil ==> absPkt(ubScionL) == old(absPkt(ubScionL)) -// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(ubScionL)) == slayers.IsSupportedPkt(ubScionL) -// @ ensures reserr == nil ==> p.NoBouncingPkt(absPkt(ubScionL)) +// @ AbsValidateIngressIDConstraintXover(absPkt(sl.View(ubScionL, 0, len(ubScionL))), path.ifsToIO_ifs(p.ingressID)) +// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))) == old(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL)))) == slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL))) +// @ ensures reserr == nil ==> p.NoBouncingPkt(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) // @ ensures reserr == nil && !p.segmentChange ==> -// @ AbsValidateEgressIDConstraint(absPkt(ubScionL), (p.ingressID != 0), dp) +// @ AbsValidateEgressIDConstraint(absPkt(sl.View(ubScionL, 0, len(ubScionL))), (p.ingressID != 0), dp) // @ ensures reserr == nil && p.segmentChange ==> -// @ absPkt(ubScionL).RightSeg != none[io.Seg] && len(get(absPkt(ubScionL).RightSeg).Past) > 0 +// @ absPkt(sl.View(ubScionL, 0, len(ubScionL))).RightSeg != none[io.Seg] && len(get(absPkt(sl.View(ubScionL, 0, len(ubScionL))).RightSeg).Past) > 0 // @ ensures reserr == nil && p.segmentChange ==> -// @ p.ingressID != 0 && AbsValidateEgressIDConstraintXover(absPkt(ubScionL), dp) +// @ p.ingressID != 0 && AbsValidateEgressIDConstraintXover(absPkt(sl.View(ubScionL, 0, len(ubScionL))), dp) // @ ensures reserr != nil && respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases func (p *scionPacketProcessor) validateEgressID( /*@ ghost dp io.DataPlaneSpec, ghost ubScionL []byte, ghost ubLL []byte, ghost startLL int, ghost endLL int @*/ ) (respr processResult, reserr error) { - // @ ghost oldPkt := absPkt(ubScionL) + // @ ghost oldPkt := absPkt(sl.View(ubScionL, 0, len(ubScionL))) pktEgressID := p.egressInterface( /*@ oldPkt @*/ ) // @ reveal AbsEgressInterfaceConstraint(oldPkt, path.ifsToIO_ifs(pktEgressID)) // @ p.d.getInternalNextHops() @@ -2647,7 +2647,7 @@ func (p *scionPacketProcessor) validateEgressID( /*@ ghost dp io.DataPlaneSpec, /*@ ubScionL, ubLL, startLL, endLL, @*/ ) // @ ghost if tmpErr != nil && tmpRes.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(tmpRes.OutPkt, tmpRes.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmpRes.OutPkt, 0, len(tmpRes.OutPkt)), tmpRes.EgressID) // @ } return tmpRes, tmpErr } @@ -2687,7 +2687,7 @@ func (p *scionPacketProcessor) validateEgressID( /*@ ghost dp io.DataPlaneSpec, "egress_id", pktEgressID, "egress_type", egress), /*@ ubScionL, ubLL, startLL, endLL, @*/) // @ ghost if tmpErr != nil && tmpRes.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(tmpRes.OutPkt, tmpRes.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmpRes.OutPkt, 0, len(tmpRes.OutPkt)), tmpRes.EgressID) // @ } return tmpRes, tmpErr } @@ -2718,7 +2718,7 @@ func (p *scionPacketProcessor) validateEgressID( /*@ ghost dp io.DataPlaneSpec, "egress_id", pktEgressID, "egress_type", egress), /*@ ubScionL, ubLL, startLL, endLL, @*/) // @ ghost if tmpErr != nil && tmpRes.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(tmpRes.OutPkt, tmpRes.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmpRes.OutPkt, 0, len(tmpRes.OutPkt)), tmpRes.EgressID) // @ } return tmpRes, tmpErr } @@ -2732,12 +2732,12 @@ func (p *scionPacketProcessor) validateEgressID( /*@ ghost dp io.DataPlaneSpec, // @ requires sl.Bytes(ub, 0, len(ub)) // @ requires acc(&p.ingressID, R21) // preconditions for IO: -// @ requires slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) -// @ requires absPkt(ub).PathNotFullyTraversed() +// @ requires slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) +// @ requires absPkt(sl.View(ub, 0, len(ub))).PathNotFullyTraversed() // @ requires acc(&p.d, R55) && acc(p.d.Mem(), _) && acc(&p.ingressID, R55) // @ requires p.LastHopLen(ub) -// @ requires p.EqAbsHopField(absPkt(ub)) -// @ requires p.EqAbsInfoField(absPkt(ub)) +// @ requires p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) +// @ requires p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) // @ ensures acc(&p.ingressID, R21) // @ ensures acc(&p.hopField, R20) // @ ensures sl.Bytes(ub, 0, len(ub)) @@ -2747,14 +2747,14 @@ func (p *scionPacketProcessor) validateEgressID( /*@ ghost dp io.DataPlaneSpec, // @ ensures err != nil ==> err.ErrorMem() // posconditions for IO: // @ ensures acc(&p.d, R55) && acc(p.d.Mem(), _) && acc(&p.ingressID, R55) -// @ ensures err == nil ==> slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) -// @ ensures err == nil ==> absPkt(ub).PathNotFullyTraversed() +// @ ensures err == nil ==> slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) +// @ ensures err == nil ==> absPkt(sl.View(ub, 0, len(ub))).PathNotFullyTraversed() // @ ensures err == nil ==> -// @ absPkt(ub) == AbsUpdateNonConsDirIngressSegID(old(absPkt(ub)), path.ifsToIO_ifs(p.ingressID)) +// @ absPkt(sl.View(ub, 0, len(ub))) == AbsUpdateNonConsDirIngressSegID(old(absPkt(sl.View(ub, 0, len(ub)))), path.ifsToIO_ifs(p.ingressID)) // @ ensures err == nil ==> p.LastHopLen(ub) -// @ ensures err == nil ==> p.EqAbsHopField(absPkt(ub)) -// @ ensures err == nil ==> p.EqAbsInfoField(absPkt(ub)) -// @ ensures err == nil ==> old(slayers.IsSupportedPkt(ub)) == slayers.IsSupportedPkt(ub) +// @ ensures err == nil ==> p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures err == nil ==> p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures err == nil ==> old(slayers.IsSupportedPkt(sl.View(ub, 0, len(ub)))) == slayers.IsSupportedPkt(sl.View(ub, 0, len(ub))) // @ decreases func (p *scionPacketProcessor) updateNonConsDirIngressSegID( /*@ ghost ub []byte @*/ ) (err error) { // @ ghost ubPath := p.scionLayer.UBPath(ub) @@ -2768,8 +2768,8 @@ func (p *scionPacketProcessor) updateNonConsDirIngressSegID( /*@ ghost ub []byte // means this comes from this AS itself, so nothing has to be done. // TODO(lukedirtwalker): For packets destined to peer links this shouldn't // be updated. - // @ reveal p.EqAbsInfoField(absPkt(ub)) - // @ reveal p.EqAbsHopField(absPkt(ub)) + // @ reveal p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) + // @ reveal p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) if !p.infoField.ConsDir && p.ingressID != 0 { p.infoField.UpdateSegID(p.hopField.Mac /*@, p.hopField.Abs() @*/) // @ reveal p.LastHopLen(ub) @@ -2780,9 +2780,9 @@ func (p *scionPacketProcessor) updateNonConsDirIngressSegID( /*@ ghost ub []byte // @ sl.SplitRange_Bytes(ub, start, end, HalfPerm) // @ sl.SplitByIndex_Bytes(ub, 0, start, slayers.CmnHdrLen, R54) // @ sl.Reslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) - // @ slayers.IsSupportedPktSubslice(ub, slayers.CmnHdrLen) + // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ p.AbsPktToSubSliceAbsPkt(ub, start, end) - // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, start) + // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), start) // @ sl.SplitRange_Bytes(ub, start, end, HalfPerm) if err := p.path.SetInfoField(p.infoField, int( /*@ unfolding acc(p.path.Mem(ubPath), R45) in (unfolding acc(p.path.Base.Mem(), R50) in @*/ p.path.PathMeta.CurrINF) /*@ ) , ubPath, @*/); err != nil { // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) @@ -2791,20 +2791,20 @@ func (p *scionPacketProcessor) updateNonConsDirIngressSegID( /*@ ghost ub []byte return serrors.WrapStr("update info field", err) } // @ ghost sl.CombineRange_Bytes(ub, start, end, HalfPerm) - // @ slayers.IsSupportedPktSubslice(ub, slayers.CmnHdrLen) + // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, start, slayers.CmnHdrLen, R54) - // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, start) + // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, sl.View(ub, 0, len(ub)), start) // @ p.SubSliceAbsPktToAbsPkt(ub, start, end) // @ ghost sl.CombineRange_Bytes(ub, start, end, HalfPerm) - // @ absPktFutureLemma(ub) - // @ assert absPkt(ub).CurrSeg.UInfo == + // @ absPktFutureLemma(sl.View(ub, 0, len(ub))) + // @ assert absPkt(sl.View(ub, 0, len(ub))).CurrSeg.UInfo == // @ old(io.upd_uinfo(path.AbsUInfoFromUint16(p.infoField.SegID), p.hopField.Abs())) - // @ assert reveal p.EqAbsInfoField(absPkt(ub)) - // @ assert reveal p.EqAbsHopField(absPkt(ub)) + // @ assert reveal p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) + // @ assert reveal p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) // @ assert reveal p.LastHopLen(ub) } - // @ assert absPkt(ub) == reveal AbsUpdateNonConsDirIngressSegID(old(absPkt(ub)), path.ifsToIO_ifs(p.ingressID)) + // @ assert absPkt(sl.View(ub, 0, len(ub))) == reveal AbsUpdateNonConsDirIngressSegID(old(absPkt(sl.View(ub, 0, len(ub)))), path.ifsToIO_ifs(p.ingressID)) return nil } @@ -2881,22 +2881,22 @@ func (p *scionPacketProcessor) currentHopPointer( /*@ ghost ubScionL []byte @*/ // @ ensures reserr == nil ==> // @ respr === processResult{} // contracts for IO-spec -// @ requires slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ requires absPkt(ubScionL).PathNotFullyTraversed() -// @ requires p.EqAbsHopField(absPkt(ubScionL)) -// @ requires p.EqAbsInfoField(absPkt(ubScionL)) -// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ ensures reserr == nil ==> absPkt(ubScionL).PathNotFullyTraversed() -// @ ensures reserr == nil ==> AbsVerifyCurrentMACConstraint(absPkt(ubScionL), dp) -// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(ubScionL)) == slayers.IsSupportedPkt(ubScionL) -// @ ensures reserr == nil ==> absPkt(ubScionL) == old(absPkt(ubScionL)) +// @ requires slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ requires absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() +// @ requires p.EqAbsHopField(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ requires p.EqAbsInfoField(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() +// @ ensures reserr == nil ==> AbsVerifyCurrentMACConstraint(absPkt(sl.View(ubScionL, 0, len(ubScionL))), dp) +// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL)))) == slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL))) +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))) == old(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) // @ ensures reserr == nil ==> p.DstIsLocalIngressID(ubScionL) == old(p.DstIsLocalIngressID(ubScionL)) // @ ensures reserr == nil ==> p.LastHopLen(ubScionL) == old(p.LastHopLen(ubScionL)) // @ ensures reserr != nil && respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases func (p *scionPacketProcessor) verifyCurrentMAC( /*@ ghost dp io.DataPlaneSpec, ghost ubScionL []byte, ghost ubLL []byte, ghost startLL int, ghost endLL int @*/ ) (respr processResult, reserr error) { - // @ ghost oldPkt := absPkt(ubScionL) + // @ ghost oldPkt := absPkt(sl.View(ubScionL, 0, len(ubScionL))) fullMac := path.FullMAC(p.mac, p.infoField, p.hopField, p.macBuffers.scionInput) // @ fold acc(sl.Bytes(p.hopField.Mac[:path.MacLen], 0, path.MacLen), R21) // @ defer unfold acc(sl.Bytes(p.hopField.Mac[:path.MacLen], 0, path.MacLen), R21) @@ -2928,7 +2928,7 @@ func (p *scionPacketProcessor) verifyCurrentMAC( /*@ ghost dp io.DataPlaneSpec, /*@ ubScionL, ubLL, startLL, endLL, @*/ ) // @ ghost if tmpErr != nil && tmpRes.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(tmpRes.OutPkt, tmpRes.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmpRes.OutPkt, 0, len(tmpRes.OutPkt)), tmpRes.EgressID) // @ } return tmpRes, tmpErr } @@ -2956,8 +2956,8 @@ func (p *scionPacketProcessor) verifyCurrentMAC( /*@ ghost dp io.DataPlaneSpec, // @ requires sl.Bytes(p.buffer.UBuf(), 0, len(p.buffer.UBuf())) // @ requires acc(&p.d, R15) && acc(p.d.Mem(), _) // pres for IO: -// @ requires slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ requires absPkt(ubScionL).PathNotFullyTraversed() +// @ requires slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ requires absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() // @ preserves acc(&p.ingressID, R40) // @ preserves ubLL == nil || ubLL === ubScionL[startLL:endLL] // @ preserves acc(&p.lastLayer, R55) && p.lastLayer != nil @@ -2985,12 +2985,12 @@ func (p *scionPacketProcessor) verifyCurrentMAC( /*@ ghost dp io.DataPlaneSpec, // @ ensures reserr == nil ==> // @ respr === processResult{} // posts for IO: -// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ ensures reserr == nil ==> absPkt(ubScionL).PathNotFullyTraversed() -// @ ensures reserr == nil ==> absPkt(ubScionL) == old(absPkt(ubScionL)) -// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(ubScionL)) == slayers.IsSupportedPkt(ubScionL) +// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))) == old(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL)))) == slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL))) // @ ensures reserr != nil && respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases 0 if sync.IgnoreBlockingForTermination() func (p *scionPacketProcessor) resolveInbound( /*@ ghost ubScionL []byte, ghost ubLL []byte, ghost startLL int, ghost endLL int @*/ ) (resaddr *net.UDPAddr, respr processResult, reserr error /*@ , ghost addrAliasesUb bool @*/) { // (VerifiedSCION) the parameter used to be p.scionLayer, @@ -3009,7 +3009,7 @@ func (p *scionPacketProcessor) resolveInbound( /*@ ghost ubScionL []byte, ghost err, /*@ ubScionL, ubLL, startLL, endLL, @*/) // @ ghost if err != nil && r.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(r.OutPkt, r.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(r.OutPkt, 0, len(r.OutPkt)), r.EgressID) // @ } return nil, r, err /*@ , false @*/ default: @@ -3026,10 +3026,10 @@ func (p *scionPacketProcessor) resolveInbound( /*@ ghost ubScionL []byte, ghost // @ requires acc(&p.hopField, R20) // @ requires !p.GetIsXoverSpec(ub) // Preconditions for IO: -// @ requires slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) -// @ requires absPkt(ub).PathNotFullyTraversed() -// @ requires p.EqAbsHopField(absPkt(ub)) -// @ requires p.EqAbsInfoField(absPkt(ub)) +// @ requires slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) +// @ requires absPkt(sl.View(ub, 0, len(ub))).PathNotFullyTraversed() +// @ requires p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) +// @ requires p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) // @ ensures acc(&p.infoField) // @ ensures acc(&p.hopField, R20) // @ ensures sl.Bytes(ub, 0, len(ub)) @@ -3038,10 +3038,10 @@ func (p *scionPacketProcessor) resolveInbound( /*@ ghost ubScionL []byte, ghost // @ ensures reserr != nil ==> p.scionLayer.NonInitMem() // @ ensures reserr != nil ==> reserr.ErrorMem() // Postconditions for IO: -// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) -// @ ensures reserr == nil ==> len(absPkt(ub).CurrSeg.Future) >= 0 -// @ ensures reserr == nil ==> absPkt(ub) == AbsProcessEgress(old(absPkt(ub))) -// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(ub)) == slayers.IsSupportedPkt(ub) +// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) +// @ ensures reserr == nil ==> len(absPkt(sl.View(ub, 0, len(ub))).CurrSeg.Future) >= 0 +// @ ensures reserr == nil ==> absPkt(sl.View(ub, 0, len(ub))) == AbsProcessEgress(old(absPkt(sl.View(ub, 0, len(ub))))) +// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(sl.View(ub, 0, len(ub)))) == slayers.IsSupportedPkt(sl.View(ub, 0, len(ub))) // @ decreases func (p *scionPacketProcessor) processEgress( /*@ ghost ub []byte @*/ ) (reserr error) { // @ ghost ubPath := p.scionLayer.UBPath(ub) @@ -3053,13 +3053,13 @@ func (p *scionPacketProcessor) processEgress( /*@ ghost ub []byte @*/ ) (reserr // @ sl.SplitRange_Bytes(ub, startP, endP, HalfPerm) // @ sl.SplitByIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) // @ sl.Reslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) - // @ slayers.IsSupportedPktSubslice(ub, slayers.CmnHdrLen) + // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ p.AbsPktToSubSliceAbsPkt(ub, startP, endP) - // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, startP) - // @ reveal p.EqAbsInfoField(absPkt(ub)) - // @ reveal p.EqAbsHopField(absPkt(ub)) + // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) + // @ reveal p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) + // @ reveal p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) // @ sl.SplitRange_Bytes(ub, startP, endP, HalfPerm) - // @ reveal p.scionLayer.ValidHeaderOffset(ub, startP) + // @ reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))[:startP]) // @ unfold acc(p.scionLayer.Mem(ub), R55) // we are the egress router and if we go in construction direction we // need to update the SegID. @@ -3091,16 +3091,16 @@ func (p *scionPacketProcessor) processEgress( /*@ ghost ub []byte @*/ ) (reserr return serrors.WrapStr("incrementing path", err) } // @ fold acc(p.scionLayer.Mem(ub), R55) - // @ assert reveal p.scionLayer.ValidHeaderOffset(ub, startP) + // @ assert reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))[:startP]) // @ ghost sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) - // @ slayers.IsSupportedPktSubslice(ub, slayers.CmnHdrLen) + // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) - // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, startP) + // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ p.SubSliceAbsPktToAbsPkt(ub, startP, endP) // @ ghost sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) - // @ absPktFutureLemma(ub) - // @ assert absPkt(ub) == reveal AbsProcessEgress(old(absPkt(ub))) + // @ absPktFutureLemma(sl.View(ub, 0, len(ub))) + // @ assert absPkt(sl.View(ub, 0, len(ub))) == reveal AbsProcessEgress(old(absPkt(sl.View(ub, 0, len(ub))))) // @ fold acc(p.scionLayer.Mem(ub), 1-R55) return nil } @@ -3113,7 +3113,7 @@ func (p *scionPacketProcessor) processEgress( /*@ ghost ub []byte @*/ ) (reserr // @ requires acc(&p.hopField) // @ requires acc(&p.infoField) // Preconditions for IO: -// @ requires slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) +// @ requires slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) // @ requires p.GetIsXoverSpec(ub) // @ requires let ubPath := p.scionLayer.UBPath(ub) in // @ (unfolding acc(p.scionLayer.Mem(ub), _) in p.path.GetBase(ubPath)) == currBase @@ -3131,17 +3131,17 @@ func (p *scionPacketProcessor) processEgress( /*@ ghost ub []byte @*/ ) (reserr // @ ensures respr === processResult{} // @ ensures reserr != nil ==> reserr.ErrorMem() // Postconditions for IO: -// @ ensures reserr == nil ==> len(old(absPkt(ub)).CurrSeg.Future) == 1 -// @ ensures reserr == nil ==> old(absPkt(ub)).LeftSeg != none[io.Seg] -// @ ensures reserr == nil ==> len(get(old(absPkt(ub)).LeftSeg).Future) > 0 -// @ ensures reserr == nil ==> len(get(old(absPkt(ub)).LeftSeg).History) == 0 -// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) -// @ ensures reserr == nil ==> absPkt(ub).PathNotFullyTraversed() -// @ ensures reserr == nil ==> p.EqAbsHopField(absPkt(ub)) -// @ ensures reserr == nil ==> p.EqAbsInfoField(absPkt(ub)) -// @ ensures reserr == nil ==> absPkt(ub) == AbsDoXover(old(absPkt(ub))) +// @ ensures reserr == nil ==> len(old(absPkt(sl.View(ub, 0, len(ub)))).CurrSeg.Future) == 1 +// @ ensures reserr == nil ==> old(absPkt(sl.View(ub, 0, len(ub)))).LeftSeg != none[io.Seg] +// @ ensures reserr == nil ==> len(get(old(absPkt(sl.View(ub, 0, len(ub)))).LeftSeg).Future) > 0 +// @ ensures reserr == nil ==> len(get(old(absPkt(sl.View(ub, 0, len(ub)))).LeftSeg).History) == 0 +// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) +// @ ensures reserr == nil ==> absPkt(sl.View(ub, 0, len(ub))).PathNotFullyTraversed() +// @ ensures reserr == nil ==> p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures reserr == nil ==> p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures reserr == nil ==> absPkt(sl.View(ub, 0, len(ub))) == AbsDoXover(old(absPkt(sl.View(ub, 0, len(ub))))) // @ ensures reserr == nil ==> -// @ old(slayers.IsSupportedPkt(ub)) == slayers.IsSupportedPkt(ub) +// @ old(slayers.IsSupportedPkt(sl.View(ub, 0, len(ub)))) == slayers.IsSupportedPkt(sl.View(ub, 0, len(ub))) // @ ensures reserr == nil ==> // @ let ubPath := p.scionLayer.UBPath(ub) in // @ (unfolding acc(p.scionLayer.Mem(ub), _) in @@ -3161,17 +3161,17 @@ func (p *scionPacketProcessor) doXover( /*@ ghost ub []byte, ghost currBase scio // @ sl.SplitRange_Bytes(ub, startP, endP, HalfPerm) // @ sl.SplitByIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) // @ sl.Reslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) - // @ slayers.IsSupportedPktSubslice(ub, slayers.CmnHdrLen) + // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ assert p.path == p.scionLayer.GetPath(ub) // @ p.AbsPktToSubSliceAbsPkt(ub, startP, endP) // @ assert p.path == p.scionLayer.GetPath(ub) - // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, startP) - // @ ghost preAbsPkt := p.path.absPkt(ubPath) + // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) + // @ ghost preAbsPkt := p.path.absPkt(sl.View(ubPath, 0, len(ubPath))) // @ p.path.XoverLemma(ubPath) - // @ reveal p.EqAbsInfoField(absPkt(ub)) - // @ reveal p.EqAbsHopField(absPkt(ub)) + // @ reveal p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) + // @ reveal p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) // @ sl.SplitRange_Bytes(ub, startP, endP, HalfPerm) - // @ reveal p.scionLayer.ValidHeaderOffset(ub, startP) + // @ reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))[:startP]) // @ unfold acc(p.scionLayer.Mem(ub), R55) // @ assert p.path.GetBase(ubPath) == currBase // @ ghost nextBase := currBase.IncPathSpec() @@ -3187,25 +3187,25 @@ func (p *scionPacketProcessor) doXover( /*@ ghost ub []byte, ghost currBase scio return processResult{}, serrors.WrapStr("incrementing path", err) } // @ assert p.path.GetBase(ubPath) == nextBase - // @ assert p.path.absPkt(ubPath) == scion.AbsXover(preAbsPkt) + // @ assert p.path.absPkt(sl.View(ubPath, 0, len(ubPath))) == scion.AbsXover(preAbsPkt) // @ fold acc(p.scionLayer.Mem(ub), R55) - // @ assert reveal p.scionLayer.ValidHeaderOffset(ub, startP) + // @ assert reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))[:startP]) // @ ghost sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) - // @ slayers.IsSupportedPktSubslice(ub, slayers.CmnHdrLen) + // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) // @ assert p.path == p.scionLayer.GetPath(ub) - // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, startP) - // @ assert p.scionLayer.ValidHeaderOffset(ub, len(ub)) + // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) + // @ assert p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))) // @ assert p.path == p.scionLayer.GetPath(ub) // @ p.SubSliceAbsPktToAbsPkt(ub, startP, endP) - // @ assert p.scionLayer.ValidHeaderOffset(ub, len(ub)) + // @ assert p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))) // @ assert p.path == p.scionLayer.GetPath(ub) // @ assert p.path.GetBase(ubPath) == nextBase - // @ assert len(get(old(absPkt(ub)).LeftSeg).Future) > 0 - // @ assert len(get(old(absPkt(ub)).LeftSeg).History) == 0 - // @ assert slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) - // @ assert absPkt(ub) == reveal AbsDoXover(old(absPkt(ub))) + // @ assert len(get(old(absPkt(sl.View(ub, 0, len(ub)))).LeftSeg).Future) > 0 + // @ assert len(get(old(absPkt(sl.View(ub, 0, len(ub)))).LeftSeg).History) == 0 + // @ assert slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) + // @ assert absPkt(sl.View(ub, 0, len(ub))) == reveal AbsDoXover(old(absPkt(sl.View(ub, 0, len(ub))))) // @ assert p.path == p.scionLayer.GetPath(ub) // @ assert p.path.GetBase(ubPath) == nextBase var err error @@ -3235,12 +3235,12 @@ func (p *scionPacketProcessor) doXover( /*@ ghost ub []byte, ghost currBase scio // @ assert p.path.GetBase(ubPath) == nextBase // @ p.SubSliceAbsPktToAbsPkt(ub, startP, endP) // @ ghost sl.CombineRange_Bytes(ub, startP, endP, HalfPerm/2) - // @ absPktFutureLemma(ub) + // @ absPktFutureLemma(sl.View(ub, 0, len(ub))) // @ p.path.DecodingLemma(ubPath, p.infoField, p.hopField) - // @ assert reveal p.path.EqAbsInfoField(p.path.absPkt(ubPath), p.infoField.ToAbsInfoField()) - // @ assert reveal p.path.EqAbsHopField(p.path.absPkt(ubPath), p.hopField.Abs()) - // @ assert reveal p.EqAbsHopField(absPkt(ub)) - // @ assert reveal p.EqAbsInfoField(absPkt(ub)) + // @ assert reveal p.path.EqAbsInfoField(p.path.absPkt(sl.View(ubPath, 0, len(ubPath))), p.infoField.ToAbsInfoField()) + // @ assert reveal p.path.EqAbsHopField(p.path.absPkt(sl.View(ubPath, 0, len(ubPath))), p.hopField.Abs()) + // @ assert reveal p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) + // @ assert reveal p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) // @ ghost sl.CombineRange_Bytes(ub, startP, endP, HalfPerm/2) // @ fold acc(p.scionLayer.Mem(ub), 1-R55) // @ assert currBase.IncPathSpec().Valid() @@ -3314,10 +3314,10 @@ func (p *scionPacketProcessor) egressInterface( /*@ ghost oldPkt io.Pkt @*/ ) (e // @ requires acc(&p.hopField, R20) // @ requires acc(&p.ingressID, R21) // pres for IO: -// @ requires slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) -// @ requires absPkt(ub).PathNotFullyTraversed() -// @ requires p.EqAbsInfoField(absPkt(ub)) -// @ requires p.EqAbsHopField(absPkt(ub)) +// @ requires slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) +// @ requires absPkt(sl.View(ub, 0, len(ub))).PathNotFullyTraversed() +// @ requires p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) +// @ requires p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) // @ preserves ubLL == nil || ubLL === ub[startLL:endLL] // @ preserves acc(&p.lastLayer, R55) && p.lastLayer != nil // @ preserves &p.scionLayer !== p.lastLayer ==> @@ -3340,11 +3340,11 @@ func (p *scionPacketProcessor) egressInterface( /*@ ghost oldPkt io.Pkt @*/ ) (e // @ ensures reserr == nil ==> // @ respr === processResult{} // posts for IO: -// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) -// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(ub)) == slayers.IsSupportedPkt(ub) -// @ ensures reserr == nil ==> absPkt(ub) == old(absPkt(ub)) +// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) +// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(sl.View(ub, 0, len(ub)))) == slayers.IsSupportedPkt(sl.View(ub, 0, len(ub))) +// @ ensures reserr == nil ==> absPkt(sl.View(ub, 0, len(ub))) == old(absPkt(sl.View(ub, 0, len(ub)))) // @ ensures reserr != nil && respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases 0 if sync.IgnoreBlockingForTermination() func (p *scionPacketProcessor) validateEgressUp( // @ ghost ub []byte, @@ -3352,7 +3352,7 @@ func (p *scionPacketProcessor) validateEgressUp( // @ ghost startLL int, // @ ghost endLL int, ) (respr processResult, reserr error) { - // @ ghost oldPkt := absPkt(ub) + // @ ghost oldPkt := absPkt(sl.View(ub, 0, len(ub))) egressID := p.egressInterface( /*@ oldPkt @ */ ) // @ p.d.getBfdSessionsMem() // @ ghost if p.d.bfdSessions != nil { unfold acc(accBfdSession(p.d.bfdSessions), _) } @@ -3376,7 +3376,7 @@ func (p *scionPacketProcessor) validateEgressUp( } tmpRes, tmpErr := p.packSCMP(typ, 0, scmpP, serrors.New("bfd session down") /*@, ub , ubLL, startLL, endLL, @*/) // @ ghost if tmpErr != nil && tmpRes.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(tmpRes.OutPkt, tmpRes.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmpRes.OutPkt, 0, len(tmpRes.OutPkt)), tmpRes.EgressID) // @ } return tmpRes, tmpErr } @@ -3423,24 +3423,24 @@ func (p *scionPacketProcessor) validateEgressUp( // @ ensures reserr == nil ==> // @ respr === processResult{} // constracts for IO-spec -// @ requires slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) +// @ requires slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) // @ requires p.DstIsLocalIngressID(ub) // @ requires p.LastHopLen(ub) -// @ requires absPkt(ub).PathNotFullyTraversed() -// @ requires p.EqAbsHopField(absPkt(ub)) -// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) +// @ requires absPkt(sl.View(ub, 0, len(ub))).PathNotFullyTraversed() +// @ requires p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) // @ ensures reserr == nil ==> p.DstIsLocalIngressID(ub) // @ ensures reserr == nil ==> p.LastHopLen(ub) -// @ ensures reserr == nil ==> absPkt(ub).PathNotFullyTraversed() -// @ ensures reserr == nil ==> p.EqAbsHopField(absPkt(ub)) -// @ ensures reserr == nil ==> absPkt(ub) == old(absPkt(ub)) -// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(ub)) == slayers.IsSupportedPkt(ub) +// @ ensures reserr == nil ==> absPkt(sl.View(ub, 0, len(ub))).PathNotFullyTraversed() +// @ ensures reserr == nil ==> p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures reserr == nil ==> absPkt(sl.View(ub, 0, len(ub))) == old(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(sl.View(ub, 0, len(ub)))) == slayers.IsSupportedPkt(sl.View(ub, 0, len(ub))) // @ ensures reserr != nil && respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases func (p *scionPacketProcessor) handleIngressRouterAlert( /*@ ghost ub []byte, ghost ubLL []byte, ghost startLL int, ghost endLL int @*/ ) (respr processResult, reserr error) { - // @ reveal p.EqAbsHopField(absPkt(ub)) - // @ assert let fut := absPkt(ub).CurrSeg.Future in + // @ reveal p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) + // @ assert let fut := absPkt(sl.View(ub, 0, len(ub))).CurrSeg.Future in // @ fut == seq[io.HF]{p.hopField.Abs()} ++ fut[1:] // @ ghost ubPath := p.scionLayer.UBPath(ub) // @ ghost startP := p.scionLayer.PathStartIdx(ub) @@ -3463,9 +3463,9 @@ func (p *scionPacketProcessor) handleIngressRouterAlert( /*@ ghost ub []byte, gh // @ sl.SplitRange_Bytes(ub, startP, endP, HalfPerm) // @ sl.SplitByIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) // @ sl.Reslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) - // @ slayers.IsSupportedPktSubslice(ub, slayers.CmnHdrLen) + // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ p.AbsPktToSubSliceAbsPkt(ub, startP, endP) - // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, startP) + // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ sl.SplitRange_Bytes(ub, startP, endP, HalfPerm) if err := p.path.SetHopField(p.hopField, int( /*@ unfolding acc(p.path.Mem(ubPath), R50) in (unfolding acc(p.path.Base.Mem(), R55) in @*/ p.path.PathMeta.CurrHF /*@ ) @*/) /*@ , ubPath @*/); err != nil { // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) @@ -3476,13 +3476,13 @@ func (p *scionPacketProcessor) handleIngressRouterAlert( /*@ ghost ub []byte, gh return processResult{}, serrors.WrapStr("update hop field", err) } // @ sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) - // @ slayers.IsSupportedPktSubslice(ub, slayers.CmnHdrLen) + // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) - // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, startP) + // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ p.SubSliceAbsPktToAbsPkt(ub, startP, endP) - // @ absPktFutureLemma(ub) - // @ assert reveal p.EqAbsHopField(absPkt(ub)) + // @ absPktFutureLemma(sl.View(ub, 0, len(ub))) + // @ assert reveal p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) // @ assert reveal p.LastHopLen(ub) // @ assert p.scionLayer.EqAbsHeader(ub) // @ sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) @@ -3538,22 +3538,22 @@ func (p *scionPacketProcessor) ingressRouterAlertFlag() (res *bool) { // @ ensures reserr == nil ==> // @ respr === processResult{} // constracts for IO-spec -// @ requires slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) -// @ requires absPkt(ub).PathNotFullyTraversed() -// @ requires p.EqAbsHopField(absPkt(ub)) -// @ requires p.EqAbsInfoField(absPkt(ub)) -// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(ub) && p.scionLayer.EqAbsHeader(ub) -// @ ensures reserr == nil ==> absPkt(ub).PathNotFullyTraversed() -// @ ensures reserr == nil ==> p.EqAbsHopField(absPkt(ub)) -// @ ensures reserr == nil ==> p.EqAbsInfoField(absPkt(ub)) -// @ ensures reserr == nil ==> absPkt(ub) == old(absPkt(ub)) -// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(ub)) == slayers.IsSupportedPkt(ub) +// @ requires slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) +// @ requires absPkt(sl.View(ub, 0, len(ub))).PathNotFullyTraversed() +// @ requires p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) +// @ requires p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) && p.scionLayer.EqAbsHeader(ub) +// @ ensures reserr == nil ==> absPkt(sl.View(ub, 0, len(ub))).PathNotFullyTraversed() +// @ ensures reserr == nil ==> p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures reserr == nil ==> p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures reserr == nil ==> absPkt(sl.View(ub, 0, len(ub))) == old(absPkt(sl.View(ub, 0, len(ub)))) +// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(sl.View(ub, 0, len(ub)))) == slayers.IsSupportedPkt(sl.View(ub, 0, len(ub))) // @ ensures reserr != nil && respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases func (p *scionPacketProcessor) handleEgressRouterAlert( /*@ ghost ub []byte, ghost ubLL []byte, ghost startLL int, ghost endLL int @*/ ) (respr processResult, reserr error) { - // @ reveal p.EqAbsHopField(absPkt(ub)) - // @ assert let fut := absPkt(ub).CurrSeg.Future in + // @ reveal p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) + // @ assert let fut := absPkt(sl.View(ub, 0, len(ub))).CurrSeg.Future in // @ fut == seq[io.HF]{p.hopField.Abs()} ++ fut[1:] // @ ghost ubPath := p.scionLayer.UBPath(ub) // @ ghost startP := p.scionLayer.PathStartIdx(ub) @@ -3565,7 +3565,7 @@ func (p *scionPacketProcessor) handleEgressRouterAlert( /*@ ghost ub []byte, gho // @ fold p.d.validResult(processResult{}, false) return processResult{}, nil } - egressID := p.egressInterface( /*@ absPkt(ub) @*/ ) + egressID := p.egressInterface( /*@ absPkt(sl.View(ub, 0, len(ub))) @*/ ) // @ p.d.getExternalMem() // @ if p.d.external != nil { unfold acc(accBatchConn(p.d.external), _) } if _, ok := p.d.external[egressID]; !ok { @@ -3580,9 +3580,9 @@ func (p *scionPacketProcessor) handleEgressRouterAlert( /*@ ghost ub []byte, gho // @ sl.SplitRange_Bytes(ub, startP, endP, HalfPerm) // @ sl.SplitByIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) // @ sl.Reslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) - // @ slayers.IsSupportedPktSubslice(ub, slayers.CmnHdrLen) + // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ p.AbsPktToSubSliceAbsPkt(ub, startP, endP) - // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, startP) + // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ sl.SplitRange_Bytes(ub, startP, endP, HalfPerm) if err := p.path.SetHopField(p.hopField, int( /*@ unfolding acc(p.path.Mem(ubPath), R50) in (unfolding acc(p.path.Base.Mem(), R55) in @*/ p.path.PathMeta.CurrHF /*@ ) @*/) /*@ , ubPath @*/); err != nil { // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) @@ -3593,14 +3593,14 @@ func (p *scionPacketProcessor) handleEgressRouterAlert( /*@ ghost ub []byte, gho return processResult{}, serrors.WrapStr("update hop field", err) } // @ sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) - // @ slayers.IsSupportedPktSubslice(ub, slayers.CmnHdrLen) + // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) - // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, startP) + // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ p.SubSliceAbsPktToAbsPkt(ub, startP, endP) - // @ absPktFutureLemma(ub) - // @ assert reveal p.EqAbsHopField(absPkt(ub)) - // @ assert reveal p.EqAbsInfoField(absPkt(ub)) + // @ absPktFutureLemma(sl.View(ub, 0, len(ub))) + // @ assert reveal p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) + // @ assert reveal p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) // @ sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) // @ fold acc(p.scionLayer.Mem(ub), R20) return p.handleSCMPTraceRouteRequest(egressID /*@, ub, ubLL, startLL, endLL @*/) @@ -3628,9 +3628,9 @@ func (p *scionPacketProcessor) egressRouterAlertFlag() (res *bool) { // @ requires acc(&p.infoField, R20) // @ requires acc(&p.hopField, R20) // pres for IO: -// @ requires slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ requires absPkt(ubScionL).PathNotFullyTraversed() -// @ requires p.EqAbsHopField(absPkt(ubScionL)) +// @ requires slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ requires absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() +// @ requires p.EqAbsHopField(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) // @ preserves acc(&p.ingressID, R22) // @ preserves acc(&p.mac, R20) && p.mac != nil && p.mac.Mem() // @ preserves acc(&p.macBuffers.scionInput, R20) @@ -3658,16 +3658,16 @@ func (p *scionPacketProcessor) egressRouterAlertFlag() (res *bool) { // @ respr === processResult{} // posts for IO: // @ ensures reserr == nil ==> old(p.DstIsLocalIngressID(ubScionL)) == p.DstIsLocalIngressID(ubScionL) -// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ ensures reserr == nil ==> absPkt(ubScionL).PathNotFullyTraversed() +// @ ensures reserr == nil ==> slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() // @ ensures reserr == nil ==> old(p.LastHopLen(ubScionL)) == p.LastHopLen(ubScionL) // @ ensures reserr == nil ==> -// @ old(p.EqAbsInfoField(absPkt(ubScionL))) == p.EqAbsInfoField(absPkt(ubScionL)) -// @ ensures reserr == nil ==> p.EqAbsHopField(absPkt(ubScionL)) -// @ ensures reserr == nil ==> absPkt(ubScionL) == old(absPkt(ubScionL)) -// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(ubScionL)) == slayers.IsSupportedPkt(ubScionL) +// @ old(p.EqAbsInfoField(absPkt(sl.View(ubScionL, 0, len(ubScionL))))) == p.EqAbsInfoField(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ ensures reserr == nil ==> p.EqAbsHopField(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))) == old(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL)))) == slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL))) // @ ensures reserr != nil && respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases func (p *scionPacketProcessor) handleSCMPTraceRouteRequest( interfaceID uint16 /*@, ghost ubScionL []byte, ghost ubLL []byte, ghost startLL int, ghost endLL int @*/) (respr processResult, reserr error) { @@ -3737,7 +3737,7 @@ func (p *scionPacketProcessor) handleSCMPTraceRouteRequest( // @ sl.CombineRange_Bytes(ubScionL, startLL, endLL, R1) tmpRes, tmpErr := p.packSCMP(slayers.SCMPTypeTracerouteReply, 0, &scmpP, (error)(nil) /*@ ,ubScionL, ubLL, startLL, endLL, @*/) // @ ghost if tmpErr != nil && tmpRes.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(tmpRes.OutPkt, tmpRes.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmpRes.OutPkt, 0, len(tmpRes.OutPkt)), tmpRes.EgressID) // @ } return tmpRes, tmpErr } @@ -3773,15 +3773,15 @@ func (p *scionPacketProcessor) handleSCMPTraceRouteRequest( // @ ensures reserr == nil ==> // @ respr === processResult{} // contracts for IO-spec -// @ requires slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ requires absPkt(ubScionL).PathNotFullyTraversed() +// @ requires slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ requires absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() // @ ensures reserr == nil ==> -// @ slayers.ValidPktMetaHdr(ubScionL) && p.scionLayer.EqAbsHeader(ubScionL) -// @ ensures reserr == nil ==> absPkt(ubScionL).PathNotFullyTraversed() -// @ ensures reserr == nil ==> absPkt(ubScionL) == old(absPkt(ubScionL)) -// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(ubScionL)) == slayers.IsSupportedPkt(ubScionL) +// @ slayers.ValidPktMetaHdr(sl.View(ubScionL, 0, len(ubScionL))) && p.scionLayer.EqAbsHeader(ubScionL) +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))).PathNotFullyTraversed() +// @ ensures reserr == nil ==> absPkt(sl.View(ubScionL, 0, len(ubScionL))) == old(absPkt(sl.View(ubScionL, 0, len(ubScionL)))) +// @ ensures reserr == nil ==> old(slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL)))) == slayers.IsSupportedPkt(sl.View(ubScionL, 0, len(ubScionL))) // @ ensures reserr != nil && respr.OutPkt != nil ==> -// @ absIO_val(respr.OutPkt, respr.EgressID).isValUnsupported +// @ absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID).isValUnsupported // @ decreases func (p *scionPacketProcessor) validatePktLen( /*@ ghost ubScionL []byte, ghost ubLL []byte, ghost startLL int, ghost endLL int @*/ ) (respr processResult, reserr error) { // @ unfold acc(p.scionLayer.Mem(ubScionL), R20) @@ -3799,7 +3799,7 @@ func (p *scionPacketProcessor) validatePktLen( /*@ ghost ubScionL []byte, ghost /*@ ubScionL, ubLL, startLL, endLL, @*/ ) // @ ghost if tmpErr != nil && tmpRes.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(tmpRes.OutPkt, tmpRes.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmpRes.OutPkt, 0, len(tmpRes.OutPkt)), tmpRes.EgressID) // @ } return tmpRes, tmpErr } @@ -3854,12 +3854,12 @@ func (p *scionPacketProcessor) validatePktLen( /*@ ghost ubScionL []byte, ghost // @ requires p.scionLayer.EqAbsHeader(ub) && p.scionLayer.EqPathType(ub) && p.scionLayer.ValidScionInitSpec(ub) // @ requires acc(ioLock.LockP(), _) // @ requires ioLock.LockInv() == SharedInv{dp, ioSharedArg} -// @ requires let absPkt := absIO_val(ub, p.ingressID) in +// @ requires let absPkt := absIO_val(sl.View(ub, 0, len(ub)), p.ingressID) in // @ absPkt.isValPkt ==> ElemWitness(ioSharedArg.IBufY, path.ifsToIO_ifs(p.ingressID), absPkt.ValPkt_2) // @ ensures reserr == nil && newAbsPkt.isValPkt ==> // @ ElemWitness(ioSharedArg.OBufY, newAbsPkt.ValPkt_1, newAbsPkt.ValPkt_2) // @ ensures respr.OutPkt != nil ==> -// @ newAbsPkt == absIO_val(respr.OutPkt, respr.EgressID) +// @ newAbsPkt == absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID) // @ ensures reserr != nil && respr.OutPkt != nil ==> // @ newAbsPkt.isValUnsupported // @ ensures (respr.OutPkt == nil) == (newAbsPkt == io.ValUnit{}) @@ -3880,12 +3880,12 @@ func (p *scionPacketProcessor) process( return r, err /*@, false, absReturnErr(r) @*/ } // @ ghost var oldPkt io.Pkt - // @ ghost if(slayers.IsSupportedPkt(ub)) { - // @ absIO_valLemma(ub, p.ingressID) - // @ oldPkt = absIO_val(ub, p.ingressID).ValPkt_2 + // @ ghost if(slayers.IsSupportedPkt(sl.View(ub, 0, len(ub)))) { + // @ absIO_valLemma(sl.View(ub, 0, len(ub)), p.ingressID) + // @ oldPkt = absIO_val(sl.View(ub, 0, len(ub)), p.ingressID).ValPkt_2 // @ } else { - // @ absPktFutureLemma(ub) - // @ oldPkt = absPkt(ub) + // @ absPktFutureLemma(sl.View(ub, 0, len(ub))) + // @ oldPkt = absPkt(sl.View(ub, 0, len(ub))) // @ } // @ nextPkt := oldPkt if r, err := p.validateHopExpiry( /*@ ub, ubLL, startLL, endLL @*/ ); err != nil { @@ -3913,8 +3913,8 @@ func (p *scionPacketProcessor) process( // @ p.scionLayer.DowngradePerm(ub) return processResult{}, err /*@, false, absReturnErr(processResult{}) @*/ } - // @ assert absPkt(ub) == AbsUpdateNonConsDirIngressSegID(oldPkt, path.ifsToIO_ifs(p.ingressID)) - // @ nextPkt = absPkt(ub) + // @ assert absPkt(sl.View(ub, 0, len(ub))) == AbsUpdateNonConsDirIngressSegID(oldPkt, path.ifsToIO_ifs(p.ingressID)) + // @ nextPkt = absPkt(sl.View(ub, 0, len(ub))) // @ AbsValidateIngressIDLemma(oldPkt, nextPkt, path.ifsToIO_ifs(p.ingressID)) if r, err := p.verifyCurrentMAC( /*@ dp, ub, ubLL, startLL, endLL @*/ ); err != nil { // @ p.scionLayer.DowngradePerm(ub) @@ -3925,7 +3925,7 @@ func (p *scionPacketProcessor) process( // @ p.scionLayer.DowngradePerm(ub) return r, err /*@, false, absReturnErr(r) @*/ } - // @ assert nextPkt == absPkt(ub) + // @ assert nextPkt == absPkt(sl.View(ub, 0, len(ub))) // Inbound: pkts destined to the local IA. // @ p.d.getLocalIA() if /*@ unfolding acc(p.scionLayer.Mem(ub), R50) in (unfolding acc(p.scionLayer.HeaderMem(ub[slayers.CmnHdrLen:]), R55) in @*/ p.scionLayer.DstIA /*@ ) @*/ == p.d.localIA { @@ -3945,10 +3945,10 @@ func (p *scionPacketProcessor) process( // @ unfold p.d.validResult(r, aliasesUb) // @ fold p.d.validResult(processResult{OutConn: p.d.internal, OutAddr: a, OutPkt: p.rawPkt}, aliasesUb) // @ assert ub === p.rawPkt - // @ ghost if(slayers.IsSupportedPkt(ub)) { + // @ ghost if(slayers.IsSupportedPkt(sl.View(ub, 0, len(ub)))) { // @ InternalEnterEvent(oldPkt, path.ifsToIO_ifs(p.ingressID), nextPkt, none[io.Ifs], ioLock, ioSharedArg, dp) // @ } - // @ newAbsPkt = reveal absIO_val(p.rawPkt, 0) + // @ newAbsPkt = reveal absIO_val(sl.View(p.rawPkt, 0, len(p.rawPkt)), 0) return processResult{OutConn: p.d.internal, OutAddr: a, OutPkt: p.rawPkt}, nil /*@, aliasesUb, newAbsPkt @*/ } // Outbound: pkts leaving the local IA. @@ -3963,9 +3963,9 @@ func (p *scionPacketProcessor) process( // @ fold p.d.validResult(processResult{}, false) return r, err /*@, false, absReturnErr(r) @*/ } - // @ assert absPkt(ub) == AbsDoXover(nextPkt) + // @ assert absPkt(sl.View(ub, 0, len(ub))) == AbsDoXover(nextPkt) // @ AbsValidateIngressIDXoverLemma(nextPkt, AbsDoXover(nextPkt), path.ifsToIO_ifs(p.ingressID)) - // @ nextPkt = absPkt(ub) + // @ nextPkt = absPkt(sl.View(ub, 0, len(ub))) if r, err := p.validateHopExpiry( /*@ ub, ubLL, startLL, endLL @*/ ); err != nil { // @ p.scionLayer.DowngradePerm(ub) return r, serrors.WithCtx(err, "info", "after xover") /*@, false, absReturnErr(r) @*/ @@ -3995,12 +3995,12 @@ func (p *scionPacketProcessor) process( // @ p.scionLayer.DowngradePerm(ub) return r, err /*@, false, absReturnErr(r) @*/ } - // @ assert nextPkt == absPkt(ub) + // @ assert nextPkt == absPkt(sl.View(ub, 0, len(ub))) if r, err := p.validateEgressUp( /*@ ub, ubLL, startLL, endLL @*/ ); err != nil { // @ p.scionLayer.DowngradePerm(ub) return r, err /*@, false, absReturnErr(r) @*/ } - // @ assert nextPkt == absPkt(ub) + // @ assert nextPkt == absPkt(sl.View(ub, 0, len(ub))) egressID := p.egressInterface( /*@ nextPkt @*/ ) // @ assert AbsEgressInterfaceConstraint(nextPkt, path.ifsToIO_ifs(egressID)) // @ p.d.getExternalMem() @@ -4013,16 +4013,16 @@ func (p *scionPacketProcessor) process( return processResult{}, err /*@, false, absReturnErr(processResult{}) @*/ } // @ p.d.InDomainExternalInForwardingMetrics(egressID) - // @ assert absPkt(ub) == AbsProcessEgress(nextPkt) - // @ nextPkt = absPkt(ub) - // @ ghost if(slayers.IsSupportedPkt(ub)) { + // @ assert absPkt(sl.View(ub, 0, len(ub))) == AbsProcessEgress(nextPkt) + // @ nextPkt = absPkt(sl.View(ub, 0, len(ub))) + // @ ghost if(slayers.IsSupportedPkt(sl.View(ub, 0, len(ub)))) { // @ ghost if(!p.segmentChange) { // @ ExternalEnterOrExitEvent(oldPkt, path.ifsToIO_ifs(p.ingressID), nextPkt, path.ifsToIO_ifs(egressID), ioLock, ioSharedArg, dp) // @ } else { // @ XoverEvent(oldPkt, path.ifsToIO_ifs(p.ingressID), nextPkt, path.ifsToIO_ifs(egressID), ioLock, ioSharedArg, dp) // @ } // @ } - // @ newAbsPkt = reveal absIO_val(p.rawPkt, egressID) + // @ newAbsPkt = reveal absIO_val(sl.View(p.rawPkt, 0, len(p.rawPkt)), egressID) // @ fold p.d.validResult(processResult{EgressID: egressID, OutConn: c, OutPkt: p.rawPkt}, false) return processResult{EgressID: egressID, OutConn: c, OutPkt: p.rawPkt}, nil /*@, false, newAbsPkt @*/ } @@ -4033,14 +4033,14 @@ func (p *scionPacketProcessor) process( // @ ghost if p.d.internalNextHops != nil { unfold acc(accAddr(p.d.internalNextHops), _) } if a, ok := p.d.internalNextHops[egressID]; ok { // @ p.d.getInternal() - // @ ghost if(slayers.IsSupportedPkt(ub)) { + // @ ghost if(slayers.IsSupportedPkt(sl.View(ub, 0, len(ub)))) { // @ if(!p.segmentChange) { // @ InternalEnterEvent(oldPkt, path.ifsToIO_ifs(p.ingressID), nextPkt, none[io.Ifs], ioLock, ioSharedArg, dp) // @ } else { // @ XoverEvent(oldPkt, path.ifsToIO_ifs(p.ingressID), nextPkt, none[io.Ifs], ioLock, ioSharedArg, dp) // @ } // @ } - // @ newAbsPkt = reveal absIO_val(p.rawPkt, 0) + // @ newAbsPkt = reveal absIO_val(sl.View(p.rawPkt, 0, len(p.rawPkt)), 0) // @ fold p.d.validResult(processResult{OutConn: p.d.internal, OutAddr: a, OutPkt: p.rawPkt}, false) return processResult{OutConn: p.d.internal, OutAddr: a, OutPkt: p.rawPkt}, nil /*@, false, newAbsPkt @*/ } @@ -4057,7 +4057,7 @@ func (p *scionPacketProcessor) process( /*@ ub, ubLL, startLL, endLL, @*/ ) // @ ghost if err != nil && tmp.OutPkt != nil { - // @ AbsUnsupportedPktIsUnsupportedVal(tmp.OutPkt, tmp.EgressID) + // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmp.OutPkt, 0, len(tmp.OutPkt)), tmp.EgressID) // @ } // @ p.scionLayer.DowngradePerm(ub) return tmp, err /*@, false, absReturnErr(tmp) @*/ @@ -4090,10 +4090,10 @@ func (p *scionPacketProcessor) process( // @ ensures reserr != nil ==> reserr.ErrorMem() // contracts for IO-spec // @ requires p.scionLayer.EqPathType(p.rawPkt) -// @ requires !slayers.IsSupportedPkt(p.rawPkt) +// @ requires !slayers.IsSupportedPkt(sl.View(p.rawPkt, 0, len(p.rawPkt))) // @ ensures (respr.OutPkt == nil) == (newAbsPkt == io.ValUnit{}) // @ ensures respr.OutPkt != nil ==> -// @ newAbsPkt == absIO_val(respr.OutPkt, respr.EgressID) && +// @ newAbsPkt == absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID) && // @ newAbsPkt.isValUnsupported // @ decreases 0 if sync.IgnoreBlockingForTermination() func (p *scionPacketProcessor) processOHP() (respr processResult, reserr error /*@ , ghost addrAliasesPkt bool, ghost newAbsPkt io.Val @*/) { @@ -4195,7 +4195,7 @@ func (p *scionPacketProcessor) processOHP() (respr processResult, reserr error / // @ p.d.InDomainExternalInForwardingMetrics(ohp.FirstHop.ConsEgress) // @ fold p.d.validResult(processResult{EgressID: ohp.FirstHop.ConsEgress, OutConn: c, OutPkt: p.rawPkt}, false) return processResult{EgressID: ohp.FirstHop.ConsEgress, OutConn: c, OutPkt: p.rawPkt}, - nil /*@ , false, reveal absIO_val(respr.OutPkt, respr.EgressID) @*/ + nil /*@ , false, reveal absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID) @*/ } // TODO parameter problem invalid interface // @ establishCannotRoute() @@ -4260,7 +4260,7 @@ func (p *scionPacketProcessor) processOHP() (respr processResult, reserr error / // @ p.d.getInternal() // @ assert p.d.internal != nil ==> acc(p.d.internal.Mem(), _) // @ fold p.d.validResult(processResult{OutConn: p.d.internal, OutAddr: a, OutPkt: p.rawPkt}, addrAliases) - return processResult{OutConn: p.d.internal, OutAddr: a, OutPkt: p.rawPkt}, nil /*@ , addrAliases, reveal absIO_val(respr.OutPkt, 0) @*/ + return processResult{OutConn: p.d.internal, OutAddr: a, OutPkt: p.rawPkt}, nil /*@ , addrAliases, reveal absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), 0) @*/ } // @ requires acc(d.Mem(), _) @@ -4351,12 +4351,12 @@ func addEndhostPort(dst *net.IPAddr) (res *net.UDPAddr) { // @ preserves sl.Bytes(buffer.UBuf(), 0, len(buffer.UBuf())) // pres for IO: // @ requires s.EqPathType(rawPkt) -// @ requires !slayers.IsSupportedPkt(rawPkt) +// @ requires !slayers.IsSupportedPkt(sl.View(rawPkt, 0, len(rawPkt))) // @ ensures sl.Bytes(rawPkt, 0, len(rawPkt)) // @ ensures acc(s.Mem(rawPkt), R00) // @ ensures res != nil ==> res.ErrorMem() // post for IO: -// @ ensures res == nil ==> !slayers.IsSupportedPkt(rawPkt) +// @ ensures res == nil ==> !slayers.IsSupportedPkt(sl.View(rawPkt, 0, len(rawPkt))) // @ decreases // (VerifiedSCION) the type of 's' was changed from slayers.SCION to *slayers.SCION. This makes // specs a lot easier and, makes the implementation faster as well by avoiding passing large data-structures @@ -4368,12 +4368,12 @@ func updateSCIONLayer(rawPkt []byte, s *slayers.SCION, buffer gopacket.Serialize if err := s.SerializeTo(buffer, gopacket.SerializeOptions{} /*@ , rawPkt @*/); err != nil { return err } - // @ reveal slayers.IsSupportedRawPkt(buffer.View()) + // @ reveal slayers.IsSupportedPkt(buffer.View()) // TODO(lukedirtwalker): We should add a method to the scion layers // which can write into the existing buffer, see also the discussion in // https://fsnets.slack.com/archives/C8ADBBG0J/p1592805884250700 rawContents := buffer.Bytes() - // @ assert !(reveal slayers.IsSupportedPkt(rawContents)) + // @ assert !(reveal slayers.IsSupportedPkt(sl.View(rawContents, 0, len(rawContents)))) // @ s.ValidSizeOhpUb(rawPkt) // @ assert len(rawContents) <= len(rawPkt) // @ unfold sl.Bytes(rawPkt, 0, len(rawPkt)) @@ -4386,7 +4386,7 @@ func updateSCIONLayer(rawPkt []byte, s *slayers.SCION, buffer gopacket.Serialize copy(rawPkt[:len(rawContents)], rawContents /*@ , R20 @*/) // @ fold sl.Bytes(rawPkt, 0, len(rawPkt)) // @ fold acc(sl.Bytes(rawContents, 0, len(rawContents)), R20) - // @ assert !(reveal slayers.IsSupportedPkt(rawPkt)) + // @ assert !(reveal slayers.IsSupportedPkt(sl.View(rawPkt, 0, len(rawPkt)))) return nil } @@ -4502,7 +4502,7 @@ func (b *bfdSend) Send(bfd *layers.BFD) error { // @ result === p.buffer.UBuf() // @ ensures reserr != nil && reserr.ErrorMem() // @ ensures result != nil ==> -// @ !slayers.IsSupportedPkt(result) +// @ !slayers.IsSupportedPkt(sl.View(result, 0, len(result))) // @ decreases func (p *scionPacketProcessor) prepareSCMP( typ slayers.SCMPType, diff --git a/router/io-spec-lemmas.gobra b/router/io-spec-lemmas.gobra index 81ffb39d1..968b547fd 100644 --- a/router/io-spec-lemmas.gobra +++ b/router/io-spec-lemmas.gobra @@ -28,13 +28,12 @@ import ( ) ghost -preserves acc(sl.Bytes(raw, 0, len(raw)), R55) ensures slayers.ValidPktMetaHdr(raw) && slayers.IsSupportedPkt(raw) ==> absIO_val(raw, ingressID).isValPkt && absIO_val(raw, ingressID).ValPkt_2 == absPkt(raw) && absPkt(raw).PathNotFullyTraversed() decreases -func absIO_valLemma(raw []byte, ingressID uint16) { +func absIO_valLemma(raw seq[byte], ingressID uint16) { if(slayers.ValidPktMetaHdr(raw) && slayers.IsSupportedPkt(raw)){ absIO := reveal absIO_val(raw, ingressID) assert absIO.isValPkt @@ -44,19 +43,16 @@ func absIO_valLemma(raw []byte, ingressID uint16) { } ghost -requires acc(sl.Bytes(raw, 0, len(raw)), R56) requires slayers.ValidPktMetaHdr(raw) -ensures acc(sl.Bytes(raw, 0, len(raw)), R56) ensures slayers.ValidPktMetaHdr(raw) ensures absPkt(raw).PathNotFullyTraversed() decreases -func absPktFutureLemma(raw []byte) { +func absPktFutureLemma(raw seq[byte]) { reveal slayers.ValidPktMetaHdr(raw) headerOffset := slayers.GetAddressOffset(raw) headerOffsetWithMetaLen := headerOffset + scion.MetaLen - assert forall k int :: {&raw[headerOffset:headerOffset+scion.MetaLen][k]} 0 <= k && k < scion.MetaLen ==> &raw[headerOffset:headerOffset+scion.MetaLen][k] == &raw[headerOffset + k] - hdr := (unfolding acc(sl.Bytes(raw, 0, len(raw)), R56) in - binary.BigEndian.Uint32(raw[headerOffset:headerOffset+scion.MetaLen])) + hdr := binary.BigEndian.Uint32Spec(raw[headerOffset], + raw[headerOffset+1], raw[headerOffset+2], raw[headerOffset+3]) metaHdr := scion.DecodedFrom(hdr) currInfIdx := int(metaHdr.CurrINF) currHfIdx := int(metaHdr.CurrHF) @@ -118,12 +114,12 @@ requires p.scionLayer.Mem(ub) requires acc(&p.d) && p.d.Mem() requires acc(&p.ingressID) requires sl.Bytes(ub, 0, len(ub)) -requires slayers.ValidPktMetaHdr(ub) +requires slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) decreases pure func (p *scionPacketProcessor) LastHopLen(ub []byte) bool { return (unfolding p.scionLayer.Mem(ub) in (unfolding p.scionLayer.HeaderMem(ub[slayers.CmnHdrLen:]) in p.scionLayer.DstIA) == (unfolding p.d.Mem() in p.d.localIA)) ==> - len(absPkt(ub).CurrSeg.Future) == 1 + len(absPkt(sl.View(ub, 0, len(ub))).CurrSeg.Future) == 1 } //TODO: Does not work with --disableNL --unsafeWildcardoptimization @@ -132,7 +128,7 @@ requires acc(p.scionLayer.Mem(ub), R50) requires acc(&p.d, R55) && acc(p.d.Mem(), _) requires acc(&p.ingressID, R55) requires acc(sl.Bytes(ub, 0, len(ub)), R56) -requires slayers.ValidPktMetaHdr(ub) +requires slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) requires p.DstIsLocalIngressID(ub) requires p.LastHopLen(ub) requires (unfolding acc(p.scionLayer.Mem(ub), R50) in @@ -142,9 +138,9 @@ ensures acc(p.scionLayer.Mem(ub), R50) ensures acc(&p.d, R55) && acc(p.d.Mem(), _) ensures acc(&p.ingressID, R55) ensures acc(sl.Bytes(ub, 0, len(ub)), R56) -ensures slayers.ValidPktMetaHdr(ub) +ensures slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) ensures p.ingressID != 0 -ensures len(absPkt(ub).CurrSeg.Future) == 1 +ensures len(absPkt(sl.View(ub, 0, len(ub))).CurrSeg.Future) == 1 decreases func (p* scionPacketProcessor) LocalDstLemma(ub []byte) { reveal p.DstIsLocalIngressID(ub) @@ -222,23 +218,22 @@ ensures acc(sl.Bytes(ub[start:end], 0, len(ub[start:end])), R50) ensures acc(&p.path, R55) && acc(p.path.Mem(ub[start:end]), R55) ensures start == p.scionLayer.PathStartIdx(ub) ensures end == p.scionLayer.PathEndIdx(ub) -ensures p.path.GetBase(ub[start:end]).EqAbsHeader(ub[start:end]) +ensures p.path.GetBase(ub[start:end]).EqAbsHeader(sl.View(ub[start:end], 0, len(ub[start:end]))) ensures p.path.GetBase(ub[start:end]).WeaklyValid() -ensures p.scionLayer.ValidHeaderOffset(ub, len(ub)) +ensures p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))) decreases func (p* scionPacketProcessor) EstablishEqAbsHeader(ub []byte, start int, end int) { - unfold acc(sl.Bytes(ub, 0, len(ub)), R56) - unfold acc(sl.Bytes(ub[start:end], 0, len(ub[start:end])), R56) + sl.ViewOfSubslice(ub, start, end, R56) + assert sl.View(ub[start:end], 0, len(ub[start:end])) == + sl.View(ub, 0, len(ub))[start:end] unfold acc(p.scionLayer.Mem(ub), R56) unfold acc(p.path.Mem(ub[start:end]), R56) reveal p.scionLayer.EqAbsHeader(ub) reveal p.scionLayer.ValidScionInitSpec(ub) - assert reveal p.scionLayer.ValidHeaderOffset(ub, len(ub)) - assert p.path.GetBase(ub[start:end]).EqAbsHeader(ub[start:end]) + assert reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))) + assert p.path.GetBase(ub[start:end]).EqAbsHeader(sl.View(ub[start:end], 0, len(ub[start:end]))) fold acc(p.path.Mem(ub[start:end]), R56) fold acc(p.scionLayer.Mem(ub), R56) - fold acc(sl.Bytes(ub[start:end], 0, len(ub[start:end])), R56) - fold acc(sl.Bytes(ub, 0, len(ub)), R56) } ghost @@ -248,7 +243,7 @@ requires acc(sl.Bytes(ub, 0, len(ub)), R50) requires acc(sl.Bytes(ub[start:end], 0, len(ub[start:end])), R50) requires acc(&p.path, R55) && acc(p.path.Mem(ub[start:end]), R55) requires p.path === p.scionLayer.GetPath(ub) -requires slayers.ValidPktMetaHdr(ub) +requires slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) requires start == p.scionLayer.PathStartIdx(ub) requires end == p.scionLayer.PathEndIdx(ub) requires p.scionLayer.EqAbsHeader(ub) @@ -256,34 +251,32 @@ ensures acc(sl.Bytes(ub, 0, len(ub)), R50) ensures acc(p.scionLayer.Mem(ub), R55) ensures acc(sl.Bytes(ub[start:end], 0, len(ub[start:end])), R50) ensures acc(&p.path, R55) && acc(p.path.Mem(ub[start:end]), R55) -ensures slayers.ValidPktMetaHdr(ub) +ensures slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) ensures p.scionLayer.EqAbsHeader(ub) ensures start == p.scionLayer.PathStartIdx(ub) ensures end == p.scionLayer.PathEndIdx(ub) -ensures scion.validPktMetaHdr(ub[start:end]) -ensures p.path.GetBase(ub[start:end]).EqAbsHeader(ub[start:end]) -ensures p.scionLayer.ValidHeaderOffset(ub, len(ub)) +ensures scion.validPktMetaHdr(sl.View(ub[start:end], 0, len(ub[start:end]))) +ensures p.path.GetBase(ub[start:end]).EqAbsHeader(sl.View(ub[start:end], 0, len(ub[start:end]))) +ensures p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))) ensures p.path === p.scionLayer.GetPath(ub) -ensures absPkt(ub) == p.path.absPkt(ub[start:end]) +ensures absPkt(sl.View(ub, 0, len(ub))) == p.path.absPkt(sl.View(ub[start:end], 0, len(ub[start:end]))) decreases func (p* scionPacketProcessor) AbsPktToSubSliceAbsPkt(ub []byte, start int, end int) { - unfold acc(sl.Bytes(ub, 0, len(ub)), R56) - unfold acc(sl.Bytes(ub[start:end], 0, len(ub[start:end])), R56) - reveal slayers.ValidPktMetaHdr(ub) + v := sl.View(ub, 0, len(ub)) + sl.ViewOfSubslice(ub, start, end, R56) + assert sl.View(ub[start:end], 0, len(ub[start:end])) == v[start:end] + reveal slayers.ValidPktMetaHdr(v) reveal p.scionLayer.EqAbsHeader(ub) - assert reveal scion.validPktMetaHdr(ub[start:end]) + assert reveal scion.validPktMetaHdr(v[start:end]) unfold acc(p.scionLayer.Mem(ub), R56) - reveal p.scionLayer.ValidHeaderOffset(ub, len(ub)) - assert p.path.GetBase(ub[start:end]).EqAbsHeader(ub[start:end]) + reveal p.scionLayer.ValidHeaderOffset(ub, v) + assert p.path.GetBase(ub[start:end]).EqAbsHeader(v[start:end]) fold acc(p.scionLayer.Mem(ub), R56) - assert start == slayers.GetAddressOffset(ub) + assert start == slayers.GetAddressOffset(v) - hdr1 := binary.BigEndian.Uint32(ub[start:start+scion.MetaLen]) - hdr2 := binary.BigEndian.Uint32(ub[start:end][:scion.MetaLen]) - assert hdr1 == hdr2 - hdr := hdr1 - fold acc(sl.Bytes(ub[start:end], 0, len(ub[start:end])), R56) - fold acc(sl.Bytes(ub, 0, len(ub)), R56) + hdr := binary.BigEndian.Uint32Spec(v[start], v[start+1], v[start+2], v[start+3]) + assert hdr == binary.BigEndian.Uint32Spec(v[start:end][0], v[start:end][1], + v[start:end][2], v[start:end][3]) headerOffsetWithMetaLen := start + scion.MetaLen metaHdr := scion.DecodedFrom(hdr) currInfIdx := int(metaHdr.CurrINF) @@ -297,11 +290,11 @@ func (p* scionPacketProcessor) AbsPktToSubSliceAbsPkt(ub []byte, start int, end numINF := segs.NumInfoFields() offset := scion.HopFieldOffset(numINF, prevSegLen, headerOffsetWithMetaLen) - scion.WidenCurrSeg(ub, offset, currInfIdx, currHfIdx-prevSegLen, segLen, headerOffsetWithMetaLen, start, end) - scion.WidenLeftSeg(ub, currInfIdx + 1, segs, headerOffsetWithMetaLen, start, end) - scion.WidenMidSeg(ub, currInfIdx + 2, segs, headerOffsetWithMetaLen, start, end) - scion.WidenRightSeg(ub, currInfIdx - 1, segs, headerOffsetWithMetaLen, start, end) - assert reveal absPkt(ub) == reveal p.path.absPkt(ub[start:end]) + scion.WidenCurrSeg(v, offset, currInfIdx, currHfIdx-prevSegLen, segLen, headerOffsetWithMetaLen, start, end) + scion.WidenLeftSeg(v, currInfIdx + 1, segs, headerOffsetWithMetaLen, start, end) + scion.WidenMidSeg(v, currInfIdx + 2, segs, headerOffsetWithMetaLen, start, end) + scion.WidenRightSeg(v, currInfIdx - 1, segs, headerOffsetWithMetaLen, start, end) + assert reveal absPkt(v) == reveal p.path.absPkt(v[start:end]) } ghost @@ -311,43 +304,41 @@ requires acc(sl.Bytes(ub, 0, len(ub)), R50) requires acc(sl.Bytes(ub[start:end], 0, len(ub[start:end])), R50) requires acc(&p.path, R55) && acc(p.path.Mem(ub[start:end]), R55) requires p.path === p.scionLayer.GetPath(ub) -requires scion.validPktMetaHdr(ub[start:end]) +requires scion.validPktMetaHdr(sl.View(ub[start:end], 0, len(ub[start:end]))) requires start == p.scionLayer.PathStartIdx(ub) requires end == p.scionLayer.PathEndIdx(ub) -requires p.path.GetBase(ub[start:end]).EqAbsHeader(ub[start:end]) -requires p.scionLayer.ValidHeaderOffset(ub, len(ub)) +requires p.path.GetBase(ub[start:end]).EqAbsHeader(sl.View(ub[start:end], 0, len(ub[start:end]))) +requires p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))) ensures acc(sl.Bytes(ub, 0, len(ub)), R50) ensures acc(p.scionLayer.Mem(ub), R55) ensures acc(sl.Bytes(ub[start:end], 0, len(ub[start:end])), R50) ensures acc(&p.path, R55) && acc(p.path.Mem(ub[start:end]), R55) -ensures slayers.ValidPktMetaHdr(ub) +ensures slayers.ValidPktMetaHdr(sl.View(ub, 0, len(ub))) ensures start == p.scionLayer.PathStartIdx(ub) ensures end == p.scionLayer.PathEndIdx(ub) -ensures scion.validPktMetaHdr(ub[start:end]) +ensures scion.validPktMetaHdr(sl.View(ub[start:end], 0, len(ub[start:end]))) ensures p.scionLayer.EqAbsHeader(ub) ensures p.path === p.scionLayer.GetPath(ub) -ensures absPkt(ub) == p.path.absPkt(ub[start:end]) -ensures p.scionLayer.ValidHeaderOffset(ub, len(ub)) +ensures absPkt(sl.View(ub, 0, len(ub))) == p.path.absPkt(sl.View(ub[start:end], 0, len(ub[start:end]))) +ensures p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))) decreases func (p* scionPacketProcessor) SubSliceAbsPktToAbsPkt(ub []byte, start int, end int){ - unfold acc(sl.Bytes(ub, 0, len(ub)), R56) - unfold acc(sl.Bytes(ub[start:end], 0, len(ub[start:end])), R56) + v := sl.View(ub, 0, len(ub)) + sl.ViewOfSubslice(ub, start, end, R56) + assert sl.View(ub[start:end], 0, len(ub[start:end])) == v[start:end] unfold acc(p.scionLayer.Mem(ub), R56) unfold acc(p.scionLayer.Path.Mem(ub[start:end]), R56) - reveal p.scionLayer.ValidHeaderOffset(ub, len(ub)) + reveal p.scionLayer.ValidHeaderOffset(ub, v) assert reveal p.scionLayer.EqAbsHeader(ub) fold acc(p.scionLayer.Path.Mem(ub[start:end]), R56) fold acc(p.scionLayer.Mem(ub), R56) - reveal scion.validPktMetaHdr(ub[start:end]) - assert reveal slayers.ValidPktMetaHdr(ub) - assert start == slayers.GetAddressOffset(ub) + reveal scion.validPktMetaHdr(v[start:end]) + assert reveal slayers.ValidPktMetaHdr(v) + assert start == slayers.GetAddressOffset(v) headerOffsetWithMetaLen := start + scion.MetaLen - hdr1 := binary.BigEndian.Uint32(ub[start:start+scion.MetaLen]) - hdr2 := binary.BigEndian.Uint32(ub[start:end][:scion.MetaLen]) - assert hdr1 == hdr2 - hdr := hdr1 - fold acc(sl.Bytes(ub[start:end], 0, len(ub[start:end])), R56) - fold acc(sl.Bytes(ub, 0, len(ub)), R56) + hdr := binary.BigEndian.Uint32Spec(v[start], v[start+1], v[start+2], v[start+3]) + assert hdr == binary.BigEndian.Uint32Spec(v[start:end][0], v[start:end][1], + v[start:end][2], v[start:end][3]) metaHdr := scion.DecodedFrom(hdr) currInfIdx := int(metaHdr.CurrINF) @@ -361,11 +352,11 @@ func (p* scionPacketProcessor) SubSliceAbsPktToAbsPkt(ub []byte, start int, end numINF := segs.NumInfoFields() offset := scion.HopFieldOffset(numINF, prevSegLen, headerOffsetWithMetaLen) - scion.WidenCurrSeg(ub, offset, currInfIdx, currHfIdx-prevSegLen, segLen, headerOffsetWithMetaLen, start, end) - scion.WidenLeftSeg(ub, currInfIdx + 1, segs, headerOffsetWithMetaLen, start, end) - scion.WidenMidSeg(ub, currInfIdx + 2, segs, headerOffsetWithMetaLen, start, end) - scion.WidenRightSeg(ub, currInfIdx - 1, segs, headerOffsetWithMetaLen, start, end) - assert reveal absPkt(ub) == reveal p.path.absPkt(ub[start:end]) + scion.WidenCurrSeg(v, offset, currInfIdx, currHfIdx-prevSegLen, segLen, headerOffsetWithMetaLen, start, end) + scion.WidenLeftSeg(v, currInfIdx + 1, segs, headerOffsetWithMetaLen, start, end) + scion.WidenMidSeg(v, currInfIdx + 2, segs, headerOffsetWithMetaLen, start, end) + scion.WidenRightSeg(v, currInfIdx - 1, segs, headerOffsetWithMetaLen, start, end) + assert reveal absPkt(v) == reveal p.path.absPkt(v[start:end]) } ghost diff --git a/router/io-spec.gobra b/router/io-spec.gobra index 21ee35375..71d902a20 100644 --- a/router/io-spec.gobra +++ b/router/io-spec.gobra @@ -32,15 +32,14 @@ import ( ghost opaque -requires sl.Bytes(raw, 0, len(raw)) requires slayers.ValidPktMetaHdr(raw) decreases -pure func absPkt(raw []byte) (res io.Pkt) { +pure func absPkt(raw seq[byte]) (res io.Pkt) { return let _ := reveal slayers.ValidPktMetaHdr(raw) in let headerOffset := slayers.GetAddressOffset(raw) in let headerOffsetWithMetaLen := headerOffset + scion.MetaLen in - let hdr := (unfolding sl.Bytes(raw, 0, len(raw)) in - binary.BigEndian.Uint32(raw[headerOffset:headerOffset+scion.MetaLen])) in + let hdr := binary.BigEndian.Uint32Spec(raw[headerOffset], + raw[headerOffset+1], raw[headerOffset+2], raw[headerOffset+3]) in let metaHdr := scion.DecodedFrom(hdr) in let currInfIdx := int(metaHdr.CurrINF) in let currHfIdx := int(metaHdr.CurrHF) in @@ -61,11 +60,10 @@ pure func absPkt(raw []byte) (res io.Pkt) { } ghost -requires sl.Bytes(raw, 0, len(raw)) ensures val.isValUnsupported ensures val.ValUnsupported_1 == path.ifsToIO_ifs(ingressID) decreases -pure func absValUnsupported(raw []byte, ingressID uint16) (val io.Val) { +pure func absValUnsupported(raw seq[byte], ingressID uint16) (val io.Val) { return io.Val(io.ValUnsupported { path.ifsToIO_ifs(ingressID), io.Unit{}, @@ -74,22 +72,19 @@ pure func absValUnsupported(raw []byte, ingressID uint16) (val io.Val) { ghost opaque -requires sl.Bytes(raw, 0, len(raw)) ensures val.isValPkt || val.isValUnsupported decreases -pure func absIO_val(raw []byte, ingressID uint16) (val io.Val) { +pure func absIO_val(raw seq[byte], ingressID uint16) (val io.Val) { return (reveal slayers.ValidPktMetaHdr(raw) && slayers.IsSupportedPkt(raw)) ? io.Val(io.ValPkt{path.ifsToIO_ifs(ingressID), absPkt(raw)}) : absValUnsupported(raw, ingressID) } ghost -requires acc(sl.Bytes(raw, 0, len(raw)), R56) requires !slayers.IsSupportedPkt(raw) -ensures acc(sl.Bytes(raw, 0, len(raw)), R56) ensures absIO_val(raw, ingressID).isValUnsupported decreases -func AbsUnsupportedPktIsUnsupportedVal(raw []byte, ingressID uint16) { +func AbsUnsupportedPktIsUnsupportedVal(raw seq[byte], ingressID uint16) { reveal absIO_val(raw, ingressID) } @@ -99,7 +94,7 @@ requires respr.OutPkt != nil ==> decreases pure func absReturnErr(respr processResult) (val io.Val) { return respr.OutPkt == nil ? io.ValUnit{} : - absIO_val(respr.OutPkt, respr.EgressID) + absIO_val(sl.View(respr.OutPkt, 0, len(respr.OutPkt)), respr.EgressID) } ghost @@ -205,5 +200,5 @@ requires sl.Bytes(msg.GetFstBuffer(), 0, len(msg.GetFstBuffer())) decreases pure func MsgToAbsVal(msg *ipv4.Message, ingressID uint16) (res io.Val) { return unfolding msg.Mem() in - absIO_val(msg.Buffers[0], ingressID) + absIO_val(sl.View(msg.Buffers[0], 0, len(msg.Buffers[0])), ingressID) } diff --git a/router/widen-lemma.gobra b/router/widen-lemma.gobra index 8c0b31711..4707c71ef 100644 --- a/router/widen-lemma.gobra +++ b/router/widen-lemma.gobra @@ -17,9 +17,7 @@ package router import ( - sl "verification/utils/slices" "verification/io" - . "verification/utils/definitions" "verification/dependencies/encoding/binary" "github.com/scionproto/scion/pkg/slayers" "github.com/scionproto/scion/pkg/slayers/path" @@ -28,16 +26,14 @@ import ( // Some things in this file can be simplified. Nonetheless, the important definition here // is absIO_valWidenLemma. Everything else can be seen as an implementation detail. +// Since packets are abstracted with views (mathematical sequences), these lemmas +// require no permissions at all. ghost -requires 0 <= length && length <= len(raw) -requires acc(sl.Bytes(raw, 0, len(raw)), R49) -requires acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R49) -ensures acc(sl.Bytes(raw, 0, len(raw)), R49) -ensures acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R49) -ensures absIO_val(raw[:length], ingressID).isValPkt ==> +requires 0 <= length && length <= len(raw) +ensures absIO_val(raw[:length], ingressID).isValPkt ==> absIO_val(raw[:length], ingressID) == absIO_val(raw, ingressID) decreases -func absIO_valWidenLemma(raw []byte, ingressID uint16, length int) { +func absIO_valWidenLemma(raw seq[byte], ingressID uint16, length int) { var ret1 io.Val var ret2 io.Val @@ -62,73 +58,54 @@ func absIO_valWidenLemma(raw []byte, ingressID uint16, length int) { ghost requires 0 <= length && length <= len(raw) -requires acc(sl.Bytes(raw, 0, len(raw)), R51) -requires acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R51) requires slayers.ValidPktMetaHdr(raw[:length]) -ensures acc(sl.Bytes(raw, 0, len(raw)), R51) -ensures acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R51) ensures slayers.ValidPktMetaHdr(raw) decreases -func ValidPktMetaHdrWidenLemma(raw []byte, length int) { - unfold acc(sl.Bytes(raw, 0, len(raw)), R56) - unfold acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R56) +func ValidPktMetaHdrWidenLemma(raw seq[byte], length int) { reveal slayers.ValidPktMetaHdr(raw[:length]) + assert raw[:length][5] == raw[5] && raw[:length][9] == raw[9] ret1 := reveal slayers.ValidPktMetaHdr(raw) ret2 := reveal slayers.ValidPktMetaHdr(raw[:length]) assert ret1 == ret2 - fold acc(sl.Bytes(raw, 0, len(raw)), R56) - fold acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R56) } ghost requires 0 <= length && length <= len(raw) -requires acc(sl.Bytes(raw, 0, len(raw)), R51) -requires acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R51) requires slayers.IsSupportedPkt(raw[:length]) -ensures acc(sl.Bytes(raw, 0, len(raw)), R51) -ensures acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R51) ensures slayers.IsSupportedPkt(raw) decreases -func IsSupportedPktWidenLemma(raw []byte, length int) { - unfold acc(sl.Bytes(raw, 0, len(raw)), R56) - unfold acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R56) +func IsSupportedPktWidenLemma(raw seq[byte], length int) { reveal slayers.IsSupportedPkt(raw[:length]) + assert raw[:length][4] == raw[4] && raw[:length][8] == raw[8] ret1 := reveal slayers.IsSupportedPkt(raw) ret2 := reveal slayers.IsSupportedPkt(raw[:length]) assert ret1 == ret2 - fold acc(sl.Bytes(raw, 0, len(raw)), R56) - fold acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R56) } ghost -requires 0 <= length && length <= len(raw) -requires acc(sl.Bytes(raw, 0, len(raw)), R50) -requires acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R50) +requires 0 <= length && length <= len(raw) requires slayers.ValidPktMetaHdr(raw) requires slayers.ValidPktMetaHdr(raw[:length]) -ensures acc(sl.Bytes(raw, 0, len(raw)), R50) -ensures acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R50) ensures slayers.ValidPktMetaHdr(raw) ensures slayers.ValidPktMetaHdr(raw[:length]) ensures absPkt(raw) == absPkt(raw[:length]) decreases -func absPktWidenLemma(raw []byte, length int) { +func absPktWidenLemma(raw seq[byte], length int) { reveal slayers.ValidPktMetaHdr(raw) reveal slayers.ValidPktMetaHdr(raw[:length]) - unfold acc(sl.Bytes(raw, 0, len(raw)), R51) - unfold acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R51) headerOffset1 := slayers.GetAddressOffset(raw) headerOffset2 := slayers.GetAddressOffset(raw[:length]) + assert raw[:length][9] == raw[9] assert headerOffset1 == headerOffset2 headerOffset := headerOffset1 headerOffsetWithMetaLen := headerOffset + scion.MetaLen - hdr1 := binary.BigEndian.Uint32(raw[headerOffset:headerOffset+scion.MetaLen]) - hdr2 := binary.BigEndian.Uint32(raw[:length][headerOffset:headerOffset+scion.MetaLen]) + hdr1 := binary.BigEndian.Uint32Spec(raw[headerOffset], + raw[headerOffset+1], raw[headerOffset+2], raw[headerOffset+3]) + hdr2 := binary.BigEndian.Uint32Spec(raw[:length][headerOffset], + raw[:length][headerOffset+1], raw[:length][headerOffset+2], raw[:length][headerOffset+3]) assert hdr1 == hdr2 hdr := hdr1 - fold acc(sl.Bytes(raw, 0, len(raw)), R51) - fold acc(sl.Bytes(raw[:length], 0, len(raw[:length])), R51) metaHdr := scion.DecodedFrom(hdr) currInfIdx := int(metaHdr.CurrINF) From bcc4ffdbd332b44666acb323355fca4b23e6fa87 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 19:08:15 +0000 Subject: [PATCH 07/35] Mark byte-parsing refactor plan as implemented Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- doc/verification/byte-slice-parsing-refactor.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/verification/byte-slice-parsing-refactor.md b/doc/verification/byte-slice-parsing-refactor.md index 76895100d..b22857b8e 100644 --- a/doc/verification/byte-slice-parsing-refactor.md +++ b/doc/verification/byte-slice-parsing-refactor.md @@ -1,5 +1,19 @@ # Plan: parsing packet bytes through `seq[byte]` views instead of slices + permissions +> **Status: implemented on this branch** (commits following this document). +> All four steps below are executed: `View` and the strengthened slice lemmas +> are in `verification/utils/slices`, the parsers in `pkg/slayers/path`, +> `pkg/slayers/path/scion`, `pkg/slayers` and `router` operate on views, and +> the OffsetEq/Widen/Subslice scaffolding is deleted or reduced to pure +> sequence lemmas. Deviations from the letter of the plan: `CurrSeg` keeps its +> packet-relative signature (offsets into the view) rather than taking +> field-exact arguments, the `*WithInfo` family is kept (seq-based) instead of +> merged, and heap-boundary predicates (`(*SCION).EqAbsHeader`, +> `CorrectlyDecoded*`, `ValidHeaderOffset`) keep a buffer argument for `Mem` +> while their contents-reasoning goes through views. The proofs have not yet +> been run through Gobra; CI is the arbiter for the proof scaffolding +> (asserts/triggers), which may need iteration. + ## Problem Ghost pure functions that parse packet fields currently take a `[]byte` plus offsets and From 17fe80580cff43b4f0a3962b4c10e2c7f12b83e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 19:48:15 +0000 Subject: [PATCH 08/35] Drop refactor plan document Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- .../byte-slice-parsing-refactor.md | 442 ------------------ 1 file changed, 442 deletions(-) delete mode 100644 doc/verification/byte-slice-parsing-refactor.md diff --git a/doc/verification/byte-slice-parsing-refactor.md b/doc/verification/byte-slice-parsing-refactor.md deleted file mode 100644 index b22857b8e..000000000 --- a/doc/verification/byte-slice-parsing-refactor.md +++ /dev/null @@ -1,442 +0,0 @@ -# Plan: parsing packet bytes through `seq[byte]` views instead of slices + permissions - -> **Status: implemented on this branch** (commits following this document). -> All four steps below are executed: `View` and the strengthened slice lemmas -> are in `verification/utils/slices`, the parsers in `pkg/slayers/path`, -> `pkg/slayers/path/scion`, `pkg/slayers` and `router` operate on views, and -> the OffsetEq/Widen/Subslice scaffolding is deleted or reduced to pure -> sequence lemmas. Deviations from the letter of the plan: `CurrSeg` keeps its -> packet-relative signature (offsets into the view) rather than taking -> field-exact arguments, the `*WithInfo` family is kept (seq-based) instead of -> merged, and heap-boundary predicates (`(*SCION).EqAbsHeader`, -> `CorrectlyDecoded*`, `ValidHeaderOffset`) keep a buffer argument for `Mem` -> while their contents-reasoning goes through views. The proofs have not yet -> been run through Gobra; CI is the arbiter for the proof scaffolding -> (asserts/triggers), which may need iteration. - -## Problem - -Ghost pure functions that parse packet fields currently take a `[]byte` plus offsets and -require `sl.Bytes(raw, 0, len(raw))` (or elementwise `acc(&raw[i])`) in their preconditions. -For example, `Timestamp` in `pkg/slayers/path/infofield_spec.gobra`: - -```gobra -ghost -requires 0 <= currINF && 0 <= headerOffset -requires InfoFieldOffset(currINF, headerOffset) + InfoLen < len(raw) -requires sl.Bytes(raw, 0, len(raw)) -decreases -pure func Timestamp(raw []byte, currINF int, headerOffset int) io.Ainfo -``` - -Because a pure function cannot perform ghost operations (`SplitRange_Bytes`, -`Reslice_Bytes`, ...), a caller holding `sl.Bytes(raw, 0, len(raw))` cannot manufacture the -predicate instance for a sub-range inside a pure context. The consequence is that *every* -function in the parsing chain must take the entire buffer plus offsets, and every -relationship between "parsed from the full buffer" and "parsed from a subslice" needs a -bespoke lemma (`BytesToAbsInfoFieldOffsetEq`, `WidenBytesHopField`, `WidenCurrSeg`, -`absIO_valWidenLemma`, `ValidPktMetaHdrSublice`, `IsSupportedPktSubslice`, -`AbsPktToSubSliceAbsPkt`, ...). These lemmas are pure permission plumbing: they unfold two -predicate instances, re-prove pointer overlap with `sl.AssertSliceOverlap`, and re-fold. - -The fix: separate *heap access* from *parsing*. Heap access is concentrated in a single -heap-dependent, opaque function (`View`) that abstracts a `sl.Bytes` range into a -mathematical `seq[byte]`. All parsing functions become permission-free pure functions over -`seq[byte]`, taking exactly the bytes of the field they parse. Sub-ranging then happens at -the sequence level (`v[a:b]`), where it is definitional, instead of at the permission level, -where it requires ghost statements. - -The codebase already anticipates this in places: -- `seqs.ToSeqByte(ub []byte) seq[byte]` (`verification/utils/seqs/seqs.gobra:48`) is an - abstract view function, characterized elementwise via `sl.GetByte`. -- `slayers.IsSupportedRawPkt(raw seq[byte])` (`pkg/slayers/scion_spec.gobra`) is a parsing - function already written over `seq[byte]`, used through `SerializeBuffer.View()`. -- `raw_spec.gobra:378` carries a `// TODO: rename this to View()` on `absPkt`. -- `binary.BigEndian.Uint16Spec/Uint32Spec` are already permission-free byte-level - functions, so no changes to the `encoding/binary` stubs are needed for reading. - -## Step 1 — `View` in `verification/utils/slices` - -Add to `verification/utils/slices/slices.gobra`: - -```gobra -ghost -opaque -requires acc(Bytes(s, start, end), _) -ensures len(res) == end - start -ensures forall i int :: { res[i] } 0 <= i && i < end - start ==> - res[i] == GetByte(s, start, end, start + i) -decreases -pure func View(s []byte, start int, end int) (res seq[byte]) { - return unfolding acc(Bytes(s, start, end), _) in ViewAux(s, start, end) -} - -// Helper over raw quantified permissions (same style as BytesToAbsInfoFieldHelper), -// so that the recursion does not need per-range predicate instances. -ghost -requires 0 <= i && i <= end -requires forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], _) -ensures len(res) == end - i -ensures forall k int :: { res[k] } 0 <= k && k < end - i ==> res[k] == s[i + k] -decreases end - i -pure func ViewAux(s []byte, i int, end int) (res seq[byte]) { - return i == end ? seq[byte]{} : seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) -} -``` - -Notes: -- The postconditions of an `opaque` function remain visible, so the elementwise - characterization (`View(s, start, end)[i] == GetByte(s, start, end, start+i)`) is - available everywhere without `reveal`. In practice callers should almost never need - `reveal View(...)`. -- The wildcard permission amount (`acc(Bytes(...), _)`) lets the function be applied under - any fraction (`R55`, etc.) without threading a `perm` argument. This matches - `seqs.ToSeqByte`. -- `View` is deliberately indexed by the *predicate instance* `Bytes(s, start, end)`. A - caller holding `Bytes(s, 0, len(s))` writes `View(s, 0, len(s))[a:b]` for a sub-range — - a pure sequence operation — instead of splitting predicates. This is the key move that - eliminates the ghost-operation-in-pure-context problem. -- Framing is by the heap: as long as the caller's fraction of `Bytes(s, start, end)` is - preserved (not unfolded and modified), `View(s, start, end)` is automatically known to - be unchanged. -- If the recursive body turns out to be brittle in verification, fall back to declaring - `View` abstract (bodyless) with the same postconditions — this is exactly the (already - trusted) `seqs.ToSeqByte` pattern, generalized with `start`/`end`. In either variant, - `seqs.ToSeqByte(ub)` becomes a deprecated alias for `View(ub, 0, len(ub))` and should be - removed at the end of the migration (its only current use is - `gopacket.SerializeBuffer.View()` in - `verification/dependencies/github.com/google/gopacket/writer.gobra`, which should be - re-specified in terms of `sl.View`). - -Additionally, add one extensionality lemma that converts elementwise knowledge into -sequence equality (needed after buffer writes, see Step 4): - -```gobra -ghost -requires acc(Bytes(s, start, end), _) -requires len(other) == end - start -requires forall i int :: { other[i] } 0 <= i && i < end - start ==> - other[i] == GetByte(s, start, end, start + i) -ensures View(s, start, end) == other -decreases -func ViewEqFromElements(s []byte, start int, end int, other seq[byte]) -``` - -## Step 2 — strengthen the specs of the `slices` package - -Every ghost operation that transfers permissions between predicate instances must now also -state what happens to the views, so that clients never lose contents information when they -split/combine/reslice. Concretely, extend the postconditions: - -```gobra -// SplitByIndex_Bytes gains: -ensures View(s, start, idx) == old(View(s, start, end))[:idx - start] -ensures View(s, idx, end) == old(View(s, start, end))[idx - start:] - -// CombineAtIndex_Bytes gains: -ensures View(s, start, end) == old(View(s, start, idx)) ++ old(View(s, idx, end)) - -// Reslice_Bytes gains: -ensures View(s[start:end], 0, end - start) == old(View(s, start, end)) - -// Unslice_Bytes gains: -ensures View(s, start, end) == old(View(s[start:end], 0, end - start)) - -// SplitRange_Bytes gains: -ensures View(s[start:end], 0, end - start) == old(View(s, 0, len(s)))[start:end] -ensures View(s, 0, start) == old(View(s, 0, len(s)))[:start] -ensures View(s, end, len(s)) == old(View(s, 0, len(s)))[end:] - -// CombineRange_Bytes gains: -ensures View(s, 0, len(s)) == - old(View(s, 0, start)) ++ old(View(s[start:end], 0, end - start)) ++ old(View(s, end, len(s))) -``` - -All of these follow from the elementwise postcondition of `View` plus the pointer-identity -facts the lemma bodies already establish (`&s[start:end][i] == &s[start+i]`); where the -prover needs help, close with `ViewEqFromElements`. - -With these in place, the generic fact that today is re-proved once per parsing function -(every `*OffsetEq` / `Widen*` / `*Subslice` lemma) is available once and for all: -**the view of a subslice is the subsequence of the view.** - -## Step 3 — refactor the parsing functions to take `seq[byte]` - -General shape: each parser takes exactly the bytes of the thing it parses, with a length -precondition instead of offset arithmetic and permissions. Offsets survive only in the -*callers*, as sequence slicing. - -### 3a. Leaf parsers — `pkg/slayers/path` - -`infofield_spec.gobra` (`InfoLen == 8`): - -```gobra -ghost -requires len(raw) == InfoLen -decreases -pure func ConsDir(raw seq[byte]) bool { return raw[0] & 0x1 == 0x1 } - -ghost -requires len(raw) == InfoLen -decreases -pure func Peer(raw seq[byte]) bool { return raw[0] & 0x2 == 0x2 } - -ghost -requires len(raw) == InfoLen -decreases -pure func Timestamp(raw seq[byte]) io.Ainfo { - return io.Ainfo{uint(binary.BigEndian.Uint32Spec(raw[4], raw[5], raw[6], raw[7]))} -} - -ghost -requires len(raw) == InfoLen -decreases -pure func AbsUinfo(raw seq[byte]) set[io.MsgTerm] { - return AbsUInfoFromUint16(binary.BigEndian.Uint16Spec(raw[2], raw[3])) -} - -ghost -opaque -requires len(raw) == InfoLen -decreases -pure func BytesToAbsInfoField(raw seq[byte]) io.AbsInfoField { - return io.AbsInfoField { - AInfo: Timestamp(raw), - UInfo: AbsUinfo(raw), - ConsDir: ConsDir(raw), - Peer: Peer(raw), - } -} -``` - -Note the switch from `binary.BigEndian.Uint32` (slice + permissions) to -`binary.BigEndian.Uint32Spec` (byte values): the `unfolding`, the `AssertSliceOverlap` -calls, and `BytesToAbsInfoFieldHelper` all disappear. `InfoFieldOffset` stays — callers -still use it to *select* the sub-sequence. - -`hopfield_spec.gobra` (`HopLen == 12`, `MacLen == 6`): - -```gobra -ghost -requires len(raw) == HopLen -decreases -pure func BytesToIO_HF(raw seq[byte]) io.HF { - return let inif2 := binary.BigEndian.Uint16Spec(raw[2], raw[3]) in - let egif2 := binary.BigEndian.Uint16Spec(raw[4], raw[5]) in - io.HF { - InIF2: ifsToIO_ifs(inif2), - EgIF2: ifsToIO_ifs(egif2), - HVF: AbsMac(FromSeqToMacArray(raw[6:6+MacLen])), - } -} -``` - -with a new permission-free companion to `FromSliceToMacArray` in `io_msgterm_spec.gobra`: - -```gobra -ghost -requires len(mac) == MacLen -ensures forall i int :: { res[i] } 0 <= i && i < MacLen ==> mac[i] == res[i] -decreases -pure func FromSeqToMacArray(mac seq[byte]) (res [MacLen]byte) { - return [MacLen]byte{ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] } -} -``` - -### 3b. Mid-level parsers — `pkg/slayers/path/scion/raw_spec.gobra` - -These functions parse *several* fields, so they take the view of the region they cover -(for the top-level ones: the whole packet) and slice it. All permissions vanish; offset -parameters survive only where they select sub-sequences. - -```gobra -ghost -requires 0 <= currHfIdx && currHfIdx <= segLen -requires len(hfBytes) == segLen * path.HopLen -ensures len(res) == segLen - currHfIdx -decreases segLen - currHfIdx -pure func hopFields(hfBytes seq[byte], currHfIdx int, segLen int) (res seq[io.HF]) { - return currHfIdx == segLen ? seq[io.HF]{} : - seq[io.HF]{path.BytesToIO_HF(hfBytes[currHfIdx*path.HopLen:(currHfIdx+1)*path.HopLen])} ++ - hopFields(hfBytes, currHfIdx + 1, segLen) -} -``` - -`segment` takes `hfBytes seq[byte]` the same way. `CurrSeg` merges with the -`CurrSegWithInfo` family from `info_hop_setter_lemmas.gobra`: since the info field is now -parsed independently of permissions, there is no reason to keep two variants ("parse info -from raw" vs. "info passed as value"): - -```gobra -ghost -opaque -requires 0 < segLen -requires 0 <= currHfIdx && currHfIdx <= segLen -requires len(infoBytes) == path.InfoLen -requires len(hfBytes) == segLen * path.HopLen -decreases -pure func CurrSeg(infoBytes seq[byte], hfBytes seq[byte], currHfIdx int, segLen int) io.Seg { - return segment(hfBytes, currHfIdx, - path.Timestamp(infoBytes), path.AbsUinfo(infoBytes), - path.ConsDir(infoBytes), path.Peer(infoBytes), segLen) -} -``` - -(If keeping the packet-relative signature is preferred for fewer call-site changes, -`CurrSeg(raw seq[byte], offset, currInfIdx, currHfIdx, segLen, headerOffset)` with -`raw[...]` slicing inside is also permission-free; the field-exact variant above is the -end state the refactor aims for, and `CurrSegWithInfo`'s existence shows it is the shape -proofs actually want.) - -`LeftSeg` / `RightSeg` / `MidSeg` / `absPkt` / `RawBytesToMetaHdr` / `RawBytesToBase` / -`validPktMetaHdr` take `raw seq[byte]` (the whole packet view) and compute the -`infoBytes`/`hfBytes` arguments by pure slicing with the existing offset functions -(`path.InfoFieldOffset`, `HopFieldOffset`). E.g.: - -```gobra -ghost -requires MetaLen <= len(raw) -decreases -pure func RawBytesToMetaHdr(raw seq[byte]) MetaHdr { - return DecodedFrom(binary.BigEndian.Uint32Spec(raw[0], raw[1], raw[2], raw[3])) -} -``` - -Method specs keep the buffer argument for the *predicate*, and pass views to the abstract -functions: `(s *Raw) absPkt(ub []byte)` becomes `(s *Raw) absPkt(raw seq[byte])` and call -sites use `s.absPkt(sl.View(ub, 0, len(ub)))`. Where an `ub` and its prefix `ub[:length]` -both appear today, both are now expressed from one view (`V := sl.View(ub, 0, len(ub))`; -prefix is `V[:length]`) — this is what kills the widening lemmas. - -The `CorrectlyDecodedInf/Hf(WithIdx)` family keeps its `(ub []byte)` receiver-style -signature at the boundary or moves to `seq[byte]`; either way its body compares against -`BytesToAbsInfoField(V[infOffset:infOffset+path.InfoLen])` with `V := sl.View(ub, 0, len(ub))`. - -### 3c. Header-level parsers — `pkg/slayers/scion_spec.gobra` and `router/io-spec.gobra` - -- `ValidPktMetaHdr(ub []byte)`, `IsSupportedPkt(ub []byte)`, `GetAddressOffset*`, - `GetLength*`, `GetPathType`, `GetNextHdr` → take `raw seq[byte]`. `IsSupportedPkt` is - simply deleted in favor of the already existing `IsSupportedRawPkt(raw seq[byte])` - (rename it to `IsSupportedPkt` once the slice version is gone). -- `router/io-spec.gobra`: `absPkt(raw seq[byte]) io.Pkt`, - `absIO_val(raw seq[byte], ingressID uint16) io.Val`, - `absValUnsupported` loses its `sl.Bytes` precondition entirely. - `MsgToAbsVal` remains heap-dependent (it looks inside `msg.Mem()`) and becomes the/an - explicit boundary: it extracts the buffer, applies `sl.View`, and calls `absIO_val`. -- Loop invariants and contracts in `router/dataplane.go` that currently say - `absIO_val(rawPkt, ingressID)` become `absIO_val(sl.View(rawPkt, 0, len(rawPkt)), ingressID)`. - This is the bulk of the (mechanical) call-site churn: ~150 references in - `router/dataplane.go`, plus `pkg/slayers/path/scion/raw.go`, `decoded.go`, `scion.go`. - -## Step 4 — lemma cleanup: deletions and additions - -### Deleted (made redundant by seq-level sub-ranging) - -| Lemma | File | -|---|---| -| `BytesToAbsInfoFieldOffsetEq`, `BytesToAbsInfoFieldHelper` | `pkg/slayers/path/infofield_spec.gobra` | -| `WidenBytesHopField`, `BytesToAbsHopFieldOffsetEq` | `pkg/slayers/path/hopfield_spec.gobra` | -| `WidenCurrSeg`, `WidenLeftSeg`, `WidenRightSeg`, `WidenMidSeg` | `pkg/slayers/path/scion/widen-lemma.gobra` (whole file) | -| `CurrSegEquality`, `LeftSegEquality(Spec)`, `RightSegEquality(Spec)`, `MidSegEquality(Spec)`, and the `CurrSegWithInfo`/`LeftSegWithInfo`/`RightSegWithInfo`/`MidSegWithInfo` duplicates | `pkg/slayers/path/scion/info_hop_setter_lemmas.gobra` (family merges into `CurrSeg` et al.) | -| `ValidPktMetaHdrSublice` | `pkg/slayers/path/scion/raw_spec.gobra` | -| `IsSupportedPktSubslice`, `ValidHeaderOffsetToSubSliceLemma`, `ValidHeaderOffsetFromSubSliceLemma` | `pkg/slayers/scion_spec.gobra` | -| `absIO_valWidenLemma`, `ValidPktMetaHdrWidenLemma`, `IsSupportedPktWidenLemma`, `absPktWidenLemma` | `router/widen-lemma.gobra` (whole file) | -| `AbsPktToSubSliceAbsPkt`, `SubSliceAbsPktToAbsPkt` | `router/io-spec-lemmas.gobra` | - -Where an opaque function is involved (e.g. `absPkt` on `V` vs. `V[:length]`), a residual -lemma may still be wanted so that call sites don't need `reveal`; such lemmas shrink to a -`reveal` + sequence reasoning with **no** permission manipulation, no `unfold`/`fold`, and -no `AssertSliceOverlap`. Expectation: `router/widen-lemma.gobra` either disappears or -becomes a ~20-line file. - -`sl.AssertSliceOverlap` keeps its remaining uses in executable-code proofs (where real -subslices are taken), but disappears from all pure parsing definitions. - -### Added - -1. `View`, `ViewAux`, `ViewEqFromElements` in `verification/utils/slices` (Step 1). -2. The strengthened postconditions of Step 2 (same package). -3. **Write-effect lemmas**: today, setter proofs (`SetInfoField`, `SetHopField`, - `info_hop_setter_lemmas.gobra`) argue field-by-field which parsing functions survive a - buffer write. With views, this becomes one generic pattern: after code unfolds - `Bytes(ub, 0, len(ub))`, writes bytes `[a, b)`, and refolds, - - ```gobra - sl.View(ub, 0, len(ub)) == - old(sl.View(ub, 0, len(ub)))[:a] ++ written ++ old(sl.View(ub, 0, len(ub)))[b:] - ``` - - which follows from `ViewEqFromElements`. To make this convenient, specify the ghost - update boundary once, e.g. as a lemma in the `slices` package: - - ```gobra - ghost - requires ... // Bytes held, old elementwise facts for [0,a) and [b,len), new bytes for [a,b) - ensures View(ub, 0, len(ub)) == oldView[:a] ++ written ++ oldView[b:] - func ViewAfterUpdate(ub []byte, a int, b int, written seq[byte], oldView seq[byte]) - ``` - - The existing `binary.BigEndian.PutUint16/PutUint32` postconditions - (`PutUint16Spec(b[0], b[1], v)`) already give the elementwise facts needed to - instantiate `written`. -4. **Seq-index congruence helpers** only if the SMT solver needs nudging, e.g. - `SubSeqIndex(v seq[byte], a, b, i)` asserting `v[a:b][i] == v[a+i]`. Gobra encodes - these definitionally for sequences, so start without them and add on demand. -5. Optional convenience: `binary.BigEndian.Uint32SeqSpec(bs seq[byte])` / - `Uint16SeqSpec(bs seq[byte])` wrappers (`requires len(bs) == 4/2`) to avoid spelling - out four indices at every use. Pure sugar over `Uint32Spec`. - -## Migration strategy - -Verification is per-package and expensive, so migrate bottom-up, keeping the build green: - -1. **`verification/utils/slices`** — add `View` + Step-2 postconditions; verify the - package standalone (plus `slices_test.gobra`). -2. **`verification/utils/seqs` / gopacket stubs** — define `ToSeqByte` as - `View(ub, 0, len(ub))` (or delete it and re-specify `SerializeBuffer.View()` against - `sl.View`). -3. **`pkg/slayers/path`** — convert leaf parsers (3a). During the transition, if needed, - keep the old slice-based functions temporarily with bodies delegating to the new ones - (`Timestamp_old(raw, i, h) == Timestamp(sl.View(raw,0,len(raw))[idx:idx+InfoLen])`), so - dependent packages keep verifying; delete them at the end. Update - `hopfield.go`/`infofield.go` proof annotations (`DecodeFromBytes`/`SerializeTo` relate - the struct to `BytesToIO_HF(sl.View(...)...)`). -4. **`pkg/slayers/path/scion`** — convert `raw_spec.gobra` (3b), merge the `*WithInfo` - family, delete `widen-lemma.gobra`, rewrite `info_hop_setter_lemmas.gobra` on top of the - write-effect lemma; update `raw.go`, `base.go`, `decoded.go` annotations. -5. **`pkg/slayers`** — convert `scion_spec.gobra` (3c), delete the subslice lemmas, update - `scion.go` annotations. -6. **`router`** — convert `io-spec.gobra`, `io-spec-lemmas.gobra`, delete - `widen-lemma.gobra`, update `dataplane.go` invariants/contracts (mechanical - `f(raw, ...)` → `f(sl.View(raw, 0, len(raw)), ...)` rewriting, then removal of now-dead - lemma calls and `SplitRange/CombineRange` choreography that existed only to feed the - old preconditions). -7. Sweep for dead code: `AssertSliceOverlap` uses in pure functions, unused `Offset` - helper parameters (`BytesToIO_HF`'s `start`/`end`), stale comments, and the `absPkt` - TODO at `raw_spec.gobra:378`. - -Each step ends with running the package's Gobra verification (CI targets / `Makefile`, -including the chopper configuration re-enabled in #420) before moving up. - -## Risks / points of attention - -- **Trigger hygiene.** The elementwise postcondition of `View` uses `{ res[i] }` as - trigger; sequence-heavy proofs can be slower or flakier than pointwise permission - reasoning in pathological cases. Mitigation: keep big parsers `opaque` (as today) and - prove equalities via the small set of lemmas rather than raw quantifier reasoning; - measure verification times per package as part of each migration step. -- **Predicate-instance discipline.** `View(s, a, b)` requires exactly the instance - `Bytes(s, a, b)`. The convention must be: take the view of the instance you hold, then - slice the sequence. Code that today holds `Bytes(ub, start, end)` mid-split (e.g. inside - `DecodeFromBytes` choreography) uses `View(ub, start, end)` at that point and the Step-2 - postconditions to relate it to the outer view when recombining. -- **`decreases` measures.** The recursive `ViewAux` and the seq-based `hopFields` need - adjusted termination measures (straightforward: `end - i`, `segLen - currHfIdx`). -- **Churn volume.** `router/dataplane.go` alone has ~150 references to `absPkt`/ - `absIO_val`-family functions. The rewrite is mechanical but broad; the temporary-alias - trick in migration step 3 keeps intermediate states verifiable. -- **What does *not* change.** The `Bytes` predicate itself, the executable code, the - `encoding/binary` trusted stubs, the IO-spec types (`io.Pkt`, `io.Seg`, - `io.AbsInfoField`, ...), and the abstract transition relations - (`router/io-spec-abstract-transitions.gobra`) are untouched — the refactor only moves - the boundary where bytes stop being heap and become math. From 108476ec0cf0243dc506efda9bf547409cd50b46 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 19:55:04 +0000 Subject: [PATCH 09/35] Fix line breaks after seq concatenation operator Gobra applies Go's automatic semicolon insertion rules, so a spec line ending in ++ is parsed as a postfix increment and the clause fails to parse. Keep the concatenation operator mid-line. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/scion/info_hop_setter_lemmas.gobra | 4 +--- verification/utils/slices/slices.gobra | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra b/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra index 7ae16a79e..013f2ea92 100644 --- a/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra +++ b/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra @@ -679,9 +679,7 @@ ensures acc(sl.Bytes(hopfields, 0, len(hopfields)), p) ensures let currHfStart := currHfIdx * path.HopLen in let currHfEnd := currHfStart + path.HopLen in sl.View(hopfields, 0, len(hopfields)) == - old(sl.View(hopfields[:currHfStart], 0, currHfStart)) ++ - old(sl.View(hopfields[currHfStart:currHfEnd], 0, path.HopLen)) ++ - old(sl.View(hopfields[currHfEnd:], 0, (segLen - currHfIdx - 1) * path.HopLen)) + old(sl.View(hopfields[:currHfStart], 0, currHfStart)) ++ old(sl.View(hopfields[currHfStart:currHfEnd], 0, path.HopLen)) ++ old(sl.View(hopfields[currHfEnd:], 0, (segLen - currHfIdx - 1) * path.HopLen)) decreases func CombineHopfields(hopfields []byte, currHfIdx int, segLen int, p perm) { currHfStart := currHfIdx * path.HopLen diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index b1e1514c4..dc43c1bc7 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -263,8 +263,8 @@ requires acc(Bytes(s[start:end], 0, end-start), p) requires acc(Bytes(s, 0, start), p) requires acc(Bytes(s, end, len(s)), p) ensures acc(Bytes(s, 0, len(s)), p) -ensures View(s, 0, len(s)) == old(View(s, 0, start)) ++ - old(View(s[start:end], 0, end-start)) ++ old(View(s, end, len(s))) +ensures View(s, 0, len(s)) == + old(View(s, 0, start)) ++ old(View(s[start:end], 0, end-start)) ++ old(View(s, end, len(s))) decreases func CombineRange_Bytes(s []byte, start int, end int, p perm) { vLeft := View(s, 0, start) From f8b333b1b3f318ebac986d9a8aa3a248d88a3332 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 21:12:26 +0000 Subject: [PATCH 10/35] Fix view-related verification errors from first CI round - Restore the MetaLen bound of MetaHdr.DecodeFromBytesSpec and SerializeToSpec as a body conjunct instead of a precondition: as a precondition it is not yet established at the point where Base.DecodeFromBytes's guarded postcondition mentions the spec. - Add CombineRangeNoView_Bytes, a variant of CombineRange_Bytes without view postconditions, for contexts where heap-dependent functions cannot be evaluated in the old state, and use it in the magic-wand package block in the epic package. - State CombineRange_Bytes's view postcondition under a single old() instead of one old() per concatenated view. - In the dataplane, evaluate the truncated ValidHeaderOffset facts over the view of the left subrange instance sl.View(ub, 0, startP), which stays available while the whole-range instance is split, and bridge between the two forms with asserts around the To/FromSubSlice lemmas. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/experimental/epic/epic.go | 4 ++-- pkg/slayers/path/scion/base_spec.gobra | 9 ++++----- router/dataplane.go | 20 ++++++++++++++++---- verification/utils/slices/slices.gobra | 20 +++++++++++++++++++- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/pkg/experimental/epic/epic.go b/pkg/experimental/epic/epic.go index e4f0bf8ca..876a3d20c 100644 --- a/pkg/experimental/epic/epic.go +++ b/pkg/experimental/epic/epic.go @@ -148,8 +148,8 @@ func CalcMac(auth []byte, pktID epic.PktID, s *slayers.SCION, // @ package (sl.Bytes(result, 0, len(result)) --* sl.Bytes(oldBuffer, 0, len(oldBuffer))) { // @ ghost if !allocatesNewBuffer { // @ assert oldBuffer === buffer - // @ sl.CombineRange_Bytes(input, start, end, writePerm) - // @ sl.CombineRange_Bytes(oldBuffer, 0, inputLength, writePerm) + // @ sl.CombineRangeNoView_Bytes(input, start, end, writePerm) + // @ sl.CombineRangeNoView_Bytes(oldBuffer, 0, inputLength, writePerm) // @ } // @ } // @ assert (sl.Bytes(result, 0, len(result)) --* sl.Bytes(oldBuffer, 0, len(oldBuffer))) diff --git a/pkg/slayers/path/scion/base_spec.gobra b/pkg/slayers/path/scion/base_spec.gobra index de76cc0db..59e0a5c31 100644 --- a/pkg/slayers/path/scion/base_spec.gobra +++ b/pkg/slayers/path/scion/base_spec.gobra @@ -240,10 +240,10 @@ pure func DecodedFrom(line uint32) MetaHdr { } ghost -requires MetaLen <= len(raw) decreases pure func (m MetaHdr) DecodeFromBytesSpec(raw seq[byte]) bool { - return 0 <= m.CurrINF && m.CurrINF <= 3 && + return MetaLen <= len(raw) && + 0 <= m.CurrINF && m.CurrINF <= 3 && 0 <= m.CurrHF && m.CurrHF < 64 && m.SegsInBounds() && let line := binary.BigEndian.Uint32Spec(raw[0], raw[1], raw[2], raw[3]) in @@ -252,7 +252,6 @@ pure func (m MetaHdr) DecodeFromBytesSpec(raw seq[byte]) bool { ghost requires s.Mem() -requires MetaLen <= len(raw) decreases pure func (s *Base) DecodeFromBytesSpec(raw seq[byte]) bool { return unfolding s.Mem() in @@ -286,10 +285,10 @@ pure func (m MetaHdr) SerializedToLine() uint32 { } ghost -requires MetaLen <= len(raw) decreases pure func (m MetaHdr) SerializeToSpec(raw seq[byte]) bool { - return let v := m.SerializedToLine() in + return MetaLen <= len(raw) && + let v := m.SerializedToLine() in binary.BigEndian.PutUint32Spec(raw[0], raw[1], raw[2], raw[3], v) } diff --git a/router/dataplane.go b/router/dataplane.go index b33a74dbf..488e55954 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -2345,6 +2345,7 @@ func (p *scionPacketProcessor) validateSrcDstIA( /*@ ghost ubScionL []byte, ghos // @ sl.SplitRange_Bytes(ubScionL, startP, endP, R50) // @ p.AbsPktToSubSliceAbsPkt(ubScionL, startP, endP) // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ubScionL, sl.View(ubScionL, 0, len(ubScionL)), startP) + // @ assert sl.View(ubScionL, 0, len(ubScionL))[:startP] == sl.View(ubScionL, 0, startP) // @ unfold acc(p.scionLayer.HeaderMem(ubScionL[slayers.CmnHdrLen:]), R20) // @ defer fold acc(p.scionLayer.HeaderMem(ubScionL[slayers.CmnHdrLen:]), R20) // @ p.d.getLocalIA() @@ -2375,6 +2376,7 @@ func (p *scionPacketProcessor) validateSrcDstIA( /*@ ghost ubScionL []byte, ghos } // @ ghost if(p.path.IsLastHopSpec(ubPath)) { // @ p.path.LastHopLemma(ubPath) + // @ assert sl.View(ubScionL, 0, len(ubScionL))[:startP] == sl.View(ubScionL, 0, startP) // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ubScionL, sl.View(ubScionL, 0, len(ubScionL)), startP) // @ p.SubSliceAbsPktToAbsPkt(ubScionL, startP, endP) // @ } @@ -2783,6 +2785,7 @@ func (p *scionPacketProcessor) updateNonConsDirIngressSegID( /*@ ghost ub []byte // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ p.AbsPktToSubSliceAbsPkt(ub, start, end) // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), start) + // @ assert sl.View(ub, 0, len(ub))[:start] == sl.View(ub, 0, start) // @ sl.SplitRange_Bytes(ub, start, end, HalfPerm) if err := p.path.SetInfoField(p.infoField, int( /*@ unfolding acc(p.path.Mem(ubPath), R45) in (unfolding acc(p.path.Base.Mem(), R50) in @*/ p.path.PathMeta.CurrINF) /*@ ) , ubPath, @*/); err != nil { // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) @@ -2794,6 +2797,7 @@ func (p *scionPacketProcessor) updateNonConsDirIngressSegID( /*@ ghost ub []byte // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, start, slayers.CmnHdrLen, R54) + // @ assert sl.View(ub, 0, len(ub))[:start] == sl.View(ub, 0, start) // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, sl.View(ub, 0, len(ub)), start) // @ p.SubSliceAbsPktToAbsPkt(ub, start, end) // @ ghost sl.CombineRange_Bytes(ub, start, end, HalfPerm) @@ -3056,10 +3060,11 @@ func (p *scionPacketProcessor) processEgress( /*@ ghost ub []byte @*/ ) (reserr // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ p.AbsPktToSubSliceAbsPkt(ub, startP, endP) // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) + // @ assert sl.View(ub, 0, len(ub))[:startP] == sl.View(ub, 0, startP) // @ reveal p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) // @ reveal p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) // @ sl.SplitRange_Bytes(ub, startP, endP, HalfPerm) - // @ reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))[:startP]) + // @ reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, startP)) // @ unfold acc(p.scionLayer.Mem(ub), R55) // we are the egress router and if we go in construction direction we // need to update the SegID. @@ -3091,11 +3096,12 @@ func (p *scionPacketProcessor) processEgress( /*@ ghost ub []byte @*/ ) (reserr return serrors.WrapStr("incrementing path", err) } // @ fold acc(p.scionLayer.Mem(ub), R55) - // @ assert reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))[:startP]) + // @ assert reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, startP)) // @ ghost sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) + // @ assert sl.View(ub, 0, len(ub))[:startP] == sl.View(ub, 0, startP) // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ p.SubSliceAbsPktToAbsPkt(ub, startP, endP) // @ ghost sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) @@ -3166,12 +3172,13 @@ func (p *scionPacketProcessor) doXover( /*@ ghost ub []byte, ghost currBase scio // @ p.AbsPktToSubSliceAbsPkt(ub, startP, endP) // @ assert p.path == p.scionLayer.GetPath(ub) // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) + // @ assert sl.View(ub, 0, len(ub))[:startP] == sl.View(ub, 0, startP) // @ ghost preAbsPkt := p.path.absPkt(sl.View(ubPath, 0, len(ubPath))) // @ p.path.XoverLemma(ubPath) // @ reveal p.EqAbsInfoField(absPkt(sl.View(ub, 0, len(ub)))) // @ reveal p.EqAbsHopField(absPkt(sl.View(ub, 0, len(ub)))) // @ sl.SplitRange_Bytes(ub, startP, endP, HalfPerm) - // @ reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))[:startP]) + // @ reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, startP)) // @ unfold acc(p.scionLayer.Mem(ub), R55) // @ assert p.path.GetBase(ubPath) == currBase // @ ghost nextBase := currBase.IncPathSpec() @@ -3189,12 +3196,13 @@ func (p *scionPacketProcessor) doXover( /*@ ghost ub []byte, ghost currBase scio // @ assert p.path.GetBase(ubPath) == nextBase // @ assert p.path.absPkt(sl.View(ubPath, 0, len(ubPath))) == scion.AbsXover(preAbsPkt) // @ fold acc(p.scionLayer.Mem(ub), R55) - // @ assert reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))[:startP]) + // @ assert reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, startP)) // @ ghost sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) // @ assert p.path == p.scionLayer.GetPath(ub) + // @ assert sl.View(ub, 0, len(ub))[:startP] == sl.View(ub, 0, startP) // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ assert p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, len(ub))) // @ assert p.path == p.scionLayer.GetPath(ub) @@ -3466,6 +3474,7 @@ func (p *scionPacketProcessor) handleIngressRouterAlert( /*@ ghost ub []byte, gh // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ p.AbsPktToSubSliceAbsPkt(ub, startP, endP) // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) + // @ assert sl.View(ub, 0, len(ub))[:startP] == sl.View(ub, 0, startP) // @ sl.SplitRange_Bytes(ub, startP, endP, HalfPerm) if err := p.path.SetHopField(p.hopField, int( /*@ unfolding acc(p.path.Mem(ubPath), R50) in (unfolding acc(p.path.Base.Mem(), R55) in @*/ p.path.PathMeta.CurrHF /*@ ) @*/) /*@ , ubPath @*/); err != nil { // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) @@ -3479,6 +3488,7 @@ func (p *scionPacketProcessor) handleIngressRouterAlert( /*@ ghost ub []byte, gh // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) + // @ assert sl.View(ub, 0, len(ub))[:startP] == sl.View(ub, 0, startP) // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ p.SubSliceAbsPktToAbsPkt(ub, startP, endP) // @ absPktFutureLemma(sl.View(ub, 0, len(ub))) @@ -3583,6 +3593,7 @@ func (p *scionPacketProcessor) handleEgressRouterAlert( /*@ ghost ub []byte, gho // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ p.AbsPktToSubSliceAbsPkt(ub, startP, endP) // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) + // @ assert sl.View(ub, 0, len(ub))[:startP] == sl.View(ub, 0, startP) // @ sl.SplitRange_Bytes(ub, startP, endP, HalfPerm) if err := p.path.SetHopField(p.hopField, int( /*@ unfolding acc(p.path.Mem(ubPath), R50) in (unfolding acc(p.path.Base.Mem(), R55) in @*/ p.path.PathMeta.CurrHF /*@ ) @*/) /*@ , ubPath @*/); err != nil { // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) @@ -3596,6 +3607,7 @@ func (p *scionPacketProcessor) handleEgressRouterAlert( /*@ ghost ub []byte, gho // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) + // @ assert sl.View(ub, 0, len(ub))[:startP] == sl.View(ub, 0, startP) // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ p.SubSliceAbsPktToAbsPkt(ub, startP, endP) // @ absPktFutureLemma(sl.View(ub, 0, len(ub))) diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index dc43c1bc7..1a41f9a61 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -264,7 +264,7 @@ requires acc(Bytes(s, 0, start), p) requires acc(Bytes(s, end, len(s)), p) ensures acc(Bytes(s, 0, len(s)), p) ensures View(s, 0, len(s)) == - old(View(s, 0, start)) ++ old(View(s[start:end], 0, end-start)) ++ old(View(s, end, len(s))) + old(View(s, 0, start) ++ View(s[start:end], 0, end-start) ++ View(s, end, len(s))) decreases func CombineRange_Bytes(s []byte, start int, end int, p perm) { vLeft := View(s, 0, start) @@ -277,6 +277,24 @@ func CombineRange_Bytes(s []byte, start int, end int, p perm) { assert vLeft ++ (vMid ++ vRight) == vLeft ++ vMid ++ vRight } +// CombineRangeNoView_Bytes behaves like CombineRange_Bytes, but does not +// relate the views of the combined predicate instances. Use this variant +// in contexts where heap-dependent functions cannot be evaluated in the +// old state of the call, e.g., when packaging magic wands. +ghost +requires 0 < p +requires 0 <= start && start <= end && end <= len(s) +requires acc(Bytes(s[start:end], 0, end-start), p) +requires acc(Bytes(s, 0, start), p) +requires acc(Bytes(s, end, len(s)), p) +ensures acc(Bytes(s, 0, len(s)), p) +decreases +func CombineRangeNoView_Bytes(s []byte, start int, end int, p perm) { + Unslice_Bytes(s, start, end, p) + CombineAtIndex_Bytes(s, start, len(s), end, p) + CombineAtIndex_Bytes(s, 0, len(s), start, p) +} + ghost ensures Bytes(nil, 0, 0) decreases From c81d4536ebc6f6ede18efe2e0a1901f8aef94810 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 08:28:10 +0000 Subject: [PATCH 11/35] Hide the elementwise characterization of View behind a lemma Bytes exists to hide the quantified permissions to a slice's elements; exposing a quantified elementwise equality as a postcondition of View reintroduced a quantifier at every application, polluting the proof context of every client (and dragging GetByte's precondition into every state where a View application is evaluated, including old states at call sites). View now only exposes the length of its result; the elementwise characterization is established explicitly, where needed, with the new ViewElems lemma. The slices-internal proofs and the DecodeFromBytes/SerializeTo proofs invoke it at the points where they bridge between heap contents and views, and seqs.ToSeqByte no longer restates the quantified equality. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/hopfield.go | 2 ++ pkg/slayers/path/infofield.go | 2 ++ pkg/slayers/path/scion/base.go | 2 ++ pkg/slayers/scion.go | 2 ++ verification/utils/seqs/seqs.gobra | 2 -- verification/utils/slices/slices.gobra | 40 ++++++++++++++++++--- verification/utils/slices/slices_test.gobra | 1 + 7 files changed, 44 insertions(+), 7 deletions(-) diff --git a/pkg/slayers/path/hopfield.go b/pkg/slayers/path/hopfield.go index 8b467c5aa..fd0807ae0 100644 --- a/pkg/slayers/path/hopfield.go +++ b/pkg/slayers/path/hopfield.go @@ -87,6 +87,7 @@ func (h *HopField) DecodeFromBytes(raw []byte) (err error) { return serrors.New("HopField raw too short", "expected", HopLen, "actual", len(raw)) } //@ ghost v := sl.View(raw, 0, HopLen) + //@ sl.ViewElems(raw, 0, HopLen, R46) //@ unfold acc(sl.Bytes(raw, 0, HopLen), R46) h.EgressRouterAlert = raw[0]&0x1 == 0x1 h.IngressRouterAlert = raw[0]&0x2 == 0x2 @@ -158,6 +159,7 @@ func (h *HopField) SerializeTo(b []byte) (err error) { //@ ghost mac := seq[byte]{b[6], b[7], b[8], b[9], b[10], b[11]} //@ assert forall i int :: { mac[i] } 0 <= i && i < MacLen ==> mac[i] == h.Mac[i] //@ fold sl.Bytes(b, 0, HopLen) + //@ sl.ViewElems(b, 0, HopLen, writePerm) //@ ghost v := sl.View(b, 0, HopLen) //@ assert v[2] == b2 && v[3] == b3 && v[4] == b4 && v[5] == b5 //@ assert forall j int :: { v[j] } 6 <= j && j < 6+MacLen ==> v[j] == mac[j-6] diff --git a/pkg/slayers/path/infofield.go b/pkg/slayers/path/infofield.go index a25ce7e56..2d644889d 100644 --- a/pkg/slayers/path/infofield.go +++ b/pkg/slayers/path/infofield.go @@ -72,6 +72,7 @@ func (inf *InfoField) DecodeFromBytes(raw []byte) (err error) { return serrors.New("InfoField raw too short", "expected", InfoLen, "actual", len(raw)) } //@ ghost v := slices.View(raw, 0, len(raw)) + //@ slices.ViewElems(raw, 0, len(raw), R50) //@ unfold acc(slices.Bytes(raw, 0, len(raw)), R50) inf.ConsDir = raw[0]&0x1 == 0x1 inf.Peer = raw[0]&0x2 == 0x2 @@ -133,6 +134,7 @@ func (inf *InfoField) SerializeTo(b []byte) (err error) { //@ ghost b6 := b[6] //@ ghost b7 := b[7] //@ fold slices.Bytes(b, 0, len(b)) + //@ slices.ViewElems(b, 0, len(b), writePerm) //@ ghost v := slices.View(b, 0, len(b)) //@ assert v[0] == b0 && v[2] == b2 && v[3] == b3 //@ assert v[4] == b4 && v[5] == b5 && v[6] == b6 && v[7] == b7 diff --git a/pkg/slayers/path/scion/base.go b/pkg/slayers/path/scion/base.go index cd58ec1f9..6c05884d5 100644 --- a/pkg/slayers/path/scion/base.go +++ b/pkg/slayers/path/scion/base.go @@ -262,6 +262,7 @@ func (m *MetaHdr) DecodeFromBytes(raw []byte) (e error) { return serrors.New("MetaHdr raw too short", "expected", int(MetaLen), "actual", int(len(raw))) } //@ ghost v := sl.View(raw, 0, len(raw)) + //@ sl.ViewElems(raw, 0, len(raw), R51) //@ unfold acc(sl.Bytes(raw, 0, len(raw)), R50) line := binary.BigEndian.Uint32(raw) m.CurrINF = uint8(line >> 30) @@ -304,6 +305,7 @@ func (m *MetaHdr) SerializeTo(b []byte) (e error) { //@ ghost b2 := b[2] //@ ghost b3 := b[3] //@ fold acc(sl.Bytes(b, 0, len(b))) + //@ sl.ViewElems(b, 0, len(b), writePerm) //@ ghost v := sl.View(b, 0, len(b)) //@ assert v[0] == b0 && v[1] == b1 && v[2] == b2 && v[3] == b3 //@ assert binary.BigEndian.PutUint32Spec(v[0], v[1], v[2], v[3], line) diff --git a/pkg/slayers/scion.go b/pkg/slayers/scion.go index f4057801b..5dea2907f 100644 --- a/pkg/slayers/scion.go +++ b/pkg/slayers/scion.go @@ -267,6 +267,7 @@ func (s *SCION) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeO // @ ghost b4 := buf[4] // @ ghost b8 := buf[8] // @ fold acc(sl.Bytes(uSerBufN, 0, len(uSerBufN)), writePerm) + // @ sl.ViewElems(uSerBufN, 0, len(uSerBufN), writePerm) // @ ghost vSer0 := sl.View(uSerBufN, 0, len(uSerBufN)) // @ assert vSer0[4] == b4 && vSer0[8] == b8 // @ ghost if s.EqPathType(ubuf) { @@ -358,6 +359,7 @@ func (s *SCION) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) (res er // @ decreases // @ outline( // @ ghost v := sl.View(data, 0, len(data)) + // @ sl.ViewElems(data, 0, len(data), R42) // @ unfold acc(sl.Bytes(data, 0, len(data)), R41) s.NextHdr = L4ProtocolType(data[4]) s.HdrLen = data[5] diff --git a/verification/utils/seqs/seqs.gobra b/verification/utils/seqs/seqs.gobra index edddfd72e..97acceee4 100644 --- a/verification/utils/seqs/seqs.gobra +++ b/verification/utils/seqs/seqs.gobra @@ -65,8 +65,6 @@ func SubSeqOfSubSeq(raw seq[byte], a int, b int, start int, length int) { ghost requires acc(sl.Bytes(ub, 0, len(ub)), _) ensures len(res) == len(ub) -ensures forall i int :: { res[i] } 0 <= i && i < len(ub) ==> - res[i] == sl.GetByte(ub, 0, len(ub), i) decreases pure func ToSeqByte(ub []byte) (res seq[byte]) { return sl.View(ub, 0, len(ub)) diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index 1a41f9a61..76c7463a0 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -45,20 +45,42 @@ pure func GetByte(s []byte, start int, end int, i int) byte { // should be performed on views: given a view of the predicate // instance one holds, the bytes of any subrange are obtained by // (pure) sequence slicing, instead of by manipulating predicate -// instances, which cannot be done in pure contexts. The elementwise -// characterization below is exposed as a postcondition, so clients -// never need to reveal View. +// instances, which cannot be done in pure contexts. +// In the same way that Bytes hides the quantified permissions to the +// elements of a slice, View deliberately hides the quantified +// relation between the sequence and the contents of the slice: its +// only visible property is the length of the result. The elementwise +// characterization is established explicitly, where needed, by +// calling ViewElems. ghost opaque requires acc(Bytes(s, start, end), _) ensures len(res) == end - start -ensures forall i int :: { res[i] } 0 <= i && i < end - start ==> - res[i] == GetByte(s, start, end, start + i) decreases pure func View(s []byte, start int, end int) (res seq[byte]) { return unfolding acc(Bytes(s, start, end), _) in ViewAux(s, start, end) } +// ViewElems establishes the elementwise characterization of +// View(s, start, end): it contains exactly the bytes of s in the +// range [start, end). +ghost +requires 0 < p +requires acc(Bytes(s, start, end), p) +ensures acc(Bytes(s, start, end), p) +ensures forall i int :: { View(s, start, end)[i] } 0 <= i && i < end - start ==> + View(s, start, end)[i] == GetByte(s, start, end, start + i) +decreases +func ViewElems(s []byte, start int, end int, p perm) { + v := reveal View(s, start, end) + unfold acc(Bytes(s, start, end), p/2) + assert forall i int :: { v[i] } 0 <= i && i < end - start ==> + v[i] == s[start + i] + fold acc(Bytes(s, start, end), p/2) + assert forall i int :: { v[i] } 0 <= i && i < end - start ==> + v[i] == GetByte(s, start, end, start + i) +} + // ViewAux computes the contents of the range [i, end) of s directly // from the elementwise access permissions, so that the recursion does // not require a predicate instance per subrange. @@ -88,6 +110,7 @@ ensures acc(Bytes(s, start, end), p) ensures View(s, start, end) == other decreases func ViewEqFromElements(s []byte, start int, end int, other seq[byte], p perm) { + ViewElems(s, start, end, p) v := View(s, start, end) assert forall i int :: { v[i] } 0 <= i && i < end - start ==> v[i] == GetByte(s, start, end, start + i) @@ -109,6 +132,8 @@ preserves acc(Bytes(s[start:end], 0, end - start), p) ensures View(s[start:end], 0, end - start) == View(s, 0, len(s))[start:end] decreases func ViewOfSubslice(s []byte, start int, end int, p perm) { + ViewElems(s, 0, len(s), p/2) + ViewElems(s[start:end], 0, end - start, p/2) vFull := View(s, 0, len(s)) vSub := View(s[start:end], 0, end - start) unfold acc(Bytes(s, 0, len(s)), p/2) @@ -137,6 +162,7 @@ ensures View(s, start, idx) == old(View(s, start, end))[:idx - start] ensures View(s, idx, end) == old(View(s, start, end))[idx - start:] decreases func SplitByIndex_Bytes(s []byte, start int, end int, idx int, p perm) { + ViewElems(s, start, end, p) v := View(s, start, end) unfold acc(Bytes(s, start, end), p) assert forall i int :: { &s[i] } start <= i && i < end ==> @@ -160,6 +186,8 @@ ensures View(s, start, end) == old(View(s, start, idx)) ++ old(View(s, idx, end)) decreases func CombineAtIndex_Bytes(s []byte, start int, end int, idx int, p perm) { + ViewElems(s, start, idx, p) + ViewElems(s, idx, end, p) v1 := View(s, start, idx) v2 := View(s, idx, end) unfold acc(Bytes(s, start, idx), p) @@ -186,6 +214,7 @@ ensures acc(Bytes(s[start:end], 0, len(s[start:end])), p) ensures View(s[start:end], 0, end - start) == old(View(s, start, end)) decreases func Reslice_Bytes(s []byte, start int, end int, p perm) { + ViewElems(s, start, end, p) v := View(s, start, end) unfold acc(Bytes(s, start, end), p) assert forall i int :: { &s[start:end][i] }{ &s[start + i] } 0 <= i && i < (end-start) ==> &s[start:end][i] == &s[start + i] @@ -207,6 +236,7 @@ ensures acc(Bytes(s, start, end), p) ensures View(s, start, end) == old(View(s[start:end], 0, end - start)) decreases func Unslice_Bytes(s []byte, start int, end int, p perm) { + ViewElems(s[start:end], 0, len(s[start:end]), p) v := View(s[start:end], 0, end - start) unfold acc(Bytes(s[start:end], 0, len(s[start:end])), p) assert 0 <= start && start <= end && end <= cap(s) diff --git a/verification/utils/slices/slices_test.gobra b/verification/utils/slices/slices_test.gobra index 6a1b505eb..7d91ed5de 100644 --- a/verification/utils/slices/slices_test.gobra +++ b/verification/utils/slices/slices_test.gobra @@ -29,6 +29,7 @@ func View_test() { fold Bytes(s, 0, 10) v := View(s, 0, 10) assert len(v) == 10 + ViewElems(s, 0, 10, writePerm) assert v[3] == GetByte(s, 0, 10, 3) // splitting preserves the contents of the views From 76c23cd5a4a5788de59dae953bb3104f9f886d31 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 08:37:13 +0000 Subject: [PATCH 12/35] Prove slice-lemma view relations by recursion instead of elementwise quantifiers The explicit elementwise reasoning made SplitByIndex_Bytes time out: the combination of the ViewElems quantifier, the GetByte bridging quantifiers and sequence take/drop axioms blows up instantiation. Prove the view relations of the split/combine/reslice lemmas structurally instead, with two recursive lemmas over ViewAux (ViewAuxSplit and ViewAuxReslice) that never introduce quantified content facts. ViewElems and ViewEqFromElements remain available for proofs that genuinely need the elementwise characterization, but the slices-internal proofs no longer use them. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- verification/utils/slices/slices.gobra | 125 ++++++++++++++----------- 1 file changed, 71 insertions(+), 54 deletions(-) diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index 76c7463a0..37238cd5c 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -96,6 +96,43 @@ pure func ViewAux(s []byte, i int, end int) (res seq[byte]) { seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) } +// ViewAuxSplit shows that the contents of the range [start, end) are +// the concatenation of the contents of [start, idx) and [idx, end). +// The proof is by recursion, so that no quantified facts about the +// contents are ever introduced. +ghost +requires 0 <= start && start <= idx && idx <= end && end <= cap(s) +requires forall k int :: { &s[k] } start <= k && k < end ==> acc(&s[k], _) +ensures ViewAux(s, start, end) == ViewAux(s, start, idx) ++ ViewAux(s, idx, end) +decreases idx - start +func ViewAuxSplit(s []byte, start int, idx int, end int) { + if start == idx { + assert ViewAux(s, start, idx) == seq[byte]{} + } else { + ViewAuxSplit(s, start + 1, idx, end) + assert ViewAux(s, start, end) == + seq[byte]{s[start]} ++ (ViewAux(s, start + 1, idx) ++ ViewAux(s, idx, end)) + assert ViewAux(s, start, idx) == seq[byte]{s[start]} ++ ViewAux(s, start + 1, idx) + } +} + +// ViewAuxReslice shows that the contents of the range [i, end) of s +// coincide with the contents of the range [i - start, end - start) of +// the subslice s[start:length]. The proof is by recursion, so that no +// quantified facts about the contents are ever introduced. +ghost +requires 0 <= start && start <= i && i <= end && end <= length && length <= cap(s) +requires forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], _) +ensures ViewAux(s, i, end) == ViewAux(s[start:length], i - start, end - start) +decreases end - i +func ViewAuxReslice(s []byte, start int, length int, i int, end int) { + AssertSliceOverlap(s, start, length) + if i != end { + assert &s[start:length][i - start] == &s[i] + ViewAuxReslice(s, start, length, i + 1, end) + } +} + // ViewEqFromElements derives an equality between View(s, start, end) // and a sequence known to be elementwise equal to it. This is the // extensionality principle used to reconstruct views after predicate @@ -132,24 +169,22 @@ preserves acc(Bytes(s[start:end], 0, end - start), p) ensures View(s[start:end], 0, end - start) == View(s, 0, len(s))[start:end] decreases func ViewOfSubslice(s []byte, start int, end int, p perm) { - ViewElems(s, 0, len(s), p/2) - ViewElems(s[start:end], 0, end - start, p/2) - vFull := View(s, 0, len(s)) - vSub := View(s[start:end], 0, end - start) + vFull := reveal View(s, 0, len(s)) + vSub := reveal View(s[start:end], 0, end - start) unfold acc(Bytes(s, 0, len(s)), p/2) unfold acc(Bytes(s[start:end], 0, end - start), p/2) AssertSliceOverlap(s, start, end) - assert forall i int :: { &s[start:end][i] } 0 <= i && i < end - start ==> - s[start:end][i] == s[start + i] - assert forall i int :: { &s[start:end][i] } 0 <= i && i < end - start ==> - vSub[i] == s[start:end][i] - assert forall i int :: { &s[i] } start <= i && i < end ==> - vFull[i] == s[i] + ViewAuxSplit(s, 0, start, len(s)) + ViewAuxSplit(s, start, end, len(s)) + ViewAuxReslice(s, start, end, start, end) + assert vSub == ViewAux(s, start, end) + assert vFull == ViewAux(s, 0, start) ++ (ViewAux(s, start, end) ++ ViewAux(s, end, len(s))) + assert len(ViewAux(s, 0, start)) == start + assert vFull[start:] == ViewAux(s, start, end) ++ ViewAux(s, end, len(s)) + assert vFull[start:][:end - start] == ViewAux(s, start, end) + assert vFull[start:end] == vFull[start:][:end - start] fold acc(Bytes(s[start:end], 0, end - start), p/2) fold acc(Bytes(s, 0, len(s)), p/2) - assert forall i int :: { vSub[i] } 0 <= i && i < end - start ==> - vSub[i] == vFull[start + i] - assert vSub == vFull[start:end] } ghost @@ -162,19 +197,17 @@ ensures View(s, start, idx) == old(View(s, start, end))[:idx - start] ensures View(s, idx, end) == old(View(s, start, end))[idx - start:] decreases func SplitByIndex_Bytes(s []byte, start int, end int, idx int, p perm) { - ViewElems(s, start, end, p) - v := View(s, start, end) + v := reveal View(s, start, end) unfold acc(Bytes(s, start, end), p) - assert forall i int :: { &s[i] } start <= i && i < end ==> - s[i] == v[i - start] + ViewAuxSplit(s, start, idx, end) + assert v == ViewAux(s, start, idx) ++ ViewAux(s, idx, end) + assert len(ViewAux(s, start, idx)) == idx - start fold acc(Bytes(s, start, idx), p) fold acc(Bytes(s, idx, end), p) - assert forall i int :: { GetByte(s, start, idx, i) } start <= i && i < idx ==> - GetByte(s, start, idx, i) == v[i - start] - assert forall i int :: { GetByte(s, idx, end, i) } idx <= i && i < end ==> - GetByte(s, idx, end, i) == v[i - start] - ViewEqFromElements(s, start, idx, v[:idx - start], p) - ViewEqFromElements(s, idx, end, v[idx - start:], p) + assert reveal View(s, start, idx) == ViewAux(s, start, idx) + assert reveal View(s, idx, end) == ViewAux(s, idx, end) + assert View(s, start, idx) == v[:idx - start] + assert View(s, idx, end) == v[idx - start:] } ghost @@ -186,22 +219,16 @@ ensures View(s, start, end) == old(View(s, start, idx)) ++ old(View(s, idx, end)) decreases func CombineAtIndex_Bytes(s []byte, start int, end int, idx int, p perm) { - ViewElems(s, start, idx, p) - ViewElems(s, idx, end, p) - v1 := View(s, start, idx) - v2 := View(s, idx, end) + v1 := reveal View(s, start, idx) + v2 := reveal View(s, idx, end) unfold acc(Bytes(s, start, idx), p) unfold acc(Bytes(s, idx, end), p) - assert forall i int :: { &s[i] } start <= i && i < idx ==> - s[i] == v1[i - start] - assert forall i int :: { &s[i] } idx <= i && i < end ==> - s[i] == v2[i - idx] + ViewAuxSplit(s, start, idx, end) + assert v1 == ViewAux(s, start, idx) + assert v2 == ViewAux(s, idx, end) fold acc(Bytes(s, start, end), p) - assert forall i int :: { GetByte(s, start, end, i) } start <= i && i < end ==> - GetByte(s, start, end, i) == (i < idx ? v1[i - start] : v2[i - idx]) - assert forall i int :: { (v1 ++ v2)[i] } 0 <= i && i < end - start ==> - (v1 ++ v2)[i] == (i < idx - start ? v1[i] : v2[i - (idx - start)]) - ViewEqFromElements(s, start, end, v1 ++ v2, p) + assert reveal View(s, start, end) == ViewAux(s, start, end) + assert View(s, start, end) == v1 ++ v2 } ghost @@ -214,18 +241,13 @@ ensures acc(Bytes(s[start:end], 0, len(s[start:end])), p) ensures View(s[start:end], 0, end - start) == old(View(s, start, end)) decreases func Reslice_Bytes(s []byte, start int, end int, p perm) { - ViewElems(s, start, end, p) - v := View(s, start, end) + v := reveal View(s, start, end) unfold acc(Bytes(s, start, end), p) assert forall i int :: { &s[start:end][i] }{ &s[start + i] } 0 <= i && i < (end-start) ==> &s[start:end][i] == &s[start + i] - assert forall i int :: { &s[start + i] } 0 <= i && i < (end-start) ==> - s[start + i] == v[i] - assert forall i int :: { &s[start:end][i] } 0 <= i && i < (end-start) ==> - s[start:end][i] == v[i] + ViewAuxReslice(s, start, end, start, end) + assert v == ViewAux(s[start:end], 0, end - start) fold acc(Bytes(s[start:end], 0, len(s[start:end])), p) - assert forall i int :: { GetByte(s[start:end], 0, end - start, i) } 0 <= i && i < (end-start) ==> - GetByte(s[start:end], 0, end - start, i) == v[i] - ViewEqFromElements(s[start:end], 0, end - start, v, p) + assert reveal View(s[start:end], 0, end - start) == ViewAux(s[start:end], 0, end - start) } ghost @@ -236,32 +258,27 @@ ensures acc(Bytes(s, start, end), p) ensures View(s, start, end) == old(View(s[start:end], 0, end - start)) decreases func Unslice_Bytes(s []byte, start int, end int, p perm) { - ViewElems(s[start:end], 0, len(s[start:end]), p) - v := View(s[start:end], 0, end - start) + v := reveal View(s[start:end], 0, end - start) unfold acc(Bytes(s[start:end], 0, len(s[start:end])), p) assert 0 <= start && start <= end && end <= cap(s) assert forall i int :: { &s[start:end][i] } 0 <= i && i < len(s[start:end]) ==> acc(&s[start:end][i], p) assert forall i int :: { &s[start:end][i] }{ &s[start + i] } 0 <= i && i < len(s[start:end]) ==> &s[start:end][i] == &s[start + i] - assert forall i int :: { &s[start:end][i] } 0 <= i && i < len(s[start:end]) ==> s[start:end][i] == v[i] invariant 0 <= j && j <= len(s[start:end]) invariant forall i int :: { &s[start:end][i] } j <= i && i < len(s[start:end]) ==> acc(&s[start:end][i], p) invariant forall i int :: { &s[start:end][i] }{ &s[start + i] } 0 <= i && i < len(s[start:end]) ==> &s[start:end][i] == &s[start + i] - invariant forall i int :: { &s[start:end][i] } j <= i && i < len(s[start:end]) ==> s[start:end][i] == v[i] invariant forall i int :: { &s[i] } start <= i && i < start+j ==> acc(&s[i], p) - invariant forall i int :: { &s[i] } start <= i && i < start+j ==> s[i] == v[i - start] decreases len(s[start:end]) - j for j := 0; j < len(s[start:end]); j++ { assert forall i int :: { &s[i] } start <= i && i < start+j ==> acc(&s[i], p) assert &s[start:end][j] == &s[start + j] assert acc(&s[start + j], p) - assert s[start + j] == v[j] assert forall i int :: { &s[i] } start <= i && i <= start+j ==> acc(&s[i], p) } + ViewAuxReslice(s, start, end, start, end) + assert v == ViewAux(s, start, end) fold acc(Bytes(s, start, end), p) - assert forall i int :: { GetByte(s, start, end, i) } start <= i && i < end ==> - GetByte(s, start, end, i) == v[i - start] - ViewEqFromElements(s, start, end, v, p) + assert reveal View(s, start, end) == ViewAux(s, start, end) } ghost From 8a3845af3e22c9d68e097efd7c7e240dcf285c5e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 08:39:29 +0000 Subject: [PATCH 13/35] Return the quantified wildcard permissions from the ViewAux lemmas ViewAuxSplit and ViewAuxReslice required the elementwise wildcard permissions without ensuring them back. Within the package this is harmless, but importing packages model the post-state from the contract alone, where the ViewAux applications in the postcondition then lack any permission to the slice elements. Declare the quantified permissions as preserved. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- verification/utils/slices/slices.gobra | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index 37238cd5c..4335f14b9 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -101,9 +101,9 @@ pure func ViewAux(s []byte, i int, end int) (res seq[byte]) { // The proof is by recursion, so that no quantified facts about the // contents are ever introduced. ghost -requires 0 <= start && start <= idx && idx <= end && end <= cap(s) -requires forall k int :: { &s[k] } start <= k && k < end ==> acc(&s[k], _) -ensures ViewAux(s, start, end) == ViewAux(s, start, idx) ++ ViewAux(s, idx, end) +requires 0 <= start && start <= idx && idx <= end && end <= cap(s) +preserves forall k int :: { &s[k] } start <= k && k < end ==> acc(&s[k], _) +ensures ViewAux(s, start, end) == ViewAux(s, start, idx) ++ ViewAux(s, idx, end) decreases idx - start func ViewAuxSplit(s []byte, start int, idx int, end int) { if start == idx { @@ -121,9 +121,9 @@ func ViewAuxSplit(s []byte, start int, idx int, end int) { // the subslice s[start:length]. The proof is by recursion, so that no // quantified facts about the contents are ever introduced. ghost -requires 0 <= start && start <= i && i <= end && end <= length && length <= cap(s) -requires forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], _) -ensures ViewAux(s, i, end) == ViewAux(s[start:length], i - start, end - start) +requires 0 <= start && start <= i && i <= end && end <= length && length <= cap(s) +preserves forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], _) +ensures ViewAux(s, i, end) == ViewAux(s[start:length], i - start, end - start) decreases end - i func ViewAuxReslice(s []byte, start int, length int, i int, end int) { AssertSliceOverlap(s, start, length) From 168be7c73a0900a05ab1d6f98ba0f6968b684d98 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 08:44:51 +0000 Subject: [PATCH 14/35] Bind the slice-overlap facts in ViewAuxReslice's postcondition The application of ViewAux to the subslice in the postcondition is only well-defined given the pointer equalities between the elements of the subslice and of the original slice. Inside the package these facts come from the lemma body, but importing packages only see the contract, so bind them in the postcondition itself with the usual AssertSliceOverlap let-binding. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- verification/utils/slices/slices.gobra | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index 4335f14b9..f6e067ee2 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -123,7 +123,8 @@ func ViewAuxSplit(s []byte, start int, idx int, end int) { ghost requires 0 <= start && start <= i && i <= end && end <= length && length <= cap(s) preserves forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], _) -ensures ViewAux(s, i, end) == ViewAux(s[start:length], i - start, end - start) +ensures let _ := AssertSliceOverlap(s, start, length) in + ViewAux(s, i, end) == ViewAux(s[start:length], i - start, end - start) decreases end - i func ViewAuxReslice(s []byte, start int, length int, i int, end int) { AssertSliceOverlap(s, start, length) From df930ee79238103210a3cfa502a0f6b32ebce212 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 08:46:58 +0000 Subject: [PATCH 15/35] Make ViewAux opaque and unroll it with reveal in the recursion lemmas CombineAtIndex_Bytes and ViewOfSubslice timed out because every ViewAux application in their contexts instantiated both its recursive definitional axiom and its quantified elementwise postcondition. Treat ViewAux like View itself: opaque, exposing only the length of its result. The recursion lemmas unroll the definition one step at a time with reveal, and the elementwise characterization moves into the recursive ViewAuxElems lemma, used only by ViewElems. Also give the verification-directory CI job more headroom, as the slices package now carries the contents reasoning. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- .github/workflows/gobra.yml | 2 +- verification/utils/slices/slices.gobra | 42 ++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/.github/workflows/gobra.yml b/.github/workflows/gobra.yml index 394f12c15..f564ff91d 100644 --- a/.github/workflows/gobra.yml +++ b/.github/workflows/gobra.yml @@ -46,7 +46,7 @@ jobs: recursive: 1 ## Due to a bug, we cannot use the recursive mode with friend pacakge invariants. excludePackages: 'layers' - timeout: 5m + timeout: 10m headerOnly: ${{ env.headerOnly }} module: ${{ env.module }} includePaths: './dependencies/ ..' # relative to project location diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index f6e067ee2..7f90c8b5b 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -74,6 +74,7 @@ decreases func ViewElems(s []byte, start int, end int, p perm) { v := reveal View(s, start, end) unfold acc(Bytes(s, start, end), p/2) + ViewAuxElems(s, start, end) assert forall i int :: { v[i] } 0 <= i && i < end - start ==> v[i] == s[start + i] fold acc(Bytes(s, start, end), p/2) @@ -83,19 +84,38 @@ func ViewElems(s []byte, start int, end int, p perm) { // ViewAux computes the contents of the range [i, end) of s directly // from the elementwise access permissions, so that the recursion does -// not require a predicate instance per subrange. +// not require a predicate instance per subrange. Like View, it is +// opaque and only exposes the length of its result: its definition is +// unrolled in a controlled way, with reveal, by the lemmas below, and +// its elementwise characterization is established by ViewAuxElems. ghost +opaque requires 0 <= i && i <= end && end <= cap(s) requires forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], _) ensures len(res) == end - i -ensures forall k int :: { res[k] } 0 <= k && k < end - i ==> - res[k] == s[i + k] decreases end - i pure func ViewAux(s []byte, i int, end int) (res seq[byte]) { return i == end ? seq[byte]{} : seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) } +// ViewAuxElems establishes the elementwise characterization of +// ViewAux(s, i, end): it contains exactly the bytes of s in the +// range [i, end). +ghost +requires 0 <= i && i <= end && end <= cap(s) +preserves forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], _) +ensures forall k int :: { ViewAux(s, i, end)[k] } 0 <= k && k < end - i ==> + ViewAux(s, i, end)[k] == s[i + k] +decreases end - i +func ViewAuxElems(s []byte, i int, end int) { + reveal ViewAux(s, i, end) + if i != end { + ViewAuxElems(s, i + 1, end) + assert ViewAux(s, i, end) == seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) + } +} + // ViewAuxSplit shows that the contents of the range [start, end) are // the concatenation of the contents of [start, idx) and [idx, end). // The proof is by recursion, so that no quantified facts about the @@ -107,12 +127,16 @@ ensures ViewAux(s, start, end) == ViewAux(s, start, idx) ++ ViewAux(s, idx, en decreases idx - start func ViewAuxSplit(s []byte, start int, idx int, end int) { if start == idx { - assert ViewAux(s, start, idx) == seq[byte]{} + assert reveal ViewAux(s, start, idx) == seq[byte]{} + assert ViewAux(s, start, end) == ViewAux(s, idx, end) } else { ViewAuxSplit(s, start + 1, idx, end) + assert reveal ViewAux(s, start, end) == + seq[byte]{s[start]} ++ ViewAux(s, start + 1, end) + assert reveal ViewAux(s, start, idx) == + seq[byte]{s[start]} ++ ViewAux(s, start + 1, idx) assert ViewAux(s, start, end) == seq[byte]{s[start]} ++ (ViewAux(s, start + 1, idx) ++ ViewAux(s, idx, end)) - assert ViewAux(s, start, idx) == seq[byte]{s[start]} ++ ViewAux(s, start + 1, idx) } } @@ -128,9 +152,15 @@ ensures let _ := AssertSliceOverlap(s, start, length) in decreases end - i func ViewAuxReslice(s []byte, start int, length int, i int, end int) { AssertSliceOverlap(s, start, length) - if i != end { + if i == end { + assert reveal ViewAux(s, i, end) == seq[byte]{} + assert reveal ViewAux(s[start:length], i - start, end - start) == seq[byte]{} + } else { assert &s[start:length][i - start] == &s[i] ViewAuxReslice(s, start, length, i + 1, end) + assert reveal ViewAux(s, i, end) == seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) + assert reveal ViewAux(s[start:length], i - start, end - start) == + seq[byte]{s[start:length][i - start]} ++ ViewAux(s[start:length], i - start + 1, end - start) } } From 073e3679ba256a722258bf2946c31c5c04de8581 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 08:54:01 +0000 Subject: [PATCH 16/35] Fix permission accounting and value framing in the slices lemmas The ViewAux helper lemmas preserved the elementwise permissions at wildcard amount, so callers could no longer restore their predicate instances at a concrete fraction afterwards: consuming a wildcard from p and receiving a wildcard back does not yield p. Give the helper lemmas an explicit permission parameter instead. In Unslice_Bytes, the permission-transfer loop additionally havocs the byte values from the verifier's perspective unless the invariants track them, so tie the contents to the entry view throughout the loop again. Also trigger the bridging assertion of ViewEqFromElements on the caller-supplied sequence, matching its precondition. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- verification/utils/slices/slices.gobra | 47 +++++++++++++-------- verification/utils/slices/slices_test.gobra | 1 + 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index 7f90c8b5b..150a3670c 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -74,7 +74,7 @@ decreases func ViewElems(s []byte, start int, end int, p perm) { v := reveal View(s, start, end) unfold acc(Bytes(s, start, end), p/2) - ViewAuxElems(s, start, end) + ViewAuxElems(s, start, end, p/2) assert forall i int :: { v[i] } 0 <= i && i < end - start ==> v[i] == s[start + i] fold acc(Bytes(s, start, end), p/2) @@ -103,15 +103,16 @@ pure func ViewAux(s []byte, i int, end int) (res seq[byte]) { // ViewAux(s, i, end): it contains exactly the bytes of s in the // range [i, end). ghost +requires 0 < p requires 0 <= i && i <= end && end <= cap(s) -preserves forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], _) +preserves forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], p) ensures forall k int :: { ViewAux(s, i, end)[k] } 0 <= k && k < end - i ==> ViewAux(s, i, end)[k] == s[i + k] decreases end - i -func ViewAuxElems(s []byte, i int, end int) { +func ViewAuxElems(s []byte, i int, end int, p perm) { reveal ViewAux(s, i, end) if i != end { - ViewAuxElems(s, i + 1, end) + ViewAuxElems(s, i + 1, end, p) assert ViewAux(s, i, end) == seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) } } @@ -121,16 +122,17 @@ func ViewAuxElems(s []byte, i int, end int) { // The proof is by recursion, so that no quantified facts about the // contents are ever introduced. ghost +requires 0 < p requires 0 <= start && start <= idx && idx <= end && end <= cap(s) -preserves forall k int :: { &s[k] } start <= k && k < end ==> acc(&s[k], _) +preserves forall k int :: { &s[k] } start <= k && k < end ==> acc(&s[k], p) ensures ViewAux(s, start, end) == ViewAux(s, start, idx) ++ ViewAux(s, idx, end) decreases idx - start -func ViewAuxSplit(s []byte, start int, idx int, end int) { +func ViewAuxSplit(s []byte, start int, idx int, end int, p perm) { if start == idx { assert reveal ViewAux(s, start, idx) == seq[byte]{} assert ViewAux(s, start, end) == ViewAux(s, idx, end) } else { - ViewAuxSplit(s, start + 1, idx, end) + ViewAuxSplit(s, start + 1, idx, end, p) assert reveal ViewAux(s, start, end) == seq[byte]{s[start]} ++ ViewAux(s, start + 1, end) assert reveal ViewAux(s, start, idx) == @@ -145,19 +147,20 @@ func ViewAuxSplit(s []byte, start int, idx int, end int) { // the subslice s[start:length]. The proof is by recursion, so that no // quantified facts about the contents are ever introduced. ghost +requires 0 < p requires 0 <= start && start <= i && i <= end && end <= length && length <= cap(s) -preserves forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], _) +preserves forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], p) ensures let _ := AssertSliceOverlap(s, start, length) in ViewAux(s, i, end) == ViewAux(s[start:length], i - start, end - start) decreases end - i -func ViewAuxReslice(s []byte, start int, length int, i int, end int) { +func ViewAuxReslice(s []byte, start int, length int, i int, end int, p perm) { AssertSliceOverlap(s, start, length) if i == end { assert reveal ViewAux(s, i, end) == seq[byte]{} assert reveal ViewAux(s[start:length], i - start, end - start) == seq[byte]{} } else { assert &s[start:length][i - start] == &s[i] - ViewAuxReslice(s, start, length, i + 1, end) + ViewAuxReslice(s, start, length, i + 1, end, p) assert reveal ViewAux(s, i, end) == seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) assert reveal ViewAux(s[start:length], i - start, end - start) == seq[byte]{s[start:length][i - start]} ++ ViewAux(s[start:length], i - start + 1, end - start) @@ -182,7 +185,7 @@ func ViewEqFromElements(s []byte, start int, end int, other seq[byte], p perm) { v := View(s, start, end) assert forall i int :: { v[i] } 0 <= i && i < end - start ==> v[i] == GetByte(s, start, end, start + i) - assert forall i int :: { v[i] } 0 <= i && i < end - start ==> + assert forall i int :: { other[i] } 0 <= i && i < end - start ==> v[i] == other[i] assert v == other } @@ -205,9 +208,9 @@ func ViewOfSubslice(s []byte, start int, end int, p perm) { unfold acc(Bytes(s, 0, len(s)), p/2) unfold acc(Bytes(s[start:end], 0, end - start), p/2) AssertSliceOverlap(s, start, end) - ViewAuxSplit(s, 0, start, len(s)) - ViewAuxSplit(s, start, end, len(s)) - ViewAuxReslice(s, start, end, start, end) + ViewAuxSplit(s, 0, start, len(s), p/2) + ViewAuxSplit(s, start, end, len(s), p/2) + ViewAuxReslice(s, start, end, start, end, p/2) assert vSub == ViewAux(s, start, end) assert vFull == ViewAux(s, 0, start) ++ (ViewAux(s, start, end) ++ ViewAux(s, end, len(s))) assert len(ViewAux(s, 0, start)) == start @@ -230,7 +233,7 @@ decreases func SplitByIndex_Bytes(s []byte, start int, end int, idx int, p perm) { v := reveal View(s, start, end) unfold acc(Bytes(s, start, end), p) - ViewAuxSplit(s, start, idx, end) + ViewAuxSplit(s, start, idx, end, p) assert v == ViewAux(s, start, idx) ++ ViewAux(s, idx, end) assert len(ViewAux(s, start, idx)) == idx - start fold acc(Bytes(s, start, idx), p) @@ -254,7 +257,7 @@ func CombineAtIndex_Bytes(s []byte, start int, end int, idx int, p perm) { v2 := reveal View(s, idx, end) unfold acc(Bytes(s, start, idx), p) unfold acc(Bytes(s, idx, end), p) - ViewAuxSplit(s, start, idx, end) + ViewAuxSplit(s, start, idx, end, p) assert v1 == ViewAux(s, start, idx) assert v2 == ViewAux(s, idx, end) fold acc(Bytes(s, start, end), p) @@ -275,7 +278,7 @@ func Reslice_Bytes(s []byte, start int, end int, p perm) { v := reveal View(s, start, end) unfold acc(Bytes(s, start, end), p) assert forall i int :: { &s[start:end][i] }{ &s[start + i] } 0 <= i && i < (end-start) ==> &s[start:end][i] == &s[start + i] - ViewAuxReslice(s, start, end, start, end) + ViewAuxReslice(s, start, end, start, end, p) assert v == ViewAux(s[start:end], 0, end - start) fold acc(Bytes(s[start:end], 0, len(s[start:end])), p) assert reveal View(s[start:end], 0, end - start) == ViewAux(s[start:end], 0, end - start) @@ -294,19 +297,27 @@ func Unslice_Bytes(s []byte, start int, end int, p perm) { assert 0 <= start && start <= end && end <= cap(s) assert forall i int :: { &s[start:end][i] } 0 <= i && i < len(s[start:end]) ==> acc(&s[start:end][i], p) assert forall i int :: { &s[start:end][i] }{ &s[start + i] } 0 <= i && i < len(s[start:end]) ==> &s[start:end][i] == &s[start + i] + ViewAuxElems(s[start:end], 0, end - start, p) + assert v == ViewAux(s[start:end], 0, end - start) + assert forall i int :: { &s[start:end][i] } 0 <= i && i < len(s[start:end]) ==> s[start:end][i] == v[i] invariant 0 <= j && j <= len(s[start:end]) invariant forall i int :: { &s[start:end][i] } j <= i && i < len(s[start:end]) ==> acc(&s[start:end][i], p) invariant forall i int :: { &s[start:end][i] }{ &s[start + i] } 0 <= i && i < len(s[start:end]) ==> &s[start:end][i] == &s[start + i] + invariant forall i int :: { &s[start:end][i] } j <= i && i < len(s[start:end]) ==> s[start:end][i] == v[i] invariant forall i int :: { &s[i] } start <= i && i < start+j ==> acc(&s[i], p) + invariant forall i int :: { &s[i] } start <= i && i < start+j ==> s[i] == v[i - start] decreases len(s[start:end]) - j for j := 0; j < len(s[start:end]); j++ { assert forall i int :: { &s[i] } start <= i && i < start+j ==> acc(&s[i], p) assert &s[start:end][j] == &s[start + j] assert acc(&s[start + j], p) + assert s[start + j] == v[j] assert forall i int :: { &s[i] } start <= i && i <= start+j ==> acc(&s[i], p) } - ViewAuxReslice(s, start, end, start, end) + ViewAuxElems(s, start, end, p) + assert forall k int :: { ViewAux(s, start, end)[k] } 0 <= k && k < end - start ==> + ViewAux(s, start, end)[k] == v[k] assert v == ViewAux(s, start, end) fold acc(Bytes(s, start, end), p) assert reveal View(s, start, end) == ViewAux(s, start, end) diff --git a/verification/utils/slices/slices_test.gobra b/verification/utils/slices/slices_test.gobra index 7d91ed5de..7e63a2e29 100644 --- a/verification/utils/slices/slices_test.gobra +++ b/verification/utils/slices/slices_test.gobra @@ -30,6 +30,7 @@ func View_test() { v := View(s, 0, 10) assert len(v) == 10 ViewElems(s, 0, 10, writePerm) + assert View(s, 0, 10)[3] == GetByte(s, 0, 10, 3) assert v[3] == GetByte(s, 0, 10, 3) // splitting preserves the contents of the views From cc5bf60988a9fbd35e1566ebb83f529d9ff35501 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 09:00:40 +0000 Subject: [PATCH 17/35] Keep ViewAux transparent and make ViewElems preserve views and bytes Equating applications of an opaque heap-dependent function across an unfold or fold does not hold up in Silicon, which made every snapshot hop in the slices proofs fail; the definitional axiom of a transparent recursive function is fuel-limited and harmless (hopFields relies on this throughout), so drop the opaque modifier from ViewAux while keeping its contents out of the postconditions. Furthermore, a call to ViewElems havocs the predicate snapshot from the caller's perspective, detaching facts anchored to earlier View and GetByte applications; ViewElems now additionally ensures that the view and the bytes are unchanged, so callers' facts carry across the call regardless of whether they were established before or after it. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- verification/utils/slices/slices.gobra | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index 150a3670c..0073a4897 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -68,8 +68,11 @@ ghost requires 0 < p requires acc(Bytes(s, start, end), p) ensures acc(Bytes(s, start, end), p) +ensures View(s, start, end) == old(View(s, start, end)) ensures forall i int :: { View(s, start, end)[i] } 0 <= i && i < end - start ==> View(s, start, end)[i] == GetByte(s, start, end, start + i) +ensures forall i int :: { old(GetByte(s, start, end, start + i)) } 0 <= i && i < end - start ==> + GetByte(s, start, end, start + i) == old(GetByte(s, start, end, start + i)) decreases func ViewElems(s []byte, start int, end int, p perm) { v := reveal View(s, start, end) @@ -84,12 +87,11 @@ func ViewElems(s []byte, start int, end int, p perm) { // ViewAux computes the contents of the range [i, end) of s directly // from the elementwise access permissions, so that the recursion does -// not require a predicate instance per subrange. Like View, it is -// opaque and only exposes the length of its result: its definition is -// unrolled in a controlled way, with reveal, by the lemmas below, and -// its elementwise characterization is established by ViewAuxElems. +// not require a predicate instance per subrange. Like View, it does +// not expose the contents of its result through a quantified +// postcondition; its elementwise characterization is established by +// ViewAuxElems. ghost -opaque requires 0 <= i && i <= end && end <= cap(s) requires forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], _) ensures len(res) == end - i @@ -110,7 +112,6 @@ ensures forall k int :: { ViewAux(s, i, end)[k] } 0 <= k && k < end - i ==> ViewAux(s, i, end)[k] == s[i + k] decreases end - i func ViewAuxElems(s []byte, i int, end int, p perm) { - reveal ViewAux(s, i, end) if i != end { ViewAuxElems(s, i + 1, end, p) assert ViewAux(s, i, end) == seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) @@ -129,13 +130,13 @@ ensures ViewAux(s, start, end) == ViewAux(s, start, idx) ++ ViewAux(s, idx, en decreases idx - start func ViewAuxSplit(s []byte, start int, idx int, end int, p perm) { if start == idx { - assert reveal ViewAux(s, start, idx) == seq[byte]{} + assert ViewAux(s, start, idx) == seq[byte]{} assert ViewAux(s, start, end) == ViewAux(s, idx, end) } else { ViewAuxSplit(s, start + 1, idx, end, p) - assert reveal ViewAux(s, start, end) == + assert ViewAux(s, start, end) == seq[byte]{s[start]} ++ ViewAux(s, start + 1, end) - assert reveal ViewAux(s, start, idx) == + assert ViewAux(s, start, idx) == seq[byte]{s[start]} ++ ViewAux(s, start + 1, idx) assert ViewAux(s, start, end) == seq[byte]{s[start]} ++ (ViewAux(s, start + 1, idx) ++ ViewAux(s, idx, end)) @@ -156,13 +157,13 @@ decreases end - i func ViewAuxReslice(s []byte, start int, length int, i int, end int, p perm) { AssertSliceOverlap(s, start, length) if i == end { - assert reveal ViewAux(s, i, end) == seq[byte]{} - assert reveal ViewAux(s[start:length], i - start, end - start) == seq[byte]{} + assert ViewAux(s, i, end) == seq[byte]{} + assert ViewAux(s[start:length], i - start, end - start) == seq[byte]{} } else { assert &s[start:length][i - start] == &s[i] ViewAuxReslice(s, start, length, i + 1, end, p) - assert reveal ViewAux(s, i, end) == seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) - assert reveal ViewAux(s[start:length], i - start, end - start) == + assert ViewAux(s, i, end) == seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) + assert ViewAux(s[start:length], i - start, end - start) == seq[byte]{s[start:length][i - start]} ++ ViewAux(s[start:length], i - start + 1, end - start) } } @@ -183,8 +184,6 @@ decreases func ViewEqFromElements(s []byte, start int, end int, other seq[byte], p perm) { ViewElems(s, start, end, p) v := View(s, start, end) - assert forall i int :: { v[i] } 0 <= i && i < end - start ==> - v[i] == GetByte(s, start, end, start + i) assert forall i int :: { other[i] } 0 <= i && i < end - start ==> v[i] == other[i] assert v == other From 65142645e44a97f8858fe67f3068a862bf829fef Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 20:08:23 +0000 Subject: [PATCH 18/35] Carry value preservation through the ViewAux lemmas' contracts The six in-package failures of the last run all had the same shape: a view captured before a call to one of the recursion lemmas could not be related to ViewAux applications after the call, because the lemma consumes the full fraction of the quantified permissions and Silicon does not preserve the snapshot identity across the call. Instead of relying on that identity, the recursion lemmas now promise explicitly that they change neither the bytes of the slice nor the values of the relevant ViewAux applications, so the callers' equalities survive the calls as contract-carried facts. ViewOfSubslice previously unfolded both predicate instances and ran the recursion lemmas over two overlapping quantified permission sets, which made the prover diverge. It is now proved by composing SplitRange_Bytes and CombineRange_Bytes on half of the permissions to the whole slice: the retained fractions pin the views, and the split postcondition provides the subsequence relation directly. CombineRange_Bytes's view postcondition now applies old() to each View application separately instead of to the whole concatenation; this is the shape CombineAtIndex_Bytes already uses, and the only shape that is known to re-check robustly when contracts are imported by the router verification job. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- verification/utils/slices/slices.gobra | 81 +++++++++++++++++++------- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index 0073a4897..1826b6aec 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -77,7 +77,9 @@ decreases func ViewElems(s []byte, start int, end int, p perm) { v := reveal View(s, start, end) unfold acc(Bytes(s, start, end), p/2) + assert v == ViewAux(s, start, end) ViewAuxElems(s, start, end, p/2) + assert v == ViewAux(s, start, end) assert forall i int :: { v[i] } 0 <= i && i < end - start ==> v[i] == s[start + i] fold acc(Bytes(s, start, end), p/2) @@ -103,29 +105,43 @@ pure func ViewAux(s []byte, i int, end int) (res seq[byte]) { // ViewAuxElems establishes the elementwise characterization of // ViewAux(s, i, end): it contains exactly the bytes of s in the -// range [i, end). +// range [i, end). It moreover guarantees that the call does not +// change the bytes of s or the value of ViewAux: callers rely on +// these contract-carried equalities, since facts about the previous +// permission snapshot do not survive the call otherwise. ghost requires 0 < p requires 0 <= i && i <= end && end <= cap(s) preserves forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], p) +ensures forall k int :: { &s[k] } i <= k && k < end ==> s[k] == old(s[k]) +ensures ViewAux(s, i, end) == old(ViewAux(s, i, end)) ensures forall k int :: { ViewAux(s, i, end)[k] } 0 <= k && k < end - i ==> ViewAux(s, i, end)[k] == s[i + k] decreases end - i func ViewAuxElems(s []byte, i int, end int, p perm) { if i != end { ViewAuxElems(s, i + 1, end, p) + assert s[i] == old(s[i]) + assert ViewAux(s, i + 1, end) == old(ViewAux(s, i + 1, end)) assert ViewAux(s, i, end) == seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) + assert old(ViewAux(s, i, end)) == old(seq[byte]{s[i]} ++ ViewAux(s, i + 1, end)) } } // ViewAuxSplit shows that the contents of the range [start, end) are // the concatenation of the contents of [start, idx) and [idx, end). // The proof is by recursion, so that no quantified facts about the -// contents are ever introduced. +// contents are ever introduced. Like ViewAuxElems, it guarantees that +// the bytes of s and the values of ViewAux are not changed by the +// call. ghost requires 0 < p requires 0 <= start && start <= idx && idx <= end && end <= cap(s) preserves forall k int :: { &s[k] } start <= k && k < end ==> acc(&s[k], p) +ensures forall k int :: { &s[k] } start <= k && k < end ==> s[k] == old(s[k]) +ensures ViewAux(s, start, end) == old(ViewAux(s, start, end)) +ensures ViewAux(s, start, idx) == old(ViewAux(s, start, idx)) +ensures ViewAux(s, idx, end) == old(ViewAux(s, idx, end)) ensures ViewAux(s, start, end) == ViewAux(s, start, idx) ++ ViewAux(s, idx, end) decreases idx - start func ViewAuxSplit(s []byte, start int, idx int, end int, p perm) { @@ -134,10 +150,17 @@ func ViewAuxSplit(s []byte, start int, idx int, end int, p perm) { assert ViewAux(s, start, end) == ViewAux(s, idx, end) } else { ViewAuxSplit(s, start + 1, idx, end, p) + assert s[start] == old(s[start]) + assert ViewAux(s, start + 1, end) == old(ViewAux(s, start + 1, end)) + assert ViewAux(s, start + 1, idx) == old(ViewAux(s, start + 1, idx)) assert ViewAux(s, start, end) == seq[byte]{s[start]} ++ ViewAux(s, start + 1, end) + assert old(ViewAux(s, start, end)) == + old(seq[byte]{s[start]} ++ ViewAux(s, start + 1, end)) assert ViewAux(s, start, idx) == seq[byte]{s[start]} ++ ViewAux(s, start + 1, idx) + assert old(ViewAux(s, start, idx)) == + old(seq[byte]{s[start]} ++ ViewAux(s, start + 1, idx)) assert ViewAux(s, start, end) == seq[byte]{s[start]} ++ (ViewAux(s, start + 1, idx) ++ ViewAux(s, idx, end)) } @@ -146,11 +169,15 @@ func ViewAuxSplit(s []byte, start int, idx int, end int, p perm) { // ViewAuxReslice shows that the contents of the range [i, end) of s // coincide with the contents of the range [i - start, end - start) of // the subslice s[start:length]. The proof is by recursion, so that no -// quantified facts about the contents are ever introduced. +// quantified facts about the contents are ever introduced. Like +// ViewAuxElems, it guarantees that the bytes of s and the values of +// ViewAux are not changed by the call. ghost requires 0 < p requires 0 <= start && start <= i && i <= end && end <= length && length <= cap(s) preserves forall k int :: { &s[k] } i <= k && k < end ==> acc(&s[k], p) +ensures forall k int :: { &s[k] } i <= k && k < end ==> s[k] == old(s[k]) +ensures ViewAux(s, i, end) == old(ViewAux(s, i, end)) ensures let _ := AssertSliceOverlap(s, start, length) in ViewAux(s, i, end) == ViewAux(s[start:length], i - start, end - start) decreases end - i @@ -162,7 +189,10 @@ func ViewAuxReslice(s []byte, start int, length int, i int, end int, p perm) { } else { assert &s[start:length][i - start] == &s[i] ViewAuxReslice(s, start, length, i + 1, end, p) + assert s[i] == old(s[i]) + assert ViewAux(s, i + 1, end) == old(ViewAux(s, i + 1, end)) assert ViewAux(s, i, end) == seq[byte]{s[i]} ++ ViewAux(s, i + 1, end) + assert old(ViewAux(s, i, end)) == old(seq[byte]{s[i]} ++ ViewAux(s, i + 1, end)) assert ViewAux(s[start:length], i - start, end - start) == seq[byte]{s[start:length][i - start]} ++ ViewAux(s[start:length], i - start + 1, end - start) } @@ -184,6 +214,12 @@ decreases func ViewEqFromElements(s []byte, start int, end int, other seq[byte], p perm) { ViewElems(s, start, end, p) v := View(s, start, end) + assert forall i int :: { other[i] } 0 <= i && i < end - start ==> + other[i] == old(GetByte(s, start, end, start + i)) + assert forall i int :: { other[i] } 0 <= i && i < end - start ==> + other[i] == GetByte(s, start, end, start + i) + assert forall i int :: { v[i] } 0 <= i && i < end - start ==> + v[i] == GetByte(s, start, end, start + i) assert forall i int :: { other[i] } 0 <= i && i < end - start ==> v[i] == other[i] assert v == other @@ -202,22 +238,20 @@ preserves acc(Bytes(s[start:end], 0, end - start), p) ensures View(s[start:end], 0, end - start) == View(s, 0, len(s))[start:end] decreases func ViewOfSubslice(s []byte, start int, end int, p perm) { - vFull := reveal View(s, 0, len(s)) - vSub := reveal View(s[start:end], 0, end - start) - unfold acc(Bytes(s, 0, len(s)), p/2) - unfold acc(Bytes(s[start:end], 0, end - start), p/2) - AssertSliceOverlap(s, start, end) - ViewAuxSplit(s, 0, start, len(s), p/2) - ViewAuxSplit(s, start, end, len(s), p/2) - ViewAuxReslice(s, start, end, start, end, p/2) - assert vSub == ViewAux(s, start, end) - assert vFull == ViewAux(s, 0, start) ++ (ViewAux(s, start, end) ++ ViewAux(s, end, len(s))) - assert len(ViewAux(s, 0, start)) == start - assert vFull[start:] == ViewAux(s, start, end) ++ ViewAux(s, end, len(s)) - assert vFull[start:][:end - start] == ViewAux(s, start, end) - assert vFull[start:end] == vFull[start:][:end - start] - fold acc(Bytes(s[start:end], 0, end - start), p/2) - fold acc(Bytes(s, 0, len(s)), p/2) + // The proof composes the split and combine lemmas on half of the + // permissions to the instance covering all of s. Splitting derives + // a second predicate instance for s[start:end], whose view is a + // subsequence of the view of s by the postcondition of + // SplitRange_Bytes; since positive fractions of both instances of + // the subslice predicate are held throughout, both instances agree + // on the view of the subslice, and the derived instance can be + // combined back without changing any views. + vFull := View(s, 0, len(s)) + SplitRange_Bytes(s, start, end, p/2) + assert View(s[start:end], 0, end - start) == vFull[start:end] + CombineRange_Bytes(s, start, end, p/2) + assert View(s, 0, len(s)) == vFull + assert View(s[start:end], 0, end - start) == vFull[start:end] } ghost @@ -232,7 +266,9 @@ decreases func SplitByIndex_Bytes(s []byte, start int, end int, idx int, p perm) { v := reveal View(s, start, end) unfold acc(Bytes(s, start, end), p) + assert v == ViewAux(s, start, end) ViewAuxSplit(s, start, idx, end, p) + assert v == ViewAux(s, start, end) assert v == ViewAux(s, start, idx) ++ ViewAux(s, idx, end) assert len(ViewAux(s, start, idx)) == idx - start fold acc(Bytes(s, start, idx), p) @@ -256,6 +292,8 @@ func CombineAtIndex_Bytes(s []byte, start int, end int, idx int, p perm) { v2 := reveal View(s, idx, end) unfold acc(Bytes(s, start, idx), p) unfold acc(Bytes(s, idx, end), p) + assert v1 == ViewAux(s, start, idx) + assert v2 == ViewAux(s, idx, end) ViewAuxSplit(s, start, idx, end, p) assert v1 == ViewAux(s, start, idx) assert v2 == ViewAux(s, idx, end) @@ -277,7 +315,9 @@ func Reslice_Bytes(s []byte, start int, end int, p perm) { v := reveal View(s, start, end) unfold acc(Bytes(s, start, end), p) assert forall i int :: { &s[start:end][i] }{ &s[start + i] } 0 <= i && i < (end-start) ==> &s[start:end][i] == &s[start + i] + assert v == ViewAux(s, start, end) ViewAuxReslice(s, start, end, start, end, p) + assert v == ViewAux(s, start, end) assert v == ViewAux(s[start:end], 0, end - start) fold acc(Bytes(s[start:end], 0, len(s[start:end])), p) assert reveal View(s[start:end], 0, end - start) == ViewAux(s[start:end], 0, end - start) @@ -296,6 +336,7 @@ func Unslice_Bytes(s []byte, start int, end int, p perm) { assert 0 <= start && start <= end && end <= cap(s) assert forall i int :: { &s[start:end][i] } 0 <= i && i < len(s[start:end]) ==> acc(&s[start:end][i], p) assert forall i int :: { &s[start:end][i] }{ &s[start + i] } 0 <= i && i < len(s[start:end]) ==> &s[start:end][i] == &s[start + i] + assert v == ViewAux(s[start:end], 0, end - start) ViewAuxElems(s[start:end], 0, end - start, p) assert v == ViewAux(s[start:end], 0, end - start) assert forall i int :: { &s[start:end][i] } 0 <= i && i < len(s[start:end]) ==> s[start:end][i] == v[i] @@ -352,7 +393,7 @@ requires acc(Bytes(s, 0, start), p) requires acc(Bytes(s, end, len(s)), p) ensures acc(Bytes(s, 0, len(s)), p) ensures View(s, 0, len(s)) == - old(View(s, 0, start) ++ View(s[start:end], 0, end-start) ++ View(s, end, len(s))) + old(View(s, 0, start)) ++ old(View(s[start:end], 0, end-start)) ++ old(View(s, end, len(s))) decreases func CombineRange_Bytes(s []byte, start int, end int, p perm) { vLeft := View(s, 0, start) From 167e926b898b44db4b5aedfaa707195182c6f0e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 20:25:10 +0000 Subject: [PATCH 19/35] Keep ViewAux well-defined at the fold boundaries and pin views in slayers The previous round left four ill-formedness errors: the asserts that relate a freshly folded predicate instance to ViewAux evaluated ViewAux after all element permissions had been folded away. The folds now happen in two halves, so that the bridging assert runs while half of the element permissions are still held. ViewAuxSplit stopped terminating under its enlarged contract; its byte-preservation quantifier is not used by any caller (framing covers the one byte the proof needs), so it is dropped, together with two redundant guiding asserts. In slayers, the common-header outline of SCION.DecodeFromBytes unfolded the full fraction of Bytes(data), losing the connection between the captured view and the refolded instance, which made the GetPathType postcondition fail. It now unfolds only R42 out of R41, so the retained instance pins the view. SCION.SerializeTo's assert vSer0[4] == b4 failed because no GetByte term from the state before the ViewElems call existed, leaving the lemma's preservation postcondition uninstantiated; explicit GetByte asserts on both sides of the call provide the triggers. DecodeFromBytes also gets exhaleMode(1), matching the setter lemmas in path/scion, as an attempt to stop its divergence under the default exhale mode. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/scion.go | 16 ++++++++++++++-- verification/utils/slices/slices.gobra | 21 +++++++++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/pkg/slayers/scion.go b/pkg/slayers/scion.go index 5dea2907f..fda6ee6e3 100644 --- a/pkg/slayers/scion.go +++ b/pkg/slayers/scion.go @@ -267,8 +267,15 @@ func (s *SCION) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeO // @ ghost b4 := buf[4] // @ ghost b8 := buf[8] // @ fold acc(sl.Bytes(uSerBufN, 0, len(uSerBufN)), writePerm) + // the GetByte terms below instantiate the preservation + // postconditions of ViewElems, which carry the byte values of the + // buffer across the lemma call + // @ assert sl.GetByte(uSerBufN, 0, len(uSerBufN), 4) == b4 + // @ assert sl.GetByte(uSerBufN, 0, len(uSerBufN), 8) == b8 // @ sl.ViewElems(uSerBufN, 0, len(uSerBufN), writePerm) // @ ghost vSer0 := sl.View(uSerBufN, 0, len(uSerBufN)) + // @ assert vSer0[4] == sl.GetByte(uSerBufN, 0, len(uSerBufN), 4) + // @ assert vSer0[8] == sl.GetByte(uSerBufN, 0, len(uSerBufN), 8) // @ assert vSer0[4] == b4 && vSer0[8] == b8 // @ ghost if s.EqPathType(ubuf) { // @ assert reveal s.EqPathTypeWithBuffer(ubuf, vSer0) @@ -326,6 +333,7 @@ func (s *SCION) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeO // @ ensures res == nil ==> s.EqPathType(data) // @ ensures res != nil ==> s.NonInitMem() && res.ErrorMem() // @ decreases +// @ #backend[exhaleMode(1)] func (s *SCION) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) (res error) { // Decode common header. if len(data) < CmnHdrLen { @@ -360,7 +368,10 @@ func (s *SCION) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) (res er // @ outline( // @ ghost v := sl.View(data, 0, len(data)) // @ sl.ViewElems(data, 0, len(data), R42) - // @ unfold acc(sl.Bytes(data, 0, len(data)), R41) + // unfolding only part of the held fraction pins the view of data: + // the retained instance is untouched, so the view after refolding + // is the same as the captured one + // @ unfold acc(sl.Bytes(data, 0, len(data)), R42) s.NextHdr = L4ProtocolType(data[4]) s.HdrLen = data[5] // @ assert &data[6:8][0] == &data[6] && &data[6:8][1] == &data[7] @@ -373,7 +384,8 @@ func (s *SCION) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) (res er s.SrcAddrType = AddrType(data[9] & 0x7) // @ assert int(s.SrcAddrType) == b.BitAnd7(int(data[9])) // @ assert v[4] == data[4] && v[5] == data[5] && v[8] == data[8] && v[9] == data[9] - // @ fold acc(sl.Bytes(data, 0, len(data)), R41) + // @ fold acc(sl.Bytes(data, 0, len(data)), R42) + // @ assert sl.View(data, 0, len(data)) == v // @ ) // Decode address header. // @ sl.SplitByIndex_Bytes(data, 0, len(data), CmnHdrLen, R41) diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index 1826b6aec..511b43d7b 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -138,7 +138,6 @@ ghost requires 0 < p requires 0 <= start && start <= idx && idx <= end && end <= cap(s) preserves forall k int :: { &s[k] } start <= k && k < end ==> acc(&s[k], p) -ensures forall k int :: { &s[k] } start <= k && k < end ==> s[k] == old(s[k]) ensures ViewAux(s, start, end) == old(ViewAux(s, start, end)) ensures ViewAux(s, start, idx) == old(ViewAux(s, start, idx)) ensures ViewAux(s, idx, end) == old(ViewAux(s, idx, end)) @@ -151,8 +150,6 @@ func ViewAuxSplit(s []byte, start int, idx int, end int, p perm) { } else { ViewAuxSplit(s, start + 1, idx, end, p) assert s[start] == old(s[start]) - assert ViewAux(s, start + 1, end) == old(ViewAux(s, start + 1, end)) - assert ViewAux(s, start + 1, idx) == old(ViewAux(s, start + 1, idx)) assert ViewAux(s, start, end) == seq[byte]{s[start]} ++ ViewAux(s, start + 1, end) assert old(ViewAux(s, start, end)) == @@ -271,10 +268,15 @@ func SplitByIndex_Bytes(s []byte, start int, end int, idx int, p perm) { assert v == ViewAux(s, start, end) assert v == ViewAux(s, start, idx) ++ ViewAux(s, idx, end) assert len(ViewAux(s, start, idx)) == idx - start - fold acc(Bytes(s, start, idx), p) - fold acc(Bytes(s, idx, end), p) + // fold in two halves: the views of the freshly folded instances are + // related to ViewAux while the remaining half of the element + // permissions still makes ViewAux well-defined + fold acc(Bytes(s, start, idx), p/2) + fold acc(Bytes(s, idx, end), p/2) assert reveal View(s, start, idx) == ViewAux(s, start, idx) assert reveal View(s, idx, end) == ViewAux(s, idx, end) + fold acc(Bytes(s, start, idx), p/2) + fold acc(Bytes(s, idx, end), p/2) assert View(s, start, idx) == v[:idx - start] assert View(s, idx, end) == v[idx - start:] } @@ -297,8 +299,9 @@ func CombineAtIndex_Bytes(s []byte, start int, end int, idx int, p perm) { ViewAuxSplit(s, start, idx, end, p) assert v1 == ViewAux(s, start, idx) assert v2 == ViewAux(s, idx, end) - fold acc(Bytes(s, start, end), p) + fold acc(Bytes(s, start, end), p/2) assert reveal View(s, start, end) == ViewAux(s, start, end) + fold acc(Bytes(s, start, end), p/2) assert View(s, start, end) == v1 ++ v2 } @@ -319,8 +322,9 @@ func Reslice_Bytes(s []byte, start int, end int, p perm) { ViewAuxReslice(s, start, end, start, end, p) assert v == ViewAux(s, start, end) assert v == ViewAux(s[start:end], 0, end - start) - fold acc(Bytes(s[start:end], 0, len(s[start:end])), p) + fold acc(Bytes(s[start:end], 0, len(s[start:end])), p/2) assert reveal View(s[start:end], 0, end - start) == ViewAux(s[start:end], 0, end - start) + fold acc(Bytes(s[start:end], 0, len(s[start:end])), p/2) } ghost @@ -359,8 +363,9 @@ func Unslice_Bytes(s []byte, start int, end int, p perm) { assert forall k int :: { ViewAux(s, start, end)[k] } 0 <= k && k < end - start ==> ViewAux(s, start, end)[k] == v[k] assert v == ViewAux(s, start, end) - fold acc(Bytes(s, start, end), p) + fold acc(Bytes(s, start, end), p/2) assert reveal View(s, start, end) == ViewAux(s, start, end) + fold acc(Bytes(s, start, end), p/2) } ghost From 05f91ed765972ca80c617ec62902832d2444195c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 20:41:12 +0000 Subject: [PATCH 20/35] Slice the view postcondition of CombineRange_Bytes into separate clauses Both the single-old and the per-application-old formulations of CombineRange_Bytes's view relation fail to re-check under the router job's verification flags, each time reporting the first View application under old(). Stating the relation as three separate clauses, one per predicate instance and each equating a subsequence of the combined view with the old view of one instance, gives the importing check smaller assertions to handle, isolates which application the failure belongs to, and keeps the other clauses usable at call sites even if one clause keeps failing. The information content is unchanged, and callers consume the relation in this sliced form anyway. ViewAuxSplit still did not terminate after its contract was slimmed, so it now runs with exhaleMode(1), like the setter lemmas in path/scion that faced similar divergence. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- verification/utils/slices/slices.gobra | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index 511b43d7b..c4e71fde2 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -143,6 +143,7 @@ ensures ViewAux(s, start, idx) == old(ViewAux(s, start, idx)) ensures ViewAux(s, idx, end) == old(ViewAux(s, idx, end)) ensures ViewAux(s, start, end) == ViewAux(s, start, idx) ++ ViewAux(s, idx, end) decreases idx - start +#backend[exhaleMode(1)] func ViewAuxSplit(s []byte, start int, idx int, end int, p perm) { if start == idx { assert ViewAux(s, start, idx) == seq[byte]{} @@ -397,8 +398,9 @@ requires acc(Bytes(s[start:end], 0, end-start), p) requires acc(Bytes(s, 0, start), p) requires acc(Bytes(s, end, len(s)), p) ensures acc(Bytes(s, 0, len(s)), p) -ensures View(s, 0, len(s)) == - old(View(s, 0, start)) ++ old(View(s[start:end], 0, end-start)) ++ old(View(s, end, len(s))) +ensures View(s, 0, len(s))[:start] == old(View(s, 0, start)) +ensures View(s, 0, len(s))[end:] == old(View(s, end, len(s))) +ensures View(s, 0, len(s))[start:end] == old(View(s[start:end], 0, end-start)) decreases func CombineRange_Bytes(s []byte, start int, end int, p perm) { vLeft := View(s, 0, start) @@ -408,7 +410,13 @@ func CombineRange_Bytes(s []byte, start int, end int, p perm) { CombineAtIndex_Bytes(s, start, len(s), end, p) CombineAtIndex_Bytes(s, 0, len(s), start, p) assert View(s, 0, len(s)) == vLeft ++ (vMid ++ vRight) - assert vLeft ++ (vMid ++ vRight) == vLeft ++ vMid ++ vRight + assert len(vLeft) == start && len(vMid) == end - start + assert View(s, 0, len(s))[:start] == vLeft + assert View(s, 0, len(s))[start:] == vMid ++ vRight + assert View(s, 0, len(s))[start:][:end - start] == vMid + assert View(s, 0, len(s))[start:end] == View(s, 0, len(s))[start:][:end - start] + assert View(s, 0, len(s))[start:][end - start:] == vRight + assert View(s, 0, len(s))[end:] == View(s, 0, len(s))[start:][end - start:] } // CombineRangeNoView_Bytes behaves like CombineRange_Bytes, but does not From 7e714c2948114613fc671d0029b47eef5bab24bf Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 20:56:45 +0000 Subject: [PATCH 21/35] Split ViewAuxSplit's preservation facts across two lemmas Carrying the preservation of all three ViewAux ranges in a single recursive lemma made the prover diverge, and forcing exhaleMode(1) on it crashed the prover instead. Each caller only needs part of the contract, so the lemma is split accordingly: ViewAuxSplit keeps the concatenation equation and the preservation of the whole range, which is what SplitByIndex_Bytes consumes, and the new ViewAuxSplitParts provides the equation and the preservation of the two subranges, which is what CombineAtIndex_Bytes consumes. Each proof now contains a single old-state definitional step, the same size at which ViewAuxElems already verifies. The byte-preservation postcondition of ViewElems now also triggers on the current-state GetByte term, so that consumers reach the old value from terms the elementwise characterization creates. InfoField.SerializeTo and HopField.SerializeTo failed with the same missing-trigger shape as SCION.SerializeTo did before: the views taken after ViewElems could not be related to the bytes captured before the fold, because no GetByte term from the pre-call state existed. The same explicit GetByte asserts on both sides of the call provide the anchors, covering the MAC bytes as well in HopField's case. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/hopfield.go | 11 +++++++ pkg/slayers/path/infofield.go | 10 ++++++ verification/utils/slices/slices.gobra | 44 +++++++++++++++++++++----- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/pkg/slayers/path/hopfield.go b/pkg/slayers/path/hopfield.go index fd0807ae0..517974afe 100644 --- a/pkg/slayers/path/hopfield.go +++ b/pkg/slayers/path/hopfield.go @@ -159,8 +159,19 @@ func (h *HopField) SerializeTo(b []byte) (err error) { //@ ghost mac := seq[byte]{b[6], b[7], b[8], b[9], b[10], b[11]} //@ assert forall i int :: { mac[i] } 0 <= i && i < MacLen ==> mac[i] == h.Mac[i] //@ fold sl.Bytes(b, 0, HopLen) + // the GetByte terms instantiate the preservation postconditions of + // ViewElems, which carry the byte values across the lemma call + //@ assert sl.GetByte(b, 0, HopLen, 2) == b2 && sl.GetByte(b, 0, HopLen, 3) == b3 + //@ assert sl.GetByte(b, 0, HopLen, 4) == b4 && sl.GetByte(b, 0, HopLen, 5) == b5 + //@ assert sl.GetByte(b, 0, HopLen, 6) == mac[0] && sl.GetByte(b, 0, HopLen, 7) == mac[1] + //@ assert sl.GetByte(b, 0, HopLen, 8) == mac[2] && sl.GetByte(b, 0, HopLen, 9) == mac[3] + //@ assert sl.GetByte(b, 0, HopLen, 10) == mac[4] && sl.GetByte(b, 0, HopLen, 11) == mac[5] //@ sl.ViewElems(b, 0, HopLen, writePerm) //@ ghost v := sl.View(b, 0, HopLen) + //@ assert v[2] == sl.GetByte(b, 0, HopLen, 2) && v[3] == sl.GetByte(b, 0, HopLen, 3) + //@ assert v[4] == sl.GetByte(b, 0, HopLen, 4) && v[5] == sl.GetByte(b, 0, HopLen, 5) + //@ assert v[6] == mac[0] && v[7] == mac[1] && v[8] == mac[2] + //@ assert v[9] == mac[3] && v[10] == mac[4] && v[11] == mac[5] //@ assert v[2] == b2 && v[3] == b3 && v[4] == b4 && v[5] == b5 //@ assert forall j int :: { v[j] } 6 <= j && j < 6+MacLen ==> v[j] == mac[j-6] //@ assert forall i int :: { v[6:6+MacLen][i] } 0 <= i && i < MacLen ==> diff --git a/pkg/slayers/path/infofield.go b/pkg/slayers/path/infofield.go index 2d644889d..ad8445884 100644 --- a/pkg/slayers/path/infofield.go +++ b/pkg/slayers/path/infofield.go @@ -134,8 +134,18 @@ func (inf *InfoField) SerializeTo(b []byte) (err error) { //@ ghost b6 := b[6] //@ ghost b7 := b[7] //@ fold slices.Bytes(b, 0, len(b)) + // the GetByte terms instantiate the preservation postconditions of + // ViewElems, which carry the byte values across the lemma call + //@ assert slices.GetByte(b, 0, len(b), 0) == b0 + //@ assert slices.GetByte(b, 0, len(b), 2) == b2 && slices.GetByte(b, 0, len(b), 3) == b3 + //@ assert slices.GetByte(b, 0, len(b), 4) == b4 && slices.GetByte(b, 0, len(b), 5) == b5 + //@ assert slices.GetByte(b, 0, len(b), 6) == b6 && slices.GetByte(b, 0, len(b), 7) == b7 //@ slices.ViewElems(b, 0, len(b), writePerm) //@ ghost v := slices.View(b, 0, len(b)) + //@ assert v[0] == slices.GetByte(b, 0, len(b), 0) + //@ assert v[2] == slices.GetByte(b, 0, len(b), 2) && v[3] == slices.GetByte(b, 0, len(b), 3) + //@ assert v[4] == slices.GetByte(b, 0, len(b), 4) && v[5] == slices.GetByte(b, 0, len(b), 5) + //@ assert v[6] == slices.GetByte(b, 0, len(b), 6) && v[7] == slices.GetByte(b, 0, len(b), 7) //@ assert v[0] == b0 && v[2] == b2 && v[3] == b3 //@ assert v[4] == b4 && v[5] == b5 && v[6] == b6 && v[7] == b7 //@ assert inf.ToAbsInfoField() == diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index c4e71fde2..8a1e68dc1 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -71,7 +71,7 @@ ensures acc(Bytes(s, start, end), p) ensures View(s, start, end) == old(View(s, start, end)) ensures forall i int :: { View(s, start, end)[i] } 0 <= i && i < end - start ==> View(s, start, end)[i] == GetByte(s, start, end, start + i) -ensures forall i int :: { old(GetByte(s, start, end, start + i)) } 0 <= i && i < end - start ==> +ensures forall i int :: { old(GetByte(s, start, end, start + i)) } { GetByte(s, start, end, start + i) } 0 <= i && i < end - start ==> GetByte(s, start, end, start + i) == old(GetByte(s, start, end, start + i)) decreases func ViewElems(s []byte, start int, end int, p perm) { @@ -131,19 +131,19 @@ func ViewAuxElems(s []byte, i int, end int, p perm) { // ViewAuxSplit shows that the contents of the range [start, end) are // the concatenation of the contents of [start, idx) and [idx, end). // The proof is by recursion, so that no quantified facts about the -// contents are ever introduced. Like ViewAuxElems, it guarantees that -// the bytes of s and the values of ViewAux are not changed by the -// call. +// contents are ever introduced. It additionally guarantees that the +// value of ViewAux over the whole range is not changed by the call, +// which is the fact SplitByIndex_Bytes needs. The preservation facts +// for the two subranges are provided by the companion lemma +// ViewAuxSplitParts; carrying all preservation facts in a single +// lemma makes the prover diverge. ghost requires 0 < p requires 0 <= start && start <= idx && idx <= end && end <= cap(s) preserves forall k int :: { &s[k] } start <= k && k < end ==> acc(&s[k], p) ensures ViewAux(s, start, end) == old(ViewAux(s, start, end)) -ensures ViewAux(s, start, idx) == old(ViewAux(s, start, idx)) -ensures ViewAux(s, idx, end) == old(ViewAux(s, idx, end)) ensures ViewAux(s, start, end) == ViewAux(s, start, idx) ++ ViewAux(s, idx, end) decreases idx - start -#backend[exhaleMode(1)] func ViewAuxSplit(s []byte, start int, idx int, end int, p perm) { if start == idx { assert ViewAux(s, start, idx) == seq[byte]{} @@ -155,10 +155,38 @@ func ViewAuxSplit(s []byte, start int, idx int, end int, p perm) { seq[byte]{s[start]} ++ ViewAux(s, start + 1, end) assert old(ViewAux(s, start, end)) == old(seq[byte]{s[start]} ++ ViewAux(s, start + 1, end)) + assert ViewAux(s, start, idx) == + seq[byte]{s[start]} ++ ViewAux(s, start + 1, idx) + assert ViewAux(s, start, end) == + seq[byte]{s[start]} ++ (ViewAux(s, start + 1, idx) ++ ViewAux(s, idx, end)) + } +} + +// ViewAuxSplitParts is the companion of ViewAuxSplit used by +// CombineAtIndex_Bytes: it establishes the same concatenation +// equation, but promises that the values of ViewAux over the two +// subranges are not changed by the call. +ghost +requires 0 < p +requires 0 <= start && start <= idx && idx <= end && end <= cap(s) +preserves forall k int :: { &s[k] } start <= k && k < end ==> acc(&s[k], p) +ensures ViewAux(s, start, idx) == old(ViewAux(s, start, idx)) +ensures ViewAux(s, idx, end) == old(ViewAux(s, idx, end)) +ensures ViewAux(s, start, end) == ViewAux(s, start, idx) ++ ViewAux(s, idx, end) +decreases idx - start +func ViewAuxSplitParts(s []byte, start int, idx int, end int, p perm) { + if start == idx { + assert ViewAux(s, start, idx) == seq[byte]{} + assert ViewAux(s, start, end) == ViewAux(s, idx, end) + } else { + ViewAuxSplitParts(s, start + 1, idx, end, p) + assert s[start] == old(s[start]) assert ViewAux(s, start, idx) == seq[byte]{s[start]} ++ ViewAux(s, start + 1, idx) assert old(ViewAux(s, start, idx)) == old(seq[byte]{s[start]} ++ ViewAux(s, start + 1, idx)) + assert ViewAux(s, start, end) == + seq[byte]{s[start]} ++ ViewAux(s, start + 1, end) assert ViewAux(s, start, end) == seq[byte]{s[start]} ++ (ViewAux(s, start + 1, idx) ++ ViewAux(s, idx, end)) } @@ -297,7 +325,7 @@ func CombineAtIndex_Bytes(s []byte, start int, end int, idx int, p perm) { unfold acc(Bytes(s, idx, end), p) assert v1 == ViewAux(s, start, idx) assert v2 == ViewAux(s, idx, end) - ViewAuxSplit(s, start, idx, end, p) + ViewAuxSplitParts(s, start, idx, end, p) assert v1 == ViewAux(s, start, idx) assert v2 == ViewAux(s, idx, end) fold acc(Bytes(s, start, end), p/2) From f64553356d980709b5c7deaf7498bc9b53ae3889 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 21:07:07 +0000 Subject: [PATCH 22/35] Pin predicate snapshots across ViewElems calls by retaining a fraction The last slices error was the step in ViewEqFromElements from the precondition's characterization of `other` (in terms of the old GetByte values) to the current state: the instantiation chain across the lemma call did not go through. The DecodeFromBytes proofs of InfoField and HopField already demonstrate the robust alternative: calling ViewElems with only part of the held fraction keeps a positive fraction of the predicate instance untouched, so its snapshot, and with it every GetByte and View application, is the same term before and after the call, and no old-state reasoning is needed at all. ViewEqFromElements now calls ViewElems with p/2, and the SerializeTo proofs of InfoField and HopField call it with half the write permission. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/hopfield.go | 2 +- pkg/slayers/path/infofield.go | 2 +- verification/utils/slices/slices.gobra | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/slayers/path/hopfield.go b/pkg/slayers/path/hopfield.go index 517974afe..06fc61e91 100644 --- a/pkg/slayers/path/hopfield.go +++ b/pkg/slayers/path/hopfield.go @@ -166,7 +166,7 @@ func (h *HopField) SerializeTo(b []byte) (err error) { //@ assert sl.GetByte(b, 0, HopLen, 6) == mac[0] && sl.GetByte(b, 0, HopLen, 7) == mac[1] //@ assert sl.GetByte(b, 0, HopLen, 8) == mac[2] && sl.GetByte(b, 0, HopLen, 9) == mac[3] //@ assert sl.GetByte(b, 0, HopLen, 10) == mac[4] && sl.GetByte(b, 0, HopLen, 11) == mac[5] - //@ sl.ViewElems(b, 0, HopLen, writePerm) + //@ sl.ViewElems(b, 0, HopLen, writePerm/2) //@ ghost v := sl.View(b, 0, HopLen) //@ assert v[2] == sl.GetByte(b, 0, HopLen, 2) && v[3] == sl.GetByte(b, 0, HopLen, 3) //@ assert v[4] == sl.GetByte(b, 0, HopLen, 4) && v[5] == sl.GetByte(b, 0, HopLen, 5) diff --git a/pkg/slayers/path/infofield.go b/pkg/slayers/path/infofield.go index ad8445884..36607937a 100644 --- a/pkg/slayers/path/infofield.go +++ b/pkg/slayers/path/infofield.go @@ -140,7 +140,7 @@ func (inf *InfoField) SerializeTo(b []byte) (err error) { //@ assert slices.GetByte(b, 0, len(b), 2) == b2 && slices.GetByte(b, 0, len(b), 3) == b3 //@ assert slices.GetByte(b, 0, len(b), 4) == b4 && slices.GetByte(b, 0, len(b), 5) == b5 //@ assert slices.GetByte(b, 0, len(b), 6) == b6 && slices.GetByte(b, 0, len(b), 7) == b7 - //@ slices.ViewElems(b, 0, len(b), writePerm) + //@ slices.ViewElems(b, 0, len(b), writePerm/2) //@ ghost v := slices.View(b, 0, len(b)) //@ assert v[0] == slices.GetByte(b, 0, len(b), 0) //@ assert v[2] == slices.GetByte(b, 0, len(b), 2) && v[3] == slices.GetByte(b, 0, len(b), 3) diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index 8a1e68dc1..eb433e04e 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -238,10 +238,13 @@ ensures acc(Bytes(s, start, end), p) ensures View(s, start, end) == other decreases func ViewEqFromElements(s []byte, start int, end int, other seq[byte], p perm) { - ViewElems(s, start, end, p) + // calling ViewElems with half of the held fraction retains a + // positive fraction of the predicate instance, which pins its + // snapshot: the GetByte and View applications before and after the + // call are then the same terms, and the precondition characterizes + // the current state directly + ViewElems(s, start, end, p/2) v := View(s, start, end) - assert forall i int :: { other[i] } 0 <= i && i < end - start ==> - other[i] == old(GetByte(s, start, end, start + i)) assert forall i int :: { other[i] } 0 <= i && i < end - start ==> other[i] == GetByte(s, start, end, start + i) assert forall i int :: { v[i] } 0 <= i && i < end - start ==> From 3126d2e3da244caf5a3fcb36a5dc327be02cc362 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 09:15:19 +0000 Subject: [PATCH 23/35] Revert the half-permission ViewElems calls in the field serializers The GetByte anchors alone made both InfoField.SerializeTo and HopField.SerializeTo verify; switching the ViewElems calls to half of the write permission on top of that made HopField.SerializeTo diverge. Go back to the exact form that verified. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/hopfield.go | 2 +- pkg/slayers/path/infofield.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/slayers/path/hopfield.go b/pkg/slayers/path/hopfield.go index 06fc61e91..517974afe 100644 --- a/pkg/slayers/path/hopfield.go +++ b/pkg/slayers/path/hopfield.go @@ -166,7 +166,7 @@ func (h *HopField) SerializeTo(b []byte) (err error) { //@ assert sl.GetByte(b, 0, HopLen, 6) == mac[0] && sl.GetByte(b, 0, HopLen, 7) == mac[1] //@ assert sl.GetByte(b, 0, HopLen, 8) == mac[2] && sl.GetByte(b, 0, HopLen, 9) == mac[3] //@ assert sl.GetByte(b, 0, HopLen, 10) == mac[4] && sl.GetByte(b, 0, HopLen, 11) == mac[5] - //@ sl.ViewElems(b, 0, HopLen, writePerm/2) + //@ sl.ViewElems(b, 0, HopLen, writePerm) //@ ghost v := sl.View(b, 0, HopLen) //@ assert v[2] == sl.GetByte(b, 0, HopLen, 2) && v[3] == sl.GetByte(b, 0, HopLen, 3) //@ assert v[4] == sl.GetByte(b, 0, HopLen, 4) && v[5] == sl.GetByte(b, 0, HopLen, 5) diff --git a/pkg/slayers/path/infofield.go b/pkg/slayers/path/infofield.go index 36607937a..ad8445884 100644 --- a/pkg/slayers/path/infofield.go +++ b/pkg/slayers/path/infofield.go @@ -140,7 +140,7 @@ func (inf *InfoField) SerializeTo(b []byte) (err error) { //@ assert slices.GetByte(b, 0, len(b), 2) == b2 && slices.GetByte(b, 0, len(b), 3) == b3 //@ assert slices.GetByte(b, 0, len(b), 4) == b4 && slices.GetByte(b, 0, len(b), 5) == b5 //@ assert slices.GetByte(b, 0, len(b), 6) == b6 && slices.GetByte(b, 0, len(b), 7) == b7 - //@ slices.ViewElems(b, 0, len(b), writePerm/2) + //@ slices.ViewElems(b, 0, len(b), writePerm) //@ ghost v := slices.View(b, 0, len(b)) //@ assert v[0] == slices.GetByte(b, 0, len(b), 0) //@ assert v[2] == slices.GetByte(b, 0, len(b), 2) && v[3] == slices.GetByte(b, 0, len(b), 3) From a18d7557b5fac7d116540b823a3b93b57158044c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 09:31:14 +0000 Subject: [PATCH 24/35] Pin the view of raw across MetaHdr.DecodeFromBytes's unfold The postcondition relates DecodeFromBytesSpec to the view of raw in the final state, but the body unfolded the full held fraction of Bytes(raw), so the refolded instance's view could not be connected to the captured one. Unfolding only R51 out of the held R50 keeps a positive fraction of the instance untouched, which pins the view, as in SCION.DecodeFromBytes's common-header outline. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/scion/base.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/slayers/path/scion/base.go b/pkg/slayers/path/scion/base.go index 6c05884d5..9b3ce951f 100644 --- a/pkg/slayers/path/scion/base.go +++ b/pkg/slayers/path/scion/base.go @@ -263,7 +263,10 @@ func (m *MetaHdr) DecodeFromBytes(raw []byte) (e error) { } //@ ghost v := sl.View(raw, 0, len(raw)) //@ sl.ViewElems(raw, 0, len(raw), R51) - //@ unfold acc(sl.Bytes(raw, 0, len(raw)), R50) + // unfolding only part of the held fraction pins the view of raw: + // the retained instance is untouched, so the view after refolding + // is the same as the captured one + //@ unfold acc(sl.Bytes(raw, 0, len(raw)), R51) line := binary.BigEndian.Uint32(raw) m.CurrINF = uint8(line >> 30) m.CurrHF = uint8(line>>24) & 0x3F @@ -276,7 +279,8 @@ func (m *MetaHdr) DecodeFromBytes(raw []byte) (e error) { //@ bit.And3fAtMost64(uint8(line>>6)) //@ bit.And3fAtMost64(uint8(line)) //@ assert v[0] == raw[0] && v[1] == raw[1] && v[2] == raw[2] && v[3] == raw[3] - //@ fold acc(sl.Bytes(raw, 0, len(raw)), R50) + //@ fold acc(sl.Bytes(raw, 0, len(raw)), R51) + //@ assert sl.View(raw, 0, len(raw)) == v //@ assert line == binary.BigEndian.Uint32Spec(v[0], v[1], v[2], v[3]) return nil } From f18eb1ea704f069912b9e325f6bbc099f37c618b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 09:47:09 +0000 Subject: [PATCH 25/35] Replace CombineRange_Bytes's old()-based view relation with ghost inputs The view postcondition of CombineRange_Bytes failed to re-check when imported by the router verification job in every formulation tried: a single old() around the concatenation, old() per View application, and one clause per instance all report that the permission to the first instance under old() might not suffice, while the structurally identical clauses of CombineAtIndex_Bytes and Unslice_Bytes pass in the same job. CombineRange_Bytes therefore no longer relates views at all, and the new CombineRangeWithViews_Bytes carries the relation through ghost parameters instead: the preconditions bind the three piece views in the state before the call, so the postconditions need no old() expressions. Call sites satisfy the bindings trivially by passing the View applications themselves. Call sites whose subsequent proof consumes the view relation are migrated to the new lemma; the remaining sites, including all deferred cleanup calls, only recombine permissions and stay on the view-free lemma. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/scion/raw.go | 10 ++--- pkg/slayers/scion.go | 4 +- router/dataplane.go | 30 ++++++------- verification/utils/slices/slices.gobra | 49 ++++++++++++++++----- verification/utils/slices/slices_test.gobra | 3 +- 5 files changed, 61 insertions(+), 35 deletions(-) diff --git a/pkg/slayers/path/scion/raw.go b/pkg/slayers/path/scion/raw.go index efbf569b5..16bb2228d 100644 --- a/pkg/slayers/path/scion/raw.go +++ b/pkg/slayers/path/scion/raw.go @@ -289,7 +289,7 @@ func (s *Raw) IncPath( /*@ ghost ubuf []byte @*/ ) (r error) { //@ b3 := metaView[3] //@ s.PathMeta.SerializeAndDeserializeLemma(b0, b1, b2, b3) //@ assert s.PathMeta.EqAbsHeader(metaView) - //@ sl.CombineRange_Bytes(ubuf, 0, MetaLen, writePerm) + //@ sl.CombineRangeWithViews_Bytes(ubuf, 0, MetaLen, writePerm, sl.View(ubuf, 0, 0), sl.View(ubuf[0:MetaLen], 0, (MetaLen)-(0)), sl.View(ubuf, MetaLen, len(ubuf))) //@ ghost newView := sl.View(ubuf, 0, len(ubuf)) //@ assert newView[:MetaLen] == metaView //@ assert newView[MetaLen:] == tail @@ -364,7 +364,7 @@ func (s *Raw) GetInfoField(idx int /*@, ghost ubuf []byte @*/) (ifield path.Info //@ ghost infoView := sl.View(s.Raw[infOffset:infOffset+path.InfoLen], 0, path.InfoLen) //@ assert infoView[:path.InfoLen] == infoView //@ assert infoView == v[infOffset:infOffset+path.InfoLen] - //@ sl.CombineRange_Bytes(ubuf, infOffset, infOffset+path.InfoLen, R20) + //@ sl.CombineRangeWithViews_Bytes(ubuf, infOffset, infOffset+path.InfoLen, R20, sl.View(ubuf, 0, infOffset), sl.View(ubuf[infOffset:infOffset+path.InfoLen], 0, (infOffset+path.InfoLen)-(infOffset)), sl.View(ubuf, infOffset+path.InfoLen, len(ubuf))) //@ assert sl.View(ubuf, 0, len(ubuf)) == v //@ fold acc(s.Mem(ubuf), R11) //@ assert reveal s.CorrectlyDecodedInfWithIdx(ubuf, idx, info) @@ -453,7 +453,7 @@ func (s *Raw) SetInfoField(info path.InfoField, idx int /*@, ghost ubuf []byte @ //@ ghost newInfoView := sl.View(s.Raw[infOffset:infOffset+path.InfoLen], 0, path.InfoLen) //@ assert newInfoView[:path.InfoLen] == newInfoView //@ assert path.BytesToAbsInfoField(newInfoView) == newInfo - //@ sl.CombineRange_Bytes(ubuf, infOffset, infOffset+path.InfoLen, writePerm) + //@ sl.CombineRangeWithViews_Bytes(ubuf, infOffset, infOffset+path.InfoLen, writePerm, sl.View(ubuf, 0, infOffset), sl.View(ubuf[infOffset:infOffset+path.InfoLen], 0, (infOffset+path.InfoLen)-(infOffset)), sl.View(ubuf, infOffset+path.InfoLen, len(ubuf))) //@ ghost newView := sl.View(ubuf, 0, len(ubuf)) //@ assert newView[:infOffset] == oldView[:infOffset] //@ assert newView[infOffset:infOffset+path.InfoLen] == newInfoView @@ -528,7 +528,7 @@ func (s *Raw) GetHopField(idx int /*@, ghost ubuf []byte @*/) (res path.HopField //@ unfold hop.Mem() //@ ghost hopView := sl.View(s.Raw[hopOffset:hopOffset+path.HopLen], 0, path.HopLen) //@ assert hopView == v[hopOffset:hopOffset+path.HopLen] - //@ sl.CombineRange_Bytes(ubuf, hopOffset, hopOffset+path.HopLen, R20) + //@ sl.CombineRangeWithViews_Bytes(ubuf, hopOffset, hopOffset+path.HopLen, R20, sl.View(ubuf, 0, hopOffset), sl.View(ubuf[hopOffset:hopOffset+path.HopLen], 0, (hopOffset+path.HopLen)-(hopOffset)), sl.View(ubuf, hopOffset+path.HopLen, len(ubuf))) //@ assert sl.View(ubuf, 0, len(ubuf)) == v //@ fold acc(s.Mem(ubuf), R11) //@ assert reveal s.CorrectlyDecodedHfWithIdx(ubuf, idx, hop) @@ -626,7 +626,7 @@ func (s *Raw) SetHopField(hop path.HopField, idx int /*@, ghost ubuf []byte @*/) ret := tmpHopField.SerializeTo(s.Raw[hopOffset : hopOffset+path.HopLen]) //@ ghost newHopView := sl.View(s.Raw[hopOffset:hopOffset+path.HopLen], 0, path.HopLen) //@ assert path.BytesToIO_HF(newHopView) == tmpHopField.Abs() - //@ sl.CombineRange_Bytes(ubuf, hopOffset, hopOffset+path.HopLen, writePerm) + //@ sl.CombineRangeWithViews_Bytes(ubuf, hopOffset, hopOffset+path.HopLen, writePerm, sl.View(ubuf, 0, hopOffset), sl.View(ubuf[hopOffset:hopOffset+path.HopLen], 0, (hopOffset+path.HopLen)-(hopOffset)), sl.View(ubuf, hopOffset+path.HopLen, len(ubuf))) //@ ghost newView := sl.View(ubuf, 0, len(ubuf)) //@ assert newView[:hopOffset] == oldView[:hopOffset] //@ assert newView[hopOffset:hopOffset+path.HopLen] == newHopView diff --git a/pkg/slayers/scion.go b/pkg/slayers/scion.go index fda6ee6e3..c832df9c6 100644 --- a/pkg/slayers/scion.go +++ b/pkg/slayers/scion.go @@ -293,7 +293,7 @@ func (s *SCION) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeO } offset := CmnHdrLen + s.AddrHdrLen( /*@ nil, true @*/ ) - // @ sl.CombineRange_Bytes(uSerBufN, CmnHdrLen, scnLen, writePerm) + // @ sl.CombineRangeWithViews_Bytes(uSerBufN, CmnHdrLen, scnLen, writePerm, sl.View(uSerBufN, 0, CmnHdrLen), sl.View(uSerBufN[CmnHdrLen:scnLen], 0, (scnLen)-(CmnHdrLen)), sl.View(uSerBufN, scnLen, len(uSerBufN))) // @ sl.CombineRange_Bytes(ubuf, CmnHdrLen, len(ubuf), R10) // @ ghost vSerA := sl.View(uSerBufN, 0, len(uSerBufN)) // @ assert vSerA[:CmnHdrLen] == vSer0[:CmnHdrLen] @@ -308,7 +308,7 @@ func (s *SCION) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeO // @ sl.SplitRange_Bytes(uSerBufN, offset, scnLen, writePerm) // @ sl.SplitRange_Bytes(ubuf, startP, endP, writePerm) tmp := s.Path.SerializeTo(buf[offset:] /*@, pathSlice @*/) - // @ sl.CombineRange_Bytes(uSerBufN, offset, scnLen, writePerm) + // @ sl.CombineRangeWithViews_Bytes(uSerBufN, offset, scnLen, writePerm, sl.View(uSerBufN, 0, offset), sl.View(uSerBufN[offset:scnLen], 0, (scnLen)-(offset)), sl.View(uSerBufN, scnLen, len(uSerBufN))) // @ sl.CombineRange_Bytes(ubuf, startP, endP, writePerm) // @ ghost vSer1 := sl.View(uSerBufN, 0, len(uSerBufN)) // @ assert vSer1[:offset] == vSerA[:offset] diff --git a/router/dataplane.go b/router/dataplane.go index 97ce86c6b..9f8ae9f7f 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -1681,7 +1681,7 @@ func (p *scionPacketProcessor) processPkt(rawPkt []byte, // @ sl.CombineRange_Bytes(ub, start, end, HalfPerm) // @ ghost if lastLayerIdx >= 0 && !offsets[lastLayerIdx].isNil { // @ o := offsets[lastLayerIdx] - // @ sl.CombineRange_Bytes(p.rawPkt, o.start, o.end, HalfPerm) + // @ sl.CombineRangeWithViews_Bytes(p.rawPkt, o.start, o.end, HalfPerm, sl.View(p.rawPkt, 0, o.start), sl.View(p.rawPkt[o.start:o.end], 0, (o.end)-(o.start)), sl.View(p.rawPkt, o.end, len(p.rawPkt))) // @ } // @ assert sl.Bytes(p.rawPkt, 0, len(p.rawPkt)) // @ unfold acc(p.d.Mem(), _) @@ -2423,7 +2423,7 @@ func (p *scionPacketProcessor) validateSrcDstIA( /*@ ghost ubScionL []byte, ghos return p.invalidSrcIA( /*@ ubScionL, ubLL, startLL, endLL @*/ ) } if p.path.IsLastHop( /*@ ubPath @*/ ) != dstIsLocal { - // @ ghost sl.CombineRange_Bytes(ubScionL, startP, endP, R50) + // @ ghost sl.CombineRangeWithViews_Bytes(ubScionL, startP, endP, R50, sl.View(ubScionL, 0, startP), sl.View(ubScionL[startP:endP], 0, (endP)-(startP)), sl.View(ubScionL, endP, len(ubScionL))) return p.invalidDstIA( /*@ ubScionL, ubLL, startLL, endLL @*/ ) } // @ ghost if(p.path.IsLastHopSpec(ubPath)) { @@ -2842,17 +2842,17 @@ func (p *scionPacketProcessor) updateNonConsDirIngressSegID( /*@ ghost ub []byte if err := p.path.SetInfoField(p.infoField, int( /*@ unfolding acc(p.path.Mem(ubPath), R45) in (unfolding acc(p.path.Base.Mem(), R50) in @*/ p.path.PathMeta.CurrINF) /*@ ) , ubPath, @*/); err != nil { // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, start, slayers.CmnHdrLen, R54) - // @ ghost sl.CombineRange_Bytes(ub, start, end, writePerm) + // @ ghost sl.CombineRangeWithViews_Bytes(ub, start, end, writePerm, sl.View(ub, 0, start), sl.View(ub[start:end], 0, (end)-(start)), sl.View(ub, end, len(ub))) return serrors.WrapStr("update info field", err) } - // @ ghost sl.CombineRange_Bytes(ub, start, end, HalfPerm) + // @ ghost sl.CombineRangeWithViews_Bytes(ub, start, end, HalfPerm, sl.View(ub, 0, start), sl.View(ub[start:end], 0, (end)-(start)), sl.View(ub, end, len(ub))) // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, start, slayers.CmnHdrLen, R54) // @ assert sl.View(ub, 0, len(ub))[:start] == sl.View(ub, 0, start) // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, sl.View(ub, 0, len(ub)), start) // @ p.SubSliceAbsPktToAbsPkt(ub, start, end) - // @ ghost sl.CombineRange_Bytes(ub, start, end, HalfPerm) + // @ ghost sl.CombineRangeWithViews_Bytes(ub, start, end, HalfPerm, sl.View(ub, 0, start), sl.View(ub[start:end], 0, (end)-(start)), sl.View(ub, end, len(ub))) // @ absPktFutureLemma(sl.View(ub, 0, len(ub))) // @ assert absPkt(sl.View(ub, 0, len(ub))).CurrSeg.UInfo == // @ old(io.upd_uinfo(path.AbsUInfoFromUint16(p.infoField.SegID), p.hopField.Abs())) @@ -3149,14 +3149,14 @@ func (p *scionPacketProcessor) processEgress( /*@ ghost ub []byte @*/ ) (reserr } // @ fold acc(p.scionLayer.Mem(ub), R55) // @ assert reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, startP)) - // @ ghost sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) + // @ ghost sl.CombineRangeWithViews_Bytes(ub, startP, endP, HalfPerm, sl.View(ub, 0, startP), sl.View(ub[startP:endP], 0, (endP)-(startP)), sl.View(ub, endP, len(ub))) // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) // @ assert sl.View(ub, 0, len(ub))[:startP] == sl.View(ub, 0, startP) // @ p.scionLayer.ValidHeaderOffsetFromSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ p.SubSliceAbsPktToAbsPkt(ub, startP, endP) - // @ ghost sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) + // @ ghost sl.CombineRangeWithViews_Bytes(ub, startP, endP, HalfPerm, sl.View(ub, 0, startP), sl.View(ub[startP:endP], 0, (endP)-(startP)), sl.View(ub, endP, len(ub))) // @ absPktFutureLemma(sl.View(ub, 0, len(ub))) // @ assert absPkt(sl.View(ub, 0, len(ub))) == reveal AbsProcessEgress(old(absPkt(sl.View(ub, 0, len(ub))))) // @ fold acc(p.scionLayer.Mem(ub), 1-R55) @@ -3249,7 +3249,7 @@ func (p *scionPacketProcessor) doXover( /*@ ghost ub []byte, ghost currBase scio // @ assert p.path.absPkt(sl.View(ubPath, 0, len(ubPath))) == scion.AbsXover(preAbsPkt) // @ fold acc(p.scionLayer.Mem(ub), R55) // @ assert reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, startP)) - // @ ghost sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) + // @ ghost sl.CombineRangeWithViews_Bytes(ub, startP, endP, HalfPerm, sl.View(ub, 0, startP), sl.View(ub[startP:endP], 0, (endP)-(startP)), sl.View(ub, endP, len(ub))) // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) @@ -3294,7 +3294,7 @@ func (p *scionPacketProcessor) doXover( /*@ ghost ub []byte, ghost currBase scio } // @ assert p.path.GetBase(ubPath) == nextBase // @ p.SubSliceAbsPktToAbsPkt(ub, startP, endP) - // @ ghost sl.CombineRange_Bytes(ub, startP, endP, HalfPerm/2) + // @ ghost sl.CombineRangeWithViews_Bytes(ub, startP, endP, HalfPerm/2, sl.View(ub, 0, startP), sl.View(ub[startP:endP], 0, (endP)-(startP)), sl.View(ub, endP, len(ub))) // @ absPktFutureLemma(sl.View(ub, 0, len(ub))) // @ p.path.DecodingLemma(ubPath, p.infoField, p.hopField) // @ assert reveal p.path.EqAbsInfoField(p.path.absPkt(sl.View(ubPath, 0, len(ubPath))), p.infoField.ToAbsInfoField()) @@ -3531,12 +3531,12 @@ func (p *scionPacketProcessor) handleIngressRouterAlert( /*@ ghost ub []byte, gh if err := p.path.SetHopField(p.hopField, int( /*@ unfolding acc(p.path.Mem(ubPath), R50) in (unfolding acc(p.path.Base.Mem(), R55) in @*/ p.path.PathMeta.CurrHF /*@ ) @*/) /*@ , ubPath @*/); err != nil { // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) - // @ sl.CombineRange_Bytes(ub, startP, endP, writePerm) + // @ sl.CombineRangeWithViews_Bytes(ub, startP, endP, writePerm, sl.View(ub, 0, startP), sl.View(ub[startP:endP], 0, (endP)-(startP)), sl.View(ub, endP, len(ub))) // @ fold acc(p.scionLayer.Mem(ub), R20) // @ fold p.d.validResult(processResult{}, false) return processResult{}, serrors.WrapStr("update hop field", err) } - // @ sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) + // @ sl.CombineRangeWithViews_Bytes(ub, startP, endP, HalfPerm, sl.View(ub, 0, startP), sl.View(ub[startP:endP], 0, (endP)-(startP)), sl.View(ub, endP, len(ub))) // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) @@ -3650,12 +3650,12 @@ func (p *scionPacketProcessor) handleEgressRouterAlert( /*@ ghost ub []byte, gho if err := p.path.SetHopField(p.hopField, int( /*@ unfolding acc(p.path.Mem(ubPath), R50) in (unfolding acc(p.path.Base.Mem(), R55) in @*/ p.path.PathMeta.CurrHF /*@ ) @*/) /*@ , ubPath @*/); err != nil { // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) - // @ sl.CombineRange_Bytes(ub, startP, endP, writePerm) + // @ sl.CombineRangeWithViews_Bytes(ub, startP, endP, writePerm, sl.View(ub, 0, startP), sl.View(ub[startP:endP], 0, (endP)-(startP)), sl.View(ub, endP, len(ub))) // @ fold acc(p.scionLayer.Mem(ub), R20) // @ fold p.d.validResult(processResult{}, false) return processResult{}, serrors.WrapStr("update hop field", err) } - // @ sl.CombineRange_Bytes(ub, startP, endP, HalfPerm) + // @ sl.CombineRangeWithViews_Bytes(ub, startP, endP, HalfPerm, sl.View(ub, 0, startP), sl.View(ub[startP:endP], 0, (endP)-(startP)), sl.View(ub, endP, len(ub))) // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) @@ -3796,9 +3796,9 @@ func (p *scionPacketProcessor) handleSCMPTraceRouteRequest( } // @ ghost sl.CombineRange_Bytes(scionPld, 4, len(scionPld), R1) // @ ghost if !scionPldIsNil { - // @ sl.CombineRange_Bytes(ubLL, maybeStartPld, maybeEndPld, R1) + // @ sl.CombineRangeWithViews_Bytes(ubLL, maybeStartPld, maybeEndPld, R1, sl.View(ubLL, 0, maybeStartPld), sl.View(ubLL[maybeStartPld:maybeEndPld], 0, (maybeEndPld)-(maybeStartPld)), sl.View(ubLL, maybeEndPld, len(ubLL))) // @ } - // @ sl.CombineRange_Bytes(ubScionL, startLL, endLL, R1) + // @ sl.CombineRangeWithViews_Bytes(ubScionL, startLL, endLL, R1, sl.View(ubScionL, 0, startLL), sl.View(ubScionL[startLL:endLL], 0, (endLL)-(startLL)), sl.View(ubScionL, endLL, len(ubScionL))) tmpRes, tmpErr := p.packSCMP(slayers.SCMPTypeTracerouteReply, 0, &scmpP, (error)(nil) /*@ ,ubScionL, ubLL, startLL, endLL, @*/) // @ ghost if tmpErr != nil && tmpRes.OutPkt != nil { // @ AbsUnsupportedPktIsUnsupportedVal(sl.View(tmpRes.OutPkt, 0, len(tmpRes.OutPkt)), tmpRes.EgressID) diff --git a/verification/utils/slices/slices.gobra b/verification/utils/slices/slices.gobra index eb433e04e..fa84f0b25 100644 --- a/verification/utils/slices/slices.gobra +++ b/verification/utils/slices/slices.gobra @@ -429,24 +429,49 @@ requires acc(Bytes(s[start:end], 0, end-start), p) requires acc(Bytes(s, 0, start), p) requires acc(Bytes(s, end, len(s)), p) ensures acc(Bytes(s, 0, len(s)), p) -ensures View(s, 0, len(s))[:start] == old(View(s, 0, start)) -ensures View(s, 0, len(s))[end:] == old(View(s, end, len(s))) -ensures View(s, 0, len(s))[start:end] == old(View(s[start:end], 0, end-start)) decreases func CombineRange_Bytes(s []byte, start int, end int, p perm) { - vLeft := View(s, 0, start) - vMid := View(s[start:end], 0, end-start) - vRight := View(s, end, len(s)) Unslice_Bytes(s, start, end, p) CombineAtIndex_Bytes(s, start, len(s), end, p) CombineAtIndex_Bytes(s, 0, len(s), start, p) - assert View(s, 0, len(s)) == vLeft ++ (vMid ++ vRight) - assert len(vLeft) == start && len(vMid) == end - start - assert View(s, 0, len(s))[:start] == vLeft - assert View(s, 0, len(s))[start:] == vMid ++ vRight - assert View(s, 0, len(s))[start:][:end - start] == vMid +} + +// CombineRangeWithViews_Bytes behaves like CombineRange_Bytes and +// additionally relates the view of the combined instance with the +// views of the three instances that are combined. The views of the +// pieces are passed as ghost parameters, bound by the preconditions +// in the state before the call: the postconditions then need no old() +// expressions, which do not re-check reliably under all verification +// configurations when the instances they depend on are consumed by +// the call. At call sites, passing View(s, 0, start), +// View(s[start:end], 0, end-start) and View(s, end, len(s)) satisfies +// the bindings trivially. +ghost +requires 0 < p +requires 0 <= start && start <= end && end <= len(s) +requires acc(Bytes(s[start:end], 0, end-start), p) +requires acc(Bytes(s, 0, start), p) +requires acc(Bytes(s, end, len(s)), p) +requires vl == View(s, 0, start) +requires vm == View(s[start:end], 0, end - start) +requires vr == View(s, end, len(s)) +ensures acc(Bytes(s, 0, len(s)), p) +ensures View(s, 0, len(s)) == vl ++ vm ++ vr +ensures View(s, 0, len(s))[:start] == vl +ensures View(s, 0, len(s))[start:end] == vm +ensures View(s, 0, len(s))[end:] == vr +decreases +func CombineRangeWithViews_Bytes(s []byte, start int, end int, p perm, ghost vl seq[byte], ghost vm seq[byte], ghost vr seq[byte]) { + Unslice_Bytes(s, start, end, p) + CombineAtIndex_Bytes(s, start, len(s), end, p) + CombineAtIndex_Bytes(s, 0, len(s), start, p) + assert View(s, 0, len(s)) == vl ++ (vm ++ vr) + assert len(vl) == start && len(vm) == end - start + assert View(s, 0, len(s))[:start] == vl + assert View(s, 0, len(s))[start:] == vm ++ vr + assert View(s, 0, len(s))[start:][:end - start] == vm assert View(s, 0, len(s))[start:end] == View(s, 0, len(s))[start:][:end - start] - assert View(s, 0, len(s))[start:][end - start:] == vRight + assert View(s, 0, len(s))[start:][end - start:] == vr assert View(s, 0, len(s))[end:] == View(s, 0, len(s))[start:][end - start:] } diff --git a/verification/utils/slices/slices_test.gobra b/verification/utils/slices/slices_test.gobra index 7e63a2e29..dd114f362 100644 --- a/verification/utils/slices/slices_test.gobra +++ b/verification/utils/slices/slices_test.gobra @@ -44,6 +44,7 @@ func View_test() { // reslicing preserves the contents of the views SplitRange_Bytes(s, 2, 8, writePerm) assert View(s[2:8], 0, 6) == v[2:8] - CombineRange_Bytes(s, 2, 8, writePerm) + CombineRangeWithViews_Bytes(s, 2, 8, writePerm, + View(s, 0, 2), View(s[2:8], 0, 6), View(s, 8, 10)) assert View(s, 0, 10) == v } \ No newline at end of file From 357e06cc0ac3ba0c0dcc79d3a5565fe4bc3c565d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 10:00:29 +0000 Subject: [PATCH 26/35] Track the serialized meta header through views in ToDecoded and Decoded Raw.ToDecoded captured the serialized meta bytes by unfolding the predicate instances and threaded them through the combines and splits with raw-value asserts, which no longer connect now that MetaHdr.SerializeTo's postcondition speaks about the view of the buffer. The bytes are now captured from that view, and the sliced-view postconditions of the split, reslice and combine lemmas carry them to the instance that Decoded.DecodeFromBytes consumes. Decoded.DecodeFromBytes's postcondition characterizes the meta header through GetByte in the final state, while Base.DecodeFromBytes provides the fact about the view at the beginning; the retained fraction of Bytes(data) pins the view across the decode loops, and a final ViewElems call bridges the view elements to the GetByte terms. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/scion/decoded.go | 11 ++++++++ pkg/slayers/path/scion/raw.go | 46 +++++++++++++------------------ 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/pkg/slayers/path/scion/decoded.go b/pkg/slayers/path/scion/decoded.go index 27eb6e8d3..281d4f428 100644 --- a/pkg/slayers/path/scion/decoded.go +++ b/pkg/slayers/path/scion/decoded.go @@ -119,6 +119,17 @@ func (s *Decoded) DecodeFromBytes(data []byte) (r error) { offset += path.HopLen } //@ sl.CombineAtIndex_Bytes(data, 0, len(data), offset, R43) + // the retained fraction of Bytes(data) pins its view, so the view + // here is the one Base.DecodeFromBytes's postcondition speaks + // about; ViewElems bridges its elements to the GetByte terms of + // the postcondition + //@ sl.ViewElems(data, 0, len(data), R43) + //@ ghost vD := sl.View(data, 0, len(data)) + //@ assert vD[0] == sl.GetByte(data, 0, len(data), 0) + //@ assert vD[1] == sl.GetByte(data, 0, len(data), 1) + //@ assert vD[2] == sl.GetByte(data, 0, len(data), 2) + //@ assert vD[3] == sl.GetByte(data, 0, len(data), 3) + //@ assert s.Base.DecodeFromBytesSpec(vD) //@ fold s.Mem(data) return nil } diff --git a/pkg/slayers/path/scion/raw.go b/pkg/slayers/path/scion/raw.go index 16bb2228d..22e6534e8 100644 --- a/pkg/slayers/path/scion/raw.go +++ b/pkg/slayers/path/scion/raw.go @@ -161,41 +161,33 @@ func (s *Raw) ToDecoded( /*@ ghost ubuf []byte @*/ ) (d *Decoded, err error) { // @ Unreachable() return nil, err } - //@ ghost b0 := (unfolding acc(sl.Bytes(s.Raw[:MetaLen], 0, MetaLen), _) in s.Raw[0]) - //@ ghost b1 := (unfolding acc(sl.Bytes(s.Raw[:MetaLen], 0, MetaLen), _) in s.Raw[1]) - //@ ghost b2 := (unfolding acc(sl.Bytes(s.Raw[:MetaLen], 0, MetaLen), _) in s.Raw[2]) - //@ ghost b3 := (unfolding acc(sl.Bytes(s.Raw[:MetaLen], 0, MetaLen), _) in s.Raw[3]) + // The postcondition of SerializeTo speaks about the view of the + // serialized buffer, so the serialized bytes are captured from that + // view; the split and combine lemmas below relate it to the views + // of the other instances, with no reasoning about raw byte values. + //@ ghost vMeta := sl.View(s.Raw[:MetaLen], 0, MetaLen) + //@ ghost b0 := vMeta[0] + //@ ghost b1 := vMeta[1] + //@ ghost b2 := vMeta[2] + //@ ghost b3 := vMeta[3] //@ assert let line := s.PathMeta.SerializedToLine() in binary.BigEndian.PutUint32Spec(b0, b1, b2, b3, line) - //@ sl.CombineRange_Bytes(ubuf, 0, MetaLen, HalfPerm) - //@ assert &ubuf[0] == &s.Raw[:MetaLen][0] - //@ assert &ubuf[1] == &s.Raw[:MetaLen][1] - //@ assert &ubuf[2] == &s.Raw[:MetaLen][2] - //@ assert &ubuf[3] == &s.Raw[:MetaLen][3] - //@ assert b0 == (unfolding acc(sl.Bytes(s.Raw[:MetaLen], 0, MetaLen), _) in ubuf[0]) - // (VerifiedSCION): for some reason, silicon requires the following line to be able to prove - // bX == ubuf[X]. - //@ assert unfolding acc(sl.Bytes(s.Raw[:MetaLen], 0, MetaLen), _) in - //@ (ubuf[0] == (unfolding acc(sl.Bytes(ubuf, 0, len(ubuf)), _) in ubuf[0])) - //@ sl.CombineRange_Bytes(ubuf, 0, MetaLen, HalfPerm) + //@ assert s.Raw[:MetaLen] === ubuf[:MetaLen] + //@ sl.CombineRangeWithViews_Bytes(ubuf, 0, MetaLen, HalfPerm, sl.View(ubuf, 0, 0), vMeta, sl.View(ubuf, MetaLen, len(ubuf))) + //@ assert sl.View(ubuf, 0, len(ubuf))[0:MetaLen] == vMeta + //@ sl.CombineRangeWithViews_Bytes(ubuf, 0, MetaLen, HalfPerm, sl.View(ubuf, 0, 0), vMeta, sl.View(ubuf, MetaLen, len(ubuf))) + //@ assert sl.View(ubuf, 0, len(ubuf))[0:MetaLen] == vMeta decoded := &Decoded{} //@ fold decoded.Base.NonInitMem() //@ fold decoded.NonInitMem() //@ sl.SplitByIndex_Bytes(ubuf, 0, len(ubuf), len(s.Raw), HalfPerm) - //@ assert unfolding acc(sl.Bytes(ubuf, 0, len(ubuf)), _) in - //@ (ubuf[0] == (unfolding acc(sl.Bytes(ubuf, 0, len(s.Raw)), _) in ubuf[0])) + //@ assert sl.View(ubuf, 0, len(s.Raw))[0:MetaLen] == vMeta //@ sl.SplitByIndex_Bytes(ubuf, 0, len(ubuf), len(s.Raw), HalfPerm) //@ sl.Reslice_Bytes(ubuf, 0, len(s.Raw), HalfPerm) - //@ assert &ubuf[0] == &ubuf[:len(s.Raw)][0] - //@ assert &ubuf[1] == &ubuf[:len(s.Raw)][1] - //@ assert &ubuf[2] == &ubuf[:len(s.Raw)][2] - //@ assert &ubuf[3] == &ubuf[:len(s.Raw)][3] - //@ assert unfolding acc(sl.Bytes(ubuf[:len(s.Raw)], 0, len(s.Raw)), _) in - //@ (ubuf[0] == (unfolding acc(sl.Bytes(ubuf, 0, len(s.Raw)), _) in ubuf[0])) - //@ assert b0 == (unfolding acc(sl.Bytes(ubuf, 0, len(s.Raw)), _) in ubuf[0]) - //@ assert b1 == (unfolding acc(sl.Bytes(ubuf, 0, len(s.Raw)), _) in ubuf[1]) - //@ assert b2 == (unfolding acc(sl.Bytes(ubuf, 0, len(s.Raw)), _) in ubuf[2]) - //@ assert b3 == (unfolding acc(sl.Bytes(ubuf, 0, len(s.Raw)), _) in ubuf[3]) + //@ assert sl.View(ubuf[:len(s.Raw)], 0, len(s.Raw))[0:MetaLen] == vMeta //@ sl.Reslice_Bytes(ubuf, 0, len(s.Raw), HalfPerm) + //@ assert s.Raw === ubuf[:len(s.Raw)] + //@ assert sl.View(s.Raw, 0, len(s.Raw))[0] == b0 && sl.View(s.Raw, 0, len(s.Raw))[1] == b1 + //@ assert sl.View(s.Raw, 0, len(s.Raw))[2] == b2 && sl.View(s.Raw, 0, len(s.Raw))[3] == b3 if err := decoded.DecodeFromBytes(s.Raw); err != nil { //@ sl.Unslice_Bytes(ubuf, 0, len(s.Raw), writePerm) //@ sl.CombineAtIndex_Bytes(ubuf, 0, len(ubuf), len(s.Raw), writePerm) From 1b5edf49011cf6fd1881532c8cadc9aff402b82a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 12:09:43 +0000 Subject: [PATCH 27/35] Carry the common-header prefix across path modifications explicitly With the contract-level import failure of the combine lemma gone, the IsSupportedPkt preservation postconditions of the five packet processing functions still failed: IsSupportedPktSubslice reduces IsSupportedPkt to the first CmnHdrLen bytes of the view on each side of the path modification, but nothing related the two prefixes. The view of the buffer up to the path offset is pinned across the modification, since a fraction of that instance is retained throughout; the added asserts capture it and equate the common-header prefixes of the packet view before and after, which closes the chain by congruence. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- router/dataplane.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/router/dataplane.go b/router/dataplane.go index 9f8ae9f7f..18617b92d 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -2835,6 +2835,12 @@ func (p *scionPacketProcessor) updateNonConsDirIngressSegID( /*@ ghost ub []byte // @ sl.SplitByIndex_Bytes(ub, 0, start, slayers.CmnHdrLen, R54) // @ sl.Reslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) + // the view of the left piece is pinned across the modification of + // the path slice; the prefix asserts carry the common-header bytes, + // and with them IsSupportedPkt, from the state before to the state after + // @ ghost vHdrPre := sl.View(ub, 0, start) + // @ assert sl.View(ub, 0, len(ub))[:start] == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:slayers.CmnHdrLen] == vHdrPre[:slayers.CmnHdrLen] // @ p.AbsPktToSubSliceAbsPkt(ub, start, end) // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), start) // @ assert sl.View(ub, 0, len(ub))[:start] == sl.View(ub, 0, start) @@ -2846,6 +2852,9 @@ func (p *scionPacketProcessor) updateNonConsDirIngressSegID( /*@ ghost ub []byte return serrors.WrapStr("update info field", err) } // @ ghost sl.CombineRangeWithViews_Bytes(ub, start, end, HalfPerm, sl.View(ub, 0, start), sl.View(ub[start:end], 0, (end)-(start)), sl.View(ub, end, len(ub))) + // @ assert sl.View(ub, 0, start) == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:start] == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:slayers.CmnHdrLen] == vHdrPre[:slayers.CmnHdrLen] // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, start, slayers.CmnHdrLen, R54) @@ -3110,6 +3119,12 @@ func (p *scionPacketProcessor) processEgress( /*@ ghost ub []byte @*/ ) (reserr // @ sl.SplitByIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) // @ sl.Reslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) + // the view of the left piece is pinned across the modification of + // the path slice; the prefix asserts carry the common-header bytes, + // and with them IsSupportedPkt, from the state before to the state after + // @ ghost vHdrPre := sl.View(ub, 0, startP) + // @ assert sl.View(ub, 0, len(ub))[:startP] == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:slayers.CmnHdrLen] == vHdrPre[:slayers.CmnHdrLen] // @ p.AbsPktToSubSliceAbsPkt(ub, startP, endP) // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ assert sl.View(ub, 0, len(ub))[:startP] == sl.View(ub, 0, startP) @@ -3150,6 +3165,9 @@ func (p *scionPacketProcessor) processEgress( /*@ ghost ub []byte @*/ ) (reserr // @ fold acc(p.scionLayer.Mem(ub), R55) // @ assert reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, startP)) // @ ghost sl.CombineRangeWithViews_Bytes(ub, startP, endP, HalfPerm, sl.View(ub, 0, startP), sl.View(ub[startP:endP], 0, (endP)-(startP)), sl.View(ub, endP, len(ub))) + // @ assert sl.View(ub, 0, startP) == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:startP] == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:slayers.CmnHdrLen] == vHdrPre[:slayers.CmnHdrLen] // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) @@ -3220,6 +3238,12 @@ func (p *scionPacketProcessor) doXover( /*@ ghost ub []byte, ghost currBase scio // @ sl.SplitByIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) // @ sl.Reslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) + // the view of the left piece is pinned across the modification of + // the path slice; the prefix asserts carry the common-header bytes, + // and with them IsSupportedPkt, from the state before to the state after + // @ ghost vHdrPre := sl.View(ub, 0, startP) + // @ assert sl.View(ub, 0, len(ub))[:startP] == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:slayers.CmnHdrLen] == vHdrPre[:slayers.CmnHdrLen] // @ assert p.path == p.scionLayer.GetPath(ub) // @ p.AbsPktToSubSliceAbsPkt(ub, startP, endP) // @ assert p.path == p.scionLayer.GetPath(ub) @@ -3250,6 +3274,9 @@ func (p *scionPacketProcessor) doXover( /*@ ghost ub []byte, ghost currBase scio // @ fold acc(p.scionLayer.Mem(ub), R55) // @ assert reveal p.scionLayer.ValidHeaderOffset(ub, sl.View(ub, 0, startP)) // @ ghost sl.CombineRangeWithViews_Bytes(ub, startP, endP, HalfPerm, sl.View(ub, 0, startP), sl.View(ub[startP:endP], 0, (endP)-(startP)), sl.View(ub, endP, len(ub))) + // @ assert sl.View(ub, 0, startP) == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:startP] == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:slayers.CmnHdrLen] == vHdrPre[:slayers.CmnHdrLen] // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) @@ -3524,6 +3551,12 @@ func (p *scionPacketProcessor) handleIngressRouterAlert( /*@ ghost ub []byte, gh // @ sl.SplitByIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) // @ sl.Reslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) + // the view of the left piece is pinned across the modification of + // the path slice; the prefix asserts carry the common-header bytes, + // and with them IsSupportedPkt, from the state before to the state after + // @ ghost vHdrPre := sl.View(ub, 0, startP) + // @ assert sl.View(ub, 0, len(ub))[:startP] == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:slayers.CmnHdrLen] == vHdrPre[:slayers.CmnHdrLen] // @ p.AbsPktToSubSliceAbsPkt(ub, startP, endP) // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ assert sl.View(ub, 0, len(ub))[:startP] == sl.View(ub, 0, startP) @@ -3537,6 +3570,9 @@ func (p *scionPacketProcessor) handleIngressRouterAlert( /*@ ghost ub []byte, gh return processResult{}, serrors.WrapStr("update hop field", err) } // @ sl.CombineRangeWithViews_Bytes(ub, startP, endP, HalfPerm, sl.View(ub, 0, startP), sl.View(ub[startP:endP], 0, (endP)-(startP)), sl.View(ub, endP, len(ub))) + // @ assert sl.View(ub, 0, startP) == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:startP] == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:slayers.CmnHdrLen] == vHdrPre[:slayers.CmnHdrLen] // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) @@ -3643,6 +3679,12 @@ func (p *scionPacketProcessor) handleEgressRouterAlert( /*@ ghost ub []byte, gho // @ sl.SplitByIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) // @ sl.Reslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) + // the view of the left piece is pinned across the modification of + // the path slice; the prefix asserts carry the common-header bytes, + // and with them IsSupportedPkt, from the state before to the state after + // @ ghost vHdrPre := sl.View(ub, 0, startP) + // @ assert sl.View(ub, 0, len(ub))[:startP] == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:slayers.CmnHdrLen] == vHdrPre[:slayers.CmnHdrLen] // @ p.AbsPktToSubSliceAbsPkt(ub, startP, endP) // @ p.scionLayer.ValidHeaderOffsetToSubSliceLemma(ub, sl.View(ub, 0, len(ub)), startP) // @ assert sl.View(ub, 0, len(ub))[:startP] == sl.View(ub, 0, startP) @@ -3656,6 +3698,9 @@ func (p *scionPacketProcessor) handleEgressRouterAlert( /*@ ghost ub []byte, gho return processResult{}, serrors.WrapStr("update hop field", err) } // @ sl.CombineRangeWithViews_Bytes(ub, startP, endP, HalfPerm, sl.View(ub, 0, startP), sl.View(ub[startP:endP], 0, (endP)-(startP)), sl.View(ub, endP, len(ub))) + // @ assert sl.View(ub, 0, startP) == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:startP] == vHdrPre + // @ assert sl.View(ub, 0, len(ub))[:slayers.CmnHdrLen] == vHdrPre[:slayers.CmnHdrLen] // @ slayers.IsSupportedPktSubslice(sl.View(ub, 0, len(ub)), slayers.CmnHdrLen) // @ sl.Unslice_Bytes(ub, 0, slayers.CmnHdrLen, R54) // @ sl.CombineAtIndex_Bytes(ub, 0, startP, slayers.CmnHdrLen, R54) From 972929a8c6958399b810d01552940242d4daffff Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 12:11:39 +0000 Subject: [PATCH 28/35] Ground the MAC equality reasoning in HopField.SerializeTo HopField.SerializeTo oscillates around the verification timeout: it diverged, passed after the trigger anchors were added, and diverged again after unrelated contract changes shifted the instantiation behavior. The quantified asserts about the MAC bytes multiply the matched triggers over sequence slicing and concatenation terms, so they are replaced by their six ground instances, and the premise of EqualSeqImplyEqualMac is stated per index instead of with a quantifier; its proof already only uses the ground instances, and its other caller has the ground facts available by instantiation. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/hopfield.go | 9 ++++----- pkg/slayers/path/io_msgterm_spec.gobra | 6 +++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pkg/slayers/path/hopfield.go b/pkg/slayers/path/hopfield.go index 517974afe..3cb587b30 100644 --- a/pkg/slayers/path/hopfield.go +++ b/pkg/slayers/path/hopfield.go @@ -173,11 +173,10 @@ func (h *HopField) SerializeTo(b []byte) (err error) { //@ assert v[6] == mac[0] && v[7] == mac[1] && v[8] == mac[2] //@ assert v[9] == mac[3] && v[10] == mac[4] && v[11] == mac[5] //@ assert v[2] == b2 && v[3] == b3 && v[4] == b4 && v[5] == b5 - //@ assert forall j int :: { v[j] } 6 <= j && j < 6+MacLen ==> v[j] == mac[j-6] - //@ assert forall i int :: { v[6:6+MacLen][i] } 0 <= i && i < MacLen ==> - //@ v[6:6+MacLen][i] == v[6+i] - //@ assert forall i int :: { h.Mac[i] } 0 <= i && i < MacLen ==> - //@ v[6:6+MacLen][i] == h.Mac[i] + //@ assert mac[0] == h.Mac[0] && mac[1] == h.Mac[1] && mac[2] == h.Mac[2] + //@ assert mac[3] == h.Mac[3] && mac[4] == h.Mac[4] && mac[5] == h.Mac[5] + //@ assert v[6:6+MacLen][0] == v[6] && v[6:6+MacLen][1] == v[7] && v[6:6+MacLen][2] == v[8] + //@ assert v[6:6+MacLen][3] == v[9] && v[6:6+MacLen][4] == v[10] && v[6:6+MacLen][5] == v[11] //@ EqualSeqImplyEqualMac(v[6:6+MacLen], h.Mac) //@ assert h.Abs() == BytesToIO_HF(v) //@ fold acc(h.Mem(), R11) diff --git a/pkg/slayers/path/io_msgterm_spec.gobra b/pkg/slayers/path/io_msgterm_spec.gobra index 2acc07949..91acc1705 100644 --- a/pkg/slayers/path/io_msgterm_spec.gobra +++ b/pkg/slayers/path/io_msgterm_spec.gobra @@ -47,7 +47,11 @@ pure func FromSeqToMacArray(mac seq[byte]) (res [MacLen]byte) { ghost requires len(mac1) == MacLen -requires forall i int :: { mac2[i] } 0 <= i && i < MacLen ==> mac1[i] == mac2[i] +// the elementwise premises are stated for each index instead of with a +// quantifier: all callers have the ground facts at hand, and the ground +// form keeps the instantiation surface of the serializer proofs small +requires mac1[0] == mac2[0] && mac1[1] == mac2[1] && mac1[2] == mac2[2] +requires mac1[3] == mac2[3] && mac1[4] == mac2[4] && mac1[5] == mac2[5] ensures AbsMac(FromSeqToMacArray(mac1)) == AbsMac(mac2) decreases func EqualSeqImplyEqualMac(mac1 seq[byte], mac2 [MacLen]byte) { From efd5d743ee8b04262cab06c76e3b2b9ebf14d8d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 12:38:54 +0000 Subject: [PATCH 29/35] Carry the header bytes through updateSCIONLayer's buffer copy The unsupported-packet assert after copying the serialized SCMP packet into rawPkt failed because nothing related the view of rawPkt after the full unfold, copy and refold to the view of the serialization buffer. IsSupportedPkt only depends on bytes 4 and 8, so ground GetByte bridges on both sides of the copy connect the two views. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- router/dataplane.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/router/dataplane.go b/router/dataplane.go index 18617b92d..e8ab03001 100644 --- a/router/dataplane.go +++ b/router/dataplane.go @@ -4483,17 +4483,34 @@ func updateSCIONLayer(rawPkt []byte, s *slayers.SCION, buffer gopacket.Serialize // https://fsnets.slack.com/archives/C8ADBBG0J/p1592805884250700 rawContents := buffer.Bytes() // @ assert !(reveal slayers.IsSupportedPkt(sl.View(rawContents, 0, len(rawContents)))) + // IsSupportedPkt only depends on bytes 4 and 8 of the packet; the + // ground facts below carry them from the serialization buffer's view + // to the view of rawPkt after the copy + // @ sl.ViewElems(rawContents, 0, len(rawContents), R21) + // @ ghost vC := sl.View(rawContents, 0, len(rawContents)) + // @ ghost c4 := sl.GetByte(rawContents, 0, len(rawContents), 4) + // @ ghost c8 := sl.GetByte(rawContents, 0, len(rawContents), 8) + // @ assert vC[4] == c4 && vC[8] == c8 // @ s.ValidSizeOhpUb(rawPkt) // @ assert len(rawContents) <= len(rawPkt) // @ unfold sl.Bytes(rawPkt, 0, len(rawPkt)) // @ unfold acc(sl.Bytes(rawContents, 0, len(rawContents)), R20) + // @ assert rawContents[4] == c4 && rawContents[8] == c8 // (VerifiedSCION) proving that the reslicing operation below is safe // was tricky and required enriching (non-modularly) the invariants of *onehop.Path // and *slayers.SCION. // @ assert forall i int :: { &rawPkt[:len(rawContents)][i] }{ &rawPkt[i] } 0 <= i && i < len(rawContents) ==> // @ &rawPkt[i] == &rawPkt[:len(rawContents)][i] copy(rawPkt[:len(rawContents)], rawContents /*@ , R20 @*/) + // @ assert rawPkt[4] == c4 && rawPkt[8] == c8 // @ fold sl.Bytes(rawPkt, 0, len(rawPkt)) + // @ assert sl.GetByte(rawPkt, 0, len(rawPkt), 4) == c4 + // @ assert sl.GetByte(rawPkt, 0, len(rawPkt), 8) == c8 + // @ sl.ViewElems(rawPkt, 0, len(rawPkt), writePerm) + // @ ghost vP := sl.View(rawPkt, 0, len(rawPkt)) + // @ assert vP[4] == sl.GetByte(rawPkt, 0, len(rawPkt), 4) + // @ assert vP[8] == sl.GetByte(rawPkt, 0, len(rawPkt), 8) + // @ assert vP[4] == vC[4] && vP[8] == vC[8] // @ fold acc(sl.Bytes(rawContents, 0, len(rawContents)), R20) // @ assert !(reveal slayers.IsSupportedPkt(sl.View(rawPkt, 0, len(rawPkt)))) return nil From 2d4e3a533b940e41f3a1fe09736b327bb677e382 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 12:51:48 +0000 Subject: [PATCH 30/35] Bridge the decoded meta header bytes to the view in ToDecoded Decoded.DecodeFromBytes characterizes the decoded meta header through GetByte in its final state, while the asserts before the call relate the view elements to the serialized bytes. The retained fraction of the instance pins the view across the call; the added ViewElems call and ground asserts connect the GetByte terms to it, which lets the serialize-deserialize roundtrip lemma conclude that the decoded meta header equals the original one. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/scion/raw.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/slayers/path/scion/raw.go b/pkg/slayers/path/scion/raw.go index 22e6534e8..2199761bd 100644 --- a/pkg/slayers/path/scion/raw.go +++ b/pkg/slayers/path/scion/raw.go @@ -196,6 +196,19 @@ func (s *Raw) ToDecoded( /*@ ghost ubuf []byte @*/ ) (d *Decoded, err error) { return nil, err } //@ ghost lenR := len(s.Raw) // TODO: move to the top and rewrite body + // the postcondition of decoded.DecodeFromBytes characterizes the + // meta header through GetByte; ViewElems bridges those terms to the + // view elements, which the asserts before the call related to the + // serialized bytes + //@ sl.ViewElems(s.Raw, 0, len(s.Raw), R43) + //@ assert sl.View(s.Raw, 0, len(s.Raw))[0] == sl.GetByte(s.Raw, 0, len(s.Raw), 0) + //@ assert sl.View(s.Raw, 0, len(s.Raw))[1] == sl.GetByte(s.Raw, 0, len(s.Raw), 1) + //@ assert sl.View(s.Raw, 0, len(s.Raw))[2] == sl.GetByte(s.Raw, 0, len(s.Raw), 2) + //@ assert sl.View(s.Raw, 0, len(s.Raw))[3] == sl.GetByte(s.Raw, 0, len(s.Raw), 3) + //@ assert sl.GetByte(s.Raw, 0, len(s.Raw), 0) == b0 + //@ assert sl.GetByte(s.Raw, 0, len(s.Raw), 1) == b1 + //@ assert sl.GetByte(s.Raw, 0, len(s.Raw), 2) == b2 + //@ assert sl.GetByte(s.Raw, 0, len(s.Raw), 3) == b3 //@ ghost if validIdxs { //@ s.PathMeta.SerializeAndDeserializeLemma(b0, b1, b2, b3) //@ assert pathMeta == decoded.GetMetaHdr(s.Raw) From 3fd25ae72b55969238c16d7b099d6bd48676ab77 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 13:30:46 +0000 Subject: [PATCH 31/35] Spell out the proof chain in EstablishBytesStoreCurrSeg The lemma calls in the body establish all ingredients of BytesStoreCurrSeg, but the postcondition check does not find the multi-step chains between them, in particular the composition of the sequence-slicing steps for the future hopfields and the congruence between the two spellings of the suffix of the hopfield bytes. The added asserts state each step explicitly. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- .../path/scion/info_hop_setter_lemmas.gobra | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra b/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra index 013f2ea92..da930e705 100644 --- a/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra +++ b/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra @@ -629,6 +629,22 @@ func EstablishBytesStoreCurrSeg(hopfields seq[byte], currHfIdx int, segLen int, widenHopFields(hopfields, 0, 0, currHfIdx, 0, currHfStart) widenHopFields(hopfields, currHfEnd, 0, segLen - currHfIdx - 1, currHfEnd, segLen * path.HopLen) widenHopFields(hopfields, currHfStart, 0, 1, currHfStart, currHfEnd) + // the asserts below spell out the chain from the facts established by + // the lemma calls above to the conjuncts of BytesStoreCurrSeg + assert currseg.Future == hopFields(hopfields, 0, currHfIdx, segLen) + assert currseg.Future[0] == path.BytesToIO_HF(hopfields[currHfStart:currHfEnd]) + assert currseg.Future == hopFields(hopfields, 0, 0, segLen)[currHfIdx:] + assert currseg.Future[1:] == hopFields(hopfields, 0, 0, segLen)[currHfIdx:][1:] + assert hopFields(hopfields, 0, 0, segLen)[currHfIdx:][1:] == + hopFields(hopfields, 0, 0, segLen)[currHfIdx + 1:] + assert currseg.Future[1:] == hopFields(hopfields, 0, currHfIdx + 1, segLen) + assert hopFields(hopfields, 0, currHfIdx + 1, segLen) == + hopFields(hopfields, currHfEnd, 0, segLen - currHfIdx - 1) + assert hopfields[currHfEnd:] == hopfields[currHfEnd:segLen * path.HopLen] + assert currseg.Future[1:] == hopFields(hopfields[currHfEnd:], 0, 0, segLen - currHfIdx - 1) + assert hopFields(hopfields, 0, 0, currHfIdx) == hopFields(hopfields[:currHfStart], 0, 0, currHfIdx) + assert currseg.Past == segPast(hopFields(hopfields[:currHfStart], 0, 0, currHfIdx)) + assert currseg.History == segHistory(hopFields(hopfields[:currHfStart], 0, 0, currHfIdx)) } // `SplitHopfields` splits the permission to the raw bytes of a segment into the permission From ba2ab4cb2f093cc2f6422cea0f2af228a8dee727 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 14:12:20 +0000 Subject: [PATCH 32/35] Trim the proof chain of EstablishBytesStoreCurrSeg The previous spelled-out chain re-derived the future hopfields in three equivalent forms and invoked an unneeded widening for the current hopfield, which made the member exceed the verification timeout instead of failing. The definition of segment shows the minimal chain: the future hopfields are the suffix of the base-zero hopFields application, so one drop-of-drop step and the existing prefix, alignment and widening lemmas suffice, with the head equality following from the definitional unrolling alone. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- pkg/slayers/path/scion/info_hop_setter_lemmas.gobra | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra b/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra index da930e705..408ca65a4 100644 --- a/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra +++ b/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra @@ -622,27 +622,22 @@ func EstablishBytesStoreCurrSeg(hopfields seq[byte], currHfIdx int, segLen int, currHfStart := currHfIdx * path.HopLen currHfEnd := currHfStart + path.HopLen + ghost hf := hopFields(hopfields, 0, 0, segLen) HopsFromSuffixOfRawMatchSuffixOfHops(hopfields, 0, 0, segLen, currHfIdx) AlignHopsOfRawWithOffsetAndIndex(hopfields, 0, currHfIdx + 1, segLen, currHfIdx + 1) HopsFromPrefixOfRawMatchPrefixOfHops(hopfields, 0, 0, segLen, currHfIdx + 1) HopsFromPrefixOfRawMatchPrefixOfHops(hopfields, 0, 0, segLen, currHfIdx) widenHopFields(hopfields, 0, 0, currHfIdx, 0, currHfStart) widenHopFields(hopfields, currHfEnd, 0, segLen - currHfIdx - 1, currHfEnd, segLen * path.HopLen) - widenHopFields(hopfields, currHfStart, 0, 1, currHfStart, currHfEnd) // the asserts below spell out the chain from the facts established by // the lemma calls above to the conjuncts of BytesStoreCurrSeg + assert currseg.Future == hf[currHfIdx:] assert currseg.Future == hopFields(hopfields, 0, currHfIdx, segLen) assert currseg.Future[0] == path.BytesToIO_HF(hopfields[currHfStart:currHfEnd]) - assert currseg.Future == hopFields(hopfields, 0, 0, segLen)[currHfIdx:] - assert currseg.Future[1:] == hopFields(hopfields, 0, 0, segLen)[currHfIdx:][1:] - assert hopFields(hopfields, 0, 0, segLen)[currHfIdx:][1:] == - hopFields(hopfields, 0, 0, segLen)[currHfIdx + 1:] + assert hf[currHfIdx:][1:] == hf[currHfIdx + 1:] assert currseg.Future[1:] == hopFields(hopfields, 0, currHfIdx + 1, segLen) - assert hopFields(hopfields, 0, currHfIdx + 1, segLen) == - hopFields(hopfields, currHfEnd, 0, segLen - currHfIdx - 1) assert hopfields[currHfEnd:] == hopfields[currHfEnd:segLen * path.HopLen] assert currseg.Future[1:] == hopFields(hopfields[currHfEnd:], 0, 0, segLen - currHfIdx - 1) - assert hopFields(hopfields, 0, 0, currHfIdx) == hopFields(hopfields[:currHfStart], 0, 0, currHfIdx) assert currseg.Past == segPast(hopFields(hopfields[:currHfStart], 0, 0, currHfIdx)) assert currseg.History == segHistory(hopFields(hopfields[:currHfStart], 0, 0, currHfIdx)) } From 9222334dc4236c3e0311f27737194f6db4b2d203 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 14:53:54 +0000 Subject: [PATCH 33/35] Split EstablishBytesStoreCurrSeg's proof across three helper lemmas Both the bare lemma calls and the spelled-out chains made the member exceed the verification timeout: with all the recursive hopFields facts in one context, the proof search grinds. The three independent parts of the chain, the head and the tail of the future hopfields and the past hopfields, are now established by dedicated helper lemmas whose contracts state exactly the subsequence forms BytesStoreCurrSeg mentions, so each proof search stays small and the main lemma only composes their postconditions. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- .../path/scion/info_hop_setter_lemmas.gobra | 65 ++++++++++++++----- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra b/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra index 408ca65a4..6362fdb27 100644 --- a/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra +++ b/pkg/slayers/path/scion/info_hop_setter_lemmas.gobra @@ -622,24 +622,59 @@ func EstablishBytesStoreCurrSeg(hopfields seq[byte], currHfIdx int, segLen int, currHfStart := currHfIdx * path.HopLen currHfEnd := currHfStart + path.HopLen - ghost hf := hopFields(hopfields, 0, 0, segLen) - HopsFromSuffixOfRawMatchSuffixOfHops(hopfields, 0, 0, segLen, currHfIdx) - AlignHopsOfRawWithOffsetAndIndex(hopfields, 0, currHfIdx + 1, segLen, currHfIdx + 1) - HopsFromPrefixOfRawMatchPrefixOfHops(hopfields, 0, 0, segLen, currHfIdx + 1) + establishCurrSegFutureHead(hopfields, currHfIdx, segLen) + establishCurrSegFutureTail(hopfields, currHfIdx, segLen) + establishCurrSegPast(hopfields, currHfIdx, segLen) + assert currseg.Future == hopFields(hopfields, 0, 0, segLen)[currHfIdx:] + assert currseg.Past == segPast(hopFields(hopfields[:currHfStart], 0, 0, currHfIdx)) + assert currseg.History == segHistory(hopFields(hopfields[:currHfStart], 0, 0, currHfIdx)) +} + +// establishCurrSegFutureHead relates the head of the future hopfields +// of the current segment to the serialization of the current hopfield. +ghost +requires 0 <= currHfIdx && currHfIdx < segLen +requires segLen * path.HopLen == len(hopfields) +ensures hopFields(hopfields, 0, 0, segLen)[currHfIdx:] == + hopFields(hopfields, 0, currHfIdx, segLen) +ensures hopFields(hopfields, 0, 0, segLen)[currHfIdx:][0] == + path.BytesToIO_HF(hopfields[currHfIdx * path.HopLen : currHfIdx * path.HopLen + path.HopLen]) +decreases +func establishCurrSegFutureHead(hopfields seq[byte], currHfIdx int, segLen int) { HopsFromPrefixOfRawMatchPrefixOfHops(hopfields, 0, 0, segLen, currHfIdx) - widenHopFields(hopfields, 0, 0, currHfIdx, 0, currHfStart) + assert hopFields(hopfields, 0, currHfIdx, segLen)[0] == + path.BytesToIO_HF(hopfields[currHfIdx * path.HopLen : currHfIdx * path.HopLen + path.HopLen]) +} + +// establishCurrSegFutureTail relates the tail of the future hopfields +// of the current segment to the bytes that follow the current hopfield. +ghost +requires 0 <= currHfIdx && currHfIdx < segLen +requires segLen * path.HopLen == len(hopfields) +ensures hopFields(hopfields, 0, 0, segLen)[currHfIdx:][1:] == + hopFields(hopfields[currHfIdx * path.HopLen + path.HopLen:], 0, 0, segLen - currHfIdx - 1) +decreases +func establishCurrSegFutureTail(hopfields seq[byte], currHfIdx int, segLen int) { + currHfEnd := currHfIdx * path.HopLen + path.HopLen + HopsFromPrefixOfRawMatchPrefixOfHops(hopfields, 0, 0, segLen, currHfIdx + 1) + assert hopFields(hopfields, 0, 0, segLen)[currHfIdx:][1:] == + hopFields(hopfields, 0, 0, segLen)[currHfIdx + 1:] + AlignHopsOfRawWithOffsetAndIndex(hopfields, 0, currHfIdx + 1, segLen, currHfIdx + 1) widenHopFields(hopfields, currHfEnd, 0, segLen - currHfIdx - 1, currHfEnd, segLen * path.HopLen) - // the asserts below spell out the chain from the facts established by - // the lemma calls above to the conjuncts of BytesStoreCurrSeg - assert currseg.Future == hf[currHfIdx:] - assert currseg.Future == hopFields(hopfields, 0, currHfIdx, segLen) - assert currseg.Future[0] == path.BytesToIO_HF(hopfields[currHfStart:currHfEnd]) - assert hf[currHfIdx:][1:] == hf[currHfIdx + 1:] - assert currseg.Future[1:] == hopFields(hopfields, 0, currHfIdx + 1, segLen) assert hopfields[currHfEnd:] == hopfields[currHfEnd:segLen * path.HopLen] - assert currseg.Future[1:] == hopFields(hopfields[currHfEnd:], 0, 0, segLen - currHfIdx - 1) - assert currseg.Past == segPast(hopFields(hopfields[:currHfStart], 0, 0, currHfIdx)) - assert currseg.History == segHistory(hopFields(hopfields[:currHfStart], 0, 0, currHfIdx)) +} + +// establishCurrSegPast relates the past hopfields of the current +// segment to the bytes that precede the current hopfield. +ghost +requires 0 <= currHfIdx && currHfIdx < segLen +requires segLen * path.HopLen == len(hopfields) +ensures hopFields(hopfields, 0, 0, segLen)[:currHfIdx] == + hopFields(hopfields[:currHfIdx * path.HopLen], 0, 0, currHfIdx) +decreases +func establishCurrSegPast(hopfields seq[byte], currHfIdx int, segLen int) { + HopsFromSuffixOfRawMatchSuffixOfHops(hopfields, 0, 0, segLen, currHfIdx) + widenHopFields(hopfields, 0, 0, currHfIdx, 0, currHfIdx * path.HopLen) } // `SplitHopfields` splits the permission to the raw bytes of a segment into the permission From 048db0f8335380160efbe2c217523e772f1b2a8f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 00:19:12 +0000 Subject: [PATCH 34/35] Raise the path/scion verification timeout to 45m as a diagnostic path/scion completed within 30m while it still had the failing BytesStoreCurrSeg postcondition, but now that the postcondition is discharged the package hits the wall with no per-member error. Raising the timeout tells us whether the proofs are sound but slow or whether a member genuinely diverges. The intent is to bring the budget back to 30m once the sequence/slice annotations are streamlined. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e --- .github/workflows/gobra.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gobra.yml b/.github/workflows/gobra.yml index f564ff91d..60af6ae6c 100644 --- a/.github/workflows/gobra.yml +++ b/.github/workflows/gobra.yml @@ -321,7 +321,9 @@ jobs: uses: viperproject/gobra-action@main with: packages: 'pkg/slayers/path/scion' - timeout: 30m + # timeout raised while the seq-based proofs are tuned; the goal is + # to bring it back to 30m once the annotations are streamlined + timeout: 45m headerOnly: ${{ env.headerOnly }} module: ${{ env.module }} includePaths: ${{ env.includePaths }} From 63d3d6681e2bd8a6f975f8a95c1c0ade535d0f63 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 10:08:54 +0000 Subject: [PATCH 35/35] Trigger CI on the raised path/scion timeout The previous commit changed only the workflow file and did not create a workflow run, so the 45m path/scion budget was never exercised. This empty commit forces a run to establish whether path/scion is sound-but-slow within 45m. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01WU4M35n52cXp2gs9RozB8e