Base64 kódolás

Használható segédanyagok: Haskell könyvtárainak dokumentációja, (lokális!) Hoogle, a tárgy honlapja és a BE-AD rendszerbe feltöltött beadandók. Ha bármilyen kérdés, észrevétel felmerül, azt a felügyelőknek kell jelezni, nem a diáktársaknak!

A feladatok egymásra épülnek ezért érdemes ezeket a megadásuk sorrendjében megoldani, de legalább megérteni az aktuális feladatot megelőző feladatokat! A függvények definíciójában lehet, sőt javasolt is alkalmazni a korábban definiált függvényeket (függetlenül attól, hogy sikerült-e azokat megadni).

Tekintve, hogy a tesztesetek, bár odafigyelés mellett íródnak, nem fedik le minden esetben a függvény teljes működését, határozottan javasolt még külön próbálgatni a megoldásokat beadás előtt, vagy megkérdezni a felügyelőket!

Részpontszámokat csak az elégséges szint elérése után lehet kapni!

A feladat összefoglaló leírása

A Base64 egy 64 karakterből álló ábécén alapuló kódolási forma, amely segítségével tetszőleges adatot tudunk szöveges formában tárolni.

A feladatban egy egyszerűsített, tetszőleges (Unicode) karakterekből álló szöveget fogunk ábrázolni 64 előre megadott karakter segítségével. Természetesen a végén megadjuk az eredeti formára való visszaalakításért felelős műveletet is.

A kódoláshoz egy szótárat vezetünk be. A szótár egy rendezett párokból, mint kulcs-érték párokból, álló lista.

type Entry = (Int, Char)
type Dictionary = [Entry]

(A type kulcsszó segítségével az Entry típus az (Int, Char) típus egy másik neve lesz, illetve a Dictionary típus az Entry típusból alkotott listákat jelenteni (ez lesz a szótárunk típusa), hasonlóan a String és [Char] viszonyához. A programban ez semmilyen további megszorítást nem indukál, csupán a beszédesebb függvénytípusok kialakításában segít.)

A szótár megadása (1 pont)

Mint azt már a bevezetőben említettük, a szótár 64 bejegyzést fog tartalmazni, amelyek az alábbiak:

Adjuk meg azt a függvényt, amely a fentebb megadott karakterek segítségével előállítja a szótárat!

Megjegyzés. A szótárban nullától kezdődő természetes számokkal (vagyis: 0,1,2…) indexeljük ezeket az elemeket. Törekedjünk a lehető legáltalánosabb megoldás megadására!

dictionary :: Dictionary

Test>
(0, 'A') :: Entry
Test>
(1, 'B') :: Entry
Test>
(25, 'Z') :: Entry
Test>
(26, 'a') :: Entry
Test>
(51, 'z') :: Entry
Test>
(52, '0') :: Entry
Test>
(62, '+') :: Entry
Test>
(63, '/') :: Entry
Test>
64 :: Int

Listák kiegészítése adott méretűre (2 pont)

Adjuk meg azt a magasabbrendű függvényt, amely segítségével egy sorozatot ki tudunk egészíteni megadott hosszúságúra, amikor arra szükség van!

pad :: ([a] -> [a] -> [a]) -> a -> Int -> [a] -> [a]

Test>
"aaaabbb" :: [Char]
Test>
"aaaaabbb" :: [Char]
Test>
"bbbaaaaa" :: [Char]
Test>
"bbb" :: [Char]
Test>
"bbb" :: [Char]
Test>
"aaabbaaa" :: [Char]

A fenti függvény segítségével megadjuk a jobbról és balról kiegészítő függvényeket, amelyekre a későbbiekben egyébként szükségünk is lesz.

padLeft  = pad (\p -> (p ++))
padRight = pad (\p -> (++ p))

A könnyebb olvashatóság kedvéért bevezetünk egy szinomimát a típusra, amely ezzel a BitString-et definiálja mint számokból álló listát.

type BitString = [Int]

Szám bitek sorozatává alakítása (1 pont)

Adjuk meg azt a függvényt, amely bitek (0 és 1 értékek) sorozatává alakít egy tetszőleges nemnegatív egész számot!

toBitString :: Int -> BitString

Test>
[] :: BitString
Test>
[1] :: BitString
Test>
[1, 0, 1, 0] :: BitString
Test>
[1, 1, 0, 0, 1, 0, 0] :: BitString

Bitek sorozata számmá alakítása (1 pont)

Adjuk meg azt a függvényt, amely bitek sorozatából előállja annak tízes számrendszerbeli megfelelőjét!

fromBitString :: BitString -> Int

Test>
0 :: Int
Test>
20 :: Int
Test>
22 :: Int

Szöveg bájtsorozattá alakítása (1 pont)

Definiáljuk azt a függvényt, amely egy szöveget bájtok sorozatává alakít!

Megjegyzés. A szöveg elemeit előbb a megfelelő karakter kóddá kell alakítani (ord), majd azokból bájtokat készíteni. Ne feledjük, hogy 8 bit az 1 bájt! Azaz, ha ennél rövidebb bitsorozatot kapunk, akkor azt egyenként ki kell egészíteni megfelelő hosszúságúra!

