mirror of
https://github.com/EvernodeXRPL/hpcore.git
synced 2026-04-29 15:37:59 +00:00
Implemented user connection challenge handshake (#20)
Implemented user connection challenge handshake. Optimized user challenge message processing.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
project(HPCore)
|
||||
|
||||
add_definitions("-std=c++17")
|
||||
|
||||
@@ -10,6 +11,7 @@ add_executable(hpcore
|
||||
src/crypto.cpp
|
||||
src/proc.cpp
|
||||
src/usr/usr.cpp
|
||||
src/usr/user_session_handler.cpp
|
||||
src/util.cpp
|
||||
src/p2p/message.pb.cc
|
||||
src/sock/socket_client.cpp
|
||||
|
||||
56
README.md
56
README.md
@@ -5,11 +5,13 @@
|
||||
|
||||
A C++ version of hotpocket designed for production envrionments, original prototype here: https://github.com/codetsunami/hotpocket
|
||||
|
||||
[Hot Pocket Wiki](https://github.com/HotPocketDev/core/wiki/Hot-Pocket-Wiki)
|
||||
|
||||
## Libraries
|
||||
* Crypto - Libsodium https://github.com/jedisct1/libsodium
|
||||
* Websockets - Boost|Beast https://github.com/boostorg/beast
|
||||
* RapidJSON - http://rapidjson.org
|
||||
* Protocol - https://github.com/protocolbuffers/protobuf
|
||||
* P2P Protocol - https://github.com/protocolbuffers/protobuf
|
||||
|
||||
## Steps to setup Hot Pocket
|
||||
|
||||
@@ -44,52 +46,42 @@ Instructions are based on [this](https://github.com/protocolbuffers/protobuf/tre
|
||||
4. Run `make && make check`
|
||||
5. Run `sudo make install`
|
||||
|
||||
#### Compile Protocol buffers
|
||||
1. Run `protoc -I=./src/p2p --cpp_out=./src/p2p ./src/p2p/message.proto`
|
||||
Ex - For message protobuf
|
||||
`protoc -I=./src/p2p --cpp_out=./src/p2p ./src/p2p/message.proto`
|
||||
|
||||
##### Compiling Protocol buffers message definitions
|
||||
When you make a change to `message.proto` defnition file, you need to run this:
|
||||
|
||||
`protoc -I=./src/p2p --cpp_out=./src/p2p ./src/p2p/message.proto`
|
||||
|
||||
#### Run ldconfig
|
||||
1. Run `sudo ldconfig`
|
||||
`sudo ldconfig`
|
||||
|
||||
This will update your library cache and avoid potential issues when running your compiled C++ program which links to newly installed libraries.
|
||||
|
||||
#### Install CMAKE
|
||||
If you use apt, run `sudo apt install cmake`
|
||||
Or follow [this](https://cmake.org/install/)
|
||||
If you use apt, run `sudo apt install cmake` or follow [this](https://cmake.org/install/).
|
||||
|
||||
#### Build and run Hot Pocket
|
||||
1. navigate to hotpocket repo root.
|
||||
1. Navigate to hotpocket repo root.
|
||||
1. Run `cmake .` (You only have to do this once)
|
||||
1. Run `make`
|
||||
1. Run `./build/hpcore new ~/mycontract`. This will initialize a new contract directory `mycontract` in your home directory.
|
||||
1. Take a look at `~/mycontract/cfg/hp.cfg`. This is your new contract config file. You can modify it according to your contract hosting requirements.
|
||||
1. Optional: Run `./build/hpcore rekey ~/mycontract` to generate new public/private key pair.
|
||||
1. Run `./build/hpcore run ~/mycontract` to run your smart contract (to do).
|
||||
1. Run `make` (Hot Pocket binary will be created as `./build/hpcore`)
|
||||
1. Refer to [Running Hot Pocket](https://github.com/HotPocketDev/core/wiki/Running-Hot-Pocket) in the Wiki.
|
||||
|
||||
Refer to [Hot Pocket Wiki](https://github.com/HotPocketDev/core/wiki/Hot-Pocket-Wiki) for more info.
|
||||
|
||||
## Code structure
|
||||
Code is divided into subsystems via namespaces. Some subsystems mentioned here are yet to be introduced.
|
||||
Code is divided into subsystems via namespaces.
|
||||
|
||||
#### conf
|
||||
Handles contract configuration. Loads and holds the central configuration object. Used by most of the subsystems.
|
||||
**conf::** Handles contract configuration. Loads and holds the central configuration object. Used by most of the subsystems.
|
||||
|
||||
#### crypto
|
||||
Handles cryptographic activities. Wraps libsodium and offers convenience functions.
|
||||
**crypto::** Handles cryptographic activities. Wraps libsodium and offers convenience functions.
|
||||
|
||||
#### proc
|
||||
Handles contract process execution.
|
||||
**proc::** Handles contract process execution.
|
||||
|
||||
#### usr
|
||||
Handles user connections and processing of user I/O with the smart contract. Makes use of **crypto** and **sock**.
|
||||
**usr::** Handles user connections and processing of user I/O with the smart contract. Makes use of **crypto** and **sock**.
|
||||
|
||||
#### p2p
|
||||
Handles peer-to-peer connections and message exchange between nodes. Also handles smart contract node-party-line (npl) I/O. Makes use of **crypto** and **sock**.
|
||||
**p2p::** Handles peer-to-peer connections and message exchange between nodes. Also handles smart contract node-party-line (npl) I/O. Makes use of **crypto** and **sock**.
|
||||
|
||||
#### cons
|
||||
Handles consensus and proposal rounds. Makes use of **usr**, **ntn** and **proc**
|
||||
**cons::** Handles consensus and proposal rounds. Makes use of **usr**, **p2p** and **proc**
|
||||
|
||||
#### sock
|
||||
Handles generic web sockets functionality. Mainly acts as a wrapper for boost/beast.
|
||||
**sock::** Handles generic web sockets functionality. Mainly acts as a wrapper for boost/beast.
|
||||
|
||||
#### shared
|
||||
Contains shared data structures/helper functions used by multiple subsystems. Used by most of the subsystems.
|
||||
**util::** Contains shared data structures/helper functions used by multiple subsystems.
|
||||
2
examples/hpclient/.gitignore
vendored
Normal file
2
examples/hpclient/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/**
|
||||
.hp_client_keys
|
||||
104
examples/hpclient/client.js
Normal file
104
examples/hpclient/client.js
Normal file
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// HotPocket client example code adopted from:
|
||||
// https://github.com/codetsunami/hotpocket/blob/master/hp_client.js
|
||||
//
|
||||
|
||||
const fs = require('fs')
|
||||
const ws_api = require('ws');
|
||||
const sodium = require('libsodium-wrappers')
|
||||
const readline = require('readline')
|
||||
|
||||
// sodium has a trigger when it's ready, we will wait and execute from there
|
||||
sodium.ready.then(main).catch((e) => { console.log(e) })
|
||||
|
||||
|
||||
function main() {
|
||||
|
||||
var keys = sodium.crypto_sign_keypair()
|
||||
|
||||
|
||||
// check for client keys
|
||||
if (!fs.existsSync('.hp_client_keys')) {
|
||||
keys.privateKey = sodium.to_hex(keys.privateKey)
|
||||
keys.publicKey = sodium.to_hex(keys.publicKey)
|
||||
fs.writeFileSync('.hp_client_keys', JSON.stringify(keys))
|
||||
} else {
|
||||
keys = JSON.parse(fs.readFileSync('.hp_client_keys'))
|
||||
keys.privateKey = Uint8Array.from(Buffer.from(keys.privateKey, 'hex'))
|
||||
keys.publicKey = Uint8Array.from(Buffer.from(keys.publicKey, 'hex'))
|
||||
}
|
||||
|
||||
|
||||
var server = 'ws://localhost:8080'
|
||||
|
||||
if (process.argv.length == 3) server = 'ws://localhost:' + process.argv[2]
|
||||
|
||||
if (process.argv.length == 4) server = 'ws://' + process.argv[2] + ':' + process.argv[3]
|
||||
|
||||
var ws = new ws_api(server)
|
||||
|
||||
/* anatomy of a public challenge
|
||||
{
|
||||
hotpocket: 0.1,
|
||||
type: 'public_challenge',
|
||||
challenge: '<base64 string>'
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
// if the console ctrl + c's us we should close ws gracefully
|
||||
process.once('SIGINT', function (code) {
|
||||
console.log('SIGINT received...');
|
||||
ws.close()
|
||||
});
|
||||
|
||||
ws.on('message', (m) => {
|
||||
console.log("-----Received raw message-----")
|
||||
console.log(m)
|
||||
console.log("------------------------------")
|
||||
|
||||
try {
|
||||
m = JSON.parse(m)
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
if (m.type != 'public_challenge') return
|
||||
|
||||
console.log("Received challenge message")
|
||||
console.log(m)
|
||||
|
||||
console.log('My public key is: ' + Buffer.from(keys.publicKey).toString('base64'));
|
||||
|
||||
// sign the challenge and send back the response
|
||||
var sigbytes = sodium.crypto_sign_detached(m.challenge, keys.privateKey);
|
||||
var response = {
|
||||
type: 'challenge_response',
|
||||
challenge: m.challenge,
|
||||
sig: Buffer.from(sigbytes).toString('base64'),
|
||||
pubkey: Buffer.from(keys.publicKey).toString('base64')
|
||||
}
|
||||
|
||||
console.log('Sending challenge response...');
|
||||
ws.send(JSON.stringify(response))
|
||||
|
||||
// start listening for stdin
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
var input_pump = () => {
|
||||
rl.question('', (answer) => {
|
||||
ws.send(answer + "\n")
|
||||
input_pump()
|
||||
})
|
||||
}
|
||||
input_pump()
|
||||
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
console.log('Server disconnected.');
|
||||
});
|
||||
}
|
||||
60
examples/hpclient/package-lock.json
generated
Normal file
60
examples/hpclient/package-lock.json
generated
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"async-limiter": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
|
||||
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
|
||||
"integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q=="
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"libsodium": {
|
||||
"version": "0.7.5",
|
||||
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.5.tgz",
|
||||
"integrity": "sha512-0YVU2QJc5sDR5HHkGCaliYImS7pGeXi11fiOfm4DirBd96PJVZIn3LJa06ZOFjLNsWkL3UbNjYhLRUOABPL9vw=="
|
||||
},
|
||||
"libsodium-wrappers": {
|
||||
"version": "0.7.5",
|
||||
"resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.5.tgz",
|
||||
"integrity": "sha512-QE9Q+FxLLGdJRiJTuC2GB3LEHZeHX/VcbMQeNPdAixEKo86JPy6bOWND1XmMLu0tjWUu0xIY0YpJYQApxIZwbQ==",
|
||||
"requires": {
|
||||
"libsodium": "0.7.5"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz",
|
||||
"integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==",
|
||||
"requires": {
|
||||
"async-limiter": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
examples/hpclient/package.json
Normal file
7
examples/hpclient/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"fs-extra": "^8.1.0",
|
||||
"libsodium-wrappers": "^0.7.5",
|
||||
"ws": "^7.1.2"
|
||||
}
|
||||
}
|
||||
@@ -154,11 +154,11 @@ int load_config()
|
||||
}
|
||||
|
||||
// Check whether this contract complies with the min version requirement.
|
||||
int verresult = util::version_compare(cfgversion, string(util::min_contract_version));
|
||||
int verresult = util::version_compare(cfgversion, string(util::MIN_CONTRACT_VERSION));
|
||||
if (verresult == -1)
|
||||
{
|
||||
cerr << "Contract version too old. Minimum "
|
||||
<< util::min_contract_version << " required. "
|
||||
<< util::MIN_CONTRACT_VERSION << " required. "
|
||||
<< cfgversion << " found.\n";
|
||||
return -1;
|
||||
}
|
||||
@@ -210,7 +210,7 @@ int save_config()
|
||||
Document d;
|
||||
d.SetObject();
|
||||
Document::AllocatorType &allocator = d.GetAllocator();
|
||||
d.AddMember("version", StringRef(util::hp_version), allocator);
|
||||
d.AddMember("version", StringRef(util::HP_VERSION), allocator);
|
||||
d.AddMember("pubkeyb64", StringRef(cfg.pubkeyb64.data()), allocator);
|
||||
d.AddMember("seckeyb64", StringRef(cfg.seckeyb64.data()), allocator);
|
||||
d.AddMember("keytype", StringRef(cfg.keytype.data()), allocator);
|
||||
@@ -382,12 +382,13 @@ int is_schema_valid(Document &d)
|
||||
const char *cfg_schema =
|
||||
"{"
|
||||
"\"type\": \"object\","
|
||||
"\"required\": [ \"version\", \"pubkeyb64\", \"seckeyb64\", \"binary\", \"binargs\", \"listenip\""
|
||||
"\"required\": [ \"version\", \"pubkeyb64\", \"seckeyb64\", \"keytype\", \"binary\", \"binargs\", \"listenip\""
|
||||
", \"peers\", \"unl\", \"peerport\", \"roundtime\", \"pubport\", \"pubmaxsize\", \"pubmaxcpm\" ],"
|
||||
"\"properties\": {"
|
||||
"\"version\": { \"type\": \"string\" },"
|
||||
"\"pubkeyb64\": { \"type\": \"string\" },"
|
||||
"\"seckeyb64\": { \"type\": \"string\" },"
|
||||
"\"keytype\": { \"type\": \"string\" },"
|
||||
"\"binary\": { \"type\": \"string\" },"
|
||||
"\"binargs\": { \"type\": \"string\" },"
|
||||
"\"listenip\": { \"type\": \"string\" },"
|
||||
|
||||
@@ -60,8 +60,8 @@ string sign(const string &msg, const string &seckey)
|
||||
/**
|
||||
* Returns the base64 signature string for a message.
|
||||
*
|
||||
* @param msg Base64 message string to sign.
|
||||
* @param seckey Base64 secret key string.
|
||||
* @param msg Message bytes to sign.
|
||||
* @param seckeyb64 Base64 secret key string.
|
||||
* @return Base64 signature string.
|
||||
*/
|
||||
string sign_b64(const string &msg, const string &seckeyb64)
|
||||
@@ -97,8 +97,8 @@ int verify(const string &msg, const string &sig, const string &pubkey)
|
||||
* Verifies the given base64 signature for the message.
|
||||
*
|
||||
* @param msg Base64 message string.
|
||||
* @param sig Base64 signature string.
|
||||
* @param pubkey Base64 secret key.
|
||||
* @param sigb64 Base64 signature string.
|
||||
* @param pubkeyb64 Base64 secret key.
|
||||
* @return 0 for successful verification. -1 for failure.
|
||||
*/
|
||||
int verify_b64(const string &msg, const string &sigb64, const string &pubkeyb64)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include "util.hpp"
|
||||
#include "conf.hpp"
|
||||
#include "crypto.hpp"
|
||||
@@ -67,7 +68,7 @@ int main(int argc, char **argv)
|
||||
if (conf::ctx.command == "version")
|
||||
{
|
||||
// Print the version
|
||||
cout << util::hp_version << endl;
|
||||
cout << util::HP_VERSION << endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -101,6 +102,10 @@ int main(int argc, char **argv)
|
||||
|
||||
// This will start hosting the contract and start consensus rounds.
|
||||
// TODO
|
||||
|
||||
// Temp code to avoid exiting.
|
||||
string s;
|
||||
cin >> s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
src/proc.cpp
10
src/proc.cpp
@@ -19,7 +19,7 @@ namespace proc
|
||||
/**
|
||||
* Keeps the currently executing contract process id (if any)
|
||||
*/
|
||||
int contract_pid = 0;
|
||||
__pid_t contract_pid = 0;
|
||||
|
||||
/**
|
||||
* Executes the contract process and passes the specified arguments.
|
||||
@@ -34,7 +34,7 @@ int exec_contract(const ContractExecArgs &args)
|
||||
return -1;
|
||||
}
|
||||
|
||||
int pid = fork();
|
||||
__pid_t pid = fork();
|
||||
if (pid > 0)
|
||||
{
|
||||
// HotPocket process.
|
||||
@@ -84,12 +84,12 @@ int write_to_stdin(const ContractExecArgs &args)
|
||||
d.SetObject();
|
||||
Document::AllocatorType &allocator = d.GetAllocator();
|
||||
|
||||
d.AddMember("version", StringRef(util::hp_version), allocator);
|
||||
d.AddMember("version", StringRef(util::HP_VERSION), allocator);
|
||||
d.AddMember("pubkey", StringRef(conf::cfg.pubkeyb64.data()), allocator);
|
||||
d.AddMember("ts", args.timestamp, allocator);
|
||||
|
||||
Value users(kObjectType);
|
||||
for (auto &[pk, user] : args.users)
|
||||
for (auto &[sid, user] : args.users)
|
||||
{
|
||||
Value fdlist(kArrayType);
|
||||
fdlist.PushBack(user.inpipe[0], allocator);
|
||||
@@ -99,7 +99,7 @@ int write_to_stdin(const ContractExecArgs &args)
|
||||
d.AddMember("usrfd", users, allocator);
|
||||
|
||||
Value peers(kObjectType);
|
||||
for (auto &[pk, peer] : args.peers)
|
||||
for (auto &[sid, peer] : args.peers)
|
||||
{
|
||||
Value fdlist(kArrayType);
|
||||
fdlist.PushBack(peer.inpipe[0], allocator);
|
||||
|
||||
@@ -36,6 +36,10 @@ void socket_session::client_run(const std::uint16_t port, const std::string &add
|
||||
port_ = port;
|
||||
address_ = address;
|
||||
|
||||
// Create a unique id for the session combining ip and port.
|
||||
uniqueid_ = address + ":";
|
||||
uniqueid_.append(std::to_string(port));
|
||||
|
||||
if (ec)
|
||||
return fail(ec, "handshake");
|
||||
|
||||
@@ -51,12 +55,12 @@ void socket_session::client_run(const std::uint16_t port, const std::string &add
|
||||
|
||||
void socket_session::fail(error_code ec, char const *what)
|
||||
{
|
||||
// std::cerr << what << ": " << ec.message() << std::endl;
|
||||
|
||||
// Don't report these
|
||||
if (ec == net::error::operation_aborted ||
|
||||
ec == websocket::error::closed)
|
||||
return;
|
||||
|
||||
// std::cerr << what << ": " << ec.message() << "\n";
|
||||
}
|
||||
|
||||
void socket_session::on_accept(error_code ec)
|
||||
@@ -78,10 +82,15 @@ void socket_session::on_accept(error_code ec)
|
||||
|
||||
void socket_session::on_read(error_code ec, std::size_t)
|
||||
{
|
||||
// read may get called when operation_aborted as well.
|
||||
// We don't need to process read operation in that case.
|
||||
if (ec == net::error::operation_aborted)
|
||||
return;
|
||||
|
||||
// Handle the error, if any
|
||||
if (ec)
|
||||
{
|
||||
//if something goes wrong when trying to read, socket connection will be closed and calling this to inform it to the handler
|
||||
// if something goes wrong when trying to read, socket connection will be closed and calling this to inform it to the handler
|
||||
on_close(ec, 1);
|
||||
return fail(ec, "read");
|
||||
}
|
||||
@@ -159,4 +168,14 @@ void socket_session::on_close(error_code ec, std::int8_t type)
|
||||
if (ec)
|
||||
return fail(ec, "close");
|
||||
}
|
||||
|
||||
// When called, initializes the unique id string for this session.
|
||||
void socket_session::init_uniqueid()
|
||||
{
|
||||
// Create a unique id for the session combining ip and port.
|
||||
// We prepare this appended string here because we need to use it for finding elemends from the maps
|
||||
// for validation purposes whenever a message is received.
|
||||
uniqueid_.append(address_).append(":").append(std::to_string(port_));
|
||||
}
|
||||
|
||||
} // namespace sock
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <bitset>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/beast.hpp>
|
||||
#include "socket_session_handler.hpp"
|
||||
@@ -44,15 +45,29 @@ class socket_session : public std::enable_shared_from_this<socket_session>
|
||||
public:
|
||||
socket_session(websocket::stream<beast::tcp_stream> &websocket, socket_session_handler &sess_handler);
|
||||
|
||||
// The port of the remote party.
|
||||
std::uint16_t port_;
|
||||
|
||||
// The IP address of the remote party.
|
||||
std::string address_;
|
||||
|
||||
// The unique identifier of the remote party (format <ip>:<port>).
|
||||
std::string uniqueid_;
|
||||
|
||||
// The set of util::SESSION_FLAG enum flags that will be set by user-code of this calss.
|
||||
// We mainly use this to store contexual information about this session based on the use case.
|
||||
// Setting and reading flags to this is completely managed by user-code.
|
||||
std::bitset<8> flags_;
|
||||
|
||||
void server_run(const std::uint16_t port, const std::string &address);
|
||||
void client_run(const std::uint16_t port, const std::string &address, error ec);
|
||||
|
||||
//Used to send message through an active websocket connection
|
||||
// Used to send message through an active websocket connection.
|
||||
void send(std::shared_ptr<std::string const> const &ss);
|
||||
|
||||
// When called, initializes the unique id string for this session.
|
||||
void init_uniqueid();
|
||||
|
||||
void close();
|
||||
};
|
||||
} // namespace sock
|
||||
|
||||
127
src/usr/user_session_handler.cpp
Normal file
127
src/usr/user_session_handler.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include "../util.hpp"
|
||||
#include "../sock/socket_session.hpp"
|
||||
#include "usr.hpp"
|
||||
#include "user_session_handler.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace beast = boost::beast;
|
||||
|
||||
using tcp = net::ip::tcp;
|
||||
using error = boost::system::error_code;
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace usr
|
||||
{
|
||||
|
||||
/**
|
||||
* This gets hit every time a client connects to HP via the public port (configured in contract config).
|
||||
*/
|
||||
void user_session_handler::on_connect(sock::socket_session *session)
|
||||
{
|
||||
cout << "User client connected " << session->address_ << ":" << session->port_ << endl;
|
||||
|
||||
// As a soon as a user conntects, we issue them a challenge message. We remember the
|
||||
// challenge we issued and later verifies the user's response with it.
|
||||
|
||||
string msg;
|
||||
string challengeb64;
|
||||
usr::create_user_challenge(msg, challengeb64);
|
||||
|
||||
// We init the session unique id to associate with the challenge.
|
||||
session->init_uniqueid();
|
||||
|
||||
// Create an entry in pending_challenges for later tracking upon challenge response.
|
||||
usr::pending_challenges[session->uniqueid_] = challengeb64;
|
||||
|
||||
// TODO: This needs to be reviewed to optimise passing the message.
|
||||
session->send(make_shared<string>(msg));
|
||||
|
||||
// Set the challenge-issued flag to help later checks in on_message.
|
||||
session->flags_.set(util::SESSION_FLAG::USER_CHALLENGE_ISSUED);
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets hit every time we receive some data from a client connected to the HP public port.
|
||||
*/
|
||||
void user_session_handler::on_message(sock::socket_session *session, const std::string &message)
|
||||
{
|
||||
// First check whether this session is pending challenge.
|
||||
// Meaning we have previously issued a challenge to the client,
|
||||
if (session->flags_[util::SESSION_FLAG::USER_CHALLENGE_ISSUED])
|
||||
{
|
||||
// The received message must be the challenge response. We need to verify it.
|
||||
auto itr = usr::pending_challenges.find(session->uniqueid_);
|
||||
if (itr != usr::pending_challenges.end())
|
||||
{
|
||||
string userpubkey;
|
||||
const string &original_challenge = itr->second;
|
||||
if (usr::verify_user_challenge_response(message, original_challenge, userpubkey) == 0)
|
||||
{
|
||||
// Challenge verification successful.
|
||||
// Promote the connection from pending-challenges to authenticated users.
|
||||
|
||||
session->flags_.reset(util::SESSION_FLAG::USER_CHALLENGE_ISSUED); // Clear challenge-issued flag
|
||||
session->flags_.set(util::SESSION_FLAG::USER_AUTHED); // Set the user-authed flag
|
||||
usr::pending_challenges.erase(session->uniqueid_); // Remove the stored challenge
|
||||
usr::add_user(session->uniqueid_, userpubkey); // Add the user to the global authed user list
|
||||
|
||||
cout << "User connection " << session->uniqueid_ << " authenticated.\n";
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "Challenge verification failed " << session->uniqueid_ << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check whether this session belongs to an authenticated (challenge-verified) user.
|
||||
else if (session->flags_[util::SESSION_FLAG::USER_AUTHED])
|
||||
{
|
||||
// Check whether this user is among authenticated users
|
||||
// and perform authenticated msg processing.
|
||||
|
||||
auto itr = usr::users.find(session->uniqueid_);
|
||||
if (itr != usr::users.end())
|
||||
{
|
||||
// This is an authed user.
|
||||
// Write the message to the user input pipe. SC will read from this pipe when it executes.
|
||||
const contract_user &user = itr->second;
|
||||
write(user.inpipe[1], message.data(), message.length());
|
||||
cout << "User " << user.pubkeyb64 << " wrote " << message.length() << " bytes to contract input.\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If for any reason we reach this point, we should drop the connection.
|
||||
session->close();
|
||||
cout << "Dropped the user connection " << session->address_ << ":" << session->port_ << endl;
|
||||
}
|
||||
|
||||
/**
|
||||
* This gets hit every time a client disconnects from the HP public port.
|
||||
*/
|
||||
void user_session_handler::on_close(sock::socket_session *session)
|
||||
{
|
||||
// Cleanup any resources related to this session.
|
||||
|
||||
// Session is awaiting challenge response.
|
||||
if (session->flags_[util::SESSION_FLAG::USER_CHALLENGE_ISSUED])
|
||||
{
|
||||
usr::pending_challenges.erase(session->uniqueid_);
|
||||
}
|
||||
// Session belongs to an authed user.
|
||||
else if (session->flags_[util::SESSION_FLAG::USER_AUTHED])
|
||||
{
|
||||
usr::remove_user(session->uniqueid_);
|
||||
}
|
||||
|
||||
cout << "User disconnected " << session->uniqueid_ << endl;
|
||||
}
|
||||
|
||||
} // namespace usr
|
||||
18
src/usr/user_session_handler.hpp
Normal file
18
src/usr/user_session_handler.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include <boost/beast/core.hpp>
|
||||
#include "../sock/socket_session_handler.hpp"
|
||||
#include "../sock/socket_session.hpp"
|
||||
|
||||
using error = boost::system::error_code;
|
||||
|
||||
namespace usr
|
||||
{
|
||||
|
||||
class user_session_handler : public sock::socket_session_handler
|
||||
{
|
||||
public:
|
||||
void on_connect(sock::socket_session *session);
|
||||
void on_message(sock::socket_session *session, const std::string &message);
|
||||
void on_close(sock::socket_session *session);
|
||||
};
|
||||
|
||||
}
|
||||
177
src/usr/usr.cpp
177
src/usr/usr.cpp
@@ -4,14 +4,17 @@
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/schema.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
#include <rapidjson/writer.h>
|
||||
#include <sodium.h>
|
||||
#include <boost/thread/thread.hpp>
|
||||
#include "../sock/socket_server.hpp"
|
||||
#include "../sock/socket_session_handler.hpp"
|
||||
#include "../util.hpp"
|
||||
#include "../conf.hpp"
|
||||
#include "../crypto.hpp"
|
||||
#include "usr.hpp"
|
||||
#include "user_session_handler.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace util;
|
||||
@@ -22,13 +25,44 @@ namespace usr
|
||||
|
||||
/**
|
||||
* Global user list. (Exposed to other sub systems)
|
||||
* Map key: User socket session id (<ip:port>)
|
||||
*/
|
||||
map<string, contract_user> users;
|
||||
|
||||
/**
|
||||
* Json schema doc used for user challenge-response json validation.
|
||||
* Keep track of verification-pending challenges issued to newly connected users.
|
||||
* Map key: User socket session id (<ip:port>)
|
||||
*/
|
||||
Document challenge_response_schemadoc;
|
||||
map<string, string> pending_challenges;
|
||||
|
||||
/**
|
||||
* User session handler instance. This instance's methods will be fired for any user socket activity.
|
||||
*/
|
||||
user_session_handler global_usr_session_handler;
|
||||
|
||||
/**
|
||||
* The IO context used by the websocket listener. (not exposed out of this namespace)
|
||||
*/
|
||||
net::io_context ioc;
|
||||
|
||||
/**
|
||||
* The thread the websocket lsitener is running on. (not exposed out of this namespace)
|
||||
*/
|
||||
thread listener_thread;
|
||||
|
||||
// Challenge response fields.
|
||||
// These fields are used on challenge response validation.
|
||||
static const char* CHALLENGE_RESP_TYPE = "type";
|
||||
static const char* CHALLENGE_RESP_CHALLENGE = "challenge";
|
||||
static const char* CHALLENGE_RESP_SIG = "sig";
|
||||
static const char* CHALLENGE_RESP_PUBKEY = "pubkey";
|
||||
|
||||
// Message type for the user challenge.
|
||||
static const char *CHALLENGE_MSGTYPE = "public_challenge";
|
||||
// Message type for the user challenge response.
|
||||
static const char *CHALLENGE_RESP_MSGTYPE = "challenge_response";
|
||||
// Length of user random challenge bytes.
|
||||
static const int CHALLENGE_LEN = 16;
|
||||
|
||||
/**
|
||||
* Initializes the usr subsystem. Must be called once during application startup.
|
||||
@@ -36,21 +70,8 @@ Document challenge_response_schemadoc;
|
||||
*/
|
||||
int init()
|
||||
{
|
||||
//We initialize the response schema doc from this json string so we can
|
||||
//use the schema repeatedly for all challenge-response validations.
|
||||
|
||||
const char *challenge_response_schema =
|
||||
"{"
|
||||
"\"type\": \"object\","
|
||||
"\"required\": [ \"type\", \"challenge\", \"sig\", \"pubkey\" ],"
|
||||
"\"properties\": {"
|
||||
"\"type\": { \"type\": \"string\" },"
|
||||
"\"challenge\": { \"type\": \"string\" },"
|
||||
"\"sig\": { \"type\": \"string\" },"
|
||||
"\"pubkey\": { \"type\": \"string\" }"
|
||||
"}"
|
||||
"}";
|
||||
challenge_response_schemadoc.Parse(challenge_response_schema);
|
||||
// Start listening for incoming user connections.
|
||||
start_listening();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -72,31 +93,31 @@ int init()
|
||||
void create_user_challenge(string &msg, string &challengeb64)
|
||||
{
|
||||
//Use libsodium to generate the random challenge bytes.
|
||||
unsigned char challenge_bytes[user_challenge_len];
|
||||
randombytes_buf(challenge_bytes, user_challenge_len);
|
||||
unsigned char challenge_bytes[CHALLENGE_LEN];
|
||||
randombytes_buf(challenge_bytes, CHALLENGE_LEN);
|
||||
|
||||
//We pass the b64 challenge string separately to the caller even though
|
||||
//we also include it in the challenge msg as well.
|
||||
|
||||
base64_encode(challenge_bytes, user_challenge_len, challengeb64);
|
||||
base64_encode(challenge_bytes, CHALLENGE_LEN, challengeb64);
|
||||
|
||||
//Construct the challenge msg json.
|
||||
Document d;
|
||||
d.SetObject();
|
||||
Document::AllocatorType &allocator = d.GetAllocator();
|
||||
d.AddMember("version", StringRef(util::hp_version), allocator);
|
||||
d.AddMember("type", StringRef(msg_public_challenge), allocator);
|
||||
d.AddMember("challenge", StringRef(challengeb64.data()), allocator);
|
||||
// We do not use RapidJson here in favour of performance because this is a simple json message.
|
||||
|
||||
StringBuffer buffer;
|
||||
Writer<StringBuffer> writer(buffer);
|
||||
d.Accept(writer);
|
||||
msg = buffer.GetString();
|
||||
// Since we know the rough size of the challenge massage we reserve adequate amount for the holder.
|
||||
// Only Hot Pocket version number is variable length. Therefore message size is roughly 95 bytes
|
||||
// so allocating 128bits for heap padding.
|
||||
msg.reserve(128);
|
||||
msg.append("{\"version\":\"")
|
||||
.append(util::HP_VERSION)
|
||||
.append("\",\"type\":\"public_challenge\",\"challenge\":\"")
|
||||
.append(challengeb64)
|
||||
.append("\"}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the user challenge response with the original challenge issued to the user
|
||||
* and the user public contained in the response.
|
||||
* and the user public key contained in the response.
|
||||
*
|
||||
* @param response The response bytes to verify. This will be parsed as json.
|
||||
* Accepted response format:
|
||||
@@ -107,42 +128,52 @@ void create_user_challenge(string &msg, string &challengeb64)
|
||||
* "pubkey": "<Base64 public key of the user>"
|
||||
* }
|
||||
* @param original_challenge The original base64 challenge string issued to the user.
|
||||
* @param extracted_pubkeyb64 The public key extracted from the response.
|
||||
* @return 0 if challenge response is verified. -1 if challenge not met or an error occurs.
|
||||
*/
|
||||
int verify_user_challenge_response(const string &response, const string &original_challenge, string &extracted_pubkeyb64)
|
||||
{
|
||||
//We load response raw bytes into json document and validate the schema.
|
||||
// We load response raw bytes into json document.
|
||||
Document d;
|
||||
d.Parse(response.data());
|
||||
|
||||
//Validate json scheme.
|
||||
//This has a cost. But we have to do this first. Otherwise field value
|
||||
//extraction will fail in subsequent steps if the message is malformed.
|
||||
SchemaDocument schema(challenge_response_schemadoc);
|
||||
SchemaValidator validator(schema);
|
||||
if (!d.Accept(validator))
|
||||
if (d.HasParseError())
|
||||
{
|
||||
cerr << "User challenge resposne schema invalid.\n";
|
||||
cerr << "Challenge response json parser error.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Validate msg type.
|
||||
if (d["type"] != msg_challenge_resp)
|
||||
// Validate msg type.
|
||||
if (!d.HasMember(CHALLENGE_RESP_TYPE) || d[CHALLENGE_RESP_TYPE] != CHALLENGE_RESP_MSGTYPE)
|
||||
{
|
||||
cerr << "User challenge response type invalid. 'challenge_response' expeced.\n";
|
||||
cerr << "User challenge response type invalid. 'challenge_response' expected.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Compare the response challenge string with the original issued challenge.
|
||||
if (d["challenge"] != original_challenge.data())
|
||||
// Compare the response challenge string with the original issued challenge.
|
||||
if (!d.HasMember(CHALLENGE_RESP_CHALLENGE) || d[CHALLENGE_RESP_CHALLENGE] != original_challenge.data())
|
||||
{
|
||||
cerr << "User challenge resposne: challenge mismatch.\n";
|
||||
cerr << "User challenge response challenge invalid.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Verify the challenge signature. We do this last due to signature verification cost.
|
||||
string sigb64 = d["sig"].GetString();
|
||||
extracted_pubkeyb64 = d["pubkey"].GetString();
|
||||
// Check for the 'sig' field existence.
|
||||
if (!d.HasMember(CHALLENGE_RESP_SIG) || !d[CHALLENGE_RESP_SIG].IsString())
|
||||
{
|
||||
cerr << "User challenge response signature invalid.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check for the 'pubkey' field existence.
|
||||
if (!d.HasMember(CHALLENGE_RESP_PUBKEY) || !d[CHALLENGE_RESP_PUBKEY].IsString())
|
||||
{
|
||||
cerr << "User challenge response public key invalid.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify the challenge signature. We do this last due to signature verification cost.
|
||||
string sigb64 = d[CHALLENGE_RESP_SIG].GetString();
|
||||
extracted_pubkeyb64 = d[CHALLENGE_RESP_PUBKEY].GetString();
|
||||
|
||||
if (crypto::verify_b64(original_challenge, sigb64, extracted_pubkeyb64) != 0)
|
||||
{
|
||||
cerr << "User challenge response signature verification failed.\n";
|
||||
@@ -153,16 +184,18 @@ int verify_user_challenge_response(const string &response, const string &origina
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified public key into the global user list.
|
||||
* Adds the user denoted by specified session id and public key to the global authed user list.
|
||||
* This should get called after the challenge handshake is verified.
|
||||
*
|
||||
* @param sessionid User socket session id.
|
||||
* @param pubkeyb64 User's base64 public key.
|
||||
* @return 0 on successful additions. -1 on failure.
|
||||
*/
|
||||
int add_user(const string &pubkeyb64)
|
||||
int add_user(const string &sessionid, const string &pubkeyb64)
|
||||
{
|
||||
if (users.count(pubkeyb64) == 1)
|
||||
if (users.count(sessionid) == 1)
|
||||
{
|
||||
cerr << pubkeyb64 << " already exist. Cannot add user.\n";
|
||||
cerr << sessionid << " already exist. Cannot add user.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -172,7 +205,7 @@ int add_user(const string &pubkeyb64)
|
||||
int inpipe[2];
|
||||
if (pipe(inpipe) != 0)
|
||||
{
|
||||
cerr << "User in pipe creation failed. pubkey:" << pubkeyb64 << endl;
|
||||
cerr << "User in pipe creation failed. sessionid:" << sessionid << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -180,7 +213,7 @@ int add_user(const string &pubkeyb64)
|
||||
int outpipe[2];
|
||||
if (pipe(outpipe) != 0)
|
||||
{
|
||||
cerr << "User out pipe creation failed. pubkey:" << pubkeyb64 << endl;
|
||||
cerr << "User out pipe creation failed. sessionid:" << sessionid << endl;
|
||||
|
||||
//We need to close 'inpipe' in case outpipe failed.
|
||||
close(inpipe[0]);
|
||||
@@ -189,7 +222,7 @@ int add_user(const string &pubkeyb64)
|
||||
return -1;
|
||||
}
|
||||
|
||||
users.insert(pair<string, contract_user>(pubkeyb64, contract_user(pubkeyb64, inpipe, outpipe)));
|
||||
users.emplace(sessionid, contract_user(pubkeyb64, inpipe, outpipe));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -199,16 +232,17 @@ int add_user(const string &pubkeyb64)
|
||||
*
|
||||
* @return 0 on successful removals. -1 on failure.
|
||||
*/
|
||||
int remove_user(const string &pubkeyb64)
|
||||
int remove_user(const string &sessionid)
|
||||
{
|
||||
if (users.count(pubkeyb64) == 0)
|
||||
auto itr = users.find(sessionid);
|
||||
|
||||
if (itr == users.end())
|
||||
{
|
||||
cerr << pubkeyb64 << " does not exist. Cannot remove user.\n";
|
||||
cerr << sessionid << " does not exist. Cannot remove user.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto itr = users.find(pubkeyb64);
|
||||
contract_user user = itr->second;
|
||||
const contract_user &user = itr->second;
|
||||
|
||||
//Close the User <--> SC I/O pipes.
|
||||
close(user.inpipe[0]);
|
||||
@@ -236,7 +270,7 @@ int read_contract_user_outputs()
|
||||
//Currently this is sequential for simplicity which will not scale well
|
||||
//when there are large number of users connected to the same HP node.
|
||||
|
||||
for (auto &[pk, user] : users)
|
||||
for (auto &[sid, user] : users)
|
||||
{
|
||||
int fdout = user.outpipe[0];
|
||||
int bytes_available = 0;
|
||||
@@ -257,4 +291,21 @@ int read_contract_user_outputs()
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts listening for incoming user websocket connections.
|
||||
*/
|
||||
void start_listening()
|
||||
{
|
||||
auto address = net::ip::make_address(conf::cfg.listenip);
|
||||
make_shared<sock::socket_server>(
|
||||
ioc,
|
||||
tcp::endpoint{address, conf::cfg.pubport},
|
||||
global_usr_session_handler)
|
||||
->run();
|
||||
|
||||
listener_thread = thread([&] { ioc.run(); });
|
||||
|
||||
cout << "Started listening for incoming user connections...\n";
|
||||
}
|
||||
|
||||
} // namespace usr
|
||||
@@ -15,32 +15,35 @@ using namespace util;
|
||||
namespace usr
|
||||
{
|
||||
|
||||
// Length of user random challenge bytes.
|
||||
static const int user_challenge_len = 16;
|
||||
|
||||
// Message type for the user challenge.
|
||||
static const char *msg_public_challenge = "public_challenge";
|
||||
|
||||
// Message type for the user challenge response.
|
||||
static const char *msg_challenge_resp = "challenge_response";
|
||||
|
||||
/**
|
||||
* Global authenticated (challenge-verified) user list.
|
||||
*/
|
||||
extern map<string, contract_user> users;
|
||||
|
||||
/**
|
||||
* Keep track of verification-pending challenges issued to newly connected users.
|
||||
*/
|
||||
extern map<string, string> pending_challenges;
|
||||
|
||||
/**
|
||||
* Keep track of verification-pending challenges issued to newly connected users.
|
||||
*/
|
||||
extern map<string, string> pending_challenges;
|
||||
|
||||
int init();
|
||||
|
||||
void create_user_challenge(string &msg, string &challengeb64);
|
||||
|
||||
int verify_user_challenge_response(const string &response, const string &original_challenge, string &extracted_pubkey);
|
||||
|
||||
int add_user(const string &pubkeyb64);
|
||||
int add_user(const string &sessionid, const string &pubkeyb64);
|
||||
|
||||
int remove_user(const string &pubkeyb64);
|
||||
int remove_user(const string &sessionid);
|
||||
|
||||
int read_contract_user_outputs();
|
||||
|
||||
void start_listening();
|
||||
|
||||
} // namespace usr
|
||||
|
||||
#endif
|
||||
@@ -30,7 +30,9 @@ int base64_encode(const unsigned char *bin, size_t bin_len, string &encoded_stri
|
||||
return -1;
|
||||
|
||||
// Assign the encoded char* onto the provided string reference.
|
||||
encoded_string = string(base64chars, base64_len);
|
||||
// "base64_len - 1" because sodium include '\0' in the calculated base64 length.
|
||||
// Therefore we need to omit it when initializing the std::string.
|
||||
encoded_string = string(base64chars, base64_len - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
17
src/util.hpp
17
src/util.hpp
@@ -13,14 +13,25 @@ namespace util
|
||||
{
|
||||
|
||||
// Hot Pocket version. Displayed on 'hotpocket version' and written to new contract configs.
|
||||
static const char *hp_version = "0.1";
|
||||
static const char *HP_VERSION = "0.1";
|
||||
|
||||
// Minimum compatible contract config version (this will be used to validate contract configs)
|
||||
static const char *min_contract_version = "0.1";
|
||||
static const char *MIN_CONTRACT_VERSION = "0.1";
|
||||
|
||||
// Minimum compatible peer message version (this will be used to accept/reject incoming peer connections)
|
||||
// (Keeping this as int for effcient msg payload and comparison)
|
||||
static const int min_peermsg_version = 1;
|
||||
static const int MIN_PEERMSG_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Set of flags used to mark status information on the session.
|
||||
* usr and p2p subsystems makes use of this to mark status information of user and peer sessions.
|
||||
* Set flags are stored in 'flags_' bitset.
|
||||
*/
|
||||
enum SESSION_FLAG
|
||||
{
|
||||
USER_CHALLENGE_ISSUED = 0,
|
||||
USER_AUTHED = 1
|
||||
};
|
||||
|
||||
/**
|
||||
* Holds information about an authenticated (challenge-verified) user
|
||||
|
||||
Reference in New Issue
Block a user