Skip to content

Commit b0a265b

Browse files
Jorropohacdias
andauthored
fix(gw): duplicate blocks and range etags (#486)
Co-authored-by: Henrique Dias <[email protected]>
1 parent 341c7da commit b0a265b

File tree

3 files changed

+59
-22
lines changed

3 files changed

+59
-22
lines changed

gateway/gateway.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ const (
174174
)
175175

176176
// DuplicateBlocksPolicy represents the content type parameter 'dups' (IPIP-412)
177-
type DuplicateBlocksPolicy int
177+
type DuplicateBlocksPolicy uint8
178178

179179
const (
180180
DuplicateBlocksUnspecified DuplicateBlocksPolicy = iota // 0 - implicit default
@@ -183,14 +183,16 @@ const (
183183
)
184184

185185
// NewDuplicateBlocksPolicy returns DuplicateBlocksPolicy based on the content type parameter 'dups' (IPIP-412)
186-
func NewDuplicateBlocksPolicy(dupsValue string) DuplicateBlocksPolicy {
186+
func NewDuplicateBlocksPolicy(dupsValue string) (DuplicateBlocksPolicy, error) {
187187
switch dupsValue {
188188
case "y":
189-
return DuplicateBlocksIncluded
189+
return DuplicateBlocksIncluded, nil
190190
case "n":
191-
return DuplicateBlocksExcluded
191+
return DuplicateBlocksExcluded, nil
192+
case "":
193+
return DuplicateBlocksUnspecified, nil
192194
}
193-
return DuplicateBlocksUnspecified
195+
return 0, fmt.Errorf("unsupported application/vnd.ipld.car content type dups parameter: %q", dupsValue)
194196
}
195197

196198
func (d DuplicateBlocksPolicy) Bool() bool {

gateway/handler_car.go

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package gateway
22

33
import (
44
"context"
5+
"encoding/binary"
56
"fmt"
67
"io"
78
"net/http"
@@ -166,20 +167,18 @@ func buildCarParams(r *http.Request, contentTypeParams map[string]string) (CarPa
166167
}
167168

168169
// optional dups from IPIP-412
169-
if dups := NewDuplicateBlocksPolicy(contentTypeParams["dups"]); dups != DuplicateBlocksUnspecified {
170-
switch dups {
171-
case DuplicateBlocksExcluded, DuplicateBlocksIncluded:
172-
params.Duplicates = dups
173-
default:
174-
return CarParams{}, fmt.Errorf("unsupported application/vnd.ipld.car content type dups parameter: %q", dups)
175-
}
176-
} else {
170+
dups, err := NewDuplicateBlocksPolicy(contentTypeParams["dups"])
171+
if err != nil {
172+
return CarParams{}, err
173+
}
174+
if dups == DuplicateBlocksUnspecified {
177175
// when duplicate block preference is not specified, we set it to
178176
// false, as this has always been the default behavior, we should
179177
// not break legacy clients, and responses to requests made via ?format=car
180178
// should benefit from block deduplication
181-
params.Duplicates = DuplicateBlocksExcluded
179+
dups = DuplicateBlocksExcluded
182180
}
181+
params.Duplicates = dups
183182

184183
return params, nil
185184
}
@@ -226,31 +225,43 @@ func getCarRootCidAndLastSegment(imPath path.ImmutablePath) (cid.Cid, string, er
226225
}
227226

228227
func getCarEtag(imPath path.ImmutablePath, params CarParams, rootCid cid.Cid) string {
229-
data := imPath.String()
228+
h := xxhash.New()
229+
h.WriteString(imPath.String())
230+
// be careful with hashes here, we need boundaries and per entry salt, we don't want a request that has:
231+
// - scope = dfs
232+
// and:
233+
// - order = dfs
234+
// to result in the same hash because if we just do hash(scope + order) they would both yield hash("dfs").
230235
if params.Scope != DagScopeAll {
231-
data += string(params.Scope)
236+
h.WriteString("\x00scope=")
237+
h.WriteString(string(params.Scope))
232238
}
233239

234240
// 'order' from IPIP-412 impact Etag only if set to something else
235241
// than DFS (which is the implicit default)
236242
if params.Order != DagOrderDFS {
237-
data += string(params.Order)
243+
h.WriteString("\x00order=")
244+
h.WriteString(string(params.Order))
238245
}
239246

240247
// 'dups' from IPIP-412 impact Etag only if 'y'
241-
if dups := params.Duplicates.String(); dups == "y" {
242-
data += dups
248+
if dups := params.Duplicates; dups == DuplicateBlocksIncluded {
249+
h.WriteString("\x00dups=y")
243250
}
244251

245252
if params.Range != nil {
246253
if params.Range.From != 0 || params.Range.To != nil {
247-
data += strconv.FormatInt(params.Range.From, 10)
254+
h.WriteString("\x00range=")
255+
var b [8]byte
256+
binary.LittleEndian.PutUint64(b[:], uint64(params.Range.From))
257+
h.Write(b[:])
248258
if params.Range.To != nil {
249-
data += strconv.FormatInt(*params.Range.To, 10)
259+
binary.LittleEndian.PutUint64(b[:], uint64(*params.Range.To))
260+
h.Write(b[:])
250261
}
251262
}
252263
}
253264

254-
suffix := strconv.FormatUint(xxhash.Sum64([]byte(data)), 32)
265+
suffix := strconv.FormatUint(h.Sum64(), 32)
255266
return `W/"` + rootCid.String() + ".car." + suffix + `"`
256267
}

gateway/handler_car_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,30 @@ func TestCarParams(t *testing.T) {
110110
require.Equal(t, test.expectedDuplicates.String(), params.Duplicates.String())
111111
}
112112
})
113+
114+
t.Run("buildCarParams from Accept header: order and dups parsing (invalid)", func(t *testing.T) {
115+
t.Parallel()
116+
117+
// below ensure the implicit default (DFS and no duplicates) is correctly inferred
118+
// from the value read from Accept header
119+
tests := []string{
120+
"application/vnd.ipld.car; dups=invalid",
121+
"application/vnd.ipld.car; order=invalid",
122+
"application/vnd.ipld.car; order=dfs; dups=invalid",
123+
"application/vnd.ipld.car; order=invalid; dups=y",
124+
}
125+
for _, test := range tests {
126+
r := mustNewRequest(t, http.MethodGet, "http://example.com/", nil)
127+
r.Header.Set("Accept", test)
128+
129+
mediaType, formatParams, err := customResponseFormat(r)
130+
assert.NoError(t, err)
131+
assert.Equal(t, carResponseFormat, mediaType)
132+
133+
_, err = buildCarParams(r, formatParams)
134+
assert.ErrorContains(t, err, "unsupported application/vnd.ipld.car content type")
135+
}
136+
})
113137
}
114138

115139
func TestContentTypeFromCarParams(t *testing.T) {

0 commit comments

Comments
 (0)