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

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

EnigmaPresentation - EnigmaSettings synchronisation, EnigmaPresentation input management updated

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