Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
L
lsquic
Manage
Activity
Members
Labels
Plan
Issues
0
Issue boards
Milestones
Iterations
Wiki
Requirements
Code
Merge requests
1
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
quic-testing
lsquic
Commits
8d029038
Commit
8d029038
authored
6 years ago
by
Dmitri Tikhonov
Browse files
Options
Downloads
Patches
Plain Diff
Drop the few remaining references to Q041
parent
97028223
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
src/liblsquic/lsquic_parse.h
+0
-4
0 additions, 4 deletions
src/liblsquic/lsquic_parse.h
src/liblsquic/lsquic_parse_gquic_Q041.c
+0
-558
0 additions, 558 deletions
src/liblsquic/lsquic_parse_gquic_Q041.c
with
0 additions
and
562 deletions
src/liblsquic/lsquic_parse.h
+
0
−
4
View file @
8d029038
...
...
@@ -161,7 +161,6 @@ struct parse_funcs
extern
const
struct
parse_funcs
lsquic_parse_funcs_gquic_le
;
/* Q039 and later are big-endian: */
extern
const
struct
parse_funcs
lsquic_parse_funcs_gquic_Q039
;
extern
const
struct
parse_funcs
lsquic_parse_funcs_gquic_Q041
;
#define select_pf_by_ver(ver) ( \
((1 << (ver)) & (1 << LSQVER_035)) \
...
...
@@ -176,9 +175,6 @@ parse_packet_in_begin (struct lsquic_packet_in *, size_t length,
enum
QUIC_FRAME_TYPE
parse_frame_type_gquic_Q035_thru_Q039
(
unsigned
char
first_byte
);
enum
QUIC_FRAME_TYPE
parse_frame_type_gquic_Q041
(
unsigned
char
first_byte
);
unsigned
parse_stream_frame_header_sz_gquic
(
unsigned
char
type
);
...
...
This diff is collapsed.
Click to expand it.
src/liblsquic/lsquic_parse_gquic_Q041.c
deleted
100644 → 0
+
0
−
558
View file @
97028223
/* Copyright (c) 2017 - 2018 LiteSpeed Technologies Inc. See LICENSE. */
#include
<assert.h>
#include
<inttypes.h>
#include
<errno.h>
#include
<stdlib.h>
#include
<string.h>
#include
<sys/queue.h>
#ifndef WIN32
#include
<sys/types.h>
#else
#include
<vc_compat.h>
#endif
#include
"lsquic_types.h"
#include
"lsquic_alarmset.h"
#include
"lsquic_packet_common.h"
#include
"lsquic_packet_in.h"
#include
"lsquic_parse.h"
#include
"lsquic_rechist.h"
#include
"lsquic_sfcw.h"
#include
"lsquic_stream.h"
#include
"lsquic_mm.h"
#include
"lsquic_malo.h"
#include
"lsquic_version.h"
#include
"lsquic.h"
#include
"lsquic_parse_gquic_be.h"
#define LSQUIC_LOGGER_MODULE LSQLM_PARSE
#include
"lsquic_logger.h"
int
gquic_ietf_gen_rst_frame
(
unsigned
char
*
buf
,
size_t
buf_len
,
uint32_t
stream_id
,
uint64_t
offset
,
uint32_t
error_code
)
{
unsigned
char
*
p
=
buf
;
if
(
buf_len
<
QUIC_RST_STREAM_SZ
)
return
-
1
;
*
p
=
0x01
;
++
p
;
#if __BYTE_ORDER == __LITTLE_ENDIAN
stream_id
=
bswap_32
(
stream_id
);
#endif
memcpy
(
p
,
&
stream_id
,
4
);
p
+=
4
;
#if __BYTE_ORDER == __LITTLE_ENDIAN
error_code
=
bswap_32
(
error_code
);
#endif
memcpy
(
p
,
&
error_code
,
4
);
p
+=
4
;
#if __BYTE_ORDER == __LITTLE_ENDIAN
offset
=
bswap_64
(
offset
);
#endif
memcpy
(
p
,
&
offset
,
8
);
p
+=
8
;
return
p
-
buf
;
}
int
gquic_ietf_parse_rst_frame
(
const
unsigned
char
*
buf
,
size_t
buf_len
,
uint32_t
*
stream_id
,
uint64_t
*
offset
,
uint32_t
*
error_code
)
{
if
(
buf_len
<
QUIC_RST_STREAM_SZ
)
return
-
1
;
READ_UINT
(
*
stream_id
,
32
,
buf
+
1
,
4
);
READ_UINT
(
*
error_code
,
32
,
buf
+
1
+
4
,
4
);
READ_UINT
(
*
offset
,
64
,
buf
+
1
+
4
+
4
,
8
);
return
QUIC_RST_STREAM_SZ
;
}
unsigned
gquic_ietf_parse_stream_frame_header_sz
(
unsigned
char
type
)
{
const
unsigned
data_len
=
(
type
&
1
)
<<
1
;
const
unsigned
stream_id_len
=
((
type
>>
3
)
&
3
)
+
1
;
const
unsigned
offset_len
=
(((
type
>>
1
)
&
3
)
<<
1
)
+
((
3
==
((
type
>>
1
)
&
3
))
<<
1
);
return
1
+
data_len
+
offset_len
+
stream_id_len
;
}
static
void
gquic_ietf_turn_on_fin
(
unsigned
char
*
stream_header
)
{
/* 11FSSOOD */
*
stream_header
|=
0x20
;
}
int
gquic_ietf_parse_stream_frame
(
const
unsigned
char
*
buf
,
size_t
rem_packet_sz
,
stream_frame_t
*
stream_frame
)
{
/* 11FSSOOD */
const
unsigned
char
*
p
=
buf
;
const
unsigned
char
*
const
pend
=
p
+
rem_packet_sz
;
CHECK_SPACE
(
1
,
p
,
pend
);
const
char
type
=
*
p
++
;
const
unsigned
data_len
=
(
type
&
1
)
<<
1
;
const
unsigned
stream_id_len
=
((
type
>>
3
)
&
3
)
+
1
;
const
unsigned
offset_len
=
(((
type
>>
1
)
&
3
)
<<
1
)
+
((
3
==
((
type
>>
1
)
&
3
))
<<
1
);
const
unsigned
need
=
data_len
+
offset_len
+
stream_id_len
;
CHECK_SPACE
(
need
,
p
,
pend
);
memset
(
stream_frame
,
0
,
sizeof
(
*
stream_frame
));
stream_frame
->
data_frame
.
df_fin
=
!!
(
type
&
0x20
);
READ_UINT
(
stream_frame
->
stream_id
,
32
,
p
,
stream_id_len
);
p
+=
stream_id_len
;
READ_UINT
(
stream_frame
->
data_frame
.
df_offset
,
64
,
p
,
offset_len
);
p
+=
offset_len
;
if
(
data_len
)
{
READ_UINT
(
stream_frame
->
data_frame
.
df_size
,
16
,
p
,
data_len
);
p
+=
data_len
;
CHECK_SPACE
(
stream_frame
->
data_frame
.
df_size
,
p
,
pend
);
stream_frame
->
data_frame
.
df_data
=
p
;
p
+=
stream_frame
->
data_frame
.
df_size
;
}
else
{
stream_frame
->
data_frame
.
df_size
=
pend
-
p
;
stream_frame
->
data_frame
.
df_data
=
p
;
p
=
pend
;
}
/* From the spec: "A stream frame must always have either non-zero
* data length or the FIN bit set.'
*/
if
(
!
(
stream_frame
->
data_frame
.
df_size
||
stream_frame
->
data_frame
.
df_fin
))
return
-
1
;
assert
(
p
<=
pend
);
return
p
-
(
unsigned
char
*
)
buf
;
}
static
size_t
gquic_ietf_calc_stream_frame_header_sz
(
uint32_t
stream_id
,
uint64_t
offset
)
{
return
/* Type */
1
/* SS: Stream ID length: 1, 2, 3, or 4 bytes */
+
(
stream_id
>
0x0000FF
)
+
(
stream_id
>
0x00FFFF
)
+
(
stream_id
>
0xFFFFFF
)
+
1
/* OO: Offset length: 0, 2, 4, or 8 bytes */
+
((
offset
>=
(
1ULL
<<
32
))
<<
2
)
+
((
offset
>=
(
1ULL
<<
16
))
<<
1
)
+
((
offset
>
1
)
<<
1
)
;
}
int
gquic_ietf_gen_stream_frame
(
unsigned
char
*
buf
,
size_t
buf_len
,
uint32_t
stream_id
,
uint64_t
offset
,
int
fin
,
size_t
size
,
gsf_read_f
gsf_read
,
void
*
stream
)
{
/* 11FSSOOD */
unsigned
slen
,
olen
,
dlen
;
unsigned
char
*
p
=
buf
+
1
;
/* SS: Stream ID length: 1, 2, 3, or 4 bytes */
slen
=
(
stream_id
>
0x0000FF
)
+
(
stream_id
>
0x00FFFF
)
+
(
stream_id
>
0xFFFFFF
)
+
1
;
/* OO: Offset length: 0, 2, 4, or 8 bytes */
olen
=
((
offset
>=
(
1ULL
<<
32
))
<<
2
)
+
((
offset
>=
(
1ULL
<<
16
))
<<
1
)
+
((
offset
>
1
)
<<
1
)
;
if
(
!
fin
)
{
unsigned
n_avail
;
uint16_t
nr
;
n_avail
=
buf_len
-
(
p
+
slen
+
olen
-
buf
);
/* If we cannot fill remaining buffer, we need to include data
* length.
*/
dlen
=
(
size
<
n_avail
)
<<
1
;
n_avail
-=
dlen
;
CHECK_SPACE
(
1
+
olen
+
slen
+
dlen
+
+
1
/* We need to write at least 1 byte */
,
buf
,
buf
+
buf_len
);
#if __BYTE_ORDER == __LITTLE_ENDIAN
stream_id
=
bswap_32
(
stream_id
);
#endif
memcpy
(
p
,
(
unsigned
char
*
)
&
stream_id
+
4
-
slen
,
slen
);
p
+=
slen
;
#if __BYTE_ORDER == __LITTLE_ENDIAN
offset
=
bswap_64
(
offset
);
#endif
memcpy
(
p
,
(
unsigned
char
*
)
&
offset
+
8
-
olen
,
olen
);
p
+=
olen
;
/* Read as much as we can */
nr
=
gsf_read
(
stream
,
p
+
dlen
,
n_avail
,
&
fin
);
assert
(
nr
!=
0
);
if
(
dlen
)
{
uint16_t
nr_copy
=
nr
;
#if __BYTE_ORDER == __LITTLE_ENDIAN
nr_copy
=
bswap_16
(
nr_copy
);
#endif
memcpy
(
p
,
&
nr_copy
,
2
);
}
p
+=
dlen
+
nr
;
}
else
{
dlen
=
2
;
CHECK_SPACE
(
1
+
slen
+
olen
+
2
,
buf
,
buf
+
buf_len
);
#if __BYTE_ORDER == __LITTLE_ENDIAN
stream_id
=
bswap_32
(
stream_id
);
#endif
memcpy
(
p
,
(
unsigned
char
*
)
&
stream_id
+
4
-
slen
,
slen
);
p
+=
slen
;
#if __BYTE_ORDER == __LITTLE_ENDIAN
offset
=
bswap_64
(
offset
);
#endif
memcpy
(
p
,
(
unsigned
char
*
)
&
offset
+
8
-
olen
,
olen
);
p
+=
olen
;
memset
(
p
,
0
,
2
);
p
+=
2
;
}
/* Convert slen to bit representation: 0 - 3: */
slen
-=
1
;
assert
(
slen
<=
3
);
/* Convert olen to bit representation: 0 - 3: */
olen
>>=
1
;
olen
-=
olen
==
4
;
assert
(
olen
<=
3
);
buf
[
0
]
=
0xC0
|
(
fin
<<
5
)
|
(
slen
<<
3
)
|
(
olen
<<
1
)
|
!!
dlen
;
return
p
-
buf
;
}
int
gquic_ietf_parse_ack_frame
(
const
unsigned
char
*
buf
,
size_t
buf_len
,
ack_info_t
*
ack
)
{
/* 101NLLMM */
lsquic_packno_t
tmp_packno
;
const
unsigned
char
type
=
buf
[
0
];
const
unsigned
char
*
p
=
buf
+
1
;
const
unsigned
char
*
const
pend
=
buf
+
buf_len
;
uint8_t
n_blocks
,
n_ts
;
assert
((
type
&
0xE0
)
==
0xA0
);
/* We're passed correct frame type */
const
int
ack_block_len
=
twobit_to_1248
(
type
&
3
);
/* MM */
const
int
largest_obs_len
=
twobit_to_1248
((
type
>>
2
)
&
3
);
/* LL */
if
(
type
&
0x10
)
{
/* N */
CHECK_SPACE
(
2
,
p
,
pend
);
n_blocks
=
*
p
++
;
}
else
{
CHECK_SPACE
(
1
,
p
,
pend
);
n_blocks
=
0
;
}
n_ts
=
*
p
++
;
const
unsigned
timestamps_size
=
(
n_ts
>
0
)
*
(
1
+
4
)
+
/* Delta LA, First Timestamp */
(
n_ts
>
1
)
*
(
n_ts
-
1
)
*
(
1
+
2
);
/* Delta LA, Time Since Previous */
CHECK_SPACE
(
largest_obs_len
+
/* Largest Acknowledged */
2
+
/* ACK delay */
ack_block_len
+
/* First ACK block length */
n_blocks
*
(
1
+
ack_block_len
)
+
/* ACK blocks */
timestamps_size
,
p
,
pend
);
READ_UINT
(
ack
->
ranges
[
0
].
high
,
64
,
p
,
largest_obs_len
);
p
+=
largest_obs_len
;
ack
->
lack_delta
=
gquic_be_read_float_time16
(
p
);
p
+=
2
;
READ_UINT
(
tmp_packno
,
64
,
p
,
ack_block_len
);
ack
->
ranges
[
0
].
low
=
ack
->
ranges
[
0
].
high
-
tmp_packno
+
1
;
p
+=
ack_block_len
;
if
(
n_blocks
)
{
unsigned
i
,
n
,
gap
;
for
(
i
=
0
,
n
=
1
,
gap
=
0
;
i
<
n_blocks
;
++
i
)
{
uint64_t
length
;
gap
+=
*
p
;
READ_UINT
(
length
,
64
,
p
+
1
,
ack_block_len
);
p
+=
1
+
ack_block_len
;
if
(
length
)
{
ack
->
ranges
[
n
].
high
=
ack
->
ranges
[
n
-
1
].
low
-
gap
-
1
;
ack
->
ranges
[
n
].
low
=
ack
->
ranges
[
n
].
high
-
length
+
1
;
++
n
;
gap
=
0
;
}
}
ack
->
n_ranges
=
n
;
}
else
ack
->
n_ranges
=
1
;
ack
->
n_timestamps
=
n_ts
;
if
(
n_ts
)
{
#if LSQUIC_PARSE_ACK_TIMESTAMPS
/* TODO */
#else
/* Just skip them for now */
p
+=
timestamps_size
;
#endif
}
assert
(
p
<=
pend
);
return
p
-
(
unsigned
char
*
)
buf
;
}
/* This function makes an assumption that there is at least one range */
int
gquic_ietf_gen_ack_frame
(
unsigned
char
*
outbuf
,
size_t
outbuf_sz
,
gaf_rechist_first_f
rechist_first
,
gaf_rechist_next_f
rechist_next
,
gaf_rechist_largest_recv_f
rechist_largest_recv
,
void
*
rechist
,
lsquic_time_t
now
,
int
*
has_missing
,
lsquic_packno_t
*
largest_received
)
{
lsquic_time_t
time_diff
;
lsquic_packno_t
tmp_packno
;
const
struct
lsquic_packno_range
*
const
first
=
rechist_first
(
rechist
);
if
(
!
first
)
{
errno
=
EINVAL
;
return
-
1
;
}
/* Copy values from the first range, because the memory the pointer
* points to may change:
*/
const
lsquic_packno_t
first_low
=
first
->
low
,
first_high
=
first
->
high
;
unsigned
char
*
p
=
outbuf
;
unsigned
char
*
const
type
=
p
;
unsigned
char
*
const
end
=
p
+
outbuf_sz
;
unsigned
char
*
n_ranges_p
;
#define AVAIL() (end - p)
#define CHECKOUT(sz) do { \
if ((intptr_t) (sz) > AVAIL()) { \
errno = ENOBUFS; \
return -1; \
} \
} while (0)
CHECKOUT
(
1
);
++
p
;
/* 101NLLMM */
*
type
=
0xA0
;
unsigned
largest_acked_len
,
ack_block_len
,
bits
;
/* Calculate largest ACKed len and set `LL' bits: */
const
lsquic_packno_t
maxno
=
first_high
;
bits
=
(
maxno
>=
(
1ULL
<<
8
))
+
(
maxno
>=
(
1ULL
<<
16
))
+
(
maxno
>=
(
1ULL
<<
32
));
largest_acked_len
=
twobit_to_1248
(
bits
);
*
type
|=
bits
<<
2
;
/* Calculate largest ACK block length and set `MM' bits: */
unsigned
n_ranges
=
0
;
lsquic_packno_t
maxdiff
=
0
;
const
struct
lsquic_packno_range
*
range
;
for
(
range
=
rechist_first
(
rechist
);
range
;
range
=
rechist_next
(
rechist
))
{
++
n_ranges
;
const
lsquic_packno_t
diff
=
range
->
high
-
range
->
low
+
1
;
if
(
diff
>
maxdiff
)
maxdiff
=
diff
;
}
bits
=
(
maxdiff
>=
(
1ULL
<<
8
))
+
(
maxdiff
>=
(
1ULL
<<
16
))
+
(
maxdiff
>=
(
1ULL
<<
32
));
ack_block_len
=
twobit_to_1248
(
bits
);
*
type
|=
bits
;
if
(
n_ranges
>
1
)
{
CHECKOUT
(
2
);
*
type
|=
0x10
;
/* N */
n_ranges_p
=
p
++
;
/* Set Num Blocks later */
}
else
{
CHECKOUT
(
1
);
n_ranges_p
=
NULL
;
}
*
p
++
=
0
;
/* Do not provide any timestamps. TODO perhaps? */
CHECKOUT
(
largest_acked_len
);
tmp_packno
=
maxno
;
#if __BYTE_ORDER == __LITTLE_ENDIAN
tmp_packno
=
bswap_64
(
maxno
);
#endif
memcpy
(
p
,
(
unsigned
char
*
)
&
tmp_packno
+
8
-
largest_acked_len
,
largest_acked_len
);
p
+=
largest_acked_len
;
CHECKOUT
(
2
);
time_diff
=
now
-
rechist_largest_recv
(
rechist
);
gquic_be_write_float_time16
(
time_diff
,
p
);
LSQ_DEBUG
(
"%s: diff: %"
PRIu64
"; encoded: 0x%04X"
,
__func__
,
time_diff
,
*
(
uint16_t
*
)
p
);
p
+=
2
;
*
has_missing
=
n_ranges
>
1
;
if
(
n_ranges
>
1
)
{
/* We need to write out at least one range */
CHECKOUT
(
2
*
(
1
+
ack_block_len
));
lsquic_packno_t
diff
=
maxno
-
first_low
+
1
;
#if __BYTE_ORDER == __LITTLE_ENDIAN
diff
=
bswap_64
(
diff
);
#endif
memcpy
(
p
,
(
unsigned
char
*
)
&
diff
+
8
-
ack_block_len
,
ack_block_len
);
p
+=
ack_block_len
;
/* Write out ack blocks until one of the following occurs:
* 1. We run out of intervals.
* 2. We run out of room.
* 3. We run out of highest possible number of ACK blocks (0xFF).
*/
range
=
rechist_first
(
rechist
);
lsquic_packno_t
gap
=
0
;
n_ranges
=
0
;
do
{
if
(
0
==
gap
)
{
const
lsquic_packno_t
prev_low
=
range
->
low
;
range
=
rechist_next
(
rechist
);
if
(
!
range
)
break
;
gap
=
prev_low
-
range
->
high
-
1
;
}
if
(
gap
>=
0x100
)
{
*
p
=
0xFF
;
gap
-=
0xFF
;
memset
(
p
+
1
,
0
,
ack_block_len
);
}
else
{
*
p
=
gap
;
gap
=
0
;
diff
=
range
->
high
-
range
->
low
+
1
;
#if __BYTE_ORDER == __LITTLE_ENDIAN
diff
=
bswap_64
(
diff
);
#endif
memcpy
(
p
+
1
,
(
unsigned
char
*
)
&
diff
+
8
-
ack_block_len
,
ack_block_len
);
}
p
+=
ack_block_len
+
1
;
++
n_ranges
;
}
while
(
n_ranges
<
0xFF
&&
AVAIL
()
>=
(
intptr_t
)
ack_block_len
+
1
+
1
/* timestamp byte */
);
*
n_ranges_p
=
n_ranges
;
}
else
{
CHECKOUT
(
ack_block_len
);
lsquic_packno_t
diff
=
maxno
-
first_low
+
1
;
#if __BYTE_ORDER == __LITTLE_ENDIAN
diff
=
bswap_64
(
diff
);
#endif
memcpy
(
p
,
(
unsigned
char
*
)
&
diff
+
8
-
ack_block_len
,
ack_block_len
);
p
+=
ack_block_len
;
}
*
largest_received
=
maxno
;
return
p
-
(
unsigned
char
*
)
outbuf
;
#undef CHECKOUT
}
const
struct
parse_funcs
lsquic_parse_funcs_gquic_Q041
=
{
.
pf_gen_ver_nego_pkt
=
gquic_be_gen_ver_nego_pkt
,
.
pf_gen_reg_pkt_header
=
gquic_be_gen_reg_pkt_header
,
.
pf_parse_packet_in_finish
=
gquic_be_parse_packet_in_finish
,
.
pf_gen_stream_frame
=
gquic_ietf_gen_stream_frame
,
.
pf_calc_stream_frame_header_sz
=
gquic_ietf_calc_stream_frame_header_sz
,
.
pf_parse_stream_frame_header_sz
=
gquic_ietf_parse_stream_frame_header_sz
,
.
pf_parse_stream_frame
=
gquic_ietf_parse_stream_frame
,
.
pf_parse_ack_frame
=
gquic_ietf_parse_ack_frame
,
.
pf_gen_ack_frame
=
gquic_ietf_gen_ack_frame
,
.
pf_gen_stop_waiting_frame
=
gquic_be_gen_stop_waiting_frame
,
.
pf_parse_stop_waiting_frame
=
gquic_be_parse_stop_waiting_frame
,
.
pf_skip_stop_waiting_frame
=
gquic_be_skip_stop_waiting_frame
,
.
pf_gen_window_update_frame
=
gquic_be_gen_window_update_frame
,
.
pf_parse_window_update_frame
=
gquic_be_parse_window_update_frame
,
.
pf_gen_blocked_frame
=
gquic_be_gen_blocked_frame
,
.
pf_parse_blocked_frame
=
gquic_be_parse_blocked_frame
,
.
pf_gen_rst_frame
=
gquic_ietf_gen_rst_frame
,
.
pf_parse_rst_frame
=
gquic_ietf_parse_rst_frame
,
.
pf_gen_connect_close_frame
=
gquic_be_gen_connect_close_frame
,
.
pf_parse_connect_close_frame
=
gquic_be_parse_connect_close_frame
,
.
pf_gen_goaway_frame
=
gquic_be_gen_goaway_frame
,
.
pf_parse_goaway_frame
=
gquic_be_parse_goaway_frame
,
.
pf_gen_ping_frame
=
gquic_be_gen_ping_frame
,
#ifndef NDEBUG
.
pf_write_float_time16
=
gquic_be_write_float_time16
,
.
pf_read_float_time16
=
gquic_be_read_float_time16
,
#endif
.
pf_parse_frame_type
=
parse_frame_type_gquic_Q041
,
.
pf_turn_on_fin
=
gquic_ietf_turn_on_fin
,
};
This diff is collapsed.
Click to expand it.
Preview
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment