Commit e09106b7 authored by Jelle De Vleeschouwer's avatar Jelle De Vleeschouwer

Proper merging of development

parent 16220668
......@@ -37,6 +37,7 @@ DHCP_CLIENT?=1
DHCP_SERVER?=1
DNS_CLIENT?=1
MDNS?=1
DNS_SD?=1
SNTP_CLIENT?=1
IPFILTER?=1
CRC?=1
......@@ -216,6 +217,9 @@ endif
ifneq ($(MDNS),0)
include rules/mdns.mk
endif
ifneq ($(DNS_SD),0)
include rules/dns_sd.mk
endif
ifneq ($(IPFILTER),0)
include rules/ipfilter.mk
endif
......@@ -328,7 +332,9 @@ units: mod core lib $(UNITS_OBJ) $(MOD_OBJ)
@$(CC) -o $(PREFIX)/test/modunit_seq.elf $(CFLAGS) -I. test/unit/modunit_seq.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a
@$(CC) -o $(PREFIX)/test/modunit_tcp.elf $(CFLAGS) -I. test/unit/modunit_pico_tcp.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a
@$(CC) -o $(PREFIX)/test/modunit_dns_client.elf $(CFLAGS) -I. test/unit/modunit_pico_dns_client.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a
@$(CC) -o $(PREFIX)/test/modunit_dns_common.elf $(CFLAGS) -I. test/unit/modunit_pico_dns_common.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a
@$(CC) -o $(PREFIX)/test/modunit_mdns.elf $(CFLAGS) -I. test/unit/modunit_pico_mdns.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a
@$(CC) -o $(PREFIX)/test/modunit_dns_sd.elf $(CFLAGS) -I. test/unit/modunit_pico_dns_sd.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a
@$(CC) -o $(PREFIX)/test/modunit_dev_loop.elf $(CFLAGS) -I. test/unit/modunit_pico_dev_loop.c -lcheck -lm -pthread -lrt $(UNITS_OBJ)
@$(CC) -o $(PREFIX)/test/modunit_ipv6_nd.elf $(CFLAGS) -I. test/unit/modunit_pico_ipv6_nd.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a
@$(CC) -o $(PREFIX)/test/modunit_pico_stack.elf $(CFLAGS) -I. test/unit/modunit_pico_stack.c -lcheck -lm -pthread -lrt $(UNITS_OBJ) $(PREFIX)/lib/libpicotcp.a
......
This diff is collapsed.
/*********************************************************************
PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved.
PicoTCP. Copyright (c) 2012 TASS Belgium NV. Some rights reserved.
See LICENSE and COPYING for usage.
.
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/* ****************************************************************************
* PicoTCP. Copyright (c) 2014 TASS Belgium NV. Some rights reserved.
* See LICENSE and COPYING for usage.
* .
* Author: Jelle De Vleeschouwer
* ****************************************************************************/
#ifndef INCLUDE_PICO_DNS_SD
#define INCLUDE_PICO_DNS_SD
#include "pico_mdns.h"
typedef struct
{
char *key;
char *value;
} key_value_pair_t;
typedef struct
{
key_value_pair_t **pairs;
uint16_t count;
} kv_vector;
#define PICO_DNS_SD_KV_VECTOR_DECLARE(name) \
kv_vector (name) = {0}
/* ****************************************************************************
* Just calls pico_mdns_init in it's turn to initialise the mDNS-module.
* See pico_mdns.h for description.
* ****************************************************************************/
int
pico_dns_sd_init( const char *_hostname,
struct pico_ip4 address,
void (*callback)(pico_mdns_rtree *,
char *,
void *),
void *arg );
/* ****************************************************************************
* Register a DNS-SD service via Multicast DNS on the local network.
*
* @param name Instance Name of the service, f.e. "Printer 2nd Floor".
* @param type ServiceType of the service, f.e. "_http._tcp".
* @param port Port number on which the service runs.
* @param txt_data TXT data to create TXT record with, need kv_vector-type,
* Declare such a type with PICO_DNS_SD_KV_VECTOR_DECLARE(*) &
* add key-value pairs with pico_dns_sd_kv_vector_add().
* @param ttl TTL
* @param callback Callback-function to call when the service is registered.
* @return
* ****************************************************************************/
int
pico_dns_sd_register_service( const char *name,
const char *type,
uint16_t port,
kv_vector *txt_data,
uint16_t ttl,
void (*callback)(pico_mdns_rtree *,
char *,
void *),
void *arg);
/* ****************************************************************************
* Does nothing for now.
*
* @param type Type to browse for.
* @param callback Callback to call when something particular happens.
* @return When the module succesfully staterd browsing the servicetype.
* ****************************************************************************/
int
pico_dns_sd_browse_service( const char *type,
void (*callback)(pico_mdns_rtree *,
char *,
void *),
void *arg );
/* ****************************************************************************
* Add a key-value pair the a key-value pair vector.
*
* @param vector Vector to add the pair to.
* @param key Key of the pair, cannot be NULL.
* @param value Value of the pair, can be NULL, empty ("") or filled ("qkejq")
* @return Returns 0 when the pair is added succesfully, something else on
* failure.
* ****************************************************************************/
int
pico_dns_sd_kv_vector_add( kv_vector *vector, char *key, char *value );
#endif /* _INCLUDE_PICO_DNS_SD */
\ No newline at end of file
This diff is collapsed.
/*********************************************************************
PicoTCP. Copyright (c) 2012-2015 Altran Intelligent Systems. Some rights reserved.
See LICENSE and COPYING for usage.
.
Author: Toon Stegen
*********************************************************************/
/* ****************************************************************************
* PicoTCP. Copyright (c) 2014 TASS Belgium NV. Some rights reserved.
* See LICENSE and COPYING for usage.
* .
* Author: Toon Stegen, Jelle De Vleeschouwer
* ****************************************************************************/
#ifndef INCLUDE_PICO_MDNS
#define INCLUDE_PICO_MDNS
#include "pico_dns_common.h"
#include "pico_tree.h"
#include "pico_ipv4.h"
/* ********************************* CONFIG ***********************************/
#define PICO_MDNS_PROBE_UNICAST 0 /* Probe queries as QU-questions */
#define PICO_MDNS_CONTINUOUS_REFRESH 0 /* Continuously update cache */
#define PICO_MDNS_ALLOW_CACHING 1 /* Enable caching on this host */
#define PICO_MDNS_DEFAULT_TTL 120 /* Default TTL of mDNS records */
#define PICO_MDNS_SERVICE_TTL 120 /* Default TTL of SRV/TXT/PTR/NSEC */
#define PICO_MDNS_PROBE_COUNT 3 /* Amount of probes to send */
#define PICO_MDNS_ANNOUNCEMENT_COUNT 2 /* Amount of announcements to send */
/* ****************************************************************************/
#define PICO_MDNS_DEST_ADDR4 "224.0.0.251"
int pico_mdns_init(char *hostname, void (*cb_initialised)(char *str, void *arg), void *arg);
int pico_mdns_getaddr(const char *url, void (*callback)(char *ip, void *arg), void *arg);
int pico_mdns_getname(const char *ip, void (*callback)(char *url, void *arg), void *arg);
int pico_mdns_flush_cache(void);
/* To make mDNS records unique or shared records */
#define PICO_MDNS_RECORD_UNIQUE 0x00u
#define PICO_MDNS_RECORD_SHARED 0x01u
/* Flag to check for when records are returned, to determine the hostname */
#define PICO_MDNS_RECORD_HOSTNAME 0x02u
#define IS_HOSTNAME_RECORD(x) \
((x) ? ((((x)->flags) & PICO_MDNS_RECORD_HOSTNAME) ? (1) : (0)) : (0))
/* --- MDNS resource record --- */
struct pico_mdns_record
{
struct pico_dns_record *record; // DNS Resource Record
uint32_t current_ttl; // Current TTL
uint8_t flags; // Resource Record flags
uint8_t claim_id; // Claim ID number
};
/* ****************************************************************************
* Compares 2 mDNS records by type, name AND rdata for a truly unique result
*
* @param ra mDNS record A
* @param rb mDNS record B
* @return 0 when records are equal, returns difference when they're not.
* ****************************************************************************/
int
pico_mdns_record_cmp( void *a, void *b );
/* ****************************************************************************
* Deletes a single mDNS resource record.
*
* @param record Void-pointer to mDNS Resource Record. Can be used with pico_-
* tree-destroy.
* @return Returns 0 on succes, something else on failure.
* ****************************************************************************/
int
pico_mdns_record_delete( void **record );
/* ****************************************************************************
* Creates a single standalone mDNS resource record with given name, type and
* data to register on the network.
*
* @param url DNS rrecord name in URL format. Will be converted to DNS
* name notation format.
* @param _rdata Memory buffer with data to insert in the resource record. If
* data of record should contain a DNS name, the name in the
* databuffer needs to be in URL-format.
* @param datalen The exact length in bytes of the _rdata-buffer. If data of
* record should contain a DNS name, datalen needs to be
* pico_dns_strlen(_rdata).
* @param rtype DNS type of the resource record to be.
* @param rclass DNS class of the resource record to be.
* @param rttl DNS ttl of the resource record to be.
* @param flags You can specify if the mDNS record should be a shared record
* rather than a unique record.
* @return Pointer to newly created mDNS resource record.
* ****************************************************************************/
struct pico_mdns_record *
pico_mdns_record_create( const char *url,
void *_rdata,
uint16_t datalen,
uint16_t rtype,
uint32_t rttl,
uint8_t flags );
/* ****************************************************************************
* Definition of DNS record tree
* ****************************************************************************/
typedef struct pico_tree pico_mdns_rtree;
#define PICO_MDNS_RTREE_DECLARE(name) \
pico_mdns_rtree (name) = {&LEAF, pico_mdns_record_cmp}
#define PICO_MDNS_RTREE_DESTROY(rtree) \
pico_tree_destroy((rtree), pico_mdns_record_delete)
#define PICO_MDNS_RTREE_ADD(tree, record) \
pico_tree_insert((tree), (record))
/* ****************************************************************************
* API-call to query a record with a certain URL and type. First checks the
* Cache for this record. If no cache-entry is found, a query will be sent on
* the wire for this record.
*
* @param url URL to query for.
* @param type DNS type top query for.
* @param callback Callback to call when records are found for the query.
* @return 0 when query is correctly parsed, something else on failure.
* ****************************************************************************/
int
pico_mdns_getrecord( const char *url, uint16_t type,
void (*callback)(pico_mdns_rtree *,
char *,
void *),
void *arg );
/* ****************************************************************************
* Claim all different mDNS records in a tree in a single API-call. All records
* in tree are called in a single new claim-session.
*
* @param rtree mDNS record tree with records to claim
* @param callback Callback to call when all record are properly claimed.
* @return 0 When claiming didn't horribly fail.
* ****************************************************************************/
int
pico_mdns_claim( pico_mdns_rtree record_tree,
void (*callback)(pico_mdns_rtree *,
char *,
void *),
void *arg );
/* ****************************************************************************
* Sets the hostname for this machine. Claims automatically a unique A record
* with the IPv4-address of this host. The hostname won't be set directly when
* this functions returns, but only if the claiming of the unique record succ-
* eeded. Init-callback will be called when the hostname-record is succesfully
* registered.
*
* @param url URL to set the hostname to.
* @param arg Argument to pass to the init-callback.
* @return 0 when the host started registering the hostname-record succesfully,
* Returns something else when it didn't succeeded.
* ****************************************************************************/
int
pico_mdns_set_hostname( const char *url, void *arg );
/* ****************************************************************************
* Get the current hostname for this machine.
*
* @return Returns the hostname for this machine when the module is initialised
* Returns NULL when the module is not initialised.
* ****************************************************************************/
const char *
pico_mdns_get_hostname( void );
#ifdef PICO_SUPPORT_IPV6
#define PICO_MDNS_DEST_ADDR6 "FF02::FB"
int pico_mdns_getaddr6(const char *url, void (*callback)(char *ip, void *arg), void *arg);
int pico_mdns_getname6(const char *ip, void (*callback)(char *url, void *arg), void *arg);
#endif
/* ****************************************************************************
* Initialises the entire mDNS-module and sets the hostname for this machine.
* Sets up the global mDNS socket properly and calls callback when succeeded.
* Only when the module is properly initialised records can be registered on
* the module.
*
* @param hostname URL to set the hostname to.
* @param address IPv4-address of this host to bind to.
* @param callback Callback to call when the hostname is registered and
* also the global mDNS module callback. Gets called when
* Passive conflicts occur, so changes in records can be
* tracked in this callback.
* @param arg Argument to pass to the init-callback.
* @return 0 when the module is properly initalised and the host started regi-
* tering the hostname. Returns something else went the host failed
* initialising the module or registering the hostname.
* ****************************************************************************/
int
pico_mdns_init( const char *hostname,
struct pico_ip4 address,
void (*callback)(pico_mdns_rtree *,
char *,
void *),
void *arg );
#endif /* _INCLUDE_PICO_MDNS */
OPTIONS+=-DPICO_SUPPORT_DNS_SD
MOD_OBJ+=$(LIBBASE)modules/pico_dns_sd.o $(LIBBASE)modules/pico_mdns.o $(LIBBASE)modules/pico_dns_common.o
\ No newline at end of file
......@@ -10,6 +10,7 @@ $(PREFIX)/examples/%.o: %.c
OBJS:= \
$(PREFIX)/examples/dhcp_client.o \
$(PREFIX)/examples/dhcp_server.o \
$(PREFIX)/examples/dns_sd.o \
$(PREFIX)/examples/dnsclient.o \
$(PREFIX)/examples/mdns.o \
$(PREFIX)/examples/multicast_recv.o \
......
#include "utils.h"
#include "pico_dns_sd.h"
#include "pico_ipv4.h"
#include "pico_addressing.h"
/*** START DNS_SD ***/
#ifdef PICO_SUPPORT_DNS_SD
#define TTL 30
static char *service_name = NULL;
void dns_sd_claimed_callback( pico_mdns_rtree *tree,
char *str,
void *arg )
{
IGNORE_PARAMETER(tree);
IGNORE_PARAMETER(str);
IGNORE_PARAMETER(arg);
}
void dns_sd_init_callback( pico_mdns_rtree *tree,
char *str,
void *arg )
{
kv_vector key_value_pair_vector = {0};
IGNORE_PARAMETER(str);
IGNORE_PARAMETER(arg);
IGNORE_PARAMETER(tree);
printf("DONE - Initialising DNS Service Discovery module.\n");
if (pico_dns_sd_register_service(service_name,
"_http._tcp", 80,
&key_value_pair_vector,
TTL, dns_sd_claimed_callback, NULL) < 0) {
printf("Registering service failed!\n");
}
}
void app_dns_sd(char *arg, struct pico_ip4 address)
{
char *hostname;
char *nxt = arg;
if (!nxt)
exit(255);
nxt = cpy_arg(&hostname, nxt);
if(!hostname) {
exit(255);
}
if(!nxt) {
printf("Not enough args supplied!\n");
exit(255);
}
nxt = cpy_arg(&service_name, nxt);
if(!service_name) {
exit(255);
}
printf("\nStarting DNS Service Discovery module...\n");
if (pico_dns_sd_init(hostname, address, &dns_sd_init_callback, NULL) != 0) {
printf("Initialisation returned with Error!\n");
exit(255);
}
while(1) {
pico_stack_tick();
usleep(2000);
}
}
#endif
/*** END DNS_SD ***/
\ No newline at end of file
#include "utils.h"
#include <pico_mdns.h>
#include "pico_dns_common.h"
#include "pico_mdns.h"
#include "pico_ipv4.h"
#include "pico_addressing.h"
/*** START MDNS ***/
#ifdef PICO_SUPPORT_MDNS
void mdns_getname6_callback(char *str, void *arg)
void mdns_init_callback( pico_mdns_rtree *rtree,
char *str,
void *arg )
{
(void) arg;
if (!str)
printf("Getname6: timeout occurred!\n");
else
printf("Getname6 callback called, str: %s\n", str);
exit(0);
}
void mdns_getaddr6_callback(char *str, void *arg)
{
(void) arg;
if (!str)
printf("Getaddr6: timeout occurred!\n");
else
printf("Getaddr6 callback called, str: %s\n", str);
if(pico_mdns_getname6(str, &mdns_getname6_callback, NULL) != 0)
printf("Getname6 returned with error!\n");
}
void mdns_getname_callback(char *str, void *arg)
{
char *peername = (char *)arg;
if(!peername) {
printf("No system name supplied!\n");
exit(-1);
}
if (!str)
printf("Getname: timeout occurred!\n");
else
printf("Getname callback called, str: %s\n", str);
if(pico_mdns_getaddr6(peername, &mdns_getaddr6_callback, NULL) != 0)
printf("Getaddr6 returned with error!\n");
printf("\nInitialised with hostname: %s\n\n", str);
}
void mdns_getaddr_callback(char *str, void *arg)
{
if (!str)
printf("Getaddr: timeout occurred!\n");
else
printf("Getaddr callback called, str: %s\n", str);
if(pico_mdns_getname(str, &mdns_getname_callback, arg) != 0)
printf("Getname returned with error!\n");
}
void mdns_init_callback(char *str, void *arg)
{
char *peername = (char *)arg;
printf("Init callback called, str: %s\n", str);
if(!peername) {
printf("No system name supplied!\n");
exit(-1);
}
if(pico_mdns_getaddr(peername, &mdns_getaddr_callback, peername) != 0)
printf("Getaddr returned with error!\n");
}
void app_mdns(char *arg)
void app_mdns(char *arg, struct pico_ip4 address)
{
char *hostname, *peername;
char *nxt = arg;
if (!nxt)
exit(255);
......@@ -91,11 +37,14 @@ void app_mdns(char *arg)
if(!peername) {
exit(255);
}
printf("Starting to claim name: %s, system name: %s\n", hostname, peername);
if(pico_mdns_init(hostname, &mdns_init_callback, peername) != 0)
printf("Init returned with error\n");
printf("\nStarting mDNS module...\n");
if (pico_mdns_init(hostname, address, &mdns_init_callback, NULL)) {
printf("Initialisation returned with Error!\n");
exit(255);
}
printf("DONE - Initialising mDNS module.\n");
while(1) {
pico_stack_tick();
usleep(2000);
......
......@@ -28,7 +28,6 @@
#include "pico_dhcp_server.h"
#include "pico_ipfilter.h"
#include "pico_olsr.h"
#include "pico_aodv.h"
#include "pico_sntp_client.h"
#include "pico_mdns.h"
#include "pico_tftp.h"
......@@ -56,14 +55,14 @@ void app_mcastreceive(char *args);
void app_ping(char *args);
void app_dhcp_server(char *args);
void app_dhcp_client(char *args);
void app_mdns(char *args);
void app_dns_sd(char *arg, struct pico_ip4 addr);
void app_mdns(char *arg, struct pico_ip4 addr);
void app_sntp(char *args);
void app_tftp(char *args);
void app_slaacv4(char *args);
void app_udpecho(char *args);
void app_sendto_test(char *args);
void app_noop(void);
void app_iperfc(char *args);
struct pico_ip4 ZERO_IP4 = {
......@@ -159,6 +158,7 @@ int main(int argc, char **argv)
};
uint16_t *macaddr_low = (uint16_t *) (macaddr + 2);
struct pico_device *dev = NULL;
struct pico_ip4 addr4 = {0};
struct pico_ip4 bcastAddr = ZERO_IP4;
struct option long_options[] = {
......@@ -255,7 +255,6 @@ int main(int argc, char **argv)
pico_string_to_ipv6(gw, gateway6.addr);
pico_ipv6_route_add(zero6, zero6, gateway6, 1, NULL);
}
pico_ipv6_dev_routing_enable(dev);
}
......@@ -309,7 +308,6 @@ int main(int argc, char **argv)
pico_string_to_ipv6(gw, gateway6.addr);
pico_ipv6_route_add(zero6, zero6, gateway6, 1, NULL);
}
pico_ipv6_dev_routing_enable(dev);
}
......@@ -345,9 +343,10 @@ int main(int argc, char **argv)
nxt = cpy_arg(&loss_out, nxt);
if (!nxt) break;
} else {
nxt = cpy_arg(&addr6, nxt);
if (!nxt) break;
printf("addr6: %s\n", addr6);
nxt = cpy_arg(&nm6, nxt);
if (!nxt) break;
......@@ -379,10 +378,10 @@ int main(int argc, char **argv)
printf("Vde created.\n");
if (!IPV6_MODE) {
pico_string_to_ipv4(addr, &ipaddr.addr);
pico_string_to_ipv4(nm, &netmask.addr);
pico_ipv4_link_add(dev, ipaddr, netmask);
addr4 = ipaddr;
bcastAddr.addr = (ipaddr.addr) | (~netmask.addr);
if (gw && *gw) {
pico_string_to_ipv4(gw, &gateway.addr);
......@@ -401,10 +400,8 @@ int main(int argc, char **argv)
pico_string_to_ipv6(gw6, gateway6.addr);
pico_ipv6_route_add(zero6, zero6, gateway6, 1, NULL);
}
pico_ipv6_dev_routing_enable(dev);
}
#endif
if (loss_in && (strlen(loss_in) > 0)) {
i_pc = (uint32_t)atoi(loss_in);
......@@ -470,8 +467,8 @@ int main(int argc, char **argv)
pico_string_to_ipv6("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", netmask6.addr);
pico_ipv6_link_add(dev, ipaddr6, netmask6);
}
pico_ipv6_dev_routing_enable(dev);
#endif
}
break;
......@@ -568,15 +565,21 @@ int main(int argc, char **argv)
return 0;
#else
app_dhcp_client(args);
#endif
} else IF_APPNAME("dns_sd") {
#ifndef PICO_SUPPORT_DNS_SD
return 0;
#else
app_dns_sd(args, addr4);
#endif
} else IF_APPNAME("mdns") {
#ifndef PICO_SUPPORT_MDNS
return 0;
#else
app_mdns(args);
app_mdns(args, addr4);
#endif
#ifdef PICO_SUPPORT_SNTP_CLIENT
}else IF_APPNAME("sntp") {
} else IF_APPNAME("sntp") {
app_sntp(args);
#endif
} else IF_APPNAME("bcast") {
......@@ -606,23 +609,6 @@ int main(int argc, char **argv)
pico_olsr_add(dev);
}
app_noop();
#endif
#ifdef PICO_SUPPORT_AODV
} else IF_APPNAME("aodv") {
union pico_address aaa;
pico_string_to_ipv4("10.10.10.10", &aaa.ip4.addr);
dev = pico_get_device("pic0");
if(dev) {
pico_aodv_add(dev);
}
dev = pico_get_device("pic1");
if(dev) {
pico_aodv_add(dev);
}
app_noop();
#endif
} else IF_APPNAME("slaacv4") {
......@@ -633,8 +619,6 @@ int main(int argc, char **argv)
#endif
} else IF_APPNAME("udp_sendto_test") {
app_sendto_test(args);
} else IF_APPNAME("iperfc") {
app_iperfc(args);
} else {
fprintf(stderr, "Unknown application %s\n", name);
usage(argv[0]);
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -9,9 +9,11 @@ rm -f /tmp/pico-mem-report-*
./build/test/modunit_tcp.elf || exit 1
./build/test/modunit_dev_loop.elf || exit 1
./build/test/modunit_dns_client.elf || exit 1
./build/test/modunit_dns_common.elf || exit 1
./build/test/modunit_sntp_client.elf || exit 1
./build/test/modunit_ipv6_nd.elf || exit 1
./build/test/modunit_mdns.elf || exit 1
./build/test/modunit_dns_sd.elf || exit 1
./build/test/modunit_ipfilter.elf || exit 1
./build/test/modunit_queue.elf || exit 1
./build/test/modunit_tftp.elf || exit 1
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment