Skip to content

Commit

Permalink
Formatting: JsThemis native extension (#412)
Browse files Browse the repository at this point in the history
* Formatting: JsThemis native extension

Add native extension of JsThemis to "make fmt" target group.

This requires quite a bit of effort because clang-tidy insists on having
access to *all* source code involved in complation, including all those
NodeJS headers and its modules.

First of all, we have to add some hardcoded paths to NodeJS headers.
I have not found a proper way to get these paths so we just hardcode
them and hope that this is fine.

Then we need to have <nan.h> header file for which we have to install
the nan module. We do a little hack to achieve that, pretending to
'format' the installation path for Node modules.

* Add NodeJS to format checking image

Install npm and nodejs as we need them for checking JsThemis native
source code. At least for now.

* Add a warning if NodeJS is not installed

Print out a warning when formatting files if NodeJS is not installed and
we are not able to reformat JsThemis files.

* Add symlink to jsthemis.mk for Rust's libthemis-src

The current build system for libthemis-src is quite messed up and
requires peculiar symlinking to work. Add a symlink for the new file
included from the main Makefile.
  • Loading branch information
forelocked-beobachter authored and ilammy committed Mar 7, 2019
1 parent 969998f commit 4d11421
Show file tree
Hide file tree
Showing 22 changed files with 1,487 additions and 1,027 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Expand Up @@ -28,6 +28,7 @@ jobs:
CLANG_FORMAT: clang-format-7
CLANG_TIDY: clang-tidy-7
steps:
- run: sudo apt-get update && sudo DEBIAN_FRONTEND=noninteractive apt-get -y install nodejs npm
- checkout
- run: git submodule update --init
- run: make fmt_check ENGINE=boringssl
Expand Down
1 change: 1 addition & 0 deletions Makefile
Expand Up @@ -242,6 +242,7 @@ endif
ifndef ERROR
include src/soter/soter.mk
include src/themis/themis.mk
include src/wrappers/themis/jsthemis/jsthemis.mk
include jni/themis_jni.mk
endif

Expand Down
28 changes: 15 additions & 13 deletions src/wrappers/themis/jsthemis/addon.cpp
Expand Up @@ -15,24 +15,26 @@
*/

#include <node.h>

#include "errors.hpp"
#include "secure_message.hpp"
#include "secure_keygen.hpp"
#include "secure_session.hpp"
#include "secure_cell_seal.hpp"
#include "secure_cell_context_imprint.hpp"
#include "secure_cell_seal.hpp"
#include "secure_cell_token_protect.hpp"
#include "secure_comparator.hpp"
#include "secure_keygen.hpp"
#include "secure_message.hpp"
#include "secure_session.hpp"

void InitAll(v8::Handle<v8::Object> exports) {
jsthemis::Errors::Init(exports);
jsthemis::SecureMessage::Init(exports);
jsthemis::KeyPair::Init(exports);
jsthemis::SecureSession::Init(exports);
jsthemis::SecureCellSeal::Init(exports);
jsthemis::SecureCellContextImprint::Init(exports);
jsthemis::SecureCellTokenProtect::Init(exports);
jsthemis::SecureComparator::Init(exports);
void InitAll(v8::Handle<v8::Object> exports)
{
jsthemis::Errors::Init(exports);
jsthemis::SecureMessage::Init(exports);
jsthemis::KeyPair::Init(exports);
jsthemis::SecureSession::Init(exports);
jsthemis::SecureCellSeal::Init(exports);
jsthemis::SecureCellContextImprint::Init(exports);
jsthemis::SecureCellTokenProtect::Init(exports);
jsthemis::SecureComparator::Init(exports);
}

NODE_MODULE(jsthemis, InitAll)
15 changes: 9 additions & 6 deletions src/wrappers/themis/jsthemis/common.hpp
Expand Up @@ -17,11 +17,14 @@
#ifndef JSTHEMIS_COMMON_HPP_
#define JSTHEMIS_COMMON_HPP_

#define RETURN_BUFFER(buf, length) \
v8::Local<v8::Object> globalObj = v8::Context::GetCurrent()->Global(); \
v8::Local<v8::Function> bufferConstructor = v8::Local<v8::Function>::Cast(globalObj->Get(v8::String::New("Buffer"))); \
v8::Handle<v8::Value> constructorArgs[3] = { buf->handle_, v8::Integer::New(length), v8::Integer::New(0) }; \
v8::Local<v8::Object> actualBuffer = bufferConstructor->NewInstance(3, constructorArgs); \
return scope.Close(actualBuffer)
#define RETURN_BUFFER(buf, length) \
v8::Local<v8::Object> globalObj = v8::Context::GetCurrent()->Global(); \
v8::Local<v8::Function> bufferConstructor = v8::Local<v8::Function>::Cast( \
globalObj->Get(v8::String::New("Buffer"))); \
v8::Handle<v8::Value> constructorArgs[3] = {buf->handle_, \
v8::Integer::New(length), \
v8::Integer::New(0)}; \
v8::Local<v8::Object> actualBuffer = bufferConstructor->NewInstance(3, constructorArgs); \
return scope.Close(actualBuffer)

#endif /* JSTHEMIS_COMMON_HPP_ */
111 changes: 62 additions & 49 deletions src/wrappers/themis/jsthemis/errors.cpp
Expand Up @@ -21,116 +21,129 @@
#include <nan.h>
#include <node.h>

namespace jsthemis {
namespace jsthemis
{

static inline void ExportStatusCode(v8::Handle<v8::Object>& exports, const char* name, themis_status_t status) {
static inline void ExportStatusCode(v8::Handle<v8::Object>& exports, const char* name, themis_status_t status)
{
exports->Set(Nan::New(name).ToLocalChecked(), Nan::New(status));
}
}

void Errors::Init(v8::Handle<v8::Object> exports) {
ExportStatusCode(exports, "SUCCESS", THEMIS_SUCCESS);
ExportStatusCode(exports, "FAIL", THEMIS_FAIL);
ExportStatusCode(exports, "INVALID_PARAMETER", THEMIS_INVALID_PARAMETER);
ExportStatusCode(exports, "NO_MEMORY", THEMIS_NO_MEMORY);
ExportStatusCode(exports, "BUFFER_TOO_SMALL", THEMIS_BUFFER_TOO_SMALL);
ExportStatusCode(exports, "DATA_CORRUPT", THEMIS_DATA_CORRUPT);
ExportStatusCode(exports, "INVALID_SIGNATURE", THEMIS_INVALID_SIGNATURE);
ExportStatusCode(exports, "NOT_SUPPORTED", THEMIS_NOT_SUPPORTED);
void Errors::Init(v8::Handle<v8::Object> exports)
{
ExportStatusCode(exports, "SUCCESS", THEMIS_SUCCESS);
ExportStatusCode(exports, "FAIL", THEMIS_FAIL);
ExportStatusCode(exports, "INVALID_PARAMETER", THEMIS_INVALID_PARAMETER);
ExportStatusCode(exports, "NO_MEMORY", THEMIS_NO_MEMORY);
ExportStatusCode(exports, "BUFFER_TOO_SMALL", THEMIS_BUFFER_TOO_SMALL);
ExportStatusCode(exports, "DATA_CORRUPT", THEMIS_DATA_CORRUPT);
ExportStatusCode(exports, "INVALID_SIGNATURE", THEMIS_INVALID_SIGNATURE);
ExportStatusCode(exports, "NOT_SUPPORTED", THEMIS_NOT_SUPPORTED);
ExportStatusCode(exports, "SSESSION_KA_NOT_FINISHED", THEMIS_SSESSION_KA_NOT_FINISHED);
ExportStatusCode(exports, "SSESSION_TRANSPORT_ERROR", THEMIS_SSESSION_TRANSPORT_ERROR);
ExportStatusCode(exports, "SSESSION_GET_PUB_FOR_ID_CALLBACK_ERROR", THEMIS_SSESSION_GET_PUB_FOR_ID_CALLBACK_ERROR);
ExportStatusCode(exports,
"SSESSION_GET_PUB_FOR_ID_CALLBACK_ERROR",
THEMIS_SSESSION_GET_PUB_FOR_ID_CALLBACK_ERROR);
ExportStatusCode(exports, "SCOMPARE_NOT_READY", THEMIS_SCOMPARE_NOT_READY);
}
}

static const char* ErrorDescription(themis_status_t status) {
static const char* ErrorDescription(themis_status_t status)
{
switch (status) {
case THEMIS_SUCCESS:
return "success";
return "success";
case THEMIS_FAIL:
return "failure";
return "failure";
case THEMIS_INVALID_PARAMETER:
return "invalid parameter";
return "invalid parameter";
case THEMIS_NO_MEMORY:
return "out of memory";
return "out of memory";
case THEMIS_BUFFER_TOO_SMALL:
return "buffer too small";
return "buffer too small";
case THEMIS_DATA_CORRUPT:
return "corrupted data";
return "corrupted data";
case THEMIS_INVALID_SIGNATURE:
return "invalid signature";
return "invalid signature";
case THEMIS_NOT_SUPPORTED:
return "operation not supported";
return "operation not supported";
default:
return "unknown error";
return "unknown error";
}
}
}

static const char* ErrorDescriptionSecureSession(themis_status_t status) {
static const char* ErrorDescriptionSecureSession(themis_status_t status)
{
switch (status) {
case THEMIS_SSESSION_SEND_OUTPUT_TO_PEER:
return "send key agreement data to peer";
return "send key agreement data to peer";
case THEMIS_SSESSION_KA_NOT_FINISHED:
return "key agreement not finished";
return "key agreement not finished";
case THEMIS_SSESSION_TRANSPORT_ERROR:
return "transport layer error";
return "transport layer error";
case THEMIS_SSESSION_GET_PUB_FOR_ID_CALLBACK_ERROR:
return "failed to get public key for ID";
return "failed to get public key for ID";
default:
return ErrorDescription(status);
return ErrorDescription(status);
}
}
}

static const char* ErrorDescriptionSecureComparator(themis_status_t status) {
static const char* ErrorDescriptionSecureComparator(themis_status_t status)
{
switch (status) {
case THEMIS_SCOMPARE_SEND_OUTPUT_TO_PEER:
return "send comparison data to peer";
return "send comparison data to peer";
case THEMIS_SCOMPARE_NOT_READY:
return "comparator not ready";
return "comparator not ready";
case THEMIS_SCOMPARE_MATCH:
return "data matches";
return "data matches";
case THEMIS_SCOMPARE_NO_MATCH:
return "data does not match";
return "data does not match";
default:
return ErrorDescription(status);
return ErrorDescription(status);
}
}
}

static v8::Local<v8::Value> WithStatus(v8::Local<v8::Value> error, themis_status_t status) {
static v8::Local<v8::Value> WithStatus(v8::Local<v8::Value> error, themis_status_t status)
{
v8::Local<v8::Object> object = error.As<v8::Object>();
object->Set(Nan::New("code").ToLocalChecked(), Nan::New(status));
return error;
}
}

void ThrowError(const char* domain, themis_status_t status) {
void ThrowError(const char* domain, themis_status_t status)
{
std::string message;
message += domain;
message += ": ";
message += ErrorDescription(status);
Nan::ThrowError(WithStatus(Nan::Error(message.c_str()), status));
}
}

void ThrowParameterError(const char* domain, const char* description) {
void ThrowParameterError(const char* domain, const char* description)
{
std::string message;
message += domain;
message += ": ";
message += description;
Nan::ThrowError(WithStatus(Nan::Error(message.c_str()), THEMIS_INVALID_PARAMETER));
}
}

void ThrowSecureSessionError(const char* domain, themis_status_t status) {
void ThrowSecureSessionError(const char* domain, themis_status_t status)
{
std::string message;
message += domain;
message += ": ";
message += ErrorDescriptionSecureSession(status);
Nan::ThrowError(WithStatus(Nan::Error(message.c_str()), status));
}
}

void ThrowSecureComparatorError(const char* domain, themis_status_t status) {
void ThrowSecureComparatorError(const char* domain, themis_status_t status)
{
std::string message;
message += domain;
message += ": ";
message += ErrorDescriptionSecureComparator(status);
Nan::ThrowError(WithStatus(Nan::Error(message.c_str()), status));
}
}

} // namespace jsthemis
18 changes: 10 additions & 8 deletions src/wrappers/themis/jsthemis/errors.hpp
Expand Up @@ -21,21 +21,23 @@

#include <themis/themis.h>

namespace jsthemis {
namespace jsthemis
{

namespace Errors {
namespace Errors
{

void Init(v8::Handle<v8::Object> exports);
void Init(v8::Handle<v8::Object> exports);

} // namespace Errors
} // namespace Errors

void ThrowError(const char* domain, themis_status_t status);
void ThrowError(const char* domain, themis_status_t status);

void ThrowParameterError(const char* domain, const char* description);
void ThrowParameterError(const char* domain, const char* description);

void ThrowSecureSessionError(const char* domain, themis_status_t status);
void ThrowSecureSessionError(const char* domain, themis_status_t status);

void ThrowSecureComparatorError(const char* domain, themis_status_t status);
void ThrowSecureComparatorError(const char* domain, themis_status_t status);

} // namespace jsthemis

Expand Down
65 changes: 65 additions & 0 deletions src/wrappers/themis/jsthemis/jsthemis.mk
@@ -0,0 +1,65 @@
#
# Copyright (c) 2019 Cossack Labs Limited
#
# 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.
#

JSTHEMIS_SRC = $(SRC_PATH)/wrappers/themis/jsthemis
JSTHEMIS_OBJ = $(OBJ_PATH)/wrappers/themis/jsthemis

JSTHEMIS_SOURCES = $(wildcard $(JSTHEMIS_SRC)/*.cpp)
JSTHEMIS_HEADERS = $(wildcard $(JSTHEMIS_SRC)/*.hpp)

# Unfortunately, clang-tidy requires full compilation flags to be able to work and
# node-gyp add quite a few custom flags with include search paths. It also requires
# some Node modules for compilation. We recreate the same environment here as for
# the "jsthemis_install" target. However, we can't do that without NPM present.
# Therefore we format JsThemis code only if NodeJS is actually installed.
ifdef NPM_VERSION

# A hack to install "nan" before formatting any JsThemis files
FMT_FIXUP += $(JSTHEMIS_OBJ)/node_modules/nan
FMT_CHECK += $(JSTHEMIS_OBJ)/node_modules/nan

$(JSTHEMIS_OBJ)/node_modules/nan:
@mkdir -p $(JSTHEMIS_OBJ) && cd $(JSTHEMIS_OBJ) && npm install nan && cd -

FMT_FIXUP += $(patsubst $(SRC_PATH)/%,$(OBJ_PATH)/%.fmt_fixup,$(JSTHEMIS_SOURCES) $(JSTHEMIS_HEADERS))
FMT_CHECK += $(patsubst $(SRC_PATH)/%,$(OBJ_PATH)/%.fmt_check,$(JSTHEMIS_SOURCES) $(JSTHEMIS_HEADERS))

JSTHEMIS_CFLAGS += $(CFLAGS)
JSTHEMIS_CFLAGS += -I$(JSTHEMIS_OBJ)/node_modules/nan

ifdef IS_LINUX
JSTHEMIS_CFLAGS += -I/usr/include/nodejs/src
JSTHEMIS_CFLAGS += -I/usr/include/nodejs/deps/v8/include
endif

$(JSTHEMIS_OBJ)/%.hpp.fmt_fixup $(JSTHEMIS_OBJ)/%.cpp.fmt_fixup: \
CMD = $(CLANG_TIDY) -fix $< -- $(JSTHEMIS_CFLAGS) 2>/dev/null && $(CLANG_FORMAT) -i $< && touch $@

$(JSTHEMIS_OBJ)/%.hpp.fmt_check $(JSTHEMIS_OBJ)/%.cpp.fmt_check: \
CMD = $(CLANG_FORMAT) $< | diff -u $< - && $(CLANG_TIDY) $< -- $(JSTHEMIS_CFLAGS) 2>/dev/null && touch $@

else # ifdef NPM_VERSION

FMT_FIXUP += $(JSTHEMIS_OBJ)/warning_fixup
FMT_CHECK += $(JSTHEMIS_OBJ)/warning_check

$(JSTHEMIS_OBJ)/warning_fixup:
$(warning NodeJS not installed, JsThemis code will not be formatted)

$(JSTHEMIS_OBJ)/warning_check:
$(warning NodeJS not installed, JsThemis code will not be checked)

endif # ifdef NPM_VERSION

0 comments on commit 4d11421

Please sign in to comment.