Browse Source
- Add LottieIcon class (TGS gzip decompress + rlottie C API render) - Add AnimTimer singleton (33ms tick, activate/deactivate refcount) - Add EmojiButton (hover-triggered animation in emoji picker) - Add AnimatedEmoji support to EmojiData (5 TGS mappings) - MessageDelegate: QTextLayout-based animated emoji overlay (text + reactions) - ChatView: drag-to-select (click deselects, 5px drag starts selection) - ChatView: hoveredIndexChanged signal for hover-triggered animations - MessageList: wire AnimTimer tick to viewport update, hover → activate - CMake: AUTORCC, rlottie, zlib dependencies - Add 5 TGS animations as Qt resourcesfeature/x25519-migration
42 changed files with 286658 additions and 73 deletions
@ -1,28 +1,800 @@ |
|||||||
# Bootstrap Makefile for utun
|
# Makefile.in generated by automake 1.16.5 from Makefile.am.
|
||||||
# All build artifacts (.o) go to build/ directory
|
# Makefile. Generated from Makefile.in by configure.
|
||||||
# Final binary (utun) is copied to project root
|
|
||||||
|
|
||||||
BUILD_DIR = build
|
# Copyright (C) 1994-2021 Free Software Foundation, Inc.
|
||||||
|
|
||||||
all: |
# This Makefile.in is free software; the Free Software Foundation
|
||||||
@mkdir -p $(BUILD_DIR)
|
# gives unlimited permission to copy and/or distribute it,
|
||||||
@if test ! -f $(BUILD_DIR)/config.status || test ./configure -nt $(BUILD_DIR)/config.status; then \
|
# with or without modifications, as long as this notice is preserved.
|
||||||
echo "Configuring in $(BUILD_DIR)/..."; \
|
|
||||||
cd $(BUILD_DIR) && ../configure; \
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
|
||||||
|
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||||
|
# PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Main Makefile.am for utun - root directory configuration
|
||||||
|
|
||||||
|
am__is_gnu_make = { \
|
||||||
|
if test -z '$(MAKELEVEL)'; then \
|
||||||
|
false; \
|
||||||
|
elif test -n '$(MAKE_HOST)'; then \
|
||||||
|
true; \
|
||||||
|
elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
|
||||||
|
true; \
|
||||||
|
else \
|
||||||
|
false; \
|
||||||
|
fi; \
|
||||||
|
} |
||||||
|
am__make_running_with_option = \
|
||||||
|
case $${target_option-} in \
|
||||||
|
?) ;; \
|
||||||
|
*) echo "am__make_running_with_option: internal error: invalid" \
|
||||||
|
"target option '$${target_option-}' specified" >&2; \
|
||||||
|
exit 1;; \
|
||||||
|
esac; \
|
||||||
|
has_opt=no; \
|
||||||
|
sane_makeflags=$$MAKEFLAGS; \
|
||||||
|
if $(am__is_gnu_make); then \
|
||||||
|
sane_makeflags=$$MFLAGS; \
|
||||||
|
else \
|
||||||
|
case $$MAKEFLAGS in \
|
||||||
|
*\\[\ \ ]*) \
|
||||||
|
bs=\\; \
|
||||||
|
sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
|
||||||
|
| sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
|
||||||
|
esac; \
|
||||||
|
fi; \
|
||||||
|
skip_next=no; \
|
||||||
|
strip_trailopt () \
|
||||||
|
{ \
|
||||||
|
flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
|
||||||
|
}; \
|
||||||
|
for flg in $$sane_makeflags; do \
|
||||||
|
test $$skip_next = yes && { skip_next=no; continue; }; \
|
||||||
|
case $$flg in \
|
||||||
|
*=*|--*) continue;; \
|
||||||
|
-*I) strip_trailopt 'I'; skip_next=yes;; \
|
||||||
|
-*I?*) strip_trailopt 'I';; \
|
||||||
|
-*O) strip_trailopt 'O'; skip_next=yes;; \
|
||||||
|
-*O?*) strip_trailopt 'O';; \
|
||||||
|
-*l) strip_trailopt 'l'; skip_next=yes;; \
|
||||||
|
-*l?*) strip_trailopt 'l';; \
|
||||||
|
-[dEDm]) skip_next=yes;; \
|
||||||
|
-[JT]) skip_next=yes;; \
|
||||||
|
esac; \
|
||||||
|
case $$flg in \
|
||||||
|
*$$target_option*) has_opt=yes; break;; \
|
||||||
|
esac; \
|
||||||
|
done; \
|
||||||
|
test $$has_opt = yes
|
||||||
|
am__make_dryrun = (target_option=n; $(am__make_running_with_option))
|
||||||
|
am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
|
||||||
|
pkgdatadir = $(datadir)/utun
|
||||||
|
pkgincludedir = $(includedir)/utun
|
||||||
|
pkglibdir = $(libdir)/utun
|
||||||
|
pkglibexecdir = $(libexecdir)/utun
|
||||||
|
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
|
||||||
|
install_sh_DATA = $(install_sh) -c -m 644
|
||||||
|
install_sh_PROGRAM = $(install_sh) -c
|
||||||
|
install_sh_SCRIPT = $(install_sh) -c
|
||||||
|
INSTALL_HEADER = $(INSTALL_DATA)
|
||||||
|
transform = $(program_transform_name)
|
||||||
|
NORMAL_INSTALL = :
|
||||||
|
PRE_INSTALL = :
|
||||||
|
POST_INSTALL = :
|
||||||
|
NORMAL_UNINSTALL = :
|
||||||
|
PRE_UNINSTALL = :
|
||||||
|
POST_UNINSTALL = :
|
||||||
|
build_triplet = x86_64-pc-linux-gnu
|
||||||
|
host_triplet = x86_64-pc-linux-gnu
|
||||||
|
subdir = .
|
||||||
|
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
|
||||||
|
am__aclocal_m4_deps = $(top_srcdir)/configure.ac
|
||||||
|
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
|
||||||
|
$(ACLOCAL_M4)
|
||||||
|
DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \
|
||||||
|
$(am__configure_deps) $(am__DIST_COMMON)
|
||||||
|
am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
|
||||||
|
configure.lineno config.status.lineno
|
||||||
|
mkinstalldirs = $(install_sh) -d
|
||||||
|
CONFIG_HEADER = config.h
|
||||||
|
CONFIG_CLEAN_FILES =
|
||||||
|
CONFIG_CLEAN_VPATH_FILES =
|
||||||
|
AM_V_P = $(am__v_P_$(V))
|
||||||
|
am__v_P_ = $(am__v_P_$(AM_DEFAULT_VERBOSITY))
|
||||||
|
am__v_P_0 = false
|
||||||
|
am__v_P_1 = :
|
||||||
|
AM_V_GEN = $(am__v_GEN_$(V))
|
||||||
|
am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
|
||||||
|
am__v_GEN_0 = @echo " GEN " $@;
|
||||||
|
am__v_GEN_1 =
|
||||||
|
AM_V_at = $(am__v_at_$(V))
|
||||||
|
am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
|
||||||
|
am__v_at_0 = @
|
||||||
|
am__v_at_1 =
|
||||||
|
SOURCES =
|
||||||
|
DIST_SOURCES =
|
||||||
|
RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
|
||||||
|
ctags-recursive dvi-recursive html-recursive info-recursive \
|
||||||
|
install-data-recursive install-dvi-recursive \
|
||||||
|
install-exec-recursive install-html-recursive \
|
||||||
|
install-info-recursive install-pdf-recursive \
|
||||||
|
install-ps-recursive install-recursive installcheck-recursive \
|
||||||
|
installdirs-recursive pdf-recursive ps-recursive \
|
||||||
|
tags-recursive uninstall-recursive
|
||||||
|
am__can_run_installinfo = \
|
||||||
|
case $$AM_UPDATE_INFO_DIR in \
|
||||||
|
n|no|NO) false;; \
|
||||||
|
*) (install-info --version) >/dev/null 2>&1;; \
|
||||||
|
esac
|
||||||
|
RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
|
||||||
|
distclean-recursive maintainer-clean-recursive
|
||||||
|
am__recursive_targets = \
|
||||||
|
$(RECURSIVE_TARGETS) \
|
||||||
|
$(RECURSIVE_CLEAN_TARGETS) \
|
||||||
|
$(am__extra_recursive_targets)
|
||||||
|
AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
|
||||||
|
cscope distdir distdir-am dist dist-all distcheck
|
||||||
|
am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) \
|
||||||
|
config.h.in
|
||||||
|
# Read a list of newline-separated strings from the standard input,
|
||||||
|
# and print each of them once, without duplicates. Input order is
|
||||||
|
# *not* preserved.
|
||||||
|
am__uniquify_input = $(AWK) '\
|
||||||
|
BEGIN { nonempty = 0; } \
|
||||||
|
{ items[$$0] = 1; nonempty = 1; } \
|
||||||
|
END { if (nonempty) { for (i in items) print i; }; } \
|
||||||
|
' |
||||||
|
# Make sure the list of sources is unique. This is necessary because,
|
||||||
|
# e.g., the same source file might be shared among _SOURCES variables
|
||||||
|
# for different programs/libraries.
|
||||||
|
am__define_uniq_tagged_files = \
|
||||||
|
list='$(am__tagged_files)'; \
|
||||||
|
unique=`for i in $$list; do \
|
||||||
|
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
|
||||||
|
done | $(am__uniquify_input)`
|
||||||
|
DIST_SUBDIRS = $(SUBDIRS)
|
||||||
|
am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/config.h.in \
|
||||||
|
$(top_srcdir)/build-aux/ar-lib $(top_srcdir)/build-aux/compile \
|
||||||
|
$(top_srcdir)/build-aux/config.guess \
|
||||||
|
$(top_srcdir)/build-aux/config.sub \
|
||||||
|
$(top_srcdir)/build-aux/install-sh \
|
||||||
|
$(top_srcdir)/build-aux/missing build-aux/ar-lib \
|
||||||
|
build-aux/compile build-aux/config.guess build-aux/config.sub \
|
||||||
|
build-aux/depcomp build-aux/install-sh build-aux/missing
|
||||||
|
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
|
||||||
|
distdir = $(PACKAGE)-$(VERSION)
|
||||||
|
top_distdir = $(distdir)
|
||||||
|
am__remove_distdir = \
|
||||||
|
if test -d "$(distdir)"; then \
|
||||||
|
find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \
|
||||||
|
&& rm -rf "$(distdir)" \
|
||||||
|
|| { sleep 5 && rm -rf "$(distdir)"; }; \
|
||||||
|
else :; fi
|
||||||
|
am__post_remove_distdir = $(am__remove_distdir)
|
||||||
|
am__relativize = \
|
||||||
|
dir0=`pwd`; \
|
||||||
|
sed_first='s,^\([^/]*\)/.*$$,\1,'; \
|
||||||
|
sed_rest='s,^[^/]*/*,,'; \
|
||||||
|
sed_last='s,^.*/\([^/]*\)$$,\1,'; \
|
||||||
|
sed_butlast='s,/*[^/]*$$,,'; \
|
||||||
|
while test -n "$$dir1"; do \
|
||||||
|
first=`echo "$$dir1" | sed -e "$$sed_first"`; \
|
||||||
|
if test "$$first" != "."; then \
|
||||||
|
if test "$$first" = ".."; then \
|
||||||
|
dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
|
||||||
|
dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
|
||||||
|
else \
|
||||||
|
first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
|
||||||
|
if test "$$first2" = "$$first"; then \
|
||||||
|
dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
|
||||||
|
else \
|
||||||
|
dir2="../$$dir2"; \
|
||||||
|
fi; \
|
||||||
|
dir0="$$dir0"/"$$first"; \
|
||||||
|
fi; \
|
||||||
|
fi; \
|
||||||
|
dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
|
||||||
|
done; \
|
||||||
|
reldir="$$dir2"
|
||||||
|
DIST_ARCHIVES = $(distdir).tar.gz
|
||||||
|
GZIP_ENV = --best
|
||||||
|
DIST_TARGETS = dist-gzip
|
||||||
|
# Exists only to be overridden by the user if desired.
|
||||||
|
AM_DISTCHECK_DVI_TARGET = dvi
|
||||||
|
distuninstallcheck_listfiles = find . -type f -print
|
||||||
|
am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \
|
||||||
|
| sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$'
|
||||||
|
distcleancheck_listfiles = find . -type f -print
|
||||||
|
ACLOCAL = ${SHELL} '/home/vnc1/proj/utun3/build-aux/missing' aclocal-1.16
|
||||||
|
AMTAR = $${TAR-tar}
|
||||||
|
AM_DEFAULT_VERBOSITY = 1
|
||||||
|
AR = ar
|
||||||
|
AUTOCONF = ${SHELL} '/home/vnc1/proj/utun3/build-aux/missing' autoconf
|
||||||
|
AUTOHEADER = ${SHELL} '/home/vnc1/proj/utun3/build-aux/missing' autoheader
|
||||||
|
AUTOMAKE = ${SHELL} '/home/vnc1/proj/utun3/build-aux/missing' automake-1.16
|
||||||
|
AWK = mawk
|
||||||
|
CC = gcc
|
||||||
|
CCDEPMODE = depmode=gcc3
|
||||||
|
CFLAGS = -g -O2
|
||||||
|
CPPFLAGS =
|
||||||
|
CSCOPE = cscope
|
||||||
|
CTAGS = ctags
|
||||||
|
CYGPATH_W = echo
|
||||||
|
DEFS = -DHAVE_CONFIG_H
|
||||||
|
DEPDIR = .deps
|
||||||
|
ECHO_C =
|
||||||
|
ECHO_N = -n
|
||||||
|
ECHO_T =
|
||||||
|
ETAGS = etags
|
||||||
|
EXEEXT =
|
||||||
|
INSTALL = /usr/bin/install -c
|
||||||
|
INSTALL_DATA = ${INSTALL} -m 644
|
||||||
|
INSTALL_PROGRAM = ${INSTALL}
|
||||||
|
INSTALL_SCRIPT = ${INSTALL}
|
||||||
|
INSTALL_STRIP_PROGRAM = $(install_sh) -c -s
|
||||||
|
LDFLAGS =
|
||||||
|
LIBOBJS =
|
||||||
|
LIBS =
|
||||||
|
LTLIBOBJS =
|
||||||
|
MAKEINFO = ${SHELL} '/home/vnc1/proj/utun3/build-aux/missing' makeinfo
|
||||||
|
MKDIR_P = /usr/bin/mkdir -p
|
||||||
|
OBJEXT = o
|
||||||
|
PACKAGE = utun
|
||||||
|
PACKAGE_BUGREPORT = https://github.com/anomalyco/utun3/issues
|
||||||
|
PACKAGE_NAME = utun
|
||||||
|
PACKAGE_STRING = utun 2.0.0
|
||||||
|
PACKAGE_TARNAME = utun
|
||||||
|
PACKAGE_URL =
|
||||||
|
PACKAGE_VERSION = 2.0.0
|
||||||
|
PATH_SEPARATOR = :
|
||||||
|
RANLIB = ranlib
|
||||||
|
SET_MAKE =
|
||||||
|
SHELL = /bin/bash
|
||||||
|
STRIP =
|
||||||
|
TUN_LIBS =
|
||||||
|
VERSION = 2.0.0
|
||||||
|
abs_builddir = /home/vnc1/proj/utun3
|
||||||
|
abs_srcdir = /home/vnc1/proj/utun3
|
||||||
|
abs_top_builddir = /home/vnc1/proj/utun3
|
||||||
|
abs_top_srcdir = /home/vnc1/proj/utun3
|
||||||
|
ac_ct_AR = ar
|
||||||
|
ac_ct_CC = gcc
|
||||||
|
am__include = include
|
||||||
|
am__leading_dot = .
|
||||||
|
am__quote =
|
||||||
|
am__tar = $${TAR-tar} chof - "$$tardir"
|
||||||
|
am__untar = $${TAR-tar} xf -
|
||||||
|
bindir = ${exec_prefix}/bin
|
||||||
|
build = x86_64-pc-linux-gnu
|
||||||
|
build_alias =
|
||||||
|
build_cpu = x86_64
|
||||||
|
build_os = linux-gnu
|
||||||
|
build_vendor = pc
|
||||||
|
builddir = .
|
||||||
|
datadir = ${datarootdir}
|
||||||
|
datarootdir = ${prefix}/share
|
||||||
|
docdir = ${datarootdir}/doc/${PACKAGE_TARNAME}
|
||||||
|
dvidir = ${docdir}
|
||||||
|
exec_prefix = ${prefix}
|
||||||
|
host = x86_64-pc-linux-gnu
|
||||||
|
host_alias =
|
||||||
|
host_cpu = x86_64
|
||||||
|
host_os = linux-gnu
|
||||||
|
host_vendor = pc
|
||||||
|
htmldir = ${docdir}
|
||||||
|
includedir = ${prefix}/include
|
||||||
|
infodir = ${datarootdir}/info
|
||||||
|
install_sh = ${SHELL} /home/vnc1/proj/utun3/build-aux/install-sh
|
||||||
|
libdir = ${exec_prefix}/lib
|
||||||
|
libexecdir = ${exec_prefix}/libexec
|
||||||
|
localedir = ${datarootdir}/locale
|
||||||
|
localstatedir = ${prefix}/var
|
||||||
|
mandir = ${datarootdir}/man
|
||||||
|
mkdir_p = $(MKDIR_P)
|
||||||
|
oldincludedir = /usr/include
|
||||||
|
pdfdir = ${docdir}
|
||||||
|
prefix = /usr/local
|
||||||
|
program_transform_name = s,x,x,
|
||||||
|
psdir = ${docdir}
|
||||||
|
runstatedir = ${localstatedir}/run
|
||||||
|
sbindir = ${exec_prefix}/sbin
|
||||||
|
sharedstatedir = ${prefix}/com
|
||||||
|
srcdir = .
|
||||||
|
sysconfdir = ${prefix}/etc
|
||||||
|
target_alias =
|
||||||
|
top_build_prefix =
|
||||||
|
top_builddir = .
|
||||||
|
top_srcdir = .
|
||||||
|
|
||||||
|
# Order matters: lib -> src -> tests (tests depend on src objects)
|
||||||
|
SUBDIRS = lib src tests
|
||||||
|
all: config.h |
||||||
|
$(MAKE) $(AM_MAKEFLAGS) all-recursive
|
||||||
|
|
||||||
|
.SUFFIXES: |
||||||
|
am--refresh: Makefile |
||||||
|
@:
|
||||||
|
$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) |
||||||
|
@for dep in $?; do \
|
||||||
|
case '$(am__configure_deps)' in \
|
||||||
|
*$$dep*) \
|
||||||
|
echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \
|
||||||
|
$(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \
|
||||||
|
&& exit 0; \
|
||||||
|
exit 1;; \
|
||||||
|
esac; \
|
||||||
|
done; \
|
||||||
|
echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \
|
||||||
|
$(am__cd) $(top_srcdir) && \
|
||||||
|
$(AUTOMAKE) --foreign Makefile
|
||||||
|
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status |
||||||
|
@case '$?' in \
|
||||||
|
*config.status*) \
|
||||||
|
echo ' $(SHELL) ./config.status'; \
|
||||||
|
$(SHELL) ./config.status;; \
|
||||||
|
*) \
|
||||||
|
echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles)'; \
|
||||||
|
cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles);; \
|
||||||
|
esac;
|
||||||
|
|
||||||
|
$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) |
||||||
|
$(SHELL) ./config.status --recheck
|
||||||
|
|
||||||
|
$(top_srcdir)/configure: $(am__configure_deps) |
||||||
|
$(am__cd) $(srcdir) && $(AUTOCONF)
|
||||||
|
$(ACLOCAL_M4): $(am__aclocal_m4_deps) |
||||||
|
$(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
|
||||||
|
$(am__aclocal_m4_deps): |
||||||
|
|
||||||
|
config.h: stamp-h1 |
||||||
|
@test -f $@ || rm -f stamp-h1
|
||||||
|
@test -f $@ || $(MAKE) $(AM_MAKEFLAGS) stamp-h1
|
||||||
|
|
||||||
|
stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status |
||||||
|
@rm -f stamp-h1
|
||||||
|
cd $(top_builddir) && $(SHELL) ./config.status config.h
|
||||||
|
$(srcdir)/config.h.in: $(am__configure_deps) |
||||||
|
($(am__cd) $(top_srcdir) && $(AUTOHEADER))
|
||||||
|
rm -f stamp-h1
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
distclean-hdr: |
||||||
|
-rm -f config.h stamp-h1
|
||||||
|
|
||||||
|
# This directory's subdirectories are mostly independent; you can cd
|
||||||
|
# into them and run 'make' without going through this Makefile.
|
||||||
|
# To change the values of 'make' variables: instead of editing Makefiles,
|
||||||
|
# (1) if the variable is set in 'config.status', edit 'config.status'
|
||||||
|
# (which will cause the Makefiles to be regenerated when you run 'make');
|
||||||
|
# (2) otherwise, pass the desired values on the 'make' command line.
|
||||||
|
$(am__recursive_targets): |
||||||
|
@fail=; \
|
||||||
|
if $(am__make_keepgoing); then \
|
||||||
|
failcom='fail=yes'; \
|
||||||
|
else \
|
||||||
|
failcom='exit 1'; \
|
||||||
|
fi; \
|
||||||
|
dot_seen=no; \
|
||||||
|
target=`echo $@ | sed s/-recursive//`; \
|
||||||
|
case "$@" in \
|
||||||
|
distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
|
||||||
|
*) list='$(SUBDIRS)' ;; \
|
||||||
|
esac; \
|
||||||
|
for subdir in $$list; do \
|
||||||
|
echo "Making $$target in $$subdir"; \
|
||||||
|
if test "$$subdir" = "."; then \
|
||||||
|
dot_seen=yes; \
|
||||||
|
local_target="$$target-am"; \
|
||||||
|
else \
|
||||||
|
local_target="$$target"; \
|
||||||
|
fi; \
|
||||||
|
($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
|
||||||
|
|| eval $$failcom; \
|
||||||
|
done; \
|
||||||
|
if test "$$dot_seen" = "no"; then \
|
||||||
|
$(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
|
||||||
|
fi; test -z "$$fail"
|
||||||
|
|
||||||
|
ID: $(am__tagged_files) |
||||||
|
$(am__define_uniq_tagged_files); mkid -fID $$unique
|
||||||
|
tags: tags-recursive |
||||||
|
TAGS: tags |
||||||
|
|
||||||
|
tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) |
||||||
|
set x; \
|
||||||
|
here=`pwd`; \
|
||||||
|
if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
|
||||||
|
include_option=--etags-include; \
|
||||||
|
empty_fix=.; \
|
||||||
|
else \
|
||||||
|
include_option=--include; \
|
||||||
|
empty_fix=; \
|
||||||
|
fi; \
|
||||||
|
list='$(SUBDIRS)'; for subdir in $$list; do \
|
||||||
|
if test "$$subdir" = .; then :; else \
|
||||||
|
test ! -f $$subdir/TAGS || \
|
||||||
|
set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
|
||||||
|
fi; \
|
||||||
|
done; \
|
||||||
|
$(am__define_uniq_tagged_files); \
|
||||||
|
shift; \
|
||||||
|
if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
|
||||||
|
test -n "$$unique" || unique=$$empty_fix; \
|
||||||
|
if test $$# -gt 0; then \
|
||||||
|
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
|
||||||
|
"$$@" $$unique; \
|
||||||
|
else \
|
||||||
|
$(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
|
||||||
|
$$unique; \
|
||||||
|
fi; \
|
||||||
|
fi
|
||||||
|
ctags: ctags-recursive |
||||||
|
|
||||||
|
CTAGS: ctags |
||||||
|
ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) |
||||||
|
$(am__define_uniq_tagged_files); \
|
||||||
|
test -z "$(CTAGS_ARGS)$$unique" \
|
||||||
|
|| $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
|
||||||
|
$$unique
|
||||||
|
|
||||||
|
GTAGS: |
||||||
|
here=`$(am__cd) $(top_builddir) && pwd` \
|
||||||
|
&& $(am__cd) $(top_srcdir) \
|
||||||
|
&& gtags -i $(GTAGS_ARGS) "$$here"
|
||||||
|
cscope: cscope.files |
||||||
|
test ! -s cscope.files \
|
||||||
|
|| $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS)
|
||||||
|
clean-cscope: |
||||||
|
-rm -f cscope.files
|
||||||
|
cscope.files: clean-cscope cscopelist |
||||||
|
cscopelist: cscopelist-recursive |
||||||
|
|
||||||
|
cscopelist-am: $(am__tagged_files) |
||||||
|
list='$(am__tagged_files)'; \
|
||||||
|
case "$(srcdir)" in \
|
||||||
|
[\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
|
||||||
|
*) sdir=$(subdir)/$(srcdir) ;; \
|
||||||
|
esac; \
|
||||||
|
for i in $$list; do \
|
||||||
|
if test -f "$$i"; then \
|
||||||
|
echo "$(subdir)/$$i"; \
|
||||||
|
else \
|
||||||
|
echo "$$sdir/$$i"; \
|
||||||
|
fi; \
|
||||||
|
done >> $(top_builddir)/cscope.files
|
||||||
|
|
||||||
|
distclean-tags: |
||||||
|
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
|
||||||
|
-rm -f cscope.out cscope.in.out cscope.po.out cscope.files
|
||||||
|
distdir: $(BUILT_SOURCES) |
||||||
|
$(MAKE) $(AM_MAKEFLAGS) distdir-am
|
||||||
|
|
||||||
|
distdir-am: $(DISTFILES) |
||||||
|
$(am__remove_distdir)
|
||||||
|
test -d "$(distdir)" || mkdir "$(distdir)"
|
||||||
|
@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
|
||||||
|
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
|
||||||
|
list='$(DISTFILES)'; \
|
||||||
|
dist_files=`for file in $$list; do echo $$file; done | \
|
||||||
|
sed -e "s|^$$srcdirstrip/||;t" \
|
||||||
|
-e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
|
||||||
|
case $$dist_files in \
|
||||||
|
*/*) $(MKDIR_P) `echo "$$dist_files" | \
|
||||||
|
sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
|
||||||
|
sort -u` ;; \
|
||||||
|
esac; \
|
||||||
|
for file in $$dist_files; do \
|
||||||
|
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
|
||||||
|
if test -d $$d/$$file; then \
|
||||||
|
dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
|
||||||
|
if test -d "$(distdir)/$$file"; then \
|
||||||
|
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
|
||||||
|
fi; \
|
||||||
|
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
|
||||||
|
cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
|
||||||
|
find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
|
||||||
|
fi; \
|
||||||
|
cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
|
||||||
|
else \
|
||||||
|
test -f "$(distdir)/$$file" \
|
||||||
|
|| cp -p $$d/$$file "$(distdir)/$$file" \
|
||||||
|
|| exit 1; \
|
||||||
|
fi; \
|
||||||
|
done
|
||||||
|
@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
|
||||||
|
if test "$$subdir" = .; then :; else \
|
||||||
|
$(am__make_dryrun) \
|
||||||
|
|| test -d "$(distdir)/$$subdir" \
|
||||||
|
|| $(MKDIR_P) "$(distdir)/$$subdir" \
|
||||||
|
|| exit 1; \
|
||||||
|
dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
|
||||||
|
$(am__relativize); \
|
||||||
|
new_distdir=$$reldir; \
|
||||||
|
dir1=$$subdir; dir2="$(top_distdir)"; \
|
||||||
|
$(am__relativize); \
|
||||||
|
new_top_distdir=$$reldir; \
|
||||||
|
echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
|
||||||
|
echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
|
||||||
|
($(am__cd) $$subdir && \
|
||||||
|
$(MAKE) $(AM_MAKEFLAGS) \
|
||||||
|
top_distdir="$$new_top_distdir" \
|
||||||
|
distdir="$$new_distdir" \
|
||||||
|
am__remove_distdir=: \
|
||||||
|
am__skip_length_check=: \
|
||||||
|
am__skip_mode_fix=: \
|
||||||
|
distdir) \
|
||||||
|
|| exit 1; \
|
||||||
|
fi; \
|
||||||
|
done
|
||||||
|
-test -n "$(am__skip_mode_fix)" \
|
||||||
|
|| find "$(distdir)" -type d ! -perm -755 \
|
||||||
|
-exec chmod u+rwx,go+rx {} \; -o \
|
||||||
|
! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
|
||||||
|
! -type d ! -perm -400 -exec chmod a+r {} \; -o \
|
||||||
|
! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
|
||||||
|
|| chmod -R a+r "$(distdir)"
|
||||||
|
dist-gzip: distdir |
||||||
|
tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz
|
||||||
|
$(am__post_remove_distdir)
|
||||||
|
|
||||||
|
dist-bzip2: distdir |
||||||
|
tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2
|
||||||
|
$(am__post_remove_distdir)
|
||||||
|
|
||||||
|
dist-lzip: distdir |
||||||
|
tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz
|
||||||
|
$(am__post_remove_distdir)
|
||||||
|
|
||||||
|
dist-xz: distdir |
||||||
|
tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz
|
||||||
|
$(am__post_remove_distdir)
|
||||||
|
|
||||||
|
dist-zstd: distdir |
||||||
|
tardir=$(distdir) && $(am__tar) | zstd -c $${ZSTD_CLEVEL-$${ZSTD_OPT--19}} >$(distdir).tar.zst
|
||||||
|
$(am__post_remove_distdir)
|
||||||
|
|
||||||
|
dist-tarZ: distdir |
||||||
|
@echo WARNING: "Support for distribution archives compressed with" \
|
||||||
|
"legacy program 'compress' is deprecated." >&2
|
||||||
|
@echo WARNING: "It will be removed altogether in Automake 2.0" >&2
|
||||||
|
tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
|
||||||
|
$(am__post_remove_distdir)
|
||||||
|
|
||||||
|
dist-shar: distdir |
||||||
|
@echo WARNING: "Support for shar distribution archives is" \
|
||||||
|
"deprecated." >&2
|
||||||
|
@echo WARNING: "It will be removed altogether in Automake 2.0" >&2
|
||||||
|
shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz
|
||||||
|
$(am__post_remove_distdir)
|
||||||
|
|
||||||
|
dist-zip: distdir |
||||||
|
-rm -f $(distdir).zip
|
||||||
|
zip -rq $(distdir).zip $(distdir)
|
||||||
|
$(am__post_remove_distdir)
|
||||||
|
|
||||||
|
dist dist-all: |
||||||
|
$(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:'
|
||||||
|
$(am__post_remove_distdir)
|
||||||
|
|
||||||
|
# This target untars the dist file and tries a VPATH configuration. Then
|
||||||
|
# it guarantees that the distribution is self-contained by making another
|
||||||
|
# tarfile.
|
||||||
|
distcheck: dist |
||||||
|
case '$(DIST_ARCHIVES)' in \
|
||||||
|
*.tar.gz*) \
|
||||||
|
eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\
|
||||||
|
*.tar.bz2*) \
|
||||||
|
bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\
|
||||||
|
*.tar.lz*) \
|
||||||
|
lzip -dc $(distdir).tar.lz | $(am__untar) ;;\
|
||||||
|
*.tar.xz*) \
|
||||||
|
xz -dc $(distdir).tar.xz | $(am__untar) ;;\
|
||||||
|
*.tar.Z*) \
|
||||||
|
uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
|
||||||
|
*.shar.gz*) \
|
||||||
|
eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\
|
||||||
|
*.zip*) \
|
||||||
|
unzip $(distdir).zip ;;\
|
||||||
|
*.tar.zst*) \
|
||||||
|
zstd -dc $(distdir).tar.zst | $(am__untar) ;;\
|
||||||
|
esac
|
||||||
|
chmod -R a-w $(distdir)
|
||||||
|
chmod u+w $(distdir)
|
||||||
|
mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst
|
||||||
|
chmod a-w $(distdir)
|
||||||
|
test -d $(distdir)/_build || exit 0; \
|
||||||
|
dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \
|
||||||
|
&& dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \
|
||||||
|
&& am__cwd=`pwd` \
|
||||||
|
&& $(am__cd) $(distdir)/_build/sub \
|
||||||
|
&& ../../configure \
|
||||||
|
$(AM_DISTCHECK_CONFIGURE_FLAGS) \
|
||||||
|
$(DISTCHECK_CONFIGURE_FLAGS) \
|
||||||
|
--srcdir=../.. --prefix="$$dc_install_base" \
|
||||||
|
&& $(MAKE) $(AM_MAKEFLAGS) \
|
||||||
|
&& $(MAKE) $(AM_MAKEFLAGS) $(AM_DISTCHECK_DVI_TARGET) \
|
||||||
|
&& $(MAKE) $(AM_MAKEFLAGS) check \
|
||||||
|
&& $(MAKE) $(AM_MAKEFLAGS) install \
|
||||||
|
&& $(MAKE) $(AM_MAKEFLAGS) installcheck \
|
||||||
|
&& $(MAKE) $(AM_MAKEFLAGS) uninstall \
|
||||||
|
&& $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \
|
||||||
|
distuninstallcheck \
|
||||||
|
&& chmod -R a-w "$$dc_install_base" \
|
||||||
|
&& ({ \
|
||||||
|
(cd ../.. && umask 077 && mkdir "$$dc_destdir") \
|
||||||
|
&& $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \
|
||||||
|
&& $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \
|
||||||
|
&& $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \
|
||||||
|
distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \
|
||||||
|
} || { rm -rf "$$dc_destdir"; exit 1; }) \
|
||||||
|
&& rm -rf "$$dc_destdir" \
|
||||||
|
&& $(MAKE) $(AM_MAKEFLAGS) dist \
|
||||||
|
&& rm -rf $(DIST_ARCHIVES) \
|
||||||
|
&& $(MAKE) $(AM_MAKEFLAGS) distcleancheck \
|
||||||
|
&& cd "$$am__cwd" \
|
||||||
|
|| exit 1
|
||||||
|
$(am__post_remove_distdir)
|
||||||
|
@(echo "$(distdir) archives ready for distribution: "; \
|
||||||
|
list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
|
||||||
|
sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
|
||||||
|
distuninstallcheck: |
||||||
|
@test -n '$(distuninstallcheck_dir)' || { \
|
||||||
|
echo 'ERROR: trying to run $@ with an empty' \
|
||||||
|
'$$(distuninstallcheck_dir)' >&2; \
|
||||||
|
exit 1; \
|
||||||
|
}; \
|
||||||
|
$(am__cd) '$(distuninstallcheck_dir)' || { \
|
||||||
|
echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \
|
||||||
|
exit 1; \
|
||||||
|
}; \
|
||||||
|
test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \
|
||||||
|
|| { echo "ERROR: files left after uninstall:" ; \
|
||||||
|
if test -n "$(DESTDIR)"; then \
|
||||||
|
echo " (check DESTDIR support)"; \
|
||||||
|
fi ; \
|
||||||
|
$(distuninstallcheck_listfiles) ; \
|
||||||
|
exit 1; } >&2
|
||||||
|
distcleancheck: distclean |
||||||
|
@if test '$(srcdir)' = . ; then \
|
||||||
|
echo "ERROR: distcleancheck can only run from a VPATH build" ; \
|
||||||
|
exit 1 ; \
|
||||||
fi
|
fi
|
||||||
@$(MAKE) -C $(BUILD_DIR)
|
@test `$(distcleancheck_listfiles) | wc -l` -eq 0 \
|
||||||
|
|| { echo "ERROR: files left in build directory after distclean:" ; \
|
||||||
|
$(distcleancheck_listfiles) ; \
|
||||||
|
exit 1; } >&2
|
||||||
|
check-am: all-am |
||||||
|
check: check-recursive |
||||||
|
all-am: Makefile config.h |
||||||
|
installdirs: installdirs-recursive |
||||||
|
installdirs-am: |
||||||
|
install: install-recursive |
||||||
|
install-exec: install-exec-recursive |
||||||
|
install-data: install-data-recursive |
||||||
|
uninstall: uninstall-recursive |
||||||
|
|
||||||
|
install-am: all-am |
||||||
|
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
|
||||||
|
|
||||||
|
installcheck: installcheck-recursive |
||||||
|
install-strip: |
||||||
|
if test -z '$(STRIP)'; then \
|
||||||
|
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
|
||||||
|
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
|
||||||
|
install; \
|
||||||
|
else \
|
||||||
|
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
|
||||||
|
install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
|
||||||
|
"INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
|
||||||
|
fi
|
||||||
|
mostlyclean-generic: |
||||||
|
|
||||||
|
clean-generic: |
||||||
|
|
||||||
|
distclean-generic: |
||||||
|
-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
|
||||||
|
-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
|
||||||
|
|
||||||
|
maintainer-clean-generic: |
||||||
|
@echo "This command is intended for maintainers to use"
|
||||||
|
@echo "it deletes files that may require special tools to rebuild."
|
||||||
|
clean: clean-recursive |
||||||
|
|
||||||
|
clean-am: clean-generic clean-local mostlyclean-am |
||||||
|
|
||||||
|
distclean: distclean-recursive |
||||||
|
-rm -f $(am__CONFIG_DISTCLEAN_FILES)
|
||||||
|
-rm -f Makefile
|
||||||
|
distclean-am: clean-am distclean-generic distclean-hdr distclean-tags |
||||||
|
|
||||||
|
dvi: dvi-recursive |
||||||
|
|
||||||
|
dvi-am: |
||||||
|
|
||||||
|
html: html-recursive |
||||||
|
|
||||||
|
html-am: |
||||||
|
|
||||||
|
info: info-recursive |
||||||
|
|
||||||
|
info-am: |
||||||
|
|
||||||
|
install-data-am: |
||||||
|
|
||||||
|
install-dvi: install-dvi-recursive |
||||||
|
|
||||||
|
install-dvi-am: |
||||||
|
|
||||||
|
install-exec-am: |
||||||
|
|
||||||
|
install-html: install-html-recursive |
||||||
|
|
||||||
|
install-html-am: |
||||||
|
|
||||||
|
install-info: install-info-recursive |
||||||
|
|
||||||
|
install-info-am: |
||||||
|
|
||||||
|
install-man: |
||||||
|
|
||||||
|
install-pdf: install-pdf-recursive |
||||||
|
|
||||||
|
install-pdf-am: |
||||||
|
|
||||||
|
install-ps: install-ps-recursive |
||||||
|
|
||||||
|
install-ps-am: |
||||||
|
|
||||||
|
installcheck-am: |
||||||
|
|
||||||
|
maintainer-clean: maintainer-clean-recursive |
||||||
|
-rm -f $(am__CONFIG_DISTCLEAN_FILES)
|
||||||
|
-rm -rf $(top_srcdir)/autom4te.cache
|
||||||
|
-rm -f Makefile
|
||||||
|
maintainer-clean-am: distclean-am maintainer-clean-generic |
||||||
|
|
||||||
|
mostlyclean: mostlyclean-recursive |
||||||
|
|
||||||
|
mostlyclean-am: mostlyclean-generic |
||||||
|
|
||||||
|
pdf: pdf-recursive |
||||||
|
|
||||||
|
pdf-am: |
||||||
|
|
||||||
|
ps: ps-recursive |
||||||
|
|
||||||
|
ps-am: |
||||||
|
|
||||||
|
uninstall-am: |
||||||
|
|
||||||
|
.MAKE: $(am__recursive_targets) all install-am install-strip |
||||||
|
|
||||||
clean: |
.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
|
||||||
-rm -rf $(BUILD_DIR)
|
am--refresh check check-am clean clean-cscope clean-generic \
|
||||||
|
clean-local cscope cscopelist-am ctags ctags-am dist dist-all \
|
||||||
|
dist-bzip2 dist-gzip dist-lzip dist-shar dist-tarZ dist-xz \
|
||||||
|
dist-zip dist-zstd distcheck distclean distclean-generic \
|
||||||
|
distclean-hdr distclean-tags distcleancheck distdir \
|
||||||
|
distuninstallcheck dvi dvi-am html html-am info info-am \
|
||||||
|
install install-am install-data install-data-am install-dvi \
|
||||||
|
install-dvi-am install-exec install-exec-am install-html \
|
||||||
|
install-html-am install-info install-info-am install-man \
|
||||||
|
install-pdf install-pdf-am install-ps install-ps-am \
|
||||||
|
install-strip installcheck installcheck-am installdirs \
|
||||||
|
installdirs-am maintainer-clean maintainer-clean-generic \
|
||||||
|
mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \
|
||||||
|
tags-am uninstall uninstall-am
|
||||||
|
|
||||||
distclean: clean |
.PRECIOUS: Makefile |
||||||
-rm -f config.status config.log config.h stamp-h1
|
|
||||||
-rm -rf autom4te.cache/
|
|
||||||
|
|
||||||
check: all |
|
||||||
@$(MAKE) -C $(BUILD_DIR) check
|
|
||||||
|
|
||||||
install: |
# Clean build directory
|
||||||
@$(MAKE) -C $(BUILD_DIR) install
|
clean-local: |
||||||
|
-rm -rf build/
|
||||||
|
|
||||||
.PHONY: all clean distclean check install |
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
||||||
|
# Otherwise a system limit (for SysV at least) may be exceeded.
|
||||||
|
.NOEXPORT: |
||||||
|
|||||||
@ -0,0 +1,585 @@ |
|||||||
|
/*
|
||||||
|
* msg_transport.c — TCP-сервер транспорта сообщений (local IPC → ETCP) |
||||||
|
* |
||||||
|
* Принимает TCP-соединения от локальных приложений (протокол msg_transport.h), |
||||||
|
* привязывает к remote peer и пересылает данные через etcp_router. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "msg_transport.h" |
||||||
|
#include "utun_instance.h" |
||||||
|
#include "etcp_router.h" |
||||||
|
#include "etcp.h" |
||||||
|
#include "../lib/u_async.h" |
||||||
|
#include "../lib/debug_config.h" |
||||||
|
#include "../lib/mem.h" |
||||||
|
|
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#ifdef _WIN32 |
||||||
|
#include <winsock2.h> |
||||||
|
#include <ws2tcpip.h> |
||||||
|
#else |
||||||
|
#include <unistd.h> |
||||||
|
#include <sys/socket.h> |
||||||
|
#include <netinet/in.h> |
||||||
|
#include <arpa/inet.h> |
||||||
|
#include <errno.h> |
||||||
|
#include <fcntl.h> |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifndef DEBUG_CATEGORY_MSGTRANSPORT |
||||||
|
#define DEBUG_CATEGORY_MSGTRANSPORT DEBUG_CATEGORY_GENERAL |
||||||
|
#endif |
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* Внутренние структуры |
||||||
|
* ============================================================================ */ |
||||||
|
|
||||||
|
struct msg_client { |
||||||
|
struct msg_client* next; |
||||||
|
struct msg_transport* transport; |
||||||
|
socket_t fd; |
||||||
|
void* socket_id; |
||||||
|
uint8_t recv_buffer[MSG_MAX_MSG_SIZE]; |
||||||
|
uint16_t recv_len; |
||||||
|
uint64_t bound_peer_id; /* 0 = не забинден */ |
||||||
|
}; |
||||||
|
|
||||||
|
struct msg_transport { |
||||||
|
struct UTUN_INSTANCE* instance; |
||||||
|
struct UASYNC* ua; |
||||||
|
socket_t listen_fd; |
||||||
|
void* listen_socket_id; |
||||||
|
struct sockaddr_storage bind_addr; |
||||||
|
struct msg_client* clients; |
||||||
|
uint32_t client_count; |
||||||
|
uint32_t max_clients; |
||||||
|
uint8_t svc_id; |
||||||
|
struct msg_transport_counters counters; |
||||||
|
}; |
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* Forward declarations |
||||||
|
* ============================================================================ */ |
||||||
|
|
||||||
|
static void accept_callback(socket_t fd, void* arg); |
||||||
|
static void client_read_callback(socket_t fd, void* arg); |
||||||
|
static void client_except_callback(socket_t fd, void* arg); |
||||||
|
static void close_client(struct msg_transport* t, struct msg_client* client); |
||||||
|
|
||||||
|
static void handle_client_data(struct msg_transport* t, struct msg_client* client); |
||||||
|
static void on_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry); |
||||||
|
|
||||||
|
static int forward_to_peer(struct msg_transport* t, uint64_t dst_peer_id, |
||||||
|
const uint8_t* data, size_t data_len, int is_signed); |
||||||
|
static void send_response(struct msg_client* client, uint8_t type, uint64_t id, |
||||||
|
const uint8_t* data, size_t data_len); |
||||||
|
static void send_error(struct msg_client* client, uint8_t error_code, const char* msg); |
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* Инициализация / завершение |
||||||
|
* ============================================================================ */ |
||||||
|
|
||||||
|
int msg_transport_init(struct msg_transport** out, |
||||||
|
struct UTUN_INSTANCE* instance, |
||||||
|
struct UASYNC* ua, |
||||||
|
struct sockaddr_storage* bind_addr, |
||||||
|
uint32_t max_clients, |
||||||
|
uint8_t svc_id) { |
||||||
|
if (!out || !instance || !ua || !bind_addr) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport_init: invalid args out=%p inst=%p ua=%p addr=%p", |
||||||
|
(void*)out, (void*)instance, (void*)ua, (void*)bind_addr); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
if (bind_addr->ss_family == 0) { |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport_init: bind_addr has no family, skipping"); |
||||||
|
*out = NULL; |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
struct msg_transport* t = u_calloc(1, sizeof(*t)); |
||||||
|
if (!t) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport_init: u_calloc failed"); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
t->instance = instance; |
||||||
|
t->ua = ua; |
||||||
|
t->max_clients = max_clients ? max_clients : 8; |
||||||
|
t->svc_id = svc_id; |
||||||
|
memcpy(&t->bind_addr, bind_addr, sizeof(*bind_addr)); |
||||||
|
|
||||||
|
int family = bind_addr->ss_family; |
||||||
|
t->listen_fd = socket(family, SOCK_STREAM, IPPROTO_TCP); |
||||||
|
|
||||||
|
#ifdef _WIN32 |
||||||
|
if (t->listen_fd == INVALID_SOCKET) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "socket() failed: %d", WSAGetLastError()); |
||||||
|
u_free(t); return -1; |
||||||
|
} |
||||||
|
u_long nonblock = 1; |
||||||
|
ioctlsocket(t->listen_fd, FIONBIO, &nonblock); |
||||||
|
int reuse = 1; |
||||||
|
setsockopt(t->listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)); |
||||||
|
#else |
||||||
|
if (t->listen_fd < 0) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "socket() failed: %s", strerror(errno)); |
||||||
|
u_free(t); return -1; |
||||||
|
} |
||||||
|
int reuse = 1; |
||||||
|
setsockopt(t->listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); |
||||||
|
int flags = fcntl(t->listen_fd, F_GETFL, 0); |
||||||
|
if (flags >= 0) fcntl(t->listen_fd, F_SETFL, flags | O_NONBLOCK); |
||||||
|
#endif |
||||||
|
|
||||||
|
socklen_t addr_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); |
||||||
|
|
||||||
|
#ifdef _WIN32 |
||||||
|
if (bind(t->listen_fd, (struct sockaddr*)bind_addr, addr_len) == SOCKET_ERROR) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "bind() failed: %d", WSAGetLastError()); |
||||||
|
closesocket(t->listen_fd); u_free(t); return -1; |
||||||
|
} |
||||||
|
if (listen(t->listen_fd, 5) == SOCKET_ERROR) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "listen() failed: %d", WSAGetLastError()); |
||||||
|
closesocket(t->listen_fd); u_free(t); return -1; |
||||||
|
} |
||||||
|
#else |
||||||
|
if (bind(t->listen_fd, (struct sockaddr*)bind_addr, addr_len) < 0) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "bind() failed: %s", strerror(errno)); |
||||||
|
close(t->listen_fd); u_free(t); return -1; |
||||||
|
} |
||||||
|
if (listen(t->listen_fd, 5) < 0) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "listen() failed: %s", strerror(errno)); |
||||||
|
close(t->listen_fd); u_free(t); return -1; |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
t->listen_socket_id = uasync_add_socket_t(ua, t->listen_fd, |
||||||
|
accept_callback, NULL, NULL, t); |
||||||
|
if (!t->listen_socket_id) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "uasync_add_socket_t failed for listen_fd"); |
||||||
|
#ifdef _WIN32 |
||||||
|
closesocket(t->listen_fd); |
||||||
|
#else |
||||||
|
close(t->listen_fd); |
||||||
|
#endif |
||||||
|
u_free(t); return -1; |
||||||
|
} |
||||||
|
|
||||||
|
if (etcp_router_bind(instance, svc_id, on_etcp_recv) != 0) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "etcp_router_bind(svc_id=%u) failed", svc_id); |
||||||
|
uasync_remove_socket_t(ua, t->listen_fd); |
||||||
|
#ifdef _WIN32 |
||||||
|
closesocket(t->listen_fd); |
||||||
|
#else |
||||||
|
close(t->listen_fd); |
||||||
|
#endif |
||||||
|
u_free(t); return -1; |
||||||
|
} |
||||||
|
|
||||||
|
if (family == AF_INET) { |
||||||
|
struct sockaddr_in* sin = (struct sockaddr_in*)bind_addr; |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport listening on %s:%d svc_id=%u", |
||||||
|
ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port), svc_id); |
||||||
|
} else { |
||||||
|
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)bind_addr; |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport listening on [%s]:%d svc_id=%u", |
||||||
|
ip_to_str(&sin6->sin6_addr, AF_INET6).str, ntohs(sin6->sin6_port), svc_id); |
||||||
|
} |
||||||
|
|
||||||
|
*out = t; |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
void msg_transport_shutdown(struct msg_transport* t) { |
||||||
|
if (!t) return; |
||||||
|
|
||||||
|
while (t->clients) close_client(t, t->clients); |
||||||
|
|
||||||
|
#ifdef _WIN32 |
||||||
|
if (t->listen_fd != INVALID_SOCKET) { |
||||||
|
uasync_remove_socket_t(t->ua, t->listen_fd); |
||||||
|
t->listen_socket_id = NULL; |
||||||
|
closesocket(t->listen_fd); |
||||||
|
t->listen_fd = INVALID_SOCKET; |
||||||
|
} |
||||||
|
#else |
||||||
|
if (t->listen_fd >= 0) { |
||||||
|
uasync_remove_socket_t(t->ua, t->listen_fd); |
||||||
|
t->listen_socket_id = NULL; |
||||||
|
close(t->listen_fd); |
||||||
|
t->listen_fd = -1; |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport shutdown complete svc_id=%u", t->svc_id); |
||||||
|
memset(t, 0, sizeof(*t)); |
||||||
|
u_free(t); |
||||||
|
} |
||||||
|
|
||||||
|
void msg_transport_get_counters(struct msg_transport* t, struct msg_transport_counters* c) { |
||||||
|
if (t && c) memcpy(c, &t->counters, sizeof(*c)); |
||||||
|
} |
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* TCP-клиенты: accept / read / close |
||||||
|
* ============================================================================ */ |
||||||
|
|
||||||
|
static void accept_callback(socket_t fd, void* arg) { |
||||||
|
struct msg_transport* t = (struct msg_transport*)arg; |
||||||
|
|
||||||
|
struct sockaddr_storage client_addr; |
||||||
|
socklen_t addr_len = sizeof(client_addr); |
||||||
|
|
||||||
|
#ifdef _WIN32 |
||||||
|
socket_t client_fd = accept(fd, (struct sockaddr*)&client_addr, &addr_len); |
||||||
|
if (client_fd == INVALID_SOCKET) { |
||||||
|
int err = WSAGetLastError(); |
||||||
|
if (err != WSAEWOULDBLOCK) |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "accept() failed: %d", err); |
||||||
|
return; |
||||||
|
} |
||||||
|
u_long nonblock = 1; |
||||||
|
ioctlsocket(client_fd, FIONBIO, &nonblock); |
||||||
|
#else |
||||||
|
socket_t client_fd = accept(fd, (struct sockaddr*)&client_addr, &addr_len); |
||||||
|
if (client_fd < 0) { |
||||||
|
if (errno != EAGAIN && errno != EWOULDBLOCK) |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "accept() failed: %s", strerror(errno)); |
||||||
|
return; |
||||||
|
} |
||||||
|
int flags = fcntl(client_fd, F_GETFL, 0); |
||||||
|
if (flags >= 0) fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); |
||||||
|
#endif |
||||||
|
|
||||||
|
if (t->client_count >= t->max_clients) { |
||||||
|
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "max_clients=%u reached, rejecting", t->max_clients); |
||||||
|
#ifdef _WIN32 |
||||||
|
closesocket(client_fd); |
||||||
|
#else |
||||||
|
close(client_fd); |
||||||
|
#endif |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
struct msg_client* client = u_calloc(1, sizeof(*client)); |
||||||
|
if (!client) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "u_calloc(msg_client) failed"); |
||||||
|
#ifdef _WIN32 |
||||||
|
closesocket(client_fd); |
||||||
|
#else |
||||||
|
close(client_fd); |
||||||
|
#endif |
||||||
|
return; |
||||||
|
} |
||||||
|
client->fd = client_fd; |
||||||
|
client->transport = t; |
||||||
|
|
||||||
|
client->socket_id = uasync_add_socket_t(t->ua, client_fd, |
||||||
|
client_read_callback, NULL, |
||||||
|
client_except_callback, client); |
||||||
|
if (!client->socket_id) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "uasync_add_socket_t failed for client"); |
||||||
|
u_free(client); |
||||||
|
#ifdef _WIN32 |
||||||
|
closesocket(client_fd); |
||||||
|
#else |
||||||
|
close(client_fd); |
||||||
|
#endif |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
client->next = t->clients; |
||||||
|
t->clients = client; |
||||||
|
t->client_count++; |
||||||
|
t->counters.accepted++; |
||||||
|
|
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport client connected (total=%u)", t->client_count); |
||||||
|
} |
||||||
|
|
||||||
|
static void client_read_callback(socket_t fd, void* arg) { |
||||||
|
struct msg_client* client = (struct msg_client*)arg; |
||||||
|
struct msg_transport* t = client->transport; |
||||||
|
|
||||||
|
uint8_t* buf = client->recv_buffer + client->recv_len; |
||||||
|
size_t buf_space = MSG_MAX_MSG_SIZE - client->recv_len; |
||||||
|
|
||||||
|
#ifdef _WIN32 |
||||||
|
int received = recv(fd, (char*)buf, (int)buf_space, 0); |
||||||
|
if (received == SOCKET_ERROR) { |
||||||
|
int err = WSAGetLastError(); |
||||||
|
if (err != WSAEWOULDBLOCK) { |
||||||
|
if (err == 10054) { close_client(t, client); return; } |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "recv() error: %d", err); |
||||||
|
close_client(t, client); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
#else |
||||||
|
ssize_t received = recv(fd, buf, buf_space, 0); |
||||||
|
if (received < 0) { |
||||||
|
if (errno != EAGAIN && errno != EWOULDBLOCK) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "recv() error: %s", strerror(errno)); |
||||||
|
close_client(t, client); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
#endif |
||||||
|
if (received == 0) { |
||||||
|
close_client(t, client); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
client->recv_len += (uint16_t)received; |
||||||
|
if (client->recv_len > MSG_MAX_MSG_SIZE) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "recv buffer overflow"); |
||||||
|
close_client(t, client); |
||||||
|
return; |
||||||
|
} |
||||||
|
handle_client_data(t, client); |
||||||
|
} |
||||||
|
|
||||||
|
static void client_except_callback(socket_t fd, void* arg) { |
||||||
|
struct msg_client* client = (struct msg_client*)arg; |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "client socket exception"); |
||||||
|
close_client(client->transport, client); |
||||||
|
(void)fd; |
||||||
|
} |
||||||
|
|
||||||
|
static void close_client(struct msg_transport* t, struct msg_client* client) { |
||||||
|
if (!t || !client) return; |
||||||
|
|
||||||
|
#ifdef _WIN32 |
||||||
|
if (client->fd != INVALID_SOCKET) { |
||||||
|
uasync_remove_socket_t(t->ua, client->fd); |
||||||
|
closesocket(client->fd); |
||||||
|
} |
||||||
|
#else |
||||||
|
if (client->fd >= 0) { |
||||||
|
uasync_remove_socket_t(t->ua, client->fd); |
||||||
|
close(client->fd); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
struct msg_client** curr = &t->clients; |
||||||
|
while (*curr) { |
||||||
|
if (*curr == client) { *curr = client->next; break; } |
||||||
|
curr = &(*curr)->next; |
||||||
|
} |
||||||
|
t->client_count--; |
||||||
|
t->counters.closed++; |
||||||
|
u_free(client); |
||||||
|
|
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "msg_transport client disconnected (total=%u)", t->client_count); |
||||||
|
} |
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* Обработка входящих сообщений от TCP-клиента |
||||||
|
* ============================================================================ */ |
||||||
|
|
||||||
|
static void handle_client_data(struct msg_transport* t, struct msg_client* client) { |
||||||
|
while (client->recv_len >= MSG_HDR_SIZE) { |
||||||
|
struct msg_header* hdr = (struct msg_header*)client->recv_buffer; |
||||||
|
|
||||||
|
if (msg_validate_header(hdr) != 0) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "invalid header size=%u type=0x%02x", hdr->size, hdr->type); |
||||||
|
send_error(client, MSG_ERR_INVALID_MSG, "Invalid message header"); |
||||||
|
close_client(t, client); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (client->recv_len < hdr->size) break; |
||||||
|
|
||||||
|
uint8_t* payload = client->recv_buffer + MSG_HDR_SIZE; |
||||||
|
uint16_t payload_size = hdr->size - MSG_HDR_SIZE; |
||||||
|
|
||||||
|
switch (hdr->type) { |
||||||
|
|
||||||
|
case MSG_CTL_BIND: { |
||||||
|
uint64_t peer_id = hdr->id; |
||||||
|
if (peer_id == 0) { |
||||||
|
send_error(client, MSG_ERR_INVALID_MSG, "BIND with zero peer_id"); |
||||||
|
break; |
||||||
|
} |
||||||
|
/* Проверить конфликт: этот peer_id уже занят другим клиентом */ |
||||||
|
struct msg_client* other = t->clients; |
||||||
|
while (other) { |
||||||
|
if (other != client && other->bound_peer_id == peer_id) { |
||||||
|
send_error(client, MSG_ERR_ID_TAKEN, "peer_id already bound by another client"); |
||||||
|
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "BIND peer=%016llx rejected: already bound", |
||||||
|
(unsigned long long)peer_id); |
||||||
|
break; |
||||||
|
} |
||||||
|
other = other->next; |
||||||
|
} |
||||||
|
if (other) break; /* конфликт */ |
||||||
|
client->bound_peer_id = peer_id; |
||||||
|
send_response(client, MSG_RSP_BIND_OK, peer_id, NULL, 0); |
||||||
|
t->counters.bind_count++; |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "BIND peer=%016llx", (unsigned long long)peer_id); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
case MSG_CTL_UNBIND: |
||||||
|
client->bound_peer_id = 0; |
||||||
|
t->counters.unbind_count++; |
||||||
|
DEBUG_INFO(DEBUG_CATEGORY_MSGTRANSPORT, "UNBIND"); |
||||||
|
break; |
||||||
|
|
||||||
|
case MSG_DATA_SEND: { |
||||||
|
uint64_t dst = hdr->id ? hdr->id : client->bound_peer_id; |
||||||
|
if (dst == 0) { |
||||||
|
send_error(client, MSG_ERR_NOT_BOUND, "Not bound to any peer"); |
||||||
|
break; |
||||||
|
} |
||||||
|
if (payload_size == 0) break; |
||||||
|
int is_signed = (hdr->options & MSG_OPT_SIGNED) ? 1 : 0; |
||||||
|
if (forward_to_peer(t, dst, payload, payload_size, is_signed) != 0) { |
||||||
|
send_error(client, MSG_ERR_SEND_FAILED, "Failed to forward message to peer"); |
||||||
|
t->counters.drops_no_route++; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
default: |
||||||
|
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "unknown message type 0x%02x from client", hdr->type); |
||||||
|
send_error(client, MSG_ERR_INVALID_MSG, "Unknown message type"); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
uint16_t msg_size = hdr->size; |
||||||
|
if (client->recv_len > msg_size) |
||||||
|
memmove(client->recv_buffer, client->recv_buffer + msg_size, client->recv_len - msg_size); |
||||||
|
client->recv_len -= msg_size; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* Отправка пиру через etcp_router |
||||||
|
* ============================================================================ */ |
||||||
|
|
||||||
|
static int forward_to_peer(struct msg_transport* t, uint64_t dst_peer_id, |
||||||
|
const uint8_t* data, size_t data_len, int is_signed) { |
||||||
|
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(t->instance, dst_peer_id, t->svc_id); |
||||||
|
if (!rconn) { |
||||||
|
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "forward_to_peer: no route to %016llx", (unsigned long long)dst_peer_id); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
int ret; |
||||||
|
if (is_signed) { |
||||||
|
ret = etcp_router_conn_send_signed(rconn, data, data_len); |
||||||
|
} else { |
||||||
|
ret = etcp_router_conn_send(rconn, data, data_len); |
||||||
|
} |
||||||
|
if (ret != 0) { |
||||||
|
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "forward_to_peer: send failed to %016llx (signed=%d)", |
||||||
|
(unsigned long long)dst_peer_id, is_signed); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
t->counters.msg_sent_to_peer++; |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* Приём от пира через etcp_router → доставка TCP-клиенту |
||||||
|
* ============================================================================ */ |
||||||
|
|
||||||
|
static void on_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry) { |
||||||
|
if (!conn || !entry) return; |
||||||
|
struct UTUN_INSTANCE* inst = conn->instance; |
||||||
|
if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return; } |
||||||
|
|
||||||
|
struct msg_transport* t = inst->msg_t; |
||||||
|
if (!t) { |
||||||
|
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: msg_transport not initialized on instance"); |
||||||
|
queue_dgram_free(entry); queue_entry_free(entry); return; |
||||||
|
} |
||||||
|
|
||||||
|
uint8_t* payload = entry->dgram + 1; |
||||||
|
size_t payload_len = entry->len - 1; |
||||||
|
uint64_t src_node_id = conn->peer_node_id; |
||||||
|
|
||||||
|
DEBUG_TRACE(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: from %016llx len=%zu", |
||||||
|
(unsigned long long)src_node_id, payload_len); |
||||||
|
|
||||||
|
/* Ищем клиента привязанного к этому пиру */ |
||||||
|
struct msg_client* client = t->clients; |
||||||
|
while (client) { |
||||||
|
if (client->bound_peer_id == src_node_id) break; |
||||||
|
client = client->next; |
||||||
|
} |
||||||
|
|
||||||
|
if (!client) { |
||||||
|
DEBUG_WARN(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: no client bound to %016llx, dropping", |
||||||
|
(unsigned long long)src_node_id); |
||||||
|
t->counters.drops_no_client++; |
||||||
|
queue_dgram_free(entry); |
||||||
|
queue_entry_free(entry); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
t->counters.msg_recv_from_peer++; |
||||||
|
|
||||||
|
/* Формируем MSG_DATA_RECV */ |
||||||
|
size_t total = MSG_HDR_SIZE + payload_len; |
||||||
|
uint8_t* buf = u_malloc(total); |
||||||
|
if (!buf) { |
||||||
|
DEBUG_ERROR(DEBUG_CATEGORY_MSGTRANSPORT, "on_etcp_recv: u_malloc failed"); |
||||||
|
queue_dgram_free(entry); queue_entry_free(entry); return; |
||||||
|
} |
||||||
|
struct msg_header* hdr = (struct msg_header*)buf; |
||||||
|
msg_build_header(hdr, (uint16_t)payload_len, MSG_DATA_RECV, src_node_id, 0); |
||||||
|
memcpy(buf + MSG_HDR_SIZE, payload, payload_len); |
||||||
|
|
||||||
|
#ifdef _WIN32 |
||||||
|
send(client->fd, (const char*)buf, (int)total, 0); |
||||||
|
#else |
||||||
|
send(client->fd, buf, total, 0); |
||||||
|
#endif |
||||||
|
u_free(buf); |
||||||
|
t->counters.msg_sent_to_client++; |
||||||
|
|
||||||
|
queue_dgram_free(entry); |
||||||
|
queue_entry_free(entry); |
||||||
|
} |
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* Помощники отправки ответов TCP-клиенту |
||||||
|
* ============================================================================ */ |
||||||
|
|
||||||
|
static void send_response(struct msg_client* client, uint8_t type, uint64_t id, |
||||||
|
const uint8_t* data, size_t data_len) { |
||||||
|
size_t total = MSG_HDR_SIZE + data_len; |
||||||
|
uint8_t* buf = u_malloc(total); |
||||||
|
if (!buf) return; |
||||||
|
|
||||||
|
struct msg_header* hdr = (struct msg_header*)buf; |
||||||
|
msg_build_header(hdr, (uint16_t)data_len, type, id, 0); |
||||||
|
if (data_len > 0) memcpy(buf + MSG_HDR_SIZE, data, data_len); |
||||||
|
|
||||||
|
#ifdef _WIN32 |
||||||
|
send(client->fd, (const char*)buf, (int)total, 0); |
||||||
|
#else |
||||||
|
send(client->fd, buf, total, 0); |
||||||
|
#endif |
||||||
|
u_free(buf); |
||||||
|
} |
||||||
|
|
||||||
|
static void send_error(struct msg_client* client, uint8_t error_code, const char* msg) { |
||||||
|
size_t msg_len = strlen(msg); |
||||||
|
size_t total = MSG_HDR_SIZE + 1 + msg_len; |
||||||
|
uint8_t* buf = u_malloc(total); |
||||||
|
if (!buf) return; |
||||||
|
|
||||||
|
struct msg_header* hdr = (struct msg_header*)buf; |
||||||
|
msg_build_header(hdr, (uint16_t)(1 + msg_len), MSG_RSP_ERROR, 0, 0); |
||||||
|
buf[MSG_HDR_SIZE] = error_code; |
||||||
|
memcpy(buf + MSG_HDR_SIZE + 1, msg, msg_len); |
||||||
|
|
||||||
|
#ifdef _WIN32 |
||||||
|
send(client->fd, (const char*)buf, (int)total, 0); |
||||||
|
#else |
||||||
|
send(client->fd, buf, total, 0); |
||||||
|
#endif |
||||||
|
u_free(buf); |
||||||
|
} |
||||||
@ -0,0 +1,118 @@ |
|||||||
|
/*
|
||||||
|
* msg_transport.h — TCP-сервер транспорта сообщений (local IPC → ETCP) |
||||||
|
* |
||||||
|
* Принимает TCP-соединения от локальных приложений. |
||||||
|
* Каждое соединение привязывается (BIND) к конкретному remote peer node_id. |
||||||
|
* Сообщения от локального клиента пересылаются пиру через etcp_router, |
||||||
|
* сообщения от пира доставляются локальному клиенту. |
||||||
|
* |
||||||
|
* Формат фрейма на TCP: |
||||||
|
* [size:2 (uint16_t, native endian)] [type:1] [id:8 (uint64_t)] [options:1] [data...] |
||||||
|
* size включает само поле size. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef MSG_TRANSPORT_H |
||||||
|
#define MSG_TRANSPORT_H |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include "../lib/socket_compat.h" |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* Протокол |
||||||
|
* ============================================================================ */ |
||||||
|
|
||||||
|
#define MSG_MAX_MSG_SIZE 8192 |
||||||
|
#define MSG_SIGN_SIZE 64 /* Ed25519 */ |
||||||
|
|
||||||
|
/* Options byte bits */ |
||||||
|
#define MSG_OPT_SIGNED 0x01 |
||||||
|
|
||||||
|
/* Типы сообщений */ |
||||||
|
#define MSG_CTL_BIND 0x01 /* GUI→utun: привязать TCP-соединение к пиру id */ |
||||||
|
#define MSG_CTL_UNBIND 0x02 /* GUI→utun: отвязать */ |
||||||
|
#define MSG_DATA_SEND 0x10 /* GUI→utun: отправить сообщение пиру id */ |
||||||
|
#define MSG_DATA_RECV 0x11 /* utun→GUI: получено сообщение от пира id */ |
||||||
|
#define MSG_RSP_BIND_OK 0xE1 /* utun→GUI: BIND успешен */ |
||||||
|
#define MSG_RSP_ERROR 0xFF /* utun→GUI: ошибка */ |
||||||
|
|
||||||
|
/* Коды ошибок для MSG_RSP_ERROR */ |
||||||
|
#define MSG_ERR_INVALID_MSG 0x01 |
||||||
|
#define MSG_ERR_NOT_BOUND 0x02 |
||||||
|
#define MSG_ERR_NO_ROUTE 0x03 |
||||||
|
#define MSG_ERR_SEND_FAILED 0x04 |
||||||
|
#define MSG_ERR_ID_TAKEN 0x06 |
||||||
|
|
||||||
|
#pragma pack(push, 1) |
||||||
|
struct msg_header { |
||||||
|
uint16_t size; /* полный размер сообщения, включая это поле */ |
||||||
|
uint8_t type; /* тип (MSG_CTL_* / MSG_DATA_* / MSG_RSP_*) */ |
||||||
|
uint64_t id; /* peer node_id */ |
||||||
|
uint8_t options; /* битовые флаги (MSG_OPT_*) */ |
||||||
|
}; |
||||||
|
#pragma pack(pop) |
||||||
|
|
||||||
|
#define MSG_HDR_SIZE sizeof(struct msg_header) /* 12 */ |
||||||
|
|
||||||
|
static inline void msg_build_header(struct msg_header* hdr, |
||||||
|
uint16_t payload_size, |
||||||
|
uint8_t msg_type, |
||||||
|
uint64_t id, |
||||||
|
uint8_t options) { |
||||||
|
hdr->size = MSG_HDR_SIZE + payload_size; |
||||||
|
hdr->type = msg_type; |
||||||
|
hdr->id = id; |
||||||
|
hdr->options = options; |
||||||
|
} |
||||||
|
|
||||||
|
static inline int msg_validate_header(const struct msg_header* hdr) { |
||||||
|
if (hdr->size < MSG_HDR_SIZE || hdr->size > MSG_MAX_MSG_SIZE) return -1; |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* Счётчики (диагностика) |
||||||
|
* ============================================================================ */ |
||||||
|
|
||||||
|
struct UTUN_INSTANCE; |
||||||
|
struct UASYNC; |
||||||
|
|
||||||
|
struct msg_transport; |
||||||
|
|
||||||
|
struct msg_transport_counters { |
||||||
|
uint64_t accepted; |
||||||
|
uint64_t closed; |
||||||
|
uint64_t bind_count; |
||||||
|
uint64_t unbind_count; |
||||||
|
uint64_t msg_sent_to_peer; |
||||||
|
uint64_t msg_recv_from_peer; |
||||||
|
uint64_t msg_sent_to_client; |
||||||
|
uint64_t errors; |
||||||
|
uint64_t drops_no_client; |
||||||
|
uint64_t drops_no_route; |
||||||
|
}; |
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* API |
||||||
|
* ============================================================================ */ |
||||||
|
|
||||||
|
int msg_transport_init(struct msg_transport** out, |
||||||
|
struct UTUN_INSTANCE* instance, |
||||||
|
struct UASYNC* ua, |
||||||
|
struct sockaddr_storage* bind_addr, |
||||||
|
uint32_t max_clients, |
||||||
|
uint8_t svc_id); |
||||||
|
|
||||||
|
void msg_transport_shutdown(struct msg_transport* t); |
||||||
|
|
||||||
|
void msg_transport_get_counters(struct msg_transport* t, |
||||||
|
struct msg_transport_counters* c); |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
#endif /* MSG_TRANSPORT_H */ |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
Архитектура чата (группового) |
||||||
|
!! пока мысли - не использовать это как готовое тз |
||||||
|
|
||||||
|
|
||||||
|
1. сообщения чата (текст + ссылки на аттачи) хранятся в таблице: |
||||||
|
id (primary key), timestamp, user_id, msg (utf8 text 4k max), sign |
||||||
|
|
||||||
|
2. реакции: key(msg_id, user_id), type, sign |
||||||
|
3. аттачи: key(msg_id, user_id), filename, sign |
||||||
|
|
||||||
|
Структура каталогов: |
||||||
|
chats.db - база |
||||||
|
<caht_id> |
||||||
@ -0,0 +1,116 @@ |
|||||||
|
#include "db_manager.h" |
||||||
|
#include "../src/debug_ui.h" |
||||||
|
|
||||||
|
DbManager::DbManager(const QString& path, QObject* parent) |
||||||
|
: QObject(parent) |
||||||
|
{ |
||||||
|
int rc = sqlite3_open(path.toUtf8().constData(), &m_db); |
||||||
|
if (rc != SQLITE_OK) { |
||||||
|
GUI_ERROR("Cannot open database: %s", sqlite3_errmsg(m_db)); |
||||||
|
sqlite3_close(m_db); |
||||||
|
m_db = nullptr; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
sqlite3_exec(m_db, "PRAGMA journal_mode=WAL", nullptr, nullptr, nullptr); |
||||||
|
|
||||||
|
initTables(); |
||||||
|
|
||||||
|
GUI_INFO("Database opened: %s", path.toUtf8().constData()); |
||||||
|
} |
||||||
|
|
||||||
|
DbManager::~DbManager() { |
||||||
|
if (m_db) { |
||||||
|
sqlite3_close(m_db); |
||||||
|
m_db = nullptr; |
||||||
|
GUI_INFO("Database closed"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void DbManager::initTables() { |
||||||
|
sqlite3_exec(m_db, "PRAGMA foreign_keys = ON", nullptr, nullptr, nullptr); |
||||||
|
|
||||||
|
const char* sql = |
||||||
|
"CREATE TABLE IF NOT EXISTS local_identity (" |
||||||
|
" id INTEGER PRIMARY KEY CHECK (id = 1)," |
||||||
|
" node_id INTEGER NOT NULL UNIQUE," |
||||||
|
" name TEXT NOT NULL," |
||||||
|
" x25519_pubkey BLOB NOT NULL," |
||||||
|
" ed25519_pubkey BLOB," |
||||||
|
" created_at TEXT DEFAULT (datetime('now'))," |
||||||
|
" updated_at TEXT DEFAULT (datetime('now'))" |
||||||
|
");" |
||||||
|
|
||||||
|
"CREATE TABLE IF NOT EXISTS nodes (" |
||||||
|
" node_id INTEGER PRIMARY KEY," |
||||||
|
" name TEXT," |
||||||
|
" x25519_pubkey BLOB," |
||||||
|
" ed25519_pubkey BLOB," |
||||||
|
" hop_count INTEGER DEFAULT 0," |
||||||
|
" is_online INTEGER DEFAULT 0," |
||||||
|
" last_seen_ver INTEGER DEFAULT 0," |
||||||
|
" connectivity_json TEXT," |
||||||
|
" created_at TEXT DEFAULT (datetime('now'))," |
||||||
|
" updated_at TEXT DEFAULT (datetime('now'))" |
||||||
|
");" |
||||||
|
|
||||||
|
"CREATE TABLE IF NOT EXISTS node_addresses (" |
||||||
|
" id INTEGER PRIMARY KEY AUTOINCREMENT," |
||||||
|
" node_id INTEGER NOT NULL REFERENCES nodes(node_id) ON DELETE CASCADE," |
||||||
|
" family INTEGER NOT NULL DEFAULT 4," |
||||||
|
" ip BLOB NOT NULL," |
||||||
|
" port INTEGER NOT NULL," |
||||||
|
" addr_type INTEGER NOT NULL DEFAULT 0," |
||||||
|
" socket_type INTEGER DEFAULT 0," |
||||||
|
" priority INTEGER DEFAULT 0" |
||||||
|
");" |
||||||
|
"CREATE INDEX IF NOT EXISTS idx_node_addr_node ON node_addresses(node_id);" |
||||||
|
|
||||||
|
"CREATE TABLE IF NOT EXISTS node_subnets (" |
||||||
|
" id INTEGER PRIMARY KEY AUTOINCREMENT," |
||||||
|
" node_id INTEGER NOT NULL REFERENCES nodes(node_id) ON DELETE CASCADE," |
||||||
|
" family INTEGER NOT NULL DEFAULT 4," |
||||||
|
" network BLOB NOT NULL," |
||||||
|
" prefix_length INTEGER NOT NULL" |
||||||
|
");" |
||||||
|
"CREATE INDEX IF NOT EXISTS idx_subnet_node ON node_subnets(node_id);" |
||||||
|
|
||||||
|
"CREATE TABLE IF NOT EXISTS channels (" |
||||||
|
" id INTEGER PRIMARY KEY AUTOINCREMENT," |
||||||
|
" channel_id TEXT NOT NULL UNIQUE," |
||||||
|
" name TEXT NOT NULL," |
||||||
|
" member_ids_json TEXT NOT NULL," |
||||||
|
" is_dm INTEGER DEFAULT 0," |
||||||
|
" last_message TEXT," |
||||||
|
" last_msg_at TEXT," |
||||||
|
" created_at TEXT DEFAULT (datetime('now'))" |
||||||
|
");" |
||||||
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_channel_cid ON channels(channel_id);" |
||||||
|
|
||||||
|
"CREATE TABLE IF NOT EXISTS messages (" |
||||||
|
" id INTEGER PRIMARY KEY AUTOINCREMENT," |
||||||
|
" channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE," |
||||||
|
" author_node_id INTEGER NOT NULL," |
||||||
|
" content TEXT NOT NULL," |
||||||
|
" content_type TEXT DEFAULT 'text/plain'," |
||||||
|
" timestamp INTEGER NOT NULL," |
||||||
|
" local_seq INTEGER DEFAULT 0," |
||||||
|
" signature BLOB," |
||||||
|
" is_outgoing INTEGER DEFAULT 0," |
||||||
|
" is_read INTEGER DEFAULT 0," |
||||||
|
" created_at TEXT DEFAULT (datetime('now'))" |
||||||
|
");" |
||||||
|
"CREATE INDEX IF NOT EXISTS idx_msg_channel_time ON messages(channel_id, timestamp);" |
||||||
|
"CREATE INDEX IF NOT EXISTS idx_msg_author ON messages(author_node_id);" |
||||||
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_msg_dedup ON messages(channel_id, author_node_id, timestamp);"; |
||||||
|
|
||||||
|
char* errMsg = nullptr; |
||||||
|
int rc = sqlite3_exec(m_db, sql, nullptr, nullptr, &errMsg); |
||||||
|
if (rc != SQLITE_OK) { |
||||||
|
GUI_ERROR("Failed to create tables: %s", errMsg); |
||||||
|
sqlite3_free(errMsg); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
GUI_INFO("Database tables initialized (6 tables, 6 indexes)"); |
||||||
|
} |
||||||
@ -0,0 +1,23 @@ |
|||||||
|
#ifndef DB_MANAGER_H |
||||||
|
#define DB_MANAGER_H |
||||||
|
|
||||||
|
#include <sqlite3.h> |
||||||
|
#include <QString> |
||||||
|
#include <QObject> |
||||||
|
|
||||||
|
class DbManager : public QObject { |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
explicit DbManager(const QString& path, QObject* parent = nullptr); |
||||||
|
~DbManager() override; |
||||||
|
|
||||||
|
bool isOpen() const { return m_db != nullptr; } |
||||||
|
sqlite3* handle() { return m_db; } |
||||||
|
|
||||||
|
private: |
||||||
|
void initTables(); |
||||||
|
|
||||||
|
sqlite3* m_db = nullptr; |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
||||||
@ -0,0 +1,96 @@ |
|||||||
|
# База данных децентрализованного чата (chatgui) |
||||||
|
|
||||||
|
## Архитектура хранения |
||||||
|
|
||||||
|
Чат **децентрализованный** — нет центрального сервера. Каждый узел (нода uTun) хранит свою |
||||||
|
**локальную SQLite БД**. В ней: |
||||||
|
|
||||||
|
- **Информация о узлах (и о себе)** (как подключиться: адреса, порты, ключи — всё из NODEINFO). плюс nickname. - и список узлов = список участников. каждый узел имеет parent node (дерево). |
||||||
|
- **История сообщений** — |
||||||
|
|
||||||
|
Данные о других узлах поступают из NODEINFO gossip-протокола uTun и синхронизируются в фоне. |
||||||
|
Сообщения приходят через `msg_transport` (TCP, локальный IPC). |
||||||
|
аттачи - загружают и кешируют узлы в соответствии со своими настройками. |
||||||
|
|
||||||
|
## Структура таблиц SQLite |
||||||
|
|
||||||
|
### `local_identity` — наш собственный узел (точнее все мои устройства) |
||||||
|
между моими устройствами синхронизируется контент - список чатов и сообщения. |
||||||
|
|
||||||
|
```sql |
||||||
|
CREATE TABLE IF NOT EXISTS nodes ( |
||||||
|
id INTEGER PRIMARY KEY, |
||||||
|
node_id INTEGER NOT NULL UNIQUE, // id из utun |
||||||
|
name TEXT NOT NULL, // ник |
||||||
|
last_seen INTEGER DEFAULT 0, |
||||||
|
online_rating INTEGER DEFAULT 0, // рейттинг связности с узлом. чем больше число тем чаще узел онлайн и с ним хорошая связь |
||||||
|
speed_rating INTEGER DEFAULT 0, // рейтинг скорости обмена с узлом |
||||||
|
rtt_rating INTEGER DEFAULT 0, // rtt до узла (через промежуточные узлы если есть) |
||||||
|
connectivity_json TEXT, // как связаться с узлом |
||||||
|
parent_node_id INTEGER DEFAULT NULL, // каждая нода должна иметь родителя (кроме корневой) |
||||||
|
main_node_id INTEGER DEFAULT NULL, // если несколько устройств у клиента, id главной ноды |
||||||
|
created_at TEXT DEFAULT (datetime('now')), |
||||||
|
); |
||||||
|
``` |
||||||
|
### `channels` — чаты (группы узлов) |
||||||
|
|
||||||
|
```sql |
||||||
|
CREATE TABLE IF NOT EXISTS channels ( |
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT, |
||||||
|
name TEXT NOT NULL, |
||||||
|
last_msg_at TEXT, |
||||||
|
); |
||||||
|
``` |
||||||
|
|
||||||
|
### `messages` — список пользователей в чате |
||||||
|
|
||||||
|
```sql |
||||||
|
CREATE TABLE IF NOT EXISTS channel_members ( |
||||||
|
channel_id INTEGER, |
||||||
|
node_id INTEGER, // если несколько устройств то ноды всех стройств |
||||||
|
); |
||||||
|
``` |
||||||
|
|
||||||
|
### `messages` — история сообщений (для каждой группы создаем новую таблицу) |
||||||
|
|
||||||
|
```sql |
||||||
|
CREATE TABLE IF NOT EXISTS messages_ch<n> ( |
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT, |
||||||
|
channel_id TEXT NOT NULL REFERENCES channels(channel_id) ON DELETE CASCADE, |
||||||
|
author_node_id INTEGER NOT NULL, |
||||||
|
content TEXT NOT NULL, |
||||||
|
content_type TEXT DEFAULT 'text/plain', |
||||||
|
timestamp INTEGER NOT NULL, |
||||||
|
local_seq INTEGER DEFAULT 0, |
||||||
|
signature BLOB, |
||||||
|
is_outgoing INTEGER DEFAULT 0, |
||||||
|
is_read INTEGER DEFAULT 0, |
||||||
|
created_at TEXT DEFAULT (datetime('now')) |
||||||
|
); |
||||||
|
CREATE INDEX IF NOT EXISTS idx_msg_channel_time ON messages(channel_id, timestamp); |
||||||
|
CREATE INDEX IF NOT EXISTS idx_msg_author ON messages(author_node_id); |
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_msg_dedup ON messages(channel_id, author_node_id, timestamp); |
||||||
|
``` |
||||||
|
|
||||||
|
**Пояснение:** каждое сообщение привязано к каналу. `author_node_id` — кто автор |
||||||
|
(из `nodes.node_id`). `timestamp` — unix время в миллисекундах *по часам отправителя* |
||||||
|
(в децентрализованной системе нет глобальных часов, но для порядка в UI достаточно). |
||||||
|
`local_seq` — монотонно возрастающий номер в пределах канала для детерминированной |
||||||
|
сортировки при одинаковых timestamp. |
||||||
|
|
||||||
|
**Дедупликация:** сообщение идентифицируется тройкой `(channel_id, author_node_id, |
||||||
|
timestamp)`. Уникальный индекс `idx_msg_dedup` предотвращает дубликаты при |
||||||
|
повторной доставке через разные пути. |
||||||
|
|
||||||
|
| Поле | Тип | Описание | |
||||||
|
|---|---|---| |
||||||
|
| `channel_id` | TEXT | Ссылка на канал | |
||||||
|
| `author_node_id` | INTEGER | Кто отправил (node_id) | |
||||||
|
| `content` | TEXT | Текст сообщения | |
||||||
|
| `content_type` | TEXT | MIME-тип: `text/plain`, в будущем `image/png` и т.д. | |
||||||
|
| `timestamp` | INTEGER | Unix timestamp (мс) — когда отправлено | |
||||||
|
| `local_seq` | INTEGER | Локальный порядковый номер в канале | |
||||||
|
| `signature` | BLOB | Ed25519 подпись (64 байта), NULL если без подписи | |
||||||
|
| `is_outgoing` | INTEGER | 1 = отправлено нами | |
||||||
|
| `is_read` | INTEGER | 1 = прочитано (для галочек в UI) | |
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,9 @@ |
|||||||
|
<RCC> |
||||||
|
<qresource prefix="/"> |
||||||
|
<file>animations/sparkles_emoji.tgs</file> |
||||||
|
<file>animations/white_flag_emoji.tgs</file> |
||||||
|
<file>animations/greeting.tgs</file> |
||||||
|
<file>animations/stop.tgs</file> |
||||||
|
<file>animations/robot.tgs</file> |
||||||
|
</qresource> |
||||||
|
</RCC> |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
#include "animtimer.h" |
||||||
|
#include "lottieicon.h" |
||||||
|
|
||||||
|
AnimTimer::AnimTimer() { |
||||||
|
m_timer.setInterval(33); |
||||||
|
connect(&m_timer, &QTimer::timeout, this, &AnimTimer::onTimer); |
||||||
|
m_timer.start(); |
||||||
|
} |
||||||
|
|
||||||
|
AnimTimer *AnimTimer::instance() { |
||||||
|
static AnimTimer s; |
||||||
|
return &s; |
||||||
|
} |
||||||
|
|
||||||
|
void AnimTimer::registerIcon(const QString &key, LottieIcon *icon) { |
||||||
|
m_icons.insert(key, icon); |
||||||
|
} |
||||||
|
|
||||||
|
void AnimTimer::unregisterIcon(const QString &key) { |
||||||
|
m_icons.remove(key); |
||||||
|
} |
||||||
|
|
||||||
|
LottieIcon *AnimTimer::icon(const QString &key) const { |
||||||
|
return m_icons.value(key, nullptr); |
||||||
|
} |
||||||
|
|
||||||
|
void AnimTimer::activate() { |
||||||
|
if (m_activeRef++ == 0) { |
||||||
|
for (auto *icon : m_icons) |
||||||
|
icon->advanceFrame(); |
||||||
|
emit ticked(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void AnimTimer::deactivate() { |
||||||
|
if (m_activeRef > 0) m_activeRef--; |
||||||
|
} |
||||||
|
|
||||||
|
void AnimTimer::onTimer() { |
||||||
|
if (m_activeRef <= 0) return; |
||||||
|
for (auto *icon : m_icons) |
||||||
|
icon->advanceFrame(); |
||||||
|
emit ticked(); |
||||||
|
} |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <QObject> |
||||||
|
#include <QTimer> |
||||||
|
#include <QHash> |
||||||
|
|
||||||
|
class LottieIcon; |
||||||
|
|
||||||
|
class AnimTimer : public QObject { |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
static AnimTimer *instance(); |
||||||
|
|
||||||
|
void registerIcon(const QString &key, LottieIcon *icon); |
||||||
|
void unregisterIcon(const QString &key); |
||||||
|
LottieIcon *icon(const QString &key) const; |
||||||
|
|
||||||
|
void activate(); |
||||||
|
void deactivate(); |
||||||
|
|
||||||
|
signals: |
||||||
|
void ticked(); |
||||||
|
|
||||||
|
private slots: |
||||||
|
void onTimer(); |
||||||
|
|
||||||
|
private: |
||||||
|
AnimTimer(); |
||||||
|
QTimer m_timer; |
||||||
|
QHash<QString, LottieIcon *> m_icons; |
||||||
|
int m_activeRef = 0; |
||||||
|
}; |
||||||
@ -0,0 +1,46 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#if !defined(NDEBUG) && !defined(GUI_DEBUG_DISABLE) |
||||||
|
|
||||||
|
#include <cstdio> |
||||||
|
#include <cstdarg> |
||||||
|
|
||||||
|
namespace GuiDebugPriv { |
||||||
|
inline FILE *s_file = stderr; |
||||||
|
|
||||||
|
inline void setFile(const char *path) { |
||||||
|
if (s_file && s_file != stderr) fclose(s_file); |
||||||
|
s_file = path ? fopen(path, "w") : nullptr; |
||||||
|
if (!s_file) s_file = stderr; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
inline void guiDebugFile(const char *path) { GuiDebugPriv::setFile(path); } |
||||||
|
|
||||||
|
inline void guiDebugLog(char lvl, const char *file, int line, |
||||||
|
const char *func, const char *fmt, ...) { |
||||||
|
const char *base = strrchr(file, '/'); |
||||||
|
if (!base) base = strrchr(file, '\\'); |
||||||
|
fprintf(GuiDebugPriv::s_file, "[%c] %s:%d %s(): ", |
||||||
|
lvl, base ? base + 1 : file, line, func); |
||||||
|
va_list ap; |
||||||
|
va_start(ap, fmt); |
||||||
|
vfprintf(GuiDebugPriv::s_file, fmt, ap); |
||||||
|
va_end(ap); |
||||||
|
fprintf(GuiDebugPriv::s_file, "\n"); |
||||||
|
fflush(GuiDebugPriv::s_file); |
||||||
|
} |
||||||
|
|
||||||
|
#define GUI_ERROR(fmt, ...) guiDebugLog('E', __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__) |
||||||
|
#define GUI_WARN(fmt, ...) guiDebugLog('W', __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__) |
||||||
|
#define GUI_INFO(fmt, ...) guiDebugLog('I', __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__) |
||||||
|
#define GUI_DEBUG(fmt, ...) guiDebugLog('D', __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__) |
||||||
|
|
||||||
|
#else |
||||||
|
|
||||||
|
#define GUI_ERROR(...) ((void)0) |
||||||
|
#define GUI_WARN(...) ((void)0) |
||||||
|
#define GUI_INFO(...) ((void)0) |
||||||
|
#define GUI_DEBUG(...) ((void)0) |
||||||
|
|
||||||
|
#endif |
||||||
@ -0,0 +1,72 @@ |
|||||||
|
#include "lottieicon.h" |
||||||
|
#include <rlottie_capi.h> |
||||||
|
#include <zlib.h> |
||||||
|
#include <cstring> |
||||||
|
|
||||||
|
static QByteArray unpackGzip(const QByteArray &data) { |
||||||
|
if (data.isEmpty()) return {}; |
||||||
|
z_stream strm = {}; |
||||||
|
int ret = inflateInit2(&strm, 16 + MAX_WBITS); |
||||||
|
if (ret != Z_OK) return {}; |
||||||
|
strm.avail_in = (uInt)data.size(); |
||||||
|
strm.next_in = (Bytef *)data.constData(); |
||||||
|
QByteArray result; |
||||||
|
result.resize(data.size() * 3); |
||||||
|
size_t total = 0; |
||||||
|
do { |
||||||
|
size_t remain = (size_t)result.size() - total; |
||||||
|
if (remain < 4096) { |
||||||
|
result.resize(result.size() * 2); |
||||||
|
remain = (size_t)result.size() - total; |
||||||
|
} |
||||||
|
strm.avail_out = (uInt)remain; |
||||||
|
strm.next_out = (Bytef *)result.data() + total; |
||||||
|
ret = inflate(&strm, Z_NO_FLUSH); |
||||||
|
if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR) { |
||||||
|
inflateEnd(&strm); |
||||||
|
return {}; |
||||||
|
} |
||||||
|
total += remain - strm.avail_out; |
||||||
|
} while (ret != Z_STREAM_END); |
||||||
|
inflateEnd(&strm); |
||||||
|
result.resize((int)total); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
LottieIcon::LottieIcon(const QByteArray &tgsData) { |
||||||
|
QByteArray json = unpackGzip(tgsData); |
||||||
|
if (json.isEmpty()) return; |
||||||
|
|
||||||
|
m_jsonData = json; |
||||||
|
m_anim = lottie_animation_from_data(m_jsonData.constData(), "", ""); |
||||||
|
if (!m_anim) return; |
||||||
|
|
||||||
|
lottie_animation_get_size(m_anim, &m_w, &m_h); |
||||||
|
m_totalFrames = (int)lottie_animation_get_totalframe(m_anim); |
||||||
|
m_frameRate = lottie_animation_get_framerate(m_anim); |
||||||
|
if (m_totalFrames <= 0) m_totalFrames = 1; |
||||||
|
m_valid = true; |
||||||
|
} |
||||||
|
|
||||||
|
LottieIcon::~LottieIcon() { |
||||||
|
if (m_anim) lottie_animation_destroy(m_anim); |
||||||
|
} |
||||||
|
|
||||||
|
QImage LottieIcon::renderFrame(int frameNum, QSize targetSize) const { |
||||||
|
if (!m_valid || targetSize.width() <= 0 || targetSize.height() <= 0) |
||||||
|
return QImage(); |
||||||
|
|
||||||
|
int w = targetSize.width(); |
||||||
|
int h = targetSize.height(); |
||||||
|
size_t stride = (size_t)w * 4; |
||||||
|
std::vector<uint32_t> buffer((size_t)w * (size_t)h); |
||||||
|
|
||||||
|
int frame = frameNum % m_totalFrames; |
||||||
|
lottie_animation_render(m_anim, (size_t)frame, buffer.data(), |
||||||
|
(size_t)w, (size_t)h, stride); |
||||||
|
|
||||||
|
QImage img(w, h, QImage::Format_ARGB32_Premultiplied); |
||||||
|
for (int y = 0; y < h; y++) |
||||||
|
std::memcpy(img.scanLine(y), &buffer[(size_t)y * (size_t)w], stride); |
||||||
|
return img; |
||||||
|
} |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <QImage> |
||||||
|
#include <QByteArray> |
||||||
|
#include <QSize> |
||||||
|
#include <cstdint> |
||||||
|
|
||||||
|
class LottieIcon { |
||||||
|
public: |
||||||
|
explicit LottieIcon(const QByteArray &tgsData); |
||||||
|
~LottieIcon(); |
||||||
|
|
||||||
|
bool isValid() const { return m_valid; } |
||||||
|
QSize originalSize() const { return QSize((int)m_w, (int)m_h); } |
||||||
|
int frameCount() const { return m_totalFrames; } |
||||||
|
double frameRate() const { return m_frameRate; } |
||||||
|
int currentFrame() const { return m_currentFrame; } |
||||||
|
|
||||||
|
QImage renderFrame(int frameNum, QSize targetSize) const; |
||||||
|
void advanceFrame() { m_currentFrame = (m_currentFrame + 1) % m_totalFrames; } |
||||||
|
|
||||||
|
private: |
||||||
|
bool m_valid = false; |
||||||
|
struct Lottie_Animation_S *m_anim = nullptr; |
||||||
|
int m_currentFrame = 0; |
||||||
|
int m_totalFrames = 0; |
||||||
|
double m_frameRate = 60.0; |
||||||
|
size_t m_w = 0, m_h = 0; |
||||||
|
QByteArray m_jsonData; |
||||||
|
}; |
||||||
@ -0,0 +1,143 @@ |
|||||||
|
/*
|
||||||
|
* msg_client.cpp — C++ транспортный клиент |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "msg_client.h" |
||||||
|
#include <cstring> |
||||||
|
|
||||||
|
MsgClient::MsgClient(QObject* parent) : QObject(parent), m_socket(new QTcpSocket(this)) { |
||||||
|
QObject::connect(m_socket, &QTcpSocket::connected, this, &MsgClient::connected); |
||||||
|
QObject::connect(m_socket, &QTcpSocket::disconnected, this, [this]() { |
||||||
|
if (m_boundPeerId) { |
||||||
|
m_boundPeerId = 0; |
||||||
|
emit unbound(); |
||||||
|
} |
||||||
|
emit disconnected(); |
||||||
|
}); |
||||||
|
QObject::connect(m_socket, &QTcpSocket::readyRead, this, &MsgClient::onReadyRead); |
||||||
|
QObject::connect(m_socket, &QAbstractSocket::errorOccurred, this, &MsgClient::onSocketError); |
||||||
|
} |
||||||
|
|
||||||
|
MsgClient::~MsgClient() { |
||||||
|
disconnectFromHost(); |
||||||
|
} |
||||||
|
|
||||||
|
void MsgClient::connectToHost(const QString& host, quint16 port) { |
||||||
|
m_recvBuffer.clear(); |
||||||
|
m_boundPeerId = 0; |
||||||
|
m_socket->connectToHost(host, port); |
||||||
|
} |
||||||
|
|
||||||
|
void MsgClient::disconnectFromHost() { |
||||||
|
if (m_socket->state() != QAbstractSocket::UnconnectedState) |
||||||
|
m_socket->disconnectFromHost(); |
||||||
|
m_recvBuffer.clear(); |
||||||
|
if (m_boundPeerId) { |
||||||
|
m_boundPeerId = 0; |
||||||
|
emit unbound(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
bool MsgClient::isConnected() const { |
||||||
|
return m_socket->state() == QAbstractSocket::ConnectedState; |
||||||
|
} |
||||||
|
|
||||||
|
void MsgClient::bind(quint64 peerId) { |
||||||
|
QByteArray msg = buildMessage(kCtlBind, peerId, 0, QByteArray()); |
||||||
|
m_socket->write(msg); |
||||||
|
} |
||||||
|
|
||||||
|
void MsgClient::unbind() { |
||||||
|
if (!m_boundPeerId) return; |
||||||
|
QByteArray msg = buildMessage(kCtlUnbind, 0, 0, QByteArray()); |
||||||
|
m_socket->write(msg); |
||||||
|
m_boundPeerId = 0; |
||||||
|
emit unbound(); |
||||||
|
} |
||||||
|
|
||||||
|
void MsgClient::send(const QByteArray& data) { |
||||||
|
if (!m_boundPeerId) return; |
||||||
|
QByteArray msg = buildMessage(kDataSend, m_boundPeerId, 0, data); |
||||||
|
m_socket->write(msg); |
||||||
|
} |
||||||
|
|
||||||
|
void MsgClient::send(const uint8_t* data, size_t len) { |
||||||
|
send(QByteArray(reinterpret_cast<const char*>(data), static_cast<int>(len))); |
||||||
|
} |
||||||
|
|
||||||
|
void MsgClient::sendSigned(const QByteArray& data) { |
||||||
|
if (!m_boundPeerId) return; |
||||||
|
QByteArray msg = buildMessage(kDataSend, m_boundPeerId, kOptSigned, data); |
||||||
|
m_socket->write(msg); |
||||||
|
} |
||||||
|
|
||||||
|
/* ============================================================================
|
||||||
|
* Внутренние |
||||||
|
* ============================================================================ */ |
||||||
|
|
||||||
|
void MsgClient::onReadyRead() { |
||||||
|
m_recvBuffer.append(m_socket->readAll()); |
||||||
|
if (m_recvBuffer.size() > static_cast<int>(kMaxMsgSize)) { |
||||||
|
m_recvBuffer.clear(); |
||||||
|
emit errorOccurred(0xFF, "recv buffer overflow"); |
||||||
|
return; |
||||||
|
} |
||||||
|
processMessages(); |
||||||
|
} |
||||||
|
|
||||||
|
void MsgClient::onSocketError(QAbstractSocket::SocketError) { |
||||||
|
emit errorOccurred(0xFF, m_socket->errorString()); |
||||||
|
} |
||||||
|
|
||||||
|
void MsgClient::processMessages() { |
||||||
|
while (static_cast<size_t>(m_recvBuffer.size()) >= kHdrSize) { |
||||||
|
uint16_t totalSize; |
||||||
|
std::memcpy(&totalSize, m_recvBuffer.constData(), 2); |
||||||
|
if (totalSize < kHdrSize || totalSize > kMaxMsgSize) { |
||||||
|
m_recvBuffer.clear(); |
||||||
|
emit errorOccurred(0xFF, "invalid message size"); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (m_recvBuffer.size() < totalSize) break; |
||||||
|
|
||||||
|
uint8_t type = static_cast<uint8_t>(m_recvBuffer[2]); |
||||||
|
uint64_t id; |
||||||
|
std::memcpy(&id, m_recvBuffer.constData() + 3, 8); |
||||||
|
QByteArray payload = m_recvBuffer.mid(static_cast<int>(kHdrSize), |
||||||
|
totalSize - static_cast<uint16_t>(kHdrSize)); |
||||||
|
|
||||||
|
dispatch(type, id, payload); |
||||||
|
|
||||||
|
m_recvBuffer.remove(0, totalSize); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void MsgClient::dispatch(uint8_t type, quint64 id, const QByteArray& payload) { |
||||||
|
switch (type) { |
||||||
|
case kRspBindOk: |
||||||
|
m_boundPeerId = id; |
||||||
|
emit bound(id); |
||||||
|
break; |
||||||
|
case kDataRecv: |
||||||
|
emit received(id, payload); |
||||||
|
break; |
||||||
|
case kRspError: |
||||||
|
uint8_t code = payload.size() > 0 ? static_cast<uint8_t>(payload[0]) : 0; |
||||||
|
QString msg = QString::fromUtf8(payload.mid(1)); |
||||||
|
emit errorOccurred(code, msg); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
QByteArray MsgClient::buildMessage(uint8_t type, quint64 id, uint8_t options, const QByteArray& data) { |
||||||
|
uint16_t totalSize = static_cast<uint16_t>(kHdrSize + data.size()); |
||||||
|
QByteArray msg; |
||||||
|
msg.resize(totalSize); |
||||||
|
std::memcpy(msg.data(), &totalSize, 2); |
||||||
|
msg[2] = static_cast<char>(type); |
||||||
|
std::memcpy(msg.data() + 3, &id, 8); |
||||||
|
msg[11] = static_cast<char>(options); |
||||||
|
if (!data.isEmpty()) |
||||||
|
std::memcpy(msg.data() + kHdrSize, data.constData(), data.size()); |
||||||
|
return msg; |
||||||
|
} |
||||||
@ -0,0 +1,65 @@ |
|||||||
|
/*
|
||||||
|
* msg_client.h — C++ транспортный клиент для chat GUI |
||||||
|
* |
||||||
|
* Подключается к utun msg_transport по localhost TCP. |
||||||
|
* Протокол: [size:2][type:1][id:8][options:1][data...] (msg_transport.h). |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef MSG_CLIENT_H |
||||||
|
#define MSG_CLIENT_H |
||||||
|
|
||||||
|
#include <QObject> |
||||||
|
#include <QByteArray> |
||||||
|
#include <QTcpSocket> |
||||||
|
#include <cstdint> |
||||||
|
|
||||||
|
class MsgClient : public QObject { |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
explicit MsgClient(QObject* parent = nullptr); |
||||||
|
~MsgClient() override; |
||||||
|
|
||||||
|
void connectToHost(const QString& host, quint16 port); |
||||||
|
void disconnectFromHost(); |
||||||
|
bool isConnected() const; |
||||||
|
|
||||||
|
void bind(quint64 peerId); |
||||||
|
void unbind(); |
||||||
|
bool isBound() const { return m_boundPeerId != 0; } |
||||||
|
quint64 boundPeerId() const { return m_boundPeerId; } |
||||||
|
|
||||||
|
void send(const QByteArray& data); |
||||||
|
void send(const uint8_t* data, size_t len); |
||||||
|
void sendSigned(const QByteArray& data); |
||||||
|
|
||||||
|
signals: |
||||||
|
void connected(); |
||||||
|
void disconnected(); |
||||||
|
void bound(quint64 peerId); |
||||||
|
void unbound(); |
||||||
|
void received(quint64 peerId, const QByteArray& data); |
||||||
|
void errorOccurred(uint8_t code, const QString& message); |
||||||
|
|
||||||
|
private: |
||||||
|
static constexpr size_t kMaxMsgSize = 8192; |
||||||
|
static constexpr uint8_t kCtlBind = 0x01; |
||||||
|
static constexpr uint8_t kCtlUnbind = 0x02; |
||||||
|
static constexpr uint8_t kDataSend = 0x10; |
||||||
|
static constexpr uint8_t kDataRecv = 0x11; |
||||||
|
static constexpr uint8_t kRspBindOk = 0xE1; |
||||||
|
static constexpr uint8_t kRspError = 0xFF; |
||||||
|
static constexpr uint8_t kOptSigned = 0x01; |
||||||
|
static constexpr size_t kHdrSize = 12; |
||||||
|
|
||||||
|
QTcpSocket* m_socket; |
||||||
|
QByteArray m_recvBuffer; |
||||||
|
quint64 m_boundPeerId = 0; |
||||||
|
|
||||||
|
void onReadyRead(); |
||||||
|
void onSocketError(QAbstractSocket::SocketError); |
||||||
|
void processMessages(); |
||||||
|
void dispatch(uint8_t type, quint64 id, const QByteArray& payload); |
||||||
|
QByteArray buildMessage(uint8_t type, quint64 id, uint8_t options, const QByteArray& data); |
||||||
|
}; |
||||||
|
|
||||||
|
#endif // MSG_CLIENT_H
|
||||||
Loading…
Reference in new issue