【问题标题】:Porting MATLAB's Reed Solomon function to Java将 MATLAB 的 Reed Solomon 函数移植到 Java
【发布时间】:2017-07-12 05:21:34
【问题描述】:

我在 MATLAB 中使用 RS(160,80) 实现了一个简单的 RS 纠错方案。基本流程如下:

  1. 我生成一个长度为 80 且每个符号 8 位的消息,并生成一个长度为 160 的 RS 代码。

  2. 生成 RS 码后,我添加/XOR 另一个码长为 160 的 Galois 字段。(该字段仅包含 00000000 和 00000001)。这是为了模拟在方案中添加错误。这会生成我的嘈杂代码。

  3. 1234563我在上一步中将其添加/异或到生成的嘈杂代码中。
  4. 最后,我通过 RS 解码器运行它来检索我的原始消息。

我的 MATLAB 函数:

function RSKeyExchange(dev1, dev2)
    dev1_fp = zeros(1,160);
    dev2_fp = zeros(1,160);

    for i=1:160
        dev1_fp(i) = str2double(dev1.key(i));
        dev2_fp(i) = str2double(dev2.key(i));
    end

    n = 160;        % total code length
    k = 80;         % message length - actual message for syncronisation
    m = 8;          % order (2^m)

    % therefore, RS decoder can detect t = n-k = 80 errors
    % and correct t/2 = 40 errors

    msg = gf(randi([1 255],1 ,80), m);
    code = rsenc(msg, n, k);

    noise_add = gf(dev1_fp, 8);
    noise_remove = gf(dev2_fp, 8);

    noisy_code = code + noise_add;

    % noisy_code is now transmitted from dev1 to dev2 (sender to receiver)

    decommited_code = noisy_code + noise_remove;
    [rxcode, cnumerr] = rsdec(decommited_code, n, k);

    fprintf('Number of errors corrected: %d\n', cnumerr);
end

现在我一直在寻找将其移植到 Java 的方法。我查找了库,但不确定如何准确移植我的特定用例。

  • Zxing - 仅将 QR 码和 Aztec 码作为输入

  • Backblaze - JavaReedSolomon - 修复代码擦除,这不是我产生的那种错误,输入是文件的形式(严重混淆这里发生的事情)

  • Simple RS error correction example - 感觉更清晰一些,但只接受字符串作为输入。我觉得我可以修改它以适合我的用例,但我不确定如何添加噪音。我不知道如何通过此实现生成 RS(160, 80) 代码,我也不知道如何生成自定义 GF 字段以添加噪声。

任何帮助都将不胜感激(尤其是如果您可以指出适合我用例的实现,或者帮助修改上述可行的资源之一)

谢谢!

