feat(usbip): Add kcp support
This commit is contained in:
parent
45c5ec307f
commit
c7fa99c7ca
|
@ -232,14 +232,14 @@ static void handleGetDescriptor(usbip_stage2_header *header)
|
||||||
os_printf("Sending only first part of CONFIG\r\n");
|
os_printf("Sending only first part of CONFIG\r\n");
|
||||||
|
|
||||||
send_stage2_submit(header, 0, header->u.cmd_submit.data_length);
|
send_stage2_submit(header, 0, header->u.cmd_submit.data_length);
|
||||||
send(kSock, kUSBd0ConfigDescriptor, sizeof(kUSBd0ConfigDescriptor), 0);
|
usbip_network_send(kSock, kUSBd0ConfigDescriptor, sizeof(kUSBd0ConfigDescriptor), 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
os_printf("Sending ALL CONFIG\r\n");
|
os_printf("Sending ALL CONFIG\r\n");
|
||||||
send_stage2_submit(header, 0, sizeof(kUSBd0ConfigDescriptor) + sizeof(kUSBd0InterfaceDescriptor));
|
send_stage2_submit(header, 0, sizeof(kUSBd0ConfigDescriptor) + sizeof(kUSBd0InterfaceDescriptor));
|
||||||
send(kSock, kUSBd0ConfigDescriptor, sizeof(kUSBd0ConfigDescriptor), 0);
|
usbip_network_send(kSock, kUSBd0ConfigDescriptor, sizeof(kUSBd0ConfigDescriptor), 0);
|
||||||
send(kSock, kUSBd0InterfaceDescriptor, sizeof(kUSBd0InterfaceDescriptor), 0);
|
usbip_network_send(kSock, kUSBd0InterfaceDescriptor, sizeof(kUSBd0InterfaceDescriptor), 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
set(COMPONENT_ADD_INCLUDEDIRS ".")
|
||||||
|
set(COMPONENT_SRCS "./ikcp.c ./ikcp_util.c")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
register_component()
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,416 @@
|
||||||
|
//=====================================================================
|
||||||
|
//
|
||||||
|
// KCP - A Better ARQ Protocol Implementation
|
||||||
|
// skywind3000 (at) gmail.com, 2010-2011
|
||||||
|
//
|
||||||
|
// Features:
|
||||||
|
// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp.
|
||||||
|
// + Maximum RTT reduce three times vs tcp.
|
||||||
|
// + Lightweight, distributed as a single source file.
|
||||||
|
//
|
||||||
|
//=====================================================================
|
||||||
|
#ifndef __IKCP_H__
|
||||||
|
#define __IKCP_H__
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================================================
|
||||||
|
// 32BIT INTEGER DEFINITION
|
||||||
|
//=====================================================================
|
||||||
|
#ifndef __INTEGER_32_BITS__
|
||||||
|
#define __INTEGER_32_BITS__
|
||||||
|
#if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \
|
||||||
|
defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \
|
||||||
|
defined(_M_AMD64)
|
||||||
|
typedef unsigned int ISTDUINT32;
|
||||||
|
typedef int ISTDINT32;
|
||||||
|
#elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \
|
||||||
|
defined(__i386) || defined(_M_X86)
|
||||||
|
typedef unsigned long ISTDUINT32;
|
||||||
|
typedef long ISTDINT32;
|
||||||
|
#elif defined(__MACOS__)
|
||||||
|
typedef UInt32 ISTDUINT32;
|
||||||
|
typedef SInt32 ISTDINT32;
|
||||||
|
#elif defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#include <sys/types.h>
|
||||||
|
typedef u_int32_t ISTDUINT32;
|
||||||
|
typedef int32_t ISTDINT32;
|
||||||
|
#elif defined(__BEOS__)
|
||||||
|
#include <sys/inttypes.h>
|
||||||
|
typedef u_int32_t ISTDUINT32;
|
||||||
|
typedef int32_t ISTDINT32;
|
||||||
|
#elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__))
|
||||||
|
typedef unsigned __int32 ISTDUINT32;
|
||||||
|
typedef __int32 ISTDINT32;
|
||||||
|
#elif defined(__GNUC__)
|
||||||
|
#include <stdint.h>
|
||||||
|
typedef uint32_t ISTDUINT32;
|
||||||
|
typedef int32_t ISTDINT32;
|
||||||
|
#else
|
||||||
|
typedef unsigned long ISTDUINT32;
|
||||||
|
typedef long ISTDINT32;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================================================
|
||||||
|
// Integer Definition
|
||||||
|
//=====================================================================
|
||||||
|
#ifndef __IINT8_DEFINED
|
||||||
|
#define __IINT8_DEFINED
|
||||||
|
typedef char IINT8;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __IUINT8_DEFINED
|
||||||
|
#define __IUINT8_DEFINED
|
||||||
|
typedef unsigned char IUINT8;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __IUINT16_DEFINED
|
||||||
|
#define __IUINT16_DEFINED
|
||||||
|
typedef unsigned short IUINT16;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __IINT16_DEFINED
|
||||||
|
#define __IINT16_DEFINED
|
||||||
|
typedef short IINT16;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __IINT32_DEFINED
|
||||||
|
#define __IINT32_DEFINED
|
||||||
|
typedef ISTDINT32 IINT32;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __IUINT32_DEFINED
|
||||||
|
#define __IUINT32_DEFINED
|
||||||
|
typedef ISTDUINT32 IUINT32;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __IINT64_DEFINED
|
||||||
|
#define __IINT64_DEFINED
|
||||||
|
#if defined(_MSC_VER) || defined(__BORLANDC__)
|
||||||
|
typedef __int64 IINT64;
|
||||||
|
#else
|
||||||
|
typedef long long IINT64;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __IUINT64_DEFINED
|
||||||
|
#define __IUINT64_DEFINED
|
||||||
|
#if defined(_MSC_VER) || defined(__BORLANDC__)
|
||||||
|
typedef unsigned __int64 IUINT64;
|
||||||
|
#else
|
||||||
|
typedef unsigned long long IUINT64;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef INLINE
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
|
||||||
|
#if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))
|
||||||
|
#define INLINE __inline__ __attribute__((always_inline))
|
||||||
|
#else
|
||||||
|
#define INLINE __inline__
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__))
|
||||||
|
#define INLINE __inline
|
||||||
|
#else
|
||||||
|
#define INLINE
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (!defined(__cplusplus)) && (!defined(inline))
|
||||||
|
#define inline INLINE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================================================
|
||||||
|
// QUEUE DEFINITION
|
||||||
|
//=====================================================================
|
||||||
|
#ifndef __IQUEUE_DEF__
|
||||||
|
#define __IQUEUE_DEF__
|
||||||
|
|
||||||
|
struct IQUEUEHEAD {
|
||||||
|
struct IQUEUEHEAD *next, *prev;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct IQUEUEHEAD iqueue_head;
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// queue init
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
#define IQUEUE_HEAD_INIT(name) { &(name), &(name) }
|
||||||
|
#define IQUEUE_HEAD(name) \
|
||||||
|
struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name)
|
||||||
|
|
||||||
|
#define IQUEUE_INIT(ptr) ( \
|
||||||
|
(ptr)->next = (ptr), (ptr)->prev = (ptr))
|
||||||
|
|
||||||
|
#define IOFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
|
||||||
|
|
||||||
|
#define ICONTAINEROF(ptr, type, member) ( \
|
||||||
|
(type*)( ((char*)((type*)ptr)) - IOFFSETOF(type, member)) )
|
||||||
|
|
||||||
|
#define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member)
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// queue operation
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
#define IQUEUE_ADD(node, head) ( \
|
||||||
|
(node)->prev = (head), (node)->next = (head)->next, \
|
||||||
|
(head)->next->prev = (node), (head)->next = (node))
|
||||||
|
|
||||||
|
#define IQUEUE_ADD_TAIL(node, head) ( \
|
||||||
|
(node)->prev = (head)->prev, (node)->next = (head), \
|
||||||
|
(head)->prev->next = (node), (head)->prev = (node))
|
||||||
|
|
||||||
|
#define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n))
|
||||||
|
|
||||||
|
#define IQUEUE_DEL(entry) (\
|
||||||
|
(entry)->next->prev = (entry)->prev, \
|
||||||
|
(entry)->prev->next = (entry)->next, \
|
||||||
|
(entry)->next = 0, (entry)->prev = 0)
|
||||||
|
|
||||||
|
#define IQUEUE_DEL_INIT(entry) do { \
|
||||||
|
IQUEUE_DEL(entry); IQUEUE_INIT(entry); } while (0)
|
||||||
|
|
||||||
|
#define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next)
|
||||||
|
|
||||||
|
#define iqueue_init IQUEUE_INIT
|
||||||
|
#define iqueue_entry IQUEUE_ENTRY
|
||||||
|
#define iqueue_add IQUEUE_ADD
|
||||||
|
#define iqueue_add_tail IQUEUE_ADD_TAIL
|
||||||
|
#define iqueue_del IQUEUE_DEL
|
||||||
|
#define iqueue_del_init IQUEUE_DEL_INIT
|
||||||
|
#define iqueue_is_empty IQUEUE_IS_EMPTY
|
||||||
|
|
||||||
|
#define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \
|
||||||
|
for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \
|
||||||
|
&((iterator)->MEMBER) != (head); \
|
||||||
|
(iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER))
|
||||||
|
|
||||||
|
#define iqueue_foreach(iterator, head, TYPE, MEMBER) \
|
||||||
|
IQUEUE_FOREACH(iterator, head, TYPE, MEMBER)
|
||||||
|
|
||||||
|
#define iqueue_foreach_entry(pos, head) \
|
||||||
|
for( (pos) = (head)->next; (pos) != (head) ; (pos) = (pos)->next )
|
||||||
|
|
||||||
|
|
||||||
|
#define __iqueue_splice(list, head) do { \
|
||||||
|
iqueue_head *first = (list)->next, *last = (list)->prev; \
|
||||||
|
iqueue_head *at = (head)->next; \
|
||||||
|
(first)->prev = (head), (head)->next = (first); \
|
||||||
|
(last)->next = (at), (at)->prev = (last); } while (0)
|
||||||
|
|
||||||
|
#define iqueue_splice(list, head) do { \
|
||||||
|
if (!iqueue_is_empty(list)) __iqueue_splice(list, head); } while (0)
|
||||||
|
|
||||||
|
#define iqueue_splice_init(list, head) do { \
|
||||||
|
iqueue_splice(list, head); iqueue_init(list); } while (0)
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(disable:4311)
|
||||||
|
#pragma warning(disable:4312)
|
||||||
|
#pragma warning(disable:4996)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// BYTE ORDER & ALIGNMENT
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
#ifndef IWORDS_BIG_ENDIAN
|
||||||
|
#ifdef _BIG_ENDIAN_
|
||||||
|
#if _BIG_ENDIAN_
|
||||||
|
#define IWORDS_BIG_ENDIAN 1
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifndef IWORDS_BIG_ENDIAN
|
||||||
|
#if defined(__hppa__) || \
|
||||||
|
defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \
|
||||||
|
(defined(__MIPS__) && defined(__MIPSEB__)) || \
|
||||||
|
defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \
|
||||||
|
defined(__sparc__) || defined(__powerpc__) || \
|
||||||
|
defined(__mc68000__) || defined(__s390x__) || defined(__s390__)
|
||||||
|
#define IWORDS_BIG_ENDIAN 1
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifndef IWORDS_BIG_ENDIAN
|
||||||
|
#define IWORDS_BIG_ENDIAN 0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef IWORDS_MUST_ALIGN
|
||||||
|
#if defined(__i386__) || defined(__i386) || defined(_i386_)
|
||||||
|
#define IWORDS_MUST_ALIGN 0
|
||||||
|
#elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__)
|
||||||
|
#define IWORDS_MUST_ALIGN 0
|
||||||
|
#elif defined(__amd64) || defined(__amd64__)
|
||||||
|
#define IWORDS_MUST_ALIGN 0
|
||||||
|
#else
|
||||||
|
#define IWORDS_MUST_ALIGN 1
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================================================
|
||||||
|
// SEGMENT
|
||||||
|
//=====================================================================
|
||||||
|
struct IKCPSEG
|
||||||
|
{
|
||||||
|
struct IQUEUEHEAD node;
|
||||||
|
IUINT32 conv;
|
||||||
|
IUINT32 cmd;
|
||||||
|
IUINT32 frg;
|
||||||
|
IUINT32 wnd;
|
||||||
|
IUINT32 ts;
|
||||||
|
IUINT32 sn;
|
||||||
|
IUINT32 una;
|
||||||
|
IUINT32 len;
|
||||||
|
IUINT32 resendts;
|
||||||
|
IUINT32 rto;
|
||||||
|
IUINT32 fastack;
|
||||||
|
IUINT32 xmit;
|
||||||
|
char data[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// IKCPCB
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
struct IKCPCB
|
||||||
|
{
|
||||||
|
IUINT32 conv, mtu, mss, state;
|
||||||
|
IUINT32 snd_una, snd_nxt, rcv_nxt;
|
||||||
|
IUINT32 ts_recent, ts_lastack, ssthresh;
|
||||||
|
IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto;
|
||||||
|
IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe;
|
||||||
|
IUINT32 current, interval, ts_flush, xmit;
|
||||||
|
IUINT32 nrcv_buf, nsnd_buf;
|
||||||
|
IUINT32 nrcv_que, nsnd_que;
|
||||||
|
IUINT32 nodelay, updated;
|
||||||
|
IUINT32 ts_probe, probe_wait;
|
||||||
|
IUINT32 dead_link, incr;
|
||||||
|
struct IQUEUEHEAD snd_queue;
|
||||||
|
struct IQUEUEHEAD rcv_queue;
|
||||||
|
struct IQUEUEHEAD snd_buf;
|
||||||
|
struct IQUEUEHEAD rcv_buf;
|
||||||
|
IUINT32 *acklist;
|
||||||
|
IUINT32 ackcount;
|
||||||
|
IUINT32 ackblock;
|
||||||
|
void *user;
|
||||||
|
char *buffer;
|
||||||
|
int fastresend;
|
||||||
|
int fastlimit;
|
||||||
|
int nocwnd, stream;
|
||||||
|
int logmask;
|
||||||
|
int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user);
|
||||||
|
void (*writelog)(const char *log, struct IKCPCB *kcp, void *user);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct IKCPCB ikcpcb;
|
||||||
|
|
||||||
|
#define IKCP_LOG_OUTPUT 1
|
||||||
|
#define IKCP_LOG_INPUT 2
|
||||||
|
#define IKCP_LOG_SEND 4
|
||||||
|
#define IKCP_LOG_RECV 8
|
||||||
|
#define IKCP_LOG_IN_DATA 16
|
||||||
|
#define IKCP_LOG_IN_ACK 32
|
||||||
|
#define IKCP_LOG_IN_PROBE 64
|
||||||
|
#define IKCP_LOG_IN_WINS 128
|
||||||
|
#define IKCP_LOG_OUT_DATA 256
|
||||||
|
#define IKCP_LOG_OUT_ACK 512
|
||||||
|
#define IKCP_LOG_OUT_PROBE 1024
|
||||||
|
#define IKCP_LOG_OUT_WINS 2048
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// interface
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
|
// create a new kcp control object, 'conv' must equal in two endpoint
|
||||||
|
// from the same connection. 'user' will be passed to the output callback
|
||||||
|
// output callback can be setup like this: 'kcp->output = my_udp_output'
|
||||||
|
ikcpcb* ikcp_create(IUINT32 conv, void *user);
|
||||||
|
|
||||||
|
// release kcp control object
|
||||||
|
void ikcp_release(ikcpcb *kcp);
|
||||||
|
|
||||||
|
// set output callback, which will be invoked by kcp
|
||||||
|
void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len,
|
||||||
|
ikcpcb *kcp, void *user));
|
||||||
|
|
||||||
|
// user/upper level recv: returns size, returns below zero for EAGAIN
|
||||||
|
int ikcp_recv(ikcpcb *kcp, char *buffer, int len);
|
||||||
|
|
||||||
|
// user/upper level send, returns below zero for error
|
||||||
|
int ikcp_send(ikcpcb *kcp, const char *buffer, int len);
|
||||||
|
|
||||||
|
// update state (call it repeatedly, every 10ms-100ms), or you can ask
|
||||||
|
// ikcp_check when to call it again (without ikcp_input/_send calling).
|
||||||
|
// 'current' - current timestamp in millisec.
|
||||||
|
void ikcp_update(ikcpcb *kcp, IUINT32 current);
|
||||||
|
|
||||||
|
// Determine when should you invoke ikcp_update:
|
||||||
|
// returns when you should invoke ikcp_update in millisec, if there
|
||||||
|
// is no ikcp_input/_send calling. you can call ikcp_update in that
|
||||||
|
// time, instead of call update repeatly.
|
||||||
|
// Important to reduce unnacessary ikcp_update invoking. use it to
|
||||||
|
// schedule ikcp_update (eg. implementing an epoll-like mechanism,
|
||||||
|
// or optimize ikcp_update when handling massive kcp connections)
|
||||||
|
IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current);
|
||||||
|
|
||||||
|
// when you received a low level packet (eg. UDP packet), call it
|
||||||
|
int ikcp_input(ikcpcb *kcp, const char *data, long size);
|
||||||
|
|
||||||
|
// flush pending data
|
||||||
|
void ikcp_flush(ikcpcb *kcp);
|
||||||
|
|
||||||
|
// check the size of next message in the recv queue
|
||||||
|
int ikcp_peeksize(const ikcpcb *kcp);
|
||||||
|
|
||||||
|
// change MTU size, default is 1400
|
||||||
|
int ikcp_setmtu(ikcpcb *kcp, int mtu);
|
||||||
|
|
||||||
|
// set maximum window size: sndwnd=32, rcvwnd=32 by default
|
||||||
|
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
|
||||||
|
|
||||||
|
// get how many packet is waiting to be sent
|
||||||
|
int ikcp_waitsnd(const ikcpcb *kcp);
|
||||||
|
|
||||||
|
// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1)
|
||||||
|
// nodelay: 0:disable(default), 1:enable
|
||||||
|
// interval: internal update timer interval in millisec, default is 100ms
|
||||||
|
// resend: 0:disable fast resend(default), 1:enable fast resend
|
||||||
|
// nc: 0:normal congestion control(default), 1:disable congestion control
|
||||||
|
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc);
|
||||||
|
|
||||||
|
|
||||||
|
void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...);
|
||||||
|
|
||||||
|
// setup allocator
|
||||||
|
void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*));
|
||||||
|
|
||||||
|
// read conv
|
||||||
|
IUINT32 ikcp_getconv(const void *ptr);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
#include "components/kcp/ikcp_util.h"
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline)) void itimeofday(long* sec, long* usec)
|
||||||
|
{
|
||||||
|
struct timeval time;
|
||||||
|
gettimeofday(&time, NULL);
|
||||||
|
if (sec) *sec = time.tv_sec;
|
||||||
|
if (usec) *usec = time.tv_usec;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get clock in millisecond 64
|
||||||
|
IINT64 iclock64(void)
|
||||||
|
{
|
||||||
|
long s, u;
|
||||||
|
IINT64 value;
|
||||||
|
itimeofday(&s, &u);
|
||||||
|
value = ((IINT64)s) * 1000 + (u / 1000);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
IUINT32 iclock()
|
||||||
|
{
|
||||||
|
return (IUINT32)(iclock64() & 0xfffffffful);
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
#ifndef __IKCP_UTIL_H__
|
||||||
|
#define __IKCP_UTIL_H__
|
||||||
|
|
||||||
|
#include "components/kcp/ikcp.h"
|
||||||
|
|
||||||
|
IINT64 iclock64(void);
|
||||||
|
IUINT32 iclock();
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,4 +1,4 @@
|
||||||
set(COMPONENT_ADD_INCLUDEDIRS "${PROJECT_PATH}")
|
set(COMPONENT_ADD_INCLUDEDIRS "${PROJECT_PATH}")
|
||||||
set(COMPONENT_SRCS "main.c timer.c tcp_server.c usbip_server.c DAP_handle.c")
|
set(COMPONENT_SRCS "main.c timer.c tcp_server.c usbip_server.c DAP_handle.c kcp_server.c")
|
||||||
|
|
||||||
register_component()
|
register_component()
|
||||||
|
|
|
@ -261,7 +261,7 @@ int fast_reply(uint8_t *buf, uint32_t length)
|
||||||
buf_header->u.ret_submit.status = 0;
|
buf_header->u.ret_submit.status = 0;
|
||||||
buf_header->u.ret_submit.data_length = 0;
|
buf_header->u.ret_submit.data_length = 0;
|
||||||
buf_header->u.ret_submit.error_count = 0;
|
buf_header->u.ret_submit.error_count = 0;
|
||||||
send(kSock, buf, 48, 0);
|
usbip_network_send(kSock, buf, 48, 0);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
/**
|
||||||
|
* @file kcp_server.c
|
||||||
|
* @author windows
|
||||||
|
* @brief usbip KCP port
|
||||||
|
* @version 0.1
|
||||||
|
* @date 2021-10-08
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "main/kcp_server.h"
|
||||||
|
#include "main/usbip_server.h"
|
||||||
|
#include "main/wifi_configuration.h"
|
||||||
|
|
||||||
|
#include "components/kcp/ikcp.h"
|
||||||
|
#include "components/kcp/ikcp_util.h"
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/event_groups.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "esp_wifi.h"
|
||||||
|
#include "esp_event_loop.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "lwip/err.h"
|
||||||
|
#include "lwip/sockets.h"
|
||||||
|
#include "lwip/sys.h"
|
||||||
|
#include <lwip/netdb.h>
|
||||||
|
|
||||||
|
extern TaskHandle_t kDAPTaskHandle;
|
||||||
|
extern int kRestartDAPHandle;
|
||||||
|
extern int kSock;
|
||||||
|
extern uint8_t kState;
|
||||||
|
|
||||||
|
static struct sockaddr_in client_addr = { 0 };
|
||||||
|
static char kcp_buffer[MTU_SIZE];
|
||||||
|
static ikcpcb *kcp1 = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
static void set_non_blocking(int sockfd) {
|
||||||
|
int flag = fcntl(sockfd, F_GETFL, 0);
|
||||||
|
if (flag < 0) {
|
||||||
|
os_printf("fcntl F_GETFL fail\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fcntl(sockfd, F_SETFL, flag | O_NONBLOCK) < 0) {
|
||||||
|
os_printf("fcntl F_SETFL fail\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int udp_output(const char *buf, int len, ikcpcb *kcp, void *user)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
int time = 10;
|
||||||
|
// Unfortunately, esp8266 often fails due to lack of memory
|
||||||
|
while (ret < 0) {
|
||||||
|
ret = sendto(kSock, buf, len, 0, (struct sockaddr *)&client_addr, sizeof(client_addr));
|
||||||
|
if (ret < 0) {
|
||||||
|
// os_printf("fail to send, retry\r\n");
|
||||||
|
int errcode = errno;
|
||||||
|
if (errno != ENOMEM)
|
||||||
|
printf("unknown errcode %d\r\n", errcode);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(time));
|
||||||
|
time += 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int kcp_network_send(const char *buffer, int len) {
|
||||||
|
return ikcp_send(kcp1, buffer, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void kcp_server_task()
|
||||||
|
{
|
||||||
|
TickType_t xLastWakeTime;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
kSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||||
|
if (kSock < 0) {
|
||||||
|
os_printf("Unable to create socket: errno %d", errno);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
os_printf("Socket created\r\n");
|
||||||
|
|
||||||
|
set_non_blocking(kSock);
|
||||||
|
|
||||||
|
|
||||||
|
struct sockaddr_in server_addr;
|
||||||
|
memset(&server_addr, 0, sizeof(server_addr));
|
||||||
|
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
server_addr.sin_family = AF_INET;
|
||||||
|
server_addr.sin_port = htons(PORT);
|
||||||
|
|
||||||
|
socklen_t socklen = sizeof(client_addr);
|
||||||
|
|
||||||
|
|
||||||
|
int err = bind(kSock, (struct sockaddr *)&server_addr, sizeof(server_addr));
|
||||||
|
if (err < 0) {
|
||||||
|
os_printf("Socket unable to bind: errno %d\r\n", errno);
|
||||||
|
}
|
||||||
|
os_printf("Socket binded\r\n");
|
||||||
|
|
||||||
|
// KCP init
|
||||||
|
if (kcp1 == NULL) {
|
||||||
|
kcp1 = ikcp_create(1, (void *)0);
|
||||||
|
}
|
||||||
|
if (kcp1 == NULL) {
|
||||||
|
os_printf("can not create kcp control block\r\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
kcp1->output = udp_output;
|
||||||
|
|
||||||
|
ikcp_wndsize(kcp1, 128, 128);
|
||||||
|
|
||||||
|
ikcp_nodelay(kcp1, 2, 10, 2, 1); // set fast mode
|
||||||
|
kcp1->rx_minrto = 10;
|
||||||
|
kcp1->fastresend = 1;
|
||||||
|
|
||||||
|
ikcp_setmtu(kcp1, 1500);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int ret = -1;
|
||||||
|
// KCP task main loop
|
||||||
|
while (1) {
|
||||||
|
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1)); // we wanna sleep absolute time
|
||||||
|
ikcp_update(kcp1, iclock());
|
||||||
|
|
||||||
|
// recv data from udp
|
||||||
|
while (1) {
|
||||||
|
ret = recvfrom(kSock, kcp_buffer, MTU_SIZE, 0, (struct sockaddr *)&client_addr, &socklen);
|
||||||
|
if (ret < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ikcp_input(kcp1, kcp_buffer, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// recv data from kdp
|
||||||
|
while (1) {
|
||||||
|
ret = ikcp_recv(kcp1, kcp_buffer, MTU_SIZE);
|
||||||
|
if (ret < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// recv user data, then handle it
|
||||||
|
switch (kState)
|
||||||
|
{
|
||||||
|
case EMULATING:
|
||||||
|
emulate((uint8_t *)kcp_buffer, ret);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACCEPTING:
|
||||||
|
kState = ATTACHING;
|
||||||
|
case ATTACHING:
|
||||||
|
attach((uint8_t *)kcp_buffer, ret);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
os_printf("unkonw kstate!\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (kcp1) {
|
||||||
|
ikcp_release(kcp1);
|
||||||
|
}
|
||||||
|
if (kSock != -1) {
|
||||||
|
os_printf("Shutting down socket and restarting...\r\n");
|
||||||
|
shutdown(kSock, 0);
|
||||||
|
close(kSock);
|
||||||
|
|
||||||
|
if (kState == EMULATING)
|
||||||
|
kState = ACCEPTING;
|
||||||
|
// Restart DAP Handle
|
||||||
|
kRestartDAPHandle = 1;
|
||||||
|
if (kDAPTaskHandle)
|
||||||
|
xTaskNotifyGive(kDAPTaskHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
#ifndef __KCP_SERVER_H__
|
||||||
|
#define __KCP_SERVER_H__
|
||||||
|
|
||||||
|
void kcp_server_task();
|
||||||
|
int kcp_network_send(const char *buffer, int len);
|
||||||
|
|
||||||
|
#endif
|
12
main/main.c
12
main/main.c
|
@ -11,6 +11,7 @@
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
|
|
||||||
#include "main/tcp_server.h"
|
#include "main/tcp_server.h"
|
||||||
|
#include "main/kcp_server.h"
|
||||||
#include "main/timer.h"
|
#include "main/timer.h"
|
||||||
#include "main/wifi_configuration.h"
|
#include "main/wifi_configuration.h"
|
||||||
|
|
||||||
|
@ -170,12 +171,17 @@ void app_main()
|
||||||
DAP_Setup();
|
DAP_Setup();
|
||||||
timer_init();
|
timer_init();
|
||||||
|
|
||||||
|
#if (USE_KCP == 1)
|
||||||
|
xTaskCreate(kcp_server_task, "kcp_server", 4096, NULL, 14, NULL);
|
||||||
|
#else
|
||||||
xTaskCreate(tcp_server_task, "tcp_server", 4096, NULL, 14, NULL);
|
xTaskCreate(tcp_server_task, "tcp_server", 4096, NULL, 14, NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
xTaskCreate(DAP_Thread, "DAP_Task", 2048, NULL, 10, &kDAPTaskHandle);
|
xTaskCreate(DAP_Thread, "DAP_Task", 2048, NULL, 10, &kDAPTaskHandle);
|
||||||
|
|
||||||
// SWO Trace Task
|
// SWO Trace Task
|
||||||
#if (SWO_FUNCTION_ENABLE == 1)
|
#if (SWO_FUNCTION_ENABLE == 1)
|
||||||
xTaskCreate(SWO_Thread, "SWO_Task", 512, NULL, 10, NULL);
|
xTaskCreate(SWO_Thread, "SWO_Task", 512, NULL, 10, NULL);
|
||||||
#endif
|
#endif
|
||||||
// It seems that the task is overly stressful...
|
// It seems that the task is overly stressful...
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "main/usbip_server.h"
|
#include "main/usbip_server.h"
|
||||||
|
#include "main/kcp_server.h"
|
||||||
#include "main/DAP_handle.h"
|
#include "main/DAP_handle.h"
|
||||||
|
#include "main/wifi_configuration.h"
|
||||||
|
|
||||||
#include "components/USBIP/usb_handle.h"
|
#include "components/USBIP/usb_handle.h"
|
||||||
#include "components/USBIP/usb_descriptor.h"
|
#include "components/USBIP/usb_descriptor.h"
|
||||||
|
@ -32,7 +34,13 @@ static void handle_unlink(usbip_stage2_header *header);
|
||||||
// unlink helper function
|
// unlink helper function
|
||||||
static void send_stage2_unlink(usbip_stage2_header *req_header);
|
static void send_stage2_unlink(usbip_stage2_header *req_header);
|
||||||
|
|
||||||
|
int usbip_network_send(int s, const void *dataptr, size_t size, int flags) {
|
||||||
|
#if (USE_KCP == 1)
|
||||||
|
return kcp_network_send(dataptr, size);
|
||||||
|
#else
|
||||||
|
return send(s, dataptr, size, flags);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
int attach(uint8_t *buffer, uint32_t length)
|
int attach(uint8_t *buffer, uint32_t length)
|
||||||
{
|
{
|
||||||
|
@ -105,7 +113,7 @@ static void send_stage1_header(uint16_t command, uint32_t status)
|
||||||
header.command = htons(command);
|
header.command = htons(command);
|
||||||
header.status = htonl(status);
|
header.status = htonl(status);
|
||||||
|
|
||||||
send(kSock, (uint8_t *)&header, sizeof(usbip_stage1_header), 0);
|
usbip_network_send(kSock, (uint8_t *)&header, sizeof(usbip_stage1_header), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_device_list()
|
static void send_device_list()
|
||||||
|
@ -119,7 +127,7 @@ static void send_device_list()
|
||||||
// we have only 1 device, so:
|
// we have only 1 device, so:
|
||||||
response_devlist.list_size = htonl(1);
|
response_devlist.list_size = htonl(1);
|
||||||
|
|
||||||
send(kSock, (uint8_t *)&response_devlist, sizeof(usbip_stage1_response_devlist), 0);
|
usbip_network_send(kSock, (uint8_t *)&response_devlist, sizeof(usbip_stage1_response_devlist), 0);
|
||||||
|
|
||||||
// may be foreach:
|
// may be foreach:
|
||||||
|
|
||||||
|
@ -155,7 +163,7 @@ static void send_device_info()
|
||||||
device.bNumConfigurations = 1;
|
device.bNumConfigurations = 1;
|
||||||
device.bNumInterfaces = 1;
|
device.bNumInterfaces = 1;
|
||||||
|
|
||||||
send(kSock, (uint8_t *)&device, sizeof(usbip_stage1_usb_device), 0);
|
usbip_network_send(kSock, (uint8_t *)&device, sizeof(usbip_stage1_usb_device), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_interface_info()
|
static void send_interface_info()
|
||||||
|
@ -167,7 +175,7 @@ static void send_interface_info()
|
||||||
interface.bInterfaceProtocol = USBD_CUSTOM_CLASS0_IF0_PROTOCOL;
|
interface.bInterfaceProtocol = USBD_CUSTOM_CLASS0_IF0_PROTOCOL;
|
||||||
interface.padding = 0; // shall be set to zero
|
interface.padding = 0; // shall be set to zero
|
||||||
|
|
||||||
send(kSock, (uint8_t *)&interface, sizeof(usbip_stage1_usb_interface), 0);
|
usbip_network_send(kSock, (uint8_t *)&interface, sizeof(usbip_stage1_usb_interface), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -341,7 +349,7 @@ void send_stage2_submit(usbip_stage2_header *req_header, int32_t status, int32_t
|
||||||
req_header->u.ret_submit.data_length = data_length;
|
req_header->u.ret_submit.data_length = data_length;
|
||||||
// already unpacked
|
// already unpacked
|
||||||
pack(req_header, sizeof(usbip_stage2_header));
|
pack(req_header, sizeof(usbip_stage2_header));
|
||||||
send(kSock, req_header, sizeof(usbip_stage2_header), 0);
|
usbip_network_send(kSock, req_header, sizeof(usbip_stage2_header), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_stage2_submit_data(usbip_stage2_header *req_header, int32_t status, const void *const data, int32_t data_length)
|
void send_stage2_submit_data(usbip_stage2_header *req_header, int32_t status, const void *const data, int32_t data_length)
|
||||||
|
@ -351,7 +359,7 @@ void send_stage2_submit_data(usbip_stage2_header *req_header, int32_t status, co
|
||||||
|
|
||||||
if (data_length)
|
if (data_length)
|
||||||
{
|
{
|
||||||
send(kSock, data, data_length, 0);
|
usbip_network_send(kSock, data, data_length, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,7 +376,7 @@ void send_stage2_submit_data_fast(usbip_stage2_header *req_header, const void *c
|
||||||
|
|
||||||
// payload
|
// payload
|
||||||
memcpy(&send_buf[sizeof(usbip_stage2_header)], data, data_length);
|
memcpy(&send_buf[sizeof(usbip_stage2_header)], data, data_length);
|
||||||
send(kSock, send_buf, sizeof(usbip_stage2_header) + data_length, 0);
|
usbip_network_send(kSock, send_buf, sizeof(usbip_stage2_header) + data_length, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -395,5 +403,5 @@ static void send_stage2_unlink(usbip_stage2_header *req_header)
|
||||||
|
|
||||||
pack(req_header, sizeof(usbip_stage2_header));
|
pack(req_header, sizeof(usbip_stage2_header));
|
||||||
|
|
||||||
send(kSock, req_header, sizeof(usbip_stage2_header), 0);
|
usbip_network_send(kSock, req_header, sizeof(usbip_stage2_header), 0);
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef __USBIP_SERVER_H__
|
#ifndef __USBIP_SERVER_H__
|
||||||
#define __USBIP_SERVER_H__
|
#define __USBIP_SERVER_H__
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
#include "components/USBIP/usbip_defs.h"
|
#include "components/USBIP/usbip_defs.h"
|
||||||
|
|
||||||
|
@ -18,6 +19,6 @@ int emulate(uint8_t *buffer, uint32_t length);
|
||||||
void send_stage2_submit_data(usbip_stage2_header *req_header, int32_t status, const void * const data, int32_t data_length);
|
void send_stage2_submit_data(usbip_stage2_header *req_header, int32_t status, const void * const data, int32_t data_length);
|
||||||
void send_stage2_submit(usbip_stage2_header *req_header, int32_t status, int32_t data_length);
|
void send_stage2_submit(usbip_stage2_header *req_header, int32_t status, int32_t data_length);
|
||||||
void send_stage2_submit_data_fast(usbip_stage2_header *req_header, const void *const data, int32_t data_length);
|
void send_stage2_submit_data_fast(usbip_stage2_header *req_header, const void *const data, int32_t data_length);
|
||||||
|
int usbip_network_send(int s, const void *dataptr, size_t size, int flags);
|
||||||
|
|
||||||
#endif
|
#endif
|
|
@ -23,5 +23,7 @@
|
||||||
// DO NOT CHANGE
|
// DO NOT CHANGE
|
||||||
#define PORT 3240
|
#define PORT 3240
|
||||||
#define CONFIG_EXAMPLE_IPV4 1
|
#define CONFIG_EXAMPLE_IPV4 1
|
||||||
|
#define USE_KCP 0
|
||||||
|
#define MTU_SIZE 1500
|
||||||
|
|
||||||
#endif
|
#endif
|
|
@ -133,7 +133,7 @@ CONFIG_ESP8266_WIFI_RX_BA_WIN_SIZE=6
|
||||||
CONFIG_ESP8266_WIFI_RX_BUFFER_NUM=28
|
CONFIG_ESP8266_WIFI_RX_BUFFER_NUM=28
|
||||||
CONFIG_ESP8266_WIFI_LEFT_CONTINUOUS_RX_BUFFER_NUM=8
|
CONFIG_ESP8266_WIFI_LEFT_CONTINUOUS_RX_BUFFER_NUM=8
|
||||||
CONFIG_ESP8266_WIFI_RX_PKT_NUM=16
|
CONFIG_ESP8266_WIFI_RX_PKT_NUM=16
|
||||||
CONFIG_ESP8266_WIFI_TX_PKT_NUM=6
|
CONFIG_ESP8266_WIFI_TX_PKT_NUM=16
|
||||||
CONFIG_ESP8266_WIFI_NVS_ENABLED=y
|
CONFIG_ESP8266_WIFI_NVS_ENABLED=y
|
||||||
CONFIG_ESP8266_WIFI_CONNECT_OPEN_ROUTER_WHEN_PWD_IS_SET=y
|
CONFIG_ESP8266_WIFI_CONNECT_OPEN_ROUTER_WHEN_PWD_IS_SET=y
|
||||||
CONFIG_ESP8266_WIFI_ENABLE_WPA3_SAE=y
|
CONFIG_ESP8266_WIFI_ENABLE_WPA3_SAE=y
|
||||||
|
@ -253,8 +253,7 @@ CONFIG_LWIP_TIMERS_ONDEMAND=y
|
||||||
CONFIG_LWIP_MAX_SOCKETS=10
|
CONFIG_LWIP_MAX_SOCKETS=10
|
||||||
# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set
|
# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set
|
||||||
# CONFIG_LWIP_SO_LINGER is not set
|
# CONFIG_LWIP_SO_LINGER is not set
|
||||||
CONFIG_LWIP_SO_REUSE=y
|
# CONFIG_LWIP_SO_REUSE is not set
|
||||||
CONFIG_LWIP_SO_REUSE_RXTOALL=y
|
|
||||||
# CONFIG_LWIP_SO_RCVBUF is not set
|
# CONFIG_LWIP_SO_RCVBUF is not set
|
||||||
# CONFIG_LWIP_NETBUF_RECVINFO is not set
|
# CONFIG_LWIP_NETBUF_RECVINFO is not set
|
||||||
CONFIG_LWIP_IP4_FRAG=y
|
CONFIG_LWIP_IP4_FRAG=y
|
||||||
|
|
Loading…
Reference in New Issue