context: using System;
  using System.IO;
  
  using Org.BouncyCastle.Crypto;
  using Org.BouncyCastle.Crypto.Macs;
  using Org.BouncyCastle.Crypto.Parameters;
  using Org.BouncyCastle.Utilities;
  
  namespace Org.BouncyCastle.Crypto.Modes
  {
      /**
      * Implements the Counter with Cipher Block Chaining mode (CCM) detailed in
      * NIST Special Publication 800-38C.
      * <p>
      * <b>Note</b>: this mode is a packet mode - it needs all the data up front.
      * </p>
      */
      public class CcmBlockCipher
          : IAeadBlockCipher
      {
          private static readonly int BlockSize = 16;
  
          private readonly IBlockCipher	cipher;
          private readonly byte[]			macBlock;
          private bool					forEncryption;
          private byte[]					nonce;
          private byte[]					initialAssociatedText;
          private int						macSize;
          private ICipherParameters		keyParam;
          private readonly MemoryStream   associatedText = new MemoryStream();
          private readonly MemoryStream   data = new MemoryStream();
  
          /**
          * Basic constructor.
          *
          * @param cipher the block cipher to be used.
          */
          public CcmBlockCipher(
              IBlockCipher cipher)
          {
              this.cipher = cipher;
              this.macBlock = new byte[BlockSize];
  
              if (cipher.GetBlockSize() != BlockSize)
                  throw new ArgumentException("cipher required with a block size of " + BlockSize + ".");
          }
  
          /**
          * return the underlying block cipher that we are wrapping.
          *
          * @return the underlying block cipher that we are wrapping.
          */
          public virtual IBlockCipher GetUnderlyingCipher()
          {
              return cipher;
          }
  
          public virtual void Init(
              bool				forEncryption,
              ICipherParameters	parameters)
          {
              this.forEncryption = forEncryption;
  
              ICipherParameters cipherParameters;
              if (parameters is AeadParameters)
              {
                  AeadParameters param = (AeadParameters) parameters;
  
                  nonce = param.GetNonce();
                  initialAssociatedText = param.GetAssociatedText();
                  macSize = param.MacSize / 8;
                  cipherParameters = param.Key;
              }
              else if (parameters is ParametersWithIV)
              {
                  ParametersWithIV param = (ParametersWithIV) parameters;
  
                  nonce = param.GetIV();
                  initialAssociatedText = null;
                  macSize = macBlock.Length / 2;
                  cipherParameters = param.Parameters;
              }
              else
              {
                  throw new ArgumentException("invalid parameters passed to CCM");
              }
  
              // NOTE: Very basic support for key re-use, but no performance gain from it
              if (cipherParameters != null)
              {
                  keyParam = cipherParameters;
              }
  
              if (nonce == null || nonce.Length < 7 || nonce.Length > 13)
              {
                  throw new ArgumentException("nonce must have length from 7 to 13 octets");
              }
  
              Reset();
          }
  
          public virtual string AlgorithmName
          {
              get { return cipher.AlgorithmName + "/CCM"; }
          }
  
          public virtual int GetBlockSize()
          {
              return cipher.GetBlockSize();
          }
  
          public virtual void ProcessAadByte(byte input)
          {
              associatedText.WriteByte(input);
          }
  
          public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len)
          {
              // TODO: Process AAD online
              associatedText.Write(inBytes, inOff, len);
          }
  
          public virtual int ProcessByte(
              byte	input,
              byte[]	outBytes,
              int		outOff)
          {
              data.WriteByte(input);
  
              return 0;
          }
  
          public virtual int ProcessBytes(
              byte[]	inBytes,
              int		inOff,
              int		inLen,
              byte[]	outBytes,
              int		outOff)
          {
              Check.DataLength(inBytes, inOff, inLen, "Input buffer too short");
  
              data.Write(inBytes, inOff, inLen);
  
              return 0;
          }
  
          public virtual int DoFinal(
              byte[]	outBytes,
              int		outOff)
          {
  #if PORTABLE
              byte[] input = data.ToArray();
              int inLen = input.Length;
  #else
              byte[] input = data.GetBuffer();
              int inLen = (int)data.Position;
  #endif
  
              int len = ProcessPacket(input, 0, inLen, outBytes, outOff);
  
              Reset();
  
              return len;
          }
  
          public virtual void Reset()
          {
              cipher.Reset();
              associatedText.SetLength(0);
              data.SetLength(0);
          }
  
          /**
          * Returns a byte array containing the mac calculated as part of the
          * last encrypt or decrypt operation.
          *
          * @return the last mac calculated.
          */
          public virtual byte[] GetMac()
          {
              return Arrays.CopyOfRange(macBlock, 0, macSize);
          }
  
          public virtual int GetUpdateOutputSize(
              int len)
          {
              return 0;
          }
  
          public virtual int GetOutputSize(
              int len)
          {
              int totalData = (int)data.Length + len;
  
              if (forEncryption)
              {
                  return totalData + macSize;
              }
  
              return totalData < macSize ? 0 : totalData - macSize;
          }
  
          /**
           * Process a packet of data for either CCM decryption or encryption.
           *
           * @param in data for processing.
           * @param inOff offset at which data starts in the input array.
           * @param inLen length of the data in the input array.
           * @return a byte array containing the processed input..
           * @throws IllegalStateException if the cipher is not appropriately set up.
           * @throws InvalidCipherTextException if the input data is truncated or the mac check fails.
           */
          public virtual byte[] ProcessPacket(byte[] input, int inOff, int inLen)
          {
              byte[] output;
  
              if (forEncryption)
              {
                  output = new byte[inLen + macSize];
              }
              else
              {
                  if (inLen < macSize)
                      throw new InvalidCipherTextException("data too short");
  
                  output = new byte[inLen - macSize];
              }
  
              ProcessPacket(input, inOff, inLen, output, 0);
  
              return output;
          }
  
          /**
           * Process a packet of data for either CCM decryption or encryption.
           *
           * @param in data for processing.
           * @param inOff offset at which data starts in the input array.
           * @param inLen length of the data in the input array.
           * @param output output array.
           * @param outOff offset into output array to start putting processed bytes.
           * @return the number of bytes added to output.
           * @throws IllegalStateException if the cipher is not appropriately set up.
           * @throws InvalidCipherTextException if the input data is truncated or the mac check fails.
           * @throws DataLengthException if output buffer too short.
           */
          public virtual int ProcessPacket(byte[] input, int inOff, int inLen, byte[] output, int outOff)
          {
              // TODO: handle null keyParam (e.g. via RepeatedKeySpec)
              // Need to keep the CTR and CBC Mac parts around and reset
  
gt:             if (keyParam == null)