

#include "CloseSessionCommand.h"
#include <protocols/secure_channel/StatusReport.h>
#include <system/SystemPacketBuffer.h>

using namespace chip;
using namespace chip::Protocols;

CHIP_ERROR CloseSessionCommand::RunCommand()
{
    CommissioneeDeviceProxy * commissioneeDeviceProxy = nullptr;
    if (CHIP_NO_ERROR == CurrentCommissioner().GetDeviceBeingCommissioned(mDestinationId, &commissioneeDeviceProxy))
    {
        return CloseSession(commissioneeDeviceProxy);
    }

    return CurrentCommissioner().GetConnectedDevice(mDestinationId, &mOnDeviceConnectedCallback,
                                                    &mOnDeviceConnectionFailureCallback);
}

CHIP_ERROR CloseSessionCommand::CloseSession(DeviceProxy * device)
{
    VerifyOrReturnError(device->GetSecureSession().HasValue(), CHIP_ERROR_INCORRECT_STATE);

    // TODO perhaps factor out this code into something on StatusReport that
    // takes an exchange and maybe a SendMessageFlags?
    SecureChannel::StatusReport statusReport(SecureChannel::GeneralStatusCode::kSuccess, SecureChannel::Id,
                                             SecureChannel::kProtocolCodeCloseSession);

    size_t reportSize = statusReport.Size();
    Encoding::LittleEndian::PacketBufferWriter bbuf(MessagePacketBuffer::New(reportSize), reportSize);
    statusReport.WriteToBuffer(bbuf);

    System::PacketBufferHandle msg = bbuf.Finalize();
    VerifyOrReturnError(!msg.IsNull(), CHIP_ERROR_NO_MEMORY);

    auto * exchange = device->GetExchangeManager()->NewContext(device->GetSecureSession().Value(), nullptr);
    VerifyOrReturnError(exchange != nullptr, CHIP_ERROR_NO_MEMORY);

    // Per spec, CloseSession reports are always sent with MRP disabled.
    CHIP_ERROR err =
        exchange->SendMessage(SecureChannel::MsgType::StatusReport, std::move(msg), Messaging::SendMessageFlags::kNoAutoRequestAck);
    if (err == CHIP_NO_ERROR)
    {
        SetCommandExitStatus(CHIP_NO_ERROR);
    }
    else
    {
        exchange->Close();
    }

    return err;
}

void CloseSessionCommand::OnDeviceConnectedFn(void * context, OperationalDeviceProxy * device)
{
    auto * command = reinterpret_cast<CloseSessionCommand *>(context);
    VerifyOrReturn(command != nullptr, ChipLogError(chipTool, "OnDeviceConnectedFn: context is null"));

    CHIP_ERROR err = command->CloseSession(device);
    VerifyOrReturn(CHIP_NO_ERROR == err, command->SetCommandExitStatus(err));
}

void CloseSessionCommand::OnDeviceConnectionFailureFn(void * context, PeerId peerId, CHIP_ERROR err)
{
    LogErrorOnFailure(err);

    auto * command = reinterpret_cast<CloseSessionCommand *>(context);
    VerifyOrReturn(command != nullptr, ChipLogError(chipTool, "OnDeviceConnectionFailureFn: context is null"));
    command->SetCommandExitStatus(err);
}
