Some minor changes

This commit is contained in:
WolverinDEV 2020-12-02 21:00:51 +01:00
parent 58dce86e0f
commit 8e7c207720
10 changed files with 217 additions and 135 deletions

View File

@ -3,7 +3,7 @@ import {
AbstractServerConnection, AbstractServerConnection,
CommandOptionDefaults, CommandOptionDefaults,
CommandOptions, CommandOptions,
ConnectionStateListener, ConnectionStatistics, ConnectionStatistics,
ServerCommand ServerCommand
} from "tc-shared/connection/ConnectionBase"; } from "tc-shared/connection/ConnectionBase";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration"; import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
@ -353,9 +353,11 @@ export class ServerConnection extends AbstractServerConnection {
} }
getControlStatistics(): ConnectionStatistics { getControlStatistics(): ConnectionStatistics {
const stats = this.nativeHandle?.statistics();
return { return {
bytesReceived: 0, bytesReceived: stats?.control_bytes_received ? stats?.control_bytes_received : 0,
bytesSend: 0 bytesSend: stats?.control_bytes_send ? stats?.control_bytes_send : 0
}; };
} }

View File

@ -4,7 +4,7 @@ import {
WhisperSessionInitializer WhisperSessionInitializer
} from "tc-shared/connection/VoiceConnection"; } from "tc-shared/connection/VoiceConnection";
import {RecorderProfile} from "tc-shared/voice/RecorderProfile"; import {RecorderProfile} from "tc-shared/voice/RecorderProfile";
import {NativeVoiceClient, NativeVoiceConnection, PlayerState} from "tc-native/connection"; import {NativeServerConnection, NativeVoiceClient, NativeVoiceConnection, PlayerState} from "tc-native/connection";
import {ServerConnection} from "./ServerConnection"; import {ServerConnection} from "./ServerConnection";
import {VoiceClient} from "tc-shared/voice/VoiceClient"; import {VoiceClient} from "tc-shared/voice/VoiceClient";
import {WhisperSession, WhisperTarget} from "tc-shared/voice/VoiceWhisper"; import {WhisperSession, WhisperTarget} from "tc-shared/voice/VoiceWhisper";
@ -231,9 +231,12 @@ export class NativeVoiceConnectionWrapper extends AbstractVoiceConnection {
} }
getConnectionStats(): Promise<ConnectionStatistics> { getConnectionStats(): Promise<ConnectionStatistics> {
/* FIXME: This is iffy! */
const stats = (this.connection as any as NativeServerConnection)["nativeHandle"]?.statistics();
return Promise.resolve({ return Promise.resolve({
bytesSend: 0, bytesSend: stats?.voice_bytes_send ? stats?.voice_bytes_send : 0,
bytesReceived: 0 bytesReceived: stats?.voice_bytes_received ? stats?.voice_bytes_received : 0
}); });
} }

View File

@ -22,7 +22,7 @@ message("Module path: ${CMAKE_MODULE_PATH}")
function(setup_nodejs) function(setup_nodejs)
set(NodeJS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") set(NodeJS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
set(NODEJS_URL "https://atom.io/download/atom-shell") set(NODEJS_URL "https://atom.io/download/atom-shell")
set(NODEJS_VERSION "v11.0.3") set(NODEJS_VERSION "v8.0.0")
#set(NODEJS_URL "https://nodejs.org/download/release/") #set(NODEJS_URL "https://nodejs.org/download/release/")
#set(NODEJS_VERSION "v12.13.0") #set(NODEJS_VERSION "v12.13.0")

View File

@ -78,6 +78,8 @@ declare module "tc-native/connection" {
/* ping in microseconds */ /* ping in microseconds */
current_ping() : number; current_ping() : number;
statistics() : { voice_bytes_received: number, voice_bytes_send: number, control_bytes_received: number, control_bytes_send } | undefined
} }
export function spawn_server_connection() : NativeServerConnection; export function spawn_server_connection() : NativeServerConnection;

View File

