Selaa lähdekoodia

removed files unsafe for master

pull/5/head
Ludovic 'Archivist' Lagouardette 4 vuotta sitten
vanhempi
commit
c9d25156f5
12 muutettua tiedostoa jossa 27 lisäystä ja 886 poistoa
  1. +0
    -147
      include/rc6_generic.hpp
  2. +0
    -2
      include/region_locker.hpp
  3. +0
    -383
      include/shared_fd.hpp
  4. +0
    -2
      include/shared_mmap.hpp
  5. +0
    -2
      include/stored_array.hpp
  6. +0
    -2
      include/stored_clog.hpp
  7. +0
    -2
      include/stored_hmap.hpp
  8. +0
    -2
      include/stored_indexed_array.hpp
  9. +0
    -2
      tests.cpp
  10. +27
    -0
      tests/gp_test.cpp
  11. +0
    -56
      tests/rc6_generic.cpp
  12. +0
    -286
      tests/shared_fd.cpp

+ 0
- 147
include/rc6_generic.hpp Näytä tiedosto

@ -1,147 +0,0 @@
#pragma once
#include <stdint.h>
#include <iostream>
#include <stddef.h>
#include <gp/math.hpp>
#include <gp/array.hpp>
#include <gp/algorithm/rotate.hpp>
// BUG: A proper investigation is required to fix this file so that it gives the correct output.
template<typename word_t = uint32_t, size_t r = 20, size_t b = 128, word_t P = 0xb7e15163, word_t Q = 0x9e3779b9>
class RC6 {
static constexpr size_t word_size = 8*sizeof(word_t);
constexpr static word_t r_l(const word_t& w, size_t v) {
return (w << v) | ( w >> (word_size-v));
}
constexpr static word_t r_r(const word_t& w, size_t v) {
return (w >> v) | ( w << (word_size-v));
}
class RC6_KeySched {
using sched_t = gp::array<word_t, 2*r+4>;
public:
static constexpr size_t c = (b+word_size-1)/word_size;
static constexpr size_t v_3 = gp::max(c, 2*r+4);
static constexpr size_t v = v_3*3;
private:
sched_t S{};
public:
constexpr RC6_KeySched(gp::array<word_t, c> L)
{
static_assert(r_l(r_r(13,13),13) == 13);
auto it = S.begin();
*(it++) = P;
for(; it != S.end(); ++it)
{
*it = *(it-1) + Q;
}
word_t A = 0;
word_t B = 0;
word_t i = 0;
word_t j = 0;
size_t s = 0;
while(true)
{
A = S[i] = r_l( S[i] + A + B, 3 );
B += A;
B = L[j] = r_l( L[j] + B, B%(word_size));
++s;
if(s >= v) break;
i = s % S.size();
j = s % L.size();
}
}
const auto cbegin()
{
return S.cbegin();
}
const auto cend()
{
return S.cend();
}
const auto crbegin()
{
return S.crbegin();
}
const auto crend()
{
return S.crend();
}
};
RC6_KeySched S;
public:
typedef gp::array<word_t, RC6_KeySched::c> key_type;
typedef gp::array<word_t, 4> block_type;
constexpr RC6(const key_type& key)
: S(key)
{}
constexpr block_type&& encrypt(block_type plaintext) {
using namespace gp::math;
auto& A = plaintext[0];
auto& B = plaintext[1];
auto& C = plaintext[2];
auto& D = plaintext[3];
auto it = S.cbegin();
B += *(it++);
D += *(it++);
for(size_t i = 0; i < r; ++i)
{
auto u = r_l( D * ( 2 * D + 1 ), msb(word_size));
auto t = r_l( B * ( 2 * B + 1 ), msb(word_size));
A = r_l((A ^ t), u % word_size) + *(it++);
C = r_l((C ^ u), t % word_size) + *(it++);
gp::rotate(plaintext.begin(), plaintext.begin()+1, plaintext.end());
}
A += *(it++);
C += *(it++);
assert(it == S.cend());
return std::move(plaintext);
}
constexpr block_type decrypt(block_type plaintext) {
using namespace gp::math;
auto& A = plaintext[0];
auto& B = plaintext[1];
auto& C = plaintext[2];
auto& D = plaintext[3];
auto it = S.crbegin();
C -= *(it++);
A -= *(it++);
for(size_t i = 0; i < r; ++i)
{
gp::rotate(plaintext.begin(), plaintext.end()-1, plaintext.end());
auto u = r_l( D * ( 2 * D + 1 ), msb(word_size));
auto t = r_l( B * ( 2 * B + 1 ), msb(word_size));
C = r_r( (C - *(it++)) , t % word_size) ^ u ;
A = r_r( (A - *(it++)) , u % word_size) ^ t ;
}
D -= *(it++);
B -= *(it++);
assert(it == S.crend());
return std::move(plaintext);
}
};

