initial commit
This commit is contained in:
238
ecc/BCH15_5.cs
Normal file
238
ecc/BCH15_5.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
|
||||
namespace ThoughtWorks.QRCode.Codec.Ecc
|
||||
{
|
||||
public class BCH15_5
|
||||
{
|
||||
internal int[][] gf16;
|
||||
internal bool[] recieveData;
|
||||
internal int numCorrectedError;
|
||||
|
||||
virtual public int NumCorrectedError
|
||||
{
|
||||
get
|
||||
{
|
||||
return numCorrectedError;
|
||||
}
|
||||
}
|
||||
|
||||
public BCH15_5(bool[] source)
|
||||
{
|
||||
gf16 = createGF16();
|
||||
recieveData = source;
|
||||
}
|
||||
|
||||
public virtual bool[] correct()
|
||||
{
|
||||
int[] s = calcSyndrome(recieveData);
|
||||
|
||||
int[] errorPos = detectErrorBitPosition(s);
|
||||
bool[] output = correctErrorBit(recieveData, errorPos);
|
||||
return output;
|
||||
}
|
||||
|
||||
internal virtual int[][] createGF16()
|
||||
{
|
||||
gf16 = new int[16][];
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
gf16[i] = new int[4];
|
||||
}
|
||||
int[] seed = new int[]{1, 1, 0, 0};
|
||||
for (int i = 0; i < 4; i++)
|
||||
gf16[i][i] = 1;
|
||||
for (int i = 0; i < 4; i++)
|
||||
gf16[4][i] = seed[i];
|
||||
for (int i = 5; i < 16; i++)
|
||||
{
|
||||
for (int j = 1; j < 4; j++)
|
||||
{
|
||||
gf16[i][j] = gf16[i - 1][j - 1];
|
||||
}
|
||||
if (gf16[i - 1][3] == 1)
|
||||
{
|
||||
for (int j = 0; j < 4; j++)
|
||||
gf16[i][j] = (gf16[i][j] + seed[j]) % 2;
|
||||
}
|
||||
}
|
||||
return gf16;
|
||||
}
|
||||
|
||||
internal virtual int searchElement(int[] x)
|
||||
{
|
||||
int k;
|
||||
for (k = 0; k < 15; k++)
|
||||
{
|
||||
if (x[0] == gf16[k][0] && x[1] == gf16[k][1] && x[2] == gf16[k][2] && x[3] == gf16[k][3])
|
||||
break;
|
||||
}
|
||||
return k;
|
||||
}
|
||||
|
||||
internal virtual int[] getCode(int input)
|
||||
{
|
||||
int[] f = new int[15];
|
||||
int[] r = new int[8];
|
||||
|
||||
for (int i = 0; i < 15; i++)
|
||||
{
|
||||
//1 + x + x^3
|
||||
int w1, w2;
|
||||
int yin;
|
||||
|
||||
w1 = r[7];
|
||||
if (i < 7)
|
||||
{
|
||||
yin = (input >> (6 - i)) % 2;
|
||||
w2 = (yin + w1) % 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
yin = w1;
|
||||
w2 = 0;
|
||||
}
|
||||
r[7] = (r[6] + w2) % 2;
|
||||
r[6] = (r[5] + w2) % 2;
|
||||
r[5] = r[4];
|
||||
r[4] = (r[3] + w2) % 2;
|
||||
r[3] = r[2];
|
||||
r[2] = r[1];
|
||||
r[1] = r[0];
|
||||
r[0] = w2;
|
||||
f[14 - i] = yin;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
internal static String[] bitName = new String[]{"c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "d0", "d1", "d2", "d3", "d4"};
|
||||
|
||||
internal virtual int addGF(int arg1, int arg2)
|
||||
{
|
||||
int[] p = new int[4];
|
||||
for (int m = 0; m < 4; m++)
|
||||
{
|
||||
int w1 = (arg1 < 0 || arg1 >= 15)?0:gf16[arg1][m];
|
||||
int w2 = (arg2 < 0 || arg2 >= 15)?0:gf16[arg2][m];
|
||||
p[m] = (w1 + w2) % 2;
|
||||
}
|
||||
return searchElement(p);
|
||||
}
|
||||
|
||||
internal virtual int[] calcSyndrome(bool[] y)
|
||||
{
|
||||
int[] s = new int[5];
|
||||
int[] p = new int[4];
|
||||
int k;
|
||||
for (k = 0; k < 15; k++)
|
||||
{
|
||||
if (y[k] == true)
|
||||
for (int m = 0; m < 4; m++)
|
||||
p[m] = (p[m] + gf16[k][m]) % 2;
|
||||
}
|
||||
k = searchElement(p);
|
||||
s[0] = (k >= 15)?- 1:k;
|
||||
|
||||
p = new int[4];
|
||||
for (k = 0; k < 15; k++)
|
||||
{
|
||||
if (y[k] == true)
|
||||
for (int m = 0; m < 4; m++)
|
||||
p[m] = (p[m] + gf16[(k * 3) % 15][m]) % 2;
|
||||
}
|
||||
|
||||
k = searchElement(p);
|
||||
|
||||
s[2] = (k >= 15)?- 1:k;
|
||||
p = new int[4];
|
||||
for (k = 0; k < 15; k++)
|
||||
{
|
||||
if (y[k] == true)
|
||||
for (int m = 0; m < 4; m++)
|
||||
p[m] = (p[m] + gf16[(k * 5) % 15][m]) % 2;
|
||||
}
|
||||
k = searchElement(p);
|
||||
s[4] = (k >= 15)?- 1:k;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
internal virtual int[] calcErrorPositionVariable(int[] s)
|
||||
{
|
||||
int[] e = new int[4];
|
||||
// calc σ1
|
||||
e[0] = s[0];
|
||||
//Console.out.println("σ1 = " + String.valueOf(e[0]));
|
||||
|
||||
// calc σ2
|
||||
int t = (s[0] + s[1]) % 15;
|
||||
int mother = addGF(s[2], t);
|
||||
mother = (mother >= 15)?- 1:mother;
|
||||
|
||||
t = (s[2] + s[1]) % 15;
|
||||
int child = addGF(s[4], t);
|
||||
child = (child >= 15)?- 1:child;
|
||||
e[1] = (child < 0 && mother < 0)?- 1:(child - mother + 15) % 15;
|
||||
|
||||
// calc σ3
|
||||
t = (s[1] + e[0]) % 15;
|
||||
int t1 = addGF(s[2], t);
|
||||
t = (s[0] + e[1]) % 15;
|
||||
e[2] = addGF(t1, t);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
internal virtual int[] detectErrorBitPosition(int[] s)
|
||||
{
|
||||
int[] e = calcErrorPositionVariable(s);
|
||||
int[] errorPos = new int[4];
|
||||
if (e[0] == - 1)
|
||||
{
|
||||
//Console.out.println("No errors.");
|
||||
return errorPos;
|
||||
}
|
||||
else if (e[1] == - 1)
|
||||
{
|
||||
errorPos[0] = 1;
|
||||
errorPos[1] = e[0];
|
||||
return errorPos;
|
||||
}
|
||||
int x3, x2, x1;
|
||||
int t, t1, t2, anError;
|
||||
//error detection
|
||||
for (int i = 0; i < 15; i++)
|
||||
{
|
||||
x3 = (i * 3) % 15;
|
||||
x2 = (i * 2) % 15;
|
||||
x1 = i;
|
||||
|
||||
//p = new int[4];
|
||||
|
||||
t = (e[0] + x2) % 15;
|
||||
t1 = addGF(x3, t);
|
||||
|
||||
t = (e[1] + x1) % 15;
|
||||
t2 = addGF(t, e[2]);
|
||||
|
||||
anError = addGF(t1, t2);
|
||||
|
||||
if (anError >= 15)
|
||||
{
|
||||
errorPos[0]++;
|
||||
errorPos[errorPos[0]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
return errorPos;
|
||||
}
|
||||
|
||||
internal virtual bool[] correctErrorBit(bool[] y, int[] errorPos)
|
||||
{
|
||||
for (int i = 1; i <= errorPos[0]; i++)
|
||||
y[errorPos[i]] = !y[errorPos[i]];
|
||||
|
||||
numCorrectedError = errorPos[0];
|
||||
return y;
|
||||
}
|
||||
}
|
||||
}
|
||||
435
ecc/ReedSolomon.cs
Normal file
435
ecc/ReedSolomon.cs
Normal file
@@ -0,0 +1,435 @@
|
||||
using System;
|
||||
|
||||
namespace ThoughtWorks.QRCode.Codec.Ecc
|
||||
{
|
||||
public class ReedSolomon
|
||||
{
|
||||
virtual public bool CorrectionSucceeded
|
||||
{
|
||||
get
|
||||
{
|
||||
return correctionSucceeded;
|
||||
}
|
||||
|
||||
}
|
||||
virtual public int NumCorrectedErrors
|
||||
{
|
||||
get
|
||||
{
|
||||
return NErrors;
|
||||
}
|
||||
|
||||
}
|
||||
//G(x)=a^8+a^4+a^3+a^2+1
|
||||
internal int[] y;
|
||||
|
||||
internal int[] gexp = new int[512];
|
||||
internal int[] glog = new int[256];
|
||||
internal int NPAR;
|
||||
//final int NPAR = 15;
|
||||
internal int MAXDEG;
|
||||
internal int[] synBytes;
|
||||
|
||||
/* The Error Locator Polynomial, also known as Lambda or Sigma. Lambda[0] == 1 */
|
||||
internal int[] Lambda;
|
||||
|
||||
/* The Error Evaluator Polynomial */
|
||||
internal int[] Omega;
|
||||
|
||||
/* local ANSI declarations */
|
||||
|
||||
/* error locations found using Chien's search*/
|
||||
internal int[] ErrorLocs = new int[256];
|
||||
internal int NErrors;
|
||||
|
||||
/* erasure flags */
|
||||
internal int[] ErasureLocs = new int[256];
|
||||
internal int NErasures = 0;
|
||||
|
||||
internal bool correctionSucceeded = true;
|
||||
|
||||
public ReedSolomon(int[] source, int NPAR)
|
||||
{
|
||||
initializeGaloisTables();
|
||||
y = source;
|
||||
this.NPAR = NPAR;
|
||||
MAXDEG = NPAR * 2;
|
||||
synBytes = new int[MAXDEG];
|
||||
Lambda = new int[MAXDEG];
|
||||
Omega = new int[MAXDEG];
|
||||
}
|
||||
|
||||
internal virtual void initializeGaloisTables()
|
||||
{
|
||||
int i, z;
|
||||
int pinit, p1, p2, p3, p4, p5, p6, p7, p8;
|
||||
|
||||
pinit = p2 = p3 = p4 = p5 = p6 = p7 = p8 = 0;
|
||||
p1 = 1;
|
||||
|
||||
gexp[0] = 1;
|
||||
gexp[255] = gexp[0];
|
||||
glog[0] = 0; /* shouldn't log[0] be an error? */
|
||||
|
||||
for (i = 1; i < 256; i++)
|
||||
{
|
||||
pinit = p8;
|
||||
p8 = p7;
|
||||
p7 = p6;
|
||||
p6 = p5;
|
||||
p5 = p4 ^ pinit;
|
||||
p4 = p3 ^ pinit;
|
||||
p3 = p2 ^ pinit;
|
||||
p2 = p1;
|
||||
p1 = pinit;
|
||||
gexp[i] = p1 + p2 * 2 + p3 * 4 + p4 * 8 + p5 * 16 + p6 * 32 + p7 * 64 + p8 * 128;
|
||||
gexp[i + 255] = gexp[i];
|
||||
}
|
||||
|
||||
for (i = 1; i < 256; i++)
|
||||
{
|
||||
for (z = 0; z < 256; z++)
|
||||
{
|
||||
if (gexp[z] == i)
|
||||
{
|
||||
glog[i] = z;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* multiplication using logarithms */
|
||||
internal virtual int gmult(int a, int b)
|
||||
{
|
||||
int i, j;
|
||||
if (a == 0 || b == 0)
|
||||
return (0);
|
||||
i = glog[a];
|
||||
j = glog[b];
|
||||
return (gexp[i + j]);
|
||||
}
|
||||
|
||||
|
||||
internal virtual int ginv(int elt)
|
||||
{
|
||||
return (gexp[255 - glog[elt]]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal virtual void decode_data(int[] data)
|
||||
{
|
||||
int i, j, sum;
|
||||
for (j = 0; j < MAXDEG; j++)
|
||||
{
|
||||
sum = 0;
|
||||
for (i = 0; i < data.Length; i++)
|
||||
{
|
||||
sum = data[i] ^ gmult(gexp[j + 1], sum);
|
||||
}
|
||||
synBytes[j] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void correct()
|
||||
{
|
||||
//
|
||||
decode_data(y);
|
||||
correctionSucceeded = true;
|
||||
bool hasError = false;
|
||||
for (int i = 0; i < synBytes.Length; i++)
|
||||
{
|
||||
//Console.out.println("SyndromeS"+String.valueOf(i) + " = " + synBytes[i]);
|
||||
if (synBytes[i] != 0)
|
||||
hasError = true;
|
||||
}
|
||||
if (hasError)
|
||||
correctionSucceeded = correct_errors_erasures(y, y.Length, 0, new int[1]);
|
||||
}
|
||||
|
||||
internal virtual void Modified_Berlekamp_Massey()
|
||||
{
|
||||
int n, L, L2, k, d, i;
|
||||
int[] psi = new int[MAXDEG];
|
||||
int[] psi2 = new int[MAXDEG];
|
||||
int[] D = new int[MAXDEG];
|
||||
int[] gamma = new int[MAXDEG];
|
||||
|
||||
/* initialize Gamma, the erasure locator polynomial */
|
||||
init_gamma(gamma);
|
||||
|
||||
/* initialize to z */
|
||||
copy_poly(D, gamma);
|
||||
mul_z_poly(D);
|
||||
|
||||
copy_poly(psi, gamma);
|
||||
k = - 1; L = NErasures;
|
||||
|
||||
for (n = NErasures; n < 8; n++)
|
||||
{
|
||||
|
||||
d = compute_discrepancy(psi, synBytes, L, n);
|
||||
|
||||
if (d != 0)
|
||||
{
|
||||
|
||||
/* psi2 = psi - d*D */
|
||||
for (i = 0; i < MAXDEG; i++)
|
||||
psi2[i] = psi[i] ^ gmult(d, D[i]);
|
||||
|
||||
|
||||
if (L < (n - k))
|
||||
{
|
||||
L2 = n - k;
|
||||
k = n - L;
|
||||
/* D = scale_poly(ginv(d), psi); */
|
||||
for (i = 0; i < MAXDEG; i++)
|
||||
D[i] = gmult(psi[i], ginv(d));
|
||||
L = L2;
|
||||
}
|
||||
|
||||
/* psi = psi2 */
|
||||
for (i = 0; i < MAXDEG; i++)
|
||||
psi[i] = psi2[i];
|
||||
}
|
||||
|
||||
mul_z_poly(D);
|
||||
}
|
||||
|
||||
for (i = 0; i < MAXDEG; i++)
|
||||
Lambda[i] = psi[i];
|
||||
compute_modified_omega();
|
||||
}
|
||||
|
||||
/* given Psi (called Lambda in Modified_Berlekamp_Massey) and synBytes,
|
||||
compute the combined erasure/error evaluator polynomial as
|
||||
Psi*S mod z^4
|
||||
*/
|
||||
internal virtual void compute_modified_omega()
|
||||
{
|
||||
int i;
|
||||
int[] product = new int[MAXDEG * 2];
|
||||
|
||||
mult_polys(product, Lambda, synBytes);
|
||||
zero_poly(Omega);
|
||||
for (i = 0; i < NPAR; i++)
|
||||
Omega[i] = product[i];
|
||||
}
|
||||
|
||||
/* polynomial multiplication */
|
||||
internal virtual void mult_polys(int[] dst, int[] p1, int[] p2)
|
||||
{
|
||||
int i, j;
|
||||
int[] tmp1 = new int[MAXDEG * 2];
|
||||
|
||||
for (i = 0; i < (MAXDEG * 2); i++)
|
||||
dst[i] = 0;
|
||||
|
||||
for (i = 0; i < MAXDEG; i++)
|
||||
{
|
||||
for (j = MAXDEG; j < (MAXDEG * 2); j++)
|
||||
tmp1[j] = 0;
|
||||
|
||||
/* scale tmp1 by p1[i] */
|
||||
for (j = 0; j < MAXDEG; j++)
|
||||
tmp1[j] = gmult(p2[j], p1[i]);
|
||||
/* and mult (shift) tmp1 right by i */
|
||||
for (j = (MAXDEG * 2) - 1; j >= i; j--)
|
||||
tmp1[j] = tmp1[j - i];
|
||||
for (j = 0; j < i; j++)
|
||||
tmp1[j] = 0;
|
||||
|
||||
/* add into partial product */
|
||||
for (j = 0; j < (MAXDEG * 2); j++)
|
||||
dst[j] ^= tmp1[j];
|
||||
}
|
||||
}
|
||||
|
||||
/* gamma = product (1-z*a^Ij) for erasure locs Ij */
|
||||
internal virtual void init_gamma(int[] gamma)
|
||||
{
|
||||
int e;
|
||||
int[] tmp = new int[MAXDEG];
|
||||
|
||||
zero_poly(gamma);
|
||||
zero_poly(tmp);
|
||||
gamma[0] = 1;
|
||||
|
||||
for (e = 0; e < NErasures; e++)
|
||||
{
|
||||
copy_poly(tmp, gamma);
|
||||
scale_poly(gexp[ErasureLocs[e]], tmp);
|
||||
mul_z_poly(tmp);
|
||||
add_polys(gamma, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal virtual void compute_next_omega(int d, int[] A, int[] dst, int[] src)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < MAXDEG; i++)
|
||||
{
|
||||
dst[i] = src[i] ^ gmult(d, A[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal virtual int compute_discrepancy(int[] lambda, int[] S, int L, int n)
|
||||
{
|
||||
int i, sum = 0;
|
||||
|
||||
for (i = 0; i <= L; i++)
|
||||
sum ^= gmult(lambda[i], S[n - i]);
|
||||
return (sum);
|
||||
}
|
||||
|
||||
/// <summary>******* polynomial arithmetic ******************</summary>
|
||||
|
||||
internal virtual void add_polys(int[] dst, int[] src)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < MAXDEG; i++)
|
||||
dst[i] ^= src[i];
|
||||
}
|
||||
|
||||
internal virtual void copy_poly(int[] dst, int[] src)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < MAXDEG; i++)
|
||||
dst[i] = src[i];
|
||||
}
|
||||
|
||||
internal virtual void scale_poly(int k, int[] poly)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < MAXDEG; i++)
|
||||
poly[i] = gmult(k, poly[i]);
|
||||
}
|
||||
|
||||
|
||||
internal virtual void zero_poly(int[] poly)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < MAXDEG; i++)
|
||||
poly[i] = 0;
|
||||
}
|
||||
|
||||
|
||||
/* multiply by z, i.e., shift right by 1 */
|
||||
internal virtual void mul_z_poly(int[] src)
|
||||
{
|
||||
int i;
|
||||
for (i = MAXDEG - 1; i > 0; i--)
|
||||
src[i] = src[i - 1];
|
||||
src[0] = 0;
|
||||
}
|
||||
|
||||
|
||||
/* Finds all the roots of an error-locator polynomial with coefficients
|
||||
* Lambda[j] by evaluating Lambda at successive values of alpha.
|
||||
*
|
||||
* This can be tested with the decoder's equations case.
|
||||
*/
|
||||
internal virtual void Find_Roots()
|
||||
{
|
||||
int sum, r, k;
|
||||
NErrors = 0;
|
||||
|
||||
for (r = 1; r < 256; r++)
|
||||
{
|
||||
sum = 0;
|
||||
/* evaluate lambda at r */
|
||||
for (k = 0; k < NPAR + 1; k++)
|
||||
{
|
||||
sum ^= gmult(gexp[(k * r) % 255], Lambda[k]);
|
||||
}
|
||||
if (sum == 0)
|
||||
{
|
||||
ErrorLocs[NErrors] = (255 - r); NErrors++;
|
||||
//if (DEBUG) fprintf(stderr, "Root found at r = %d, (255-r) = %d\n", r, (255-r));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Combined Erasure And Error Magnitude Computation
|
||||
*
|
||||
* Pass in the codeword, its size in bytes, as well as
|
||||
* an array of any known erasure locations, along the number
|
||||
* of these erasures.
|
||||
*
|
||||
* Evaluate Omega(actually Psi)/Lambda' at the roots
|
||||
* alpha^(-i) for error locs i.
|
||||
*
|
||||
* Returns 1 if everything ok, or 0 if an out-of-bounds error is found
|
||||
*
|
||||
*/
|
||||
|
||||
internal virtual bool correct_errors_erasures(int[] codeword, int csize, int nerasures, int[] erasures)
|
||||
{
|
||||
int r, i, j, err;
|
||||
|
||||
/* If you want to take advantage of erasure correction, be sure to
|
||||
set NErasures and ErasureLocs[] with the locations of erasures.
|
||||
*/
|
||||
NErasures = nerasures;
|
||||
for (i = 0; i < NErasures; i++)
|
||||
ErasureLocs[i] = erasures[i];
|
||||
|
||||
Modified_Berlekamp_Massey();
|
||||
Find_Roots();
|
||||
|
||||
|
||||
if ((NErrors <= NPAR) || NErrors > 0)
|
||||
{
|
||||
|
||||
/* first check for illegal error locs */
|
||||
for (r = 0; r < NErrors; r++)
|
||||
{
|
||||
if (ErrorLocs[r] >= csize)
|
||||
{
|
||||
//if (DEBUG) fprintf(stderr, "Error loc i=%d outside of codeword length %d\n", i, csize);
|
||||
//Console.out.println("Error loc i="+ErrorLocs[r]+" outside of codeword length"+csize);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (r = 0; r < NErrors; r++)
|
||||
{
|
||||
int num, denom;
|
||||
i = ErrorLocs[r];
|
||||
/* evaluate Omega at alpha^(-i) */
|
||||
|
||||
num = 0;
|
||||
for (j = 0; j < MAXDEG; j++)
|
||||
num ^= gmult(Omega[j], gexp[((255 - i) * j) % 255]);
|
||||
|
||||
/* evaluate Lambda' (derivative) at alpha^(-i) ; all odd powers disappear */
|
||||
denom = 0;
|
||||
for (j = 1; j < MAXDEG; j += 2)
|
||||
{
|
||||
denom ^= gmult(Lambda[j], gexp[((255 - i) * (j - 1)) % 255]);
|
||||
}
|
||||
|
||||
err = gmult(num, ginv(denom));
|
||||
//if (DEBUG) fprintf(stderr, "Error magnitude %#x at loc %d\n", err, csize-i);
|
||||
|
||||
codeword[csize - i - 1] ^= err;
|
||||
}
|
||||
//for (int p = 0; p < codeword.length; p++)
|
||||
// Console.out.println(codeword[p]);
|
||||
//Console.out.println("correction succeeded");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//if (DEBUG && NErrors) fprintf(stderr, "Uncorrectable codeword\n");
|
||||
//Console.out.println("Uncorrectable codeword");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user