lib: make parse_canframe CAN XL aware
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>pull/504/head
parent
3644a54d5b
commit
34a1cfad29
|
|
@ -306,6 +306,11 @@ int main(int argc, char **argv)
|
|||
struct sockaddr_can addr = {
|
||||
.can_family = AF_CAN,
|
||||
};
|
||||
struct can_raw_vcid_options vcid_opts = {
|
||||
.flags = CAN_RAW_XL_VCID_RX_FILTER,
|
||||
.rx_vcid = 0,
|
||||
.rx_vcid_mask = 0,
|
||||
};
|
||||
char ctrlmsg[CMSG_SPACE(sizeof(struct timeval)) +
|
||||
CMSG_SPACE(3 * sizeof(struct timespec)) +
|
||||
CMSG_SPACE(sizeof(__u32))];
|
||||
|
|
@ -601,6 +606,9 @@ int main(int argc, char **argv)
|
|||
/* try to switch the socket into CAN XL mode */
|
||||
setsockopt(obj->s, SOL_CAN_RAW, CAN_RAW_XL_FRAMES, &canfx_on, sizeof(canfx_on));
|
||||
|
||||
/* try to enable the CAN XL VCID pass through mode */
|
||||
setsockopt(obj->s, SOL_CAN_RAW, CAN_RAW_XL_VCID_OPTS, &vcid_opts, sizeof(vcid_opts));
|
||||
|
||||
if (rcvbuf_size) {
|
||||
int curr_rcvbuf_size;
|
||||
socklen_t curr_rcvbuf_size_len = sizeof(curr_rcvbuf_size);
|
||||
|
|
|
|||
31
canplayer.c
31
canplayer.c
|
|
@ -71,7 +71,7 @@ struct assignment {
|
|||
char rxif[IFNAMSIZ];
|
||||
};
|
||||
static struct assignment asgn[CHANNELS];
|
||||
const int canfd_on = 1;
|
||||
const int canfx_on = 1;
|
||||
|
||||
extern int optind, opterr, optopt;
|
||||
|
||||
|
|
@ -239,9 +239,12 @@ int add_assignment(char *mode, int socket, char *txname, char *rxname, int verbo
|
|||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
static char buf[BUFSZ], device[BUFSZ], ascframe[BUFSZ];
|
||||
static char buf[BUFSZ], device[BUFSZ], ascframe[10000];
|
||||
struct sockaddr_can addr;
|
||||
static struct canfd_frame frame;
|
||||
struct can_raw_vcid_options vcid_opts = {
|
||||
.flags = CAN_RAW_XL_VCID_TX_PASS,
|
||||
};
|
||||
static cu_t cu;
|
||||
static struct timeval today_tv, log_tv, last_log_tv, diff_tv;
|
||||
struct timespec sleep_ts;
|
||||
int s; /* CAN_RAW socket */
|
||||
|
|
@ -360,7 +363,13 @@ int main(int argc, char **argv)
|
|||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
|
||||
|
||||
/* try to switch the socket into CAN FD mode */
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfd_on, sizeof(canfd_on));
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfx_on, sizeof(canfx_on));
|
||||
|
||||
/* try to switch the socket into CAN XL mode */
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_FRAMES, &canfx_on, sizeof(canfx_on));
|
||||
|
||||
/* try to enable the CAN XL VCID pass through mode */
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_VCID_OPTS, &vcid_opts, sizeof(vcid_opts));
|
||||
|
||||
if (loopback_disable) {
|
||||
int loopback = 0;
|
||||
|
|
@ -470,26 +479,28 @@ int main(int argc, char **argv)
|
|||
|
||||
} else if (txidx > 0) { /* only send to valid CAN devices */
|
||||
|
||||
txmtu = parse_canframe(ascframe, &frame); /* dual-use frame */
|
||||
txmtu = parse_canframe(ascframe, &cu); /* dual-use frame */
|
||||
if (!txmtu) {
|
||||
fprintf(stderr, "wrong CAN frame format: '%s'!", ascframe);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* CAN XL frames need real frame length for sending */
|
||||
if (txmtu == CANXL_MTU)
|
||||
txmtu = CANXL_HDR_SIZE + cu.xl.len;
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = txidx; /* send via this interface */
|
||||
|
||||
if (sendto(s, &frame, txmtu, 0, (struct sockaddr *)&addr, sizeof(addr)) != txmtu) {
|
||||
if (sendto(s, &cu, txmtu, 0, (struct sockaddr *)&addr, sizeof(addr)) != txmtu) {
|
||||
perror("sendto");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
static char abuf[10000]; /* ASCII buf FIXME - use calculated value */
|
||||
|
||||
printf("%s (%s) ", get_txname(device), device);
|
||||
sprint_long_canframe(abuf, (cu_t *)&frame, CANLIB_VIEW_INDENT_SFF);
|
||||
printf("%s\n", abuf);
|
||||
sprint_long_canframe(ascframe, &cu, CANLIB_VIEW_INDENT_SFF);
|
||||
printf("%s\n", ascframe);
|
||||
}
|
||||
|
||||
if (count && (--count == 0))
|
||||
|
|
|
|||
50
cansend.c
50
cansend.c
|
|
@ -92,9 +92,12 @@ int main(int argc, char **argv)
|
|||
int s; /* can raw socket */
|
||||
int required_mtu;
|
||||
int mtu;
|
||||
int enable_canfd = 1;
|
||||
int enable_canfx = 1;
|
||||
struct sockaddr_can addr;
|
||||
struct canfd_frame frame;
|
||||
struct can_raw_vcid_options vcid_opts = {
|
||||
.flags = CAN_RAW_XL_VCID_TX_PASS,
|
||||
};
|
||||
static cu_t cu;
|
||||
struct ifreq ifr;
|
||||
|
||||
/* check command line options */
|
||||
|
|
@ -104,7 +107,7 @@ int main(int argc, char **argv)
|
|||
}
|
||||
|
||||
/* parse CAN frame */
|
||||
required_mtu = parse_canframe(argv[2], &frame);
|
||||
required_mtu = parse_canframe(argv[2], &cu);
|
||||
if (!required_mtu) {
|
||||
fprintf(stderr, "\nWrong CAN-frame format!\n\n");
|
||||
print_usage(argv[0]);
|
||||
|
|
@ -137,22 +140,39 @@ int main(int argc, char **argv)
|
|||
}
|
||||
mtu = ifr.ifr_mtu;
|
||||
|
||||
if (mtu != CANFD_MTU && mtu != CANXL_MTU) {
|
||||
printf("CAN interface is only Classical CAN capable - sorry.\n");
|
||||
return 1;
|
||||
if (mtu == CANFD_MTU) {
|
||||
/* interface is ok - try to switch the socket into CAN FD mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
|
||||
&enable_canfx, sizeof(enable_canfx))){
|
||||
printf("error when enabling CAN FD support\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* interface is ok - try to switch the socket into CAN FD mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
|
||||
&enable_canfd, sizeof(enable_canfd))){
|
||||
printf("error when enabling CAN FD support\n");
|
||||
return 1;
|
||||
if (mtu >= CANXL_MIN_MTU) {
|
||||
/* interface is ok - try to switch the socket into CAN XL mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_FRAMES,
|
||||
&enable_canfx, sizeof(enable_canfx))){
|
||||
printf("error when enabling CAN XL support\n");
|
||||
return 1;
|
||||
}
|
||||
/* try to enable the CAN XL VCID pass through mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_VCID_OPTS,
|
||||
&vcid_opts, sizeof(vcid_opts))) {
|
||||
printf("error when enabling CAN XL VCID pass through\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ensure discrete CAN FD length values 0..8, 12, 16, 20, 24, 32, 64 */
|
||||
frame.len = can_fd_dlc2len(can_fd_len2dlc(frame.len));
|
||||
}
|
||||
|
||||
/* ensure discrete CAN FD length values 0..8, 12, 16, 20, 24, 32, 64 */
|
||||
if (required_mtu == CANFD_MTU)
|
||||
cu.fd.len = can_fd_dlc2len(can_fd_len2dlc(cu.fd.len));
|
||||
|
||||
/* CAN XL frames need real frame length for sending */
|
||||
if (required_mtu == CANXL_MTU)
|
||||
required_mtu = CANXL_HDR_SIZE + cu.xl.len;
|
||||
|
||||
/*
|
||||
* disable default receive filter on this RAW socket This is
|
||||
* obsolete as we do not read from the socket at all, but for
|
||||
|
|
@ -167,7 +187,7 @@ int main(int argc, char **argv)
|
|||
}
|
||||
|
||||
/* send frame */
|
||||
if (write(s, &frame, required_mtu) != required_mtu) {
|
||||
if (write(s, &cu, required_mtu) != required_mtu) {
|
||||
perror("write");
|
||||
return 1;
|
||||
}
|
||||
|
|
|
|||
110
lib.c
110
lib.c
|
|
@ -55,6 +55,7 @@
|
|||
|
||||
#define CANID_DELIM '#'
|
||||
#define CC_DLC_DELIM '_'
|
||||
#define XL_HDR_DELIM ':'
|
||||
#define DATA_SEPERATOR '.'
|
||||
|
||||
const char hex_asc_upper[] = "0123456789ABCDEF";
|
||||
|
|
@ -153,78 +154,128 @@ int hexstring2data(char *arg, unsigned char *data, int maxdlen)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int parse_canframe(char *cs, struct canfd_frame *cf)
|
||||
int parse_canframe(char *cs, cu_t *cu)
|
||||
{
|
||||
/* documentation see lib.h */
|
||||
|
||||
int i, idx, dlen, len;
|
||||
int maxdlen = CAN_MAX_DLEN;
|
||||
int ret = CAN_MTU;
|
||||
int mtu = CAN_MTU;
|
||||
__u8 *data = cu->fd.data; /* fill CAN CC/FD data by default */
|
||||
canid_t tmp;
|
||||
|
||||
len = strlen(cs);
|
||||
//printf("'%s' len %d\n", cs, len);
|
||||
|
||||
memset(cf, 0, sizeof(*cf)); /* init CAN FD frame, e.g. LEN = 0 */
|
||||
memset(cu, 0, sizeof(*cu)); /* init CAN CC/FD/XL frame, e.g. LEN = 0 */
|
||||
|
||||
if (len < 4)
|
||||
return 0;
|
||||
|
||||
if (cs[3] == CANID_DELIM) { /* 3 digits */
|
||||
if (cs[3] == CANID_DELIM) { /* 3 digits SFF */
|
||||
|
||||
idx = 4;
|
||||
for (i = 0; i < 3; i++) {
|
||||
if ((tmp = asc2nibble(cs[i])) > 0x0F)
|
||||
return 0;
|
||||
cf->can_id |= (tmp << (2 - i) * 4);
|
||||
cu->cc.can_id |= (tmp << (2 - i) * 4);
|
||||
}
|
||||
|
||||
} else if (cs[8] == CANID_DELIM) { /* 8 digits */
|
||||
} else if (cs[5] == CANID_DELIM) { /* 5 digits CAN XL VCID/PRIO*/
|
||||
|
||||
idx = 6;
|
||||
for (i = 0; i < 5; i++) {
|
||||
if ((tmp = asc2nibble(cs[i])) > 0x0F)
|
||||
return 0;
|
||||
cu->xl.prio |= (tmp << (4 - i) * 4);
|
||||
}
|
||||
|
||||
/* the VCID starts at bit position 16 */
|
||||
tmp = (cu->xl.prio << 4) & CANXL_VCID_MASK;
|
||||
cu->xl.prio &= CANXL_PRIO_MASK;
|
||||
cu->xl.prio |= tmp;
|
||||
|
||||
} else if (cs[8] == CANID_DELIM) { /* 8 digits EFF */
|
||||
|
||||
idx = 9;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if ((tmp = asc2nibble(cs[i])) > 0x0F)
|
||||
return 0;
|
||||
cf->can_id |= (tmp << (7 - i) * 4);
|
||||
cu->cc.can_id |= (tmp << (7 - i) * 4);
|
||||
}
|
||||
if (!(cf->can_id & CAN_ERR_FLAG)) /* 8 digits but no errorframe? */
|
||||
cf->can_id |= CAN_EFF_FLAG; /* then it is an extended frame */
|
||||
if (!(cu->cc.can_id & CAN_ERR_FLAG)) /* 8 digits but no errorframe? */
|
||||
cu->cc.can_id |= CAN_EFF_FLAG; /* then it is an extended frame */
|
||||
|
||||
} else
|
||||
return 0;
|
||||
|
||||
if ((cs[idx] == 'R') || (cs[idx] == 'r')) { /* RTR frame */
|
||||
cf->can_id |= CAN_RTR_FLAG;
|
||||
cu->cc.can_id |= CAN_RTR_FLAG;
|
||||
|
||||
/* check for optional DLC value for CAN 2.0B frames */
|
||||
if (cs[++idx] && (tmp = asc2nibble(cs[idx++])) <= CAN_MAX_DLEN) {
|
||||
cf->len = tmp;
|
||||
cu->cc.len = tmp;
|
||||
|
||||
/* check for optional raw DLC value for CAN 2.0B frames */
|
||||
if ((tmp == CAN_MAX_DLEN) && (cs[idx++] == CC_DLC_DELIM)) {
|
||||
tmp = asc2nibble(cs[idx]);
|
||||
if ((tmp > CAN_MAX_DLEN) && (tmp <= CAN_MAX_RAW_DLC)) {
|
||||
struct can_frame *ccf = (struct can_frame *)cf;
|
||||
|
||||
ccf->len8_dlc = tmp;
|
||||
}
|
||||
if ((tmp > CAN_MAX_DLEN) && (tmp <= CAN_MAX_RAW_DLC))
|
||||
cu->cc.len8_dlc = tmp;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
return mtu;
|
||||
}
|
||||
|
||||
if (cs[idx] == CANID_DELIM) { /* CAN FD frame escape char '##' */
|
||||
|
||||
maxdlen = CANFD_MAX_DLEN;
|
||||
ret = CANFD_MTU;
|
||||
mtu = CANFD_MTU;
|
||||
|
||||
/* CAN FD frame <canid>##<flags><data>* */
|
||||
if ((tmp = asc2nibble(cs[idx + 1])) > 0x0F)
|
||||
return 0;
|
||||
|
||||
cf->flags = tmp;
|
||||
cf->flags |= CANFD_FDF; /* dual-use */
|
||||
cu->fd.flags = tmp;
|
||||
cu->fd.flags |= CANFD_FDF; /* dual-use */
|
||||
idx += 2;
|
||||
|
||||
} else if (cs[idx + 14] == CANID_DELIM) { /* CAN XL frame '#80:00:11223344#' */
|
||||
|
||||
maxdlen = CANXL_MAX_DLEN;
|
||||
mtu = CANXL_MTU;
|
||||
data = cu->xl.data; /* fill CAN XL data */
|
||||
|
||||
if ((cs[idx + 2] != XL_HDR_DELIM) || (cs[idx + 5] != XL_HDR_DELIM))
|
||||
return 0;
|
||||
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
cu->xl.flags = (tmp << 4);
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
cu->xl.flags |= tmp;
|
||||
|
||||
/* force CAN XL flag if it was missing in the ASCII string */
|
||||
cu->xl.flags |= CANXL_XLF;
|
||||
|
||||
idx++; /* skip XL_HDR_DELIM */
|
||||
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
cu->xl.sdt = (tmp << 4);
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
cu->xl.sdt |= tmp;
|
||||
|
||||
idx++; /* skip XL_HDR_DELIM */
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
cu->xl.af |= (tmp << (7 - i) * 4);
|
||||
}
|
||||
|
||||
idx++; /* skip CANID_DELIM */
|
||||
}
|
||||
|
||||
for (i = 0, dlen = 0; i < maxdlen; i++) {
|
||||
|
|
@ -236,26 +287,27 @@ int parse_canframe(char *cs, struct canfd_frame *cf)
|
|||
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
cf->data[i] = (tmp << 4);
|
||||
data[i] = (tmp << 4);
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
cf->data[i] |= tmp;
|
||||
data[i] |= tmp;
|
||||
dlen++;
|
||||
}
|
||||
cf->len = dlen;
|
||||
|
||||
if (mtu == CANXL_MTU)
|
||||
cu->xl.len = dlen;
|
||||
else
|
||||
cu->fd.len = dlen;
|
||||
|
||||
/* check for extra DLC when having a Classic CAN with 8 bytes payload */
|
||||
if ((maxdlen == CAN_MAX_DLEN) && (dlen == CAN_MAX_DLEN) && (cs[idx++] == CC_DLC_DELIM)) {
|
||||
unsigned char dlc = asc2nibble(cs[idx]);
|
||||
|
||||
if ((dlc > CAN_MAX_DLEN) && (dlc <= CAN_MAX_RAW_DLC)) {
|
||||
struct can_frame *ccf = (struct can_frame *)cf;
|
||||
|
||||
ccf->len8_dlc = dlc;
|
||||
}
|
||||
if ((dlc > CAN_MAX_DLEN) && (dlc <= CAN_MAX_RAW_DLC))
|
||||
cu->cc.len8_dlc = dlc;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return mtu;
|
||||
}
|
||||
|
||||
int sprint_canframe(char *buf, cu_t *cu, int sep)
|
||||
|
|
|
|||
3
lib.h
3
lib.h
|
|
@ -111,8 +111,7 @@ int hexstring2data(char *arg, unsigned char *data, int maxdlen);
|
|||
*
|
||||
*/
|
||||
|
||||
struct canfd_frame;
|
||||
int parse_canframe(char *cs, struct canfd_frame *cf);
|
||||
int parse_canframe(char *cs, cu_t *cu);
|
||||
/*
|
||||
* Transfers a valid ASCII string describing a CAN frame into struct canfd_frame.
|
||||
*
|
||||
|
|
|
|||
19
log2asc.c
19
log2asc.c
|
|
@ -183,9 +183,9 @@ void canfd_asc(struct canfd_frame *cf, int devno, int mtu, char *extra_info, FIL
|
|||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
static char buf[BUFSZ], device[BUFSZ], ascframe[BUFSZ], extra_info[BUFSZ];
|
||||
static char buf[BUFSZ], device[BUFSZ], ascframe[10000], extra_info[BUFSZ];
|
||||
|
||||
struct canfd_frame cf;
|
||||
static cu_t cu;
|
||||
static struct timeval tv, start_tv;
|
||||
FILE *infile = stdin;
|
||||
FILE *outfile = stdout;
|
||||
|
|
@ -286,20 +286,23 @@ int main(int argc, char **argv)
|
|||
(crlf)?"\r\n":"\n");
|
||||
}
|
||||
|
||||
for (i=0, devno=0; i<maxdev; i++) {
|
||||
for (i = 0, devno = 0; i < maxdev; i++) {
|
||||
if (!strcmp(device, argv[optind+i])) {
|
||||
devno = i+1; /* start with channel '1' */
|
||||
devno = i + 1; /* start with channel '1' */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (devno) { /* only convert for selected CAN devices */
|
||||
mtu = parse_canframe(ascframe, &cf);
|
||||
|
||||
mtu = parse_canframe(ascframe, &cu);
|
||||
|
||||
/* convert only CAN CC and CAN FD frames */
|
||||
if ((mtu != CAN_MTU) && (mtu != CANFD_MTU))
|
||||
return 1;
|
||||
|
||||
/* we don't support error message frames in CAN FD */
|
||||
if ((mtu == CANFD_MTU) && (cf.can_id & CAN_ERR_FLAG))
|
||||
if ((mtu == CANFD_MTU) && (cu.cc.can_id & CAN_ERR_FLAG))
|
||||
continue;
|
||||
|
||||
tv.tv_sec = tv.tv_sec - start_tv.tv_sec;
|
||||
|
|
@ -315,9 +318,9 @@ int main(int argc, char **argv)
|
|||
fprintf(outfile, "%4llu.%06llu ", (unsigned long long)tv.tv_sec, (unsigned long long)tv.tv_usec);
|
||||
|
||||
if ((mtu == CAN_MTU) && (fdfmt == 0))
|
||||
can_asc(&cf, devno, nortrdlc, extra_info, outfile);
|
||||
can_asc(&cu.fd, devno, nortrdlc, extra_info, outfile);
|
||||
else
|
||||
canfd_asc(&cf, devno, mtu, extra_info, outfile);
|
||||
canfd_asc(&cu.fd, devno, mtu, extra_info, outfile);
|
||||
|
||||
if (crlf)
|
||||
fprintf(outfile, "\r");
|
||||
|
|
|
|||
14
log2long.c
14
log2long.c
|
|
@ -55,27 +55,27 @@
|
|||
int main(void)
|
||||
{
|
||||
char buf[BUFSZ], timestamp[BUFSZ], device[BUFSZ], ascframe[BUFSZ];
|
||||
struct canfd_frame cf;
|
||||
static cu_t cu;
|
||||
int mtu;
|
||||
|
||||
while (fgets(buf, BUFSZ-1, stdin)) {
|
||||
if (sscanf(buf, "%s %s %s", timestamp, device, ascframe) != 3)
|
||||
return 1;
|
||||
|
||||
mtu = parse_canframe(ascframe, &cf);
|
||||
mtu = parse_canframe(ascframe, &cu);
|
||||
|
||||
/* mark dual-use struct canfd_frame */
|
||||
/* mark dual-use struct canfd_frame - no CAN_XL support */
|
||||
if (mtu == CAN_MTU)
|
||||
cf.flags = 0;
|
||||
cu.fd.flags = 0;
|
||||
else if (mtu == CANFD_MTU)
|
||||
cf.flags |= CANFD_FDF;
|
||||
cu.fd.flags |= CANFD_FDF;
|
||||
else {
|
||||
fprintf(stderr, "read: incomplete CAN frame\n");
|
||||
fprintf(stderr, "read: no valid CAN CC/FD frame\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* with ASCII output */
|
||||
sprint_long_canframe(ascframe, (cu_t *)&cf,
|
||||
sprint_long_canframe(ascframe, &cu,
|
||||
(CANLIB_VIEW_INDENT_SFF | CANLIB_VIEW_ASCII));
|
||||
|
||||
printf("%s %s %s\n", timestamp, device, ascframe);
|
||||
|
|
|
|||
Loading…
Reference in New Issue