@ -67,6 +67,11 @@ void ProtocolHandler::reset() {
this->crypt_handler.reset(); this->crypt_handler.reset();
this->ping.ping_received_timestamp = system_clock::time_point{}; this->ping.ping_received_timestamp = system_clock::time_point{};
this->statistics_.control_bytes_received = 0;
this->statistics_.control_bytes_send = 0;
this->statistics_.voice_bytes_send = 0;
this->statistics_.voice_bytes_received = 0;
} }
void ProtocolHandler::connect() { void ProtocolHandler::connect() {
@ -125,8 +130,9 @@ void ProtocolHandler::execute_tick() {
} }
void ProtocolHandler::execute_resend() { void ProtocolHandler::execute_resend() {
if(this->connection_state >= connection_state::DISCONNECTED) if(this->connection_state >= connection_state::DISCONNECTED) {
return; return;
}
std::deque<std::shared_ptr<ts::connection::AcknowledgeManager::Entry>> buffers; std::deque<std::shared_ptr<ts::connection::AcknowledgeManager::Entry>> buffers;
auto now = system_clock::now(); auto now = system_clock::now();
@ -145,8 +151,12 @@ void ProtocolHandler::execute_resend() {
auto socket = this->handle->get_socket(); auto socket = this->handle->get_socket();
if(socket) { if(socket) {
for(const auto& buffer : buffers) for(const auto& buffer : buffers) {
socket->send_message(buffer->buffer); socket->send_message(buffer->buffer);
/* only control packets are getting resend */
this->statistics_.control_bytes_send += buffer->buffer.length();
}
} }
this->handle->schedule_resend(next); this->handle->schedule_resend(next);
@ -169,11 +179,17 @@ void ProtocolHandler::progress_packet(const pipes::buffer_view &buffer) {
auto ordered = packet_type.type() == protocol::COMMAND || packet_type.type() == protocol::COMMAND_LOW; auto ordered = packet_type.type() == protocol::COMMAND || packet_type.type() == protocol::COMMAND_LOW;
//log_trace(category::connection, tr("Received packet {} with id {}"), packet->type().name(), packet->packetId()); //log_trace(category::connection, tr("Received packet {} with id {}"), packet->type().name(), packet->packetId());
/* switch(packet_type.type()) {
if(ordered) case ts::protocol::PacketType::VOICE:
if(rand() & 1) case ts::protocol::PacketType::VOICE_WHISPER:
return; this->statistics_.voice_bytes_received += buffer.length();
*/ break;
case ts::protocol::PacketType::COMMAND:
case ts::protocol::PacketType::COMMAND_LOW:
this->statistics_.control_bytes_received += buffer.length();
break;
}
/* special handling */ /* special handling */
if(packet_type.type() == protocol::INIT1) { if(packet_type.type() == protocol::INIT1) {
@ -565,6 +581,7 @@ void ProtocolHandler::send_command(const ts::Command &cmd, const std::function<v
//log_trace(category::connection, tr("Time needed for command: {}ms. Success: {}"), chrono::duration_cast<chrono::milliseconds>(end - begin).count(), f); //log_trace(category::connection, tr("Time needed for command: {}ms. Success: {}"), chrono::duration_cast<chrono::milliseconds>(end - begin).count(), f);
}); });
} }
packet->enable_flag(PacketFlag::NewProtocol); packet->enable_flag(PacketFlag::NewProtocol);
this->send_packet(packet); this->send_packet(packet);
} }
@ -594,8 +611,24 @@ void ProtocolHandler::send_packet(const std::shared_ptr<ts::protocol::ClientPack
return; return;
} }
for(const auto& buffer : result) size_t total_size{0};
for(const auto& buffer : result) {
socket->send_message(buffer); socket->send_message(buffer);
total_size += buffer.length();
}
switch(packet->type().type()) {
case ts::protocol::PacketType::VOICE:
case ts::protocol::PacketType::VOICE_WHISPER:
this->statistics_.voice_bytes_send += total_size;
break;
case ts::protocol::PacketType::COMMAND:
case ts::protocol::PacketType::COMMAND_LOW:
this->statistics_.control_bytes_send += total_size;
break;
}
} }
void ProtocolHandler::send_acknowledge(uint16_t packet_id, bool low) { void ProtocolHandler::send_acknowledge(uint16_t packet_id, bool low) {
@ -632,4 +665,8 @@ void ProtocolHandler::disconnect(const std::string &reason) {
if(success && this->connection_state == connection_state::DISCONNECTING && this->disconnect_id == did) if(success && this->connection_state == connection_state::DISCONNECTING && this->disconnect_id == did)
this->handle->close_connection(); this->handle->close_connection();
}); });
}
const ConnectionStatistics& ProtocolHandler::statistics() {
return this->statistics_;
} }

