* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed 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 "ecmascript/js_api/js_api_buffer.h"
#include <cstring>
#include <cstdint>
#include <cstring>
#include <string_view>
#include "ecmascript/base/typed_array_helper-inl.h"
#include "ecmascript/base/typed_array_helper.h"
#include "ecmascript/byte_array.h"
#include "ecmascript/ecma_string-inl.h"
#include "ecmascript/ecma_string.h"
#include "ecmascript/ecma_vm.h"
#include "ecmascript/js_array.h"
#include "ecmascript/js_arraybuffer.h"
#include "ecmascript/js_dataview.h"
#include "ecmascript/js_handle.h"
#include "ecmascript/js_hclass.h"
#include "ecmascript/js_object.h"
#include "ecmascript/js_tagged_number.h"
#include "ecmascript/js_tagged_value.h"
#include "ecmascript/js_typed_array.h"
#include "ecmascript/object_factory.h"
#include "jsnapi_expo.h"
#include "macros.h"
namespace panda::ecmascript {
using ContainerError = containers::ContainerError;
using string = std::string;
using string_view = std::string_view;
using ErrorFlag = containers::ErrorFlag;
using ElementSize = base::ElementSize;
using EncodingType = JSAPIFastBuffer::EncodingType;
using TypedArrayHelper = base::TypedArrayHelper;
using BuiltinsArrayBuffer = builtins::BuiltinsArrayBuffer;
using u16string = std::u16string;
namespace {
const std::map<string, EncodingType> ENCODING_MAP = {
{"hex", EncodingType::HEX}, {"base64url", EncodingType::BASE64URL}, {"base64", EncodingType::BASE64},
{"ascii", EncodingType::ASCII}, {"latin1", EncodingType::LATIN1}, {"binary", EncodingType::BINARY},
{"ucs2", EncodingType::UTF16LE}, {"ucs-2", EncodingType::UTF16LE}, {"utf-16le", EncodingType::UTF16LE},
{"utf16le", EncodingType::UTF16LE}, {"utf-8", EncodingType::UTF8}, {"utf8", EncodingType::UTF8},
};
}
const uint8_t BASE64_TABLE[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -1, -1, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
-1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
inline bool RangeChecker(uint32_t rangeLeft, uint32_t rangeRight, uint32_t l, uint32_t r)
{
return l <= r && l <= rangeRight && r >= rangeLeft;
}
bool IsBase64Char(unsigned char c)
{
return (isalnum(c) || (c == '+') || (c == '/') || (c == '-') || (c == '_'));
}
EncodingType JSAPIFastBuffer::GetEncodingType(string encode)
{
if (ENCODING_MAP.find(encode) != ENCODING_MAP.end()) {
return ENCODING_MAP.at(encode);
}
return EncodingType::INVALID;
}
EncodingType JSAPIFastBuffer::GetEncodingType(JSThread *thread, const JSHandle<JSTaggedValue> &encode)
{
auto strAccessor = EcmaStringAccessor(JSHandle<EcmaString>(encode));
auto str = strAccessor.ToStdString(thread);
return GetEncodingType(str);
}
JSTypedArray *GetUInt8ArrayFromBufferObject(JSThread *thread, JSTaggedValue buffer)
{
if (buffer.IsJSUint8Array()) {
return JSTypedArray::Cast(buffer.GetTaggedObject());
}
ASSERT(buffer.IsJSAPIBuffer());
return JSTypedArray::Cast(
JSAPIFastBuffer::Cast(buffer.GetTaggedObject())->GetFastBufferData(thread).GetTaggedObject());
}
JSTypedArray *GetUInt8ArrayFromBufferObject(JSThread *thread, JSHandle<JSTaggedValue> buffer)
{
return GetUInt8ArrayFromBufferObject(thread, buffer.GetTaggedValue());
}
uint8_t *GetUnderlyingData(JSThread *thread, JSTypedArray *array, uint32_t offset)
{
if (array->GetByteLength() <= offset) {
return nullptr;
}
JSTaggedValue arrayBuffer = array->GetViewedArrayBufferOrByteArray(thread);
if (BuiltinsArrayBuffer::IsDetachedBuffer(thread, arrayBuffer)) {
return nullptr;
}
auto res =
reinterpret_cast<uint8_t *>(builtins::BuiltinsArrayBuffer::GetDataPointFromBuffer(thread, arrayBuffer, offset));
return res;
}
uint8_t *GetUnderlyingData(JSThread *thread, JSTaggedValue obj, uint32_t offset = 0)
{
if (obj.IsJSAPIBuffer()) {
JSAPIFastBuffer *buffer = JSAPIFastBuffer::Cast(obj.GetTaggedObject());
if (buffer->GetLength() <= offset) {
return nullptr;
}
offset += buffer->GetOffset();
}
JSTypedArray *array = GetUInt8ArrayFromBufferObject(thread, obj);
return GetUnderlyingData(thread, array, offset);
}
int32_t GetValueInt32(JSHandle<JSTaggedValue> valueHandle)
{
JSTaggedValue value = valueHandle.GetTaggedValue();
if (UNLIKELY(value.IsDouble())) {
return static_cast<int32_t>(
ecmascript::base::NumberHelper::DoubleToInt(value.GetDouble(), ecmascript::base::INT32_BITS));
}
return value.GetInt();
}
uint32_t GetValueUInt32(JSHandle<JSTaggedValue> valueHandle)
{
return static_cast<uint32_t>(GetValueInt32(valueHandle));
}
string FromStringUtf8(JSThread *thread, const JSHandle<JSTaggedValue> &str)
{
auto strAccessor = EcmaStringAccessor(JSHandle<EcmaString>(str));
auto res = strAccessor.ToStdString(thread);
return res;
}
string_view FromStringUtf16(JSThread *thread, const JSHandle<JSTaggedValue> &str, string &stringDecoded)
{
auto strAccessor = EcmaStringAccessor(JSHandle<EcmaString>(str));
auto u16string = StringConverter::Utf8ToUtf16BE(strAccessor.ToStdString(thread));
stringDecoded = StringConverter::Utf16StrToStr(u16string);
return string_view(stringDecoded);
}
string_view FromStringASCII(JSThread *thread, const JSHandle<JSTaggedValue> &str, string &stringDecoded)
{
auto strAccessor = EcmaStringAccessor(JSHandle<EcmaString>(str));
uint32_t length = strAccessor.GetLength();
stringDecoded.reserve(length);
stringDecoded.resize(length);
strAccessor.WriteToOneByte(thread, reinterpret_cast<uint8_t *>(stringDecoded.data()), length);
return string_view(stringDecoded);
}
string_view FromStringBase64(JSThread *thread, const JSHandle<JSTaggedValue> &str, string &stringDecoded)
{
auto strAccessor = EcmaStringAccessor(JSHandle<EcmaString>(str));
string target = strAccessor.ToStdString(thread);
StringConverter::Base64Decode(target, stringDecoded);
return std::string_view(stringDecoded);
}
std::string ConvertEcmaStringToStdString(JSThread *thread, const JSHandle<JSTaggedValue> &str)
{
ASSERT(str->IsString());
auto strAccessor = EcmaStringAccessor(JSHandle<EcmaString>(str));
return strAccessor.ToStdString(thread);
}
JSHandle<JSTypedArray> JSAPIFastBuffer::NewUint8Array(JSThread *thread, uint32_t length)
{
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
JSHandle<JSTaggedValue> handleTagValFunc = env->GetUint8ArrayFunction();
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSObject> obj =
factory->NewJSObjectByConstructor(JSHandle<JSFunction>(handleTagValFunc), handleTagValFunc);
DataViewType arrayType = DataViewType::UINT8;
if (length > 0) {
TypedArrayHelper::AllocateTypedArrayBuffer(thread, obj, length, arrayType);
} else {
auto jsTypedArray = JSTypedArray::Cast(*obj);
jsTypedArray->SetContentType(arrayType);
jsTypedArray->SetByteLength(0);
jsTypedArray->SetByteOffset(0);
jsTypedArray->SetArrayLength(0);
}
return JSHandle<JSTypedArray>(obj);
}
JSTaggedValue JSAPIFastBuffer::Copy(JSThread *thread, const JSHandle<JSTaggedValue> &dst,
const JSHandle<JSTaggedValue> &src, uint32_t tStart, uint32_t sStart, uint32_t sEnd)
{
ASSERT(tStart >= 0 && sStart >= 0 && sStart <= sEnd);
uint32_t copyLength = sEnd - sStart;
JSTypedArray *dstArray = GetUInt8ArrayFromBufferObject(thread, dst);
uint32_t dstLength = dst->IsJSAPIBuffer() ? JSHandle<JSAPIFastBuffer>(dst)->GetLength() : dstArray->GetByteLength();
if (dstLength <= tStart) {
return JSTaggedValue(0);
}
copyLength = std::min(copyLength, dstLength - tStart);
uint8_t *srcData = GetUnderlyingData(thread, src.GetTaggedValue(), sStart);
uint8_t *dstData = GetUnderlyingData(thread, dst.GetTaggedValue(), tStart);
if (srcData == nullptr || dstData == nullptr) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::ARRAY_BUFFER_IS_NULL_OR_DETACHED,
"The underlying ArrayBuffer is null or detached.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
WriteBytes(thread, srcData, copyLength, dstData);
return JSTaggedValue(copyLength);
}
JSTaggedValue JSAPIFastBuffer::CreateBufferFromArrayLike(JSThread *thread, const JSHandle<JSAPIFastBuffer> &buffer,
const JSHandle<JSTaggedValue> &obj)
{
if (!obj->IsECMAObject()) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR,
"CreateBufferFromArrayLike must accept object.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
JSHandle<JSTaggedValue> lengthKeyHandle = thread->GlobalConstants()->GetHandledLengthString();
JSHandle<JSTaggedValue> value = JSObject::GetProperty(thread, obj, lengthKeyHandle).GetValue();
JSTaggedNumber number = JSTaggedValue::ToLength(thread, value);
if (number.GetNumber() > JSObject::MAX_ELEMENT_INDEX) {
JSTaggedValue error =
ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, "len is bigger than 2^32 - 1.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
uint32_t len = number.ToUint32();
auto fastBufferData = NewUint8Array(thread, len);
buffer->SetFastBufferData(thread, fastBufferData);
buffer->SetLength(len);
for (uint32_t i = 0; i < len; i++) {
JSTaggedValue next = JSTaggedValue::GetProperty(thread, obj, i).GetValue().GetTaggedValue();
if (!next.IsInt()) {
JSTaggedValue error =
ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, "value in arraylike must be a integer.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
buffer->SetValueByIndex(thread, i, JSTaggedValue(static_cast<uint8_t>(next.GetInt())), JSType::JS_UINT8_ARRAY);
}
return buffer.GetTaggedValue();
}
JSTaggedValue JSAPIFastBuffer::FromArrayBuffer(JSThread *thread, const JSHandle<JSAPIFastBuffer> &buffer,
const JSHandle<JSTaggedValue> &src, uint32_t byteOffset, uint32_t length)
{
auto srcBuffer = JSHandle<JSArrayBuffer>(src);
uint32_t srcLength = srcBuffer->GetArrayBufferByteLength();
if (srcLength <= byteOffset) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR,
"byteOffset must less than length.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
auto len = std::min(length, srcLength - byteOffset);
auto array = NewUint8Array(thread, 0);
array->SetViewedArrayBufferOrByteArray(thread, srcBuffer);
array->SetByteLength(len);
array->SetArrayLength(len);
array->SetByteOffset(byteOffset);
buffer->SetFastBufferData(thread, array);
buffer->SetLength(len);
buffer->SetOffset(byteOffset);
return buffer.GetTaggedValue();
}
string_view FromStringHex(JSThread *thread, const JSHandle<JSTaggedValue> &str, string &stringDecoded)
{
auto strAccessor = EcmaStringAccessor(JSHandle<EcmaString>(str));
auto hexStr = strAccessor.ToStdString(thread);
StringConverter::HexDecode(hexStr, stringDecoded);
return std::string_view(stringDecoded);
}
bool IsOneByte(uint8_t u8Char)
{
return (u8Char & 0x80) == 0;
}
void StringConverter::Utf8ToUtf16BEToData(const unsigned char *data, u16string &u16Str, string::size_type &index,
uint8_t &c1)
{
uint8_t c2 = data[++index];
uint8_t c3 = data[++index];
uint8_t c4 = data[++index];
uint32_t codePoint = ((c1 & LOWER_3_BITS_MASK) << (3 * UTF8_VALID_BITS)) |
((c2 & LOWER_6_BITS_MASK) << (2 * UTF8_VALID_BITS)) |
((c3 & LOWER_6_BITS_MASK) << UTF8_VALID_BITS) | (c4 & LOWER_6_BITS_MASK);
if (codePoint >= UTF16_SPECIAL_VALUE) {
codePoint -= UTF16_SPECIAL_VALUE;
u16Str.push_back(static_cast<char16_t>((codePoint >> 10) | HIGH_AGENT_MASK));
u16Str.push_back(static_cast<char16_t>((codePoint & LOWER_10_BITS_MASK) | LOW_AGENT_MASK));
} else {
u16Str.push_back(static_cast<char16_t>(codePoint));
}
}
u16string StringConverter::Utf8ToUtf16BE(const string &u8Str, bool *ok)
{
u16string u16Str = u"";
u16Str.reserve(u8Str.size());
string::size_type len = u8Str.length();
const unsigned char *data = reinterpret_cast<const unsigned char *>(u8Str.data());
bool isOk = true;
for (string::size_type i = 0; i < len; ++i) {
uint8_t c1 = data[i];
if (IsOneByte(c1)) {
u16Str.push_back(static_cast<char16_t>(c1));
continue;
}
switch (c1 & HIGER_4_BITS_MASK) {
case FOUR_BYTES_STYLE: {
Utf8ToUtf16BEToData(data, u16Str, i, c1);
break;
}
case THREE_BYTES_STYLE: {
uint8_t c2 = data[++i];
uint8_t c3 = data[++i];
uint32_t codePoint = ((c1 & LOWER_4_BITS_MASK) << (2 * UTF8_VALID_BITS)) |
((c2 & LOWER_6_BITS_MASK) << UTF8_VALID_BITS) | (c3 & LOWER_6_BITS_MASK);
u16Str.push_back(static_cast<char16_t>(codePoint));
break;
}
case TWO_BYTES_STYLE1:
case TWO_BYTES_STYLE2: {
uint8_t c2 = data[++i];
uint32_t codePoint = ((c1 & LOWER_5_BITS_MASK) << UTF8_VALID_BITS) | (c2 & LOWER_6_BITS_MASK);
u16Str.push_back(static_cast<char16_t>(codePoint));
break;
}
default: {
isOk = false;
break;
}
}
}
if (ok != nullptr) {
*ok = isOk;
}
return u16Str;
}
u16string StringConverter::Utf16BEToLE(const u16string &wstr)
{
u16string str16 = u"";
const char16_t *data = wstr.data();
for (unsigned int i = 0; i < wstr.length(); i++) {
char16_t wc = data[i];
char16_t high = (wc >> 8) & 0x00FF;
char16_t low = wc & 0x00FF;
char16_t c16 = (low << 8) | high;
str16.push_back(c16);
}
return str16;
}
string StringConverter::Utf16BEToANSI(const u16string &wstr)
{
string ret = "";
for (u16string::const_iterator it = wstr.begin(); it != wstr.end(); ++it) {
char16_t wc = (*it);
char c = static_cast<char>(wc & LOWER_8_BITS_MASK);
ret.push_back(c);
}
return ret;
}
string StringConverter::Utf8ToUtf16BEToANSI(const string &str)
{
u16string u16Str = Utf8ToUtf16BE(str);
string ret = Utf16BEToANSI(u16Str);
return ret;
}
string JSAPIFastBuffer::GetString(JSThread *thread, const JSHandle<JSTaggedValue> &str, EncodingType encodingType)
{
string_view data;
string strDecoded;
switch (encodingType) {
case UTF8:
strDecoded = FromStringUtf8(thread, str);
data = strDecoded;
break;
case ASCII:
case LATIN1:
case BINARY:
data = FromStringASCII(thread, str, strDecoded);
break;
case UTF16LE:
data = FromStringUtf16(thread, str, strDecoded);
break;
case BASE64:
case BASE64URL:
data = FromStringBase64(thread, str, strDecoded);
break;
case HEX:
data = FromStringHex(thread, str, strDecoded);
break;
default:
return "";
}
return strDecoded;
}
string JSAPIFastBuffer::GetString(JSThread *thread, const JSHandle<JSTaggedValue> &str,
JSHandle<JSTaggedValue> encoding)
{
EncodingType encodingType = UTF8;
encodingType = GetEncodingType(ConvertEcmaStringToStdString(thread, encoding));
return GetString(thread, str, encodingType);
}
JSTaggedValue JSAPIFastBuffer::FromString(JSThread *thread, const JSHandle<JSAPIFastBuffer> &buffer,
const JSHandle<JSTaggedValue> &str, const JSHandle<JSTaggedValue> &encoding)
{
if (encoding->IsUndefinedOrNull()) {
return FromString(thread, buffer, str);
}
EncodingType encodingType;
if (!encoding->IsString() || (encodingType = GetEncodingType(thread, encoding)) == EncodingType::INVALID) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR,
"Parameter error. The type of \"encoding\" must be BufferEncoding. the encoding code is unknown");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
return FromString(thread, buffer, str, encodingType);
}
JSTaggedValue JSAPIFastBuffer::FromString(JSThread *thread, const JSHandle<JSAPIFastBuffer> &buffer,
const JSHandle<JSTaggedValue> &str, EncodingType encoding)
{
string_view data;
string strDecoded;
switch (encoding) {
case UTF8:
strDecoded = FromStringUtf8(thread, str);
data = strDecoded;
break;
case ASCII:
case LATIN1:
case BINARY:
data = FromStringASCII(thread, str, strDecoded);
break;
case UTF16LE:
data = FromStringUtf16(thread, str, strDecoded);
break;
case BASE64:
case BASE64URL:
data = FromStringBase64(thread, str, strDecoded);
break;
case HEX:
data = FromStringHex(thread, str, strDecoded);
break;
default:
THROW_RANGE_ERROR_AND_RETURN(thread, "Not supported encodingType", JSTaggedValue::Exception());
}
uint32_t length = data.length();
JSHandle<JSTypedArray> array = NewUint8Array(thread, length);
buffer->SetFastBufferData(thread, array);
buffer->SetLength(length);
if (length != 0) {
auto *dst = GetUnderlyingData(thread, buffer.GetTaggedValue());
if (dst == nullptr) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::ARRAY_BUFFER_IS_NULL_OR_DETACHED,
"The underlying ArrayBuffer is null or detached.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
WriteBytes(thread, reinterpret_cast<const uint8_t *>(data.data()), length, reinterpret_cast<uint8_t *>(dst));
}
return buffer.GetTaggedValue();
}
JSTaggedValue JSAPIFastBuffer::ToString(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer, EncodingType encodingType,
uint32_t start, uint32_t end)
{
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
if (!RangeChecker(0, buffer->GetLength(), start, end)) {
std::ostringstream oss;
oss << "Buffer ToString parameter are out of range";
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
auto arrayBuffer = GetArrayBuffer(thread, buffer);
if (BuiltinsArrayBuffer::IsDetachedBuffer(thread, arrayBuffer)) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::ARRAY_BUFFER_IS_NULL_OR_DETACHED,
"The underlying ArrayBuffer is null or detached.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
uint8_t *data = reinterpret_cast<uint8_t *>(
builtins::BuiltinsArrayBuffer::GetDataPointFromBuffer(thread, arrayBuffer, buffer->GetOffset() + start));
uint32_t len = end - start;
string strDecoded;
switch (encodingType) {
case UTF8:
return factory->NewFromUtf8WithoutStringTable(string_view(reinterpret_cast<const char *>(data), len))
.GetTaggedValue();
case ASCII:
return factory->NewFromASCII(string_view(reinterpret_cast<const char *>(data), len)).GetTaggedValue();
case UTF16LE:
return factory->NewFromUtf16(reinterpret_cast<uint16_t *>(data), len / 2).GetTaggedValue();
case LATIN1:
case BINARY:
StringConverter::Latin1Encode(reinterpret_cast<unsigned char *>(data), len, strDecoded);
break;
case BASE64:
case BASE64URL:
StringConverter::Base64Encode(reinterpret_cast<unsigned char *>(data), len, strDecoded,
encodingType == BASE64URL);
break;
case HEX:
StringConverter::HexEncode(reinterpret_cast<unsigned char *>(data), len, strDecoded);
break;
default:
THROW_RANGE_ERROR_AND_RETURN(thread, "Not supported encodingType", JSTaggedValue::Exception());
}
return factory->NewFromUtf8(strDecoded).GetTaggedValue();
}
JSTaggedValue JSAPIFastBuffer::WriteString(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer,
JSHandle<JSTaggedValue> &value, uint32_t offset, uint32_t maxLen,
EncodingType encoding)
{
ASSERT(value->IsString());
uint32_t bufLength = buffer->GetLength();
if (offset > bufLength) {
return JSTaggedValue(0);
}
maxLen = std::min(maxLen, bufLength - offset);
string_view data;
string strDecoded;
switch (encoding) {
case UTF8:
strDecoded = FromStringUtf8(thread, value);
data = strDecoded;
break;
case ASCII:
case LATIN1:
case BINARY:
data = FromStringASCII(thread, value, strDecoded);
break;
case UTF16LE:
strDecoded = FromStringUtf16(thread, value, strDecoded);
data = strDecoded;
break;
case BASE64:
case BASE64URL:
data = FromStringBase64(thread, value, strDecoded);
break;
case HEX:
data = FromStringHex(thread, value, strDecoded);
break;
default:
THROW_RANGE_ERROR_AND_RETURN(thread, "Not supported encodingType", JSTaggedValue::Exception());
}
uint32_t strLength = data.length();
if (strLength == 0) {
return JSTaggedValue(0);
}
maxLen = std::min(maxLen, strLength);
auto *dst = GetUnderlyingData(thread, buffer.GetTaggedValue(), offset);
if (dst == nullptr) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::ARRAY_BUFFER_IS_NULL_OR_DETACHED,
"The underlying ArrayBuffer is null or detached.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
WriteBytes(thread, reinterpret_cast<const uint8_t *>(data.data()), maxLen, reinterpret_cast<uint8_t *>(dst));
return JSTaggedValue(maxLen);
}
JSTaggedValue JSAPIFastBuffer::AllocateFastBuffer(JSThread *thread, const JSHandle<JSAPIFastBuffer> &buffer,
uint32_t byteLength, uint32_t byteOffset)
{
JSHandle<JSTypedArray> handleUint8Array = JSAPIFastBuffer::NewUint8Array(thread, byteLength);
buffer->SetFastBufferData(thread, handleUint8Array);
buffer->SetLength(byteLength);
buffer->SetOffset(byteOffset);
return buffer.GetTaggedValue();
}
JSTaggedValue JSAPIFastBuffer::AllocateFromBufferObject(JSThread *thread, const JSHandle<JSAPIFastBuffer> &buffer,
const JSHandle<JSTaggedValue> &src, uint32_t byteLength,
uint32_t byteOffset)
{
auto uint8Array = GetUInt8ArrayFromBufferObject(thread, src);
uint32_t maxLength = std::min(uint8Array->GetByteLength(), byteLength);
buffer->SetFastBufferData(thread, JSTaggedValue(uint8Array));
buffer->SetLength(maxLength);
buffer->SetOffset(std::min(maxLength, byteOffset));
return buffer.GetTaggedValue();
}
JSTaggedValue JSAPIFastBuffer::GetArrayBuffer(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer)
{
auto array =
JSHandle<JSTypedArray>(thread, JSTypedArray::Cast(buffer->GetFastBufferData(thread).GetTaggedObject()));
return JSTypedArray::GetOffHeapBuffer(thread, array);
}
JSTaggedValue JSAPIFastBuffer::FromBufferToArray(JSThread *thread, JSHandle<JSTaggedValue> &value)
{
auto buffer = JSHandle<JSAPIFastBuffer>(value);
int32_t length = static_cast<int32_t>(buffer->GetLength());
JSHandle<JSTaggedValue> array = JSArray::ArrayCreate(thread, JSTaggedNumber(length));
JSHandle<JSTypedArray> typedArray =
JSHandle<JSTypedArray>(thread, JSTypedArray::Cast(buffer->GetFastBufferData(thread).GetTaggedObject()));
for (int i = 0; i < length; i++) {
JSTaggedValue bufferValue = GetValueByIndex(thread, typedArray.GetTaggedValue(), i, JSType::JS_UINT8_ARRAY);
JSArray::FastSetPropertyByValue(thread, array, i, JSHandle<JSTaggedValue>(thread, bufferValue));
}
return array.GetTaggedValue();
}
JSTaggedValue JSAPIFastBuffer::Entries(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer)
{
auto array =
JSHandle<JSTypedArray>(thread, JSTypedArray::Cast(buffer->GetFastBufferData(thread).GetTaggedObject()));
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSArrayIterator> iter(
factory->NewJSArrayIterator(JSHandle<JSObject>(thread, array.GetTaggedValue()), IterationKind::KEY_AND_VALUE));
return iter.GetTaggedValue();
}
JSTaggedValue JSAPIFastBuffer::Keys(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer)
{
auto array =
JSHandle<JSTypedArray>(thread, JSTypedArray::Cast(buffer->GetFastBufferData(thread).GetTaggedObject()));
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSArrayIterator> iter(
factory->NewJSArrayIterator(JSHandle<JSObject>(thread, array.GetTaggedValue()), IterationKind::KEY));
return iter.GetTaggedValue();
}
JSTaggedValue JSAPIFastBuffer::Values(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer)
{
auto array =
JSHandle<JSTypedArray>(thread, JSTypedArray::Cast(buffer->GetFastBufferData(thread).GetTaggedObject()));
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSArrayIterator> iter(
factory->NewJSArrayIterator(JSHandle<JSObject>(thread, array.GetTaggedValue()), IterationKind::VALUE));
return iter.GetTaggedValue();
}
std::string StringConverter::Utf16StrToStr(std::u16string &value)
{
string str = "";
const char16_t *data = reinterpret_cast<const char16_t *>(value.data());
for (unsigned int i = 0; i < value.length(); i++) {
char16_t c = data[i];
char high = static_cast<char>((c >> 8) & 0x00FF);
char low = static_cast<char>(c & 0x00FF);
str.push_back(low);
str.push_back(high);
}
return str;
}
JSTaggedValue JSAPIFastBuffer::FillString(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer,
JSHandle<JSTaggedValue> &valueHandle, EncodingType encoding, uint32_t start,
uint32_t end)
{
string str = GetString(thread, valueHandle, encoding);
if (str.length() == 0) {
str.push_back(0);
}
return WriteStringLoop(thread, buffer, str, start, end);
}
JSTaggedValue JSAPIFastBuffer::Fill(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer,
JSHandle<JSTaggedValue> &valueHandle, EncodingType encoding, uint32_t start,
uint32_t end)
{
if (valueHandle->IsUndefined() || valueHandle->IsNull()) {
valueHandle = JSHandle<JSTaggedValue>(thread, JSTaggedValue(0));
}
if (valueHandle->IsNumber()) {
if (!valueHandle->IsInt()) {
std::ostringstream oss;
oss << "Buffer fill number shoule be a integer";
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Undefined());
}
uint8_t value = static_cast<uint8_t>(valueHandle->GetInt());
uint32_t length = end - start;
uint8_t *data = GetUnderlyingData(thread, buffer.GetTaggedValue(), start);
if (data == nullptr) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::ARRAY_BUFFER_IS_NULL_OR_DETACHED,
"The underlying ArrayBuffer is null or detached.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
if (memset_s(data, length, value, length) != EOK) {
JSTaggedValue error =
ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, "Fill value memset failed.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
} else if (valueHandle->IsString()) {
FillString(thread, buffer, valueHandle, encoding, start, end);
} else if (valueHandle->IsJSAPIBuffer() || valueHandle->IsTypedArray()) {
WriteBufferObjectLoop(thread, buffer, valueHandle, start, end);
}
return buffer.GetTaggedValue();
}
JSTaggedValue JSAPIFastBuffer::Compare(JSThread *thread, JSHandle<JSAPIFastBuffer> &a, JSHandle<JSTaggedValue> &bObj,
uint32_t sStart, uint32_t sEnd, uint32_t tStart, uint32_t tEnd)
{
if (sStart >= sEnd) {
return JSTaggedValue(tStart >= tEnd ? 0 : -1);
}
if (tStart >= tEnd) {
return JSTaggedValue(1);
}
JSTypedArray *typedArrayA = GetUInt8ArrayFromBufferObject(thread, a.GetTaggedValue());
JSTypedArray *typedArrayB = GetUInt8ArrayFromBufferObject(thread, bObj);
uint32_t aCmpLength = sEnd - sStart;
uint32_t bCmpLength = tEnd - tStart;
if (typedArrayA->GetByteLength() < aCmpLength || typedArrayB->GetByteLength() < bCmpLength) {
std::ostringstream oss;
oss << "Buffer Compare parameters are out of range.";
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
uint8_t *aPointer = GetUnderlyingData(thread, a.GetTaggedValue(), sStart);
uint8_t *bPointer = GetUnderlyingData(thread, bObj.GetTaggedValue(), tStart);
if (aPointer == nullptr || bPointer == nullptr) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::ARRAY_BUFFER_IS_NULL_OR_DETACHED,
"Buffer Compare memory ptr is nullptr.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
uint32_t length = std::min(aCmpLength, bCmpLength);
int32_t ret = memcmp(aPointer, bPointer, length);
if (ret == 0) {
if (aCmpLength != bCmpLength) {
return aCmpLength < bCmpLength ? JSTaggedValue(-1) : JSTaggedValue(1);
}
return JSTaggedValue(0);
}
return ret < 0 ? JSTaggedValue(-1) : JSTaggedValue(1);
}
JSTaggedValue JSAPIFastBuffer::Includes(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer,
JSHandle<JSTaggedValue> valueHandle, uint32_t start, EncodingType encoding)
{
JSTaggedValue res = IndexOf(thread, buffer, valueHandle, start, encoding, false);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return res.GetInt() == -1 ? JSTaggedValue::False() : JSTaggedValue::True();
}
int32_t JSAPIFastBuffer::StringMatch(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer, const uint8_t *str,
uint32_t start, uint32_t strLength, bool isReverse)
{
if (buffer->GetLength() < strLength) {
return -1;
}
auto src = GetUnderlyingData(thread, buffer.GetTaggedValue());
if (src == nullptr) {
return -1;
}
uint32_t end = buffer->GetLength() - strLength;
if (isReverse) {
for (int32_t i = std::min(end, start); i >= 0; --i) {
if (memcmp(str, src + i, strLength) == 0) {
return i;
}
}
} else {
for (uint32_t i = start; i <= end; ++i) {
if (memcmp(str, src + i, strLength) == 0) {
return i;
}
}
}
return -1;
}
JSTaggedValue JSAPIFastBuffer::IndexOf(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer,
JSHandle<JSTaggedValue> valueHandle, uint32_t start, EncodingType encoding,
bool isReverse)
{
if (!valueHandle->IsNumber()) {
if (valueHandle->IsString()) {
string str = GetString(thread, valueHandle, encoding);
const uint8_t *data = reinterpret_cast<const uint8_t *>(str.data());
uint32_t len = str.length();
return len == 0 ? JSTaggedValue(0)
: JSTaggedValue(StringMatch(thread, buffer, data, start, len, isReverse));
} else if (valueHandle->IsJSAPIBuffer() || valueHandle->IsJSUint8Array()) {
const uint8_t *data = GetUnderlyingData(thread, valueHandle.GetTaggedValue());
uint32_t len = valueHandle->IsJSAPIBuffer() ? JSHandle<JSAPIFastBuffer>(valueHandle)->GetLength()
: JSHandle<JSTypedArray>(valueHandle)->GetByteLength();
if (len == 0) {
return JSTaggedValue(0);
}
if (data == nullptr) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::ARRAY_BUFFER_IS_NULL_OR_DETACHED,
"The underlying ArrayBuffer is null or detached.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
return JSTaggedValue(StringMatch(thread, buffer, data, start, len, isReverse));
}
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, "IndexOf value invalid.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
if (!valueHandle->IsInt()) {
std::ostringstream oss;
oss << "Buffer includes value shoule be integer when value is number";
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
uint8_t value = static_cast<uint8_t>(valueHandle->GetInt());
uint32_t length = buffer->GetLength();
if (isReverse) {
for (int32_t i = std::min(length - 1, start); i >= 0; --i) {
if (value == ReadUInt8(thread, buffer, i, true).GetInt()) {
return JSTaggedValue(i);
}
}
} else {
for (uint32_t i = start; i < length; ++i) {
if (value == ReadUInt8(thread, buffer, i, true).GetInt()) {
return JSTaggedValue(i);
}
}
}
return JSTaggedValue(-1);
}
bool JSAPIFastBuffer::WriteBytes(JSThread *thread, const uint8_t *src, unsigned int size, uint8_t *dest)
{
if (src == nullptr || dest == nullptr) {
return false;
}
if (size == 0) {
return true;
}
if (memmove_s(dest, size, src, size) != EOK) {
std::ostringstream oss;
oss << "Buffer WriteBytes memmove_s failed";
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, false);
}
return true;
}
bool JSAPIFastBuffer::WriteDataLoop(const uint8_t *src, uint8_t *dest, uint32_t length, uint32_t start, uint32_t end)
{
if (end - start <= 0 || length == 0) {
return false;
}
dest += start;
end -= start;
uint32_t pos = 0;
uint32_t copySize = std::min(length, end);
if (memmove_s(dest, copySize, src, copySize) != EOK) {
return false;
}
pos += copySize;
while (pos < end) {
copySize = std::min(copySize, end - pos);
if (memmove_s(dest + pos, copySize, dest, copySize) != EOK) {
return false;
}
pos += copySize;
copySize <<= 1;
}
return true;
}
JSTaggedValue JSAPIFastBuffer::WriteStringLoop(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer, string_view data,
uint32_t start, uint32_t end)
{
uint32_t length = data.length();
uint8_t *dst = GetUnderlyingData(thread, buffer.GetTaggedValue());
uint8_t *str = const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(data.data()));
if (str == nullptr || dst == nullptr) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::ARRAY_BUFFER_IS_NULL_OR_DETACHED,
"The underlying ArrayBuffer is null or detached.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
if (!WriteDataLoop(str, dst, length, start, end)) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, "Write data Failed.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
return JSTaggedValue(length);
}
JSTaggedValue JSAPIFastBuffer::WriteBufferObjectLoop(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer,
JSHandle<JSTaggedValue> &srcObj, uint32_t start, uint32_t end)
{
if (end - start > buffer->GetLength()) {
return JSTaggedValue(0);
}
auto srcArray = GetUInt8ArrayFromBufferObject(thread, srcObj);
uint32_t length = srcArray->GetByteLength();
uint8_t *src = GetUnderlyingData(thread, srcObj.GetTaggedValue());
uint8_t *dst = GetUnderlyingData(thread, buffer.GetTaggedValue());
if (src == nullptr || dst == nullptr) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::ARRAY_BUFFER_IS_NULL_OR_DETACHED,
"The underlying ArrayBuffer is null or detached.");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
WriteDataLoop(src, dst, length, start, end);
return JSTaggedValue(length);
}
bool JSAPIFastBuffer::WriteBytes(JSThread *thread, JSHandle<JSTaggedValue> srcData, JSHandle<JSTaggedValue> dstData,
uint32_t sStart, uint32_t tStart, uint32_t size)
{
ASSERT(srcData->IsByteArray() || srcData->IsArrayBuffer());
ASSERT(dstData->IsByteArray() || dstData->IsArrayBuffer());
auto src = reinterpret_cast<uint8_t *>(
builtins::BuiltinsArrayBuffer::GetDataPointFromBuffer(thread, srcData.GetTaggedValue(), sStart));
auto dst = reinterpret_cast<uint8_t *>(
builtins::BuiltinsArrayBuffer::GetDataPointFromBuffer(thread, dstData.GetTaggedValue(), tStart));
return WriteBytes(thread, src, size, dst);
}
JSTaggedValue JSAPIFastBuffer::SetValueByIndex(JSThread *thread, const JSTaggedValue typedarray, uint32_t index,
JSTaggedValue value, JSType valueType, bool littleEndian)
{
ASSERT(typedarray.IsJSUint8Array());
JSTypedArray *typedarrayObj = JSTypedArray::Cast(typedarray.GetTaggedObject());
if (UNLIKELY(value.IsECMAObject())) {
return JSTaggedValue::Hole();
}
JSTaggedValue buffer = typedarrayObj->GetViewedArrayBufferOrByteArray(thread);
DataViewType elementType = TypedArrayHelper::GetType(valueType);
auto valueHandle = JSHandle<JSTaggedValue>(thread, value);
return BuiltinsArrayBuffer::SetValueInBuffer(thread, buffer, index, elementType, valueHandle, littleEndian);
}
JSTaggedValue JSAPIFastBuffer::GetValueByIndex(JSThread *thread, const JSTaggedValue typedarray, uint32_t index,
JSType jsType, bool littleEndian)
{
ASSERT(typedarray.IsTypedArray() || typedarray.IsSharedTypedArray());
JSTypedArray *typedarrayObj = JSTypedArray::Cast(typedarray.GetTaggedObject());
JSTaggedValue buffer = typedarrayObj->GetViewedArrayBufferOrByteArray(thread);
if (buffer.IsArrayBuffer() && BuiltinsArrayBuffer::IsDetachedBuffer(thread, buffer)) {
return JSTaggedValue::Undefined();
}
DataViewType elementType = TypedArrayHelper::GetType(jsType);
return BuiltinsArrayBuffer::GetValueFromBuffer(thread, buffer, index, elementType, littleEndian);
}
OperationResult JSAPIFastBuffer::GetProperty(JSThread *thread, const JSHandle<JSAPIFastBuffer> &obj,
const JSHandle<JSTaggedValue> &key)
{
uint32_t length = obj->GetLength();
int32_t index = key->GetInt();
if (index < 0 || static_cast<uint32_t>(index) >= length) {
std::ostringstream oss;
oss << "The value of \"index\" is out of range. It must be >= 0 and <= " << (length - 1)
<< ". Received value is: " << index;
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error,
OperationResult(thread, JSTaggedValue::Exception(), PropertyMetaData(false)));
}
return OperationResult(thread, ReadUInt8(thread, obj, index, true), PropertyMetaData(false));
}
bool JSAPIFastBuffer::SetProperty(JSThread *thread, const JSHandle<JSAPIFastBuffer> &obj,
const JSHandle<JSTaggedValue> &key, const JSHandle<JSTaggedValue> &value)
{
uint32_t length = obj->GetLength();
int32_t index = key->GetInt();
if (index < 0 || static_cast<uint32_t>(index) >= length) {
return false;
}
WriteUInt8(thread, obj, value, index, true);
return true;
}
void StringConverter::Base64Encode(const unsigned char *src, uint32_t len, string &outStr, bool isUrl)
{
if (!len) {
outStr = "";
return;
}
char *out = nullptr;
char *pos = nullptr;
const unsigned char *pEnd = nullptr;
const unsigned char *pStart = nullptr;
size_t outLen = 4 * ((len + 2) / 3);
outStr.resize(outLen);
out = outStr.data();
pEnd = src + len;
pStart = src;
pos = out;
const char *table = isUrl ? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
while (pEnd - pStart >= 3) {
*pos = table[pStart[0] >> 2];
*(pos + 1) = table[((pStart[0] & LOWER_2_BITS_MASK) << 4) | (pStart[1] >> 4)];
*(pos + 2) = table[((pStart[1] & LOWER_4_BITS_MASK) << 2) | (pStart[2] >> 6)];
*(pos + 3) = table[pStart[2] & LOWER_6_BITS_MASK];
pos += 4;
pStart += 3;
}
if (pEnd - pStart > 0) {
*pos = table[pStart[0] >> 2];
if (pEnd - pStart == 1) {
*(pos + 1) = table[(pStart[0] & LOWER_2_BITS_MASK) << 4];
*(pos + 2) = '=';
} else {
*(pos + 1) = table[((pStart[0] & LOWER_2_BITS_MASK) << 4) | (pStart[1] >> 4)];
*(pos + 2) = table[(pStart[1] & LOWER_4_BITS_MASK) << 2];
}
*(pos + 3) = '=';
}
if (isUrl) {
size_t poss = outStr.find_last_not_of('=');
if (poss != std::string::npos) {
outStr.erase(poss + 1);
}
}
}
void DecodeBase64CharSlow(const uint8_t *data, string &ret, size_t len, uint32_t &cursor, uint32_t &retIter)
{
auto &table = BASE64_TABLE;
size_t count = 0;
CVector<unsigned char> temp(4, 0);
while (cursor < len && count < 4) {
unsigned char ch = table[data[cursor++]];
if (ch > StringConverter::LOWER_6_BITS_MASK) {
continue;
}
temp[count++] = ch;
}
if (count > JSAPIFastBuffer::OneByte) {
ret[retIter] =
((temp[0] & StringConverter::LOWER_6_BITS_MASK) << 2) | ((temp[JSAPIFastBuffer::OneByte] & 0x30) >> 4);
++retIter;
}
if (count > JSAPIFastBuffer::TwoBytes) {
ret[retIter] = ((temp[JSAPIFastBuffer::OneByte] & StringConverter::LOWER_4_BITS_MASK) << 4) |
((temp[JSAPIFastBuffer::TwoBytes] & StringConverter::MIDDLE_4_BITS_MASK) >> 2);
++retIter;
}
if (count > JSAPIFastBuffer::ThreeBytes) {
ret[retIter] = ((temp[JSAPIFastBuffer::TwoBytes] & StringConverter::LOWER_2_BITS_MASK) << 6) |
(temp[JSAPIFastBuffer::ThreeBytes] & StringConverter::LOWER_6_BITS_MASK);
++retIter;
}
}
void StringConverter::Base64Decode(string_view encodedStr, string &ret)
{
size_t len = encodedStr.length();
unsigned int cursor = 0;
CVector<unsigned char> charArray4(4, 0);
uint32_t upperLength = (len + 3) / 4 * 3;
ret.reserve(upperLength);
ret.resize(upperLength);
uint32_t maxLen = len / 4 * 4;
uint32_t retIter = 0;
auto &table = BASE64_TABLE;
const uint8_t *data = reinterpret_cast<const uint8_t *>(encodedStr.data());
while (cursor < maxLen) {
ASSERT(cursor + 4 <= len);
charArray4[0] = static_cast<unsigned char>(table[data[cursor]]);
charArray4[1] = static_cast<unsigned char>(table[data[cursor + 1]]);
charArray4[2] = static_cast<unsigned char>(table[data[cursor + 2]]);
charArray4[3] = static_cast<unsigned char>(table[data[cursor + 3]]);
if ((charArray4[0] | charArray4[1] | charArray4[2] | charArray4[3]) > LOWER_6_BITS_MASK) {
DecodeBase64CharSlow(data, ret, len, cursor, retIter);
continue;
}
ret[retIter] = ((charArray4[0] & LOWER_6_BITS_MASK) << 2) | ((charArray4[1] & 0x30) >> 4);
ret[retIter + 1] = ((charArray4[1] & LOWER_4_BITS_MASK) << 4) | ((charArray4[2] & MIDDLE_4_BITS_MASK) >> 2);
ret[retIter + 2] = ((charArray4[2] & LOWER_2_BITS_MASK) << 6) | (charArray4[3] & LOWER_6_BITS_MASK);
retIter += 3;
cursor += 4;
}
if (cursor < len) {
DecodeBase64CharSlow(data, ret, len, cursor, retIter);
}
ret.resize(retIter);
}
void StringConverter::Latin1Encode(const unsigned char *data, uint32_t len, std::string &ret)
{
ret.reserve(len * 2);
for (uint32_t i = 0; i < len; i++) {
unsigned char c = data[i];
if (c < 0x80) {
ret.push_back(c);
} else {
ret.push_back(0xc0 | (c >> UTF8_VALID_BITS));
ret.push_back(0x80 | (c & 0x3f));
}
}
}
bool IsValidHex(const string &hex)
{
if (!hex.length()) {
return false;
}
for (unsigned int i = 0; i < hex.size(); i++) {
char c = hex.at(i);
if (!(c <= '9' && c >= '0') && !(c <= 'F' && c >= 'A') && !(c <= 'f' && c >= 'a')) {
return false;
}
}
return true;
}
void StringConverter::HexEncode(const unsigned char *hexStr, uint32_t len, string &ret)
{
ret.reserve(2 * len);
for (uint32_t i = 0; i < len; i++) {
uint8_t high = hexStr[i] >> 4;
uint8_t low = hexStr[i] & 0x0f;
ret.push_back(high > 9 ? 'a' + (high % 10) : '0' + high);
ret.push_back(low > 9 ? 'a' + (low % 10) : '0' + low);
}
}
void StringConverter::HexDecode(string_view hexStr, string &ret)
{
unsigned int arrSize = hexStr.size();
ret.reserve(arrSize / 2);
string hexStrTmp;
int num = 0;
hexStrTmp.resize(2);
for (unsigned int i = 0; i < arrSize / 2; i++) {
hexStrTmp[0] = hexStr[i * 2];
hexStrTmp[1] = hexStr[i * 2 + 1];
if (!IsValidHex(hexStrTmp)) {
break;
}
num = stoi(hexStrTmp, nullptr, 16);
ret.push_back(static_cast<char>(num));
}
}
std::pair<JSTaggedValue, JSTaggedValue> SplitUInt(JSHandle<JSTaggedValue> &value, uint64_t byteLength)
{
ASSERT(value->IsNumber());
uint64_t temp;
if (value->IsDouble()) {
temp = static_cast<uint64_t>(value->GetDouble());
} else {
temp = static_cast<uint64_t>(value->GetInt());
}
byteLength *= JSAPIFastBuffer::ONE_BYTE_BIT_LENGTH;
uint64_t bitMask = static_cast<uint64_t>(static_cast<uint64_t>(1) << byteLength) - 1;
auto l = JSTaggedValue(static_cast<uint32_t>(temp & bitMask));
auto r = JSTaggedValue(static_cast<uint32_t>(temp >> byteLength));
return std::make_pair(l, r);
}
JSTaggedValue MergeUInt(JSTaggedValue low, JSTaggedValue high, uint64_t byteLengthLow)
{
byteLengthLow *= JSAPIFastBuffer::ONE_BYTE_BIT_LENGTH;
uint64_t lowValue = 0;
uint64_t highValue = 0;
lowValue |= static_cast<uint64_t>(low.GetNumber());
highValue |= static_cast<uint64_t>(high.GetNumber());
return JSTaggedValue(static_cast<double>((highValue << byteLengthLow) | lowValue));
}
JSTaggedValue JSAPIFastBuffer::WriteBytesValue(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer,
JSHandle<JSTaggedValue> &value, uint32_t offset, ByteLength byteLength,
bool littleEndian)
{
if (UNLIKELY(byteLength > SixBytes)) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR,
"WriteInt or WriteUInt only support 1 byte to 6 bytes");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
switch (byteLength) {
case OneByte:
return WriteUInt8(thread, buffer, value, offset, littleEndian);
case TwoBytes:
return WriteUInt16(thread, buffer, value, offset, littleEndian);
case FourBytes:
return WriteUInt32(thread, buffer, value, offset, littleEndian);
default:
break;
}
if (byteLength > FourBytes) {
std::pair<JSTaggedValue, JSTaggedValue> valuePair = SplitUInt(value, ThreeBytes);
if (valuePair.first == JSTaggedValue::Undefined()) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR,
"WriteInt or WriteUInt only support Integer Type");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
JSHandle<JSTaggedValue> lowValue = JSHandle<JSTaggedValue>(thread, valuePair.first);
JSHandle<JSTaggedValue> highValue = JSHandle<JSTaggedValue>(thread, valuePair.second);
ByteLength highBytes = static_cast<ByteLength>(byteLength - ThreeBytes);
if (littleEndian) {
auto nextIndex = WriteBytesValue(thread, buffer, lowValue, offset, ThreeBytes, littleEndian);
return WriteBytesValue(thread, buffer, highValue, nextIndex.GetInt(), highBytes, littleEndian);
}
auto nextIndex = WriteBytesValue(thread, buffer, highValue, offset, highBytes, littleEndian);
return WriteBytesValue(thread, buffer, lowValue, nextIndex.GetInt(), ThreeBytes, littleEndian);
}
std::pair<JSTaggedValue, JSTaggedValue> valuePair = SplitUInt(value, TwoBytes);
if (valuePair.first == JSTaggedValue::Undefined()) {
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR,
"WriteInt or WriteUInt only support Integer Type");
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
JSHandle<JSTaggedValue> lowValue = JSHandle<JSTaggedValue>(thread, valuePair.first);
JSHandle<JSTaggedValue> highValue = JSHandle<JSTaggedValue>(thread, valuePair.second);
ByteLength highBytes = static_cast<ByteLength>(byteLength - TwoBytes);
if (littleEndian) {
auto nextIndex = WriteBytesValue(thread, buffer, lowValue, offset, TwoBytes, littleEndian);
return WriteBytesValue(thread, buffer, highValue, nextIndex.GetInt(), highBytes, littleEndian);
}
auto nextIndex = WriteBytesValue(thread, buffer, highValue, offset, highBytes, littleEndian);
return WriteBytesValue(thread, buffer, lowValue, nextIndex.GetInt(), TwoBytes, littleEndian);
}
JSTaggedValue JSAPIFastBuffer::ReadBytes(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer, uint32_t offset,
ByteLength byteLength, bool littleEndian)
{
if (UNLIKELY(byteLength > SixBytes || byteLength < OneByte)) {
std::ostringstream oss;
oss << "ReadInt or ReadUInt only support 1 byte to 6 bytes";
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
switch (byteLength) {
case OneByte:
return ReadUInt8(thread, buffer, offset, littleEndian);
case TwoBytes:
return ReadUInt16(thread, buffer, offset, littleEndian);
case FourBytes:
return ReadUInt32(thread, buffer, offset, littleEndian);
default:
break;
}
if (byteLength > FourBytes) {
auto valueLow = ReadBytes(thread, buffer, offset, ThreeBytes, littleEndian);
ByteLength highBytes = static_cast<ByteLength>(byteLength - ThreeBytes);
auto valueHigh = ReadBytes(thread, buffer, offset + ThreeBytes, highBytes, littleEndian);
if (littleEndian) {
return MergeUInt(valueLow, valueHigh, ThreeBytes);
}
return MergeUInt(valueHigh, valueLow, highBytes);
}
auto valueLow = ReadBytes(thread, buffer, offset, TwoBytes, littleEndian);
ByteLength highBytes = static_cast<ByteLength>(byteLength - TwoBytes);
auto valueHigh = ReadBytes(thread, buffer, offset + TwoBytes, highBytes, littleEndian);
if (littleEndian) {
return MergeUInt(valueLow, valueHigh, TwoBytes);
}
return MergeUInt(valueHigh, valueLow, highBytes);
}
JSTaggedValue JSAPIFastBuffer::ReadInt(JSThread *thread, JSHandle<JSAPIFastBuffer> &buffer, uint32_t offset,
ByteLength byteLength, bool littleEndian)
{
auto ret = ReadBytes(thread, buffer, offset, byteLength, littleEndian);
int64_t value = ret.GetNumber();
int64_t negetiveMask = (1LL << (byteLength * JSAPIFastBuffer::ONE_BYTE_BIT_LENGTH - 1));
if (value & negetiveMask) {
int64_t bitMask = negetiveMask | (negetiveMask - 1);
bitMask &= -value;
return JSTaggedValue(JSTaggedNumber(JSTaggedValue(bitMask)) * JSTaggedNumber(-1));
}
return JSTaggedValue(value);
}
JSTaggedValue JSAPIFastBuffer::WriteBigUInt64(JSThread *thread, const JSHandle<JSAPIFastBuffer> &buffer,
const JSHandle<JSTaggedValue> &value, uint32_t offset, bool littleEndian)
{
JSType type = JSType::JS_BIGUINT64_ARRAY;
auto byteSize = base::TypedArrayHelper::GetElementSize(type);
JSHandle<JSTypedArray> typedArray =
JSHandle<JSTypedArray>(thread, JSTypedArray::Cast(buffer->GetFastBufferData(thread).GetTaggedObject()));
uint64_t valueNum;
bool isLossLess;
ASSERT(value->IsBigInt() || value->IsNumber() || value->IsBoolean());
BigInt::BigIntToUint64(thread, value, &valueNum, &isLossLess);
uint64_t left = 0;
uint64_t right = UINT64_MAX;
if (valueNum > right || valueNum < left) {
std::ostringstream oss;
oss << std::fixed << "The value of \"value\" is out of range. It must be >= " << left << " and <= " << right
<< ". Received value is: " << valueNum;
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
if (offset < 0 || offset + NumberSize::BIGUINT64 > buffer->GetLength()) {
std::ostringstream oss;
oss << std::fixed << "The value of \"offset\" is out of range. It must be >= " << 0
<< " and <= " << buffer->GetLength() - NumberSize::BIGUINT64 << ". Received value is: " << offset;
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
JSAPIFastBuffer::SetValueByIndex(thread, typedArray.GetTaggedValue(), offset, value.GetTaggedValue(), type,
littleEndian);
return JSTaggedValue(offset + byteSize);
}
JSTaggedValue JSAPIFastBuffer::ReadBigUInt64(JSThread *thread, const JSHandle<JSAPIFastBuffer> &buffer, uint32_t offset,
bool littleEndian)
{
JSType type = JSType::JS_BIGUINT64_ARRAY;
JSHandle<JSTypedArray> typedArray =
JSHandle<JSTypedArray>(thread, JSTypedArray ::Cast(buffer->GetFastBufferData(thread).GetTaggedObject()));
if (offset < 0 || offset + NumberSize::BIGUINT64 > buffer->GetLength()) {
std::ostringstream oss;
oss << std::fixed << "The value of \"offset\" is out of range. It must be >= " << 0
<< " and <= " << buffer->GetLength() - NumberSize::BIGUINT64 << ". Received value is: " << offset;
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
return JSAPIFastBuffer::GetValueByIndex(thread, typedArray.GetTaggedValue(), offset, type, littleEndian);
}
JSTaggedValue JSAPIFastBuffer::WriteBigInt64(JSThread *thread, const JSHandle<JSAPIFastBuffer> &buffer,
const JSHandle<JSTaggedValue> &value, uint32_t offset, bool littleEndian)
{
JSType type = JSType::JS_BIGINT64_ARRAY;
auto byteSize = base::TypedArrayHelper::GetElementSize(type);
JSHandle<JSTypedArray> typedArray =
JSHandle<JSTypedArray>(thread, JSTypedArray ::Cast(buffer->GetFastBufferData(thread).GetTaggedObject()));
int64_t valueNum;
bool isLossLess;
ASSERT(value->IsBigInt() || value->IsNumber() || value->IsBoolean());
BigInt::BigIntToInt64(thread, value, &valueNum, &isLossLess);
int64_t left = INT64_MIN;
int64_t right = INT64_MAX;
if (valueNum > right || valueNum < left) {
std::ostringstream oss;
oss << std::fixed << "The value of \"value\" is out of range. It must be >= " << left << " and <= " << right
<< ". Received value is: " << valueNum;
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
if (offset < 0 || offset + NumberSize::BIGUINT64 > buffer->GetLength()) {
std::ostringstream oss;
oss << std::fixed << "The value of \"offset\" is out of range. It must be >= " << 0
<< " and <= " << buffer->GetLength() - NumberSize::BIGUINT64 << ". Received value is: " << offset;
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
JSAPIFastBuffer::SetValueByIndex(thread, typedArray.GetTaggedValue(), offset, value.GetTaggedValue(), type,
littleEndian);
return JSTaggedValue(offset + byteSize);
}
JSTaggedValue JSAPIFastBuffer::ReadBigInt64(JSThread *thread, const JSHandle<JSAPIFastBuffer> &buffer, uint32_t offset,
bool littleEndian)
{
JSType type = JSType::JS_BIGINT64_ARRAY;
JSHandle<JSTypedArray> typedArray =
JSHandle<JSTypedArray>(thread, JSTypedArray ::Cast(buffer->GetFastBufferData(thread).GetTaggedObject()));
if (offset < 0 || offset + NumberSize::BIGINT64 > buffer->GetLength()) {
std::ostringstream oss;
oss << std::fixed << "The value of \"offset\" is out of range. It must be >= " << 0
<< " and <= " << buffer->GetLength() - NumberSize::BIGINT64 << ". Received value is: " << offset;
JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str());
THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
}
return JSAPIFastBuffer ::GetValueByIndex(thread, typedArray.GetTaggedValue(), offset, type, littleEndian);
}
}