source: trunk/CrypPlugins/KeySearcher/KeySearcher.cs @ 843

Last change on this file since 843 was 843, checked in by Sven Rech, 12 years ago

much better thread support for keysearcher now

File size: 30.3 KB
Line 
1using System;
2using System.Linq;
3using System.Text;
4using Cryptool.PluginBase.Analysis;
5using Cryptool.PluginBase;
6using System.Windows.Controls;
7using System.ComponentModel;
8using Cryptool.PluginBase.Control;
9using System.Collections;
10using System.Collections.Generic;
11using System.Threading;
12using System.Windows.Threading;
13
14namespace KeySearcher
15{
16    public class KeyPattern
17    {
18        private class Wildcard
19        {
20            private char[] values = new char[256];
21            private int length;
22            private int counter;
23           
24            public Wildcard(string valuePattern)
25            {
26                counter = 0;
27                length = 0;
28                int i = 1;
29                while (valuePattern[i] != ']')
30                {
31                    if (valuePattern[i + 1] == '-')
32                    {
33                        for (char c = valuePattern[i]; c <= valuePattern[i + 2]; c++)
34                            values[length++] = c;
35                        i += 2;
36                    }
37                    else
38                        values[length++] = valuePattern[i];
39                    i++;
40                }
41            }
42
43            public Wildcard(Wildcard wc)
44            {
45                length = wc.length;
46                counter = wc.counter;
47                for (int i = 0; i < 256; i++)
48                    values[i] = wc.values[i];
49            }
50
51            private Wildcard()
52            {
53            }
54
55            public Wildcard[] split()
56            {
57                int length = this.length - this.counter;
58                Wildcard[] wcs = new Wildcard[2];
59                wcs[0] = new Wildcard();
60                wcs[0].counter = 0;
61                wcs[0].length = length / 2;
62                wcs[1] = new Wildcard();
63                wcs[1].counter = 0;
64                wcs[1].length = length - wcs[0].length;
65                for (int i = 0; i < wcs[0].length; i++)
66                    wcs[0].values[i] = values[this.counter + i];
67                for (int i = 0; i < wcs[1].length; i++)
68                    wcs[1].values[i] = values[i + this.counter + wcs[0].length];
69                return wcs;
70            }
71
72            public char getChar()
73            {
74                return values[counter];
75            }
76
77            public bool succ()
78            {
79                counter++;
80                if (counter >= length)
81                {
82                    counter = 0;
83                    return true;
84                }
85                return false;
86            }
87
88            public int size()
89            {
90                return length;
91            }
92
93
94            public int count()
95            {
96                return counter;
97            }
98
99            public void resetCounter()
100            {
101                counter = 0;
102            }
103        }
104
105        private string pattern;
106        private string key;       
107        private ArrayList wildcardList;
108
109        public KeyPattern(string pattern)
110        {
111            this.pattern = pattern;
112        }
113
114        public KeyPattern[] split()
115        {
116            KeyPattern[] patterns = new KeyPattern[2];
117            for (int i = 0; i < 2; i++)
118            {
119                patterns[i] = new KeyPattern(pattern);
120                patterns[i].key = key;
121                patterns[i].wildcardList = new ArrayList();
122            }
123            bool s = false;
124            for (int i = 0; i < wildcardList.Count; i++)
125            {
126                Wildcard wc = ((Wildcard)wildcardList[i]);
127                if (!s && (wc.size() - wc.count()) > 1)
128                {
129                    Wildcard[] wcs = wc.split();
130                    patterns[0].wildcardList.Add(wcs[0]);
131                    patterns[1].wildcardList.Add(wcs[1]);
132                    s = true;
133                }
134                else
135                {
136                    patterns[0].wildcardList.Add(new Wildcard(wc));
137                    Wildcard copy = new Wildcard(wc);
138                    if (s)
139                        copy.resetCounter();
140                    patterns[1].wildcardList.Add(copy);
141                }
142            }
143            return patterns;
144        }
145
146        public string giveWildcardKey()
147        {
148            string res = "";
149            int i = 0;
150            while (i < pattern.Length)
151            {
152                if (pattern[i] != '[')
153                    res += pattern[i];
154                else
155                {
156                    res += '*';
157                    while (pattern[i] != ']')
158                        i++;
159                }
160                i++;
161            }
162            return res;
163        }
164
165        public bool testKey(string key)
166        {
167            int kcount = 0;
168            int pcount = 0;
169            while (kcount < key.Length && pcount < pattern.Length)
170            {
171                if (pattern[pcount] != '[')
172                {
173                    if (key[kcount] != '*' && pattern[pcount] != key[kcount])
174                        return false;
175                    kcount++;
176                    pcount++;
177                }
178                else
179                {
180                    bool contains = false;
181                    pcount++;
182                    while (pattern[pcount] != ']')
183                    {
184                        if (key[kcount] != '*')
185                        {
186                            if (pattern[pcount + 1] == '-')
187                            {
188                                if (key[kcount] >= pattern[pcount] && key[kcount] <= pattern[pcount + 2])
189                                    contains = true;
190                                pcount += 2;
191                            }
192                            else
193                                if (pattern[pcount] == key[kcount])
194                                    contains = true;
195                        }
196                        pcount++;
197                    }
198                    if (!contains && !(key[kcount] == '*'))
199                        return false;
200                    kcount++;
201                    pcount++;
202                }               
203            }
204            if (pcount != pattern.Length || kcount != key.Length)
205                return false;
206            return true;
207        }
208
209        public long initKeyIteration(string key)
210        {
211            long counter = 1;
212            this.key = key;
213            int pcount = 0;
214            wildcardList = new ArrayList();
215            for (int i = 0; i < key.Length; i++)
216            {
217                if (key[i] == '*')
218                {
219                    Wildcard wc = new Wildcard(pattern.Substring(pcount, pattern.IndexOf(']', pcount) + 1 - pcount));
220                    wildcardList.Add(wc);
221                    counter *= wc.size();
222                }
223
224                if (pattern[pcount] == '[')
225                    while (pattern[pcount] != ']')
226                        pcount++;
227                pcount++;
228            }
229            return counter;
230        }
231
232        public long size()
233        {
234            if (wildcardList == null)
235                return 0;
236            long counter = 1;
237            foreach (Wildcard wc in wildcardList)
238                    counter *= wc.size();
239            return counter;
240        }
241
242        public bool nextKey()
243        {
244            int wildcardCount = wildcardList.Count-1;
245            bool overflow = ((Wildcard)wildcardList[wildcardCount]).succ();
246            wildcardCount--;
247            while (overflow && (wildcardCount >= 0))
248                overflow = ((Wildcard)wildcardList[wildcardCount--]).succ();
249            return !overflow;
250        }
251
252        public string getKey()
253        {
254            string res = "";
255            int wildcardCount = 0;
256            for (int i = 0; i < key.Length; i++)
257            {
258                if (key[i] != '*')
259                    res += key[i];
260                else
261                {
262                    Wildcard wc = (Wildcard)wildcardList[wildcardCount++];
263                    res += wc.getChar();
264                }
265            }
266            return res;
267        }
268    }
269   
270    [Author("Thomas Schmid", "thomas.schmid@cryptool.org", "Uni Siegen", "http://www.uni-siegen.de")]
271    //[Author("Sven Rech", "rech@cryptool.org", "Uni Duisburg-Essen", "http://www.uni-due.de")]
272    [PluginInfo(true, "KeySearcher", "Bruteforces a decryption algorithm.", null, "KeySearcher/Images/icon.png")]
273    public class KeySearcher : IAnalysisMisc
274    {
275        private Queue valuequeue;
276        private double value_threshold;
277        private int maxThread;  //the thread with the most keys left
278        private Mutex maxThreadMutex = new Mutex();
279
280        private KeyPattern pattern = null;
281        public KeyPattern Pattern
282        {
283            get
284            {
285                return pattern;
286            }
287            set
288            {
289                pattern = value;
290                if ((settings.Key == null) ||((settings.Key != null) && !pattern.testKey(settings.Key)))
291                    settings.Key = pattern.giveWildcardKey();
292            }
293        }
294
295        private bool stop;
296
297        #region IPlugin Members
298
299        public event StatusChangedEventHandler OnPluginStatusChanged;
300
301        public event GuiLogNotificationEventHandler OnGuiLogNotificationOccured;
302
303        public event PluginProgressChangedEventHandler OnPluginProgressChanged;
304
305        private KeySearcherSettings settings;
306
307        public KeySearcher()
308        {
309            settings = new KeySearcherSettings(this);
310            QuickWatchPresentation = new KeySearcherQuickWatchPresentation();
311        }
312
313        public ISettings Settings
314        {
315            get { return settings; }
316        }
317
318        public UserControl Presentation
319        {
320            get { return null; }
321        }
322
323        public UserControl QuickWatchPresentation
324        {
325            get;
326            private set;
327        }
328
329        public void PreExecution()
330        {
331        }
332
333        public void Execute()
334        {
335        }
336
337        private class ThreadStackElement
338        {
339            public AutoResetEvent ev;
340            public int threadid;
341        }
342
343        private void KeySearcherJob(object param)
344        {
345            object[] parameters = (object[])param;
346            KeyPattern[] patterns = (KeyPattern[])parameters[0];
347            int threadid = (int)parameters[1];
348            Int64[] doneKeysArray = (Int64[])parameters[2];
349            Int64[] keycounterArray = (Int64[])parameters[3];
350            Int64[] keysLeft = (Int64[])parameters[4];
351            IControlEncryption sender = (IControlEncryption)parameters[5];           
352            int bytesToUse = (int)parameters[6];
353            Stack threadStack = (Stack)parameters[7];
354
355            KeyPattern pattern = patterns[threadid];
356
357            try
358            {
359                while (pattern != null)
360                {
361                    long size = pattern.size();
362                    keysLeft[threadid] = size;
363
364                    do
365                    {
366                        ValueKey valueKey = new ValueKey();
367                        try
368                        {
369                            valueKey.key = pattern.getKey();
370                        }
371                        catch (Exception ex)
372                        {
373                            GuiLogMessage("Could not get next Key: " + ex.Message, NotificationLevel.Error);
374                            return;
375                        }
376
377                        try
378                        {
379                            valueKey.decryption = sender.Decrypt(ControlMaster.getKeyFromString(valueKey.key), bytesToUse);
380                        }
381                        catch (Exception ex)
382                        {
383                            GuiLogMessage("Decryption is not possible: " + ex.Message, NotificationLevel.Error);
384                            GuiLogMessage("Stack Trace: " + ex.StackTrace, NotificationLevel.Error);
385                            return;
386                        }
387
388                        try
389                        {
390                            valueKey.value = CostMaster.calculateCost(valueKey.decryption);
391                        }
392                        catch (Exception ex)
393                        {
394                            GuiLogMessage("Cost calculation is not possible: " + ex.Message, NotificationLevel.Error);
395                            return;
396                        }
397
398                        if (this.costMaster.getRelationOperator() == RelationOperator.LargerThen)
399                        {
400                            if (valueKey.value > value_threshold)
401                                valuequeue.Enqueue(valueKey);
402                        }
403                        else
404                        {
405                            if (valueKey.value < value_threshold)
406                                valuequeue.Enqueue(valueKey);
407                        }
408
409                        doneKeysArray[threadid]++;
410                        keycounterArray[threadid]++;
411                        keysLeft[threadid]--;
412
413                        //if we are the thread with most keys left, we have to share them:
414                        if (maxThread == threadid && threadStack.Count != 0)
415                        {
416                            maxThreadMutex.WaitOne();
417                            if (maxThread == threadid && threadStack.Count != 0)
418                            {
419                                KeyPattern[] split = pattern.split();
420                                patterns[threadid] = split[0];
421                                pattern = split[0];
422                                ThreadStackElement elem = (ThreadStackElement)threadStack.Pop();
423                                patterns[elem.threadid] = split[1];
424                                elem.ev.Set();    //wake the other thread up
425                                maxThread = -1;
426                                size = pattern.size();
427                                keysLeft[threadid] = size;
428                            }
429                            maxThreadMutex.ReleaseMutex();
430                        }
431
432                    } while (pattern.nextKey() && !stop);
433
434                    //Let's wait until another thread is willing to share with us:
435                    pattern = null;
436                    ThreadStackElement el = new ThreadStackElement();
437                    el.ev = new AutoResetEvent(false);
438                    el.threadid = threadid;
439                    patterns[threadid] = null;
440                    threadStack.Push(el);
441                    GuiLogMessage("Thread waiting for new keys.", NotificationLevel.Debug);
442                    el.ev.WaitOne();
443                    GuiLogMessage("Thread waking up with new keys.", NotificationLevel.Debug);
444                    pattern = patterns[threadid];
445                }
446            }
447            finally
448            {
449                sender.Dispose();
450            }
451        }
452
453        public void process(IControlEncryption sender)
454        {
455            if (sender != null && costMaster != null)
456            {
457                int maxInList = 10;
458                LinkedList<ValueKey> costList = new LinkedList<ValueKey>();
459                ValueKey valueKey = new ValueKey();
460                if (this.costMaster.getRelationOperator() == RelationOperator.LessThen)
461                    valueKey.value = double.MaxValue;
462                else
463                    valueKey.value = double.MinValue;
464                valueKey.key = "dummykey";
465                valueKey.decryption = new byte[0];
466                value_threshold = valueKey.value;
467                LinkedListNode<ValueKey> node = costList.AddFirst(valueKey);
468                for (int i = 1; i < maxInList; i++)
469                {
470                    node = costList.AddAfter(node, valueKey);
471                }
472
473                stop = false;
474                if (!Pattern.testKey(settings.Key))
475                {
476                    GuiLogMessage("Wrong key pattern!", NotificationLevel.Error);
477                    return;
478                }
479
480                int bytesToUse = 0;
481
482                try
483                {
484                    bytesToUse = CostMaster.getBytesToUse();
485                }
486                catch (Exception ex)
487                {
488                    GuiLogMessage("Bytes used not valid: " + ex.Message, NotificationLevel.Error);
489                    return;
490                }
491
492                LinkedListNode<ValueKey> linkedListNode;
493
494                KeyPattern[] patterns = new KeyPattern[settings.CoresUsed+1];
495                long size = Pattern.initKeyIteration(settings.Key);
496               
497                if (settings.CoresUsed > 0)
498                {
499                    KeyPattern[] patterns2 = Pattern.split();                   
500                    patterns[0] = patterns2[0];
501                    patterns[1] = patterns2[1];
502                    int p = 1;
503                    int threads = settings.CoresUsed - 1;
504                    while (threads > 0)
505                    {
506                        int maxPattern = -1;
507                        long max = 0;
508                        for (int i = 0; i <= p; i++)
509                            if (patterns[i].size() > max)
510                            {
511                                max = patterns[i].size();
512                                maxPattern = i;
513                            }
514                        KeyPattern[] patterns3 = patterns[maxPattern].split();
515                        patterns[maxPattern] = patterns3[0];
516                        patterns[++p] = patterns3[1];
517                        threads--;
518                    }
519                }
520                else
521                    patterns[0] = Pattern;
522
523                valuequeue = Queue.Synchronized(new Queue());
524
525                Int64[] doneKeysA = new Int64[patterns.Length];
526                Int64[] keycounters = new Int64[patterns.Length];
527                Int64[] keysleft = new Int64[patterns.Length];
528                Stack threadStack = Stack.Synchronized(new Stack());
529                for (int i = 0; i < patterns.Length; i++)
530                {
531                    WaitCallback worker = new WaitCallback(KeySearcherJob);
532                    doneKeysA[i] = new Int64();
533                    keycounters[i] = new Int64();
534                    ThreadPool.QueueUserWorkItem(worker, new object[] { patterns, i, doneKeysA, keycounters, keysleft, sender.clone(), bytesToUse, threadStack });
535                }
536               
537                //update message:
538                while (!stop)
539                {
540                    Thread.Sleep(1000);
541
542                    //update toplist:
543                    while (valuequeue.Count != 0)
544                    {
545                        ValueKey vk = (ValueKey)valuequeue.Dequeue();
546                        if (this.costMaster.getRelationOperator() == RelationOperator.LargerThen)
547                        {
548                            if (vk.value > costList.Last().value)
549                            {
550                                node = costList.First;
551                                while (node != null)
552                                {
553
554                                    if (vk.value > node.Value.value)
555                                    {
556                                        costList.AddBefore(node, vk);
557                                        costList.RemoveLast();
558                                        value_threshold = costList.Last.Value.value;
559                                        break;
560                                    }
561                                    node = node.Next;
562                                }//end while
563                            }//end if
564                        }
565                        else
566                        {
567                            node = costList.First;
568                            if (vk.value < costList.Last().value)
569                            {
570                                while (node != null)
571                                {
572
573                                    if (vk.value < node.Value.value)
574                                    {
575                                        costList.AddBefore(node, vk);
576                                        costList.RemoveLast();
577                                        value_threshold = costList.Last.Value.value;
578                                        break;
579                                    }
580                                    node = node.Next;
581                                }//end while
582                            }//end if
583                        }
584                    }
585
586                    long keycounter = 0;
587                    long doneKeys = 0;
588                    foreach (Int64 dk in doneKeysA)
589                        doneKeys += dk;
590                    foreach (Int64 kc in keycounters)
591                        keycounter += kc;
592
593                    if (keycounter > size)
594                        GuiLogMessage("There must be an error, because we bruteforced too much keys...", NotificationLevel.Error);
595
596                    //Let's determine which thread has the most keys to share:
597                    if (size - keycounter > 1000)
598                    {
599                        maxThreadMutex.WaitOne();
600                        long max = 0;
601                        int id = -1;
602                        for (int i = 0; i < patterns.Length; i++)
603                            if (keysleft[i] > max)
604                            {
605                                max = keysleft[i];
606                                id = i;
607                            }
608                        maxThread = id;
609                        maxThreadMutex.ReleaseMutex();
610                    }
611
612                    ProgressChanged(keycounter, size);
613
614                    if (QuickWatchPresentation.IsVisible && doneKeys != 0)
615                    {
616                        double time = ((size - keycounter) / doneKeys);
617                        TimeSpan timeleft = new TimeSpan(-1);
618
619                        try
620                        {
621                            if (time / (24 * 60 * 60) <= int.MaxValue)
622                            {
623                                int days = (int)(time / (24 * 60 * 60));
624                                time = time - (days * 24 * 60 * 60);
625                                int hours = (int)(time / (60 * 60));
626                                time = time - (hours * 60 * 60);
627                                int minutes = (int)(time / 60);
628                                time = time - (minutes * 60);
629                                int seconds = (int)time;
630
631                                timeleft = new TimeSpan(days, hours, minutes, (int)seconds, 0);
632                            }
633                        }
634                        catch
635                        {
636                            //can not calculate time span
637                        }
638
639                        ((KeySearcherQuickWatchPresentation)QuickWatchPresentation).Dispatcher.Invoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
640                        {
641                            ((KeySearcherQuickWatchPresentation)QuickWatchPresentation).keysPerSecond.Text = "" + doneKeys;
642                            if (timeleft != new TimeSpan(-1))
643                            {
644                                ((KeySearcherQuickWatchPresentation)QuickWatchPresentation).timeLeft.Text = "" + timeleft;
645                                try
646                                {
647                                    ((KeySearcherQuickWatchPresentation)QuickWatchPresentation).endTime.Text = "" + DateTime.Now.Add(timeleft);
648                                }
649                                catch
650                                {
651                                    ((KeySearcherQuickWatchPresentation)QuickWatchPresentation).endTime.Text = "in a galaxy far, far away...";
652                                }
653                            }
654                            else
655                            {
656                                ((KeySearcherQuickWatchPresentation)QuickWatchPresentation).timeLeft.Text = "incalculable :-)";
657                                ((KeySearcherQuickWatchPresentation)QuickWatchPresentation).endTime.Text = "in a galaxy far, far away...";
658                            }
659
660                            ((KeySearcherQuickWatchPresentation)QuickWatchPresentation).listbox.Items.Clear();
661                            linkedListNode = costList.First;
662                            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
663                            int i = 0;
664                            while (linkedListNode != null)
665                            {
666                                i++;
667                                ((KeySearcherQuickWatchPresentation)QuickWatchPresentation).listbox.Items.Add(i + ") " + Math.Round(linkedListNode.Value.value, 4) + " = " + linkedListNode.Value.key + " : \"" +
668                                    enc.GetString(linkedListNode.Value.decryption).Replace("\n", "").Replace("\r", "").Replace("\t", "") + "\"");
669                                linkedListNode = linkedListNode.Next;
670                            }
671                        }
672                        , null);
673                    }//end if
674                    doneKeys = 0;
675                    for (int i = 0; i < doneKeysA.Length; i++)
676                        doneKeysA[i] = 0;
677
678                    if (!stop && QuickWatchPresentation.IsVisible)
679                    {
680
681                        ((KeySearcherQuickWatchPresentation)QuickWatchPresentation).Dispatcher.Invoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
682                        {
683                            ((KeySearcherQuickWatchPresentation)QuickWatchPresentation).listbox.Items.Clear();
684                            linkedListNode = costList.First;
685                            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
686                            int i = 0;
687                            while (linkedListNode != null)
688                            {
689                                i++;
690                                ((KeySearcherQuickWatchPresentation)QuickWatchPresentation).listbox.Items.Add(i + ") " + Math.Round(linkedListNode.Value.value, 4) + " = " + linkedListNode.Value.key + " : \"" +
691                                    enc.GetString(linkedListNode.Value.decryption).Replace("\n", "").Replace("\r", "").Replace("\t", "") + "\"");
692                                linkedListNode = linkedListNode.Next;
693                            }
694                        }
695                        , null);
696                    }
697
698                    if (keycounter >= size)
699                        break;
700                }//end while
701
702                //wake up all sleeping threads:
703                while (threadStack.Count != 0)
704                    ((ThreadStackElement)threadStack.Pop()).ev.Set();
705            }//end if
706
707            if (!stop)
708                ProgressChanged(1, 1);
709        }
710
711        public void PostExecution()
712        {
713        }
714
715        public void Pause()
716        {
717        }
718
719        public void Stop()
720        {
721            stop = true;
722        }
723
724        public void Initialize()
725        {
726        }
727
728        public void Dispose()
729        {
730        }
731
732        #endregion
733
734        #region INotifyPropertyChanged Members
735
736        public event PropertyChangedEventHandler PropertyChanged;
737
738        public void OnPropertyChanged(string name)
739        {
740            if (PropertyChanged != null)
741            {
742                PropertyChanged(this, new PropertyChangedEventArgs(name));
743            }
744        }
745
746        #endregion
747
748        private void keyPatternChanged()
749        {
750            Pattern = new KeyPattern(controlMaster.getKeyPattern());
751        }
752
753        private void onStatusChanged(IControl sender, bool readyForExecution)
754        {
755            if (readyForExecution)
756            {
757                this.process((IControlEncryption)sender);
758            }
759        }
760
761        #region IControlEncryption Members
762
763        private IControlEncryption controlMaster;
764        [PropertyInfo(Direction.ControlMaster, "Control Master", "Used for bruteforcing", "", DisplayLevel.Beginner)]
765        public IControlEncryption ControlMaster
766        {
767            get { return controlMaster; }
768            set
769            {
770                if (controlMaster != null)
771                {
772                    controlMaster.keyPatternChanged -= keyPatternChanged;
773                    controlMaster.OnStatusChanged -= onStatusChanged;
774                }
775                if (value != null)
776                {
777                    Pattern = new KeyPattern(value.getKeyPattern());
778                    value.keyPatternChanged += keyPatternChanged;
779                    value.OnStatusChanged += onStatusChanged;
780                    controlMaster = value;
781                    OnPropertyChanged("ControlMaster");
782                   
783                }
784                else
785                    controlMaster = null;
786            }
787        }
788
789        #endregion
790
791        #region IControlCost Members
792
793        private IControlCost costMaster;
794        [PropertyInfo(Direction.ControlMaster, "Cost Master", "Used for cost calculation", "", DisplayLevel.Beginner)]
795        public IControlCost CostMaster
796        {
797            get { return costMaster; }
798            set
799            {
800                costMaster = value;
801            }
802        }
803
804        #endregion
805
806        public void GuiLogMessage(string message, NotificationLevel loglevel)
807        {
808            if (OnGuiLogNotificationOccured != null)
809                OnGuiLogNotificationOccured(this, new GuiLogEventArgs(message, this, loglevel));
810        }
811
812        public void ProgressChanged(double value, double max)
813        {
814            if (OnPluginProgressChanged != null)
815            {
816                OnPluginProgressChanged(this, new PluginProgressEventArgs(value, max));
817
818            }
819        }
820
821        private struct ValueKey
822        {
823            public double value;
824            public String key;
825            public byte[] decryption;
826        };
827    }
828}
Note: See TracBrowser for help on using the repository browser.