carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package maintenance
2
3import (
4 "bytes"
5 "context"
6 "crypto/ecdh"
7 "crypto/rand"
8 "os"
9 "strconv"
10 "testing"
11 "time"
12)
13
14func resultPipe(t *testing.T) (*os.File, *os.File) {
15 t.Helper()
16 r, w, err := os.Pipe()
17 if err != nil {
18 t.Fatal(err)
19 }
20 t.Cleanup(func() { _ = r.Close(); _ = w.Close() })
21 return r, w
22}
23func resultContext(t *testing.T) context.Context {
24 t.Helper()
25 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
26 t.Cleanup(cancel)
27 return ctx
28}
29
30func TestCaptureResultCompleteBoundaries(t *testing.T) {
31 for _, n := range []int{0, 1, MaxResultBytes} {
32 t.Run(strconv.Itoa(n), func(t *testing.T) {
33 r, w := resultPipe(t)
34 body := bytes.Repeat([]byte{'x'}, n)
35 done := make(chan error, 1)
36 go func() { _, e := w.Write(body); _ = w.Close(); done <- e }()
37 got, err := CaptureResult(resultContext(t), r)
38 if err != nil || !bytes.Equal(got, body) {
39 t.Fatalf("size%d capture failed: %v", n, err)
40 }
41 if err := <-done; err != nil {
42 t.Fatal(err)
43 }
44 if _, err := r.Stat(); err == nil {
45 t.Fatal("capture retained its owned read pipe")
46 }
47 })
48 }
49}
50
51func TestCaptureResultOverflowWithoutEOF(t *testing.T) {
52 r, w := resultPipe(t)
53 done := make(chan error, 1)
54 go func() { _, err := w.Write(bytes.Repeat([]byte{'x'}, MaxResultBytes+1)); done <- err }()
55 body, err := CaptureResult(resultContext(t), r)
56 if body != nil || err != ErrResultOverflow {
57 t.Fatalf("overflow = %d bytes, %v", len(body), err)
58 }
59 if err := <-done; err != nil {
60 t.Fatal(err)
61 }
62 // The write end is deliberately still open: overflow cannot require EOF.
63 if _, err := w.Stat(); err != nil {
64 t.Fatal("writer closed before overflow observation")
65 }
66}
67
68func TestCaptureResultCancellationDiscardsPartialBytes(t *testing.T) {
69 r, w := resultPipe(t)
70 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
71 defer cancel()
72 type answer struct {
73 body []byte
74 err error
75 }
76 done := make(chan answer, 1)
77 go func() { b, e := CaptureResult(ctx, r); done <- answer{b, e} }()
78 if _, err := w.Write([]byte("synthetic-private-fragment")); err != nil {
79 t.Fatal(err)
80 }
81 cancel()
82 select {
83 case got := <-done:
84 if got.body != nil || got.err != ErrResult {
85 t.Fatal("cancel returned partial bytes or success")
86 }
87 case <-time.After(time.Second):
88 t.Fatal("cancellation did not unblock the actual pipe")
89 }
90 if _, err := r.Stat(); err == nil {
91 t.Fatal("cancel retained read descriptor")
92 }
93 if _, err := w.Stat(); err != nil {
94 t.Fatal("capture claimed to terminate independent writer")
95 }
96}
97
98func TestCaptureResultRequiresEOFBeforeDeadline(t *testing.T) {
99 r, _ := resultPipe(t)
100 ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
101 defer cancel()
102 done := make(chan error, 1)
103 go func() {
104 b, e := CaptureResult(ctx, r)
105 if b != nil {
106 done <- nil
107 return
108 }
109 done <- e
110 }()
111 select {
112 case err := <-done:
113 if err != ErrResult {
114 t.Fatal("open writer accepted without EOF")
115 }
116 case <-time.After(time.Second):
117 t.Fatal("deadline did not close pipe")
118 }
119}
120
121func TestCaptureResultRefusesUnboundedOrNonpipeInput(t *testing.T) {
122 long, cancelLong := context.WithTimeout(context.Background(), MaxExecutionDuration+time.Minute)
123 defer cancelLong()
124 expired, cancelExpired := context.WithCancel(resultContext(t))
125 cancelExpired()
126 for _, ctx := range []context.Context{nil, context.Background(), long, expired} {
127 r, _ := resultPipe(t)
128 if b, e := CaptureResult(ctx, r); b != nil || e != ErrResult {
129 t.Fatal("invalid context accepted")
130 }
131 if _, e := r.Stat(); e == nil {
132 t.Fatal("refusal retained read pipe")
133 }
134 }
135 f, e := os.CreateTemp(t.TempDir(), "regular")
136 if e != nil {
137 t.Fatal(e)
138 }
139 if b, e := CaptureResult(resultContext(t), f); b != nil || e != ErrResult {
140 t.Fatal("regular file accepted as private stdout")
141 }
142 if b, e := CaptureResult(resultContext(t), nil); b != nil || e != ErrResult {
143 t.Fatal("nil pipe accepted")
144 }
145}
146
147func TestResultEnvelopePurposeAndLimits(t *testing.T) {
148 key, err := ecdh.P256().GenerateKey(rand.Reader)
149 if err != nil {
150 t.Fatal(err)
151 }
152 fingerprint, err := RecipientFingerprint(key.PublicKey())
153 if err != nil {
154 t.Fatal(err)
155 }
156 binding := Binding{Version: 1, Direction: DirectionResult, Account: "account", App: "home", Host: "test.example", RequestID: "request", AttemptID: "attempt", Recipient: fingerprint}
157 for _, n := range []int{0, 1, MaxResultBytes} {
158 body := bytes.Repeat([]byte{'r'}, n)
159 sealed, err := SealResult(key.PublicKey(), binding, body)
160 if err != nil {
161 t.Fatal(err)
162 }
163 got, err := OpenResult(key, binding, sealed)
164 if err != nil || !bytes.Equal(body, got) {
165 t.Fatalf("result roundtrip size%d: %v", n, err)
166 }
167 }
168 tooLarge := bytes.Repeat([]byte{'r'}, MaxResultBytes+1)
169 if _, err := SealResult(key.PublicKey(), binding, tooLarge); err != ErrResult {
170 t.Fatal("oversized seal accepted")
171 }
172 // Generic crypto accepts this size. Base64's rounding also fits the result
173 // encoded-length bound, so the separate decoded-length check must reject it.
174 sealed, err := Seal(key.PublicKey(), binding, tooLarge)
175 if err != nil {
176 t.Fatal(err)
177 }
178 if b, err := OpenResult(key, binding, sealed); b != nil || err != ErrResult {
179 t.Fatal("oversized decoded result accepted")
180 }
181 for _, direction := range []Direction{DirectionInput, DirectionEnvironment} {
182 wrong := binding
183 wrong.Direction = direction
184 if _, err := SealResult(key.PublicKey(), wrong, []byte("ok")); err != ErrResult {
185 t.Fatal("wrong seal purpose accepted")
186 }
187 sealed, err := Seal(key.PublicKey(), wrong, []byte("ok"))
188 if err != nil {
189 t.Fatal(err)
190 }
191 if b, err := OpenResult(key, wrong, sealed); b != nil || err != ErrResult {
192 t.Fatal("wrong open purpose accepted")
193 }
194 }
195}
196