recreated a base for a python simplified version

This commit is contained in:
study-faraphel 2025-01-03 21:57:08 +01:00
parent dfb96a7853
commit c825ef8bef
94 changed files with 445 additions and 3074 deletions

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "external/argparse"]
path = external/argparse
url = https://github.com/p-ranav/argparse

View file

@ -1,90 +0,0 @@
cmake_minimum_required(VERSION 3.25)
project(M2-PT-DRP LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(M2-PT-DRP
source/main.cpp
source/packets/audio/AudioPacketData.hpp
source/utils/audio/audio.cpp
source/utils/audio/audio.hpp
source/managers/Manager.cpp
source/managers/Manager.hpp
source/packets/base/Packet.hpp
source/behavior/events/types.hpp
source/packets/base/Packet.cpp
source/RemotePeer.hpp
source/behavior/events/base/BaseEvent.hpp
source/behavior/events/base/BaseEvent.hpp
source/behavior/events/audio/AudioPacketsComparator.cpp
source/behavior/events/audio/AudioPacketsComparator.hpp
source/behavior/events/audio/AudioEvent.hpp
source/behavior/events/audio/AudioEvent.cpp
source/behavior/events/pong/PongEvent.cpp
source/behavior/events/pong/PongEvent.hpp
source/behavior/events/search/SearchEvent.cpp
source/behavior/events/search/SearchEvent.hpp
source/behavior/events/info/InfoEvent.cpp
source/behavior/events/info/InfoEvent.hpp
source/behavior/tasks/types.hpp
source/behavior/tasks/base/BaseTask.hpp
source/behavior/tasks/server/ServerTask.cpp
source/behavior/tasks/server/ServerTask.hpp
source/behavior/tasks/undefined/UndefinedTask.cpp
source/behavior/tasks/undefined/UndefinedTask.hpp
source/behavior/tasks/client/ClientTask.cpp
source/behavior/tasks/client/ClientTask.hpp
source/Context.hpp
source/packets/search/SearchPacketData.hpp
source/packets/base/PacketContent.cpp
source/packets/base/PacketContent.hpp
source/packets/base/SecurityMode.hpp
source/packets/info/InfoPacketData.hpp
source/utils/time/Chrony.cpp
source/utils/time/Chrony.hpp
source/Peer.hpp
source/utils/network/network.cpp
source/utils/network/network.hpp
source/Peer.cpp
source/RemotePeer.cpp
source/Context.cpp
source/test.cpp
source/utils/crypto/aes/AesKey.cpp
source/utils/crypto/aes/AesKey.hpp
source/utils/crypto/rsa/RsaPublicKey.cpp
source/utils/crypto/rsa/RsaPublicKey.hpp
source/utils/crypto/rsa/RsaPrivateKey.cpp
source/utils/crypto/rsa/RsaPrivateKey.hpp
source/utils/crypto/rsa/RsaPrivateKey.hpp
source/utils/crypto/rsa/RsaKeyPair.cpp
source/utils/crypto/rsa/RsaKeyPair.hpp
source/utils/serialize/basics.cpp
source/utils/serialize/basics.hpp
source/packets/audio/AudioPacketData.cpp
source/packets/info/InfoPacketData.cpp
source/packets/search/SearchPacketData.cpp
source/managers/SendManager.cpp
source/managers/ReceiveManager.cpp
source/managers/ReceiveManager.hpp
source/managers/SendManager.hpp
)
target_include_directories(M2-PT-DRP PRIVATE
source
${MPG123_INCLUDE_DIRS}
${PORTAUDIO_INCLUDE_DIRS}
${OPENSSL_INCLUDE_DIRS}
)
target_link_libraries(M2-PT-DRP PRIVATE
argparse
mpg123
portaudio
ssl
crypto
argparse::argparse
)
add_subdirectory(external/argparse)

View file

@ -1,24 +0,0 @@
FROM debian:latest
# update repositories
RUN apt-get update -y
RUN apt-get upgrade -y
# install build dependencies
RUN apt-get install -y cmake gcc g++ ninja-build
# install application dependencies
RUN apt-get install -y libmpg123-dev libssl-dev portaudio19-dev
# enable sound
# RUN echo "pcm.!default pulse\nctl.!default pulse" > /root/.asoundrc
# copy the application
WORKDIR /app
COPY . /app
# build the application
RUN cmake -S . -B build -G Ninja
RUN cmake --build build
# run the application
CMD ["build/M2-PT-DRP", "--host", "::1", "--port", "15650", "--ipv6"]

View file

@ -1,37 +0,0 @@
services:
machine-1:
build:
context: ./
dockerfile: ./Dockerfile
network: host
networks:
- machines
machine-2:
build:
context: ./
dockerfile: ./Dockerfile
network: host
networks:
- machines
machine-3:
build:
context: ./
dockerfile: ./Dockerfile
network: host
networks:
- machines
machine-4:
build:
context: ./
dockerfile: ./Dockerfile
network: host
networks:
- machines
networks:
machines:
enable_ipv6: true
attachable: true

1
external/argparse vendored

@ -1 +0,0 @@
Subproject commit cbd9fd8ed675ed6a2ac1bd7142d318c6ad5d3462

4
requirements.txt Normal file
View file

@ -0,0 +1,4 @@
sounddevice
msgpack
cryptography
bidict

View file

@ -1,22 +0,0 @@
#include "Context.hpp"
#include "utils/crypto/rsa/RsaKeyPair.hpp"
Context::Context(const int socket, addrinfo* broadcastAddressInfo) {
const auto keyPair = drp::util::crypto::RsaKeyPair(2048);
// communication
this->socket = socket;
this->broadcastAddressInfo = broadcastAddressInfo;
this->server = nullptr;
// ourselves
this->me = Peer(keyPair.getPublicKey());
// others
this->latestPeerDiscovery = std::chrono::high_resolution_clock::now();
// crytography
this->cryptoRsaPrivateKey = keyPair.getPrivateKey();
}

View file

@ -1,36 +0,0 @@
#pragma once
#include <list>
#include <memory>
#include <netdb.h>
#include "RemotePeer.hpp"
#include "utils/crypto/aes/AesKey.hpp"
#include "utils/crypto/rsa/RsaPrivateKey.hpp"
/**
* Information about the current state of our machine.
* Used everywhere to determinate how to behave.
* Information contained here may be private, contrary to the "me" attribute holding public data that can be shared.
*/
class Context {
public:
explicit Context(int socket, addrinfo* broadcastAddressInfo);
// communication
int socket; /// current socket file descriptor, used to communicate
addrinfo* broadcastAddressInfo; /// address used to broadcast messages
std::shared_ptr<RemotePeer> server; /// peer currently used as the server
// ourselves
Peer me; /// information about our own machine
// others
std::list<std::shared_ptr<RemotePeer>> remotePeers {}; /// information about other machines
std::chrono::high_resolution_clock::time_point latestPeerDiscovery; /// time of the latest discovered machine
// cryptography
drp::util::crypto::RsaPrivateKey cryptoRsaPrivateKey {}; /// the RSA private key
drp::util::crypto::AesKey256 cryptoAesKey = {}; /// the AES secret key
};

View file

@ -1,78 +0,0 @@
#include "Peer.hpp"
#include "behavior/tasks/undefined/UndefinedTask.hpp"
#include "utils/serialize/basics.hpp"
Peer::Peer() = default;
Peer::Peer(const drp::util::crypto::RsaPublicKey& cryptoRsaPublicKey) {
this->id = randomDistribution(randomGenerator);
this->channel = 0;
this->serverEnabled = false;
this->status = drp::task::TaskType::UNDEFINED;
this->latencyAverage = std::chrono::high_resolution_clock::duration::max();
this->cryptoRsaPublicKey = cryptoRsaPublicKey;
}
Peer::Peer(
std::uint32_t id,
bool serverEnabled,
drp::task::TaskType status,
std::uint8_t channel,
const std::chrono::high_resolution_clock::duration& latencyAverage,
const drp::util::crypto::RsaPublicKey& cryptoRsaPublicKey
) {
this->id = id;
this->serverEnabled = serverEnabled;
this->status = status;
this->channel = channel;
this->latencyAverage = latencyAverage;
this->cryptoRsaPublicKey = cryptoRsaPublicKey;
}
std::vector<std::uint8_t> Peer::serialize() const {
std::vector<std::uint8_t> data;
// serialized the members
const auto serializedId = drp::util::serialize::serializeObject<std::uint32_t>(this->id);
const auto serializedServerEnabled = drp::util::serialize::serializeObject<std::uint8_t>(this->serverEnabled);
const auto serializedStatus = drp::util::serialize::serializeObject<std::uint8_t>(static_cast<std::uint8_t>(this->status));
const auto serializedChannel = drp::util::serialize::serializeObject<std::uint8_t>(this->channel);
const auto serializedLatencyAverage = drp::util::serialize::serializeObject(this->latencyAverage);
const auto serializedPublicKey = this->cryptoRsaPublicKey.serialize();
// store them in the data
data.insert(data.end(), serializedId.begin(), serializedId.end());
data.insert(data.end(), serializedServerEnabled.begin(), serializedServerEnabled.end());
data.insert(data.end(), serializedStatus.begin(), serializedStatus.end());
data.insert(data.end(), serializedChannel.begin(), serializedChannel.end());
data.insert(data.end(), serializedLatencyAverage.begin(), serializedLatencyAverage.end());
data.insert(data.end(), serializedPublicKey.begin(), serializedPublicKey.end());
return data;
}
Peer Peer::deserialize(std::vector<std::uint8_t>& data) {
// deserialize the members
const auto id = drp::util::serialize::deserializeObject<std::uint32_t>(data);
const auto serverEnabled = drp::util::serialize::deserializeObject<std::uint8_t>(data);
const auto status = static_cast<drp::task::TaskType>(drp::util::serialize::deserializeObject<std::uint8_t>(data));
const auto channel = drp::util::serialize::deserializeObject<std::uint8_t>(data);
const auto latencyAverage = drp::util::serialize::deserializeObject<std::chrono::high_resolution_clock::duration>(data);
const auto publicKey = drp::util::crypto::RsaPublicKey::deserialize(data);
return Peer(id, serverEnabled, status, channel, latencyAverage, publicKey);
}
std::mt19937 Peer::randomGenerator = std::mt19937(std::random_device{}());
std::uniform_int_distribution<std::uint32_t> Peer::randomDistribution = std::uniform_int_distribution(
std::numeric_limits<std::uint32_t>::min(),
std::numeric_limits<std::uint32_t>::max()
);

View file

@ -1,47 +0,0 @@
#pragma once
#include <chrono>
#include <cstdint>
#include <random>
#include "behavior/tasks/types.hpp"
#include "utils/crypto/rsa/RsaPublicKey.hpp"
/**
* Contains common information about a certain peer.
* All the information contained here are "public": they can be communicated to anybody else safely.
*/
class Peer {
public:
Peer();
explicit Peer(const drp::util::crypto::RsaPublicKey& cryptoRsaPublicKey);
explicit Peer(
std::uint32_t id,
bool serverEnabled,
drp::task::TaskType status,
std::uint8_t channel,
const std::chrono::high_resolution_clock::duration& latencyAverage,
const drp::util::crypto::RsaPublicKey& cryptoRsaPublicKey
);
[[nodiscard]] std::vector<std::uint8_t> serialize() const;
static Peer deserialize(std::vector<std::uint8_t> &data);
// identification
std::uint32_t id {}; // TODO(Faraphel): shall be removed in the future
// network
bool serverEnabled {};
drp::task::TaskType status {};
std::uint8_t channel {};
std::chrono::high_resolution_clock::duration latencyAverage {};
// cryptography
drp::util::crypto::RsaPublicKey cryptoRsaPublicKey {};
private:
// random
static std::mt19937 randomGenerator;
static std::uniform_int_distribution<std::uint32_t> randomDistribution;
};

View file

@ -1,15 +0,0 @@
#include "RemotePeer.hpp"
RemotePeer::RemotePeer(const sockaddr_storage& address, const socklen_t addressLength, const Peer& peer) {
this->address = address;
this->addressLength = addressLength;
this->information = peer;
this->latestConnection = std::chrono::system_clock::now();
this->latency = std::chrono::high_resolution_clock::duration::max();
}
void RemotePeer::update(const Peer& peer) {
this->information = peer;
}

View file

@ -1,27 +0,0 @@
#pragma once
#include <chrono>
#include <sys/socket.h>
#include "Peer.hpp"
/**
* Contains information about a distant peer.
*/
class RemotePeer {
public:
explicit RemotePeer(const sockaddr_storage& address, socklen_t addressLength, const Peer& peer);
void update(const Peer& peer);
// communication
sockaddr_storage address {};
socklen_t addressLength;
std::chrono::high_resolution_clock::time_point latestConnection;
std::chrono::high_resolution_clock::duration latency{};
// information
Peer information;
};

3
source/__init__.py Normal file
View file

@ -0,0 +1,3 @@
from . import managers
from . import behaviors
from . import packets

15
source/__main__.py Normal file
View file

@ -0,0 +1,15 @@
import threading
from source.managers import Manager
manager = Manager("wlp1s0")
thread_receive = threading.Thread(target=manager.receiveLoop)
thread_send = threading.Thread(target=manager.sendLoop)
thread_receive.start()
thread_send.start()
thread_receive.join()
thread_send.join()

View file

@ -1 +0,0 @@
This directory contains the code describing how to react to a specific event.

View file

@ -1,145 +0,0 @@
#include "AudioEvent.hpp"
#include <iostream>
#include <bits/unique_lock.h>
#include "utils/audio/audio.hpp"
namespace drp::event {
AudioEvent::AudioEvent() {
this->stream = nullptr;
this->audioLock = std::unique_lock(this->audioMutex);
this->streamChannels = 0;
this->streamSampleFormat = 0;
this->streamRate = 0;
// start a thread for the player
this->playerThread = std::thread(&AudioEvent::loopPlay, this);
}
AudioEvent::~AudioEvent() {
// stop any currently playing audio
Pa_StopStream(this->stream);
// close the audio stream
if (const PaError error = Pa_CloseStream(this->stream))
std::cerr << "[Event - Audio] Could not close the stream: " << std::string(Pa_GetErrorText(error)) << std::endl;
}
void AudioEvent::handle(
Context& context,
std::vector<std::uint8_t>& data,
const sockaddr_storage& fromAddress,
const socklen_t fromAddressLength
) {
// get the audio data in the content
const auto audioData = packet::audio::AudioPacketData::deserialize(data);
// save it in the audio queue
this->audioQueue.push(audioData);
// notify that a new audio chunk is available
this->audioCondition.notify_one();
}
void AudioEvent::updateAudioStream(const int channels, const std::uint32_t sampleFormat, const double sampleRate) {
// check if any information changed. If no, ignore this
if (
this->streamChannels == channels &&
this->streamSampleFormat == sampleFormat &&
this->streamRate == sampleRate
)
return;
// close the current stream
// ignore errors that could happen if no audio is currently playing
Pa_CloseStream(&this->stream);
// open a new stream with the new settings
if (const PaError error = Pa_OpenDefaultStream(
&this->stream,
0,
channels,
sampleFormat,
sampleRate,
paFramesPerBufferUnspecified,
nullptr,
nullptr
) != paNoError)
throw std::runtime_error("[Event - Audio] Could not open the stream: " + std::string(Pa_GetErrorText(error)));
// update the new audio values
this->streamChannels = channels;
this->streamSampleFormat = sampleFormat;
this->streamRate = sampleRate;
}
void AudioEvent::loopPlay() {
while (true) {
// wait for a new element in the audio queue
this->audioCondition.wait(
this->audioLock,
[this] { return !this->audioQueue.empty(); }
);
// get the most recent audio chunk
const auto audioPacket = this->audioQueue.top();
// if the packet should have started playing before, skip it
if (audioPacket.timePlay < std::chrono::high_resolution_clock::now()) {
this->audioQueue.pop();
continue;
}
// update the stream with the new audio settings
this->updateAudioStream(
audioPacket.channels,
audioPacket.sampleFormat,
audioPacket.sampleRate
);
// wait until it must be played
std::this_thread::sleep_until(audioPacket.timePlay);
auto cTimePlay = std::chrono::high_resolution_clock::to_time_t(audioPacket.timePlay);
std::cout << "[Event - Audio] Playing: " << std::ctime(&cTimePlay) << std::endl;
// immediately stop playing music
// this avoids an offset created if this client's clock is too ahead of the others
// don't handle errors since audio might not be playing before
Pa_AbortStream(this->stream);
// play the new audio data
if (const int error = Pa_StartStream(this->stream) != paNoError)
throw std::runtime_error("[Event - Audio] Could not start the PortAudio stream: " + std::string(Pa_GetErrorText(error)));
// write the new audio data into the audio buffer
const int error = Pa_WriteStream(
this->stream,
audioPacket.content.data(),
audioPacket.content.size() / Pa_GetSampleSize(this->streamSampleFormat) / this->streamChannels
);
switch (error) {
// success
case paNoError:
break;
// the output might be very slightly underflowed,
// causing a very small period where no noise will be played.
case paOutputUnderflowed:
break;
default:
std::cerr << "[Event - Audio] Could not write to the audio stream: " << Pa_GetErrorText(error) << std::endl;
}
// remove the audio chunk
this->audioQueue.pop();
}
}
}

View file

@ -1,44 +0,0 @@
#pragma once
#include <condition_variable>
#include <portaudio.h>
#include <queue>
#include "AudioPacketsComparator.hpp"
#include "../base/BaseEvent.hpp"
namespace drp::event {
class AudioEvent : public BaseEvent {
public:
AudioEvent();
~AudioEvent() override;
void updateAudioStream(int channels, std::uint32_t sampleFormat, double sampleRate);
void loopPlay();
void handle(
Context& context,
std::vector<std::uint8_t>& data,
const sockaddr_storage& fromAddress,
socklen_t fromAddressLength
) override;
private:
std::thread playerThread;
PaStream* stream;
int streamChannels;
std::uint32_t streamSampleFormat;
double streamRate;
std::priority_queue<packet::audio::AudioPacketData, std::vector<packet::audio::AudioPacketData>, AudioPacketsComparator> audioQueue;
std::mutex audioMutex;
std::unique_lock<std::mutex> audioLock;
std::condition_variable audioCondition;
};
}

View file

@ -1,12 +0,0 @@
#include "AudioPacketsComparator.hpp"
namespace drp::event {
bool AudioPacketsComparator::operator()(const packet::audio::AudioPacketData& a, const packet::audio::AudioPacketData& b) const {
return a.timePlay > b.timePlay;
}
}

View file

@ -1,14 +0,0 @@
#pragma once
#include "packets/audio/AudioPacketData.hpp"
namespace drp::event {
struct AudioPacketsComparator {
bool operator() (const packet::audio::AudioPacketData& a, const packet::audio::AudioPacketData& b) const;
};
}

View file

@ -1,22 +0,0 @@
#pragma once
#include "packets/base/Packet.hpp"
#include "Context.hpp"
namespace drp::event {
class BaseEvent {
public:
virtual ~BaseEvent() = default;
virtual void handle(
Context& context,
std::vector<std::uint8_t>& data,
const sockaddr_storage& fromAddress,
socklen_t fromAddressLength
) = 0;
};
}

View file

@ -1,52 +0,0 @@
#include "InfoEvent.hpp"
#include <iostream>
#include <ranges>
#include <sys/socket.h>
#include "packets/info/InfoPacketData.hpp"
namespace drp::event {
void InfoEvent::handle(
Context& context,
std::vector<std::uint8_t>& data,
const sockaddr_storage& fromAddress,
const socklen_t fromAddressLength
) {
std::cout << "[Event - Info] Received peer information." << std::endl;
// get the peer information
const auto packetData = packet::info::InfoPacketData::deserialize(data);
const Peer packetPeer = packetData.peer;
// check if the peer address is already in the map
const auto itRemotePeer = std::ranges::find_if(
context.remotePeers,
[&](const auto& remotePeer) {
return remotePeer->information.id == packetPeer.id;
}
);
// if not already stored, create a new remote peer.
if (itRemotePeer == context.remotePeers.end()) {
// if not found, create a new peer
const auto remotePeer = std::make_shared<RemotePeer>(fromAddress, fromAddressLength, packetPeer);
// register it in the peer list
context.remotePeers.push_back(remotePeer);
// update the latest discovery time
context.latestPeerDiscovery = std::chrono::high_resolution_clock::now();
} else {
// get the peer
const auto& remotePeer = *itRemotePeer;
// update the peer information
remotePeer->update(packetPeer);
}
// TODO(Faraphel): interpret the timestamp and calculate average ping
}
}

View file

@ -1,21 +0,0 @@
#pragma once
#include "../base/BaseEvent.hpp"
#include <vector>
namespace drp::event {
class InfoEvent : public BaseEvent {
public:
void handle(
Context& context,
std::vector<std::uint8_t>& data,
const sockaddr_storage& fromAddress,
socklen_t fromAddressLength
) override;
};
}

View file

@ -1,19 +0,0 @@
#include "PongEvent.hpp"
#include <iostream>
namespace drp::event {
void PongEvent::handle(
Context& context,
std::vector<std::uint8_t>& data,
const sockaddr_storage& fromAddress,
const socklen_t fromAddressLength
) {
std::cout << "[Event - Pong] Pong." << std::endl;
}
}

View file

@ -1,20 +0,0 @@
#pragma once
#include "../base/BaseEvent.hpp"
namespace drp::event {
class PongEvent : public BaseEvent {
public:
void handle(
Context& context,
std::vector<std::uint8_t>& data,
const sockaddr_storage& fromAddress,
socklen_t fromAddressLength
) override;
};
}

View file

@ -1,59 +0,0 @@
#include "SearchEvent.hpp"
#include <cerrno>
#include <sys/socket.h>
#include <cstring>
#include <iostream>
#include <ostream>
#include "packets/base/SecurityMode.hpp"
#include "packets/info/InfoPacketData.hpp"
namespace drp {
void event::SearchEvent::handle(
Context& context,
std::vector<std::uint8_t>& data,
const sockaddr_storage& fromAddress,
const socklen_t fromAddressLength
) {
packet::base::Packet packet {};
packet::base::PacketContent packetContent {};
// create the packet header (available to read for everyone)
packet.channel = 0;
// create the packet data containing our information
packet::info::InfoPacketData packetData {};
packetData.peer = context.me;
packetContent.eventType = EventType::INFO;
packetContent.data = packetData.serialize();
packet.setContent(context, packet::base::SecurityMode::PLAIN, packetContent);
// TODO(Faraphel): send back the timestamp too
const auto serializedPacket = packet.serialize();
// send back our information
if (sendto(
context.socket,
serializedPacket.data(),
serializedPacket.size(),
0,
reinterpret_cast<const sockaddr*>(&fromAddress),
fromAddressLength
) == -1) {
std::cerr << "[Event - Search] Could not send information: " << strerror(errno) << std::endl;
return;
}
std::cout << "[Event - Search] Sent back information." << std::endl;
}
}

View file

@ -1,20 +0,0 @@
#pragma once
#include "../base/BaseEvent.hpp"
namespace drp::event {
class SearchEvent : public BaseEvent {
public:
void handle(
Context& context,
std::vector<std::uint8_t>& data,
const sockaddr_storage& fromAddress,
socklen_t fromAddressLength
) override;
};
}

View file

@ -1,25 +0,0 @@
#pragma once
namespace drp::event {
enum class EventType {
// debug
PING = 0x00, // simple ping
PONG = 0x01, // ping response
// protocol
SEARCH = 0x10, // search for devices
INFO = 0x011, // information about ourselves
// security
RSA_PUBLIC_KEY = 0x20, // sharing asymmetric public key
AES_SECRET_KEY = 0x21, // sharing symmetric private key
// functionality
AUDIO = 0x30, // play a sound at a given time
};
}

View file

@ -1,2 +0,0 @@
This directory contains the code describing how should the machine send event to its peers.
TODO(Faraphel): rename "roles" ?

View file

@ -1,26 +0,0 @@
#pragma once
#include "Context.hpp"
namespace drp::task {
/**
* The base to define a task.
* A task is a state for the machine, defining how it shall behave.
*/
class BaseTask {
public:
virtual ~BaseTask() = default;
/**
* The handle of the task.
* Contain the behavior of that specific task.
* @param context the context to use.
*/
virtual void handle(Context& context) = 0;
};
}

View file

@ -1,41 +0,0 @@
#include "ClientTask.hpp"
#include <iostream>
#include <thread>
namespace drp::task {
void ClientTask::use(Context& context, const std::shared_ptr<RemotePeer>& server) {
context.me.status = TaskType::CLIENT;
context.server = server;
}
void ClientTask::handle(Context& context) {
// get the server hostname
char host[NI_MAXHOST];
char port[NI_MAXSERV];
getnameinfo(
reinterpret_cast<sockaddr*>(&context.server->address), context.server->addressLength,
host, sizeof(host),
port, sizeof(port),
NI_NUMERICHOST | NI_NUMERICSERV
);
// connect to the chrony server
// TODO(Faraphel): only once ?
FILE* chronyProcess = popen(("chronyc add server " + std::string(host) + " iburst 2>&1").c_str(), "r");
if (pclose(chronyProcess) == -1)
std::cerr << "[Task - Client] Failed to connect to chrony server !" << std::endl;
// TODO(Faraphel): check if the server is still reachable.
// if connection lost, go back to undefined mode.
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}

View file

@ -1,22 +0,0 @@
#pragma once
#include "../base/BaseTask.hpp"
namespace drp::task {
class ClientTask final : public BaseTask {
public:
void handle(Context& context) override;
/**
* Set this task as the current one.
* @param context the context to apply the state on.
* @param server the server to use.
*/
static void use(Context& context, const std::shared_ptr<RemotePeer>& server);
};
}

View file

@ -1,142 +0,0 @@
#include "ServerTask.hpp"
#include <iostream>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <mpg123.h>
#include <netdb.h>
#include <stdexcept>
#include <thread>
#include <utility>
#include <sys/socket.h>
#include "packets/audio/AudioPacketData.hpp"
#include "packets/base/Packet.hpp"
#include "packets/base/SecurityMode.hpp"
#include "utils/audio/audio.hpp"
namespace drp::task {
ServerTask::ServerTask() {
this->channels = 0;
this->encoding = 0;
this->sampleRate = 0;
// create a new mpg123 handle
int error;
this->mpgHandle = mpg123_new(nullptr, &error);
if (this->mpgHandle == nullptr)
throw std::runtime_error("[Task - Server] Could not create a mpg123 handle.");
// open the mp3 file
// TODO(Faraphel): mp3 file as argument
if (mpg123_open(
this->mpgHandle,
"./assets/Queen - Another One Bites the Dust.mp3"
) != MPG123_OK)
throw std::runtime_error("[Task - Server] Could not open file.");
// get the format of the file
if (mpg123_getformat(
this->mpgHandle,
&this->sampleRate,
&this->channels,
&this->encoding
) != MPG123_OK)
throw std::runtime_error("[Task - Server] Could not get the format of the file.");
}
void ServerTask::use(Context& context, const std::shared_ptr<RemotePeer>& server) {
context.me.status = TaskType::SERVER;
context.server = server;
}
ServerTask::~ServerTask() {
// delete the mpg123 handle
mpg123_close(this->mpgHandle);
mpg123_delete(this->mpgHandle);
}
void ServerTask::handle(Context& context) {
// TODO(Faraphel): create a chrony server
// get the time of the start of the processing
const auto startProcessingTime = std::chrono::high_resolution_clock::now();
// prepare the packet structure
packet::base::Packet packet {};
packet::base::PacketContent packetContent {};
std::size_t done;
// create a packet
packet::audio::AudioPacketData audioPacket;
packet.channel = 0;
// set the audio settings
audioPacket.channels = this->channels;
audioPacket.sampleFormat = util::encoding_mpg123_to_PulseAudio(this->encoding);
audioPacket.sampleRate = this->sampleRate;
std::vector<std::uint8_t> content(64992);
// read the file
if (mpg123_read(
this->mpgHandle,
content.data(),
content.size(),
&done
) != MPG123_OK) {
std::cerr << "[Task - Server] Could not read audio data from file." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
return;
}
// resize the content to fit
content.resize(done);
audioPacket.content = content;
// set the target time
// TODO(Faraphel): dynamically change this delay to be the lowest possible
audioPacket.timePlay =
std::chrono::high_resolution_clock::now() +
std::chrono::milliseconds(5000);
packetContent.eventType = event::EventType::AUDIO;
packetContent.data = audioPacket.serialize();
packet.setContent(context, packet::base::SecurityMode::AES, packetContent);
const auto serializedPacket = packet.serialize();
// broadcast the audio data
if (sendto(
context.socket,
serializedPacket.data(),
serializedPacket.size(),
0,
context.broadcastAddressInfo->ai_addr,
context.broadcastAddressInfo->ai_addrlen
) == -1) {
std::cerr << "[Task - Server] Could not send audio packet: " << strerror(errno) << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
return;
}
std::cout << "[Task - Server] Sent: " << done << " bytes" << std::endl;
// wait for the duration of the audio chunk, since the start of the processing
std::this_thread::sleep_until(startProcessingTime + util::get_audio_chunk_duration(
this->sampleRate,
this->channels,
mpg123_encsize(this->encoding),
done
));
}
}

View file

@ -1,37 +0,0 @@
#pragma once
#include <mpg123.h>
#include <netdb.h>
#include "../base/BaseTask.hpp"
namespace drp::task {
/**
* the audio Server.
* Read and broadcast audio data.
*/
class ServerTask : public BaseTask {
public:
explicit ServerTask();
~ServerTask() override;
/**
* Set this task as the current one.
* @param context the context to apply the state on.
* @param server the server to use.
*/
static void use(Context& context, const std::shared_ptr<RemotePeer>& server);
void handle(Context& context) override;
private:
mpg123_handle* mpgHandle;
long sampleRate;
int channels;
int encoding;
};
}

View file

@ -1,14 +0,0 @@
#pragma once
namespace drp::task {
enum class TaskType {
UNDEFINED = 0x00,
CLIENT = 0x01,
SERVER = 0x02,
};
}

View file

@ -1,119 +0,0 @@
#include "UndefinedTask.hpp"
#include "../types.hpp"
#include <chrono>
#include <cstring>
#include <iostream>
#include <thread>
#include <algorithm>
#include <sys/socket.h>
#include <ifaddrs.h>
#include "Context.hpp"
#include "behavior/tasks/client/ClientTask.hpp"
#include "behavior/tasks/server/ServerTask.hpp"
#include "packets/base/Packet.hpp"
#include "packets/base/SecurityMode.hpp"
#include "packets/search/SearchPacketData.hpp"
#include "utils/network/network.hpp"
namespace drp::task {
void UndefinedTask::use(Context &context) {
context.me.status = TaskType::UNDEFINED;
context.server = nullptr;
context.remotePeers.clear();
}
void UndefinedTask::handle(Context& context) {
std::cout << "[Task - Undefined] List of peers: " << std::endl;
for (const auto& remotePeer : context.remotePeers)
std::cout <<
"\tPeer(id=" << remotePeer->information.id << ", " <<
"status=" << std::to_string(static_cast<std::uint8_t>(remotePeer->information.status)) << ")" <<
std::endl;
// search if a server is available among the peer.
const auto& server = std::ranges::find_if(
context.remotePeers,
[&](const auto& peer) { return peer->information.status == TaskType::SERVER; }
);
// if a server have been found, use it
if (server != context.remotePeers.end()) {
// go into client mode
ClientTask::use(context, *server);
return;
}
// wait that no more new peers are being discovered
if (
std::chrono::high_resolution_clock::now() - context.latestPeerDiscovery >
std::chrono::milliseconds(5000)
) {
std::cout << "No more peers discovered." << std::endl;
// otherwise, become the server if we have the highest ID.
// TODO(Faraphel): should use the machine with the lowest average ping
if (context.me.serverEnabled) {
// find the remote peer with the highest id that can be a server
const std::shared_ptr<RemotePeer> serverCandidate = *std::ranges::max_element(
context.remotePeers,
[&](auto& remotePeer1, auto& remotePeer2) {
return (
(remotePeer1->information.serverEnabled ? remotePeer1->information.id : 0) <
(remotePeer2->information.serverEnabled ? remotePeer2->information.id : 0)
);
}
);
// check if we are this peer
if (util::network::is_localhost(serverCandidate->address, serverCandidate->addressLength)) {
std::cout << "[Task - Undefined] Becoming server..." << std::endl;
// go into server mode
ServerTask::use(context, serverCandidate);
return;
}
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// prepare a search message
packet::base::Packet packet {};
packet::base::PacketContent packetContent {};
packet::search::SearchPacketData packetData {};
// broadcast message
packet.channel = 0;
// search message with the time of the message being sent
packetData.timestamp = std::chrono::high_resolution_clock::now();
packetContent.eventType = event::EventType::SEARCH;
packetContent.data = packetData.serialize();
packet.setContent(context, packet::base::SecurityMode::PLAIN, packetContent);
std::cout << "[Task - Undefined] Looking for new peers." << std::endl;
const auto serializedPacket = packet.serialize();
// send the search message
if (sendto(
context.socket,
serializedPacket.data(),
serializedPacket.size(),
0,
context.broadcastAddressInfo->ai_addr,
context.broadcastAddressInfo->ai_addrlen
) == -1)
std::cerr << "[Task - Undefined] Could not send search event: " << strerror(errno) << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}

View file

@ -1,18 +0,0 @@
#pragma once
#include <map>
#include "../base/BaseTask.hpp"
namespace drp::task {
class UndefinedTask final : public BaseTask {
public:
static void use(Context& context);
void handle(Context& context) override;
};
}

View file

View file

@ -0,0 +1,7 @@
from . import base
from source import packets
class DiscoveryEvent(base.BaseEvent):
def handle(self, packet: packets.DiscoveryPacket):
pass

View file

View file

@ -0,0 +1,5 @@
import abc
class BaseEvent(abc.ABC):
pass

View file

@ -0,0 +1 @@
from .BaseEvent import BaseEvent

View file

@ -0,0 +1,9 @@
from . import base
from source import managers, packets
class UndefinedRole(base.BaseRole):
def run(self, manager: "managers.Manager"):
packet = packets.DiscoveryPacket()
manager.communication.broadcast(packet)

View file

@ -0,0 +1 @@
from .UndefinedRole import UndefinedRole

View file

@ -0,0 +1,11 @@
import abc
from source import managers
class BaseRole(abc.ABC):
@abc.abstractmethod
def run(self, manager: "managers.Manager") -> None:
"""
Behavior of the role
"""

View file

@ -0,0 +1 @@
from .BaseRole import BaseRole

View file

@ -1,41 +0,0 @@
#include <mpg123.h>
#include <portaudio.h>
#include <stdexcept>
#include "argparse/argparse.hpp"
#include "managers/Manager.hpp"
int main(const int argc, char* argv[]) {
// initialize the mpg123 library
if (mpg123_init() != MPG123_OK)
throw std::runtime_error("Error while initializing mpg123.");
// initialize the PortAudio library
if (Pa_Initialize() != paNoError)
throw std::runtime_error("Could not initialize PortAudio.");
argparse::ArgumentParser parser("Program");
parser.add_argument("-h", "--host").help("Host address").default_value(std::string("127.0.0.1"));
parser.add_argument("-p", "--port").help("Port").default_value(std::string("15650"));
parser.add_argument("-6", "--ipv6").help("Use IPv6").flag();
try {
parser.parse_args(argc, argv);
} catch (const std::exception& exception) {
std::cerr << exception.what() << std::endl;
return EXIT_FAILURE;
}
auto manager = drp::managers::Manager(
parser.get<std::string>("--host"),
parser.get<std::string>("--port"),
parser.get<bool>("-6")
);
manager.loop();
// close the libraries
Pa_Terminate();
mpg123_exit();
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,118 @@
import socket
import typing
import zlib
import bidict
from source import packets
from source.utils.crypto.type import CipherType
class CommunicationManager:
"""
Manage everything about communication
"""
def __init__(self, interface: str, broadcast_address: str = "ff02::1", port: int = 5555):
self.broadcast_address = broadcast_address
self.port = port
# create an IPv6 UDP socket
self.socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
# enable broadcast messages
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True)
# use multicast on the selected interface
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, socket.if_nametoindex(interface))
# bind to listen for any message on this port
self.socket.bind(("::", self.port))
# create a dictionary to hold the types of packets and their headers.
self.packet_types: bidict.bidict[bytes, typing.Type[packets.base.BasePacket]] = bidict.bidict()
# the secret key used for AES communication
self.secret_key: bytes = b"secret key!"
def __del__(self):
# close the socket
self.socket.close()
def register_packet_type(self, header: bytes, packet_type: typing.Type[packets.base.BasePacket]) -> None:
"""
Register a new kind of packet that can be sent or received.
:param header: the binary header identifying the packet
:param packet_type: the class of the packet
"""
if len(header) != 4:
raise Exception("The header should be exactly 4 bytes long.")
self.packet_types[header] = packet_type
def packet_encode(self, packet: packets.base.BasePacket, cipher_type: CipherType) -> bytes:
"""
Encode a packet for diffusion
:param packet: a packet to encode to be sent
:param cipher_type: the type of cipher
:return: an encoded packet
"""
# get the header identifier of the type of this packet
header: typing.Optional[bytes] = self.packet_types.inverse.get(type(packet))
if header is None:
raise Exception(f"Unrecognised packet type: {type(packet)}. Has it been registered ?")
# get the encoded packet data
data = packet.pack()
# calculate its checksum using CRC32
checksum = zlib.crc32(data).to_bytes(4, byteorder='big')
return checksum + header + data
def packet_decode(self, payload: bytes) -> packets.base.BasePacket:
"""
Decode a payload into a packet
:param payload: the data of the packet
:return: the deserialized packet
"""
# split the header and data from the raw payload
checksum: int = int.from_bytes(payload[:4], "big")
header: bytes = payload[4:8]
data: bytes = payload[8:]
# verify the checksum for corruption
if zlib.crc32(data) != checksum:
raise ValueError("The checksum is invalid.")
# get the type of the packet from its header
packet_type: typing.Optional[typing.Type[packets.base.BasePacket]] = self.packet_types.get(header)
if header is None:
raise Exception(f"Unrecognised packet header: {header}. Has it been registered ?")
# unpack the packet
return packet_type.unpack(data)
def broadcast(self, packet: packets.base.BasePacket, cipher_type: CipherType = None):
"""
Broadcast a message in the network
:param cipher_type: the type of cipher
:param packet: the message to broadcast
"""
# TODO(Faraphel): should encrypt the data if required, prepend encryption mode
# TODO(Faraphel): use a channel system (OR ESTABLISH ANOTHER PORT ???)
self.socket.sendto(self.packet_encode(packet, cipher_type), (self.broadcast_address, self.port))
def receive(self) -> tuple[packets.base.BasePacket, tuple]:
"""
Receive a packet
:return: the packet content alongside the address of the sender
"""
# receive a message
payload, address = self.socket.recvfrom(65536)
# decode the payload
return self.packet_decode(payload), address
# TODO(Faraphel): should decrypt the data

View file

@ -1,112 +0,0 @@
#include "Manager.hpp"
#include <algorithm>
#include <stdexcept>
#include <cstring>
#include <iostream>
#include <netdb.h>
#include <ostream>
#include <thread>
#include <random>
#include <sys/socket.h>
#include "behavior/events/audio/AudioEvent.hpp"
#include "behavior/tasks/server/ServerTask.hpp"
#include "utils/crypto/aes/AesKey.hpp"
#include "utils/crypto/rsa/RsaKeyPair.hpp"
namespace drp::managers {
Manager::Manager(const std::string& address, const std::string& port, const bool useIpv6) {
std::cout << "Broadcast address: " << address << ":" << port << " (" << (useIpv6 ? "IPv6" : "IPv4") << ")" << std::endl;
// hints for the communication
addrinfo broadcastAddressHints {};
broadcastAddressHints.ai_family = useIpv6 ? AF_INET6 : AF_INET;
broadcastAddressHints.ai_socktype = SOCK_DGRAM;
broadcastAddressHints.ai_protocol = IPPROTO_UDP;
// create the client socket
const int sock = socket(
broadcastAddressHints.ai_family,
broadcastAddressHints.ai_socktype,
broadcastAddressHints.ai_protocol
);
if (sock < 0)
throw std::runtime_error("Could not create the socket: " + std::string(strerror(errno)));
// allow IPv6 multicast loopback so that we can receive our own messages.
constexpr int socketLoopback = 1;
if (setsockopt(
sock,
IPPROTO_IPV6,
IPV6_MULTICAST_LOOP,
&socketLoopback,
sizeof(socketLoopback)
) < 0) {
std::cerr << "Failed to set IPV6_MULTICAST_LOOP: " << strerror(errno) << std::endl;
}
// get the information for the broadcast local-link address
addrinfo* broadcastAddressInfo = nullptr;
if (const int error = getaddrinfo(
address.c_str(),
port.c_str(),
&broadcastAddressHints,
&broadcastAddressInfo
) != 0)
throw std::runtime_error("Could not get the broadcast address: " + std::string(gai_strerror(error)));
// hints for the bind address
addrinfo anyAddressHints {};
anyAddressHints.ai_family = useIpv6 ? AF_INET6 : AF_INET;
anyAddressHints.ai_flags = AI_PASSIVE;
anyAddressHints.ai_socktype = SOCK_DGRAM;
anyAddressHints.ai_protocol = IPPROTO_UDP;
// get the information for the broadcast local-link address
addrinfo *anyAddressInfo;
if (const int error = getaddrinfo(
nullptr,
port.c_str(),
&anyAddressHints,
&anyAddressInfo
) != 0)
throw std::runtime_error("Could not get the any address: " + std::string(gai_strerror(error)));
// bind the socket to the address
if (bind(
sock,
anyAddressInfo->ai_addr,
anyAddressInfo->ai_addrlen
) < 0)
throw std::runtime_error("Could not bind to the address: " + std::string(strerror(errno)));
// create the context
this->context = std::make_shared<Context>(sock, broadcastAddressInfo);
// TODO(Faraphel): should only be enabled if it can really emit sound.
this->context->me.serverEnabled = true;
// create the sub-managers.
this->sendManager = std::make_unique<SendManager>(context);
this->receiveManager = std::make_unique<ReceiveManager>(context);
}
Manager::~Manager() {
freeaddrinfo(this->context->broadcastAddressInfo);
}
void Manager::loop() {
// run an event receiver and sender
std::thread senderThread(&SendManager::loop, this->sendManager.get());
std::thread receiverThread(&ReceiveManager::loop, this->receiveManager.get());
senderThread.join();
receiverThread.join();
}
}

View file

@ -1,32 +0,0 @@
#pragma once
#include <memory>
#include "Context.hpp"
#include "managers/ReceiveManager.hpp"
#include "managers/SendManager.hpp"
namespace drp::managers {
/**
* The Manager.
* serve as the mainloop of the program.
*/
class Manager {
public:
Manager(const std::string& address, const std::string& port, bool useIpv6 = false);
~Manager();
void loop();
private:
std::shared_ptr<Context> context; /// context used between the events types
std::unique_ptr<SendManager> sendManager;
std::unique_ptr<ReceiveManager> receiveManager;
};
}

View file

@ -0,0 +1,30 @@
from . import CommunicationManager
from source import packets
from source.behaviors import roles
class Manager:
def __init__(self, interface: str):
self.communication = CommunicationManager(interface)
self.communication.register_packet_type(b"DISC", packets.DiscoveryPacket)
# define the role of our machine
# TODO(Faraphel): give the manager to the role directly ? register ?
self.role = roles.UndefinedRole()
def sendLoop(self):
while True:
self.role.run(self)
def receiveLoop(self):
try:
while True:
packet, address = self.communication.receive()
print(f"Received message from {address}: {packet}")
# get corresponding event
# handle it
except KeyboardInterrupt:
print("Stopping listener.")

View file

@ -1,101 +0,0 @@
#include "ReceiveManager.hpp"
#include <cstring>
#include <iostream>
#include "behavior/events/audio/AudioEvent.hpp"
#include "behavior/events/info/InfoEvent.hpp"
#include "behavior/events/pong/PongEvent.hpp"
#include "behavior/events/search/SearchEvent.hpp"
namespace drp::managers {
ReceiveManager::ReceiveManager(const std::shared_ptr<Context>& context) {
this->context = context;
// register the different events type
this->registry = {
{event::EventType::PONG, std::make_shared<event::PongEvent>()},
{event::EventType::SEARCH, std::make_shared<event::SearchEvent>()},
{event::EventType::INFO, std::make_shared<event::InfoEvent>()},
{event::EventType::AUDIO, std::make_shared<event::AudioEvent>()},
};
}
void ReceiveManager::run() const {
// prepare space for the sender address
sockaddr_storage fromAddress {};
socklen_t fromAddressLength = sizeof(fromAddress);
std::array<std::uint8_t, packet::base::maxPacketLength> buffer {};
// receive new data
const ssize_t size = recvfrom(
this->context->socket,
buffer.data(),
buffer.size(),
0,
reinterpret_cast<sockaddr*>(&fromAddress),
&fromAddressLength
);
if (size == -1)
throw std::runtime_error("[Receiver] Could not receive the packet: " + std::string(strerror(errno)));
// deserialize the packet
std::vector data(buffer.begin(), buffer.end());
const auto packet = drp::packet::base::Packet::deserialize(data);
// if the packet channel is neither 0 (all) nor the current one, ignore it
if (packet.channel != 0 && packet.channel != this->context->me.channel)
return;
// decrypt the packet
// TODO(Faraphel): handle exception ?
drp::packet::base::PacketContent packetContent = packet.getContent(*this->context);
// look for a saved peer with the same address
auto remotePeer = std::ranges::find_if(
this->context->remotePeers,
[&](const std::shared_ptr<RemotePeer>& remotePeer) { return
remotePeer->addressLength == fromAddressLength and
std::memcmp(&fromAddress, &remotePeer->address, fromAddressLength) == 0;
}
);
// if found, update the latest connection date
if (remotePeer != this->context->remotePeers.end())
(*remotePeer)->latestConnection = std::chrono::high_resolution_clock::now();
// get the corresponding event class
std::shared_ptr<event::BaseEvent> event;
try {
event = this->registry.at(packetContent.eventType);
} catch (const std::out_of_range& exception) {
std::cerr << "[Receiver] Unsupported event type: " << std::to_string(static_cast<int>(packetContent.eventType)) << std::endl;
return;
}
std::cout << "[Receiver] handling event: " << std::to_string(static_cast<int>(packetContent.eventType)) << std::endl;
// ask the event class to handle the event
try {
event->handle(
*this->context,
packetContent.data,
fromAddress,
fromAddressLength
);
} catch (const std::exception& exception) {
std::cerr << "[Receiver] Unhandled exception: " << exception.what() << std::endl;
}
}
void ReceiveManager::loop() const {
while (true)
this->run();
}
}

View file

@ -1,29 +0,0 @@
#pragma once
#include <map>
#include <memory>
#include "behavior/events/types.hpp"
#include "behavior/events/base/BaseEvent.hpp"
namespace drp::managers {
/**
* The ReceiveManager class.
* Handle everything related to receiving messages.
*/
class ReceiveManager {
public:
explicit ReceiveManager(const std::shared_ptr<Context>& context);
void run() const;
[[noreturn]] void loop() const;
private:
std::shared_ptr<Context> context;
std::map<event::EventType, std::shared_ptr<event::BaseEvent>> registry;
};
}

View file

@ -1,52 +0,0 @@
#include "SendManager.hpp"
#include <iostream>
#include "behavior/tasks/client/ClientTask.hpp"
#include "behavior/tasks/server/ServerTask.hpp"
#include "behavior/tasks/undefined/UndefinedTask.hpp"
namespace drp::managers {
SendManager::SendManager(const std::shared_ptr<Context>& context) {
this->context = context;
// register the different tasks type
this->registry = {
{task::TaskType::UNDEFINED, std::make_shared<task::UndefinedTask>()},
{task::TaskType::CLIENT, std::make_shared<task::ClientTask>()},
{task::TaskType::SERVER, std::make_shared<task::ServerTask>()},
};
}
void SendManager::run() const {
std::cout << "[Sender] Handling status: " + std::to_string(static_cast<int>(this->context->me.status)) << std::endl;
// get the corresponding task class
std::shared_ptr<task::BaseTask> task;
try {
task = this->registry.at(this->context->me.status);
} catch (const std::out_of_range& exception) {
std::cerr << "[Sender] Unsupported status: " << std::to_string(static_cast<int>(this->context->me.status)) << std::endl;
return;
}
// ask the task class to handle the task
try {
task->handle(*this->context);
} catch (const std::exception& exception) {
std::cerr << "[Sender] Unhandled exception: " << exception.what() << std::endl;
}
}
void SendManager::loop() const {
while (true)
this->run();
}
}

View file

@ -1,30 +0,0 @@
#pragma once
#include <map>
#include <memory>
#include <thread>
#include "behavior/tasks/types.hpp"
#include "behavior/tasks/base/BaseTask.hpp"
namespace drp::managers {
/**
* The SendManager class.
* Handle everything related to sending messages.
*/
class SendManager {
public:
explicit SendManager(const std::shared_ptr<Context>& context);
void run() const;
[[noreturn]] void loop() const;
private:
std::shared_ptr<Context> context;
std::map<task::TaskType, std::shared_ptr<task::BaseTask>> registry;
};
}

View file

@ -0,0 +1,2 @@
from .CommunicationManager import CommunicationManager
from .Manager import Manager

View file

@ -0,0 +1,30 @@
import msgpack
from source.packets import base
class AudioPacket(base.BasePacket):
"""
Represent a packet of audio data
"""
def __init__(self, data: bytes, rate: int, channels: int, encoding: int):
super().__init__()
self.data = data
self.rate = rate
self.channels = channels
self.encoding = encoding
def pack(self) -> bytes:
return msgpack.packb((
self.data,
self.rate,
self.channels,
self.encoding
))
@classmethod
def unpack(cls, data: bytes):
return cls(*msgpack.unpackb(data))

View file

@ -0,0 +1,22 @@
import msgpack
from source.packets import base
class DiscoveryPacket(base.BasePacket):
"""
Represent a packet used to discover new devices in the network.
"""
def __init__(self):
super().__init__()
def __repr__(self) -> str:
return f"<{self.__class__.__name__}>"
def pack(self) -> bytes:
return msgpack.packb(())
@classmethod
def unpack(cls, data: bytes):
return cls()

View file

@ -0,0 +1,4 @@
from . import base
from .AudioPacket import AudioPacket
from .DiscoveryPacket import DiscoveryPacket

View file

@ -1,43 +0,0 @@
#include "AudioPacketData.hpp"
#include "utils/serialize/basics.hpp"
namespace drp::packet::audio {
std::vector<std::uint8_t> AudioPacketData::serialize() const {
// serialize the members
const auto serializedTimePlay = util::serialize::serializeObject(this->timePlay);
const auto serializedChannels = util::serialize::serializeObject(this->channels);
const auto serializedSampleFormat = util::serialize::serializeObject(this->sampleFormat);
const auto serializedSampleRate = util::serialize::serializeObject(this->sampleRate);
const auto serializedContent = util::serialize::serializeVector(this->content);
// create a buffer to store our members
std::vector<std::uint8_t> data;
// store our members
data.insert(data.end(), serializedTimePlay.begin(), serializedTimePlay.end());
data.insert(data.end(), serializedChannels.begin(), serializedChannels.end());
data.insert(data.end(), serializedSampleFormat.begin(), serializedSampleFormat.end());
data.insert(data.end(), serializedSampleRate.begin(), serializedSampleRate.end());
data.insert(data.end(), serializedContent.begin(), serializedContent.end());
return data;
}
AudioPacketData AudioPacketData::deserialize(std::vector<std::uint8_t>& data) {
// deserialize the members
const auto packetTimePlay = util::serialize::deserializeObject<std::chrono::time_point<std::chrono::high_resolution_clock>>(data);
const auto packetChannels = util::serialize::deserializeObject<std::uint8_t>(data);
const auto packetSampleFormat = util::serialize::deserializeObject<std::uint32_t>(data);
const auto packetSampleRate = util::serialize::deserializeObject<std::uint32_t>(data);
const auto packetContent = util::serialize::deserializeVector<std::uint8_t>(data);
return {packetTimePlay, packetChannels, packetSampleFormat, packetSampleRate, packetContent};
}
}

View file

@ -1,32 +0,0 @@
#pragma once
#include <chrono>
#include <cstdint>
#include <vector>
namespace drp::packet::audio {
/**
* Represent the content of an audio packet.
* Contains a chunk of audio and its metadata to play it.
*/
class AudioPacketData {
public:
[[nodiscard]] std::vector<std::uint8_t> serialize() const;
static AudioPacketData deserialize(std::vector<std::uint8_t>& data);
// scheduling
// TODO(Faraphel): use a more "fixed" size format ?
std::chrono::time_point<std::chrono::high_resolution_clock> timePlay;
// audio settings
std::uint8_t channels {};
std::uint32_t sampleFormat {};
std::uint32_t sampleRate {};
// content
std::vector<std::uint8_t> content {};
};
}

View file

@ -0,0 +1,19 @@
import abc
class BasePacket(abc.ABC):
@abc.abstractmethod
def pack(self) -> bytes:
"""
Serialize the object to bytes.
:return: bytes representing the object
"""
@classmethod
@abc.abstractmethod
def unpack(cls, data: bytes) -> "BasePacket":
"""
Deserialize the object from bytes.
:param data: the data to deserialize
:return: the deserialized object
"""

View file

@ -1,99 +0,0 @@
#include "Packet.hpp"
#include <stdexcept>
#include "SecurityMode.hpp"
#include "utils/serialize/basics.hpp"
namespace drp::packet::base {
Packet::Packet() = default;
Packet::Packet(const std::uint8_t channel, const SecurityMode securityMode, const std::vector<uint8_t>& content) {
this->channel = channel;
this->securityMode = securityMode;
this->content = content;
}
PacketContent Packet::getContent(const Context& context) const {
std::vector<std::uint8_t> content;
switch (this->securityMode) {
case SecurityMode::PLAIN:
// copy the content
content = this->content;
break;
case SecurityMode::AES:
// decrypt the content
content = context.cryptoAesKey.decrypt(this->content);
break;
case SecurityMode::RSA:
throw std::runtime_error("Not implemented.");
default:
throw std::runtime_error("Unsupported security mode.");
}
// deserialize the content
return PacketContent::deserialize(content);
}
void Packet::setContent(const Context& context, const SecurityMode securityMode, const PacketContent& packetContent) {
this->securityMode = securityMode;
const std::vector<std::uint8_t> content = packetContent.serialize();
switch (this->securityMode) {
case SecurityMode::PLAIN:
// directly save the serialized content
this->content = content;
break;
case SecurityMode::AES:
// encrypt it with the defined AES key.
this->content = context.cryptoAesKey.encrypt(content);
break;
case SecurityMode::RSA:
throw std::runtime_error("Not implemented.");
default:
throw std::runtime_error("Unsupported security mode.");
}
}
std::vector<std::uint8_t> Packet::serialize() const {
// serialize the members
const auto serializedChannel = util::serialize::serializeObject(this->channel);
const auto serializedSecurityMode = util::serialize::serializeObject(static_cast<std::uint8_t>(this->securityMode));
const auto serializedContent = util::serialize::serializeVector(this->content);
// create a buffer to store our members
std::vector<std::uint8_t> data;
// store our members
data.insert(data.end(), serializedChannel.begin(), serializedChannel.end());
data.insert(data.end(), serializedSecurityMode.begin(), serializedSecurityMode.end());
data.insert(data.end(), serializedContent.begin(), serializedContent.end());
return data;
}
Packet Packet::deserialize(std::vector<std::uint8_t>& data) {
// deserialize the members
const auto packetChannel = util::serialize::deserializeObject<std::uint8_t>(data);
const auto packetSecurityMode = static_cast<SecurityMode>(util::serialize::deserializeObject<std::uint8_t>(data));
const auto packetContent = util::serialize::deserializeVector<std::uint8_t>(data);
return Packet(packetChannel, packetSecurityMode, packetContent);
}
}

View file

@ -1,39 +0,0 @@
#pragma once
#include <cstdint>
#include "Context.hpp"
#include "PacketContent.hpp"
#include "SecurityMode.hpp"
namespace drp::packet::base {
/**
* A generic packet that can be transmitted through the network.
* @param channel the channel of the packet. Two system can be created inside a same network by using different
* channels value. "0" is used for "broadcast" message across networks.
* @param securityMode the type of security used in the packet.
* @param _content the content of the packet. It is encrypted accordingly to the securityMode.
*/
class Packet {
public:
Packet();
explicit Packet(std::uint8_t channel, SecurityMode securityMode, const std::vector<uint8_t>& content);
[[nodiscard]] PacketContent getContent(const Context& context) const;
void setContent(const Context& context, SecurityMode securityMode, const PacketContent& packetContent);
[[nodiscard]] std::vector<std::uint8_t> serialize() const;
static Packet deserialize(std::vector<std::uint8_t>& data);
std::uint8_t channel {};
private:
SecurityMode securityMode {};
std::vector<std::uint8_t> content;
};
}

View file

@ -1,47 +0,0 @@
#include "PacketContent.hpp"
#include <cstring>
#include <stdexcept>
#include "Packet.hpp"
#include "SecurityMode.hpp"
#include "behavior/events/types.hpp"
#include "utils/serialize/basics.hpp"
namespace drp::packet::base {
PacketContent::PacketContent() = default;
PacketContent::PacketContent(const event::EventType eventType, const std::vector<std::uint8_t>& data) {
this->eventType = eventType;
this->data = data;
}
std::vector<std::uint8_t> PacketContent::serialize() const {
// serialize the members
const auto serializedEventType = util::serialize::serializeObject(static_cast<std::uint8_t>(this->eventType));
const auto serializedData = util::serialize::serializeVector(this->data);
// create a buffer to store our members
std::vector<std::uint8_t> data;
// store our members
data.insert(data.end(), serializedEventType.begin(), serializedEventType.end());
data.insert(data.end(), serializedData.begin(), serializedData.end());
return data;
}
PacketContent PacketContent::deserialize(std::vector<std::uint8_t>& data) {
// deserialize the members
const auto contentEventType = static_cast<event::EventType>(util::serialize::deserializeObject<std::uint8_t>(data));
const auto contentData = util::serialize::deserializeVector<std::uint8_t>(data);
return {contentEventType, contentData};
}
}

View file

@ -1,35 +0,0 @@
#pragma once
#include <cstdint>
#include <limits>
#include <vector>
#include "behavior/events/types.hpp"
namespace drp::packet::base {
// The maximum length of a packet. Cannot be larger than 65565 (uint16 max).
constexpr std::uint16_t maxPacketLength = std::numeric_limits<std::uint16_t>::max();
/**
* The content of a generic packet.
* @param eventType the type of event that the packet want to trigger.
* @param data the data of the event.
*/
class PacketContent {
public:
PacketContent();
PacketContent(event::EventType eventType, const std::vector<std::uint8_t>& data);
[[nodiscard]] std::vector<std::uint8_t> serialize() const;
static PacketContent deserialize(std::vector<std::uint8_t>& data);
event::EventType eventType {};
std::vector<std::uint8_t> data;
};
}

View file

@ -1,14 +0,0 @@
#pragma once
namespace drp::packet::base {
enum class SecurityMode {
PLAIN = 0x00,
AES = 0x01,
RSA = 0x02,
};
}

View file

@ -0,0 +1 @@
from .BasePacket import BasePacket

View file

@ -1,23 +0,0 @@
#include "InfoPacketData.hpp"
namespace drp::packet::info {
std::vector<std::uint8_t> InfoPacketData::serialize() const {
// serialize the members
const auto serializedPeer = this->peer.serialize();
return serializedPeer;
}
InfoPacketData InfoPacketData::deserialize(std::vector<std::uint8_t>& data) {
// deserialize the members
const auto packetPeer = Peer::deserialize(data);
return {packetPeer};
}
}

View file

@ -1,22 +0,0 @@
#pragma once
#include "RemotePeer.hpp"
namespace drp::packet::info {
/**
* Represent the content of an info packet.
* Contains information about the peer sending it.
*/
class InfoPacketData {
public:
[[nodiscard]] std::vector<std::uint8_t> serialize() const;
static InfoPacketData deserialize(std::vector<std::uint8_t>& data);
Peer peer;
};
}

View file

@ -1,25 +0,0 @@
#include "SearchPacketData.hpp"
#include "utils/serialize/basics.hpp"
namespace drp::packet::search {
std::vector<std::uint8_t> SearchPacketData::serialize() const {
// serialize the members
const auto serializedTimestamp = util::serialize::serializeObject(this->timestamp);
return serializedTimestamp;
}
SearchPacketData SearchPacketData::deserialize(std::vector<std::uint8_t>& data) {
// deserialize the members
const auto packetTimestamp = util::serialize::deserializeObject<std::chrono::time_point<std::chrono::high_resolution_clock>>(data);
return SearchPacketData(packetTimestamp);
}
}

View file

@ -1,23 +0,0 @@
#pragma once
#include <chrono>
#include <vector>
namespace drp::packet::search {
/**
* Represent a discovery request.
* Sent by someone to get information about other available machine in the network.
*/
class SearchPacketData {
public:
[[nodiscard]] std::vector<std::uint8_t> serialize() const;
static SearchPacketData deserialize(std::vector<std::uint8_t>& data);
std::chrono::time_point<std::chrono::system_clock> timestamp; /// timestamp when the search request was sent
};
}

View file

@ -1,146 +0,0 @@
#include <iostream>
#include <vector>
#include "Peer.hpp"
#include "utils/crypto/aes/AesKey.hpp"
#include "utils/crypto/rsa/RsaKeyPair.hpp"
#include "utils/serialize/basics.hpp"
int mainAes() {
const auto aesKey = drp::util::crypto::AesKey256();
// plain
std::string text = "hello world!";
const std::vector<std::uint8_t> plainData(text.begin(), text.end());
std::cout << "plain: ";
for (const auto& byte : plainData)
std::cout << std::to_string(byte) << "-";
std::cout << std::endl;
// encrypted
const auto encryptedData = aesKey.encrypt(plainData);
std::cout << "encrypted: ";
for (const auto& byte : encryptedData)
std::cout << std::to_string(byte) << "-";
std::cout << std::endl;
// decrypted
const auto decryptedData = aesKey.decrypt(encryptedData);
std::cout << "decrypted: ";
for (const auto& byte : decryptedData)
std::cout << std::to_string(byte) << "-";
std::cout << std::endl;
return 0;
}
int mainRsa() {
const auto rsaKey = drp::util::crypto::RsaKeyPair(2048);
const auto rsaPrivateKey = rsaKey.getPrivateKey();
const auto rsaPublicKey = rsaKey.getPublicKey();
// plain
std::string text = "hello world!";
const std::vector<std::uint8_t> plainData(text.begin(), text.end());
std::cout << "plain: ";
for (const auto& byte : plainData)
std::cout << std::to_string(byte) << "-";
std::cout << std::endl;
// encrypted
const auto encryptedData = rsaPublicKey.encrypt(plainData);
std::cout << "encrypted: ";
for (const auto& byte : encryptedData)
std::cout << std::to_string(byte) << "-";
std::cout << std::endl;
// decrypted
const auto decryptedData = rsaPrivateKey.decrypt(encryptedData);
std::cout << "decrypted: ";
for (const auto& byte : decryptedData)
std::cout << std::to_string(byte) << "-";
std::cout << std::endl;
return 0;
}
int mainSerialize() {
std::string text = "hello world!";
const std::vector<char> plainData(text.begin(), text.end());
std::cout << "plain: ";
for (const auto& byte : plainData)
std::cout << std::to_string(byte) << "-";
std::cout << std::endl;
std::vector<std::uint8_t> serializedData = drp::util::serialize::serializeVector(plainData);
std::cout << "serialized: ";
for (const auto& byte : serializedData)
std::cout << std::to_string(byte) << "-";
std::cout << std::endl;
std::vector<char> deserializedData = drp::util::serialize::deserializeVector<char>(serializedData);
std::cout << "deserialized: ";
for (const auto& byte : deserializedData)
std::cout << std::to_string(byte) << "-";
std::cout << std::endl;
return 0;
}
int mainRsaSerialize() {
// create a key pair
drp::util::crypto::RsaKeyPair keyPair(2048);
// create an object
Peer peer;
peer.channel = 7;
peer.id = 253;
// serialize it
auto serializedPeer = peer.serialize();
std::cout << "serialized: ";
for (const auto& byte : serializedPeer)
std::cout << std::to_string(byte) << "-";
std::cout << std::endl;
// encrypt it
const std::vector<std::uint8_t> encryptedSerializedPeer = keyPair.getPublicKey().encrypt(serializedPeer);
std::cout << "encrypted: ";
for (const auto& byte : encryptedSerializedPeer)
std::cout << std::to_string(byte) << "-";
std::cout << std::endl;
// decrypt it
std::vector<std::uint8_t> decryptedSerializedPeer = keyPair.getPrivateKey().decrypt(encryptedSerializedPeer);
std::cout << "decrypted: ";
for (const auto& byte : decryptedSerializedPeer)
std::cout << std::to_string(byte) << "-";
std::cout << std::endl;
const auto deserializedPeer = Peer::deserialize(decryptedSerializedPeer);
std::cout << "peer id: " << std::to_string(deserializedPeer.id) << std::endl;
std::cout << "peer channel: " << std::to_string(deserializedPeer.channel) << std::endl;
return 0;
}
int main_test() {
// mainAes();
// mainRsa();
// mainSerialize();
mainRsaSerialize();
return 0;
}

0
source/utils/__init__.py Normal file
View file

View file

@ -1,47 +0,0 @@
#include "audio.hpp"
#include <chrono>
#include <stdexcept>
#include <fmt123.h>
#include <portaudio.h>
namespace drp::util {
std::uint32_t encoding_mpg123_to_PulseAudio(const int encoding_mpg123) {
switch (encoding_mpg123) {
case MPG123_ENC_UNSIGNED_8:
return paUInt8;
case MPG123_ENC_SIGNED_8:
return paInt8;
case MPG123_ENC_SIGNED_16:
return paInt16;
case MPG123_ENC_SIGNED_24:
return paInt24;
case MPG123_ENC_SIGNED_32:
return paInt32;
case MPG123_ENC_FLOAT:
case MPG123_ENC_FLOAT_32:
return paFloat32;
default:
throw std::runtime_error("Invalid encoding value.");
}
}
std::chrono::milliseconds get_audio_chunk_duration(
const long sampleRate,
const int channels,
const int encodingSize,
const std::size_t length
) {
return std::chrono::milliseconds(static_cast<std::uint64_t>(
(1 / static_cast<double>(sampleRate * channels * encodingSize)) *
1000 * static_cast<double>(length)
));
}
}

View file

@ -1,19 +0,0 @@
#pragma once
#include <chrono>
#include <cstdint>
namespace drp::util {
std::uint32_t encoding_mpg123_to_PulseAudio(int encoding_mpg123);
std::chrono::milliseconds get_audio_chunk_duration(
long sampleRate,
int channels,
int encodingSize,
std::size_t length
);
}

View file

View file

@ -0,0 +1,47 @@
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
def aes_ecb_encrypt(data: bytes, key: bytes) -> bytes:
"""
Encrypt the message using AES in ECB mode.
:param data: the data to cipher
:param key: the key to use for the cipher
:return: the encrypted data
"""
# pad the data with PKCS7 for AES to work properly
padder = padding.PKCS7(128).padder()
padded_data = padder.update(data) + padder.finalize()
# create the AES cipher in ECB mode
cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
encryptor = cipher.encryptor()
# encrypt the padded data
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
return encrypted_data
def aes_ecb_decrypt(encrypted_data: bytes, key: bytes) -> bytes:
"""
Decrypt data encrypted with AES in CBC mode.
:param encrypted_data: the encrypted data
:param key: the key used to encrypt it
:return: the decrypted data
"""
# create the AES cipher in ECB mode
cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())
decryptor = cipher.decryptor()
# decrypt the encrypted data
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
# unpad the data
unpadder = padding.PKCS7(128).unpadder()
data = unpadder.update(decrypted_data) + unpadder.finalize()
return data

View file

@ -1 +0,0 @@
#include "AesKey.hpp"

View file

@ -1,166 +0,0 @@
#pragma once
#include <array>
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <openssl/evp.h>
namespace drp::util::crypto {
/**
* Represent an AES key.
* Allow for encrypting and decrypting data.
* @tparam keySize the size of the key (in bytes)
* @tparam ivSize the size of the initialisation vector (in bytes)
*/
template<std::size_t keySize, std::size_t ivSize, const EVP_CIPHER*(cipherFunction)()>
class AesKey {
public:
/**
* Create a random AES key.
*/
AesKey() {
// generate a random key
for (auto& byte : this->_data)
byte = randomDistribution(randomGenerator);
}
explicit AesKey(const std::array<std::uint8_t, keySize>& data) {
this->_data = data;
}
/**
* Encrypt data with this key.
* @param plainData the data to encrypt
* @return the encrypted data. It will always be longer than the original data.
*/
[[nodiscard]] std::vector<std::uint8_t> encrypt(const std::vector<std::uint8_t>& plainData) const {
// create an initialization vector
std::array<std::uint8_t, ivSize> iv {};
for (auto& byte : iv)
byte = randomDistribution(randomGenerator);
// create the cipher context
const auto context = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(
EVP_CIPHER_CTX_new(),
EVP_CIPHER_CTX_free
);
if (context == nullptr)
throw std::runtime_error("Error creating EVP_CIPHER_CTX");
// initialize the encryptor
if (EVP_EncryptInit_ex(
context.get(),
cipherFunction(),
nullptr,
this->_data.data(),
iv.data()
) != 1)
throw std::runtime_error("Error initializing encryption");
std::vector<std::uint8_t> encryptedData(ivSize + plainData.size() + EVP_CIPHER_block_size(EVP_aes_256_cbc()));
std::copy(iv.begin(), iv.end(), encryptedData.begin());
int length;
// encrypt the data
if (EVP_EncryptUpdate(
context.get(),
encryptedData.data() + ivSize,
&length,
plainData.data(),
static_cast<int>(plainData.size())
) != 1)
throw std::runtime_error("Error encrypting data");
int encryptedDataLength = length;
// finalize the encryption
if (EVP_EncryptFinal_ex(
context.get(),
encryptedData.data() + ivSize + encryptedDataLength,
&length
) != 1)
throw std::runtime_error("Error finalizing encryption");
encryptedDataLength += length;
encryptedData.resize(ivSize + encryptedDataLength);
return encryptedData;
}
/**
* Decrypt data with this key.
* @param rawEncryptedData the encrypted data to decrypt.
* @return the decrypted data.
*/
[[nodiscard]] std::vector<std::uint8_t> decrypt(const std::vector<std::uint8_t>& rawEncryptedData) const {
// create a cipher context
const auto context = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(
EVP_CIPHER_CTX_new(),
EVP_CIPHER_CTX_free
);
std::array<std::uint8_t, ivSize> iv;
std::copy(rawEncryptedData.begin(), rawEncryptedData.begin() + ivSize, iv.data());
const std::vector encryptedData(rawEncryptedData.begin() + ivSize, rawEncryptedData.end());
// initialize the decryptor
if (EVP_DecryptInit_ex(
context.get(),
cipherFunction(),
nullptr,
this->_data.data(),
iv.data()
) != 1)
throw std::runtime_error("Error initializing decryptor");
std::vector<std::uint8_t> plainData(encryptedData.size());
int length;
// decrypt the data
if (EVP_DecryptUpdate(
context.get(),
plainData.data(),
&length,
encryptedData.data(),
static_cast<int>(encryptedData.size())
) != 1)
throw std::runtime_error("Error decrypting data");
// finalize the decryption
if (EVP_DecryptFinal_ex(
context.get(),
plainData.data(),
&length
) != 1)
throw std::runtime_error("Error finalizing decryptor");
plainData.resize(length);
return plainData;
}
private:
static std::mt19937 randomGenerator;
static std::uniform_int_distribution<std::uint8_t> randomDistribution;
std::array<std::uint8_t, keySize> _data;
};
template<std::size_t keySize, std::size_t ivSize, const EVP_CIPHER*(cipherFunction)()>
std::mt19937 AesKey<keySize, ivSize, cipherFunction>::randomGenerator = std::mt19937(std::random_device{}());
template<std::size_t keySize, std::size_t ivSize, const EVP_CIPHER*(cipherFunction)()>
std::uniform_int_distribution<std::uint8_t> AesKey<keySize, ivSize, cipherFunction>::randomDistribution = std::uniform_int_distribution(
std::numeric_limits<std::uint8_t>::min(),
std::numeric_limits<std::uint8_t>::max()
);
using AesKey256 = AesKey<256/8, 16, EVP_aes_256_cbc>;
}

View file

@ -0,0 +1,51 @@
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
def rsa_encrypt(data: bytes, public_key_data: bytes) -> bytes:
"""
Encrypt data with RSA using a public key
:param data: the data to encrypt
:param public_key_data: the public key to encrypt with
:return: the encrypted data
"""
# load the public key
public_key = serialization.load_der_public_key(public_key_data)
# verify if the key is loaded
if not isinstance(public_key, rsa.RSAPublicKey):
raise ValueError("Could not load the public key.")
# encrypt the data with the key
return public_key.encrypt(
data,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
def rsa_decrypt(encrypted_data: bytes, private_key_data: bytes) -> bytes:
"""
Decrypt the data with the RSA private key
:param encrypted_data: the data to decrypt
:param private_key_data: the private key data
:return: the decrypted data
"""
# load the private key
private_key = serialization.load_der_private_key(private_key_data, None)
# verify if the key is loaded
if not isinstance(private_key, rsa.RSAPrivateKey):
raise ValueError("Could not load the private key.")
# decrypt the data
return private_key.decrypt(
encrypted_data,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)

View file

@ -1,98 +0,0 @@
#include "RsaKeyPair.hpp"
#include <openssl/x509.h>
namespace drp::util::crypto {
RsaKeyPair::RsaKeyPair(const std::size_t size, const int padMode) {
// create the context
const std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> context(
EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr),
EVP_PKEY_CTX_free
);
if (context == nullptr)
throw std::runtime_error("Could not create an EVP context.");
if (EVP_PKEY_keygen_init(context.get()) <= 0)
throw std::runtime_error("Could not initialize the EVP context.");
// configure the context
if (EVP_PKEY_CTX_set_rsa_keygen_bits(context.get(), static_cast<int>(size)) <= 0)
throw std::runtime_error("Error setting RSA key size.");
// create the key pair
EVP_PKEY* rawKeyPair = nullptr;
if (EVP_PKEY_keygen(context.get(), &rawKeyPair) <= 0)
throw std::runtime_error("Could not generate RSA key pair.");
if (rawKeyPair == nullptr)
throw std::runtime_error("Could not generate RSA key pair.");
std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> keyPair(
rawKeyPair,
EVP_PKEY_free
);
// extract the private key
const std::unique_ptr<BIO, decltype(&BIO_free)> privateBio(
BIO_new(BIO_s_mem()),
BIO_free
);
if (privateBio == nullptr)
throw std::runtime_error("Could not create RSA private key.");
if (!i2d_PrivateKey_bio(
privateBio.get(),
keyPair.get()
))
throw std::runtime_error("Could not generate RSA private key.");
std::vector<std::uint8_t> privateKeyData(BIO_pending(privateBio.get()));
if (BIO_read(
privateBio.get(),
privateKeyData.data(),
static_cast<int>(privateKeyData.size())
) <= 0)
throw std::runtime_error("Could not read RSA private key.");
this->privateKey = RsaPrivateKey(privateKeyData, padMode);
// extract the public key
const std::unique_ptr<BIO, decltype(&BIO_free)> publicBio(
BIO_new(BIO_s_mem()),
BIO_free
);
if (publicBio == nullptr)
throw std::runtime_error("Could not create RSA public key.");
if (!i2d_PUBKEY_bio(
publicBio.get(),
keyPair.get()
))
throw std::runtime_error("Could not generate RSA public key.");
std::vector<std::uint8_t> publicKeyData(BIO_pending(publicBio.get()));
if (BIO_read(
publicBio.get(),
publicKeyData.data(),
static_cast<int>(publicKeyData.size())
) <= 0)
throw std::runtime_error("Could not read RSA public key.");
this->publicKey = RsaPublicKey(publicKeyData, padMode);
}
RsaPublicKey RsaKeyPair::getPublicKey() const {
return this->publicKey;
}
RsaPrivateKey RsaKeyPair::getPrivateKey() const {
return this->privateKey;
}
}

View file

@ -1,44 +0,0 @@
#pragma once
#include <iostream>
#include <memory>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/types.h>
#include "RsaPrivateKey.hpp"
#include "RsaPublicKey.hpp"
namespace drp::util::crypto {
/**
* Represent a pair of RSA key.
*/
class RsaKeyPair {
public:
/**
* Generate a pair of public and private RSA keys.
*/
explicit RsaKeyPair(std::size_t size, int padMode = RSA_PKCS1_OAEP_PADDING);
/**
* Get the public key.
* @return the public key.
*/
[[nodiscard]] RsaPublicKey getPublicKey() const;
/**
* Get the private key.
* @return the private key.
*/
[[nodiscard]] RsaPrivateKey getPrivateKey() const;
private:
RsaPrivateKey privateKey;
RsaPublicKey publicKey;
};
}

View file

@ -1,121 +0,0 @@
#include "RsaPrivateKey.hpp"
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include "utils/serialize/basics.hpp"
namespace drp::util::crypto {
RsaPrivateKey::RsaPrivateKey() = default;
RsaPrivateKey::RsaPrivateKey(const std::vector<std::uint8_t>& data, const int padMode) {
this->_data = data;
this->padMode = padMode;
}
std::vector<std::uint8_t> RsaPrivateKey::decrypt(const std::vector<std::uint8_t>& encryptedData) const {
const auto key = this->getOpenSslKey();
// initialize the encryption context
const std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> context(
EVP_PKEY_CTX_new(key.get(), nullptr),
EVP_PKEY_CTX_free
);
if (context == nullptr)
throw std::runtime_error("Could not create EVP_PKEY_CTX.");
// initialize the encryption operation
if (EVP_PKEY_decrypt_init(context.get()) <= 0)
throw std::runtime_error("Could not initialize encryption.");
// set the padding
if (EVP_PKEY_CTX_set_rsa_padding(context.get(), RSA_PKCS1_OAEP_PADDING) <= 0)
throw std::runtime_error("Could not set RSA padding.");
// get the size of the output buffer
std::size_t decryptedDataLength;
if (EVP_PKEY_decrypt(
context.get(),
nullptr,
&decryptedDataLength,
encryptedData.data(),
encryptedData.size()
) <= 0)
throw std::runtime_error("Could not determine output length.");
std::vector<std::uint8_t> decryptedData(decryptedDataLength);
// encrypt the data
if (EVP_PKEY_decrypt(
context.get(),
decryptedData.data(),
&decryptedDataLength,
encryptedData.data(),
encryptedData.size()
) <= 0)
throw std::runtime_error("Could not decrypt data.");
decryptedData.resize(decryptedDataLength);
return decryptedData;
}
std::vector<std::uint8_t> RsaPrivateKey::serialize() const {
// serialize the members
const auto serializedData = serialize::serializeVector(this->_data);
const auto serializedPadMode = serialize::serializeObject(static_cast<std::uint8_t>(this->padMode));
// create a buffer to store our members
std::vector<std::uint8_t> data;
// store our members
data.insert(data.end(), serializedData.begin(), serializedData.end());
data.insert(data.end(), serializedPadMode.begin(), serializedPadMode.end());
return data;
}
RsaPrivateKey RsaPrivateKey::deserialize(std::vector<std::uint8_t>& data) {
// deserialize the members
const auto keyData = serialize::deserializeVector<std::uint8_t>(data);
const auto keyPadding = static_cast<int>(serialize::deserializeObject<std::uint8_t>(data));
return RsaPrivateKey(keyData, keyPadding);
}
[[nodiscard]] std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> RsaPrivateKey::getOpenSslKey() const {
// get the bio from the private key data
const std::unique_ptr<BIO, decltype(&BIO_free)> bio(
BIO_new_mem_buf(
this->_data.data(),
static_cast<int>(this->_data.size())
),
BIO_free
);
if (bio == nullptr)
throw std::runtime_error("Could not create BIO for private key.");
// get the key from the bio
EVP_PKEY* rawKey = nullptr;
if (!d2i_PrivateKey_bio(
bio.get(),
&rawKey
))
throw std::runtime_error("Could not deserialize RSA private key.");
return {
rawKey,
EVP_PKEY_free
};
}
}

View file

@ -1,32 +0,0 @@
#pragma once
#include <memory>
#include <vector>
#include <openssl/evp.h>
namespace drp::util::crypto {
/**
* Represent an RSA private key.
*/
class RsaPrivateKey {
public:
RsaPrivateKey();
explicit RsaPrivateKey(const std::vector<std::uint8_t>& data, int padMode);
[[nodiscard]] std::vector<std::uint8_t> decrypt(const std::vector<std::uint8_t>& encryptedData) const;
[[nodiscard]] std::vector<std::uint8_t> serialize() const;
static RsaPrivateKey deserialize(std::vector<std::uint8_t>& data);
private:
[[nodiscard]] std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> getOpenSslKey() const;
int padMode {};
std::vector<std::uint8_t> _data;
};
}

View file

@ -1,121 +0,0 @@
#include "RsaPublicKey.hpp"
#include <ranges>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include "utils/serialize/basics.hpp"
namespace drp::util::crypto {
RsaPublicKey::RsaPublicKey() = default;
RsaPublicKey::RsaPublicKey(const std::vector<uint8_t>& data, const int padMode) {
this->_data = data;
this->padMode = padMode;
}
[[nodiscard]] std::vector<uint8_t> RsaPublicKey::encrypt(const std::vector<std::uint8_t>& plainData) const {
const auto key = this->getOpenSslKey();
// initialize the encryption context
const std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> context(
EVP_PKEY_CTX_new(key.get(), nullptr),
EVP_PKEY_CTX_free
);
if (context == nullptr)
throw std::runtime_error("Could not create EVP_PKEY_CTX.");
// initialize the encryption operation
if (EVP_PKEY_encrypt_init(context.get()) <= 0)
throw std::runtime_error("Could not initialize encryption.");
// set the padding
if (EVP_PKEY_CTX_set_rsa_padding(context.get(), this->padMode) <= 0)
throw std::runtime_error("Could not set RSA padding.");
// get the size of the output buffer
std::size_t encryptedDataLength;
if (EVP_PKEY_encrypt(
context.get(),
nullptr,
&encryptedDataLength,
plainData.data(),
plainData.size()
) <= 0)
throw std::runtime_error("Could not determine output length.");
std::vector<std::uint8_t> encryptedData(encryptedDataLength);
// encrypt the data
if (EVP_PKEY_encrypt(
context.get(),
encryptedData.data(),
&encryptedDataLength,
plainData.data(),
plainData.size()
) <= 0)
throw std::runtime_error("Could not encrypt data.");
encryptedData.resize(encryptedDataLength);
return encryptedData;
}
std::vector<std::uint8_t> RsaPublicKey::serialize() const {
// serialize the members
const auto serializedData = serialize::serializeVector(this->_data);
const auto serializedPadMode = serialize::serializeObject<std::uint8_t>(static_cast<std::uint8_t>(this->padMode));
// create a buffer to store our members
std::vector<std::uint8_t> data;
// store our members
data.insert(data.end(), serializedData.begin(), serializedData.end());
data.insert(data.end(), serializedPadMode.begin(), serializedPadMode.end());
return data;
}
RsaPublicKey RsaPublicKey::deserialize(std::vector<std::uint8_t>& data) {
// deserialize the members
const auto keyData = serialize::deserializeVector<std::uint8_t>(data);
const auto keyPadding = static_cast<int>(serialize::deserializeObject<std::uint8_t>(data));
return RsaPublicKey(keyData, keyPadding);
}
[[nodiscard]] std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> RsaPublicKey::getOpenSslKey() const {
// get the bio from the public key data
const std::unique_ptr<BIO, decltype(&BIO_free)> bio(
BIO_new_mem_buf(
this->_data.data(),
static_cast<int>(this->_data.size())
),
BIO_free
);
if (bio == nullptr)
throw std::runtime_error("Could not create BIO for public key.");
// get the key from the bio
EVP_PKEY* rawKey = nullptr;
if (!d2i_PUBKEY_bio(
bio.get(),
&rawKey
))
throw std::runtime_error("Could not deserialize RSA public key.");
return {
rawKey,
EVP_PKEY_free
};
}
}

View file

@ -1,37 +0,0 @@
#pragma once
#include <vector>
#include "RsaPrivateKey.hpp"
namespace drp::util::crypto {
/**
* Represent an RSA public key.
*/
class RsaPublicKey {
public:
RsaPublicKey();
explicit RsaPublicKey(const std::vector<uint8_t>& data, int padMode);
/**
* Encrypt data with the public key. Can only be decrypted with the corresponding private key.
* @param plainData the plain data.
* @return the encrypted data.
*/
[[nodiscard]] std::vector<uint8_t> encrypt(const std::vector<std::uint8_t>& plainData) const;
[[nodiscard]] std::vector<std::uint8_t> serialize() const;
static RsaPublicKey deserialize(std::vector<std::uint8_t>& data);
private:
[[nodiscard]] std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> getOpenSslKey() const;
int padMode {};
std::vector<uint8_t> _data;
};
}

View file

@ -0,0 +1,7 @@
import enum
class CipherType(enum.Enum):
NONE = 0x00
AES_ECB = 0x01
RSA = 0x02

View file

@ -0,0 +1,57 @@
import typing
from source.utils.crypto import aes, rsa
from source.utils.crypto.type import CipherType
def encrypt(data: bytes, key: typing.Optional[bytes] = None, cipher_type: CipherType = CipherType.NONE) -> bytes:
"""
Encrypt data on various cipher type.
:param data: the data to cipher
:param key: the key to cipher the data
:param cipher_type: the type of cipher to use
:return:
"""
match cipher_type:
case CipherType.NONE:
return data
case CipherType.AES_ECB:
if key is None:
raise ValueError("The key cannot be None.")
return aes.aes_ecb_encrypt(data, key)
case CipherType.RSA:
if key is None:
raise ValueError("The key cannot be None.")
return rsa.rsa_encrypt(data, key)
case _:
raise KeyError("Unknown cipher mode.")
def decrypt(data: bytes, key: typing.Optional[bytes] = None, cipher_type: CipherType = CipherType.NONE) -> bytes:
"""
Encrypt data on various cipher type.
:param data: the data to decipher
:param key: the key to cipher the data
:param cipher_type: the type of cipher to use
:return:
"""
match cipher_type:
case CipherType.NONE:
return data
case CipherType.AES_ECB:
if key is None:
raise ValueError("The key cannot be None.")
return aes.aes_ecb_decrypt(data, key)
case CipherType.RSA:
if key is None:
raise ValueError("The key cannot be None.")
return rsa.rsa_decrypt(data, key)
case _:
raise KeyError("Unknown cipher mode.")

View file

@ -1,60 +0,0 @@
#include "network.hpp"
#include <cstring>
#include <ifaddrs.h>
#include <stdexcept>
#include <netinet/in.h>
namespace drp::util::network {
bool is_localhost(const sockaddr_storage& address, const socklen_t addressLength) {
// iterate through all the interfaces
ifaddrs* interfaces;
if (getifaddrs(&interfaces) == -1)
throw std::runtime_error("Could not get network interfaces.");
bool result = false;
for (const ifaddrs *interface = interfaces; interface; interface = interface->ifa_next) {
// if the interface family does not correspond to the server candidate, ignore it
if (interface->ifa_addr->sa_family != address.ss_family)
continue;
switch (address.ss_family) {
case AF_INET: {
const auto interfaceIpv4 = reinterpret_cast<const sockaddr_in*>(interface->ifa_addr);
const auto addressIpv4 = reinterpret_cast<const sockaddr_in*>(&address);
// the family have already been checked.
if (std::memcmp(&interfaceIpv4->sin_addr, &addressIpv4->sin_addr, sizeof(in_addr)) == 0)
result = true;
break;
}
case AF_INET6: {
const auto interfaceIpv6 = reinterpret_cast<const sockaddr_in6*>(interface->ifa_addr);
const auto addressIpv6 = reinterpret_cast<const sockaddr_in6*>(&address);
// the interface and the family have already been checked.
if (std::memcmp(&interfaceIpv6->sin6_addr, &addressIpv6->sin6_addr, sizeof(in6_addr)) == 0)
result = true;
break;
}
default:
throw std::runtime_error("Unknown address family.");
}
if (result == true)
break;
}
freeifaddrs(interfaces);
return result;
}
}

View file

@ -1,19 +0,0 @@
#pragma once
#include <cstddef>
#include <sys/socket.h>
namespace drp::util::network {
/**
* Indicate if an address refer to the local machine.
* @param address the address to check.
* @param addressLength the length of the address.
* @return True if the address refer to ourselves, False otherwise.
*/
bool is_localhost(const sockaddr_storage& address, socklen_t addressLength);
}

View file

@ -1,96 +0,0 @@
#pragma once
#include <cstring>
#include <cstdint>
#include <vector>
namespace drp::util::serialize {
/**
* Serialize a basic object to a vector of bytes
* @tparam Type the type of the object.
* @param object the object to serialize.
* @return the object as a vector of bytes.
*/
template<typename Type>
std::vector<std::uint8_t> serializeObject(const Type& object) {
// create a vector with enough space for the object
std::vector<std::uint8_t> buffer(sizeof(Type));
// copy the object data to the buffer
std::memcpy(buffer.data(), &object, sizeof(Type));
return buffer;
}
/**
* Deserialize a vector of bytes into an object.
* @warning the data used in parameter will be stripped of the data used to deserialize the object.
* @tparam Type the type of the object
* @param data the data of the object
* @return the object
*/
template<typename Type>
Type deserializeObject(std::vector<std::uint8_t>& data) {
// create an object based on the data
Type object;
std::memcpy(&object, data.data(), sizeof(Type));
// remove the space used by the object in the data
data.erase(data.begin(), data.begin() + sizeof(Type));
return object;
}
/**
* Serialize a vector of anything to a vector of bytes.
* @tparam Type the type of the data contained in the vector.
* @tparam SizeType the range of the size of the vector.
* @param object the vector to serialize.
* @return the vector data as a vector of bytes.
*/
template<typename Type, typename SizeType = std::uint16_t>
std::vector<std::uint8_t> serializeVector(const std::vector<Type>& object) {
// create a vector with enough size for the size and the data of the vector
std::vector<std::uint8_t> buffer(sizeof(SizeType) + object.size() * sizeof(Type));
// save the size of the vector
auto size = static_cast<SizeType>(object.size());
std::memcpy(buffer.data(), &size, sizeof(SizeType));
// save the content of the vector
std::memcpy(buffer.data() + sizeof(SizeType), object.data(), object.size() * sizeof(Type));
return buffer;
}
/**
* Deserialize a vector of bytes into a vector of object.
* @warning the data used in parameter will be stripped of the data used to deserialize the object.
* @tparam Type the type of the object
* @tparam SizeType the type of the size of the vector
* @param data the data in the vector
* @return the vector of object
*/
template<typename Type, typename SizeType = std::uint16_t>
std::vector<Type> deserializeVector(std::vector<std::uint8_t>& data) {
// get the size of the vector
SizeType size;
std::memcpy(&size, data.data(), sizeof(SizeType));
// create a vector with enough size of the data
std::vector<Type> object(size);
// restore the data into the object
std::memcpy(object.data(), data.data() + sizeof(SizeType), size * sizeof(Type));
// remove the data used for the deserialization from the source
data.erase(data.begin(), data.begin() + sizeof(SizeType) + size * sizeof(Type));
return object;
}
}

View file

@ -1 +0,0 @@
#include "Chrony.hpp"

View file

@ -1,12 +0,0 @@
#pragma once
namespace drp::util::time {
class Chrony {
};
}