+ 0
- 2
include/region_locker.hpp Näytä tiedosto

@ -1,2 +0,0 @@
#pragma once
// UNIMPLEMENTED: see filename

+ 0
- 383
include/shared_fd.hpp Näytä tiedosto

@ -1,383 +0,0 @@
#pragma once
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <string>
#include <sstream>
#include <atomic>
#include <array>
#include <variant>
#include <cassert>
// TODO: Scavenge for useful parts and discard
namespace gp{
using open_opt_flags = int;
enum class open_options : open_opt_flags {
append = O_APPEND,
create = O_CREAT,
async = O_ASYNC,
direct = O_DIRECT,
data_sync = O_DSYNC,
file_sync = O_SYNC,
exclusive = O_EXCL,
no_access_time = O_NOATIME,
no_follow = O_NOFOLLOW,
read = O_RDONLY,
write = O_WRONLY,
#ifdef O_TEMP
temporary = O_TEMP,
#endif
#ifdef O_TRUC
truncate = O_TRUC,
#endif
};
using open_opt_mode = int;
enum class open_modes : open_opt_mode {
user_read = S_IRUSR,
user_write = S_IWUSR,
user_exec = S_IXUSR,
group_read = S_IRGRP,
group_write = S_IWGRP,
group_exec = S_IXGRP,
other_read = S_IROTH,
other_write = S_IWOTH,
other_exec = S_IXOTH,
set_uid = S_ISUID,
set_gid = S_ISGID,
sticky_bit = S_ISVTX,
};
enum class socket_domain : int {
ip4 = AF_INET,
ip6 = AF_INET6,
unix = AF_UNIX
};
enum class socket_protocol : int {
tcp_like = SOCK_STREAM,
udp_like = SOCK_DGRAM
};
using socket_opt_flags = int;
enum class net_socket_opt_flags : socket_opt_flags {
non_blocking = SOCK_NONBLOCK,
close_on_exec = SOCK_CLOEXEC,
};
using socket_opt_flags = int;
enum class unix_socket_opt_flags : socket_opt_flags {
non_blocking = SOCK_NONBLOCK,
close_on_exec = SOCK_CLOEXEC,
};
using address = std::variant<sockaddr_in, sockaddr_in6, sockaddr_un>;
template<typename T>
struct stream_expect{
const T v;
stream_expect(T value)
: v(value)
{}
};
template<typename T>
std::istream& operator>>(std::istream& in, const stream_expect<T>& expector)
{
T vin;
in >> vin;
if(vin != expector.v)
throw std::runtime_error("Expector failed");
return in;
}
address make_ipv4(const std::string_view addr, const uint16_t port)
{
std::stringstream din;
din<<addr;
std::array<uint8_t, 4> address{0};
int ex;
din>>ex
>>stream_expect<char>('.');
address[0]=ex;
din>>ex
>>stream_expect<char>('.');
address[1]=ex;
din>>ex
>>stream_expect<char>('.');
address[2]=ex;
din>>ex;
address[3]=ex;
sockaddr_in ret;
in_addr ret_addr;
ret_addr.s_addr = *((uint32_t*)&address);
ret.sin_family = AF_INET;
ret.sin_addr = ret_addr;
ret.sin_port = htons(port);
return ret;
}
// TODO: make an IPv6 parser
//address make_ipv6(const std::string_view addr, const uint16_t port){}
address make_unix(const std::string_view filename)
{
sockaddr_un ret;
if(filename.size()>(sizeof(ret.sun_path)-1))
throw std::runtime_error("Filename too long");
ret.sun_family = AF_UNIX;
*std::copy(filename.begin(), filename.end(), ret.sun_path) = '\0';
return ret;
}
class shared_fd {
int fd;
std::atomic_int* guard;
int last_error;
shared_fd(int fd_v)
: fd(fd_v)
, guard(new std::atomic_int{1})
, last_error(0)
{}
public:
shared_fd()
: fd(-1)
, guard(nullptr)
, last_error(0)
{}
shared_fd(const shared_fd& oth)
: fd(oth.fd)
, guard(oth.guard)
, last_error(0)
{
if(guard)
guard->fetch_add(1, std::memory_order_acq_rel);
}
shared_fd(shared_fd&& oth)
: last_error(0)
{
fd=oth.fd;
guard=oth.guard;
oth.fd=-1;
oth.guard=nullptr;
}
const int get() const {
return fd;
}
void operator=(const shared_fd& oth)
{
this->~shared_fd();
fd = oth.fd;
guard = oth.guard;
if(guard)
guard->fetch_add(1, std::memory_order_acq_rel);
}
void operator=(shared_fd&& oth)
{
std::swap(fd,oth.fd);
std::swap(guard,oth.guard);
}
static shared_fd open(const std::string& filename, const open_opt_flags& flags, const open_opt_mode& mode = 0)
{
shared_fd ret{::open(filename.c_str(), flags, mode)};
return ret;
}
static shared_fd create(const std::string& filename, const open_opt_mode& mode)
{
shared_fd ret{::creat(filename.c_str(), mode)};
return ret;
}
static shared_fd tcp_socket()
{
shared_fd ret;
auto res = ::socket(AF_INET, SOCK_STREAM, 0);
if(res >= 0)
{
ret = shared_fd{res};
fcntl(res, F_SETFL, O_NONBLOCK);
}
else
ret.last_error = errno;
return ret;
}
static shared_fd socket(const socket_domain& dom, const socket_protocol& proto)//, const net_socket_opt_flags& flags)
{
shared_fd ret;
auto res = ::socket((int)dom, (int)proto, (int)0);
if(res >= 0)
{
ret = shared_fd{res};
}
else
ret.last_error = errno;
return ret;
}
static shared_fd unix_socket(const socket_protocol& proto, const socket_opt_flags flags) {
shared_fd ret;
auto res = ::socket((int)AF_UNIX, (int)proto, (int)flags);
if(res >= 0)
ret = shared_fd{res};
return ret;
}
static std::pair<shared_fd,shared_fd> unix_socket_pair(const socket_protocol& proto, const net_socket_opt_flags flags) {
std::pair<gp::shared_fd, gp::shared_fd> ret;
int fds[2];
auto result = ::socketpair((int)AF_UNIX, (int)proto, (int)flags, fds);
if(result != 0)
return ret;
ret.first = shared_fd(fds[0]);
ret.second = shared_fd(fds[1]);
return ret;
}
bool is_valid() const {
return guard && (fd>=0);
}
bool has_failed() const {
return last_error;
}
bool was_connection_refused() const {
return last_error==ECONNREFUSED;
}
bool in_progress() const {
return last_error==EINPROGRESS;
}
bool must_retry() const {
return last_error==EAGAIN;
}
int error() const {
return last_error;
}
void bind(const address& addr)
{
int ret;
std::visit([&](const auto& v){
ret = ::bind(fd, (struct sockaddr*)&v, sizeof(v));
}, addr);
if(ret==0)
{
last_error = 0;
return;
}
else
last_error = errno;
}
void connect(const address& addr)
{
int ret;
std::visit([&](const auto& v){
ret = ::connect(fd, (struct sockaddr*)&v, sizeof(v));
}, addr);
if(ret!=0)
{
last_error = errno;
return;
}
last_error = 0;
}
void listen(const int& backlog = 16)
{
int ret = 0;
std::visit([&](){
ret = ::listen(fd, backlog);
});
if(ret!=0)
{
last_error = errno;
return;
}
last_error = 0;
}
shared_fd accept()
{
int ret = 0;
std::array<uint8_t, 256> buffer;
socklen_t len;
ret = ::accept(fd,(sockaddr*)buffer.begin(), &len);
if(ret!=0)
{
last_error = errno;
return shared_fd{};
}
last_error = 0;
return shared_fd{ret};
}
std::string_view read(const std::string_view buffer)
{
int sz = ::read(fd, (void*)(buffer.begin()), buffer.size());
if(sz<0)
last_error = errno;
else
last_error = 0;
sz = sz<0?0:sz;
return std::string_view(buffer.begin(), sz);
}
std::string_view write(const std::string_view buffer)
{
int sz = ::write(fd, (void*)(buffer.begin()), buffer.size());
if(sz<0)
last_error = errno;
else
last_error = 0;
sz = sz<0?0:sz;
return std::string_view(buffer.begin()+sz, buffer.size()-sz);
}
~shared_fd()
{
if(guard)
if(guard->fetch_sub(1, std::memory_order_acq_rel) == 1)
{
assert(guard->load() == 0);
assert(fd >= 0);
delete guard;
guard = nullptr;
if(fd >= 0)
{
::close(fd);
}
}
}
};
}

