{-# OPTIONS_HADDOCK show-extensions #-} {-| Module : Crypto.Enigma.Display Description : Display of Enigma machine state and encoding. License : BSD3 Maintainer : royl@aldaron.com Stability : experimental Portability : POSIX A module for rich display of the state state of and encoding performed by Enigma machines defined in "Crypto.Enigma". -} module Crypto.Enigma.Display ( -- * Configuration display showEnigmaConfig, showEnigmaConfigInternal, -- * Operation display showEnigmaOperation, showEnigmaOperationInternal, -- * Encoding display showEnigmaEncoding ) where import Control.Applicative import Data.Monoid import Data.Char import Data.List import Data.List.Split (chunksOf) import Data.String.Utils (replace) import Text.Printf (printf) import Crypto.Enigma.Utils import Crypto.Enigma {-# ANN module ("HLint: ignore Use infix"::String) #-} {-# ANN module ("HLint: ignore Use mappend"::String) #-} {-# ANN module ("HLint: error Redundant $"::String) #-} {-# ANN module ("HLint: ignore Use ."::String) #-} -- TBD - Fix name of more detaild display -> ..EnigmConfigSchematic ? <<< -- REV - Final newline in show... functions is a bit inconsistent -- Helpers =================================================================== -- Message entry ------------------------------------------------------------- -- Some standard substitions performed by (Kriegsmarine) operators preproc :: String -> Message preproc s = filter (`elem` ['A'..'Z']) $ foldl1 fmap (uncurry replace <$> subs) $ toUpper <$> s where subs = [(" ",""),(".","X"),(",","Y"),("'","J"),(">","J"),("<","J"),("!","X"), ("?","UD"),("-","YY"),(":","XX"),("(","KK"),(")","KK"), ("1","YQ"),("2","YW"),("3","YE"),("4","YR"),("5","YT"), ("6","YZ"),("7","YU"),("8","YI"),("9","YO"),("0","YP")] -- Message display ----------------------------------------------------------- -- TBD - Don't remove spaces (at least in showEnigmaOperation and instead put a blank line?) -- Standard formatting of encoded messages postproc :: String -> String postproc = unlines . chunksOf 60 . unwords . chunksOf 4 -- Mapping markup ----------------------------------------------------------- -- TBD Move up (closer to encoding?) -- TBD - Can't use below unless encode handles ch == ' ' -- locate the index of the encoding with enc of ch, in s locCar :: Char -> String -> Mapping -> Maybe Int locCar ch s enc = elemIndex (encode enc ch) s decorate :: Char -> String decorate ch = ch:"\818\773" --decorate ch = ['[',ch,']'] -- version that works when Unicode fails to display properly (e.g. IHaskell as of 0.7.1.0) markedMapping :: Maybe Int -> Mapping -> String markedMapping (Just loc) e = take loc <> decorate.(!!loc) <> drop (loc + 1) $ e markedMapping Nothing e = e -- Machine operation display ================================================= -- Preprocess a message and produce a configuration display for the starting configuraiton -- and for each character of the message, using the provied configuration display function. showEnigmaOperation_ :: (EnigmaConfig -> Char -> String) -> EnigmaConfig -> String -> String showEnigmaOperation_ df ec msg = unlines $ zipWith df (iterate step ec) (' ':(preproc msg)) -- Configuration display ----------------------------------------------------- -- | Display a summary of the Enigma machine configuration as its encoding (see 'Mapping'), -- the letters at the windows (see 'windows'), and the 'Position's of the rotors (see 'positions'). -- -- If a valid 'Message' character is provided, indicate that as input and mark the encoded letter. -- -- For example, #showEnigmaConfigEG# -- -- >>> putStr $ showEnigmaConfig (configEnigma "b-γ-V-VIII-II" "LFAQ" "UX.MO.KZ.AY.EF.PL" "03.17.04.11") 'K' -- K > CMAWFEKLNVG̲̅HBIUYTXZQOJDRPS LFAQ 10 16 24 07 -- -- shows the process of encoding of the letter __@\'K\'@__ to __@\'G\'@__. showEnigmaConfig :: EnigmaConfig -> Char -> String showEnigmaConfig ec ch = fmt ch' (markedMapping (locCar ch' enc enc) enc) (windows ec) (reverse $ tail.init $ positions ec) where ch' = if ch `elem` letters then ch else ' ' enc = enigmaMapping ec fmt ch e ws ps = lbl ++ " " ++ e ++ " " ++ ws ++ " "++ ps' where lbl = if ch == ' ' then " " else ch:" >" ps' = unwords $ (printf "%02d") <$> ps -- TBD - Add figure from MMA tool showing mapping -- | Display a summary of the Enigma machine configuration as a schematic showing the encoding (see 'Mapping') -- performed by each stage (see 'stageMappingList'), along with an indication of the stage -- (rotor number, @\"P\"@ for plugboard, or @\"R\"@ for reflector), window letter (see 'windows'), -- 'Position' (see 'positions') and 'Name', -- followed by the encoding for the machine, and preceeded by a (trivial, no-op) keyboard \"encoding\" -- for reference. -- -- If a valid 'Message' character is provided, indicate that as input and mark the letter it is encoded to at -- each stage; mark its encoding as output. -- -- For example, #showEnigmaConfigInternalEG# -- -- >>> putStr $ showEnigmaConfigInternal (configEnigma "b-γ-V-VIII-II" "LFAQ" "UX.MO.KZ.AY.EF.PL" "03.17.04.11") 'K' -- K > ABCDEFGHIJK̲̅LMNOPQRSTUVWXYZ -- P YBCDFEGHIJZ̲̅PONMLQRSTXVWUAK UX.MO.KZ.AY.EF.PL -- 1 LORVFBQNGWKATHJSZPIYUDXEMC̲̅ Q 07 II -- 2 BJY̲̅INTKWOARFEMVSGCUDPHZQLX A 24 VIII -- 3 ILHXUBZQPNVGKMCRTEJFADOYS̲̅W F 16 V -- 4 YDSKZPTNCHGQOMXAUWJ̲̅FBRELVI L 10 γ -- R ENKQAUYWJI̲̅COPBLMDXZVFTHRGS b -- 4 PUIBWTKJZ̲̅SDXNHMFLVCGQYROAE γ -- 3 UFOVRTLCASMBNJWIHPYQEKZDXG̲̅ V -- 2 JARTMLQ̲̅VDBGYNEIUXKPFSOHZCW VIII -- 1 LFZVXEINSOKAYHBRG̲̅CPMUDJWTQ II -- P YBCDFEG̲̅HIJZPONMLQRSTXVWUAK UX.MO.KZ.AY.EF.PL -- G < CMAWFEKLNVG̲̅HBIUYTXZQOJDRPS -- -- shows the process of encoding of the letter __@\'K\'@__ to __@\'G\'@__: -- -- * __@\'K\'@__ is entered at the keyboard, which is then -- * encoded by the plugboard (@\'P\'@), which includes @\"KZ\"@ in its specification (see 'Name'), -- to __@\'Z\'@__, which is then -- * encoded by the first rotor (@\'1\'@), a @\"II\"@ rotor in the @06@ position (and @\'Q\'@ at the window), -- to __@\'C\'@__, which is then -- * encoded by the second rotor (@\'2\'@), a @\"VIII\"@ rotor in the @24@ position (and @\'A\'@ at the window), -- to __@\'Y\'@__, which is then -- * encoded by the third rotor (@\'3\'@), a @\"V\"@ rotor in the @16@ position (and @\'F\'@ at the window), -- to __@\'S\'@__, which is then -- * encoded by the fourth rotor (@\'4\'@), a @\"γ\"@ rotor in the @10@ position (and @\'L\'@ at the window), -- to __@\'J\'@__, which is then -- * encoded by the reflector rotor (@\'U\'@), a @\"b\"@ reflector, -- to __@\'I\'@__, which reverses the signal sending it back through the rotors, where it is then -- * encoded in reverse by the fourth rotor (@\'4\'@), -- to __@\'Z\'@__, which is then -- * encoded in reverse by the third rotor (@\'3\'@), -- to __@\'G\'@__, which is then -- * encoded in reverse by the second rotor (@\'2\'@), -- to __@\'Q\'@__, which is then -- * encoded in reverse by the first rotor (@\'1\'@), -- to __@\'G\'@__, which is then -- * encoded left unchainged by the plugboard (@\'P\'@), and finally -- * displayed as __@\'G\'@__ -- -- Note that (as follows from 'Mapping') the position of the marked letter at each stage is the alphabetic position -- of the marked letter at the previous stage. showEnigmaConfigInternal :: EnigmaConfig -> Char -> String showEnigmaConfigInternal ec ch = unlines $ [fmt (if ch' == ' ' then "" else ch':" >") (markedMapping (head charLocs) letters) ' ' 0 ""] ++ (zipWith5 fmt (init <> reverse $ ["P"] ++ (show <$> (tail.init $ stages ec)) ++ ["R"]) (zipWith markedMapping (tail.init $ charLocs) (stageMappingList ec)) (" " ++ (reverse $ windows ec) ++ replicate (length $ positions ec) ' ') ([0] ++ ((tail.init $ positions ec)) ++ replicate (length $ positions ec) 0 ) (components ec ++ (tail $ reverse $ components ec)) ) ++ [fmt (if ch' == ' ' then "" else (encode (enigmaMapping ec) ch'):" <") (markedMapping (last charLocs) (enigmaMapping ec)) ' ' 0 ""] where ch' = if ch `elem` letters then ch else ' ' charLocs = zipWith (locCar ch') ([letters] ++ stageMappingList ec ++ [enigmaMapping ec]) ([letters] ++ enigmaMappingList ec ++ [enigmaMapping ec]) fmt l e w p n = lbl ++ " " ++ e ++ " " ++ (w:[]) ++ " " ++ p' ++ " " ++ n where lbl = printf "%3.3s" l :: String p' = if p == 0 then " " else printf "%02d" (p::Int) -- Operation display --------------------------------------------------------- -- | Show a summary of an Enigma machine configuration (see 'showEnigmaConfig') -- and for each subsequent configuration as it processes each letter of a message. #showEnigmaOperationEG# -- -- >>> putStr $ showEnigmaOperation (configEnigma "b-γ-V-VIII-II" "LFAP" "UX.MO.KZ.AY.EF.PL" "03.17.04.11") "KRIEG" -- OHNKJYSBTEDMLCARWPGIXZQUFV LFAP 10 16 24 06 -- K > CMAWFEKLNVG̲̅HBIUYTXZQOJDRPS LFAQ 10 16 24 07 -- R > HXETCUMASQNZGKRYJO̲̅IDFWVBPL LFAR 10 16 24 08 -- I > FGRJUABYW̲̅DZSXVQTOCLPENIMHK LFAS 10 16 24 09 -- E > SJWYN̲̅UZPQBVXRETHIMAOFKCLDG LFAT 10 16 24 10 -- G > EOKPAQW̲̅JLHCISTBDFVMNXRGUZY LFAU 10 16 24 11 -- -- Note that the first line of the display represents the initial configuration of the machine, but does not -- perform any encoding (as explained in 'step'). -- Note also that the second line of this display is the same as one displayed in the examle for 'showEnigmaConfig'. showEnigmaOperation :: EnigmaConfig -> String -> String showEnigmaOperation ec msg = showEnigmaOperation_ showEnigmaConfig ec msg -- | Show a schematic of an Enigma machine's internal configuration (see 'showEnigmaConfigInternal') -- and for each subsequent configuration as it processes each letter of a message. -- -- >>> putStr $ showEnigmaOperationInternal (configEnigma "b-γ-V-VIII-II" "LFAP" "UX.MO.KZ.AY.EF.PL" "03.17.04.11") "KR" -- ABCDEFGHIJKLMNOPQRSTUVWXYZ -- P YBCDFEGHIJZPONMLQRSTXVWUAK UX.MO.KZ.AY.EF.PL -- 1 DMPSWGCROHXLBUIKTAQJZVEYFN P 06 II -- 2 BJYINTKWOARFEMVSGCUDPHZQLX A 24 VIII -- 3 ILHXUBZQPNVGKMCRTEJFADOYSW F 16 V -- 4 YDSKZPTNCHGQOMXAUWJFBRELVI L 10 γ -- R ENKQAUYWJICOPBLMDXZVFTHRGS b -- 4 PUIBWTKJZSDXNHMFLVCGQYROAE γ -- 3 UFOVRTLCASMBNJWIHPYQEKZDXG V -- 2 JARTMLQVDBGYNEIUXKPFSOHZCW VIII -- 1 RMGAWYFJOTPLBZICSHDQNVEKXU II -- P YBCDFEGHIJZPONMLQRSTXVWUAK UX.MO.KZ.AY.EF.PL -- OHNKJYSBTEDMLCARWPGIXZQUFV -- -- K > ABCDEFGHIJK̲̅LMNOPQRSTUVWXYZ -- P YBCDFEGHIJZ̲̅PONMLQRSTXVWUAK UX.MO.KZ.AY.EF.PL -- 1 LORVFBQNGWKATHJSZPIYUDXEMC̲̅ Q 07 II -- 2 BJY̲̅INTKWOARFEMVSGCUDPHZQLX A 24 VIII -- 3 ILHXUBZQPNVGKMCRTEJFADOYS̲̅W F 16 V -- 4 YDSKZPTNCHGQOMXAUWJ̲̅FBRELVI L 10 γ -- R ENKQAUYWJI̲̅COPBLMDXZVFTHRGS b -- 4 PUIBWTKJZ̲̅SDXNHMFLVCGQYROAE γ -- 3 UFOVRTLCASMBNJWIHPYQEKZDXG̲̅ V -- 2 JARTMLQ̲̅VDBGYNEIUXKPFSOHZCW VIII -- 1 LFZVXEINSOKAYHBRG̲̅CPMUDJWTQ II -- P YBCDFEG̲̅HIJZPONMLQRSTXVWUAK UX.MO.KZ.AY.EF.PL -- G < CMAWFEKLNVG̲̅HBIUYTXZQOJDRPS -- -- R > ABCDEFGHIJKLMNOPQR̲̅STUVWXYZ -- P YBCDFEGHIJZPONMLQR̲̅STXVWUAK UX.MO.KZ.AY.EF.PL -- 1 NQUEAPMFVJZSGIRYOH̲̅XTCWDLBK R 08 II -- 2 BJYINTKW̲̅OARFEMVSGCUDPHZQLX A 24 VIII -- 3 ILHXUBZQPNVGKMCRTEJFADO̲̅YSW F 16 V -- 4 YDSKZPTNCHGQOMX̲̅AUWJFBRELVI L 10 γ -- R ENKQAUYWJICOPBLMDXZVFTHR̲̅GS b -- 4 PUIBWTKJZSDXNHMFLV̲̅CGQYROAE γ -- 3 UFOVRTLCASMBNJWIHPYQEK̲̅ZDXG V -- 2 JARTMLQVDBG̲̅YNEIUXKPFSOHZCW VIII -- 1 EYUWDHM̲̅RNJZXGAQFBOLTCIVSPK II -- P YBCDFEGHIJZPO̲̅NMLQRSTXVWUAK UX.MO.KZ.AY.EF.PL -- O < HXETCUMASQNZGKRYJO̲̅IDFWVBPL -- -- Note that the first block of the display represents the initial configuration of the machine, but does not -- perform any encoding (as explained in 'step'). Note also that the second block of this display is the same -- as one displayed in the examle for 'showEnigmaConfigInternal'. showEnigmaOperationInternal :: EnigmaConfig -> String -> String showEnigmaOperationInternal ec msg = showEnigmaOperation_ showEnigmaConfigInternal ec msg -- Encoding display ========================================================== -- | Show the conventionally formatted encoding of a mssage by an (initial) Enigma machine configuration. -- -- >>> let cfg = configEnigma "c-β-V-VI-VIII" "CDTJ" "AE.BF.CM.DQ.HU.JN.LX.PR.SZ.VW" "05.16.05.12" -- >>> putStr $ showEnigmaEncoding cfg "FOLGENDES IST SOFORT BEKANNTZUGEBEN" -- RBBF PMHP HGCZ XTDY GAHG UFXG EWKB LKGJ showEnigmaEncoding :: EnigmaConfig -> String -> String showEnigmaEncoding ec msg = postproc $ enigmaEncoding ec (preproc msg)