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

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

EnigmaPresentation weekly update: Presentation-Drag-and-Drop <-> EnigmaSettings sync, EnigmaSettings PlugBoard bugfix, EnigmaSettings Presentation Speed Slider added.

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