/// <summary>
        /// Encrypt a stream with a given set of headers and write to an output stream. The caller is responsible for consistency and completeness
        /// of the headers. Headers that are not known until encryption and compression are added here.
        /// </summary>
        /// <param name="outputDocumentHeaders"></param>
        /// <param name="inputStream"></param>
        /// <param name="outputStream"></param>
        public void EncryptTo(DocumentHeaders outputDocumentHeaders, Stream inputStream, Stream outputStream, AxCryptOptions options, ProgressContext progress)
        {
            if (outputDocumentHeaders == null)
            {
                throw new ArgumentNullException("outputDocumentHeaders");
            }
            if (inputStream == null)
            {
                throw new ArgumentNullException("inputStream");
            }
            if (outputStream == null)
            {
                throw new ArgumentNullException("outputStream");
            }
            if (progress == null)
            {
                throw new ArgumentNullException("progress");
            }
            if (!outputStream.CanSeek)
            {
                throw new ArgumentException("The output stream must support seek in order to back-track and write the HMAC.");
            }
            if (options.HasMask(AxCryptOptions.EncryptWithCompression) && options.HasMask(AxCryptOptions.EncryptWithoutCompression))
            {
                throw new ArgumentException("Invalid options, cannot specify both with and without compression.");
            }
            if (!options.HasMask(AxCryptOptions.EncryptWithCompression) && !options.HasMask(AxCryptOptions.EncryptWithoutCompression))
            {
                throw new ArgumentException("Invalid options, must specify either with or without compression.");
            }
            bool isCompressed = options.HasMask(AxCryptOptions.EncryptWithCompression);
            outputDocumentHeaders.IsCompressed = isCompressed;
            outputDocumentHeaders.WriteWithoutHmac(outputStream);
            using (ICryptoTransform encryptor = DataCrypto.CreateEncryptingTransform())
            {
                long outputStartPosition = outputStream.Position;
                using (CryptoStream encryptingStream = new CryptoStream(new NonClosingStream(outputStream), encryptor, CryptoStreamMode.Write))
                {
                    if (isCompressed)
                    {
                        EncryptWithCompressionInternal(outputDocumentHeaders, inputStream, encryptingStream, progress);
                    }
                    else
                    {
                        outputDocumentHeaders.PlaintextLength = CopyToWithCount(inputStream, encryptingStream, progress);
                    }
                }
                outputStream.Flush();
                outputDocumentHeaders.CipherTextLength = outputStream.Position - outputStartPosition;
                using (HmacStream outputHmacStream = new HmacStream(outputDocumentHeaders.HmacSubkey.Key, outputStream))
                {
                    outputDocumentHeaders.WriteWithHmac(outputHmacStream);
                    outputHmacStream.ReadFrom(outputStream);
                    outputDocumentHeaders.Hmac = outputHmacStream.HmacResult;
                }

                // Rewind and rewrite the headers, now with the updated HMAC
                outputDocumentHeaders.WriteWithoutHmac(outputStream);
                outputStream.Position = outputStream.Length;
            }
        }
        public static void TestHmacStream()
        {
            Assert.Throws<ArgumentNullException>(() =>
            {
                using (HmacStream hmacStream = new HmacStream(null)) { }
            });

            AesKey key = new AesKey(new byte[16]);
            using (HmacStream hmacStream = new HmacStream(key))
            {
                Assert.That(hmacStream.CanRead, Is.False, "HmacStream does not support reading.");
                Assert.That(hmacStream.CanSeek, Is.False, "HmacStream does not support seeking.");
                Assert.That(hmacStream.CanWrite, Is.True, "HmacStream does support writing.");

                Assert.Throws<NotSupportedException>(() =>
                {
                    byte[] buffer = new byte[5];
                    hmacStream.Read(buffer, 0, buffer.Length);
                });

                Assert.Throws<NotSupportedException>(() =>
                {
                    hmacStream.Seek(0, SeekOrigin.Begin);
                });

                Assert.Throws<NotSupportedException>(() =>
                {
                    hmacStream.SetLength(0);
                });

                Assert.Throws<ArgumentNullException>(() =>
                {
                    hmacStream.ReadFrom(null);
                });

                hmacStream.Write(new byte[10], 0, 10);
                using (Stream dataStream = new MemoryStream())
                {
                    dataStream.Write(new byte[10], 0, 10);
                    dataStream.Position = 0;
                    hmacStream.ReadFrom(dataStream);
                }
                Assert.That(hmacStream.Position, Is.EqualTo(20), "There are 20 bytes written so the position should be 20.");
                Assert.That(hmacStream.Length, Is.EqualTo(20), "There are 20 bytes written so the position should be 20.");
                hmacStream.Flush();
                Assert.That(hmacStream.Position, Is.EqualTo(20), "Nothing should change after Flush(), this is not a buffered stream.");
                Assert.That(hmacStream.Length, Is.EqualTo(20), "Nothing should change after Flush(), this is not a buffered stream.");

                Assert.Throws<NotSupportedException>(() =>
                {
                    hmacStream.Position = 0;
                }, "Position is not supported.");

                DataHmac dataHmac = hmacStream.HmacResult;
                Assert.That(dataHmac.GetBytes(), Is.EquivalentTo(new byte[] { 0x62, 0x6f, 0x2c, 0x61, 0xc7, 0x68, 0x00, 0xb3, 0xa6, 0x8d, 0xf9, 0x55, 0x95, 0xbc, 0x1f, 0xd1 }), "The HMAC of 20 bytes of zero with 128-bit AesKey all zero should be this.");

                Assert.Throws<InvalidOperationException>(() =>
                {
                    hmacStream.Write(new byte[1], 0, 1);
                }, "Can't write to the stream after checking and thus finalizing the HMAC");

                // This also implicitly covers double-dispose since we're in a using block.
                hmacStream.Dispose();

                Assert.Throws<ObjectDisposedException>(() =>
                {
                    DataHmac invalidDataHmac = hmacStream.HmacResult;

                    // Remove FxCop warning
                    Object.Equals(invalidDataHmac, null);
                });
                Assert.Throws<ObjectDisposedException>(() =>
                {
                    hmacStream.Write(new byte[1], 0, 1);
                });
                Assert.Throws<ObjectDisposedException>(() =>
                {
                    using (Stream stream = new MemoryStream())
                    {
                        hmacStream.ReadFrom(stream);
                    }
                });
            }
        }