Skip to content

Commit d3d4197

Browse files
authored
Add optional pledgeinsize function to transcoding protocol (#239)
1 parent 130a1ff commit d3d4197

File tree

4 files changed

+86
-14
lines changed

4 files changed

+86
-14
lines changed

docs/src/reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Base.position(::NoopStream)
4242
```@docs
4343
TranscodingStreams.Codec
4444
TranscodingStreams.expectedsize
45+
TranscodingStreams.pledgeinsize
4546
TranscodingStreams.minoutsize
4647
TranscodingStreams.initialize
4748
TranscodingStreams.finalize

src/codec.jl

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Transcoding proceeds by calling some functions in a specific way. We call this
1414
1515
There are six functions for a codec to implement:
1616
- `expectedsize`: return the expected size of transcoded data
17+
- `pledgeinsize`: tell the codec the total input size
1718
- `minoutsize`: return the minimum output size of `process`
1819
- `initialize`: initialize the codec
1920
- `finalize`: finalize the codec
@@ -22,7 +23,7 @@ There are six functions for a codec to implement:
2223
2324
These are defined in the `TranscodingStreams` and a new codec type must extend
2425
these methods if necessary. Implementing a `process` method is mandatory but
25-
others are optional. `expectedsize`, `minoutsize`, `initialize`, `finalize`,
26+
others are optional. `expectedsize`, `minoutsize`, `pledgeinsize`, `initialize`, `finalize`,
2627
and `startproc` have a default implementation.
2728
2829
Your codec type is denoted by `C` and its object by `codec`.
@@ -39,6 +40,18 @@ used as a hint to determine the size of a data buffer when `transcode` is
3940
called. A good hint will reduce the number of buffer resizing and hence result
4041
in better performance.
4142
43+
### `pledgeinsize`
44+
45+
The `pledgeinsize(codec::C, insize::Int64, error::Error)::Symbol` method is used
46+
when `transcode` is called to tell the `codec` the total input size.
47+
This is called after `startproc` and before `process`. Some
48+
compressors can add this total input size to a header, making `expectedsize`
49+
accurate during later decompression. By default this just returns `:ok`.
50+
If there is an error, the return code must be `:error` and the `error` argument
51+
must be set to an exception object. Setting an inaccurate `insize` may cause the
52+
codec to error later on while processing data. A negative `insize` means unknown
53+
content size.
54+
4255
### `minoutsize`
4356
4457
The `minoutsize(codec::C, input::Memory)::Int` method takes `codec` and `input`,
@@ -71,10 +84,11 @@ the stream will become the close mode for safety.
7184
### `startproc`
7285
7386
The `startproc(codec::C, mode::Symbol, error::Error)::Symbol` method takes
74-
`codec`, `mode` and `error`, and returns a status code. This is called just
75-
before the stream starts reading or writing data. `mode` is either `:read` or
76-
`:write` and then the stream starts reading or writing, respectively. The
77-
return code must be `:ok` if `codec` is ready to read or write data. Otherwise,
87+
`codec`, `mode`, and `error`, and returns a status code. This resets the state
88+
of the codec and is called before the stream starts processing data.
89+
After a call to `startproc`, `pledgeinsize` can be optionally called.
90+
`mode` is either `:read` or `:write`. The
91+
return code must be `:ok` if `codec` is ready to process data. Otherwise,
7892
it must be `:error` and the `error` argument must be set to an exception object.
7993
8094
### `process`
@@ -112,6 +126,17 @@ function expectedsize(codec::Codec, input::Memory)::Int
112126
return input.size
113127
end
114128

129+
"""
130+
pledgeinsize(codec::Codec, insize::Int64, error::Error)::Symbol
131+
132+
Tell the codec the total input size.
133+
134+
The default method does nothing and returns `:ok`.
135+
"""
136+
function pledgeinsize(codec::Codec, insize::Int64, error::Error)::Symbol
137+
return :ok
138+
end
139+
115140
"""
116141
minoutsize(codec::Codec, input::Memory)::Int
117142

src/transcode.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ function unsafe_transcode!(
147147
if code === :error
148148
@goto error
149149
end
150+
if pledgeinsize(codec, Int64(buffersize(input)), error) === :error
151+
@goto error
152+
end
150153
n = GC.@preserve input minoutsize(codec, buffermem(input))
151154
@label process
152155
makemargin!(output, n)
@@ -168,6 +171,9 @@ function unsafe_transcode!(
168171
if startproc(codec, :write, error) === :error
169172
@goto error
170173
end
174+
if pledgeinsize(codec, Int64(buffersize(input)), error) === :error
175+
@goto error
176+
end
171177
n = GC.@preserve input minoutsize(codec, buffermem(input))
172178
@goto process
173179
end

test/codecdoubleframe.jl

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,19 @@ struct DoubleFrameEncoder <: TranscodingStreams.Codec
2020
opened::Base.RefValue{Bool}
2121
stopped::Base.RefValue{Bool}
2222
got_stop_msg::Base.RefValue{Bool}
23+
pledged_in_size::Base.RefValue{Int64}
24+
in_size_count::Base.RefValue{Int64}
2325
end
2426

25-
DoubleFrameEncoder() = DoubleFrameEncoder(Ref(false), Ref(false), Ref(false))
27+
DoubleFrameEncoder() = DoubleFrameEncoder(Ref(false), Ref(false), Ref(false), Ref(Int64(-1)), Ref(Int64(0)))
2628

2729
function TranscodingStreams.process(
2830
codec :: DoubleFrameEncoder,
2931
input :: TranscodingStreams.Memory,
3032
output :: TranscodingStreams.Memory,
3133
error_ref :: TranscodingStreams.Error,
3234
)
35+
pledged = codec.pledged_in_size[]
3336
if input.size == 0
3437
codec.got_stop_msg[] = true
3538
end
@@ -45,26 +48,59 @@ function TranscodingStreams.process(
4548
return 0, 0, :error
4649
elseif !codec.opened[]
4750
output[1] = UInt8('[')
48-
output[2] = UInt8(' ')
51+
if pledged (0:9)
52+
output[2] = UInt8('0'+pledged)
53+
else
54+
output[2] = UInt8(' ')
55+
end
4956
codec.opened[] = true
5057
return 0, 2, :ok
5158
elseif codec.got_stop_msg[]
59+
# check in_size_count against pledged
60+
if pledged (0:9)
61+
if pledged > codec.in_size_count[]
62+
error_ref[] = ErrorException("pledged in size was too big")
63+
return 0, 0, :error
64+
end
65+
end
5266
output[1] = UInt8(' ')
5367
output[2] = UInt8(']')
5468
codec.stopped[] = true
5569
return 0, 2, :end
5670
else
5771
i = j = 0
72+
# check input.size against pledged
73+
if pledged (0:9)
74+
if input.size > pledged || pledged - input.size < codec.in_size_count[]
75+
error_ref[] = ErrorException("pledged in size was too small")
76+
return 0, 0, :error
77+
end
78+
end
5879
while i + 1 lastindex(input) && j + 2 lastindex(output)
5980
b = input[i+1]
6081
i += 1
6182
output[j+1] = output[j+2] = b
6283
j += 2
6384
end
85+
codec.in_size_count[] += i
6486
return i, j, :ok
6587
end
6688
end
6789

90+
function TranscodingStreams.pledgeinsize(
91+
codec::DoubleFrameEncoder,
92+
insize::Int64,
93+
error::Error,
94+
)::Symbol
95+
if codec.opened[]
96+
error[] = ErrorException("pledgeinsize called after opening")
97+
return :error
98+
else
99+
codec.pledged_in_size[] = insize
100+
return :ok
101+
end
102+
end
103+
68104
function TranscodingStreams.expectedsize(
69105
:: DoubleFrameEncoder,
70106
input :: TranscodingStreams.Memory)
@@ -81,6 +117,8 @@ function TranscodingStreams.startproc(codec::DoubleFrameEncoder, ::Symbol, error
81117
codec.opened[] = false
82118
codec.got_stop_msg[] = false
83119
codec.stopped[] = false
120+
codec.pledged_in_size[] = -1
121+
codec.in_size_count[] = 0
84122
return :ok
85123
end
86124

@@ -149,7 +187,7 @@ function TranscodingStreams.process(
149187
codec.a[] != UInt8('[') && error("expected [")
150188
@label state2
151189
do_read(codec.a) || return (codec.state[]=2; (Δin, Δout, :ok))
152-
codec.a[] != UInt8(' ') && error("expected space")
190+
codec.a[] (UInt8(' '), UInt8('0'):UInt8('9')...) && error("expected space or size")
153191
while true
154192
@label state3
155193
do_read(codec.a) || return (codec.state[]=3; (Δin, Δout, :ok))
@@ -189,12 +227,14 @@ DoubleFrameDecoderStream(stream::IO; kwargs...) = TranscodingStream(DoubleFrameD
189227

190228

191229
@testset "DoubleFrame Codecs" begin
192-
@test transcode(DoubleFrameEncoder, b"") == b"[ ]"
193-
@test transcode(DoubleFrameEncoder, b"a") == b"[ aa ]"
194-
@test transcode(DoubleFrameEncoder, b"ab") == b"[ aabb ]"
195-
@test transcode(DoubleFrameEncoder(), b"") == b"[ ]"
196-
@test transcode(DoubleFrameEncoder(), b"a") == b"[ aa ]"
197-
@test transcode(DoubleFrameEncoder(), b"ab") == b"[ aabb ]"
230+
@test transcode(DoubleFrameEncoder, b"") == b"[0 ]"
231+
@test transcode(DoubleFrameEncoder, b"a") == b"[1aa ]"
232+
@test transcode(DoubleFrameEncoder, b"ab") == b"[2aabb ]"
233+
@test transcode(DoubleFrameEncoder(), b"") == b"[0 ]"
234+
@test transcode(DoubleFrameEncoder(), b"a") == b"[1aa ]"
235+
@test transcode(DoubleFrameEncoder(), b"ab") == b"[2aabb ]"
236+
@test transcode(DoubleFrameEncoder(), ones(UInt8,9)) == [b"[9"; ones(UInt8,18); b" ]";]
237+
@test transcode(DoubleFrameEncoder(), ones(UInt8,10)) == [b"[ "; ones(UInt8,20); b" ]";]
198238

199239
@test_throws Exception transcode(DoubleFrameDecoder, b"")
200240
@test_throws Exception transcode(DoubleFrameDecoder, b" [")

0 commit comments

Comments
 (0)