diff --git a/sdk/parts/receive.go b/sdk/parts/receive.go index c77bcfe0..9fac2795 100644 --- a/sdk/parts/receive.go +++ b/sdk/parts/receive.go @@ -239,7 +239,7 @@ func (r *Receiver[M]) arm(id string) error { } // Take takes one chunk of a part from the transfer's opener: chunks of a part arrive in sequence from 0, the last -// marked; the whole part must hash to its digest, else its chunks are dropped and the part is lacked again. Belongs, +// marked, and a chunk 0 again starts the part afresh (its earlier chunks are dropped); the whole part must hash to its digest, else its chunks are dropped and the part is lacked again. Belongs, // when given, refuses a transfer the caller's own shape says is not this call's (another seat's, say). When the last // lacked part arrives the transfer ends: the caller's check, then its store. func (r *Receiver[M]) Take(id, opener, digest string, sequence int, last bool, data []byte, belongs func(Transfer[M]) error) (Received, error) { @@ -264,9 +264,14 @@ func (r *Receiver[M]) Take(id, opener, digest string, sequence int, last bool, d return Received{}, fmt.Errorf("transfer %s names no part %s", id, digest) case slices.Contains(t.Held, digest): return Received{ID: id, Lacks: t.Lacks()}, nil - case sequence != t.Chunks[digest]: + case sequence != t.Chunks[digest] && sequence != 0: return Received{}, fmt.Errorf("part %s of transfer %s waits for chunk %d, not %d", digest, id, t.Chunks[digest], sequence) } + if sequence == 0 && t.Chunks[digest] != 0 { + // A part the holder lacks travels again whole from chunk 0 (design 51 Publishing): its earlier chunks are dropped. + r.dropChunks(t, digest) + t.Chunks[digest] = 0 + } if err := r.Objects.Put(r.chunkName(id, digest, sequence), data); err != nil { return Received{}, fmt.Errorf("the objects did not take the chunk: %w", err) } diff --git a/sdk/parts/receive_test.go b/sdk/parts/receive_test.go index d4ee5dfb..54046826 100644 --- a/sdk/parts/receive_test.go +++ b/sdk/parts/receive_test.go @@ -133,9 +133,14 @@ func aHolderTakesAFileInParts(t *testing.T) { if _, err := pkg.Open("t1", "other", []Want{part}, packageShape{}); err == nil || !strings.Contains(err.Error(), "another caller") { t.Errorf("reopen by another = %v", err) } - if _, err := pkg.Take("t1", "builder", part.Digest, 0, false, blob[:1000], nil); err == nil || !strings.Contains(err.Error(), "waits for chunk 1") { + // 1966/AC203-2: a chunk that is neither the cursor nor 0 is refused, naming the cursor. + if _, err := pkg.Take("t1", "builder", part.Digest, 2, false, blob[:1000], nil); err == nil || !strings.Contains(err.Error(), "waits for chunk 1, not 2") { t.Errorf("a chunk out of sequence = %v", err) } + // 1966/AC203-1: a chunk 0 again (the part lacked travels whole again) drops the earlier chunks and takes it. + if _, err := pkg.Take("t1", "builder", part.Digest, 0, false, blob[:1000], nil); err != nil { + t.Errorf("chunk 0 again = %v", err) + } a, err = pkg.Take("t1", "builder", part.Digest, 1, true, blob[1000:], nil) if err != nil || !a.Ended || len(pkgLog) != 1 || pkgLog[0].outcome != "stored" || string(pkgLog[0].parts[part.Digest]) != string(blob) { t.Fatalf("pkg end = %+v, %v, %+v", a, err, pkgLog)