/*
 *  Copyright (c) 2022, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file
 *   This file implements IP4 headers processing.
 */

#include "ip4_types.hpp"

#include "common/numeric_limits.hpp"
#include "common/offset_range.hpp"
#include "net/ip6_address.hpp"

namespace ot {
namespace Ip4 {

Error Address::FromString(const char *aString, char aTerminatorChar)
{
    constexpr char kSeparatorChar = '.';

    Error       error = kErrorParse;
    const char *cur   = aString;

    for (uint8_t index = 0;; index++)
    {
        SuccessOrExit(StringParseUint8(cur, mFields.m8[index]));

        if (index == sizeof(Address) - 1)
        {
            break;
        }

        VerifyOrExit(*cur == kSeparatorChar);
        cur++;
    }

    VerifyOrExit(*cur == aTerminatorChar);
    error = kErrorNone;

exit:
    return error;
}

Error Address::ExtractFromIp4MappedIp6Address(const Ip6::Address &aIp6Address)
{
    Error error = kErrorNone;

    VerifyOrExit(aIp6Address.IsIp4Mapped(), error = kErrorParse);
    InitFrom(&aIp6Address.GetBytes()[12]);

exit:
    return error;
}

void Address::ExtractFromIp6Address(uint8_t aPrefixLength, const Ip6::Address &aIp6Address)
{
    // The prefix length must be 32, 40, 48, 56, 64, 96. IPv4 bytes are added
    // after the prefix, skipping over the bits 64 to 71 (byte at `kSkipIndex`)
    // which must be set to zero. The suffix is set to zero (per RFC 6052).
    //
    //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    //    |PL| 0-------------32--40--48--56--64--72--80--88--96--104---------|
    //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    //    |32|     prefix    |v4(32)         | u | suffix                    |
    //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    //    |40|     prefix        |v4(24)     | u |(8)| suffix                |
    //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    //    |48|     prefix            |v4(16) | u | (16)  | suffix            |
    //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    //    |56|     prefix                |(8)| u |  v4(24)   | suffix        |
    //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    //    |64|     prefix                    | u |   v4(32)      | suffix    |
    //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    //    |96|     prefix                                    |    v4(32)     |
    //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

    constexpr uint8_t kSkipIndex = 8;

    uint8_t ip6Index;

    OT_ASSERT(Ip6::Prefix::IsValidNat64PrefixLength(aPrefixLength));

    ip6Index = aPrefixLength / kBitsPerByte;

    for (uint8_t &i : mFields.m8)
    {
        if (ip6Index == kSkipIndex)
        {
            ip6Index++;
        }

        i = aIp6Address.GetBytes()[ip6Index++];
    }
}

void Address::SynthesizeFromCidrAndHost(const Cidr &aCidr, const uint32_t aHost)
{
    mFields.m32 = (aCidr.mAddress.mFields.m32 & aCidr.SubnetMask()) | (BigEndian::HostSwap32(aHost) & aCidr.HostMask());
}

void Address::ToString(StringWriter &aWriter) const
{
    aWriter.Append("%u.%u.%u.%u", mFields.m8[0], mFields.m8[1], mFields.m8[2], mFields.m8[3]);
}

void Address::ToString(char *aBuffer, uint16_t aSize) const
{
    StringWriter writer(aBuffer, aSize);

    ToString(writer);
}

Address::InfoString Address::ToString(void) const
{
    InfoString string;

    ToString(string);

    return string;
}

Error Cidr::FromString(const char *aString)
{
    constexpr char     kSlashChar     = '/';
    constexpr uint16_t kMaxCidrLength = 32;

    Error       error = kErrorParse;
    const char *cur;

    SuccessOrExit(AsCoreType(&mAddress).FromString(aString, kSlashChar));

    cur = StringFind(aString, kSlashChar);
    VerifyOrExit(cur != nullptr);
    cur++;

    SuccessOrExit(StringParseUint8(cur, mLength, kMaxCidrLength));
    VerifyOrExit(*cur == kNullChar);

    error = kErrorNone;

exit:
    return error;
}

void Cidr::ToString(StringWriter &aWriter) const
{
    AsCoreType(&mAddress).ToString(aWriter);
    aWriter.Append("/%u", mLength);
}

void Cidr::ToString(char *aBuffer, uint16_t aSize) const
{
    StringWriter writer(aBuffer, aSize);

    ToString(writer);
}

Cidr::InfoString Cidr::ToString(void) const
{
    InfoString string;

    ToString(string);

    return string;
}

bool Cidr::operator==(const Cidr &aOther) const
{
    return (mLength == aOther.mLength) && (CountMatchingBits(GetBytes(), aOther.GetBytes(), mLength) >= mLength);
}

void Cidr::Set(const uint8_t *aAddress, uint8_t aLength)
{
    memcpy(mAddress.mFields.m8, aAddress, Ip4::Address::kSize);
    mLength = aLength;
}

Error Header::ParseFrom(const Message &aMessage)
{
    Error error = kErrorParse;

    SuccessOrExit(aMessage.Read(0, *this));
    VerifyOrExit(IsValid());
    VerifyOrExit(GetTotalLength() == aMessage.GetLength());

    if (GetIhl() > kMinIhl)
    {
        VerifyOrExit(!HasSourceRouteOption(aMessage));
    }

    error = kErrorNone;

exit:
    return error;
}

bool Header::HasSourceRouteOption(const Message &aMessage) const
{
    bool        hasSourceRoute = false;
    uint16_t    headerLen      = GetHeaderLength();
    OffsetRange range;

    VerifyOrExit(headerLen <= aMessage.GetLength());
    range.InitFromRange(sizeof(Header), headerLen);

    while (!range.IsEmpty())
    {
        uint8_t optionType;
        uint8_t optionLen;

        SuccessOrExit(aMessage.ReadAndAdvance(range, optionType));

        if (optionType == kOptionEnd)
        {
            break;
        }

        if (optionType == kOptionNop)
        {
            continue;
        }

        SuccessOrExit(aMessage.Read(range, optionLen));
        VerifyOrExit(optionLen >= 2 && range.Contains(optionLen - 1));

        if (optionType == kOptionLsrr || optionType == kOptionSsrr)
        {
            hasSourceRoute = true;
            ExitNow();
        }

        range.AdvanceOffset(optionLen - 1);
    }

exit:
    return hasSourceRoute;
}

//---------------------------------------------------------------------------------------------------------------------
// Headers

Error Headers::ParseFrom(const Message &aMessage)
{
    Error    error = kErrorParse;
    uint16_t headerLen;

    Clear();

    SuccessOrExit(mIp4Header.ParseFrom(aMessage));

    headerLen = mIp4Header.GetHeaderLength();

    switch (mIp4Header.GetProtocol())
    {
    case kProtoUdp:
        SuccessOrExit(aMessage.Read(headerLen, mHeader.mUdp));
        break;
    case kProtoTcp:
        SuccessOrExit(aMessage.Read(headerLen, mHeader.mTcp));
        break;
    case kProtoIcmp:
        SuccessOrExit(aMessage.Read(headerLen, mHeader.mIcmp));
        break;
    default:
        break;
    }

    error = kErrorNone;

exit:
    return error;
}

uint16_t Headers::GetSourcePort(void) const
{
    uint16_t port = 0;

    switch (GetIpProto())
    {
    case kProtoUdp:
        port = mHeader.mUdp.GetSourcePort();
        break;

    case kProtoTcp:
        port = mHeader.mTcp.GetSourcePort();
        break;

    default:
        break;
    }

    return port;
}

uint16_t Headers::GetDestinationPort(void) const
{
    uint16_t port = 0;

    switch (GetIpProto())
    {
    case kProtoUdp:
        port = mHeader.mUdp.GetDestinationPort();
        break;

    case kProtoTcp:
        port = mHeader.mTcp.GetDestinationPort();
        break;

    default:
        break;
    }

    return port;
}

void Headers::SetDestinationPort(uint16_t aDstPort)
{
    switch (GetIpProto())
    {
    case kProtoUdp:
        mHeader.mUdp.SetDestinationPort(aDstPort);
        break;

    case kProtoTcp:
        mHeader.mTcp.SetDestinationPort(aDstPort);
        break;

    default:
        break;
    }
}

uint16_t Headers::GetChecksum(void) const
{
    uint16_t checksum = 0;

    switch (GetIpProto())
    {
    case kProtoUdp:
        checksum = mHeader.mUdp.GetChecksum();
        break;

    case kProtoTcp:
        checksum = mHeader.mTcp.GetChecksum();
        break;

    case kProtoIcmp:
        checksum = mHeader.mIcmp.GetChecksum();
        break;

    default:
        break;
    }

    return checksum;
}

} // namespace Ip4
} // namespace ot