+ 0
- 2
include/shared_mmap.hpp Näytä tiedosto

@ -1,2 +0,0 @@
#pragma once
// UNIMPLEMENTED: see filename

+ 0
- 2
include/stored_array.hpp Näytä tiedosto

@ -1,2 +0,0 @@
#pragma once
// UNIMPLEMENTED: see filename

+ 0
- 2
include/stored_clog.hpp Näytä tiedosto

@ -1,2 +0,0 @@
#pragma once
// UNIMPLEMENTED: see filename

+ 0
- 2
include/stored_hmap.hpp Näytä tiedosto

@ -1,2 +0,0 @@
#pragma once
// UNIMPLEMENTED: see filename

+ 0
- 2
include/stored_indexed_array.hpp Näytä tiedosto

@ -1,2 +0,0 @@
#pragma once
// UNIMPLEMENTED: see filename

+ 0
- 2
tests.cpp Näytä tiedosto

@ -2,8 +2,6 @@
#include "allocator.hpp"
#include "gp_config.hpp"
#include "meta_test.cpp"
#include "shared_fd.cpp"
#include "rc6_generic.cpp"
#include "gp_test.cpp"
#include "bloomfilter.cpp"
#include "quotient_filter.cpp"

+ 27
- 0
tests/gp_test.cpp Näytä tiedosto

@ -10,6 +10,7 @@
#include "gp/algorithm/rotate.hpp"
#include "gp/algorithm/move.hpp"
#include "gp/ring_list.hpp"
#include "gp/bitops.hpp"
#include <thread>
#include <chrono>
#include <set>
@ -661,3 +662,29 @@ struct buffer_test : public test_scaffold {
};
append_test dummy_gs87ytf5f5(new buffer_test{});
struct endian_test : public test_scaffold {
endian_test() {
name = __FILE__ ":11";
}
virtual int run() {
int res = 0;
{
gp::endian_wrapper<uint32_t> a = 0x41424344UL;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wfour-char-constants"
#pragma gcc diagnostic push
#pragma gcc diagnostic ignored "-Wfour-char-constants"
gp_config::assertion(a.value != 'ABCD', "Not a big endian in a big endian wrapper");
#pragma gcc diagnostic pop
#pragma clang diagnostic pop
gp_config::assertion(gp::swap_endian<uint32_t>(0x41424344UL)==0x44434241UL, "swap_endian doesn't swap endian");
}
return res;
}
};
append_test dummy_45zelotf5f5(new endian_test{});

+ 0
- 56
tests/rc6_generic.cpp Näytä tiedosto

@ -1,56 +0,0 @@
#include "rc6_generic.hpp"
#include "test_scaffold.h"
#include <iostream>
#include <ios>
struct RC6test : public test_scaffold {
RC6test() {
name = __FILE__ ":1";
}
virtual int run() {
using rc = RC6<>;
auto test = [](rc::key_type key, rc::block_type plaintext, rc::block_type expected) -> bool {
auto cipher = rc{key};
plaintext = cipher.encrypt(plaintext);
return plaintext != expected;
};
auto res = 0;
res += test(
{0, 0, 0, 0},
{0,0,0,0},
{0x8fc3a536,0x56b1f778,0xc129df4e,0x9848a41e}
);
res += test(
{0x80000000, 0x00000000, 0x00000000, 0x00000000},
{0,0,0,0},
{0x1AD578A0, 0x2A081628, 0x50A15A15, 0x52A17AD4}
);
return res;
}
};
append_test dummy_szfhu5463(new RC6test{});
struct RC6test2 : public test_scaffold {
RC6test2() {
name = __FILE__ ":2";
}
virtual int run() {
using rc = RC6<>;
rc::key_type key{0,0,0,0};
rc::block_type plaintext{0,0,0,0};
rc::block_type expected{0,0,0,0};
auto cipher = rc{key};
auto plaintext2 = cipher.encrypt(plaintext);
auto plaintext3 = cipher.decrypt(plaintext2);
return plaintext3 != expected;
}
};
append_test dummy_szmltz63(new RC6test2{});

+ 0
- 286
tests/shared_fd.cpp Näytä tiedosto

@ -1,286 +0,0 @@
#include "shared_fd.hpp"
#include "test_scaffold.h"
#include <thread>
#include <chrono>
#include <iostream>
#include <iomanip>
struct create_test : public test_scaffold {
create_test() {
name = __FILE__ ":1";
}
virtual int run() {
auto fd = gp::shared_fd::create("./bin/test_n", int(gp::open_modes::user_read) | int(gp::open_modes::user_write));
return fd.is_valid()?0:1;
}
};
append_test dummy_sdfhuisd3(new create_test{});
struct open_test : public test_scaffold {
open_test() {
name = __FILE__ ":2";
}
virtual int run() {
auto fd = gp::shared_fd::open("./bin/test_n", int(gp::open_options::append));
return fd.is_valid()?0:1;
}
};
append_test dummy_sdf564dd3(new open_test{});
struct manip_test : public test_scaffold {
manip_test() {
name = __FILE__ ":3";
}
virtual int run() {
auto fd = gp::shared_fd::open("./bin/test_n", int(gp::open_options::append));
int error = fd.is_valid()?0:1;
error += !(fd.get()>=0);
auto fd_cpy = fd;
error += fd_cpy.is_valid()?0:1;
gp::shared_fd constr_cpy(fd_cpy);
error += constr_cpy.is_valid()?0:1;
gp::shared_fd constr_mv(std::move(constr_cpy));
error += constr_mv.is_valid()?0:1;
gp::shared_fd assign_cpy;
assign_cpy.operator=(fd_cpy);
error += assign_cpy.is_valid()?0:1;
gp::shared_fd assign_mv;
assign_mv.operator=(std::move(assign_cpy));
error += assign_mv.is_valid()?0:1;
error += (!assign_cpy.is_valid())?0:1;
return error;
}
};
append_test dummy_lkjs64dd3(new manip_test{});
struct rw_test : public test_scaffold {
rw_test() {
name = __FILE__ ":4";
}
virtual int run() {
auto fd = gp::shared_fd::open("./bin/test_n", int(gp::open_options::write));
int error = fd.is_valid()?0:1;
fd.write("potatoes");
error += fd.has_failed();
fd = gp::shared_fd::open("./bin/test_n", int(gp::open_options::read));
std::array<char,8> buffer;
auto str = fd.read(std::string_view(buffer.begin(), buffer.size()));
error += fd.has_failed();
error += (str != "potatoes");
return error;
}
};
append_test dummy_l6z5e4rdd3(new rw_test{});
struct rw_err_test : public test_scaffold {
rw_err_test() {
name = __FILE__ ":5";
}
virtual int run() {
auto fd = gp::shared_fd::create("./bin/test_n", int(gp::open_modes::user_read) | int(gp::open_modes::user_write));
fd = gp::shared_fd::open("./bin/test_n", int(gp::open_options::read));
int error = fd.is_valid()?0:1;
fd.write("potatoes");
error += fd.has_failed();
fd = gp::shared_fd::open("./bin/test_n", int(gp::open_options::write));
error += fd.is_valid()?0:1;
std::array<char,8> buffer;
auto str = fd.read(std::string_view(buffer.begin(), buffer.size()));
error += fd.has_failed();
return error == 2 ? 0 : 1;
}
};
append_test dummy_l6987erd3(new rw_err_test{});
/*
struct make_address_test : public test_scaffold {
make_address_test() {
name = __FILE__ ":6";
}
virtual int run() {
int error = 0;
gp::address ipv4 = gp::make_ipv4("127.0.0.1", 0x1234);
auto p = std::get<sockaddr_in>(ipv4);
error += (p.sin_family != AF_INET);
error += (p.sin_addr.s_addr != htonl(0x7F000001));
error += (p.sin_port != htons(0x1234));
try{
gp::make_ipv4("not an IP", 1234);
error += 1;
}catch(...){}
std::string filename = "/tmp/my_socket";
gp::address unix = gp::make_unix(filename);
auto q = std::get<sockaddr_un>(unix);
error += (q.sun_family != AF_UNIX);
error += strcmp(filename.c_str(), q.sun_path);
try{
std::string long_str(1024, 'p');
gp::make_unix(long_str);
error += 1;
}catch(...){}
return error;
}
};
append_test dummy_l6923ml3(new make_address_test{});
struct sockets_test : public test_scaffold {
sockets_test() {
name = __FILE__ ":7";
}
virtual int run() {
int error = 0;
auto v = gp::shared_fd::socket(gp::socket_domain::ip4, gp::socket_protocol::tcp_like);
error += !(v.is_valid());
v = gp::shared_fd::socket(gp::socket_domain::ip4, gp::socket_protocol::tcp_like);
error += !(v.is_valid());
auto pair_v = gp::shared_fd::unix_socket_pair(gp::socket_protocol::tcp_like, (gp::net_socket_opt_flags)0);
error += !(pair_v.first.is_valid());
error += !(pair_v.second.is_valid());
pair_v = gp::shared_fd::unix_socket_pair(gp::socket_protocol::tcp_like, (gp::net_socket_opt_flags)-1);
error += pair_v.first.is_valid();
error += pair_v.second.is_valid();
auto u_v = gp::shared_fd::unix_socket(gp::socket_protocol::tcp_like, (gp::socket_opt_flags)0);
error += !(u_v.is_valid());
u_v = gp::shared_fd::unix_socket(gp::socket_protocol::tcp_like, (gp::socket_opt_flags)-1);
error += u_v.is_valid();
return error;
}
};
append_test dummy_r3321443(new sockets_test{});
using namespace std::chrono_literals;
struct sockets_co_test : public test_scaffold {
sockets_co_test() {
name = __FILE__ ":8";
}
virtual int run() {
std::atomic_int error = 0;
{
auto v2 = gp::shared_fd::tcp_socket();
error += !(v2.is_valid());
std::thread sside([&](){
auto v1 = gp::shared_fd::tcp_socket();
v1.bind(gp::make_ipv4("0.0.0.0",1235));
error += v1.has_failed();
v1.listen();
std::cout << "listens?:" << v1.is_valid()<<std::endl;
error += !(v1.is_valid());
gp::shared_fd listener = v1;
gp::shared_fd v3;
do
{
v3 = listener.accept();
std::this_thread::sleep_for(500ms);
std::cout << "accept_attempt:" << v3.error()<<std::endl;
}while(listener.must_retry());
puts("accepted");
error += !(listener.has_failed());
do
{
v3.write("potatoes");
std::this_thread::sleep_for(1ms);
}while(v3.must_retry());
puts("sent");
error += !(v3.has_failed());
return;
});
std::this_thread::sleep_for(6ms);
v2.connect(gp::make_ipv4("255.255.255.255",1235));
error += v2.has_failed();
do{
v2.connect(gp::make_ipv4("127.0.0.1",1235));
std::this_thread::sleep_for(500ms);
std::cout << "connect_attempt:" << v2.is_valid() << " with " << v2.error()<<std::endl;
}while(v2.error() != EISCONN);
std::this_thread::sleep_for(1ms);
auto status = v2.was_connection_refused();
error += status;
if(!status)
{
puts("connected");
std::array<char, 32> buffer;
std::string_view retstr;
do
{
retstr = v2.read(std::string_view(buffer.begin(), buffer.size()));
std::this_thread::sleep_for(1ms);
}
while(v2.must_retry());
puts("received");
std::cout<< "out:" << std::quoted(retstr) << std::endl;
error += !(v2.has_failed());
error += retstr != "potatoes";
if(sside.joinable())
sside.join();
}
return error;
}
return error;
}
};
// append_test dummy_polmdf43(new sockets_co_test{});
*/

Ladataan…
Peruuta
Tallenna