/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

using System;
using System.Collections;
using System.Collections.Specialized;
using System.Text;

using NUnit.Framework;
using Qpid.Sasl;
using Qpid.Sasl.Mechanisms;

namespace Qpid.Sasl.Tests.Mechanisms
{
   [TestFixture]
   public class DigestSaslClientTests : ISaslCallbackHandler
   {
      private const string USERNAME = "chris";
      private const string PASSWORD = "secret";
      private const string AUTHID = null;
      private const string PROTOCOL = "IMAP";
      private const string SERVERNAME = "elwood.innosoft.com";

      #region Digest Challenge Parsing Tests
      //
      // Digest Challenge Parsing Tests
      //

      [Test]
      public void CanParseSimpleString()
      {
         string challenge = "realm=\"elwood.innosoft.com\", algorithm=md5-sess";
         StringDictionary values = DigestChallenge.ParseParameters(challenge);
         Assert.AreEqual(2, values.Count);
         Assert.AreEqual("elwood.innosoft.com", values["realm"]);
         Assert.AreEqual("md5-sess", values["algorithm"]);
      }

      [Test]
      public void CanParseEscapedQuotes()
      {
         string challenge = "realm=\"elwood\\\".innosoft.com\", algorithm=md5-sess";
         StringDictionary values = DigestChallenge.ParseParameters(challenge);
         Assert.AreEqual(2, values.Count);
         Assert.AreEqual("elwood\\\".innosoft.com", values["realm"]);
         Assert.AreEqual("md5-sess", values["algorithm"]);
      }

      [Test]
      public void CanParseEmbeddedDelimiter()
      {
         string challenge = "realm=\"elwood,innosoft.com\", algorithm=md5-sess";
         StringDictionary values = DigestChallenge.ParseParameters(challenge);
         Assert.AreEqual(2, values.Count);
         Assert.AreEqual("elwood,innosoft.com", values["realm"]);
         Assert.AreEqual("md5-sess", values["algorithm"]);
      }

      [Test]
      public void CanParse1()
      {
         string challenge = "realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",qop=\"auth\",algorithm=md5-sess,charset=utf-8";
         DigestChallenge parsed = DigestChallenge.Parse(challenge);

         Assert.AreEqual("elwood.innosoft.com", parsed.Realm);
         Assert.AreEqual("OA6MG9tEQGm2hh", parsed.Nonce);
         Assert.Contains("auth", parsed.QopOptions);
         Assert.AreEqual("md5-sess", parsed.Algorithm);
         Assert.AreEqual("utf-8", parsed.Charset);
      }

      #endregion // Digest Challenge Parsing Tests


      #region Digest Response Tests
      //
      // Digest Response Tests
      //

      [Test]
      public void CanWriteResponse()
      {
         DigestResponse resp = new DigestResponse();
         resp.Username = "user";
         resp.Realm = "nowhere.com";
         resp.Nonce = "OA9BSXrbuRhWay";
         resp.Cnonce = "OA9BSuZWMSpW8m";
         resp.NonceCount = 16;
         resp.DigestUri = "acap/elwood.innosoft.com";
         resp.Response = "6084c6db3fede7352c551284490fd0fc";
         resp.Qop = "auth";
         resp.MaxBuffer = 65536;
         resp.Cipher = "3des";
         resp.Authzid = "user2";
         resp.AuthParam = "ap";
         resp.Charset = "utf-8";

         string expected = "username=\"user\",realm=\"nowhere.com\",nonce=\"OA9BSXrbuRhWay\",cnonce=\"OA9BSuZWMSpW8m\",nc=00000010,qop=auth,digest-uri=\"acap/elwood.innosoft.com\",response=\"6084c6db3fede7352c551284490fd0fc\",maxbuf=65536,charset=utf-8,cipher=3des,authzid=\"user2\",auth-param=\"ap\"";
         Assert.AreEqual(expected, resp.ToString());
      }

      [Test]
      public void CanWriteEscapedSecuence()
      {
         DigestResponse resp = new DigestResponse();
         resp.Username = "us\"er";

         string expected = "username=\"us\\\"er\",nc=00000000,maxbuf=0";
         Assert.AreEqual(expected, resp.ToString());
      }

