Skip to content
REFERENCE / ZLIB × LEAN

Streams.lean

Source snapshot · 6f2038807010fa43 · Source at the matching commit ↗

Lean source with line numbers. Follow a line number to locate a statement, or an import to read its dependency.

1import ZlibF.Spec.Public.History
2import ZlibF.Spec.Api.Inflate
3import ZlibF.Spec.PlatformMemory
4
5namespace ZlibF.Spec.Public
6open C ZlibF.Spec
7
8def advance : BPtr → Nat → BPtr
9  | .null, _ => .null
10  | .ref r i, n => .ref r (i + n)
11
12def StreamAccounting (z z' : ZStream) (used produced : Nat) : Prop :=
13  used ≤ z.avail_in.toNat ∧ produced ≤ z.avail_out.toNat ∧
14  z'.avail_in.toNat + used = z.avail_in.toNat ∧
15  z'.avail_out.toNat + produced = z.avail_out.toNat ∧
16  z'.next_in = advance z.next_in used ∧ z'.next_out = advance z.next_out produced ∧
17  z'.state = z.state ∧ z'.zalloc = z.zalloc ∧ z'.zfree = z.zfree ∧
18  z'.opaque = z.opaque ∧ z'.reserved = z.reserved
19
20/-- `int inflate(z_streamp strm, int flush)`.
21The domain is actual init/call history, not an arbitrary hand-built decoder state.
22The selected LP64 platform scope explicitly requires every live represented
23byte object to have at most PTRDIFF_MAX bytes (size < 2^63), including caller
24storage from non-malloc origins. See docs/deviations.md.
25Includes all signed flush arguments, progress, terminal/error modes, output bounds,
26counter timing on late memory failure, caller frame and ownership of a new window.
27The sequence target states format correctness across all input/output splits. -/
28def Goal.inflate : Prop :=
29  ∀ h r i z s flush,
30    StreamReachable .inflate h r → Zlib.Inflate.Spec.Good h r i z s →
31    ZlibF.PlatformMemory.ByteObjectsFit h →
32    Zlib.Inflate.Spec.DefAlloc z → StreamBuffers h r → ActiveHeader h r →
33    ∃ rc after z' s' used out,
34      C.run (Zlib.Inflate.inflate (some r) flush) h = .ok (rc, after) ∧
35      Zlib.Inflate.Spec.Good after r i z' s' ∧ StreamAccounting z z' used out.length ∧
36      Bytes after z.next_out out.length out ∧
37      (rc = Z_OK ∨ rc = Z_STREAM_END ∨ rc = Z_NEED_DICT ∨ rc = Z_STREAM_ERROR ∨
38       rc = Z_DATA_ERROR ∨ rc = Z_MEM_ERROR ∨ rc = Z_BUF_ERROR) ∧
39      (rc = Z_OK → (used ≠ 0 ∨ out ≠ []) ∧ flush ≠ Z_FINISH) ∧
40      (rc = Z_STREAM_END → s'.mode = DONE) ∧
41      (rc = Z_NEED_DICT → s'.mode = DICT) ∧
42      (rc = Z_DATA_ERROR → s'.mode = BAD) ∧
43      (rc = Z_MEM_ERROR → s'.mode = MEM ∧ z'.total_in = z.total_in ∧ z'.total_out = z.total_out) ∧
44      (rc = Z_OK ∨ rc = Z_STREAM_END ∨ rc = Z_DATA_ERROR ∨ rc = Z_BUF_ERROR →
45        z'.total_in = z.total_in + UInt64.ofNat used ∧
46        z'.total_out = z.total_out + UInt64.ofNat out.length) ∧
47      BufferFrame h after
48        (fun b => StreamRoot r b ∨ InflateOwned i s b ∨ s.head = some (.tab b))
49        (⟨z.next_out, out.length⟩ :: HeaderRegions h s.head) ∧
50      LiveChange h after (fun b => InflateOwned i s' b ∧ ¬ h.live b) (fun _ => False)
51
52/-- `int deflate(z_streamp strm, int flush)`.
53Preserve the live stream and its five allocations. Account for exactly the bytes accepted
54and delivered, including progress on a full output buffer and clean parameter refusals.
55The sequence target states valid encoding, flush semantics and completion. -/
56def Goal.deflate : Prop :=
57  ∀ h r i z s flush,
58    StreamReachable .deflate h r → Zlib.Deflate.Spec.DGood h r i z s →
59    StreamBuffers h r → ActiveHeader h r →
60    ∃ rc after z' s' used out,
61      C.run (Zlib.Deflate.deflate (some r) flush) h = .ok (rc, after) ∧
62      Zlib.Deflate.Spec.DGood after r i z' s' ∧ StreamAccounting z z' used out.length ∧
63      Bytes after z.next_out out.length out ∧
64      z'.total_in = z.total_in + UInt64.ofNat used ∧
65      z'.total_out = z.total_out + UInt64.ofNat out.length ∧
66      (rc = Z_OK ∨ rc = Z_STREAM_END ∨ rc = Z_STREAM_ERROR ∨ rc = Z_BUF_ERROR) ∧
67      (rc = Z_STREAM_ERROR ↔
68        flush.toInt < 0 ∨ 5 < flush.toInt ∨ z.next_out = .null ∨
69        (z.next_in = .null ∧ z.avail_in ≠ 0) ∨
70        (s.status = FINISH_STATE ∧ flush ≠ Z_FINISH)) ∧
71      (flush.toInt < 0 ∨ 5 < flush.toInt → rc = Z_STREAM_ERROR ∧ after = h) ∧
72      (rc = Z_STREAM_END → flush = Z_FINISH ∧ s'.status = FINISH_STATE ∧
73        z'.avail_in = 0 ∧ s'.pending = 0 ∧ s'.bi_valid = 0) ∧
74      BufferFrame h after (fun b => StreamRoot r b ∨ DeflateOwned i s b) [⟨z.next_out, out.length⟩] ∧
75      LiveChange h after (fun _ => False) (fun _ => False) ∧ after.nalloc = h.nalloc
76
77def bound64 (n : Nat) : UInt64 := UInt64.ofNat (min n (2 ^ 64 - 1))
78
79def WrapperLength (h : Heap) (s : DeflateState) (n : Nat) : Prop :=
80  (s.wrap = 0 ∧ n = 0) ∨
81  ((s.wrap = 1 ∨ s.wrap = -1) ∧ n = 6 + if s.strstart = 0 then 0 else 4) ∨
82  ((s.wrap = 2 ∨ s.wrap = -2) ∧
83    ((s.gzhead = none ∧ n = 18) ∨ ∃ g gh name comment,
84      s.gzhead = some (.tab g) ∧ h.gzheaders g = some gh ∧
85      (gh.name = .null ∧ name = [] ∨ CString h gh.name name) ∧
86      (gh.comment = .null ∧ comment = [] ∨ CString h gh.comment comment) ∧
87      n = 18 + (if gh.extra = .null then 0 else 2 + gh.extra_len.toNat) +
88        (if gh.name = .null then 0 else name.length + 1) +
89        (if gh.comment = .null then 0 else comment.length + 1) +
90        (if gh.hcrc = 0 then 0 else 2)))
91
92def deflateBoundValue (s : DeflateState) (wrapper n : Nat) : UInt64 :=
93  if s.w_bits = 15 ∧ s.hash_bits = 15 then
94    bound64 (n + n / 4096 + n / 16384 + n / 33554432 + 7 + wrapper)
95  else if s.w_bits ≤ s.hash_bits ∧ s.level ≠ 0 then
96    bound64 (n + n / 8 + n / 256 + n / 512 + 4 + wrapper)
97  else bound64 (n + n / 32 + n / 128 + n / 2048 + 7 + wrapper)
98
99def DeflateBoundContract (program : ZStreamPtr → UInt64 → M UInt64) : Prop :=
100  (∀ h n, C.run (program none n) h = .ok
101    (bound64 (max (n.toNat + n.toNat / 8 + n.toNat / 256 + n.toNat / 512 + 4)
102      (n.toNat + n.toNat / 32 + n.toNat / 128 + n.toNat / 2048 + 7) + 18), h)) ∧
103  (∀ h r i z s wrapper n,
104    Zlib.Deflate.Spec.DGood h r i z s → WrapperLength h s wrapper →
105    C.run (program (some r) n) h = .ok (deflateBoundValue s wrapper n.toNat, h))
106
107/-- `z_size_t deflateBound_z(z_streamp strm, z_size_t sourceLen)`.
108The mathematical bound includes the entire gzip header, with saturation instead of overflow.
109The known extra-length arithmetic issue remains an implementation proof blocker. -/
110def Goal.deflateBound_z : Prop := DeflateBoundContract Zlib.Deflate.deflateBound_z
111
112/-- `uLong deflateBound(z_streamp strm, uLong sourceLen)`.
113The LP64 wrapper has the same numeric range; other ABIs need a conversion contract. -/
114def Goal.deflateBound : Prop := DeflateBoundContract Zlib.Deflate.deflateBound
115
116def PendingBits (h : Heap) (s : DeflateState) (bits : List Bool) : Prop :=
117  ∃ bytes, Bytes h s.pending_out s.pending.toNat bytes ∧
118    bits = bytesToBits bytes ++ ((List.range s.bi_valid.toInt.toNat).map fun j =>
119      decide (s.bi_buf.toNat / 2 ^ j % 2 = 1))
120
121/-- `int deflatePrime(z_streamp strm, int bits, int value)`.
122Append the requested low bits in transmission order. Compaction can occur before a buffer
123error; invalid bit counts are refused before compaction. No allocation occurs. -/
124def Goal.deflatePrime : Prop :=
125  ∀ h r i z s count value old,
126    Zlib.Deflate.Spec.DGood h r i z s → PendingBits h s old →
127    ∃ rc after s', C.run (Zlib.Deflate.deflatePrime (some r) count value) h = .ok (rc, after) ∧
128      Zlib.Deflate.Spec.DGood after r i z s' ∧
129      (count.toInt < 0 ∨ 16 < count.toInt → rc = Z_BUF_ERROR ∧ after = h) ∧
130      (0 ≤ count.toInt → count.toInt ≤ 16 →
131        s'.pending_out = s'.pending_buf ∧
132        if s.pending_buf_size.toNat - s.pending.toNat < (s.bi_valid.toInt.toNat + count.toInt.toNat) / 8 then
133          rc = Z_BUF_ERROR ∧ PendingBits after s' old
134        else rc = Z_OK ∧ PendingBits after s'
135          (old ++ (List.range count.toInt.toNat).map (fun j => decide (value.toUInt32.toNat / 2 ^ j % 2 = 1)))) ∧
136      Frame h after (DeflateOwned i s) ∧ LiveChange h after (fun _ => False) (fun _ => False) ∧
137      after.nalloc = h.nalloc
138
139/-- `int deflateParams(z_streamp strm, int level, int strategy)`.
140May consume input and write output while finishing the old block. A buffer error retains
141the old parameters; successful changes select the canonical tuning row for a changed level. -/
142def Goal.deflateParams : Prop :=
143  ∀ h r i z s level strategy,
144    StreamReachable .deflate h r → Zlib.Deflate.Spec.DGood h r i z s →
145    StreamBuffers h r → ActiveHeader h r →
146    ∃ rc after z' s' used out,
147      C.run (Zlib.Deflate.deflateParams (some r) level strategy) h = .ok (rc, after) ∧
148      Zlib.Deflate.Spec.DGood after r i z' s' ∧ StreamAccounting z z' used out.length ∧
149      Bytes after z.next_out out.length out ∧
150      (rc = Z_OK ∨ rc = Z_BUF_ERROR ∨ rc = Z_STREAM_ERROR) ∧
151      (rc = Z_OK → s'.level = (if level = -1 then 6 else level) ∧ s'.strategy = strategy) ∧
152      (rc ≠ Z_OK → s'.level = s.level ∧ s'.strategy = s.strategy) ∧
153      ((level.toInt < -1 ∨ 9 < level.toInt ∨ strategy.toInt < 0 ∨ 4 < strategy.toInt) →
154        rc = Z_STREAM_ERROR ∧ after = h) ∧
155      BufferFrame h after (fun b => StreamRoot r b ∨ DeflateOwned i s b) [⟨z.next_out, out.length⟩] ∧
156      LiveChange h after (fun _ => False) (fun _ => False) ∧ after.nalloc = h.nalloc
157
158def heldSyncBytes (s : InflateState) : List UInt8 :=
159  (List.range ((s.bits.toNat - s.bits.toNat % 8) / 8)).map fun j =>
160    UInt8.ofNat (s.hold.toNat / 2 ^ (s.bits.toNat % 8 + 8 * j) % 256)
161
162/-- `int inflateSync(z_streamp strm)`.
163Search byte-aligned held bits before new input, retaining the partial marker match.
164On success retain totals and restart at TYPE with validation disabled. -/
165def Goal.inflateSync : Prop :=
166  ∀ h r i z s input,
167    StreamReachable .inflate h r → Zlib.Inflate.Spec.Good h r i z s →
168    Zlib.Inflate.Spec.DefAlloc z → Bytes h z.next_in z.avail_in.toNat input →
169    ∃ rc after z' s' used,
170      C.run (Zlib.Inflate.inflateSync (some r)) h = .ok (rc, after) ∧
171      Zlib.Inflate.Spec.Good after r i z' s' ∧ used ≤ input.length ∧
172      z'.next_in = advance z.next_in used ∧ z'.avail_in.toNat + used = z.avail_in.toNat ∧
173      z'.total_in = z.total_in + UInt64.ofNat used ∧ z'.total_out = z.total_out ∧
174      (if z.avail_in = 0 ∧ s.bits.toNat < 8 then rc = Z_BUF_ERROR ∧ z' = z ∧ s' = s
175       else let start := if s.mode = SYNC then s.have.toNat else
176              (Api.syncSearch 0 (heldSyncBytes s)).1
177            let found := Api.syncSearch start input
178            used = found.2 ∧
179            if found.1 = 4 then rc = Z_OK ∧ s'.mode = TYPE ∧ (s'.wrap &&& 4) = 0
180            else rc = Z_DATA_ERROR ∧ s'.mode = SYNC ∧ s'.have.toNat = found.1) ∧
181      Frame h after (fun b => StreamRoot r b ∨ InflateOwned i s b) ∧
182      LiveChange h after (fun _ => False)
183        (fun b => InflateOwned i s b ∧ ¬ InflateOwned i s' b)
184
185end ZlibF.Spec.Public
ZLIB × LEAN

Global contents

READING NOWStreams.lean