/// <summary> /// Encode the length of the packets that are in the data BufferChunk array and place the /// encoded length over GF16 in the 2 first byte of the checksum packets that are in /// the result BufferChunk array /// </summary> /// <param name="data">The BufferChunk array containing the data packets to get the size</param> /// <param name="checksum">The number of checksum packets to generate. /// This number is actually also the number of column of the Vandermonde /// encoding matrix</param> /// <param name="encode">The Vandermonde encoding matrix (size min should be /// Vandermonde #column: checksum, Vandermonde #row: data.Length) /// </param> /// <param name="result">The BufferChunk checksum packets array of containing the encoded length (over GF16) /// in the first 2 bytes</param> private static void EncodeRSLength(BufferChunk[] data, BufferChunk[] result, UInt16[,] encode) { // Get the length once to avoid having the overhead of getting it again inside a inner (performance) // Note that this value is also used outside of this method, so we could have had a parameter too, // but I prefer to get it here to avoid inconsistencies int dataPackets = data.Length; int checksum = result.Length; // Note: The size of the Vandermonde matrix used to encode the length is // Vandermonde #column: checksum, Vandermonde #row: dataPackets // TODO: Validate if Vandermonde # max column >= checksum and // Vandermonde #row >= dataPackets // TODO: Validate that there are no null entries in data and result // TODO: Validate that all the checksum packet have at least 2 bytes // Length encoding for (int encodeColumn = 0; encodeColumn < checksum; encodeColumn++) { UInt16 encodeValue = 0; for (int dataPacket = 0; dataPacket < dataPackets; dataPacket++) { // TODO: performance - lengths of BC don't change between loops, store once? JVE UInt16 dataValue = GF16.Multiply((UInt16)data[dataPacket].Length, encode[encodeColumn, dataPacket]); encodeValue = GF16.Add(encodeValue, dataValue); } result[encodeColumn] += encodeValue; } }
/// <summary> /// Decode to retrieve the missing data packet(s) (null entries) in the data BufferChunk array and place the /// decoded result over GF16 in order in the recovery BufferChunk array /// </summary> /// <param name="data">The data BufferChunk array</param> /// <param name="checksum">The number of checksum packet(s) that were used to encode</param> /// <param name="decode">The decoding GF16 matrix</param> /// <param name="recovery">The recovery BufferChunk array to place the recovered result</param> private static void DecodeRSData(BufferChunk[] data, int checksum, GF16[,] decode, BufferChunk[] recovery) { // Important! For now I assume the following in the firstMatrix: // - The array is always of size data + checksum // - #checksum not null entries = #packet lost // - recovery.length is exactly the number of data packet lost // - checksum param is # checksum packets that were used to encode // Reconstruct data // Get the length once to avoid having the overhead of getting it again inside a inner (performance) // Note that this value is also used outside of this method, so we could have had a parameter too, // but I prefer to get it here to avoid inconsitencies int dataLength = data.Length; // Scan column after column int recoveryColumn = 0; for (int dataColumn = 0; dataColumn < dataLength - checksum; dataColumn++) { // recover only the missing column if (data[dataColumn] == null) { int recoveryRowLength = recovery[recoveryColumn].Length; // For each column fill out the output mtx row after row for (int recoveryRow = 0; recoveryRow < recoveryRowLength; recoveryRow += 2) { int imRowIndex = 0; // nnDataColumn - not null data index for (int nnDataColumn = 0; nnDataColumn < dataLength; nnDataColumn++) { // Perform the core operation mult with add in GF16 // TODO: We could crunch the column so we avoid this test if (data[nnDataColumn] != null) { if (recoveryRow < data[nnDataColumn].Length) { UInt16 currentValue = GF16.Multiply(data[nnDataColumn].GetUInt16(recoveryRow), decode[dataColumn, imRowIndex].Value); currentValue = GF16.Add(recovery[recoveryColumn].GetUInt16(recoveryRow), currentValue); recovery[recoveryColumn].SetUInt16(recoveryRow, currentValue); } imRowIndex++; } } } recoveryColumn++; } } }
/// <summary> /// Create a Vandermonde matrix of size row x column over GF16 /// </summary> /// <remarks> /// The Vandermonde matrix is typically used to create the encoding matrix where: /// - The number of Columns of the matrix correspond to number of checksum /// packets. /// - The number of Rows of the matrix correspond to number of data packets. /// </remarks> /// <param name="columns">The number of columns of the Vandermonde matrix</param> /// <param name="rows">The number of rows of the Vandermode matrix</param> /// <returns></returns> public static UInt16[,] CreateVandermondeMatrix(int columns, int rows) { // TODO: Add input validation // maxChecksumPackets will be the max number of Columns of the encoding static matrix // maxDataPackets will be the max number of Rows of the encoding static matrix UInt16[,] vandermondeMtx = new UInt16[columns, rows]; // Creation of the Vandermonde Matrix over GF16 with the following // (2^column)^row // As an example, a 5 x 3 Vandermonde Matrix over GF16 (5 data packets, 3 checksum packets) // would give the following: // // 1^0 2^0 4^0 // 1^1 2^1 4^1 // 1^2 2^2 4^2 // 1^3 2^3 4^3 // 1^4 2^4 4^4 // // Which gives: // // 1 1 1 // 1 2 4 // 1 4 16 // 1 8 64 // 1 16 256 for (int col = 0; col < columns; col++) { // multFactor is the number to multiply to get the value in the next row // for a given column of the Vandermonde matrix UInt16 multFactor = GF16.Power(2, (uint)col); for (int row = 0; row < rows; row++) { if (row == 0) { // Special case the first row (power of zero) vandermondeMtx[col, row] = 1; } else { // Each element of the Vandermonde matrix is calculated as (2^column)^row over GF16 // This algorithm uses the previous row to compute the next one to improve // the performances (instead of recalculating (2^column)^row) vandermondeMtx[col, row] = GF16.Multiply(vandermondeMtx[col, row - 1], multFactor); } } } return(vandermondeMtx); }
/// <summary> /// Invert the matrix by using Gauss-Jordan using GF16 operations /// </summary> /// <remarks> /// The inversion is done by adding the identity matrix to the right /// and using Gauss-Jordan algorithm (elementary /// operations on rows) until we get the identity matrix /// on the left. Then we strip the identity on the left /// in order to get the reverted matrix. /// </remarks> /// <param name="mtxIn_GF16">The matrix to invert</param> /// <returns>The inverted matrix</returns> public static GF16[,] Invert(GF16[,] mtxIn_GF16) { // Add the ID to the right to be able to apply Gauss-Jordan to revert the matrix GF16[,] mtxWithID_GF16 = FEC_Matrix.AddIdentityRight(mtxIn_GF16); // Apply Gauss Jordan algo to revert the matrix GF16[,] mtxInvert_GF16 = FEC_Matrix.GaussJordan(mtxWithID_GF16); // Remove the ID on the left GF16[,] mtxInvertStripped_GF16 = FEC_Matrix.StripIdentity(mtxInvert_GF16); // Return the inverted matrix return mtxInvertStripped_GF16; }
/// <summary> /// Remove the identity at the front of the matrix /// </summary> /// <param name="matrixIn">The input matrix</param> /// <returns>Stripped Matrix without the identity at the front</returns> private static GF16[,] StripIdentity(GF16[,] matrixIn) { int rowLength = matrixIn.GetLength(1); int columnLength = matrixIn.GetLength(0); GF16[,] matrixOut = new GF16[columnLength - rowLength, rowLength]; for (int row = 0; row < rowLength; row++) { for (int column = rowLength; column < columnLength; column++) { matrixOut[column - rowLength, row] = matrixIn[column, row]; } } return(matrixOut); }
/// <summary> /// Add identity to the right side of a matrix /// </summary> /// <remarks>This is use when doing Jordan-Gauss elimination</remarks> /// <param name="matrixIn">The matrix</param> /// <returns>The new matrix with the identity</returns> private static GF16[,] AddIdentityRight(GF16[,] matrixIn) { Int32 rowLength = matrixIn.GetLength(1); Int32 columnLength = matrixIn.GetLength(0); GF16[,] matrixOut = new GF16[columnLength * 2, rowLength]; // Add in identity portion to the top for (Int32 column = 0; column < columnLength; column++) { matrixOut[column + columnLength, column] = (UInt16)1; } // Copy the previous matrix to the bottom MatrixCopyTo(matrixIn, matrixOut); return(matrixOut); }
/// <summary> /// Power is used to create teh Vandermonde matrix /// </summary> /// <param name="exponent"></param> /// <returns></returns> public GF16 Power(int exponent) { if (Value == 0) { return(this); } if (exponent == 0) { return(new GF16((UInt16)1)); } GF16 original = this; for (int i = 1; i < exponent; i++) { this *= original; } return(this); }
/// <summary> /// Encode the packets that are in the data BufferChunk array and place the /// encoded result over GF16 in the checksum packets that are in /// the result BufferChunk array /// </summary> /// <param name="data">The BufferChunk array containing the data packets</param> /// <param name="checksum">The number of checksum packets to generate. /// This number is actually also the number of column of the Vandermonde /// encoding matrix</param> /// <param name="checksum">The BufferChunk checksum packets array of containing the encoded data (over GF16) /// after the first 2 bytes (the first 2 bytes are used to encode the length)</param> /// <param name="encode">The Vandermonde encoding matrix (size min should be /// Vandermonde #column: checksum, Vandermonde #row: data.Length) /// </param> /// <param name="checksumRowsInt16">The number of row (in int 16 chunks) of the checksum packets</param> private static void EncodeRSData(BufferChunk[] data, BufferChunk[] checksum, UInt16[,] encode, int maxDataLength) { // Note: The size of the Vandermonde matrix used to encode the length is // Vandermonde #column: checksumLength, Vandermonde #row: dataPackets // TODO: Validate if Vandermonde # max column >= checksum and // Vandermonde #row >= dataPackets // TODO: Validate that there are no null entries in data and checksum // Get the length once to avoid having the overhead of getting it again inside a inner (performance) int dataPackets = data.Length; int checksumLength = checksum.Length; // Note that we scan column after column, so we generate the checksum packets // one after the other for (int checksumColumn = 0; checksumColumn < checksumLength; checksumColumn++) { // For each column fill out the checksum mtx row after row for (int checksumRow = 0; checksumRow < maxDataLength; checksumRow += 2) { for (int encodeRow = 0; encodeRow < dataPackets; encodeRow++) { // If we pass the size of the current data packet, so no operation are required // if ((checksumRow) < data[encodeRow].Length) { UInt16 currentValue = GF16.Multiply(data[encodeRow].GetUInt16(checksumRow), encode[checksumColumn, encodeRow]); currentValue = GF16.Add(checksum[checksumColumn].GetUInt16(checksumRow), currentValue); checksum[checksumColumn].SetUInt16(checksumRow, currentValue); } } } } }
/// <summary> /// Add identity to the right side of a matrix /// </summary> /// <remarks>This is use when doing Jordan-Gauss elimination</remarks> /// <param name="matrixIn">The matrix</param> /// <returns>The new matrix with the identity</returns> private static GF16[,] AddIdentityRight (GF16[,] matrixIn) { Int32 rowLength = matrixIn.GetLength(1); Int32 columnLength = matrixIn.GetLength(0); GF16[,] matrixOut = new GF16[columnLength * 2, rowLength]; // Add in identity portion to the top for (Int32 column = 0; column < columnLength; column++) { matrixOut[column + columnLength, column] = (UInt16)1; } // Copy the previous matrix to the bottom MatrixCopyTo(matrixIn, matrixOut); return matrixOut; }
static GF16() { GF16.fillLogTables(); }
/// <summary> /// Decode to retrieve the length of the missing data packet(s) (null entries) in the /// data BufferChunk array and set the length of the packets in the recovery BufferChunk array. /// We have to do that because every data packet could have a different length. /// </summary> /// <param name="data">The data BufferChunk array</param> /// <param name="checksum">The number of checksum packet(s) that were used to encode</param> /// <param name="decode">The decoding GF16 matrix</param> /// <param name="recovery">The recovery BufferChunk array to set the length of the recovered packets</param> private static void DecodeRSLength(BufferChunk[] data, int checksum, GF16[,] decode, BufferChunk[] recovery) { // Important! For now I assume the following in the firstMatrix: // - The array is always of size data + checksum // - #checksum not null entries = #packet lost // - recovery.length is exactly the number of data packet lost // - checksum param is # checksum packets that were used to encode // Get the length once to avoid having the overhead of getting it again inside a inner (performance) // Note that this value is also used outside of this method, so we could have had a parameter too, // but I prefer to get it here to avoid inconsitencies int dataLength = data.Length; int recoveryColumn = 0; for (int dataColumn = 0; dataColumn < dataLength - checksum; dataColumn++) { // recover only the missing column if (data[dataColumn] == null) { // Inverted matrix row index int imRowIndex = 0; // length of the recovered buffer UInt16 currentLength = 0; for (int dataIndex = 0; dataIndex < dataLength; dataIndex++) { // Perform the core operation mult with add in GF16 // TODO: We could crunch the column so we avoid this test if (data[dataIndex] != null) { UInt16 length = 0; if (dataIndex < dataLength - checksum) // For the data part, we get the length of the BufferChunk { length = (UInt16) data[dataIndex].Length; } else // For the checksum part, the encoded length is inside the first 2 bytes { length = data[dataIndex].GetUInt16(0); } UInt16 currentValue = GF16.Multiply(length, decode[dataColumn, imRowIndex].Value); currentLength = GF16.Add(currentLength, currentValue); imRowIndex++; } // if } // for column (do elementary operations) BufferChunk bc = recovery[recoveryColumn]; bc.Reset(bc.Index, currentLength); // Reset all of the recovery packets so they have no data // TODO - Temporary workaround for column based approach - JVE 7/6/2004 bc.Clear(); recoveryColumn++; } // if } // for dataColumn }
/// <summary> /// Create the decoding matrix /// </summary> /// <param name="nbDataPackets">Number of data packet</param> /// <param name="nbChecksumPackets">Number of checksum packet</param> /// <param name="rtpPackets">Array of rtpPackets</param> /// <returns>The decoding matrix</returns> /// <example> /// For example, if you have 3 data packets and 2 checksum /// packets and get the last data packet, you will get /// the following matrix /// 0 1 1 /// 0 1 2 /// 1 1 4 /// </example> // TODO: To be more generic, we should use System.Array instead of array of BufferChunks public static GF16[,] DecodingMatrixGeneration(BufferChunk[] dataReceived, int nbChecksumPackets) { // nbTotalPackets is the total # rtpPackets to create (data packets and checksum packets) int nbTotalPackets = dataReceived.Length; int nbDataPackets = nbTotalPackets - nbChecksumPackets; // Let's suppose that we lost the 2 first packets // So [E'] that should have been // 1 0 0 1 1 // 0 1 0 1 2 // 0 0 1 1 4 // is actually // 0 1 1 // 0 1 2 // 1 1 4 // The reverted decoding matrix will be multiplied by data received to get all the datas // for instance [D0..D2] = [D2 C0 C1] x 1/[E'] // => So the number of columns should correspond to the number of data recieved + checksum received // and the number of rows should correspond of the number of data packet K // count the number of packets received int nbPacketsReceived = ReceivedPacketsCount(nbTotalPackets, dataReceived); // nbPacketsReceived columns, K rows // Note that the # columns is smaller than the number of packet received // in case the number of packet lost was smaller than the number than // the number of checksum packets (we short the matrix: It's like if we lost the last // checksum(s) packet(s) GF16[,] mtxEprime_GF16 = new GF16[nbPacketsReceived, nbDataPackets]; // Construct the matrix mtxEprime_GF16 from the rtpPackets received // Note see also Jay's Decode method // Loop trough the data rtpPackets and put a 1 on the row of the packet // received int foundIndex = 0; for (int index=0; index < nbDataPackets; index++) { if (dataReceived[index] != null) // not a packet lost { // Generate a row in the mtxEprime_GF16 (decode matrix) if (index < nbDataPackets) { // TODO: To be clean, we should have initialized all the other items // on the row explicitely to zero or make sure their are set them to zero // when creating the new matrix mtxEprime_GF16[foundIndex, index] = (UInt16)1; } foundIndex++; // if (foundIndex >= nbPacketsReceived-nbAdditionalPacketsReceived) // return mtxEprime_GF16; } } // Generate a Vandermonde Mtx for the other items // TODO: Change that to use the Vandermonde static mtx for (UInt32 m = 0; m < nbChecksumPackets; m++) { // Note: If a checksum packet has been lost, the column is skiped if (dataReceived[m+nbDataPackets] != null) // not a packet lost { for (UInt32 k = 0; k < nbDataPackets; k++) { // Each element of the Vandermonde matrix is calculated as (m+1)^k over GF16 // mtx[column, row] = (column+1)^row // With Vandermonde: 1^x, 2^x, 3^x, ... the code was: // mtxEprime_GF16[foundIndex, k] = GF16.Power((UInt16)(m+1), (UInt32)k); // I experimented problems (no pivot found during invertion of Eprime) // when losing 3 packets (worked fine when losing 2 packets) // So I changed, to use Vandermonde matrix were mtx[column, row] = (2^column)^row mtxEprime_GF16[foundIndex, k] = GF16.Power((UInt16)GF16.Power(2, (m)), (UInt32)k); // TODO: Check if Jay's approach is more efficient: mtxE_GF16[m, k] = GF16.gf_exp[GF16.Modnn(k*m)]; } foundIndex++; // if (foundIndex >= nbPacketsReceived-nbAdditionalPacketsReceived) // return mtxEprime_GF16; } } // for return mtxEprime_GF16; }
/// <summary> /// Decode to retrieve the missing data packet(s) (null entries) in the data BufferChunk array and place the /// decoded result over GF16 in order in the recovery BufferChunk array with the right length (every /// data packet can have a different length) /// </summary> /// <param name="checksum">The number of checksum packet(s) that were used to encode</param> /// <param name="data">The data BufferChunk array</param> /// <param name="decode">The decoding GF16 matrix</param> /// <param name="recovery">The recovery BufferChunk array to place the recovered result</param> public static void DecodeRS(int checksum, BufferChunk[] data, GF16[,] decode, BufferChunk[] recovery) { // TODO: Validation discussed with Jason: // Check in data array if: the #data lost = # checksum and if this number = recovery.length // Decode the length and set it to the recovery packets DecodeRSLength(data, checksum, decode, recovery); // ... and don't forget to adjust the index of the checksum BufferChunk because // we don't want to take in account the length anymore int dataLength = data.Length; for (int i = dataLength - checksum; i < dataLength; i++) { BufferChunk bc = data[i]; if(bc != null) { bc.Reset(bc.Index + CFec.SIZE_OVERHEAD, bc.Length - CFec.SIZE_OVERHEAD); } } // Adjust all of the data (and recovery) to a 16bit boundary to avoid byte operations // Important: This assumes the provided BufferChunks have room to grow to 16bit boundary Queue paddedBufferChunks = new Queue(); AddPadding(data, checksum, paddedBufferChunks); AddPadding(recovery, 0, paddedBufferChunks); // Decode the data and set the result into the recovery packets DecodeRSData(data, checksum, decode, recovery); // Reset all the packets back to their initial length RemovePadding(paddedBufferChunks); // The caller is responsible for resetting all BufferChunks so there is no need // to reset the checksum packets }
/// <summary> /// Decode to retrieve the length of the missing data packet(s) (null entries) in the /// data BufferChunk array and set the length of the packets in the recovery BufferChunk array. /// We have to do that because every data packet could have a different length. /// </summary> /// <param name="data">The data BufferChunk array</param> /// <param name="checksum">The number of checksum packet(s) that were used to encode</param> /// <param name="decode">The decoding GF16 matrix</param> /// <param name="recovery">The recovery BufferChunk array to set the length of the recovered packets</param> private static void DecodeRSLength(BufferChunk[] data, int checksum, GF16[,] decode, BufferChunk[] recovery) { // Important! For now I assume the following in the firstMatrix: // - The array is always of size data + checksum // - #checksum not null entries = #packet lost // - recovery.length is exactly the number of data packet lost // - checksum param is # checksum packets that were used to encode // Get the length once to avoid having the overhead of getting it again inside a inner (performance) // Note that this value is also used outside of this method, so we could have had a parameter too, // but I prefer to get it here to avoid inconsitencies int dataLength = data.Length; int recoveryColumn = 0; for (int dataColumn = 0; dataColumn < dataLength - checksum; dataColumn++) { // recover only the missing column if (data[dataColumn] == null) { // Inverted matrix row index int imRowIndex = 0; // length of the recovered buffer UInt16 currentLength = 0; for (int dataIndex = 0; dataIndex < dataLength; dataIndex++) { // Perform the core operation mult with add in GF16 // TODO: We could crunch the column so we avoid this test if (data[dataIndex] != null) { UInt16 length = 0; if (dataIndex < dataLength - checksum) // For the data part, we get the length of the BufferChunk { length = (UInt16)data[dataIndex].Length; } else // For the checksum part, the encoded length is inside the first 2 bytes { length = data[dataIndex].GetUInt16(0); } UInt16 currentValue = GF16.Multiply(length, decode[dataColumn, imRowIndex].Value); currentLength = GF16.Add(currentLength, currentValue); imRowIndex++; } // if } // for column (do elementary operations) BufferChunk bc = recovery[recoveryColumn]; bc.Reset(bc.Index, currentLength); // Reset all of the recovery packets so they have no data // TODO - Temporary workaround for column based approach - JVE 7/6/2004 bc.Clear(); recoveryColumn++; } // if } // for dataColumn }
/// <summary> /// Copy a matrix to a destination matrix /// </summary> /// <param name="matrixIn">The matrix to copy</param> /// <param name="matrixDestination">The destination matrix</param> private static void MatrixCopyTo(GF16[,] matrixIn, GF16[,] matrixDestination) { int rowLength = matrixIn.GetLength(1); int columnLength = matrixIn.GetLength(0); for (int row = 0; row < rowLength; row++) for (int column = 0; column < columnLength; column++) matrixDestination[column, row] = matrixIn[column, row]; }
/// <summary> /// Gauss - Jordan elimination. /// This algorithm takes a matrix and use elementary /// row operations to put the matrix in a row echelon form. This /// means that the result matrix will have the identity matrix at the /// begining. /// This method can be used in 2 specific scenarios: /// - To solve a linear system numerically /// - To invert a matrix /// </summary> /// <example> /// For instance the matrix: /// 0 1 1 1 0 0 /// 0 1 2 0 1 0 /// 1 1 4 0 0 1 /// /// Will become (over int): /// [ ID ] [ rest ] /// 1 0 0 2 -3 1 /// 0 1 0 2 -1 0 /// 0 0 1 -1 1 0 /// </example> /// <remarks> /// For the Reed Solomon encoding, Gauss - Jordan elimination /// is used to invert the decoding matrix. This is needed to /// recover from packet loss. /// Important note: The input is modified /// </remarks> /// <param name="matrixIn">The matrix to transform in a row echelon form</param> /// <returns>The matrix in a row echelon from</returns> private static GF16[,] GaussJordan(GF16[,] matrixIn) { // Matrix size variables int rowLength = matrixIn.GetLength(1); int columnLength = matrixIn.GetLength(0); // The number of pivots needed is the min between row/column // For instance the matrix: // 0 1 1 1 0 0 // 0 1 2 0 1 0 // 1 1 4 0 0 1 // has 3 pivots int pivots = Math.Min(rowLength, columnLength); // Loop through all the pivot columns for (int pivotColumn = 0; pivotColumn < pivots; pivotColumn++) { // TODO: Place Step 1 in a separate method // Step 1: Find the Pivot Row and reduce the pivot to have a factor of 1 int pivotRow = -1; // Loop through all the rows to find a pivot for the pivotColumn for (int row = 0; row < rowLength; row++) { // A pivot must be non-zero... if (matrixIn[pivotColumn, row].Value != 0) { // ...and don't pivot on a row that has previous non-zero column entries // Loop through columns of the row Check is the row found respect the non-zero column rule bool isRowWithPreviousZero = true; for (int column = 0; column < pivotColumn; column++) { // Check for non-zero if (matrixIn[column, row].Value != 0) { // The row is not a pivot because it has previous non-zero entries isRowWithPreviousZero = false; break; } } // If the row found has previous zero, it is a pivot row if (isRowWithPreviousZero) { // The current row is the pivot pivotRow = row; GF16 factorPivot = matrixIn[pivotColumn, row]; // Reduce the pivot row to have a factor of 1 for(int column = pivotColumn; column < columnLength; column++) { matrixIn[column, row] /= factorPivot; } // We can exit the loop through rows in Step 1 break; } } } // Ensure that a pivot has been found if (pivotRow == -1) { throw new ApplicationException("No pivot row found!"); } // TODO: Place Step 2 in a separate method // Step 2: Do elementary operation to all rows (except the pivot row and // the rows that have zero on the pivot column) // The final goal is to have a zero for all rows on the column corresponding to the // pivot column // Loop trough the rows to do elementary operation (subtraction) for (int row = 0; row < rowLength; row++) { // Note: We skip the pivot row and the rows that has zero on the pivot // column (no operation requiered) if ((row != pivotRow) && (matrixIn[ pivotColumn, row].Value != 0)) { // Now, determine the factor by which the pivot row must be added to the target // row to cancel out the pivot value GF16 factorOperation = matrixIn[ pivotColumn, row ]; // Perform the operation for each column of the current row for(int column = 0; column < columnLength; column++) { // Perform a subtraction. The final goal is to have a zero for all rows // on the column corresponding to the pivot column matrixIn[column, row] -= factorOperation * matrixIn[column, pivotRow]; } } } } // At this point the work manipulation is done but we might get the rows // out of order. // For instance, the input matrix: // 0 1 1 1 0 0 // 0 1 2 0 1 0 // 1 1 4 0 0 1 // // will become the following (over int operation): // 0 1 0 2 -1 0 // 0 0 1 -1 1 0 // 1 0 0 2 -3 1 // // so now, we need to swap the row to place them in order to obtain the following: // [ ID ] [ rest ] // 1 0 0 2 -3 1 // 0 1 0 2 -1 0 // 0 0 1 -1 1 0 // TODO: Place Step 3 in a separate method // Step 3: Swap raw into order to have an identity matrix at the front for(int row = 0; row < rowLength; row++) // can use row < rowLength -1, because last row should always be in order? { // Skip rows that are in proper order if (matrixIn[row,row].Value != 1) { // Not in proper order, search down through the matrix bool found = false; for (int searchRow = 0; searchRow < rowLength; searchRow++) { if (matrixIn[row, searchRow].Value == 1) // found the row we're looking for { found = true; // Do the swap operation // Save the target row to temporary swap space first GF16[] swapRowTemp = new GF16[columnLength]; for(int swapRowColumn = 0; swapRowColumn < columnLength; swapRowColumn++) { swapRowTemp[swapRowColumn] = matrixIn[swapRowColumn, row]; } // Replace the target row contents with the found row contents for(int swapRowColumn = 0; swapRowColumn < columnLength; swapRowColumn++) { matrixIn[swapRowColumn, row] = matrixIn[swapRowColumn,searchRow]; } // Replace the found row contents with the temporary swap space (previous target row contents) for(int swapRowColumn = 0; swapRowColumn < columnLength; swapRowColumn++) { matrixIn[swapRowColumn,searchRow] = swapRowTemp[swapRowColumn]; } } } if(!found) throw new ApplicationException("Swap row not found!"); } } return matrixIn; }
/// <summary> /// Decode to retrieve the missing data packet(s) (null entries) in the data BufferChunk array and place the /// decoded result over GF16 in order in the recovery BufferChunk array /// </summary> /// <param name="data">The data BufferChunk array</param> /// <param name="checksum">The number of checksum packet(s) that were used to encode</param> /// <param name="decode">The decoding GF16 matrix</param> /// <param name="recovery">The recovery BufferChunk array to place the recovered result</param> private static void DecodeRSData(BufferChunk[] data, int checksum, GF16[,] decode, BufferChunk[] recovery) { // Important! For now I assume the following in the firstMatrix: // - The array is always of size data + checksum // - #checksum not null entries = #packet lost // - recovery.length is exactly the number of data packet lost // - checksum param is # checksum packets that were used to encode // Reconstruct data // Get the length once to avoid having the overhead of getting it again inside a inner (performance) // Note that this value is also used outside of this method, so we could have had a parameter too, // but I prefer to get it here to avoid inconsitencies int dataLength = data.Length; // Scan column after column int recoveryColumn = 0; for (int dataColumn = 0; dataColumn < dataLength - checksum; dataColumn++) { // recover only the missing column if (data[dataColumn] == null) { int recoveryRowLength = recovery[recoveryColumn].Length; // For each column fill out the output mtx row after row for (int recoveryRow = 0; recoveryRow < recoveryRowLength; recoveryRow += 2) { int imRowIndex = 0; // nnDataColumn - not null data index for (int nnDataColumn = 0; nnDataColumn < dataLength; nnDataColumn++) { // Perform the core operation mult with add in GF16 // TODO: We could crunch the column so we avoid this test if (data[nnDataColumn] != null) { if(recoveryRow < data[nnDataColumn].Length) { UInt16 currentValue = GF16.Multiply(data[nnDataColumn].GetUInt16(recoveryRow), decode[dataColumn, imRowIndex].Value); currentValue = GF16.Add(recovery[recoveryColumn].GetUInt16(recoveryRow), currentValue); recovery[recoveryColumn].SetUInt16(recoveryRow, currentValue); } imRowIndex++; } } } recoveryColumn++; } } }
/// <summary> /// Remove the identity at the front of the matrix /// </summary> /// <param name="matrixIn">The input matrix</param> /// <returns>Stripped Matrix without the identity at the front</returns> private static GF16[,] StripIdentity(GF16[,] matrixIn) { int rowLength = matrixIn.GetLength(1); int columnLength = matrixIn.GetLength(0); GF16[,] matrixOut = new GF16[columnLength - rowLength, rowLength]; for (int row = 0; row < rowLength; row++) for (int column = rowLength; column < columnLength; column++) matrixOut[column - rowLength, row] = matrixIn[column, row]; return matrixOut; }
/// <summary> /// Overload the - operator /// </summary> /// <param name="a">First operande</param> /// <param name="b">Second operande</param> /// <remarks> /// The subtraction is the same as the addition /// because with Galois fields, each number is its /// own negative. So there is no need to have a Sub /// method /// </remarks> /// <returns>Subrtraction of the 2 Galois Fields (XOR)</returns> public static GF16 operator -(GF16 a, GF16 b) { return(GF16.Add(a.Value, b.Value)); }
/// <summary> /// Gauss - Jordan elimination. /// This algorithm takes a matrix and use elementary /// row operations to put the matrix in a row echelon form. This /// means that the result matrix will have the identity matrix at the /// begining. /// This method can be used in 2 specific scenarios: /// - To solve a linear system numerically /// - To invert a matrix /// </summary> /// <example> /// For instance the matrix: /// 0 1 1 1 0 0 /// 0 1 2 0 1 0 /// 1 1 4 0 0 1 /// /// Will become (over int): /// [ ID ] [ rest ] /// 1 0 0 2 -3 1 /// 0 1 0 2 -1 0 /// 0 0 1 -1 1 0 /// </example> /// <remarks> /// For the Reed Solomon encoding, Gauss - Jordan elimination /// is used to invert the decoding matrix. This is needed to /// recover from packet loss. /// Important note: The input is modified /// </remarks> /// <param name="matrixIn">The matrix to transform in a row echelon form</param> /// <returns>The matrix in a row echelon from</returns> private static GF16[,] GaussJordan(GF16[,] matrixIn) { // Matrix size variables int rowLength = matrixIn.GetLength(1); int columnLength = matrixIn.GetLength(0); // The number of pivots needed is the min between row/column // For instance the matrix: // 0 1 1 1 0 0 // 0 1 2 0 1 0 // 1 1 4 0 0 1 // has 3 pivots int pivots = Math.Min(rowLength, columnLength); // Loop through all the pivot columns for (int pivotColumn = 0; pivotColumn < pivots; pivotColumn++) { // TODO: Place Step 1 in a separate method // Step 1: Find the Pivot Row and reduce the pivot to have a factor of 1 int pivotRow = -1; // Loop through all the rows to find a pivot for the pivotColumn for (int row = 0; row < rowLength; row++) { // A pivot must be non-zero... if (matrixIn[pivotColumn, row].Value != 0) { // ...and don't pivot on a row that has previous non-zero column entries // Loop through columns of the row Check is the row found respect the non-zero column rule bool isRowWithPreviousZero = true; for (int column = 0; column < pivotColumn; column++) { // Check for non-zero if (matrixIn[column, row].Value != 0) { // The row is not a pivot because it has previous non-zero entries isRowWithPreviousZero = false; break; } } // If the row found has previous zero, it is a pivot row if (isRowWithPreviousZero) { // The current row is the pivot pivotRow = row; GF16 factorPivot = matrixIn[pivotColumn, row]; // Reduce the pivot row to have a factor of 1 for (int column = pivotColumn; column < columnLength; column++) { matrixIn[column, row] /= factorPivot; } // We can exit the loop through rows in Step 1 break; } } } // Ensure that a pivot has been found if (pivotRow == -1) { throw new ApplicationException("No pivot row found!"); } // TODO: Place Step 2 in a separate method // Step 2: Do elementary operation to all rows (except the pivot row and // the rows that have zero on the pivot column) // The final goal is to have a zero for all rows on the column corresponding to the // pivot column // Loop trough the rows to do elementary operation (subtraction) for (int row = 0; row < rowLength; row++) { // Note: We skip the pivot row and the rows that has zero on the pivot // column (no operation requiered) if ((row != pivotRow) && (matrixIn[pivotColumn, row].Value != 0)) { // Now, determine the factor by which the pivot row must be added to the target // row to cancel out the pivot value GF16 factorOperation = matrixIn[pivotColumn, row]; // Perform the operation for each column of the current row for (int column = 0; column < columnLength; column++) { // Perform a subtraction. The final goal is to have a zero for all rows // on the column corresponding to the pivot column matrixIn[column, row] -= factorOperation * matrixIn[column, pivotRow]; } } } } // At this point the work manipulation is done but we might get the rows // out of order. // For instance, the input matrix: // 0 1 1 1 0 0 // 0 1 2 0 1 0 // 1 1 4 0 0 1 // // will become the following (over int operation): // 0 1 0 2 -1 0 // 0 0 1 -1 1 0 // 1 0 0 2 -3 1 // // so now, we need to swap the row to place them in order to obtain the following: // [ ID ] [ rest ] // 1 0 0 2 -3 1 // 0 1 0 2 -1 0 // 0 0 1 -1 1 0 // TODO: Place Step 3 in a separate method // Step 3: Swap raw into order to have an identity matrix at the front for (int row = 0; row < rowLength; row++) // can use row < rowLength -1, because last row should always be in order? { // Skip rows that are in proper order if (matrixIn[row, row].Value != 1) { // Not in proper order, search down through the matrix bool found = false; for (int searchRow = 0; searchRow < rowLength; searchRow++) { if (matrixIn[row, searchRow].Value == 1) // found the row we're looking for { found = true; // Do the swap operation // Save the target row to temporary swap space first GF16[] swapRowTemp = new GF16[columnLength]; for (int swapRowColumn = 0; swapRowColumn < columnLength; swapRowColumn++) { swapRowTemp[swapRowColumn] = matrixIn[swapRowColumn, row]; } // Replace the target row contents with the found row contents for (int swapRowColumn = 0; swapRowColumn < columnLength; swapRowColumn++) { matrixIn[swapRowColumn, row] = matrixIn[swapRowColumn, searchRow]; } // Replace the found row contents with the temporary swap space (previous target row contents) for (int swapRowColumn = 0; swapRowColumn < columnLength; swapRowColumn++) { matrixIn[swapRowColumn, searchRow] = swapRowTemp[swapRowColumn]; } } } if (!found) { throw new ApplicationException("Swap row not found!"); } } } return(matrixIn); }
/// <summary> /// Overload the * operator /// </summary> /// <param name="a">First operande</param> /// <param name="b">Second operande</param> /// <returns>Multiplication of the 2 Galois Fields</returns> public static GF16 operator *(GF16 a, GF16 b) { return(GF16.Multiply(a.Value, b.Value)); }
/// <summary> /// Overload the / operator /// </summary> /// <param name="a">First operande</param> /// <param name="b">Second operande</param> /// <returns>a div b in Galois Fields</returns> public static GF16 operator /(GF16 a, GF16 b) { return(GF16.Divide(a.Value, b.Value)); }
/// <summary> /// Create the decoding matrix /// </summary> /// <param name="nbDataPackets">Number of data packet</param> /// <param name="nbChecksumPackets">Number of checksum packet</param> /// <param name="rtpPackets">Array of rtpPackets</param> /// <returns>The decoding matrix</returns> /// <example> /// For example, if you have 3 data packets and 2 checksum /// packets and get the last data packet, you will get /// the following matrix /// 0 1 1 /// 0 1 2 /// 1 1 4 /// </example> // TODO: To be more generic, we should use System.Array instead of array of BufferChunks public static GF16[,] DecodingMatrixGeneration(BufferChunk[] dataReceived, int nbChecksumPackets) { // nbTotalPackets is the total # rtpPackets to create (data packets and checksum packets) int nbTotalPackets = dataReceived.Length; int nbDataPackets = nbTotalPackets - nbChecksumPackets; // Let's suppose that we lost the 2 first packets // So [E'] that should have been // 1 0 0 1 1 // 0 1 0 1 2 // 0 0 1 1 4 // is actually // 0 1 1 // 0 1 2 // 1 1 4 // The reverted decoding matrix will be multiplied by data received to get all the datas // for instance [D0..D2] = [D2 C0 C1] x 1/[E'] // => So the number of columns should correspond to the number of data recieved + checksum received // and the number of rows should correspond of the number of data packet K // count the number of packets received int nbPacketsReceived = ReceivedPacketsCount(nbTotalPackets, dataReceived); // nbPacketsReceived columns, K rows // Note that the # columns is smaller than the number of packet received // in case the number of packet lost was smaller than the number than // the number of checksum packets (we short the matrix: It's like if we lost the last // checksum(s) packet(s) GF16[,] mtxEprime_GF16 = new GF16[nbPacketsReceived, nbDataPackets]; // Construct the matrix mtxEprime_GF16 from the rtpPackets received // Note see also Jay's Decode method // Loop trough the data rtpPackets and put a 1 on the row of the packet // received int foundIndex = 0; for (int index = 0; index < nbDataPackets; index++) { if (dataReceived[index] != null) // not a packet lost { // Generate a row in the mtxEprime_GF16 (decode matrix) if (index < nbDataPackets) { // TODO: To be clean, we should have initialized all the other items // on the row explicitely to zero or make sure their are set them to zero // when creating the new matrix mtxEprime_GF16[foundIndex, index] = (UInt16)1; } foundIndex++; // if (foundIndex >= nbPacketsReceived-nbAdditionalPacketsReceived) // return mtxEprime_GF16; } } // Generate a Vandermonde Mtx for the other items // TODO: Change that to use the Vandermonde static mtx for (UInt32 m = 0; m < nbChecksumPackets; m++) { // Note: If a checksum packet has been lost, the column is skiped if (dataReceived[m + nbDataPackets] != null) // not a packet lost { for (UInt32 k = 0; k < nbDataPackets; k++) { // Each element of the Vandermonde matrix is calculated as (m+1)^k over GF16 // mtx[column, row] = (column+1)^row // With Vandermonde: 1^x, 2^x, 3^x, ... the code was: // mtxEprime_GF16[foundIndex, k] = GF16.Power((UInt16)(m+1), (UInt32)k); // I experimented problems (no pivot found during invertion of Eprime) // when losing 3 packets (worked fine when losing 2 packets) // So I changed, to use Vandermonde matrix were mtx[column, row] = (2^column)^row mtxEprime_GF16[foundIndex, k] = GF16.Power((UInt16)GF16.Power(2, (m)), (UInt32)k); // TODO: Check if Jay's approach is more efficient: mtxE_GF16[m, k] = GF16.gf_exp[GF16.Modnn(k*m)]; } foundIndex++; // if (foundIndex >= nbPacketsReceived-nbAdditionalPacketsReceived) // return mtxEprime_GF16; } } // for return(mtxEprime_GF16); }