      #endregion // Digest Response Tests


      #region Authentication Tests
      //
      // Authentication Tests
      //

      [Test]
      public void ReturnsRightMechanismName()
      {
         ISaslClient client = CreateClient();

         Assert.AreEqual("DIGEST-MD5", client.MechanismName);
      }

      [Test]
      public void HasInitialResponseReturnsFalse()
      {
         ISaslClient client = CreateClient();

         Assert.IsFalse(client.HasInitialResponse);
      }

      [Test]
      public void CanAuthenticate()
      {
         string challenge = "realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",qop=\"auth\",algorithm=md5-sess,charset=utf-8";
         DigestSaslClient client = CreateClient();
         client.Cnonce = "OA6MHXh6VqTrRk";

         byte[] bresp = client.EvaluateChallenge(Encoding.UTF8.GetBytes(challenge));
         string response = Encoding.UTF8.GetString(bresp);
         string expectedResp = "username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",cnonce=\"" +
            client.Cnonce + "\",nc=00000001,qop=auth,digest-uri=\"imap/elwood.innosoft.com\",response=\"d388dad90d4bbd760a152321f2143af7\",maxbuf=65536,charset=utf-8";

         Assert.AreEqual(expectedResp, response);
         Assert.IsFalse(client.IsComplete);

         string challenge2 = "rspauth=ea40f60335c427b5527b84dbabcdfffd";
         bresp = client.EvaluateChallenge(Encoding.UTF8.GetBytes(challenge2));
         // client responds with zero-length array
         Assert.AreEqual(0, bresp.Length);
         Assert.IsTrue(client.IsComplete);
      }

      [Test]
      [ExpectedException(typeof(ArgumentNullException))]
      public void ThrowsExceptionWhenChallengeIsMissing()
      {
         DigestSaslClient client = CreateClient();
         client.EvaluateChallenge(null);
      }


      [Test]
      [ExpectedException(typeof(SaslException))]
      public void ThrowsExceptionWhenNonceMissing()
      {
         string challenge = "realm=\"elwood.innosoft.com\"";
         DigestSaslClient client = CreateClient();

         client.EvaluateChallenge(Encoding.UTF8.GetBytes(challenge));
      }

      [Test]
      [ExpectedException(typeof(SaslException))]
      public void ThrowsExceptionWhenAlgorithmMissing()
      {
         string challenge = "realm=\"elwood.innosoft.com\",nonce=\"asdasadsad\"";
         DigestSaslClient client = CreateClient();

         client.EvaluateChallenge(Encoding.UTF8.GetBytes(challenge));
      }

      [Test]
      [ExpectedException(typeof(SaslException))]
      public void ThrowsExceptionWhenSecondChallengeInvalid()
      {
         string challenge = "realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",qop=\"auth\",algorithm=md5-sess,charset=utf-8";
         DigestSaslClient client = CreateClient();

         byte[] bresp = client.EvaluateChallenge(Encoding.UTF8.GetBytes(challenge));
         Encoding.UTF8.GetString(bresp);

         // repeat challenge 1, which is incorrect
         client.EvaluateChallenge(Encoding.UTF8.GetBytes(challenge));
      }

      private DigestSaslClient CreateClient()
      {
         return new DigestSaslClient(
            AUTHID, SERVERNAME, PROTOCOL, 
            new Hashtable(), this
            );
      }

      void ISaslCallbackHandler.Handle(ISaslCallback[] callbacks)
      {
         foreach ( ISaslCallback cb in callbacks )
         {
            if ( cb is NameCallback )
            {
               ((NameCallback)cb).Text = USERNAME;
            } else if ( cb is PasswordCallback )
            {
               ((PasswordCallback)cb).Text = PASSWORD;
            } else if ( cb is RealmCallback )
            {
               ((RealmCallback)cb).Text = SERVERNAME;
            }
         }
      }

      #endregion // Authentication Tests


   } // class DigestSaslClientTests

} // namespace Qpid.Sasl.Tests.Mechanisms
