Skip to content

Commit 4c023f7

Browse files
authored
Fix ReconstructSome crash (#303)
Fixes #302 When selectively reconstructing shards, all data shards must be reconstructed for the parity to be reconstructible. It seems like it would be possible to do all reconstruction in one pass, which would be faster with the generated code. Will explore that separately.
1 parent c6a3b70 commit 4c023f7

File tree

2 files changed

+134
-18
lines changed

2 files changed

+134
-18
lines changed

reedsolomon.go

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,10 +1411,10 @@ func (r *reedSolomon) ReconstructData(shards [][]byte) error {
14111411
// As the reconstructed shard set may contain missing parity shards,
14121412
// calling the Verify function is likely to fail.
14131413
func (r *reedSolomon) ReconstructSome(shards [][]byte, required []bool) error {
1414-
if len(required) == r.totalShards {
1415-
return r.reconstruct(shards, false, required)
1414+
if len(required) != r.dataShards && len(required) != r.totalShards {
1415+
return ErrInvalidInput
14161416
}
1417-
return r.reconstruct(shards, true, required)
1417+
return r.reconstruct(shards, len(required) == r.dataShards, required)
14181418
}
14191419

14201420
// reconstruct will recreate the missing data totalShards, and unless
@@ -1442,14 +1442,20 @@ func (r *reedSolomon) reconstruct(shards [][]byte, dataOnly bool, required []boo
14421442
numberPresent := 0
14431443
dataPresent := 0
14441444
missingRequired := 0
1445+
needAllData := false
14451446
for i := 0; i < r.totalShards; i++ {
14461447
if len(shards[i]) != 0 {
14471448
numberPresent++
14481449
if i < r.dataShards {
14491450
dataPresent++
14501451
}
1451-
} else if required != nil && required[i] {
1452-
missingRequired++
1452+
} else if required != nil {
1453+
if !dataOnly && i >= r.dataShards {
1454+
needAllData = true
1455+
}
1456+
if required[i] {
1457+
missingRequired++
1458+
}
14531459
}
14541460
}
14551461
if numberPresent == r.totalShards || dataOnly && dataPresent == r.dataShards ||
@@ -1526,23 +1532,24 @@ func (r *reedSolomon) reconstruct(shards [][]byte, dataOnly bool, required []boo
15261532
// The input to the coding is all of the shards we actually
15271533
// have, and the output is the missing data shards. The computation
15281534
// is done using the special decode matrix we just built.
1535+
outputCount := 0
15291536
outputs := make([][]byte, r.parityShards)
15301537
matrixRows := make([][]byte, r.parityShards)
1531-
outputCount := 0
1532-
1533-
for iShard := 0; iShard < r.dataShards; iShard++ {
1534-
if len(shards[iShard]) == 0 && (required == nil || required[iShard]) {
1535-
if cap(shards[iShard]) >= shardSize {
1536-
shards[iShard] = shards[iShard][0:shardSize]
1537-
} else {
1538-
shards[iShard] = AllocAligned(1, shardSize)[0]
1538+
if dataPresent != r.dataShards {
1539+
for iShard := 0; iShard < r.dataShards; iShard++ {
1540+
if len(shards[iShard]) == 0 && (needAllData || required == nil || required[iShard]) {
1541+
if cap(shards[iShard]) >= shardSize {
1542+
shards[iShard] = shards[iShard][0:shardSize]
1543+
} else {
1544+
shards[iShard] = AllocAligned(1, shardSize)[0]
1545+
}
1546+
outputs[outputCount] = shards[iShard]
1547+
matrixRows[outputCount] = dataDecodeMatrix[iShard]
1548+
outputCount++
15391549
}
1540-
outputs[outputCount] = shards[iShard]
1541-
matrixRows[outputCount] = dataDecodeMatrix[iShard]
1542-
outputCount++
15431550
}
1551+
r.codeSomeShards(matrixRows, subShards, outputs[:outputCount], shardSize)
15441552
}
1545-
r.codeSomeShards(matrixRows, subShards, outputs[:outputCount], shardSize)
15461553

15471554
if dataOnly {
15481555
// Exit out early if we are only interested in the data shards
@@ -1568,7 +1575,9 @@ func (r *reedSolomon) reconstruct(shards [][]byte, dataOnly bool, required []boo
15681575
outputCount++
15691576
}
15701577
}
1571-
r.codeSomeShards(matrixRows, shards[:r.dataShards], outputs[:outputCount], shardSize)
1578+
if outputCount > 0 {
1579+
r.codeSomeShards(matrixRows, shards[:r.dataShards], outputs[:outputCount], shardSize)
1580+
}
15721581
return nil
15731582
}
15741583

reedsolomon_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2240,3 +2240,110 @@ func TestReentrant(t *testing.T) {
22402240
}
22412241
}
22422242
}
2243+
2244+
func TestRSReconstructSome(t *testing.T) {
2245+
const (
2246+
dataShards = 3
2247+
parityShards = 2
2248+
sectorSize = 1 << 20 // 1 MiB
2249+
)
2250+
stripedSplit := func(data []byte, dataShards [][]byte) {
2251+
const leafSize = 64
2252+
buf := bytes.NewBuffer(data)
2253+
for off := 0; buf.Len() > 0; off += leafSize {
2254+
for _, shard := range dataShards {
2255+
copy(shard[off:], buf.Next(leafSize))
2256+
}
2257+
}
2258+
}
2259+
2260+
// prepare random data
2261+
buf := make([]byte, sectorSize*dataShards)
2262+
rand.Read(buf)
2263+
2264+
// prepare shards
2265+
shards := make([][]byte, dataShards+parityShards)
2266+
for i := range shards {
2267+
shards[i] = make([]byte, sectorSize)
2268+
}
2269+
2270+
for i, o := range testOpts() {
2271+
toFill := []int{0, 1, 2, 3, 4}
2272+
t.Run(fmt.Sprintf("options %d", i), func(t *testing.T) {
2273+
// Shuffle the indices to fill
2274+
rand.Shuffle(len(toFill), func(i, j int) {
2275+
toFill[i], toFill[j] = toFill[j], toFill[i]
2276+
})
2277+
stripedSplit(buf, shards[:dataShards])
2278+
// encode shards
2279+
enc, err := New(dataShards, parityShards, o...)
2280+
if err != nil {
2281+
t.Fatal(err)
2282+
}
2283+
err = enc.Encode(shards)
2284+
if err != nil {
2285+
t.Fatal(err)
2286+
}
2287+
2288+
for _, required := range [][]bool{
2289+
{false, false, false, false, true},
2290+
{false, false, false, true, true},
2291+
{false, false, true, false, true},
2292+
{false, false, true, true, true},
2293+
{false, true, false, false, true},
2294+
{false, true, false, true, true},
2295+
{false, true, true, false, true},
2296+
{false, true, true, true, true},
2297+
{true, false, false, false, true},
2298+
{true, false, false, true, true},
2299+
{true, false, true, false, true},
2300+
{true, false, true, true, true},
2301+
{true, true, false, false, true},
2302+
{true, true, false, true, true},
2303+
{true, true, true, false, true},
2304+
{true, true, true, true, true},
2305+
2306+
{false, false, false, false, false},
2307+
{false, false, false, true, false},
2308+
{false, false, true, false, false},
2309+
{false, false, true, true, false},
2310+
{false, true, false, false, false},
2311+
{false, true, false, true, false},
2312+
{false, true, true, false, false},
2313+
{false, true, true, true, false},
2314+
{true, false, false, false, false},
2315+
{true, false, false, true, false},
2316+
{true, false, true, false, false},
2317+
{true, false, true, true, false},
2318+
{true, true, false, false, false},
2319+
{true, true, false, true, false},
2320+
{true, true, true, false, false},
2321+
{true, true, true, true, false},
2322+
} {
2323+
downloaded := make([][]byte, len(shards))
2324+
downloaded[toFill[0]] = append([]byte{}, shards[toFill[0]]...)
2325+
downloaded[toFill[1]] = append([]byte{}, shards[toFill[1]]...)
2326+
downloaded[toFill[2]] = append([]byte{}, shards[toFill[2]]...)
2327+
err = enc.ReconstructSome(downloaded, required)
2328+
if err != nil {
2329+
t.Fatal(err)
2330+
}
2331+
for i, shard := range downloaded {
2332+
if shard == nil && required[i] {
2333+
t.Errorf("shard idx %d should not be nil", i)
2334+
}
2335+
if !required[i] && shard == nil {
2336+
// Ignore, if it wasn't reconstructed
2337+
continue
2338+
}
2339+
if len(shard) != sectorSize {
2340+
t.Errorf("shard idx %d has wrong size: %d, want %d", i, len(shard), sectorSize)
2341+
}
2342+
if !bytes.Equal(shard, shards[i]) {
2343+
t.Errorf("shard idx %d does not match original", i)
2344+
}
2345+
}
2346+
}
2347+
})
2348+
}
2349+
}

0 commit comments

Comments
 (0)