Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
Actor Framework
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
cpp-libs
Actor Framework
Commits
3e4f2dfa
Unverified
Commit
3e4f2dfa
authored
Jul 14, 2023
by
Dominik Charousset
Committed by
GitHub
Jul 14, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1488
Drop unnecessary arguments in octet_stream::policy
parents
bc88f45e
19ea85cd
Changes
11
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
121 additions
and
82 deletions
+121
-82
libcaf_net/caf/net/http/server_factory.hpp
libcaf_net/caf/net/http/server_factory.hpp
+1
-2
libcaf_net/caf/net/lp/client_factory.hpp
libcaf_net/caf/net/lp/client_factory.hpp
+1
-2
libcaf_net/caf/net/lp/server_factory.hpp
libcaf_net/caf/net/lp/server_factory.hpp
+1
-2
libcaf_net/caf/net/octet_stream/policy.hpp
libcaf_net/caf/net/octet_stream/policy.hpp
+9
-6
libcaf_net/caf/net/octet_stream/transport.hpp
libcaf_net/caf/net/octet_stream/transport.hpp
+29
-7
libcaf_net/caf/net/ssl/transport.hpp
libcaf_net/caf/net/ssl/transport.hpp
+8
-6
libcaf_net/caf/net/web_socket/client_factory.hpp
libcaf_net/caf/net/web_socket/client_factory.hpp
+1
-2
libcaf_net/caf/net/web_socket/server_factory.hpp
libcaf_net/caf/net/web_socket/server_factory.hpp
+1
-2
libcaf_net/src/net/octet_stream/policy.cpp
libcaf_net/src/net/octet_stream/policy.cpp
+0
-26
libcaf_net/src/net/octet_stream/transport.cpp
libcaf_net/src/net/octet_stream/transport.cpp
+55
-14
libcaf_net/src/net/ssl/transport.cpp
libcaf_net/src/net/ssl/transport.cpp
+15
-13
No files found.
libcaf_net/caf/net/http/server_factory.hpp
View file @
3e4f2dfa
...
...
@@ -82,10 +82,9 @@ public:
connection_handle
conn
)
override
{
auto
app
=
net
::
http
::
router
::
make
(
routes_
);
auto
serv
=
net
::
http
::
server
::
make
(
std
::
move
(
app
));
auto
fd
=
conn
.
fd
();
auto
transport
=
Transport
::
make
(
std
::
move
(
conn
),
std
::
move
(
serv
));
transport
->
max_consecutive_reads
(
max_consecutive_reads_
);
transport
->
active_policy
().
accept
(
fd
);
transport
->
active_policy
().
accept
();
auto
res
=
net
::
socket_manager
::
make
(
mpx
,
std
::
move
(
transport
));
mpx
->
watch
(
res
->
as_disposable
());
return
res
;
...
...
libcaf_net/caf/net/lp/client_factory.hpp
View file @
3e4f2dfa
...
...
@@ -102,9 +102,8 @@ private:
auto
bridge
=
bridge_t
::
make
(
std
::
move
(
a2s_pull
),
std
::
move
(
s2a_push
));
auto
bridge_ptr
=
bridge
.
get
();
auto
impl
=
framing
::
make
(
std
::
move
(
bridge
));
auto
fd
=
conn
.
fd
();
auto
transport
=
transport_t
::
make
(
std
::
move
(
conn
),
std
::
move
(
impl
));
transport
->
active_policy
().
connect
(
fd
);
transport
->
active_policy
().
connect
();
auto
ptr
=
socket_manager
::
make
(
cfg
.
mpx
,
std
::
move
(
transport
));
bridge_ptr
->
self_ref
(
ptr
->
as_disposable
());
cfg
.
mpx
->
start
(
ptr
);
...
...
libcaf_net/caf/net/lp/server_factory.hpp
View file @
3e4f2dfa
...
...
@@ -99,10 +99,9 @@ public:
auto
bridge
=
bridge_t
::
make
(
producer_
);
auto
bridge_ptr
=
bridge
.
get
();
auto
impl
=
net
::
lp
::
framing
::
make
(
std
::
move
(
bridge
));
auto
fd
=
conn
.
fd
();
auto
transport
=
Transport
::
make
(
std
::
move
(
conn
),
std
::
move
(
impl
));
transport
->
max_consecutive_reads
(
max_consecutive_reads_
);
transport
->
active_policy
().
accept
(
fd
);
transport
->
active_policy
().
accept
();
auto
mgr
=
net
::
socket_manager
::
make
(
mpx
,
std
::
move
(
transport
));
bridge_ptr
->
self_ref
(
mgr
->
as_disposable
());
return
mgr
;
...
...
libcaf_net/caf/net/octet_stream/policy.hpp
View file @
3e4f2dfa
...
...
@@ -18,25 +18,28 @@ class CAF_NET_EXPORT policy {
public:
virtual
~
policy
();
/// Returns the handle for the managed socket.
virtual
stream_socket
handle
()
const
=
0
;
/// Reads data from the socket into the buffer.
virtual
ptrdiff_t
read
(
stream_socket
x
,
byte_span
buf
)
;
virtual
ptrdiff_t
read
(
byte_span
buf
)
=
0
;
/// Writes data from the buffer to the socket.
virtual
ptrdiff_t
write
(
stream_socket
x
,
const_byte_span
buf
)
;
virtual
ptrdiff_t
write
(
const_byte_span
buf
)
=
0
;
/// Returns the last socket error on this thread.
virtual
errc
last_error
(
stream_socket
,
ptrdiff_t
)
;
virtual
errc
last_error
(
ptrdiff_t
)
=
0
;
/// Checks whether connecting a non-blocking socket was successful.
virtual
ptrdiff_t
connect
(
stream_socket
x
)
;
virtual
ptrdiff_t
connect
(
)
=
0
;
/// Convenience function that always returns 1. Exists to make writing code
/// against multiple policies easier by providing the same interface.
virtual
ptrdiff_t
accept
(
stream_socket
)
;
virtual
ptrdiff_t
accept
(
)
=
0
;
/// Returns the number of bytes that are buffered internally and available
/// for immediate read.
virtual
size_t
buffered
()
const
noexcept
;
virtual
size_t
buffered
()
const
noexcept
=
0
;
};
}
// namespace caf::net::octet_stream
libcaf_net/caf/net/octet_stream/transport.hpp
View file @
3e4f2dfa
...
...
@@ -43,6 +43,29 @@ public:
bool
shutting_down
:
1
;
};
class
policy_impl
:
public
octet_stream
::
policy
{
public:
explicit
policy_impl
(
stream_socket
fd
)
:
fd
(
std
::
move
(
fd
))
{
// nop
}
stream_socket
handle
()
const
override
;
ptrdiff_t
read
(
byte_span
)
override
;
ptrdiff_t
write
(
const_byte_span
)
override
;
octet_stream
::
errc
last_error
(
ptrdiff_t
)
override
;
ptrdiff_t
connect
()
override
;
ptrdiff_t
accept
()
override
;
size_t
buffered
()
const
noexcept
override
;
stream_socket
fd
;
};
// -- constants --------------------------------------------------------------
static
constexpr
size_t
default_buf_size
=
4
*
1024
;
// 4 KiB
...
...
@@ -51,12 +74,14 @@ public:
transport
(
stream_socket
fd
,
upper_layer_ptr
up
);
transport
(
stream_socket
fd
,
upper_layer_ptr
up
,
policy
*
custom
);
transport
(
policy
*
custom
,
upper_layer_ptr
up
);
transport
(
const
transport
&
)
=
delete
;
transport
&
operator
=
(
const
transport
&
)
=
delete
;
~
transport
();
// -- factories --------------------------------------------------------------
static
std
::
unique_ptr
<
transport
>
make
(
stream_socket
fd
,
upper_layer_ptr
up
);
...
...
@@ -148,9 +173,6 @@ protected:
// -- member variables -------------------------------------------------------
/// The socket file descriptor.
stream_socket
fd_
;
/// Stores temporary flags.
flags_t
flags_
;
...
...
@@ -190,13 +212,13 @@ protected:
policy
*
policy_
=
nullptr
;
/// Fallback policy.
policy
default_policy_
;
union
{
policy_impl
default_policy_
;
};
/// Setting this to non-null informs the transport to replace `up_` with
/// `next_`.
upper_layer_ptr
next_
;
// TODO: add [[no_unique_address]] to default_policy_ when switching to C++20.
};
}
// namespace caf::net::octet_stream
libcaf_net/caf/net/ssl/transport.hpp
View file @
3e4f2dfa
...
...
@@ -26,15 +26,17 @@ public:
public:
explicit
policy_impl
(
connection
conn
);
ptrdiff_t
read
(
stream_socket
,
byte_span
)
override
;
stream_socket
handle
()
const
override
;
ptrdiff_t
write
(
stream_socket
,
const_
byte_span
)
override
;
ptrdiff_t
read
(
byte_span
)
override
;
octet_stream
::
errc
last_error
(
stream_socket
,
ptrdiff_t
)
override
;
ptrdiff_t
write
(
const_byte_span
)
override
;
ptrdiff_t
connect
(
stream_socke
t
)
override
;
octet_stream
::
errc
last_error
(
ptrdiff_
t
)
override
;
ptrdiff_t
accept
(
stream_socket
)
override
;
ptrdiff_t
connect
()
override
;
ptrdiff_t
accept
()
override
;
size_t
buffered
()
const
noexcept
override
;
...
...
@@ -66,7 +68,7 @@ public:
private:
// -- constructors, destructors, and assignment operators --------------------
transport
(
stream_socket
fd
,
connection
conn
,
upper_layer_ptr
up
);
transport
(
connection
conn
,
upper_layer_ptr
up
);
policy_impl
policy_impl_
;
};
...
...
libcaf_net/caf/net/web_socket/client_factory.hpp
View file @
3e4f2dfa
...
...
@@ -98,9 +98,8 @@ private:
auto
bridge
=
bridge_t
::
make
(
std
::
move
(
a2s_pull
),
std
::
move
(
s2a_push
));
auto
bridge_ptr
=
bridge
.
get
();
auto
impl
=
client
::
make
(
std
::
move
(
cfg
.
hs
),
std
::
move
(
bridge
));
auto
fd
=
conn
.
fd
();
auto
transport
=
transport_t
::
make
(
std
::
move
(
conn
),
std
::
move
(
impl
));
transport
->
active_policy
().
connect
(
fd
);
transport
->
active_policy
().
connect
();
auto
ptr
=
socket_manager
::
make
(
cfg
.
mpx
,
std
::
move
(
transport
));
bridge_ptr
->
self_ref
(
ptr
->
as_disposable
());
cfg
.
mpx
->
start
(
ptr
);
...
...
libcaf_net/caf/net/web_socket/server_factory.hpp
View file @
3e4f2dfa
...
...
@@ -129,10 +129,9 @@ public:
auto
app
=
bridge_t
::
make
(
on_request_
,
producer_
);
auto
app_ptr
=
app
.
get
();
auto
ws
=
net
::
web_socket
::
server
::
make
(
std
::
move
(
app
));
auto
fd
=
conn
.
fd
();
auto
transport
=
Transport
::
make
(
std
::
move
(
conn
),
std
::
move
(
ws
));
transport
->
max_consecutive_reads
(
max_consecutive_reads_
);
transport
->
active_policy
().
accept
(
fd
);
transport
->
active_policy
().
accept
();
auto
mgr
=
net
::
socket_manager
::
make
(
mpx
,
std
::
move
(
transport
));
app_ptr
->
self_ref
(
mgr
->
as_disposable
());
return
mgr
;
...
...
libcaf_net/src/net/octet_stream/policy.cpp
View file @
3e4f2dfa
...
...
@@ -9,30 +9,4 @@ policy::~policy() {
// nop
}
ptrdiff_t
policy
::
read
(
stream_socket
x
,
byte_span
buf
)
{
return
net
::
read
(
x
,
buf
);
}
ptrdiff_t
policy
::
write
(
stream_socket
x
,
const_byte_span
buf
)
{
return
net
::
write
(
x
,
buf
);
}
errc
policy
::
last_error
(
stream_socket
,
ptrdiff_t
)
{
return
last_socket_error_is_temporary
()
?
errc
::
temporary
:
errc
::
permanent
;
}
ptrdiff_t
policy
::
connect
(
stream_socket
x
)
{
// A connection is established if the OS reports a socket as ready for read
// or write and if there is no error on the socket.
return
probe
(
x
)
?
1
:
-
1
;
}
ptrdiff_t
policy
::
accept
(
stream_socket
)
{
return
1
;
}
size_t
policy
::
buffered
()
const
noexcept
{
return
0
;
}
}
// namespace caf::net::octet_stream
libcaf_net/src/net/octet_stream/transport.cpp
View file @
3e4f2dfa
...
...
@@ -9,20 +9,61 @@
#include "caf/net/receive_policy.hpp"
#include "caf/net/socket_manager.hpp"
#include <new>
namespace
caf
::
net
::
octet_stream
{
// -- nested types -------------------------------------------------------------
stream_socket
transport
::
policy_impl
::
handle
()
const
{
return
fd
;
}
ptrdiff_t
transport
::
policy_impl
::
read
(
byte_span
buf
)
{
return
net
::
read
(
fd
,
buf
);
}
ptrdiff_t
transport
::
policy_impl
::
write
(
const_byte_span
buf
)
{
return
net
::
write
(
fd
,
buf
);
}
errc
transport
::
policy_impl
::
last_error
(
ptrdiff_t
)
{
return
last_socket_error_is_temporary
()
?
errc
::
temporary
:
errc
::
permanent
;
}
ptrdiff_t
transport
::
policy_impl
::
connect
()
{
// A connection is established if the OS reports a socket as ready for read
// or write and if there is no error on the socket.
return
probe
(
fd
)
?
1
:
-
1
;
}
ptrdiff_t
transport
::
policy_impl
::
accept
()
{
return
1
;
}
size_t
transport
::
policy_impl
::
buffered
()
const
noexcept
{
return
0
;
}
// -- constructors, destructors, and assignment operators ----------------------
transport
::
transport
(
stream_socket
fd
,
upper_layer_ptr
up
)
:
fd_
(
fd
),
up_
(
std
::
move
(
up
)),
policy_
(
&
default_policy_
)
{
:
up_
(
std
::
move
(
up
)
)
{
memset
(
&
flags_
,
0
,
sizeof
(
flags_t
));
new
(
&
default_policy_
)
policy_impl
(
fd
);
policy_
=
&
default_policy_
;
}
transport
::
transport
(
stream_socket
fd
,
upper_layer_ptr
up
,
policy
*
policy
)
:
fd_
(
fd
),
up_
(
std
::
move
(
up
)),
policy_
(
policy
)
{
transport
::
transport
(
policy
*
policy
,
upper_layer_ptr
up
)
:
up_
(
std
::
move
(
up
)),
policy_
(
policy
)
{
memset
(
&
flags_
,
0
,
sizeof
(
flags_t
));
}
transport
::~
transport
()
{
if
(
policy_
==
&
default_policy_
)
default_policy_
.
~
policy_impl
();
}
// -- factories ----------------------------------------------------------------
std
::
unique_ptr
<
transport
>
transport
::
make
(
stream_socket
fd
,
...
...
@@ -110,7 +151,7 @@ error transport::start(socket_manager* owner) {
// CAF_LOG_ERROR("nodelay failed: " << err);
// return err;
// }
if
(
auto
socket_buf_size
=
send_buffer_size
(
fd_
))
{
if
(
auto
socket_buf_size
=
send_buffer_size
(
policy_
->
handle
()
))
{
max_write_buf_size_
=
*
socket_buf_size
;
CAF_ASSERT
(
max_write_buf_size_
>
0
);
write_buf_
.
reserve
(
max_write_buf_size_
*
2
);
...
...
@@ -122,11 +163,11 @@ error transport::start(socket_manager* owner) {
}
socket
transport
::
handle
()
const
{
return
fd_
;
return
policy_
->
handle
()
;
}
void
transport
::
handle_read_event
()
{
CAF_LOG_TRACE
(
CAF_ARG2
(
"socket"
,
fd_
.
id
));
CAF_LOG_TRACE
(
CAF_ARG2
(
"socket"
,
handle
()
));
// Resume a write operation if the transport waited for the socket to be
// readable from the last call to handle_write_event.
if
(
flags_
.
wanted_read_from_write_event
)
{
...
...
@@ -150,11 +191,11 @@ void transport::handle_read_event() {
if
(
read_buf_
.
size
()
<
max_read_size_
)
read_buf_
.
resize
(
max_read_size_
);
// Fill up our buffer.
auto
rd
=
policy_
->
read
(
fd_
,
make_span
(
read_buf_
.
data
()
+
buffered_
,
read_buf_
.
size
()
-
buffered_
));
auto
rd
=
policy_
->
read
(
make_span
(
read_buf_
.
data
()
+
buffered_
,
read_buf_
.
size
()
-
buffered_
));
// Stop if we failed to get more data.
if
(
rd
<
0
)
{
switch
(
policy_
->
last_error
(
fd_
,
rd
))
{
switch
(
policy_
->
last_error
(
rd
))
{
case
errc
:
:
temporary
:
case
errc
:
:
want_read
:
// Try again later.
...
...
@@ -178,8 +219,8 @@ void transport::handle_read_event() {
if
(
auto
policy_buffered
=
policy_
->
buffered
();
policy_buffered
>
0
)
{
if
(
auto
n
=
buffered_
+
policy_buffered
;
n
>
read_buf_
.
size
())
read_buf_
.
resize
(
n
);
auto
rd2
=
policy_
->
read
(
fd_
,
make_span
(
read_buf_
.
data
()
+
buffered_
,
policy_buffered
));
auto
rd2
=
policy_
->
read
(
make_span
(
read_buf_
.
data
()
+
buffered_
,
policy_buffered
));
if
(
rd2
!=
static_cast
<
ptrdiff_t
>
(
policy_buffered
))
{
CAF_LOG_ERROR
(
"failed to read buffered data from the policy"
);
return
fail
(
make_error
(
sec
::
socket_operation_failed
));
...
...
@@ -267,7 +308,7 @@ void transport::fail(const error& reason) {
}
void
transport
::
handle_write_event
()
{
CAF_LOG_TRACE
(
CAF_ARG2
(
"socket"
,
fd_
.
id
));
CAF_LOG_TRACE
(
CAF_ARG2
(
"socket"
,
handle
()
));
// Resume a read operation if the transport waited for the socket to be
// writable from the last call to handle_read_event.
if
(
flags_
.
wanted_write_from_read_event
)
{
...
...
@@ -291,7 +332,7 @@ void transport::handle_write_event() {
// Allow the upper layer to add extra data to the write buffer.
up_
->
prepare_send
();
}
auto
write_res
=
policy_
->
write
(
fd_
,
write_buf_
);
auto
write_res
=
policy_
->
write
(
write_buf_
);
if
(
write_res
>
0
)
{
write_buf_
.
erase
(
write_buf_
.
begin
(),
write_buf_
.
begin
()
+
write_res
);
if
(
write_buf_
.
empty
()
&&
up_
->
done_sending
())
{
...
...
@@ -305,7 +346,7 @@ void transport::handle_write_event() {
}
else
if
(
write_res
<
0
)
{
// Try again later on temporary errors such as EWOULDBLOCK and
// stop writing to the socket on hard errors.
switch
(
policy_
->
last_error
(
fd_
,
write_res
))
{
switch
(
policy_
->
last_error
(
write_res
))
{
case
errc
:
:
temporary
:
case
errc
:
:
want_write
:
return
;
...
...
libcaf_net/src/net/ssl/transport.cpp
View file @
3e4f2dfa
...
...
@@ -53,7 +53,7 @@ public:
up_
->
abort
(
make_error
(
sec
::
connection_closed
));
owner_
->
deregister
();
}
else
{
auto
err
=
policy_
.
last_error
(
policy_
.
conn
.
fd
(),
res
);
auto
err
=
policy_
.
last_error
(
res
);
switch
(
err
)
{
case
octet_stream
:
:
errc
::
want_read
:
case
octet_stream
:
:
errc
::
temporary
:
...
...
@@ -81,7 +81,7 @@ public:
up_
->
abort
(
make_error
(
sec
::
connection_closed
));
owner_
->
deregister
();
}
else
{
switch
(
policy_
.
last_error
(
policy_
.
conn
.
fd
(),
res
))
{
switch
(
policy_
.
last_error
(
res
))
{
case
octet_stream
:
:
errc
::
want_write
:
case
octet_stream
:
:
errc
::
temporary
:
break
;
...
...
@@ -133,16 +133,19 @@ transport::policy_impl::policy_impl(connection conn) : conn(std::move(conn)) {
// nop
}
ptrdiff_t
transport
::
policy_impl
::
read
(
stream_socket
,
byte_span
buf
)
{
stream_socket
transport
::
policy_impl
::
handle
()
const
{
return
conn
.
fd
();
}
ptrdiff_t
transport
::
policy_impl
::
read
(
byte_span
buf
)
{
return
conn
.
read
(
buf
);
}
ptrdiff_t
transport
::
policy_impl
::
write
(
stream_socket
,
const_byte_span
buf
)
{
ptrdiff_t
transport
::
policy_impl
::
write
(
const_byte_span
buf
)
{
return
conn
.
write
(
buf
);
}
octet_stream
::
errc
transport
::
policy_impl
::
last_error
(
stream_socket
fd
,
ptrdiff_t
ret
)
{
octet_stream
::
errc
transport
::
policy_impl
::
last_error
(
ptrdiff_t
ret
)
{
switch
(
conn
.
last_error
(
ret
))
{
case
errc
:
:
none
:
case
errc
:
:
want_accept
:
...
...
@@ -151,7 +154,7 @@ octet_stream::errc transport::policy_impl::last_error(stream_socket fd,
return
octet_stream
::
errc
::
temporary
;
case
errc
:
:
syscall_failed
:
// Need to consult errno, which we just leave to the default policy.
return
octet_stream
::
policy
{}.
last_error
(
fd
,
ret
);
return
super
::
policy_impl
{
conn
.
fd
()}.
last_error
(
ret
);
case
errc
:
:
want_read
:
return
octet_stream
::
errc
::
want_read
;
case
errc
:
:
want_write
:
...
...
@@ -163,11 +166,11 @@ octet_stream::errc transport::policy_impl::last_error(stream_socket fd,
}
}
ptrdiff_t
transport
::
policy_impl
::
connect
(
stream_socket
)
{
ptrdiff_t
transport
::
policy_impl
::
connect
()
{
return
conn
.
connect
();
}
ptrdiff_t
transport
::
policy_impl
::
accept
(
stream_socket
)
{
ptrdiff_t
transport
::
policy_impl
::
accept
()
{
return
conn
.
accept
();
}
...
...
@@ -180,8 +183,7 @@ size_t transport::policy_impl::buffered() const noexcept {
std
::
unique_ptr
<
transport
>
transport
::
make
(
connection
conn
,
upper_layer_ptr
up
)
{
// Note: can't use make_unique because the constructor is private.
auto
fd
=
conn
.
fd
();
auto
ptr
=
new
transport
(
fd
,
std
::
move
(
conn
),
std
::
move
(
up
));
auto
ptr
=
new
transport
(
std
::
move
(
conn
),
std
::
move
(
up
));
return
std
::
unique_ptr
<
transport
>
{
ptr
};
}
...
...
@@ -199,8 +201,8 @@ transport::worker_ptr transport::make_client(connection conn,
// -- constructors, destructors, and assignment operators ----------------------
transport
::
transport
(
stream_socket
fd
,
connection
conn
,
upper_layer_ptr
up
)
:
super
(
fd
,
std
::
move
(
up
),
&
policy_impl_
),
policy_impl_
(
std
::
move
(
conn
))
{
transport
::
transport
(
connection
conn
,
upper_layer_ptr
up
)
:
super
(
&
policy_impl_
,
std
::
move
(
up
)
),
policy_impl_
(
std
::
move
(
conn
))
{
// nop
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment