Compare commits
81 Commits
cca_rework
...
6e681aa3fd
Author | SHA1 | Date | |
---|---|---|---|
6e681aa3fd | |||
1d97dbe73d | |||
0e9b1b8877 | |||
f449cf623d | |||
bee7de3fb7 | |||
822b979286 | |||
ef91ec14fc | |||
699957f79a | |||
02d58928f4 | |||
60e6f91541 | |||
92373d34f7 | |||
e0b278b168 | |||
e5681b4ad5 | |||
79e3070422 | |||
bf1fa64973 | |||
11dee5870c | |||
fab3d42ee9 | |||
269daaa764 | |||
ea945e6360 | |||
b068819069 | |||
b64a4ae31c | |||
266cddf816 | |||
eaaf798661 | |||
d19fc6ba30 | |||
613b183592 | |||
3fd6183c21 | |||
92b3d1a5fb | |||
edf58b70f5 | |||
33560f8f8a | |||
3286a7228c | |||
b53e291c68 | |||
27cade4dfe | |||
0b4041db7e | |||
e9e38db1d5 | |||
c8619561ec | |||
1b630bc07f | |||
ee2411b8e0 | |||
bc7417c1cd | |||
3827733f08 | |||
5400c13f88 | |||
8972386971 | |||
b27107af4c | |||
bcde244a3c | |||
e9f22bc9ae | |||
c09f2e6f8f | |||
0eb30246a8 | |||
c52ac19285 | |||
1231e792a7 | |||
319e754aff | |||
a4201f4407 | |||
57575330dd | |||
eb2a19d8f3 | |||
dfcb5dee97 | |||
0d40d1abaa | |||
61b667a4aa | |||
c03282eae8 | |||
5fd1f2ab84 | |||
bccd04316a | |||
ccf66fb80c | |||
ea032244e7 | |||
0df0760c06 | |||
f02b03da7c | |||
103f36f2d2 | |||
ad918a3253 | |||
70cea0d219 | |||
b0e2cab17a | |||
0a53a76eb3 | |||
5995059777 | |||
abf2645099 | |||
7c16c54649 | |||
a80e74065c | |||
77f21f01e9 | |||
27fd9e688b | |||
f28e79dcbc | |||
7af5fda0a6 | |||
f91780c602 | |||
1e6929c93b | |||
81a353570b | |||
070585ab3d | |||
ba8befbb2d | |||
a1a9bf886a |
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
.vs/
|
||||
*.o
|
||||
*.swp
|
||||
~*
|
||||
*~
|
||||
.idea/
|
||||
cmake-build-debug/
|
||||
cmake-build-debugandtest/
|
||||
cmake-build-release/
|
||||
*.stackdump
|
||||
*.coredump
|
||||
compile_commands.json
|
||||
/build*
|
||||
.clangd
|
||||
.cache
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
CMakeLists.txt.user*
|
||||
CMakeCache.txt
|
@ -51,9 +51,25 @@ add_library(solanaceae_sha1_ngcft1
|
||||
./solanaceae/ngc_ft1_sha1/hash_utils.hpp
|
||||
./solanaceae/ngc_ft1_sha1/hash_utils.cpp
|
||||
|
||||
./solanaceae/ngc_ft1_sha1/util.hpp
|
||||
|
||||
./solanaceae/ngc_ft1_sha1/ft1_sha1_info.hpp
|
||||
./solanaceae/ngc_ft1_sha1/ft1_sha1_info.cpp
|
||||
|
||||
./solanaceae/ngc_ft1_sha1/components.hpp
|
||||
./solanaceae/ngc_ft1_sha1/components.cpp
|
||||
|
||||
./solanaceae/ngc_ft1_sha1/contact_components.hpp
|
||||
|
||||
./solanaceae/ngc_ft1_sha1/chunk_picker.hpp
|
||||
./solanaceae/ngc_ft1_sha1/chunk_picker.cpp
|
||||
|
||||
./solanaceae/ngc_ft1_sha1/participation.hpp
|
||||
./solanaceae/ngc_ft1_sha1/participation.cpp
|
||||
|
||||
./solanaceae/ngc_ft1_sha1/receiving_transfers.hpp
|
||||
./solanaceae/ngc_ft1_sha1/receiving_transfers.cpp
|
||||
|
||||
./solanaceae/ngc_ft1_sha1/sha1_ngcft1.hpp
|
||||
./solanaceae/ngc_ft1_sha1/sha1_ngcft1.cpp
|
||||
)
|
||||
@ -65,5 +81,26 @@ target_link_libraries(solanaceae_sha1_ngcft1 PUBLIC
|
||||
sha1::sha1
|
||||
solanaceae_tox_contacts
|
||||
solanaceae_message3
|
||||
solanaceae_object_store
|
||||
solanaceae_file2
|
||||
)
|
||||
|
||||
########################################
|
||||
|
||||
option(SOLANACEAE_NGCFT1_SHA1_BUILD_TESTING "Build the solanaceae_ngcft1_sha1 tests" OFF)
|
||||
message("II SOLANACEAE_NGCFT1_SHA1_BUILD_TESTING " ${SOLANACEAE_NGCFT1_SHA1_BUILD_TESTING})
|
||||
|
||||
# TODO: proper options n shit
|
||||
if (SOLANACEAE_NGCFT1_SHA1_BUILD_TESTING)
|
||||
include(CTest)
|
||||
|
||||
#add_executable(bitset_tests
|
||||
# ./solanaceae/ngc_ft1_sha1/bitset_tests.cpp
|
||||
#)
|
||||
|
||||
#target_link_libraries(bitset_tests PUBLIC
|
||||
# solanaceae_sha1_ngcft1
|
||||
#)
|
||||
|
||||
endif()
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
#include "./ngcext.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
|
||||
NGCEXTEventProvider::NGCEXTEventProvider(ToxEventProviderI& tep) : _tep(tep) {
|
||||
_tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_CUSTOM_PACKET);
|
||||
_tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_CUSTOM_PRIVATE_PACKET);
|
||||
NGCEXTEventProvider::NGCEXTEventProvider(ToxI& t, ToxEventProviderI& tep) : _t(t), _tep(tep) {
|
||||
_tep.subscribe(this, Tox_Event_Type::TOX_EVENT_GROUP_CUSTOM_PACKET);
|
||||
_tep.subscribe(this, Tox_Event_Type::TOX_EVENT_GROUP_CUSTOM_PRIVATE_PACKET);
|
||||
}
|
||||
|
||||
#define _DATA_HAVE(x, error) if ((data_size - curser) < (x)) { error; }
|
||||
@ -112,6 +113,8 @@ bool NGCEXTEventProvider::parse_ft1_init_ack(
|
||||
_DATA_HAVE(sizeof(e.transfer_id), std::cerr << "NGCEXT: packet too small, missing transfer_id\n"; return false)
|
||||
e.transfer_id = data[curser++];
|
||||
|
||||
e.max_lossy_data_size = 500-4; // -4 and 500 are hardcoded
|
||||
|
||||
return dispatch(
|
||||
NGCEXT_Event::FT1_INIT_ACK,
|
||||
e
|
||||
@ -224,6 +227,198 @@ bool NGCEXTEventProvider::parse_ft1_message(
|
||||
);
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::parse_ft1_init_ack_v2(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
const uint8_t* data, size_t data_size,
|
||||
bool _private
|
||||
) {
|
||||
if (!_private) {
|
||||
std::cerr << "NGCEXT: ft1_init_ack_v2 cant be public\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
Events::NGCEXT_ft1_init_ack e;
|
||||
e.group_number = group_number;
|
||||
e.peer_number = peer_number;
|
||||
size_t curser = 0;
|
||||
|
||||
// - 1 byte (temporary_file_tf_id)
|
||||
_DATA_HAVE(sizeof(e.transfer_id), std::cerr << "NGCEXT: packet too small, missing transfer_id\n"; return false)
|
||||
e.transfer_id = data[curser++];
|
||||
|
||||
// - 2 byte (max_lossy_data_size)
|
||||
if ((data_size - curser) >= sizeof(e.max_lossy_data_size)) {
|
||||
e.max_lossy_data_size = 0;
|
||||
for (size_t i = 0; i < sizeof(e.max_lossy_data_size); i++, curser++) {
|
||||
e.max_lossy_data_size |= uint16_t(data[curser]) << (i*8);
|
||||
}
|
||||
} else {
|
||||
e.max_lossy_data_size = 500-4; // default
|
||||
}
|
||||
|
||||
return dispatch(
|
||||
NGCEXT_Event::FT1_INIT_ACK,
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::parse_ft1_have(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
const uint8_t* data, size_t data_size,
|
||||
bool _private
|
||||
) {
|
||||
if (!_private) {
|
||||
std::cerr << "NGCEXT: ft1_have cant be public\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
Events::NGCEXT_ft1_have e;
|
||||
e.group_number = group_number;
|
||||
e.peer_number = peer_number;
|
||||
size_t curser = 0;
|
||||
|
||||
// - 4 byte (file_kind)
|
||||
e.file_kind = 0u;
|
||||
_DATA_HAVE(sizeof(e.file_kind), std::cerr << "NGCEXT: packet too small, missing file_kind\n"; return false)
|
||||
for (size_t i = 0; i < sizeof(e.file_kind); i++, curser++) {
|
||||
e.file_kind |= uint32_t(data[curser]) << (i*8);
|
||||
}
|
||||
|
||||
// - X bytes (file_kind dependent id, differnt sizes)
|
||||
uint16_t file_id_size = 0u;
|
||||
_DATA_HAVE(sizeof(file_id_size), std::cerr << "NGCEXT: packet too small, missing file_id_size\n"; return false)
|
||||
for (size_t i = 0; i < sizeof(file_id_size); i++, curser++) {
|
||||
file_id_size |= uint32_t(data[curser]) << (i*8);
|
||||
}
|
||||
|
||||
_DATA_HAVE(file_id_size, std::cerr << "NGCEXT: packet too small, missing file_id, or file_id_size too large(" << data_size-curser << ")\n"; return false)
|
||||
|
||||
e.file_id = {data+curser, data+curser+file_id_size};
|
||||
curser += file_id_size;
|
||||
|
||||
// - array [
|
||||
// - 4 bytes (chunk index)
|
||||
// - ]
|
||||
while (curser < data_size) {
|
||||
_DATA_HAVE(sizeof(uint32_t), std::cerr << "NGCEXT: packet too small, broken chunk index\n"; return false)
|
||||
uint32_t chunk_index = 0u;
|
||||
for (size_t i = 0; i < sizeof(chunk_index); i++, curser++) {
|
||||
chunk_index |= uint32_t(data[curser]) << (i*8);
|
||||
}
|
||||
e.chunks.push_back(chunk_index);
|
||||
}
|
||||
|
||||
return dispatch(
|
||||
NGCEXT_Event::FT1_HAVE,
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::parse_ft1_bitset(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
const uint8_t* data, size_t data_size,
|
||||
bool _private
|
||||
) {
|
||||
if (!_private) {
|
||||
std::cerr << "NGCEXT: ft1_bitset cant be public\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
Events::NGCEXT_ft1_bitset e;
|
||||
e.group_number = group_number;
|
||||
e.peer_number = peer_number;
|
||||
size_t curser = 0;
|
||||
|
||||
// - 4 byte (file_kind)
|
||||
e.file_kind = 0u;
|
||||
_DATA_HAVE(sizeof(e.file_kind), std::cerr << "NGCEXT: packet too small, missing file_kind\n"; return false)
|
||||
for (size_t i = 0; i < sizeof(e.file_kind); i++, curser++) {
|
||||
e.file_kind |= uint32_t(data[curser]) << (i*8);
|
||||
}
|
||||
|
||||
// - X bytes (file_kind dependent id, differnt sizes)
|
||||
uint16_t file_id_size = 0u;
|
||||
_DATA_HAVE(sizeof(file_id_size), std::cerr << "NGCEXT: packet too small, missing file_id_size\n"; return false)
|
||||
for (size_t i = 0; i < sizeof(file_id_size); i++, curser++) {
|
||||
file_id_size |= uint32_t(data[curser]) << (i*8);
|
||||
}
|
||||
|
||||
_DATA_HAVE(file_id_size, std::cerr << "NGCEXT: packet too small, missing file_id, or file_id_size too large (" << data_size-curser << ")\n"; return false)
|
||||
|
||||
e.file_id = {data+curser, data+curser+file_id_size};
|
||||
curser += file_id_size;
|
||||
|
||||
e.start_chunk = 0u;
|
||||
_DATA_HAVE(sizeof(e.start_chunk), std::cerr << "NGCEXT: packet too small, missing start_chunk\n"; return false)
|
||||
for (size_t i = 0; i < sizeof(e.start_chunk); i++, curser++) {
|
||||
e.start_chunk |= uint32_t(data[curser]) << (i*8);
|
||||
}
|
||||
|
||||
// - X bytes
|
||||
// - array [
|
||||
// - 1 bit (have chunk)
|
||||
// - ] (filled up with zero)
|
||||
// high to low?
|
||||
// simply rest of file packet
|
||||
e.chunk_bitset = {data+curser, data+curser+(data_size-curser)};
|
||||
|
||||
return dispatch(
|
||||
NGCEXT_Event::FT1_BITSET,
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::parse_ft1_have_all(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
const uint8_t* data, size_t data_size,
|
||||
bool _private
|
||||
) {
|
||||
// can be public
|
||||
// TODO: warn on public?
|
||||
|
||||
Events::NGCEXT_ft1_have_all e;
|
||||
e.group_number = group_number;
|
||||
e.peer_number = peer_number;
|
||||
size_t curser = 0;
|
||||
|
||||
// - 4 byte (file_kind)
|
||||
e.file_kind = 0u;
|
||||
_DATA_HAVE(sizeof(e.file_kind), std::cerr << "NGCEXT: packet too small, missing file_kind\n"; return false)
|
||||
for (size_t i = 0; i < sizeof(e.file_kind); i++, curser++) {
|
||||
e.file_kind |= uint32_t(data[curser]) << (i*8);
|
||||
}
|
||||
|
||||
_DATA_HAVE(1, std::cerr << "NGCEXT: packet too small, missing file_id\n"; return false)
|
||||
|
||||
// - X bytes (file_id, differnt sizes)
|
||||
e.file_id = {data+curser, data+curser+(data_size-curser)};
|
||||
|
||||
return dispatch(
|
||||
NGCEXT_Event::FT1_HAVE_ALL,
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::parse_pc1_announce(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
const uint8_t* data, size_t data_size,
|
||||
bool _private
|
||||
) {
|
||||
// can be public
|
||||
Events::NGCEXT_pc1_announce e;
|
||||
e.group_number = group_number;
|
||||
e.peer_number = peer_number;
|
||||
size_t curser = 0;
|
||||
|
||||
// - X bytes (id, differnt sizes)
|
||||
e.id = {data+curser, data+curser+(data_size-curser)};
|
||||
|
||||
return dispatch(
|
||||
NGCEXT_Event::PC1_ANNOUNCE,
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::handlePacket(
|
||||
const uint32_t group_number,
|
||||
const uint32_t peer_number,
|
||||
@ -247,13 +442,22 @@ bool NGCEXTEventProvider::handlePacket(
|
||||
case NGCEXT_Event::FT1_INIT:
|
||||
return parse_ft1_init(group_number, peer_number, data+1, data_size-1, _private);
|
||||
case NGCEXT_Event::FT1_INIT_ACK:
|
||||
return parse_ft1_init_ack(group_number, peer_number, data+1, data_size-1, _private);
|
||||
//return parse_ft1_init_ack(group_number, peer_number, data+1, data_size-1, _private);
|
||||
return parse_ft1_init_ack_v2(group_number, peer_number, data+1, data_size-1, _private);
|
||||
case NGCEXT_Event::FT1_DATA:
|
||||
return parse_ft1_data(group_number, peer_number, data+1, data_size-1, _private);
|
||||
case NGCEXT_Event::FT1_DATA_ACK:
|
||||
return parse_ft1_data_ack(group_number, peer_number, data+1, data_size-1, _private);
|
||||
case NGCEXT_Event::FT1_MESSAGE:
|
||||
return parse_ft1_message(group_number, peer_number, data+1, data_size-1, _private);
|
||||
case NGCEXT_Event::FT1_HAVE:
|
||||
return parse_ft1_have(group_number, peer_number, data+1, data_size-1, _private);
|
||||
case NGCEXT_Event::FT1_BITSET:
|
||||
return parse_ft1_bitset(group_number, peer_number, data+1, data_size-1, _private);
|
||||
case NGCEXT_Event::FT1_HAVE_ALL:
|
||||
return parse_ft1_have_all(group_number, peer_number, data+1, data_size-1, _private);
|
||||
case NGCEXT_Event::PC1_ANNOUNCE:
|
||||
return parse_pc1_announce(group_number, peer_number, data+1, data_size-1, _private);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -261,6 +465,276 @@ bool NGCEXTEventProvider::handlePacket(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::send_ft1_request(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
) {
|
||||
// - 1 byte packet id
|
||||
// - 4 byte file_kind
|
||||
// - X bytes file_id
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_REQUEST));
|
||||
for (size_t i = 0; i < sizeof(file_kind); i++) {
|
||||
pkg.push_back((file_kind>>(i*8)) & 0xff);
|
||||
}
|
||||
for (size_t i = 0; i < file_id_size; i++) {
|
||||
pkg.push_back(file_id[i]);
|
||||
}
|
||||
|
||||
// lossless
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::send_ft1_init(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
uint64_t file_size,
|
||||
uint8_t transfer_id,
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
) {
|
||||
// - 1 byte packet id
|
||||
// - 4 byte (file_kind)
|
||||
// - 8 bytes (data size)
|
||||
// - 1 byte (temporary_file_tf_id, for this peer only, technically just a prefix to distinguish between simultainious fts)
|
||||
// - X bytes (file_kind dependent id, differnt sizes)
|
||||
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_INIT));
|
||||
for (size_t i = 0; i < sizeof(file_kind); i++) {
|
||||
pkg.push_back((file_kind>>(i*8)) & 0xff);
|
||||
}
|
||||
for (size_t i = 0; i < sizeof(file_size); i++) {
|
||||
pkg.push_back((file_size>>(i*8)) & 0xff);
|
||||
}
|
||||
pkg.push_back(transfer_id);
|
||||
for (size_t i = 0; i < file_id_size; i++) {
|
||||
pkg.push_back(file_id[i]);
|
||||
}
|
||||
|
||||
// lossless
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::send_ft1_init_ack(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint8_t transfer_id
|
||||
) {
|
||||
// - 1 byte packet id
|
||||
// - 1 byte transfer_id
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_INIT_ACK));
|
||||
pkg.push_back(transfer_id);
|
||||
|
||||
// - 2 bytes max_lossy_data_size
|
||||
const uint16_t max_lossy_data_size = _t.toxGroupMaxCustomLossyPacketLength() - 4;
|
||||
for (size_t i = 0; i < sizeof(uint16_t); i++) {
|
||||
pkg.push_back((max_lossy_data_size>>(i*8)) & 0xff);
|
||||
}
|
||||
|
||||
// lossless
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::send_ft1_data(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint8_t transfer_id,
|
||||
uint16_t sequence_id,
|
||||
const uint8_t* data, size_t data_size
|
||||
) {
|
||||
assert(data_size > 0);
|
||||
|
||||
// TODO
|
||||
// check header_size+data_size <= max pkg size
|
||||
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.reserve(2048); // saves a ton of allocations
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_DATA));
|
||||
pkg.push_back(transfer_id);
|
||||
pkg.push_back(sequence_id & 0xff);
|
||||
pkg.push_back((sequence_id >> (1*8)) & 0xff);
|
||||
|
||||
// TODO: optimize
|
||||
for (size_t i = 0; i < data_size; i++) {
|
||||
pkg.push_back(data[i]);
|
||||
}
|
||||
|
||||
// lossy
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, false, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::send_ft1_data_ack(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint8_t transfer_id,
|
||||
const uint16_t* seq_ids, size_t seq_ids_size
|
||||
) {
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.reserve(1+1+2*32); // 32acks in a single pkg should be unlikely
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_DATA_ACK));
|
||||
pkg.push_back(transfer_id);
|
||||
|
||||
// TODO: optimize
|
||||
for (size_t i = 0; i < seq_ids_size; i++) {
|
||||
pkg.push_back(seq_ids[i] & 0xff);
|
||||
pkg.push_back((seq_ids[i] >> (1*8)) & 0xff);
|
||||
}
|
||||
|
||||
// lossy
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, false, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::send_all_ft1_message(
|
||||
uint32_t group_number,
|
||||
uint32_t message_id,
|
||||
uint32_t file_kind,
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
) {
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_MESSAGE));
|
||||
|
||||
for (size_t i = 0; i < sizeof(message_id); i++) {
|
||||
pkg.push_back((message_id>>(i*8)) & 0xff);
|
||||
}
|
||||
for (size_t i = 0; i < sizeof(file_kind); i++) {
|
||||
pkg.push_back((file_kind>>(i*8)) & 0xff);
|
||||
}
|
||||
for (size_t i = 0; i < file_id_size; i++) {
|
||||
pkg.push_back(file_id[i]);
|
||||
}
|
||||
|
||||
// lossless
|
||||
return _t.toxGroupSendCustomPacket(group_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::send_ft1_have(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
const uint8_t* file_id, size_t file_id_size,
|
||||
const uint32_t* chunks_data, size_t chunks_size
|
||||
) {
|
||||
// 16bit file id size
|
||||
assert(file_id_size <= 0xffff);
|
||||
if (file_id_size > 0xffff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_HAVE));
|
||||
|
||||
for (size_t i = 0; i < sizeof(file_kind); i++) {
|
||||
pkg.push_back((file_kind>>(i*8)) & 0xff);
|
||||
}
|
||||
|
||||
// file id not last in packet, needs explicit size
|
||||
const uint16_t file_id_size_cast = file_id_size;
|
||||
for (size_t i = 0; i < sizeof(file_id_size_cast); i++) {
|
||||
pkg.push_back((file_id_size_cast>>(i*8)) & 0xff);
|
||||
}
|
||||
for (size_t i = 0; i < file_id_size; i++) {
|
||||
pkg.push_back(file_id[i]);
|
||||
}
|
||||
|
||||
// rest is chunks
|
||||
for (size_t c_i = 0; c_i < chunks_size; c_i++) {
|
||||
for (size_t i = 0; i < sizeof(chunks_data[c_i]); i++) {
|
||||
pkg.push_back((chunks_data[c_i]>>(i*8)) & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
// lossless
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::send_ft1_bitset(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
const uint8_t* file_id, size_t file_id_size,
|
||||
uint32_t start_chunk,
|
||||
const uint8_t* bitset_data, size_t bitset_size // size is bytes
|
||||
) {
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_BITSET));
|
||||
|
||||
for (size_t i = 0; i < sizeof(file_kind); i++) {
|
||||
pkg.push_back((file_kind>>(i*8)) & 0xff);
|
||||
}
|
||||
|
||||
// file id not last in packet, needs explicit size
|
||||
const uint16_t file_id_size_cast = file_id_size;
|
||||
for (size_t i = 0; i < sizeof(file_id_size_cast); i++) {
|
||||
pkg.push_back((file_id_size_cast>>(i*8)) & 0xff);
|
||||
}
|
||||
for (size_t i = 0; i < file_id_size; i++) {
|
||||
pkg.push_back(file_id[i]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(start_chunk); i++) {
|
||||
pkg.push_back((start_chunk>>(i*8)) & 0xff);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < bitset_size; i++) {
|
||||
pkg.push_back(bitset_data[i]);
|
||||
}
|
||||
|
||||
// lossless
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::send_ft1_have_all(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
) {
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_HAVE_ALL));
|
||||
|
||||
for (size_t i = 0; i < sizeof(file_kind); i++) {
|
||||
pkg.push_back((file_kind>>(i*8)) & 0xff);
|
||||
}
|
||||
for (size_t i = 0; i < file_id_size; i++) {
|
||||
pkg.push_back(file_id[i]);
|
||||
}
|
||||
|
||||
// lossless
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> build_pc1_announce(const uint8_t* id_data, size_t id_size) {
|
||||
// - 1 byte packet id
|
||||
// - X bytes (id, differnt sizes)
|
||||
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::PC1_ANNOUNCE));
|
||||
for (size_t i = 0; i < id_size; i++) {
|
||||
pkg.push_back(id_data[i]);
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::send_pc1_announce(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
const uint8_t* id_data, size_t id_size
|
||||
) {
|
||||
auto pkg = build_pc1_announce(id_data, id_size);
|
||||
|
||||
std::cout << "NEEP: sending PC1_ANNOUNCE s:" << pkg.size() - sizeof(NGCEXT_Event::PC1_ANNOUNCE) << "\n";
|
||||
|
||||
// lossless?
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::send_all_pc1_announce(
|
||||
uint32_t group_number,
|
||||
const uint8_t* id_data, size_t id_size
|
||||
) {
|
||||
auto pkg = build_pc1_announce(id_data, id_size);
|
||||
|
||||
std::cout << "NEEP: sending all PC1_ANNOUNCE s:" << pkg.size() - sizeof(NGCEXT_Event::PC1_ANNOUNCE) << "\n";
|
||||
|
||||
// lossless?
|
||||
return _t.toxGroupSendCustomPacket(group_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCEXTEventProvider::onToxEvent(const Tox_Event_Group_Custom_Packet* e) {
|
||||
const auto group_number = tox_event_group_custom_packet_get_group_number(e);
|
||||
const auto peer_number = tox_event_group_custom_packet_get_peer_id(e);
|
||||
|
@ -3,6 +3,7 @@
|
||||
// solanaceae port of tox_ngc_ext
|
||||
|
||||
#include <solanaceae/toxcore/tox_event_interface.hpp>
|
||||
#include <solanaceae/toxcore/tox_interface.hpp>
|
||||
#include <solanaceae/util/event_provider.hpp>
|
||||
|
||||
#include <solanaceae/toxcore/tox_key.hpp>
|
||||
@ -70,8 +71,6 @@ namespace Events {
|
||||
|
||||
// - X bytes (file_kind dependent id, differnt sizes)
|
||||
std::vector<uint8_t> file_id;
|
||||
|
||||
// TODO: max supported lossy packet size
|
||||
};
|
||||
|
||||
struct NGCEXT_ft1_init_ack {
|
||||
@ -81,7 +80,8 @@ namespace Events {
|
||||
// - 1 byte (transfer_id)
|
||||
uint8_t transfer_id;
|
||||
|
||||
// TODO: max supported lossy packet size
|
||||
// - 2 byte (self_max_lossy_data_size)
|
||||
uint16_t max_lossy_data_size;
|
||||
};
|
||||
|
||||
struct NGCEXT_ft1_data {
|
||||
@ -120,7 +120,6 @@ namespace Events {
|
||||
// - 4 byte (message_id)
|
||||
uint32_t message_id;
|
||||
|
||||
// request the other side to initiate a FT
|
||||
// - 4 byte (file_kind)
|
||||
uint32_t file_kind;
|
||||
|
||||
@ -128,6 +127,60 @@ namespace Events {
|
||||
std::vector<uint8_t> file_id;
|
||||
};
|
||||
|
||||
struct NGCEXT_ft1_have {
|
||||
uint32_t group_number;
|
||||
uint32_t peer_number;
|
||||
|
||||
// - 4 byte (file_kind)
|
||||
uint32_t file_kind;
|
||||
|
||||
// - X bytes (file_kind dependent id, differnt sizes)
|
||||
std::vector<uint8_t> file_id;
|
||||
|
||||
// - array [
|
||||
// - 4 bytes (chunk index)
|
||||
// - ]
|
||||
std::vector<uint32_t> chunks;
|
||||
};
|
||||
|
||||
struct NGCEXT_ft1_bitset {
|
||||
uint32_t group_number;
|
||||
uint32_t peer_number;
|
||||
|
||||
// - 4 byte (file_kind)
|
||||
uint32_t file_kind;
|
||||
|
||||
// - X bytes (file_kind dependent id, differnt sizes)
|
||||
std::vector<uint8_t> file_id;
|
||||
|
||||
uint32_t start_chunk;
|
||||
|
||||
// - array [
|
||||
// - 1 bit (have chunk)
|
||||
// - ] (filled up with zero)
|
||||
// high to low?
|
||||
std::vector<uint8_t> chunk_bitset;
|
||||
};
|
||||
|
||||
struct NGCEXT_ft1_have_all {
|
||||
uint32_t group_number;
|
||||
uint32_t peer_number;
|
||||
|
||||
// - 4 byte (file_kind)
|
||||
uint32_t file_kind;
|
||||
|
||||
// - X bytes (file_kind dependent id, differnt sizes)
|
||||
std::vector<uint8_t> file_id;
|
||||
};
|
||||
|
||||
struct NGCEXT_pc1_announce {
|
||||
uint32_t group_number;
|
||||
uint32_t peer_number;
|
||||
|
||||
// - X bytes (id, differnt sizes)
|
||||
std::vector<uint8_t> id;
|
||||
};
|
||||
|
||||
} // Events
|
||||
|
||||
enum class NGCEXT_Event : uint8_t {
|
||||
@ -163,6 +216,7 @@ enum class NGCEXT_Event : uint8_t {
|
||||
// acknowlage init (like an accept)
|
||||
// like tox ft control continue
|
||||
// - 1 byte (transfer_id)
|
||||
// - 2 byte (self_max_lossy_data_size) (optional since v2)
|
||||
FT1_INIT_ACK,
|
||||
|
||||
// TODO: init deny, speed up non acceptance
|
||||
@ -186,11 +240,50 @@ enum class NGCEXT_Event : uint8_t {
|
||||
// send file as message
|
||||
// basically the opposite of request
|
||||
// contains file_kind and file_id (and timestamp?)
|
||||
// - 4 byte (message_id)
|
||||
// - 4 byte (file_kind)
|
||||
// - 4 bytes (message_id)
|
||||
// - 4 bytes (file_kind)
|
||||
// - X bytes (file_kind dependent id, differnt sizes)
|
||||
FT1_MESSAGE,
|
||||
|
||||
// announce you have specified chunks, for given info
|
||||
// this is info/chunk specific
|
||||
// bundle these together to reduce overhead (like maybe every 16, max 1min)
|
||||
// - 4 bytes (file_kind)
|
||||
// - X bytes (file_kind dependent id, differnt sizes)
|
||||
// - array [
|
||||
// - 4 bytes (chunk index)
|
||||
// - ]
|
||||
FT1_HAVE,
|
||||
|
||||
// tell the other peer which chunks, for a given info you have
|
||||
// compressed down to a bitset (in parts)
|
||||
// supposed to only be sent once on participation announcement, when mutual interest
|
||||
// it is always assumed by the other side, that you dont have the chunk, until told otherwise,
|
||||
// so you can be smart about what you send.
|
||||
// - 4 bytes (file_kind)
|
||||
// - X bytes (file_kind dependent id, differnt sizes)
|
||||
// - 4 bytes (first chunk index in bitset)
|
||||
// - array [
|
||||
// - 1 bit (have chunk)
|
||||
// - ] (filled up with zero)
|
||||
FT1_BITSET,
|
||||
|
||||
// announce you have all chunks, for given info
|
||||
// prefer over have and bitset
|
||||
// - 4 bytes (file_kind)
|
||||
// - X bytes (file_kind dependent id, differnt sizes)
|
||||
FT1_HAVE_ALL,
|
||||
|
||||
// TODO: FT1_IDONTHAVE, tell a peer you no longer have said chunk
|
||||
// TODO: FT1_REJECT, tell a peer you wont fulfil the request
|
||||
|
||||
// tell another peer that you are participating in X
|
||||
// you can reply with PC1_ANNOUNCE, to let the other side know, you too are participating in X
|
||||
// you should NOT announce often, since this hits peers that not participate
|
||||
// ft1 uses fk+id
|
||||
// - x bytes (id, different sizes)
|
||||
PC1_ANNOUNCE = 0x80 | 32u,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
@ -204,15 +297,20 @@ struct NGCEXTEventI {
|
||||
virtual bool onEvent(const Events::NGCEXT_ft1_data&) { return false; }
|
||||
virtual bool onEvent(const Events::NGCEXT_ft1_data_ack&) { return false; }
|
||||
virtual bool onEvent(const Events::NGCEXT_ft1_message&) { return false; }
|
||||
virtual bool onEvent(const Events::NGCEXT_ft1_have&) { return false; }
|
||||
virtual bool onEvent(const Events::NGCEXT_ft1_bitset&) { return false; }
|
||||
virtual bool onEvent(const Events::NGCEXT_ft1_have_all&) { return false; }
|
||||
virtual bool onEvent(const Events::NGCEXT_pc1_announce&) { return false; }
|
||||
};
|
||||
|
||||
using NGCEXTEventProviderI = EventProviderI<NGCEXTEventI>;
|
||||
|
||||
class NGCEXTEventProvider : public ToxEventI, public NGCEXTEventProviderI {
|
||||
ToxI& _t;
|
||||
ToxEventProviderI& _tep;
|
||||
|
||||
public:
|
||||
NGCEXTEventProvider(ToxEventProviderI& tep/*, ToxI& t*/);
|
||||
NGCEXTEventProvider(ToxI& t, ToxEventProviderI& tep);
|
||||
|
||||
protected:
|
||||
bool parse_hs1_request_last_ids(
|
||||
@ -263,6 +361,36 @@ class NGCEXTEventProvider : public ToxEventI, public NGCEXTEventProviderI {
|
||||
bool _private
|
||||
);
|
||||
|
||||
bool parse_ft1_init_ack_v2(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
const uint8_t* data, size_t data_size,
|
||||
bool _private
|
||||
);
|
||||
|
||||
bool parse_ft1_have(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
const uint8_t* data, size_t data_size,
|
||||
bool _private
|
||||
);
|
||||
|
||||
bool parse_ft1_bitset(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
const uint8_t* data, size_t data_size,
|
||||
bool _private
|
||||
);
|
||||
|
||||
bool parse_ft1_have_all(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
const uint8_t* data, size_t data_size,
|
||||
bool _private
|
||||
);
|
||||
|
||||
bool parse_pc1_announce(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
const uint8_t* data, size_t data_size,
|
||||
bool _private
|
||||
);
|
||||
|
||||
bool handlePacket(
|
||||
const uint32_t group_number,
|
||||
const uint32_t peer_number,
|
||||
@ -271,6 +399,78 @@ class NGCEXTEventProvider : public ToxEventI, public NGCEXTEventProviderI {
|
||||
const bool _private
|
||||
);
|
||||
|
||||
public: // send api
|
||||
bool send_ft1_request(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
);
|
||||
|
||||
bool send_ft1_init(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
uint64_t file_size,
|
||||
uint8_t transfer_id,
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
);
|
||||
|
||||
bool send_ft1_init_ack(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint8_t transfer_id
|
||||
);
|
||||
|
||||
bool send_ft1_data(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint8_t transfer_id,
|
||||
uint16_t sequence_id,
|
||||
const uint8_t* data, size_t data_size
|
||||
);
|
||||
|
||||
bool send_ft1_data_ack(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint8_t transfer_id,
|
||||
const uint16_t* seq_ids, size_t seq_ids_size
|
||||
);
|
||||
|
||||
// TODO: add private version
|
||||
bool send_all_ft1_message(
|
||||
uint32_t group_number,
|
||||
uint32_t message_id,
|
||||
uint32_t file_kind,
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
);
|
||||
|
||||
bool send_ft1_have(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
const uint8_t* file_id, size_t file_id_size,
|
||||
const uint32_t* chunks_data, size_t chunks_size
|
||||
);
|
||||
|
||||
bool send_ft1_bitset(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
const uint8_t* file_id, size_t file_id_size,
|
||||
uint32_t start_chunk,
|
||||
const uint8_t* bitset_data, size_t bitset_size // size is bytes
|
||||
);
|
||||
|
||||
bool send_ft1_have_all(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
);
|
||||
|
||||
bool send_pc1_announce(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
const uint8_t* id_data, size_t id_size
|
||||
);
|
||||
|
||||
bool send_all_pc1_announce(
|
||||
uint32_t group_number,
|
||||
const uint8_t* id_data, size_t id_size
|
||||
);
|
||||
|
||||
protected:
|
||||
bool onToxEvent(const Tox_Event_Group_Custom_Packet* e) override;
|
||||
bool onToxEvent(const Tox_Event_Group_Custom_Private_Packet* e) override;
|
||||
|
@ -38,22 +38,38 @@ struct CCAI {
|
||||
//static_assert(maximum_segment_size == 574); // mesured in wireshark
|
||||
|
||||
// flow control
|
||||
float max_byterate_allowed {10*1024*1024}; // 10MiB/s
|
||||
//float max_byterate_allowed {100.f*1024*1024}; // 100MiB/s
|
||||
float max_byterate_allowed {10.f*1024*1024}; // 10MiB/s
|
||||
//float max_byterate_allowed {1.f*1024*1024}; // 1MiB/s
|
||||
//float max_byterate_allowed {0.6f*1024*1024}; // 600KiB/s
|
||||
//float max_byterate_allowed {0.5f*1024*1024}; // 500KiB/s
|
||||
//float max_byterate_allowed {0.15f*1024*1024}; // 150KiB/s
|
||||
//float max_byterate_allowed {0.05f*1024*1024}; // 50KiB/s
|
||||
|
||||
public: // api
|
||||
CCAI(size_t maximum_segment_data_size) : MAXIMUM_SEGMENT_DATA_SIZE(maximum_segment_data_size) {}
|
||||
virtual ~CCAI(void) {}
|
||||
|
||||
// returns current rtt/delay
|
||||
virtual float getCurrentDelay(void) const = 0;
|
||||
|
||||
// return the current believed window in bytes of how much data can be inflight,
|
||||
//virtual float getCWnD(void) const = 0;
|
||||
virtual float getWindow(void) const = 0;
|
||||
|
||||
// TODO: api for how much data we should send
|
||||
// take time since last sent into account
|
||||
// respect max_byterate_allowed
|
||||
virtual size_t canSend(void) = 0;
|
||||
virtual int64_t canSend(float time_delta) = 0;
|
||||
|
||||
// get the list of timed out seq_ids
|
||||
virtual std::vector<SeqIDType> getTimeouts(void) const = 0;
|
||||
|
||||
// returns -1 if not implemented, can return 0
|
||||
virtual int64_t inFlightCount(void) const { return -1; }
|
||||
|
||||
// returns -1 if not implemented, can return 0
|
||||
virtual int64_t inFlightBytes(void) const { return -1; }
|
||||
|
||||
public: // callbacks
|
||||
// data size is without overhead
|
||||
virtual void onSent(SeqIDType seq, size_t data_size) = 0;
|
||||
|
@ -3,14 +3,25 @@
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
void CUBIC::updateReductionTimer(float time_delta) {
|
||||
const auto now {getTimeNow()};
|
||||
|
||||
// only keep updating while the cca interaction is not too long ago
|
||||
if (now - _time_point_last_update <= getCurrentDelay()*4.f) {
|
||||
_time_since_reduction += time_delta;
|
||||
}
|
||||
}
|
||||
|
||||
void CUBIC::resetReductionTimer(void) {
|
||||
_time_since_reduction = 0.f;
|
||||
}
|
||||
|
||||
float CUBIC::getCWnD(void) const {
|
||||
const double K = cbrt(
|
||||
(_window_max * (1. - BETA)) / SCALING_CONSTANT
|
||||
);
|
||||
|
||||
const double time_since_reduction = getTimeNow() - _time_point_reduction;
|
||||
|
||||
const double TK = time_since_reduction - K;
|
||||
const double TK = _time_since_reduction - K;
|
||||
|
||||
const double cwnd =
|
||||
SCALING_CONSTANT
|
||||
@ -33,29 +44,67 @@ float CUBIC::getCWnD(void) const {
|
||||
}
|
||||
|
||||
void CUBIC::onCongestion(void) {
|
||||
if (getTimeNow() - _time_point_reduction >= getCurrentDelay()) {
|
||||
const auto current_cwnd = getCWnD();
|
||||
_time_point_reduction = getTimeNow();
|
||||
_window_max = current_cwnd;
|
||||
// 8 is probably too much (800ms for 100ms rtt)
|
||||
if (_time_since_reduction >= getCurrentDelay()*4.f) {
|
||||
const auto tmp_old_tp = _time_since_reduction;
|
||||
|
||||
std::cout << "CONGESTION! cwnd:" << current_cwnd << "\n";
|
||||
const auto current_cwnd = getCWnD(); // TODO: remove, only used by logging?
|
||||
const auto current_wnd = getWindow(); // respects cwnd and fwnd
|
||||
|
||||
resetReductionTimer();
|
||||
|
||||
if (current_cwnd < _window_max) {
|
||||
// congestion before reaching the inflection point (prev window_max).
|
||||
// reduce to wnd*beta to be fair
|
||||
_window_max = current_wnd * BETA;
|
||||
} else {
|
||||
_window_max = current_wnd;
|
||||
}
|
||||
|
||||
_window_max = std::max(_window_max, 2.0*MAXIMUM_SEGMENT_SIZE);
|
||||
|
||||
#if 1
|
||||
std::cout << "----CONGESTION!"
|
||||
<< " cwnd:" << current_cwnd
|
||||
<< " wnd:" << current_wnd
|
||||
<< " cwnd_max:" << _window_max
|
||||
<< " pts:" << tmp_old_tp
|
||||
<< " rtt:" << getCurrentDelay()
|
||||
<< "\n"
|
||||
;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
size_t CUBIC::canSend(void) {
|
||||
const auto fspace_pkgs = FlowOnly::canSend();
|
||||
float CUBIC::getWindow(void) const {
|
||||
return std::min<float>(getCWnD(), FlowOnly::getWindow());
|
||||
}
|
||||
|
||||
int64_t CUBIC::canSend(float time_delta) {
|
||||
const auto fspace_pkgs = FlowOnly::canSend(time_delta);
|
||||
|
||||
updateReductionTimer(time_delta);
|
||||
|
||||
if (fspace_pkgs == 0u) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
const int64_t cspace_bytes = getCWnD() - _in_flight_bytes;
|
||||
const auto window = getCWnD();
|
||||
int64_t cspace_bytes = window - _in_flight_bytes;
|
||||
if (cspace_bytes < MAXIMUM_SEGMENT_DATA_SIZE) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
// also limit to max sendrate per tick, which is usually smaller than window
|
||||
// this is mostly to prevent spikes on empty windows
|
||||
const auto rate = window / getCurrentDelay();
|
||||
|
||||
// we dont want this limit to fall below atleast 1 segment
|
||||
const int64_t max_bytes_per_tick = std::max<int64_t>(rate * time_delta + 0.5f, MAXIMUM_SEGMENT_SIZE);
|
||||
cspace_bytes = std::min<int64_t>(cspace_bytes, max_bytes_per_tick);
|
||||
|
||||
// limit to whole packets
|
||||
size_t cspace_pkgs = std::floor(cspace_bytes / MAXIMUM_SEGMENT_DATA_SIZE) * MAXIMUM_SEGMENT_DATA_SIZE;
|
||||
int64_t cspace_pkgs = (cspace_bytes / MAXIMUM_SEGMENT_DATA_SIZE) * MAXIMUM_SEGMENT_DATA_SIZE;
|
||||
|
||||
return std::min(cspace_pkgs, fspace_pkgs);
|
||||
}
|
||||
|
@ -2,13 +2,9 @@
|
||||
|
||||
#include "./flow_only.hpp"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
struct CUBIC : public FlowOnly {
|
||||
//using clock = std::chrono::steady_clock;
|
||||
|
||||
public: // config
|
||||
static constexpr float BETA {0.7f};
|
||||
static constexpr float BETA {0.8f};
|
||||
static constexpr float SCALING_CONSTANT {0.4f};
|
||||
static constexpr float RTT_EMA_ALPHA = 0.1f; // 0.1 is very smooth, might need more
|
||||
|
||||
@ -16,37 +12,26 @@ struct CUBIC : public FlowOnly {
|
||||
// window size before last reduciton
|
||||
double _window_max {2.f * MAXIMUM_SEGMENT_SIZE}; // start with mss*2
|
||||
//double _window_last_max {2.f * MAXIMUM_SEGMENT_SIZE};
|
||||
double _time_point_reduction {getTimeNow()};
|
||||
|
||||
double _time_since_reduction {12.f}; // warm start
|
||||
|
||||
private:
|
||||
void updateReductionTimer(float time_delta);
|
||||
void resetReductionTimer(void);
|
||||
|
||||
float getCWnD(void) const;
|
||||
|
||||
// moving avg over the last few delay samples
|
||||
// VERY sensitive to bundling acks
|
||||
//float getCurrentDelay(void) const;
|
||||
|
||||
//void addRTT(float new_delay);
|
||||
|
||||
void onCongestion(void) override;
|
||||
|
||||
public: // api
|
||||
CUBIC(size_t maximum_segment_data_size) : FlowOnly(maximum_segment_data_size) {}
|
||||
virtual ~CUBIC(void) {}
|
||||
|
||||
float getWindow(void) const override;
|
||||
|
||||
// TODO: api for how much data we should send
|
||||
// take time since last sent into account
|
||||
// respect max_byterate_allowed
|
||||
size_t canSend(void) override;
|
||||
|
||||
// get the list of timed out seq_ids
|
||||
//std::vector<SeqIDType> getTimeouts(void) const override;
|
||||
|
||||
public: // callbacks
|
||||
// data size is without overhead
|
||||
//void onSent(SeqIDType seq, size_t data_size) override;
|
||||
|
||||
//void onAck(std::vector<SeqIDType> seqs) override;
|
||||
|
||||
// if discard, not resent, not inflight
|
||||
//void onLoss(SeqIDType seq, bool discard) override;
|
||||
int64_t canSend(float time_delta) override;
|
||||
};
|
||||
|
||||
|
@ -6,10 +6,16 @@
|
||||
#include <algorithm>
|
||||
|
||||
float FlowOnly::getCurrentDelay(void) const {
|
||||
return std::min(_rtt_ema, RTT_MAX);
|
||||
// below 1ms is useless
|
||||
return std::clamp(_rtt_ema, 0.001f, RTT_MAX);
|
||||
}
|
||||
|
||||
void FlowOnly::addRTT(float new_delay) {
|
||||
if (new_delay > _rtt_ema * RTT_UP_MAX) {
|
||||
// too large a jump up, to be taken into account
|
||||
return;
|
||||
}
|
||||
|
||||
// lerp(new_delay, rtt_ema, 0.1)
|
||||
_rtt_ema = RTT_EMA_ALPHA * new_delay + (1.f - RTT_EMA_ALPHA) * _rtt_ema;
|
||||
}
|
||||
@ -23,7 +29,40 @@ void FlowOnly::updateWindow(void) {
|
||||
_fwnd = std::max(_fwnd, 2.f * MAXIMUM_SEGMENT_DATA_SIZE);
|
||||
}
|
||||
|
||||
size_t FlowOnly::canSend(void) {
|
||||
void FlowOnly::updateCongestion(void) {
|
||||
updateWindow();
|
||||
const auto tmp_window = getWindow();
|
||||
// packet window * 0.3
|
||||
// but atleast 4
|
||||
int32_t max_consecutive_events = std::clamp<int32_t>(
|
||||
(tmp_window/MAXIMUM_SEGMENT_DATA_SIZE) * 0.3f,
|
||||
4,
|
||||
50 // limit TODO: fix idle/time starved algo
|
||||
);
|
||||
// TODO: magic number
|
||||
|
||||
#if 0
|
||||
std::cout << "NGC_FT1 Flow: pkg out of order"
|
||||
<< " w:" << tmp_window
|
||||
<< " pw:" << tmp_window/MAXIMUM_SEGMENT_DATA_SIZE
|
||||
<< " coe:" << _consecutive_events
|
||||
<< " mcoe:" << max_consecutive_events
|
||||
<< "\n";
|
||||
#endif
|
||||
|
||||
if (_consecutive_events > max_consecutive_events) {
|
||||
//std::cout << "CONGESTION! NGC_FT1 flow: pkg out of order\n";
|
||||
onCongestion();
|
||||
|
||||
// TODO: set _consecutive_events to zero?
|
||||
}
|
||||
}
|
||||
|
||||
float FlowOnly::getWindow(void) const {
|
||||
return _fwnd;
|
||||
}
|
||||
|
||||
int64_t FlowOnly::canSend(float time_delta) {
|
||||
if (_in_flight.empty()) {
|
||||
assert(_in_flight_bytes == 0);
|
||||
return MAXIMUM_SEGMENT_DATA_SIZE;
|
||||
@ -31,16 +70,17 @@ size_t FlowOnly::canSend(void) {
|
||||
|
||||
updateWindow();
|
||||
|
||||
const int64_t fspace = _fwnd - _in_flight_bytes;
|
||||
int64_t fspace = _fwnd - _in_flight_bytes;
|
||||
if (fspace < MAXIMUM_SEGMENT_DATA_SIZE) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
// limit to whole packets
|
||||
size_t space = std::floor(fspace / MAXIMUM_SEGMENT_DATA_SIZE)
|
||||
* MAXIMUM_SEGMENT_DATA_SIZE;
|
||||
// also limit to max sendrate per tick, which is usually smaller than window
|
||||
// this is mostly to prevent spikes on empty windows
|
||||
fspace = std::min<int64_t>(fspace, max_byterate_allowed * time_delta + 0.5f);
|
||||
|
||||
return space;
|
||||
// limit to whole packets
|
||||
return (fspace / MAXIMUM_SEGMENT_DATA_SIZE) * MAXIMUM_SEGMENT_DATA_SIZE;
|
||||
}
|
||||
|
||||
std::vector<FlowOnly::SeqIDType> FlowOnly::getTimeouts(void) const {
|
||||
@ -49,7 +89,7 @@ std::vector<FlowOnly::SeqIDType> FlowOnly::getTimeouts(void) const {
|
||||
// after 3 rtt delay, we trigger timeout
|
||||
const auto now_adjusted = getTimeNow() - getCurrentDelay()*3.f;
|
||||
|
||||
for (const auto& [seq, time_stamp, size] : _in_flight) {
|
||||
for (const auto& [seq, time_stamp, size, _] : _in_flight) {
|
||||
if (now_adjusted > time_stamp) {
|
||||
list.push_back(seq);
|
||||
}
|
||||
@ -58,16 +98,35 @@ std::vector<FlowOnly::SeqIDType> FlowOnly::getTimeouts(void) const {
|
||||
return list;
|
||||
}
|
||||
|
||||
int64_t FlowOnly::inFlightCount(void) const {
|
||||
return _in_flight.size();
|
||||
}
|
||||
|
||||
int64_t FlowOnly::inFlightBytes(void) const {
|
||||
return _in_flight_bytes;
|
||||
}
|
||||
|
||||
void FlowOnly::onSent(SeqIDType seq, size_t data_size) {
|
||||
if constexpr (true) {
|
||||
size_t sum {0u};
|
||||
for (const auto& it : _in_flight) {
|
||||
assert(std::get<0>(it) != seq);
|
||||
assert(it.id != seq);
|
||||
sum += it.bytes;
|
||||
}
|
||||
assert(_in_flight_bytes == sum);
|
||||
}
|
||||
|
||||
_in_flight.push_back({seq, getTimeNow(), data_size + SEGMENT_OVERHEAD});
|
||||
_in_flight_bytes += data_size + SEGMENT_OVERHEAD;
|
||||
//_recently_sent_bytes += data_size + SEGMENT_OVERHEAD;
|
||||
const auto& new_entry = _in_flight.emplace_back(
|
||||
FlyingBunch{
|
||||
seq,
|
||||
static_cast<float>(getTimeNow()),
|
||||
data_size + SEGMENT_OVERHEAD,
|
||||
false
|
||||
}
|
||||
);
|
||||
_in_flight_bytes += new_entry.bytes;
|
||||
|
||||
_time_point_last_update = getTimeNow();
|
||||
}
|
||||
|
||||
void FlowOnly::onAck(std::vector<SeqIDType> seqs) {
|
||||
@ -78,28 +137,31 @@ void FlowOnly::onAck(std::vector<SeqIDType> seqs) {
|
||||
|
||||
const auto now {getTimeNow()};
|
||||
|
||||
_time_point_last_update = now;
|
||||
|
||||
// first seq in seqs is the actual value, all extra are for redundency
|
||||
{ // skip in ack is congestion event
|
||||
// 1. look at primary ack of packet
|
||||
auto it = std::find_if(_in_flight.begin(), _in_flight.end(), [seq = seqs.front()](const auto& v) -> bool {
|
||||
return std::get<0>(v) == seq;
|
||||
return v.id == seq;
|
||||
});
|
||||
if (it != _in_flight.end()) {
|
||||
if (it != _in_flight.begin()) {
|
||||
if (it != _in_flight.end() && !it->ignore) {
|
||||
// find first non ignore, it should be the expected
|
||||
auto first_it = std::find_if_not(_in_flight.cbegin(), _in_flight.cend(), [](const auto& v) -> bool { return v.ignore; });
|
||||
|
||||
if (first_it != _in_flight.cend() && it != first_it) {
|
||||
// not next expected seq -> skip detected
|
||||
|
||||
std::cout << "CONGESTION out of order\n";
|
||||
onCongestion();
|
||||
//if (getTimeNow() >= _last_congestion_event + _last_congestion_rtt) {
|
||||
//_recently_lost_data = true;
|
||||
//_last_congestion_event = getTimeNow();
|
||||
//_last_congestion_rtt = getCurrentDelay();
|
||||
//}
|
||||
_consecutive_events++;
|
||||
it->ignore = true; // only handle once
|
||||
|
||||
updateCongestion();
|
||||
} else {
|
||||
// only mesure delay, if not a congestion
|
||||
addRTT(now - std::get<1>(*it));
|
||||
addRTT(now - it->timestamp);
|
||||
_consecutive_events = 0;
|
||||
}
|
||||
} else {
|
||||
} else { // TOOD: if ! ignore too
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
#if 0
|
||||
// assume we got a duplicated packet
|
||||
@ -111,14 +173,14 @@ void FlowOnly::onAck(std::vector<SeqIDType> seqs) {
|
||||
|
||||
for (const auto& seq : seqs) {
|
||||
auto it = std::find_if(_in_flight.begin(), _in_flight.end(), [seq](const auto& v) -> bool {
|
||||
return std::get<0>(v) == seq;
|
||||
return v.id == seq;
|
||||
});
|
||||
|
||||
if (it == _in_flight.end()) {
|
||||
continue; // not found, ignore
|
||||
} else {
|
||||
//most_recent = std::max(most_recent, std::get<1>(*it));
|
||||
_in_flight_bytes -= std::get<2>(*it);
|
||||
_in_flight_bytes -= it->bytes;
|
||||
assert(_in_flight_bytes >= 0);
|
||||
//_recently_acked_data += std::get<2>(*it);
|
||||
_in_flight.erase(it);
|
||||
@ -128,8 +190,8 @@ void FlowOnly::onAck(std::vector<SeqIDType> seqs) {
|
||||
|
||||
void FlowOnly::onLoss(SeqIDType seq, bool discard) {
|
||||
auto it = std::find_if(_in_flight.begin(), _in_flight.end(), [seq](const auto& v) -> bool {
|
||||
assert(!std::isnan(std::get<1>(v)));
|
||||
return std::get<0>(v) == seq;
|
||||
assert(!std::isnan(v.timestamp));
|
||||
return v.id == seq;
|
||||
});
|
||||
|
||||
if (it == _in_flight.end()) {
|
||||
@ -137,24 +199,27 @@ void FlowOnly::onLoss(SeqIDType seq, bool discard) {
|
||||
return; // not found, ignore ??
|
||||
}
|
||||
|
||||
std::cerr << "FLOW loss\n";
|
||||
//std::cerr << "FLOW loss\n";
|
||||
|
||||
// "if data lost is not to be retransmitted"
|
||||
if (discard) {
|
||||
_in_flight_bytes -= std::get<2>(*it);
|
||||
_in_flight_bytes -= it->bytes;
|
||||
assert(_in_flight_bytes >= 0);
|
||||
_in_flight.erase(it);
|
||||
} else {
|
||||
// and not take into rtt
|
||||
it->timestamp = getTimeNow();
|
||||
it->ignore = true;
|
||||
}
|
||||
// TODO: reset timestamp?
|
||||
|
||||
#if 0 // temporarily disable ce for timeout
|
||||
// at most once per rtt?
|
||||
// TODO: use delay at event instead
|
||||
if (getTimeNow() >= _last_congestion_event + _last_congestion_rtt) {
|
||||
_recently_lost_data = true;
|
||||
_last_congestion_event = getTimeNow();
|
||||
_last_congestion_rtt = getCurrentDelay();
|
||||
// usually after data arrived out-of-order/duplicate
|
||||
if (!it->ignore) {
|
||||
it->ignore = true; // only handle once
|
||||
//_consecutive_events++;
|
||||
|
||||
//updateCongestion();
|
||||
// this is usually a safe indicator for congestion/maxed connection
|
||||
onCongestion();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -4,24 +4,16 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
struct FlowOnly : public CCAI {
|
||||
protected:
|
||||
using clock = std::chrono::steady_clock;
|
||||
|
||||
public: // config
|
||||
static constexpr float RTT_EMA_ALPHA = 0.1f; // might need over time
|
||||
static constexpr float RTT_EMA_ALPHA = 0.001f; // might need change over time
|
||||
static constexpr float RTT_UP_MAX = 3.0f; // how much larger a delay can be to be taken into account
|
||||
static constexpr float RTT_MAX = 2.f; // 2 sec is probably too much
|
||||
|
||||
//float max_byterate_allowed {100.f*1024*1024}; // 100MiB/s
|
||||
float max_byterate_allowed {10.f*1024*1024}; // 10MiB/s
|
||||
//float max_byterate_allowed {1.f*1024*1024}; // 1MiB/s
|
||||
//float max_byterate_allowed {0.6f*1024*1024}; // 600KiB/s
|
||||
//float max_byterate_allowed {0.5f*1024*1024}; // 500KiB/s
|
||||
//float max_byterate_allowed {0.05f*1024*1024}; // 50KiB/s
|
||||
//float max_byterate_allowed {0.15f*1024*1024}; // 150KiB/s
|
||||
|
||||
protected:
|
||||
// initialize to low value, will get corrected very fast
|
||||
float _fwnd {0.01f * max_byterate_allowed}; // in bytes
|
||||
@ -30,11 +22,24 @@ struct FlowOnly : public CCAI {
|
||||
float _rtt_ema {0.1f};
|
||||
|
||||
// list of sequence ids and timestamps of when they where sent (and payload size)
|
||||
std::vector<std::tuple<SeqIDType, float, size_t>> _in_flight;
|
||||
struct FlyingBunch {
|
||||
SeqIDType id;
|
||||
float timestamp;
|
||||
size_t bytes;
|
||||
|
||||
// set to true if counted as ce or resent due to timeout
|
||||
bool ignore {false};
|
||||
};
|
||||
std::vector<FlyingBunch> _in_flight;
|
||||
int64_t _in_flight_bytes {0};
|
||||
|
||||
int32_t _consecutive_events {0};
|
||||
|
||||
clock::time_point _time_start_offset;
|
||||
|
||||
// used to clamp growth rate in the void
|
||||
double _time_point_last_update {getTimeNow()};
|
||||
|
||||
protected:
|
||||
// make values relative to algo start for readability (and precision)
|
||||
// get timestamp in seconds
|
||||
@ -44,7 +49,10 @@ struct FlowOnly : public CCAI {
|
||||
|
||||
// moving avg over the last few delay samples
|
||||
// VERY sensitive to bundling acks
|
||||
float getCurrentDelay(void) const;
|
||||
float getCurrentDelay(void) const override;
|
||||
|
||||
// call updateWindow() to update this value
|
||||
float getWindow(void) const override;
|
||||
|
||||
void addRTT(float new_delay);
|
||||
|
||||
@ -52,17 +60,24 @@ struct FlowOnly : public CCAI {
|
||||
|
||||
virtual void onCongestion(void) {};
|
||||
|
||||
// internal logic, calls the onCongestion() event
|
||||
void updateCongestion(void);
|
||||
|
||||
public: // api
|
||||
FlowOnly(size_t maximum_segment_data_size) : CCAI(maximum_segment_data_size) {}
|
||||
virtual ~FlowOnly(void) {}
|
||||
|
||||
// TODO: api for how much data we should send
|
||||
// take time since last sent into account
|
||||
// respect max_byterate_allowed
|
||||
size_t canSend(void) override;
|
||||
int64_t canSend(float time_delta) override;
|
||||
|
||||
// get the list of timed out seq_ids
|
||||
std::vector<SeqIDType> getTimeouts(void) const override;
|
||||
|
||||
int64_t inFlightCount(void) const override;
|
||||
int64_t inFlightBytes(void) const override;
|
||||
|
||||
public: // callbacks
|
||||
// data size is without overhead
|
||||
void onSent(SeqIDType seq, size_t data_size) override;
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <deque>
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <tuple>
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
@ -19,7 +20,7 @@ LEDBAT::LEDBAT(size_t maximum_segment_data_size) : CCAI(maximum_segment_data_siz
|
||||
_time_start_offset = clock::now();
|
||||
}
|
||||
|
||||
size_t LEDBAT::canSend(void) {
|
||||
int64_t LEDBAT::canSend(float time_delta) {
|
||||
if (_in_flight.empty()) {
|
||||
return MAXIMUM_SEGMENT_DATA_SIZE;
|
||||
}
|
||||
@ -34,9 +35,7 @@ size_t LEDBAT::canSend(void) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
size_t space = std::ceil(std::min<float>(cspace, fspace) / MAXIMUM_SEGMENT_DATA_SIZE) * MAXIMUM_SEGMENT_DATA_SIZE;
|
||||
|
||||
return space;
|
||||
return std::ceil(std::min<float>(cspace, fspace) / MAXIMUM_SEGMENT_DATA_SIZE) * MAXIMUM_SEGMENT_DATA_SIZE;
|
||||
}
|
||||
|
||||
std::vector<LEDBAT::SeqIDType> LEDBAT::getTimeouts(void) const {
|
||||
|
@ -11,7 +11,7 @@
|
||||
// LEDBAT++: https://www.ietf.org/archive/id/draft-irtf-iccrg-ledbat-plus-plus-01.txt
|
||||
|
||||
// LEDBAT++ implementation
|
||||
struct LEDBAT : public CCAI{
|
||||
struct LEDBAT : public CCAI {
|
||||
public: // config
|
||||
#if 0
|
||||
using SeqIDType = std::pair<uint8_t, uint16_t>; // tf_id, seq_id
|
||||
@ -47,21 +47,20 @@ struct LEDBAT : public CCAI{
|
||||
|
||||
//static constexpr size_t rtt_buffer_size_max {2000};
|
||||
|
||||
float max_byterate_allowed {10*1024*1024}; // 10MiB/s
|
||||
|
||||
public:
|
||||
LEDBAT(size_t maximum_segment_data_size);
|
||||
virtual ~LEDBAT(void) {}
|
||||
|
||||
// return the current believed window in bytes of how much data can be inflight,
|
||||
// without overstepping the delay requirement
|
||||
float getCWnD(void) const {
|
||||
float getWindow(void) const override {
|
||||
return _cwnd;
|
||||
}
|
||||
|
||||
// TODO: api for how much data we should send
|
||||
// take time since last sent into account
|
||||
// respect max_byterate_allowed
|
||||
size_t canSend(void) override;
|
||||
int64_t canSend(float time_delta) override;
|
||||
|
||||
// get the list of timed out seq_ids
|
||||
std::vector<SeqIDType> getTimeouts(void) const override;
|
||||
@ -86,7 +85,7 @@ struct LEDBAT : public CCAI{
|
||||
|
||||
// moving avg over the last few delay samples
|
||||
// VERY sensitive to bundling acks
|
||||
float getCurrentDelay(void) const;
|
||||
float getCurrentDelay(void) const override;
|
||||
|
||||
void addRTT(float new_delay);
|
||||
|
||||
|
@ -1,149 +1,21 @@
|
||||
#include "./ngcft1.hpp"
|
||||
|
||||
#include <solanaceae/toxcore/utils.hpp>
|
||||
#include "./flow_only.hpp"
|
||||
#include "./cubic.hpp"
|
||||
#include "./ledbat.hpp"
|
||||
|
||||
#include <solanaceae/util/utils.hpp>
|
||||
|
||||
#include <sodium.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
bool NGCFT1::sendPKG_FT1_REQUEST(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
) {
|
||||
// - 1 byte packet id
|
||||
// - 4 byte file_kind
|
||||
// - X bytes file_id
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_REQUEST));
|
||||
for (size_t i = 0; i < sizeof(file_kind); i++) {
|
||||
pkg.push_back((file_kind>>(i*8)) & 0xff);
|
||||
}
|
||||
for (size_t i = 0; i < file_id_size; i++) {
|
||||
pkg.push_back(file_id[i]);
|
||||
}
|
||||
|
||||
// lossless
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCFT1::sendPKG_FT1_INIT(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
uint64_t file_size,
|
||||
uint8_t transfer_id,
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
) {
|
||||
// - 1 byte packet id
|
||||
// - 4 byte (file_kind)
|
||||
// - 8 bytes (data size)
|
||||
// - 1 byte (temporary_file_tf_id, for this peer only, technically just a prefix to distinguish between simultainious fts)
|
||||
// - X bytes (file_kind dependent id, differnt sizes)
|
||||
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_INIT));
|
||||
for (size_t i = 0; i < sizeof(file_kind); i++) {
|
||||
pkg.push_back((file_kind>>(i*8)) & 0xff);
|
||||
}
|
||||
for (size_t i = 0; i < sizeof(file_size); i++) {
|
||||
pkg.push_back((file_size>>(i*8)) & 0xff);
|
||||
}
|
||||
pkg.push_back(transfer_id);
|
||||
for (size_t i = 0; i < file_id_size; i++) {
|
||||
pkg.push_back(file_id[i]);
|
||||
}
|
||||
|
||||
// lossless
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCFT1::sendPKG_FT1_INIT_ACK(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint8_t transfer_id
|
||||
) {
|
||||
// - 1 byte packet id
|
||||
// - 1 byte transfer_id
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_INIT_ACK));
|
||||
pkg.push_back(transfer_id);
|
||||
|
||||
// lossless
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCFT1::sendPKG_FT1_DATA(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint8_t transfer_id,
|
||||
uint16_t sequence_id,
|
||||
const uint8_t* data, size_t data_size
|
||||
) {
|
||||
assert(data_size > 0);
|
||||
|
||||
// TODO
|
||||
// check header_size+data_size <= max pkg size
|
||||
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_DATA));
|
||||
pkg.push_back(transfer_id);
|
||||
pkg.push_back(sequence_id & 0xff);
|
||||
pkg.push_back((sequence_id >> (1*8)) & 0xff);
|
||||
|
||||
// TODO: optimize
|
||||
for (size_t i = 0; i < data_size; i++) {
|
||||
pkg.push_back(data[i]);
|
||||
}
|
||||
|
||||
// lossy
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, false, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCFT1::sendPKG_FT1_DATA_ACK(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint8_t transfer_id,
|
||||
const uint16_t* seq_ids, size_t seq_ids_size
|
||||
) {
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_DATA_ACK));
|
||||
pkg.push_back(transfer_id);
|
||||
|
||||
// TODO: optimize
|
||||
for (size_t i = 0; i < seq_ids_size; i++) {
|
||||
pkg.push_back(seq_ids[i] & 0xff);
|
||||
pkg.push_back((seq_ids[i] >> (1*8)) & 0xff);
|
||||
}
|
||||
|
||||
// lossy
|
||||
return _t.toxGroupSendCustomPrivatePacket(group_number, peer_number, false, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PRIVATE_PACKET_OK;
|
||||
}
|
||||
|
||||
bool NGCFT1::sendPKG_FT1_MESSAGE(
|
||||
uint32_t group_number,
|
||||
uint32_t message_id,
|
||||
uint32_t file_kind,
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
) {
|
||||
std::vector<uint8_t> pkg;
|
||||
pkg.push_back(static_cast<uint8_t>(NGCEXT_Event::FT1_MESSAGE));
|
||||
|
||||
for (size_t i = 0; i < sizeof(message_id); i++) {
|
||||
pkg.push_back((message_id>>(i*8)) & 0xff);
|
||||
}
|
||||
for (size_t i = 0; i < sizeof(file_kind); i++) {
|
||||
pkg.push_back((file_kind>>(i*8)) & 0xff);
|
||||
}
|
||||
for (size_t i = 0; i < file_id_size; i++) {
|
||||
pkg.push_back(file_id[i]);
|
||||
}
|
||||
|
||||
// lossless
|
||||
return _t.toxGroupSendCustomPacket(group_number, true, pkg) == TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK;
|
||||
}
|
||||
|
||||
void NGCFT1::updateSendTransfer(float time_delta, uint32_t group_number, uint32_t peer_number, Group::Peer& peer, size_t idx, std::set<CCAI::SeqIDType>& timeouts_set) {
|
||||
void NGCFT1::updateSendTransfer(float time_delta, uint32_t group_number, uint32_t peer_number, Group::Peer& peer, size_t idx, std::set<CCAI::SeqIDType>& timeouts_set, int64_t& can_packet_size) {
|
||||
auto& tf_opt = peer.send_transfers.at(idx);
|
||||
assert(tf_opt.has_value());
|
||||
auto& tf = tf_opt.value();
|
||||
@ -168,29 +40,50 @@ void NGCFT1::updateSendTransfer(float time_delta, uint32_t group_number, uint32_
|
||||
} else {
|
||||
// timed out, resend
|
||||
std::cerr << "NGCFT1 warning: ft init timed out, resending\n";
|
||||
sendPKG_FT1_INIT(group_number, peer_number, tf.file_kind, tf.file_size, idx, tf.file_id.data(), tf.file_id.size());
|
||||
//sendPKG_FT1_INIT(group_number, peer_number, tf.file_kind, tf.file_size, idx, tf.file_id.data(), tf.file_id.size());
|
||||
_neep.send_ft1_init(group_number, peer_number, tf.file_kind, tf.file_size, idx, tf.file_id.data(), tf.file_id.size());
|
||||
tf.inits_sent++;
|
||||
tf.time_since_activity = 0.f;
|
||||
}
|
||||
}
|
||||
//break;
|
||||
return;
|
||||
case State::SENDING: {
|
||||
tf.ssb.for_each(time_delta, [&](uint16_t id, const std::vector<uint8_t>& data, float& time_since_activity) {
|
||||
// no ack after 5 sec -> resend
|
||||
//if (time_since_activity >= ngc_ft1_ctx->options.sending_resend_without_ack_after) {
|
||||
if (timeouts_set.count({idx, id})) {
|
||||
// TODO: can fail
|
||||
sendPKG_FT1_DATA(group_number, peer_number, idx, id, data.data(), data.size());
|
||||
peer.cca->onLoss({idx, id}, false);
|
||||
time_since_activity = 0.f;
|
||||
timeouts_set.erase({idx, id});
|
||||
case State::FINISHING: // we still have unacked packets
|
||||
tf.ssb.for_each(time_delta, [&](uint16_t id, const std::vector<uint8_t>& data, float& time_since_activity) {
|
||||
if (can_packet_size >= data.size() && timeouts_set.count({idx, id})) {
|
||||
_neep.send_ft1_data(group_number, peer_number, idx, id, data.data(), data.size());
|
||||
peer.cca->onLoss({idx, id}, false);
|
||||
time_since_activity = 0.f;
|
||||
timeouts_set.erase({idx, id});
|
||||
can_packet_size -= data.size();
|
||||
}
|
||||
});
|
||||
if (tf.time_since_activity >= sending_give_up_after) {
|
||||
// no ack after 30sec, close ft
|
||||
std::cerr << "NGCFT1 warning: sending ft finishing timed out, deleting\n";
|
||||
dispatch(
|
||||
NGCFT1_Event::send_done,
|
||||
Events::NGCFT1_send_done{
|
||||
group_number, peer_number,
|
||||
static_cast<uint8_t>(idx),
|
||||
}
|
||||
);
|
||||
|
||||
// clean up cca
|
||||
tf.ssb.for_each(time_delta, [&](uint16_t id, const std::vector<uint8_t>& data, float& time_since_activity) {
|
||||
peer.cca->onLoss({idx, id}, true);
|
||||
timeouts_set.erase({idx, id});
|
||||
});
|
||||
|
||||
if (tf.time_since_activity >= sending_give_up_after) {
|
||||
tf_opt.reset();
|
||||
}
|
||||
break;
|
||||
case State::SENDING: {
|
||||
// first handle overall timeout (could otherwise do resends directly before, which is useless)
|
||||
// timeout increases with active transfers (otherwise we could starve them)
|
||||
if (tf.time_since_activity >= (sending_give_up_after * peer.active_send_transfers)) {
|
||||
// no ack after 30sec, close ft
|
||||
std::cerr << "NGCFT1 warning: sending ft in progress timed out, deleting\n";
|
||||
std::cerr << "NGCFT1 warning: sending ft in progress timed out, deleting (ifc:" << peer.cca->inFlightCount() << ")\n";
|
||||
dispatch(
|
||||
NGCFT1_Event::send_done,
|
||||
Events::NGCFT1_send_done{
|
||||
@ -210,22 +103,23 @@ void NGCFT1::updateSendTransfer(float time_delta, uint32_t group_number, uint32_
|
||||
return;
|
||||
}
|
||||
|
||||
// do resends
|
||||
tf.ssb.for_each(time_delta, [&](uint16_t id, const std::vector<uint8_t>& data, float& time_since_activity) {
|
||||
if (can_packet_size >= data.size() && time_since_activity >= peer.cca->getCurrentDelay() && timeouts_set.count({idx, id})) {
|
||||
// TODO: can fail
|
||||
_neep.send_ft1_data(group_number, peer_number, idx, id, data.data(), data.size());
|
||||
peer.cca->onLoss({idx, id}, false);
|
||||
time_since_activity = 0.f;
|
||||
timeouts_set.erase({idx, id});
|
||||
can_packet_size -= data.size();
|
||||
}
|
||||
});
|
||||
|
||||
// if chunks in flight < window size (2)
|
||||
//while (tf.ssb.size() < ngc_ft1_ctx->options.packet_window_size) {
|
||||
int64_t can_packet_size {static_cast<int64_t>(peer.cca->canSend())};
|
||||
//if (can_packet_size) {
|
||||
//std::cerr << "FT: can_packet_size: " << can_packet_size;
|
||||
//}
|
||||
size_t count {0};
|
||||
while (can_packet_size > 0 && tf.file_size > 0) {
|
||||
std::vector<uint8_t> new_data;
|
||||
|
||||
// TODO: parameterize packet size? -> only if JF increases lossy packet size >:)
|
||||
//size_t chunk_size = std::min<size_t>(496u, tf.file_size - tf.file_size_current);
|
||||
//size_t chunk_size = std::min<size_t>(can_packet_size, tf.file_size - tf.file_size_current);
|
||||
size_t chunk_size = std::min<size_t>({
|
||||
//496u,
|
||||
//996u,
|
||||
peer.cca->MAXIMUM_SEGMENT_DATA_SIZE,
|
||||
static_cast<size_t>(can_packet_size),
|
||||
tf.file_size - tf.file_size_current
|
||||
@ -237,14 +131,6 @@ void NGCFT1::updateSendTransfer(float time_delta, uint32_t group_number, uint32_
|
||||
|
||||
new_data.resize(chunk_size);
|
||||
|
||||
//ngc_ft1_ctx->cb_send_data[tf.file_kind](
|
||||
//tox,
|
||||
//group_number, peer_number,
|
||||
//idx,
|
||||
//tf.file_size_current,
|
||||
//new_data.data(), new_data.size(),
|
||||
//ngc_ft1_ctx->ud_send_data.count(tf.file_kind) ? ngc_ft1_ctx->ud_send_data.at(tf.file_kind) : nullptr
|
||||
//);
|
||||
assert(idx <= 0xffu);
|
||||
// TODO: check return value
|
||||
dispatch(
|
||||
@ -258,72 +144,90 @@ void NGCFT1::updateSendTransfer(float time_delta, uint32_t group_number, uint32_
|
||||
);
|
||||
|
||||
uint16_t seq_id = tf.ssb.add(std::move(new_data));
|
||||
sendPKG_FT1_DATA(group_number, peer_number, idx, seq_id, tf.ssb.entries.at(seq_id).data.data(), tf.ssb.entries.at(seq_id).data.size());
|
||||
peer.cca->onSent({idx, seq_id}, chunk_size);
|
||||
|
||||
#if defined(EXTRA_LOGGING) && EXTRA_LOGGING == 1
|
||||
fprintf(stderr, "FT: sent data size: %ld (seq %d)\n", chunk_size, seq_id);
|
||||
#endif
|
||||
const bool sent = _neep.send_ft1_data(group_number, peer_number, idx, seq_id, tf.ssb.entries.at(seq_id).data.data(), tf.ssb.entries.at(seq_id).data.size());
|
||||
if (sent) {
|
||||
peer.cca->onSent({idx, seq_id}, chunk_size);
|
||||
} else {
|
||||
std::cerr << "NGCFT1: failed to send packet (queue full?) --------------\n";
|
||||
peer.cca->onLoss({idx, seq_id}, false); // HACK: fake congestion event
|
||||
// TODO: onCongestion
|
||||
can_packet_size = 0;
|
||||
}
|
||||
|
||||
tf.file_size_current += chunk_size;
|
||||
can_packet_size -= chunk_size;
|
||||
count++;
|
||||
}
|
||||
//if (count) {
|
||||
//std::cerr << " split over " << count << "\n";
|
||||
//}
|
||||
}
|
||||
break;
|
||||
case State::FINISHING: // we still have unacked packets
|
||||
tf.ssb.for_each(time_delta, [&](uint16_t id, const std::vector<uint8_t>& data, float& time_since_activity) {
|
||||
// no ack after 5 sec -> resend
|
||||
//if (time_since_activity >= ngc_ft1_ctx->options.sending_resend_without_ack_after) {
|
||||
if (timeouts_set.count({idx, id})) {
|
||||
sendPKG_FT1_DATA(group_number, peer_number, idx, id, data.data(), data.size());
|
||||
peer.cca->onLoss({idx, id}, false);
|
||||
time_since_activity = 0.f;
|
||||
timeouts_set.erase({idx, id});
|
||||
}
|
||||
});
|
||||
if (tf.time_since_activity >= sending_give_up_after) {
|
||||
// no ack after 30sec, close ft
|
||||
// TODO: notify app
|
||||
std::cerr << "NGCFT1 warning: sending ft finishing timed out, deleting\n";
|
||||
|
||||
// clean up cca
|
||||
tf.ssb.for_each(time_delta, [&](uint16_t id, const std::vector<uint8_t>& data, float& time_since_activity) {
|
||||
peer.cca->onLoss({idx, id}, true);
|
||||
timeouts_set.erase({idx, id});
|
||||
});
|
||||
|
||||
tf_opt.reset();
|
||||
}
|
||||
break;
|
||||
default: // invalid state, delete
|
||||
std::cerr << "NGCFT1 error: ft in invalid state, deleting\n";
|
||||
assert(false && "ft in invalid state");
|
||||
tf_opt.reset();
|
||||
//continue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void NGCFT1::iteratePeer(float time_delta, uint32_t group_number, uint32_t peer_number, Group::Peer& peer) {
|
||||
auto timeouts = peer.cca->getTimeouts();
|
||||
std::set<CCAI::SeqIDType> timeouts_set{timeouts.cbegin(), timeouts.cend()};
|
||||
if (peer.cca) {
|
||||
auto timeouts = peer.cca->getTimeouts();
|
||||
std::set<CCAI::SeqIDType> timeouts_set{timeouts.cbegin(), timeouts.cend()};
|
||||
|
||||
for (size_t idx = 0; idx < peer.send_transfers.size(); idx++) {
|
||||
if (peer.send_transfers.at(idx).has_value()) {
|
||||
updateSendTransfer(time_delta, group_number, peer_number, peer, idx, timeouts_set);
|
||||
int64_t can_packet_size {peer.cca->canSend(time_delta)}; // might get more space while iterating (time)
|
||||
|
||||
// get number current running transfers TODO: improve
|
||||
peer.active_send_transfers = 0;
|
||||
for (const auto& it : peer.send_transfers) {
|
||||
if (it.has_value()) {
|
||||
peer.active_send_transfers++;
|
||||
}
|
||||
}
|
||||
|
||||
// change iterate start position to not starve transfers in the back
|
||||
size_t iterated_count = 0;
|
||||
bool last_send_found = false;
|
||||
for (size_t idx = peer.next_send_transfer_send_idx; iterated_count < peer.send_transfers.size(); idx++, iterated_count++) {
|
||||
idx = idx % peer.send_transfers.size();
|
||||
|
||||
if (peer.send_transfers.at(idx).has_value()) {
|
||||
if (!last_send_found && can_packet_size <= 0) {
|
||||
peer.next_send_transfer_send_idx = idx;
|
||||
last_send_found = true; // only set once
|
||||
}
|
||||
updateSendTransfer(time_delta, group_number, peer_number, peer, idx, timeouts_set, can_packet_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: receiving tranfers?
|
||||
}
|
||||
|
||||
const CCAI* NGCFT1::getPeerCCA(
|
||||
uint32_t group_number,
|
||||
uint32_t peer_number
|
||||
) const {
|
||||
auto group_it = groups.find(group_number);
|
||||
if (group_it == groups.end()) {
|
||||
return nullptr;;
|
||||
}
|
||||
|
||||
auto peer_it = group_it->second.peers.find(peer_number);
|
||||
if (peer_it == group_it->second.peers.end()) {
|
||||
return nullptr;;
|
||||
}
|
||||
|
||||
const auto& cca_ptr = peer_it->second.cca;
|
||||
|
||||
if (!cca_ptr) {
|
||||
return nullptr;;
|
||||
}
|
||||
|
||||
return cca_ptr.get();
|
||||
}
|
||||
|
||||
NGCFT1::NGCFT1(
|
||||
ToxI& t,
|
||||
ToxEventProviderI& tep,
|
||||
NGCEXTEventProviderI& neep
|
||||
NGCEXTEventProvider& neep
|
||||
) : _t(t), _tep(tep), _neep(neep)
|
||||
{
|
||||
_neep.subscribe(this, NGCEXT_Event::FT1_REQUEST);
|
||||
@ -333,15 +237,47 @@ NGCFT1::NGCFT1(
|
||||
_neep.subscribe(this, NGCEXT_Event::FT1_DATA_ACK);
|
||||
_neep.subscribe(this, NGCEXT_Event::FT1_MESSAGE);
|
||||
|
||||
_tep.subscribe(this, Tox_Event::TOX_EVENT_GROUP_PEER_EXIT);
|
||||
_tep.subscribe(this, Tox_Event_Type::TOX_EVENT_GROUP_PEER_EXIT);
|
||||
}
|
||||
|
||||
void NGCFT1::iterate(float time_delta) {
|
||||
float NGCFT1::iterate(float time_delta) {
|
||||
_time_since_activity += time_delta;
|
||||
bool transfer_in_progress {false};
|
||||
for (auto& [group_number, group] : groups) {
|
||||
for (auto& [peer_number, peer] : group.peers) {
|
||||
iteratePeer(time_delta, group_number, peer_number, peer);
|
||||
|
||||
// find any active transfer
|
||||
if (!transfer_in_progress) {
|
||||
for (const auto& t : peer.send_transfers) {
|
||||
if (t.has_value()) {
|
||||
transfer_in_progress = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!transfer_in_progress) {
|
||||
for (const auto& t : peer.recv_transfers) {
|
||||
if (t.has_value()) {
|
||||
transfer_in_progress = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (transfer_in_progress) {
|
||||
_time_since_activity = 0.f;
|
||||
// ~15ms for up to 1mb/s
|
||||
// ~5ms for up to 4mb/s
|
||||
return 0.005f; // 5ms
|
||||
} else if (_time_since_activity < 0.5f) {
|
||||
// bc of temporality
|
||||
return 0.025f;
|
||||
} else {
|
||||
return 1.f; // once a sec might be too little
|
||||
}
|
||||
}
|
||||
|
||||
void NGCFT1::NGC_FT1_send_request_private(
|
||||
@ -350,7 +286,7 @@ void NGCFT1::NGC_FT1_send_request_private(
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
) {
|
||||
// TODO: error check
|
||||
sendPKG_FT1_REQUEST(group_number, peer_number, file_kind, file_id, file_id_size);
|
||||
_neep.send_ft1_request(group_number, peer_number, file_kind, file_id, file_id_size);
|
||||
}
|
||||
|
||||
bool NGCFT1::NGC_FT1_send_init_private(
|
||||
@ -391,7 +327,7 @@ bool NGCFT1::NGC_FT1_send_init_private(
|
||||
}
|
||||
|
||||
// TODO: check return value
|
||||
sendPKG_FT1_INIT(group_number, peer_number, file_kind, file_size, idx, file_id, file_id_size);
|
||||
_neep.send_ft1_init(group_number, peer_number, file_kind, file_size, idx, file_id, file_id_size);
|
||||
|
||||
peer.send_transfers[idx] = Group::Peer::SendTransfer{
|
||||
file_kind,
|
||||
@ -421,12 +357,58 @@ bool NGCFT1::NGC_FT1_send_message_public(
|
||||
message_id = randombytes_random();
|
||||
|
||||
// TODO: check return value
|
||||
return sendPKG_FT1_MESSAGE(group_number, message_id, file_kind, file_id, file_id_size);
|
||||
return _neep.send_all_ft1_message(group_number, message_id, file_kind, file_id, file_id_size);
|
||||
}
|
||||
|
||||
float NGCFT1::getPeerDelay(uint32_t group_number, uint32_t peer_number) const {
|
||||
auto* cca_ptr = getPeerCCA(group_number, peer_number);
|
||||
|
||||
if (cca_ptr == nullptr) {
|
||||
return -1.f;
|
||||
}
|
||||
|
||||
return cca_ptr->getCurrentDelay();
|
||||
}
|
||||
|
||||
float NGCFT1::getPeerWindow(uint32_t group_number, uint32_t peer_number) const {
|
||||
auto* cca_ptr = getPeerCCA(group_number, peer_number);
|
||||
|
||||
if (cca_ptr == nullptr) {
|
||||
return -1.f;
|
||||
}
|
||||
|
||||
return cca_ptr->getWindow();
|
||||
}
|
||||
|
||||
int64_t NGCFT1::getPeerInFlightPackets(
|
||||
uint32_t group_number,
|
||||
uint32_t peer_number
|
||||
) const {
|
||||
auto* cca_ptr = getPeerCCA(group_number, peer_number);
|
||||
|
||||
if (cca_ptr == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cca_ptr->inFlightCount();
|
||||
}
|
||||
|
||||
int64_t NGCFT1::getPeerInFlightBytes(
|
||||
uint32_t group_number,
|
||||
uint32_t peer_number
|
||||
) const {
|
||||
auto* cca_ptr = getPeerCCA(group_number, peer_number);
|
||||
|
||||
if (cca_ptr == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cca_ptr->inFlightCount();
|
||||
}
|
||||
|
||||
bool NGCFT1::onEvent(const Events::NGCEXT_ft1_request& e) {
|
||||
//#if !NDEBUG
|
||||
std::cout << "NGCFT1: FT1_REQUEST fk:" << e.file_kind << " [" << bin2hex(e.file_id) << "]\n";
|
||||
std::cout << "NGCFT1: got FT1_REQUEST fk:" << e.file_kind << " [" << bin2hex(e.file_id) << "]\n";
|
||||
//#endif
|
||||
|
||||
// .... just rethrow??
|
||||
@ -443,7 +425,7 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_request& e) {
|
||||
|
||||
bool NGCFT1::onEvent(const Events::NGCEXT_ft1_init& e) {
|
||||
//#if !NDEBUG
|
||||
std::cout << "NGCFT1: FT1_INIT fk:" << e.file_kind << " fs:" << e.file_size << " tid:" << int(e.transfer_id) << " [" << bin2hex(e.file_id) << "]\n";
|
||||
std::cout << "NGCFT1: got FT1_INIT fk:" << e.file_kind << " fs:" << e.file_size << " tid:" << int(e.transfer_id) << " [" << bin2hex(e.file_id) << "]\n";
|
||||
//#endif
|
||||
|
||||
bool accept = false;
|
||||
@ -464,13 +446,13 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_init& e) {
|
||||
return true; // return true?
|
||||
}
|
||||
|
||||
sendPKG_FT1_INIT_ACK(e.group_number, e.peer_number, e.transfer_id);
|
||||
_neep.send_ft1_init_ack(e.group_number, e.peer_number, e.transfer_id);
|
||||
|
||||
std::cout << "NGCFT1: accepted init\n";
|
||||
|
||||
auto& peer = groups[e.group_number].peers[e.peer_number];
|
||||
if (peer.recv_transfers[e.transfer_id].has_value()) {
|
||||
std::cerr << "NGCFT1 warning: overwriting existing recv_transfer " << int(e.transfer_id) << "\n";
|
||||
std::cerr << "NGCFT1 warning: overwriting existing recv_transfer " << int(e.transfer_id) << ", other peer started new transfer on preexising\n";
|
||||
}
|
||||
|
||||
peer.recv_transfers[e.transfer_id] = Group::Peer::RecvTransfer{
|
||||
@ -487,7 +469,7 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_init& e) {
|
||||
|
||||
bool NGCFT1::onEvent(const Events::NGCEXT_ft1_init_ack& e) {
|
||||
//#if !NDEBUG
|
||||
std::cout << "NGCFT1: FT1_INIT_ACK\n";
|
||||
std::cout << "NGCFT1: got FT1_INIT_ACK mds:" << e.max_lossy_data_size << "\n";
|
||||
//#endif
|
||||
|
||||
// we now should start sending data
|
||||
@ -507,10 +489,28 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_init_ack& e) {
|
||||
|
||||
using State = Group::Peer::SendTransfer::State;
|
||||
if (transfer.state != State::INIT_SENT) {
|
||||
std::cerr << "NGCFT1 error: inti_ack but not in INIT_SENT state\n";
|
||||
std::cerr << "NGCFT1 error: init_ack but not in INIT_SENT state\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
// negotiated packet_data_size
|
||||
const auto negotiated_packet_data_size = std::min<uint32_t>(e.max_lossy_data_size, _t.toxGroupMaxCustomLossyPacketLength()-4);
|
||||
// TODO: reset cca with new pkg size
|
||||
if (!peer.cca) {
|
||||
// make random max of [1020-1220]
|
||||
const uint32_t random_max_data_size = (1024-4) + _rng()%201;
|
||||
const uint32_t randomized_negotiated_packet_data_size = std::min(negotiated_packet_data_size, random_max_data_size);
|
||||
|
||||
peer.max_packet_data_size = randomized_negotiated_packet_data_size;
|
||||
|
||||
std::cerr << "NGCFT1: creating cca with max:" << peer.max_packet_data_size << "\n";
|
||||
|
||||
peer.cca = std::make_unique<CUBIC>(peer.max_packet_data_size);
|
||||
//peer.cca = std::make_unique<LEDBAT>(peer.max_packet_data_size);
|
||||
//peer.cca = std::make_unique<FlowOnly>(peer.max_packet_data_size);
|
||||
//peer.cca->max_byterate_allowed = 1.f *1024*1024;
|
||||
}
|
||||
|
||||
// iterate will now call NGC_FT1_send_data_cb
|
||||
transfer.state = State::SENDING;
|
||||
transfer.time_since_activity = 0.f;
|
||||
@ -520,7 +520,7 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_init_ack& e) {
|
||||
|
||||
bool NGCFT1::onEvent(const Events::NGCEXT_ft1_data& e) {
|
||||
#if !NDEBUG
|
||||
std::cout << "NGCFT1: FT1_DATA\n";
|
||||
//std::cout << "NGCFT1: got FT1_DATA\n";
|
||||
#endif
|
||||
|
||||
if (e.data.empty()) {
|
||||
@ -568,7 +568,7 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_data& e) {
|
||||
// TODO: check if this caps at max acks
|
||||
if (!ack_seq_ids.empty()) {
|
||||
// TODO: check return value
|
||||
sendPKG_FT1_DATA_ACK(e.group_number, e.peer_number, e.transfer_id, ack_seq_ids.data(), ack_seq_ids.size());
|
||||
_neep.send_ft1_data_ack(e.group_number, e.peer_number, e.transfer_id, ack_seq_ids.data(), ack_seq_ids.size());
|
||||
}
|
||||
|
||||
|
||||
@ -582,6 +582,11 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_data& e) {
|
||||
e.transfer_id
|
||||
}
|
||||
);
|
||||
|
||||
// delete transfer
|
||||
// TODO: keep around for remote timeout + delay + offset, so we can be sure all acks where received
|
||||
// or implement a dedicated finished that needs to be acked
|
||||
peer.recv_transfers[e.transfer_id].reset();
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -589,7 +594,7 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_data& e) {
|
||||
|
||||
bool NGCFT1::onEvent(const Events::NGCEXT_ft1_data_ack& e) {
|
||||
#if !NDEBUG
|
||||
//std::cout << "NGCFT1: FT1_DATA_ACK\n";
|
||||
//std::cout << "NGCFT1: got FT1_DATA_ACK\n";
|
||||
#endif
|
||||
|
||||
if (!groups.count(e.group_number)) {
|
||||
@ -599,7 +604,8 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_data_ack& e) {
|
||||
|
||||
Group::Peer& peer = groups[e.group_number].peers[e.peer_number];
|
||||
if (!peer.send_transfers[e.transfer_id].has_value()) {
|
||||
std::cerr << "NGCFT1 warning: data_ack for unknown transfer\n";
|
||||
// we delete directly, packets might still be in flight (in practice they are when ce)
|
||||
//std::cerr << "NGCFT1 warning: data_ack for unknown transfer\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -625,7 +631,7 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_data_ack& e) {
|
||||
|
||||
// delete if all packets acked
|
||||
if (transfer.file_size == transfer.file_size_current && transfer.ssb.size() == 0) {
|
||||
std::cout << "NGCFT1: " << int(e.transfer_id) << " done\n";
|
||||
std::cout << "NGCFT1: " << int(e.transfer_id) << " done. wnd:" << peer.cca->getWindow() << "\n";
|
||||
dispatch(
|
||||
NGCFT1_Event::send_done,
|
||||
Events::NGCFT1_send_done{
|
||||
@ -641,7 +647,7 @@ bool NGCFT1::onEvent(const Events::NGCEXT_ft1_data_ack& e) {
|
||||
}
|
||||
|
||||
bool NGCFT1::onEvent(const Events::NGCEXT_ft1_message& e) {
|
||||
std::cout << "NGCFT1: FT1_MESSAGE mid:" << e.message_id << " fk:" << e.file_kind << " [" << bin2hex(e.file_id) << "]\n";
|
||||
std::cout << "NGCFT1: got FT1_MESSAGE mid:" << e.message_id << " fk:" << e.file_kind << " [" << bin2hex(e.file_id) << "]\n";
|
||||
|
||||
// .... just rethrow??
|
||||
// TODO: dont
|
||||
@ -711,7 +717,7 @@ bool NGCFT1::onToxEvent(const Tox_Event_Group_Peer_Exit* e) {
|
||||
}
|
||||
|
||||
// reset cca
|
||||
peer.cca = std::make_unique<CUBIC>(500-4); // TODO: replace with tox_group_max_custom_lossy_packet_length()-4
|
||||
peer.cca.reset(); // dont actually reallocate
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -2,22 +2,22 @@
|
||||
|
||||
// solanaceae port of tox_ngc_ft1
|
||||
|
||||
#include <solanaceae/toxcore/tox_interface.hpp>
|
||||
#include <solanaceae/toxcore/tox_event_interface.hpp>
|
||||
#include <solanaceae/toxcore/tox_interface.hpp>
|
||||
|
||||
#include <solanaceae/ngc_ext/ngcext.hpp>
|
||||
#include "./cubic.hpp"
|
||||
//#include "./flow_only.hpp"
|
||||
//#include "./ledbat.hpp"
|
||||
#include "./cca.hpp"
|
||||
|
||||
#include "./rcv_buf.hpp"
|
||||
#include "./snd_buf.hpp"
|
||||
|
||||
#include "./ngcft1_file_kind.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
|
||||
namespace Events {
|
||||
|
||||
@ -131,17 +131,22 @@ using NGCFT1EventProviderI = EventProviderI<NGCFT1EventI>;
|
||||
class NGCFT1 : public ToxEventI, public NGCEXTEventI, public NGCFT1EventProviderI {
|
||||
ToxI& _t;
|
||||
ToxEventProviderI& _tep;
|
||||
NGCEXTEventProviderI& _neep;
|
||||
NGCEXTEventProvider& _neep; // not the interface?
|
||||
|
||||
std::default_random_engine _rng{std::random_device{}()};
|
||||
|
||||
float _time_since_activity {10.f};
|
||||
|
||||
// TODO: config
|
||||
size_t acks_per_packet {3u}; // 3
|
||||
float init_retry_timeout_after {5.f}; // 10sec
|
||||
float sending_give_up_after {30.f}; // 30sec
|
||||
|
||||
float init_retry_timeout_after {4.f};
|
||||
float sending_give_up_after {15.f}; // 30sec (per active transfer)
|
||||
|
||||
struct Group {
|
||||
struct Peer {
|
||||
std::unique_ptr<CCAI> cca = std::make_unique<CUBIC>(500-4); // TODO: replace with tox_group_max_custom_lossy_packet_length()-4
|
||||
uint32_t max_packet_data_size {500-4};
|
||||
//std::unique_ptr<CCAI> cca = std::make_unique<CUBIC>(max_packet_data_size); // TODO: replace with tox_group_max_custom_lossy_packet_length()-4
|
||||
std::unique_ptr<CCAI> cca;
|
||||
|
||||
struct RecvTransfer {
|
||||
uint32_t file_kind;
|
||||
@ -188,33 +193,30 @@ class NGCFT1 : public ToxEventI, public NGCEXTEventI, public NGCFT1EventProvider
|
||||
};
|
||||
std::array<std::optional<SendTransfer>, 256> send_transfers;
|
||||
size_t next_send_transfer_idx {0}; // next id will be 0
|
||||
size_t next_send_transfer_send_idx {0};
|
||||
|
||||
size_t active_send_transfers {0};
|
||||
};
|
||||
std::map<uint32_t, Peer> peers;
|
||||
};
|
||||
std::map<uint32_t, Group> groups;
|
||||
|
||||
protected:
|
||||
bool sendPKG_FT1_REQUEST(uint32_t group_number, uint32_t peer_number, uint32_t file_kind, const uint8_t* file_id, size_t file_id_size);
|
||||
bool sendPKG_FT1_INIT(uint32_t group_number, uint32_t peer_number, uint32_t file_kind, uint64_t file_size, uint8_t transfer_id, const uint8_t* file_id, size_t file_id_size);
|
||||
bool sendPKG_FT1_INIT_ACK(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id);
|
||||
bool sendPKG_FT1_DATA(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id, uint16_t sequence_id, const uint8_t* data, size_t data_size);
|
||||
bool sendPKG_FT1_DATA_ACK(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id, const uint16_t* seq_ids, size_t seq_ids_size);
|
||||
bool sendPKG_FT1_MESSAGE(uint32_t group_number, uint32_t message_id, uint32_t file_kind, const uint8_t* file_id, size_t file_id_size);
|
||||
|
||||
void updateSendTransfer(float time_delta, uint32_t group_number, uint32_t peer_number, Group::Peer& peer, size_t idx, std::set<CCAI::SeqIDType>& timeouts_set);
|
||||
void updateSendTransfer(float time_delta, uint32_t group_number, uint32_t peer_number, Group::Peer& peer, size_t idx, std::set<CCAI::SeqIDType>& timeouts_set, int64_t& can_packet_size);
|
||||
void iteratePeer(float time_delta, uint32_t group_number, uint32_t peer_number, Group::Peer& peer);
|
||||
|
||||
const CCAI* getPeerCCA(uint32_t group_number, uint32_t peer_number) const;
|
||||
|
||||
public:
|
||||
NGCFT1(
|
||||
ToxI& t,
|
||||
ToxEventProviderI& tep,
|
||||
NGCEXTEventProviderI& neep
|
||||
NGCEXTEventProvider& neep
|
||||
);
|
||||
|
||||
void iterate(float delta);
|
||||
float iterate(float delta);
|
||||
|
||||
public: // ft1 api
|
||||
// TODO: public variant?
|
||||
void NGC_FT1_send_request_private(
|
||||
uint32_t group_number, uint32_t peer_number,
|
||||
uint32_t file_kind,
|
||||
@ -238,6 +240,23 @@ class NGCFT1 : public ToxEventI, public NGCEXTEventI, public NGCFT1EventProvider
|
||||
const uint8_t* file_id, size_t file_id_size
|
||||
);
|
||||
|
||||
public: // cca stuff
|
||||
// rtt/delay
|
||||
// negative on error or no cca
|
||||
float getPeerDelay(uint32_t group_number, uint32_t peer_number) const;
|
||||
|
||||
// belived possible current window
|
||||
// negative on error or no cca
|
||||
float getPeerWindow(uint32_t group_number, uint32_t peer_number) const;
|
||||
|
||||
// packets in flight
|
||||
// returns -1 if error or no cca
|
||||
int64_t getPeerInFlightPackets(uint32_t group_number, uint32_t peer_number) const;
|
||||
|
||||
// actual bytes in flight (aka window)
|
||||
// returns -1 if error or no cca
|
||||
int64_t getPeerInFlightBytes(uint32_t group_number, uint32_t peer_number) const;
|
||||
|
||||
protected:
|
||||
bool onEvent(const Events::NGCEXT_ft1_request&) override;
|
||||
bool onEvent(const Events::NGCEXT_ft1_init&) override;
|
||||
|
354
solanaceae/ngc_ft1_sha1/chunk_picker.cpp
Normal file
354
solanaceae/ngc_ft1_sha1/chunk_picker.cpp
Normal file
@ -0,0 +1,354 @@
|
||||
#include "./chunk_picker.hpp"
|
||||
|
||||
#include <solanaceae/tox_contacts/components.hpp>
|
||||
|
||||
#include "./components.hpp"
|
||||
#include "./contact_components.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
// TODO: move ps to own file
|
||||
// picker strategies are generators
|
||||
// gen returns true if a valid chunk was picked
|
||||
// ps should be light weight and no persistant state
|
||||
// ps produce an index only once
|
||||
|
||||
// simply scans from the beginning, requesting chunks in that order
|
||||
struct PickerStrategySequential {
|
||||
const BitSet& chunk_candidates;
|
||||
const size_t total_chunks;
|
||||
|
||||
size_t i {0u};
|
||||
|
||||
PickerStrategySequential(
|
||||
const BitSet& chunk_candidates_,
|
||||
const size_t total_chunks_,
|
||||
const size_t start_offset_ = 0u
|
||||
) :
|
||||
chunk_candidates(chunk_candidates_),
|
||||
total_chunks(total_chunks_),
|
||||
i(start_offset_)
|
||||
{}
|
||||
|
||||
|
||||
bool gen(size_t& out_chunk_idx) {
|
||||
for (; i < total_chunks && i < chunk_candidates.size_bits(); i++) {
|
||||
if (chunk_candidates[i]) {
|
||||
out_chunk_idx = i;
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// chooses a random start position and then requests linearly from there
|
||||
struct PickerStrategyRandom {
|
||||
const BitSet& chunk_candidates;
|
||||
const size_t total_chunks;
|
||||
std::minstd_rand& rng;
|
||||
|
||||
size_t count {0u};
|
||||
size_t i {rng()%total_chunks};
|
||||
|
||||
PickerStrategyRandom(
|
||||
const BitSet& chunk_candidates_,
|
||||
const size_t total_chunks_,
|
||||
std::minstd_rand& rng_
|
||||
) :
|
||||
chunk_candidates(chunk_candidates_),
|
||||
total_chunks(total_chunks_),
|
||||
rng(rng_)
|
||||
{}
|
||||
|
||||
bool gen(size_t& out_chunk_idx) {
|
||||
for (; count < total_chunks; count++, i++) {
|
||||
// wrap around
|
||||
if (i >= total_chunks) {
|
||||
i = i%total_chunks;
|
||||
}
|
||||
|
||||
if (chunk_candidates[i]) {
|
||||
out_chunk_idx = i;
|
||||
count++;
|
||||
i++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// switches randomly between random and sequential
|
||||
struct PickerStrategyRandomSequential {
|
||||
PickerStrategyRandom psr;
|
||||
PickerStrategySequential pssf;
|
||||
|
||||
// TODO: configurable
|
||||
std::bernoulli_distribution d{0.5f};
|
||||
|
||||
PickerStrategyRandomSequential(
|
||||
const BitSet& chunk_candidates_,
|
||||
const size_t total_chunks_,
|
||||
std::minstd_rand& rng_,
|
||||
const size_t start_offset_ = 0u
|
||||
) :
|
||||
psr(chunk_candidates_, total_chunks_, rng_),
|
||||
pssf(chunk_candidates_, total_chunks_, start_offset_)
|
||||
{}
|
||||
|
||||
bool gen(size_t& out_chunk_idx) {
|
||||
if (d(psr.rng)) {
|
||||
return psr.gen(out_chunk_idx);
|
||||
} else {
|
||||
return pssf.gen(out_chunk_idx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void ChunkPicker::updateParticipation(
|
||||
Contact3Handle c,
|
||||
ObjectRegistry& objreg
|
||||
) {
|
||||
if (!c.all_of<Contact::Components::FT1Participation>()) {
|
||||
participating_unfinished.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
entt::dense_set<Object> checked;
|
||||
for (const Object ov : c.get<Contact::Components::FT1Participation>().participating) {
|
||||
const ObjectHandle o {objreg, ov};
|
||||
|
||||
if (participating_unfinished.contains(o)) {
|
||||
if (!o.all_of<Components::FT1ChunkSHA1Cache, Components::FT1InfoSHA1>()) {
|
||||
participating_unfinished.erase(o);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (o.all_of<Message::Components::Transfer::TagPaused>()) {
|
||||
participating_unfinished.erase(o);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (o.get<Components::FT1ChunkSHA1Cache>().have_all) {
|
||||
participating_unfinished.erase(o);
|
||||
}
|
||||
} else {
|
||||
if (!o.all_of<Components::FT1ChunkSHA1Cache, Components::FT1InfoSHA1>()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (o.all_of<Message::Components::Transfer::TagPaused>()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!o.get<Components::FT1ChunkSHA1Cache>().have_all) {
|
||||
using Priority = Components::DownloadPriority::Priority;
|
||||
Priority prio = Priority::NORMAL;
|
||||
|
||||
if (o.all_of<Components::DownloadPriority>()) {
|
||||
prio = o.get<Components::DownloadPriority>().p;
|
||||
}
|
||||
|
||||
uint16_t pskips =
|
||||
prio == Priority::HIGHER ? 0u :
|
||||
prio == Priority::HIGH ? 1u :
|
||||
prio == Priority::NORMAL ? 2u :
|
||||
prio == Priority::LOW ? 4u :
|
||||
8u
|
||||
;
|
||||
|
||||
participating_unfinished.emplace(o, ParticipationEntry{pskips});
|
||||
}
|
||||
}
|
||||
checked.emplace(o);
|
||||
}
|
||||
|
||||
// now we still need to remove left over unfinished.
|
||||
// TODO: how did they get left over
|
||||
entt::dense_set<Object> to_remove;
|
||||
for (const auto& [o, _] : participating_unfinished) {
|
||||
if (!checked.contains(o)) {
|
||||
std::cerr << "unfinished contained non participating\n";
|
||||
to_remove.emplace(o);
|
||||
}
|
||||
}
|
||||
for (const auto& o : to_remove) {
|
||||
participating_unfinished.erase(o);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ChunkPicker::ContentChunkR> ChunkPicker::updateChunkRequests(
|
||||
Contact3Handle c,
|
||||
ObjectRegistry& objreg,
|
||||
ReceivingTransfers& rt,
|
||||
const size_t open_requests
|
||||
//NGCFT1& nft
|
||||
) {
|
||||
if (!static_cast<bool>(c)) {
|
||||
assert(false); return {};
|
||||
}
|
||||
|
||||
if (!c.all_of<Contact::Components::ToxGroupPeerEphemeral>()) {
|
||||
assert(false); return {};
|
||||
}
|
||||
const auto [group_number, peer_number] = c.get<Contact::Components::ToxGroupPeerEphemeral>();
|
||||
|
||||
updateParticipation(c, objreg);
|
||||
|
||||
if (participating_unfinished.empty()) {
|
||||
participating_in_last = entt::null;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<ContentChunkR> req_ret;
|
||||
|
||||
// count running tf and open requests
|
||||
const size_t num_ongoing_transfers = rt.sizePeer(group_number, peer_number);
|
||||
// TODO: account for open requests
|
||||
const int64_t num_total = num_ongoing_transfers + open_requests;
|
||||
// TODO: base max on rate(chunks per sec), gonna be ass with variable chunk size
|
||||
const size_t num_requests = std::max<int64_t>(0, int64_t(max_tf_chunk_requests)-num_total);
|
||||
std::cerr << "CP: want " << num_requests << "(rt:" << num_ongoing_transfers << " or:" << open_requests << ") from " << group_number << ":" << peer_number << "\n";
|
||||
|
||||
// while n < X
|
||||
|
||||
// round robin content (remember last obj)
|
||||
if (!objreg.valid(participating_in_last) || !participating_unfinished.count(participating_in_last)) {
|
||||
participating_in_last = participating_unfinished.begin()->first;
|
||||
//participating_in_last = *participating_unfinished.begin();
|
||||
}
|
||||
assert(objreg.valid(participating_in_last));
|
||||
|
||||
auto it = participating_unfinished.find(participating_in_last);
|
||||
// hard limit robin rounds to array size time 20
|
||||
for (size_t i = 0; req_ret.size() < num_requests && i < participating_unfinished.size()*20; i++) {
|
||||
if (it == participating_unfinished.end()) {
|
||||
it = participating_unfinished.begin();
|
||||
}
|
||||
|
||||
if (it->second.skips < it->second.should_skip) {
|
||||
it->second.skips++;
|
||||
continue;
|
||||
}
|
||||
|
||||
ObjectHandle o {objreg, it->first};
|
||||
|
||||
// intersect self have with other have
|
||||
if (!o.all_of<Components::RemoteHave, Components::FT1ChunkSHA1Cache, Components::FT1InfoSHA1>()) {
|
||||
// rare case where no one other has anything
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& cc = o.get<Components::FT1ChunkSHA1Cache>();
|
||||
if (cc.have_all) {
|
||||
std::cerr << "ChunkPicker error: completed content still in participating_unfinished!\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& others_have = o.get<Components::RemoteHave>().others;
|
||||
auto other_it = others_have.find(c);
|
||||
if (other_it == others_have.end()) {
|
||||
// rare case where the other is participating but has nothing
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& other_have = other_it->second;
|
||||
|
||||
BitSet chunk_candidates = cc.have_chunk;
|
||||
if (!other_have.have_all) {
|
||||
// AND is the same as ~(~A | ~B)
|
||||
// that means we leave chunk_candidates as (have is inverted want)
|
||||
// merge is or
|
||||
// invert at the end
|
||||
chunk_candidates
|
||||
.merge(other_have.have.invert())
|
||||
.invert();
|
||||
// TODO: add intersect for more perf
|
||||
} else {
|
||||
chunk_candidates.invert();
|
||||
}
|
||||
const auto& info = o.get<Components::FT1InfoSHA1>();
|
||||
const auto total_chunks = info.chunks.size();
|
||||
auto& requested_chunks = o.get_or_emplace<Components::FT1ChunkSHA1Requested>().chunks;
|
||||
|
||||
// TODO: trim off round up to 8, since they are now always set
|
||||
|
||||
// now select (globaly) unrequested other have
|
||||
// TODO: how do we prioritize within a file?
|
||||
// - sequential (walk from start (or readhead?))
|
||||
// - random (choose random start pos and walk)
|
||||
// - random/sequential (randomly choose between the 2)
|
||||
// - rarest (keep track of rarity and sort by that)
|
||||
// - steaming (use readhead to determain time critical chunks, potentially over requesting, first (relative to stream head) otherwise
|
||||
// maybe look into libtorrens deadline stuff
|
||||
// - arbitrary priority maps/functions (and combine with above in rations)
|
||||
|
||||
// TODO: configurable
|
||||
size_t start_offset {0u};
|
||||
if (o.all_of<Components::ReadHeadHint>()) {
|
||||
const auto byte_offset = o.get<Components::ReadHeadHint>().offset_into_file;
|
||||
if (byte_offset <= info.file_size) {
|
||||
start_offset = o.get<Components::ReadHeadHint>().offset_into_file/info.chunk_size;
|
||||
} else {
|
||||
// error?
|
||||
}
|
||||
}
|
||||
//PickerStrategySequential ps(chunk_candidates, total_chunks, start_offset);
|
||||
//PickerStrategyRandom ps(chunk_candidates, total_chunks, _rng);
|
||||
PickerStrategyRandomSequential ps(chunk_candidates, total_chunks, _rng, start_offset);
|
||||
size_t out_chunk_idx {0};
|
||||
while (ps.gen(out_chunk_idx) && req_ret.size() < num_requests) {
|
||||
// out_chunk_idx is a potential candidate we can request form peer
|
||||
|
||||
// - check against double requests
|
||||
if (std::find_if(req_ret.cbegin(), req_ret.cend(), [&](const ContentChunkR& x) -> bool {
|
||||
return x.object == o && x.chunk_index == out_chunk_idx;
|
||||
}) != req_ret.cend()) {
|
||||
// already in return array
|
||||
// how did we get here? should we fast exit? if sequential strat, we would want to
|
||||
continue; // skip
|
||||
}
|
||||
|
||||
// - check against global requests (this might differ based on strat)
|
||||
if (requested_chunks.count(out_chunk_idx) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// - we check against globally running transfers (this might differ based on strat)
|
||||
if (rt.containsChunk(o, out_chunk_idx)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if nothing else blocks this, add to ret
|
||||
req_ret.push_back(ContentChunkR{o, out_chunk_idx});
|
||||
|
||||
// TODO: move this after packet was sent successfully
|
||||
// (move net in? hmm)
|
||||
requested_chunks[out_chunk_idx] = Components::FT1ChunkSHA1Requested::Entry{0.f, c};
|
||||
}
|
||||
}
|
||||
|
||||
if (it == participating_unfinished.end() || ++it == participating_unfinished.end()) {
|
||||
participating_in_last = entt::null;
|
||||
} else {
|
||||
participating_in_last = it->first;
|
||||
}
|
||||
|
||||
if (req_ret.size() < num_requests) {
|
||||
std::cerr << "CP: could not fulfil, " << group_number << ":" << peer_number << " only has " << req_ret.size() << " candidates\n";
|
||||
}
|
||||
|
||||
// -- no -- (just compat with old code, ignore)
|
||||
// if n < X
|
||||
// optimistically request 1 chunk other does not have
|
||||
// (don't mark es requested? or lower cooldown to re-request?)
|
||||
|
||||
return req_ret;
|
||||
}
|
||||
|
76
solanaceae/ngc_ft1_sha1/chunk_picker.hpp
Normal file
76
solanaceae/ngc_ft1_sha1/chunk_picker.hpp
Normal file
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/contact/contact_model3.hpp>
|
||||
#include <solanaceae/object_store/object_store.hpp>
|
||||
|
||||
#include "./components.hpp"
|
||||
|
||||
#include "./receiving_transfers.hpp"
|
||||
|
||||
#include <entt/container/dense_map.hpp>
|
||||
#include <entt/container/dense_set.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
|
||||
//#include <solanaceae/ngc_ft1/ngcft1.hpp>
|
||||
|
||||
// goal is to always keep 2 transfers running and X(6) requests queued up
|
||||
// per peer
|
||||
|
||||
struct ChunkPickerUpdateTag {};
|
||||
|
||||
struct ChunkPickerTimer {
|
||||
// adds update tag on 0
|
||||
float timer {0.f};
|
||||
};
|
||||
|
||||
// contact component?
|
||||
struct ChunkPicker {
|
||||
// max transfers
|
||||
static constexpr size_t max_tf_info_requests {1};
|
||||
static constexpr size_t max_tf_chunk_requests {3}; // TODO: dynamic, function/factor of (window(delay*speed)/chunksize)
|
||||
|
||||
// TODO: cheaper init? tls rng for deep seeding?
|
||||
std::minstd_rand _rng{std::random_device{}()};
|
||||
|
||||
// TODO: handle with hash utils?
|
||||
struct ParticipationEntry {
|
||||
ParticipationEntry(void) {}
|
||||
ParticipationEntry(uint16_t s) : should_skip(s) {}
|
||||
// skips in round robin -> lower should_skip => higher priority
|
||||
// TODO: replace with enum value
|
||||
uint16_t should_skip {2}; // 0 high, 8 low (double each time? 0,1,2,4,8)
|
||||
uint16_t skips {0};
|
||||
};
|
||||
entt::dense_map<Object, ParticipationEntry> participating_unfinished;
|
||||
Object participating_in_last {entt::null};
|
||||
|
||||
private: // TODO: properly sort
|
||||
// updates participating_unfinished
|
||||
void updateParticipation(
|
||||
Contact3Handle c,
|
||||
ObjectRegistry& objreg
|
||||
);
|
||||
public:
|
||||
|
||||
// ---------- tick ----------
|
||||
|
||||
//void sendInfoRequests();
|
||||
|
||||
// is this like a system?
|
||||
struct ContentChunkR {
|
||||
ObjectHandle object;
|
||||
size_t chunk_index;
|
||||
};
|
||||
// returns list of chunks to request
|
||||
[[nodiscard]] std::vector<ContentChunkR> updateChunkRequests(
|
||||
Contact3Handle c,
|
||||
ObjectRegistry& objreg,
|
||||
ReceivingTransfers& rt,
|
||||
const size_t open_requests
|
||||
//NGCFT1& nft
|
||||
);
|
||||
};
|
||||
|
25
solanaceae/ngc_ft1_sha1/components.cpp
Normal file
25
solanaceae/ngc_ft1_sha1/components.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include "./components.hpp"
|
||||
|
||||
std::vector<size_t> Components::FT1ChunkSHA1Cache::chunkIndices(const SHA1Digest& hash) const {
|
||||
const auto it = chunk_hash_to_index.find(hash);
|
||||
if (it != chunk_hash_to_index.cend()) {
|
||||
return it->second;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool Components::FT1ChunkSHA1Cache::haveChunk(const SHA1Digest& hash) const {
|
||||
if (have_all) { // short cut
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto i_vec = chunkIndices(hash); !i_vec.empty()) {
|
||||
// TODO: should i test all?
|
||||
return have_chunk[i_vec.front()];
|
||||
}
|
||||
|
||||
// not part of this file
|
||||
return false;
|
||||
}
|
||||
|
98
solanaceae/ngc_ft1_sha1/components.hpp
Normal file
98
solanaceae/ngc_ft1_sha1/components.hpp
Normal file
@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/contact/components.hpp>
|
||||
#include <solanaceae/message3/components.hpp>
|
||||
#include <solanaceae/message3/registry_message_model.hpp>
|
||||
|
||||
#include <solanaceae/util/bitset.hpp>
|
||||
|
||||
#include <entt/container/dense_set.hpp>
|
||||
|
||||
#include "./ft1_sha1_info.hpp"
|
||||
#include "./hash_utils.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
// TODO: rename to object components
|
||||
namespace Components {
|
||||
|
||||
struct Messages {
|
||||
// dense set instead?
|
||||
std::vector<Message3Handle> messages;
|
||||
};
|
||||
|
||||
using FT1InfoSHA1 = FT1InfoSHA1;
|
||||
|
||||
struct FT1InfoSHA1Data {
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
struct FT1InfoSHA1Hash {
|
||||
std::vector<uint8_t> hash;
|
||||
};
|
||||
|
||||
struct FT1ChunkSHA1Cache {
|
||||
// TODO: extract have_chunk, have_all and have_count to generic comp
|
||||
|
||||
// have_chunk is the size of info.chunks.size(), or empty if have_all
|
||||
// keep in mind bitset rounds up to 8s
|
||||
BitSet have_chunk{0};
|
||||
|
||||
bool have_all {false};
|
||||
size_t have_count {0};
|
||||
entt::dense_map<SHA1Digest, std::vector<size_t>> chunk_hash_to_index;
|
||||
|
||||
std::vector<size_t> chunkIndices(const SHA1Digest& hash) const;
|
||||
bool haveChunk(const SHA1Digest& hash) const;
|
||||
};
|
||||
|
||||
struct FT1ChunkSHA1Requested {
|
||||
// requested chunks with a timer since last request
|
||||
struct Entry {
|
||||
float timer {0.f};
|
||||
Contact3 c {entt::null};
|
||||
};
|
||||
entt::dense_map<size_t, Entry> chunks;
|
||||
};
|
||||
|
||||
// TODO: once announce is shipped, remove the "Suspected"
|
||||
struct SuspectedParticipants {
|
||||
entt::dense_set<Contact3> participants;
|
||||
};
|
||||
|
||||
struct RemoteHave {
|
||||
struct Entry {
|
||||
bool have_all {false};
|
||||
BitSet have;
|
||||
};
|
||||
entt::dense_map<Contact3, Entry> others;
|
||||
};
|
||||
|
||||
struct ReRequestInfoTimer {
|
||||
float timer {0.f};
|
||||
};
|
||||
|
||||
struct DownloadPriority {
|
||||
// download/retreival priority in comparison to other objects
|
||||
// not all backends implement this
|
||||
// priority can be weak, meaning low priority dls will still get transfer activity, just less often
|
||||
enum class Priority {
|
||||
HIGHER,
|
||||
HIGH,
|
||||
NORMAL,
|
||||
LOW,
|
||||
LOWER,
|
||||
} p = Priority::NORMAL;
|
||||
};
|
||||
|
||||
struct ReadHeadHint {
|
||||
// points to the first byte we want
|
||||
// this is just a hint, that can be set from outside
|
||||
// to guide the sequential "piece picker" strategy
|
||||
// ? the strategy *should* set this to the first byte we dont yet have
|
||||
uint64_t offset_into_file {0u};
|
||||
};
|
||||
|
||||
} // Components
|
||||
|
13
solanaceae/ngc_ft1_sha1/contact_components.hpp
Normal file
13
solanaceae/ngc_ft1_sha1/contact_components.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/object_store/object_store.hpp>
|
||||
#include <entt/container/dense_set.hpp>
|
||||
|
||||
namespace Contact::Components {
|
||||
|
||||
struct FT1Participation {
|
||||
entt::dense_set<Object> participating;
|
||||
};
|
||||
|
||||
} // Contact::Components
|
||||
|
@ -1,58 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/message3/file.hpp>
|
||||
#include <solanaceae/file/file2.hpp>
|
||||
|
||||
#include "./mio.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
struct FileRWMapped : public FileI {
|
||||
struct File2RWMapped : public File2I {
|
||||
mio::ummap_sink _file_map;
|
||||
|
||||
// TODO: add truncate support?
|
||||
FileRWMapped(std::string_view file_path, uint64_t file_size) {
|
||||
_file_size = file_size;
|
||||
// TODO: rw always true?
|
||||
File2RWMapped(std::string_view file_path, int64_t file_size = -1) : File2I(true, true) {
|
||||
std::filesystem::path native_file_path{file_path};
|
||||
|
||||
if (!std::filesystem::exists(file_path)) {
|
||||
std::ofstream(std::string{file_path}) << '\0'; // force create the file
|
||||
if (!std::filesystem::exists(native_file_path)) {
|
||||
std::ofstream(native_file_path) << '\0'; // force create the file
|
||||
}
|
||||
|
||||
_file_size = std::filesystem::file_size(native_file_path);
|
||||
if (file_size >= 0 && _file_size != file_size) {
|
||||
_file_size = file_size;
|
||||
std::filesystem::resize_file(native_file_path, file_size); // ensure size, usually sparse
|
||||
}
|
||||
std::filesystem::resize_file(file_path, file_size); // ensure size, usually sparse
|
||||
|
||||
std::error_code err;
|
||||
// sink, is also read
|
||||
_file_map.map(std::string{file_path}, 0, file_size, err);
|
||||
_file_map.map(native_file_path.u8string(), 0, _file_size, err);
|
||||
|
||||
if (err) {
|
||||
// TODO: errro
|
||||
std::cerr << "FileRWMapped error: mapping file failed " << err << "\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~FileRWMapped(void) override {}
|
||||
virtual ~File2RWMapped(void) override {}
|
||||
|
||||
bool isGood(void) override {
|
||||
return _file_map.is_mapped();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> read(uint64_t pos, uint64_t size) override {
|
||||
if (pos+size > _file_size) {
|
||||
//assert(false && "read past end");
|
||||
return {};
|
||||
}
|
||||
|
||||
return {_file_map.data()+pos, _file_map.data()+(pos+size)};
|
||||
}
|
||||
|
||||
bool write(uint64_t pos, const std::vector<uint8_t>& data) override {
|
||||
if (pos+data.size() > _file_size) {
|
||||
bool write(const ByteSpan data, int64_t pos = -1) override {
|
||||
// TODO: support streaming write
|
||||
if (pos < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(_file_map.data()+pos, data.data(), data.size());
|
||||
if (data.empty()) {
|
||||
return true; // false?
|
||||
}
|
||||
|
||||
// file size is fix for mmaped files
|
||||
if (pos+data.size > _file_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(_file_map.data()+pos, data.ptr, data.size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ByteSpanWithOwnership read(uint64_t size, int64_t pos = -1) override {
|
||||
if (pos+size > _file_size) {
|
||||
//assert(false && "read past end");
|
||||
return ByteSpan{};
|
||||
}
|
||||
|
||||
// return non-owning
|
||||
return ByteSpan{_file_map.data()+pos, size};
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -18,7 +18,7 @@ struct SHA1Digest {
|
||||
bool operator==(const SHA1Digest& other) const { return data == other.data; }
|
||||
bool operator!=(const SHA1Digest& other) const { return data != other.data; }
|
||||
|
||||
size_t size(void) const { return data.size(); }
|
||||
constexpr size_t size(void) const { return data.size(); }
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const SHA1Digest& v);
|
||||
|
48
solanaceae/ngc_ft1_sha1/participation.cpp
Normal file
48
solanaceae/ngc_ft1_sha1/participation.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include "./participation.hpp"
|
||||
|
||||
#include "./contact_components.hpp"
|
||||
#include "./chunk_picker.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
bool addParticipation(Contact3Handle c, ObjectHandle o) {
|
||||
bool was_new {false};
|
||||
assert(static_cast<bool>(o));
|
||||
assert(static_cast<bool>(c));
|
||||
|
||||
if (static_cast<bool>(o)) {
|
||||
const auto [_, inserted] = o.get_or_emplace<Components::SuspectedParticipants>().participants.emplace(c);
|
||||
was_new = inserted;
|
||||
}
|
||||
|
||||
if (static_cast<bool>(c)) {
|
||||
const auto [_, inserted] = c.get_or_emplace<Contact::Components::FT1Participation>().participating.emplace(o);
|
||||
was_new = was_new || inserted;
|
||||
}
|
||||
|
||||
//std::cout << "added " << (was_new?"new ":"") << "participant\n";
|
||||
|
||||
return was_new;
|
||||
}
|
||||
|
||||
void removeParticipation(Contact3Handle c, ObjectHandle o) {
|
||||
assert(static_cast<bool>(o));
|
||||
assert(static_cast<bool>(c));
|
||||
|
||||
if (static_cast<bool>(o) && o.all_of<Components::SuspectedParticipants>()) {
|
||||
o.get<Components::SuspectedParticipants>().participants.erase(c);
|
||||
}
|
||||
|
||||
if (static_cast<bool>(c)) {
|
||||
if (c.all_of<Contact::Components::FT1Participation>()) {
|
||||
c.get<Contact::Components::FT1Participation>().participating.erase(o);
|
||||
}
|
||||
|
||||
if (c.all_of<ChunkPicker>()) {
|
||||
c.get<ChunkPicker>().participating_unfinished.erase(o);
|
||||
}
|
||||
}
|
||||
|
||||
//std::cout << "removed participant\n";
|
||||
}
|
||||
|
8
solanaceae/ngc_ft1_sha1/participation.hpp
Normal file
8
solanaceae/ngc_ft1_sha1/participation.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/object_store/object_store.hpp>
|
||||
#include <solanaceae/contact/contact_model3.hpp>
|
||||
|
||||
bool addParticipation(Contact3Handle c, ObjectHandle o);
|
||||
void removeParticipation(Contact3Handle c, ObjectHandle o);
|
||||
|
131
solanaceae/ngc_ft1_sha1/receiving_transfers.cpp
Normal file
131
solanaceae/ngc_ft1_sha1/receiving_transfers.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
#include "./receiving_transfers.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void ReceivingTransfers::tick(float delta) {
|
||||
for (auto peer_it = _data.begin(); peer_it != _data.end();) {
|
||||
for (auto it = peer_it->second.begin(); it != peer_it->second.end();) {
|
||||
it->second.time_since_activity += delta;
|
||||
|
||||
// if we have not heard for 20sec, timeout
|
||||
if (it->second.time_since_activity >= 20.f) {
|
||||
std::cerr << "SHA1_NGCFT1 warning: receiving tansfer timed out " << "." << int(it->first) << "\n";
|
||||
// TODO: if info, requeue? or just keep the timer comp? - no, timer comp will continue ticking, even if loading
|
||||
//it->second.v
|
||||
it = peer_it->second.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
if (peer_it->second.empty()) {
|
||||
// cleanup unused peers too agressive?
|
||||
peer_it = _data.erase(peer_it);
|
||||
} else {
|
||||
peer_it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReceivingTransfers::Entry& ReceivingTransfers::emplaceInfo(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id, const Entry::Info& info) {
|
||||
auto& ent = _data[combine_ids(group_number, peer_number)][transfer_id];
|
||||
ent.v = info;
|
||||
return ent;
|
||||
}
|
||||
|
||||
ReceivingTransfers::Entry& ReceivingTransfers::emplaceChunk(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id, const Entry::Chunk& chunk) {
|
||||
assert(!chunk.chunk_indices.empty());
|
||||
assert(!containsPeerChunk(group_number, peer_number, chunk.content, chunk.chunk_indices.front()));
|
||||
auto& ent = _data[combine_ids(group_number, peer_number)][transfer_id];
|
||||
ent.v = chunk;
|
||||
return ent;
|
||||
}
|
||||
|
||||
bool ReceivingTransfers::containsPeerTransfer(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id) const {
|
||||
auto it = _data.find(combine_ids(group_number, peer_number));
|
||||
if (it == _data.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return it->second.count(transfer_id);
|
||||
}
|
||||
|
||||
bool ReceivingTransfers::containsChunk(ObjectHandle o, size_t chunk_idx) const {
|
||||
for (const auto& [_, p] : _data) {
|
||||
for (const auto& [_2, v] : p) {
|
||||
if (!v.isChunk()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& c = v.getChunk();
|
||||
if (c.content != o) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto idx : c.chunk_indices) {
|
||||
if (idx == chunk_idx) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReceivingTransfers::containsPeerChunk(uint32_t group_number, uint32_t peer_number, ObjectHandle o, size_t chunk_idx) const {
|
||||
auto it = _data.find(combine_ids(group_number, peer_number));
|
||||
if (it == _data.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& [_, v] : it->second) {
|
||||
if (!v.isChunk()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& c = v.getChunk();
|
||||
if (c.content != o) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto idx : c.chunk_indices) {
|
||||
if (idx == chunk_idx) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ReceivingTransfers::removePeer(uint32_t group_number, uint32_t peer_number) {
|
||||
_data.erase(combine_ids(group_number, peer_number));
|
||||
}
|
||||
|
||||
void ReceivingTransfers::removePeerTransfer(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id) {
|
||||
auto it = _data.find(combine_ids(group_number, peer_number));
|
||||
if (it == _data.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
it->second.erase(transfer_id);
|
||||
}
|
||||
|
||||
size_t ReceivingTransfers::size(void) const {
|
||||
size_t count {0};
|
||||
for (const auto& [_, p] : _data) {
|
||||
count += p.size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t ReceivingTransfers::sizePeer(uint32_t group_number, uint32_t peer_number) const {
|
||||
auto it = _data.find(combine_ids(group_number, peer_number));
|
||||
if (it == _data.end()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return it->second.size();
|
||||
}
|
||||
|
66
solanaceae/ngc_ft1_sha1/receiving_transfers.hpp
Normal file
66
solanaceae/ngc_ft1_sha1/receiving_transfers.hpp
Normal file
@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include <solanaceae/object_store/object_store.hpp>
|
||||
|
||||
#include <entt/container/dense_map.hpp>
|
||||
|
||||
#include "./util.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
struct ReceivingTransfers {
|
||||
struct Entry {
|
||||
struct Info {
|
||||
ObjectHandle content;
|
||||
// copy of info data
|
||||
// too large?
|
||||
std::vector<uint8_t> info_data;
|
||||
};
|
||||
|
||||
struct Chunk {
|
||||
ObjectHandle content;
|
||||
std::vector<size_t> chunk_indices;
|
||||
// or data?
|
||||
// if memmapped, this would be just a pointer
|
||||
};
|
||||
|
||||
std::variant<Info, Chunk> v;
|
||||
|
||||
float time_since_activity {0.f};
|
||||
|
||||
bool isInfo(void) const { return std::holds_alternative<Info>(v); }
|
||||
bool isChunk(void) const { return std::holds_alternative<Chunk>(v); }
|
||||
|
||||
Info& getInfo(void) { return std::get<Info>(v); }
|
||||
const Info& getInfo(void) const { return std::get<Info>(v); }
|
||||
Chunk& getChunk(void) { return std::get<Chunk>(v); }
|
||||
const Chunk& getChunk(void) const { return std::get<Chunk>(v); }
|
||||
};
|
||||
|
||||
// key is groupid + peerid
|
||||
// TODO: replace with contact
|
||||
//using ReceivingTransfers = entt::dense_map<uint64_t, entt::dense_map<uint8_t, ReceivingTransferE>>;
|
||||
entt::dense_map<uint64_t, entt::dense_map<uint8_t, Entry>> _data;
|
||||
|
||||
void tick(float delta);
|
||||
|
||||
Entry& emplaceInfo(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id, const Entry::Info& info);
|
||||
Entry& emplaceChunk(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id, const Entry::Chunk& chunk);
|
||||
|
||||
bool containsPeer(uint32_t group_number, uint32_t peer_number) const { return _data.count(combine_ids(group_number, peer_number)); }
|
||||
bool containsPeerTransfer(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id) const;
|
||||
bool containsChunk(ObjectHandle o, size_t chunk_idx) const;
|
||||
bool containsPeerChunk(uint32_t group_number, uint32_t peer_number, ObjectHandle o, size_t chunk_idx) const;
|
||||
|
||||
auto& getPeer(uint32_t group_number, uint32_t peer_number) { return _data.at(combine_ids(group_number, peer_number)); }
|
||||
auto& getTransfer(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id) { return getPeer(group_number, peer_number).at(transfer_id); }
|
||||
|
||||
void removePeer(uint32_t group_number, uint32_t peer_number);
|
||||
void removePeerTransfer(uint32_t group_number, uint32_t peer_number, uint8_t transfer_id);
|
||||
|
||||
size_t size(void) const;
|
||||
size_t sizePeer(uint32_t group_number, uint32_t peer_number) const;
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,13 +2,16 @@
|
||||
|
||||
// solanaceae port of sha1 fts for NGCFT1
|
||||
|
||||
#include <solanaceae/object_store/object_store.hpp>
|
||||
#include <solanaceae/contact/contact_model3.hpp>
|
||||
#include <solanaceae/message3/registry_message_model.hpp>
|
||||
#include <solanaceae/tox_contacts/tox_contact_model2.hpp>
|
||||
#include <solanaceae/util/bitset.hpp>
|
||||
|
||||
#include <solanaceae/ngc_ft1/ngcft1.hpp>
|
||||
|
||||
#include "./ft1_sha1_info.hpp"
|
||||
#include "./receiving_transfers.hpp"
|
||||
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/entity/handle.hpp>
|
||||
@ -20,33 +23,30 @@
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
|
||||
enum class Content : uint32_t {};
|
||||
using ContentRegistry = entt::basic_registry<Content>;
|
||||
using ContentHandle = entt::basic_handle<ContentRegistry>;
|
||||
|
||||
class SHA1_NGCFT1 : public RegistryMessageModelEventI, public NGCFT1EventI {
|
||||
class SHA1_NGCFT1 : public ToxEventI, public RegistryMessageModelEventI, public NGCFT1EventI, public NGCEXTEventI {
|
||||
ObjectStore2& _os;
|
||||
// TODO: backend abstraction
|
||||
Contact3Registry& _cr;
|
||||
RegistryMessageModel& _rmm;
|
||||
NGCFT1& _nft;
|
||||
ToxContactModel2& _tcm;
|
||||
ToxEventProviderI& _tep;
|
||||
NGCEXTEventProvider& _neep;
|
||||
|
||||
std::minstd_rand _rng {1337*11};
|
||||
|
||||
// registry per group?
|
||||
ContentRegistry _contentr;
|
||||
|
||||
// limit this to each group?
|
||||
entt::dense_map<SHA1Digest, ContentHandle> _info_to_content;
|
||||
entt::dense_map<SHA1Digest, ObjectHandle> _info_to_content;
|
||||
|
||||
// sha1 chunk index
|
||||
// TODO: optimize lookup
|
||||
// TODO: multiple contents. hashes might be unique, but data is not
|
||||
entt::dense_map<SHA1Digest, ContentHandle> _chunks;
|
||||
entt::dense_map<SHA1Digest, ObjectHandle> _chunks;
|
||||
|
||||
// group_number, peer_number, content, chunk_hash, timer
|
||||
std::deque<std::tuple<uint32_t, uint32_t, ContentHandle, SHA1Digest, float>> _queue_requested_chunk;
|
||||
std::deque<std::tuple<uint32_t, uint32_t, ObjectHandle, SHA1Digest, float>> _queue_requested_chunk;
|
||||
//void queueUpRequestInfo(uint32_t group_number, uint32_t peer_number, const SHA1Digest& hash);
|
||||
void queueUpRequestChunk(uint32_t group_number, uint32_t peer_number, ContentHandle content, const SHA1Digest& hash);
|
||||
void queueUpRequestChunk(uint32_t group_number, uint32_t peer_number, ObjectHandle content, const SHA1Digest& hash);
|
||||
|
||||
struct SendingTransfer {
|
||||
struct Info {
|
||||
@ -56,7 +56,7 @@ class SHA1_NGCFT1 : public RegistryMessageModelEventI, public NGCFT1EventI {
|
||||
};
|
||||
|
||||
struct Chunk {
|
||||
ContentHandle content;
|
||||
ObjectHandle content;
|
||||
size_t chunk_index; // <.< remove offset_into_file
|
||||
//uint64_t offset_into_file;
|
||||
// or data?
|
||||
@ -70,65 +70,50 @@ class SHA1_NGCFT1 : public RegistryMessageModelEventI, public NGCFT1EventI {
|
||||
// key is groupid + peerid
|
||||
entt::dense_map<uint64_t, entt::dense_map<uint8_t, SendingTransfer>> _sending_transfers;
|
||||
|
||||
struct ReceivingTransfer {
|
||||
struct Info {
|
||||
ContentHandle content;
|
||||
// copy of info data
|
||||
// too large?
|
||||
std::vector<uint8_t> info_data;
|
||||
};
|
||||
|
||||
struct Chunk {
|
||||
ContentHandle content;
|
||||
std::vector<size_t> chunk_indices;
|
||||
// or data?
|
||||
// if memmapped, this would be just a pointer
|
||||
};
|
||||
|
||||
std::variant<Info, Chunk> v;
|
||||
|
||||
float time_since_activity {0.f};
|
||||
};
|
||||
// key is groupid + peerid
|
||||
entt::dense_map<uint64_t, entt::dense_map<uint8_t, ReceivingTransfer>> _receiving_transfers;
|
||||
ReceivingTransfers _receiving_transfers;
|
||||
|
||||
// makes request rotate around open content
|
||||
std::deque<ContentHandle> _queue_content_want_info;
|
||||
std::deque<ContentHandle> _queue_content_want_chunk;
|
||||
std::deque<ObjectHandle> _queue_content_want_info;
|
||||
|
||||
struct QBitsetEntry {
|
||||
Contact3Handle c;
|
||||
ObjectHandle o;
|
||||
};
|
||||
std::deque<QBitsetEntry> _queue_send_bitset;
|
||||
|
||||
// workaround missing contact events
|
||||
// only used to remove participation on peer exit
|
||||
entt::dense_map<uint64_t, Contact3Handle> _tox_peer_to_contact;
|
||||
|
||||
std::atomic_bool _info_builder_dirty {false};
|
||||
std::mutex _info_builder_queue_mutex;
|
||||
//struct InfoBuilderEntry {
|
||||
//// called on completion on the iterate thread
|
||||
//// (owning)
|
||||
//std::function<void(void)> fn;
|
||||
//};
|
||||
using InfoBuilderEntry = std::function<void(void)>;
|
||||
std::list<InfoBuilderEntry> _info_builder_queue;
|
||||
|
||||
static uint64_t combineIds(const uint32_t group_number, const uint32_t peer_number);
|
||||
void updateMessages(ObjectHandle ce);
|
||||
|
||||
void updateMessages(ContentHandle ce);
|
||||
std::optional<std::pair<uint32_t, uint32_t>> selectPeerForRequest(ObjectHandle ce);
|
||||
|
||||
std::optional<std::pair<uint32_t, uint32_t>> selectPeerForRequest(ContentHandle ce);
|
||||
void queueBitsetSendFull(Contact3Handle c, ObjectHandle o);
|
||||
|
||||
public: // TODO: config
|
||||
bool _udp_only {false};
|
||||
|
||||
size_t _max_concurrent_in {4};
|
||||
size_t _max_concurrent_out {6};
|
||||
// TODO: probably also includes running transfers rn (meh)
|
||||
size_t _max_pending_requests {32}; // per content
|
||||
size_t _max_concurrent_in {4}; // info only
|
||||
size_t _max_concurrent_out {3*4}; // HACK: allow ideal number for 4 peers
|
||||
|
||||
public:
|
||||
SHA1_NGCFT1(
|
||||
ObjectStore2& os,
|
||||
Contact3Registry& cr,
|
||||
RegistryMessageModel& rmm,
|
||||
NGCFT1& nft,
|
||||
ToxContactModel2& tcm
|
||||
ToxContactModel2& tcm,
|
||||
ToxEventProviderI& tep,
|
||||
NGCEXTEventProvider& neep
|
||||
);
|
||||
|
||||
void iterate(float delta);
|
||||
float iterate(float delta);
|
||||
|
||||
protected: // rmm events (actions)
|
||||
bool onEvent(const Message::Events::MessageUpdated&) override;
|
||||
@ -143,5 +128,13 @@ class SHA1_NGCFT1 : public RegistryMessageModelEventI, public NGCFT1EventI {
|
||||
bool onEvent(const Events::NGCFT1_recv_message&) override;
|
||||
|
||||
bool sendFilePath(const Contact3 c, std::string_view file_name, std::string_view file_path) override;
|
||||
|
||||
bool onToxEvent(const Tox_Event_Group_Peer_Exit* e) override;
|
||||
|
||||
bool onEvent(const Events::NGCEXT_ft1_have&) override;
|
||||
bool onEvent(const Events::NGCEXT_ft1_bitset&) override;
|
||||
bool onEvent(const Events::NGCEXT_ft1_have_all&) override;
|
||||
|
||||
bool onEvent(const Events::NGCEXT_pc1_announce&) override;
|
||||
};
|
||||
|
||||
|
13
solanaceae/ngc_ft1_sha1/util.hpp
Normal file
13
solanaceae/ngc_ft1_sha1/util.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
inline static uint64_t combine_ids(const uint32_t group_number, const uint32_t peer_number) {
|
||||
return (uint64_t(group_number) << 32) | peer_number;
|
||||
}
|
||||
|
||||
inline static void decompose_ids(const uint64_t combined_id, uint32_t& group_number, uint32_t& peer_number) {
|
||||
group_number = combined_id >> 32;
|
||||
peer_number = combined_id & 0xffffffff;
|
||||
}
|
||||
|
Reference in New Issue
Block a user