-
Notifications
You must be signed in to change notification settings - Fork 0
/
rs.doc
234 lines (166 loc) · 8.52 KB
/
rs.doc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
Dummies guide to Reed-Solomon coding.
This text tries to explain all you need to know to implement Reed-Solomon
coding for the purpose of adding one or more checksum files to a number of
given files.
1 - What can be done with it ?
Let's say you have a set of N files you want to protect from damage.
You can then create M checksum files.
Suppose one or more of the files go missing. You can take the remaining
files, add checksum files so you have N files again, and then pass all of
those through a routine that restores the missing files.
The checksum files don't depend on each other, so any of them will do.
You can even create more of them afterward.
2 - How does it work, basically ?
You're making checksum files, or restoring missing files, or even a mix of
that. Either way, you have N input files, and M output files.
You only need two routines. One routine calculates a set of 'multipliers'
for each output file there's a list of 'multipliers', one per input file.
The other routine reads in bytes from all the input files. For each output
file, those bytes are 'multiplied' , 'added' together, and written to the
output file. Nothing more to it.
'Adding' and 'subtracting' are both actually a XOR operation.
'Multiplying' is done with a separate function, that takes two bytes, does
some lookups in tables, and returns one result byte. You can look in
appendix A for more details on this, but all you need to know that this way
of multiplying behaves roughly as you would expect.
The routine that calculates the multipliers is a bit more complicated.
3 - How are the multipliers calculated ?
First, we'll define a function F(i,j): F(i,j) = i^(j-1)
That is, i to the power of (j-1), using the special 'multiplication'.
You can easily do this with a 'power' function, which also takes two bytes,
does some lookups and returns another byte.
For calculating the checksum files, the multipliers are very simple.
If you number the original files from 1 to N, and the checksum files from 1
to M, then the multiplier from input file i to output file j is F(i,j)
3.1 - Intermezzo
This means that the first checksum file will just be all input files added
together. F(i,0) = i^0 = 1. So recovering one file with the first checksum
file is pretty easy: take the checksum file, and subtract the remaining
files.
Recovering one file with another checksum file is a bit more complicated,
but should still be easy to understand: If you take the checksum file,
subtract each remaining file multiplied by F(i,j), you'll be left with the
missing file multiplied by its multiplier. So if you divide the whole bunch
by that multiplier, you'll get the missing file.
If you want to recover more than one file, it gets more complicated.
4 - Multipliers for recovering files
If you know a bit of matrix calculus, this is easy. You make a matrix.
Each row corresponds to a file you have, each column corresponds to an
original file. On each row you put a single 1 if it's an original file (on
the original file's column). Otherwise you have a checksum file, and you
put the row F(i,j) (where j is the number of the checksum file).
If you look at the set of original files, and the set of files you have as
vectors, then multiplying the originals by the matrix will get you the files
you have. So if you calculate the inverse of the matrix, using gaussian
elimination, you can multiply that by the files you have, and get the
original files back.
Easy, if you know how gaussian elimination works. If not, read on.
4.1 - What is gaussian elimination ?
For each file you want to recover, you make two arrays. Both arrays are a
series of multipliers. The first array should be used on the original
files, and the second array should be used on the files you have now.
You fill in the first array with F(i,j), where j is the number of one of the
checksum files you have. The second array is filled with zeroes, with a
single 1 on the position the checksum file is.
This means that both arrays would result in creating the checksum file.
Now, we want to transform those arrays so that the first array contains a
single 1, on the position of a file we don't have. We take care that each
transformantion will be on both arrays, so they would still have the same
result.
To transform the arrays, you can do two things:
- For each original file you have, you can put a 0 at its position in the
first array, and subtract the value that was there from the same position
in the second array. In other words, you subtract the file, multiplied by
a factor, from both results.
- You divide both arrays by a certain factor, so you get a 1 at the position
of a missing file. Then you subtract both arrays, multiplied by a factor,
from another array so that you get a 0 at the position of the 1.
After this is done, the first array has a single 1 at the position of a file
we don't have, so the result of the first array would be that original file.
So will the result of the second array. We can then use the second array as
multipliers for the input files.
5 - An example
Suppose you have five values: A, B, C, D, E
You want to add three checksums to this: X, Y, Z
So the equations become:
1: 1*A + 1*B + 1*C + 1*D + 1*E = X
2: 1*A + 2*B + 3*C + 4*D + 5*E = Y
3: 1*A + 4*B + 9*C + 16*D + 25*E = Z
Now, we're missing some values: B, C, D
We make the following equations:
1: 1*A + 1*B + 1*C + 1*D + 1*E = 0*A + 0*E + 1*X + 0*Y + 0*Z
2: 1*A + 2*B + 3*C + 4*D + 5*E = 0*A + 0*E + 0*X + 1*Y + 0*Z
3: 1*A + 4*B + 9*C + 16*D + 25*E = 0*A + 0*E + 0*X + 0*Y + 1*Z
First, we can subtract the A's and E's from both sides:
1: 0*A + 1*B + 1*C + 1*D + 0*E = -1*A + -1*E + 1*X + 0*Y + 0*Z
2: 0*A + 2*B + 3*C + 4*D + 0*E = -1*A + -5*E + 0*X + 1*Y + 0*Z
3: 0*A + 4*B + 9*C + 16*D + 0*E = -1*A +-25*E + 0*X + 0*Y + 1*Z
Then, we divide the first row by 1 (it remains the same)
1: 0*A + 1*B + 1*C + 1*D + 0*E = -1*A + -1*E + 1*X + 0*Y + 0*Z
And we subtract it from the other rows (multiplied by 2 and 4 respectively:
2: 0*A + 0*B + 1*C + 2*D + 0*E = 1*A + -3*E + -2*X + 1*Y + 0*Z
3: 0*A + 0*B + 5*C + 12*D + 0*E = 3*A +-21*E + -4*X + 0*Y + 1*Z
Same goes for the second row (divide by 1, subtract multiplied by 1 and 5):
2: 0*A + 0*B + 1*C + 2*D + 0*E = 1*A + -3*E + -2*X + 1*Y + 0*Z
1: 0*A + 1*B + 0*C + -1*D + 0*E = -2*A + 2*E + 3*X + -1*Y + 0*Z
3: 0*A + 0*B + 0*C + 2*D + 0*E = -2*A + -6*E + 6*X + -5*Y + 1*Z
And for the third row (divide by 2, subtract multiplied by -1 and 2):
3: 0*A + 0*B + 0*C + 1*D + 0*E = -1*A + -3*E + 3*X + -2.5*Y + 0.5*Z
1: 0*A + 1*B + 0*C + 0*D + 0*E = -3*A + -1*E + 6*X + -3.5*Y + 0.5*Z
2: 0*A + 0*B + 1*C + 0*D + 0*E = 3*A + 3*E + -8*X + 6 *Y + -1 *Z
We can simplify this to:
B = -3*A + -1*E + 6*X + -3.5*Y + 0.5*Z
C = 3*A + 3*E + -8*X + 6 *Y + -1 *Z
D = -1*A + -3*E + 3*X + -2.5*Y + 0.5*Z
Appendix A - Galois Fields
All through this document, we were doing calculations on bytes. Because we
don't want roundoff/cutoff errors and whatnot, we need to have a form of
'calculation' that behaves like we would expect, but stays within the 0-255
range. This is called a Galois Field.
Basically, adding and subtracting are both done by XORing two bytes:
a '+' b = a '-' b = a XOR b
Multiplying and dividing are done with tables. We precalculate a table with
exponents, with an accompanying reverse-lookup table:
a '*' b = exp(log(a) + log(b))
a '/' b = exp(log(a) - log(b))
Exponentiating is also easy, using the same tables:
a '^' b = exp(log(a) * b)
The table wraps around after 255 bytes, (note: 255, not 256) and you also
need to have special cases for a = 0 and b = 0, so the routines become:
multiply(a, b) {
if (a == 0) return 0;
if (b == 0) return 0;
i = log[a] + log[b];
if (i > 255) i = i - 255;
return exp[i];
}
divide(a, b) {
if (a == 0) return 0;
if (b == 0) return 0;
i = log[a] - log[b];
if (i < 0) i = i + 255;
return exp[i];
}
power(a, b) {
if (a == 0) return 0;
i = log[a] * b;
while (i > 255) i = i - 255;
return exp[i];
}
Now, all we need is a routine to set up the exp and log tables. This is
rather complicated to explain, so here's the routine that does it:
init() {
b = 1;
for (l = 0; l < 255; l++) {
exp[l] = b;
log[b] = l;
b += b;
if (b > 255) b ^= 285;
}
exp[255] = exp[0];
}
Basically, b is doubled each time, and if it gets too large, it's XORed
with a 'magic' number. The 'magic' number is chosen so that b will have
all possible values. If you really want to know, it's binary 100011101,
which corresponds to x^8 + x^4 + x^3 + x^2 + 1.
Willem.