/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "vm/Xdr.h"

#include "mozilla/PodOperations.h"

#include <string.h>

#include "jsapi.h"
#include "jsscript.h"

#include "vm/Debugger.h"
#include "vm/EnvironmentObject.h"

using namespace js;
using mozilla::PodEqual;

template<XDRMode mode>
void
XDRState<mode>::postProcessContextErrors(JSContext* cx)
{
    if (cx->isExceptionPending()) {
        MOZ_ASSERT(resultCode_ == JS::TranscodeResult_Ok);
        resultCode_ = JS::TranscodeResult_Throw;
    }
}

template<XDRMode mode>
bool
XDRState<mode>::codeChars(const Latin1Char* chars, size_t nchars)
{
    static_assert(sizeof(Latin1Char) == sizeof(uint8_t), "Latin1Char must fit in 1 byte");

    MOZ_ASSERT(mode == XDR_ENCODE);

    if (nchars == 0)
        return true;
    uint8_t* ptr = buf.write(nchars);
    if (!ptr)
        return false;

    mozilla::PodCopy(ptr, chars, nchars);
    return true;
}

template<XDRMode mode>
bool
XDRState<mode>::codeChars(char16_t* chars, size_t nchars)
{
    if (nchars == 0)
        return true;
    size_t nbytes = nchars * sizeof(char16_t);
    if (mode == XDR_ENCODE) {
        uint8_t* ptr = buf.write(nbytes);
        if (!ptr)
            return false;
        mozilla::NativeEndian::copyAndSwapToLittleEndian(ptr, chars, nchars);
    } else {
        const uint8_t* ptr = buf.read(nbytes);
        mozilla::NativeEndian::copyAndSwapFromLittleEndian(chars, ptr, nchars);
    }
    return true;
}

template<XDRMode mode>
static bool
VersionCheck(XDRState<mode>* xdr)
{
    JS::BuildIdCharVector buildId;
    if (!xdr->cx()->buildIdOp() || !xdr->cx()->buildIdOp()(&buildId)) {
        JS_ReportErrorNumberASCII(xdr->cx(), GetErrorMessage, nullptr,
                                  JSMSG_BUILD_ID_NOT_AVAILABLE);
        return false;
    }
    MOZ_ASSERT(!buildId.empty());

    uint32_t buildIdLength;
    if (mode == XDR_ENCODE)
        buildIdLength = buildId.length();

    if (!xdr->codeUint32(&buildIdLength))
        return false;

    if (mode == XDR_DECODE && buildIdLength != buildId.length())
        return xdr->fail(JS::TranscodeResult_Failure_BadBuildId);

    if (mode == XDR_ENCODE) {
        if (!xdr->codeBytes(buildId.begin(), buildIdLength))
            return false;
    } else {
        JS::BuildIdCharVector decodedBuildId;

        // buildIdLength is already checked against the length of current
        // buildId.
        if (!decodedBuildId.resize(buildIdLength)) {
            ReportOutOfMemory(xdr->cx());
            return false;
        }

        if (!xdr->codeBytes(decodedBuildId.begin(), buildIdLength))
            return false;

        // We do not provide binary compatibility with older scripts.
        if (!PodEqual(decodedBuildId.begin(), buildId.begin(), buildIdLength))
            return xdr->fail(JS::TranscodeResult_Failure_BadBuildId);
    }

    return true;
}

template<XDRMode mode>
bool
XDRState<mode>::codeFunction(MutableHandleFunction funp)
{
    if (mode == XDR_DECODE)
        funp.set(nullptr);
    else
        MOZ_ASSERT(funp->nonLazyScript()->enclosingScope()->is<GlobalScope>());

    if (!VersionCheck(this)) {
        postProcessContextErrors(cx());
        return false;
    }

    RootedScope scope(cx(), &cx()->global()->emptyGlobalScope());
    if (!XDRInterpretedFunction(this, scope, nullptr, funp)) {
        postProcessContextErrors(cx());
        funp.set(nullptr);
        return false;
    }

    return true;
}

template<XDRMode mode>
bool
XDRState<mode>::codeScript(MutableHandleScript scriptp)
{
    if (mode == XDR_DECODE)
        scriptp.set(nullptr);
    else
        MOZ_ASSERT(!scriptp->enclosingScope());

    if (!VersionCheck(this)) {
        postProcessContextErrors(cx());
        return false;
    }

    if (!XDRScript(this, nullptr, nullptr, nullptr, scriptp)) {
        postProcessContextErrors(cx());
        scriptp.set(nullptr);
        return false;
    }

    return true;
}

template<XDRMode mode>
bool
XDRState<mode>::codeConstValue(MutableHandleValue vp)
{
    return XDRScriptConst(this, vp);
}

template class js::XDRState<XDR_ENCODE>;
template class js::XDRState<XDR_DECODE>;