Po drátě 4: Řešení úlohy č. 13

Zase jednou značně self-referenční úloha. Program na zabezpečení Céčkového zdrojáku proti chybám při přenosu přidáním jakýchsi kontrolních součtů na konce řádků. Ovšem na programu se už vyřádil nějaký šotek a spoustu chyb v něm natropil. Některé zjevné, jiné, jak se brzy ukáže, značně nenápadné.

Úlohu bylo možné řešit zkusmo – měnit podezřelé kousky řádků tak dlouho, než budou kontrolní součty sedět, ale jednak to v těch nejzajímavějších místech jde dost ztuha, jednak jsou překlepy i v kontrolních součtech (jak napovídá třeba přítomnost "n" v jinak poklidně hexadecimálním čísle).

Proto je lepší zamyslet se nad tím, jak kód funguje. Zabezpečuje jednotlivé řádky, ale počítá se 8-bitovýmy xory, takže chrání zvlášť všechny nulté bity bajtů, zvlášť všechny první atd. Uvažujme o něm tedy jako o kódu, který doplní 57 datových bitů o 6 kontrolních, spočítaných následovně: Očíslujeme si oněch 57 datových bitů 6-bitovými čísly, ve kterých jsou alespoň dvě jedničky (prvních pár bitů tedy dostane čísla 000011, 000101, 000110 a 000111). Potom i-tý z kontrolních bitů nastavíme na xor všech datových bitů, jejichž pořadové číslo má na i-tém místě jedničku.

Všimněte si, že takový kód dokáže opravit libovolnou jednobitovou chybu: Pokud nastane v datové části, překlopí se alespoň dva kontrolní bity (protože jsme používali jen čísla bitů, ve kterých jsou aspoň dvě jedničky) a podle toho, které, ihned poznáme, o který datový bit se jednalo. Pokud naopak nastane chyba v kontrolním bitu, hned víme, že se tak stalo, protože nesedí právě jeden kontrolní bit.

Náš kód je tedy schopen na každé z osmi bitových pozic v bajtu opravit jednu chybu na řádku. Na některých řádcích se tyto chyby soustředily do jediného bajtu, na jiných zase byla každá pozice špatně v jiném znaku. Chyby, které kód neumí opravit, každopádně šotek nenapáchal.

Na dekódování si můžeme napsat třeba následující program:

#include <stdio.h>
#include <string.h>

#define Z 6
#define L ((1<<Z)-Z-1)

int main(int argc, char **argv)
{
  char line[256];
  int checks[7];

  int verb = (argc > 1);

  while (fgets(line, sizeof(line), stdin))
    {
      int l = strlen(line);
      if (l && line[l-1] == '\n')
        l--;
      if (l != L+3+2*Z || memcmp(line+L, "// ", 3))
        {
          fprintf(stderr, "Syntax error: %s\n", line);
          return 1;
        }
      for (int k=0; k<Z; k++)
        sscanf(line+L+3+2*k, "%2x", &checks[k]);
      for (int i=0, j=0; i < (1<<Z); i++)
        if (i & (i-1))
          {
            for (int k=0; k<Z; k++)
              if (i & (1<<k))
                checks[k] ^= line[j];
            j++;
          }
      int e = 0, fixes=0;
      for (int k=0; k<Z; k++)
        e |= checks[k];
      if (e)
        for (int b=1; b<256; b+=b)
          {
            int p = 0;
            for (int k=0; k<Z; k++)
              if (checks[k] & b)
                p |= (1<<k);
            for (int i=0, j=0; i < (1<<Z); i++)
              if (i & (i-1))
                {
                  if (i==p)
                    {
                      line[j] ^= b;
                      fixes++;
                    }
                  j++;
                }
          }
      l = L;
      while (l>0 && line[l-1] == ' ')
        l--;
      line[l] = 0;
      if (verb)
        {
          if (fixes)
            printf("C%d  ", fixes);
          else if (e)
            printf("XX  ");
          else
            printf("..  ");
        }
      puts(line);
    }
  return 0;
}

Opravený zdroják pak vyjde takto (ten šotek je ale potvora!):

/*
 *  An error correction code capable of correcting
 *  single-bit errors.
 *
 *  (c) 2009 SoTec s.r.o.
 */

#include <stdio.h>
#include <string.h>
#include <inttypes.h>

int main(void)
{
  char x[256];
  unsigned char ch[6], pw[6] = { };
  while (fgets(x, sizeof(x), stdin))
    {
      int l = strlen(x);
      if (l && x[l-1] == '\n')
        l--;
      while (l < 57)
        x[l++] = ' ';
      l = 57;
      memset(ch, 0, sizeof(ch));
      for (int i=0, j=0; i < (1<<6); i++)
        if (i & (i-1))
          {
            for (int k=0; k<6; k++)
              if (i & (1<<k))
                ch[k] ^= x[j];
            j++;
          }
      l += sprintf(x+l, "// ");
      for (int k=0; k<6; k++)
        {
          l += sprintf(x+l, "%02x", ch[k]);
          pw[k] -= ch[k];
        }
      puts(x);
    }
  uint32_t speed_of_light = 299780450;
  uint32_t number = 0062660730;
  for (int k=0; k<6; k++) {
    number += pw[k];
  number *= speed_of_light; }
  if (number % 1001 == 997)
    fprintf(stderr, "Password: %08X\n", number);
  else
    fprintf(stderr, "This is not the right file.\n");
  return 0;
}

Komentář

Úlohy, ve kterých je potřeba si program nejdříve opravit, patří k podrátí klasice, ovšem opravovat namísto hloupých programátorských chyb škodolibé zásahy do zdrojáku leží přeci jenom o level výše :-)

Způsob kódování jsme úmyslně vybrali jednoduchý (kdo se už s teorií samoopravných kódů někdy potkal, jistě poznal Hammingův kód), i tak se s ním dá užít spousta legrace bez přemíry matematiky.

Menší oříšek představovalo zabudování kontroly konsistence hesla do programu – pokud přidáte kontrolu, samozřejmě se změní kontrolní součty a tím i heslo, takže musíte upravit kontrolu, tím se změní heslo, a tak dále. Jak z toho ven?

Úlohu s vydatnou pomocí všech šotků z okolí napsal a opravil Martin Mareš.

Re: Po drátě 4: Řešení úlohy č. 13 thefox (18. 11. 2009 - 18:04) Sbalit(2)
Argh, tak toto bolo dosť nefér, dať tri chyby do jedného riadku tak, že sa dajú "opraviť" rôznymi spôsobmi:

diff vystup.moj.txt vystup.ofic.txt
41c41
< uint32_t speed_of_light = 289791451; // 22437b6d303a
---
> uint32_t speed_of_light = 299780450; // 22437b6d303a
Re: Po drátě 4: Řešení úlohy č. 13 Martin Mares (org) (18. 11. 2009 - 18:06) Sbalit(1)
Jak už jsem psal, kód zaručuje opravu jednobitové chyby na každé bitové
pozici v řádku a takové řešení skutečně bylo jenom jediné.