【问题讨论】:

    标签: java matlab error-correction reed-solomon galois-field


    【解决方案1】:

    我查看了“简单 RS”示例“GF28”Java 代码。解码器似乎只处理擦除(其中一个输入是一组坏索引)。它使用基于十六进制 11B = x^8 + x^4 + x^3 + x + 1 的 GF(256),与 AES 加密相同。这是一个不寻常的选择,因为最低的“原语”是 3(除零以外的所有数字都可以认为是 3 的幂),而不是“原语”为 2 的字段。字段多项式通过 PX 定义,所以它可以很容易地改变。我不确定为什么它动态生成表而不是在初始化期间生成它们,使用第二组真/假表来指示是否生成了特定的表值。

    我有一个用于 8 位 GF(256) 字段的 C RS 演示程序。它是交互式的,你选择一个字段(共有30个),是否使用自倒数多项式(通常不使用),如果生成多项式的第一个连续根为1(如果没有指定,则第一个连续root 是“原语”)、奇偶校验字节数和数据字节数。它同时处理擦除和错误,由于它是一个演示程序,它包括 3 种主要解码器算法类型的代码彼得森矩阵算法、杉山对扩展欧几里得算法和伯莱坎普梅西算法的改编,以及用于生成错误值的 Forney 算法。交互部分可以替换为选择所有这些参数的代码。根据您的情况,将 MAXPAR 的定义从 20 更改为 80(最大奇偶校验数)。用户输入是通过标准输入,因此可以使用文本输入文件来运行演示。

    http://rcgldr.net/misc/eccdemo8.zip

    在我的演示中,要生成代码字,用户输入值(用户选项“E”或“C”),然后输入“N”进行编码。为了产生错误,用户在特定位置输入值。为了生成擦除,用户使用“P”选项在特定位置输入擦除值。 “F”用于修正(修正)码字。

    wiki 文章包含 3 种主要类型的解码器的逻辑。它还解释了傅里叶变换,但傅里叶变换需要使用其他解码器之一来生成误差多项式,并且不实用。

    https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction#Error_correction_algorithms


    根据您的评论,我查看了 Zxing 库。方法描述有点简洁,并且使用了错误的术语。以下是描述的重做:

    GenericGF(primitive, size, b)
        primitive - wrong term, this is the field polynomial
    
        size - The description is "size of the field", but that is
        determined by the polynomial, a n+1 bit polynomial is used
        for an n bit field. My guess is this is the size of the
        generator polynomial, perhaps the number of roots.
    
        b - alpha raised to the b power is the first consecutive root.
        alpha is the primitive for the particular field. Unlike the
        comment, b == 0 is about as common as b == 1.
    

    https://en.wikipedia.org/wiki/Primitive_element_(finite_field)

    encode(int[] toEncode, int ecBytes)
        toEncode - toEncode includes space for the "parity" data used
        to encode the message.
    
        ecByte - the number of "parity" elements at the end of to Encode.
        Why is it named ecBytes when it is an array of ints?
    
    decode(int[] received, int twoS )
        received is an array of concatenated encoded messages?
        Since encode generates a single encoded message, then it would
        make more sense for decode to decode a single encoded message.
    
        twoS - the number of encoded messages?
    
        Based on the references, it is using the Sugiyama adaptation
        of extended Euclid algorithm. A side benefit is that the
        error evaluator polynomial is generated during the process.
    

    包装注释也有错误。 GF(256) 码字的最大大小为 255 字节,因为错误位置被限制在 0 到 254 的范围内。这是因为位置与幂有关,在 GF(256) 中提高到 255 次幂的任何数字都是同一个号码。


    请注意,在不触发库异常的情况下,可能会出现错误更正。但是,如果有 80 个奇偶校验字节,则可能需要 41 个错误。错误纠正将涉及生成一组 40 个位置和值,这些位置和值产生有效码字,但与原始码字相差 81 或更多字节(81 或更多错误)。然而,对于 160 字节的缩短码字,所有 40 个生成的位置都必须在 0 到 159 的范围内。假设计算位置的随机、均匀分布,它们错误纠正的几率是 ((160!) /((160-40)!))/(255^40) != 3.86 × 10^-11 。如果使用全长 (255) 码字或较少数量的奇偶校验,则错误纠正是一个问题。


    我做了一个 java RS ecc GF(256) 例子。它不处理擦除(这需要修改已知擦除位置的校正子,然后将错误定位器与擦除定位器合并)。它使用 Euclid 算法来计算错误定位器和错误评估器多项式。它已被评论,但您可能需要查看 Wiki RS 文章以更好地理解它。该代码通过根据需要使用 byte&0xff 将它们转换为无符号整数来处理 Java 有符号字节。我将参数设置为 80 个消息字节、80 个奇偶校验字节以匹配问题的示例。尽管对于 GF(256),加法和减法都是异或,但代码对加法和减法使用单独的函数,因此代码对应于适用于 GF(p) 的算法,其中 p 是质数,例如 GF( 257)。对于 GF(p),Lambda 的“导数”的计算也会有所不同,这里对此进行了解释:

    https://en.wikipedia.org/wiki/Forney_algorithm#Formal_derivative

    链接到 java rsecc GF(256) 示例:

    http://rcgldr.net/misc/rsecc8.zip

    交互式 C 版本更易于理解,因为它是交互式的,显示一些内部计算(可以更改定义以启用/禁用显示的内容),并且用户可以尝试不同的变体。

    【讨论】:

    • 嘿,谢谢!虽然我使用 Zxing 库解决了我的问题(找到了一个可以完成大量繁重工作的包装器),但您的资源非常有用。
    • @doberoi96 - 我用 cmets 更新了关于 Zxing 库的答案。
    • 哇,感谢您的描述+更正。描述基本上是我一直需要的。关于错误更正,当我选择 40 个错误作为阈值时,我是安全的。这实际上是一种加密方案,不正确的身份验证尝试会产生更多的错误,大约 70 个错误,而正确的错误则保持在 30 个以下。因此,出于所有实际目的,它应该这样做。但说真的,谢谢,很好的收获。
    • @doberoi96 - 我添加了一个指向 java RS ecc 测试程序的链接,与问题中的类似。
    • 干杯!你帮了大忙。 :-)
    【解决方案2】:

    好的,所以我能够解决我的问题。我妥协了一点,所以它不是一个确切的端口,而且被黑了。无论哪种方式,它都适用于我的用例。

    我为 Zxing 找到了一个 wrapper library,它完成了相当多的繁重工作。

    包装器使用GenericGF.QR_CODE_FIELD_256,而不是像我的 MATLAB 程序那样使用 GF(8),它使用 GF(256) 进行编码。

    这是基本上实现相同目的的java实现。 (MsgKeyPair 只是一个包含两个字符串的数据结构)

    public MsgKeyPair generateKey(String fingerprint){
        EncoderDecoder encoderDecoder = new EncoderDecoder();
    
        try {
            String message = generateMessage();
            byte[] data = message.getBytes();
    
            byte[] encodedData = encoderDecoder.encodeData(data, 80);
            byte[] noisyData = encodedData.clone();
    
            for(int i=0; i<encodedData.length; i++){
                byte one = 0xff & 00000001;
                if(fingerprint.charAt(i) == '1') {
                    int oneInt = (int) one;
                    int edInt = (int)encodedData[i];
                    int xor = oneInt ^ edInt;
    
                    noisyData[i] = (byte)(0xff & xor);
                }
            }
    
            String keyToSend = new String(Base64.encode(noisyData, Base64.DEFAULT));
            return new MsgKeyPair(message, keyToSend);
        }
        catch (Exception e){
            Log.e(TAG, "generateKey: ", e);
        }
    
        return new MsgKeyPair("", "");
    }
    
    public String KeyDecoder(String fingerprint, String key) throws Exception{
        byte[] noisyData = Base64.decode(key, Base64.DEFAULT);
    
        byte[] decomCode = noisyData.clone();
        for(int i=0; i<noisyData.length; i++){
            byte one = 0xff & 00000001;
            if(fingerprint.charAt(i) == '1'){
                int oneInt = (int)one;
                int ndInt = (int) noisyData[i];
                int xor = oneInt ^ ndInt;
    
                decomCode[i] = (byte)(0xff & xor);
            }
        }
    
        EncoderDecoder encoderDecoder = new EncoderDecoder();
    
        byte[] decodedData = encoderDecoder.decodeData(decomCode, 80);
        String originalMessage = new String(decodedData);
    
        return originalMessage;
    }
    
    public static String generateMessage(){
        String msg = "";
    
        for(int i=0; i<80; i++){
            int bit = Math.round(randomWithRange(0,1));
            msg = msg + String.valueOf(bit);
        }
    
        return msg;
    }
    
    private static int randomWithRange(int min, int max)
    {
        int range = (max - min) + 1;
        return (int)(Math.random() * range) + min;
    }
    

    【讨论】:

    • 我不太关注这个。为什么叫 Base64?编码数据的大小是 160 字节(80 个数据字节,80 个“奇偶校验”(ecc)字节)吗?此代码示例中未包含某些函数。我假设某处存在从 byte[] 到 Zxing 库使用的 int[] 的转换?
    • 如果有兴趣,我可以将我的 RS 演示代码的子集移植到 Java 中,只包括编码器和解码器算法之一,可能是扩展的欧几里德算法,因为它也会生成错误值多项式。
    • @rcgldr - 抱歉,应该详细说明。 1) Base64 位与 RS 编码/解码没有任何关系。在从第一个函数传递它之前,我只是将我的字节数组转换为字符串。 2) 是的,encodedData 是 160 字节。 3)我不确定,真的。我基本上围绕我在答案中链接的包装器工作。关于港口,如果你能这样做,那就太好了!我正要获得错误值,但决定稍后再处理。如果您能移植一个子集,我将不胜感激。