Commit 6aa4eec6 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] dvb: Update DVB core

From: Michael Hunold <hunold@linuxtv.org>

add a parameter to dvb_filter_pes2ts function to specify whether the packet
is a payload unit start or not.

new section demux code by emard

change license GPL -> LGPL for dvb_ringbuffer, like all other DVB core files

fix rare crash on invalid packets, patch by Asier Aguirre

i2c: copy the data variable as well on register client so that detach sees it.
parent 5e32009d
...@@ -43,6 +43,15 @@ ...@@ -43,6 +43,15 @@
#define DMX_MAX_FILTER_SIZE 18 #define DMX_MAX_FILTER_SIZE 18
#endif #endif
/*
* DMX_MAX_SECFEED_SIZE: Maximum length (in bytes) of a private section feed filter.
*/
#ifndef DMX_MAX_SECFEED_SIZE
#define DMX_MAX_SECFEED_SIZE 4096
#endif
/* /*
* enum dmx_success: Success codes for the Demux Callback API. * enum dmx_success: Success codes for the Demux Callback API.
*/ */
...@@ -143,9 +152,9 @@ struct dmx_section_feed { ...@@ -143,9 +152,9 @@ struct dmx_section_feed {
int check_crc; int check_crc;
u32 crc_val; u32 crc_val;
u8 secbuf[4096]; u8 *secbuf;
int secbufp; u8 secbuf_base[DMX_MAX_SECFEED_SIZE];
int seclen; u16 secbufp, seclen, tsfeedp;
int (*set) (struct dmx_section_feed* feed, int (*set) (struct dmx_section_feed* feed,
u16 pid, u16 pid,
......
...@@ -34,6 +34,11 @@ ...@@ -34,6 +34,11 @@
#include "dvb_functions.h" #include "dvb_functions.h"
#define NOBUFS #define NOBUFS
/*
** #define DVB_DEMUX_SECTION_LOSS_LOG to monitor payload loss in the syslog
*/
// #define DVB_DEMUX_SECTION_LOSS_LOG
LIST_HEAD(dmx_muxs); LIST_HEAD(dmx_muxs);
...@@ -87,7 +92,7 @@ static inline u16 ts_pid(const u8 *buf) ...@@ -87,7 +92,7 @@ static inline u16 ts_pid(const u8 *buf)
} }
static inline int payload(const u8 *tsp) static inline u8 payload(const u8 *tsp)
{ {
if (!(tsp[3]&0x10)) // no payload? if (!(tsp[3]&0x10)) // no payload?
return 0; return 0;
...@@ -188,9 +193,7 @@ static inline int dvb_dmx_swfilter_section_feed (struct dvb_demux_feed *feed) ...@@ -188,9 +193,7 @@ static inline int dvb_dmx_swfilter_section_feed (struct dvb_demux_feed *feed)
struct dvb_demux_filter *f = feed->filter; struct dvb_demux_filter *f = feed->filter;
struct dmx_section_feed *sec = &feed->feed.sec; struct dmx_section_feed *sec = &feed->feed.sec;
u8 *buf = sec->secbuf; u8 *buf = sec->secbuf;
int section_syntax_indicator;
if (sec->secbufp != sec->seclen)
return -1;
if (!sec->is_filtering) if (!sec->is_filtering)
return 0; return 0;
...@@ -198,15 +201,19 @@ static inline int dvb_dmx_swfilter_section_feed (struct dvb_demux_feed *feed) ...@@ -198,15 +201,19 @@ static inline int dvb_dmx_swfilter_section_feed (struct dvb_demux_feed *feed)
if (!f) if (!f)
return 0; return 0;
if (sec->check_crc && demux->check_crc32(feed, sec->secbuf, sec->seclen)) if (sec->check_crc) {
section_syntax_indicator = ((sec->secbuf[1] & 0x80) != 0);
if (section_syntax_indicator &&
demux->check_crc32(feed, sec->secbuf, sec->seclen))
return -1; return -1;
}
do { do {
if (dvb_dmx_swfilter_sectionfilter(feed, f) < 0) if (dvb_dmx_swfilter_sectionfilter(feed, f) < 0)
return -1; return -1;
} while ((f = f->next) && sec->is_filtering); } while ((f = f->next) && sec->is_filtering);
sec->secbufp = sec->seclen = 0; sec->seclen = 0;
memset(buf, 0, DVB_DEMUX_MASK_MAX); memset(buf, 0, DVB_DEMUX_MASK_MAX);
...@@ -214,128 +221,147 @@ static inline int dvb_dmx_swfilter_section_feed (struct dvb_demux_feed *feed) ...@@ -214,128 +221,147 @@ static inline int dvb_dmx_swfilter_section_feed (struct dvb_demux_feed *feed)
} }
static int dvb_dmx_swfilter_section_packet(struct dvb_demux_feed *feed, const u8 *buf) static void dvb_dmx_swfilter_section_new(struct dvb_demux_feed *feed)
{ {
struct dvb_demux *demux = feed->demux;
struct dmx_section_feed *sec = &feed->feed.sec; struct dmx_section_feed *sec = &feed->feed.sec;
int p, count;
int ccok, rest;
u8 cc;
if (!(count = payload(buf)))
return -1;
p = 188-count;
cc = buf[3] & 0x0f; #ifdef DVB_DEMUX_SECTION_LOSS_LOG
ccok = ((feed->cc+1) & 0x0f) == cc ? 1 : 0; if(sec->secbufp < sec->tsfeedp)
feed->cc = cc; {
int i, n = sec->tsfeedp - sec->secbufp;
if (buf[1] & 0x40) { // PUSI set
// offset to start of first section is in buf[p] /* section padding is done with 0xff bytes entirely.
if (p+buf[p]>187) // trash if it points beyond packet ** due to speed reasons, we won't check all of them
return -1; ** but just first and last
*/
if (buf[p] && ccok) { // rest of previous section? if(sec->secbuf[0] != 0xff || sec->secbuf[n-1] != 0xff)
// did we have enough data in last packet to calc length? {
int tmp = 3 - sec->secbufp; printk("dvb_demux.c section ts padding loss: %d/%d\n",
n, sec->tsfeedp);
if (tmp > 0 && tmp != 3) { printk("dvb_demux.c pad data:");
if (p + tmp >= 187) for(i = 0; i < n; i++)
return -1; printk(" %02x", sec->secbuf[i]);
printk("\n");
demux->memcopy (feed, sec->secbuf+sec->secbufp,
buf+p+1, tmp);
sec->seclen = section_length(sec->secbuf);
if (sec->seclen > 4096)
return -1;
} }
rest = sec->seclen - sec->secbufp;
if (rest == buf[p] && sec->seclen) {
demux->memcopy (feed, sec->secbuf + sec->secbufp,
buf+p+1, buf[p]);
sec->secbufp += buf[p];
dvb_dmx_swfilter_section_feed(feed);
} }
#endif
sec->tsfeedp = sec->secbufp = sec->seclen = 0;
sec->secbuf = sec->secbuf_base;
} }
p += buf[p] + 1; // skip rest of last section /*
count = 188 - p; ** Losless Section Demux 1.4 by Emard
*/
static int dvb_dmx_swfilter_section_copy_dump(struct dvb_demux_feed *feed, const u8 *buf, u8 len)
{
struct dvb_demux *demux = feed->demux;
struct dmx_section_feed *sec = &feed->feed.sec;
u16 limit, seclen, n;
while (count) { if(sec->tsfeedp >= DMX_MAX_SECFEED_SIZE)
return 0;
sec->crc_val = ~0; if(sec->tsfeedp + len > DMX_MAX_SECFEED_SIZE)
{
#ifdef DVB_DEMUX_SECTION_LOSS_LOG
printk("dvb_demux.c section buffer full loss: %d/%d\n",
sec->tsfeedp + len - DMX_MAX_SECFEED_SIZE, DMX_MAX_SECFEED_SIZE);
#endif
len = DMX_MAX_SECFEED_SIZE - sec->tsfeedp;
}
if ((count>2) && // enough data to determine sec length? if(len <= 0)
((sec->seclen = section_length(buf+p)) <= count)) { return 0;
if (sec->seclen>4096)
return -1;
demux->memcopy (feed, sec->secbuf, buf+p, demux->memcopy(feed, sec->secbuf_base + sec->tsfeedp, buf, len);
sec->seclen); sec->tsfeedp += len;
sec->secbufp = sec->seclen; /* -----------------------------------------------------
p += sec->seclen; ** Dump all the sections we can find in the data (Emard)
count = 188 - p; */
dvb_dmx_swfilter_section_feed(feed); limit = sec->tsfeedp;
if(limit > DMX_MAX_SECFEED_SIZE)
return -1; /* internal error should never happen */
// filling bytes until packet end? /* to be sure always set secbuf */
if (count && buf[p]==0xff) sec->secbuf = sec->secbuf_base + sec->secbufp;
count=0;
} else { // section continues to following TS packet for(n = 0; sec->secbufp + 2 < limit; n++)
demux->memcopy(feed, sec->secbuf, buf+p, count); {
sec->secbufp+=count; seclen = section_length(sec->secbuf);
count=0; if(seclen <= 0 || seclen > DMX_MAX_SECFEED_SIZE
} || seclen + sec->secbufp > limit)
return 0;
sec->seclen = seclen;
sec->crc_val = ~0;
/* dump [secbuf .. secbuf+seclen) */
dvb_dmx_swfilter_section_feed(feed);
sec->secbufp += seclen; /* secbufp and secbuf moving together is */
sec->secbuf += seclen; /* redundand but saves pointer arithmetic */
} }
return 0; return 0;
} }
// section continued below
if (!ccok)
return -1;
if (!sec->secbufp) // any data in last ts packet? static int dvb_dmx_swfilter_section_packet(struct dvb_demux_feed *feed, const u8 *buf)
return -1; {
u8 p, count;
int ccok;
u8 cc;
// did we have enough data in last packet to calc section length? count = payload(buf);
if (sec->secbufp < 3) {
int tmp = 3 - sec->secbufp;
if (tmp>count) if (count == 0) /* count == 0 if no payload or out of range */
return -1; return -1;
sec->crc_val = ~0; p = 188-count; /* payload start */
demux->memcopy (feed, sec->secbuf + sec->secbufp, buf+p, tmp);
sec->seclen = section_length(sec->secbuf); cc = buf[3] & 0x0f;
ccok = ((feed->cc+1) & 0x0f) == cc ? 1 : 0;
if (sec->seclen > 4096) feed->cc = cc;
return -1; if(ccok == 0)
{
#ifdef DVB_DEMUX_SECTION_LOSS_LOG
printk("dvb_demux.c discontinuity detected %d bytes lost\n", count);
/* those bytes under sume circumstances will again be reported
** in the following dvb_dmx_swfilter_section_new
*/
#endif
dvb_dmx_swfilter_section_new(feed);
return 0;
} }
rest = sec->seclen - sec->secbufp; if(buf[1] & 0x40)
{
if (rest < 0) // PUSI=1 (is set), section boundary is here
return -1; if(count > 1 && buf[p] < count)
{
if (rest <= count) { // section completed in this TS packet const u8 *before = buf+p+1;
demux->memcopy (feed, sec->secbuf + sec->secbufp, buf+p, rest); u8 before_len = buf[p];
sec->secbufp += rest; const u8 *after = before+before_len;
dvb_dmx_swfilter_section_feed(feed); u8 after_len = count-1-before_len;
} else { // section continues in following ts packet
demux->memcopy (feed, sec->secbuf + sec->secbufp, buf+p, count); dvb_dmx_swfilter_section_copy_dump(feed, before, before_len);
sec->secbufp += count; dvb_dmx_swfilter_section_new(feed);
dvb_dmx_swfilter_section_copy_dump(feed, after, after_len);
}
#ifdef DVB_DEMUX_SECTION_LOSS_LOG
else
if(count > 0)
printk("dvb_demux.c PUSI=1 but %d bytes lost\n", count);
#endif
} }
else
{
// PUSI=0 (is not set), no section boundary
const u8 *entire = buf+p;
u8 entire_len = count;
dvb_dmx_swfilter_section_copy_dump(feed, entire, entire_len);
}
return 0; return 0;
} }
...@@ -439,6 +465,50 @@ void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count) ...@@ -439,6 +465,50 @@ void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count)
spin_unlock(&demux->lock); spin_unlock(&demux->lock);
} }
void dvb_dmx_swfilter_204(struct dvb_demux *demux, const u8 *buf, size_t count)
{
int p = 0,i, j;
u8 tmppack[188];
spin_lock(&demux->lock);
if ((i = demux->tsbufp)) {
if (count < (j=204-i)) {
memcpy(&demux->tsbuf[i], buf, count);
demux->tsbufp += count;
goto bailout;
}
memcpy(&demux->tsbuf[i], buf, j);
if ((demux->tsbuf[0] == 0x47)|(demux->tsbuf[0]==0xB8)) {
memcpy(tmppack, demux->tsbuf, 188);
if (tmppack[0] == 0xB8) tmppack[0] = 0x47;
dvb_dmx_swfilter_packet(demux, tmppack);
}
demux->tsbufp = 0;
p += j;
}
while (p < count) {
if ((buf[p] == 0x47)|(buf[p] == 0xB8)) {
if (count-p >= 204) {
memcpy(tmppack, buf+p, 188);
if (tmppack[0] == 0xB8) tmppack[0] = 0x47;
dvb_dmx_swfilter_packet(demux, tmppack);
p += 204;
} else {
i = count-p;
memcpy(demux->tsbuf, buf+p, i);
demux->tsbufp=i;
goto bailout;
}
} else {
p++;
}
}
bailout:
spin_unlock(&demux->lock);
}
static struct dvb_demux_filter * dvb_dmx_filter_alloc(struct dvb_demux *demux) static struct dvb_demux_filter * dvb_dmx_filter_alloc(struct dvb_demux *demux)
{ {
...@@ -848,6 +918,9 @@ static int dmx_section_feed_start_filtering(struct dmx_section_feed *feed) ...@@ -848,6 +918,9 @@ static int dmx_section_feed_start_filtering(struct dmx_section_feed *feed)
up(&dvbdmx->mutex); up(&dvbdmx->mutex);
return -EINVAL; return -EINVAL;
} }
dvbdmxfeed->feed.sec.tsfeedp = 0;
dvbdmxfeed->feed.sec.secbuf = dvbdmxfeed->feed.sec.secbuf_base;
dvbdmxfeed->feed.sec.secbufp=0; dvbdmxfeed->feed.sec.secbufp=0;
dvbdmxfeed->feed.sec.seclen=0; dvbdmxfeed->feed.sec.seclen=0;
...@@ -946,7 +1019,9 @@ static int dvbdmx_allocate_section_feed(struct dmx_demux *demux, ...@@ -946,7 +1019,9 @@ static int dvbdmx_allocate_section_feed(struct dmx_demux *demux,
dvbdmxfeed->cb.sec=callback; dvbdmxfeed->cb.sec=callback;
dvbdmxfeed->demux=dvbdmx; dvbdmxfeed->demux=dvbdmx;
dvbdmxfeed->pid=0xffff; dvbdmxfeed->pid=0xffff;
dvbdmxfeed->feed.sec.secbufp=0; dvbdmxfeed->feed.sec.secbuf = dvbdmxfeed->feed.sec.secbuf_base;
dvbdmxfeed->feed.sec.secbufp = dvbdmxfeed->feed.sec.seclen = 0;
dvbdmxfeed->feed.sec.tsfeedp = 0;
dvbdmxfeed->filter=0; dvbdmxfeed->filter=0;
dvbdmxfeed->buffer=0; dvbdmxfeed->buffer=0;
......
...@@ -127,7 +127,7 @@ struct dvb_demux { ...@@ -127,7 +127,7 @@ struct dvb_demux {
#define DMX_MAX_PID 0x2000 #define DMX_MAX_PID 0x2000
struct list_head feed_list; struct list_head feed_list;
u8 tsbuf[188]; u8 tsbuf[204];
int tsbufp; int tsbufp;
struct semaphore mutex; struct semaphore mutex;
...@@ -140,6 +140,7 @@ int dvb_dmx_release(struct dvb_demux *dvbdemux); ...@@ -140,6 +140,7 @@ int dvb_dmx_release(struct dvb_demux *dvbdemux);
void dvb_dmx_swfilter_packet(struct dvb_demux *dvbdmx, const u8 *buf); void dvb_dmx_swfilter_packet(struct dvb_demux *dvbdmx, const u8 *buf);
void dvb_dmx_swfilter_packets(struct dvb_demux *dvbdmx, const u8 *buf, size_t count); void dvb_dmx_swfilter_packets(struct dvb_demux *dvbdmx, const u8 *buf, size_t count);
void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count); void dvb_dmx_swfilter(struct dvb_demux *demux, const u8 *buf, size_t count);
void dvb_dmx_swfilter_204(struct dvb_demux *demux, const u8 *buf, size_t count);
int dvbdmx_connect_frontend(struct dmx_demux *demux, struct dmx_frontend *frontend); int dvbdmx_connect_frontend(struct dmx_demux *demux, struct dmx_frontend *frontend);
int dvbdmx_disconnect_frontend(struct dmx_demux *demux); int dvbdmx_disconnect_frontend(struct dmx_demux *demux);
......
...@@ -564,14 +564,18 @@ void dvb_filter_pes2ts_init(struct dvb_filter_pes2ts *p2ts, unsigned short pid, ...@@ -564,14 +564,18 @@ void dvb_filter_pes2ts_init(struct dvb_filter_pes2ts *p2ts, unsigned short pid,
p2ts->priv=priv; p2ts->priv=priv;
} }
int dvb_filter_pes2ts(struct dvb_filter_pes2ts *p2ts, unsigned char *pes, int len) int dvb_filter_pes2ts(struct dvb_filter_pes2ts *p2ts, unsigned char *pes,
int len, int payload_start)
{ {
unsigned char *buf=p2ts->buf; unsigned char *buf=p2ts->buf;
int ret=0, rest; int ret=0, rest;
//len=6+((pes[4]<<8)|pes[5]); //len=6+((pes[4]<<8)|pes[5]);
if (payload_start)
buf[1]|=0x40; buf[1]|=0x40;
else
buf[1]&=~0x40;
while (len>=184) { while (len>=184) {
buf[3]=0x10|((p2ts->cc++)&0x0f); buf[3]=0x10|((p2ts->cc++)&0x0f);
memcpy(buf+4, pes, 184); memcpy(buf+4, pes, 184);
......
...@@ -37,7 +37,8 @@ struct dvb_filter_pes2ts { ...@@ -37,7 +37,8 @@ struct dvb_filter_pes2ts {
void dvb_filter_pes2ts_init(struct dvb_filter_pes2ts *p2ts, unsigned short pid, void dvb_filter_pes2ts_init(struct dvb_filter_pes2ts *p2ts, unsigned short pid,
dvb_filter_pes2ts_cb_t *cb, void *priv); dvb_filter_pes2ts_cb_t *cb, void *priv);
int dvb_filter_pes2ts(struct dvb_filter_pes2ts *p2ts, unsigned char *pes, int len); int dvb_filter_pes2ts(struct dvb_filter_pes2ts *p2ts, unsigned char *pes,
int len, int payload_start);
#define PROG_STREAM_MAP 0xBC #define PROG_STREAM_MAP 0xBC
......
...@@ -51,6 +51,7 @@ static int register_i2c_client (struct dvb_i2c_bus *i2c, struct dvb_i2c_device * ...@@ -51,6 +51,7 @@ static int register_i2c_client (struct dvb_i2c_bus *i2c, struct dvb_i2c_device *
client->detach = dev->detach; client->detach = dev->detach;
client->owner = dev->owner; client->owner = dev->owner;
client->data = dev->data;
INIT_LIST_HEAD(&client->list_head); INIT_LIST_HEAD(&client->list_head);
......
...@@ -18,6 +18,7 @@ EXPORT_SYMBOL(dvb_dmx_release); ...@@ -18,6 +18,7 @@ EXPORT_SYMBOL(dvb_dmx_release);
EXPORT_SYMBOL(dvb_dmx_swfilter_packet); EXPORT_SYMBOL(dvb_dmx_swfilter_packet);
EXPORT_SYMBOL(dvb_dmx_swfilter_packets); EXPORT_SYMBOL(dvb_dmx_swfilter_packets);
EXPORT_SYMBOL(dvb_dmx_swfilter); EXPORT_SYMBOL(dvb_dmx_swfilter);
EXPORT_SYMBOL(dvb_dmx_swfilter_204);
EXPORT_SYMBOL(dvbdmx_connect_frontend); EXPORT_SYMBOL(dvbdmx_connect_frontend);
EXPORT_SYMBOL(dvbdmx_disconnect_frontend); EXPORT_SYMBOL(dvbdmx_disconnect_frontend);
......
...@@ -9,24 +9,18 @@ ...@@ -9,24 +9,18 @@
* & Marcus Metzler for convergence integrated media GmbH * & Marcus Metzler for convergence integrated media GmbH
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 * as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version. * of the License, or (at your option) any later version.
* *
*
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU Lesser General Public License for more details.
*
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
*
*
* the project's page is at http://www.linuxtv.org/dvb/
*/ */
...@@ -167,11 +161,11 @@ ssize_t dvb_ringbuffer_write(struct dvb_ringbuffer *rbuf, const u8 *buf, ...@@ -167,11 +161,11 @@ ssize_t dvb_ringbuffer_write(struct dvb_ringbuffer *rbuf, const u8 *buf,
} }
EXPORT_SYMBOL_GPL(dvb_ringbuffer_init); EXPORT_SYMBOL(dvb_ringbuffer_init);
EXPORT_SYMBOL_GPL(dvb_ringbuffer_empty); EXPORT_SYMBOL(dvb_ringbuffer_empty);
EXPORT_SYMBOL_GPL(dvb_ringbuffer_free); EXPORT_SYMBOL(dvb_ringbuffer_free);
EXPORT_SYMBOL_GPL(dvb_ringbuffer_avail); EXPORT_SYMBOL(dvb_ringbuffer_avail);
EXPORT_SYMBOL_GPL(dvb_ringbuffer_flush); EXPORT_SYMBOL(dvb_ringbuffer_flush);
EXPORT_SYMBOL_GPL(dvb_ringbuffer_flush_spinlock_wakeup); EXPORT_SYMBOL(dvb_ringbuffer_flush_spinlock_wakeup);
EXPORT_SYMBOL_GPL(dvb_ringbuffer_read); EXPORT_SYMBOL(dvb_ringbuffer_read);
EXPORT_SYMBOL_GPL(dvb_ringbuffer_write); EXPORT_SYMBOL(dvb_ringbuffer_write);
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software * along with this program; if not, write to the Free Software
......
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