fec_test.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. package fec
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "math/rand/v2"
  6. "testing"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. )
  10. // slowMul is carry-less multiplication reduced by gfPoly, used to check the tables.
  11. func slowMul(a, b byte) byte {
  12. var p int
  13. x, y := int(a), int(b)
  14. for y > 0 {
  15. if y&1 != 0 {
  16. p ^= x
  17. }
  18. x <<= 1
  19. if x&0x100 != 0 {
  20. x ^= gfPoly
  21. }
  22. y >>= 1
  23. }
  24. return byte(p)
  25. }
  26. func TestGF(t *testing.T) {
  27. for a := range 256 {
  28. for b := range 256 {
  29. require.Equal(t, slowMul(byte(a), byte(b)), gfMul(byte(a), byte(b)), "%d*%d", a, b)
  30. }
  31. if a != 0 {
  32. require.Equal(t, byte(1), gfMul(byte(a), gfInv(byte(a))), "inv(%d)", a)
  33. }
  34. }
  35. src := make([]byte, 256)
  36. for i := range src {
  37. src[i] = byte(i)
  38. }
  39. for c := range 256 {
  40. dst := make([]byte, 256)
  41. mulAdd(dst, src, byte(c))
  42. for i := range src {
  43. require.Equal(t, gfMul(byte(c), src[i]), dst[i])
  44. }
  45. }
  46. }
  47. func TestLayout(t *testing.T) {
  48. for msgLen := 1; msgLen <= 20000; msgLen++ {
  49. l, err := NewLayout(msgLen, 1176)
  50. require.NoError(t, err)
  51. require.Equal(t, (msgLen+1175)/1176, l.K)
  52. require.LessOrEqual(t, l.SymbolSize, 1176)
  53. require.GreaterOrEqual(t, l.K*l.SymbolSize, msgLen)
  54. require.Less(t, l.K*l.SymbolSize-msgLen, l.K, "padding must be less than one byte per symbol")
  55. }
  56. l, err := NewLayout(65535, 1176)
  57. require.NoError(t, err)
  58. assert.Equal(t, 56, l.K)
  59. _, err = NewLayout(0, 1176)
  60. require.ErrorIs(t, err, ErrInvalidLayout)
  61. _, err = NewLayout(100, 0)
  62. require.ErrorIs(t, err, ErrInvalidLayout)
  63. _, err = NewLayout(256, 1)
  64. require.ErrorIs(t, err, ErrInvalidLayout)
  65. _, err = NewLayout(255, 1)
  66. require.NoError(t, err)
  67. }
  68. func randMsg(r *rand.Rand, n int) []byte {
  69. b := make([]byte, n)
  70. for i := range b {
  71. b[i] = byte(r.Uint32())
  72. }
  73. return b
  74. }
  75. func decodeWith(t *testing.T, enc *Encoder, msgLen, maxSym int, indexes []int) []byte {
  76. t.Helper()
  77. dec, err := NewDecoder(msgLen, maxSym)
  78. require.NoError(t, err)
  79. for _, idx := range indexes {
  80. sym, err := enc.Symbol(nil, idx)
  81. require.NoError(t, err)
  82. _, err = dec.Add(idx, sym)
  83. require.NoError(t, err)
  84. }
  85. require.True(t, dec.Ready())
  86. // Decode after a prefix, into capacity holding garbage, which must neither
  87. // disturb the prefix nor leak into the message.
  88. dst := make([]byte, 3, 3+dec.Layout().K*dec.Layout().SymbolSize)
  89. for i := range dst[:cap(dst)] {
  90. dst[:cap(dst)][i] = 0xa5
  91. }
  92. out, err := dec.Decode(dst)
  93. require.NoError(t, err)
  94. require.Equal(t, []byte{0xa5, 0xa5, 0xa5}, out[:3])
  95. require.Equal(t, &dst[:1][0], &out[:1][0], "decoded in place")
  96. return out[3:]
  97. }
  98. // TestExhaustiveSubsets decodes every k-subset of the first k+m symbols for
  99. // small codes, which checks the MDS property directly.
  100. func TestExhaustiveSubsets(t *testing.T) {
  101. r := rand.New(rand.NewPCG(1, 2))
  102. const maxSym = 16
  103. for k := 1; k <= 10; k++ {
  104. for m := 1; m <= 5; m++ {
  105. msgLen := (k-1)*maxSym + 1 + r.IntN(maxSym)
  106. msg := randMsg(r, msgLen)
  107. enc, err := NewEncoder(msg, maxSym)
  108. require.NoError(t, err)
  109. require.Equal(t, k, enc.Layout().K)
  110. n := k + m
  111. for mask := 0; mask < 1<<n; mask++ {
  112. var idx []int
  113. for i := range n {
  114. if mask&(1<<i) != 0 {
  115. idx = append(idx, i)
  116. }
  117. }
  118. if len(idx) != k {
  119. continue
  120. }
  121. require.Equal(t, msg, decodeWith(t, enc, msgLen, maxSym, idx), "k=%d m=%d idx=%v", k, m, idx)
  122. }
  123. }
  124. }
  125. }
  126. // TestRandomHighIndexes draws symbols from the whole index space, the shape
  127. // retransmits produce once fresh repair symbols are in play.
  128. func TestRandomHighIndexes(t *testing.T) {
  129. r := rand.New(rand.NewPCG(3, 4))
  130. for range 2000 {
  131. msgLen := 1 + r.IntN(65535)
  132. msg := randMsg(r, msgLen)
  133. enc, err := NewEncoder(msg, 1176)
  134. require.NoError(t, err)
  135. k := enc.Layout().K
  136. idx := r.Perm(MaxSymbols)[:k]
  137. require.Equal(t, msg, decodeWith(t, enc, msgLen, 1176, idx), "k=%d idx=%v", k, idx)
  138. }
  139. }
  140. func TestDecoderBounds(t *testing.T) {
  141. r := rand.New(rand.NewPCG(5, 6))
  142. msg := randMsg(r, 3000)
  143. enc, err := NewEncoder(msg, 1176)
  144. require.NoError(t, err)
  145. l := enc.Layout()
  146. require.Equal(t, 3, l.K)
  147. require.Equal(t, 1000, l.SymbolSize)
  148. dec, err := NewDecoder(3000, 1176)
  149. require.NoError(t, err)
  150. _, err = dec.Decode(nil)
  151. require.ErrorIs(t, err, ErrNotEnough)
  152. _, err = dec.Add(-1, make([]byte, 1000))
  153. require.ErrorIs(t, err, ErrIndexRange)
  154. _, err = dec.Add(MaxSymbols, make([]byte, 1000))
  155. require.ErrorIs(t, err, ErrIndexRange)
  156. _, err = dec.Add(0, make([]byte, 999))
  157. require.ErrorIs(t, err, ErrSymbolSize)
  158. s5, _ := enc.Symbol(nil, 5)
  159. ready, err := dec.Add(5, s5)
  160. require.NoError(t, err)
  161. require.False(t, ready)
  162. // A duplicate index is ignored, even with different contents.
  163. ready, err = dec.Add(5, make([]byte, 1000))
  164. require.NoError(t, err)
  165. require.False(t, ready)
  166. s0, _ := enc.Symbol(nil, 0)
  167. s9, _ := enc.Symbol(nil, 9)
  168. _, err = dec.Add(0, s0)
  169. require.NoError(t, err)
  170. ready, err = dec.Add(9, s9)
  171. require.NoError(t, err)
  172. require.True(t, ready)
  173. // Symbols beyond K are not stored.
  174. ready, err = dec.Add(1, make([]byte, 1000))
  175. require.NoError(t, err)
  176. require.True(t, ready)
  177. require.Len(t, dec.syms, 3)
  178. out, err := dec.Decode(nil)
  179. require.NoError(t, err)
  180. require.Equal(t, msg, out)
  181. }
  182. func TestCorruptPadding(t *testing.T) {
  183. // 3001 bytes in 3 symbols of 1001 leaves 2 bytes of padding in the last one.
  184. dec, err := NewDecoder(3001, 1176)
  185. require.NoError(t, err)
  186. require.Equal(t, 1001, dec.Layout().SymbolSize)
  187. last := make([]byte, 1001)
  188. last[1000] = 1
  189. for i, s := range [][]byte{make([]byte, 1001), make([]byte, 1001), last} {
  190. _, err = dec.Add(i, s)
  191. require.NoError(t, err)
  192. }
  193. _, err = dec.Decode(nil)
  194. require.ErrorIs(t, err, ErrCorrupt)
  195. }
  196. // TestKnownAnswer pins the encoding. Both ends of a handshake must produce
  197. // identical repair symbols, so any change here is a wire format change.
  198. func TestKnownAnswer(t *testing.T) {
  199. msg := make([]byte, 3000)
  200. for i := range msg {
  201. msg[i] = byte(i*7 + 3)
  202. }
  203. enc, err := NewEncoder(msg, 1176)
  204. require.NoError(t, err)
  205. // Cross-checked against an independent bit-serial GF(2^8) implementation.
  206. want := map[int]string{
  207. 3: "52124f58276ec6338d3b3f4eed273bc7cabfe4f0f467928962b046ad1b348f50",
  208. 4: "a50928b04738981985a7d4a17ffaf8d7e30014ca64fc53c99800ffb61aec4aa7",
  209. 5: "ada4c95a7facbc542aaef584e6f0d081b7b0d0eed69ded03b57d0753a119cfc9",
  210. 255: "839066cdf94f963b223fb1c1c1360f698c14f6349b25653ff5b521791a4021b1",
  211. }
  212. for idx, h := range want {
  213. sym, err := enc.Symbol(nil, idx)
  214. require.NoError(t, err)
  215. sum := sha256.Sum256(sym)
  216. assert.Equal(t, h, hex.EncodeToString(sum[:]), "symbol %d", idx)
  217. }
  218. }
  219. func FuzzRoundTrip(f *testing.F) {
  220. f.Add([]byte("hello"), uint16(2), uint64(0))
  221. f.Add(make([]byte, 9000), uint16(1176), uint64(7))
  222. f.Fuzz(func(t *testing.T, msg []byte, maxSym uint16, seed uint64) {
  223. if len(msg) == 0 || maxSym == 0 {
  224. return
  225. }
  226. enc, err := NewEncoder(msg, int(maxSym))
  227. if err != nil {
  228. return
  229. }
  230. r := rand.New(rand.NewPCG(seed, seed))
  231. idx := r.Perm(MaxSymbols)[:enc.Layout().K]
  232. require.Equal(t, msg, decodeWith(t, enc, len(msg), int(maxSym), idx))
  233. })
  234. }
  235. // FuzzDecoder feeds arbitrary symbols to a decoder; it must never panic and
  236. // must either fail or return a message of the declared length.
  237. func FuzzDecoder(f *testing.F) {
  238. f.Add(uint16(3000), []byte{0, 5, 9, 200}, []byte("some symbol bytes"))
  239. f.Fuzz(func(t *testing.T, msgLen uint16, indexes []byte, fill []byte) {
  240. dec, err := NewDecoder(int(msgLen), 1176)
  241. if err != nil {
  242. return
  243. }
  244. size := dec.Layout().SymbolSize
  245. for i, idx := range indexes {
  246. sym := make([]byte, size)
  247. for j := range sym {
  248. if len(fill) > 0 {
  249. sym[j] = fill[(i+j)%len(fill)]
  250. }
  251. }
  252. if _, err := dec.Add(int(idx), sym); err != nil {
  253. t.Fatal(err)
  254. }
  255. }
  256. out, err := dec.Decode(nil)
  257. if err == nil {
  258. require.Len(t, out, int(msgLen))
  259. }
  260. })
  261. }
  262. // BenchmarkRepair and BenchmarkDecode use a v3-sized msg2 (~9.8KB, k=9) with
  263. // three repair symbols, decoding with three data symbols missing.
  264. func BenchmarkRepair(b *testing.B) {
  265. msg := randMsg(rand.New(rand.NewPCG(7, 8)), 9800)
  266. enc, err := NewEncoder(msg, 1176)
  267. require.NoError(b, err)
  268. buf := make([]byte, 0, 1176)
  269. b.SetBytes(int64(len(msg)))
  270. for b.Loop() {
  271. for idx := 9; idx < 12; idx++ {
  272. buf, _ = enc.Symbol(buf[:0], idx)
  273. }
  274. }
  275. }
  276. func BenchmarkDecode(b *testing.B) {
  277. msg := randMsg(rand.New(rand.NewPCG(7, 8)), 9800)
  278. enc, err := NewEncoder(msg, 1176)
  279. require.NoError(b, err)
  280. idx := []int{0, 1, 2, 3, 5, 6, 9, 10, 11}
  281. syms := make([][]byte, len(idx))
  282. for i, x := range idx {
  283. syms[i], _ = enc.Symbol(nil, x)
  284. }
  285. b.SetBytes(int64(len(msg)))
  286. for b.Loop() {
  287. dec, _ := NewDecoder(len(msg), 1176)
  288. for i, x := range idx {
  289. _, _ = dec.Add(x, syms[i])
  290. }
  291. if _, err := dec.Decode(nil); err != nil {
  292. b.Fatal(err)
  293. }
  294. }
  295. }