/*
 *
 * 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.
 *
 */
#include "qpid/broker/amqp/Sasl.h"
#include "qpid/broker/Broker.h"
#include "qpid/log/Statement.h"
#include "qpid/framing/ProtocolInitiation.h"
#include "qpid/sys/OutputControl.h"
#include "qpid/sys/SecurityLayer.h"
#include <boost/format.hpp>
#include <vector>

namespace qpid {
namespace broker {
namespace amqp {

Sasl::Sasl(qpid::sys::OutputControl& o, const std::string& id, qpid::broker::Broker& broker, std::auto_ptr<qpid::SaslServer> auth)
    : qpid::amqp::SaslServer(id), out(o), connection(out, id, broker, true),
      authenticator(auth),
      state(INCOMPLETE), writeHeader(true), haveOutput(true)
{
    out.activateOutput();
    mechanisms(authenticator->getMechanisms());
}

Sasl::~Sasl() {}

size_t Sasl::decode(const char* buffer, size_t size)
{
    if (state == AUTHENTICATED) {
        if (securityLayer.get()) return securityLayer->decode(buffer, size);
        else return connection.decode(buffer, size);
    } else if (state == INCOMPLETE && size) {
        size_t decoded = read(buffer, size);
        QPID_LOG(trace, id << " Sasl::decode(" << size << "): " << decoded);
        return decoded;
    } else {
        return 0;
    }
}

size_t Sasl::encode(char* buffer, size_t size)
{
    if (state == AUTHENTICATED) {
        if (securityLayer.get()) return securityLayer->encode(buffer, size);
        else return connection.encode(buffer, size);
    } else {
        size_t encoded = 0;
        if (writeHeader) {
            encoded += writeProtocolHeader(buffer, size);
            if (!encoded) return 0;
            writeHeader = false;
        }
        if (encoded < size) {
            encoded += write(buffer + encoded, size - encoded);
        }
        if (state == SUCCESS_PENDING) {
            state = AUTHENTICATED;
        } else if (state == FAILURE_PENDING) {
            state = FAILED;
        } else {
            haveOutput = (encoded == size);
        }
        QPID_LOG(trace, id << " Sasl::encode(" << size << "): " << encoded);
        return encoded;
    }
}

bool Sasl::canEncode()
{
    if (state == AUTHENTICATED) {
        if (securityLayer.get()) return securityLayer->canEncode();
        else return connection.canEncode();
    } else {
        return haveOutput;
    }
}

void Sasl::closed()
{
    if (state == AUTHENTICATED) {
        connection.closed();
    } else {
        QPID_LOG(info, id << " Connection closed prior to authentication completing");
        state = FAILED;
    }
}
bool Sasl::isClosed() const
{
    if (state == AUTHENTICATED) {
        return connection.isClosed();
    } else {
        return state == FAILED;
    }
}

framing::ProtocolVersion Sasl::getVersion() const
{
    return connection.getVersion();
}
namespace {
const std::string EMPTY;
}

void Sasl::init(const std::string& mechanism, const std::string* response, const std::string* /*hostname*/)
{
    QPID_LOG_CAT(debug, protocol, id << " Received SASL-INIT(" << mechanism << ", " << (response ? *response : EMPTY) <<  ")");
    //TODO: what should we do with hostname here?
    std::string c;
    respond(authenticator->start(mechanism, response, c), c);
    connection.setSaslMechanism(mechanism);
}

void Sasl::response(const std::string* r)
{
    QPID_LOG_CAT(debug, protocol, id << " Received SASL-RESPONSE(" << (r ? *r : EMPTY) <<  ")");
    std::string c;
    respond(authenticator->step(r, c), c);
}

void Sasl::respond(qpid::SaslServer::Status status, const std::string& chllnge)
{
    switch (status) {
      case qpid::SaslServer::OK:
        connection.setUserid(authenticator->getUserid());
        completed(true);
        //can't set authenticated & failed until we have actually sent the outcome
        state = SUCCESS_PENDING;
        securityLayer = authenticator->getSecurityLayer(65535);
        if (securityLayer.get()) {
            QPID_LOG_CAT(info, security, id << " Security layer installed");
            securityLayer->init(&connection);
            connection.setSaslSsf(securityLayer->getSsf());
        }
        QPID_LOG_CAT(info, security, id << " Authenticated as " << authenticator->getUserid());
        break;
      case qpid::SaslServer::FAIL:
        completed(false);
        state = FAILURE_PENDING;
        QPID_LOG_CAT(info, security, id << " Failed to authenticate");
        break;
      case qpid::SaslServer::CHALLENGE:
        challenge(&chllnge);
        QPID_LOG_CAT(info, security, id << " Challenge issued");
        break;
    }
    haveOutput = true;
    out.activateOutput();
}
}}} // namespace qpid::broker::amqp
