aboutsummaryrefslogtreecommitdiff
path: root/internal/iolimit/expect_length_reader.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-06-08 09:58:26 +0000
committerGravatar Runxi Yu2026-06-08 09:58:26 +0000
commit71f44dca0b6210fa501e9d8450ee3d7bf5f73347 (patch)
treeaf6af234e9f12d8aa71e59ffb77ae06635df289b /internal/iolimit/expect_length_reader.go
parentREFATOR: object and object/fetch are done (diff)
signatureNo signature
internal/iolimit: Add
Might move this to lgo sometime
Diffstat (limited to 'internal/iolimit/expect_length_reader.go')
-rw-r--r--internal/iolimit/expect_length_reader.go78
1 files changed, 78 insertions, 0 deletions
diff --git a/internal/iolimit/expect_length_reader.go b/internal/iolimit/expect_length_reader.go
new file mode 100644
index 00000000..317f1b16
--- /dev/null
+++ b/internal/iolimit/expect_length_reader.go
@@ -0,0 +1,78 @@
+package iolimit
+
+import (
+ "errors"
+ "io"
+)
+
+// ErrExpectedLengthExceeded reports that a stream
+// produced bytes beyond the expected length.
+var ErrExpectedLengthExceeded = errors.New("iolimit: stream exceeded expected length")
+
+// ExpectLengthReader wraps src and enforces an expected byte length.
+//
+// It returns io.ErrUnexpectedEOF
+// if src ends before expected bytes are read.
+// It returns ErrExpectedLengthExceeded
+// if reads continue beyond the expected boundary
+// and src still produces bytes.
+//
+// This reader does not drain src on close or at the expected boundary.
+// As a result,
+// overlength streams are detected only
+// when a caller reads at or past the boundary.
+func ExpectLengthReader(src io.Reader, expected uint64) io.Reader {
+ return &expectLengthReader{
+ src: src,
+ remaining: expected,
+ }
+}
+
+type expectLengthReader struct {
+ src io.Reader
+ remaining uint64
+}
+
+func (reader *expectLengthReader) Read(dst []byte) (int, error) {
+ if len(dst) == 0 {
+ return 0, nil
+ }
+
+ if reader.remaining == 0 {
+ var probe [1]byte
+
+ n, err := reader.src.Read(probe[:])
+ if n > 0 {
+ return 0, ErrExpectedLengthExceeded
+ }
+
+ if err == nil {
+ return 0, nil
+ }
+
+ return 0, err //nolint:wrapcheck
+ }
+
+ if uint64(len(dst)) > reader.remaining {
+ dst = dst[:int(reader.remaining)]
+ }
+
+ n, err := reader.src.Read(dst)
+ if n > 0 {
+ reader.remaining -= uint64(n)
+ }
+
+ if errors.Is(err, io.EOF) {
+ if reader.remaining > 0 {
+ return n, io.ErrUnexpectedEOF
+ }
+
+ if n > 0 {
+ return n, nil
+ }
+
+ return 0, io.EOF
+ }
+
+ return n, err //nolint:wrapcheck
+}