1 /**
2 * Copyright (c) 2023-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "include/object_header.h"
17 #include "intrinsics.h"
18 #include "plugins/ets/runtime/types/ets_arraybuffer.h"
19 #include "plugins/ets/runtime/types/ets_string.h"
20 #include "plugins/ets/runtime/ets_handle_scope.h"
21 #include "plugins/ets/runtime/ets_handle.h"
22 #include "plugins/ets/runtime/types/ets_primitives.h"
23 #include "plugins/ets/runtime/intrinsics/helpers/array_buffer_helper.h"
24
25 using namespace std::string_view_literals;
26 constexpr std::array UTF8_ENCODINGS = {"utf8"sv, "utf-8"sv, "ascii"sv};
27 constexpr std::array UTF16_ENCODINGS = {"utf16le"sv, "ucs2"sv, "ucs-2"sv};
28 constexpr std::array BASE64_ENCODINGS = {"base64"sv, "base64url"sv};
29 constexpr std::array LATIN_ENCODINGS = {"latin1"sv, "binary"sv};
30
31 namespace ark::ets::intrinsics {
32
33 /// @brief Creates a new ArrayBuffer with specified size and optional initial data
CreateArrayBuffer(EtsCoroutine * coro,EtsInt byteLength,const uint8_t * data=nullptr)34 static EtsHandle<EtsEscompatArrayBuffer> CreateArrayBuffer(EtsCoroutine *coro, EtsInt byteLength,
35 const uint8_t *data = nullptr)
36 {
37 void *buffer = nullptr;
38 EtsHandle<EtsEscompatArrayBuffer> newBuffer(coro, EtsEscompatArrayBuffer::Create(coro, byteLength, &buffer));
39 if (UNLIKELY(newBuffer.GetPtr() == nullptr)) {
40 return newBuffer;
41 }
42 if (data != nullptr && byteLength > 0) {
43 std::copy_n(data, byteLength, reinterpret_cast<uint8_t *>(buffer));
44 }
45 return newBuffer;
46 }
47
48 /**
49 * @brief Converts an ETS string to bytes using specified encoding
50 * @details Supported encodings:
51 * - UTF-8/UTF-8
52 * - ASCII (7-bit)
53 * - UTF-16LE/UCS2
54 * - Base64/Base64URL
55 * - Latin1/Binary
56 * - Hex (must be even length)
57 */
ConvertEtsStringToBytes(EtsString * strObj,EtsString * encodingObj,EtsCoroutine * coro,const LanguageContext & ctx)58 static PandaVector<uint8_t> ConvertEtsStringToBytes(EtsString *strObj, EtsString *encodingObj, EtsCoroutine *coro,
59 const LanguageContext &ctx)
60 {
61 PandaVector<uint8_t> strBuf;
62 PandaVector<uint8_t> encodingBuf;
63 PandaString input(strObj->ConvertToStringView(&strBuf));
64 PandaString encoding =
65 encodingObj != nullptr ? PandaString(encodingObj->ConvertToStringView(&encodingBuf)) : "utf8";
66 auto res = helpers::encoding::ConvertStringToBytes(input, encoding);
67 if (std::holds_alternative<helpers::Err<PandaString>>(res)) {
68 ThrowException(ctx, coro, ctx.GetIllegalArgumentExceptionClassDescriptor(),
69 utf::CStringAsMutf8(std::get<helpers::Err<PandaString>>(res).Message().c_str()));
70 return PandaVector<uint8_t> {};
71 }
72 return std::get<PandaVector<uint8_t>>(res);
73 }
74
75 /// @brief Calculates byte length of string when encoded
EtsStringBytesLength(EtsString * strObj,EtsString * encodingObj)76 extern "C" ets_int EtsStringBytesLength(EtsString *strObj, EtsString *encodingObj)
77 {
78 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
79 auto ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
80 PandaVector<uint8_t> strBuf;
81 PandaVector<uint8_t> encodingBuf;
82 PandaString input(strObj->ConvertToStringView(&strBuf));
83 PandaString encoding =
84 encodingObj != nullptr ? PandaString(encodingObj->ConvertToStringView(&encodingBuf)) : "utf8";
85 auto res = helpers::encoding::CalculateStringBytesLength(input, encoding);
86 if (std::holds_alternative<helpers::Err<PandaString>>(res)) {
87 ThrowException(ctx, coro, ctx.GetIllegalArgumentExceptionClassDescriptor(),
88 utf::CStringAsMutf8(std::get<helpers::Err<PandaString>>(res).Message().c_str()));
89 return -1;
90 }
91 return std::get<int32_t>(res);
92 }
93
94 /// @brief Creates ArrayBuffer from encoded string
EtsArrayBufferFromEncodedString(EtsString * strObj,EtsString * encodingObj)95 extern "C" EtsEscompatArrayBuffer *EtsArrayBufferFromEncodedString(EtsString *strObj, EtsString *encodingObj)
96 {
97 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
98 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
99 PandaVector<uint8_t> bytes = ConvertEtsStringToBytes(strObj, encodingObj, coro, ctx);
100 auto byteLength = static_cast<EtsInt>(bytes.size());
101 [[maybe_unused]] EtsHandleScope s(coro);
102 EtsHandle<EtsEscompatArrayBuffer> newBuffer =
103 CreateArrayBuffer(coro, byteLength, byteLength > 0 ? bytes.data() : nullptr);
104 if (newBuffer.GetPtr() == nullptr) {
105 ASSERT(coro->HasPendingException());
106 return nullptr;
107 }
108 return newBuffer.GetPtr();
109 }
110
EtsEscompatArrayBufferSetValues(EtsEscompatArrayBuffer * arrayBuffer,EtsEscompatArrayBuffer * other,EtsInt begin)111 extern "C" void EtsEscompatArrayBufferSetValues(EtsEscompatArrayBuffer *arrayBuffer, EtsEscompatArrayBuffer *other,
112 EtsInt begin)
113 {
114 arrayBuffer->SetValues(other, begin);
115 }
116
EtsEscompatArrayBufferAt(EtsEscompatArrayBuffer * arrayBuffer,EtsInt pos)117 extern "C" EtsByte EtsEscompatArrayBufferAt(EtsEscompatArrayBuffer *arrayBuffer, EtsInt pos)
118 {
119 return arrayBuffer->At(pos);
120 }
121
EtsEscompatArrayBufferSet(EtsEscompatArrayBuffer * arrayBuffer,EtsInt pos,EtsByte val)122 extern "C" void EtsEscompatArrayBufferSet(EtsEscompatArrayBuffer *arrayBuffer, EtsInt pos, EtsByte val)
123 {
124 arrayBuffer->Set(pos, val);
125 }
126
EtsEscompatArrayBufferAllocateNonMovable(EtsInt length)127 extern "C" ObjectHeader *EtsEscompatArrayBufferAllocateNonMovable(EtsInt length)
128 {
129 return EtsEscompatArrayBuffer::AllocateNonMovableArray(length);
130 }
131
EtsEscompatArrayBufferGetAddress(ObjectHeader * byteArray)132 extern "C" EtsLong EtsEscompatArrayBufferGetAddress(ObjectHeader *byteArray)
133 {
134 ASSERT(byteArray != nullptr);
135 return EtsEscompatArrayBuffer::GetAddress(reinterpret_cast<EtsByteArray *>(byteArray));
136 }
137
138 /// @brief Creates new ArrayBuffer from slice of existing buffer
EtsArrayBufferFromBufferSlice(EtsEscompatArrayBuffer * obj,ets_int offset,ets_int length)139 extern "C" EtsEscompatArrayBuffer *EtsArrayBufferFromBufferSlice(EtsEscompatArrayBuffer *obj, ets_int offset,
140 ets_int length)
141 {
142 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
143 LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
144 [[maybe_unused]] EtsHandleScope s(coro);
145 EtsHandle<EtsEscompatArrayBuffer> original(coro, obj);
146 if (original->WasDetached()) {
147 ThrowException(ctx, coro, ctx.GetIllegalArgumentExceptionClassDescriptor(),
148 utf::CStringAsMutf8("ArrayBuffer was detached"));
149 return nullptr;
150 }
151 ASSERT(original->GetData() != nullptr);
152
153 EtsInt origByteLength = original->GetByteLength();
154 if (offset < 0 || offset > origByteLength) {
155 ThrowException(ctx, coro, ctx.GetIndexOutOfBoundsExceptionClassDescriptor(),
156 utf::CStringAsMutf8("Offset is out of bounds"));
157 return nullptr;
158 }
159 if (length < 0 || length > origByteLength - offset) {
160 ThrowException(ctx, coro, ctx.GetIndexOutOfBoundsExceptionClassDescriptor(),
161 utf::CStringAsMutf8("Length is out of bounds"));
162 return nullptr;
163 }
164
165 EtsHandle<EtsEscompatArrayBuffer> newBuffer = CreateArrayBuffer(coro, length);
166 if (newBuffer.GetPtr() == nullptr) {
167 ASSERT(coro->HasPendingException());
168 return nullptr;
169 }
170 std::copy_n(std::next(reinterpret_cast<uint8_t *>(original->GetData()), offset), length,
171 reinterpret_cast<uint8_t *>(newBuffer->GetData()));
172 return newBuffer.GetPtr();
173 }
174
175 /**
176 * @brief Converts ArrayBuffer content to string using specified encoding.
177 *
178 * This function validates the input buffer and indices, extracts the requested bytes,
179 * determines the encoding, and then converts the byte sequence into the desired string.
180 */
EtsArrayBufferToString(EtsEscompatArrayBuffer * buffer,EtsString * encodingObj,ets_int start,ets_int end)181 extern "C" EtsString *EtsArrayBufferToString(EtsEscompatArrayBuffer *buffer, EtsString *encodingObj, ets_int start,
182 ets_int end)
183 {
184 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
185 auto ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::ETS);
186
187 auto vb = helpers::encoding::ValidateBuffer(buffer);
188 if (std::holds_alternative<helpers::Err<PandaString>>(vb)) {
189 ThrowException(ctx, coro, ctx.GetIllegalArgumentExceptionClassDescriptor(),
190 utf::CStringAsMutf8(std::get<helpers::Err<PandaString>>(vb).Message().c_str()));
191 return nullptr;
192 }
193
194 [[maybe_unused]] EtsHandleScope scope(coro);
195 EtsHandle<EtsEscompatArrayBuffer> buf(coro, buffer);
196 EtsInt byteLength = buf->GetByteLength();
197
198 auto vi = helpers::encoding::ValidateIndices(byteLength, start, end);
199 if (std::holds_alternative<helpers::Err<PandaString>>(vi)) {
200 ThrowException(ctx, coro, ctx.GetIllegalArgumentExceptionClassDescriptor(),
201 utf::CStringAsMutf8(std::get<helpers::Err<PandaString>>(vi).Message().c_str()));
202 return nullptr;
203 }
204
205 auto length = static_cast<size_t>(end - start);
206 PandaVector<uint8_t> bytes(length);
207 if (buf->GetData() != nullptr) {
208 std::copy_n(std::next(reinterpret_cast<uint8_t *>(buf->GetData()), start), length, bytes.data());
209 }
210
211 PandaVector<uint8_t> encodingBuf;
212 PandaString encoding =
213 encodingObj != nullptr ? PandaString(encodingObj->ConvertToStringView(&encodingBuf)) : "utf8";
214
215 PandaString output;
216 if (std::find(UTF8_ENCODINGS.begin(), UTF8_ENCODINGS.end(), encoding) != UTF8_ENCODINGS.end()) {
217 output = helpers::encoding::ConvertUtf8Encoding(bytes);
218 } else if (std::find(UTF16_ENCODINGS.begin(), UTF16_ENCODINGS.end(), encoding) != UTF16_ENCODINGS.end()) {
219 output = helpers::encoding::ConvertUtf16Encoding(bytes);
220 } else if (std::find(BASE64_ENCODINGS.begin(), BASE64_ENCODINGS.end(), encoding) != BASE64_ENCODINGS.end()) {
221 output = helpers::encoding::ConvertBase64Encoding(bytes, encoding);
222 } else if (encoding == "hex") {
223 output = helpers::encoding::ConvertHexEncoding(bytes);
224 } else if (std::find(LATIN_ENCODINGS.begin(), LATIN_ENCODINGS.end(), encoding) != LATIN_ENCODINGS.end()) {
225 output = helpers::encoding::ConvertLatinEncoding(bytes);
226 } else {
227 PandaString errMsg = "Unsupported encoding: " + encoding;
228 ThrowException(ctx, coro, ctx.GetIllegalArgumentExceptionClassDescriptor(),
229 utf::CStringAsMutf8(errMsg.c_str()));
230 return nullptr;
231 }
232 return EtsString::CreateFromUtf8(output.data(), output.size());
233 }
234
235 } // namespace ark::ets::intrinsics
236