Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
O
OpenXG-RAN
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
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
alex037yang
OpenXG-RAN
Commits
e7faea35
Commit
e7faea35
authored
Apr 25, 2016
by
Cedric Roux
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
split tracer in local/remote and some work on the new gui
parent
4e88857b
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
230 additions
and
164 deletions
+230
-164
common/utils/T/tracer/Makefile
common/utils/T/tracer/Makefile
+7
-26
common/utils/T/tracer/Makefile.local
common/utils/T/tracer/Makefile.local
+21
-0
common/utils/T/tracer/Makefile.remote
common/utils/T/tracer/Makefile.remote
+26
-0
common/utils/T/tracer/defs.h
common/utils/T/tracer/defs.h
+5
-0
common/utils/T/tracer/forward.c
common/utils/T/tracer/forward.c
+12
-2
common/utils/T/tracer/local.c
common/utils/T/tracer/local.c
+138
-0
common/utils/T/tracer/remote.c
common/utils/T/tracer/remote.c
+21
-136
No files found.
common/utils/T/tracer/Makefile
View file @
e7faea35
CC
=
gcc
CFLAGS
=
-Wall
-g
-pthread
-DT_TRACER
all
:
local remote
#CFLAGS += -O3 -ffast-math -fomit-frame-pointer
local
:
make
-f
Makefile.local
LIBS
=
-lX11
-lm
#comment those two lines to NOT use shared memory
CFLAGS
+=
-DT_USE_SHARED_MEMORY
LIBS
+=
-lrt
PROG
=
tracer
OBJS
=
main.o plot.o database.o forward.o gui.o
$(PROG)
:
gui/gui.a $(OBJS)
$(CC)
$(CFLAGS)
-o
$(PROG)
$(OBJS)
gui/gui.a
$(LIBS)
.PHONY
:
gui/gui.a
gui/gui.a
:
cd
gui
&&
make
%.o
:
%.c
$(CC)
$(CFLAGS)
-c
-o
$@
$<
main.o
:
../T_IDs.h ../T_defs.h
remote
:
make
-f
Makefile.remote
clean
:
rm
-f
*
.o
$(PROG)
core
cd
gui
&&
mak
e clean
make
-f
Makefile.local clean
make
-f
Makefile.remot
e clean
common/utils/T/tracer/Makefile.local
0 → 100644
View file @
e7faea35
CC
=
gcc
CFLAGS
=
-Wall
-g
-pthread
-DT_TRACER
#CFLAGS += -O3 -ffast-math -fomit-frame-pointer
CFLAGS
+=
-DT_USE_SHARED_MEMORY
LIBS
+=
-lrt
PROG
=
tracer_local
OBJS
=
local.o forward.o
$(PROG)
:
$(OBJS)
$(CC)
$(CFLAGS)
-o
$(PROG)
$(OBJS)
$(LIBS)
%.o
:
%.c
$(CC)
$(CFLAGS)
-c
-o
$@
$<
main.o
:
../T_IDs.h ../T_defs.h
clean
:
rm
-f
*
.o
$(PROG)
core
common/utils/T/tracer/Makefile.remote
0 → 100644
View file @
e7faea35
CC
=
gcc
CFLAGS
=
-Wall
-g
-pthread
-DT_TRACER
#CFLAGS += -O3 -ffast-math -fomit-frame-pointer
LIBS
=
-lX11
-lm
PROG
=
tracer_remote
OBJS
=
remote.o plot.o database.o gui.o
$(PROG)
:
gui/gui.a $(OBJS)
$(CC)
$(CFLAGS)
-o
$(PROG)
$(OBJS)
gui/gui.a
$(LIBS)
.PHONY
:
gui/gui.a
gui/gui.a
:
cd
gui
&&
make
%.o
:
%.c
$(CC)
$(CFLAGS)
-c
-o
$@
$<
main.o
:
../T_IDs.h ../T_defs.h
clean
:
rm
-f
*
.o
$(PROG)
core
cd
gui
&&
make clean
common/utils/T/tracer/defs.h
View file @
e7faea35
...
...
@@ -27,4 +27,9 @@ void *forwarder(char *ip, int port);
void
forward
(
void
*
forwarder
,
char
*
buf
,
int
size
);
void
forward_start_client
(
void
*
forwarder
,
int
socket
);
/* T gui functions */
void
t_gui_start
(
void
);
void
t_gui_set_input_signal
(
int
eNB
,
int
frame
,
int
subframe
,
int
antenna
,
int
size
,
void
*
buf
);
#endif
/* _TRACER_DEFS_H_ */
common/utils/T/tracer/forward.c
View file @
e7faea35
...
...
@@ -122,6 +122,9 @@ void *forwarder(char *ip, int port)
f
->
sc
=
-
1
;
f
->
head
=
f
->
tail
=
NULL
;
printf
(
"connecting to remote tracer %s:%d
\n
"
,
ip
,
port
);
again:
f
->
s
=
socket
(
AF_INET
,
SOCK_STREAM
,
0
);
if
(
f
->
s
==
-
1
)
{
perror
(
"socket"
);
exit
(
1
);
}
...
...
@@ -129,8 +132,15 @@ void *forwarder(char *ip, int port)
a
.
sin_port
=
htons
(
port
);
a
.
sin_addr
.
s_addr
=
inet_addr
(
ip
);
if
(
connect
(
f
->
s
,
(
struct
sockaddr
*
)
&
a
,
sizeof
(
a
))
==
-
1
)
{
perror
(
"connect"
);
exit
(
1
);
}
if
(
connect
(
f
->
s
,
(
struct
sockaddr
*
)
&
a
,
sizeof
(
a
))
==
-
1
)
{
perror
(
"connect"
);
close
(
f
->
s
);
printf
(
"trying again in 1s
\n
"
);
sleep
(
1
);
goto
again
;
}
printf
(
"connected
\n
"
);
new_thread
(
data_sender
,
f
);
...
...
common/utils/T/tracer/local.c
0 → 100644
View file @
e7faea35
#include <stdio.h>
#include <string.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include "defs.h"
#include "../T_defs.h"
T_cache_t
*
T_cache
;
int
T_busylist_head
;
int
T_pos
;
int
get_connection
(
char
*
addr
,
int
port
)
{
struct
sockaddr_in
a
;
socklen_t
alen
;
int
s
,
t
;
printf
(
"waiting for connection on %s:%d
\n
"
,
addr
,
port
);
s
=
socket
(
AF_INET
,
SOCK_STREAM
,
0
);
if
(
s
==
-
1
)
{
perror
(
"socket"
);
exit
(
1
);
}
t
=
1
;
if
(
setsockopt
(
s
,
SOL_SOCKET
,
SO_REUSEADDR
,
&
t
,
sizeof
(
int
)))
{
perror
(
"setsockopt"
);
exit
(
1
);
}
a
.
sin_family
=
AF_INET
;
a
.
sin_port
=
htons
(
port
);
a
.
sin_addr
.
s_addr
=
inet_addr
(
addr
);
if
(
bind
(
s
,
(
struct
sockaddr
*
)
&
a
,
sizeof
(
a
)))
{
perror
(
"bind"
);
exit
(
1
);
}
if
(
listen
(
s
,
5
))
{
perror
(
"bind"
);
exit
(
1
);
}
alen
=
sizeof
(
a
);
t
=
accept
(
s
,
(
struct
sockaddr
*
)
&
a
,
&
alen
);
if
(
t
==
-
1
)
{
perror
(
"accept"
);
exit
(
1
);
}
close
(
s
);
printf
(
"connected
\n
"
);
return
t
;
}
void
wait_message
(
void
)
{
while
(
T_cache
[
T_busylist_head
].
busy
==
0
)
usleep
(
1000
);
}
void
init_shm
(
void
)
{
int
i
;
int
s
=
shm_open
(
T_SHM_FILENAME
,
O_RDWR
|
O_CREAT
/*| O_SYNC*/
,
0666
);
if
(
s
==
-
1
)
{
perror
(
T_SHM_FILENAME
);
abort
();
}
if
(
ftruncate
(
s
,
T_CACHE_SIZE
*
sizeof
(
T_cache_t
)))
{
perror
(
T_SHM_FILENAME
);
abort
();
}
T_cache
=
mmap
(
NULL
,
T_CACHE_SIZE
*
sizeof
(
T_cache_t
),
PROT_READ
|
PROT_WRITE
,
MAP_SHARED
,
s
,
0
);
if
(
T_cache
==
NULL
)
{
perror
(
T_SHM_FILENAME
);
abort
();
}
close
(
s
);
/* let's garbage the memory to catch some potential problems
* (think multiprocessor sync issues, barriers, etc.)
*/
memset
(
T_cache
,
0x55
,
T_CACHE_SIZE
*
sizeof
(
T_cache_t
));
for
(
i
=
0
;
i
<
T_CACHE_SIZE
;
i
++
)
T_cache
[
i
].
busy
=
0
;
}
void
new_thread
(
void
*
(
*
f
)(
void
*
),
void
*
data
)
{
pthread_t
t
;
pthread_attr_t
att
;
if
(
pthread_attr_init
(
&
att
))
{
fprintf
(
stderr
,
"pthread_attr_init err
\n
"
);
exit
(
1
);
}
if
(
pthread_attr_setdetachstate
(
&
att
,
PTHREAD_CREATE_DETACHED
))
{
fprintf
(
stderr
,
"pthread_attr_setdetachstate err
\n
"
);
exit
(
1
);
}
if
(
pthread_attr_setstacksize
(
&
att
,
10000000
))
{
fprintf
(
stderr
,
"pthread_attr_setstacksize err
\n
"
);
exit
(
1
);
}
if
(
pthread_create
(
&
t
,
&
att
,
f
,
data
))
{
fprintf
(
stderr
,
"pthread_create err
\n
"
);
exit
(
1
);
}
if
(
pthread_attr_destroy
(
&
att
))
{
fprintf
(
stderr
,
"pthread_attr_destroy err
\n
"
);
exit
(
1
);
}
}
void
usage
(
void
)
{
printf
(
"tracer - local side
\n
"
"options:
\n
"
" -l <IP address> <port> local side (forwards packets to remote IP:port)
\n
"
);
exit
(
1
);
}
int
main
(
int
n
,
char
**
v
)
{
int
s
;
int
i
;
char
*
remote_ip
=
NULL
;
int
remote_port
=
-
1
;
int
port
=
2020
;
void
*
f
;
for
(
i
=
1
;
i
<
n
;
i
++
)
{
if
(
!
strcmp
(
v
[
i
],
"-h"
)
||
!
strcmp
(
v
[
i
],
"--help"
))
usage
();
if
(
!
strcmp
(
v
[
i
],
"-l"
))
{
if
(
i
>
n
-
3
)
usage
();
remote_ip
=
v
[
++
i
];
remote_port
=
atoi
(
v
[
++
i
]);
continue
;
}
printf
(
"ERROR: unknown option %s
\n
"
,
v
[
i
]);
usage
();
}
if
(
remote_port
==
-
1
||
remote_ip
==
NULL
)
usage
();
f
=
forwarder
(
remote_ip
,
remote_port
);
init_shm
();
s
=
get_connection
(
"127.0.0.1"
,
port
);
forward_start_client
(
f
,
s
);
/* read messages */
while
(
1
)
{
wait_message
();
__sync_synchronize
();
forward
(
f
,
T_cache
[
T_busylist_head
].
buffer
,
T_cache
[
T_busylist_head
].
length
);
T_cache
[
T_busylist_head
].
busy
=
0
;
T_busylist_head
++
;
T_busylist_head
&=
T_CACHE_SIZE
-
1
;
}
return
0
;
}
common/utils/T/tracer/
main
.c
→
common/utils/T/tracer/
remote
.c
View file @
e7faea35
...
...
@@ -12,6 +12,8 @@
#include "defs.h"
#define DEFAULT_REMOTE_PORT 2020
#define T_ID(x) x
#include "../T_IDs.h"
#include "../T_defs.h"
...
...
@@ -25,26 +27,6 @@ void *pusch_iq_plot;
void
*
pucch_iq_plot
;
void
*
pucch_plot
;
#ifdef T_USE_SHARED_MEMORY
T_cache_t
*
T_cache
;
int
T_busylist_head
;
int
T_pos
;
static
inline
int
GET
(
int
s
,
void
*
out
,
int
count
)
{
if
(
count
==
1
)
{
*
(
char
*
)
out
=
T_cache
[
T_busylist_head
].
buffer
[
T_pos
];
T_pos
++
;
return
1
;
}
memcpy
(
out
,
T_cache
[
T_busylist_head
].
buffer
+
T_pos
,
count
);
T_pos
+=
count
;
return
count
;
}
#else
/* T_USE_SHARED_MEMORY */
#define GET fullread
int
fullread
(
int
fd
,
void
*
_buf
,
int
count
)
...
...
@@ -62,14 +44,14 @@ int fullread(int fd, void *_buf, int count)
return
ret
;
}
#endif
/* T_USE_SHARED_MEMORY */
int
get_connection
(
char
*
addr
,
int
port
)
{
struct
sockaddr_in
a
;
socklen_t
alen
;
int
s
,
t
;
printf
(
"waiting for connection on %s:%d
\n
"
,
addr
,
port
);
s
=
socket
(
AF_INET
,
SOCK_STREAM
,
0
);
if
(
s
==
-
1
)
{
perror
(
"socket"
);
exit
(
1
);
}
t
=
1
;
...
...
@@ -86,6 +68,9 @@ int get_connection(char *addr, int port)
t
=
accept
(
s
,
(
struct
sockaddr
*
)
&
a
,
&
alen
);
if
(
t
==
-
1
)
{
perror
(
"accept"
);
exit
(
1
);
}
close
(
s
);
printf
(
"connected
\n
"
);
return
t
;
}
...
...
@@ -107,9 +92,6 @@ void get_message(int s)
} while (0)
int
m
;
#ifdef T_USE_SHARED_MEMORY
T_pos
=
0
;
#endif
if
(
GET
(
s
,
&
m
,
sizeof
(
int
))
!=
sizeof
(
int
))
abort
();
switch
(
m
)
{
case
T_first
:
{
...
...
@@ -255,6 +237,7 @@ void get_message(int s)
{
printf
(
"bad T_ENB_INPUT_SIGNAL, only 7680 samples allowed "
"(received %d bytes = %d samples)
\n
"
,
size
,
size
/
4
);
abort
();}
if
(
ul_plot
)
iq_plot_set
(
ul_plot
,
(
short
*
)
buf
,
7680
,
subframe
*
7680
,
0
);
t_gui_set_input_signal
(
eNB
,
frame
,
subframe
,
antenna
,
size
,
buf
);
break
;
}
case
T_ENB_UL_CHANNEL_ESTIMATE
:
{
...
...
@@ -339,43 +322,8 @@ void get_message(int s)
}
default:
printf
(
"unkown message type %d
\n
"
,
m
);
abort
();
}
#ifdef T_USE_SHARED_MEMORY
T_cache
[
T_busylist_head
].
busy
=
0
;
T_busylist_head
++
;
T_busylist_head
&=
T_CACHE_SIZE
-
1
;
#endif
}
#ifdef T_USE_SHARED_MEMORY
void
wait_message
(
void
)
{
while
(
T_cache
[
T_busylist_head
].
busy
==
0
)
usleep
(
1000
);
}
void
init_shm
(
void
)
{
int
i
;
int
s
=
shm_open
(
T_SHM_FILENAME
,
O_RDWR
|
O_CREAT
/*| O_SYNC*/
,
0666
);
if
(
s
==
-
1
)
{
perror
(
T_SHM_FILENAME
);
abort
();
}
if
(
ftruncate
(
s
,
T_CACHE_SIZE
*
sizeof
(
T_cache_t
)))
{
perror
(
T_SHM_FILENAME
);
abort
();
}
T_cache
=
mmap
(
NULL
,
T_CACHE_SIZE
*
sizeof
(
T_cache_t
),
PROT_READ
|
PROT_WRITE
,
MAP_SHARED
,
s
,
0
);
if
(
T_cache
==
NULL
)
{
perror
(
T_SHM_FILENAME
);
abort
();
}
close
(
s
);
/* let's garbage the memory to catch some potential problems
* (think multiprocessor sync issues, barriers, etc.)
*/
memset
(
T_cache
,
0x55
,
T_CACHE_SIZE
*
sizeof
(
T_cache_t
));
for
(
i
=
0
;
i
<
T_CACHE_SIZE
;
i
++
)
T_cache
[
i
].
busy
=
0
;
}
#endif
/* T_USE_SHARED_MEMORY */
void
new_thread
(
void
*
(
*
f
)(
void
*
),
void
*
data
)
{
pthread_t
t
;
...
...
@@ -396,22 +344,22 @@ void new_thread(void *(*f)(void *), void *data)
void
usage
(
void
)
{
printf
(
"common options:
\n
"
"tracer - remote side
\n
"
"options:
\n
"
" -d <database file> this option is mandatory
\n
"
" -li print IDs in the database
\n
"
" -lg print GROUPs in the database
\n
"
" -dump dump the database
\n
"
" -x run with XFORMS (revisited)
\n
"
" -T run with T GUI
\n
"
" -on <GROUP or ID> turn log ON for given GROUP or ID
\n
"
" -off <GROUP or ID> turn log OFF for given GROUP or ID
\n
"
" -ON turn all logs ON
\n
"
" -OFF turn all logs OFF
\n
"
"note: you may pass several -on/-off/-ON/-OFF, they will be processed in order
\n
"
" by default, all is off
\n
"
"
\n
"
"remote mode options: in this mode you run a local tracer and a remote one
\n
"
" -r <port> remote side (use given port)
\n
"
" -l <IP address> <port> local side (forwards packets to remote IP:port)
\n
"
" -r <port> remote side: use given port (default %d)
\n
"
,
DEFAULT_REMOTE_PORT
);
exit
(
1
);
}
...
...
@@ -428,18 +376,12 @@ int main(int n, char **v)
int
do_list_groups
=
0
;
int
do_dump_database
=
0
;
int
do_xforms
=
0
;
int
do_T_gui
=
0
;
char
**
on_off_name
;
int
*
on_off_action
;
int
on_off_n
=
0
;
int
is_on
[
T_NUMBER_OF_IDS
];
int
remote_local
=
0
;
int
remote_remote
=
0
;
char
*
remote_ip
=
NULL
;
int
remote_port
=
-
1
;
int
port
=
2020
;
#ifdef T_USE_SHARED_MEMORY
void
*
f
;
#endif
int
port
=
DEFAULT_REMOTE_PORT
;
memset
(
is_on
,
0
,
sizeof
(
is_on
));
...
...
@@ -454,6 +396,7 @@ int main(int n, char **v)
if
(
!
strcmp
(
v
[
i
],
"-lg"
))
{
do_list_groups
=
1
;
continue
;
}
if
(
!
strcmp
(
v
[
i
],
"-dump"
))
{
do_dump_database
=
1
;
continue
;
}
if
(
!
strcmp
(
v
[
i
],
"-x"
))
{
do_xforms
=
1
;
continue
;
}
if
(
!
strcmp
(
v
[
i
],
"-T"
))
{
do_T_gui
=
1
;
continue
;
}
if
(
!
strcmp
(
v
[
i
],
"-on"
))
{
if
(
i
>
n
-
2
)
usage
();
on_off_name
[
on_off_n
]
=
v
[
++
i
];
on_off_action
[
on_off_n
++
]
=
1
;
continue
;
}
if
(
!
strcmp
(
v
[
i
],
"-off"
))
{
if
(
i
>
n
-
2
)
usage
();
...
...
@@ -462,45 +405,13 @@ int main(int n, char **v)
{
on_off_name
[
on_off_n
]
=
NULL
;
on_off_action
[
on_off_n
++
]
=
1
;
continue
;
}
if
(
!
strcmp
(
v
[
i
],
"-OFF"
))
{
on_off_name
[
on_off_n
]
=
NULL
;
on_off_action
[
on_off_n
++
]
=
0
;
continue
;
}
if
(
!
strcmp
(
v
[
i
],
"-r"
))
{
if
(
i
>
n
-
2
)
usage
();
remote_remote
=
1
;
if
(
!
strcmp
(
v
[
i
],
"-r"
))
{
if
(
i
>
n
-
2
)
usage
();
port
=
atoi
(
v
[
++
i
]);
continue
;
}
if
(
!
strcmp
(
v
[
i
],
"-l"
))
{
if
(
i
>
n
-
3
)
usage
();
remote_local
=
1
;
remote_ip
=
v
[
++
i
];
remote_port
=
atoi
(
v
[
++
i
]);
continue
;
}
printf
(
"ERROR: unknown option %s
\n
"
,
v
[
i
]);
usage
();
}
#ifndef T_USE_SHARED_MEMORY
/* gcc shut up */
(
void
)
remote_port
;
(
void
)
remote_ip
;
#endif
#ifdef T_USE_SHARED_MEMORY
if
(
remote_remote
)
{
printf
(
"ERROR: remote 'remote side' does not run with shared memory
\n
"
);
printf
(
"recompile without T_USE_SHARED_MEMORY (edit Makefile)
\n
"
);
exit
(
1
);
}
#endif
if
(
remote_remote
)
{
/* TODO: setup 'secure' connection with remote part */
}
#ifndef T_USE_SHARED_MEMORY
if
(
remote_local
)
{
printf
(
"ERROR: remote 'local side' does not run without shared memory
\n
"
);
printf
(
"recompile with T_USE_SHARED_MEMORY (edit Makefile)
\n
"
);
exit
(
1
);
}
#endif
#ifdef T_USE_SHARED_MEMORY
if
(
remote_local
)
f
=
forwarder
(
remote_ip
,
remote_port
);
#endif
if
(
remote_local
)
goto
no_database
;
/* TODO: setup 'secure' connection with remote part */
if
(
database_filename
==
NULL
)
{
printf
(
"ERROR: provide a database file (-d)
\n
"
);
...
...
@@ -517,7 +428,6 @@ int main(int n, char **v)
for
(
i
=
0
;
i
<
on_off_n
;
i
++
)
on_off
(
database
,
on_off_name
[
i
],
is_on
,
on_off_action
[
i
]);
no_database:
if
(
do_xforms
)
{
ul_plot
=
make_plot
(
512
,
100
,
"UL Input Signal"
,
1
,
7680
*
10
,
PLOT_VS_TIME
,
BLUE
);
...
...
@@ -533,19 +443,12 @@ no_database:
/* pucch 1 */
10240
,
PLOT_MINMAX
,
BLUE
);
}
if
(
do_T_gui
)
{
t_gui_start
();
}
#ifdef T_USE_SHARED_MEMORY
init_shm
();
#endif
s
=
get_connection
(
"127.0.0.1"
,
port
);
if
(
remote_local
)
{
#ifdef T_USE_SHARED_MEMORY
forward_start_client
(
f
,
s
);
#endif
goto
no_init_message
;
}
/* send the first message - activate selected traces */
t
=
0
;
if
(
write
(
s
,
&
t
,
1
)
!=
1
)
abort
();
...
...
@@ -556,26 +459,8 @@ no_database:
if
(
is_on
[
l
])
if
(
write
(
s
,
&
l
,
sizeof
(
int
))
!=
sizeof
(
int
))
abort
();
no_init_message:
/* read messages */
while
(
1
)
{
#ifdef T_USE_SHARED_MEMORY
wait_message
();
__sync_synchronize
();
#endif
#ifdef T_USE_SHARED_MEMORY
if
(
remote_local
)
{
forward
(
f
,
T_cache
[
T_busylist_head
].
buffer
,
T_cache
[
T_busylist_head
].
length
);
T_cache
[
T_busylist_head
].
busy
=
0
;
T_busylist_head
++
;
T_busylist_head
&=
T_CACHE_SIZE
-
1
;
continue
;
}
#endif
get_message
(
s
);
}
return
0
;
...
...
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