Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Xiph.Org
Icecast libigloo
Commits
97b84a11
Commit
97b84a11
authored
Feb 14, 2021
by
Philipp Schafft
🦁
Committed by
Philipp Schafft
Feb 22, 2022
Browse files
Feature: Added basic PRNG
parent
cbc8b50d
Changes
5
Hide whitespace changes
Inline
Side-by-side
configure.ac
View file @
97b84a11
...
...
@@ -86,12 +86,16 @@ AC_CHECK_FUNCS_ONCE([clock_gettime gettimeofday ftime clock_nanosleep nanosleep
AC_CHECK_FUNCS_ONCE([sysconf])
AC_CHECK_FUNCS_ONCE([uname getuid getpid getppid])
AC_TYPE_SIZE_T
AC_TYPE_SSIZE_T
AC_TYPE_UINTPTR_T
ACX_PTHREAD([], [AC_MSG_ERROR([POSIX threads missing])])
AC_SEARCH_LIBS([log2], [m], [], [AC_MSG_ERROR([unable to find the log2() function])])
CFLAGS="${CFLAGS} ${PTHREAD_CFLAGS}"
CPPFLAGS="${CPPFLAGS} ${PTHREAD_CPPFLAGS}"
LIBS="${LIBS} ${PTHREAD_LIBS}"
...
...
include/igloo/prng.h
View file @
97b84a11
...
...
@@ -23,8 +23,27 @@
extern
"C"
{
#endif
#include
<stdint.h>
#include
<sys/types.h>
#include
<igloo/types.h>
typedef
uint64_t
igloo_prng_flags_t
;
/* Change options */
igloo_error_t
igloo_prng_configure
(
igloo_ro_t
instance
,
igloo_prng_flags_t
addflags
,
igloo_prng_flags_t
removeflags
,
ssize_t
overcommitment
);
/* Run a reseed now, useful to be run at times of idle */
igloo_error_t
igloo_prng_auto_reseed
(
igloo_ro_t
instance
);
/* write data to the PRNG to seed it */
igloo_error_t
igloo_prng_write
(
igloo_ro_t
instance
,
const
void
*
buffer
,
size_t
len
,
ssize_t
bits
);
/* read pseudo random bytes from the PRNG */
ssize_t
igloo_prng_read
(
igloo_ro_t
instance
,
void
*
buffer
,
size_t
len
);
/* write len pseudo random bytes to a file. If len is -1 a default value is used. */
igloo_error_t
igloo_prng_write_file
(
igloo_ro_t
instance
,
const
char
*
filename
,
ssize_t
len
);
/* read at max len bytes from the file and see the PRNG with it. if len is -1 all of the file is read. */
igloo_error_t
igloo_prng_read_file
(
igloo_ro_t
instance
,
const
char
*
filename
,
ssize_t
len
,
ssize_t
bits
);
#ifdef __cplusplus
}
...
...
src/igloo.c
View file @
97b84a11
...
...
@@ -41,6 +41,7 @@ struct igloo_instance_tag {
igloo_ro_t
logger
;
igloo_instance_log_handler_t
*
logger_handler
;
igloo_sp_state_t
stringpool_state
;
igloo_prng_state_t
prng_state
;
};
static
igloo_error_t
igloo_instance__new
(
igloo_ro_t
self
,
const
igloo_ro_type_t
*
type
,
va_list
ap
);
...
...
@@ -70,6 +71,10 @@ igloo_error_t igloo_instance__new(igloo_ro_t self, const igloo_ro_type_t *type,
if
(
error
!=
igloo_ERROR_NONE
)
return
error
;
error
=
igloo_instance_prng_init
(
&
(
instance
->
prng_state
));
if
(
error
!=
igloo_ERROR_NONE
)
return
error
;
return
igloo_ERROR_NONE
;
}
...
...
@@ -79,6 +84,7 @@ static void igloo_instance__free(igloo_ro_t self)
igloo_ro_weak_unref
(
&
(
instance
->
logger
));
igloo_instance_prng_destroy
(
&
(
instance
->
prng_state
));
igloo_instance_sp_destroy
(
&
(
instance
->
stringpool_state
));
}
...
...
@@ -109,7 +115,7 @@ static igloo_error_t render_error(igloo_error_t error, char *buffer, size_t len)
static
igloo_error_t
igloo_instance__stringify
(
igloo_ro_t
self
,
char
**
result
,
igloo_ro_sy_t
flags
)
{
#define STRINGIFY_FORMAT "%s, error=%s, stringpool=%s}", from_parent, errorbuffer, stringpool
#define STRINGIFY_FORMAT "%s, error=%s, stringpool=%s
, prng=%s
}", from_parent, errorbuffer, stringpool
, prng
igloo_instance_t
*
instance
=
igloo_ro_to_type
(
self
,
igloo_instance_t
);
igloo_error_t
error
;
char
*
from_parent
;
...
...
@@ -117,6 +123,7 @@ static igloo_error_t igloo_instance__stringify(igloo_ro_t self, char **result, i
int
ret
,
res
;
char
errorbuffer
[
80
];
char
stringpool
[
256
];
char
prng
[
80
];
if
(
!
(
flags
&
igloo_RO_SY_DEBUG
))
return
igloo_ERROR_NOSYS
;
...
...
@@ -129,6 +136,10 @@ static igloo_error_t igloo_instance__stringify(igloo_ro_t self, char **result, i
if
(
error
!=
igloo_ERROR_NONE
)
return
error
;
error
=
igloo_instance_prng_stringify
(
&
(
instance
->
prng_state
),
prng
,
sizeof
(
prng
));
if
(
error
!=
igloo_ERROR_NONE
)
return
error
;
error
=
igloo_ro_stringify_parent
(
self
,
&
from_parent
,
flags
,
igloo_ro_full_t
);
if
(
error
!=
igloo_ERROR_NONE
)
return
error
;
...
...
@@ -294,3 +305,16 @@ igloo_sp_state_t *igloo_instance_get_stringpool_state(igloo_ro_t self)
return
&
(
instance
->
stringpool_state
);
}
igloo_prng_state_t
*
igloo_instance_get_prng_state
(
igloo_ro_t
self
,
size_t
*
instancelen
)
{
igloo_instance_t
*
instance
=
get_instance
(
self
);
if
(
instancelen
)
*
instancelen
=
sizeof
(
*
instance
);
if
(
!
instance
)
return
NULL
;
return
&
(
instance
->
prng_state
);
}
src/private.h
View file @
97b84a11
...
...
@@ -24,6 +24,7 @@ extern "C" {
#endif
#include
<stdbool.h>
#include
<pthread.h>
#include
<igloo/types.h>
#include
<igloo/rwlock.h>
...
...
@@ -49,16 +50,37 @@ typedef struct {
igloo_sp_area_t
*
areas
[
3
];
}
igloo_sp_state_t
;
typedef
char
igloo_digest_512_t
[
512
/
8
];
typedef
struct
{
uint64_t
state
;
uint64_t
flags
;
size_t
bits
;
size_t
overcommitment
;
size_t
callcount
;
pthread_mutex_t
lock
;
igloo_digest_512_t
initial
;
igloo_digest_512_t
auto_seed0
;
igloo_digest_512_t
auto_seed1
;
igloo_digest_512_t
manual_seed0
;
igloo_digest_512_t
manual_seed1
;
igloo_digest_512_t
last_result
;
}
igloo_prng_state_t
;
extern
const
igloo_ro_type_t
*
igloo_instance_type
;
igloo_error_t
igloo_ro_bootstrap
(
igloo_ro_t
*
instance
,
const
igloo_ro_type_t
*
type
);
igloo_ro_t
igloo_ro_get_instance_unsafe
(
igloo_ro_t
self
,
const
igloo_ro_type_t
*
type
);
igloo_sp_state_t
*
igloo_instance_get_stringpool_state
(
igloo_ro_t
self
);
igloo_prng_state_t
*
igloo_instance_get_prng_state
(
igloo_ro_t
self
,
size_t
*
instancelen
);
igloo_error_t
igloo_instance_sp_init
(
igloo_sp_state_t
*
state
);
void
igloo_instance_sp_destroy
(
igloo_sp_state_t
*
state
);
igloo_error_t
igloo_instance_sp_stringify
(
igloo_sp_state_t
*
state
,
char
*
buf
,
size_t
len
);
igloo_error_t
igloo_instance_prng_init
(
igloo_prng_state_t
*
state
);
void
igloo_instance_prng_destroy
(
igloo_prng_state_t
*
state
);
igloo_error_t
igloo_instance_prng_stringify
(
igloo_prng_state_t
*
state
,
char
*
buf
,
size_t
len
);
#ifdef __cplusplus
}
#endif
...
...
src/prng.c
View file @
97b84a11
...
...
@@ -20,7 +20,421 @@
#include
<config.h>
#endif
#include
<stdio.h>
#include
<string.h>
#include
<math.h>
#include
<pthread.h>
#ifdef HAVE_UNAME
#include
<sys/utsname.h>
#endif
#if defined(HAVE_GETUID) || defined(HAVE_GETPID) || defined(HAVE_GETPPID)
#include
<unistd.h>
#include
<sys/types.h>
#endif
#include
<igloo/prng.h>
#include
<igloo/error.h>
#include
<igloo/digest.h>
#include
<igloo/ro.h>
#include
<igloo/time.h>
#include
"private.h"
#define HASHFUNC "SHA3-512" // must be a 512 bit function!
#define MAX_BITS_PER_HASH 192
#define MAX_BITS_PER_STATE (MAX_BITS_PER_HASH*3)
#define STATE_INITED 0x01U
#define STATE_AUTO_SEED_0 0x02U
#define STATE_MAN_SEED_0 0x04U
typedef
struct
{
igloo_ctime_t
ctime
;
pthread_t
thread
;
const
void
*
stack
;
const
void
*
instance
;
const
void
*
buffer
;
size_t
bufferlen
;
}
igloo_prng_buffer_extra_t
;
static
inline
void
buffer_extra_init
(
igloo_prng_buffer_extra_t
*
extra
,
const
char
*
instance
)
{
igloo_error_t
error
;
memset
(
extra
,
0
,
sizeof
(
*
extra
));
extra
->
thread
=
pthread_self
();
extra
->
stack
=
&
error
;
// any object on the current stack.
extra
->
instance
=
instance
;
error
=
igloo_ctime_from_now
(
&
(
extra
->
ctime
),
igloo_CLOCK_MONOTONIC
);
if
(
error
!=
igloo_ERROR_NONE
)
igloo_ctime_from_now
(
&
(
extra
->
ctime
),
igloo_CLOCK_REALTIME
);
}
igloo_error_t
igloo_instance_prng_init
(
igloo_prng_state_t
*
state
)
{
if
(
pthread_mutex_init
(
&
(
state
->
lock
),
NULL
)
!=
0
)
return
igloo_ERROR_GENERIC
;
state
->
overcommitment
=
32
;
return
igloo_ERROR_NONE
;
}
void
igloo_instance_prng_destroy
(
igloo_prng_state_t
*
state
)
{
pthread_mutex_destroy
(
&
(
state
->
lock
));
}
igloo_error_t
igloo_instance_prng_stringify
(
igloo_prng_state_t
*
state
,
char
*
buf
,
size_t
len
)
{
(
void
)
state
;
pthread_mutex_lock
(
&
(
state
->
lock
));
snprintf
(
buf
,
len
,
"{bits=%zu}"
,
state
->
bits
);
pthread_mutex_unlock
(
&
(
state
->
lock
));
return
igloo_ERROR_NONE
;
}
static
igloo_error_t
get_inited_state
(
igloo_prng_state_t
**
state
,
size_t
*
instancelen
,
igloo_ro_t
instance
)
{
igloo_prng_state_t
*
self
;
size_t
len
;
*
state
=
self
=
igloo_instance_get_prng_state
(
instance
,
&
len
);
if
(
!*
state
)
return
igloo_ERROR_BADSTATE
;
if
(
instancelen
)
*
instancelen
=
len
;
pthread_mutex_lock
(
&
(
self
->
lock
));
if
(
!
(
self
->
state
&
STATE_INITED
))
{
igloo_digest_t
*
digest
=
NULL
;
igloo_error_t
error
;
const
void
*
instanceraw
=
igloo_ro_to_type
(
instance
,
igloo_ro_stub_t
);
struct
{
igloo_prng_buffer_extra_t
extra
;
int
debian
;
#ifdef HAVE_UNAME
struct
utsname
utsname
;
#endif
#ifdef HAVE_GETUID
uid_t
uid
;
#endif
#ifdef HAVE_GETPID
pid_t
pid
;
#endif
#ifdef HAVE_GETPPID
pid_t
ppid
;
#endif
}
seed
;
buffer_extra_init
(
&
(
seed
.
extra
),
instanceraw
);
seed
.
extra
.
buffer
=
instanceraw
;
seed
.
extra
.
bufferlen
=
len
;
memset
(
&
seed
,
0
,
sizeof
(
seed
));
seed
.
debian
=
4
;
#ifdef HAVE_UNAME
uname
(
&
seed
.
utsname
);
#endif
#ifdef HAVE_GETUID
seed
.
uid
=
getuid
();
#endif
#ifdef HAVE_GETPID
seed
.
pid
=
getpid
();
#endif
#ifdef HAVE_GETPPID
seed
.
ppid
=
getppid
();
#endif
error
=
igloo_digest_new
(
&
digest
,
instance
,
HASHFUNC
);
if
(
error
!=
igloo_ERROR_NONE
)
return
error
;
if
(
igloo_digest_write
(
digest
,
instanceraw
,
len
)
!=
(
ssize_t
)
len
)
{
igloo_ro_unref
(
&
digest
);
return
igloo_ERROR_GENERIC
;
}
if
(
igloo_digest_write
(
digest
,
&
seed
,
sizeof
(
seed
))
!=
(
ssize_t
)
sizeof
(
seed
))
{
igloo_ro_unref
(
&
digest
);
return
igloo_ERROR_GENERIC
;
}
if
(
igloo_digest_read
(
digest
,
self
->
initial
,
sizeof
(
self
->
initial
))
!=
sizeof
(
self
->
initial
))
{
igloo_ro_unref
(
&
digest
);
return
igloo_ERROR_GENERIC
;
}
error
=
igloo_ro_unref
(
&
digest
);
if
(
error
!=
igloo_ERROR_NONE
)
return
error
;
self
->
bits
=
5
;
self
->
state
|=
STATE_INITED
;
}
return
igloo_ERROR_NONE
;
}
static
ssize_t
igloo_prng_estimate_bits
(
const
void
*
buffer
,
size_t
len
)
{
size_t
counts
[
256
];
size_t
i
;
double
entpb
=
0
;
if
(
!
buffer
)
return
-
1
;
if
(
len
<
2
)
return
0
;
memset
(
counts
,
0
,
sizeof
(
counts
));
for
(
i
=
0
;
i
<
len
;
i
++
)
counts
[((
const
unsigned
char
*
)
buffer
)[
i
]]
++
;
for
(
i
=
0
;
i
<
256
;
i
++
)
{
double
probability
;
if
(
!
counts
[
i
])
continue
;
probability
=
counts
[
i
]
/
(
double
)
len
;
entpb
+=
probability
*
log2
(
1
.
/
probability
);
}
return
entpb
*
len
;
}
igloo_error_t
igloo_prng_configure
(
igloo_ro_t
instance
,
igloo_prng_flags_t
addflags
,
igloo_prng_flags_t
removeflags
,
ssize_t
overcommitment
)
{
igloo_prng_state_t
*
self
;
self
=
igloo_instance_get_prng_state
(
instance
,
NULL
);
if
(
!
self
)
return
igloo_ERROR_BADSTATE
;
pthread_mutex_lock
(
&
(
self
->
lock
));
self
->
flags
|=
addflags
|
removeflags
;
self
->
flags
-=
removeflags
;
if
(
overcommitment
>
0
)
self
->
overcommitment
=
overcommitment
;
pthread_mutex_unlock
(
&
(
self
->
lock
));
return
igloo_ERROR_NONE
;
}
static
igloo_error_t
digest_instance_extra_once
(
const
void
*
instanceraw
,
size_t
instancelen
,
igloo_ro_t
instance
,
igloo_prng_buffer_extra_t
*
extra
,
const
void
*
mid
,
size_t
midlen
,
igloo_digest_512_t
out
)
{
igloo_digest_t
*
digest
=
NULL
;
igloo_error_t
error
;
error
=
igloo_digest_new
(
&
digest
,
instance
,
HASHFUNC
);
if
(
error
!=
igloo_ERROR_NONE
)
return
error
;
if
(
igloo_digest_write
(
digest
,
instanceraw
,
instancelen
)
!=
(
ssize_t
)
instancelen
)
{
igloo_ro_unref
(
&
digest
);
return
igloo_ERROR_GENERIC
;
}
if
(
mid
&&
midlen
)
{
if
(
igloo_digest_write
(
digest
,
mid
,
midlen
)
!=
(
ssize_t
)
midlen
)
{
igloo_ro_unref
(
&
digest
);
return
igloo_ERROR_GENERIC
;
}
}
if
(
igloo_digest_write
(
digest
,
extra
,
sizeof
(
*
extra
))
!=
(
ssize_t
)
sizeof
(
*
extra
))
{
igloo_ro_unref
(
&
digest
);
return
igloo_ERROR_GENERIC
;
}
if
(
igloo_digest_read
(
digest
,
out
,
sizeof
(
igloo_digest_512_t
))
!=
sizeof
(
igloo_digest_512_t
))
{
igloo_ro_unref
(
&
digest
);
return
igloo_ERROR_GENERIC
;
}
error
=
igloo_ro_unref
(
&
digest
);
if
(
error
!=
igloo_ERROR_NONE
)
return
error
;
return
igloo_ERROR_NONE
;
}
igloo_error_t
igloo_prng_auto_reseed_unlocked
(
igloo_prng_state_t
*
self
,
const
void
*
instanceraw
,
size_t
instancelen
,
igloo_ro_t
instance
)
{
igloo_prng_buffer_extra_t
extra
;
igloo_error_t
error
;
buffer_extra_init
(
&
extra
,
instanceraw
);
self
->
callcount
++
;
if
(
self
->
state
&
STATE_AUTO_SEED_0
)
{
error
=
digest_instance_extra_once
(
instanceraw
,
instancelen
,
instance
,
&
extra
,
NULL
,
0
,
self
->
auto_seed0
);
}
else
{
error
=
digest_instance_extra_once
(
instanceraw
,
instancelen
,
instance
,
&
extra
,
NULL
,
0
,
self
->
auto_seed1
);
}
if
(
error
!=
igloo_ERROR_NONE
)
{
pthread_mutex_unlock
(
&
(
self
->
lock
));
return
error
;
}
self
->
state
^=
STATE_AUTO_SEED_0
;
self
->
bits
+=
3
;
return
igloo_ERROR_NONE
;
}
igloo_error_t
igloo_prng_auto_reseed
(
igloo_ro_t
instance
)
{
igloo_prng_state_t
*
self
;
size_t
instancelen
;
igloo_error_t
error
=
get_inited_state
(
&
self
,
&
instancelen
,
instance
);
const
void
*
instanceraw
=
igloo_ro_to_type
(
instance
,
igloo_ro_stub_t
);
if
(
error
!=
igloo_ERROR_NONE
)
return
error
;
error
=
igloo_prng_auto_reseed_unlocked
(
self
,
instanceraw
,
instancelen
,
instance
);
pthread_mutex_unlock
(
&
(
self
->
lock
));
return
error
;
}
igloo_error_t
igloo_prng_write
(
igloo_ro_t
instance
,
const
void
*
buffer
,
size_t
len
,
ssize_t
bits
)
{
igloo_prng_state_t
*
self
;
size_t
instancelen
;
igloo_error_t
error
;
const
void
*
instanceraw
;
igloo_prng_buffer_extra_t
extra
;
if
(
bits
>
(
ssize_t
)(
len
*
8
))
return
igloo_ERROR_INVAL
;
if
(
bits
<
0
)
{
ssize_t
ent
=
igloo_prng_estimate_bits
(
buffer
,
len
);
bits
=
(
len
*
8
)
/
32
;
if
(
ent
>
0
&&
bits
>
ent
)
bits
=
ent
;
}
if
(
bits
>
MAX_BITS_PER_HASH
)
bits
=
MAX_BITS_PER_HASH
;
error
=
get_inited_state
(
&
self
,
&
instancelen
,
instance
);
if
(
error
!=
igloo_ERROR_NONE
)
return
error
;
instanceraw
=
igloo_ro_to_type
(
instance
,
igloo_ro_stub_t
);
buffer_extra_init
(
&
extra
,
instanceraw
);
self
->
callcount
++
;
if
(
self
->
state
&
STATE_MAN_SEED_0
)
{
error
=
digest_instance_extra_once
(
instanceraw
,
instancelen
,
instance
,
&
extra
,
buffer
,
len
,
self
->
manual_seed0
);
}
else
{
error
=
digest_instance_extra_once
(
instanceraw
,
instancelen
,
instance
,
&
extra
,
buffer
,
len
,
self
->
manual_seed1
);
}
if
(
error
!=
igloo_ERROR_NONE
)
{
pthread_mutex_unlock
(
&
(
self
->
lock
));
return
error
;
}
self
->
state
^=
STATE_MAN_SEED_0
;
self
->
bits
+=
bits
;
self
->
bits
+=
1
;
if
(
self
->
bits
>
MAX_BITS_PER_STATE
)
self
->
bits
=
MAX_BITS_PER_STATE
;
pthread_mutex_unlock
(
&
(
self
->
lock
));
return
igloo_ERROR_NONE
;
}
static
igloo_error_t
igloo_prng_read__inner
(
igloo_prng_state_t
*
self
,
const
void
*
instanceraw
,
size_t
instancelen
,
igloo_ro_t
instance
,
igloo_prng_buffer_extra_t
*
extra
)
{
self
->
callcount
++
;
return
digest_instance_extra_once
(
instanceraw
,
instancelen
,
instance
,
extra
,
NULL
,
0
,
self
->
last_result
);
}
ssize_t
igloo_prng_read
(
igloo_ro_t
instance
,
void
*
buffer
,
size_t
len
)
{
igloo_prng_state_t
*
self
;
size_t
instancelen
;
igloo_error_t
error
=
get_inited_state
(
&
self
,
&
instancelen
,
instance
);
const
void
*
instanceraw
=
igloo_ro_to_type
(
instance
,
igloo_ro_stub_t
);
size_t
done
=
0
;
size_t
bitswant
;
igloo_prng_buffer_extra_t
extra
;
if
(
error
!=
igloo_ERROR_NONE
)
return
-
1
;
bitswant
=
((
len
*
8
)
/
self
->
overcommitment
)
+
1
;
if
(
bitswant
>
self
->
bits
)
{
size_t
try
;
for
(
try
=
0
;
bitswant
>
self
->
bits
&&
try
<
2
;
try
++
)
{
error
=
igloo_prng_auto_reseed_unlocked
(
self
,
instanceraw
,
instancelen
,
instance
);
if
(
error
!=
igloo_ERROR_NONE
)
break
;
}
if
(
bitswant
>
self
->
bits
)
{
pthread_mutex_unlock
(
&
(
self
->
lock
));
return
-
1
;
}
}
self
->
bits
-=
bitswant
;
buffer_extra_init
(
&
extra
,
instanceraw
);
extra
.
buffer
=
buffer
;
extra
.
bufferlen
=
len
;
while
(
done
<
len
)
{
size_t
todo
=
(
len
-
done
)
<
sizeof
(
self
->
last_result
)
?
len
-
done
:
sizeof
(
self
->
last_result
);
error
=
igloo_prng_read__inner
(
self
,
instanceraw
,
instancelen
,
instance
,
&
extra
);
if
(
error
!=
igloo_ERROR_NONE
)
{
pthread_mutex_unlock
(
&
(
self
->
lock
));
if
(
done
>
0
)
{
return
done
;
}
else
{
return
-
1
;
}
}
memcpy
(
buffer
+
done
,
self
->
last_result
,
todo
);
done
+=
todo
;
}
pthread_mutex_unlock
(
&
(
self
->
lock
));
return
done
;
}
/* write len pseudo random bytes to a file. If len is -1 a default value is used. */
igloo_error_t
igloo_prng_write_file
(
igloo_ro_t
instance
,
const
char
*
filename
,
ssize_t
len
);
/* read at max len bytes from the file and see the PRNG with it. if len is -1 all of the file is read. */
igloo_error_t
igloo_prng_read_file
(
igloo_ro_t
instance
,
const
char
*
filename
,
ssize_t
len
,
ssize_t
bits
);
Write
Preview
Supports
Markdown
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