DecryptionStack.cs
author Thomas
Thu, 14 Sep 2017 15:04:41 +0200
branchOUT-308
changeset 1833 45ccf9517e8e
parent 1807 202698c8b70e
child 1848 415a7457c221
permissions -rw-r--r--
Add dedicated decryption task for FPP messages
Thomas@1770
     1
´╗┐using System;
Thomas@1770
     2
using System.Collections.Generic;
Thomas@1833
     3
using System.Threading.Tasks;
Thomas@1770
     4
using Outlook = Microsoft.Office.Interop.Outlook;
Thomas@1770
     5
Thomas@1770
     6
namespace pEp
Thomas@1770
     7
{
Thomas@1785
     8
    internal class DecryptionStack : Stack<string>, IDisposable
Thomas@1770
     9
    {
Thomas@1778
    10
        private const int MAX_DECRYPTION_COUNT                       = 5;       // Maximum amount of concurrently decrypted items
Thomas@1770
    11
        private const int DECRYPTION_INTERVAL                        = 500;     // in ms
Thomas@1770
    12
Thomas@1770
    13
        private System.Windows.Forms.Timer decryptionTimer           = null;
Thomas@1770
    14
        private int decryptionCounter                                = 0;
Thomas@1770
    15
        private object mutexDecryptionCounter                        = new object();
Thomas@1770
    16
        private object mutexDecryptionStack                          = new object();
Thomas@1770
    17
Thomas@1770
    18
        /// <summary>
Thomas@1770
    19
        /// Default constructor.
Thomas@1770
    20
        /// </summary>
Thomas@1770
    21
        public DecryptionStack()
Thomas@1770
    22
        {
Thomas@1770
    23
            this.decryptionTimer = new System.Windows.Forms.Timer();
Thomas@1770
    24
            this.decryptionTimer.Interval = DECRYPTION_INTERVAL;
Thomas@1770
    25
            this.decryptionTimer.Tick += this.DecryptionTimer_Tick;
Thomas@1770
    26
        }
Thomas@1770
    27
Thomas@1770
    28
        /// <summary>
Thomas@1785
    29
        /// Implementing IDisposable
Thomas@1785
    30
        /// </summary>
Thomas@1785
    31
        public void Dispose()
Thomas@1785
    32
        {
Thomas@1785
    33
            this.decryptionTimer.Dispose();
Thomas@1785
    34
        }
Thomas@1785
    35
Thomas@1785
    36
        /// <summary>
Thomas@1770
    37
        /// Increments the decryption counter if maximum has not yet been reached.
Thomas@1770
    38
        /// </summary>
Thomas@1770
    39
        /// <returns>True if counter was encrypted, false if maximum has already been reached.</returns>
Thomas@1770
    40
        private bool IncrementDecryptionCounter()
Thomas@1770
    41
        {
Thomas@1770
    42
            bool incremented = false;
Thomas@1770
    43
Thomas@1770
    44
            lock (mutexDecryptionCounter)
Thomas@1770
    45
            {
Thomas@1770
    46
                if (this.decryptionCounter < MAX_DECRYPTION_COUNT)
Thomas@1770
    47
                {
Thomas@1770
    48
                    this.decryptionCounter++;
Thomas@1770
    49
                    incremented = true;
Thomas@1803
    50
                    Log.Verbose("DecryptionStack.IncrementDecryptionCounter: counter incremented to " + decryptionCounter);
Thomas@1770
    51
                }
Thomas@1770
    52
            }
Thomas@1770
    53
Thomas@1770
    54
            return incremented;
Thomas@1770
    55
        }
Thomas@1770
    56
Thomas@1770
    57
        /// <summary>
Thomas@1770
    58
        /// Decrements the decryption counter if it is bigger than 0.
Thomas@1770
    59
        /// </summary>
Thomas@1770
    60
        public void DecrementDecryptionCounter()
Thomas@1770
    61
        {
Thomas@1770
    62
            lock (mutexDecryptionCounter)
Thomas@1770
    63
            {
Thomas@1770
    64
                if (this.decryptionCounter > 0)
Thomas@1770
    65
                {
Thomas@1770
    66
                    this.decryptionCounter--;
Thomas@1803
    67
                    Log.Verbose("DecryptionStack.DecrementDecryptionCounter: counter decremented to " + decryptionCounter);
Thomas@1770
    68
                }
Thomas@1770
    69
            }
Thomas@1770
    70
        }
Thomas@1770
    71
Thomas@1770
    72
        /// <summary>
Thomas@1770
    73
        /// Resets the decryption counter to 0.
Thomas@1770
    74
        /// </summary>
Thomas@1770
    75
        private void ResetDecryptionCounter()
Thomas@1770
    76
        {
Thomas@1770
    77
            lock (mutexDecryptionCounter)
Thomas@1770
    78
            {
Thomas@1770
    79
                this.decryptionCounter = 0;
Thomas@1803
    80
                Log.Verbose("DecryptionStack.ResetDecryptionCounter: counter reset");
Thomas@1770
    81
            }
Thomas@1770
    82
        }
Thomas@1770
    83
Thomas@1770
    84
        /// <summary>
Thomas@1770
    85
        /// Locks the decryption stack and tries to get its first element.
Thomas@1770
    86
        /// Also checks if the following element(s) are duplicates of the one
Thomas@1770
    87
        /// returned and removes them in this case.
Thomas@1770
    88
        /// </summary>
Thomas@1770
    89
        /// <param name="entryId">The retrieved entryId or an empty string if none was retrieved.</param>
Thomas@1770
    90
        /// <returns>Tue if the operation retrieved successfully an entryId. Otherwise false.</returns>
Thomas@1778
    91
        private bool TryPop(out string entryId)
Thomas@1770
    92
        {
Thomas@1778
    93
            entryId = null;
Thomas@1770
    94
Thomas@1770
    95
            lock (mutexDecryptionStack)
Thomas@1770
    96
            {
Thomas@1770
    97
                try
Thomas@1770
    98
                {
Thomas@1770
    99
                    if (this.Count > 0)
Thomas@1770
   100
                    {
Thomas@1770
   101
                        entryId = this.Pop();
Thomas@1807
   102
                        Log.Verbose("DecryptionStack.TryPop: retrieved entryId " + entryId);
Thomas@1770
   103
                    }
Thomas@1770
   104
                }
Thomas@1770
   105
                catch (Exception ex)
Thomas@1770
   106
                {
Thomas@1770
   107
                    entryId = string.Empty;
Thomas@1770
   108
                    Log.Error("TryPopDecryptionStack: Error getting first element from stack. " + ex.ToString());
Thomas@1770
   109
                }
Thomas@1770
   110
Thomas@1770
   111
                // If an entryId could be retrieved, check following item(s)
Thomas@1770
   112
                if (string.IsNullOrEmpty(entryId) == false)
Thomas@1770
   113
                {
Thomas@1770
   114
                    do
Thomas@1770
   115
                    {
Thomas@1770
   116
                        if (this.Count > 0)
Thomas@1770
   117
                        {
Thomas@1778
   118
                            string nextEntryId = null;
Thomas@1770
   119
Thomas@1770
   120
                            try
Thomas@1770
   121
                            {
Thomas@1770
   122
                                nextEntryId = this.Peek();
Thomas@1770
   123
                            }
Thomas@1770
   124
                            catch (Exception ex)
Thomas@1770
   125
                            {
Thomas@1770
   126
                                Log.Error("TryPopDecryptionStack: Error removing duplicates from stack. " + ex.ToString());
Thomas@1770
   127
                            }
Thomas@1770
   128
Thomas@1770
   129
                            // If next item equals the retrieved entryId, remove it
Thomas@1770
   130
                            if (string.Equals(entryId, nextEntryId))
Thomas@1770
   131
                            {
Thomas@1770
   132
                                try
Thomas@1770
   133
                                {
Thomas@1770
   134
                                    this.Pop();
Thomas@1770
   135
                                }
Thomas@1770
   136
                                catch (Exception ex)
Thomas@1770
   137
                                {
Thomas@1770
   138
                                    Log.Error("TryPopDecryptionStack: Error removing duplicates from stack. " + ex.ToString());
Thomas@1770
   139
                                    break;
Thomas@1770
   140
                                }
Thomas@1770
   141
                            }
Thomas@1770
   142
                            else
Thomas@1770
   143
                            {
Thomas@1770
   144
                                break;
Thomas@1770
   145
                            }
Thomas@1770
   146
                        }
Thomas@1770
   147
                        else
Thomas@1770
   148
                        {
Thomas@1770
   149
                            break;
Thomas@1770
   150
                        }
Thomas@1770
   151
Thomas@1770
   152
                    } while (true);
Thomas@1770
   153
                }
Thomas@1770
   154
            }
Thomas@1770
   155
Thomas@1770
   156
            return (string.IsNullOrEmpty(entryId) == false);
Thomas@1770
   157
        }
Thomas@1770
   158
Thomas@1770
   159
        /// <summary>
Thomas@1770
   160
        /// Locks the decryption stack and tries to push the given entryId.
Thomas@1770
   161
        /// The item is not pushed if the last entryId is the same.
Thomas@1770
   162
        /// </summary>
Thomas@1770
   163
        /// <param name="entryId">The entryId to push to the stack.</param>
Thomas@1770
   164
        /// <returns>True if item was pushed, otherwise false.</returns>
Thomas@1778
   165
        public bool TryPush(string entryId)
Thomas@1770
   166
        {
Thomas@1770
   167
            bool pushed = false;
Thomas@1778
   168
            string currentId = null;
Thomas@1770
   169
Thomas@1770
   170
            if (string.IsNullOrEmpty(entryId))
Thomas@1770
   171
            {
Thomas@1770
   172
                return false;
Thomas@1770
   173
            }
Thomas@1770
   174
Thomas@1770
   175
            // Push item to stack if not already on top of it
Thomas@1770
   176
            lock (mutexDecryptionStack)
Thomas@1770
   177
            {
Thomas@1770
   178
                if (this.Count > 0)
Thomas@1770
   179
                {
Thomas@1770
   180
                    try
Thomas@1770
   181
                    {
Thomas@1770
   182
                        currentId = this.Peek();
Thomas@1770
   183
                    }
Thomas@1770
   184
                    catch (Exception ex)
Thomas@1770
   185
                    {
Thomas@1778
   186
                        Log.Error("DecryptionStack.TryPush: Error peeking item. " + ex.ToString());
Thomas@1770
   187
                    }
Thomas@1770
   188
                }
Thomas@1770
   189
Thomas@1770
   190
                if (entryId?.Equals(currentId) == false)
Thomas@1770
   191
                {
Thomas@1770
   192
                    this.Push(entryId);
Thomas@1770
   193
                    pushed = true;
Thomas@1803
   194
                    Log.Verbose("DecryptionStack.TryPush: pushed entryId " + entryId);
Thomas@1770
   195
                }
Thomas@1770
   196
            }
Thomas@1778
   197
Thomas@1778
   198
            // If item was pushed, enable timer and start decryption
Thomas@1778
   199
            if (pushed &&
Thomas@1778
   200
                this.decryptionTimer.Enabled == false)
Thomas@1778
   201
            {
Thomas@1778
   202
                this.decryptionTimer.Start();
Thomas@1778
   203
                this.DecryptionTimer_Tick(null, null);
Thomas@1803
   204
                Log.Verbose("DecryptionStack.TryPush: decryptionTimer started");
Thomas@1778
   205
            }
Thomas@1778
   206
Thomas@1770
   207
            return pushed;
Thomas@1770
   208
        }
Thomas@1770
   209
Thomas@1770
   210
        /// <summary>
Thomas@1778
   211
        /// Event handler for when the decryption timer has elapsed.
Thomas@1770
   212
        /// </summary>
Thomas@1770
   213
        private void DecryptionTimer_Tick(object sender, EventArgs e)
Thomas@1770
   214
        {
Thomas@1770
   215
            Outlook.MailItem omi = null;
Thomas@1778
   216
            string entryId = null;
Thomas@1770
   217
            CryptableMailItem cmi = null;
Thomas@1770
   218
Thomas@1770
   219
            // Try to increment the decryption counter
Thomas@1770
   220
            if (this.IncrementDecryptionCounter() == false)
Thomas@1770
   221
            {
Thomas@1803
   222
                Log.Verbose("DecryptionStack.DecryptionTimer_Tick: decryption queue full");
Thomas@1770
   223
                return;
Thomas@1770
   224
            }
Thomas@1770
   225
Thomas@1778
   226
            // Pop item from the decryption stack
Thomas@1778
   227
            if (this.TryPop(out entryId))
Thomas@1770
   228
            {
Thomas@1778
   229
                // Get mail item and set process flag to true
Thomas@1778
   230
                try
Thomas@1770
   231
                {
Thomas@1778
   232
                    omi = Globals.ThisAddIn.Application.Session.GetItemFromID(entryId);
Thomas@1778
   233
                }
Thomas@1778
   234
                catch (Exception ex)
Thomas@1778
   235
                {
Thomas@1778
   236
                    omi = null;
Thomas@1778
   237
                    Log.Error("DecryptionTimer_Tick: Error getting item from ID. " + ex.ToString());
Thomas@1778
   238
                }
Thomas@1770
   239
Thomas@1784
   240
                if ((omi != null) &&
Thomas@1784
   241
                    (omi.GetIsPEPEnabled() || omi.GetIsDecryptAlwaysEnabled()))
Thomas@1778
   242
                {
Thomas@1833
   243
                    /* Forcefully protected messages need a special treatment as it might be needed to open a
Thomas@1833
   244
                     * handshake dialog, which has to happen from the main thread. The constructors of those 
Thomas@1833
   245
                     * dialogs cannot be called from the background worker. 
Thomas@1833
   246
                     */ 
Thomas@1833
   247
                    if (omi.GetForceProtection())
Thomas@1833
   248
                    {
Thomas@1833
   249
                        Log.Verbose("DecryptionStack.DecryptionTimer_Tick: Force Protection Protocol Message found. Processing...");
Thomas@1833
   250
                        var fppMessage = new FPPMessage(omi);
Thomas@1833
   251
                        fppMessage.ProcessIncoming();
Thomas@1833
   252
                    }
Thomas@1833
   253
                    else
Thomas@1833
   254
                    {
Thomas@1833
   255
                        // Process item
Thomas@1833
   256
                        cmi = new CryptableMailItem(omi, true);
Thomas@1778
   257
Thomas@1833
   258
                        /* Note: Make sure to dispose the MailItem after processing (pass true).
Thomas@1833
   259
                         * This is needed as decryption is asynchronous but the MailItem reference still needs to be released.
Thomas@1833
   260
                         * Do NOT use omi or cmi references after this point!
Thomas@1833
   261
                         */
Thomas@1833
   262
                        cmi.StartProcessing(true);
Thomas@1833
   263
                        Log.Verbose("DecryptionStack.DecryptionTimer_Tick: started decryption of item with entryId " + omi.EntryID);
Thomas@1833
   264
                    }
Thomas@1770
   265
                }
Thomas@1770
   266
                else
Thomas@1770
   267
                {
Thomas@1778
   268
                    // If we don't process, decrement the decryption counter again
Thomas@1778
   269
                    this.DecrementDecryptionCounter();
Thomas@1803
   270
                    Log.Verbose("DecryptionStack.DecryptionTimer_Tick: item will not be processed");
Thomas@1770
   271
                }
Thomas@1770
   272
            }
Thomas@1802
   273
            else
Thomas@1770
   274
            {
Thomas@1802
   275
                // If no more item is on the stack, stop the timer and set decryption counter to 0
Thomas@1802
   276
                if (this.Count <= 0)
Thomas@1802
   277
                {
Thomas@1802
   278
                    this.ResetDecryptionCounter();
Thomas@1802
   279
                    this.decryptionTimer.Stop();
Thomas@1803
   280
                    Log.Verbose("DecryptionStack.DecryptionTimer_Tick: counter reset and timer stopped");
Thomas@1802
   281
                }
Thomas@1770
   282
            }
Thomas@1770
   283
        }
Thomas@1770
   284
    }
Thomas@1770
   285
}