source: trunk/CrypPlugins/Enigma/Enigma.cs @ 2636

Last change on this file since 2636 was 2636, checked in by weyers, 11 years ago

Enigma Presentation first implementation attempt.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to URL Author Date Rev Id
File size: 19.1 KB
Line 
1/*
2   Copyright 2008-2009, Dr. Arno Wacker, University of Duisburg-Essen
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17
18using System;
19using System.Collections.Generic;
20using System.Linq;
21using System.Text;
22
23// additional needed libs
24using System.Windows.Controls;
25using System.ComponentModel;
26using System.Threading;
27using System.Collections;
28using System.Diagnostics;
29using System.IO;
30using System.Reflection;
31using System.Resources;
32
33//Cryptool 2.0 specific includes
34using Cryptool;
35using Cryptool.PluginBase;
36using Cryptool.PluginBase.Analysis;
37using Cryptool.PluginBase.Cryptography;
38using Cryptool.PluginBase.Miscellaneous;
39using Cryptool.PluginBase.IO;
40
41
42namespace Cryptool.Enigma
43{
44    [Author("Dr. Arno Wacker, Matthäus Wander", "arno.wacker@cryptool.org", "Uni Duisburg-Essen, Fachgebiet Verteilte Systeme", "http://www.vs.uni-due.de")]
45    [PluginInfo(false, "Enigma", "Polyalphabetic rotor-cipher machine", null,
46      "Enigma/Images/Enigma.png", "Enigma/Images/encrypt.png", "Enigma/Images/decrypt.png")]
47    [EncryptionType(EncryptionType.Classic)]
48    public class Enigma: IEncryption, ISpecific
49    {
50        #region Constants
51
52        internal const int ABSOLUTE = 0;
53        internal const int PERCENTAGED = 1;
54        internal const int LOG2 = 2;
55        internal const int SINKOV = 3;
56
57        #endregion
58
59        #region Private variables
60
61        private EnigmaSettings settings;
62        private EnigmaPresentation myPresentation;
63        private EnigmaCore core;
64        private EnigmaAnalyzer analyzer;
65        private string inputString;
66        private IDictionary<int, IDictionary<string, double[]>> statistics;
67        // FIXME: enable optional statistics input
68        //private IDictionary<string, double[]> inputTriGrams;
69        private string outputString;
70        private string savedKey;
71       
72        #endregion
73
74        #region Private methods
75
76        #region Formatting stuff
77
78        /// <summary>
79        /// Encrypts or decrypts a string with the given key (rotor positions) and formats
80        /// the output according to the settings
81        /// </summary>
82        /// <param name="rotor1Pos">Position of rotor 1 (fastest)</param>
83        /// <param name="rotor2Pos">Position of rotor 2 (middle)</param>
84        /// <param name="rotor3Pos">Position of rotor 3 (slowest)</param>
85        /// <param name="rotor4Pos">Position of rotor 4 (extra rotor for M4)</param>
86        /// <param name="text">The text for en/decryption. This string may contain
87        /// arbitrary characters, which will be dealt with according to the settings given</param>
88        /// <returns>The encrypted/decrypted string</returns>
89        private string FormattedEncrypt(int rotor1Pos, int rotor2Pos, int rotor3Pos, int rotor4Pos, string text)
90        {
91            return postFormatOutput(core.Encrypt(rotor1Pos, rotor2Pos, rotor3Pos, rotor4Pos, preFormatInput(text)));
92        }
93
94        internal class UnknownToken
95        {
96            internal string text;
97            internal int position;
98
99            internal UnknownToken(char c, int position)
100            {
101                this.text = char.ToString(c);
102                this.position = position;
103            }
104
105            public override string ToString()
106            {
107                return "[" + text + "," + position + "]";
108            }
109        }
110
111        IList<UnknownToken> unknownList = new List<UnknownToken>();
112
113        /// <summary>
114        /// Format the string to contain only alphabet characters in upper case
115        /// </summary>
116        /// <param name="text">The string to be prepared</param>
117        /// <returns>The properly formated string to be processed direct by the encryption function</returns>
118        private string preFormatInput(string text)
119        {
120            StringBuilder result = new StringBuilder();
121            bool newToken = true;
122            unknownList.Clear();
123
124            for (int i = 0; i < text.Length; i++)
125            {
126                if (settings.Alphabet.Contains(char.ToUpper(text[i])))
127                {
128                    newToken = true;
129                    result.Append(char.ToUpper(text[i])); // FIXME: shall save positions of lowercase letters
130                }
131                else if (settings.UnknownSymbolHandling != 1) // 1 := remove
132                {
133                    // 0 := preserve, 2 := replace by X
134                    char symbol = settings.UnknownSymbolHandling == 0 ? text[i] : 'X';
135
136                    if (newToken)
137                    {
138                        unknownList.Add(new UnknownToken(symbol, i));
139                        newToken = false;
140                    }
141                    else
142                    {
143                        unknownList.Last().text += symbol;
144                    }
145                }
146            }
147
148            return result.ToString().ToUpper();
149
150        }
151
152        //// legacy code
153        //switch (settings.UnknownSymbolHandling)
154        //{
155        //    case 0: // ignore
156        //        result.Append(c);
157        //        break;
158        //    case 1: // remove
159        //        continue;
160        //    case 2: // replace by X
161        //        result.Append('X');
162        //        break;
163        //}
164
165        /// <summary>
166        /// Formats the string processed by the encryption for presentation according
167        /// to the settings given
168        /// </summary>
169        /// <param name="text">The encrypted text</param>
170        /// <returns>The formatted text for output</returns>
171        private string postFormatOutput(string text)
172        {
173            StringBuilder workstring = new StringBuilder(text);
174            foreach (UnknownToken token in unknownList)
175            {
176                workstring.Insert(token.position, token.text);
177            }
178
179            switch (settings.CaseHandling)
180            {
181                default:
182                case 0: // preserve
183                    // FIXME: shall restore lowercase letters
184                    return workstring.ToString();
185                case 1: // upper
186                    return workstring.ToString().ToUpper();
187                case 2: // lower
188                    return workstring.ToString().ToLower();
189            }
190        }
191
192        #endregion
193
194        #region Analyzer event handler
195
196        /// <summary>
197        /// This eventhandler is called, when the analyzer has an intermediate result
198        /// </summary>
199        /// <param name="sender"></param>
200        /// <param name="e"></param>
201        private void analyzer_OnIntermediateResult(object sender, IntermediateResultEventArgs e)
202        {
203            // Got an intermidate results from the analyzer, hence display it
204            outputString = postFormatOutput(e.Result);
205            OnPropertyChanged("OutputString");
206        }
207
208        #endregion
209
210        #region n-gram frequencies
211
212        private IDictionary<string, double[]> LoadDefaultStatistics(int length)
213        {
214            Dictionary<string, double[]> grams = new Dictionary<string, double[]>();
215
216            StreamReader reader = new StreamReader(Path.Combine(DirectoryHelper.DirectoryCrypPlugins, GetStatisticsFilename(length)));
217
218            string line;
219            while ((line = reader.ReadLine()) != null)
220            {
221                if (line.StartsWith("#"))
222                    continue;
223
224                string[] tokens = WordTokenizer.tokenize(line).ToArray();
225                if (tokens.Length == 0)
226                    continue;
227                Debug.Assert(tokens.Length == 2, "Expected 2 tokens, found " + tokens.Length + " on one line");
228
229                grams.Add(tokens[0], new double[] { Double.Parse(tokens[1]), 0, 0, 0 });
230            }
231
232            double sum = grams.Values.Sum(item => item[ABSOLUTE]);
233            LogMessage("Sum of all n-gram counts is: " + sum, NotificationLevel.Debug);
234
235            // calculate scaled values
236            foreach (double[] g in grams.Values)
237            {
238                g[PERCENTAGED] = g[ABSOLUTE] / sum;
239                g[LOG2] = Math.Log(g[ABSOLUTE], 2);
240                g[SINKOV] = Math.Log(g[PERCENTAGED], Math.E);
241            }
242
243            return grams;
244        }
245
246        /// <summary>
247        /// Get file name for default n-gram frequencies.
248        /// </summary>
249        /// <param name="length"></param>
250        /// <exception cref="NotSupportedException">No default n-gram frequencies available</exception>
251        /// <returns></returns>
252        private string GetStatisticsFilename(int length)
253        {
254            if (length < 1)
255            {
256                throw new ArgumentOutOfRangeException("There is no known default statistic for an n-gram length of " + length);
257            }
258
259            return "Enigma_" + length + "gram_Frequency.txt";
260        }
261
262        #endregion
263
264        #endregion
265
266        #region Constructor
267
268        public Enigma()
269        {
270            this.settings = new EnigmaSettings();
271            this.core = new EnigmaCore(this);
272            this.analyzer = new EnigmaAnalyzer(this);
273            this.analyzer.OnIntermediateResult += new EventHandler<IntermediateResultEventArgs>(analyzer_OnIntermediateResult);
274            this.statistics = new Dictionary<int, IDictionary<string, double[]>>();
275            myPresentation = new EnigmaPresentation();
276            this.Presentation = myPresentation;
277            this.settings.PropertyChanged += myPresentation.settings_OnPropertyChange;
278            this.settings.PropertyChanged += settings_OnPropertyChange;
279        }
280
281        #endregion
282
283        #region Events
284
285#pragma warning disable 67
286        public event StatusChangedEventHandler OnPluginStatusChanged;
287#pragma warning restore
288        public event GuiLogNotificationEventHandler OnGuiLogNotificationOccured;
289        public event PluginProgressChangedEventHandler OnPluginProgressChanged;
290
291        private void settings_OnPropertyChange(object sender, PropertyChangedEventArgs e)
292        {
293            EnigmaSettings dummyset = sender as EnigmaSettings;
294            //myPresentation.settingsChanged(dummyset);
295           
296           
297            LogMessage("OnPropertyChange " + e.PropertyName, NotificationLevel.Debug);
298        }
299
300        #endregion
301
302        #region IPlugin properties
303
304        public ISettings Settings
305        {
306            get { return this.settings; }
307        }
308
309        public UserControl Presentation
310        {
311            get;
312            private set;
313        }
314
315        public UserControl QuickWatchPresentation
316        {
317            get { return Presentation; }
318        }
319
320        #endregion
321
322        #region Connector properties
323
324        [PropertyInfo(Direction.InputData, "Text input", "Input a string to be processed by the Enigma machine", "", true, false, QuickWatchFormat.Text, null)]
325        public string InputString
326        {
327            get { return this.inputString; }
328            set
329            {
330                if (value != inputString)
331                {
332                    this.inputString = value;
333                    OnPropertyChanged("InputString");
334                }
335            }
336        }
337
338        //[PropertyInfo(Direction.InputData, "n-gram dictionary", "Dictionary with gram counts (string -> [absolute, percentaged, log2])", "", false, false, QuickWatchFormat.Text, "FrequencyTest.QuickWatchDictionary")]
339        //public IDictionary<string, double[]> InputGrams
340        //{
341        //    get { return this.inputTriGrams; }
342        //    set
343        //    {
344        //        if (value != inputTriGrams)
345        //        {
346        //            this.inputTriGrams = value;
347        //            OnPropertyChanged("InputTriGrams");
348        //        }
349        //    }
350        //}
351
352        [PropertyInfo(Direction.OutputData, "Text output", "The string after processing with the Enigma machine", "", false, false, QuickWatchFormat.Text, null)]
353        public string OutputString
354        {
355            get { return this.outputString; }
356            set
357            {
358                outputString = value;
359                OnPropertyChanged("OutputString");
360            }
361        }
362
363        #endregion
364
365        #region Public methods
366
367        public void PreExecution()
368        {
369            EventsHelper.GuiLogMessage(OnGuiLogNotificationOccured, this, new GuiLogEventArgs("Preparing enigma for operation..", this,  NotificationLevel.Info));
370
371            if (settings.Model != 3)
372            {
373                EventsHelper.GuiLogMessage(OnGuiLogNotificationOccured, this, new GuiLogEventArgs("This simulator is work in progress. As of right now only Enigma I is supported!!", this, NotificationLevel.Warning));
374                return;
375            }
376
377            // remember the current key-setting, in order to restore on stop
378            savedKey = settings.Key;
379
380            //configure the enigma
381            core.setInternalConfig(settings.Rotor1, settings.Rotor2, settings.Rotor3, settings.Rotor4,
382                        settings.Reflector, settings.Ring1, settings.Ring2, settings.Ring3, settings.Ring4,
383                        settings.PlugBoard);
384        }
385
386        public void Execute()
387        {
388            if (inputString == null)
389                return;
390
391
392            if (settings.Model != 3)
393            {
394                LogMessage("This simulator is work in progress. As of right now only Enigma I is supported!!", NotificationLevel.Error);
395                return;
396            }
397
398           
399
400            switch (settings.Action)
401            {
402                case 0:
403                    LogMessage("Enigma encryption/decryption started...", NotificationLevel.Info);
404
405                    // re-set the key, in case we get executed again during single run
406                    settings.Key = savedKey.ToUpper();
407
408                    // do the encryption
409                    outputString = FormattedEncrypt(settings.Alphabet.IndexOf(settings.Key[2]), 
410                        settings.Alphabet.IndexOf(settings.Key[1]),
411                        settings.Alphabet.IndexOf(settings.Key[0]), 
412                        0, inputString);
413
414
415                    // FIXME: output all scorings
416                    LogMessage("Enigma encryption done. The resulting index of coincidences is " + analyzer.calculateScore(outputString, 0), NotificationLevel.Info);
417
418                    // "fire" the output
419                    OnPropertyChanged("OutputString");
420                    break;
421                case 1:
422                    LogMessage("Enigma analysis starting ...", NotificationLevel.Info);
423
424                    //prepare for analysis
425                    LogMessage("ANALYSIS: Preformatting text...", NotificationLevel.Debug);
426                    string preformatedText = preFormatInput(inputString);
427
428                    // perform the analysis
429                    outputString = postFormatOutput(analyzer.Analyze(preformatedText));
430                    OnPropertyChanged("OutputString");
431
432                    ShowProgress(1000, 1000);
433                    break;
434                default:
435                    break;
436            }
437
438        }
439
440        public void PostExecution()
441        {
442            LogMessage("Enigma shutting down. Reverting key to inial value!", NotificationLevel.Info);
443            if (savedKey != null && savedKey.Length > 0)
444            {
445                settings.Key = savedKey; // re-set the key
446            }
447           
448        }
449
450        public void Pause()
451        {
452            LogMessage("The \"Pause\"-Feature is not implemented!", NotificationLevel.Warning);
453        }
454
455        public void Stop()
456        {
457            LogMessage("Enigma stopped", NotificationLevel.Info);
458            analyzer.StopAnalysis();
459        }
460
461        public void Initialize()
462        {
463            LogMessage("Initializing..", NotificationLevel.Debug);
464            this.settings.Initialize();
465        }
466
467        public void Dispose()
468        {
469            LogMessage("Dispose", NotificationLevel.Debug);
470        }
471
472
473
474        /// <summary>
475        /// Logs a message to the Cryptool console
476        /// </summary>
477        public void LogMessage(string msg, NotificationLevel level)
478        {
479            EventsHelper.GuiLogMessage(OnGuiLogNotificationOccured, this, new GuiLogEventArgs(msg, this, level));
480        }
481
482        /// <summary>
483        /// Sets the progress bar for this plugin
484        /// </summary>
485        /// <param name="val"></param>
486        /// <param name="max"></param>
487        public void ShowProgress(double val, double max)
488        {
489            EventsHelper.ProgressChanged(OnPluginProgressChanged, this, new PluginProgressEventArgs(val, max));
490        }
491
492        /// <summary>
493        /// Returns a formated string with all plugs from a given substitution string
494        /// This method should be move to some more adequate place
495        /// </summary>
496        /// <param name="pb">The substitution string for a plugboard</param>
497        /// <returns>A list of plugs</returns>
498        public string pB2String(string pb)
499        {
500            if (pb.Length != settings.Alphabet.Length)
501                return "-- no plugs --";
502
503
504            StringBuilder result = new StringBuilder();
505
506            for (int i = 0; i < settings.Alphabet.Length; i++)
507            {
508                if (settings.Alphabet[i] != pb[i] && !result.ToString().Contains(settings.Alphabet[i]))
509                {
510                    if (result.Length > 0)
511                        result.Append(' ');
512
513                    result.Append(settings.Alphabet[i].ToString() + pb[i].ToString());
514                }
515            }
516
517            if (result.Length == 0)
518                result.Append("-- no plugs --");
519
520            return result.ToString();
521        }
522
523        public IDictionary<string, double[]> GetStatistics(int gramLength)
524        {
525            // FIXME: inputTriGrams is not being used!
526
527            // FIXME: implement exception handling
528
529            if (!statistics.ContainsKey(gramLength))
530            {
531                LogMessage("Trying to load default statistics for " + gramLength + "-grams", NotificationLevel.Info);
532                statistics[gramLength] = LoadDefaultStatistics(gramLength);
533            }
534
535            return statistics[gramLength];
536        }
537
538        #endregion
539
540        #region INotifyPropertyChanged Member
541
542        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
543
544        public void OnPropertyChanged(string name)
545        {
546            EventsHelper.PropertyChanged(PropertyChanged, this, new PropertyChangedEventArgs(name));
547        }
548
549        #endregion
550    }
551}
Note: See TracBrowser for help on using the repository browser.