| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you 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. | |
| // The functions in this file are specifically not cross-compiled to IR because there | |
| // is no signifcant performance benefit to be gained. | |
| #include "exprs/string-functions.h" | |
| #include <openssl/bio.h> | |
| #include <openssl/evp.h> | |
| #include "gutil/strings/stringpiece.h" | |
| #include <gutil/strings/util.h> | |
| #include <rapidjson/document.h> | |
| #include <rapidjson/stringbuffer.h> | |
| #include <rapidjson/error/en.h> | |
| #include <rapidjson/writer.h> | |
| #include <string> | |
| #include "exprs/anyval-util.h" | |
| #include "util/openssl-util.h" | |
| #include "util/string-util.h" | |
| #include "util/string-parser.h" | |
| #include "common/names.h" | |
| #include "cast-functions.h" | |
| using namespace rapidjson; | |
| using namespace impala_udf; | |
| namespace impala { | |
| #define RETURN_IF_OOM(stmt, result_on_err) \ | |
| try {stmt;} catch (const std::bad_alloc& e) { \ | |
| VLOG_QUERY << "Allocation failed: " << e.what(); return result_on_err; } | |
| #define RETURN_NULL_IF_OOM(stmt) RETURN_IF_OOM(stmt, StringVal::null()); | |
| /// RapidJSON combines different types using templates. A class containing all required | |
| /// interface can be an allocator. | |
| /// This class is a wrapper of FunctionContext for RapidJSON to allocate tracked memory. | |
| class JsonUdfAllocator { | |
| public: | |
| static const bool kNeedFree = false; | |
| JsonUdfAllocator() { DCHECK(false) << "Default constructor should not be used"; } | |
| JsonUdfAllocator(FunctionContext* ctx): ctx_(ctx) {} | |
| void* Malloc(size_t size) { | |
| if (!size) return nullptr; | |
| StringVal buffer(ctx_, size); | |
| if (UNLIKELY(buffer.is_null)) { | |
| // RapidJSON cannot handle allocation failures. We can only throw an exception | |
| // to stop it. | |
| throw std::bad_alloc(); | |
| } | |
| return buffer.ptr; | |
| } | |
| void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { | |
| if (originalPtr == nullptr && newSize > 0) return Malloc(newSize); | |
| if (newSize == 0) return nullptr; | |
| // Do not shrink if new size is smaller than original | |
| if (originalSize >= newSize) return originalPtr; | |
| void* newBuffer = Malloc(newSize); | |
| if (originalSize > 0) memcpy(newBuffer, originalPtr, originalSize); | |
| // Don't need to free the original space. The allocated memory will be recycled in | |
| // bulk after the UDF returns | |
| return newBuffer; | |
| } | |
| // The allocated memory will be recycled in bulk after the UDF returns | |
| static void Free(void* ptr) {} | |
| private: | |
| FunctionContext* ctx_; | |
| }; | |
| /// RapidJSON requires input strings that end with a trailing '\0'. Since StringVal | |
| /// doesn't have a trailing '\0', we need a wrapper Stream for it. | |
| /// A class containing all required interface can be a usable stream for RapidJSON. | |
| class StringValStream { | |
| public: | |
| typedef UTF8<>::Ch Ch; | |
| StringValStream(const StringVal *str) | |
| : pos_(reinterpret_cast<Ch*>(str->ptr)), | |
| head_(reinterpret_cast<Ch*>(str->ptr)), | |
| tail_(reinterpret_cast<Ch*>(str->ptr + str->len)) {} | |
| char Peek() const { | |
| if (pos_ == tail_) return 0; | |
| return *pos_; | |
| } | |
| char Take() { | |
| if (pos_ == tail_) return 0; | |
| return *pos_++; | |
| } | |
| size_t Tell() const { return pos_ - head_; } | |
| // Interfaces that should not be called | |
| char* PutBegin() { DCHECK(false); return 0; } | |
| void Put(Ch) { DCHECK(false); } | |
| size_t PutEnd(Ch*) { DCHECK(false); return 0; } | |
| const Ch* pos_; // Current read position. | |
| const Ch* head_; // Original head of the string. | |
| const Ch* tail_; // Original tail of the string. | |
| }; | |
| typedef GenericDocument<UTF8<>, JsonUdfAllocator> JsonUdfDocument; | |
| typedef GenericValue<UTF8<>, JsonUdfAllocator> JsonUdfValue; | |
| typedef GenericStringBuffer<UTF8<>, JsonUdfAllocator> JsonUdfStrBuffer; | |
| typedef Writer<JsonUdfStrBuffer, UTF8<>, UTF8<>, JsonUdfAllocator> JsonUdfWriter; | |
| static StringVal ToStringVal(FunctionContext* ctx, const JsonUdfValue& values, | |
| JsonUdfAllocator* allocator) { | |
| DCHECK(values.IsArray()); | |
| if (values.Empty()) return StringVal::null(); | |
| JsonUdfStrBuffer sb(allocator); | |
| JsonUdfWriter writer(sb, allocator); | |
| JsonUdfValue res; | |
| if (values.Size() == 1) { | |
| const JsonUdfValue& v = values[0]; | |
| if (v.IsNull()) return StringVal::null(); | |
| if (v.IsString()) { | |
| // RapidJSON will quote the strings. It's incompatible with Hive's behavior when | |
| // the string is at the root, so we convert it ourselves here. | |
| return StringVal::CopyFrom(ctx, reinterpret_cast<const uint8_t*>(v.GetString()), | |
| v.GetStringLength()); | |
| } | |
| RETURN_NULL_IF_OOM(v.Accept(writer)); | |
| } else { // multiple selected items, return an array string | |
| RETURN_NULL_IF_OOM(values.Accept(writer)); | |
| } | |
| const char* res_ptr = sb.GetString(); | |
| return StringVal::CopyFrom(ctx, reinterpret_cast<const uint8_t*>(res_ptr), | |
| sb.GetSize()); | |
| } | |
| // Extract all the values for 'key' where objects in 'queue' contain that key. | |
| // Replace the contents of queue with the values found. | |
| static void SelectByKey(const string& key, JsonUdfValue* queue, | |
| JsonUdfAllocator* allocator) { | |
| SizeType old_items = queue->Size(); // RapidJson uses SizeType instead of size_t | |
| JsonUdfValue item; | |
| for (SizeType i = 0; i < old_items; ++i) { | |
| item = (*queue)[i]; | |
| if (!item.IsObject() || !item.HasMember(key)) continue; | |
| queue->PushBack(item[key], *allocator); | |
| } | |
| queue->Erase(queue->Begin(), queue->Begin() + old_items); | |
| } | |
| // Extract all the values for 'index' where arrays in 'queue' contain that index. | |
| // Replace the contents of queue with the values found. | |
| static void SelectByIndex(const int index, JsonUdfValue* queue, | |
| JsonUdfAllocator* allocator) { | |
| DCHECK(queue->IsArray()); | |
| SizeType old_items = queue->Size(); | |
| for (SizeType i = 0; i < old_items; ++i) { | |
| JsonUdfValue& item = (*queue)[i]; | |
| if (!item.IsArray() || index >= item.Capacity()) continue; | |
| queue->PushBack(item[index], *allocator); | |
| } | |
| queue->Erase(queue->Begin(), queue->Begin() + old_items); | |
| } | |
| // Expand all arrays in the queue and replace the contents of queue with them. | |
| static void ExpandArrays(JsonUdfValue* queue, JsonUdfAllocator* allocator) { | |
| DCHECK(queue->IsArray()); | |
| SizeType old_items = queue->Size(); | |
| for (SizeType i = 0; i < old_items; ++i) { | |
| if (!(*queue)[i].IsArray()) continue; | |
| for (auto& v : (*queue)[i].GetArray()) queue->PushBack(v, *allocator); | |
| } | |
| queue->Erase(queue->Begin(), queue->Begin() + old_items); | |
| } | |
| // Extract all values of the objects in queue and replace the contents of queue with them | |
| static void ExtractValues(JsonUdfValue* queue, JsonUdfAllocator* allocator) { | |
| SizeType old_items = queue->Size(); | |
| for (SizeType i = 0; i < old_items; ++i) { | |
| if (!(*queue)[i].IsObject()) continue; | |
| for (auto& m : (*queue)[i].GetObject()) queue->PushBack(m.value, *allocator); | |
| } | |
| queue->Erase(queue->Begin(), queue->Begin() + old_items); | |
| } | |
| /// Process wildcard(*) in value selection. path_idx is the index after the wildcard in | |
| /// path_str. Return next unprocessed index in path_str. Return -1 for errors. | |
| static int ProcessWildcardKey(FunctionContext* ctx, const StringVal& path_str, | |
| int path_idx, JsonUdfValue* queue, JsonUdfAllocator* allocator) { | |
| DCHECK(queue->IsArray()); | |
| const uint8_t* path = path_str.ptr; | |
| while (path_idx < path_str.len) { | |
| if (path[path_idx] == '[' || path[path_idx] == '.') break; | |
| if (path[path_idx] != ' ') { | |
| string msg = Substitute("Failed to parse json path '$0': " | |
| "Encountered '$1' in position $2, expects ' ', '[' or '.'", | |
| AnyValUtil::ToString(path_str), static_cast<char>(path[path_idx]), path_idx); | |
| ctx->SetError(msg.c_str()); | |
| return -1; | |
| } | |
| ++path_idx; | |
| } | |
| RETURN_IF_OOM(ExtractValues(queue, allocator), -1); | |
| return path_idx; | |
| } | |
| /// Process wildcard(*) in array selection. path_idx is the index after the wildcard in | |
| /// path_str. Return next unprocessed index in path_str. Return -1 for errors. | |
| static int ProcessWildcardIndex(FunctionContext* ctx, const StringVal& path_str, | |
| int path_idx, JsonUdfValue* queue, JsonUdfAllocator* allocator) { | |
| const uint8_t* path = path_str.ptr; | |
| while (path_idx < path_str.len && path[path_idx] != ']') { | |
| if (path[path_idx] != ' ') { // have something else illegal | |
| string msg = Substitute("Failed to parse json path '$0': " | |
| "Encountered '$1' in position $2, expects ' ' or ']'", | |
| AnyValUtil::ToString(path_str), static_cast<char>(path[path_idx]), path_idx); | |
| ctx->SetError(msg.c_str()); | |
| return -1; | |
| } | |
| ++path_idx; | |
| } | |
| if (path_idx == path_str.len) { | |
| string msg = Substitute("Unclosed brackets in json path '$0'", | |
| AnyValUtil::ToString(path_str)); | |
| ctx->SetError(msg.c_str()); | |
| return -1; | |
| } | |
| RETURN_IF_OOM(ExpandArrays(queue, allocator), -1); | |
| return path_idx + 1; // path_idx points at ']' | |
| } | |
| /// Process number in array selection. path_idx points at the start of the number in | |
| /// path_str. Return next unprocessed index in path_str. Return -1 for errors. | |
| static int ProcessNumberIndex(FunctionContext* ctx, const StringVal& path_str, | |
| int path_idx, JsonUdfValue* queue, JsonUdfAllocator* allocator) { | |
| const uint8_t* path = path_str.ptr; | |
| const char* number_start = reinterpret_cast<const char*>(path + path_idx); | |
| int i = path_idx; | |
| while (i < path_str.len && path[i] != ']') ++i; | |
| if (i == path_str.len) { | |
| string msg = Substitute("Unclosed brackets in json path '$0'", | |
| AnyValUtil::ToString(path_str)); | |
| ctx->SetError(msg.c_str()); | |
| return -1; | |
| } | |
| StringParser::ParseResult parse_res; | |
| int index = StringParser::StringToInt<int>(number_start, i - path_idx, &parse_res); | |
| if (parse_res != StringParser::PARSE_SUCCESS || index < 0) { | |
| const char* failure; | |
| if (parse_res == StringParser::PARSE_FAILURE) { | |
| failure = "Failed to parse json path '$0': Expected number at position $1"; | |
| } else if (parse_res == StringParser::PARSE_OVERFLOW) { | |
| failure = "Failed to parse json path '$0': Index too large at position $1"; | |
| } else { | |
| DCHECK(parse_res == StringParser::PARSE_SUCCESS && index < 0); | |
| failure = "Failed to parse json path '$0': Negative index at position $1"; | |
| } | |
| string msg = Substitute(failure, AnyValUtil::ToString(path_str), path_idx); | |
| ctx->SetError(msg.c_str()); | |
| return -1; | |
| } | |
| RETURN_IF_OOM(SelectByIndex(index, queue, allocator), -1); | |
| return i + 1; // i points at ']' | |
| } | |
| /// Parse json_str into Document. Return false for errors. | |
| static bool ParseStringVal(FunctionContext* ctx, const StringVal& json_str, | |
| JsonUdfDocument* doc) { | |
| StringValStream stream(&json_str); | |
| RETURN_IF_OOM(doc->ParseStream(stream), false); | |
| if (doc->HasParseError()) { | |
| string msg = Substitute("Failed to parse json at position $0 since: $1." | |
| " Json string:\n$2", doc->GetErrorOffset(), | |
| GetParseError_En(doc->GetParseError()), AnyValUtil::ToString(json_str)); | |
| ctx->AddWarning(msg.c_str()); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Initial capacity of the BFS queue used in GetJsonObjectImpl | |
| static const int INITIAL_QUEUE_CAPACITY = 64; | |
| /// TODO(IMPALA-7610): parse the JSON path and cache it so we don't need to parse it | |
| /// everytime | |
| StringVal StringFunctions::GetJsonObjectImpl(FunctionContext* ctx, | |
| const StringVal& json_str, const StringVal& path_str) { | |
| if (UNLIKELY(json_str.is_null || json_str.len == 0)) return StringVal::null(); | |
| if (UNLIKELY(path_str.is_null || path_str.len == 0)) { | |
| ctx->SetError("Empty json path"); | |
| return StringVal::null(); | |
| } | |
| int beg = 0; | |
| // Strip off preceding whitespace. | |
| while (beg < path_str.len && path_str.ptr[beg] == ' ') beg++; | |
| if (UNLIKELY(beg == path_str.len || path_str.ptr[beg] != '$')) { | |
| // Here we use '$$' to escape '$' in Substitute | |
| string msg = Substitute("Failed to parse json path '$0': Should start with '$$'", | |
| AnyValUtil::ToString(path_str)); | |
| ctx->SetError(msg.c_str()); | |
| return StringVal::null(); | |
| } | |
| JsonUdfAllocator allocator(ctx); | |
| JsonUdfDocument document(&allocator); | |
| if (!ParseStringVal(ctx, json_str, &document)) return StringVal::null(); | |
| // BFS to extract selected values. We use array of RapidJson instead of std::vector to | |
| // track its memory. | |
| JsonUdfValue queue(kArrayType); | |
| RETURN_NULL_IF_OOM(queue.Reserve(INITIAL_QUEUE_CAPACITY, allocator)); | |
| RETURN_NULL_IF_OOM(queue.PushBack(document, allocator)); | |
| const uint8_t* path = path_str.ptr; | |
| const uint8_t* path_end = path + path_str.len; | |
| for (int i = beg + 1; i < path_str.len;) { | |
| // Each round we extract new items into the queue. Old items will be removed. | |
| switch (path[i]) { | |
| case '$': { | |
| string msg = Substitute("Failed to parse json path '$0':" | |
| " $$ should only be placed at start", AnyValUtil::ToString(path_str)); | |
| ctx->SetError(msg.c_str()); | |
| return StringVal::null(); | |
| } | |
| case '.': { | |
| // Hive does not skip the heading and trailing whitespaces since it simply splits | |
| // the json path by '.'. We should keep the same behavior with MySQL. See | |
| // JSON_EXTRACT in MySQL(5.7+). | |
| for (++i; i < path_str.len && path[i] == ' '; ++i); // skip whitespaces | |
| if (i == path_str.len) { | |
| string msg = Substitute("Failed to parse json path '$0': Found a trailing '.'", | |
| AnyValUtil::ToString(path_str)); | |
| ctx->SetError(msg.c_str()); | |
| return StringVal::null(); | |
| } | |
| if (path[i] == '*') { | |
| i = ProcessWildcardKey(ctx, path_str, ++i, &queue, &allocator); | |
| if (i < 0) return StringVal::null(); | |
| break; | |
| } | |
| const uint8_t* start = path + i; | |
| const uint8_t* end = FindEndOfIdentifier(start, path_end); | |
| // Set error if looking for an empty key | |
| if (end == nullptr) { | |
| string msg = Substitute( | |
| "Failed to parse json path '$0': Expected key at position $1", | |
| AnyValUtil::ToString(path_str), i); | |
| ctx->SetError(msg.c_str()); | |
| return StringVal::null(); | |
| } | |
| // Convert to string to automatically null terminate. | |
| string key = string(start, end); | |
| RETURN_NULL_IF_OOM(SelectByKey(key, &queue, &allocator)); | |
| i += (end - start); | |
| break; | |
| } | |
| case '[': { | |
| // TODO(IMPALA-7611) support range syntax like [2 to 7] and keyword `last`. | |
| // Hive has not supported it yet but MySQL does since 8.0.2. See | |
| // https://dev.mysql.com/worklog/task/?id=9831 and | |
| // https://github.com/mysql/mysql-server/commit/9f4678a | |
| for (++i; i < path_str.len && path[i] == ' '; ++i); // skip whitespaces | |
| if (i == path_str.len) return StringVal::null(); | |
| if (path[i] == '*') { | |
| i = ProcessWildcardIndex(ctx, path_str, ++i, &queue, &allocator); | |
| if (i < 0) return StringVal::null(); | |
| break; | |
| } | |
| // else it should be a number | |
| i = ProcessNumberIndex(ctx, path_str, i, &queue, &allocator); | |
| if (i < 0) return StringVal::null(); | |
| break; | |
| } | |
| case ' ': | |
| ++i; | |
| break; | |
| default: { | |
| string msg = Substitute( | |
| "Failed to parse json path '$0': Unexpected char '$1' at position $2", | |
| AnyValUtil::ToString(path_str), static_cast<char>(path[i]), i); | |
| ctx->SetError(msg.c_str()); | |
| return StringVal::null(); | |
| } | |
| } | |
| } | |
| return ToStringVal(ctx, queue, &allocator); | |
| } | |
| // Initializes the EncryptionKey for AES Encryption/ Decryption by validating arguments. | |
| Status InitializeEncryptionKey(FunctionContext* ctx, const StringVal& expr, | |
| const StringVal& key, const StringVal& mode, const StringVal& iv, bool is_encrypt, | |
| EncryptionKey* encryption_key) { | |
| if (key.is_null) { | |
| return Status(Substitute("Key cannot be NULL.")); | |
| } | |
| if (key.len != 16 && key.len != 32) { | |
| return Status(Substitute("AES only supports 128 and 256 bit key lengths.")); | |
| } | |
| AES_CIPHER_MODE cipher_mode; | |
| // If user passed a "NULL" field in mode, default AES encryption mode is chosen. | |
| if (mode.is_null) { | |
| cipher_mode = EncryptionKey::GetSupportedDefaultMode(); | |
| bool* state = reinterpret_cast<bool*>( | |
| ctx->GetFunctionState(FunctionContext::THREAD_LOCAL)); | |
| if (state == nullptr || *state == false) { | |
| VLOG_QUERY << "No AES mode was specified by user. Using " << | |
| EncryptionKey::ModeToString(cipher_mode) << " mode as default."; | |
| if (state != nullptr) { | |
| *state = true; | |
| } | |
| } | |
| } else { | |
| cipher_mode = EncryptionKey::StringToMode | |
| (std::string_view(reinterpret_cast<const char*>(mode.ptr), mode.len)); | |
| } | |
| if (cipher_mode == AES_CIPHER_MODE::INVALID) { | |
| return Status(Substitute("Invalid AES 'mode': $0", StringPiece | |
| (reinterpret_cast<const char*>(mode.ptr), mode.len)).c_str()); | |
| } | |
| bool is_ecb = (cipher_mode == AES_CIPHER_MODE::AES_256_ECB | |
| || cipher_mode == AES_CIPHER_MODE::AES_128_ECB); | |
| // Check if iv is null in case of non ECB modes. | |
| if (!is_ecb && iv.is_null) { | |
| return Status(Substitute("IV vector required for $0 mode", | |
| EncryptionKey::ModeToString(cipher_mode)).c_str()); | |
| } | |
| // Check if IV vector size is valid (<= AES_BLOCK_SIZE) in case of non ECB modes. | |
| if (!is_ecb && iv.len > AES_BLOCK_SIZE) { | |
| return Status(Substitute("IV vector size is greater than 16 bytes.")); | |
| } | |
| // ECB mode is not supported for Encryption. | |
| if (is_encrypt && is_ecb) { | |
| return Status(Substitute("ECB mode is not supported for encryption.")); | |
| } | |
| // Initialize key and IV. | |
| Status status = encryption_key->InitializeFields(key.ptr, key.len, | |
| iv.ptr, iv.len, cipher_mode); | |
| return status; | |
| } | |
| // An entrypoint to perform AES decryption on a given expression string. | |
| // GCM mode expects expression, key, AES mode and iv vector. | |
| // CTR and CFB modes expect expression, key, AES mode and iv vector. | |
| // ECB mode expects expression, key, AES mode. | |
| // | |
| // If 'mode' is NULL, a default mode will be chosen at runtime. | |
| // | |
| // If the mode passed by the user is not supported by Impala or by the OpenSSL | |
| // library used internally, an error is returned. | |
| // | |
| // Description of the modes supported: | |
| // AES-GCM (Advanced Encryption Standard Galois/Counter Mode) is a mode of operation | |
| // for symmetric key cryptographic block ciphers. It combines the AES block cipher | |
| // with the Galois/Counter Mode (GCM) operation for authenticated encryption. | |
| // AES-GCM provides both confidentiality and integrity, making it suitable for secure | |
| // communication and storage. Due to its security features and efficiency, | |
| // AES-GCM is chosen as the default choice for the current implementation. | |
| // | |
| // AES-ECB (Electronic Codebook) mode is a basic mode of operation for the AES | |
| // block cipher. In ECB mode, each block of plaintext is encrypted independently with | |
| // the same key, resulting in identical plaintext blocks producing identical ciphertext | |
| // blocks. It is included for bringing compatibility with legacy systems. | |
| // NOTE: This mode is only supported for decryption. | |
| // | |
| // CTR Mode: | |
| // AES-CTR (Counter) mode is a mode of operation for block ciphers that turns a block | |
| // cipher into a stream cipher. It works by encrypting a counter value to produce a | |
| // stream of key material, which is then XORed with the plaintext to produce the | |
| // ciphertext. CTR mode offers parallel encryption and decryption. | |
| // | |
| // CFB Mode: | |
| // AES-CFB (Cipher Feedback) mode is another mode of operation for block ciphers, where | |
| // ciphertext feedback is used to create a stream of key material. It operates on a | |
| // block-by-block basis, where the previous ciphertext block is encrypted and then XORed | |
| // with the plaintext to produce the next ciphertext block. | |
| StringVal StringFunctions::AesDecryptImpl(FunctionContext* ctx, const StringVal& expr, | |
| const StringVal& key, const StringVal& mode, const StringVal& iv) { | |
| if (expr.is_null) { | |
| return StringVal::null(); | |
| } | |
| EncryptionKey encryption_key; | |
| Status status = InitializeEncryptionKey(ctx, expr, key, mode, iv, false, | |
| &encryption_key); | |
| if (!status.ok()) { | |
| ctx->SetError(status.msg().msg().data()); | |
| return StringVal::null(); | |
| } | |
| int64_t len = expr.len; | |
| // Remove spaces and set value of gcm_tag in case of GCM mode | |
| if (encryption_key.IsGcmMode()) { | |
| if (len < AES_BLOCK_SIZE) { | |
| ctx->SetError("AES GCM input too short to contain a tag"); | |
| return StringVal::null(); | |
| } | |
| len -= AES_BLOCK_SIZE; | |
| encryption_key.SetGcmTag(expr.ptr + len); | |
| } | |
| StringVal result = StringVal(ctx, len); | |
| // Decrypt the input | |
| int64_t out_len = 0; | |
| status = encryption_key.Decrypt(expr.ptr, len, result.ptr, &out_len); | |
| if (!status.ok()) { | |
| ctx->SetError("AES decryption failed"); | |
| return StringVal::null(); | |
| } | |
| const int64_t len_diff = result.len - out_len; | |
| DCHECK(len_diff == 0 || (encryption_key.IsEcbMode() && 0 < len_diff && | |
| len_diff <= AES_BLOCK_SIZE)); | |
| result.len = out_len; | |
| return result; | |
| } | |
| // An entrypoint to perform AES encryption on a given expression string. In contrast | |
| // to AesDecryptImpl(), it does not support ECB modes. For other details, see the | |
| // comment at AesDecryptImpl(). | |
| StringVal StringFunctions::AesEncryptImpl(FunctionContext* ctx, const StringVal& expr, | |
| const StringVal& key, const StringVal& mode, const StringVal& iv) { | |
| if (expr.is_null) { | |
| return StringVal::null(); | |
| } | |
| EncryptionKey encryption_key; | |
| Status status = InitializeEncryptionKey(ctx, expr, key, mode, iv, true, | |
| &encryption_key); | |
| if (!status.ok()) { | |
| ctx->SetError(status.msg().msg().data()); | |
| return StringVal::null(); | |
| } | |
| // Calculate expected output length | |
| int expected_out_len = expr.len; | |
| // Append space for gcm_tag in case of GCM mode | |
| if (encryption_key.IsGcmMode()) { | |
| expected_out_len += AES_BLOCK_SIZE; | |
| } | |
| // Allocate buffer for output | |
| StringVal result = StringVal(ctx, expected_out_len); | |
| // Encrypt the input | |
| int64_t out_len = 0; | |
| status = encryption_key.Encrypt(expr.ptr, expr.len, result.ptr, | |
| &out_len); | |
| if (!status.ok()) { | |
| ctx->SetError("AES encryption failed."); | |
| return StringVal::null(); | |
| } | |
| // Append gcm_tag to encrypted buffer | |
| if (encryption_key.IsGcmMode()) { | |
| encryption_key.GetGcmTag(result.ptr + out_len); | |
| out_len += AES_BLOCK_SIZE; | |
| } | |
| // Ensure expected output length matches actual output length | |
| DCHECK_EQ(expected_out_len, out_len); | |
| return result; | |
| } | |
| } |