toBinary :: String -> BitString

Test>
[0, 1, 1, 1, 0, 0, 1, 1] :: BitString
Test>
[0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1] :: BitString

Listák felszeletelése (1 pont)

Adjuk meg azt a függvényt, amely egy listát a kért hosszúságú darabokra szabdal!

Megjegyzés. Ha a megadott méret nem pozitív szám, akkor az error függvény segítségével adjunk erről hibajelzést!

chunksOf :: Int -> [a] -> [[a]]

Test>
[] :: [[()]]
Test>
[[1, 2, 3, 4, 5, 6], [7, 8, 9, 10]] :: [[Integer]]
Test>
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15]] :: [[Integer]]
Test>
⊥₁ :: [[()]]
⊥₁: chunksOf: Invalid chunk length
CallStack (from HasCallStack):
  error, called at ./Base64.lhs:186:27 in main:Base64
Test>
⊥₁ :: [[()]]
⊥₁: chunksOf: Invalid chunk length
CallStack (from HasCallStack):
  error, called at ./Base64.lhs:186:27 in main:Base64

Elem keresése (1 pont)

Definiáljuk azt a függvényt, amelyik megadja az adott tulajdonságot teljesítő elemek közül az elsőt!

Megjegyzés. Ha a listában nincs a feltételnek megfelelő elem, akkor az error függvény segítségével adjunk erről hibajelzést!

findFirst :: (a -> Bool) -> [a] -> a

Test>
1 :: Integer
Test>
5 :: Integer
Test>
⊥₁ :: Integer
⊥₁: findFirst: No such entry
CallStack (from HasCallStack):
  error, called at ./Base64.lhs:207:20 in main:Base64
Test>
⊥₁ :: Integer
⊥₁: findFirst: No such entry
CallStack (from HasCallStack):
  error, called at ./Base64.lhs:207:20 in main:Base64

Keresés a szótárban (1 pont)

Adjuk meg azt a függvényt, amely megadja a számnak megfelelő karaktert a szótárból!

Megjegyzés. Ne feledjük, hogy a szótárban nem BitString értékeket társítottunk a karakterekhez, a BitString-et előbb át kell alakítani tízes számrendszerbeli számmá.

findChar :: Dictionary -> BitString -> Char

Test>
'A' :: Char
Test>
'E' :: Char
Test>
'8' :: Char
Test>
'/' :: Char
Test>
⊥₁ :: Char
⊥₁: findFirst: No such entry
CallStack (from HasCallStack):
  error, called at ./Base64.lhs:207:20 in main:Base64

Szöveg fordítása (2 pont)

Adjuk meg azt a függvényt, amely egy tetszőleges szöveget átalakít Base64 formátumú ábrázolásra!

Megjegyzés. Az átalakító algoritmus részletes ismertetése lentebb található.

translate :: Dictionary -> String -> String

Test>
"TWFu" :: String
Test>
"cGxlYXN1cmUu" :: String
Test>
"cGxlYXN1cmU" :: String
Test>
"cGxlYXN1cg" :: String
Test>
"cA" :: String
Test>
[] :: String

Az algoritmus szemléltetése:

Base643737a371c1ad863ea107495bed121fe3.png

Az algoritmus lépései:

Szabványos kód előállítása (2 pont)

Adjuk meg azt a függvényt, amely az előző függvény (translate) felhasználásával szabványos Base64 formátumú szöveget állít elő!

Az átfordítás eredménye akkor tekinthető szabványosnak, ha az elemszáma 4-gyel osztható. Ha a translate függvény eredménye nem ilyen, akkor a szöveget ki kell bővíteni jobbról '=' karakterekkel, hogy ez teljesüljön.

encode :: Dictionary -> String -> String

Test>
"U2F2ZSBvdXIgc291bHMh" :: String
Test>
"U2F2ZSBvdXIgc291bHM=" :: String
Test>
"U2F2ZSBvdXIgc291bA==" :: String
Test>
"U2F2ZSBvdXIgc291" :: String

Keresés a szótárban (2) (1 pont)

Adjuk meg azt a függvényt, amely megadja az adott karakterhez tartozó szótárbeli indexet bináris formában!

findCode :: Dictionary -> Char -> BitString

Test>
[1, 1, 0, 1, 0] :: BitString
Test>
[1, 1, 0, 0, 1] :: BitString
Test>
[1, 1, 1, 1, 0, 1] :: BitString

Szöveg visszafejtése (2 pont)

Definiáljuk azt a függvényt, amely a Base64 formátumban megadott szöveget visszaalakítja hagyományos Unicode (UTF-8) kódolásra!

Megjegyzés. Ne feledkezzünk meg a visszafejtés előtt arról, hogy a szöveg '=' karaktereket tartalmazhat (a szabványosítás miatt). Ezekre viszont nincs szükség a visszakódolás során.

decode :: Dictionary -> String -> String

Test>
"Save Our Souls!" :: String
Test>
"Save Our Souls" :: String
Test>
"Save Our Soul" :: String

Pontozás (elmélet + gyakorlat)