fec_test.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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. out, err := dec.Decode()
  87. require.NoError(t, err)
  88. return out
  89. }
  90. // TestExhaustiveSubsets decodes every k-subset of the first k+m symbols for
  91. // small codes, which checks the MDS property directly.
  92. func TestExhaustiveSubsets(t *testing.T) {
  93. r := rand.New(rand.NewPCG(1, 2))
  94. const maxSym = 16
  95. for k := 1; k <= 10; k++ {
  96. for m := 1; m <= 5; m++ {
  97. msgLen := (k-1)*maxSym + 1 + r.IntN(maxSym)
  98. msg := randMsg(r, msgLen)
  99. enc, err := NewEncoder(msg, maxSym)
  100. require.NoError(t, err)
  101. require.Equal(t, k, enc.Layout().K)
  102. n := k + m
  103. for mask := 0; mask < 1<<n; mask++ {
  104. var idx []int
  105. for i := range n {
  106. if mask&(1<<i) != 0 {
  107. idx = append(idx, i)
  108. }
  109. }
  110. if len(idx) != k {
  111. continue
  112. }
  113. require.Equal(t, msg, decodeWith(t, enc, msgLen, maxSym, idx), "k=%d m=%d idx=%v", k, m, idx)
  114. }
  115. }
  116. }
  117. }
  118. // TestRandomHighIndexes draws symbols from the whole index space, the shape
  119. // retransmits produce once fresh repair symbols are in play.
  120. func TestRandomHighIndexes(t *testing.T) {
  121. r := rand.New(rand.NewPCG(3, 4))
  122. for range 2000 {
  123. msgLen := 1 + r.IntN(65535)
  124. msg := randMsg(r, msgLen)
  125. enc, err := NewEncoder(msg, 1176)
  126. require.NoError(t, err)
  127. k := enc.Layout().K
  128. idx := r.Perm(MaxSymbols)[:k]
  129. require.Equal(t, msg, decodeWith(t, enc, msgLen, 1176, idx), "k=%d idx=%v", k, idx)
  130. }
  131. }
  132. func TestDecoderBounds(t *testing.T) {
  133. r := rand.New(rand.NewPCG(5, 6))
  134. msg := randMsg(r, 3000)
  135. enc, err := NewEncoder(msg, 1176)
  136. require.NoError(t, err)
  137. l := enc.Layout()
  138. require.Equal(t, 3, l.K)
  139. require.Equal(t, 1000, l.SymbolSize)
  140. dec, err := NewDecoder(3000, 1176)
  141. require.NoError(t, err)
  142. _, err = dec.Decode()
  143. require.ErrorIs(t, err, ErrNotEnough)
  144. _, err = dec.Add(-1, make([]byte, 1000))
  145. require.ErrorIs(t, err, ErrIndexRange)
  146. _, err = dec.Add(MaxSymbols, make([]byte, 1000))
  147. require.ErrorIs(t, err, ErrIndexRange)
  148. _, err = dec.Add(0, make([]byte, 999))
  149. require.ErrorIs(t, err, ErrSymbolSize)
  150. s5, _ := enc.Symbol(nil, 5)
  151. ready, err := dec.Add(5, s5)
  152. require.NoError(t, err)
  153. require.False(t, ready)
  154. // A duplicate index is ignored, even with different contents.
  155. ready, err = dec.Add(5, make([]byte, 1000))
  156. require.NoError(t, err)
  157. require.False(t, ready)
  158. s0, _ := enc.Symbol(nil, 0)
  159. s9, _ := enc.Symbol(nil, 9)
  160. _, err = dec.Add(0, s0)
  161. require.NoError(t, err)
  162. ready, err = dec.Add(9, s9)
  163. require.NoError(t, err)
  164. require.True(t, ready)
  165. // Symbols beyond K are not stored.
  166. ready, err = dec.Add(1, make([]byte, 1000))
  167. require.NoError(t, err)
  168. require.True(t, ready)
  169. require.Len(t, dec.syms, 3)
  170. out, err := dec.Decode()
  171. require.NoError(t, err)
  172. require.Equal(t, msg, out)
  173. }
  174. func TestCorruptPadding(t *testing.T) {
  175. // 3001 bytes in 3 symbols of 1001 leaves 2 bytes of padding in the last one.
  176. dec, err := NewDecoder(3001, 1176)
  177. require.NoError(t, err)
  178. require.Equal(t, 1001, dec.Layout().SymbolSize)
  179. last := make([]byte, 1001)
  180. last[1000] = 1
  181. for i, s := range [][]byte{make([]byte, 1001), make([]byte, 1001), last} {
  182. _, err = dec.Add(i, s)
  183. require.NoError(t, err)
  184. }
  185. _, err = dec.Decode()
  186. require.ErrorIs(t, err, ErrCorrupt)
  187. }
  188. // TestKnownAnswer pins the encoding. Both ends of a handshake must produce
  189. // identical repair symbols, so any change here is a wire format change.
  190. func TestKnownAnswer(t *testing.T) {
  191. msg := make([]byte, 3000)
  192. for i := range msg {
  193. msg[i] = byte(i*7 + 3)
  194. }
  195. enc, err := NewEncoder(msg, 1176)
  196. require.NoError(t, err)
  197. // Cross-checked against an independent bit-serial GF(2^8) implementation.
  198. want := map[int]string{
  199. 3: "52124f58276ec6338d3b3f4eed273bc7cabfe4f0f467928962b046ad1b348f50",
  200. 4: "a50928b04738981985a7d4a17ffaf8d7e30014ca64fc53c99800ffb61aec4aa7",
  201. 5: "ada4c95a7facbc542aaef584e6f0d081b7b0d0eed69ded03b57d0753a119cfc9",
  202. 255: "839066cdf94f963b223fb1c1c1360f698c14f6349b25653ff5b521791a4021b1",
  203. }
  204. for idx, h := range want {
  205. sym, err := enc.Symbol(nil, idx)
  206. require.NoError(t, err)
  207. sum := sha256.Sum256(sym)
  208. assert.Equal(t, h, hex.EncodeToString(sum[:]), "symbol %d", idx)
  209. }
  210. }
  211. func FuzzRoundTrip(f *testing.F) {
  212. f.Add([]byte("hello"), uint16(2), uint64(0))
  213. f.Add(make([]byte, 9000), uint16(1176), uint64(7))
  214. f.Fuzz(func(t *testing.T, msg []byte, maxSym uint16, seed uint64) {
  215. if len(msg) == 0 || maxSym == 0 {
  216. return
  217. }
  218. enc, err := NewEncoder(msg, int(maxSym))
  219. if err != nil {
  220. return
  221. }
  222. r := rand.New(rand.NewPCG(seed, seed))
  223. idx := r.Perm(MaxSymbols)[:enc.Layout().K]
  224. require.Equal(t, msg, decodeWith(t, enc, len(msg), int(maxSym), idx))
  225. })
  226. }
  227. // FuzzDecoder feeds arbitrary symbols to a decoder; it must never panic and
  228. // must either fail or return a message of the declared length.
  229. func FuzzDecoder(f *testing.F) {
  230. f.Add(uint16(3000), []byte{0, 5, 9, 200}, []byte("some symbol bytes"))
  231. f.Fuzz(func(t *testing.T, msgLen uint16, indexes []byte, fill []byte) {
  232. dec, err := NewDecoder(int(msgLen), 1176)
  233. if err != nil {
  234. return
  235. }
  236. size := dec.Layout().SymbolSize
  237. for i, idx := range indexes {
  238. sym := make([]byte, size)
  239. for j := range sym {
  240. if len(fill) > 0 {
  241. sym[j] = fill[(i+j)%len(fill)]
  242. }
  243. }
  244. if _, err := dec.Add(int(idx), sym); err != nil {
  245. t.Fatal(err)
  246. }
  247. }
  248. out, err := dec.Decode()
  249. if err == nil {
  250. require.Len(t, out, int(msgLen))
  251. }
  252. })
  253. }
  254. // BenchmarkRepair and BenchmarkDecode use a v3-sized msg2 (~9.8KB, k=9) with
  255. // three repair symbols, decoding with three data symbols missing.
  256. func BenchmarkRepair(b *testing.B) {
  257. msg := randMsg(rand.New(rand.NewPCG(7, 8)), 9800)
  258. enc, err := NewEncoder(msg, 1176)
  259. require.NoError(b, err)
  260. buf := make([]byte, 0, 1176)
  261. b.SetBytes(int64(len(msg)))
  262. for b.Loop() {
  263. for idx := 9; idx < 12; idx++ {
  264. buf, _ = enc.Symbol(buf[:0], idx)
  265. }
  266. }
  267. }
  268. func BenchmarkDecode(b *testing.B) {
  269. msg := randMsg(rand.New(rand.NewPCG(7, 8)), 9800)
  270. enc, err := NewEncoder(msg, 1176)
  271. require.NoError(b, err)
  272. idx := []int{0, 1, 2, 3, 5, 6, 9, 10, 11}
  273. syms := make([][]byte, len(idx))
  274. for i, x := range idx {
  275. syms[i], _ = enc.Symbol(nil, x)
  276. }
  277. b.SetBytes(int64(len(msg)))
  278. for b.Loop() {
  279. dec, _ := NewDecoder(len(msg), 1176)
  280. for i, x := range idx {
  281. _, _ = dec.Add(x, syms[i])
  282. }
  283. if _, err := dec.Decode(); err != nil {
  284. b.Fatal(err)
  285. }
  286. }
  287. }