View File

@ -16,139 +16,147 @@
#include <protocol/generation.h> #include <protocol/generation.h>
#include "ServerConnection.h" #include "ServerConnection.h"
namespace ts { namespace ts::connection {
namespace connection { class CryptionHandler;
class CryptionHandler; class CompressionHandler;
class CompressionHandler;
}
} }
namespace tc { namespace tc::connection {
namespace connection { class ServerConnection;
class ServerConnection;
namespace connection_state { namespace connection_state {
enum value { enum value {
INITIALIZING, INITIALIZING,
INIT_LOW, INIT_LOW,
INIT_HIGH, INIT_HIGH,
CONNECTING, CONNECTING,
CONNECTED, CONNECTED,
DISCONNECTING, DISCONNECTING,
DISCONNECTED DISCONNECTED
}; };
}; };
namespace pow_state { namespace pow_state {
enum value : uint8_t { enum value : uint8_t {
COOKIE_GET, COOKIE_GET,
COOKIE_SET, COOKIE_SET,
PUZZLE_GET, PUZZLE_GET,
PUZZLE_SET, PUZZLE_SET,
PUZZLE_SOLVE, PUZZLE_SOLVE,
PUZZLE_RESET, PUZZLE_RESET,
COMPLETED, COMPLETED,
COMMAND_RESET = 127, COMMAND_RESET = 127,
UNSET = 0xFB UNSET = 0xFB
}; };
}; };
class ProtocolHandler { struct ConnectionStatistics {
typedef ts::protocol::PacketRingBuffer<ts::protocol::ServerPacket, 88> packet_buffer_t; size_t control_bytes_send{0};
typedef std::array<packet_buffer_t, 8> packet_buffers_t; size_t control_bytes_received{0};
friend class ServerConnection;
public:
ProtocolHandler(ServerConnection*);
~ProtocolHandler();
void reset(); size_t voice_bytes_send{0};
void connect(); size_t voice_bytes_received{0};
void execute_tick(); };
void execute_resend();
void progress_packet(const pipes::buffer_view& /* buffer */); class ProtocolHandler {
bool handle_packets(); /* if true we have more left */ typedef ts::protocol::PacketRingBuffer<ts::protocol::ServerPacket, 88> packet_buffer_t;
void send_packet(const std::shared_ptr<ts::protocol::ClientPacket>& /* packet */); typedef std::array<packet_buffer_t, 8> packet_buffers_t;
void send_command(const ts::Command& /* command */, const std::function<void(bool)> & /* acknowledge callback */ = NULL); friend class ServerConnection;
public:
explicit ProtocolHandler(ServerConnection*);
~ProtocolHandler();
void disconnect(const std::string& /* message */); void reset();
void connect();
void execute_tick();
void execute_resend();
void send_acknowledge(uint16_t /* packet id */, bool /* low */); const ConnectionStatistics& statistics();
ecc_key& get_identity_key() { return this->crypto.identity; } void progress_packet(const pipes::buffer_view& /* buffer */);
bool handle_packets(); /* if true we have more left */
void send_packet(const std::shared_ptr<ts::protocol::ClientPacket>& /* packet */);
void send_command(const ts::Command& /* command */, const std::function<void(bool)> & /* acknowledge callback */ = NULL);
inline std::chrono::microseconds current_ping() { return this->ping.value; } void disconnect(const std::string& /* message */);
void send_acknowledge(uint16_t /* packet id */, bool /* low */);
connection_state::value connection_state = connection_state::INITIALIZING; ecc_key& get_identity_key() { return this->crypto.identity; }
server_type::value server_type = server_type::TEASPEAK;
private:
void do_close_connection(); /* only call from ServerConnection. Close all connections via ServerConnection! */
void handlePacketCommand(const std::shared_ptr<ts::protocol::ServerPacket>&); inline std::chrono::microseconds current_ping() { return this->ping.value; }
void handlePacketAck(const std::shared_ptr<ts::protocol::ServerPacket>&);
void handlePacketVoice(const std::shared_ptr<ts::protocol::ServerPacket>&);
void handlePacketPing(const std::shared_ptr<ts::protocol::ServerPacket>&);
void handlePacketInit(const std::shared_ptr<ts::protocol::ServerPacket>&);
bool create_datagram_packets(std::vector<pipes::buffer> &result, const std::shared_ptr<ts::protocol::ClientPacket> &packet); connection_state::value connection_state = connection_state::INITIALIZING;
server_type::value server_type = server_type::TEASPEAK;
private:
void do_close_connection(); /* only call from ServerConnection. Close all connections via ServerConnection! */
ServerConnection* handle; void handlePacketCommand(const std::shared_ptr<ts::protocol::ServerPacket>&);
void handlePacketAck(const std::shared_ptr<ts::protocol::ServerPacket>&);
void handlePacketVoice(const std::shared_ptr<ts::protocol::ServerPacket>&);
void handlePacketPing(const std::shared_ptr<ts::protocol::ServerPacket>&);
void handlePacketInit(const std::shared_ptr<ts::protocol::ServerPacket>&);
std::chrono::system_clock::time_point connect_timestamp; bool create_datagram_packets(std::vector<pipes::buffer> &result, const std::shared_ptr<ts::protocol::ClientPacket> &packet);
std::chrono::system_clock::time_point disconnect_timestamp;
uint8_t disconnect_id = 0;
struct { ServerConnection* handle;
size_t retry_count{0};
pow_state::value state;
uint64_t client_ts3_build_timestamp = 173265950 /* TS3 */; /* needs to be lower than 173265950 for old stuff, else new protocol */ std::chrono::system_clock::time_point connect_timestamp;
uint8_t client_control_data[4] = {0,0,0,0}; std::chrono::system_clock::time_point disconnect_timestamp;
uint8_t server_control_data[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; uint8_t disconnect_id = 0;
uint8_t server_data[100];
std::chrono::system_clock::time_point last_response; struct {
std::chrono::system_clock::time_point last_resend; size_t retry_count{0};
pipes::buffer last_buffer; pow_state::value state;
} pow;
void pow_send_cookie_get();
struct { uint64_t client_ts3_build_timestamp = 173265950 /* TS3 */; /* needs to be lower than 173265950 for old stuff, else new protocol */
uint8_t alpha[10]; uint8_t client_control_data[4] = {0,0,0,0};
uint8_t beta[54]; uint8_t server_control_data[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
uint8_t beta_length; /* 10 or 54 */ uint8_t server_data[100];
ecc_key identity{}; std::chrono::system_clock::time_point last_response;
std::chrono::system_clock::time_point last_resend;
pipes::buffer last_buffer;
} pow;
void pow_send_cookie_get();
std::string initiv_command; struct {
} crypto; uint8_t alpha[10];
std::string generate_client_initiv(); uint8_t beta[54];
uint8_t beta_length; /* 10 or 54 */
uint16_t client_id = 0; ecc_key identity{};
ts::protocol::PacketIdManager _packet_id_manager;
packet_buffers_t _packet_buffers;
std::array<bool, 9> _packet_buffer_overflow{false};
std::array<ts::protocol::generation_estimator, 9> incoming_generation_estimators{}; /* implementation is thread save */ std::string initiv_command;
uint8_t _packet_buffers_index = 0; } crypto;
std::string generate_client_initiv();
bool crypt_setupped{false}; uint16_t client_id = 0;
ts::connection::CryptHandler crypt_handler; ts::protocol::PacketIdManager _packet_id_manager;
ts::connection::CompressionHandler compression_handler; packet_buffers_t _packet_buffers;
ts::connection::AcknowledgeManager acknowledge_handler; std::array<bool, 9> _packet_buffer_overflow{false};
void handleCommandInitIVExpend(ts::Command&); std::array<ts::protocol::generation_estimator, 9> incoming_generation_estimators{}; /* implementation is thread save */
void handleCommandInitIVExpend2(ts::Command&); uint8_t _packet_buffers_index = 0;
void handleCommandInitServer(ts::Command&);
struct { bool crypt_setupped{false};
std::chrono::system_clock::time_point ping_send_timestamp; ts::connection::CryptHandler crypt_handler;
std::chrono::system_clock::time_point ping_received_timestamp; ts::connection::CompressionHandler compression_handler;
std::chrono::microseconds value; ts::connection::AcknowledgeManager acknowledge_handler;
uint16_t ping_id;
std::chrono::microseconds interval = std::chrono::microseconds(2500); ConnectionStatistics statistics_{};
} ping;
void ping_send_request(); struct {
}; std::chrono::system_clock::time_point ping_send_timestamp{};
} std::chrono::system_clock::time_point ping_received_timestamp{};
std::chrono::microseconds value{0};
uint16_t ping_id{0};
std::chrono::microseconds interval{2500};
} ping;
void handleCommandInitIVExpend(ts::Command&);
void handleCommandInitIVExpend2(ts::Command&);
void handleCommandInitServer(ts::Command&);
void ping_send_request();
};
} }

View File

@ -16,14 +16,16 @@ using namespace ts::protocol;
using namespace ts; using namespace ts;
inline void generate_random(uint8_t *destination, size_t length) { inline void generate_random(uint8_t *destination, size_t length) {
while(length-- > 0) while(length-- > 0) {
*(destination++) = (uint8_t) rand(); *(destination++) = (uint8_t) rand();
}
} }
inline void write_reversed(uint8_t* destination, uint8_t* source, size_t length) { inline void write_reversed(uint8_t* destination, uint8_t* source, size_t length) {
destination += length; destination += length;
while(length-- > 0) while(length-- > 0) {
*(--destination) = *(source++); *(--destination) = *(source++);
}
} }
inline bool solve_puzzle(mp_int& x, mp_int& n, mp_int& result, uint32_t level) { inline bool solve_puzzle(mp_int& x, mp_int& n, mp_int& result, uint32_t level) {
@ -63,8 +65,10 @@ void ProtocolHandler::handlePacketInit(const std::shared_ptr<ts::protocol::Serve
} }
log_trace(category::connection, tr("[POW] State {} | {}"), packet_state, data.length()); log_trace(category::connection, tr("[POW] State {} | {}"), packet_state, data.length());
if(packet_state != this->pow.state) if(packet_state != this->pow.state) {
return; //TODO handle error? return; //TODO handle error?
}
this->acknowledge_handler.reset(); /* we don't need an ack anymore for our init packet */ this->acknowledge_handler.reset(); /* we don't need an ack anymore for our init packet */
if(packet_state == pow_state::COOKIE_SET) { if(packet_state == pow_state::COOKIE_SET) {
@ -146,6 +150,7 @@ void ProtocolHandler::handlePacketInit(const std::shared_ptr<ts::protocol::Serve
this->pow.last_resend = system_clock::now(); this->pow.last_resend = system_clock::now();
this->send_packet(make_shared<ClientPacket>(PacketTypeInfo::Init1, PacketFlag::Unencrypted, this->pow.last_buffer)); this->send_packet(make_shared<ClientPacket>(PacketTypeInfo::Init1, PacketFlag::Unencrypted, this->pow.last_buffer));
} }
mp_clear_multi(&point_x, &point_n, &result, nullptr); mp_clear_multi(&point_x, &point_n, &result, nullptr);
this->connection_state = connection_state::INIT_HIGH; this->connection_state = connection_state::INIT_HIGH;
} }

View File

@ -65,6 +65,7 @@ NAN_MODULE_INIT(ServerConnection::Init) {
Nan::SetPrototypeMethod(klass, "send_voice_data", ServerConnection::_send_voice_data); Nan::SetPrototypeMethod(klass, "send_voice_data", ServerConnection::_send_voice_data);
Nan::SetPrototypeMethod(klass, "send_voice_data_raw", ServerConnection::_send_voice_data_raw); Nan::SetPrototypeMethod(klass, "send_voice_data_raw", ServerConnection::_send_voice_data_raw);
Nan::SetPrototypeMethod(klass, "current_ping", ServerConnection::_current_ping); Nan::SetPrototypeMethod(klass, "current_ping", ServerConnection::_current_ping);
Nan::SetPrototypeMethod(klass, "statistics", ServerConnection::statistics);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked()); constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
} }
@ -620,7 +621,7 @@ void ServerConnection::send_voice_data(const void *buffer, size_t buffer_length,
} }
void ServerConnection::close_connection() { void ServerConnection::close_connection() {
lock_guard lock(this->disconnect_lock); lock_guard lock{this->disconnect_lock};
if(this->socket && this_thread::get_id() == this->socket->io_thread().get_id()) { if(this->socket && this_thread::get_id() == this->socket->io_thread().get_id()) {
logger::debug(category::connection, tr("close_connection() called in IO thread. Closing connection within event loop!")); logger::debug(category::connection, tr("close_connection() called in IO thread. Closing connection within event loop!"));
if(!this->event_loop_execute_connection_close) { if(!this->event_loop_execute_connection_close) {
@ -643,11 +644,13 @@ void ServerConnection::close_connection() {
} }
void ServerConnection::execute_tick() { void ServerConnection::execute_tick() {
if(this->protocol_handler) if(this->protocol_handler) {
this->protocol_handler->execute_tick(); this->protocol_handler->execute_tick();
}
if(auto vc{this->voice_connection}; vc) if(auto vc{this->voice_connection}; vc) {
vc->execute_tick(); vc->execute_tick();
}
} }
void ServerConnection::_execute_callback_commands() { void ServerConnection::_execute_callback_commands() {
@ -759,9 +762,30 @@ void ServerConnection::_execute_callback_disconnect(const std::string &reason) {
NAN_METHOD(ServerConnection::_current_ping) { NAN_METHOD(ServerConnection::_current_ping) {
auto connection = ObjectWrap::Unwrap<ServerConnection>(info.Holder()); auto connection = ObjectWrap::Unwrap<ServerConnection>(info.Holder());
lock_guard lock{connection->disconnect_lock};
auto& phandler = connection->protocol_handler; auto& phandler = connection->protocol_handler;
if(phandler) if(phandler)
info.GetReturnValue().Set((uint32_t) chrono::floor<microseconds>(phandler->current_ping()).count()); info.GetReturnValue().Set((uint32_t) chrono::floor<microseconds>(phandler->current_ping()).count());
else else
info.GetReturnValue().Set(-1); info.GetReturnValue().Set(-1);
}
NAN_METHOD(ServerConnection::statistics) {
auto connection = ObjectWrap::Unwrap<ServerConnection>(info.Holder());
lock_guard lock{connection->disconnect_lock};
auto& phandler = connection->protocol_handler;
if(phandler) {
auto statistics = phandler->statistics();
auto result = Nan::New<v8::Object>();
Nan::Set(result, Nan::LocalString("voice_bytes_received"), Nan::New<v8::Number>(statistics.voice_bytes_received));
Nan::Set(result, Nan::LocalString("voice_bytes_send"), Nan::New<v8::Number>(statistics.voice_bytes_send));
Nan::Set(result, Nan::LocalString("control_bytes_received"), Nan::New<v8::Number>(statistics.control_bytes_received));
Nan::Set(result, Nan::LocalString("control_bytes_send"), Nan::New<v8::Number>(statistics.control_bytes_send));
info.GetReturnValue().Set(result);
} else {
info.GetReturnValue().Set(Nan::Undefined());
}
} }

View File

@ -86,6 +86,7 @@ namespace tc {
static NAN_METHOD(_send_voice_data_raw); static NAN_METHOD(_send_voice_data_raw);
static NAN_METHOD(_error_message); static NAN_METHOD(_error_message);
static NAN_METHOD(_current_ping); static NAN_METHOD(_current_ping);
static NAN_METHOD(statistics);
std::unique_ptr<Nan::Callback> callback_connect; std::unique_ptr<Nan::Callback> callback_connect;
std::unique_ptr<Nan::Callback> callback_disconnect; std::unique_ptr<Nan::Callback> callback_disconnect;

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "TeaClient", "name": "TeaClient",
"version": "1.4.13", "version": "1.5.0-2",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {