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

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

EnigmaPresentation Settings synchronisation and start on play update and bugfixes, Pictures updated as .png, Rotor2 synchronisation update, Reflector bugfix

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to URL Author Date Rev Id
File size: 19.4 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            String input = preFormatInput(text);
92            myPresentation.setinput(input);
93            myPresentation.playClick(null, EventArgs.Empty);
94            return postFormatOutput(core.Encrypt(rotor1Pos, rotor2Pos, rotor3Pos, rotor4Pos, input));
95        }
96
97        internal class UnknownToken
98        {
99            internal string text;
100            internal int position;
101
102            internal UnknownToken(char c, int position)
103            {
104                this.text = char.ToString(c);
105                this.position = position;
106            }
107
108            public override string ToString()
109            {
110                return "[" + text + "," + position + "]";
111            }
112        }
113
114        IList<UnknownToken> unknownList = new List<UnknownToken>();
115
116        /// <summary>
117        /// Format the string to contain only alphabet characters in upper case
118        /// </summary>
119        /// <param name="text">The string to be prepared</param>
120        /// <returns>The properly formated string to be processed direct by the encryption function</returns>
121        private string preFormatInput(string text)
122        {
123            StringBuilder result = new StringBuilder();
124            bool newToken = true;
125            unknownList.Clear();
126
127            for (int i = 0; i < text.Length; i++)
128            {
129                if (settings.Alphabet.Contains(char.ToUpper(text[i])))
130                {
131                    newToken = true;
132                    result.Append(char.ToUpper(text[i])); // FIXME: shall save positions of lowercase letters
133                }
134                else if (settings.UnknownSymbolHandling != 1) // 1 := remove
135                {
136                    // 0 := preserve, 2 := replace by X
137                    char symbol = settings.UnknownSymbolHandling == 0 ? text[i] : 'X';
138
139                    if (newToken)
140                    {
141                        unknownList.Add(new UnknownToken(symbol, i));
142                        newToken = false;
143                    }
144                    else
145                    {
146                        unknownList.Last().text += symbol;
147                    }
148                }
149            }
150
151            return result.ToString().ToUpper();
152
153        }
154
155        //// legacy code
156        //switch (settings.UnknownSymbolHandling)
157        //{
158        //    case 0: // ignore
159        //        result.Append(c);
160        //        break;
161        //    case 1: // remove
162        //        continue;
163        //    case 2: // replace by X
164        //        result.Append('X');
165        //        break;
166        //}
167
168        /// <summary>
169        /// Formats the string processed by the encryption for presentation according
170        /// to the settings given
171        /// </summary>
172        /// <param name="text">The encrypted text</param>
173        /// <returns>The formatted text for output</returns>
174        private string postFormatOutput(string text)
175        {
176            StringBuilder workstring = new StringBuilder(text);
177            foreach (UnknownToken token in unknownList)
178            {
179                workstring.Insert(token.position, token.text);
180            }
181
182            switch (settings.CaseHandling)
183            {
184                default:
185                case 0: // preserve
186                    // FIXME: shall restore lowercase letters
187                    return workstring.ToString();
188                case 1: // upper
189                    return workstring.ToString().ToUpper();
190                case 2: // lower
191                    return workstring.ToString().ToLower();
192            }
193        }
194
195        #endregion
196
197        #region Analyzer event handler
198
199        /// <summary>
200        /// This eventhandler is called, when the analyzer has an intermediate result
201        /// </summary>
202        /// <param name="sender"></param>
203        /// <param name="e"></param>
204        private void analyzer_OnIntermediateResult(object sender, IntermediateResultEventArgs e)
205        {
206            // Got an intermidate results from the analyzer, hence display it
207            outputString = postFormatOutput(e.Result);
208            OnPropertyChanged("OutputString");
209        }
210
211        #endregion
212
213        #region n-gram frequencies
214
215        private IDictionary<string, double[]> LoadDefaultStatistics(int length)
216        {
217            Dictionary<string, double[]> grams = new Dictionary<string, double[]>();
218
219            StreamReader reader = new StreamReader(Path.Combine(DirectoryHelper.DirectoryCrypPlugins, GetStatisticsFilename(length)));
220
221            string line;
222            while ((line = reader.ReadLine()) != null)
223            {
224                if (line.StartsWith("#"))
225                    continue;
226
227                string[] tokens = WordTokenizer.tokenize(line).ToArray();
228                if (tokens.Length == 0)
229                    continue;
230                Debug.Assert(tokens.Length == 2, "Expected 2 tokens, found " + tokens.Length + " on one line");
231
232                grams.Add(tokens[0], new double[] { Double.Parse(tokens[1]), 0, 0, 0 });
233            }
234
235            double sum = grams.Values.Sum(item => item[ABSOLUTE]);
236            LogMessage("Sum of all n-gram counts is: " + sum, NotificationLevel.Debug);
237
238            // calculate scaled values
239            foreach (double[] g in grams.Values)
240            {
241                g[PERCENTAGED] = g[ABSOLUTE] / sum;
242                g[LOG2] = Math.Log(g[ABSOLUTE], 2);
243                g[SINKOV] = Math.Log(g[PERCENTAGED], Math.E);
244            }
245
246            return grams;
247        }
248
249        /// <summary>
250        /// Get file name for default n-gram frequencies.
251        /// </summary>
252        /// <param name="length"></param>
253        /// <exception cref="NotSupportedException">No default n-gram frequencies available</exception>
254        /// <returns></returns>
255        private string GetStatisticsFilename(int length)
256        {
257            if (length < 1)
258            {
259                throw new ArgumentOutOfRangeException("There is no known default statistic for an n-gram length of " + length);
260            }
261
262            return "Enigma_" + length + "gram_Frequency.txt";
263        }
264
265        #endregion
266
267        #endregion
268
269        #region Constructor
270
271        public Enigma()
272        {
273            this.settings = new EnigmaSettings();
274            this.core = new EnigmaCore(this);
275            this.analyzer = new EnigmaAnalyzer(this);
276            this.analyzer.OnIntermediateResult += new EventHandler<IntermediateResultEventArgs>(analyzer_OnIntermediateResult);
277            this.statistics = new Dictionary<int, IDictionary<string, double[]>>();
278            myPresentation = new EnigmaPresentation(this.settings);
279            this.Presentation = myPresentation;
280            this.settings.PropertyChanged += myPresentation.settings_OnPropertyChange;
281            this.settings.PropertyChanged += settings_OnPropertyChange;
282        }
283
284        #endregion
285
286        #region Events
287
288#pragma warning disable 67
289        public event StatusChangedEventHandler OnPluginStatusChanged;
290#pragma warning restore
291        public event GuiLogNotificationEventHandler OnGuiLogNotificationOccured;
292        public event PluginProgressChangedEventHandler OnPluginProgressChanged;
293
294        private void settings_OnPropertyChange(object sender, PropertyChangedEventArgs e)
295        {
296            EnigmaSettings dummyset = sender as EnigmaSettings;
297            //myPresentation.settingsChanged(dummyset);
298           
299           
300            LogMessage("OnPropertyChange " + e.PropertyName, NotificationLevel.Debug);
301        }
302
303        #endregion
304
305        #region IPlugin properties
306
307        public ISettings Settings
308        {
309            get { return this.settings; }
310        }
311
312        public UserControl Presentation
313        {
314            get;
315            private set;
316        }
317
318        public UserControl QuickWatchPresentation
319        {
320            get { return Presentation; }
321        }
322
323        #endregion
324
325        #region Connector properties
326
327        [PropertyInfo(Direction.InputData, "Text input", "Input a string to be processed by the Enigma machine", "", true, false, QuickWatchFormat.Text, null)]
328        public string InputString
329        {
330            get { return this.inputString; }
331            set
332            {
333                if (value != inputString)
334                {
335                    this.inputString = value;
336                    OnPropertyChanged("InputString");
337                }
338            }
339        }
340
341        //[PropertyInfo(Direction.InputData, "n-gram dictionary", "Dictionary with gram counts (string -> [absolute, percentaged, log2])", "", false, false, QuickWatchFormat.Text, "FrequencyTest.QuickWatchDictionary")]
342        //public IDictionary<string, double[]> InputGrams
343        //{
344        //    get { return this.inputTriGrams; }
345        //    set
346        //    {
347        //        if (value != inputTriGrams)
348        //        {
349        //            this.inputTriGrams = value;
350        //            OnPropertyChanged("InputTriGrams");
351        //        }
352        //    }
353        //}
354
355        [PropertyInfo(Direction.OutputData, "Text output", "The string after processing with the Enigma machine", "", false, false, QuickWatchFormat.Text, null)]
356        public string OutputString
357        {
358            get { return this.outputString; }
359            set
360            {
361                outputString = value;
362                OnPropertyChanged("OutputString");
363            }
364        }
365
366        #endregion
367
368        #region Public methods
369
370        public void PreExecution()
371        {
372            EventsHelper.GuiLogMessage(OnGuiLogNotificationOccured, this, new GuiLogEventArgs("Preparing enigma for operation..", this,  NotificationLevel.Info));
373
374            if (settings.Model != 3)
375            {
376                EventsHelper.GuiLogMessage(OnGuiLogNotificationOccured, this, new GuiLogEventArgs("This simulator is work in progress. As of right now only Enigma I is supported!!", this, NotificationLevel.Warning));
377                return;
378            }
379
380            // remember the current key-setting, in order to restore on stop
381            savedKey = settings.Key;
382
383            //configure the enigma
384            core.setInternalConfig(settings.Rotor1, settings.Rotor2, settings.Rotor3, settings.Rotor4,
385                        settings.Reflector, settings.Ring1, settings.Ring2, settings.Ring3, settings.Ring4,
386                        settings.PlugBoard);
387        }
388
389        public void Execute()
390        {
391            if (inputString == null)
392                return;
393
394
395            if (settings.Model != 3)
396            {
397                LogMessage("This simulator is work in progress. As of right now only Enigma I is supported!!", NotificationLevel.Error);
398                return;
399            }
400
401           
402
403            switch (settings.Action)
404            {
405                case 0:
406                    LogMessage("Enigma encryption/decryption started...", NotificationLevel.Info);
407
408                    // re-set the key, in case we get executed again during single run
409                    settings.Key = savedKey.ToUpper();
410
411                    // do the encryption
412                    outputString = FormattedEncrypt(settings.Alphabet.IndexOf(settings.Key[2]), 
413                        settings.Alphabet.IndexOf(settings.Key[1]),
414                        settings.Alphabet.IndexOf(settings.Key[0]), 
415                        0, inputString);
416
417
418                    // FIXME: output all scorings
419                    LogMessage("Enigma encryption done. The resulting index of coincidences is " + analyzer.calculateScore(outputString, 0), NotificationLevel.Info);
420
421                    // "fire" the output
422                    OnPropertyChanged("OutputString");
423                    break;
424                case 1:
425                    LogMessage("Enigma analysis starting ...", NotificationLevel.Info);
426
427                    //prepare for analysis
428                    LogMessage("ANALYSIS: Preformatting text...", NotificationLevel.Debug);
429                    string preformatedText = preFormatInput(inputString);
430
431                    // perform the analysis
432                    outputString = postFormatOutput(analyzer.Analyze(preformatedText));
433                    OnPropertyChanged("OutputString");
434
435                    ShowProgress(1000, 1000);
436                    break;
437                default:
438                    break;
439            }
440
441        }
442
443        public void PostExecution()
444        {
445            LogMessage("Enigma shutting down. Reverting key to inial value!", NotificationLevel.Info);
446            if (savedKey != null && savedKey.Length > 0)
447            {
448                settings.Key = savedKey; // re-set the key
449            }
450           
451        }
452
453        public void Pause()
454        {
455            LogMessage("The \"Pause\"-Feature is not implemented!", NotificationLevel.Warning);
456        }
457
458        public void Stop()
459        {
460            LogMessage("Enigma stopped", NotificationLevel.Info);
461            myPresentation.stopclick(this, EventArgs.Empty);
462            analyzer.StopAnalysis();
463        }
464
465        public void Initialize()
466        {
467            LogMessage("Initializing..", NotificationLevel.Debug);
468            this.settings.Initialize();
469        }
470
471        public void Dispose()
472        {
473            LogMessage("Dispose", NotificationLevel.Debug);
474        }
475
476
477
478        /// <summary>
479        /// Logs a message to the Cryptool console
480        /// </summary>
481        public void LogMessage(string msg, NotificationLevel level)
482        {
483            EventsHelper.GuiLogMessage(OnGuiLogNotificationOccured, this, new GuiLogEventArgs(msg, this, level));
484        }
485
486        /// <summary>
487        /// Sets the progress bar for this plugin
488        /// </summary>
489        /// <param name="val"></param>
490        /// <param name="max"></param>
491        public void ShowProgress(double val, double max)
492        {
493            EventsHelper.ProgressChanged(OnPluginProgressChanged, this, new PluginProgressEventArgs(val, max));
494        }
495
496        /// <summary>
497        /// Returns a formated string with all plugs from a given substitution string
498        /// This method should be move to some more adequate place
499        /// </summary>
500        /// <param name="pb">The substitution string for a plugboard</param>
501        /// <returns>A list of plugs</returns>
502        public string pB2String(string pb)
503        {
504            if (pb.Length != settings.Alphabet.Length)
505                return "-- no plugs --";
506
507
508            StringBuilder result = new StringBuilder();
509
510            for (int i = 0; i < settings.Alphabet.Length; i++)
511            {
512                if (settings.Alphabet[i] != pb[i] && !result.ToString().Contains(settings.Alphabet[i]))
513                {
514                    if (result.Length > 0)
515                        result.Append(' ');
516
517                    result.Append(settings.Alphabet[i].ToString() + pb[i].ToString());
518                }
519            }
520
521            if (result.Length == 0)
522                result.Append("-- no plugs --");
523
524            return result.ToString();
525        }
526
527        public IDictionary<string, double[]> GetStatistics(int gramLength)
528        {
529            // FIXME: inputTriGrams is not being used!
530
531            // FIXME: implement exception handling
532
533            if (!statistics.ContainsKey(gramLength))
534            {
535                LogMessage("Trying to load default statistics for " + gramLength + "-grams", NotificationLevel.Info);
536                statistics[gramLength] = LoadDefaultStatistics(gramLength);
537            }
538
539            return statistics[gramLength];
540        }
541
542        #endregion
543
544        #region INotifyPropertyChanged Member
545
546        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
547
548        public void OnPropertyChanged(string name)
549        {
550            EventsHelper.PropertyChanged(PropertyChanged, this, new PropertyChangedEventArgs(name));
551        }
552
553        #endregion
554    }
555}
Note: See TracBrowser for help on using the repository browser.