pax_global_header00006660000000000000000000000064132113132400014500gustar00rootroot0000000000000052 comment=1c970b07147908fd2351577f09298fa82601f7a2 i2pd-2.17.0/000077500000000000000000000000001321131324000124255ustar00rootroot00000000000000i2pd-2.17.0/.dir-locals.el000066400000000000000000000001031321131324000150500ustar00rootroot00000000000000((c++-mode . ((indent-tabs-mode . t))) (c-mode . ((mode . c++)))) i2pd-2.17.0/.gitignore000066400000000000000000000060531321131324000144210ustar00rootroot00000000000000# i2pd *.o router.info router.keys i2p libi2pd.so netDb /i2pd /libi2pd.a /libi2pdclient.a i2pd.exe # Autotools autom4te.cache .deps stamp-h1 #Makefile config.h config.h.in~ config.log config.status config.sub ################# ## Eclipse ################# *.pydevproject .project .metadata bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .classpath .settings/ .loadpath # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # CDT-specific .cproject # PDT-specific .buildpath ################# ## Visual Studio ################# ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.sln.docstates # Build results [Dd]ebug/ [Rr]elease/ x64/ [Bb]in/ [Oo]bj/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* *_i.c *_p.c *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.log *.scc # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch *.ncrunch* .*crunch*.local.xml # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.Publish.xml *.pubxml # NuGet Packages Directory ## TODO: If you have NuGet Package Restore enabled, uncomment the next line #packages/ # Windows Azure Build Output csx *.build.csdef # Windows Store app package directory AppPackages/ # Others sql/ *.Cache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.[Pp]ublish.xml *.pfx *.publishsettings # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file to a newer # Visual Studio version. Backup files are not needed, because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files App_Data/*.mdf App_Data/*.ldf ############# ## Windows detritus ############# # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Mac crap .DS_Store ############# ## Python ############# *.py[co] # Packages *.egg *.egg-info dist/ eggs/ parts/ var/ sdist/ develop-eggs/ .installed.cfg # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox #Translations *.mo #Mr Developer .mr.developer.cfg # Sphinx docs/_build /androidIdea/ # Doxygen docs/generated # emacs files *~ *\#* # gdb files .gdb_history # cmake makefile build/Makefile # debian stuff .pc/ # qt qt/i2pd_qt/*.ui.autosave qt/i2pd_qt/*.ui.bk* qt/i2pd_qt/*.ui_* #unknown android stuff android/libs/ i2pd-2.17.0/.travis.yml000066400000000000000000000027641321131324000145470ustar00rootroot00000000000000language: cpp cache: apt: true os: - linux - osx dist: trusty sudo: required compiler: - g++ - clang++ env: global: - MAKEFLAGS="-j 2" matrix: - BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes - BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no - BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes - BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no matrix: exclude: - os: osx env: BUILD_TYPE=cmake UPNP=ON MAKE_UPNP=yes - os: osx env: BUILD_TYPE=cmake UPNP=OFF MAKE_UPNP=no - os: linux compiler: clang++ env: BUILD_TYPE=make UPNP=ON MAKE_UPNP=yes - os: linux compiler: clang++ env: BUILD_TYPE=make UPNP=OFF MAKE_UPNP=no addons: apt: packages: - build-essential - cmake - g++ - clang - libboost-chrono-dev - libboost-date-time-dev - libboost-filesystem-dev - libboost-program-options-dev - libboost-system-dev - libboost-thread-dev - libminiupnpc-dev - libssl-dev before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install libressl miniupnpc ; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated boost || brew upgrade boost ; fi script: - if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "cmake" ]]; then cd build && cmake -DCMAKE_BUILD_TYPE=Release -DWITH_UPNP=${UPNP} && make ; fi - if [[ "$TRAVIS_OS_NAME" == "linux" && "$BUILD_TYPE" == "make" ]]; then make USE_UPNP=${MAKE_UPNP} ; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then make HOMEBREW=1 USE_UPNP=${MAKE_UPNP} ; fi i2pd-2.17.0/ChangeLog000066400000000000000000000136031321131324000142020ustar00rootroot00000000000000# for this file format description, # see https://github.com/olivierlacan/keep-a-changelog ## [2.17.0] - 2017-12-04 ### Added - Reseed through HTTP and SOCKS proxy - Show status of client services through web console - Change log level through web connsole - transient keys for tunnels - i2p.streaming.initialAckDelay parameter - CRYPTO_TYPE for SAM destination - signature and crypto type for newkeys BOB command ### Changed - Correct publication of ECIES destinations - Disable RSA signatures completely ### Fixed - CVE-2017-17066 - Possible buffer overflow for RSA-4096 - Shutdown from web console for Windows - Web console page layout ## [2.16.0] - 2017-11-13 ### Added - https and "Connect" method for HTTP proxy - outproxy for HTTP proxy - initial support of ECIES crypto - NTCP soft and hard descriptors limits - Support full timestamps in logs ### Changed - Faster implmentation of GOST R 34.11 hash - Reject routers with RSA signtures - Reload config and shudown from Windows GUI - Update tunnels address(destination) without restart ### Fixed - BOB crashes if destination is not set - Correct SAM tunnel name - QT GUI issues ## [2.15.0] - 2017-08-17 ### Added - QT GUI - Ability to add and remove I2P tunnels without restart - Ability to disable SOCKS outproxy option ### Changed - Strip-out Accept-* hedaers in HTTP proxy - Don't run peer test if nat=false - Separate output of NTCP and SSU sessions in Transports tab ### Fixed - Handle lines with comments in hosts.txt file for address book - Run router with empty netdb for testnet - Skip expired introducers by iexp ## [2.14.0] - 2017-06-01 ### Added - Transit traffic bandwidth limitation - NTCP connections through HTTP and SOCKS proxies - Ability to disable address helper for HTTP proxy ### Changed - Reseed servers list - Minimal required version is 4.0 for Android ### Fixed - Ignore comments in addressbook feed ## [2.13.0] - 2017-04-06 ### Added - Persist local destination's tags - GOST signature types 9 and 10 - Exploratory tunnels configuration ### Changed - Reseed servers list - Inactive NTCP sockets get closed faster - Some EdDSA speed up ### Fixed - Multiple acceptors for SAM - Follow on data after STREAM CREATE for SAM - Memory leaks ## [2.12.0] - 2017-02-14 ### Added - Additional HTTP and SOCKS proxy tunnels - Reseed from ZIP archive - Some stats in a main window for Windows version ### Changed - Reseed servers list - MTU of 1488 for ipv6 - Android and Mac OS X versions use OpenSSL 1.1 - New logo for Android ### Fixed - Multiple memory leaks - Incomptibility of some EdDSA private keys with Java - Clock skew for Windows XP - Occasional crashes with I2PSnark ## [2.11.0] - 2016-12-18 ### Added - Websockets support - Reseed through a floodfill - Tunnel configuration for HTTP and SOCKS proxy - Zero-hops tunnels for destinations - Multiple acceptors for SAM ### Changed - Reseed servers list - DHT uses AVX if applicable - New logo - LeaseSet lookups ### Fixed - HTTP Proxy connection reset for Windows - Crash upon SAM session termination - Can't connect to a destination for a longer time after restart - Mass packet loss for UDP tunnels ## [2.10.2] - 2016-12-04 ### Fixed - Fixes UPnP discovery bug, producing excessive CPU usage - Fixes sudden SSU thread stop for Windows. ## [2.10.1] - 2016-11-07 ### Fixed - Fixed some performance issues for Windows and Android ## [2.10.0] - 2016-10-17 ### Added - Datagram i2p tunnels - Unique local addresses for server tunnels - Configurable list of reseed servers and initial addressbook - Configurable netid - Initial iOS support ### Changed - Reduced file descriptiors usage - Strict reseed checks enabled by default ## Fixed - Multiple fixes in I2CP and BOB implementations ## [2.9.0] - 2016-08-12 ### Changed - Proxy refactoring & speedup - Transmission-I2P support - Graceful shutdown for Windows - Android without QT - Reduced number of timers in SSU - ipv6 peer test support - Reseed from SU3 file ## [2.8.0] - 2016-06-20 ### Added - Basic Android support - I2CP implementation - 'doxygen' target ### Changed - I2PControl refactoring & fixes (proper jsonrpc responses on errors) - boost::regex no more needed ### Fixed - initscripts: added openrc one, in sysv-ish make I2PD_PORT optional - properly close NTCP sessions (memleak) ## [2.7.0] - 2016-05-18 ### Added - Precomputed El-Gamal/DH tables - Configurable limit of transit tunnels ### Changed - Speed-up of assymetric crypto for non-x64 platforms - Refactoring of web-console ## [2.6.0] - 2016-03-31 ### Added - Gracefull shutdown on SIGINT - Numeric bandwidth limits (was: by router class) - Jumpservices in web-console - Logging to syslog - Tray icon for windows application ### Changed - Logs refactoring - Improved statistics in web-console ### Deprecated: - Renamed main/tunnels config files (will use old, if found, but emits warning) ## [2.5.1] - 2016-03-10 ### Fixed - Doesn't create ~/.i2pd dir if missing ## [2.5.0] - 2016-03-04 ### Added - IRC server tunnels - SOCKS outproxy support - Support for gzipped addressbook updates - Support for router families ### Changed - Shared RTT/RTO between streams - Filesystem work refactoring ## [2.4.0] - 2016-02-03 ### Added - X-I2P-* headers for server http-tunnels - I2CP options for I2P tunnels - Show I2P tunnels in webconsole ### Changed - Refactoring of cmdline/config parsing ## [2.3.0] - 2016-01-12 ### Added - Support for new router bandwidth class codes (P and X) - I2PControl supports external webui - Added --pidfile and --notransit parameters - Ability to specify signature type for i2p tunnel ### Changed - Fixed multiple floodfill-related bugs - New webconsole layout ## [2.2.0] - 2015-12-22 ### Added - Ability to connect to router without ip via introducer ### Changed - Persist temporary encryption keys for local destinations - Performance improvements for EdDSA - New addressbook structure ## [2.1.0] - 2015-11-12 ### Added - Implementation of EdDSA ### Changed - EdDSA is default signature type for new RouterInfos i2pd-2.17.0/Dockerfile000066400000000000000000000042661321131324000144270ustar00rootroot00000000000000FROM alpine:latest MAINTAINER Mikal Villa ENV GIT_BRANCH="master" ENV I2PD_PREFIX="/opt/i2pd-${GIT_BRANCH}" ENV PATH=${I2PD_PREFIX}/bin:$PATH ENV GOSU_VERSION=1.7 ENV GOSU_SHASUM="34049cfc713e8b74b90d6de49690fa601dc040021980812b2f1f691534be8a50 /usr/local/bin/gosu" RUN mkdir /user && adduser -S -h /user i2pd && chown -R i2pd:nobody /user # # Each RUN is a layer, adding the dependencies and building i2pd in one layer takes around 8-900Mb, so to keep the # image under 20mb we need to remove all the build dependencies in the same "RUN" / layer. # # 1. install deps, clone and build. # 2. strip binaries. # 3. Purge all dependencies and other unrelated packages, including build directory. RUN apk --no-cache --virtual build-dependendencies add make gcc g++ libtool boost-dev build-base openssl-dev openssl git \ && mkdir -p /tmp/build \ && cd /tmp/build && git clone -b ${GIT_BRANCH} https://github.com/PurpleI2P/i2pd.git \ && cd i2pd \ && make -j4 \ && mkdir -p ${I2PD_PREFIX}/bin \ && mv i2pd ${I2PD_PREFIX}/bin/ \ && cd ${I2PD_PREFIX}/bin \ && strip i2pd \ && rm -fr /tmp/build && apk --purge del build-dependendencies build-base fortify-headers boost-dev zlib-dev openssl-dev \ boost-python3 python3 gdbm boost-unit_test_framework boost-python linux-headers boost-prg_exec_monitor \ boost-serialization boost-signals boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre \ libtool g++ gcc pkgconfig # 2. Adding required libraries to run i2pd to ensure it will run. RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl musl-utils libstdc++ # Gosu is a replacement for su/sudo in docker and not a backdoor :) See https://github.com/tianon/gosu RUN wget -O /usr/local/bin/gosu https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64 \ && echo "${GOSU_SHASUM}" | sha256sum -c && chmod +x /usr/local/bin/gosu COPY entrypoint.sh /entrypoint.sh RUN chmod a+x /entrypoint.sh RUN echo "export PATH=${PATH}" >> /etc/profile VOLUME [ "/var/lib/i2pd" ] EXPOSE 7070 4444 4447 7656 2827 7654 7650 ENTRYPOINT [ "/entrypoint.sh" ] i2pd-2.17.0/LICENSE000066400000000000000000000027321321131324000134360ustar00rootroot00000000000000Copyright (c) 2013-2015, The PurpleI2P Project All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. i2pd-2.17.0/Makefile000066400000000000000000000063711321131324000140740ustar00rootroot00000000000000UNAME := $(shell uname -s) SHLIB := libi2pd.so ARLIB := libi2pd.a SHLIB_CLIENT := libi2pdclient.so ARLIB_CLIENT := libi2pdclient.a I2PD := i2pd GREP := grep DEPS := obj/make.dep LIB_SRC_DIR := libi2pd LIB_CLIENT_SRC_DIR := libi2pd_client DAEMON_SRC_DIR := daemon include filelist.mk USE_AESNI := yes USE_AVX := yes USE_STATIC := no USE_MESHNET := no USE_UPNP := no ifeq ($(WEBSOCKETS),1) NEEDED_CXXFLAGS += -DWITH_EVENTS endif ifeq ($(UNAME),Darwin) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp ifeq ($(HOMEBREW),1) include Makefile.homebrew else include Makefile.osx endif else ifeq ($(shell echo $(UNAME) | $(GREP) -Ec '(Free|Open)BSD'),1) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.bsd else ifeq ($(UNAME),Linux) DAEMON_SRC += $(DAEMON_SRC_DIR)/UnixDaemon.cpp include Makefile.linux else DAEMON_SRC += Win32/DaemonWin32.cpp Win32/Win32Service.cpp Win32/Win32App.cpp include Makefile.mingw endif ifeq ($(USE_MESHNET),yes) NEEDED_CXXFLAGS += -DMESHNET endif NEEDED_CXXFLAGS += -I$(LIB_SRC_DIR) -I$(LIB_CLIENT_SRC_DIR) all: mk_obj_dir $(ARLIB) $(ARLIB_CLIENT) $(I2PD) mk_obj_dir: @mkdir -p obj @mkdir -p obj/Win32 @mkdir -p obj/$(LIB_SRC_DIR) @mkdir -p obj/$(LIB_CLIENT_SRC_DIR) @mkdir -p obj/$(DAEMON_SRC_DIR) api: mk_obj_dir $(SHLIB) $(ARLIB) api_client: mk_obj_dir $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) ## NOTE: The NEEDED_CXXFLAGS are here so that CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. ## For example, when adding 'hardening flags' to the build ## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. deps: mk_obj_dir $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) -MM *.cpp > $(DEPS) @sed -i -e '/\.o:/ s/^/obj\//' $(DEPS) obj/%.o: %.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) $(CPU_FLAGS) -c -o $@ $< # '-' is 'ignore if missing' on first run -include $(DEPS) DAEMON_OBJS += $(patsubst %.cpp,obj/%.o,$(DAEMON_SRC)) $(I2PD): $(DAEMON_OBJS) $(ARLIB) $(ARLIB_CLIENT) $(CXX) -o $@ $^ $(LDFLAGS) $(LDLIBS) $(SHLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) ifneq ($(USE_STATIC),yes) $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ endif $(SHLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) $(CXX) $(LDFLAGS) $(LDLIBS) -shared -o $@ $^ $(ARLIB): $(patsubst %.cpp,obj/%.o,$(LIB_SRC)) $(AR) -r $@ $^ $(ARLIB_CLIENT): $(patsubst %.cpp,obj/%.o,$(LIB_CLIENT_SRC)) $(AR) -r $@ $^ clean: $(RM) -r obj $(RM) -r docs/generated $(RM) $(I2PD) $(SHLIB) $(ARLIB) $(SHLIB_CLIENT) $(ARLIB_CLIENT) strip: $(I2PD) $(SHLIB_CLIENT) $(SHLIB) strip $^ LATEST_TAG=$(shell git describe --tags --abbrev=0 openssl) BRANCH=$(shell git rev-parse --abbrev-ref HEAD) dist: git archive --format=tar.gz -9 --worktree-attributes \ --prefix=i2pd_$(LATEST_TAG)/ $(LATEST_TAG) -o i2pd_$(LATEST_TAG).tar.gz last-dist: git archive --format=tar.gz -9 --worktree-attributes \ --prefix=i2pd_$(LATEST_TAG)/ $(BRANCH) -o ../i2pd_$(LATEST_TAG).orig.tar.gz doxygen: doxygen -s docs/Doxyfile .PHONY: all .PHONY: clean .PHONY: deps .PHONY: doxygen .PHONY: dist .PHONY: api .PHONY: api_client .PHONY: mk_obj_dir i2pd-2.17.0/Makefile.bsd000066400000000000000000000013531321131324000146360ustar00rootroot00000000000000CXX = clang++ CXXFLAGS = -O2 ## NOTE: NEEDED_CXXFLAGS is here so that custom CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. ## For example, when adding 'hardening flags' to the build ## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. NEEDED_CXXFLAGS = -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 INCFLAGS = -I/usr/include/ -I/usr/local/include/ LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread i2pd-2.17.0/Makefile.homebrew000066400000000000000000000030251321131324000156740ustar00rootroot00000000000000# root directory holding homebrew BREWROOT = /usr/local BOOSTROOT = ${BREWROOT}/opt/boost SSLROOT = ${BREWROOT}/opt/libressl UPNPROOT = ${BREWROOT}/opt/miniupnpc CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX -Wno-overloaded-virtual INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include ifndef TRAVIS CXX = clang++ endif ifeq ($(USE_STATIC),yes) LDLIBS = -lz ${SSLROOT}/lib/libcrypto.a ${SSLROOT}/lib/libssl.a ${BOOSTROOT}/lib/libboost_system.a ${BOOSTROOT}/lib/libboost_date_time.a ${BOOSTROOT}/lib/libboost_filesystem.a ${BOOSTROOT}/lib/libboost_program_options.a -lpthread else LDFLAGS = -L${SSLROOT}/lib -L${BOOSTROOT}/lib LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread endif ifeq ($(USE_UPNP),yes) LDFLAGS += -ldl CXXFLAGS += -DUSE_UPNP INCFLAGS += -I${UPNPROOT}/include ifeq ($(USE_STATIC),yes) LDLIBS += ${UPNPROOT}/lib/libminiupnpc.a else LDFLAGS += -L${UPNPROOT}/lib LDLIBS += -lminiupnpc endif endif # OSX Notes # http://www.hutsby.net/2011/08/macs-with-aes-ni.html # Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2 # Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic # note from psi: 2009 macbook does not have aesni #ifeq ($(USE_AESNI),yes) # CXXFLAGS += -maes -DAESNI #endif # Disabled, since it will be the default make rule. I think its better # to define the default rule in Makefile and not Makefile. - torkel #install: all # test -d ${PREFIX} || mkdir -p ${PREFIX}/ # cp -r i2p ${PREFIX}/ i2pd-2.17.0/Makefile.linux000066400000000000000000000047351321131324000152340ustar00rootroot00000000000000# set defaults instead redefine CXXFLAGS ?= -g -Wall -Wextra -Wno-unused-parameter -pedantic -Wno-misleading-indentation INCFLAGS ?= ## NOTE: The NEEDED_CXXFLAGS are here so that custom CXXFLAGS can be specified at build time ## **without** overwriting the CXXFLAGS which we need in order to build. ## For example, when adding 'hardening flags' to the build ## (e.g. -fstack-protector-strong -Wformat -Werror=format-security), we do not want to remove ## -std=c++11. If you want to remove this variable please do so in a way that allows setting ## custom FLAGS to work at build-time. # detect proper flag for c++11 support by compilers CXXVER := $(shell $(CXX) -dumpversion) ifeq ($(shell expr match $(CXX) 'clang'),5) NEEDED_CXXFLAGS += -std=c++11 else ifeq ($(shell expr match ${CXXVER} "4\.[0-9][0-9]"),4) # gcc >= 4.10 NEEDED_CXXFLAGS += -std=c++11 else ifeq ($(shell expr match ${CXXVER} "4\.[7-9]"),3) # >= 4.7 NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 else ifeq ($(shell expr match ${CXXVER} "4\.6"),3) # = 4.6 NEEDED_CXXFLAGS += -std=c++0x else ifeq ($(shell expr match ${CXXVER} "[5-7]\.[0-9]"),3) # gcc >= 5.0 NEEDED_CXXFLAGS += -std=c++11 else ifeq ($(shell expr match ${CXXVER} "7"),1) # gcc 7 ubuntu NEEDED_CXXFLAGS += -std=c++11 else # not supported $(error Compiler too old) endif NEEDED_CXXFLAGS += -fPIC ifeq ($(USE_STATIC),yes) # NOTE: on glibc you will get this warning: # Using 'getaddrinfo' in statically linked applications requires at runtime # the shared libraries from the glibc version used for linking LIBDIR := /usr/lib LDLIBS = $(LIBDIR)/libboost_system.a LDLIBS += $(LIBDIR)/libboost_date_time.a LDLIBS += $(LIBDIR)/libboost_filesystem.a LDLIBS += $(LIBDIR)/libboost_program_options.a LDLIBS += $(LIBDIR)/libssl.a LDLIBS += $(LIBDIR)/libcrypto.a LDLIBS += $(LIBDIR)/libz.a LDLIBS += -lpthread -static-libstdc++ -static-libgcc -lrt -ldl USE_AESNI := no else LDLIBS = -lcrypto -lssl -lz -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread endif # UPNP Support (miniupnpc 1.5 and higher) ifeq ($(USE_UPNP),yes) CXXFLAGS += -DUSE_UPNP ifeq ($(USE_STATIC),yes) LDLIBS += $(LIBDIR)/libminiupnpc.a else LDLIBS += -lminiupnpc endif endif ifeq ($(USE_AESNI),yes) #check if AES-NI is supported by CPU ifneq ($(shell $(GREP) -c aes /proc/cpuinfo),0) CPU_FLAGS += -maes -DAESNI endif endif ifeq ($(USE_AVX),yes) #check if AVX supported by CPU ifneq ($(shell $(GREP) -c avx /proc/cpuinfo),0) CPU_FLAGS += -mavx endif endif i2pd-2.17.0/Makefile.mingw000066400000000000000000000022721321131324000152100ustar00rootroot00000000000000USE_WIN32_APP=yes CXX = g++ WINDRES = windres CXXFLAGS = -Os -D_MT -DWIN32 -D_WINDOWS -DWIN32_LEAN_AND_MEAN NEEDED_CXXFLAGS = -std=c++11 BOOST_SUFFIX = -mt INCFLAGS = -Idaemon -I. LDFLAGS = -s -Wl,-rpath,/usr/local/lib -Wl,-Bstatic -static-libgcc -static-libstdc++ # UPNP Support ifeq ($(USE_UPNP),yes) CXXFLAGS += -DUSE_UPNP -DMINIUPNP_STATICLIB LDLIBS = -lminiupnpc endif LDLIBS += \ -lboost_system$(BOOST_SUFFIX) \ -lboost_date_time$(BOOST_SUFFIX) \ -lboost_filesystem$(BOOST_SUFFIX) \ -lboost_program_options$(BOOST_SUFFIX) \ -lssl \ -lcrypto \ -lz \ -lwsock32 \ -lws2_32 \ -lgdi32 \ -liphlpapi \ -lstdc++ \ -lpthread ifeq ($(USE_WIN32_APP), yes) CXXFLAGS += -DWIN32_APP LDFLAGS += -mwindows DAEMON_RC += Win32/Resource.rc DAEMON_OBJS += $(patsubst %.rc,obj/%.o,$(DAEMON_RC)) endif # don't change following line to ifeq ($(USE_AESNI),yes) !!! ifeq ($(USE_AESNI),1) CPU_FLAGS += -maes -DAESNI else CPU_FLAGS += -msse endif ifeq ($(USE_AVX),1) CPU_FLAGS += -mavx endif ifeq ($(USE_ASLR),yes) LDFLAGS += -Wl,--nxcompat -Wl,--high-entropy-va -Wl,--dynamicbase,--export-all-symbols endif obj/%.o : %.rc $(WINDRES) -i $< -o $@ i2pd-2.17.0/Makefile.osx000066400000000000000000000020731321131324000146770ustar00rootroot00000000000000CXX = clang++ CXXFLAGS = -Os -Wall -std=c++11 -DMAC_OSX #CXXFLAGS = -g -O2 -Wall -std=c++11 INCFLAGS = -I/usr/local/include LDFLAGS = -Wl,-rpath,/usr/local/lib -L/usr/local/lib ifeq ($(USE_STATIC),yes) LDLIBS = -lz /usr/local/lib/libcrypto.a /usr/local/lib/libssl.a /usr/local/lib/libboost_system.a /usr/local/lib/libboost_date_time.a /usr/local/lib/libboost_filesystem.a /usr/local/lib/libboost_program_options.a -lpthread else LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread endif ifeq ($(USE_UPNP),yes) LDFLAGS += -ldl CXXFLAGS += -DUSE_UPNP ifeq ($(USE_STATIC),yes) LDLIBS += /usr/local/lib/libminiupnpc.a else LDLIBS += -lminiupnpc endif endif ifeq ($(USE_AESNI),1) CXXFLAGS += -maes -DAESNI else CXXFLAGS += -msse endif ifeq ($(USE_AVX),1) CXXFLAGS += -mavx endif # Disabled, since it will be the default make rule. I think its better # to define the default rule in Makefile and not Makefile. - torkel #install: all # test -d ${PREFIX} || mkdir -p ${PREFIX}/ # cp -r i2p ${PREFIX}/ i2pd-2.17.0/README.md000066400000000000000000000056401321131324000137110ustar00rootroot00000000000000i2pd ==== [Русская версия](https://github.com/PurpleI2P/i2pd_docs_ru/blob/master/README.md) i2pd (I2P Daemon) is a full-featured C++ implementation of I2P client. I2P (Invisible Internet Protocol) is a universal anonymous network layer. All communications over I2P are anonymous and end-to-end encrypted, participants don't reveal their real IP addresses. I2P client is a software used for building and using anonymous I2P networks. Such networks are commonly used for anonymous peer-to-peer applications (filesharing, cryptocurrencies) and anonymous client-server applications (websites, instant messengers, chat-servers). I2P allows people from all around the world to communicate and share information without restrictions. Features -------- * Distributed anonymous networking framework * End-to-end encrypted communications * Small footprint, simple dependencies, fast performance * Rich set of APIs for developers of secure applications Resources --------- * [Website](http://i2pd.website) * [Documentation](https://i2pd.readthedocs.io/en/latest/) * [Wiki](https://github.com/PurpleI2P/i2pd/wiki) * [Tickets/Issues](https://github.com/PurpleI2P/i2pd/issues) * [Specifications](https://geti2p.net/spec) * [Twitter](https://twitter.com/hashtag/i2pd) Installing ---------- The easiest way to install i2pd is by using [precompiled binaries](https://github.com/PurpleI2P/i2pd/releases/latest). See [documentation](https://i2pd.readthedocs.io/en/latest/) for how to build i2pd from source on your OS. Build instructions: * [unix](https://i2pd.readthedocs.io/en/latest/devs/building/unix/) * [windows](https://i2pd.readthedocs.io/en/latest/devs/building/windows/) * [iOS](https://i2pd.readthedocs.io/en/latest/devs/building/ios/) * [android](https://i2pd.readthedocs.io/en/latest/devs/building/android/) **Supported systems:** * GNU/Linux x86/x64 - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) * Windows - [![Build status](https://ci.appveyor.com/api/projects/status/1908qe4p48ff1x23?svg=true)](https://ci.appveyor.com/project/PurpleI2P/i2pd) * Mac OS X - [![Build Status](https://travis-ci.org/PurpleI2P/i2pd.svg?branch=openssl)](https://travis-ci.org/PurpleI2P/i2pd) * FreeBSD * Android * iOS Using i2pd ---------- See [documentation](https://i2pd.readthedocs.io/en/latest/user-guide/run/) and [example config file](https://github.com/PurpleI2P/i2pd/blob/openssl/contrib/i2pd.conf). Donations --------- BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY ZEC: t1cTckLuXsr1dwVrK4NDzfhehss4NvMadAJ DASH: Xw8YUrQpYzP9tZBmbjqxS3M97Q7v3vJKUF LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 DOGE: DNXLQKziRPAsD9H3DFNjk4fLQrdaSX893Y ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z GST: GbD2JSQHBHCKLa9WTHmigJRpyFgmBj4woG License ------- This project is licensed under the BSD 3-clause license, which can be found in the file LICENSE in the root of the project source code. i2pd-2.17.0/Win32/000077500000000000000000000000001321131324000133275ustar00rootroot00000000000000i2pd-2.17.0/Win32/DaemonWin32.cpp000066400000000000000000000050701321131324000160630ustar00rootroot00000000000000#include #include #include "Config.h" #include "Daemon.h" #include "util.h" #include "Log.h" #ifdef _WIN32 #include "Win32/Win32Service.h" #ifdef WIN32_APP #include "Win32/Win32App.h" #endif namespace i2p { namespace util { bool DaemonWin32::init(int argc, char* argv[]) { setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); setlocale(LC_ALL, "Russian"); if (!Daemon_Singleton::init(argc, argv)) return false; std::string serviceControl; i2p::config::GetOption("svcctl", serviceControl); if (serviceControl == "install") { LogPrint(eLogInfo, "WinSVC: installing ", SERVICE_NAME, " as service"); InstallService( SERVICE_NAME, // Name of service SERVICE_DISPLAY_NAME, // Name to display SERVICE_START_TYPE, // Service start type SERVICE_DEPENDENCIES, // Dependencies SERVICE_ACCOUNT, // Service running account SERVICE_PASSWORD // Password of the account ); return false; } else if (serviceControl == "remove") { LogPrint(eLogInfo, "WinSVC: uninstalling ", SERVICE_NAME, " service"); UninstallService(SERVICE_NAME); return false; } if (isDaemon) { LogPrint(eLogDebug, "Daemon: running as service"); I2PService service(SERVICE_NAME); if (!I2PService::Run(service)) { LogPrint(eLogError, "Daemon: Service failed to run w/err 0x%08lx\n", GetLastError()); return false; } return false; } else LogPrint(eLogDebug, "Daemon: running as user"); return true; } bool DaemonWin32::start() { setlocale(LC_CTYPE, ""); SetConsoleCP(1251); SetConsoleOutputCP(1251); setlocale(LC_ALL, "Russian"); #ifdef WIN32_APP if (!i2p::win32::StartWin32App ()) return false; // override log i2p::config::SetOption("log", std::string ("file")); #endif bool ret = Daemon_Singleton::start(); if (ret && i2p::log::Logger().GetLogType() == eLogFile) { // TODO: find out where this garbage to console comes from SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE); SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE); } bool insomnia; i2p::config::GetOption("insomnia", insomnia); if (insomnia) SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); return ret; } bool DaemonWin32::stop() { #ifdef WIN32_APP i2p::win32::StopWin32App (); #endif return Daemon_Singleton::stop(); } void DaemonWin32::run () { #ifdef WIN32_APP i2p::win32::RunWin32App (); #else while (running) { std::this_thread::sleep_for (std::chrono::seconds(1)); } #endif } } } #endif //_WIN32 i2pd-2.17.0/Win32/Resource.rc000066400000000000000000000011671321131324000154510ustar00rootroot00000000000000#include "resource.h" #define APSTUDIO_READONLY_SYMBOLS #include "winres.h" #undef APSTUDIO_READONLY_SYMBOLS #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #ifdef APSTUDIO_INVOKED 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED MAINICON ICON "mask.ico" #endif // English (United States) resources #ifndef APSTUDIO_INVOKED #include "Resource.rc2" #endif // not APSTUDIO_INVOKED i2pd-2.17.0/Win32/Resource.rc2000066400000000000000000000020661321131324000155320ustar00rootroot00000000000000#ifdef APSTUDIO_INVOKED #error this file is not editable by Microsoft Visual C++ #endif //APSTUDIO_INVOKED #include "../libi2pd/version.h" VS_VERSION_INFO VERSIONINFO FILEVERSION I2PD_VERSION_MAJOR,I2PD_VERSION_MINOR,I2PD_VERSION_MICRO,I2PD_VERSION_PATCH PRODUCTVERSION I2P_VERSION_MAJOR,I2P_VERSION_MINOR,I2P_VERSION_MICRO,I2P_VERSION_PATCH FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x40004L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Purple I2P" VALUE "FileDescription", "C++ I2P daemon" VALUE "FileVersion", I2PD_VERSION VALUE "InternalName", CODENAME VALUE "LegalCopyright", "Copyright (C) 2013-2017, The PurpleI2P Project" VALUE "OriginalFilename", "i2pd" VALUE "ProductName", "Purple I2P" VALUE "ProductVersion", I2P_VERSION END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END i2pd-2.17.0/Win32/Win32App.cpp000066400000000000000000000261131321131324000154010ustar00rootroot00000000000000#include #include #include #include "ClientContext.h" #include "Config.h" #include "NetDb.hpp" #include "RouterContext.h" #include "Transports.h" #include "Tunnel.h" #include "version.h" #include "resource.h" #include "Daemon.h" #include "Win32App.h" #include #if defined(_MSC_VER) && _MSC_VER < 1900 #define snprintf _snprintf #endif #define ID_ABOUT 2000 #define ID_EXIT 2001 #define ID_CONSOLE 2002 #define ID_APP 2003 #define ID_GRACEFUL_SHUTDOWN 2004 #define ID_STOP_GRACEFUL_SHUTDOWN 2005 #define ID_RELOAD 2006 #define ID_TRAY_ICON 2050 #define WM_TRAYICON (WM_USER + 1) #define IDT_GRACEFUL_SHUTDOWN_TIMER 2100 #define FRAME_UPDATE_TIMER 2101 namespace i2p { namespace win32 { static void ShowPopupMenu (HWND hWnd, POINT *curpos, int wDefaultItem) { HMENU hPopup = CreatePopupMenu(); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_CONSOLE, "Open &console"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_APP, "Show app"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_ABOUT, "&About..."); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_RELOAD, "&Reload configs"); if (!i2p::util::DaemonWin32::Instance ().isGraceful) InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_GRACEFUL_SHUTDOWN, "&Graceful shutdown"); else InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_STOP_GRACEFUL_SHUTDOWN, "&Stop graceful shutdown"); InsertMenu (hPopup, -1, MF_BYPOSITION | MF_STRING, ID_EXIT, "E&xit"); SetMenuDefaultItem (hPopup, ID_CONSOLE, FALSE); SendMessage (hWnd, WM_INITMENUPOPUP, (WPARAM)hPopup, 0); POINT p; if (!curpos) { GetCursorPos (&p); curpos = &p; } WORD cmd = TrackPopupMenu (hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, curpos->x, curpos->y, 0, hWnd, NULL); SendMessage (hWnd, WM_COMMAND, cmd, 0); DestroyMenu(hPopup); } static void AddTrayIcon (HWND hWnd) { NOTIFYICONDATA nid; memset(&nid, 0, sizeof(nid)); nid.cbSize = sizeof(nid); nid.hWnd = hWnd; nid.uID = ID_TRAY_ICON; nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO; nid.uCallbackMessage = WM_TRAYICON; nid.hIcon = LoadIcon (GetModuleHandle(NULL), MAKEINTRESOURCE (MAINICON)); strcpy (nid.szTip, "i2pd"); strcpy (nid.szInfo, "i2pd is starting"); Shell_NotifyIcon(NIM_ADD, &nid ); } static void RemoveTrayIcon (HWND hWnd) { NOTIFYICONDATA nid; nid.hWnd = hWnd; nid.uID = ID_TRAY_ICON; Shell_NotifyIcon (NIM_DELETE, &nid); } static void ShowUptime (std::stringstream& s, int seconds) { int num; if ((num = seconds / 86400) > 0) { s << num << " days, "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { s << num << " hours, "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { s << num << " min, "; seconds -= num * 60; } s << seconds << " seconds\n"; } template static void ShowTransfered (std::stringstream& s, size transfer) { auto bytes = transfer & 0x03ff; transfer >>= 10; auto kbytes = transfer & 0x03ff; transfer >>= 10; auto mbytes = transfer & 0x03ff; transfer >>= 10; auto gbytes = transfer & 0x03ff; if (gbytes) s << gbytes << " GB, "; if (mbytes) s << mbytes << " MB, "; if (kbytes) s << kbytes << " KB, "; s << bytes << " Bytes\n"; } static void PrintMainWindowText (std::stringstream& s) { s << "\n"; s << "Status: "; switch (i2p::context.GetStatus()) { case eRouterStatusOK: s << "OK"; break; case eRouterStatusTesting: s << "Testing"; break; case eRouterStatusFirewalled: s << "Firewalled"; break; case eRouterStatusError: { switch (i2p::context.GetError()) { case eRouterErrorClockSkew: s << "Clock skew"; break; default: s << "Error"; } break; } default: s << "Unknown"; } s << "; "; s << "Success Rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() << "%\n"; s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); s << "\n"; s << "Inbound: " << i2p::transport::transports.GetInBandwidth() / 1024 << " KiB/s; "; s << "Outbound: " << i2p::transport::transports.GetOutBandwidth() / 1024 << " KiB/s\n"; s << "Received: "; ShowTransfered (s, i2p::transport::transports.GetTotalReceivedBytes()); s << "Sent: "; ShowTransfered (s, i2p::transport::transports.GetTotalSentBytes()); s << "\n"; s << "Routers: " << i2p::data::netdb.GetNumRouters () << "; "; s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << "; "; s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "\n"; s << "Tunnels: "; s << "In: " << i2p::tunnel::tunnels.CountInboundTunnels() << "; "; s << "Out: " << i2p::tunnel::tunnels.CountOutboundTunnels() << "; "; s << "Transit: " << i2p::tunnel::tunnels.CountTransitTunnels() << "\n"; s << "\n"; } static LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: { AddTrayIcon (hWnd); break; } case WM_CLOSE: { RemoveTrayIcon (hWnd); KillTimer (hWnd, FRAME_UPDATE_TIMER); KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); PostQuitMessage (0); break; } case WM_COMMAND: { switch (LOWORD(wParam)) { case ID_ABOUT: { std::stringstream text; text << "Version: " << I2PD_VERSION << " " << CODENAME; MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); return 0; } case ID_EXIT: { PostMessage (hWnd, WM_CLOSE, 0, 0); return 0; } case ID_GRACEFUL_SHUTDOWN: { i2p::context.SetAcceptsTunnels (false); SetTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER, 10*60*1000, nullptr); // 10 minutes i2p::util::DaemonWin32::Instance ().isGraceful = true; return 0; } case ID_STOP_GRACEFUL_SHUTDOWN: { i2p::context.SetAcceptsTunnels (true); KillTimer (hWnd, IDT_GRACEFUL_SHUTDOWN_TIMER); i2p::util::DaemonWin32::Instance ().isGraceful = false; return 0; } case ID_RELOAD: { i2p::client::context.ReloadConfig(); std::stringstream text; text << "I2Pd reloading configs..."; MessageBox( hWnd, TEXT(text.str ().c_str ()), TEXT("i2pd"), MB_ICONINFORMATION | MB_OK ); return 0; } case ID_CONSOLE: { char buf[30]; std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); snprintf(buf, 30, "http://%s:%d", httpAddr.c_str(), httpPort); ShellExecute(NULL, "open", buf, NULL, NULL, SW_SHOWNORMAL); return 0; } case ID_APP: { ShowWindow(hWnd, SW_SHOW); SetTimer(hWnd, FRAME_UPDATE_TIMER, 3000, NULL); return 0; } } break; } case WM_SYSCOMMAND: { switch (wParam) { case SC_MINIMIZE: { ShowWindow(hWnd, SW_HIDE); KillTimer (hWnd, FRAME_UPDATE_TIMER); return 0; } case SC_CLOSE: { std::string close; i2p::config::GetOption("close", close); if (0 == close.compare("ask")) switch(::MessageBox(hWnd, "Would you like to minimize instead of exiting?" " You can add 'close' configuration option. Valid values are: ask, minimize, exit.", "Minimize instead of exiting?", MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON1)) { case IDYES: close = "minimize"; break; case IDNO: close = "exit"; break; default: return 0; } if (0 == close.compare("minimize")) { ShowWindow(hWnd, SW_HIDE); KillTimer (hWnd, FRAME_UPDATE_TIMER); return 0; } if (0 != close.compare("exit")) { ::MessageBox(hWnd, close.c_str(), "Unknown close action in config", MB_OK | MB_ICONWARNING); return 0; } } } } case WM_TRAYICON: { switch (lParam) { case WM_LBUTTONUP: case WM_RBUTTONUP: { SetForegroundWindow (hWnd); ShowPopupMenu(hWnd, NULL, -1); PostMessage (hWnd, WM_APP + 1, 0, 0); break; } } break; } case WM_TIMER: { if (wParam == IDT_GRACEFUL_SHUTDOWN_TIMER) { PostMessage (hWnd, WM_CLOSE, 0, 0); // exit return 0; } if (wParam == FRAME_UPDATE_TIMER) { InvalidateRect(hWnd, NULL, TRUE); } break; } case WM_PAINT: { HDC hDC; PAINTSTRUCT ps; RECT rp; HFONT hFont; std::stringstream s; PrintMainWindowText (s); hDC = BeginPaint (hWnd, &ps); GetClientRect(hWnd, &rp); SetTextColor(hDC, 0x00D43B69); hFont = CreateFont(18,0,0,0,0,0,0,0,DEFAULT_CHARSET,0,0,0,0,TEXT("Times New Roman")); SelectObject(hDC,hFont); DrawText(hDC, TEXT(s.str().c_str()), s.str().length(), &rp, DT_CENTER|DT_VCENTER); DeleteObject(hFont); EndPaint(hWnd, &ps); break; } } return DefWindowProc( hWnd, uMsg, wParam, lParam); } bool StartWin32App () { if (FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd"))) { MessageBox(NULL, TEXT("I2Pd is running already"), TEXT("Warning"), MB_OK); return false; } // register main window auto hInst = GetModuleHandle(NULL); WNDCLASSEX wclx; memset (&wclx, 0, sizeof(wclx)); wclx.cbSize = sizeof(wclx); wclx.style = 0; wclx.lpfnWndProc = WndProc; //wclx.cbClsExtra = 0; //wclx.cbWndExtra = 0; wclx.hInstance = hInst; wclx.hIcon = LoadIcon (hInst, MAKEINTRESOURCE(MAINICON)); wclx.hCursor = LoadCursor (NULL, IDC_ARROW); //wclx.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wclx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wclx.lpszMenuName = NULL; wclx.lpszClassName = I2PD_WIN32_CLASSNAME; RegisterClassEx (&wclx); // create new window if (!CreateWindow(I2PD_WIN32_CLASSNAME, TEXT("i2pd"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 100, 100, 350, 210, NULL, NULL, hInst, NULL)) { MessageBox(NULL, "Failed to create main window", TEXT("Warning!"), MB_ICONERROR | MB_OK | MB_TOPMOST); return false; } return true; } int RunWin32App () { MSG msg; while (GetMessage (&msg, NULL, 0, 0 )) { TranslateMessage (&msg); DispatchMessage (&msg); } return msg.wParam; } void StopWin32App () { HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_EXIT, 0), 0); UnregisterClass (I2PD_WIN32_CLASSNAME, GetModuleHandle(NULL)); } bool GracefulShutdown () { HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_GRACEFUL_SHUTDOWN, 0), 0); return hWnd; } bool StopGracefulShutdown () { HWND hWnd = FindWindow (I2PD_WIN32_CLASSNAME, TEXT("i2pd")); if (hWnd) PostMessage (hWnd, WM_COMMAND, MAKEWPARAM(ID_STOP_GRACEFUL_SHUTDOWN, 0), 0); return hWnd; } } } i2pd-2.17.0/Win32/Win32App.h000066400000000000000000000004501321131324000150420ustar00rootroot00000000000000#ifndef WIN32APP_H__ #define WIN32APP_H__ #define I2PD_WIN32_CLASSNAME "i2pd main window" namespace i2p { namespace win32 { bool StartWin32App (); void StopWin32App (); int RunWin32App (); bool GracefulShutdown (); bool StopGracefulShutdown (); } } #endif // WIN32APP_H__ i2pd-2.17.0/Win32/Win32Service.cpp000066400000000000000000000234661321131324000162710ustar00rootroot00000000000000#ifdef _WIN32 #define _CRT_SECURE_NO_WARNINGS // to use freopen #endif #include "Win32Service.h" #include #include #include #include "Daemon.h" #include "Log.h" I2PService *I2PService::s_service = NULL; BOOL I2PService::isService() { BOOL bIsService = FALSE; HWINSTA hWinStation = GetProcessWindowStation(); if (hWinStation != NULL) { USEROBJECTFLAGS uof = { 0 }; if (GetUserObjectInformation(hWinStation, UOI_FLAGS, &uof, sizeof(USEROBJECTFLAGS), NULL) && ((uof.dwFlags & WSF_VISIBLE) == 0)) { bIsService = TRUE; } } return bIsService; } BOOL I2PService::Run(I2PService &service) { s_service = &service; SERVICE_TABLE_ENTRY serviceTable[] = { { service.m_name, ServiceMain }, { NULL, NULL } }; return StartServiceCtrlDispatcher(serviceTable); } void WINAPI I2PService::ServiceMain(DWORD dwArgc, PSTR *pszArgv) { assert(s_service != NULL); s_service->m_statusHandle = RegisterServiceCtrlHandler( s_service->m_name, ServiceCtrlHandler); if (s_service->m_statusHandle == NULL) { throw GetLastError(); } s_service->Start(dwArgc, pszArgv); } void WINAPI I2PService::ServiceCtrlHandler(DWORD dwCtrl) { switch (dwCtrl) { case SERVICE_CONTROL_STOP: s_service->Stop(); break; case SERVICE_CONTROL_PAUSE: s_service->Pause(); break; case SERVICE_CONTROL_CONTINUE: s_service->Continue(); break; case SERVICE_CONTROL_SHUTDOWN: s_service->Shutdown(); break; case SERVICE_CONTROL_INTERROGATE: break; default: break; } } I2PService::I2PService(PSTR pszServiceName, BOOL fCanStop, BOOL fCanShutdown, BOOL fCanPauseContinue) { m_name = (pszServiceName == NULL) ? (PSTR)"" : pszServiceName; m_statusHandle = NULL; m_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; m_status.dwCurrentState = SERVICE_START_PENDING; DWORD dwControlsAccepted = 0; if (fCanStop) dwControlsAccepted |= SERVICE_ACCEPT_STOP; if (fCanShutdown) dwControlsAccepted |= SERVICE_ACCEPT_SHUTDOWN; if (fCanPauseContinue) dwControlsAccepted |= SERVICE_ACCEPT_PAUSE_CONTINUE; m_status.dwControlsAccepted = dwControlsAccepted; m_status.dwWin32ExitCode = NO_ERROR; m_status.dwServiceSpecificExitCode = 0; m_status.dwCheckPoint = 0; m_status.dwWaitHint = 0; m_fStopping = FALSE; // Create a manual-reset event that is not signaled at first to indicate // the stopped signal of the service. m_hStoppedEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (m_hStoppedEvent == NULL) { throw GetLastError(); } } I2PService::~I2PService(void) { if (m_hStoppedEvent) { CloseHandle(m_hStoppedEvent); m_hStoppedEvent = NULL; } } void I2PService::Start(DWORD dwArgc, PSTR *pszArgv) { try { SetServiceStatus(SERVICE_START_PENDING); OnStart(dwArgc, pszArgv); SetServiceStatus(SERVICE_RUNNING); } catch (DWORD dwError) { LogPrint(eLogError, "Win32Service Start", dwError); SetServiceStatus(SERVICE_STOPPED, dwError); } catch (...) { LogPrint(eLogError, "Win32Service failed to start.", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_STOPPED); } } void I2PService::OnStart(DWORD dwArgc, PSTR *pszArgv) { LogPrint(eLogInfo, "Win32Service in OnStart", EVENTLOG_INFORMATION_TYPE); Daemon.start(); //i2p::util::config::OptionParser(dwArgc, pszArgv); //i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs); //i2p::context.OverrideNTCPAddress(i2p::util::config::GetCharArg("-host", "127.0.0.1"), // i2p::util::config::GetArg("-port", 17070)); _worker = new std::thread(std::bind(&I2PService::WorkerThread, this)); } void I2PService::WorkerThread() { while (!m_fStopping) { ::Sleep(1000); // Simulate some lengthy operations. } // Signal the stopped event. SetEvent(m_hStoppedEvent); } void I2PService::Stop() { DWORD dwOriginalState = m_status.dwCurrentState; try { SetServiceStatus(SERVICE_STOP_PENDING); OnStop(); SetServiceStatus(SERVICE_STOPPED); } catch (DWORD dwError) { LogPrint(eLogInfo, "Win32Service Stop", dwError); SetServiceStatus(dwOriginalState); } catch (...) { LogPrint(eLogError, "Win32Service failed to stop.", EVENTLOG_ERROR_TYPE); SetServiceStatus(dwOriginalState); } } void I2PService::OnStop() { // Log a service stop message to the Application log. LogPrint(eLogInfo, "Win32Service in OnStop", EVENTLOG_INFORMATION_TYPE); Daemon.stop(); m_fStopping = TRUE; if (WaitForSingleObject(m_hStoppedEvent, INFINITE) != WAIT_OBJECT_0) { throw GetLastError(); } _worker->join(); delete _worker; } void I2PService::Pause() { try { SetServiceStatus(SERVICE_PAUSE_PENDING); OnPause(); SetServiceStatus(SERVICE_PAUSED); } catch (DWORD dwError) { LogPrint(eLogError, "Win32Service Pause", dwError); SetServiceStatus(SERVICE_RUNNING); } catch (...) { LogPrint(eLogError, "Win32Service failed to pause.", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_RUNNING); } } void I2PService::OnPause() { } void I2PService::Continue() { try { SetServiceStatus(SERVICE_CONTINUE_PENDING); OnContinue(); SetServiceStatus(SERVICE_RUNNING); } catch (DWORD dwError) { LogPrint(eLogError, "Win32Service Continue", dwError); SetServiceStatus(SERVICE_PAUSED); } catch (...) { LogPrint(eLogError, "Win32Service failed to resume.", EVENTLOG_ERROR_TYPE); SetServiceStatus(SERVICE_PAUSED); } } void I2PService::OnContinue() { } void I2PService::Shutdown() { try { OnShutdown(); SetServiceStatus(SERVICE_STOPPED); } catch (DWORD dwError) { LogPrint(eLogError, "Win32Service Shutdown", dwError); } catch (...) { LogPrint(eLogError, "Win32Service failed to shut down.", EVENTLOG_ERROR_TYPE); } } void I2PService::OnShutdown() { } void I2PService::SetServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) { static DWORD dwCheckPoint = 1; m_status.dwCurrentState = dwCurrentState; m_status.dwWin32ExitCode = dwWin32ExitCode; m_status.dwWaitHint = dwWaitHint; m_status.dwCheckPoint = ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED)) ? 0 : dwCheckPoint++; ::SetServiceStatus(m_statusHandle, &m_status); } //***************************************************************************** void FreeHandles(SC_HANDLE schSCManager, SC_HANDLE schService) { if (schSCManager) { CloseServiceHandle(schSCManager); schSCManager = NULL; } if (schService) { CloseServiceHandle(schService); schService = NULL; } } void InstallService(PCSTR pszServiceName, PCSTR pszDisplayName, DWORD dwStartType, PCSTR pszDependencies, PCSTR pszAccount, PCSTR pszPassword) { printf("Try to install Win32Service (%s).\n", pszServiceName); char szPath[MAX_PATH]; SC_HANDLE schSCManager = NULL; SC_HANDLE schService = NULL; if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath)) == 0) { printf("GetModuleFileName failed w/err 0x%08lx\n", GetLastError()); FreeHandles(schSCManager, schService); return; } strncat(szPath, " --daemon", MAX_PATH); // Open the local default service control manager database schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE); if (schSCManager == NULL) { printf("OpenSCManager failed w/err 0x%08lx\n", GetLastError()); FreeHandles(schSCManager, schService); return; } // Install the service into SCM by calling CreateService schService = CreateService( schSCManager, // SCManager database pszServiceName, // Name of service pszDisplayName, // Name to display SERVICE_QUERY_STATUS, // Desired access SERVICE_WIN32_OWN_PROCESS, // Service type dwStartType, // Service start type SERVICE_ERROR_NORMAL, // Error control type szPath, // Service's binary NULL, // No load ordering group NULL, // No tag identifier pszDependencies, // Dependencies pszAccount, // Service running account pszPassword // Password of the account ); if (schService == NULL) { printf("CreateService failed w/err 0x%08lx\n", GetLastError()); FreeHandles(schSCManager, schService); return; } printf("Win32Service is installed as %s.\n", pszServiceName); // Centralized cleanup for all allocated resources. FreeHandles(schSCManager, schService); } void UninstallService(PCSTR pszServiceName) { printf("Try to uninstall Win32Service (%s).\n", pszServiceName); SC_HANDLE schSCManager = NULL; SC_HANDLE schService = NULL; SERVICE_STATUS ssSvcStatus = {}; // Open the local default service control manager database schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if (schSCManager == NULL) { printf("OpenSCManager failed w/err 0x%08lx\n", GetLastError()); FreeHandles(schSCManager, schService); return; } // Open the service with delete, stop, and query status permissions schService = OpenService(schSCManager, pszServiceName, SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE); if (schService == NULL) { printf("OpenService failed w/err 0x%08lx\n", GetLastError()); FreeHandles(schSCManager, schService); return; } // Try to stop the service if (ControlService(schService, SERVICE_CONTROL_STOP, &ssSvcStatus)) { printf("Stopping %s.\n", pszServiceName); Sleep(1000); while (QueryServiceStatus(schService, &ssSvcStatus)) { if (ssSvcStatus.dwCurrentState == SERVICE_STOP_PENDING) { printf("."); Sleep(1000); } else break; } if (ssSvcStatus.dwCurrentState == SERVICE_STOPPED) { printf("\n%s is stopped.\n", pszServiceName); } else { printf("\n%s failed to stop.\n", pszServiceName); } } // Now remove the service by calling DeleteService. if (!DeleteService(schService)) { printf("DeleteService failed w/err 0x%08lx\n", GetLastError()); FreeHandles(schSCManager, schService); return; } printf("%s is removed.\n", pszServiceName); // Centralized cleanup for all allocated resources. FreeHandles(schSCManager, schService); } i2pd-2.17.0/Win32/Win32Service.h000066400000000000000000000034721321131324000157310ustar00rootroot00000000000000#ifndef WIN_32_SERVICE_H__ #define WIN_32_SERVICE_H__ #include #include #ifdef _WIN32 // Internal name of the service #define SERVICE_NAME "i2pdService" // Displayed name of the service #define SERVICE_DISPLAY_NAME "i2pd router service" // Service start options. #define SERVICE_START_TYPE SERVICE_DEMAND_START // List of service dependencies - "dep1\0dep2\0\0" #define SERVICE_DEPENDENCIES "" // The name of the account under which the service should run #define SERVICE_ACCOUNT "NT AUTHORITY\\LocalService" // The password to the service account name #define SERVICE_PASSWORD NULL #endif class I2PService { public: I2PService(PSTR pszServiceName, BOOL fCanStop = TRUE, BOOL fCanShutdown = TRUE, BOOL fCanPauseContinue = FALSE); virtual ~I2PService(void); static BOOL isService(); static BOOL Run(I2PService &service); void Stop(); protected: virtual void OnStart(DWORD dwArgc, PSTR *pszArgv); virtual void OnStop(); virtual void OnPause(); virtual void OnContinue(); virtual void OnShutdown(); void SetServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode = NO_ERROR, DWORD dwWaitHint = 0); private: static void WINAPI ServiceMain(DWORD dwArgc, LPSTR *lpszArgv); static void WINAPI ServiceCtrlHandler(DWORD dwCtrl); void WorkerThread(); void Start(DWORD dwArgc, PSTR *pszArgv); void Pause(); void Continue(); void Shutdown(); static I2PService* s_service; PSTR m_name; SERVICE_STATUS m_status; SERVICE_STATUS_HANDLE m_statusHandle; BOOL m_fStopping; HANDLE m_hStoppedEvent; std::thread* _worker; }; void InstallService( PCSTR pszServiceName, PCSTR pszDisplayName, DWORD dwStartType, PCSTR pszDependencies, PCSTR pszAccount, PCSTR pszPassword ); void UninstallService(PCSTR pszServiceName); #endif // WIN_32_SERVICE_H__i2pd-2.17.0/Win32/installer.iss000066400000000000000000000030531321131324000160450ustar00rootroot00000000000000#define I2Pd_AppName "i2pd" #define I2Pd_ver "2.17.0" #define I2Pd_Publisher "PurpleI2P" [Setup] AppName={#I2Pd_AppName} AppVersion={#I2Pd_ver} AppPublisher={#I2Pd_Publisher} DefaultDirName={pf}\I2Pd DefaultGroupName=I2Pd UninstallDisplayIcon={app}\I2Pd.exe OutputDir=. LicenseFile=../LICENSE OutputBaseFilename=setup_{#I2Pd_AppName}_v{#I2Pd_ver} SetupIconFile=mask.ico InternalCompressLevel=ultra64 Compression=lzma/ultra64 SolidCompression=true ArchitecturesInstallIn64BitMode=x64 AppVerName={#I2Pd_AppName} ExtraDiskSpaceRequired=15 AppID={{621A23E0-3CF4-4BD6-97BC-4835EA5206A2} AppPublisherURL=http://i2pd.website/ AppSupportURL=https://github.com/PurpleI2P/i2pd/issues AppUpdatesURL=https://github.com/PurpleI2P/i2pd/releases [Files] Source: ..\i2pd_x86.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: not IsWin64 Source: ..\i2pd_x64.exe; DestDir: {app}; DestName: i2pd.exe; Flags: ignoreversion; Check: IsWin64 Source: ..\README.md; DestDir: {app}; DestName: Readme.txt; Flags: onlyifdoesntexist Source: ..\contrib\i2pd.conf; DestDir: {userappdata}\i2pd; Flags: onlyifdoesntexist Source: ..\contrib\subscriptions.txt; DestDir: {userappdata}\i2pd; Flags: onlyifdoesntexist Source: ..\contrib\tunnels.conf; DestDir: {userappdata}\i2pd; Flags: onlyifdoesntexist Source: ..\contrib\certificates\*; DestDir: {userappdata}\i2pd\certificates; Flags: onlyifdoesntexist recursesubdirs createallsubdirs [Icons] Name: {group}\I2Pd; Filename: {app}\i2pd.exe Name: {group}\Readme; Filename: {app}\Readme.txt [UninstallDelete] Type: filesandordirs; Name: {app} i2pd-2.17.0/Win32/mask.bmp000066400000000000000000000623321321131324000147700ustar00rootroot00000000000000BMd6(9#.#.ův}bGJtX)2o(o(o(m'o=}VwOWW"n(o(o(o(o(o(o(n(h)m/g%o(o(o(o(o(o(o(o(J#4AHGFHHHF9Hlm(o(o(o(o(n<{u|[-6l'o(o(o(o(o(X*=yH@I1JD 78NGHC6iP#6o(o(o(o(uo'󓇉[)n(o(o(g(7ZH?/+4MJOaTXG"7R{HH7m=`l(o(o(n(j~xeu\`d%n(?4MG;|1K5o3e:q8Wx;yHC:uR 0o(o(m'i|xpgUe>/@3m-_G4d3bHH?2LFH5hBOwo(o(l'mStlxxW~ˆ\p%cw8m3iAHG9tC7n8oH>*XH7m@|g)o(a$[mwxxo!漢u;txw=3e6k@4fFG9t=|H>HH?9s5iDS#5o([/yxxxqꘃp/vxxxl;rHHGAG:t7lHHE6j6j1_=|CD>^n([Hwxxq1𖃜k(}xxxxxt=DFHHHHG:t6kHHHHGHCE?bk'bnxxr ļ뛳k,|wxxxxxwXEB8THG2j"Iq(V?9r6kHHHHHHHH_$Jq>HHHHHCOYxxU^fiwwo"xxxxh}Y hhDry_$[(?&: '2K#A`3dFG)= !DhFHHHHS&lxzGl@Irvk2zvwj~c/p|b[co(E/G?2b4f3b)Ik0[:vFC-X' !0?HHHB5o}P𪟡o>Hb-]uMi}YiuQk(l(8XHG=|4e3d=~@5h9s:v3c1d0d+Y0eHHHGj9Bb&2_#m'o(g)l'o(T*;wDHDA:tHG:v:v=}7mHHHHHGGDIon(o(o(o(o(o(o(o(l'vJSr@Jo(o(S-??HF5hF?OwP'<AD3Nn)o(o(o(D9U4f=|6j><|5g?4f?Nvn(m(e)xEOj&o(o(o(o(F*R-tO4yd"{ZLo(o(o(o(f)TRn!!=hqo:7ynؑ{W(b) F@N*V ),O\{Uȼ]xEp!1墺j>GWӋ/{H)IKF@N*x< ~}ojP72Z.CjTu2{#W!G'ClWܔ-Z^P,=o5͙Ikq:0t^EZٿ^ҫ9wb?ubAY\#&s$ *j?رeu {~Y~Nu߿Er~mb,M0K`î΂w7xY!Gbk~Ǹ]KrsC\;@KK w`LL<y}Yg#OE$,c68W74u矜7/}#\8*~Y6[ uThm~oެ^~ϱo9ZΙh#Egٸ2#yخ]Mv}6_>]0ۯxΊvDuhYgEի,kJׯq?SXRw;'a!:7q%0.}ɨX+k. $^ŸH8sĒ ҌTB짭|vى ;UW$a(^{*?EZ~pyef)TV:,@#S(Pqdq \4azW9]^we_Q -f5`:럭=b7(pW'NUgg2 [Qeʱu0P()53'G>=0SYWn9܊ɀ0Xſ)vtٴ5/YFÏ2AY55rPRW <4HbF{|rzGWo(E}fzZS}ef֕SEih}w]~u'pj?%@eQpmR=s;~Ɠ5vs; of8AN_c"͚)iE V ݯuu.8UG4 aul.[(ń.c{zDs;339?+ lV*֗n XیS]3pE5*#ށG浯}xe٧9jVyjgk KxUZ5K@eCLX70#i0%?T#E:Esm\囃_LKqڡ|;)/EqQOXP1(wHK,FI%~8A}}#~NhaY|#+Wn}~qBd_6qv 6⭝飯z4ϷlܠDOD TXAM7~i-YUW-Nʆ%=}-W;57UXŐzS}D׏E@D#FKlWШUeg9`O_ϩ>yd332/v9|Ybix1uHҮ`$wN|t}%~P#1O~R7?3?of5]$ImsБxѺu뺠?#Y?j7PfbZ+7qDZ?tjǓ@KOpK0wr`|R 晢:U3`3ߡO~.J](m*۴wɀB3d9 cDžB|ww2ȐtS, X.7/- ?ZtBQj˞.FX'qtFU n $4v㨾Fz ;q `ؐWrLGkln9mJ{#UEwn)[I0klw@31RŴ]ݨ s'y{O0h$o" X(+qG6trUjS?]YcѓJ+ӫ>v߲T ;zf~vK 9i6QFpp@? -ZfYwv _=emaVGDh0.`gG$8|{M2]Ӓ4 Sgaȳ:>0ҘgI(Qe??ɩݸh)Wx@&gE6>sj 7)2 93$bV 0 /+zy.%ew9Af47 ZHa # %ά7K U;Y==X8`_5yXtMO2)?#F\}i:@E 횤\_lgd_pRx}?;ok+ ,YAYSN 7ǀoᏏ-d߇TמH AAsGY{8Т;&])mZn~@2]`ܸq9 M Tٌ^4 PoPHYjuƞhk?Ur胡-Տgv̛o 8˫TCIo8 @+;J/]~%>n_-W V>بxLb 0 78M.nEr'?ˀTvG/njnђT^htyh r!O]3Mn.l ;E{:R8^`vu)/G*yd2{١-Xt.v}rM:{j €ۿgq-1 ËZ~i|Vo辀>?[ugb[l, S@xu@nl> 2)t]#D'V&,8w5~%qqLh__Q霳2VAsXPuh[CZtmNu"n>H(;>a~%rF#IJ(jYX=sYO)i:[K䍂S?]놆<HH7t6&k+yq{*^:tP.wS nטN.V[ϪMz$hU%c.LH] ^a]woLweaG˿_ޒԋ`d*e_ue Ȍa#wK [×C=4Rc϶Z=ݎ9TQlˁ:]O+>| A64fkN{D`Lׯٹ;cٓq;xrV !FQXAZ3rXͳ^z7|ifL|TxP.le} DѰ5]!@EBˤUҖgBbVpUb;h,?fes!/ܠZ"rq ai#RݻK_;C_;ŗK$ڦwuO@ʙlVe$=hzg4#l{M7iр'Pd86^0QDu %uM'B}Ҝa&f݀iK?Tuӎ?HUߡ`k'7-jWsՖ'|bPq-Dޖ\t.-,ǿ3gL7Y)5)q5rXb#Lc Ṋ65 ?_r0'z_ǩ,L]FAR+x1k")8qgY\Z|GԔ;:bNƹTVǺihLFTj.HŸ8O'#H==qG 滭F2{0-P8%24`DպXJe n6I:[5 9LuJ?I퀰5??fmキƿ tqvY ~d]X'Q?HuE jCZÿZ]=wyRMO+*|p6}}86jnim E `qō bo;qy82 8@@Qe2Wp;[Go 4)/w BAnvd`0<`f$kԕ9&n7^<_׭U;3*p/N/bЫ N/ՈXF+}Q@+ĉ73?4K\TA-C8! q@#vV3.Z/׋[ 34b]֟btCj2$@XI5Dv+YczOZ6 Q*_a=a0U*~9$&i Qd տ&N8w?d⋭Le#-24#~2+1X&0^?x4}}}^B8=-:&~"x܎>zd`<90 )wnK5Q&\zh*RRրYQeTŎv0NJAN/̺ZxhPEc$F7mh xXգBۨ6qc8{[Pa)bW1\`%2,)S} G@b^pe[qͶ=5}82F0=u-_Fl$)GtIr;U`p]~uΝ;7/1Ve"Vlltt0xMmq\?Ԝc cwލ(}Cڅ!bǹ4Q:ql,7]vٕ}NF6|Lѻh^K *t*]iPH*maBO]sOϽ/fAV㘡3'DLzo}@c.dWn|]UܘHHf3̽%%Cif;Q~[!"#IYw 2AX񠪢Swᄱ+@3/ҢE[zÑ7^SoMDS;&J;F+cԲuՒRӶatlrpOL0_>t |mqNQcuLH7}*հZ@oꯉ[G*L{(UV+\r,r-ɉBtTŸc20jdmb]oT~, (L+XL;?A7׃ ` (iSa֗~O>B'7kxٯFZh 1=.Z@guZ\$5MWEq Hf8m3A+T!3,eo+#GzA˳dUTbZ{Ze_|'̇{K=?=eeNZe%L 1Jr w+/j^}gO̻/={mP1- *>wҁߓw7*mׅcw[#8_G9 y!mL!WR'=~?m]k~ꖆ.hGO\7@f#]X_esJzAHK" C 57yz/zj OjŒ ɰnz\GAqE4+vݰ!e|>IJ9Y1W_}NޢOw^ %-c=4{2ȪEZ6ahn3fw'L8iXLƇ`t>pa^ qvsNZ]ߠv&Lף/Dvvb|+t /qźf!Fp@H3ꨰSKJL^d,+*Jt?Y{饗~Y?S) ?-78"S~zN;VOg30ʈN;$s7؟RH*(X#+Wy|X XRyܿqXb&k+Rn8U!طV_{G]oOuXۻyV ӎ9C@3TIS"J,Y4,y?x$EUdr(!߻qD& D,Y+`e&`=-TOZ.څ+"r ضm8v5˽<8P nVԑqYNqiun#4 W;ڪϷ9/F* Y،$ %Ko8}~Cv= "d"SEcc;XqNr93/[O-V TFK Y5]UUQIIXRbE9Eh7xrY_@,~C%w`8G$,fi%0H$h>h0FkJ b,а%daCׂR:)h)@UhvNF(|^~NbhvH4 lnlh>l~e+G=a8 3~[6Xqb~C@b#$mZ#Ək*f)Fak\#41H몛I%nwJKM,CjFJkswt9&V-fkLo± 03**!2? ĵoQ#rmkwu[LCȔ'^.la\bevB)ƌbR7!mS6Hu0RYQq[IIROB7ТbR##8NQ5YvwBٞM8EISTƙS +6bNXX Xh+6lؐNVoh.v`!=2#q! !yjwI&zLf U/`Â" 7 Xe6euZTP1 4jwJ3|$C+!Ifld>,`#ɈxCݛ|<Ł-ϙzN=;#$wYדd|.N3@7JU n ƾ#rcMHg%ق="0db Y0 GYF(T`.^4`"$̺Vib+V+rÛj='%~%7$9T2gFʤ҂ ȴUn NJ7Ǻ4ۗ돨5u#Y4o| ns%E)/om7:@i)S;֮xϴq6l}]q/V<3I}R ?ͻҗ;_2j`~{N$aTS\8 l[.lU,-jcj5 $Zlw11 02`2DA!< =qE]9)!=x&A!ӊɺH5K5 òr.s*qe&;̅Hl!Uf+pY1OwL*&7'5L06|~?2MôȄi 2i"D46*S5;ؖ ;>=dT}m79:  7`AK fT~kTۿ'%ZnH68٢r,nnxfk!"cF<6_5tՐ kt?í aQɡV)X8^a8ƏF|S=Nl#+q{=Ѡ<R_C[FcӞ~l75A4>KBHB%&'~;fMnQN|@RKtp D]er8 l!${U5ݱ5sfzlzz!WDvFdOh+ym=b;s^yݫ>&BŊy@8-+޿4H3M1S~%7;7nDo?/rkϳ߯{ML{J4-12R]I2k;̕'Ց2=Iû0Ս8rwgMVwn!L_yuMxt+;luJۿ{&HBӠC4]zbs?WsB8'QTZ]ʏrgr%DA#LΊz2tŖ#wl~m۶nYX``W9a6. a3 M1vmx\ldt] 7w<|xQav'h V k/vl*K]V8ܤ[ kW+:N7|;WY  G" b(O i2)!`4:2WyAf2?k ԭG#ZHi;@N{nc|~W(:#A~4g,w'Fo|VhPC|j36lǮl^KKˆӍ;j@!?3I\Y,+S KS=>:IRB6CL]8QK~Q+۟) {ُt;lĀD[vXo[dMr-~7sE$G<9 $!GlI:-UEBY6=ǥنs k.bdF= j*4ka > u<$ЫDQ'n!dt5ˬZ>{YʲLQfIf[q7{pQRQOlr\8}Y s?/7=r#!XsS %8vp63SoyM]($m\T׼;ȁb/eW8aEڦп#oJI^TG S+,c]k|6߾ڌ"8g>0 93mh_O`kXi(h^5e[1C(KOhk ZobZu!hڠ.YBdvqԽF$`n!OcD6@C + KׯgFƟQ/IE"oMGL0;3@ÌvdPb" :㠸 o揯͜93 [NN?C{PJKa?aة Ԉ[3O˹)w Xa__EB)ZM (  /ڢ**1E\@?vn VPddwpKS@G"O[vDXOv rc0_@滶cw>d _zE@i)xD1wFb S奅1NeOF{ V$v4Bcx6}T|GtEUhT#hO&ҙ í!GefP r8-.Gub-{MQ&1غj ͜|ZMw"e:ٷjY.0!Q>n $wE-%Ů?=͐77g\66B,QQ'(}3E߅[\`+g QkJ`@\m #!.D0 ^|V \*yOn@B &bP-@c hkz`B"cZ;壥VBzlwiwG.6V_1keƿ>pd@>#^c$FY9ƑI@zAf!Z`DSҍvl 6vk,5Wsm_|kn/LwAPt|o}p٠V;q8eB>6F6*nJIV(2(ƚtu󻙚L{s:IYƠ k":F+A ʅlc4rV!s]^m [!!2_y0V¢}-NN9 3(<#$+G;-Y%+C긴9Fu ؄eJ|XP_ޘha=}8N:sj63[ HZQnn ߶BR+#1*pl[A 7_;b#Vn@&n uz P?OV2y%v2KZql՟1ks6!#e .ĸE|1'ؾzEN5؋o~O/)+rsln1ϤW-J[qL*u>r|}HX⯋rN2dkKNFqí4S{dۦvV. (\9XY7v4:Po]D+Н`CSEakk>`@8 c.R[4%F d$V;+s`BNڪ Ƙښ[A=puݺGzp8 `/}U~]ʙme=UvbIi @wj>c`}n7zg0P?Ȁ Nqx-Zx,߾:IZb37M&>HJATr=0sL -1+H5p`tz k`kȌ%Ls"bMVXO?,d:к;ߋq*@Gηke?qS-q"V\:!. a{'4!(H">ăSwgv{<ȆDBU d5DžLb8H窅}F1D'sۡ3 _⅋GgCUh:jsgvOO> ?g6坰h{bv4xPACxN)-])ZY# Zjp8ufμ#ǩL8u+71v~LE8ι^O( 4W0!m<.<A!5i$Xj!cIj؍!q {+a}>fmM0< ?FTX 5 :\87eYLN}E; )״q9"O78ȟ4FR{V`qmn 1kKkq1z>%B+fʨR )9Ѩ%bD4!jk%3'><7=ql\?$y `tPV{~C[0a @f&~vCPv6`n(!EB7ڥh4eƘI!BY 0kT" D$.ɂo EXwwϗMF^v Јp~|psaP?,y o?k|6\::tShߢdLl5aX e8<_ VCGTzojI&s1}6E~!5!_elf+p!+!w[uIˠ69<4f o-yet@\>)$V6UV/rl8#lzۿR*iTgu4tTP^>{h+mTI1>/v k6"[K+&~`ڈAϵ%X5^"x(3n)Ãci*O3,h q|8; <%l1 g tAjQ8x"9Y3쉚VM f p]%6yss^\ oo MS<~?Cpk@@y0&DϩfC7JH-7 UHLV[tFX.;S|w8ӡg-Ĵja[ h*q{ ʉ\B|`R3xGo7I hx")Ԥ~kC&D4 8"No6 N@5cAܨc\?oT?l7!BuIx/<ˆӫ\qӨ>xo*MPT\#=fĠn >S׫=UAĕpkq9PkAg`(8`Rk|Ig J/bqӑ_e%Ij;!&Q)Zhw Vi䦺 YU![/wWi~hePFmfx:yݚښnxhSj^TIM!'V5i24&i7m eYw)E3q<.`Wfo2?tYrF3o¸~.$Uxfi Jo1 ~Z:S >~=!`B3b\cj%J jQxҺ\{Z.; V3M"DiE:agæ\`>Ӭg.2mMG LV aQkĈu ,pp}ϫ OOnC_\+޴i3a䟳zٶɂP~H,n8s L3J{h0hxh@Hk1Y[p7G͇Ʃ±> ZhmV8ta.`]2T=#."ZA4Tbeu^_ p!bB+]1⦅A4c$ĕ^">i ?P gO@"dVR"(fM#6Xm: ##u׎UF&j``^ˀ%HR9h֖`-Ro%ԦͰ1?Jj@+yPN^:V`rE8Cf)J09G|iFxV50n>Lz&q Hi|Z%g!-qw­tB9bVğ7͟MXb aԔ:iPaccOjr+O>]5aj9(~bPOuPE40md.h9)ex3[0iZ?BXB| &/'(i%p^CtB&q 8ku\wDI`5HۇJi.P8$e2qz>猄H l~Lf=^o%^U 1bbGW%i{ hSv;.; %u5 >|cPiE*"Q+fw/χ. )0-"8 Dx~9I^D׋/,$" VuCPx \6SJV\Jуiuh61zvP a NE*&Y `OnasAKB ֈAb@4.(I(.Ӧ $7G1=1JJ DSűr έdq`z'Ph`:{?FZ@%zE.Ǡז|b-LL:  nHӁh*DR)N0"9% 4Hw1 z0[}%E?܂GŮX[۸oYXha >*p22TzA"f,o4͇~?F1*>H# 'v~\mLG:T v،SۉqOA8aOT0!_/.U.(#Dn^E=n[7p4?d50GE->L@ 1/f5a1J@\ !Lt=t ,kdzG7jc'1k /OXI s)l8T7L[N/i#w*SOcu/_nyx*|س{[ 6 PF_AhsN.wh;L3TEM6ؘ5=ugC1@"hH`k3: !KedaNn:9&Tۚ.Z{ˉezG;f"*!eH`=PMM] F7 -tpCIK76b)`0 }iq.M= xXS?y1sA,@Tc[lM3']IH{IܸΛ;`4hr1Gv;y=9UqrfU‡Xmu8hK]Aq|oWQMYٯfE'"u8?)ߍDP0ؗ__] e~bsq+"pDžEp7ht;sd^|!jRe7kɎ{~ \2,X#p['jtgy_PN؛[to 3H! |gzٸ 0Z}bC7`ʚ0ju37X軽*!XJ``f==Ȩ`3Y/s~b ~xa1u3/~OiVtH,kfgUd Ԩ >5H5)tZ5H1hsW:DPRqM~EYƘEe:&X(1-%uǣ3#pہxfC9bc̼v6_ҒT>2lCgV<|>ҵ7X9G~2Π:dz qMlIե nv=qhьƑaLX2^?YeNuY>TXip,AՋ0ZHpC $V32a=H]fg33a(*ºTf<~^5G3t55Hkd$3oBpC>TJJd < NPY;-k=qn;o [oz1df>gu;\C?)/O(,y V]`E8ӜI^?!˟X?(O Y*]Zp+5s+w}TNJ{,yTt^d̉g7U˿@A2Ƿ 섻|&8Oʺ=S'/Ϟ&i|vH.pu!ݰ~3Jgn Igu7oL{@2aVg:Ls]Ռs_VYy,ze`9iUe|pmO^Ko>Ҍ|ɠe޳~㸆YI r}k>/Lv>5|9o1#zwܼժ={z?Cn{<{c WpŠg%")0,15vPQ#5z"*ᶧ[`[s*:0wG~߽MSaDpts;@㰨Ez _(CNl[iO.-q6Zֻ5_|^=[/}eO~r;ܲ$>7'6#z|zyڪ%Z.$H(쾑 PePd:b^fUIΆ\` d%p[Rr钳7¬c2$ϔaQՍQW2 (G4fწ`&!&A(+dsɏW}ˆ]0 DeNũ{&Xv@gϔӘ+Fp @X Wc'>8{:>Kp87\4KGlՄ XGe0 f z= DdZ{\|X!ÊG6S-E+`z6Y`3POH%OKD$4=`QX@o`rNڴEn$(9hRlEax 85Nd9!"!P('piܤtZXe#Aeʲ>92SҬGѥP@9/9O1CV|;Ċ}xc?K?~`tK .ܵxQ_9Ő\okk_񩶾v#qpvza !% A9 )V"w|Ar .oi#u <':N>2t P•7"' KNmp(BLin!;֨*3VOͱ`0w>?M|b?=b-2faP&–(PGi$8 ͏BC- b992: 9^5D[wﺤ/ѴG' {t@&Aزɵe;PyFI#KAA~ ! TYMs%˞OA`HPC*$;Z,6[Fx=}%Ճ' kߏA8ɴV7'﷾a8L$p_v5\3pwGgola֝w)>W+mdVхxbK9>yK4" `w!:pFӸըN5ɊnM?cGD#:d7{ǁ-~O'tHSR{A9@YX 9`eUCKxƢbKD3a%̅(( !P{Iw{- rjgl $@ t>ೲr/cۘVM?™̙w"װ>?/:ht"E>e(]y+v i=8+Og!uva lCc7vR2m\0KgϳψyUY'N jkj2IYy4B F@vb ^Sꃀ&b s2iQ;7#rfew QH!3_ f>,y nUhFgiPz/|l7|""&#hPA!,[ ڞ eH; #]DJ/, (}6v5tJZz{-c{}, ؆˛9q hyN4dL=6oV $):V͇: ePkͱQnu87¿ގ5eP%hmNf]tW~a! IS[?j kKϙ6vw͢Pfe +5% p~B4{:r^b$ 4p_E1L[[cbV0YP`Ŋ͛7608t];R,+ldK59 kmU[3ro8Oy ?RrLS\vnyHA\T*ICA&^':r˛.{kb2iu t^/.8kv~|J!*bNjxZ_ʒ"(˶RMglͥAU7Q+fjP@SC\*-b@hʟ; ^2ax>2*et៰7 jE Fxew?T4+iI&?Iݐ_2.CFQh:XjÓ[N+|N8t-a}xOr?Ӹ[]jPyxIi7rFVfzۋ& 9Ț*j9>VqU#*+1-8Ȫl/2?ʛF#XǓZeEyLaC+Nv^dXYB#& jpo0íõXkj2)XI@  aTBП DR84TNDT[BJF9``7Js=îPac..3 dc#kGu0 H n cاhr,qYh$X2KчՃia,H0Hރ5mC) HE|4 ,kl}IhOZyؔ3qSzp9wnx~{/u%D-p%7bIzN+r—,e3!c^o>" 袼]7nAK.`]Ɠ#LM@dN@:~L1&t,<^/GTHmlȃUw}YKxs+ T aSS gUNtVlQ*3[cӼKcg!e5X&h EҢ11.N4CTCXѯa@ jx(eFYy(* BdU;X VmV~]dY63]˲{`~Ev< a= ĬCDzPAyfڠ4%B3{T]m͇D8TDcFܩa={`Szd`нg5ĥ`Z6\Wzsd,MթjJ9m /Z 3u@).05B'[LHˌ +bV)3)Yܳ8{z2eu6b(1."AklS="57|<&xA}ay])?k_ͰOHqG4 k}0'!!=ej PJguph&iq% 9F@CX 1bp${- 7w9-ˏuT6íXml".s !3d%&jkW͠10!?T`Ra y֦M]2G L+4%~v^6uLW1zAc<ᵚ~, %<JSJ}y5̯ҮBEe>ʦ ~tv`Qyf-WӶX0Ej0N +ń\K+l‡ 8t[)N~_s]Vf)&VC_(3˰AzC"UgK7m1/*'np#EU"rHM(1-Nr]ўX_ƨ-OȹY&ٖӉzIUe!$:8Flbi.v DR5pZAgنMnԵS nrZ Qs]Q m ylZLi0g49c_k@`aQ2O`*֪\\57c, E Tn{6l%3 ##&423dvD뒻}oZONxQ;YWRg) IX+RVb"_zinwoG2pv̙ۇɢt>n0G h_cig{ʄBiVR: e$ͨ7/Rj0%M$bҦ5LLLZMATK@T/j䀒ұClJ*) N!XY2w#Aej8?Nio_n`L #c'jekEIȿ;: 3˼pьpktv}ei8d$=t;ԀiŠSU}:`W^̄N'EXzص7 v1#TT2[uZKnOho5g9b q Y(X)"b4 !#9WjjbG}'ZL@|t_eV0%Uټ[>t(5 O>2I̮,ѰX(ɪHjdF8BW /eDz2̣qqFp;oV#g<[GS}gKDL FUEL_}'=#l7L·i h(0N"C2,/ eVxhbXQ_!_ > Ls@߅s"DӇWwgTw wh)>2Mm#ڡr=?Kw?ٜ+VsS ٮHE (0v-lkKQxִ AMC;' vY<ב^S=g.OMkqM >yҦ0LcP`ЅɍmT'*6'}Z, !k vڹ𹹙P!h՜h߭M!6xbc$, AьC1eX%'R4sKe;O<l cA4Bu  _[ c9NNC0眖M_Q"c9e]EAk: .悏G2Z 8֒b&kuhnn7#[ҹp\<c@6!D Hrؔ=Uek544GߏpCejHˮi,4)56)8!p15:/gcJVE^Myhnoc S aZqSx0r4~ֶT!›`-sdSŠ9t 4U&AhKO0%9 cC;qK6X9^[Vz0gor <5 M| c*CI.G{+=\'48 4bq~;8}`K`*_Yg [X-ygs#' 9ly.~e?>ʬ*yZ\McN68|1"H4D4#eQi (BW^ (-2UҎ^xzKl&VҚj[0ѱRPF^b3h#~eAq~c2|"('.@@)x%Z)p V( Y>]vU<[@ |4 7?86a̳ҏYV#zȦz'[|% BOYw}u/)̍TYL//KB4TV)x 38mu/p-9!(`[|lIT-I\jc WxِuB zw8h}6 O5aK`%q# V00fWx\4/~J `{KO]ۈF0 آMiH|Y1?b#3H#Ɠe;tJx|7i^K.>?HdI2;2xxWH瞝իyG؝q{c'=->}{E+g;&8gn"WOc{rX$<3 t0DRpP0(bO+v@ᴙߝv$nb<N,LY/I̷SAp XIfӀߎ@gzj{?1ӊ?Bg`dZgB,aZ&ޑ/O8Xړv0 /9;oVxXSlwt Da,6r_Ӻi;=+{x7,l ܚF%r=1lHN0~6exjM`N ɭyiEp4? R,?1X1ɱczE^o ٱʑV Dl"Z  t/ ( :zFoz3+Ld2 0O,k1d9ŒӸGz.|2v@[hy1h#glӃ7pC1a*泱dX<ں;N0y '+b&^Zdf?7L@a1%E.Usύ~-Q:k2H#! 6FjnXh-9NF>#¨+@ĻH3M^ƃLC:hNZn))8"3Έqa*W:ܹCR\8d-,€|^676~z&,sY.zsOD *8X;D#q>`-,xpn=cUլ5o1yG6\;< Vݔ 8B+ 1Li6|c$=n/\x< ]ts'֖lú)`g6I䷥=$C0dq'ZSQqyy+ +-mƲuĪ<mq?><k mYqMyN;2|us^J# xYDT;<z${9XlmpT qFMM2,[춎 'W=lç*l@J5ڑ_^Ne395Qʤ*31=@t<+gsf)ʺG `ؕ9kV@P 3d<=.d4BleSH08)G=PdŠJv h dm0`k>qmÅY$/ЬJcEء8Oh@e\*$ _y}ŋ+ksy/>DނHAE6}E.(Q3'UXK\b>'8y+5]bFa J1S$`z\91nv|Bx-bd1/|$6 xvRZ`W[6q!ĉܱ]~)hq<#|!eH7 _1r8b)ėmqpD? "QPx&l5GkIbuϼ<% b nfsj`Mp䒯O:`ue"d#[sy>pO,r6vd%GU3{Y)xln[3,,]SNE9F<a0C\fπ !l샥su `w[ &pc-@ |uR4+!B;yJ0qa,"ӑ%@DXz `%%I{̹=t e<3v8neߙ oj_spCic ,B[T͗flؖk^hHP2胼kjZp.(03=|>e_MXM\I#Zju_w^RUT@џMϙNhވZcQJIQ9kY/姃Jq1iM<# rdyNȊQD5!$uSslyPf)Pz[S*C# KuJ>Q\'gZ8dzʸ%b- "1KG0mN);00Ku Vm>h—gÕ3h.?6`ęcmR?D`*%F&ftaOoRR`V08Fzk;cAxmoNRhy.Rl Ck_w:e3}z/l<@k'!_]~chܢxN2rv )%EX hjBUDha5Mp=׿4k4IEe-pօe|\WT(A Y?b,1Y&3ч&`g jZX8Y8هy(Sz=A APC{ƾҒco RSQ9.cx 8?yF;#LBM}' N+vR ?}y /&OK0)Tw"߂,w _ʺJBo>gU[21Hgs~VdDUd@b-.se| 8dZ`$1'sax(n֞`O' U!BuUm8;86hU( v"? T 2\!!ra(y(3?9q:\32HN:U.,&·{Cwb8hV)b 38hC79lGI 5;Fkz(]>Sw[XoR : 97J, #XFl,U5I@DMTJ`\=E^QWGKXh%\{r ,qזS>3{Ӏ\d#C@1HӀX j(.Q0t6#PIƘs]kjGc xm ehBq V7L"? 阳anY\1+#fMhEyc7{(32E~ǺDqߘ;s.ә ?i)1X?( i,͘%b1 Jq<&뵵{ _+p8S>#/vx(Ls=?*?T[:$Qs&XBg7 x%N)>: 4+6P 3Np zЍ- >77 ] 2mm+π+.'`$m FAXK3 ?~_ !/=Fv*:p?']I|sad..uͅ qBc0AQ%{buѧ⯿N7^p8ʰN6'qLa2+x/b}\iFf,,׆Bjp' ϴes`=;Zt~ 雷Px 'CLfZ7oXnjƒB `x v:څ蚟Hr}, 3p Zy6a#-4FBdGSf+~n\0 3&VJJR?"CB(~\Me^3'N5'UzX?,LdA#(sZj& [ d́*Wy)y,?m8-k6 50M_Œ?`$\lD/)b~nXNk54 NƐ=ޘLT qnO!M|ZϞ3'z!S<,`0oH#[n;G10F9Jr0¿%>t0%1]m89Qpm;, } @-qg~L+ ]KkN B$".C`okptrØ;daQQRHlUL{%eD԰(hBX:Pp ^b?MDȇS >bRj]J4B('۪"1a5FUX% R:LVQQ=LPK'YgB)rA,JRAƟ q0Nݏʨɳ[Ϟ%O(7Q.kH#M?q; |FMgIHimPzPO|v̰LEZe8:] UaG[~jN\>PENի-vKA5=z4u\+18+"dfP&lfrpMҧKiܧ6|fy 9_%R̼R9Vpp6%ɴA{~frFpi> {K0 W"NYף{XOmǼr~"rKiz_^gi iztW i߾{\ Yoy!( {{l.q-}tvٓhS< bw0L+t„B;zJAu.)ɑ89P m 0+Ï.QC,?Xw ܗ"oׅuKئo*2TJ#UR!,RF_(/ aR-2HƑm5]ZۈQ"̺C}C BS NКxD߾5CLfV %ټzDqwN43˄nH-rj#Ԗ.Xo.EGrW8279X]3ooUDw =`Y斻 Gf,K4in{Ctb!,5'zqu wcA XfK4*se {'7ʇҢ _ {:r Nң,Pz_@V^veSWm\uc<_%;Zǃ9r~x]T # e7'woz6H7051n9Sϟc疟 rJ%; J5*tx ߎ:gUY;@xݰ<% 88h#\1OCrN5=(]s2>ƌӰrkw DdN|"U0I#aVDOoWkp/\37  {~R &zLt { :h'2H˶/ɘLW)g I@e̜l}]ޖݻM Sf|?_g϶Z[N \?;{/,,b/K4QK%O!䧉ILb%QAAz/˲la{mk}3,guν{ι|0Ζ #5ˇO&s#\>9MttzFK1턿n3c |TN7 f<]T`CJ<"%}1J4`ɩGF{6tÿֵC}w6lT' :u+ =!3t3=X`1#۷QD$J}~.HWUU088.AbgN9)ɧgjg$bn'm>3~ "C.6o~Mg] @IgaQ}M-YT;n'E}t FKtYv_ॵ l;71U_Xi08RqL 2t2q4ᅽ. u h=|VУ:V۾#BooG7tz1['VX|F&ܥ/fHcCl(}$g -PmU9 8y m~8 G1,G'Ο@(h}VK`I2م-SД bh„ᰄ5In> o$x4pxbfM,gա{I6ih. &aZÀ` ]''gÔq4h@hPzv5n>8 3K3rDخ̧[pc Ixy( m@z چl (_~ 64`g ) NyfyPkx3$h$ }O]FaE~N݉W_V) /b=>\XTpK;²Ol!_H_/?Z%LϵM) X.A CT#2ŃqVtw9O'8:}EA@P1NMUX,Xa2}YlD]Էj'4s~*|mV!Ahc?!ZNL66A@gpNy|ez\09)P==!bjQc@Ux~B!v`]] ۛrb0i p$3:7hcNwXshV> 3+t??WLM~n$#ԆOs诔et#a2bM&=T|ܰr_ 4:ဝ`ʔRO^L):̜{b 7ĐFF˅_Za,2ڬN{d}ʯ+:[))v XCQfDq D2K*lcz5(*)Ys{`-ԭHOɆ@#\2 s28bD!LhJ( >2.ʃ<yE. ]`q{P#' TUws5=3rM 3o_\5ulKeDdKj݁zzq=F q8:S2>;9/,CFKȲek3j]ۑGgBPzy~\0& F.2ƻO~j w9gD@owI.Ҡw7:?^xL ]]vjx5ÀXKqv"?>9ֆ8,L BPyH7dxfX^(  aD:(iRi*z7<UE>^pߜs{x$ xL0QPqш8LxݬT>}=G q>>X3d fDz)38o9YbNhX N$xldg;\\w-}V.%T߯^L<sn?A',Jusz@ X6;.4>OTȨ}Ix6 >D0_TQ6R- sl%ݦ0u;r,H>DNZ/լ#sk 1U[oٲnU]Jt%Zb;V!MQ4>X@Kn9z4ȴ¬1ey5>Jc >d77/kMFKZ //rX`/oഌ@d*}@]VCSVZ52?z2[~oh!Y]44td?"v[Km5CtYzv?Sʧmn.i^5М0O IR9<'ÃNA];hŊZV.OsNc7a!ؑ@Wݼú3OE46 =ʄV*F}Hthz} %'ݼ]B/p&tn- ; n 9V<cm^,H4H;Ϝ#CgG t•16g H$#TCcNdU~~ǽ^ut"5d<^gq@釞S?aL34YjF9v 2*23hA=6$aV|mfGJu $[/|NO"6^%7, &kLRY< MK+&f=?33jӏL#dt"^2q[x']$Z`J'_#csNqhЙ@j(vť} oio z=^paz{{XEaf{ܾDhL/3.=fQ?4 UQ¹#o8;. ǑSXjaI7uò]^hp%.A*0 &I]! )u+OЯN]Ȑ% k&Mm~n* f73yLW8:+gu,R`EkZ_ ?ZWO}#:A$%OGUUꋯN/^uM0 O63SpXy}7sxB$"d ~yi!7y~$p ѵ0s=֎>@ P APu>>d(3cF/uWTgMn͟  WG)xc[:ۛS!6dvXQ:dDܼͬ4 '8ے{x9 N&D4.ؑIl#ɾ4(]S%| _}޽{Q׸r)N03N WZ,X+b#i-~( k {`V~@N/bybDZC]>&!'hԭ0Tk4CNR#bPP&&=z/rk~bI_r<13}0NX3u0K o>RM Nᜀ/ |'0k޴{U"&ah3YVΐpy`bSto#o'TML˦MBiM~LjfMKuќt{$ *4#mVL}V1}A$@zt7+|:ɕe$p8h[N%HzPPk wqQ^>K::FscK;EA [wC+jqњ#/ Xdb;)ɈVw@$eT f>h5dGa}`f3tHhȮa#ҋgxvѡz2 IHʼ25" $-: ?!on@ `erPX EF0}6>>u->a ܋ga9La[MNZm\4=><_t'TUU \X8cGcNX<yh4أ1Izz}UpVpNQ2ci8#@oO9SneWwv@4)je&xHLnPP$ao Şؠk]egPY01FU( Kqs];q})8 㾄.nZ]ޅ|/1ory8/;?$=Qx@zpyydzCG~Y=jT1+5Aw5Q#I:/iC>ʱ' v8+Q$u%f±ʛC_ N8iEOg Bz~y'e˖IA9p8R{m?;\*4j:ViD*+yLc.ao4/DMvJ{ד՟.Iu\qr@)`˲9Y'j$)"X9h5q6ApZ1BqpiFoD:LA1Xa u cQ$d,vCve,< $ЃQnE1\Y_% |M?ʉj:z%cKƝH`X3b?9EF6P$[Z.5HگLyegɶYqrP8Z7@M@GQخG _Vm*Z'x3R->K$kX^pLiĖ* * \4Fd=g9 FMD9 cXƪ%pi>MA3ɣl\whW/k3ze 6]WbFb jnp{;}SY4􆫫ExIjt%j,QUHI.BBc9d)+`UƏ:}'B*M"nKE2y| 9ċzR,ȁaX-~%7 ~;Y =mVUAzT  j Mm|3)b&< _(qm.7>Q aci=ګ4WHk,+ ޶uwʌ7BMgHQ/$VIsll v73sڶnLb(̙q޽ ҮT. D_z"2PӞ@%px *wgo; ,֟5>8`rr&f M֧D(f^>{~ɞ^{^A*%4gbLf&.t_^3a"ADަe ^BT fi*S"oaS \ 6M*5 eV@w b=EދFdk\19Shtvw>eÄ?z EI  % ̈-5;f[rJhil12=|>LX \sqbHP T0'h$[dzL-:ܲѮ;UT?>opsO{us?bXQ\yvii~kKM  U|=PuSϦ˻t25i[aUp$Zr:]˛WD|4[UwmaW$uz.$Y&m1ʴ0tQfb9l}5_a^F"ݶ}WM;`VqbEUQw>5K$)GР}t$B3A 9e}CFq D9KSPza<[8e'Q ZN.J5, v fHs0j8W~}\!YmtCj@'ߞ$_jӐysDqıS=Dta*; Z h$8QWowO(!x_ kye_W"|&[0V(S-}+q1{nm:hU˻|/$ <T~tCB8'Vlv-3m9 dwg$CLD1x0!}ےYYNwIi֪,+!@ØM)j}6szE )ŞNPSҥ5J g  eJJwO /$YQm[Ȍj:^hc\ĥO Q Ku_٬=U/h֔~ _o(2.p~fs{ǝ 5R,ST#a7Z-ʝN\U͵Un#ցNL|@TJy``S<0D69tn@o?LF?ӓVԿ9Yj䗿DSt>h-C]{^vٷ\gq˯J:WEQR#(4G{uBj *hbJJڵ#`3~n=oc& |$>cXx !v- qOz 5icn B뼫Ni>f '%|KSg 8zM]uyI7;kjϭ&M!-C_voSk3;v~G/S$'`LyIȭ hm5Pu[̌U#]+6ZuW]P{A߰qpH1c2 WNeL5iXQ}fGFtGƢڥ3XKmR=~হ\ս:fgksN ި>+xaqǀS3U ߞ;"VX7e˖sQ Ň)ϟov643U PP|JZ$AEn뻻,O3/,Nrfby #ٳݾ:A朥hGBVH@i/d~e7#p)rRUŖ9S`W7\o5*-:(ʯNBLYVhmBOY_?gHЄ˯L\hcg9:ZA2{n}xG.vLS>wԼjَs% Hap# x;1oq`ٵ5q77n八]$'l nꤺ p xQ4HHZS v>HHK8_rJZ,Zp(`嬸Sb?K5j}{g4¦$iyX`bl4@"ͪ5!tT/]Fh@ʠ+*ܛRܭ&^ *.0/1 &?n) 9S:T瞈6!^Udhf`?ڄǡO:d̴^j V.1'J.|4lMoi=D ,s`@ 7 $V?eb^pj-Yc|;UI,ˌ*DO5i`!"^-lme1*{wo}[E7'p(HTpx& e}_z2,W䁞Eܴvc@q0&]qqNO!S/D_hP@>Tی]RB\[nSXzk JyzMwp*(p=A&Yށ|(JIL 2j@Nn1Z̋ck2O'Xǀ/HagiJ?KuAQ%]%׭ЧnѶh2!RoM-I_(3׈6kI@0/?r o%p$l:MfiYz "N'/b榐[{ ~giJَH-:+Xa։ٯYסC2],'uNjjƷAK&u1sDZ? z)دykCj}a?#@cNΤ35sbՔ0SWVquj|D|8N> QtkZٹifY& YBH\?O?,}b 4?*>-8I"(7`5py]%,k>'V!a~z YTCP^Ԭܞ& \J%9MoȺicGNN`MJ7^+0eJud/t߇HZq.,| ̫V}RF,.sZƲٗ81$$s;oX4Ao{{. q1XhQiK?*|L$6OWzL7DaNՆn\CNJZ!d3bϓVKUQA5C4k OT6Ӧ5aX(w^;:e{g{8c< ØXv$j"[ DRf! ;.0Mr~6-8*i]O?C8:UG'/;C\Nʁ6n?HCm'z81 b*}p!C%́t> ˺A-?Lģ jlM^pk60џU.[ NxHO"3X;lBXvŏ>=Bhڄ1ߛT鹭rb,'WV3lfo?ڱ'z1\%1&Kos3f$P`]i#;cYG}Im{[(40QJ98RP2&یr pv_TYN TzDL>6/_E4ϸRl&y뭕L8>[k+1g'X LeCSeSbfLak&hTZQ{Jj@bi1u:4jqgղ2 S I'8N#GaG6U xaI lٴ;m lYv[ q}Dn¹Iӭ"!jdn>92~Tj ōzY{L{'ɿc> 3f|GX\4Ɩ0-h p83AŔXP{{/U©Az'-04= -gL鄹-`DqTVjB;>ܹS3LVSX UP5Ћ͂Cbf/ $("wo,jjjL2ct`L%.;Sw &36AK\1CY36cAkWաsi\/x7A2a8w! xm͘.!$-&|0oh+۷˿ԣ)u-)9'p18A|oL1/l@VTӤo`UnSjK_7EV}d/Y2=τoQ(LPfIXդGI90c\~7_?穻:қOU bpv:-ӸRpp"cB\ 6O;؆]xyaIy d 3c2R|a?C`zʓ Fʆ|ХpjxqƢELj֍g=uNz9|(D<:[^޿c]\ :~ K[c[fsl2@F{A%w_8]q8~0rgzJ ۯip-#}[=_~ɌW_]>xӸ1, ?n5hF@*l8%lO `_54g{)'̶8Y =_ bU oeU/!*'uJ;PZU=ZͅMµtft>\_zD8,"I)`KIi ,!p A%VSx7d+^޿NtLٱW}$[?ʙKmVA.&F/Ppo=L=\tywO.\X (o!S OvaW :[i;~rHu&/lBo[F.>*ǀSUWT 0U]yM[ř gfl:@ڥ^qcxW>eI_C|o{>a\vsDk̶ 6cLW;3q+~础[q!UT5]9Zf<[C{`]ҵfu> zi'[㟴*<1j ;WhxNg@VH3mkWS8:U'WPpŬ/ECF: ΩMHI;jqK_7C {v鯣xOpPL~_üZ8 oET?xzV^Q߹{ 4b0Q|}ISe3gb12K:{49LUBRs3Ql,j äR f&y2iD!U2 ~wA `(.$%9xg1TElm<{ɖ}Bm[z+<՞CmGlfZ3bc&T61,a1} aG46k \ sotCd%m?<)&=`rb`&M歏vE_NVD<"Bi`O, Y +ob˰ί( †ޱݪְVhq*!Nܸ2f7vT3?tt ٹ'k /TO᛿4uw>$jk_z8D%9ł:ILLQaYLj(!d2d½RCw4(}#8|.曋dy_n%3 aYApJ"ƻ#?JG`_법?;w~#BNHr+hcV4%$2SNG\+)wޓ`Xz)tTA>U|Y<3) ]%W\D?} jvμoڴ'WONB2JKL3T; ѐ@$`$?(St}\}kqWj'/f\d~ Ζ'ׯVq6jocpiiekn}68QT>+SM,kS1 $ (Lgym0*FF##m^>d$ k0b;g,I'k[^>{:#NGc_G%K Γ֟T4a.8֤"Ĭ?dYSSEn\~tFu n͸D &/IEUx֢i8b7ȦŽ6׋KQ?@p$ku ƧIJ yFFp? ϴw[='8F,Hi71$~+$x!#awGff[)gTD}X '͚vn!S95YRM`2Dqca")[6G~"Zޭnn&zs֟Gqcԑ @ww{Ұ_j1ԝ~<'6 ˨ Nq: H(°Ga]&3~j?813@nٗyRv{5 m}_& Qǘq7,w Af?dK~6Ǩb̙ڦ[dKcaxYmO}'8F ~grud7{&po&~ QAZZ ;jݛ_W"ٹ NLyK͝Owv^~˄81*(+?.+G||reINUMTCR>zõn}?Xv?81 w~^ռ+6!/OCc^{ͮ6mLZqQ@bxWc0<ɔ):gz{-.O%.: @Wٜ%}70JS4! gi^7;kx( Nq Tk]wyR }yW|`kް)g};% Qٳr@]qxtlC#_5$3]~881j/N×r3lw{7uwkN=~'8F\XRbuXK#8Ǘq#/1G_b;F{IENDB`( ...6NT*66?33'88-Z_jo#5 NUU'7Z /  5f$H"Y?? 1a"a"3'' -Z l'J%RUUU$$$8:' U$$$'tSn'_!35**AFidkaf%>AJ*** `Nn&k&C#:H$$#=Bl%BG`H$$SMo'm'X *x***8 EK OSD1'HIm'n'e#8(7<3bhfl (9b=Dk&n'l'C  7UUU-44";BQFLa(/q$ -;G2< V 4@h%n'n'T(e33*D2 """,:f$n(o(n'Y +u??kBPmwug|IU(-k3<g$n(o(n(`"6"""*?  dAMkvwsWg2< ->?i&n'o(n(h%9 U%*0,.m",/Q$66 b@Mmwwuav<GBJDk&n'o(o(m'? &*"*X` ~=@W??? kDQqwwvjDP &V ?[Gn&o(o(o(n'E+515H ah"~KYsxxxmJX#(d $HHuNp'o(o(o(n(O*N?U!88M ls( #(XiwxxxpM\%*g --)Vo(o(o(o(o(Y &j$#/5+cj ov V\ 5.7f{wxxwpN]#(d !-c"o(o(o(o(o(a#&  15wGNLS/50***fBPrxxxwpJY!'[=5i%o(o(o(o(o(b#+  ?).c '!%DUUU?33/%/8 " &]pwxxxwnFS!L33iEl&o(o(o(o(o(d$7///2<:F#':333%80)0%em '+/A7=+p(f(f(f)h*k)n*s,x+{.---.~.y-q,g/[%K@4(  3U`ANswxvjOa9&Q(1O!9R#6K!-: %*f%%%"333333 mAm&n'o(o(o(o(n(m'M ;07dxN_$(8**1$Z` ry "- AKQRTUVY Y [!["["[#["Z!Y TRMIHD<1'z%^"< '   %' -e{wu_q7(Q.Lt8o=~A?9u.T~$4H!'*U''' ---$Wn'o(o(o(o(o(o(o'O H4AMrMZ %/333&15CINX]BGh?$o,?N[h$m&m'm(m'm(n(n(n(n(n(n'n'o'n&o&n'j%d#^"["T H>90} \<$$ |KZv[n10U5iC8p9t=~EGE9v)Fh*4y!!,UUUQ5g%n'o(o(o(o(o(o(o'O P$(,lXiuJV 'U80$$$$$ :c,4@O\!c"i%o'n'n(n(o(n(n(o(o(o(n(o(o(n'n(m'k'j&e$[!SM<2/kC ) ;09Sc3/V8qFF.W/Z-UCGHF@-U~!-:(((55#Nn'o(o(o(o(o(o(o(n'R,V5@ktCP 33" ?#f'5=FSb"h$l&n'n(n(o(o(o(o(o(o(o(o(o(n(o(o(n(l'g%d$Y KC4 %m+: &,G8nFHG:x.W8qGHHHGC/X"-9...!!!E. d#n'o(o(o(o(o(o(o(o'V4aUUU &WO_wn=I 333 /&P)-9IXf%m'o(o(o(o(o(o(o(o(o(o(o(o(n(o(o(o(n'n'j&b#\!N>5#O^/A7nGHHHGEGHHHHHHB.T~%-5 Im&o'o(o(o(o(o(o(o(o(Y!3m330;f{wh|4= UUU***'""4"_*3F\"j&o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(m(i&^"B "+<6lFHHHGGGHHHHHHHHA)Ge,_#n'o(o(o(o(o(o(o(o(o(]"3v$$"YM]tv^r&.c$$$###+#l(<Vi&n(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(o(n'`&5%55eFHHHHGHHGHHHHHHHF\!l'n(o(o(o(o(o(o(o(o(o(o(o(o(j'E(/LrCGHHE9n7-T}?5NtU)n'o(o(o(o(o(n(o(`"5{"%*Yiuwwm8D"33A 7Xk%n(o(o(o(o(n'f'< -2^EGHF3f -!QOPOW^FTcptxiefC(-IE /1G<|GHHHF1_7n4j+Ot)<'3dGHHGC-V<}:qE$5k'o(o(o(o(o(n'T.X @!'O_sxxxw[p' .s ':' !!!K&Df$e'= ,2\FH@$Lv+B(W@F?=}3c+PxBGHF<$Mx "*==GHGG4h4fA7GGGGGD,`,&IoDHHH<}-WD6\R,n'o(o(o(n(n'O MA 8Dbvwwxxwv\n'0t 407j:EPaYk1;)CcAF7v#Ku-`AGGG2b5iB2b.V:wADGGGE+]$74jGHGC-T?=|@+?j'o(o(o(o(l'F, 3O`oxxxxxwo@L*$ (-Y7EL\_sns`q14Z<~E7v.dGGF0\9tD7@^_(o(o(o(o(h%? %GTi}vxxxxxxvXj)0i!%609tCP[nktvvo@$U2]8q,U&Gj2dAGHHGH1]9tGGGEA7n.WEGHGA"Jr&PzDFD5h3fF6^R,n(o(o(o(d$MDtvwxxxxxwl:E  !$E3=K[cvrwwwww\n.8\,T~.X8q9u2b,S|;zGHGH1^9tF=EGH>1^EGHHGA-[8q?3g8p0\E;wC(9k'o(o(o(_&RVwwxxxxxxuPa O#&V6AO_g{rwwwxxwrC$Y,Mt0^BGHF;x,R{<{GGH1^:tC/ZAGHEBGGDGGHD9s5k-W;y.WDA8:Te'n(o(n'T'_lwxxxxxxwey2;$! 'b9ETdjuwwxxxxxwdv47`=CGHHHG:v0\DGH1^:uC.VAHHHHHE3e3e?DGD/[2a@-TBF5Lr[)n(o(l&K/kwxxxxxwpFS!!5 *#(l;FWglvwwwxxxxxxvPa1UFGHHHHHFAFGH1^:uC.WAHHHHHG@4f,T}/Y2c0^,S|?B,S|AG7dM!0n(o(d$OEqwxxxxxuWg" '`$$$  ("(p;HWimwwxxxxxxxxxwdu2#F9rHHHHHHHHHHGH1^:tC.WAHHHHHHGGB<{6m7q@GC-S}AH<{A(9l(n'Y$W^wxxxxwvg{9B UUU!$j9DXgmvxxxxxxxxxxxwh{A;*4M@GHHHHHHHHHGH1]9tB-WAHHHHHHHHGGGGGHE9tDHA:6Ne(k&O.ewxxxxxwoES#(33""!/2K3:r(55L:::  ]5?VglwxxxxxxxxxxxxwlKBB!1JnFGHHHHHHHHHGH1^8sB.WAHHHHHHHHHHHHHHGFGHD5Ik]'d%LCrwxxxxuSe&+W3 FKf|.GJR???$I. 6P^j~wxxxxxxxxxxxxxxpNKX$E*4aGGHGG?4q-a+]0g8yBH1`7oB.WAGHHHHHHHHHHHHHHHHHF4YW*S&\fwxxxwv_r19+$$#tzW\33 1(1GUfyvxxxxxxxxxxxxxxwtUZU'h%=%5;vGGG?(U)@     &4P0h/\7oB.VBFFHGGHHHHHHHHHHHHHG8iC .I8lxxxxwg{;E $$1sy OUw "$h>I_qsxxxxxxxxxxxwxxxxw_lO,j%b%31F@GF6u0J    7U+Qz:vB-WA0h"Js-`:}DGGHHHHHHHHHHHHR[vxxxwlCP4***"""%+/]d y`f(49, E/7Rbmwxxxxxxxxxxxxxwtng|XhI6e%n(T'0JnDF3n"4   #5/b,R|?B0^@>` )<]2kBGHHHHHHHHHHH?31Xh|wxxwpJY!(E33 &d," %69Y"7;R*33UUU '$ *}ERfzvwxxxxwwxxxwuoeyYlL\@L2;%-+Y"n'k'@!.6hG5t"4      7T7t5j0\EFAD=^   :Z5sEHHHHHHHHHHC6K2<( /"sW>-fOk&m'b&20E?>1M     /-D9X!?^#A_/Y6l,R{>GHHF&Q}  &&R@HHHHHHHHHD6Jyb!wvwqQ_( -_ $$$ ![4IO7Q   7(/JYj~vwwwwwvph|WhGT9D+2 ! }Z  9$*** =d#m'n'R'0JnE;}+Z-]0e2i3i2e/\+Nt&Cb)Jn*Ns.W>GGHG>3k$8  7U:{GHHHHHHHF6XZ!nvqO_'/` 88  '$c5L^!R2% 7$$$!#m9F\oswvwvtjZkIX6A& + ! f A) ? ,nW!n'n'j(@ /6jD2c0]0]/Z-V-U~-V*Mq&Ba*Kp-U3c;x?CGGG8q-V/c*A    &:3mFGHHHHHG5dHZi~JY).W ??9$w:Ob"g#J+"U**E9E<<<UUU  S,8P`lvwvodxRb=H- 5"a=3%0Ei%o'n'a&12I?F<{9v:v;x?>1`,Rz3f5h/X-S}-T},S|-U9sEB9r2b.V4l(U2L '    ,.bEGHHHHG7o%-,1')n"""33':' ,X)@Se$l&a!?%_55C#AFf[aMR /2FD&,CQdzrpi~WiFS6@% +vG!3** 4] n'n'm'Q(0LrEGGGGG?.X0]8q0\-U8rADC<|-U4h?,S{;{7n,R{3c8u7v2j*X$Kt!Bf=^<\%Nx>GHHHHH;x6$07!y,,,4*88f33888 !!!-"`&:I^!i%n'j&S5<$$$***#DGd CN/ 7L\XkTfFT5A' .#U .  3*3e-T~<}F;z,S|1_6k/[.W@=,R|<~.W8rGGC;w2b.W-U~.X7pFHHHHHHHH>;/Ef%n'l'b#QB7/)"! " (1;CKVc#k&n'o(o(n(j&W7%J** 8 OS } {X_(  UUU#AJk&o(o(n'Z&0,Rz<{GGGGE0^5i?-S{?5j0[EGHHHHHHHHHHHHHHH>(d??--6a#n(o(o(o(m)M)4`E-U>HHHHHE2b2`FHF=~6^5X:tD4g2d<}-S|B@-U7oFHHHHHGBEHHE2b;wG9qF!/m*n(o(o(o(o(o(o(o(o(o(o(o(o(o(n(n'j&\!D+z,***UU'GKm'o(o(o(o(m)M)5`E/[;yHHHHF6n-WBHD6b;1FO-S+=*=6a>,S|>1`5jF>,T}7mFGGGG?.YAGF6m.XCF6^O)m'o(o(o(o(o(o(o(o(o(o(o(o(n'n'l&]!G05 $004`"o(o(o(o(o(n(Q'2XE3e5jHHHG9r,S{?GB4S{D$4c(n(n'c(=):7j3d3e>+Qy>G?.W1_>DD;w,S{7qFF9v-Rz?GC4Ee]*n'o(o(o(o(o(o(o(o(o(n(m(n'j%\ F1 7 UU"IIl&o(o(o(o(o(n(X&2HjD;z.VDHE7o,R{=G@5LpK"0h)m(o(o(m'_(67P8r,S{<~7n.VCGC4h,S{.V-U-U:vFF:w+Qy<|==~=};.Bh'n'o(o(o(o(o(o(n'n'n'k%d"TC0t1* 0_"o'o(o(o(o(o(n(e(80E>D0\1`8q0\.V>G@5GiO /j)n(o(o(o(n(l(O,3S}5k.XB3d/ZBGGC>>DHD7n,Qz<|A,T}9u5\P-l'n(o(o(o(n(n(n(n&g$\ M<-Y#UFGk&n(o(o(o(o(o(n(m(M.5XEB3e0]7nCF=~6BaS-l)n(o(o(o(o(o(n(f';,?9r0^4eC3d-U~;zEGGGF=0\.W>C/Z4g>87Oc)n(n(n(n'm'k&f$^!N?0n':*?&0]!o'n(o(o(o(o(o(o(n(f(@):5bBGGGA8f35[H+l'n'o(o(o(o(o(o(o(m'U*3Lq>-U6lD9t-V.X3d4i3d.Y-T~6lCC0\1`C6VQ.l'm'j'h%_"SK=,r,9***U FFl&o(o(o(o(o(o(o(o(n(n'S#- 65Co7Z8_7S:=lM&e^pL5i$o'o(o(o(o(o(o(o(o(i'A(98m?-U~5gDC;y5k4g5k:yBF?.W2bC8n4(8V#Y!PC86z-N0---??((.["n(o(o(o(o(o(o(o(o(o(c#3 LXdvY"mV$l]pj}ttRR^#o'n(o(o(o(o(o(o(o(n']'68S?@0[/X<{EGGGFA4h,R{6mD;y'FD<~7o5j6m:vAFD8h;4c?K %)FEj%n'o(o(o(o(o(o(o(m'P%###+ #^[lxwxxxxwQaOm'o(o(o(o(o(o(o(o(o(o(l(U+85M6c@EGGGFC:s5JwH)di ~pO`%,D  +[!n'o(o(o(o(o(o(n(n'Y(  ?333 P[lxxxxxxrANY:c#o(o(o(o(o(o(o(o(o(o(o(m(c(= -(4O4N}8a8f8c6T8>kF,b_ srwwu]q6Ax  ($IEj%n'o(o(o(o(o(o(n'^!/V*** HScg|rvvwwxxxxwui}O^7A"(}> *$$ &,5**UUU!MDk&n'o(o(o(o(o(n(g$; &&& =Jpxxxwwdw+1#2Fi%o(o(o(o(o(o(o(o(o(o(o'c#+ 3 %-D4?KY^qnvvwwxxxxxwkZkBO3< %t L&  *???,a*** !-.4]<GN]`rjqvwxxxwxxti~[oP_=J09+4+0i!'MB  ;3 . * *  , /!E_d %(-YUUU$UHk&o(o(o(o(o(o(j&F"%%%"$09ocvvxxwx[n$, "9a$m'o(o(o(o(o(o(o(o(n(n'T"$00((,',[;DETN_\pjortwxxyxvqmey[lQaK[JXGTBM<F7B-6  ip v|-)->%%%1`!n'o(o(o(o(o(k'J$''''#.GTcwxxwx[l&,(3Dg$n(o(o(o(o(o(o(o(o(n'e$6A3 !&5,5[/67@FTQ`XiZm[m_qbvcwdxexcvat]p[lWhP`FU8C) 1 "YCDIfmkrho$BKT... ***#eJm&n(o(o(o(n(k'K%!!!- +IVuxxwx\m(,*(XMk&n'o(o(o(o(o(o(o(o(n'O! %1 E U# (^/5e2;o1_"n'o(o(o(o(o(o(o(Z!.'''-"vCh%j&W9#&&&(UUU =Hpvw^r#+zUU%0;\"m'o(o(o(o(o(o(m'S+$$$33 #f@c#Y ?$H$6$*+4o`tvwfz/8'3V k'n(o(o(o(o(o(l&L$<----28(04?#^5N<(Q"""UUU4GVtvo6A /Nh%n'o(o(o(o(n'i&Dp** (55LR:?/'Q((UUU2;gzvv?K+&(lGe$n(n(o(o(o(n(f$<W$$$(+/F -0 33 !MQauwJXG* %Q?^"m'o(o(o(o(n'a"5?UUU+.1R  %CGu8  9CmwUg%f***86Vl&o(o(o(o(n'Z.)//7 OUah ho,5:4UUU$ )bVgv`t! )#.Mi$n'n(o(n'm&T*(..&AFeFJm(-22333 '>Ioh})2$(eDb"m'o(o(n(k&G#s.. 333*** %+tZll6@ #H9Y l&n(o(n(f$<!KUUU />Kj>I +-0Qi%n'n(n'\!/ ' (.ZlCR 7)|Ge$o(n(m&N&"""5AMBNB"33%^<_"n(n'h%<H'/.;#e88 $$$E4X m'n'Y ****5.5&KQ xrx!HM\?333.Ok&l'A"X333!$'M $;;8#*#$(Hh%\!(' ))> AFm$**$xB`#?QT 5((OVhp }'?BT???""i<>8=HLx&,1.??&..!>CnGM!;=c888$$$ $}\c [a/'/ **** ***33DKs !$$E???!EJc z x##(9-6?GJeknu"FMp666&&&!.$*??? ??????????? ???????????0 ?? ????  ???????(@ BUU'NX/OO[[5??]aD !7B... &*0,%,$$$8pN**5Z]!@~?TT*pvTOW .B^!U )33 }***(3]"d#9X^bFntET1:% +)%)X!k&E333&!;GO_ERy// %"U n'U!,""BN`sZlAK^' S n']#.1$;HxcwjQa06/*$Tn(d$:O$$ 9Bocws^p;GR$$%/X!n(i%@s[bCy~PW# 8Eyg{vfy@Lk 'A]!o(l'H???3sx_ANoxh}BOp2ec#o(n(O PXbgp[d8???&??$$$ %/Sdvwg{?Ke3***<i%o(o(S*.4M)7***ciM swu'''        333???5?lxv]p*5Z$$***Jn'o(o(Z!)ALBQUhow mr2CJLMNOOMIFBo6P23 !.Ugt\$w?Az3\2a+Ij"0>%+W[!o(o(o(^"'-#+TfIX8BTYX=A:-T>NZ!b#h%l'm'l'l&k&i%d$]"W!MD{8M- ' 9DQ&n=e;y6lDA2a#5G9""3>i%o(o(o(_"45:CWezFUx***!+59]@LX c#l'o(o(o(o(o(m'j&e$[!RGz5C!*>8fFC=GHE5f($-qSo(o(o(o(b$< ?P`lBNX???.16lEZ"j&o(o(o(o(o(o(o(n(i&J/6eFHFGHHHC5Eha&o(o(o(o(e$;I6?Ug|f|7<3+R@[!l'o(o(o(o(o(o(^+Ip:TBHHA7gO+Bm'o(o(o(f$>N%"SctZk( ($$$,bF`"n'o(o(o(f(@BcCF8`D.;U3;I-96XAHF6n=Vc+o(o(o(f$=K CNnqGTy 1|Og%o(l'I+B?E.^/A=TnGa}KIU9)4FiEH>8lO.Gn(o(o(c$<C!!<Ei|wcv/:0333$8?]!X.Ih|wsKY'' 4>5BM::8Nw>/e6uD6n5j:wCF<#Gn?G9s=qY"7o(o([",,L\nxwbv095-6BP\Vhf|lC6q7o2h?GF8pD=7n>G:|-_D;{DC@EF=9u5k:xBXh)m']Guxxw^q( 5&-22GV`sqwxxuI7xAGHC@F8p:uBHH@7p;{6k:w9u@s^0i'a^xxxk@M_)41FT`tqwxxxw^l:TGHHHHF8p9tCHHHD?AD=~BT*@a-jwxxrRb$$GO PT6???$+#CM_rrxxxxxxhsK8>qHF>8x;A7o9tCHHHHHHHGEH:X[Bsxv^p4:'(5C cg@;E_Xjoxxxxxxxn\;N'*$333**? 1;RPakpf{XhDS3>{" )J &(V l'P&;>|8r6m5k.Y-U0\6l=}C5j'Nw*? &-`FHH?{AT>Fw.. 333/KDX!V @w(RY(px4?PaXkM\=G-3T !& Ejg$j'A>]AFD7n2c6m:v:x:v6n5i9t3f0c/a.a4nDHHAB#7>0s#:' $$.!&55ZAO`#e#S 7J$$$_fP *8q,3J"",.!Y!n'c(:S}9tHE6m;z9v9t<{7n7p5j;yD>:v;zFHHHBQ(?k'`#U NKLS Z!c$j&l'_"G-"&??U]hT\B??Cug&o(],:e9sHFB=~:xFE=7n6m7o@HHGHHGEBT'=n(o(o(o(o(o(o(o(m'e$Q5L 1$Y"n(o([0a5k6l<}7oAGG>EA:x@y[ 3o(o(o(o(o(o(n'g%V >f,Bwg%o(o(_,=j7oDA7nABT]1l(Y 48X6l6m@8r9t8r:wA6m>@]d*o(o(o(n(k&d$V Af++#V!n(o(o(h(CKr=~6l8r?CMua.n(o(m(J1K4d8r7oAA@=7n9u6lJ6Sm(n(k&e$]!O?L0 ?uf%o(o(o(n(V1;S@qAgG;zY2n(o(o(o(c+=T6l:w8r7n7o;y:x7o:ZV+^#V KBQ.!$$$-"T n'o(o(o(i&D%\mf#j qZFl'o(o(o(n(U&<=k8r8r;{;z7p:v8k+3H{594"=tf$o(o(o(l'O/+|i}xxvIK`#o(o(o(o(l(S+B>f>=~=~>|CZR(q>H!+#T m'o(o(n(V-_-73h|xxp:COl'o(o(o(o(m(S,32WADJBW0}l vlN^/:[?yf%o(o(n([!1{ //`sxxk4>b8?_"o(o(o(o(o(b#/DPx[nltwxug{Sa=Iy-6=$ ***im:{vRZ;,(U n(o(o(`#7 Whwxh}2;Q$$Jh&o(o(o(o(j&@(2CMLQa^qg}nrtndyYkQaGS@L6?y(.r"w qv???***Ag$o(o(a#9$LYsxh|29K2R k'o(o(o(n(X!*B$02B2=IWIXvN^SbVhXiTdK[EQ9Fs' -T 7Y`jelsRZ"0:X!n(n(b"<(@LSmxi~6?P45Z"m'o(o(o(i&@"""**   **Ej&o(b#<%07%ewwl6A];M]"n(o(o(o(^"5`34j`#n'`";33 Ufup>Go>Z_"n(o(o(m'S05.BTk&X 8~EOcnrFR**@[_#n(o(o(j&J)(2Ma"L0T 07%atuN] 333>R[#m'o(o(g%C-$?H"+/;=K7{%"$$$N]sWi "9CW k&o(o(b#9| ?G &!&(3337BEh|`s%+)0/Og%o(n(\!5_"KK%| QV5Uefy0I34DRi&e$A 3?q.@888 1)Hd%[!2KUio tz^'A]"Ekrm |""":vB AFm&?L??KRG!IP&"""gmuzu???dhz nqa**? ==!???????|????0 x 0??(0` %UUUrw1ip"UU ***$HH BG#VK&***[[?_<?Z!BTfo> xt3330/`"S __qvn 6N;Do3;0%^"^!4", ,CQUgLYd*8/ \#f$CH$$GV|cw]n>L9-"]"k&Kp$$$ FTsi}g|JW` .,_#m'OruCZZ IVolM]s88 5Cc$o(W  qvc ]f???(PPUU''UfvmM[m**=ph%o(\"( ';@!(Cq???orG SWR2#0%3(5+5+4'1. . 8C[jv\s-/Qp'8I-$$$* Km'o(a$/ AOtCRi]f<}!.4BR[!`#b#a$`#]#W PHj?@/   RdX.=]8oFmNY a#j&o(n(m'j&f%`#W Ow=B.2W@vB=GB1^? i%o(o(e$C5@MOeyJXK***,8?DV h%o(o(o(o(n(k'M&;=wGDDHH?H/Hm(o(o(g%B=& &Ykh}>D)???+)AY!j&o(o(o(_1AfE?cH6M?8]@{H@~1h0f7p7nA/W,WF=?\f-o(d$0IBKf{vau1:;DM]_[oG%a8h6t?A:x:w<}D2k9xA;qX+Do(^)P[nxpIYd$$;F/Tdf{qo>E~:v9vAB=}B@CC:{9u9tKFkm(a?sxv]q& 3;D8Tdi}txw]'|@{GFBB:u?HF<|;x9u:wEbf*bUwxlEQB:?0Rai}txxxlyC8iEGDDA9t@HHGEFCCzY7jrxrSdz**[ev{:/8M[zfztxxxwp]@CRA(R*>*?(R~9s;{:}CHHHHDM)[su]n5 C$$$BE\ X >KN^pqwuoh|^qSaQ=]/=h Ad %7,Y?2k$0G3lEHHER:tat?F$$$$419(q?E0LL ???.9,Pai}pg{ZjIX;Go07E" )%=Od$Q.H:t,Y+V(Lr+Rz8rE8t-C  %LuCHGHF\nAM+<?LN=d. qv6+!EM^ZlQ`BMp09: !6Y i'CJqCA6l3e7n9t9t6m4j,Y'Nu&Oy;~HH=P~A 3T&((-'<DGzT^"U!=>pud $7Oj4=:"* EQg%c+;ZAE9u;y<{=8q6k<|B>@HHHFXg&a$Z!W Y ^#d$i%b#N/$HHRY%???<X!n(_1;g@GA;zCBs9r6m9u@GGEEAHP{l(o(o(o(o(m'f%U >=***ETg%o(a0=i=B:wAtR4Qa0DDh5i:v;{<|<{>:x=|P9Xn(o(o(l&d$X!DJ' -U!n(o(i)FOy:v:v?jV-Fm(o(`29X7p;y=<}:v8r;`^4j&e%^!QD8. ARe%o(o(m'N6M>S@\$pe-o(o(m(O5S9n9u:x:y9v8p9AaP!KbB65 -U m'o(n(Z!CBrwmT0n'o(o(i+LBe5).19DG ~333A^f$o(e%C/8fzxat%/>%]#n(o(o(d$:\/?BQ2M]_Vg[m_q]pViL[AL,3h*9YxszK1W n(f%F&\nvcw*3DGc$n(o(n'R'$3 ': **  ???Fi%e$F$$Q`grg|-4'?I^d%o(o(h&G$$$:S`#`#B->I-jj9F:***Jcd%o(o(b#=W3@RS;V 3 [mnBNT**IZa$n(n(["8;_g }."*=p+IWWkJYwDG\"l'm'U -'hkG job22`sRc<.V!j%k&O$3L TZ-*UU KZsSd$$5Nf$h%G$$$18$BO''3 Fe`#^#;E5Cu~iUUU?<HW!M":N ??739#,eiy"UU"DD??mt{ bh1NN^b>TT <<<p<8800 ??? ??( @ D2bio[`_D?,["G+??w{>*,NrHT[?L<"_#Sb3DN]x]oSbC33? c%Y"UU$HH33 Teyh|Zjn88 ;+d$`"3 8 q3ff"ff': Zko\mtHHDJh%c%<  @X7Rb`x}36$UK[QaPbQXPFH.7**=J:i~Y+4Q.S}7$$$Pl'f%E!HV[GVGbb V\/6AKyX!_#e%i&g%c$^"W!rC#H@@{Ax?@4Lqu^#o(g%K,3D\mRb> 9J VW!g%n(o(k'Q0MADwC~G>m_ 4o(h%L2Q_XeyJZ/CZZ"j&`"8Co6fDJbAGB==MNzfMvlJ])wwnyHT*^powwrX#Y@y._%Ks3i:y?FHGFigpqXjVjqnt8FTeij~ojauVfV?P6X*U);1G4k8u'9#Fk?HG{f#]or$$H3 F:>r$!$E]];!Ea\N]]oVhKWU<B*-F3a'FW=3g3d9u9t*TfD<|=~=|7n<}BDGE\&=d%a$b$e%`#T u?UUUH5g%d4>jB>|JXQ=`8d:x?@>Ane4o(m&g%["I-U**Y"m'j*GKyAjKH{h/l)IAd8q;z;y8rCIoc&`#X"hI&3D4e$o(c$TTm%cfh'o(e4DV:r?@(  @io)dk&u!XnI!&P Vmm@%[KXhBH$mM!]"?ffss ??LL _pvbxtE\ T!-c$Q"_o hs, x%DKJRi^"b#`%gSAYOG>vR,Ei&W$#Pa?Nb'N' T!-^"g&U:\?gArLTi*V=Gbt\hLf bqAU*k?lkoQE6q:y@BY=~mXn333 sx5_r(Tedbwat{ZJAU*R}5l(Qz>MVYLSR"%R GGO"DLev-H[??W#`!6CrBo@b<{AQMyf%b$X!\D"`$og,S<])U_(B?e@hN2Y`#OUR e%[&j}aSj(Y#?Z+dwVg[<K3py;mH$W"w\#N' f{raqac$qg&Z"IO_Xc?O\\ UU W^FH$FT _o0[md_a$ic$T*xx?_66gTUU?Y GT#{6$6w -GL2kq-i2pd-2.17.0/Win32/resource.h000066400000000000000000000004351321131324000153310ustar00rootroot00000000000000//{{NO_DEPENDENCIES}} #define MAINICON 101 #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif i2pd-2.17.0/Win32/winres.h000066400000000000000000000001051321131324000150030ustar00rootroot00000000000000#ifndef WINRES_H__ #define WINRES_H__ #include #endif i2pd-2.17.0/android/000077500000000000000000000000001321131324000140455ustar00rootroot00000000000000i2pd-2.17.0/android/.gitignore000066400000000000000000000001011321131324000160250ustar00rootroot00000000000000gen tests .idea ant.properties local.properties build.sh bin log*i2pd-2.17.0/android/AndroidManifest.xml000077500000000000000000000023571321131324000176500ustar00rootroot00000000000000 i2pd-2.17.0/android/build.xml000066400000000000000000000100001321131324000156550ustar00rootroot00000000000000 i2pd-2.17.0/android/jni/000077500000000000000000000000001321131324000146255ustar00rootroot00000000000000i2pd-2.17.0/android/jni/Android.mk000077500000000000000000000050421321131324000165420ustar00rootroot00000000000000LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := i2pd LOCAL_CPP_FEATURES := rtti exceptions LOCAL_C_INCLUDES += $(IFADDRS_PATH) $(LIB_SRC_PATH) $(LIB_CLIENT_SRC_PATH) $(DAEMON_SRC_PATH) LOCAL_STATIC_LIBRARIES := \ boost_system \ boost_date_time \ boost_filesystem \ boost_program_options \ crypto ssl \ miniupnpc LOCAL_LDLIBS := -lz LOCAL_SRC_FILES := DaemonAndroid.cpp i2pd_android.cpp $(IFADDRS_PATH)/ifaddrs.c \ $(wildcard $(LIB_SRC_PATH)/*.cpp)\ $(wildcard $(LIB_CLIENT_SRC_PATH)/*.cpp)\ $(DAEMON_SRC_PATH)/Daemon.cpp \ $(DAEMON_SRC_PATH)/UPnP.cpp \ $(DAEMON_SRC_PATH)/HTTPServer.cpp \ $(DAEMON_SRC_PATH)/I2PControl.cpp include $(BUILD_SHARED_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := boost_system LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_62_0/$(TARGET_ARCH_ABI)/lib/libboost_system.a LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_62_0/include include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := boost_date_time LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_62_0/$(TARGET_ARCH_ABI)/lib/libboost_date_time.a LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_62_0/include include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := boost_filesystem LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_62_0/$(TARGET_ARCH_ABI)/lib/libboost_filesystem.a LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_62_0/include include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := boost_program_options LOCAL_SRC_FILES := $(BOOST_PATH)/boost_1_62_0/$(TARGET_ARCH_ABI)/lib/libboost_program_options.a LOCAL_EXPORT_C_INCLUDES := $(BOOST_PATH)/boost_1_62_0/include include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := crypto LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.0e/$(TARGET_ARCH_ABI)/lib/libcrypto.a LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.0e/include include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := ssl LOCAL_SRC_FILES := $(OPENSSL_PATH)/openssl-1.1.0e/$(TARGET_ARCH_ABI)/lib/libssl.a LOCAL_EXPORT_C_INCLUDES := $(OPENSSL_PATH)/openssl-1.1.0e/include LOCAL_STATIC_LIBRARIES := crypto include $(PREBUILT_STATIC_LIBRARY) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := miniupnpc LOCAL_SRC_FILES := $(MINIUPNP_PATH)/miniupnp-2.0/$(TARGET_ARCH_ABI)/lib/libminiupnpc.a LOCAL_EXPORT_C_INCLUDES := $(MINIUPNP_PATH)/miniupnp-2.0/include include $(PREBUILT_STATIC_LIBRARY) i2pd-2.17.0/android/jni/Application.mk000077500000000000000000000027131321131324000174270ustar00rootroot00000000000000#APP_ABI := all #APP_ABI := armeabi-v7a x86 #APP_ABI := x86 #APP_ABI := x86_64 APP_ABI := armeabi-v7a #can be android-3 but will fail for x86 since arch-x86 is not present at ndkroot/platforms/android-3/ . libz is taken from there. APP_PLATFORM := android-14 # http://stackoverflow.com/a/21386866/529442 http://stackoverflow.com/a/15616255/529442 to enable c++11 support in Eclipse NDK_TOOLCHAIN_VERSION := 4.9 # APP_STL := stlport_shared --> does not seem to contain C++11 features APP_STL := gnustl_shared # Enable c++11 extentions in source code APP_CPPFLAGS += -std=c++11 APP_CPPFLAGS += -DANDROID -D__ANDROID__ -DUSE_UPNP ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) APP_CPPFLAGS += -DANDROID_ARM7A endif APP_OPTIM := debug # git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/android-ifaddrs.git # change to your own I2PD_LIBS_PATH = /path/to/libraries BOOST_PATH = $(I2PD_LIBS_PATH)/Boost-for-Android-Prebuilt OPENSSL_PATH = $(I2PD_LIBS_PATH)/OpenSSL-for-Android-Prebuilt MINIUPNP_PATH = $(I2PD_LIBS_PATH)/MiniUPnP-for-Android-Prebuilt IFADDRS_PATH = $(I2PD_LIBS_PATH)/android-ifaddrs # don't change me I2PD_SRC_PATH = $(PWD)/.. LIB_SRC_PATH = $(I2PD_SRC_PATH)/libi2pd LIB_CLIENT_SRC_PATH = $(I2PD_SRC_PATH)/libi2pd_client DAEMON_SRC_PATH = $(I2PD_SRC_PATH)/daemon i2pd-2.17.0/android/jni/DaemonAndroid.cpp000066400000000000000000000102431321131324000200350ustar00rootroot00000000000000#include "DaemonAndroid.h" #include "Daemon.h" #include #include #include #include //#include "mainwindow.h" namespace i2p { namespace android { /* Worker::Worker (DaemonAndroidImpl& daemon): m_Daemon (daemon) { } void Worker::startDaemon() { Log.d(TAG"Performing daemon start..."); m_Daemon.start(); Log.d(TAG"Daemon started."); emit resultReady(); } void Worker::restartDaemon() { Log.d(TAG"Performing daemon restart..."); m_Daemon.restart(); Log.d(TAG"Daemon restarted."); emit resultReady(); } void Worker::stopDaemon() { Log.d(TAG"Performing daemon stop..."); m_Daemon.stop(); Log.d(TAG"Daemon stopped."); emit resultReady(); } Controller::Controller(DaemonAndroidImpl& daemon): m_Daemon (daemon) { Worker *worker = new Worker (m_Daemon); worker->moveToThread(&workerThread); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &Controller::startDaemon, worker, &Worker::startDaemon); connect(this, &Controller::stopDaemon, worker, &Worker::stopDaemon); connect(this, &Controller::restartDaemon, worker, &Worker::restartDaemon); connect(worker, &Worker::resultReady, this, &Controller::handleResults); workerThread.start(); } Controller::~Controller() { Log.d(TAG"Closing and waiting for daemon worker thread..."); workerThread.quit(); workerThread.wait(); Log.d(TAG"Waiting for daemon worker thread finished."); if(m_Daemon.isRunning()) { Log.d(TAG"Stopping the daemon..."); m_Daemon.stop(); Log.d(TAG"Stopped the daemon."); } } */ DaemonAndroidImpl::DaemonAndroidImpl () //: /*mutex(nullptr), */ //m_IsRunning(false), //m_RunningChangedCallback(nullptr) { } DaemonAndroidImpl::~DaemonAndroidImpl () { //delete mutex; } bool DaemonAndroidImpl::init(int argc, char* argv[]) { //mutex=new QMutex(QMutex::Recursive); //setRunningCallback(0); //m_IsRunning=false; return Daemon.init(argc,argv); } void DaemonAndroidImpl::start() { //QMutexLocker locker(mutex); //setRunning(true); Daemon.start(); } void DaemonAndroidImpl::stop() { //QMutexLocker locker(mutex); Daemon.stop(); //setRunning(false); } void DaemonAndroidImpl::restart() { //QMutexLocker locker(mutex); stop(); start(); } /* void DaemonAndroidImpl::setRunningCallback(runningChangedCallback cb) { m_RunningChangedCallback = cb; } bool DaemonAndroidImpl::isRunning() { return m_IsRunning; } void DaemonAndroidImpl::setRunning(bool newValue) { bool oldValue = m_IsRunning; if(oldValue!=newValue) { m_IsRunning = newValue; if(m_RunningChangedCallback) m_RunningChangedCallback(); } } */ static DaemonAndroidImpl daemon; static char* argv[1]={strdup("tmp")}; /** * returns error details if failed * returns "ok" if daemon initialized and started okay */ std::string start(/*int argc, char* argv[]*/) { try { //int result; { //Log.d(TAG"Initialising the daemon..."); bool daemonInitSuccess = daemon.init(1,argv); if(!daemonInitSuccess) { //QMessageBox::critical(0, "Error", "Daemon init failed"); return "Daemon init failed"; } //Log.d(TAG"Initialised, creating the main window..."); //MainWindow w; //Log.d(TAG"Before main window.show()..."); //w.show (); { //i2p::qt::Controller daemonQtController(daemon); //Log.d(TAG"Starting the daemon..."); //emit daemonQtController.startDaemon(); //daemon.start (); //Log.d(TAG"Starting GUI event loop..."); //result = app.exec(); //daemon.stop (); daemon.start(); } } //QMessageBox::information(&w, "Debug", "demon stopped"); //Log.d(TAG"Exiting the application"); //return result; } catch (boost::exception& ex) { std::stringstream ss; ss << boost::diagnostic_information(ex); return ss.str(); } catch (std::exception& ex) { std::stringstream ss; ss << ex.what(); return ss.str(); } catch(...) { return "unknown exception"; } return "ok"; } void stop() { daemon.stop(); } } } i2pd-2.17.0/android/jni/DaemonAndroid.h000066400000000000000000000030411321131324000175000ustar00rootroot00000000000000#ifndef DAEMON_ANDROID_H #define DAEMON_ANDROID_H #include namespace i2p { namespace android { class DaemonAndroidImpl { public: DaemonAndroidImpl (); ~DaemonAndroidImpl (); //typedef void (*runningChangedCallback)(); /** * @return success */ bool init(int argc, char* argv[]); void start(); void stop(); void restart(); //void setRunningCallback(runningChangedCallback cb); //bool isRunning(); private: //void setRunning(bool running); private: //QMutex* mutex; //bool m_IsRunning; //runningChangedCallback m_RunningChangedCallback; }; /** * returns "ok" if daemon init failed * returns errinfo if daemon initialized and started okay */ std::string start(); // stops the daemon void stop(); /* class Worker : public QObject { Q_OBJECT public: Worker (DaemonAndroidImpl& daemon); private: DaemonAndroidImpl& m_Daemon; public slots: void startDaemon(); void restartDaemon(); void stopDaemon(); signals: void resultReady(); }; class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller(DaemonAndroidImpl& daemon); ~Controller(); private: DaemonAndroidImpl& m_Daemon; public slots: void handleResults(){} signals: void startDaemon(); void stopDaemon(); void restartDaemon(); }; */ } } #endif // DAEMON_ANDROID_H i2pd-2.17.0/android/jni/i2pd_android.cpp000077500000000000000000000033371321131324000177000ustar00rootroot00000000000000 //#include #include #include "org_purplei2p_i2pd_I2PD_JNI.h" #include "DaemonAndroid.h" #include "RouterContext.h" #include "Transports.h" JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith (JNIEnv * env, jclass clazz) { #if defined(__arm__) #if defined(__ARM_ARCH_7A__) #if defined(__ARM_NEON__) #if defined(__ARM_PCS_VFP) #define ABI "armeabi-v7a/NEON (hard-float)" #else #define ABI "armeabi-v7a/NEON" #endif #else #if defined(__ARM_PCS_VFP) #define ABI "armeabi-v7a (hard-float)" #else #define ABI "armeabi-v7a" #endif #endif #else #define ABI "armeabi" #endif #elif defined(__i386__) #define ABI "x86" #elif defined(__x86_64__) #define ABI "x86_64" #elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */ #define ABI "mips64" #elif defined(__mips__) #define ABI "mips" #elif defined(__aarch64__) #define ABI "arm64-v8a" #else #define ABI "unknown" #endif return env->NewStringUTF(ABI); } JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon (JNIEnv * env, jclass clazz) { return env->NewStringUTF(i2p::android::start().c_str()); } JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon (JNIEnv * env, jclass clazz) { i2p::android::stop(); } JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels (JNIEnv * env, jclass clazz) { i2p::context.SetAcceptsTunnels (false); } JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged (JNIEnv * env, jclass clazz, jboolean isConnected) { bool isConnectedBool = (bool) isConnected; i2p::transport::transports.SetOnline (isConnectedBool); } i2pd-2.17.0/android/jni/org_purplei2p_i2pd_I2PD_JNI.h000066400000000000000000000016341321131324000217670ustar00rootroot00000000000000/* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class org_purplei2p_i2pd_I2PD_JNI */ #ifndef _Included_org_purplei2p_i2pd_I2PD_JNI #define _Included_org_purplei2p_i2pd_I2PD_JNI #ifdef __cplusplus extern "C" { #endif /* * Class: org_purplei2p_i2pd_I2PD_JNI * Method: stringFromJNI * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith (JNIEnv *, jclass); JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon (JNIEnv *, jclass); JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon (JNIEnv *, jclass); JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels (JNIEnv *, jclass); JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged (JNIEnv * env, jclass clazz, jboolean isConnected); #ifdef __cplusplus } #endif #endif i2pd-2.17.0/android/proguard-project.txt000066400000000000000000000014151321131324000200760ustar00rootroot00000000000000# To enable ProGuard in your project, edit project.properties # to define the proguard.config property as described in that file. # # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in ${sdk.dir}/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the ProGuard # include property in project.properties. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} i2pd-2.17.0/android/project.properties000066400000000000000000000010631321131324000176310ustar00rootroot00000000000000# This file is automatically generated by Android Tools. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! # # This file must be checked in Version Control Systems. # # To customize properties used by the Ant build system edit # "ant.properties", and override values to adapt the script to your # project structure. # # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. target=android-25 i2pd-2.17.0/android/res/000077500000000000000000000000001321131324000146365ustar00rootroot00000000000000i2pd-2.17.0/android/res/drawable/000077500000000000000000000000001321131324000164175ustar00rootroot00000000000000i2pd-2.17.0/android/res/drawable/icon.png000066400000000000000000001104711321131324000200610ustar00rootroot00000000000000PNG  IHDRXbKGD(oT| pHYs.#.#x?vtIME 5&k IDATxw|UL%'PB]E@uŲ뺶U׮څUTޑ^SH9&(. *y䖙9|SOOOOOO~N9e„sdxhw9'&KFO~2}!rc$ZC;o촬5wS L;e">MIbԝBG{Z.suW9Aο3ɶ]R@6mr ]'(lTpde!O$߁# !_%$q(\d9wϮ+~0Hnd(f6`@;eu?X#GߛoZm._a&7'h$k,;FLlvӃ>/3XQӧNJm32+Uy Rfո _~sRoDx5;-8?_vAlgnF܌!SzOd.aRj^zZozSs.9G^(3sNc)kr!tBc, IL]HWiN95BZFILvɰooi3֢g1O(xH0@ew^(a၏>i@Q&%ɧ\1|H{yߴRO1l0hI9-yS@EfD8屖I<7 ݴ =iI yK7aIp%fy^xn<7<=bn7)s7>9Mt޸9OK <==ҁ8$#D8vSKs\bn=pDcnf&9E:t_ ;;7 !1hjZ0!5 nj7RWOOKOׯߤq}QqsN%z ՞S 3SyֿKK>@P\wKQMo:YseE:^zժ;gViǷphUB~~!EJy ()) -Mֺ( ]ۋC'i~ڼdm` P=kZ&$Dz/2337'G/m-m/jOY.*H8k/gdU?)O-t 98LD psq ~~ {fŜh$@kS;)AO.@Fn3`?-ȹ\9h^ imܲe[kB<ԉ3f U(KKQ*6MV^ܵ.Бzfrę!Ƃ%+?زyk'Aqd#3͝= 4 @ES$D`x * %o/Xt7%WMge_$mҫ#cԐ!ڧqAmlMQm>,NU8hݼ6i泒t{PMͰ/LMWt8}:O'fJ;jg3M(J'"B"VѢۻ~.+Yۼ.QI$EC;P)^߀ċ֬CXs&Q0+Z^8ElX 9O[gLFZIBܴ$5h!Tw\3<{ɺssy"PId\:[݀$5nRUU)` SQaN k5WXb|MZuu対5r@pSהl9ej!^YSU$QsZ8@Ϣ O͈:)|㬓URCM4Ek$x|ߞo9IN <+q211A(K\"U2idҬѳxSiB,5 FUHyJTuNNe.uŊqۻ[M0B@ ( lt?B̬4{uꞈꢘ5ǏiSmBF%_WٮlRmSh,x4@@ 7)Q 7dԋ#t@HJovfKye}eADQO킦spE0CӚv.7@iҤIƺ Ȁcb MՠCk ?KIτ0$vH.}wq~4$ 8" "ի]rǷ7eE^g*zt Hfvf5XEo/"Ftۍfi`'l?,,4l2k%6>6q$Kqa~J@\`pC1ד?{2aH Y_nPW*7(vi۷o.[apx9`}3{vsW. FWZ^uyh-5m(rՐZsw)\eŕ pђJ9 qnp ƹFhLA{` $KMS&ѥ_/?% GhL m(.1?Z}=! g = ֮;n1: {D JԠ~-~nF8EԴ$RUY/|.`PΡusn#~m{*!$ES G#[ٽkXwӎLL\0K9Hc6^z_ҒUոn:}rs ;Sx|>"yM`48| /o ͛5"L<#+rs)뉢yA̺T금Wν˶s_z`"ro˅DJ*ܥEM6{ObeKEwlZe3 [cs XzfC+W.O~͛7}~rm n'PUgPӿpξh D)LT}W/H&v`9ДXWުv D$w"%|3w|k8STq~âJD8ɻ{nٳGS#3!S/-Hvvoo> #y:ù][K;o?נW]ꩱ~G{lcA:TBL)=L1@P3XLNh0P"NvBjjj \]>5??\ 8@z `-!D8A@Ӑhê+tt?'$ė֎ %;.hѥ؏9S'-fXǙgB@٥@d7^2,B_fs5;wRVdٮ= mM! QNr$ժ?7K?󓂒?'ڨ&5rK"uẁl/g禟R'csrn_Z{` xTοZ>jLa_wM 'c]BB $XPBE|GS{M7r!ׯ`<< wwl؂54Tw,tq4PG{ Vkqz{=[% &ɽJ6;遻|}pZykZlS~w_aBBc-B囝oO>e$xg﮾ARZZ{hW/G+> Pe 9Ԕ Wm((5;D;=%1tKvS ܪ(Hi涍*A]^> $ ,|~iicmزe 9,NMDmL*W@yP oV۞zJQ[ٿ90"Q'Fi+Hx/PRLH5߸qy睧YKƫqްkmHJ~7vuxˏeJ+ʦ#z½O #c(P:6bpN,r:k% &ɽZg;{޴ ??oM .cޔ4]!n3iHjsu ̙5Q-ug%JLFPPY 1 ޟ!0wCR$,(yDMnG|[^\} @ܻ쏂Χ41-TY͋⋯ߙ5kR6o޼㺞4r:C.'BAӑ,7wE g[\Ρ3|!+f]523ɠGSRRhRRMLH4))fff Ǔh;FXz6f̨7ox;#"M]ޣ*Tkus[o<,NvVPZVeeg9߆{go=i|—-ź+IKKa4=#}^Wu8 c`3kHaώeVtuy~4 yyJ80&j./lG>ψ7@c!V ^*S jpQ^%%;n[@/]*~( DQdG^~&]n-::f:n!k? 򦮥UmW_u턿?{ouן(z}>Uop㚙;k;Wچ esQϪcIaPQPAWGXAY HfQZK1Z3mf;F8p ill`\>\18|X::zUSe2d2 V;isE9l(bOUcy[{۠FNW˗k)@:ky3N+\7ߤX/QB9J+Bt wqnGo妣R)ZWZ8vgU =0p^)M, :XKXpHύ"}^12j9MTS"P9z[?@![glD9B,B,NGh&ᖽS) jꃍXr:g"<'G6gt+/w\5_}rHpNOK+j huqԭ)6EU(gpԐ`w&L%| !D8i"Yl!5=eYgMiGܝHD1BcO==ܶuLUed!F[0wYݰaá 2qGeT)>y4EqMc`D0ph\;("] ץC*ThQ:xڦ.]:.!VI/UUm'z5k $T:D'sa^ .B2v5eWvyNsE@֭, x[K|!ˢs~P"":ug˖ctV(S`)!  0ZJޝuya N6H<Ƚ m۶Bnn.-..z -\juT0d8'PRB/9@*, Sܨ5+u:(.)HvŖ;bK(&h ՗Nλ_~2kF`z]ڛ+I 5y\;oZoch9pI&ęv_~6{lOsHc v30b zHA1 ܱ oBmpU%GQX9vxޙ3[m(..p195Ggq 6"[2PS!am gĭzżhڰWޱ/TUqq;6hoK7PF! z` `IOHUUpι DEp3Ntޅ OΤa!^HS3%ihZs cƎ+֬ҿ/Y$g6:6Ҥs^:E'PGZ~ݶu=pe_7Q2Rd Ii*{vT =BspPBRCkO]Zx_~d=BII#=rؾHaV OAYs;wճfc~1('eb rǂB+ 0߽>2O& MZ;C{B5^ѻa\Q۶{[I$js88g9v.22d7yv?wa4lgWTxe(O IDAT%ݒyxitn=ss.dfeX匐º9ХD`}ԭ  ʟ~7/ y\lԤY3N=A{]OJճnK;MacbF !Hfلً/xBݜIF V)?hxRXIe䙗M7})Vc2X𘔎qڍyؾI&T-[bŗ\H`(D dD( zAGtLj?!(]rNm5؏OR`<0SQ*ZGFd yv^25-$3=WmJl os_1וԀBvNѣG, ׇ//?:l:m٤x^)lȢB \pWc5oʳB1K9ppŻBl)XJñ'1#MN_qѪ.lDezә{uZbcCыNr↕@Oi@=LDc,\5:/ `7[)ZH&%@f=突 `T|UTюG j+,*/~kǣ'7r~OII N x.wDbUoޝm}lxgQVÍFdD:Mjy\Z^GՖɏYu@A:c7ꃝP-HI&s@ "rڰɽ_D&!cg>rr FY+ziYhoJGWm確NAP]Snw_T_'!3r<e2oJDO_*+`Q&[".j$z싅[yٳDSvTwhAv4X6ۧ$:Q.*mbTGB.M"Q`ƊupЉ8rƝx`v PzVZGǯ@Q0i7wKXƺwhE(8dv\uV8%*X^""G8%B<:V¯ٳ\]' /ǝpH}\>\لc\Mà34#0sG (Dޏ4lu?`3k­]Jrz쯿^~P3"]иMF֯_9E%QU%fF?d @~?EHDy"뜈D]"I+P=4ɢ'D 45a{3. ;9! |sqY)pT7q5hN*f[ h,RH·k{3XAIDywP>\7)92N= scnuOb)m;ﯻ>35H *}$ѣGӵkײ{GzC0ȬK+?D(K!ެ!+A@HwPө[@'d$j48ti`\9nHɲQR,?8Typ{gCgnR jۃhVQ:8z\3Lf@N"IJJjkk$˟w_(sQN^lDa)yc<\g)]*kg:#?C2`9 @R:|wE^~ᅧԝ: rc"( / ۯn!`ހ`7ॵm(k2!B7 A?<#!_!)nvE6} 'THS-dG;"L04Ō(2ʀJPэ "F!),W09ߎ;NODY ~iPއ4p @ ¯Ub{K﨤akeS7؆P_r/'2D}D"(!Za B6X/΂QNJ;l@2c + >E + qub[#lx(!! ?z ^u8?H<}A"EMG/ƦJ.;H~$e"%: *ap @an6bkME% I1/ yqFYޖZP%`5!' rR$4̕a^wfٞޭFԳJ˒K9dIt]/lܬziſ:#M`8=VcX j:X[<bFEBGARӬhpcGC-"$0^7܉j;وHh ƅPc;4tT@Űq5x2&ڐk7؊FX%$Eǃ"+ր~F$.AXOŷ\2*d,rjZ2溚NeR;#*I|~_ ?C^^C%">xF"Pz3+ >,ҎEE8kh$&ؐk27X߂ &94،YucCubeh8 0@h99' 5An<;#3n▷+! (2C"2ч-(GZ:Sj@Fl(o eȉ5 .#7ofƈt+onb= 0aX Ef(zRբUC:|Aw~m3g6+䧐ܜB*]g"(ڽ(t7& 85Jq{UbL :(VNɳ55 /Έ4A52fAHaEY)b PqphPQY;OlF25Mn|Y ^, f N:W|\|PvwbSe7ЊC( S-B*Y/`rmO~݀x#2F[Ƌ F$9tp4lmUX a C"g&]j4e{<9F9rc=jOnbaQZRȉ flg(uSR6Aڃ3 h @〢rdSxz N6#¯ M@Br>8\{Nk 'ln<%KhS+}Y68/wwu-x X+BA+qӢJ\>6i яSTA ZCxfYZ!cXqËjaʂH8A~1 AA-UR- ju45jbf3V*-)3)3WWr+-oۂRhi 0[8:{p$E8vh dl,< "X71׆FwO-mĄ\F%k#dج2`8gL{DRICg )R߽珈<jۃ*\4*g DO)ܽk ar LɈ404Ռ *qƠHVx9&ٱֵ`Bq6]xKJt+K*fh!~RYxPU%D(iUM%"(~ڲgZTWp@ V@NE!qI%G] 4?pZ䞠ɏ$4Av|!g&(͌*Gw $g/LwX՞!oG;& cxm}+lOGWN$ SuYWE/BcyH M0(ɌKGi|X A U=W{(!eO+xbs0D(Ɓ]P[1 sS $%% }7bTU,n,nI:4G!7yin8½rlxlLFg y5dN(/^Y6ϯhĥ! MڎS2UԠ%/3B*;ST8*CnwHF}ڎ 䞮6MCq6BcdBw0q1OCez(zfz'>0V[[d0`pZ0{f&##ZY$xkVR!eM(HB_@w 2g/~!*Ht0kp$t"=p^aWaÃa|Qfz-e#tuc) %pЉ$*_ ' :WaFNT]SCGumm-OOO:;]u |z$4Ԟ={Fq;;9 "~]{oϫԭ^1z&Mŵoǰ4 %z{!#F?%sTơ)Yހ FDC/R>с.H'A/QlkǓ秣 O@r+>>M%4LAJ|`LLjDF<?Ý 7 'g˟ƌOxmٽWdzR~V@ov7*dLp(w45qնv]v:S\NzC J0QUaDT,J)҂$^"]~ 7-7uJ` x 4Ehp`50($"raQڽ*8= z&xj< V7᯳S`5x]@ jn`7  mlƒxm] ܡp{qzGg7+E)@d&Q"2FȾ}kD]Gi*#cb#ED'éptu; $ @PeÜB2m(PMif 2D.SP"On3D\oChD,j2i]NVݷ'+sdSvNnGEy 9qMS&ǖ]mYh8Ţ uؾhmC*JhAm3Q!6M @l~s&L׽ξ,G ?߂$q́ty4_ -0ErP@9'"y ]d9ֶjwdm[۴O$MZ^ 4έkK3L2=NK@$iׯcKs [्we'Ҝ"nӆ˫u*-tK;] \P4+^_|~G! QOnbL&;1؅Ng(/X閪H@TG?< F`Xa=$paa鶲gϚ3}yOLv Ri3bY~Ҝr)J" VDjZ? /Yc~̿_ |lE g޺Ι9Hŗ IDAT{G߾|g}i?7ͱO*A) "2\c:;ujР!LlR*UW$JaGrd~vBwwGu9xQu"o2FY%=Xñ I|ĄȺ^<KoO}R U^\F6&i2a>o?لٕEpeGn-ǻ}Gyq^M{u4t'H/!VMhJznMj ]P pn@4BOlCªGS͘OSœ3h,eɐnR͙@wjL-Oj@8P[ h2୺] M/.*xe]߇f 8wl9`2$c .,ȇ&`ž T3QM^hņ(. ˎ=-ݏm &%Mu Vn]TWP0Yt3~U 7v#tO1Sz̄J!//:3=/EѦ&JpsK[.LzrUMS(wC= Dsѽ!3[.zTq=Ȝ3u)!MƏv5 厶 5cⴚk0bLegNΓL#|C  ژt@UܟlHJ:tܛ !Lj r{;h%EB|>߻w }gW]Vki1ÒhC{}%~E9T݂ɀ` ,I 9Dd8%dx%@LƋ;3ƒ-@I "xuW;nj" ̲X0X'GYG r/v♍xlM;zb :;T|(`Չ@[& ߘ_ L>(ĪQqν{'N{7_ܣ-^rǚSΚ54 ?^ Še-s}Ҽ JF,--=S{ 2:Rl#]4QVN`Y^igȲL.FvV_=U Vz'$W{y^QCam?koE"Qq,ӖzN4$,;fV1*Lf?VaDǧF=ڃ:"")"%"#d`VD'ث_T7ۃ-!:A,  "aۗI9'ڀ.oI\./$O6&HH'd_|1ݽ{7mnn{,޵kbT:N˪ K],(,Z_lMptosx?jnx.2sϱ  5=D {³/}\3V=bm,ϔXԊ G FF6VsCi:a"JP=g1 YSD4ㆅ״??V2&ffffq.hH7F;1 `F9݅8`n:BFN,ht͈$-IRx8N O 'oMbO*wg8 ӛ*7 g ^a3-kNk& n"#?Vyhm~0.o\'EV8p)eDEbDw*LjLPGCBS&۶4?v o---& |ʣ4Nzk r,!yJ&Ȍ>#Wc TQ<0p mrJ'پt+MZG|K/E^SJiȐ6X>k(ΰC( Yn @5Tެ cվ@8^/'7cZ y> INgHGs_5vLۇcE&qgVg7O@C*މ-ۼ81\6vu3n@y.Ȉ}:(\%R[Wؾ}J9ZV 3|bf¥TpG%jnutkrmO^~5}"s"o}[B62soSFNgl9,dE>y0 V Db0z ˆQ3㧑Nԛ!6`9=sC/v`>y: R{ax8m@RU @d{e12ׅQ.Qc[^4|yN.Vt3/WWЁ޲Pך𛥍x<0߇puhtn]Ti? }fk88lDucÆimע2"S>kF}'λX@KlbtD_]+r+* ~iΝ'!̤ "[͕(DJ9;5r]јNXA@ $"r=gD#F2\b~q9)c+A3"ГXfGuT)(D"@  n'!3J#V a+>+ "DbZ[?ɵ6HH|"(Wc5f%MU%< xl$EC!TneYH(pլB|sA1RM\z;6 ߞJ]C9"&o}.sRCa pHYTίJ+AJ،c{pCڥӡCBH#^Uۍ]zĮv]/_,Ees?'r~YWm0hGo ۼ=?h?.\pມKntHdž@Ʊ2-@CsG:-sbFQm+('H/3wxo&ѹA,ΨL%]u r6-`wK~9#_?n}r̴opA]#'M^e"Py\/+}ٺ3VW_w FWo֎N^3j7> ujCk+d<9и!pD 1I nBl Fl9C}g]T":Q0ԉ "`>h&,ǝxp{_6a| Yv~4i8\jC\`)Nh gʸjf!y,J[1 lGgD)=Yﲗw^ȸd찼~B[J?#*L?67 ,|%b]=]?:'cWI!`Ǽ MAdIqT Mk IGBRtxuV)J3ߞ_]cҐ"HFFc?Qb1f`T˧Oœs Tbn~{a pA/&#! Uċtd >z,"]'acqѠ|V%@ L+bvdgqB_>ZotȑdmԤ9)틿_Ryg*$PĬFw4pe  v14(Y&s~9Uw5P]N=U81ԇ)KG68i0'aqï!C- [qۄL!N"0Itk4$쾺ʖkhx='1*BWᇮD⤼A¹:@8XEE[?ݻnwVD(AD{ D@̊%K￐ vt ;;bjv6zd *P` A .KC0ns! 9DHj&IgOK MnaGkC[Chh}_gD`h.XLM6u?pN;EEATa{]$o~euP\sоoIc\d0[ 2<\?ЅO%:ڭY3fRB 6ekc3X5#4ܜuHb ee@ 8F Cƀp@(@TT@0 DCB@ (jnSCz? "C ;+5 Ld8`c]aҴzϓ>t [vh[=r㟳fLAۄWС4w7n`̌ _VC1/?)`!b h-PܔZ]pˍpIC)C8HsttPr?8?QLX!3dÆB@zm NdD@Ŕ%fv[Ҿ޾&؉9m_)p/- Cs]nqF=]W$Bk$dDL$&pea n\6f*9 LO''!S!H)|!gi*whjpn.gE64ERiP@-O&5H'r혯L_K $m/7ntoeܚw~9rY1U+_hT/.+@SdX=.G_Xj|G0qP;?H,09=}A [[@OBg^bK6l뱋h hGo@KH1LN9БDBG[ 9MEޛ|Pe"at84s\q3>j\iMU;936@>YM[ا52ZZfB[[^RP Us+Jrshܠ֙ 0J&n46GEWi/TeqbN0DB'PBxaƐlE\06߉]XqEb2`H9̓x+rKAօ|9wP|@%"`K'XZ4՞$6l2zM$ӂB?zVA˛|y`ݲlC΅;WLdirJly4quB1IDmYčT YQqF H1:DXޔkC`}I :ٍ=ׂ@e  R$EJ- AI`g?z\ό>H86Ņ"AT/o ;y)^%vG=L$s:m&(_@Z]nlzgv=^)&t1*?^0Z)A]{xe=j_8-/n`xx39} 1fF6pD_-Q/r`w{HC`1QhH?j@@1 Tą% _3H0UXHۿü{7 9@>IwYxNnn/Z,+W& s;^EuYNj78"uJi6z 7roOl:jH!6{_z#CcB(18pF49&9[@q[](JSpf1$CpPc`TncⅦJFO"@"OB&"=W'3K 0<##=YbRk헾I,}(qStn3[҇La)C}ӱ5.@w؀fr&GD5ܟĝKpR\4!:hl =Uf0wwPyP! ͏7bOGYH^ V(!G0|IGϷ"%aLDMQ Et"!°8b]C{qEC'EcppT̉Ɗ_b~!$;c_5!rkP$c `@9bÑp`\ǚ`?\Q?sP@KBwH;MQ+W0:߁P„,9{C)PP&e@(aRÝMwavu~rABI̱ p*|Iaw[{vvtaR>i 6.Ȥޮ4H7ADr-cvJ ߾?L?/˽ڀ܆̡ul+Ux>$UڙY&̦^Xk+QeC\ ^frDyq.%Z1HY_aC 9o? ·fr h(N E `i7 bDrɃ$CasxYٟPKREcG(L5mA+(ջz1"9vfrpX# IDATk1#ܸ|Nrxl|vPBR";86~e6L-;ݫH@-{xuW d$|if6lfVH Bq<}I*);@qˍD7]fdx\"Q4xi󪼘QAOCkWC`X\GX0M_mM|2>mZkV#>0}'@"L=ٽ8p:0XKk+ck_v-֢۟i%s@%g/Fk :C1\0.=uo#1܇{ԻhK8ӎB`jc Qq]x>mP<ܘQ$ivwçjͰFdWV!w_=i=ƃQ\9- Y6o0U8_uHj^Ko69<]"W\:yg`$M0aZs|&a,9s]n[-XL#Ԕ%#Óݝ(͔12ǑR` U.̫D};{t412ua| ,֧"[&,;Ƈfit(o^ݯ`M]?2@%;{I)9O=Ͽ7q湹v4@A>8''1 #d=׼ާ''r:)"J mra IɌVs4LLԄ?ih'N===1˾py>#u-DUoUL+NS_1܂#ԅ%2  #ϧ11 ^b^]ŀnuyN.Gp<k{ݿo+wYri?ZB;("ES_n^wjCxqWh5(sJڪfwe?}=ߵk4˅}p4ʱNOZZRJ # o]9bYruPsF6w>ҍ(=YP5H1"g!+0})I v㖰xB7ENS ,3m360MyӖɓ_?۶ g&w:%:ɭ#^O-X[#Ry6}b}v7PEe˖Y ι4#|>nF\h$91^!0t <;ڣoU{A:c0xc oD4{uB51) 660ԍ]Q<+v"NjAˀ7p,0b?vw:O{ iqxdȶmfԈ/`uy՗֚D)؏!>B.h#YxIIiT.IOJ:;;իW[eY?O'l{occm9jG4XKl%OyDvw4cT#r_*` jFGF#Cs2'NCH 7.q'-/Rï//öygP4!8;WE4F3r~Wocm#t;UġIARR gzh@TM =]|H02K_o|kŝ(}fA9,#\8 =ҋiHsGThbZR(DET K]\1S#L@DW@vw&]()xt}^ݩ 6I91YX˒ [QHR $==OR[#zhIC)ѕV#N7pwAt~!䜓W_}w^iϧL9()S'Xڄ]aWepq,t!:`Zm̀aiGI^w-*7B Ac}Q?yyXǏj`AS9 Mig].XuҍI;{&&Q\-%H,p v֣:V!v>_|1ikkn`PtƌNKw[KiC.$88@I''k%֖7emKJ PY)BRA32q ?} Ēr˘Y^P1UE /,Fw_Oo;- >L!)! WKnl<{BJ)A:6Mtx7 k&aLy4M/߿==\Y|ȓm/x)ٙ٢]ʃ@*'&-DɁ] )ŌrS$E<Ѕ3Ga ̬"%!` 'b9 g"+ɍxt}y0 CC_Gnb[d}'*K PpΉ]PGH(ݢYiuN{,Yl6b&}7-b7p8u=ۥ]e9 v)ȧXp6R&!#c\3>J˜ҒhYHh&Of2t0J|q$8{#E`0*8^ZG#I74M A'ebK:8hٕۂDs֢DqUXflݲggR,[c:QORꍤb>Z/vY'AΞ;vYgo`̘1d߾}w17ɷm4;-odՈ)jΔXDW㐅إ؎̴3mhնV֥jE PP l!, ! &}9g76;'}'yu QHHh_0 iЙ:ꤾ'8e`Eu7廧Goޱks}i=mdȜ\7C"Lm U ԟk'NT\qI/z_RkG \{Ud➻=\tMV,]8ۥ*+r2$m(jב }Z!N 9"|KQ98``:Lp$4BP0O mthROdT!Ӌ]ȕ4E&VoEvfy7ujt*!(kGcMPwG07Tǿ4n0TՇ-+Lﯾ{ZPL15'^-tߚ}>8GA]X0J;֐u'\gBgkc2wPe[b8;rxX=={W~kt[c#DU5-/w؇ah!TB)3, A(%TfLPB"K"$!$,K.8tnP04Ci<d#fП0~C8}3*|W['ԣDk#+yyYBQd#jjjXĉ^i]! [ɔfrM 9ͭ;盐0}C T"`h%9b7ϺjX}xds"͟]:ڬ|laImL1Q&Imfŷtǃwe+"$PY {їLSq!qAbY(s~F+݌ uII$I=I"ABeYLӐt]mbL&`"2K*xhh9ax}dzUIaHڷcIR^~6 pB\Wn\{w1UU"b)3Ob!茶F_ӳI B&2#?tbxϿ:^$lT `C 6@VVV>q3=K׋&rrHp*A.Qد}Uk3ҙI)2Ʃdwk7Eן:!$yJ87nt/^ )--ץjK1_n`ʕdv2 $7_{ (Fо/.(&y/$ޱxnC` _BJizz:NM0XB"~s+ջ"֖umܜ:ʌ;v.q3Ro>,|?)=ۀ=5_zv-h*F/lJ:k4z)"!A|ºΖ&.s=lT `C٠ֳTlf\"8]")Ĺ[.>{COcw߸>utTy|R7ʧT; WrMPxDYR;\mMKhF )&G/95$y'(PV\7qI˗\#O BNNXqI߸-m~!H'ӌnO}Ck g%~8޳>h%7ѧo&qd~oaQ4a ';A흱Ї|3v]g>L&Qe->vyÌm=kR=TB8,@@EQͩUS`^A>RvphC.˾z465CfZIJ=-=)jܼ6&I,qqPvd8wd|0 H@$"{F^^U^׉WJD$q2FyU^%m{SIZOh𱁁x2R f r[twČv̘1c2ydadj/eOw 4x-TOdVv?ut `nфa@[G+1MN(aE/6-wb |-Q1~B[*V.̺fړ؝ݗHA2ڵwH?tt@?`\T.ѼH]لYČ`:Mq 1O6B]yOϜ@o@rQZUec0O5PrXGgO/YfR?Le!9)Q30yT6EOcH6K(23L&P>ڒ4ZMP54E 2,_(Oy5S)9]n8msΝi@_arJT!kt+fr>ATE/Rz1d)Ѓ>ܿڢq;r W]g\}:1"D%qF?2UgQQ<;ƀD}]klm |؊:HW;!6OdH_6m}}( N2SCntȣM]JWTf0pgmݾ\ V}{ojzz3 cDwt Gr_?|l\ ѓT`]4}]JNQn|-f "x5{,`B`ęNF#`FfV֣YΫDYCƘebL2>.w,e#Ye"ݙ}VgDAs/{rL:;\׋c+JEbXQpZeXvg^I Aź6eH(ly v (,&,} %!A`;>[A3ɡP.ΆdB0"QDN7`EŠ8,GBH2ueC5c04G}>KRA$S*ydI͗]P@찪{4͒EA?ht"L9zȎvXR2haO7/7)fO9[FPRRB,IYB%n$@Ĉ".>!gKkC9DE5as#^!*k9{-3>DÏ%D(DE~+i!*!qBrV6_bTͲ Q/(\ě{E-(ukϔ1"D狊x"Qs=RE<,Cih8;iYE\le3&DCw8D*x-DzY?EI[)D qwiuYhAKkC9D J!:!rqoҶz?#'"тRC9DE,mV J! Qcn :Ȗݏcb" esrSO5l9}m6i Q>MۗYy]`l 4Czszfv?x!bk.C_Gϰ=DCl Dqlaf̘ Q_Ʊםr Gq|:oN̩CmZ3CϜpķ,_J89Sk9;D3[s~Nglq0\ċ\!:5YmLQ;h{ǖrC{ho..cKǖ=ڻg6ԥ5D c{{> J "^22)e٬D_}ڥu1>Sz[~~:|gidr-םeAKk/(-jӾ4۲GI[7n9 rqwi]U*]ڋx>K)-D3>-D5g[~O"ޤ<mV}νn^VyA4ui^Cg_]CP^P5LK{[r{am Jh.k9pmҶPQCy.k9[qmPҺ깞{R׸{Rě>dk9 Sp1! .3RW>̶"..ko?]Y?Q .o9=SdKvi.?|SMB4Ė3RI|WREK{[B4Ė! %{*ar1??})W7KB~9=8>s\~7oqLn$D[Bt{mBK{nkQ=لBo =fx"ޥoGxCv[ 7IENDB`i2pd-2.17.0/android/res/menu/000077500000000000000000000000001321131324000156025ustar00rootroot00000000000000i2pd-2.17.0/android/res/menu/options_main.xml000066400000000000000000000007771321131324000210360ustar00rootroot00000000000000 i2pd-2.17.0/android/res/values/000077500000000000000000000000001321131324000161355ustar00rootroot00000000000000i2pd-2.17.0/android/res/values/strings.xml000077500000000000000000000011241321131324000203510ustar00rootroot00000000000000 i2pd i2pd started i2pd service started i2pd service stopped Quit Graceful Quit Graceful quit is already in progress Graceful quit is in progress i2pd-2.17.0/android/src/000077500000000000000000000000001321131324000146345ustar00rootroot00000000000000i2pd-2.17.0/android/src/org/000077500000000000000000000000001321131324000154235ustar00rootroot00000000000000i2pd-2.17.0/android/src/org/purplei2p/000077500000000000000000000000001321131324000173455ustar00rootroot00000000000000i2pd-2.17.0/android/src/org/purplei2p/i2pd/000077500000000000000000000000001321131324000202035ustar00rootroot00000000000000i2pd-2.17.0/android/src/org/purplei2p/i2pd/DaemonSingleton.java000066400000000000000000000063061321131324000241410ustar00rootroot00000000000000package org.purplei2p.i2pd; import java.util.HashSet; import java.util.Set; import android.util.Log; public class DaemonSingleton { private static final String TAG="i2pd"; private static final DaemonSingleton instance = new DaemonSingleton(); public static interface StateUpdateListener { void daemonStateUpdate(); } private final Set stateUpdateListeners = new HashSet(); public static DaemonSingleton getInstance() { return instance; } public synchronized void addStateChangeListener(StateUpdateListener listener) { stateUpdateListeners.add(listener); } public synchronized void removeStateChangeListener(StateUpdateListener listener) { stateUpdateListeners.remove(listener); } public synchronized void stopAcceptingTunnels() { if(isStartedOkay()){ state=State.gracefulShutdownInProgress; fireStateUpdate(); I2PD_JNI.stopAcceptingTunnels(); } } public void onNetworkStateChange(boolean isConnected) { I2PD_JNI.onNetworkStateChanged(isConnected); } private boolean startedOkay; public static enum State {uninitialized,starting,jniLibraryLoaded,startedOkay,startFailed,gracefulShutdownInProgress}; private State state = State.uninitialized; public State getState() { return state; } public synchronized void start() { if(state != State.uninitialized)return; state = State.starting; fireStateUpdate(); new Thread(new Runnable(){ @Override public void run() { try { I2PD_JNI.loadLibraries(); synchronized (DaemonSingleton.this) { state = State.jniLibraryLoaded; fireStateUpdate(); } } catch (Throwable tr) { lastThrowable=tr; synchronized (DaemonSingleton.this) { state = State.startFailed; fireStateUpdate(); } return; } try { synchronized (DaemonSingleton.this) { daemonStartResult = I2PD_JNI.startDaemon(); if("ok".equals(daemonStartResult)){ state=State.startedOkay; setStartedOkay(true); }else state=State.startFailed; fireStateUpdate(); } } catch (Throwable tr) { lastThrowable=tr; synchronized (DaemonSingleton.this) { state = State.startFailed; fireStateUpdate(); } return; } } }, "i2pdDaemonStart").start(); } private Throwable lastThrowable; private String daemonStartResult="N/A"; private synchronized void fireStateUpdate() { Log.i(TAG, "daemon state change: "+state); for(StateUpdateListener listener : stateUpdateListeners) { try { listener.daemonStateUpdate(); } catch (Throwable tr) { Log.e(TAG, "exception in listener ignored", tr); } } } public Throwable getLastThrowable() { return lastThrowable; } public String getDaemonStartResult() { return daemonStartResult; } private final Object startedOkayLock = new Object(); public boolean isStartedOkay() { synchronized (startedOkayLock) { return startedOkay; } } private void setStartedOkay(boolean startedOkay) { synchronized (startedOkayLock) { this.startedOkay = startedOkay; } } public synchronized void stopDaemon() { if(isStartedOkay()){ try {I2PD_JNI.stopDaemon();}catch(Throwable tr){Log.e(TAG, "", tr);} setStartedOkay(false); } } } i2pd-2.17.0/android/src/org/purplei2p/i2pd/ForegroundService.java000066400000000000000000000066001321131324000245030ustar00rootroot00000000000000package org.purplei2p.i2pd; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.util.Log; import android.widget.Toast; public class ForegroundService extends Service { private NotificationManager notificationManager; // Unique Identification Number for the Notification. // We use it on Notification start, and to cancel it. private int NOTIFICATION = R.string.i2pd_started; /** * Class for clients to access. Because we know this service always * runs in the same process as its clients, we don't need to deal with * IPC. */ public class LocalBinder extends Binder { ForegroundService getService() { return ForegroundService.this; } } @Override public void onCreate() { notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); // Display a notification about us starting. We put an icon in the status bar. showNotification(); daemon.start(); // Tell the user we started. Toast.makeText(this, R.string.i2pd_service_started, Toast.LENGTH_SHORT).show(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("ForegroundService", "Received start id " + startId + ": " + intent); daemon.start(); return START_STICKY; } @Override public void onDestroy() { // Cancel the persistent notification. notificationManager.cancel(NOTIFICATION); stopForeground(true); // Tell the user we stopped. Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show(); } @Override public IBinder onBind(Intent intent) { return mBinder; } // This is the object that receives interactions from clients. See // RemoteService for a more complete example. private final IBinder mBinder = new LocalBinder(); /** * Show a notification while this service is running. */ private void showNotification() { // In this sample, we'll use the same text for the ticker and the expanded notification CharSequence text = getText(R.string.i2pd_started); // The PendingIntent to launch our activity if the user selects this notification PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, I2PD.class), 0); // Set the info for the views that show in the notification panel. Notification notification = new Notification.Builder(this) .setSmallIcon(R.drawable.itoopie_notification_icon) // the status icon .setTicker(text) // the status text .setWhen(System.currentTimeMillis()) // the time stamp .setContentTitle(getText(R.string.app_name)) // the label of the entry .setContentText(text) // the contents of the entry .setContentIntent(contentIntent) // The intent to send when the entry is clicked .build(); // Send the notification. //mNM.notify(NOTIFICATION, notification); startForeground(NOTIFICATION, notification); } private final DaemonSingleton daemon = DaemonSingleton.getInstance(); } i2pd-2.17.0/android/src/org/purplei2p/i2pd/I2PD.java000077500000000000000000000162311321131324000215520ustar00rootroot00000000000000package org.purplei2p.i2pd; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Timer; import java.util.TimerTask; import android.annotation.SuppressLint; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; import android.widget.Toast; public class I2PD extends Activity { private static final String TAG = "i2pd"; private TextView textView; private final DaemonSingleton daemon = DaemonSingleton.getInstance(); private DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = new DaemonSingleton.StateUpdateListener() { @Override public void daemonStateUpdate() { runOnUiThread(new Runnable(){ @Override public void run() { try { if(textView==null)return; Throwable tr = daemon.getLastThrowable(); if(tr!=null) { textView.setText(throwableToString(tr)); return; } DaemonSingleton.State state = daemon.getState(); textView.setText(String.valueOf(state)+ (DaemonSingleton.State.startFailed.equals(state)?": "+daemon.getDaemonStartResult():"")); } catch (Throwable tr) { Log.e(TAG,"error ignored",tr); } } }); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); textView = new TextView(this); setContentView(textView); DaemonSingleton.getInstance().addStateChangeListener(daemonStateUpdatedListener); daemonStateUpdatedListener.daemonStateUpdate(); //set the app be foreground doBindService(); } @Override protected void onDestroy() { super.onDestroy(); localDestroy(); } private void localDestroy() { textView = null; DaemonSingleton.getInstance().removeStateChangeListener(daemonStateUpdatedListener); Timer gracefulQuitTimer = getGracefulQuitTimer(); if(gracefulQuitTimer!=null) { gracefulQuitTimer.cancel(); setGracefulQuitTimer(null); } try{ doUnbindService(); }catch(Throwable tr){ Log.e(TAG, "", tr); } } private CharSequence throwableToString(Throwable tr) { StringWriter sw = new StringWriter(8192); PrintWriter pw = new PrintWriter(sw); tr.printStackTrace(pw); pw.close(); return sw.toString(); } // private LocalService mBoundService; private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the service object we can use to // interact with the service. Because we have bound to a explicit // service that we know is running in our own process, we can // cast its IBinder to a concrete class and directly access it. // mBoundService = ((LocalService.LocalBinder)service).getService(); // Tell the user about this for our demo. // Toast.makeText(Binding.this, R.string.local_service_connected, // Toast.LENGTH_SHORT).show(); } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. // Because it is running in our same process, we should never // see this happen. // mBoundService = null; // Toast.makeText(Binding.this, R.string.local_service_disconnected, // Toast.LENGTH_SHORT).show(); } }; private boolean mIsBound; private void doBindService() { // Establish a connection with the service. We use an explicit // class name because we want a specific service implementation that // we know will be running in our own process (and thus won't be // supporting component replacement by other applications). bindService(new Intent(this, ForegroundService.class), mConnection, Context.BIND_AUTO_CREATE); mIsBound = true; } private void doUnbindService() { if (mIsBound) { // Detach our existing connection. unbindService(mConnection); mIsBound = false; } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.options_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); switch(id){ case R.id.action_quit: quit(); return true; case R.id.action_graceful_quit: gracefulQuit(); return true; } return super.onOptionsItemSelected(item); } @SuppressLint("NewApi") private void quit() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { finishAndRemoveTask(); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { finishAffinity(); } else { //moveTaskToBack(true); finish(); } }catch (Throwable tr) { Log.e(TAG, "", tr); } try{ daemon.stopDaemon(); }catch (Throwable tr) { Log.e(TAG, "", tr); } System.exit(0); } private Timer gracefulQuitTimer; private final Object gracefulQuitTimerLock = new Object(); private void gracefulQuit() { if(getGracefulQuitTimer()!=null){ Toast.makeText(this, R.string.graceful_quit_is_already_in_progress, Toast.LENGTH_SHORT).show(); return; } Toast.makeText(this, R.string.graceful_quit_is_in_progress, Toast.LENGTH_SHORT).show(); new Thread(new Runnable(){ @Override public void run() { try{ Log.d(TAG, "grac stopping"); if(daemon.isStartedOkay()) { daemon.stopAcceptingTunnels(); Timer gracefulQuitTimer = new Timer(true); setGracefulQuitTimer(gracefulQuitTimer); gracefulQuitTimer.schedule(new TimerTask(){ @Override public void run() { quit(); } }, 10*60*1000/*milliseconds*/); }else{ quit(); } } catch(Throwable tr) { Log.e(TAG,"",tr); } } },"gracQuitInit").start(); } private Timer getGracefulQuitTimer() { synchronized (gracefulQuitTimerLock) { return gracefulQuitTimer; } } private void setGracefulQuitTimer(Timer gracefulQuitTimer) { synchronized (gracefulQuitTimerLock) { this.gracefulQuitTimer = gracefulQuitTimer; } } } i2pd-2.17.0/android/src/org/purplei2p/i2pd/I2PD_JNI.java000066400000000000000000000011451321131324000222450ustar00rootroot00000000000000package org.purplei2p.i2pd; public class I2PD_JNI { public static native String getABICompiledWith(); /** * returns error info if failed * returns "ok" if daemon initialized and started okay */ public static native String startDaemon(); //should only be called after startDaemon() success public static native void stopDaemon(); public static native void stopAcceptingTunnels(); public static native void onNetworkStateChanged(boolean isConnected); public static void loadLibraries() { System.loadLibrary("gnustl_shared"); System.loadLibrary("i2pd"); } } i2pd-2.17.0/android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java000066400000000000000000000020701321131324000262720ustar00rootroot00000000000000package org.purplei2p.i2pd; import android.util.Log; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkInfo; public class NetworkStateChangeReceiver extends BroadcastReceiver { private static final String TAG = "i2pd"; //api level 1 @Override public void onReceive(final Context context, final Intent intent) { Log.d(TAG,"Network state change"); try { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo(); boolean isConnected = activeNetworkInfo!=null && activeNetworkInfo.isConnected(); // https://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html?hl=ru // boolean isWiFi = activeNetworkInfo!=null && (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI); I2PD_JNI.onNetworkStateChanged(isConnected); } catch (Throwable tr) { Log.d(TAG,"",tr); } } } i2pd-2.17.0/appveyor.yml000066400000000000000000000021321321131324000150130ustar00rootroot00000000000000version: 2.17.{build} pull_requests: do_not_increment_build_number: true branches: only: - openssl skip_tags: true os: Visual Studio 2015 shallow_clone: true clone_depth: 1 environment: MSYS2_PATH_TYPE: inherit CHERE_INVOKING: enabled_from_arguments matrix: - MSYSTEM: MINGW64 - MSYSTEM: MINGW32 install: - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Rns gcc-fortran gcc" - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" - c:\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" - if "%MSYSTEM%" == "MINGW64" ( c:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-x86_64-boost mingw-w64-x86_64-miniupnpc" ) else ( c:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw-w64-i686-boost mingw-w64-i686-miniupnpc" ) - if "%MSYSTEM%" == "MINGW64" ( set "bitness=64" ) else ( set "bitness=32" ) build_script: - cmd: >- cd \projects\i2pd echo MSYSTEM = %MSYSTEM%, bitness = %bitness% - c:\msys64\usr\bin\bash -lc "make USE_UPNP=yes -j2" - 7z a -tzip -mx9 -mmt i2pd-mingw-win%bitness%.zip i2pd.exe test: off artifacts: - path: i2pd-mingw-win*.zip i2pd-2.17.0/build/000077500000000000000000000000001321131324000135245ustar00rootroot00000000000000i2pd-2.17.0/build/.gitignore000066400000000000000000000003341321131324000155140ustar00rootroot00000000000000# Various generated files /CMakeFiles/ /i2pd /libi2pd.a /libi2pdclient.a /cmake_install.cmake /CMakeCache.txt /CPackConfig.cmake /CPackSourceConfig.cmake /install_manifest.txt # windows build script i2pd*.zip build*.log i2pd-2.17.0/build/CMakeLists.txt000066400000000000000000000542641321131324000162770ustar00rootroot00000000000000cmake_minimum_required ( VERSION 2.8.12 ) # this addresses CMP0059 with CMake > 3.3 for PCH flags cmake_policy( VERSION 2.8.12 ) project ( "i2pd" ) # for debugging #set(CMAKE_VERBOSE_MAKEFILE on) # configurale options option(WITH_AESNI "Use AES-NI instructions set" OFF) option(WITH_AVX "Use AVX instructions" OFF) option(WITH_HARDENING "Use hardening compiler flags" OFF) option(WITH_LIBRARY "Build library" ON) option(WITH_BINARY "Build binary" ON) option(WITH_STATIC "Static build" OFF) option(WITH_UPNP "Include support for UPnP client" OFF) option(WITH_PCH "Use precompiled header" OFF) option(WITH_GUI "Include GUI (currently MS Windows only)" ON) option(WITH_MESHNET "Build for cjdns test network" OFF) option(WITH_ADDRSANITIZER "Build with address sanitizer unix only" OFF) option(WITH_THREADSANITIZER "Build with thread sanitizer unix only" OFF) option(WITH_I2LUA "Build for i2lua" OFF) option(WITH_WEBSOCKETS "Build with websocket ui" OFF) # paths set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules" ) set ( CMAKE_SOURCE_DIR ".." ) set(LIBI2PD_SRC_DIR ../libi2pd) set(LIBI2PD_CLIENT_SRC_DIR ../libi2pd_client) include_directories(${LIBI2PD_SRC_DIR}) include_directories(${LIBI2PD_CLIENT_SRC_DIR}) set (LIBI2PD_SRC "${LIBI2PD_SRC_DIR}/BloomFilter.cpp" "${LIBI2PD_SRC_DIR}/Config.cpp" "${LIBI2PD_SRC_DIR}/Crypto.cpp" "${LIBI2PD_SRC_DIR}/CryptoKey.cpp" "${LIBI2PD_SRC_DIR}/Garlic.cpp" "${LIBI2PD_SRC_DIR}/Gzip.cpp" "${LIBI2PD_SRC_DIR}/HTTP.cpp" "${LIBI2PD_SRC_DIR}/I2NPProtocol.cpp" "${LIBI2PD_SRC_DIR}/Identity.cpp" "${LIBI2PD_SRC_DIR}/LeaseSet.cpp" "${LIBI2PD_SRC_DIR}/FS.cpp" "${LIBI2PD_SRC_DIR}/Log.cpp" "${LIBI2PD_SRC_DIR}/NTCPSession.cpp" "${LIBI2PD_SRC_DIR}/NetDbRequests.cpp" "${LIBI2PD_SRC_DIR}/NetDb.cpp" "${LIBI2PD_SRC_DIR}/Profiling.cpp" "${LIBI2PD_SRC_DIR}/Reseed.cpp" "${LIBI2PD_SRC_DIR}/RouterContext.cpp" "${LIBI2PD_SRC_DIR}/RouterInfo.cpp" "${LIBI2PD_SRC_DIR}/SSU.cpp" "${LIBI2PD_SRC_DIR}/SSUData.cpp" "${LIBI2PD_SRC_DIR}/SSUSession.cpp" "${LIBI2PD_SRC_DIR}/Streaming.cpp" "${LIBI2PD_SRC_DIR}/Destination.cpp" "${LIBI2PD_SRC_DIR}/TransitTunnel.cpp" "${LIBI2PD_SRC_DIR}/Tunnel.cpp" "${LIBI2PD_SRC_DIR}/TunnelGateway.cpp" "${LIBI2PD_SRC_DIR}/Transports.cpp" "${LIBI2PD_SRC_DIR}/TunnelEndpoint.cpp" "${LIBI2PD_SRC_DIR}/TunnelPool.cpp" "${LIBI2PD_SRC_DIR}/Base.cpp" "${LIBI2PD_SRC_DIR}/util.cpp" "${LIBI2PD_SRC_DIR}/Datagram.cpp" "${LIBI2PD_SRC_DIR}/Family.cpp" "${LIBI2PD_SRC_DIR}/Signature.cpp" "${LIBI2PD_SRC_DIR}/Timestamp.cpp" "${LIBI2PD_SRC_DIR}/api.cpp" "${LIBI2PD_SRC_DIR}/Event.cpp" "${LIBI2PD_SRC_DIR}/Gost.cpp" ) if (WITH_WEBSOCKETS) add_definitions(-DWITH_EVENTS) find_package(websocketpp REQUIRED) endif () if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR MSYS) list (APPEND LIBI2PD_SRC "${CMAKE_SOURCE_DIR}/I2PEndian.cpp") endif () if (WITH_I2LUA) add_definitions(-DI2LUA) endif() add_library(libi2pd ${LIBI2PD_SRC}) set_target_properties(libi2pd PROPERTIES PREFIX "") install(TARGETS libi2pd EXPORT libi2pd ARCHIVE DESTINATION lib COMPONENT Libraries) # TODO Make libi2pd available to 3rd party projects via CMake as imported target # FIXME This pulls stdafx # install(EXPORT libi2pd DESTINATION ${CMAKE_INSTALL_LIBDIR}) set (CLIENT_SRC "${LIBI2PD_CLIENT_SRC_DIR}/AddressBook.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/BOB.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/ClientContext.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/MatchedDestination.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/I2PTunnel.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/I2PService.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/SAM.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/SOCKS.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/HTTPProxy.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/I2CP.cpp" "${LIBI2PD_CLIENT_SRC_DIR}/WebSocks.cpp" ) if(WITH_WEBSOCKETS) list (APPEND CLIENT_SRC "${LIBI2PD_CLIENT_SRC_DIR}/Websocket.cpp") endif () add_library(i2pdclient ${CLIENT_SRC}) set(DAEMON_SRC_DIR ../daemon) set (DAEMON_SRC "${DAEMON_SRC_DIR}/Daemon.cpp" "${DAEMON_SRC_DIR}/HTTPServer.cpp" "${DAEMON_SRC_DIR}/I2PControl.cpp" "${DAEMON_SRC_DIR}/i2pd.cpp" "${DAEMON_SRC_DIR}/UPnP.cpp" ) if (WITH_MESHNET) add_definitions(-DMESHNET) endif () if (WITH_UPNP) add_definitions(-DUSE_UPNP) if (NOT MSVC AND NOT MSYS) set(DL_LIB ${CMAKE_DL_LIBS}) endif () endif () # compiler flags customization (by vendor) if (MSVC) add_definitions( -DWIN32_LEAN_AND_MEAN -DNOMINMAX ) # TODO Check & report to Boost dev, there should be no need for these two add_definitions( -DBOOST_THREAD_NO_LIB -DBOOST_CHRONO_NO_LIB ) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL" ) set( CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /INCREMENTAL:NO /LTCG" ) set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} /GL" ) set( CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /INCREMENTAL:NO /LTCG" ) else() if (MSYS OR MINGW) add_definitions( -DWIN32_LEAN_AND_MEAN ) endif () set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Winvalid-pch -Wno-unused-parameter" ) set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -pedantic" ) # TODO: The following is incompatible with static build and enabled hardening for OpenWRT. # Multiple definitions of __stack_chk_fail (libssp & libc) set( CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -flto -s -ffunction-sections -fdata-sections" ) set( CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "-Wl,--gc-sections" ) # -flto is added from above endif () # check for c++11 support include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG("-std=c++11" CXX11_SUPPORTED) CHECK_CXX_COMPILER_FLAG("-std=c++0x" CXX0X_SUPPORTED) if (CXX11_SUPPORTED) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11" ) elseif (CXX0X_SUPPORTED) # gcc 4.6 set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x" ) elseif (NOT MSVC) message(SEND_ERROR "C++11 standart not seems to be supported by compiler. Too old version?") endif () if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe") if (WITH_HARDENING) add_definitions( "-D_FORTIFY_SOURCE=2" ) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat -Wformat-security -Werror=format-security" ) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector --param ssp-buffer-size=4" ) endif () elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # more tweaks if (NOT (MSVC OR MSYS OR APPLE)) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable -Wno-overloaded-virtual -Wno-c99-extensions" ) endif() endif () if (WITH_HARDENING AND MSVC) # Most security options like dynamic base, buffer & stack checks are ON by default set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /guard:cf" ) endif () # compiler flags customization (by system) if (CMAKE_SYSTEM_NAME STREQUAL "Linux") list (APPEND DAEMON_SRC "${DAEMON_SRC_DIR}/UnixDaemon.cpp") # "'sleep_for' is not a member of 'std::this_thread'" in gcc 4.7/4.8 add_definitions( "-D_GLIBCXX_USE_NANOSLEEP=1" ) elseif (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") list (APPEND DAEMON_SRC "${DAEMON_SRC_DIR}/UnixDaemon.cpp") # "'sleep_for' is not a member of 'std::this_thread'" in gcc 4.7/4.8 add_definitions( "-D_GLIBCXX_USE_NANOSLEEP=1" ) elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") list (APPEND DAEMON_SRC "${DAEMON_SRC_DIR}/UnixDaemon.cpp") elseif (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") list (APPEND DAEMON_SRC "${DAEMON_SRC_DIR}/UnixDaemon.cpp") elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR MSYS) list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/DaemonWin32.cpp") if (WITH_GUI) list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32App.cpp") set_source_files_properties("${CMAKE_SOURCE_DIR}/Win32/DaemonWin32.cpp" PROPERTIES COMPILE_DEFINITIONS WIN32_APP) endif () list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Win32Service.cpp") list (APPEND DAEMON_SRC "${CMAKE_SOURCE_DIR}/Win32/Resource.rc") endif () if (WITH_AESNI) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes" ) add_definitions ( -DAESNI ) endif() if (WITH_AVX) set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx" ) endif() if (WITH_ADDRSANITIZER) if (NOT MSVC) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer" ) set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address" ) else () message( SEND_ERROR "MSVC does not support address sanitizer option") endif() endif() if (WITH_THREADSANITIZER) if (WITH_ADDRSANITIZER) message( FATAL_ERROR "thread sanitizer option cannot be combined with address sanitizer") elseif (NOT MSVC) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread" ) set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread" ) else () message( SEND_ERROR "MSVC does not support address sanitizer option") endif() endif() # libraries # TODO: once CMake 3.1+ becomes mainstream, see e.g. http://stackoverflow.com/a/29871891/673826 # use imported Threads::Threads instead set(THREADS_PREFER_PTHREAD_FLAG ON) if (IOS) set(CMAKE_THREAD_LIBS_INIT "-lpthread") set(CMAKE_HAVE_THREADS_LIBRARY 1) set(CMAKE_USE_WIN32_THREADS_INIT 0) set(CMAKE_USE_PTHREADS_INIT 1) else() find_package ( Threads REQUIRED ) endif() if(THREADS_HAVE_PTHREAD_ARG) # compile time flag set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") endif() if (WITH_STATIC) set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_STATIC_RUNTIME ON) if (WIN32 AND NOT MSYS AND NOT MINGW) # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace foreach(flag_var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) if(${flag_var} MATCHES "/MD") string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") endif(${flag_var} MATCHES "/MD") endforeach(flag_var) else () set(CMAKE_FIND_LIBRARY_SUFFIXES .a) endif () set(BUILD_SHARED_LIBS OFF) if (${CMAKE_CXX_COMPILER} MATCHES ".*-openwrt-.*") set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread" ) # set( CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,--whole-archive -lpthread -Wl,--no-whole-archive" ) set( CMAKE_THREAD_LIBS_INIT "gcc_eh -Wl,-u,pthread_create,-u,pthread_once,-u,pthread_mutex_lock,-u,pthread_mutex_unlock,-u,pthread_join,-u,pthread_equal,-u,pthread_detach,-u,pthread_cond_wait,-u,pthread_cond_signal,-u,pthread_cond_destroy,-u,pthread_cond_broadcast,-u,pthread_cancel" ) endif () else() if (NOT WIN32 AND NOT MSYS) # TODO: Consider separate compilation for LIBI2PD_SRC for library. # No need in -fPIC overhead for binary if not interested in library # HINT: revert c266cff CMakeLists.txt: compilation speed up set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" ) endif () add_definitions(-DBOOST_SYSTEM_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_DATE_TIME_DYN_LINK -DBOOST_REGEX_DYN_LINK) endif () if (WITH_PCH) include_directories(BEFORE ${CMAKE_BINARY_DIR}) add_library(stdafx STATIC "${LIBI2PD_SRC_DIR}/stdafx.cpp") if(MSVC) target_compile_options(stdafx PRIVATE /Ycstdafx.h /Zm155) add_custom_command(TARGET stdafx POST_BUILD COMMAND xcopy /y stdafx.dir\\$\\*.pdb libi2pd.dir\\$\\ COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pdclient.dir\\$\\ COMMAND xcopy /y stdafx.dir\\$\\*.pdb i2pd.dir\\$\\ WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) target_compile_options(libi2pd PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") target_compile_options(i2pdclient PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") else() string(TOUPPER ${CMAKE_BUILD_TYPE} BTU) get_directory_property(DEFS DEFINITIONS) string(REPLACE " " ";" FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BTU}} ${DEFS}") add_custom_command(TARGET stdafx PRE_BUILD COMMAND ${CMAKE_CXX_COMPILER} ${FLAGS} -c ${CMAKE_CURRENT_SOURCE_DIR}/../libi2pd/stdafx.h -o ${CMAKE_BINARY_DIR}/stdafx.h.gch ) target_compile_options(libi2pd PRIVATE -include libi2pd/stdafx.h) target_compile_options(i2pdclient PRIVATE -include libi2pd/stdafx.h) endif() target_link_libraries(libi2pd stdafx) endif() target_link_libraries(i2pdclient libi2pd) find_package ( Boost COMPONENTS system filesystem program_options date_time REQUIRED ) if(NOT DEFINED Boost_INCLUDE_DIRS) message(SEND_ERROR "Boost is not found, or your boost version was bellow 1.46. Please download Boost!") endif() find_package ( OpenSSL REQUIRED ) if(NOT DEFINED OPENSSL_INCLUDE_DIR) message(SEND_ERROR "Could not find OpenSSL. Please download and install it first!") else() if(NOT (OPENSSL_VERSION VERSION_LESS 1.1)) message(WARNING "Your OpenSSL version ${OPENSSL_VERSION} >=1.1 is experimental: build with v1.0 when possible.") endif() endif() if (WITH_UPNP) find_package ( MiniUPnPc REQUIRED ) include_directories( SYSTEM ${MINIUPNPC_INCLUDE_DIR} ) endif() find_package ( ZLIB ) if (NOT ZLIB_FOUND ) # We are probably on Windows find_program( PATCH patch C:/Program Files/Git/usr/bin C:/msys64/usr/bin C:/msys32/usr/bin C:/Strawberry/c/bin ) include( ExternalProject ) if( CMAKE_SIZEOF_VOID_P EQUAL 8 ) set( ZLIB_EXTRA -DAMD64=ON ) else() set( ZLIB_EXTRA -DASM686=ON "-DCMAKE_ASM_MASM_FLAGS=/W0 /safeseh" ) endif() ExternalProject_Add(zlib-project URL http://zlib.net/zlib-1.2.8.tar.gz URL_MD5 44d667c142d7cda120332623eab69f40 PREFIX ${CMAKE_CURRENT_BINARY_DIR}/zlib PATCH_COMMAND "${PATCH}" -p0 < ${CMAKE_CURRENT_SOURCE_DIR}/cmake-zlib-static.patch && "${PATCH}" -p0 < ${CMAKE_CURRENT_SOURCE_DIR}/cmake-zlib-amd64.patch CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= -DWITH_STATIC=${WITH_STATIC} ${ZLIB_EXTRA} ) if (WITH_PCH) add_dependencies( stdafx zlib-project ) else () add_dependencies( libi2pd zlib-project ) endif () # ExternalProject_Get_Property(zlib-project install_dir) set ( ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/zlib/include" CACHE FILEPATH "zlib include dir" FORCE) if (NOT WITH_STATIC) set ( ZLIB_LIBRARY debug zlibd optimized zlib CACHE STRING "zlib libraries" FORCE) endif () endif () if (WITH_STATIC AND (MSVC OR MSYS)) set ( ZLIB_LIBRARY debug zlibstaticd optimized zlibstatic CACHE STRING "zlib libraries" FORCE) endif () link_directories(${CMAKE_CURRENT_BINARY_DIR}/zlib/lib ${ZLIB_ROOT}/lib) # load includes include_directories( SYSTEM ${Boost_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ) # warn if for meshnet if (WITH_MESHNET) message(STATUS "Building for testnet") message(WARNING "This build will NOT work on mainline i2p") endif() # show summary message(STATUS "---------------------------------------") message(STATUS "Build type : ${CMAKE_BUILD_TYPE}") message(STATUS "Compiler vendor : ${CMAKE_CXX_COMPILER_ID}") message(STATUS "Compiler version : ${CMAKE_CXX_COMPILER_VERSION}") message(STATUS "Compiler path : ${CMAKE_CXX_COMPILER}") message(STATUS "Install prefix: : ${CMAKE_INSTALL_PREFIX}") message(STATUS "Options:") message(STATUS " AESNI : ${WITH_AESNI}") message(STATUS " AVX : ${WITH_AVX}") message(STATUS " HARDENING : ${WITH_HARDENING}") message(STATUS " LIBRARY : ${WITH_LIBRARY}") message(STATUS " BINARY : ${WITH_BINARY}") message(STATUS " STATIC BUILD : ${WITH_STATIC}") message(STATUS " UPnP : ${WITH_UPNP}") message(STATUS " PCH : ${WITH_PCH}") message(STATUS " MESHNET : ${WITH_MESHNET}") message(STATUS " ADDRSANITIZER : ${WITH_ADDRSANITIZER}") message(STATUS " THREADSANITIZER : ${WITH_THREADSANITIZER}") message(STATUS " I2LUA : ${WITH_I2LUA}") message(STATUS " WEBSOCKETS : ${WITH_WEBSOCKETS}") message(STATUS "---------------------------------------") #Handle paths nicely include(GNUInstallDirs) if (WITH_BINARY) add_executable ( "${PROJECT_NAME}" ${DAEMON_SRC} ) if (WIN32 AND WITH_GUI) set_target_properties("${PROJECT_NAME}" PROPERTIES WIN32_EXECUTABLE TRUE ) endif() if(NOT MSVC) if (WITH_STATIC) set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-static" ) endif () endif() if (WITH_PCH) if (MSVC) target_compile_options("${PROJECT_NAME}" PRIVATE /FIstdafx.h /Yustdafx.h /Zm155 "/Fp${CMAKE_BINARY_DIR}/stdafx.dir/$/stdafx.pch") else() target_compile_options("${PROJECT_NAME}" PRIVATE -include libi2pd/stdafx.h) endif() endif() if (WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT MSYS AND NOT MINGW) set_target_properties("${PROJECT_NAME}" PROPERTIES LINK_FLAGS "-z relro -z now" ) endif () if (WITH_UPNP) target_link_libraries("${PROJECT_NAME}" "${MINIUPNPC_LIBRARY}") endif () # FindBoost pulls pthread for thread which is broken for static linking at least on Ubuntu 15.04 list(GET Boost_LIBRARIES -1 LAST_Boost_LIBRARIES) if(${LAST_Boost_LIBRARIES} MATCHES ".*pthread.*") list(REMOVE_AT Boost_LIBRARIES -1) endif() if (MSYS OR MINGW) set (MINGW_EXTRA -lws2_32 -lmswsock -liphlpapi ) endif () if (WITH_STATIC) set(DL_LIB ${CMAKE_DL_LIBS}) endif() target_link_libraries( "${PROJECT_NAME}" libi2pd i2pdclient ${DL_LIB} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_EXTRA} ${DL_LIB}) install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) set (APPS "\${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") set (DIRS "${Boost_LIBRARY_DIR};${OPENSSL_INCLUDE_DIR}/../bin;${ZLIB_INCLUDE_DIR}/../bin;/mingw32/bin") if (MSVC) install(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} CONFIGURATIONS DEBUG RELWITHDEBINFO COMPONENT Symbols) # TODO Somehow this picks lots of unrelevant stuff with MSYS. OS X testing needed. INSTALL(CODE " include(BundleUtilities) fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\") " COMPONENT Runtime) endif () if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") if (NOT (MSVC OR MSYS OR APPLE)) # for Clang build on Linux target_link_libraries("${PROJECT_NAME}" stdc++) endif() endif() endif () install(FILES ../LICENSE DESTINATION . COMPONENT Runtime ) # Take a copy on Appveyor install(FILES "C:/projects/openssl-$ENV{OPENSSL}/LICENSE" DESTINATION . COMPONENT Runtime RENAME LICENSE_OPENSSL OPTIONAL # for local builds only! ) file(GLOB_RECURSE I2PD_SOURCES "../libi2pd/*.cpp" "../libi2pd_client/*.cpp" "../daemon/*.cpp" "../build" "../Win32" "../Makefile*") install(FILES ${I2PD_SOURCES} DESTINATION src/ COMPONENT Source) # install(DIRECTORY ../ DESTINATION src/ # # OPTIONAL # COMPONENT Source FILES_MATCHING # PATTERN .git EXCLUDE # PATTERN "*.cpp" # ) file(GLOB I2PD_HEADERS "../libi2pd/*.h" "../libi2pd_client/*.h" "../daemon/*.h") install(FILES ${I2PD_HEADERS} DESTINATION src/ COMPONENT Headers) # install(DIRECTORY ../ DESTINATION src/ # # OPTIONAL # COMPONENT Headers FILES_MATCHING # PATTERN .git EXCLUDE # PATTERN "*.h" # ) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Purple I2P, a C++ I2P daemon") set(CPACK_PACKAGE_VENDOR "Purple I2P") set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../README.md") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE") file(READ ../libi2pd/version.h version_h) string(REGEX REPLACE ".*I2PD_VERSION_MAJOR ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_MAJOR "${version_h}") string(REGEX REPLACE ".*I2PD_VERSION_MINOR ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_MINOR "${version_h}") string(REGEX REPLACE ".*I2PD_VERSION_MICRO ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_MICRO "${version_h}") string(REGEX REPLACE ".*I2PD_VERSION_PATCH ([0-9]+).*" "\\1" CPACK_PACKAGE_VERSION_PATCH "${version_h}") set(CPACK_PACKAGE_INSTALL_DIRECTORY "Purple I2P")# ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}") include(CPackComponent) cpack_add_component(Runtime DESCRIPTION "Main files" REQUIRED INSTALL_TYPES minimal) cpack_add_component(Symbols DISPLAY_NAME "Debug symbols" DESCRIPTION "Debug symbols for use with WinDbg or Visual Studio" INSTALL_TYPES recommended full ) cpack_add_component(Libraries DESCRIPTION "Binary libraries for development" INSTALL_TYPES full dev3rd ) cpack_add_component(Source DISPLAY_NAME "Source code" DESCRIPTION "I2pd source code" INSTALL_TYPES full ) cpack_add_component(Headers DISPLAY_NAME "Header files" DESCRIPTION "I2pd header files for development" INSTALL_TYPES full dev3rd ) install(FILES ${MINIUPNPC_INCLUDE_DIR}/miniupnpc/miniupnpc.dll DESTINATION bin COMPONENT MiniUPnPc OPTIONAL ) install(FILES ${MINIUPNPC_INCLUDE_DIR}/miniupnpc/LICENSE DESTINATION . COMPONENT MiniUPnPc RENAME LICENSE_MINIUPNPC OPTIONAL ) cpack_add_component(MiniUPnPc INSTALL_TYPES full recommended # DOWNLOADED # ARCHIVE_FILE miniupnpc-win32.zip ) cpack_add_install_type(recommended DISPLAY_NAME Recommended) cpack_add_install_type(dev3rd DISPLAY_NAME "Third party development") cpack_add_install_type(full DISPLAY_NAME Full) cpack_add_install_type(minimal DISPLAY_NAME Minimal) if((WIN32 OR MSYS) AND NOT UNIX) # There is a bug in NSI that does not handle full unix paths properly. Make # sure there is at least one set of four (4) backlasshes. set(CPACK_NSIS_DEFINES "RequestExecutionLevel user") set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/../Win32\\\\mask.bmp") set(CPACK_NSIS_INSTALLED_ICON_NAME "bin/i2pd.exe") SET(CPACK_NSIS_DISPLAY_NAME "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") set(CPACK_NSIS_HELP_LINK "https:\\\\\\\\github.com\\\\PurpleI2P\\\\i2pd\\\\issues") set(CPACK_NSIS_URL_INFO_ABOUT "https:\\\\\\\\github.com\\\\PurpleI2P\\\\i2pd") set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Install i2pd as windows service.lnk' '$INSTDIR\\\\bin\\\\i2pd.exe' '--service=install' CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Remove i2pd windows service.lnk' '$INSTDIR\\\\bin\\\\i2pd.exe' '--service=remove'") set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\Install i2pd as windows service.lnk' Delete '$SMPROGRAMS\\\\$START_MENU\\\\Remove i2pd windows service.lnk'") else() set(CPACK_STRIP_FILES "bin/i2pd") set(CPACK_SOURCE_STRIP_FILES "") endif() set(CPACK_PACKAGE_EXECUTABLES "i2pd" "C++ I2P daemon") set(CPACK_SOURCE_GENERATOR "TGZ") include(CPack) i2pd-2.17.0/build/build_mingw.cmd000066400000000000000000000051641321131324000165170ustar00rootroot00000000000000@echo off setlocal enableextensions enabledelayedexpansion title Building i2pd REM Copyright (c) 2013-2017, The PurpleI2P Project REM This file is part of Purple i2pd project and licensed under BSD3 REM See full license text in LICENSE file at top of project tree REM To use that script, you must have installed in your MSYS installation theese packages: REM Base: git make zip REM x86_64: mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-gcc REM i686: mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-gcc REM setting up variables for MSYS REM Note: if you installed MSYS64 to different path, edit WD variable (only C:\msys64 needed to edit)! set "WD=C:\msys64\usr\bin\" set MSYS2_PATH_TYPE=inherit set CHERE_INVOKING=enabled_from_arguments set MSYSTEM=MSYS set "xSH=%WD%bash -lc" REM detecting number of processors and subtract 1. set /a threads=%NUMBER_OF_PROCESSORS%-1 REM we must work in root of repo cd .. REM deleting old log files del /S build_*.log >> nul echo Receiving latest commit and cleaning up... %xSH% "git pull && make clean" > build/build_git.log 2>&1 echo. REM set to variable current commit hash FOR /F "usebackq" %%a IN (`%xSH% 'git describe --tags'`) DO ( set tag=%%a ) %xSH% "echo To use configs and certificates, move all files and certificates folder from contrib directory here. > README.txt" >> nul REM starting building set MSYSTEM=MINGW32 set bitness=32 call :BUILDING echo. set MSYSTEM=MINGW64 set bitness=64 call :BUILDING echo. del README.txt >> nul echo Build complete... pause exit /b 0 :BUILDING echo Building i2pd %tag% for win%bitness%: echo Build AVX+AESNI... %xSH% "make USE_UPNP=yes USE_AVX=1 USE_AESNI=1 -j%threads% && zip -r9 build/i2pd_%tag%_win%bitness%_mingw_avx_aesni.zip i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates && make clean" > build/build_win%bitness%_avx_aesni.log 2>&1 echo Build AVX... %xSH% "make USE_UPNP=yes USE_AVX=1 -j%threads% && zip -r9 build/i2pd_%tag%_win%bitness%_mingw_avx.zip i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates && make clean" > build/build_win%bitness%_avx.log 2>&1 echo Build AESNI... %xSH% "make USE_UPNP=yes USE_AESNI=1 -j%threads% && zip -r9 build/i2pd_%tag%_win%bitness%_mingw_aesni.zip i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates && make clean" > build/build_win%bitness%_aesni.log 2>&1 echo Build without extensions... %xSH% "make USE_UPNP=yes -j%threads% && zip -r9 build/i2pd_%tag%_win%bitness%_mingw.zip i2pd.exe README.txt contrib/i2pd.conf contrib/tunnels.conf contrib/certificates && make clean" > build/build_win%bitness%.log 2>&1 :EOFi2pd-2.17.0/build/cmake-zlib-amd64.patch000066400000000000000000000004601321131324000174740ustar00rootroot00000000000000--- CMakeLists.txt.orig 2015-12-07 14:19:36.447689600 -0600 +++ CMakeLists.txt 2015-12-07 14:18:23.004419900 -0600 @@ -165,6 +165,7 @@ ENABLE_LANGUAGE(ASM_MASM) set(ZLIB_ASMS contrib/masmx64/gvmat64.asm + contrib/masmx64/inffas8664.c contrib/masmx64/inffasx64.asm ) endif() i2pd-2.17.0/build/cmake-zlib-static.patch000066400000000000000000000023021321131324000200450ustar00rootroot00000000000000--- CMakeLists.txt.orig 2013-04-28 17:57:10.000000000 -0500 +++ CMakeLists.txt 2015-12-03 12:53:52.371087900 -0600 @@ -7,6 +7,7 @@ option(ASM686 "Enable building i686 assembly implementation") option(AMD64 "Enable building amd64 assembly implementation") +option(WITH_STATIC "Static runtime on Windows" OFF) set(INSTALL_BIN_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Installation directory for executables") set(INSTALL_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Installation directory for libraries") @@ -66,6 +67,17 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) endif() +if(WITH_STATIC AND (MSVC OR MSYS)) + # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace + foreach(flag_var + CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE + CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) + if(${flag_var} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endif(${flag_var} MATCHES "/MD") + endforeach(flag_var) +endif() + if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) # If we're doing an out of source build and the user has a zconf.h # in their source tree... i2pd-2.17.0/build/cmake_modules/000077500000000000000000000000001321131324000163345ustar00rootroot00000000000000i2pd-2.17.0/build/cmake_modules/FindMiniUPnPc.cmake000066400000000000000000000013241321131324000217410ustar00rootroot00000000000000# - Find MINIUPNPC if(MINIUPNPC_INCLUDE_DIR AND MINIUPNPC_LIBRARY) set(MINIUPNPC_FOUND TRUE) else() find_path(MINIUPNPC_INCLUDE_DIR miniupnpc/miniupnpc.h /usr/include /usr/local/include /opt/local/include $ENV{SystemDrive} ${PROJECT_SOURCE_DIR}/../.. ) find_library(MINIUPNPC_LIBRARY miniupnpc) if(MINIUPNPC_INCLUDE_DIR AND MINIUPNPC_LIBRARY) set(MINIUPNPC_FOUND TRUE) message(STATUS "Found MiniUPnP headers: ${MINIUPNPC_INCLUDE_DIR}") message(STATUS "Found MiniUPnP library: ${MINIUPNPC_LIBRARY}") else() set(MINIUPNPC_FOUND FALSE) message(STATUS "MiniUPnP not found.") endif() mark_as_advanced(MINIUPNPC_INCLUDE_DIR MINIUPNPC_LIBRARY) endif() i2pd-2.17.0/build/docker/000077500000000000000000000000001321131324000147735ustar00rootroot00000000000000i2pd-2.17.0/build/docker/README.md000066400000000000000000000025771321131324000162650ustar00rootroot00000000000000Howto build & run ================== **Build** Assuming you're in the root directory of the anoncoin source code. $ `cd build/docker` $ `docker -t meeh/i2pd:latest .` **Run** To run either the local build, or if not found - fetched prebuild from hub.docker.io, run the following command. $ `docker run --name anonnode -v /path/to/i2pd/datadir/on/host:/var/lib/i2pd -p 7070:7070 -p 4444:4444 -p 4447:4447 -p 7656:7656 -p 2827:2827 -p 7654:7654 -p 7650:7650 -d meeh/i2pd` All the ports ( -p HOSTPORT:DOCKERPORT ) is optional. However the command above enable all features (Webconsole, HTTP Proxy, BOB, SAM, i2cp, etc) The volume ( -v HOSTDIR:DOCKERDIR ) is also optional, but if you don't use it, your config, routerid and private keys will die along with the container. **Options** Options are set via docker environment variables. This can be set at run with -e parameters. * **ENABLE_IPV6** - Enable IPv6 support. Any value can be used - it triggers as long as it's not empty. * **LOGLEVEL** - Set the loglevel. * **ENABLE_AUTH** - Enable auth for the webconsole. Username and password needs to be set manually in i2pd.conf cause security reasons. **Logging** Logging happens to STDOUT as the best practise with docker containers, since infrastructure systems like kubernetes with ELK integration can automaticly forward the log to say, kibana or greylog without manual setup. :) i2pd-2.17.0/build/docker/old-ubuntu-based/000077500000000000000000000000001321131324000201455ustar00rootroot00000000000000i2pd-2.17.0/build/docker/old-ubuntu-based/Dockerfile000066400000000000000000000004311321131324000221350ustar00rootroot00000000000000FROM ubuntu RUN apt-get update && apt-get install -y libboost-dev libboost-filesystem-dev \ libboost-program-options-dev libboost-date-time-dev \ libssl-dev git build-essential RUN git clone https://github.com/PurpleI2P/i2pd.git WORKDIR /i2pd RUN make CMD ./i2pd i2pd-2.17.0/build/fig.yml000066400000000000000000000000231321131324000150070ustar00rootroot00000000000000i2pd: build: . i2pd-2.17.0/contrib/000077500000000000000000000000001321131324000140655ustar00rootroot00000000000000i2pd-2.17.0/contrib/apparmor/000077500000000000000000000000001321131324000157065ustar00rootroot00000000000000i2pd-2.17.0/contrib/apparmor/usr.sbin.i2pd000066400000000000000000000011721321131324000202320ustar00rootroot00000000000000# Basic profile for i2pd # Should work without modifications with Ubuntu/Debian packages # Author: Darknet Villain # #include /usr/sbin/i2pd { #include network inet dgram, network inet stream, network inet6 dgram, network inet6 stream, network netlink raw, /etc/gai.conf r, /etc/host.conf r, /etc/hosts r, /etc/nsswitch.conf r, /run/resolvconf/resolv.conf r, # path specific (feel free to modify if you have another paths) /etc/i2pd/** r, /var/lib/i2pd/** rw, /var/log/i2pd.log w, /var/run/i2pd/i2pd.pid rw, /usr/sbin/i2pd mr, } i2pd-2.17.0/contrib/certificates/000077500000000000000000000000001321131324000165325ustar00rootroot00000000000000i2pd-2.17.0/contrib/certificates/family/000077500000000000000000000000001321131324000200135ustar00rootroot00000000000000i2pd-2.17.0/contrib/certificates/family/gostcoin.crt000066400000000000000000000013251321131324000223530ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB6jCCAY+gAwIBAgIJAPeWi4iUKLBJMAoGCCqGSM49BAMCMHoxCzAJBgNVBAYT AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u eW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYDVQQDDBdnb3N0Y29p bi5mYW1pbHkuaTJwLm5ldDAeFw0xNzA4MDExMzQ4MzdaFw0yNzA3MzAxMzQ4Mzda MHoxCzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNV BAoMFUkyUCBBbm9ueW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYD VQQDDBdnb3N0Y29pbi5mYW1pbHkuaTJwLm5ldDBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABC+9iIYumUNnsqKbnTluHimV8OdGvo7yeGxuqhfNNB2b3jvbFJ81scgH dsZtMQmUxgKM5nH+NQJMoCxHhSlRy2QwCgYIKoZIzj0EAwIDSQAwRgIhANNh7mOp nBBPRh2a/ipG1VYS0d+mNjSrpz8xWcG3CXPLAiEAjM5MTfv9sOJ74PeZVhFZ02w4 vhgyZCeLJ57f123Lm1A= -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/family/i2p-dev.crt000066400000000000000000000014011321131324000217670ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICCjCCAa2gAwIBAgIEfT9YJTAMBggqhkjOPQQDAgUAMHkxCzAJBgNVBAYTAlhY MQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBBbm9ueW1v dXMgTmV0d29yazEPMA0GA1UECxMGZmFtaWx5MR8wHQYDVQQDExZpMnAtZGV2LmZh bWlseS5pMnAubmV0MB4XDTE1MTIwOTIxNDIzM1oXDTI1MTIwODIxNDIzM1oweTEL MAkGA1UEBhMCWFgxCzAJBgNVBAgTAlhYMQswCQYDVQQHEwJYWDEeMBwGA1UEChMV STJQIEFub255bW91cyBOZXR3b3JrMQ8wDQYDVQQLEwZmYW1pbHkxHzAdBgNVBAMT FmkycC1kZXYuZmFtaWx5LmkycC5uZXQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC AAR7FPSglYrxeSPzv74A1fTwjajZWV0TljqEMBS/56juZQB/7xOwrsHFHA0eEEF9 dTH64wx3lhV/9sh/stwPU2MToyEwHzAdBgNVHQ4EFgQUQh4uRP1aaX8TJX5dljrS CeFNjcAwDAYIKoZIzj0EAwIFAANJADBGAiEAhXlEKGCjJ4urpi2db3OIMl9pB+9t M+oVtAqBamWvVBICIQDBaIqfwLzFameO5ULgGRMysKQkL0O5mH6xo910YQV8jQ== -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/family/i2pd-dev.crt000066400000000000000000000013251321131324000221400ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIB6TCCAY+gAwIBAgIJAI7G9MXxh7OjMAoGCCqGSM49BAMCMHoxCzAJBgNVBAYT AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u eW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYDVQQDDBdpMnBkLWRl di5mYW1pbHkuaTJwLm5ldDAeFw0xNjAyMjAxNDE2MzhaFw0yNjAyMTcxNDE2Mzha MHoxCzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNV BAoMFUkyUCBBbm9ueW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYD VQQDDBdpMnBkLWRldi5mYW1pbHkuaTJwLm5ldDBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABMlWL3loKVOfsA8Rm91QR53Il69mQiaB7n3rUhfPkJb9MYc1S4198azE iSnNZSXicKDPIifaCgvONmbACzElHc8wCgYIKoZIzj0EAwIDSAAwRQIgYWmSFuai TJvVrlB5RlbiiNFCEootjWP8BFM3t/yFeaQCIQDkg4xcQIRGTHhjrCsxmlz9KcRF G+eIF+ATfI93nPseLw== -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/family/mca2-i2p.crt000066400000000000000000000012341321131324000220370ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBwTCCAWigAwIBAgIJAOZBC10+/38EMAkGByqGSM49BAEwZzELMAkGA1UEBhMC QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp dHMgUHR5IEx0ZDEgMB4GA1UEAwwXbWNhMi1pMnAuZmFtaWx5LmkycC5uZXQwHhcN MTYwMzI4MjIwMjMxWhcNMjYwMzI2MjIwMjMxWjBnMQswCQYDVQQGEwJBVTETMBEG A1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkg THRkMSAwHgYDVQQDDBdtY2EyLWkycC5mYW1pbHkuaTJwLm5ldDBZMBMGByqGSM49 AgEGCCqGSM49AwEHA0IABNNyfzJr/rMSUeWliVBbJHRF2+qMypOlHEZ9m1nNATVX 64OhuyuVCmbF9R3oDkcZZJQQK1ovXd/EsbAIWDI8K/gwCQYHKoZIzj0EAQNIADBF AiEApmv2tvMwzlvPjHJG1/5aXOSjYWw2s4ETeGt4abWPQkACIBbF3RuCHuzg+KN8 N0n9hAJztAqhRCdG3hilxF4fbVLp -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/family/volatile.crt000066400000000000000000000012401321131324000223410ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBxDCCAWmgAwIBAgIJAJnJIdKHYwWcMAoGCCqGSM49BAMCMGcxCzAJBgNVBAYT AkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRn aXRzIFB0eSBMdGQxIDAeBgNVBAMMF3ZvbGF0aWxlLmZhbWlseS5pMnAubmV0MB4X DTE2MDQyNjE1MjAyNloXDTI2MDQyNDE1MjAyNlowZzELMAkGA1UEBhMCQVUxEzAR BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 IEx0ZDEgMB4GA1UEAwwXdm9sYXRpbGUuZmFtaWx5LmkycC5uZXQwWTATBgcqhkjO PQIBBggqhkjOPQMBBwNCAARf6LBfbbfL6HInvC/4wAGaN3rj0eeLE/OdBpA93R3L s8EUp0YTEJHWPo9APiKMmAwQSsMJfjhNrbp+UWEnnx2LMAoGCCqGSM49BAMCA0kA MEYCIQDpQu2KPV5G1JOFLoZvdj+rcvEnjxM/FxkaqikwkVx8FAIhANP7DkUal+GT SuiCtcqM4QyIBsfsCJBWEMzovft164Bo -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/reseed/000077500000000000000000000000001321131324000200015ustar00rootroot00000000000000i2pd-2.17.0/contrib/certificates/reseed/atomike_at_mail.i2p.crt000066400000000000000000000040721321131324000243260ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIF5TCCA82gAwIBAgIRANFIiHpTaRY2Z30TQOiuqFcwDQYJKoZIhvcNAQELBQAw cDELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGTAXBgNVBAMM EGF0b21pa2VAbWFpbC5pMnAwHhcNMTYwODAyMTQyNDEyWhcNMjYwODAyMTQyNDEy WjBwMQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYD VQQKExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UE AwwQYXRvbWlrZUBtYWlsLmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC ggIBAMLRmxclaAvm405JLHNNiniUi0aZaBoLJ+afwn2LGfTDUhTD5Y8lW6V9o90n eTNOCaiid7bWpVBkA1M4gZ9TdUnP0POa99jXZbj4PHFRl1l8k4Ap12PUO3hgwtH7 7j7j+UPaIuE2y+U7hJbmyQ0v7r8yjGWSTtSqs+exNhyr4Mh7DvacZySZ+oqQdXYA vnfDpBX1dKlN1Nb4XloG0uE1OK1YfJoC+p+v8qXjKagIdZgThdmsWcQ82EGI+Q9u VfrE4m3CNwJy0X86wMNYqHej88wBHnJMmTm+cZtFLVmZsRqnuLAQL1wrfCbGSltR zhVQHTysLwMz9+llTXtzMf+R2kcEAYWiPc5IRVU+LvkN/610r5fuHW+OcQ9ZgRVn PMqlv5PDG2ZxdIOAQQsOd7fH0r5q3MhqlVstVE45Rl33uA+M7wjJK2cvnOoSioxp szn2GIZliXQXo4dJczgfN2U4PLBGRBGmrB1R2S1YsG6CrSJuMCX14VKJP69Nfm8a EDA5GKNke+ZpXCszPLaNMB70LVFQc9FmMhsOgLIIoJBgd61uMgokMJJMLaWN0RaK w1ZduxYGUmg2T2pi/clIkVzZmlcHKViUn0sMcKD+ibEPOvQIB/3HPEEt6iIkanc/ da5IFzikkaykt/Tu6o8rreeEu65HkIxFaCHegSXLHSyxj00BAgMBAAGjejB4MA4G A1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYD VR0TAQH/BAUwAwEB/zAZBgNVHQ4EEgQQYXRvbWlrZUBtYWlsLmkycDAbBgNVHSME FDASgBBhdG9taWtlQG1haWwuaTJwMA0GCSqGSIb3DQEBCwUAA4ICAQAA0MdWfN/N 1q5CdJqDyw4JQwzdYkA27Wr02qIcmwnqjcCEDPl4uDTyqN9gbEpJ48AcsdXRa6GE lLh/qJ67I6YDe63LuhndzRULNgxGHVMGS8kBJIssQehb2rOFnbUTp0gMR+0QpXXe omase4kL90c9uuYX1vXaO/ADssY2/QX49prwJO+UY/jGhcX4YheFI/teA85u6Qko ero437Shqhl0kbdK+eBkOFf9a7mGxpMT73KE1jFS6433W4fFOkybQ1dcS0qStaUM 3qKC0EQCbAl1seAp3AGuG46swHZB0rZ1WCKVAr5yqCWSWMYO+fL6FosNg9z/VDVh g6FFfoGrv19yaVFa9AvQsk1ATZ+bwtHProNx2Xet9pnAI30dT16+C5wCctoR6RVf iOHl6CGqadjOycbMDVvOfJhypNDgWW3gBaCfXiAocJTLpR7hKNZ2bnvcP2xyXH1j Qz/kiMJoZ3+TV1yC/x/maAHsUIQHqqd6ZRj7x5MgJq0UBdITo2ZQVfXYI0ZGIeNm fMu+P5448+NdpASa9QoqS8kPFeUaHJMzMFHBKhrr8lTJeZ82hKBXt5jD3Tbef5Ck n5auKu2D0IjvrzsdIpNMQAhuBPT06TW/LzN/MvardZcaLcBmcutefw6Z7RsedHvj cGpnw4a2u9sHZIUNHzoGq32+7UWXsBI5Ow== -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/reseed/backup_at_mail.i2p.crt000066400000000000000000000036541321131324000241470ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFfTCCA2WgAwIBAgIEOprmhjANBgkqhkiG9w0BAQ0FADBvMQswCQYDVQQGEwJY WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEYMBYGA1UEAwwPYmFja3VwQG1haWwu aTJwMB4XDTEzMTAxMzEzNDQ1NVoXDTIzMTAxMzEzNDQ1NVowbzELMAkGA1UEBhMC WFgxCzAJBgNVBAgTAlhYMQswCQYDVQQHEwJYWDEeMBwGA1UEChMVSTJQIEFub255 bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGDAWBgNVBAMMD2JhY2t1cEBtYWls LmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIoAkobXwk/Enf1d roHyqCyvcJfZJVTwb/LgYWAvCBMCr+RGqlSgtk3g69Y3I0xU08fD2kGt3r5Pwsbr omXIbJAcccyLqmQ5QX6QgL+X9VpMDp9C4h2RogCrqLBAWw4cuZ4RS9VCpP1Yis7H uejYqENP86p7BsRnuW/4cYnfunAdMpss4LpRGQXt1nTX+kfgCYgnKFbFqwAHt7yV Ds+Pe6FuBHPlp+sc1amKRcUnSvhXLsv43VicnT7xYL/kUsN83wrtHA3B4aGDx3aA 3/EzuRmIXQB0BlTZILMEyYwG/nc4OsW82QYrvEZ9BIg9A4lF/wS/KZCICPxLF2zo dGjnmlgkiA4s8eO+va/ElHyELjckVXqmG1eXHhSkEsDvOQJy01IUuwLinvq7cUbJ HfJBZJllEg+sLDCv3FkEqN+XjBNFfQN4oNew4w6IPY6YH1INVB9LL0Cmdu4DudLv TY8OcI8eSfez3hmm+pYQ23PJRYYnvRDnRECyIWBegkckWRh8U/WvZUYUvETK6EDl /0KpTtfzX6MqHA5D6bTAB8Y3ijGMLrZ/B5vj5yCoZbLiGme9X2moR2k1LEhdhtzV exsqezCpg6dn48FTX7mHjvR5/r4kz2jqBGmdPUWIIxnjFUzDUK3llVQiHihleHpe jL4LqnhBGKWFRTaVwaIkBG4zAfIzAgMBAAGjITAfMB0GA1UdDgQWBBQNkfW7bSMl 1/4KDbgwrkf9x1Zu/TANBgkqhkiG9w0BAQ0FAAOCAgEAGg3a3rTf0EznQocmio0T 5gCoL0n8h6yKW/PyPAIELrd9wiYjhJFcWvMTcJJJnVqmAL5vpvhaAFVtAfx70MGa 0DZ7FvytK5hEfF4IqOFDyEEVGJR5rIpVK4MeI1nmwEsxdbW+FhODjtRzgYO8XBME Xj4aY1FWg9vxc3reUj6PSFsZtsB0aLiRgL9JDovJIiRw0Uqr1v2wXBte5yVCxDge vTREZtpK4cKetoOa68pwSXI32JwKE18j6bfdKVBCcYQKlKP/3gHGduaDrQv3w32S DRym5s6MREeTUOtAw4wq46KpdOX8yyAqJPrCfMwS6ORd3t+egqOw0PUnsqb97w4O lUtrRYvb2cOj60SmRx4vJvItyuHbKqIK7o2e1RcUZPXYoAVx2ww4XB2Wk4D7LSAs cS7nLj8yAqzJ2qqtBzxu+zILJtkVa12dKF0xmS0BxBp4sCYiBtmAVE8AWQqEuSHA FrMWqoXcjcfdvvyX487FFWWUE7ZBIn0hee2sK9J9+SPtqczJaN7TF3K3nzo65WJG 1epltmq2Ugjb67Gz7v4y7H23DJ/qhm8yLtCHTj69HTta5I08j6Kut924WLZaiMO/ 4YoEL5AE63X0sxYibKFQiq7FW5nUJA280GRlY3xSMFzlB2ggazrUV3YAWVDhfdnI flpzWXkFM2D36OUaubfe9YY= -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/reseed/bugme_at_mail.i2p.crt000066400000000000000000000036501321131324000237750ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFezCCA2OgAwIBAgIEUQYyQjANBgkqhkiG9w0BAQ0FADBuMQswCQYDVQQGEwJY WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOYnVnbWVAbWFpbC5p MnAwHhcNMTQxMTA2MDkxMTE0WhcNMjQxMTA1MDkxMTE0WjBuMQswCQYDVQQGEwJY WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOYnVnbWVAbWFpbC5p MnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCrThOH0eSDT0VnCSBC sqYmAydWH+O8eNttDXr2mSvZLhvAW+6/xHTkKhaWvkIvvS0Vh8hujMnD90Cgp4Fk TKCxMj9K527o5xIZwWW05OevbjlBwIpVLO1PjmsfsoD1nIX14eEzJSEoAulKsv7V jGUC/6hC11mmVvH9buQLSRv6sCjuAcMszmw3TAD+XYBIs+z57KuwYXtX3+OA543c l1/ZKLYkkwY8cwzZqWDVWqTKP5TfVae58t40HhJk3bOsr21FZsaOjlmao3GO+d/3 exKuUGJRcolSqskL3sZ1ovFqko81obvvx0upI0YA0iMr/NRGl3VPuf/LJvRppYGc LsJHgy9TIgtHvaXRi5Nt4CbKl9sZh/7WkkTTI5YGvevu00btlabAN+DSAZZqdsB3 wY8HhM1MHiA9SWsqwU65TwErcRrjNna2FiDHEu0xk5+/iAGl6CSKHZBmNcYKXSv8 cwShB0jjmciK0a05nC638RPgj0fng7KRrSglyzfjXRrljmZ40LSBL/GGMZMWpOM7 mEsBH5UZJ/2BEmjc9X9257zBdx8BK8y1TXpAligpNBsERcTw1WP1PJ35einZvlXW qI3GwMf0sl26sn+evcK0gDl27jVDZ45MtNQEq64M4NV3Tn9zq0eg/39YvjVeqrI5 l7sxmYqYGR6BuSncwdc4x+t6swIDAQABoyEwHzAdBgNVHQ4EFgQU/REZ7NMbVZHr Xkao6Q8Ccqv2kAMwDQYJKoZIhvcNAQENBQADggIBACc2YjLVNbl1kJUdg2klCLJt 5LjNTiIZa2Cha5GStlC/lyoRRge6+q/y9TN3tTptlzLPS9pI9EE1GfIQaE+HAk+e /bC3KUOAHgVuETvsNAbfpaVsPCdWpFuXmp/4b9iDN7qZy4afTKUPA/Ir/cLfNp14 JULfP4z2yFOsCQZ5viNFAs1u99FrwobV2LBzUSIJQewsksuOwj96zIyau0Y629oJ k+og88Tifd9EH3MVZNGhdpojQDDdwHQSITnCDgfRP5yER1WIA4jg6l+mM90QkvLY 5NjWTna5kJ3X6UizvgCk365yzT2sbN3R9UGXfCJa9GBcnnviJtJF3+/gC0abwY2f NtVYp32Xky45NY/NdRhDg0bjHP3psxmX+Sc0M9NuQcDQ+fUR+CzM0IGeiszkzXOs RG+bOou2cZ81G4oxWdAALHIRrn7VvLGlkFMxiIZyhYcTGQZzsTPT6n18dY99+DAV yQWZfIRdm8DOnt0G+cwfeohc/9ZwDmj4jJAAi0aeTXdY6NEGIVydk6MAycEhg2Hx 9EV96kRwZNIW0AGY8CozECFL3Eyo2ClQVV4Q35SsBibsitDjM03usc2DJ/qjynXA C8HoOSWgbddiBvqZueqK8GdhykOy3J3ysr+MNN/lbG48LqkQr1OWxev9rGGQ6RJT wpBgPyAFAwouPy1whmnx -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/reseed/creativecowpat_at_mail.i2p.crt000066400000000000000000000041431321131324000257140ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIGAzCCA+ugAwIBAgIRAJNGLpTSm2U3GjXmFkjT/0cwDQYJKoZIhvcNAQELBQAw dzELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwGA1UE ChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxIDAeBgNVBAMM F2NyZWF0aXZlY293cGF0QG1haWwuaTJwMB4XDTE3MDUyNjE5NDQzOVoXDTI3MDUy NjE5NDQzOVowdzELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJY WDEeMBwGA1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAx IDAeBgNVBAMMF2NyZWF0aXZlY293cGF0QG1haWwuaTJwMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAo3XP4JToVbfM5e4GxyAqzu2DJV7ohpzlLqMLyz/9 XgZ7ipctNoxVZytoaNgMeAHInJn5OhUC4D+emsgsLJqFjnb2pxf6v45sRZLBMieb wJlxUmskucpTXwDwuHBk/s3xmH4IluadmzwiCMyycQFH/CNXmu5bonAuZ075rT1Q a8W0vb8eSfNYXn+FKQBROqsL5Ep+iJM6FX+oWMxJPk/zNluIu9qTdZL7Fts2+ObP X5WLE4Dtot57vMI2Tg3fjnpgvk3ynQjacS8+CBEbvA/j32PBS1mQB+ebl56CQTBv glHrXiNdp24TAwy8mwWHknhpt4cvRXOJGZphSVNRYFVk0vv7CTjmQg6WOBGD+d/P cosvyKxQz4WUSmtaKUftgCBdnemeM7BppZv2URflEOY6Uv3f9xlEC6yVEzSaa2Md tG6XRkDyupWCBFwmSm1uS+SXXhxAQGn3eMXPFA1XkwNnZtmM9kvSVt34FBE231oN 4oM7rE3ZDyTocZw7cv7bl8idmqsLXDTSFn5Q2iLwvw6ZeTenk8qHrq9kVH1UVE2l 31iKDNdGQkkVcnTWYfiqriwGLpTqbeD/8n9OBgCke1TiKQzP1o66nhkGJTiiRLFK A8rlSpqBcjGbXDs/X+Ote9MrCxE089eCqN51kzDeQ4Yvy8gDOTBPGEhBLirx+3pp yWkCAwEAAaOBiTCBhjAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYBBQUH AwIGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wIAYDVR0OBBkEF2NyZWF0aXZl Y293cGF0QG1haWwuaTJwMCIGA1UdIwQbMBmAF2NyZWF0aXZlY293cGF0QG1haWwu aTJwMA0GCSqGSIb3DQEBCwUAA4ICAQCYzeYyPYhW+/SZSfpDl6JTzXy8S6NG+yjq pcinxaIF4XFoXLwWD3uHR4jgpU750mhHJjpGIaltZjFaqLbqtysbqb0vdShyaK/n Td4CXrNBvEHvLI6DZyDX4BcDlhCI7/dMCSHXwFIhRHhYSnTsJO32BdP5DsUUAlSW G0FlEEWjlxcdRwIITv70cFNlNOqJeyvtk9DPT+nEzssKWxVZcqN4GK8dvQVWgL91 8uzrcAYpAEQfmkKzsGmV4v5gWumLZmnzc24hUhVsHhIph4HAmjPMFCppI1tgiwg7 fH71MYB8b9KBJKipkLdAL292mDLS4G3MGQwMbcjnTyIqOktmyyj/1CorZAKqBtzu Qyo7z8FM2pd5nzk7QDx/vsJ4bNAYvVu7titDW5mv5JDoQcp2uDVGePlonX3I8iFx CqKFzGHiR0EU8oWw0Pqf+y2rEV4L74agmUR7VbA+/ovz0UnDUoXIynSwpK7Kfo8D B7ky9RnmsxJX6TXaMVW06IlYuwIUsAWbMhKvdXbGZur5VVi1ZY1/HgZZnoXejzCe w3mMl6movkcA0noDXQ+eauUDHjktrVUJdZKYvZNjfnz2rB+MI5wB/hzeBv4KuYFE oTFt8SwTzs0joM4c7RomTxc+QFe832SvjPAnxQn17qSjD8z4c7Ako6sCKvpdBSDm Hz8KWVkHZg== -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/reseed/echelon_at_mail.i2p.crt000066400000000000000000000036601321131324000243140ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFfzCCA2egAwIBAgIESg3kkzANBgkqhkiG9w0BAQ0FADBwMQswCQYDVQQGEwJY WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UEAwwQZWNoZWxvbkBtYWls LmkycDAeFw0xNDA3MzExNjQ3MDJaFw0yNDA3MzAxNjQ3MDJaMHAxCzAJBgNVBAYT AlhYMQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBBbm9u eW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRkwFwYDVQQDDBBlY2hlbG9uQG1h aWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmcEgLwwhzLNe XLOMSrhwB8hWpOhfjo4s6S/wjBtjjUc8nI3D0hSn3HY26p0rvcvNEWexPUpPULmC exGkU463nu7PiFONiORI1eJAiUFHibRiaA7Wboyo38pO73KirwjG07Y+Ua0jp+HS +4FQ/I/9H/bPplReTOU/6hmRbgQ69U8nE68HzZHQxP68yVJ2rPHSXMPhF4R1h0G1 1mCAT+TgTsnwHNGF77XHJnY4/M4e2cgycEZjZow36C3t2mNDVkMgF19QQeb9WmLR zREn3nq9BJqHpUkn9yWw0kKXTZSds+7UxESfzf3BzK0+hky2fh5H+qbYAo2lz4yj 81MXTAu+4RRkg4DBLlF+2dkclhwQLxxzvkRC6tPkn5i33Yltg7EfzA9IoQ05potJ I+iOcF+aStfFgFj9u3B5UkcF4P0cH1QD3c6BK4hIezQYqRoPly1gHqg+XdwjG/dr 4as7HA9FTz3p2E8nClpIC1x3hfgwAdfd29aeBxO1WW/z99iMF7TBAF+u5T86XEW1 WpknqCbTli36yJ8a5fPWxZHrryBRJT5yLxejjFeadtutBSwljiVFq+Y38VqwFivq VLiBt7IxAsZ8iilgfnnnAvBH6chWfSKb4H7kB4TJvDiV96QmmvoEaWYNHZozMhyK tO3b5w+xqbJXyCLA3Q75jD0km76hjcECAwEAAaMhMB8wHQYDVR0OBBYEFAHQcAam QRS/EUhuCSr9pB4Ux0rYMA0GCSqGSIb3DQEBDQUAA4ICAQBq1+1QLmgLAjrTg3tb 4XKgAVICQRoBDNUEobQg3pYeUX9eFNya2RxNljuvYpwT80ilGMPOXcjddmr5ngiK dbGRcuuJk9MPEHtPaPT3+JJlvKQ3B3g2wva2Wz2OAyLZUGQs389K4nTbwh4QF0n2 aHFL8BHiD62hiKnCoNaW4ZovUNNvOxo9lMyAiaFU2gqQNcdad8hP9EAllbvbxDx9 Tjww2UbwQUIHS9rna4Tlu+f0hDXTWIutc2A51W2fJCb7L3+lYO7Wv55ND/WtryLZ XpMp27+MpuEnN3kQmz/l9R0hIJsWc/x9GQkjm5wEaIZEyTtenqwRKGmVCtAj0Pgv jn1L3/lWmrNq+OZHb/QeyfKtA3nXfQKVmT98ewQiK/S5i1xIAXCJPytOD887b/o1 cdurTmCiZMwgiQ+HLJqCg3MDa5mvKqRkRdZXfE6aQWEcSbpAhpV15R17q7L+Fg0W shLSNucxyGNU8PjiC/nOmqfqUiPiMltJjPmscxBLim8foyxjakC4+6N6m+Jzgznj PocBehFAfKYj66XEwzIBN7Z2uuXoYH9YptkocFjTzvchcryVulDWZ4FWxreUMhpM 4oyjjhSB4tB9clXlwMqg577q3D6Ms0zLTqsztyPN3zr6jGev3jpVq7Q1GOlciHPv JNJOWTH/Vas1W6XlwGcOOAARTQ== -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/reseed/hottuna_at_mail.i2p.crt000066400000000000000000000040211321131324000243510ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFxzCCA6+gAwIBAgIQZfqn0yiJL3dGgCjeOeWS6DANBgkqhkiG9w0BAQsFADBw MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEZMBcGA1UEAwwQ aG90dHVuYUBtYWlsLmkycDAeFw0xNjExMDkwMzE1MzJaFw0yNjExMDkwMzE1MzJa MHAxCzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxHjAcBgNV BAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRkwFwYDVQQD DBBob3R0dW5hQG1haWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC AgEA21Bfgcc9VVH4l2u1YvYlTw2OPUyQb16X2IOW0PzdsUO5W78Loueu974BkiKi 84lQZanLr0OwEopdfutGc6gegSLmwaWx5YCG5uwpLOPkDiObfX+nptH6As/B1cn+ mzejYdVKRnWd7EtHW0iseSsILBK1YbGw4AGpXJ8k18DJSzUt2+spOkpBW6XqectN 8y2JDSTns8yiNxietVeRN/clolDXT9ZwWHkd+QMHTKhgl3Uz1knOffU0L9l4ij4E oFgPfQo8NL63kLM24hF1hM/At7XvE4iOlObFwPXE+H5EGZpT5+A7Oezepvd/VMzM tCJ49hM0OlR393tKFONye5GCYeSDJGdPEB6+rBptpRrlch63tG9ktpCRrg2wQWgC e3aOE1xVRrmwiTZ+jpfsOCbZrrSA/C4Bmp6AfGchyHuDGGkRU/FJwa1YLJe0dkWG ITLWeh4zeVuAS5mctdv9NQ5wflSGz9S8HjsPBS5+CDOFHh4cexXRG3ITfk6aLhuY KTMlkIO4SHKmnwAvy1sFlsqj6PbfVjpHPLg625fdNxBpe57TLxtIdBB3C7ccQSRW +UG6Cmbcmh80PbsSR132NLMlzLhbaOjxeCWWJRo6cLuHBptAFMNwqsXt8xVf9M0N NdJoKUmblyvjnq0N8aMEqtQ1uGMTaCB39cutHQq+reD/uzsCAwEAAaNdMFswDgYD VR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNV HRMBAf8EBTADAQH/MBkGA1UdDgQSBBBob3R0dW5hQG1haWwuaTJwMA0GCSqGSIb3 DQEBCwUAA4ICAQCibFV8t4pajP176u3jx31x1kgqX6Nd+0YFARPZQjq99kUyoZer GyHGsMWgM281RxiZkveHxR7Hm7pEd1nkhG3rm+d7GdJ2p2hujr9xUvl0zEqAAqtm lkYI6uJ13WBjFc9/QuRIdeIeSUN+eazSXNg2nJhoV4pF9n2Q2xDc9dH4GWO93cMX JPKVGujT3s0b7LWsEguZBPdaPW7wwZd902Cg/M5fE1hZQ8/SIAGUtylb/ZilVeTS spxWP1gX3NT1SSvv0s6oL7eADCgtggWaMxEjZhi6WMnPUeeFY8X+6trkTlnF9+r/ HiVvvzQKrPPtB3j1xfQCAF6gUKN4iY+2AOExv4rl/l+JJbPhpd/FuvD8AVkLMZ8X uPe0Ew2xv30cc8JjGDzQvoSpBmVTra4f+xqH+w8UEmxnx97Ye2aUCtnPykACnFte oT97K5052B1zq+4fu4xaHZnEzPYVK5POzOufNLPgciJsWrR5GDWtHd+ht/ZD37+b +j1BXpeBWUBQgluFv+lNMVNPJxc2OMELR1EtEwXD7mTuuUEtF5Pi63IerQ5LzD3G KBvXhMB0XhpE6WG6pBwAvkGf5zVv/CxClJH4BQbdZwj9HYddfEQlPl0z/XFR2M0+ 9/8nBfGSPYIt6KeHBCeyQWTdE9gqSzMwTMFsennXmaT8gyc7eKqKF6adqw== -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/reseed/igor_at_novg.net.crt000066400000000000000000000040051321131324000237540ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFvjCCA6agAwIBAgIQIDtv8tGMh0FyB2w5XjfZxTANBgkqhkiG9w0BAQsFADBt MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEWMBQGA1UEAwwN aWdvckBub3ZnLm5ldDAeFw0xNzA3MjQxODI4NThaFw0yNzA3MjQxODI4NThaMG0x CzAJBgNVBAYTAlhYMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxHjAcBgNVBAoT FUkyUCBBbm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRYwFAYDVQQDDA1p Z29yQG5vdmcubmV0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxst4 cam3YibBtQHGPCPX13uRQti56U3XZytSZntaKrUFmJxjt41Q/mOy3KYo+lBvhfDF x3tWKjgP9LJOJ28zvddFhZVNxqZRjcnAoPuSOVCw88g01D9OAasKF11hCfdxZP6h vGm8WCnjD8KPcYFxJC4HJUiFeProAwuTzEAESTRk4CAQe3Ie91JspuqoLUc5Qxlm w5QpjnjfZY4kaVHmZDKGIZDgNIt5v85bu4pWwZ6O+o90xQqjxvjyz/xccIec3sHw MHJ8h8ZKMokCKEJTaRWBvdeNXki7nf3gUy/3GjYQlzo0Nxk/Hw4svPcA+eL0AYiy Jn83bIB5VToW2zYUdV4u3qHeAhEg8Y7HI0kKcSUGm9AQXzbzP8YCHxi0sbb0GAJy f1Xf3XzoPfT64giD8ReUHhwKpyMB6uvG/NfWSZAzeAO/NT7DAwXpKIVQdkVdqy8b mvHvjf9/kWKOirA2Nygf3r79Vbg2mqbYC/b63XI9hheU689+O7qyhTEhNz+11X0d Zax7UPrLrwOeB9TNfEnztsmrHNdv2n+KcOO2o11Wvz2nHP9g+dgwoZSD1ZEpFzWP 0sD5knKLwAL/64qLlAQ1feqW7hMr80IADcKjLSODkIDIIGm0ksXqEzTjz1JzbRDq jUjq7EAlkw3G69rv1gHxIntllJRQidAqecyWHOMCAwEAAaNaMFgwDgYDVR0PAQH/ BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8E BTADAQH/MBYGA1UdDgQPBA1pZ29yQG5vdmcubmV0MA0GCSqGSIb3DQEBCwUAA4IC AQADyPaec28qc1HQtAV5dscJr47k92RTfvan+GEgIwyQDHZQm38eyTb05xipQCdk 5ruUDFXLB5qXXFJKUbQM6IpaktmWDJqk4Zn+1nGbtFEbKgrF55pd63+NQer5QW9o 3+dGj0eZJa3HX5EBkd2r7j2LFuB6uxv3r/xiTeHaaflCnsmyDLfb7axvYhyEzHQS AUi1bR+ln+dXewdtuojqc1+YmVGDgzWZK2T0oOz2E21CpZUDiP3wv9QfMaotLEal zECnbhS++q889inN3GB4kIoN6WpPpeYtTV+/r7FLv9+KUOV1s2z6mxIqC5wBFhZs 0Sr1kVo8hB/EW/YYhDp99LoAOjIO6nn1h+qttfzBYr6C16j+8lGK2A12REJ4LiUQ cQI/0zTjt2C8Ns6ueNzMLQN1Mvmlg1Z8wIB7Az7jsIbY2zFJ0M5qR5VJveTj33K4 4WSbC/zMWOBYHTVBvGmc6JGhu5ZUTZ+mWP7QfimGu+tdhvtrybFjE9ROIE/4yFr6 GkxEyt0UY87TeKXJ/3KygvkMwdvqGWiZhItb807iy99+cySujtbGfF2ZXYGjBXVW dJOVRbyGQkHh6lrWHQM4ntBv4x+5QA+OAan5PBF3tcDx1vefPx+asYslbOXpzII5 qhvoQxuRs6j5jsVFG6RdsKNeQAt87Mb2u2zK2ZakMdyD1w== -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/reseed/lazygravy_at_mail.i2p.crt000066400000000000000000000040321321131324000247210ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFzTCCA7WgAwIBAgIQCnVoosrOolXsY+bR5kByeTANBgkqhkiG9w0BAQsFADBy MQswCQYDVQQGEwJYWDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMR4wHAYDVQQK ExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEbMBkGA1UEAwwS bGF6eWdyYXZ5QG1haWwuaTJwMB4XDTE2MTIyNzE1NDEzNloXDTI2MTIyNzE1NDEz NlowcjELMAkGA1UEBhMCWFgxCzAJBgNVBAcTAlhYMQswCQYDVQQJEwJYWDEeMBwG A1UEChMVSTJQIEFub255bW91cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxGzAZBgNV BAMMEmxhenlncmF2eUBtYWlsLmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC AgoCggIBAN3q+0nUzz9+CBSoXUNf8K6kIc9zF+OP1NVBmOu3zTtkcEnhTtoDNXeU EV8DhlBhEACbPomA+szQ5zp3O3OYQc2NV50S7KKqlfn5LBBE3BL2grTeBxUMysDd 0TlpxcHKwaog4TZtkHxeNO94F1vgeOkOnlpCQ6H3cMkPEGG3zu1A1ccgPiYO838/ HNMkSF//VZJLOfPe1vmn9xTB7wZ0DLpEh12QZGg3irA+QDX5zy6Ffl+/Lp+L4tXT uPZUaC6CL6EABX4DvQcFrOtiWfkbi/ROgYCeTrYw1XbDHfPc+MBxGo1bX7JjnD0o mFFvo+PjxvWDmCad2TaITh6DwGEeWKu8NtJAyaO5p1ntauuWGB5Xzua4aMmIy7GT esHQkhW+5IooM0R5bZI8/KXo4Bj52bX5qv+oBiExc6PUUTLWyjoWHb7fKdddwGfc lUfniV/fw7/9ysIkQZcXLDCXR6O/nH9aGDZ7bxHedw4/LxAXYPfNojb5j7ZVa65o PWD5xuQfbE+95DdbnKjcjYiam4kjApe7YPwOhtoRJYSGAkrpIMfzFxCXgjTsi3Kw Ov+sYmBvWBK4ROWQZTgHei3x4FpAGWHCAeTeeQGKmWQ8tT7ZklWD9fBm3J/KXo7I WCxRW9oedItyqbRuAGxqaoaGSk6TtPVjyPIUExDp1dr4p1nM1TOLAgMBAAGjXzBd MA4GA1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEw DwYDVR0TAQH/BAUwAwEB/zAbBgNVHQ4EFAQSbGF6eWdyYXZ5QG1haWwuaTJwMA0G CSqGSIb3DQEBCwUAA4ICAQA2fei/JajeQ7Rn0Hu3IhgF9FDXyxDfcS9Kp+gHE56A 50VOtOcvAQabi/+lt5DqkiBwanj0Ti/ydFRyEmPo45+fUfFuCgXcofro8PGGqFEz rZGtknH/0hiGfhLR9yQXY8xFS4yvLZvuIcTHa9QPJg3tB9KeYQzF91NQVb5XAyE7 O3RvollADTV31Xbhxjb7lgra6ff9dZQJE6xtlSk/mnhILjlW80+iPKuj3exBgbJv ktiR4ZT4xjh1ZgNJX5br86MZrhyyyGWwHWHS0e443eSrrmAPD69zxsfvhoikRX1z tDz0zB70DwS4pSbVrFuWaIAcbg36vWO8tYPBzV8iBB/tBTURGJjv6Q0EoI5GHmJi LOhU3B6xublv8Tcoc3tgMqI9STnWROtTiCS6LsWNSXhVpIZqvaiOEtPN4HyL33sf j5rfPq76gKrTloeLnwLGq0Rs94ScffYkBap3fQ/ALb87LQcwSN4EkObur5pcd7TS qNdanvCGK8v1UYVzH4l9jekPGsM5euohwAkIl1kZ6+tqGY/MTa7HwTTQyLDTco1t sPy6neN46+H5DYHADyU5H2G39Kk3WcLmPtfxlPDM6e73+47fJkXnmiaWM0Lrt80y Enng6bFGMZH01ZsqBk09H+Uswv8h7k69q9uWAS95KE0omCMVtIpoPZXTnRhe6mBC +g== -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/reseed/meeh_at_mail.i2p.crt000066400000000000000000000036501321131324000236140ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFeTCCA2GgAwIBAgIEZZozujANBgkqhkiG9w0BAQ0FADBtMQswCQYDVQQGEwJY WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEWMBQGA1UEAwwNbWVlaEBtYWlsLmky cDAeFw0xNDA2MjgyMjQ5MDlaFw0yNDA2MjcyMjQ5MDlaMG0xCzAJBgNVBAYTAlhY MQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBBbm9ueW1v dXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRYwFAYDVQQDDA1tZWVoQG1haWwuaTJw MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnVnmPE4uUvCky0yCnnVH cJEDqzwDPupx0zr0YDlhZk5VOPPecx5haayJ/V6nXPc1aVVWn+CHfedcF2aBgN4K 5aBueS/l6l5WHcv02DofAqlTmyAws3oQeR1qoTuW24cKRtLR7h5bxv63f6bgp6e+ RihFNez6UxErnRPuJOJEO2Im6EgVp6fz7tQ7R35zxAUeES2YILPySvzy2vYm/EEG jXX7Ap2A5svVo90xCMOeUZ/55vLsjyIshN+tV87U4xwvAkUmwsmWVHm3BQpHkI6z zMJie6epB8Bqm0GYm0EcElJH4OCxGTvDLoghpswbuUO7iy3JSfoL7ZCnoiQdK9K4 yVVChj8lG+r7KaTowK96iZep+sZefjOt5VFGuW2Fi/WBv3ldiLlJAo/ZfrUM4+vG fyNBXbl6bX87uTCGOT1p3dazo+zJMsAZ+Y93DlM/mDEWFa1kKNrs74syzaWEqF4L KQE6VoYn80OOzafSigTVQgSwUtQtB0XGhMzJhyxU2XHWe1LFIy7Pta0B+lDiZj7c I8nXxYjsDfEu/Elj/Ra9N6bH0awmgB5JDa+Tbir+oEM5SyDfpSaCGuatdGxjweGI kVmFU0SqCZV/8TXbIu6MUVzTZMZVT94edifFSRad4fqw7eZbSXlPu++3d1/btn6h ibM04nkv0mm+FxCKB/wdAkECAwEAAaMhMB8wHQYDVR0OBBYEFO7jIkSRkoXyJcho 9/Q0gDOINa5EMA0GCSqGSIb3DQEBDQUAA4ICAQBzfWO7+8HWOKLaYWToJ6XZbpNF 3wXv1yC4W/HRR80m4JSsq9r0d7838Nvd7vLVP6MY6MaVb/JnV76FdQ5WQ6ticD0Y o3zmpqqbKVSspN0lrkig4surT88AjfVQz/vEIzKNQEbpzc3hC2LCiE2u+cK/ix4j b9RohnaPvwLnew5RNQRpcmk+XejaNITISr2yQIwXL7TEYy8HdGCfzFSSFhKe9vkb GsWS5ASrUzRoprswmlgRe8gEHI+d51Z7mWgna0/5mBz9bH/3QXtpxlLWm3bVV+kt pZjQDTHE0GqG2YsD1Gmp4LU/JFhCojMTtiPCXmr9KFtpiVlx06DuKm5PC8Ak+5w+ m/DQYYfv9z+AA5Y430bjnzwg67bhqVyyek4wcDQinFswv3h4bIB7CJujDcEqXXza lhG1ufPPCUTMrVjh7AShohZraqlSlyQPY9vEppLwD4W1d+MqDHM7ljOH7gQYaUPi wE30AdXEOxLZcT3aRKxkKf2esNofSuUC/+NXQvPjpuI4UJKO3eegi+M9dbnKoNWs MPPLPpycecWPheFYM5K6Ao63cjlUY2wYwCfDTFgjA5q8i/Rp7i6Z6fLE3YWJ4VdR WOFB7hlluQ//jMW6M1qz6IYXmlUjcXl81VEvlOH/QBNrPvX3I3SYXYgVRnVGUudB o3eNsanvTU+TIFBh2Q== -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/reseed/r4sas-reseed_at_mail.i2p.crt000066400000000000000000000036741321131324000252050ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFiTCCA3GgAwIBAgIEY2XeQjANBgkqhkiG9w0BAQ0FADB1MQswCQYDVQQGEwJY WDELMAkGA1UECAwCWFgxHjAcBgNVBAcMFUkyUCBBbm9ueW1vdXMgTmV0d29yazEL MAkGA1UECgwCWFgxDDAKBgNVBAsMA0kyUDEeMBwGA1UEAwwVcjRzYXMtcmVzZWVk QG1haWwuaTJwMB4XDTE3MDYyMjEwNTQ1NFoXDTI3MDYyMDEwNTQ1NFowdTELMAkG A1UEBhMCWFgxCzAJBgNVBAgMAlhYMR4wHAYDVQQHDBVJMlAgQW5vbnltb3VzIE5l dHdvcmsxCzAJBgNVBAoMAlhYMQwwCgYDVQQLDANJMlAxHjAcBgNVBAMMFXI0c2Fz LXJlc2VlZEBtYWlsLmkycDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB ANgsj5LhF4uGG4RDueShqYQZsG5Rz6XUAtK9sVGFdmdJTDZirUMZcCGCGZP/Harz QaZU9EYxOCztnpLCQksSCpdRsij56MURS0tW/1x7LHIDUOi911Of57jgIHH+3E5n 6tuRxEk6J/9Ji3PI+89kl0sPKMVFMyKkINprVTA5zr/keyYEG0p6HSEYYiJkQH78 8uoOCAmlk9mxkJFb+zviCk6jsYwdH+ofD6Lw5ueOlYUbeZ9Nd7jfSdf20XM7ofIw W2COtsbq3J7vNrQJMV7HkHxVx/7OqmjQF02OahZFZREVZqbHpL501iTn9Iqd5qKq IsxYjk7ZnP4UUCBk8NOU5TuWsy0qNw+TJDI9s55Fi4KPtXWf47HIl6CdpM5y/D5L eufCojSwPKlrD6x9gTyJdBggBZRIyplXdKffo/95hUhEkv86yfsVVR7Gu1uy0O8T Gtb8Da/oi5eEZBHWonLVicLPei5jeo+1gbR09PQ6s41uMZlOhMe4RSgiIQj/7UVo ffKdl1MPNKr1u2fgVj8kxqg8ZivWKQ2taEgimU2EkQcNcE96M9yQlNNpNvqSAQVk wYXlHt0AN6A1A8u1pItxaTwXnbmx+OBJZoKl4ZQeaC8wtKjTgAgVXp+g5iot2gir LjxCRx1WLG1c8vRg1W8CDZII8Swc8EWpMhI+0hPv7/4/AgMBAAGjITAfMB0GA1Ud DgQWBBTN5sKbrNzwE8sgMGDekfOPgX8/JDANBgkqhkiG9w0BAQ0FAAOCAgEAjLaB bHqvFTs0ikAtesk9r8+8XVIsP5FR57zZCek2vxkHcCQWw8Uqs3ndInRX4FirKSLT WRb4aSwFCkrmwueecTpXN/RBC+fZj+POCfdILEsA+FGreAM2q5ZXv/Q0jyIXOXEM +KL0JZXnNS0/dqR3IYbC7f39CL6Sf40gRGTwTWWGg3KnynoS0v1zQcZLTMhHBD2X tgdIPbroq9t4gXa7Dhm0egYfQOI/7re2wiZT7UWVVwEpYqKf6JApFHa1nNOFMrLF 45JHQIHArkoxpQdfSe9HBoyJiB5vz398rHZeqbJaF3PIg9rxWWY/NvvOVuIk8U5z 0jExhg29a88B32U7ndvQJqIuGiQghzCiLxC/y1+wAdpeDSbD3OAOHqplvMj3BUn9 yhDSLSjtfBJjnXKxtEcWLR0edHCGEk5mAcL7q1WNxDpxaICwGGpNZN53CtFx7amb egYil448DmiqoQTCTE9pBz8YjwiVfCYLYv17O0NJyYM9Efy/wL3rFlsPJniWHMuH imZybVU4ukjvfOZ+LY4COTwz6w4sfA7a+i+2mOynC7eKX8Yg6i1nXlcY1Z8ykNgi 7B3kz1T/DV56CIm6QUWtepfuKTYq4C6QrBBIXLk1d5g95aWA21u1LRqNZ9GLH+eA gfvIm7v+cELj8a53EQY0LafzZqNC5kQAp916coU= -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/reseed/zmx_at_mail.i2p.crt000066400000000000000000000040421321131324000235100ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIF1TCCA72gAwIBAgIRAJBHySZnvNg3lU00//fwny4wDQYJKoZIhvcNAQELBQAw bDELMAkGA1UEBhMCWFgxHjAcBgNVBAoTFUkyUCBBbm9ueW1vdXMgTmV0d29yazEM MAoGA1UECxMDSTJQMQswCQYDVQQHEwJYWDELMAkGA1UECRMCWFgxFTATBgNVBAMM DHpteEBtYWlsLmkycDAeFw0xNjAxMDExNzE5MTlaFw0yNjAxMDExNzE5MTlaMGwx CzAJBgNVBAYTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnltb3VzIE5ldHdvcmsxDDAK BgNVBAsTA0kyUDELMAkGA1UEBxMCWFgxCzAJBgNVBAkTAlhYMRUwEwYDVQQDDAx6 bXhAbWFpbC5pMnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnDGVU iC6pNJ3mfqZRQYACUbQ6SQI05yh3PawHqQrmiW3rD05SXBCF+6b2EpA4U0ThFhtm cGyUObtBL749x03SUYcWhknZNq+zrvb9AypaKFpIx2DjFT8vQadn0l71cNaiwxX1 Wzk1Au6mh9SFPvH5gDF9SQol7dYYKnn9L61V7hvH9fDiZyoi9Cz3ifE3SAWoM2PJ lBzbu16tyQE94HvIdZhp8cE/6/kiW1wjSqvT9dfZ4gMuZHOF5E8lkq/bg8tPa/oj rglY7ozT/9/IWtJ7ERcDyepmKjq7+Xx4sNXTvc+B7D4XfMjhaxFLtV/kLQ9mqx8R UPvPy+atw7mlfUf822YFSft2jBAxNJwCPdhXuuFkTUTIk9YXcChUCSPyv17gej/P A++/hdhYI/kIs8AVsaJjytTqwU3A2Pt1QogM8VLsSJ2NY7gSzj868nzIZ4OuoWbz KzpnS/3bQkYHrqMtDIjRr1bOudxbu2/ben5v8Qg9wE9uV/8YNhhaKAcfJOV6OXfF MYec9DOEVVvECOfYUX35Vtn/w7E6SSL7Gu6QEWviA4Bf2XBh1YFX0ZpBUMY9awNz 7PDf+z+YGkrQ6ifvLPW9vHW3lmouRWzo5NgJIIvLYBJKmxkf08p94s8YailjiGzA dJWXg3HDWgwMe7BY7AJQbU/o35Vv+0CroUsR3wIDAQABo3IwcDAOBgNVHQ8BAf8E BAMCAoQwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA8GA1UdEwEB/wQF MAMBAf8wFQYDVR0OBA4EDHpteEBtYWlsLmkycDAXBgNVHSMEEDAOgAx6bXhAbWFp bC5pMnAwDQYJKoZIhvcNAQELBQADggIBAATXH/PNdF40DjD9DcF4W5Ot7CWGskDY cR4ywtvU2EcDNEwv4q0FPEpxy5LPaUmTKQ6fsRXUZizjaPLpgCLbv9qYc5xRLrSi yk9mrAbJ1iEU+DfHHBcS1VQWtc7+9LA0W3ZIA+pygjPjTxwQqQAcjn4BdfaIQpVa VJ2kl5JtbTuYHL80GAQFYnzCCa5GKM7zgcLsyO1mQwnpDvFeSlKJJ6rx1QjhlJu+ 90Ig8IOBCIgokfUv9OdYBl6rmDq9i9pvqJU+H4VepqE1jnDAO+YqQ4laZj7LVVM8 I9uia+8RKntUOBkUkLB3ouGdVJUmp3kGrkExxUdDHYP9VNJG6ZMwyKO8HXGtoTsR TFWIEIbq/biBL9obM/d8fRV5xpfZNbPi6cRzw8REY9UIKECKr7B2B6PnDVVQIQw0 7SCVjmSYWexOqoJPZ1L7/AZDP/tFvx32cWwCszj5jqUaPo9ZNPb6DxQJDdNaZrFH 3CA+PbiaeEz9IH0yBY/6wQgO0k3qOyFQrlkC+YRoYUQNc+6xS38l5ZnYUtBAy8ms N43eODQ/OhsLzy6PwwXdzvR/0g18SrQyTLfbn2b/kwvbC8Qe40QFfkOf5lPXjdnP Ii/lcMuvDMlMhoWGFwWm5bkkXE81TKnFXu2/IMsW6HYb3oiTjkaCap22fCr9l0jj fNr8P7NIRyZ8 -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/router/000077500000000000000000000000001321131324000200525ustar00rootroot00000000000000i2pd-2.17.0/contrib/certificates/router/killyourtv_at_mail.i2p.crt000066400000000000000000000036701321131324000251750ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFhTCCA22gAwIBAgIELuRWgDANBgkqhkiG9w0BAQ0FADBzMQswCQYDVQQGEwJY WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEcMBoGA1UEAwwTa2lsbHlvdXJ0dkBt YWlsLmkycDAeFw0xMzEwMDYyMTM5MzFaFw0yMzEwMDYyMTM5MzFaMHMxCzAJBgNV BAYTAlhYMQswCQYDVQQIEwJYWDELMAkGA1UEBxMCWFgxHjAcBgNVBAoTFUkyUCBB bm9ueW1vdXMgTmV0d29yazEMMAoGA1UECxMDSTJQMRwwGgYDVQQDDBNraWxseW91 cnR2QG1haWwuaTJwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAig3u niLWm0y/TFJtciHgmWUt20FOdQrxkiSZ87G8xjuGfq7TbGIiVDn7pQZcHidpq+Dk 47sm+Swqhb4psSijj0AXUEVKlV39jF5IZE+VUgmEtMqQbnBkWudaTJPWcEe9T/Kd 8Oz2jgsnrD/EGVTMKBBjt/gk8VqTWvpCdCF1GhqcCeUTFHzjhN9jtoRCaJ2DClpO Px+86+d3s9PqUFo8gcD/dbbyJCMqUCMBLtIy/Ooouxb9cfWtXfyOlphU+enmdvuA 0BDewb9pOJg2/kVd9/9moDWcBGChLOlfSlxpDwyUtcclcpvwnG7c6o4or6gqLeOf AbCpse623utV7fWlFWG7M4AQ/2emhhe4YoMJQnflydzV8bPRJxRTeW1j/9UfpvLT nO5LHp0oBXE0GqAPjxuAr+r5IDXFbkKYNjK5oWQB/Ul3LkexulYdCzHWbGd1Ja5b sbiOy6t/hH6G8DD75HYb+PQZaNZWBv90EyOq1JDSUPw6nxVbhiBldi3ipc8/1X51 FbzBqJ+QO1XKrKqxWxBKoTekuy38KRzsmkSCpY+WJ9f0gLOKtxzVO2HNNqqVFGQf RGIbrNA0JSRQ1fgelccfrcRIXIZ3B8Tk/wxCIzCY6Yvg2jezz2xJkVdqOUsznS2v +xJe67PYIAeMVtcfO4kmuCvyIYhsUEpob2n/5lkCAwEAAaMhMB8wHQYDVR0OBBYE FCLneov6QMtvra5FSoSLhdymi++rMA0GCSqGSIb3DQEBDQUAA4ICAQAIcqbiwjdQ M9VlGBiHe5eVsL6OM9zfRqR1wnRg4Q6ce65XDfEOYleBWaaNJA4BdykcA4fkUN1h M2D9FDQScsyPTOuzJ6o75TYh0JOtF51yCi9iuemcosxAwsm90ZXGuMDfDYeyND5c PAkWfyCP+jwLYbNo/hkNqyv+XWHXPQmT2adRnPXINVUQuBxVPC//C9wv2uDYWhgS f8M425VPp4/R/uks9mlzTx08DwacvouD0YOC+HZE4sWq+2smgeBInMiyr/THYzl+ baMtYgVs8IKUD2gtjfXZoaQNg3eq5SedSf/5F0S/LCdu9/ccQ8CzSEoVTiQFtO78 SaU37xai8+QTSVpPuINigxCoXmkubBd+voEmWRcBd/XB5L+u+MFU/jXyyBj2BXVj 6agqVzY53KVYt23/63QliAUWyxT+ns9gRxVN1jrMhHdiDwsdT4NbzHxg1Su4eiHv C/wjD3Dga0BRTEGylpHZGzb1U1rZRHM3ho3f1QkmRPPLcBUMTyUTxJm+GEeuhPvp +TBf3Kg/YkdpnEMlagqcyHuIrf3m8Z/pTmpOIbekJWbbA7tluvWbMWw2ARB7dUOE fHYVISh0DTw2oVXxM82/q8XXHnhEXv2nW3K40x1VabxUN+sF4M/7YA8nJqwsPJei 749STYJRfZXdIe69M9zpM5unxENAsiPJgQ== -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/router/orignal_at_mail.i2p.crt000066400000000000000000000035631321131324000244050ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFVDCCAzwCCQC2r1XWYtqtAzANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJY WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMRMwEQYDVQQKDApQdXJwbGUgSTJQ MQ0wCwYDVQQLDARJMlBEMR8wHQYJKoZIhvcNAQkBFhBvcmlnbmFsQG1haWwuaTJw MB4XDTE1MDIyMjEzNTgxOFoXDTI1MDIxOTEzNTgxOFowbDELMAkGA1UEBhMCWFgx CzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEGA1UECgwKUHVycGxlIEkyUDEN MAsGA1UECwwESTJQRDEfMB0GCSqGSIb3DQEJARYQb3JpZ25hbEBtYWlsLmkycDCC AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALp3D/gdvFjrMm+IE8tHZCWE hQ6Pp0CCgCGDBC3WQFLqR98bqVPl4UwRG/MKY/LY7Woai06JNmGcpfw0LMoNnHxT bvKtDRe/8kQdhdLHhgIkWKSbMvTAl7uUdV6FzsPgDR0x7scoFVWEhkF0wfmzGF2V yr/WCBQejFPu69z03m5tRQ8Xjp2txWV45RawUmFu50bgbZvLCSLfTkIvxmfJzgPN pJ3sPa/g7TBZl2uEiAu4uaEKvTuuzStOWCGgFaHYFVlTfFXTvmhFMqHfaidtzrlu H35WGrmIWTDl6uGPC5QkSppvkj73rDj5aEyPzWMz5DN3YeECoVSchN+OJJCM6m7+ rLFYXghVEp2h+T9O1GBRfcHlQ2E3CrWWvxhmK8dfteJmd501dyNX2paeuIg/aPFO 54/8m2r11uyF29hgY8VWLdXtqvwhKuK36PCzofEwDp9QQX8GRsEV4pZTrn4bDhGo kb9BF7TZTqtL3uyiRmIyBXrNNiYlA1Xm4fyKRtxl0mrPaUXdgdnCt3KxOAJ8WM2B 7L/kk9U8C/nexHbMxIZfTap49XcUg5dxSO9kOBosIOcCUms8sAzBPDV2tWAByhYF jI/Tutbd3F0+fvcmTcIFOlGbOxKgO2SfwXjv/44g/3LMK6IAMFB9UOc8KhnnJP0f uAHvMXn1ahRs4pM1VizLAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAIOxdaXT+wfu nv/+1hy5T4TlRMNNsuj79ROcy6Mp+JwMG50HjTc0qTlXh8C7nHybDJn4v7DA+Nyn RxT0J5I+Gqn+Na9TaC9mLeX/lwe8/KomyhBWxjrsyWj1V6v/cLO924S2rtcfzMDm l3SFh9YHM1KF/R9N1XYBwtMzr3bupWDnE1yycYp1F4sMLr5SMzMQ0svQpQEM2/y5 kly8+eUzryhm+ag9x1686uEG5gxhQ1eHQoZEaClHUOsV+28+d5If7cqcYx9Hf5Tt CiVjJQzdxBF+6GeiJtKxnLtevqlkbyIJt6Cm9/7YIy/ovRGF2AKSYN6oCwmZQ6i1 8nRnFq5zE7O94m+GXconWZxy0wVqA6472HThMi7S+Tk/eLYen2ilGY+KCb9a0FH5 5MOuWSoJZ8/HfW2VeQmL8EjhWm5F2ybg28wgXK4BOGR3jQi03Fsc+AFidnWxSKo0 aiJoPgOsfyu8/fnCcAi07kSmjzUKIWskApgcpGQLNXHFK9mtg7+VA8esRnfLlKtP tJf+nNAPY1sqHfGBzh7WWGWal5RGHF5nEm3ta3oiFF5sMKCJ6C87zVwFkEcRytGC xOGmiG1O1RPrO5NG7rZUaQ4y1OKl2Y1H+nGONzZ3mvoAOvxEq6JtUnU2kZscpPlk fpeOSDoGBYJGbIpzDreBDhxaZrwGq36k -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/router/str4d_at_mail.i2p.crt000066400000000000000000000036501321131324000240070ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFezCCA2OgAwIBAgIEHLJfZzANBgkqhkiG9w0BAQ0FADBuMQswCQYDVQQGEwJY WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOc3RyNGRAbWFpbC5p MnAwHhcNMTMxMDI2MTExODQxWhcNMjMxMDI2MTExODQxWjBuMQswCQYDVQQGEwJY WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEXMBUGA1UEAwwOc3RyNGRAbWFpbC5p MnAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvw0vTay1IPOgxvwe8 yt5jGakha20kw9qDb6zbEL87EWEkeOzdu7iUC69lkxVP9Ws8EbLtkeMf/CXg6CC1 e+w8WpOHj5prOsiOlrIO+2I1tKMaMUuJDX2wK4I5ZSw/Kieimh9xqOBZknDmtwjw 2HPW8rpxMqrScaGAP6sQD8Gh4XKKkLogfxYPzF8NnC6O8vBkFKVU2WSVZ0jPAQfv 6luPdA+5lES+5UPWr9Yhv/CX4siGKUTxchqJRf2VU4o5BzzXae4asVA/NY7lKgEw eDDufbm0mRFWP4mbmXRlODuJ8GMnJbMQkNcAvZUnUcvpSTnGnIvxyxtXP5P6ic8V 3b9HV2eIsbfO1xrgyr6/9qgGpXcdDJejhvNg6fZgQeO40bOGQYwV8bNvsNQHqnZl KsVhsMQkOubMxcHTBadcifi8PmdeJ5hxyyqJmyrwkmg2ijnN521M6YkoBzl+8VAi zLmqKZfvN5t+pb9PZ3U3jHfkeIEwDRYRAOsvVqch5+ZfSv8x/Te6o15zDKPJQtWK ty42GV1vERw30oSZQdrRRy/+4+HSRs3/Zb368OdAbcr+f/xPvwceYGWPeNNIoZ/x xkIQE3xgEK+eJyPM9McjlCAezZZclT7fWfiEYNJAiS3fGALi+a+cGYWWULxCXpz+ y397OHhZBhnh7D9K8aPePB8tCwIDAQABoyEwHzAdBgNVHQ4EFgQUezvGHq3h1gbC Hs2LLVoll5fIUWMwDQYJKoZIhvcNAQENBQADggIBAF7SG1WBcE1r5eyTp/BLFZfG iPtvqu+B1L2HutPum/Xf8A5fxR4kcKAKpVdu6vnDzCRAsAC9YvyETgAzI2nfVgLk l9YZ31tSi6qxnMsQsV5o9lt/q2Rvsf2Zi/Ir8AlWtvnP8YG0Aj/8AG8MyhMLaIdj M2FuakPs8RqEjoJL9dTOC9VTQpNTwBH9guP9UalWYwlkaXDzMoyO4nswT/GpCpg8 4m4RO6grzdsEIamD/PCBM5f/vq+y08GaqfXpX9+8CbaX3tdzd3x48wPphmdpkptk aRELIpLJZiK+Mos7W+0ZS8SHxGDIosjqVsgbZPmk12+VBcVgLOr8W1D7osS4OY59 2GMUVV/GhoDh8wR/Td5wpZlcPE0NWmljjVg9+1E8ePAyMZy+U1KCiMlRVdRy518O dOzzUUQGqGQHosRrH0ypS3MGbMLmbuWFRiz7q/3mUmW2xikH9I1t/6ZMNUvh+IWL kGAaEf2JIv/D8+QsC0Un1W09DgvYz7qmKSeHhBixlLe68vgXtz/Fa+rRMsmPrueo 4wk/u/VyILo0BJP860APJMZbm+DPfGhV9DF9L5Gx9+d/BlduBVGHc+AQSWbU70dS eH4/rgUYRikWlgwUxjY8/QQTlfx5xl28tG0xdO9libN22z7UwTGfm48BQIdrTyER hqQ7usTy3oaWD85MbJ0q -----END CERTIFICATE----- i2pd-2.17.0/contrib/certificates/router/zzz_at_mail.i2p.crt000066400000000000000000000036441321131324000236070ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFdzCCA1+gAwIBAgIEcwrwsjANBgkqhkiG9w0BAQ0FADBsMQswCQYDVQQGEwJY WDELMAkGA1UECBMCWFgxCzAJBgNVBAcTAlhYMR4wHAYDVQQKExVJMlAgQW5vbnlt b3VzIE5ldHdvcmsxDDAKBgNVBAsTA0kyUDEVMBMGA1UEAwwMenp6QG1haWwuaTJw MB4XDTEzMDkzMDE3NDEyNVoXDTIzMDkzMDE3NDEyNVowbDELMAkGA1UEBhMCWFgx CzAJBgNVBAgTAlhYMQswCQYDVQQHEwJYWDEeMBwGA1UEChMVSTJQIEFub255bW91 cyBOZXR3b3JrMQwwCgYDVQQLEwNJMlAxFTATBgNVBAMMDHp6ekBtYWlsLmkycDCC AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJV4ptvhlfrcize9/ARz4lUy iLtLMSvSST1BdZjLJTwus05EUs0oiqnv9qXYIWGRB97aKlAmqSxsn4ZgBttCgmev IkuiZ8kbdqI5YaT98yKW5P2Prt9p9cPbnz5/qjwZ5L9W+k/Itx7bv2pkNEP0NLYo NrgHHTb1hsyRxc0lfPYk2BwsIi8hIWBHNrRpR41EWFXfqPcdsxS8cQhxVj4zLG/R aMm4H8T+V1R1Khl4R4qqRgXBP305xqqRoawHmZ/S9/RkF0Ji6IYwBq9iWthWol6W sMDn1xhZk9765fk+ohAC2XWuGSFCr02JOILRV3x/8OUxT1GYgYjc7FfyWIekg/pZ yotlhL2I3SMWOH3PdG58iDY121hq/LsSKM9aP20rwtvssnw+8Aex01YDkI3bM6yO HNi+tRojaJcJciBWv6cuiFKvQdxj/mOhOr0u0lHLlJ4jqES8uvVJkS7X/C4BB7ra bJYQgumZMYvVQJFIjo8vZxMXue53o65FRidvAUT29ay54UTiL7jRV9w1wHnzLapU xT1v7kWpWJcZ1zzC8coJjW+6ijkk38cVLb80u1Q4kEbmP2rDxw6jRvmqg6DcCKjK oqDt+XQ6P5grxAxLT+VMfB404WHHwNs6BB841//4ZnXvy3msMONY/5y0fsblURgh IS2UG1TAjR+x7+XikGx9AgMBAAGjITAfMB0GA1UdDgQWBBSvx/fCCP8UeHwjN65p EoHjgRfiIzANBgkqhkiG9w0BAQ0FAAOCAgEAYgVE1Aa/Ok5k+Jvujbx72bktRWXo Y4UfbWH/426VdgqXt3n9XtJUNM2oI4ODwITM4O15SyXQTLJhnvJz5ELcJV8nqviZ RjK2HNX1BW7IEta3tacCvVnjzZ265kCT59uW+qmd+5PiaAYI5lYUn8P6pe+6neSa HW6ecXCrdxJetSYfUUuKeV6YHpdzfjtZClLmwl91sJUBKcjK+Q9G/cE6HnwcDH1s uXr7SgkBt/qc/OlNuu4fnTqUA58TAumdq9cD+eLBilDFrux1HsUZMuBUp64x5oPi gme+3VewsczfFEtrxaG6+l6UA40Lerdx9XECZcDCcFsK6MS1uQ2HYjsyZcWnNT3l 6eDNUbjrllwxDdRAk0cbWiMuc21CFq/1v2QMXk88EiBjEajqzyXUPmKzwFhit6pr 5kfjfXNq+pxQSCoaqjpzVKjb3CqMhSlC8cLgrPw6HEgGnjCy4cTLFHlVmD64M778 tj6rE7CntcmUi8GKmZKyaMyUo3QQUcrjO5IQ4+3iGUgMkZuujyjrZiOJbvircPmK 4IQEXzJ/G00upqtqKstRybaWSbJ/k6iuturtA2n8MJiCBjhLy8dtTgDbFaDaNF7F NHeqQjIJDLhYDy6mi4gya3A0ort777Inl/rWYLo067pYM+EWDw66GdpbEIB0Bp71 pwvcQcjIzbUzEK0= -----END CERTIFICATE----- i2pd-2.17.0/contrib/debian/000077500000000000000000000000001321131324000153075ustar00rootroot00000000000000i2pd-2.17.0/contrib/debian/README000066400000000000000000000002211321131324000161620ustar00rootroot00000000000000This forder contain systemd unit files. To use systemd daemon control, place files from this directory to debian folder before building package. i2pd-2.17.0/contrib/debian/i2pd.service000066400000000000000000000013211321131324000175240ustar00rootroot00000000000000[Unit] Description=I2P Router written in C++ After=network.target [Service] User=i2pd Group=i2pd RuntimeDirectory=i2pd RuntimeDirectoryMode=0700 Type=simple ExecStart=/usr/sbin/i2pd --conf=/etc/i2pd/i2pd.conf --pidfile=/var/run/i2pd/i2pd.pid --logfile=/var/log/i2pd/i2pd.log --daemon --service ExecReload=/bin/kill -HUP $MAINPID PIDFile=/var/run/i2pd/i2pd.pid ### Uncomment, if auto restart needed #Restart=on-failure ### Use SIGINT for gracefull stop daemon. # i2pd stops accepting new tunnels and waits ~10 min while old ones do not die. KillSignal=SIGINT TimeoutStopSec=10m # If you have problems with hunging i2pd, you can try enable this #LimitNOFILE=4096 PrivateDevices=yes [Install] WantedBy=multi-user.target i2pd-2.17.0/contrib/debian/i2pd.tmpfile000066400000000000000000000001061321131324000175240ustar00rootroot00000000000000d /var/run/i2pd 0755 i2pd i2pd - - d /var/log/i2pd 0755 i2pd i2pd - - i2pd-2.17.0/contrib/docker/000077500000000000000000000000001321131324000153345ustar00rootroot00000000000000i2pd-2.17.0/contrib/docker/Dockerfile000066400000000000000000000042101321131324000173230ustar00rootroot00000000000000FROM alpine:latest LABEL authors "Mikal Villa , Darknet Villain " # Expose git branch, tag and URL variables as arguments ARG GIT_BRANCH="master" ENV GIT_BRANCH=${GIT_BRANCH} ARG GIT_TAG="" ENV GIT_TAG=${GIT_TAG} ARG REPO_URL="https://github.com/PurpleI2P/i2pd.git" ENV REPO_URL=${REPO_URL} ENV I2PD_HOME="/home/i2pd" ENV DATA_DIR="${I2PD_HOME}/data" RUN mkdir -p "$I2PD_HOME" "$DATA_DIR" \ && adduser -S -h "$I2PD_HOME" i2pd \ && chown -R i2pd:nobody "$I2PD_HOME" # # Each RUN is a layer, adding the dependencies and building i2pd in one layer takes around 8-900Mb, so to keep the # image under 20mb we need to remove all the build dependencies in the same "RUN" / layer. # # 1. install deps, clone and build. # 2. strip binaries. # 3. Purge all dependencies and other unrelated packages, including build directory. RUN apk --no-cache --virtual build-dependendencies add make gcc g++ libtool boost-dev build-base openssl-dev openssl git \ && mkdir -p /tmp/build \ && cd /tmp/build && git clone -b ${GIT_BRANCH} ${REPO_URL} \ && cd i2pd \ && if [ -n "${GIT_TAG}" ]; then git checkout tags/${GIT_TAG}; fi \ && make \ && cp -R contrib/certificates /i2pd_certificates \ && mkdir -p /usr/local/bin \ && mv i2pd /usr/local/bin \ && cd /usr/local/bin \ && strip i2pd \ && rm -fr /tmp/build && apk --purge del build-dependendencies build-base fortify-headers boost-dev zlib-dev openssl-dev \ boost-python3 python3 gdbm boost-unit_test_framework boost-python linux-headers boost-prg_exec_monitor \ boost-serialization boost-signals boost-wave boost-wserialization boost-math boost-graph boost-regex git pcre \ libtool g++ gcc pkgconfig # 2. Adding required libraries to run i2pd to ensure it will run. RUN apk --no-cache add boost-filesystem boost-system boost-program_options boost-date_time boost-thread boost-iostreams openssl musl-utils libstdc++ COPY entrypoint.sh /entrypoint.sh RUN chmod a+x /entrypoint.sh RUN echo "export DATA_DIR=${DATA_DIR}" >> /etc/profile VOLUME "$DATA_DIR" EXPOSE 7070 4444 4447 7656 2827 7654 7650 USER i2pd ENTRYPOINT [ "/entrypoint.sh" ] i2pd-2.17.0/contrib/docker/entrypoint.sh000066400000000000000000000010431321131324000201010ustar00rootroot00000000000000#!/bin/sh COMMAND=/usr/local/bin/i2pd # To make ports exposeable # Note: $DATA_DIR is defined in /etc/profile DEFAULT_ARGS=" --datadir=$DATA_DIR --reseed.verify=true --upnp.enabled=false --http.enabled=true --http.address=0.0.0.0 --httpproxy.enabled=true --httpproxy.address=0.0.0.0 --socksproxy.enabled=true --socksproxy.address=0.0.0.0 --sam.enabled=true --sam.address=0.0.0.0" if [ "$1" = "--help" ]; then set -- $COMMAND --help else ln -s /i2pd_certificates "$DATA_DIR"/certificates set -- $COMMAND $DEFAULT_ARGS $@ fi exec "$@" i2pd-2.17.0/contrib/i2pd.conf000066400000000000000000000117631321131324000156020ustar00rootroot00000000000000## Configuration file for a typical i2pd user ## See https://i2pd.readthedocs.org/en/latest/configuration.html ## for more options you can use in this file. ## Lines that begin with "## " try to explain what's going on. Lines ## that begin with just "#" are disabled commands: you can enable them ## by removing the "#" symbol. ## Tunnels config file ## Default: ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf # tunconf = /var/lib/i2pd/tunnels.conf ## Where to write pidfile (don't write by default) # pidfile = /var/run/i2pd.pid ## Logging configuration section ## By default logs go to stdout with level 'info' and higher ## ## Logs destination (valid values: stdout, file, syslog) ## * stdout - print log entries to stdout ## * file - log entries to a file ## * syslog - use syslog, see man 3 syslog # log = file ## Path to logfile (default - autodetect) # logfile = /var/log/i2pd.log ## Log messages above this level (debug, *info, warn, error, none) ## If you set it to none, logging will be disabled # loglevel = info ## Path to storage of i2pd data (RI, keys, peer profiles, ...) ## Default: ~/.i2pd or /var/lib/i2pd # datadir = /var/lib/i2pd ## Daemon mode. Router will go to background after start # daemon = true ## Run as a service. Router will use system folders like ‘/var/lib/i2pd’ # service = true ## Specify a family, router belongs to (default - none) # family = ## External IP address to listen for connections ## By default i2pd sets IP automatically # host = 1.2.3.4 ## Port to listen for connections ## By default i2pd picks random port. You MUST pick a random number too, ## don't just uncomment this # port = 4567 ## Enable communication through ipv4 ipv4 = true ## Enable communication through ipv6 ipv6 = false ## Network interface to bind to # ifname = ## Enable NTCP transport (default = true) # ntcp = true ## Enable SSU transport (default = true) # ssu = true ## Should we assume we are behind NAT? (false only in MeshNet) # nat = true ## Bandwidth configuration ## L limit bandwidth to 32KBs/sec, O - to 256KBs/sec, P - to 2048KBs/sec, ## X - unlimited ## Default is X for floodfill, L for regular node # bandwidth = L ## Router will not accept transit tunnels, disabling transit traffic completely ## (default = false) # notransit = true ## Router will be floodfill # floodfill = true [limits] ## Maximum active transit sessions (default:2500) # transittunnels = 2500 [precomputation] ## Enable or disable elgamal precomputation table ## By default, enabled on i386 hosts # elgamal = true [upnp] ## Enable or disable UPnP: automatic port forwarding (enabled by default in WINDOWS, ANDROID) # enabled = false ## Name i2pd appears in UPnP forwardings list (default = I2Pd) # name = I2Pd [reseed] ## Enable or disable reseed data verification. verify = true ## URLs to request reseed data from, separated by comma ## Default: "mainline" I2P Network reseeds # urls = https://reseed.i2p-projekt.de/,https://i2p.mooo.com/netDb/,https://netdb.i2p2.no/ ## Path to local reseed data file (.su3) for manual reseeding # file = /path/to/i2pseeds.su3 ## or HTTPS URL to reseed from # file = https://legit-website.com/i2pseeds.su3 [addressbook] ## AddressBook subscription URL for initial setup ## Default: inr.i2p at "mainline" I2P Network # defaulturl = http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt ## Optional subscriptions URLs, separated by comma # subscriptions = http://inr.i2p/export/alive-hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt,http://rus.i2p/hosts.txt [http] ## Uncomment and set to 'false' to disable Web Console # enabled = true ## Address and port service will listen on address = 127.0.0.1 port = 7070 [httpproxy] ## Uncomment and set to 'false' to disable HTTP Proxy # enabled = true ## Address and port service will listen on address = 127.0.0.1 port = 4444 ## Optional keys file for proxy local destination # keys = http-proxy-keys.dat [socksproxy] ## Uncomment and set to 'false' to disable SOCKS Proxy # enabled = true ## Address and port service will listen on address = 127.0.0.1 port = 4447 ## Optional keys file for proxy local destination # keys = socks-proxy-keys.dat ## Socks outproxy. Example below is set to use Tor for all connections except i2p ## Uncomment and set to 'true' to enable using of SOCKS outproxy # outproxy.enabled = false ## Address and port of outproxy # outproxy = 127.0.0.1 # outproxyport = 9050 [sam] ## Uncomment and set to 'true' to enable SAM Bridge enabled = true ## Address and port service will listen on # address = 127.0.0.1 # port = 7656 [bob] ## Uncomment and set to 'true' to enable BOB command channel # enabled = false ## Address and port service will listen on # address = 127.0.0.1 # port = 2827 [i2cp] ## Uncomment and set to 'true' to enable I2CP protocol # enabled = false ## Address and port service will listen on # address = 127.0.0.1 # port = 7654 [i2pcontrol] ## Uncomment and set to 'true' to enable I2PControl protocol # enabled = false ## Address and port service will listen on # address = 127.0.0.1 # port = 7650 i2pd-2.17.0/contrib/rpm/000077500000000000000000000000001321131324000146635ustar00rootroot00000000000000i2pd-2.17.0/contrib/rpm/i2pd.service000066400000000000000000000003461321131324000171060ustar00rootroot00000000000000[Unit] Description=I2P router After=network.target [Service] User=i2pd Group=i2pd Type=simple ExecStart=/usr/bin/i2pd --service PIDFile=/var/lib/i2pd/i2pd.pid Restart=always PrivateTmp=true [Install] WantedBy=multi-user.target i2pd-2.17.0/contrib/rpm/i2pd.spec000066400000000000000000000134151321131324000164010ustar00rootroot00000000000000%define build_timestamp %(date +"%Y%m%d") Name: i2pd Version: 2.17.0 Release: %{build_timestamp}git%{?dist} Summary: I2P router written in C++ License: BSD URL: https://github.com/PurpleI2P/i2pd Source0: https://github.com/PurpleI2P/i2pd/archive/%{version}/%name-%version.tar.gz %if 0%{?rhel} == 7 BuildRequires: cmake3 %else BuildRequires: cmake %endif BuildRequires: chrpath BuildRequires: gcc-c++ BuildRequires: zlib-devel BuildRequires: boost-devel BuildRequires: openssl-devel BuildRequires: miniupnpc-devel BuildRequires: systemd-units %description C++ implementation of I2P. %package systemd Summary: Files to run I2P router under systemd Requires: i2pd Requires: systemd Requires(pre): %{_sbindir}/useradd %{_sbindir}/groupadd Obsoletes: %{name}-daemon %description systemd C++ implementation of I2P. This package contains systemd unit file to run i2pd as a system service using dedicated user's permissions. %prep %setup -q %build cd build %if 0%{?rhel} == 7 %cmake3 \ -DWITH_LIBRARY=OFF \ -DWITH_UPNP=ON \ -DWITH_HARDENING=ON \ -DBUILD_SHARED_LIBS:BOOL=OFF %else %cmake \ -DWITH_LIBRARY=OFF \ -DWITH_UPNP=ON \ -DWITH_HARDENING=ON \ -DBUILD_SHARED_LIBS:BOOL=OFF %endif make %{?_smp_mflags} %install cd build chrpath -d i2pd install -D -m 755 i2pd %{buildroot}%{_bindir}/i2pd install -D -m 644 %{_builddir}/%{name}-%{version}/contrib/rpm/i2pd.service %{buildroot}/%{_unitdir}/i2pd.service install -d -m 700 %{buildroot}/%{_sharedstatedir}/i2pd %pre systemd getent group i2pd >/dev/null || %{_sbindir}/groupadd -r i2pd getent passwd i2pd >/dev/null || \ %{_sbindir}/useradd -r -g i2pd -s %{_sbindir}/nologin \ -d %{_sharedstatedir}/i2pd -c 'I2P Service' i2pd %post systemd %systemd_post i2pd.service %preun systemd %systemd_preun i2pd.service %postun systemd %systemd_postun_with_restart i2pd.service %files %doc LICENSE README.md %_bindir/i2pd %files systemd /%_unitdir/i2pd.service %dir %attr(0700,i2pd,i2pd) %_sharedstatedir/i2pd %changelog * Mon Dec 04 2017 orignal - 2.17.0 - Added reseed through HTTP and SOCKS proxy - Added show status of client services through web console - Added change log level through web connsole - Added transient keys for tunnels - Added i2p.streaming.initialAckDelay parameter - Added CRYPTO_TYPE for SAM destination - Added signature and crypto type for newkeys BOB command - Changed - correct publication of ECIES destinations - Changed - disable RSA signatures completely - Fixed CVE-2017-17066 - Fixed possible buffer overflow for RSA-4096 - Fixed shutdown from web console for Windows - Fixed web console page layout * Mon Nov 13 2017 orignal - 2.16.0 - Added https and "Connect" method for HTTP proxy - Added outproxy for HTTP proxy - Added initial support of ECIES crypto - Added NTCP soft and hard descriptors limits - Added support full timestamps in logs - Changed faster implmentation of GOST R 34.11 hash - Changed reject routers with RSA signtures - Changed reload config and shudown from Windows GUI - Changed update tunnels address(destination) without restart - Fixed BOB crashes if destination is not set - Fixed correct SAM tunnel name - Fixed QT GUI issues * Thu Aug 17 2017 orignal - 2.15.0 - Added QT GUI - Added ability add and remove I2P tunnels without restart - Added ability to disable SOCKS outproxy option - Changed strip-out Accept-* hedaers in HTTP proxy - Changed peer test if nat=false - Changed separate output of NTCP and SSU sessions in Transports tab - Fixed handle lines with comments in hosts.txt file for address book - Fixed run router with empty netdb for testnet - Fixed skip expired introducers by iexp * Thu Jun 01 2017 orignal - 2.14.0 - Added transit traffic bandwidth limitation - Added NTCP connections through HTTP and SOCKS proxies - Added ability to disable address helper for HTTP proxy - Changed reseed servers list * Thu Apr 06 2017 orignal - 2.13.0 - Added persist local destination's tags - Added GOST signature types 9 and 10 - Added exploratory tunnels configuration - Changed reseed servers list - Changed inactive NTCP sockets get closed faster - Changed some EdDSA speed up - Fixed multiple acceptors for SAM - Fixed follow on data after STREAM CREATE for SAM - Fixed memory leaks * Tue Feb 14 2017 orignal - 2.12.0 - Additional HTTP and SOCKS proxy tunnels - Reseed from ZIP archive - 'X' bandwidth code - Reduced memory and file descriptors usage * Mon Dec 19 2016 orignal - 2.11.0 - Full support of zero-hops tunnels - Tunnel configuration for HTTP and SOCKS proxy - Websockets support - Multiple acceptors for SAM destination - Routing path for UDP tunnels - Reseed through a floodfill - Use AVX instructions for DHT and HMAC if applicable - Fixed UPnP discovery bug, producing excessive CPU usage - Handle multiple lookups of the same LeaseSet correctly * Thu Oct 20 2016 Anatolii Vorona - 2.10.0-3 - add support C7 - move rpm-related files to contrib folder * Sun Oct 16 2016 Oleg Girko - 2.10.0-1 - update to 2.10.0 * Sun Aug 14 2016 Oleg Girko - 2.9.0-1 - update to 2.9.0 * Sun Aug 07 2016 Oleg Girko - 2.8.0-2 - rename daemon subpackage to systemd * Sat Aug 06 2016 Oleg Girko - 2.8.0-1 - update to 2.8.0 - remove wrong rpath from i2pd binary - add daemon subpackage with systemd unit file * Sat May 21 2016 Oleg Girko - 2.7.0-1 - update to 2.7.0 * Tue Apr 05 2016 Oleg Girko - 2.6.0-1 - update to 2.6.0 * Tue Jan 26 2016 Yaroslav Sidlovsky - 2.3.0-1 - initial package for version 2.3.0 i2pd-2.17.0/contrib/subscriptions.txt000066400000000000000000000001551321131324000175360ustar00rootroot00000000000000http://inr.i2p/export/alive-hosts.txt http://stats.i2p/cgi-bin/newhosts.txt http://i2p-projekt.i2p/hosts.txt i2pd-2.17.0/contrib/tunnels.conf000066400000000000000000000011301321131324000164170ustar00rootroot00000000000000[IRC-IRC2P] type = client address = 127.0.0.1 port = 6668 destination = irc.postman.i2p destinationport = 6667 keys = irc-keys.dat #[IRC-ILITA] #type = client #address = 127.0.0.1 #port = 6669 #destination = irc.ilita.i2p #destinationport = 6667 #keys = irc-keys.dat #[SMTP] #type = client #address = 127.0.0.1 #port = 7659 #destination = smtp.postman.i2p #destinationport = 25 #keys = smtp-keys.dat #[POP3] #type = client #address = 127.0.0.1 #port = 7660 #destination = pop.postman.i2p #destinationport = 110 #keys = pop3-keys.dat # see more examples in /usr/share/doc/i2pd/configuration.md.gz i2pd-2.17.0/daemon/000077500000000000000000000000001321131324000136705ustar00rootroot00000000000000i2pd-2.17.0/daemon/Daemon.cpp000066400000000000000000000271221321131324000156030ustar00rootroot00000000000000#include #include #include "Daemon.h" #include "Config.h" #include "Log.h" #include "FS.h" #include "Base.h" #include "version.h" #include "Transports.h" #include "NTCPSession.h" #include "RouterInfo.h" #include "RouterContext.h" #include "Tunnel.h" #include "HTTP.h" #include "NetDb.hpp" #include "Garlic.h" #include "Streaming.h" #include "Destination.h" #include "HTTPServer.h" #include "I2PControl.h" #include "ClientContext.h" #include "Crypto.h" #include "UPnP.h" #include "util.h" #include "Event.h" #include "Websocket.h" namespace i2p { namespace util { class Daemon_Singleton::Daemon_Singleton_Private { public: Daemon_Singleton_Private() {}; ~Daemon_Singleton_Private() {}; std::unique_ptr httpServer; std::unique_ptr m_I2PControlService; std::unique_ptr UPnP; #ifdef WITH_EVENTS std::unique_ptr m_WebsocketServer; #endif }; Daemon_Singleton::Daemon_Singleton() : isDaemon(false), running(true), d(*new Daemon_Singleton_Private()) {} Daemon_Singleton::~Daemon_Singleton() { delete &d; } bool Daemon_Singleton::IsService () const { bool service = false; #ifndef _WIN32 i2p::config::GetOption("service", service); #endif return service; } bool Daemon_Singleton::init(int argc, char* argv[]) { i2p::config::Init(); i2p::config::ParseCmdline(argc, argv); std::string config; i2p::config::GetOption("conf", config); std::string datadir; i2p::config::GetOption("datadir", datadir); i2p::fs::DetectDataDir(datadir, IsService()); i2p::fs::Init(); datadir = i2p::fs::GetDataDir(); // TODO: drop old name detection in v2.8.0 if (config == "") { config = i2p::fs::DataDirPath("i2p.conf"); if (i2p::fs::Exists (config)) { LogPrint(eLogWarning, "Daemon: please rename i2p.conf to i2pd.conf here: ", config); } else { config = i2p::fs::DataDirPath("i2pd.conf"); if (!i2p::fs::Exists (config)) { // use i2pd.conf only if exists config = ""; /* reset */ } } } i2p::config::ParseConfig(config); i2p::config::Finalize(); i2p::config::GetOption("daemon", isDaemon); std::string logs = ""; i2p::config::GetOption("log", logs); std::string logfile = ""; i2p::config::GetOption("logfile", logfile); std::string loglevel = ""; i2p::config::GetOption("loglevel", loglevel); bool logclftime; i2p::config::GetOption("logclftime", logclftime); /* setup logging */ if (logclftime) i2p::log::Logger().SetTimeFormat ("[%d/%b/%Y:%H:%M:%S %z]"); if (isDaemon && (logs == "" || logs == "stdout")) logs = "file"; i2p::log::Logger().SetLogLevel(loglevel); if (logs == "file") { if (logfile == "") logfile = i2p::fs::DataDirPath("i2pd.log"); LogPrint(eLogInfo, "Log: will send messages to ", logfile); i2p::log::Logger().SendTo (logfile); #ifndef _WIN32 } else if (logs == "syslog") { LogPrint(eLogInfo, "Log: will send messages to syslog"); i2p::log::Logger().SendTo("i2pd", LOG_DAEMON); #endif } else { // use stdout -- default } LogPrint(eLogInfo, "i2pd v", VERSION, " starting"); #ifdef AESNI LogPrint(eLogInfo, "AESNI enabled"); #endif #if defined(__AVX__) LogPrint(eLogInfo, "AVX enabled"); #endif LogPrint(eLogDebug, "FS: main config file: ", config); LogPrint(eLogDebug, "FS: data directory: ", datadir); bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); i2p::crypto::InitCrypto (precomputation); int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); i2p::context.Init (); bool ipv6; i2p::config::GetOption("ipv6", ipv6); bool ipv4; i2p::config::GetOption("ipv4", ipv4); #ifdef MESHNET // manual override for meshnet ipv4 = false; ipv6 = true; #endif uint16_t port; i2p::config::GetOption("port", port); if (!i2p::config::IsDefault("port")) { LogPrint(eLogInfo, "Daemon: accepting incoming connections at port ", port); i2p::context.UpdatePort (port); } i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); bool transit; i2p::config::GetOption("notransit", transit); i2p::context.SetAcceptsTunnels (!transit); uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); SetMaxNumTransitTunnels (transitTunnels); bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); if (isFloodfill) { LogPrint(eLogInfo, "Daemon: router will be floodfill"); i2p::context.SetFloodfill (true); } else { i2p::context.SetFloodfill (false); } /* this section also honors 'floodfill' flag, if set above */ std::string bandwidth; i2p::config::GetOption("bandwidth", bandwidth); if (bandwidth.length () > 0) { if (bandwidth[0] >= 'K' && bandwidth[0] <= 'X') { i2p::context.SetBandwidth (bandwidth[0]); LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), "KBps"); } else { auto value = std::atoi(bandwidth.c_str()); if (value > 0) { i2p::context.SetBandwidth (value); LogPrint(eLogInfo, "Daemon: bandwidth set to ", i2p::context.GetBandwidthLimit (), " KBps"); } else { LogPrint(eLogInfo, "Daemon: unexpected bandwidth ", bandwidth, ". Set to 'low'"); i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); } } } else if (isFloodfill) { LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'"); i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_EXTRA_BANDWIDTH1); } else { LogPrint(eLogInfo, "Daemon: bandwidth set to 'low'"); i2p::context.SetBandwidth (i2p::data::CAPS_FLAG_LOW_BANDWIDTH2); } int shareRatio; i2p::config::GetOption("share", shareRatio); i2p::context.SetShareRatio (shareRatio); std::string family; i2p::config::GetOption("family", family); i2p::context.SetFamily (family); if (family.length () > 0) LogPrint(eLogInfo, "Daemon: family set to ", family); bool trust; i2p::config::GetOption("trust.enabled", trust); if (trust) { LogPrint(eLogInfo, "Daemon: explicit trust enabled"); std::string fam; i2p::config::GetOption("trust.family", fam); std::string routers; i2p::config::GetOption("trust.routers", routers); bool restricted = false; if (fam.length() > 0) { std::set fams; size_t pos = 0, comma; do { comma = fam.find (',', pos); fams.insert (fam.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); pos = comma + 1; } while (comma != std::string::npos); i2p::transport::transports.RestrictRoutesToFamilies(fams); restricted = fams.size() > 0; } if (routers.length() > 0) { std::set idents; size_t pos = 0, comma; do { comma = routers.find (',', pos); i2p::data::IdentHash ident; ident.FromBase64 (routers.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); idents.insert (ident); pos = comma + 1; } while (comma != std::string::npos); LogPrint(eLogInfo, "Daemon: setting restricted routes to use ", idents.size(), " trusted routesrs"); i2p::transport::transports.RestrictRoutesToRouters(idents); restricted = idents.size() > 0; } if(!restricted) LogPrint(eLogError, "Daemon: no trusted routers of families specififed"); } bool hidden; i2p::config::GetOption("trust.hidden", hidden); if (hidden) { LogPrint(eLogInfo, "Daemon: using hidden mode"); i2p::data::netdb.SetHidden(true); } return true; } bool Daemon_Singleton::start() { i2p::log::Logger().Start(); LogPrint(eLogInfo, "Daemon: starting NetDB"); i2p::data::netdb.Start(); bool upnp; i2p::config::GetOption("upnp.enabled", upnp); if (upnp) { d.UPnP = std::unique_ptr(new i2p::transport::UPnP); d.UPnP->Start (); } bool ntcp; i2p::config::GetOption("ntcp", ntcp); bool ssu; i2p::config::GetOption("ssu", ssu); LogPrint(eLogInfo, "Daemon: starting Transports"); if(!ssu) LogPrint(eLogInfo, "Daemon: ssu disabled"); if(!ntcp) LogPrint(eLogInfo, "Daemon: ntcp disabled"); i2p::transport::transports.Start(ntcp, ssu); if (i2p::transport::transports.IsBoundNTCP() || i2p::transport::transports.IsBoundSSU()) { LogPrint(eLogInfo, "Daemon: Transports started"); } else { LogPrint(eLogError, "Daemon: failed to start Transports"); /** shut down netdb right away */ i2p::transport::transports.Stop(); i2p::data::netdb.Stop(); return false; } bool http; i2p::config::GetOption("http.enabled", http); if (http) { std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); LogPrint(eLogInfo, "Daemon: starting HTTP Server at ", httpAddr, ":", httpPort); d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); d.httpServer->Start(); } LogPrint(eLogInfo, "Daemon: starting Tunnels"); i2p::tunnel::tunnels.Start(); LogPrint(eLogInfo, "Daemon: starting Client"); i2p::client::context.Start (); // I2P Control Protocol bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); if (i2pcontrol) { std::string i2pcpAddr; i2p::config::GetOption("i2pcontrol.address", i2pcpAddr); uint16_t i2pcpPort; i2p::config::GetOption("i2pcontrol.port", i2pcpPort); LogPrint(eLogInfo, "Daemon: starting I2PControl at ", i2pcpAddr, ":", i2pcpPort); d.m_I2PControlService = std::unique_ptr(new i2p::client::I2PControlService (i2pcpAddr, i2pcpPort)); d.m_I2PControlService->Start (); } #ifdef WITH_EVENTS bool websocket; i2p::config::GetOption("websockets.enabled", websocket); if(websocket) { std::string websocketAddr; i2p::config::GetOption("websockets.address", websocketAddr); uint16_t websocketPort; i2p::config::GetOption("websockets.port", websocketPort); LogPrint(eLogInfo, "Daemon: starting Websocket server at ", websocketAddr, ":", websocketPort); d.m_WebsocketServer = std::unique_ptr(new i2p::event::WebsocketServer (websocketAddr, websocketPort)); d.m_WebsocketServer->Start(); i2p::event::core.SetListener(d.m_WebsocketServer->ToListener()); } #endif return true; } bool Daemon_Singleton::stop() { #ifdef WITH_EVENTS i2p::event::core.SetListener(nullptr); #endif LogPrint(eLogInfo, "Daemon: shutting down"); LogPrint(eLogInfo, "Daemon: stopping Client"); i2p::client::context.Stop(); LogPrint(eLogInfo, "Daemon: stopping Tunnels"); i2p::tunnel::tunnels.Stop(); if (d.UPnP) { d.UPnP->Stop (); d.UPnP = nullptr; } LogPrint(eLogInfo, "Daemon: stopping Transports"); i2p::transport::transports.Stop(); LogPrint(eLogInfo, "Daemon: stopping NetDB"); i2p::data::netdb.Stop(); if (d.httpServer) { LogPrint(eLogInfo, "Daemon: stopping HTTP Server"); d.httpServer->Stop(); d.httpServer = nullptr; } if (d.m_I2PControlService) { LogPrint(eLogInfo, "Daemon: stopping I2PControl"); d.m_I2PControlService->Stop (); d.m_I2PControlService = nullptr; } #ifdef WITH_EVENTS if (d.m_WebsocketServer) { LogPrint(eLogInfo, "Daemon: stopping Websocket server"); d.m_WebsocketServer->Stop(); d.m_WebsocketServer = nullptr; } #endif i2p::crypto::TerminateCrypto (); i2p::log::Logger().Stop(); return true; } } } i2pd-2.17.0/daemon/Daemon.h000066400000000000000000000036071321131324000152520ustar00rootroot00000000000000#ifndef DAEMON_H__ #define DAEMON_H__ #include #include namespace i2p { namespace util { class Daemon_Singleton_Private; class Daemon_Singleton { public: virtual bool init(int argc, char* argv[]); virtual bool start(); virtual bool stop(); virtual void run () {}; bool isDaemon; bool running; protected: Daemon_Singleton(); virtual ~Daemon_Singleton(); bool IsService () const; // d-pointer for httpServer, httpProxy, etc. class Daemon_Singleton_Private; Daemon_Singleton_Private &d; }; #if defined(QT_GUI_LIB) // check if QT #define Daemon i2p::util::DaemonQT::Instance() // dummy, invoked from RunQT class DaemonQT: public i2p::util::Daemon_Singleton { public: static DaemonQT& Instance() { static DaemonQT instance; return instance; } }; #elif defined(ANDROID) #define Daemon i2p::util::DaemonAndroid::Instance() // dummy, invoked from android/jni/DaemonAndroid.* class DaemonAndroid: public i2p::util::Daemon_Singleton { public: static DaemonAndroid& Instance() { static DaemonAndroid instance; return instance; } }; #elif defined(_WIN32) #define Daemon i2p::util::DaemonWin32::Instance() class DaemonWin32 : public Daemon_Singleton { public: static DaemonWin32& Instance() { static DaemonWin32 instance; return instance; } bool init(int argc, char* argv[]); bool start(); bool stop(); void run (); bool isGraceful; DaemonWin32 ():isGraceful(false) {} }; #else #define Daemon i2p::util::DaemonLinux::Instance() class DaemonLinux : public Daemon_Singleton { public: static DaemonLinux& Instance() { static DaemonLinux instance; return instance; } bool start(); bool stop(); void run (); private: std::string pidfile; int pidFH; public: int gracefulShutdownInterval; // in seconds }; #endif } } #endif // DAEMON_H__ i2pd-2.17.0/daemon/HTTPServer.cpp000066400000000000000000001161001321131324000163410ustar00rootroot00000000000000#include #include #include #include #include #include #include "Base.h" #include "FS.h" #include "Log.h" #include "Config.h" #include "Tunnel.h" #include "Transports.h" #include "NetDb.hpp" #include "HTTP.h" #include "LeaseSet.h" #include "Destination.h" #include "RouterContext.h" #include "ClientContext.h" #include "HTTPServer.h" #include "Daemon.h" #include "util.h" #ifdef WIN32_APP #include "Win32/Win32App.h" #endif // For image and info #include "version.h" namespace i2p { namespace http { const char *itoopieFavicon = "data:image/png;base64," "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACx" "jwv8YQUAAAAJcEhZcwAALiIAAC4iAari3ZIAAAAHdElNRQfgCQsUNSZrkhi1AAAAGXRFWHRTb2Z0" "d2FyZQBwYWludC5uZXQgNC4wLjEyQwRr7AAAAoJJREFUOE9jwAUqi4Q1oEwwcDTV1+5sETaBclGB" "vb09C5QJB6kWpvFQJoOCeLC5kmjEHCgXE2SlyETLi3h6QrkM4VL+ssWSCZUgtopITLKqaOotRTEn" "cbAkLqAkGtOqLBLVAWLXyWSVFkkmRiqLxuaqiWb/VBYJMAYrwgckJY25VEUzniqKhjU2y+RtCRSP" "6lUXy/1jIBV5tlYxZUaFVMq2NInwIi9hO8fSfOEAqDZUoCwal6MulvOvyS7gi69K4j9zxZT/m0ps" "/28ptvvvquXXryIa7QYMMdTwqi0WNtVi0GIDseXl7TnUxFKfnGlxAGp0+D8j2eH/8Ub7/9e7nf7X" "+Af/B7rwt6pI0h0l0WhQADOC9DBkhSirpImHNVZKp24ukkyoshGLnN8d5fA/y13t/44Kq/8hlnL/" "z7fZ/58f6vcxSNpbVUVFhV1RLNBVTsQzVYZPSwhsCAhkiIfpNMrkbO6TLf071Sfk/5ZSi/+7q6z/" "P5ns+v9mj/P/CpuI/20y+aeNGYxZoVoYGmsF3aFMBAAZlCwftnF9ke3//bU2//fXWP8/UGv731Am" "+V+DdNblSqnUYqhSTKAiYSOqJBrVqiaa+S3UNPr/gmyH/xuKXf63hnn/B8bIP0UxHfEyyeSNQKVM" "EB1AEB2twhcTLp+gIBJUoyKasEpVJHmqskh8qryovUG/ffCHHRU2q/Tk/YuB6eGPsbExa7ZkpLu1" "oLEcVDtuUCgV1w60rQzElpRUE1EVSX0BYidHiInXF4nagNhYQW60EF+ApH1ktni0A1SIITSUgVlZ" "JHYnlIsfzJjIp9xZKswL5YKBHL+coKJoRDaUSzoozxHVrygQU4JykQADAwAT5b1NHtwZugAAAABJ" "RU5ErkJggg=="; const char *cssStyles = "\r\n"; const char HTTP_PAGE_TUNNELS[] = "tunnels"; const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels"; const char HTTP_PAGE_TRANSPORTS[] = "transports"; const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations"; const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination"; const char HTTP_PAGE_I2CP_LOCAL_DESTINATION[] = "i2cp_local_destination"; const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions"; const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; const char HTTP_PAGE_COMMANDS[] = "commands"; const char HTTP_PAGE_LEASESETS[] = "leasesets"; const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit"; const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit"; const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start"; const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; const char HTTP_COMMAND_LOGLEVEL[] = "set_loglevel"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; static void ShowUptime (std::stringstream& s, int seconds) { int num; if ((num = seconds / 86400) > 0) { s << num << " days, "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { s << num << " hours, "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { s << num << " min, "; seconds -= num * 60; } s << seconds << " seconds"; } static void ShowTraffic (std::stringstream& s, uint64_t bytes) { s << std::fixed << std::setprecision(2); auto numKBytes = (double) bytes / 1024; if (numKBytes < 1024) s << numKBytes << " KiB"; else if (numKBytes < 1024 * 1024) s << numKBytes / 1024 << " MiB"; else s << numKBytes / 1024 / 1024 << " GiB"; } static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) { std::string state; switch (eState) { case i2p::tunnel::eTunnelStateBuildReplyReceived : case i2p::tunnel::eTunnelStatePending : state = "building"; break; case i2p::tunnel::eTunnelStateBuildFailed : case i2p::tunnel::eTunnelStateTestFailed : case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; case i2p::tunnel::eTunnelStateEstablished : state = "established"; break; default: state = "unknown"; break; } s << " " << state << ((explr) ? " (exploratory)" : "") << ", "; s << " " << (int) (bytes / 1024) << " KiB
\r\n"; } static void SetLogLevel (const std::string& level) { if (level == "none" || level == "error" || level == "warn" || level == "info" || level == "debug") i2p::log::Logger().SetLogLevel(level); else { LogPrint(eLogError, "HTTPServer: unknown loglevel set attempted"); return; } i2p::log::Logger().Reopen (); } static void ShowPageHead (std::stringstream& s) { s << "\r\n" "\r\n" /* TODO: Add support for locale */ " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ #if (!defined(WIN32)) " \r\n" #else " \r\n" #endif " \r\n" " Purple I2P " VERSION " Webconsole\r\n" << cssStyles << "\r\n"; s << "\r\n" "
i2pd webconsole
\r\n" "
\r\n" "
\r\n" " Main page
\r\n
\r\n" " Router commands
\r\n" " Local destinations
\r\n" " LeaseSets
\r\n" " Tunnels
\r\n" " Transit tunnels
\r\n" " Transports
\r\n" " I2P tunnels
\r\n"; if (i2p::client::context.GetSAMBridge ()) s << " SAM sessions
\r\n"; s << "
\r\n" "
"; } static void ShowPageTail (std::stringstream& s) { s << "
\r\n" "\r\n" "\r\n"; } static void ShowError(std::stringstream& s, const std::string& string) { s << "ERROR: " << string << "
\r\n"; } void ShowStatus (std::stringstream& s, bool includeHiddenContent) { s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); s << "
\r\n"; s << "Network status: "; switch (i2p::context.GetStatus ()) { case eRouterStatusOK: s << "OK"; break; case eRouterStatusTesting: s << "Testing"; break; case eRouterStatusFirewalled: s << "Firewalled"; break; case eRouterStatusError: { s << "Error"; switch (i2p::context.GetError ()) { case eRouterErrorClockSkew: s << "
Clock skew"; break; default: ; } break; } default: s << "Unknown"; } s << "
\r\n"; #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) if (auto remains = Daemon.gracefulShutdownInterval) { s << "Stopping in: "; s << remains << " seconds"; s << "
\r\n"; } #endif auto family = i2p::context.GetFamily (); if (family.length () > 0) s << "Family: " << family << "
\r\n"; s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; s << "Received: "; ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); s << " (" << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " KiB/s)
\r\n"; s << "Sent: "; ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); s << " (" << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " KiB/s)
\r\n"; s << "Transit: "; ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " KiB/s)
\r\n"; s << "Data path: " << i2p::fs::GetDataDir() << "
\r\n"; s << "
\r\n\r\n

\r\n"; if(includeHiddenContent) { s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
\r\n"; s << "Router Family: " << i2p::context.GetRouterInfo().GetProperty("family") << "
\r\n"; s << "Router Caps: " << i2p::context.GetRouterInfo().GetProperty("caps") << "
\r\n"; s << "Our external address:" << "
\r\n" ; for (const auto& address : i2p::context.GetRouterInfo().GetAddresses()) { switch (address->transportStyle) { case i2p::data::RouterInfo::eTransportNTCP: if (address->host.is_v6 ()) s << "NTCP6  "; else s << "NTCP  "; break; case i2p::data::RouterInfo::eTransportSSU: if (address->host.is_v6 ()) s << "SSU6     "; else s << "SSU     "; break; default: s << "Unknown  "; } s << address->host.to_string() << ":" << address->port << "
\r\n"; } } s << "

\r\n
\r\n"; s << "Routers: " << i2p::data::netdb.GetNumRouters () << " "; s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
\r\n"; size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n
\r\n"; s << "\r\n"; s << "\r\n"; s << "\r\n"; s << "\r\n"; s << "\r\n"; s << "\r\n"; bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); s << "\r\n"; s << "
Services
ServiceState
" << "HTTP Proxy" << "
" << "SOCKS Proxy" << "
" << "BOB" << "
" << "SAM" << "
" << "I2CP" << "
" << "I2PControl" << "
\r\n"; } void ShowLocalDestinations (std::stringstream& s) { s << "Local Destinations:
\r\n
\r\n"; for (auto& it: i2p::client::context.GetDestinations ()) { auto ident = it.second->GetIdentHash (); s << ""; s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; } auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer && !(i2cpServer->GetSessions ().empty ())) { s << "
I2CP Local Destinations:
\r\n
\r\n"; for (auto& it: i2cpServer->GetSessions ()) { auto dest = it.second->GetDestination (); if (dest) { auto ident = dest->GetIdentHash (); s << ""; s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; } } } } static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr dest) { s << "Base64:
\r\n
\r\n
\r\n"; if(dest->GetNumRemoteLeaseSets()) { s << "
\r\n\r\n

\r\n"; for(auto& it: dest->GetLeaseSets ()) s << it.second->GetIdentHash ().ToBase32 () << "
\r\n"; s << "

\r\n
\r\n"; } else s << "LeaseSets: 0
\r\n"; auto pool = dest->GetTunnelPool (); if (pool) { s << "Inbound tunnels:
\r\n"; for (auto & it : pool->GetInboundTunnels ()) { it->Print(s); if(it->LatencyIsKnown()) s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); } s << "
\r\n"; s << "Outbound tunnels:
\r\n"; for (auto & it : pool->GetOutboundTunnels ()) { it->Print(s); if(it->LatencyIsKnown()) s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); } } s << "
\r\n"; s << "Tags
Incoming: " << dest->GetNumIncomingTags () << "
"; if (!dest->GetSessions ().empty ()) { s << "
\r\n\r\n

\r\n"; for (const auto& it: dest->GetSessions ()) s << i2p::client::context.GetAddressBook ().ToAddress(it.first) << " " << it.second->GetNumOutgoingTags () << "
\r\n"; s << "

\r\n
\r\n"; } else s << "Outgoing: 0
\r\n"; s << "
\r\n"; } void ShowLocalDestination (std::stringstream& s, const std::string& b32) { s << "Local Destination:
\r\n
\r\n"; i2p::data::IdentHash ident; ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); if (dest) { ShowLeaseSetDestination (s, dest); // show streams s << "\r\n"; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << "\r\n"; for (const auto& it: dest->GetAllStreams ()) { s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << "\r\n"; } s << "
Streams
StreamIDDestinationSentReceivedOutInBufRTTWindowStatus
" << it->GetSendStreamID () << "" << i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()) << "" << it->GetNumSentBytes () << "" << it->GetNumReceivedBytes () << "" << it->GetSendQueueSize () << "" << it->GetReceiveQueueSize () << "" << it->GetSendBufferSize () << "" << it->GetRTT () << "" << it->GetWindowSize () << "" << (int)it->GetStatus () << "
"; } } static void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) { auto i2cpServer = i2p::client::context.GetI2CPServer (); if (i2cpServer) { s << "I2CP Local Destination:
\r\n
\r\n"; auto it = i2cpServer->GetSessions ().find (std::stoi (id)); if (it != i2cpServer->GetSessions ().end ()) ShowLeaseSetDestination (s, it->second->GetDestination ()); else ShowError(s, "I2CP session not found"); } else ShowError(s, "I2CP is not enabled"); } void ShowLeasesSets(std::stringstream& s) { s << "LeaseSets:
\r\n
\r\n"; int counter = 1; // for each lease set i2p::data::netdb.VisitLeaseSets( [&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) { // create copy of lease set so we extract leases i2p::data::LeaseSet ls(leaseSet->GetBuffer(), leaseSet->GetBufferLen()); s << "
\r\n"; if (!ls.IsValid()) s << "
!! Invalid !!
\r\n"; s << "
\r\n"; s << "\r\n

\r\n"; s << "Expires: " << ls.GetExpirationTime() << "
\r\n"; auto leases = ls.GetNonExpiredLeases(); s << "Non Expired Leases: " << leases.size() << "
\r\n"; for ( auto & l : leases ) { s << "Gateway: " << l->tunnelGateway.ToBase64() << "
\r\n"; s << "TunnelID: " << l->tunnelID << "
\r\n"; s << "EndDate: " << l->endDate << "
\r\n"; } s << "

\r\n
\r\n
\r\n"; } ); // end for each lease set } void ShowTunnels (std::stringstream& s) { s << "Tunnels:
\r\n
\r\n"; s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n"; auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); s << "Inbound tunnels:
\r\n"; for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { it->Print(s); if(it->LatencyIsKnown()) s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); } s << "
\r\n"; s << "Outbound tunnels:
\r\n"; for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { it->Print(s); if(it->LatencyIsKnown()) s << " ( " << it->GetMeanLatency() << "ms )"; ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); } s << "
\r\n"; } static void ShowCommands (std::stringstream& s, uint32_t token) { /* commands */ s << "Router Commands
\r\n
\r\n"; s << " Run peer test
\r\n"; //s << " Reload config
\r\n"; if (i2p::context.AcceptsTunnels ()) s << " Decline transit tunnels
\r\n"; else s << " Accept transit tunnels
\r\n"; #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) if (Daemon.gracefulShutdownInterval) s << " Cancel graceful shutdown
"; else s << " Start graceful shutdown
\r\n"; #elif defined(WIN32_APP) if (i2p::util::DaemonWin32::Instance().isGraceful) s << " Cancel graceful shutdown
"; else s << " Graceful shutdown
\r\n"; #endif s << " Force shutdown
\r\n"; s << "
\r\nLogging level
\r\n"; s << " [none] "; s << " [error] "; s << " [warn] "; s << " [info] "; s << " [debug]
\r\n"; } void ShowTransitTunnels (std::stringstream& s) { s << "Transit tunnels:
\r\n
\r\n"; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { if (std::dynamic_pointer_cast(it)) s << it->GetTunnelID () << " ⇒ "; else if (std::dynamic_pointer_cast(it)) s << " ⇒ " << it->GetTunnelID (); else s << " ⇒ " << it->GetTunnelID () << " ⇒ "; s << " " << it->GetNumTransmittedBytes () << "
\r\n"; } } void ShowTransports (std::stringstream& s) { s << "Transports:
\r\n
\r\n"; auto ntcpServer = i2p::transport::transports.GetNTCPServer (); if (ntcpServer) { auto sessions = ntcpServer->GetNTCPSessions (); if (!sessions.empty ()) { std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; for (const auto& it: sessions ) { if (it.second && it.second->IsEstablished () && !it.second->GetSocket ().remote_endpoint ().address ().is_v6 ()) { // incoming connection doesn't have remote RI if (it.second->IsOutgoing ()) tmp_s << " ⇒ "; tmp_s << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " << it.second->GetSocket ().remote_endpoint().address ().to_string (); if (!it.second->IsOutgoing ()) tmp_s << " ⇒ "; tmp_s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; tmp_s << "
\r\n" << std::endl; cnt++; } if (it.second && it.second->IsEstablished () && it.second->GetSocket ().remote_endpoint ().address ().is_v6 ()) { if (it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; tmp_s6 << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << ": " << "[" << it.second->GetSocket ().remote_endpoint().address ().to_string () << "]"; if (!it.second->IsOutgoing ()) tmp_s6 << " ⇒ "; tmp_s6 << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; tmp_s6 << "
\r\n" << std::endl; cnt6++; } } if (!tmp_s.str ().empty ()) { s << "
\r\n\r\n

"; s << tmp_s.str () << "

\r\n
\r\n"; } if (!tmp_s6.str ().empty ()) { s << "
\r\n\r\n

"; s << tmp_s6.str () << "

\r\n
\r\n"; } } } auto ssuServer = i2p::transport::transports.GetSSUServer (); if (ssuServer) { auto sessions = ssuServer->GetSessions (); if (!sessions.empty ()) { s << "
\r\n\r\n

"; for (const auto& it: sessions) { auto endpoint = it.second->GetRemoteEndpoint (); if (it.second->IsOutgoing ()) s << " ⇒ "; s << endpoint.address ().to_string () << ":" << endpoint.port (); if (!it.second->IsOutgoing ()) s << " ⇒ "; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; if (it.second->GetRelayTag ()) s << " [itag:" << it.second->GetRelayTag () << "]"; s << "
\r\n" << std::endl; } s << "

\r\n
\r\n"; } auto sessions6 = ssuServer->GetSessionsV6 (); if (!sessions6.empty ()) { s << "
\r\n\r\n

"; for (const auto& it: sessions6) { auto endpoint = it.second->GetRemoteEndpoint (); if (it.second->IsOutgoing ()) s << " ⇒ "; s << "[" << endpoint.address ().to_string () << "]:" << endpoint.port (); if (!it.second->IsOutgoing ()) s << " ⇒ "; s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]"; if (it.second->GetRelayTag ()) s << " [itag:" << it.second->GetRelayTag () << "]"; s << "
\r\n" << std::endl; } s << "

\r\n
\r\n"; } } } void ShowSAMSessions (std::stringstream& s) { auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { ShowError(s, "SAM disabled"); return; } s << "SAM Sessions:
\r\n
\r\n"; for (auto& it: sam->GetSessions ()) { auto& name = it.second->localDestination->GetNickname (); s << ""; s << name << " (" << it.first << ")
\r\n" << std::endl; } } static void ShowSAMSession (std::stringstream& s, const std::string& id) { s << "SAM Session:
\r\n
\r\n"; auto sam = i2p::client::context.GetSAMBridge (); if (!sam) { ShowError(s, "SAM disabled"); return; } auto session = sam->FindSession (id); if (!session) { ShowError(s, "SAM session not found"); return; } auto& ident = session->localDestination->GetIdentHash(); s << ""; s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n"; s << "
\r\n"; s << "Streams:
\r\n"; for (const auto& it: session->ListSockets()) { switch (it->GetSocketType ()) { case i2p::client::eSAMSocketTypeSession : s << "session"; break; case i2p::client::eSAMSocketTypeStream : s << "stream"; break; case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break; default: s << "unknown"; break; } s << " [" << it->GetSocket ().remote_endpoint() << "]"; s << "
\r\n"; } } void ShowI2PTunnels (std::stringstream& s) { s << "Client Tunnels:
\r\n
\r\n"; for (auto& it: i2p::client::context.GetClientTunnels ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << ""; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; } auto httpProxy = i2p::client::context.GetHttpProxy (); if (httpProxy) { auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); s << ""; s << "HTTP Proxy" << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; } auto socksProxy = i2p::client::context.GetSocksProxy (); if (socksProxy) { auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); s << ""; s << "SOCKS Proxy" << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; } auto& serverTunnels = i2p::client::context.GetServerTunnels (); if (!serverTunnels.empty ()) { s << "
\r\nServer Tunnels:
\r\n
\r\n"; for (auto& it: serverTunnels) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << ""; s << it.second->GetName () << " ⇒ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << ":" << it.second->GetLocalPort (); s << "
\r\n"<< std::endl; } } auto& clientForwards = i2p::client::context.GetClientForwards (); if (!clientForwards.empty ()) { s << "
\r\nClient Forwards:
\r\n
\r\n"; for (auto& it: clientForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << ""; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; } } auto& serverForwards = i2p::client::context.GetServerForwards (); if (!serverForwards.empty ()) { s << "
\r\nServer Forwards:
\r\n
\r\n"; for (auto& it: serverForwards) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << ""; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; } } } HTTPConnection::HTTPConnection (std::shared_ptr socket): m_Socket (socket), m_Timer (socket->get_io_service ()), m_BufferLen (0) { /* cache options */ i2p::config::GetOption("http.auth", needAuth); i2p::config::GetOption("http.user", user); i2p::config::GetOption("http.pass", pass); } void HTTPConnection::Receive () { m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), std::bind(&HTTPConnection::HandleReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { if (ecode != boost::asio::error::operation_aborted) Terminate (ecode); return; } m_Buffer[bytes_transferred] = '\0'; m_BufferLen = bytes_transferred; RunRequest(); Receive (); } void HTTPConnection::RunRequest () { HTTPReq request; int ret = request.parse(m_Buffer); if (ret < 0) { m_Buffer[0] = '\0'; m_BufferLen = 0; return; /* error */ } if (ret == 0) return; /* need more data */ HandleRequest (request); } void HTTPConnection::Terminate (const boost::system::error_code& ecode) { if (ecode == boost::asio::error::operation_aborted) return; boost::system::error_code ignored_ec; m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); m_Socket->close (); } bool HTTPConnection::CheckAuth (const HTTPReq & req) { /* method #1: http://user:pass@127.0.0.1:7070/ */ if (req.uri.find('@') != std::string::npos) { URL url; if (url.parse(req.uri) && url.user == user && url.pass == pass) return true; } /* method #2: 'Authorization' header sent */ auto provided = req.GetHeader ("Authorization"); if (provided.length () > 0) { bool result = false; std::string expected = user + ":" + pass; size_t b64_sz = i2p::data::Base64EncodingBufferSize(expected.length()) + 1; char * b64_creds = new char[b64_sz]; std::size_t len = 0; len = i2p::data::ByteStreamToBase64((unsigned char *)expected.c_str(), expected.length(), b64_creds, b64_sz); /* if we decoded properly then check credentials */ if(len) { b64_creds[len] = '\0'; expected = "Basic "; expected += b64_creds; result = expected == provided; } delete [] b64_creds; return result; } LogPrint(eLogWarning, "HTTPServer: auth failure from ", m_Socket->remote_endpoint().address ()); return false; } void HTTPConnection::HandleRequest (const HTTPReq & req) { std::stringstream s; std::string content; HTTPRes res; LogPrint(eLogDebug, "HTTPServer: request: ", req.uri); if (needAuth && !CheckAuth(req)) { res.code = 401; res.add_header("WWW-Authenticate", "Basic realm=\"WebAdmin\""); SendReply(res, content); return; } // Html5 head start ShowPageHead (s); if (req.uri.find("page=") != std::string::npos) { HandlePage (req, res, s); } else if (req.uri.find("cmd=") != std::string::npos) { HandleCommand (req, res, s); } else { ShowStatus (s, true); res.add_header("Refresh", "10"); } ShowPageTail (s); res.code = 200; content = s.str (); SendReply (res, content); } std::map HTTPConnection::m_Tokens; void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s) { std::map params; std::string page(""); URL url; url.parse(req.uri); url.parse_query(params); page = params["page"]; if (page == HTTP_PAGE_TRANSPORTS) ShowTransports (s); else if (page == HTTP_PAGE_TUNNELS) ShowTunnels (s); else if (page == HTTP_PAGE_COMMANDS) { uint32_t token; RAND_bytes ((uint8_t *)&token, 4); token &= 0x7FFFFFFF; // clear first bit auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_Tokens.begin (); it != m_Tokens.end (); ) { if (ts > it->second + TOKEN_EXPIRATION_TIMEOUT) it = m_Tokens.erase (it); else ++it; } m_Tokens[token] = ts; ShowCommands (s, token); } else if (page == HTTP_PAGE_TRANSIT_TUNNELS) ShowTransitTunnels (s); else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) ShowLocalDestinations (s); else if (page == HTTP_PAGE_LOCAL_DESTINATION) ShowLocalDestination (s, params["b32"]); else if (page == HTTP_PAGE_I2CP_LOCAL_DESTINATION) ShowI2CPLocalDestination (s, params["i2cp_id"]); else if (page == HTTP_PAGE_SAM_SESSIONS) ShowSAMSessions (s); else if (page == HTTP_PAGE_SAM_SESSION) ShowSAMSession (s, params["sam_id"]); else if (page == HTTP_PAGE_I2P_TUNNELS) ShowI2PTunnels (s); else if (page == HTTP_PAGE_LEASESETS) ShowLeasesSets(s); else { res.code = 400; ShowError(s, "Unknown page: " + page); return; } } void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s) { std::map params; URL url; url.parse(req.uri); url.parse_query(params); std::string token = params["token"]; if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) { ShowError(s, "Invalid token"); return; } std::string cmd = params["cmd"]; if (cmd == HTTP_COMMAND_RUN_PEER_TEST) i2p::transport::transports.PeerTest (); else if (cmd == HTTP_COMMAND_RELOAD_CONFIG) i2p::client::context.ReloadConfig (); else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT) i2p::context.SetAcceptsTunnels (true); else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT) i2p::context.SetAcceptsTunnels (false); else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { i2p::context.SetAcceptsTunnels (false); #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) Daemon.gracefulShutdownInterval = 10*60; #elif defined(WIN32_APP) i2p::win32::GracefulShutdown (); #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { i2p::context.SetAcceptsTunnels (true); #if (!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) Daemon.gracefulShutdownInterval = 0; #elif defined(WIN32_APP) i2p::win32::StopGracefulShutdown (); #endif } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { #ifndef WIN32_APP Daemon.running = false; #else i2p::win32::StopWin32App (); #endif } else if (cmd == HTTP_COMMAND_LOGLEVEL){ std::string level = params["level"]; SetLogLevel (level); } else { res.code = 400; ShowError(s, "Unknown command: " + cmd); return; } s << "SUCCESS: Command accepted

\r\n"; s << "Back to commands list
\r\n"; s << "

You will be redirected in 5 seconds"; res.add_header("Refresh", "5; url=/?page=commands"); } void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) { reply.add_header("X-Frame-Options", "SAMEORIGIN"); reply.add_header("Content-Type", "text/html"); reply.body = content; m_SendBuffer = reply.to_string(); boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); } HTTPServer::HTTPServer (const std::string& address, int port): m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)) { } HTTPServer::~HTTPServer () { Stop (); } void HTTPServer::Start () { bool needAuth; i2p::config::GetOption("http.auth", needAuth); std::string user; i2p::config::GetOption("http.user", user); std::string pass; i2p::config::GetOption("http.pass", pass); /* generate pass if needed */ if (needAuth && pass == "") { uint8_t random[16]; char alnum[] = "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; pass.resize(sizeof(random)); RAND_bytes(random, sizeof(random)); for (size_t i = 0; i < sizeof(random); i++) { pass[i] = alnum[random[i] % (sizeof(alnum) - 1)]; } i2p::config::SetOption("http.pass", pass); LogPrint(eLogInfo, "HTTPServer: password set to ", pass); } m_IsRunning = true; m_Thread = std::unique_ptr(new std::thread (std::bind (&HTTPServer::Run, this))); m_Acceptor.listen (); Accept (); } void HTTPServer::Stop () { m_IsRunning = false; m_Acceptor.close(); m_Service.stop (); if (m_Thread) { m_Thread->join (); m_Thread = nullptr; } } void HTTPServer::Run () { while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, "HTTPServer: runtime exception: ", ex.what ()); } } } void HTTPServer::Accept () { auto newSocket = std::make_shared (m_Service); m_Acceptor.async_accept (*newSocket, boost::bind (&HTTPServer::HandleAccept, this, boost::asio::placeholders::error, newSocket)); } void HTTPServer::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr newSocket) { if (ecode) { if(newSocket) newSocket->close(); LogPrint(eLogError, "HTTP Server: error handling accept ", ecode.message()); if(ecode != boost::asio::error::operation_aborted) Accept(); return; } CreateConnection(newSocket); Accept (); } void HTTPServer::CreateConnection(std::shared_ptr newSocket) { auto conn = std::make_shared (newSocket); conn->Receive (); } } // http } // i2p i2pd-2.17.0/daemon/HTTPServer.h000066400000000000000000000051401321131324000160070ustar00rootroot00000000000000#ifndef HTTP_SERVER_H__ #define HTTP_SERVER_H__ #include #include #include #include #include #include #include #include "HTTP.h" namespace i2p { namespace http { const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192; const int TOKEN_EXPIRATION_TIMEOUT = 30; // in seconds class HTTPConnection: public std::enable_shared_from_this { public: HTTPConnection (std::shared_ptr socket); void Receive (); private: void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void Terminate (const boost::system::error_code& ecode); void RunRequest (); bool CheckAuth (const HTTPReq & req); void HandleRequest (const HTTPReq & req); void HandlePage (const HTTPReq & req, HTTPRes & res, std::stringstream& data); void HandleCommand (const HTTPReq & req, HTTPRes & res, std::stringstream& data); void SendReply (HTTPRes & res, std::string & content); private: std::shared_ptr m_Socket; boost::asio::deadline_timer m_Timer; char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; size_t m_BufferLen; std::string m_SendBuffer; bool needAuth; std::string user; std::string pass; static std::map m_Tokens; // token->timestamp in seconds }; class HTTPServer { public: HTTPServer (const std::string& address, int port); ~HTTPServer (); void Start (); void Stop (); private: void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr newSocket); void CreateConnection(std::shared_ptr newSocket); private: bool m_IsRunning; std::unique_ptr m_Thread; boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; boost::asio::ip::tcp::acceptor m_Acceptor; }; //all the below functions are also used by Qt GUI, see mainwindow.cpp -> getStatusPageHtml void ShowStatus (std::stringstream& s, bool includeHiddenContent); void ShowLocalDestinations (std::stringstream& s); void ShowLeasesSets(std::stringstream& s); void ShowTunnels (std::stringstream& s); void ShowTransitTunnels (std::stringstream& s); void ShowTransports (std::stringstream& s); void ShowSAMSessions (std::stringstream& s); void ShowI2PTunnels (std::stringstream& s); void ShowLocalDestination (std::stringstream& s, const std::string& b32); } // http } // i2p #endif /* HTTP_SERVER_H__ */ i2pd-2.17.0/daemon/I2PControl.cpp000066400000000000000000000511531321131324000163340ustar00rootroot00000000000000#include #include #include #include #include #include #include #include // There is bug in boost 1.49 with gcc 4.7 coming with Debian Wheezy #define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) #if !GCC47_BOOST149 #include #endif #include "Crypto.h" #include "FS.h" #include "Log.h" #include "Config.h" #include "NetDb.hpp" #include "RouterContext.h" #include "Daemon.h" #include "Tunnel.h" #include "Timestamp.h" #include "Transports.h" #include "version.h" #include "util.h" #include "ClientContext.h" #include "I2PControl.h" namespace i2p { namespace client { I2PControlService::I2PControlService (const std::string& address, int port): m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), m_SSLContext (m_Service, boost::asio::ssl::context::sslv23), m_ShutdownTimer (m_Service) { i2p::config::GetOption("i2pcontrol.password", m_Password); // certificate / keys std::string i2pcp_crt; i2p::config::GetOption("i2pcontrol.cert", i2pcp_crt); std::string i2pcp_key; i2p::config::GetOption("i2pcontrol.key", i2pcp_key); if (i2pcp_crt.at(0) != '/') i2pcp_crt = i2p::fs::DataDirPath(i2pcp_crt); if (i2pcp_key.at(0) != '/') i2pcp_key = i2p::fs::DataDirPath(i2pcp_key); if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) { LogPrint (eLogInfo, "I2PControl: creating new certificate for control connection"); CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str()); } else { LogPrint(eLogDebug, "I2PControl: using cert from ", i2pcp_crt); } m_SSLContext.set_options (boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); m_SSLContext.use_certificate_file (i2pcp_crt, boost::asio::ssl::context::pem); m_SSLContext.use_private_key_file (i2pcp_key, boost::asio::ssl::context::pem); // handlers m_MethodHandlers["Authenticate"] = &I2PControlService::AuthenticateHandler; m_MethodHandlers["Echo"] = &I2PControlService::EchoHandler; m_MethodHandlers["I2PControl"] = &I2PControlService::I2PControlHandler; m_MethodHandlers["RouterInfo"] = &I2PControlService::RouterInfoHandler; m_MethodHandlers["RouterManager"] = &I2PControlService::RouterManagerHandler; m_MethodHandlers["NetworkSetting"] = &I2PControlService::NetworkSettingHandler; // I2PControl m_I2PControlHandlers["i2pcontrol.password"] = &I2PControlService::PasswordHandler; // RouterInfo m_RouterInfoHandlers["i2p.router.uptime"] = &I2PControlService::UptimeHandler; m_RouterInfoHandlers["i2p.router.version"] = &I2PControlService::VersionHandler; m_RouterInfoHandlers["i2p.router.status"] = &I2PControlService::StatusHandler; m_RouterInfoHandlers["i2p.router.netdb.knownpeers"] = &I2PControlService::NetDbKnownPeersHandler; m_RouterInfoHandlers["i2p.router.netdb.activepeers"] = &I2PControlService::NetDbActivePeersHandler; m_RouterInfoHandlers["i2p.router.net.bw.inbound.1s"] = &I2PControlService::InboundBandwidth1S; m_RouterInfoHandlers["i2p.router.net.bw.outbound.1s"] = &I2PControlService::OutboundBandwidth1S; m_RouterInfoHandlers["i2p.router.net.status"] = &I2PControlService::NetStatusHandler; m_RouterInfoHandlers["i2p.router.net.tunnels.participating"] = &I2PControlService::TunnelsParticipatingHandler; m_RouterInfoHandlers["i2p.router.net.tunnels.successrate"] = &I2PControlService::TunnelsSuccessRateHandler; m_RouterInfoHandlers["i2p.router.net.total.received.bytes"] = &I2PControlService::NetTotalReceivedBytes; m_RouterInfoHandlers["i2p.router.net.total.sent.bytes"] = &I2PControlService::NetTotalSentBytes; // RouterManager m_RouterManagerHandlers["Reseed"] = &I2PControlService::ReseedHandler; m_RouterManagerHandlers["Shutdown"] = &I2PControlService::ShutdownHandler; m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler; // NetworkSetting m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlService::InboundBandwidthLimit; m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlService::OutboundBandwidthLimit; } I2PControlService::~I2PControlService () { Stop (); } void I2PControlService::Start () { if (!m_IsRunning) { Accept (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&I2PControlService::Run, this)); } } void I2PControlService::Stop () { if (m_IsRunning) { m_IsRunning = false; m_Acceptor.cancel (); m_Service.stop (); if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = nullptr; } } } void I2PControlService::Run () { while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, "I2PControl: runtime exception: ", ex.what ()); } } } void I2PControlService::Accept () { auto newSocket = std::make_shared (m_Service, m_SSLContext); m_Acceptor.async_accept (newSocket->lowest_layer(), std::bind (&I2PControlService::HandleAccept, this, std::placeholders::_1, newSocket)); } void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) { if (ecode != boost::asio::error::operation_aborted) Accept (); if (ecode) { LogPrint (eLogError, "I2PControl: accept error: ", ecode.message ()); return; } LogPrint (eLogDebug, "I2PControl: new request from ", socket->lowest_layer ().remote_endpoint ()); Handshake (socket); } void I2PControlService::Handshake (std::shared_ptr socket) { socket->async_handshake(boost::asio::ssl::stream_base::server, std::bind( &I2PControlService::HandleHandshake, this, std::placeholders::_1, socket)); } void I2PControlService::HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket) { if (ecode) { LogPrint (eLogError, "I2PControl: handshake error: ", ecode.message ()); return; } //std::this_thread::sleep_for (std::chrono::milliseconds(5)); ReadRequest (socket); } void I2PControlService::ReadRequest (std::shared_ptr socket) { auto request = std::make_shared(); socket->async_read_some ( #if defined(BOOST_ASIO_HAS_STD_ARRAY) boost::asio::buffer (*request), #else boost::asio::buffer (request->data (), request->size ()), #endif std::bind(&I2PControlService::HandleRequestReceived, this, std::placeholders::_1, std::placeholders::_2, socket, request)); } void I2PControlService::HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf) { if (ecode) { LogPrint (eLogError, "I2PControl: read error: ", ecode.message ()); return; } else { bool isHtml = !memcmp (buf->data (), "POST", 4); try { std::stringstream ss; ss.write (buf->data (), bytes_transferred); if (isHtml) { std::string header; size_t contentLength = 0; while (!ss.eof () && header != "\r") { std::getline(ss, header); auto colon = header.find (':'); if (colon != std::string::npos && header.substr (0, colon) == "Content-Length") contentLength = std::stoi (header.substr (colon + 1)); } if (ss.eof ()) { LogPrint (eLogError, "I2PControl: malformed request, HTTP header expected"); return; // TODO: } std::streamoff rem = contentLength + ss.tellg () - bytes_transferred; // more bytes to read if (rem > 0) { bytes_transferred = boost::asio::read (*socket, boost::asio::buffer (buf->data (), rem)); ss.write (buf->data (), bytes_transferred); } } std::ostringstream response; #if GCC47_BOOST149 LogPrint (eLogError, "I2PControl: json_read is not supported due bug in boost 1.49 with gcc 4.7"); response << "{\"id\":null,\"error\":"; response << "{\"code\":-32603,\"message\":\"JSON requests is not supported with this version of boost\"},"; response << "\"jsonrpc\":\"2.0\"}"; #else boost::property_tree::ptree pt; boost::property_tree::read_json (ss, pt); std::string id = pt.get("id"); std::string method = pt.get("method"); auto it = m_MethodHandlers.find (method); if (it != m_MethodHandlers.end ()) { response << "{\"id\":" << id << ",\"result\":{"; (this->*(it->second))(pt.get_child ("params"), response); response << "},\"jsonrpc\":\"2.0\"}"; } else { LogPrint (eLogWarning, "I2PControl: unknown method ", method); response << "{\"id\":null,\"error\":"; response << "{\"code\":-32601,\"message\":\"Method not found\"},"; response << "\"jsonrpc\":\"2.0\"}"; } #endif SendResponse (socket, buf, response, isHtml); } catch (std::exception& ex) { LogPrint (eLogError, "I2PControl: exception when handle request: ", ex.what ()); std::ostringstream response; response << "{\"id\":null,\"error\":"; response << "{\"code\":-32700,\"message\":\"" << ex.what () << "\"},"; response << "\"jsonrpc\":\"2.0\"}"; SendResponse (socket, buf, response, isHtml); } catch (...) { LogPrint (eLogError, "I2PControl: handle request unknown exception"); } } } void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, int value) const { ss << "\"" << name << "\":" << value; } void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const { ss << "\"" << name << "\":"; if (value.length () > 0) ss << "\"" << value << "\""; else ss << "null"; } void I2PControlService::InsertParam (std::ostringstream& ss, const std::string& name, double value) const { ss << "\"" << name << "\":" << std::fixed << std::setprecision(2) << value; } void I2PControlService::SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml) { size_t len = response.str ().length (), offset = 0; if (isHtml) { std::ostringstream header; header << "HTTP/1.1 200 OK\r\n"; header << "Connection: close\r\n"; header << "Content-Length: " << boost::lexical_cast(len) << "\r\n"; header << "Content-Type: application/json\r\n"; header << "Date: "; auto facet = new boost::local_time::local_time_facet ("%a, %d %b %Y %H:%M:%S GMT"); header.imbue(std::locale (header.getloc(), facet)); header << boost::posix_time::second_clock::local_time() << "\r\n"; header << "\r\n"; offset = header.str ().size (); memcpy (buf->data (), header.str ().c_str (), offset); } memcpy (buf->data () + offset, response.str ().c_str (), len); boost::asio::async_write (*socket, boost::asio::buffer (buf->data (), offset + len), boost::asio::transfer_all (), std::bind(&I2PControlService::HandleResponseSent, this, std::placeholders::_1, std::placeholders::_2, socket, buf)); } void I2PControlService::HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf) { if (ecode) { LogPrint (eLogError, "I2PControl: write error: ", ecode.message ()); } } // handlers void I2PControlService::AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { int api = params.get ("API"); auto password = params.get ("Password"); LogPrint (eLogDebug, "I2PControl: Authenticate API=", api, " Password=", password); if (password != m_Password) { LogPrint (eLogError, "I2PControl: Authenticate - Invalid password: ", password); return; } InsertParam (results, "API", api); results << ","; std::string token = boost::lexical_cast(i2p::util::GetSecondsSinceEpoch ()); m_Tokens.insert (token); InsertParam (results, "Token", token); } void I2PControlService::EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { auto echo = params.get ("Echo"); LogPrint (eLogDebug, "I2PControl Echo Echo=", echo); InsertParam (results, "Result", echo); } // I2PControl void I2PControlService::I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { for (auto& it: params) { LogPrint (eLogDebug, "I2PControl: I2PControl request: ", it.first); auto it1 = m_I2PControlHandlers.find (it.first); if (it1 != m_I2PControlHandlers.end ()) { (this->*(it1->second))(it.second.data ()); InsertParam (results, it.first, ""); } else LogPrint (eLogError, "I2PControl: I2PControl unknown request: ", it.first); } } void I2PControlService::PasswordHandler (const std::string& value) { LogPrint (eLogWarning, "I2PControl: new password=", value, ", to make it persistent you should update your config!"); m_Password = value; m_Tokens.clear (); } // RouterInfo void I2PControlService::RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { for (auto it = params.begin (); it != params.end (); it++) { LogPrint (eLogDebug, "I2PControl: RouterInfo request: ", it->first); auto it1 = m_RouterInfoHandlers.find (it->first); if (it1 != m_RouterInfoHandlers.end ()) { if (it != params.begin ()) results << ","; (this->*(it1->second))(results); } else LogPrint (eLogError, "I2PControl: RouterInfo unknown request ", it->first); } } void I2PControlService::UptimeHandler (std::ostringstream& results) { InsertParam (results, "i2p.router.uptime", (int)i2p::context.GetUptime ()*1000); } void I2PControlService::VersionHandler (std::ostringstream& results) { InsertParam (results, "i2p.router.version", VERSION); } void I2PControlService::StatusHandler (std::ostringstream& results) { auto dest = i2p::client::context.GetSharedLocalDestination (); InsertParam (results, "i2p.router.status", (dest && dest->IsReady ()) ? "1" : "0"); } void I2PControlService::NetDbKnownPeersHandler (std::ostringstream& results) { InsertParam (results, "i2p.router.netdb.knownpeers", i2p::data::netdb.GetNumRouters ()); } void I2PControlService::NetDbActivePeersHandler (std::ostringstream& results) { InsertParam (results, "i2p.router.netdb.activepeers", (int)i2p::transport::transports.GetPeers ().size ()); } void I2PControlService::NetStatusHandler (std::ostringstream& results) { InsertParam (results, "i2p.router.net.status", (int)i2p::context.GetStatus ()); } void I2PControlService::TunnelsParticipatingHandler (std::ostringstream& results) { int transit = i2p::tunnel::tunnels.GetTransitTunnels ().size (); InsertParam (results, "i2p.router.net.tunnels.participating", transit); } void I2PControlService::TunnelsSuccessRateHandler (std::ostringstream& results) { int rate = i2p::tunnel::tunnels.GetTunnelCreationSuccessRate (); InsertParam (results, "i2p.router.net.tunnels.successrate", rate); } void I2PControlService::InboundBandwidth1S (std::ostringstream& results) { double bw = i2p::transport::transports.GetInBandwidth (); InsertParam (results, "i2p.router.net.bw.inbound.1s", bw); } void I2PControlService::OutboundBandwidth1S (std::ostringstream& results) { double bw = i2p::transport::transports.GetOutBandwidth (); InsertParam (results, "i2p.router.net.bw.outbound.1s", bw); } void I2PControlService::NetTotalReceivedBytes (std::ostringstream& results) { InsertParam (results, "i2p.router.net.total.received.bytes", (double)i2p::transport::transports.GetTotalReceivedBytes ()); } void I2PControlService::NetTotalSentBytes (std::ostringstream& results) { InsertParam (results, "i2p.router.net.total.sent.bytes", (double)i2p::transport::transports.GetTotalSentBytes ()); } // RouterManager void I2PControlService::RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { for (auto it = params.begin (); it != params.end (); it++) { if (it != params.begin ()) results << ","; LogPrint (eLogDebug, "I2PControl: RouterManager request: ", it->first); auto it1 = m_RouterManagerHandlers.find (it->first); if (it1 != m_RouterManagerHandlers.end ()) { (this->*(it1->second))(results); } else LogPrint (eLogError, "I2PControl: RouterManager unknown request: ", it->first); } } void I2PControlService::ShutdownHandler (std::ostringstream& results) { LogPrint (eLogInfo, "I2PControl: Shutdown requested"); InsertParam (results, "Shutdown", ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(1)); // 1 second to make sure response has been sent m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) { Daemon.running = 0; }); } void I2PControlService::ShutdownGracefulHandler (std::ostringstream& results) { i2p::context.SetAcceptsTunnels (false); int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout (); LogPrint (eLogInfo, "I2PControl: Graceful shutdown requested, ", timeout, " seconds remains"); InsertParam (results, "ShutdownGraceful", ""); m_ShutdownTimer.expires_from_now (boost::posix_time::seconds(timeout + 1)); // + 1 second m_ShutdownTimer.async_wait ( [](const boost::system::error_code& ecode) { Daemon.running = 0; }); } void I2PControlService::ReseedHandler (std::ostringstream& results) { LogPrint (eLogInfo, "I2PControl: Reseed requested"); InsertParam (results, "Reseed", ""); i2p::data::netdb.Reseed (); } // network setting void I2PControlService::NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results) { for (auto it = params.begin (); it != params.end (); it++) { LogPrint (eLogDebug, "I2PControl: NetworkSetting request: ", it->first); auto it1 = m_NetworkSettingHandlers.find (it->first); if (it1 != m_NetworkSettingHandlers.end ()) { if (it != params.begin ()) results << ","; (this->*(it1->second))(it->second.data (), results); } else LogPrint (eLogError, "I2PControl: NetworkSetting unknown request: ", it->first); } } void I2PControlService::InboundBandwidthLimit (const std::string& value, std::ostringstream& results) { if (value != "null") i2p::context.SetBandwidth (std::atoi(value.c_str())); int bw = i2p::context.GetBandwidthLimit(); InsertParam (results, "i2p.router.net.bw.in", bw); } void I2PControlService::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results) { if (value != "null") i2p::context.SetBandwidth (std::atoi(value.c_str())); int bw = i2p::context.GetBandwidthLimit(); InsertParam (results, "i2p.router.net.bw.out", bw); } // certificate void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path) { FILE *f = NULL; EVP_PKEY * pkey = EVP_PKEY_new (); RSA * rsa = RSA_new (); BIGNUM * e = BN_dup (i2p::crypto::GetRSAE ()); RSA_generate_key_ex (rsa, 4096, e, NULL); BN_free (e); if (rsa) { EVP_PKEY_assign_RSA (pkey, rsa); X509 * x509 = X509_new (); ASN1_INTEGER_set (X509_get_serialNumber (x509), 1); X509_gmtime_adj (X509_get_notBefore (x509), 0); X509_gmtime_adj (X509_get_notAfter (x509), I2P_CONTROL_CERTIFICATE_VALIDITY*24*60*60); // expiration X509_set_pubkey (x509, pkey); // public key X509_NAME * name = X509_get_subject_name (x509); X509_NAME_add_entry_by_txt (name, "C", MBSTRING_ASC, (unsigned char *)"A1", -1, -1, 0); // country (Anonymous proxy) X509_NAME_add_entry_by_txt (name, "O", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_ORGANIZATION, -1, -1, 0); // organization X509_NAME_add_entry_by_txt (name, "CN", MBSTRING_ASC, (unsigned char *)I2P_CONTROL_CERTIFICATE_COMMON_NAME, -1, -1, 0); // common name X509_set_issuer_name (x509, name); // set issuer to ourselves X509_sign (x509, pkey, EVP_sha1 ()); // sign // save cert if ((f = fopen (crt_path, "wb")) != NULL) { LogPrint (eLogInfo, "I2PControl: saving new cert to ", crt_path); PEM_write_X509 (f, x509); fclose (f); } else { LogPrint (eLogError, "I2PControl: can't write cert: ", strerror(errno)); } // save key if ((f = fopen (key_path, "wb")) != NULL) { LogPrint (eLogInfo, "I2PControl: saving cert key to ", key_path); PEM_write_PrivateKey (f, pkey, NULL, NULL, 0, NULL, NULL); fclose (f); } else { LogPrint (eLogError, "I2PControl: can't write key: ", strerror(errno)); } X509_free (x509); } else { LogPrint (eLogError, "I2PControl: can't create RSA key for certificate"); } EVP_PKEY_free (pkey); } } } i2pd-2.17.0/daemon/I2PControl.h000066400000000000000000000120231321131324000157720ustar00rootroot00000000000000#ifndef I2P_CONTROL_H__ #define I2P_CONTROL_H__ #include #include #include #include #include #include #include #include #include #include #include namespace i2p { namespace client { const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024; typedef std::array I2PControlBuffer; const long I2P_CONTROL_CERTIFICATE_VALIDITY = 365*10; // 10 years const char I2P_CONTROL_CERTIFICATE_COMMON_NAME[] = "i2pd.i2pcontrol"; const char I2P_CONTROL_CERTIFICATE_ORGANIZATION[] = "Purple I2P"; class I2PControlService { typedef boost::asio::ssl::stream ssl_socket; public: I2PControlService (const std::string& address, int port); ~I2PControlService (); void Start (); void Stop (); private: void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); void Handshake (std::shared_ptr socket); void HandleHandshake (const boost::system::error_code& ecode, std::shared_ptr socket); void ReadRequest (std::shared_ptr socket); void HandleRequestReceived (const boost::system::error_code& ecode, size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); void SendResponse (std::shared_ptr socket, std::shared_ptr buf, std::ostringstream& response, bool isHtml); void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr socket, std::shared_ptr buf); void CreateCertificate (const char *crt_path, const char *key_path); private: void InsertParam (std::ostringstream& ss, const std::string& name, int value) const; void InsertParam (std::ostringstream& ss, const std::string& name, double value) const; void InsertParam (std::ostringstream& ss, const std::string& name, const std::string& value) const; // methods typedef void (I2PControlService::*MethodHandler)(const boost::property_tree::ptree& params, std::ostringstream& results); void AuthenticateHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void EchoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void I2PControlHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void RouterInfoHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void RouterManagerHandler (const boost::property_tree::ptree& params, std::ostringstream& results); void NetworkSettingHandler (const boost::property_tree::ptree& params, std::ostringstream& results); // I2PControl typedef void (I2PControlService::*I2PControlRequestHandler)(const std::string& value); void PasswordHandler (const std::string& value); // RouterInfo typedef void (I2PControlService::*RouterInfoRequestHandler)(std::ostringstream& results); void UptimeHandler (std::ostringstream& results); void VersionHandler (std::ostringstream& results); void StatusHandler (std::ostringstream& results); void NetDbKnownPeersHandler (std::ostringstream& results); void NetDbActivePeersHandler (std::ostringstream& results); void NetStatusHandler (std::ostringstream& results); void TunnelsParticipatingHandler (std::ostringstream& results); void TunnelsSuccessRateHandler (std::ostringstream& results); void InboundBandwidth1S (std::ostringstream& results); void OutboundBandwidth1S (std::ostringstream& results); void NetTotalReceivedBytes (std::ostringstream& results); void NetTotalSentBytes (std::ostringstream& results); // RouterManager typedef void (I2PControlService::*RouterManagerRequestHandler)(std::ostringstream& results); void ShutdownHandler (std::ostringstream& results); void ShutdownGracefulHandler (std::ostringstream& results); void ReseedHandler (std::ostringstream& results); // NetworkSetting typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); void InboundBandwidthLimit (const std::string& value, std::ostringstream& results); void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results); private: std::string m_Password; bool m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::ssl::context m_SSLContext; boost::asio::deadline_timer m_ShutdownTimer; std::set m_Tokens; std::map m_MethodHandlers; std::map m_I2PControlHandlers; std::map m_RouterInfoHandlers; std::map m_RouterManagerHandlers; std::map m_NetworkSettingHandlers; }; } } #endif i2pd-2.17.0/daemon/UPnP.cpp000066400000000000000000000121461321131324000152220ustar00rootroot00000000000000#ifdef USE_UPNP #include #include #include #include #include #include "Log.h" #include "RouterContext.h" #include "UPnP.h" #include "NetDb.hpp" #include "util.h" #include "RouterInfo.h" #include "Config.h" #include #include namespace i2p { namespace transport { UPnP::UPnP () : m_IsRunning(false), m_Thread (nullptr), m_Timer (m_Service) { } void UPnP::Stop () { if (m_IsRunning) { LogPrint(eLogInfo, "UPnP: stopping"); m_IsRunning = false; m_Timer.cancel (); m_Service.stop (); if (m_Thread) { m_Thread->join (); m_Thread.reset (nullptr); } CloseMapping (); Close (); } } void UPnP::Start() { m_IsRunning = true; LogPrint(eLogInfo, "UPnP: starting"); m_Service.post (std::bind (&UPnP::Discover, this)); std::unique_lock l(m_StartedMutex); m_Thread.reset (new std::thread (std::bind (&UPnP::Run, this))); m_Started.wait_for (l, std::chrono::seconds (5)); // 5 seconds maximum } UPnP::~UPnP () { Stop (); } void UPnP::Run () { while (m_IsRunning) { try { m_Service.run (); // Discover failed break; // terminate the thread } catch (std::exception& ex) { LogPrint (eLogError, "UPnP: runtime exception: ", ex.what ()); PortMapping (); } } } void UPnP::Discover () { #if MINIUPNPC_API_VERSION >= 14 int nerror = 0; m_Devlist = upnpDiscover (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, 2, &nerror); #elif ( MINIUPNPC_API_VERSION >= 8 || defined(UPNPDISCOVER_SUCCESS) ) int nerror = 0; m_Devlist = upnpDiscover (2000, m_MulticastIf, m_Minissdpdpath, 0, 0, &nerror); #else m_Devlist = upnpDiscover (2000, m_MulticastIf, m_Minissdpdpath, 0); #endif { // notify satrting thread std::unique_lock l(m_StartedMutex); m_Started.notify_all (); } int r; r = UPNP_GetValidIGD (m_Devlist, &m_upnpUrls, &m_upnpData, m_NetworkAddr, sizeof (m_NetworkAddr)); if (r == 1) { r = UPNP_GetExternalIPAddress (m_upnpUrls.controlURL, m_upnpData.first.servicetype, m_externalIPAddress); if(r != UPNPCOMMAND_SUCCESS) { LogPrint (eLogError, "UPnP: UPNP_GetExternalIPAddress() returned ", r); return; } else { if (!m_externalIPAddress[0]) { LogPrint (eLogError, "UPnP: GetExternalIPAddress() failed."); return; } } } else { LogPrint (eLogError, "UPnP: GetValidIGD() failed."); return; } // UPnP discovered LogPrint (eLogDebug, "UPnP: ExternalIPAddress is ", m_externalIPAddress); i2p::context.UpdateAddress (boost::asio::ip::address::from_string (m_externalIPAddress)); // port mapping PortMapping (); } void UPnP::PortMapping () { const auto& a = context.GetRouterInfo().GetAddresses(); for (const auto& address : a) { if (!address->host.is_v6 ()) TryPortMapping (address); } m_Timer.expires_from_now (boost::posix_time::minutes(20)); // every 20 minutes m_Timer.async_wait ([this](const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) PortMapping (); }); } void UPnP::CloseMapping () { const auto& a = context.GetRouterInfo().GetAddresses(); for (const auto& address : a) { if (!address->host.is_v6 ()) CloseMapping (address); } } void UPnP::TryPortMapping (std::shared_ptr address) { std::string strType (GetProto (address)), strPort (std::to_string (address->port)); int r; std::string strDesc; i2p::config::GetOption("upnp.name", strDesc); #ifdef UPNPDISCOVER_SUCCESS r = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0, "0"); #else r = UPNP_AddPortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strPort.c_str (), m_NetworkAddr, strDesc.c_str (), strType.c_str (), 0); #endif if (r!=UPNPCOMMAND_SUCCESS) { LogPrint (eLogError, "UPnP: AddPortMapping (", m_NetworkAddr, ":", strPort, ") failed with code ", r); return; } else { LogPrint (eLogDebug, "UPnP: Port Mapping successful. (", m_NetworkAddr ,":", strPort, " type ", strType, " -> ", m_externalIPAddress ,":", strPort ,")"); return; } } void UPnP::CloseMapping (std::shared_ptr address) { std::string strType (GetProto (address)), strPort (std::to_string (address->port)); int r = 0; r = UPNP_DeletePortMapping (m_upnpUrls.controlURL, m_upnpData.first.servicetype, strPort.c_str (), strType.c_str (), 0); LogPrint (eLogError, "UPnP: DeletePortMapping() returned : ", r); } void UPnP::Close () { freeUPNPDevlist (m_Devlist); m_Devlist = 0; FreeUPNPUrls (&m_upnpUrls); } std::string UPnP::GetProto (std::shared_ptr address) { switch (address->transportStyle) { case i2p::data::RouterInfo::eTransportNTCP: return "TCP"; break; case i2p::data::RouterInfo::eTransportSSU: default: return "UDP"; } } } } #else /* USE_UPNP */ namespace i2p { namespace transport { } } #endif /* USE_UPNP */ i2pd-2.17.0/daemon/UPnP.h000066400000000000000000000031271321131324000146660ustar00rootroot00000000000000#ifndef __UPNP_H__ #define __UPNP_H__ #ifdef USE_UPNP #include #include #include #include #include #include #include #include #include #include namespace i2p { namespace transport { class UPnP { public: UPnP (); ~UPnP (); void Close (); void Start (); void Stop (); private: void Discover (); void PortMapping (); void TryPortMapping (std::shared_ptr address); void CloseMapping (); void CloseMapping (std::shared_ptr address); void Run (); std::string GetProto (std::shared_ptr address); private: bool m_IsRunning; std::unique_ptr m_Thread; std::condition_variable m_Started; std::mutex m_StartedMutex; boost::asio::io_service m_Service; boost::asio::deadline_timer m_Timer; struct UPNPUrls m_upnpUrls; struct IGDdatas m_upnpData; // For miniupnpc char * m_MulticastIf = 0; char * m_Minissdpdpath = 0; struct UPNPDev * m_Devlist = 0; char m_NetworkAddr[64]; char m_externalIPAddress[40]; }; } } #else // USE_UPNP namespace i2p { namespace transport { /* class stub */ class UPnP { public: UPnP () {}; ~UPnP () {}; void Start () { LogPrint(eLogWarning, "UPnP: this module was disabled at compile-time"); } void Stop () {}; }; } } #endif // USE_UPNP #endif // __UPNP_H__ i2pd-2.17.0/daemon/UnixDaemon.cpp000066400000000000000000000117601321131324000164500ustar00rootroot00000000000000#include "Daemon.h" #ifndef _WIN32 #include #include #include #include #include #include #include #include "Config.h" #include "FS.h" #include "Log.h" #include "RouterContext.h" #include "ClientContext.h" void handle_signal(int sig) { switch (sig) { case SIGHUP: LogPrint(eLogInfo, "Daemon: Got SIGHUP, reopening tunnel configuration..."); i2p::client::context.ReloadConfig(); break; case SIGUSR1: LogPrint(eLogInfo, "Daemon: Got SIGUSR1, reopening logs..."); i2p::log::Logger().Reopen (); break; case SIGINT: if (i2p::context.AcceptsTunnels () && !Daemon.gracefulShutdownInterval) { i2p::context.SetAcceptsTunnels (false); Daemon.gracefulShutdownInterval = 10*60; // 10 minutes LogPrint(eLogInfo, "Graceful shutdown after ", Daemon.gracefulShutdownInterval, " seconds"); } else Daemon.running = 0; break; case SIGABRT: case SIGTERM: Daemon.running = 0; // Exit loop break; case SIGPIPE: LogPrint(eLogInfo, "SIGPIPE received"); break; } } namespace i2p { namespace util { bool DaemonLinux::start() { if (isDaemon) { pid_t pid; pid = fork(); if (pid > 0) // parent ::exit (EXIT_SUCCESS); if (pid < 0) // error { LogPrint(eLogError, "Daemon: could not fork: ", strerror(errno)); return false; } // child umask(S_IWGRP | S_IRWXO); // 0027 int sid = setsid(); if (sid < 0) { LogPrint(eLogError, "Daemon: could not create process group."); return false; } std::string d = i2p::fs::GetDataDir(); if (chdir(d.c_str()) != 0) { LogPrint(eLogError, "Daemon: could not chdir: ", strerror(errno)); return false; } // point std{in,out,err} descriptors to /dev/null freopen("/dev/null", "r", stdin); freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); } // set proc limits struct rlimit limit; uint16_t nfiles; i2p::config::GetOption("limits.openfiles", nfiles); getrlimit(RLIMIT_NOFILE, &limit); if (nfiles == 0) { LogPrint(eLogInfo, "Daemon: using system limit in ", limit.rlim_cur, " max open files"); } else if (nfiles <= limit.rlim_max) { limit.rlim_cur = nfiles; if (setrlimit(RLIMIT_NOFILE, &limit) == 0) { LogPrint(eLogInfo, "Daemon: set max number of open files to ", nfiles, " (system limit is ", limit.rlim_max, ")"); } else { LogPrint(eLogError, "Daemon: can't set max number of open files: ", strerror(errno)); } } else { LogPrint(eLogError, "Daemon: limits.openfiles exceeds system limit: ", limit.rlim_max); } uint32_t cfsize; i2p::config::GetOption("limits.coresize", cfsize); if (cfsize) // core file size set { cfsize *= 1024; getrlimit(RLIMIT_CORE, &limit); if (cfsize <= limit.rlim_max) { limit.rlim_cur = cfsize; if (setrlimit(RLIMIT_CORE, &limit) != 0) { LogPrint(eLogError, "Daemon: can't set max size of coredump: ", strerror(errno)); } else if (cfsize == 0) { LogPrint(eLogInfo, "Daemon: coredumps disabled"); } else { LogPrint(eLogInfo, "Daemon: set max size of core files to ", cfsize / 1024, "Kb"); } } else { LogPrint(eLogError, "Daemon: limits.coresize exceeds system limit: ", limit.rlim_max); } } // Pidfile // this code is c-styled and a bit ugly, but we need fd for locking pidfile std::string pidfile; i2p::config::GetOption("pidfile", pidfile); if (pidfile == "") { pidfile = i2p::fs::DataDirPath("i2pd.pid"); } if (pidfile != "") { pidFH = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600); if (pidFH < 0) { LogPrint(eLogError, "Daemon: could not create pid file ", pidfile, ": ", strerror(errno)); return false; } if (lockf(pidFH, F_TLOCK, 0) != 0) { LogPrint(eLogError, "Daemon: could not lock pid file ", pidfile, ": ", strerror(errno)); return false; } char pid[10]; sprintf(pid, "%d\n", getpid()); ftruncate(pidFH, 0); if (write(pidFH, pid, strlen(pid)) < 0) { LogPrint(eLogError, "Daemon: could not write pidfile: ", strerror(errno)); return false; } } gracefulShutdownInterval = 0; // not specified // Signal handler struct sigaction sa; sa.sa_handler = handle_signal; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGHUP, &sa, 0); sigaction(SIGUSR1, &sa, 0); sigaction(SIGABRT, &sa, 0); sigaction(SIGTERM, &sa, 0); sigaction(SIGINT, &sa, 0); sigaction(SIGPIPE, &sa, 0); return Daemon_Singleton::start(); } bool DaemonLinux::stop() { i2p::fs::Remove(pidfile); return Daemon_Singleton::stop(); } void DaemonLinux::run () { while (running) { std::this_thread::sleep_for (std::chrono::seconds(1)); if (gracefulShutdownInterval) { gracefulShutdownInterval--; // - 1 second if (gracefulShutdownInterval <= 0) { LogPrint(eLogInfo, "Graceful shutdown"); return; } } } } } } #endif i2pd-2.17.0/daemon/i2pd.cpp000066400000000000000000000011341321131324000152310ustar00rootroot00000000000000#include #include "Daemon.h" #if defined(QT_GUI_LIB) namespace i2p { namespace qt { int RunQT (int argc, char* argv[]); } } int main( int argc, char* argv[] ) { return i2p::qt::RunQT (argc, argv); } #else int main( int argc, char* argv[] ) { if (Daemon.init(argc, argv)) { if (Daemon.start()) Daemon.run (); Daemon.stop(); } return EXIT_SUCCESS; } #endif #ifdef _WIN32 #include int CALLBACK WinMain( _In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) { return main(__argc, __argv); } #endif i2pd-2.17.0/debian/000077500000000000000000000000001321131324000136475ustar00rootroot00000000000000i2pd-2.17.0/debian/.gitignore000066400000000000000000000001741321131324000156410ustar00rootroot00000000000000files i2pd-dbg.substvars i2pd-dbg/ i2pd.postinst.debhelper i2pd.postrm.debhelper i2pd.prerm.debhelper i2pd.substvars i2pd/ i2pd-2.17.0/debian/changelog000066400000000000000000000045551321131324000155320ustar00rootroot00000000000000i2pd (2.17.0-1) unstable; urgency=low * updated to version 2.17.0/0.9.32 -- orignal Mon, 4 Dec 2017 18:00:00 +0000 i2pd (2.16.0-1) unstable; urgency=low * updated to version 2.16.0/0.9.32 -- orignal Mon, 13 Nov 2017 18:00:00 +0000 i2pd (2.15.0-1) unstable; urgency=low * updated to version 2.15.0/0.9.31 -- orignal Thu, 17 Aug 2017 18:00:00 +0000 i2pd (2.14.0-1) unstable; urgency=low * updated to version 2.14.0/0.9.30 * updated debian/control * renamed logrotate to i2pd.logrotate * fixed init.d script -- orignal Thu, 1 Jun 2017 14:00:00 +0000 i2pd (2.13.0-1) unstable; urgency=low * updated to version 2.13.0/0.9.29 * updated debian/control * renamed logrotate to i2pd.logrotate * fixed init.d script -- orignal Thu, 6 Apr 2017 14:00:00 +0000 i2pd (2.12.0-1) unstable; urgency=low * updated to version 2.12.0/0.9.28 -- orignal Tue, 14 Feb 2017 17:59:30 +0000 i2pd (2.11.0-1) unstable; urgency=low * updated to version 2.11.0/0.9.28 -- orignal Sun, 18 Dec 2016 21:01:30 +0000 i2pd (2.10.2-1) unstable; urgency=low * updated to version 2.10.2 -- orignal Sun, 4 Dec 2016 19:38:30 +0000 i2pd (2.10.1-1) unstable; urgency=low * updated to version 2.10.1 -- orignal Mon, 7 Nov 2016 14:18:30 +0000 i2pd (2.10.0-1) unstable; urgency=low * updated to version 2.10.0/0.9.27 * reseed.verify set to true by default -- orignal Sun, 16 Oct 2016 13:55:40 +0000 i2pd (2.9.0-1) unstable; urgency=low * updated to version 2.9.0 * updated tune-patch * removed I2PD_PORT in i2pd.default * removed all port assigments in services files * fixed logrotate * subscriptions.txt and tunnels.conf taken from docs folder -- orignal Fri, 12 Aug 2016 14:25:40 +0000 i2pd (2.7.0-1) unstable; urgency=low * updated to version 2.7.0/0.9.25 -- hagen Wed, 18 May 2016 01:11:04 +0000 i2pd (2.2.0-2) unstable; urgency=low * updated to version 2.2.0 -- hagen Wed, 23 Dec 2015 01:29:40 +0000 i2pd (2.1.0-1) unstable; urgency=low * updated to version 2.1.0/0.9.23 * updated deps -- hagen Fri, 19 Sep 2014 05:16:12 +0000 i2pd-2.17.0/debian/compat000066400000000000000000000000021321131324000150450ustar00rootroot000000000000009 i2pd-2.17.0/debian/control000066400000000000000000000025431321131324000152560ustar00rootroot00000000000000Source: i2pd Section: net Priority: optional Maintainer: R4SAS Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.16.1~), gcc (>= 4.7) | clang (>= 3.3), libboost-system-dev (>= 1.46), libboost-date-time-dev, libboost-filesystem-dev, libboost-program-options-dev, libminiupnpc-dev, libssl-dev, zlib1g-dev, dh-apparmor Standards-Version: 3.9.6 Homepage: http://i2pd.website/ Vcs-Git: git://github.com/PurpleI2P/i2pd.git Vcs-Browser: https://github.com/PurpleI2P/i2pd.git Package: i2pd Architecture: any Pre-Depends: adduser Depends: ${shlibs:Depends}, ${misc:Depends} Suggests: tor, privoxy, apparmor Description: A full-featured C++ implementation of I2P client. I2P (Invisible Internet Protocol) is a universal anonymous network layer. All communications over I2P are anonymous and end-to-end encrypted, participants don't reveal their real IP addresses. . This package contains the full-featured C++ implementation of I2P router. Package: i2pd-dbg Architecture: any Priority: extra Section: debug Depends: i2pd (= ${binary:Version}), ${misc:Depends} Suggests: gdb Description: i2pd debugging symbols I2P (Invisible Internet Protocol) is a universal anonymous network layer. All communications over I2P are anonymous and end-to-end encrypted, participants don't reveal their real IP addresses. . This package contains symbols required for debugging. i2pd-2.17.0/debian/copyright000066400000000000000000000051631321131324000156070ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: i2pd Source: https://github.com/PurpleI2P Files: * Copyright: 2013-2017 PurpleI2P License: BSD-3-clause Copyright (c) 2013-2017, The PurpleI2P Project . All rights reserved. . Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. . 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. . THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Files: debian/* Copyright: 2016-2017 R4SAS 2014-2016 hagen 2013-2015 Kill Your TV License: GPL-2.0+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU General Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". i2pd-2.17.0/debian/docs000066400000000000000000000000121321131324000145130ustar00rootroot00000000000000README.md i2pd-2.17.0/debian/i2pd.1000066400000000000000000000060671321131324000146000ustar00rootroot00000000000000.TH I2PD "1" "March 31, 2015" .SH NAME i2pd \- Load-balanced unspoofable packet switching network .SH SYNOPSIS .B i2pd [\fIOPTION1\fR] [\fIOPTION2\fR]... .SH DESCRIPTION i2pd is a C++ implementation of the router for the I2P anonymizing network, offering a simple layer that identity-sensitive applications can use to securely communicate. All data is wrapped with several layers of encryption, and the network is both distributed and dynamic, with no trusted parties. .PP Any of the configuration options below can be used in the \fBDAEMON_ARGS\fR variable in \fI/etc/default/i2pd\fR. .BR .TP \fB\-\-help\fR Show available options. .TP \fB\-\-conf=\fR Config file (default: \fI~/.i2pd/i2pd.conf\fR or \fI/var/lib/i2pd/i2pd.conf\fR) .BR This parameter will be silently ignored if the specified config file does not exist. Options specified on the command line take precedence over those in the config file. .TP \fB\-\-tunconf=\fR Tunnels config file (default: \fI~/.i2pd/tunnels.conf\fR or \fI/var/lib/i2pd/tunnels.conf\fR) .TP \fB\-\-pidfile=\fR Where to write pidfile (don\'t write by default) .TP \fB\-\-log=\fR Logs destination: \fIstdout\fR, \fIfile\fR, \fIsyslog\fR (\fIstdout\fR if not set, \fIfile\fR - otherwise, for compatibility) .TP \fB\-\-logfile\fR Path to logfile (default - autodetect) .TP \fB\-\-loglevel=\fR Log messages above this level (\fIdebug\fR, \fBinfo\fR, \fIwarn\fR, \fIerror\fR) .TP \fB\-\-datadir=\fR Path to storage of i2pd data (RI, keys, peer profiles, ...) .TP \fB\-\-host=\fR The external IP address .TP \fB\-\-port=\fR The port to listen on for incoming connections .TP \fB\-\-daemon\fR Router will go to background after start .TP \fB\-\-service\fR Router will use system folders like \fI/var/lib/i2pd\fR .TP \fB\-\-ipv6\fR Enable communication through ipv6. false by default .TP \fB\-\-notransit\fR Router will not accept transit tunnels at startup .TP \fB\-\-floodfill\fR Router will be floodfill .TP \fB\-\-bandwidth=\fR Bandwidth limit: integer in KBps or letter aliases: \fIL (32KBps)\fR, O (256), P (2048), X (>9000) .TP \fB\-\-family=\fR Name of a family, router belongs to. .PP See service-specific parameters in example config file \fIcontrib/i2pd.conf\fR .SH FILES .PP /etc/i2pd/i2pd.conf, /etc/i2pd/tunnels.conf, /etc/default/i2pd .RS 4 i2pd configuration files (when running as a system service) .RE .PP /var/lib/i2pd/ .RS 4 i2pd profile directory (when running as a system service, see \fB\-\-service\fR above) .RE .PP $HOME/.i2pd/ .RS 4 i2pd profile directory (when running as a normal user) .RE .PP /usr/share/doc/i2pd/examples/hosts.txt.gz .RS 4 default I2P hosts file .SH AUTHOR This manual page was written by kytv for the Debian system (but may be used by others). .PP Updated by hagen in 2016. .PP Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License, Version 2 or any later version published by the Free Software Foundation .BR On Debian systems, the complete text of the GNU General Public License can be found in \fI/usr/share/common-licenses/GPL\fR i2pd-2.17.0/debian/i2pd.default000066400000000000000000000005451321131324000160570ustar00rootroot00000000000000# Defaults for i2pd initscript # sourced by /etc/init.d/i2pd # installed at /etc/default/i2pd by the maintainer scripts I2PD_ENABLED="yes" # Additional options that are passed to the Daemon. # see possible switches in /usr/share/doc/i2pd/configuration.md.gz DAEMON_OPTS="" # If you have problems with hunging i2pd, you can try enable this ulimit -n 4096 i2pd-2.17.0/debian/i2pd.dirs000066400000000000000000000000261321131324000153660ustar00rootroot00000000000000etc/i2pd var/lib/i2pd i2pd-2.17.0/debian/i2pd.init000066400000000000000000000067071321131324000154040ustar00rootroot00000000000000#!/bin/sh ### BEGIN INIT INFO # Provides: i2pd # Required-Start: $network $local_fs $remote_fs # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: i2p router written in C++ ### END INIT INFO # Author: hagen PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC=i2pd # Introduce a short description here NAME=i2pd # Introduce the short server's name here DAEMON=/usr/sbin/$NAME # Introduce the server's location here DAEMON_OPTS="" # Arguments to run the daemon with PIDFILE=/var/run/$NAME/$NAME.pid I2PCONF=/etc/$NAME/i2pd.conf TUNCONF=/etc/$NAME/tunnels.conf LOGFILE=/var/log/$NAME/$NAME.log USER="i2pd" # Exit if the package is not installed [ -x $DAEMON ] || exit 0 [ -r /etc/default/$NAME ] && . /etc/default/$NAME . /lib/init/vars.sh . /lib/lsb/init-functions # Function that starts the daemon/service do_start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started if [ "x$I2PD_ENABLED" != "xyes" ]; then log_warning_msg "$NAME disabled in config" return 2 fi test -e /var/run/i2pd || install -m 755 -o i2pd -g i2pd -d /var/run/i2pd touch "$PIDFILE" chown -f $USER:adm "$PIDFILE" test -e /var/log/i2pd || install -m 755 -o i2pd -g i2pd -d /var/log/i2pd touch "$LOGFILE" chown -f $USER:adm "$LOGFILE" start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" --test > /dev/null \ || return 1 start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid "$USER" -- \ --service --daemon --log=file --logfile=$LOGFILE --conf=$I2PCONF --tunconf=$TUNCONF \ --pidfile=$PIDFILE $DAEMON_OPTS > /dev/null 2>&1 \ || return 2 return $? } # Function that stops the daemon/service do_stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON [ "$?" = 2 ] && return 2 rm -f $PIDFILE return "$RETVAL" } # Function that sends a SIGHUP to the daemon/service do_reload() { start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME return 0 } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME" do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; status) status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? ;; reload|force-reload) log_daemon_msg "Reloading $DESC" "$NAME" do_reload log_end_msg $? ;; restart) log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) echo "Usage: $0 {start|stop|status|restart|reload}" >&2 exit 3 ;; esac : i2pd-2.17.0/debian/i2pd.install000066400000000000000000000003071321131324000160750ustar00rootroot00000000000000i2pd usr/sbin/ contrib/i2pd.conf etc/i2pd/ contrib/tunnels.conf etc/i2pd/ contrib/subscriptions.txt etc/i2pd/ contrib/certificates/ usr/share/i2pd/ contrib/apparmor/usr.sbin.i2pd etc/apparmor.d i2pd-2.17.0/debian/i2pd.links000066400000000000000000000003151321131324000155460ustar00rootroot00000000000000etc/i2pd/i2pd.conf var/lib/i2pd/i2pd.conf etc/i2pd/tunnels.conf var/lib/i2pd/tunnels.conf etc/i2pd/subscriptions.txt var/lib/i2pd/subscriptions.txt usr/share/i2pd/certificates var/lib/i2pd/certificates i2pd-2.17.0/debian/i2pd.logrotate000066400000000000000000000002331321131324000164250ustar00rootroot00000000000000/var/log/i2pd/i2pd.log { rotate 6 daily missingok notifempty compress delaycompress copytruncate } i2pd-2.17.0/debian/i2pd.manpages000066400000000000000000000000161321131324000162170ustar00rootroot00000000000000debian/i2pd.1 i2pd-2.17.0/debian/i2pd.openrc000066400000000000000000000015151321131324000157170ustar00rootroot00000000000000#!/sbin/openrc-run pidfile="/var/run/i2pd/i2pd.pid" logfile="/var/log/i2pd/i2pd.log" mainconf="/etc/i2pd/i2pd.conf" tunconf="/etc/i2pd/tunnels.conf" name="i2pd" command="/usr/sbin/i2pd" command_args="--service --daemon --log=file --logfile=$logfile --conf=$mainconf --tunconf=$tunconf --pidfile=$pidfile" description="i2p router written in C++" required_dirs="/var/lib/i2pd" required_files="$mainconf" start_stop_daemon_args="--chuid i2pd" depend() { need mountall use net after bootmisc } start_pre() { if [ -r /etc/default/i2pd ]; then . /etc/default/i2pd fi if [ "x$I2PD_ENABLED" != "xyes" ]; then ewarn "i2pd disabled in /etc/default/i2pd" exit 1 fi checkpath -f -o i2pd:adm $logfile checkpath -f -o i2pd:adm $pidfile if [ -n "$DAEMON_OPTS" ]; then command_args="$command_args $DAEMON_OPTS" fi } i2pd-2.17.0/debian/i2pd.upstart000066400000000000000000000004301321131324000161260ustar00rootroot00000000000000description "i2p client daemon" start on runlevel [2345] stop on runlevel [016] or unmounting-filesystem # these can be overridden in /etc/init/i2pd.override env LOGFILE="/var/log/i2pd/i2pd.log" expect fork exec /usr/sbin/i2pd --daemon --service --log=file --logfile=$LOGFILE i2pd-2.17.0/debian/patches/000077500000000000000000000000001321131324000152765ustar00rootroot00000000000000i2pd-2.17.0/debian/patches/01-tune-build-opts.patch000066400000000000000000000004601321131324000215700ustar00rootroot00000000000000diff --git a/Makefile b/Makefile index bdadfe0..2f71eec 100644 --- a/Makefile +++ b/Makefile @@ -9,10 +9,10 @@ DEPS := obj/make.dep include filelist.mk -USE_AESNI := yes +USE_AESNI := no -USE_AVX := yes +USE_AVX := no USE_STATIC := no USE_MESHNET := no USE_UPNP := no ifeq ($(WEBSOCKETS),1) i2pd-2.17.0/debian/patches/series000066400000000000000000000000311321131324000165050ustar00rootroot0000000000000001-tune-build-opts.patch i2pd-2.17.0/debian/postinst000077500000000000000000000017211321131324000154610ustar00rootroot00000000000000#!/bin/sh set -e LOGFILE='/var/log/i2pd/i2pd.log' I2PDHOME='/var/lib/i2pd' I2PDUSER='i2pd' case "$1" in configure|reconfigure) # Older versions of adduser created the home directory. # The version of adduser in Debian unstable does not. # Create user and group as a system user. if getent passwd $I2PDUSER > /dev/null 2>&1; then groupadd -f $I2PDUSER || true usermod -s "/bin/false" -e 1 $I2PDUSER > /dev/null || true else adduser --system --quiet --group --home $I2PDHOME $I2PDUSER fi mkdir -p -m0750 /var/log/i2pd chown -f ${I2PDUSER}:adm /var/log/i2pd touch $LOGFILE chmod 640 $LOGFILE chown -f ${I2PDUSER}:adm $LOGFILE mkdir -p -m0750 $I2PDHOME chown -f -R -P ${I2PDUSER}:${I2PDUSER} ${I2PDHOME} ;; abort-upgrade|abort-remove|abort-deconfigure) echo "Aborting upgrade" exit 0 ;; *) echo "postinst called with unknown argument '$1'" >&2 exit 0 ;; esac #DEBHELPER# exit 0 i2pd-2.17.0/debian/postrm000077500000000000000000000002631321131324000151220ustar00rootroot00000000000000#!/bin/sh set -e if [ "$1" = "purge" ]; then rm -f /etc/default/i2pd rm -rf /etc/i2pd rm -rf /var/lib/i2pd rm -rf /var/log/i2pd rm -rf /var/run/i2pd fi #DEBHELPER# exit 0 i2pd-2.17.0/debian/rules000077500000000000000000000007051321131324000147310ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow DPKG_EXPORT_BUILDFLAGS = 1 include /usr/share/dpkg/buildflags.mk CXXFLAGS+=$(CPPFLAGS) PREFIX=/usr %: dh $@ --parallel dh_apparmor --profile-name=usr.sbin.i2pd -pi2pd override_dh_strip: dh_strip --dbg-package=i2pd-dbg override_dh_shlibdeps: dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info i2pd-2.17.0/debian/source/000077500000000000000000000000001321131324000151475ustar00rootroot00000000000000i2pd-2.17.0/debian/source/format000066400000000000000000000000141321131324000163550ustar00rootroot000000000000003.0 (quilt) i2pd-2.17.0/debian/watch000066400000000000000000000002121321131324000146730ustar00rootroot00000000000000version=3 opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/i2pd-$1\.tar\.gz/ \ https://github.com/PurpleI2P/i2pd/tags .*/v?(\d\S*)\.tar\.gz i2pd-2.17.0/docs/000077500000000000000000000000001321131324000133555ustar00rootroot00000000000000i2pd-2.17.0/docs/Doxyfile000066400000000000000000003106661321131324000150770ustar00rootroot00000000000000# Doxyfile 1.8.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See http://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = "i2pd" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "load-balanced unspoofable packet switching network" # With the PROJECT_LOGO tag one can specify an logo or icon that is included in # the documentation. The maximum height of the logo should not exceed 55 pixels # and the maximum width should not exceed 200 pixels. Doxygen will copy the logo # to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = docs/generated # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a # new page for each member. If set to NO, the documentation of a member will be # part of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by by putting a % sign in front of the word # or globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined # locally in source files will be included in the documentation. If set to NO # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO these classes will be included in the various overviews. This option has # no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the # todo list. This list is created by putting \todo commands in the # documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the # test list. This list is created by putting \test commands in the # documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES the list # will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = NO # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO doxygen will only warn about wrong or incomplete parameter # documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. # Note: If this tag is empty the current directory is searched. INPUT = # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank the # following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, # *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, # *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. FILE_PATTERNS = *.cpp \ *.h \ *.hpp # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER ) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES, then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see http://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = NO # If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the # clang parser (see: http://clang.llvm.org/) for more accurate parsing at the # cost of reduced performance. This can be particularly helpful with template # rich C++ code for which doxygen's built-in parser lacks the necessary type # information. # Note: The availability of this option depends on whether or not doxygen was # compiled with the --with-libclang option. # The default value is: NO. CLANG_ASSISTED_PARSING = NO # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_OPTIONS = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefor more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra stylesheet files is of importance (e.g. the last # stylesheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the stylesheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to NO can help when comparing the output of multiple runs. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: http://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler ( hhc.exe). If non-empty # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated ( # YES) or that it should be included in the master .chm file ( NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated ( # YES) or a normal table of contents ( NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using prerendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /

(); s.read ((char *)&address->cost, sizeof (address->cost)); s.read ((char *)&address->date, sizeof (address->date)); char transportStyle[5]; ReadString (transportStyle, 5, s); if (!strcmp (transportStyle, "NTCP")) address->transportStyle = eTransportNTCP; else if (!strcmp (transportStyle, "SSU")) { address->transportStyle = eTransportSSU; address->ssu.reset (new SSUExt ()); address->ssu->mtu = 0; } else address->transportStyle = eTransportUnknown; address->port = 0; uint16_t size, r = 0; s.read ((char *)&size, sizeof (size)); if (!s) return; size = be16toh (size); while (r < size) { char key[255], value[255]; r += ReadString (key, 255, s); s.seekg (1, std::ios_base::cur); r++; // = r += ReadString (value, 255, s); s.seekg (1, std::ios_base::cur); r++; // ; if (!s) return; if (!strcmp (key, "host")) { boost::system::error_code ecode; address->host = boost::asio::ip::address::from_string (value, ecode); if (ecode) { if (address->transportStyle == eTransportNTCP) { supportedTransports |= eNTCPV4; // TODO: address->addressString = value; } else { supportedTransports |= eSSUV4; // TODO: address->addressString = value; } } else { // add supported protocol if (address->host.is_v4 ()) supportedTransports |= (address->transportStyle == eTransportNTCP) ? eNTCPV4 : eSSUV4; else supportedTransports |= (address->transportStyle == eTransportNTCP) ? eNTCPV6 : eSSUV6; } } else if (!strcmp (key, "port")) address->port = boost::lexical_cast(value); else if (!strcmp (key, "mtu")) { if (address->ssu) address->ssu->mtu = boost::lexical_cast(value); else LogPrint (eLogWarning, "RouterInfo: Unexpected field 'mtu' for NTCP"); } else if (!strcmp (key, "key")) { if (address->ssu) Base64ToByteStream (value, strlen (value), address->ssu->key, 32); else LogPrint (eLogWarning, "RouterInfo: Unexpected field 'key' for NTCP"); } else if (!strcmp (key, "caps")) ExtractCaps (value); else if (key[0] == 'i') { // introducers introducers = true; size_t l = strlen(key); unsigned char index = key[l-1] - '0'; // TODO: key[l-1] = 0; if (index > 9) { LogPrint (eLogError, "RouterInfo: Unexpected introducer's index ", index, " skipped"); if (s) continue; else return; } if (index >= address->ssu->introducers.size ()) address->ssu->introducers.resize (index + 1); Introducer& introducer = address->ssu->introducers.at (index); if (!strcmp (key, "ihost")) { boost::system::error_code ecode; introducer.iHost = boost::asio::ip::address::from_string (value, ecode); } else if (!strcmp (key, "iport")) introducer.iPort = boost::lexical_cast(value); else if (!strcmp (key, "itag")) introducer.iTag = boost::lexical_cast(value); else if (!strcmp (key, "ikey")) Base64ToByteStream (value, strlen (value), introducer.iKey, 32); else if (!strcmp (key, "iexp")) introducer.iExp = boost::lexical_cast(value); } if (!s) return; } if (introducers) supportedTransports |= eSSUV4; // in case if host is not presented if (supportedTransports) { addresses->push_back(address); m_SupportedTransports |= supportedTransports; } } #if (BOOST_VERSION >= 105300) boost::atomic_store (&m_Addresses, addresses); #else m_Addresses = addresses; // race condition #endif // read peers uint8_t numPeers; s.read ((char *)&numPeers, sizeof (numPeers)); if (!s) return; s.seekg (numPeers*32, std::ios_base::cur); // TODO: read peers // read properties uint16_t size, r = 0; s.read ((char *)&size, sizeof (size)); if (!s) return; size = be16toh (size); while (r < size) { char key[255], value[255]; r += ReadString (key, 255, s); s.seekg (1, std::ios_base::cur); r++; // = r += ReadString (value, 255, s); s.seekg (1, std::ios_base::cur); r++; // ; if (!s) return; m_Properties[key] = value; // extract caps if (!strcmp (key, "caps")) ExtractCaps (value); // check netId else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != i2p::context.GetNetID ()) { LogPrint (eLogError, "RouterInfo: Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); m_IsUnreachable = true; } // family else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY)) { m_Family = value; boost::to_lower (m_Family); } else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG)) { if (!netdb.GetFamilies ().VerifyFamily (m_Family, GetIdentHash (), value)) { LogPrint (eLogWarning, "RouterInfo: family signature verification failed"); m_Family.clear (); } } if (!s) return; } if (!m_SupportedTransports || !m_Addresses->size() || (UsesIntroducer () && !introducers)) SetUnreachable (true); } bool RouterInfo::IsFamily(const std::string & fam) const { return m_Family == fam; } void RouterInfo::ExtractCaps (const char * value) { const char * cap = value; while (*cap) { switch (*cap) { case CAPS_FLAG_FLOODFILL: m_Caps |= Caps::eFloodfill; break; case CAPS_FLAG_HIGH_BANDWIDTH1: case CAPS_FLAG_HIGH_BANDWIDTH2: case CAPS_FLAG_HIGH_BANDWIDTH3: m_Caps |= Caps::eHighBandwidth; break; case CAPS_FLAG_EXTRA_BANDWIDTH1: case CAPS_FLAG_EXTRA_BANDWIDTH2: m_Caps |= Caps::eExtraBandwidth; break; case CAPS_FLAG_HIDDEN: m_Caps |= Caps::eHidden; break; case CAPS_FLAG_REACHABLE: m_Caps |= Caps::eReachable; break; case CAPS_FLAG_UNREACHABLE: m_Caps |= Caps::eUnreachable; break; case CAPS_FLAG_SSU_TESTING: m_Caps |= Caps::eSSUTesting; break; case CAPS_FLAG_SSU_INTRODUCER: m_Caps |= Caps::eSSUIntroducer; break; default: ; } cap++; } } void RouterInfo::UpdateCapsProperty () { std::string caps; if (m_Caps & eFloodfill) { if (m_Caps & eExtraBandwidth) caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 : // 'X' CAPS_FLAG_EXTRA_BANDWIDTH1; // 'P' caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' caps += CAPS_FLAG_FLOODFILL; // floodfill } else { if (m_Caps & eExtraBandwidth) { caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_EXTRA_BANDWIDTH2 /* 'X' */ : CAPS_FLAG_EXTRA_BANDWIDTH1; /*'P' */ caps += CAPS_FLAG_HIGH_BANDWIDTH3; // 'O' } else caps += (m_Caps & eHighBandwidth) ? CAPS_FLAG_HIGH_BANDWIDTH3 /* 'O' */: CAPS_FLAG_LOW_BANDWIDTH2 /* 'L' */; // bandwidth } if (m_Caps & eHidden) caps += CAPS_FLAG_HIDDEN; // hidden if (m_Caps & eReachable) caps += CAPS_FLAG_REACHABLE; // reachable if (m_Caps & eUnreachable) caps += CAPS_FLAG_UNREACHABLE; // unreachable SetProperty ("caps", caps); } void RouterInfo::WriteToStream (std::ostream& s) const { uint64_t ts = htobe64 (m_Timestamp); s.write ((const char *)&ts, sizeof (ts)); // addresses uint8_t numAddresses = m_Addresses->size (); s.write ((char *)&numAddresses, sizeof (numAddresses)); for (const auto& addr_ptr : *m_Addresses) { const Address& address = *addr_ptr; s.write ((const char *)&address.cost, sizeof (address.cost)); s.write ((const char *)&address.date, sizeof (address.date)); std::stringstream properties; if (address.transportStyle == eTransportNTCP) WriteString ("NTCP", s); else if (address.transportStyle == eTransportSSU) { WriteString ("SSU", s); // caps WriteString ("caps", properties); properties << '='; std::string caps; if (IsPeerTesting ()) caps += CAPS_FLAG_SSU_TESTING; if (IsIntroducer ()) caps += CAPS_FLAG_SSU_INTRODUCER; WriteString (caps, properties); properties << ';'; } else WriteString ("", s); WriteString ("host", properties); properties << '='; WriteString (address.host.to_string (), properties); properties << ';'; if (address.transportStyle == eTransportSSU) { // write introducers if any if (address.ssu->introducers.size () > 0) { int i = 0; for (const auto& introducer: address.ssu->introducers) { WriteString ("ihost" + boost::lexical_cast(i), properties); properties << '='; WriteString (introducer.iHost.to_string (), properties); properties << ';'; i++; } i = 0; for (const auto& introducer: address.ssu->introducers) { WriteString ("ikey" + boost::lexical_cast(i), properties); properties << '='; char value[64]; size_t l = ByteStreamToBase64 (introducer.iKey, 32, value, 64); value[l] = 0; WriteString (value, properties); properties << ';'; i++; } i = 0; for (const auto& introducer: address.ssu->introducers) { WriteString ("iport" + boost::lexical_cast(i), properties); properties << '='; WriteString (boost::lexical_cast(introducer.iPort), properties); properties << ';'; i++; } i = 0; for (const auto& introducer: address.ssu->introducers) { WriteString ("itag" + boost::lexical_cast(i), properties); properties << '='; WriteString (boost::lexical_cast(introducer.iTag), properties); properties << ';'; i++; } i = 0; for (const auto& introducer: address.ssu->introducers) { if (introducer.iExp) // expiration is specified { WriteString ("iexp" + boost::lexical_cast(i), properties); properties << '='; WriteString (boost::lexical_cast(introducer.iExp), properties); properties << ';'; } i++; } } // write intro key WriteString ("key", properties); properties << '='; char value[64]; size_t l = ByteStreamToBase64 (address.ssu->key, 32, value, 64); value[l] = 0; WriteString (value, properties); properties << ';'; // write mtu if (address.ssu->mtu) { WriteString ("mtu", properties); properties << '='; WriteString (boost::lexical_cast(address.ssu->mtu), properties); properties << ';'; } } WriteString ("port", properties); properties << '='; WriteString (boost::lexical_cast(address.port), properties); properties << ';'; uint16_t size = htobe16 (properties.str ().size ()); s.write ((char *)&size, sizeof (size)); s.write (properties.str ().c_str (), properties.str ().size ()); } // peers uint8_t numPeers = 0; s.write ((char *)&numPeers, sizeof (numPeers)); // properties std::stringstream properties; for (const auto& p : m_Properties) { WriteString (p.first, properties); properties << '='; WriteString (p.second, properties); properties << ';'; } uint16_t size = htobe16 (properties.str ().size ()); s.write ((char *)&size, sizeof (size)); s.write (properties.str ().c_str (), properties.str ().size ()); } bool RouterInfo::IsNewer (const uint8_t * buf, size_t len) const { if (!m_RouterIdentity) return false; size_t size = m_RouterIdentity->GetFullLen (); if (size + 8 > len) return false; return bufbe64toh (buf + size) > m_Timestamp; } const uint8_t * RouterInfo::LoadBuffer () { if (!m_Buffer) { if (LoadFile ()) LogPrint (eLogDebug, "RouterInfo: Buffer for ", GetIdentHashAbbreviation (GetIdentHash ()), " loaded from file"); } return m_Buffer; } void RouterInfo::CreateBuffer (const PrivateKeys& privateKeys) { m_Timestamp = i2p::util::GetMillisecondsSinceEpoch (); // refresh timstamp std::stringstream s; uint8_t ident[1024]; auto identLen = privateKeys.GetPublic ()->ToBuffer (ident, 1024); s.write ((char *)ident, identLen); WriteToStream (s); m_BufferLen = s.str ().size (); if (!m_Buffer) m_Buffer = new uint8_t[MAX_RI_BUFFER_SIZE]; memcpy (m_Buffer, s.str ().c_str (), m_BufferLen); // signature privateKeys.Sign ((uint8_t *)m_Buffer, m_BufferLen, (uint8_t *)m_Buffer + m_BufferLen); m_BufferLen += privateKeys.GetPublic ()->GetSignatureLen (); } bool RouterInfo::SaveToFile (const std::string& fullPath) { m_FullPath = fullPath; if (!m_Buffer) { LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL"); return false; } std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); if (!f.is_open ()) { LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath); return false; } f.write ((char *)m_Buffer, m_BufferLen); return true; } size_t RouterInfo::ReadString (char * str, size_t len, std::istream& s) const { uint8_t l; s.read ((char *)&l, 1); if (l < len) { s.read (str, l); if (!s) l = 0; // failed, return empty string str[l] = 0; } else { LogPrint (eLogWarning, "RouterInfo: string length ", (int)l, " exceeds buffer size ", len); s.seekg (l, std::ios::cur); // skip str[0] = 0; } return l+1; } void RouterInfo::WriteString (const std::string& str, std::ostream& s) const { uint8_t len = str.size (); s.write ((char *)&len, 1); s.write (str.c_str (), len); } void RouterInfo::AddNTCPAddress (const char * host, int port) { auto addr = std::make_shared
(); addr->host = boost::asio::ip::address::from_string (host); addr->port = port; addr->transportStyle = eTransportNTCP; addr->cost = 2; addr->date = 0; for (const auto& it: *m_Addresses) // don't insert same address twice if (*it == *addr) return; m_SupportedTransports |= addr->host.is_v6 () ? eNTCPV6 : eNTCPV4; m_Addresses->push_back(std::move(addr)); } void RouterInfo::AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu) { auto addr = std::make_shared
(); addr->host = boost::asio::ip::address::from_string (host); addr->port = port; addr->transportStyle = eTransportSSU; addr->cost = 10; // NTCP should have priority over SSU addr->date = 0; addr->ssu.reset (new SSUExt ()); addr->ssu->mtu = mtu; memcpy (addr->ssu->key, key, 32); for (const auto& it: *m_Addresses) // don't insert same address twice if (*it == *addr) return; m_SupportedTransports |= addr->host.is_v6 () ? eSSUV6 : eSSUV4; m_Addresses->push_back(std::move(addr)); m_Caps |= eSSUTesting; m_Caps |= eSSUIntroducer; } bool RouterInfo::AddIntroducer (const Introducer& introducer) { for (auto& addr : *m_Addresses) { if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) { for (auto& intro: addr->ssu->introducers) if (intro.iTag == introducer.iTag) return false; // already presented addr->ssu->introducers.push_back (introducer); return true; } } return false; } bool RouterInfo::RemoveIntroducer (const boost::asio::ip::udp::endpoint& e) { for (auto& addr: *m_Addresses) { if (addr->transportStyle == eTransportSSU && addr->host.is_v4 ()) { for (auto it = addr->ssu->introducers.begin (); it != addr->ssu->introducers.end (); ++it) if ( boost::asio::ip::udp::endpoint (it->iHost, it->iPort) == e) { addr->ssu->introducers.erase (it); return true; } } } return false; } void RouterInfo::SetCaps (uint8_t caps) { m_Caps = caps; UpdateCapsProperty (); } void RouterInfo::SetCaps (const char * caps) { SetProperty ("caps", caps); m_Caps = 0; ExtractCaps (caps); } void RouterInfo::SetProperty (const std::string& key, const std::string& value) { m_Properties[key] = value; } void RouterInfo::DeleteProperty (const std::string& key) { m_Properties.erase (key); } std::string RouterInfo::GetProperty (const std::string& key) const { auto it = m_Properties.find (key); if (it != m_Properties.end ()) return it->second; return ""; } bool RouterInfo::IsNTCP (bool v4only) const { if (v4only) return m_SupportedTransports & eNTCPV4; else return m_SupportedTransports & (eNTCPV4 | eNTCPV6); } bool RouterInfo::IsSSU (bool v4only) const { if (v4only) return m_SupportedTransports & eSSUV4; else return m_SupportedTransports & (eSSUV4 | eSSUV6); } bool RouterInfo::IsV6 () const { return m_SupportedTransports & (eNTCPV6 | eSSUV6); } bool RouterInfo::IsV4 () const { return m_SupportedTransports & (eNTCPV4 | eSSUV4); } void RouterInfo::EnableV6 () { if (!IsV6 ()) m_SupportedTransports |= eNTCPV6 | eSSUV6; } void RouterInfo::EnableV4 () { if (!IsV4 ()) m_SupportedTransports |= eNTCPV4 | eSSUV4; } void RouterInfo::DisableV6 () { if (IsV6 ()) { m_SupportedTransports &= ~(eNTCPV6 | eSSUV6); for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; if (addr->host.is_v6 ()) it = m_Addresses->erase (it); else ++it; } } } void RouterInfo::DisableV4 () { if (IsV4 ()) { m_SupportedTransports &= ~(eNTCPV4 | eSSUV4); for (auto it = m_Addresses->begin (); it != m_Addresses->end ();) { auto addr = *it; if (addr->host.is_v4 ()) it = m_Addresses->erase (it); else ++it; } } } bool RouterInfo::UsesIntroducer () const { return m_Caps & Caps::eUnreachable; // non-reachable } std::shared_ptr RouterInfo::GetNTCPAddress (bool v4only) const { return GetAddress (eTransportNTCP, v4only); } std::shared_ptr RouterInfo::GetSSUAddress (bool v4only) const { return GetAddress (eTransportSSU, v4only); } std::shared_ptr RouterInfo::GetSSUV6Address () const { return GetAddress (eTransportSSU, false, true); } std::shared_ptr RouterInfo::GetAddress (TransportStyle s, bool v4only, bool v6only) const { #if (BOOST_VERSION >= 105300) auto addresses = boost::atomic_load (&m_Addresses); #else auto addresses = m_Addresses; #endif for (const auto& address : *addresses) { if (address->transportStyle == s) { if ((!v4only || address->host.is_v4 ()) && (!v6only || address->host.is_v6 ())) return address; } } return nullptr; } std::shared_ptr RouterInfo::GetProfile () const { if (!m_Profile) m_Profile = GetRouterProfile (GetIdentHash ()); return m_Profile; } void RouterInfo::Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const { auto encryptor = m_RouterIdentity->CreateEncryptor (nullptr); if (encryptor) encryptor->Encrypt (data, encrypted, ctx); } } } i2pd-2.17.0/libi2pd/RouterInfo.h000066400000000000000000000170061321131324000162230ustar00rootroot00000000000000#ifndef ROUTER_INFO_H__ #define ROUTER_INFO_H__ #include #include #include #include #include #include #include #include #include "Identity.h" #include "Profiling.h" namespace i2p { namespace data { const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets"; const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters"; const char ROUTER_INFO_PROPERTY_NETID[] = "netId"; const char ROUTER_INFO_PROPERTY_FAMILY[] = "family"; const char ROUTER_INFO_PROPERTY_FAMILY_SIG[] = "family.sig"; const char CAPS_FLAG_FLOODFILL = 'f'; const char CAPS_FLAG_HIDDEN = 'H'; const char CAPS_FLAG_REACHABLE = 'R'; const char CAPS_FLAG_UNREACHABLE = 'U'; /* bandwidth flags */ const char CAPS_FLAG_LOW_BANDWIDTH1 = 'K'; /* < 12 KBps */ const char CAPS_FLAG_LOW_BANDWIDTH2 = 'L'; /* 12-48 KBps */ const char CAPS_FLAG_HIGH_BANDWIDTH1 = 'M'; /* 48-64 KBps */ const char CAPS_FLAG_HIGH_BANDWIDTH2 = 'N'; /* 64-128 KBps */ const char CAPS_FLAG_HIGH_BANDWIDTH3 = 'O'; /* 128-256 KBps */ const char CAPS_FLAG_EXTRA_BANDWIDTH1 = 'P'; /* 256-2000 KBps */ const char CAPS_FLAG_EXTRA_BANDWIDTH2 = 'X'; /* > 2000 KBps */ const char CAPS_FLAG_SSU_TESTING = 'B'; const char CAPS_FLAG_SSU_INTRODUCER = 'C'; const int MAX_RI_BUFFER_SIZE = 2048; class RouterInfo: public RoutingDestination { public: enum SupportedTranports { eNTCPV4 = 0x01, eNTCPV6 = 0x02, eSSUV4 = 0x04, eSSUV6 = 0x08 }; enum Caps { eFloodfill = 0x01, eHighBandwidth = 0x02, eExtraBandwidth = 0x04, eReachable = 0x08, eSSUTesting = 0x10, eSSUIntroducer = 0x20, eHidden = 0x40, eUnreachable = 0x80 }; enum TransportStyle { eTransportUnknown = 0, eTransportNTCP, eTransportSSU }; typedef Tag<32> IntroKey; // should be castable to MacKey and AESKey struct Introducer { Introducer (): iExp (0) {}; boost::asio::ip::address iHost; int iPort; IntroKey iKey; uint32_t iTag; uint32_t iExp; }; struct SSUExt { int mtu; IntroKey key; // intro key for SSU std::vector introducers; }; struct Address { TransportStyle transportStyle; boost::asio::ip::address host; std::string addressString; int port; uint64_t date; uint8_t cost; std::unique_ptr ssu; // not null for SSU bool IsCompatible (const boost::asio::ip::address& other) const { return (host.is_v4 () && other.is_v4 ()) || (host.is_v6 () && other.is_v6 ()); } bool operator==(const Address& other) const { return transportStyle == other.transportStyle && host == other.host && port == other.port; } bool operator!=(const Address& other) const { return !(*this == other); } }; typedef std::list > Addresses; RouterInfo (); RouterInfo (const std::string& fullPath); RouterInfo (const RouterInfo& ) = default; RouterInfo& operator=(const RouterInfo& ) = default; RouterInfo (const uint8_t * buf, int len); ~RouterInfo (); std::shared_ptr GetRouterIdentity () const { return m_RouterIdentity; }; void SetRouterIdentity (std::shared_ptr identity); std::string GetIdentHashBase64 () const { return GetIdentHash ().ToBase64 (); }; uint64_t GetTimestamp () const { return m_Timestamp; }; Addresses& GetAddresses () { return *m_Addresses; }; // should be called for local RI only, otherwise must return shared_ptr std::shared_ptr GetNTCPAddress (bool v4only = true) const; std::shared_ptr GetSSUAddress (bool v4only = true) const; std::shared_ptr GetSSUV6Address () const; void AddNTCPAddress (const char * host, int port); void AddSSUAddress (const char * host, int port, const uint8_t * key, int mtu = 0); bool AddIntroducer (const Introducer& introducer); bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only void DeleteProperty (const std::string& key); // called from RouterContext only std::string GetProperty (const std::string& key) const; // called from RouterContext only void ClearProperties () { m_Properties.clear (); }; bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; bool IsReachable () const { return m_Caps & Caps::eReachable; }; bool IsNTCP (bool v4only = true) const; bool IsSSU (bool v4only = true) const; bool IsV6 () const; bool IsV4 () const; void EnableV6 (); void DisableV6 (); void EnableV4 (); void DisableV4 (); bool IsCompatible (const RouterInfo& other) const { return m_SupportedTransports & other.m_SupportedTransports; }; bool UsesIntroducer () const; bool IsIntroducer () const { return m_Caps & eSSUIntroducer; }; bool IsPeerTesting () const { return m_Caps & eSSUTesting; }; bool IsHidden () const { return m_Caps & eHidden; }; bool IsHighBandwidth () const { return m_Caps & RouterInfo::eHighBandwidth; }; bool IsExtraBandwidth () const { return m_Caps & RouterInfo::eExtraBandwidth; }; uint8_t GetCaps () const { return m_Caps; }; void SetCaps (uint8_t caps); void SetCaps (const char * caps); void SetUnreachable (bool unreachable) { m_IsUnreachable = unreachable; }; bool IsUnreachable () const { return m_IsUnreachable; }; const uint8_t * GetBuffer () const { return m_Buffer; }; const uint8_t * LoadBuffer (); // load if necessary int GetBufferLen () const { return m_BufferLen; }; void CreateBuffer (const PrivateKeys& privateKeys); bool IsUpdated () const { return m_IsUpdated; }; void SetUpdated (bool updated) { m_IsUpdated = updated; }; bool SaveToFile (const std::string& fullPath); std::shared_ptr GetProfile () const; void SaveProfile () { if (m_Profile) m_Profile->Save (GetIdentHash ()); }; void Update (const uint8_t * buf, int len); void DeleteBuffer () { delete[] m_Buffer; m_Buffer = nullptr; }; bool IsNewer (const uint8_t * buf, size_t len) const; /** return true if we are in a router family and the signature is valid */ bool IsFamily(const std::string & fam) const; // implements RoutingDestination std::shared_ptr GetIdentity () const { return m_RouterIdentity; }; void Encrypt (const uint8_t * data, uint8_t * encrypted, BN_CTX * ctx) const; bool IsDestination () const { return false; }; private: bool LoadFile (); void ReadFromFile (); void ReadFromStream (std::istream& s); void ReadFromBuffer (bool verifySignature); void WriteToStream (std::ostream& s) const; size_t ReadString (char* str, size_t len, std::istream& s) const; void WriteString (const std::string& str, std::ostream& s) const; void ExtractCaps (const char * value); std::shared_ptr GetAddress (TransportStyle s, bool v4only, bool v6only = false) const; void UpdateCapsProperty (); private: std::string m_FullPath, m_Family; std::shared_ptr m_RouterIdentity; uint8_t * m_Buffer; size_t m_BufferLen; uint64_t m_Timestamp; boost::shared_ptr m_Addresses; // TODO: use std::shared_ptr and std::atomic_store for gcc >= 4.9 std::map m_Properties; bool m_IsUpdated, m_IsUnreachable; uint8_t m_SupportedTransports, m_Caps; mutable std::shared_ptr m_Profile; }; } } #endif i2pd-2.17.0/libi2pd/SSU.cpp000066400000000000000000000563201321131324000151360ustar00rootroot00000000000000#include #include #include "Log.h" #include "Timestamp.h" #include "RouterContext.h" #include "NetDb.hpp" #include "SSU.h" namespace i2p { namespace transport { SSUServer::SSUServer (const boost::asio::ip::address & addr, int port): m_OnlyV6(true), m_IsRunning(false), m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), m_EndpointV6 (addr, port), m_Socket (m_ReceiversService, m_Endpoint), m_SocketV6 (m_ReceiversServiceV6), m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_ServiceV6) { OpenSocketV6 (); } SSUServer::SSUServer (int port): m_OnlyV6(false), m_IsRunning(false), m_Thread (nullptr), m_ThreadV6 (nullptr), m_ReceiversThread (nullptr), m_ReceiversThreadV6 (nullptr), m_Work (m_Service), m_WorkV6 (m_ServiceV6), m_ReceiversWork (m_ReceiversService), m_ReceiversWorkV6 (m_ReceiversServiceV6), m_Endpoint (boost::asio::ip::udp::v4 (), port), m_EndpointV6 (boost::asio::ip::udp::v6 (), port), m_Socket (m_ReceiversService), m_SocketV6 (m_ReceiversServiceV6), m_IntroducersUpdateTimer (m_Service), m_PeerTestsCleanupTimer (m_Service), m_TerminationTimer (m_Service), m_TerminationTimerV6 (m_ServiceV6) { OpenSocket (); if (context.SupportsV6 ()) OpenSocketV6 (); } SSUServer::~SSUServer () { } void SSUServer::OpenSocket () { m_Socket.open (boost::asio::ip::udp::v4()); m_Socket.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); m_Socket.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); m_Socket.bind (m_Endpoint); } void SSUServer::OpenSocketV6 () { m_SocketV6.open (boost::asio::ip::udp::v6()); m_SocketV6.set_option (boost::asio::ip::v6_only (true)); m_SocketV6.set_option (boost::asio::socket_base::receive_buffer_size (SSU_SOCKET_RECEIVE_BUFFER_SIZE)); m_SocketV6.set_option (boost::asio::socket_base::send_buffer_size (SSU_SOCKET_SEND_BUFFER_SIZE)); m_SocketV6.bind (m_EndpointV6); } void SSUServer::Start () { m_IsRunning = true; if (!m_OnlyV6) { m_ReceiversThread = new std::thread (std::bind (&SSUServer::RunReceivers, this)); m_Thread = new std::thread (std::bind (&SSUServer::Run, this)); m_ReceiversService.post (std::bind (&SSUServer::Receive, this)); ScheduleTermination (); } if (context.SupportsV6 ()) { m_ReceiversThreadV6 = new std::thread (std::bind (&SSUServer::RunReceiversV6, this)); m_ThreadV6 = new std::thread (std::bind (&SSUServer::RunV6, this)); m_ReceiversServiceV6.post (std::bind (&SSUServer::ReceiveV6, this)); ScheduleTerminationV6 (); } SchedulePeerTestsCleanupTimer (); ScheduleIntroducersUpdateTimer (); // wait for 30 seconds and decide if we need introducers } void SSUServer::Stop () { DeleteAllSessions (); m_IsRunning = false; m_TerminationTimer.cancel (); m_TerminationTimerV6.cancel (); m_Service.stop (); m_Socket.close (); m_ServiceV6.stop (); m_SocketV6.close (); m_ReceiversService.stop (); m_ReceiversServiceV6.stop (); if (m_ReceiversThread) { m_ReceiversThread->join (); delete m_ReceiversThread; m_ReceiversThread = nullptr; } if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = nullptr; } if (m_ReceiversThreadV6) { m_ReceiversThreadV6->join (); delete m_ReceiversThreadV6; m_ReceiversThreadV6 = nullptr; } if (m_ThreadV6) { m_ThreadV6->join (); delete m_ThreadV6; m_ThreadV6 = nullptr; } } void SSUServer::Run () { while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, "SSU: server runtime exception: ", ex.what ()); } } } void SSUServer::RunV6 () { while (m_IsRunning) { try { m_ServiceV6.run (); } catch (std::exception& ex) { LogPrint (eLogError, "SSU: v6 server runtime exception: ", ex.what ()); } } } void SSUServer::RunReceivers () { while (m_IsRunning) { try { m_ReceiversService.run (); } catch (std::exception& ex) { LogPrint (eLogError, "SSU: receivers runtime exception: ", ex.what ()); } } } void SSUServer::RunReceiversV6 () { while (m_IsRunning) { try { m_ReceiversServiceV6.run (); } catch (std::exception& ex) { LogPrint (eLogError, "SSU: v6 receivers runtime exception: ", ex.what ()); } } } void SSUServer::AddRelay (uint32_t tag, std::shared_ptr relay) { m_Relays[tag] = relay; } void SSUServer::RemoveRelay (uint32_t tag) { m_Relays.erase (tag); } std::shared_ptr SSUServer::FindRelaySession (uint32_t tag) { auto it = m_Relays.find (tag); if (it != m_Relays.end ()) { if (it->second->GetState () == eSessionStateEstablished) return it->second; else m_Relays.erase (it); } return nullptr; } void SSUServer::Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to) { if (to.protocol () == boost::asio::ip::udp::v4()) m_Socket.send_to (boost::asio::buffer (buf, len), to); else m_SocketV6.send_to (boost::asio::buffer (buf, len), to); } void SSUServer::Receive () { SSUPacket * packet = new SSUPacket (); m_Socket.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, std::bind (&SSUServer::HandleReceivedFrom, this, std::placeholders::_1, std::placeholders::_2, packet)); } void SSUServer::ReceiveV6 () { SSUPacket * packet = new SSUPacket (); m_SocketV6.async_receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, std::bind (&SSUServer::HandleReceivedFromV6, this, std::placeholders::_1, std::placeholders::_2, packet)); } void SSUServer::HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) { if (!ecode) { packet->len = bytes_transferred; std::vector packets; packets.push_back (packet); boost::system::error_code ec; size_t moreBytes = m_Socket.available(ec); if (!ec) { while (moreBytes && packets.size () < 25) { packet = new SSUPacket (); packet->len = m_Socket.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V4), packet->from, 0, ec); if (!ec) { packets.push_back (packet); moreBytes = m_Socket.available(ec); if (ec) break; } else { LogPrint (eLogError, "SSU: receive_from error: ", ec.message ()); delete packet; break; } } } m_Service.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_Sessions)); Receive (); } else { delete packet; if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "SSU: receive error: ", ecode.message ()); m_Socket.close (); OpenSocket (); Receive (); } } } void SSUServer::HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet) { if (!ecode) { packet->len = bytes_transferred; std::vector packets; packets.push_back (packet); boost::system::error_code ec; size_t moreBytes = m_SocketV6.available (ec); if (!ec) { while (moreBytes && packets.size () < 25) { packet = new SSUPacket (); packet->len = m_SocketV6.receive_from (boost::asio::buffer (packet->buf, SSU_MTU_V6), packet->from, 0, ec); if (!ec) { packets.push_back (packet); moreBytes = m_SocketV6.available(ec); if (ec) break; } else { LogPrint (eLogError, "SSU: v6 receive_from error: ", ec.message ()); delete packet; break; } } } m_ServiceV6.post (std::bind (&SSUServer::HandleReceivedPackets, this, packets, &m_SessionsV6)); ReceiveV6 (); } else { delete packet; if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "SSU: v6 receive error: ", ecode.message ()); m_SocketV6.close (); OpenSocketV6 (); ReceiveV6 (); } } } void SSUServer::HandleReceivedPackets (std::vector packets, std::map > * sessions) { std::shared_ptr session; for (auto& packet: packets) { try { if (!session || session->GetRemoteEndpoint () != packet->from) // we received packet for other session than previous { if (session) session->FlushData (); auto it = sessions->find (packet->from); if (it != sessions->end ()) session = it->second; if (!session) { session = std::make_shared (*this, packet->from); session->WaitForConnect (); (*sessions)[packet->from] = session; LogPrint (eLogDebug, "SSU: new session from ", packet->from.address ().to_string (), ":", packet->from.port (), " created"); } } session->ProcessNextMessage (packet->buf, packet->len, packet->from); } catch (std::exception& ex) { LogPrint (eLogError, "SSU: HandleReceivedPackets ", ex.what ()); if (session) session->FlushData (); session = nullptr; } delete packet; } if (session) session->FlushData (); } std::shared_ptr SSUServer::FindSession (std::shared_ptr router) const { if (!router) return nullptr; auto address = router->GetSSUAddress (true); // v4 only if (!address) return nullptr; auto session = FindSession (boost::asio::ip::udp::endpoint (address->host, address->port)); if (session || !context.SupportsV6 ()) return session; // try v6 address = router->GetSSUV6Address (); if (!address) return nullptr; return FindSession (boost::asio::ip::udp::endpoint (address->host, address->port)); } std::shared_ptr SSUServer::FindSession (const boost::asio::ip::udp::endpoint& e) const { auto& sessions = e.address ().is_v6 () ? m_SessionsV6 : m_Sessions; auto it = sessions.find (e); if (it != sessions.end ()) return it->second; else return nullptr; } void SSUServer::CreateSession (std::shared_ptr router, bool peerTest, bool v4only) { auto address = router->GetSSUAddress (v4only || !context.SupportsV6 ()); if (address) CreateSession (router, address->host, address->port, peerTest); else LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); } void SSUServer::CreateSession (std::shared_ptr router, const boost::asio::ip::address& addr, int port, bool peerTest) { if (router) { if (router->UsesIntroducer ()) m_Service.post (std::bind (&SSUServer::CreateSessionThroughIntroducer, this, router, peerTest)); // always V4 thread else { boost::asio::ip::udp::endpoint remoteEndpoint (addr, port); auto& s = addr.is_v6 () ? m_ServiceV6 : m_Service; s.post (std::bind (&SSUServer::CreateDirectSession, this, router, remoteEndpoint, peerTest)); } } } void SSUServer::CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest) { auto& sessions = remoteEndpoint.address ().is_v6 () ? m_SessionsV6 : m_Sessions; auto it = sessions.find (remoteEndpoint); if (it != sessions.end ()) { auto session = it->second; if (peerTest && session->GetState () == eSessionStateEstablished) session->SendPeerTest (); } else { // otherwise create new session auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); sessions[remoteEndpoint] = session; // connect LogPrint (eLogDebug, "SSU: Creating new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] ", remoteEndpoint.address ().to_string (), ":", remoteEndpoint.port ()); session->Connect (); } } void SSUServer::CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest) { if (router && router->UsesIntroducer ()) { auto address = router->GetSSUAddress (true); // v4 only for now if (address) { boost::asio::ip::udp::endpoint remoteEndpoint (address->host, address->port); auto it = m_Sessions.find (remoteEndpoint); // check if session is presented already if (it != m_Sessions.end ()) { auto session = it->second; if (peerTest && session->GetState () == eSessionStateEstablished) session->SendPeerTest (); return; } // create new session int numIntroducers = address->ssu->introducers.size (); if (numIntroducers > 0) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); std::shared_ptr introducerSession; const i2p::data::RouterInfo::Introducer * introducer = nullptr; // we might have a session to introducer already for (int i = 0; i < numIntroducers; i++) { auto intr = &(address->ssu->introducers[i]); if (intr->iExp > 0 && ts > intr->iExp) continue; // skip expired introducer boost::asio::ip::udp::endpoint ep (intr->iHost, intr->iPort); if (ep.address ().is_v4 ()) // ipv4 only { if (!introducer) introducer = intr; // we pick first one for now it = m_Sessions.find (ep); if (it != m_Sessions.end ()) { introducerSession = it->second; break; } } } if (!introducer) { LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no ipv4 non-expired introducers presented"); return; } if (introducerSession) // session found LogPrint (eLogWarning, "SSU: Session to introducer already exists"); else // create new { LogPrint (eLogDebug, "SSU: Creating new session to introducer ", introducer->iHost); boost::asio::ip::udp::endpoint introducerEndpoint (introducer->iHost, introducer->iPort); introducerSession = std::make_shared (*this, introducerEndpoint, router); m_Sessions[introducerEndpoint] = introducerSession; } #if BOOST_VERSION >= 104900 if (!address->host.is_unspecified () && address->port) #endif { // create session auto session = std::make_shared (*this, remoteEndpoint, router, peerTest); m_Sessions[remoteEndpoint] = session; // introduce LogPrint (eLogInfo, "SSU: Introduce new session to [", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), "] through introducer ", introducer->iHost, ":", introducer->iPort); session->WaitForIntroduction (); if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable { uint8_t buf[1]; Send (buf, 0, remoteEndpoint); // send HolePunch } } introducerSession->Introduce (*introducer, router); } else LogPrint (eLogWarning, "SSU: Can't connect to unreachable router and no introducers present"); } else LogPrint (eLogWarning, "SSU: Router ", i2p::data::GetIdentHashAbbreviation (router->GetIdentHash ()), " doesn't have SSU address"); } } void SSUServer::DeleteSession (std::shared_ptr session) { if (session) { session->Close (); auto& ep = session->GetRemoteEndpoint (); if (ep.address ().is_v6 ()) m_SessionsV6.erase (ep); else m_Sessions.erase (ep); } } void SSUServer::DeleteAllSessions () { for (auto& it: m_Sessions) it.second->Close (); m_Sessions.clear (); for (auto& it: m_SessionsV6) it.second->Close (); m_SessionsV6.clear (); } template std::shared_ptr SSUServer::GetRandomV4Session (Filter filter) // v4 only { std::vector > filteredSessions; for (const auto& s :m_Sessions) if (filter (s.second)) filteredSessions.push_back (s.second); if (filteredSessions.size () > 0) { auto ind = rand () % filteredSessions.size (); return filteredSessions[ind]; } return nullptr; } std::shared_ptr SSUServer::GetRandomEstablishedV4Session (std::shared_ptr excluded) // v4 only { return GetRandomV4Session ( [excluded](std::shared_ptr session)->bool { return session->GetState () == eSessionStateEstablished && session != excluded; } ); } template std::shared_ptr SSUServer::GetRandomV6Session (Filter filter) // v6 only { std::vector > filteredSessions; for (const auto& s :m_SessionsV6) if (filter (s.second)) filteredSessions.push_back (s.second); if (filteredSessions.size () > 0) { auto ind = rand () % filteredSessions.size (); return filteredSessions[ind]; } return nullptr; } std::shared_ptr SSUServer::GetRandomEstablishedV6Session (std::shared_ptr excluded) // v6 only { return GetRandomV6Session ( [excluded](std::shared_ptr session)->bool { return session->GetState () == eSessionStateEstablished && session != excluded; } ); } std::set SSUServer::FindIntroducers (int maxNumIntroducers) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); std::set ret; for (int i = 0; i < maxNumIntroducers; i++) { auto session = GetRandomV4Session ( [&ret, ts](std::shared_ptr session)->bool { return session->GetRelayTag () && !ret.count (session.get ()) && session->GetState () == eSessionStateEstablished && ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION; } ); if (session) { ret.insert (session.get ()); break; } } return ret; } void SSUServer::ScheduleIntroducersUpdateTimer () { m_IntroducersUpdateTimer.expires_from_now (boost::posix_time::seconds(SSU_KEEP_ALIVE_INTERVAL)); m_IntroducersUpdateTimer.async_wait (std::bind (&SSUServer::HandleIntroducersUpdateTimer, this, std::placeholders::_1)); } void SSUServer::HandleIntroducersUpdateTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { // timeout expired if (i2p::context.GetStatus () == eRouterStatusTesting) { // we still don't know if we need introducers ScheduleIntroducersUpdateTimer (); return; } if (i2p::context.GetStatus () == eRouterStatusOK) return; // we don't need introducers anymore // we are firewalled if (!i2p::context.IsUnreachable ()) i2p::context.SetUnreachable (); std::list newList; size_t numIntroducers = 0; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (const auto& it : m_Introducers) { auto session = FindSession (it); if (session && ts < session->GetCreationTime () + SSU_TO_INTRODUCER_SESSION_DURATION) { session->SendKeepAlive (); newList.push_back (it); numIntroducers++; } else i2p::context.RemoveIntroducer (it); } if (numIntroducers < SSU_MAX_NUM_INTRODUCERS) { // create new auto introducers = FindIntroducers (SSU_MAX_NUM_INTRODUCERS); for (const auto& it1: introducers) { const auto& ep = it1->GetRemoteEndpoint (); i2p::data::RouterInfo::Introducer introducer; introducer.iHost = ep.address (); introducer.iPort = ep.port (); introducer.iTag = it1->GetRelayTag (); introducer.iKey = it1->GetIntroKey (); if (i2p::context.AddIntroducer (introducer)) { newList.push_back (ep); if (newList.size () >= SSU_MAX_NUM_INTRODUCERS) break; } } } m_Introducers = newList; if (m_Introducers.size () < SSU_MAX_NUM_INTRODUCERS) { auto introducer = i2p::data::netdb.GetRandomIntroducer (); if (introducer) CreateSession (introducer); } ScheduleIntroducersUpdateTimer (); } } void SSUServer::NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session) { m_PeerTests[nonce] = { i2p::util::GetMillisecondsSinceEpoch (), role, session }; } PeerTestParticipant SSUServer::GetPeerTestParticipant (uint32_t nonce) { auto it = m_PeerTests.find (nonce); if (it != m_PeerTests.end ()) return it->second.role; else return ePeerTestParticipantUnknown; } std::shared_ptr SSUServer::GetPeerTestSession (uint32_t nonce) { auto it = m_PeerTests.find (nonce); if (it != m_PeerTests.end ()) return it->second.session; else return nullptr; } void SSUServer::UpdatePeerTest (uint32_t nonce, PeerTestParticipant role) { auto it = m_PeerTests.find (nonce); if (it != m_PeerTests.end ()) it->second.role = role; } void SSUServer::RemovePeerTest (uint32_t nonce) { m_PeerTests.erase (nonce); } void SSUServer::SchedulePeerTestsCleanupTimer () { m_PeerTestsCleanupTimer.expires_from_now (boost::posix_time::seconds(SSU_PEER_TEST_TIMEOUT)); m_PeerTestsCleanupTimer.async_wait (std::bind (&SSUServer::HandlePeerTestsCleanupTimer, this, std::placeholders::_1)); } void SSUServer::HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { int numDeleted = 0; uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto it = m_PeerTests.begin (); it != m_PeerTests.end ();) { if (ts > it->second.creationTime + SSU_PEER_TEST_TIMEOUT*1000LL) { numDeleted++; it = m_PeerTests.erase (it); } else ++it; } if (numDeleted > 0) LogPrint (eLogDebug, "SSU: ", numDeleted, " peer tests have been expired"); SchedulePeerTestsCleanupTimer (); } } void SSUServer::ScheduleTermination () { m_TerminationTimer.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT)); m_TerminationTimer.async_wait (std::bind (&SSUServer::HandleTerminationTimer, this, std::placeholders::_1)); } void SSUServer::HandleTerminationTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto& it: m_Sessions) if (it.second->IsTerminationTimeoutExpired (ts)) { auto session = it.second; m_Service.post ([session] { LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); session->Failed (); }); } ScheduleTermination (); } } void SSUServer::ScheduleTerminationV6 () { m_TerminationTimerV6.expires_from_now (boost::posix_time::seconds(SSU_TERMINATION_CHECK_TIMEOUT)); m_TerminationTimerV6.async_wait (std::bind (&SSUServer::HandleTerminationTimerV6, this, std::placeholders::_1)); } void SSUServer::HandleTerminationTimerV6 (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto& it: m_SessionsV6) if (it.second->IsTerminationTimeoutExpired (ts)) { auto session = it.second; m_ServiceV6.post ([session] { LogPrint (eLogWarning, "SSU: no activity with ", session->GetRemoteEndpoint (), " for ", session->GetTerminationTimeout (), " seconds"); session->Failed (); }); } ScheduleTerminationV6 (); } } } } i2pd-2.17.0/libi2pd/SSU.h000066400000000000000000000127551321131324000146070ustar00rootroot00000000000000#ifndef SSU_H__ #define SSU_H__ #include #include #include #include #include #include #include #include #include "Crypto.h" #include "I2PEndian.h" #include "Identity.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "SSUSession.h" namespace i2p { namespace transport { const int SSU_KEEP_ALIVE_INTERVAL = 30; // 30 seconds const int SSU_PEER_TEST_TIMEOUT = 60; // 60 seconds const int SSU_TO_INTRODUCER_SESSION_DURATION = 3600; // 1 hour const int SSU_TERMINATION_CHECK_TIMEOUT = 30; // 30 seconds const size_t SSU_MAX_NUM_INTRODUCERS = 3; const size_t SSU_SOCKET_RECEIVE_BUFFER_SIZE = 0x1FFFF; // 128K const size_t SSU_SOCKET_SEND_BUFFER_SIZE = 0x1FFFF; // 128K struct SSUPacket { i2p::crypto::AESAlignedBuffer buf; // max MTU + iv + size boost::asio::ip::udp::endpoint from; size_t len; }; class SSUServer { public: SSUServer (int port); SSUServer (const boost::asio::ip::address & addr, int port); // ipv6 only constructor ~SSUServer (); void Start (); void Stop (); void CreateSession (std::shared_ptr router, bool peerTest = false, bool v4only = false); void CreateSession (std::shared_ptr router, const boost::asio::ip::address& addr, int port, bool peerTest = false); void CreateDirectSession (std::shared_ptr router, boost::asio::ip::udp::endpoint remoteEndpoint, bool peerTest); std::shared_ptr FindSession (std::shared_ptr router) const; std::shared_ptr FindSession (const boost::asio::ip::udp::endpoint& e) const; std::shared_ptr GetRandomEstablishedV4Session (std::shared_ptr excluded); std::shared_ptr GetRandomEstablishedV6Session (std::shared_ptr excluded); void DeleteSession (std::shared_ptr session); void DeleteAllSessions (); boost::asio::io_service& GetService () { return m_Service; }; boost::asio::io_service& GetServiceV6 () { return m_ServiceV6; }; const boost::asio::ip::udp::endpoint& GetEndpoint () const { return m_Endpoint; }; void Send (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& to); void AddRelay (uint32_t tag, std::shared_ptr relay); void RemoveRelay (uint32_t tag); std::shared_ptr FindRelaySession (uint32_t tag); void NewPeerTest (uint32_t nonce, PeerTestParticipant role, std::shared_ptr session = nullptr); PeerTestParticipant GetPeerTestParticipant (uint32_t nonce); std::shared_ptr GetPeerTestSession (uint32_t nonce); void UpdatePeerTest (uint32_t nonce, PeerTestParticipant role); void RemovePeerTest (uint32_t nonce); private: void OpenSocket (); void OpenSocketV6 (); void Run (); void RunV6 (); void RunReceivers (); void RunReceiversV6 (); void Receive (); void ReceiveV6 (); void HandleReceivedFrom (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); void HandleReceivedFromV6 (const boost::system::error_code& ecode, std::size_t bytes_transferred, SSUPacket * packet); void HandleReceivedPackets (std::vector packets, std::map >* sessions); void CreateSessionThroughIntroducer (std::shared_ptr router, bool peerTest = false); template std::shared_ptr GetRandomV4Session (Filter filter); template std::shared_ptr GetRandomV6Session (Filter filter); std::set FindIntroducers (int maxNumIntroducers); void ScheduleIntroducersUpdateTimer (); void HandleIntroducersUpdateTimer (const boost::system::error_code& ecode); void SchedulePeerTestsCleanupTimer (); void HandlePeerTestsCleanupTimer (const boost::system::error_code& ecode); // timer void ScheduleTermination (); void HandleTerminationTimer (const boost::system::error_code& ecode); void ScheduleTerminationV6 (); void HandleTerminationTimerV6 (const boost::system::error_code& ecode); private: struct PeerTest { uint64_t creationTime; PeerTestParticipant role; std::shared_ptr session; // for Bob to Alice }; bool m_OnlyV6; bool m_IsRunning; std::thread * m_Thread, * m_ThreadV6, * m_ReceiversThread, * m_ReceiversThreadV6; boost::asio::io_service m_Service, m_ServiceV6, m_ReceiversService, m_ReceiversServiceV6; boost::asio::io_service::work m_Work, m_WorkV6, m_ReceiversWork, m_ReceiversWorkV6; boost::asio::ip::udp::endpoint m_Endpoint, m_EndpointV6; boost::asio::ip::udp::socket m_Socket, m_SocketV6; boost::asio::deadline_timer m_IntroducersUpdateTimer, m_PeerTestsCleanupTimer, m_TerminationTimer, m_TerminationTimerV6; std::list m_Introducers; // introducers we are connected to std::map > m_Sessions, m_SessionsV6; std::map > m_Relays; // we are introducer std::map m_PeerTests; // nonce -> creation time in milliseconds public: // for HTTP only const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; const decltype(m_SessionsV6)& GetSessionsV6 () const { return m_SessionsV6; }; }; } } #endif i2pd-2.17.0/libi2pd/SSUData.cpp000066400000000000000000000355321321131324000157320ustar00rootroot00000000000000#include #include #include "Log.h" #include "Timestamp.h" #include "NetDb.hpp" #include "SSU.h" #include "SSUData.h" #ifdef WITH_EVENTS #include "Event.h" #endif namespace i2p { namespace transport { void IncompleteMessage::AttachNextFragment (const uint8_t * fragment, size_t fragmentSize) { if (msg->len + fragmentSize > msg->maxLen) { LogPrint (eLogWarning, "SSU: I2NP message size ", msg->maxLen, " is not enough"); auto newMsg = NewI2NPMessage (); *newMsg = *msg; msg = newMsg; } if (msg->Concat (fragment, fragmentSize) < fragmentSize) LogPrint (eLogError, "SSU: I2NP buffer overflow ", msg->maxLen); nextFragmentNum++; } SSUData::SSUData (SSUSession& session): m_Session (session), m_ResendTimer (session.GetService ()), m_IncompleteMessagesCleanupTimer (session.GetService ()), m_MaxPacketSize (session.IsV6 () ? SSU_V6_MAX_PACKET_SIZE : SSU_V4_MAX_PACKET_SIZE), m_PacketSize (m_MaxPacketSize), m_LastMessageReceivedTime (0) { } SSUData::~SSUData () { } void SSUData::Start () { ScheduleIncompleteMessagesCleanup (); } void SSUData::Stop () { m_ResendTimer.cancel (); m_IncompleteMessagesCleanupTimer.cancel (); m_IncompleteMessages.clear (); m_SentMessages.clear (); m_ReceivedMessages.clear (); } void SSUData::AdjustPacketSize (std::shared_ptr remoteRouter) { if (!remoteRouter) return; auto ssuAddress = remoteRouter->GetSSUAddress (); if (ssuAddress && ssuAddress->ssu->mtu) { if (m_Session.IsV6 ()) m_PacketSize = ssuAddress->ssu->mtu - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; else m_PacketSize = ssuAddress->ssu->mtu - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; if (m_PacketSize > 0) { // make sure packet size multiple of 16 m_PacketSize >>= 4; m_PacketSize <<= 4; if (m_PacketSize > m_MaxPacketSize) m_PacketSize = m_MaxPacketSize; LogPrint (eLogDebug, "SSU: MTU=", ssuAddress->ssu->mtu, " packet size=", m_PacketSize); } else { LogPrint (eLogWarning, "SSU: Unexpected MTU ", ssuAddress->ssu->mtu); m_PacketSize = m_MaxPacketSize; } } } void SSUData::UpdatePacketSize (const i2p::data::IdentHash& remoteIdent) { auto routerInfo = i2p::data::netdb.FindRouter (remoteIdent); if (routerInfo) AdjustPacketSize (routerInfo); } void SSUData::ProcessSentMessageAck (uint32_t msgID) { auto it = m_SentMessages.find (msgID); if (it != m_SentMessages.end ()) { m_SentMessages.erase (it); if (m_SentMessages.empty ()) m_ResendTimer.cancel (); } } void SSUData::ProcessAcks (uint8_t *& buf, uint8_t flag) { if (flag & DATA_FLAG_EXPLICIT_ACKS_INCLUDED) { // explicit ACKs uint8_t numAcks =*buf; buf++; for (int i = 0; i < numAcks; i++) ProcessSentMessageAck (bufbe32toh (buf+i*4)); buf += numAcks*4; } if (flag & DATA_FLAG_ACK_BITFIELDS_INCLUDED) { // explicit ACK bitfields uint8_t numBitfields =*buf; buf++; for (int i = 0; i < numBitfields; i++) { uint32_t msgID = bufbe32toh (buf); buf += 4; // msgID auto it = m_SentMessages.find (msgID); // process individual Ack bitfields bool isNonLast = false; int fragment = 0; do { uint8_t bitfield = *buf; isNonLast = bitfield & 0x80; bitfield &= 0x7F; // clear MSB if (bitfield && it != m_SentMessages.end ()) { int numSentFragments = it->second->fragments.size (); // process bits uint8_t mask = 0x01; for (int j = 0; j < 7; j++) { if (bitfield & mask) { if (fragment < numSentFragments) it->second->fragments[fragment].reset (nullptr); } fragment++; mask <<= 1; } } buf++; } while (isNonLast); } } } void SSUData::ProcessFragments (uint8_t * buf) { uint8_t numFragments = *buf; // number of fragments buf++; for (int i = 0; i < numFragments; i++) { uint32_t msgID = bufbe32toh (buf); // message ID buf += 4; uint8_t frag[4] = {0}; memcpy (frag + 1, buf, 3); buf += 3; uint32_t fragmentInfo = bufbe32toh (frag); // fragment info uint16_t fragmentSize = fragmentInfo & 0x3FFF; // bits 0 - 13 bool isLast = fragmentInfo & 0x010000; // bit 16 uint8_t fragmentNum = fragmentInfo >> 17; // bits 23 - 17 if (fragmentSize >= SSU_V4_MAX_PACKET_SIZE) { LogPrint (eLogError, "SSU: Fragment size ", fragmentSize, " exceeds max SSU packet size"); return; } // find message with msgID auto it = m_IncompleteMessages.find (msgID); if (it == m_IncompleteMessages.end ()) { // create new message auto msg = NewI2NPShortMessage (); msg->len -= I2NP_SHORT_HEADER_SIZE; it = m_IncompleteMessages.insert (std::make_pair (msgID, std::unique_ptr(new IncompleteMessage (msg)))).first; } std::unique_ptr& incompleteMessage = it->second; // handle current fragment if (fragmentNum == incompleteMessage->nextFragmentNum) { // expected fragment incompleteMessage->AttachNextFragment (buf, fragmentSize); if (!isLast && !incompleteMessage->savedFragments.empty ()) { // try saved fragments for (auto it1 = incompleteMessage->savedFragments.begin (); it1 != incompleteMessage->savedFragments.end ();) { auto& savedFragment = *it1; if (savedFragment->fragmentNum == incompleteMessage->nextFragmentNum) { incompleteMessage->AttachNextFragment (savedFragment->buf, savedFragment->len); isLast = savedFragment->isLast; incompleteMessage->savedFragments.erase (it1++); } else break; } if (isLast) LogPrint (eLogDebug, "SSU: Message ", msgID, " complete"); } } else { if (fragmentNum < incompleteMessage->nextFragmentNum) // duplicate fragment LogPrint (eLogWarning, "SSU: Duplicate fragment ", (int)fragmentNum, " of message ", msgID, ", ignored"); else { // missing fragment LogPrint (eLogWarning, "SSU: Missing fragments from ", (int)incompleteMessage->nextFragmentNum, " to ", fragmentNum - 1, " of message ", msgID); auto savedFragment = new Fragment (fragmentNum, buf, fragmentSize, isLast); if (incompleteMessage->savedFragments.insert (std::unique_ptr(savedFragment)).second) incompleteMessage->lastFragmentInsertTime = i2p::util::GetSecondsSinceEpoch (); else LogPrint (eLogWarning, "SSU: Fragment ", (int)fragmentNum, " of message ", msgID, " already saved"); } isLast = false; } if (isLast) { // delete incomplete message auto msg = incompleteMessage->msg; incompleteMessage->msg = nullptr; m_IncompleteMessages.erase (msgID); // process message SendMsgAck (msgID); msg->FromSSU (msgID); if (m_Session.GetState () == eSessionStateEstablished) { if (!m_ReceivedMessages.count (msgID)) { m_ReceivedMessages.insert (msgID); m_LastMessageReceivedTime = i2p::util::GetSecondsSinceEpoch (); if (!msg->IsExpired ()) { #ifdef WITH_EVENTS QueueIntEvent("transport.recvmsg", m_Session.GetIdentHashBase64(), 1); #endif m_Handler.PutNextMessage (msg); } else LogPrint (eLogDebug, "SSU: message expired"); } else LogPrint (eLogWarning, "SSU: Message ", msgID, " already received"); } else { // we expect DeliveryStatus if (msg->GetTypeID () == eI2NPDeliveryStatus) { LogPrint (eLogDebug, "SSU: session established"); m_Session.Established (); } else LogPrint (eLogError, "SSU: unexpected message ", (int)msg->GetTypeID ()); } } else SendFragmentAck (msgID, fragmentNum); buf += fragmentSize; } } void SSUData::FlushReceivedMessage () { m_Handler.Flush (); } void SSUData::ProcessMessage (uint8_t * buf, size_t len) { //uint8_t * start = buf; uint8_t flag = *buf; buf++; LogPrint (eLogDebug, "SSU: Process data, flags=", (int)flag, ", len=", len); // process acks if presented if (flag & (DATA_FLAG_ACK_BITFIELDS_INCLUDED | DATA_FLAG_EXPLICIT_ACKS_INCLUDED)) ProcessAcks (buf, flag); // extended data if presented if (flag & DATA_FLAG_EXTENDED_DATA_INCLUDED) { uint8_t extendedDataSize = *buf; buf++; // size LogPrint (eLogDebug, "SSU: extended data of ", extendedDataSize, " bytes present"); buf += extendedDataSize; } // process data ProcessFragments (buf); } void SSUData::Send (std::shared_ptr msg) { uint32_t msgID = msg->ToSSU (); if (m_SentMessages.count (msgID) > 0) { LogPrint (eLogWarning, "SSU: message ", msgID, " already sent"); return; } if (m_SentMessages.empty ()) // schedule resend at first message only ScheduleResend (); auto ret = m_SentMessages.insert (std::make_pair (msgID, std::unique_ptr(new SentMessage))); std::unique_ptr& sentMessage = ret.first->second; if (ret.second) { sentMessage->nextResendTime = i2p::util::GetSecondsSinceEpoch () + RESEND_INTERVAL; sentMessage->numResends = 0; } auto& fragments = sentMessage->fragments; size_t payloadSize = m_PacketSize - sizeof (SSUHeader) - 9; // 9 = flag + #frg(1) + messageID(4) + frag info (3) size_t len = msg->GetLength (); uint8_t * msgBuf = msg->GetSSUHeader (); uint32_t fragmentNum = 0; while (len > 0) { Fragment * fragment = new Fragment; fragment->fragmentNum = fragmentNum; uint8_t * buf = fragment->buf; uint8_t * payload = buf + sizeof (SSUHeader); *payload = DATA_FLAG_WANT_REPLY; // for compatibility payload++; *payload = 1; // always 1 message fragment per message payload++; htobe32buf (payload, msgID); payload += 4; bool isLast = (len <= payloadSize); size_t size = isLast ? len : payloadSize; uint32_t fragmentInfo = (fragmentNum << 17); if (isLast) fragmentInfo |= 0x010000; fragmentInfo |= size; fragmentInfo = htobe32 (fragmentInfo); memcpy (payload, (uint8_t *)(&fragmentInfo) + 1, 3); payload += 3; memcpy (payload, msgBuf, size); size += payload - buf; if (size & 0x0F) // make sure 16 bytes boundary size = ((size >> 4) + 1) << 4; // (/16 + 1)*16 fragment->len = size; fragments.push_back (std::unique_ptr (fragment)); // encrypt message with session key m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, size); try { m_Session.Send (buf, size); } catch (boost::system::system_error& ec) { LogPrint (eLogWarning, "SSU: Can't send data fragment ", ec.what ()); } if (!isLast) { len -= payloadSize; msgBuf += payloadSize; } else len = 0; fragmentNum++; } } void SSUData::SendMsgAck (uint32_t msgID) { uint8_t buf[48 + 18] = {0}; // actual length is 44 = 37 + 7 but pad it to multiple of 16 uint8_t * payload = buf + sizeof (SSUHeader); *payload = DATA_FLAG_EXPLICIT_ACKS_INCLUDED; // flag payload++; *payload = 1; // number of ACKs payload++; htobe32buf (payload, msgID); // msgID payload += 4; *payload = 0; // number of fragments // encrypt message with session key m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); m_Session.Send (buf, 48); } void SSUData::SendFragmentAck (uint32_t msgID, int fragmentNum) { if (fragmentNum > 64) { LogPrint (eLogWarning, "SSU: Fragment number ", fragmentNum, " exceeds 64"); return; } uint8_t buf[64 + 18] = {0}; uint8_t * payload = buf + sizeof (SSUHeader); *payload = DATA_FLAG_ACK_BITFIELDS_INCLUDED; // flag payload++; *payload = 1; // number of ACK bitfields payload++; // one ack *(uint32_t *)(payload) = htobe32 (msgID); // msgID payload += 4; div_t d = div (fragmentNum, 7); memset (payload, 0x80, d.quot); // 0x80 means non-last payload += d.quot; *payload = 0x01 << d.rem; // set corresponding bit payload++; *payload = 0; // number of fragments size_t len = d.quot < 4 ? 48 : 64; // 48 = 37 + 7 + 4 (3+1) // encrypt message with session key m_Session.FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, len); m_Session.Send (buf, len); } void SSUData::ScheduleResend() { m_ResendTimer.cancel (); m_ResendTimer.expires_from_now (boost::posix_time::seconds(RESEND_INTERVAL)); auto s = m_Session.shared_from_this(); m_ResendTimer.async_wait ([s](const boost::system::error_code& ecode) { s->m_Data.HandleResendTimer (ecode); }); } void SSUData::HandleResendTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); int numResent = 0; for (auto it = m_SentMessages.begin (); it != m_SentMessages.end ();) { if (ts >= it->second->nextResendTime) { if (it->second->numResends < MAX_NUM_RESENDS) { for (auto& f: it->second->fragments) if (f) { try { m_Session.Send (f->buf, f->len); // resend numResent++; } catch (boost::system::system_error& ec) { LogPrint (eLogWarning, "SSU: Can't resend data fragment ", ec.what ()); } } it->second->numResends++; it->second->nextResendTime += it->second->numResends*RESEND_INTERVAL; ++it; } else { LogPrint (eLogInfo, "SSU: message has not been ACKed after ", MAX_NUM_RESENDS, " attempts, deleted"); it = m_SentMessages.erase (it); } } else ++it; } if (m_SentMessages.empty ()) return; // nothing to resend if (numResent < MAX_OUTGOING_WINDOW_SIZE) ScheduleResend (); else { LogPrint (eLogError, "SSU: resend window exceeds max size. Session terminated"); m_Session.Close (); } } } void SSUData::ScheduleIncompleteMessagesCleanup () { m_IncompleteMessagesCleanupTimer.cancel (); m_IncompleteMessagesCleanupTimer.expires_from_now (boost::posix_time::seconds(INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT)); auto s = m_Session.shared_from_this(); m_IncompleteMessagesCleanupTimer.async_wait ([s](const boost::system::error_code& ecode) { s->m_Data.HandleIncompleteMessagesCleanupTimer (ecode); }); } void SSUData::HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) { if (ts > it->second->lastFragmentInsertTime + INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT) { LogPrint (eLogWarning, "SSU: message ", it->first, " was not completed in ", INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT, " seconds, deleted"); it = m_IncompleteMessages.erase (it); } else ++it; } // decay if (m_ReceivedMessages.size () > MAX_NUM_RECEIVED_MESSAGES || i2p::util::GetSecondsSinceEpoch () > m_LastMessageReceivedTime + DECAY_INTERVAL) m_ReceivedMessages.clear (); ScheduleIncompleteMessagesCleanup (); } } } } i2pd-2.17.0/libi2pd/SSUData.h000066400000000000000000000074431321131324000153770ustar00rootroot00000000000000#ifndef SSU_DATA_H__ #define SSU_DATA_H__ #include #include #include #include #include #include #include #include "I2NPProtocol.h" #include "Identity.h" #include "RouterInfo.h" namespace i2p { namespace transport { const size_t SSU_MTU_V4 = 1484; #ifdef MESHNET const size_t SSU_MTU_V6 = 1286; #else const size_t SSU_MTU_V6 = 1488; #endif const size_t IPV4_HEADER_SIZE = 20; const size_t IPV6_HEADER_SIZE = 40; const size_t UDP_HEADER_SIZE = 8; const size_t SSU_V4_MAX_PACKET_SIZE = SSU_MTU_V4 - IPV4_HEADER_SIZE - UDP_HEADER_SIZE; // 1456 const size_t SSU_V6_MAX_PACKET_SIZE = SSU_MTU_V6 - IPV6_HEADER_SIZE - UDP_HEADER_SIZE; // 1440 const int RESEND_INTERVAL = 3; // in seconds const int MAX_NUM_RESENDS = 5; const int DECAY_INTERVAL = 20; // in seconds const int INCOMPLETE_MESSAGES_CLEANUP_TIMEOUT = 30; // in seconds const unsigned int MAX_NUM_RECEIVED_MESSAGES = 1000; // how many msgID we store for duplicates check const int MAX_OUTGOING_WINDOW_SIZE = 200; // how many unacked message we can store // data flags const uint8_t DATA_FLAG_EXTENDED_DATA_INCLUDED = 0x02; const uint8_t DATA_FLAG_WANT_REPLY = 0x04; const uint8_t DATA_FLAG_REQUEST_PREVIOUS_ACKS = 0x08; const uint8_t DATA_FLAG_EXPLICIT_CONGESTION_NOTIFICATION = 0x10; const uint8_t DATA_FLAG_ACK_BITFIELDS_INCLUDED = 0x40; const uint8_t DATA_FLAG_EXPLICIT_ACKS_INCLUDED = 0x80; struct Fragment { int fragmentNum; size_t len; bool isLast; uint8_t buf[SSU_V4_MAX_PACKET_SIZE + 18]; // use biggest Fragment () = default; Fragment (int n, const uint8_t * b, int l, bool last): fragmentNum (n), len (l), isLast (last) { memcpy (buf, b, len); }; }; struct FragmentCmp { bool operator() (const std::unique_ptr& f1, const std::unique_ptr& f2) const { return f1->fragmentNum < f2->fragmentNum; }; }; struct IncompleteMessage { std::shared_ptr msg; int nextFragmentNum; uint32_t lastFragmentInsertTime; // in seconds std::set, FragmentCmp> savedFragments; IncompleteMessage (std::shared_ptr m): msg (m), nextFragmentNum (0), lastFragmentInsertTime (0) {}; void AttachNextFragment (const uint8_t * fragment, size_t fragmentSize); }; struct SentMessage { std::vector > fragments; uint32_t nextResendTime; // in seconds int numResends; }; class SSUSession; class SSUData { public: SSUData (SSUSession& session); ~SSUData (); void Start (); void Stop (); void ProcessMessage (uint8_t * buf, size_t len); void FlushReceivedMessage (); void Send (std::shared_ptr msg); void AdjustPacketSize (std::shared_ptr remoteRouter); void UpdatePacketSize (const i2p::data::IdentHash& remoteIdent); private: void SendMsgAck (uint32_t msgID); void SendFragmentAck (uint32_t msgID, int fragmentNum); void ProcessAcks (uint8_t *& buf, uint8_t flag); void ProcessFragments (uint8_t * buf); void ProcessSentMessageAck (uint32_t msgID); void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); void ScheduleIncompleteMessagesCleanup (); void HandleIncompleteMessagesCleanupTimer (const boost::system::error_code& ecode); private: SSUSession& m_Session; std::map > m_IncompleteMessages; std::map > m_SentMessages; std::unordered_set m_ReceivedMessages; boost::asio::deadline_timer m_ResendTimer, m_IncompleteMessagesCleanupTimer; int m_MaxPacketSize, m_PacketSize; i2p::I2NPMessagesHandler m_Handler; uint32_t m_LastMessageReceivedTime; // in second }; } } #endif i2pd-2.17.0/libi2pd/SSUSession.cpp000066400000000000000000001152761321131324000165100ustar00rootroot00000000000000#include #include "Crypto.h" #include "Log.h" #include "Timestamp.h" #include "RouterContext.h" #include "Transports.h" #include "NetDb.hpp" #include "SSU.h" #include "SSUSession.h" namespace i2p { namespace transport { SSUSession::SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, std::shared_ptr router, bool peerTest ): TransportSession (router, SSU_TERMINATION_TIMEOUT), m_Server (server), m_RemoteEndpoint (remoteEndpoint), m_ConnectTimer (GetService ()), m_IsPeerTest (peerTest),m_State (eSessionStateUnknown), m_IsSessionKey (false), m_RelayTag (0), m_SentRelayTag (0), m_Data (*this), m_IsDataReceived (false) { if (router) { // we are client auto address = router->GetSSUAddress (false); if (address) m_IntroKey = address->ssu->key; m_Data.AdjustPacketSize (router); // mtu } else { // we are server auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); if (address) m_IntroKey = address->ssu->key; } m_CreationTime = i2p::util::GetSecondsSinceEpoch (); } SSUSession::~SSUSession () { } boost::asio::io_service& SSUSession::GetService () { return IsV6 () ? m_Server.GetServiceV6 () : m_Server.GetService (); } void SSUSession::CreateAESandMacKey (const uint8_t * pubKey) { uint8_t sharedKey[256]; m_DHKeysPair->Agree (pubKey, sharedKey); uint8_t * sessionKey = m_SessionKey, * macKey = m_MacKey; if (sharedKey[0] & 0x80) { sessionKey[0] = 0; memcpy (sessionKey + 1, sharedKey, 31); memcpy (macKey, sharedKey + 31, 32); } else if (sharedKey[0]) { memcpy (sessionKey, sharedKey, 32); memcpy (macKey, sharedKey + 32, 32); } else { // find first non-zero byte uint8_t * nonZero = sharedKey + 1; while (!*nonZero) { nonZero++; if (nonZero - sharedKey > 32) { LogPrint (eLogWarning, "SSU: first 32 bytes of shared key is all zeros. Ignored"); return; } } memcpy (sessionKey, nonZero, 32); SHA256(nonZero, 64 - (nonZero - sharedKey), macKey); } m_IsSessionKey = true; m_SessionKeyEncryption.SetKey (m_SessionKey); m_SessionKeyDecryption.SetKey (m_SessionKey); } void SSUSession::ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { m_NumReceivedBytes += len; i2p::transport::transports.UpdateReceivedBytes (len); if (m_State == eSessionStateIntroduced) { // HolePunch received LogPrint (eLogDebug, "SSU: HolePunch of ", len, " bytes received"); m_State = eSessionStateUnknown; Connect (); } else { if (!len) return; // ignore zero-length packets if (m_State == eSessionStateEstablished) m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); if (m_IsSessionKey && Validate (buf, len, m_MacKey)) // try session key first DecryptSessionKey (buf, len); else { if (m_State == eSessionStateEstablished) Reset (); // new session key required // try intro key depending on side if (Validate (buf, len, m_IntroKey)) Decrypt (buf, len, m_IntroKey); else { // try own intro key auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); if (!address) { LogPrint (eLogInfo, "SSU is not supported"); return; } if (Validate (buf, len, address->ssu->key)) Decrypt (buf, len, address->ssu->key); else { LogPrint (eLogWarning, "SSU: MAC verification failed ", len, " bytes from ", senderEndpoint); m_Server.DeleteSession (shared_from_this ()); return; } } } // successfully decrypted ProcessMessage (buf, len, senderEndpoint); } } size_t SSUSession::GetSSUHeaderSize (const uint8_t * buf) const { size_t s = sizeof (SSUHeader); if (((const SSUHeader *)buf)->IsExtendedOptions ()) s += buf[s] + 1; // byte right after header is extended options length return s; } void SSUSession::ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { len -= (len & 0x0F); // %16, delete extra padding if (len <= sizeof (SSUHeader)) return; // drop empty message //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved auto headerSize = GetSSUHeaderSize (buf); if (headerSize >= len) { LogPrint (eLogError, "SSU header size ", headerSize, " exceeds packet length ", len); return; } SSUHeader * header = (SSUHeader *)buf; switch (header->GetPayloadType ()) { case PAYLOAD_TYPE_DATA: ProcessData (buf + headerSize, len - headerSize); break; case PAYLOAD_TYPE_SESSION_REQUEST: ProcessSessionRequest (buf, len, senderEndpoint); // buf with header break; case PAYLOAD_TYPE_SESSION_CREATED: ProcessSessionCreated (buf, len); // buf with header break; case PAYLOAD_TYPE_SESSION_CONFIRMED: ProcessSessionConfirmed (buf, len); // buf with header break; case PAYLOAD_TYPE_PEER_TEST: LogPrint (eLogDebug, "SSU: peer test received"); ProcessPeerTest (buf + headerSize, len - headerSize, senderEndpoint); break; case PAYLOAD_TYPE_SESSION_DESTROYED: { LogPrint (eLogDebug, "SSU: session destroy received"); m_Server.DeleteSession (shared_from_this ()); break; } case PAYLOAD_TYPE_RELAY_RESPONSE: ProcessRelayResponse (buf + headerSize, len - headerSize); if (m_State != eSessionStateEstablished) m_Server.DeleteSession (shared_from_this ()); break; case PAYLOAD_TYPE_RELAY_REQUEST: LogPrint (eLogDebug, "SSU: relay request received"); ProcessRelayRequest (buf + headerSize, len - headerSize, senderEndpoint); break; case PAYLOAD_TYPE_RELAY_INTRO: LogPrint (eLogDebug, "SSU: relay intro received"); ProcessRelayIntro (buf + headerSize, len - headerSize); break; default: LogPrint (eLogWarning, "SSU: Unexpected payload type ", (int)header->GetPayloadType ()); } } void SSUSession::ProcessSessionRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { LogPrint (eLogDebug, "SSU message: session request"); bool sendRelayTag = true; auto headerSize = sizeof (SSUHeader); if (((SSUHeader *)buf)->IsExtendedOptions ()) { uint8_t extendedOptionsLen = buf[headerSize]; headerSize++; if (extendedOptionsLen >= 3) // options are presented { uint16_t flags = bufbe16toh (buf + headerSize); sendRelayTag = flags & EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG; } headerSize += extendedOptionsLen; } if (headerSize >= len) { LogPrint (eLogError, "Session reaquest header size ", headerSize, " exceeds packet length ", len); return; } m_RemoteEndpoint = senderEndpoint; if (!m_DHKeysPair) m_DHKeysPair = transports.GetNextDHKeysPair (); CreateAESandMacKey (buf + headerSize); SendSessionCreated (buf + headerSize, sendRelayTag); } void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len) { if (!IsOutgoing () || !m_DHKeysPair) { LogPrint (eLogWarning, "SSU: Unsolicited session created message"); return; } LogPrint (eLogDebug, "SSU message: session created"); m_ConnectTimer.cancel (); // connect timer SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, signed on time auto headerSize = GetSSUHeaderSize (buf); if (headerSize >= len) { LogPrint (eLogError, "Session created header size ", headerSize, " exceeds packet length ", len); return; } uint8_t * payload = buf + headerSize; uint8_t * y = payload; CreateAESandMacKey (y); s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x s.Insert (y, 256); // y payload += 256; uint8_t addressSize = *payload; payload += 1; // size uint8_t * ourAddress = payload; boost::asio::ip::address ourIP; if (addressSize == 4) // v4 { boost::asio::ip::address_v4::bytes_type bytes; memcpy (bytes.data (), ourAddress, 4); ourIP = boost::asio::ip::address_v4 (bytes); } else // v6 { boost::asio::ip::address_v6::bytes_type bytes; memcpy (bytes.data (), ourAddress, 16); ourIP = boost::asio::ip::address_v6 (bytes); } s.Insert (ourAddress, addressSize); // our IP payload += addressSize; // address uint16_t ourPort = bufbe16toh (payload); s.Insert (payload, 2); // our port payload += 2; // port if (m_RemoteEndpoint.address ().is_v4 ()) s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 else s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP v6 s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port s.Insert (payload, 8); // relayTag and signed on time m_RelayTag = bufbe32toh (payload); payload += 4; // relayTag if (i2p::context.GetStatus () == eRouterStatusTesting) { auto ts = i2p::util::GetSecondsSinceEpoch (); uint32_t signedOnTime = bufbe32toh(payload); if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) { LogPrint (eLogError, "SSU: clock skew detected ", (int)ts - signedOnTime, ". Check your clock"); i2p::context.SetError (eRouterErrorClockSkew); } } payload += 4; // signed on time // decrypt signature size_t signatureLen = m_RemoteIdentity->GetSignatureLen (); size_t paddingSize = signatureLen & 0x0F; // %16 if (paddingSize > 0) signatureLen += (16 - paddingSize); //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved m_SessionKeyDecryption.SetIV (((SSUHeader *)buf)->iv); m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); // TODO: non-const payload // verify signature if (s.Verify (m_RemoteIdentity, payload)) { LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); i2p::context.UpdateAddress (ourIP); SendSessionConfirmed (y, ourAddress, addressSize + 2); } else { LogPrint (eLogError, "SSU: message 'created' signature verification failed"); Failed (); } } void SSUSession::ProcessSessionConfirmed (const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SSU: Session confirmed received"); auto headerSize = GetSSUHeaderSize (buf); if (headerSize >= len) { LogPrint (eLogError, "SSU: Session confirmed header size ", len, " exceeds packet length ", len); return; } const uint8_t * payload = buf + headerSize; payload++; // identity fragment info uint16_t identitySize = bufbe16toh (payload); payload += 2; // size of identity fragment auto identity = std::make_shared (payload, identitySize); auto existing = i2p::data::netdb.FindRouter (identity->GetIdentHash ()); // check if exists already SetRemoteIdentity (existing ? existing->GetRouterIdentity () : identity); m_Data.UpdatePacketSize (m_RemoteIdentity->GetIdentHash ()); payload += identitySize; // identity auto ts = i2p::util::GetSecondsSinceEpoch (); uint32_t signedOnTime = bufbe32toh(payload); if (signedOnTime < ts - SSU_CLOCK_SKEW || signedOnTime > ts + SSU_CLOCK_SKEW) { LogPrint (eLogError, "SSU message 'confirmed' time difference ", (int)ts - signedOnTime, " exceeds clock skew"); Failed (); return; } if (m_SignedData) m_SignedData->Insert (payload, 4); // insert Alice's signed on time payload += 4; // signed-on time size_t paddingSize = (payload - buf) + m_RemoteIdentity->GetSignatureLen (); paddingSize &= 0x0F; // %16 if (paddingSize > 0) paddingSize = 16 - paddingSize; payload += paddingSize; // verify signature if (m_SignedData && m_SignedData->Verify (m_RemoteIdentity, payload)) { m_Data.Send (CreateDeliveryStatusMsg (0)); Established (); } else { LogPrint (eLogError, "SSU message 'confirmed' signature verification failed"); Failed (); } } void SSUSession::SendSessionRequest () { uint8_t buf[320 + 18] = {0}; // 304 bytes for ipv4, 320 for ipv6 uint8_t * payload = buf + sizeof (SSUHeader); uint8_t flag = 0; // fill extended options, 3 bytes extended options don't change message size if (i2p::context.GetStatus () == eRouterStatusOK) // we don't need relays { // tell out peer to now assign relay tag flag = SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; *payload = 2; payload++; // 1 byte length uint16_t flags = 0; // clear EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG htobe16buf (payload, flags); payload += 2; } // fill payload memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); // x bool isV4 = m_RemoteEndpoint.address ().is_v4 (); if (isV4) { payload[256] = 4; memcpy (payload + 257, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); } else { payload[256] = 16; memcpy (payload + 257, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); } // encrypt and send uint8_t iv[16]; RAND_bytes (iv, 16); // random iv FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_REQUEST, buf, isV4 ? 304 : 320, m_IntroKey, iv, m_IntroKey, flag); m_Server.Send (buf, isV4 ? 304 : 320, m_RemoteEndpoint); } void SSUSession::SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce) { auto address = i2p::context.GetRouterInfo ().GetSSUAddress (false); if (!address) { LogPrint (eLogInfo, "SSU is not supported"); return; } uint8_t buf[96 + 18] = {0}; uint8_t * payload = buf + sizeof (SSUHeader); htobe32buf (payload, introducer.iTag); payload += 4; *payload = 0; // no address payload++; htobuf16(payload, 0); // port = 0 payload += 2; *payload = 0; // challenge payload++; memcpy (payload, (const uint8_t *)address->ssu->key, 32); payload += 32; htobe32buf (payload, nonce); // nonce uint8_t iv[16]; RAND_bytes (iv, 16); // random iv if (m_State == eSessionStateEstablished) FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, m_SessionKey, iv, m_MacKey); else FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_REQUEST, buf, 96, introducer.iKey, iv, introducer.iKey); m_Server.Send (buf, 96, m_RemoteEndpoint); } void SSUSession::SendSessionCreated (const uint8_t * x, bool sendRelayTag) { auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () : i2p::context.GetRouterInfo ().GetSSUAddress (true); //v4 only if (!address) { LogPrint (eLogInfo, "SSU is not supported"); return; } SignedData s; // x,y, remote IP, remote port, our IP, our port, relayTag, signed on time s.Insert (x, 256); // x uint8_t buf[384 + 18] = {0}; uint8_t * payload = buf + sizeof (SSUHeader); memcpy (payload, m_DHKeysPair->GetPublicKey (), 256); s.Insert (payload, 256); // y payload += 256; if (m_RemoteEndpoint.address ().is_v4 ()) { // ipv4 *payload = 4; payload++; memcpy (payload, m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data(), 4); s.Insert (payload, 4); // remote endpoint IP V4 payload += 4; } else { // ipv6 *payload = 16; payload++; memcpy (payload, m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data(), 16); s.Insert (payload, 16); // remote endpoint IP V6 payload += 16; } htobe16buf (payload, m_RemoteEndpoint.port ()); s.Insert (payload, 2); // remote port payload += 2; if (address->host.is_v4 ()) s.Insert (address->host.to_v4 ().to_bytes ().data (), 4); // our IP V4 else s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6 s.Insert (htobe16 (address->port)); // our port if (sendRelayTag && i2p::context.GetRouterInfo ().IsIntroducer () && !IsV6 ()) { RAND_bytes((uint8_t *)&m_SentRelayTag, 4); if (!m_SentRelayTag) m_SentRelayTag = 1; } htobe32buf (payload, m_SentRelayTag); payload += 4; // relay tag htobe32buf (payload, i2p::util::GetSecondsSinceEpoch ()); // signed on time payload += 4; s.Insert (payload - 8, 4); // relayTag // we have to store this signed data for session confirmed // same data but signed on time, it will Alice's there m_SignedData = std::unique_ptr(new SignedData (s)); s.Insert (payload - 4, 4); // BOB's signed on time s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature uint8_t iv[16]; RAND_bytes (iv, 16); // random iv // encrypt signature and padding with newly created session key size_t signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); size_t paddingSize = signatureLen & 0x0F; // %16 if (paddingSize > 0) { // fill random padding RAND_bytes(payload + signatureLen, (16 - paddingSize)); signatureLen += (16 - paddingSize); } m_SessionKeyEncryption.SetIV (iv); m_SessionKeyEncryption.Encrypt (payload, signatureLen, payload); payload += signatureLen; size_t msgLen = payload - buf; // encrypt message with intro key FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CREATED, buf, msgLen, m_IntroKey, iv, m_IntroKey); Send (buf, msgLen); } void SSUSession::SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen) { uint8_t buf[512 + 18] = {0}; uint8_t * payload = buf + sizeof (SSUHeader); *payload = 1; // 1 fragment payload++; // info size_t identLen = i2p::context.GetIdentity ()->GetFullLen (); // 387+ bytes htobe16buf (payload, identLen); payload += 2; // cursize i2p::context.GetIdentity ()->ToBuffer (payload, identLen); payload += identLen; uint32_t signedOnTime = i2p::util::GetSecondsSinceEpoch (); htobe32buf (payload, signedOnTime); // signed on time payload += 4; auto signatureLen = i2p::context.GetIdentity ()->GetSignatureLen (); size_t paddingSize = ((payload - buf) + signatureLen)%16; if (paddingSize > 0) paddingSize = 16 - paddingSize; RAND_bytes(payload, paddingSize); // fill padding with random payload += paddingSize; // padding size // signature SignedData s; // x,y, our IP, our port, remote IP, remote port, relayTag, our signed on time s.Insert (m_DHKeysPair->GetPublicKey (), 256); // x s.Insert (y, 256); // y s.Insert (ourAddress, ourAddressLen); // our address/port as seem by party if (m_RemoteEndpoint.address ().is_v4 ()) s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP V4 else s.Insert (m_RemoteEndpoint.address ().to_v6 ().to_bytes ().data (), 16); // remote IP V6 s.Insert (htobe16 (m_RemoteEndpoint.port ())); // remote port s.Insert (htobe32 (m_RelayTag)); // relay tag s.Insert (htobe32 (signedOnTime)); // signed on time s.Sign (i2p::context.GetPrivateKeys (), payload); // DSA signature payload += signatureLen; size_t msgLen = payload - buf; uint8_t iv[16]; RAND_bytes (iv, 16); // random iv // encrypt message with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_CONFIRMED, buf, msgLen, m_SessionKey, iv, m_MacKey); Send (buf, msgLen); } void SSUSession::ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from) { uint32_t relayTag = bufbe32toh (buf); auto session = m_Server.FindRelaySession (relayTag); if (session) { buf += 4; // relay tag uint8_t size = *buf; buf++; // size buf += size; // address buf += 2; // port uint8_t challengeSize = *buf; buf++; // challenge size buf += challengeSize; const uint8_t * introKey = buf; buf += 32; // introkey uint32_t nonce = bufbe32toh (buf); SendRelayResponse (nonce, from, introKey, session->m_RemoteEndpoint); SendRelayIntro (session, from); } } void SSUSession::SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to) { // Charlie's address always v4 if (!to.address ().is_v4 ()) { LogPrint (eLogWarning, "SSU: Charlie's IP must be v4"); return; } uint8_t buf[80 + 18] = {0}; // 64 Alice's ipv4 and 80 Alice's ipv6 uint8_t * payload = buf + sizeof (SSUHeader); *payload = 4; payload++; // size htobe32buf (payload, to.address ().to_v4 ().to_ulong ()); // Charlie's IP payload += 4; // address htobe16buf (payload, to.port ()); // Charlie's port payload += 2; // port // Alice bool isV4 = from.address ().is_v4 (); // Alice's if (isV4) { *payload = 4; payload++; // size memcpy (payload, from.address ().to_v4 ().to_bytes ().data (), 4); // Alice's IP V4 payload += 4; // address } else { *payload = 16; payload++; // size memcpy (payload, from.address ().to_v6 ().to_bytes ().data (), 16); // Alice's IP V6 payload += 16; // address } htobe16buf (payload, from.port ()); // Alice's port payload += 2; // port htobe32buf (payload, nonce); if (m_State == eSessionStateEstablished) { // encrypt with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80); Send (buf, isV4 ? 64 : 80); } else { // ecrypt with Alice's intro key uint8_t iv[16]; RAND_bytes (iv, 16); // random iv FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_RESPONSE, buf, isV4 ? 64 : 80, introKey, iv, introKey); m_Server.Send (buf, isV4 ? 64 : 80, from); } LogPrint (eLogDebug, "SSU: relay response sent"); } void SSUSession::SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from) { if (!session) return; // Alice's address always v4 if (!from.address ().is_v4 ()) { LogPrint (eLogWarning, "SSU: Alice's IP must be v4"); return; } uint8_t buf[48 + 18] = {0}; uint8_t * payload = buf + sizeof (SSUHeader); *payload = 4; payload++; // size htobe32buf (payload, from.address ().to_v4 ().to_ulong ()); // Alice's IP payload += 4; // address htobe16buf (payload, from.port ()); // Alice's port payload += 2; // port *payload = 0; // challenge size uint8_t iv[16]; RAND_bytes (iv, 16); // random iv FillHeaderAndEncrypt (PAYLOAD_TYPE_RELAY_INTRO, buf, 48, session->m_SessionKey, iv, session->m_MacKey); m_Server.Send (buf, 48, session->m_RemoteEndpoint); LogPrint (eLogDebug, "SSU: relay intro sent"); } void SSUSession::ProcessRelayResponse (const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SSU message: Relay response received"); uint8_t remoteSize = *buf; buf++; // remote size boost::asio::ip::address_v4 remoteIP (bufbe32toh (buf)); buf += remoteSize; // remote address uint16_t remotePort = bufbe16toh (buf); buf += 2; // remote port uint8_t ourSize = *buf; buf++; // our size boost::asio::ip::address ourIP; if (ourSize == 4) { boost::asio::ip::address_v4::bytes_type bytes; memcpy (bytes.data (), buf, 4); ourIP = boost::asio::ip::address_v4 (bytes); } else { boost::asio::ip::address_v6::bytes_type bytes; memcpy (bytes.data (), buf, 16); ourIP = boost::asio::ip::address_v6 (bytes); } buf += ourSize; // our address uint16_t ourPort = bufbe16toh (buf); buf += 2; // our port LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); i2p::context.UpdateAddress (ourIP); uint32_t nonce = bufbe32toh (buf); buf += 4; // nonce auto it = m_RelayRequests.find (nonce); if (it != m_RelayRequests.end ()) { // check if we are waiting for introduction boost::asio::ip::udp::endpoint remoteEndpoint (remoteIP, remotePort); if (!m_Server.FindSession (remoteEndpoint)) { // we didn't have correct endpoint when sent relay request // now we do LogPrint (eLogInfo, "SSU: RelayReponse connecting to endpoint ", remoteEndpoint); if (i2p::context.GetRouterInfo ().UsesIntroducer ()) // if we are unreachable m_Server.Send (buf, 0, remoteEndpoint); // send HolePunch m_Server.CreateDirectSession (it->second, remoteEndpoint, false); } // delete request m_RelayRequests.erase (it); } else LogPrint (eLogError, "SSU: Unsolicited RelayResponse, nonce=", nonce); } void SSUSession::ProcessRelayIntro (const uint8_t * buf, size_t len) { uint8_t size = *buf; if (size == 4) { buf++; // size boost::asio::ip::address_v4 address (bufbe32toh (buf)); buf += 4; // address uint16_t port = bufbe16toh (buf); // send hole punch of 0 bytes m_Server.Send (buf, 0, boost::asio::ip::udp::endpoint (address, port)); } else LogPrint (eLogWarning, "SSU: Address size ", size, " is not supported"); } void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag) { if (len < sizeof (SSUHeader)) { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } SSUHeader * header = (SSUHeader *)buf; memcpy (header->iv, iv, 16); header->flag = flag | (payloadType << 4); // MSB is 0 htobe32buf (header->time, i2p::util::GetSecondsSinceEpoch ()); uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); i2p::crypto::CBCEncryption encryption; encryption.SetKey (aesKey); encryption.SetIV (iv); encryption.Encrypt (encrypted, encryptedLen, encrypted); // assume actual buffer size is 18 (16 + 2) bytes more memcpy (buf + len, iv, 16); htobe16buf (buf + len + 16, encryptedLen); i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, header->mac); } void SSUSession::FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len) { if (len < sizeof (SSUHeader)) { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } SSUHeader * header = (SSUHeader *)buf; RAND_bytes (header->iv, 16); // random iv m_SessionKeyEncryption.SetIV (header->iv); header->flag = payloadType << 4; // MSB is 0 htobe32buf (header->time, i2p::util::GetSecondsSinceEpoch ()); uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); m_SessionKeyEncryption.Encrypt (encrypted, encryptedLen, encrypted); // assume actual buffer size is 18 (16 + 2) bytes more memcpy (buf + len, header->iv, 16); htobe16buf (buf + len + 16, encryptedLen); i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, m_MacKey, header->mac); } void SSUSession::Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey) { if (len < sizeof (SSUHeader)) { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } SSUHeader * header = (SSUHeader *)buf; uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); i2p::crypto::CBCDecryption decryption; decryption.SetKey (aesKey); decryption.SetIV (header->iv); decryption.Decrypt (encrypted, encryptedLen, encrypted); } void SSUSession::DecryptSessionKey (uint8_t * buf, size_t len) { if (len < sizeof (SSUHeader)) { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return; } SSUHeader * header = (SSUHeader *)buf; uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); if (encryptedLen > 0) { m_SessionKeyDecryption.SetIV (header->iv); m_SessionKeyDecryption.Decrypt (encrypted, encryptedLen, encrypted); } } bool SSUSession::Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey) { if (len < sizeof (SSUHeader)) { LogPrint (eLogError, "SSU: Unexpected packet length ", len); return false; } SSUHeader * header = (SSUHeader *)buf; uint8_t * encrypted = &header->flag; uint16_t encryptedLen = len - (encrypted - buf); // assume actual buffer size is 18 (16 + 2) bytes more memcpy (buf + len, header->iv, 16); htobe16buf (buf + len + 16, encryptedLen); uint8_t digest[16]; i2p::crypto::HMACMD5Digest (encrypted, encryptedLen + 18, macKey, digest); return !memcmp (header->mac, digest, 16); } void SSUSession::Connect () { if (m_State == eSessionStateUnknown) { // set connect timer ScheduleConnectTimer (); m_DHKeysPair = transports.GetNextDHKeysPair (); SendSessionRequest (); } } void SSUSession::WaitForConnect () { if (!IsOutgoing ()) // incoming session ScheduleConnectTimer (); else LogPrint (eLogError, "SSU: wait for connect for outgoing session"); } void SSUSession::ScheduleConnectTimer () { m_ConnectTimer.cancel (); m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, shared_from_this (), std::placeholders::_1)); } void SSUSession::HandleConnectTimer (const boost::system::error_code& ecode) { if (!ecode) { // timeout expired LogPrint (eLogWarning, "SSU: session with ", m_RemoteEndpoint, " was not established after ", SSU_CONNECT_TIMEOUT, " seconds"); Failed (); } } void SSUSession::Introduce (const i2p::data::RouterInfo::Introducer& introducer, std::shared_ptr to) { if (m_State == eSessionStateUnknown) { // set connect timer m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, shared_from_this (), std::placeholders::_1)); } uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); m_RelayRequests[nonce] = to; SendRelayRequest (introducer, nonce); } void SSUSession::WaitForIntroduction () { m_State = eSessionStateIntroduced; // set connect timer m_ConnectTimer.expires_from_now (boost::posix_time::seconds(SSU_CONNECT_TIMEOUT)); m_ConnectTimer.async_wait (std::bind (&SSUSession::HandleConnectTimer, shared_from_this (), std::placeholders::_1)); } void SSUSession::Close () { SendSessionDestroyed (); Reset (); m_State = eSessionStateClosed; } void SSUSession::Reset () { m_State = eSessionStateUnknown; transports.PeerDisconnected (shared_from_this ()); m_Data.Stop (); m_ConnectTimer.cancel (); if (m_SentRelayTag) { m_Server.RemoveRelay (m_SentRelayTag); // relay tag is not valid anymore m_SentRelayTag = 0; } m_DHKeysPair = nullptr; m_SignedData = nullptr; m_IsSessionKey = false; } void SSUSession::Done () { GetService ().post (std::bind (&SSUSession::Failed, shared_from_this ())); } void SSUSession::Established () { m_State = eSessionStateEstablished; m_DHKeysPair = nullptr; m_SignedData = nullptr; m_Data.Start (); transports.PeerConnected (shared_from_this ()); if (m_IsPeerTest) SendPeerTest (); if (m_SentRelayTag) m_Server.AddRelay (m_SentRelayTag, shared_from_this ()); m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); } void SSUSession::Failed () { if (m_State != eSessionStateFailed) { m_State = eSessionStateFailed; m_Server.DeleteSession (shared_from_this ()); } } void SSUSession::SendI2NPMessages (const std::vector >& msgs) { GetService ().post (std::bind (&SSUSession::PostI2NPMessages, shared_from_this (), msgs)); } void SSUSession::PostI2NPMessages (std::vector > msgs) { if (m_State == eSessionStateEstablished) { for (const auto& it: msgs) if (it) m_Data.Send (it); } } void SSUSession::ProcessData (uint8_t * buf, size_t len) { m_Data.ProcessMessage (buf, len); m_IsDataReceived = true; } void SSUSession::FlushData () { if (m_IsDataReceived) { m_Data.FlushReceivedMessage (); m_IsDataReceived = false; } } void SSUSession::ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint) { uint32_t nonce = bufbe32toh (buf); // 4 bytes uint8_t size = buf[4]; // 1 byte const uint8_t * address = buf + 5; // big endian, size bytes uint16_t port = buf16toh(buf + size + 5); // big endian, 2 bytes const uint8_t * introKey = buf + size + 7; if (port && (size != 4) && (size != 16)) { LogPrint (eLogWarning, "SSU: Address of ", size, " bytes not supported"); return; } switch (m_Server.GetPeerTestParticipant (nonce)) { // existing test case ePeerTestParticipantAlice1: { if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob { LogPrint (eLogDebug, "SSU: peer test from Bob. We are Alice"); if (i2p::context.GetStatus () == eRouterStatusTesting) // still not OK i2p::context.SetStatus (eRouterStatusFirewalled); } else { LogPrint (eLogDebug, "SSU: first peer test from Charlie. We are Alice"); if (m_State == eSessionStateEstablished) LogPrint (eLogWarning, "SSU: first peer test from Charlie through established session. We are Alice"); i2p::context.SetStatus (eRouterStatusOK); m_Server.UpdatePeerTest (nonce, ePeerTestParticipantAlice2); SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, true, false); // to Charlie } break; } case ePeerTestParticipantAlice2: { if (m_Server.GetPeerTestSession (nonce) == shared_from_this ()) // Alice-Bob LogPrint (eLogDebug, "SSU: peer test from Bob. We are Alice"); else { // peer test successive LogPrint (eLogDebug, "SSU: second peer test from Charlie. We are Alice"); i2p::context.SetStatus (eRouterStatusOK); m_Server.RemovePeerTest (nonce); } break; } case ePeerTestParticipantBob: { LogPrint (eLogDebug, "SSU: peer test from Charlie. We are Bob"); auto session = m_Server.GetPeerTestSession (nonce); // session with Alice from PeerTest if (session && session->m_State == eSessionStateEstablished) session->Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Alice m_Server.RemovePeerTest (nonce); // nonce has been used break; } case ePeerTestParticipantCharlie: { LogPrint (eLogDebug, "SSU: peer test from Alice. We are Charlie"); SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey); // to Alice with her actual address m_Server.RemovePeerTest (nonce); // nonce has been used break; } // test not found case ePeerTestParticipantUnknown: { if (m_State == eSessionStateEstablished) { // new test if (port) { LogPrint (eLogDebug, "SSU: peer test from Bob. We are Charlie"); m_Server.NewPeerTest (nonce, ePeerTestParticipantCharlie); Send (PAYLOAD_TYPE_PEER_TEST, buf, len); // back to Bob boost::asio::ip::address addr; // Alice's address if (size == 4) // v4 { boost::asio::ip::address_v4::bytes_type bytes; memcpy (bytes.data (), address, 4); addr = boost::asio::ip::address_v4 (bytes); } else // v6 { boost::asio::ip::address_v6::bytes_type bytes; memcpy (bytes.data (), address, 16); addr = boost::asio::ip::address_v6 (bytes); } SendPeerTest (nonce, addr, be16toh (port), introKey); // to Alice with her address received from Bob } else { LogPrint (eLogDebug, "SSU: peer test from Alice. We are Bob"); auto session = senderEndpoint.address ().is_v4 () ? m_Server.GetRandomEstablishedV4Session (shared_from_this ()) : m_Server.GetRandomEstablishedV6Session (shared_from_this ()); // Charlie if (session) { m_Server.NewPeerTest (nonce, ePeerTestParticipantBob, shared_from_this ()); session->SendPeerTest (nonce, senderEndpoint.address (), senderEndpoint.port (), introKey, false); // to Charlie with Alice's actual address } } } else LogPrint (eLogError, "SSU: unexpected peer test"); } } } void SSUSession::SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, const uint8_t * introKey, bool toAddress, bool sendAddress) // toAddress is true for Alice<->Chalie communications only // sendAddress is false if message comes from Alice { uint8_t buf[80 + 18] = {0}; uint8_t iv[16]; uint8_t * payload = buf + sizeof (SSUHeader); htobe32buf (payload, nonce); payload += 4; // nonce // address and port if (sendAddress) { if (address.is_v4 ()) { *payload = 4; memcpy (payload + 1, address.to_v4 ().to_bytes ().data (), 4); // our IP V4 } else if (address.is_v6 ()) { *payload = 16; memcpy (payload + 1, address.to_v6 ().to_bytes ().data (), 16); // our IP V6 } else *payload = 0; payload += (payload[0] + 1); } else { *payload = 0; payload++; //size } htobe16buf (payload, port); payload += 2; // port // intro key if (toAddress) { // send our intro key to address instead it's own auto addr = i2p::context.GetRouterInfo ().GetSSUAddress (); if (addr) memcpy (payload, addr->ssu->key, 32); // intro key else LogPrint (eLogInfo, "SSU is not supported. Can't send peer test"); } else memcpy (payload, introKey, 32); // intro key // send RAND_bytes (iv, 16); // random iv if (toAddress) { // encrypt message with specified intro key FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80, introKey, iv, introKey); boost::asio::ip::udp::endpoint e (address, port); m_Server.Send (buf, 80, e); } else { // encrypt message with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_PEER_TEST, buf, 80); Send (buf, 80); } } void SSUSession::SendPeerTest () { // we are Alice LogPrint (eLogDebug, "SSU: sending peer test"); auto address = i2p::context.GetRouterInfo ().GetSSUAddress (i2p::context.SupportsV4 ()); if (!address) { LogPrint (eLogInfo, "SSU is not supported. Can't send peer test"); return; } uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); if (!nonce) nonce = 1; m_IsPeerTest = false; m_Server.NewPeerTest (nonce, ePeerTestParticipantAlice1, shared_from_this ()); SendPeerTest (nonce, boost::asio::ip::address(), 0, address->ssu->key, false, false); // address and port always zero for Alice } void SSUSession::SendKeepAlive () { if (m_State == eSessionStateEstablished) { uint8_t buf[48 + 18] = {0}; uint8_t * payload = buf + sizeof (SSUHeader); *payload = 0; // flags payload++; *payload = 0; // num fragments // encrypt message with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_DATA, buf, 48); Send (buf, 48); LogPrint (eLogDebug, "SSU: keep-alive sent"); m_LastActivityTimestamp = i2p::util::GetSecondsSinceEpoch (); } } void SSUSession::SendSessionDestroyed () { if (m_IsSessionKey) { uint8_t buf[48 + 18] = {0}; // encrypt message with session key FillHeaderAndEncrypt (PAYLOAD_TYPE_SESSION_DESTROYED, buf, 48); try { Send (buf, 48); } catch (std::exception& ex) { LogPrint (eLogWarning, "SSU: exception while sending session destoroyed: ", ex.what ()); } LogPrint (eLogDebug, "SSU: session destroyed sent"); } } void SSUSession::Send (uint8_t type, const uint8_t * payload, size_t len) { uint8_t buf[SSU_MTU_V4 + 18] = {0}; size_t msgSize = len + sizeof (SSUHeader); size_t paddingSize = msgSize & 0x0F; // %16 if (paddingSize > 0) msgSize += (16 - paddingSize); if (msgSize > SSU_MTU_V4) { LogPrint (eLogWarning, "SSU: payload size ", msgSize, " exceeds MTU"); return; } memcpy (buf + sizeof (SSUHeader), payload, len); // encrypt message with session key FillHeaderAndEncrypt (type, buf, msgSize); Send (buf, msgSize); } void SSUSession::Send (const uint8_t * buf, size_t size) { m_NumSentBytes += size; i2p::transport::transports.UpdateSentBytes (size); m_Server.Send (buf, size, m_RemoteEndpoint); } } } i2pd-2.17.0/libi2pd/SSUSession.h000066400000000000000000000145351321131324000161510ustar00rootroot00000000000000#ifndef SSU_SESSION_H__ #define SSU_SESSION_H__ #include #include #include #include "Crypto.h" #include "I2NPProtocol.h" #include "TransportSession.h" #include "SSUData.h" namespace i2p { namespace transport { const uint8_t SSU_HEADER_EXTENDED_OPTIONS_INCLUDED = 0x04; struct SSUHeader { uint8_t mac[16]; uint8_t iv[16]; uint8_t flag; uint8_t time[4]; uint8_t GetPayloadType () const { return flag >> 4; }; bool IsExtendedOptions () const { return flag & SSU_HEADER_EXTENDED_OPTIONS_INCLUDED; }; }; const int SSU_CONNECT_TIMEOUT = 5; // 5 seconds const int SSU_TERMINATION_TIMEOUT = 330; // 5.5 minutes const int SSU_CLOCK_SKEW = 60; // in seconds // payload types (4 bits) const uint8_t PAYLOAD_TYPE_SESSION_REQUEST = 0; const uint8_t PAYLOAD_TYPE_SESSION_CREATED = 1; const uint8_t PAYLOAD_TYPE_SESSION_CONFIRMED = 2; const uint8_t PAYLOAD_TYPE_RELAY_REQUEST = 3; const uint8_t PAYLOAD_TYPE_RELAY_RESPONSE = 4; const uint8_t PAYLOAD_TYPE_RELAY_INTRO = 5; const uint8_t PAYLOAD_TYPE_DATA = 6; const uint8_t PAYLOAD_TYPE_PEER_TEST = 7; const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8; // extended options const uint16_t EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG = 0x0001; enum SessionState { eSessionStateUnknown, eSessionStateIntroduced, eSessionStateEstablished, eSessionStateClosed, eSessionStateFailed }; enum PeerTestParticipant { ePeerTestParticipantUnknown = 0, ePeerTestParticipantAlice1, ePeerTestParticipantAlice2, ePeerTestParticipantBob, ePeerTestParticipantCharlie }; class SSUServer; class SSUSession: public TransportSession, public std::enable_shared_from_this { public: SSUSession (SSUServer& server, boost::asio::ip::udp::endpoint& remoteEndpoint, std::shared_ptr router = nullptr, bool peerTest = false); void ProcessNextMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); ~SSUSession (); void Connect (); void WaitForConnect (); void Introduce (const i2p::data::RouterInfo::Introducer& introducer, std::shared_ptr to); // Alice to Charlie void WaitForIntroduction (); void Close (); void Done (); void Failed (); boost::asio::ip::udp::endpoint& GetRemoteEndpoint () { return m_RemoteEndpoint; }; bool IsV6 () const { return m_RemoteEndpoint.address ().is_v6 (); }; void SendI2NPMessages (const std::vector >& msgs); void SendPeerTest (); // Alice SessionState GetState () const { return m_State; }; size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; void SendKeepAlive (); uint32_t GetRelayTag () const { return m_RelayTag; }; const i2p::data::RouterInfo::IntroKey& GetIntroKey () const { return m_IntroKey; }; uint32_t GetCreationTime () const { return m_CreationTime; }; void FlushData (); private: boost::asio::io_service& GetService (); void CreateAESandMacKey (const uint8_t * pubKey); size_t GetSSUHeaderSize (const uint8_t * buf) const; void PostI2NPMessages (std::vector > msgs); void ProcessMessage (uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); // call for established session void ProcessSessionRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); void SendSessionRequest (); void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce); void ProcessSessionCreated (uint8_t * buf, size_t len); void SendSessionCreated (const uint8_t * x, bool sendRelayTag = true); void ProcessSessionConfirmed (const uint8_t * buf, size_t len); void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen); void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from); void SendRelayResponse (uint32_t nonce, const boost::asio::ip::udp::endpoint& from, const uint8_t * introKey, const boost::asio::ip::udp::endpoint& to); void SendRelayIntro (std::shared_ptr session, const boost::asio::ip::udp::endpoint& from); void ProcessRelayResponse (const uint8_t * buf, size_t len); void ProcessRelayIntro (const uint8_t * buf, size_t len); void Established (); void ScheduleConnectTimer (); void HandleConnectTimer (const boost::system::error_code& ecode); void ProcessPeerTest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint); void SendPeerTest (uint32_t nonce, const boost::asio::ip::address& address, uint16_t port, const uint8_t * introKey, bool toAddress = true, bool sendAddress = true); void ProcessData (uint8_t * buf, size_t len); void SendSessionDestroyed (); void Send (uint8_t type, const uint8_t * payload, size_t len); // with session key void Send (const uint8_t * buf, size_t size); void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey, const uint8_t * iv, const i2p::crypto::MACKey& macKey, uint8_t flag = 0); void FillHeaderAndEncrypt (uint8_t payloadType, uint8_t * buf, size_t len); // with session key void Decrypt (uint8_t * buf, size_t len, const i2p::crypto::AESKey& aesKey); void DecryptSessionKey (uint8_t * buf, size_t len); bool Validate (uint8_t * buf, size_t len, const i2p::crypto::MACKey& macKey); void Reset (); private: friend class SSUData; // TODO: change in later SSUServer& m_Server; boost::asio::ip::udp::endpoint m_RemoteEndpoint; boost::asio::deadline_timer m_ConnectTimer; bool m_IsPeerTest; SessionState m_State; bool m_IsSessionKey; uint32_t m_RelayTag; // received from peer uint32_t m_SentRelayTag; // sent by us i2p::crypto::CBCEncryption m_SessionKeyEncryption; i2p::crypto::CBCDecryption m_SessionKeyDecryption; i2p::crypto::AESKey m_SessionKey; i2p::crypto::MACKey m_MacKey; i2p::data::RouterInfo::IntroKey m_IntroKey; uint32_t m_CreationTime; // seconds since epoch SSUData m_Data; bool m_IsDataReceived; std::unique_ptr m_SignedData; // we need it for SessionConfirmed only std::map > m_RelayRequests; // nonce->Charlie }; } } #endif i2pd-2.17.0/libi2pd/Signature.cpp000066400000000000000000000356311321131324000164270ustar00rootroot00000000000000#include #include "Log.h" #include "Signature.h" namespace i2p { namespace crypto { class Ed25519 { public: Ed25519 () { BN_CTX * ctx = BN_CTX_new (); BIGNUM * tmp = BN_new (); q = BN_new (); // 2^255-19 BN_set_bit (q, 255); // 2^255 BN_sub_word (q, 19); l = BN_new (); // 2^252 + 27742317777372353535851937790883648493 BN_set_bit (l, 252); two_252_2 = BN_dup (l); BN_dec2bn (&tmp, "27742317777372353535851937790883648493"); BN_add (l, l, tmp); BN_sub_word (two_252_2, 2); // 2^252 - 2 // -121665*inv(121666) d = BN_new (); BN_set_word (tmp, 121666); BN_mod_inverse (tmp, tmp, q, ctx); BN_set_word (d, 121665); BN_set_negative (d, 1); BN_mul (d, d, tmp, ctx); // 2^((q-1)/4) I = BN_new (); BN_free (tmp); tmp = BN_dup (q); BN_sub_word (tmp, 1); BN_div_word (tmp, 4); BN_set_word (I, 2); BN_mod_exp (I, I, tmp, q, ctx); BN_free (tmp); // 4*inv(5) BIGNUM * By = BN_new (); BN_set_word (By, 5); BN_mod_inverse (By, By, q, ctx); BN_mul_word (By, 4); BIGNUM * Bx = RecoverX (By, ctx); BN_mod (Bx, Bx, q, ctx); // % q BN_mod (By, By, q, ctx); // % q // precalculate Bi256 table Bi256Carry = { Bx, By }; // B for (int i = 0; i < 32; i++) { Bi256[i][0] = Bi256Carry; // first point for (int j = 1; j < 128; j++) Bi256[i][j] = Sum (Bi256[i][j-1], Bi256[i][0], ctx); // (256+j+1)^i*B Bi256Carry = Bi256[i][127]; for (int j = 0; j < 128; j++) // add first point 128 more times Bi256Carry = Sum (Bi256Carry, Bi256[i][0], ctx); } BN_CTX_free (ctx); } Ed25519 (const Ed25519& other): q (BN_dup (other.q)), l (BN_dup (other.l)), d (BN_dup (other.d)), I (BN_dup (other.I)), two_252_2 (BN_dup (other.two_252_2)), Bi256Carry (other.Bi256Carry) { for (int i = 0; i < 32; i++) for (int j = 0; j < 128; j++) Bi256[i][j] = other.Bi256[i][j]; } ~Ed25519 () { BN_free (q); BN_free (l); BN_free (d); BN_free (I); BN_free (two_252_2); } EDDSAPoint GeneratePublicKey (const uint8_t * expandedPrivateKey, BN_CTX * ctx) const { return MulB (expandedPrivateKey, ctx); // left half of expanded key, considered as Little Endian } EDDSAPoint DecodePublicKey (const uint8_t * buf, BN_CTX * ctx) const { return DecodePoint (buf, ctx); } void EncodePublicKey (const EDDSAPoint& publicKey, uint8_t * buf, BN_CTX * ctx) const { EncodePoint (Normalize (publicKey, ctx), buf); } bool Verify (const EDDSAPoint& publicKey, const uint8_t * digest, const uint8_t * signature) const { BN_CTX * ctx = BN_CTX_new (); BIGNUM * h = DecodeBN<64> (digest); // signature 0..31 - R, 32..63 - S // B*S = R + PK*h => R = B*S - PK*h // we don't decode R, but encode (B*S - PK*h) auto Bs = MulB (signature + EDDSA25519_SIGNATURE_LENGTH/2, ctx); // B*S; BN_mod (h, h, l, ctx); // public key is multiple of B, but B%l = 0 auto PKh = Mul (publicKey, h, ctx); // PK*h uint8_t diff[32]; EncodePoint (Normalize (Sum (Bs, -PKh, ctx), ctx), diff); // Bs - PKh encoded bool passed = !memcmp (signature, diff, 32); // R BN_free (h); BN_CTX_free (ctx); if (!passed) LogPrint (eLogError, "25519 signature verification failed"); return passed; } void Sign (const uint8_t * expandedPrivateKey, const uint8_t * publicKeyEncoded, const uint8_t * buf, size_t len, uint8_t * signature) const { BN_CTX * bnCtx = BN_CTX_new (); // calculate r SHA512_CTX ctx; SHA512_Init (&ctx); SHA512_Update (&ctx, expandedPrivateKey + EDDSA25519_PRIVATE_KEY_LENGTH, EDDSA25519_PRIVATE_KEY_LENGTH); // right half of expanded key SHA512_Update (&ctx, buf, len); // data uint8_t digest[64]; SHA512_Final (digest, &ctx); BIGNUM * r = DecodeBN<32> (digest); // DecodeBN<64> (digest); // for test vectors // calculate R uint8_t R[EDDSA25519_SIGNATURE_LENGTH/2]; // we must use separate buffer because signature might be inside buf EncodePoint (Normalize (MulB (digest, bnCtx), bnCtx), R); // EncodePoint (Mul (B, r, bnCtx), R); // for test vectors // calculate S SHA512_Init (&ctx); SHA512_Update (&ctx, R, EDDSA25519_SIGNATURE_LENGTH/2); // R SHA512_Update (&ctx, publicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key SHA512_Update (&ctx, buf, len); // data SHA512_Final (digest, &ctx); BIGNUM * h = DecodeBN<64> (digest); // S = (r + h*a) % l BIGNUM * a = DecodeBN (expandedPrivateKey); // left half of expanded key BN_mod_mul (h, h, a, l, bnCtx); // %l BN_mod_add (h, h, r, l, bnCtx); // %l memcpy (signature, R, EDDSA25519_SIGNATURE_LENGTH/2); EncodeBN (h, signature + EDDSA25519_SIGNATURE_LENGTH/2, EDDSA25519_SIGNATURE_LENGTH/2); // S BN_free (r); BN_free (h); BN_free (a); BN_CTX_free (bnCtx); } private: EDDSAPoint Sum (const EDDSAPoint& p1, const EDDSAPoint& p2, BN_CTX * ctx) const { // x3 = (x1*y2+y1*x2)*(z1*z2-d*t1*t2) // y3 = (y1*y2+x1*x2)*(z1*z2+d*t1*t2) // z3 = (z1*z2-d*t1*t2)*(z1*z2+d*t1*t2) // t3 = (y1*y2+x1*x2)*(x1*y2+y1*x2) BIGNUM * x3 = BN_new (), * y3 = BN_new (), * z3 = BN_new (), * t3 = BN_new (); BN_mul (x3, p1.x, p2.x, ctx); // A = x1*x2 BN_mul (y3, p1.y, p2.y, ctx); // B = y1*y2 BN_CTX_start (ctx); BIGNUM * t1 = p1.t, * t2 = p2.t; if (!t1) { t1 = BN_CTX_get (ctx); BN_mul (t1, p1.x, p1.y, ctx); } if (!t2) { t2 = BN_CTX_get (ctx); BN_mul (t2, p2.x, p2.y, ctx); } BN_mul (t3, t1, t2, ctx); BN_mul (t3, t3, d, ctx); // C = d*t1*t2 if (p1.z) { if (p2.z) BN_mul (z3, p1.z, p2.z, ctx); // D = z1*z2 else BN_copy (z3, p1.z); // D = z1 } else { if (p2.z) BN_copy (z3, p2.z); // D = z2 else BN_one (z3); // D = 1 } BIGNUM * E = BN_CTX_get (ctx), * F = BN_CTX_get (ctx), * G = BN_CTX_get (ctx), * H = BN_CTX_get (ctx); BN_add (E, p1.x, p1.y); BN_add (F, p2.x, p2.y); BN_mul (E, E, F, ctx); // (x1 + y1)*(x2 + y2) BN_sub (E, E, x3); BN_sub (E, E, y3); // E = (x1 + y1)*(x2 + y2) - A - B BN_sub (F, z3, t3); // F = D - C BN_add (G, z3, t3); // G = D + C BN_add (H, y3, x3); // H = B + A BN_mod_mul (x3, E, F, q, ctx); // x3 = E*F BN_mod_mul (y3, G, H, q, ctx); // y3 = G*H BN_mod_mul (z3, F, G, q, ctx); // z3 = F*G BN_mod_mul (t3, E, H, q, ctx); // t3 = E*H BN_CTX_end (ctx); return EDDSAPoint {x3, y3, z3, t3}; } void Double (EDDSAPoint& p, BN_CTX * ctx) const { BN_CTX_start (ctx); BIGNUM * x2 = BN_CTX_get (ctx), * y2 = BN_CTX_get (ctx), * z2 = BN_CTX_get (ctx), * t2 = BN_CTX_get (ctx); BN_sqr (x2, p.x, ctx); // x2 = A = x^2 BN_sqr (y2, p.y, ctx); // y2 = B = y^2 if (p.t) BN_sqr (t2, p.t, ctx); // t2 = t^2 else { BN_mul (t2, p.x, p.y, ctx); // t = x*y BN_sqr (t2, t2, ctx); // t2 = t^2 } BN_mul (t2, t2, d, ctx); // t2 = C = d*t^2 if (p.z) BN_sqr (z2, p.z, ctx); // z2 = D = z^2 else BN_one (z2); // z2 = 1 BIGNUM * E = BN_CTX_get (ctx), * F = BN_CTX_get (ctx), * G = BN_CTX_get (ctx), * H = BN_CTX_get (ctx); // E = (x+y)*(x+y)-A-B = x^2+y^2+2xy-A-B = 2xy BN_mul (E, p.x, p.y, ctx); BN_lshift1 (E, E); // E =2*x*y BN_sub (F, z2, t2); // F = D - C BN_add (G, z2, t2); // G = D + C BN_add (H, y2, x2); // H = B + A BN_mod_mul (p.x, E, F, q, ctx); // x2 = E*F BN_mod_mul (p.y, G, H, q, ctx); // y2 = G*H if (!p.z) p.z = BN_new (); BN_mod_mul (p.z, F, G, q, ctx); // z2 = F*G if (!p.t) p.t = BN_new (); BN_mod_mul (p.t, E, H, q, ctx); // t2 = E*H BN_CTX_end (ctx); } EDDSAPoint Mul (const EDDSAPoint& p, const BIGNUM * e, BN_CTX * ctx) const { BIGNUM * zero = BN_new (), * one = BN_new (); BN_zero (zero); BN_one (one); EDDSAPoint res {zero, one}; if (!BN_is_zero (e)) { int bitCount = BN_num_bits (e); for (int i = bitCount - 1; i >= 0; i--) { Double (res, ctx); if (BN_is_bit_set (e, i)) res = Sum (res, p, ctx); } } return res; } EDDSAPoint MulB (const uint8_t * e, BN_CTX * ctx) const // B*e, e is 32 bytes Little Endian { BIGNUM * zero = BN_new (), * one = BN_new (); BN_zero (zero); BN_one (one); EDDSAPoint res {zero, one}; bool carry = false; for (int i = 0; i < 32; i++) { uint8_t x = e[i]; if (carry) { if (x < 255) { x++; carry = false; } else x = 0; } if (x > 0) { if (x <= 128) res = Sum (res, Bi256[i][x-1], ctx); else { res = Sum (res, -Bi256[i][255-x], ctx); // -Bi[256-x] carry = true; } } } if (carry) res = Sum (res, Bi256Carry, ctx); return res; } EDDSAPoint Normalize (const EDDSAPoint& p, BN_CTX * ctx) const { if (p.z) { BIGNUM * x = BN_new (), * y = BN_new (); BN_mod_inverse (y, p.z, q, ctx); BN_mod_mul (x, p.x, y, q, ctx); // x = x/z BN_mod_mul (y, p.y, y, q, ctx); // y = y/z return EDDSAPoint{x, y}; } else return EDDSAPoint{BN_dup (p.x), BN_dup (p.y)}; } bool IsOnCurve (const EDDSAPoint& p, BN_CTX * ctx) const { BN_CTX_start (ctx); BIGNUM * x2 = BN_CTX_get (ctx), * y2 = BN_CTX_get (ctx), * tmp = BN_CTX_get (ctx); BN_sqr (x2, p.x, ctx); // x^2 BN_sqr (y2, p.y, ctx); // y^2 // y^2 - x^2 - 1 - d*x^2*y^2 BN_mul (tmp, d, x2, ctx); BN_mul (tmp, tmp, y2, ctx); BN_sub (tmp, y2, tmp); BN_sub (tmp, tmp, x2); BN_sub_word (tmp, 1); BN_mod (tmp, tmp, q, ctx); // % q bool ret = BN_is_zero (tmp); BN_CTX_end (ctx); return ret; } BIGNUM * RecoverX (const BIGNUM * y, BN_CTX * ctx) const { BN_CTX_start (ctx); BIGNUM * y2 = BN_CTX_get (ctx), * xx = BN_CTX_get (ctx); BN_sqr (y2, y, ctx); // y^2 // xx = (y^2 -1)*inv(d*y^2 +1) BN_mul (xx, d, y2, ctx); BN_add_word (xx, 1); BN_mod_inverse (xx, xx, q, ctx); BN_sub_word (y2, 1); BN_mul (xx, y2, xx, ctx); // x = srqt(xx) = xx^(2^252-2) BIGNUM * x = BN_new (); BN_mod_exp (x, xx, two_252_2, q, ctx); // check (x^2 -xx) % q BN_sqr (y2, x, ctx); BN_mod_sub (y2, y2, xx, q, ctx); if (!BN_is_zero (y2)) BN_mod_mul (x, x, I, q, ctx); if (BN_is_odd (x)) BN_sub (x, q, x); BN_CTX_end (ctx); return x; } EDDSAPoint DecodePoint (const uint8_t * buf, BN_CTX * ctx) const { // buf is 32 bytes Little Endian, convert it to Big Endian uint8_t buf1[EDDSA25519_PUBLIC_KEY_LENGTH]; for (size_t i = 0; i < EDDSA25519_PUBLIC_KEY_LENGTH/2; i++) // invert bytes { buf1[i] = buf[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i]; buf1[EDDSA25519_PUBLIC_KEY_LENGTH -1 - i] = buf[i]; } bool isHighestBitSet = buf1[0] & 0x80; if (isHighestBitSet) buf1[0] &= 0x7f; // clear highest bit BIGNUM * y = BN_new (); BN_bin2bn (buf1, EDDSA25519_PUBLIC_KEY_LENGTH, y); BIGNUM * x = RecoverX (y, ctx); if (BN_is_bit_set (x, 0) != isHighestBitSet) BN_sub (x, q, x); // x = q - x BIGNUM * z = BN_new (), * t = BN_new (); BN_one (z); BN_mod_mul (t, x, y, q, ctx); // pre-calculate t EDDSAPoint p {x, y, z, t}; if (!IsOnCurve (p, ctx)) LogPrint (eLogError, "Decoded point is not on 25519"); return p; } void EncodePoint (const EDDSAPoint& p, uint8_t * buf) const { EncodeBN (p.y, buf,EDDSA25519_PUBLIC_KEY_LENGTH); if (BN_is_bit_set (p.x, 0)) // highest bit buf[EDDSA25519_PUBLIC_KEY_LENGTH - 1] |= 0x80; // set highest bit } template BIGNUM * DecodeBN (const uint8_t * buf) const { // buf is Little Endian convert it to Big Endian uint8_t buf1[len]; for (size_t i = 0; i < len/2; i++) // invert bytes { buf1[i] = buf[len -1 - i]; buf1[len -1 - i] = buf[i]; } BIGNUM * res = BN_new (); BN_bin2bn (buf1, len, res); return res; } void EncodeBN (const BIGNUM * bn, uint8_t * buf, size_t len) const { bn2buf (bn, buf, len); // To Little Endian for (size_t i = 0; i < len/2; i++) // invert bytes { uint8_t tmp = buf[i]; buf[i] = buf[len -1 - i]; buf[len -1 - i] = tmp; } } private: BIGNUM * q, * l, * d, * I; // transient values BIGNUM * two_252_2; // 2^252-2 EDDSAPoint Bi256[32][128]; // per byte, Bi256[i][j] = (256+j+1)^i*B, we don't store zeroes // if j > 128 we use 256 - j and carry 1 to next byte // Bi256[0][0] = B, base point EDDSAPoint Bi256Carry; // Bi256[32][0] }; static std::unique_ptr g_Ed25519; std::unique_ptr& GetEd25519 () { if (!g_Ed25519) { auto c = new Ed25519(); if (!g_Ed25519) // make sure it was not created already g_Ed25519.reset (c); else delete c; } return g_Ed25519; } EDDSA25519Verifier::EDDSA25519Verifier (const uint8_t * signingKey) { memcpy (m_PublicKeyEncoded, signingKey, EDDSA25519_PUBLIC_KEY_LENGTH); BN_CTX * ctx = BN_CTX_new (); m_PublicKey = GetEd25519 ()->DecodePublicKey (m_PublicKeyEncoded, ctx); BN_CTX_free (ctx); } bool EDDSA25519Verifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { uint8_t digest[64]; SHA512_CTX ctx; SHA512_Init (&ctx); SHA512_Update (&ctx, signature, EDDSA25519_SIGNATURE_LENGTH/2); // R SHA512_Update (&ctx, m_PublicKeyEncoded, EDDSA25519_PUBLIC_KEY_LENGTH); // public key SHA512_Update (&ctx, buf, len); // data SHA512_Final (digest, &ctx); return GetEd25519 ()->Verify (m_PublicKey, digest, signature); } EDDSA25519Signer::EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) { // expand key SHA512 (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH, m_ExpandedPrivateKey); m_ExpandedPrivateKey[0] &= 0xF8; // drop last 3 bits m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0x3F; // drop first 2 bits m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] |= 0x40; // set second bit // generate and encode public key BN_CTX * ctx = BN_CTX_new (); auto publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, ctx); GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx); if (signingPublicKey && memcmp (m_PublicKeyEncoded, signingPublicKey, EDDSA25519_PUBLIC_KEY_LENGTH)) { // keys don't match, it means older key with 0x1F LogPrint (eLogWarning, "Older EdDSA key detected"); m_ExpandedPrivateKey[EDDSA25519_PRIVATE_KEY_LENGTH - 1] &= 0xDF; // drop third bit publicKey = GetEd25519 ()->GeneratePublicKey (m_ExpandedPrivateKey, ctx); GetEd25519 ()->EncodePublicKey (publicKey, m_PublicKeyEncoded, ctx); } BN_CTX_free (ctx); } void EDDSA25519Signer::Sign (const uint8_t * buf, int len, uint8_t * signature) const { GetEd25519 ()->Sign (m_ExpandedPrivateKey, m_PublicKeyEncoded, buf, len, signature); } } } i2pd-2.17.0/libi2pd/Signature.h000066400000000000000000000414121321131324000160660ustar00rootroot00000000000000#ifndef SIGNATURE_H__ #define SIGNATURE_H__ #include #include #include #include #include #include #include #include "Crypto.h" #include "Gost.h" namespace i2p { namespace crypto { class Verifier { public: virtual ~Verifier () {}; virtual bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const = 0; virtual size_t GetPublicKeyLen () const = 0; virtual size_t GetSignatureLen () const = 0; virtual size_t GetPrivateKeyLen () const { return GetSignatureLen ()/2; }; }; class Signer { public: virtual ~Signer () {}; virtual void Sign (const uint8_t * buf, int len, uint8_t * signature) const = 0; }; const size_t DSA_PUBLIC_KEY_LENGTH = 128; const size_t DSA_SIGNATURE_LENGTH = 40; const size_t DSA_PRIVATE_KEY_LENGTH = DSA_SIGNATURE_LENGTH/2; class DSAVerifier: public Verifier { public: DSAVerifier (const uint8_t * signingKey) { m_PublicKey = CreateDSA (); DSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, DSA_PUBLIC_KEY_LENGTH, NULL), NULL); } ~DSAVerifier () { DSA_free (m_PublicKey); } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { // calculate SHA1 digest uint8_t digest[20]; SHA1 (buf, len, digest); // signature DSA_SIG * sig = DSA_SIG_new(); DSA_SIG_set0 (sig, BN_bin2bn (signature, DSA_SIGNATURE_LENGTH/2, NULL), BN_bin2bn (signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2, NULL)); // DSA verification int ret = DSA_do_verify (digest, 20, sig, m_PublicKey); DSA_SIG_free(sig); return ret; } size_t GetPublicKeyLen () const { return DSA_PUBLIC_KEY_LENGTH; }; size_t GetSignatureLen () const { return DSA_SIGNATURE_LENGTH; }; private: DSA * m_PublicKey; }; class DSASigner: public Signer { public: DSASigner (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey) // openssl 1.1 always requires DSA public key even for signing { m_PrivateKey = CreateDSA (); DSA_set0_key (m_PrivateKey, BN_bin2bn (signingPublicKey, DSA_PUBLIC_KEY_LENGTH, NULL), BN_bin2bn (signingPrivateKey, DSA_PRIVATE_KEY_LENGTH, NULL)); } ~DSASigner () { DSA_free (m_PrivateKey); } void Sign (const uint8_t * buf, int len, uint8_t * signature) const { uint8_t digest[20]; SHA1 (buf, len, digest); DSA_SIG * sig = DSA_do_sign (digest, 20, m_PrivateKey); const BIGNUM * r, * s; DSA_SIG_get0 (sig, &r, &s); bn2buf (r, signature, DSA_SIGNATURE_LENGTH/2); bn2buf (s, signature + DSA_SIGNATURE_LENGTH/2, DSA_SIGNATURE_LENGTH/2); DSA_SIG_free(sig); } private: DSA * m_PrivateKey; }; inline void CreateDSARandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { DSA * dsa = CreateDSA (); DSA_generate_key (dsa); const BIGNUM * pub_key, * priv_key; DSA_get0_key(dsa, &pub_key, &priv_key); bn2buf (priv_key, signingPrivateKey, DSA_PRIVATE_KEY_LENGTH); bn2buf (pub_key, signingPublicKey, DSA_PUBLIC_KEY_LENGTH); DSA_free (dsa); } struct SHA256Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { SHA256 (buf, len, digest); } enum { hashLen = 32 }; }; struct SHA384Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { SHA384 (buf, len, digest); } enum { hashLen = 48 }; }; struct SHA512Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { SHA512 (buf, len, digest); } enum { hashLen = 64 }; }; template class ECDSAVerifier: public Verifier { public: ECDSAVerifier (const uint8_t * signingKey) { m_PublicKey = EC_KEY_new_by_curve_name (curve); BIGNUM * x = BN_bin2bn (signingKey, keyLen/2, NULL); BIGNUM * y = BN_bin2bn (signingKey + keyLen/2, keyLen/2, NULL); EC_KEY_set_public_key_affine_coordinates (m_PublicKey, x, y); BN_free (x); BN_free (y); } ~ECDSAVerifier () { EC_KEY_free (m_PublicKey); } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { uint8_t digest[Hash::hashLen]; Hash::CalculateHash (buf, len, digest); ECDSA_SIG * sig = ECDSA_SIG_new(); auto r = BN_bin2bn (signature, GetSignatureLen ()/2, NULL); auto s = BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL); ECDSA_SIG_set0(sig, r, s); // ECDSA verification int ret = ECDSA_do_verify (digest, Hash::hashLen, sig, m_PublicKey); ECDSA_SIG_free(sig); return ret; } size_t GetPublicKeyLen () const { return keyLen; }; size_t GetSignatureLen () const { return keyLen; }; // signature length = key length private: EC_KEY * m_PublicKey; }; template class ECDSASigner: public Signer { public: ECDSASigner (const uint8_t * signingPrivateKey) { m_PrivateKey = EC_KEY_new_by_curve_name (curve); EC_KEY_set_private_key (m_PrivateKey, BN_bin2bn (signingPrivateKey, keyLen/2, NULL)); } ~ECDSASigner () { EC_KEY_free (m_PrivateKey); } void Sign (const uint8_t * buf, int len, uint8_t * signature) const { uint8_t digest[Hash::hashLen]; Hash::CalculateHash (buf, len, digest); ECDSA_SIG * sig = ECDSA_do_sign (digest, Hash::hashLen, m_PrivateKey); const BIGNUM * r, * s; ECDSA_SIG_get0 (sig, &r, &s); // signatureLen = keyLen bn2buf (r, signature, keyLen/2); bn2buf (s, signature + keyLen/2, keyLen/2); ECDSA_SIG_free(sig); } private: EC_KEY * m_PrivateKey; }; inline void CreateECDSARandomKeys (int curve, size_t keyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { EC_KEY * signingKey = EC_KEY_new_by_curve_name (curve); EC_KEY_generate_key (signingKey); bn2buf (EC_KEY_get0_private_key (signingKey), signingPrivateKey, keyLen/2); BIGNUM * x = BN_new(), * y = BN_new(); EC_POINT_get_affine_coordinates_GFp (EC_KEY_get0_group(signingKey), EC_KEY_get0_public_key (signingKey), x, y, NULL); bn2buf (x, signingPublicKey, keyLen/2); bn2buf (y, signingPublicKey + keyLen/2, keyLen/2); BN_free (x); BN_free (y); EC_KEY_free (signingKey); } // ECDSA_SHA256_P256 const size_t ECDSAP256_KEY_LENGTH = 64; typedef ECDSAVerifier ECDSAP256Verifier; typedef ECDSASigner ECDSAP256Signer; inline void CreateECDSAP256RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { CreateECDSARandomKeys (NID_X9_62_prime256v1, ECDSAP256_KEY_LENGTH, signingPrivateKey, signingPublicKey); } // ECDSA_SHA384_P384 const size_t ECDSAP384_KEY_LENGTH = 96; typedef ECDSAVerifier ECDSAP384Verifier; typedef ECDSASigner ECDSAP384Signer; inline void CreateECDSAP384RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { CreateECDSARandomKeys (NID_secp384r1, ECDSAP384_KEY_LENGTH, signingPrivateKey, signingPublicKey); } // ECDSA_SHA512_P521 const size_t ECDSAP521_KEY_LENGTH = 132; typedef ECDSAVerifier ECDSAP521Verifier; typedef ECDSASigner ECDSAP521Signer; inline void CreateECDSAP521RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { CreateECDSARandomKeys (NID_secp521r1, ECDSAP521_KEY_LENGTH, signingPrivateKey, signingPublicKey); } // RSA template class RSAVerifier: public Verifier { public: RSAVerifier (const uint8_t * signingKey) { m_PublicKey = RSA_new (); RSA_set0_key (m_PublicKey, BN_bin2bn (signingKey, keyLen, NULL) /* n */ , BN_dup (GetRSAE ()) /* d */, NULL); } ~RSAVerifier () { RSA_free (m_PublicKey); } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { uint8_t digest[Hash::hashLen]; Hash::CalculateHash (buf, len, digest); return RSA_verify (type, digest, Hash::hashLen, signature, GetSignatureLen (), m_PublicKey); } size_t GetPublicKeyLen () const { return keyLen; } size_t GetSignatureLen () const { return keyLen; } size_t GetPrivateKeyLen () const { return GetSignatureLen ()*2; }; private: RSA * m_PublicKey; }; template class RSASigner: public Signer { public: RSASigner (const uint8_t * signingPrivateKey) { m_PrivateKey = RSA_new (); RSA_set0_key (m_PrivateKey, BN_bin2bn (signingPrivateKey, keyLen, NULL), /* n */ BN_dup (GetRSAE ()) /* e */, BN_bin2bn (signingPrivateKey + keyLen, keyLen, NULL) /* d */); } ~RSASigner () { RSA_free (m_PrivateKey); } void Sign (const uint8_t * buf, int len, uint8_t * signature) const { uint8_t digest[Hash::hashLen]; Hash::CalculateHash (buf, len, digest); unsigned int signatureLen = keyLen; RSA_sign (type, digest, Hash::hashLen, signature, &signatureLen, m_PrivateKey); } private: RSA * m_PrivateKey; }; inline void CreateRSARandomKeys (size_t publicKeyLen, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { RSA * rsa = RSA_new (); BIGNUM * e = BN_dup (GetRSAE ()); // make it non-const RSA_generate_key_ex (rsa, publicKeyLen*8, e, NULL); const BIGNUM * n, * d, * e1; RSA_get0_key (rsa, &n, &e1, &d); bn2buf (n, signingPrivateKey, publicKeyLen); bn2buf (d, signingPrivateKey + publicKeyLen, publicKeyLen); bn2buf (n, signingPublicKey, publicKeyLen); BN_free (e); // this e is not assigned to rsa->e RSA_free (rsa); } // RSA_SHA256_2048 const size_t RSASHA2562048_KEY_LENGTH = 256; typedef RSAVerifier RSASHA2562048Verifier; typedef RSASigner RSASHA2562048Signer; // RSA_SHA384_3072 const size_t RSASHA3843072_KEY_LENGTH = 384; typedef RSAVerifier RSASHA3843072Verifier; typedef RSASigner RSASHA3843072Signer; // RSA_SHA512_4096 const size_t RSASHA5124096_KEY_LENGTH = 512; typedef RSAVerifier RSASHA5124096Verifier; typedef RSASigner RSASHA5124096Signer; // EdDSA struct EDDSAPoint { BIGNUM * x {nullptr}; BIGNUM * y {nullptr}; BIGNUM * z {nullptr}; BIGNUM * t {nullptr}; // projective coordinates EDDSAPoint () {} EDDSAPoint (const EDDSAPoint& other) { *this = other; } EDDSAPoint (EDDSAPoint&& other) { *this = std::move (other); } EDDSAPoint (BIGNUM * x1, BIGNUM * y1, BIGNUM * z1 = nullptr, BIGNUM * t1 = nullptr) : x(x1) , y(y1) , z(z1) , t(t1) {} ~EDDSAPoint () { BN_free (x); BN_free (y); BN_free(z); BN_free(t); } EDDSAPoint& operator=(EDDSAPoint&& other) { if (this != &other) { BN_free (x); x = other.x; other.x = nullptr; BN_free (y); y = other.y; other.y = nullptr; BN_free (z); z = other.z; other.z = nullptr; BN_free (t); t = other.t; other.t = nullptr; } return *this; } EDDSAPoint& operator=(const EDDSAPoint& other) { if (this != &other) { BN_free (x); x = other.x ? BN_dup (other.x) : nullptr; BN_free (y); y = other.y ? BN_dup (other.y) : nullptr; BN_free (z); z = other.z ? BN_dup (other.z) : nullptr; BN_free (t); t = other.t ? BN_dup (other.t) : nullptr; } return *this; } EDDSAPoint operator-() const { BIGNUM * x1 = NULL, * y1 = NULL, * z1 = NULL, * t1 = NULL; if (x) { x1 = BN_dup (x); BN_set_negative (x1, !BN_is_negative (x)); }; if (y) y1 = BN_dup (y); if (z) z1 = BN_dup (z); if (t) { t1 = BN_dup (t); BN_set_negative (t1, !BN_is_negative (t)); }; return EDDSAPoint {x1, y1, z1, t1}; } }; const size_t EDDSA25519_PUBLIC_KEY_LENGTH = 32; const size_t EDDSA25519_SIGNATURE_LENGTH = 64; const size_t EDDSA25519_PRIVATE_KEY_LENGTH = 32; class EDDSA25519Verifier: public Verifier { public: EDDSA25519Verifier (const uint8_t * signingKey); bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const; size_t GetPublicKeyLen () const { return EDDSA25519_PUBLIC_KEY_LENGTH; }; size_t GetSignatureLen () const { return EDDSA25519_SIGNATURE_LENGTH; }; private: EDDSAPoint m_PublicKey; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; class EDDSA25519Signer: public Signer { public: EDDSA25519Signer (const uint8_t * signingPrivateKey, const uint8_t * signingPublicKey = nullptr); // we pass signingPublicKey to check if it matches private key void Sign (const uint8_t * buf, int len, uint8_t * signature) const; const uint8_t * GetPublicKey () const { return m_PublicKeyEncoded; }; private: uint8_t m_ExpandedPrivateKey[64]; uint8_t m_PublicKeyEncoded[EDDSA25519_PUBLIC_KEY_LENGTH]; }; inline void CreateEDDSA25519RandomKeys (uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { RAND_bytes (signingPrivateKey, EDDSA25519_PRIVATE_KEY_LENGTH); EDDSA25519Signer signer (signingPrivateKey); memcpy (signingPublicKey, signer.GetPublicKey (), EDDSA25519_PUBLIC_KEY_LENGTH); } // ГОСТ Р 34.11 struct GOSTR3411_256_Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { GOSTR3411_2012_256 (buf, len, digest); } enum { hashLen = 32 }; }; struct GOSTR3411_512_Hash { static void CalculateHash (const uint8_t * buf, size_t len, uint8_t * digest) { GOSTR3411_2012_512 (buf, len, digest); } enum { hashLen = 64 }; }; // ГОСТ Р 34.10 const size_t GOSTR3410_256_PUBLIC_KEY_LENGTH = 64; const size_t GOSTR3410_512_PUBLIC_KEY_LENGTH = 128; template class GOSTR3410Verifier: public Verifier { public: enum { keyLen = Hash::hashLen }; GOSTR3410Verifier (GOSTR3410ParamSet paramSet, const uint8_t * signingKey): m_ParamSet (paramSet) { BIGNUM * x = BN_bin2bn (signingKey, GetPublicKeyLen ()/2, NULL); BIGNUM * y = BN_bin2bn (signingKey + GetPublicKeyLen ()/2, GetPublicKeyLen ()/2, NULL); m_PublicKey = GetGOSTR3410Curve (m_ParamSet)->CreatePoint (x, y); BN_free (x); BN_free (y); } ~GOSTR3410Verifier () { EC_POINT_free (m_PublicKey); } bool Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const { uint8_t digest[Hash::hashLen]; Hash::CalculateHash (buf, len, digest); BIGNUM * d = BN_bin2bn (digest, Hash::hashLen, nullptr); BIGNUM * r = BN_bin2bn (signature, GetSignatureLen ()/2, NULL); BIGNUM * s = BN_bin2bn (signature + GetSignatureLen ()/2, GetSignatureLen ()/2, NULL); bool ret = GetGOSTR3410Curve (m_ParamSet)->Verify (m_PublicKey, d, r, s); BN_free (d); BN_free (r); BN_free (s); return ret; } size_t GetPublicKeyLen () const { return keyLen*2; } size_t GetSignatureLen () const { return keyLen*2; } private: GOSTR3410ParamSet m_ParamSet; EC_POINT * m_PublicKey; }; template class GOSTR3410Signer: public Signer { public: enum { keyLen = Hash::hashLen }; GOSTR3410Signer (GOSTR3410ParamSet paramSet, const uint8_t * signingPrivateKey): m_ParamSet (paramSet) { m_PrivateKey = BN_bin2bn (signingPrivateKey, keyLen, nullptr); } ~GOSTR3410Signer () { BN_free (m_PrivateKey); } void Sign (const uint8_t * buf, int len, uint8_t * signature) const { uint8_t digest[Hash::hashLen]; Hash::CalculateHash (buf, len, digest); BIGNUM * d = BN_bin2bn (digest, Hash::hashLen, nullptr); BIGNUM * r = BN_new (), * s = BN_new (); GetGOSTR3410Curve (m_ParamSet)->Sign (m_PrivateKey, d, r, s); bn2buf (r, signature, keyLen); bn2buf (s, signature + keyLen, keyLen); BN_free (d); BN_free (r); BN_free (s); } private: GOSTR3410ParamSet m_ParamSet; BIGNUM * m_PrivateKey; }; inline void CreateGOSTR3410RandomKeys (GOSTR3410ParamSet paramSet, uint8_t * signingPrivateKey, uint8_t * signingPublicKey) { const auto& curve = GetGOSTR3410Curve (paramSet); auto keyLen = curve->GetKeyLen (); RAND_bytes (signingPrivateKey, keyLen); BIGNUM * priv = BN_bin2bn (signingPrivateKey, keyLen, nullptr); auto pub = curve->MulP (priv); BN_free (priv); BIGNUM * x = BN_new (), * y = BN_new (); curve->GetXY (pub, x, y); EC_POINT_free (pub); bn2buf (x, signingPublicKey, keyLen); bn2buf (y, signingPublicKey + keyLen, keyLen); BN_free (x); BN_free (y); } typedef GOSTR3410Verifier GOSTR3410_256_Verifier; typedef GOSTR3410Signer GOSTR3410_256_Signer; typedef GOSTR3410Verifier GOSTR3410_512_Verifier; typedef GOSTR3410Signer GOSTR3410_512_Signer; } } #endif i2pd-2.17.0/libi2pd/Streaming.cpp000066400000000000000000001125621321131324000164160ustar00rootroot00000000000000#include "Crypto.h" #include "Log.h" #include "RouterInfo.h" #include "RouterContext.h" #include "Tunnel.h" #include "Timestamp.h" #include "Destination.h" #include "Streaming.h" namespace i2p { namespace stream { void SendBufferQueue::Add (const uint8_t * buf, size_t len, SendHandler handler) { m_Buffers.push_back (std::make_shared(buf, len, handler)); m_Size += len; } size_t SendBufferQueue::Get (uint8_t * buf, size_t len) { size_t offset = 0; while (!m_Buffers.empty () && offset < len) { auto nextBuffer = m_Buffers.front (); auto rem = nextBuffer->GetRemainingSize (); if (offset + rem <= len) { // whole buffer memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), rem); offset += rem; m_Buffers.pop_front (); // delete it } else { // partially rem = len - offset; memcpy (buf + offset, nextBuffer->GetRemaningBuffer (), len - offset); nextBuffer->offset += (len - offset); offset = len; // break } } m_Size -= offset; return offset; } void SendBufferQueue::CleanUp () { if (!m_Buffers.empty ()) { for (auto it: m_Buffers) it->Cancel (); m_Buffers.clear (); m_Size = 0; } } Stream::Stream (boost::asio::io_service& service, StreamingDestination& local, std::shared_ptr remote, int port): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), m_RemoteLeaseSet (remote), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (port), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0) { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); m_RemoteIdentity = remote->GetIdentity (); } Stream::Stream (boost::asio::io_service& service, StreamingDestination& local): m_Service (service), m_SendStreamID (0), m_SequenceNumber (0), m_LastReceivedSequenceNumber (-1), m_Status (eStreamStatusNew), m_IsAckSendScheduled (false), m_LocalDestination (local), m_ReceiveTimer (m_Service), m_ResendTimer (m_Service), m_AckSendTimer (m_Service), m_NumSentBytes (0), m_NumReceivedBytes (0), m_Port (0), m_WindowSize (MIN_WINDOW_SIZE), m_RTT (INITIAL_RTT), m_RTO (INITIAL_RTO), m_AckDelay (local.GetOwner ()->GetStreamingAckDelay ()), m_LastWindowSizeIncreaseTime (0), m_NumResendAttempts (0) { RAND_bytes ((uint8_t *)&m_RecvStreamID, 4); } Stream::~Stream () { CleanUp (); LogPrint (eLogDebug, "Streaming: Stream deleted"); } void Stream::Terminate () { m_AckSendTimer.cancel (); m_ReceiveTimer.cancel (); m_ResendTimer.cancel (); //CleanUp (); /* Need to recheck - broke working on windows */ m_LocalDestination.DeleteStream (shared_from_this ()); } void Stream::CleanUp () { { std::unique_lock l(m_SendBufferMutex); m_SendBuffer.CleanUp (); } while (!m_ReceiveQueue.empty ()) { auto packet = m_ReceiveQueue.front (); m_ReceiveQueue.pop (); m_LocalDestination.DeletePacket (packet); } for (auto it: m_SentPackets) m_LocalDestination.DeletePacket (it); m_SentPackets.clear (); for (auto it: m_SavedPackets) m_LocalDestination.DeletePacket (it); m_SavedPackets.clear (); } void Stream::HandleNextPacket (Packet * packet) { m_NumReceivedBytes += packet->GetLength (); if (!m_SendStreamID) m_SendStreamID = packet->GetReceiveStreamID (); if (!packet->IsNoAck ()) // ack received ProcessAck (packet); int32_t receivedSeqn = packet->GetSeqn (); bool isSyn = packet->IsSYN (); if (!receivedSeqn && !isSyn) { // plain ack LogPrint (eLogDebug, "Streaming: Plain ACK received"); m_LocalDestination.DeletePacket (packet); return; } LogPrint (eLogDebug, "Streaming: Received seqn=", receivedSeqn, " on sSID=", m_SendStreamID); if (receivedSeqn == m_LastReceivedSequenceNumber + 1) { // we have received next in sequence message ProcessPacket (packet); // we should also try stored messages if any for (auto it = m_SavedPackets.begin (); it != m_SavedPackets.end ();) { if ((*it)->GetSeqn () == (uint32_t)(m_LastReceivedSequenceNumber + 1)) { Packet * savedPacket = *it; m_SavedPackets.erase (it++); ProcessPacket (savedPacket); } else break; } // schedule ack for last message if (m_Status == eStreamStatusOpen) { if (!m_IsAckSendScheduled) { m_IsAckSendScheduled = true; auto ackTimeout = m_RTT/10; if (ackTimeout > m_AckDelay) ackTimeout = m_AckDelay; m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(ackTimeout)); m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, shared_from_this (), std::placeholders::_1)); } } else if (isSyn) // we have to send SYN back to incoming connection SendBuffer (); // also sets m_IsOpen } else { if (receivedSeqn <= m_LastReceivedSequenceNumber) { // we have received duplicate LogPrint (eLogWarning, "Streaming: Duplicate message ", receivedSeqn, " on sSID=", m_SendStreamID); SendQuickAck (); // resend ack for previous message again m_LocalDestination.DeletePacket (packet); // packet dropped } else { LogPrint (eLogWarning, "Streaming: Missing messages on sSID=", m_SendStreamID, ": from ", m_LastReceivedSequenceNumber + 1, " to ", receivedSeqn - 1); // save message and wait for missing message again SavePacket (packet); if (m_LastReceivedSequenceNumber >= 0) { // send NACKs for missing messages ASAP if (m_IsAckSendScheduled) { m_IsAckSendScheduled = false; m_AckSendTimer.cancel (); } SendQuickAck (); } else { // wait for SYN m_IsAckSendScheduled = true; m_AckSendTimer.expires_from_now (boost::posix_time::milliseconds(SYN_TIMEOUT)); m_AckSendTimer.async_wait (std::bind (&Stream::HandleAckSendTimer, shared_from_this (), std::placeholders::_1)); } } } } void Stream::SavePacket (Packet * packet) { if (!m_SavedPackets.insert (packet).second) m_LocalDestination.DeletePacket (packet); } void Stream::ProcessPacket (Packet * packet) { // process flags uint32_t receivedSeqn = packet->GetSeqn (); uint16_t flags = packet->GetFlags (); LogPrint (eLogDebug, "Streaming: Process seqn=", receivedSeqn, ", flags=", flags); const uint8_t * optionData = packet->GetOptionData (); if (flags & PACKET_FLAG_DELAY_REQUESTED) optionData += 2; if (flags & PACKET_FLAG_FROM_INCLUDED) { m_RemoteIdentity = std::make_shared(optionData, packet->GetOptionSize ()); if (m_RemoteIdentity->IsRSA ()) { LogPrint (eLogInfo, "Streaming: Incoming stream from RSA destination ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " Discarded"); m_LocalDestination.DeletePacket (packet); Terminate (); return; } optionData += m_RemoteIdentity->GetFullLen (); if (!m_RemoteLeaseSet) LogPrint (eLogDebug, "Streaming: Incoming stream from ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), ", sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); } if (flags & PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED) { uint16_t maxPacketSize = bufbe16toh (optionData); LogPrint (eLogDebug, "Streaming: Max packet size ", maxPacketSize); optionData += 2; } if (flags & PACKET_FLAG_SIGNATURE_INCLUDED) { uint8_t signature[256]; auto signatureLen = m_RemoteIdentity->GetSignatureLen (); if(signatureLen <= sizeof(signature)) { memcpy (signature, optionData, signatureLen); memset (const_cast(optionData), 0, signatureLen); if (!m_RemoteIdentity->Verify (packet->GetBuffer (), packet->GetLength (), signature)) { LogPrint (eLogError, "Streaming: Signature verification failed, sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID); Close (); flags |= PACKET_FLAG_CLOSE; } memcpy (const_cast(optionData), signature, signatureLen); optionData += signatureLen; } else { LogPrint(eLogError, "Streaming: Signature too big, ", signatureLen, " bytes"); } } packet->offset = packet->GetPayload () - packet->buf; if (packet->GetLength () > 0) { m_ReceiveQueue.push (packet); m_ReceiveTimer.cancel (); } else m_LocalDestination.DeletePacket (packet); m_LastReceivedSequenceNumber = receivedSeqn; if (flags & PACKET_FLAG_RESET) { LogPrint (eLogDebug, "Streaming: closing stream sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ": reset flag received in packet #", receivedSeqn); m_Status = eStreamStatusReset; Close (); } else if (flags & PACKET_FLAG_CLOSE) { if (m_Status != eStreamStatusClosed) SendClose (); m_Status = eStreamStatusClosed; Terminate (); } } void Stream::ProcessAck (Packet * packet) { bool acknowledged = false; auto ts = i2p::util::GetMillisecondsSinceEpoch (); uint32_t ackThrough = packet->GetAckThrough (); if (ackThrough > m_SequenceNumber) { LogPrint (eLogError, "Streaming: Unexpected ackThrough=", ackThrough, " > seqn=", m_SequenceNumber); return; } int nackCount = packet->GetNACKCount (); for (auto it = m_SentPackets.begin (); it != m_SentPackets.end ();) { auto seqn = (*it)->GetSeqn (); if (seqn <= ackThrough) { if (nackCount > 0) { bool nacked = false; for (int i = 0; i < nackCount; i++) if (seqn == packet->GetNACK (i)) { nacked = true; break; } if (nacked) { LogPrint (eLogDebug, "Streaming: Packet ", seqn, " NACK"); ++it; continue; } } auto sentPacket = *it; uint64_t rtt = ts - sentPacket->sendTime; if(ts < sentPacket->sendTime) { LogPrint(eLogError, "Streaming: Packet ", seqn, "sent from the future, sendTime=", sentPacket->sendTime); rtt = 1; } m_RTT = (m_RTT*seqn + rtt)/(seqn + 1); m_RTO = m_RTT*1.5; // TODO: implement it better LogPrint (eLogDebug, "Streaming: Packet ", seqn, " acknowledged rtt=", rtt, " sentTime=", sentPacket->sendTime); m_SentPackets.erase (it++); m_LocalDestination.DeletePacket (sentPacket); acknowledged = true; if (m_WindowSize < WINDOW_SIZE) m_WindowSize++; // slow start else { // linear growth if (ts > m_LastWindowSizeIncreaseTime + m_RTT) { m_WindowSize++; if (m_WindowSize > MAX_WINDOW_SIZE) m_WindowSize = MAX_WINDOW_SIZE; m_LastWindowSizeIncreaseTime = ts; } } if (!seqn && m_RoutingSession) // first message confirmed m_RoutingSession->SetSharedRoutingPath ( std::make_shared ( i2p::garlic::GarlicRoutingPath{m_CurrentOutboundTunnel, m_CurrentRemoteLease, m_RTT, 0, 0})); } else break; } if (m_SentPackets.empty ()) m_ResendTimer.cancel (); if (acknowledged) { m_NumResendAttempts = 0; SendBuffer (); } if (m_Status == eStreamStatusClosed) Terminate (); else if (m_Status == eStreamStatusClosing) Close (); // check is all outgoing messages have been sent and we can send close } size_t Stream::Send (const uint8_t * buf, size_t len) { // TODO: check max buffer size AsyncSend (buf, len, nullptr); return len; } void Stream::AsyncSend (const uint8_t * buf, size_t len, SendHandler handler) { if (len > 0 && buf) { std::unique_lock l(m_SendBufferMutex); m_SendBuffer.Add (buf, len, handler); } else if (handler) handler(boost::system::error_code ()); m_Service.post (std::bind (&Stream::SendBuffer, shared_from_this ())); } void Stream::SendBuffer () { int numMsgs = m_WindowSize - m_SentPackets.size (); if (numMsgs <= 0) return; // window is full bool isNoAck = m_LastReceivedSequenceNumber < 0; // first packet std::vector packets; { std::unique_lock l(m_SendBufferMutex); while ((m_Status == eStreamStatusNew) || (IsEstablished () && !m_SendBuffer.IsEmpty () && numMsgs > 0)) { Packet * p = m_LocalDestination.NewPacket (); uint8_t * packet = p->GetBuffer (); // TODO: implement setters size_t size = 0; htobe32buf (packet + size, m_SendStreamID); size += 4; // sendStreamID htobe32buf (packet + size, m_RecvStreamID); size += 4; // receiveStreamID htobe32buf (packet + size, m_SequenceNumber++); size += 4; // sequenceNum if (isNoAck) htobuf32 (packet + size, 0); else htobe32buf (packet + size, m_LastReceivedSequenceNumber); size += 4; // ack Through packet[size] = 0; size++; // NACK count packet[size] = m_RTO/1000; size++; // resend delay if (m_Status == eStreamStatusNew) { // initial packet m_Status = eStreamStatusOpen; uint16_t flags = PACKET_FLAG_SYNCHRONIZE | PACKET_FLAG_FROM_INCLUDED | PACKET_FLAG_SIGNATURE_INCLUDED | PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED; if (isNoAck) flags |= PACKET_FLAG_NO_ACK; htobe16buf (packet + size, flags); size += 2; // flags size_t identityLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetFullLen (); size_t signatureLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetSignatureLen (); htobe16buf (packet + size, identityLen + signatureLen + 2); // identity + signature + packet size size += 2; // options size m_LocalDestination.GetOwner ()->GetIdentity ()->ToBuffer (packet + size, identityLen); size += identityLen; // from htobe16buf (packet + size, STREAMING_MTU); size += 2; // max packet size uint8_t * signature = packet + size; // set it later memset (signature, 0, signatureLen); // zeroes for now size += signatureLen; // signature size += m_SendBuffer.Get (packet + size, STREAMING_MTU - size); // payload m_LocalDestination.GetOwner ()->Sign (packet, size, signature); } else { // follow on packet htobuf16 (packet + size, 0); size += 2; // flags htobuf16 (packet + size, 0); // no options size += 2; // options size size += m_SendBuffer.Get(packet + size, STREAMING_MTU - size); // payload } p->len = size; packets.push_back (p); numMsgs--; } } if (packets.size () > 0) { if (m_SavedPackets.empty ()) // no NACKS { m_IsAckSendScheduled = false; m_AckSendTimer.cancel (); } bool isEmpty = m_SentPackets.empty (); auto ts = i2p::util::GetMillisecondsSinceEpoch (); for (auto& it: packets) { it->sendTime = ts; m_SentPackets.insert (it); } SendPackets (packets); if (m_Status == eStreamStatusClosing && m_SendBuffer.IsEmpty ()) SendClose (); if (isEmpty) ScheduleResend (); } } void Stream::SendQuickAck () { int32_t lastReceivedSeqn = m_LastReceivedSequenceNumber; if (!m_SavedPackets.empty ()) { int32_t seqn = (*m_SavedPackets.rbegin ())->GetSeqn (); if (seqn > lastReceivedSeqn) lastReceivedSeqn = seqn; } if (lastReceivedSeqn < 0) { LogPrint (eLogError, "Streaming: No packets have been received yet"); return; } Packet p; uint8_t * packet = p.GetBuffer (); size_t size = 0; htobe32buf (packet + size, m_SendStreamID); size += 4; // sendStreamID htobe32buf (packet + size, m_RecvStreamID); size += 4; // receiveStreamID htobuf32 (packet + size, 0); // this is plain Ack message size += 4; // sequenceNum htobe32buf (packet + size, lastReceivedSeqn); size += 4; // ack Through uint8_t numNacks = 0; if (lastReceivedSeqn > m_LastReceivedSequenceNumber) { // fill NACKs uint8_t * nacks = packet + size + 1; auto nextSeqn = m_LastReceivedSequenceNumber + 1; for (auto it: m_SavedPackets) { auto seqn = it->GetSeqn (); if (numNacks + (seqn - nextSeqn) >= 256) { LogPrint (eLogError, "Streaming: Number of NACKs exceeds 256. seqn=", seqn, " nextSeqn=", nextSeqn); htobe32buf (packet + 12, nextSeqn); // change ack Through break; } for (uint32_t i = nextSeqn; i < seqn; i++) { htobe32buf (nacks, i); nacks += 4; numNacks++; } nextSeqn = seqn + 1; } packet[size] = numNacks; size++; // NACK count size += numNacks*4; // NACKs } else { // No NACKs packet[size] = 0; size++; // NACK count } size++; // resend delay htobuf16 (packet + size, 0); // nof flags set size += 2; // flags htobuf16 (packet + size, 0); // no options size += 2; // options size p.len = size; SendPackets (std::vector { &p }); LogPrint (eLogDebug, "Streaming: Quick Ack sent. ", (int)numNacks, " NACKs"); } void Stream::Close () { LogPrint(eLogDebug, "Streaming: closing stream with sSID=", m_SendStreamID, ", rSID=", m_RecvStreamID, ", status=", m_Status); switch (m_Status) { case eStreamStatusOpen: m_Status = eStreamStatusClosing; Close (); // recursion if (m_Status == eStreamStatusClosing) //still closing LogPrint (eLogDebug, "Streaming: Trying to send stream data before closing, sSID=", m_SendStreamID); break; case eStreamStatusReset: // TODO: send reset Terminate (); break; case eStreamStatusClosing: if (m_SentPackets.empty () && m_SendBuffer.IsEmpty ()) // nothing to send { m_Status = eStreamStatusClosed; SendClose (); } break; case eStreamStatusClosed: // already closed Terminate (); break; default: LogPrint (eLogWarning, "Streaming: Unexpected stream status ", (int)m_Status, "sSID=", m_SendStreamID); }; } void Stream::SendClose () { Packet * p = m_LocalDestination.NewPacket (); uint8_t * packet = p->GetBuffer (); size_t size = 0; htobe32buf (packet + size, m_SendStreamID); size += 4; // sendStreamID htobe32buf (packet + size, m_RecvStreamID); size += 4; // receiveStreamID htobe32buf (packet + size, m_SequenceNumber++); size += 4; // sequenceNum htobe32buf (packet + size, m_LastReceivedSequenceNumber >= 0 ? m_LastReceivedSequenceNumber : 0); size += 4; // ack Through packet[size] = 0; size++; // NACK count size++; // resend delay htobe16buf (packet + size, PACKET_FLAG_CLOSE | PACKET_FLAG_SIGNATURE_INCLUDED); size += 2; // flags size_t signatureLen = m_LocalDestination.GetOwner ()->GetIdentity ()->GetSignatureLen (); htobe16buf (packet + size, signatureLen); // signature only size += 2; // options size uint8_t * signature = packet + size; memset (packet + size, 0, signatureLen); size += signatureLen; // signature m_LocalDestination.GetOwner ()->Sign (packet, size, signature); p->len = size; m_Service.post (std::bind (&Stream::SendPacket, shared_from_this (), p)); LogPrint (eLogDebug, "Streaming: FIN sent, sSID=", m_SendStreamID); } size_t Stream::ConcatenatePackets (uint8_t * buf, size_t len) { size_t pos = 0; while (pos < len && !m_ReceiveQueue.empty ()) { Packet * packet = m_ReceiveQueue.front (); size_t l = std::min (packet->GetLength (), len - pos); memcpy (buf + pos, packet->GetBuffer (), l); pos += l; packet->offset += l; if (!packet->GetLength ()) { m_ReceiveQueue.pop (); m_LocalDestination.DeletePacket (packet); } } return pos; } bool Stream::SendPacket (Packet * packet) { if (packet) { if (m_IsAckSendScheduled) { m_IsAckSendScheduled = false; m_AckSendTimer.cancel (); } SendPackets (std::vector { packet }); bool isEmpty = m_SentPackets.empty (); m_SentPackets.insert (packet); if (isEmpty) ScheduleResend (); return true; } else return false; } void Stream::SendPackets (const std::vector& packets) { if (!m_RemoteLeaseSet) { UpdateCurrentRemoteLease (); if (!m_RemoteLeaseSet) { LogPrint (eLogError, "Streaming: Can't send packets, missing remote LeaseSet, sSID=", m_SendStreamID); return; } } if (!m_RoutingSession || !m_RoutingSession->GetOwner ()) // expired and detached m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); if (!m_CurrentOutboundTunnel && m_RoutingSession) // first message to send { // try to get shared path first auto routingPath = m_RoutingSession->GetSharedRoutingPath (); if (routingPath) { m_CurrentOutboundTunnel = routingPath->outboundTunnel; m_CurrentRemoteLease = routingPath->remoteLease; m_RTT = routingPath->rtt; m_RTO = m_RTT*1.5; // TODO: implement it better } } if (!m_CurrentOutboundTunnel || !m_CurrentOutboundTunnel->IsEstablished ()) m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNewOutboundTunnel (m_CurrentOutboundTunnel); if (!m_CurrentOutboundTunnel) { LogPrint (eLogError, "Streaming: No outbound tunnels in the pool, sSID=", m_SendStreamID); return; } auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (!m_CurrentRemoteLease || !m_CurrentRemoteLease->endDate || // excluded from LeaseSet ts >= m_CurrentRemoteLease->endDate - i2p::data::LEASE_ENDDATE_THRESHOLD) UpdateCurrentRemoteLease (true); if (m_CurrentRemoteLease && ts < m_CurrentRemoteLease->endDate + i2p::data::LEASE_ENDDATE_THRESHOLD) { std::vector msgs; for (auto it: packets) { auto msg = m_RoutingSession->WrapSingleMessage (m_LocalDestination.CreateDataMessage (it->GetBuffer (), it->GetLength (), m_Port)); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, m_CurrentRemoteLease->tunnelGateway, m_CurrentRemoteLease->tunnelID, msg }); m_NumSentBytes += it->GetLength (); } m_CurrentOutboundTunnel->SendTunnelDataMsg (msgs); } else { LogPrint (eLogWarning, "Streaming: Remote lease is not available, sSID=", m_SendStreamID); if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); // invalidate routing path } } void Stream::SendUpdatedLeaseSet () { if (m_RoutingSession) { if (m_RoutingSession->IsLeaseSetNonConfirmed ()) { auto ts = i2p::util::GetMillisecondsSinceEpoch (); if (ts > m_RoutingSession->GetLeaseSetSubmissionTime () + i2p::garlic::LEASET_CONFIRMATION_TIMEOUT) { // LeaseSet was not confirmed, should try other tunnels LogPrint (eLogWarning, "Streaming: LeaseSet was not confrimed in ", i2p::garlic::LEASET_CONFIRMATION_TIMEOUT, " milliseconds. Trying to resubmit"); m_RoutingSession->SetSharedRoutingPath (nullptr); m_CurrentOutboundTunnel = nullptr; m_CurrentRemoteLease = nullptr; SendQuickAck (); } } else if (m_RoutingSession->IsLeaseSetUpdated ()) { LogPrint (eLogDebug, "Streaming: sending updated LeaseSet"); SendQuickAck (); } } } void Stream::ScheduleResend () { m_ResendTimer.cancel (); // check for invalid value if (m_RTO <= 0) m_RTO = INITIAL_RTO; m_ResendTimer.expires_from_now (boost::posix_time::milliseconds(m_RTO)); m_ResendTimer.async_wait (std::bind (&Stream::HandleResendTimer, shared_from_this (), std::placeholders::_1)); } void Stream::HandleResendTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { // check for resend attempts if (m_NumResendAttempts >= MAX_NUM_RESEND_ATTEMPTS) { LogPrint (eLogWarning, "Streaming: packet was not ACKed after ", MAX_NUM_RESEND_ATTEMPTS, " attempts, terminate, rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); m_Status = eStreamStatusReset; Close (); return; } // collect packets to resend auto ts = i2p::util::GetMillisecondsSinceEpoch (); std::vector packets; for (auto it : m_SentPackets) { if (ts >= it->sendTime + m_RTO) { it->sendTime = ts; packets.push_back (it); } } // select tunnels if necessary and send if (packets.size () > 0) { m_NumResendAttempts++; m_RTO *= 2; switch (m_NumResendAttempts) { case 1: // congesion avoidance m_WindowSize /= 2; if (m_WindowSize < MIN_WINDOW_SIZE) m_WindowSize = MIN_WINDOW_SIZE; break; case 2: m_RTO = INITIAL_RTO; // drop RTO to initial upon tunnels pair change first time // no break here case 4: if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); UpdateCurrentRemoteLease (); // pick another lease LogPrint (eLogWarning, "Streaming: Another remote lease has been selected for stream with rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); break; case 3: // pick another outbound tunnel if (m_RoutingSession) m_RoutingSession->SetSharedRoutingPath (nullptr); m_CurrentOutboundTunnel = m_LocalDestination.GetOwner ()->GetTunnelPool ()->GetNextOutboundTunnel (m_CurrentOutboundTunnel); LogPrint (eLogWarning, "Streaming: Another outbound tunnel has been selected for stream with sSID=", m_SendStreamID); break; default: ; } SendPackets (packets); } ScheduleResend (); } } void Stream::HandleAckSendTimer (const boost::system::error_code& ecode) { if (m_IsAckSendScheduled) { if (m_LastReceivedSequenceNumber < 0) { LogPrint (eLogWarning, "Streaming: SYN has not been received after ", SYN_TIMEOUT, " milliseconds after follow on, terminate rSID=", m_RecvStreamID, ", sSID=", m_SendStreamID); m_Status = eStreamStatusReset; Close (); return; } if (m_Status == eStreamStatusOpen) { if (m_RoutingSession && m_RoutingSession->IsLeaseSetNonConfirmed ()) { // seems something went wrong and we should re-select tunnels m_CurrentOutboundTunnel = nullptr; m_CurrentRemoteLease = nullptr; } SendQuickAck (); } m_IsAckSendScheduled = false; } } void Stream::UpdateCurrentRemoteLease (bool expired) { if (!m_RemoteLeaseSet || m_RemoteLeaseSet->IsExpired ()) { m_RemoteLeaseSet = m_LocalDestination.GetOwner ()->FindLeaseSet (m_RemoteIdentity->GetIdentHash ()); if (!m_RemoteLeaseSet) { LogPrint (eLogWarning, "Streaming: LeaseSet ", m_RemoteIdentity->GetIdentHash ().ToBase64 (), " not found"); m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // try to request for a next attempt } } if (m_RemoteLeaseSet) { if (!m_RoutingSession) m_RoutingSession = m_LocalDestination.GetOwner ()->GetRoutingSession (m_RemoteLeaseSet, true); auto leases = m_RemoteLeaseSet->GetNonExpiredLeases (false); // try without threshold first if (leases.empty ()) { expired = false; m_LocalDestination.GetOwner ()->RequestDestination (m_RemoteIdentity->GetIdentHash ()); // time to request leases = m_RemoteLeaseSet->GetNonExpiredLeases (true); // then with threshold } if (!leases.empty ()) { bool updated = false; if (expired && m_CurrentRemoteLease) { for (const auto& it: leases) if ((it->tunnelGateway == m_CurrentRemoteLease->tunnelGateway) && (it->tunnelID != m_CurrentRemoteLease->tunnelID)) { m_CurrentRemoteLease = it; updated = true; break; } } if (!updated) { uint32_t i = rand () % leases.size (); if (m_CurrentRemoteLease && leases[i]->tunnelID == m_CurrentRemoteLease->tunnelID) // make sure we don't select previous i = (i + 1) % leases.size (); // if so, pick next m_CurrentRemoteLease = leases[i]; } } else { LogPrint (eLogWarning, "Streaming: All remote leases are expired"); m_RemoteLeaseSet = nullptr; m_CurrentRemoteLease = nullptr; // we have requested expired before, no need to do it twice } } else { LogPrint (eLogWarning, "Streaming: Remote LeaseSet not found"); m_CurrentRemoteLease = nullptr; } } StreamingDestination::StreamingDestination (std::shared_ptr owner, uint16_t localPort, bool gzip): m_Owner (owner), m_LocalPort (localPort), m_Gzip (gzip), m_LastIncomingReceiveStreamID (0), m_PendingIncomingTimer (m_Owner->GetService ()), m_ConnTrackTimer(m_Owner->GetService()), m_ConnsPerMinute(DEFAULT_MAX_CONNS_PER_MIN), m_LastBanClear(i2p::util::GetMillisecondsSinceEpoch()), m_EnableDrop(false) { } StreamingDestination::~StreamingDestination () { for (auto& it: m_SavedPackets) { for (auto it1: it.second) DeletePacket (it1); it.second.clear (); } m_SavedPackets.clear (); } void StreamingDestination::Start () { ScheduleConnTrack(); } void StreamingDestination::Stop () { ResetAcceptor (); m_PendingIncomingTimer.cancel (); m_PendingIncomingStreams.clear (); m_ConnTrackTimer.cancel(); { std::unique_lock l(m_StreamsMutex); m_Streams.clear (); } { std::unique_lock l(m_ConnsMutex); m_Conns.clear (); } } void StreamingDestination::HandleNextPacket (Packet * packet) { uint32_t sendStreamID = packet->GetSendStreamID (); if (sendStreamID) { auto it = m_Streams.find (sendStreamID); if (it != m_Streams.end ()) it->second->HandleNextPacket (packet); else { LogPrint (eLogInfo, "Streaming: Unknown stream sSID=", sendStreamID); DeletePacket (packet); } } else { if (packet->IsSYN () && !packet->GetSeqn ()) // new incoming stream { uint32_t receiveStreamID = packet->GetReceiveStreamID (); if (receiveStreamID == m_LastIncomingReceiveStreamID) { // already pending LogPrint(eLogWarning, "Streaming: Incoming streaming with rSID=", receiveStreamID, " already exists"); DeletePacket (packet); // drop it, because previous should be connected return; } auto incomingStream = CreateNewIncomingStream (); incomingStream->HandleNextPacket (packet); // SYN auto ident = incomingStream->GetRemoteIdentity(); if(ident && m_EnableDrop) { auto ih = ident->GetIdentHash(); if(DropNewStream(ih)) { // drop LogPrint(eLogWarning, "Streaming: Dropping connection, too many inbound streams from ", ih.ToBase32()); incomingStream->Terminate(); return; } } m_LastIncomingReceiveStreamID = receiveStreamID; // handle saved packets if any { auto it = m_SavedPackets.find (receiveStreamID); if (it != m_SavedPackets.end ()) { LogPrint (eLogDebug, "Streaming: Processing ", it->second.size (), " saved packets for rSID=", receiveStreamID); for (auto it1: it->second) incomingStream->HandleNextPacket (it1); m_SavedPackets.erase (it); } } // accept if (m_Acceptor != nullptr) m_Acceptor (incomingStream); else { LogPrint (eLogWarning, "Streaming: Acceptor for incoming stream is not set"); if (m_PendingIncomingStreams.size () < MAX_PENDING_INCOMING_BACKLOG) { m_PendingIncomingStreams.push_back (incomingStream); m_PendingIncomingTimer.cancel (); m_PendingIncomingTimer.expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT)); m_PendingIncomingTimer.async_wait (std::bind (&StreamingDestination::HandlePendingIncomingTimer, shared_from_this (), std::placeholders::_1)); LogPrint (eLogDebug, "Streaming: Pending incoming stream added, rSID=", receiveStreamID); } else { LogPrint (eLogWarning, "Streaming: Pending incoming streams backlog exceeds ", MAX_PENDING_INCOMING_BACKLOG); incomingStream->Close (); } } } else // follow on packet without SYN { uint32_t receiveStreamID = packet->GetReceiveStreamID (); for (auto& it: m_Streams) if (it.second->GetSendStreamID () == receiveStreamID) { // found it.second->HandleNextPacket (packet); return; } // save follow on packet auto it = m_SavedPackets.find (receiveStreamID); if (it != m_SavedPackets.end ()) it->second.push_back (packet); else { m_SavedPackets[receiveStreamID] = std::list{ packet }; auto timer = std::make_shared (m_Owner->GetService ()); timer->expires_from_now (boost::posix_time::seconds(PENDING_INCOMING_TIMEOUT)); auto s = shared_from_this (); timer->async_wait ([s,timer,receiveStreamID](const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto it = s->m_SavedPackets.find (receiveStreamID); if (it != s->m_SavedPackets.end ()) { for (auto it1: it->second) s->DeletePacket (it1); it->second.clear (); s->m_SavedPackets.erase (it); } } }); } } } } std::shared_ptr StreamingDestination::CreateNewOutgoingStream (std::shared_ptr remote, int port) { auto s = std::make_shared (m_Owner->GetService (), *this, remote, port); std::unique_lock l(m_StreamsMutex); m_Streams[s->GetRecvStreamID ()] = s; return s; } std::shared_ptr StreamingDestination::CreateNewIncomingStream () { auto s = std::make_shared (m_Owner->GetService (), *this); std::unique_lock l(m_StreamsMutex); m_Streams[s->GetRecvStreamID ()] = s; return s; } void StreamingDestination::DeleteStream (std::shared_ptr stream) { if (stream) { std::unique_lock l(m_StreamsMutex); auto it = m_Streams.find (stream->GetRecvStreamID ()); if (it != m_Streams.end ()) m_Streams.erase (it); } } void StreamingDestination::SetAcceptor (const Acceptor& acceptor) { m_Acceptor = acceptor; // we must set it immediately for IsAcceptorSet auto s = shared_from_this (); m_Owner->GetService ().post([s](void) { // take care about incoming queue for (auto& it: s->m_PendingIncomingStreams) if (it->GetStatus () == eStreamStatusOpen) // still open? s->m_Acceptor (it); s->m_PendingIncomingStreams.clear (); s->m_PendingIncomingTimer.cancel (); }); } void StreamingDestination::ResetAcceptor () { if (m_Acceptor) m_Acceptor (nullptr); m_Acceptor = nullptr; } void StreamingDestination::AcceptOnce (const Acceptor& acceptor) { m_Owner->GetService ().post([acceptor, this](void) { if (!m_PendingIncomingStreams.empty ()) { acceptor (m_PendingIncomingStreams.front ()); m_PendingIncomingStreams.pop_front (); if (m_PendingIncomingStreams.empty ()) m_PendingIncomingTimer.cancel (); } else // we must save old acceptor and set it back { m_Acceptor = std::bind (&StreamingDestination::AcceptOnceAcceptor, this, std::placeholders::_1, acceptor, m_Acceptor); } }); } void StreamingDestination::AcceptOnceAcceptor (std::shared_ptr stream, Acceptor acceptor, Acceptor prev) { m_Acceptor = prev; acceptor (stream); } void StreamingDestination::HandlePendingIncomingTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogWarning, "Streaming: Pending incoming timeout expired"); for (auto& it: m_PendingIncomingStreams) it->Close (); m_PendingIncomingStreams.clear (); } } void StreamingDestination::HandleDataMessagePayload (const uint8_t * buf, size_t len) { // unzip it Packet * uncompressed = NewPacket (); uncompressed->offset = 0; uncompressed->len = m_Inflator.Inflate (buf, len, uncompressed->buf, MAX_PACKET_SIZE); if (uncompressed->len) HandleNextPacket (uncompressed); else DeletePacket (uncompressed); } std::shared_ptr StreamingDestination::CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort) { auto msg = NewI2NPShortMessage (); if (!m_Gzip || len <= i2p::stream::COMPRESSION_THRESHOLD_SIZE) m_Deflator.SetCompressionLevel (Z_NO_COMPRESSION); else m_Deflator.SetCompressionLevel (Z_DEFAULT_COMPRESSION); uint8_t * buf = msg->GetPayload (); buf += 4; // reserve for lengthlength msg->len += 4; size_t size = m_Deflator.Deflate (payload, len, buf, msg->maxLen - msg->len); if (size) { htobe32buf (msg->GetPayload (), size); // length htobe16buf (buf + 4, m_LocalPort); // source port htobe16buf (buf + 6, toPort); // destination port buf[9] = i2p::client::PROTOCOL_TYPE_STREAMING; // streaming protocol msg->len += size; msg->FillI2NPMessageHeader (eI2NPData); } else msg = nullptr; return msg; } void StreamingDestination::SetMaxConnsPerMinute(const uint32_t conns) { m_EnableDrop = conns > 0; m_ConnsPerMinute = conns; LogPrint(eLogDebug, "Streaming: Set max conns per minute per destination to ", conns); } bool StreamingDestination::DropNewStream(const i2p::data::IdentHash & ih) { std::lock_guard lock(m_ConnsMutex); if (m_Banned.size() > MAX_BANNED_CONNS) return true; // overload auto end = std::end(m_Banned); if ( std::find(std::begin(m_Banned), end, ih) != end) return true; // already banned auto itr = m_Conns.find(ih); if (itr == m_Conns.end()) m_Conns[ih] = 0; m_Conns[ih] += 1; bool ban = m_Conns[ih] >= m_ConnsPerMinute; if (ban) { m_Banned.push_back(ih); m_Conns.erase(ih); LogPrint(eLogWarning, "Streaming: ban ", ih.ToBase32()); } return ban; } void StreamingDestination::HandleConnTrack(const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { { // acquire lock std::lock_guard lock(m_ConnsMutex); // clear conn tracking m_Conns.clear(); // check for ban clear auto ts = i2p::util::GetMillisecondsSinceEpoch(); if (ts - m_LastBanClear >= DEFAULT_BAN_INTERVAL) { // clear bans m_Banned.clear(); m_LastBanClear = ts; } } // reschedule timer ScheduleConnTrack(); } } void StreamingDestination::ScheduleConnTrack() { m_ConnTrackTimer.expires_from_now (boost::posix_time::seconds(60)); m_ConnTrackTimer.async_wait ( std::bind (&StreamingDestination::HandleConnTrack, shared_from_this (), std::placeholders::_1)); } } } i2pd-2.17.0/libi2pd/Streaming.h000066400000000000000000000324311321131324000160570ustar00rootroot00000000000000#ifndef STREAMING_H__ #define STREAMING_H__ #include #include #include #include #include #include #include #include #include #include "Base.h" #include "I2PEndian.h" #include "Identity.h" #include "LeaseSet.h" #include "I2NPProtocol.h" #include "Garlic.h" #include "Tunnel.h" #include "util.h" // MemoryPool namespace i2p { namespace client { class ClientDestination; } namespace stream { const uint16_t PACKET_FLAG_SYNCHRONIZE = 0x0001; const uint16_t PACKET_FLAG_CLOSE = 0x0002; const uint16_t PACKET_FLAG_RESET = 0x0004; const uint16_t PACKET_FLAG_SIGNATURE_INCLUDED = 0x0008; const uint16_t PACKET_FLAG_SIGNATURE_REQUESTED = 0x0010; const uint16_t PACKET_FLAG_FROM_INCLUDED = 0x0020; const uint16_t PACKET_FLAG_DELAY_REQUESTED = 0x0040; const uint16_t PACKET_FLAG_MAX_PACKET_SIZE_INCLUDED = 0x0080; const uint16_t PACKET_FLAG_PROFILE_INTERACTIVE = 0x0100; const uint16_t PACKET_FLAG_ECHO = 0x0200; const uint16_t PACKET_FLAG_NO_ACK = 0x0400; const size_t STREAMING_MTU = 1730; const size_t MAX_PACKET_SIZE = 4096; const size_t COMPRESSION_THRESHOLD_SIZE = 66; const int MAX_NUM_RESEND_ATTEMPTS = 6; const int WINDOW_SIZE = 6; // in messages const int MIN_WINDOW_SIZE = 1; const int MAX_WINDOW_SIZE = 128; const int INITIAL_RTT = 8000; // in milliseconds const int INITIAL_RTO = 9000; // in milliseconds const int SYN_TIMEOUT = 200; // how long we wait for SYN after follow-on, in milliseconds const size_t MAX_PENDING_INCOMING_BACKLOG = 128; const int PENDING_INCOMING_TIMEOUT = 10; // in seconds const int MAX_RECEIVE_TIMEOUT = 30; // in seconds /** i2cp option for limiting inbound stremaing connections */ const char I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN[] = "maxconns"; /** default maximum connections attempts per minute per destination */ const uint32_t DEFAULT_MAX_CONNS_PER_MIN = 600; /** * max banned destinations per local destination * TODO: make configurable */ const uint16_t MAX_BANNED_CONNS = 9999; /** * length of a ban in ms * TODO: make configurable */ const uint64_t DEFAULT_BAN_INTERVAL = 60 * 60 * 1000; struct Packet { size_t len, offset; uint8_t buf[MAX_PACKET_SIZE]; uint64_t sendTime; Packet (): len (0), offset (0), sendTime (0) {}; uint8_t * GetBuffer () { return buf + offset; }; size_t GetLength () const { return len - offset; }; uint32_t GetSendStreamID () const { return bufbe32toh (buf); }; uint32_t GetReceiveStreamID () const { return bufbe32toh (buf + 4); }; uint32_t GetSeqn () const { return bufbe32toh (buf + 8); }; uint32_t GetAckThrough () const { return bufbe32toh (buf + 12); }; uint8_t GetNACKCount () const { return buf[16]; }; uint32_t GetNACK (int i) const { return bufbe32toh (buf + 17 + 4 * i); }; const uint8_t * GetOption () const { return buf + 17 + GetNACKCount ()*4 + 3; }; // 3 = resendDelay + flags uint16_t GetFlags () const { return bufbe16toh (GetOption () - 2); }; uint16_t GetOptionSize () const { return bufbe16toh (GetOption ()); }; const uint8_t * GetOptionData () const { return GetOption () + 2; }; const uint8_t * GetPayload () const { return GetOptionData () + GetOptionSize (); }; bool IsSYN () const { return GetFlags () & PACKET_FLAG_SYNCHRONIZE; }; bool IsNoAck () const { return GetFlags () & PACKET_FLAG_NO_ACK; }; }; struct PacketCmp { bool operator() (const Packet * p1, const Packet * p2) const { return p1->GetSeqn () < p2->GetSeqn (); }; }; typedef std::function SendHandler; struct SendBuffer { uint8_t * buf; size_t len, offset; SendHandler handler; SendBuffer (const uint8_t * b, size_t l, SendHandler h): len(l), offset (0), handler(h) { buf = new uint8_t[len]; memcpy (buf, b, len); } ~SendBuffer () { delete[] buf; if (handler) handler(boost::system::error_code ()); } size_t GetRemainingSize () const { return len - offset; }; const uint8_t * GetRemaningBuffer () const { return buf + offset; }; void Cancel () { if (handler) handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted)); handler = nullptr; }; }; class SendBufferQueue { public: SendBufferQueue (): m_Size (0) {}; ~SendBufferQueue () { CleanUp (); }; void Add (const uint8_t * buf, size_t len, SendHandler handler); size_t Get (uint8_t * buf, size_t len); size_t GetSize () const { return m_Size; }; bool IsEmpty () const { return m_Buffers.empty (); }; void CleanUp (); private: std::list > m_Buffers; size_t m_Size; }; enum StreamStatus { eStreamStatusNew = 0, eStreamStatusOpen, eStreamStatusReset, eStreamStatusClosing, eStreamStatusClosed }; class StreamingDestination; class Stream: public std::enable_shared_from_this { public: Stream (boost::asio::io_service& service, StreamingDestination& local, std::shared_ptr remote, int port = 0); // outgoing Stream (boost::asio::io_service& service, StreamingDestination& local); // incoming ~Stream (); uint32_t GetSendStreamID () const { return m_SendStreamID; }; uint32_t GetRecvStreamID () const { return m_RecvStreamID; }; std::shared_ptr GetRemoteLeaseSet () const { return m_RemoteLeaseSet; }; std::shared_ptr GetRemoteIdentity () const { return m_RemoteIdentity; }; bool IsOpen () const { return m_Status == eStreamStatusOpen; }; bool IsEstablished () const { return m_SendStreamID; }; StreamStatus GetStatus () const { return m_Status; }; StreamingDestination& GetLocalDestination () { return m_LocalDestination; }; void HandleNextPacket (Packet * packet); size_t Send (const uint8_t * buf, size_t len); void AsyncSend (const uint8_t * buf, size_t len, SendHandler handler); template void AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout = 0); size_t ReadSome (uint8_t * buf, size_t len) { return ConcatenatePackets (buf, len); }; void Close (); void Cancel () { m_ReceiveTimer.cancel (); }; size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; size_t GetSendQueueSize () const { return m_SentPackets.size (); }; size_t GetReceiveQueueSize () const { return m_ReceiveQueue.size (); }; size_t GetSendBufferSize () const { return m_SendBuffer.GetSize (); }; int GetWindowSize () const { return m_WindowSize; }; int GetRTT () const { return m_RTT; }; /** don't call me */ void Terminate (); private: void CleanUp (); void SendBuffer (); void SendQuickAck (); void SendClose (); bool SendPacket (Packet * packet); void SendPackets (const std::vector& packets); void SendUpdatedLeaseSet (); void SavePacket (Packet * packet); void ProcessPacket (Packet * packet); void ProcessAck (Packet * packet); size_t ConcatenatePackets (uint8_t * buf, size_t len); void UpdateCurrentRemoteLease (bool expired = false); template void HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout); void ScheduleResend (); void HandleResendTimer (const boost::system::error_code& ecode); void HandleAckSendTimer (const boost::system::error_code& ecode); private: boost::asio::io_service& m_Service; uint32_t m_SendStreamID, m_RecvStreamID, m_SequenceNumber; int32_t m_LastReceivedSequenceNumber; StreamStatus m_Status; bool m_IsAckSendScheduled; StreamingDestination& m_LocalDestination; std::shared_ptr m_RemoteIdentity; std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_RoutingSession; std::shared_ptr m_CurrentRemoteLease; std::shared_ptr m_CurrentOutboundTunnel; std::queue m_ReceiveQueue; std::set m_SavedPackets; std::set m_SentPackets; boost::asio::deadline_timer m_ReceiveTimer, m_ResendTimer, m_AckSendTimer; size_t m_NumSentBytes, m_NumReceivedBytes; uint16_t m_Port; std::mutex m_SendBufferMutex; SendBufferQueue m_SendBuffer; int m_WindowSize, m_RTT, m_RTO, m_AckDelay; uint64_t m_LastWindowSizeIncreaseTime; int m_NumResendAttempts; }; class StreamingDestination: public std::enable_shared_from_this { public: typedef std::function)> Acceptor; StreamingDestination (std::shared_ptr owner, uint16_t localPort = 0, bool gzip = true); ~StreamingDestination (); void Start (); void Stop (); std::shared_ptr CreateNewOutgoingStream (std::shared_ptr remote, int port = 0); void DeleteStream (std::shared_ptr stream); void SetAcceptor (const Acceptor& acceptor); void ResetAcceptor (); bool IsAcceptorSet () const { return m_Acceptor != nullptr; }; void AcceptOnce (const Acceptor& acceptor); std::shared_ptr GetOwner () const { return m_Owner; }; void SetOwner (std::shared_ptr owner) { m_Owner = owner; }; uint16_t GetLocalPort () const { return m_LocalPort; }; void HandleDataMessagePayload (const uint8_t * buf, size_t len); std::shared_ptr CreateDataMessage (const uint8_t * payload, size_t len, uint16_t toPort); /** set max connections per minute per destination */ void SetMaxConnsPerMinute(const uint32_t conns); Packet * NewPacket () { return m_PacketsPool.Acquire (); } void DeletePacket (Packet * p) { m_PacketsPool.Release (p); } private: void AcceptOnceAcceptor (std::shared_ptr stream, Acceptor acceptor, Acceptor prev); void HandleNextPacket (Packet * packet); std::shared_ptr CreateNewIncomingStream (); void HandlePendingIncomingTimer (const boost::system::error_code& ecode); /** handle cleaning up connection tracking for ratelimits */ void HandleConnTrack(const boost::system::error_code& ecode); bool DropNewStream(const i2p::data::IdentHash & ident); void ScheduleConnTrack(); private: std::shared_ptr m_Owner; uint16_t m_LocalPort; bool m_Gzip; // gzip compression of data messages std::mutex m_StreamsMutex; std::map > m_Streams; // sendStreamID->stream Acceptor m_Acceptor; uint32_t m_LastIncomingReceiveStreamID; std::list > m_PendingIncomingStreams; boost::asio::deadline_timer m_PendingIncomingTimer; std::map > m_SavedPackets; // receiveStreamID->packets, arrived before SYN std::mutex m_ConnsMutex; /** how many connections per minute did each identity have */ std::map m_Conns; boost::asio::deadline_timer m_ConnTrackTimer; uint32_t m_ConnsPerMinute; /** banned identities */ std::vector m_Banned; uint64_t m_LastBanClear; i2p::util::MemoryPool m_PacketsPool; bool m_EnableDrop; public: i2p::data::GzipInflator m_Inflator; i2p::data::GzipDeflator m_Deflator; // for HTTP only const decltype(m_Streams)& GetStreams () const { return m_Streams; }; }; //------------------------------------------------- template void Stream::AsyncReceive (const Buffer& buffer, ReceiveHandler handler, int timeout) { auto s = shared_from_this(); m_Service.post ([=](void) { if (!m_ReceiveQueue.empty () || m_Status == eStreamStatusReset) s->HandleReceiveTimer (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), buffer, handler, 0); else { int t = (timeout > MAX_RECEIVE_TIMEOUT) ? MAX_RECEIVE_TIMEOUT : timeout; s->m_ReceiveTimer.expires_from_now (boost::posix_time::seconds(t)); s->m_ReceiveTimer.async_wait ([=](const boost::system::error_code& ecode) { s->HandleReceiveTimer (ecode, buffer, handler, timeout - t); }); } }); } template void Stream::HandleReceiveTimer (const boost::system::error_code& ecode, const Buffer& buffer, ReceiveHandler handler, int remainingTimeout) { size_t received = ConcatenatePackets (boost::asio::buffer_cast(buffer), boost::asio::buffer_size(buffer)); if (received > 0) handler (boost::system::error_code (), received); else if (ecode == boost::asio::error::operation_aborted) { // timeout not expired if (m_Status == eStreamStatusReset) handler (boost::asio::error::make_error_code (boost::asio::error::connection_reset), 0); else handler (boost::asio::error::make_error_code (boost::asio::error::operation_aborted), 0); } else { // timeout expired if (remainingTimeout <= 0) handler (boost::asio::error::make_error_code (boost::asio::error::timed_out), received); else { // itermediate iterrupt SendUpdatedLeaseSet (); // send our leaseset if applicable AsyncReceive (buffer, handler, remainingTimeout); } } } } } #endif i2pd-2.17.0/libi2pd/Tag.h000066400000000000000000000037041321131324000146420ustar00rootroot00000000000000#ifndef TAG_H__ #define TAG_H__ /* * Copyright (c) 2013-2017, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ #include #include #include #include "Base.h" namespace i2p { namespace data { template class Tag { BOOST_STATIC_ASSERT_MSG(sz % 8 == 0, "Tag size must be multiple of 8 bytes"); public: Tag () = default; Tag (const uint8_t * buf) { memcpy (m_Buf, buf, sz); } bool operator== (const Tag& other) const { return !memcmp (m_Buf, other.m_Buf, sz); } bool operator!= (const Tag& other) const { return !(*this == other); } bool operator< (const Tag& other) const { return memcmp (m_Buf, other.m_Buf, sz) < 0; } uint8_t * operator()() { return m_Buf; } const uint8_t * operator()() const { return m_Buf; } operator uint8_t * () { return m_Buf; } operator const uint8_t * () const { return m_Buf; } const uint8_t * data() const { return m_Buf; } const uint64_t * GetLL () const { return ll; } bool IsZero () const { for (size_t i = 0; i < sz/8; ++i) if (ll[i]) return false; return true; } void Fill(uint8_t c) { memset(m_Buf, c, sz); } void Randomize() { RAND_bytes(m_Buf, sz); } std::string ToBase64 () const { char str[sz*2]; size_t l = i2p::data::ByteStreamToBase64 (m_Buf, sz, str, sz*2); return std::string (str, str + l); } std::string ToBase32 () const { char str[sz*2]; size_t l = i2p::data::ByteStreamToBase32 (m_Buf, sz, str, sz*2); return std::string (str, str + l); } void FromBase32 (const std::string& s) { i2p::data::Base32ToByteStream (s.c_str (), s.length (), m_Buf, sz); } void FromBase64 (const std::string& s) { i2p::data::Base64ToByteStream (s.c_str (), s.length (), m_Buf, sz); } private: union // 8 bytes aligned { uint8_t m_Buf[sz]; uint64_t ll[sz/8]; }; }; } // data } // i2p #endif /* TAG_H__ */ i2pd-2.17.0/libi2pd/Timestamp.cpp000066400000000000000000000033061321131324000164230ustar00rootroot00000000000000#include #include #include #include "Log.h" #include "I2PEndian.h" #include "Timestamp.h" #ifdef WIN32 #ifndef _WIN64 #define _USE_32BIT_TIME_T #endif #endif namespace i2p { namespace util { static int64_t g_TimeOffset = 0; // in seconds void SyncTimeWithNTP (const std::string& address) { boost::asio::io_service service; boost::asio::ip::udp::resolver::query query (boost::asio::ip::udp::v4 (), address, "ntp"); boost::system::error_code ec; auto it = boost::asio::ip::udp::resolver (service).resolve (query, ec); if (!ec && it != boost::asio::ip::udp::resolver::iterator()) { auto ep = (*it).endpoint (); // take first one boost::asio::ip::udp::socket socket (service); socket.open (boost::asio::ip::udp::v4 (), ec); if (!ec) { uint8_t buf[48];// 48 bytes NTP request/response memset (buf, 0, 48); htobe32buf (buf, (3 << 27) | (3 << 24)); // RFC 4330 size_t len = 0; try { socket.send_to (boost::asio::buffer (buf, 48), ep); int i = 0; while (!socket.available() && i < 10) // 10 seconds max { std::this_thread::sleep_for (std::chrono::seconds(1)); i++; } if (socket.available ()) len = socket.receive_from (boost::asio::buffer (buf, 48), ep); } catch (std::exception& e) { LogPrint (eLogError, "NTP error: ", e.what ()); } if (len >= 8) { auto ourTs = GetSecondsSinceEpoch (); uint32_t ts = bufbe32toh (buf + 32); if (ts > 2208988800U) ts -= 2208988800U; // 1/1/1970 from 1/1/1900 g_TimeOffset = ts - ourTs; LogPrint (eLogInfo, address, " time offset from system time is ", g_TimeOffset, " seconds"); } } } } } } i2pd-2.17.0/libi2pd/Timestamp.h000066400000000000000000000012341321131324000160660ustar00rootroot00000000000000#ifndef TIMESTAMP_H__ #define TIMESTAMP_H__ #include #include namespace i2p { namespace util { inline uint64_t GetMillisecondsSinceEpoch () { return std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count (); } inline uint32_t GetHoursSinceEpoch () { return std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count (); } inline uint64_t GetSecondsSinceEpoch () { return std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count (); } } } #endif i2pd-2.17.0/libi2pd/TransitTunnel.cpp000066400000000000000000000070651321131324000173000ustar00rootroot00000000000000#include #include "I2PEndian.h" #include "Log.h" #include "RouterContext.h" #include "I2NPProtocol.h" #include "Tunnel.h" #include "Transports.h" #include "TransitTunnel.h" namespace i2p { namespace tunnel { TransitTunnel::TransitTunnel (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey): TunnelBase (receiveTunnelID, nextTunnelID, nextIdent) { m_Encryption.SetKeys (layerKey, ivKey); } void TransitTunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { m_Encryption.Encrypt (in->GetPayload () + 4, out->GetPayload () + 4); i2p::transport::transports.UpdateTotalTransitTransmittedBytes (TUNNEL_DATA_MSG_SIZE); } TransitTunnelParticipant::~TransitTunnelParticipant () { } void TransitTunnelParticipant::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { auto newMsg = CreateEmptyTunnelDataMsg (); EncryptTunnelMsg (tunnelMsg, newMsg); m_NumTransmittedBytes += tunnelMsg->GetLength (); htobe32buf (newMsg->GetPayload (), GetNextTunnelID ()); newMsg->FillI2NPMessageHeader (eI2NPTunnelData); m_TunnelDataMsgs.push_back (newMsg); } void TransitTunnelParticipant::FlushTunnelDataMsgs () { if (!m_TunnelDataMsgs.empty ()) { auto num = m_TunnelDataMsgs.size (); if (num > 1) LogPrint (eLogDebug, "TransitTunnel: ", GetTunnelID (), "->", GetNextTunnelID (), " ", num); i2p::transport::transports.SendMessages (GetNextIdentHash (), m_TunnelDataMsgs); m_TunnelDataMsgs.clear (); } } void TransitTunnel::SendTunnelDataMsg (std::shared_ptr msg) { LogPrint (eLogError, "TransitTunnel: We are not a gateway for ", GetTunnelID ()); } void TransitTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { LogPrint (eLogError, "TransitTunnel: Incoming tunnel message is not supported ", GetTunnelID ()); } void TransitTunnelGateway::SendTunnelDataMsg (std::shared_ptr msg) { TunnelMessageBlock block; block.deliveryType = eDeliveryTypeLocal; block.data = msg; std::unique_lock l(m_SendMutex); m_Gateway.PutTunnelDataMsg (block); } void TransitTunnelGateway::FlushTunnelDataMsgs () { std::unique_lock l(m_SendMutex); m_Gateway.SendBuffer (); } void TransitTunnelEndpoint::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { auto newMsg = CreateEmptyTunnelDataMsg (); EncryptTunnelMsg (tunnelMsg, newMsg); LogPrint (eLogDebug, "TransitTunnel: handle msg for endpoint ", GetTunnelID ()); m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey, bool isGateway, bool isEndpoint) { if (isEndpoint) { LogPrint (eLogDebug, "TransitTunnel: endpoint ", receiveTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else if (isGateway) { LogPrint (eLogInfo, "TransitTunnel: gateway ", receiveTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } else { LogPrint (eLogDebug, "TransitTunnel: ", receiveTunnelID, "->", nextTunnelID, " created"); return std::make_shared (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey); } } } } i2pd-2.17.0/libi2pd/TransitTunnel.h000066400000000000000000000060311321131324000167350ustar00rootroot00000000000000#ifndef TRANSIT_TUNNEL_H__ #define TRANSIT_TUNNEL_H__ #include #include #include #include #include "Crypto.h" #include "I2NPProtocol.h" #include "TunnelEndpoint.h" #include "TunnelGateway.h" #include "TunnelBase.h" namespace i2p { namespace tunnel { class TransitTunnel: public TunnelBase { public: TransitTunnel (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey); virtual size_t GetNumTransmittedBytes () const { return 0; }; // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); private: i2p::crypto::TunnelEncryption m_Encryption; }; class TransitTunnelParticipant: public TransitTunnel { public: TransitTunnelParticipant (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_NumTransmittedBytes (0) {}; ~TransitTunnelParticipant (); size_t GetNumTransmittedBytes () const { return m_NumTransmittedBytes; }; void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); void FlushTunnelDataMsgs (); private: size_t m_NumTransmittedBytes; std::vector > m_TunnelDataMsgs; }; class TransitTunnelGateway: public TransitTunnel { public: TransitTunnelGateway (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_Gateway(this) {}; void SendTunnelDataMsg (std::shared_ptr msg); void FlushTunnelDataMsgs (); size_t GetNumTransmittedBytes () const { return m_Gateway.GetNumSentBytes (); }; private: std::mutex m_SendMutex; TunnelGateway m_Gateway; }; class TransitTunnelEndpoint: public TransitTunnel { public: TransitTunnelEndpoint (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey): TransitTunnel (receiveTunnelID, nextIdent, nextTunnelID, layerKey, ivKey), m_Endpoint (false) {}; // transit endpoint is always outbound void Cleanup () { m_Endpoint.Cleanup (); } void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); size_t GetNumTransmittedBytes () const { return m_Endpoint.GetNumReceivedBytes (); } private: TunnelEndpoint m_Endpoint; }; std::shared_ptr CreateTransitTunnel (uint32_t receiveTunnelID, const uint8_t * nextIdent, uint32_t nextTunnelID, const uint8_t * layerKey,const uint8_t * ivKey, bool isGateway, bool isEndpoint); } } #endif i2pd-2.17.0/libi2pd/TransportSession.h000066400000000000000000000054431321131324000174710ustar00rootroot00000000000000#ifndef TRANSPORT_SESSION_H__ #define TRANSPORT_SESSION_H__ #include #include #include #include #include "Identity.h" #include "Crypto.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "Timestamp.h" namespace i2p { namespace transport { class SignedData { public: SignedData () {} SignedData (const SignedData& other) { m_Stream << other.m_Stream.rdbuf (); } void Insert (const uint8_t * buf, size_t len) { m_Stream.write ((char *)buf, len); } template void Insert (T t) { m_Stream.write ((char *)&t, sizeof (T)); } bool Verify (std::shared_ptr ident, const uint8_t * signature) const { return ident->Verify ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); } void Sign (const i2p::data::PrivateKeys& keys, uint8_t * signature) const { keys.Sign ((const uint8_t *)m_Stream.str ().c_str (), m_Stream.str ().size (), signature); } private: std::stringstream m_Stream; }; class TransportSession { public: TransportSession (std::shared_ptr router, int terminationTimeout): m_DHKeysPair (nullptr), m_NumSentBytes (0), m_NumReceivedBytes (0), m_IsOutgoing (router), m_TerminationTimeout (terminationTimeout), m_LastActivityTimestamp (i2p::util::GetSecondsSinceEpoch ()) { if (router) m_RemoteIdentity = router->GetRouterIdentity (); } virtual ~TransportSession () {}; virtual void Done () = 0; std::string GetIdentHashBase64() const { return m_RemoteIdentity ? m_RemoteIdentity->GetIdentHash().ToBase64() : ""; } std::shared_ptr GetRemoteIdentity () { return m_RemoteIdentity; }; void SetRemoteIdentity (std::shared_ptr ident) { m_RemoteIdentity = ident; }; size_t GetNumSentBytes () const { return m_NumSentBytes; }; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; bool IsOutgoing () const { return m_IsOutgoing; }; int GetTerminationTimeout () const { return m_TerminationTimeout; }; void SetTerminationTimeout (int terminationTimeout) { m_TerminationTimeout = terminationTimeout; }; bool IsTerminationTimeoutExpired (uint64_t ts) const { return ts >= m_LastActivityTimestamp + GetTerminationTimeout (); }; virtual void SendI2NPMessages (const std::vector >& msgs) = 0; protected: std::shared_ptr m_RemoteIdentity; std::shared_ptr m_DHKeysPair; // X - for client and Y - for server size_t m_NumSentBytes, m_NumReceivedBytes; bool m_IsOutgoing; int m_TerminationTimeout; uint64_t m_LastActivityTimestamp; }; } } #endif i2pd-2.17.0/libi2pd/Transports.cpp000066400000000000000000000646511321131324000166510ustar00rootroot00000000000000#include "Log.h" #include "Crypto.h" #include "RouterContext.h" #include "I2NPProtocol.h" #include "NetDb.hpp" #include "Transports.h" #include "Config.h" #include "HTTP.h" #ifdef WITH_EVENTS #include "Event.h" #include "util.h" #endif using namespace i2p::data; namespace i2p { namespace transport { DHKeysPairSupplier::DHKeysPairSupplier (int size): m_QueueSize (size), m_IsRunning (false), m_Thread (nullptr) { } DHKeysPairSupplier::~DHKeysPairSupplier () { Stop (); } void DHKeysPairSupplier::Start () { m_IsRunning = true; m_Thread = new std::thread (std::bind (&DHKeysPairSupplier::Run, this)); } void DHKeysPairSupplier::Stop () { m_IsRunning = false; m_Acquired.notify_one (); if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = 0; } } void DHKeysPairSupplier::Run () { while (m_IsRunning) { int num, total = 0; while ((num = m_QueueSize - (int)m_Queue.size ()) > 0 && total < 20) { CreateDHKeysPairs (num); total += num; } if (total >= 20) { LogPrint (eLogWarning, "Transports: ", total, " DH keys generated at the time"); std::this_thread::sleep_for (std::chrono::seconds(1)); // take a break } else { std::unique_lock l(m_AcquiredMutex); m_Acquired.wait (l); // wait for element gets aquired } } } void DHKeysPairSupplier::CreateDHKeysPairs (int num) { if (num > 0) { for (int i = 0; i < num; i++) { auto pair = std::make_shared (); pair->GenerateKeys (); std::unique_lock l(m_AcquiredMutex); m_Queue.push (pair); } } } std::shared_ptr DHKeysPairSupplier::Acquire () { { std::unique_lock l(m_AcquiredMutex); if (!m_Queue.empty ()) { auto pair = m_Queue.front (); m_Queue.pop (); m_Acquired.notify_one (); return pair; } } // queue is empty, create new auto pair = std::make_shared (); pair->GenerateKeys (); return pair; } void DHKeysPairSupplier::Return (std::shared_ptr pair) { if (pair) { std::unique_lockl(m_AcquiredMutex); if ((int)m_Queue.size () < 2*m_QueueSize) m_Queue.push (pair); } else LogPrint(eLogError, "Transports: return null DHKeys"); } Transports transports; Transports::Transports (): m_IsOnline (true), m_IsRunning (false), m_IsNAT (true), m_Thread (nullptr), m_Service (nullptr), m_Work (nullptr), m_PeerCleanupTimer (nullptr), m_PeerTestTimer (nullptr), m_NTCPServer (nullptr), m_SSUServer (nullptr), m_DHKeysPairSupplier (5), // 5 pre-generated keys m_TotalSentBytes(0), m_TotalReceivedBytes(0), m_TotalTransitTransmittedBytes (0), m_InBandwidth (0), m_OutBandwidth (0), m_TransitBandwidth(0), m_LastInBandwidthUpdateBytes (0), m_LastOutBandwidthUpdateBytes (0), m_LastTransitBandwidthUpdateBytes (0), m_LastBandwidthUpdateTime (0) { } Transports::~Transports () { Stop (); if (m_Service) { delete m_PeerCleanupTimer; m_PeerCleanupTimer = nullptr; delete m_PeerTestTimer; m_PeerTestTimer = nullptr; delete m_Work; m_Work = nullptr; delete m_Service; m_Service = nullptr; } } void Transports::Start (bool enableNTCP, bool enableSSU) { if (!m_Service) { m_Service = new boost::asio::io_service (); m_Work = new boost::asio::io_service::work (*m_Service); m_PeerCleanupTimer = new boost::asio::deadline_timer (*m_Service); m_PeerTestTimer = new boost::asio::deadline_timer (*m_Service); } i2p::config::GetOption("nat", m_IsNAT); m_DHKeysPairSupplier.Start (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&Transports::Run, this)); std::string ntcpproxy; i2p::config::GetOption("ntcpproxy", ntcpproxy); i2p::http::URL proxyurl; uint16_t softLimit, hardLimit; i2p::config::GetOption("limits.ntcpsoft", softLimit); i2p::config::GetOption("limits.ntcphard", hardLimit); if(softLimit > 0 && hardLimit > 0 && softLimit >= hardLimit) { LogPrint(eLogError, "ntcp soft limit must be less than ntcp hard limit"); return; } if(ntcpproxy.size() && enableNTCP) { if(proxyurl.parse(ntcpproxy)) { if(proxyurl.schema == "socks" || proxyurl.schema == "http") { m_NTCPServer = new NTCPServer(); m_NTCPServer->SetSessionLimits(softLimit, hardLimit); NTCPServer::ProxyType proxytype = NTCPServer::eSocksProxy; if (proxyurl.schema == "http") proxytype = NTCPServer::eHTTPProxy; m_NTCPServer->UseProxy(proxytype, proxyurl.host, proxyurl.port) ; m_NTCPServer->Start(); if(!m_NTCPServer->NetworkIsReady()) { LogPrint(eLogError, "Transports: NTCP failed to start with proxy"); m_NTCPServer->Stop(); delete m_NTCPServer; m_NTCPServer = nullptr; } } else LogPrint(eLogError, "Transports: unsupported NTCP proxy URL ", ntcpproxy); } else LogPrint(eLogError, "Transports: invalid NTCP proxy url ", ntcpproxy); return; } // create acceptors auto& addresses = context.GetRouterInfo ().GetAddresses (); for (const auto& address : addresses) { if (!address) continue; if (m_NTCPServer == nullptr && enableNTCP) { m_NTCPServer = new NTCPServer (); m_NTCPServer->SetSessionLimits(softLimit, hardLimit); m_NTCPServer->Start (); if (!(m_NTCPServer->IsBoundV6() || m_NTCPServer->IsBoundV4())) { /** failed to bind to NTCP */ LogPrint(eLogError, "Transports: failed to bind to TCP"); m_NTCPServer->Stop(); delete m_NTCPServer; m_NTCPServer = nullptr; } } if (address->transportStyle == RouterInfo::eTransportSSU) { if (m_SSUServer == nullptr && enableSSU) { if (address->host.is_v4()) m_SSUServer = new SSUServer (address->port); else m_SSUServer = new SSUServer (address->host, address->port); LogPrint (eLogInfo, "Transports: Start listening UDP port ", address->port); try { m_SSUServer->Start (); } catch ( std::exception & ex ) { LogPrint(eLogError, "Transports: Failed to bind to UDP port", address->port); delete m_SSUServer; m_SSUServer = nullptr; continue; } DetectExternalIP (); } else LogPrint (eLogError, "Transports: SSU server already exists"); } } m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); if (m_IsNAT) { m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } } void Transports::Stop () { if (m_PeerCleanupTimer) m_PeerCleanupTimer->cancel (); if (m_PeerTestTimer) m_PeerTestTimer->cancel (); m_Peers.clear (); if (m_SSUServer) { m_SSUServer->Stop (); delete m_SSUServer; m_SSUServer = nullptr; } if (m_NTCPServer) { m_NTCPServer->Stop (); delete m_NTCPServer; m_NTCPServer = nullptr; } m_DHKeysPairSupplier.Stop (); m_IsRunning = false; if (m_Service) m_Service->stop (); if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = nullptr; } } void Transports::Run () { while (m_IsRunning && m_Service) { try { m_Service->run (); } catch (std::exception& ex) { LogPrint (eLogError, "Transports: runtime exception: ", ex.what ()); } } } void Transports::UpdateBandwidth () { uint64_t ts = i2p::util::GetMillisecondsSinceEpoch (); if (m_LastBandwidthUpdateTime > 0) { auto delta = ts - m_LastBandwidthUpdateTime; if (delta > 0) { m_InBandwidth = (m_TotalReceivedBytes - m_LastInBandwidthUpdateBytes)*1000/delta; // per second m_OutBandwidth = (m_TotalSentBytes - m_LastOutBandwidthUpdateBytes)*1000/delta; // per second m_TransitBandwidth = (m_TotalTransitTransmittedBytes - m_LastTransitBandwidthUpdateBytes)*1000/delta; } } m_LastBandwidthUpdateTime = ts; m_LastInBandwidthUpdateBytes = m_TotalReceivedBytes; m_LastOutBandwidthUpdateBytes = m_TotalSentBytes; m_LastTransitBandwidthUpdateBytes = m_TotalTransitTransmittedBytes; } bool Transports::IsBandwidthExceeded () const { auto limit = i2p::context.GetBandwidthLimit() * 1024; // convert to bytes auto bw = std::max (m_InBandwidth, m_OutBandwidth); return bw > limit; } bool Transports::IsTransitBandwidthExceeded () const { auto limit = i2p::context.GetTransitBandwidthLimit() * 1024; // convert to bytes return m_TransitBandwidth > limit; } void Transports::SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg) { SendMessages (ident, std::vector > {msg }); } void Transports::SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs) { #ifdef WITH_EVENTS QueueIntEvent("transport.send", ident.ToBase64(), msgs.size()); #endif m_Service->post (std::bind (&Transports::PostMessages, this, ident, msgs)); } void Transports::PostMessages (i2p::data::IdentHash ident, std::vector > msgs) { if (ident == i2p::context.GetRouterInfo ().GetIdentHash ()) { // we send it to ourself for (auto& it: msgs) m_LoopbackHandler.PutNextMessage (it); m_LoopbackHandler.Flush (); return; } if(RoutesRestricted() && ! IsRestrictedPeer(ident)) return; auto it = m_Peers.find (ident); if (it == m_Peers.end ()) { bool connected = false; try { auto r = netdb.FindRouter (ident); { std::unique_lock l(m_PeersMutex); it = m_Peers.insert (std::pair(ident, { 0, r, {}, i2p::util::GetSecondsSinceEpoch (), {} })).first; } connected = ConnectToPeer (ident, it->second); } catch (std::exception& ex) { LogPrint (eLogError, "Transports: PostMessages exception:", ex.what ()); } if (!connected) return; } if (!it->second.sessions.empty ()) it->second.sessions.front ()->SendI2NPMessages (msgs); else { if (it->second.delayedMessages.size () < MAX_NUM_DELAYED_MESSAGES) { for (auto& it1: msgs) it->second.delayedMessages.push_back (it1); } else { LogPrint (eLogWarning, "Transports: delayed messages queue size exceeds ", MAX_NUM_DELAYED_MESSAGES); std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } } bool Transports::ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer) { if (peer.router) // we have RI already { if (!peer.numAttempts) // NTCP { peer.numAttempts++; auto address = peer.router->GetNTCPAddress (!context.SupportsV6 ()); if (address && m_NTCPServer) { #if BOOST_VERSION >= 104900 if (!address->host.is_unspecified ()) // we have address now #else boost::system::error_code ecode; address->host.to_string (ecode); if (!ecode) #endif { if (!peer.router->UsesIntroducer () && !peer.router->IsUnreachable ()) { if(!m_NTCPServer->ShouldLimit()) { auto s = std::make_shared (*m_NTCPServer, peer.router); if(m_NTCPServer->UsingProxy()) { NTCPServer::RemoteAddressType remote = NTCPServer::eIP4Address; std::string addr = address->host.to_string(); if(address->host.is_v6()) remote = NTCPServer::eIP6Address; m_NTCPServer->ConnectWithProxy(addr, address->port, remote, s); } else m_NTCPServer->Connect (address->host, address->port, s); return true; } else { LogPrint(eLogWarning, "Transports: NTCP Limit hit falling back to SSU"); } } } else // we don't have address { if (address->addressString.length () > 0) // trying to resolve { if(m_NTCPServer->UsingProxy()) { auto s = std::make_shared (*m_NTCPServer, peer.router); m_NTCPServer->ConnectWithProxy(address->addressString, address->port, NTCPServer::eHostname, s); } else { LogPrint (eLogDebug, "Transports: Resolving NTCP ", address->addressString); NTCPResolve (address->addressString, ident); } return true; } } } else LogPrint (eLogDebug, "Transports: NTCP address is not present for ", i2p::data::GetIdentHashAbbreviation (ident), ", trying SSU"); } if (peer.numAttempts == 1)// SSU { peer.numAttempts++; if (m_SSUServer && peer.router->IsSSU (!context.SupportsV6 ())) { auto address = peer.router->GetSSUAddress (!context.SupportsV6 ()); #if BOOST_VERSION >= 104900 if (!address->host.is_unspecified ()) // we have address now #else boost::system::error_code ecode; address->host.to_string (ecode); if (!ecode) #endif { m_SSUServer->CreateSession (peer.router, address->host, address->port); return true; } else // we don't have address { if (address->addressString.length () > 0) // trying to resolve { LogPrint (eLogDebug, "Transports: Resolving SSU ", address->addressString); SSUResolve (address->addressString, ident); return true; } } } } LogPrint (eLogInfo, "Transports: No NTCP or SSU addresses available"); peer.Done (); std::unique_lock l(m_PeersMutex); m_Peers.erase (ident); return false; } else // otherwise request RI { LogPrint (eLogInfo, "Transports: RouterInfo for ", ident.ToBase64 (), " not found, requested"); i2p::data::netdb.RequestDestination (ident, std::bind ( &Transports::RequestComplete, this, std::placeholders::_1, ident)); } return true; } void Transports::RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident) { m_Service->post (std::bind (&Transports::HandleRequestComplete, this, r, ident)); } void Transports::HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident) { auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { if (r) { LogPrint (eLogDebug, "Transports: RouterInfo for ", ident.ToBase64 (), " found, Trying to connect"); it->second.router = r; ConnectToPeer (ident, it->second); } else { LogPrint (eLogWarning, "Transports: RouterInfo not found, Failed to send messages"); std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } } void Transports::NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident) { auto resolver = std::make_shared(*m_Service); resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""), std::bind (&Transports::HandleNTCPResolve, this, std::placeholders::_1, std::placeholders::_2, ident, resolver)); } void Transports::HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, i2p::data::IdentHash ident, std::shared_ptr resolver) { auto it1 = m_Peers.find (ident); if (it1 != m_Peers.end ()) { auto& peer = it1->second; if (!ecode && peer.router) { while (it != boost::asio::ip::tcp::resolver::iterator()) { auto address = (*it).endpoint ().address (); LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); if (address.is_v4 () || context.SupportsV6 ()) { auto addr = peer.router->GetNTCPAddress (); // TODO: take one we requested if (addr) { auto s = std::make_shared (*m_NTCPServer, peer.router); m_NTCPServer->Connect (address, addr->port, s); return; } break; } else LogPrint (eLogInfo, "Transports: NTCP ", address, " is not supported"); it++; } } LogPrint (eLogError, "Transports: Unable to resolve NTCP address: ", ecode.message ()); std::unique_lock l(m_PeersMutex); m_Peers.erase (it1); } } void Transports::SSUResolve (const std::string& addr, const i2p::data::IdentHash& ident) { auto resolver = std::make_shared(*m_Service); resolver->async_resolve (boost::asio::ip::tcp::resolver::query (addr, ""), std::bind (&Transports::HandleSSUResolve, this, std::placeholders::_1, std::placeholders::_2, ident, resolver)); } void Transports::HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, i2p::data::IdentHash ident, std::shared_ptr resolver) { auto it1 = m_Peers.find (ident); if (it1 != m_Peers.end ()) { auto& peer = it1->second; if (!ecode && peer.router) { while (it != boost::asio::ip::tcp::resolver::iterator()) { auto address = (*it).endpoint ().address (); LogPrint (eLogDebug, "Transports: ", (*it).host_name (), " has been resolved to ", address); if (address.is_v4 () || context.SupportsV6 ()) { auto addr = peer.router->GetSSUAddress (); // TODO: take one we requested if (addr) { m_SSUServer->CreateSession (peer.router, address, addr->port); return; } break; } else LogPrint (eLogInfo, "Transports: SSU ", address, " is not supported"); it++; } } LogPrint (eLogError, "Transports: Unable to resolve SSU address: ", ecode.message ()); std::unique_lock l(m_PeersMutex); m_Peers.erase (it1); } } void Transports::CloseSession (std::shared_ptr router) { if (!router) return; m_Service->post (std::bind (&Transports::PostCloseSession, this, router)); } void Transports::PostCloseSession (std::shared_ptr router) { auto ssuSession = m_SSUServer ? m_SSUServer->FindSession (router) : nullptr; if (ssuSession) // try SSU first { m_SSUServer->DeleteSession (ssuSession); LogPrint (eLogDebug, "Transports: SSU session closed"); } auto ntcpSession = m_NTCPServer ? m_NTCPServer->FindNTCPSession(router->GetIdentHash()) : nullptr; if (ntcpSession) // try deleting ntcp session too { ntcpSession->Terminate (); LogPrint(eLogDebug, "Transports: NTCP session closed"); } } void Transports::DetectExternalIP () { if (RoutesRestricted()) { LogPrint(eLogInfo, "Transports: restricted routes enabled, not detecting ip"); i2p::context.SetStatus (eRouterStatusOK); return; } if (m_SSUServer) { bool isv4 = i2p::context.SupportsV4 (); if (m_IsNAT && isv4) i2p::context.SetStatus (eRouterStatusTesting); for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomPeerTestRouter (isv4); // v4 only if v4 if (router) m_SSUServer->CreateSession (router, true, isv4); // peer test else { // if not peer test capable routers found pick any router = i2p::data::netdb.GetRandomRouter (); if (router && router->IsSSU ()) m_SSUServer->CreateSession (router); // no peer test } } } else LogPrint (eLogError, "Transports: Can't detect external IP. SSU is not available"); } void Transports::PeerTest () { if (RoutesRestricted() || !i2p::context.SupportsV4 ()) return; if (m_SSUServer) { bool statusChanged = false; for (int i = 0; i < 5; i++) { auto router = i2p::data::netdb.GetRandomPeerTestRouter (true); // v4 only if (router) { if (!statusChanged) { statusChanged = true; i2p::context.SetStatus (eRouterStatusTesting); // first time only } m_SSUServer->CreateSession (router, true, true); // peer test v4 } } if (!statusChanged) LogPrint (eLogWarning, "Can't find routers for peer test"); } } std::shared_ptr Transports::GetNextDHKeysPair () { return m_DHKeysPairSupplier.Acquire (); } void Transports::ReuseDHKeysPair (std::shared_ptr pair) { m_DHKeysPairSupplier.Return (pair); } void Transports::PeerConnected (std::shared_ptr session) { m_Service->post([session, this]() { auto remoteIdentity = session->GetRemoteIdentity (); if (!remoteIdentity) return; auto ident = remoteIdentity->GetIdentHash (); auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { #ifdef WITH_EVENTS EmitEvent({{"type" , "transport.connected"}, {"ident", ident.ToBase64()}, {"inbound", "false"}}); #endif bool sendDatabaseStore = true; if (it->second.delayedMessages.size () > 0) { // check if first message is our DatabaseStore (publishing) auto firstMsg = it->second.delayedMessages[0]; if (firstMsg && firstMsg->GetTypeID () == eI2NPDatabaseStore && i2p::data::IdentHash(firstMsg->GetPayload () + DATABASE_STORE_KEY_OFFSET) == i2p::context.GetIdentHash ()) sendDatabaseStore = false; // we have it in the list already } if (sendDatabaseStore) session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); else session->SetTerminationTimeout (10); // most likely it's publishing, no follow-up messages expected, set timeout to 10 seconds it->second.sessions.push_back (session); session->SendI2NPMessages (it->second.delayedMessages); it->second.delayedMessages.clear (); } else // incoming connection { if(RoutesRestricted() && ! IsRestrictedPeer(ident)) { // not trusted LogPrint(eLogWarning, "Transports: closing untrusted inbound connection from ", ident.ToBase64()); session->Done(); return; } #ifdef WITH_EVENTS EmitEvent({{"type" , "transport.connected"}, {"ident", ident.ToBase64()}, {"inbound", "true"}}); #endif session->SendI2NPMessages ({ CreateDatabaseStoreMsg () }); // send DatabaseStore std::unique_lock l(m_PeersMutex); m_Peers.insert (std::make_pair (ident, Peer{ 0, nullptr, { session }, i2p::util::GetSecondsSinceEpoch (), {} })); } }); } void Transports::PeerDisconnected (std::shared_ptr session) { m_Service->post([session, this]() { auto remoteIdentity = session->GetRemoteIdentity (); if (!remoteIdentity) return; auto ident = remoteIdentity->GetIdentHash (); #ifdef WITH_EVENTS EmitEvent({{"type" , "transport.disconnected"}, {"ident", ident.ToBase64()}}); #endif auto it = m_Peers.find (ident); if (it != m_Peers.end ()) { it->second.sessions.remove (session); if (it->second.sessions.empty ()) // TODO: why? { if (it->second.delayedMessages.size () > 0) ConnectToPeer (ident, it->second); else { std::unique_lock l(m_PeersMutex); m_Peers.erase (it); } } } }); } bool Transports::IsConnected (const i2p::data::IdentHash& ident) const { std::unique_lock l(m_PeersMutex); auto it = m_Peers.find (ident); return it != m_Peers.end (); } void Transports::HandlePeerCleanupTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_Peers.begin (); it != m_Peers.end (); ) { if (it->second.sessions.empty () && ts > it->second.creationTime + SESSION_CREATION_TIMEOUT) { LogPrint (eLogWarning, "Transports: Session to peer ", it->first.ToBase64 (), " has not been created in ", SESSION_CREATION_TIMEOUT, " seconds"); auto profile = i2p::data::GetRouterProfile(it->first); if (profile) { profile->TunnelNonReplied(); profile->Save(it->first); } std::unique_lock l(m_PeersMutex); it = m_Peers.erase (it); } else ++it; } UpdateBandwidth (); // TODO: use separate timer(s) for it if (i2p::context.GetStatus () == eRouterStatusTesting) // if still testing, repeat peer test DetectExternalIP (); m_PeerCleanupTimer->expires_from_now (boost::posix_time::seconds(5*SESSION_CREATION_TIMEOUT)); m_PeerCleanupTimer->async_wait (std::bind (&Transports::HandlePeerCleanupTimer, this, std::placeholders::_1)); } } void Transports::HandlePeerTestTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { PeerTest (); m_PeerTestTimer->expires_from_now (boost::posix_time::minutes(PEER_TEST_INTERVAL)); m_PeerTestTimer->async_wait (std::bind (&Transports::HandlePeerTestTimer, this, std::placeholders::_1)); } } std::shared_ptr Transports::GetRandomPeer () const { if (m_Peers.empty ()) return nullptr; std::unique_lock l(m_PeersMutex); auto it = m_Peers.begin (); std::advance (it, rand () % m_Peers.size ()); return it != m_Peers.end () ? it->second.router : nullptr; } void Transports::RestrictRoutesToFamilies(std::set families) { std::lock_guard lock(m_FamilyMutex); m_TrustedFamilies.clear(); for ( const auto& fam : families ) m_TrustedFamilies.push_back(fam); } void Transports::RestrictRoutesToRouters(std::set routers) { std::unique_lock lock(m_TrustedRoutersMutex); m_TrustedRouters.clear(); for (const auto & ri : routers ) m_TrustedRouters.push_back(ri); } bool Transports::RoutesRestricted() const { std::unique_lock famlock(m_FamilyMutex); std::unique_lock routerslock(m_TrustedRoutersMutex); return m_TrustedFamilies.size() > 0 || m_TrustedRouters.size() > 0; } /** XXX: if routes are not restricted this dies */ std::shared_ptr Transports::GetRestrictedPeer() const { { std::lock_guard l(m_FamilyMutex); std::string fam; auto sz = m_TrustedFamilies.size(); if(sz > 1) { auto it = m_TrustedFamilies.begin (); std::advance(it, rand() % sz); fam = *it; boost::to_lower(fam); } else if (sz == 1) { fam = m_TrustedFamilies[0]; } if (fam.size()) return i2p::data::netdb.GetRandomRouterInFamily(fam); } { std::unique_lock l(m_TrustedRoutersMutex); auto sz = m_TrustedRouters.size(); if (sz) { if(sz == 1) return i2p::data::netdb.FindRouter(m_TrustedRouters[0]); auto it = m_TrustedRouters.begin(); std::advance(it, rand() % sz); return i2p::data::netdb.FindRouter(*it); } } return nullptr; } bool Transports::IsRestrictedPeer(const i2p::data::IdentHash & ih) const { { std::unique_lock l(m_TrustedRoutersMutex); for (const auto & r : m_TrustedRouters ) if ( r == ih ) return true; } { std::unique_lock l(m_FamilyMutex); auto ri = i2p::data::netdb.FindRouter(ih); for (const auto & fam : m_TrustedFamilies) if(ri->IsFamily(fam)) return true; } return false; } } } i2pd-2.17.0/libi2pd/Transports.h000066400000000000000000000150501321131324000163030ustar00rootroot00000000000000#ifndef TRANSPORTS_H__ #define TRANSPORTS_H__ #include #include #include #include #include #include #include #include #include #include #include #include "TransportSession.h" #include "NTCPSession.h" #include "SSU.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "Identity.h" namespace i2p { namespace transport { class DHKeysPairSupplier { public: DHKeysPairSupplier (int size); ~DHKeysPairSupplier (); void Start (); void Stop (); std::shared_ptr Acquire (); void Return (std::shared_ptr pair); private: void Run (); void CreateDHKeysPairs (int num); private: const int m_QueueSize; std::queue > m_Queue; bool m_IsRunning; std::thread * m_Thread; std::condition_variable m_Acquired; std::mutex m_AcquiredMutex; }; struct Peer { int numAttempts; std::shared_ptr router; std::list > sessions; uint64_t creationTime; std::vector > delayedMessages; void Done () { for (auto& it: sessions) it->Done (); } }; const size_t SESSION_CREATION_TIMEOUT = 10; // in seconds const int PEER_TEST_INTERVAL = 71; // in minutes const int MAX_NUM_DELAYED_MESSAGES = 50; class Transports { public: Transports (); ~Transports (); void Start (bool enableNTCP=true, bool enableSSU=true); void Stop (); bool IsBoundNTCP() const { return m_NTCPServer != nullptr; } bool IsBoundSSU() const { return m_SSUServer != nullptr; } bool IsOnline() const { return m_IsOnline; }; void SetOnline (bool online) { m_IsOnline = online; }; boost::asio::io_service& GetService () { return *m_Service; }; std::shared_ptr GetNextDHKeysPair (); void ReuseDHKeysPair (std::shared_ptr pair); void SendMessage (const i2p::data::IdentHash& ident, std::shared_ptr msg); void SendMessages (const i2p::data::IdentHash& ident, const std::vector >& msgs); void CloseSession (std::shared_ptr router); void PeerConnected (std::shared_ptr session); void PeerDisconnected (std::shared_ptr session); bool IsConnected (const i2p::data::IdentHash& ident) const; void UpdateSentBytes (uint64_t numBytes) { m_TotalSentBytes += numBytes; }; void UpdateReceivedBytes (uint64_t numBytes) { m_TotalReceivedBytes += numBytes; }; uint64_t GetTotalSentBytes () const { return m_TotalSentBytes; }; uint64_t GetTotalReceivedBytes () const { return m_TotalReceivedBytes; }; uint64_t GetTotalTransitTransmittedBytes () const { return m_TotalTransitTransmittedBytes; } void UpdateTotalTransitTransmittedBytes (uint32_t add) { m_TotalTransitTransmittedBytes += add; }; uint32_t GetInBandwidth () const { return m_InBandwidth; }; uint32_t GetOutBandwidth () const { return m_OutBandwidth; }; uint32_t GetTransitBandwidth () const { return m_TransitBandwidth; }; bool IsBandwidthExceeded () const; bool IsTransitBandwidthExceeded () const; size_t GetNumPeers () const { return m_Peers.size (); }; std::shared_ptr GetRandomPeer () const; /** get a trusted first hop for restricted routes */ std::shared_ptr GetRestrictedPeer() const; /** do we want to use restricted routes? */ bool RoutesRestricted() const; /** restrict routes to use only these router families for first hops */ void RestrictRoutesToFamilies(std::set families); /** restrict routes to use only these routers for first hops */ void RestrictRoutesToRouters(std::set routers); bool IsRestrictedPeer(const i2p::data::IdentHash & ident) const; void PeerTest (); private: void Run (); void RequestComplete (std::shared_ptr r, const i2p::data::IdentHash& ident); void HandleRequestComplete (std::shared_ptr r, i2p::data::IdentHash ident); void PostMessages (i2p::data::IdentHash ident, std::vector > msgs); void PostCloseSession (std::shared_ptr router); bool ConnectToPeer (const i2p::data::IdentHash& ident, Peer& peer); void HandlePeerCleanupTimer (const boost::system::error_code& ecode); void HandlePeerTestTimer (const boost::system::error_code& ecode); void NTCPResolve (const std::string& addr, const i2p::data::IdentHash& ident); void HandleNTCPResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, i2p::data::IdentHash ident, std::shared_ptr resolver); void SSUResolve (const std::string& addr, const i2p::data::IdentHash& ident); void HandleSSUResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, i2p::data::IdentHash ident, std::shared_ptr resolver); void UpdateBandwidth (); void DetectExternalIP (); private: bool m_IsOnline, m_IsRunning, m_IsNAT; std::thread * m_Thread; boost::asio::io_service * m_Service; boost::asio::io_service::work * m_Work; boost::asio::deadline_timer * m_PeerCleanupTimer, * m_PeerTestTimer; NTCPServer * m_NTCPServer; SSUServer * m_SSUServer; mutable std::mutex m_PeersMutex; std::map m_Peers; DHKeysPairSupplier m_DHKeysPairSupplier; std::atomic m_TotalSentBytes, m_TotalReceivedBytes, m_TotalTransitTransmittedBytes; uint32_t m_InBandwidth, m_OutBandwidth, m_TransitBandwidth; // bytes per second uint64_t m_LastInBandwidthUpdateBytes, m_LastOutBandwidthUpdateBytes, m_LastTransitBandwidthUpdateBytes; uint64_t m_LastBandwidthUpdateTime; /** which router families to trust for first hops */ std::vector m_TrustedFamilies; mutable std::mutex m_FamilyMutex; /** which routers for first hop to trust */ std::vector m_TrustedRouters; mutable std::mutex m_TrustedRoutersMutex; i2p::I2NPMessagesHandler m_LoopbackHandler; public: // for HTTP only const NTCPServer * GetNTCPServer () const { return m_NTCPServer; }; const SSUServer * GetSSUServer () const { return m_SSUServer; }; const decltype(m_Peers)& GetPeers () const { return m_Peers; }; }; extern Transports transports; } } #endif i2pd-2.17.0/libi2pd/Tunnel.cpp000066400000000000000000000637671321131324000157460ustar00rootroot00000000000000#include #include "I2PEndian.h" #include #include #include #include "Crypto.h" #include "RouterContext.h" #include "Log.h" #include "Timestamp.h" #include "I2NPProtocol.h" #include "Transports.h" #include "NetDb.hpp" #include "Config.h" #include "Tunnel.h" #include "TunnelPool.h" #ifdef WITH_EVENTS #include "Event.h" #endif namespace i2p { namespace tunnel { Tunnel::Tunnel (std::shared_ptr config): TunnelBase (config->GetTunnelID (), config->GetNextTunnelID (), config->GetNextIdentHash ()), m_Config (config), m_Pool (nullptr), m_State (eTunnelStatePending), m_IsRecreated (false), m_Latency (0) { } Tunnel::~Tunnel () { } void Tunnel::Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel) { #ifdef WITH_EVENTS std::string peers = i2p::context.GetIdentity()->GetIdentHash().ToBase64(); #endif auto numHops = m_Config->GetNumHops (); int numRecords = numHops <= STANDARD_NUM_RECORDS ? STANDARD_NUM_RECORDS : numHops; auto msg = NewI2NPShortMessage (); *msg->GetPayload () = numRecords; msg->len += numRecords*TUNNEL_BUILD_RECORD_SIZE + 1; // shuffle records std::vector recordIndicies; for (int i = 0; i < numRecords; i++) recordIndicies.push_back(i); std::random_shuffle (recordIndicies.begin(), recordIndicies.end()); // create real records uint8_t * records = msg->GetPayload () + 1; TunnelHopConfig * hop = m_Config->GetFirstHop (); int i = 0; BN_CTX * ctx = BN_CTX_new (); while (hop) { uint32_t msgID; if (hop->next) // we set replyMsgID for last hop only RAND_bytes ((uint8_t *)&msgID, 4); else msgID = replyMsgID; int idx = recordIndicies[i]; hop->CreateBuildRequestRecord (records + idx*TUNNEL_BUILD_RECORD_SIZE, msgID, ctx); hop->recordIndex = idx; i++; #ifdef WITH_EVENTS peers += ":" + hop->ident->GetIdentHash().ToBase64(); #endif hop = hop->next; } BN_CTX_free (ctx); #ifdef WITH_EVENTS EmitTunnelEvent("tunnel.build", this, peers); #endif // fill up fake records with random data for (int i = numHops; i < numRecords; i++) { int idx = recordIndicies[i]; RAND_bytes (records + idx*TUNNEL_BUILD_RECORD_SIZE, TUNNEL_BUILD_RECORD_SIZE); } // decrypt real records i2p::crypto::CBCDecryption decryption; hop = m_Config->GetLastHop ()->prev; while (hop) { decryption.SetKey (hop->replyKey); // decrypt records after current hop TunnelHopConfig * hop1 = hop->next; while (hop1) { decryption.SetIV (hop->replyIV); uint8_t * record = records + hop1->recordIndex*TUNNEL_BUILD_RECORD_SIZE; decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); hop1 = hop1->next; } hop = hop->prev; } msg->FillI2NPMessageHeader (eI2NPVariableTunnelBuild); // send message if (outboundTunnel) outboundTunnel->SendTunnelDataMsg (GetNextIdentHash (), 0, msg); else i2p::transport::transports.SendMessage (GetNextIdentHash (), msg); } bool Tunnel::HandleTunnelBuildResponse (uint8_t * msg, size_t len) { LogPrint (eLogDebug, "Tunnel: TunnelBuildResponse ", (int)msg[0], " records."); i2p::crypto::CBCDecryption decryption; TunnelHopConfig * hop = m_Config->GetLastHop (); while (hop) { decryption.SetKey (hop->replyKey); // decrypt records before and including current hop TunnelHopConfig * hop1 = hop; while (hop1) { auto idx = hop1->recordIndex; if (idx >= 0 && idx < msg[0]) { uint8_t * record = msg + 1 + idx*TUNNEL_BUILD_RECORD_SIZE; decryption.SetIV (hop->replyIV); decryption.Decrypt(record, TUNNEL_BUILD_RECORD_SIZE, record); } else LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range"); hop1 = hop1->prev; } hop = hop->prev; } bool established = true; hop = m_Config->GetFirstHop (); while (hop) { const uint8_t * record = msg + 1 + hop->recordIndex*TUNNEL_BUILD_RECORD_SIZE; uint8_t ret = record[BUILD_RESPONSE_RECORD_RET_OFFSET]; LogPrint (eLogDebug, "Tunnel: Build response ret code=", (int)ret); auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); if (profile) profile->TunnelBuildResponse (ret); if (ret) // if any of participants declined the tunnel is not established established = false; hop = hop->next; } if (established) { // create tunnel decryptions from layer and iv keys in reverse order hop = m_Config->GetLastHop (); while (hop) { auto tunnelHop = new TunnelHop; tunnelHop->ident = hop->ident; tunnelHop->decryption.SetKeys (hop->layerKey, hop->ivKey); m_Hops.push_back (std::unique_ptr(tunnelHop)); hop = hop->prev; } m_Config = nullptr; } if (established) m_State = eTunnelStateEstablished; return established; } bool Tunnel::LatencyFitsRange(uint64_t lower, uint64_t upper) const { auto latency = GetMeanLatency(); return latency >= lower && latency <= upper; } void Tunnel::EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) { const uint8_t * inPayload = in->GetPayload () + 4; uint8_t * outPayload = out->GetPayload () + 4; for (auto& it: m_Hops) { it->decryption.Decrypt (inPayload, outPayload); inPayload = outPayload; } } void Tunnel::SendTunnelDataMsg (std::shared_ptr msg) { LogPrint (eLogWarning, "Tunnel: Can't send I2NP messages without delivery instructions"); } std::vector > Tunnel::GetPeers () const { auto peers = GetInvertedPeers (); std::reverse (peers.begin (), peers.end ()); return peers; } std::vector > Tunnel::GetInvertedPeers () const { // hops are in inverted order std::vector > ret; for (auto& it: m_Hops) ret.push_back (it->ident); return ret; } void Tunnel::SetState(TunnelState state) { m_State = state; #ifdef WITH_EVENTS EmitTunnelEvent("tunnel.state", this, state); #endif } void Tunnel::PrintHops (std::stringstream& s) const { // hops are in inverted order, we must print in direct order for (auto it = m_Hops.rbegin (); it != m_Hops.rend (); it++) { s << " ⇒ "; s << i2p::data::GetIdentHashAbbreviation ((*it)->ident->GetIdentHash ()); } } void InboundTunnel::HandleTunnelDataMsg (std::shared_ptr msg) { if (IsFailed ()) SetState (eTunnelStateEstablished); // incoming messages means a tunnel is alive auto newMsg = CreateEmptyTunnelDataMsg (); EncryptTunnelMsg (msg, newMsg); newMsg->from = shared_from_this (); m_Endpoint.HandleDecryptedTunnelDataMsg (newMsg); } void InboundTunnel::Print (std::stringstream& s) const { PrintHops (s); s << " ⇒ " << GetTunnelID () << ":me"; } ZeroHopsInboundTunnel::ZeroHopsInboundTunnel (): InboundTunnel (std::make_shared ()), m_NumReceivedBytes (0) { } void ZeroHopsInboundTunnel::SendTunnelDataMsg (std::shared_ptr msg) { if (msg) { m_NumReceivedBytes += msg->GetLength (); msg->from = shared_from_this (); HandleI2NPMessage (msg); } } void ZeroHopsInboundTunnel::Print (std::stringstream& s) const { s << " ⇒ " << GetTunnelID () << ":me"; } void OutboundTunnel::SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg) { TunnelMessageBlock block; if (gwHash) { block.hash = gwHash; if (gwTunnel) { block.deliveryType = eDeliveryTypeTunnel; block.tunnelID = gwTunnel; } else block.deliveryType = eDeliveryTypeRouter; } else block.deliveryType = eDeliveryTypeLocal; block.data = msg; SendTunnelDataMsg ({block}); } void OutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) { std::unique_lock l(m_SendMutex); for (auto& it : msgs) m_Gateway.PutTunnelDataMsg (it); m_Gateway.SendBuffer (); } void OutboundTunnel::HandleTunnelDataMsg (std::shared_ptr tunnelMsg) { LogPrint (eLogError, "Tunnel: incoming message for outbound tunnel ", GetTunnelID ()); } void OutboundTunnel::Print (std::stringstream& s) const { s << GetTunnelID () << ":me"; PrintHops (s); s << " ⇒ "; } ZeroHopsOutboundTunnel::ZeroHopsOutboundTunnel (): OutboundTunnel (std::make_shared ()), m_NumSentBytes (0) { } void ZeroHopsOutboundTunnel::SendTunnelDataMsg (const std::vector& msgs) { for (auto& msg : msgs) { switch (msg.deliveryType) { case eDeliveryTypeLocal: i2p::HandleI2NPMessage (msg.data); break; case eDeliveryTypeTunnel: i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); break; case eDeliveryTypeRouter: i2p::transport::transports.SendMessage (msg.hash, msg.data); break; default: LogPrint (eLogError, "Tunnel: Unknown delivery type ", (int)msg.deliveryType); } } } void ZeroHopsOutboundTunnel::Print (std::stringstream& s) const { s << GetTunnelID () << ":me ⇒ "; } Tunnels tunnels; Tunnels::Tunnels (): m_IsRunning (false), m_Thread (nullptr), m_NumSuccesiveTunnelCreations (0), m_NumFailedTunnelCreations (0) { } Tunnels::~Tunnels () { } std::shared_ptr Tunnels::GetTunnel (uint32_t tunnelID) { auto it = m_Tunnels.find(tunnelID); if (it != m_Tunnels.end ()) return it->second; return nullptr; } std::shared_ptr Tunnels::GetPendingInboundTunnel (uint32_t replyMsgID) { return GetPendingTunnel (replyMsgID, m_PendingInboundTunnels); } std::shared_ptr Tunnels::GetPendingOutboundTunnel (uint32_t replyMsgID) { return GetPendingTunnel (replyMsgID, m_PendingOutboundTunnels); } template std::shared_ptr Tunnels::GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels) { auto it = pendingTunnels.find(replyMsgID); if (it != pendingTunnels.end () && it->second->GetState () == eTunnelStatePending) { it->second->SetState (eTunnelStateBuildReplyReceived); return it->second; } return nullptr; } std::shared_ptr Tunnels::GetNextInboundTunnel () { std::shared_ptr tunnel; size_t minReceived = 0; for (const auto& it : m_InboundTunnels) { if (!it->IsEstablished ()) continue; if (!tunnel || it->GetNumReceivedBytes () < minReceived) { tunnel = it; minReceived = it->GetNumReceivedBytes (); } } return tunnel; } std::shared_ptr Tunnels::GetNextOutboundTunnel () { if (m_OutboundTunnels.empty ()) return nullptr; uint32_t ind = rand () % m_OutboundTunnels.size (), i = 0; std::shared_ptr tunnel; for (const auto& it: m_OutboundTunnels) { if (it->IsEstablished ()) { tunnel = it; i++; } if (i > ind && tunnel) break; } return tunnel; } std::shared_ptr Tunnels::CreateTunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels) { auto pool = std::make_shared (numInboundHops, numOutboundHops, numInboundTunnels, numOutboundTunnels); std::unique_lock l(m_PoolsMutex); m_Pools.push_back (pool); return pool; } void Tunnels::DeleteTunnelPool (std::shared_ptr pool) { if (pool) { StopTunnelPool (pool); { std::unique_lock l(m_PoolsMutex); m_Pools.remove (pool); } } } void Tunnels::StopTunnelPool (std::shared_ptr pool) { if (pool) { pool->SetActive (false); pool->DetachTunnels (); } } void Tunnels::AddTransitTunnel (std::shared_ptr tunnel) { if (m_Tunnels.emplace (tunnel->GetTunnelID (), tunnel).second) m_TransitTunnels.push_back (tunnel); else LogPrint (eLogError, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " already exists"); } void Tunnels::Start () { m_IsRunning = true; m_Thread = new std::thread (std::bind (&Tunnels::Run, this)); } void Tunnels::Stop () { m_IsRunning = false; m_Queue.WakeUp (); if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = 0; } } void Tunnels::Run () { std::this_thread::sleep_for (std::chrono::seconds(1)); // wait for other parts are ready uint64_t lastTs = 0; while (m_IsRunning) { try { auto msg = m_Queue.GetNextWithTimeout (1000); // 1 sec if (msg) { uint32_t prevTunnelID = 0, tunnelID = 0; std::shared_ptr prevTunnel; do { std::shared_ptr tunnel; uint8_t typeID = msg->GetTypeID (); switch (typeID) { case eI2NPTunnelData: case eI2NPTunnelGateway: { tunnelID = bufbe32toh (msg->GetPayload ()); if (tunnelID == prevTunnelID) tunnel = prevTunnel; else if (prevTunnel) prevTunnel->FlushTunnelDataMsgs (); if (!tunnel) tunnel = GetTunnel (tunnelID); if (tunnel) { if (typeID == eI2NPTunnelData) tunnel->HandleTunnelDataMsg (msg); else // tunnel gateway assumed HandleTunnelGatewayMsg (tunnel, msg); } else LogPrint (eLogWarning, "Tunnel: tunnel not found, tunnelID=", tunnelID, " previousTunnelID=", prevTunnelID, " type=", (int)typeID); break; } case eI2NPVariableTunnelBuild: case eI2NPVariableTunnelBuildReply: case eI2NPTunnelBuild: case eI2NPTunnelBuildReply: HandleI2NPMessage (msg->GetBuffer (), msg->GetLength ()); break; default: LogPrint (eLogWarning, "Tunnel: unexpected messsage type ", (int) typeID); } msg = m_Queue.Get (); if (msg) { prevTunnelID = tunnelID; prevTunnel = tunnel; } else if (tunnel) tunnel->FlushTunnelDataMsgs (); } while (msg); } uint64_t ts = i2p::util::GetSecondsSinceEpoch (); if (ts - lastTs >= 15) // manage tunnels every 15 seconds { ManageTunnels (); lastTs = ts; } } catch (std::exception& ex) { LogPrint (eLogError, "Tunnel: runtime exception: ", ex.what ()); } } } void Tunnels::HandleTunnelGatewayMsg (std::shared_ptr tunnel, std::shared_ptr msg) { if (!tunnel) { LogPrint (eLogError, "Tunnel: missing tunnel for gateway"); return; } const uint8_t * payload = msg->GetPayload (); uint16_t len = bufbe16toh(payload + TUNNEL_GATEWAY_HEADER_LENGTH_OFFSET); // we make payload as new I2NP message to send msg->offset += I2NP_HEADER_SIZE + TUNNEL_GATEWAY_HEADER_SIZE; if (msg->offset + len > msg->len) { LogPrint (eLogError, "Tunnel: gateway payload ", (int)len, " exceeds message length ", (int)msg->len); return; } msg->len = msg->offset + len; auto typeID = msg->GetTypeID (); LogPrint (eLogDebug, "Tunnel: gateway of ", (int) len, " bytes for tunnel ", tunnel->GetTunnelID (), ", msg type ", (int)typeID); if (IsRouterInfoMsg (msg) || typeID == eI2NPDatabaseSearchReply) // transit DatabaseStore my contain new/updated RI // or DatabaseSearchReply with new routers i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg)); tunnel->SendTunnelDataMsg (msg); } void Tunnels::ManageTunnels () { ManagePendingTunnels (); ManageInboundTunnels (); ManageOutboundTunnels (); ManageTransitTunnels (); ManageTunnelPools (); } void Tunnels::ManagePendingTunnels () { ManagePendingTunnels (m_PendingInboundTunnels); ManagePendingTunnels (m_PendingOutboundTunnels); } template void Tunnels::ManagePendingTunnels (PendingTunnels& pendingTunnels) { // check pending tunnel. delete failed or timeout uint64_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = pendingTunnels.begin (); it != pendingTunnels.end ();) { auto tunnel = it->second; auto pool = tunnel->GetTunnelPool(); switch (tunnel->GetState ()) { case eTunnelStatePending: if (ts > tunnel->GetCreationTime () + TUNNEL_CREATION_TIMEOUT) { LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " timeout, deleted"); // update stats auto config = tunnel->GetTunnelConfig (); if (config) { auto hop = config->GetFirstHop (); while (hop) { if (hop->ident) { auto profile = i2p::data::netdb.FindRouterProfile (hop->ident->GetIdentHash ()); if (profile) profile->TunnelNonReplied (); } hop = hop->next; } } #ifdef WITH_EVENTS EmitTunnelEvent("tunnel.state", tunnel.get(), eTunnelStateBuildFailed); #endif // for i2lua if(pool) pool->OnTunnelBuildResult(tunnel, eBuildResultTimeout); // delete it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; } else ++it; break; case eTunnelStateBuildFailed: LogPrint (eLogDebug, "Tunnel: pending build request ", it->first, " failed, deleted"); #ifdef WITH_EVENTS EmitTunnelEvent("tunnel.state", tunnel.get(), eTunnelStateBuildFailed); #endif // for i2lua if(pool) pool->OnTunnelBuildResult(tunnel, eBuildResultRejected); it = pendingTunnels.erase (it); m_NumFailedTunnelCreations++; break; case eTunnelStateBuildReplyReceived: // intermediate state, will be either established of build failed ++it; break; default: // success it = pendingTunnels.erase (it); m_NumSuccesiveTunnelCreations++; } } } void Tunnels::ManageOutboundTunnels () { uint64_t ts = i2p::util::GetSecondsSinceEpoch (); { for (auto it = m_OutboundTunnels.begin (); it != m_OutboundTunnels.end ();) { auto tunnel = *it; if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired"); auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); // we don't have outbound tunnels in m_Tunnels it = m_OutboundTunnels.erase (it); } else { if (tunnel->IsEstablished ()) { if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { tunnel->SetIsRecreated (); auto pool = tunnel->GetTunnelPool (); if (pool) pool->RecreateOutboundTunnel (tunnel); } if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) tunnel->SetState (eTunnelStateExpiring); } ++it; } } } if (m_OutboundTunnels.size () < 3) { // trying to create one more oubound tunnel auto inboundTunnel = GetNextInboundTunnel (); auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : i2p::data::netdb.GetRandomRouter (); if (!inboundTunnel || !router) return; LogPrint (eLogDebug, "Tunnel: creating one hop outbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()) ); } } void Tunnels::ManageInboundTunnels () { uint64_t ts = i2p::util::GetSecondsSinceEpoch (); { for (auto it = m_InboundTunnels.begin (); it != m_InboundTunnels.end ();) { auto tunnel = *it; if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { LogPrint (eLogDebug, "Tunnel: tunnel with id ", tunnel->GetTunnelID (), " expired"); auto pool = tunnel->GetTunnelPool (); if (pool) pool->TunnelExpired (tunnel); m_Tunnels.erase (tunnel->GetTunnelID ()); it = m_InboundTunnels.erase (it); } else { if (tunnel->IsEstablished ()) { if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { tunnel->SetIsRecreated (); auto pool = tunnel->GetTunnelPool (); if (pool) pool->RecreateInboundTunnel (tunnel); } if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) tunnel->SetState (eTunnelStateExpiring); else // we don't need to cleanup expiring tunnels tunnel->Cleanup (); } it++; } } } if (m_InboundTunnels.empty ()) { LogPrint (eLogDebug, "Tunnel: Creating zero hops inbound tunnel"); CreateZeroHopsInboundTunnel (); CreateZeroHopsOutboundTunnel (); if (!m_ExploratoryPool) { int ibLen; i2p::config::GetOption("exploratory.inbound.length", ibLen); int obLen; i2p::config::GetOption("exploratory.outbound.length", obLen); int ibNum; i2p::config::GetOption("exploratory.inbound.quantity", ibNum); int obNum; i2p::config::GetOption("exploratory.outbound.quantity", obNum); m_ExploratoryPool = CreateTunnelPool (ibLen, obLen, ibNum, obNum); m_ExploratoryPool->SetLocalDestination (i2p::context.GetSharedDestination ()); } return; } if (m_OutboundTunnels.empty () || m_InboundTunnels.size () < 3) { // trying to create one more inbound tunnel auto router = i2p::transport::transports.RoutesRestricted() ? i2p::transport::transports.GetRestrictedPeer() : i2p::data::netdb.GetRandomRouter (); if (!router) { LogPrint (eLogWarning, "Tunnel: can't find any router, skip creating tunnel"); return; } LogPrint (eLogDebug, "Tunnel: creating one hop inbound tunnel"); CreateTunnel ( std::make_shared (std::vector > { router->GetRouterIdentity () }) ); } } void Tunnels::ManageTransitTunnels () { uint32_t ts = i2p::util::GetSecondsSinceEpoch (); for (auto it = m_TransitTunnels.begin (); it != m_TransitTunnels.end ();) { auto tunnel = *it; if (ts > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT) { LogPrint (eLogDebug, "Tunnel: Transit tunnel with id ", tunnel->GetTunnelID (), " expired"); m_Tunnels.erase (tunnel->GetTunnelID ()); it = m_TransitTunnels.erase (it); } else { tunnel->Cleanup (); it++; } } } void Tunnels::ManageTunnelPools () { std::unique_lock l(m_PoolsMutex); for (auto& pool : m_Pools) { if (pool && pool->IsActive ()) { pool->CreateTunnels (); pool->TestTunnels (); } } } void Tunnels::PostTunnelData (std::shared_ptr msg) { if (msg) m_Queue.Put (msg); } void Tunnels::PostTunnelData (const std::vector >& msgs) { m_Queue.Put (msgs); } template std::shared_ptr Tunnels::CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) { auto newTunnel = std::make_shared (config); uint32_t replyMsgID; RAND_bytes ((uint8_t *)&replyMsgID, 4); AddPendingTunnel (replyMsgID, newTunnel); newTunnel->Build (replyMsgID, outboundTunnel); return newTunnel; } std::shared_ptr Tunnels::CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel) { if (config) return CreateTunnel(config, outboundTunnel); else return CreateZeroHopsInboundTunnel (); } std::shared_ptr Tunnels::CreateOutboundTunnel (std::shared_ptr config) { if (config) return CreateTunnel(config); else return CreateZeroHopsOutboundTunnel (); } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { m_PendingInboundTunnels[replyMsgID] = tunnel; } void Tunnels::AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel) { m_PendingOutboundTunnels[replyMsgID] = tunnel; } void Tunnels::AddOutboundTunnel (std::shared_ptr newTunnel) { // we don't need to insert it to m_Tunnels m_OutboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (pool && pool->IsActive ()) pool->TunnelCreated (newTunnel); else newTunnel->SetTunnelPool (nullptr); } void Tunnels::AddInboundTunnel (std::shared_ptr newTunnel) { if (m_Tunnels.emplace (newTunnel->GetTunnelID (), newTunnel).second) { m_InboundTunnels.push_back (newTunnel); auto pool = newTunnel->GetTunnelPool (); if (!pool) { // build symmetric outbound tunnel CreateTunnel (std::make_shared(newTunnel->GetInvertedPeers (), newTunnel->GetNextTunnelID (), newTunnel->GetNextIdentHash ()), GetNextOutboundTunnel ()); } else { if (pool->IsActive ()) pool->TunnelCreated (newTunnel); else newTunnel->SetTunnelPool (nullptr); } } else LogPrint (eLogError, "Tunnel: tunnel with id ", newTunnel->GetTunnelID (), " already exists"); } std::shared_ptr Tunnels::CreateZeroHopsInboundTunnel () { auto inboundTunnel = std::make_shared (); inboundTunnel->SetState (eTunnelStateEstablished); m_InboundTunnels.push_back (inboundTunnel); m_Tunnels[inboundTunnel->GetTunnelID ()] = inboundTunnel; return inboundTunnel; } std::shared_ptr Tunnels::CreateZeroHopsOutboundTunnel () { auto outboundTunnel = std::make_shared (); outboundTunnel->SetState (eTunnelStateEstablished); m_OutboundTunnels.push_back (outboundTunnel); // we don't insert into m_Tunnels return outboundTunnel; } int Tunnels::GetTransitTunnelsExpirationTimeout () { int timeout = 0; uint32_t ts = i2p::util::GetSecondsSinceEpoch (); // TODO: possible race condition with I2PControl for (const auto& it : m_TransitTunnels) { int t = it->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT - ts; if (t > timeout) timeout = t; } return timeout; } size_t Tunnels::CountTransitTunnels() const { // TODO: locking return m_TransitTunnels.size(); } size_t Tunnels::CountInboundTunnels() const { // TODO: locking return m_InboundTunnels.size(); } size_t Tunnels::CountOutboundTunnels() const { // TODO: locking return m_OutboundTunnels.size(); } } } i2pd-2.17.0/libi2pd/Tunnel.h000066400000000000000000000244641321131324000154020ustar00rootroot00000000000000#ifndef TUNNEL_H__ #define TUNNEL_H__ #include #include #include #include #include #include #include #include #include #include "Queue.h" #include "Crypto.h" #include "TunnelConfig.h" #include "TunnelPool.h" #include "TransitTunnel.h" #include "TunnelEndpoint.h" #include "TunnelGateway.h" #include "TunnelBase.h" #include "I2NPProtocol.h" #include "Event.h" namespace i2p { namespace tunnel { template static void EmitTunnelEvent(const std::string & ev, const TunnelT & t) { #ifdef WITH_EVENTS EmitEvent({{"type", ev}, {"tid", std::to_string(t->GetTunnelID())}}); #else (void) ev; (void) t; #endif } template static void EmitTunnelEvent(const std::string & ev, TunnelT * t, const T & val) { #ifdef WITH_EVENTS EmitEvent({{"type", ev}, {"tid", std::to_string(t->GetTunnelID())}, {"value", std::to_string(val)}, {"inbound", std::to_string(t->IsInbound())}}); #else (void) ev; (void) t; (void) val; #endif } template static void EmitTunnelEvent(const std::string & ev, TunnelT * t, const std::string & val) { #ifdef WITH_EVENTS EmitEvent({{"type", ev}, {"tid", std::to_string(t->GetTunnelID())}, {"value", val}, {"inbound", std::to_string(t->IsInbound())}}); #else (void) ev; (void) t; (void) val; #endif } const int TUNNEL_EXPIRATION_TIMEOUT = 660; // 11 minutes const int TUNNEL_EXPIRATION_THRESHOLD = 60; // 1 minute const int TUNNEL_RECREATION_THRESHOLD = 90; // 1.5 minutes const int TUNNEL_CREATION_TIMEOUT = 30; // 30 seconds const int STANDARD_NUM_RECORDS = 5; // in VariableTunnelBuild message enum TunnelState { eTunnelStatePending, eTunnelStateBuildReplyReceived, eTunnelStateBuildFailed, eTunnelStateEstablished, eTunnelStateTestFailed, eTunnelStateFailed, eTunnelStateExpiring }; class OutboundTunnel; class InboundTunnel; class Tunnel: public TunnelBase { struct TunnelHop { std::shared_ptr ident; i2p::crypto::TunnelDecryption decryption; }; public: Tunnel (std::shared_ptr config); ~Tunnel (); void Build (uint32_t replyMsgID, std::shared_ptr outboundTunnel = nullptr); std::shared_ptr GetTunnelConfig () const { return m_Config; } std::vector > GetPeers () const; std::vector > GetInvertedPeers () const; TunnelState GetState () const { return m_State; }; void SetState (TunnelState state); bool IsEstablished () const { return m_State == eTunnelStateEstablished; }; bool IsFailed () const { return m_State == eTunnelStateFailed; }; bool IsRecreated () const { return m_IsRecreated; }; void SetIsRecreated () { m_IsRecreated = true; }; virtual bool IsInbound() const = 0; std::shared_ptr GetTunnelPool () const { return m_Pool; }; void SetTunnelPool (std::shared_ptr pool) { m_Pool = pool; }; bool HandleTunnelBuildResponse (uint8_t * msg, size_t len); virtual void Print (std::stringstream&) const {}; // implements TunnelBase void SendTunnelDataMsg (std::shared_ptr msg); void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out); /** @brief add latency sample */ void AddLatencySample(const uint64_t ms) { m_Latency = (m_Latency + ms) >> 1; } /** @brief get this tunnel's estimated latency */ uint64_t GetMeanLatency() const { return m_Latency; } /** @brief return true if this tunnel's latency fits in range [lowerbound, upperbound] */ bool LatencyFitsRange(uint64_t lowerbound, uint64_t upperbound) const; bool LatencyIsKnown() const { return m_Latency > 0; } protected: void PrintHops (std::stringstream& s) const; private: std::shared_ptr m_Config; std::vector > m_Hops; std::shared_ptr m_Pool; // pool, tunnel belongs to, or null TunnelState m_State; bool m_IsRecreated; uint64_t m_Latency; // in milliseconds }; class OutboundTunnel: public Tunnel { public: OutboundTunnel (std::shared_ptr config): Tunnel (config), m_Gateway (this), m_EndpointIdentHash (config->GetLastIdentHash ()) {}; void SendTunnelDataMsg (const uint8_t * gwHash, uint32_t gwTunnel, std::shared_ptr msg); virtual void SendTunnelDataMsg (const std::vector& msgs); // multiple messages const i2p::data::IdentHash& GetEndpointIdentHash () const { return m_EndpointIdentHash; }; virtual size_t GetNumSentBytes () const { return m_Gateway.GetNumSentBytes (); }; void Print (std::stringstream& s) const; // implements TunnelBase void HandleTunnelDataMsg (std::shared_ptr tunnelMsg); bool IsInbound() const { return false; } private: std::mutex m_SendMutex; TunnelGateway m_Gateway; i2p::data::IdentHash m_EndpointIdentHash; }; class InboundTunnel: public Tunnel, public std::enable_shared_from_this { public: InboundTunnel (std::shared_ptr config): Tunnel (config), m_Endpoint (true) {}; void HandleTunnelDataMsg (std::shared_ptr msg); virtual size_t GetNumReceivedBytes () const { return m_Endpoint.GetNumReceivedBytes (); }; void Print (std::stringstream& s) const; bool IsInbound() const { return true; } // override TunnelBase void Cleanup () { m_Endpoint.Cleanup (); }; private: TunnelEndpoint m_Endpoint; }; class ZeroHopsInboundTunnel: public InboundTunnel { public: ZeroHopsInboundTunnel (); void SendTunnelDataMsg (std::shared_ptr msg); void Print (std::stringstream& s) const; size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; private: size_t m_NumReceivedBytes; }; class ZeroHopsOutboundTunnel: public OutboundTunnel { public: ZeroHopsOutboundTunnel (); void SendTunnelDataMsg (const std::vector& msgs); void Print (std::stringstream& s) const; size_t GetNumSentBytes () const { return m_NumSentBytes; }; private: size_t m_NumSentBytes; }; class Tunnels { public: Tunnels (); ~Tunnels (); void Start (); void Stop (); std::shared_ptr GetPendingInboundTunnel (uint32_t replyMsgID); std::shared_ptr GetPendingOutboundTunnel (uint32_t replyMsgID); std::shared_ptr GetNextInboundTunnel (); std::shared_ptr GetNextOutboundTunnel (); std::shared_ptr GetExploratoryPool () const { return m_ExploratoryPool; }; std::shared_ptr GetTunnel (uint32_t tunnelID); int GetTransitTunnelsExpirationTimeout (); void AddTransitTunnel (std::shared_ptr tunnel); void AddOutboundTunnel (std::shared_ptr newTunnel); void AddInboundTunnel (std::shared_ptr newTunnel); std::shared_ptr CreateInboundTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel); std::shared_ptr CreateOutboundTunnel (std::shared_ptr config); void PostTunnelData (std::shared_ptr msg); void PostTunnelData (const std::vector >& msgs); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); void AddPendingTunnel (uint32_t replyMsgID, std::shared_ptr tunnel); std::shared_ptr CreateTunnelPool (int numInboundHops, int numOuboundHops, int numInboundTunnels, int numOutboundTunnels); void DeleteTunnelPool (std::shared_ptr pool); void StopTunnelPool (std::shared_ptr pool); private: template std::shared_ptr CreateTunnel (std::shared_ptr config, std::shared_ptr outboundTunnel = nullptr); template std::shared_ptr GetPendingTunnel (uint32_t replyMsgID, const std::map >& pendingTunnels); void HandleTunnelGatewayMsg (std::shared_ptr tunnel, std::shared_ptr msg); void Run (); void ManageTunnels (); void ManageOutboundTunnels (); void ManageInboundTunnels (); void ManageTransitTunnels (); void ManagePendingTunnels (); template void ManagePendingTunnels (PendingTunnels& pendingTunnels); void ManageTunnelPools (); std::shared_ptr CreateZeroHopsInboundTunnel (); std::shared_ptr CreateZeroHopsOutboundTunnel (); private: bool m_IsRunning; std::thread * m_Thread; std::map > m_PendingInboundTunnels; // by replyMsgID std::map > m_PendingOutboundTunnels; // by replyMsgID std::list > m_InboundTunnels; std::list > m_OutboundTunnels; std::list > m_TransitTunnels; std::unordered_map > m_Tunnels; // tunnelID->tunnel known by this id std::mutex m_PoolsMutex; std::list> m_Pools; std::shared_ptr m_ExploratoryPool; i2p::util::Queue > m_Queue; // some stats int m_NumSuccesiveTunnelCreations, m_NumFailedTunnelCreations; public: // for HTTP only const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; }; const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; const decltype(m_TransitTunnels)& GetTransitTunnels () const { return m_TransitTunnels; }; size_t CountTransitTunnels() const; size_t CountInboundTunnels() const; size_t CountOutboundTunnels() const; int GetQueueSize () { return m_Queue.GetSize (); }; int GetTunnelCreationSuccessRate () const // in percents { int totalNum = m_NumSuccesiveTunnelCreations + m_NumFailedTunnelCreations; return totalNum ? m_NumSuccesiveTunnelCreations*100/totalNum : 0; } }; extern Tunnels tunnels; } } #endif i2pd-2.17.0/libi2pd/TunnelBase.h000066400000000000000000000040601321131324000161630ustar00rootroot00000000000000#ifndef TUNNEL_BASE_H__ #define TUNNEL_BASE_H__ #include #include #include "Timestamp.h" #include "I2NPProtocol.h" #include "Identity.h" namespace i2p { namespace tunnel { const size_t TUNNEL_DATA_MSG_SIZE = 1028; const size_t TUNNEL_DATA_ENCRYPTED_SIZE = 1008; const size_t TUNNEL_DATA_MAX_PAYLOAD_SIZE = 1003; enum TunnelDeliveryType { eDeliveryTypeLocal = 0, eDeliveryTypeTunnel = 1, eDeliveryTypeRouter = 2 }; struct TunnelMessageBlock { TunnelDeliveryType deliveryType; i2p::data::IdentHash hash; uint32_t tunnelID; std::shared_ptr data; }; class TunnelBase { public: TunnelBase (uint32_t tunnelID, uint32_t nextTunnelID, i2p::data::IdentHash nextIdent): m_TunnelID (tunnelID), m_NextTunnelID (nextTunnelID), m_NextIdent (nextIdent), m_CreationTime (i2p::util::GetSecondsSinceEpoch ()) {}; virtual ~TunnelBase () {}; virtual void Cleanup () {}; virtual void HandleTunnelDataMsg (std::shared_ptr tunnelMsg) = 0; virtual void SendTunnelDataMsg (std::shared_ptr msg) = 0; virtual void FlushTunnelDataMsgs () {}; virtual void EncryptTunnelMsg (std::shared_ptr in, std::shared_ptr out) = 0; uint32_t GetNextTunnelID () const { return m_NextTunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return m_NextIdent; }; virtual uint32_t GetTunnelID () const { return m_TunnelID; }; // as known at our side uint32_t GetCreationTime () const { return m_CreationTime; }; void SetCreationTime (uint32_t t) { m_CreationTime = t; }; private: uint32_t m_TunnelID, m_NextTunnelID; i2p::data::IdentHash m_NextIdent; uint32_t m_CreationTime; // seconds since epoch }; struct TunnelCreationTimeCmp { bool operator() (std::shared_ptr t1, std::shared_ptr t2) const { if (t1->GetCreationTime () != t2->GetCreationTime ()) return t1->GetCreationTime () > t2->GetCreationTime (); else return t1 < t2; }; }; } } #endif i2pd-2.17.0/libi2pd/TunnelConfig.h000066400000000000000000000143171321131324000165240ustar00rootroot00000000000000#ifndef TUNNEL_CONFIG_H__ #define TUNNEL_CONFIG_H__ #include #include #include #include #include "Crypto.h" #include "Identity.h" #include "RouterContext.h" #include "Timestamp.h" namespace i2p { namespace tunnel { struct TunnelHopConfig { std::shared_ptr ident; i2p::data::IdentHash nextIdent; uint32_t tunnelID, nextTunnelID; uint8_t layerKey[32]; uint8_t ivKey[32]; uint8_t replyKey[32]; uint8_t replyIV[16]; bool isGateway, isEndpoint; TunnelHopConfig * next, * prev; int recordIndex; // record # in tunnel build message TunnelHopConfig (std::shared_ptr r) { RAND_bytes (layerKey, 32); RAND_bytes (ivKey, 32); RAND_bytes (replyKey, 32); RAND_bytes (replyIV, 16); RAND_bytes ((uint8_t *)&tunnelID, 4); isGateway = true; isEndpoint = true; ident = r; //nextRouter = nullptr; nextTunnelID = 0; next = nullptr; prev = nullptr; } void SetNextIdent (const i2p::data::IdentHash& ident) { nextIdent = ident; isEndpoint = false; RAND_bytes ((uint8_t *)&nextTunnelID, 4); } void SetReplyHop (uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) { nextIdent = replyIdent; nextTunnelID = replyTunnelID; isEndpoint = true; } void SetNext (TunnelHopConfig * n) { next = n; if (next) { next->prev = this; next->isGateway = false; isEndpoint = false; nextIdent = next->ident->GetIdentHash (); nextTunnelID = next->tunnelID; } } void SetPrev (TunnelHopConfig * p) { prev = p; if (prev) { prev->next = this; prev->isEndpoint = false; isGateway = false; } } void CreateBuildRequestRecord (uint8_t * record, uint32_t replyMsgID, BN_CTX * ctx) const { uint8_t clearText[BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE]; htobe32buf (clearText + BUILD_REQUEST_RECORD_RECEIVE_TUNNEL_OFFSET, tunnelID); memcpy (clearText + BUILD_REQUEST_RECORD_OUR_IDENT_OFFSET, ident->GetIdentHash (), 32); htobe32buf (clearText + BUILD_REQUEST_RECORD_NEXT_TUNNEL_OFFSET, nextTunnelID); memcpy (clearText + BUILD_REQUEST_RECORD_NEXT_IDENT_OFFSET, nextIdent, 32); memcpy (clearText + BUILD_REQUEST_RECORD_LAYER_KEY_OFFSET, layerKey, 32); memcpy (clearText + BUILD_REQUEST_RECORD_IV_KEY_OFFSET, ivKey, 32); memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_KEY_OFFSET, replyKey, 32); memcpy (clearText + BUILD_REQUEST_RECORD_REPLY_IV_OFFSET, replyIV, 16); uint8_t flag = 0; if (isGateway) flag |= 0x80; if (isEndpoint) flag |= 0x40; clearText[BUILD_REQUEST_RECORD_FLAG_OFFSET] = flag; htobe32buf (clearText + BUILD_REQUEST_RECORD_REQUEST_TIME_OFFSET, i2p::util::GetHoursSinceEpoch ()); htobe32buf (clearText + BUILD_REQUEST_RECORD_SEND_MSG_ID_OFFSET, replyMsgID); RAND_bytes (clearText + BUILD_REQUEST_RECORD_PADDING_OFFSET, BUILD_REQUEST_RECORD_CLEAR_TEXT_SIZE - BUILD_REQUEST_RECORD_PADDING_OFFSET); i2p::crypto::ElGamalEncrypt (ident->GetEncryptionPublicKey (), clearText, record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, ctx); memcpy (record + BUILD_REQUEST_RECORD_TO_PEER_OFFSET, (const uint8_t *)ident->GetIdentHash (), 16); } }; class TunnelConfig { public: TunnelConfig (std::vector > peers) // inbound { CreatePeers (peers); m_LastHop->SetNextIdent (i2p::context.GetIdentHash ()); } TunnelConfig (std::vector > peers, uint32_t replyTunnelID, const i2p::data::IdentHash& replyIdent) // outbound { CreatePeers (peers); m_FirstHop->isGateway = false; m_LastHop->SetReplyHop (replyTunnelID, replyIdent); } ~TunnelConfig () { TunnelHopConfig * hop = m_FirstHop; while (hop) { auto tmp = hop; hop = hop->next; delete tmp; } } TunnelHopConfig * GetFirstHop () const { return m_FirstHop; } TunnelHopConfig * GetLastHop () const { return m_LastHop; } int GetNumHops () const { int num = 0; TunnelHopConfig * hop = m_FirstHop; while (hop) { num++; hop = hop->next; } return num; } bool IsEmpty () const { return !m_FirstHop; } virtual bool IsInbound () const { return m_FirstHop->isGateway; } virtual uint32_t GetTunnelID () const { if (!m_FirstHop) return 0; return IsInbound () ? m_LastHop->nextTunnelID : m_FirstHop->tunnelID; } virtual uint32_t GetNextTunnelID () const { if (!m_FirstHop) return 0; return m_FirstHop->tunnelID; } virtual const i2p::data::IdentHash& GetNextIdentHash () const { return m_FirstHop->ident->GetIdentHash (); } virtual const i2p::data::IdentHash& GetLastIdentHash () const { return m_LastHop->ident->GetIdentHash (); } std::vector > GetPeers () const { std::vector > peers; TunnelHopConfig * hop = m_FirstHop; while (hop) { peers.push_back (hop->ident); hop = hop->next; } return peers; } protected: // this constructor can't be called from outside TunnelConfig (): m_FirstHop (nullptr), m_LastHop (nullptr) { } private: template void CreatePeers (const Peers& peers) { TunnelHopConfig * prev = nullptr; for (const auto& it: peers) { auto hop = new TunnelHopConfig (it); if (prev) prev->SetNext (hop); else m_FirstHop = hop; prev = hop; } m_LastHop = prev; } private: TunnelHopConfig * m_FirstHop, * m_LastHop; }; class ZeroHopsTunnelConfig: public TunnelConfig { public: ZeroHopsTunnelConfig () { RAND_bytes ((uint8_t *)&m_TunnelID, 4);}; bool IsInbound () const { return true; }; // TODO: uint32_t GetTunnelID () const { return m_TunnelID; }; uint32_t GetNextTunnelID () const { return m_TunnelID; }; const i2p::data::IdentHash& GetNextIdentHash () const { return i2p::context.GetIdentHash (); }; const i2p::data::IdentHash& GetLastIdentHash () const { return i2p::context.GetIdentHash (); }; private: uint32_t m_TunnelID; }; } } #endif i2pd-2.17.0/libi2pd/TunnelEndpoint.cpp000066400000000000000000000226621321131324000174340ustar00rootroot00000000000000#include "I2PEndian.h" #include #include "Crypto.h" #include "Log.h" #include "NetDb.hpp" #include "I2NPProtocol.h" #include "Transports.h" #include "RouterContext.h" #include "Timestamp.h" #include "TunnelEndpoint.h" namespace i2p { namespace tunnel { TunnelEndpoint::~TunnelEndpoint () { } void TunnelEndpoint::HandleDecryptedTunnelDataMsg (std::shared_ptr msg) { m_NumReceivedBytes += TUNNEL_DATA_MSG_SIZE; uint8_t * decrypted = msg->GetPayload () + 20; // 4 + 16 uint8_t * zero = (uint8_t *)memchr (decrypted + 4, 0, TUNNEL_DATA_ENCRYPTED_SIZE - 4); // witout 4-byte checksum if (zero) { uint8_t * fragment = zero + 1; // verify checksum memcpy (msg->GetPayload () + TUNNEL_DATA_MSG_SIZE, msg->GetPayload () + 4, 16); // copy iv to the end uint8_t hash[32]; SHA256(fragment, TUNNEL_DATA_MSG_SIZE -(fragment - msg->GetPayload ()) + 16, hash); // payload + iv if (memcmp (hash, decrypted, 4)) { LogPrint (eLogError, "TunnelMessage: checksum verification failed"); return; } // process fragments while (fragment < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) { uint8_t flag = fragment[0]; fragment++; bool isFollowOnFragment = flag & 0x80, isLastFragment = true; uint32_t msgID = 0; int fragmentNum = 0; TunnelMessageBlockEx m; if (!isFollowOnFragment) { // first fragment m.deliveryType = (TunnelDeliveryType)((flag >> 5) & 0x03); switch (m.deliveryType) { case eDeliveryTypeLocal: // 0 break; case eDeliveryTypeTunnel: // 1 m.tunnelID = bufbe32toh (fragment); fragment += 4; // tunnelID m.hash = i2p::data::IdentHash (fragment); fragment += 32; // hash break; case eDeliveryTypeRouter: // 2 m.hash = i2p::data::IdentHash (fragment); fragment += 32; // to hash break; default: ; } bool isFragmented = flag & 0x08; if (isFragmented) { // Message ID msgID = bufbe32toh (fragment); fragment += 4; isLastFragment = false; } } else { // follow on msgID = bufbe32toh (fragment); // MessageID fragment += 4; fragmentNum = (flag >> 1) & 0x3F; // 6 bits isLastFragment = flag & 0x01; } uint16_t size = bufbe16toh (fragment); fragment += 2; msg->offset = fragment - msg->buf; msg->len = msg->offset + size; if (msg->len > msg->maxLen) { LogPrint (eLogError, "TunnelMessage: fragment is too long ", (int)size); return; } if (fragment + size < decrypted + TUNNEL_DATA_ENCRYPTED_SIZE) { // this is not last message. we have to copy it m.data = NewI2NPTunnelMessage (); m.data->offset += TUNNEL_GATEWAY_HEADER_SIZE; // reserve room for TunnelGateway header m.data->len += TUNNEL_GATEWAY_HEADER_SIZE; *(m.data) = *msg; } else m.data = msg; if (!isFollowOnFragment && isLastFragment) HandleNextMessage (m); else { if (msgID) // msgID is presented, assume message is fragmented { if (!isFollowOnFragment) // create new incomlete message { m.nextFragmentNum = 1; m.receiveTime = i2p::util::GetMillisecondsSinceEpoch (); auto ret = m_IncompleteMessages.insert (std::pair(msgID, m)); if (ret.second) HandleOutOfSequenceFragments (msgID, ret.first->second); else LogPrint (eLogError, "TunnelMessage: Incomplete message ", msgID, " already exists"); } else { m.nextFragmentNum = fragmentNum; HandleFollowOnFragment (msgID, isLastFragment, m); } } else LogPrint (eLogError, "TunnelMessage: Message is fragmented, but msgID is not presented"); } fragment += size; } } else LogPrint (eLogError, "TunnelMessage: zero not found"); } void TunnelEndpoint::HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m) { auto fragment = m.data->GetBuffer (); auto size = m.data->GetLength (); auto it = m_IncompleteMessages.find (msgID); if (it != m_IncompleteMessages.end()) { auto& msg = it->second; if (m.nextFragmentNum == msg.nextFragmentNum) { if (msg.data->len + size < I2NP_MAX_MESSAGE_SIZE) // check if message is not too long { if (msg.data->len + size > msg.data->maxLen) { // LogPrint (eLogWarning, "TunnelMessage: I2NP message size ", msg.data->maxLen, " is not enough"); auto newMsg = NewI2NPMessage (); *newMsg = *(msg.data); msg.data = newMsg; } if (msg.data->Concat (fragment, size) < size) // concatenate fragment LogPrint (eLogError, "TunnelMessage: I2NP buffer overflow ", msg.data->maxLen); if (isLastFragment) { // message complete HandleNextMessage (msg); m_IncompleteMessages.erase (it); } else { msg.nextFragmentNum++; HandleOutOfSequenceFragments (msgID, msg); } } else { LogPrint (eLogError, "TunnelMessage: Fragment ", m.nextFragmentNum, " of message ", msgID, "exceeds max I2NP message size, message dropped"); m_IncompleteMessages.erase (it); } } else { LogPrint (eLogWarning, "TunnelMessage: Unexpected fragment ", (int)m.nextFragmentNum, " instead ", (int)msg.nextFragmentNum, " of message ", msgID, ", saved"); AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); } } else { LogPrint (eLogWarning, "TunnelMessage: First fragment of message ", msgID, " not found, saved"); AddOutOfSequenceFragment (msgID, m.nextFragmentNum, isLastFragment, m.data); } } void TunnelEndpoint::AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data) { if (!m_OutOfSequenceFragments.insert ({{msgID, fragmentNum}, {isLastFragment, data, i2p::util::GetMillisecondsSinceEpoch () }}).second) LogPrint (eLogInfo, "TunnelMessage: duplicate out-of-sequence fragment ", fragmentNum, " of message ", msgID); } void TunnelEndpoint::HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg) { while (ConcatNextOutOfSequenceFragment (msgID, msg)) { if (!msg.nextFragmentNum) // message complete { HandleNextMessage (msg); m_IncompleteMessages.erase (msgID); break; } } } bool TunnelEndpoint::ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg) { auto it = m_OutOfSequenceFragments.find ({msgID, msg.nextFragmentNum}); if (it != m_OutOfSequenceFragments.end ()) { LogPrint (eLogDebug, "TunnelMessage: Out-of-sequence fragment ", (int)msg.nextFragmentNum, " of message ", msgID, " found"); size_t size = it->second.data->GetLength (); if (msg.data->len + size > msg.data->maxLen) { LogPrint (eLogWarning, "TunnelMessage: Tunnel endpoint I2NP message size ", msg.data->maxLen, " is not enough"); auto newMsg = NewI2NPMessage (); *newMsg = *(msg.data); msg.data = newMsg; } if (msg.data->Concat (it->second.data->GetBuffer (), size) < size) // concatenate out-of-sync fragment LogPrint (eLogError, "TunnelMessage: Tunnel endpoint I2NP buffer overflow ", msg.data->maxLen); if (it->second.isLastFragment) // message complete msg.nextFragmentNum = 0; else msg.nextFragmentNum++; m_OutOfSequenceFragments.erase (it); return true; } return false; } void TunnelEndpoint::HandleNextMessage (const TunnelMessageBlock& msg) { if (!m_IsInbound && msg.data->IsExpired ()) { LogPrint (eLogInfo, "TunnelMessage: message expired"); return; } uint8_t typeID = msg.data->GetTypeID (); LogPrint (eLogDebug, "TunnelMessage: handle fragment of ", msg.data->GetLength (), " bytes, msg type ", (int)typeID); // catch RI or reply with new list of routers if ((IsRouterInfoMsg (msg.data) || typeID == eI2NPDatabaseSearchReply) && !m_IsInbound && msg.deliveryType != eDeliveryTypeLocal) i2p::data::netdb.PostI2NPMsg (CopyI2NPMessage (msg.data)); switch (msg.deliveryType) { case eDeliveryTypeLocal: i2p::HandleI2NPMessage (msg.data); break; case eDeliveryTypeTunnel: if (!m_IsInbound) // outbound transit tunnel i2p::transport::transports.SendMessage (msg.hash, i2p::CreateTunnelGatewayMsg (msg.tunnelID, msg.data)); else LogPrint (eLogError, "TunnelMessage: Delivery type 'tunnel' arrived from an inbound tunnel, dropped"); break; case eDeliveryTypeRouter: if (!m_IsInbound) // outbound transit tunnel i2p::transport::transports.SendMessage (msg.hash, msg.data); else // we shouldn't send this message. possible leakage LogPrint (eLogError, "TunnelMessage: Delivery type 'router' arrived from an inbound tunnel, dropped"); break; default: LogPrint (eLogError, "TunnelMessage: Unknown delivery type ", (int)msg.deliveryType); }; } void TunnelEndpoint::Cleanup () { auto ts = i2p::util::GetMillisecondsSinceEpoch (); // out-of-sequence fragments for (auto it = m_OutOfSequenceFragments.begin (); it != m_OutOfSequenceFragments.end ();) { if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) it = m_OutOfSequenceFragments.erase (it); else ++it; } // incomplete messages for (auto it = m_IncompleteMessages.begin (); it != m_IncompleteMessages.end ();) { if (ts > it->second.receiveTime + i2p::I2NP_MESSAGE_EXPIRATION_TIMEOUT) it = m_IncompleteMessages.erase (it); else ++it; } } } } i2pd-2.17.0/libi2pd/TunnelEndpoint.h000066400000000000000000000030421321131324000170700ustar00rootroot00000000000000#ifndef TUNNEL_ENDPOINT_H__ #define TUNNEL_ENDPOINT_H__ #include #include #include #include "I2NPProtocol.h" #include "TunnelBase.h" namespace i2p { namespace tunnel { class TunnelEndpoint { struct TunnelMessageBlockEx: public TunnelMessageBlock { uint64_t receiveTime; // milliseconds since epoch uint8_t nextFragmentNum; }; struct Fragment { bool isLastFragment; std::shared_ptr data; uint64_t receiveTime; // milliseconds since epoch }; public: TunnelEndpoint (bool isInbound): m_IsInbound (isInbound), m_NumReceivedBytes (0) {}; ~TunnelEndpoint (); size_t GetNumReceivedBytes () const { return m_NumReceivedBytes; }; void Cleanup (); void HandleDecryptedTunnelDataMsg (std::shared_ptr msg); private: void HandleFollowOnFragment (uint32_t msgID, bool isLastFragment, const TunnelMessageBlockEx& m); void HandleNextMessage (const TunnelMessageBlock& msg); void AddOutOfSequenceFragment (uint32_t msgID, uint8_t fragmentNum, bool isLastFragment, std::shared_ptr data); bool ConcatNextOutOfSequenceFragment (uint32_t msgID, TunnelMessageBlockEx& msg); // true if something added void HandleOutOfSequenceFragments (uint32_t msgID, TunnelMessageBlockEx& msg); private: std::map m_IncompleteMessages; std::map, Fragment> m_OutOfSequenceFragments; // (msgID, fragment#)->fragment bool m_IsInbound; size_t m_NumReceivedBytes; }; } } #endif i2pd-2.17.0/libi2pd/TunnelGateway.cpp000066400000000000000000000157511321131324000172560ustar00rootroot00000000000000#include #include "Crypto.h" #include "I2PEndian.h" #include "Log.h" #include "RouterContext.h" #include "Transports.h" #include "TunnelGateway.h" namespace i2p { namespace tunnel { TunnelGatewayBuffer::TunnelGatewayBuffer (): m_CurrentTunnelDataMsg (nullptr), m_RemainingSize (0) { RAND_bytes (m_NonZeroRandomBuffer, TUNNEL_DATA_MAX_PAYLOAD_SIZE); for (size_t i = 0; i < TUNNEL_DATA_MAX_PAYLOAD_SIZE; i++) if (!m_NonZeroRandomBuffer[i]) m_NonZeroRandomBuffer[i] = 1; } TunnelGatewayBuffer::~TunnelGatewayBuffer () { ClearTunnelDataMsgs (); } void TunnelGatewayBuffer::PutI2NPMsg (const TunnelMessageBlock& block) { bool messageCreated = false; if (!m_CurrentTunnelDataMsg) { CreateCurrentTunnelDataMessage (); messageCreated = true; } // create delivery instructions uint8_t di[43]; // max delivery instruction length is 43 for tunnel size_t diLen = 1;// flag if (block.deliveryType != eDeliveryTypeLocal) // tunnel or router { if (block.deliveryType == eDeliveryTypeTunnel) { htobe32buf (di + diLen, block.tunnelID); diLen += 4; // tunnelID } memcpy (di + diLen, block.hash, 32); diLen += 32; //len } di[0] = block.deliveryType << 5; // set delivery type // create fragments const std::shared_ptr & msg = block.data; size_t fullMsgLen = diLen + msg->GetLength () + 2; // delivery instructions + payload + 2 bytes length if (fullMsgLen <= m_RemainingSize) { // message fits. First and last fragment htobe16buf (di + diLen, msg->GetLength ()); diLen += 2; // size memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len, di, diLen); memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len + diLen, msg->GetBuffer (), msg->GetLength ()); m_CurrentTunnelDataMsg->len += diLen + msg->GetLength (); m_RemainingSize -= diLen + msg->GetLength (); if (!m_RemainingSize) CompleteCurrentTunnelDataMessage (); } else { if (!messageCreated) // check if we should complete previous message { size_t numFollowOnFragments = fullMsgLen / TUNNEL_DATA_MAX_PAYLOAD_SIZE; // length of bytes don't fit full tunnel message // every follow-on fragment adds 7 bytes size_t nonFit = (fullMsgLen + numFollowOnFragments*7) % TUNNEL_DATA_MAX_PAYLOAD_SIZE; if (!nonFit || nonFit > m_RemainingSize) { CompleteCurrentTunnelDataMessage (); CreateCurrentTunnelDataMessage (); } } if (diLen + 6 <= m_RemainingSize) { // delivery instructions fit uint32_t msgID; memcpy (&msgID, msg->GetHeader () + I2NP_HEADER_MSGID_OFFSET, 4); // in network bytes order size_t size = m_RemainingSize - diLen - 6; // 6 = 4 (msgID) + 2 (size) // first fragment di[0] |= 0x08; // fragmented htobuf32 (di + diLen, msgID); diLen += 4; // Message ID htobe16buf (di + diLen, size); diLen += 2; // size memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len, di, diLen); memcpy (m_CurrentTunnelDataMsg->buf + m_CurrentTunnelDataMsg->len + diLen, msg->GetBuffer (), size); m_CurrentTunnelDataMsg->len += diLen + size; CompleteCurrentTunnelDataMessage (); // follow on fragments int fragmentNumber = 1; while (size < msg->GetLength ()) { CreateCurrentTunnelDataMessage (); uint8_t * buf = m_CurrentTunnelDataMsg->GetBuffer (); buf[0] = 0x80 | (fragmentNumber << 1); // frag bool isLastFragment = false; size_t s = msg->GetLength () - size; if (s > TUNNEL_DATA_MAX_PAYLOAD_SIZE - 7) // 7 follow on instructions s = TUNNEL_DATA_MAX_PAYLOAD_SIZE - 7; else // last fragment { buf[0] |= 0x01; isLastFragment = true; } htobuf32 (buf + 1, msgID); //Message ID htobe16buf (buf + 5, s); // size memcpy (buf + 7, msg->GetBuffer () + size, s); m_CurrentTunnelDataMsg->len += s+7; if (isLastFragment) { if(m_RemainingSize < (s+7)) { LogPrint (eLogError, "TunnelGateway: remaining size overflow: ", m_RemainingSize, " < ", s+7); } else { m_RemainingSize -= s+7; if (m_RemainingSize == 0) CompleteCurrentTunnelDataMessage (); } } else CompleteCurrentTunnelDataMessage (); size += s; fragmentNumber++; } } else { // delivery instructions don't fit. Create new message CompleteCurrentTunnelDataMessage (); PutI2NPMsg (block); // don't delete msg because it's taken care inside } } } void TunnelGatewayBuffer::ClearTunnelDataMsgs () { m_TunnelDataMsgs.clear (); m_CurrentTunnelDataMsg = nullptr; } void TunnelGatewayBuffer::CreateCurrentTunnelDataMessage () { m_CurrentTunnelDataMsg = nullptr; m_CurrentTunnelDataMsg = NewI2NPShortMessage (); m_CurrentTunnelDataMsg->Align (12); // we reserve space for padding m_CurrentTunnelDataMsg->offset += TUNNEL_DATA_MSG_SIZE + I2NP_HEADER_SIZE; m_CurrentTunnelDataMsg->len = m_CurrentTunnelDataMsg->offset; m_RemainingSize = TUNNEL_DATA_MAX_PAYLOAD_SIZE; } void TunnelGatewayBuffer::CompleteCurrentTunnelDataMessage () { if (!m_CurrentTunnelDataMsg) return; uint8_t * payload = m_CurrentTunnelDataMsg->GetBuffer (); size_t size = m_CurrentTunnelDataMsg->len - m_CurrentTunnelDataMsg->offset; m_CurrentTunnelDataMsg->offset = m_CurrentTunnelDataMsg->len - TUNNEL_DATA_MSG_SIZE - I2NP_HEADER_SIZE; uint8_t * buf = m_CurrentTunnelDataMsg->GetPayload (); RAND_bytes (buf + 4, 16); // original IV memcpy (payload + size, buf + 4, 16); // copy IV for checksum uint8_t hash[32]; SHA256(payload, size+16, hash); memcpy (buf+20, hash, 4); // checksum payload[-1] = 0; // zero ptrdiff_t paddingSize = payload - buf - 25; // 25 = 24 + 1 if (paddingSize > 0) { // non-zero padding auto randomOffset = rand () % (TUNNEL_DATA_MAX_PAYLOAD_SIZE - paddingSize + 1); memcpy (buf + 24, m_NonZeroRandomBuffer + randomOffset, paddingSize); } // we can't fill message header yet because encryption is required m_TunnelDataMsgs.push_back (m_CurrentTunnelDataMsg); m_CurrentTunnelDataMsg = nullptr; } void TunnelGateway::SendTunnelDataMsg (const TunnelMessageBlock& block) { if (block.data) { PutTunnelDataMsg (block); SendBuffer (); } } void TunnelGateway::PutTunnelDataMsg (const TunnelMessageBlock& block) { if (block.data) m_Buffer.PutI2NPMsg (block); } void TunnelGateway::SendBuffer () { m_Buffer.CompleteCurrentTunnelDataMessage (); std::vector > newTunnelMsgs; const auto& tunnelDataMsgs = m_Buffer.GetTunnelDataMsgs (); for (auto& tunnelMsg : tunnelDataMsgs) { auto newMsg = CreateEmptyTunnelDataMsg (); m_Tunnel->EncryptTunnelMsg (tunnelMsg, newMsg); htobe32buf (newMsg->GetPayload (), m_Tunnel->GetNextTunnelID ()); newMsg->FillI2NPMessageHeader (eI2NPTunnelData); newTunnelMsgs.push_back (newMsg); m_NumSentBytes += TUNNEL_DATA_MSG_SIZE; } m_Buffer.ClearTunnelDataMsgs (); i2p::transport::transports.SendMessages (m_Tunnel->GetNextIdentHash (), newTunnelMsgs); } } } i2pd-2.17.0/libi2pd/TunnelGateway.h000066400000000000000000000024111321131324000167100ustar00rootroot00000000000000#ifndef TUNNEL_GATEWAY_H__ #define TUNNEL_GATEWAY_H__ #include #include #include #include "I2NPProtocol.h" #include "TunnelBase.h" namespace i2p { namespace tunnel { class TunnelGatewayBuffer { public: TunnelGatewayBuffer (); ~TunnelGatewayBuffer (); void PutI2NPMsg (const TunnelMessageBlock& block); const std::vector >& GetTunnelDataMsgs () const { return m_TunnelDataMsgs; }; void ClearTunnelDataMsgs (); void CompleteCurrentTunnelDataMessage (); private: void CreateCurrentTunnelDataMessage (); private: std::vector > m_TunnelDataMsgs; std::shared_ptr m_CurrentTunnelDataMsg; size_t m_RemainingSize; uint8_t m_NonZeroRandomBuffer[TUNNEL_DATA_MAX_PAYLOAD_SIZE]; }; class TunnelGateway { public: TunnelGateway (TunnelBase * tunnel): m_Tunnel (tunnel), m_NumSentBytes (0) {}; void SendTunnelDataMsg (const TunnelMessageBlock& block); void PutTunnelDataMsg (const TunnelMessageBlock& block); void SendBuffer (); size_t GetNumSentBytes () const { return m_NumSentBytes; }; private: TunnelBase * m_Tunnel; TunnelGatewayBuffer m_Buffer; size_t m_NumSentBytes; }; } } #endif i2pd-2.17.0/libi2pd/TunnelPool.cpp000066400000000000000000000427621321131324000165700ustar00rootroot00000000000000#include #include "I2PEndian.h" #include "Crypto.h" #include "Tunnel.h" #include "NetDb.hpp" #include "Timestamp.h" #include "Garlic.h" #include "Transports.h" #include "Log.h" #include "Tunnel.h" #include "TunnelPool.h" #include "Destination.h" #ifdef WITH_EVENTS #include "Event.h" #endif namespace i2p { namespace tunnel { TunnelPool::TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels): m_NumInboundHops (numInboundHops), m_NumOutboundHops (numOutboundHops), m_NumInboundTunnels (numInboundTunnels), m_NumOutboundTunnels (numOutboundTunnels), m_IsActive (true), m_CustomPeerSelector(nullptr) { } TunnelPool::~TunnelPool () { DetachTunnels (); } void TunnelPool::SetExplicitPeers (std::shared_ptr > explicitPeers) { m_ExplicitPeers = explicitPeers; if (m_ExplicitPeers) { int size = m_ExplicitPeers->size (); if (m_NumInboundHops > size) { m_NumInboundHops = size; LogPrint (eLogInfo, "Tunnels: Inbound tunnel length has beed adjusted to ", size, " for explicit peers"); } if (m_NumOutboundHops > size) { m_NumOutboundHops = size; LogPrint (eLogInfo, "Tunnels: Outbound tunnel length has beed adjusted to ", size, " for explicit peers"); } m_NumInboundTunnels = 1; m_NumOutboundTunnels = 1; } } void TunnelPool::DetachTunnels () { { std::unique_lock l(m_InboundTunnelsMutex); for (auto& it: m_InboundTunnels) it->SetTunnelPool (nullptr); m_InboundTunnels.clear (); } { std::unique_lock l(m_OutboundTunnelsMutex); for (auto& it: m_OutboundTunnels) it->SetTunnelPool (nullptr); m_OutboundTunnels.clear (); } m_Tests.clear (); } void TunnelPool::TunnelCreated (std::shared_ptr createdTunnel) { if (!m_IsActive) return; { #ifdef WITH_EVENTS EmitTunnelEvent("tunnels.created", createdTunnel); #endif std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.insert (createdTunnel); } if (m_LocalDestination) m_LocalDestination->SetLeaseSetUpdated (); OnTunnelBuildResult(createdTunnel, eBuildResultOkay); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) { if (expiredTunnel) { #ifdef WITH_EVENTS EmitTunnelEvent("tunnels.expired", expiredTunnel); #endif expiredTunnel->SetTunnelPool (nullptr); for (auto& it: m_Tests) if (it.second.second == expiredTunnel) it.second.second = nullptr; std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.erase (expiredTunnel); } } void TunnelPool::TunnelCreated (std::shared_ptr createdTunnel) { if (!m_IsActive) return; { #ifdef WITH_EVENTS EmitTunnelEvent("tunnels.created", createdTunnel); #endif std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.insert (createdTunnel); } OnTunnelBuildResult(createdTunnel, eBuildResultOkay); //CreatePairedInboundTunnel (createdTunnel); } void TunnelPool::TunnelExpired (std::shared_ptr expiredTunnel) { if (expiredTunnel) { #ifdef WITH_EVENTS EmitTunnelEvent("tunnels.expired", expiredTunnel); #endif expiredTunnel->SetTunnelPool (nullptr); for (auto& it: m_Tests) if (it.second.first == expiredTunnel) it.second.first = nullptr; std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.erase (expiredTunnel); } } std::vector > TunnelPool::GetInboundTunnels (int num) const { std::vector > v; int i = 0; std::unique_lock l(m_InboundTunnelsMutex); for (const auto& it : m_InboundTunnels) { if (i >= num) break; if (it->IsEstablished ()) { v.push_back (it); i++; } } return v; } std::shared_ptr TunnelPool::GetNextOutboundTunnel (std::shared_ptr excluded) const { std::unique_lock l(m_OutboundTunnelsMutex); return GetNextTunnel (m_OutboundTunnels, excluded); } std::shared_ptr TunnelPool::GetNextInboundTunnel (std::shared_ptr excluded) const { std::unique_lock l(m_InboundTunnelsMutex); return GetNextTunnel (m_InboundTunnels, excluded); } template typename TTunnels::value_type TunnelPool::GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const { if (tunnels.empty ()) return nullptr; uint32_t ind = rand () % (tunnels.size ()/2 + 1), i = 0; typename TTunnels::value_type tunnel = nullptr; for (const auto& it: tunnels) { if (it->IsEstablished () && it != excluded) { if(HasLatencyRequirement() && it->LatencyIsKnown() && !it->LatencyFitsRange(m_MinLatency, m_MaxLatency)) { i ++; continue; } tunnel = it; i++; } if (i > ind && tunnel) break; } if(HasLatencyRequirement() && !tunnel) { ind = rand () % (tunnels.size ()/2 + 1), i = 0; for (const auto& it: tunnels) { if (it->IsEstablished () && it != excluded) { tunnel = it; i++; } if (i > ind && tunnel) break; } } if (!tunnel && excluded && excluded->IsEstablished ()) tunnel = excluded; return tunnel; } std::shared_ptr TunnelPool::GetNewOutboundTunnel (std::shared_ptr old) const { if (old && old->IsEstablished ()) return old; std::shared_ptr tunnel; if (old) { std::unique_lock l(m_OutboundTunnelsMutex); for (const auto& it: m_OutboundTunnels) if (it->IsEstablished () && old->GetEndpointIdentHash () == it->GetEndpointIdentHash ()) { tunnel = it; break; } } if (!tunnel) tunnel = GetNextOutboundTunnel (); return tunnel; } void TunnelPool::CreateTunnels () { int num = 0; { std::unique_lock l(m_OutboundTunnelsMutex); for (const auto& it : m_OutboundTunnels) if (it->IsEstablished ()) num++; } for (int i = num; i < m_NumOutboundTunnels; i++) CreateOutboundTunnel (); num = 0; { std::unique_lock l(m_InboundTunnelsMutex); for (const auto& it : m_InboundTunnels) if (it->IsEstablished ()) num++; } for (int i = num; i < m_NumInboundTunnels; i++) CreateInboundTunnel (); if (num < m_NumInboundTunnels && m_NumInboundHops <= 0 && m_LocalDestination) // zero hops IB m_LocalDestination->SetLeaseSetUpdated (); // update LeaseSet immediately } void TunnelPool::TestTunnels () { decltype(m_Tests) tests; { std::unique_lock l(m_TestsMutex); tests.swap(m_Tests); } for (auto& it: tests) { LogPrint (eLogWarning, "Tunnels: test of tunnel ", it.first, " failed"); // if test failed again with another tunnel we consider it failed if (it.second.first) { if (it.second.first->GetState () == eTunnelStateTestFailed) { it.second.first->SetState (eTunnelStateFailed); std::unique_lock l(m_OutboundTunnelsMutex); m_OutboundTunnels.erase (it.second.first); } else it.second.first->SetState (eTunnelStateTestFailed); } if (it.second.second) { if (it.second.second->GetState () == eTunnelStateTestFailed) { it.second.second->SetState (eTunnelStateFailed); { std::unique_lock l(m_InboundTunnelsMutex); m_InboundTunnels.erase (it.second.second); } if (m_LocalDestination) m_LocalDestination->SetLeaseSetUpdated (); } else it.second.second->SetState (eTunnelStateTestFailed); } } // new tests auto it1 = m_OutboundTunnels.begin (); auto it2 = m_InboundTunnels.begin (); while (it1 != m_OutboundTunnels.end () && it2 != m_InboundTunnels.end ()) { bool failed = false; if ((*it1)->IsFailed ()) { failed = true; ++it1; } if ((*it2)->IsFailed ()) { failed = true; ++it2; } if (!failed) { uint32_t msgID; RAND_bytes ((uint8_t *)&msgID, 4); { std::unique_lock l(m_TestsMutex); m_Tests[msgID] = std::make_pair (*it1, *it2); } (*it1)->SendTunnelDataMsg ((*it2)->GetNextIdentHash (), (*it2)->GetNextTunnelID (), CreateDeliveryStatusMsg (msgID)); ++it1; ++it2; } } } void TunnelPool::ProcessGarlicMessage (std::shared_ptr msg) { if (m_LocalDestination) m_LocalDestination->ProcessGarlicMessage (msg); else LogPrint (eLogWarning, "Tunnels: local destination doesn't exist, dropped"); } void TunnelPool::ProcessDeliveryStatus (std::shared_ptr msg) { const uint8_t * buf = msg->GetPayload (); uint32_t msgID = bufbe32toh (buf); buf += 4; uint64_t timestamp = bufbe64toh (buf); decltype(m_Tests)::mapped_type test; bool found = false; { std::unique_lock l(m_TestsMutex); auto it = m_Tests.find (msgID); if (it != m_Tests.end ()) { found = true; test = it->second; m_Tests.erase (it); } } if (found) { // restore from test failed state if any if (test.first->GetState () == eTunnelStateTestFailed) test.first->SetState (eTunnelStateEstablished); if (test.second->GetState () == eTunnelStateTestFailed) test.second->SetState (eTunnelStateEstablished); uint64_t dlt = i2p::util::GetMillisecondsSinceEpoch () - timestamp; LogPrint (eLogDebug, "Tunnels: test of ", msgID, " successful. ", dlt, " milliseconds"); // update latency uint64_t latency = dlt / 2; test.first->AddLatencySample(latency); test.second->AddLatencySample(latency); } else { if (m_LocalDestination) m_LocalDestination->ProcessDeliveryStatusMessage (msg); else LogPrint (eLogWarning, "Tunnels: Local destination doesn't exist, dropped"); } } std::shared_ptr TunnelPool::SelectNextHop (std::shared_ptr prevHop) const { bool isExploratory = (i2p::tunnel::tunnels.GetExploratoryPool () == shared_from_this ()); auto hop = isExploratory ? i2p::data::netdb.GetRandomRouter (prevHop): i2p::data::netdb.GetHighBandwidthRandomRouter (prevHop); if (!hop || hop->GetProfile ()->IsBad ()) hop = i2p::data::netdb.GetRandomRouter (prevHop); return hop; } bool StandardSelectPeers(Path & peers, int numHops, bool inbound, SelectHopFunc nextHop) { auto prevHop = i2p::context.GetSharedRouterInfo (); if(i2p::transport::transports.RoutesRestricted()) { /** if routes are restricted prepend trusted first hop */ auto hop = i2p::transport::transports.GetRestrictedPeer(); if(!hop) return false; peers.push_back(hop->GetRouterIdentity()); prevHop = hop; } else if (i2p::transport::transports.GetNumPeers () > 25) { auto r = i2p::transport::transports.GetRandomPeer (); if (r && !r->GetProfile ()->IsBad ()) { prevHop = r; peers.push_back (r->GetRouterIdentity ()); numHops--; } } for(int i = 0; i < numHops; i++ ) { auto hop = nextHop (prevHop); if (!hop) { LogPrint (eLogError, "Tunnels: Can't select next hop for ", prevHop->GetIdentHashBase64 ()); return false; } prevHop = hop; peers.push_back (hop->GetRouterIdentity ()); } return true; } bool TunnelPool::SelectPeers (std::vector >& peers, bool isInbound) { int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; // peers is empty if (numHops <= 0) return true; // custom peer selector in use ? { std::lock_guard lock(m_CustomPeerSelectorMutex); if (m_CustomPeerSelector) return m_CustomPeerSelector->SelectPeers(peers, numHops, isInbound); } // explicit peers in use if (m_ExplicitPeers) return SelectExplicitPeers (peers, isInbound); return StandardSelectPeers(peers, numHops, isInbound, std::bind(&TunnelPool::SelectNextHop, this, std::placeholders::_1)); } bool TunnelPool::SelectExplicitPeers (std::vector >& peers, bool isInbound) { int size = m_ExplicitPeers->size (); std::vector peerIndicies; for (int i = 0; i < size; i++) peerIndicies.push_back(i); std::random_shuffle (peerIndicies.begin(), peerIndicies.end()); int numHops = isInbound ? m_NumInboundHops : m_NumOutboundHops; for (int i = 0; i < numHops; i++) { auto& ident = (*m_ExplicitPeers)[peerIndicies[i]]; auto r = i2p::data::netdb.FindRouter (ident); if (r) peers.push_back (r->GetRouterIdentity ()); else { LogPrint (eLogInfo, "Tunnels: Can't find router for ", ident.ToBase64 ()); i2p::data::netdb.RequestDestination (ident); return false; } } return true; } void TunnelPool::CreateInboundTunnel () { auto outboundTunnel = GetNextOutboundTunnel (); if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Creating destination inbound tunnel..."); std::vector > peers; if (SelectPeers (peers, true)) { std::shared_ptr config; if (m_NumInboundHops > 0) { std::reverse (peers.begin (), peers.end ()); config = std::make_shared (peers); } auto tunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } else LogPrint (eLogError, "Tunnels: Can't create inbound tunnel, no peers available"); } void TunnelPool::RecreateInboundTunnel (std::shared_ptr tunnel) { auto outboundTunnel = GetNextOutboundTunnel (); if (!outboundTunnel) outboundTunnel = tunnels.GetNextOutboundTunnel (); LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel..."); std::shared_ptr config; if (m_NumInboundHops > 0) config = std::make_shared(tunnel->GetPeers ()); auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel); newTunnel->SetTunnelPool (shared_from_this()); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); } void TunnelPool::CreateOutboundTunnel () { auto inboundTunnel = GetNextInboundTunnel (); if (!inboundTunnel) inboundTunnel = tunnels.GetNextInboundTunnel (); if (inboundTunnel) { LogPrint (eLogDebug, "Tunnels: Creating destination outbound tunnel..."); std::vector > peers; if (SelectPeers (peers, false)) { std::shared_ptr config; if (m_NumOutboundHops > 0) config = std::make_shared(peers, inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); auto tunnel = tunnels.CreateOutboundTunnel (config); tunnel->SetTunnelPool (shared_from_this ()); if (tunnel->IsEstablished ()) // zero hops TunnelCreated (tunnel); } else LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no peers available"); } else LogPrint (eLogError, "Tunnels: Can't create outbound tunnel, no inbound tunnels found"); } void TunnelPool::RecreateOutboundTunnel (std::shared_ptr tunnel) { auto inboundTunnel = GetNextInboundTunnel (); if (!inboundTunnel) inboundTunnel = tunnels.GetNextInboundTunnel (); if (inboundTunnel) { LogPrint (eLogDebug, "Tunnels: Re-creating destination outbound tunnel..."); std::shared_ptr config; if (m_NumOutboundHops > 0) config = std::make_shared(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ()); auto newTunnel = tunnels.CreateOutboundTunnel (config); newTunnel->SetTunnelPool (shared_from_this ()); if (newTunnel->IsEstablished ()) // zero hops TunnelCreated (newTunnel); } else LogPrint (eLogDebug, "Tunnels: Can't re-create outbound tunnel, no inbound tunnels found"); } void TunnelPool::CreatePairedInboundTunnel (std::shared_ptr outboundTunnel) { LogPrint (eLogDebug, "Tunnels: Creating paired inbound tunnel..."); auto tunnel = tunnels.CreateInboundTunnel (std::make_shared(outboundTunnel->GetInvertedPeers ()), outboundTunnel); tunnel->SetTunnelPool (shared_from_this ()); } void TunnelPool::SetCustomPeerSelector(ITunnelPeerSelector * selector) { std::lock_guard lock(m_CustomPeerSelectorMutex); m_CustomPeerSelector = selector; } void TunnelPool::UnsetCustomPeerSelector() { SetCustomPeerSelector(nullptr); } bool TunnelPool::HasCustomPeerSelector() { std::lock_guard lock(m_CustomPeerSelectorMutex); return m_CustomPeerSelector != nullptr; } std::shared_ptr TunnelPool::GetLowestLatencyInboundTunnel(std::shared_ptr exclude) const { std::shared_ptr tun = nullptr; std::unique_lock lock(m_InboundTunnelsMutex); uint64_t min = 1000000; for (const auto & itr : m_InboundTunnels) { if(!itr->LatencyIsKnown()) continue; auto l = itr->GetMeanLatency(); if (l >= min) continue; tun = itr; if(tun == exclude) continue; min = l; } return tun; } std::shared_ptr TunnelPool::GetLowestLatencyOutboundTunnel(std::shared_ptr exclude) const { std::shared_ptr tun = nullptr; std::unique_lock lock(m_OutboundTunnelsMutex); uint64_t min = 1000000; for (const auto & itr : m_OutboundTunnels) { if(!itr->LatencyIsKnown()) continue; auto l = itr->GetMeanLatency(); if (l >= min) continue; tun = itr; if(tun == exclude) continue; min = l; } return tun; } void TunnelPool::OnTunnelBuildResult(std::shared_ptr tunnel, TunnelBuildResult result) { auto peers = tunnel->GetPeers(); if(m_CustomPeerSelector) m_CustomPeerSelector->OnBuildResult(peers, tunnel->IsInbound(), result); } } } i2pd-2.17.0/libi2pd/TunnelPool.h000066400000000000000000000135111321131324000162230ustar00rootroot00000000000000#ifndef TUNNEL_POOL__ #define TUNNEL_POOL__ #include #include #include #include #include #include #include "Identity.h" #include "LeaseSet.h" #include "RouterInfo.h" #include "I2NPProtocol.h" #include "TunnelBase.h" #include "RouterContext.h" #include "Garlic.h" namespace i2p { namespace tunnel { class Tunnel; class InboundTunnel; class OutboundTunnel; enum TunnelBuildResult { eBuildResultOkay, // tunnel was built okay eBuildResultRejected, // tunnel build was explicitly rejected eBuildResultTimeout // tunnel build timed out }; typedef std::shared_ptr Peer; typedef std::vector Path; /** interface for custom tunnel peer selection algorithm */ struct ITunnelPeerSelector { virtual ~ITunnelPeerSelector() {}; virtual bool SelectPeers(Path & peers, int hops, bool isInbound) = 0; virtual bool OnBuildResult(const Path & peers, bool isInbound, TunnelBuildResult result) = 0; }; typedef std::function(std::shared_ptr)> SelectHopFunc; // standard peer selection algorithm bool StandardSelectPeers(Path & path, int hops, bool inbound, SelectHopFunc nextHop); class TunnelPool: public std::enable_shared_from_this // per local destination { public: TunnelPool (int numInboundHops, int numOutboundHops, int numInboundTunnels, int numOutboundTunnels); ~TunnelPool (); std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; void SetLocalDestination (std::shared_ptr destination) { m_LocalDestination = destination; }; void SetExplicitPeers (std::shared_ptr > explicitPeers); void CreateTunnels (); void TunnelCreated (std::shared_ptr createdTunnel); void TunnelExpired (std::shared_ptr expiredTunnel); void TunnelCreated (std::shared_ptr createdTunnel); void TunnelExpired (std::shared_ptr expiredTunnel); void RecreateInboundTunnel (std::shared_ptr tunnel); void RecreateOutboundTunnel (std::shared_ptr tunnel); std::vector > GetInboundTunnels (int num) const; std::shared_ptr GetNextOutboundTunnel (std::shared_ptr excluded = nullptr) const; std::shared_ptr GetNextInboundTunnel (std::shared_ptr excluded = nullptr) const; std::shared_ptr GetNewOutboundTunnel (std::shared_ptr old) const; void TestTunnels (); void ProcessGarlicMessage (std::shared_ptr msg); void ProcessDeliveryStatus (std::shared_ptr msg); bool IsActive () const { return m_IsActive; }; void SetActive (bool isActive) { m_IsActive = isActive; }; void DetachTunnels (); int GetNumInboundTunnels () const { return m_NumInboundTunnels; }; int GetNumOutboundTunnels () const { return m_NumOutboundTunnels; }; void SetCustomPeerSelector(ITunnelPeerSelector * selector); void UnsetCustomPeerSelector(); bool HasCustomPeerSelector(); /** @brief make this tunnel pool yield tunnels that fit latency range [min, max] */ void RequireLatency(uint64_t min, uint64_t max) { m_MinLatency = min; m_MaxLatency = max; } /** @brief return true if this tunnel pool has a latency requirement */ bool HasLatencyRequirement() const { return m_MinLatency > 0 && m_MaxLatency > 0; } /** @brief get the lowest latency tunnel in this tunnel pool regardless of latency requirements */ std::shared_ptr GetLowestLatencyInboundTunnel(std::shared_ptr exclude=nullptr) const; std::shared_ptr GetLowestLatencyOutboundTunnel(std::shared_ptr exclude=nullptr) const; void OnTunnelBuildResult(std::shared_ptr tunnel, TunnelBuildResult result); // for overriding tunnel peer selection std::shared_ptr SelectNextHop (std::shared_ptr prevHop) const; private: void CreateInboundTunnel (); void CreateOutboundTunnel (); void CreatePairedInboundTunnel (std::shared_ptr outboundTunnel); template typename TTunnels::value_type GetNextTunnel (TTunnels& tunnels, typename TTunnels::value_type excluded) const; bool SelectPeers (std::vector >& hops, bool isInbound); bool SelectExplicitPeers (std::vector >& hops, bool isInbound); private: std::shared_ptr m_LocalDestination; int m_NumInboundHops, m_NumOutboundHops, m_NumInboundTunnels, m_NumOutboundTunnels; std::shared_ptr > m_ExplicitPeers; mutable std::mutex m_InboundTunnelsMutex; std::set, TunnelCreationTimeCmp> m_InboundTunnels; // recent tunnel appears first mutable std::mutex m_OutboundTunnelsMutex; std::set, TunnelCreationTimeCmp> m_OutboundTunnels; mutable std::mutex m_TestsMutex; std::map, std::shared_ptr > > m_Tests; bool m_IsActive; std::mutex m_CustomPeerSelectorMutex; ITunnelPeerSelector * m_CustomPeerSelector; uint64_t m_MinLatency=0; // if > 0 this tunnel pool will try building tunnels with minimum latency by ms uint64_t m_MaxLatency=0; // if > 0 this tunnel pool will try building tunnels with maximum latency by ms public: // for HTTP only const decltype(m_OutboundTunnels)& GetOutboundTunnels () const { return m_OutboundTunnels; }; const decltype(m_InboundTunnels)& GetInboundTunnels () const { return m_InboundTunnels; }; }; } } #endif i2pd-2.17.0/libi2pd/api.cpp000066400000000000000000000071151321131324000152330ustar00rootroot00000000000000#include #include #include "Config.h" #include "Log.h" #include "NetDb.hpp" #include "Transports.h" #include "Tunnel.h" #include "RouterContext.h" #include "Identity.h" #include "Destination.h" #include "Crypto.h" #include "FS.h" #include "api.h" namespace i2p { namespace api { void InitI2P (int argc, char* argv[], const char * appName) { i2p::config::Init (); i2p::config::ParseCmdline (argc, argv, true); // ignore unknown options and help i2p::config::Finalize (); std::string datadir; i2p::config::GetOption("datadir", datadir); i2p::fs::SetAppName (appName); i2p::fs::DetectDataDir(datadir, false); i2p::fs::Init(); #if defined(__x86_64__) i2p::crypto::InitCrypto (false); #else i2p::crypto::InitCrypto (true); #endif int netID; i2p::config::GetOption("netid", netID); i2p::context.SetNetID (netID); i2p::context.Init (); } void TerminateI2P () { i2p::crypto::TerminateCrypto (); } void StartI2P (std::shared_ptr logStream) { if (logStream) i2p::log::Logger().SendTo (logStream); else i2p::log::Logger().SendTo (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log")); i2p::log::Logger().Start (); LogPrint(eLogInfo, "API: starting NetDB"); i2p::data::netdb.Start(); LogPrint(eLogInfo, "API: starting Transports"); i2p::transport::transports.Start(); LogPrint(eLogInfo, "API: starting Tunnels"); i2p::tunnel::tunnels.Start(); } void StopI2P () { LogPrint(eLogInfo, "API: shutting down"); LogPrint(eLogInfo, "API: stopping Tunnels"); i2p::tunnel::tunnels.Stop(); LogPrint(eLogInfo, "API: stopping Transports"); i2p::transport::transports.Stop(); LogPrint(eLogInfo, "API: stopping NetDB"); i2p::data::netdb.Stop(); i2p::log::Logger().Stop (); } void RunPeerTest () { i2p::transport::transports.PeerTest (); } std::shared_ptr CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { auto localDestination = std::make_shared (keys, isPublic, params); localDestination->Start (); return localDestination; } std::shared_ptr CreateLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, const std::map * params) { i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType); auto localDestination = std::make_shared (keys, isPublic, params); localDestination->Start (); return localDestination; } void DestroyLocalDestination (std::shared_ptr dest) { if (dest) dest->Stop (); } void RequestLeaseSet (std::shared_ptr dest, const i2p::data::IdentHash& remote) { if (dest) dest->RequestDestination (remote); } std::shared_ptr CreateStream (std::shared_ptr dest, const i2p::data::IdentHash& remote) { if (!dest) return nullptr; auto leaseSet = dest->FindLeaseSet (remote); if (leaseSet) { auto stream = dest->CreateStream (leaseSet); stream->Send (nullptr, 0); // connect return stream; } else { RequestLeaseSet (dest, remote); return nullptr; } } void AcceptStream (std::shared_ptr dest, const i2p::stream::StreamingDestination::Acceptor& acceptor) { if (dest) dest->AcceptStreams (acceptor); } void DestroyStream (std::shared_ptr stream) { if (stream) stream->Close (); } } } i2pd-2.17.0/libi2pd/api.h000066400000000000000000000031051321131324000146730ustar00rootroot00000000000000#ifndef API_H__ #define API_H__ #include #include #include "Identity.h" #include "Destination.h" #include "Streaming.h" namespace i2p { namespace api { // initialization start and stop void InitI2P (int argc, char* argv[], const char * appName); void TerminateI2P (); void StartI2P (std::shared_ptr logStream = nullptr); // write system log to logStream, if not specified to .log in application's folder void StopI2P (); void RunPeerTest (); // should be called after UPnP // destinations std::shared_ptr CreateLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); std::shared_ptr CreateLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, const std::map * params = nullptr); // transient destinations usually not published void DestroyLocalDestination (std::shared_ptr dest); // streams void RequestLeaseSet (std::shared_ptr dest, const i2p::data::IdentHash& remote); std::shared_ptr CreateStream (std::shared_ptr dest, const i2p::data::IdentHash& remote); void AcceptStream (std::shared_ptr dest, const i2p::stream::StreamingDestination::Acceptor& acceptor); void DestroyStream (std::shared_ptr stream); } } #endif i2pd-2.17.0/libi2pd/util.cpp000066400000000000000000000237061321131324000154430ustar00rootroot00000000000000#include #include #include #include "util.h" #include "Log.h" #ifdef WIN32 #include #include #include #include #include #include #include #ifdef _MSC_VER #pragma comment(lib, "IPHLPAPI.lib") #endif // _MSC_VER #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) #define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) int inet_pton(int af, const char *src, void *dst) { /* This function was written by Petar Korponai?. See http://stackoverflow.com/questions/15660203/inet-pton-identifier-not-found */ struct sockaddr_storage ss; int size = sizeof (ss); char src_copy[INET6_ADDRSTRLEN + 1]; ZeroMemory (&ss, sizeof (ss)); strncpy (src_copy, src, INET6_ADDRSTRLEN + 1); src_copy[INET6_ADDRSTRLEN] = 0; if (WSAStringToAddress (src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0) { switch (af) { case AF_INET: *(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr; return 1; case AF_INET6: *(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr; return 1; } } return 0; } #else /* !WIN32 => UNIX */ #include #include #endif namespace i2p { namespace util { namespace net { #ifdef WIN32 int GetMTUWindowsIpv4(sockaddr_in inputAddress, int fallback) { ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; if(GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) { FREE(pAddresses); pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); } DWORD dwRetVal = GetAdaptersAddresses( AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); if(dwRetVal != NO_ERROR) { LogPrint(eLogError, "NetIface: GetMTU(): enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return fallback; } pCurrAddresses = pAddresses; while(pCurrAddresses) { PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; if(pUnicast == nullptr) { LogPrint(eLogError, "NetIface: GetMTU(): not a unicast ipv4 address, this is not supported"); } for(int i = 0; pUnicast != nullptr; ++i) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in* localInterfaceAddress = (sockaddr_in*) lpAddr; if(localInterfaceAddress->sin_addr.S_un.S_addr == inputAddress.sin_addr.S_un.S_addr) { auto result = pAddresses->Mtu; FREE(pAddresses); return result; } pUnicast = pUnicast->Next; } pCurrAddresses = pCurrAddresses->Next; } LogPrint(eLogError, "NetIface: GetMTU(): no usable unicast ipv4 addresses found"); FREE(pAddresses); return fallback; } int GetMTUWindowsIpv6(sockaddr_in6 inputAddress, int fallback) { ULONG outBufLen = 0; PIP_ADAPTER_ADDRESSES pAddresses = nullptr; PIP_ADAPTER_ADDRESSES pCurrAddresses = nullptr; PIP_ADAPTER_UNICAST_ADDRESS pUnicast = nullptr; if(GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) { FREE(pAddresses); pAddresses = (IP_ADAPTER_ADDRESSES*) MALLOC(outBufLen); } DWORD dwRetVal = GetAdaptersAddresses( AF_INET6, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddresses, &outBufLen ); if(dwRetVal != NO_ERROR) { LogPrint(eLogError, "NetIface: GetMTU(): enclosed GetAdaptersAddresses() call has failed"); FREE(pAddresses); return fallback; } bool found_address = false; pCurrAddresses = pAddresses; while(pCurrAddresses) { PIP_ADAPTER_UNICAST_ADDRESS firstUnicastAddress = pCurrAddresses->FirstUnicastAddress; pUnicast = pCurrAddresses->FirstUnicastAddress; if(pUnicast == nullptr) { LogPrint(eLogError, "NetIface: GetMTU(): not a unicast ipv6 address, this is not supported"); } for(int i = 0; pUnicast != nullptr; ++i) { LPSOCKADDR lpAddr = pUnicast->Address.lpSockaddr; sockaddr_in6 *localInterfaceAddress = (sockaddr_in6*) lpAddr; for (int j = 0; j != 8; ++j) { if (localInterfaceAddress->sin6_addr.u.Word[j] != inputAddress.sin6_addr.u.Word[j]) { break; } else { found_address = true; } } if (found_address) { auto result = pAddresses->Mtu; FREE(pAddresses); pAddresses = nullptr; return result; } pUnicast = pUnicast->Next; } pCurrAddresses = pCurrAddresses->Next; } LogPrint(eLogError, "NetIface: GetMTU(): no usable unicast ipv6 addresses found"); FREE(pAddresses); return fallback; } int GetMTUWindows(const boost::asio::ip::address& localAddress, int fallback) { #ifdef UNICODE string localAddress_temporary = localAddress.to_string(); wstring localAddressUniversal(localAddress_temporary.begin(), localAddress_temporary.end()); #else std::string localAddressUniversal = localAddress.to_string(); #endif if(localAddress.is_v4()) { sockaddr_in inputAddress; inet_pton(AF_INET, localAddressUniversal.c_str(), &(inputAddress.sin_addr)); return GetMTUWindowsIpv4(inputAddress, fallback); } else if(localAddress.is_v6()) { sockaddr_in6 inputAddress; inet_pton(AF_INET6, localAddressUniversal.c_str(), &(inputAddress.sin6_addr)); return GetMTUWindowsIpv6(inputAddress, fallback); } else { LogPrint(eLogError, "NetIface: GetMTU(): address family is not supported"); return fallback; } } #else // assume unix int GetMTUUnix(const boost::asio::ip::address& localAddress, int fallback) { ifaddrs* ifaddr, *ifa = nullptr; if(getifaddrs(&ifaddr) == -1) { LogPrint(eLogError, "NetIface: Can't call getifaddrs(): ", strerror(errno)); return fallback; } int family = 0; // look for interface matching local address for(ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { if(!ifa->ifa_addr) continue; family = ifa->ifa_addr->sa_family; if(family == AF_INET && localAddress.is_v4()) { sockaddr_in* sa = (sockaddr_in*) ifa->ifa_addr; if(!memcmp(&sa->sin_addr, localAddress.to_v4().to_bytes().data(), 4)) break; // address matches } else if(family == AF_INET6 && localAddress.is_v6()) { sockaddr_in6* sa = (sockaddr_in6*) ifa->ifa_addr; if(!memcmp(&sa->sin6_addr, localAddress.to_v6().to_bytes().data(), 16)) break; // address matches } } int mtu = fallback; if(ifa && family) { // interface found? int fd = socket(family, SOCK_DGRAM, 0); if(fd > 0) { ifreq ifr; strncpy(ifr.ifr_name, ifa->ifa_name, IFNAMSIZ); // set interface for query if(ioctl(fd, SIOCGIFMTU, &ifr) >= 0) mtu = ifr.ifr_mtu; // MTU else LogPrint (eLogError, "NetIface: Failed to run ioctl: ", strerror(errno)); close(fd); } else LogPrint(eLogError, "NetIface: Failed to create datagram socket"); } else LogPrint(eLogWarning, "NetIface: interface for local address", localAddress.to_string(), " not found"); freeifaddrs(ifaddr); return mtu; } #endif // WIN32 int GetMTU(const boost::asio::ip::address& localAddress) { const int fallback = 576; // fallback MTU #ifdef WIN32 return GetMTUWindows(localAddress, fallback); #else return GetMTUUnix(localAddress, fallback); #endif return fallback; } const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6) { #ifdef WIN32 LogPrint(eLogError, "NetIface: cannot get address by interface name, not implemented on WIN32"); return boost::asio::ip::address::from_string("127.0.0.1"); #else int af = (ipv6 ? AF_INET6 : AF_INET); ifaddrs * addrs = nullptr; if(getifaddrs(&addrs) == 0) { // got ifaddrs ifaddrs * cur = addrs; while(cur) { std::string cur_ifname(cur->ifa_name); if (cur_ifname == ifname && cur->ifa_addr && cur->ifa_addr->sa_family == af) { // match char * addr = new char[INET6_ADDRSTRLEN]; bzero(addr, INET6_ADDRSTRLEN); if(af == AF_INET) inet_ntop(af, &((sockaddr_in *)cur->ifa_addr)->sin_addr, addr, INET6_ADDRSTRLEN); else inet_ntop(af, &((sockaddr_in6 *)cur->ifa_addr)->sin6_addr, addr, INET6_ADDRSTRLEN); freeifaddrs(addrs); std::string cur_ifaddr(addr); delete[] addr; return boost::asio::ip::address::from_string(cur_ifaddr); } cur = cur->ifa_next; } } if(addrs) freeifaddrs(addrs); std::string fallback; if(ipv6) { fallback = "::"; LogPrint(eLogWarning, "NetIface: cannot find ipv6 address for interface ", ifname); } else { fallback = "127.0.0.1"; LogPrint(eLogWarning, "NetIface: cannot find ipv4 address for interface ", ifname); } return boost::asio::ip::address::from_string(fallback); #endif } } } // util } // i2p i2pd-2.17.0/libi2pd/util.h000066400000000000000000000051741321131324000151070ustar00rootroot00000000000000#ifndef UTIL_H #define UTIL_H #include #include #include #include #include #include #ifdef ANDROID #include namespace std { template std::string to_string(T value) { return boost::lexical_cast(value); } inline int stoi(const std::string& str) { return boost::lexical_cast(str); } } #endif namespace i2p { namespace util { template class MemoryPool { //BOOST_STATIC_ASSERT_MSG(sizeof(T) >= sizeof(void*), "size cannot be less that general pointer size"); public: MemoryPool (): m_Head (nullptr) {} ~MemoryPool () { while (m_Head) { auto tmp = m_Head; m_Head = static_cast(*(void * *)m_Head); // next delete tmp; } } template T * Acquire (TArgs&&... args) { if (!m_Head) return new T(std::forward(args)...); else { auto tmp = m_Head; m_Head = static_cast(*(void * *)m_Head); // next return new (tmp)T(std::forward(args)...); } } void Release (T * t) { if (!t) return; t->~T (); *(void * *)t = m_Head; // next m_Head = t; } template std::unique_ptr > AcquireUnique (TArgs&&... args) { return std::unique_ptr >(Acquire (std::forward(args)...), std::bind (&MemoryPool::Release, this, std::placeholders::_1)); } template std::shared_ptr AcquireShared (TArgs&&... args) { return std::shared_ptr(Acquire (std::forward(args)...), std::bind (&MemoryPool::Release, this, std::placeholders::_1)); } protected: T * m_Head; }; template class MemoryPoolMt: public MemoryPool { public: MemoryPoolMt () {} template T * AcquireMt (TArgs&&... args) { if (!this->m_Head) return new T(std::forward(args)...); std::lock_guard l(m_Mutex); return this->Acquire (std::forward(args)...); } void ReleaseMt (T * t) { std::lock_guard l(m_Mutex); this->Release (t); } templateclass C, typename... R> void ReleaseMt(const C& c) { std::lock_guard l(m_Mutex); for (auto& it: c) this->Release (it); } private: std::mutex m_Mutex; }; namespace net { int GetMTU (const boost::asio::ip::address& localAddress); const boost::asio::ip::address GetInterfaceAddress(const std::string & ifname, bool ipv6=false); } } } #endif i2pd-2.17.0/libi2pd/version.h000066400000000000000000000012651321131324000156140ustar00rootroot00000000000000#ifndef _VERSION_H_ #define _VERSION_H_ #define CODENAME "Purple" #define STRINGIZE(x) #x #define MAKE_VERSION(a,b,c) STRINGIZE(a) "." STRINGIZE(b) "." STRINGIZE(c) #define I2PD_VERSION_MAJOR 2 #define I2PD_VERSION_MINOR 17 #define I2PD_VERSION_MICRO 0 #define I2PD_VERSION_PATCH 0 #define I2PD_VERSION MAKE_VERSION(I2PD_VERSION_MAJOR, I2PD_VERSION_MINOR, I2PD_VERSION_MICRO) #define VERSION I2PD_VERSION #ifdef MESHNET #define I2PD_NET_ID 3 #else #define I2PD_NET_ID 2 #endif #define I2P_VERSION_MAJOR 0 #define I2P_VERSION_MINOR 9 #define I2P_VERSION_MICRO 32 #define I2P_VERSION_PATCH 0 #define I2P_VERSION MAKE_VERSION(I2P_VERSION_MAJOR, I2P_VERSION_MINOR, I2P_VERSION_MICRO) #endif i2pd-2.17.0/libi2pd_client/000077500000000000000000000000001321131324000153105ustar00rootroot00000000000000i2pd-2.17.0/libi2pd_client/AddressBook.cpp000066400000000000000000000663771321131324000202370ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "Base.h" #include "util.h" #include "Identity.h" #include "FS.h" #include "Log.h" #include "HTTP.h" #include "NetDb.hpp" #include "ClientContext.h" #include "AddressBook.h" #include "Config.h" namespace i2p { namespace client { // TODO: this is actually proxy class class AddressBookFilesystemStorage: public AddressBookStorage { private: i2p::fs::HashedStorage storage; std::string etagsPath, indexPath, localPath; public: AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") {}; std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const; void AddAddress (std::shared_ptr address); void RemoveAddress (const i2p::data::IdentHash& ident); bool Init (); int Load (std::map& addresses); int LoadLocal (std::map& addresses); int Save (const std::map& addresses); void SaveEtag (const i2p::data::IdentHash& subsciption, const std::string& etag, const std::string& lastModified); bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified); private: int LoadFromFile (const std::string& filename, std::map& addresses); // returns -1 if can't open file, otherwise number of records }; bool AddressBookFilesystemStorage::Init() { storage.SetPlace(i2p::fs::GetDataDir()); // init storage if (storage.Init(i2p::data::GetBase32SubstitutionTable(), 32)) { // init ETags etagsPath = i2p::fs::StorageRootPath (storage, "etags"); if (!i2p::fs::Exists (etagsPath)) i2p::fs::CreateDirectory (etagsPath); // init address files indexPath = i2p::fs::StorageRootPath (storage, "addresses.csv"); localPath = i2p::fs::StorageRootPath (storage, "local.csv"); return true; } return false; } std::shared_ptr AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const { std::string filename = storage.Path(ident.ToBase32()); std::ifstream f(filename, std::ifstream::binary); if (!f.is_open ()) { LogPrint(eLogDebug, "Addressbook: Requested, but not found: ", filename); return nullptr; } f.seekg (0,std::ios::end); size_t len = f.tellg (); if (len < i2p::data::DEFAULT_IDENTITY_SIZE) { LogPrint (eLogError, "Addressbook: File ", filename, " is too short: ", len); return nullptr; } f.seekg(0, std::ios::beg); uint8_t * buf = new uint8_t[len]; f.read((char *)buf, len); auto address = std::make_shared(buf, len); delete[] buf; return address; } void AddressBookFilesystemStorage::AddAddress (std::shared_ptr address) { std::string path = storage.Path( address->GetIdentHash().ToBase32() ); std::ofstream f (path, std::ofstream::binary | std::ofstream::out); if (!f.is_open ()) { LogPrint (eLogError, "Addressbook: can't open file ", path); return; } size_t len = address->GetFullLen (); uint8_t * buf = new uint8_t[len]; address->ToBuffer (buf, len); f.write ((char *)buf, len); delete[] buf; } void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident) { storage.Remove( ident.ToBase32() ); } int AddressBookFilesystemStorage::LoadFromFile (const std::string& filename, std::map& addresses) { int num = 0; std::ifstream f (filename, std::ifstream::in); // in text mode if (!f) return -1; addresses.clear (); while (!f.eof ()) { std::string s; getline(f, s); if (!s.length()) continue; // skip empty line std::size_t pos = s.find(','); if (pos != std::string::npos) { std::string name = s.substr(0, pos++); std::string addr = s.substr(pos); i2p::data::IdentHash ident; ident.FromBase32 (addr); addresses[name] = ident; num++; } } return num; } int AddressBookFilesystemStorage::Load (std::map& addresses) { int num = LoadFromFile (indexPath, addresses); if (num < 0) { LogPrint(eLogWarning, "Addressbook: Can't open ", indexPath); return 0; } LogPrint(eLogInfo, "Addressbook: using index file ", indexPath); LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded from storage"); return num; } int AddressBookFilesystemStorage::LoadLocal (std::map& addresses) { int num = LoadFromFile (localPath, addresses); if (num < 0) return 0; LogPrint (eLogInfo, "Addressbook: ", num, " local addresses loaded"); return num; } int AddressBookFilesystemStorage::Save (const std::map& addresses) { if (addresses.empty()) { LogPrint(eLogWarning, "Addressbook: not saving empty addressbook"); return 0; } int num = 0; std::ofstream f (indexPath, std::ofstream::out); // in text mode if (!f.is_open ()) { LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath); return 0; } for (const auto& it: addresses) { f << it.first << "," << it.second.ToBase32 () << std::endl; num++; } LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved"); return num; } void AddressBookFilesystemStorage::SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) { std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt"; std::ofstream f (fname, std::ofstream::out | std::ofstream::trunc); if (f) { f << etag << std::endl; f<< lastModified << std::endl; } } bool AddressBookFilesystemStorage::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) { std::string fname = etagsPath + i2p::fs::dirSep + subscription.ToBase32 () + ".txt"; std::ifstream f (fname, std::ofstream::in); if (!f || f.eof ()) return false; std::getline (f, etag); if (f.eof ()) return false; std::getline (f, lastModified); return true; } //--------------------------------------------------------------------- AddressBook::AddressBook (): m_Storage(nullptr), m_IsLoaded (false), m_IsDownloading (false), m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr) { } AddressBook::~AddressBook () { Stop (); } void AddressBook::Start () { if (!m_Storage) m_Storage = new AddressBookFilesystemStorage; m_Storage->Init(); LoadHosts (); /* try storage, then hosts.txt, then download */ StartSubscriptions (); StartLookups (); } void AddressBook::StartResolvers () { LoadLocal (); } void AddressBook::Stop () { StopLookups (); StopSubscriptions (); if (m_SubscriptionsUpdateTimer) { delete m_SubscriptionsUpdateTimer; m_SubscriptionsUpdateTimer = nullptr; } if (m_IsDownloading) { LogPrint (eLogInfo, "Addressbook: subscriptions is downloading, abort"); for (int i = 0; i < 30; i++) { if (!m_IsDownloading) { LogPrint (eLogInfo, "Addressbook: subscriptions download complete"); break; } std::this_thread::sleep_for (std::chrono::seconds (1)); // wait for 1 seconds } LogPrint (eLogError, "Addressbook: subscription download timeout"); m_IsDownloading = false; } if (m_Storage) { m_Storage->Save (m_Addresses); delete m_Storage; m_Storage = nullptr; } m_DefaultSubscription = nullptr; m_Subscriptions.clear (); } bool AddressBook::GetIdentHash (const std::string& address, i2p::data::IdentHash& ident) { auto pos = address.find(".b32.i2p"); if (pos != std::string::npos) { Base32ToByteStream (address.c_str(), pos, ident, 32); return true; } else { pos = address.find (".i2p"); if (pos != std::string::npos) { auto identHash = FindAddress (address); if (identHash) { ident = *identHash; return true; } else { LookupAddress (address); // TODO: return false; } } } // if not .b32 we assume full base64 address i2p::data::IdentityEx dest; if (!dest.FromBase64 (address)) return false; ident = dest.GetIdentHash (); return true; } const i2p::data::IdentHash * AddressBook::FindAddress (const std::string& address) { auto it = m_Addresses.find (address); if (it != m_Addresses.end ()) return &it->second; return nullptr; } void AddressBook::InsertAddress (const std::string& address, const std::string& base64) { auto ident = std::make_shared(); ident->FromBase64 (base64); m_Storage->AddAddress (ident); m_Addresses[address] = ident->GetIdentHash (); LogPrint (eLogInfo, "Addressbook: added ", address," -> ", ToAddress(ident->GetIdentHash ())); } void AddressBook::InsertAddress (std::shared_ptr address) { m_Storage->AddAddress (address); } std::shared_ptr AddressBook::GetAddress (const std::string& address) { i2p::data::IdentHash ident; if (!GetIdentHash (address, ident)) return nullptr; return m_Storage->GetAddress (ident); } void AddressBook::LoadHosts () { if (m_Storage->Load (m_Addresses) > 0) { m_IsLoaded = true; return; } // then try hosts.txt std::ifstream f (i2p::fs::DataDirPath("hosts.txt"), std::ifstream::in); // in text mode if (f.is_open ()) { LoadHostsFromStream (f, false); m_IsLoaded = true; } } bool AddressBook::LoadHostsFromStream (std::istream& f, bool is_update) { std::unique_lock l(m_AddressBookMutex); int numAddresses = 0; bool incomplete = false; std::string s; while (!f.eof ()) { getline(f, s); if (!s.length() || s[0] == '#') continue; // skip empty or comment line size_t pos = s.find('='); if (pos != std::string::npos) { std::string name = s.substr(0, pos++); std::string addr = s.substr(pos); size_t pos = s.find('#'); if (pos != std::string::npos) addr = addr.substr(pos); // remove comments auto ident = std::make_shared (); if (!ident->FromBase64(addr)) { LogPrint (eLogError, "Addressbook: malformed address ", addr, " for ", name); incomplete = f.eof (); continue; } numAddresses++; auto it = m_Addresses.find (name); if (it != m_Addresses.end ()) // aleady exists ? { if (it->second != ident->GetIdentHash ()) // address changed? { it->second = ident->GetIdentHash (); m_Storage->AddAddress (ident); LogPrint (eLogInfo, "Addressbook: updated host: ", name); } } else { m_Addresses.insert (std::make_pair (name, ident->GetIdentHash ())); m_Storage->AddAddress (ident); if (is_update) LogPrint (eLogInfo, "Addressbook: added new host: ", name); } } else incomplete = f.eof (); } LogPrint (eLogInfo, "Addressbook: ", numAddresses, " addresses processed"); if (numAddresses > 0) { if (!incomplete) m_IsLoaded = true; m_Storage->Save (m_Addresses); } return !incomplete; } void AddressBook::LoadSubscriptions () { if (!m_Subscriptions.size ()) { std::ifstream f (i2p::fs::DataDirPath ("subscriptions.txt"), std::ifstream::in); // in text mode if (f.is_open ()) { std::string s; while (!f.eof ()) { getline(f, s); if (!s.length()) continue; // skip empty line m_Subscriptions.push_back (std::make_shared (*this, s)); } LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); LogPrint (eLogWarning, "Addressbook: subscriptions.txt usage is deprecated, use config file instead"); } else if (!i2p::config::IsDefault("addressbook.subscriptions")) { // using config file items std::string subscriptionURLs; i2p::config::GetOption("addressbook.subscriptions", subscriptionURLs); std::vector subsList; boost::split(subsList, subscriptionURLs, boost::is_any_of(","), boost::token_compress_on); for (size_t i = 0; i < subsList.size (); i++) { m_Subscriptions.push_back (std::make_shared (*this, subsList[i])); } LogPrint (eLogInfo, "Addressbook: ", m_Subscriptions.size (), " subscriptions urls loaded"); } } else LogPrint (eLogError, "Addressbook: subscriptions already loaded"); } void AddressBook::LoadLocal () { std::map localAddresses; m_Storage->LoadLocal (localAddresses); for (const auto& it: localAddresses) { auto dot = it.first.find ('.'); if (dot != std::string::npos) { auto domain = it.first.substr (dot + 1); auto it1 = m_Addresses.find (domain); // find domain in our addressbook if (it1 != m_Addresses.end ()) { auto dest = context.FindLocalDestination (it1->second); if (dest) { // address is ours std::shared_ptr resolver; auto it2 = m_Resolvers.find (it1->second); if (it2 != m_Resolvers.end ()) resolver = it2->second; // resolver exists else { // create new resolver resolver = std::make_shared(dest); m_Resolvers.insert (std::make_pair(it1->second, resolver)); } resolver->AddAddress (it.first, it.second); } } } } } bool AddressBook::GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) { if (m_Storage) return m_Storage->GetEtag (subscription, etag, lastModified); else return false; } void AddressBook::DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) { m_IsDownloading = false; int nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT; if (success) { if (m_DefaultSubscription) m_DefaultSubscription = nullptr; if (m_IsLoaded) nextUpdateTimeout = CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT; else m_IsLoaded = true; if (m_Storage) m_Storage->SaveEtag (subscription, etag, lastModified); } if (m_SubscriptionsUpdateTimer) { m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(nextUpdateTimeout)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, this, std::placeholders::_1)); } } void AddressBook::StartSubscriptions () { LoadSubscriptions (); if (m_IsLoaded && m_Subscriptions.empty ()) return; auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { m_SubscriptionsUpdateTimer = new boost::asio::deadline_timer (dest->GetService ()); m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, this, std::placeholders::_1)); } else LogPrint (eLogError, "Addressbook: can't start subscriptions: missing shared local destination"); } void AddressBook::StopSubscriptions () { if (m_SubscriptionsUpdateTimer) m_SubscriptionsUpdateTimer->cancel (); } void AddressBook::HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { auto dest = i2p::client::context.GetSharedLocalDestination (); if (!dest) { LogPrint(eLogWarning, "Addressbook: missing local destination, skip subscription update"); return; } if (!m_IsDownloading && dest->IsReady ()) { if (!m_IsLoaded) { // download it from default subscription LogPrint (eLogInfo, "Addressbook: trying to download it from default subscription."); std::string defaultSubURL; i2p::config::GetOption("addressbook.defaulturl", defaultSubURL); if (!m_DefaultSubscription) m_DefaultSubscription = std::make_shared(*this, defaultSubURL); m_IsDownloading = true; std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_DefaultSubscription)); load_hosts.detach(); // TODO: use join } else if (!m_Subscriptions.empty ()) { // pick random subscription auto ind = rand () % m_Subscriptions.size(); m_IsDownloading = true; std::thread load_hosts(std::bind (&AddressBookSubscription::CheckUpdates, m_Subscriptions[ind])); load_hosts.detach(); // TODO: use join } } else { // try it again later m_SubscriptionsUpdateTimer->expires_from_now (boost::posix_time::minutes(INITIAL_SUBSCRIPTION_RETRY_TIMEOUT)); m_SubscriptionsUpdateTimer->async_wait (std::bind (&AddressBook::HandleSubscriptionsUpdateTimer, this, std::placeholders::_1)); } } } void AddressBook::StartLookups () { auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { auto datagram = dest->GetDatagramDestination (); if (!datagram) datagram = dest->CreateDatagramDestination (); datagram->SetReceiver (std::bind (&AddressBook::HandleLookupResponse, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), ADDRESS_RESPONSE_DATAGRAM_PORT); } } void AddressBook::StopLookups () { auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { auto datagram = dest->GetDatagramDestination (); if (datagram) datagram->ResetReceiver (ADDRESS_RESPONSE_DATAGRAM_PORT); } } void AddressBook::LookupAddress (const std::string& address) { const i2p::data::IdentHash * ident = nullptr; auto dot = address.find ('.'); if (dot != std::string::npos) ident = FindAddress (address.substr (dot + 1)); if (!ident) { LogPrint (eLogError, "Addressbook: Can't find domain for ", address); return; } auto dest = i2p::client::context.GetSharedLocalDestination (); if (dest) { auto datagram = dest->GetDatagramDestination (); if (datagram) { uint32_t nonce; RAND_bytes ((uint8_t *)&nonce, 4); { std::unique_lock l(m_LookupsMutex); m_Lookups[nonce] = address; } LogPrint (eLogDebug, "Addressbook: Lookup of ", address, " to ", ident->ToBase32 (), " nonce=", nonce); size_t len = address.length () + 9; uint8_t * buf = new uint8_t[len]; memset (buf, 0, 4); htobe32buf (buf + 4, nonce); buf[8] = address.length (); memcpy (buf + 9, address.c_str (), address.length ()); datagram->SendDatagramTo (buf, len, *ident, ADDRESS_RESPONSE_DATAGRAM_PORT, ADDRESS_RESOLVER_DATAGRAM_PORT); delete[] buf; } } } void AddressBook::HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if (len < 44) { LogPrint (eLogError, "Addressbook: Lookup response is too short ", len); return; } uint32_t nonce = bufbe32toh (buf + 4); LogPrint (eLogDebug, "Addressbook: Lookup response received from ", from.GetIdentHash ().ToBase32 (), " nonce=", nonce); std::string address; { std::unique_lock l(m_LookupsMutex); auto it = m_Lookups.find (nonce); if (it != m_Lookups.end ()) { address = it->second; m_Lookups.erase (it); } } if (address.length () > 0) { // TODO: verify from i2p::data::IdentHash hash(buf + 8); if (!hash.IsZero ()) m_Addresses[address] = hash; else LogPrint (eLogInfo, "AddressBook: Lookup response: ", address, " not found"); } } AddressBookSubscription::AddressBookSubscription (AddressBook& book, const std::string& link): m_Book (book), m_Link (link) { } void AddressBookSubscription::CheckUpdates () { bool result = MakeRequest (); m_Book.DownloadComplete (result, m_Ident, m_Etag, m_LastModified); } bool AddressBookSubscription::MakeRequest () { i2p::http::URL url; // must be run in separate thread LogPrint (eLogInfo, "Addressbook: Downloading hosts database from ", m_Link); if (!url.parse(m_Link)) { LogPrint(eLogError, "Addressbook: failed to parse url: ", m_Link); return false; } if (!m_Book.GetIdentHash (url.host, m_Ident)) { LogPrint (eLogError, "Addressbook: Can't resolve ", url.host); return false; } /* this code block still needs some love */ std::condition_variable newDataReceived; std::mutex newDataReceivedMutex; auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (m_Ident); if (!leaseSet) { std::unique_lock l(newDataReceivedMutex); i2p::client::context.GetSharedLocalDestination ()->RequestDestination (m_Ident, [&newDataReceived, &leaseSet, &newDataReceivedMutex](std::shared_ptr ls) { leaseSet = ls; std::unique_lock l1(newDataReceivedMutex); newDataReceived.notify_all (); }); if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) { LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired"); i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (m_Ident, false); // don't notify, because we know it already return false; } } if (!leaseSet) { /* still no leaseset found */ LogPrint (eLogError, "Addressbook: LeaseSet for address ", url.host, " not found"); return false; } if (m_Etag.empty() && m_LastModified.empty()) { m_Book.GetEtag (m_Ident, m_Etag, m_LastModified); LogPrint (eLogDebug, "Addressbook: loaded for ", url.host, ": ETag: ", m_Etag, ", Last-Modified: ", m_LastModified); } /* save url parts for later use */ std::string dest_host = url.host; int dest_port = url.port ? url.port : 80; /* create http request & send it */ i2p::http::HTTPReq req; req.AddHeader("Host", dest_host); req.AddHeader("User-Agent", "Wget/1.11.4"); req.AddHeader("X-Accept-Encoding", "x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n"); req.AddHeader("Connection", "close"); if (!m_Etag.empty()) req.AddHeader("If-None-Match", m_Etag); if (!m_LastModified.empty()) req.AddHeader("If-Modified-Since", m_LastModified); /* convert url to relative */ url.schema = ""; url.host = ""; req.uri = url.to_string(); auto stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (leaseSet, dest_port); std::string request = req.to_string(); stream->Send ((const uint8_t *) request.data(), request.length()); /* read response */ std::string response; uint8_t recv_buf[4096]; bool end = false; int numAttempts = 5; while (!end) { stream->AsyncReceive (boost::asio::buffer (recv_buf, 4096), [&](const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (bytes_transferred) response.append ((char *)recv_buf, bytes_transferred); if (ecode == boost::asio::error::timed_out || !stream->IsOpen ()) end = true; newDataReceived.notify_all (); }, 30); // wait for 30 seconds std::unique_lock l(newDataReceivedMutex); if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout) { LogPrint (eLogError, "Addressbook: subscriptions request timeout expired"); numAttempts++; if (numAttempts > 5) end = true; } } // process remaining buffer while (size_t len = stream->ReadSome (recv_buf, sizeof(recv_buf))) response.append ((char *)recv_buf, len); /* parse response */ i2p::http::HTTPRes res; int res_head_len = res.parse(response); if (res_head_len < 0) { LogPrint(eLogError, "Addressbook: can't parse http response from ", dest_host); return false; } if (res_head_len == 0) { LogPrint(eLogError, "Addressbook: incomplete http response from ", dest_host, ", interrupted by timeout"); return false; } /* assert: res_head_len > 0 */ response.erase(0, res_head_len); if (res.code == 304) { LogPrint (eLogInfo, "Addressbook: no updates from ", dest_host, ", code 304"); return false; } if (res.code != 200) { LogPrint (eLogWarning, "Adressbook: can't get updates from ", dest_host, ", response code ", res.code); return false; } int len = res.content_length(); if (response.empty()) { LogPrint(eLogError, "Addressbook: empty response from ", dest_host, ", expected ", len, " bytes"); return false; } if (!res.is_gzipped () && len > 0 && len != (int) response.length()) { LogPrint(eLogError, "Addressbook: response size mismatch, expected: ", len, ", got: ", response.length(), "bytes"); return false; } /* assert: res.code == 200 */ auto it = res.headers.find("ETag"); if (it != res.headers.end()) m_Etag = it->second; it = res.headers.find("If-Modified-Since"); if (it != res.headers.end()) m_LastModified = it->second; if (res.is_chunked()) { std::stringstream in(response), out; i2p::http::MergeChunkedResponse (in, out); response = out.str(); } else if (res.is_gzipped()) { std::stringstream out; i2p::data::GzipInflator inflator; inflator.Inflate ((const uint8_t *) response.data(), response.length(), out); if (out.fail()) { LogPrint(eLogError, "Addressbook: can't gunzip http response"); return false; } response = out.str(); } std::stringstream ss(response); LogPrint (eLogInfo, "Addressbook: got update from ", dest_host); m_Book.LoadHostsFromStream (ss, true); return true; } AddressResolver::AddressResolver (std::shared_ptr destination): m_LocalDestination (destination) { if (m_LocalDestination) { auto datagram = m_LocalDestination->GetDatagramDestination (); if (!datagram) datagram = m_LocalDestination->CreateDatagramDestination (); datagram->SetReceiver (std::bind (&AddressResolver::HandleRequest, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), ADDRESS_RESOLVER_DATAGRAM_PORT); } } AddressResolver::~AddressResolver () { if (m_LocalDestination) { auto datagram = m_LocalDestination->GetDatagramDestination (); if (datagram) datagram->ResetReceiver (ADDRESS_RESOLVER_DATAGRAM_PORT); } } void AddressResolver::HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if (len < 9 || len < buf[8] + 9U) { LogPrint (eLogError, "Addressbook: Address request is too short ", len); return; } // read requested address uint8_t l = buf[8]; char address[255]; memcpy (address, buf + 9, l); address[l] = 0; LogPrint (eLogDebug, "Addressbook: Address request ", address); // send response uint8_t response[44]; memset (response, 0, 4); // reserved memcpy (response + 4, buf + 4, 4); // nonce auto it = m_LocalAddresses.find (address); // address lookup if (it != m_LocalAddresses.end ()) memcpy (response + 8, it->second, 32); // ident else memset (response + 8, 0, 32); // not found memset (response + 40, 0, 4); // set expiration time to zero m_LocalDestination->GetDatagramDestination ()->SendDatagramTo (response, 44, from.GetIdentHash(), toPort, fromPort); } void AddressResolver::AddAddress (const std::string& name, const i2p::data::IdentHash& ident) { m_LocalAddresses[name] = ident; } } } i2pd-2.17.0/libi2pd_client/AddressBook.h000066400000000000000000000115651321131324000176710ustar00rootroot00000000000000#ifndef ADDRESS_BOOK_H__ #define ADDRESS_BOOK_H__ #include #include #include #include #include #include #include #include #include "Base.h" #include "Identity.h" #include "Log.h" #include "Destination.h" namespace i2p { namespace client { const int INITIAL_SUBSCRIPTION_UPDATE_TIMEOUT = 3; // in minutes const int INITIAL_SUBSCRIPTION_RETRY_TIMEOUT = 1; // in minutes const int CONTINIOUS_SUBSCRIPTION_UPDATE_TIMEOUT = 720; // in minutes (12 hours) const int CONTINIOUS_SUBSCRIPTION_RETRY_TIMEOUT = 5; // in minutes const int SUBSCRIPTION_REQUEST_TIMEOUT = 60; //in second const uint16_t ADDRESS_RESOLVER_DATAGRAM_PORT = 53; const uint16_t ADDRESS_RESPONSE_DATAGRAM_PORT = 54; inline std::string GetB32Address(const i2p::data::IdentHash& ident) { return ident.ToBase32().append(".b32.i2p"); } class AddressBookStorage // interface for storage { public: virtual ~AddressBookStorage () {}; virtual std::shared_ptr GetAddress (const i2p::data::IdentHash& ident) const = 0; virtual void AddAddress (std::shared_ptr address) = 0; virtual void RemoveAddress (const i2p::data::IdentHash& ident) = 0; virtual bool Init () = 0; virtual int Load (std::map& addresses) = 0; virtual int LoadLocal (std::map& addresses) = 0; virtual int Save (const std::map& addresses) = 0; virtual void SaveEtag (const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified) = 0; virtual bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified) = 0; }; class AddressBookSubscription; class AddressResolver; class AddressBook { public: AddressBook (); ~AddressBook (); void Start (); void StartResolvers (); void Stop (); bool GetIdentHash (const std::string& address, i2p::data::IdentHash& ident); std::shared_ptr GetAddress (const std::string& address); const i2p::data::IdentHash * FindAddress (const std::string& address); void LookupAddress (const std::string& address); void InsertAddress (const std::string& address, const std::string& base64); // for jump service void InsertAddress (std::shared_ptr address); bool LoadHostsFromStream (std::istream& f, bool is_update); void DownloadComplete (bool success, const i2p::data::IdentHash& subscription, const std::string& etag, const std::string& lastModified); //This method returns the ".b32.i2p" address std::string ToAddress(const i2p::data::IdentHash& ident) { return GetB32Address(ident); } std::string ToAddress(std::shared_ptr ident) { return ToAddress(ident->GetIdentHash ()); } bool GetEtag (const i2p::data::IdentHash& subscription, std::string& etag, std::string& lastModified); private: void StartSubscriptions (); void StopSubscriptions (); void LoadHosts (); void LoadSubscriptions (); void LoadLocal (); void HandleSubscriptionsUpdateTimer (const boost::system::error_code& ecode); void StartLookups (); void StopLookups (); void HandleLookupResponse (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); private: std::mutex m_AddressBookMutex; std::map m_Addresses; std::map > m_Resolvers; // local destination->resolver std::mutex m_LookupsMutex; std::map m_Lookups; // nonce -> address AddressBookStorage * m_Storage; volatile bool m_IsLoaded, m_IsDownloading; std::vector > m_Subscriptions; std::shared_ptr m_DefaultSubscription; // in case if we don't know any addresses yet boost::asio::deadline_timer * m_SubscriptionsUpdateTimer; }; class AddressBookSubscription { public: AddressBookSubscription (AddressBook& book, const std::string& link); void CheckUpdates (); private: bool MakeRequest (); private: AddressBook& m_Book; std::string m_Link, m_Etag, m_LastModified; i2p::data::IdentHash m_Ident; // m_Etag must be surrounded by "" }; class AddressResolver { public: AddressResolver (std::shared_ptr destination); ~AddressResolver (); void AddAddress (const std::string& name, const i2p::data::IdentHash& ident); private: void HandleRequest (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); private: std::shared_ptr m_LocalDestination; std::map m_LocalAddresses; }; } } #endif i2pd-2.17.0/libi2pd_client/BOB.cpp000066400000000000000000000525611321131324000164270ustar00rootroot00000000000000#include #include "Log.h" #include "ClientContext.h" #include "util.h" #include "BOB.h" namespace i2p { namespace client { BOBI2PInboundTunnel::BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr localDestination): BOBI2PTunnel (localDestination), m_Acceptor (localDestination->GetService (), ep) { } BOBI2PInboundTunnel::~BOBI2PInboundTunnel () { Stop (); } void BOBI2PInboundTunnel::Start () { m_Acceptor.listen (); Accept (); } void BOBI2PInboundTunnel::Stop () { m_Acceptor.close(); ClearHandlers (); } void BOBI2PInboundTunnel::Accept () { auto receiver = std::make_shared (); receiver->socket = std::make_shared (GetService ()); m_Acceptor.async_accept (*receiver->socket, std::bind (&BOBI2PInboundTunnel::HandleAccept, this, std::placeholders::_1, receiver)); } void BOBI2PInboundTunnel::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr receiver) { if (!ecode) { Accept (); ReceiveAddress (receiver); } } void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr receiver) { receiver->socket->async_read_some (boost::asio::buffer( receiver->buffer + receiver->bufferOffset, BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset), std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this, std::placeholders::_1, std::placeholders::_2, receiver)); } void BOBI2PInboundTunnel::HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr receiver) { if (ecode) LogPrint (eLogError, "BOB: inbound tunnel read error: ", ecode.message ()); else { receiver->bufferOffset += bytes_transferred; receiver->buffer[receiver->bufferOffset] = 0; char * eol = strchr (receiver->buffer, '\n'); if (eol) { *eol = 0; if (eol != receiver->buffer && eol[-1] == '\r') eol[-1] = 0; // workaround for Transmission, it sends '\r\n' terminated address receiver->data = (uint8_t *)eol + 1; receiver->dataLen = receiver->bufferOffset - (eol - receiver->buffer + 1); i2p::data::IdentHash ident; if (!context.GetAddressBook ().GetIdentHash (receiver->buffer, ident)) { LogPrint (eLogError, "BOB: address ", receiver->buffer, " not found"); return; } auto leaseSet = GetLocalDestination ()->FindLeaseSet (ident); if (leaseSet) CreateConnection (receiver, leaseSet); else GetLocalDestination ()->RequestDestination (ident, std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete, this, std::placeholders::_1, receiver)); } else { if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE) ReceiveAddress (receiver); else LogPrint (eLogError, "BOB: missing inbound address"); } } } void BOBI2PInboundTunnel::HandleDestinationRequestComplete (std::shared_ptr leaseSet, std::shared_ptr receiver) { if (leaseSet) CreateConnection (receiver, leaseSet); else LogPrint (eLogError, "BOB: LeaseSet for inbound destination not found"); } void BOBI2PInboundTunnel::CreateConnection (std::shared_ptr receiver, std::shared_ptr leaseSet) { LogPrint (eLogDebug, "BOB: New inbound connection"); auto connection = std::make_shared(this, receiver->socket, leaseSet); AddHandler (connection); connection->I2PConnect (receiver->data, receiver->dataLen); } BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& address, int port, std::shared_ptr localDestination, bool quiet): BOBI2PTunnel (localDestination), m_Endpoint (boost::asio::ip::address::from_string (address), port), m_IsQuiet (quiet) { } void BOBI2POutboundTunnel::Start () { Accept (); } void BOBI2POutboundTunnel::Stop () { ClearHandlers (); } void BOBI2POutboundTunnel::Accept () { auto localDestination = GetLocalDestination (); if (localDestination) localDestination->AcceptStreams (std::bind (&BOBI2POutboundTunnel::HandleAccept, this, std::placeholders::_1)); else LogPrint (eLogError, "BOB: Local destination not set for server tunnel"); } void BOBI2POutboundTunnel::HandleAccept (std::shared_ptr stream) { if (stream) { auto conn = std::make_shared (this, stream, std::make_shared (GetService ()), m_Endpoint, m_IsQuiet); AddHandler (conn); conn->Connect (); } } BOBDestination::BOBDestination (std::shared_ptr localDestination): m_LocalDestination (localDestination), m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr) { } BOBDestination::~BOBDestination () { delete m_OutboundTunnel; delete m_InboundTunnel; i2p::client::context.DeleteLocalDestination (m_LocalDestination); } void BOBDestination::Start () { if (m_OutboundTunnel) m_OutboundTunnel->Start (); if (m_InboundTunnel) m_InboundTunnel->Start (); } void BOBDestination::Stop () { StopTunnels (); m_LocalDestination->Stop (); } void BOBDestination::StopTunnels () { if (m_OutboundTunnel) { m_OutboundTunnel->Stop (); delete m_OutboundTunnel; m_OutboundTunnel = nullptr; } if (m_InboundTunnel) { m_InboundTunnel->Stop (); delete m_InboundTunnel; m_InboundTunnel = nullptr; } } void BOBDestination::CreateInboundTunnel (int port, const std::string& address) { if (!m_InboundTunnel) { boost::asio::ip::tcp::endpoint ep(boost::asio::ip::tcp::v4(), port); if (!address.empty ()) { boost::system::error_code ec; auto addr = boost::asio::ip::address::from_string (address, ec); if (!ec) ep.address (addr); else LogPrint (eLogError, "BOB: ", ec.message ()); } m_InboundTunnel = new BOBI2PInboundTunnel (ep, m_LocalDestination); } } void BOBDestination::CreateOutboundTunnel (const std::string& address, int port, bool quiet) { if (!m_OutboundTunnel) m_OutboundTunnel = new BOBI2POutboundTunnel (address, port, m_LocalDestination, quiet); } BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner): m_Owner (owner), m_Socket (m_Owner.GetService ()), m_ReceiveBufferOffset (0), m_IsOpen (true), m_IsQuiet (false), m_IsActive (false), m_InPort (0), m_OutPort (0), m_CurrentDestination (nullptr) { } BOBCommandSession::~BOBCommandSession () { } void BOBCommandSession::Terminate () { m_Socket.close (); m_IsOpen = false; } void BOBCommandSession::Receive () { m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, BOB_COMMAND_BUFFER_SIZE - m_ReceiveBufferOffset), std::bind(&BOBCommandSession::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void BOBCommandSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "BOB: command channel read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else { size_t size = m_ReceiveBufferOffset + bytes_transferred; m_ReceiveBuffer[size] = 0; char * eol = strchr (m_ReceiveBuffer, '\n'); if (eol) { *eol = 0; char * operand = strchr (m_ReceiveBuffer, ' '); if (operand) { *operand = 0; operand++; } else operand = eol; // process command auto& handlers = m_Owner.GetCommandHandlers (); auto it = handlers.find (m_ReceiveBuffer); if (it != handlers.end ()) (this->*(it->second))(operand, eol - operand); else { LogPrint (eLogError, "BOB: unknown command ", m_ReceiveBuffer); SendReplyError ("unknown command"); } m_ReceiveBufferOffset = size - (eol - m_ReceiveBuffer) - 1; memmove (m_ReceiveBuffer, eol + 1, m_ReceiveBufferOffset); } else { if (size < BOB_COMMAND_BUFFER_SIZE) m_ReceiveBufferOffset = size; else { LogPrint (eLogError, "BOB: Malformed input of the command channel"); Terminate (); } } } } void BOBCommandSession::Send (size_t len) { boost::asio::async_write (m_Socket, boost::asio::buffer (m_SendBuffer, len), boost::asio::transfer_all (), std::bind(&BOBCommandSession::HandleSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "BOB: command channel send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else { if (m_IsOpen) Receive (); else Terminate (); } } void BOBCommandSession::SendReplyOK (const char * msg) { #ifdef _MSC_VER size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg); #else size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg); #endif Send (len); } void BOBCommandSession::SendReplyError (const char * msg) { #ifdef _MSC_VER size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg); #else size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg); #endif Send (len); } void BOBCommandSession::SendVersion () { size_t len = strlen (BOB_VERSION); memcpy (m_SendBuffer, BOB_VERSION, len); Send (len); } void BOBCommandSession::SendData (const char * nickname) { #ifdef _MSC_VER size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname); #else size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname); #endif Send (len); } void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: zap"); Terminate (); } void BOBCommandSession::QuitCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: quit"); m_IsOpen = false; SendReplyOK ("Bye!"); } void BOBCommandSession::StartCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: start ", m_Nickname); if (m_IsActive) { SendReplyError ("tunnel is active"); return; } if (!m_CurrentDestination) { m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options)); m_Owner.AddDestination (m_Nickname, m_CurrentDestination); } if (m_InPort) m_CurrentDestination->CreateInboundTunnel (m_InPort, m_Address); if (m_OutPort && !m_Address.empty ()) m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet); m_CurrentDestination->Start (); SendReplyOK ("Tunnel starting"); m_IsActive = true; } void BOBCommandSession::StopCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: stop ", m_Nickname); if (!m_IsActive) { SendReplyError ("tunnel is inactive"); return; } auto dest = m_Owner.FindDestination (m_Nickname); if (dest) { dest->StopTunnels (); SendReplyOK ("Tunnel stopping"); } else SendReplyError ("tunnel not found"); m_IsActive = false; } void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: setnick ", operand); m_Nickname = operand; std::string msg ("Nickname set to "); msg += m_Nickname; SendReplyOK (msg.c_str ()); } void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getnick ", operand); m_CurrentDestination = m_Owner.FindDestination (operand); if (m_CurrentDestination) { m_Keys = m_CurrentDestination->GetKeys (); m_Nickname = operand; } if (m_Nickname == operand) { std::string msg ("Nickname set to "); msg += m_Nickname; SendReplyOK (msg.c_str ()); } else SendReplyError ("no nickname has been set"); } void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: newkeys"); i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; if (*operand) { try { char * operand1 = (char *)strchr (operand, ' '); if (operand1) { *operand1 = 0; operand1++; cryptoType = std::stoi(operand1); } signatureType = std::stoi(operand); } catch (std::invalid_argument& ex) { LogPrint (eLogWarning, "BOB: newkeys ", ex.what ()); } } m_Keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); } void BOBCommandSession::SetkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: setkeys ", operand); if (m_Keys.FromBase64 (operand)) SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); else SendReplyError ("invalid keys"); } void BOBCommandSession::GetkeysCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getkeys"); if (m_Keys.GetPublic ()) // keys are set ? SendReplyOK (m_Keys.ToBase64 ().c_str ()); else SendReplyError ("keys are not set"); } void BOBCommandSession::GetdestCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: getdest"); if (m_Keys.GetPublic ()) // keys are set ? SendReplyOK (m_Keys.GetPublic ()->ToBase64 ().c_str ()); else SendReplyError ("keys are not set"); } void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: outhost ", operand); m_Address = operand; SendReplyOK ("outhost set"); } void BOBCommandSession::OutportCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: outport ", operand); m_OutPort = std::stoi(operand); if (m_OutPort >= 0) SendReplyOK ("outbound port set"); else SendReplyError ("port out of range"); } void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: inhost ", operand); m_Address = operand; SendReplyOK ("inhost set"); } void BOBCommandSession::InportCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: inport ", operand); m_InPort = std::stoi(operand); if (m_InPort >= 0) SendReplyOK ("inbound port set"); else SendReplyError ("port out of range"); } void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: quiet"); if (m_Nickname.length () > 0) { if (!m_IsActive) { m_IsQuiet = true; SendReplyOK ("Quiet set"); } else SendReplyError ("tunnel is active"); } else SendReplyError ("no nickname has been set"); } void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: lookup ", operand); i2p::data::IdentHash ident; if (!context.GetAddressBook ().GetIdentHash (operand, ident)) { SendReplyError ("Address Not found"); return; } auto localDestination = m_CurrentDestination ? m_CurrentDestination->GetLocalDestination () : i2p::client::context.GetSharedLocalDestination (); auto leaseSet = localDestination->FindLeaseSet (ident); if (leaseSet) SendReplyOK (leaseSet->GetIdentity ()->ToBase64 ().c_str ()); else { auto s = shared_from_this (); localDestination->RequestDestination (ident, [s](std::shared_ptr ls) { if (ls) s->SendReplyOK (ls->GetIdentity ()->ToBase64 ().c_str ()); else s->SendReplyError ("LeaseSet Not found"); } ); } } void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: clear"); m_Owner.DeleteDestination (m_Nickname); m_Nickname = ""; SendReplyOK ("cleared"); } void BOBCommandSession::ListCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: list"); const auto& destinations = m_Owner.GetDestinations (); for (const auto& it: destinations) SendData (it.first.c_str ()); SendReplyOK ("Listing done"); } void BOBCommandSession::OptionCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: option ", operand); const char * value = strchr (operand, '='); if (value) { std::string msg ("option "); *(const_cast(value)) = 0; m_Options[operand] = value + 1; msg += operand; *(const_cast(value)) = '='; msg += " set to "; msg += value; SendReplyOK (msg.c_str ()); } else SendReplyError ("malformed"); } void BOBCommandSession::StatusCommandHandler (const char * operand, size_t len) { LogPrint (eLogDebug, "BOB: status ", operand); if (m_Nickname == operand) { std::stringstream s; s << "DATA"; s << " NICKNAME: "; s << m_Nickname; if (m_CurrentDestination) { if (m_CurrentDestination->GetLocalDestination ()->IsReady ()) s << " STARTING: false RUNNING: true STOPPING: false"; else s << " STARTING: true RUNNING: false STOPPING: false"; } else s << " STARTING: false RUNNING: false STOPPING: false"; s << " KEYS: true"; s << " QUIET: "; s << (m_IsQuiet ? "true":"false"); if (m_InPort) { s << " INPORT: " << m_InPort; s << " INHOST: " << (m_Address.length () > 0 ? m_Address : "127.0.0.1"); } if (m_OutPort) { s << " OUTPORT: " << m_OutPort; s << " OUTHOST: " << (m_Address.length () > 0 ? m_Address : "127.0.0.1"); } SendReplyOK (s.str().c_str()); } else SendReplyError ("no nickname has been set"); } BOBCommandChannel::BOBCommandChannel (const std::string& address, int port): m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)) { // command -> handler m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler; m_CommandHandlers[BOB_COMMAND_QUIT] = &BOBCommandSession::QuitCommandHandler; m_CommandHandlers[BOB_COMMAND_START] = &BOBCommandSession::StartCommandHandler; m_CommandHandlers[BOB_COMMAND_STOP] = &BOBCommandSession::StopCommandHandler; m_CommandHandlers[BOB_COMMAND_SETNICK] = &BOBCommandSession::SetNickCommandHandler; m_CommandHandlers[BOB_COMMAND_GETNICK] = &BOBCommandSession::GetNickCommandHandler; m_CommandHandlers[BOB_COMMAND_NEWKEYS] = &BOBCommandSession::NewkeysCommandHandler; m_CommandHandlers[BOB_COMMAND_GETKEYS] = &BOBCommandSession::GetkeysCommandHandler; m_CommandHandlers[BOB_COMMAND_SETKEYS] = &BOBCommandSession::SetkeysCommandHandler; m_CommandHandlers[BOB_COMMAND_GETDEST] = &BOBCommandSession::GetdestCommandHandler; m_CommandHandlers[BOB_COMMAND_OUTHOST] = &BOBCommandSession::OuthostCommandHandler; m_CommandHandlers[BOB_COMMAND_OUTPORT] = &BOBCommandSession::OutportCommandHandler; m_CommandHandlers[BOB_COMMAND_INHOST] = &BOBCommandSession::InhostCommandHandler; m_CommandHandlers[BOB_COMMAND_INPORT] = &BOBCommandSession::InportCommandHandler; m_CommandHandlers[BOB_COMMAND_QUIET] = &BOBCommandSession::QuietCommandHandler; m_CommandHandlers[BOB_COMMAND_LOOKUP] = &BOBCommandSession::LookupCommandHandler; m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler; m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler; m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler; m_CommandHandlers[BOB_COMMAND_STATUS] = &BOBCommandSession::StatusCommandHandler; } BOBCommandChannel::~BOBCommandChannel () { Stop (); for (const auto& it: m_Destinations) delete it.second; } void BOBCommandChannel::Start () { Accept (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&BOBCommandChannel::Run, this)); } void BOBCommandChannel::Stop () { m_IsRunning = false; for (auto& it: m_Destinations) it.second->Stop (); m_Acceptor.cancel (); m_Service.stop (); if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = nullptr; } } void BOBCommandChannel::Run () { while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, "BOB: runtime exception: ", ex.what ()); } } } void BOBCommandChannel::AddDestination (const std::string& name, BOBDestination * dest) { m_Destinations[name] = dest; } void BOBCommandChannel::DeleteDestination (const std::string& name) { auto it = m_Destinations.find (name); if (it != m_Destinations.end ()) { it->second->Stop (); delete it->second; m_Destinations.erase (it); } } BOBDestination * BOBCommandChannel::FindDestination (const std::string& name) { auto it = m_Destinations.find (name); if (it != m_Destinations.end ()) return it->second; return nullptr; } void BOBCommandChannel::Accept () { auto newSession = std::make_shared (*this); m_Acceptor.async_accept (newSession->GetSocket (), std::bind (&BOBCommandChannel::HandleAccept, this, std::placeholders::_1, newSession)); } void BOBCommandChannel::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr session) { if (ecode != boost::asio::error::operation_aborted) Accept (); if (!ecode) { LogPrint (eLogInfo, "BOB: New command connection from ", session->GetSocket ().remote_endpoint ()); session->SendVersion (); } else LogPrint (eLogError, "BOB: accept error: ", ecode.message ()); } } } i2pd-2.17.0/libi2pd_client/BOB.h000066400000000000000000000167721321131324000161000ustar00rootroot00000000000000#ifndef BOB_H__ #define BOB_H__ #include #include #include #include #include #include #include "I2PTunnel.h" #include "I2PService.h" #include "Identity.h" #include "LeaseSet.h" namespace i2p { namespace client { const size_t BOB_COMMAND_BUFFER_SIZE = 1024; const char BOB_COMMAND_ZAP[] = "zap"; const char BOB_COMMAND_QUIT[] = "quit"; const char BOB_COMMAND_START[] = "start"; const char BOB_COMMAND_STOP[] = "stop"; const char BOB_COMMAND_SETNICK[] = "setnick"; const char BOB_COMMAND_GETNICK[] = "getnick"; const char BOB_COMMAND_NEWKEYS[] = "newkeys"; const char BOB_COMMAND_GETKEYS[] = "getkeys"; const char BOB_COMMAND_SETKEYS[] = "setkeys"; const char BOB_COMMAND_GETDEST[] = "getdest"; const char BOB_COMMAND_OUTHOST[] = "outhost"; const char BOB_COMMAND_OUTPORT[] = "outport"; const char BOB_COMMAND_INHOST[] = "inhost"; const char BOB_COMMAND_INPORT[] = "inport"; const char BOB_COMMAND_QUIET[] = "quiet"; const char BOB_COMMAND_LOOKUP[] = "lookup"; const char BOB_COMMAND_CLEAR[] = "clear"; const char BOB_COMMAND_LIST[] = "list"; const char BOB_COMMAND_OPTION[] = "option"; const char BOB_COMMAND_STATUS[] = "status"; const char BOB_VERSION[] = "BOB 00.00.10\nOK\n"; const char BOB_REPLY_OK[] = "OK %s\n"; const char BOB_REPLY_ERROR[] = "ERROR %s\n"; const char BOB_DATA[] = "NICKNAME %s\n"; class BOBI2PTunnel: public I2PService { public: BOBI2PTunnel (std::shared_ptr localDestination): I2PService (localDestination) {}; virtual void Start () {}; virtual void Stop () {}; }; class BOBI2PInboundTunnel: public BOBI2PTunnel { struct AddressReceiver { std::shared_ptr socket; char buffer[BOB_COMMAND_BUFFER_SIZE + 1]; // for destination base64 address uint8_t * data; // pointer to buffer size_t dataLen, bufferOffset; AddressReceiver (): data (nullptr), dataLen (0), bufferOffset (0) {}; }; public: BOBI2PInboundTunnel (const boost::asio::ip::tcp::endpoint& ep, std::shared_ptr localDestination); ~BOBI2PInboundTunnel (); void Start (); void Stop (); private: void Accept (); void HandleAccept (const boost::system::error_code& ecode, std::shared_ptr receiver); void ReceiveAddress (std::shared_ptr receiver); void HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred, std::shared_ptr receiver); void HandleDestinationRequestComplete (std::shared_ptr leaseSet, std::shared_ptr receiver); void CreateConnection (std::shared_ptr receiver, std::shared_ptr leaseSet); private: boost::asio::ip::tcp::acceptor m_Acceptor; }; class BOBI2POutboundTunnel: public BOBI2PTunnel { public: BOBI2POutboundTunnel (const std::string& address, int port, std::shared_ptr localDestination, bool quiet); void Start (); void Stop (); void SetQuiet () { m_IsQuiet = true; }; private: void Accept (); void HandleAccept (std::shared_ptr stream); private: boost::asio::ip::tcp::endpoint m_Endpoint; bool m_IsQuiet; }; class BOBDestination { public: BOBDestination (std::shared_ptr localDestination); ~BOBDestination (); void Start (); void Stop (); void StopTunnels (); void CreateInboundTunnel (int port, const std::string& address); void CreateOutboundTunnel (const std::string& address, int port, bool quiet); const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); }; std::shared_ptr GetLocalDestination () const { return m_LocalDestination; }; private: std::shared_ptr m_LocalDestination; BOBI2POutboundTunnel * m_OutboundTunnel; BOBI2PInboundTunnel * m_InboundTunnel; }; class BOBCommandChannel; class BOBCommandSession: public std::enable_shared_from_this { public: BOBCommandSession (BOBCommandChannel& owner); ~BOBCommandSession (); void Terminate (); boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; void SendVersion (); // command handlers void ZapCommandHandler (const char * operand, size_t len); void QuitCommandHandler (const char * operand, size_t len); void StartCommandHandler (const char * operand, size_t len); void StopCommandHandler (const char * operand, size_t len); void SetNickCommandHandler (const char * operand, size_t len); void GetNickCommandHandler (const char * operand, size_t len); void NewkeysCommandHandler (const char * operand, size_t len); void SetkeysCommandHandler (const char * operand, size_t len); void GetkeysCommandHandler (const char * operand, size_t len); void GetdestCommandHandler (const char * operand, size_t len); void OuthostCommandHandler (const char * operand, size_t len); void OutportCommandHandler (const char * operand, size_t len); void InhostCommandHandler (const char * operand, size_t len); void InportCommandHandler (const char * operand, size_t len); void QuietCommandHandler (const char * operand, size_t len); void LookupCommandHandler (const char * operand, size_t len); void ClearCommandHandler (const char * operand, size_t len); void ListCommandHandler (const char * operand, size_t len); void OptionCommandHandler (const char * operand, size_t len); void StatusCommandHandler (const char * operand, size_t len); private: void Receive (); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void Send (size_t len); void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void SendReplyOK (const char * msg); void SendReplyError (const char * msg); void SendData (const char * nickname); private: BOBCommandChannel& m_Owner; boost::asio::ip::tcp::socket m_Socket; char m_ReceiveBuffer[BOB_COMMAND_BUFFER_SIZE + 1], m_SendBuffer[BOB_COMMAND_BUFFER_SIZE + 1]; size_t m_ReceiveBufferOffset; bool m_IsOpen, m_IsQuiet, m_IsActive; std::string m_Nickname, m_Address; int m_InPort, m_OutPort; i2p::data::PrivateKeys m_Keys; std::map m_Options; BOBDestination * m_CurrentDestination; }; typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len); class BOBCommandChannel { public: BOBCommandChannel (const std::string& address, int port); ~BOBCommandChannel (); void Start (); void Stop (); boost::asio::io_service& GetService () { return m_Service; }; void AddDestination (const std::string& name, BOBDestination * dest); void DeleteDestination (const std::string& name); BOBDestination * FindDestination (const std::string& name); private: void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr session); private: bool m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; std::map m_Destinations; std::map m_CommandHandlers; public: const decltype(m_CommandHandlers)& GetCommandHandlers () const { return m_CommandHandlers; }; const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; }; }; } } #endif i2pd-2.17.0/libi2pd_client/ClientContext.cpp000066400000000000000000000671401321131324000206070ustar00rootroot00000000000000#include #include #include #include #include "Config.h" #include "FS.h" #include "Log.h" #include "Identity.h" #include "util.h" #include "ClientContext.h" #include "SOCKS.h" #include "WebSocks.h" #include "MatchedDestination.h" namespace i2p { namespace client { ClientContext context; ClientContext::ClientContext (): m_SharedLocalDestination (nullptr), m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr), m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr) { } ClientContext::~ClientContext () { delete m_HttpProxy; delete m_SocksProxy; delete m_SamBridge; delete m_BOBCommandChannel; delete m_I2CPServer; } void ClientContext::Start () { if (!m_SharedLocalDestination) { m_SharedLocalDestination = CreateNewLocalDestination (); // non-public, DSA m_SharedLocalDestination->Acquire (); m_Destinations[m_SharedLocalDestination->GetIdentity ()->GetIdentHash ()] = m_SharedLocalDestination; m_SharedLocalDestination->Start (); } m_AddressBook.Start (); std::shared_ptr localDestination; bool httproxy; i2p::config::GetOption("httpproxy.enabled", httproxy); if (httproxy) { std::string httpProxyKeys; i2p::config::GetOption("httpproxy.keys", httpProxyKeys); std::string httpProxyAddr; i2p::config::GetOption("httpproxy.address", httpProxyAddr); uint16_t httpProxyPort; i2p::config::GetOption("httpproxy.port", httpProxyPort); i2p::data::SigningKeyType sigType; i2p::config::GetOption("httpproxy.signaturetype", sigType); std::string httpOutProxyURL; i2p::config::GetOption("httpproxy.outproxy", httpOutProxyURL); LogPrint(eLogInfo, "Clients: starting HTTP Proxy at ", httpProxyAddr, ":", httpProxyPort); if (httpProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; if(LoadPrivateKeys (keys, httpProxyKeys, sigType)) { std::map params; ReadI2CPOptionsFromConfig ("httpproxy.", params); localDestination = CreateNewLocalDestination (keys, false, ¶ms); localDestination->Acquire (); } else LogPrint(eLogError, "Clients: failed to load HTTP Proxy key"); } try { m_HttpProxy = new i2p::proxy::HTTPProxy("HTTP Proxy", httpProxyAddr, httpProxyPort, httpOutProxyURL, localDestination); m_HttpProxy->Start(); } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in HTTP Proxy: ", e.what()); } } localDestination = nullptr; bool socksproxy; i2p::config::GetOption("socksproxy.enabled", socksproxy); if (socksproxy) { std::string socksProxyKeys; i2p::config::GetOption("socksproxy.keys", socksProxyKeys); std::string socksProxyAddr; i2p::config::GetOption("socksproxy.address", socksProxyAddr); uint16_t socksProxyPort; i2p::config::GetOption("socksproxy.port", socksProxyPort); bool socksOutProxy; i2p::config::GetOption("socksproxy.outproxy.enabled", socksOutProxy); std::string socksOutProxyAddr; i2p::config::GetOption("socksproxy.outproxy", socksOutProxyAddr); uint16_t socksOutProxyPort; i2p::config::GetOption("socksproxy.outproxyport", socksOutProxyPort); i2p::data::SigningKeyType sigType; i2p::config::GetOption("socksproxy.signaturetype", sigType); LogPrint(eLogInfo, "Clients: starting SOCKS Proxy at ", socksProxyAddr, ":", socksProxyPort); if (socksProxyKeys.length () > 0) { i2p::data::PrivateKeys keys; if (LoadPrivateKeys (keys, socksProxyKeys, sigType)) { std::map params; ReadI2CPOptionsFromConfig ("socksproxy.", params); localDestination = CreateNewLocalDestination (keys, false, ¶ms); localDestination->Acquire (); } else LogPrint(eLogError, "Clients: failed to load SOCKS Proxy key"); } try { m_SocksProxy = new i2p::proxy::SOCKSProxy("SOCKS", socksProxyAddr, socksProxyPort, socksOutProxy, socksOutProxyAddr, socksOutProxyPort, localDestination); m_SocksProxy->Start(); } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in SOCKS Proxy: ", e.what()); } } // I2P tunnels ReadTunnels (); // SAM bool sam; i2p::config::GetOption("sam.enabled", sam); if (sam) { std::string samAddr; i2p::config::GetOption("sam.address", samAddr); uint16_t samPort; i2p::config::GetOption("sam.port", samPort); LogPrint(eLogInfo, "Clients: starting SAM bridge at ", samAddr, ":", samPort); try { m_SamBridge = new SAMBridge (samAddr, samPort); m_SamBridge->Start (); } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in SAM bridge: ", e.what()); } } // BOB bool bob; i2p::config::GetOption("bob.enabled", bob); if (bob) { std::string bobAddr; i2p::config::GetOption("bob.address", bobAddr); uint16_t bobPort; i2p::config::GetOption("bob.port", bobPort); LogPrint(eLogInfo, "Clients: starting BOB command channel at ", bobAddr, ":", bobPort); try { m_BOBCommandChannel = new BOBCommandChannel (bobAddr, bobPort); m_BOBCommandChannel->Start (); } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in BOB bridge: ", e.what()); } } // I2CP bool i2cp; i2p::config::GetOption("i2cp.enabled", i2cp); if (i2cp) { std::string i2cpAddr; i2p::config::GetOption("i2cp.address", i2cpAddr); uint16_t i2cpPort; i2p::config::GetOption("i2cp.port", i2cpPort); LogPrint(eLogInfo, "Clients: starting I2CP at ", i2cpAddr, ":", i2cpPort); try { m_I2CPServer = new I2CPServer (i2cpAddr, i2cpPort); m_I2CPServer->Start (); } catch (std::exception& e) { LogPrint(eLogError, "Clients: Exception in I2CP: ", e.what()); } } m_AddressBook.StartResolvers (); // start UDP cleanup if (!m_ServerForwards.empty ()) { m_CleanupUDPTimer.reset (new boost::asio::deadline_timer(m_SharedLocalDestination->GetService ())); ScheduleCleanupUDP(); } } void ClientContext::Stop () { if (m_HttpProxy) { LogPrint(eLogInfo, "Clients: stopping HTTP Proxy"); m_HttpProxy->Stop(); delete m_HttpProxy; m_HttpProxy = nullptr; } if (m_SocksProxy) { LogPrint(eLogInfo, "Clients: stopping SOCKS Proxy"); m_SocksProxy->Stop(); delete m_SocksProxy; m_SocksProxy = nullptr; } for (auto& it: m_ClientTunnels) { LogPrint(eLogInfo, "Clients: stopping I2P client tunnel on port ", it.first); it.second->Stop (); } m_ClientTunnels.clear (); for (auto& it: m_ServerTunnels) { LogPrint(eLogInfo, "Clients: stopping I2P server tunnel"); it.second->Stop (); } m_ServerTunnels.clear (); if (m_SamBridge) { LogPrint(eLogInfo, "Clients: stopping SAM bridge"); m_SamBridge->Stop (); delete m_SamBridge; m_SamBridge = nullptr; } if (m_BOBCommandChannel) { LogPrint(eLogInfo, "Clients: stopping BOB command channel"); m_BOBCommandChannel->Stop (); delete m_BOBCommandChannel; m_BOBCommandChannel = nullptr; } if (m_I2CPServer) { LogPrint(eLogInfo, "Clients: stopping I2CP"); m_I2CPServer->Stop (); delete m_I2CPServer; m_I2CPServer = nullptr; } LogPrint(eLogInfo, "Clients: stopping AddressBook"); m_AddressBook.Stop (); { std::lock_guard lock(m_ForwardsMutex); m_ServerForwards.clear(); m_ClientForwards.clear(); } if (m_CleanupUDPTimer) { m_CleanupUDPTimer->cancel (); m_CleanupUDPTimer = nullptr; } for (auto& it: m_Destinations) it.second->Stop (); m_Destinations.clear (); m_SharedLocalDestination = nullptr; } void ClientContext::ReloadConfig () { // TODO: handle config changes /*std::string config; i2p::config::GetOption("conf", config); i2p::config::ParseConfig(config);*/ // handle tunnels // reset isUpdated for each tunnel VisitTunnels ([](I2PService * s)->bool { s->isUpdated = false; return true; }); // reload tunnels ReadTunnels(); // delete not updated tunnels (not in config anymore) VisitTunnels ([](I2PService * s)->bool { return s->isUpdated; }); // delete unused destinations std::unique_lock l(m_DestinationsMutex); for (auto it = m_Destinations.begin (); it != m_Destinations.end ();) { auto dest = it->second; if (dest->GetRefCounter () > 0) ++it; // skip else { dest->Stop (); it = m_Destinations.erase (it); } } } bool ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType) { if (filename == "transient") { keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); LogPrint (eLogInfo, "Clients: New transient keys address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); return true; } bool success = true; std::string fullPath = i2p::fs::DataDirPath (filename); std::ifstream s(fullPath, std::ifstream::binary); if (s.is_open ()) { s.seekg (0, std::ios::end); size_t len = s.tellg(); s.seekg (0, std::ios::beg); uint8_t * buf = new uint8_t[len]; s.read ((char *)buf, len); if(!keys.FromBuffer (buf, len)) { LogPrint (eLogError, "Clients: failed to load keyfile ", filename); success = false; } else LogPrint (eLogInfo, "Clients: Local address ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " loaded"); delete[] buf; } else { LogPrint (eLogError, "Clients: can't open file ", fullPath, " Creating new one with signature type ", sigType, " crypto type ", cryptoType); keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); size_t len = keys.GetFullLen (); uint8_t * buf = new uint8_t[len]; len = keys.ToBuffer (buf, len); f.write ((char *)buf, len); delete[] buf; LogPrint (eLogInfo, "Clients: New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " created"); } return success; } std::vector > ClientContext::GetForwardInfosFor(const i2p::data::IdentHash & destination) { std::vector > infos; std::lock_guard lock(m_ForwardsMutex); for(const auto & c : m_ClientForwards) { if (c.second->IsLocalDestination(destination)) { for (auto & i : c.second->GetSessions()) infos.push_back(i); break; } } for(const auto & s : m_ServerForwards) { if(std::get<0>(s.first) == destination) { for( auto & i : s.second->GetSessions()) infos.push_back(i); break; } } return infos; } std::shared_ptr ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType, i2p::data::CryptoKeyType cryptoType, const std::map * params) { i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType, cryptoType); auto localDestination = std::make_shared (keys, isPublic, params); std::unique_lock l(m_DestinationsMutex); m_Destinations[localDestination->GetIdentHash ()] = localDestination; localDestination->Start (); return localDestination; } std::shared_ptr ClientContext::CreateNewMatchedTunnelDestination(const i2p::data::PrivateKeys &keys, const std::string & name, const std::map * params) { MatchedTunnelDestination * cl = new MatchedTunnelDestination(keys, name, params); auto localDestination = std::shared_ptr(cl); std::unique_lock l(m_DestinationsMutex); m_Destinations[localDestination->GetIdentHash ()] = localDestination; localDestination->Start (); return localDestination; } void ClientContext::DeleteLocalDestination (std::shared_ptr destination) { if (!destination) return; auto it = m_Destinations.find (destination->GetIdentHash ()); if (it != m_Destinations.end ()) { auto d = it->second; { std::unique_lock l(m_DestinationsMutex); m_Destinations.erase (it); } d->Stop (); } } std::shared_ptr ClientContext::CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic, const std::map * params) { auto it = m_Destinations.find (keys.GetPublic ()->GetIdentHash ()); if (it != m_Destinations.end ()) { LogPrint (eLogWarning, "Clients: Local destination ", m_AddressBook.ToAddress(keys.GetPublic ()->GetIdentHash ()), " exists"); if (!it->second->IsRunning ()) { it->second->Start (); return it->second; } return nullptr; } auto localDestination = std::make_shared (keys, isPublic, params); std::unique_lock l(m_DestinationsMutex); m_Destinations[keys.GetPublic ()->GetIdentHash ()] = localDestination; localDestination->Start (); return localDestination; } std::shared_ptr ClientContext::FindLocalDestination (const i2p::data::IdentHash& destination) const { auto it = m_Destinations.find (destination); if (it != m_Destinations.end ()) return it->second; return nullptr; } template std::string ClientContext::GetI2CPOption (const Section& section, const std::string& name, const Type& value) const { return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value)); } template void ClientContext::ReadI2CPOptions (const Section& section, std::map& options) const { options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); options[I2CP_PARAM_TAGS_TO_SEND] = GetI2CPOption (section, I2CP_PARAM_TAGS_TO_SEND, DEFAULT_TAGS_TO_SEND); options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY); options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY); options[I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY] = GetI2CPOption(section, I2CP_PARAM_STREAMING_INITIAL_ACK_DELAY, DEFAULT_INITIAL_ACK_DELAY); } void ClientContext::ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const { std::string value; if (i2p::config::GetOption(prefix + I2CP_PARAM_INBOUND_TUNNEL_LENGTH, value)) options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, value)) options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, value)) options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, value)) options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_MIN_TUNNEL_LATENCY, value)) options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = value; if (i2p::config::GetOption(prefix + I2CP_PARAM_MAX_TUNNEL_LATENCY, value)) options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = value; } void ClientContext::ReadTunnels () { boost::property_tree::ptree pt; std::string tunConf; i2p::config::GetOption("tunconf", tunConf); if (tunConf == "") { // TODO: cleanup this in 2.8.0 tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); if (i2p::fs::Exists(tunConf)) { LogPrint(eLogWarning, "FS: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); } else { tunConf = i2p::fs::DataDirPath ("tunnels.conf"); } } LogPrint(eLogDebug, "FS: tunnels config file: ", tunConf); try { boost::property_tree::read_ini (tunConf, pt); } catch (std::exception& ex) { LogPrint (eLogWarning, "Clients: Can't read ", tunConf, ": ", ex.what ()); return; } int numClientTunnels = 0, numServerTunnels = 0; for (auto& section: pt) { std::string name = section.first; try { std::string type = section.second.get (I2P_TUNNELS_SECTION_TYPE); if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_SOCKS || type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS || type == I2P_TUNNELS_SECTION_TYPE_HTTPPROXY || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { // mandatory params std::string dest; if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); // optional params bool matchTunnels = section.second.get(I2P_CLIENT_TUNNEL_MATCH_TUNNELS, false); std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); // I2CP std::map options; ReadI2CPOptions (section, options); std::shared_ptr localDestination = nullptr; if (keys.length () > 0) { i2p::data::PrivateKeys k; if(LoadPrivateKeys (k, keys, sigType, cryptoType)) { localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); if (!localDestination) { if(matchTunnels) localDestination = CreateNewMatchedTunnelDestination(k, dest, &options); else localDestination = CreateNewLocalDestination (k, type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT, &options); } } } if (type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { // udp client // TODO: hostnames boost::asio::ip::udp::endpoint end(boost::asio::ip::address::from_string(address), port); if (!localDestination) { localDestination = m_SharedLocalDestination; } auto clientTunnel = new I2PUDPClientTunnel(name, dest, end, localDestination, destinationPort); if(m_ClientForwards.insert(std::make_pair(end, std::unique_ptr(clientTunnel))).second) { clientTunnel->Start(); } else LogPrint(eLogError, "Clients: I2P Client forward for endpoint ", end, " already exists"); } else { boost::asio::ip::tcp::endpoint clientEndpoint; I2PService * clientTunnel = nullptr; if (type == I2P_TUNNELS_SECTION_TYPE_SOCKS) { // socks proxy clientTunnel = new i2p::proxy::SOCKSProxy(name, address, port, false, "", destinationPort, localDestination); clientEndpoint = ((i2p::proxy::SOCKSProxy*)clientTunnel)->GetLocalEndpoint (); } else if (type == I2P_TUNNELS_SECTION_TYPE_HTTPPROXY) { // http proxy std::string outproxy = section.second.get("outproxy", ""); clientTunnel = new i2p::proxy::HTTPProxy(name, address, port, outproxy, localDestination); clientEndpoint = ((i2p::proxy::HTTPProxy*)clientTunnel)->GetLocalEndpoint (); } else if (type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS) { // websocks proxy clientTunnel = new WebSocks(address, port, localDestination);; clientEndpoint = ((WebSocks*)clientTunnel)->GetLocalEndpoint(); } else { // tcp client clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); clientEndpoint = ((I2PClientTunnel*)clientTunnel)->GetLocalEndpoint (); } uint32_t timeout = section.second.get(I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT, 0); if(timeout) { clientTunnel->SetConnectTimeout(timeout); LogPrint(eLogInfo, "Clients: I2P Client tunnel connect timeout set to ", timeout); } auto ins = m_ClientTunnels.insert (std::make_pair (clientEndpoint, std::unique_ptr(clientTunnel))); if (ins.second) { clientTunnel->Start (); numClientTunnels++; } else { // TODO: update if (ins.first->second->GetLocalDestination () != clientTunnel->GetLocalDestination ()) { LogPrint (eLogInfo, "Clients: I2P client tunnel destination updated"); ins.first->second->SetLocalDestination (clientTunnel->GetLocalDestination ()); } ins.first->second->isUpdated = true; LogPrint (eLogInfo, "Clients: I2P client tunnel for endpoint ", clientEndpoint, " already exists"); } } } else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC || type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // mandatory params std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); int port = section.second.get (I2P_SERVER_TUNNEL_PORT); std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); // optional params int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); i2p::data::CryptoKeyType cryptoType = section.second.get (I2P_CLIENT_TUNNEL_CRYPTO_TYPE, i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); uint32_t maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, "127.0.0.1"); bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); // I2CP std::map options; ReadI2CPOptions (section, options); std::shared_ptr localDestination = nullptr; i2p::data::PrivateKeys k; if(!LoadPrivateKeys (k, keys, sigType, cryptoType)) continue; localDestination = FindLocalDestination (k.GetPublic ()->GetIdentHash ()); if (!localDestination) localDestination = CreateNewLocalDestination (k, true, &options); if (type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // udp server tunnel // TODO: hostnames auto localAddress = boost::asio::ip::address::from_string(address); boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(host), port); I2PUDPServerTunnel * serverTunnel = new I2PUDPServerTunnel(name, localDestination, localAddress, endpoint, port); if(!isUniqueLocal) { LogPrint(eLogInfo, "Clients: disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } std::lock_guard lock(m_ForwardsMutex); if(m_ServerForwards.insert( std::make_pair( std::make_pair( localDestination->GetIdentHash(), port), std::unique_ptr(serverTunnel))).second) { serverTunnel->Start(); LogPrint(eLogInfo, "Clients: I2P Server Forward created for UDP Endpoint ", host, ":", port, " bound on ", address, " for ",localDestination->GetIdentHash().ToBase32()); } else LogPrint(eLogError, "Clients: I2P Server Forward for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash()), "/", port, "already exists"); continue; } I2PServerTunnel * serverTunnel; if (type == I2P_TUNNELS_SECTION_TYPE_HTTP) serverTunnel = new I2PServerTunnelHTTP (name, host, port, localDestination, hostOverride, inPort, gzip); else if (type == I2P_TUNNELS_SECTION_TYPE_IRC) serverTunnel = new I2PServerTunnelIRC (name, host, port, localDestination, webircpass, inPort, gzip); else // regular server tunnel by default serverTunnel = new I2PServerTunnel (name, host, port, localDestination, inPort, gzip); LogPrint(eLogInfo, "Clients: Set Max Conns To ", maxConns); serverTunnel->SetMaxConnsPerMinute(maxConns); if(!isUniqueLocal) { LogPrint(eLogInfo, "Clients: disabling loopback address mapping"); serverTunnel->SetUniqueLocal(isUniqueLocal); } if (accessList.length () > 0) { std::set idents; size_t pos = 0, comma; do { comma = accessList.find (',', pos); i2p::data::IdentHash ident; ident.FromBase32 (accessList.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); idents.insert (ident); pos = comma + 1; } while (comma != std::string::npos); serverTunnel->SetAccessList (idents); } auto ins = m_ServerTunnels.insert (std::make_pair ( std::make_pair (localDestination->GetIdentHash (), inPort), std::unique_ptr(serverTunnel))); if (ins.second) { serverTunnel->Start (); numServerTunnels++; } else { // TODO: update if (ins.first->second->GetLocalDestination () != serverTunnel->GetLocalDestination ()) { LogPrint (eLogInfo, "Clients: I2P server tunnel destination updated"); ins.first->second->SetLocalDestination (serverTunnel->GetLocalDestination ()); } ins.first->second->isUpdated = true; LogPrint (eLogInfo, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists"); } } else LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", tunConf); } catch (std::exception& ex) { LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ()); } } LogPrint (eLogInfo, "Clients: ", numClientTunnels, " I2P client tunnels created"); LogPrint (eLogInfo, "Clients: ", numServerTunnels, " I2P server tunnels created"); } void ClientContext::ScheduleCleanupUDP() { if (m_CleanupUDPTimer) { // schedule cleanup in 17 seconds m_CleanupUDPTimer->expires_from_now (boost::posix_time::seconds (17)); m_CleanupUDPTimer->async_wait(std::bind(&ClientContext::CleanupUDP, this, std::placeholders::_1)); } } void ClientContext::CleanupUDP(const boost::system::error_code & ecode) { if(!ecode) { std::lock_guard lock(m_ForwardsMutex); for (auto & s : m_ServerForwards ) s.second->ExpireStale(); ScheduleCleanupUDP(); } } template void VisitTunnelsContainer (Container& c, Visitor v) { for (auto it = c.begin (); it != c.end ();) { if (!v (it->second.get ())) { it->second->Stop (); it = c.erase (it); } else it++; } } template void ClientContext::VisitTunnels (Visitor v) { VisitTunnelsContainer (m_ClientTunnels, v); VisitTunnelsContainer (m_ServerTunnels, v); // TODO: implement UDP forwards } } } i2pd-2.17.0/libi2pd_client/ClientContext.h000066400000000000000000000143171321131324000202520ustar00rootroot00000000000000#ifndef CLIENT_CONTEXT_H__ #define CLIENT_CONTEXT_H__ #include #include #include #include #include "Destination.h" #include "I2PService.h" #include "HTTPProxy.h" #include "SOCKS.h" #include "I2PTunnel.h" #include "SAM.h" #include "BOB.h" #include "I2CP.h" #include "AddressBook.h" namespace i2p { namespace client { const char I2P_TUNNELS_SECTION_TYPE[] = "type"; const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc"; const char I2P_TUNNELS_SECTION_TYPE_UDPCLIENT[] = "udpclient"; const char I2P_TUNNELS_SECTION_TYPE_UDPSERVER[] = "udpserver"; const char I2P_TUNNELS_SECTION_TYPE_SOCKS[] = "socks"; const char I2P_TUNNELS_SECTION_TYPE_WEBSOCKS[] = "websocks"; const char I2P_TUNNELS_SECTION_TYPE_HTTPPROXY[] = "httpproxy"; const char I2P_CLIENT_TUNNEL_PORT[] = "port"; const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address"; const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination"; const char I2P_CLIENT_TUNNEL_KEYS[] = "keys"; const char I2P_CLIENT_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_CLIENT_TUNNEL_CRYPTO_TYPE[] = "cryptotype"; const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport"; const char I2P_CLIENT_TUNNEL_MATCH_TUNNELS[] = "matchtunnels"; const char I2P_CLIENT_TUNNEL_CONNECT_TIMEOUT[] = "connecttimeout"; const char I2P_SERVER_TUNNEL_HOST[] = "host"; const char I2P_SERVER_TUNNEL_HOST_OVERRIDE[] = "hostoverride"; const char I2P_SERVER_TUNNEL_PORT[] = "port"; const char I2P_SERVER_TUNNEL_KEYS[] = "keys"; const char I2P_SERVER_TUNNEL_SIGNATURE_TYPE[] = "signaturetype"; const char I2P_SERVER_TUNNEL_INPORT[] = "inport"; const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist"; const char I2P_SERVER_TUNNEL_GZIP[] = "gzip"; const char I2P_SERVER_TUNNEL_WEBIRC_PASSWORD[] = "webircpassword"; const char I2P_SERVER_TUNNEL_ADDRESS[] = "address"; const char I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL[] = "enableuniquelocal"; class ClientContext { public: ClientContext (); ~ClientContext (); void Start (); void Stop (); void ReloadConfig (); std::shared_ptr GetSharedLocalDestination () const { return m_SharedLocalDestination; }; std::shared_ptr CreateNewLocalDestination (bool isPublic = false, // transient i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL, const std::map * params = nullptr); // used by SAM only std::shared_ptr CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true, const std::map * params = nullptr); std::shared_ptr CreateNewMatchedTunnelDestination(const i2p::data::PrivateKeys &keys, const std::string & name, const std::map * params = nullptr); void DeleteLocalDestination (std::shared_ptr destination); std::shared_ptr FindLocalDestination (const i2p::data::IdentHash& destination) const; bool LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256, i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL); AddressBook& GetAddressBook () { return m_AddressBook; }; const BOBCommandChannel * GetBOBCommandChannel () const { return m_BOBCommandChannel; }; const SAMBridge * GetSAMBridge () const { return m_SamBridge; }; const I2CPServer * GetI2CPServer () const { return m_I2CPServer; }; std::vector > GetForwardInfosFor(const i2p::data::IdentHash & destination); private: void ReadTunnels (); template std::string GetI2CPOption (const Section& section, const std::string& name, const Type& value) const; template void ReadI2CPOptions (const Section& section, std::map& options) const; // for tunnels void ReadI2CPOptionsFromConfig (const std::string& prefix, std::map& options) const; // for HTTP and SOCKS proxy void CleanupUDP(const boost::system::error_code & ecode); void ScheduleCleanupUDP(); template void VisitTunnels (Visitor v); // Visitor: (I2PService *) -> bool, true means retain private: std::mutex m_DestinationsMutex; std::map > m_Destinations; std::shared_ptr m_SharedLocalDestination; AddressBook m_AddressBook; i2p::proxy::HTTPProxy * m_HttpProxy; i2p::proxy::SOCKSProxy * m_SocksProxy; std::map > m_ClientTunnels; // local endpoint->tunnel std::map, std::unique_ptr > m_ServerTunnels; // ->tunnel std::mutex m_ForwardsMutex; std::map > m_ClientForwards; // local endpoint -> udp tunnel std::map, std::unique_ptr > m_ServerForwards; // -> udp tunnel SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; I2CPServer * m_I2CPServer; std::unique_ptr m_CleanupUDPTimer; public: // for HTTP const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; }; const decltype(m_ClientTunnels)& GetClientTunnels () const { return m_ClientTunnels; }; const decltype(m_ServerTunnels)& GetServerTunnels () const { return m_ServerTunnels; }; const decltype(m_ClientForwards)& GetClientForwards () const { return m_ClientForwards; } const decltype(m_ServerForwards)& GetServerForwards () const { return m_ServerForwards; } const i2p::proxy::HTTPProxy * GetHttpProxy () const { return m_HttpProxy; } const i2p::proxy::SOCKSProxy * GetSocksProxy () const { return m_SocksProxy; } }; extern ClientContext context; } } #endif i2pd-2.17.0/libi2pd_client/HTTPProxy.cpp000066400000000000000000000533441321131324000176460ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "I2PService.h" #include "Destination.h" #include "HTTPProxy.h" #include "util.h" #include "Identity.h" #include "Streaming.h" #include "Destination.h" #include "ClientContext.h" #include "I2PEndian.h" #include "I2PTunnel.h" #include "Config.h" #include "HTTP.h" namespace i2p { namespace proxy { std::map jumpservices = { { "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" }, { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, }; static const char *pageHead = "\r\n" " I2Pd HTTP proxy\r\n" " \r\n" "\r\n" ; bool str_rmatch(std::string & str, const char *suffix) { auto pos = str.rfind (suffix); if (pos == std::string::npos) return false; /* not found */ if (str.length() == (pos + std::strlen(suffix))) return true; /* match */ return false; } class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: bool HandleRequest(); void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64, bool & confirm); void SanitizeHTTPRequest(i2p::http::HTTPReq & req); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); /* error helpers */ void GenericProxyError(const char *title, const char *description); void GenericProxyInfo(const char *title, const char *description); void HostNotFound(std::string & host); void SendProxyError(std::string & content); void ForwardToUpstreamProxy(); void HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec); void HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec); void HTTPConnect(const std::string & host, uint16_t port); void HandleHTTPConnectStreamRequestComplete(std::shared_ptr stream); void HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transfered); void HandleSocksProxyReply(const boost::system::error_code & ec, std::size_t bytes_transfered); typedef std::function ProxyResolvedHandler; void HandleUpstreamProxyResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr, ProxyResolvedHandler handler); void SocksProxySuccess(); void HandoverToUpstreamProxy(); uint8_t m_recv_chunk[8192]; std::string m_recv_buf; // from client std::string m_send_buf; // to upstream std::shared_ptr m_sock; std::shared_ptr m_proxysock; boost::asio::ip::tcp::resolver m_proxy_resolver; std::string m_OutproxyUrl; i2p::http::URL m_ProxyURL; i2p::http::URL m_RequestURL; uint8_t m_socks_buf[255+8]; // for socks request/response ssize_t m_req_len; i2p::http::URL m_ClientRequestURL; i2p::http::HTTPReq m_ClientRequest; i2p::http::HTTPRes m_ClientResponse; std::stringstream m_ClientRequestBuffer; public: HTTPReqHandler(HTTPProxy * parent, std::shared_ptr sock) : I2PServiceHandler(parent), m_sock(sock), m_proxysock(std::make_shared(parent->GetService())), m_proxy_resolver(parent->GetService()), m_OutproxyUrl(parent->GetOutproxyURL()) {} ~HTTPReqHandler() { Terminate(); } void Handle () { AsyncSockRead(); } /* overload */ }; void HTTPReqHandler::AsyncSockRead() { LogPrint(eLogDebug, "HTTPProxy: async sock read"); if (!m_sock) { LogPrint(eLogError, "HTTPProxy: no socket for read"); return; } m_sock->async_read_some(boost::asio::buffer(m_recv_chunk, sizeof(m_recv_chunk)), std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } void HTTPReqHandler::Terminate() { if (Kill()) return; if (m_sock) { LogPrint(eLogDebug, "HTTPProxy: close sock"); m_sock->close(); m_sock = nullptr; } if(m_proxysock) { LogPrint(eLogDebug, "HTTPProxy: close proxysock"); if(m_proxysock->is_open()) m_proxysock->close(); m_proxysock = nullptr; } Done(shared_from_this()); } void HTTPReqHandler::GenericProxyError(const char *title, const char *description) { std::stringstream ss; ss << "

Proxy error: " << title << "

\r\n"; ss << "

" << description << "

\r\n"; std::string content = ss.str(); SendProxyError(content); } void HTTPReqHandler::GenericProxyInfo(const char *title, const char *description) { std::stringstream ss; ss << "

Proxy info: " << title << "

\r\n"; ss << "

" << description << "

\r\n"; std::string content = ss.str(); SendProxyError(content); } void HTTPReqHandler::HostNotFound(std::string & host) { std::stringstream ss; ss << "

Proxy error: Host not found

\r\n" << "

Remote host not found in router's addressbook

\r\n" << "

You may try to find this host on jumpservices below:

\r\n" << "\r\n"; std::string content = ss.str(); SendProxyError(content); } void HTTPReqHandler::SendProxyError(std::string & content) { i2p::http::HTTPRes res; res.code = 500; res.add_header("Content-Type", "text/html; charset=UTF-8"); res.add_header("Connection", "close"); std::stringstream ss; ss << "\r\n" << pageHead << "" << content << "\r\n" << "\r\n"; res.body = ss.str(); std::string response = res.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(response), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64, bool & confirm) { confirm = false; const char *param = "i2paddresshelper="; std::size_t pos = url.query.find(param); std::size_t len = std::strlen(param); std::map params; if (pos == std::string::npos) return false; /* not found */ if (!url.parse_query(params)) return false; std::string value = params["i2paddresshelper"]; len += value.length(); b64 = i2p::http::UrlDecode(value); // if we need update exists, request formed with update param if (params["update"] == "true") { len += std::strlen("&update=true"); confirm = true; } url.query.replace(pos, len, ""); return true; } void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req) { /* drop common headers */ req.RemoveHeader("Referer"); req.RemoveHeader("Via"); req.RemoveHeader("From"); req.RemoveHeader("Forwarded"); req.RemoveHeader("Accept", "Accept-Encoding"); // Accept*, but Accept-Encoding /* drop proxy-disclosing headers */ req.RemoveHeader("X-Forwarded"); req.RemoveHeader("Proxy-"); // Proxy-* /* replace headers */ req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); /* add headers */ req.AddHeader("Connection", "close"); /* keep-alive conns not supported yet */ } /** * @brief Try to parse request from @a m_recv_buf * If parsing success, rebuild request and store to @a m_send_buf * with remaining data tail * @return true on processed request or false if more data needed */ bool HTTPReqHandler::HandleRequest() { std::string b64; m_req_len = m_ClientRequest.parse(m_recv_buf); if (m_req_len == 0) return false; /* need more data */ if (m_req_len < 0) { LogPrint(eLogError, "HTTPProxy: unable to parse request"); GenericProxyError("Invalid request", "Proxy unable to parse your request"); return true; /* parse error */ } /* parsing success, now let's look inside request */ LogPrint(eLogDebug, "HTTPProxy: requested: ", m_ClientRequest.uri); m_RequestURL.parse(m_ClientRequest.uri); bool m_Confirm; if (ExtractAddressHelper(m_RequestURL, b64, m_Confirm)) { bool addresshelper; i2p::config::GetOption("httpproxy.addresshelper", addresshelper); if (!addresshelper) { LogPrint(eLogWarning, "HTTPProxy: addresshelper request rejected"); GenericProxyError("Invalid request", "addresshelper is not supported"); return true; } if (!i2p::client::context.GetAddressBook ().FindAddress (m_RequestURL.host) || m_Confirm) { i2p::client::context.GetAddressBook ().InsertAddress (m_RequestURL.host, b64); LogPrint (eLogInfo, "HTTPProxy: added b64 from addresshelper for ", m_RequestURL.host); std::string full_url = m_RequestURL.to_string(); std::stringstream ss; ss << "Host " << m_RequestURL.host << " added to router's addressbook from helper. " << "Click here to proceed."; GenericProxyInfo("Addresshelper found", ss.str().c_str()); return true; /* request processed */ } else { std::stringstream ss; ss << "Host " << m_RequestURL.host << " already in router's addressbook. " << "Click here to update record."; GenericProxyInfo("Addresshelper found", ss.str().c_str()); return true; /* request processed */ } } std::string dest_host; uint16_t dest_port; bool useConnect = false; if(m_ClientRequest.method == "CONNECT") { std::string uri(m_ClientRequest.uri); auto pos = uri.find(":"); if(pos == std::string::npos || pos == uri.size() - 1) { GenericProxyError("Invalid Request", "invalid request uri"); return true; } else { useConnect = true; dest_port = std::stoi(uri.substr(pos+1)); dest_host = uri.substr(0, pos); } } else { SanitizeHTTPRequest(m_ClientRequest); dest_host = m_RequestURL.host; dest_port = m_RequestURL.port; /* always set port, even if missing in request */ if (!dest_port) dest_port = (m_RequestURL.schema == "https") ? 443 : 80; /* detect dest_host, set proper 'Host' header in upstream request */ if (dest_host != "") { /* absolute url, replace 'Host' header */ std::string h = dest_host; if (dest_port != 0 && dest_port != 80) h += ":" + std::to_string(dest_port); m_ClientRequest.UpdateHeader("Host", h); } else { auto h = m_ClientRequest.GetHeader ("Host"); if (h.length () > 0) { /* relative url and 'Host' header provided. transparent proxy mode? */ i2p::http::URL u; std::string t = "http://" + h; u.parse(t); dest_host = u.host; dest_port = u.port; } else { /* relative url and missing 'Host' header */ GenericProxyError("Invalid request", "Can't detect destination host from request"); return true; } } } /* check dest_host really exists and inside I2P network */ i2p::data::IdentHash identHash; if (str_rmatch(dest_host, ".i2p")) { if (!i2p::client::context.GetAddressBook ().GetIdentHash (dest_host, identHash)) { HostNotFound(dest_host); return true; /* request processed */ } } else { if(m_OutproxyUrl.size()) { LogPrint (eLogDebug, "HTTPProxy: use outproxy ", m_OutproxyUrl); if(m_ProxyURL.parse(m_OutproxyUrl)) ForwardToUpstreamProxy(); else GenericProxyError("Outproxy failure", "bad outproxy settings"); } else { LogPrint (eLogWarning, "HTTPProxy: outproxy failure for ", dest_host, ": no outprxy enabled"); std::string message = "Host " + dest_host + " not inside I2P network, but outproxy is not enabled"; GenericProxyError("Outproxy failure", message.c_str()); } return true; } if(useConnect) { HTTPConnect(dest_host, dest_port); return true; } /* make relative url */ m_RequestURL.schema = ""; m_RequestURL.host = ""; m_ClientRequest.uri = m_RequestURL.to_string(); /* drop original request from recv buffer */ m_recv_buf.erase(0, m_req_len); /* build new buffer from modified request and data from original request */ m_send_buf = m_ClientRequest.to_string(); m_send_buf.append(m_recv_buf); /* connect to destination */ LogPrint(eLogDebug, "HTTPProxy: connecting to host ", dest_host, ":", dest_port); GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), dest_host, dest_port); return true; } void HTTPReqHandler::ForwardToUpstreamProxy() { LogPrint(eLogDebug, "HTTPProxy: forward to upstream"); // build http requset m_ClientRequestURL = m_RequestURL; LogPrint(eLogDebug, "HTTPProxy: ", m_ClientRequestURL.host); m_ClientRequestURL.schema = ""; m_ClientRequestURL.host = ""; m_ClientRequest.uri = m_ClientRequestURL.to_string(); m_ClientRequest.write(m_ClientRequestBuffer); m_ClientRequestBuffer << m_recv_buf.substr(m_req_len); // assume http if empty schema if (m_ProxyURL.schema == "" || m_ProxyURL.schema == "http") { // handle upstream http proxy if (!m_ProxyURL.port) m_ProxyURL.port = 80; if (m_ProxyURL.is_i2p()) { m_send_buf = m_recv_buf; GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_ProxyURL.host, m_ProxyURL.port); } else { boost::asio::ip::tcp::resolver::query q(m_ProxyURL.host, std::to_string(m_ProxyURL.port)); m_proxy_resolver.async_resolve(q, std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) { m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamHTTPProxyConnect, this, std::placeholders::_1)); })); } } else if (m_ProxyURL.schema == "socks") { // handle upstream socks proxy if (!m_ProxyURL.port) m_ProxyURL.port = 9050; // default to tor default if not specified boost::asio::ip::tcp::resolver::query q(m_ProxyURL.host, std::to_string(m_ProxyURL.port)); m_proxy_resolver.async_resolve(q, std::bind(&HTTPReqHandler::HandleUpstreamProxyResolved, this, std::placeholders::_1, std::placeholders::_2, [&](boost::asio::ip::tcp::endpoint ep) { m_proxysock->async_connect(ep, std::bind(&HTTPReqHandler::HandleUpstreamSocksProxyConnect, this, std::placeholders::_1)); })); } else { // unknown type, complain GenericProxyError("unknown outproxy url", m_ProxyURL.to_string().c_str()); } } void HTTPReqHandler::HandleUpstreamProxyResolved(const boost::system::error_code & ec, boost::asio::ip::tcp::resolver::iterator it, ProxyResolvedHandler handler) { if(ec) GenericProxyError("cannot resolve upstream proxy", ec.message().c_str()); else handler(*it); } void HTTPReqHandler::HandleUpstreamSocksProxyConnect(const boost::system::error_code & ec) { if(!ec) { if(m_RequestURL.host.size() > 255) { GenericProxyError("hostname too long", m_RequestURL.host.c_str()); return; } uint16_t port = m_RequestURL.port; if(!port) port = 80; LogPrint(eLogDebug, "HTTPProxy: connected to socks upstream"); std::string host = m_RequestURL.host; std::size_t reqsize = 0; m_socks_buf[0] = '\x04'; m_socks_buf[1] = 1; htobe16buf(m_socks_buf+2, port); m_socks_buf[4] = 0; m_socks_buf[5] = 0; m_socks_buf[6] = 0; m_socks_buf[7] = 1; // user id m_socks_buf[8] = 'i'; m_socks_buf[9] = '2'; m_socks_buf[10] = 'p'; m_socks_buf[11] = 'd'; m_socks_buf[12] = 0; reqsize += 13; memcpy(m_socks_buf+ reqsize, host.c_str(), host.size()); reqsize += host.size(); m_socks_buf[++reqsize] = 0; boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_socks_buf, reqsize), boost::asio::transfer_all(), std::bind(&HTTPReqHandler::HandleSocksProxySendHandshake, this, std::placeholders::_1, std::placeholders::_2)); } else GenericProxyError("cannot connect to upstream socks proxy", ec.message().c_str()); } void HTTPReqHandler::HandleSocksProxySendHandshake(const boost::system::error_code & ec, std::size_t bytes_transferred) { LogPrint(eLogDebug, "HTTPProxy: upstream socks handshake sent"); if(ec) GenericProxyError("Cannot negotiate with socks proxy", ec.message().c_str()); else m_proxysock->async_read_some(boost::asio::buffer(m_socks_buf, 8), std::bind(&HTTPReqHandler::HandleSocksProxyReply, this, std::placeholders::_1, std::placeholders::_2)); } void HTTPReqHandler::HandoverToUpstreamProxy() { LogPrint(eLogDebug, "HTTPProxy: handover to socks proxy"); auto connection = std::make_shared(GetOwner(), m_proxysock, m_sock); m_sock = nullptr; m_proxysock = nullptr; GetOwner()->AddHandler(connection); connection->Start(); Terminate(); } void HTTPReqHandler::HTTPConnect(const std::string & host, uint16_t port) { LogPrint(eLogDebug, "HTTPProxy: CONNECT ",host, ":", port); std::string hostname(host); if(str_rmatch(hostname, ".i2p")) GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleHTTPConnectStreamRequestComplete, shared_from_this(), std::placeholders::_1), host, port); else ForwardToUpstreamProxy(); } void HTTPReqHandler::HandleHTTPConnectStreamRequestComplete(std::shared_ptr stream) { if(stream) { m_ClientResponse.code = 200; m_ClientResponse.status = "OK"; m_send_buf = m_ClientResponse.to_string(); m_sock->send(boost::asio::buffer(m_send_buf)); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler(connection); connection->I2PConnect(); m_sock = nullptr; Terminate(); } else { GenericProxyError("CONNECT error", "Failed to Connect"); } } void HTTPReqHandler::SocksProxySuccess() { if(m_ClientRequest.method == "CONNECT") { m_ClientResponse.code = 200; m_send_buf = m_ClientResponse.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&] (const boost::system::error_code & ec, std::size_t transferred) { if(ec) GenericProxyError("socks proxy error", ec.message().c_str()); else HandoverToUpstreamProxy(); }); } else { m_send_buf = m_ClientRequestBuffer.str(); LogPrint(eLogDebug, "HTTPProxy: send ", m_send_buf.size(), " bytes"); boost::asio::async_write(*m_proxysock, boost::asio::buffer(m_send_buf), boost::asio::transfer_all(), [&](const boost::system::error_code & ec, std::size_t transferred) { if(ec) GenericProxyError("failed to send request to upstream", ec.message().c_str()); else HandoverToUpstreamProxy(); }); } } void HTTPReqHandler::HandleSocksProxyReply(const boost::system::error_code & ec, std::size_t bytes_transferred) { if(!ec) { if(m_socks_buf[1] == 90) { // success SocksProxySuccess(); } else { std::stringstream ss; ss << "error code: "; ss << (int) m_socks_buf[1]; std::string msg = ss.str(); GenericProxyError("Socks Proxy error", msg.c_str()); } } else GenericProxyError("No Reply From socks proxy", ec.message().c_str()); } void HTTPReqHandler::HandleUpstreamHTTPProxyConnect(const boost::system::error_code & ec) { if(!ec) { LogPrint(eLogDebug, "HTTPProxy: connected to http upstream"); GenericProxyError("cannot connect", "http out proxy not implemented"); } else GenericProxyError("cannot connect to upstream http proxy", ec.message().c_str()); } /* will be called after some data received from client */ void HTTPReqHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes, recv buf: ", m_recv_buf.length(), ", send buf: ", m_send_buf.length()); if(ecode) { LogPrint(eLogWarning, "HTTPProxy: sock recv got error: ", ecode); Terminate(); return; } m_recv_buf.append(reinterpret_cast(m_recv_chunk), len); if (HandleRequest()) { m_recv_buf.clear(); return; } AsyncSockRead(); } void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) { if (ecode) LogPrint (eLogError, "HTTPProxy: Closing socket after sending failure because: ", ecode.message ()); Terminate(); } void HTTPReqHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (!stream) { LogPrint (eLogError, "HTTPProxy: error when creating the stream, check the previous warnings for more info"); GenericProxyError("Host is down", "Can't create connection to requested host, it may be down. Please try again later."); return; } if (Kill()) return; LogPrint (eLogDebug, "HTTPProxy: Created new I2PTunnel stream, sSID=", stream->GetSendStreamID(), ", rSID=", stream->GetRecvStreamID()); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (reinterpret_cast(m_send_buf.data()), m_send_buf.length()); Done (shared_from_this()); } HTTPProxy::HTTPProxy(const std::string& name, const std::string& address, int port, const std::string & outproxy, std::shared_ptr localDestination): TCPIPAcceptor(address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), m_Name (name), m_OutproxyUrl(outproxy) { } std::shared_ptr HTTPProxy::CreateHandler(std::shared_ptr socket) { return std::make_shared (this, socket); } } // http } // i2p i2pd-2.17.0/libi2pd_client/HTTPProxy.h000066400000000000000000000016461321131324000173110ustar00rootroot00000000000000#ifndef HTTP_PROXY_H__ #define HTTP_PROXY_H__ namespace i2p { namespace proxy { class HTTPProxy: public i2p::client::TCPIPAcceptor { public: HTTPProxy(const std::string& name, const std::string& address, int port, const std::string & outproxy, std::shared_ptr localDestination); HTTPProxy(const std::string& name, const std::string& address, int port, std::shared_ptr localDestination = nullptr) : HTTPProxy(name, address, port, "", localDestination) {} ; ~HTTPProxy() {}; std::string GetOutproxyURL() const { return m_OutproxyUrl; } protected: // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return m_Name.c_str (); } private: std::string m_Name; std::string m_OutproxyUrl; }; } // http } // i2p #endif i2pd-2.17.0/libi2pd_client/I2CP.cpp000066400000000000000000000545351321131324000165250ustar00rootroot00000000000000/* * Copyright (c) 2013-2016, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ #include #include #include #include "I2PEndian.h" #include "Log.h" #include "Timestamp.h" #include "LeaseSet.h" #include "ClientContext.h" #include "Transports.h" #include "Signature.h" #include "I2CP.h" namespace i2p { namespace client { I2CPDestination::I2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params): LeaseSetDestination (isPublic, ¶ms), m_Owner (owner), m_Identity (identity) { } void I2CPDestination::SetEncryptionPrivateKey (const uint8_t * key) { memcpy (m_EncryptionPrivateKey, key, 256); m_Decryptor = i2p::data::PrivateKeys::CreateDecryptor (m_Identity->GetCryptoKeyType (), m_EncryptionPrivateKey); } bool I2CPDestination::Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const { if (m_Decryptor) return m_Decryptor->Decrypt (encrypted, data, ctx); else LogPrint (eLogError, "I2CP: decryptor is not set"); return false; } void I2CPDestination::HandleDataMessage (const uint8_t * buf, size_t len) { uint32_t length = bufbe32toh (buf); if (length > len - 4) length = len - 4; m_Owner->SendMessagePayloadMessage (buf + 4, length); } void I2CPDestination::CreateNewLeaseSet (std::vector > tunnels) { i2p::data::LocalLeaseSet ls (m_Identity, m_EncryptionPrivateKey, tunnels); // we don't care about encryption key m_LeaseSetExpirationTime = ls.GetExpirationTime (); uint8_t * leases = ls.GetLeases (); leases[-1] = tunnels.size (); htobe16buf (leases - 3, m_Owner->GetSessionID ()); size_t l = 2/*sessionID*/ + 1/*num leases*/ + i2p::data::LEASE_SIZE*tunnels.size (); m_Owner->SendI2CPMessage (I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE, leases - 3, l); } void I2CPDestination::LeaseSetCreated (const uint8_t * buf, size_t len) { auto ls = new i2p::data::LocalLeaseSet (m_Identity, buf, len); ls->SetExpirationTime (m_LeaseSetExpirationTime); SetLeaseSet (ls); } void I2CPDestination::SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce) { auto msg = NewI2NPMessage (); uint8_t * buf = msg->GetPayload (); htobe32buf (buf, len); memcpy (buf + 4, payload, len); msg->len += len + 4; msg->FillI2NPMessageHeader (eI2NPData); auto s = GetSharedFromThis (); auto remote = FindLeaseSet (ident); if (remote) { GetService ().post ( [s, msg, remote, nonce]() { bool sent = s->SendMsg (msg, remote); s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); }); } else { RequestDestination (ident, [s, msg, nonce](std::shared_ptr ls) { if (ls) { bool sent = s->SendMsg (msg, ls); s->m_Owner->SendMessageStatusMessage (nonce, sent ? eI2CPMessageStatusGuaranteedSuccess : eI2CPMessageStatusGuaranteedFailure); } else s->m_Owner->SendMessageStatusMessage (nonce, eI2CPMessageStatusNoLeaseSet); }); } } bool I2CPDestination::SendMsg (std::shared_ptr msg, std::shared_ptr remote) { auto remoteSession = GetRoutingSession (remote, true); if (!remoteSession) { LogPrint (eLogError, "I2CP: Failed to create remote session"); return false; } auto path = remoteSession->GetSharedRoutingPath (); std::shared_ptr outboundTunnel; std::shared_ptr remoteLease; if (path) { if (!remoteSession->CleanupUnconfirmedTags ()) // no stuck tags { outboundTunnel = path->outboundTunnel; remoteLease = path->remoteLease; } else remoteSession->SetSharedRoutingPath (nullptr); } else { outboundTunnel = GetTunnelPool ()->GetNextOutboundTunnel (); auto leases = remote->GetNonExpiredLeases (); if (!leases.empty ()) remoteLease = leases[rand () % leases.size ()]; if (remoteLease && outboundTunnel) remoteSession->SetSharedRoutingPath (std::make_shared ( i2p::garlic::GarlicRoutingPath{outboundTunnel, remoteLease, 10000, 0, 0})); // 10 secs RTT else remoteSession->SetSharedRoutingPath (nullptr); } if (remoteLease && outboundTunnel) { std::vector msgs; auto garlic = remoteSession->WrapSingleMessage (msg); msgs.push_back (i2p::tunnel::TunnelMessageBlock { i2p::tunnel::eDeliveryTypeTunnel, remoteLease->tunnelGateway, remoteLease->tunnelID, garlic }); outboundTunnel->SendTunnelDataMsg (msgs); return true; } else { if (outboundTunnel) LogPrint (eLogWarning, "I2CP: Failed to send message. All leases expired"); else LogPrint (eLogWarning, "I2CP: Failed to send message. No outbound tunnels"); return false; } } I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): m_Owner (owner), m_Socket (socket), m_Payload (nullptr), m_SessionID (0xFFFF), m_MessageID (0), m_IsSendAccepted (true) { } I2CPSession::~I2CPSession () { delete[] m_Payload; } void I2CPSession::Start () { ReadProtocolByte (); } void I2CPSession::Stop () { Terminate (); } void I2CPSession::ReadProtocolByte () { if (m_Socket) { auto s = shared_from_this (); m_Socket->async_read_some (boost::asio::buffer (m_Header, 1), [s](const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (!ecode && bytes_transferred > 0 && s->m_Header[0] == I2CP_PROTOCOL_BYTE) s->ReceiveHeader (); else s->Terminate (); }); } } void I2CPSession::ReceiveHeader () { boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Header, I2CP_HEADER_SIZE), boost::asio::transfer_all (), std::bind (&I2CPSession::HandleReceivedHeader, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void I2CPSession::HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) Terminate (); else { m_PayloadLen = bufbe32toh (m_Header + I2CP_HEADER_LENGTH_OFFSET); if (m_PayloadLen > 0) { m_Payload = new uint8_t[m_PayloadLen]; ReceivePayload (); } else // no following payload { HandleMessage (); ReceiveHeader (); // next message } } } void I2CPSession::ReceivePayload () { boost::asio::async_read (*m_Socket, boost::asio::buffer (m_Payload, m_PayloadLen), boost::asio::transfer_all (), std::bind (&I2CPSession::HandleReceivedPayload, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void I2CPSession::HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) Terminate (); else { HandleMessage (); delete[] m_Payload; m_Payload = nullptr; m_PayloadLen = 0; ReceiveHeader (); // next message } } void I2CPSession::HandleMessage () { auto handler = m_Owner.GetMessagesHandlers ()[m_Header[I2CP_HEADER_TYPE_OFFSET]]; if (handler) (this->*handler)(m_Payload, m_PayloadLen); else LogPrint (eLogError, "I2CP: Unknown I2CP messsage ", (int)m_Header[I2CP_HEADER_TYPE_OFFSET]); } void I2CPSession::Terminate () { if (m_Destination) { m_Destination->Stop (); m_Destination = nullptr; } if (m_Socket) { m_Socket->close (); m_Socket = nullptr; } m_Owner.RemoveSession (GetSessionID ()); LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " terminated"); } void I2CPSession::SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len) { auto socket = m_Socket; if (socket) { auto l = len + I2CP_HEADER_SIZE; uint8_t * buf = new uint8_t[l]; htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len); buf[I2CP_HEADER_TYPE_OFFSET] = type; memcpy (buf + I2CP_HEADER_SIZE, payload, len); boost::asio::async_write (*socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, buf)); } else LogPrint (eLogError, "I2CP: Can't write to the socket"); } void I2CPSession::HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf) { delete[] buf; if (ecode && ecode != boost::asio::error::operation_aborted) Terminate (); } std::string I2CPSession::ExtractString (const uint8_t * buf, size_t len) { uint8_t l = buf[0]; if (l > len) l = len; return std::string ((const char *)(buf + 1), l); } size_t I2CPSession::PutString (uint8_t * buf, size_t len, const std::string& str) { auto l = str.length (); if (l + 1 >= len) l = len - 1; if (l > 255) l = 255; // 1 byte max buf[0] = l; memcpy (buf + 1, str.c_str (), l); return l + 1; } void I2CPSession::ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping) // TODO: move to Base.cpp { size_t offset = 0; while (offset < len) { std::string param = ExtractString (buf + offset, len - offset); offset += param.length () + 1; if (buf[offset] != '=') { LogPrint (eLogWarning, "I2CP: Unexpected character ", buf[offset], " instead '=' after ", param); break; } offset++; std::string value = ExtractString (buf + offset, len - offset); offset += value.length () + 1; if (buf[offset] != ';') { LogPrint (eLogWarning, "I2CP: Unexpected character ", buf[offset], " instead ';' after ", value); break; } offset++; mapping.insert (std::make_pair (param, value)); } } void I2CPSession::GetDateMessageHandler (const uint8_t * buf, size_t len) { // get version auto version = ExtractString (buf, len); auto l = version.length () + 1 + 8; uint8_t * payload = new uint8_t[l]; // set date auto ts = i2p::util::GetMillisecondsSinceEpoch (); htobe64buf (payload, ts); // echo vesrion back PutString (payload + 8, l - 8, version); SendI2CPMessage (I2CP_SET_DATE_MESSAGE, payload, l); delete[] payload; } void I2CPSession::CreateSessionMessageHandler (const uint8_t * buf, size_t len) { RAND_bytes ((uint8_t *)&m_SessionID, 2); m_Owner.InsertSession (shared_from_this ()); auto identity = std::make_shared(); size_t offset = identity->FromBuffer (buf, len); if (!offset) { LogPrint (eLogError, "I2CP: create session maformed identity"); SendSessionStatusMessage (3); // invalid return; } uint16_t optionsSize = bufbe16toh (buf + offset); offset += 2; if (optionsSize > len - offset) { LogPrint (eLogError, "I2CP: options size ", optionsSize, "exceeds message size"); SendSessionStatusMessage (3); // invalid return; } std::map params; ExtractMapping (buf + offset, optionsSize, params); offset += optionsSize; // options if (params[I2CP_PARAM_MESSAGE_RELIABILITY] == "none") m_IsSendAccepted = false; offset += 8; // date if (identity->Verify (buf, offset, buf + offset)) // signature { bool isPublic = true; if (params[I2CP_PARAM_DONT_PUBLISH_LEASESET] == "true") isPublic = false; if (!m_Destination) { m_Destination = std::make_shared(shared_from_this (), identity, isPublic, params); SendSessionStatusMessage (1); // created LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " created"); m_Destination->Start (); } else { LogPrint (eLogError, "I2CP: session already exists"); SendSessionStatusMessage (4); // refused } } else { LogPrint (eLogError, "I2CP: create session signature verification falied"); SendSessionStatusMessage (3); // invalid } } void I2CPSession::DestroySessionMessageHandler (const uint8_t * buf, size_t len) { SendSessionStatusMessage (0); // destroy LogPrint (eLogDebug, "I2CP: session ", m_SessionID, " destroyed"); if (m_Destination) { m_Destination->Stop (); m_Destination = 0; } } void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len) { // TODO: implement actual reconfiguration SendSessionStatusMessage (2); // updated } void I2CPSession::SendSessionStatusMessage (uint8_t status) { uint8_t buf[3]; htobe16buf (buf, m_SessionID); buf[2] = status; SendI2CPMessage (I2CP_SESSION_STATUS_MESSAGE, buf, 3); } void I2CPSession::SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status) { if (!nonce) return; // don't send status with zero nonce uint8_t buf[15]; htobe16buf (buf, m_SessionID); htobe32buf (buf + 2, m_MessageID++); buf[6] = (uint8_t)status; memset (buf + 7, 0, 4); // size htobe32buf (buf + 11, nonce); SendI2CPMessage (I2CP_MESSAGE_STATUS_MESSAGE, buf, 15); } void I2CPSession::CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); if (sessionID == m_SessionID) { size_t offset = 2; if (m_Destination) { offset += i2p::crypto::DSA_PRIVATE_KEY_LENGTH; // skip signing private key // we always assume this field as 20 bytes (DSA) regardless actual size // instead of //offset += m_Destination->GetIdentity ()->GetSigningPrivateKeyLen (); m_Destination->SetEncryptionPrivateKey (buf + offset); offset += 256; m_Destination->LeaseSetCreated (buf + offset, len - offset); } } else LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } void I2CPSession::SendMessageMessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); if (sessionID == m_SessionID) { size_t offset = 2; if (m_Destination) { i2p::data::IdentityEx identity; size_t identsize = identity.FromBuffer (buf + offset, len - offset); if (identsize) { offset += identsize; uint32_t payloadLen = bufbe32toh (buf + offset); if (payloadLen + offset <= len) { offset += 4; uint32_t nonce = bufbe32toh (buf + offset + payloadLen); if (m_IsSendAccepted) SendMessageStatusMessage (nonce, eI2CPMessageStatusAccepted); // accepted m_Destination->SendMsgTo (buf + offset, payloadLen, identity.GetIdentHash (), nonce); } else LogPrint(eLogError, "I2CP: cannot send message, too big"); } else LogPrint(eLogError, "I2CP: invalid identity"); } } else LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } void I2CPSession::SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len) { SendMessageMessageHandler (buf, len - 8); // ignore flags(2) and expiration(6) } void I2CPSession::HostLookupMessageHandler (const uint8_t * buf, size_t len) { uint16_t sessionID = bufbe16toh (buf); if (sessionID == m_SessionID || sessionID == 0xFFFF) // -1 means without session { uint32_t requestID = bufbe32toh (buf + 2); //uint32_t timeout = bufbe32toh (buf + 6); i2p::data::IdentHash ident; switch (buf[10]) { case 0: // hash ident = i2p::data::IdentHash (buf + 11); break; case 1: // address { auto name = ExtractString (buf + 11, len - 11); if (!i2p::client::context.GetAddressBook ().GetIdentHash (name, ident)) { LogPrint (eLogError, "I2CP: address ", name, " not found"); SendHostReplyMessage (requestID, nullptr); return; } break; } default: LogPrint (eLogError, "I2CP: request type ", (int)buf[10], " is not supported"); SendHostReplyMessage (requestID, nullptr); return; } std::shared_ptr destination = m_Destination; if(!destination) destination = i2p::client::context.GetSharedLocalDestination (); if (destination) { auto ls = destination->FindLeaseSet (ident); if (ls) SendHostReplyMessage (requestID, ls->GetIdentity ()); else { auto s = shared_from_this (); destination->RequestDestination (ident, [s, requestID](std::shared_ptr leaseSet) { s->SendHostReplyMessage (requestID, leaseSet ? leaseSet->GetIdentity () : nullptr); }); } } else SendHostReplyMessage (requestID, nullptr); } else LogPrint (eLogError, "I2CP: unexpected sessionID ", sessionID); } void I2CPSession::SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity) { if (identity) { size_t l = identity->GetFullLen () + 7; uint8_t * buf = new uint8_t[l]; htobe16buf (buf, m_SessionID); htobe32buf (buf + 2, requestID); buf[6] = 0; // result code identity->ToBuffer (buf + 7, l - 7); SendI2CPMessage (I2CP_HOST_REPLY_MESSAGE, buf, l); delete[] buf; } else { uint8_t buf[7]; htobe16buf (buf, m_SessionID); htobe32buf (buf + 2, requestID); buf[6] = 1; // result code SendI2CPMessage (I2CP_HOST_REPLY_MESSAGE, buf, 7); } } void I2CPSession::DestLookupMessageHandler (const uint8_t * buf, size_t len) { if (m_Destination) { auto ls = m_Destination->FindLeaseSet (buf); if (ls) { auto l = ls->GetIdentity ()->GetFullLen (); uint8_t * identBuf = new uint8_t[l]; ls->GetIdentity ()->ToBuffer (identBuf, l); SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, identBuf, l); delete[] identBuf; } else { auto s = shared_from_this (); i2p::data::IdentHash ident (buf); m_Destination->RequestDestination (ident, [s, ident](std::shared_ptr leaseSet) { if (leaseSet) // found { auto l = leaseSet->GetIdentity ()->GetFullLen (); uint8_t * identBuf = new uint8_t[l]; leaseSet->GetIdentity ()->ToBuffer (identBuf, l); s->SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, identBuf, l); delete[] identBuf; } else s->SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, ident, 32); // not found }); } } else SendI2CPMessage (I2CP_DEST_REPLY_MESSAGE, buf, 32); } void I2CPSession::GetBandwidthLimitsMessageHandler (const uint8_t * buf, size_t len) { uint8_t limits[64]; memset (limits, 0, 64); htobe32buf (limits, i2p::transport::transports.GetInBandwidth ()); // inbound htobe32buf (limits + 4, i2p::transport::transports.GetOutBandwidth ()); // outbound SendI2CPMessage (I2CP_BANDWIDTH_LIMITS_MESSAGE, limits, 64); } void I2CPSession::SendMessagePayloadMessage (const uint8_t * payload, size_t len) { // we don't use SendI2CPMessage to eliminate additional copy auto l = len + 10 + I2CP_HEADER_SIZE; uint8_t * buf = new uint8_t[l]; htobe32buf (buf + I2CP_HEADER_LENGTH_OFFSET, len + 10); buf[I2CP_HEADER_TYPE_OFFSET] = I2CP_MESSAGE_PAYLOAD_MESSAGE; htobe16buf (buf + I2CP_HEADER_SIZE, m_SessionID); htobe32buf (buf + I2CP_HEADER_SIZE + 2, m_MessageID++); htobe32buf (buf + I2CP_HEADER_SIZE + 6, len); memcpy (buf + I2CP_HEADER_SIZE + 10, payload, len); boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, l), boost::asio::transfer_all (), std::bind(&I2CPSession::HandleI2CPMessageSent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, buf)); } I2CPServer::I2CPServer (const std::string& interface, int port): m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, #ifdef ANDROID I2CPSession::proto::endpoint(std::string (1, '\0') + interface)) // leading 0 for abstract address #else I2CPSession::proto::endpoint(boost::asio::ip::address::from_string(interface), port)) #endif { memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; m_MessagesHandlers[I2CP_CREATE_SESSION_MESSAGE] = &I2CPSession::CreateSessionMessageHandler; m_MessagesHandlers[I2CP_DESTROY_SESSION_MESSAGE] = &I2CPSession::DestroySessionMessageHandler; m_MessagesHandlers[I2CP_RECONFIGURE_SESSION_MESSAGE] = &I2CPSession::ReconfigureSessionMessageHandler; m_MessagesHandlers[I2CP_CREATE_LEASESET_MESSAGE] = &I2CPSession::CreateLeaseSetMessageHandler; m_MessagesHandlers[I2CP_SEND_MESSAGE_MESSAGE] = &I2CPSession::SendMessageMessageHandler; m_MessagesHandlers[I2CP_SEND_MESSAGE_EXPIRES_MESSAGE] = &I2CPSession::SendMessageExpiresMessageHandler; m_MessagesHandlers[I2CP_HOST_LOOKUP_MESSAGE] = &I2CPSession::HostLookupMessageHandler; m_MessagesHandlers[I2CP_DEST_LOOKUP_MESSAGE] = &I2CPSession::DestLookupMessageHandler; m_MessagesHandlers[I2CP_GET_BANDWIDTH_LIMITS_MESSAGE] = &I2CPSession::GetBandwidthLimitsMessageHandler; } I2CPServer::~I2CPServer () { if (m_IsRunning) Stop (); } void I2CPServer::Start () { Accept (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&I2CPServer::Run, this)); } void I2CPServer::Stop () { m_IsRunning = false; m_Acceptor.cancel (); for (auto& it: m_Sessions) it.second->Stop (); m_Sessions.clear (); m_Service.stop (); if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = nullptr; } } void I2CPServer::Run () { while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, "I2CP: runtime exception: ", ex.what ()); } } } void I2CPServer::Accept () { auto newSocket = std::make_shared (m_Service); m_Acceptor.async_accept (*newSocket, std::bind (&I2CPServer::HandleAccept, this, std::placeholders::_1, newSocket)); } void I2CPServer::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) { if (!ecode && socket) { boost::system::error_code ec; auto ep = socket->remote_endpoint (ec); if (!ec) { LogPrint (eLogDebug, "I2CP: new connection from ", ep); auto session = std::make_shared(*this, socket); session->Start (); } else LogPrint (eLogError, "I2CP: incoming connection error ", ec.message ()); } else LogPrint (eLogError, "I2CP: accept error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Accept (); } bool I2CPServer::InsertSession (std::shared_ptr session) { if (!session) return false; if (!m_Sessions.insert({session->GetSessionID (), session}).second) { LogPrint (eLogError, "I2CP: duplicate session id ", session->GetSessionID ()); return false; } return true; } void I2CPServer::RemoveSession (uint16_t sessionID) { m_Sessions.erase (sessionID); } } } i2pd-2.17.0/libi2pd_client/I2CP.h000066400000000000000000000156631321131324000161710ustar00rootroot00000000000000/* * Copyright (c) 2013-2016, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ #ifndef I2CP_H__ #define I2CP_H__ #include #include #include #include #include #include #include "Destination.h" namespace i2p { namespace client { const uint8_t I2CP_PROTOCOL_BYTE = 0x2A; const size_t I2CP_SESSION_BUFFER_SIZE = 4096; const size_t I2CP_HEADER_LENGTH_OFFSET = 0; const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; const size_t I2CP_HEADER_SIZE = I2CP_HEADER_TYPE_OFFSET + 1; const uint8_t I2CP_GET_DATE_MESSAGE = 32; const uint8_t I2CP_SET_DATE_MESSAGE = 33; const uint8_t I2CP_CREATE_SESSION_MESSAGE = 1; const uint8_t I2CP_RECONFIGURE_SESSION_MESSAGE = 2; const uint8_t I2CP_SESSION_STATUS_MESSAGE = 20; const uint8_t I2CP_DESTROY_SESSION_MESSAGE = 3; const uint8_t I2CP_REQUEST_VARIABLE_LEASESET_MESSAGE = 37; const uint8_t I2CP_CREATE_LEASESET_MESSAGE = 4; const uint8_t I2CP_SEND_MESSAGE_MESSAGE = 5; const uint8_t I2CP_SEND_MESSAGE_EXPIRES_MESSAGE = 36; const uint8_t I2CP_MESSAGE_PAYLOAD_MESSAGE = 31; const uint8_t I2CP_MESSAGE_STATUS_MESSAGE = 22; const uint8_t I2CP_HOST_LOOKUP_MESSAGE = 38; const uint8_t I2CP_HOST_REPLY_MESSAGE = 39; const uint8_t I2CP_DEST_LOOKUP_MESSAGE = 34; const uint8_t I2CP_DEST_REPLY_MESSAGE = 35; const uint8_t I2CP_GET_BANDWIDTH_LIMITS_MESSAGE = 8; const uint8_t I2CP_BANDWIDTH_LIMITS_MESSAGE = 23; enum I2CPMessageStatus { eI2CPMessageStatusAccepted = 1, eI2CPMessageStatusGuaranteedSuccess = 4, eI2CPMessageStatusGuaranteedFailure = 5, eI2CPMessageStatusNoLeaseSet = 21 }; // params const char I2CP_PARAM_DONT_PUBLISH_LEASESET[] = "i2cp.dontPublishLeaseSet"; const char I2CP_PARAM_MESSAGE_RELIABILITY[] = "i2cp.messageReliability"; class I2CPSession; class I2CPDestination: public LeaseSetDestination { public: I2CPDestination (std::shared_ptr owner, std::shared_ptr identity, bool isPublic, const std::map& params); void SetEncryptionPrivateKey (const uint8_t * key); void LeaseSetCreated (const uint8_t * buf, size_t len); // called from I2CPSession void SendMsgTo (const uint8_t * payload, size_t len, const i2p::data::IdentHash& ident, uint32_t nonce); // called from I2CPSession // implements LocalDestination bool Decrypt (const uint8_t * encrypted, uint8_t * data, BN_CTX * ctx) const; std::shared_ptr GetIdentity () const { return m_Identity; }; protected: // I2CP void HandleDataMessage (const uint8_t * buf, size_t len); void CreateNewLeaseSet (std::vector > tunnels); private: std::shared_ptr GetSharedFromThis () { return std::static_pointer_cast(shared_from_this ()); } bool SendMsg (std::shared_ptr msg, std::shared_ptr remote); private: std::shared_ptr m_Owner; std::shared_ptr m_Identity; uint8_t m_EncryptionPrivateKey[256]; std::shared_ptr m_Decryptor; uint64_t m_LeaseSetExpirationTime; }; class I2CPServer; class I2CPSession: public std::enable_shared_from_this { public: #ifdef ANDROID typedef boost::asio::local::stream_protocol proto; #else typedef boost::asio::ip::tcp proto; #endif I2CPSession (I2CPServer& owner, std::shared_ptr socket); ~I2CPSession (); void Start (); void Stop (); uint16_t GetSessionID () const { return m_SessionID; }; std::shared_ptr GetDestination () const { return m_Destination; }; // called from I2CPDestination void SendI2CPMessage (uint8_t type, const uint8_t * payload, size_t len); void SendMessagePayloadMessage (const uint8_t * payload, size_t len); void SendMessageStatusMessage (uint32_t nonce, I2CPMessageStatus status); // message handlers void GetDateMessageHandler (const uint8_t * buf, size_t len); void CreateSessionMessageHandler (const uint8_t * buf, size_t len); void DestroySessionMessageHandler (const uint8_t * buf, size_t len); void ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len); void CreateLeaseSetMessageHandler (const uint8_t * buf, size_t len); void SendMessageMessageHandler (const uint8_t * buf, size_t len); void SendMessageExpiresMessageHandler (const uint8_t * buf, size_t len); void HostLookupMessageHandler (const uint8_t * buf, size_t len); void DestLookupMessageHandler (const uint8_t * buf, size_t len); void GetBandwidthLimitsMessageHandler (const uint8_t * buf, size_t len); private: void ReadProtocolByte (); void ReceiveHeader (); void HandleReceivedHeader (const boost::system::error_code& ecode, std::size_t bytes_transferred); void ReceivePayload (); void HandleReceivedPayload (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleMessage (); void Terminate (); void HandleI2CPMessageSent (const boost::system::error_code& ecode, std::size_t bytes_transferred, const uint8_t * buf); std::string ExtractString (const uint8_t * buf, size_t len); size_t PutString (uint8_t * buf, size_t len, const std::string& str); void ExtractMapping (const uint8_t * buf, size_t len, std::map& mapping); void SendSessionStatusMessage (uint8_t status); void SendHostReplyMessage (uint32_t requestID, std::shared_ptr identity); private: I2CPServer& m_Owner; std::shared_ptr m_Socket; uint8_t m_Header[I2CP_HEADER_SIZE], * m_Payload; size_t m_PayloadLen; std::shared_ptr m_Destination; uint16_t m_SessionID; uint32_t m_MessageID; bool m_IsSendAccepted; }; typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); class I2CPServer { public: I2CPServer (const std::string& interface, int port); ~I2CPServer (); void Start (); void Stop (); boost::asio::io_service& GetService () { return m_Service; }; bool InsertSession (std::shared_ptr session); void RemoveSession (uint16_t sessionID); private: void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); private: I2CPMessageHandler m_MessagesHandlers[256]; std::map > m_Sessions; bool m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; I2CPSession::proto::acceptor m_Acceptor; public: const decltype(m_MessagesHandlers)& GetMessagesHandlers () const { return m_MessagesHandlers; }; // for HTTP const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; }; } } #endif i2pd-2.17.0/libi2pd_client/I2PService.cpp000066400000000000000000000213331321131324000177310ustar00rootroot00000000000000#include "Destination.h" #include "Identity.h" #include "ClientContext.h" #include "I2PService.h" #include namespace i2p { namespace client { static const i2p::data::SigningKeyType I2P_SERVICE_DEFAULT_KEY_TYPE = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; I2PService::I2PService (std::shared_ptr localDestination): m_LocalDestination (localDestination ? localDestination : i2p::client::context.CreateNewLocalDestination (false, I2P_SERVICE_DEFAULT_KEY_TYPE)), m_ReadyTimer(m_LocalDestination->GetService()), m_ConnectTimeout(0), isUpdated (true) { m_LocalDestination->Acquire (); } I2PService::I2PService (i2p::data::SigningKeyType kt): m_LocalDestination (i2p::client::context.CreateNewLocalDestination (false, kt)), m_ReadyTimer(m_LocalDestination->GetService()), m_ConnectTimeout(0), isUpdated (true) { m_LocalDestination->Acquire (); } I2PService::~I2PService () { ClearHandlers (); if (m_LocalDestination) m_LocalDestination->Release (); } void I2PService::ClearHandlers () { if(m_ConnectTimeout) m_ReadyTimer.cancel(); std::unique_lock l(m_HandlersMutex); for (auto it: m_Handlers) it->Terminate (); m_Handlers.clear(); } void I2PService::SetConnectTimeout(uint32_t timeout) { if(timeout && !m_ConnectTimeout) { TriggerReadyCheckTimer(); } else if (m_ConnectTimeout && !timeout) { m_ReadyTimer.cancel(); } m_ConnectTimeout = timeout; } void I2PService::AddReadyCallback(ReadyCallback cb) { uint32_t now = i2p::util::GetSecondsSinceEpoch(); uint32_t tm = now + m_ConnectTimeout; LogPrint(eLogDebug, "I2PService::AddReadyCallback() ", tm, " ", now); m_ReadyCallbacks.push_back({cb, tm}); } void I2PService::TriggerReadyCheckTimer() { m_ReadyTimer.expires_from_now(boost::posix_time::seconds (1)); m_ReadyTimer.async_wait(std::bind(&I2PService::HandleReadyCheckTimer, this, std::placeholders::_1)); } void I2PService::HandleReadyCheckTimer(const boost::system::error_code &ec) { if(ec || m_LocalDestination->IsReady()) { for(auto & itr : m_ReadyCallbacks) itr.first(ec); m_ReadyCallbacks.clear(); } else if(!m_LocalDestination->IsReady()) { // expire timed out requests uint32_t now = i2p::util::GetSecondsSinceEpoch (); auto itr = m_ReadyCallbacks.begin(); while(itr != m_ReadyCallbacks.end()) { if(itr->second >= now) { itr->first(boost::asio::error::timed_out); itr = m_ReadyCallbacks.erase(itr); } else ++itr; } } if(!ec) TriggerReadyCheckTimer(); } void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port) { assert(streamRequestComplete); i2p::data::IdentHash identHash; if (i2p::client::context.GetAddressBook ().GetIdentHash (dest, identHash)) CreateStream(streamRequestComplete, identHash, port); else { LogPrint (eLogWarning, "I2PService: Remote destination not found: ", dest); streamRequestComplete (nullptr); } } void I2PService::CreateStream(StreamRequestComplete streamRequestComplete, const i2p::data::IdentHash & identHash, int port) { if(m_ConnectTimeout) { if(m_LocalDestination->IsReady()) m_LocalDestination->CreateStream (streamRequestComplete, identHash, port); else { AddReadyCallback([this, streamRequestComplete, identHash, port] (const boost::system::error_code & ec) { if(ec) { LogPrint(eLogWarning, "I2PService::CeateStream() ", ec.message()); streamRequestComplete(nullptr); } else this->m_LocalDestination->CreateStream(streamRequestComplete, identHash, port); }); } } else m_LocalDestination->CreateStream(streamRequestComplete, identHash, port); } TCPIPPipe::TCPIPPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream) : I2PServiceHandler(owner), m_up(upstream), m_down(downstream) { boost::asio::socket_base::receive_buffer_size option(TCP_IP_PIPE_BUFFER_SIZE); upstream->set_option(option); downstream->set_option(option); } TCPIPPipe::~TCPIPPipe() { Terminate(); } void TCPIPPipe::Start() { AsyncReceiveUpstream(); AsyncReceiveDownstream(); } void TCPIPPipe::Terminate() { if(Kill()) return; if (m_up) { if (m_up->is_open()) m_up->close(); m_up = nullptr; } if (m_down) { if (m_down->is_open()) m_down->close(); m_down = nullptr; } Done(shared_from_this()); } void TCPIPPipe::AsyncReceiveUpstream() { if (m_up) { m_up->async_read_some(boost::asio::buffer(m_upstream_to_down_buf, TCP_IP_PIPE_BUFFER_SIZE), std::bind(&TCPIPPipe::HandleUpstreamReceived, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else LogPrint(eLogError, "TCPIPPipe: upstream receive: no socket"); } void TCPIPPipe::AsyncReceiveDownstream() { if (m_down) { m_down->async_read_some(boost::asio::buffer(m_downstream_to_up_buf, TCP_IP_PIPE_BUFFER_SIZE), std::bind(&TCPIPPipe::HandleDownstreamReceived, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else LogPrint(eLogError, "TCPIPPipe: downstream receive: no socket"); } void TCPIPPipe::UpstreamWrite(size_t len) { if (m_up) { LogPrint(eLogDebug, "TCPIPPipe: upstream: ", (int) len, " bytes written"); boost::asio::async_write(*m_up, boost::asio::buffer(m_upstream_buf, len), boost::asio::transfer_all(), std::bind(&TCPIPPipe::HandleUpstreamWrite, shared_from_this(), std::placeholders::_1)); } else LogPrint(eLogError, "TCPIPPipe: upstream write: no socket"); } void TCPIPPipe::DownstreamWrite(size_t len) { if (m_down) { LogPrint(eLogDebug, "TCPIPPipe: downstream: ", (int) len, " bytes written"); boost::asio::async_write(*m_down, boost::asio::buffer(m_downstream_buf, len), boost::asio::transfer_all(), std::bind(&TCPIPPipe::HandleDownstreamWrite, shared_from_this(), std::placeholders::_1)); } else LogPrint(eLogError, "TCPIPPipe: downstream write: no socket"); } void TCPIPPipe::HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) { LogPrint(eLogDebug, "TCPIPPipe: downstream: ", (int) bytes_transfered, " bytes received"); if (ecode) { LogPrint(eLogError, "TCPIPPipe: downstream read error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } else { if (bytes_transfered > 0 ) memcpy(m_upstream_buf, m_downstream_to_up_buf, bytes_transfered); UpstreamWrite(bytes_transfered); } } void TCPIPPipe::HandleDownstreamWrite(const boost::system::error_code & ecode) { if (ecode) { LogPrint(eLogError, "TCPIPPipe: downstream write error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } else AsyncReceiveUpstream(); } void TCPIPPipe::HandleUpstreamWrite(const boost::system::error_code & ecode) { if (ecode) { LogPrint(eLogError, "TCPIPPipe: upstream write error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } else AsyncReceiveDownstream(); } void TCPIPPipe::HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transfered) { LogPrint(eLogDebug, "TCPIPPipe: upstream ", (int)bytes_transfered, " bytes received"); if (ecode) { LogPrint(eLogError, "TCPIPPipe: upstream read error:" , ecode.message()); if (ecode != boost::asio::error::operation_aborted) Terminate(); } else { if (bytes_transfered > 0 ) memcpy(m_downstream_buf, m_upstream_to_down_buf, bytes_transfered); DownstreamWrite(bytes_transfered); } } void TCPIPAcceptor::Start () { m_Acceptor.reset (new boost::asio::ip::tcp::acceptor (GetService (), m_LocalEndpoint)); m_Acceptor->listen (); Accept (); } void TCPIPAcceptor::Stop () { if (m_Acceptor) { m_Acceptor->close(); m_Acceptor.reset (nullptr); } m_Timer.cancel (); ClearHandlers(); } void TCPIPAcceptor::Accept () { auto newSocket = std::make_shared (GetService ()); m_Acceptor->async_accept (*newSocket, std::bind (&TCPIPAcceptor::HandleAccept, this, std::placeholders::_1, newSocket)); } void TCPIPAcceptor::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr socket) { if (!ecode) { LogPrint(eLogDebug, "I2PService: ", GetName(), " accepted"); auto handler = CreateHandler(socket); if (handler) { AddHandler(handler); handler->Handle(); } else socket->close(); Accept(); } else { if (ecode != boost::asio::error::operation_aborted) LogPrint (eLogError, "I2PService: ", GetName(), " closing socket on accept because: ", ecode.message ()); } } } } i2pd-2.17.0/libi2pd_client/I2PService.h000066400000000000000000000136741321131324000174070ustar00rootroot00000000000000#ifndef I2PSERVICE_H__ #define I2PSERVICE_H__ #include #include #include #include #include #include "Destination.h" #include "Identity.h" namespace i2p { namespace client { class I2PServiceHandler; class I2PService : std::enable_shared_from_this { public: typedef std::function ReadyCallback; public: I2PService (std::shared_ptr localDestination = nullptr); I2PService (i2p::data::SigningKeyType kt); virtual ~I2PService (); inline void AddHandler (std::shared_ptr conn) { std::unique_lock l(m_HandlersMutex); m_Handlers.insert(conn); } inline void RemoveHandler (std::shared_ptr conn) { std::unique_lock l(m_HandlersMutex); m_Handlers.erase(conn); } void ClearHandlers (); void SetConnectTimeout(uint32_t timeout); void AddReadyCallback(ReadyCallback cb); inline std::shared_ptr GetLocalDestination () { return m_LocalDestination; } inline std::shared_ptr GetLocalDestination () const { return m_LocalDestination; } inline void SetLocalDestination (std::shared_ptr dest) { if (m_LocalDestination) m_LocalDestination->Release (); if (dest) dest->Acquire (); m_LocalDestination = dest; } void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port = 0); void CreateStream(StreamRequestComplete complete, const i2p::data::IdentHash & ident, int port); inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); } virtual void Start () = 0; virtual void Stop () = 0; virtual const char* GetName() { return "Generic I2P Service"; } private: void TriggerReadyCheckTimer(); void HandleReadyCheckTimer(const boost::system::error_code & ec); private: std::shared_ptr m_LocalDestination; std::unordered_set > m_Handlers; std::mutex m_HandlersMutex; std::vector > m_ReadyCallbacks; boost::asio::deadline_timer m_ReadyTimer; uint32_t m_ConnectTimeout; public: bool isUpdated; // transient, used during reload only }; /*Simple interface for I2PHandlers, allows detection of finalization amongst other things */ class I2PServiceHandler { public: I2PServiceHandler(I2PService * parent) : m_Service(parent), m_Dead(false) { } virtual ~I2PServiceHandler() { } //If you override this make sure you call it from the children virtual void Handle() {}; //Start handling the socket void Terminate () { Kill (); }; protected: // Call when terminating or handing over to avoid race conditions inline bool Kill () { return m_Dead.exchange(true); } // Call to know if the handler is dead inline bool Dead () { return m_Dead; } // Call when done to clean up (make sure Kill is called first) inline void Done (std::shared_ptr me) { if(m_Service) m_Service->RemoveHandler(me); } // Call to talk with the owner inline I2PService * GetOwner() { return m_Service; } private: I2PService *m_Service; std::atomic m_Dead; //To avoid cleaning up multiple times }; const size_t TCP_IP_PIPE_BUFFER_SIZE = 8192 * 8; // bidirectional pipe for 2 tcp/ip sockets class TCPIPPipe: public I2PServiceHandler, public std::enable_shared_from_this { public: TCPIPPipe(I2PService * owner, std::shared_ptr upstream, std::shared_ptr downstream); ~TCPIPPipe(); void Start(); protected: void Terminate(); void AsyncReceiveUpstream(); void AsyncReceiveDownstream(); void HandleUpstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred); void HandleDownstreamReceived(const boost::system::error_code & ecode, std::size_t bytes_transferred); void HandleUpstreamWrite(const boost::system::error_code & ecode); void HandleDownstreamWrite(const boost::system::error_code & ecode); void UpstreamWrite(size_t len); void DownstreamWrite(size_t len); private: uint8_t m_upstream_to_down_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_to_up_buf[TCP_IP_PIPE_BUFFER_SIZE]; uint8_t m_upstream_buf[TCP_IP_PIPE_BUFFER_SIZE], m_downstream_buf[TCP_IP_PIPE_BUFFER_SIZE]; std::shared_ptr m_up, m_down; }; /* TODO: support IPv6 too */ //This is a service that listens for connections on the IP network and interacts with I2P class TCPIPAcceptor: public I2PService { public: TCPIPAcceptor (const std::string& address, int port, std::shared_ptr localDestination = nullptr) : I2PService(localDestination), m_LocalEndpoint (boost::asio::ip::address::from_string(address), port), m_Timer (GetService ()) {} TCPIPAcceptor (const std::string& address, int port, i2p::data::SigningKeyType kt) : I2PService(kt), m_LocalEndpoint (boost::asio::ip::address::from_string(address), port), m_Timer (GetService ()) {} virtual ~TCPIPAcceptor () { TCPIPAcceptor::Stop(); } //If you override this make sure you call it from the children void Start (); //If you override this make sure you call it from the children void Stop (); const boost::asio::ip::tcp::endpoint& GetLocalEndpoint () const { return m_LocalEndpoint; }; virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; } protected: virtual std::shared_ptr CreateHandler(std::shared_ptr socket) = 0; private: void Accept(); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); boost::asio::ip::tcp::endpoint m_LocalEndpoint; std::unique_ptr m_Acceptor; boost::asio::deadline_timer m_Timer; }; } } #endif i2pd-2.17.0/libi2pd_client/I2PTunnel.cpp000066400000000000000000000666671321131324000176210ustar00rootroot00000000000000#include #include "Base.h" #include "Log.h" #include "Destination.h" #include "ClientContext.h" #include "I2PTunnel.h" namespace i2p { namespace client { /** set standard socket options */ static void I2PTunnelSetSocketOptions(std::shared_ptr socket) { if (socket && socket->is_open()) { boost::asio::socket_base::receive_buffer_size option(I2P_TUNNEL_CONNECTION_BUFFER_SIZE); socket->set_option(option); } } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr leaseSet, int port): I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port); } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream): I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true) { } I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, bool quiet): I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream), m_RemoteEndpoint (target), m_IsQuiet (quiet) { } I2PTunnelConnection::~I2PTunnelConnection () { } void I2PTunnelConnection::I2PConnect (const uint8_t * msg, size_t len) { if (m_Stream) { if (msg) m_Stream->Send (msg, len); // connect and send else m_Stream->Send (m_Buffer, 0); // connect } StreamReceive (); Receive (); } static boost::asio::ip::address GetLoopbackAddressFor(const i2p::data::IdentHash & addr) { boost::asio::ip::address_v4::bytes_type bytes; const uint8_t * ident = addr; bytes[0] = 127; memcpy (bytes.data ()+1, ident, 3); boost::asio::ip::address ourIP = boost::asio::ip::address_v4 (bytes); return ourIP; } static void MapToLoopback(const std::shared_ptr & sock, const i2p::data::IdentHash & addr) { // bind to 127.x.x.x address // where x.x.x are first three bytes from ident auto ourIP = GetLoopbackAddressFor(addr); sock->bind (boost::asio::ip::tcp::endpoint (ourIP, 0)); } void I2PTunnelConnection::Connect (bool isUniqueLocal) { I2PTunnelSetSocketOptions(m_Socket); if (m_Socket) { #ifdef __linux__ if (isUniqueLocal && m_RemoteEndpoint.address ().is_v4 () && m_RemoteEndpoint.address ().to_v4 ().to_bytes ()[0] == 127) { m_Socket->open (boost::asio::ip::tcp::v4 ()); auto ident = m_Stream->GetRemoteIdentity()->GetIdentHash(); MapToLoopback(m_Socket, ident); } #endif m_Socket->async_connect (m_RemoteEndpoint, std::bind (&I2PTunnelConnection::HandleConnect, shared_from_this (), std::placeholders::_1)); } } void I2PTunnelConnection::Terminate () { if (Kill()) return; if (m_Stream) { m_Stream->Close (); m_Stream.reset (); } boost::system::error_code ec; m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec); // avoid RST m_Socket->close (); Done(shared_from_this ()); } void I2PTunnelConnection::Receive () { m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), std::bind(&I2PTunnelConnection::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void I2PTunnelConnection::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "I2PTunnel: read error: ", ecode.message ()); Terminate (); } } else { if (m_Stream) { auto s = shared_from_this (); m_Stream->AsyncSend (m_Buffer, bytes_transferred, [s](const boost::system::error_code& ecode) { if (!ecode) s->Receive (); else s->Terminate (); }); } } } void I2PTunnelConnection::HandleWrite (const boost::system::error_code& ecode) { if (ecode) { LogPrint (eLogError, "I2PTunnel: write error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate (); } else StreamReceive (); } void I2PTunnelConnection::StreamReceive () { if (m_Stream) { if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular { m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE), std::bind (&I2PTunnelConnection::HandleStreamReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2), I2P_TUNNEL_CONNECTION_MAX_IDLE); } else // closed by peer { // get remaning data auto len = m_Stream->ReadSome (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE); if (len > 0) // still some data Write (m_StreamBuffer, len); else // no more data Terminate (); } } } void I2PTunnelConnection::HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { if (ecode != boost::asio::error::operation_aborted) { LogPrint (eLogError, "I2PTunnel: stream read error: ", ecode.message ()); if (bytes_transferred > 0) Write (m_StreamBuffer, bytes_transferred); // postpone termination else if (ecode == boost::asio::error::timed_out && m_Stream && m_Stream->IsOpen ()) StreamReceive (); else Terminate (); } else Terminate (); } else Write (m_StreamBuffer, bytes_transferred); } void I2PTunnelConnection::Write (const uint8_t * buf, size_t len) { boost::asio::async_write (*m_Socket, boost::asio::buffer (buf, len), boost::asio::transfer_all (), std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); } void I2PTunnelConnection::HandleConnect (const boost::system::error_code& ecode) { if (ecode) { LogPrint (eLogError, "I2PTunnel: connect error: ", ecode.message ()); Terminate (); } else { LogPrint (eLogDebug, "I2PTunnel: connected"); if (m_IsQuiet) StreamReceive (); else { // send destination first like received from I2P std::string dest = m_Stream->GetRemoteIdentity ()->ToBase64 (); dest += "\n"; if(sizeof(m_StreamBuffer) >= dest.size()) { memcpy (m_StreamBuffer, dest.c_str (), dest.size ()); } HandleStreamReceive (boost::system::error_code (), dest.size ()); } Receive (); } } void I2PClientTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) { if (m_HeaderSent) I2PTunnelConnection::Write (buf, len); else { m_InHeader.clear (); m_InHeader.write ((const char *)buf, len); std::string line; bool endOfHeader = false; while (!endOfHeader) { std::getline(m_InHeader, line); if (!m_InHeader.fail ()) { if (line == "\r") endOfHeader = true; else { if (!m_ConnectionSent && !line.compare(0, 10, "Connection")) { m_OutHeader << "Connection: close\r\n"; m_ConnectionSent = true; } else if (!m_ProxyConnectionSent && !line.compare(0, 16, "Proxy-Connection")) { m_OutHeader << "Proxy-Connection: close\r\n"; m_ProxyConnectionSent = true; } else m_OutHeader << line << "\n"; } } else break; } if (endOfHeader) { if (!m_ConnectionSent) m_OutHeader << "Connection: close\r\n"; if (!m_ProxyConnectionSent) m_OutHeader << "Proxy-Connection: close\r\n"; m_OutHeader << "\r\n"; // end of header m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header m_InHeader.str (""); m_HeaderSent = true; I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); } } } I2PServerTunnelConnectionHTTP::I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& host): I2PTunnelConnection (owner, stream, socket, target), m_Host (host), m_HeaderSent (false), m_From (stream->GetRemoteIdentity ()) { } void I2PServerTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len) { if (m_HeaderSent) I2PTunnelConnection::Write (buf, len); else { m_InHeader.clear (); m_InHeader.write ((const char *)buf, len); std::string line; bool endOfHeader = false; while (!endOfHeader) { std::getline(m_InHeader, line); if (!m_InHeader.fail ()) { if (line == "\r") endOfHeader = true; else { if (m_Host.length () > 0 && line.find ("Host:") != std::string::npos) m_OutHeader << "Host: " << m_Host << "\r\n"; // override host else m_OutHeader << line << "\n"; } } else break; } // add X-I2P fields if (m_From) { m_OutHeader << X_I2P_DEST_B32 << ": " << context.GetAddressBook ().ToAddress(m_From->GetIdentHash ()) << "\r\n"; m_OutHeader << X_I2P_DEST_HASH << ": " << m_From->GetIdentHash ().ToBase64 () << "\r\n"; m_OutHeader << X_I2P_DEST_B64 << ": " << m_From->ToBase64 () << "\r\n"; } if (endOfHeader) { m_OutHeader << "\r\n"; // end of header m_OutHeader << m_InHeader.str ().substr (m_InHeader.tellg ()); // data right after header m_InHeader.str (""); m_HeaderSent = true; I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ()); } } } I2PTunnelConnectionIRC::I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& webircpass): I2PTunnelConnection (owner, stream, socket, target), m_From (stream->GetRemoteIdentity ()), m_NeedsWebIrc (webircpass.length() ? true : false), m_WebircPass (webircpass) { } void I2PTunnelConnectionIRC::Write (const uint8_t * buf, size_t len) { m_OutPacket.str (""); if (m_NeedsWebIrc) { m_NeedsWebIrc = false; m_OutPacket << "WEBIRC " << m_WebircPass << " cgiirc " << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()) << " " << GetSocket ()->local_endpoint ().address () << std::endl; } m_InPacket.clear (); m_InPacket.write ((const char *)buf, len); while (!m_InPacket.eof () && !m_InPacket.fail ()) { std::string line; std::getline (m_InPacket, line); if (line.length () == 0 && m_InPacket.eof ()) m_InPacket.str (""); auto pos = line.find ("USER"); if (!pos) // start of line { pos = line.find (" "); pos++; pos = line.find (" ", pos); pos++; auto nextpos = line.find (" ", pos); m_OutPacket << line.substr (0, pos); m_OutPacket << context.GetAddressBook ().ToAddress (m_From->GetIdentHash ()); m_OutPacket << line.substr (nextpos) << '\n'; } else m_OutPacket << line << '\n'; } I2PTunnelConnection::Write ((uint8_t *)m_OutPacket.str ().c_str (), m_OutPacket.str ().length ()); } /* This handler tries to stablish a connection with the desired server and dies if it fails to do so */ class I2PClientTunnelHandler: public I2PServiceHandler, public std::enable_shared_from_this { public: I2PClientTunnelHandler (I2PClientTunnel * parent, i2p::data::IdentHash destination, int destinationPort, std::shared_ptr socket): I2PServiceHandler(parent), m_DestinationIdentHash(destination), m_DestinationPort (destinationPort), m_Socket(socket) {}; void Handle(); void Terminate(); private: void HandleStreamRequestComplete (std::shared_ptr stream); i2p::data::IdentHash m_DestinationIdentHash; int m_DestinationPort; std::shared_ptr m_Socket; }; void I2PClientTunnelHandler::Handle() { GetOwner()->CreateStream ( std::bind (&I2PClientTunnelHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_DestinationIdentHash, m_DestinationPort); } void I2PClientTunnelHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (stream) { if (Kill()) return; LogPrint (eLogDebug, "I2PTunnel: new connection"); auto connection = std::make_shared(GetOwner(), m_Socket, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (); Done(shared_from_this()); } else { LogPrint (eLogError, "I2PTunnel: Client Tunnel Issue when creating the stream, check the previous warnings for more info."); Terminate(); } } void I2PClientTunnelHandler::Terminate() { if (Kill()) return; if (m_Socket) { m_Socket->close(); m_Socket = nullptr; } Done(shared_from_this()); } I2PClientTunnel::I2PClientTunnel (const std::string& name, const std::string& destination, const std::string& address, int port, std::shared_ptr localDestination, int destinationPort): TCPIPAcceptor (address, port, localDestination), m_Name (name), m_Destination (destination), m_DestinationIdentHash (nullptr), m_DestinationPort (destinationPort) { } void I2PClientTunnel::Start () { TCPIPAcceptor::Start (); GetIdentHash(); } void I2PClientTunnel::Stop () { TCPIPAcceptor::Stop(); auto *originalIdentHash = m_DestinationIdentHash; m_DestinationIdentHash = nullptr; delete originalIdentHash; } /* HACK: maybe we should create a caching IdentHash provider in AddressBook */ const i2p::data::IdentHash * I2PClientTunnel::GetIdentHash () { if (!m_DestinationIdentHash) { i2p::data::IdentHash identHash; if (i2p::client::context.GetAddressBook ().GetIdentHash (m_Destination, identHash)) m_DestinationIdentHash = new i2p::data::IdentHash (identHash); else LogPrint (eLogWarning, "I2PTunnel: Remote destination ", m_Destination, " not found"); } return m_DestinationIdentHash; } std::shared_ptr I2PClientTunnel::CreateHandler(std::shared_ptr socket) { const i2p::data::IdentHash *identHash = GetIdentHash(); if (identHash) return std::make_shared(this, *identHash, m_DestinationPort, socket); else return nullptr; } I2PServerTunnel::I2PServerTunnel (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, int inport, bool gzip): I2PService (localDestination), m_IsUniqueLocal(true), m_Name (name), m_Address (address), m_Port (port), m_IsAccessList (false) { m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port, gzip); } void I2PServerTunnel::Start () { m_Endpoint.port (m_Port); boost::system::error_code ec; auto addr = boost::asio::ip::address::from_string (m_Address, ec); if (!ec) { m_Endpoint.address (addr); Accept (); } else { auto resolver = std::make_shared(GetService ()); resolver->async_resolve (boost::asio::ip::tcp::resolver::query (m_Address, ""), std::bind (&I2PServerTunnel::HandleResolve, this, std::placeholders::_1, std::placeholders::_2, resolver)); } } void I2PServerTunnel::Stop () { ClearHandlers (); } void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, std::shared_ptr resolver) { if (!ecode) { auto addr = (*it).endpoint ().address (); LogPrint (eLogInfo, "I2PTunnel: server tunnel ", (*it).host_name (), " has been resolved to ", addr); m_Endpoint.address (addr); Accept (); } else LogPrint (eLogError, "I2PTunnel: Unable to resolve server tunnel address: ", ecode.message ()); } void I2PServerTunnel::SetAccessList (const std::set& accessList) { m_AccessList = accessList; m_IsAccessList = true; } void I2PServerTunnel::Accept () { if (m_PortDestination) m_PortDestination->SetAcceptor (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1)); auto localDestination = GetLocalDestination (); if (localDestination) { if (!localDestination->IsAcceptingStreams ()) // set it as default if not set yet localDestination->AcceptStreams (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1)); } else LogPrint (eLogError, "I2PTunnel: Local destination not set for server tunnel"); } void I2PServerTunnel::HandleAccept (std::shared_ptr stream) { if (stream) { if (m_IsAccessList) { if (!m_AccessList.count (stream->GetRemoteIdentity ()->GetIdentHash ())) { LogPrint (eLogWarning, "I2PTunnel: Address ", stream->GetRemoteIdentity ()->GetIdentHash ().ToBase32 (), " is not in white list. Incoming connection dropped"); stream->Close (); return; } } // new connection auto conn = CreateI2PConnection (stream); AddHandler (conn); conn->Connect (m_IsUniqueLocal); } } std::shared_ptr I2PServerTunnel::CreateI2PConnection (std::shared_ptr stream) { return std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint ()); } I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& host, int inport, bool gzip): I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_Host (host) { } std::shared_ptr I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr stream) { return std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), m_Host); } I2PServerTunnelIRC::I2PServerTunnelIRC (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& webircpass, int inport, bool gzip): I2PServerTunnel (name, address, port, localDestination, inport, gzip), m_WebircPass (webircpass) { } std::shared_ptr I2PServerTunnelIRC::CreateI2PConnection (std::shared_ptr stream) { return std::make_shared (this, stream, std::make_shared (GetService ()), GetEndpoint (), this->m_WebircPass); } void I2PUDPServerTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { std::lock_guard lock(m_SessionsMutex); auto session = ObtainUDPSession(from, toPort, fromPort); session->IPSocket.send_to(boost::asio::buffer(buf, len), m_RemoteEndpoint); session->LastActivity = i2p::util::GetMillisecondsSinceEpoch(); } void I2PUDPServerTunnel::ExpireStale(const uint64_t delta) { std::lock_guard lock(m_SessionsMutex); uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); auto itr = m_Sessions.begin(); while(itr != m_Sessions.end()) { if(now - (*itr)->LastActivity >= delta ) itr = m_Sessions.erase(itr); else ++itr; } } void I2PUDPClientTunnel::ExpireStale(const uint64_t delta) { std::lock_guard lock(m_SessionsMutex); uint64_t now = i2p::util::GetMillisecondsSinceEpoch(); std::vector removePorts; for (const auto & s : m_Sessions) { if (now - s.second.second >= delta) removePorts.push_back(s.first); } for(auto port : removePorts) { m_Sessions.erase(port); } } UDPSessionPtr I2PUDPServerTunnel::ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort) { auto ih = from.GetIdentHash(); for (auto & s : m_Sessions ) { if ( s->Identity == ih) { /** found existing session */ LogPrint(eLogDebug, "UDPServer: found session ", s->IPSocket.local_endpoint(), " ", ih.ToBase32()); return s; } } boost::asio::ip::address addr; /** create new udp session */ if(m_IsUniqueLocal && m_LocalAddress.is_loopback()) { auto ident = from.GetIdentHash(); addr = GetLoopbackAddressFor(ident); } else addr = m_LocalAddress; boost::asio::ip::udp::endpoint ep(addr, 0); m_Sessions.push_back(std::make_shared(ep, m_LocalDest, m_RemoteEndpoint, &ih, localPort, remotePort)); auto & back = m_Sessions.back(); return back; } UDPSession::UDPSession(boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint endpoint, const i2p::data::IdentHash * to, uint16_t ourPort, uint16_t theirPort) : m_Destination(localDestination->GetDatagramDestination()), IPSocket(localDestination->GetService(), localEndpoint), SendEndpoint(endpoint), LastActivity(i2p::util::GetMillisecondsSinceEpoch()), LocalPort(ourPort), RemotePort(theirPort) { memcpy(Identity, to->data(), 32); Receive(); } void UDPSession::Receive() { LogPrint(eLogDebug, "UDPSession: Receive"); IPSocket.async_receive_from(boost::asio::buffer(m_Buffer, I2P_UDP_MAX_MTU), FromEndpoint, std::bind(&UDPSession::HandleReceived, this, std::placeholders::_1, std::placeholders::_2)); } void UDPSession::HandleReceived(const boost::system::error_code & ecode, std::size_t len) { if(!ecode) { LogPrint(eLogDebug, "UDPSession: forward ", len, "B from ", FromEndpoint); LastActivity = i2p::util::GetMillisecondsSinceEpoch(); m_Destination->SendDatagramTo(m_Buffer, len, Identity, LocalPort, RemotePort); Receive(); } else { LogPrint(eLogError, "UDPSession: ", ecode.message()); } } I2PUDPServerTunnel::I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::address localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port) : m_IsUniqueLocal(true), m_Name(name), m_LocalAddress(localAddress), m_RemoteEndpoint(forwardTo) { m_LocalDest = localDestination; m_LocalDest->Start(); auto dgram = m_LocalDest->CreateDatagramDestination(); dgram->SetReceiver(std::bind(&I2PUDPServerTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); } I2PUDPServerTunnel::~I2PUDPServerTunnel() { auto dgram = m_LocalDest->GetDatagramDestination(); if (dgram) dgram->ResetReceiver(); LogPrint(eLogInfo, "UDPServer: done"); } void I2PUDPServerTunnel::Start() { m_LocalDest->Start(); } std::vector > I2PUDPServerTunnel::GetSessions() { std::vector > sessions; std::lock_guard lock(m_SessionsMutex); for ( UDPSessionPtr s : m_Sessions ) { if (!s->m_Destination) continue; auto info = s->m_Destination->GetInfoForRemote(s->Identity); if(!info) continue; auto sinfo = std::make_shared(); sinfo->Name = m_Name; sinfo->LocalIdent = std::make_shared(m_LocalDest->GetIdentHash().data()); sinfo->RemoteIdent = std::make_shared(s->Identity.data()); sinfo->CurrentIBGW = info->IBGW; sinfo->CurrentOBEP = info->OBEP; sessions.push_back(sinfo); } return sessions; } I2PUDPClientTunnel::I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort) : m_Name(name), m_RemoteDest(remoteDest), m_LocalDest(localDestination), m_LocalEndpoint(localEndpoint), m_RemoteIdent(nullptr), m_ResolveThread(nullptr), m_LocalSocket(localDestination->GetService(), localEndpoint), RemotePort(remotePort), m_cancel_resolve(false) { auto dgram = m_LocalDest->CreateDatagramDestination(); dgram->SetReceiver(std::bind(&I2PUDPClientTunnel::HandleRecvFromI2P, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); } void I2PUDPClientTunnel::Start() { m_LocalDest->Start(); if (m_ResolveThread == nullptr) m_ResolveThread = new std::thread(std::bind(&I2PUDPClientTunnel::TryResolving, this)); RecvFromLocal(); } void I2PUDPClientTunnel::RecvFromLocal() { m_LocalSocket.async_receive_from(boost::asio::buffer(m_RecvBuff, I2P_UDP_MAX_MTU), m_RecvEndpoint, std::bind(&I2PUDPClientTunnel::HandleRecvFromLocal, this, std::placeholders::_1, std::placeholders::_2)); } void I2PUDPClientTunnel::HandleRecvFromLocal(const boost::system::error_code & ec, std::size_t transferred) { if(ec) { LogPrint(eLogError, "UDP Client: ", ec.message()); return; } if(!m_RemoteIdent) { LogPrint(eLogWarning, "UDP Client: remote endpoint not resolved yet"); RecvFromLocal(); return; // drop, remote not resolved } auto remotePort = m_RecvEndpoint.port(); auto itr = m_Sessions.find(remotePort); if (itr == m_Sessions.end()) { // track new udp convo m_Sessions[remotePort] = {boost::asio::ip::udp::endpoint(m_RecvEndpoint), 0}; } // send off to remote i2p destination LogPrint(eLogDebug, "UDP Client: send ", transferred, " to ", m_RemoteIdent->ToBase32(), ":", RemotePort); m_LocalDest->GetDatagramDestination()->SendDatagramTo(m_RecvBuff, transferred, *m_RemoteIdent, remotePort, RemotePort); // mark convo as active m_Sessions[remotePort].second = i2p::util::GetMillisecondsSinceEpoch(); RecvFromLocal(); } std::vector > I2PUDPClientTunnel::GetSessions() { // TODO: implement std::vector > infos; return infos; } void I2PUDPClientTunnel::TryResolving() { LogPrint(eLogInfo, "UDP Tunnel: Trying to resolve ", m_RemoteDest); i2p::data::IdentHash * h = new i2p::data::IdentHash; while(!context.GetAddressBook().GetIdentHash(m_RemoteDest, *h) && !m_cancel_resolve) { LogPrint(eLogWarning, "UDP Tunnel: failed to lookup ", m_RemoteDest); std::this_thread::sleep_for(std::chrono::seconds(1)); } if(m_cancel_resolve) { LogPrint(eLogError, "UDP Tunnel: lookup of ", m_RemoteDest, " was cancelled"); return; } m_RemoteIdent = h; LogPrint(eLogInfo, "UDP Tunnel: resolved ", m_RemoteDest, " to ", m_RemoteIdent->ToBase32()); } void I2PUDPClientTunnel::HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { if(m_RemoteIdent && from.GetIdentHash() == *m_RemoteIdent) { auto itr = m_Sessions.find(toPort); // found convo ? if(itr != m_Sessions.end()) { // found convo if (len > 0) { LogPrint(eLogDebug, "UDP Client: got ", len, "B from ", from.GetIdentHash().ToBase32()); m_LocalSocket.send_to(boost::asio::buffer(buf, len), itr->second.first); // mark convo as active itr->second.second = i2p::util::GetMillisecondsSinceEpoch(); } } else LogPrint(eLogWarning, "UDP Client: not tracking udp session using port ", (int) toPort); } else LogPrint(eLogWarning, "UDP Client: unwarrented traffic from ", from.GetIdentHash().ToBase32()); } I2PUDPClientTunnel::~I2PUDPClientTunnel() { auto dgram = m_LocalDest->GetDatagramDestination(); if (dgram) dgram->ResetReceiver(); m_Sessions.clear(); if(m_LocalSocket.is_open()) m_LocalSocket.close(); m_cancel_resolve = true; if(m_ResolveThread) { m_ResolveThread->join(); delete m_ResolveThread; m_ResolveThread = nullptr; } if (m_RemoteIdent) delete m_RemoteIdent; } } } i2pd-2.17.0/libi2pd_client/I2PTunnel.h000066400000000000000000000276071321131324000172550ustar00rootroot00000000000000#ifndef I2PTUNNEL_H__ #define I2PTUNNEL_H__ #include #include #include #include #include #include #include #include "Identity.h" #include "Destination.h" #include "Datagram.h" #include "Streaming.h" #include "I2PService.h" namespace i2p { namespace client { const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 65536; const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds // for HTTP tunnels const char X_I2P_DEST_HASH[] = "X-I2P-DestHash"; // hash in base64 const char X_I2P_DEST_B64[] = "X-I2P-DestB64"; // full address in base64 const char X_I2P_DEST_B32[] = "X-I2P-DestB32"; // .b32.i2p address class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this { public: I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr leaseSet, int port = 0); // to I2P I2PTunnelConnection (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream); // to I2P using simplified API I2PTunnelConnection (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, bool quiet = true); // from I2P ~I2PTunnelConnection (); void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0); void Connect (bool isUniqueLocal = true); protected: void Terminate (); void Receive (); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); virtual void Write (const uint8_t * buf, size_t len); // can be overloaded void HandleWrite (const boost::system::error_code& ecode); void StreamReceive (); void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleConnect (const boost::system::error_code& ecode); std::shared_ptr GetSocket () const { return m_Socket; }; private: uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE]; std::shared_ptr m_Socket; std::shared_ptr m_Stream; boost::asio::ip::tcp::endpoint m_RemoteEndpoint; bool m_IsQuiet; // don't send destination }; class I2PClientTunnelConnectionHTTP: public I2PTunnelConnection { public: I2PClientTunnelConnectionHTTP (I2PService * owner, std::shared_ptr socket, std::shared_ptr stream): I2PTunnelConnection (owner, socket, stream), m_HeaderSent (false), m_ConnectionSent (false), m_ProxyConnectionSent (false) {}; protected: void Write (const uint8_t * buf, size_t len); private: std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent, m_ConnectionSent, m_ProxyConnectionSent; }; class I2PServerTunnelConnectionHTTP: public I2PTunnelConnection { public: I2PServerTunnelConnectionHTTP (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& host); protected: void Write (const uint8_t * buf, size_t len); private: std::string m_Host; std::stringstream m_InHeader, m_OutHeader; bool m_HeaderSent; std::shared_ptr m_From; }; class I2PTunnelConnectionIRC: public I2PTunnelConnection { public: I2PTunnelConnectionIRC (I2PService * owner, std::shared_ptr stream, std::shared_ptr socket, const boost::asio::ip::tcp::endpoint& target, const std::string& m_WebircPass); protected: void Write (const uint8_t * buf, size_t len); private: std::shared_ptr m_From; std::stringstream m_OutPacket, m_InPacket; bool m_NeedsWebIrc; std::string m_WebircPass; }; class I2PClientTunnel: public TCPIPAcceptor { protected: // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); public: I2PClientTunnel (const std::string& name, const std::string& destination, const std::string& address, int port, std::shared_ptr localDestination, int destinationPort = 0); ~I2PClientTunnel () {} void Start (); void Stop (); const char* GetName() { return m_Name.c_str (); } private: const i2p::data::IdentHash * GetIdentHash (); private: std::string m_Name, m_Destination; const i2p::data::IdentHash * m_DestinationIdentHash; int m_DestinationPort; }; /** 2 minute timeout for udp sessions */ const uint64_t I2P_UDP_SESSION_TIMEOUT = 1000 * 60 * 2; /** max size for i2p udp */ const size_t I2P_UDP_MAX_MTU = i2p::datagram::MAX_DATAGRAM_SIZE; struct UDPSession { i2p::datagram::DatagramDestination * m_Destination; boost::asio::ip::udp::socket IPSocket; i2p::data::IdentHash Identity; boost::asio::ip::udp::endpoint FromEndpoint; boost::asio::ip::udp::endpoint SendEndpoint; uint64_t LastActivity; uint16_t LocalPort; uint16_t RemotePort; uint8_t m_Buffer[I2P_UDP_MAX_MTU]; UDPSession(boost::asio::ip::udp::endpoint localEndpoint, const std::shared_ptr & localDestination, boost::asio::ip::udp::endpoint remote, const i2p::data::IdentHash * ident, uint16_t ourPort, uint16_t theirPort); void HandleReceived(const boost::system::error_code & ecode, std::size_t len); void Receive(); }; /** read only info about a datagram session */ struct DatagramSessionInfo { /** the name of this forward */ std::string Name; /** ident hash of local destination */ std::shared_ptr LocalIdent; /** ident hash of remote destination */ std::shared_ptr RemoteIdent; /** ident hash of IBGW in use currently in this session or nullptr if none is set */ std::shared_ptr CurrentIBGW; /** ident hash of OBEP in use for this session or nullptr if none is set */ std::shared_ptr CurrentOBEP; /** i2p router's udp endpoint */ boost::asio::ip::udp::endpoint LocalEndpoint; /** client's udp endpoint */ boost::asio::ip::udp::endpoint RemoteEndpoint; /** how long has this converstation been idle in ms */ uint64_t idle; }; typedef std::shared_ptr UDPSessionPtr; /** server side udp tunnel, many i2p inbound to 1 ip outbound */ class I2PUDPServerTunnel { public: I2PUDPServerTunnel(const std::string & name, std::shared_ptr localDestination, boost::asio::ip::address localAddress, boost::asio::ip::udp::endpoint forwardTo, uint16_t port); ~I2PUDPServerTunnel(); /** expire stale udp conversations */ void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); void Start(); const char * GetName() const { return m_Name.c_str(); } std::vector > GetSessions(); std::shared_ptr GetLocalDestination () const { return m_LocalDest; } void SetUniqueLocal(bool isUniqueLocal = true) { m_IsUniqueLocal = isUniqueLocal; } private: void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); UDPSessionPtr ObtainUDPSession(const i2p::data::IdentityEx& from, uint16_t localPort, uint16_t remotePort); private: bool m_IsUniqueLocal; const std::string m_Name; boost::asio::ip::address m_LocalAddress; boost::asio::ip::udp::endpoint m_RemoteEndpoint; std::mutex m_SessionsMutex; std::vector m_Sessions; std::shared_ptr m_LocalDest; }; class I2PUDPClientTunnel { public: I2PUDPClientTunnel(const std::string & name, const std::string &remoteDest, boost::asio::ip::udp::endpoint localEndpoint, std::shared_ptr localDestination, uint16_t remotePort); ~I2PUDPClientTunnel(); void Start(); const char * GetName() const { return m_Name.c_str(); } std::vector > GetSessions(); bool IsLocalDestination(const i2p::data::IdentHash & destination) const { return destination == m_LocalDest->GetIdentHash(); } std::shared_ptr GetLocalDestination () const { return m_LocalDest; } void ExpireStale(const uint64_t delta=I2P_UDP_SESSION_TIMEOUT); private: typedef std::pair UDPConvo; void RecvFromLocal(); void HandleRecvFromLocal(const boost::system::error_code & e, std::size_t transferred); void HandleRecvFromI2P(const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void TryResolving(); const std::string m_Name; std::mutex m_SessionsMutex; std::map m_Sessions; // maps i2p port -> local udp convo const std::string m_RemoteDest; std::shared_ptr m_LocalDest; const boost::asio::ip::udp::endpoint m_LocalEndpoint; i2p::data::IdentHash * m_RemoteIdent; std::thread * m_ResolveThread; boost::asio::ip::udp::socket m_LocalSocket; boost::asio::ip::udp::endpoint m_RecvEndpoint; uint8_t m_RecvBuff[I2P_UDP_MAX_MTU]; uint16_t RemotePort; bool m_cancel_resolve; }; class I2PServerTunnel: public I2PService { public: I2PServerTunnel (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, int inport = 0, bool gzip = true); void Start (); void Stop (); void SetAccessList (const std::set& accessList); void SetUniqueLocal (bool isUniqueLocal) { m_IsUniqueLocal = isUniqueLocal; } bool IsUniqueLocal () const { return m_IsUniqueLocal; } const std::string& GetAddress() const { return m_Address; } int GetPort () const { return m_Port; }; uint16_t GetLocalPort () const { return m_PortDestination->GetLocalPort (); }; const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; } const char* GetName() { return m_Name.c_str (); } void SetMaxConnsPerMinute(const uint32_t conns) { m_PortDestination->SetMaxConnsPerMinute(conns); } private: void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it, std::shared_ptr resolver); void Accept (); void HandleAccept (std::shared_ptr stream); virtual std::shared_ptr CreateI2PConnection (std::shared_ptr stream); private: bool m_IsUniqueLocal; std::string m_Name, m_Address; int m_Port; boost::asio::ip::tcp::endpoint m_Endpoint; std::shared_ptr m_PortDestination; std::set m_AccessList; bool m_IsAccessList; }; class I2PServerTunnelHTTP: public I2PServerTunnel { public: I2PServerTunnelHTTP (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& host, int inport = 0, bool gzip = true); private: std::shared_ptr CreateI2PConnection (std::shared_ptr stream); private: std::string m_Host; }; class I2PServerTunnelIRC: public I2PServerTunnel { public: I2PServerTunnelIRC (const std::string& name, const std::string& address, int port, std::shared_ptr localDestination, const std::string& webircpass, int inport = 0, bool gzip = true); private: std::shared_ptr CreateI2PConnection (std::shared_ptr stream); private: std::string m_WebircPass; }; } } #endif i2pd-2.17.0/libi2pd_client/MatchedDestination.cpp000066400000000000000000000057601321131324000215730ustar00rootroot00000000000000#include "MatchedDestination.h" #include "Log.h" #include "ClientContext.h" namespace i2p { namespace client { MatchedTunnelDestination::MatchedTunnelDestination(const i2p::data::PrivateKeys & keys, const std::string & remoteName, const std::map * params) : ClientDestination(keys, false, params), m_RemoteName(remoteName) {} void MatchedTunnelDestination::ResolveCurrentLeaseSet() { if(i2p::client::context.GetAddressBook().GetIdentHash(m_RemoteName, m_RemoteIdent)) { auto ls = FindLeaseSet(m_RemoteIdent); if(ls) { HandleFoundCurrentLeaseSet(ls); } else RequestDestination(m_RemoteIdent, std::bind(&MatchedTunnelDestination::HandleFoundCurrentLeaseSet, this, std::placeholders::_1)); } else LogPrint(eLogWarning, "Destination: failed to resolve ", m_RemoteName); } void MatchedTunnelDestination::HandleFoundCurrentLeaseSet(std::shared_ptr ls) { if(ls) { LogPrint(eLogDebug, "Destination: resolved remote lease set for ", m_RemoteName); m_RemoteLeaseSet = ls; } else { m_ResolveTimer->expires_from_now(boost::posix_time::seconds(1)); m_ResolveTimer->async_wait([&](const boost::system::error_code & ec) { if(!ec) ResolveCurrentLeaseSet(); }); } } bool MatchedTunnelDestination::Start() { if(ClientDestination::Start()) { m_ResolveTimer = std::make_shared(GetService()); GetTunnelPool()->SetCustomPeerSelector(this); ResolveCurrentLeaseSet(); return true; } else return false; } bool MatchedTunnelDestination::Stop() { if(ClientDestination::Stop()) { if(m_ResolveTimer) m_ResolveTimer->cancel(); return true; } else return false; } bool MatchedTunnelDestination::SelectPeers(i2p::tunnel::Path & path, int hops, bool inbound) { auto pool = GetTunnelPool(); if(!i2p::tunnel::StandardSelectPeers(path, hops, inbound, std::bind(&i2p::tunnel::TunnelPool::SelectNextHop, pool, std::placeholders::_1))) return false; // more here for outbound tunnels if(!inbound && m_RemoteLeaseSet) { if(m_RemoteLeaseSet->IsExpired()) { ResolveCurrentLeaseSet(); } if(m_RemoteLeaseSet && !m_RemoteLeaseSet->IsExpired()) { // remote lease set is good auto leases = m_RemoteLeaseSet->GetNonExpiredLeases(); // pick lease std::shared_ptr obep; while(!obep && leases.size() > 0) { auto idx = rand() % leases.size(); auto lease = leases[idx]; obep = i2p::data::netdb.FindRouter(lease->tunnelGateway); leases.erase(leases.begin()+idx); } if(obep) { path.push_back(obep->GetRouterIdentity()); LogPrint(eLogDebug, "Destination: found OBEP matching IBGW"); } else LogPrint(eLogWarning, "Destination: could not find proper IBGW for matched outbound tunnel"); } } return true; } bool MatchedTunnelDestination::OnBuildResult(const i2p::tunnel::Path & path, bool inbound, i2p::tunnel::TunnelBuildResult result) { return true; } } } i2pd-2.17.0/libi2pd_client/MatchedDestination.h000066400000000000000000000020531321131324000212300ustar00rootroot00000000000000#ifndef MATCHED_DESTINATION_H_ #define MATCHED_DESTINATION_H_ #include "Destination.h" #include namespace i2p { namespace client { /** client tunnel that uses same OBEP as IBGW of each remote lease for a remote destination */ class MatchedTunnelDestination : public ClientDestination, public i2p::tunnel::ITunnelPeerSelector { public: MatchedTunnelDestination(const i2p::data::PrivateKeys& keys, const std::string & remoteName, const std::map * params = nullptr); bool Start(); bool Stop(); bool SelectPeers(i2p::tunnel::Path & peers, int hops, bool inbound); bool OnBuildResult(const i2p::tunnel::Path & peers, bool inbound, i2p::tunnel::TunnelBuildResult result); private: void ResolveCurrentLeaseSet(); void HandleFoundCurrentLeaseSet(std::shared_ptr ls); private: std::string m_RemoteName; i2p::data::IdentHash m_RemoteIdent; std::shared_ptr m_RemoteLeaseSet; std::shared_ptr m_ResolveTimer; }; } } #endif i2pd-2.17.0/libi2pd_client/SAM.cpp000066400000000000000000000764401321131324000164470ustar00rootroot00000000000000#include #include #ifdef _MSC_VER #include #endif #include "Base.h" #include "Identity.h" #include "Log.h" #include "Destination.h" #include "ClientContext.h" #include "util.h" #include "SAM.h" namespace i2p { namespace client { SAMSocket::SAMSocket (SAMBridge& owner): m_Owner (owner), m_Socket (m_Owner.GetService ()), m_Timer (m_Owner.GetService ()), m_BufferOffset (0), m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false), m_IsAccepting (false), m_Stream (nullptr), m_Session (nullptr) { } SAMSocket::~SAMSocket () { Terminate ("~SAMSocket()"); } void SAMSocket::CloseStream (const char* reason) { LogPrint (eLogDebug, "SAMSocket::CloseStream, reason: ", reason); if (m_Stream) { m_Stream->Close (); m_Stream.reset (); } } void SAMSocket::Terminate (const char* reason) { CloseStream (reason); switch (m_SocketType) { case eSAMSocketTypeSession: m_Owner.CloseSession (m_ID); break; case eSAMSocketTypeStream: { if (m_Session) m_Session->DelSocket (shared_from_this ()); break; } case eSAMSocketTypeAcceptor: { if (m_Session) { m_Session->DelSocket (shared_from_this ()); if (m_IsAccepting && m_Session->localDestination) m_Session->localDestination->StopAcceptingStreams (); } break; } default: ; } m_SocketType = eSAMSocketTypeTerminated; if (m_Socket.is_open()) m_Socket.close (); m_Session = nullptr; } void SAMSocket::ReceiveHandshake () { m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE), std::bind(&SAMSocket::HandleHandshakeReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void SAMSocket::HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: handshake read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: handshake read error"); } else { m_Buffer[bytes_transferred] = 0; char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred); if (eol) *eol = 0; LogPrint (eLogDebug, "SAM: handshake ", m_Buffer); char * separator = strchr (m_Buffer, ' '); if (separator) { separator = strchr (separator + 1, ' '); if (separator) *separator = 0; } if (!strcmp (m_Buffer, SAM_HANDSHAKE)) { std::string version("3.0"); // try to find MIN and MAX, 3.0 if not found if (separator) { separator++; std::map params; ExtractParams (separator, params); //auto it = params.find (SAM_PARAM_MAX); // TODO: check MIN as well //if (it != params.end ()) // version = it->second; } if (version[0] == '3') // we support v3 (3.0 and 3.1) only { #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ()); #else size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ()); #endif boost::asio::async_write (m_Socket, boost::asio::buffer (m_Buffer, l), boost::asio::transfer_all (), std::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } else SendMessageReply (SAM_HANDSHAKE_I2P_ERROR, strlen (SAM_HANDSHAKE_I2P_ERROR), true); } else { LogPrint (eLogError, "SAM: handshake mismatch"); Terminate ("SAM: handshake mismatch"); } } } void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: handshake reply send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: handshake reply send error"); } else { m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE), std::bind(&SAMSocket::HandleMessage, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } } void SAMSocket::SendMessageReply (const char * msg, size_t len, bool close) { LogPrint (eLogDebug, "SAMSocket::SendMessageReply, close=",close?"true":"false", " reason: ", msg); if (!m_IsSilent) boost::asio::async_write (m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (), std::bind(&SAMSocket::HandleMessageReplySent, shared_from_this (), std::placeholders::_1, std::placeholders::_2, close)); else { if (close) Terminate ("SAMSocket::SendMessageReply(close=true)"); else Receive (); } } void SAMSocket::HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close) { if (ecode) { LogPrint (eLogError, "SAM: reply send error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: reply send error"); } else { if (close) Terminate ("SAMSocket::HandleMessageReplySent(close=true)"); else Receive (); } } void SAMSocket::HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("SAM: read error"); } else if (m_SocketType == eSAMSocketTypeStream) HandleReceived (ecode, bytes_transferred); else { bytes_transferred += m_BufferOffset; m_BufferOffset = 0; m_Buffer[bytes_transferred] = 0; char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred); if (eol) { *eol = 0; char * separator = strchr (m_Buffer, ' '); if (separator) { separator = strchr (separator + 1, ' '); if (separator) *separator = 0; else separator = eol; if (!strcmp (m_Buffer, SAM_SESSION_CREATE)) ProcessSessionCreate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_STREAM_CONNECT)) ProcessStreamConnect (separator + 1, bytes_transferred - (separator - m_Buffer) - 1, bytes_transferred - (eol - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_STREAM_ACCEPT)) ProcessStreamAccept (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_DEST_GENERATE)) ProcessDestGenerate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP)) ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1); else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND)) { size_t len = bytes_transferred - (separator - m_Buffer) - 1; size_t processed = ProcessDatagramSend (separator + 1, len, eol + 1); if (processed < len) { m_BufferOffset = len - processed; if (processed > 0) memmove (m_Buffer, separator + 1 + processed, m_BufferOffset); else { // restore string back *separator = ' '; *eol = '\n'; } } // since it's SAM v1 reply is not expected Receive (); } else { LogPrint (eLogError, "SAM: unexpected message ", m_Buffer); Terminate ("SAM: unexpected message"); } } else { LogPrint (eLogError, "SAM: malformed message ", m_Buffer); Terminate ("malformed message"); } } else { LogPrint (eLogWarning, "SAM: incomplete message ", bytes_transferred); m_BufferOffset = bytes_transferred; // try to receive remaining message Receive (); } } } void SAMSocket::ProcessSessionCreate (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: session create: ", buf); std::map params; ExtractParams (buf, params); std::string& style = params[SAM_PARAM_STYLE]; std::string& id = params[SAM_PARAM_ID]; std::string& destination = params[SAM_PARAM_DESTINATION]; m_ID = id; if (m_Owner.FindSession (id)) { // session exists SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), true); return; } std::shared_ptr forward = nullptr; if (style == SAM_VALUE_DATAGRAM && params.find(SAM_VALUE_HOST) != params.end() && params.find(SAM_VALUE_PORT) != params.end()) { // udp forward selected boost::system::error_code e; // TODO: support hostnames in udp forward auto addr = boost::asio::ip::address::from_string(params[SAM_VALUE_HOST], e); if (e) { // not an ip address SendI2PError("Invalid IP Address in HOST"); return; } auto port = std::stoi(params[SAM_VALUE_PORT]); if (port == -1) { SendI2PError("Invalid port"); return; } forward = std::make_shared(addr, port); } // create destination m_Session = m_Owner.CreateSession (id, destination == SAM_VALUE_TRANSIENT ? "" : destination, ¶ms); if (m_Session) { m_SocketType = eSAMSocketTypeSession; if (style == SAM_VALUE_DATAGRAM) { m_Session->UDPEndpoint = forward; auto dest = m_Session->localDestination->CreateDatagramDestination (); dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); } if (m_Session->localDestination->IsReady ()) SendSessionCreateReplyOk (); else { m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL)); m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer, shared_from_this (), std::placeholders::_1)); } } else SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_DEST, strlen(SAM_SESSION_CREATE_DUPLICATED_DEST), true); } void SAMSocket::HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode) { if (ecode != boost::asio::error::operation_aborted) { if (m_Session->localDestination->IsReady ()) SendSessionCreateReplyOk (); else { m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL)); m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer, shared_from_this (), std::placeholders::_1)); } } } void SAMSocket::SendSessionCreateReplyOk () { uint8_t buf[1024]; char priv[1024]; size_t l = m_Session->localDestination->GetPrivateKeys ().ToBuffer (buf, 1024); size_t l1 = i2p::data::ByteStreamToBase64 (buf, l, priv, 1024); priv[l1] = 0; #ifdef _MSC_VER size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv); #else size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv); #endif SendMessageReply (m_Buffer, l2, false); } void SAMSocket::ProcessStreamConnect (char * buf, size_t len, size_t rem) { LogPrint (eLogDebug, "SAM: stream connect: ", buf); std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; std::string& destination = params[SAM_PARAM_DESTINATION]; std::string& silent = params[SAM_PARAM_SILENT]; if (silent == SAM_VALUE_TRUE) m_IsSilent = true; m_ID = id; m_Session = m_Owner.FindSession (id); if (m_Session) { if (rem > 0) // handle follow on data { memmove (m_Buffer, buf + len + 1, rem); // buf is a pointer to m_Buffer's content m_BufferOffset = rem; } else m_BufferOffset = 0; auto dest = std::make_shared (); size_t l = dest->FromBase64(destination); if (l > 0) { context.GetAddressBook().InsertAddress(dest); auto leaseSet = m_Session->localDestination->FindLeaseSet(dest->GetIdentHash()); if (leaseSet) Connect(leaseSet); else { m_Session->localDestination->RequestDestination(dest->GetIdentHash(), std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete, shared_from_this(), std::placeholders::_1)); } } else SendMessageReply(SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true); } else SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } void SAMSocket::Connect (std::shared_ptr remote) { m_SocketType = eSAMSocketTypeStream; m_Session->AddSocket (shared_from_this ()); m_Stream = m_Session->localDestination->CreateStream (remote); m_Stream->Send ((uint8_t *)m_Buffer, m_BufferOffset); // connect and send m_BufferOffset = 0; I2PReceive (); SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } void SAMSocket::HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet) { if (leaseSet) Connect (leaseSet); else { LogPrint (eLogError, "SAM: destination to connect not found"); SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true); } } void SAMSocket::ProcessStreamAccept (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: stream accept: ", buf); std::map params; ExtractParams (buf, params); std::string& id = params[SAM_PARAM_ID]; std::string& silent = params[SAM_PARAM_SILENT]; if (silent == SAM_VALUE_TRUE) m_IsSilent = true; m_ID = id; m_Session = m_Owner.FindSession (id); if (m_Session) { m_SocketType = eSAMSocketTypeAcceptor; m_Session->AddSocket (shared_from_this ()); if (!m_Session->localDestination->IsAcceptingStreams ()) { m_IsAccepting = true; m_Session->localDestination->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1)); } SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false); } else SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true); } size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data) { LogPrint (eLogDebug, "SAM: datagram send: ", buf, " ", len); std::map params; ExtractParams (buf, params); size_t size = std::stoi(params[SAM_PARAM_SIZE]), offset = data - buf; if (offset + size <= len) { if (m_Session) { auto d = m_Session->localDestination->GetDatagramDestination (); if (d) { i2p::data::IdentityEx dest; dest.FromBase64 (params[SAM_PARAM_DESTINATION]); d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ()); } else LogPrint (eLogError, "SAM: missing datagram destination"); } else LogPrint (eLogError, "SAM: session is not created from DATAGRAM SEND"); } else { LogPrint (eLogWarning, "SAM: sent datagram size ", size, " exceeds buffer ", len - offset); return 0; // try to receive more } return offset + size; } void SAMSocket::ProcessDestGenerate (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: dest generate"); std::map params; ExtractParams (buf, params); // extract signature type i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; auto it = params.find (SAM_PARAM_SIGNATURE_TYPE); if (it != params.end ()) // TODO: extract string values signatureType = std::stoi(it->second); it = params.find (SAM_PARAM_CRYPTO_TYPE); if (it != params.end ()) cryptoType = std::stoi(it->second); auto keys = i2p::data::PrivateKeys::CreateRandomKeys (signatureType, cryptoType); #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); #else size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY, keys.GetPublic ()->ToBase64 ().c_str (), keys.ToBase64 ().c_str ()); #endif SendMessageReply (m_Buffer, l, false); } void SAMSocket::ProcessNamingLookup (char * buf, size_t len) { LogPrint (eLogDebug, "SAM: naming lookup: ", buf); std::map params; ExtractParams (buf, params); std::string& name = params[SAM_PARAM_NAME]; std::shared_ptr identity; i2p::data::IdentHash ident; auto dest = m_Session == nullptr ? context.GetSharedLocalDestination() : m_Session->localDestination; if (name == "ME") SendNamingLookupReply (dest->GetIdentity ()); else if ((identity = context.GetAddressBook ().GetAddress (name)) != nullptr) SendNamingLookupReply (identity); else if (context.GetAddressBook ().GetIdentHash (name, ident)) { auto leaseSet = dest->FindLeaseSet (ident); if (leaseSet) SendNamingLookupReply (leaseSet->GetIdentity ()); else dest->RequestDestination (ident, std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete, shared_from_this (), std::placeholders::_1, ident)); } else { LogPrint (eLogError, "SAM: naming failed, unknown address ", name); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #else size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str()); #endif SendMessageReply (m_Buffer, len, false); } } void SAMSocket::SendI2PError(const std::string & msg) { LogPrint (eLogError, "SAM: i2p error ", msg); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str()); #else size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_STATUS_I2P_ERROR, msg.c_str()); #endif SendMessageReply (m_Buffer, len, true); } void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, i2p::data::IdentHash ident) { if (leaseSet) { context.GetAddressBook ().InsertAddress (leaseSet->GetIdentity ()); SendNamingLookupReply (leaseSet->GetIdentity ()); } else { LogPrint (eLogError, "SAM: naming lookup failed. LeaseSet for ", ident.ToBase32 (), " not found"); #ifdef _MSC_VER size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, context.GetAddressBook ().ToAddress (ident).c_str()); #else size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, context.GetAddressBook ().ToAddress (ident).c_str()); #endif SendMessageReply (m_Buffer, len, false); } } void SAMSocket::SendNamingLookupReply (std::shared_ptr identity) { auto base64 = identity->ToBase64 (); #ifdef _MSC_VER size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ()); #else size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ()); #endif SendMessageReply (m_Buffer, l, false); } void SAMSocket::ExtractParams (char * buf, std::map& params) { char * separator; do { separator = strchr (buf, ' '); if (separator) *separator = 0; char * value = strchr (buf, '='); if (value) { *value = 0; value++; params[buf] = value; } buf = separator + 1; } while (separator); } void SAMSocket::Receive () { if (m_BufferOffset >= SAM_SOCKET_BUFFER_SIZE) { LogPrint (eLogError, "SAM: Buffer is full, terminate"); Terminate ("Buffer is full"); return; } m_Socket.async_read_some (boost::asio::buffer(m_Buffer + m_BufferOffset, SAM_SOCKET_BUFFER_SIZE - m_BufferOffset), std::bind((m_SocketType == eSAMSocketTypeStream) ? &SAMSocket::HandleReceived : &SAMSocket::HandleMessage, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); } void SAMSocket::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("read error"); } else { if (m_Stream) { bytes_transferred += m_BufferOffset; m_BufferOffset = 0; auto s = shared_from_this (); m_Stream->AsyncSend ((uint8_t *)m_Buffer, bytes_transferred, [s](const boost::system::error_code& ecode) { if (!ecode) s->Receive (); else s->m_Owner.GetService ().post ([s] { s->Terminate ("AsyncSend failed"); }); }); } } } void SAMSocket::I2PReceive () { if (m_Stream) { if (m_Stream->GetStatus () == i2p::stream::eStreamStatusNew || m_Stream->GetStatus () == i2p::stream::eStreamStatusOpen) // regular { m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE), std::bind (&SAMSocket::HandleI2PReceive, shared_from_this (), std::placeholders::_1, std::placeholders::_2), SAM_SOCKET_CONNECTION_MAX_IDLE); } else // closed by peer { // get remaning data auto len = m_Stream->ReadSome (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE); if (len > 0) // still some data { boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, len), std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1)); } else // no more data Terminate ("no more data"); } } } void SAMSocket::HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (ecode) { LogPrint (eLogError, "SAM: stream read error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) { if (bytes_transferred > 0) boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, bytes_transferred), std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1)); // postpone termination else { auto s = shared_from_this (); m_Owner.GetService ().post ([s] { s->Terminate ("stream read error"); }); } } else { auto s = shared_from_this (); m_Owner.GetService ().post ([s] { s->Terminate ("stream read error (op aborted)"); }); } } else { if (m_SocketType != eSAMSocketTypeTerminated) // check for possible race condition with Terminate() boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, bytes_transferred), std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1)); } } void SAMSocket::HandleWriteI2PData (const boost::system::error_code& ecode) { if (ecode) { LogPrint (eLogError, "SAM: socket write error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Terminate ("socket write error at HandleWriteI2PData"); } else I2PReceive (); } void SAMSocket::HandleI2PAccept (std::shared_ptr stream) { if (stream) { LogPrint (eLogDebug, "SAM: incoming I2P connection for session ", m_ID); m_SocketType = eSAMSocketTypeStream; m_IsAccepting = false; m_Stream = stream; context.GetAddressBook ().InsertAddress (stream->GetRemoteIdentity ()); auto session = m_Owner.FindSession (m_ID); if (session) { // find more pending acceptors for (auto it: session->ListSockets ()) if (it->m_SocketType == eSAMSocketTypeAcceptor) { it->m_IsAccepting = true; session->localDestination->AcceptOnce (std::bind (&SAMSocket::HandleI2PAccept, it, std::placeholders::_1)); break; } } if (!m_IsSilent) { // get remote peer address auto ident_ptr = stream->GetRemoteIdentity(); const size_t ident_len = ident_ptr->GetFullLen(); uint8_t* ident = new uint8_t[ident_len]; // send remote peer address as base64 const size_t l = ident_ptr->ToBuffer (ident, ident_len); const size_t l1 = i2p::data::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE); delete[] ident; m_StreamBuffer[l1] = '\n'; HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream } else I2PReceive (); } else LogPrint (eLogWarning, "SAM: I2P acceptor has been reset"); } void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len) { LogPrint (eLogDebug, "SAM: datagram received ", len); auto base64 = from.ToBase64 (); auto ep = m_Session->UDPEndpoint; if (ep) { // udp forward enabled size_t bsz = base64.size(); size_t sz = bsz + 1 + len; // build datagram body uint8_t * data = new uint8_t[sz]; // Destination memcpy(data, base64.c_str(), bsz); // linefeed data[bsz] = '\n'; // Payload memcpy(data+bsz+1, buf, len); // send to remote endpoint m_Owner.SendTo(data, sz, ep); delete [] data; } else { #ifdef _MSC_VER size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); #else size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), (long unsigned int)len); #endif if (len < SAM_SOCKET_BUFFER_SIZE - l) { memcpy (m_StreamBuffer + l, buf, len); boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, len + l), std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1)); } else LogPrint (eLogWarning, "SAM: received datagram size ", len," exceeds buffer"); } } SAMSession::SAMSession (std::shared_ptr dest): localDestination (dest), UDPEndpoint(nullptr) { } SAMSession::~SAMSession () { CloseStreams(); i2p::client::context.DeleteLocalDestination (localDestination); } void SAMSession::CloseStreams () { std::vector > socks; { std::lock_guard lock(m_SocketsMutex); for (const auto& sock : m_Sockets) { socks.push_back(sock); } } for (auto & sock : socks ) sock->Terminate("SAMSession::CloseStreams()"); m_Sockets.clear(); } SAMBridge::SAMBridge (const std::string& address, int port): m_IsRunning (false), m_Thread (nullptr), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)), m_DatagramEndpoint (boost::asio::ip::address::from_string(address), port-1), m_DatagramSocket (m_Service, m_DatagramEndpoint) { } SAMBridge::~SAMBridge () { if (m_IsRunning) Stop (); } void SAMBridge::Start () { Accept (); ReceiveDatagram (); m_IsRunning = true; m_Thread = new std::thread (std::bind (&SAMBridge::Run, this)); } void SAMBridge::Stop () { m_IsRunning = false; m_Acceptor.cancel (); for (auto& it: m_Sessions) it.second->CloseStreams (); m_Sessions.clear (); m_Service.stop (); if (m_Thread) { m_Thread->join (); delete m_Thread; m_Thread = nullptr; } } void SAMBridge::Run () { while (m_IsRunning) { try { m_Service.run (); } catch (std::exception& ex) { LogPrint (eLogError, "SAM: runtime exception: ", ex.what ()); } } } void SAMBridge::Accept () { auto newSocket = std::make_shared (*this); m_Acceptor.async_accept (newSocket->GetSocket (), std::bind (&SAMBridge::HandleAccept, this, std::placeholders::_1, newSocket)); } void SAMBridge::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket) { if (!ecode) { boost::system::error_code ec; auto ep = socket->GetSocket ().remote_endpoint (ec); if (!ec) { LogPrint (eLogDebug, "SAM: new connection from ", ep); socket->ReceiveHandshake (); } else LogPrint (eLogError, "SAM: incoming connection error ", ec.message ()); } else LogPrint (eLogError, "SAM: accept error: ", ecode.message ()); if (ecode != boost::asio::error::operation_aborted) Accept (); } std::shared_ptr SAMBridge::CreateSession (const std::string& id, const std::string& destination, const std::map * params) { std::shared_ptr localDestination = nullptr; if (destination != "") { i2p::data::PrivateKeys keys; if (!keys.FromBase64 (destination)) return nullptr; localDestination = i2p::client::context.CreateNewLocalDestination (keys, true, params); } else // transient { // extract signature type i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1; i2p::data::CryptoKeyType cryptoType = i2p::data::CRYPTO_KEY_TYPE_ELGAMAL; if (params) { auto it = params->find (SAM_PARAM_SIGNATURE_TYPE); if (it != params->end ()) // TODO: extract string values signatureType = std::stoi(it->second); it = params->find (SAM_PARAM_CRYPTO_TYPE); if (it != params->end ()) cryptoType = std::stoi(it->second); } localDestination = i2p::client::context.CreateNewLocalDestination (true, signatureType, cryptoType, params); } if (localDestination) { localDestination->Acquire (); auto session = std::make_shared(localDestination); std::unique_lock l(m_SessionsMutex); auto ret = m_Sessions.insert (std::make_pair(id, session)); if (!ret.second) LogPrint (eLogWarning, "SAM: Session ", id, " already exists"); return ret.first->second; } return nullptr; } void SAMBridge::CloseSession (const std::string& id) { std::shared_ptr session; { std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (id); if (it != m_Sessions.end ()) { session = it->second; m_Sessions.erase (it); } } if (session) { session->localDestination->Release (); session->localDestination->StopAcceptingStreams (); session->CloseStreams (); } } std::shared_ptr SAMBridge::FindSession (const std::string& id) const { std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (id); if (it != m_Sessions.end ()) return it->second; return nullptr; } void SAMBridge::SendTo(const uint8_t * buf, size_t len, std::shared_ptr remote) { if(remote) { m_DatagramSocket.send_to(boost::asio::buffer(buf, len), *remote); } } void SAMBridge::ReceiveDatagram () { m_DatagramSocket.async_receive_from ( boost::asio::buffer (m_DatagramReceiveBuffer, i2p::datagram::MAX_DATAGRAM_SIZE), m_SenderEndpoint, std::bind (&SAMBridge::HandleReceivedDatagram, this, std::placeholders::_1, std::placeholders::_2)); } void SAMBridge::HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred) { if (!ecode) { m_DatagramReceiveBuffer[bytes_transferred] = 0; char * eol = strchr ((char *)m_DatagramReceiveBuffer, '\n'); *eol = 0; eol++; size_t payloadLen = bytes_transferred - ((uint8_t *)eol - m_DatagramReceiveBuffer); LogPrint (eLogDebug, "SAM: datagram received ", m_DatagramReceiveBuffer," size=", payloadLen); char * sessionID = strchr ((char *)m_DatagramReceiveBuffer, ' '); if (sessionID) { sessionID++; char * destination = strchr (sessionID, ' '); if (destination) { *destination = 0; destination++; auto session = FindSession (sessionID); if (session) { i2p::data::IdentityEx dest; dest.FromBase64 (destination); session->localDestination->GetDatagramDestination ()-> SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ()); } else LogPrint (eLogError, "SAM: Session ", sessionID, " not found"); } else LogPrint (eLogError, "SAM: Missing destination key"); } else LogPrint (eLogError, "SAM: Missing sessionID"); ReceiveDatagram (); } else LogPrint (eLogError, "SAM: datagram receive error: ", ecode.message ()); } } } i2pd-2.17.0/libi2pd_client/SAM.h000066400000000000000000000211421321131324000161010ustar00rootroot00000000000000#ifndef SAM_H__ #define SAM_H__ #include #include #include #include #include #include #include #include #include "Identity.h" #include "LeaseSet.h" #include "Streaming.h" #include "Destination.h" namespace i2p { namespace client { const size_t SAM_SOCKET_BUFFER_SIZE = 8192; const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds const int SAM_SESSION_READINESS_CHECK_INTERVAL = 20; // in seconds const char SAM_HANDSHAKE[] = "HELLO VERSION"; const char SAM_HANDSHAKE_REPLY[] = "HELLO REPLY RESULT=OK VERSION=%s\n"; const char SAM_HANDSHAKE_I2P_ERROR[] = "HELLO REPLY RESULT=I2P_ERROR\n"; const char SAM_SESSION_CREATE[] = "SESSION CREATE"; const char SAM_SESSION_CREATE_REPLY_OK[] = "SESSION STATUS RESULT=OK DESTINATION=%s\n"; const char SAM_SESSION_CREATE_DUPLICATED_ID[] = "SESSION STATUS RESULT=DUPLICATED_ID\n"; const char SAM_SESSION_CREATE_DUPLICATED_DEST[] = "SESSION STATUS RESULT=DUPLICATED_DEST\n"; const char SAM_SESSION_STATUS_INVALID_KEY[] = "SESSION STATUS RESULT=INVALID_KEY\n"; const char SAM_SESSION_STATUS_I2P_ERROR[] = "SESSION STATUS RESULT=I2P_ERROR MESSAGE=%s\n"; const char SAM_STREAM_CONNECT[] = "STREAM CONNECT"; const char SAM_STREAM_STATUS_OK[] = "STREAM STATUS RESULT=OK\n"; const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n"; const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER\n"; const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR\n"; const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT"; const char SAM_DATAGRAM_SEND[] = "DATAGRAM SEND"; const char SAM_DEST_GENERATE[] = "DEST GENERATE"; const char SAM_DEST_REPLY[] = "DEST REPLY PUB=%s PRIV=%s\n"; const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n"; const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP"; const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n"; const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n"; const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n"; const char SAM_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=INVALID_KEY_NOT_FOUND NAME=%s\n"; const char SAM_PARAM_MIN[] = "MIN"; const char SAM_PARAM_MAX[] = "MAX"; const char SAM_PARAM_STYLE[] = "STYLE"; const char SAM_PARAM_ID[] = "ID"; const char SAM_PARAM_SILENT[] = "SILENT"; const char SAM_PARAM_DESTINATION[] = "DESTINATION"; const char SAM_PARAM_NAME[] = "NAME"; const char SAM_PARAM_SIGNATURE_TYPE[] = "SIGNATURE_TYPE"; const char SAM_PARAM_CRYPTO_TYPE[] = "CRYPTO_TYPE"; const char SAM_PARAM_SIZE[] = "SIZE"; const char SAM_VALUE_TRANSIENT[] = "TRANSIENT"; const char SAM_VALUE_STREAM[] = "STREAM"; const char SAM_VALUE_DATAGRAM[] = "DATAGRAM"; const char SAM_VALUE_RAW[] = "RAW"; const char SAM_VALUE_TRUE[] = "true"; const char SAM_VALUE_FALSE[] = "false"; const char SAM_VALUE_HOST[] = "HOST"; const char SAM_VALUE_PORT[] = "PORT"; enum SAMSocketType { eSAMSocketTypeUnknown, eSAMSocketTypeSession, eSAMSocketTypeStream, eSAMSocketTypeAcceptor, eSAMSocketTypeTerminated }; class SAMBridge; struct SAMSession; class SAMSocket: public std::enable_shared_from_this { public: SAMSocket (SAMBridge& owner); ~SAMSocket (); void CloseStream (const char* reason); // TODO: implement it better boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; }; void ReceiveHandshake (); void SetSocketType (SAMSocketType socketType) { m_SocketType = socketType; }; SAMSocketType GetSocketType () const { return m_SocketType; }; void Terminate (const char* reason); private: void HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred); void SendMessageReply (const char * msg, size_t len, bool close); void HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close); void Receive (); void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); void I2PReceive (); void HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); void HandleI2PAccept (std::shared_ptr stream); void HandleWriteI2PData (const boost::system::error_code& ecode); void HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len); void ProcessSessionCreate (char * buf, size_t len); void ProcessStreamConnect (char * buf, size_t len, size_t rem); void ProcessStreamAccept (char * buf, size_t len); void ProcessDestGenerate (char * buf, size_t len); void ProcessNamingLookup (char * buf, size_t len); void SendI2PError(const std::string & msg); size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0 void ExtractParams (char * buf, std::map& params); void Connect (std::shared_ptr remote); void HandleConnectLeaseSetRequestComplete (std::shared_ptr leaseSet); void SendNamingLookupReply (std::shared_ptr identity); void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr leaseSet, i2p::data::IdentHash ident); void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode); void SendSessionCreateReplyOk (); private: SAMBridge& m_Owner; boost::asio::ip::tcp::socket m_Socket; boost::asio::deadline_timer m_Timer; char m_Buffer[SAM_SOCKET_BUFFER_SIZE + 1]; size_t m_BufferOffset; uint8_t m_StreamBuffer[SAM_SOCKET_BUFFER_SIZE]; SAMSocketType m_SocketType; std::string m_ID; // nickname bool m_IsSilent; bool m_IsAccepting; // for eSAMSocketTypeAcceptor only std::shared_ptr m_Stream; std::shared_ptr m_Session; }; struct SAMSession { std::shared_ptr localDestination; std::list > m_Sockets; std::shared_ptr UDPEndpoint; std::mutex m_SocketsMutex; /** safely add a socket to this session */ void AddSocket(const std::shared_ptr & sock) { std::lock_guard lock(m_SocketsMutex); m_Sockets.push_back(sock); } /** safely remove a socket from this session */ void DelSocket(const std::shared_ptr & sock) { std::lock_guard lock(m_SocketsMutex); m_Sockets.remove(sock); } /** get a list holding a copy of all sam sockets from this session */ std::list > ListSockets() { std::list > l; { std::lock_guard lock(m_SocketsMutex); for(const auto& sock : m_Sockets ) l.push_back(sock); } return l; } SAMSession (std::shared_ptr dest); ~SAMSession (); void CloseStreams (); }; class SAMBridge { public: SAMBridge (const std::string& address, int port); ~SAMBridge (); void Start (); void Stop (); boost::asio::io_service& GetService () { return m_Service; }; std::shared_ptr CreateSession (const std::string& id, const std::string& destination, // empty string means transient const std::map * params); void CloseSession (const std::string& id); std::shared_ptr FindSession (const std::string& id) const; /** send raw data to remote endpoint from our UDP Socket */ void SendTo(const uint8_t * buf, size_t len, std::shared_ptr remote); private: void Run (); void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr socket); void ReceiveDatagram (); void HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred); private: bool m_IsRunning; std::thread * m_Thread; boost::asio::io_service m_Service; boost::asio::ip::tcp::acceptor m_Acceptor; boost::asio::ip::udp::endpoint m_DatagramEndpoint, m_SenderEndpoint; boost::asio::ip::udp::socket m_DatagramSocket; mutable std::mutex m_SessionsMutex; std::map > m_Sessions; uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1]; public: // for HTTP const decltype(m_Sessions)& GetSessions () const { return m_Sessions; }; }; } } #endif i2pd-2.17.0/libi2pd_client/SOCKS.cpp000066400000000000000000000613071321131324000167050ustar00rootroot00000000000000#include #include #include #include #include "SOCKS.h" #include "Identity.h" #include "Streaming.h" #include "Destination.h" #include "ClientContext.h" #include "I2PEndian.h" #include "I2PTunnel.h" #include "I2PService.h" #include "util.h" namespace i2p { namespace proxy { static const size_t socks_buffer_size = 8192; static const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse static const size_t SOCKS_FORWARDER_BUFFER_SIZE = 8192; static const size_t SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE = 8; struct SOCKSDnsAddress { uint8_t size; char value[max_socks_hostname_size]; void FromString (const std::string& str) { size = str.length(); if (str.length() > max_socks_hostname_size) size = max_socks_hostname_size; memcpy(value,str.c_str(),size); } std::string ToString() { return std::string(value, size); } void push_back (char c) { value[size++] = c; } }; class SOCKSServer; class SOCKSHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: enum state { GET_SOCKSV, GET_COMMAND, GET_PORT, GET_IPV4, GET4_IDENT, GET4A_HOST, GET5_AUTHNUM, GET5_AUTH, GET5_REQUESTV, GET5_GETRSV, GET5_GETADDRTYPE, GET5_IPV6, GET5_HOST_SIZE, GET5_HOST, READY, UPSTREAM_RESOLVE, UPSTREAM_CONNECT, UPSTREAM_HANDSHAKE }; enum authMethods { AUTH_NONE = 0, //No authentication, skip to next step AUTH_GSSAPI = 1, //GSSAPI authentication AUTH_USERPASSWD = 2, //Username and password AUTH_UNACCEPTABLE = 0xff //No acceptable method found }; enum addrTypes { ADDR_IPV4 = 1, //IPv4 address (4 octets) ADDR_DNS = 3, // DNS name (up to 255 octets) ADDR_IPV6 = 4 //IPV6 address (16 octets) }; enum errTypes { SOCKS5_OK = 0, // No error for SOCKS5 SOCKS5_GEN_FAIL = 1, // General server failure SOCKS5_RULE_DENIED = 2, // Connection disallowed by ruleset SOCKS5_NET_UNREACH = 3, // Network unreachable SOCKS5_HOST_UNREACH = 4, // Host unreachable SOCKS5_CONN_REFUSED = 5, // Connection refused by the peer SOCKS5_TTL_EXPIRED = 6, // TTL Expired SOCKS5_CMD_UNSUP = 7, // Command unsuported SOCKS5_ADDR_UNSUP = 8, // Address type unsuported SOCKS4_OK = 90, // No error for SOCKS4 SOCKS4_FAIL = 91, // Failed establishing connecting or not allowed SOCKS4_IDENTD_MISSING = 92, // Couldn't connect to the identd server SOCKS4_IDENTD_DIFFER = 93 // The ID reported by the application and by identd differ }; enum cmdTypes { CMD_CONNECT = 1, // TCP Connect CMD_BIND = 2, // TCP Bind CMD_UDP = 3 // UDP associate }; enum socksVersions { SOCKS4 = 4, // SOCKS4 SOCKS5 = 5 // SOCKS5 }; union address { uint32_t ip; SOCKSDnsAddress dns; uint8_t ipv6[16]; }; void EnterState(state nstate, uint8_t parseleft = 1); bool HandleData(uint8_t *sock_buff, std::size_t len); bool ValidateSOCKSRequest(); void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); boost::asio::const_buffers_1 GenerateSOCKS5SelectAuth(authMethods method); boost::asio::const_buffers_1 GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port); boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port); boost::asio::const_buffers_1 GenerateUpstreamRequest(); bool Socks5ChooseAuth(); void SocksRequestFailed(errTypes error); void SocksRequestSuccess(); void SentSocksFailed(const boost::system::error_code & ecode); void SentSocksDone(const boost::system::error_code & ecode); void SentSocksResponse(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); void ForwardSOCKS(); void SocksUpstreamSuccess(); void AsyncUpstreamSockRead(); void SendUpstreamRequest(); void HandleUpstreamData(uint8_t * buff, std::size_t len); void HandleUpstreamSockSend(const boost::system::error_code & ecode, std::size_t bytes_transfered); void HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr); void HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr); boost::asio::ip::tcp::resolver m_proxy_resolver; uint8_t m_sock_buff[socks_buffer_size]; std::shared_ptr m_sock, m_upstreamSock; std::shared_ptr m_stream; uint8_t *m_remaining_data; //Data left to be sent uint8_t *m_remaining_upstream_data; //upstream data left to be forwarded uint8_t m_response[7+max_socks_hostname_size]; uint8_t m_upstream_response[SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE]; uint8_t m_upstream_request[14+max_socks_hostname_size]; std::size_t m_upstream_response_len; address m_address; //Address std::size_t m_remaining_data_len; //Size of the data left to be sent uint32_t m_4aip; //Used in 4a requests uint16_t m_port; uint8_t m_command; uint8_t m_parseleft; //Octets left to parse authMethods m_authchosen; //Authentication chosen addrTypes m_addrtype; //Address type chosen socksVersions m_socksv; //Socks version cmdTypes m_cmd; // Command requested state m_state; const bool m_UseUpstreamProxy; // do we want to use the upstream proxy for non i2p addresses? const std::string m_UpstreamProxyAddress; const uint16_t m_UpstreamProxyPort; public: SOCKSHandler(SOCKSServer * parent, std::shared_ptr sock, const std::string & upstreamAddr, const uint16_t upstreamPort, const bool useUpstream) : I2PServiceHandler(parent), m_proxy_resolver(parent->GetService()), m_sock(sock), m_stream(nullptr), m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4), m_UseUpstreamProxy(useUpstream), m_UpstreamProxyAddress(upstreamAddr), m_UpstreamProxyPort(upstreamPort) { m_address.ip = 0; EnterState(GET_SOCKSV); } ~SOCKSHandler() { Terminate(); } void Handle() { AsyncSockRead(); } }; void SOCKSHandler::AsyncSockRead() { LogPrint(eLogDebug, "SOCKS: async sock read"); if (m_sock) { m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size), std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { LogPrint(eLogError,"SOCKS: no socket for read"); } } void SOCKSHandler::Terminate() { if (Kill()) return; if (m_sock) { LogPrint(eLogDebug, "SOCKS: closing socket"); m_sock->close(); m_sock = nullptr; } if (m_upstreamSock) { LogPrint(eLogDebug, "SOCKS: closing upstream socket"); m_upstreamSock->close(); m_upstreamSock = nullptr; } if (m_stream) { LogPrint(eLogDebug, "SOCKS: closing stream"); m_stream.reset (); } Done(shared_from_this()); } boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port) { assert(error >= SOCKS4_OK); m_response[0] = '\x00'; //Version m_response[1] = error; //Response code htobe16buf(m_response+2,port); //Port htobe32buf(m_response+4,ip); //IP return boost::asio::const_buffers_1(m_response,8); } boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port) { size_t size = 6; assert(error <= SOCKS5_ADDR_UNSUP); m_response[0] = '\x05'; //Version m_response[1] = error; //Response code m_response[2] = '\x00'; //RSV m_response[3] = type; //Address type switch (type) { case ADDR_IPV4: size = 10; htobe32buf(m_response+4,addr.ip); break; case ADDR_IPV6: size = 22; memcpy(m_response+4,addr.ipv6, 16); break; case ADDR_DNS: size = 7+addr.dns.size; m_response[4] = addr.dns.size; memcpy(m_response+5,addr.dns.value, addr.dns.size); break; } htobe16buf(m_response+size-2,port); //Port return boost::asio::const_buffers_1(m_response,size); } boost::asio::const_buffers_1 SOCKSHandler::GenerateUpstreamRequest() { size_t upstreamRequestSize = 0; // TODO: negotiate with upstream // SOCKS 4a m_upstream_request[0] = '\x04'; //version m_upstream_request[1] = m_cmd; htobe16buf(m_upstream_request+2, m_port); m_upstream_request[4] = 0; m_upstream_request[5] = 0; m_upstream_request[6] = 0; m_upstream_request[7] = 1; // user id m_upstream_request[8] = 'i'; m_upstream_request[9] = '2'; m_upstream_request[10] = 'p'; m_upstream_request[11] = 'd'; m_upstream_request[12] = 0; upstreamRequestSize += 13; if (m_address.dns.size <= max_socks_hostname_size - ( upstreamRequestSize + 1) ) { // bounds check okay memcpy(m_upstream_request + upstreamRequestSize, m_address.dns.value, m_address.dns.size); upstreamRequestSize += m_address.dns.size; // null terminate m_upstream_request[++upstreamRequestSize] = 0; } else { LogPrint(eLogError, "SOCKS: BUG!!! m_addr.dns.sizs > max_socks_hostname - ( upstreamRequestSize + 1 ) )"); } return boost::asio::const_buffers_1(m_upstream_request, upstreamRequestSize); } bool SOCKSHandler::Socks5ChooseAuth() { m_response[0] = '\x05'; //Version m_response[1] = m_authchosen; //Response code boost::asio::const_buffers_1 response(m_response,2); if (m_authchosen == AUTH_UNACCEPTABLE) { LogPrint(eLogWarning, "SOCKS: v5 authentication negotiation failed"); boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, shared_from_this(), std::placeholders::_1)); return false; } else { LogPrint(eLogDebug, "SOCKS: v5 choosing authentication method: ", m_authchosen); boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse, shared_from_this(), std::placeholders::_1)); return true; } } /* All hope is lost beyond this point */ void SOCKSHandler::SocksRequestFailed(SOCKSHandler::errTypes error) { boost::asio::const_buffers_1 response(nullptr,0); assert(error != SOCKS4_OK && error != SOCKS5_OK); switch (m_socksv) { case SOCKS4: LogPrint(eLogWarning, "SOCKS: v4 request failed: ", error); if (error < SOCKS4_OK) error = SOCKS4_FAIL; //Transparently map SOCKS5 errors response = GenerateSOCKS4Response(error, m_4aip, m_port); break; case SOCKS5: LogPrint(eLogWarning, "SOCKS: v5 request failed: ", error); response = GenerateSOCKS5Response(error, m_addrtype, m_address, m_port); break; } boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed, shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::SocksRequestSuccess() { boost::asio::const_buffers_1 response(nullptr,0); //TODO: this should depend on things like the command type and callbacks may change switch (m_socksv) { case SOCKS4: LogPrint(eLogInfo, "SOCKS: v4 connection success"); response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port); break; case SOCKS5: LogPrint(eLogInfo, "SOCKS: v5 connection success"); auto s = i2p::client::context.GetAddressBook().ToAddress(GetOwner()->GetLocalDestination()->GetIdentHash()); address ad; ad.dns.FromString(s); //HACK only 16 bits passed in port as SOCKS5 doesn't allow for more response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, ad, m_stream->GetRecvStreamID()); break; } boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone, shared_from_this(), std::placeholders::_1)); } void SOCKSHandler::EnterState(SOCKSHandler::state nstate, uint8_t parseleft) { switch (nstate) { case GET_PORT: parseleft = 2; break; case GET_IPV4: m_addrtype = ADDR_IPV4; m_address.ip = 0; parseleft = 4; break; case GET4_IDENT: m_4aip = m_address.ip; break; case GET4A_HOST: case GET5_HOST: m_addrtype = ADDR_DNS; m_address.dns.size = 0; break; case GET5_IPV6: m_addrtype = ADDR_IPV6; parseleft = 16; break; default:; } m_parseleft = parseleft; m_state = nstate; } bool SOCKSHandler::ValidateSOCKSRequest() { if ( m_cmd != CMD_CONNECT ) { //TODO: we need to support binds and other shit! LogPrint(eLogError, "SOCKS: unsupported command: ", m_cmd); SocksRequestFailed(SOCKS5_CMD_UNSUP); return false; } //TODO: we may want to support other address types! if ( m_addrtype != ADDR_DNS ) { switch (m_socksv) { case SOCKS5: LogPrint(eLogError, "SOCKS: v5 unsupported address type: ", m_addrtype); break; case SOCKS4: LogPrint(eLogError, "SOCKS: request with v4a rejected because it's actually SOCKS4"); break; } SocksRequestFailed(SOCKS5_ADDR_UNSUP); return false; } return true; } bool SOCKSHandler::HandleData(uint8_t *sock_buff, std::size_t len) { assert(len); // This should always be called with a least a byte left to parse while (len > 0) { switch (m_state) { case GET_SOCKSV: m_socksv = (SOCKSHandler::socksVersions) *sock_buff; switch (*sock_buff) { case SOCKS4: EnterState(GET_COMMAND); //Initialize the parser at the right position break; case SOCKS5: EnterState(GET5_AUTHNUM); //Initialize the parser at the right position break; default: LogPrint(eLogError, "SOCKS: rejected invalid version: ", ((int)*sock_buff)); Terminate(); return false; } break; case GET5_AUTHNUM: EnterState(GET5_AUTH, *sock_buff); break; case GET5_AUTH: m_parseleft --; if (*sock_buff == AUTH_NONE) m_authchosen = AUTH_NONE; if ( m_parseleft == 0 ) { if (!Socks5ChooseAuth()) return false; EnterState(GET5_REQUESTV); } break; case GET_COMMAND: switch (*sock_buff) { case CMD_CONNECT: case CMD_BIND: break; case CMD_UDP: if (m_socksv == SOCKS5) break; default: LogPrint(eLogError, "SOCKS: invalid command: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } m_cmd = (SOCKSHandler::cmdTypes)*sock_buff; switch (m_socksv) { case SOCKS5: EnterState(GET5_GETRSV); break; case SOCKS4: EnterState(GET_PORT); break; } break; case GET_PORT: m_port = (m_port << 8)|((uint16_t)*sock_buff); m_parseleft--; if (m_parseleft == 0) { switch (m_socksv) { case SOCKS5: EnterState(READY); break; case SOCKS4: EnterState(GET_IPV4); break; } } break; case GET_IPV4: m_address.ip = (m_address.ip << 8)|((uint32_t)*sock_buff); m_parseleft--; if (m_parseleft == 0) { switch (m_socksv) { case SOCKS5: EnterState(GET_PORT); break; case SOCKS4: EnterState(GET4_IDENT); m_4aip = m_address.ip; break; } } break; case GET4_IDENT: if (!*sock_buff) { if( m_4aip == 0 || m_4aip > 255 ) EnterState(READY); else EnterState(GET4A_HOST); } break; case GET4A_HOST: if (!*sock_buff) { EnterState(READY); break; } if (m_address.dns.size >= max_socks_hostname_size) { LogPrint(eLogError, "SOCKS: v4a req failed: destination is too large"); SocksRequestFailed(SOCKS4_FAIL); return false; } m_address.dns.push_back(*sock_buff); break; case GET5_REQUESTV: if (*sock_buff != SOCKS5) { LogPrint(eLogError,"SOCKS: v5 rejected unknown request version: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } EnterState(GET_COMMAND); break; case GET5_GETRSV: if ( *sock_buff != 0 ) { LogPrint(eLogError, "SOCKS: v5 unknown reserved field: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } EnterState(GET5_GETADDRTYPE); break; case GET5_GETADDRTYPE: switch (*sock_buff) { case ADDR_IPV4: EnterState(GET_IPV4); break; case ADDR_IPV6: EnterState(GET5_IPV6); break; case ADDR_DNS : EnterState(GET5_HOST_SIZE); break; default: LogPrint(eLogError, "SOCKS: v5 unknown address type: ", ((int)*sock_buff)); SocksRequestFailed(SOCKS5_GEN_FAIL); return false; } break; case GET5_IPV6: m_address.ipv6[16-m_parseleft] = *sock_buff; m_parseleft--; if (m_parseleft == 0) EnterState(GET_PORT); break; case GET5_HOST_SIZE: EnterState(GET5_HOST, *sock_buff); break; case GET5_HOST: m_address.dns.push_back(*sock_buff); m_parseleft--; if (m_parseleft == 0) EnterState(GET_PORT); break; default: LogPrint(eLogError, "SOCKS: parse state?? ", m_state); Terminate(); return false; } sock_buff++; len--; if (m_state == READY) { m_remaining_data_len = len; m_remaining_data = sock_buff; return ValidateSOCKSRequest(); } } return true; } void SOCKSHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { LogPrint(eLogDebug, "SOCKS: recieved ", len, " bytes"); if(ecode) { LogPrint(eLogWarning, "SOCKS: recv got error: ", ecode); Terminate(); return; } if (HandleData(m_sock_buff, len)) { if (m_state == READY) { const std::string addr = m_address.dns.ToString(); LogPrint(eLogInfo, "SOCKS: requested ", addr, ":" , m_port); const size_t addrlen = addr.size(); // does it end with .i2p? if ( addr.rfind(".i2p") == addrlen - 4) { // yes it does, make an i2p session GetOwner()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1), m_address.dns.ToString(), m_port); } else if (m_UseUpstreamProxy) { // forward it to upstream proxy ForwardSOCKS(); } else { // no upstream proxy SocksRequestFailed(SOCKS5_ADDR_UNSUP); } } else AsyncSockRead(); } } void SOCKSHandler::SentSocksFailed(const boost::system::error_code & ecode) { if (ecode) LogPrint (eLogError, "SOCKS: closing socket after sending failure because: ", ecode.message ()); Terminate(); } void SOCKSHandler::SentSocksDone(const boost::system::error_code & ecode) { if (!ecode) { if (Kill()) return; LogPrint (eLogInfo, "SOCKS: new I2PTunnel connection"); auto connection = std::make_shared(GetOwner(), m_sock, m_stream); GetOwner()->AddHandler (connection); connection->I2PConnect (m_remaining_data,m_remaining_data_len); Done(shared_from_this()); } else { LogPrint (eLogError, "SOCKS: closing socket after completion reply because: ", ecode.message ()); Terminate(); } } void SOCKSHandler::SentSocksResponse(const boost::system::error_code & ecode) { if (ecode) { LogPrint (eLogError, "SOCKS: closing socket after sending reply because: ", ecode.message ()); Terminate(); } } void SOCKSHandler::HandleStreamRequestComplete (std::shared_ptr stream) { if (stream) { m_stream = stream; SocksRequestSuccess(); } else { LogPrint (eLogError, "SOCKS: error when creating the stream, check the previous warnings for more info"); SocksRequestFailed(SOCKS5_HOST_UNREACH); } } void SOCKSHandler::ForwardSOCKS() { LogPrint(eLogInfo, "SOCKS: forwarding to upstream"); EnterState(UPSTREAM_RESOLVE); boost::asio::ip::tcp::resolver::query q(m_UpstreamProxyAddress, std::to_string(m_UpstreamProxyPort)); m_proxy_resolver.async_resolve(q, std::bind(&SOCKSHandler::HandleUpstreamResolved, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } void SOCKSHandler::AsyncUpstreamSockRead() { LogPrint(eLogDebug, "SOCKS: async upstream sock read"); if (m_upstreamSock) { m_upstreamSock->async_read_some(boost::asio::buffer(m_upstream_response, SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE), std::bind(&SOCKSHandler::HandleUpstreamSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } else { LogPrint(eLogError, "SOCKS: no upstream socket for read"); SocksRequestFailed(SOCKS5_GEN_FAIL); } } void SOCKSHandler::HandleUpstreamSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered) { if (ecode) { if (m_state == UPSTREAM_HANDSHAKE ) { // we are trying to handshake but it failed SocksRequestFailed(SOCKS5_NET_UNREACH); } else { LogPrint(eLogError, "SOCKS: bad state when reading from upstream: ", (int) m_state); } return; } HandleUpstreamData(m_upstream_response, bytes_transfered); } void SOCKSHandler::SocksUpstreamSuccess() { LogPrint(eLogInfo, "SOCKS: upstream success"); boost::asio::const_buffers_1 response(nullptr, 0); switch (m_socksv) { case SOCKS4: LogPrint(eLogInfo, "SOCKS: v4 connection success"); response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port); break; case SOCKS5: LogPrint(eLogInfo, "SOCKS: v5 connection success"); //HACK only 16 bits passed in port as SOCKS5 doesn't allow for more response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, m_address, m_port); break; } m_sock->send(response); auto forwarder = std::make_shared(GetOwner(), m_sock, m_upstreamSock); m_upstreamSock = nullptr; m_sock = nullptr; GetOwner()->AddHandler(forwarder); forwarder->Start(); Terminate(); } void SOCKSHandler::HandleUpstreamData(uint8_t * dataptr, std::size_t len) { if (m_state == UPSTREAM_HANDSHAKE) { m_upstream_response_len += len; // handle handshake data if (m_upstream_response_len < SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) { // too small, continue reading AsyncUpstreamSockRead(); } else if (len == SOCKS_UPSTREAM_SOCKS4A_REPLY_SIZE) { // just right uint8_t resp = m_upstream_response[1]; if (resp == SOCKS4_OK) { // we have connected ! SocksUpstreamSuccess(); } else { // upstream failure LogPrint(eLogError, "SOCKS: upstream proxy failure: ", (int) resp); // TODO: runtime error? SocksRequestFailed(SOCKS5_GEN_FAIL); } } else { // too big SocksRequestFailed(SOCKS5_GEN_FAIL); } } else { // invalid state LogPrint(eLogError, "SOCKS: invalid state reading from upstream: ", (int) m_state); } } void SOCKSHandler::SendUpstreamRequest() { LogPrint(eLogInfo, "SOCKS: negotiating with upstream proxy"); EnterState(UPSTREAM_HANDSHAKE); if (m_upstreamSock) { boost::asio::write(*m_upstreamSock, GenerateUpstreamRequest()); AsyncUpstreamSockRead(); } else { LogPrint(eLogError, "SOCKS: no upstream socket to send handshake to"); } } void SOCKSHandler::HandleUpstreamConnected(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) { if (ecode) { LogPrint(eLogWarning, "SOCKS: could not connect to upstream proxy: ", ecode.message()); SocksRequestFailed(SOCKS5_NET_UNREACH); return; } LogPrint(eLogInfo, "SOCKS: connected to upstream proxy"); SendUpstreamRequest(); } void SOCKSHandler::HandleUpstreamResolved(const boost::system::error_code & ecode, boost::asio::ip::tcp::resolver::iterator itr) { if (ecode) { // error resolving LogPrint(eLogWarning, "SOCKS: upstream proxy", m_UpstreamProxyAddress, " not resolved: ", ecode.message()); SocksRequestFailed(SOCKS5_NET_UNREACH); return; } LogPrint(eLogInfo, "SOCKS: upstream proxy resolved"); EnterState(UPSTREAM_CONNECT); auto & service = GetOwner()->GetService(); m_upstreamSock = std::make_shared(service); boost::asio::async_connect(*m_upstreamSock, itr, std::bind(&SOCKSHandler::HandleUpstreamConnected, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } SOCKSServer::SOCKSServer(const std::string& name, const std::string& address, int port, bool outEnable, const std::string& outAddress, uint16_t outPort, std::shared_ptr localDestination) : TCPIPAcceptor (address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ()), m_Name (name) { m_UseUpstreamProxy = false; if (outAddress.length() > 0 && outEnable) SetUpstreamProxy(outAddress, outPort); } std::shared_ptr SOCKSServer::CreateHandler(std::shared_ptr socket) { return std::make_shared (this, socket, m_UpstreamProxyAddress, m_UpstreamProxyPort, m_UseUpstreamProxy); } void SOCKSServer::SetUpstreamProxy(const std::string & addr, const uint16_t port) { m_UpstreamProxyAddress = addr; m_UpstreamProxyPort = port; m_UseUpstreamProxy = true; } } } i2pd-2.17.0/libi2pd_client/SOCKS.h000066400000000000000000000017021321131324000163430ustar00rootroot00000000000000#ifndef SOCKS_H__ #define SOCKS_H__ #include #include #include #include #include "I2PService.h" namespace i2p { namespace proxy { class SOCKSServer: public i2p::client::TCPIPAcceptor { public: SOCKSServer(const std::string& name, const std::string& address, int port, bool outEnable, const std::string& outAddress, uint16_t outPort, std::shared_ptr localDestination = nullptr); ~SOCKSServer() {}; void SetUpstreamProxy(const std::string & addr, const uint16_t port); protected: // Implements TCPIPAcceptor std::shared_ptr CreateHandler(std::shared_ptr socket); const char* GetName() { return m_Name.c_str (); } private: std::string m_Name; std::string m_UpstreamProxyAddress; uint16_t m_UpstreamProxyPort; bool m_UseUpstreamProxy; }; typedef SOCKSServer SOCKSProxy; } } #endif i2pd-2.17.0/libi2pd_client/WebSocks.cpp000066400000000000000000000261741321131324000175460ustar00rootroot00000000000000#include "WebSocks.h" #include "Log.h" #include #ifdef WITH_EVENTS #include "ClientContext.h" #include "Identity.h" #include "Destination.h" #include "Streaming.h" #include #include #include #include #define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) #if !GCC47_BOOST149 #include #endif namespace i2p { namespace client { typedef websocketpp::server WebSocksServerImpl; typedef std::function)> StreamConnectFunc; struct IWebSocksConn : public I2PServiceHandler { IWebSocksConn(I2PService * parent) : I2PServiceHandler(parent) {} virtual void Close() = 0; virtual void GotMessage(const websocketpp::connection_hdl & conn, WebSocksServerImpl::message_ptr msg) = 0; }; typedef std::shared_ptr WebSocksConn_ptr; WebSocksConn_ptr CreateWebSocksConn(const websocketpp::connection_hdl & conn, WebSocksImpl * parent); class WebSocksImpl { typedef std::mutex mutex_t; typedef std::unique_lock lock_t; typedef std::shared_ptr Destination_t; public: typedef WebSocksServerImpl ServerImpl; typedef ServerImpl::message_ptr MessagePtr; WebSocksImpl(const std::string & addr, int port) : Parent(nullptr), m_Run(false), m_Addr(addr), m_Port(port), m_Thread(nullptr) { m_Server.init_asio(); m_Server.set_open_handler(std::bind(&WebSocksImpl::ConnOpened, this, std::placeholders::_1)); } void InitializeDestination(WebSocks * parent) { Parent = parent; m_Dest = Parent->GetLocalDestination(); } ServerImpl::connection_ptr GetConn(const websocketpp::connection_hdl & conn) { return m_Server.get_con_from_hdl(conn); } void CloseConn(const websocketpp::connection_hdl & conn) { auto c = GetConn(conn); if(c) c->close(websocketpp::close::status::normal, "closed"); } void CreateStreamTo(const std::string & addr, int port, StreamConnectFunc complete) { auto & addressbook = i2p::client::context.GetAddressBook(); i2p::data::IdentHash ident; if(addressbook.GetIdentHash(addr, ident)) { // address found m_Dest->CreateStream(complete, ident, port); } else { // not found complete(nullptr); } } void ConnOpened(websocketpp::connection_hdl conn) { auto ptr = CreateWebSocksConn(conn, this); Parent->AddHandler(ptr); m_Conns.push_back(ptr); } void Start() { if(m_Run) return; // already started m_Server.listen(boost::asio::ip::address::from_string(m_Addr), m_Port); m_Server.start_accept(); m_Run = true; m_Thread = new std::thread([&] (){ while(m_Run) { try { m_Server.run(); } catch( std::exception & ex) { LogPrint(eLogError, "Websocks runtime exception: ", ex.what()); } } }); m_Dest->Start(); } void Stop() { for(const auto & conn : m_Conns) conn->Close(); m_Dest->Stop(); m_Run = false; m_Server.stop(); if(m_Thread) { m_Thread->join(); delete m_Thread; } m_Thread = nullptr; } boost::asio::ip::tcp::endpoint GetLocalEndpoint() { return boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(m_Addr), m_Port); } WebSocks * Parent; private: std::vector m_Conns; bool m_Run; ServerImpl m_Server; std::string m_Addr; int m_Port; std::thread * m_Thread; Destination_t m_Dest; }; struct WebSocksConn : public IWebSocksConn , public std::enable_shared_from_this { enum ConnState { eWSCInitial, eWSCTryConnect, eWSCFailConnect, eWSCOkayConnect, eWSCClose, eWSCEnd }; typedef WebSocksServerImpl ServerImpl; typedef ServerImpl::message_ptr Message_t; typedef websocketpp::connection_hdl ServerConn; typedef std::shared_ptr Destination_t; typedef std::shared_ptr StreamDest_t; typedef std::shared_ptr Stream_t; ServerConn m_Conn; Stream_t m_Stream; ConnState m_State; WebSocksImpl * m_Parent; std::string m_RemoteAddr; int m_RemotePort; uint8_t m_RecvBuf[2048]; WebSocksConn(const ServerConn & conn, WebSocksImpl * parent) : IWebSocksConn(parent->Parent), m_Conn(conn), m_Stream(nullptr), m_State(eWSCInitial), m_Parent(parent) { } ~WebSocksConn() { Close(); } void EnterState(ConnState state) { LogPrint(eLogDebug, "websocks: state ", m_State, " -> ", state); switch(m_State) { case eWSCInitial: if (state == eWSCClose) { m_State = eWSCClose; // connection was opened but never used LogPrint(eLogInfo, "websocks: connection closed but never used"); Close(); return; } else if (state == eWSCTryConnect) { // we will try to connect m_State = eWSCTryConnect; m_Parent->CreateStreamTo(m_RemoteAddr, m_RemotePort, std::bind(&WebSocksConn::ConnectResult, this, std::placeholders::_1)); } else { LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); } return; case eWSCTryConnect: if(state == eWSCOkayConnect) { // we connected okay LogPrint(eLogDebug, "websocks: connected to ", m_RemoteAddr, ":", m_RemotePort); SendResponse(""); m_State = eWSCOkayConnect; } else if(state == eWSCFailConnect) { // we did not connect okay LogPrint(eLogDebug, "websocks: failed to connect to ", m_RemoteAddr, ":", m_RemotePort); SendResponse("failed to connect"); m_State = eWSCFailConnect; EnterState(eWSCInitial); } else if(state == eWSCClose) { // premature close LogPrint(eLogWarning, "websocks: websocket connection closed prematurely"); m_State = eWSCClose; } else { LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); } return; case eWSCFailConnect: if (state == eWSCInitial) { // reset to initial state so we can try connecting again m_RemoteAddr = ""; m_RemotePort = 0; LogPrint(eLogDebug, "websocks: reset websocket conn to initial state"); m_State = eWSCInitial; } else if (state == eWSCClose) { // we are going to close the connection m_State = eWSCClose; Close(); } else { LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); } return; case eWSCOkayConnect: if(state == eWSCClose) { // graceful close m_State = eWSCClose; Close(); } else { LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); } case eWSCClose: if(state == eWSCEnd) { LogPrint(eLogDebug, "websocks: socket ended"); Kill(); auto me = shared_from_this(); Done(me); } else { LogPrint(eLogWarning, "websocks: invalid state change ", m_State, " -> ", state); } return; default: LogPrint(eLogError, "websocks: bad state ", m_State); } } void StartForwarding() { LogPrint(eLogDebug, "websocks: begin forwarding data"); uint8_t b[1]; m_Stream->Send(b, 0); AsyncRecv(); } void HandleAsyncRecv(const boost::system::error_code &ec, std::size_t n) { if(ec) { // error LogPrint(eLogWarning, "websocks: connection error ", ec.message()); EnterState(eWSCClose); } else { // forward data LogPrint(eLogDebug, "websocks recv ", n); std::string str((char*)m_RecvBuf, n); auto conn = m_Parent->GetConn(m_Conn); if(!conn) { LogPrint(eLogWarning, "websocks: connection is gone"); EnterState(eWSCClose); return; } conn->send(str); AsyncRecv(); } } void AsyncRecv() { m_Stream->AsyncReceive( boost::asio::buffer(m_RecvBuf, sizeof(m_RecvBuf)), std::bind(&WebSocksConn::HandleAsyncRecv, this, std::placeholders::_1, std::placeholders::_2), 60); } /** @brief send error message or empty string for success */ void SendResponse(const std::string & errormsg) { boost::property_tree::ptree resp; if(errormsg.size()) { resp.put("error", errormsg); resp.put("success", 0); } else { resp.put("success", 1); } std::ostringstream ss; write_json(ss, resp); auto conn = m_Parent->GetConn(m_Conn); if(conn) conn->send(ss.str()); } void ConnectResult(Stream_t stream) { m_Stream = stream; if(m_State == eWSCClose) { // premature close of websocket Close(); return; } if(m_Stream) { // connect good EnterState(eWSCOkayConnect); StartForwarding(); } else { // connect failed EnterState(eWSCFailConnect); } } virtual void GotMessage(const websocketpp::connection_hdl & conn, WebSocksServerImpl::message_ptr msg) { (void) conn; std::string payload = msg->get_payload(); if(m_State == eWSCOkayConnect) { // forward to server LogPrint(eLogDebug, "websocks: forward ", payload.size()); m_Stream->Send((uint8_t*)payload.c_str(), payload.size()); } else if (m_State == eWSCInitial) { // recv connect request auto itr = payload.find(":"); if(itr == std::string::npos) { // no port m_RemotePort = 0; m_RemoteAddr = payload; } else { // includes port m_RemotePort = std::stoi(payload.substr(itr+1)); m_RemoteAddr = payload.substr(0, itr); } EnterState(eWSCTryConnect); } else { // wtf? LogPrint(eLogWarning, "websocks: got message in invalid state ", m_State); } } virtual void Close() { if(m_State == eWSCClose) { LogPrint(eLogDebug, "websocks: closing connection"); if(m_Stream) m_Stream->Close(); m_Parent->CloseConn(m_Conn); EnterState(eWSCEnd); } else { EnterState(eWSCClose); } } }; WebSocksConn_ptr CreateWebSocksConn(const websocketpp::connection_hdl & conn, WebSocksImpl * parent) { auto ptr = std::make_shared(conn, parent); auto c = parent->GetConn(conn); c->set_message_handler(std::bind(&WebSocksConn::GotMessage, ptr.get(), std::placeholders::_1, std::placeholders::_2)); return ptr; } } } #else // no websocket support namespace i2p { namespace client { class WebSocksImpl { public: WebSocksImpl(const std::string & addr, int port) : m_Addr(addr), m_Port(port) { } ~WebSocksImpl() { } void Start() { LogPrint(eLogInfo, "WebSockets not enabled on compile time"); } void Stop() { } void InitializeDestination(WebSocks * parent) { } boost::asio::ip::tcp::endpoint GetLocalEndpoint() { return boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(m_Addr), m_Port); } std::string m_Addr; int m_Port; }; } } #endif namespace i2p { namespace client { WebSocks::WebSocks(const std::string & addr, int port, std::shared_ptr localDestination) : m_Impl(new WebSocksImpl(addr, port)) { m_Impl->InitializeDestination(this); } WebSocks::~WebSocks() { delete m_Impl; } void WebSocks::Start() { m_Impl->Start(); GetLocalDestination()->Start(); } boost::asio::ip::tcp::endpoint WebSocks::GetLocalEndpoint() const { return m_Impl->GetLocalEndpoint(); } void WebSocks::Stop() { m_Impl->Stop(); GetLocalDestination()->Stop(); } } } i2pd-2.17.0/libi2pd_client/WebSocks.h000066400000000000000000000011331321131324000171770ustar00rootroot00000000000000#ifndef WEBSOCKS_H_ #define WEBSOCKS_H_ #include #include #include "I2PService.h" #include "Destination.h" namespace i2p { namespace client { class WebSocksImpl; /** @brief websocket socks proxy server */ class WebSocks : public i2p::client::I2PService { public: WebSocks(const std::string & addr, int port, std::shared_ptr localDestination); ~WebSocks(); void Start(); void Stop(); boost::asio::ip::tcp::endpoint GetLocalEndpoint() const; const char * GetName() { return "WebSOCKS Proxy"; } private: WebSocksImpl * m_Impl; }; } } #endif i2pd-2.17.0/libi2pd_client/Websocket.cpp000066400000000000000000000110311321131324000177360ustar00rootroot00000000000000#ifdef WITH_EVENTS #include "Websocket.h" #include "Log.h" #include #include #include #include #include #define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) #if !GCC47_BOOST149 #include #endif #include namespace i2p { namespace event { typedef websocketpp::server ServerImpl; typedef websocketpp::connection_hdl ServerConn; class WebsocketServerImpl : public EventListener { private: typedef ServerImpl::message_ptr MessagePtr; public: WebsocketServerImpl(const std::string & addr, int port) : m_run(false), m_ws_thread(nullptr), m_ev_thread(nullptr), m_WebsocketTicker(m_Service) { m_server.init_asio(); m_server.set_open_handler(std::bind(&WebsocketServerImpl::ConnOpened, this, std::placeholders::_1)); m_server.set_close_handler(std::bind(&WebsocketServerImpl::ConnClosed, this, std::placeholders::_1)); m_server.set_message_handler(std::bind(&WebsocketServerImpl::OnConnMessage, this, std::placeholders::_1, std::placeholders::_2)); m_server.listen(boost::asio::ip::address::from_string(addr), port); } ~WebsocketServerImpl() { } void Start() { m_run = true; m_server.start_accept(); m_ws_thread = new std::thread([&] () { while(m_run) { try { m_server.run(); } catch (std::exception & e ) { LogPrint(eLogError, "Websocket server: ", e.what()); } } }); m_ev_thread = new std::thread([&] () { while(m_run) { try { m_Service.run(); break; } catch (std::exception & e ) { LogPrint(eLogError, "Websocket service: ", e.what()); } } }); ScheduleTick(); } void Stop() { m_run = false; m_Service.stop(); m_server.stop(); if(m_ev_thread) { m_ev_thread->join(); delete m_ev_thread; } m_ev_thread = nullptr; if(m_ws_thread) { m_ws_thread->join(); delete m_ws_thread; } m_ws_thread = nullptr; } void ConnOpened(ServerConn c) { std::lock_guard lock(m_connsMutex); m_conns.insert(c); } void ConnClosed(ServerConn c) { std::lock_guard lock(m_connsMutex); m_conns.erase(c); } void OnConnMessage(ServerConn conn, ServerImpl::message_ptr msg) { (void) conn; (void) msg; } void HandleTick(const boost::system::error_code & ec) { if(ec != boost::asio::error::operation_aborted) LogPrint(eLogError, "Websocket ticker: ", ec.message()); // pump collected events to us i2p::event::core.PumpCollected(this); ScheduleTick(); } void ScheduleTick() { LogPrint(eLogDebug, "Websocket schedule tick"); boost::posix_time::seconds dlt(1); m_WebsocketTicker.expires_from_now(dlt); m_WebsocketTicker.async_wait(std::bind(&WebsocketServerImpl::HandleTick, this, std::placeholders::_1)); } /** @brief called from m_ev_thread */ void HandlePumpEvent(const EventType & ev, const uint64_t & val) { EventType e; for (const auto & i : ev) e[i.first] = i.second; e["number"] = std::to_string(val); HandleEvent(e); } /** @brief called from m_ws_thread */ void HandleEvent(const EventType & ev) { std::lock_guard lock(m_connsMutex); boost::property_tree::ptree event; for (const auto & item : ev) { event.put(item.first, item.second); } std::ostringstream ss; write_json(ss, event); std::string s = ss.str(); ConnList::iterator it; for (it = m_conns.begin(); it != m_conns.end(); ++it) { ServerImpl::connection_ptr con = m_server.get_con_from_hdl(*it); con->send(s); } } private: typedef std::set > ConnList; bool m_run; std::thread * m_ws_thread; std::thread * m_ev_thread; std::mutex m_connsMutex; ConnList m_conns; ServerImpl m_server; boost::asio::io_service m_Service; boost::asio::deadline_timer m_WebsocketTicker; }; WebsocketServer::WebsocketServer(const std::string & addr, int port) : m_impl(new WebsocketServerImpl(addr, port)) {} WebsocketServer::~WebsocketServer() { delete m_impl; } void WebsocketServer::Start() { m_impl->Start(); } void WebsocketServer::Stop() { m_impl->Stop(); } EventListener * WebsocketServer::ToListener() { return m_impl; } } } #endif i2pd-2.17.0/libi2pd_client/Websocket.h000066400000000000000000000005771321131324000174200ustar00rootroot00000000000000#ifndef WEBSOCKET_H__ #define WEBSOCKET_H__ #include "Event.h" namespace i2p { namespace event { class WebsocketServerImpl; class WebsocketServer { public: WebsocketServer(const std::string & addr, int port); ~WebsocketServer(); void Start(); void Stop(); EventListener * ToListener(); private: WebsocketServerImpl * m_impl; }; } } #endif i2pd-2.17.0/qt/000077500000000000000000000000001321131324000130515ustar00rootroot00000000000000i2pd-2.17.0/qt/.gitignore000066400000000000000000000000111321131324000150310ustar00rootroot00000000000000/build*/ i2pd-2.17.0/qt/i2pd_qt/000077500000000000000000000000001321131324000144135ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/.gitignore000066400000000000000000000001461321131324000164040ustar00rootroot00000000000000i2pd_qt.pro.user* moc_* ui_* qrc_* i2pd_qt Makefile* *.stash object_script.* i2pd_qt_plugin_import.cppi2pd-2.17.0/qt/i2pd_qt/ClientTunnelPane.cpp000066400000000000000000000233271321131324000203360ustar00rootroot00000000000000#include "ClientTunnelPane.h" #include "ClientContext.h" #include "SignatureTypeComboboxFactory.h" #include "QVBoxLayout" ClientTunnelPane::ClientTunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener, ClientTunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow): TunnelPane(tunnelsPageUpdateListener, tunconf, wrongInputPane_, wrongInputLabel_, mainWindow) {} void ClientTunnelPane::setGroupBoxTitle(const QString & title) { clientTunnelNameGroupBox->setTitle(title); } void ClientTunnelPane::deleteClientTunnelForm() { TunnelPane::deleteTunnelForm(); delete clientTunnelNameGroupBox; clientTunnelNameGroupBox=nullptr; //gridLayoutWidget_2->deleteLater(); //gridLayoutWidget_2=nullptr; } int ClientTunnelPane::appendClientTunnelForm( ClientTunnelConfig* tunnelConfig, QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height) { ClientTunnelPane& ui = *this; clientTunnelNameGroupBox = new QGroupBox(tunnelsFormGridLayoutWidget); clientTunnelNameGroupBox->setObjectName(QStringLiteral("clientTunnelNameGroupBox")); //tunnel gridLayoutWidget_2 = new QWidget(clientTunnelNameGroupBox); QComboBox *tunnelTypeComboBox = new QComboBox(gridLayoutWidget_2); tunnelTypeComboBox->setObjectName(QStringLiteral("tunnelTypeComboBox")); tunnelTypeComboBox->addItem("Client", i2p::client::I2P_TUNNELS_SECTION_TYPE_CLIENT); tunnelTypeComboBox->addItem("Socks", i2p::client::I2P_TUNNELS_SECTION_TYPE_SOCKS); tunnelTypeComboBox->addItem("Websocks", i2p::client::I2P_TUNNELS_SECTION_TYPE_WEBSOCKS); tunnelTypeComboBox->addItem("HTTP Proxy", i2p::client::I2P_TUNNELS_SECTION_TYPE_HTTPPROXY); tunnelTypeComboBox->addItem("UDP Client", i2p::client::I2P_TUNNELS_SECTION_TYPE_UDPCLIENT); int h=(7+4)*60; gridLayoutWidget_2->setGeometry(QRect(0, 0, 561, h)); clientTunnelNameGroupBox->setGeometry(QRect(0, 0, 561, h)); { const QString& type = tunnelConfig->getType(); int index=0; if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_CLIENT)tunnelTypeComboBox->setCurrentIndex(index); ++index; if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_SOCKS)tunnelTypeComboBox->setCurrentIndex(index); ++index; if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_WEBSOCKS)tunnelTypeComboBox->setCurrentIndex(index); ++index; if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_HTTPPROXY)tunnelTypeComboBox->setCurrentIndex(index); ++index; if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_UDPCLIENT)tunnelTypeComboBox->setCurrentIndex(index); ++index; } setupTunnelPane(tunnelConfig, clientTunnelNameGroupBox, gridLayoutWidget_2, tunnelTypeComboBox, tunnelsFormGridLayoutWidget, tunnelsRow, height, h); //this->tunnelGroupBox->setGeometry(QRect(0, tunnelsFormGridLayoutWidget->height()+10, 561, (7+5)*40+10)); /* std::string destination; */ //host ui.horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.destinationLabel = new QLabel(gridLayoutWidget_2); destinationLabel->setObjectName(QStringLiteral("destinationLabel")); horizontalLayout_2->addWidget(destinationLabel); ui.destinationLineEdit = new QLineEdit(gridLayoutWidget_2); destinationLineEdit->setObjectName(QStringLiteral("destinationLineEdit")); destinationLineEdit->setText(tunnelConfig->getdest().c_str()); QObject::connect(destinationLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(destinationLineEdit); ui.destinationHorizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(destinationHorizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); /* * int port; */ int gridIndex = 2; { int port = tunnelConfig->getport(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.portLabel = new QLabel(gridLayoutWidget_2); portLabel->setObjectName(QStringLiteral("portLabel")); horizontalLayout_2->addWidget(portLabel); ui.portLineEdit = new QLineEdit(gridLayoutWidget_2); portLineEdit->setObjectName(QStringLiteral("portLineEdit")); portLineEdit->setText(QString::number(port)); portLineEdit->setMaximumWidth(80); QObject::connect(portLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(portLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } /* * std::string keys; */ { std::string keys = tunnelConfig->getkeys(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.keysLabel = new QLabel(gridLayoutWidget_2); keysLabel->setObjectName(QStringLiteral("keysLabel")); horizontalLayout_2->addWidget(keysLabel); ui.keysLineEdit = new QLineEdit(gridLayoutWidget_2); keysLineEdit->setObjectName(QStringLiteral("keysLineEdit")); keysLineEdit->setText(keys.c_str()); QObject::connect(keysLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(keysLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } /* * std::string address; */ { std::string address = tunnelConfig->getaddress(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.addressLabel = new QLabel(gridLayoutWidget_2); addressLabel->setObjectName(QStringLiteral("addressLabel")); horizontalLayout_2->addWidget(addressLabel); ui.addressLineEdit = new QLineEdit(gridLayoutWidget_2); addressLineEdit->setObjectName(QStringLiteral("addressLineEdit")); addressLineEdit->setText(address.c_str()); QObject::connect(addressLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(addressLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } /* int destinationPort; i2p::data::SigningKeyType sigType; */ { int destinationPort = tunnelConfig->getdestinationPort(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.destinationPortLabel = new QLabel(gridLayoutWidget_2); destinationPortLabel->setObjectName(QStringLiteral("destinationPortLabel")); horizontalLayout_2->addWidget(destinationPortLabel); ui.destinationPortLineEdit = new QLineEdit(gridLayoutWidget_2); destinationPortLineEdit->setObjectName(QStringLiteral("destinationPortLineEdit")); destinationPortLineEdit->setText(QString::number(destinationPort)); destinationPortLineEdit->setMaximumWidth(80); QObject::connect(destinationPortLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(destinationPortLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { i2p::data::SigningKeyType sigType = tunnelConfig->getsigType(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.sigTypeLabel = new QLabel(gridLayoutWidget_2); sigTypeLabel->setObjectName(QStringLiteral("sigTypeLabel")); horizontalLayout_2->addWidget(sigTypeLabel); ui.sigTypeComboBox = SignatureTypeComboBoxFactory::createSignatureTypeComboBox(gridLayoutWidget_2, sigType); sigTypeComboBox->setObjectName(QStringLiteral("sigTypeComboBox")); QObject::connect(sigTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updated())); horizontalLayout_2->addWidget(sigTypeComboBox); QPushButton * lockButton2 = new QPushButton(gridLayoutWidget_2); horizontalLayout_2->addWidget(lockButton2); widgetlocks.add(new widgetlock(sigTypeComboBox, lockButton2)); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { I2CPParameters& i2cpParameters = tunnelConfig->getI2cpParameters(); appendControlsForI2CPParameters(i2cpParameters, gridIndex); } retranslateClientTunnelForm(ui); tunnelGridLayout->invalidate(); return h; } ServerTunnelPane* ClientTunnelPane::asServerTunnelPane(){return nullptr;} ClientTunnelPane* ClientTunnelPane::asClientTunnelPane(){return this;} i2pd-2.17.0/qt/i2pd_qt/ClientTunnelPane.h000066400000000000000000000066401321131324000200020ustar00rootroot00000000000000#ifndef CLIENTTUNNELPANE_H #define CLIENTTUNNELPANE_H #include "QGridLayout" #include "QVBoxLayout" #include "TunnelPane.h" class ClientTunnelConfig; class ServerTunnelPane; class TunnelPane; class ClientTunnelPane : public TunnelPane { Q_OBJECT public: ClientTunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener, ClientTunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow); virtual ~ClientTunnelPane(){} virtual ServerTunnelPane* asServerTunnelPane(); virtual ClientTunnelPane* asClientTunnelPane(); int appendClientTunnelForm(ClientTunnelConfig* tunnelConfig, QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height); void deleteClientTunnelForm(); private: QGroupBox *clientTunnelNameGroupBox; //tunnel QWidget *gridLayoutWidget_2; //destination QHBoxLayout *horizontalLayout_2; QLabel *destinationLabel; QLineEdit *destinationLineEdit; QSpacerItem *destinationHorizontalSpacer; //port QLabel * portLabel; QLineEdit * portLineEdit; //keys QLabel * keysLabel; QLineEdit * keysLineEdit; //address QLabel * addressLabel; QLineEdit * addressLineEdit; //destinationPort QLabel * destinationPortLabel; QLineEdit * destinationPortLineEdit; //sigType QLabel * sigTypeLabel; QComboBox * sigTypeComboBox; protected slots: virtual void setGroupBoxTitle(const QString & title); private: void retranslateClientTunnelForm(ClientTunnelPane& /*ui*/) { typeLabel->setText(QApplication::translate("cltTunForm", "Client tunnel type:", 0)); destinationLabel->setText(QApplication::translate("cltTunForm", "Destination:", 0)); portLabel->setText(QApplication::translate("cltTunForm", "Port:", 0)); keysLabel->setText(QApplication::translate("cltTunForm", "Keys:", 0)); destinationPortLabel->setText(QApplication::translate("cltTunForm", "Destination port:", 0)); addressLabel->setText(QApplication::translate("cltTunForm", "Address:", 0)); sigTypeLabel->setText(QApplication::translate("cltTunForm", "Signature type:", 0)); } protected: virtual bool applyDataFromUIToTunnelConfig() { QString cannotSaveSettings = QApplication::tr("Cannot save settings."); bool ok=TunnelPane::applyDataFromUIToTunnelConfig(); if(!ok)return false; ClientTunnelConfig* ctc=tunnelConfig->asClientTunnelConfig(); assert(ctc!=nullptr); //destination ctc->setdest(destinationLineEdit->text().toStdString()); auto portStr=portLineEdit->text(); int portInt=portStr.toInt(&ok); if(!ok){ highlightWrongInput(QApplication::tr("Bad port, must be int.")+" "+cannotSaveSettings,portLineEdit); return false; } ctc->setport(portInt); ctc->setkeys(keysLineEdit->text().toStdString()); ctc->setaddress(addressLineEdit->text().toStdString()); auto dportStr=destinationPortLineEdit->text(); int dportInt=dportStr.toInt(&ok); if(!ok){ highlightWrongInput(QApplication::tr("Bad destinationPort, must be int.")+" "+cannotSaveSettings,destinationPortLineEdit); return false; } ctc->setdestinationPort(dportInt); ctc->setsigType(readSigTypeComboboxUI(sigTypeComboBox)); return true; } }; #endif // CLIENTTUNNELPANE_H i2pd-2.17.0/qt/i2pd_qt/DaemonQT.cpp000066400000000000000000000106651321131324000165770ustar00rootroot00000000000000#include "DaemonQT.h" #include "Daemon.h" #include "mainwindow.h" #include #include #include #include namespace i2p { namespace qt { Worker::Worker (DaemonQTImpl& daemon): m_Daemon (daemon) { } void Worker::startDaemon() { qDebug("Performing daemon start..."); //try{ m_Daemon.start(); qDebug("Daemon started."); emit resultReady(false, ""); /*}catch(std::exception ex){ emit resultReady(true, ex.what()); }catch(...){ emit resultReady(true, QObject::tr("Error: unknown exception")); }*/ } void Worker::restartDaemon() { qDebug("Performing daemon restart..."); //try{ m_Daemon.restart(); qDebug("Daemon restarted."); emit resultReady(false, ""); /*}catch(std::exception ex){ emit resultReady(true, ex.what()); }catch(...){ emit resultReady(true, QObject::tr("Error: unknown exception")); }*/ } void Worker::stopDaemon() { qDebug("Performing daemon stop..."); //try{ m_Daemon.stop(); qDebug("Daemon stopped."); emit resultReady(false, ""); /*}catch(std::exception ex){ emit resultReady(true, ex.what()); }catch(...){ emit resultReady(true, QObject::tr("Error: unknown exception")); }*/ } Controller::Controller(DaemonQTImpl& daemon): m_Daemon (daemon) { Worker *worker = new Worker (m_Daemon); worker->moveToThread(&workerThread); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &Controller::startDaemon, worker, &Worker::startDaemon); connect(this, &Controller::stopDaemon, worker, &Worker::stopDaemon); connect(this, &Controller::restartDaemon, worker, &Worker::restartDaemon); connect(worker, &Worker::resultReady, this, &Controller::handleResults); workerThread.start(); } Controller::~Controller() { qDebug("Closing and waiting for daemon worker thread..."); workerThread.quit(); workerThread.wait(); qDebug("Waiting for daemon worker thread finished."); if(m_Daemon.isRunning()) { qDebug("Stopping the daemon..."); m_Daemon.stop(); qDebug("Stopped the daemon."); } } DaemonQTImpl::DaemonQTImpl (): mutex(nullptr), m_IsRunning(nullptr), m_RunningChangedCallback(nullptr) { } DaemonQTImpl::~DaemonQTImpl () { delete mutex; } bool DaemonQTImpl::init(int argc, char* argv[]) { mutex=new QMutex(QMutex::Recursive); setRunningCallback(0); m_IsRunning=false; return Daemon.init(argc,argv); } void DaemonQTImpl::start() { QMutexLocker locker(mutex); setRunning(true); Daemon.start(); } void DaemonQTImpl::stop() { QMutexLocker locker(mutex); Daemon.stop(); setRunning(false); } void DaemonQTImpl::restart() { QMutexLocker locker(mutex); stop(); start(); } void DaemonQTImpl::setRunningCallback(runningChangedCallback cb) { m_RunningChangedCallback = cb; } bool DaemonQTImpl::isRunning() { return m_IsRunning; } void DaemonQTImpl::setRunning(bool newValue) { bool oldValue = m_IsRunning; if(oldValue!=newValue) { m_IsRunning = newValue; if(m_RunningChangedCallback) m_RunningChangedCallback(); } } int RunQT (int argc, char* argv[]) { QApplication app(argc, argv); int result; { DaemonQTImpl daemon; qDebug("Initialising the daemon..."); bool daemonInitSuccess = daemon.init(argc, argv); if(!daemonInitSuccess) { QMessageBox::critical(0, "Error", "Daemon init failed"); return 1; } qDebug("Initialised, creating the main window..."); MainWindow w; qDebug("Before main window.show()..."); w.show (); { i2p::qt::Controller daemonQtController(daemon); w.setI2PController(&daemonQtController); qDebug("Starting the daemon..."); emit daemonQtController.startDaemon(); //daemon.start (); qDebug("Starting GUI event loop..."); result = app.exec(); //daemon.stop (); } } //QMessageBox::information(&w, "Debug", "demon stopped"); qDebug("Exiting the application"); return result; } } } i2pd-2.17.0/qt/i2pd_qt/DaemonQT.h000066400000000000000000000032271321131324000162400ustar00rootroot00000000000000#ifndef DAEMONQT_H #define DAEMONQT_H #include #include #include #include namespace i2p { namespace qt { class DaemonQTImpl { public: DaemonQTImpl (); ~DaemonQTImpl (); typedef void (*runningChangedCallback)(); /** * @brief init * @param argc * @param argv * @return success */ bool init(int argc, char* argv[]); void start(); void stop(); void restart(); void setRunningCallback(runningChangedCallback cb); bool isRunning(); private: void setRunning(bool running); void showError(std::string errorMsg); private: QMutex* mutex; bool m_IsRunning; runningChangedCallback m_RunningChangedCallback; }; class Worker : public QObject { Q_OBJECT public: Worker (DaemonQTImpl& daemon); private: DaemonQTImpl& m_Daemon; public slots: void startDaemon(); void restartDaemon(); void stopDaemon(); signals: void resultReady(bool failed, QString failureMessage); }; class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller(DaemonQTImpl& daemon); ~Controller(); private: DaemonQTImpl& m_Daemon; public slots: void handleResults(bool failed, QString failureMessage){ if(failed){ QMessageBox::critical(0, QObject::tr("Error"), failureMessage); } } signals: void startDaemon(); void stopDaemon(); void restartDaemon(); }; } } #endif // DAEMONQT_H i2pd-2.17.0/qt/i2pd_qt/MainWindowItems.cpp000066400000000000000000000000361321131324000201740ustar00rootroot00000000000000#include "MainWindowItems.h" i2pd-2.17.0/qt/i2pd_qt/MainWindowItems.h000066400000000000000000000004211321131324000176370ustar00rootroot00000000000000#ifndef MAINWINDOWITEMS_H #define MAINWINDOWITEMS_H #include #include #include #include #include #include #include #include "mainwindow.h" class MainWindow; #endif // MAINWINDOWITEMS_H i2pd-2.17.0/qt/i2pd_qt/README.md000066400000000000000000000001211321131324000156640ustar00rootroot00000000000000# Build Requirements * Qt 5 is necessary (because Qt4 lacks QtWidgets/ folder) i2pd-2.17.0/qt/i2pd_qt/ServerTunnelPane.cpp000066400000000000000000000353031321131324000203630ustar00rootroot00000000000000#include "ServerTunnelPane.h" #include "ClientContext.h" #include "SignatureTypeComboboxFactory.h" ServerTunnelPane::ServerTunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener, ServerTunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow): TunnelPane(tunnelsPageUpdateListener, tunconf, wrongInputPane_, wrongInputLabel_, mainWindow) {} void ServerTunnelPane::setGroupBoxTitle(const QString & title) { serverTunnelNameGroupBox->setTitle(title); } int ServerTunnelPane::appendServerTunnelForm( ServerTunnelConfig* tunnelConfig, QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height) { ServerTunnelPane& ui = *this; serverTunnelNameGroupBox = new QGroupBox(tunnelsFormGridLayoutWidget); serverTunnelNameGroupBox->setObjectName(QStringLiteral("serverTunnelNameGroupBox")); //tunnel gridLayoutWidget_2 = new QWidget(serverTunnelNameGroupBox); QComboBox *tunnelTypeComboBox = new QComboBox(gridLayoutWidget_2); tunnelTypeComboBox->setObjectName(QStringLiteral("tunnelTypeComboBox")); tunnelTypeComboBox->addItem("Server", i2p::client::I2P_TUNNELS_SECTION_TYPE_SERVER); tunnelTypeComboBox->addItem("HTTP", i2p::client::I2P_TUNNELS_SECTION_TYPE_HTTP); tunnelTypeComboBox->addItem("IRC", i2p::client::I2P_TUNNELS_SECTION_TYPE_IRC); tunnelTypeComboBox->addItem("UDP Server", i2p::client::I2P_TUNNELS_SECTION_TYPE_UDPSERVER); int h=19*60; gridLayoutWidget_2->setGeometry(QRect(0, 0, 561, h)); serverTunnelNameGroupBox->setGeometry(QRect(0, 0, 561, h)); { const QString& type = tunnelConfig->getType(); int index=0; if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_SERVER)tunnelTypeComboBox->setCurrentIndex(index); ++index; if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_HTTP)tunnelTypeComboBox->setCurrentIndex(index); ++index; if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_IRC)tunnelTypeComboBox->setCurrentIndex(index); ++index; if(type==i2p::client::I2P_TUNNELS_SECTION_TYPE_UDPSERVER)tunnelTypeComboBox->setCurrentIndex(index); ++index; } setupTunnelPane(tunnelConfig, serverTunnelNameGroupBox, gridLayoutWidget_2, tunnelTypeComboBox, tunnelsFormGridLayoutWidget, tunnelsRow, height, h); //this->tunnelGroupBox->setGeometry(QRect(0, tunnelsFormGridLayoutWidget->height()+10, 561, 18*40+10)); //host ui.horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.hostLabel = new QLabel(gridLayoutWidget_2); hostLabel->setObjectName(QStringLiteral("hostLabel")); horizontalLayout_2->addWidget(hostLabel); ui.hostLineEdit = new QLineEdit(gridLayoutWidget_2); hostLineEdit->setObjectName(QStringLiteral("hostLineEdit")); hostLineEdit->setText(tunnelConfig->gethost().c_str()); QObject::connect(hostLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(hostLineEdit); ui.hostHorizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(hostHorizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); int gridIndex = 2; { int port = tunnelConfig->getport(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.portLabel = new QLabel(gridLayoutWidget_2); portLabel->setObjectName(QStringLiteral("portLabel")); horizontalLayout_2->addWidget(portLabel); ui.portLineEdit = new QLineEdit(gridLayoutWidget_2); portLineEdit->setObjectName(QStringLiteral("portLineEdit")); portLineEdit->setText(QString::number(port)); portLineEdit->setMaximumWidth(80); QObject::connect(portLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(portLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { std::string keys = tunnelConfig->getkeys(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.keysLabel = new QLabel(gridLayoutWidget_2); keysLabel->setObjectName(QStringLiteral("keysLabel")); horizontalLayout_2->addWidget(keysLabel); ui.keysLineEdit = new QLineEdit(gridLayoutWidget_2); keysLineEdit->setObjectName(QStringLiteral("keysLineEdit")); keysLineEdit->setText(keys.c_str()); QObject::connect(keysLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(keysLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { int inPort = tunnelConfig->getinPort(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.inPortLabel = new QLabel(gridLayoutWidget_2); inPortLabel->setObjectName(QStringLiteral("inPortLabel")); horizontalLayout_2->addWidget(inPortLabel); ui.inPortLineEdit = new QLineEdit(gridLayoutWidget_2); inPortLineEdit->setObjectName(QStringLiteral("inPortLineEdit")); inPortLineEdit->setText(QString::number(inPort)); inPortLineEdit->setMaximumWidth(80); QObject::connect(inPortLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(inPortLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { std::string accessList = tunnelConfig->getaccessList(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.accessListLabel = new QLabel(gridLayoutWidget_2); accessListLabel->setObjectName(QStringLiteral("accessListLabel")); horizontalLayout_2->addWidget(accessListLabel); ui.accessListLineEdit = new QLineEdit(gridLayoutWidget_2); accessListLineEdit->setObjectName(QStringLiteral("accessListLineEdit")); accessListLineEdit->setText(accessList.c_str()); QObject::connect(accessListLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(accessListLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { std::string hostOverride = tunnelConfig->gethostOverride(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.hostOverrideLabel = new QLabel(gridLayoutWidget_2); hostOverrideLabel->setObjectName(QStringLiteral("hostOverrideLabel")); horizontalLayout_2->addWidget(hostOverrideLabel); ui.hostOverrideLineEdit = new QLineEdit(gridLayoutWidget_2); hostOverrideLineEdit->setObjectName(QStringLiteral("hostOverrideLineEdit")); hostOverrideLineEdit->setText(hostOverride.c_str()); QObject::connect(hostOverrideLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(hostOverrideLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { std::string webIRCPass = tunnelConfig->getwebircpass(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.webIRCPassLabel = new QLabel(gridLayoutWidget_2); webIRCPassLabel->setObjectName(QStringLiteral("webIRCPassLabel")); horizontalLayout_2->addWidget(webIRCPassLabel); ui.webIRCPassLineEdit = new QLineEdit(gridLayoutWidget_2); webIRCPassLineEdit->setObjectName(QStringLiteral("webIRCPassLineEdit")); webIRCPassLineEdit->setText(webIRCPass.c_str()); QObject::connect(webIRCPassLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(webIRCPassLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { bool gzip = tunnelConfig->getgzip(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.gzipCheckBox = new QCheckBox(gridLayoutWidget_2); gzipCheckBox->setObjectName(QStringLiteral("gzipCheckBox")); gzipCheckBox->setChecked(gzip); QObject::connect(gzipCheckBox, SIGNAL(stateChanged(int)), this, SLOT(updated())); horizontalLayout_2->addWidget(gzipCheckBox); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { i2p::data::SigningKeyType sigType = tunnelConfig->getsigType(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.sigTypeLabel = new QLabel(gridLayoutWidget_2); sigTypeLabel->setObjectName(QStringLiteral("sigTypeLabel")); horizontalLayout_2->addWidget(sigTypeLabel); ui.sigTypeComboBox = SignatureTypeComboBoxFactory::createSignatureTypeComboBox(gridLayoutWidget_2, sigType); sigTypeComboBox->setObjectName(QStringLiteral("sigTypeComboBox")); QObject::connect(sigTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updated())); horizontalLayout_2->addWidget(sigTypeComboBox); QPushButton * lockButton2 = new QPushButton(gridLayoutWidget_2); horizontalLayout_2->addWidget(lockButton2); widgetlocks.add(new widgetlock(sigTypeComboBox, lockButton2)); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { uint32_t maxConns = tunnelConfig->getmaxConns(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.maxConnsLabel = new QLabel(gridLayoutWidget_2); maxConnsLabel->setObjectName(QStringLiteral("maxConnsLabel")); horizontalLayout_2->addWidget(maxConnsLabel); ui.maxConnsLineEdit = new QLineEdit(gridLayoutWidget_2); maxConnsLineEdit->setObjectName(QStringLiteral("maxConnsLineEdit")); maxConnsLineEdit->setText(QString::number(maxConns)); maxConnsLineEdit->setMaximumWidth(80); QObject::connect(maxConnsLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(maxConnsLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { std::string address = tunnelConfig->getaddress(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.addressLabel = new QLabel(gridLayoutWidget_2); addressLabel->setObjectName(QStringLiteral("addressLabel")); horizontalLayout_2->addWidget(addressLabel); ui.addressLineEdit = new QLineEdit(gridLayoutWidget_2); addressLineEdit->setObjectName(QStringLiteral("addressLineEdit")); addressLineEdit->setText(address.c_str()); QObject::connect(addressLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(addressLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { bool isUniqueLocal = tunnelConfig->getisUniqueLocal(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); ui.isUniqueLocalCheckBox = new QCheckBox(gridLayoutWidget_2); isUniqueLocalCheckBox->setObjectName(QStringLiteral("isUniqueLocalCheckBox")); isUniqueLocalCheckBox->setChecked(isUniqueLocal); QObject::connect(gzipCheckBox, SIGNAL(stateChanged(int)), this, SLOT(updated())); horizontalLayout_2->addWidget(isUniqueLocalCheckBox); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { I2CPParameters& i2cpParameters = tunnelConfig->getI2cpParameters(); appendControlsForI2CPParameters(i2cpParameters, gridIndex); } retranslateServerTunnelForm(ui); tunnelGridLayout->invalidate(); return h; } void ServerTunnelPane::deleteServerTunnelForm() { TunnelPane::deleteTunnelForm(); delete serverTunnelNameGroupBox;//->deleteLater(); serverTunnelNameGroupBox=nullptr; //gridLayoutWidget_2->deleteLater(); //gridLayoutWidget_2=nullptr; } ServerTunnelPane* ServerTunnelPane::asServerTunnelPane(){return this;} ClientTunnelPane* ServerTunnelPane::asClientTunnelPane(){return nullptr;} i2pd-2.17.0/qt/i2pd_qt/ServerTunnelPane.h000066400000000000000000000125101321131324000200230ustar00rootroot00000000000000#ifndef SERVERTUNNELPANE_H #define SERVERTUNNELPANE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "QVBoxLayout" #include "QCheckBox" #include "assert.h" #include "TunnelPane.h" #include "TunnelsPageUpdateListener.h" class ServerTunnelConfig; class ClientTunnelPane; class ServerTunnelPane : public TunnelPane { Q_OBJECT public: ServerTunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener, ServerTunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow); virtual ~ServerTunnelPane(){} virtual ServerTunnelPane* asServerTunnelPane(); virtual ClientTunnelPane* asClientTunnelPane(); int appendServerTunnelForm(ServerTunnelConfig* tunnelConfig, QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height); void deleteServerTunnelForm(); private: QGroupBox *serverTunnelNameGroupBox; //tunnel QWidget *gridLayoutWidget_2; //host QHBoxLayout *horizontalLayout_2; QLabel *hostLabel; QLineEdit *hostLineEdit; QSpacerItem *hostHorizontalSpacer; //port QLabel * portLabel; QLineEdit * portLineEdit; //keys QLabel * keysLabel; QLineEdit * keysLineEdit; //inPort QLabel * inPortLabel; QLineEdit * inPortLineEdit; //accessList QLabel * accessListLabel; QLineEdit * accessListLineEdit; //hostOverride QLabel * hostOverrideLabel; QLineEdit * hostOverrideLineEdit; //webIRCPass QLabel * webIRCPassLabel; QLineEdit * webIRCPassLineEdit; //address QLabel * addressLabel; QLineEdit * addressLineEdit; //maxConns QLabel * maxConnsLabel; QLineEdit * maxConnsLineEdit; //gzip QCheckBox * gzipCheckBox; //isUniqueLocal QCheckBox * isUniqueLocalCheckBox; //sigType QLabel * sigTypeLabel; QComboBox * sigTypeComboBox; protected slots: virtual void setGroupBoxTitle(const QString & title); private: void retranslateServerTunnelForm(ServerTunnelPane& /*ui*/) { typeLabel->setText(QApplication::translate("srvTunForm", "Server tunnel type:", 0)); hostLabel->setText(QApplication::translate("srvTunForm", "Host:", 0)); portLabel->setText(QApplication::translate("srvTunForm", "Port:", 0)); keysLabel->setText(QApplication::translate("srvTunForm", "Keys:", 0)); inPortLabel->setText(QApplication::translate("srvTunForm", "InPort:", 0)); accessListLabel->setText(QApplication::translate("srvTunForm", "Access list:", 0)); hostOverrideLabel->setText(QApplication::translate("srvTunForm", "Host override:", 0)); webIRCPassLabel->setText(QApplication::translate("srvTunForm", "WebIRC password:", 0)); addressLabel->setText(QApplication::translate("srvTunForm", "Address:", 0)); maxConnsLabel->setText(QApplication::translate("srvTunForm", "Max connections:", 0)); gzipCheckBox->setText(QApplication::translate("srvTunForm", "GZip", 0)); isUniqueLocalCheckBox->setText(QApplication::translate("srvTunForm", "Is unique local", 0)); sigTypeLabel->setText(QApplication::translate("cltTunForm", "Signature type:", 0)); } protected: virtual bool applyDataFromUIToTunnelConfig() { QString cannotSaveSettings = QApplication::tr("Cannot save settings."); bool ok=TunnelPane::applyDataFromUIToTunnelConfig(); if(!ok)return false; ServerTunnelConfig* stc=tunnelConfig->asServerTunnelConfig(); assert(stc!=nullptr); stc->sethost(hostLineEdit->text().toStdString()); auto portStr=portLineEdit->text(); int portInt=portStr.toInt(&ok); if(!ok){ highlightWrongInput(QApplication::tr("Bad port, must be int.")+" "+cannotSaveSettings,portLineEdit); return false; } stc->setport(portInt); stc->setkeys(keysLineEdit->text().toStdString()); auto str=inPortLineEdit->text(); int inPortInt=str.toInt(&ok); if(!ok){ highlightWrongInput(QApplication::tr("Bad inPort, must be int.")+" "+cannotSaveSettings,inPortLineEdit); return false; } stc->setinPort(inPortInt); stc->setaccessList(accessListLineEdit->text().toStdString()); stc->sethostOverride(hostOverrideLineEdit->text().toStdString()); stc->setwebircpass(webIRCPassLineEdit->text().toStdString()); stc->setaddress(addressLineEdit->text().toStdString()); auto mcStr=maxConnsLineEdit->text(); uint32_t mcInt=(uint32_t)mcStr.toInt(&ok); if(!ok){ highlightWrongInput(QApplication::tr("Bad maxConns, must be int.")+" "+cannotSaveSettings,maxConnsLineEdit); return false; } stc->setmaxConns(mcInt); stc->setgzip(gzipCheckBox->isChecked()); stc->setisUniqueLocal(isUniqueLocalCheckBox->isChecked()); stc->setsigType(readSigTypeComboboxUI(sigTypeComboBox)); return true; } }; #endif // SERVERTUNNELPANE_H i2pd-2.17.0/qt/i2pd_qt/SignatureTypeComboboxFactory.cpp000066400000000000000000000000531321131324000227410ustar00rootroot00000000000000#include "SignatureTypeComboboxFactory.h" i2pd-2.17.0/qt/i2pd_qt/SignatureTypeComboboxFactory.h000066400000000000000000000120651321131324000224140ustar00rootroot00000000000000#ifndef SIGNATURETYPECOMBOBOXFACTORY_H #define SIGNATURETYPECOMBOBOXFACTORY_H #include #include #include #include "Identity.h" class SignatureTypeComboBoxFactory { static const QVariant createUserData(const uint16_t sigType) { return QVariant::fromValue((uint)sigType); } static void addItem(QComboBox* signatureTypeCombobox, QString text, const uint16_t sigType) { const QVariant userData = createUserData(sigType); signatureTypeCombobox->addItem(text, userData); } public: static const uint16_t getSigType(const QVariant& var) { return (uint16_t)var.toInt(); } static void fillComboBox(QComboBox* signatureTypeCombobox, uint16_t selectedSigType) { /* https://geti2p.net/spec/common-structures#certificate все коды перечислены это таблица "The defined Signing Public Key types are:" ? да see also: Identity.h line 55 */ int index=0; bool foundSelected=false; using namespace i2p::data; addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "DSA_SHA1", 0), SIGNING_KEY_TYPE_DSA_SHA1); //0 if(selectedSigType==SIGNING_KEY_TYPE_DSA_SHA1){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} ++index; addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "ECDSA_SHA256_P256", 0), SIGNING_KEY_TYPE_ECDSA_SHA256_P256); //1 if(selectedSigType==SIGNING_KEY_TYPE_ECDSA_SHA256_P256){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} ++index; addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "ECDSA_SHA384_P384", 0), SIGNING_KEY_TYPE_ECDSA_SHA384_P384); //2 if(selectedSigType==SIGNING_KEY_TYPE_ECDSA_SHA384_P384){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} ++index; addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "ECDSA_SHA512_P521", 0), SIGNING_KEY_TYPE_ECDSA_SHA512_P521); //3 if(selectedSigType==SIGNING_KEY_TYPE_ECDSA_SHA512_P521){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} ++index; addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "RSA_SHA256_2048", 0), SIGNING_KEY_TYPE_RSA_SHA256_2048); //4 if(selectedSigType==SIGNING_KEY_TYPE_RSA_SHA256_2048){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} ++index; addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "RSA_SHA384_3072", 0), SIGNING_KEY_TYPE_RSA_SHA384_3072); //5 if(selectedSigType==SIGNING_KEY_TYPE_RSA_SHA384_3072){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} ++index; addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "RSA_SHA512_4096", 0), SIGNING_KEY_TYPE_RSA_SHA512_4096); //6 if(selectedSigType==SIGNING_KEY_TYPE_RSA_SHA512_4096){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} ++index; addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "EDDSA_SHA512_ED25519", 0), SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519); //7 if(selectedSigType==SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} ++index; addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "EDDSA_SHA512_ED25519PH", 0), SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph); //8 if(selectedSigType==SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519ph){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} ++index; // the following signature type should never appear in netid=2 addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256", 0), SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256); //9 if(selectedSigType==SIGNING_KEY_TYPE_GOSTR3410_CRYPTO_PRO_A_GOSTR3411_256){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} ++index; addItem(signatureTypeCombobox, QApplication::translate("signatureTypeCombobox", "GOSTR3410_TC26_A_512_GOSTR3411_512", 0), SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512); //10 if(selectedSigType==SIGNING_KEY_TYPE_GOSTR3410_TC26_A_512_GOSTR3411_512){signatureTypeCombobox->setCurrentIndex(index);foundSelected=true;} ++index; if(!foundSelected){ addItem(signatureTypeCombobox, QString::number(selectedSigType), selectedSigType); //unknown sigtype signatureTypeCombobox->setCurrentIndex(index); } } static QComboBox* createSignatureTypeComboBox(QWidget* parent, uint16_t selectedSigType) { QComboBox* signatureTypeCombobox = new QComboBox(parent); fillComboBox(signatureTypeCombobox, selectedSigType); return signatureTypeCombobox; } }; #endif // SIGNATURETYPECOMBOBOXFACTORY_H i2pd-2.17.0/qt/i2pd_qt/TunnelConfig.cpp000066400000000000000000000052551321131324000175210ustar00rootroot00000000000000#include "TunnelConfig.h" void TunnelConfig::saveHeaderToStringStream(std::stringstream& out) { out << "[" << name << "]\n" << "type=" << type.toStdString() << "\n"; } void TunnelConfig::saveI2CPParametersToStringStream(std::stringstream& out) { if (i2cpParameters.getInbound_length().toUShort() != i2p::client::DEFAULT_INBOUND_TUNNEL_LENGTH) out << i2p::client::I2CP_PARAM_INBOUND_TUNNEL_LENGTH << "=" << i2cpParameters.getInbound_length().toStdString() << "\n"; if (i2cpParameters.getOutbound_length().toUShort() != i2p::client::DEFAULT_OUTBOUND_TUNNEL_LENGTH) out << i2p::client::I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH << "=" << i2cpParameters.getOutbound_length().toStdString() << "\n"; if (i2cpParameters.getInbound_quantity().toUShort() != i2p::client::DEFAULT_INBOUND_TUNNELS_QUANTITY) out << i2p::client::I2CP_PARAM_INBOUND_TUNNELS_QUANTITY << "=" << i2cpParameters.getInbound_quantity().toStdString() << "\n"; if (i2cpParameters.getOutbound_quantity().toUShort() != i2p::client::DEFAULT_OUTBOUND_TUNNELS_QUANTITY) out << i2p::client::I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY << "=" << i2cpParameters.getOutbound_quantity().toStdString() << "\n"; if (i2cpParameters.getCrypto_tagsToSend().toUShort() != i2p::client::DEFAULT_TAGS_TO_SEND) out << i2p::client::I2CP_PARAM_TAGS_TO_SEND << "=" << i2cpParameters.getCrypto_tagsToSend().toStdString() << "\n"; if (!i2cpParameters.getExplicitPeers().isEmpty()) //todo #947 out << i2p::client::I2CP_PARAM_EXPLICIT_PEERS << "=" << i2cpParameters.getExplicitPeers().toStdString() << "\n"; out << "\n"; } void ClientTunnelConfig::saveToStringStream(std::stringstream& out) { out << "address=" << address << "\n" << "port=" << port << "\n" << "destination=" << dest << "\n" << "keys=" << keys << "\n" << "destinationport=" << destinationPort << "\n" << "signaturetype=" << sigType << "\n"; } void ServerTunnelConfig::saveToStringStream(std::stringstream& out) { out << "host=" << host << "\n" << "port=" << port << "\n" << "keys=" << keys << "\n" << "signaturetype=" << sigType << "\n" << "inport=" << inPort << "\n" << "accesslist=" << accessList << "\n" << "gzip=" << (gzip?"true":"false") << "\n" << "enableuniquelocal=" << (isUniqueLocal?"true":"false") << "\n" << "address=" << address << "\n" << "hostoverride=" << hostOverride << "\n" << "webircpassword=" << webircpass << "\n" << "maxconns=" << maxConns << "\n"; } i2pd-2.17.0/qt/i2pd_qt/TunnelConfig.h000066400000000000000000000251011321131324000171560ustar00rootroot00000000000000#ifndef TUNNELCONFIG_H #define TUNNELCONFIG_H #include "QString" #include #include "../../libi2pd_client/ClientContext.h" #include "../../libi2pd/Destination.h" #include "TunnelsPageUpdateListener.h" class I2CPParameters{ QString inbound_length;//number of hops of an inbound tunnel. 3 by default; lower value is faster but dangerous QString outbound_length;//number of hops of an outbound tunnel. 3 by default; lower value is faster but dangerous QString inbound_quantity; //number of inbound tunnels. 5 by default QString outbound_quantity; //number of outbound tunnels. 5 by default QString crypto_tagsToSend; //number of ElGamal/AES tags to send. 40 by default; too low value may cause problems with tunnel building QString explicitPeers; //list of comma-separated b64 addresses of peers to use, default: unset public: I2CPParameters(): inbound_length(), outbound_length(), inbound_quantity(), outbound_quantity(), crypto_tagsToSend(), explicitPeers() {} const QString& getInbound_length(){return inbound_length;} const QString& getOutbound_length(){return outbound_length;} const QString& getInbound_quantity(){return inbound_quantity;} const QString& getOutbound_quantity(){return outbound_quantity;} const QString& getCrypto_tagsToSend(){return crypto_tagsToSend;} const QString& getExplicitPeers(){return explicitPeers;} void setInbound_length(QString inbound_length_){inbound_length=inbound_length_;} void setOutbound_length(QString outbound_length_){outbound_length=outbound_length_;} void setInbound_quantity(QString inbound_quantity_){inbound_quantity=inbound_quantity_;} void setOutbound_quantity(QString outbound_quantity_){outbound_quantity=outbound_quantity_;} void setCrypto_tagsToSend(QString crypto_tagsToSend_){crypto_tagsToSend=crypto_tagsToSend_;} void setExplicitPeers(QString explicitPeers_){explicitPeers=explicitPeers_;} }; class ClientTunnelConfig; class ServerTunnelConfig; class TunnelConfig { /* const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc"; const char I2P_TUNNELS_SECTION_TYPE_UDPCLIENT[] = "udpclient"; const char I2P_TUNNELS_SECTION_TYPE_UDPSERVER[] = "udpserver"; const char I2P_TUNNELS_SECTION_TYPE_SOCKS[] = "socks"; const char I2P_TUNNELS_SECTION_TYPE_WEBSOCKS[] = "websocks"; const char I2P_TUNNELS_SECTION_TYPE_HTTPPROXY[] = "httpproxy"; */ QString type; std::string name; public: TunnelConfig(std::string name_, QString& type_, I2CPParameters& i2cpParameters_): type(type_), name(name_), i2cpParameters(i2cpParameters_) {} virtual ~TunnelConfig(){} const QString& getType(){return type;} const std::string& getName(){return name;} void setType(const QString& type_){type=type_;} void setName(const std::string& name_){name=name_;} I2CPParameters& getI2cpParameters(){return i2cpParameters;} void saveHeaderToStringStream(std::stringstream& out); void saveI2CPParametersToStringStream(std::stringstream& out); virtual void saveToStringStream(std::stringstream& out)=0; virtual ClientTunnelConfig* asClientTunnelConfig()=0; virtual ServerTunnelConfig* asServerTunnelConfig()=0; private: I2CPParameters i2cpParameters; }; /* # mandatory parameters: std::string dest; if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); # optional parameters (may be omitted) std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); int destinationPort = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); # * keys -- our identity, if unset, will be generated on every startup, # if set and file missing, keys will be generated and placed to this file # * address -- local interface to bind # * signaturetype -- signature type for new destination. 0 (DSA/SHA1), 1 (EcDSA/SHA256) or 7 (EdDSA/SHA512) [somelabel] type = client address = 127.0.0.1 port = 6668 destination = irc.postman.i2p keys = irc-keys.dat */ class ClientTunnelConfig : public TunnelConfig { std::string dest; int port; std::string keys; std::string address; int destinationPort; i2p::data::SigningKeyType sigType; public: ClientTunnelConfig(std::string name_, QString type_, I2CPParameters& i2cpParameters_, std::string dest_, int port_, std::string keys_, std::string address_, int destinationPort_, i2p::data::SigningKeyType sigType_): TunnelConfig(name_, type_, i2cpParameters_), dest(dest_), port(port_), keys(keys_), address(address_), destinationPort(destinationPort_), sigType(sigType_){} std::string& getdest(){return dest;} int getport(){return port;} std::string & getkeys(){return keys;} std::string & getaddress(){return address;} int getdestinationPort(){return destinationPort;} i2p::data::SigningKeyType getsigType(){return sigType;} void setdest(const std::string& dest_){dest=dest_;} void setport(int port_){port=port_;} void setkeys(const std::string & keys_){keys=keys_;} void setaddress(const std::string & address_){address=address_;} void setdestinationPort(int destinationPort_){destinationPort=destinationPort_;} void setsigType(i2p::data::SigningKeyType sigType_){sigType=sigType_;} virtual void saveToStringStream(std::stringstream& out); virtual ClientTunnelConfig* asClientTunnelConfig(){return this;} virtual ServerTunnelConfig* asServerTunnelConfig(){return nullptr;} }; /* # mandatory parameters: # * host -- ip address of our service # * port -- port of our service # * keys -- file with LeaseSet of address in i2p std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); int port = section.second.get (I2P_SERVER_TUNNEL_PORT); std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); # optional parameters (may be omitted) int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); uint32_t maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, "127.0.0.1"); bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); # * inport -- optional, i2p service port, if unset - the same as 'port' # * accesslist -- comma-separated list of i2p addresses, allowed to connect # every address is b32 without '.b32.i2p' part [somelabel] type = server host = 127.0.0.1 port = 6667 keys = irc.dat */ class ServerTunnelConfig : public TunnelConfig { std::string host; int port; std::string keys; int inPort; std::string accessList; std::string hostOverride; std::string webircpass; bool gzip; i2p::data::SigningKeyType sigType; uint32_t maxConns; std::string address; bool isUniqueLocal; public: ServerTunnelConfig(std::string name_, QString type_, I2CPParameters& i2cpParameters_, std::string host_, int port_, std::string keys_, int inPort_, std::string accessList_, std::string hostOverride_, std::string webircpass_, bool gzip_, i2p::data::SigningKeyType sigType_, uint32_t maxConns_, std::string address_, bool isUniqueLocal_): TunnelConfig(name_, type_, i2cpParameters_), host(host_), port(port_), keys(keys_), inPort(inPort_), accessList(accessList_), hostOverride(hostOverride_), webircpass(webircpass_), gzip(gzip_), sigType(sigType_), maxConns(maxConns_), address(address_), isUniqueLocal(isUniqueLocal_) {} std::string& gethost(){return host;} int getport(){return port;} std::string& getkeys(){return keys;} int getinPort(){return inPort;} std::string& getaccessList(){return accessList;} std::string& gethostOverride(){return hostOverride;} std::string& getwebircpass(){return webircpass;} bool getgzip(){return gzip;} i2p::data::SigningKeyType getsigType(){return sigType;} uint32_t getmaxConns(){return maxConns;} std::string& getaddress(){return address;} bool getisUniqueLocal(){return isUniqueLocal;} void sethost(const std::string& host_){host=host_;} void setport(int port_){port=port_;} void setkeys(const std::string& keys_){keys=keys_;} void setinPort(int inPort_){inPort=inPort_;} void setaccessList(const std::string& accessList_){accessList=accessList_;} void sethostOverride(const std::string& hostOverride_){hostOverride=hostOverride_;} void setwebircpass(const std::string& webircpass_){webircpass=webircpass_;} void setgzip(bool gzip_){gzip=gzip_;} void setsigType(i2p::data::SigningKeyType sigType_){sigType=sigType_;} void setmaxConns(uint32_t maxConns_){maxConns=maxConns_;} void setaddress(const std::string& address_){address=address_;} void setisUniqueLocal(bool isUniqueLocal_){isUniqueLocal=isUniqueLocal_;} virtual void saveToStringStream(std::stringstream& out); virtual ClientTunnelConfig* asClientTunnelConfig(){return nullptr;} virtual ServerTunnelConfig* asServerTunnelConfig(){return this;} }; #endif // TUNNELCONFIG_H i2pd-2.17.0/qt/i2pd_qt/TunnelPane.cpp000066400000000000000000000301261321131324000171720ustar00rootroot00000000000000#include "TunnelPane.h" #include "QMessageBox" #include "mainwindow.h" #include "ui_mainwindow.h" TunnelPane::TunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener_, TunnelConfig* tunnelConfig_, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow_): QObject(), mainWindow(mainWindow_), wrongInputPane(wrongInputPane_), wrongInputLabel(wrongInputLabel_), tunnelConfig(tunnelConfig_), tunnelsPageUpdateListener(tunnelsPageUpdateListener_), gridLayoutWidget_2(nullptr) {} void TunnelPane::setupTunnelPane( TunnelConfig* tunnelConfig, QGroupBox *tunnelGroupBox, QWidget* gridLayoutWidget_2, QComboBox * tunnelTypeComboBox, QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height, int h) { tunnelGroupBox->setGeometry(0, tunnelsFormGridLayoutWidget->height(), gridLayoutWidget_2->width(), h); tunnelsFormGridLayoutWidget->resize(527, tunnelsFormGridLayoutWidget->height()+h); QObject::connect(tunnelTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updated())); this->tunnelGroupBox=tunnelGroupBox; gridLayoutWidget_2->setObjectName(QStringLiteral("gridLayoutWidget_2")); this->gridLayoutWidget_2=gridLayoutWidget_2; tunnelGridLayout = new QVBoxLayout(gridLayoutWidget_2); tunnelGridLayout->setObjectName(QStringLiteral("tunnelGridLayout")); tunnelGridLayout->setContentsMargins(5, 5, 5, 5); tunnelGridLayout->setSpacing(5); //header QHBoxLayout *headerHorizontalLayout = new QHBoxLayout(); headerHorizontalLayout->setObjectName(QStringLiteral("headerHorizontalLayout")); nameLabel = new QLabel(gridLayoutWidget_2); nameLabel->setObjectName(QStringLiteral("nameLabel")); headerHorizontalLayout->addWidget(nameLabel); nameLineEdit = new QLineEdit(gridLayoutWidget_2); nameLineEdit->setObjectName(QStringLiteral("nameLineEdit")); const QString& tunnelName=tunnelConfig->getName().c_str(); nameLineEdit->setText(tunnelName); setGroupBoxTitle(tunnelName); QObject::connect(nameLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(setGroupBoxTitle(const QString &))); QObject::connect(nameLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); headerHorizontalLayout->addWidget(nameLineEdit); headerHorizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); headerHorizontalLayout->addItem(headerHorizontalSpacer); deletePushButton = new QPushButton(gridLayoutWidget_2); deletePushButton->setObjectName(QStringLiteral("deletePushButton")); QObject::connect(deletePushButton, SIGNAL(released()), this, SLOT(deleteButtonReleased()));//MainWindow::DeleteTunnelNamed(std::string name) { headerHorizontalLayout->addWidget(deletePushButton); tunnelGridLayout->addLayout(headerHorizontalLayout); //type { const QString& type = tunnelConfig->getType(); QHBoxLayout * horizontalLayout_ = new QHBoxLayout(); horizontalLayout_->setObjectName(QStringLiteral("horizontalLayout_")); typeLabel = new QLabel(gridLayoutWidget_2); typeLabel->setObjectName(QStringLiteral("typeLabel")); horizontalLayout_->addWidget(typeLabel); horizontalLayout_->addWidget(tunnelTypeComboBox); QPushButton * lockButton1 = new QPushButton(gridLayoutWidget_2); horizontalLayout_->addWidget(lockButton1); widgetlocks.add(new widgetlock(tunnelTypeComboBox, lockButton1)); this->tunnelTypeComboBox=tunnelTypeComboBox; QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_); } retranslateTunnelForm(*this); } void TunnelPane::appendControlsForI2CPParameters(I2CPParameters& i2cpParameters, int& gridIndex) { { //number of hops of an inbound tunnel const QString& inbound_length=i2cpParameters.getInbound_length(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); inbound_lengthLabel = new QLabel(gridLayoutWidget_2); inbound_lengthLabel->setObjectName(QStringLiteral("inbound_lengthLabel")); horizontalLayout_2->addWidget(inbound_lengthLabel); inbound_lengthLineEdit = new QLineEdit(gridLayoutWidget_2); inbound_lengthLineEdit->setObjectName(QStringLiteral("inbound_lengthLineEdit")); inbound_lengthLineEdit->setText(inbound_length); inbound_lengthLineEdit->setMaximumWidth(80); QObject::connect(inbound_lengthLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(inbound_lengthLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { //number of hops of an outbound tunnel const QString& outbound_length=i2cpParameters.getOutbound_length(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); outbound_lengthLabel = new QLabel(gridLayoutWidget_2); outbound_lengthLabel->setObjectName(QStringLiteral("outbound_lengthLabel")); horizontalLayout_2->addWidget(outbound_lengthLabel); outbound_lengthLineEdit = new QLineEdit(gridLayoutWidget_2); outbound_lengthLineEdit->setObjectName(QStringLiteral("outbound_lengthLineEdit")); outbound_lengthLineEdit->setText(outbound_length); outbound_lengthLineEdit->setMaximumWidth(80); QObject::connect(outbound_lengthLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(outbound_lengthLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { //number of inbound tunnels const QString& inbound_quantity=i2cpParameters.getInbound_quantity(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); inbound_quantityLabel = new QLabel(gridLayoutWidget_2); inbound_quantityLabel->setObjectName(QStringLiteral("inbound_quantityLabel")); horizontalLayout_2->addWidget(inbound_quantityLabel); inbound_quantityLineEdit = new QLineEdit(gridLayoutWidget_2); inbound_quantityLineEdit->setObjectName(QStringLiteral("inbound_quantityLineEdit")); inbound_quantityLineEdit->setText(inbound_quantity); inbound_quantityLineEdit->setMaximumWidth(80); QObject::connect(inbound_quantityLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(inbound_quantityLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { //number of outbound tunnels const QString& outbound_quantity=i2cpParameters.getOutbound_quantity(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); outbound_quantityLabel = new QLabel(gridLayoutWidget_2); outbound_quantityLabel->setObjectName(QStringLiteral("outbound_quantityLabel")); horizontalLayout_2->addWidget(outbound_quantityLabel); outbound_quantityLineEdit = new QLineEdit(gridLayoutWidget_2); outbound_quantityLineEdit->setObjectName(QStringLiteral("outbound_quantityLineEdit")); outbound_quantityLineEdit->setText(outbound_quantity); outbound_quantityLineEdit->setMaximumWidth(80); QObject::connect(outbound_quantityLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(outbound_quantityLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } { //number of ElGamal/AES tags to send const QString& crypto_tagsToSend=i2cpParameters.getCrypto_tagsToSend(); QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); horizontalLayout_2->setObjectName(QStringLiteral("horizontalLayout_2")); crypto_tagsToSendLabel = new QLabel(gridLayoutWidget_2); crypto_tagsToSendLabel->setObjectName(QStringLiteral("crypto_tagsToSendLabel")); horizontalLayout_2->addWidget(crypto_tagsToSendLabel); crypto_tagsToSendLineEdit = new QLineEdit(gridLayoutWidget_2); crypto_tagsToSendLineEdit->setObjectName(QStringLiteral("crypto_tagsToSendLineEdit")); crypto_tagsToSendLineEdit->setText(crypto_tagsToSend); crypto_tagsToSendLineEdit->setMaximumWidth(80); QObject::connect(crypto_tagsToSendLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updated())); horizontalLayout_2->addWidget(crypto_tagsToSendLineEdit); QSpacerItem * horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_2->addItem(horizontalSpacer); tunnelGridLayout->addLayout(horizontalLayout_2); } retranslateI2CPParameters(); } void TunnelPane::updated() { std::string oldName=tunnelConfig->getName(); //validate and show red if invalid hideWrongInputLabel(); if(!mainWindow->applyTunnelsUiToConfigs())return; tunnelsPageUpdateListener->updated(oldName, tunnelConfig); } void TunnelPane::deleteButtonReleased() { QMessageBox msgBox; msgBox.setText(QApplication::tr("Are you sure to delete this tunnel?")); msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Cancel); int ret = msgBox.exec(); switch (ret) { case QMessageBox::Ok: // OK was clicked hideWrongInputLabel(); tunnelsPageUpdateListener->needsDeleting(tunnelConfig->getName()); break; case QMessageBox::Cancel: // Cancel was clicked return; } } /* const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client"; const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server"; const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http"; const char I2P_TUNNELS_SECTION_TYPE_IRC[] = "irc"; const char I2P_TUNNELS_SECTION_TYPE_UDPCLIENT[] = "udpclient"; const char I2P_TUNNELS_SECTION_TYPE_UDPSERVER[] = "udpserver"; const char I2P_TUNNELS_SECTION_TYPE_SOCKS[] = "socks"; const char I2P_TUNNELS_SECTION_TYPE_WEBSOCKS[] = "websocks"; const char I2P_TUNNELS_SECTION_TYPE_HTTPPROXY[] = "httpproxy"; */ QString TunnelPane::readTunnelTypeComboboxData() { return tunnelTypeComboBox->currentData().toString(); } i2p::data::SigningKeyType TunnelPane::readSigTypeComboboxUI(QComboBox* sigTypeComboBox) { return (i2p::data::SigningKeyType) sigTypeComboBox->currentData().toInt(); } void TunnelPane::deleteTunnelForm() { widgetlocks.deleteListeners(); } void TunnelPane::highlightWrongInput(QString warningText, QWidget* controlWithWrongInput) { wrongInputPane->setVisible(true); wrongInputLabel->setText(warningText); mainWindow->adjustSizesAccordingToWrongLabel(); if(controlWithWrongInput){ mainWindow->ui->tunnelsScrollArea->ensureWidgetVisible(controlWithWrongInput); controlWithWrongInput->setFocus(); } mainWindow->showTunnelsPage(); } void TunnelPane::hideWrongInputLabel() const { wrongInputPane->setVisible(false); mainWindow->adjustSizesAccordingToWrongLabel(); } i2pd-2.17.0/qt/i2pd_qt/TunnelPane.h000066400000000000000000000104021321131324000166320ustar00rootroot00000000000000#ifndef TUNNELPANE_H #define TUNNELPANE_H #include "QObject" #include "QWidget" #include "QComboBox" #include "QGridLayout" #include "QLabel" #include "QPushButton" #include "QApplication" #include "QLineEdit" #include "QGroupBox" #include "QVBoxLayout" #include "TunnelConfig.h" #include #include class ServerTunnelPane; class ClientTunnelPane; class TunnelConfig; class I2CPParameters; class MainWindow; class TunnelPane : public QObject { Q_OBJECT public: TunnelPane(TunnelsPageUpdateListener* tunnelsPageUpdateListener_, TunnelConfig* tunconf, QWidget* wrongInputPane_, QLabel* wrongInputLabel_, MainWindow* mainWindow_); virtual ~TunnelPane(){} void deleteTunnelForm(); void hideWrongInputLabel() const; void highlightWrongInput(QString warningText, QWidget* controlWithWrongInput); virtual ServerTunnelPane* asServerTunnelPane()=0; virtual ClientTunnelPane* asClientTunnelPane()=0; protected: MainWindow* mainWindow; QWidget * wrongInputPane; QLabel* wrongInputLabel; TunnelConfig* tunnelConfig; widgetlockregistry widgetlocks; TunnelsPageUpdateListener* tunnelsPageUpdateListener; QVBoxLayout *tunnelGridLayout; QGroupBox *tunnelGroupBox; QWidget* gridLayoutWidget_2; //header QLabel *nameLabel; QLineEdit *nameLineEdit; public: QLineEdit * getNameLineEdit() { return nameLineEdit; } public slots: void updated(); void deleteButtonReleased(); protected: QSpacerItem *headerHorizontalSpacer; QPushButton *deletePushButton; //type QComboBox *tunnelTypeComboBox; QLabel *typeLabel; //i2cp QLabel * inbound_lengthLabel; QLineEdit * inbound_lengthLineEdit; QLabel * outbound_lengthLabel; QLineEdit * outbound_lengthLineEdit; QLabel * inbound_quantityLabel; QLineEdit * inbound_quantityLineEdit; QLabel * outbound_quantityLabel; QLineEdit * outbound_quantityLineEdit; QLabel * crypto_tagsToSendLabel; QLineEdit * crypto_tagsToSendLineEdit; QString readTunnelTypeComboboxData(); //should be created by factory i2p::data::SigningKeyType readSigTypeComboboxUI(QComboBox* sigTypeComboBox); public: //returns false when invalid data at UI virtual bool applyDataFromUIToTunnelConfig() { tunnelConfig->setName(nameLineEdit->text().toStdString()); tunnelConfig->setType(readTunnelTypeComboboxData()); I2CPParameters& i2cpParams=tunnelConfig->getI2cpParameters(); i2cpParams.setInbound_length(inbound_lengthLineEdit->text()); i2cpParams.setInbound_quantity(inbound_quantityLineEdit->text()); i2cpParams.setOutbound_length(outbound_lengthLineEdit->text()); i2cpParams.setOutbound_quantity(outbound_quantityLineEdit->text()); i2cpParams.setCrypto_tagsToSend(crypto_tagsToSendLineEdit->text()); return true; } protected: void setupTunnelPane( TunnelConfig* tunnelConfig, QGroupBox *tunnelGroupBox, QWidget* gridLayoutWidget_2, QComboBox * tunnelTypeComboBox, QWidget *tunnelsFormGridLayoutWidget, int tunnelsRow, int height, int h); void appendControlsForI2CPParameters(I2CPParameters& i2cpParameters, int& gridIndex); public: int height() { return gridLayoutWidget_2?gridLayoutWidget_2->height():0; } protected slots: virtual void setGroupBoxTitle(const QString & title)=0; private: void retranslateTunnelForm(TunnelPane& ui) { ui.deletePushButton->setText(QApplication::translate("tunForm", "Delete Tunnel", 0)); ui.nameLabel->setText(QApplication::translate("tunForm", "Tunnel name:", 0)); } void retranslateI2CPParameters() { inbound_lengthLabel->setText(QApplication::translate("tunForm", "Number of hops of an inbound tunnel:", 0));; outbound_lengthLabel->setText(QApplication::translate("tunForm", "Number of hops of an outbound tunnel:", 0));; inbound_quantityLabel->setText(QApplication::translate("tunForm", "Number of inbound tunnels:", 0));; outbound_quantityLabel->setText(QApplication::translate("tunForm", "Number of outbound tunnels:", 0));; crypto_tagsToSendLabel->setText(QApplication::translate("tunForm", "Number of ElGamal/AES tags to send:", 0));; } }; #endif // TUNNELPANE_H i2pd-2.17.0/qt/i2pd_qt/TunnelsPageUpdateListener.h000066400000000000000000000004611321131324000216630ustar00rootroot00000000000000#ifndef TUNNELSPAGEUPDATELISTENER_H #define TUNNELSPAGEUPDATELISTENER_H class TunnelConfig; class TunnelsPageUpdateListener { public: virtual void updated(std::string oldName, TunnelConfig* tunConf)=0; virtual void needsDeleting(std::string oldName)=0; }; #endif // TUNNELSPAGEUPDATELISTENER_H i2pd-2.17.0/qt/i2pd_qt/android/000077500000000000000000000000001321131324000160335ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/.gitignore000066400000000000000000000000061321131324000200170ustar00rootroot00000000000000/gen/ i2pd-2.17.0/qt/i2pd_qt/android/AndroidManifest.xml000066400000000000000000000111551321131324000216270ustar00rootroot00000000000000 i2pd-2.17.0/qt/i2pd_qt/android/build.gradle000066400000000000000000000027361321131324000203220ustar00rootroot00000000000000buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.1.0' } } allprojects { repositories { jcenter() } } apply plugin: 'com.android.application' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } android { /******************************************************* * The following variables: * - androidBuildToolsVersion, * - androidCompileSdkVersion * - qt5AndroidDir - holds the path to qt android files * needed to build any Qt application * on Android. * * are defined in gradle.properties file. This file is * updated by QtCreator and androiddeployqt tools. * Changing them manually might break the compilation! *******************************************************/ compileSdkVersion androidCompileSdkVersion.toInteger() buildToolsVersion androidBuildToolsVersion sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] res.srcDirs = [qt5AndroidDir + '/res', 'res'] resources.srcDirs = ['src'] renderscript.srcDirs = ['src'] assets.srcDirs = ['assets'] jniLibs.srcDirs = ['libs'] } } lintOptions { abortOnError false } } i2pd-2.17.0/qt/i2pd_qt/android/libs/000077500000000000000000000000001321131324000167645ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/libs/android-support-v4.jar000066400000000000000000053315541321131324000231630ustar00rootroot00000000000000PKjH META-INF/PKPKjHMETA-INF/MANIFEST.MFMLK-. K-*ϳR03r.JM,IMu [*h%&*8%krrPKCDPK jHandroid/PK SjHandroid/support/PK jHandroid/support/annotation/PKjH+android/support/annotation/IntegerRes.classMO@E u`HH=U(Rf)tI%yC7wfg~~sӈ*&014[)pQkLcDiN22n,+n_GQ඘ 0#sXE;[ Dຸ`*%~LvnC U_/P{? pz^6¡2YZ-,ltj@k?y4_Qdn h[@P l;urF}EpoPKY$PKjH9android/support/annotation/RequiresPermission$Write.classQ=KA}yzj!"-V J ZXm!lKv5 ?J4)惙70_8!G*+p}~Ufd =J]9֧ʘ+ xVjm !~*J;dDWs~N2&͔+;f/s66eN{a\놳hbc ۻL9".Yr? &<􄓕r'.R *bU dUl ¦D5Ĉ]XֱӨ}PK2[4 PKjH)android/support/annotation/ArrayRes.classMO1"u`J8*,d d!\>tpb‘cz2&Z(`8 D9=Ʌ$Ii`dj߂ĨRą:Lc9^wD.))\b|6YC;wC?55{p?˃VsLTNEeieZF3+ݴ1zum[07VP|kۄh*a 4 8PKz%PKjH-android/support/annotation/WorkerThread.class=O0_+ c(b!CG-b(J 1ɩq:(@;D’;׺Oyhy8pPTc)1~'4z*83#g$rR7˔Flp>tl⋿Wq>PrpV̄d,a3T"ͭ}tR\Co؍"..냔jN]gh􂇈= 20\,J&"ϕ6s6\{)ņ~6C2)Ul[MLŞ}'PKy9MPKjH(android/support/annotation/AnimRes.classN1"0x[0^,KWH226.LR2dC«|(1b?|~|Ùs XT"\u$JlUkr25O$Xa]?z߄7qm1IKyT"irtl\A/ uqTKi/tMwIP9> 18=& AHf8uƣ! j)"5jgfcR1cG-M&l30ZAlC$8Bù/(KIPK%"PKjH,android/support/annotation/DrawableRes.classPMO1"u`8pd=UX"I d!\<;!%Kbk(ԃMl7yo2pg3hLE8R 23& r+\j +"J W% jjMIA{H&Q5 \U7eBKj>HٛR$Q9?M ZSrz!MA x]pE@3b?/dϿ*ѓ#M5{N ?؀&>U=ķp.}:t;:8PKw/%PKjH+android/support/annotation/MainThread.class=O0_+- C :2UmCK !&79WN5~? a3PHX{'sZ<34#]1˔!I$  a\ҳ\YJ}Fjb;Ou\,IJ.ʙ%lfZ5Nu*kQpVE>'c(%=BɈ?C xx4AP/õPIe⯊,ӹ;g+)-(6 v*VNa}lܱ]B-U>tPKj]++PKjH-android/support/annotation/StyleableRes.classPMO1"7S%.LY&. ̓?ezؤoM҃3=`ЌM%8 tbU( #{+ B[ƙ%\kcF BK_$rM ?҉Q2ٺ~uyTdK/n6E_x1GpTk>g<0` "3rE=x2)g.NX:U=7p.}* :h;PKR:&PKjH)android/support/annotation/ColorInt.classPMK1ԶkA<=źŅ-K/$݆&e-yDzX0yCf޼>> pÉSv"#!9Ag.BelΓRSɩR0#Z\EK>'hl,mlXKo]0iJpQݐb΍]WKmWܚZc O#A74z$Q`G SB.W+]?[!eWv݃;5ԿyVhG_P8DAPKɻdPKjH)android/support/annotation/StyleRes.classN1ǧ ~< K$Y%\a6  f8uʣ ;S-~:5Ckw8[Rl G٭PwP vlԳvk:p 49i99PK'$PKjH.android/support/annotation/TransitionRes.classMO@_^<?D/M0%zvK_Gg=&Mdgfgw2onqCÑ@{b|Ep#oi~PKePKjH'android/support/annotation/IntDef.classKAƟkK+/>бuge:GEe0yy>>\CG٥1 d@aLhUD@ ]Ӭ@шfee,\JnlѳZB1a\-M߅%K&n* Pъ\{66+|dڼojCF _^J3B5v|F7҈::R|$SYrܥl f6(`Svy!졈RR ePK;f)PKjH8android/support/annotation/RequiresPermission$Read.classQMK@}ӦƪQA ѓh Of([Mk(q`{a>y3 {!6C!v**&\y鴴>QƔ^y]$J[vl'9)CB\Lx=g߭p1I/]_{8^B.Jr-Yd9?bl|bpZjY_boTP:6^W-[&4ŽK_P%dI PKlBePKjH,android/support/annotation/FractionRes.classN0ϥmhRбLJA rS*ĩWcx(ęv%|;龾?>8wKHWE!ZXWb#\2rsYE\)mEJeL+X']=!Cm#N aRz<|hÀ,"5jgfcRޟch+[Af`(>[%*E=Is_P8Fɉ/PKƴ3"PKjH(android/support/annotation/MenuRes.classMO@Eu`8p*HR) YRݒ<QYcC7wfg~|x3.$O#꫘CyHYg%R*̵QkLcN4*љ]`7z߄K|MQ඘ 0#s'vxuqTK2z݆'P{? p^:REM}>N;(27- P{{J(kU:l9_#qu_P&iJPKH"PKjH%android/support/annotation/Size.classN@gR!*_T9؃GO+x dIihxPlT=|ow6op% *U8#̘r-^UB'P~"c*dɞ6FZWN&jg~9b3F gn Ƽ#e)!aPc Cx]](4qf$!7\0: #) 4:Z'2GoҧBRw)mClSszQVMRZ/:LMYc3F*KzVo>Tū|;DyXf 9殊W b?{,/43jZXml.'3U(6-->%8Q(UEmNc'<} 4p PK *PKjH*android/support/annotation/StringRes.classMO1"u`Hɍ7ެB`<34F<D%m{<|?ýPL˙ch J u,}v* ZCڮz|*-'NNN~PKV%PKjH)android/support/annotation/IntRange.classQN@=#"&D]؅KWJ@ a 0!eJJ˅GwB$dǹN|~É# A$ :CCeXx64^Ɛi8ꉪl-TvgšǢOn SsWckXP"=1ALM(vp~ͣPXw1&|"zrڏ Ci{6%G5ǥZmmc{5u.y؏߷d"# C~b8[T%5 7S0(KQäBz!F"7mQ;/P6vt.@PK~d0IPKjH&android/support/annotation/IdRes.classP;O0>Jy бLJA rS*ĩԿG!(C$,|_W;p%n"̐ib9\`pV5e)rFjU2̈́Zp:֨ O 5!RGFd#L&[[0y1/EDCzЎos$QՋ_1Ό/S#y8ϾG*-Lݲs]Ơo,Vsw9`Ai@s-hR!{H| Gpl7h"@©PK PKjH+android/support/annotation/PluralsRes.classMO1" u`H8zD5p`Ґ%%]?8 6Iߙ<ә..=y8pP< Q9Vy*Tɬ[p&k9Kd$Xa31z߄7q#m1IKyTBԼ:6*wɄẸ`*ҥ%xFT} ϣ>Cm#N aRz<|hȟ€AEj囍Ich;+[a m30?ZAlC$8±s_Q8AII7PK4/%PKjH(android/support/annotation/BoolRes.classKO1ǧ "GS%nx0eҒ. _̓e#6I3#|{̃s.4cSd)B1*¼[**k\rV3 +MCk n˙-R U?(\ qຼ -q@[/msˀAU֠NGQ>Mhs_pm'GN:NPKI>$PKjH)android/support/annotation/DimenRes.classMk1Xu~z(cOTآ⥇ m"YC@T餔Ja3y&3;.=x8pP<@%WRG2F0\DLɵ6VXetp:O^7MV.HZ"#INcx z!L. "]JKmq>Ho]KOqgyDa0 B5Q/3 C0 HZY^y1v}G-U(l30hZAl}$8sQhIPK[)#PKjH)android/support/annotation/Nullable.classMO1߂q`JH6D/2!%,]?85Q箤VMee6w,JL:R:USַm7M`ׇZfߛPKa3PKjH,android/support/annotation/AnimatorRes.classN0ϥmhRбLJA rS*ĩWcx(ęv%|;龾?>8wKHWE!>Wr-.B,+ Jƹ,"C6HJeL+X']=!ezc6I3Gpgt4'J :#m1Kd1潕q\LrkmM_ OX#u[<0.gbDGHm#TAtAFd !L\LEDKmy>P֛nS$LƘǜAL X+<|xÀЋȅij2dލ+L,ҍveBwܩ@[kP'ۣGv &s_p-'NNNPK/3F*PKjH*android/support/annotation/LayoutRes.classN1"0x[0^bX2 * d d l\2%3^ͅC[b?=߹|}|ù  Ә2!F(:7e(WFh*39K+0R>t.tH?13KX"Hrۻ:҉.8a"=ǃx,T} &/Cm#nAd;Asvy>р?ãPT˹Nߺ14%ņp>[ %{N _jmzxP2 'NN~PK/$PKjH*android/support/annotation/StringDef.classQJAk5jQD^܃GOA# 9$iÄlk?J($9W< #mH/:f@yvB.W;aQdkP"}.rE׫PE}^6>^@ǫ?w dNXLMIX=>P8d|D ?\ yDlL$榉y$K-Xſ9l&li͎6iSPKlYo~~PK jHandroid/support/v4/PK jHandroid/support/v4/print/PK}jH2android/support/v4/print/PrintHelperKitkat$2.classY |nfw2 ` ,DMH0@J8,lvYk/{[zibɂFJųTkֶZ[{[~n$` }ﻏ;=`.ի8_p 7p psq&nc uTN[2|[+(اK߯zp-WqcC +C{rŽû( G:[rFp62KUiٖ6dh3 c#7R+߳OZlm6y~b1 =nۉTvYB:)m !\D c/ѻ%jqiDF2-3񿰈e`FrR">2iX gYW+aO/Jj0SNmrB8?(49h= YN=At $[2 '1BWq4P $?/VT!nsVٱ'U!:sDJɌ;s]_6&RZѿAlGI! aNY^&3GLVA+ZѢfvS{U0[Tw/9ޤ','@|'3xdYA+ VǗՎMc`HØGwB^^.3"VkDmÒ}c@=C>t :Mwb>nú|I){O '4b@c24̟K6a4kC49Y뛓'+rښf7=kLz\nhCy65]ܼ%-ۿ*Ib ]\=LYhl纝ER/hNoC9yb.="ጃM[~t`Z L>qTsxP }EKy&(-.ɞQg%{Vw-1ja3-PՅ@u 3 \]p]( .xOGgP={c$؀ }?.vLC^+⽁}Ƽnh,܏=K(-JcuP\{*\r%nn ̬cLA1 ㄼBY(槗MˡEM(UQ3jh Z4:,V6 ,bBP [ ^*Wʪ&lQ^mt.k3_r/8Mj|QY1AV,_ա=:T^*\V>AIWB0v`2]iWj2, %(|+-P lqĚ:e=/η^Lc+D4&C\] ]]F6[[1nC!>X7 Ǘv[ v>:ڍ)ЦݏSk-FE>7S^QtcZ-jz3_QL@UW }E>/1Y>w7iޏ4\Zx浤qתegS>_<>wu,7[ZW$O"̬-e!=64p_>_7Mzkb+EfUnek ٫>'^1#(;r&G{(>Oa?ϰTxeLE\c,$U|^ Tp V%obw9>q^aGcK=Kd*pGF"kn|f_ue.khܑ:լץJj/;RX89jF"Cfa3 %]} JN̆iKAQk"pZK±Pxt(7"!%8)1a5O/:Ra .29>6ONmmS`YPd,8N=l*d(sGUZ2FATUwr N5YȌm?Uŵm:93JV\Ci.sCn3RfZn3f4,]DCa/HHN3SAu)Jth<J͂`GTKns2Fg,m쌠sz;:gR(ebM#8Z:j. D3u`*aƒ3/w2ȼ! cTD-2?u׎k,XʶxpHѱHȶͪB]6P16Wc^Ȥ6p3ڋ 4sbc)UȌRƛX(jiBt'r"sfef ̈rфy6cNjNe,~U*тQ<-3&'rCP> kHkdrƍԙYu' G;Q5Ty&Eɀ٢S(ڃodB[wD:_E틏'صd֡ ꈁAoc_Ưx6pB68F3jBaFn1]U,;/҈s}C'T]4 gi ) -{XmAߤn+,(C*mK.t4K͊l`<;#ЈiNXI!J3^WfJtTzt ^x4k2KIjIܕ#`l&lȀܧˠ!c<(Ey+,9vFP 3币g!]Laoؐ Lb?uZrjlxf0E1͘h4a D.^U9)]f s畹|o-ʵJV5Uu>Ӫ!nquo;;O @|hGhbi^hF&S<gQL誎W GdPRLbؕ[CIbw"%L'ߵUjyUSo#SfsJ?zJMfc0~czmߋ Tǂm@QΏf3C=,>d ;Brtk.Y#ઙ>%H j<5K^B0p}^ڃXk _CIB, aD,6@C,*W)XKpAzVu3  Jx\Vx6IK S9 ?!؃R]Zwt_"h7?quuR$yNJ&gms'ŅbWr<,Tnse2eՀ}uDQlnägnBe ;/L#+P*+T*Q-qEG@qLj1"uxڜfsjٖ#C5~)e ɑNq8r[5@֊,y8hlz269YPXhzV;@_9цcZ| V֔5ZnHiӊ Oa| ure얯|yCuD9e%T ,Mm?Rbkp2-Ђ0D^iva񤎧hgk<QR>nKT)pF(}B| Ekte:*ʱẦU;Pl>pdS.>lht9u)\7|L~}XX`5ȇ)ir 6y^ISS99Hr}%lR(l{dc#`ׂ)d/\h2~__|o\  c__?/XW5|ẵn#}wQ"߃_~UCQ/?t&hלtܫ1ypLVZ ަ-;R}b<m >aBV |V[k82YC[=f android/support/v4/print/PrintHelper$PrintHelperStubImpl.class[OA]P+",(bM*eSnw-_41HH|gf7ZZФ3;93~ `a9 zLJ|XƐʊ=v5V ˰lϐmۺf[&ClM7uw!alnj[/ p"b|#0u}u. PQ45{PG\Q1wmKߕFnٮ$yH@QLM8-S*0&ʭ~rՈ\WܜPʟnMKnB&P CUʯbUWdC1+rɥJى#{|o-S7DZRulW:kUl0UCԾv= J75W~eg.9ה8t.!f3 y3*Y  xxi?iFR#ӾIjU+ hCWusN> ӽa@ӇNӌ#L_Cj(/i1'Z=]!"̇H6r70H0"44:Du)!AEc8s)3Ds,;A)) yQ>?]*c},@4MTLDB&: yG9 d催@{<%p cA:&ۓ?uL)?@~]gM 71- 1P\X`gO~9{wXJkaWiR@Mt +EN`7B]F,91web?PK BPK}jHFandroid/support/v4/print/PrintHelperKitkat$OnPrintFinishCallback.classM 0Z0 =PŅ>mFCZ\x%Y1`Gz!GB}8]~1XH%-*YKwnrW#"̹L.3f/EǮ3VTqUlbT3ÏVZ +nnd$':G P 5/(!,5DZ/PK7"PKjH@android/support/v4/print/PrintHelper$PrintHelperKitkatImpl.class[sDn@hkn:EqCicI n.G,i$_x`4vOf~J8ƱZgڳ:~{9kύ[4rxK4`gx,o*y7xS͒42I#ʸ 㢌U}=kxTZŐض- 4pejvs̆o=%i v؁Ae&G*; aOմV.huFrUG׬U3 JIt}L*z0FFƒ ΫCB^y% 0X*vwʒgRf-0a鴲b f\~bzY_b(T7˚jivS,'4w}5*=3M7#+Hӹ )ai1k q~cw,i8B~@/bhݡΐ>}E7\~D|]s6== 1Y3LJ Ge{tZ]o:[5<%{fLd335e'Xt6FP{A.A}W}\lT0 я^x=_T4zK'-nobhmj !DC8@m j$ǯ0q0y<޳x$Hp/xkص-/A v)m9xQ6rא L)1~8?P؏18N1'Սs'T,ҍsnLG83q8n[=qfq$w=lNM g8 :&!WȸMwrPK%(jUX[k}⣶xn$$w99Kp@)x=<)RÇyQc qKq3>`<>۟RyghS.|VA>󸋇݌~QH|YWU5#R7l7%a*V/#L!$(ZbۖNzR`zZ &,#n[yDh mZf^uh۴`Lw+A( KK(vXZӈ$K!gvö\%zI@݈VUk ˍl̂)׭ƘLKX%SimR%tZު5$I&i$k ԏ[Dvrp ګW^Xz|WºN _dewv_@fLYՓ=="n`3l=lÌq-z3҉JTKغU&Ytјo6ᛑa_/wqL1W3]uފPd&(E6D(*#z&6h 'T$'32l8DPMnlI2ݳުYdFLFOF,#a+m&*s 3شfَ`l )Z_D=m1aDhfܦo YG.Bgklq@W9KQ|M)lFqg-߻LK+רT*5c.^<]ZwX_aiIGS J4R3<RgE**PrMNl*UE>QOi=xNAyNR1a>̲L\TX:{$N/^P?V";%ކTR.9 ,py&r*#Ěgf*IQ ΙBdHiɦʼnmҺNܞiNFFcze*?.ۍ^ם M#U\b|yM<[fD^QaoHiׄPFi."e4`kOwMQ{_w; Cfvucy`Pq'E"zkck ALښegnP] P@g.w2˹Q:vU\ƌADxJq/Q`Dm*ɥ\c}SS͢}sDM0Mr用m%b jc)ݹ;epJRmhB*쳚B0&'p^/V[X֖qw㳯0?SS 6 ,rK |srhqtjcfQөP92pCHLG#\fG|^qkTTp>:3uLM3SI'<[%^JUtKsuݜ^^Eq]:I/JK8Eht8p 2W,(LRoc>ԓB4Ԑ~Nw@zz)^(J1;&Ƈh QQ5P*Y[&~?;Ĭ)9)"33hclpw̲beja͂k>3a֙:zHi12R"ik T[QہMQu"jFW3OnDnr!~[xkk9|%u A9H3%%!s7{YBoEBZ5!$G"zgϓx`9r?eWh50O,dzWq fiF0OI}F+ c;E {Ǎ&n,nL*%.lXkTpϜPKT_!PKjH@android/support/v4/print/PrintHelper$OnPrintFinishCallback.class 0Z7蠋trN {Z$Pb΍qBTj΄dz D{S\R hgB ~!b’5\]V`e/mV"$h&Bw-t9@k /PKEKPKjHAandroid/support/v4/print/PrintHelper$PrintHelperVersionImpl.classRN1Ȃ {Г$IPL,nB|6>ev5D{|ݙo/p'T<8 P|&rZG5Ѧ-kW9 !ܕpzpHUJQI9`#:):c6cT2О/FbH‘-.v8:q.n ~܈~ S#q0ċ,-yE1JK>7ړP4(-vc;/G4F]k~%P]<@ ʦAq21z &MВ /Ti^O['d3ZaIURlF<ÆHcYRʒ _1QJ*aWLRa jJ6)֌dvsES -{U$+eEGҳQ*ͬ1I=) w&]%חVԸlб;#}n">G">|*3 _+_︟AFُR*Μ3}B-DZV/*}2lĮ?U@.ռ M:TbZ2um?0x>bAQ4RۂlD(~^KyVfsxHl2fhvZC}N->Lino^RxYOuhϧHݟD͏G)xa:|U0TsAȻxLVu{D)񜨤=jspR2HLyfǩf6Kŀ7m=CqObE>& ]؋}Azk=T<0I k2p?;lƻD%݃'jk 0kKn@jiǮ F\69|ԍ$ci?+!h=?$<`>|ʇC>|IKؗ1AfEmk6åmK7 X֬M9RX\djJK%|pA,[7@`!뺡X̩#`*fhv=f`1i؎j8XѺ(o2bGH*[QP\L9d,HcJ"_M6(V*g]W}v\6-'h>\Ʉk"vRl ; ވO놖rVsEϖZv=KT8gDN4XD8ayQ-^@ (b,嫈 #m95'E!-w"iH.cUY4ЗϹ}*HSlա2UpV-2;%0;?Tj)TZNK-y;\\B-iM9x- yv]- ܛ`Q<9]K!}zI-h:סּ& q^+% N/ZʬXyUgǖp$Q30dL`yu02&ya]Ĕ.ޓqwd,2]>[}>( cġVrZNxG /puZ66 èHH7|knVZI8٤cUыch2E9,% D{ΆI5 *7_g78o4exfV߁AI1McA/Rc7i'gj]+AƧ]_cpgxzE?-=v^Z>2i'?D/N -*la@#a^"e!O=WOi-PMjaX5c(bM~z}zy\r?m(( RZnGIn4ܒJ[J}͔n6w)mG)Li%Usjm}a&;A%{[]HQNkQ5>@ IZjMav^&dEs/I"iΦE,ylaz-wphB/ӝ5+HO/pkq"|PKn PK}jH6android/support/v4/print/PrintHelperKitkat$2$1$1.classS]OA=Cv Bh@3ݝuٝ ~?x,ĸ̙;wν3_n.\,q'၇U ˬ +DOx82xSnT*H"}-n3|.4/6ECu ؝|-&d'4y$:njK9y۽z p,*}ŵwPcC,/qsnNo6iy}sȭPKtPKjH,android/support/v4/print/PrintHelper$1.classA 0E= zQt! $$ix(1ōK33|~<1)Az5+^*A] nYvPK]DbBtF7Xa@"p[Ž>Lq]E e[(D`wai!FPK>z`کPKjHBandroid/support/v4/print/PrintHelper$PrintHelperKitkatImpl$1.classTMo@}q I EJkh> QQh7l-{ #QY'Rr-ygvg潙^SVp fUTn`Àm!5 p@aHB!EoMMy($~°&( c原tw~CYEb?6Es:p)Fk="U yԹuPj]8vt<z?=,0a?G)tMyb?>;cN)^&B>v-8ضpu K,,a&t5yr#rM\ܣY@'@mjF!Elz1K܉w!s5z34 $ +B֫}d>svQp%uF~aV+kcN4yNRL+ Qb!)oz.P)JR۸C+*iW \MbmPQ}PK)PK jHandroid/support/v4/net/PKjH1android/support/v4/net/TrafficStatsCompat$1.classA 0EhZs qm)1)I\x%F\q{ c1Br0F*A-?evܻVJ~Zg8\ȜdZxk Z R9۟Jyoe:  @Z#Cv%>vPKAPKjHTandroid/support/v4/net/ConnectivityManagerCompat$ConnectivityManagerCompatImpl.class?kA3+;SY$+*t6 ^WB"v3fp0B;F3ʫ?ٳIY2ًh͕PrvNHޮxr&joj.ĤH893˜_pbx@[ d5c⑉@X / k~KMeeTvp>=~-:jk{d닺tdWI]{=dbғI&k0<(mt r$n^bPוVg D}3WW`JQ/*'BrIXo(-[~[os<_ 4?THxw.}Y ]U#eMEWz{Ma(Ywcq`N\-;wӜj OlI{?scU;NHx}ۅTBk04=<2p汎=]c"겆 i k^e9r1}+.Ą(y ϰHMUh?7cqiBhX`RT4)r~Mw6_\4]'c L3$"6i3w Ynl(rdnٴ)t=owQ6(` ^yO SU YaBVK݅1tnas%asװ̌U6]KiRs_yqۆX;SR>ԧ Q$g˂C{Jruac]dZG R Sr񿛴czƏRD_n9lbڐzTEFC8"^i8.G) &c4Uaeb[x(tZjk{vf{(''F7?u72([l6z pi/01Z$ (5J6&%^<$"0+hy)|xM78B#Qy&J~*=4dVɿdU(=9twe^VO_VV,ة2XR߶ b 5INYeD5#%U=4 Oog@r X[6#*Ҥ&[ A37MB7PKY%8:PKyjH?android/support/v4/net/ConnectivityManagerCompatJellyBean.classQAKAۖee֥ FPBP;32K~T4EPBy|= Ƞb+V c!9!˕!SS!'Bu"*`Qid܈!Zg2Jp4(m/kJJ1f`u E3dPXÛ)}kv_}hV O؞*%Wj~.bӽؘyA%ѿ'%,ɮY5asd-ml!m})f@ل,!g!99*~DGƷ- ;PK~ײ>PPKojHAandroid/support/v4/net/ConnectivityManagerCompatGingerbread.classSMo@};1iJi)-4i*NK\T@$PZm/鶉m9%\ā+B)R$Vۙy럿} 6Xq66p ̞'%`Oʗ~[FGcR3ޱT:M}ZS^( 0;zReG-ዮA?nGRxUŠQ@At֒ZF#K2_':}BZ(am:-P !3aPYK [ y} |bο{t*F" , ,͉(bPrqf'}x ܕz&K_ed`|W s[.kb.}I7@,ov[<?6X._ լ@W.`6>%\LVpgcs̖TJ2os"w/Xh] <%,\1QLu5x7PK6x~PKjHJandroid/support/v4/net/TrafficStatsCompat$BaseTrafficStatsCompatImpl.classOAƿٖ.-厂TԶ jt*14$>Pfw_ h?xfw^>v|3ΙK؊!*Š`>A,ĐsQDBŲ ӯ-?>KwYsGի q0r6C,}NWhZ*3rԂYC@iX7ʖv^7-'{548\On> b҇ Y& ɂnڑJQ!t9aĎ[~ݤJ6#S7J ٯ ;i͆ᐳ&ImqFR+nwңxubOy!b99bH0Dj3VIdS/H@cR%m=$U~' ̪daGD۹>uӶ&c*^ с AåtaE~ѣw ]l FIl#Lha#`P> t| RpS4{p /I+\ ?Q.VZ *fpGӯM)⪹J"]⮧yw'AP7Į& 0ʿR0>aJ]$㉋H]_>CGrZ#𳁛"PK3`PKjHLandroid/support/v4/net/TrafficStatsCompat$BaseTrafficStatsCompatImpl$1.classT]o0=W2m Hm蘨TVxtuI*q&G! $&%QcsvD -xd`B-B <1Pe(LM6^Db~z**iE)W4`a(VD$ Gq$}7MQܳ]7ʽ_7/Cڵl(>Ca#Cq< DダNcssPݦ}z[Rk3ҰARd#Wuϸp S]9 xaAo CWdك|>uOC'J0H0r%pj5k0.0.ٱ Ǔ%HI}h-~2/bCLT7i&E*^ܝ> W\͍N]6.\OzNwxg X)iVZv9GUMRP}0wc5b?OIYaUS:iex~Mas,Pn6G*icQF2f1a~&O?ڹd"51I rl ˿篟U#;ATJHQ@ 0RLCc] 2?Rq0'<*e_4c,تȌ%PKXPKjH_android/support/v4/net/ConnectivityManagerCompat$GingerbreadConnectivityManagerCompatImpl.classRMK[A=7_/MӚF#~@OЍD X1]ڮ&yC2/̛DOn\U\Q}/!Yt1s{Μ;t9C k;#!s'duMHOJhБ)6¨h=M /J)zZ5V\hz0  }BeSEJVj @yn8c񡫥ugnSWӨ,[^\ZiGT0$$\*!-PKR&PKvjHBandroid/support/v4/net/ConnectivityManagerCompatHoneycombMR2.classS[kANvӭiZ[kM". 11 i$I>6ٌNL"K_**xv g9߹|gf S'Z8x`0SR2eɞnIbM16ep,,մ':'"Pyjͩ5Zng/[Ѿ/=ʌmTt'́f"aE_0ِ@ߩW̾#ļ.aF(7v:-ZeW1S*a.z]iBHOV!?{|&"8gJb{ SVaFؼa<4Wae^G#TE6?pX4sYx3SBMN;iWFZ;8ǨTf|R?05Ǽs;#G8 G"0= ;6ʸet /1`%j/PKңPKxjH2android/support/v4/net/TrafficStatsCompatIcs.classN@%NB (ЄPJKh{Ro%&vdoPCU]hLT2O82abKRVBM*pG=!=yʐ F# 狳+( \>ࡧ'IC^yC^x='FA(/ %Q#l ê;<\t žj2lxH\zƾ$vS^ RyF]YCz*,cn]}ǹ՜zA#ul:hqOUi,iaVV`YXŚXWቅmba5ò6=~9^ lmt O],ӏZwo"ŜNDъXFւzɤycL0z5T6izKs:N4Dy|k4qo* ljc4pJJ͕dӄ*4a=V jXtWa&FRb8) i!x֠L|\PK?~~+oPKjH6android/support/v4/net/ConnectivityManagerCompat.classV]oE=$^{I6MBm@MMm'&%E!l5M OPZ)TBaxKh9n \ZUS{jk{EmFSV#[-\W|ۥ+Yz1'[H g{ ZzAu߳fk]0]]ç$^8anӪo=QQWuA.XYޫ+N: ^M:ҷq5'=No]~^#{^@һ L+KR0i܎|Nvzm LA3RdQQ6EA}xoWF=+ ¯n:6ת$Po^-]X%lT3y~vAXbB'&t }JGX'G i"XXK¬ QI<#{uŌ ,r`OM3燆aqQc&$&)8f,k䲟PK@ȆuPKjHIandroid/support/v4/net/TrafficStatsCompat$IcsTrafficStatsCompatImpl.classSKo@6/'}-$m郗 )*"RD .gulc;? '"~bvk4R<3;37]'=#rlJ%ŶJaHXî= c CTr~%&om&OosLVX7;<yܞzϳf zNw=CbpDh G0̙~Me0 E1}Nx ܾ |ҍ &C (rjK߁g(>3f~1'2ܢ׌c%]^FU1ᙎۘQ@QXֱ*5tl9C?ôN8j }3>(¨BluWkc,P;%臢K+KNdwod$0E2MLd̓fr":u{ը8_c?݈a_uK9AZpHTYzT;IK\tW2+%-ɚ {3qsjJ uC5Gj mH p1&i!Hϒ,=^j|BOəOqG pgdvw5i{dwl[p@h~!? pP/E!Ke0cn T|^t`r<Ԟ3Pji*3S!Øuu;oVe! 1-c})?h8OY=YwLYĘ;l2iKNw r cJ/fl_)@3b>)۹KH puPjF0] E ܉鸋q)4 ˨M-3x)wr%LsǠGQrU?$A#}<3|Us3(<'aS5d1zo(fOΓQ4D*FtbVZќ!YgD}ZV#L(+A]"ch,CO2YO1O3o0GwrM͛q aBv0z-A/'do$!*[foTq$Kf*eP?ɻ {==AٰJR%+(91OHTxKi<x #7GѼH@0gsE*:&-ۄ3[??~Έ" åXBZ NiRKhWzNLKFq ~Gqf (|k :/)Wů Y8Ģ0O@I|rtr{nafˁ-}Twf}Sڟ_hql=/;CC'9::qyY7k̵G`X9wt9^IW~5T*E*2bLk+6mꒈH5I !7oak!"팈$fM_- {tPG(N+T/*UԪf*l?e}e{=Izq|k>_F.ZĎ;k5&#y؊2`3Yƚ Yr/2 8&[Ulr:A3V,T۰L]u֫C݀aqv5Nܣ- k|?qb/C+&›[$rY~݌yc)!Ç"PKGʯ PKjHTandroid/support/v4/content/ContentResolverCompat$ContentResolverCompatImplBase.classTMo1}צKJ[(m$mB[5 'gפ[6vn' (&CBrg<~37/_Ǟ46L2p@@!Г^!Y,3j O^% ɒo(\{zhL'^ZS22<BTz?ru) ?lpjvv'ڜʐ~=÷(_VG)Bm˶ 'qLIT`׸tS%!o Ӂ%Ge8 [O{ Kc̮V‰2,O`07"? 0?< r ^ڍNwƈ1g/Eoywd6UO;-V&^nt d lJ5`3_lk1,)A+&ó`M+ZJzV{eX;uu޿HF}g t#&H˟>&͙ظK4 $J4t@,ɂe|G2ΐީX)+meΐH[8efKBk;{4p00ǘs܌ [BS; C m%CsF7re0ք{ʀB:ӖHy(mؔ*mKY,Yi-x9#̼fK%K9渖h@}G/Rm1|KTKU LhzYB3YVTDF~UYfNpt\ 0Rɪi^8"+lʶ¨ϿɒKgaWCﱕ}ZS2Cusa|cho3h̨:P+*f0p#rDG;)Sw9TɪUV9dK7("h$(>Lm f4:4F])IZI[~ Jmz*7K4c:Z`8@'`H^ɺiRNi{"9CV-VV ";8j9j@ER|w$SVa3q?u$i3ڃ>xM<1ɡ&=x ;#"QN8F  Nandroid/support/v4/content/IntentCompat$IntentCompatImpl.classPJ0=qs|/A@Ba*XڅMJ=~v brr9''_O78 q0i)HRєh9VN(':/gEt՚5D \"2:mtA%Ԝ'H5ڝ?*WaȾ]22x`8' B05vU8^0.v8/2G VJIF ols/|p2t<#1j?<=Ç PK^PKjH2android/support/v4/content/ModernAsyncTask$1.classSmOA~J`-/5HҤh" ^= JmI(Q%;<3VFa\# E %YP6眹7҂m dacl{3f#\/^:l[MԢ%T6| R!6CK6n B A+}G `pjR (,ֹloQJG6L)Xz<0L4{o2i-}, V)SM2ca6[!:v!14H/#_>V\Coh V0r"hG+:0zԌ'x` Fz9*9"Gm17tʗC/Ldy#$pniv.P%tU6kS-LR&?r:L!*koE(m>XaS*"@Т& I! -'0ω8vbF I pacwN5hh N*.A +]8=\{6VI<֧t~S}B@C@Ɓ dzQ$"KB?hp5Ё80 ;m T,Tq-ZZ(WDvwI-B0l4lRxgcXF2Ԥ}/b)<9v=ea ;It/E`]'a/yG bL8WWcҚ흗)agM'04κ3 ۓ Jvߗqe( Vez9 ZB" ɘɚ"!I O`id$/"9\$)Y/(2o"ZM1#qn%\b)k'Nb'וacLq#M/cd3 DQ"J8XÂ9kGL3X5PKO;)jPKyjH3android/support/v4/content/IntentCompatIcsMr1.classQNB1+(w.ѕCbB/ВB_JI{NsI_ߞ_c7SAGG;RAR$j m!lɷU=/!m39J h8N6t~B% T)3 :Ayny c#4*3:frHF 9"Oɟ:w&SIg} j2ÇJ[TWt/еIc rilkr+e?O=o۪8=ӋL/HaƤ,>lΫ?!vU d=lD"~PKwPPPKjHEandroid/support/v4/content/SharedPreferencesCompat$EditorCompat.classU[OA^/hR(bA$P 4b!dvfwKWA Q_MQmhҝswΜWØGW0!]G 5DʐFc_ 11C|bI-Xp&<1սt;B1'Y(CncP* nU\ۨ^ql׷zٶ|aziW4ݫSIkA ϟ-UG"S2g t*C8oWnab.n%`]C c4D-%_N`36}>{GouП>>3.1koTE6ˑ xW2t3/*lgaAbt)7AORR!QWS:I!L)E2C"JjT ;u#\ad)n`=LtoҿM?bD64(L!kE7C]ۃͤR.QX)=#H`2 %&I<,HgĚs<3I)!YN!rio` Ql:)VwM%Bf*Q w#*QͿPKU%PKjH@android/support/v4/content/ModernAsyncTask$InternalHandler.classTOA+\9_A CD@ZA1j)^swO&Dg}Dc`G!zn?y-30KNr'`L5w"KWbBGF +hAEĊ_ =yW6x#O*;+Uvݙ6ݐd$ɽ\=0kR`a(( VAd؎OY2 t6x=vXk3^D YGЮV}"y={RDD"qgq_ ƺ_ qU}8Qbrr&lL0qSǴIqaɘŜy1XRkU:#g1gUQ>m aԲA[08;_RgP؏J[Qmс}il}iaY#;qbXZ)XNuחB'7|Z-H5Nꫛ6 DnfcHCۛ8A3tdqi5^B:#c0י:յ}$_~N蠴How:?0~b`NNY#xoX:\\D?ԟ]d0PK@,PKjHFandroid/support/v4/content/LocalBroadcastManager$BroadcastRecord.classS]OA=Ӗ.K[|"H (VMtwRfv ?b?e3={vsf\^Xrc/fYp+TP)ìUczAl)iCI@Ȯ Ô»RyZs_&vϡl+fZ8xP9=B5!TJ݈' d6Ӊuu? q0I+zn-rRܔJ[ k+EaK%fg-VDLݶ?Z[:p3z4(s5~]'睾dcfb8Ӂ,ߛ.XrQb%o>=tJ 5-}k+&ڨ:X5F- Jfd !j:7L7Xrw"*:u> $fw O0 X4Cz⳴3S!gag=DŽ#s{7PK3PKsjH6android/support/v4/content/IntentCompatHoneycomb.class[KA㥭mE͂zڨ^B3(}vjԝQ[SCͮ 9sgn_Nc"M[(C@69\]$+6A.; 5u:ސ.O0x@pܠKޱ` }eNmW ńo"Sހk)ؕS#yn)#g_)rI=V.OԵHDP ߳@Q_M z.˱?@2&EZvG2W⏉}+-em /I`%Jz& X\Ep>Nû1IQ7PKm$SLXPKjH-android/support/v4/content/CursorLoader.classWw\Ud2ofҤi4m$JHYiM 2yM_;4;J]PJ)" *-JjAPAQAPA<ef2 =cN]o_<p LܠjpC χ|Q%,í2 6'eOVwSB8 _j_B_rp#{u|3_A%CmY}GV ȁŨl8XGrq2?IOiovL{iks~ƎX/Ι\XNv_28X:agDg;ݱ<w/Ei/FlXX.o63"D,sh_v2U˦Km- 3 f.JSٙmfU`&pl+mVMSQ9uGRGiNdJVBjlIDb684ct(sb]#2>I1gz&M34̩Uh;qKǔS4^/MSOk& ,JULšQXS)1fMbY]TV ,\o%զlY_D&1=yY~S$G*[<)m&[lf12yPtJ7ĜXd"8KuiiX& 4VϚ1ZI:Nxg񜁕Xe8@/5'8WO ?3<^09~aniE\j`H_+3p7kY]^5 VFh^npʒ=F4I\i5f$ƕ:^7QFo`%ROwrٔJ 2oXhh%r `Ca<Ф\͒VmjgF.8r[P01B}OmmU؂'sz}-FrTˤ1r*oڻ47LE4Xlq %0|-t1?f{<"ϣ95~e4Y P!T\Xb#jF+EZHROCD} rB GFƳ9mh:}h:@E/Hn{N#FHp - M)H,IWjYjCFiRLtU fq3f-|(<f(#NKmuo6S7{.jMeuQf`;GPHegMҝET܀nܨdRe'̃Jdje2'|j:&OB X$LzÏҧXdN Xdܻ죞ꤎ/ɯGJLf¸%"jr#% W$ rAVĆSIP O3U`9+R_.s H} U\;+Z(Ŋl\x*l,ъi(Պ|Eyw^"Ȱg(Vu!nĒv7"DN@(tc{Ј/<1 ۃ6Aங1^S gx^ #O1,?0 `eU |Hh_PKSQ IPKjH2android/support/v4/content/ModernAsyncTask$2.classUNAVD<[-R( P Fifw}$M>e債ɕ4v@)ىy>`i<3,h,JޥQ7v_;s !kk+ ҡ%?ݷoDo#k7Uq 54TIDB<4!^A}mND*x4JVB0 fjV@CzDq6e4$Q#>xW}1DbGђ 覅xX5p0$Xٌ.QɨȋI"XGҬ+&Mc5"<f xS¨#V}t<] D1mf`sPK)yPKjH@android/support/v4/content/ModernAsyncTask$AsyncTaskResult.classRMOQ=2XDK18 h ĄJ V43oH\ʊpߴXYXؼ{ιwg.~ a6 s昷h%!wJKn]7Nz0JKOaWFj#>Sa-r: :tw:6*H샶rw+_7 jf`m2PlJ~NN:2jNr+D/"<>cf8 4VJFcɄɖ֍ML.wM4l4[nbw}^DCòvyK_PALbL4F%|WN8wYwP}9߸E2 ~%K1L`HSU0)U}ֳ20ӬG1p-ePKZ2PKjHLandroid/support/v4/content/SharedPreferencesCompat$EditorCompat$Helper.classPMKBA=Io6 [ԃj"B0Ih?w'c<֢Џ4"*;s9/O%(P%dUByul(C'A6!M?$G8K 0~'Ax< ߔ|C.s)yP,5,a*X._PK촔,kPK jHandroid/support/v4/content/res/PKyjH:android/support/v4/content/res/ResourcesCompatIcsMr1.classRMo1},Yд48=`!*.E\wgJ]v +C?Ezf<{+lhI6FX!4h[BuHhm Cmdg90<Jl# \iQXȬ S)߷' i߻ {N~))uFxכ)ΕMӭcdq3/k%q#'Թ9BMJ Q~sxkָT|uH/Lp t ; aiXe n ;5ڄ9X\"/YUίj`79D~TwymV`-^+b>}s;k?QG{j^Q TGX欆7yJ8:ZPKPy%PKjH4android/support/v4/content/res/TypedArrayUtils.classTSW. YSU!i"hfʃ}$XM77T>/:ワa1̹w=WZy|U׸a͂57Ykupmܲ}")W6kaS Z=mhCnÆ 6e3kvuQ7v #/xPqR(??h/ vZ`Z՞V )pCþn l ؒ_kmm ̦ :@5/'m=U~gy?H)UC#jZ9RL9`E}lYHUA߉dkof;D"`a#u]ʑRM4U`9a'jZS`Wwr< &O̺|#8,w$oNev깉 [Xk2:J7{`\yy颀8S2s1m,V]\=D7~z;yMpD|8[tFX&ȎK8o9V@ZOWu/n0BM0"v=[N- cteHxr} |mW+#"ayȗ/j0,]gnˣc[: m"}YWQ΍R#P6êwoU!{ -<",_9 FTOq|CF3.Sar䰊*a?b{ۧ5[tF'{7:pD UA98)ѓGB5%LW~lW57tky,W|gUOϑ~-.n=,GbKcW9jLRL}F̈kX$I`=="?PKB PKjH9android/support/v4/content/res/ResourcesCompatApi23.class]o0_ca%c[1Zm5U6Q)v&QNQ\)LbJ@D{߾ ^ U<:x`aTҼb(w ò/8N"9"u_<:扴?s*S>Wae8։Ο{VF(%"މTO@C=ygÍa:҉mѩ)%aTM犞ogAwD%&$CF U3N%j{*uðCÍejzrU/fwteKۮF^sb'.qDMeX8/{;>UUo@}=U 6R⿋_T[hB7>ezA<,#[}HN Qѝ p<VIefqNocJ7L:ֲƝl]ܣu+iPn5x%X!['QXPK`PKjH4android/support/v4/content/res/ResourcesCompat.classSFǿg'mLc@cRmo@,y$>_ha?T{&vٽ=Y/q:>>֑Ǣ\DJKR5cϿێP`u`X+Ã@ OE|ϮU ̲_A(Վv50kf#+x5bHmWmoJ ÅWmy^) ,1l8=3ùJȫOyP_*tv]Xd^ȕ(A]Yj:N;LGu L_w+=l?N˪.#=+ [=:G9u>pk|#E;_19ju< /ڮ. lѩ wmj'^\:+ja͖}G?q Xs8ᡜ!a]% u9<2 9|n&n1̞ !)ww̍'JKߪk^ӭuO15 e#/05mZ-WuTYy*m2̟-}ga/-5 Z8[|ps> (<-%hٷۂ9ҿ !.1\t?ߠ7ϥDtQ8"XE{ ].cLIW"HY$H57I&ro".LGc4OGs[

k$Sb$䐊0 :/+NIjYO#?2gOy {'y-}pE%I9{wo~U Fd,8GcqΟyp PK&m& PKjH>android/support/v4/content/Loader$OnLoadCanceledListener.classOj@}61ZOX C{(F(B6$vewQō"v.o̼y3gCtB=R-TTE.lhEdZ9VNԳlB(VJ0rLx6Jr+E)J?לdD2w=s!nM聾M6s8-l G.BuPD+<;e!Gp1F PK!PKjH9android/support/v4/content/AsyncTaskLoader$LoadTask.classUmSU~n^X64U|)ذPR (4P0P_Xf6B+ԏ0#tFgM9% $vJG{{s?`z }<~)GYǂ/%?,oW_< l:o\VྂUnΑp~Ggj]4jkI/uj9{2{۞-ĺ@mW&4]63Z^MdTkzqM%[ެ@&}#%H1-{M]7d;^.ρ1E)-7[֫U<2Hr9grӳ-(- oR[Ɣu)Hcs :B־NjX\ݳեa-s~߰*l6UtWߥ*]lc!&U>)Q joIݫTTs\ve:V ѷٜ}掶פPSs kf 8Q.)py*.!<8oU<ĺ QÁdHO mJ DsJ=О#sD=$O4]bŻR<{J)0?l 7lRX _I,H+~&)`Su7/0V, |B}߄{^ N\! k EhJfX.E~6%n433{3Dnu!ȺfޫLnK$-""49RLv%FDuZ ~ ?}Y ב^}$PK+"|I PKsjH7android/support/v4/content/ContextCompatHoneycomb.classR]KA=feafeDdaDCKOP#(ꥠ~@?*Ff̽s3^m$9--0e u!/u 3 ^ZT5[E u# u4P6v #;^((w-N:k uDpƽq$#T.}+[[~:'T_ o% pDAӹP  ؊җ`&:E؉<1l3ncEYlȣȰ2L4t BCe)OY{~c~ zw0dgG˯`tH"'k' 'gh> L g1ۅ(;/Wb:dti}a.0z4APK2C0pPKjH6android/support/v4/content/ContentResolverCompat.classU[OAv)RZ \Mf²[g$"PKXPKjH7android/support/v4/content/ParallelExecutorCompat.classRQOA-T@E *kCq,Ćh$HesKB0l&þ4яemEp'Fc*T}Mȭw +LǘƄPÁ'6;~ieVp$,WRjm2-4m8x5\`Յi''o: DŽs&{o0tkvo[WJj$XrN{]NxvE-ְ< Oظ"hev|Vm{^C|rs$Tz&PL?xvzgg80qzi,%XT0V䲛G2Xɍ-dg̠odrYX^s}PKRPKjHBandroid/support/v4/content/IntentCompat$IntentCompatImplBase.classT[OA-m) KT (P$Fb&\㰝ԅnOH4gv{V4Μ9s|+UbP((1C6e9bJp G$ m`:s*Z5/릨^4N}O $˖ƍcnrRw0Q2]aEn4^rvi hpdSfͶ\4kfyj/ x ]/u=fFnIR7D!ӷܨk0(RGM<\.KܬB憈ǜL˔Ms2u&iԾp\nxsN=jT؁uakbOM!/!058BF/T{q+t]S/fZ+|yZ|j_3F`NKLO$U=U4E-݁]m;C, O 7lmlyK е-X$3 j86SH.éx\ӓuX,*RڴGc!RsPd@'tqGHr:bgSwT1p^[`Yn}Z8EPy#[.Գ;mX8is#H.Or@ScDjd$O֍f$v0Tg9%Z*HZ r(k=9unRpbM )O'䶩숺v&ɼaߐ!: <Ɔidl8k6)\5U򀖴Y%%2r8]ڶuwi!)C1l%5 -9ŵ'fƚ|ca5UGֈCSD$ ԝ͖a3Ҥ|24ȦK$ypwG,ifq;wIC뢸A<9Y]I}w%gN C&%gDP {)xұ{#v޹h K)zoG̨)YĞmi#*/Ddr}! 9|8zXFiőcF!LN, +˾<_87d<5BR?Q3VH2B%Q¿%NK>=V6OS"Z/ݨi""I۱$`-(6'  NҎinwшvda䥖z&ŝ?S{g?ȗnA.#]Hb0͘xbT ize}@/CU\3h;/Y#׾i~0[3 W3COS8jUAYci[2cc]amCpp+ا`/ RVJ}I*xh؁^G vYy"8!o`t)؉|Q)eE,gQ " E$$Ex6W!. 3G1jnPp;>Y+Bs(gXV Q.՚]dVRc*+V%IƪVITQ>_7 jT3'wi&Kɬ~Z"J󗓲bD[p'kN>DGE>[D05c>X p.u1GptE,M|\AzrYC̻ej/Qc-CvFwPn6!Ivԛ8褮@Gkc@]~2l1dlZhԮb8MY6K]I5_h-~*u Mq\6Ç+}WFA櫶V[JV.x\;X?B 胃YE֯mp'c}k6<\}3WQzqyT칊\c :*Q lh\ <<0 xG}ȊPJ"2Br2 v Jsvc^CD;^ Ӟ @O{L6I`F ]ic(> sHג>ښw^߃ ? W`-!\7'$H$HΕQChhd.'6XZx(ƀnuzbu?iUG- ~Ob!.:eeQT *6uk(K"/#Xw?} 7%QQ{6\f[ȕ:$\jKԒp1! $R[¥T{f;/2lKpc/r;@Cwg~ZLa-,܊X;` +SiaFXIM2~|`&WN s>h|֦]Q2+DІ+U W᝕JXZ㡈Y0Xcsi?/slg8lmR s͜uCeY1.6Jk zaz e;Y(NGU6vmdHGoMa-Oooˀc֎l}3Y뵃nG=ěsΠ+֎DM+0ץUX'Au_I8!~#}\5Qaakو?+b&I$ Q/nD 6lG&*rX9l+6J0Xeg * ]F"/DAkiAǑgҊߐ2rqxń#d|R)(` p"ې˯E9B(ԚՀjA`@<I<%)3+l+$ [ +1B,3$̺,5(S zz /Ց2" xDz%Y,ϡB<rJҪEtTO7\%t!Y6sT)AfbT1(ÍbEF)Vyxguw|GŇvNAN;zjVª#h'O`!^=Sg1pβ=Π(=`XAq#|M\OwRg=ҙu<}_s^G\%ֳ=PKǷ-!  PKjH.android/support/v4/content/ContextCompat.classWix~?[v888 lG C@>؊k m7&ZP#ZPpZzһZ(-=S:+ɲ,Nofwf's*/oZ\/މwz/n%ǃz>/ޏ%ȇ>Ň>M׷xp{1|?'q;)}gw. =ۋX3,?aC,9}_<;8/yWypǣ GJwz*cJ0#ejH.COYn **;<+;#$ t{g@96{{yWkk{k&2opuäWHz4;XBeF\I *̦ڣ>lp0N& m Ru+~M*a$[ژfi*u&l-sJnNtJwNݜܾ v2F.rޏf02j)}}J2Rjmź5;7NbhFpfu(9XE7t ڞjJwSswM:Syߣ:ci-1ܯX{g,PRGF QLYD^&T^hpŔd$ Di)2vĘo6* 9(sXLwd|Ox-^' 1%|O:X@CNɸcg0~"C^?$<Ëg%<'#Q~\X9 1H#J"dr쏫v#LgNj)~)E~ Ƙ ;O%B7v`hFI8)x2_+2W7~BvrQ9|M, |횦T:6M2?3;/xZ_e Ϸ͈^5nIU4 D#Ljv%T{޶+Ar/V5x:7Fc֚ڦY($)Нfѐ׍a*#|)89N!ϺsA)=IEu>_"Mcpaa78]ȢH%ׯ*(}O͛W(0 )ZRRu#ϷO؊]~24+﷫^U9 DQKN+h_h[OQu`E`[OS63uԈU8KY[rܾL1豌lO[Z"kЖr0y +\yt|ŢU ޘ"Zo[/yk/[oO. (h, >m衧fl y@ )*K#{֋>zϊ"E7"+CƪXhmujxS(s*eS#}oFa/lGqymG?s`Fmx%2O ~)ocCUxSpO404)x|| CeI),s=ҶDr2Iw{%E` 'Rzarf$CEgQ)G8}le(_tHje6M,6ϐgs)/P^/l6e6,U\xVe*U%*uE.4G22#4fb%DKuzrk#jr4&lgS^|U4>M@x CH]gՔո3w=}KSuqTNo]W&T4?JD V68.km[RnсC!I1q1@ G3%U6o WIM%8OA8%7*Ej[> A`5 SX:AkD4#br$D)e3hr䲨 €E5yYDU?Cq Y KeS2N)'6x(c| 2>JuҔ<HRY,3O`Q6k:s!X9Uh^VUdjTkz}r=LFm)Q'| X4qo*Cz\=dIpZߏ3|hB`: K8 Mfr-tnbq{:.&#Wb3=g ݲ-,":Ʃ'ưڊÚ XH\Jr`#f d̵I!b knEWP˞ba9I5/bv A*dP+z(sfzGm%-vc+JN]/1PKx [ PKjH-android/support/v4/content/IntentCompat.classVRF 8$@[SI MDUdZ h[Cl#L{׻>Gzۦ43}CNgm͐={s=+o zcx('by*)ulrq݌#!VyIMۗFJYW^ϐTlt: Qٱ=~[f_0-v!f!dy0J-m U(lF[W`?j贏He*g蒶mP6)!)՜TlI=GV-B Zvj&T-,)Ty]KmanQߵ-TTnRշ,;GI;6튼aA .8u-HTN|ͬUqO̘҃׃t؎@ydJ/t\Z!CtZo"rd^0zXvr%K͈LHc>0D4I<JI1Gu{bW/,^;N?i jËfXjf@A3o}Jfwyd֌&] ~`uˣ:׶evgu_4N/޲굹JQ~t+Ovƶu*s9d.Q̈{HiL*=5Ӵ"4? a?KZ&я0o04$Yr^xK`r8Q@.ylO!X$Kkse$ك|  DK @@La΋`ypHH'݊FXzHAdb'\I%QD@4 bĈ `kR++j3jDRD B`MMJ*RD<㔬%D4AteWFS/PKjzPKjH@android/support/v4/content/Loader$ForceLoadContentObserver.classSkkA=Iv۪i|6I LH*Z&٩Yg&(nXF2{_, n 7Xv[6VmT ɑ+wV;\}%**;"j)LZ mTbg+= )=Hx9 -aig/"bpQ;q,ٜ.'2tZXt޳Fk/@]DOV$;W0jq ^=0rbŽs ήP-iDe7!wQ,mq,SsCpfu]Tİ<.x»<C&G1^ m#Ke pnצ3fg%/Z u)zEEv~3X#1rI ؏PKkUPKjH6android/support/v4/content/LocalBroadcastManager.classY `Tՙy޹y d(< / @MR%k>mnVm vnm궶dsdE͐s=kȳǿr8<}[KoX0>ox҂ kk##ݑDW}[&Ot-5%L$ Vrؑ2A9L%QgM5&92֖H/]H/EI=O,}m5ecSx:3n~\ΐd?1[`SKJ&SKrBHwNH"K>2KcNJZ|yHq9m5/_tͥ[Wk]so\ض^Ny(㭥XJ09'8ϮBe6U7^%޵dPˌh<Vd'ٖ4־XjC;.(6ERq]4#PԖD{;D:!.uv&'-UebyRUWy$GӕgW ؑh4N^`'2uIˣd%R=pNK7_!a˸t fV!n[<ⵑFF  uhq# 1566a3+Ż6B[ Ġp1mtW ŒB5m)b[JԖ22I&LQLe?Uq3nQ$S튾ܖL,nK̰eZ ٶ#~S0u|wwt{;B*C̒t!PrKȉZ;ǖRn<[*)B݄$-*w(H8dćlPȹt(Id">;Cmia <;.XÐZ[d2ͦĶDDh$8t;CdơW^t`oH- >mY,Kl9W'~ oK>qnak*ySJc&8-=HKD4K/9I B"@4έUYu.T-tdWs skCH< u4KKكwBZgTT}:?iqguɾD&̈́j B&ۻF/n,=7&ˉ\OMd!?zR\5b붴b  H*bPU4Fh/lіmG5HF6'1yHWZlu%OT&>;Lr$KGnUjNrI Nq^oiYoq/ltUE.3؎-8i˫ 5naøO;`(UM#WA%hw,B!d"KNe]%ƒK4\ X7ކ 𫞅sjp~^WylprD9"r} T/%ϵƓ|P_셫pEAxګ=Z͑G[j"D!O7z8Nc'wcP4 Q\6EQZz5i[m EN>.>UQ}frjsZ5QA?&WJ̀߀Zu7;T+MqġOF͔5lR9XUV{1aZ)aAQ+<05{A 7nv7 8+k8+lTaȲ0Qs,Lg3hjfУ#¾#Aߡ=O(xwo?A>ٸQ,%ElAqIO:|730>K[j=L[^ga'DPNZFʞI]rݥ:V TG*Vrs(I\R:m>yGΗac1p'NoQ˂ > ^YĦܭuGQP{d b74Øj~kK{*w/S i!T7}9}(4݁;k5V푠E`4u0~>Gx1 `*ɹxP=<_cZ:#QKs?BwOل>/njM:8[C7ҭ>_B/$\ R/*^<*9@pS;y^Aqw)Yҁ_1SwyԵ`Jy~Nk _J1k {9 p68V7 3 b sj :ujVfH` Ǣ V%*"CeXFi$l jb&P8QVQ%އsGkýN~_?O-I}A_5E_qu+\D>;;]S{a'JZ%r"zUګo]zѳXc釼Pc9~8y<؍1 _ ݋x /%o/%xEUY}x]{oQn6Y>7]x[yIyV\>#j{Т+e3=h˴PKNPKyjH?android/support/v4/content/ContentResolverCompatJellybean.classTMo@}|8 )mR PJ[ I ,*"*N{8^SCHW~b!MBdxv͛-lņ<`U Tw'{ jm!m)G0,6'GݶOyۥHlq-A0C=G+AJfi+/^hZ}D mGuۂ{ HCw!jDh>Ӳ{t1[^1n{_a6y L+ҁ9OdMFZ2'3}nRy S2=(MKX&ם .O]Mb%;wi@ydyW(j:)YvԳC.aZm&)8 ^^k. K M6<VϏZTxkC e:2-[W_ޑvC:c^TْdvA}ow%pZU MoV9jNSYa䳙W"jRʬyoGk$ee^ J #O$u#5/גd J6>ԥg_Tܓˋ*ÊbkWQK{jds) _7#Vkc"Ƴ]:e2&fgxe 1f[َZsiJG\VPӇhw7Az3Q^h}|7S|O |1l`)R(G ]z7<=^k \|͸o8ˌow$ˬIf~?s?c` M/P^ q&sĝ%Zz'5ǶsDӒejQ8ium o$weLJz$73L :_PK˚PKjH>android/support/v4/content/Loader$OnLoadCompleteListener.classON0}WJBR:uGb(K=HեRE&TlQs;AZ޻pa^a`tnd97M"y帨,a_tN9R룮Ax%Y}b .S(PK@_ǒPKjHDandroid/support/v4/content/IntentCompat$IntentCompatImplIcsMr1.classRMO1}NBBhʷ8 AvUSS!"DA;+6vu"SQUK@`W}~xZ9|(=,1K%>Cvd-CY悷bBfCɍtC0g/eP++ tmۋQrj0J <|!ۿ~S0; #bg{g72?Scv ~eKT 9LS2PKSl9PKjH8android/support/v4/content/LocalBroadcastManager$1.classT]OA=ӯ"XjAD@"OMl4`>l'jivL0(q&և03=ܙ7#lq[]Vsp!a\m2Y<9hX"P2٘@tH0*`_9~ɔ*acbmDf*e1$9J>CAVmѷFg~nPK /PKjH?android/support/v4/content/ModernAsyncTask$WorkerRunnable.classS]OA=]XX+ԂY a%4!MLE 4t;l3;Kgd?o0Rb,{{=x%Bx`9 C5 GS~ΣST$&f=NJB .T4J/f֥Tc杔j+KxzȵC7':}R/WJf\PE F;_ݞ~ȋč8*v6&ּ݉ ۓ1du'}9U7C #S-) FLNc<اGJ^ PC0PKK)PKyjH7android/support/v4/content/ContextCompatJellybean.classRJ1=ikGV u7"hA](ݸJASƤLӢFGwRQvLN=72oj==J+{Đn2uӑ M-kю7M(U"3NjM;Qz&|xCԖ|us\Fc[ P=*$U~U7No@w"$9pTPI*j]؀TgѤZӍ28*Iy|޽P! Oa1зݕ!,L*H<)z鲠 Yl=iZ,`H3*ni}Fw]JdSv Xpw%g!eR3OPKDZpPKjH8android/support/v4/content/SharedPreferencesCompat.classQMO1}"ԃ 7&eJnă?e^<`{:v>?a2Zl!\7y\'[׵LzBB/ ҵ"4zڨdܓz6@:]?bŏtNz$D,΋Y$bk2^Gҩѩg唉U|NFLXEY Y4/a8V1Kuۄ\h*1'1q&yZX9y2)!XX. @c~ޜM-fh0ְ PK$b!PKjH2android/support/v4/content/ModernAsyncTask$4.classSn@}8q\BK&aF\$`)ƶf7ZA w΀B> 1kzH{;ogvgv `wj*L4V5]3qUk&Xgصo"P2 $NJYHCJw.B d e3ߛ'cDxc0qӂ[Y- -tsqxr^AW),lcxg^38❳w`Bj/IIn?~ʰ ϑ/3g:QJ̗<1_TA$xݧuj 5$7hD,/Hh};^'0?x2RN?@:KXA Ml-H"){E:㵉K5XY|f =el>^PKGPKjH'android/support/v4/content/Loader.classWktTd23LL ZH !Q73;,jڗ}H-%DjO_};732Ьsog}s ||}+A|_㯯sZ\o׷j1q AP8 8'.7Oq=nyڏcA y0͉Nr*p:!3ܟ (|18y?^>]I(鸚T}ZTӪ!MSߓTY5DžR=I_O&/׍jOIlȪvJ&D@McN20;_v2j\Y^Bӊ3T.hѺ %5 .dޅDhsX es)0#)kYV,Lub=_jU `BJ:gHITCjlVKBZHKkf@[ ;6x{-ЧUԀjS$ q%A14KҲ7ϟy4L}| jR0$+䒄9o]Ly;WlS@ T~,0a9~J%#II[ ۋI4mjj:>lm︈i)_HWer4`j;d<`hۗQùt)bTofƺޗ²~_*˲WA"ųF:7ƒxu]rtk7rPU?^ uAoC!Eb!O*l1J>8~\ cUa1EBdBA fHA#p&KZrP7ֱܖ1v`wg2I-nҳQAYXcIsXp|,9LvJ^fAəZs:\O_#C4obXPRW&h|d2j-G"9^dL~*#z>o؀WC`|@}yE- gM-e(k2_.[d{~:]RGGC h0h픺ӨpF ZQW0J3a((i|8ji8PWW/"c:_*m܊AI 9L$`$A K›'t6*vd[x cNPptVúꎭkՎ-/< yC$ӰM%ɶrY460D%vԽPj!KFQjq(\w!&Qa6_mjKW[lWk 2ߛd|oi%1_w1eSTzYbV) -{"JDtLDM$3&ҲuDE=b&h$_5hkڼ\Jm(e0";yupb.,O ǡsNEp GW w[ǁZ%=L.`ZO"?k\Cs4Y)4'+y4G(CҜF pY8NA4Jd(C&dqSœ.JOsk<[Qv;W`H&f,Ջl׺NZ֫!NdW&n)g]fGǜ,ʾ- JNs08%&U}_ +uě.х wlu'(TGzDmwHݟ&w)_z/I)_KѶi26$ OAd(x꿑N9h*Vllq<4RjuVYڏE̥)shEcD+ɴb\+`ƘM36Ƌ`/f\tkI#+DT-[Dbq\vUދSuj+|.W`xvKk}5Կ?PKPcU DPKojH8android/support/v4/content/EditorCompatGingerbread.classS[o0ܵz>uD !M@2Q4$ޜĬ;rJ"Bc< `$9wo߿|<7 Roq{&z Tb\0ox'rVT #beEicvie^,6fܾH0x%|tCk0繠̏zL*ywSE0Gv_ة!grg%/4<:cd.%TXavҖ[$~nZ$.L$^Ȓߵ?-\"_m3ઋka jeO)]u]vrrb]xPKLPKsjH8android/support/v4/content/ExecutorCompatHoneycomb.classQMO@ (Qo&McBá+Fe%-^L<Qm1no{ov6 Yh(db4 w%AZhM>`yM8)hpz=*(|pA@Ʉ O}|J.|<}6s>'( PA=y.j)Cz ,&<v#_n8TrXA*Cp؎Gn#HBz#EGE[nqoݷw%8A8DR}Lj+ HDK\^U1{Ȩi@k 3*_W ",PK@|SPKjH@android/support/v4/content/FileProvider$SimplePathStrategy.classWk|f3! HI+07hWmfڪndQT۬#X;젾4qNOSNgLS}S:E*?T_kFo9 ?dpso nq/ UgyCeNeGv Mt6d[W +*ۺZF΂˵W2]AACt"<6hG7ҲiFz6~vV3qo[) c^Hf$*5e|K,.wtIym,rAh~wl[*%\UaY%qǽ_٭ᆱLE^_tv[%s6(T[4,ŸYMe :H7@V-;EM/KiFm,רd{3y &EbuԘsYA"ER}xƟ;})ָI$oz/`T@wu~b$Z6o[$ǜqA .672G k7,}% D ,hz6i(q6Hͣ=#:Osqx:*#Q'nDq;VUƍX8jrJ/`m(6o{Ti!0]BkrF<| b\(l !P,)ݐhQ<؃)~, i:ҤP /z'`nJh?nqlf7ȀؒD[ܸ97aI4ң yNJ Xd>Bސ$0=5i֐/Ø:0=m9m/:|XY*juyw0aԷ(9̢܍g;ovHo65d>Ü`溶I.Br9PZm[qPU6P-FxsGq%nOxGij8%Y</☃bd [1jOųs1BBq+I-xRDm#%y+sw|4p>olvJm')VTxSeNF{X?!1]4Ѧ%$%Ա8D]Z%>3='vOtr0@(%/(nQL y[Gp]';ri٘B^?saO,FsE,ܵ#4O/V4f1_BwU$^6q qIqx7Om 2סpQӋydj$]؃2ӹ^  tq]?Ir)O8=D~ gș9C[XIqpz|Ո`?F_PK3PKjH3android/support/v4/content/ContextCompatApi21.classRN@=BCRK]xA"x(^%Krj_@ЏB C$gvg9{fwlC ,5Ba_:> -Ba|E(7uNmv+M^KFڞ|}hE&ՎL   s([_Gg*sX{ȰNWH]?$t@XEi@'~|ޏAMTXGh慼Ba,Uf;*aA){OXxg.bU> OOq־Pw07*dǿm8@&A}#m)vJv0PKʰPKjH@android/support/v4/content/IntentCompat$IntentCompatImplHC.classR]KA=֚mef=YAԋA`=T>M; z*z;TT0s{a_ߞ_`%!R,,XXd>JCxa!Qa*8o{5\Z:5y5F4(-.^jK vE)< q\_֝jv:GBgPl!8RZvc(>,>F+ι'׿̻d72X1̘.Dy;\ہ+NIdrp[6aYX1)-_1i?); ưG VY:Wao_+S\? ToG_DڦPm;bTMH'cl`;m@&PUnb2\J${JîOًt ];Fd6Њ)!ւ4-5'P{Z"%;Q]:mMݬp:je6/?Ha q0L|x.3|3G+ø&S/B0 x1./S@/FOC) jܥJ!K 2 4b ؀۰[+I'>!.a\4 QNx,E3IfhO5,hu1sLjHcԼzBL6$Z"-"ta[ =m6PߩP!j3HpcG(+xd(` Mߏt'gtqtИP'4sk) 5L&ZL]˲$_ysw\wK#xY4?xUˇO?s"yMLG*W}?:7oD[oۢyGPOqC"*y| `^\g<8H*RT`X%b\ HKTAUR. ZjīE]ψ J!aGk>H5>ZZZ/޵E\urU+]eĽJЇ}{T4*]&9@SG[^U*mUi!M%>k[m%hɤa$tHtݹ/KX|MIɑē#[bm}ܴwt ڶwo :Tb&Ӷ뉌+vwS1zZ[v9 XhmoWs'׷w96=ΒhiKVi' Ûnч;;Ӈbzng,n>EuaˌGҙTʴئȐ-eXX|ذVy6!kt ߧ)hWͺw9̪vƓFWftа!bsgm-T8`RN<ݶ٢L֚-?boLա,5!tVFv\ [q^fM9#ϊ wXZҰ#TT؇L+nK7H tɞ3Xͼmʂfm}PO6\Cf-k5w #u8\+N(+p@2o4츙+87nFbb3{K19X=(1kb!a|c:s< kU {8bء}GSl5󭺮ٹF2-/Qn59dڰx2[Hq-]0 ̤e6].(@ֱ{&!2gd6iff:ң[CFBn5CV3D  fykffJicddM .e,fh x`ر`ch'uA8i$G =Ry4p@^69 j8>95jvBUv__\p5}>U]MkTVFt=BO!]&bن]ye3zK:a w%uIwa{?,1 ?k4HC,5pʙ;t$ƈ4#=hk̇övhph(PG [VFfGYni4B72@4~oT&Z5JvD&G54JY$@2M^ਆn]|U<62ٔ!Eଌ阐*e4:JCRۢѹx|\)PC%I.aѰFk@Z> }Pit>̒" )C8AREPy9g52v3 ǥgyc\eΖtSsl.ib؝FA6́E!X"!ߥg%B MeeGqsF,uB|$*KKj ބGnNS*h-ٝAl_AWkr^PqEHGwMx`y yҕӆ9l #1.E-.T+2W"bkf=U0JeTk 9BSrDK8':Rd! ,Tyo.{dLrk/k',2>D=fb̘QXO҄s[ =W޲6)1s#kfY flUj}v+rp#g,@$ .ȋX2قڜ秗/ɦ@,TWUOq3X/(J9] ,7'+KYX(BPyT!c<1o7w<7oh޼ɼyͼ6s〨xݖ4xznosO{j'@_V~kY"|!s9 ù^BN*) pi (u' ŧz&= ΂O,0 '2.)*/`)]j5t=6.g9JVإ}c7Gx}IzU_w5l3aG,9!{EaG1$+~Jꋵ'P$DuOC} %'Ҩ*EQ_ O4r~ ʵ`YxPRސBq,=%O[5:>9QUlO";PHuORQ NNz xNH[މR8[58;8^Oga&,Z$G'9+⒣;y-GwO/}EswUd}p,5)˭&xMb~Vt{VZ0 -$V2XuO:'9oh0<㇕1I7QNgB͹ .gm5l7"|sEΎQǎEr>yKZz=E?r:p ++'\~^Jr9_.zz-S{r\W)Kub4 'SKcL#xT楢[|]Pv=3w6_#OzeNzeNzȑB[9 6a8 m,Gm&]_αp )*>9}*rTԧSKhgAŗٹnK($LV8@T30jqjcS |\;q>`C"rzLX'#?{sUS":k/8D0u>k"ӕ b+JYje)JJ4(U*hQVV=g~K Gˣ2\2 ay=+)Vѭ ݧU| _Fo9{pvbtSlNXe9&k$V;MplvՋIq{ZZ7V Q܀FU.e3)U"4_ CَCXJ3nSv*xDiSJ;Un\)1H׋#ƠMvO T.n<[-pF.^wW9-Y*߱ǣ#9! lP*US؇5thΥ9=.M9yX.޻9Q+mn:bz7`zׯ vMOy왞i}p"Nqo^5EU=<'c=Rp";%4*?v)?F)?\:c^c&#΍.ɍvF{r#+p8kR--󔔔Jbw}s \kBlj{cy}g# JN.حz^ JuP7P˔_ ʯs7" +7}wHPaM dm"xUVb}#GM&UpMtpS2\s}<ViXU窚5usïIvC[ȯ1zAPwY ?, bYEKA5G=D'}x1ɱK_@G"n.EϺ$x{;N >Ȧh+?0}Gkr?PKid.'#PKjHEandroid/support/v4/content/LocalBroadcastManager$ReceiverRecord.classSkkA=n&UcU76M/ۊ VZ2㖸6|A)(nhQ\;s{g?~QN*JsOE0rrKSc!E-*`apÐrpl( -Vwv.̎΍`w:o/z JPki+aZX_40lC0ī%քIVm7cJ oDcъe g[-A;vi;nS< g|E]99v}Hi b[y0$vfx?"Ttn; hRZ*RXQ1&PSKĵ?;XL3,tUDFT!1dv"t!yתU2ƛMapAjT>~ K: 2IWv 0eS)5;ONUDFE˸J\q~0@{^:ć3\c](擑w4Bv1lM\ncy<2v[_kYOҤ1Al-:Aoc@L,jᥢE伍; nyګCI"GVxRtњ-8CsxdٿPKEM(PK jHandroid/support/v4/os/PKvjHCandroid/support/v4/os/ParcelableCompatCreatorHoneycombMR2Stub.classS]o0=ndl@*դ=:5C>CcW^e\k4K:;Xj?PKp@dRPKsjH4android/support/v4/os/AsyncTaskCompatHoneycomb.classSn@=ۤ 1^ 咶"j<(R*d(5Yk^x>B7MD񜙳gf߾ nY"EK~Cz!ߔ}0x {BgɑSg^c{J};NH*mMcPW| GйzmanV#ߌ uNv48搚ɴI1,rCgSrRU]pwESWn>9=cʴڞüe۵E!/Ht&' Mܱ, EHº *d,q\cX!3 j#aayh;\^k_~Sk4X+ϵz1v6u  Ҳ4EcxY@A2Ts42`d?eH`bψL!UZ{)g+q d/xG*9Wm%HX#'$Ff]AI'YGIF_PKmc2PKjH'android/support/v4/os/TraceCompat.classn@M,q64 3--$ \@MSr[UYGS B̺i{1;73_x{,E\暃2s |SAc0>ҍ^hߺciơbmk:#4q L}ב\e2+Gp2Eq,FgyvS4aMGadS7Nn"ՌCQRSM;mXX 0 K|=XeX<$•޻}ahf$T6{stXaU}4i?{JG)b#|@n@+X|/!{<!*iZZ&F_iL}FT{ĘZ>AB S}A3DsK)m|ym9.˹yr ;PKn'PKjH6android/support/v4/os/IResultReceiver$Stub$Proxy.classURG=#B,- B $!vE$rHUTjk]/򫩲qI*".RL9sg~MOQLFcF70_aN_۸ecm{wCJ0 r.^ byB3$o{2\̞0`-u"_|ڭ Yy2\zڎBwhUQS}!< EȐx 3ta *͚"l5Ԇp'&7]vaB[+!X+ 1L'|.VmB׵|-F4`c+0tU~KW4m_Eqolܵ і'!%i}T;qЏ}0%*Df AX8\`%+ʸgXs{L ޯ=:.Yr?`q HM5=2GNS笆IONCUDwKES_e3t&UF :DI\ %'>6= V9{zY1dh8˄("mGcLqt_pW#΢Db/;tI,0\Ç8Y hv3п0AsWXLC$a0oE| YyfTy疥DjaD8;fbf!$T&>#1>O~& M9#}#]?1)S| |׍6&SׇG@F<.Ҝ4jՑPKKVXPK}jH3android/support/v4/os/EnvironmentCompatKitKat.classPN1}uEM<O`= 腍p/,%KKғƃGgAcxIg:oy+sP ]{> Kb(6[=c)-ogR@=aU^GBQCͦSc_pk=WԮc&Suk~"]p$u܌b.2FZ U:!G#(Cȼb8cYN F 2ΰm?_<H] ^2ˑ'E{bi6P.`r9 *ePKAL0PKjH.android/support/v4/os/CancellationSignal.classV[oSGַ[B c;1@B) 4Z؇U/HCՠ>RRK[RDCR!o؉P v曙=Ͽ8~4χ&s9q@ N6NJSapFY q z>mْe S SR( %+M[z>cXiX,VƑXr-dz3y=ױy@`V9Ĝ~CL #m mxY1 gsR5X lH-O>3$`!&u3+(]l΁A5RR7nI`$2 k,,Ưm8xj+ͭS,Lϩ k+'BbIیaf*-"5R0m\52$a'‚6Fȝ[%h{ o|q*cETUoTG:BkDAD bL Ac"wp(I od[i.`i̐\znBvݍj}/ >X/9*oM:ZM|^u*pI`W^2c)mc7>4^<55 ݒS#[l@_fZ.8dɹs))G5\W7P!V+\m#FgCUhl O%#R>)GWw't*zei5mXC ! >J|~5ՕM1h@h2RǃᩔJh Jux\ɮ2rk5O D@>_;lS7MݫV~/ew4|$T"J`Rq.R^IٍJF&E%]¾?K.0L3peH(R8D>ə-q\B1şԥ.GDa{e:A47ۦvnʝ}*KC9aBY5xTeO[cs5v |;50h=&zMJ>em#e#"O*"ySrת]:ةBp""BJFdd0iEv(|,kAD/n)2w(*ɝM gha2 !=ӷu-UFP^;/T>E|: =iz T|?b22)_9ޚ1 drTٶ0 9 k&XᑦJ~E],q_RsI GM2n8hׄmyvoPKf&V PKjH+android/support/v4/os/IResultReceiver.classPMK@}ScƪUz0{f )x&ݰ 68A|||ƁŽ®ž„,b$g mI8>ƖUeBvڲ曋GB:-Aa_µ໪f¡9u̹c2!XiY7]4&}{V=,W\DZ2_7"1$#J [FTjے0P07PK3%XPKjH+android/support/v4/os/AsyncTaskCompat.classT[OA.-*XK/@"KH06V 1Ps#`?RꟇ4I^b$Se7 4 Dr ô{gXWza Tc͍L9|= svոl֜ھ*fHZ?rtt=k(.fF(a.$!v> S)(ڦ&kTY}S|hzuUo3(-+R+:,AQSO_K ,0}!]O )T^aēT9̐I^3~mxM>J7+؋޶C}D0gC8{ !Q ) SbGPuo1oq\A/U3X  ,Nv;= A,uIB9JB䂷k7vn$PK˴BSPKjH;android/support/v4/os/ResultReceiver$MyResultReceiver.classTMo@MLB $-u)Z )Р"muH1J+đ? 1"Y=;yg]UL`aI- ˰KL ֊`B kꑎ51n7z񐭩Ue0vrr/>Ul\H`!V\sk 5 'A1͐- Jղh+<: ap#޳cZwERX ׀,jJ@Tp7|)>Ǚ8>)J('q!n]D\3$,s'HĆ+}۸3p1yD=kJk2S:*`}.##ӟe:y2*,f\hj[:MROq8 uwAP AW)籆brAhgnHSΛ*a=A&RzGdՌF :@1VbPK̚L]PKjH:android/support/v4/os/ParcelableCompat$CompatCreator.classTmoP~.0 {qs9+n8"H4$KHFOhfgimJ(㹅1YО{ys `NbY9嫠`GAQcTm۟<=;Zzz[.ڦ[Yw=ׅ}Wup/L3TJU˱C%;@CX4嘯ݖ)ap>’Ř" 㘢ns3\@1O{$szw>Ce&s^Ϊ@JԖ%s_A5c~u;Gֱmۧ*ʔ@D͐pϯ৤M0 =Y_5'hTBjd[mrir)L撖,eEA $<հ] qGA4$ia q +)# _ǃxPK85c:PKjH*android/support/v4/os/ResultReceiver.classVmOW~.00 "b R oV^,PjmFEmڦMC/jIڤ&6OjϽ3.f7r9y˝w814R>M)‡&5LcpG,4|PvW'iTnɤgI+m٫'`f.gtjǜZZufV@ T/0LV5rv&撡G \Dn/++'W/Hn ҆&G'grYd[Wk Mً=zWCUc}MEk<Ѥ@WQLl^bͪ"[,ѹ_QGŬ6&oXoߕt\sJ3j a8բtmB8XLTQJFeDjM-U1-PKR PKjH5android/support/v4/os/ResultReceiver$MyRunnable.classSQO@Zw (('%PL (bL.909 ${w-)[m/ga?Em gKp16,kmA76dnᭅeӶ {5 WsZ˞ FޗD0{(9; Utd]|{ 4 ^9b0J-_#O Sm]qk;wPO;29M)x :0R=՝F6/C%dCj|Z~q CJ/A#} CjGr&8SwzFۋy D틡T]2R 7S\ nK5hfp,V7āRuK\ma8% tPXxGKЊbXGɁa:*;{{&X%&ϐِ-@+P/(X1CV/~ q{PKCb`G,PKjH?android/support/v4/os/CancellationSignal$OnCancelListener.class= @h̟ v^"6n B ~Ma $Jܨ]7̼<_-SBhL(8Y_ Ŵu&R {SF_dڔf=ymdl[UnXcƲadgiEMX WvzPZa0u[) "=M#L>G4F PKhʹPKvjH<android/support/v4/os/ParcelableCompatCreatorCallbacks.classPMK@}[k֯Bzq# Bs4I͆MſUm73oͬ7o.p(1(ׂj| 0  ʂ+h,hE\R5$cE[>[F Y96Z04KSWVkza8 Ə&>o]L5nNY+2)bJ$eF9>ԛ|ulw؟ z 2G9ZoPK)PKjH,android/support/v4/os/ResultReceiver$1.classkkA$I\5&6^b6u+ER %X &!-1E?J: TL;AlpEr"Ta*#W\cg4S! hHM;V| {JOq7"M/PvXŰ7/c'tߠ;[R^*w 蹈 4khmFlQaXIW}*l1,7o]EGMwikt-y)r(3z;C3F/41`So8K=.@tJ01?Ec}P]R.XzBVjab>`r+`Mr5FVY|)ecRŵXu7&nQM)Q+̌wePKPKjH-android/support/v4/os/EnvironmentCompat.classTRG=֋% c&ͅ$6"8 XXNRAk/3ݑ|JWJ+^!aR~陞3}t?7SX1acS|&>Køc`D0U0l]_c3-H2n0ċOy.vU{~P.+{eU<~ kB\Kq"6Q Fv+`sNչTҡ{\?apν“e`9eWr/ꢩ%f1myb ԟxJd\w7(CT'ϐ*rӰVc-v0^:FLyi_`c7\-!%:8^:V:fϞ|/g/7a N0pF6Lˤs:ႎݸ\pUucn[/5L1[QSxcrrB+'da(B'Uwl3l/#sylriR!d /2g8e)QL8]cxVˉELٷ㉹R]ȇg(\:83Q 5L33+K)$/ik6rFJ_2+V/27]52Q/W-Y"~VHY Ur]s CdMj4]ٿ/,K[nPo΋F/$cckXMF} `֮,w\AU:ߒuTtb:^Ug~HA55}@7 l7#cD ã|"ПfQ1pUJQ8 ?{t㍮-H1Ի/Q $w>^K sWufi6DToȿW!H}haHR:8 RIix6|{)i}z!V~ >4C-U1Q#AQ606?t"k=ѱ`z=k?ԁdʆ]ث֕ TK?l zt#ǎ(W#eǁ Q4Sa'L;qB.v@> E1AC&u^!EߓYNb1K~,y(a2u&>U+&uf^#O6C% u",gr;=%fw5'OSkP_ݢ6rK~jPKa PKjHhandroid/support/v4/hardware/fingerprint/FingerprintManagerCompatApi23$AuthenticationResultInternal.classTjA=If5jlkAHS]P@ThCz=)ev63hXP/>T鷛XM9g}mE<-g6`[Kl3 ]юRBB$"au t$/I8ƛ{#\ TCc-{8V49櫻MJRIᴾ;}b+ JW*!AHj7yZf|,nf$>72R"ICQFhC`NK6?ws#Ij_e{Spb e/a5k\ z_ B=ͥ4^+{d2Y,u[4;_`ݟ. ه |oyNXFcUix'^΁Xo˕rE^ƝXX:\%TB 6G PK0UPKjHiandroid/support/v4/hardware/fingerprint/FingerprintManagerCompat$LegacyFingerprintManagerCompatImpl.classUMo@}/!RJJ壔 .A)"8lmelą~g8p@rg<;+X"[n[(X(2QcBP"*nSNCz/ECsd:Bo O`*h+T-)Ӓ^S&XF_q;]:]MՌ/y \մ^zn ^xB`g-n"9ƯC0.ROh5C 7 X[H-5V/y7Ę ܌Yp$ 0,ۋ̖g;2ٿPK flPKjHbandroid/support/v4/hardware/fingerprint/FingerprintManagerCompatApi23$AuthenticationCallback.classTn@=IІʥH$,Qx*UQ#" 'wmv;xB{IH`33;ώ6p)\/b5B*~L%' m+K9\ :K㾊խ$K+W* ]!TZZKEI-g9QN_0S'MhI#" t@׶ ajCBSvDjWnڏDGN=Y}Q.C+6g.Q-KO'"0MF9?yzFui K^'H+ʎ9[ " &9gxݗ.k՝TB_EYx'l i=fVPDž iA1k,:a Awg--e`U2)O)XuP<vWR%V@|>DWS{ | / PKnu PKjHSandroid/support/v4/hardware/fingerprint/FingerprintManagerCompat$CryptoObject.classKo@8 '-Nn RBE׍$k;%QgG((c~!E+TD@BʎppA| qΣhKI%#ʀ(s(kjDYG1ZYʐ(o(~LQ69z_a|.ceJNra6cnU\I6g?$tABU[ PKVE90PKjHMandroid/support/v4/hardware/fingerprint/FingerprintManagerCompatApi23$1.classNAǿ )ZĪ?AkՂ51nK{xս+!DhP㤴"ͥ{8 %pY5t$qUu5|C575Lk`p'iq)q덆 zPn`[<=7͑ju8ݲk5OʔYKa.nEȚ5yr3jM3خG">d)@6lqam?q@B$~bqB"Kޙyf/ fp.wV=L6@J'nWrd"Fyt iҗ:QHT=]%T[ZK"%{:0 8 "{ݾ0{aF4t6-^;NH=2&2F# s=֑R}i;'ي{@-B+Kp}69b}T}t}'})pqǓd u5$g/)5k1zPtZil )w~ݑ>b*+YG_̂8<y%J<X.@=ZKd^hy,dyi,ra_'`9z1g0yv3%V ~QeQZ6^ v7q),PKGPKjHhandroid/support/v4/hardware/fingerprint/FingerprintManagerCompat$Api23FingerprintManagerCompatImpl.classXYSG ŀ,N#xqNDC&1l-^Vjőwb*NU~@~C*yK I]2u?` _#[X͢}b -hP"+{/X1X]A}n l0HZǦ,gtxsDG[Il%mmqcTL1!xr, /2&b&Cˬ7[klfkqr'&J' ӷ/qvkDz5t>IMض+%<#x&Y~àCyhsixf^,{u^b~m+& V‰r^|1?\0g*K(w Ӷ%kv ,WvNmXOAC"e uoPٌ0#}ezX04r"C/ CG014/x$9ǓdIoR+x@KLM;9C˓Q`xq\Ori4Pک/lߡR_"ܲQC [eU*U LFGGvQi0,qr&pCC85 C:Ufa|j4a{B s5]Xt ŰV[_\6w6CWm&{v.'U{ȋH- +Tɚg0r-] K]KRMvC}jע&,+΅fWϞzET@#2^ڥ0)H?qFQHR qizg+];nhm[B?=N^gyBYV{# '/SR?+DiY! UD9P/d}Wɂ`P_S+P9@L'G~ ? h/_id:;=o/gDP p%AAԛx m&wh`\e1W\Up >ə`OI?'}I~oȴ413l1eAfpBKˠYNX'3hg(CeAC3#gA1ֈL-Z h@z? $]Vlۄ B^`g!XVg] [ d vA ; d&e 鶔_YMѽLpjc}EʚdڈټzzJ'4lqFR09z!~a@gGԟ7A&ǃPKܩ kPKjHKandroid/support/v4/hardware/fingerprint/FingerprintManagerCompatApi23.classWRG=b 07CB|`{eC"DHp0hJ]qT yyAT\I)5, KP53}{z?` ! Qx!|eCas5y9k!tI߆ЊuxSϘzmY珧Lm0A$BD2=M鬛-Фk4̈́^(QS:%0prdMwruے& D탌~5WI2)_xxUM'ua鏲; ^ 8&Ui]`ݔǤFRuLvq5%䞴uuӰt;cΕ%iI靌t3 [{z@,\4Ryߝ.GdƧ}Uu 3kiԓe*فs<j,.[OP61^~m+USZ[jU#3gghI{Q6)f̓%R77ME%ٖ4>@VƂr#@Yh߳edG.\4V([.$3ou+p}recZ8 R?ЪŏjNs=4|Ww J fM0lㅂ۸ SKç,+WĦ?nlQ7S0EQ`79Gv ;L9 \dX}QϬ{qO`&"z|զ^]P]S?p\}'ۻFs#\qWU=^r عBFwFLp}5'>s(Afmޕѵmjqq{U[kIji+\Wq~, M&^q$_h@D)MJgA{ZF:;|E5oZ܅ꡈ M#hX)?z8AӾͽ,?z;$K N\<_R2eޡLR-1DЉx!i@h17]?]4_#c^4>f?}_PK?kKPKjHXandroid/support/v4/hardware/fingerprint/FingerprintManagerCompatApi23$CryptoObject.class[kAlL\mzU7`}S Vd3&Suv67`|3KkΜ/sv*FY,ksS[9x9 MYyVa~}r?A[ɴBrB1L}?P82G1 ea(mSJT?2.Cݪ ūMFons%ӆL܊A= 0I"(ÚdOq߽7q%W2 +yΕq,.|r89oH 'O;>i项uYĂW:ayJ/cy|eL tWR،*kROrDwus(ጋ< .aϗao]Oɩp>> ZZy0z:`< E?`rN~ dg+6uK:EqL=iPfQ I#>Q:DyCc(sKGZa;'|$ʧ1K43MYb;-m1ϦMa+#3_F_ŵp.qeqPK]>DPKjHFandroid/support/v4/hardware/fingerprint/FingerprintManagerCompat.classWmSU~nޖl7)Bi Rv6XKHxP*fIfv7h:ʌ?Op, :ds{K\W `Uя\hNaYͽ$QP 9K*4gֵ S5g92w8yAް3L*7vQ0]caxL' X] C[7(X#:yi#pWˏM#`]'0%qȝZ@ ŻV񛍆 ƽ73c(v:5([b9gfw "s;X8axc-dUng{|xw"ɶ' ɔafؼJgFʐ﹠ gN,3TVB(8,M Mg-*5:Տ78n (Ћqi}Iё:BUD?c⥵RvM'T) ˼n:6=\QrfR$.&k SRCZ4+а+ ѳ74 "͝%<:3 U=cFn] zt<2=zs+xpTiٕRa?JG*-mV6_UUzS/B?$x{)% Hm#4b" jޗPqd q4\F :^H-m|) LWș1?&/ /@"^!;@,эDydP >"-@!M`(Ɍ]_OA'NUƴ3Ziٴc%NyH* "ܠ &n!Nԁq2DzOrn#$ss#a`sڃF $#/!Pl"1@mʎq4{A$TX\;T~ΆCQ}$_PKop PKjHcandroid/support/v4/hardware/fingerprint/FingerprintManagerCompat$FingerprintManagerCompatImpl.classRN1}dQ7rqѬH$H410VX[P̓G A^}7o2zp^+e"E#Ll"ekRQPC|=C)Jd2RXX2o.3 7o7KqdžRD$v'V>I٦Dҏcm, t:j"9&{4ژ #_H.v'®nRj!}Z$<Փ .X_7]Ta1U ) W:ÐFp2, Ia}o peyĪ;erNc[7PKmbUPKjH[android/support/v4/hardware/fingerprint/FingerprintManagerCompat$AuthenticationResult.classSMo@}qc\Bh $HH*%T#=o%Y[u?r ġ?Uu4 KgwÐ-4osnrp Ļdʔ;σ2dΌ$ YyPFP8 Ȋo©~voP7ns&m\W֘rj.Q8PJ\ saUKڟB"EXE |ַfC>Su~!\WG@q`|ɸS O%:˄FUPPKMPK jH$android/support/v4/hardware/display/PKzjHDandroid/support/v4/hardware/display/DisplayManagerJellybeanMr1.classSAS@-Q XAAEH[!!38)z6)aB٤+g4@[=y}{o<~F^I V+%+)RjCh)vC'`X|,\ۓf2 YsyB<^tm_Fqȥ}̥0Ax25Ƥ6%3f&Ðk0V$`, ]GZ|i30!B bR~(YZ<M_{(}#-Q-L+ƖbȣAE-lАӱ-+0`j3W-dQ Ftҏa7c݉e'$WH}J ҆R.I`5크ԤһHI`b%_ %x?#D \T@PYT%~PK)$8OPKjH>android/support/v4/hardware/display/DisplayManagerCompat.classU]OG=7^{Y MiG!ԛ44q0!1 a#ɲk>MD*R[5+;|"w>sϙ{WQ0$|a7TL"[*5đW!ܼbQ9wFܭ̝à+Ã@ ZY4 BZrgrڎX+<خV!Y.۾`pZ(s{vð<7nh.mVCcw~chArV˛a~'rY,J뛏KjP3VNwq4oM:4DؒVH.C|vp!`P^ mWw_[<;ܷ弻6 4r* MM^ ?= P:R}Ijȭtq(<KT+DJAf/][&eڝs~;`9KԪ^۷=[N8-2uⴎ"r'V=+d0Ob֔%O:!Ùc[^k[OEUf]Cۍ6֧j>Em^gbT W޼6{K* gXjNcj^ިp\1ڄ;eDr!z0.k D=}By|,7hAv${jO9jd^$.R7+hXyz$FJ^++dȨHfT0/YRc80Xg H1Yx<<| |d 'N`lG_PK`yqmPKjHIandroid/support/v4/hardware/display/DisplayManagerCompat$LegacyImpl.classSMo@}umJ (%ZK)RZ.NBąk$7$~B:&iEsA\<3o<+UlH GNE @J1*⸪J V1՟vioqׄ0_vu̪2E[?Z`0mߤL/pl_ؾ^_0ĊNU0M[l7}w-ʎ:c1eQFToX Zɶ[ z+5 ֚jB^^׆|N>*5] 3 &5X)eO%|rJ_2Zb7i! ~9O>ϔykzwMV7/jۑs+O 4]C<0fNߊL0v46`h0 6pa$C?la_Q1 $-|Qe4ǥtpw_Rgr9oZ}MgMD b9p:`y#enf0M̆6t=>aak -z)B\b$ZAnvFs'l:9~qT(I AD O=^[A KI!#d;!FBf~${X%zDDEKr9 ijQVPK?uPK jHandroid/support/v4/app/PKjH<android/support/v4/app/NotificationCompatApi21$Builder.classYy|Ɩ>-!d.BH`D"(LVjepۤiKnz7Zhn;MCEe~o{͛7o~]L/V?z܂1z9WpLnIFbjFaZFcznћ[6F;ybn&a^&cr<> ۙxL2ɝLbr72{|ɽL>8>рOSL>3w}^|I|E@ ([5jY1LF,ЬcNwۮ1b5װH@maf!NOmL GF5'_խ>s~acF"}zvAC?N~n% +VҕtτnJKFCCCr t\κwm|ȔIqJ:)QyL3( ]y԰׆MqyfmZw2ŽS T%G]\#WMdiqcXSiOkהDυ@lPqBAo96F0RNZhE[#up"Yүd[T @H4cqG׭@9I=] :7IGdvi -@FN2,%O!IwS6GF۲SeM#t[$T/xE Jh% lIoo)e@S jB7.(l)gZ˅&x1lx~V:L+JM[|(ZG xYW WL`H)Nvj9Xit֐Txnz#WP %i'{-Qwsfs|VEwlgg2ev|>K6 Kʊ\u7_=zRS4왙F`ՌBU؄͠Qjfj0X~78gPhPKX_z=4G zh$Df-塕>mk_ltp|0N'qzZa^$ŗk<, {Acn,yK2jYC(PzɬN4`p`KxX{;. {X+u'.8 ܋+6[~pS?-ݺh'jFM")`>rj'ADנ7Evcmx,m}Z| T# ̄!( 0$zz'쌞H$z!U" h(NIft";K$J4J+jȠKt&HvDlZ$:)CȐCԼ/ 7 T?G)8ขߓȿ ^ M}=sP%ojo23K[|psf9w朙w X!͘C3&cH`J 0ffqq\ah0's Z; W8;EZx6/yuQn0"{n ʥKhRZびž#-݊+=QI&O|K1^`E$U68$w 7jKԌ|u>063x[E. VV($8'$eh(O eUI;ed*k)p" ktAQ/%drbR\*o$ y7(qi*l*0/Ueo;ХgaV`Ec,HXSϰjnc@/1tB_~.uk }[̏瞿ʦ6uH "g_IonS\pd{eoa"1y0~8/bEFCQțD.n^y 0C8w7)9Bandroid/support/v4/app/RemoteInputCompatBase$RemoteInput.classRMo1}NlZJB J=$PTAWDXԠlF+^~*G!fEyy_]~&fQ5ZO&+@kryNya_޺3Ke؎H`>Q!rٳTCTr~rDjEYHfYtMEl{2k,nLBzHHK3)3f,9$-OLJk2/XZ>fkI^uQoNVhh0#]%¥We/:-yY4B<ؾi~~xD)^4Ư?!uQŶY}?5̱m2+Z?H;@` bTa~Ae;|̬| QCXo|PKAIPKjH:android/support/v4/app/ShareCompat$ShareCompatImplJB.classRMo0}ΆMYlK*]@@[ -BHKP܍%vp? .p*G!iT Uϼ<3_x[=X \ pH*i3t]?ҩ`XH%^Ş0^N|mз6.S~0QΫJdgUjLê.Kml8 yYs?d =QMy)"g73\j*ƣDbTvaPe'ieZW eևGϤx(ƣ7+cX=BE'6SL!-͵uUtl0lpG ߂;o:աC@^HȝxX?,Cz8O d= ޗc yph[r_Vf:i|4<\3hNT󨷫8G| ,PK5NePKjH/android/support/v4/app/FragmentActivity$1.classT]OA=C+m  ERZl_4&XŴJ;+;O/&$>Q;55{ܹs=a^yHa7SF2U!%UO2h6 R;'Mw\WzAV[ze 'x ̮nЦcлv 5/;^}2S5.LGɸ}qiq) {hL`Tm}ޢ'_!2[67܍i j`xӧNӒ"hK#;:tG1uŪ5븇xCcI ,ORl@CȩQL;C l!i7 p2uTSM&#-BiL!7x" gᤘ-FIaӭJ; gi(V-_?װf(Sdp3)%]!SQ{F~9! 98w91:XK|sdp q, I .oG 8@ HGQyp0\ 38u5/LHPKx|\cPKjH5android/support/v4/app/NotificationCompatExtras.classn@gӴ !C @8ĪB,SEuv"z6wāN(V.;^> }7={ER1B&nKQh*%kֳ]eة'pPxL`c.~On};^\A؆!p~bpB` \%@ӖS\0&LEt2stL7H6G^8u*ɧVQTں~e<<PͥeS|ҊxN(Kw9gׅm!`!OX /Ez/e~P:Zxqfİ2аl(p,+41/YLj6y 8[kMX|/5ܩmnב7jU;0cx3FqW _*˽ t>ݹva#Nr=PKWPKjH2android/support/v4/app/ActivityOptionsCompat.classVsSEm.=$MhU^mA[J)Z-&fɦ»:!38δ##Ó >>nNӦB:hfrv?>0B) caDž0|!FRrI~&4\ &] S }LU9s-&k0"'2/pqsܶgeyX4 14ysTv d89jXYXƶiذlox"U+vEl`pa"&öQnRatǍC{,& ,2op፷OgE:&(3+^.p}ȿ xPKnkXpŕ$W<+r gsu$;8Y?zh5cC\ g# e)XkU2]*rie F,oXp5&Û`hKeQ=7 T= ۫)J . S\Ų$x>vnN0a 2UѤZDVyDˢϠ(Uo:Ǟ+h8JU泖!J.|C|^w1nqq5AǕq ݁KPC%++c\!aӖQbTrYt[LדVgi.wT; !z ּ04\ъYCRgXG.̽>u!/mhG?Q! kKDÆ[hlf ◪rٽ.c)Vz&RT1#/䋻fG*ґux1נbp?D]Q/w ןґYh[s:w~*Wz_T?yZـk =No=_[hT =߃56Mvn[{\D=ZNnUcdu2]4zlѦL Z xA#pÃn#%h렏z <WI2Ĩ h) 1jRzڗi>QD-ŧl'*>Q̧zbчRQ) %i$>&PJ>JITNI?)dҶ*%A4k hiKTψ< zR~@Km$0>[m{Do?*}Kh\C=@D¸M/шȍPo(%e*0:F*+OMcU- 4Q 5F_؂wH MAjn{҅JowE Il8 ؽvJWC_;tm˨#|;ۮ6V'<3-^Zuo ^<$y-LWZs訤:tۤ1o1ag2m0Kc e7cE5YUBbY(]t3fޮ9Ty-źG L CbǭISlJk9̌lp̲N]+GcQ5 iB$4c@U jR O4,i 4`U)Oː:()R'd]o SK{KD{@1,,ܶ,lJ^VBn82gQQ4CM_1gF)B54rà.Z}/p"MSx^:7Km^ԭJ"*y8U}Np ;h :n>@>@H7և" D/~ #DDQkjNV8TS?1WhhhiD"x!3Rh] !; $R!N>ޘ/PKpGPKjHGandroid/support/v4/app/FragmentManager$OnBackStackChangedListener.classOjP՘BOP@A&KLL_{O?΃Џ_z afaf /"<s_;LRB{<uu0,RGf͙GNM2NZ;x[}։C{dk^dUzZm IaX@h BCĞc4ݰ{PK]Ao,PKjH<android/support/v4/app/NotificationCompatApi20$Builder.classXy|ƖƶlN8DJBq `ZZKVj8i>gHӻ’&Mmz}mz7m{,oYoy͛7o޼Ge6/ / V˙d*kmLnћ[yocvF`N&w0]dn&a^&c~2L1009I&wUpw1{},{q_> >'|?8ų? >AW1"lfJwԸeNrzN`QfHŴl6vQ#m-.LٙG\G#񶒸m[)S eƓ]q!-fjV:;9 n%sW8lҺ۫glW0ùѭajҎ3ghx|hh(^ɒ! fq>#Wlue C z.gX|fDwi#&qZfh3tXJ+٬Ck&d5'k\zI'cTYixX#peҎ9X&O3͑QH3Ck<[ eS-aM)jkwiY9W&/*/+*SF960䣼 qtR1LI1A-I |է-ZN_O:뵂3Aw\_zGU\Ս0T18Ny'o38*+-Z<~[w]U?T#Pc&?OU *~_%*~_ ~w,{A'_Ty1*_L{S<4&IUEêcT/U4*B4C42jT1[W ̞_*b*E*:D'ej?GbPz=;T1ẈbxrV.JSz+)z*23JFt"SE,X3K,P\U9 | ~tDwTuiF.U5+`ȇ=M~H71dE47i4΀–%DZz킟7G{I^( U)'WGgzUDs6]p%aw k)æk !F'ͨ·!cđQ߉Epuzg->v%8F y}Fz̥q1nuՍZ:omEQd1u&b]W.ESCJ&/zU$ZT(7T<*S((e~R4NU6)ݤ*YzFmj-\݅7QoxvAV:݅ύV*}O{/sz競Kvy&r3~ʿ>w{L MT#5]Qro@θ^R\[h?˦QњJs'onRY}nUp4&WLjh9؈ML5Z*Ϩ @Pa$ǩs0뗟X~j8ڕP塞BAjBQ#̢_jf{h~ {蠦۠Df4q"RƙKyVy BV\!#uDȈ4Es6n.8B- +ɨ-ưn,ayZ2a M%'H)ˏ3piilpbnH/k=\q?yXLtEh z+ A%NDλ|u؋$paF? =x2 HV}'T'Q/8E! B((S4$z:5Jt !U" #h(IhD)B$"(;[4J4mjZK!,u¦=!K3"t(p ſCBawDĥ߸@y8MgW!NN7cŒ竃ǒ@IxKLn &J'7 HwscRbH uW,^9BsIuhIUdK5/PP#;0y IX;O\{ G2ңk/$}1zKfd{3gPK >i PKjH2android/support/v4/app/SharedElementCallback.classWi`Tl/3y I Cb 4ҰhLKD dfZ[-mmjq@ .^RZZZ~罙dh=s~};p:ʇBÕ>8p |Xi\T65V y9rpofí.^2)dK} )/v˰Gwp{qs wP{d~}_4eaQeW'D'e@~-zFdg4<5xhGC] u&|45.iXtzjR(;eRBZjmM]S[sr~#6"݁VrtE^}4Hd *LNf__mrQn\۶4ςP$\ଜѮએv혥H#o3:A&iۍxHi+J(;s/Ɠ# `WC8$p6 UG(kl+\%5"„`2Д#l+n)::3)ӈ [I-)C;[A2d]vL `0R>𱮔rʩ\vVҰʭ+gnJS9 ]yOW&2z XaI]*OWj DZå2|*TEىSvUTו_MU*D5IW񖮦}Y`wX!2+DԾЛ,FJ`W^HVG)R %V Kav7U8s) F"!M'[5ul~c1QK8$2:{X;F@DO<ȄV`)9bI%z{ +W7ʑpHF&쌎]"mU8/ go("ۙ2mKZ(Kh"7"0K{t!V+4JMe]^=vŒ50e&Mt!fʦ ک4vi$.{B ſqB"ԙnt\3FI0onŶO k4YSHr64M|..Xh(:dRlI6yekRCRv<N}3:2 ᐆəOZ(Pz(,܁>օr&B*[jY8[Xf~ZXBK`B"\fVYiB-) 欕3'8¯uVV;wW  q:VXbld Qf0(mH;G}kW}g=w`^xVV)k)x9 In z yt'x^/bʤȟ}v c`\bUص }'J6="{P:O+vm*]\M5qWȯ ?p; dV e &0i@JR>~\{>bO8' Cx)dф0FKqx3$!0² 2[fKy;{{&W0F1UP^R>T.&r5T:T!B15bJpR7ݳ[R3IŰf0 3]]LSMt| eLL~?.ZCR8M#TIv!s]4;xׁI&1KPf6m1V\Oc/(PKTh۾ PKjH-android/support/v4/app/RemoteInput$Impl.classJAk"tzۛ $Ӹӄٙa~p|JM4xP҇.j?>@Y|NaJ>'δAhBƬ=60G-U gBo̓̆/c5.)&Pm MʙB/RDcݭukCA5ޓOԎĕs&r =tIwcL 8_PK86PKjH9android/support/v4/app/FragmentTransitionCompat21$1.classRn@=۸qcLKP%iXB*%j( oUٵ֛[ x(ĬH]:p(` ߘM՗w*:Ijrҍ D=M܊rÄwbs:"M"y˰qy*zgNN|{)5}9S9ˬZ>QBO5Z etHԺ + NDLQ<*6gwXm}{[qQ|'aV..KSV#T0APK 3PKjHFandroid/support/v4/app/TaskStackBuilder$TaskStackBuilderImplBase.classSNA=Ӗ]h+J(-TV'KI*4u`]w~/b4(mm"t /sܹsνw6-Ha)]`.uuXeж+#tϐl0Ӗx;>A8)=;<~̨w2d(wxxuIy;-RFt]l;< eܵOF}/PFoEXJl2B זntp][^3#A5ߜ;\j VϵѬbQUn @*|%[I$g}Oj0pf2Luxn鞯weL S4^N&y;2zy>ϓ&r s1{yq?XױOH/wCaQ#[Ae?|"Or|f֓GYI#d_ B4o-dp:yTs˓ged'VNǗh LӚ$`E$=/N׍i^?AVъxPJo>C`/q1Ȑ JIS+k*u, E*:EFLU\&[$OG }lC PKFPKjH6android/support/v4/app/FragmentStatePagerAdapter.classX xTof&o2yHjCaD0P46,-Kyt[RPkU*ڊ *Zj5ZŶv}32i_|nssop4l*T\ t3 'P*O+,×NbuoP?7Vk-xdRU>BQ􃱹0|1AdEh>+䔄gmrsF[iuO!xnMe3A).*ʣc*JIl;&y~d;M*[2m .J, E,TL_FDL_38*dgܸ%|;/J\?UOok ~s0q'D؁=uKشJBGJPg4 cxjϙ3>ZW;>E-+sLgfl9Ai\\79Uga[B̡;>mj|iݎ٭K#ýq mן.!9Be:,Q{iTo=~ؓFli4SAM{x(`sPs1a4 $l+EV즯qYt"A"j$.-Di&JS Qa)JxP-BfZ*v to2($bՂ_GlWd)J93/Х0c$6h\Jp.{W0c W)'9ڑqTK&ery&0R:|'4XV 9>cw6# FxH+Z\%L^ o̦NW1 odMLXkЄ1 7 nà%AK-9 aӱJ'r2R^#'`K*V tF"u3jsgTҢ&{2nPj&inI;ww)c9U[W)wK)>T,iB'f2і[0~Hvٙ1t6qT{S~zN+S*#MOJqXkyx)75H2qYʯȰ  s "1f?d|0PL}Mxi/應R(\=& Q"(Ӿ3W zق\ zk|Ǒb%ilwJt_зK eoZP+ h.7/a 4^,űH `YKk2IB[Q_=9+څ)@fW2-8G@&ēO,9^ϒ矧 ,K؀ٯ_e{:#śy?0b,x!$SMůUL5ҥEx^y=eWySlP/W%hFU%(}#9:g_~S?f怆+~ʃ*`t<$VW4*tj}o KsQPK5'ۀ} mPKjHSandroid/support/v4/app/FragmentManagerImpl$AnimateOnHWLayerIfNeededListener$1.classTo0vRV1~Ct"6*&*ubSyvzJ(qg!@ w(9$xhw)xP2(ຍ"nظ[n[`(LkM 53%CEOx'"i~GZ(08mEc+i*Rn+?8Nv\^‡PP'RIᠾP>Ca#A@J'x41o΅ E2=,sXVa4N<'Mе  'ϕDTîУwV8(a=-l9h`jP eC 4j91 ^.04O'C 9oo]kݝyf牔d,'A58#l1'N*tztSPmx$=R,'*]V.f`ΓuwO Hw܈X"">!L"? <;þTp)+4usUVڽB)dPKV)PKjH4android/support/v4/app/SuperNotCalledException.classP=O0(>*EHCbwVt0A&DN:6UHGhn,s&tJx[k)jbse&w\TO k"X{O^ƞoDŽݜ@((`P:0j/Њ?PK PKyjH4android/support/v4/app/ActivityOptionsCompatJB.classTkOA=S JA^",X|sh7tlJ?e3] —sΝ;~ `" aÄ&Ւ5`QbS}f: y)I: w + μp|0pRt(h.&rtNAGO?鰒V \RdJʢ u}BQ89"5q s&1b&DOM4DD#S+ ĝ]kmgӸ \p 6~4޸zmהLmb9Bz(ԱՂ(\;Eb䭠Mm!tO CW3ZuVIH]ѣ%+D} !l3@Nn>b@މWV&m"+LEEExa4a&8BsKJt1i␶., Y&O!Zҙ|ki!q[]<-s3>К7UHf&@bA&PKjH;android/support/v4/app/FragmentManager$BackStackEntry.classN@gZADDDԓ'!4AM/0nvKy|(1P0ofw?^l8#q۽+fcs\ h].h`x$MDNredLZLN#U*1hd8{>FDNi/6Sf\9;P{Iĸ?p^kFp:#aLܣE+zs啃il4  ;ef*EzaWiV~7Fe ű*mP`U;"Z+c_b(uP5Ku(fm>PK+s:0PKjH'android/support/v4/app/Fragment$1.classTn@=uc4}G)ui I uK .(i<$8Ć`B%`He{|9޹~X,F0o"RsX0peWMdP6QAk Q[eWnK {ݮDNݮVGoK%; @te!S]0ڐJl:"xw=7&vx {h+ `J0$VZ!sy_mHHP,+G+v!SfY!MML?HvD]e()tO)?X8eM;[=$Dj-t{ASlH-;t9Ug]5=?)na g,XzYw'-& $&H0  MI$#3ťZۺTpR-@%Xnmڽ{mj͛HP?{oyNsGKh%}N#2<&2|i̞I?=EO _ڗY?ͧde9 _ )7o;􂏾>~(G~$~,O>~F?Kre?^_WkK Bp,?_WO)ÿd^-#We^d  flp̊^/x+ǒC}1I 'F3F˾=W^a0SYg2:=6bET=?-TvRW *j^~3uwm zV?߽?t6Sg>P{_&M -i^Ū\uVuv:@:r@%$v:y]L'޿ňcRFL$dT}[&pe0#LxvGODRh]n7P<.jl2ҙTr~Hǣ\-ycg(,f`*nՙ:'0V7~S@OrPSQY[DWfK>kkN2 HCq#bw"ѝ2ef>lh^;DF81MGAZ1RD"3QD :vHCW3-oy+ }H-7nn37#@᳢nDZq34*#J3#0 bS5 b8 3FORX[N1ײA/}SɐeM˼‘!+pЪ.߽ZxqбТi!s%œSvʇ}D(4e! 5T >Xu&+L;S(t{H"3@Z5"aU %JFêMFb 16;#pbs l'¦\#5X gm)RÞHެK-w\l4ӾdBL4a6ݎ9Ih"cqSY49)D/͈ ˭5(Snr `No)[Ȯd'SHJ<750b]GUu/94VD%+Ǘғ$5F1.52lA"2l+ zy<Yq+54"+4LC]^\5OƵ<]r9r3ݢm"nָ@aUGj\ Uu.y5>a6uSnGOKi3Qb*d7FfcDƳyjԸ4'tuD@kmP}| <"^Ԙ+Xcʹ;a.&@фnd4LIoDsDSTehc'e+gj|ⱝ2Aiwq+l#)5r<{Nn$y׼JhR*&NJܝh ˴qj]P4B09`l2q2Nr:/}P$yx4:%m89l2Qzr3ަL 4?Z3t{`TX"q#z Ҡ)al:(6EB`H3RA@3yM[AꎯLcd@ɱN4zRqm7h""NX+N4[Ȋ>~S5U)#x+& sBVZ'4G0:@IV޴ITU+t\* nr9Ir'WoGḒ҄xhBU s˻ 8R8`Nla2_kd*orydxR@[c4NsZ~5oʯI0TDйTIi5!>ǺuX86X_Xr~c݀{F7:MZ__T_4EG[,znUNc\,Œa`!*8:D.C9tH9[.QʁB% ,]H^Ǣq[]gvN$=BE=T*_T x!~b%ty0 FՔ:@۩͔l2ZeQfE8O]\QIRlwE;Džo`KZȿj~mJ揑6*t MASZCAVAU=]LSZ"(!UbkgC S!5Ͼ# gjL!!v.*C2S^DEIpB.ӓevhPd;93:w w wQZ>~NQx3Ӱ Yg#Oc֛U лM(&+ xޏ"T8uC6?_6,g!H: > >Cbx1R029WZ?yJ\|PK76/ PKjH,android/support/v4/app/FragmentTabHost.classXi`\Wu,i$˒-[^xh4l&Gk,#Ɏe[Q ANq Axpq5Y~p-__J%xPt(Wt b>;_ X;  J b)yOs!D5\E|Koڿ=W jq:^b0~Qߺ?A?џ?gA/O?s^Th^RQ/6L5<<&ޠO~R2BK*V<%-+=2PʕMEPBURYDX, Ҁ, rVs"vtISv{nG0o@_k$SC4'z<=hdb=KgEބHShݩd>=ߟLe7RvoȐd:D;v+ irlĺ{sZ䀪Qg(ϒ*GwHV7&NؽNJI?-#Ďn4 L'D: eZ1jd cOAmlj `\Acvf2v-(K2MD24-@5n63ΕdԎS1.2'cYG1Gm^F62Tk ;Y6W(v 5xTOrGnN"͠sRv*z9'PaS8dL3S)aEO.xCj^pӟrM@"s1L$X2A+ gԏ=vpWE}cY3lV *ɂKE!/S>4QIB ndE;Qiy$y9nc2]k w OO&?Hg1=gR▝V12vfvFWL]>n(.(kG1 +y :== zX:%gwcDRl%{,To@E2wYs"QǽҪB5R1Hr MKQf[xSL&;i^ZFj,K޼n4DJNǧ K- sEۓ4g_x֩J~:l&Aa7dͲ* -Y#k-$yoIHjXoPYKR+eauCG},$bI OW-Y"B7PeDӫd$+ǝ;v܄޳Q_loHgjs{j3'Lu>j>WSTZЛ]`ڬ]WϺnIKnYiԗI 31/M&47qɀd4=m[n\OwNۡp6[ۦwgGΔ| ]CFS<GˎFtzپr/%M90+g XciB.7 ͵36LgkMW4^&`)'JbՌݺ_7vZmRtu,=^(?=Hvws։b!Vi5 ezNy|y:7?lP=w"=^&,R6vA6 s1Č y%gijW;6￀>VhO[& A!yz/b|/=eOoyq;p@£t{>9:q| Xsx=W*`fN}HߕE)ެD))ŕVJyHΞ$EgooG_c7Y]}*-/8#oE #x\v%N<xVF~8/'uNjW~s7+a rk%W(J;kG05FY82ʇQ1N+H(wz^.A0b,/ƊHO)^ 7d;Tg2au7{FQl3eiꇱ^]6~6c6_r| zw4r}Ȭ2M&VEnøfdͼbT2~{pov;4 cSG0n Yԍ`v?w^U:Y[ńv ;x!Ǝ\qw`wpWƫ:Su7p[,ߦa.=т(~#OX3c~j9k04X e(! ʁ&_ y =|$6c i';.i6)hѣ3جU*%gfufqVek㨊\Ekk%n8q>Lhn\AYą:î9@_D -b`xE|8 ~"+%J)NKHzY7bE6YwR|@bːo ?3YD@\O|XbIR?eeYy%Oȋ_FN̥S@a^[#T%MʹhEЧ̙t񎠥ez1Fkq0/^~ TjPɫkrϋ&R%SڞS[Y{az GapK=GlBqiĨggcɕk]M6M+Lbp,6\A~jDぱBKlF@ܭ(mX$;BnCHn)SJgfH19jx'<^^>xqxkפͻٷЗK:[s3ï zs>suW"aQ%:GџvU15/N5]ꮠXW8O%ܥEJRgRqX:spsфIcXH w#1_`E:$*.-8.L6!$N8'GV9I'܋g6x7lD>f&Cβ~ Od ?KBLn-&>3r_ehfs.~7eöENw(n>*N~evƂ_PKs*s"PKjHWandroid/support/v4/app/ActivityCompatApi23$RequestPermissionsRequestCodeValidator.classJ@mcbc7Ju@ @_LIgƙIWsPbnܔŽ9@ N7(Jw:mMSJ[ !7ɶnz|QEVڝ8/nq)MUh>Nsbۼųpuq2F|pgjؼVH9/%FIpS1x2L7PKțaPKjHEandroid/support/v4/app/ActivityCompat21$SharedElementCallback21.classTKo1ۆ@KB  U*!*7]+q؋ gqBG!HMEaoz1Q`HOh-XgՔmXxK&7-eO*CCc#T=޶F^01h$G!qY_T!MNę씉W(\EWguO9mkcnbvI_4ٳU`ܗABa) 5^tߢ DFyMxf[ ib9g~3? LFЇ{$K2!ɤ$SQ$0-wz1"bm w!o3vI0te KrA8[`'kC gTFrkEfsݰvSp/1hNժ ,JmW؎ROgCfICuYn5R?sECީ'L;P٬@= a28.`B;./yysb͐w:k5tSqpA4W42TqY- 0;^.%+kqrH`d8aH7i_c[?& fu,jN%[LKb 2XMS:H T ]̒wfi$U֏|F00Oo( ĭ,a;jk&YT>" c3#~Qpl 4[$ ~nV:e_PKqH-3PKjH<android/support/v4/app/Fragment$InstantiationException.classQMK1}Z~\TU܃"BKPX<{ md7dw˓'WB23/7o&o/ΰ]B +d|PD9P U`-}]rAPKq8PePKjH4android/support/v4/app/ActionBarDrawerToggle$1.classM 0hZ vʅKiB\bjKHJֻJu  u#I&\ hTV+n6߄9,".x íJǀseMŚe~&3^)a֒[+,!*cK!0‘k]}wE7PK%ʲPKyjH;android/support/v4/app/NotificationBuilderWithActions.class=N1F K.P"qA(@zvŶloGr(]A!L1yof6_.q"PrVLY#B˄:*QG[Q2޻z"{Y[m=)G}gWRߙJ~TJW+^3~˄V74_2 &y0IڢѤ{f!2 [PK<PKjHPandroid/support/v4/app/NotificationCompat$CarExtender$UnreadConversation$1.classV[OA=nP*7+x `@T4$ħiw%씈 }DGۥ R.;3o̥`bDp 1DĨ1C#!>[g0 -:+JI5^ri蕹Εkr mh!mKR Upy|_J%]P׎'P,ri+ϱΔ}Z)Gs=.;1ǐ(U61Et^ŒЫ6妵#7N]繟 Dn34N(ܒ$n~:riP-|31HU/lЎ*ỻJ ]!Ci+KM;\EDMel`؍KzUU NxzzyIyYv>z˳hEaYGaϊIqw{:}Ve}N6S f an 39 w ބfyj!F$ƩKZ;A6Bf@ 4bz Ў †DǺ!3E|K$`?k#H4Ⱦ+D}Ϡ#DWp55BF]"B70dc⑭!!|G$3 -ܦшw3I>@'JCъALxPKZ۴NPKjHSandroid/support/v4/app/FragmentManagerImpl$AnimateOnHWLayerIfNeededListener$2.classT]o0=^Jt+PF] !j:TĴ'JҢ,$~B_4 )~؟!pW,qB ,\ 57Ml0L-u%Cz'":+L(0Ec;i*R˕DwqGILv~‡PY+>$0B%6cB v UW*q0"9⃀,U7x󩱠"PF2V$"'ؗе  '{ TaWdȷ6a(c4LlhbjP M :2|Θ x4J~:H)as)^;ת3+mq)uu0Xp'IO*qvƜf"ANb*83 :\,F׾HkU*U5XCsm&;a5pS]Z Dabix6Py dByh,tgI[{BȿPK+pPKjH?android/support/v4/app/NotificationCompat$Action$Extender.classNA RSHh 'X]|6 " 0̟|dkZCO/u%hBktL.lP,PތNQ) k|Z>1^U"*~ {sp|7K$]R^@u[}'ەτ^l)pIלxɄј@ GhUCsO 83Tׁp7PKsPKjH-android/support/v4/app/ActivityCompat$1.classT[SPN6@/ܴ-(܄ʥڂ O)$&)3K|q!ꃸ'ACv9~}>0r )-)n'ъ$I}SH`ZŌYs r jzi kE%黦L3hҏ뾹oΗums7'аO7\Uk0"C|O2!XR(XUw ߱Yunmqה TnQ-X djc~~Ήlp15A2#3*.,"ħK^1\t$m;N[ՀBH;(53mUN@25*2$7횫˦du̸D'%[Ge߳wU,hXDAC5t :5tW# KXְUʡ gk޺بism+p! ;ixÍ3N!l Cu6M5!N`G0|B(1r }f8W1KdϴtuS`{Y= rVd<\;Vbm⋛1DS)i@Z 䕦9VHwFrz}>>O2y k{pjxv%Y"9D\>D'cXZy#ب4cwߥW$HEϑOhQ \G ]D=B-"ˊ"=xփb稠**Qe'PFQ):?e$)B\%D!↊$QMiȓ?(6B@)APK)\4ZPKjH3android/support/v4/app/RemoteInputCompatApi20.classVKSGFZ$w$^I&A@ 2eN1䵒X,v$wr%Wr)WߓP 83-W"+Y2-s护1jqEt^SMgT`z!nK%r;cqT/iۦRI%ՙ)# fǬ*$ ӆYɢjۚ- )Ne0։SUmmv>Y;䷭@'>'MrƲT"͇xmr2hgkQԒ{$ -sAҀ1qv74ǵ"i <͛.^]JDL;>[6 E6vy PA xŪ/l+*UkOҥYl嵔׺RwT%5"'# 26)Cǖ@G!.{ ̖bAnMhd-À)2,Ñ&d|2PJQ8όE؈miyjG;gF)5Cۭ5i%XKFڕHqֱtc1YeԜVvErSf3BvMSktziA3Ţ4-eZ۵Џ̪@Vu,t(/YSOʫzZwIIlGT|T&/4wp9\O*Nî_e4CuSym&*Vz4?{oRU\P> ORfSImiqJm Rh{s|XOGHt"0T!CAh"/~=w_% Hme,}acdc#za0H>0Mۆ>F Ҷa coR6skh^P?nj&}_~8TAS4Vp t4]q#vu (e7vBEH'V̍.B8GmiGݺa}H0N&)bRgxkD_qr( 3tg^Ӯ+UK|c WEh}?N]~*!z_`f_DPKV3T4 PKjH,android/support/v4/app/FragmentManager.classUrE=8q'6\A6p.De*ieivkwV *(zk]l@˜>3 `+$x ;SxBaJi)# Pgڊd˃@) -e;" +UãH[ Siz7!룪 G;D9v4,;m$}#]0KyHSVn|7v;nִ3wB_ܾx]9/' 0XP9]Q8㋒qC!O5#4 tE Ne׊:tCvס/ <>1a,/N,yڂX"3&rC@>'EF {9C}l%}i%]wPNg ThVD+Ttsȗ:Q r1\;5kx"nPO7 ˫E&)2Nj>' q  HPKQ+j PKjH1android/support/v4/app/RemoteInput$ImplBase.classKo@А(oSZ !J,YRx ]ˏ =8Hp/?FЊp<~3;gk*Ml(d=?f( rei2pdirNXxjDd-!x;QsvJ5$d˘["Hivau=%~rl0g(3CFf/bj^vZqݢԾ^-q4 ͤC\B50oaSj}CWј˽{L* Ksv R iƓa {lhðwI M#F!Rc1MHb7m9b`oӲ8Ĩm1"*=72}46i$j)%,Zˌ_Ӫ8Ish=.Ԛf#*/A<ߨr!,(լyS!Y g:.b-kKv %\8WLIZ8K,PKUFPKjH-android/support/v4/app/BackStackState$1.classkkAe7ƴXE݊~B iҭ۝0菒 ҂?%,$A3gϾ3sfNc<(w HZ5=3nf`\ ҵC-ba_Qԕr{̄nB=2.J;FRFs|셓!G EG w<|͕#iƮ՗{Ϲs=f_`U 2L6= }/aʎx(GbJ _o8 עT<Lm4ahVV*Z1=Mݢv֌fq1z |a9%zf9(X^j(  dZ(Wm~XTeӲSq\7ίCeغ]$'s5^Txf_6gUȋ,(kMaCl|^!tf. nM[U!cʪVZ\<\ ^avz 6:c褃9֫]/a$gjwN%MN%-nw"]b!jV>xJj瘤7YG}"DVMt<&)8 O56ZڬhpVm_sHj>U*S 7wDp2 ?00b0$o/Y痬K+I ( r%8.s\y7VȤDK=rf8BzEbr-ep[ktvp >twK[4F+Dpk fM R-lQ"HYKd|KF՗ۮd;ܥ=Cn5dO8ҩl=}l:AP'h$YQy4Φ!:k0ɺ@3(3U|@8PKj{ PKjHJandroid/support/v4/app/NotificationCompat$NotificationCompatImplBase.classW[wUNvM[iEVE@d0<p-\|\Kג/rߙiiss? vUT-UVŻp1|!A:isJc(q] 4n[rđV^KP`05',ǘ11,3m+ɖ+1Yx4u;]ժεqil(nbNjXJV*ᰣӮQ EmL5u[]R::Ztң+Md i@^h-r[%q[Ɯ#/ˊk @;ı% ^pUMf&L^"\U\A.h=> H,utW3Uv^/)gm97nT=9+ScR(Tj* v,Ĺ|678YFO)3t\Kga?]<r"Wio~Ȭz%-hs ㊙AYL0SfiIJ9m˭0ʹT7p.G#/g^i벐YݮJ?!ZX>.pWABΐ[;|B幯LC'àhU=WY:,gC 8`3FPh s0QBu0_bn'{̼x9ee uR\mX%VGq<GN*f񡆇q8>0/8>QgM#y!'y/׊"lhˏm?<Ž%#x&d \X3p[V\+/hwD>v'L'LWtʌ &}W9yRwJ=)ד?IdK,\. *̄p+>c)۰lá;gJgFbf n%cˏ'{J;~ lf٥v_Κg_p4@=Ed"ɲ__'M 7"X E:<5; :ů-~ÀxS{ЇԪ^󨤰Hk1@9 ^AP|uD%,1O%|2bt|& 0Nh/ߟ]ZI oVhYQwx#pcl굕xBO(['f3GM|w o5$*x68 ۚ`G_42M1`v@EцuWSyW 9\׻<OilcFd.ֻ>YhkRW (2l~fPK cGPKjHLandroid/support/v4/app/NotificationManagerCompat$ServiceConnectedEvent.classRJ1=G[BŅEQu.ә)m2̤/"(񦊸PD s9 yy}zEŔuf=1Dh%i`XsZavX cTH&>: O߻ܗJ/˾\15B]*v"6uJuO?H{25zR<N>InԄ'IFȈWH-7.FXZK?ox1`^!=,0e\݆0!ʀcXʊV/EFc|@5!r Gr%Zg SFީ`ԩb+cd%Y,C621>!PK`>xPKjHNandroid/support/v4/app/NotificationCompat$CarExtender$UnreadConversation.classVrE=#-K;bHm $E[,'`DẐR U@ |͖wf9*1܏cClJ%MEOd*JQ35dY8w59 |45b<4M4ʖy9-ꖩA]oMZHߊcQj Ts.:Dr= M$ftœtzUqn? _/n7澖T(qB0P<0=:Ir`WIZݶRA!BE4WFjGcԛ:;QN]ХvcY1u)8s3xcZYvr\ui.w.3lVE =KoʼnaԦ}>x+wP*3~[1,Ca|œ%ѷXEFIƞO&N|搣OyY#R_dw0C7p3/?+[[=GPJ_ =>JC+K*%2@jw]PG 6ؑQ kXst_>G7$yM.QWLx]>Rpj,W,FJ).>=%PKDD PKyjH8android/support/v4/app/NotificationCompatJellybean.classZ |TW?'M& H@,%$ d$@q<´83aQjjZuAPVQVuZVmKE{~ywy{,ȿ8FDo>_4.GfnVyl$Pۨquƛ[2om3ry\!+}GګDPj]2[=…c ۄRmw"ՒM @0= &bPŔW%ZZhduuaK$H꜒5H( ɳ6~rBeˈu†7c!i[[Ģ@4ܵ2_Mv h_0v v{퉽BPLK(Ӫ"Ec5NwckHQ-ՋTw1]| AʮP/O4tKJ(lz nc{~͚2bʈh#fu4%΁bV`~FlP n1HGՑ\7W %_lMlʼnSdAnflstLXck\  @"ł{B2hOBOZĎ8jTcˆZ3d\:ߕv6'540`d۟nH%*Hۙ? SdQ3[<<5Z5I*)mAىm2 |/p@c8læ6M"Ǡh@NE9$#"l8?75d$3}iN𖬿 E==&H@B2>'1w5QbR.֤X1kSh< 6fS㢞yk;LFV:c'9CjHy L4f;l7C54uw,0Lk#= IN^Ubޔ63}RJ~.8p 85!VBoFؙcőFh!q{#&1(6:n?ӝ8 %M؃8Q h2fvų؈đ{&✳mdV.PjqzPǢ}n#,ɋ0=pM^qt)-3n47ƽ||Haӳ0ɶщ͙QX 9^3, [GcI;k y)N1`,3'lDJ4BjDqM2_>bwԱ:qF{ϲU!z1!@?JP\M-| hxWt&_{tXԇǸ/+s.$w=:݋a ^fޫ>>t9red<G9C1lqɮi ?tieģgN :t#>_?!>Ο<$T6_g|SCN.Ex,EE L.5q>|Td_cPHL%"EhQP+8/224ThI?DXtY: >Ώ ~LbM't.O6q:7]su>@M:6vђ?eO藰QvaųFoj ?qOt ?$:?Ϳ]+ _~N˿E6jda#JAHOgɐ;:sdZ/_o@|D09DRMXtyYVOb_7`Z}y LlBuhs6{(:Nryj)?WDnw~WdDG|3 6eѹx<쌸mlw)sv-J|ODSZG>wPI{A~ѕJ2@/E90"]bt1sџ+5vD%>|Dc`p8sGdM^\qaOAr _ ܀ ؋\c8kr !q6Lv<v&0]1UksA9;7LwܨOg~Ȕnn~σ_CC93YAyH=7>'omg'Ff!$9Cc IF\) ILHDGgsЩKݨP:b.Û3Hwc|D%5ʇh%gTäވbиok45ﴽO)9F0 H;d|uw$txS e:2 \9bq#dw3d tn LuxJMѰ#\Dlڐ]TjT,낼/ǥ"GDSO1S4ChE{ݎ8!:WZG;tވv9h.Fh_O7;U. 9ߢߊ[qo۬v|;|(JFQz?gwix@5D̢;.]N(KR#t?y);Ϩ$mOW@*҅tZgIއwj%OqUwE*:}^StFc_ /ʪ>3Zϼ?c-i LOp0=]11,II`cygm6U6U6U6Uq >v2}4%P9}ƇA,$%QQhKrO"Ug3dKm C_ i liӣ䜅ui2 }j Ht̖CFt>iooooc VY Y x'OS4K(S/7M@=h+WpSJ1iUMKjs NRQ~TV(ޘO?QvІ`.h}S.l O@ГTATM*(cB!f{h)lJBq] _"lD=l{>yM}a=e+=F4mҭh֦/Ywe'id*+ '|.hrޜCta%cD0ƤAOTҍeیC4~0Hj);^ 8̥WN**=T-e&娐 SڧUN+Cg_0&z 3f(=IGc'Q-%]_oڟV[p<"Vٰo!l464+[B=NAm=w+J\4K҄>ziC!&~ՠ,"ԇ!8^ [3NP?#oU  ]IGEҿw?Ԥ"!IS)HHb"xcj'0Cf/*EYҲ-b4/^ՌҔ- Ҋa m9&q:dϠ**!LgbLY)Ug0w@`2w;ފ@3J8Ej.?Acɓ^viipi4C\YL@)5N IXwH9g4l#LFy&ӡ4gRϢ\L<|]r)]e !\D?/~^*ekPQkI Yomу♊BU|yx+ڎw9+"~L]H!涄}HHղa%0*uDtGUXx/qUP$γ*$TTTT{9H W]]2-)KG.p`R$Y<DIаD(un-=v?**R7y!LWp(H- i d@ Ey}(AC\ҰQΆKz6 a Rq zU=LϦPAx̒ϰp&ηHb+j*+Ze9PKjH2android/support/v4/app/FragmentManagerImpl$2.classSn@=\L\CCi8moE )"IVk/| ҎggΌ~ج\ܨ*nz5u]qqjaXdE)mgYy<pc{,ݡ'LS(X$}P`X'R*&CaJgm ~OJ)sAQ%]HB^bKuO85RR'2>H]|CG %6BltP8z=<#C2Co )?-)l> d_D/,tHi$Xg8I O ͠ʣY8/m2 SNC4IЬ;Shv{͛r~? S>wfC LAn N JA8=l`)A> N>?Eit?I9gss)ePib:}/:`$BWj*jz5k}:bA7Rt~j-Txou~$wQ{u~/}>~?!>fw Q(xPƟS: a{ǟQ</PH?% ^ ~F?U ^u ޠoR JEBxw(.}~@8~I~E_S!Q*->) >'"g +%F>; Rs ET>h!`|E> }GQ{tu!tᡔWYt"[>J o.tA*E.t1XCtQ(E)E.b.Fb.=Zck1V;^t1Q唪E.(Z]LBfE.AiZL*$NMY120ULẘ.fR,ʜM]A9GR^s~%b>}PB-X#Sl ŖRlŚ(b+(bk*&k(:JѺ8​ UبVi lE&:hj#!MlōX<P$<{k" p8m b&Z[m-@<dd@7p[4jtwGڭu=PgmK3Xv`͙ج%{HeZ95P׶ģpL[1r9Hxu /_tՆUM-71(Xqs5>l2` r"H:{ /54ϯ_5%A 4ZϱZ˪+VI!ͪԃ-I/.%˼p  ^-A%5 fM`j,ڻḢ$ uDYzìzػј)v>5m,;hk3j 3t툤 ^ *SW}P0]`h7tw&أ1SU/*<'7Dڂ-؆#59*ӵ ZSIc8o(*h݂"j҄堮ep=eP و]"1&S3܈c!] ζ>w1#Q`rgHP@1%=(v&L "q>rOUMs2s$zע@lY0CT& 9BJ$㤙AI_u^tKPKsc&rצ`WǨD(Dlu-ڂQK$RO Cjpu NHAyP0ηU@82-c 'ws VtDWKY R#S33Y|k0Iv6F"@9nrtg5*q= 6MW:$5 u)>(XhN#M=]T¥@@4DiwP䄬 3N+j%C%T hfsi)n 3p≨s&CceMa3S+/J8X^Ƀ8uvt'jʌ˪j&šh['h`\sB 6Sm[yH\b^*m4#RP*2(j.ųmXQ c&lǬ5k|yc':ސY?[zb(&9Z.}NdjHy_'*0\@Üt =11+p[yl#Mg\љC ¯\_ dӶ)!.2CR8h2v7ձi\)2lo(ۂq@DrAԫ)GHA{_c2[-S<f4$TR_CCoTVe5QGItXRCn"E)҃ h<8mj*3MF;\RZU2d!)քf͒p6N(TҖF72E\=M(in֤/YΪ.-4)ӽB1B# aTù&Ӧb '0riە&&ˏͺ8>v)_O:աHqzYQa,j J-N9]vHXSo*B63>^yKIX|*뻊4ʏEY!{Z*KhnAߕƺ4V9(NtcDycci犃]GaGgw $ҷH: R؞g3tgf" BA4*P7. X-6"K5JM-nt$6I̕k ,Ζ%hTm+Im"돮=/UT aad+Imi_WE.GBȱ$K@%XX;̼BfکN}-Ԇ*)H Vl0 6` 6k0(h`1K(XJ `ch=|&†&kb!N'dq!Ne>gP|C&ND!gJ^e:/gphtC"a3xogwhJtnǕ4V=;C~`?ohO+#O76qN1t`sy v$+@\h8p:aqD3ն/As0+5X #PX&6.vnpgEZ n 6v&3NM~ljVcC0+pjz n>}()!vQp+6č&CL2F f4~\Pbq+F55b; Ɉ[ǭ66` (6{w5! q7!~C+h>C/0'S}ξăx?L|J hL Fe|s-k^`Y! &MAfsWkh( z5bzg6pyՇRWA_I`؍|o}_i%Lf6CD9VOǡ qVԇt {yb*fq}GCKY FQ:v(ܧyId 'KYk=S.Ƥ{Wc p#5krɳ|O2HOެ77<ȉG'f W癸C87#}uD:*H9S&lh6 "VE }qꫛƹHgˋ=H\FL[6JfN |60\FHUZ~`gZo6ɇ+2W~^g5Udg@ER7ƣ^}n}K3/!:'s U-^B I|[춈uEzS\.94Hү o9nufTGh;*hۉ}tvR2*dKLOƚ&$gML{cwS=N=l]Z;;"WYIV04&`2\d7HsxPj12GbtiMenGeB#2gƥCc&TZ^qqoL")um$-y\ԒЃa>m4{,T(6帺 1\5WC降. _0ѩ3wБ5KX0_٘96l\LdK`Ж.sˢ!X~t +a5#TH6J~h6F~Ǫq;M߉[[U~5IV~QɊUo)C0 lp֫zsշ͓*@}eF]Kw.cM\}W,[* Wc~ߥP`b/JJ^RUV0{!>3CqÌR<䈮pcg!y!+U0M1l T046 fC̀l&,agH0(eul>hUl? z6db ;CBر8L:#!$ zi(jyT`Li^(^Чd .Bi/ 녲RO^^y\wFѽ0 )K8 za|>j@? 0B~7Dc V`4*ٝ(PHv4fE$Zdd 5vB;P\sMyv5"ѥiW>\ I j?"^EUQkwN(*<"=g?Iqs%22^hXZ7AKVB. g(oPp2`"&!iA={`9Q\ \MعD_Z"ED!"^"v%"{z:K\{5'Wj3[W8,x ׄ |6vZ0TG]Zُ\wB vj|Z GaA"'~&  |]Q, 8Iۢ+8]`$O#Mk'}<*˛ǗwicLƮQdײ\DjWWzuwUŪ1%/iݍF{airZmlVYݨ.SttsM˞&'NbF v3%#N!ߊtoc+]?TqIw,^iEbaB4 +ݚp0j~{2;еVV8h.olK]rCC.F;R5F7ɝ*'+Nrz.RkOEdͶYL2v/ۃ-l _W8:)?v q GaJ=\ váY9w8Z9GB*U sOr'܉hr>(a ǡOp y?cڦYQP W +]w86~̵x8;O(ay`'lǞH)lTc* $m0.8[aab22$u0C\9x+O[pK.7 ԓSObűQ y+CXcD X X:YmJ,}.}' jAEGA8QɞKe킡8pEކrI*3c/t9l"щ_DFtۀLLb/X@A-rLt,nsOU+VbA@^а,: {!,X˝84q 0U 9Tkf0ph^ S/DG>%W|\"ߴOQeG 朚 gD]6a`@W LcJ s^c[>G+#q'hmt?OW9}C~qacd}~u$uJB"U)I~a iO:k;[CF-;]%stgR<^`G {_yR}Ɵ__)8ug2k!^a[koseЧ-r F{2զGc?XtM9׃ (̳y0P t{?Tdjlz 0S Rr'8XVlXL؟؟թ$Pf bn-a(!flF%!; sMY!y&cγߥ&O9,)!]eb!-F_“!]s \ =? Kmc5т8Qɧ,Cy,>g_(Y̱Ndqv3ɞ+fm=c_&anQjq%.}WjO\C4]kfGz/TP%CN!W0ȡc޳,qΊY.>(vWɗ~ʎ y(ؿbߜ +~aͺJ9bG&&^.c]0%A9AýpJrFNlrNܛ%A0[E0;ITkr."-ujκm`k ['a['ѳiE@3sy?@'`p)k]5H @f+ǒyrڟY^aN`q/\u;\'39U^7׿2˫:R=^~`/\@ބ1<>L5f^m .ޤy@Nǵ(?Is8Cն״S#,aqg|c|1#-K&:r y߰iD kA#pH<1 ߨ*s?azwԃ,.S!W;kg,ieQ|uJ|zhVQx=*gUp^F^A~6U(^:2Tio"co#c:=8^{u痶iёP.x;946쏷_8yS%H%@ r;cO?#WA Ffc$ Cc9B %Gz? s4bc!j 1O=Zo_ 'Iw=<ڗhB4/DGclX4v]u씢ϜJz^u&fI/S><3uֳaR=F0Aσ)zmYYHu~," 7(;ۺ)b ҄\`ByfӸ|`"旧<#H } 'adxR%L&&\+\={L_@nd{c_ɰ]ţA@Y }^o_9mkx=щ皨d0tjMi&'['sz\uD&[]Tj_5]@Cv_z{Q.: fۺt6t[уNVֱT Vs:xĶ,rЇdwΠ]VR>)͓y8K\UREs.mE>1{1},P`),]d-JW(c)t7?2PK]8PKjH-android/support/v4/app/ActivityCompat22.classN10rw4AwCh}*̤J7|($^M{.R((BH@SBVRtSFF:/G]it/ĔB# u_LQ+fBF8iǑf({10YF~N F3*"\7r&=l `׃* 1-F2PPZgP>Hd 8)V_@Or\ ˫9Y7Wg$x yy'PKh,PKjHQandroid/support/v4/app/FragmentManagerImpl$AnimateOnHWLayerIfNeededListener.classSUǿ' Y.RR !lJ7 jJXlJ}$+ݺd6/ࣿG`[m/:8㿢KT&ysssn~F>^1EO*8Ex E QDqAvK <`0:o%卜xp^Z1Q̙Uװ .l,QUҪcnuo1lNEli=t7oaVYŵSEBWiG{`9Y6s27N-/gJi8ϋMGr傰օc~s0^3"~jR);^E/:bdT՛Bu- ccz(,**BVl,^yʊmߍ0]/#,,Kvݬy.K.}r_Z_U\,ؖ5b拊BVLN)/לhJMuQ*Tǐ_:șiT<5g0 3*1bFFȬ9Y,y(1zf}., D{__-a|K^pX)W|Hv ̼o8'F'? " gYl~' 1k4lQzʙf y!zzЇ8I4}S7ڈ4ƘCHpqn"7$L(HIW8DCl'qJs&H9?gmIj>:d-+!ZA%Sj7A;HjҦoKD- ʑo~| w#M"&߆Bwxw15=8"N1h1Md6rDu^szQ;*VU."Cs:; WVn-H݆><#2E?}4K&2~Է9j2p 2+7V|gGV]RʾzO?YB/"אK+B ǐcaGDr9PK ,zPKsjH4android/support/v4/app/ActivityCompatHoneycomb.classn@1KHi 5R BBEEHv= S5XlĂg\,X`3goᾏ1,XƊ[V=yX'TD'rcP9Б$LT"_eݶ4oD;ftK">FsGxId^ؠ4 V=?T:9E"aajNz/e+(NQ\QuS֙ I'8F%£t\FV54wjjt;tFa3{S̄Q #5Tyبa^ S$ۏ#LJ8lfM?UT"/wlc_A_(fZ -q.{W bw`3.dtx^Warߔu7redAq fs9>`!Gw&qPK3PKjH;android/support/v4/app/NotificationManagerCompat$Impl.class1O0ېP 11 AĐH"~$&rmq( {ӻ+sDFg8HU\j'DENhp5uJƤRpMgTjY?0$Fۘ:Q])j?R\^ UO$5-C4.R/l'Br_r7BpkhŦI17!X˰5l.m/|s8c8+ z^4;=lv=O0`7|PK PKjH>android/support/v4/app/NotificationCompat$Action$Builder.classWsU6m)Xh)PZDa0*Md7l6Ctu_G3Ql(6{ٻϟpI\ pIC7.хIxx_ ?¤k4`- lF+NcDgSbB"f$,y`!ql,/o H F2oعNwʦ1Gd!e{ t ;:V6iɋ\;}s֪SJl.3Svֶ dٳQ5 OZ9.5ROx|\,:\R9AB-N֊_B_;YcKڲŒN3h+k,,4,NmIrl峦+0VϚ2w ERXK/UJ5lU2|w6_܆r[C+Ba#]i zڛ`ۤgdnOEn魠moG"ľE%B!yt:(ȡ*do5j~, S|}&I"/КxJ3ZiVDxO<.F].xI١ R,ċ eO(Nb星m ^iB"1U kx)9{DZ 䉯g :ao="k@KMUC,aj'_ҘVuRbW\:A_X.™P~f>OhF{_PKT( PKjH+android/support/v4/app/BackStackState.classX xTo&3o2yYH0AXceĆdQdap6g&֥Ժk]@ 3qnjv{kdBQ=ܳKǞyl5ҁ% 3pkf6jqpwܝYh]}Mv@d؝B~AzDCG{TG+^GDŽ{L}q np; Ab2ijpOe OkxFfz5<4w5+ Maqp{6ogegیmQTmv[s@AE'h G]Jh,R~#m v; 眉ۺ^_um8DyUV+v\y= zh.#Kkmkn7 +ot^M<Si#öLVò 84s s~;d:aYbU*QeC3`%KlLHx;5Otji%pwIag}4&o U"Ffΰè Yr {F[0t!k0k*J& :}ɚ&;]R*tAsp_bѡWإ"uawV?H|>3K~[J4M+pu WD.h.ӱN? 2EL`ulnpn@;ҿ:7G)+CaDG:J@=.<%qLL+rMD>C1\z9VL]YABWĈfESv]e)MWUpykTFauҙ:6mӯ4wl1:Yδm\a짶wyemku<08=Y^i La|ڲo*{_q4µ/n7y VC+"-|䊆iBne4f*EY|ph7է9TC!/zݲږ +W7oHGڥᠿϒiz:?ÉS(].OTtT ?KbIU3ai~ND&4p8eYXyMӎdAX Ǥqdn\Hs nkK6>Ad9ZS8L L*9\q3sgaD)Dn23 H#H 3$VfKisZwcg,N[#W7ﴩo 7Ҝtt8a1 -aГb(:6qB:!H'P*tr SNl?oAq#?٘lF(w aa![SY+1q5c B=Xp:ph8 aOD5+U1Zr>UT+F攛eXFsq.9T ˆppQtlj=r5j5vB|eK8]p FP? MG%p1g1p"cZ> fVWxc_*2r 3y&-Zi&ϔKt\TU>i8fKeLIdfɬAdzِB²,e9B0\.J >Q J+ ;Y,`bƚCܲ+nyb(1`n/j|r8*y83XPyd:8=SWTvI3O+k1 ت 2o?@1@*Esjw#PlEvc;h7HD]ԔŴ/Ίcvbbkzϱ}T NE1:&b%1T]q1aЉ1LZ$Sb,,UҪ'R`}b69̏&\tf BR7a۰0q\H_q1^gѿyUv(.Usq: B5JՌT Vk Fm*y^]Q]ԭY݃[TnU{qY!Zs)]N.?֚6An6j6)Kgd_A􅟠UHc rս* &~ vuDQLU֏٘1}v%^eMfe:'V(\k'_uR'q.tT˄%8$qJS8C&&&&JSS202fIލ"agAc&l0&z:\g77:.M2;݋FBdhozǸ&ӬJ:4lG+&Z+?PK% PKjHOandroid/support/v4/app/NotificationCompat$NotificationCompatImplJellybean.classW{UtIYEEPԖPD@5PJ%@9t.qwToxx?~l4$)I_x/of;~/,ߕG0.l>f4f2`f2%]f0smgNfn.f&n"Rne'Y-q7p˻%l`yev%< A%KjjʣV*Pզj{QNGImVBѻ[Yk9햫mYf23JUzTcQ]q V)fmi'LZپ0$wJxX@jaU%%BpB,'Ғ2{u3A_ȴB:7'R+04}CʚF"vH&(t0hsV2:;\,Ͷ i_Yv]\MMJ OY`HU\K2Wi8qTܔMƉ.-™yll?ќ<ɕ ]PIShdʊkL}`-_a[$-y`fqigObd,NaRRUie20W£Kgd[Ik :5P^gZ-4@ t>\64:(6*cFm˴ -mYp㩞tJ&C7r† yΤ\Dc6EXVwoVK+z'Sbݱ|@sDv/Fp֞p+}%G ^s)r_UwtUT~8~=aS,L_m|XE=8I(Rkssڍ~fdqe-=q|"lߞ\>]13r%N䈎?Z͜10cތ.[hcWC$Eu,ȀڋHgG,K  ,,=ǒv  \Zju/P_̔:ԃ&:EH 8@J+3 H0toTt>Ehc!# *&DN!":hiDM&B4ho ALcy 5ƌI !Ԏ͡NAuy礡Y[˼P,d/C3ʨ~PH8A?+#!;WF5|364|[A,e (|[<"R?*<&rG4ߓ@J#!~"?c? _J+i~76."9`^dlgGfiƵps.U~QtfT }'d&iDFm'z 6^K1ћ6Rۍ|R΢M閔/rټ?>frJ5HX\fzaωLP7;f3R|6c.;wZVUՁEXpXN*ƘP0D\ɰɆL+4AT֨W-eXT|6kUq=VZڴd*n(^4e@q*%/Zkȅ)cs>JEhmƕEbGt>lF\xUYç N)e I+e$Y@:`Z8%]A)N3JQ#Q>X޽)V첌oa/y#Uֲ`EKjYvl"]Q-lg}8͚=vgiÖ&sƎ02M [ɐA3S˛v{Jw/ώ IIM!q!IZ,IUū:kt!\fC~p2q΅u31]qȘ!,bjiN>ag3wJ6IM:V!Stإ#FufD$.qa t\S=5yuC:>u|BI\|T1R*ohپ12K+56(鰓3ÒM:5:tQNsiN)QDm=CL 5tj66˔׆L%:m}:͒G9T%f f8OY<)KMK[$u)g [%ray鴀h  ggcBUBSFӌ$$Iݜ}Ǔ^B+r1'ȬΊ%YjȖ|dX ۓP2y1o"8;hycJb%V*5xܩ@ ievdJ:Pw8E[ǩ֮6@6DžWf9y37{TjK"8ZQMڈdH'L"g dUurʏMULZ,Yz3ÁrsVӣX2e(ռXK֧ēf7t|e\K"H0:bȅ.޹"tW4Ro ~pYZwjؕr,uu*_vO3yA烮y#wmh'!z.sS=5siSngrz.3rm#g  # E38 |<54d\;J:۹v#aVk0ƫq|_6?gEu@ &QYs j877hE]SG@ SQIBv_n|y" ՄykA hn+b,86T.P j^. K8.)nJf:w"j;=p`MM͹M7 gYx7++YŸo]x%xox{sb&5z{x ,S|ml&x;fmN$gΗBc).aw $ˮV2_KΨ^''Z!}XEGuJH/+{lwB+=JXe]Fi:?6%xME|`c_ͱp Zp-o ~U쿗6weUuxT 5w}ˎ*>ѕ @~9 rW[}o?|7D'WBT"~hm>vxm$ֈ^׍@=,21 ˂w\PJXЎSxk4\_<TVd@$FWs8E8+*d?$k(KT_.w`;՜U-u7Q F}eUlU~ڗqh4yX1N.h>|B P,XJǠE-F-I XԁwJ\ExH-Ez+'ʜt)[ʡD%?zy0܊/:69.^#* .׮qxܱKNel N[%KmKĢ}cg!.EzX(\D#yS~Z^%ʫj5ƣ65hirl\:mwS>Ej!e,5ڏ! +Oʎ=-m:d~M~SW|RNQ1e@uF4&ҩ8N ڌNC7maӟ@c/%t&.q}dz­4 J~2McvIJiJy<Q=+kF9 z*_OcHelp,\OQ'g^D/5yAmۿ;VCw(g(cX@-հs_PK6 PKjH9android/support/v4/app/FragmentTransitionCompat21$3.classTOA=hXĪ Қ`4bR|uCWw[@4>qy?>|wv^y=I@'hd泺-]ӵ @RNc7432BpưϿ.:|)Ga.C+kMhwQW_XwܶPuІ ^˱W򌄏R3qlA|OSc jZsoF@G4~j%}KiI' d$pUE(/cVhC8LM#sHv6h6/%:xEDh A\%k7pTx :$S-dCװ )_}=\.=)m+.TJC8.|J=vs JKf3;Q2=$R; %OCr^hq"PK~ PKjHVandroid/support/v4/app/NotificationCompat$NotificationCompatImplIceCreamSandwich.classVrFֹqR.-$@Kn h MEiBYˋԒ\iMxh_f3<@ߥۈ41i=s={ΞWLc>|k u 1L2L1|× _1d: s8 ,ѡ6FFݨ :TKUYX!emMƚmaDRdCKЍ4^Pyrc% [گ ^ح$QdR M鲓4(6)GnbZе9+Ϭ-䩤fQ!tm=ݹۀӣNJ[&#J+:LvԮRK$'-\1jľZ<*'w^{$K'W`1m,2pX6VXٰq~XSa]eBOy!ރӉ~d7 MP:t>BU4JvgT~@ghV;UAsj28nUEKCvzB‡@UՖ4.WqVTʄߞu_ҫ yQ?2^R`QXE.(ȸ< ~לJ,U'N^@֛E,Q%VIrO>KGʨ8С4)Ȟ߻{c>AT;[yu߷ϮkSmsO==p:}!+pshxT$ea6No`ght}6"&!%D}D9~"hK|41~>MC'2ydZ!)tz`]ď6tӲ02s>Ds PKh\F+ PKSjH.android/support/v4/app/BundleCompatDonut.classU]WWCbD(j؂ E |4%|P6Ʉ?Zյ'\;3և{>97qøN 3!c!Qy_cX%z _aSrƷQ<6ܡӗK˰9*1Z&6.G<ϣm'2:˹Vt% ˍ̹rD>G)WhƄ\sp;°uTLb;( |?)˧[g+::ξЮnGᷭv lj'U2A}}[*.z ս]BYTa]U:+Sk+%k*ɞ=fFVaYb1ϏtRFJW,UJqNN3t=*381rxM{%M-wzNt }[}؃}-i)s82VB6p:!a\ɛ0$Gs~M|I|cH-Imb||}w_FI)@:VpKmq[Z W?3$f>呫Ff~>q  ׸Bp4C0 h1tZ@$ԞғS N.Vt;VlawL|G_36 ܴ2PK86 PKjHNandroid/support/v4/app/ActivityCompat$OnRequestPermissionsResultCallback.classPMK@mkbc'=JTb%/=Mҡl<QCEAfj3Hڛ$g҉FTW5vNEct5-)hv1!pRnj|lTm C0JNNg~5f#'wqdlڊl%~zbC΅F: ӹAV>>.m}P}!AC $ъ&w? LCGZKI?BzUtD3*i.*ˋ=BoW:E~\btg G-w bRw0-:NhC5ca6hh_WSKߣTgjn­۵1P*=I5Unf}goIe]TiN#Uf龥iߘOÄ!w4>4lqS}GH0H3|Pc)FE]h)AA+S'2B8CrƶpD'=:thhd@>#"=2: F!L?!wN zR4e(.#YWpg]ubЪ#/.+앋 % Sͧ9sPhpϞ"j)F8%CVcp aIz[9 T%uaHҪ;.d'PK:BPKyjH'android/support/v4/app/BundleUtil.classRMo@}'qYʇ8N!J)KOm9vR NH&-BIP-{3͛9 V qGF=239=gր![6Cx^|<|RG~G f{V;1#x{kdh( l!q2eC~ {c76[ `0/ܖ?GPB I@#MJqV 5J18BWe6P` 2(xG2T-h 6.%ꬕ=se]lOKTH贋#?exUb+DϡH\ϋ,?m"]n裙OYF}li-&2Z\! V'g }GY S]?C<9{ZsB2:jQEױF܄N^-%\j*as^5ʐ(ChDUƺLI37PKgPK|jH8android/support/v4/app/ActivityManagerCompatKitKat.classQMK1}ik׮kZE[ /]Tzm 5Yv/ (1y3oxI^ߞ_`FUkXPPE2?ɂ1Cۥ`)9, Uy2tOCYƮ$D$3ͯA;C|m E8{Jl2. + >`/j{h o#]9 0X }{ʋ&3Gl,,|7q}ֵPK¯4'PKjH;android/support/v4/app/NotificationManagerCompat$Task.classN1N1 )(HR$H$藻%8\lD(ODmfgG3o1P(v Y[7l֙Zz5]4OhZgV˓BqZ-JM#ÿ[XprY&is絹+ 246wKZC I)%K-M+J~. uhpOj CΒtyn =ߓ $zyxfR\cvzE6oR8.iCRg{aikX`7= X;U-hC;ª33RF Ha@?RͰQlUYR%+0rACKb0,TyV r=aH?S^NX.Dd#|3uO`Sjuz9褞q.\g jHL}GjAq(tzB%I:aTZ}g EH)=$)yVg> Mz+ JPEMnp(j$9dbe+Ԇ,+t4XR걎f<=a56nFcVf[CU&+V σKԶг5PKtqJPKjH6android/support/v4/app/ShareCompat$IntentBuilder.classXy`}{dL,l@@$@8e! $ ͐ w'jk/mZJkmG[{B= Z[{X[ZSmߛn&|ޛ}{+| 79nÍ<7{E?n iҶj$ltt%뫄S<-*>^x\q[%\RDI(v2M+4Ş+Ģ=ecގ'v}#8E"O=nQFLKZHXOeܯMҲwpH3KUO2٢l6PkkP*%Stk(M#ŊFV$T!7QeQ Kͼ:4r=ΜQLrvJuA?lsȘЯ'>K3-}Y7=xBԡڒM:1&fgs \k?\@ufWCS gPNK42" h@HLb*iX&N3d\lkFٓ3 . x"C,R:nAj룓eS;tT*`x\` hL)`ꒆrV6훍mmA:?퐌fP 蘲gSY@ 8|N POL-*gE\1K| U,B4 r\%"YDqaU]xVCUS~ ͳ؊OjZk22o} a1G߱Åz@Py8;MySY^E,5PI!s<ɬE1 %2Z2c>em,^VCXRS z |܂+Nu4B:u*B^gJv=+g JXYΎ:[VV~ X(LgXxVeYAZJX++۱2ɖ,OZ&Ԉ+ʧtUT^BPK %PKjH.android/support/v4/app/BackStackRecord$2.classV_SU]dv)PVj[SLŊ-R[ B]R*>L>: 88Ng: DZ3sϹ9'0pi1 nu:udt,1x>0tK L ɐjKɋp%] e8^%sw5.Xײp*B85)ô+X@XfՑQ 9/HS[bѓA4b:7X's}HT9"d`Y;+AwHHJb6]hD:ïM3IRmF!6 GC$TrȎ[!J^7<̥KME Vvw乷!Dk r*N$l=YaE=;65ܓL w^HI{h9TeoSbQ}Rf\;$u^k^NCxDMǀ8i6qgMs $񩉻L&= l9Y_bĚ`& bO4O!U%)Š/dIN[$]:#fjTQ"A=oZj-[U RI1?9NHڼ^s (x%Q' } ߍW(`lʭb9d%lJaw ;/8yap>u>һq&zWaX E;uS)s'`*`_P!kԘ2=/#/h/:;ՄӐ1͉Ө1M*BK[h%T#vw#W. PKjH<android/support/v4/app/ShareCompat$ShareCompatImplBase.classTmsU~nI-iƼBk5H`ڪnIvȌ_N?0';Eg è/N~ؽs}s XTЇqI;(D0@xbRA/.{bzYƔe\i1@U%a^L 4I`:3 *g8Z6L>j,rX'OlZ}^ 1o;%wpz+K͋VF>9Lź8@eͬږQ;fӲżlУ[=ֲ 7[%7"dtZFmrzĊR 2!x?0(ѵ&6 #򲶢Y)BfBŵ V/NARE} jYqv;B;)ULHA_!业MܬR*ߟњm(e|zΊ"*ḊI)ö"W*ଊLEܤnA-KG F@jH,ܡ,2F?Av8u!碫s¢\syqɲnoKt )])kD@3t9N0xK͚<ٵ2|X˴ )]ʐD'3u{U ſtz{&R#IYy?91@J#)n:(u `a7B#pQ"OǶk^n#Eh7nC_!@hf$m!̰)tނL)֏ ZFrHE\Rk:L1)!|\BBWcD(6l\YGt)#ǤP}7HynaА|l#hW\cx}+% %+2} =qq qBz~Ktfobik_c4=`.~ (~<'^@:9GFqЦ#zӫtǬ%gr$MYv[Eg? ʹ p"7M#ܖy_.:PKJ1PKjH0android/support/v4/app/RemoteInput$Builder.classsUǿ7iɺIKj/+lSd-"(?m# Jδ>l-o.+0tGlH}|ιs_^(.0Hw'W5\⺎ 9˦"|ᆎ<4|͠{wD'v>C7reգ[2^7pkeuS,|[ОWY [t8xۘ7j(D5EG lAn܋BNc57iZAǚ+-3W߉1{c^bM0Rs|m5DoB)CGΓšh!_|M2/Š;y3;Atv;#kmHc.jG^ʕy(jh`^.P' @m898\9 `r't ,6xܜ[X ip܏2Aڢ:vVn81pGg(1MТwV E;rn[^Q!J~{h` DŽO o3(r7ۍ5aGY^B)a,V&frIw `x*܁~jeV=SىYz:GFQjN])ݪR(ENWzuSԛM [H/o3oBfm"gyrho'=A$&kg'4|O_'l!FS#G464@nb: :JSM:OUҫ7w{5 ; kmo{8 #a8 0{N] Ə`q-пD4A,q`)TkF->Q9׮Z%d*-jJuG" Y҆6`heqNLrV*WJ%A:cUڷ*jJAvSS]pp+#4p\'0mf5c^&IǵV ?m-lWJwNah+SbdѪ*,-$\È-ىC`tg*E+kVJ~)߫V -r}l&HNMFX65Kk9tn]Pm/p&uV&8VHc̸Utƪ%4ӛĊv^|7GSz}1j;@<^\l'&IQMx bd׮ S_Kʙ>{& JVLzV1N ݻE3ROgN.٫9xV]Cډ!E:U~ܑ:4+b1sdMBpkϚxϛ\/x/MWxkxd=j i-mB2񮨾'&~3(V WWlo8℅e6!>c)[XMUnݹ1[}VRu;ZXw]w>ݲ{*Rr0-t披FN[D{,$Nm s q5W`k'"-d'4\懲=VgYpJNxU t^n;ͣQɾב6kނW.;RɻBLzُۅjhOn' uiԬjoҢL}ф&5E?na֎Bo5廳MZ~>>_Z7ϘjA~߄߷ -Fف&٤9[8w~ӾG пSZ9?؅Sȫ3_%j&=L=1e{k S ) 5Ñ m75< sB?ў*{k0FjH3چ[$ZÎa#c\GhdRs_0lȞhdU̹'@GH԰S[M(Էa'i"a/1#.a9 2 :0f)㸃1ޝK1'xNRzG I&1My?QфιI3wt9]MZW2U&֊BԑבHe5Nd *-{HAɜ9g(Gd]2I|=_gu]B.c{.3׀$Cp >|{9a>O/2x/+G#u#u#u#1''eBs=Jz_O"y=25s!QȾAo8bӐ}up}18\}y Au{DR zu_Q%}l붻03KmQ^ZqB,~U8o7ʰƆ5] HQcm?\cPC7Ql8̉/} K%}m&o}E?`wzO {Гp_7fht71Q$4+GPK! PKsjHCandroid/support/v4/app/NotificationBuilderWithBuilderAccessor.classO=O0}/10tPT U  :npّ1QI${޽}|~9Ø*ܗ:Oc8# Q!+OͯI E_l-Eڥ%n(y-lzԹ",[w>ZRyou&*AeQXxuw !nlx.L_י!&LF8$4`@C]>ƨV0A-PKPKjH.android/support/v4/app/FragmentTabHost$1.classA 0EhZ v\2@,z &%I{8CwaG@ΪѩJ&MH{^,y L+e Lj2ӪȘiZi u~ ۊ# !YeNI)RK狦wnCϵˡЇPKWiPKsjH6android/support/v4/app/TaskStackBuilderHoneycomb.classRMK@}kkkWVT /ZW&JO?J݈҃d{3 ,ls(Qc!% 9Cv/hK*qܶEt!9Y}%cW~Jߍn7tyTsf7/P/m7*w<-{RK Ki*-f8}Kz=uy'iCTWa8wukOڳܷ5e;TS8 ׀kE "BU"Øq:QZxf0~x%d04 h[r)2ԟlڡ3g ,(`K_c4N}~_`` 6`v Se3PKkV'PKjH?android/support/v4/app/NotificationCompat$BuilderExtender.classS.A=w1a#&$ ºPҺ:5+| +a߈ۣYDιuo#%P;1ba8eUʬ!S5I(T/ q]P~̘31{  ]ߗaQ$P jUzؗ˶{Ou\a+"fVu␅ d Q Xt]E:,>"z⡜6 !w+wT.KG-&ĔiʟCKڞO}\rN+~ҏ[4L/W{6#1f@t+4ml ȡ~6cњGp$iȧ)android/support/v4/app/ActionBarDrawerToggleJellybeanMR2.classU[S#E=ML.eY.ƽ",]PWȲJVJX>XIffRO KEם`Uʇtz>wϿ>O`'%!V԰lG+5P[Ř- <#a-|m EXŧFpX {&Jxnac _aaݻztGQaW%Q[s(ym+8K̦{uLh{1.sU;o]""GtVR\ rMzvg$BЍ峻A*u߆P8ǎ'pM b~A<ӏ,J;4韩_~ԡ4FmA @ 8nzwR '[1R)R ") h3u G'M:r wpгI[$j6 +J5A@ &a1Gh-Z!0Xɜ"}͈޷);t]RjI3o00UI?rR(3ON$"I" 3rN9A"e\"XgqTY* QQu"dHA:-QFUJktRZiSZ7੶; * CʼԱƴo"2Qv!4.k׼PK~g PKyjH-android/support/v4/app/ActivityCompatJB.classn@mqs64RC/]RQU7KJ^GX B!i{<3_8*c>S %CZdȶdeX}vwYH/y șU"du a2P}>u%o}ޜ5C8ّr=e^c3>^m 'c("I= ix"I_ 鑲w4F}b[~vSMdXOdaͰtE Oչvt 냌=zӋ:^@% (Q@&naE>>7CcJY_ÿ1K ʙ=rT0Oҗt!Ow6YFv +3;@g! H6OZ%pst,0;1aqְc l`4z]Fi#ڍ$-\s5Nm2D.XiA(O PK yPKjH2android/support/v4/app/AppOpsManagerCompat23.classS]OP~tÎT•3FĄdl$#\y`k[?y7xaGӑ&=yk `ot<+ lhĖ(̐z/~`H ɪ 5z{&~!Ysm9TdE 5|WyZ}{uy /8o v=ݫ0y s6R- #UsjR^NSV/"PKLPKjH/android/support/v4/app/FragmentController.classX{xUM6dҤMiIB)*ZR**>oߙ}n6/Ξ{<9ۧ}q:| >W5|__q|Q#Gq'#$_jqt=pZ8_3B 9'B&q!_ !-!'|Gy!=!O  yZ{FȏDOLυB/+s;`ufmˈGәTʲĆJEXR7-v[Ȑ1fjNM9f뚣Zc[n+n 3I籩׊Ҧ~3ݾAIߊimoZ  LB^q{v&de`]gܠ]R~QU yYdK;gtٗy~Kvې=-Ԍ.,II9 ϴ1ʐ %f{QQuwh*X[gU<ߨ-W~W߫؊m*?S'y'T\g_U / U*#CWq/H b I ]-$-2I^͸S[Vw yީ]x"r7ޣW1!*>T|eߛ`xMt򽒔1q5煏#{VNʫ_ZԶrpERGJb=6{#90s@Qs?JyZTpWqq_?}jOpw!J%4f5<Ֆu=M"3j1+[~^pZ[U֟B]41+Sa؎p~/Zƣ˷(+|SwGݜ:z9[w%Gju5 (.oR$~[pswNC=,9Λт[p1^u Xq֢σ!n~ebt2^isc/G\;ju+39ˇ\ l|3pkܯ9kZf]gdo MِKlm>EA 69ϧђ/fϖA8ιވQ *Ar4;|/Z@TdX^8>̒ # *, °q6&U  Lkiτ`ڂ0r|.&]fYy/8`. ¼PLa0}˃8A_0ra^ jalY}!}"S7~ Sl}pywEo=\Eh֖A{7Aڊ 53/boo\({b_4o(t_nfeРa)B;"U|߱`,+ :+^Er^E^b=Lj%hR2Ԥ{at1pB1 b&ƑP >),v%ΐ޵t?y|8C!f \Q2!~Rԋ?!_|YNŭsqYT徂楰?涨q|5 nPK|>b WPKjH+android/support/v4/app/ActivityCompat.classXsW/ {˪3ZV3)5YRSF\AfMeTl}'v9)궭|ú;Gŧj86d$g'LccsUHBb8CyJ{y<s֍wK5l[@b/hNB;Yf1 ՓP64'vbUbqɫViUU8Mm9OΑ ~+x'n(x?VfiȁS?M)+x?W8Nqnq+" 1 ֠+)I{j`u1}*aQgӉd`u#xoxnОG KK睂,0+d1F C{fLrP"уf'IS衯>coW-Bo/ފI ESj2L.+DU]$Hox-dpoR5YZ?]4g9HA@ K%xPx<,PyBU lEu.LB_ i ݤЉ+BUR! S5t4u4D~M~Cl񛊫ᷤq =Z(P`mB4 q7ػ[+P'@$PBLY_owj&ރxGH 0x]I%a5ixy<;\FȦ t=&R_Tz L:E"G_SGw5#Q CT l.k&[%yc^ڲTa% ZDsYm`*"/! oc 칅?ʺw }bGKx?e"8@9sE,(Ⳃ.n,.v?.ZpEv m,[` B e^*G&2q8GT"l۸ }rIf栝l";|hc )WeKHӾ&Nnf10i_~?PKV`8PKjH0android/support/v4/app/FragmentTransaction.classUR@>K JQA /jLS?̒ЅdSm3|(6ՠL {;6.NC )xW)(H*B. `AΥaB#Y|57Zdr%rM~kC) 2%~0˕rvx-]=!00WXJ~' [,:n]PY@=li˔s-v5<> v#!5Ց=,~]_i7$YWmᴶ ,g\=^Mw~FR̿e蕀Q'ƥ*z{ѪvՅx9>6YϥIêAˀ{1qeKVC|io6+peun0s*Asf?5V6,~2zSɫja`:҈O L D<ÈO1| cxMi iӐOpom%.刪|erM`;6I-f44љFfDomMeTyj>݉Dɸ/lpOHf)ձř\G9,M|1kp9iNQ }a@O/PK9Kv PKjH:android/support/v4/app/NotificationCompat$InboxStyle.class[OAS],-w\K&$M}۶kݭ)o0Mv)Af93w&qWQHje9"ŘtbB$,ЛY,*xɼFWD!EUf0K[4+Wm7of)Z. -2C8p^:9_r%teUZ|.1,V8L[pszVlHຶ.YM3[=^0+r6eZ岹 -=79eKbR*1sU^*>Q R25<ҮbӞ+lW,q!S|8euadm%[uߐ'!V E]zU?os߿$DCDG;b:1:n踉aHaFm̪0uh=,zGŐ}ۙEs93[+ 䡍p3i& {J7ZE׸yU;/RIae"ir?."\fIzWS:65aS |/ ksJ%p?ɇM%p;޶f8v$Yt:,Ѱ:6M=_Rށ O84K>RQگMe숃ޠJ H 2Q`ҪH5 ;C"5EgpZz8$ |O9B}U d fsT p=ps(& t.5y /90n`Q0dܢnZpB(3*|=R0(X%\&[uX%B4/PKB*PKjH5android/support/v4/app/ShareCompat$IntentReader.classXy`gL,lHȁPH7ِM9J!6YBٺ HVaw ݫz *b5UAV[}of2Z`o}{}/<҉,"Ѕiv Íl~Mlv3fpV|P‡\0>"c>OH} ">§~fncEΰ>.܁;E%nz Gy&6Q6_b_fG%1+ᘄq .Ľ"p [zPW8A쾔;C٩J|?p'@"֕YfTBJSRf>ۿICLqm}B ^obx y)}w ~a6S'xTƛPLx P2~_a7vx RN2.:oDQƟg:dYzX̤smv~ZdQͬhK{HJ]UF16gOF߱]?d ^y޾`Hx8JDqmqڦztOivY+^ezqhx JRwP(*8)Ι -鉉ZP̺޴5#J:$~ƹ,{jS"PPtZ:ًzslժ& %U]G:H*aƖEcAuY C0t :2OӼvouC4Û< x-Vc^ϘSg#xH erf@Ip[[56h?\62ExCI/1!Abc\ȍ J(@5'0۰Vg"b6ƌ٥ceWzZ,!Lv+AvW^%Q"!b|,NJDz"[ ^A_3 FW 0oͮ % l&[G|P*,| AqYb83/}֧_fp1}wU l6$-и@pgQ򝁷5Y}mv/*Nvuèn;qn;z!ߨ}/ @%$j?E3hR9k7qs-J.7W E,1<90FnnȢN-H~3ACa~@l:kus氭ӘɆ,1+MH&Ixǰ}^ gQiNJYxMs!ҴBz,.4a0 ssBzN!Jϫ,ǔBhrWaZ+= 6dzdbW_udJDBG;,bʙFvőpɶ,*ǘLAhw@XpuB#B΋[6RBR[YGAٕQy'enfgPʍҤ$ B>Œ>2q\x97JZh  7\Mhn` [$ndlqF-ނZZeT6o'NGRM)͋Ns?<@4o#DO]vO4,Vfڙu.q=J\F 'G8^'Xv*W6ZX9![Me?PK|9k PKjH9android/support/v4/app/FragmentTabHost$SavedState$1.classkoAߡ jIibE`5") ^LV2KfUM4Q3 CfgΞ}33/xb@ De@ö:r::1h{p R6wza͠ײ{rɳ=Qcg48r|ɇv|f][V]6ó9 SVMnMKTqGUjxGm+v=vpuL$`xP*2q7Lʰ4Eakt /gUp$ʪfn!;/Ei-NMN_WȲhfʛ?FF&Z pFs$5@u+{\AvaHXeO-L^ЬTOD&1'0sL 7q ,OS$Ri)FNTt9).}l^EIһ,Spqz %C~$PKuFCPKjH6android/support/v4/app/NotificationManagerCompat.classY xTd!!`$!kX4 M̌H# XXͺV\nD0ծŶZkXڊje2hssy'޾ 墨6\*U4bU܈,{-[EX[ qnWpSt|[ ܥ…ew@vTcH]8^H器]_(xP|,#xT >&S8p*~ʑǏD"?NO)Dڣg**V*VbiUp@Oмo 뉄/ m-R~@߈ (FgI C=Fkp{,o,pzPr !ʸdEO ~͍dOh^O.!Gzx(һJ`wN$HrN.<#Pl>{y[Ok<s[ o]Δ Tv4o;{5k'MJˑ:[tnjnS"!]Z7 H8'w{|-uݾ oTx|A`vyYDatS('CH{(4D{4#xnh  ,h~ARlF; .z#z27hG{q-WȤqSNjģ$)V%ì%z,֐GK ,K&qӓAHbh<ٰkx`\t4*؎IT~qP3il 1B×jҳѠޤCo *Qg8W"Zry6?"ME7gL=J,[Q\}4QY{Re_4.Lc4erΈU{ǥ;@c>&k$8::vȰɢ ~"*ҺJ@v+ 3Fy.vҌRUpx)|qRFU*ad`[4cU~9VqJf䢉T'lR: jpN.OjYoj…kNRX M++I ,=&fɘS]dzI9N_^E99tL$^gX пaoY5pK&_ZsIѺ,o)B~h ,T؛`yO+Sc"|iu2CB(wx7kX45יS2۔,ithx,Ï R*0T*bމKJ%mA4?"/=׏SAdO̫ΔN vV(H!|[PUMX1`Rma=ɩ6GZ7ޭʳ=kCMҌiNSh4lqKQ9Fl%cƣWSX:[zj Ezz/U.>2j8.plhv1 !p{6xQӳ쟑otoY3d?VX|+q̖7:5嶠DQ|/)[>% \'o@l]a2BsEv[dfCbv {PP\Pxw>Xa*b؋܇˼>X(nq1PS2` }%X o'(90iť7`{ӻ)0vØ)7g>LAnv?xx~2q-#T!dmiN - R,[aQQcXdzܴRn/nkӹT0HC>IЧʜeR]SZ^b^uR5Qvj~ 1'5^yXk`(>JS9N]*ޯվoyw{?ം3*G?Ἂ Q؎Ua9# iU_S\1(=SE%z@u*(LJ(*]阆Eq{/.I\@Jm&ˈ1lpy#HE]Vbnj -$oLw5Vц;덠#hk%,a;^#eD;Iz/E.N'ajMSs2R`q8^[4d'0" B%4A$LklBbߖ;Q&娬 I٪ I 3M)jkTn 7]Uv9aZ ݛ2f3*U,ɰK(>&Ptϥƒ,R^@NL d ܺYb|h Fh$^ :4S)( %;OD9'/wʈʉ***j"?,"h6D#OZD-!ZJh9 #Xu#22xͨ5-xblm(V;~5gTY'zQf@Ntvs08VvPƙEڱJ}Cn+h^JNBvy*Ɲ2 #L|d@ᧀ*7LCWIQ^q{*Oz-柣PKPKjH4android/support/v4/app/NavUtils$NavUtilsImplJB.class[OQ-uX ^Ib1ryCfw"_IL8g-T$\fg~3?i,x Pb*:%b %˻ZEsW nKiQJ<'a|nZzpv&zkj):ҙoe@qhݡ"UK=:R @?aÔnN(/tKAz9E35mxDգΏ|&<ˏd'i8lӰW'_'FyRoTl꒷P1NP)7~X:=F47ިtD^t5)e>)L9"N]xgSx;K}kf?PKh5PKjH7android/support/v4/app/FragmentTabHost$SavedState.classUrFN$YV$0姸ť]n)$$iJ(vB[:e)#w*6aL鞤qPxu޷yluXVFt7vInUҡaEê|~c uu _I:"}視ɠ7!Qw]/;<DŐ_{t1o|ofs_ίs=2pxĀ3 SzAwwCswwUwvRzk^-Revptit&زgQ+twZB^;ܷ>Q[62?R"X ӧ#'#ҍRM.É O|;M/V(I{ݐa;tVgB/'LihU|pcqY?"E||GJo$L$~Ֆr,7R⾁7ND5c _z]c83 /~C e ,ērX>y6)V *n7Vpg'&^Uڟ{2[3#PT5±,ntd.Q{iURAjgA(vHe[>[Vi PIN Z$UʕG6Ez}q<{:["U s`Nr+]c1sHlfps=*򓞓(߇: EIٴގ Kxf,WWP%O&"U^BL^G L*(l6?IbS+5b8d$-N2͞y?Hz叓|OCg ^/1MI `2Epe"_}OYl✒UcJiPKDJPPKjH3android/support/v4/app/FragmentManagerState$1.class[kAnWi/^ź-" R"I2S6avS/ >]E?JҬcg*<RB7<,_YSx:/gc\;#½J:8Q|M+*c)&%˷eƐ#t|_4i׵JY.[e 1GӦcilB䶽|)3cDO 8s' a>c~e7R̗_Ƚ75KR`_ Qt8oQem\0;U\q0"!2]3g6֏;rd‹eYhOx]ǻ+f +|->p}xmftcVа5zya݊s:,OMd6[by:˔W}͍ ޷Ϭ.|O6%q0bl'ſ\d}ci訅b`B]h(w9qafXVA$2DnI4[Yf!/eYV#=j꡵ksM>DjX.&VFbeŅ=V ^VЊg瓓E [ hIT=rF7TGjj%ta-Vwkj e2.VQ\E ;Cz_wx+rr핉Eo$ZOa~JQDz`"*h|}D mam!G\:]B> m\SW'1m$>bڊmң($)|=&`Vyrs)ȵSCAG&= =" Bjuz}7ҝn[w1e wAwA:tdqy5g5z#藎:5$^H8HG Zg4B. B&y\Fd[(7\­>zmƅ`!2RE_G}F|􇢘֐5|l .e%,.cዸkk 9bK>|YJ S|:gbL<6!X`e٬c3:rp[QQ _z e~x8ETↂ-%}x3&ZzfvDG43FT)3]ڤEIT44" 6SfKRŨhp},FH;Bx^WݾmB7.|O!Jg mdzGϱ/0/2/)GK1C9[eIEj-j֤H7N7ͬެ h$f۸r_*y|\R]ҕHPK$4BPKjH*android/support/v4/app/ServiceCompat.classPJ@}.syڈZD;EpLX&Yt\6$`Qf7o3_ΰƖm'S,G-PVbJYq[0PW(%8,8au7 J=G2ge1ߡ^DK8XU4RAյlTП7y(W5S/]alOg>ְNOa6XU}), #c,lyH6t4XaM:57PK%QrPKjH0android/support/v4/app/AppOpsManagerCompat.classUrY`ğdu]u1I]W0scTL Y| ubV>ʬ>ݧt7g3S"rS.`Q?,I#ɂ ` !Lhs7}c׭ |vYkSJT~ZTj%f0\-n(ÖqZ[-mJƒJSڢ 1c^aXp=u@߸swQ(:]}r #yKX"C43ӢEcҝ*05Mv{X̢ AuMk刦Ss3|6z#,obP#%<ҫ=X]o:x L Bqzc><֫8d3%BfvD`@/J2gapjG0L}5C!h0$]~AH\/Ih03R2wm{0NV;hpz^\2y@kJ gpZHkAV5)(k wdd z췇3W[{n4{x7zr+=[ՆQXlOja.s\NsS{!0GϛFE S=.I'wHܡOQ?BNΒdҚ.t֑h8 $rKϓgd7}'#>&y~̄h#.؛= enY ef*]9tECZx}R7X g7qd/\kU*4EH./Va{N'(AoTҶ;2޺Ayu9/8&ujTxI xỊe$PKck6PKsjH8android/support/v4/app/NotificationCompatHoneycomb.classWisG}k^I^cB8d G؀r#ya+V+ 7Ԧ*JU>* $Bw-# 4=ozzzzz̬܊"X=2EЂL{1h?y!Fat7{XHeecN03*e(c, E& {KYY8,*2dKhM*![5"!4!3bR {Xr1:AgT/KNf[/WJ%vcqTZs[f*TgeU&hFU$)LGN< '1qD: ,ē+̉Wg׵pDrĈ.ˉEaF\9ޯ;EXU9䜿f Ѻ 4rLޟ-jMZm̼%!c4HX(Gc Ŗ1톥 $+_1L¬])rSK3Ie)exj%eNj,KfhБqܱjɣ2_'dLx@ƃ20%"cUءs24`zγV2RlzR#8Qz;I%inpCHpq6m\n&mR&&vvvvw-?Ӽ{}=s/}SnlcxOD0'CL6w!|wx}o0a'Ə"o8_i?`+ފ y‹!\M OYnhU j&3P.N2Җvhs`x+m Gl/&5S3.A417S鬀hH1S4cs0dsaZ[$_e#L0GS䨡&4> =^8r6l15Ck'jLMDGO@RMOLX޾%m"S㚥 o īBh 5Mݭ%fzqvRMKK igQzqda.#A\fRO$i':jLê X3: ؿEYkLP&qBS aZKInzvKyDφ/ jn#}[.xU7أ'۴씩g,,$0눩[x}I cj'lڊsIrbtD.I|C5ɈM)#LLZAYwݒھ"*|^ Yao׬QF'd~uOѸ67P CcGEa8M1`1\SHzVaI9؟K&ٷ *RMI=AH$cZ:'aG|jv, |\,&^(d82(c`gc@:hغk#LMN P5ՔFv,1Ds~P3Sz6ۥyiaes+.:BSj6%+=zgNO]Et5V\u8Av /UYW )L:q&ki ͜է(&{U&.&./֮q Rr'$&3$ PIg(a%r^QF5|.VV5iòxTNLAbdvy+˧?˷ T\|lIlZ"_#C4y艒MIGKz2'1YZ|Weaj]鋼BeW;WeY_ ɸKxfI0ߠ,pu v  f irkrN9~)9eL9帆`8}'h5 4Eo_Ei!ɂ mxy@@&1!gG5#8Ns>;WK;+6Dxootw`t[]" E. /!p"#G8?Ob)_}I~ԓPͤ6ii{ڒG0M IFJ|r*$bfW@ ]~K5xER8$$)Ș3S%$Cn$m4|xK>)KsXGh.[*#p$GRg8^Ch&{v}?'Ib&C=tqd:93@ -(N/yH80 mUcF.ӬD!*iHy/V-.9^b/1^_17Hiy#!Ζf#ǥ)[USӮ'.H6_ߊ;-oRL*$R;m?pq˧|>Uw6aW)G ]]A\zQU[D$E{I_OUJx>TU .]|އ{-GWU>聮ݣU;s6{hW Bt  ũ^5ZbM$*TȞ A#>.\v ]|%r#:˸=c}ϡRU}yV牝/ _J%ŗы%K]2Ї[as<.۾R6{i~fvIkLJ wztUy8|J>m"05o_M X}7Ь3fLƧ!b_.QTF꜀o¿I*ߢ6ۤgߡb]jfG_z%x .Q[?&V7籊ye"'[s"qOٟ?C /?ɱ& &5ԅ5ӂ Tۢ .v*+|T EKI Jj(0 "QNEBꮕ #dcu/ nQ @b> N\: {W_}?b}(tg~qn& F3fфь8D+^!V`P0,ڰ֣C2De$sJ{ڸ?7O4Xc.x 1' Xqul?C`ucrz\Oy\Y%~ k:KXy7q$!~7Jq vm1.vBO]Hې^bqMaUzH-CoTlEa,A\\UB aAH?圕W+_@'E>B}A;f'?=}U㘾MW;gwD߿vھp#%PKjH2android/support/v4/app/Fragment$SavedState$1.classmkA$64jcEB0Vl.]"M ~?8{5$AonggSTSPǣ"x=*((gm B!,ޱ]j4 lt<;\j=uƂSgHuXX݀K]WȦ}_70T:ܵg[?=gpCƔ4(\B<ߘxk.Cv ! )Gj]:bRy[f~# [ՔMT)9J}Gdc`◘8nc, LnFRy솴 ]>G TP{|Bdhl#JvTs-=eՒPK Q9 PKjH0android/support/v4/app/ActivityCompatApi23.classSOoA}ХŶPTB]M'1& +ؔiaw]0~^j`2f!P5l33ޛ73|8bǰq<@A6bxppj}Z~cӵZ/:Cq4fkΔ0vDZ_4P'B/።ݦ~ vI !@6(2E_ulPFy&D()/(4~B}CDQ]!zJJ9Gy`?Ώ뱅`t3^0Yrr;`$;'4!MF +_հRgƈ)J{4nGrrDŪPK/hPKxjHGandroid/support/v4/app/NotificationCompatIceCreamSandwich$Builder.classWi{U~ovv(("@")euܦә00((n&ÿ s?G:>f'L1RWLf ㈎o񝎣L1q\ #~q/:c_EoV|O.` 32r IU)+|~)svRyZ62(/MVz[nVd]%xҢQCFCeuv$d8jӥĜ6kY/T#j/5r9M:9zLI&3]5$<.}WX@#X aUsjǺ|ّrY2չimWn3=QYΡf2ټK=vڡ*HE)Y:-ni$Vgsbm)9`,vn.fE9| `SAUueqRW('c)iW4جQG5F{p,qw=;"@6!8Kg9W]0o.(4 ytUE Gq kiӂ4Nj^p!5ҥR磞Z:F|4c -cG>Zi_sAhCUpgⴔqڈ3>W-j3Ǵ:VeZ}h%/g6ꭍ>f#]mfO,I X IJЮtOc9BjZUh:)ԅ &TXz֒\Bcq+  |;Ѥ[z 4oL%8o@:/.Πm~ɥ#B% @/6练DwRU@fes*UmXHXBUyCPK*PKjH2android/support/v4/app/NavUtils$NavUtilsImpl.classJ1OjԱjå-+)b;.iSIH2 ӑ`)Y$~$oΰ`36A 3\؉\׾t~EpںbrlS5-XV%سO*KǑ%e=AC\$`!ڀM9՛[)ˣ',g4e2CgL|p2Kzƀ=9TiytESײ/%7Y-As~ OFJ/VQ+4@5Qz17N [PKȓ"(<PK2jH<android/support/v4/app/NotificationManagerCompatEclair.classJAƿlnU6ZiWPB7b!ZD~ƑufL>Vo,ЇXi9;|w͟_`\,VTT =D6O } S-E;LND;⛙Dt*is&{-:׏crqj#@D7LE,A 4B‡8hKGBucHm4m꾖:ѵUrC&a:=s7գG:ҧaԝj{I~vcfX7w䶶00ʘ~fNc1wCuU/ϋKĖ'9Q FOR˻ 0) 8|ܭ'1QdtIf1zps-P}{zrKCni9>q>SPKBctPKjH)android/support/v4/app/BundleCompat.classRKo@6/IJxC6x=g\3o9VTg&f]NĹB?Ó}WPpi{>O,e4tPnnYh*ԐGA QfαcƱ ]˲͵~ǠL+8o|<Q0s >7w1@N(MkO+$r/`I'2n|"b&j`6@UK_?Cm41uIIe`kT7" E,EJ"r$C(%)ʱhn^!EQJ^ъ*T)B^S{ZcPKz?L% PKjH0android/support/v4/app/FragmentManagerImpl.class}`TU7! zHPBhZ0$DL`޻XX; ("^v-}o^$ys=S=o{wlc0z S(39'ƉE_L1ŏ ,1/~/fX8SCe?t K5)MQcZRg~4?["c>gXLSNY89T.8|quZ"/.Z2K\NWZ*J^mk}%~:`)se-7nnq%nm&/[NKe-q%ZOTv%xr`YazoG,;, h$Ner2.Rcڣ>{`'>JD'iOz>ψ?鞕B/'$'\\sx@^KT2=^쫦% &=2L%1Żx61=>z|J8K*/;*?&?R'KL_,/jos_zJ:HC8L[^ZH5CCRO~iIe~'Ԥ%$/e{S&Q&ْ,Bl wAe*=@W?|*QY{Z{[Z-ٟ$P6 0@?9&C,9jc%GPۑeі̱$K,9e.L")5!{˩JNL%gX8Ϥv%Pǹl%Su!=PBK.b/K-y{G*)K\c%X9K>O^NjT%_+|Ւ_kxoM91DR>$)gɷoSkL)?`7heyh%@~EEzrYQMMA(6:S2+\QWCw[umYlj6A~z rَi6rGSø w8\1mc#Η0zd\|d$-m3k5hDJPv/6&crűԻ YᚺZtOMq6RcX6PEv]3@([MSK8pqui1E1֏$jU47ڱ2Q6])nE*p\5ɜȣab)?6'&/6+:5<*\1 zaۄt1Z<ɒ 3oJ5C>!r>*_⢐vvcP5ڣq.4k͋*iۣ=rjQF6QPIQVYT?؛.i~ۖT"Y*\y*)!*֮#uS`I>) SAjUzEeuyl_wdnUDEv gWC A.-or[$V91zgeLyɿ4ךjv)#5wh'vj&?Z`Vqi @B7>ޓ3[Qm?n'Wz9o_QYvus6_RZSUTh $z@ K Hch]H1n9vC%:~IMqHnҎEiV S/{]6\߷폼70jCZd䙕kfCG?hk 0p1-+V;5EhB!ȶ/+.D.Bc,D H/y~m|~=]btDC.&؎k ;ld]]h #>d`91-PxQm,rkkd'6 VZ?jrFQtynWxK=JO:>YYK"=q_bJ͟ S~6p-zujGs٘]I9u(K͌fjbQ06{'b_7hO`! 谴{/k>Ô7|D@S~kG) ȟ//8Sp3GV樀rh"OXzxzLj"&c&ҁfmeh¼)X˾FC*&5E&s0͡XfkU5UM dtTӀ>MެAu$zeKkJq/[UJ853f쓀}5G yOjPs~]U}BrD^f*)-V(̡n%2Ee >ozZ/E4bjZPǫD"IXN6UQ@-WU9Pa/Ve;wra%˚VJu*gUFk 9*.%D7|*W * u W[u_PPժ&jiVu*)s[eS fkQbu ZS[T-CR޼F]K'A"N0f5u"d{*y0&JqɎik1BEeh ;"𵴇ͲoUrpP_'zG7 n% uTAزoG(KTlVcs:RP74MhyGhۢ+\A(,dkP PBFohJ?/ *W "&<@ꖀU݆˃#Dgg,$.D*}J oh:mȀ (V`dceCTW]Cd,y(SPE݁ךּZBag ʋmN|AT$M`ϱ3"X+;' Au9Ou/ ̩jFzDyXAR[=ʓa6M]!ݖsq]٭jVnN>_,lv}1v_4*u'[Mkr@Mn2V#l^?^}@)M|/M{{Z=b,|_KGf2"7iKԢ Lu/] O6JJuCAFiḺ>VzEuͣc-:D G$O|X=>FiKH@@=A}C@=):SxZZF) gԟY\_0gϫEz9^!UھsBZԠV-].TWQzZ]8䔖d ҕY!$]1Bm-=QV? HI${ORs82P_#1G*ۥPU| JДRRu]IZJ TF0DW:zQQM7(#ğL:o7-x+Ԕpq,\]d)N~FG~<.kDYc>m:e0XQMGߐk=X9rSЊC;.вÊ=ƅwWFUI7yf- lķیv(\jJO8ו.WSshn>KJz/̛7?v)-_QlUh+˫jXWJqZ E qpuY6 EcwY2ABN7^ άĎZ'ĎMNwC9Q*=ǢAFm:3c0Ƒf_@؋IYZ 7_̎Z.hj^=!gu TaYbJk>V.;VˣdTP+nN ƈāĊj5f"%(jM/Yes2?rzxm$J:%-u秄׭\) )_N Z1naLUtk[^*ZA}!V:r/%-DHt)QC3F˘s:P-.?%sucTѾӠ炞ϠM4-P^bxKokl/uW7zzXV/.#)#Ʒ6R*+Zƫ\|Zey\ u=Vu}ʛsD˨&#cNvj 5 .}h\cHM%|XCGS7<6WnQ|IG-MʧVW6WDkhwpRnh388t-Cؚ]E߸4k%4.O!3Q1>VMϣJ;5/ jn-iND"FXJ'|AMz}>]N)8|*-,펞l6|+-fby f}c䀝s2@kH&B#khE¼ڷ`0$j>ضOL?jB=z$T_tF +eW%vJ<&ZJ Zl8J fmk^Z-4ϕ] mqGUE[,{YlOok nʊ( 1"qHMrHott*1tyRn1y,ЁM7HMDnplg !ٌGvnH c as[Sb{qSc}M|O3V[>AI䀍 <9_hA~JGʼnF]K,[1`ԡV lk95n]P.ݱ) *Vv=1C9R\$labW8寑P $Ŗ; Q`RЇӳѦ'(ˢ~y0BF/o&In mH"rwXM hx\[i4fee*_ID{v1 #RZNHLEi:d٤7!.EeuHZ&h\UYECK[cc}?aqcED.l9It04\ުuMۇ,+"Vȁc:(\hבyyn ߦ6.S`{D|o;>ѩw DnZWD#j7Fz.v4zn-;GF:}TcrXMҺcr/:ڼ&䖑6IEy,o[+&-бQh{Zw%bL~Dv{̩%E^b8WY fxmfV5TP+4puQ=͎avc;!,q[9>IO~??='1'vb~=O?I%̿,'*5kz7ӓfhHO<c"BHiȂ'):ʻ!ҠwDZ8<GL@A@L}L_|>yg"Њ@b\We{} >q Sfs<9fcm{7 G4BߦM'fBvј\@uH?D%}Qr#gaf4aOOك< mD0sIFf.ȊlHj%tji`W(ݾ%ޙ<-ދ%RXݱ:cuvCu20KvA0Y}H# T/HũLf$bD Q8rWz1%٪S ͖h܇9\};c!Yil:"^eHrd HUFtq[:b5ppu29r?W#%<,lVQZ1>vNtcF? 971p6cXZѦVim"5*5_P3\HyFJsGJxG"YHOp\L= _σƙ q/}$'\gr$e@_V#arLhS3m#Lk>++!_ZJ Eev өɸyNMAGD@TG0G㜙a|3(+"9ȈBx♅Ik iU1(Ӵ8͛GPѴc6<~wv~;3PLfȧ!&B;! > LȴӜm)mC@fgɷ;`Ff+Krw¬۠K{`6)9E6D!mB##lG+hn07b$rl- >34\vaH5&{Pky1N\ BC6>&ff&.Dp7,餅lGBIKt;$QsxS럆nI6ɳH0?S ]zbЙaiE&,Fq7X:I ^a3F)mpJ69>;zÂ$1L1S-0>I'g'Q:h5ZAPDx(K=Ӂ5mOCl}Us+sŽα/rzO&G~ k"ҩzKŗF~ *sGw@ѬFFMR,g4_hg4tZoءb?ng|! S_V5d?}XUmT/j=&uIvAbS)ZEԬsj55>)z3ܙ>;igz9Nv͹MCԜ(kMQ!ҵ{Ŷ`{tOsjM~R$uDa$Y2NSy1J)`;@:&3vr z2+4{\n#6JtDM ӈ y&/LC0LGOAsVߡe{-.\ҫw2".8F4htአ p$wU9fW75AS72  4U@w83aELa\lx:SR+~Tj})S՘8^kQuvq>>E6sdgؚ.4^q$9d7 ^q_",{ ..]]G? 86}duEcF>G~0bTPԼrFZ6m"8n-K1C6oĿZhR} .6ms򁤛q C8m8G؎kFGqFaz: X'#1b S0_/WXt2".#:iaa+f[`k6|poG=_i.Pr!18{(mn4tVQz׮M;a~`wF&~'5,&KO\[/?'nġ__@K/g6ٛd#<Ogf3xvH p=x=/`M 929/{Qisth'4t+&^k鋾E؇^:a&fڄd$o-N~b0jm<,2[1IC\T6GʆV\e\X~s҂r LKFǓ)IlL}N&}a}4.Fzh@5N>h4̮͞(៤j&&y7hoD#|xo ~1ۏTosRԌsy j魼wwΆ]Hq)4ǿi֖I3cov1lP;܂r] [=pw;bXoCr S,i0$9>--IrU7WbYkѲ _ī0Am7ax DB| ׉xL| /m|.opH||{(~`)GEĿYo%˦l8l%] BJvTVi-b{BƳge{Y&7e{LfvHv~ٙ'4AvCe7#q2ħP'^|LE9.ƿr$]-1'2x1ZN1UYrX rX&g @+ D|*_%Oyl/ \a9ZJ=KOg2yJ&kY+:CO5%yu_Cj|D],uܩ.ܭ|\._}&iy|Q7C =bqku`7`YMѪ8޶m1bX?f[r1u>mRF|m r䔎t;BE&~˜pL`vt|!OCo? *>VBAe/ѫ'0dc툃ؽ|&k+b49Fs涱6Pn{"9߾T!Y|sp!%_f\p>M5nvRk7;f:ĕ{H4z:&]vA>r{ ⩛H(VP7vPO1cLx{fԴ4%S#Fm=ŜȒӯn |' baqv1:߂87(߅^=-?#$?$s?``jt % ^/z[s^m)|ĀPo!"Zǥ=vML`vlglAPAp{}1H΍R(o_Τ{ +#uȐx4s~CG G\|@#?s+: u?/Mow0e`n&g휳[a1?ә>eV@Uu|ujd-qT|E-mhuDt٠8J *B ]hSU<8MUg^.5OJl%;RbK m|Ffh64(*i*PnFvNֵuwo>}R)88 .0\XT7XC AXl;Dw|# :^OQ`QSwnB {cg=t%^^?o K2pҽq#ciJd Ez#@CT:Taʀ|φ 5j@Xj0\•j\Fz.ewSw42A%uBvE\iI?JfYTfĭYohJ}QDDLc #mڜs [1j,r8(Pa4ɅSdTSV!M{n|/tpK =HP+Yc`&ꊴe'2:e5QqPj̄~jdf85&y0S͇j!-F[ .59zlpl:s*tٖG½. oA-BG'AeH@EHHb$P   T 'SZUg*B.ֻZhK%v-4P@L,ӞoZB}wfd؏C AB"PPkQt(VgZYp:6<绤9%.iThqKMk1A-iR$4M6-EzǤbD$m27'-/@B^ y1$䥐.9 (SWej^]Ե'u]0cT{QJ=>ROڋ.@{.)ϡK[6-ٺ:o&ZUƚG"Z;;4j}7!7%~6s8q/t_-h } ѝV_w )))+8z'&w41Ǧ;(1Qnl־2o)6R{F{<t? ;yqTFN$.)x>3.((VM%M$r32\΢g?Gf?(ݚieA/N k袾UwhՏp aZeXuF&EZ`+M1yA%goS%l9Q6jWE2'g<"|\ƶev]y@;=\Kh\h[B몝=#8 ah vw<^B>f~Gx`&4>w; N8z夐f.Ohx0aP䣶kPÕ2P!Ø>B-.ɕE -0QTh#3BG*?R02jSHLhd5G4DR:d={3j7=\0={h%:'tHaͰ}e6Ez{%ج3WA lVPz3\fqF~Hʢ96B;%t ;͔mThp"la,a-%6A"BJ@\|6o>T(aIt AVG+i+-9˨!ot6*ľtݘxtMߵ?a\6vAO\Y\zy]933[}ֱW&6 ]6?+2[C3?xMCt&\0x&/}l5 6w hp6>mwZK^r0~ex['8GgDN}|| (i;<3AҎ(l! T!i%y-xw>*I1JO`i+GBﳖBl $MAɳ,b󬬤'h5|JIYMG92_jҌB_[4AƏ0 d_07 E.Z b`)<Zk vOeSIK%1Qϗ"O_0/Ζ9Ǖgkna'=6u/;y/(v8u ǥcR4TppFqIion>9~;yIPbŨSSdИOmfד2-9n$ pcnXÅ[ {K0(fV >P:ӶB;B0hY"'SWd@n_ }2+ H?+ٯJ4oM,(upBI$$>ZmDwj륻Fdg}$57PpdrSsTU\$KAcc+b#5{X.T%e5*\Nh`Ai}шCfga2n0C1{@ ^ p WYp9a93P Q4sX's,nc)l9!Vid5,vY6sم\ǘdO3OSBW|31kf-o0j&Wu,G+ +h>:qo' 3Cu~D \נЉO:uɷT 0T_!K4i"iaw-4M4S&tңdEZy>2WtׂgFS]EanS8j搮d+*WF YLڞq= O/rVdvC:wN3AO:- sTI]&<璨Q0nF yJ둏DY֗7< 󠣹z`g^'AؼN5ѯ3/5e޼3K+V*xf=dn͛]V¼ ~0o4`-ly`ne?{<>v`>.5oE ]o`w;6F}h>MsogӼ 0G~>[)qzQ}<ʄVlm h qwg rw a Fg3ws<6>+O.G6Gv8";ȎBdGC:X\?<[Ž>Ap~pAb87AEGsSkS%?42ߋ!U_ DePFSyL9XК 'Z̚[puRd' C?G&4MCMd!\&E"Du$o(سΩ']!b*3a>]祽ZɢPO |zj À%ܛiM?:Y>k9tU eS^N8\v/F1!њ {sI,# n!_8K~xmo[]HpT,y Y<:ԙNa$:Go6ˎz{}}6}}..""Ɨ!Ɨ#W W E@tjFi!:}"MtuQ3(1@?HF$^o-VIG;} (gd[tMi݂ Cas;JqB#D7GaA~ă9HNoq[!2fLS *G' co|19qޔ* %QtFofNdBU)z9g)(pڧ$+Ebۂ%+eW QA]ȮAl\̗kѳ5$od׍0-wN*j7AܯۼQR||3 O190hA(²(;?D=nG'7HDA7%q۱(Ň sڅqao!ִ?NfFFIfQ﹋D`˳idGAId)6 | wl`Cu]AFr b! ;g96;yTB-QIZ:yU"w7#GkXDy t|c=$fu[F֗=}ї||//u%>sq:dPLOЏ"z>()|KTO. x|`?`#zW/oЛ~Gd}њGF4IOWn̾ })ԫL: hlb#~mЗy䇖 H(@r_/welܽ݇;;\=d ߳a& t?/3|U"vNPKpRPKjHGandroid/support/v4/app/ActivityCompat21$SharedElementCallbackImpl.classVoF~. MZZhaв22PL[0Itu9ߴI['wvĉYQ5Ezサy?OkxAý *AǦ0x(Ŗe<*c6s23Y-:-i;"n0B8(r"wF ?;cӎC7~eǫ+9uuWіJt&pvN{2mЕt\ Hv1%A(q;3\ŀ0fX6^Cntb3nk.ÝT֒܀,XQWyztsA]O8oCf eWNH/>T2eM Ϝ[t UGb̆ifͫd^=#zOfHgNUyJ|&dխQ -e4sqꇔa!cwC*{ q0OZ>  ʤOp'燸KGƳ? >Mױ:A.Prwp$P?PKg\ PKjHHandroid/support/v4/app/ActivityOptionsCompat$ActivityOptionsImpl21.classMo@M|.)|h4br!ѠJR8-uiu$~ J|H 1r@Xhe{gvٙsذQ*,ܱq220M&^$IHG.CHPo^J IUDB=enMpJV3"SȺF`v52ZNߣw3bd$"b'ontzWXexs ͫQm'ٿ߆4d/zzn=V)vl_PKڡ+PKjH,android/support/v4/app/FragmentState$1.classn0_'iKc姌߲ISEJ M8r[˔:E 8$\EQ/?wL- jfĦH'aH 7 z/fPL ϕd&8t|0X])jej=.GsF?N=Ol>!~Q}b(+eh{=϶ꉐL9a5RC֭I{*uʡsrla@E(}g,y0S4ۺzO1,Kz۫e;whBߛC[3POHg\]wxOBj6-qB Ν}Z[M{kvg~,T}Ȧl )XaVE;NFgHpߐ:h"y1KX POתl;ROg0ٿ,*XqFE-hѨ 9[1琻ʒyl72nf0)EzKjsԱnPKIPKjHAandroid/support/v4/app/NotificationManagerCompat$CancelTask.classU{SId.˪1 "HH;$]4GWf*J,{vs*JtL~ӳ~c}ԍ^,_~$tY WuhẔ74LKYpS-⮊CO dEf^Ua[2qxKS i2*C&K4 %{!{*C_q97)ꡚvkgŢpDHNĠWgVaQdJMxwf? SYccQBao">R)@ (nխ iWujdEY]yneFXv E* ZLz;Y03vܦC˾û,`` 14p\jC8⾁8i! Gw.ۼ2I-7iYF&Z.YʲL\P10ۤ{om 0 b~d>[~lo\l sm%oWMuRLҟ;==}X۽~N r蓖]zB7h!K(}+K5dJl JIYC{rryYRP,#s8q?O4&ЁkpCn\ev[  3+IMfghQZ^9+>-re/1hdh(*2 TD/s|yKޅ "V&ȱؼ0;e?O^UQ\y/'k|: Ɂ|9 28H֡RR_.q|#6 8 ܥ c[;uEH8#88dΩ8C;IdoPK<`w8PKjHCandroid/support/v4/app/ActionBarDrawerToggle$DelegateProvider.classJ@5ijڋWqACQQ_` a˺6n|J^ff>>`c=&a`?vݳ- c\H1\҉1쮵*Ϊ\TuYZEs*dYQ5WIxӨJM5^@Õ?kqWk-#POv(Yxx&cpԯ6*gG,p)tƙ'-up_D tu +A; 0 ])V$SlDPK7vPKyjH7android/support/v4/app/RemoteInputCompatJellybean.classWitƒ5cyA`@``dX@(6. 2@ mܱ<Y mӴMKHӍ.Iweи[ڴM}o{zҦlGsyw.ᄃg[ 6du81Vj1Fk:jы7xƛA[6A]P OP "wAD  CxQ~bA<>%Ge|XGzA|>*x?&8'BCC=dWwoI-2GCidvJf򖖱hN;FK;$;.P#\5drh`oh`hbbO6#cX;$"${4_42Ęnkci(Mii跎y &̸5B.5q-YKOdr';Ӭ;ter.dӺOс2n j"͞y i)+k`d`6/|luUfiz ##%s)AE"7kYĤ~&]"?;<4aE/ɢ, :dnRwk908ȥRbpB2ӻ5 i+ %2aR*ŋ[*%`K+ɱTSi#9fq{T>=2ed3gXc2gk]w%g?aM3`:֯nc4\!g8TEPKsPġhw o9. e fJ3-]Vڬ)ݭV|VBuZ*>|XHy_E>S愣Zŗ0I{,S#y|YWU f@`*. uObJ7pESo*xS}< /WDLY K,Ž 7Q<@ROS?/dRůk:TU;^ !DAϤ5?*JB -fWs<ԕNL<+L;Lm#%21oh9wg/]tÕ;M;i>Tthyjc#=.UDzȫFR63♦2rG,#Nċ r WW* KA V^]c-v 45:ojJt=-}H[Qt#B_*WX'%r]IT+; ˀz|U_X]ZcZߘЇOtq (h5Gѽ^KG1a]_n<4t]WX|SGƽtgQF`>J߸KN?' Qr1T7~z6_^(MߖӼD^LIbo # ͜o.`^R6|KKη_V6" +H9J wXpbjvx#TVqm,዆656hHQ m^59`C݁Wij5s/|׶˨yX*jXOjP, ƢXH!`cq,$،Nⴰ!ƾiat60 m؃>j/e[OZE.!\~ߎ9,lk6m]IJ3>& tP؎z]6s\ Q. A Qf0NZ3pPj| (cD7H*bmitI%}P1ڸ""ƮEK>:H`m7q\;b""Q3aTM b[Dl].I lۙdJMm6V>"m6VmrKb>m6bcMJ3[P^V>R%&IJ50F*Baz0ƵP+C2$8VD/m a[{jKoK!P}-,;PSh=띥r3ˈROLh v0j/ti7< l 7r7终oWdpP٩lَ0?ފ4X<\1a*.dIh9y*aGda:#eEU;gN(ߍO_RF_5PKQ$L]PKjH9android/support/v4/app/FragmentTransitionCompat21$4.classVsU&c Mi DD#ۖQQX)E5ْMHQQ??_gq|q>: niBbQ43{sŤS) 0- `F<}x6^ 4 "C8BxjH CK2bJЄ\g CBA9 &CGIŬqð$U=mZ:Q D\\yTuK5CgXk2Ʌkr,ZRE[%:,x޲fכ\".9gUrsֱӘC&6-]w8NͮچIgLon36F<_XzfZx?꘶YtI6f҄UgLO lLp;k()(ἌIxPF ,- _` d lHж 2qA2^2.5 ˸7$)2"Oe2tZ$Aqn \{ɸ%| C|$KX'a=P9&9ar>5kqFEljp4i-h]}ð_HR95ۯ+:ҝ4 T054L;æxs,tW+205y}r Hew*=DJ7$39`]>%vǯnNIY՚e[LDg^t"G9Űr=唬Ed.p4*=tO| Sb4.bFg#BbtVκoqqwi&:렻rϷ }h/A^2PS+hS*(o^%^AUS"RWW" J$XAP*)7!hA #o]ͮÃv-*߂-CY*B~.yjZU*W蓪":fH,s GtO .G`غ=ޡw!֏xmQiHij '/sOB59u>. (Ki( ƲH9bgqb:@p%f p֯X 7y'i!!0P<(SBblЇ}x|R8P*Rn]ҰA Y ^奍Lg<|:EOa>kRӢ@d7Z#7F[{k oۘl.q\/6b{t𶙩HisIȴ.jfO쵅1-Ot5}EN5iBg%a6Vb℺Q1B gfcrT_5M8 2A3bJ 1qYߌ f68LkpnHbK~B}.k KF\oPJ6yjAe.7G &9 ]iUw]g$n72[TjxT4^8qsɤ"41au|6f?PysQjf2xGb{tQ9_ {iؙj'd8o9J-#x&{FI"e떆З1ܬ i#bƦ5ug a997anD e({W>|i'YUNi upz%jL8\4FX1LG V_Tol6rhd٢5}6?4QYH_HSvA4/KT jXJSeɝs;BY Ww:L.QY3QfqN9`rIp5|VE'(φڰH|#-,^]ޞj)3uŖnrz!`!onyU N{H/_F5(5ҕ2]uԕu9`RT-wm͏`Ø.k֗T^'K+Kz;qciޖ4S|- lP)mi͓nO7qy[&Yw쮽nLvYv͌yU[6}mۯi]y])v3ɖ58)>7p˯쓙~.?ϙCd?!+F>gxׇ$ï ~1߿t.RhpLN3Y5_Lcsw֍k`mKڐݡBOMV/`JkTaeXvV=SlȨv6aoUomƌ0cxje!%O 6M+JTZzF؂@P%_4˝ y}Чn^mB@-WG1V]8;G5yrQE}:5EK\kR=GO=9BY=$8$n^"j%LA6T.[ǫe5w,q+gp,N-i+G:[8QUrG G91#%z+X^} ,+ -Ř)[zqTAA!~XG.!1) O!w nAWDž'~!}Rȧ|Zg|VQ!|^_׃(R_+*4|]7Vߖ;A|7F?lcA? 3B~W~7~K6RI!LtSF.gLұdf[mkٙ.};,Ysс|&2=ݟ.Ԑs mCF4ZpX!ne 7)ͫ(^}W[]K;RH̸A)}.$edƢ7ΛyFwi5w[\_ᷛ4q+ܴņcdncL(V1^ŮE A;:dḙ4FRFjXR/0~4b.ڎݿv:k-w{S7Bx-eLȅ>;-W&6{:jb- td+ sOe[\R#p Oh>';4 ǧrCLoֱ3b$5BcNŖaˍ%3;cEJlr'x+r* )ms볦: ps|v9!R]G=O G ǞlѿUrun qH\Ȫ*N5A5Iß*ˑ}OŐicHU?`$3>=v 9sQ7 JV>l] nD XQv W/expr8Gm~jxN`Y9}`L6D.o*{i'p-_<_:/tO#xTax t f4:ձՓStj&t5 iJm: `)d[iӄr:әt:K<ٴ@P#(BtlVNR*cKuZBK9TDPe=սu:ct|Χ 8sY)괜.j@1괂VΘn6J+8ǭ$Gz{@!jtNbxiNkά:iuRSL綑}fo55(+c؟Hg O rFzLnPU>GI eӘ5ZOX~*lS afDb/CPmkt\'= '2\S)ź(J'-bM$?kd-v"'k^H +ߩ#rv lZJV˱͓OZ6 HK.%"Rѣ6jGe3n򍸻Y['h)3ٿc』eyrFG񆬱qO9ͬn4e)mˌ|8Jj?e: @63?ڃWٛIsȧd0KKHmN:RٱzIaKBMx1)/k_+m ߝ/ MYj4S2(*lU)t2gCe݉|85I8񮜙;#kהx_mSWJ&SrY kLUᇣڅ8g]ط =SRJ>"i2sebJϯrdR#jxL$7nt7_Uj9G("~7Re_xg@4Ido]UIgg|J]V3Զ&?ѓLKOj0qf M%g# މ]xP׎:#{1#yuW,>%wB xh`_f-\=1hC'<⯛\̩mp0ڂeV99gx8s<zX=ebsqCYs"!PPši/9eauz<<Nߌ )B+J:Y]ECVMHmٵJ(ZTp7L7V,C2~Ũ C6Z;hqڊ.{h^ak1\tn!g FJDB8,ǨABBikR(HWF ]ͨE!QB9kSqskBq dAHkYKg35uz^pjx^@^^Ds{hэ>U3}tW4&`'3@I kh-ʗdaCVmºA%.ǷQ#u]ǏѫjVHx ^[PUuĢzUjt_GB  CbJyXG bMo>A>:.>$NN0boy9obtjGѥY]n~W/)8PKNڂ PKjH:android/support/v4/app/INotificationSideChannel$Stub.classV[se~6IC =@[@DIh8 "% mmb9)ul-nl (3p^թ^otƟo`ۤ!y=7p;n\]H| |>L1i9|$9dhG cVuQpY]QO< CE\i˼yK xf>1y{|}JjTؾY[2 P.9J 4=M=Yzi0 Reh>=QA> Z =c$!:.2 1{(51Nyq29&Sʖ^]"iÈ+D*b/'bbٳPKJgPKjH2android/support/v4/app/RemoteInput$ImplApi20.classSn@=5WiS4I(F)*RSuj+c >Ab$j ;;sgS ஁{8rsl0_:b(G %|~ ex(.Ej$#:,uDXSACX2J8 c XhtR8"k7mWLɉ+hK¶sChP;EɝT/(&=V3pT6`aV4* GrIps"N YM X4QEl*e? OhkJ &tX{r4iHWWqkAS`BJ|N[.G] ,BF8M 0h,Whf  Yzr Bk7M-l ڳ̀j*U5[3'UhlX-mƵlAVp X:wppU[mLX+m*PKNt PKjHLandroid/support/v4/app/ActionBarDrawerToggle$ActionBarDrawerToggleImpl.classN0mI (b@03l*E@yJvx5B$Fx?^\GQBn4=(.7 ݡb tP%-ljzb=BcKٝ0Ew80ta=jR[O[Jtc٘^PK(i^PKjHGandroid/support/v4/app/NotificationCompat$Action$WearableExtender.classWWWMG@B$~WM A VI/kwM9Ωtˮ]OmmLZE{޽ͣ/p۱WَeHy$r aR|L+ᏬC"rvy((X9ti|,03 Cxd4.ȋ?%kIS䀀`Fd0!sp4py$6s~lX(CQ\#c#eKbP03@$s XHit(/;)9'tD;$sJ>gM&r.\/*ZB1)y56LtP7)C Z8);!QΨ)bzVɍ˳)'SrN岡yL QP3G4MSr>pơESrQYe9fr!c:&d̤rS Bafh ֿ^d UP2yIs/ȉk#rVHYxU8\TSzFKxɗ2OӘHe4JFl-+@+ʝ=ƋK頫ZTJ$B)bOdZ/Fb?ɠ6_U&Tɏ;Yy'mɚItW7gZ6HV)6r1:$ nL(YN:k$3\B l6F1$'a7vIxo22_.-ޤy[poQ H%쁇ZS^kN"U5-+=/|( &!Q ! VgOu;X鯬<}EpI[Y=K \m#A4eKhO^}f}&ѬE98n4 ?M_S>7ǐN g(Qk#c8N! 6qBI=S6P88~y>]B+ \l n$]g%CW)(mno >O{ O0oGdFe 8d6f$k69U r A& LA6:)8E/ B)hm:a褣 cp1766t vZG ٠萞}q?%^b^k26zW\϶ƺMh( 8E1b6Sߣ[w!Zi%6yMi,52$}+*)m["خ۔{B,wnRr&{~Xl?mVa  Gi6v q)cDo@#;iJXq bj"& 0.S|oWJI9kJJc'3jĉc\Dfr.L5LTb&u8jT=/KTXN/LwT_sPiX qn!V PK؁PKjHGandroid/support/v4/app/ActivityCompat$SharedElementCallback21Impl.classVKsTƑ$%)iKiBNCJj6ঝ]7&V$vcðaX60a?pbl 0g=|+Xà xGn])6#EUn; Z]w[2,׸S7–{Ad4fv}˵+ #p 鎖rUn3LaﶚvdywxulE 'd4oue vPuy\M#iEΡ=zMG '`RV@Yd'oq3,RTkrۑotu,[cGMy\1/ūXqE7u>Ie=J场-)]:w1\8[O1CFV< 9ImgY(V^iYOcAcʹ1*$'+ҍ#d%hV$,];cNjH(eҲL$tf;^;xvOKR(}Sy)q5Eg(Z 2KZ;5蝉55Zr?H2wUv*-ũ(2y_R[$[;e|΄쇔x- Vz&Qd"K71%7fL-QQ'qSi~LCsPKqx PKjH2android/support/v4/app/ActionBarDrawerToggle.classYy`T}{dHBEH``V !&&y${ V[R<JQ֣֫jmm3{vw%f~s|3䡷8M7؇8n,Vp8̓#?Gs0=7}L}_nGf-e]Sb@UZ75u4߰u$Z$٥oڴvafL 6 ^'C,P`L 9;ڎh_RӶ`weU;ВxsMzl( }d4>f( ĈjKjO\ _DT4D5%12?"Joi&=p{L[䳤I6@GzU1M7';?%Jzn5_H7olB`$\.zHnv`D_L5B]ZW2px6i12uʣ2і`O_\: oz oalDf VH{LƏ? dUiA}M dG(ʃ9PGؗ NEF6/LrJ Lڷl![Ӭ'#֛qb$ L_tb:LhF$uzX:ENjq}IQDC3V 겸#0ZCɵD(eYbGvbod(,P){bn 3[NCrXN- ]74;b~׶#ecӿţc1+PŅ яsi8Up ÉvVcy!?I{n! Ҟ;wN9P-s K0[[8!}ރ$lDO@^I.Fe-) d/\((n^Q(B1 ~QNQnQ>Q)^mhbre~lKj~Tة.ipG ;,/hQA--ۤ[':K] E-<2&HƔA24d)4 `[<㨛AA㔴-8E5TQ 1 /fZ4h1ĺ΂B9i+Ch7vc.8 5٢)ۇ0u'"SN'G<.GXɢDub[nG)S3E&K8LM !PŵGporV&=sP{2RaP.'477b9Y.uyl &).8PFMKuɬnAYM[cig_SͤBy =v2Lw`؋yb5$X||b֋눲K l0Az`L#V2ava0ᔰ8qC dT "]\HqY=L!q*n e@}" ^%S|M& f=F \G*RػG0F=x)LOgi)Nfbqb8b jO95$ձ4^9@׍~QϐgQ"ʋkŭ22u)kKD.p}=pR1p*rgFyl`^"0/W1[ao#!X+)W,I?4L_ r9Wģ:3v3 ^[( ,-YZi⽏oh bpᠾgemܰCQ J·ֻM-n )),HR4b}t1J]n4Ƴ q PKC(W _PKjHLandroid/support/v4/app/NotificationCompat$NotificationCompatImplKitKat.classWi{U~oLS*EvĢ--XD@!"q&C3  .Z"̤iij;3F/Va} B 3< C 3< 0t2" *.(ưaCA/[f)ϖVk`b' Xn/J81`xRS(^^CoFTfP[zj+1)o6JC4n: b-ؚiM==k&ZH4zKbD,Sxܴl߁>% R+p@QgRE$<+ 5Z#Ʀj8\Ǭ)]'Jf:m)b|J&Žv {7xTZ V_KȆ5$HL4sri ' IspN<$^ P.2hb&vHg^# lɸi۫P|1mK38)Zq &u]hR $M*7f ϙ#ʨR 绗^6xeaxUd7d8}';2ex$ϾpCqΰUd|S%q2yh 2.಄+،j}2MF ZeML4V5T FӎKL3/͔Y0 x)G[g9oRcun ?Imʘ|֏iPSɊ?^4{`FSb!?yI #-H-q u9Zewc%iAWջSa6 I( )$)")&HJH$$Hd$SHHn"JRN2f$3Hf"}sZ1X-5K/cAor9SEs 1q؞%.}Y!܃L܋ (^^݇9鄞H.%>K[e9=JlU.w.>TL^g ݡelmEys ώ^d sG&Q´eeS(ageSj0pth q'Jj:@kHsN[HW~Ɲ#u:H֥b/3U/PK_ dOPKjH7android/support/v4/app/NotificationCompat$Builder.classZy`^fggw39 g(D٠*B`BA@I2;b[k޵^Z`i-}ۻU{V{vvf[x׼oȽ1Qf"1I5Ԩ AVKM]ճ(5Rs5Q361.4SB"jS N,2bOi6&if95Rs5eV}Vt55ky*5k.Ԅ1yS cite5<ʶ 1mQW5ƺ=,\v(l®b0];; u}{;5]Y3ok=  "\6F#IK!>mij6ki#ڄ֑̱qү/fRǭ=erLO7d顤fDuf<[Æ~A{=z&k1Lf=x,-7n$[ ;|YJfw!3[ Z=I: 2z׸5ݦ̐=@C1MJkg"_岖1_~w6FfY+iv+GHz*8T옥}q=g`;1SO05[M;l!wwhjȶP\|P!ѬIN#$qsHwtZ6kIK!c ,$vv3)Yڸb64WB^j,K|*㶥!oYjLqQEF3l#X(n9 Y.г9l[;Lw_a-6Ym B]B^m5r kAs `Sdg!aqSEWBمbGˬ1(DCO86>Ad2Z:xOt ~~]B9Oi24=DŃbdlC+01F")ƌ(N,lBHgr9ʢBI PUcLK  i3^!7?O⩌ZؤmzZv3xP16XHI(n0rOOÕGֳl/a>˨t`? E,o[;0lX7U~r,m\ihDa e>(0kl-yOī{ md&(x4ͬbW'^ͱ,: ^5[dgZpD dgqKgvi8<nb` -5V 1$HRޞRrC/-U]p)ge-jK* 9/p8VuUԫ5x(VJ8" bkgJVASdEXi<'VO4=O_Q~,>cP4ުSAyΆeQitFxw j;6ᙐΨixeL1ӱ2Խn>hF0c]S U]/oyĶ9KlN|zt:BcqR_'nUZ@(K:nnh穛tJRn’J y~6.*vpaV18B*ذ p &T z֯6*CgPs!8eC0VB #ԌPs5mO$Rx텽* a{P}yalΨl? tLqx1^ReJKM0~SĻ 7=}^!=$F)hWk~ñ/tN؍FQ_q~L\##\=~m <c ͟R < ͮ|Pc Tt2>({Y'}1ל@e$M"l#m-E6A, u9|97[\4zZowZ }WXW4 LWx'_[[o- =.L.u#s+ xD0#.J4M-aL ]L^ 5+PZhpb̧3n"HBnyLA89L#.ȸh-.Eᜀ\9pC}^}|q')._*}` أ`_ƪQqE/mVn~L[DWD¹ 4cP- T~~TX*8KeV.r̉8 ~ ?`{x=X򋜆+%yJ h QA@M#.~!A*&Dº/= lr9orMf#c$NeLZ.oI 0W\<p1oń2_O8aO_X N-h[[ ŇoZp;*+[%;YDc1$C"?C*g}`#`{2fiOWTOy0DJ@W;<$>G6PIJPaZ y,X0P{89].\ lN  f r-PVB ЉuŞ@?,X8%Š  {`WL(enEK<$0$BMK: 97a(G##PKrnN+PKjH8android/support/v4/app/FragmentTransaction$Transit.classJ1ѵIOz=x0%zh/ Lg̗O78IJ CGjQRE*jA+8-jRhV;5(px%7W.B'nN"[볷|>H \of*UCR[EU^jZah)Fʹ x%;ζBm(phqѬ6vxo.}K]=d8TW}PKź6PKjHFandroid/support/v4/app/NotificationCompat$NotificationCompatImpl.classUR@i ޸iVEGڱZcqF- f3Wұ 6ٳ9w.v׷6DÚu06abYBL8pa*z+ kʭFCw<ۖՏtn:w idn6BC$,ؿLSà#OH=a BOz,a4J.hdHkSp|CB*m)+zU]{JPrC~u5L_$af4-zJ0{v3.Q=*K5w8sTΊ,~ cU)1ک Mn5 IqWeyA~dHS %=l8Z=JjkA9i-%x$#L>Çǽ&9}TRw0DCʼ(5ll߉tEŲ=W\7e(c͐ ^dMzaٜF:Yުla6ZE.ķ:_WC!Pb^˳]AeKߊd jV&!X`1I .PE%g1/Wk__fq7 W@堈4E#*N-n2GȑӴҿPKHĬj PKjH.android/support/v4/app/FragmentContainer.classAK0_nusns<) AoaaLl 3MJVX~?R@b5/op{eÎ Mv υ@pzj!0 '\߳IJc,3-z!:Q:&}O$64=,h_Yȥ)i}!OWϞOK@25tiD=X`K)aF(MWe GDo-,ÿp?1JJ^/JGss 8P 60979rxkcN[9PKjH*android/support/v4/app/LoaderManager.classSMo@}IRhwB!NH6"H[r+ٵv%~KG!v!{y;o>2 *EԋX+d8SzۯBJWJ|B˓^fț}c[9"yRÁл|R)\{=УVcq(YǛ+KK d('2A1OV,_4g UV22Q)}﹒G&oZ#~-Kz:8vl?MN=̑aA0zܟl^ Pc:A3UO>ÉÞ9iO EGH3r^h/"5/'uULUG b\䬘9 a鐇c)#SN}k[$*;q=ẉTDu0fW1lˋ`0gu0yXEu24Nun mr8EsV`(Ю9a5 YJd|w%/c%\ipB$VhewFrcv!E>N0stZW&ijuP(}PK##6PKjH7android/support/v4/app/FragmentTransitionCompat21.class[ x\u>gv$K,-yCY,˶lKwI`97Y<MfF6 C $ihYDRH!ƄC$m= K4]lȒ|Z߻.{{ORuS I#`g<4%b%2zR\oxAw4DvWֽgJlfЉ)h ~<C3B;%wAiDTuR)#ϲ#zJd?W pBxcnI B- N ƐSQWJTɎ풳E~IY3͚? 1oP~+0˜\yLND5ʺ'օ|4#!Iw$k5R S <5l6'Ef%Vm"Yk噒P?-~6עrVMXE3WʘLzP'ʐR4JG;X֟4Zydw3Y"<9%:|5y#i#%X%x?-+Ϋ,L;G 5'ϠQ܋"#z0:Ш0V\+jjzB0i`dr( BzԜW:bsk/78EqNgSA\iMQ8:ЗdښqR=I"UCo0Ec!@sQ86[bLGe;w[O$-Z$<3݋?7 c Ph4D'5zhJm8\Tlk^rƋxF/ѷ5:KT2B@k\+fuոWjWktӸLa4n'45 NVTNoV۸0Y4zQzijW IT$3oxH¡zwi[>>oL2a}k:ΉqP4%vPnުqH:oeZ|PXD{,I&D!LA8nsn $DXZخn~Ԏ#LȀɧh)f)0u]$&,g8abFuъg#y)3뻪ϸm^ǃ=ǝ0-ѝ_GQB!݈y5ev`ꀈ*S)ˉ|bU dUS.]c9N}l3FdZ_3C<;0N5{7˨ P&=+lfʛp$,UNdؚklkUS>k"ѣf<갘{0%9TD^bUT&G6*5YԊi{^M׀vYg8MNc⭃T؆) p)A(hQ-@-KZZf0kTҺe RRrꦐr1jhO5ǂԓՋYά )z=8@SuWc "-"3c@CsA[d ϔHYU+JJi.5aˆP+UZ*Ψ K5tJPR1*T,p !o:h?D I+m3T;pJhRUd6hʬ}UUaˎ;7vxR:O&QaͧgtI*khr?s B )8t)y~̖:D*"JXn}*,v{nȫar9}giHP)Dx ]ty\)*{W~8<I6DtOҎ<.f K̀Ef&97vsZ`tv>]XT(}\tsiBP!N"uéZ.$Zu;F*DW~*'77+)ﴻS4\Ti ]۔ <hqUr1j0KDȇC}8(hp Rc̕UU& 5[Qg4 48TWuu-\8GD'u6}?*A!8󣴀 TPq$"4$Lu3@ggoIIq j/#'XfrjU:}}nH ށ\vN9Ee?/eÚ甛ڱӧ1ZNJM$t/3״7r: K_,ɷPw13s6CT𛩨Syfc?M08aw;'hFƝ ͅU)쨓/IB#ce0GlPϨ~N+W^C:x~F2>؈M t9et3aRkQ-S+xnjo$䰃g 8TKEljewc>5r {t*w} oL3MߤH=MLS4N| uV6Vc'[uXHj؞cc{jCTeyz\B8ih4-~|Ԋ+4$<7ނ2<-[wh#s6-b+oav-<,|HYd-%dV;0O_V] ;@) Z+9ZERҾQJ-#$R>O|Up S|E,0Rbi?EN+U'hs߃d=TΦ s bq)MA{:OIw *ӻL0]UfMdҌ_C+ة8^,!nIGV%iW[BR-IrI'q#$P0p)7j! x^tx/n2ZԬ^ Y_hR\'*vU,=N[|#մBe~AAwmU^ $^)',Gm᫃TUX4 x lyMe=]WP @+x%Ai6nR[ih+W|ގt1]Y݆ҿJ!DyQi<12ed?O߁Py,)?:biRb/[((J Zo/۠@]zy%%Rq nm"oټ6#WvjF{3w(lDM83F0r҃Ztcgn(&- TIlxܻ4E(ݢp jsy SM JF]jqȫW,U{UIzn0A5)8n#@eQLQ<%"p khߨTq֩6֔NGMJm*IKjE]rpfلjf*!Pt4ҤP7f2"uAS`&`bFHmRcn¡'͡,R J_G% dQDÊ 6s 1]fu?KǨv*w]1]\+ w8ݭ:NpXPI0Z@4ZPTK-$OAʔ!Zyvak6c8Wu U'hJ|աo|Z,E:Gs\X@ ((EH+BQQЃlkz﯐+77#3tM0u 7m"(`>!K{[ޡRe=]= Sh0԰)Z{WBtU=PP >~A~!KPi+?_0Ue`Gz*}4/mX10/mX1_ U([~3IiRfZMUV3Zi$*N,~"Ԩuu*?Y'^kWi1,oTec-I.[nl6$ ʝVbĸlU8GfftЏ$AҵH;Y9wv?RXkWģ\(=[ JZh.W\0ƛ/]a+fz; PKK4PKjH6android/support/v4/app/NavUtils$NavUtilsImplBase.classWiWW~ LBъ !UBDjk/̘LPnv7~K?szӏ&Ʃ Ha`&g}~ZQ;YR=Ru`qi% EVì . 5-'k<.!2bԔSIL˴օM;cfbrv5hI Oed JlV"mH&8vڍHljon[Eiir{9k˕>xܳ=HOyx7g嬌$59Mk"zhdUue6dʈ㚶%욓99m KƊW7 @0R`\&Kʬ)Ƕ Kb Kp33v6s!]F9sN`[ѳyTqU~)rtKΚT=:EMe g%^ r \vB2C 4Wj0@`}m,qfTt4UY-.C¢QlCxY`=_QjxMȚ7pYÛ2c"RVݼedH3U긻uJ&Zx oVjMI+U4ͷfd"1iiC #ugU+ WXVRig"}l]IKXH'*t/~*)~@//TW~zGv܀Eg m|y#{ˢ~J7h6j[Mttހ?n6^Ò[&n Ρz?T :䰴P94D脸NKM؍'_MslO5ieS ;YKvQ-FDsu;7ha OK-~ 4!4WU)S-9,ay,'¡V\ R"Þ[WK([ (լ,2$B-ʒf$[;hsחjPKb{R PKojH:android/support/v4/app/NotificationCompatGingerbread.classSKo@qbGRzH*.AD*Irw]gP8Q& sWy|;^⨊2U<*m\8`JR=`pz: T4cl:2Jǜ ë(2'Yc'ɐUO_&ܼj,a*xԥ<[ K~2Os5{<틫LPo(TD@۔Lbd %3.unFaKq:&?zQ3 k76\fh.iakq?LP7վPyå"cx⡎B zAN fjiqI{8o~:0!'ʈ;Xuȕ;FȺypF Z +V:5JϋD(Dr7ĝH޲jmEX}J{@@mQID7PK?J#PKSjH3android/support/v4/app/NotificationCompatBase.classSn1=N6Y6ܒ[4y *H%ήJD| _/ QY'-UT k39;3//(2ࡋU\~d_| )>K߱fܙaX7cMSӁ4Y3xsazf_K }=J"^f9wyGuOak2,c;{=eښtC FP3eXE^ihoֽ@{$P+N? "H@kn>۪9ߑk~M^Y$7prp -;h5V[rk%Y!@S5%PK<PKSjH:android/support/v4/app/NotificationCompatBase$Action.classSMo@}N-4>P!$ d)*Vpt+g ~'$~biphv7?~xGm4q}BmlфṄ( Y "&ʠm}ChohIX)-Xbgedb\ӳǪ  F%aQfmx, UG*L3awD!{3^D(vzDX.Z0:"LcϥԱ%͒DJm ]p?H(=}戀#}`5)R'y֏rjtVZ&a!>k%c\3v |[̖wx'2fdmNxz')mN FDz]; ,lC ˸x XUTWz7*}kUJH]x^$[nn{PKJPKjH/android/support/v4/app/NotificationCompat.classZ |ՙ>l$8ΉsA|vBHBI!:$;hݱ<:I1m)-қ^B 콁v!)[.*{Wmw۲m)Go4=rv˼o{7ܫ_x^:-h֊uY參x7[E[x+oszjKEshvzj}MI5qzj}-EphzEKH릟rqN/+Es@4EsH4Wp=ny7nA^K zi^'耗 :jnǘI2#Hws!炧 Ffȹ9xsX(s#Lb% f62cLʀf90NlpLsqs־br͔nC$[0o4PȦ􂖞Jݻ.˴z1pL/0VHf .\V]BKzf399lƘIfcL[K #7mLy#΃Z?j3aʒͿQ6a:r`t9?1#AؔtLC!k4L~8 "ގ/p̞ZcodZU4X4 bG ~ `Ih6Ħ33mhSfDYe2قU LPrYs'?=5zt'MesZpiDk~-$а?wFg`<$t D1CNB65[7!j7i'jS2 G=y>̛Qd9{dZ4ƙ'bюć1Pp eRaBZn,t>_Jt/1 N14&̜ۣOM̭۫x1 v,bW ;.ޭ!3cDcF.!^Ԣ\dzjDϙLWa҄_ 7#Nj|"뼣m Qb7 26]0S=\N AJno"v/"NE܎*#) FN/dss"u^sUPz*#>3UVCw0Y/ɛͷ0͉.ULw"rQ&Lnju|WŚhI#G>aee:Vݿ%.w"вbr3̴fxhǮEǗ w[S?M3Bl=8wC~#6{D"2ucUU(l]ϴzYhH%X6"q_QnuO69˿m~Ff Ņ[!(iӅъ6] jSNNÑ4 `-bҏEF_W;]Eϫ O<÷f~oۘz=7MNWTT|wwn~{^LS3]yuNUh>,?揪18V|JO}*B\.حUq͟T$ԙbD_7mRud/0ǖ[MR:L V#HFn2`u. ̾.-}c:f~Y[ET `1էѥw[Ew,*p7vx]:& 5rFe'rxIXaMbX1E['&sL6-!DI##0U|!Gܛ,5rA#;%B NPUdƵ>a 簯\]LjM9L. nTyDHfJSغtVC4ISO98\=e&/8F Ӌ|<Ȭ{E.0ikx<#P˕6y3W>VP0͟:\1tD,mMa1A ,$hdj`RōF&iL|Te.‹\*gy5i&q2 i}J| Ucg=ωy;q(!S>֮ gyWNMձT3F۲1>(%x߱Q-pC93HK\՝`4:DWZG xTo%<81g=?1Yϛ f g%<|9ʋ_-HR$| i :ˋ7K-omzCw)w%w-wGW~?A #($qOH>%IOI%iH!%K,_ %)ey~ mwf>2+J?y .c~AO) ? C??#g$?~N_J77 &?a\KT8>bZ/(Qn?ǝZzECB(1 w'7c>ұ;FZQ#J?1%eE; _EkAH[ki|?h3z%s5|Vmv~t׾ѹ1ںeTSH/MzRGy==?p+m=v<Z1"}(VW==(u66?DOP׉i #-f7=```6Z`!\n1yżB0,>0[bJ :pQ?J)ȣ\Eaڤ(V|tDP( JLN*t2Hw)ݫU5i^mny }q;ZB>P?^> h]=!@:^ z 4䪧0h 4JC4 z n^8>~a#A<>z]ҀK1)9PK)PKjH?android/support/v4/app/NotificationCompat$BigPictureStyle.class]S@-mBCʗD>DN;a)r].e&J?x6 L޳y='Oxj 50{I(W2dNɼ:‘3llU#ҵkY^7'*;u;E.+mYxB0tyLTAx|xid;vuGrGi5#OE^ǃ|ծ9-ȶ|Hkwk5k×b[8ݚ-':1蹆y> U;Ks\IM!>_ ]۪^Aip٩2CB6'Ov1N(X [^Fo_qʌڈ 015 O߄^}JRױʰtn sDg t?'CU ^*rasboLiqOz]tS (R!H50U:ҋ់Zb" bDjPVB\sp9D0 0heLe(@f+b)}ʆƉV IIZh:nD\9]g>q[Oc"lGTKdڠM}^m jSp;F`=3}@r;$m0q`Y3[YLahڨfjF¼ )R !I\Ca<PK7yPKjH;android/support/v4/app/FragmentActivity$HostCallbacks.classVKsUnfLzHO 0Q2$Tnf:&3CwOB|lXhFʅKB]7Ѝn,幷;=op}sW_؇OhR!\i1$Đ Rb6A!-1bh6k ufu2t s=iZ2jYô<ZdmЭU1:EXI榧9hAV&HMѸ|2\Vch0k9Q3ށaEOdZ0S$*HPt[˨ĕfi1nJAu2QQtZDs1h%ԋ=%+d!%듡 ȥ^  e΍Kq.RB[%!C֔'>],DIuQRV<QEy T=yVSK$!\SCY/)0q&ڍF1/ uPP3srfR:s&va% Uq20rfB=++SЂ 65[AcHz`/(xb)x7&-{ 8U!>Rg8Po^Ui`صJ:HʗYfQFh,%1ÚTy nOJ _tUyܳJ|uvfUo!H}ҷ _gCwUL´VRz.*7`jw0l(}Cް$E}ԦsQe߶_#\)(qAn M j)3E}'Uչ5aNh)umC60]G,Mphu 5{5Ӳil}ZwG;)H؄ lw?ֳ{O ׇҋFԃxѓhv}~Nk((-F͒8+Hg3A?<rCw ʝ)H.8r!%B|C9t%o1-P/Յ'COc-H#ڨ,La%4&*8xs"%o5Nqdp9]N-\Kf\f]PK!3 PKjHXandroid/support/v4/app/NotificationManagerCompat$SideChannelManager$ListenerRecord.classS]OA=-]. ZŪ ~(7~kC-?Ik D+%<>{拭S>^kIu&9ԑ}u  .0Mˆ"f8rz:NӔqE*։>7H&MܾTR0a1l'2 {"ހ:KcIS_6D6<ڞrÜ#HÈ\߸?QNDx+ Xk+qbp.]8ptҫ\ީ]7@!S3Me c1oa>]SK[(RF}FD!U}z9X=3U̇sd'1u9%YXBUd2 V +Vk_C@zOR*( 9NlU/PKM%PKSjHBandroid/support/v4/app/NotificationCompatBase$Action$Factory.classPN@˟^9@ c IFvŒ[[-&Ab`;vNvj0 BaPyWF!T])5Chڷ1Ye(:hΐ\|]*xiwZd ުs-~hLZb:xtʚcD=tz8G0M0q)HH`<oDDٔF>\cPK;ܯCPKjH1android/support/v4/app/FragmentManagerState.classUYSA:K¢FMP!**N:nU3N*0ZA20i \3qE2q7Mtc=1q S&q˳[(jp*iq6޿7fa)9R&SggJr ?ĉ3{ƭ"}.OLh=^<=z SGb6!C}|z##wu5{Ms|6138²( U([D*Cfв Ci[6z9#tc DLa@`@sxx=}lQztIzCdQ{[CZk0՜Tu zNz4_jDb/rZ c[qY:!wFZgMN갣ةGV%PKh[(PKjHKandroid/support/v4/app/NotificationCompat$NotificationCompatImplApi20.classW{UtR+ ;hYԖXDH!J!HPq hf&L&@}}7T@ o/~3IiI"I3_rssSpkY'(3&f]̺i̮cv=3dg,fd x ln` ,M^L^܂[+IY.fw3ٽvJO|fhbRU*&NjW:cT3*hFXbi\NWZB`Rikbk7bx)]  C1%PҠbD-Sxܴlߖ>%dFeI-̪jIEӅ%l )iM6ָ Saۣtu'h]4Uչ)t!N̗4@}FS]d+x6YuŗϭoP q71Y= z}hV6E.3BbN}H2;iq$kGr[*JisJBM3zZԂ -{PTh9aFڌ?{M kCWk&Tޟn;Ze;4Wಔb ,J%HobS/pPGM uDjQy|$ɲ!ArlT\Iңi0$wg}~IDÈDDÉdDgUM4hhs%G4hALl'eR/&ҋsj=.Q s Q7ypLALgm.uqYG1 %Tی+ҝ[Iv%$+k'C>LŴgPN9ARM X'H%bYz,'ɮQkɑ}gg%qM5v JwpU6VcM:B*}Ym/.׌en2[K=itwA~. ϱ,n܄p!+׍W9\a s0 I.嶐%4Pltө'YG~j^|#^w_FPK*`PKjHNandroid/support/v4/app/ActionBarDrawerToggle$ActionBarDrawerToggleImplHC.classTOA-]ZW BjKKhҚFE.۱ iw7*wOGd??fnbpy}oޏ??Nia~BV8l ӯƳ\5y6껴j&1Q6 (L |#%*+ucxz㞯a ۅ6a4qas\$sMq .USudH fucAjf]2Fpp5 *58m36F?F;1IjtF7N?NJ:t`$apjhp! + 6!Xi71ч4";)7RFT;à*[X 4hKXF2ncZ0!yRtjiiև!Q-JEiڞg0J RIa-U!d*} Bkoot4i|Y:E=YJ6Y ES5vyAt=Nc39.r:ĆWl"u3{NR&v7хK&㉉9<5Ƽg4< /h"%V,^Xc~3,]"/Q}MWaܝOVzGmBf~uGQWGէ 7e{#i9\6Pf}kiVm{ ߉`K}z}Y(z+\/X"W[}6~g:=ido(}U#/+eٰ!#xPY['|XbiE'ԙ2EyX4yjOilzA= u*5|Rg. AEH}5FiFeؗU/Ffqfu4]Kܬw@WDH$1V$NF isCU?zt dM+;Nz1Jh$І~tN3PKesvPKjH*android/support/v4/app/RemoteInput$1.classTNQ=ҲjA?b[>M&bH_d[wE$}8wV+;s朝|3%dA39  ]˱Udɠ݆`8S(lՅm \ۛܳwT3A:6}Awk5 ?l]/0 nOD DiAnĐ`x=n16rvJ]r{y(S{]lUu}c=t(`B<ҞC;x ^J?Q!6 aWql)OHܶ'DZQ}E/S/d0 8b8竅t)Ū.V)6ֹ/r?M*ᆞ)*lݼd@}1m'Et:0L\q u c!ϔwII* \@- nͿb;@nٿaZaq)z}5HA'!{2J'pl\\8&@G$΍*)vc8zډquvc8GuP".R}&(|NrCQNQ*Xv`r1(8y8N|CøgWJҋ~\n O  NFx:#kQMPKҪPKjHAandroid/support/v4/app/NotificationManagerCompat$ImplEclair.classn@ƿI:6-4P@ H m9cp -8Qqx(ĬcJKYo 3X⊋GRIP_Cpt/!uJ$%vS^ivt,K^-:,~{TȜJTEwZbe:7f(,F0RB>A&ڄo,T )N0vM.U?j-ZB8};C\BM&g0[}yVDv:`4]g#c "^Œx '969s7PKp=lPKjH;android/support/v4/app/ShareCompat$ShareCompatImplICS.classTNQN[XX T~D-EԢnj0{fjLјx >e-q9;gf̞*T\W1I)n´nܔzBZ n3t?`һ mxNP+ 1/U2X˼]S~71bzWpW|ͩsE-oYU Y+ppmнql:wD8Ym=3Wl +@g[e(Z uSԏB| +6¤X6 =o2,^J(b;v{;ض'#v2E;p)!O=uy,hXĒe *iX5 wpW=d3I1^[LTCh|Js:m { PHOmE.G5qf/H@3V O&JH3**8#Bg6j k7kǞ`K%w Ïlzp|a;}GFlýgOL}a3TYxY/!xy,xI!yo ,VPKr 8PKjHHandroid/support/v4/app/ActivityOptionsCompat$ActivityOptionsImplJB.classMO@ML>)B-ԤBĖؒx-{¿(PC ?ʡbU+;3μK%ܾÓ=f2ʝYݯ|TJ/,>CEtPͰPliY MMK1A!ݳblcÌ`PK+PKsjHLandroid/support/v4/app/ActionBarDrawerToggleHoneycomb$SetIndicatorInfo.classU[oUq̆4Υq[(Nt)M IڒI; 8 MZ.]"$.o<FB'$~S |ܪ$!Y̙f7o.$PG%%Fp]@FxG6 LJL%ц2jr7B7% 35Ljv9%28_1Lj:eWUeFA4"pr+]':r U| 5m`ZNw풱n*0rfY6_;&캙g `cw^@˺%yQ3%ϙKUjRy2omT veW@9UVSX{nr7 \g2\iQtf`0 t^qU8][`3}R=$]-ez'reۯQה:%Hh-ahMCb`[-5e:RχTqo t/oe. ,WQb1-xEG#1uCEquE{jxY,߇K.nxwcc8.roK+ Nah?^ INʪ&fbNyN~>ml9NoY ID,N)5#LP6g2ҳ'jIvaK>_nQaDHΫn <C"A!:IE_-D;+6&?eK,K 63Zʁhñ-Xc2-Gw!:s4 x$HqPBU'TNqL̄{ 5jUQQLE@v! e[KgTp荩Z:t}ThLSZcK˕Diy^MF^(~n&un+U2Tt*9>ZANeO*w*&n MeSy<~m,a9®@?R.7si"|.i` !g| Ђ-|oPKPKSjHFandroid/support/v4/app/RemoteInputCompatBase$RemoteInput$Factory.classP=OA}Ǘ\Br tR-wAe3=OkY$"Ca7GņmXՆsX\{9C= knd;u$wLJAu)SYmg`kN`G6KCЄoyBX"fZs [ tD  pX"(dE-p8Ug:ęU-˕oPKހH3PKSjH2android/support/v4/app/RemoteInputCompatBase.classPAK0}ٺuͩ} Ct(f1A3k;5TB`1.no M=oPK]2 PKjH@android/support/v4/app/AppOpsManagerCompat$AppOpsManager23.classS]oQ=wٲ~R6Z+^ƴ!1ҤM_| ^ݛݥ>G.FX)w=PI"WI&C^u X C-C,dknfXn>l_u)n-޽PqP> R6ޱ8Wr߷ pNsER^`]W,.$Ī Kz\Mɰm|rcp:l!yT;n`+ȖX5eAuW_g0Z#Cb Kb}f`DJILz{Ҕs_TQG]SZgY 1a/;\jA.Cy>{-P[*M,"M"m!LlZ7w_#Z»=eXi\pUpآ[HMmn4kw4=!p+PkA}_Քfaط)r-$c35fFB:%>@a=|O CZm1bb:v5E'k!Ӝi#$1IcLxF^߀Ewr/^,ݠ*(adbehV?PKJ7δVPKjH2android/support/v4/app/FragmentManagerImpl$5.classTOAm{PEr`LjLXRߖ޶뵈O& H⃏>Gg䬗|of?}&0"I$pW{ 䱤ıGAf#0_\2۝V|nV黔F#[e(9r+;{lKp&p۵űY%rIťpvG*X2h[%&+{PxItŭf{:YYJm5y-H25"7\ʴưa•㶴a ub``y.o1&ݎW;:\TkԮmYkm[6vZq0CǸ$tL`M ܌N 9c04=|OHYVnŐ w1=!ØvŁKYН.Wj*t.$ӏc~BSt 7&VF=TJM N#T'0I"O`ƬvQ(^ %Bt|G,7i3i:Yڍ(Vي4 3~ $vqe$~E\%34bGLj>} .R-Jp;f"s,k]ĵ%H[oPKJo]PKjH4android/support/v4/app/NotificationCompatApi21.classX |ݙl&\DT7 !m@K\ ͂md3ŽpxE]U[>@lmh}CM6lA_7<#j8q+nSpeFwǐ;U%VIf9|u{h}r~U<$LJUl돨xT;1;|Q1`d14%e[XW, I"04\X<K抄iLʍ(.p7XOQtӈ4I]``D`맴>FW̬OոN|[c7G7#FLSF4ɌўH f…оRf٨lVVeC3:;lt Xjj{Xb&#Q+%z$tQkWEx^ͩ()7Pw(BX0kDTz5ѺXX2dvw(9O|j.#M'2FB"R1++L82H4C( b(hHcvXY+J;'y֔MLk%?EM2LK.ԠOo4x N ë~W H'挆??k @$ڨ4JpljZS&5+/dO_@y&֗) 5x:* ocƂ̬ D41Iin҄C85 [ J([BDd1ESGtMx Mi'fI1G`DQ:x%1F\3 KrcsO%0J'#Q]r)cdꉴ,dKZ670%L#b03iE8`[" n D`١굥ߕΓeΏ/[yZ#_W􈑗}͹F^ O?~E^(=WiBcR)}ZeQm9^1}anskŧa;6Qw/Ev0LxYW!Hh("o(sF];c9eőX~(h(XJeU^ebX</)bmng -"+yk=g=D~.OD`#.lWct6EVz KnZb#2k?,0޹okG,f}#Wa?JRgU'^=q+/YIgдwѡ(ٴDG 0>,"|hN$ Dm6|2qІ/e6 .n!>ՆCĽ6&^a+O1 lMچU56\A!" O% O#^k^L8À+Gg-~>9p0(gac9#Д&=-`ހ38!9,.i={0cŔTX3w5JYh,| reW3[Or[10?pb̙++a*lFkZ܌p7 yg#3cL4_`cl6-!p#e-lhRY#l8]opWP<~ZSڊʤuD3zOEDqkVU֢xA8ѫ4?P^*rm7OS=#>S=3xNWžm`17!73`Q[0w`.P;؎ tr"E}?w0e= dxw1Og?'æPéiᤞ.%Vkv)kv9>PVRV(`z-t%IA&UrR6&6ҩV? ^Y~ktӘ9 8+^؋cQ>z!ILL2zs^&+9qk#+I'P8QY9eQϡ!yXhl^5\?tU7P毙c;v'X/Y"gY.5\٢LX;q G9/qǭ\9Up5֢tPK˚- PKjH0android/support/v4/app/Fragment$SavedState.classTkS@=BӤ-OłgA P)`miW 5~aT?݄R={v|`  1n6{rv_`mBC S1k|W| sqy$J `KM9<*Rzl1eVgWV5sO%CÙuUq_HISÙ CmIUHsvj/n ^n !W5<T؊6,KFXgDT^"J7,X:eٸN@ͮ S5aCHt5h!rGd*&LVC-+Ʈ1c=݊ C\4oIk&eB_iFD?X`5d3vH>1Ȑf爳Ru 0eǑHXGY $"GW2n"1Ijuʐ8u@SLPK߶2PKjH2android/support/v4/app/ActivityManagerCompat.classRMo@}qcZPZhijq+B*-H!*.Y-e9{ ?!;y~?~wmZ KXq+nXP}"4Oʝ1CeWOCs p"C> Yhc|FV0l ZN$"7{(rw|#3isŏEÈmLݐ{"> wfmof(ae }lSed(z*$y(w҆ g>KV^nt̥WngA[n㎅ u4_ZpuLNo,}`?z5R$$45Ccxx}iЋʿ- tr \3"]Ziw~E,U/ \J~A|JTiCm?䚴ZZVVe0qدb&v̬PK8d!PKjHKandroid/support/v4/app/NotificationManagerCompat$ImplIceCreamSandwich.classQMK@}MZM<V`h%P{Mv%لMZIGӘC=(.̛v}{ypV&,[h2Τ9?d(8/j=D检2^`ȵyNdt8p=hW& UW)'Ut$};qS{vj8Q*oS+HQt;6Ck,ҁ3Bp! 8ɖp*h=ѕ~?3^EE 'piR؆A;?]Z ńl|)+[dKYe,ÜlwPfN5Pl 4oPK쏤UMPKjH3android/support/v4/app/NoSaveStateFrameLayout.classT[SZ];FJңWĖkT9G`cf|ܶ['&$/8skjm{ﶾ]O_`Us.%Z0q9,֚àvEf(bUzw65u+ J + %0Tu<|DU.5:ZQ!pGSST(`ڣfalo\iWPXj9/TUn͘ IedbIjZSaٕQ(NTt(.{)i(T/̦*4MV/Vh3,f+4G 5(4O(.KMuӥnZEg@̈́XgPJ"H~Qw=h#xB`M_wSa誈nR9慠7'ۍ&=j/'``3ڭż{OPB =0E댄aID΄(4D,KKBq؈3:eբZ#| XpG0sPJoEXDW ^9P3h|QbaG?k΁ H!ݡ6#^o b_ncۋ.&`Ete i7]wz9a'7ɋ[pVl#m%ȏaW`ǻ˜!?x"ebAvv NLǾXdI6uFHi2"a~MWY'mtZ9I a ĜӟvӅ]iV]X{QkLa)hɹJ,jq7TZ D,MDbL pɢ1#`yGĒ w|#rv5}엌m e{nCU>&-Kr513e){*Ń*Hv;p[Oŭ܌}nZr0Ї aภifM`4+U⸛VBU\Ri U2ZV6&7}B͒*g4F;JYoDǝˬkkca]EBZ$8fuSj-OhHBk34aǸDBk/eˌ{i;Wt%]tJ:dEhZ*6h MK:W憊E7"oSI1Fm_vo-#<8hn7uTM'u)VMAB8Č%I)US),BމU<"r43kK5|Z{HP)BQ7mS)F>#Op)C(g]$]Bhv֬-wIPJiGZ8ۡr go.<tiyTIئ_@.ڭuO*,5KU#ݜ#|s;^MT>_hţn 5hnY[V^߳Mt;m%y[3rAw2t(uX0fP=K֞&nԴɍ7*QUW.)颲QH J~)r>.=5ۿE-")_*Q4+:nŒ )\!KKbVAV=Xre״dцel3g|9Rx '+/N֝Y"2& X9@N#xꭖ!10}5XL%7Ƀ&fꔣFuݑ!0-LK9VšU./PDd&֥uéBf v;K5FWR;֧^bUf] /ڙg k~a5 Dd3)HS͵Yh#M[xߌu>pJQ^g|@e5@5{ 1jJ]ǐww #MQ7P7QPY^Ksr9mg<e9Mي+R /R]dMP@ˣFFJ10d4;C&>/d9aⷬܘ-%SDb L#(Y^L 1n~ RًrW1AfaHC-M`+HUXHHfdq<84Ka,!*6:K;hI, fI栈梄PFXK2NWU=:Q٪:Q[~,W h{36@zW[Ell&V̥eX@˱+Z2ޘ! )%˲羬҉c-GXGa5 S 3h nlY`GṘe Sz+|ђDz},leC-LK'044{t;ہ{3H}Sc/cް{SJS)"W菁,fG^9Yoمt̺A L+B].1ұpx:qpos# %  ۴ R g)N.BA^I۴gwa+$J&#)L;CAYYTN7z4-_rxʉ~N3_ȶ~ȜIUxdQR<_s01I04e_2سfgp^\§ϑ%&^./@׳r丁Q3&쒷q<[.\Mz7ҧq}!|J$ f#[Є5GFږ^.r\8\n8b)Qȼ'gx+tѹ8վXK)' \wȄy~.Q\(R{Բ/84<% RiTX. .opi+dԋe͚^,O3U~lχ؞؞===|yul#l'؞O3=L'$=ŵ8EZ4^g[z=h JI €h'^R<ɣrΤOb 3pRt<` <ă/EYLVkѭ0U_mQ#r]DZzUw kSm =̱wde.i6z7۬3ҕ«kF8zћ 0bzǼHKe'p%$)Ncl0WM>'^nWKy+%Nf[' yRڊ {0rͥf{ ɆtسӞMIs貗M=slLY/sFnAC>og2vt#X2VX} >Ǚr9t9c&>aշ;>rȧ;s x2OVn2Goz%NA{C&PKjH+android/support/v4/app/ListFragment$1.classR[/AFkQE)ވ! l]ăG3Q/n3sw_vE?Md0fMaEELt/6j+7kq޳yeWe*'dtP.QcH`J%.f]&_x QuJ `ΕPfJ2Ċβnq3GBxSZPƅ}d-% X.CNSW jc~j]遏e݆%%MB_̓C3?'|C@X-pro|ZR.\-!Ӥv;|`ΟZrE?*с65dk<w?~ QZ(eU-e k 쎴e!],3dv`lH[>4wěYɭcIuD +@: yAvku~uɠm[x}A[#[溎mHr|PKjH2android/support/v4/app/AppOpsManagerCompat$1.class1 @Eh4qXx1Yb$.IgtF>-PK^ԊVPKjHBandroid/support/v4/app/TaskStackBuilder$TaskStackBuilderImpl.classPJ1}Sn]+x?ԋ9m= z7òmL&[6~%fW,X=8y{AI}anl-md VYݺF _ʗr fՆ ?RJBp][McpjUu^,Z{Fm;N oz*vnT8aӒfahL8M#=CH{PKPPKjH%android/support/v4/app/NavUtils.classXwW=[Hۉc7iImyQJ űqUj+V .KHcyiF̌\-4{)[XJ`14S>99|͌%{owGz㿷_p i$ A,p">>S|"W4WwR&|o>/i<#Ẏ/EKrK_|U uu9o%-]t2V,˜= t]53eCCS  2T'ah,)ь!qClEL^ c3 c\,1‹ $CLCKE|.gvti rтc~h35i&2C]EC\{,ikK}e4t[h̙'FJ%xًP.fTNH*9Ygy3,3H3ڮ'[a *YNtB\t$/O)9# 9/Lo".䠅O+&= okk]ލy*K'3#$]% ͖6ca&Gt jCc8c?3tZ|HwO$]Bc\pddHI5gkT; Y-+v$Gv>nolci{MK2۔:~dB7}bp7aeTJZRtc u[˪1}I4:1]7l |ǦH) ?I8҇oU.ptgb$v?R.$Z3=Bu/ꀽY[ܷNf<]aI5-',FY:smASv<,(HHh,QJfL糄4[*q*eR) (^%? ?5䔡Z2,"(+-B.[95-hjQkBKW`rmCVR)^$}70^`+gK/K^Ŀ$W$\w}pSk0<&˃[Z*$:[K=rԨO?4ANJ s%5In.;Éop'VC_Z\^BtRSWOKآ#Y`-{ln90JA)lZ WCVL;mHT(L}i@N1?fExe\]U~$XeX-hO8 "5SbX4ȯ}ԓny`)1_&~ R'SP4SENũ+V]tLx~MEZ5pJOUeoԼh-";9IOYE`5TUeGa^0]1#Ż}c8J?ָh'{:ίQXA( DZ5ćZVP;[hri|&^#z`4AO!{p7y+ƍ"WyڂǗ:gpdxDnp UԮ6ώ-ɕlq g ^CXdMU4¾޻V*#}{2 -P־ 1Woh A?Iq1L:089GSEG1w H EB\}fsAY:w"};f{9<@xu8ataZY d&`ećć~"I͎ %Z$.iJþ4AA^˷!"r};NC$fVYDv\/@q䛼 p+&"9qs-d}^y$ͼxoyg/ZA{gjEum,@pv Z4iסМ d,W:'h攂UBv6PV9Adu \jMjkhx xk`PNz袇i yz-xx)@ Qu-|;M!3tNh9Z"/CNwPK;—0MPKjH<android/support/v4/app/NotificationCompat$BigTextStyle.class]OA)n[Q('e"1MH »aLwnD~FG.X%5Ş33{37ed,Ҹ3Mmni7Q4QbHU/L9N{Nm /0'C|a!YۂKO 4FN&~wy >>L-cjGZ{ieH>N76˃VOql-*up(wAK`arܵQ Kڔl!Q]O'9~b@5.lrJ5um-eLGk56 cׁ9 0^l!l_ l ah0^!i(gp.YŀUL (>"2![dw!i:Iz5Y\Cf3F,G.Ƈ!T]:=yvK{0/ǘulm\W 5NCF9s8K~Q$um4 oPKYPKjHKandroid/support/v4/app/TaskStackBuilder$TaskStackBuilderImplJellybean.classTOARJR(-TѷiiԤ[cޏFE&g(](Fvۙofg&`+ 5by|Cng;V5T5RI!Q2$%[RIGcrLnrW2Ir^fo7%\o ~\1 ۴ rri^;Ҭ PT]CB Vun:Noxy_jxn,[4j#]oQ(fƤ KFzsibp9WǥgCͻtKstc2dȶ5Ş Q<[c>9ju=:iAǦ K?aUW9&k_ƫ>,o8nY{qEU6S&hH_ oa4&O_ic EТѠSNL}hOGJḅ@F /MRO|̋zbB#RJ,>!~DbM/"މ/CTb+yWP*JFٯG7pdKgI3?PKdA7PKjH?android/support/v4/app/TaskStackBuilder$SupportParentable.class;o>c^vvnFԒ҂ĢԼ̲̒Jϼ AJC'1/(?3E?9,fd /-JNuIed I,.ILv*II-J,KdgadP JD?@] S@ bK-rI,.N-fdY t:6120201# #3 &4H ;''PK=֩@PKjH@android/support/v4/app/INotificationSideChannel$Stub$Proxy.classVsUnm-TЊQ6MhP(ѶTRBgd.q-08>9Au}U? Gvl:={wsD w /t`,WËj8SILt`Rpy / XU/[=Zǭ ĸ:IvCƤW%)ǵgVmN.h*6/}GF#Xq^- Jk5h snk֋YJ3^,98[v$B-2:Z퇼:d@v=e;(/Ɋ}nT|xcU. 5.ʁ U)03KMѮ*~]uY&Eka̩ @<˔mzUm>+]]倛O˺WnT`^%ߊt/}0CŦ:k &XO5sQqhUl ݘNa7B>?(@a2ػ̬V>s+wyZ<@T(4K˪j%@:*Pۖ;9M.m݄--);0{T;yl}5Hͽ_ 0- mvwoWnVj6yDTM^Zz:O{sr'Ƿz9FEt>7^=BTqġsľڸaRMFQ<CƣAqP x eZ8bCd= 9in"6&g< T>atp_$x7 ur\`fӯdodhasT cva~F?PK'e] PKjH2android/support/v4/app/FragmentManagerImpl$4.classSn@=\LKnm 8N@} j$&^\ڲRS$> 1FBHK;3;;3?~~pZ⁅p5 " D݄PsY }a('Ƀ00lu(tQyOx0*;" c^*vE;1Ca*f4=Kv< ~k `;J-y 4\k\2R$Ed CݢGF9|2JC$M4lla5,۸ ƒeh3ccOmP8]CYw$W*2w)uAe8_^F\0{W# S ^2 jժaڙ;Z͜3*U>mʳ䃡hza8uu;#;$>90,)45瘡"EJu: 3̤%SVL65}'+:An(+P]eԻ PCgLjsԄLenV4"c p)Heǩ)bINLfhĄe:zQMڪ RFTQUnYёb_وXF2FFT&ʗ~a "^ _D aî3rav*ɹUyYgB!O4/3zTcD$Z;o2>#x^ ZDA#i&lnUv"~vx i!xI 5N]RZHēx>zvvh?i^Iѕ:Bu C/'| l2t~c#~Kutb21$QG~o B' N-'y2~8LAF0JzC'0VN]D;^z/ Q w 4hPG`+p`w Lf$d]39f<۟P袕mb~ g@W@}*mtaX_[if..fbjwa~2{7>:AB&{[hbDr礇9I'8IDQcI#XG(#N!=YO8'J) eJPݔnam 3vnjJ '(!sn<2=?"rp1Gp;gy=C~J7XJ0S7sY.>ȓ\w/ EݗxNvhV%}[*ٛ) {d .}CM|)႗7&s7ɽ\}_ă9˃HFЛ#5[`;F|@S x dG2we-Z7[  0-㩑NUZ[;7EƋ:64Է.iZRѸaUㅫ;64.iȜ2ye .Imjhj`u`oX|9TRц-iIΦ}usG;6!d-_ڸaeb&dS-h vC{ʶ%`f|0O[ "k5*kAS؆PJ4xC+ &fк@o+6(_ P;FHYހ72":W.Z^_ō0/>UKҩMYcӖYDQ^щhv;:зux7 6` mu:"|$өFL~L}0,M&BH+jg>$n Ў.M<V–2R͆KV9f.$!WS@A>zFìa#n3lbKW;2rfJQjXJT!5ya#mHR oF3[ve/[=|q}*,6:GCqkYæ4 Hy'ҌڤɊ߬E|k B|*/Ot؋a+苫-9XOcĚMA+X~rۼ.Nw;QI>LHA:e L”O$!MDTCp6Ӫ@gй Bg6CNFmǘˏɶ0|(^iZ+4s7ʩ7Ѿ;Ԛ4+^}a2MqBI˸w%lt9R"FA# drt(Zv Q"h]&|r4#F>u) YKH'CAU_a . n%C*I24nQm6MoalQa6`9 ;PaCk7F!M1wGa)+hAA 596UVHaNuei`Nپ@)lKf~l$!TESBﱢ2RSk |DcRBYP+Ҫmz&CP::rek]H/9pGzl5˧avi$3͸wZ$]FX3;͂!mB< ]4BH0oi5uLRQ&z9$MܪORIL͂rwMg3tGֿtiY" u$R& ʃ!>ICi/+bX3erFsΔ$\|k/֏dkAӓi1OE Ù+C6+yfKz0)fp1Y\P>?2].rW)3ffi/ c]C' w:/ +tl;p.ޡx5MſucZǵ$$x nRwM ]Jea^ꖙҋu4񨓕Yt2Oqtx reױ/JR'- uYDD1r.8]tY*t9^Nqy@r"xiUMrY$'ThRUZ5.k@񨝪 ^(<b  }!9:OyktZ06Gĝ:^;g?3K]-B.ϑ4y.ȪųGt/qqsQb.XE#KXFr]61 XE7s8Y-BHR]repț_מ20&,q[o^ëBoě(4Kr.;t7]իY7u8 s;&C X+ʂ!̹>˯&4Y^ܧ"y5,aɕP xB|kYAKA²TNY9bŞtI3BY5{)sN3Rl0L')K{|+5cTKRdgv@me,1P:6[M<4KD֝Bb9Yk#l>ZFMdŴm!/8K Vu1A e6=d1'Ӭ>-GLjho tJ$>ԧ.MW$?$&>Q0qU{J:x}Ɋ Ms97m׾YϚ8iWŵ0q*$'a(_?(鍯,|ǽsM'>W]M]ye]|ZD|a^~+if'%zϪSR]EDoW09gW ;4בM'd_s,~mmLu*>a~-*oJ익Rs1Kz^u\Vr8wU .g_AĆBˍr=I bo^VDu[GΈ]o d{XI"8al\ws)yi쁬(2xN ?2h!6;uCiop64.%RH޽IZ,酋(:oRa`;oT:'qk..s&=G._} ~Q0;٪۷`}9Bm ;ͥ1I޼?l馦1,'fN֓@V;ؾٷ~~F^B_KUqϭfg`X=Rp o2V|{W3ّrc[,4J .?x{J"; ĘTwi4P`D]v.4M$n.~6" TA!_vپ`@ش,_1UR>B%!/Z /|(wq';Pוnp4}]Mps0ЛГeѠ _8^iisl74;V\*_麗ip 9Ą(aIZpM$ c;:_9nނMDunbzNzܒ9wܣ_>K{%t1D9TJҜGz^*2QINpDOGAdP=Ap JnZFhG7`6u\|*>Q;'30eZ3u5yeqFuřG} hwax( cx^ZY& +KJk`/h=DНw;_BLP wL6P ~ Bv*vІ 4s:a>d2V[͵ U wρc@j8TkXzO}O-,}S%Q( Ze⬍ ]q$)T|,R )+: y{ * 01ce+ EH0 ZJ:@rZ<^|&5ҥx{;-8b5 LN~K6K8(֔XI`M`Fe<-iXJ 7*,V28$[8Nڒ8glFaV?n:[Ab*~%man>Bi! k^u>S宍ŒVa K 0=; svJU;̭ryu*Q(v#;gFo:!XxLj ; 3gfΊ¢} Բ!8TJBjq˸Hױ$A!_0gB9΂Z svy9<nz2xl^|&d-=Њ~8VN)p.a<-]^ 0Eút^94jiDp}^%Hs@>yaIcLŴVc oPZ--f'RSVTA2p"?{ lc9c-JAڰ?pS%!Ȇ\J KƖD I5kJ" Iߧ<WN1QP]SLgҒ8ǜhflQxrNj+Mѓ*7[V?J-3?fXĸVɜ.'C0 =6 ?L K; : M!%qI+_ۡ&א6_pxL86څvBļ~1^k3rO:1l2Ւ[n nm)ff`Yhn}%*j`:4P HXrUi'㌎9y\wL;^`3Vx1p&8KN`ѹբsE\ aV`raUhJ~o%1&cM]v/4Ϋ0gPzZNR5\Ĵ(A"$&}~ 6<9/nhΕk0u럧c%+EAnB9A4c^Ni c%:KT:L!ԋפ/ _SD&$(Q(-ń#=h[$ɲ)U$ ($KR4r@9q`9k.(duP0\J X 5ȡ:u 7{PiVտa Ɖ (4N+\m b2r|6_o+y=n&*T]}RJڢ(a w19m?(TSs;(Q}f;j&}df΀(M.~:a0bvE$[wV˾YYHsA$ 1J f<.{ٖ7\9sQFެ( hԘժQ~Rs3 3;QHѿ1u9%;3(;Fi\"=>qOq6d#! ΃&P--):ɽsl:Gvnk.NLAX2D67Ô̈́2ϓz")N Y9:izZw!k()Q㾚KwA6 Y"4ȽӼ˨pZAո?n:TVPOZ(|ʼu1+"&\u !9R8Ƌ`oAx~B [Cd!x\-$b!^]qcqI᧔K6{JZpGLcN!lsq(Bʻ" _Se˜8TT=0ϱEi*N BNqDL8iŬfdGᗨ/bknI˙' Sñ'/Vx]| ˖@o͌jemkCTyp#(D98fuUq'VEW3[?܍(쓐K'&Hxd&LH'̒Y0[f2Å2.o̵.ݼ"6lomP>ZkZ8m ѥC;vAΣZ!xʪ!x*%ZKicA\N&愜mRk'1'-B5yzy^(,? 61abRXCd\Du0Q^ I6X:K.{O]Y9M l pR޳'iYoyY[Q80c³Vxpӊ0)aBnV´p.=!:Lz9r ,۠InJɫ wMKTRYE\RXΞ"|2LQFmRNvem_vC U;-܌Q*R06:RXƣhPj񻐭s1PByz2T"9r?+FmX% N\(QoUʨNx]eˆք+؟;]sS(=D`|;grM<{o64ܛz~^=拭 W৩m J. .\X..f(LG,Lm#1 19J{ʊ 4A.xIlh!)Ӗl w. |Vx~A:Lx~|*-Wo2 _|;3 /~(^PKyh%SPK|jH5android/support/v4/app/NotificationCompatKitKat.classWKWEy<:!$yD A^`H&LD% 4 h7.\BAWYu_aX9TUW}t_x u|#r a>"X0n!²^VBC)At#%l5nž1x6+ qԝI_O2J y Uv$7goqe)SZ6Xmi<,G'[挕pgNw3^ε-$ʥzGVV̔!H:`; jIt2;g: 3J8% kN|c "m9"nfr(ϊμNOX5JKv2rw7Sbaʽ<&ɼwM-N-{a^]7Bru(cJS1膖p;+mKVvPAZU z' j (UNv4_+ !~ݶrd7fpsMK8nJҳh"Ns%(sB>$tZH\gѬ`M:FT`82xZa*0q‚"*ld8*rdh)5 \ ֤䑊m|pF#Cӻ/n$m]="H|0trd?I+x0r܊e:i3twJʅ(ե%ͲN\KJĐEi"Wia14KK4,tדЀ; K̬s;!䄙-n:B$Y?˃N0W'#"/x5.B={S6˘e.7¾?2tUL }H1K\@}ʿSuU4.+-jJcf<@%C;a:"R[{/+| /{³p= !*?{L%@P ZS7E{BkQ׶3Զvh!9i to%8+Ml]εF^g\ k8A"G!Ѝ71JZ<;#={. &ENasuХ럺S} obbPP*6`ÏU ?U@u f/Q7r>A?ZøUJ}CPK8|R PKjHVandroid/support/v4/app/NotificationCompat$CarExtender$UnreadConversation$Builder.classVSU$$!!6j[@(ETD.o' c'}q|HiGg(s7kIaxsϽg<|@ &8 MI9=MniR҂yl`Bi,m`!n-/La|OlBCz,}5Iܲjx&CC<ׅU(O[d­%)YC[K,̒6Fvsd7\TajAnM2d%jK]#Le dL!,i-4\wck SB6֦魈ͺt&'5ӣKmzź}]̪QZE6=_( ƍO͢]Y msF͆T-,0!jJ8.}S3[mC)y&9SIz{"H/r,af(^ѷʹsE.d9EACJM_/k (xCsRCw򿴠?H S- !+x5\&(ШZ!ͧ:T; w\]8`gb e )0JZe}|V}03V^ZQ$pHArʒ D'5iه< xS,K`7 ;UޗpSPG >-pCm|S bHgw03to]|5&FUӜ'7ȞVBdz=4Z2CIs< |jN 뺖)f!1wzz&?5I=A͌{ݠ&V=}F7XeXRnoM۶HmW ԁi&0P_ש W3>?RҖܰr& M4rvL,gsЦRoJ!JL3]M=&EupH/ @$o2P4Gܩ>H e;OA!Hf!)ϣx3A w!PwB(-:Vb' *4EBT]lbGxB^_BOG QnOjih;Iϐ2]Jә5k6*{Љۄ/Ġ9$4!%pg8P8Auc' 11L Q Ka6Lh:X ѱKOHyPwtMqwS9|z gședh"vw&툅 \ 6DPK3-MF PKjH6android/support/v4/app/RemoteInput$ImplJellybean.classNAӖE""iiu&$5 &pմ;%ۙ~Vz%9LW0] E/v93sfS4Kr q'rXP|*7dWk v$lUr'e'yz },Doݐa3WP VG)=OB9v;}_}oʡdGqTb(hWہvT$UЬ`m|vjF[r$EitY,\#/7#.l &g2Lj*$UcO(w0t $3E$`Ks W.+Vx@/PKZ6a}PKjH2android/support/v4/app/FragmentManagerImpl$3.classSn@=\C* - qҸ *&Y\gmN )|5bigƳ3g|+&^䰀J;"lp\ ;yԮ0[/v\)ތ=wCWOiř f[J<E<9TE*I/v]uD񷌆M_˾G@ħ@G&7q a :x-<&Ï!?aLcju4gC~1 _$3#:q_MUa-y=£;"ޣ OKa~dOY̙DLE*W^QLk4:9z0R'4]Uh4Z,QM?Mh&`V}Z e}FS]$hhEl.㼋kdo:'(Z\ KH]@74-J%ܕ)n,{4kd#~PK"R&<PKyjH'android/support/v4/app/NavUtilsJB.classSQoP.cdlM؜U8Q AL|)&'4132[ l$I==|9;>,4leQ¶Zvr#]pKz,!0,# e3j±nyC|͵xB}GΤ<=wZ+Zfy/=Ϭ!E>Cm˷ܷ̒"+G]cDPma9@YGA ޱ;m.'{3JVKSÂf;<mJHF3Hafu%1kux'/QtBEWX2WZY+cSZ4Ol.h 6"Ө:6b}dG:J5a_PKadPKjH<android/support/v4/app/FragmentManagerImpl$FragmentTag.classRJP=IZB]" ](B%FқDWCܪ3s2s侼?>E9 ˆ0񪏷2Xfa] {ە*H]VF+# )V'=i0/i}@S^r6f%HYے|FZ&A/LxHܥ*>ű5R(:N J=.:%E/GmK]ra)#%+)Ϩ.!3%q/ݥT+7; ˉoߔQu/%@=H)9DaL (v!\ЊcAP!Haw pRE$iHH*RFIJҩ{%8|"r] F7z۝7 \v%ڃ>b?ԟr(H/Q.!ۅ_<|4)ai8aa#]Ed %'yc)G_J}]0)&v1. .. ՑPpA 0J@__gp9UCD B?Yi@ ͝[>S/,[PlW |sprh}C]](fP]aUm 1Z {S͙pJM$ɆUӦQ1pai~9ET8eMibY!t3Kɢ,_BEUo>xaaL vj̀A^GOWb8c~"_^7z-W e $MWmq0kImꈞOd|T,i֡[_O-\2- Vk(T[[*`e`کa(Pbau&0!3va`]AwFV7dǂU(0wW:Dw, ,5+D/ұ5Dks#ʹ*4LhƷõ D \HHoNJQ>:]/H,lT*U6ʃy $ 50<پ_'Q`ȀX*jQ'X ^]WEBX\lYFbԅ+QIJsۃĆk%s#(kGFD8ÎDrITEm%6BP(!6QV6Ԡ=DWN6b $C;DֈpkLE)Aa%.MmCzNأͭnCrdetVJG;2UhHS [;V [rԶHGjE( /1\ $?}{ePDžFK ա#p_o jCk0S~! 52̸@9XFոkk}]4Κ! G0:;qAnP`inwMG䎎Aw䟧t$!YRh %}wdRPB5.?Ip\9յYH&յYGVdR-| 0ˏnЕV ҫxK{u}qm]d=꠵C踴~O V]?U_ۼ5l)|x7_a8P+jM+pE)?EZ u=^n]8=PAkWb:B3 <)BNΎЌ qPFm;bۣ0xMJu@6!)8'j0⢀(=:18݃ >,Q?Kɦ 68lIu땟kQ㨜 T#nS:E %BԷK5r`@UB_uDL/2~fB.CR`~($7ۉbF8 j ^wȂfַE =&XZŠS+G(T[珌޿  Q3KZ8`5L\c].iˇj#Я.KQgoBF+_?4 Tq#eGDyV}"`Ӥw)/HZcd^.M`qsp$e H(VD1_8F6΃xGcXƶ #)`V'`ԓ)jR'O]M,c`2_,@qDȅ4k8Q'1RY]Sĩvq&b]ThR4vd!MT4MbI3S@FNu:}'T6O_ DyyHq%n+#'ZBZ'jm<%R4 udjVˠo;. U r#H҆>$7!O51cgi,EP!kE x: =j =+Caf^cc 8G*+f+i/\U]IpKŃPx"!ϊǏaGV<' ј0'EBq?Y/b&i|KvJ M5Fr(G~C%!DحfѢC0~G#cI'hkb/'SxZ<_4ƻn@M' F^/ieJq \k/[.ָg"StHLK|-ZQ {q:%&}ZK=x6)Ɖv&>s(x;sD#Gioؿmc?16Ќji*?񘉁Xo]] ovu`GbpTGׯ?JpzMX1<҇$EB3뢟]d%_LT#K#cزbkPH77elE7SJ#ЀPrp` }.NPj}~w q?°'6V[d:'~WgaxD[ TVbBaѧ ~}+*X*ObڢcѰ0d$ 2gx{ Rz}\^(4sȲLF5~QG7v8N:̶;vxmN':;*Mp w=s;40rB2(kQj ٺjgH;5 ÙrhwgG~=O ֆbb$HCͭQOMz7iw!1{m8HotLVV+,wYvYpTڧ(K*; rksl!:@?/k5ޭmДPp:DtN]O.!,&/KSIկ'#;8q+J\*2y$s3jB=]6\~'-FlVW6;?I|#6+zW k:e-zK%lk!Xn٧-xkz1jeq1p0zCwbIFQ6`9`>Nٝua,˙rZݰRvc垖r{YDR@{7==A3x6^g<}/2|sQfń~duW663 JTVOÒAmg]b:a0t (W!}dp0ZEG I&s%lD`2W6*q3K̆ _z& ePZ]}݌Rfpf7=Q#+zaph;کQ1;;u`4ШovwmAFFAN^"h}zX!F_P"_1h:p5@fcsA#Uv ؓZ|-i P>O> MO~ '}""IvIfN.Se;LA.s6C @=nѿ(ڮ0*iњ0P8=qFYqhXǒVfuG#Z|n e+hX[KVRɫ+u2dr5 ԍ6xhѮg^ A7f# qE#%B.Bcvtú4ُ DӹdlB[^`c~6 6D&q7)|+ST>`y-׳\V¯`s?x+ϰ6"[̿bKV*($b'd,҃Ѕ݆fnc6sfhy(SrxSP2t[*R0[ldϢ6sMYB.s! iO EvX5975 dAn 0hRv;m9;0K Lo *a$on3]x3?O)ld 4 {&L0v2;2G8?hQ%[)hz C %<( ;/s0ag?2w^;i7C>7fXHͰh'M!8f trQ݈"!_<&?zDќu$4@a 4Pa [aНf)Xc_N *{BzzvTz {5RݰԴBzCGUzП]%0] eP.v,`WZThB ,93+YTF\^SZ`wg\q οk%d҄[ZRT.T$lKF58tȵY1 '!oz#@ROa{td$?7,a NlIlȽp==.*ةQF6q1ףX/70wS=r ǗV{a%*PjZQnշxj3)R9;P,2]۠mG^@G>(dMvYq. SOb|㓆)WoV7(?@:QȫME-eJ ձ cQ 6UP[ y/,yF*.ؓPBK,tEwS&&&u"Z$Ta"mPφˣ]eQZ#"' = :^6p΁dK|G~d!uD"}Hqd"FD@Dv~c)zp쾰`ababM7=֣KWbXסWұ0LӛSf"X:MNPY';s:г Lns7x9Hv|gh*@*㡃h7e7e7%Ɯs]`\tj:NdO&*cgԁ=hhzѵPY>I v/qwX6B/X7d&f(HpظR\O<cxa&4 HLd 8ˤ|],4]B~S Apc`tA~.M,v""rw ]tc$y'אOu-*){AYr$7,j+U byq#0vcG;lN-(ʎ3<!CaۆtDᵦgbH̃r-q"G}Xue׷*j^"uxpۚpZ:F>mMOJ@,N k`7o7ޤS ngM7(r&qZ #'g오[M*+ɘ٠n)z 崘rmKݚ۹R6[#4 %ΌJdAhYjnlҡL[i- jͰyc!ng+d 쉉a2z6–niZ{k#qaJ}ooKEYYWQѕiT,/5er}.$cJERۭ\χ /Ir4WT~%WW\~5Nk~= o p)6>wNo68f*{Y_d~ m jmVk䏲Gx߿;$;ğ O9o` |/2 __+k<_5 ^~9#1$3ntN{0x[:$8\[ v/ֹ0003Wfj1)I a&W>SPfME >9c=$c 2PWq|5FaaA;ig_;1EgMeJG`=°0=3~@C=d샋GYz';+_LDqͰm B/WVlϣo;8(hj0+(+א^t"B ;Y8ar@HvL)'90\%Gl䷂/ k%[R!o̵cd *Џ9a  f'.'Y"1PH梭vd.G7c̵=:s|Bg _fbKr^e 1 t xH>SZFxrOwA'1jC۠$TJ+ Ph 7mǘ@zl#ب[NFXܖɱ(/k< c H"BDw&z@Ȇy"^^x`7p[ExP Gc">hC51 #}1>b Uo8NgLLfNQ(b&J{# vH%q!i[hܒŜ$ӕ$¢$UTIr!-Eoi?B:0Y@R:Μǣ:%UNth1ި=CWw9nݖِ/yѵM[![f78vtlK+fԄ1MXKe P&,@MXb1KQAXy8.4$pm+aQD5S:ԊiVlg o>?Uttݐݤ@$MK˞ЇSkAMEdvt(PiVqP`|>!:{h3Ft6 7^V>kXm#*6_47A8}v4NfDWt6S \nW3 봹 0BZQq"lYvnX!,ŨЎogcD \z=+3Ƣ e+#Ң#6-QjobqIP {3NRKتȕ(r-id[6zuaP,΁q.,AV !".Iw\".v*D{W7z8 n`.%Yf[0q;+`3ŝlNwUX{j\+v])`7!Xxnhao=JeO؏i,i9>Tc|xO/Z$^U ^#Ӄ Ü,VXz@7i]l Nf=h#er:#uMfm3hvK@`[G 6`aiZ}\*0)p ZUޓA neoJce+ss>srTPu(0bރŦ] gn=Ӕ=n=_'<-#>Hs;o̭ީFdt ؕFPDlEHe[V4!߲VM_]0݋صYmGin/4-Ќc˲}S7Vrž`S?ߗM}ihef>od[pC͹ӡ f*NHUR`J:R2`A@W(%NUzB(8˗(Je\JDs^ʃ\9ClP=0ܨCz^oF׳}tRB LNM{v!Ec}0:^<4t^2 1KM OrznHH{MM^OtQh=Q`_[z(PASF@We$VFHGh5㎮[.)h=0`eR2IIu͛`YM%9GWp^3}b': FOoqJ(Pq&Yl? 2tuDQ>#|6"D8 Noͣ | m0LW@CI=)u#\J22Gg2G裋;a3z# @i^D{C=Tӱ.FVэ(-]Ěab`g'HAd -6Շfbg] MdiqS̽ЊQ>\\lB{͸ H3ҩ |/!4<"ZF_ȻʩTN4n#qJ)D <%')B CuٝiΗ3}ʽit80#NK)*k>2`2>Mʈ9ˆҷDD~`,a3:8‰*KFo4![xcsobGϷ Xf$S[xfxg3F.lPInۈ_*Hq`@FKQ)͝NuvI -IŮ}r'Iңt piZ+pkTTwj+dZ ;u|H|`<+{e^>)#iQ뢌I%t>z'BseO 2k\B*;ͭ^͗h?3 jꟽz {/_ 4Вe/ݸ+9{]9[eCrtSՐ\:3?nBx,Un-pj-P+[ .،Ө {Y>د<o(Jy41*,Siac̣<)ϰl+V^b3Y ;Mj7YXy]nU>`*Y {]7\Џ63b¾BW4)[33Nr˨rTHCi_4vg;$th 1;\0m&/ f`K/?ٷVo?أ 7tV !{C#H$ĥ=C:Ӕ0i߁?2hX?4~X\0)vQab,fHfg#j;$98A~c4cJw6|](eB^E߄ͭߚﱣ_y3ChThմ٦!Xl9BdWe&s4vK-n[Z_^4\*&֯+_>;yg>0}hI>Nz4vhz j)MMjTD5CrJJ3,viL~$IS.sĩ9T9RiN4"ZF1|h` T O$"SO (S;3<:x-;y$19ȋ1jWHQC/U2{N<<9i,'7x srAF1ۛ߳U`7_CE$@#?>P")j?p YͅLu0Q}S)D Mfc:[dnS'Lm5LD I>8͘'nB`|ҡ,ØtEng{rWRdWw,{oǘBSiE-FŞπaLT,T瘛 p: 16:2FQN2qu8wg7iT CfJ=Ǚ=Ǚ=?2{FCTP ld.Tx2)|hR)EtE둪fŅ\۫r҉q2d `Z\.6գTbsA]=1< ZeEQ4?XipƢKl 44YyJ̛q۰ϖsj hsInL ׍]tdLsqi!MUGgF&O+DZa!==lw3(F/Y^4F}s`Fcw\ԵRAOu=`zg9Pk |"ܨ^ng>Bv/17Bd&)*.s$*]wi'Fo|1:CmxTFɾV"&a!f8n9Kv=?Ҵ:Pz :k8_NzkАmBCvi]З4uAe|M93cF[qU%͖e#6cA~Co:!Bf7ᴌhs}J|c>VP;p݉Cw2% WPIp%GgaF:88a@ ⹆}> 2iK VL[uCCt 1Kv4{ TD%dcs;Q6"KN:G=,mtގW8Az?,A>ԇB AlQ y#eLrzLA1 ߁d7`~a1ǽҼey4#C4Ma9\3SHZޟ3F$};2}(rr( Q$iEv1I? M} Oxi4)f yLr#"S$KMI,5$iO$K- ʏd(E@i󄌍Û3ux~u‡90#q |9gؕUf78:96?=Ico\spu߫p5h WӉ1F(Fcbb1YL馘hy[vlҲ2v``۴`[S"?wI|dw+ e;w9wν;~ ` q3[fEE3lTlaH{k}i];v/ZGzǰT~; T WtzՖj x]bqoMm_K_o(ݑ 3]/܎ڎ]_-Iю{6{0vmHZaoS+ʐ,6Z&ԱzkpC4 ODgSw&ƭqh %I}"ң9d1i -Z{D7N1}a-N4Z|gXA?lPLܢ)1 ǁ{pFR+JnU:\1gm SA1Ko闳?%AaQ\XlIm`F|'[Ff@v}gbWڑ1*(T2Eo4sSaP Vp%ˈGḊkQ rR6i@}"I񛸕TxKLЎ*4_*ܦSuJұtbY H0G(P(PK2$PKjH4android/support/v4/app/FragmentTabHost$TabInfo.classURP]JA"pIx (3>搆$$eo;Q-"!d'~ `22%esаaIcd(k}y(ͺtkf%,A 0t,W `\6}[p2b*kDoI{Neӭm&{n(0~T4 %S6FuUBOqj^r~<صwrn{oKQf*P妻 蛮kQy |D@NtיKwW4oOU}ƁsҲ /-ŋ >Lxr%e揫Nj4XP-E!r.*8ӥ\R뗸xM߲7N`W1#+:1!y*&{a {M'}kKakTÐ=oJ_Oxw@R3pC12#;'[;*Ca}HPYh4/l>ڍq܃ R[WL+8J_gر|]BYgv$wuؘayaRBG4u = eDD7 ]LE፼֡v!>|3.f2t YKzr ӨH. =AgzV8&Orh!#B8FhķۄJK;.̵H~~AV- 7H>*hB|GJ2VesވQc9IP[tf*PKɯPKjH9android/support/v4/app/FragmentTransitionCompat21$2.classWsU6ɶZJ AiZH)`)EEZ[nmm<(o㌭'flGڥh{=񏯯X 0 A;Gt0nlcc %쒰[B@qZIcn+eQE5uW@QKml7l#(>ЛvkMY4jzSIQ³d/mQmSSlQ'{e@È*/2L;^QRȰQF2ص˂9ɘR%,wztV"}4+)*a.Se8K5'K+4(yH}٫ D6lT୧^TvxhKv+] ̈1%ѡϳDݫQP p fS]g*m$411=6OlN6yIIm\hAc7ҁ*!wg$hEa?dOp"e5i)nNH[*a3cz̿uL:&=0,٬ڽF\s2yA,Kd,EDF e*q%J8&8N8S^e2ଌWpNy₌ט:.2`7q.KLdK% mC7jBxde|>q~e|OO!rLhڡlvYI7&:Qhq5*u7R{b. ,b)x|ޮ=mIJ)הbNr2[,Eǣ΅ԡDf;MQ "XF BBw} L5iUZ8&O๓*qpRWyۆ.eBe-\rL;4)0Ox˘(44\L"|;66t 7\j}g,6rMlL; @ 例[PZ- Q4Hբ&nÜc/ ՔfaJם>bߤR,K-uLl;> ȗ:Tr—Ogaj*Lr$eqS9z\ ,TR½ L윚O:&[#>yȯg :h^@:\5@^xqR('E؏8y8Httt{e+b8{q W9N3-?~'(V9XC#ɘErKRkрF䣇ס9 Oe7F;wlMaݯ[IKQ8ي KVTkiOPPKOLPKjH4android/support/v4/app/NotificationCompatApi20.classYwg~fwf'YB(Kل@fҔ.R  ):I0QmB_Zm-U-6|تxOV<3wsfw{}s} uy ]+Wq858"nGX><,F SxD>*^c L5Ndxu\5GOۓ,t }^Y_/ __ 5qoSμ4l ZҲ {KJfF2s=-ACBMi=~ޭ83;=) rIkukNl>I۹q=s~s@ϙikKz86g5b^ŷTPdE R\BYOCSU,!@˃c,'AF[z+SOk9vWՆi9E}_N D]a {#'*$w. v`mǺ,>}P?939P@2Y*9DF*`y,3q.etg&OѮb\JR^eٕ7P{SIy0_YV+:iJ/bGm_B h*Q׍n {si 3,b? RY/gtu"A9j5BDª84 Kg4?T# ?~W4ἆxCŨ~\ЋbE pYSa ?oix[9~xGip[ sW5|{5_i5 617wҕ0w6hx˦;X䭄"TVJۄMaد_2IPv9urr@{5`(jm0i +'Jp.ӰY@\.,nuC5qw|kek ϭ~dWMoi9DHYp'ێl]lE4vQ鈔JJXk)LX]XN_fǢȔT)p*R^$e)g%V5 ~\Qȣ[)JCr|#Ξ%-tg=JJ7Hw8ϮgA eJb͏&: Agx݉~BzVa1]u" NFM|q/uzsA@f**1,GNmy7iBA996`ԕC7;5N .j ]0 Q̍]DmO3mu \‚#tx"c1(9#aGɅ؃>4y2VUwԥOu{*V,m,G3Fw̜iL{ˈ [ !kf*k1Nqi\cPBNUQn[定)K:sfjt<+3 QNOl:ȍf:mķM 9iSDBq^p|+Zk!s3Nn&OLBgZr%,[OLzsc5.!EDxch: = Edde2t\+95OM{OMxrqa&>.ipiVq'&y.{Pws hP1^2K"f3)/ʑD%8f s*5FXCD.o!E 1,1`0T]ܱD{ \24\a0m1zQCR~*U)lIaITǜɆS67R7r/T]jl^ZNaOXc23q@ W%J9ADs&S~#kQE35%YGW4 Hz&Mz4Ӫ Jmh?ϣ!JblB gT>xeBM(-(U(̣<CkQhQqd&>/;@Mon[^clȺv]dͼ]p>FKS"'Ā?PKg%@PKjH8android/support/v4/app/ShareCompat$ShareCompatImpl.classQMK@}[kSkkЂr3 "d[ͺ7af|0/ON00gXycTY*= HYREFa)E&,D]Sb2SɌ6FXޞq5O*Z թti˂poD+x!TCK!R%kMƤt! b{sb ?,Q5vCe8 KF [L.\ 1Ŷ˹/ao4~PKnFPKjH-android/support/v4/app/TaskStackBuilder.classY tߒ5nRǏr1>OȷOʅOǧZ gYK}r ^|Ňk wJr.w93&'^=Z><|߇CxduX>%oc=YG|&~ˇS|[Z^S\6Gu#CSZ'm[#fl$fʲi+ c|J#I.+gtTSSl0Von 49 Ţ4lfdZ-p"qoDO$zRC=.-(qS(i0[_" `˄b;8 ևYi`~x.%^Vr=E$7ff& P>y*0c`e&Q1yeNÑJ苸nկ23$-Ph~\e 7x]ӣ'-upAO@rCdXOjZzQ\Ք+{F(s4iy63P1DK$n 9m`1Xg_ V+%N\ITlf[qL ץb;v.q/E.G/,lXoTžUv;SBp,j;X. JlNePb4sE/|Sp-|EȆNɴ.8{\W*]:1],89}!(sYʖ@s?EwccXq ^@ Xa( x3.X.*9jZ=eyXAT܌9 Qq$OoTJD捓̻CM<֫a!B23jp|^~9qzXGLg3fJ6\rus0US1"-)s \is[aY*ưt:ZD4T8 Yʐ3|N`vN~q,@gp?pajtMѵzAB2 `5rGrwyN''NbB'֡J^a؈bv͸@lATluRvۭ8#U:ށiH=\;Y`*%䝶<_SKo920xzw{I*Eڦ8ك@J $X/N\rq\tN]q!; #AC> Y*M8n4Mɺ5]mIVS7Ymnm]dnɺ[vL-w'C1$?{ι{﹘ BϭPopϛr8 &>omck ,Yʈ)yb*lV0qX-FJ˪,pFT[+ Z la-V& |lPX#**5QZkZLfSz"7X (Z aEVgviWmeI*؅wt{<~fw38,+} =1{#pq\vBkiH{ #vA}‰Ho`>A;A]aA*ݷ {p$^$T(vz GNt+C.Б䬯 a<&}Q'<6=`'L{{$54&@\"6{=>)]zp u s~F+Jop˷9[pt.p,5$HѼ Kv)zt18I=3Xm%-+'ZE@-Rܼ@zHQWb1gK~̟ѐEW^uz0T)>eϟ0_t$=o${4cc)XqL΀Rt.([T1Y ̲L.72OD]F-ū>EW"JQphq.\cO}'h,;ҝ*puMT,TcG'™P'ՑBz}H`2q2ރnDG}&ݠz8&xDƙ\)rYh%j#rG,C֞ҦF=q\ O!:ʭSpR\p AOL*ž8e6 >bRK/*$ xT8E}Dy< ۩]US_eTv=lls+Ke cR5aT1%0NIy~\dcN=xBNvOrPU,, DGQ2:-7Z^U6P0CوF>KEu0+vTv"xHe{t,LbGTv'ޥ2 T_*::0eX~<_ atubMG۵*'Fg䣬Z(^m՗t+P*/95^#: t ?"?d@~7XD#"!75+?bW#qߌ]~_Qw"?1 86_ 1G q.3 S>?XV">+LAc!'Sś3z{ޞh]h k\~P.%`+PvBbLyr(`qw!ouهf o ZͰo|#ڹ vmff/r >Te?[ˠ]Q9UOBo ՛9yeQQ_q*]_$.ò1&Gm,)69`ŋp,or4\S)n7+ΪYhBfHP eH+BwRCBjC)-rn;̝w 68;( 1N} x^C:LJ<( yL ( z(<+1xDOǑ*G kxJ+ S64W-of.Sz_Yʱ.qgm;ly_-2m(7U|*Dvlꯒ9AV9Nak܂&1h%QAk/`5Q"f茈 I3UZGTFACbF3$ spmnk}:qhļ8>~G0&a 8 O4|$\́{~?2 sXK6H*|/T2e;05fQ?vT‡~| SWkLqRpR[/mf7~v-6j-Өͨv0jmt$;qo 'fNB'^@'>+Gqc= `3?NI8/B?'!uuH173jQl@ {\y?e0h0|V|>}dpr/K<>;{IܝR-bj4n{_pA{:v݈ Iȿalg#Er$"$ gK yEG R$%GKHZG7R$ *Ew`7Re<[/",y a-{1?k9Tv*3Aʽ``}ˮ[|T@,G&_!ܧ#* fO -9Gs{tբ,m*3`)%_L >E5:SJk2.mK w@{IwkK^0J?`2W}p nCæqqg~!sg?=B?) [%3kݍu 9p\١caOPBg0 k lk +ka'f̄mPKqa-PKyjH*android/support/v4/app/ShareCompatJB.classAK0jun:M6`AD*g]2fcICU)%~y!,4l`FCXʜ0;>%CPU,/@k1HiI(*?î/NԐϲ4M#.ҔcLSa.N{ "f1w x= M&P*k DICy r*6Z.\24WDIykxlL_Mv"˕"Lk jnFU7PK1L0PKjHIandroid/support/v4/app/NotificationManagerCompat$SideChannelManager.classYy|T$w2 e! AAH$3aKd $3,l֥}+RbVѐqkU\۪mUkߦO|]^k1{l *y9yǟ0~n9HvXQ.v ˎl<[m"QWy͎|*!;>P.?W+;v)dʒUZʑ"; ͝HYhbT[tkFGq{6p*z&ƚڲ("0g,&L*3&j|~%vg;䓺htTaxbޑ{vT0GxՖu-2XVD1Ep@uԬI_jfs6*GK my4_Dx/G`LTVokǐ0bW}ܫ1 NM6'dRH?Í _K3ܑ@N^qerьQ v.=a_ =gLaŘP>Rp6ž)_U`M0jm _oLU6.#ZLfY!/=[4cHiM'3~7T+ӛym$I:6i99qCKR)d{0F#WdE'XQ:֔IG˔2Ƣ?XW(I'Hj0o. D돀XiO_-OVqঠZ`+}z )^ǽ( xG;iɱ4 MލWipX4(tƖQ%tJ$ž'UzVJ?&ӣ'lg''BQ9z^ #3hdc@GZX]tٮ#C^WyVeHy-UwWMpT-=U]_7a? sYI ⸣vOֲg#ZyoԯG3#t;^' Y"PU~nY:މDѐʁr:͌0=aA'obyUNp_i :L> f>!ghp/Ҹ:#n?~Yli/S魜 s{mP,պͯ@/ hSs1&m}cXMQvg_|JR&ǯe- /Yz(6>u=GfrS67k^:P3,ݢ37)a(u&7)^\h'/xr6=o%{|A8fu"焖1660eov_]%$C`9GEFH1uq(fd@yKjЊ|JFuT:-aa-ԌFq9'[h1$d^JeLΙ<Q91r% 0O-6[>31ۃ ]d+ [*+Y;06c~+"1Οm^'4#잰 vG2ٳgTޞ'+F'iigQg 0ϸJSRo&ߓlOdAJSjܓքQO/X&_:Z?*ʚ})gLjm`PD5_Q'؁s`7q.(ŷp!ߖRp/N_Kq\WWP\.曳(`zn{}yVAG>1<}>۫+.?tXi PJi&S9h&PS%q%z܅*.Xýو,}T>+Uُ~P%NJsDE{ح|7E"O+OeԇL㥷;WͿzAϠd{抪>L_1L1ɕc{",1(ZONB#- ZVZ6G5M8xB޴G_',~r沂2XUy#q5+LfYAGhb8 S ϠضWT`Ɓ"› ө7c.`>AV r/G O w 7!`!La0!_kB&p+Qn1Dqu~܇jvJH|(  m-;:68>2M>J)=z>D/l!)x{S& 7&k:Q5BG33ϙOqLâݒ>>cNa^־񈴋[6 ̞_XvaRa0E.<'8q&W2y`M =(n~7a] Gql3&鸲+30(يY-DjxjLIf89feALKXc%[cb%&=|CĜA˗V)Æ8Ix,b?&ETdX'@A5\tpQk:WmvjANI(\+G%iu:qPwȞ[=?y·.@1]G|.]: t96襫p ]Z~v|nt 2 ^yW堝9Z:3$JS@Β|%J1)Ɏ!(e]su]Rj`Ǔ%` *79F<'KQD/b"DDq& R'Q'dc,>TZbqc؜"a\,8pSVl~uC~ܒ_$ V5'II]obF8;7U\gs^\:.pX^U۪D #w%6orbޖp"ђߖ_y*&[Yull{߯`IAp}ȑ1hO?bIMb3}6saiv{-= ^X%ZrEǖ\|b[ e+A9~art%/ߕ$(opR@]h/8v\4~KF?8,rOZ>wWʸtK~_SaLGGB~MHiR0H ..><8EϼItfKH?!.DR85RQ~a~~U"F5szIOr]sUڦV8@U* P*iXB(P/":BߑF!9~T,e#ͽEMۗbjIo'i".&g̫=n".(&}'gb刟פvUi,xEz[Nn +c*PKR(gPKjH2android/support/v4/app/FragmentManagerImpl$1.classR[K1=3u^Z >lX\A)邰^P[v&Yf3Cffo m(˸|999_<<We,,Xbc]t}aUci$aI5 =IK=doCvPޏC0ݑJ僮2Ӊ]p-zA0xJ 4lq\ҹa@Tk-(=gc]D)1+)xrʲPBNݍr&k] $3SDf~=$B)c~ !QO}!r'nxdPW"yn#I^:CRʨb+ҫTUb.*(P@L&M<_ƩH0 :a0n &@Y uU:A9tבM t B3Ȃ$] R-]:ܪ>ܯN|Vg]lVgM5JMeC d.wVnt(ljgo&WCQ.fDm_uQZN>JrtC~*2ڙáT^zܮK mYD7* f.nHtgj'^-8:&`' Pҟ 7 362G|HH\*F4`uuw&h0ca蕃OR31v9ƥum\JG%u ?' ]v2r78Uf謗Tԙ)eRi[Q; Sg}Йmǚ)̨{vq-0B:풒dG?A,nR{B"cn>u h(>: +X_jE X,ZL,.WQ:*Rg TYJglp lHgÌwʮGz#ڮkĴO(tW8R::h:6 ME u!e& #XHĆb$70 ƥ1tǢY6y˷UDj$EtJTFB;iUeyjYQU&VO8H 2EV%iu:F!}ָ(EoY1QYi6!~yz_>|.*M^9ȏvq AIAKL{3>'QD%z+ciG*Hzu}=VMبORKZBrte](=*F08qj]fI7d0MI. QrI:jh'}cB@Е ]e2T~ȗ'a%1ua"w¦$fP, 4֏Se-ͪ(l Sp:Õ `xeg;ؿFWZ?wqn] >q&) @}0s2,M>W`K#KD!LW}'s<3(!ne(é/WⰗFB#Ǡl;a3COca͇kC{JZ4,/a^4> y x)p|-4SJ1rt#i&|k 5)edz,/OZTJ(Y3`[cUsx]-UϠPAU> $)ROͧWWHqK,F _sRE;p'q*'՗Z,C9?KUD{q5IvwB'=^A7S.iT>C1)A5֊jKK[Xh[(5˚r:@4=|.&yYp:3)Y+k >$&d4^$EPt AB~vP}PI1?G Ս#Ҡ#~-j9akEfL"~ISIR!CRj9J!އJ #M袩YINjqP%(=o*=| 4 4`+`q;q;w~(7x?S2/?|+?W<~-O ,ŸaN0[DF:id"Plgn(ʵt(VXB֢)GiV`>G 1 ұx. (γaJ|T`!Z{BNc!dyqL!!SvCL\F;Koa!װw{_ {ӿP*TG"73}a;ЈKpuuN֯1f2;_|6Q,jgR32 6/ki{n q2&BPKUjA} KoWgvɔ[m `jmɐwfțkSք#X vT݋rvnd'UƵsQH*wQOl#?a= !~ڿߑ=ćm\pMo;b>USm?1~|WZvD1^ᘮ(W\hSXxNQ^bR0 CJq㷟9ƿg73|_%; s/q )@!0&e+UPk<(P;6 O;(-r*:q1.g'1ꁫt+bUUVc-Zj]{da!eg1|ݻt%Wp I.jNjHvrc?n1*XȷD^|FWLU#J" vS33͏q{L%7BWل2e3-%R؆˔";Sv2ܥ\G^Wo܇ʀtDT hrGv ;O898_)Nzp9{ANRM:]EKϸ4#{/c//Q:l.VT?ƆU%_!ĻCuKF{ꉜW"`JB3siܡ$7d*?"J:z[N73NnА) dOpF%^k2J9bkҍoK%4|ɰ5r֔Q.bem~Xd]dfs я[<PKnv*PKjH;android/support/v4/app/NotificationCompat$CarExtender.classX[wF<fL#ˎP؉#SR5cyp&H3hB--%ZJ/ &mPxJ/tO}hX%%s>/߷>'78l }D ħQq\(r\PaHX(Lw2[EE}Z#f*|]82U< DVwv kph92lIVĽc YZ2c:s&NKz[)1]Ԭ襚.?]e39 W'2csd7+LesrQ-NL*^НE#W(vLւc EG0 ap[TkiRIm|,zjY\o]1%Gv2;p^0\9aZFV7i}dȜE4;Ű{do:B&Y7]?"(H]fjBɠeĒ vv VԝU :W<^} _P̒WM'mF6aàkAoڔ}WioѪ;|[2 7'R\u_YY6r֒Y5{tG/d,aoHN{xrK:<Ko jdtv ɖgLcFmx}Ɂ^ :S=8@] hĐ-Zy /'~5 /L\͛ N[nrQt|W ?t{f`_ۭK/9n>G6i  T|T~DŝV>{>wMҟYGUϢXG r BVCyl#]9{C3{Vcp;!9[}xQ*u-ve N#%~b#,soc:Y$x@8.kN\~<F"L٢(_p I~R~3PKjPKxjH?android/support/v4/app/NotificationCompatIceCreamSandwich.classQMK@}ӯm^ċ9E*BJd[ݰIGػЅ73ypM=yx'xRi HkiL,[#4gVdRI荕rK,;ID6VUrsñЩ5* 2ύu:yNS/*Ne.(b97̇jJGU)?r+fń~fBOB&p_W#Pi_|9i3hۜ$Ɲ[:5窍/PKJPKjHKandroid/support/v4/app/TaskStackBuilder$TaskStackBuilderImplHoneycomb.classTmoA~-bRDV -jMK^B| M5&.w+n{w5?s!Jifv^y?}63HcI bBz˸aECEC!% >CXa)x붅6YM>dx3 yӓ%<%ޛN͐5^/(A+s=u@չ0'r<ʒc@M'P /yIuy_)WPz%# ݹ88ì' F kw(Ks@::dȴg].0>YZ,w-niXBdž ˂h?aUG>&k_'KǷ>oH4DK:}l+.@ OјƓrzb%M'H&W>E3OE8K sȓ$a~R$K7ĞGlk> 3Q*aCT p1.E#DZ".Lp9¾s$isj*PK8,:PKjHEandroid/support/v4/app/FragmentTransitionCompat21$EpicenterView.classQ=KA3gbb"XKNQ$7\v K.FBdaṽ[!XADGx4cgXdZ=!)rPڱ"4ڨl@bδ6P:dPrE kc2M/yXEv"d 4ښ%uv(᝝HtnTf :TE(vPį+E @L 2V$"d^l*rT?PK-' PK jH(android/support/v4/accessibilityservice/PKjHLandroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.classVs-[gE.'Db$䓑d9ד|VΑOB:(iKҔ2әvvA@I?R)}-E_~hfnw۷e߾/G>N>QoޔxSe$lJꃋm>IxE5 ;'U %g^HHJIP%,p7$pNy ?7qË0M/~ ȇˋ3WyZsvvwVf-(]ŝe:K'XhѬV*~|U*I' VjG[ZN0^)v\Ll6PuM,#QeYh -fDHfUcUcU#3a`hT]qfqg .Vt$Wzi9L)-s'ȫG՘F %Ui!z( DFMqu LI (ѸI)Z"rÁ=HKJ:>} ޘ:$ɩ`hz4S =1hSgi3H_'dLe8yT&\ah18ÑPe}a0x*K)-[ c ۱Uɘ"qD)oftqؔ_SQ2 ,W[g0'SMfS݊cù{dOfjX{ow]世[6hR )K[KWkB7(LtR9fլUY/m0<{6]nx]vlSVmXz_씭LKɠ3ED,:#mnUuDS3viŒY)FE\φ@({ i \<+tim~FR?}cOI?X#+|ŪL'*T9_&?{~3 <:^IPc-*-.jZBd\9Y-E^g鈋')\*%:*=tRSpH;ɋ_2 FʏgT.[J(Mx8SDF৘F:O}~mD϶sD?F?O6[D+h~_m^K>j("q3N W!v3A$ho` :#5ϐ4}m>@"ӍφnT~{# /ZOKlvې퉰 A@Z#컍}-O3=8Md1"WCI٠, Rwscl0 A l"-8mV5Vl7Y k^zf(M~;xQĕbB7|^wGI|1/%ͷnnPfn/QZlGfb -C7u`]>Tu:k_nb =DPXdgBgm2rClwn@|4kw&?$k鷛N_I8=tXqS~F~.GT]f;ΰ8>sg$h36|bLBaEI'86.w0M~o8s` #9z'*LL)2FKkB''FHXbQO{ y1OR8a Kx?/0_PK2= PKjHqandroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$AccessibilityServiceInfoJellyBeanMr2.classSmKA~V3,+{>hADA …!ڽqw C?͝􊵰3<3>>?R1L%1Ys iI!WbE.eiYɔmUwK1xtHq;p+gzD˄D#T F#=3PK, *rv PKjHmandroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$AccessibilityServiceInfoStubImpl.class]o0_]3Jac| e\ 1"T4҂I* ?qWH\QeTyO/QG\pqEH*i3TZ}jGRO*7"yFVz:Q'2O4 ͝0@FҾEPtՁ$㈡UJ$#C$Z<3cX/SP=]fX j_DTjvBY~{5_Ű=',T0Z1<;ވ܋z>T @FGi2?"^<It|a-`vB+SBqVUI<ʬ~I]r/N XgcXu,σFX/CTVЄC tٸRY, #[[8EFuKS:OR1;̬eWp~>0ƅ0Ji]X=Ae\)c5:/P~z4WU[PK4f lPK{jHXandroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompatJellyBeanMr2.classRJ@=h> .\. @"hAT[cfdZ?qěTǪǙ:}}{~J6baCXҜ0d+Cؔ< )5σ2?1CîV(Rڸ}c@q,H= &`#Uu5A0><Ի5'LG<U>;MUVmа,?`[j}q.SPw0u6nFnÞ{oD6݆}IN.,4K'yd>(t&1G@Gd$o~K^@4JPKoBPKjHlandroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$AccessibilityServiceInfoIcsImpl.classOAǿK[Zj =hbcBj0+ `ץ^.wEQk%^lKnfnw3;wXU (q͈F0lR+y2?J re![ a&"z>k]I_}3,nxcِ"JOj/pm>*%XP~fȦw0ӽ𣐸qDRJuUvR5w@i4a%~N/b;"aj{$b/bx2r97Ssw4;"nb4 :a93. O.!xT҉'Xi`w&{o?V=DؔcsfnˆSyܲql`Ɯ1ocy1Iehp4z])ǰy<,,okKWrH3ҹ/`$d)v(f{W ?N/:Ok&'LbS04C0An fah5ヘW)KlWV?#At&M^So9;BvD1r "YTp: n PKWa PKjHpandroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$AccessibilityServiceInfoVersionImpl.classRN0.mRJ)%F`Lt*ET 56׹T; ~nҊ2sop>}%P c'}&cDLhD:iwJ2Osфr!r4*ɋ YJ١3 ssPF\ "BIZצr+rtR$UTBHsH>Z4clfeJ[_λ<^|ڸ9iPշf(% aL-1rxUkPpX@ 72*Y-qpJn?PKqO2PKxjHOandroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompatIcs.class]k@iCخuZk PVťBW*z7;)ٙFO^荂x&Gb 'y{朙>pNrkUg֝pfku\g=PZه ~琡5dX+-'LaD<:r?Z O\QaN$6\j"eߦ2ɔq`Wk1=2r} md&+7]ԖKv;/^E+{3`XT$*hxƃQ0R^89<2?91`cIMbi- 2J4L!+7gwi4Ķv<,;ӆa; vZtx$3u*tDZ~d6;uʹF/ ghЗѷzZ˃ΒXb6q/#?%. F)\ WV)Z;Jr,E~PKFMPK jHandroid/support/v4/animation/PKSjH?android/support/v4/animation/AnimatorUpdateListenerCompat.class}1@E Zq 0VĊH?,] AZo>S aaI2l%a XDžQ(<7NpJv+SM=Cf"6et@Vj7 L8 BHdd=A{FpۆqPK7PKtjHcandroid/support/v4/animation/HoneycombMr1AnimatorCompatProvider$AnimatorListenerCompatWrapper.class]OAٖn[ֶ (P h4H4S1v%h?b2mxsfݙ}' e Ҙ0ncZfu(7qDŽ͐(j&J˦[-_vgx^f:A(Pk}Qݶ8xq!V#!̙5 #W5~50^BN`=Zty>'WOVR)Tl>9H紆|zo#䊤#{ cqr%qeC'W%ta bNvbaw<- òp9KJXÄXfx_'cu=-f+%ft*gt\~7?ZPԐ40PЭ?VAw!OVoiG|XynFy~$(R,"IMBn)c% ]4,]]Kf.eE;d(#Czc'.9T 0'☩^1?`N1^gb~eA;t_ j.PbwS?PKBuVPKtjHbandroid/support/v4/animation/HoneycombMr1AnimatorCompatProvider$HoneycombValueAnimatorCompat.classSGǿw9[+`+-!Q"-P16 eZ;أ]rҙ LgtS!Qrowg{߳|]RnH`Y+-UPp@ _0P4kpasůW۫jSN_ju0+b7ڎuao9v+7s %Muel3DѳHqf'mW3e igCm4u ] tmse=T?$@{rTo`$sO j"01̐]vt2\0C0$pCY8b!5wEag)at.Im<+w-QeHuH[9%)#g0, f8p ^m_E%[m$`lyM%GN~љ9\6q ߘDYãAeƵee j%|kbߙ2Wo᱃OW}ި9ۖYTL*c ?F.}\/,wH/r"L ad5SThءd)Y +T鞤jt Z*Fz=QT>0R\2,l%\ςQzOoT&ds*!F{ ؟dpӈS;K_踊ʴg4A}w1dL=bGHC S ]81>QK qR<:B{4¦;4>g ;lCJ9a'[vzykT xTPCޢ)p/R4=/1Yz/c둌Yڣ6 5!/`|svs0[8%]ޠ6ӥwwv+/6NT S2-wR s*Q9zK8CRh$ȾE}Iä?PKYa5߶ PKSjH>android/support/v4/animation/DonutAnimatorCompatProvider.classJA{^SmMZۢ) -~r=6{_O|>Tq. h>q;;^X^ܺY^1Tiv(_p C@)aZŐaT~a]|Ka#8L=a~^D'Qa*4Z0cml0jP\9FbXNw+3:4gߩ'B3E\~HPK_bRPKSjHVandroid/support/v4/animation/DonutAnimatorCompatProvider$DonutFloatValueAnimator.classXksU~6M4- KՖji)--޶Rݰ **x̴0Gp9d[Rh+t&ss/hX=DacBBdŚBr PX>.ĉr8ٰ'x#8i)ę o\^7»8/ą. qIb|Jf9=e(Vʩ-V,Ô wjlVJPKZ*W9tQXR1(W+gv{ԥIВl.1L+6!4C b565$K5=8^T- HCz=|P0tƐTͺ ʁju ߗ I㧃]JRU|Xԗ힘J+dR–i (B.]̮MVʳ",zT3ŚJjY"$:9/aF~֪ 5<'L>6'-QJ?٢&UOY!%Pٺx<.!LE}'EKz!^Bc.jW HD5|*aOg\F q_ຌ^4pCM|)+HhƑ'k '`»=Gw,{j'sP}wx+ͱ:i-yt'ȼ)R%HYƓ[XΪ\NYMM)R)gЉ6S9v/%fL7Xou$Bt ǫ9i5ZJV=fƴ3}%۔rg`WT(šA(ʣQe" 3Si([6|3JxAQM2]II4NRM$,wɎa<[{0g>>w1ՖlZp |];jw1LJsҽ% f6Ё:r貭!EhEm7O%|@BjNFK"zsNnCs (FQ~CNŒBx]ax>A 2^qv=t*"V`0I8̰_@[=,ɎA$BNV_V/rO8Q`\~̿#HGISI Ga)tJLyTs&6Xc!pz,Soz pLTO1J=ą9UK'1DIc4Fo4MDi&Sqfj_I]D|}Eb +Z^BRcϳ.1|S</hDyFF:A#gOF<]Ę=OW<HjA*1ʯʯPA䔕S@UndVDdFz'xG%y3IRyݚA|iee,)[F>)dPK}PKSjH6android/support/v4/animation/ValueAnimatorCompat.classOJ0=]6D|zcA] M]RҴ>%c ";_3$O0aT=KpH k/<8c3U9eXӪ,u9Ւ2_ؕ,KrOm1D]دY8'˂aCJANS#I1wE|a`j˩*$QFE-W~ɂb8/:V s~zhO}l`p[]~0PKgK PKSjH9android/support/v4/animation/AnimatorListenerCompat.class}Oj@}cS"h/݃zTDO4ʆ6?~@?J`qǼfxF|czv0}_ɝչΫDyK\6rY7#% /&'^s6F*lm2YRog畮> 3a|d+V%.$}( -ͧ{x>PK[d>PKjH7android/support/v4/animation/AnimatorCompatHelper.classT[OA.l x m $ƶKdۉ.nw6m ?'_Q3K`p.sefӃA f5c5,H`V#Jg4dn*rHn5[/jZsos&r&naFME0<ā-R{j k= 52T[˽Nm_!ҍ |5_C4!A}0~;3sv̷š>hB~pOÀ EQM5 i0aϧ1Ƹ Q荸1:oϚNlQ)#'lbJI+,0'X i(y 93sya'-sH/ɔ {iS Cɕbn[Zb;K愙ٔ F. 6+No򭶊0C l%zYq [_-|9(Y2nh=/(ΚC,K{p =01aVG8cKO(e|=|:pVLSALEt H2$+O,Ї\\:jS2;)I97pJ#’fu9'Ϥ" KH. nyNq&y`ˬx#3 m?y-3oDD_05*Nk4&-PG'T}(t֒0넼4[[7z+|:;Assٮ琚pE kw-ܼ-q{'9G1tե0C0NNbiĨERzF#R*4- -JkE=]JQWN(cJW3x}1 PKSuPKtjHEandroid/support/v4/animation/HoneycombMr1AnimatorCompatProvider.classTmOA~oZ  RҨ1vzF$ F_MQk-m\nowggf?0TX>w %1))L+08oEZNZ/΂UpdKJ02N*2(CC_e]2]ؖ! 47 R3-3E,ChN7u!_Kd VA0tuS<+sY9ni+ύ wty]ܷ:*mrTek3LcUmN/X4,;DFiJF.eԽ)4M FZ*(V-h-M"qw(D$KGI. 0*g"A駗nbd>.m|8G0xSJecR;/Vu|#VbuA\m=y$_$CCW'){zH(C #yUj^nBųd$]EYW7:hׅ:PK34[PKtjHdandroid/support/v4/animation/HoneycombMr1AnimatorCompatProvider$HoneycombValueAnimatorCompat$1.classTNA-=[NZESJATLCD"F㶷%w%L Fx(`.ݙ_<œF0clY̍>Xp,<00ÕlH[DUP"`بrZznntgnWYjn 46ѡ  Z ݬ׸g]]2>C2|9nz];s; d<\WW(B!=PJ%޶uO'U~{Sp0|jV*p\3n N}grBD bG$瓃_>NU{:ѡl,bFc6!gmL¥n&*ݫFdayBa( X W ]=LVO ]oͧ9p /XO*MR'"Ilj[}ڜTO*K'HU~ =V j`= j|7QI쐶1OdRc{\~Tj wSƝfwi`$K3I#~PK"({PK jHandroid/support/v4/provider/PKjH4android/support/v4/provider/SingleDocumentFile.classmsFg; J[J<&BiB M4pCxȒF>gδ3e3=ӽj"{<^voYl08%%np +J*qG5%*qOXWoti+'ȵila$rim7g'juM|k# JO(pњF[K,5߶'V({dע0ӳv]IM 鍡NrLT v-ɕaX{cYkyf]vᨥ4H|C4A0fZVC5]ͪ-a) rx09rs BjAJpG{roWV]"Gu˵ES T<+M@N&-VhGg}PM(xh` IY^Tw<;67sxdnTb[J̪9%)1:`P3Q}tt$4EZ\zX*2`g똤kx耵FgLc)0|EC+OE\y:4qC8(kq|2)MR4Y|q?7mA_PKt? PKjH8android/support/v4/provider/DocumentsContractApi21.classWi{U~oL2@({R"PASDKR ;M:΄ɤP);|)AO~7[so&!MI#8_"OKx+8SQpN3x;61Ga5V#>ҍ# uUޥsn::i4S:%ZOnh@Ձ̤ޥZ:wehKF2T,dLˎ 294+e&sCagImI#\AIKSmmΡH $Ir$IahvlWU\uAJ“*#Z0T>dڔͤՑuǫJniI۴F/ܬXZF~KhaQJkx ֳ67l׼VN9yNh8/B(sjɤu!,v#BA=4CKt8%c$5gT[PZ3geM7s")#nk^KVǶqT+T^*ׅQdPi3隝&f̉#g|C e0HL7gDPŃ0-P̜tJm{E. "h& T.26R˝X,/:*]A )x(xǟ%i.aehd|)!SGX'S\QЏEԉ@+Xd}ęWQ%Tfě|/񕄯|o|勇+8QC. ?' ?+ &o¶m(ŞGu dt#]0|u(2:; $M ]S9Zbr8Yh 1Ϡ:2ܘyBI`DU>gj',%|*zb/5)X3"ݮMTһ#LF3H5f$E^!_|6NPt]IR\L)M_̦htDF5RWR#nlJ!Mպa1мj0tJb43}`ׄH ~8hT hr>qK?6:eyxV@тV`% VMqF<')X'蜇+U/M|54?^4?4ZvxI.HkW[[Z yuƽ,[e'T¾pMajqL=v/V71gH?8f1{#Sp6x1?0*n1Ṃd~(Ή+\ނZOk$wgQZ) k(jnr~Oo{џ } aO@I,0u#@rmD yJdGIiEBj6<6`,Qa3Iu;)mgie1|w`zĸMvb& ;zdX_YI'\nM,\F-d\PK=9C? c-hKuX JGh+*2A=K(Ib6bL vask!g'7ynPK1ٯ PKjH1android/support/v4/provider/RawDocumentFile.classW {~'[6!BbH 沖Rrb L$:ٝvvB*j[/U[RVlmluhk/§f&a|lΜs{='~_X±(ŷy,ǣ4'"8)o(xB'n6 <2(d9iPz?/J4/I4HXz9(Z$z?OY0ސsҼ[4oKs^п/"d,8 Ba(2)4RT6kZvȺD2)Jl5SzvntҾGAL#MMnmL I-G y>FNAFܥMX7Ceq'h5[w˰3榐2a22o~;a+ǶI=kfcIcR},0 *SF.֎ iRLL֬ )W ZuJƯ:cԶM똂n+>h NJvN#vr'#&\i+w#箃r2 =nhZL$ev"XMPJONG?jlҨ"ͧJ94jeAp@WPfTZy;{Z;l؉AOe\+nae8Y9:vt~o>˜,h6eBOϤISEms6U%[#Ulҧ]p!M!̀ʴzr[8%\YUK9'(kJlN@x-9qmӡ;3Dž\\ճ'TBZ&1ΐ~ VyTOmӺիt2,fNCT熹_^ge"x({eXv3f-|kvk:6`#Io={EE!iPޑG`*.#8z<56]ˣb2Qy<ԑ˨.b̆`MgZ&NISrg~!ث"\(.L[y,reBP6Q1Y˧$$dn'B(r|籴~]R׾4ʌw)t]s*? %}}}^_{-?k?r+R1_X~Vwڧ}{F__o˴/Xcsn0CB1Y9Y5yݝC]Wush pe(ĭGȓAvPV5y}P4hr?؏q$q<ᠻs*Qߠs#JѐEN"{Qs2^ 2^!@˫ZC!~A\~$.Q?񦌟xKox~ Oىeى. F6oYkDM4 zVvQuDHtY 1VҚ0f$j=[z^>52 =r9ôS[9ӘSH2ZӳeIkONdE1u -$Ey*Y{լ8ZOHS K*2ƼP Lhրa"u%X#45i]wgϪ,5y_Б~ Q毛Z2i{tT[AIyH㺖Gpzi.e'ITqw- 2'dJKk08M}I&Y&2L *G]KRvRy-]0%g2fxGBfN{y1)O*2LB&KSڸZH#Z`B9J>C$w{J2m䵧 fYEkZJd,[<%%ڛYq),d-=NKa39ջroCƻ QhJhMa8@FE (H`S&M)x1 >U*?)_(2̣Cªg-P;I%LqR y[F-3ڣDP=ZʵwUxu\9Yy]56-נ}ǀ`3UNPJHϣjL{H`+6l0ᷥm142S(1L]S1+ T2z[SatyNQu.;ftCFcypzNQHQ'c?uExјlrldD,v{nJH$I,V t^GːGC&=4êYi$MX;lL"(6L^f^Gr3٫cIDɗn_Ի`'R[0Sꨛj'Ohd<%cpt@H9"sءd~)ht(p.1໎uU𭷷U!l _\&epLȎ|5rvv]a%^}{`ɉR+xHnxoQm;R25ߣej^"&l?6aA"\T]bw;%m5e#^Mt;Hqg~x oK’h&72 # 72Ƒ8ži6ɘasl̳Y@1q+:{lLeu+dk%mb\@65ܵq"6}+^r5 `M <d}I}G9`( yRC`ZYh,"x_,_v[ĻЉw> Xa\"*FȣjO?{"p9zE[X3'B.]EiZs!fL1d1وf|ɘG3BO]yOt#Fqݓqʐv1nz2f1:܌'Js'cd5bw3=]nCO>?!heTѝPG &MPڭO+QKajS{L9Ne%WqJ)[L)C[,bNN^W§n !r|fK> CC/J#)K·7(ͺ0ԇ>tՇ +[VpPK PKjH.android/support/v4/provider/DocumentFile.classVSU.d Ӷk?"E)(,U%YnM}o>_::JNܻ2!ͽ9Njߟx #A2* >Rq;U܉c|EQ|Q:{O,Z+ȩȫ -3ڦ24s-vmn24LZjՌ*Zlt3-O9bѲph[<۩)+W*йk )4M3'o֓eP&<94M}TX팶ba+YSq׹ClEmH‹[)UѸ$Ӕ,C_9Cv/e;Z0u7Eks@y0 q\i>@H(pd`|rgzL={}5p7򹿖 J*uս˘O,Ԕ&zε-CHj ^'=esiq U y}ߗɑz w<9\k暻. G3t6¤-%P^7t菸RD s«{VEnޚP8-y mKdWԑZUG4m__͕| XmQn-h+ݺ a8m}GYQaPv *ppbpQbH7 lZ٠*h jfUl2t4ԕ7٫ [sTe-6m=I%RSS7Nej?[˹E>4Hh%R݉#LlҾ+ Z4惒f8U],xusfӧA:ЌؒO'I']i^"]:3dﯨI>'4d"^4NI&FUs,E6#`e;$V;8G2pG( (F'hEzAf. Z 0iH!gn.lA #r&9ZeOQ>A.‚7D!z3Z?'/(_Uwٻ糷Uwvo}mK9 xZ֯o+X[ʬ-Hլq0A}G 9#B%ޥH+ &A]YqA>dcR^8ޓ}_N_/|ISO:qXC$;=\TZL_24 a!Ra?Wťw`chgq}lBz?NASAJ4n_zR@= i ]0YkIk^^c]#H1¼Շr]!8#PKGn| PK jHandroid/support/v4/widget/PKjHGandroid/support/v4/widget/DrawerLayout$DrawerLayoutCompatImplBase.class[O@S-"wTDTK$ݶNN>蓉~?LD9s\?~~`F16J6Vm1_@W CCza*6i uȏ}LWCG\Io)s.c=τ0q]iD.ɦP >cA*<Jω( v3Մv:1˃[ nZN 6)řsD-SV11#'c u{~)orAyw|*\U1a]IM-^C,}śR3 7u&, 4Y h_Ik=(Sˏ0FWtFiv s^r Y"=q_hx]~8lIP@;e~$IM.12(FD4s:9L&P7{&[4l<1\, [A:?Phgʌf+,FdDr!3(ieN8444fZ =~v fSjZ lv 7boR2SZ͙5b͵R6t3.],> ]HY(b7G/PKhK~(PKjHGandroid/support/v4/widget/SlidingPaneLayout$AccessibilityDelegate.classW wW^$?YIdy4K-Q6 ud;UI8Mx4'fSh(вo^BJNw$v#Cαw߻}7zO=`kQT S B╓ NeO* N)b:3=-'iM Q|nnS|Q>ܬ X'!/2⫸kx<_V{*'U\xY^·U<ܣ=UoP.Q=gc>~'*~ ?/X,SbL2;~#SbLrtË*?}g XIڢnK\Nj}wҞ4}W0_k6B?sǸ%*GxTV&^W_<;9֤-LiNo2K7S%![ ʉ%꜠jE lij knV̂W^7enuȳM t'@֑lsК,,U+2WwpY7Y.pR7kͥo/vw/WXh$;_ bNJn&pq*BV4jfQ-jv_&5^7OmˣG WẘgW|=jPiLL7S2QWkƔc^u :sdcc%tt=^ƌ*=n[Qq_ ;stN ?oOe>G%Lj].F~@?r@{;?"^Я4v'S!X|c_DaBd Rb/UR'%RQY3(VZ;;p 2L " l͇:2u v{gў|G|gKh-b/uK#DGyuD$| #rJH !΁ر6A:VVsq7$,xPI (gvIʮJOOw̴?؁`JB.y>y<8 00Bd1%qt0*ggk8..rq'xxgxxx$Hz6T^1SSl*]`SJҴvHˍƨ*UnRP%P1x[{RVY%70 BlalsSəCJ*ChjcᚾI5m[e ]$ |^7Y-3Ʌ4ֻYNu{W@ǩ֞SU3ҴWSehvd;g̖ rjoqjT5Ѭʷbh|nlzǴM*9mJ15=LYԍ;ۀ[ʰ2P!j9֜Z&!9 M ׆T@tL%}GɋB)J+t„J7ۼCڋ1, [R}2ffK;"iS4U]XAío9E|aSד"J3ECX!Z^; 4moJ Af#֌J*/.m2UI9tS;ץy:n%Aԯ*iQ@ܖj6Xtb^=fJ+ЋFZ,[yoHxSHh^.(ej26c C2P [;,#M2⊌N+8dpo/CŇ2|$#eDBuqfhY]:F2̈́amt 2d[Qfuz-9$ g­Oenf?T;u7X'M圶?ߊ(ZU_zwU/>(z6Ј{p/ẽ$Bk#YH:m,;Ȳl@}tيE#_^ ?7Ħ- UV µ_li;>%n5܀t/6Vl!f4iH|-:WY  jQ"f X=:Y c p@ԎkGh_*OB+&iAt{msUXjf^))YYj7J$JR(5n%QRKԺQD9JčZˣE(}%Qz:pI^(i̡V7E}X5D-;#vZK(ii>vN[D..HϢNr%HZ$:N⨓8j'#)rb(DQ)E}E2A䄵!s"1 DbpLqB;A$l*h Eóh]TVqMP[qjܡwqjܡwm\;EZHhIDM:Y4]'AZPs(9=Ў۔=N ʌdl*{&qT_K>c6I,V]`ӯ?5gK5Ζj0noFTK։IN'LN*IuN5TCP.QOMG[Uи4n PKoryPKSjH9android/support/v4/widget/CompoundButtonCompatDonut.classVVFc 2FbRB ITJmHLKBB1ڤ7!$Wk.kѦ]K }F9gm?` V0߅##YX{X(V*DÚ6"،3|.$| _2m/3sꡚ6Tλngbt\tU.9 ׵̬Pw spJ{ܴwDCT8i ɑDr! d'|r-'<4Vm]|a; wsY-v*e;zt:([!T7kso馛a)qVaXL.qvHư켫\&<&gmB5xb3lF%-eI^1Mng q8!r芶Z.隓ޤdq;[^"n`aybu-! r!N6#,s֭|E+yˮHȊajkwS!rjihުpZݺ-e\u}#b)\=# bW02\mj郳(xCdQ"M]f %RlNcrYeOIe )l5ĩ} OK9U%[ ~lR3 5s޷t~^L:uA-N>H6$jC&Zj0zKY v+qbL6.9Mz+T=F:gi2 56*ٖ?Z+tR9|EtpA/2AH_!t#*:ޣbA?+$[VFk{W=e_bn`,P!XBo+M+HW">n"xoIsb}dd#끇UF"N+*MS4♻g%%{C#h&D$j'7 }BFC,.9w;@Um#1NfDl<2b:+;gemQ+D+0$y6L7F=!PHt9&"mUή zCV %<]Qà|^vgH)^IqKB;-# 꼱@NSXNAlu[x׎ ɷT ¸IH%ET> Og< E y-+@)@iy[5qQs.c8PKH-PKjH2android/support/v4/widget/AutoScrollHelper$1.classM 0WݸwJ zEv$ջJs  u!⣩lʛB1a9(eU^Uk* ٟMKHYSdUeiׅY^jF3lWJ:ǎ|BI©'w&`;@3tpq%7PK.,PKjH3android/support/v4/widget/TextViewCompatDonut.classV[WWNL7Ѷ!(^BM@JmuH̤նbo]}Z]>t-tkчߙLBsggoI?·*bGzs*+R*Wj>7|]oofdU`XĒL+*vc6[rBV qo+(PKWnUtaU)xG@I\1;zA ^WK[4@GұKnsU6ҩ鉌H02A@iJ_3iX9}uVƢedݸf )'gfz0Ӯ 75X,ZsI":ӦmL FqV_ '[szє.%xZsEKB)Ցm3dYc͝3I'_q.5J B/ndWޚ >9χi>:Xxɫ-FN]kYMXܙNwbT9lwT/ttK楿Xʲ%fRik_`WpBЋ%C[<%렭hRfr1ˤɌ6!Ai<x[yj5!!c^EZZVA7+f9c{ }  ,kw&&1V6 v'؎^aDGoGĻ! ஂO53 ˿KUDEf +5@&VV%҆Jo&i%R}YK/9gV^,ΚC5$ $kZCM4q 26#xJ # !Sz\B?cGb?3 iJPb&Lj`xH7}/I_>#Ǯ繊sb ~\f/^U8%DK:ߡ3_`m01pd LtGϰ 4wdרDe2x"v9Nf_$kP0.0cڍ<PpQA B+}47} n:}O5Ѝ\a"+!nrϳ (*8LSŘG J&Bu;0IyB߬sD;t˞ ҿ5`?PKXe; PKjH>android/support/v4/widget/SlidingPaneLayout$LayoutParams.classmoEk|}m I۔Z4% nQڋ;n`$@ PP !> 1>FCZݹwf?2Vpr wWͫ4汐 ,js3ƒeki}N!v̐T Đ [.fK>éz)b_ң /n#y3OI7~xg*M[<ڏ&TS NwZnDa(Ƚ;(w# * D10ge}.T ]h.x!ӷq7Ȭ~}5:PɷxА 6Gg{ni5SS^?T%[R(Qdù4NԀc8D (n[ԗw)lh֊]jRB*qgMkӦNxA +6rP õ[ aWal@{pS?-=c}^OAà~Z>WVӷD[)>B ۀg3Чy=11=MߓȚu8ZGY"F1F,yp X!;bȏ֧HZ!c}Fً8&]u5~aaM#MRE=E :⯐5S(!~$KXO[_h/#掂&~AGBF}hbY$mY$0 #)1U(2 ř_܅Zgv1Dfw<$ M>F1aFC0Y3)%ݭ"ue/дɣ%LϦos7jR/\,qk_7CH PKc_@{PKjHHandroid/support/v4/widget/AutoScrollHelper$ScrollAnimationRunnable.classVmsU~Mڴ- ЂCMRZPic%%P%-UmrmM_vRqqK9[:‡<9s'O=pu }8IpNC?0 .j8 Y00Pd CI .'PNJWڽUMN f-6 #;fmEyBӳ+UǮ/zC9(oܴLoB xj^ >eה@Oٴr:Yzvըzhs2\7<Ӷ5-+KẊIGQ_iZinÔ,MK7Tƣ,B6SYSuX@w3 Z9 p3QZ#1% hTմbКaxo8,D5Tt1\Ǽ,긁%|3vd->g ap2:j _Xj5| 䮳uZ.Gt״w _2?ŨV\i]M]eX Rk3\l-Z/6dmg8tvN宪?-M0;QF[=덦<X1lZb<)sCɯtӷS.$C'ߣs5n+= fןXzj_|z2p~e:Lh/Y '&[ >ùduPK'{PKjH=android/support/v4/widget/PopupMenuCompat$PopupMenuImpl.classNN0-%nKK@^$=!U wX!Uj[>BH!fg_ 32|#U\8/ C0Q+]&T!/Jtx-jKreX"u!53'^teSXpMezcى2ƒo,X捛^nLI4Q(Øpu+ao jNk]1&qӨv{GPKzPKjH4android/support/v4/widget/SwipeRefreshLayout$5.classS]kA=MfMD_XSMӚME m;Maw__QmLCag޹̽w]1kR(XXu=-ah&C*jɰXeԸ_zNt rzN_zM9]Un#d= ٚTi!&K滼Ƥ Da(Rhc%y$}Ux1,$Г3:=\`XOaȌ쵥<};FvEGpƪR;̟΢r{JܶJQLl,l 6,Xm8tՉʤwq\5gF&IK8D5FΊmuxnkaޑ*'K@-" hƗ'P`l.ILq<榨MlV*]$M  Wރ? Y3a|F1fEy(ld5&?^5:G6Fc~(bD#7ֈ`Rh02#g 1^?5p)+qpsrdcb R_PKM<3PKjH4android/support/v4/widget/SwipeRefreshLayout$3.classSjA=ӤIYihMSƶ Հ ?N̲3Ic Cw1"2g{~]4K㎇Ep _D`[ D008։ &{Tݩ[1Hvgd\*i_00Y\Aűa!r0}#g)M\uF*e\ua-2*8)zPK1PKjH<android/support/v4/widget/CompoundButtonCompatLollipop.classmOPwuɀ1F7c2CC&mqIm[Z_Ps;a̭Isw_/ P)` Uc֍aPgȽ SN!R=PlK_|:"< o5J>WunX +AْQ3:ι!A9艀 =rP$jZJ%X+ђ*''(E''g 3b %Mo$؆m/ƃV/2r+k?2jK?PK0 9PKjHJandroid/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImplJB.classVSe}et6ֲX$\*(,h%z[6ɖnfwC_33:>8>ώx| ) 8߹n><|=1y0B3"cZa"༸Wa,, 񺌗`IƲa4@}YFV*C[Ldw33fz|7 N.Q\u?bSf0i2mӿ1NߌirqE'c֚CF*e'[6 t1x7h5:j_0=h2swX,YgEn=BFsc^Tr\?=FozU6@a {f ct>X F!B{i-iz{hIi=T8(P膌~cmh$ 40gO!}n3D/}+KbB7dmʹKd =IQS3AN"LƐn^1Lj4P3Di}cb8')wйLuv k714|ݻJd)갾G#_0(3.<&JaFai q\"bu. 3F*hWE X!6 y@PKKZ PKjH@android/support/v4/widget/AutoScrollHelper$ClampedScroller.classVKoWfƱ=$$ y9ż hBCh#苉}ƌgxnZeVv*R+Z]sg4UZs=w *HzfE}Fv '7aEK4*XOnL93Գfa2k991iM8o+鋰tKawrnTEPl&MUA3Fq!t6˰(}v==ny~(==w'\xYD6ndxu_dI)Lt#:v'OG]|N!FBSz $$fMBCrO`snLjc_K'kz9 5/Y_m|8Z3Y8*f%H⇎sQOPKTZ  PKjH5android/support/v4/widget/DrawerLayout$LockMode.classN0?) 1$Tɩr1v8j <, ,g>>\80Q@pN̼ڒOUbj%pU6Wz֥^YhQv%wCf6ѵ@V⯓?|S60`e.dYm6Y_ɭ.VO/16[K~jTY p+R!S :l]4=;~m@GC.Ŝ `_PK.PKjH6android/support/v4/widget/PopupWindowCompatApi21.classQsW7,IjlU 51FSIb 6oeݝ%i@J_|N:}.D@"mr9s~`?sXM`wHb-uܕIq/ϐM>/($p顄GqlJx,xX Uzi x L4;jVw ܂7o4>WϨ[#6L 3K&[(:5ƙ.:<0v`azG׬q"hjIkcT庎GkYkq[iלsjA5WWx7X%6аw~?z8[e44?A7tlt{F اhۚ#rcyq$rqՕXq-Uͺ $NӍM3Txtnd\Ă c _Ix*m7}ҧX):-N@JWEΫC^Ŵ>w ;2vLF`Xf(2" \|g!Ṍ*d|Z`S7 =p8=݃l?(:KϖD=]^eindJ[®9An)~SFZ*M9!"E#~ Ev$ tN}ް;{&{hqO̎xӦ(Oƛg WE>zm34(p/G=l)\Lo9"y\(w6`8>ծOXco1mLT,%|+na\gr?11G2WH{(}H>"c\ d90VKi(=6"䥐ie5 Xuu%̽(͘'@"VWAsJmcWej܉6@gQ0ISL7OLjpRtr\+PKꙧ~PKjH8android/support/v4/widget/DrawerLayout$EdgeGravity.classJ1k7Oz=Po"*[%%&%ݲً h`23 ||I^xJу6$0jC~jW˕iZڠ_EzaZT#K2@n:2%a}rFg5?gha(pleQYȍΗOҋ1K*jÝh-QEALL%pGDchg?5=*m GS=$UdT=pfLPy2c ӇЊu.O6h$BѰk|A?2=ET"/#I)"#gK|9a@y`5]mHr80a~q2cX2:u#8i[_E=ء]DEH2X,2(OeNtfNG}waܠPFHTI,D-ߓ aȋ')þ؀±~ĕȢ`، $[xZCƷj;4 +4)".9- eXXg}u̶M6S\ۯ'.ᐵf9zXիNe CO Fuد_@v$i{$d2͛ȸ(d8))>`,u !UHS[8\yi!80;/Lz6NRARJT|MG$___7[J<ޏFlA=6*tԵR = =dxlTjk͠jܯu3H0l @R1ۤOl1>`*5Ҭ uS,d{p_4KFܖq. [ychđaK{sGJM.hjjbx*Z]pI2s,{d?Unb/J;kJ>XMYلZjDk}(jvnBAy͙L L5:NWhtx.h\¥F'l zXnc.3}>ɟs|Fн+ZiyU5~H?t֜|Ɵ[|Ʒ'/5"I/ w]}+F?@o as5prZ?":W%/k^5~m?@cjo9 40<-y~A?#e\E%~V LR??8O[/L' zº]MNXLuQlwn%^$o0@99{mQɴfYp,poWri)bj\jhAO3N'2{9]+Wt͠p~?X !G,ׂG,1A_o?A*n>j?yg,|,ˠ}0} h4 GGֻ"__qf;Aen iV8:iEwK$VH^_߈nEiwPYB R B}]aTbžs("C f/")<6:T<m%-XTQwNU6o3**aX<-TVF4 AnzӄWoT0kzY]'Y}>A"dlVQ );~xM6M+ uwMj1^5TR tfNmb8'PX<~T k`3ͫm~ ߚ6u:IDW=9Uy^3uTWG~,!b&©#{ꎬ>}Q S&y+JÔ )ż>M;ךKhmCg}6(o\2'Mr΄BBQ&dv&rZ|!h8bO/S,-?џŃBe5'5Dw$TX~`[h9uiGzޘhs9؟'1owlKM²兆¬h!L'-X(شn(8gʹPJQj_kF*֣dqe!˅M-6$s1Z{l)\ YfQJCREPLNՓx lM[;iN0HXv>OFc|Ƨ6FTI]i:X!e`ϖRUfP ϤE y65sk(h'Xgn^2jKq֗>tX iG}Qv-31oksYLS%/@ԲsͩHJNC5:RI=_]p]~a6Y{Z[\껌vflg>A]l^[ٌKKWف@^n;#jJ5%4[}Zkz58uz,;pj^A@j^6U4P=弞jt.o y ŵPC1&JUnlTIGx:0;QAzEF\ Eh0$|Tasij0l6FӀ#sQuF^cfw^ LxUp)"IJ⩚b'iFr&Pwg{5 '^5xyƿa>#7CW?PKgV*PKjHMandroid/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImplJBMR1.classTn@=酖r)iNxhVA}EmT|♏;QJwvvϜ=;_xt4-]۸cA{.=`ZC;Аx΅z]ZYim?\E]2lґ^=6{?}yY_yu7óYT&WG㮉}(8D?]3$ټjsyBQjV1~DҤ'ʏԡ8p9G\&m1JQgo 3ݑ 'd+AX%8+^{c|Qh !L #)f[Ż6-MH^#fp$9FqOtSH;kݩPKZ*QQPKjHJandroid/support/v4/widget/SearchViewCompat$OnQueryTextListenerCompat.classTKo@q&5P«48 ĥ*U(%UmUvO\8 ~? 1GC+zfo_x#N`$,U(_Dϗ:9lL*!c=oҟǵ`$RjI"9t-~G(ND2u;Rްd\;.Eθ(>RCa'U8r uD!C Db'ǞQZne‡FE ll`}Paa}j WGhUϓIRk6y|7VٜF|U) /q:ߙ<&n[9y.@9`]69&Dpic3'5tn/&eNYVPat k뙷juub,QE-PK(N\PKjH0android/support/v4/widget/ViewDragHelper$2.classRN0BPC)K qqaHe-mbqw!H> \*ZxϯXrŤ^Ly̝L7JeB0Hf3&h2lTx<Ҽq*{Ե/4 V 8 CeEP"BFqGU;`֊cЇOAZ׻.o܌NEĩi7(/޸-NU=šyr>\|,`^CWv/ ݤJ-)c:U<[bb\xSCE X =Of"/0@oc0#(tvڮg+2fWȱ?$n$v韥w]$ PK^׀PKojH<android/support/v4/widget/PopupWindowCompatGingerbread.classT]oG=;^g8)!Z)vK6 BpBp)H=gwW}V@}JgPY{=sܻϿp . a 34 !M ⢶_2q?bI5Ӟi\40=7 lTZqM]u8J?J2+A3;ҝ50qOt;Tw ea-|cJL]U*/F{[O_`]-q 8&ʾjG$p=y'Y,dFKoz@leuf{-V {uMyxݹ<7xʐ^H`ǔ?hH{ZGşWgJd$areNDOF[+90K'2o$ur)p\w)syަe@:yt߈ |PKzp`[tPKojH9android/support/v4/widget/ScrollerCompatGingerbread.classT[SF> ID\B۔@% %@,-ydِ>틙ig:}CTQL5o۳G_Y03 ff _PآMa—v) es7(ᘆq ?!Dg d9>ϸK8^tI0}\8 $|:s3"]93נ}Nt0?_ τ~@0)?&nz| qcR חT0-h%" 6GwE9Es8慠B/90%B2/$B2/$$yDq_ChQ /f<K w. ӬXiݭYq,<喣(_d!"ȚYD![4尯B ? {GxrE_8׆]O'Zw72]DxE9ZZ,=z O OPK+ PKjH:android/support/v4/widget/MaterialProgressDrawable$3.classT]OA=Ӗnw~|T)- 51}:ne>"B!`;w=;3{gXӑFр[p[ j(iXPְȐ[NP\fָ}ϱ͠n{~hvfϱ"4wx(|/| xob7!X36=[0)u6J1j:HΔJ -) YBJ%c0?(v˱ӎWͿg6C<`|`q8Nmwr4_v|Qz-2V ȎW|}hqmzOE5uZT4ci^[a) F0XV]dV0A{ 5c]cqn[o2s`HTor3+tXߟ^.N'bt0M?nK,WCQd{*Fw4K Gc} O(qR𐾊!S~ P~qLq1&0I\H-#u:Z#>#SU\ׅB7p3xJ+I>FK@$# HB%Q3;Gc!z@ąVp'PK'fPKjH?android/support/v4/widget/CursorAdapter$MyDataSetObserver.classTnP=81nㆆ#@(u+AEH R uwS_ZԎDĂ`,/A ihI<ϙǟ?Eٔmp=9ܰTiᖅyB6 ~x/G&^H=E/jA0HQ6lF$ZA(:R=.[Ѷ超 t>:4ukk"2y҉HEpPfWıմ]T&XM> "ܑ>!S}NEz8-T *80҈!Wgn- sp42 }As|gOˌ}cc+0[cTF7@'+u%\yEE,̰?nPKc ( PKsjH9android/support/v4/widget/SearchViewCompatHoneycomb.classSFǿ,8 $ i'M IpC^h\!&$дԖ$}_wQ%2v%{ݷoK #6s"T~ 1R ;QV..X*oroswU}}VPrĘvT,%-  G 'WE Y2 ړ3 :ҥ53-1Q) {ژ/ЗX)kf wc7g +g\ʩ%M-Npn*# ;1J%KfK;,]P!-Y\WݑF2؞lYPOݎp0P"d`FL;a'4 4jdl"Y9nďV7,cQFzdC X V&&ˉ辱l /hmI8ΣV/< ߺ-@l5v[78nS o@QRSp"hS4ӥKaeY/4\S)sC޹Tq*E62Gm&李ӡly2SH>ՔVDe:M=A/ÌTqݒu@X{k:vS,P-RRhE( "qz̹1򘴢Q-UQR6˔*vV\5Y`854?:>R2mZ{q.y #<_s'F<;tp2j$ ᴜY7d@rc, mJRy_(k韲&]ȟuІT582Lғ^7p M m$[C$"4Sx(@Չh?^CGO!Z,.m%E'чa?Xנ/r ;M=tg yiP9Mx?]~3GpVpL+=^";R"."d]~k>KN"ۏt6A+ⰷJXCO4D@?FCj%Q=~;d?+U-3uJ^d޸|&kx/|^m0!5:nհ/w)cǭW_rq ̄qnm)率3s>?NVCpҪ߷xMNˎ"DORSGI^d]hMPKׂPKjHJandroid/support/v4/widget/SlidingPaneLayout$SimplePanelSlideListener.class]K@ӯhGuW]]\W^T B OIHҊ?+ ?F3iTЬ3s}5u,7 s-,JJx_"j+ cO-C2D<-cf4kzʕbeHk5ODde }Q/0r2S鮡8?b_넙p_3an*FFF"oE2&P0%T,y~d ;T5v""J {cj{BwFDvϐYOP2| ilĹt8-9V1ʙcy߼5%ƥbj4q|Mֹ'L002` :X|7CD3~[YaOPK&PKjHAandroid/support/v4/widget/DrawerLayout$SimpleDrawerListener.classKO"A##׺>PcЃsPOz`CBB jq4?'~?z1l&SS_W3Ʀm;6 ̹ MU\I)_޵dW<,Ղ"T;1ZFExw(2HK_|g]DIUun:}^K$>qa6L!di(UL973(Ld HjUsz/t k?,{Ad"WieFl&|ڗTF ۲mVDS#c\s<.ũne<"p57j8* ƃ՛b͝aMzzC ,,0˚||oFWĝ3I4,0b,s1&10^Ɉ u|(|w)ͥŒoa7}PK`qFPKjH8android/support/v4/widget/ListViewAutoScrollHelper.classTmoT=qs׍ m$-֖ttm[׈ Bbnl N9N')I|$('&εt!T\sϿ,a\g0o=\P5e+̢%M0s/(6ҭ=.OjMُZ{|SX] ̔G*-}3p=z :^tX# 7Fu&t*aĻ3 L3, !*[\PKA?iPPKjH4android/support/v4/widget/SwipeRefreshLayout$6.classUr71^6?R-!iu~pK ԉ[AʎznzJ3`88^;9yq7 Y;\9xm-Z%|\ewT4Sd7Z^j{K^Om{=Օdh.\V xEaBy{-MCldQv7rK n- "$Yx#GEOt6jPIZx޺wئVZTP!_g?WimES?ؙ#I-XҬ=r-?fJJz&;FiKjDJya]tר+&@U[@(l| BCF -Z`?1{saI®p >9Gxy:UNx!RJ ϐ>AA<݃GG'#v2N|ҝn=ki!~i^_WqA*B~+I|DEp>IOK|!(~H>>44f^ X|N?JE%IPKYr?PKxjH@android/support/v4/widget/SearchViewCompatIcs$MySearchView.classQMO1}Nnn BP>$Z UBVT9fv-bMgR(xC07o7?Ju 09},XdvaqUjLD++bw_fT0LGٱ0qFF[' K!̰I' \د08QIBD;IV=Ss";NhpnGetQ-bd0)`n'\UhYFzjGWĸ^{ ^vjF%PKHRPKjH/android/support/v4/widget/CursorAdapter$1.classA 0EhZ OB7 HQp!.,4iHu64^׮w:I{AB¥!p 8 N3o_^Ů#A̛v7ƀ!/X$DW,y U2ɼN 2oy 2o1`&K$s?@вyXW]36λa/8묡@鹶my3nnZݞeΚ=˨(FaaܼTS`qP[кO ^^ݵ &onΚPi>$BxKH{S ElAcJ?t]dCް$ny+ JBV]ldIE&]jaa2 k&4Nq#-2{C.ONV/}I/R,1v5ck%ح;\=)(NoC0z{þh&oļZU/@X< *OR֢@>uw0o9K/MϴoT((S8PaT&s9³*\.%fɔ(2e\!3/%`]]P` Ll|rf_KЋXyi*Ɔ?ct"pm}h C? K$_9قȽP<{  cy'0C qH z> ~0OS8=m`a]`0IsVD20>݁qmaa)Hm&[x53r-Zx4/9T*3po (ыq3xfq!ނd+9\,:+:+Q}!D )dKD!YH -'q) EJK)y)%/S< aK!oΡz). PU - b ҤXMդXMդXMդXMդXM%EܛF/,m8c\3a?"O]FFE 0Ϩ7J}J>EqAv$ˆ~EߺdslZھ] tW* ̕00v]N MJn0_p19YÜlXNkf{b\s|4ܛkE GxPK$˜ PKjHOandroid/support/v4/widget/ExploreByTouchHelper$ExploreByTouchNodeProvider.classO@ǿ;︻"'_QSwjv5e?/^!J·^vf3~} `eP+"u4h1PEXk16SRxVwREVo:/_=ّ{.WL"zXeȯI3loŝ=v=NF7zu Fɞb0 jwYͦN;_q]bO":&v/^Ơp=,Ok aMvNDa{BE1]mtFKYq#!m_ϗo'NjSrAAD`\S0̽Zvm㸥6BvCy[寄Afc!6Wļ -m,0W6 sBe-C :͑JϬnQ'z~E}łb#{3+7Uaa7iD;d9F30Lkl`|uMF5 cR3?\HF)@kcxڦ$+ jz _L`;´6`SUuw/IN`o:ӷ`pe6m$q(iuTINDu7PKEglhPKjHKandroid/support/v4/widget/SimpleCursorAdapter$CursorToStringConverter.class=N@F &&p (`ХBH"Q8xGf#kfnC!b ͼ|{0IDŽһ%}ź0\ig[zeqNZJOZ ~iٕ do[3ἰMugtYnz4ŔpڦUw^8_a. aft%ug?֏-p/@0u#~ٮfoRLPKr+ePKjHPandroid/support/v4/widget/SearchViewCompat$SearchViewCompatHoneycombImpl$1.classU]oP~N(`sT(nnfabw@b[/ޘ8M">L=8q޷wDAnV )@SĠMA]+W=71VTN zc6 mԃ^_vCMjm>.L]gm;(,+XfX<|:Nax~Gb m3-FMUJu59Cڰ]ipl8$e:uӷ?ʢ `0"KBUZ!W4̾;ko.ka)"K'PM*Zq@ -adraUk9^@x/x*VpW9̪H⬊,VU\¼+v Ü{ 1N7⋣ X(4Vc،H yxb=Մ1%PcŊb\V"U*4` H7Vj>HBh wU4.bb~}0Z5}T!@~d4X =C0jf!TpVAq8)$ Cʼnc$PƝxPKkVWPKxjH3android/support/v4/widget/SearchViewCompatIcs.classQo0n 0 -bFڞBB"njmZ'Jܖ}+x $3 qNGi}w;_krC ߬E ƪdzaĩ=>VciEV7d҄ykjt5 ̒*XHJZN,!1\u1aɷʪ6CS1>gxy%i iO>h0i g۸Vo(s?Q]pjEM(.uA^no^+~I68uPK<PKjH1android/support/v4/widget/PopupWindowCompat.classV[sDd;V$˱c'MqR @.$"Mq"\;FUGő4l_a:<P:3G 0ݨit$1aS}BG)gϩF_`_b _JBk4==cHd2Q1"·| *e6 "L%L>AO#&؄/PK PKjHFandroid/support/v4/widget/TextViewCompat$JbMr1TextViewCompatImpl.classT]oASvbEVJ?pch1&$|ر.3dvcI2Y`Ƥpa3'_(' p&lp'>CNU.#ZSǴI&kR;'R?ftaEv{TW=@*S>CQ WI:~דJ;ΐ'L;ˋs$|́ Cco0j>@OK%SE{w\LΕccws}M&P\Fe#.N%]m]Vs$vfRcb2$vRBrVEzu*#CA['XtT `>zH}fϹYڵ[u \+)ȍB>xg aǖ0? L0ۻ_|A -$1F̫i(Lhro7X#l( u,cP$C&`'*H6YȆ,M .n̉"f}g+iw1Z]!tWBw6g\o%iD6܅*Q̻ PKޢ9TPKjHOandroid/support/v4/widget/CompoundButtonCompat$LollipopCompoundButtonImpl.classTn@=$vI iJMH(DQ'٤۲>B:&!E’gֳgΞ?<ƣ tΠ;U2E: s!Q,2$kn3,6߆V˦rm[ H kslRN Fq_ }Ӱ<~b~teUA)d-[n]GrG>c$W,UU ֧B YIu_կ.}"Mug|;|G=NGT6g^xX3nP7@Oֱe s:LKX6p n`ym*ڶ>na-t>)nP/amH=)z$TtWZ, |p> dnJɏì; kc2Vl|t E6҄I)^wPwT~<jJ;>V*O} 6[{7^dW[Ś:ϒ[ߐb U~*W0*Hr발 L:,%af9̍1#u`iQi* di<0cfJ?PKl߲PKjHLandroid/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImplBase.classS[oAXmT7U)]oo!i$!i|Yf7?5>4d<mD799sw\_<9pp mJbƖ60dK% ty͐i`XnI%Ox' Kvy412a(җ+p(|ܦR"n__&5FNNR5\'6E̓fV%_PKmJPKjH=android/support/v4/widget/NestedScrollView$SavedState$1.classmkA$64jc}6ګHI-`1 6%p+{TE?JZ %;;}2a6P2Q6Q1ؕ r!QҔh B}]yB]a=GұB{K/B%B{wݎ?TI!S'\f[+/2Ĥǐ[dHybP+5ʟΑ* k0Eq^~^HZLePҬa0/c+NCv^M?$ށԷ ?Bs@z"< idL=sʻ8̈́7"eΝʐsY<"`Ծ 6lT,%)ey*X`kHV1"NW#6õHنR@ Dl".e?scǛh)hEX>ci@K֢-9%'ĖvnmPKif9PKjH4android/support/v4/widget/CompoundButtonCompat.classVQsDv-[Ii IF%IPIg2SGZY)}'f 31P:mg$aؓIz۽ݓ_TQRY#pWrXɢUY,)XS򞊼_(0_ J(f DZm0L\ =b 2/iwN`ʨo} <ޢw-:\XfH/q; mPqwFٱ-s&Xms)S`oCn誶#j< k}odc~S’(eDz'g|3F&c8`͎%,q\27\2W5#dݣZ=)W3EtpD9xKu* TzT$Ցk7GWÇK#a3\9ttVFOHU|-#{},N O='y֓Imָ߲\L j枩a8a j5cA"n)hhmꪘoΈ t x/&ְa.CAfPMgW_ylD|6ÞS_1LTh 6߷r}%v4 Yzآ6=B: SWO}dLABV i.J'H}MxtiԺ$J I3 k/L5m}K\WJJ<1aKHDUR ~oT{zM:PXAVvOCߤO7~(_ѧ -tgȔ' şQxϡį,GH<-ޢa2bq "o*in"1 b٥(6>&EC&iSc$特w;Cds$&'y8s ! :֑PKo PKjHFandroid/support/v4/widget/TextViewCompat$Api23TextViewCompatImpl.classRjA&IqM!*zWc@\AڒIv#3l"CxxPiTTsΜ0߾ `7C԰* pP*i1TG N@*1,0|gu'<q#{W`hrs ؑG}Y24c駼( JITy# N̡eX)u^ nP#_vc7VΞ s Fqb^D4K* +LO)mZHF'hհL]ӷ۷oS' …4Tk>M,,w//(Zs#Jc6ݩХ! +"͜'> Yw8Gy @E-P.CyO9҃#*Kkp>&ɞ)nynM>! _v ' +ñxk6XvN7ScMX6$},.E{dK{?[ўNQDݯgPK D9PKjHAandroid/support/v4/widget/PopupWindowCompat$PopupWindowImpl.classP]KA=cffmZ=$A`=(`P6:3h\)E>8\'..,-8-=EOQp!qq}2o1E$ΘY:ʙJ`*NIɊ6XW;Y cV.D.$VDۈ{ }Ed*`Oqb/צ}14ќZ8'~t&P?l\n:\&'9SjʹM()?A˨U P P/BC%i6+8.s gm`iPK LPKxjH3android/support/v4/widget/EdgeEffectCompatIcs.classT[OAڭKE"RJBIMF mSd%KQRM(K/&6||N * ,0<0+¬ &z ]P |"(T{񟅨s*JH/PK rPKSjH3android/support/v4/widget/ListViewCompatDonut.classSOP=wV(E~92 m: 5љ%K&[+\ڥ/3W&b<[0QxMo|m3Xtl(bG-⑲t8Ⱦ_ ˕VW`%}duP*j;a +A[#k,s7Z2;׃0>0^x $]QvTUy3W`ΝR)AQ>d,_`>5cf}w| Hmv;{t$`asRuqK?61yMQ1QMHjHae.5+ ?Ζ}7ٳw̜{;j'q7M$5q4l;ں!gC" B1bXu&O;Hb΃nCM DؑIWԄŃRt֒Ԉ&X:LGp.w:,o~+ҧhM0AkUB+M2tkg>c"ʟbSjcGdά)ٯ04},`p"#=pY+']\t14z~PKg|ȞiPKjHGandroid/support/v4/widget/DrawerLayout$ChildAccessibilityDelegate.classTk@~n%.Kk:ۦ~C(!l5mYRe)~Љ҂R萐>hE[,*b 3'Dd ӋSR Cw$r; $u[Q%y'wÓ_c)&nW~ͅDC>iS*9-ȕzf 壔{s(Şx-de?17݄&*a354L40 ;O0ʺЩKm:ռlϗs=Doy,t^>tI5̲-kSKl|k^bKSD9jSnRlVa+4G3*MuXioXVP F,te [_`73XF5kU)ȘZƄDZ`PKﳅ PKjHDandroid/support/v4/widget/ScrollerCompat$ScrollerCompatImplIcs.classR]KA=Y} = C!`K=џ z*zlHA;g90_oP#<*5ǐ zc`(Z7d(,+30ːҷBuI$p{6ᅞ0(_:f"szn>Hg$"sOEN@-G]|M{#v8vjtrBEWsj~+8(`E g.ap-PC%Pghl!MH)~`PfRd9y{Ns4g4~c"ӗ:_PL욯H=͋ihquVGMl%R/=(OQ;X#}>rPK^kPKjHEandroid/support/v4/widget/TextViewCompat$BaseTextViewCompatImpl.classUNA>ni@ZVBU~ FiRQi;vٝ|_oxP3*-MLMzٙo3?} > <'a.oc6@^u/ QY]Kx.qY݃6o -+R-uYS[dM#uIs5uﴠ7|o.7WՋSsЛӔ5W^0I%y&Y`g06~ PKy?"? PKjH7android/support/v4/widget/ViewDragHelper$Callback.classKSA{ !0PYU(*1XvSMwzU|?˞ؘt='}Ex0<(Z6S9Z1 nWJĚ؄Qy4 CKV-Q8LTOJERFz2KpYׂ`'6T4`W)V̴벣º4ބU$ ?lPR4X:+h-V|HC'p~pOֶKR~NAI8GX ɹY[nMJn+o׸ĵ_PK *PKjHGandroid/support/v4/widget/EdgeEffectCompat$EdgeEffectLollipopImpl.classRJ@=Fc}k}k ZR- wd#ә ⿸q(& B40soν̹ü>=DF60maC"w+ Ð+8h"< EȨkj+R\q/wMqI۵->ȉo] P1ߓQ̵Ge(ijM;DM&N|Ѓ1daa2TQ}l^ʿ',=Т[EC%H>-ҞO^4E\e|֖BMNKdIF10NYTHfO9}PKtϐ*PKjH>android/support/v4/widget/SimpleCursorAdapter$ViewBinder.classJAƿΜ'X)Ri@&!lXݽ˻Y>kP,lb|3?^QcB$.l;!\?Ӿ1ZIUy54G^q5|hdv$Tky46極kn-\&}? ]6>VHT`(z$N3@Yv:.E*rАpa8T+b PK0EPKjH9android/support/v4/widget/DrawerLayout$LayoutParams.classSUǿ7 %,@G֐JK+-hKr al:3δ7۰s=s_ xK o!B VCxCHcM LV3BzO +x`c15Lo.,/3$C{2n:P~ ^y;`Pg8e2Ys؋ FqO 3O:.7ӎp-4 3nlųpӉ'.s*9<ّ͝ C aN&_.lq{M*LWꅌnBw'ζQdRz*95iNbzXݵl'77rym}a/e2[CN3 ؼ_+ܜme$J٦Vns#@ rF:kh,W!r3~u/hr:)iy8 Bwz=?t }0N ѫ^2:|Cl*H-Y 9p #/0#iM$^đO%P ÕR9nӴط@ddNƹ:C]]+O/!w'rytt `(<[ΊGS3H_0}q :A,%{<9 HN|99NtTh$+ EcGDOC(1*M Hc/zjM gqA/q֣$Rz (ZQ a0 LfM>3EGk#72`žIøl$$?$@;O%$^dҍQ8QH4-p{PA!t?ʠ=jnLdi7Mh٩\h[Fk.tT ŅZz쌊xͅeP{h 95}5|w\YXD֟sTܥtW0 Q +A (5VkSǽV2j_ -yѪ4}S'qY^~:+F+_0=?PK9fp PKjHOandroid/support/v4/widget/ListPopupWindowCompat$KitKatListPopupWindowImpl.classSn@= hrI! V@!A" {nؖN~ @<|b H$P$,yv={Ι߾xf%U;/tKBx@(#_jջl~q ^fN>yR`cu@x%{Ms}+ Orvcn;y7qΙ\ 3\eFy?=lPKPKjH.android/support/v4/widget/TextViewCompat.classVmSU~nHH,PEKhʛo@[EB@Mr& ԟR_q:/3{YҖlh:49ss=_S$Ԇ\bYx[C$b55 qdB+zC&; RBoƑe]XS[^)WL(WJ aMLɰ@936ӽs\4=^?C*lAXXe_5XEݫV*UzY9˶ -#9BM;EйjZ#s|&΂m }25.MmYGhX)1O2ֆ91d^>gc@{HkC{}}m,k)R.]SZtuS5M4ruIWhps%cK"kl.nZ,=R_dN 8!wS6Mo?A5Ս>~)ҧHlMwq PKVJ PKjH5android/support/v4/widget/ResourceCursorAdapter.classU[OA-]( KA)Š-JTHDqiG\,E&>Q3-3g9|;wX!-hŸ\&15Y~ L QixaA+ ,V^rʒgս-d0\Vѱ͢k=K3 92N̂m n }Q"#EC[F닮SP!h9ș_q[yalӑ F)o8|9#Ye!nb;Bߝ6ڮS^QjCo UBzo 'O3g- ԇ"%0CO)%lr!I)])p28!Q<%غ<3̨[xGq\@{4k/q, C9J(S)Y$EH.-%qyE&qy;\0X㒯M*>bQsb Z¯@j5DyaYn]jSDƢ~jqbʻO}:P]^U} :>`5{oPKԙSPKjHAandroid/support/v4/widget/PopupMenuCompat$BasePopupMenuImpl.classRMO@}PPHR$CdH%7*,rv-'QI켝ff5O8nyTml`NNV7B:f$ k2 dk@% \UH(eؿ:>t :C)gУƾoș9j4옉/ƂZʼn?1/H{&^AIӽSxBFv뵁NM#C2^7xAP2q0?U2fP;X6>12a=[z`Fl悼 zEׄW/LJ?#o{J}J lK/ (b==31D.Gw^ʬΘɭF[ ԹjXa>a*p5|PKNK.PKjHDandroid/support/v4/widget/SlidingPaneLayout$DragHelperCallback.classVWVҕX@6?#c$PvEZ`eW]-ؤMҦi4nRa`NzәJ<j3̝oקXKiLWАMt$0 D y,l<|. <,p9Ix%9|wk_c7x_a߈1|KAԝ3 M kVα\ێ^J_7r3a͎j>- n7,=, ~;+6,k&99H{mnu_3i-uȲhnjN: i+ u~]A}|՞+ Dv0s jQPP ՝!PT᜖Ug[AЎi֬Nʑ!N(*1Te}~/-h16j װm1ǰYOGg\a+nyA7u`@cv2FΝ38>ȍEݤ $ŐIUQ/H'Eٖ>61IZ=YSϗ&hPF0!g/Xt¸n|1fׇez@Okdxm㸜mqU΀bxS\+~NyC7:xT.Ӛ21f/8Y}Wbγh[1|[wv U㒯1D-۸=\<|_%K[*~ߨ-K*~fwqX𡊏xGڟX3_UNJ)8+Xo.IS&^E]滽 +bXfBǂ-ê֭nб/<]yҽ6\)xC\fC{#*H)xd~|ؾq[[(B^-Lc0 wQ )3 y΍X7mtxxy!^ <-Nt4 `asRCvіi(qj96Jiάa=Tq+IS(Jf?AIpM}urW-7mӗ>1lx4z$#Pp~VnY%^OR%BkYB1BmOZhD-ZIVYO6d4<0Cao{"!W!VXUGB*HR:z ?c?˞**7IT޺vy* *kvjX F)OA4Yф^ FD3.6y:/$qWdQ:s^gHӞ`$FLt( (#H)R%)">iD:3E\{`h<@j*m|=51c{}v57']gߐR\FQoe7#z{D R<%(½7Y#nD7*'(NG ØǴ8 ]+m>/ S8Cىb 5 )i?)3F A?+8!!Үx(.qrMB| > J_?^`t{hIٖw||B|B&6Ӳ"mSL4 4MEd*FKTb'3"%ͤ E*Kt-/9_$rb\I2f58b 7Ŵ苽ص|w_>'H`YVڷg_؊!\>҇R%H[$YdYIV01đ c=0 F0 [") XYuvbt]W -?p. d SACvBa|g,noeVJ6җV nkYθ;ժS2ә=L #lW7v0 tϙdI5q e͎oPS };%Q[%K[fJ7IxҨHpZ^tt]E^t0*3<]ͭ2C05~6Aث;:޲G6؈xPқH&Iط)BFl1B#ut)(z%L. yҁ^~mw_pm2UwB72 ścWIk~yKZ8:VG4kJ]Mgi_}tujx:zoSK,<Qk{~{x#o>|ћH>@w?4ƕ1A~e}R^!5C(B4h ,ISQrΪE&&sp ~H eDPKP~. PKjH5android/support/v4/widget/ListPopupWindowCompat.classT[OQNoۖ@"xKQJ65>,m=>*@O28۪ib69s9|gf2_|`OHb2Ѓf0NQL*9*Ȇ6mU4+֑vh )43 axM{ m"va=͊cɊVmr<ʮԦ{9^ Z!$M=cOM =gUCo^bz#1ӟtn#}x{^1){YwĚwKV(6 SYޅT~׸jpsW- 6֤8Rtd[}&,Jk^ :ut iKQR+-i Eܮ(Y.Km[9?w H!FpUb 1u迌ߋN0 jH 3 +x0Ϯ+xLCwfjULyUQ+lQiŵV^ y Iw0B~3s)4$3`Ǥ0Hg$C"-uaA @O b< pIk9 ";}sGޣ q2 Q:Duh?%˄Ҡ +P}0+Ma/A.&C7(J_ xOPKYMyPKjHLandroid/support/v4/widget/PopupWindowCompat$GingerbreadPopupWindowImpl.class]kPٯivZvh\X(PX|?yUlh $39_ ٨㡍<2*RBu}P@}ɏx(}1 D{YeTJXyL'W!H&Pd/ *pB#zOl*߼)c} nS`ә;ɬ4ﱺg\mϞW6LVAmeV^%GPH `g"K` ⬆U>@{&1FʖRVj|͍\rAAfz5ƈ}ZpX,b!5˩"URjjP%TR멶V4@#53Df;o+h jydZ[ ki)T-*yъu4y=Z=~>NƦV*ڨPba27a8;0htٹ#z ưa>D=b[v~?&Csv/!q6~h6'Ը&^jr͗_~UdlsvC* ?JWS**aA5v~-]ONЫUE}7RءXyS%D7^꿅[mv淩ISnL; ĝ5n;Kx>* ?D~*l@;x)#6 WǨƖSA|Ɵ$J~N~AS|ڳ՞I;55~Cc/ԗxB*F^v&jo;T]*@"?RR?Tѷi{Txg*'}@Ň IzVJktJ qS*>sma " 1\-x_i ia7xg1HwmK":@R+zZ  H +mۅ` Rm"&mfI 2M Ao { ֏Nw( 1CHhZ\̀2H Ca?RQVU]SVװx]KbκMN91H8y]E] 斊e565,nnn1Hm^WU[Q_RDE bI*ipQUՕuM-jhcC]EKmòu5- M\VleE]mպ9S|ʪn(Ҷlqvf6WVUUh r$8@jg% u- Zj/F e02EǜeȢj/iA^(S#- )rGW; #un'yJ7KWbA ܜåfnypyif_7.A]l^ih t#/vwVU>Ҥ6/ts aߵ̋0=m@g'jtog7:+rW߲ ˋ[DߜRG2Or4Jp\-BwDH{ǽѷbl{qXhBuҩ hqpK<] }N*U(Xk_d`č& @5~JE:b]vڌw`F<dWPdqam["H}⚽ K=X ` YLbV Gbޖ@ `V*njЂcmm(tӻ{q'a j6ub:%:&IeXc0u;ڌpXhzۀc?9iwUĭ }#ڣcU>CBKk+^5D0㬈γGѓ2lveQK ƓGS :U 0nBz92f42F~ I5n zUw?}Y/5`*骥m'-@* +ڈc@a[ˈkDuPƃS[k;lbt*[TL+_6r'1&=/a#L9$]=[힭цbMduVksDh 0#BCM 5(Рtc!(z:9p[Eŵ`j 6i"DZV-KSĜxɛ6_xɖ:( Ĉ ́tEjx Ռ"'Q=_Gdw4pt6yUbE[ $ Ffcm={$ƿAwC3Dv# d!6פ#YGacG( U[RPi6[o${)qA1(b2=}J"g^+x=-> "Ka$T5n`im Nk gQ-k*ymm+=Wf~^;Uyw:о B8i~M v7zPhԩSL;B,25 eA/w9}$*#gN`q◹ 2- fmc6gEP}/Mw5Vs<}#S-iߋ'5R)gH[[@Lj%֩J2Q=t6tdg "acut1%]L$11=>8h ]$ޱٺ dgoa0'0b8QX lCbE.a"y*O;^3tE%ivvbͤWJ|5dP;u:CE6E Rq.ꨧeT4PHrv&t,Ztmb.V.Nf 7b5X#r.Nt6z{tVM&=}8 _38. +  =pa{/4B y6[_z ;^=Y&m'MZN}sY L4c9Vk }Vk 3um0H_zU^M?"AڣeAo6̘ `TW;LA=g³<^dl7̇0·8}9kenUz o-!U6q@tQrafK"[\uRAw"5B3UX+Z:/RC1(tF!'ƽ0k5sqpN7]ήk+8b_ #6>5nˌw'NVQ]쨼I}$a<ҫ uxIvHm7Kң=[AT#V.4m܀'ǀVZt[W(iCalsst7 ;!iyl^毎^ES!zwt2o{C? ˣ̷XȠB6`L6O3.k\5}&Y@JĤ}$ayf:҅>=._݆˖~'UTU5 [4pIIץV4 ]!Bܤ. }662NJ~/&@{YgnKo Jc Ȥ8׸iQVʟouuwz yz8jM0>π7 #_p/- '-@.haǸ-Y Nd6d˸ێ8m~ )Nӱai;f9,SZYr7oΣHI7;qlN_N ץy=U~t[Cr ?͒29_|GHxݣ\6O~Odw.TrPEJJVտ-t$9 8Rϖc غ`AC:~R!ZIte7!۶p9B>0sx \ڬ>p܇kr$c S OC6<#9dxo^dqnX3k!,Za:ЬdA=T, IddF.ayCXoނLx[nS0vPV5 Bo+lQ6 {k>\Z$F'E>t# EG/$.ID1[fv eT`il"T-e%$+W8G8&KM&L&C(4UNHvyr9 Jw KFA>9z@aXj9&c.#vg 0ݎ ]`:gP}fr~s\w 0D?N9 `aqP\rr?T)YP)Q]!q9R(6p-}'Q]cYkP 2 a 2\іC R؈^6fY]oUKo`sv}b 1Vϳ x-ߣgIB3 ޛ\Ij(m:YX!Y@)6!e", 4MDT |mJfKQgOtVY pt4kX[( S:"Ft;4yް\l:}h9Ev(VA*Ysy?WI*/d5Pl6hKDn +(*=l6X&y"MlUIprc/ش}(% PpQR-Jpp ۪}U-n<-oTsLЋ@n{&BL+=iB8k^j:Rll d Lv& g$v`. mȶ 3q՗Y"+(Jrq{v4ĉ,IEy㩎ZST쥿yiOQa]4. E9 ]|J:(c֋.AK`?w,n\䈱BNtcw`0O#M}=Qi[nENylLFg!-( NZI+zw0a֌jb1;L Rݨ!u/}hH-?$n!dYk('!ދ! 9%iۋ޶#wC~sRS!O1|:L3}SlfǸ Dxuv(k_"s*F\yRMUˇH~*h6! ǚ<GXK: kL>< -U]l9Hq^~v|]u:2<jF(JY6B_ƔyX%bAa,4rى@;cT5u?exuh+n0{w:påĝp m>7* eJ.vHY]dDR4H̰} zVD9= `?CnIen SB3 gpNB':? Fpc~ WM'ݱCQeh/$~8w|#8_e\ Pe-;Ȅ0a~>љBFnyp_K)ub"iu {![;{停+ q ?"{So__mCpN ]b+>7֌wN`}/7Q޶y!yEfѲlJvoMgǜ[ vLB[v^]tza&\}(I1uQI e |Qo-߆>v ~ ?z~{``?@}('C,+Q3 'ƟbiY%#c iĴCP MW]-퇻cb\o9;;j94B=z#bIWP~/&!I1 0RLEL+2bw<$ӳlW~AEnwR?ܿʨPwAԆD1eR&:1 +(1L,T-1ИT6%2fC"Df?o6^=T(]= Ą VmaQIL263X!/jrb,WԳfNT6؍5M%ás0}֟ȦvHYF$áX0Y/T1?(aI Y%4 +T!^Z?<cط&))Rw4l]'}5bsǢhqք.67<Ǎt y3w|zIFYñ-jD| >Y)Dy$Ue'C"`Bh'GؿݸXgZn5&| ߮sq.S4d[|lx7-εP#}Q^N7G#f ]P/LW58 ~ޞzF39XY_*a|H$:S6"D!xv-|CگL8');1;i#V3 N1;u2Zjq.#kAƯǸp $l\ O¯{af 'u׸aZ_@s{RԳ5x[줔6Ρ>5[]YvU$z.ȇ>q!ދlrv(p)4)ĕx_p:8,zIq(n7n\ Gĭ,M2,WgĝLŖrq;U+Uc!i3>}-/5Ja5iIl"d8R9Z"Q.f3O\Z\o%UOK U܆}$R ,DOt?&l␲*)k(ҐkaF$wT ef|(9oԦ'bn,8٘0z&0]@xɴ10Z^UVeTLIO%㺙.UH#40՘ _`B>e ~m2n E&޵NpH6g'`|:q!x\HZ` KUhK^o{_ã ћ(w`==?_Ѐ>sG OJp_!ė G5<'iӒ}->wRᨖƒteLrL g&4+acwab40cIT}(}G|20r[wsζgb]ƳG_#Ѕ~ig>!M AFJ{mPeURT3* iy=E$rtP|G2U23wgC;2`v>i1H|L2iq )R0NWy3GzGã[x!ţ8S=cbN968cn׼&@V&|}hW @9Ģ$m"/{baPߩvoUƇ-ײd~(s ?PK+_PKjH0android/support/v4/widget/ViewDragHelper$1.classQMO1}#*Yox@cFo/*A=hBڒē?8]P4L{v`%j5Ա6:=![MBF0F [VHa2nJ} ӮN uc;e(FvDH$~ߺT œ:*MΜ\xݤm,Sm KWk[59UM=9ܴchӹViFCLb3ūXmT TC̤l)#?o6ɷ3< z|230 0?&_r+V‡J7ZaGTG2QXgE,ZS"}X]L^$&VtFRefI"JDAP7?e*%&jX~ G[$GD9 8Q䡝i t iίwp֑wQq>:}16ܢp>}~ Yqt*ߑAf:#e7%e<`d;egH A?:&?yo H/lVLβ$Dq J\)s>x1E$ks^1,[n<0ˎ1(r PK?`j PKjHPandroid/support/v4/widget/SearchViewCompat$SearchViewCompatHoneycombImpl$2.classTn0=nK@A0c2m!V6ч>V)$mWH<|⺭ƴF=66s{Y#ylaBgr(D(Y2X`Mw{=Jf,V HP}a T$w].3'nʹrkۣHsxi)\%Pyf;TMguף75a9W{<4. [^O!a#HiA@:G wV3qA{`ldX;ѭ'pZZYfsݚ`(6kZOβj%$=xI(Xl#@J'Ym ūmx[Whpᯟ^aXX鱊7-iӚ,5A"EA&zd[Kk/@ڴRrb{X4>SG, +Pq95hxa{pk=ю0k6- FlU.8C8s2>@H8ZUp]؄_ b wv &iW(.V6Hti  2`DtЕ7}ȏ=l(07V#'9ć|B튓H `"V Ab+o!mMFTK>JMꊫe5VM.i}eٓ21* 2"bsxL+TK{a# iL/kwקUq2PibQVVk924HW3آ0U47tQW>W‡Oo8c M!)"6Rɲ7 %KiMs"hUM. 'ń^IiHUe6$f:7-!]|~}`Mm3x*NaZKDL[f Uz呔]D̆"mh",LdP_('n&LlFIjhpUZjL#iKK )׎9s5EyzPXoXz5jZS][m@mߥy*[PTа&ܰ zz^.ٵWH4DHH_X;JUd8z-բ 2Cq,_cÛMRirr[` ydecflN "dvȞ~Tp5ּ !cE$˗+w, l@leԺpʭU5/,@tHywwZ„=NY&]bšm|ߥm6zcE]dsY[Z6%y`B, 8aZ鍚٭']2( !a9,'JFx=D=fcv<*^Q]m n&Q3#Za^YgB #{ْ7#Gt/nÖ V2P;m@<†XS5Ljh@vCF]56"eqa7!.Z^sXNTv,ZoZC/" U$qNH6IDSDhH쮷mJkPPуŵG mTڅ)b-e x5lYp Mh~,izն~fڻ!rãr'A|i2*s|Yn-_+= tVQڌX '0O<&Jo b1(D^JC6GMWa0{@D2BbJá63hu4U̐$v-ڦQ3(̋$I]F! "EK"ۋٜJ uK㝅8cc͐HWpd^!cXC{ebZhmK枌; +RV'4mpv M-Mѽ 8 ^ []VŔ[Jl|i1=8'7͉m&)&sG }shmKRwVBIh+%699}CІwO jDƢT \\ui3Ñhha,Nq>Դ2)7jYApHNW~hA3Դ^]c¨Kt#цW;dlf(A|.)3 vNgӐ9{?Rvk3X`k ul&͔D)k)K1Q~{C@|hYN&#j5պINe|B~1*3X>-wC9qN;Δ*ҕʗ]ɮ2O kmQzQo]n(*a4B9e v=/4|&(aQ`gV=Ǟ6سldtB&g:{K.C^Kkbg{hM孍ͨ*7 5{H9tJ*4!R(IE.Fc`!@PcB ESR+i"I+2 V;:Gߜ` v>`0% 5"^~ & JC""h迱 1o6D!ʳDM 1ꊐbfe/W7( /T_5U[jmyawCt5W%(79-lno 6t$#UyryE}QCsdgbOEeL"L)LW *i ,5X&Dh ^Nwq71:7t( e8.ea5 m2Gyֈe,ڈ]Bڞ58 ^L hl" fba֠ıۍq G(;FJq! IXbᢕsk\QV|Σ`>D Q.fd=:A!RTRR%b;jC'l%*VQ#b!]C,I%Q.s1 OA,٘s Q/b)NPqV8瓉4+(9Ejy!:8tJV3 (i@PEraJV  FD ph„MZX,F BjJpCs &q$Sc.Jb,gԀ&G&H!+tQ/)J\0kX׋ IjT9\u3Q:*&20 iؼP`E]]Jzc:㦜ymDڪ"ω4ӝ&qf&|t6WM߃ŤȐf,_aFϘq=th_ǑRZsp]x%Xc͉ޜ@Cf&njSv"(z۠[&]j=[ O¼C˒IPb GU^k8q9!5AGin$M\%9\D7I#C+%F7gQ<iPL6>.&owk謾=.jdWsƷ҉HV d<ֵ2V0KcwS~<35; DWU\< e[!qMNM4hLgK0'GeNWd%-#>yy5,kbZz;uN6˭<T 咨^!O׃Ty*Gi?)~ii?&-2$ bb8W%fek2O(co?JptIJ?hݩmSw64M"[=fͪx`pUU٩ _:yFG}ݣ. 91&b.'NF>-EI]b7@)M]?7&@thP7:68-\ݜcc d > b`&B6M \6MKt,pgby\Wtk?[JG (r<'&r,/tay1;OV(rK-|:#ZTlOpzn=WZgl5gzjlON']s:ՕuV}lQڵX3Y+(M~Dմc~ [md0Y¿-hϱ[عzg=g6y!.;/]l=/ʧZy~tN%Xw5k4%(aA}D6>jDHf3vN`r]`A} *A>ZNVhGsYQfv qo5A`?z\<wX|?mdP$~J)ɡ_  A$ k}W] }0?I/ha>(2 .YǠ 17\tB1=sNA Pٰs%ٰ$p(!P`$N4V9 (rqa<J AY7adDŔ}0/C&< ,0^Q _$xG0sPI,X 43}6d񫑵ocd퇑Ş`gxm }·m߰{dR43]Ul4RxaI/`̙@Ph_ 8s*;eyl~c=s@$$ez>R}n #;aND2L:3qq$ ]e7CG,`P,;B8Y1C&8JmF/eIw 0JDތ GbϔGaN&!C6($;~Sfs9̋=F!!(}0; R σ.:@(0eg#h@{'`0;!҆QA1ۃ*K8_T p[$,UmU^}sZ08_ۢYŜwmqY~GPnRK: D[HQ'-o`{ưwa-d=5XXIrOb!3Gtu!TN-ƾ;5@u|d#p̕jϕe9sw|\6` vК #H3ZH9<nC{/&lY&5˴e&e|e_w#*5˲e%fe_w><`ctA=0 1X|?p*'ʟY8.^% kp%rԯ&o] )VޯcNN s*<70GM%w1|iq-`:%d"dYddC3Y 1$5$|Dkgd[7xd(.޶F*F~ڄ˝C(k: "ݕ(B*aGu0 A(`$"ѻ8 L5=_C2Ӛ=.3tJ[IT%24觤Csg @ *7N K&sw =g${̀dm0Bn*(P?*kTQFSʴ#MOs{.:5uOX㢚fsDoGoӏX,!O!ƠL"Tn"qhgQR`h\5(WySe )0Fe re8W^*X:e4ĕ1Y )2 nT&|T*A8{RٹH( bsڔً/{ه翗}$7$w?K 9$>La>FF=so5U]dI=mAh[+J1T/3q}iT7CĴH`]vD@D(}=zvb "# ;aS7GZ.e&a2lT )sa.>('BV('J:(xo#0L@괕y~*벑R.X%`;{" C58l&7`ioQ9a ßpq D) 9iȟg@*gj,W"Z!$J##m(E؈ۈ8́4"|]k o[Z3Zdn4}lHߔ|$yZr*>t;WEqM`$brPZAUP_![i&aVdF퐮 XCp5UXk)dĖ3?l  C}vaHeAM텬q8G1a7\Pu]j(20Ohz&cϭ8LvBA@QDS<2lY()sxؕU(}ע]lvÀ9ME&8H<"f}<֗+{KIBh 4N.x鼉5Rf9Dt4ʝeeVҌq͖+IN[649hq*Ӟ"U&VNa4.2l-;D9Òe1! L C$Ց{P)"=ÔQuGm!Ky@4fJ졩P"ȌAǑ}y."8*L'>tl/'z~5TJ0`':~= $^]5t"Z@8+OIf(O˵Ԛ:gZ|xs q%IÙC8ys \hG1t}ϱ ٣]j9fqBȡ,ɞH8n]ЄJs+ i& 0r/ W` (| ˨/e;M]\˂b[kΰ5CCԌe3`Ckdp.ZirLm_ni{hP߃Gy HEx +kiT~Xw{P>F r8LB .upHuޅ؈pfbʼp|Lf/+S) |]~;b'L)]NV.2X)./CE3/vA.)'(W(:W*?&tb2RVeȵr2>Zsc?w/w?M@rm{&bߨ¿@ %W}7Pxr&OB|IB+V DE&\,Jp7<[Ãb 1~&E1b8|.F0#[f lǦl2vBb l1]%s ثb{]g 8WSE-!a:Bx p*|ȋ81bp ϐugÕ܇\: X1YjRgltFR6vs gBJ7)eK=Y3=1>JGRo^̞yWNk^un>7R-~Gt:|ޔtw,vb'<4#G`Bڌèwgwe LFNY?#z}={ȉ᧠it+Z_' nv iޟXȿC3QM9ݬX؅!]4Bu0L4 L_8vF&H >@G@l Ю -@߯;zt ݌nE@/ȯXC)9B^mK\^q9tս{:Arl7(|(fΪfC1iX mݬ3wp纓f*Hc PsI;׃Zf_;,cNΐe^YUQqw{QS؎UId)!mN6~i4i8e ^pĴOuoz۟"&~C jA,Z3ͮ;$&dfSFHm*}r`]BN Yv&(E$夢?,6tp:4g ЀT&%I6 Ƚ7"b'o1D#ŭȽwMȏ`U6!] DS{<%Y}c X?,Y/j2S/j:[/~.olx&~}h*>bo?߈?K Ƈ#bS@ 1`V4l)/! "^*#,)5G>uُࣤ 4CҐQăEb(xG2w>ǚ;ƙ.6KCeʘCC_Œ/tM?e(G`0c߀H!vUK̵c@GCx'+>E+go8e[#24V*3 08-v`?I 8h]cy%SIiD.BoH_% |r0G|;v:(on9Z['նcFwgI։ e܌uZ!+.'530~}ہM#*T[吣 *LD 4lj0u>]|i͛<]/[7=OT( 'JtݜǜeۿUDyעs\AsVxv`H,9sp:E}0m` ܾn,>3,?^Ftuc {ތ^o:[R*]vuBBWUMCU9Ph%M 쇟!ge* stEt(2 n!ST(RXu:LQgٖJkf T^ZF 7h G8e*^_Q؈Д~m'\&RE{ рKk +r1f0?@Gտ@ݟRw*_ݲҵ͋:]a',PR:h?SG: …e5 ߯;N/^JnzMyNcSNx~ACԄv;2o)Am[.KQ\oPM!ޡ ̼kR!osR^ Z0D.9TNLjݫ9DAyTSQ)dNoΗo$գP 54R*VAZ 0\=NPk`V!AL8O] epz2܀nQO`z:<ϫAxC] ̣65†kYMPXe-,T[65.Wٵzvݭndfz6{G=}KL!c`6_ZK1wMӤ5nJpx&Y 6HaDPhP[6m'Ir] a[Glw#uZ=Hl#^GB(:mKP$5_T|)(MLep7 |9ܰWPx)zx½Rs^p?|ǿ yrV`wЄTZ/3i,qsoVe*E3Ȧ.Y_gsїا[ve>SoxՋФ\ːQUqlA1σh'?_m#Kd?vBBЖ陸#s+tGʽv'6{&iu|iyg C:>i\]M(Dɟ:`5F睲_Q'|Lxс)LI.ٴ9V.ɟv $7 مɭaz'@ W{&1حIx@} Q7נ%03,Ag+9:$|զѫ|oWF^!I j Ilp}8lex"ۧ>ſN33L_!UZu/K I?5#$T,792MT\TIzrr'rInn̓qq׮{5ol7{h[;ZwMI*o? zKHW`*V_y/a:ԫBJoBP 4@.sQÏ?S9CxAQ﫟Gw9*/%'\l(/`zA A6_E ֹ0#sÏVFjmVIx.Mǐ6?}m>LdUXQ!e1Ιy\)/UGKh_DMF?vd鵠g+1(SmGP!Uc)Eܱ9S~byk8]pbMP57k6gk`*nV}.}|tWtR%umL7/ۦ!3̏Ad]~msۉg:ŕ{h\96&?t'(WdhCO i0Ij_Q㻥CH; Quta]Ks(.fJ6lCz%N;v/oebKvKf|\,E.Zm1U~ d;M#P̦{Y̋,nLjA]>Lvڗป%Gt 3=V>b9SE?ȗv\&}( !`8Pz̵fdJ\;>}d's/ۦ"Ŭ~E]^Fm 0TcY)zԇ9_x~_"C3OXd6כQb%[8&<Mc`?wfyYJ7%hJq ɯ@|87[WhKh[te=Hw:Hd#mn%aiq'@^ISl@~u)ĆĆ]*>}+N4hs3,}UJc;'\]~9 +$# 3PO"+R"We$jr|s|5v'}Qqut/P+Mv>3p#Mrd'=˕Lh/ 3P*YXڟj{a]ueXn!{h2O1TiPV!Q&qhfTb\Ԣn{?kЂ`61̆c]Z`6ѐO0l>Ku!>K941 gs0 ,a~6" Q~ nmcV0UbyqPK_9@PK}jH4android/support/v4/widget/ListViewCompatKitKat.classQN@}b@%&5$&FKҒ@,/x(l!9켝; 4 phU5uv%Cv ِPEO(92oӑ?QHʼn\ E,U L8g(NťOmG&P^4t ӁH;3q(-+[`b)hT&,'b!LlAa0qLhwm0oy5r<=:l+9;.e`ۼX|wMrZ~F֙ce UP,h(c?k\8_PK#D)PKjH4android/support/v4/widget/SwipeRefreshLayout$7.class]o0_Y? VCK&qBBK(#mqG!B\8~}rΓɷ_C7B,fU0TXvagUjLc;sm\<ۏ2=.>\#x='RIaStkT0ca8@'<q#z I0D/ J}l<Ά+{̈́;v 5bs%R9arq'ҡE֧'!tDUi++:Z:FMrg?Ã2LJ Oam{wG-dcFhQ.SDzf:t!}ZHE = "ґTqMOE-/)Bsl3*GCԃuxK˸R6꫸Fs@ZkPKoEޖPKjH0android/support/v4/widget/SearchViewCompat.class[WW'\Dn"bm*l7Q@ E[&313`kھAirQjh_^9 33VW|̙gO?? y|FE(vC07Ɇ rgTg 0a,ala, $LI0n ތ ފKoG-wx7:xִxRlnBUAI)Zxaq 3dzoIqefuTJhV"2l}3a|Y3s]SvQ vW ǒ.z:fy`cKzcnC # =}Uf:;m>?KS4gؔ >UŒ:֤YʠþHZZyBkO- M_Zf|Er l] cd8 {Jq-k6ڬ\xuDOm4h3ILH7Fh,4z^$2å#$Vg:dzr-\_å 5Տ o$nRE5UkxF-"74!s]GxXN9$'E>΄`ddLEJq}Ai$"QIsLNIcE6qC\1^nzl][Y@,G.P-/(Oszʩ^Y}UӸe 1@hJn;ՔUƤH,4~BAqF Я`BaCQ0*̇H1|`\ $S N㌂f✂7'zE]H*p}3'|^u־ NahY׹FCG ǟDd3EdZ)_9G#A|AlMiƊz6;;~6kK?u515C{[[r;drzmQ陋}tZwCAE0Hϝe'0ܮ k,% ݵ !9OT߀ZI;a6HE$%E2^޺ pxG$ppGxx< D,#9'&60ft\2lj "d<)T7<ܯ82+,d0?"gߡDٷtٷIAiâz$NsXxYBqN/)@(&ٿwSRSo$wI*$mvJUEOINI<$]H%)oWIWu]PKjHGandroid/support/v4/widget/PopupWindowCompat$KitKatPopupWindowImpl.classRMo@}:qpB -)P",*UF 8aok,N ~bvT",yy''Vq-BJTGXy%xmKZG%dܓ荸}1 CG*?kNw*u%42ɴvG$*Ijgafz-j6~v`tz|TwQuڛ*Y](U˩gDjC8C- 1:m\ۓxx@H@ՀTp$.p._ ubĎw<3Yחw0q=)φsxAa"%(2XŒ(E,ES`(0\+I|ALo|*/Ըab,Y<}12d g80|O⼖ѓi3ZQ+vV'KӚ)z&j hGWOE^3|m+a$RUO ;=>PYШ'%v3f2YvSXD"(ٰg<mR:V5p q.rcθZōy- E+4=I)Bd/k+D/kZ4Kѹ2O BJd}zHFv"|"]˙2d;=7K8ܴlGAq d;x;N,|č^!n~wӊJN:|6ˀٔuL/3 2Oѝ("lѭm]R=p'Jm } o:uKѻ9k|<}}A7-ʟ֯;3Ss^[/.P,uj}#&BA zdWW~$Ȣ7J{7_M5!\'#_⯍8>jEp4  ʠS$:T72>$$wdΘt Td(Q Ѹvrߡw#nTU+E3է7o CTF 6֪p+I׉ RB~@B@;!Cdsa/G3r/g!颓,gݥa⪬$hp_^;$#6UŔ:+xh A&aCPG}&8UT:EB^$kxzYr~ 7]µ{~7+J9Մ]:pPΟ5hƣT?Fu7*V!lPJ07PK(2FPKsjHUandroid/support/v4/widget/SearchViewCompatHoneycomb$OnCloseListenerCompatBridge.classPA xM}(\66:,3~8vTuE]3!1Z1ڄqaB0;DZOalSg#2FJ[rvei_qPxsi} q^:|m=9&i )9s@*qa>F.wnp9|ȍr|?(+>'xrfN|'v3n|3/p ܗu^,r=v1/MLRk,_gnY-Lne7v&b2L&w2LdLRL 1ɽLcr?19N z{bPBBu u-\WbZȨe ݚQ3OBmVHbzMףArJS"Z:BF/%lvWSwwYbsWURWe@)8.:[McRLQ^gn[7Hd\hS3.nK(+18ߗ0eTV׮>)ZSC՟Y 3šAXd-T 5qV%HA2<%8'lSzt+6uGR8\oedH%Lh`{5(Z,jd2=lhW6.sT#%LգyHp>dAfM\OƖ bFSOЅHX$%0KڬG(pQs2zF^ nӜdXBz,UϘ'15ʉ=r^یx&qpxHm8bs\ZF3~HU7s!c%2~C2bU?C2a'(#'*qD'X}U~eİC¬S+NDO3O2~#2~#NJo[OicBωgd<^"^GRBq2"7;!3ykL`w2dSbyH Ib9sv&&dN0b\Y R3y$,-[$錣zQ^ %#k-ǮR^hɉ5nn^'y٬ NJ{i{=ѥJ퍯y.q-EdRdZRjG{SOnCI#ݼn1dfUZ~>2B Mf1<ɞhmym`CkgG=zjJd B$qzT nZqsid6r_a&^Њ=DInIŜO@vs2YbidbʟcdcI\pM ֍s٨.7vnC eƣ%Q+I ̼>"#IVٰfum#q*'U܄eJ5j$ 7e$uRYBە.J'8N'ZTT&Uk1+*.XFvRFuŪI)mWzǪ4cDg7ICfJrq=ڕs3xǀT]]3NЁiX&6 (+ㆋVkH8l$Tz}!yQ̓.SAĵppe:N^(qf)*I֢C(¹C^})|Cp/{AHOsR(}Q:28P8-L*w*4N-6,&/`S-)nmhc@P܁6~l)b=br!y2p֓AagҞ3&Ql$l"cf'o qW|#)Xn7qP&0f\[qL_rS)L* Ln4C3=)V:RSxO=Vnpxaè`xc/A$V#!၂Nt>9B4.KjE0гNjtr V1pbۏҮtVS87E]("(5`ƍS(OR!?JgCy @yT1E{˦gs8ffL  Hr$g]1Imt.ҌUSp8Z}Djc~Q6y\)Sy/;o{sw> .å>.>*@ۄVGd*Aw7*ٗ6&I"V z2e63Z;rIXLn^wy!)W:'aŲFXD/4CiY&M<~V Dc5p[{ڪ=VYK.4w I%ӓOU؆^y^yTz 7F^ 0VtE.XgVfv–ʦRYqoB2bHPW0t~9*'xqs|}CQ~E'y\W7s0Xr83c ݚW~{Pq;Bg'~E@cy7p V s9R!PK= lvPKjHBandroid/support/v4/widget/EdgeEffectCompat$EdgeEffectIcsImpl.classKSACB6DAPAVK *UZbq6 f7I|ܳyύߚ'_kBkvpQd"?PK]zPKjH9android/support/v4/widget/CompoundButtonCompatApi23.classQN1=pQ$V\Dt1Qх},doF~e HؤszoJhb28PGl" 5w)5˻-$9u#&H 0b}6^W!{$VJIq(juBQFić#˳KzuRoG<nyc5{ކ +G.7mx +6lj08."$a򧹱deֽ`ӑdt4Ri}#,'=2ks(GLYWPKB?zPKSjH6android/support/v4/widget/TintableCompoundButton.classN1OETcظ@5cBⅈq_%C;i;n.|BvsNoN` om$qLUQH{#%8*0j%BRriVUt2˽R~"(>$Ҋɾ0:Ӭ-R*'JQH%QĜ:3K@&HV<A5)F,~-C.뫤.zϠS釉o332 7zJd8 =>#8h!>w҈ovy%ˑ"IT`)w^p_] /bzT`p qK^XqkU'PK"{iYPKjH<android/support/v4/widget/SlidingPaneLayout$SavedState.classTRA=dIXA&Q@h·w&h%~V) *U~%L%R;}Lw'1,P!hV71$-jBE2sJ= ,/鹬AKny7 [03˳S+*fbܱl 7seV! =zuH0ƦɝM9NNV ŭ-vԝx6o sszN|"EyI4CG,Uߌ#"2/_chXNriӑUOS$I_NLU*GehyЫ[桏F]TޠA?,JM=x>I$Cª||D'p nE)zb7ۅ-T;_ln3Z*p?+%{9xT\UFAşn HhsX C&PeB.7IO9¤ XE#YA=}̃ "}Q D쨺~W]!7q SD#u{e}AH*}r ]P@BNv4hd0PK858# PKjHMandroid/support/v4/widget/CompoundButtonCompat$CompoundButtonCompatImpl.classN1"07Ѝ  ݱ1&et:n.|x.KZ8pDP uaQuu\ӆ24>wRh.4U<01T]4(곁oW7~/n Y\5<< h=@1ŵ(`i,e'77Ԝ|eư]*ZɈ^L׆l,f }%_-.>M,l|ܕQ} - WYwg0AI% N9ƺ}@> -+cl_ tPKvYKbPKjHIandroid/support/v4/widget/ListPopupWindowCompat$ListPopupWindowImpl.classOMK@}Sk~xoBzqz1"T,z^%nIw|(q#9 ͼp9 )"Υ(C.ҕԡ*Je0!L"2U v5DQȂtE-x&tʣK?:ɍJxd_]oEG'^LQepVM4XQ޶`XT֚=oTʒ\tfaF+lO=A}z!8ñ7t#PKVPKjH7android/support/v4/widget/DrawerLayoutCompatApi21.classVWU$I@*/EBi,Z|R"Vd .uƯo}=G=~GMI8df޹3;3?g(>) sa{$̇`AEInI$KRa9HJr[IR!0Vaw$\Tm|Aw yPyS+ٻSҲN\udxE 0ceu# #/7Һ&W:VF3W4ǐϕŀn셄:m9nlblt7vѶt';cmؚ;ec2V~}ʶR9tȮMCߊl) /ZfW"<5 YY7lp#{ڦ3|.gtے'(r^Q@9ڦ2^2Yk8_C5'g 4/qmNDQ1elйwGv}FJmZ=^F %kWL 25tR>S !+tU;d ~ ;{G3Yye)e)$3PKgyYe PKjH9android/support/v4/widget/ContentLoadingProgressBar.classUmSU~n^-y)*5YZKKZTB %VIX2M /:3@{$Gs=9=ܓ_cjBR,Y<`X?/Q4aE8 śdnO0c,nj{'s-ajfqٲOKkVB"o[n$d|E lڰr1; (p>m5sEs !wˠ ܬB;;&H0 ni[+co:z491< tJJ錫e0Sgt2aV_?lV^XUxc n[3u߱ͳ^I4sZ~Bht^ }i[HMJ@;K֚ 4OWR601"sd}q*pGuwn[eG Q h"~v>*`S|Ny,vC>;ڈ* <5\4ȳ}={1E*h*PXf#?|$),=(n(Nax_9&}kPKxt PKjH?android/support/v4/widget/EdgeEffectCompat$EdgeEffectImpl.classMO@ElHz1LC&$Ie|1H芠9$4PR[X$>sHYhlyLnVe\ıHR{ycBv&c~m;P8gQ)$o{#8 ( {"]ZMaiCEkua_kTqRu2J_PK2,RPKjHCandroid/support/v4/widget/DrawerLayout$DrawerLayoutCompatImpl.classRN@}K!N$n""TARVqYp֫:_Џ; $li߮ͼ_`#@;̍N MB+ l.ӱi5Wt;W-h4;[L$|7,+S)\$?gK"GC\/mp:ө➔&KGy9&:foeC/|¡Myo:O>ka~ YZEq ,W ?a hV:-ձAVPK'n}PKjHGandroid/support/v4/widget/NestedScrollView$OnScrollChangeListener.classN1At\0x8fF"0#3(qz;_ ъAf9t+͆ \Y +q$N*p! +ŧ,{EH2U:Dt^m<M^\ٰ#t3x UzYAy>"*(AUa7#$~PK;YPKjH/android/support/v4/widget/PopupMenuCompat.classT[OQNiRhQZEdM}1,㓯DI$cThMl|9sٙo9sf#PGw1q," "K*@Spa ,۷K`H)Mp7+.(Xf 륭""7%۶xZkU;i݌M^B!uBܴa(MQSy&Vu8ޮ$Kק;|fUla j]*[1k=s2-xk7whxt\KCNG'>XatzWZqe20n~۝.ժ; JTOCrL .㪂 bbg3${o?_ ' ,WL_2 VE*1 mJILHB(yK&ɜ4*I`a1CZwf IRdM=F(C!|D_ޞCPq IpKap\r=9AB0r Gm q,a:[)LunP|C?0˾ n( LDH!9BrX49h _+tK_PKDflPKjHEandroid/support/v4/widget/PopupWindowCompat$BasePopupWindowImpl.class]oAk ZEJKeCCilBMMz7)l. o42Y6 B/vΙ3gٙ/RI Elj(i!ִM-n3%2 =E 1wM51cz =:M%ƅc1 n'(v˕fK#]_}~eo]^8ߘ@0,zٯytefxUQQZ}a͸NNfGI10AjmL '\;R*Dw.# O;!n{>Ir$f8mGGSʺkǪNg nSux h42x9^:*-ns*ׁ3cΰRxչDҥg>fsd7/D Tp )c,pIPKזc_eJHuBwD7$'ʻE^,VȋSmewSYczwRÝV{XaɪhoZ~:EB]~ܟ%:SЃCl7UPc,͒&fl91PK[PKjH>android/support/v4/widget/SlidingPaneLayout$SavedState$1.classn@Mc; F P%hB@HUJRT qW׮l'Uy*i%B:VȲw<ݙ]3Cj< _8۴ -X+-^`lҒNo;O~P*2]O axt}s譕fLB)baN`2$qy^l>̞$OTdȅ>͝s iV&/l2ٖ=}Ʀ1|U QH}4jnRܘ|MͅvҖӵ]>`ߵH"e`lLnu.\2PeXg*S$4@i&uү\81թP/sF75ޮeȔ|ԕSd\D I!0yprCUDP"TbizERib~^h p,A|FrQCEgqg,r ?K,^EнSp *'F~dPKvLPKjH9android/support/v4/widget/DrawerLayout$SavedState$1.classmOPw6L&L|#h)a|HfH()-7`/Psm1@s?'?G:gRգ nv/vu 3Mzm!?C٦z`2-V+ Yw MZҳ-x20́muE`| dzSX1d;R%\vð]TITZ98gt?ڐC9ܙ!yh$~>Ы(Y 0CqZ)*PKQ7-PKjH0android/support/v4/widget/SwipeProgressBar.classWypݷ&/Y^0& a3D%DSbC_^>oP[[ƢU*M,%vuǝ3ؙgj~{ t8{=wOX8;'vbvg(; ~ pu]!B* yHƏdv_~HbgBr _<8=qO\9hpPEO9iyND(wD&)i>#B ?~ٲ/ ֮% ]aZk2ɚ:36l&e9fB}mK_{W\c3k{¬5WO%>`w]գ0L сQkKl4hlOdt*3JwDVaz~`L E#F=Cxk,"DłttpJw[鈕ȚtY)Y3e"x[4 #D" xk2L/sKܲ<\n3m }]GRW'1#{Be$:S&#:Vq$ؑuf:*}gГ¼p*Lg7/o[);L[jSAq8ce{!K_Op"Φ,pG*|#ԷSތ^$"׬5S9o2d<"@kYƶfFoݴ /5@~w*Kɍ*,3cDe,f["_h[ 3A! Y!sEr~,Rj? `XBYۿ fO}do#snkk稽Bj"{b9-},޴퐯?&㌣Usar[T4Nr6MW!ɋ?ylVIg \+ \ \ ^ȥB:e[-!y~sx@/xxɏ Yu# Gy$^kx7񖁷qwq ul5=ğ Y+' ۓgv g{c-퉉T!ك^TΠ1f&7Z5Q d([c!e1uNi/uZ&2+-ͫҫ7gA.;(;̚Xv$`nw}fj2|;Y<G1g_[1'8pDUI5O%xFuڃ>-"VpL;?x{w|j_\]5]cVCGV,g%ڷ-ye}&b|:rSJ&srq8Zzj.@bB&+".bf.e]r_a+u3RJ&]BaܬA+[yQVa8יҝ?FM'gLxi϶YĠמSlifw_Zk|1,Åc,>fBǥ4A SԻIvU5S:u\qHϕs Y- Ƽ<1k[ SUԥk^l/YU;NoGbHL[_0+n]0U-G,k5!yͦkr{KKɸ߰FJ4]g]H_v|i9 |1_e;{$aSKi S^FW"e+ZBl&3n #mk(|Ixvބƃ`}^㞈BEt%L%Ox3L ,U*Clk}'U )׮W"%7M=>h Ō~ 0 40 é4q<*poEH< aUpUcyޡ3Ky]nԗRzb^SIQ!y]fkĨXWHJ튲; 0/6 MC@m}ԛF? ]xV݀FS8K'Hahq{CJ^pG"3Wxm3bkxMjJx6^Gqd(!&viX_0X*n"ɰT+dK15`4()XH{Ij/"q@xʐ :돊8fC<-n#4ldQ1(ag/fT v.!۰h)w2-]<%Зh M)dvF%.bSO ]"\mYx&I#C{@n1p2 -Ivf Y 9 2 [PK>PKjH;android/support/v4/widget/DrawerLayout$DrawerListener.classJ1]ڊ ԋ9+BB 5eI$Ĥ9?3|in>k却B Iθ(j% m4^z{r^J-/9.ٗ^^s2F<_ʮD ͆<ÀpwrUYY}%ҳE923:IGGV@l+OUJ +IH@8DlGh|ۏq4G?PKcPKjHCandroid/support/v4/widget/PopupMenuCompat$KitKatPopupMenuImpl.class]kAӤ&Fk&/m ?Htr?ItffٝMVRij`=3yy0~ 5^G'xa6^>r.!߲EXQɨ/t=JDŽʩv]0 ϔINFa@(V Xq'_Ad@IȉqC=ٲPk%Îm:vxx~ۦc՝Hx{_˱4C]k-]3 )w}S!.mձNG1㽑e,yaOh.4 nVֶknVmvew< 7s9u;=cu !x&va'YXT́:eD:|,@QQπ|Ig/8bʵpĔb.F FMbu+c{Pn%2 {\NDSw2ܺpOhrLv^w ̜ȼ~ '% C媅2?CI7}œocn5hxIf*0X9jKY-$B>Bv 4 }1-f i)9=M5+k3dxc()x%e"䆕ڨ4sB "?N͉]lF&V t57z¶%BP99"1P#䛯c`uS̈bay )N M) )Fws *+ R;*&ܱzְ.BYD͙-5bؽ-*8brQSm)յJZ%Fbs T[녞`O"~ZJʳGg }$YJQŞIp) Õ@( 'fEA#9 _d79&&ARzHZ!BmEB\f{7 F@Y8P-sK> 9ID>c/ nޒŋ Cbj$QǮ,gxL悎]KY5j*hHT*";{:1zh–Z\_vbuzlh!֟ #tN+1=NOt{x?#%tت,A=i~xzJd;̡tHhxXp7e3xHgu:Lsz,zDuak>g^89Pu\O:p)9N")|%%Iv$B}w4f%Mmм1; M DCRw%g͵' k+$D]*L&^:',|,qi$T͘,[ 3;{i9i0[ KVӽg2GW%XiF{"6]gK-W#:Ap럀/ؘ~Nvs:u+s:ϩV_!:H2>_]D PY8G^?m^ԙ>n4jd264jvt̺5'&JL |6#}’!)AcLjZ؏:*!'R.>ְ?AIA&]h}hggH=R  u/YP"M~%`%Hft4F ⥙.?sx1MC78OiX'ԓj/ԓϜyzfK_"mayùe釐1F.osCq;EzdF֖aL}[q9qx+"aAƐ],n‰:L:hRl8g|8 ]?t&UfRq z_fNd+*sx+N ikk (>#a xq2w4[a AC+lSP;];u o,`(ڋ,鍠g)<"@6O|HfYcyR͌ca.s ͹δ k Eʹ?la 017\ofI߂ ,Nvme|둋o;l>w.mW>E yHoKt2}#yKs&4Y/4J'y|٘Ǖ-I,K܇'9M ,Br ($6r[|qxBGsjUSy,4 YͣȦȦ*XcbS{w2*g 7 oIUzR>U\%o0PezHnX^5SW:2YNJQx%Vs.4䅗*NM[`97ihdzSkl"4לJ,=H0Nc5Zc-֣'9lQ On,ZUjgY1$WjPgp҉c le))N EԮtV&8; Սʀ36#Z'̤s1b܆[8s{𼲒'/-g5gei.aokxEÑ1_4`31JRYzo sȿ#dž!%TɌjq4ֻV.kfBbvx`|66ƶJ ۭHT͕*tCve s+؇q^v3^tE8ΕGڢbE+$eΓt8ƌ8ί-A{ˑIWʮF]yt=;MXA79ѭHntND{q-݇[qq=q\b=}Zi8-*_Xb"=0z8s~,\ѱShQh\q<#ҧ73KW-V2ʶBU+t&lmvRaQ2rn~ܪ6L\w܁D%U*J䄤- ɜ3LTw#1`c!:4q,Ec9+H`! 8d:x˦=bѮt^[68%9jr"QuhlGc .֛tW<'|?x+ZͿç"ӤT˵hT65$d$32[%dˎ4 /Z^W=`dE$uhl}z?lH.g.nj9j i#_?)#"9- PK},PKsjH;android/support/v4/widget/SearchViewCompatHoneycomb$2.classTn@=I$54JC$A-bQĂ\"* v{LDM" >B eK9s_D,֊X%qܡ*JRe>aI 6L/W ;kE"IdB F^2Mz-odUSoW8趔M(-kWa{J_^k r&˾זhG\M ݟٴ8QS1j3kq (`L'b(8:`k;;2A!u72nXfRF!g>yf{ PK?zoPKjHIandroid/support/v4/widget/SearchViewCompat$SearchViewCompatStubImpl.classVKSA !KQ_ P@h45ZM2>x/zʃ?e3\v(8lfvo??mxMT5xA qMhʎH*%Y`.f:F+]bW p1՘KLNF2m+y~9ouyΩKs`cm9~G`ӣ CIqc&0mW,Q91nz# :J`&ЗIK oY&11mq-&ʶvǒv̛h{8yIr#y<m[޲@[μ5.W>uQs19u4XDb !X>^7{X@+DZIE b{)]vqKw["Ynŀċ6{x\܍TBHn:G(^Uw L2~&CYjP23&AʶXq-dkV1 ۙ+7VՠKy `Fg=̍k]ľаehI>hlaU46mI JT=Jzu@3d4(Ag'p"Z_;A`気!`R ǒy7>?i8q$be8IA foGmp Gǡ3 cJĐo27bvNʄ]H#ƧIFOI؀iu }*Y%Y8'Ϋ`BpI)Ეj ] LQ%R"ku5&4JBzny|q 1c&ܒ #/˄}Re%ܓP1%Cd&&QT$4K)Jh]AhFM蓒Г>}Vʩ;%9 R0/#~ϠqHB82-PK@Rs| PKjHHandroid/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImpl.classJADc܍k/9-7@@1{i̲;_QDA ҇ꦺG9F9 '{vj9J>J{:47fh橠z`/3~ ]{Y$\,gh+y+dP׌'V{e&OT,obZ)8 3j"0rİ8`[VmR48yF~pg: qp.GB1%t$0pǟOc |c''6ByTPK0V@vPKjHHandroid/support/v4/widget/DrawerLayout$DrawerLayoutCompatImplApi21.classmS@WJ%3 -E"> E:u`HC{&KR;B_rK*0upNo/{I~"lO0C-O2 KixΐZ^3td%d-[ }EXa&`5]R2T0)% [xPy'X2os߷xȝtECse`ԗcQXq3 ð:Jk쓂[G3g-+bzai§8f5_v/3ۊYSwlP.ՕN#T٣PWUvN3;>fAXсẠKq)Ɓ6o;:NUc#:l_S ajȄnJe{B>9 SE;L3HRb<$:vjRz\jF(μq]Zq֞ V1]#y7ՙ Z 7vi"VÅd!mOzgq;E<ۢzXgr07 _XpCeĴUi|k2wXk fw(٘* +u.B*PVJceel`j+1;.4MӥRV iO?/@'t ](q /$V6܂xU˜7!PK@=\PKjH1android/support/v4/widget/SlidingPaneLayout.class< `of$M$6@@pA@ $D \ddaw7ThU#ފ>jzZUl m3ߜo޼3xe&έLDŽzMcNIvȏ| %S-8; |:T\9IC*(WI,WQr<5̦\5Pr%5R2y̷J($jXH";_̗PNBT?/r;DJ|`?%+-yv$(f x؀9 Yyyn+y5:JSr%gRrA;rPs)@ɯ<|"FlBr%0)5%[)Kev~9+iU\M]!RxmDz=%7PRF{umlJn6хdEnvK-e-n/%]'>@v 6J:Mk}G(C ZcvZ}')>M=~Gku%LQY*>G]*Jr/Q2 ǫF+'Aɛu!%hiH _WJޡR8[vFC?P '^!pks 0PC!"FQٕwE_],h @+ohG` 7zcX_P ސ?c­ 1믕M3Q 'u;{wWӫz|oیȻ*#` . 6뗰&q[@ԗM5keAo.A|0H1o(l%TΪ*?~ټ.{ꥳj]Lh+g-W3og1R+0骭!,U3~ B`1lGYuM^_x-yeo |eoKS!Z3:{{ ^hlip&\n] \xr2Hב Db5X.J\#TV  zkz8ګ'Ia=>bz `m8YBI --Hl̈́_?V$d$ a -X ܇H;EGy-P\nhLe}G SH`#_|SkxHĻpƮ@ckTKRzD4ILy5 *ZꍍSi K!dBbSF.Y+$5nk ^WRp7 H9+XSI5HpDd, E1M4蔏XEXxI~5M`W ,>(j'R2՛Z#^Q$S^D`oIڟYzGٙPX_Z@BZ?揔B42 :Ds5=@"ST$H_%SQjɳ``/m慺;Y,Ip

fﺚ5zV\kC‰JxA} ^4~Fr0DPU(YZ]sl}(k%Nyol*[C64 'ڐ؈:@^ iVlku-#D8vZ Żp-6 i">$.+f8`4&wjU72~GAZV!H%VnmhO;4ݯr;~4ºRB *D))h Im"1ċ#bU8B~;Cxr|k1 nI>@W2ui%J`Ajwk̓>:#F<"*-Z\~aR09IQS)RIfꓠROºu"RRjCaR (@Zw- /˃-M^$B !Snoؒ8DkTr6gF5H6}13k 4H`); M!MHo9$h[s!͐Ⱥ7JJKs}AU.uMi !"`T0`mӭk(5O\[#D&bZ0*Go6H;E @eϡ {bM{j-rl12 xQ ޚ#V׷BMTZ-!1l7^hi@s3FrFQ*+(C0z{qP=OljR,&Ý$G )D)=Pa␅FwruHӁ$eػ4<;ʾ?bt''iґs)_ K8$0I{,Cɕc';v=׍r4Yr@&>_mU 4~/E4h-G3wd Cx!=7LĎIJը4eb쑆{ R*լSUϿ&_DIsR 2SUW |rWCl, ?*Ufbfe/=ZGԭVe6fG ^?-',lXK * ʏDq@lb&UEVac0>GnU"CN"h G0E.l$А"rl*(ɧd%by*f %()(J!1TFPRBGRQC*:G=&hb<k VkULǪb2%S u{ADPjY(:F&ͭXqboؿ5tQזW^6|D Ѩ> ͟UQ]U=iLLEL.E#R`86\,Cbڱb/6D ~8NtU`B3K%TQ[H̤uqJJf(w<[ğjJЈK%%c]NKeP]~8Q Dm'b!%bU,!q]2FqJ>gTTShSu;&4LIng*{=H]E)w4 W`HG!(T޷Hl]RF;h>U&U*UAU4qMaѢE#-*PKn2ѱTkub*gmf+Jt$IbQٔ^yqkPk0XlC2RŹb*~EZ49Q!Y |Fq^e/oV0ą*{ k p#~͆$+-bbч#U\ezV$KU-NU_J|9bA2!iqƸĠC7 JG2-v摒#4d]>X.|j0NBF?/c")Q?8*)Ȋɕ2WtH26%]_GրuJgZpfc `!H^/=- sñJy,%HI"4 i/bG,_p3@SgDp7Dg={$¨4DpN:q]qqN'TvP;4a@88N52%WZEdN`;GFYy!/ q~]uWKw3uVʤ&~f&{[H{\N%թnKKu]5KkրqiW']Ԁқpւysq5ݹUϭׁD!] OS|*D iu+Qy:4o*}TC$%vs( 6z_PWmhuh/`k%}eh9{(֚p#Y@.W=L/мΚ2d{mCކKK37,YڞwIUkheHv2Lo:883yW6:>ws[ƶM#$#SIf=ԉZ]- J/,>I! я骤8 J:ym })2["aR3}P]qU^߭#[X5 u 5D\XTghGS9j mTDaSR(f ~}KO!jG |@k % ͆?N@tNևu08ގ6`OYVQgD;,emtR)ETo9YUu(ڙ-hVqѩTd*#ҫ܇o*TK%};ŋ7aP&_J2YW'P+]Ăr*RӠmF0-)X `4`(][ KA~@y(l_]ƬKK\N| %R߀_ im#!LD"W¸U"~C;b1&R xa|bC=?/eR9eƿod=N~]t-Q~{4uY~Ns+Bp@^-F]~Ld[:I+.0t8#]vc-B?3 s9z7rd. P[ݥ.P{CQ Ь4BMpt RFE J N\ \ljJ;!rwB$UVp-:\vC[ Gݮ{`7bn6NN޽(" ir (`9q<?e@ ,\-~N,lc0> { HAcuIssT]CFF'HɉQN= Bjd%˖&M.t8hM7NNtÝnR|:6v&UdAW؆og'vʴh(9ct-E`853句|Y@-Cm\ͩYooj4W'L`]뻩M(0ʤFV ltT8EBz΄pw`'; uv!|6]"4٥c@,@<CNC6s6!VS1gG#,qsheӐipb2a'6s&r9ufb. .csYkV9 .32l_nJ&;s xU6s&(sfuAp)A~\vT$eƺ)ͱX?@_ʰ[.ZgPPTqb Vk+q ^ SI+(;af$ n9@F]*]PAXƮs#TINF"p%]i/7"dm 3 ;]$Ag}To m qף Yl;n[ nv{R|ѻ`'`;8+zBb̏a ^'BH8_ճO_@ʱ [[m@`70 RY"WMR)EWw3zVڡh+L\&AnMN L[a> yuC&Q7pU3-8N=?v-̤}RKucMxq>]u;4G7MK3 WݰpIJ9fv6ܭ:EONMwZo5):`I; KWy6_1I~OJ=Sz s y2alZ>CE/UCe-+kJ9lQ@} w'>+a;>D Q9<ǾؗF|a_w1*ُN,r8g\07c*id|ɢ Y'Ğl/?ȇP!>%hFr+wteŚx~ =|6?/)|*_Ƨu|kL~?;y5G^ /dOGe/ 17RWFA"yL\[E_+nŭ ?S<%~xoM~oEH?s#On]K~DH2'Z!C⭢:[ȭb&6@>b.GlFilrl}}9W4טw :J'_Uful>߃_fCkv >VJd܅\4kׄ1\@#BfojB/YPw!Ykĥ,$]ȇE :х|TT#GN89/芓'ݮ4Yd1Ӆ>Ÿn+@euA)'~t@)x%G5wWw1Z6`4YoWc)FDǡ !O?@pN p~f`*1-a !{i>:НB5;dJBOd ŋfntRNhj26RCsjCDZF(9LRXMƑv84+^C1 0VCqlgc=!z+^O=i6ݑ,-Mh6h5էeڅ9-[ˤz;G=9$ -W-I_e :8] l;a 5Sѡ.8!ϭa:f0]&\f~8ïZʯo|+7@=-,7)*4mp6b; {.~[|';߼nVf##lfGY= l=ھ?}he Evm㯰l#_G;'oٻ-5vwx iJ'uI1T/gI" Δ)tw1Yuw\&{ ;[ڕMb$'Dz_Q9U8ǹ4&}1`fԟL~B;Ojfʝ/536JlF?hA~ǽ=ć4lPs+"yHQϞZ؁p/,5)i5 TO̲-'ivAYݫ!xsH1mAfQzJR8Gaۻ"ۋS{1D@*9DQ%&DQnaHY~E BE7Szy?z2MeKtkm6T).#Əpw]FUܲ HOlEHcYnOh9soԾA7 0; ԆKf2w%DOn.IbW'mRȖӏњXU;NVnECtӁgti'D` ݘw[ 1 {]B3RP֨iz}m=F)4$fdr"ֺsM`r-&B *r}+a^\׮ gL2A:踜5& s-ffƑ9*!$c"wi7\͔9H2σMY%/P $TxZ= 8O wZ.zA1Wd 5aI%H2_ NyC~ !o=&J#(STUwFy<z=f.i(-R$UN7턆Du/yꄫ6jTp.w]zFӵzg7 [DwmJG Je֭`a5ߺ[لɤM4N nW&͝ ;lw݂NU^ uOG@؉R QM3wkAy}.>BS܈A< 8Q ̯q#~<6"p >q~'³h>V| C*%|qe7gO'q}C%L{Z>+ɔ! ߱ߓlY1ulOJF뵁2$p)B2)pR`zPS7]qN${z0)'$ w*BOIP^K ްNCX~qQ$YNIsh_ tŽ *@%@#>~'I7OS؇Q1v؛`og#s~[Ɔ`F2_y7]zU5NR$86YW (;zeϱN,&z0$T4fyys'tЩLgϥGg፨6NrP\ cK`r)LRʕP\ 5uPS.UOV+r3lݿ=q!p;$+xn<> ZmdoKo3 Ϯ1Ua|dB/s|d@`{G9V֗s=9td̐W1v:N8hC!]PT֝^.oyobt5vX&bOlɷvhOHeW! r-8MAvk ]Tj B]tof]usU߷!Wo'b8V<]0Cf+@r4(~b8Sy. rN呄A +@ޗTd0"E/ vumoo Ax\yLk[@=IW0 vnȓW3#lb8} 9r)UqʻpIfy{C@⨈OmpId3nG=k>Jãdffc6߁u&  da6(JQ@_wO_'<.߬mww]"SP+ @({$9^هBd)ϣ{Ty&(M`rm)g'`EՒv?H,8}"g>cJ)F'Dpp?b/"s%dXKzmhag?xjngHr =PpvʌV(rGI={ )υ@Q7TO:*M,8O"D~b6.{`2p:p_{`1O>=O|o0gQ@jLB.x ~8:QHmK+}mƳs/ꦒ`H7`Iw1ә~di]#5KeXP͡"\PB]Id/5ӍW(] L!9t^Ea`"6H39 ۤB)29a)3 yw`n$& ~(Dz%$]Nљ~Y95IҚA#i+)콎`i7lm`+l۹#`ͬ4c/ "R>\*ephrPvi!ps '3А\<+) ZYG v+KĎB}̃P>e3s8+_}noNz (P/?ataZg#yv ȃkhrGr| -^Mx(>Ds ~ƶWWw~7[7,/`ߏ~l/aَn l"O銍&JhUD1w៑PK ::XqȕZWDx beb)6 'to`BE2"I$^uEZ~24Fǩ?OUp"S7oGH7%,$2}7q(L;uLhn]'iT3Bh\'?tmO&ڮ^vM={򵲣Og1*pyǂe^^wT4}t)),hķ>E~yB T99op#\^3Hv 3\q¥dftL"jXb5Sqr /mc occVUV6PKt PKjHFandroid/support/v4/widget/DrawerLayoutCompatApi21$InsetsListener.classSKsA&dC^ d%&^,#V(192v*n.Pzԋ,(c*5/~ +Mft[AZAFC4!B%K siV.C^7HXܨqGe;.Cls+)l/uW;5eۖim#ciONwW6vmUHB,s׶siVofxݖՈGUkSR `b"Ð`mlWϸR^&͏!ZZNC^)osӈ2d/bpWE[*m; wj 4AT4h8>x0Ip2V(0]E|v3 `MARUxFQCo`sͯmI!SB.2PKuOPKjHEandroid/support/v4/widget/SearchViewCompat$SearchViewCompatImpl.classTMsA}$$` !~k4 {POD-RJ -0Iή5e𕐡4{طL(PLaaUaA|P.زxkuj(~\_J4Ari(AD{Hf@%M { 7TRs~cR3za^fX=Ƥ\kyo\s3Y;gH'k ]}x4w [a۶[l{YtES }T؞mu'q1HG5ӥr% 0%ᑒji\!FMFq@phϛ9+X^>PSLS5ҘM:k+moѳ:6o3\<<=ᾤBH+^1a_ ]^3a*F#^CA[5**<[r뺊C- =bun.:1U)dsF`fL4'**eH/׀o_w9 \TB j&L8` LwqM%#k8!W;8@8sڎ QR-™hjn}DjY82h}Bj;Cxj|xkZEi<ՌKCI/*F{9KpylFofN \K{;v J吶h~h-%U)w7p~-O!3l7PKQ5PKzjH3android/support/v4/widget/TextViewCompatJbMr1.classUMOSA=ӾVA)bRh`nھ`^tpΝ7n0q+3(0&N;gܹsܙ˷O\Ǎ>ؘ%L5fƘY-)}[ YWAyRVrUY\˲&e-*pW |UqFٚsUeΪB)w_K\H6*m…nUjŸﳝw&YPЩ;@Lj֣[ @"2~*Rگq誀x&p|EEYja`2c/7B| m2"GM5p'b1D6I.Az z+~3N] ?xvfr| ǴRНn_EPI0u\}:v]W’ |b+ɝGƈ"2N0ВM0rb!<+o/HN3{8/|Gb8xP)CCqcyQKMS[H]Z@yW9sAb.MjRp+RnNhࡨ pM-pB3,(ӪL2HYअSfA䴅vtrbzUbϓINV_pd+IٵO6 97Х/+)]ˡ*j=%( DOm5l1jMc)GhB4=J+Oh-tf,]P:U6Xk0b{" /pD^ig[i IЏ$D JAFPB jA;D!w}UvN ҅4A9: ;@,^C3 k F r.p##'5tuBz6)8SjEd㴑ԧeoHۀ ui:ƿˠpa8,/Lp$Fw"I~PKo!gPKjH.android/support/v4/widget/ListViewCompat.classRnA( t+R 1z1T HMiaԩ ٝqh|(-Xc^s~fv~c6q- nD: {26{=ƑcTd(S5duX0{R~2舏BT{:GҮB83l0^8L᫙%O FjӶuL:2_V$ I%L*i3dI{I?ٓ;z2(JDx%>F} v\p C2y!WDAaD?38Q'q,p`1.+ߕkM<\UbM_GXf 2 \V}j{2Q9{F,JQln*ZXs4\u\B2.SUP:?@++QfW_;KV+6ҫki\''qP PKO_PKjHDandroid/support/v4/widget/SwipeRefreshLayout$OnRefreshListener.class;o>c^vvnFԴ Ff 0FҢT̜TF̂T IJDvFļ҂2̔}Lm*0|2KRRxibFm9yIY% :X,@/2YL ` Hs1԰3pqPK: PKjHMandroid/support/v4/widget/MaterialProgressDrawable$ProgressDrawableSize.classJ1ƿֵ*^<d[OKOw%%&%ɶy|(1{ q` M>>\0A7~@'Vi8yN8[:ъf/$iez'UCclgGj29%*IhXz`4fY;2Z, ry-W(9Dzۏ61FxN-q6<nTLћha;^IFEA=^PKdPKjHCandroid/support/v4/widget/TextViewCompat$JbTextViewCompatImpl.classN@bVA@+ ]%n4,L\LD@Kry(74.|xtΙ3I?>"#Ue]æ-!Z(b ɚtMe [Mkn7ܓjcW֝u)zAn2U^}_smv{d(sԾ7WfX 5ވUQ*C"w S!S:!Su;^C\Juhϼ AӰc RvNu5 IܴҬ+0yRO^ h䔙x 앒UN1`Kdh>QgPU8Av_"4e1lP Rl`PKwwQPKjH0android/support/v4/widget/NestedScrollView.class |8{͆$d9@ (YXHqw]EQq7?I)*~JT8pW7)S-QNEb,2"KdS1 C<WP*W90wySSR1)Fг-FQs48-Ɗq+OsӇzRc2*&PD;jqITLשT$k9AOL:)fWCPΥ41ڪ6][,b\p*NuDz`C,uC?"" Ơ-N˝ZVJq:vhj*XCZ*Πڙ)bh\f*-@E DEn3նPJעv\Qg0QK%uzRTlźb >;8 _CŹ4<*Χ"ȣ{"bbʝ%TL%~.kr 鵞>\IüqACz#jVEkZ}r[\/nC*~D]J4 :ďgf@p/qCwCSDF_4@mo[}H 7DC gi{Cs8Z׾1PD؟AvՒ5 7T/_ZW~Q2Aju=GOvձ W,]2+1ȪԊ &5D"Q:v>mEcy9`h6}͑Ia62^W#N:mҶc :)֫:ᣁz[ ~Xڶ"ccMK F[燢PG EFim:ߎPgtA0݁KZAN\SF[`E@mEl!56t,$iAطqcOCOqkW}[hÓ&~YR[[iJZ-n25 4mEySC+%>l &䦪hpk`Y( kϘyiKK$ZZ_4m,mI^-]f8y1;&>ɐSU]]P;vE5u5.S(^8OYaEc]M2_8]h͚>,ovH'ag%ĦmY5fVɊ%eTA h5w. 5wF)Xo$-C;=^mh{ICX1UHp+ 1eI(ZE''h7rT8pV'`׊O-9d,d`."ӟ*>`$ !T$Ͳc^4|/9;:ΎV$MjwpPUWG'Ɛ5\5LNlk l(bWRQVZmHG}82ӲYioMߚ 鴁ALⒻP2 0AGqLoG'`S']D)lH`^;Pرabx\}å,=YiC,u{A46{S1XjrZ}m8V%wĀR_q1rWX܀=`Giqd[}q(<w*jn w&7h OD)6ً4ܕ;#4R'ɫm΂?BT<f.r6R`#qƇ(NE}QUz zȬj "Ç #$&O߫_zT-?& ''1D3]I0*Xs%}[oc<QaG;X4$j*D\o6 Kj[1cYN1C"~@{54psL32ɧt!`diKK6O/C3k&TȪ =fc.Ȓ%)LI!R!MBa(hW5ͩf|8 uw Z=*腃|M>z៘f0Z"r ;a;CclGtIid2)2^.|`sγ%[IR#~KLx72'W²+؆V6(3D @%- Dɥ3.u`@(#0ko3eHZP2߆k2) ՔorK3Prc.9M- ]L)U\;Np`k0iaj.DcJ[q"M|3ydKx-ŕoaΊV3nu"( qnOdu>Pg+T4a>鬌M46]gT`'l&PQFl>*nuY]D<!.2&SY=[`w?%\l;tm۬VB^CCg[6B].8x'eO"1* *OT{Z<9qDg$]%< ,/%.^DW6j?<6Qcm+ӴJ.NQtJ]rS)[w`(2\"M \g2TEŽ:[t ZDGأxMgO'WxCo~#b0[9t[6 Z;j]+~#Ix>!69ĝxF~ O#]YEo|z@'T|JPgܣt9yVſ& jHLajQEVP9G_P%j`{рI]|%9׺FPΦ@"77EaY LQ #a K8v5uu7)XEWRu%8+Jd)ٺ22@ΆQrt."Lf'!0DMJsDL<3Lw(R Na(b$D_B{T$֕Q2VW)qj ECEoVR-p8் o+%ū+JLP&꼞Xm)Bp$$d:er92%e*:1 s<B'῅fǫte2]Wʕ: :+udą Pj2K3/+Օ >ŊJs,a?D~PHA( U̧b1G igi|Be[ue1iu GxT tJ-s{-{)X ~5﷿n@x/嗡ZRGXO\,ѕ8L9,?u:! ~y''\fM1lp`w ʾ~VGǪl>`d$1,egSI Rt$Ot;60#ĜkKINNSiuby kk٣AOq+c-H9,vBHu鹎l-#9 l~ ÆIļZ$O:d\"P#u~-?3\p`}O]b1;6Y弊๬EsD/oZD񷽈EJ8 i[\ڃm23IVF*KN Egcw-$V[[ dP6Gyķ!BG~}h+S EĴ,1?%NHƒnl uNtH~.}'y#U1;D':Mx80g2:$2`ˋ[tLۀlhCҤMj iXeB:e#4$cq; QII"duħX cQ+vL;'gƄUۃbPD|g 7ny "爌g<tCAPրOɇ#$,_(Ͷ;LyVߎbҌm#lJ<;6YDWO؟/| [vk÷&o<U")nl\$̟~́5D_VѶt<;孩\cf;|tĵF}RqyU5v䦳p#Cx uq:{%&[6qQrګ6DB H+0j3(]q"ZԧP$h(T&) iya_{U:%9D (3i1Êxhᐑ cd,`X>+Fl,cc0bVb}YMc&I9$STt,739|fg>|VyJ>(U/ S>BzŬVN:s\=["K24\7=|s`+<18*l zjX#[#"`k:`r3N8Noi<#GlEF?k69d7d Ai8oa(à6փh+=NzMjӽRcH3>(-{2 E/dRyZv/ Sz4b=<|/~,>`x|IK qI! `(CtHqp%`29mT01@#<gQ Vx ΁W"xUeL,[d$ |6f$9s ryB~~/Hރ_^b-Ƞ&s).w녢{49n1ss$tF&O l!^IZY9Uy"*>>rF'cрCiL)((yj/IrJtv\XI(YDAVVޔTױ`?P6m7&Tv6FaI!`¸dP_HP^*G*VN} Ʀ%nk~9?ccY4P= J7(yj[ŨKhg./e|&2q z؋P*Ζi=s#)tL r9ӂi@F@+LpJpBi2 LD<&Sv>)A1]iN{"pr+< qQzd0Ѷal(h,΃6 Y> 1k§Y&z YcUXp%C9!]5' W<=&BY0)\v0)$gיb#ݣE&)ژ+0GCe#%6v.YPʆ QQ$" me<\$ d,n(n&(F^<U,ZfJY1#w"N"䷓r?VT)]<Y\=桉4Y v a8"^A?/7e$kd+)NSLT7?Nj^('BB0Q8=`Kf뚀"r-n:D5&ɘň͖r!ߠ|1[Fztb t;DLrbp!=p2;3{`y=Y ]I:/y=PE=PM=Pq$FR(ϛmi*XMK1yl?Mjk2JrY`>}0I B‡p'6 C-jhݘeJh[60N@+SApX0k)\vł໾>85x'_?I| [7Њ'A]2+UZ+Y,yƒepvc8TN@}onDzm+5 ~ŰJmDk](B]2M$Mc>MfpQR@܍byEt\zJL$4m'4_X{ú/|Bl+UԵ˒MHUK<&i2\iSTOx9ղ,er[; z2/ ee|;=y_1dC;L\"LUjrĨdh{4H^uB0cE"'.b,b.@{|uF` LDV^Cj1f/^ʣ~ gii [۔u r=媬L3rgqx_BDCڽڙ?9M;c@1O`'`_ !lg,om{4XiqHk,lɯcJ `tA숤i6B9jY/7c8W3@f&2ºa3Xs؋%5Bz/`54ܫ]^h޿jƁ.@x-an/ށLZ6y2^.pr&xo asW\q98x^Js#q I)ƿحu/z#{mN7ZUcm)+aзQF>%14ܱ NIO4<F,(0{` σeBhB^p- ?c|4H5il6M0NV2אD1:Qnԛ(f8(ka+!z,"({W/x_lq7ܰeM+Xܛ-kzHr$o*y bmy%&g$Yqq(N$FD-"y~""M 7uALďriDZ)`6Z^h(@N@֤hz [tTIRqb?-kXerijɤa0RdZ;a0=˝ep 31n82ٽnoz p[K=b@T%Fk/5`ic]%DPKE0*@ShI߀,& oH6wV}؆7B':ssTAu_+>SB0&8 l '[*lHaBgE&"aG e׋\vΎU1!Ʋw8Ͼ-Jx򑢔eLL'|Wx7ir1_'N?y㢚?+E C,Z8!NY^e"_&FbX!&V/ke8]LY4m/q\/}M"ڥz 6]*:]Ʀ($b{X3Dz6k.vKgEހ߰-ǿ5w!Gh #)'/b8 އSY7SE1Á5pD'8gV#5 %0͹$NH"-E^ưckehg:/ zat C-d3GOAG/ mf:?Z=CLwEӫ_LXOp=k+ ITn bF\.4R{2("):!O1l$΁\'΃EBcŖؤr ̑c=}adccpE_-S-#(jAf@yg 3P:֌I AfF(^X'pn8٘4pxj7#=㸝P$vxq)L4qT0_\ UDq54kI\ ~qlC}PqQ0}%'$[U@ ;&T_ DPI<YtN78}|n4+'y`>R` {aaA?|d|L^x48A܂d?ېOnG s=V/q[a1^ba3G-z[p 2SpR.L<xЖ]a:# ٦cT9'srܐO:Qҹp#eOQ =~jd[=V@Zn1}ll1f +-HaM"FT2`-M樖L_ lsC.Aؖoxt)kÇ`- ~$8nLvX˂cb-vv =)WBl#b) 9F$Ce1Nz*6CiRz(ЦX]l*Qak#eF^/dg*R?(@U SXvxO{e&P/4o"aE1R,u 1(w88:˕ꅳ(/O)<ޏ2Rߒ<(f,|t=n|r(8)V~ .Z !IQ4!'tkeڀ,s[ɂTe((Rr`2+P JUFC28آe\Lip@w)3>eZpH\2ZY'^锫L]r՗ypm0?99c)rMdV2O|r8bAw ~KHLZKsZЏ#P 7P)JFҎ#j{Izi"3[̽U] bx1.@("B^X Y䇵0ZY%gzU` k?lTp; \lE #ުP;_b`7Zx#(c*4( h".hT}R~"Ӂ+hMnZEI$>lJ= P+AU"̓\ ylVkpuP\v{NaTk:FFܑfx7Sho'4\ʧG.%]QlgkJ;=}?2'^?%~/yp`K[e4s-f>VR m>ݷQ5ZGhnS6az629VB,bs쮛/77ͦKO{pa(đTe|ug`+)>eaS|I^GTI.Yj#d_ȅR/RP/Cqvk!5BjJ#)'˅=WBJqB}!cWRm5w~R]_/J~-'t*ZiMT-G_ <nYqtR{4oMH@WNtPPr~et'eI5B!uBV -$drE11(a))6ɏS@(wҙ5@͖2!]$;ղ tm( `6&v4gf 2b~j5o-qcxV )eG!bKFe78%J& #6M Ryї.]+Lm;&jaVӴP͂Jmѷ +m&PN1\Hh#}t~ȅT 7UXW~︩rE6d -d -Z$2T[o( "# }_OiuHzpi{qn˭)磒 1n S_OC?q}хCud㧩:*~cK#vO3%z}FQOLjq2@TǺъa>T&h5]q=p!Ek m%N&ik\[ suPiMТ<-N{Mp?L[ ̑58Iw<kzx/l!n5M[fȪ2)2/2.ۭ]EN3hԶ;;u;_SfwЕGj. ٝY|~*>upJe4dPK4"=~PK}jH;android/support/v4/widget/ListPopupWindowCompatKitKat.classR]kA=7M͚~h|H+tTDbC|2;-R%IQΝ=sb7"6QV WJ+]JB3VZ~,O0cd-6*} Vݑ*/bSkTehґtQ eEL$PC:J[+F}˥t .zo $4kL?c1Q&( e:D^:{igS ֯9~6M;ic/@ aJX#ELX!@@/7ru|SawW⺸ t:mקn!qA-ށX5\}'*OΰuF`{*qA֧긃ܯpakmf[5${Q= #e?(FFvmZё"r*0A6Hkjݹ¿wLnw+%'屄)Ic5$ Zr }.(teWŒ77/`9Bg! (b :27Ja 2lBb[& -b~\|4o |Gj{㤅Te\Kl}SQB9ёy,1J1^NWPKPiQPKjH4android/support/v4/widget/SwipeRefreshLayout$4.classSNQ][XV(??U*CBP&i{lw7gO4&V>&&qRAc0v3ٙo&Ɛ##c9,XA!b%.Jd׏ZU&_ޕ7[2W5x*w(Tn=d$^c%VMOe fajx`텞j2/:Pu3] ?J2e2P{>cAPw編EY] >Uk K=3tT* }Wm*ӫV> ;)ù-.hyØfv t+2Xu't0(;pQ`6i5!wM,a0 \ \ר8Q`mfSq~BL[4?"R'?SgG7o 0 =,@<ЊlL =>4>:wD,Y;m-J}H>zDOLWLmÝkBJ^,FI/6޼G 6oX`EXwq/m1O),$ًx@ڢ 1 PKVOPKjHDandroid/support/v4/widget/SlidingPaneLayout$PanelSlideListener.classJ1u׮[- xPsЛP*zͰddj|JL`/0L?^b8 tIZ6K/+iUXr?Z_aˊ0gg$K׵kkOSu2*pqJx绦qmjUs.~pY>ș޳'LR0bu \#7#`H`D"1OG:8@)PfOPKyUPKjH4android/support/v4/widget/SwipeRefreshLayout$2.classSjAI4MhMSX"H1 F/L4;mF63$o!Cg61".wΞ͙YF7<,㦇kU"j ;IŰ*4ZA2cml0 2<6Me,ވc#a}LO)F6CJW*z<:Ϗ"TzCnʼ  9xҴ2\oSk<Ӿ*9fĭԊaYPM\ٿ_4TVXG܊/G"в [I DGnvpJs5t"+a:,⎏磀B - s"}1<ɰXnl ( "VbW&VP%ka\? p$o;cvC; -LJŕԞ=k|k~ҧ.~0y\Dűa9KYn~_Q()Imf8'qh R2q9%H*yX!T%]oT/PKb^A-PKjHFandroid/support/v4/widget/PopupWindowCompat$Api23PopupWindowImpl.classSn@=Ғp 4@ T("@*R߶*5r+_Rx(Ĭc%,yf<:6&n:ᮁ&C |PhKG0,w]_"u].\6 с2TdT< `u|_m ԫ.@J OC>mˁzJ(wCx\m0j[{I6 ϰ$g{tlص6J^Ǐ]>q3RbݎkzCJ,9g~q]Ԑ~Cna ,\@%\pW-\ ^AD% ʉ7(DtӤ@Sʦtw;7*rXXJ,%Pfz) ݪ!w6)8*QQVQITK*W[G%1X 2,sIs#|kj92'}i._22U%yZAy PK.ƞPKjHEandroid/support/v4/widget/ScrollerCompat$ScrollerCompatImplBase.classVASEzYͲwbbI0 (I`k Dfga053KN^x)^,x*C*CuCbګY U۵47V-@3 gm>p9O{ #l4)# 4A`Ѻɪ|ϮfaysخYay/ę 9ӷ3,鹡iʱi[ˆko WF1ܵ# )nF Cxv c8WsFCdJɐCn 3uvӔ/wXB%x>iaФ"4ws>_%ɉUl]s)Xi'\cZYfH]c8iܒشHQĆцd9D#R')JzOд#gR_OCRi㫮ooQCMJe,U],?hcLօeo:u<7̢MkOޛzb G:4w׸8z簖0|X:vW(i fq{XA&^aǏ0t!MND>վ/$].EI5h_]K=$N>4 3 i YeҌts24H$1O8.7א@ѿ;>a 9{Zh &C8I^ C9N㌌xF:='m$1 xSJRq.HÝ8BH%s ^ 0412T%ӛlF~'#?Z>l)DFe$^~ZͨAޖ.ͦH8D!1U)ЯGzNs\sOE"1z}Y",tEBjxujt[D֫y=UѹY(2Z5ta]'+- z4kHvSp݊v\5IV)(4>oW8㧄nČD[}b,r*ԡnC3A7oo]TRF=m~ PR:M^MmزX <n <̖\UͩPzSzA7TMqʊ ׋V]Jʓϻ4(nsVPB5v:m?tל#UЩtܪ\krɈS Y*k@F+[v +ƶwA0s~QGYu4T5p w&+Q5MiGrͰ [,.XVHKAחӗSA} ˸r!URvׯ]eM-i\x;x.4F%xsnj3}e$WơG>sԧ*Pq4ZY#?cd`.z M7$'jS$F+FhcZ4zu h)ON2e/jz#ˣ=3 I+C{` LnϞ@ɜ}ό1A;ar`c( rQ-=˸Bd]5Č7iпec{6t3ߣ \?) I L(:oָ =GvnI`,l,QZ \#{Ik DB>Mރ9C\woؤ[? PoM)h3S +?SL96'M| +* $Ov# ӵHO~X\m"-6= r[!CbwfrI)LLJ?5i80FumkQeE꾘ϔ~Kl2>9r '%C1z}=|d KPK:PKjH?android/support/v4/widget/DrawerLayout$ViewDragCallback$1.classR]KA=cV'nW֨m(KTOv8v]?KhiO '9{Ϲ'u<"PzRcTkD.%_ ށ#$2alX~]^& 2ؑ!,-UڢJJta{o&SC0PZe-i~VDD#DF}t?0l[Ts&Ӵ83Wkڹ۞8Q}(ӳ8s%[Ïnǭs:ɴ90 YA TTO餇 kM#:-▿+M7gVb.,#MSؠh˿!w2O4=Әnay,aPK/Ϭ7PKjH/android/support/v4/widget/CircleImageView.classX{TW~d27n`֚@i,!f`0Ap$7, Zq֥U&hm+C|fɹ;;~9?kцAe"琯EE#g-V1\se{ٹ*>G/Kr6+̾$/U\ ^bU__ߔbԷd2;*SI=QE2#ߛ  0?6鋝Ec lP3Q=U2DU jN#<769G{"'TўHxOxԦfh2ߧ֋f63`_T$Y36>vy[ia=iY3c)t&ýX *ft}]KsF~D?J}4SzY%|B&؉4p2W>q,#c#9R;!)5'ff狓&}/rl1}̊ cPPȦݗwSF`gߤ`;;`]KNjz1=gd%[bhi+Vqd#*~3B)O0"@ora qҧu ,ix?`h5?Rc ?OU\ #8BAѫA T\/#ٲE/54Q[ 7; :{ UPGqJixO|43-P٭#-ЯTw Bo:-!/J\.KRa b b5،]#<@zw&݉=.SJz_-8_Ikz*l96rv* owmmNM]n֫kQCkޅC5l}65G8Nt3<5*>bXx!qnǏfy,ko)pU7]`|nrJ9EM5<Vܮ\-s,~̌fGYN82.ĠUD%QÖi+Ae6fu’q >r/.ݲ2Y%~qe(2pdvPj;i =r5N֝Uw$4IlAa bl@1-L;LvI,PK5i>PKsjH;android/support/v4/widget/SearchViewCompatHoneycomb$1.classmoA zZ>PPy=MhRFPrl`c7&/~(,4 wݙ?~؆fĵEq47,,ܴpsH %B %}'£Q#E5t~fKМ|E"bxr t^8c׮K18F8NU1û\V(ՠI-R׽NCd {?\A)&-ǔҖ+fLE3 xB?wM○wM2񁉜YqemΣPlT[q<٪e]_JscQ:toH9+mWU[y ׮ؤ@f7_o^L;neM0PkjULwmY! Ԋ@,׬() uHyQDYz54@k#ka,h(/Wyyc{i4y~iRיhyA'yt՜lt=n]g_9#0|` ݪ[iWY(wJ\*j2N+gsّ6^6)ci {pٸ{6q__q'm,#dcK&>U*MHФ;NKNwwGKDd|NePÚ*;Wd0)rtC烋пEމOo֬}ϟЗM!RڝQvix6r;#pVkӛ'<CD"@r}༎'|lAwIhi-3OѷقIk)QS7?R4a-b+^ہ#a;3?ʧۿI_}k-Ju_T#&+%jQb n0A96͆r`&!ʓ]A̟饱qi'01B@܅8 i0tk&.R&%i*xsPKd4tJPKjHAandroid/support/v4/widget/ScrollerCompat$ScrollerCompatImpl.classn16I%QhZJ)0 e#UJQa:NpqI)Ƃg&ВAx1Ϝ_xU<C]8ɽ<j-i󄛁j k4>ۿS%bnԘ{eMۥVso]urɧ<܌ť>nHĹw{pKm%.Ît27f3e;?14h:>~p 2#òw,/]-jx:&HJ.-Q.l̄}du҅= *fw{*~'J|љ8!JK|:4 _*&iJ+Ek2Nipۋ1d<_p~Qa?qЍ:'y9y0/O1$/Or˷\8Ӎgp>;s|z^ n4; ]^xQK2^W 4MMtBɤظg](QSI51&:Bڠp3L_(Dd=҃i5G@H':AwHBFZ"1J-Vpݩ\0_iU+ꣁuz,=@pDgx\OZFhFՔŰ=IJD3w߳n`ȏ^(Vܬ`=6Pmao:E]@k)l ,#5~eƹJr 6M^ŠkZw75W")}nW>p+!Sv<4(&h j$; //TM0m.D!f ef{3tüHP-7I15Wʓp9@0q,*^?v2qn(G~_RA9f'qE$*ZBTz?d<@x!a,#NãQ=N$<+$84S*[ ^ŤtLq!bX ,B.$d5&Q)p@^FL+dWs(/(4`E]Fq"J&&*Nʁmͨ҈׈Tbi01vWYl1a&7v @%ve1|6XHeɑc-NJ9$8٤tKf@s1vNWZ j,EUG0{ea>n,#~ &pN"k~dXټZK \/e*Jh'%ǿPC(C`dH.K i,l$lO)z0Nitl0F qpn[,L0jygT+'EuBܪ%n&QsD9ֈ [RM&^^^NçE>S# T1|K*vZZώK\˩.ˮ,.|Ԫ,./qo!u>'8i+M8e#I,9H6 X8q^NYW i$}``.C.cĞx ^^_(ε@kz]MVߠ7Z'upSAKl$mQ.6Jl2ęV>tm!&Jq1vF\eQ X&B7ݟ-T} d"O߈OY馝]X"\ !|YiPsNͭ(IAbZVQ4 3"[(1nĸ-[ͭ-R[I[YSd%MʩU7s^1vUS>ۉ^29nj -1?S/>/XmW͘!*;m" 4vh H1,5If e4mzni, 1]4n,iZؗ!8YV O8J%N|Hp7Br>\]"s\Ϗb ;]K06"87OpnEp&ztofT&8%;Q Ig?PKݹM PKjH=android/support/v4/widget/MaterialProgressDrawable$Ring.classY{`wݗ\ޘ ! yH$$Dr9G@V[[KmQ>j}T4bj}UۊZTk[ |w9H7;;;3sE݅xҁ\i~-%_1yɳLnz -s39 x^b;'^sd?13Wjm/5 o.coO-&o;<.x߅JṣLJڄL<̹Ⱦp33L Y&EH1Lƹ!;(pGhOB`B/zq__ȟ͵Z_(43> *Agw[b@pO@oF/y ÁFŔ&3HgOvCL#Mўt%}P,JfH.XIe-DsDbXkZwmV̈́,scAd11M.%l#ç:%f%2u}k h2QhH{UP/)|<1]0aȏ4 ^CPrO9 r5 D."[cuxjК f?Zb?OVm[\5 Uk|/6 aby~Hy(ۣݳ]p5"hEn*`Q{ʛXdG/͗5O t39R d_?h D{J Oпu_,|CLZJ7C՗=~M57¶ltN#VImhnn %B|HBZLa1] aSaV%+y%[L[`beqQZuFJjCarR`JW OdTv|6/h Z!;=X̑"ϩrQ GLjaT@Ȑ1 s`ְd20/n`YiIpDB&*$̈rFDkE##F rS)SeJq/8kK,2$x= 0Y(Hf(¡*ap/tHJ-m>+02ъ5fhCLaәl`&&ة#"4b.e(1SE..*Cula+IFEc0Stwb!|Z,Gq7C, b.],^Av!rq.V09uLꙬ$Q u$3?>]OFqYm3YX%Ztq )E7ɳ2IECK;[:\޽mƥ;cm$gtSrHj<&ɣXvp ˦ȌLNN,!vq(2Tkh_dZWla-iyM4Y`\vA}nFkrhav_բGv7PE`w!&WoFT,)z,i]SF*zߛƺtά-31{| zGe.s䏩(*m tň Q2^nyM00<gTY=G& \+uy뾶Dwy53tQfpb _^ҩO_7;rLo== ۵ROƷlOWnT&Rݔ-UMٞ}V-rR-mPS~jò#Q5m]OgSXi8߻ aX!82l='ף!)Bǘ!y+>09k #S(!.o&(&6o*Axs)WFxŋX&^Bx*&>DćGp8$0Hn;ň؁ $Rv!fO@|5]vCgtu|oٮ!LQYv]sK t'u̝GF|ڀXKf4p͛y/)8LݲJJl%ޥv0r^nfx+7w?"|tWDRfy'J2=~7SZ^n|bơ\j5Z)& m*zi8KJ+Je.=Hp9.TW|RPC8;"Ӗ"yr\4MהBwiȣô9W9j`JJC>Ka[o U4 gj@R:3/"948^;%!fi#Qj&y5Yb/PNH{h83=z-RiK& gl8 hsPy1KyB,תѮ-BV2DܦZjxRkZ V~J۱ I).'Cya#e4q,Ze̲$ \Ad'usY浤Pbkصӱ[nþWRt'b?J´m$囎Q1tC,U̺so^wb?;e=UR2\n%N/0"iȶ!OR4܈S\*SXN8QQoKn6fYg[޷hU>I^YC9 Ie)ԍܤ>ԥP]p΃-!]ߕUi8׬ssN3%#re8]ʡMr:tC3]\.^vFI*HeTbty/IhCystK-oCn3CC~Sܹ=l8snfhYސ{skV Ώ^rZoELH+=73D2 QGt=AT{Z:H@ Ʈj詵YXCOʹ= /j+rrLA(SA2YgEQ*Cg?ď G4Ñw8#?j&2ѻg йT>& W2FQ*OsEH,E8g(CejDr70EB|w y(%)70GLq+0Kv[VrK>M9bAŊ1&XYK }J4gI~LNf*cb*?,:l|,(be J-Ejdf\j97ECI#Ը 5? tQ%:Dt`3*ՕejgQ*uPK"c} mPKjH=android/support/v4/widget/DrawerLayout$ViewDragCallback.classXkt\U̝'f$mbiGCZRH) T&N37iS_ mkh1*A_Eq-]|e[>Ν!LYYǙg?K}FA Z~ @yC<|{">"|LC< [:ۏY>^G^1v<I={_>/% 5< :fnF*5a$*i3۟2r93 *&r۳Ƭ)Pi3if dֶL:]t0S3{%ӻMtژH j0fΔ,XؙrrR|swXiUnї[v<V3d'Ԙxm왂kiy!6Y@23ǎr{췖,ܬʚY0A[Xb72L &fiCj%)%TRAm3;HKF1ld<@t9:q)ʥIj7]qWnNb e}? sp 2@б3 <02q!>Opx$/ ]]Xc͚0RdNwӄzbIQz&RvLYWyO,GLF#XV.B֓BPϚ)?K^{&1ŊB-.&I ';DN\b;$qץEIN'Hp6X3hqC:=0YG bwY눝v4JE123*޿6%]\cfֱ|-b$f.׼q=--%o~f0鈌3ل9`k.t%>wOҥ}TZަ `N\Wz _ѱ _5Up}YK$pQônx5E7~l{4 E,3R&JWP*p 9f\+\ ҚlHuFxW{!%¾00Rt]+uKڞ"B8*]TžJףSP};+j#r ) ZPǡPOxP ZOJWu;nl;$iBuC)\녢5 X҅1/% jqa;IԬtKt6FWh2:5/ݗG=qєt=WXy8G3> k؊WF;Vj@OtiBoh(to#_+*v{Ѝ:YPFi^Wxtf$PS^Ah0_4L$O-K$gS1G:ϠnGGq]R F(26W b8.A߆P\C+>M-{cˆםC%/屼:BB%Ba4RlDE 9=O~ɖzzSGPߦG\!t I>N-4lR%B%Z\y$+UjMGbٺ˴GNx1O2CyZSOd)&QDE M[Dn0ErGD 60AI0RMP܉;arvfA"{%TDqPR+D2u>m5y(b[G}eV PYs&1wWqUX~/%3CC?LgdQ1mS 8ؿuu|&KيVM!Wc@.NX"7ze3SC7Y\5<ʇ&ߏ;m.UBymjцMbDvpF%â&qZl@!uPf `JsʇIz܃ILBSMPrXz FjU2NH@/R=e2o&"}Ll$b#K*{u˵Ϡ'A-ݍ)T92s.21/:(70M0nB|b/Ęt,TOnMXmurf]Gob0U^7tU$ryk,!z|lމvb?ф*~u.߭.w=Kv* :>;:L"/d!m|! r=089Oud 3SμQbQG;G*l6Dү/NwS-mZgPK7ó 0PKjHFandroid/support/v4/widget/NestedScrollView$AccessibilityDelegate.classV[SG݆en%FX\B r%vFvtwf~7T\MT!I%RyH9!! rwY IPt9}g~ @3>e q |8$DLB /@x[̎;ic81Bb\&8.t'0ĻI1;%&Ĕq`3-`['mYi3)ѦZrLsL)/a xp]sLFҘ<212m,ݱM=ΤӶEg[M}𢃆pܱ18mՐ ҆svR+]qϴ-p>,Hv34&JqC9X:O 0ad\%.qLʰkc`h^2VݟxMJ}RHCM4G4]7n=%ՈK_tۚBi"](hgt@tIMŤMٶ:9Øئ]ֿ-;7.4%񩊻ӷ5=$I4ש8pI<)q pqsOruE#POMb#hX=눷bAy;Z gQ4Y+n&m)8Zqys+WyqJfPAy#Cj$^[2"h l؂ **|-b8On*K(Wװ_G@36~={:R-K^_t_t`/.FPUI~GPoC8*V1P"p΁+B3xJ!/ffPT%=2^!AO?":)g?G7@FnG`yy؇7%9:Չ.J/pNF.=k4)X 9oCPK^ PKjH.android/support/v4/widget/ViewDragHelper.class{ |Edr!$GI`$ >dLLLBpQu"ʮ" sޕCWuޫLX}JիwW{82Q105&aasw=A' =d=KS愃d9{^f=bJK^uAЛE/WmNQkjqw{{q;Ѳ;^a15OSG35毴o3g_/sj_Ҿ};wFqbC!fn6mE'+l <۩IrpOIjR4|OGeQ㢦/5A9&x>a3^Q3BjCa uEDs75Ŵ`$5h8Z5K8:j#x<>iDj&Q3^3LtjJi8ԔQSNV4xv̡sGM|>A YHKI? c45%v^cPwb;_bK|/+ _e 3}Z_zNo8hE(`ՔbY+ %ձ`~p4 B-iyK+eίΫ)_ȀyBoYeF禚3e KgΛE̚.TARy٬ ++j$ /ΚTͧAϨKSJ1*.,Y㭚w쪅UjJ+ k3i2sRő.d3JHϢA#[G|)X쫯bDZjC&鍥`k`~$~dm7}X0|EEe(J_4fJӬr} *_Y^zFSoD5]]OSfѲȆ0-YEj5ͨEf*}as$/i 6t{>kO#*T09$_\ٓ(wIaums$wwE["ͱ%0V_53M~Y6.FفPSyH|i=kii ƅP $-GmkzniiM/A2v] }$֒eHՁ ,l }kCȾJ[ms@j߆3{ fF9*ƵLT6_h9Hc5Ti6:[Ayr4-Je5+c3lhSwQAqc%3oM)XC5Ѩ5gs7#[kW[:/y|HAV~IJ -_Vh EQdkht%>@$`~:K'aE=\kZӄg6Cʌ{QI1|=<3!FpeokH!ZE"A١fhc$ATA+1tI R0 Uxk4҂`%@!Oy c@"G/2A T5*JTK¬9-anoo+Bn֢n7Y{'_|3_*ue~D )E06 D8։S8l5mh'82wT|VKed^_n V X⌅6klCffCЂk#*FUzf+ * *"a_S'dա~ՅP,jk Xhŧ|mr**T+Q#i} 4+"ː+@lxa ߵ vWҁdLF"͔>4uY'D rH6=ւm%pH ^D?dKN&k tɼLJάA~7M5[ĴVT3CGi*/b`N4?Gkި$H,Di%Qf" fSlBK-h9sJn!Piω6DZB~LW(I|#Z]Xv(V́^Ri#.ژd>-L> ŨFdFk92goB*wMdlް?fp4H-&}AzLz7[1D.u9@Q}^`~#~a钋 K0Q (lnteLU Ng$D^Cr v'BSGy9 9**s\@zݳM VH^D$(~%I׆.-!$%SzD5&Erdgr00ҋe`#YzMa퐁S_J':OC6L[B$7H7W!3s#$ɵ!9#-AJqtMFSΠ&{N:s' Y_'1=Y8N`pשz_V@ } ‘X@A%Ð~qG> ^G'u_!hSNxIfw&lmkzH=:=x  ޅ<[P݋3ZP$ÉMf_c%X hm !XC+G bSo"'/voNd1 a~eKR06||5ix9ɆN A:g޲HK,#+@K"qYF‰q[|+ɷQst56l:YdN~-?9D6ɯGw:l.F~αf~ rjoNf۝d0M͝j{| /bd8|^ 5RN~77yN~V ~ਝ?㇜A?Laz\;XRX*^^z9Q5P('VsOOP$)j8 Ňf6|5658젓Ei/ '{59m r?cvҺ [G cl/2nb7;x <"/WH^';|F.[X) CDf٭jɼ Z# 㭣3p#NYV-.] @g0bVO|MT/4EhA09*ƚGx8ֱ;j: ytcgcd\{ϔLa;l|ongI`T/pPr*Y9g/*`L:sa(ݽ.A녁 x 2%ܒY</M!ƹ$& J.Ss, T7 |kqƖ 򆝵G|~kQ[}:CluXr{:&m/uR1|Ru=1Ys!H5CK<lmLчl|Nܼ$5UuJD31_0?B%Hxf]5gr 3N nEz/Uhϲ=uoPɲeew2rj;̛*UUovo׮M]{#U4"To4-eάF$h+(".AtLNgFTiؿЬ Id͈bF*4_S$^JGjlx#}Oiso#I.H0 `Ҟ7FcGj#M^l˔2dOwFS].E ʛyoKN鱱̐NM3+٘9G-ʦ7˥!U~)up̱#KdP^BV/A =0}p?7or8hC qPaNqGG=?0~Ǐ& xvޓ 㧐ل/p|"a|E]ϫL/տz տ~Lm8%_]տcIU9Ƌ-?O%9H+$* % q@c;vHWlm@X C.%3|3:k#7jaз9 }T繩@8,^6wZ LB&ؤ?_S|'lj( GIG1$<2G!QQ*#@{H/>Eqdv@Ð5Awgu}" l;%P!?7 @Z$yb A!X <2.Rve[`9j6hdWv5\j_ /ߒ1[$EHsC(NC!3ഁR14%xes"YZr,t3Al>6&әMU"OȱÐw>!q 16$W7*rTt G@E4pƜ"zE$77Pi0ҽǠpQ9t ,BFH,>nwaT̼(vԭ`^d fo9MB=%T_I W[biґN.%?q`>rέzݜf?~\MM? 'и7;;؟ U_hC:ٗǾJxQzbT1zk(*tcHM=00vُbj#H|Dƻ.Bvҏdqy\Bnbnxn/w<,qdT<ra돐+\,!U /Ruw$QWb*dj^s=%vGwIOv]9@ a$faK2g Գ xT ΂\6cآB|#LBe8KN"޲ G'ܹy`Nxv_`pd &4qČI ф2PNCPF 2(:x= Lr]cl~e ]]vM]ˎqzKwhΕ:.L8W^H@&x% χ5e!x"RVe:M -.T4LʀRw`1+BHnjb.{Nō#dGӏD(Ir) iGI~C&NExŶgt{bɪ? i:K:R-Z%tꒈGk<bOKC˾8͕-ɚ7w|y:zGa'MTWe4Jݖ\JۖnN!x0_X Ck` y|-LPPva)& V.b%HKAa +1RA++1"lD94sr@%xszٹp $R`i*464)A N pRgSHxӉ,D4, tTl!EymsmcP1ҷlQn$<%o732ggyʻ\l7%\9gvT{_z *F3(x+ m(h|LB~9W@*ф|MVMF$)"l2daӤ` .eœTha3Il&B`zqh0qAǏ \L-37D, ^%b Rsw؁N6d;!M >xE VH*IPD }lI R9BnBnh!JkCnbM!0[c("8`nCF݃!ZE PDDp("xJ"8<`O/lBp>>]Y} wdc LR`p!H,\Xybvo~u0&2 W@3E|tA@:l/Ƨ'`&f_$keUJ^6_(e -9X5Q&{sK82شpJ&_eM~SF0t.[|d;5.}iʕPd' ~H)"_!9p.iyWˇ|mx k0NkaVYf[yT,UѨQH`%h0l0\قveBk4X0S&3@Џ<-8T'! 1(Ghj0P6U*Ԇ$n~dU/Ev lcꝥG'!G7vFӵA/IՈ\=ݻ{ֳmTdy1\yg#JV۳m]x k?+c7\kIgz8{Yz1%D3{.\˹ t2mLޡvvșlQWblKrJNھƓJ$Rq[=ix'ݕq:JO9>q̓i>a+͕]Y0ݕR\=DX7CmSY.NJ^;M%mNJTn-Ml c9(j9q'ѫ> 5ڳ\&<'^; iåK#eE{ h}sfw =8>khϵ1O0SVfj`^ퟬZ[}۠}Ͷh߰mivX{Dؓ` ;;%C>NH\KD'dя|K\'_#0y(mbD#fY2 Xd\աK/mKa|Yz@ ,#Y8"ԫ814{4=Tb`9B(!Tc<]P&mercU)xW,NY{6;6SĮߝ&c]~*Y:2Sf Oz)_V#ZTo^Imٔ-nUvqkT2p^A=}z7){4c.c\=DئQ|tBMˠ& ΉraB2\fFvvp0]:#? ^wEq/'Qy˪M%lv,/.b7(Z^$Q>F'>G<|xH< :e(Cal6W ]HK7ӧvG]bst"˯pĻhAa-f+>y⏰L j_~䗂f*Vu{hc7&|ѿt#==3ypYRGs!jmD`( RL %Q t$k(e]w捜bSL̓vA:tҕ;gsX*7\,K7V Φ(&[U2;YL'.Y!9AcS_ַoc+ tIP~#-ftҏB0`ȐuC[K߆iQ_D7FоC{wz2觧[OzTPz= B .ӳ~uŮ˭/K*_A!%l7B/r!Ֆ:6Q;[pfv!te/\Ur碉جOP $R>qC^ }H%V)}YEHSl~`jA&+Q k+%%6۝7buo$ve0!4p`~}]-ֻq]vދ=Uҏ蘣3W@> ^0SJ}.AD-|Z_ &bn*̚V6\%%a¿QJZY'5BBji"pܓQat\ɾ)=uW<נ}((Z_:kq#o~[7:LUT=P̱ޚJPgyP-^Nyn!ڧ)GuŎfpSYr K vWX R0LA9r0>Mt'*C:ړ<6{ {쨽Ip3"L ?}YE%._iⲹ츠&!r'I{-EI.GS! UJHӷ:\yP_a~-jX_>Р߀Vo~;рJq=FG~Ub[U{`3=RSALV e5D62n?/*g)W)E*&wQ&[&(/0dv#PW, bYNN>?èryPG*jp ->hByOTXR-vs|61t9Dz~y@m9OCХį?niԟ!0[?sp@ C0ߥ[-ބ\l9gү06 |REBLBo.{ 9_lgε[s]4tkmjΎWGau7Z+A`,zY,Rp!$yJ_֧OR/Uߘƻsey{wt0ܝy'{1ػD;s@k/+a?H3Br|qh\ !w? 'Xorg!G~XD2I{~$ҟ}PK]KJH,+TPKjH0android/support/v4/widget/AutoScrollHelper.classX `TWϝI<.y !yM!@)24CB#L.a<%R޶<\|+xn%SsY{oPZm7:-6u3lf[lM'rVMb*ءU.iXLO4GɤT *: %⑈5ȦX8LX,nmk Z{vom$ %SX+Iv UML!@^K+vmwZo@kG / Aν^Ek{nΎ@S GWIg4ӆ`?7&CCDqxUpjlJv=2'&!`Rzb( 9֏43p5a #h]hֳ9 `l13 n:CSnaȣp45'hSҠ=>dtx(:l0eHNG" =TJqfޟܭ'75c!=3D['<:nkNVPufowLM2.XYi+9S3B훏sx045c,-YԾwޖ% -AjTG)}j)ٟ pLڥ&=cLCDq3sae{bJG'kqt5)JME(1 z cƬzHp89LtE0B˰Y5*hOJ ̭t"!ߡC Ģ /<rH90cCAalsZlx_ LlQڢ MKCU45#)2BP Bz2YbrrVedyV+'\ߐ"+z8xmV~WF\_V/ܕչ+k]7wln*#93 YӉ-oݬquܼGFiJjtӨBx8 zRкwNS60*V (M!n!UthS.[{4F5$7i0SAn܌rs}{=a*,1n΋`{UѥnO%UѣfEj6qF_DEw`(O/CY^4z0 !tUĀ8&كe^&""&bHEBIDTŰ&d'Eg ^AM ҺN)n 'SzOi23ȓjLF|-NG!< '/;b *h^ξEjcI񫄫8fAX<`G(sCU_f0{U臰}Lc I;ܟ:(Ǝzx`ʨ&gervJSۧ1nw-9~L5!f .'S1"YKy>w.Hګ#^d@P {ƙ+Kɂ~yWJ5>6CхZ ks`X .DM:N54P^ uaܝ3.xߤoo֜mߞ3 c'{wDx>b}ܜ2fw@)O1W vUбA=*讜|㻥9rދ&A?A~"q C"|O"ich}_.챹wݻƨzJ1F3F3'&-#%*'ogvã4gSw&OEOTn, n}vNߣ3^)$2{ $u/"M@9C6@ٱQs4 ~}ҢSeifZfN:sM4=djBQL9>chs<+ӊլƖj{TӄғđK,%%1tx:>NKGa ( &(-a[Ma.XV)@q>G˒碿4N+oyCG`3nU!p8M qS9I?G{} 6F}qZdVv0ڜ_-^`րtƃp OQΚig~TYS&8g)?G yZ!^5eߦ;tXJw,~,O;h-x7⋠8k0:C_YGi !gˬyTT#N[|gy8'1Լlk7qSj֝SxSiTd^o"~;s*F^BM _BM{`]XDwR'pۤ5M w0qӸGOHhVjt gd1?w6%3  ~5r4GԀPܬ(]Q]qdӯ7[  a|gīz݃^uĨWΎG-WZJ(T̥Ze>-PjiRGQYH-Yi}21\+lP엎WeFcoA$5'C=BB4(Aa3 P.jA}[BXM;0RQ. *=+쟧d"0%z|#LsanнuN y { uxݏ|({Ï^Hi,y9yVѷF\TiG~ZE9@7)ԭGX0ߒ`, V[V4>T} P=2.ԗߕЗ!_G6UBCpBt:9dL[ 薿MPw櫸.K{٬*wsm\R> rjuFퟢmv FYIB0b6E7BcN{w.ԥ| gOB"$} Ix8敖PUĢ|+RAl,Kʇp?L*R>:Z~1yIJe4Z(:hj! FkjEAh$Aėә]` LMEIw'aiSFuթV4cKǐiGI5Q̶ׂ8EP~՞y0{ZoZA wYѠ}&IN.kw鱰nlHCflcEa1]rkP ,u:'N3zKIN6yjO?bvn2:l (ӶJʃBf"{b@? Kxv.S,ZYו&fln ը([vbBo1`LP^Rcbۚߓ0گ~>2xLRrKRg=Cslf 4`%9EH{8Tup9-h6;ZXpzEqX."}Uʪ\zu6[.'Flԧbj|208w\gx&vb.vq*q( XV'p7cKz0zw7rxhx*0ޥc mp|]iGamǑir) x ߫h̃X+͓Ͳ; 0{1ǜo sü2rC`mU߿V ٠ 9?荘^67!q6nR6帕vCv.| w܃8e1\ward3ځCL}e*W'G!J7 ~?_0C3YCV^aNjr vO8rTzy9%+rnP)Uuy$@%/b"ư K#/,u׸7!xC SEJ,?tKр )/ |l$Q{"K[TV,rUܭY£ 0*s[y{G5{a$G*+OM E5QD5j~ 72fE1ߔ%YK"d3eŇ\8$6r=Brq(هɭ@-C Mu\By<Liji!J0ޡ$ u35㫥M'alaUX i(ua.V76U´ T7A^ S]B=P+RyZ^պr@߮˸ɺ"U@,VLLeyAy[ׂ[]O⃽tɁRL3.&|:K?SjIe1O\KcϜo[))sA{utt Kdϐq_g3iruynsTiޮ(AixZXpcNvvvF̜< AL'+,Q?'1/]?)+5:J3+8(9-3'Aԥ(<'23 GA+1/(?3E DD<3%=D]#El L b1I&6PK[T/PKjHMandroid/support/v4/widget/CompoundButtonCompat$BaseCompoundButtonCompat.class]s@+~jBDmP-Heڙvz lڿWvφ#f`g=$;5L!)WÊ jXU# 0$6M˔ClTݴG>othgn7y;Z1yd [e۳=Ii[j%^,;uyj92\׳if-ȞG.1̸BMKMRKMے’#\ΞR(JI!FUx8hRWQcUR: kZ;7m̦kRSg&)6 _x,r r+6~cڳ=):xO.kxHcA"踋ea?5=ô:pm|jY:H ̨0M&b[]8YJF>KLЏC+fYF6 M"; \Q;` d*jʻzȟY`b^L+ nbꒀAV g~xdņU)f{%zQㆈ0B~&1ٽ}f.z ELS}o7irO_PK>jPK jHandroid/support/v4/database/PKjH5android/support/v4/database/DatabaseUtilsCompat.classT]S@=ҖZ QEI (ZaCG}q`t?Bc}G9ޤEӓݳޛ篯m(%1$nᶂ$cϹ884Ŝ]m ќ[sjalbը oW-beL]0_vs̚lI}׸Uz<\s.% c\ #\ysv]Hϴ^Fݨ2 V$7lrzka CЫSܫ^J Ϭ!%Hn$tYNW{ٲ$CS8B0㟗!m` } &:W t1".b C$󑔢UZHkۯ#E Q}| ML-bKT2BVV#َ0q#*Q;",=4oPKԱPK jHandroid/support/v4/view/PKjH@android/support/v4/view/ViewCompat$EclairMr1ViewCompatImpl.classRn@=7qm(Ej⑊@@,#:ȱN +*|:T*’}sι??x*;je.B$7PP~ȆGRGgh EJUBXBzI7"CHݞ})K8t{#xSbBM%q͉Ԅ:LHqLXMd=VeZ~>о{'4gȾQwvF㘰r`˱qG(FէC="FE{.b 66/O'@~%yטl_.6i~9-X+/Y<Ķ;;%NZ3[2;+ٖYo;9y Pw&oyǘviZOQZVOFp3gU ,5uPK_GPKjH2android/support/v4/view/LayoutInflaterCompat.classR@ۖMSU?DEB FJq,v+'!$i[ |$q<be`Lo{r;̉Hb<  q7qȘŸi13"D9 }u׮yi,;USvfy: )EFSu]=9Kg}]P]]m<"ӂjUۨnZO亡O:|/3,ÛghWtaΆ&Y[SͲ|#[l*ZTtuoY9m'պ*].C%\ǰcYKΓZWEi +fX )gZ[ƶi%vEٮP3pyeIY+RJOV(u ;%M#/)" a!m}N=Ԋsr^A/#'4FXDM!?@8Wey+U\#P#ȁ_c!+>={S:D>}Po*bd!D$h B<-(P9,5` 71BQ?/tOJ*1? I3QqI/I/Ec􇹃{PKZd֫PKjH<android/support/v4/view/MenuItemCompat$MenuVersionImpl.classN@ƿAlA \M`8p n-y|(㶐H#U{_wof|{pBQ`"]ٵ[yc_ccaݹYl_)G*I%Pi'H W=k $I-JSMfl aU[~o#C>&4% Uf2.u49Bt{Kn'f N;b[v8w$>܎/s#R M cV1h>ͼ(тhhnS7|pt޲۱ kf 7f.o%۵ \!Uah|^UE2ٰ{4 j6M7u7Lta" tПC\aK HwՆ]]#l:r-p.&9Z}Yeth%k%;ٝ->IR&6e;ar_2 W#w/U|2`6!as װ1ʋNSgÒe4 Yj1 y5 AY ~poY 1_~>'p&] ʷ*CZPNt39c `!PK٣sϹAYA/2v6ɾW}^'~&ti0QjrroQ8#1)Dʩ- |}Ԧjx;O{'d( #11IM'[jze-ǀK|(R0r]ޑ4 {DBK/+n*U~ 1#~2q6š8S8-9f8 ~%''p{D Q!#tᅄ Z|H > A9D)Zӓ-hqO~'?9\U_{ު|(q>'Is? axFHJ:PKp* PKjHDandroid/support/v4/view/ViewCompat$ResolvedLayoutDirectionMode.classJ1kk7&*{OwIIɮ<>8{q/bf&3OW8000g*szԆFsM[U\Z g zEsН.gF#oR6dFlT<8"}qF>ПM_!¡Et!}^2Z ([ʋ35qU%ͼgW'vOR=k-tdoGL{uñ\w {q&PKe2#PKyjHgandroid/support/v4/view/AccessibilityDelegateCompatJellyBean$AccessibilityDelegateBridgeJellyBean.classR]OA=Gkk" F1>[y@cCL|v.e0.?E.Hݚ&<ܓ{5,1_+BeXqOaH}?NH{o\ kIA' -&~01\_'aX)c5aXnߣgtm"gk~?λ yK6;-#]C޵I >6l bDK>Nx3.hZ(,MlC,1>n@[8_Iq?O X$׊м-jX)t[0fY5-ŝ#P3 [``@&18BE<ʰts/q-2AZ0f[BqGO ZحR=A,O$Ky۪+R5QJ۷,mh7Ey "m4$ #k&|ٴ%q3Vymۜ>vÿ́eF^tؕLdohֺ8Yf)ԽFUL )?ȜԶ4)R]|+h(2X| tPL=_  p_ PpYl*̈~WyAjRo7 {a}]ˋKpm򾒛 @Fa>& X)Ζ)n":WuΣu礳b;#>L^p1/C1(6a=ovu' //I8He/ c"HMxg1V!L6L8!}4T bO?f {D{|#I".C㌏pkFoMbbo(qyؒ;Q 2SBSO!OϤc;Q{`dW8֦#Ȅ:2 rE$t8@v#jK⣑ۖTCPE:TUw N*4: bSdK*%fy|T(!JHyđ&0U›g}QH:;؟bGw (yW!k6dbtǍzS4GqotDS87ŻhF.߁fR&u=m[{F.Lmh>T4-()PkSt,x`0#]RCzg"x]ȏ9^)-s vpW}KEq3 LaWҏmLR$LRX$>2Ye>",xv1˸`PK"# PKjH)android/support/v4/view/ViewPager$1.classRMo@}/'ơo -miFTP"Rrᴱd+g7/₄@8A(H3;͌w}]4]Tp";pQƦl3T搡0: +}t2 >YGK{˄ ^O)w#$>Wae'tc3)^rHw: N'STgĤ^i;pz4g4ˤQRlvc>~6<"#9RܤvR##nt|LKԔ{8}ӿH8=CD:jDx&vZpgxR kazSHXiw+luKO'!ʭO`)(JG;!Lycb[9w mݜLaㄕ+h .STh< }b[~6Go p1c]켂N~lr@ ӽPKU|PKjH2android/support/v4/view/ViewPager$SavedState.classVWE6feyRiHRڗBQEP^@=Iai؍ j}ݿC8(dIlfgw_? 9w0Eb|*`AsY ^Wtk\!TTw|x=XTᒬ]A^fz.0G͛n TK !TJcXiXv{-#Jfg rU.KO A*JjmuXd7搫@ᴑ7S'kۥrhNrzr4n01řOLImȆ@ /w2$D,+l0kWp PrH=p5ʄ"ob[eG9m c0GGT5/& *V"ȊsQddj*V (?@C4\ $ ;05\ I3e@T錄Ywtө֣a+(ipPVaQix/p'xS s _K _k CxR{+F÷N0F|/!q?H8 Ͱm ~p #Ծ.ϬSREIΛTm(v!Jج~vOdΎ%G!9VڪgW#rf[:#tu֥^sgZkNp]WW{봺;qE/;"ԫZP zdrӆ Rɡ#15cf^(c:)kHj5<\+2t1ix o4tm Az9ӫ9@tѭ#4Wc>!Ӝ+GI2E2=_NFK18h]8AB\ zc r:zo (D| =mq@վvJ/nmGs?yP$\;D!4EZRphӼ_jTjUڱ LGLX -߇?OTе9G7w/🢯KetʫӸ,᪼nvnQ<`I~S ^TBƌqXh{ C!~DjtR/N/-̫0 a PK1lq PKsjH<android/support/v4/view/VelocityTrackerCompatHoneycomb.classQKKBAvM2EF]tEDFAr6w:MA~@?*V ⢁wHFHB/@E9!V[mVO{ui^³΀zCY9mW5 U d5yOBM.[SW[[eE8(R-/׊Uf!MpwkmfEnȵyUxnD(r&#oxBczX7.!j^!ִfiIZKo]ʼf'ga E,/VoPK$EoePKjH'android/support/v4/view/ViewPager.class}w|UE3r_K! ^`DB Bx$/Ƀ$/%4]u %ֵ (b{Y+׵|ϙ{_I̝;̙̹ϧ0J/qkR0%;]<3G 1Tr*6']`Z(yg(yVtAivyJ^EJ^_ zhPUJ^_ߠ ޤ-J6w.诽G]ڇ.#cSLܥ}}IoSߔQڣTD{J~E[ .~~J~5.(#<)E ]0JKhBP"BpS8(q %nzaR% 4J"%IHvBBTjFZSAI&rD*fSるE!z:! zQM%)CI_JQҟ@S2%vK C)Fu)AHz%QHɑN1FEMq7hb(r:1S!Df.|U31(sr8hp!2qOQA鴌 8S3Y!vs\/#|9q!uL(w1 %\JeD6W^4U.@ltb⠞(w5^\KfJz!noʛ͔k-4̭4mT^Aɝ)KCCCm&T6hۨvJ7r?%;P;)yGcXOP]ORŔGOg jK|%ߊ.KDQ(_(UL|Y7~jr6>с|+%|:H$u⨽4钆D*zE:)q!&  JhlJHJ)IBd2 TLC4I2Qd̤$NplA s]E C $a($ e I|Ttr5QR@`)=DJ61脂%JIR#n%]YH}"&qj2L74D z!ǺFzs9A``P8 5L 7͡&fiCCiJ?X_ji 4Z4 `5Ep8,Xl^S  \s+UsUA*CM=f5UqZCM,n h7CMTy4suY s'iW;J !@( lX6)P34%n74׵(HNb euΛ;uɔ)ӧ"SKU,UR2wʜSg.]4m\l^JE ̝ZQQ6uI9Ef+Q^^:sIq܊S.)5gIIYi1 ;Fƞ6b%sKOWI8uTm̟Z6kJi"|6-5bIQEW[?uuc9Ps7dʬEѬ9`ݰ#>s5 &EqƗBaPS󰕣DjH_&å Fc΢T/COT8,i$:oEMM5`Jw\&e1;0ķTJzǕ+ +d"ܗPCqT]UIS~A* e5N;#K GC`QJXax0bVÝᏌ E%CNFjo ԇVf(giFYIo`S:\3" Ҍă#eB~]tpJ]NY:@JmCEIeyz4kGo޹D#cj_ʿoPE+*3aThWWh .ZQ&:*. iqnM*46* ,JhMѸ.PjV<WUuYX.o:i=@P{ȄZP4hcV@g n?ԣ@>)j?;k3CsјՕF[:y5! T=yg K]q6D XC4ha3Ee4D[Snn1pzzlZz\CRr$WMk紦ʐS3' Tڰb!%LNt KB`? bjQE^*eBWe@PԵ0Sjo,*V_2DjȬTGG"ckh*xkUA0ųKLꆃՔU۽<HА4xB33 ? q4W汍 AYG$nlF‚ kđԚpH+ci3C%trz!bFaa@]t`KjCB4ˠZxuX :uNuVA҄%ʖ^pT]2%ar(JUC4Rog 1XwVÑWѝ.3ٞkqtbKE!צPK,mƬ8éSݐq]CҘz!*TΡUe\aU4!wcDX7)dTk!z",;R_wVf7v10(E kQiNǟ Q?-3FJ 5G>*B3`pjQЯJTvb:j-WuDnA-$5ٮj EBuVwfu PU8kt!zRUDpW{*NI"2挴jDX=4" ̨/[5#ywV®PU5}dŌ4Dݲ- ظ"@V*W7Ą8156[u,F e:Z*Cb(GԺЪȪxxQO׸-%,t0liJDt)ruS}`1=#.UB^ ")ݐ?q@]oݫ; :~Q6Q+h  X\i9w=Ց$Dˑ @ oVT :u;o7,06CNM}6]lWЗ6GC? &s*I t66VC-56PcC{AMGNlمmC1OeՐ a;, :=)5Nʺ sHG ?iEcM:L8\f >#WYOjpJFIs8GdQ44k4(1ξtU. qmBPb0d_d/L>ejn^ 7Si75L,{٪[4L ^5ku}>2ǔ|I/~ĄKvuv/<(U0ym>}61ɔeRYNr@tcIk`{ YdcCN2\Yay/rG Ȕq&+&x>d{Y{8;H89L.0aP(7r <'%\*\&+ MY%5զ2HK=Hd ܋*jyƺ` +<۷Lͬ:lb[DmiCImsWN4"AÑ}~Yb*Ɂlc >iWMGh^G_T܃Uvݮ݁BDL]bq!JBSrQ⟼:%+Mh-_{LD+ߗs"2?" DdP)%Q9뉘hݐ_+5uƧ7)o"Nߐj%DGr )_~ż`U-(Oݯ=StRPij QAvmʟ)EjȽO7tSNңWB^Cv:Թ·ԺFh#Mmvɾt{Ўc ~M~fԝTӁ&eia=ГL=YG8xSOLսShK!,5L@Ϣ`z.jK Waw-@z}>8p`PyfHQ07jP6k'O}sZ@>Aպ4ڙ^`ꃩ1FFѸΛ0z!Թ:o?LzaROy[j/ϐG(}bS?RW1.ӦS? >4)[ L$>64Ƶu4>ԋZJ6a؀#HcנIa#? {t2)Ȟ8dp q$G'C˲sNctvuudy0Wg\.=ηư|YXQ|ߕ2+18ȁQEji@jG<tjQ ;^% !FTh7.Y7:wn@C[5E2kȕGe{h!/ȍ`>]94H6vW|t?(v;]BB[ZO]6ujx\eN2 {ZhGJ(%&1uaE_]\VuN^h:Է>ItFYF0!k*jҶDCc%}<}q*q̃ 6 @?&tV}MՍѐn59EO׎PLK?8uqq$: 128>r\؄h!.P>:En$?N].ka6_<ؖSxFմypXa1ϲ5TJV\]їZfX05* CŃ)eE!7Tv1} K9bPBu HАnE}9z]uOWP ]ЊHW /4xbKK'S?$,0#zu;d X?VGY2I;"tCp;Ƴ plEY@E4Tyow㔆]'Pr$.vٟ p43}"%Sb1a/fVLwW9N79.;qV%mZo刢Tt;S*0)VDRmrԯSS)޿"ЩƨlSVtN 6Iu6&S;/FB~"bjFR(E*5TE4b1*([[]E!ႉ$: (loƩRGI`bqMQ]0xA3CXc𧞇R^\ ]n +B,zNvCAULq'nrK+椫ݒ: ʑ>X9daMyv_L+K 9 qG[e s ݴAl*|R0T$R #Ao Yө6Jx;9T!J|SZg-#?| ӧF ,MV43p5cFiII3F@W)PXăz E6Z ȵǹT},l,Yi8GGv>VB|*DĊb׃q4NTrjZKOe%Xbsڠ.xkKbόu݅eIunS"wwsMaoa/+EPL$V\GOI&k?fL}Y5;8'.-O{ŐNk0[P֒Iq $ͮ3z@,?|_rkay{\~,?Wށaq凱=b=`18Bn$S((Z eϩ EzK)nH=_aku|Q7q]|ev!H=?>_/_oGOO3Ek?~\P_ʰo0 FbQjFqB_ʑG=Uqj;.agl=|.lyo6| Ȉp& ]i# ^AP̟MOAlK89P 0u\ .nZle'{ClδFSkz# G^[@&jBRhmR_6'8c@d!5L, d1R~X'  'R'XDژi`)=pcDUMʷB!fL`}0 fñ8kx631YA}F3X>/"ld_uH-?-<2`?; /-~DՈDHGQ& $> `?7 p,J~pҌaH7ɘ4^|>\{ACzWv~Cوe[yۡJNĽބR+kֱu iILLC/n@?X?L|*/3!Oq! Ɇ1 }.EeϰF*H,ש|ce?^fwh3N4 \J>g`8ӣ,r4%(X([$"j/X;R= 㳣d[gZՖ#ﲯ62^GK/W.lV uYEQXNGv@M٦Bjۢ^}#*m* $PK^շAc~!hH瓐&#wNFip$x^ Ӑ׎EYˡ \KKk$0Y3W"5]s*=8RԀɼZ^imh ע}3R *90_8J!ogt=FKN^]JLth7L7L7͒%YQYf˳dkau;p;Q9';ۓ\r$"h{$0{.'FUJ}Hne) J[tQNDLщ1|aDNA(c( GI 0?@P^8v (ڨ\ݖ12IP 1x#NX3SL:ldqUJI2lq"-{;\M.\`7jpnH"|8F⹱F CP<(|E_A_ʗQ%M(o  j03TEQ^E|%1»_oN.pm,bxC=q]A/~,ew6Kࠝnox,@vJ{LΙ/P;v! ":4^|{任Mėo< M$_]ZL%+{F{hD×!fS `Z]:?HB6Dc%TiKz˞}}ZIrkuq άynqsEי/C4qgGuXukjmh~-|"hwمLB$,-Pr5dRa0޹Q]jmъcX^QUhxB^&4ȕ͈2>Ye:NYW2M]\߀&׵e˗1s H/̥Vm˷C{ma7Ga1 pd\~YØČDsYvs=^O2_/TG;iAtX^ÎG|ˆ0kv2ӡX;΄YH;h@@;VhAv>.S L"X] jEepv9lҮ;+AjxDЮ7ѤJ뙮Vna[OMm};bԵW5WͯEJ}O\"myKR!$9~^t5%,ߢ0ůW ɷbJ^:a\o 8 s&"P).O$2nԁ$~#hx"GPAͼ敝, 2Q>sYCd{ f淵C9WCBw=$RnH;Sy𐻡WG E0@@Hfh۲(WՊ˔qbˍ7oF߁8qCJCxxmxIZt+U DV|=Fߢ:F󲖥+N:-_F["dWlR5epPs-p8ãńVy͐r'0MI[Om|h4q=njOB g/spj/ت 7jxX{ރg?wOcSZӾ_o} {~`ڏ_6H~fGb7Vcӵ9Esbp&M8GLB4 HP.D,K0!xSg ː$ÝT>D4QMn]oan16LT$E^,J/%E+ޙV`'vxz cTώ5vs"VhS2_O/|fXOgddY廕o"ܭ,omx{+E, hBSB&kD Ok@_fS`G2iLq=(48 s% Hn%L+T$Y&y sNVgv2~ |f|$߆u&;΃s4sm/dv4/*wA1VrMEJ@CÙz,L?\H HKF4(v(EyA|ˑΦ mHHOe$E_2;v2&d>rruP +3텲hr1һzs6 !q4S2%wͰSUBP~ف挛=V3/T~9 )*_ȿGb9;+yqFAhD# @3^F{/yf7Ӈ ({hƬVW##C](PvIb4F#G l4,]0 ƜwOG~Z3a{dҲ5PPֺCv&AL>mZubex }Rn뼎Y#bx޹Gۈ:7lxnzd5/m8η΍0˙&vŽY [,xoT5MnoUBr]K D>U6͹6Lcx@>lնb+DHBYM)8,'bѦQΡssvDspe"X,wS]ȋDT2ii-`:sȾB3 n?7ͭ^kـp96 1ؿ chd)^D20PCkP(^0^M-ނx8_ `n)>sx۽)OW~?,A|;V gCh_V*~aůlՋ}ۭ)5vb)MRgK'{^>&/K&d{I<)^(SD3d:_(32,/|[ݲ7o}`2`6rxw06OC;8n#ӭv0Ica% QШ:>:%ѳ^1Lvv4`w*$ *'R Ϋ3(\6D` f#y tQS9JSm (ddQ}NoI'߃G.X7o5Ϗ?}>K{)VQE֯B9.D;؇>~XN>\Y -Aq|28R7'r1Hy$!]DȗK>{$%/W/H~7 2 Eۣ=dB=;r Kx1>>"B*P5P7j1.IJ`"W@Ye ! O eɖ޾Yly.<(z \|qut_ȇ Z>98h. ۠F(Bb#xȡodM7rCgvpȵO@ ya<Jp< ʿ]f..n YIʽB$Te {?E>a(%ZW ;]䥸A*#"w>:9%@ a#!w+U֕FШGŇ͔v!G h3#P7~Άu}rM9x?o..k'7KW6%Q0G񏣬'@ O}?!l,^xɖ%@݈{m"bylC+QߝіGh#s6mw EUk;S>3tsELLExNyG1 ?i9817UmKn,bPWKߪ u0Є:panYtk}I'C9>r)~S VB3B%xͤ["wm^ЌtDAZ7i#Od^2H}3dP U8&ӃE&R-+L&{=/Lx(zڙFs&yu\CKQoT#Chy+S:g}^m_[{V&w0vƝT& >|50⻏MqOL G"iROA|I Q"_R:̕oqro)mX/߁ ]LWNcxV~/OU_;+L~ _ÏM~Ǥ9,EȲϬ_h;Jc%r?;Fq`պd'Rw{YzȮԓz]z:{Jd7EKem'W )S oؽx `. )[| jRXſ\*+@ *`1N(chҹU7Yؕ0j΁9V;bM[G-_%Rٓ'bq7QvU!J%>2h(?Ab\ QMY4 0R18?@Wȶoh|EdT4bnbsdUD6]C.JO~SùQۇ\-5syȘA49i8TwdHÎA uU+tӦvnKc.wỉx6=z c)KWüVdbe@ҔQMC^GWE-/>qL !0V 30[L uҏ5ؠ[qp>'$ucPf@MS5r4Z UtZetuu^>͠lLau=ۡ9=ۡ=ۡ=KYLQ#I|/TwkV)PY>r+rW2.B7BSG+L }շ>o" դ$K>SB5Y+L SI#1=FJr޴ԢXK7.,0ԓP iz Uu^']a^뮲nzoChJn3[*LyuQOr<Uy\4O*UV56&3RԍIAfMM^3s][ Y&jF [|$BerVΡBeJVRM+}Tg]ܛ>+i<-E| ө7adV *oQJ.@]UUJfx3QaMzq9tu1O% mo!A!w5͌2ӡt}*z ӐCO3r 90U dyp> F_'d}1lяU_t?l+)=5G̡3So`z#KObzӛ}%fEv~ Og Y?as&V2~~rOF3$دh ?(-E̫Y2R4iGXҎ0 R. D74a#4L8%Px 0[n) +A6p9A12GK‡1l;Fh)\ҝKSNeW:Q)|*3L>VP'e*7L?>TR6ʦhʦ{i9dSS>MTbŅƏ ̉ "^3I(!J&iuq`{4}OSfBr78i=ߚ HՐzgTUS9f1$`Ǔz`M%-͐Cz7EJ|UK!cٶ= {!K}WoC=p~?L}D{ꏂ_h jR&3*M &bWx re][h(3Qױ,#u墛nG%K)z&iq:(o?T!݋z=dEzP"Mhg!_ї]wB㻊ߥ.{c%DrHcN*pzhBv smE?{ n8lÀ \f&Ä[h3a#viWtxȆ/\ ?`t7K0Lkg>Vf(r|UёpR .TW+^,3b`jq/PÉ*-PiDD)%u*?OVVY}sAwk?hPYyWty)\x׾tQbB _\Zv}z[qٸ}'\ 2H1P @ i"0Xӌ0ۨz6A kXoKUp5 Xwq i1N L9(xe~ 4 %a]W~2` y .@yúOM]pe'v+WmHp~{7(uu=i}} X+g?B&ʵTWύ"XwǿRsדQ N)X rBǘ$x *mg}նZu``^}0olIj+ E(e?'b?;݋$ӎ -x.o Yd8uB9b H ;0h R܁Z`[m3I\ #gLU w;/^ #3gۡm Ι/Ge لzʧeg3緳!ݗ!ۘb102jwdPHP #NN†9EhE|Y+ 5P7'5ԡpr-N .4Fca0 TmgGi=zcI l)e mSQ7*lڔyb)/#7^,zo>[li|Ď2>aOYo|*X#;4~fofc2~g~Grh=MG ƾpʲEvbZ@X(^T hB6#(>R_ic5imBC>He t&2=kT*!)ҕģW*Sxw4bFq;7I}~bUQPGV9$3-V?߀&&ϥ|z>$coe;7med ^"Mfb+ A fT#C =ÁqLzC7lGyl]ԑDcx"ILA/b'(I=Y%GcB(оs /uΛ[Jj0]uӴfv~dxB Aa s-ʔTX^RQBlj صWU\>Iȃi8Ix$ 9Q(" F(  *>Tlj}UzŶkko 5{C|_̞={f55kINըF>G ע0]d7De^(e/8F~-9wȁK9Cr$^pLkr,^񞜀Od%>㔼DH9Y)"[^*i"9] rWr\Wɹ:9O$rS^' :y!Fy'gqH$ʥ☼0-⤼U\&Yʐ+yNXru\mXqYkU{ar5A&ʍ֥r5Wo-[r[r7STˆ /!5 s,68'S)C1 }B*{5Z{sfD4"oH #QL10hoF8ߴEwrPXU E&ip1!F[8YMH?r!JOFIԂ<1պD3 $ˁ9c0aHHP&18%+Z͆܉#'ݕb,:n*ϪK4ǜO#%f0FP5oD|,,{ǵص̰u;IVS7Qe.| NU !lBߜ vs @ܸI+Tm4cy^,nlTS ;T%mem FYw(ehpq*f$G bK.ˆt֍]WҷLg揨AUekTβάK>I#2ӈgϢd݊i-'.ܧ)OHp9&/0G~/X*J+l}QS~?$E ސf|HJŸT@:Ijиv\l4`vb4IހrvL%},+ĸّ MbBq$o6dI@`PJuhyF{K\ټȹX,"^Ch:t>$.=D(p@\^ @_ C݅u7=44kQ֣ڀj#ƪM4N|$&]þ.JSBU!o][aTF3yE7k+NQ'u۝`A+ل1I˿GE@Cd=[5y`( 15PD`Un䪇QPA_Hc(*cD>T'pz~QϠu{oDTdDT $@ɤcrq"➁A5:{7G%Cqy)!Xa uBeS4wCndxѥ*)A /dŧ+>OT>! .fkb+RSQ{ oSDļwDC#[uyOW##+.o@=Kr"όdE֗hZ_i9ګW IS ij kLQoaz;#lS^}=C< 8~_OĻ3"_՟}noON'pC ᆍx^6 {L C І0o cj]Yz @eQeLWI64E9)ĀVԩs zWM+/;$J͎_tlgixe\"+Q^_3#q- bi?̃=5LɢiK|A>D|Ojl"{E۸/zE jJKt~ӑۤ ]aN˥e!Q̊qط"%rqѴuuAn& f?N&u*[]89P v;SZ쯈=H~k$z SVrL@Wdn硇钔XSc߳ fjr $Vgsoyd?c◱re!Uf{%s_MPD7ʜH x! 91y0sQ }i[Rsy ,_rH\UEHUNSXACur tCPF]ꋩnlk'n&/K}M6Kѻv-?Lu,sH=fG?l5n;Xo:Z`=_nCbY-eM9moGVW w A#=kQb+zحAoSz~oHO_n@nQ|O'EB?%XT?'fO}o%e8le}}\kc7ݾ׈C!^kN2e"/Y@>=DsL< 8fSx^Ge7(dfwcgLY63ŷ :4c|21Oh JG{tX"-9fm.~w:Ҹmk/.!}@b9= V0Oi@g#ӓIXϛZrZ9;'!ZW5te2&46/nљN < }}i%dꗉC럣>"}W Z~I$5'0Q NeM oa~;>30#&3F.3I@ZiҸK)9,16{sVRiѿ%b;dV;iK..^G v']~Li>A%*$\žߵK= ŬaeVi戻0=u+K=>mB9(C4)}n8/Im,_;#-QLܳdrB/tY`{8+āS4_c7bxhߑh}+L͸ bDIN:;m{ /%Ak(Qlfs3&s Bv ҈Ψ;=is&#J@ol(x8A^E7Ǎ yGK&ڲ}7'D˰جN{h'L'Q'Nw:/9]Rdv)^& HzJ8rw8R>s@gzGzXGLH5cB׭/\IJ !VgDB9V<`Ń aH[O^Őf"μ`RB>ɕ1[ Iml>N/ !|9\,B|88 t)C3Ý)lUtZgO=2(Yۻ (D`J1^;* ^qV+6m{Y|ߠʘO/>'GDۑ#vN Ք4Jw g?a*҆PFJ )CJpڻf))N؟YOCYJ7n PK7&h4PKjH(android/support/v4/view/ViewCompat.class\y|mɒ7˱s`;8!Yc%e$ʼnl! !(7$B(Uh zR=J~Ι>?73yof͛Y}7?EY`h' ed}%^A9«!Bx _#C=›o!»@#Ÿ_>>@##| §!B79_"|5A8`!PPP`E!"eF#A0 a1 "p"reݲԲfY qe$1]  0=Act68,LlL&م*V9ᰶCUHLNz"0%'U⮰H( JƵhbƬQkdfP2z1u|isjVibD*w0,q9[5@a(<ѐZK;g?-Iz\1P7NLMrsPY?gZҔS,y፲'Ϟ>{2}VVoRڕ3e 1l/R2'6d !%3^k=N浭aW^a^FCV@"D֪jƷ+Ig,DAs!q7;[m*Ōޟg9y5]RR|c@&5044MG\ѭ&D6Eb*&/DxhF5WS4mLJX –>6q^&x>Υ&Zd5K-FF Qjӥ-p.biqVc aN }Kj\%hĢ)1R$ƘJ(ft4z(|:E(3fe?ƫE؛ 9e]lr%3^){0Fsx*KI_&3Gc`"ߑKMDDJޗC| T"$+D!u%*Q;uHT:h9h:Fc8V&c)tD0{f t-z,0@" ,E V.t% N,m@pFiD=|~Zu:e>mħd|: TaB!T|LH4BPjzK IP.3=,؉ 띇/ ^& ҽ0j$z+Mwѻ%z/O?Qp$#IQDqXp>%ѧQg6KdYOdyC|5]R@sїE}lZr?.(!!Eoۨ~.xi-1A9ry7mMg?L', pNa SL1gwd6 Nž6 Fs.]=6g3O5]6' A 1'#I M(jr//?P:0?9Y"yküO&yWy)?e_Y rVncnI*ى7xBL(ww4<6M`lQ sbFDs%J:J7|SZ*qM/a,nA U\8:2϶qU@J] }ESYLe:vD YM\ɰ,P,qF4")Qz?N[5,% [ 7pM>J'*Tft_I:}L7"ayk&L$G0`k;5:7F/ղ}lHs.|^e8.d50S^=t98eXsSǪ3" ؞>>Fup!K~9 a@Hx,C(!uµBXQ@8z!" pp1K8 C^H%\!+q4p_ B**QiHJ'sU:I V0FE*dUqU:Ep<6D ' ۸ ,.,bh`[&eeY\py' 褡ˣ[R7UFP&JJHfV*m[x*yêtTzHRX=wUJ W%Msab\(ka"8ǫxr Vvh`Ww4AbyoXk{[>6-P˪ `* F/a⪵.XD'2jGY1>:I8w@+_Ee,]l!% XSySSbp-Kg2\phu01csv 'Pƿ^(XRD`XDPߣr1V%z9BFVsA]S:RW*ԟ}Z#ىk^ǪS3o!9.ƹIeoqUFdԩQsי b#KMDu mDVӦU{JzyQIo4!*M=>Lo!%6k9dnu de:$=}rGNĸ.zZC=4H%Lg fӝ>},ӝƊ{}s?nECpA2{Ӭ0,EP7 I*=\BJq7A /$e O |^ 脏q &n ;qs1Y; _-"< `XjA铺؎?3sL$Q櫏:),'7s D!Cc=v8tJ5)-SAst!Ŋ%%]g 1 Tcc~rڲIA|$pTEIk*F~mETTm EgTT :]k*u}GuTEgL]TSQwuт .:Dښ . suшdd΃ B>*I ^IWI=du/gGVX&.5z-^Gj[o$;77K묷[wgww ߢ'q b =^PKu6AwPKjH\android/support/v4/view/ViewPropertyAnimatorCompat$JBMr2ViewPropertyAnimatorCompatImpl.classS[KA&f5MzVKdD@!!J:1C;Yf7>)>Q5& 9ef{{w}`<#},X"vV.!SZoӒnJS4CLͱ*Vkvk"i="1bڑH($ZK[ EK& ݲFE&Aw+F_h FeoTu"mdB$nG#wMetԏF5+__'L `η sXBh[' %ju5gM)4]Ao "2'Y\%"m׵@9uGp̸+xQJ.>zdMCOp 02g\󘱀w PKvdKPKjH-android/support/v4/view/ViewGroupCompat.classVSU]$,%jh *Z?m!|-Zqܡ[ݘݠ~88/P:}]qso8_s7a1(aI 7ĐCJ i1dİX# E?r \U-d^A(U?&{T glxIDz̲Sַ6$,u0`-k ;r`Dab1QW-?zj.jn#H$卙jz6tca{yêv:G0dszb:Y SI.Ur٩xMQh ZsҴM*ChXN3t'M[^H)VިB>I<1zߪrr4],r5 ez;sn7 qNk&ul 23!m*u"Z\˽㙎-l&=ޜy҂g[}]N+<(mh]K;Nŕ tIn.`KZE+W1l1/έtY\TlB:dѪW\b8Q|3edjMnH;ƶFT<'UCSW*&1PcZ ~u 1$NX6G YՓ~:!~_vBp!4˰7L/m̏ *}fTISz=9 _zk; ?JZ#C0Έl i|43JF+MhHZ3%<%ǥp^w6v}cWr 4 u~L~MY&ȺdH׎)r?J꾚u/E" rNXL{hk C[U\AQ-Bl骼fHނz}#cyǙVlb`clp.x,݃ ھ|,PA$t%>%nC!("%4)2EHt Q0CB S,3}cH_gHOYr T.Qb#/J%G^1=$HZUʥkjH3 1fHk4EEX[wӈ!`0"4-a| 1[Φ8 5z3)=wtw {jcΣ =<3FPÌoX|+xh7PKvJ]PKjH5android/support/v4/view/ViewPager$PagerObserver.classR]OA=w[v jb bI\ /_JL@>e'0̒o2G,+H0a}s9g3?})y*xࡍE]t@5Bg(th"(2I7b_M?SZ% +G %an|5=K+YF{b2F:,vzKJC𷵖f0q, y-҃eH(֩P$2t%𲈱35BM͞|8fΞQEf-K>"9B͋eɎL! DT;3yͦ5YsΕë[ w1W|Ơ }3kV h ڳj o(=\/ƵǬFFȒz)"f7Yng1ZqvژzƨPKLZPKjH\android/support/v4/view/AccessibilityDelegateCompat$AccessibilityDelegateJellyBeanImpl.classVmOP~6VV s PP&3I0觻:]¿?A$&cpI#*vƿ a6gyti=::ߓ0}<U=/{"?c$ @Dkkq<㌊_:-խEu<P UAFp;*(cԒZ}Rgȫe( e1Vj:Eiڥ}`e[sd/)@CAFh&oORc~~#~3,S%) kAH: t r[PKHK-PK}jH4android/support/v4/view/ViewParentCompatKitKat.classJ@QcjkD4)P&]JLB|(q;7>-dP2jyX!䎔1!%dZAOmˋ#kxځ+Tr3b^[(P=;ai{x`|\H܇B+}.!auQ0-R(?ڡ381pIjhC7uwoQhrt}WQk]cRAj#zjaYO[+q:Tx.a%1⸁v(,CK˞  (!71Ts7C .)%rWHBI`MVPUe#-k ]MeTVimS ڊo.(tp.7Byqni\\:7Y:;3 ݲ^Q }BtIʥq㴦k[ ClPEMFmKN,QQ+{74eWQ]؜(lRLԤmobSFC9򳋜4:Hps5FpbB~;{[ Fs61*^S6BU kb /놥ٚ*lӸzGaȽ`/NS)J6t􅘓CӼn 0pBE,z4je7PK!ڗ(m@n3,*«nCLb0s-$}Ve E:F:RZP)Q,x'qF7l-C6B7EP>eЄ:4fQGH}׋2[e-rTUd NX콧B0+2#EM7Ix*nh1K$k?S؃r8^KS4iIێ-jMEe ; :qLfk0l`do*V5 SSU 7Es5}ՏDd :)bF &n/)XLWKqve(Euglv=:e{:k 0C"V9@@U%Ee8h=H7o!J;7,{E쎔>D%Taڥy /ҙ<̈oaG v@H}$wy Bufȿ(=[uH7 uD< $vc|s!(=9xOI3w<܄pq8NH[YH-|I&) Mӆbjxz'NJmJ6C>A0xW 蜛}$W]z@KWCLbĦYb_ aChʸgeNvTB,>ў?L}Nx-HbשQќq5/߾]yn`J4&G"?ƺ}E덡/V̯DϯHv?G#";$Oz FXGv[}bH`p2zr~;8M12ǻSnu g}#}1vz1vHX8.!‰9܄\dw3"*<}Pԅz߃]P:QtvqŌg1Z$E AD(DZ@dylG(`53-`L~i`0R?0ηXPK(G0iPKjH?android/support/v4/view/ViewPager$MyAccessibilityDelegate.classV[sEzwI% aٰ@@ ,BD6oLv;3l Z>ॴ|вT,вX 像>Xeyzv@T*ݧO}O?wCގѰGdsT1 8!pR#SN#%mg4(8amByAb\АkrIٛrT0zyԷwܰcfr^b~bԜ7r&æmzGvFW?͠$`X7nb\1keø1i580*æs (Y29#=eMZF$q,"5;ErL2o%Nc*KMFAA;%CnX%0&B eq M&+猢 *.2X6Jt){aoVAg[$֢pk4㙎TvV#XRpsn2g ;qbGZ-݌8iʜ_[OݗyCVteOc')5CŬ :99uK u8(*{lأCYǼ ak 1 ->)Gh m2\/ jNxFm7Ӟk9:U]G%Uhh2oiq,Cj3JU nt%q R[MrG/J]:Edr(Ъ}CsvE/Dz>h(zԷ?J:14@D%)@ ac;;TRAk.ԉAl=^7 ]?k0VW&`]!ل}6;Nlh^7q)6Ve] O!tw=H60b7P }Ka񕭯+4ɴ8KZB5K4b0'SݴN~N~{K BdlRv>6'nEYc 5+*ؤ,ZLgb]>JOOa$cK_dm)_W+3g\Z0'-Н\Y]Rm5hJAz"~{FH#PKX PKjH9android/support/v4/view/ViewCompat$ScrollIndicators.classAK1_Z۵kk[o=CyEPb )id+5(1Lxy/|9|~X"84Œ!Mc zIWD+.F;Vѣ 'WÕo\^pz`B5(Y|0_fS9BY*coyyGy(ԥ,3SJdS%ꚂcYowT82Izlj= 0{)a^g$PK0mPKyjHDandroid/support/v4/view/AccessibilityDelegateCompatJellyBean$1.class]SFߵ;AIS74)-IBGjJLՙN{!Kкl^3tEQHNLiju9g={dϿF(fQ5eTT@5KyG,j+]Þlj =O4-M`^VuVl` rtnK׻szW]=W;m? Đo_a!Ug8Wwv06\IӰ Wp0o{ UdtdR;>ҡ}!izR*|KG143L?!tc°Oo.~ڧ}iζdX>^Z8{ 9jG'p_g}:.?p2l bv%N㪒l(H6Vu#6895qՕ]aqq‰vVvmY4}!cWu0&=q,pVpgЎ&(T( R<3%Ǵ'S Z9\p krxOM|aW@Qm0ng0_0ONW2d_.7ka4>:|vˬG ?m<*ɼ:غ1WSg.}>b(}macc*WKLq!A+įtjdJx*$]dQ l>I|q7`߳ wsݠL[?M%q[6s I|؏}& ew:.2pTEB&Rt#=}dRpgx!t|E")I~ng? _Sf(^#Rj0G> _iM@YGC2SU6VIEԟ"gj(g gwuRXIPKyy PKjH2android/support/v4/view/ViewCompat$LayerType.classOJ@=VwYU  JOӋL΄dɯ(fc\pߏs'{+W)ʹ!Z{w;U)^i 14y嵳բF7~2!Oz2%>;Ӛk?8Uv;Ee.z-˛)3sk)* b}Q.%'p}>:]4zq=`=& K!'EPKPKxjH/android/support/v4/view/MenuItemCompatIcs.classTn@=(mڤK[S["To۲Px G!f(WAԖwws̞폟_^YXMܒÆ6!;Ҩ(*1؏@8cװ{mkp]1h-̦e>Vx$d<2$+C8C-lbrѵȳvLș &>=ݏPsnZ7k-o0\aE1*c?`[J"hŰBe')یv{ Ec⪘{qB7ָ'瑉!w E\Oz_%$I'T) :w3_PKmV1PKsjH/android/support/v4/view/ViewGroupCompatHC.classQMO@}+J= ÅPmm]^4Gμy uy؃Ơ _.C1{qJ7bw|Q2Sx(R̫{1R8v ىpPAO.zUR WJ SP  fE)Q PԽ)a9O4&L0=C9=ܝRWw^]MGҳFACI`:I4Pd$o.f֮?a;BjJ elg+YPK?P0PKjH2android/support/v4/view/NestedScrollingChild.classuMN0%MHk)n T"]ոvXp,HH~ߌzpYI)+*UQrTMWPG<%)Me'zv5T㱡9=L]RKMpń/f.vE-5R}$ɯUODMQ}Evd,V-cJ6~x}섞i`ic!NOS9=Dž!FN/16IHsp PKPKjH?android/support/v4/view/ViewPager$OnAdapterChangeListener.class @gI4`g-Xhڈ DҟfHSDbg`?vv 5B+QP^*I sLDg">xNv󣷷nFf޻9g=v?_~ +FD#gH%9DrL#َ#y$'H^$y$)I^!9MrUHΒ#y $H$y"%I!Lr]H}| sVw.@k1E~5lΥF=,&үE䰬lE#fUeܯ2LeHtZI9Upƪ~9552M7|<*,1qrJ )FhIfs;2{I3GfsU~ym"T,⺜IѬoPVF7RC-pdhH7OQ3J 弉l]jɼR*=XȐ h)ٜ2J77PTriŌKjHTh50*c1Ԍ.IPtdeZy7+:r2GK$w\"v#fM iFqYGۀ!,x%Rշj^3"U}q3R܌)>9R|rxUxUxUx%R\*NfÚ?Jڰ_Wfr^ ZjP&i"ĦYrr)9ˆD=QCc$45ȕ+cwtUt+CwZ-2F3Du4M6AiN/ jh i9=T+7r^`),s V<J %^|(GL`#Jdn&H$HIDHv$Ed#IFI$1d$ T/ADY0$ȃႛim^zY/TS=/)ۈ쪱/B&^a+`ϰS&'<}ɜ"wr^iL,@ݧoW|*1l-@-y<לP@NjbY#m_V,n8Tu0X[ 'I TΜ<0yn,0ۘmpWINhwCH^hɞrҞbxج[Jk־ l4\:G밑,ZYM\LQm0\bR6n` f33l0xZ>Cv- pYf9Uᄭ0TazfjOR"Xm|j K=5S,$ROLΧRO#L:E깚;Թ,3_lS,z?KRXu%jROC<`}<9A/~< g'矶y,jO]RB5R`4{]p 7R/PK(x' PKjHNandroid/support/v4/view/VelocityTrackerCompat$VelocityTrackerVersionImpl.class;o>c>vNvFԒԜ̒JF% ļr}LHQbrvjDO$BWp~iQr[fN*#܂IJDv^FťE%e&XmSA K-*- E3/hJNbqqj1#9yIY% $XX4+Ivn TPK pPK{jHDandroid/support/v4/view/ViewPropertyAnimatorCompatJellybeanMr2.classRJA}1 Jň(;I-ӉWzQ (& (0^{#U̺cE .q0`8!eleIͫ@*EiD@^_FEf2f/TAhU0z5Wpbt( TEXmvu%@uQЬsi+Myg}e?DRkeXtL)=IXۈ˗.p`"IL9N3?DGhw\%K& 3~ZDXhPx*A?hv#1v,ރn.x#Bc򭀱m67whCb7_(- mjPK'}PKzjH4android/support/v4/view/ViewCompatJellybeanMr1.classS[OA>ӫ,ȵ\*X Sք'g2dNK|&>Q3Zt;I=s|9o`  MIx,a[Ž' v4 {RxvD ;`{Mj=] 2uK4rI&9 l>w^y٥UB5*^1mڌz Ej3e ]Za%giKO1 6MP :J7U h휗CKUR&N\W58+ { 5A'8ܫ `̡紅:w̥7Q#mkLl_J P^!E{zOꌀq׃2;^d~AԄ0g&s& 3҄9-JX}Vex`wܒ-z}zWw_^ڱL_W H:Y0'~3Y&NA S*9 ÈfF6p;z-}"Ä"OT1Y˴ ~PY)vUGAF&5wzVZj-5ZwԚӚJFMeRAVv :lhYԒoDy-y wȟ0S23ElcRɷ+N ܣil2F10hWQw%?א Rd/PKR PKjHcandroid/support/v4/view/ViewPropertyAnimatorCompat$BaseViewPropertyAnimatorCompatImpl$Starter.classUNQ-R[PĢ PE (Dc nݢ>@"b41/㜥\McgmDҍLF1Z>+uEn([AD0aRm S@aŐ+%vD*/͔-7RRlA B㲱i lKB4ɬ0mXOpJQͥrhNjgضvNFA8 E~Q kK%yB' )L^JDX6w٤a7]Nt!2:05L\XgMKʉ %W>tۑ6e}*U$>zLbMqUO3^E7cp ~è%w7Ê(Vd0^[8Bp*99oqw<~xц #DO9ˌ3hA f gtB:/Xp5Gk[2WUKe?`ncڅ6̈0EoA4,a5ɻx*\xEOͺXML¦-g6S&cL=r9Ӳg3]*f2se/zVM+jfs=ВSF:f2Z04n/0L֣u(A^Eʹ m=Ҭ=(C3R3 Ÿuы[Enh<WBY_OXIO> pS4ƍq_Z~{m ϷhpA:#{o0X䷱͇b0hVVCR8*aZ]$x#%"gG KHsx%|SP+TV~s'x?p|$ZZ?\qDg^GFf4 ~`9yN2LWDR쓕 P' o@oPoH|3 R u* xyn>pR%KK_I`v8:C)?n_PKyNj PKsjH*android/support/v4/view/ViewCompatHC.classVwEKӼX,)} HDДPdld& sJx5|fo kބ M Gp4!8K!G>&S|N_\"Lp+ \#[\B+|G/߻EI)V*|-#8:ը,@U3ɰ KT)1"i ۝#RQMU~=Nngy?Щ&Ӓ.&%a%)!{pcgG"Ws>.ZJ'4)W"@RRF;Mړ(i$@\$k&E1Qj&j"+)GTtȐ Aб_IY3<#ԑY]9J-uQFVW[Vbqma7 5)'$CQSY={ǠjB8փC)rU:u)oY)⡒[ClMĦ^jS/ 6=dU#TԶ3t&MRAjgFa/5?-pRMMH؞"2J$#ZJ#aG*Dd dN+}Vֹ#vNɓvZVӘ>TPs 5isZszYA"}R<9\p3W|3DB0[)O R3ZD(t,*>[֍KYI6X["Ex~%-+pG&BZ; 4`'A/AA?.pW=! 8Hp0DaA{"(A"<8Y+($DE}V=15OOHWx\.cgX"Uprvyjc]6HAtzvF8f|-s& 5@X-?>|V>!6a쬅gEk<6GӟcŧPĭfͦYafYIE|z06 T?WkC48 .f(dJj- ^8ڃ*Sj'{=穾q,O'tl pXu\?;d7K^%wsKrnf.Kϒr;d%o{,9%qO.Kb\..y!KE,yK%WQ.yl2ժi|9LkT0 qd¨)CX_G2Z{ZKYjeFZZz}|:V+ZeY#uVu :jdN2ZZOZPt|e:[FKrq9%`Dm"$..rd,Fva%p9WXePZMUԺ6ָwN[ksNaVKtL7s6ԑgpV ͗4πlV5hM| en=u3u b愿օAX\ -a}~_/]m ȥAWkƳ91[0_f0E\mǭYL#PKMa_PKsjH4android/support/v4/view/LayoutInflaterCompatHC.classV[sF6q,[(j vHLSHKIhy dIz!do错NCS;=+;ƹfVwe_;ӘхyQCN‚a,FiXp7e|^q+G@C`U З|%> `MZX,H Mlg뾣U*ܙK3(YN\ ˳s45k=ZO1m4[L3 9mU45; ݓ-:^4%_B.q /4l!hdMk欮s}D oD7cZ:V&61\{2ٻ턚<0҉ Ẃ@$0D;2 , aDBEw c8v' vaޔi7 e{jѰ 4 umT-UGUSKMCDԲ*W}oVp7=wB6bKr0I@J\m" #PaJvfKksda2?!unݻK^oix{aB4xlosN/qpY~V`VvծhDvQ+8a=>'v"{ٴ4}+%=3ґGވ`Hjps۽Jt)Յ>Ȣ-\]%@@t8Bă 5[z$^= DaH͔:1N&l(8 ]^{!XC(QCÉ?6"~@C c!Ldl ($fppfB^&|ItB}%La I oul`"<DJm^K p05{4p8~?y c5ƹS( zFtDw1Ow{8ziM.eΔx ZSʙ|dPP i׿ T>G ^>A ʺՀUQSYSٯ6 `JD7һDs+FR<^-5)eV,ϐ[`{W%/$tTTOPKR|PKjHYandroid/support/v4/view/GestureDetectorCompat$GestureDetectorCompatImplJellybeanMr2.classUKOQN)oe&~"#=mdk{-v^>z > BX) 8xVTZ=LisIA`w ^¿*OZ”IP9ђ;$G8N#,* |p1DU1A[.J׼{ m^m2h;--w:~hڦ![NC0VM[u[5>54S!wMyAI*c6Nv\m=S1z;=r{;ư!2!WDL_ɐ͠aLʁwyE% oo|x)ݪ0}uxң؁ub۔:cf1< 'a)/аF;ϰ>-0ÜeXncQƄP* +$]~Ƕ@vtMMdK i=hT OhZ0Z@(vFquIaWIFf|lm*_G(/ C#x$J}S}V)Cy);NC`ؗ!{,#>E4 (D#6geBX -QA3f8 PK҇NwPKjHUandroid/support/v4/view/ScaleGestureDetectorCompat$BaseScaleGestureDetectorImpl.classSMo1}Ɇ%PJRZp`*UA𡊈*!7'eGGqġ?? 1,U&qǞyy}80ªGkx`G%a|1teIt=,D>sZVvE$9,(NBFƲ`{/Ö'H2-~%ah)͝& C]vB Q)歯 gw{̔fGIB`\nh)5[y{iߟ6 {OS ~/":a??4y=s2{C\is,ByW˖`䅁kF4 d^!g|ކC9!v QfX2`bEs p5o/IWr9U!gs~C':e6+l^eټM6wټm6y{lg|c6gl>gE_& 2S {( ]AaܼLk&=iӚ99cޢ*hMC3h7˛ ҝASs]4+F&yRKRKZKc (޴Y5H@OO ԖYԔVv@T % x!঍ Q>&G]]L>oXT 4/iLM7āZ2DٲD95)"g)+m̆vSEKbXN1tgl+@!1GQA4zf*pcYnf!e⤾P톻|pgW=)cބoc2/eDV׳yZX薧`{pi3:LtuAr 9..vf/[u+o 4#ViT͑r !cΗQYfB\64JhajtNu5 |v9c4\l43ƼaZgc̴ayi ɷgTrt75h3iΩԠ-xŕTR,/~Tt$-58VuvS4xsy7'DGƸDW@l7 rS-I O~r;V;YRGƋ]pry!;T<}*)R4Qq*wc8$N8*qNE\R1+*f1:TWTM`ȳ$?uި3'񃂮QMZ<:6 N4ӼAc5_ɩP qR`/]l?y9@UkutEoR^ zG wzUWD/!I R@?:XXG@Qox+Qx:ge!3sydrqɰ85D;;*W-BuYԆŵHq8!0NBD}*F Q2g>̹0Ha1$]vrC^}↥5n kKE&økC_箖XK#QDc_ R#Zxń7A^\x1)0dM$#/bk?U zLqbc8EҁT׽%VĶ0堠tDG;ZFi#)9> aF&!^'j/@=! ;k40(/t"B+$vX&+` o )n.pSmGeL8Iϖ%w:!uھEf6(BM}P )ubFu집~%پ`R`s˔LHLmPK $PKjH,android/support/v4/view/KeyEventCompat.classsEǿ&$ -b i(DZigI ^.k{p]G_:bq|3g{M/5}v>&^1ky[41(xG6DSMQ4b3xWdlJ(ǐqKMhV5V薩&8ܑPa8nVT<äR^`X*fͶZm4,6Mm[@r IӲ몡ƋUˮrByBުHA7yi^Z5h&Y4ը.d0ZUhOtz3C((󷅾;7ի-Y=jaNc&uVs-q>Q1$v^3}P⎫jus01rLw6w h;svP&7_6լ= AJGy` (F^p s핹a 3"%k\orat ~; Sr)MYM]fKDN&=b}t,iSEgld ”12 2^ d2れ,d2qYX𑌻8͐#%!tYx!Nǹ{lՂd) {)\kq,QO]E+*yأ=BT{W)FVgqfc|bsL<| ߂=D)48B.7ٲQ"/h6&Rb%r!$3SQx<+wհ! /PKgdE PKjH<android/support/v4/view/ViewCompat$LayoutDirectionMode.classN0x0HHEE&'ʵ 1<ⲐR-}߯ow8 00 0*RzֆFKM߮U.z.Г+-fGe?/Ȅ<&{T8c}sF[~_ߓxTA٬p:e𲾗5ʖf]PyuO;ߩ^"6,-|fN"@E8གྷ|Y8 GqRWc PKWPKCjH1android/support/v4/view/ViewCompatEclairMr1.classUmSW~j|iT҈ibh1M6hmARAM\k],~I:c3iv?g P'={?00eUaZLw|, #' 悘"2a,bIa/|DVAN8$0ZJغYM[fLg]3R RKFٖ悭Ig* !KNyGSԝYiLW)W;.j;tyX%Xlݟ=&0̲mT^ZjL*N@d iؖӉ$9uu/+nVE` O{u&|:̖=۞YQ9b3WxcfI^,vr:QQN> \ Uo_>+|J)Up߀w&G?P>Fo(Q=;TsĹ\_C^51w`x%A\ߦ/W 8]60ܾmm^Or <2U y |HFn61M*!FBHۨA9qgk ^Z~@Tձ"=ٙJ y̔076m,xPؕJF{ Z!_]!;OJCos#;s棞 >P8iu$Hj&$Q>C05 FˮAM !87`jh!YOD-*Rtܓ;Աk:`X ogYS/Ez(aæ0}G`(tl\/.qc>e`xWXfh>w&su1=8Ǝ*&ֱa`xƐPhkW=d2@|f*=("d!)'7ʵI(gM{=HJm= E܉n,+K}꟢+]vdW5$rdG9;3aaE,T HXMl_}1NL؛4D5yWe+ )eA"bFVQhc}kҼ2#UPK$T^=CPKsjH5android/support/v4/view/KeyEventCompatHoneycomb.classQNA= (^Q , Dca7 !ˀoX~}sޙ׷XI#4,3a12z!^(3X.+2'|BU9{eazdm i嶥uD-B%NM* /ıм6~r0|C鯾ěǪ./迉B\0ol'D`s"ġ4 pRø,&mLc Pr ,z#I!USZVPKgz\pPKjHIandroid/support/v4/view/KeyEventCompat$HoneycombKeyEventVersionImpl.classRn@}$v hT |BH!i/E9p$[ލNPJ\ԊG!frξv<_ eQ=æ KdPotM Km0 w#Tںǣ7Үb1(ڮV⨧;q vIVa<P)aOAVѲ$@4#)>Mx5uy$=򃔧zd˓=ݗJ0l|eĶD.鉷Ҏ`ߦ~#Â-7qmX^E\+-gOzd؞O gO^h enV<5@DlssBULf1!۬L q VW0-3&>ncU骯h8m}fc3] PVï\GXPK^mŞPKxjH+android/support/v4/view/ViewCompatICS.classT[OA[ٺ*WAAY"ID & CdwZ_02ٖ*Iϙ=s79;ylFaÉbύ`,KWY`-W mkRIghIy9p?#)xYs_rEdɜNO=_;(řO&ONVz\e}uz{@TVS ]gI_de)/|-%@d6+@JWMc}ޡ^ձWXs e^UQA> -R% jfP\׌=\:Pa.k!wS v*Jx?2 y*Mܕu)4L!ՑGꑡږ:;* $qj<3tI]UH0=gŶ U{M3lŽm`#lkNkq;S.k#HGa CDsQKΚhz𽚮3MRo_txX{ܡ/ӟ. ]CoB&tm $kЅntoxLv|M }mCڷH,uqqFo M!Nc?r? EÍI>UC7lfLxO,+,hNЄ0Xt>V!&kVu%jyzAדaMe]Fk-_PKE53X HPK\+!]PKjHQandroid/support/v4/view/ScaleGestureDetectorCompat$ScaleGestureDetectorImpl.class1KAx/0 jg6wd{Rʥ+70?_я8&{By*5?Ԝ.n^WBK!ޖ+NbN8S4!¥4p\Î:Ce(\T]ǍPK6ձ{-C9PJVȭ6\ ?Q3:N痯ݨ_#E;X6EȝLIJ!2%}'". FX&bnPŭ )ɼOK##p 6"m\a[]=(d8{\!WCyD=24}&eӱ&gX3\Ci1J6IgkZk Y9䱇URanPK<PKjH+android/support/v4/view/PagerTabStrip.classY x\U>'3L_tiRH`2B H蒤I;4$יt,aEu! F&*o?Y|ߜ{{r_>]Wi>QFA_(=&5/ 7MM)797A|߹SVV#!?vv?qOgB~._mO _ <-¿sM-'YnjM߅C}Fz#)!/)X$M_<ƐK8 NbkvIOss q o T-ANrRae2qv}]\-5j@TѕF<=jD3fd o tuvmldGt@G_`ǖ-:>q1- vtfUe;l֙-;Lޞ]#[z2P`0oPe,GBF:JDI84ӲX_$>h8jg"Nd!xA@zxosܺ%^Tn"q̬5d"nK{#TiWbfl| Al\1huD2%iD#񰙌F& `̝6vyl/g;'f#F/$;gw, &2.`z#S/oy(OtkͣLήD6VbLliV"dDGdD6әWʌ'։s?SG Y{1,h% ۘȤ#֎4ΤNqu ^4E4x] mBְq(OGbf >IEaH13m&;Dڐ5Ms1ӭJs@9X|D&ڥI3:ck٦ϰ KS5iqҤ圮v-nIxt'K&2DR؞m͉et5>L# 4׆* k*,lOY#tL"+"\OUvO 8ZMsva܊!@;\ݕ132@.#>aȮGl1Uvk\TiSn~0< XL8H;gOJ]鞄Bq3au=mf$ tnaZ1z,t=ϛuJHitdxAw u^gjFlu^STbBBƅk4n>Wg&Q֌9O&J|Aw|׹jN 8ᴲh:9E:Ktמ u~_s;yyNo5NzNJB>DG4ެswjܥ1{t B/`nm ^v9m/iܯ /'c::9zJQКN@/xH:jڒV\Lgp)R=+lT䏪|T8r k|s @k^aذG"qAB.ҫ8SZf:cFt{"caEcLj_ѻ a7pfX(:TQ`E0jU{UJ֕h?dyl0iNg=SGX*lgM*'ʤ,ϭ.?=l Ļu3<+g1qRYiցB%ͧSQ8q4"1q[;bcl,zci#H R<3dzBU 9ߡb0vdǺ2ɤuj ϸb rQ 5/(dyli~`GrE_վ-蘸iXeL\Q;O=W"ͣ : Z/Q|ZIW%ɏwa|mxw8qhlbh<ޢq}3K@~ncvۄݎ#բ+~ƞGW=ho(inoRU}Fȁ"VT< r n&RIz [,e|Jɉg-MS$9vNsJuMQrcB>EhJҖ,UfaZxyw:EoYٴU~OMYs:>o𷜠3z;,5 ,wd YORN41f_pZVYZ8΄3;n.hmeAEgS7·#^ W '. 3 w;(!K.unh&\pq5?[YnZznn;_~6pOaSkney:ATb^XոV|śKwx̛~wf{a\E,ųv-[T3Gm:boѪ|u)=Jn :ڳxshkNZ\M_WuNS[,|r~zKA_6WzoGϡzT7 ܶ5W7U]+V=WDsn.mǻMt @QM(;*K逽Se.6KZ'y`mxP}GPц ka{ Wc)4Yj=) rYVTbWy Ub#P0m:BlY/ P? \B%$),]R6MPѹmA{]ъ]hH"ܝ4ɻE1z/Kۜ,Q-t]+˫x+zP&~cIoi%=|.J'TW xRsH^X>BXŚrpe9̶Xf`yK-0j^ Fu bZbgHDi*V0&X m(5Z`W]մP7EHan$CG7sjDy '٬6 | I48ƃ& aW t*j/ ?P@Z/*?PKq PKjHFandroid/support/v4/view/ViewParentCompat$ViewParentCompatICSImpl.classJPZzBwQBP…pH\* ō+ŅCD ^9s7O^ߞ_lb14&Ĕ ̶UC/W cJBY(פw*j+CeVUx*nB'“vPtJJZ\ɶWK./ e~躎-zY z*~PgUMY*9hp5<1%.|M$$27&"t2 lrwy,?,dB17 C2R^*z1؞R"vC$췸 HN8uw}$-Cp͍=ntk7`툔T 3,[|> bQ2z<0+N+iG7 MT1[c_&c:4"e6ȻSu@zkNZ~[ұi]N5ŊTl7sR=TNMRyTo_i`n6%!,DpC5#xAL`1t׿ ۧW!6hq^MymTz .ҷ~P[ O9N`B%tBj?\A;E*pԇq k`, iTh>@ !W4[=,=t|L$\%E)ՔBiDFiLf(->>Vh&o/Xg⼇zWpH ]j NNTZڳZgFB PKp  PKjHeandroid/support/v4/view/accessibility/AccessibilityWindowInfoCompat$AccessibilityWindowInfoImpl.classJ1xzD8 ݹa(4Ș IZ髹|(1x2Μ>,X%(BTwjMÔFxZlUg AN fes,ΨŃ.IcՒӜ;{MkT&Z$lh53ṟuf sĬhs& sXp7q+gQ¨5EDΏ}l􍋟Fsai//nB4'+fZD=NE!v eJ۰} 6{T,%X:KuRc!AKAglg g9P#øDɜSn3s0 Xq8'X.UPKG3zIPKjHqandroid/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderKitKatImpl$1.classW[SF7a#bn!18WZ.Ő4&4M[!f<^/%}KҙBfӷ7t:MJLj;9Y~ulłhDЊRP4K،e殄{D𑸹/l2Y@š"e%WBQqCL%RhZN|#Ym NrջSYfk5M2MnǯHaw'U(a@^mfn ktimPs4MsC_)6tkUH{Tbqq_ uP8pW)-EʚVaFui0ORU -Z^eRS"&1T +VT 8/ VȚΪ%-RVy%3ōdj+9R"C`oR+("R @ygzzGcb3@|3.R%bDqr辉=W^ D`tM cAX(h,!2!u=4C!݁gp*ꔧ,yM\ԟǶ>]C E0 _ Nh1n4n\>BFW}n*auZݨZ$1%Kޕ0NM $wSU!_a-##ȞPD~7vW"°( -!yV鬈&I)lK oD%J^S<Mߐowu<ϜُY79vLoP'FF!CMPK?  PKzjHSandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompatJellybeanMr1.classSMo@}uZZHhh6棷 RT)mrv$3? 1넒D [gޛ7#+gu踯၎ *LEGʘXv@&/d`Xvd NrקȪT(K˘ҳnF;{R|y%XҗI>N ‹O^ ﻂ'ѓ:R,?#ty>{.ZI2iKTQ Qx kSh)O dtJ-xT93s9/kxB +)ı"㱌5  Xv!3Ka z^ֱZie`j~ZqT S;m[.;a̋mᕊY̝;>>>߿Nle +>& 9'[J;BԐΓ&99HAM'a*L 17n[#Yf&/$q/#"H9WowtMcWĿv)5עݾi?_V{8[?B-uLA7YIoiRt5B&= çͽTJ[mT [h)]P vgU~bmj; ue^aptQ.K~^o9=h&YWgmE ?uN!}kaԓ*C ݧRfC8j/N$q@$O1}τ|sg<mWB xT*PKowPKjHWandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompat$RangeInfoCompat.classUOA,PQ* Ѷ(GڒZ&Be%容^'c$`|&>qzX_fwfvfnvӯLA!$4aLHbR)) =bfd%+X;mu[7^m."C3_N1W2L91s3<[UucS[.o=cG% yaBc̭^rIgVjI]~.J,\^6kU^䰖׍ ZQLk;J,RZ"9v%N& ]g)M2=wݍ0j)duzT&Rv'H2d:I6Gg&y3julXn\#éN_ "b,0Ͼ ̾,>((.+ X.礣w:d J;0E>.5q@;'~8|?+xeQ|Tӫ.!y˟E01v~jz1kN.8|s57'a3 :yh=O0Ckȓ=~PKiح_PKyjHVandroid/support/v4/view/accessibility/AccessibilityNodeProviderCompatJellyBean$1.classU[OA,Ѣ"",H 5,HIm;!f/EA>h1`|G,!P $n:{̙;O xA ,ҘASQY5)̥0ϐmxvfYEM8"8pmn]0h{}jJsaz*-t4~ss[&$4u8n/3$W*[Ns1Uaƽm&_u-1=me"0jG)f̰X4:R5^R,Ű0+u_9bulcʺxB֗ɁHѫUN"yk̖K3=ݻ\>Ub{XJat\_v̶Maˀ :ŘЖz)Tg.ZYu i9OQkE 4⩆!,1]6b wP0 W݅ sð}aj$? 뽝N׵}n/KkhtY}`HJп}t_TF [Vp$ӨErThԦ6Bo+X~! T2tlFQ0n:SKk oqzQMFg#c܍ֵ7{Ѿ-e@ ;2sd,EcA"%=xD1GQFRtY$T KPK"pPKyjHPandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompatJellyBean.classT]sF=8vdI@(GLh@(ZZ'(RFо>tݵ!ͤ{s='{,+^UXR UXV +*4TXU MTX/U O/ VPX \p)}i-o{r8kPb!ޖC}7 kG }';"ٖ?tPK.W}\ܯ1r]ږPyW‰kZj -]YÈ;L=]=#|q`D" Ȩ%jB.!ù Mg@7a ]vp28((ŮzB]IíhO`IhȱQ\뱭ɭ\PkA;ZVDht.Nxofj~qL_7mre(D-xCO5Oq0N (u},KpTbias GxtL&U7&n`DoMh~4Hy)O`XrO.>||Z'<#}4yf%*qd9M e\Iz7q@pl2TnpbQ/^8E&jT/$M (WЭ{Ġ^WYE-L{4siw̅440_'̳Iy#?j1PhiI~|COV& h TBnMNh_m¨"g4DE%9"hb0=32{vDN:;ʿ(A]NtA³*Xj! գ\*3MUϡzP{u,_>Wky^={#Orz"-ls ]^exsPK~Y PKjHrandroid/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderJellyBeanImpl.classU]o0=d cJ7!!xB*@EUA4 $^q:v? 6G!n2RB-=7OЅnpm,;n$T2=iǑxk⡌y-:\7{}5x 3eafcҠ -E{ 9Vruȍb% FL,#?IS(űUو{}'CM=#{QVoS`&ë0B;>QɃKi8`40ttV'&}ejv)T3,_@7Q+)?sݿ3E)ӻVP5>Jz|u 9ߞqz`W,}nk3g|I $]5ڷp=/aVqYf}س?PKHPKjHdandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoIcsImpl.class[xEBҔt˥P@-P(P@jiAT\l-nݴ ( " *7E>>/yfӤi|gg9KWΝ p#yC$I^$ye#$GI^!y5c$I^'yIS$o&9C$KYIΑ|@!G$I>&SH>'@ɗ$I":yStCwIнt |fLWnhթƨfW l)2U%@t}NnK0\U5֣zBwT#!lԙa7&aЬb^Q1c!;Ljjҵ搒 qfcRqv6d\uݐ_鈪I %#mTNe2wdm,ڗ!N\O)J,V:T<!%Gz=["jڥN7b 3V;LQ-ҍxf)GOt]qCqRs`J{i5'@^ż`fʈaF4Y▒U;4E`{)!Ƶ(ZE6`ù ?I[-AaƹL4U+zŊhRjH-&op0Zz2zb{TZmH'*vzg3øݮ=_$oy$4L2ՔQO/ݮ2x_Pk0ͦݲncD/%pnr-LjVi5v|a}#/;3SR٦^n5;H‰? /f,-Ο65Mϒqӳdӯfb/TN#-p-c>uY;r=2Volfj[h{f6fA64/ncf'[BhYn|դ9"ދ `gk7>V%vӠN^hc]N)0dI 0S^~'C˰d,a)=d%YN|ԫBL$NRO4$I4`ʐ9,IC2cPb0qLw+l ]VIzT++c5 =Z _9dWVRj`+d` 'o=eS]B>5RB]Q+s _&!qW$l$rSd0r.`Vp,PQa1EL["R V9,fq1X)f18ӟŔ 0\3Vѹ,f\b& 0+Yt&b*F.f 0;.FG/lO.B.S˖/aːZ+ɥ6CXjڣ7/78RԵΥ>̏@jz~CY*ZB/֍XoeMHmRbħckNH}̣nS H(n[R[=6~FH!ncH=ޅ==ԜXz|S3ϳ0 EhK[PK> PKjHaandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat.classW[SSWvI#UmX!!AO9$4 AcCI@m__Ї:mǪ3}/[gзNӵف˷ַ{(ÇLk~X݂,8y+^\G]cx!ϰP`(2,2\g(1* K 7n`{ F&n7B6HBH k٬].fs\喖hU((쒞e,pɴ sbn.R^Z\,*c}#bjD(ƋsQ/:'ТF1-YVU1=-к&)%:/4Q튚jY*t=JCi ٧BⱩtFKgX<9X*j<4#jΧ MY[=0M_U-9U>NŖӫR阀Z9A;VMoubSZ6"fU2lƌTj54E5kي•Hb r}QS_J)]}ݓ :m*7s;0klfbOZsUPdƜ0֬5ب#Jr=DznR3юei \z^]7{x:S܄Ee\7MgjwPIݟ*.H8y @ÐwxzQ, p] ! ^( 3`8p4>~2 G ~MX^> uCE:S:S2m¯kQg_zeW=SG/7]M7Qz6Kv1y=ae#*UG=qeO({RSʞV}+ʞUvP>dt1${O¶p> n܇GE.%,fpx y)ʞUF(LveG$'}4{kUE4KhzGM,$04OR F+ż 8JNlx]f"ARH+hf14nĄl J͔j ^&}bG&fʒPKQ Rb PKjHLandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.classWGǿ y(AV,R AqkжX,9Idf7@~9;c 8{wv}̝Ͽ E>Ma)6,Y KK9|`YQe9p9ܗCEU99<"oȡŠ8ϵ^v]V󺁎)`@5h pFLO+1gBDPĸY`nD9Wox~[=[|-J%^pD(76;{̬s*RU­f^&*{SU/ nFg.VZr$:•[((WlkMJLgV7eQ&+ۊ%;8Ti4 ɥOFťM;Rwi]wcM'/kL7ObK{;a1mXT= yOj4mdm܆=eЪJJ{U!XE˶m1nC cvDݗ(d7[Q+W ܯbߞ=&bj/tgt G|nSuXzNZ~^ұ0 hצN5ŊT_ 兎O0@<Ĝr_XұXq]5c1ijxV9ί_rxq>z/37E6H1 D`$=/#*Q.6ԵPJ*ͱ`1_?CűWХ̌좿clMbVL뾦9ݩH%2HP&Dq34yߊK{P\ 7134wDmҿ\?PK]>hR PKjHjandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoJellybeanImpl.classToD&YӔ@[I[SZ~@!6 RHl2^E8P 8r.p,뭅*Xoμ/+8 PbEwXUוXS=%JPb] |Uz2ZdշJ  M^%TӷhW l[lIWFw pݻ-=u Dpy i7t̰Ad/})>6A03w<:]v[C6&`՛wx.v̍aGS |?bx2ǁv2z]-<{00a-=gշ{CIb s#%x`RGt.}j;NcWD27ef'>㡈^`p[R9P sWt:4?lZzL~YJ+5"&^8vzE6c0CTn}4Tk\ՉÆ3;C1rKk% 90TάJufq.((O`JMYx%΢f`Ny3p \T-]ƫMWEtP*q=>Xv>x -SB4ӛޥǎB휤w_G .i&Vms{)eqҍxV#u^!Qe=_ȧb"VZ'c2Ԭ!fx /S3,BėRD)YhϝSEڇ +cعl`uԩ#0JsC>no L߃I$ӉZK)|M&^{1x܏^'Wɒ\HRJ޸ucК=-㤴Fz;(Yʿ )/JqN='/␡+I_cxvJ:Xk/֡jFhxqgnqa>Ʊ58n`Gq|q܆q|q}Wo.]0\ݛ6CiLI:[giY0lb2rhzɇ5=zzaK.׍iE(Swq CqE8=ůO'}|$)&<ç>ig|,s>|H]~>|>|UF g}X$aJhdǃ׹@=LQ^wGԋ7IXBm_k- ^Ky`߷U<˟>(Xn RW "NbG,TX tn)"t75=VֹSxGb>T[3U/ia*Ek-4^(]-X+H*eӾbbՊr=#aioٛo| w8`xϐ$p@'(tNjmP6 Eb DR"MRG=:KDPKb&PKjHaandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoImpl.classVi{EEH !cUQMB4xMf:ٞ{v }=lay1v~jef؀+cǵ"ژPfر'wíIJVKx{љ k #WaHᑜ+} ?[|%;O3i*M餪kò5kHm>F%!+kl]cK"]-+M8~%ɰnҚX?eam O٩x1p/&Z:Og 5I 4hHN Yjڤt_P]AK՝ڇWK*nu) uv,SNSE. ʕU"\qm0t_]`C;r)ks|GJBn9A6'=;yQnTVtv.I63S<3⦆M{I>NkG&fҲtv6^^\eX 7n(E5n(@$ڸ) eFbiQ4|"u"J:"<A ,+ԲdkqRϕWJ\΄rNrp:>8)wh!vőr4VB-Os,D)4⭱~0$ݪN9yw|R"mKYBugw.I_D11QÄ. (k(%5E?9Xr&V[]O˥Rlduk5:Ktl2Hɕnk16}ka,ö1=a#[mvez-v}gACYx?`,>8 wF(ml#?cG-`'->~ IO? ~,3| ||<$%瑯x y9vO,~ s|57߂g8BAe؏疶PK(TPK}jHWandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat$RangeInfo.classOAǿn`_/% iRP!>8dmvUÁo $g(㛵 Yڨ y37_XD1 &l #$H>&S$H># $H$k$WH w!@^an([m%u=qf7k$Yllbe8 ̩u8FH@#-;ӝXO*3MfW'5ahN3e$N:l7ڻ^& a4':#ku.F-`2-xfˈge/3QxmDS0I3!z9qsꢎqD3YD ?gDG;ǔ##@@9;Ow!KQ*&8Yt)pU-Z8 5hfy{b8Iy w<*0D2e!(٩֠g}y,(TX \Wa2 Z eL1+$)LsS.FTJ0&10OOC?D"{M1-cTYoQ|%"L1;%|k 0x `<[*1"1*1 ~J}엠+Gm@qBɼAóx*X |vShg(j 婇zDJ G,QxQR!Q8{xqR;X;<$ROI]>?{yiRK"zf?IqH XzIJjc]S/#c}Q ,WzMJuiq7zSJm3+"#RR ԟ:S!$)H>'9Mr /IΒ#k$H!"%H'LrI\#)?Gmy ՊK8iSZ6벝)mSl)u =ӬՃW9AӏO%\\ 3>~n ?(`5οX/|~RBпVWdGW 2 U2޿VO@د''okd'x&_?$ɼEEfY|qSxnSy!o6A/B?;B?34U#3o>?2Z`{Ğ7J0Cۛ>(1PiDL7+"\qw@{^quItsxAAq)itOÈ;"Ati))wq$83Jb.;\[ҹN"5;K|wqg$=AtqgwNkH[#DgBwq$.\iˈ"52ixU] ٬]G\z8D- 7I箘FܠF0w l {[/M?Y=PKPe5PKjHmandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoJellybeanMr1Impl.classoAǿ ҫTU[_lԘTL# 5/܊w_"6F/e3'%Mal)sO2ye J3O+ç \Ɛ({`XJWuZuuvUZ70l5"d]:2ꂻNv̊ tLVkn{~huY])Z|gMV3Ue DXu{>C.W=]n9mZ/':ٍ>$QDa3,M9͐L%{^Hw!\aB z4 -gc7ľT̞D s,K̊UȨeF-X3F`J2$^q} {@g^Pdg0OVW\$`iL }xeUvGyh_i"Bv5$#Aي2SvB5Z,~F m5FX7#n6ɫߴbwg5M6Ѧn@ӓ3A>5>;U1~\1܉ױH>MQE\VHPK6XZ{PK}jHmandroid/support/v4/view/accessibility/AccessibilityNodeProviderCompatKitKat$AccessibilityNodeInfoBridge.classJ1OjB7.ZfPZT+wL:ɐdFj.|J ,ro8w'+yp@ 4gw#"vBˉ"8neLFq<'Jv+$8iP+Reh/a;ӉZ͞%8o-y2,|S+b:&wD$M5'jG 5ՑJu"vЋOZe"^&0{C=DM$J[]L7ʖ9t#?iF.YOKh13&c@P@E-]CҼz(;뮯:]h?PK,GPKjH_android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21$CollectionItemInfo.classRJ@=V^(.]E(T j:fBV'7n\k?JTЁ{ǜs'[X̣HaZÌYh!Z1d, ö~Y1r[が0]ʐbr]DRyH4޹bЫ'a((^#Pa-WAdͶ&w.]ݘϷ}"wTQŗ˽o2<1, osޅyP"\<%2!fFjؓHtC0`}H/DƼHtߒO soxb JѦ!KI+=%\ө'`Cds4‘Nf8[zFۮOYHc9Ay('K$PKwd}KPKjHmandroid/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderStubImpl.classSMKQ=OmҬ신BH[4Tb՛=g8ҿUТЏ.B7y{'SfĺM[ څtepɐ,W g \C9B=r!OYis%#{Lg8,Ҕ ^PBYlu9Adm~SC22׼^ NͻVSZity  +4+2ͩ C %ne#a4; >C~r ޛKG6+AM,4$<`oq8M;KX;;J@$VIFt : R>X>,{6:B|sMwg*_} vhl՘ ,X$`Y' HXLZaP+}(=wfWwq=ES\Wtʨ9$42dTHF#[ =Ny+O>*K*Va,KKaXڹ1^rE1rVUօlz/*!};p0a˰m'N77b*2-NؕeP y Zřᙝv@5GʅO/ 9s('NxnvTlZ q w}\ 3w?;8GDQ/-':kLclRbKshrV3ӨB?Т5bkm X{JpsǮ$slױPKPKjH`android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$CollectionItemInfoCompat.classV[SPNo!\DQڂ X@ Ȍ茓&&\/ '㞤$/B;=똓ƭ8> eN50(w7$=A 2,ARFbhXV4u*gq4n24,à5niGw]RV9 J4bl[r4}+Z<˅Utԕ WU-mg^i؝mJERdP7M}r9ыZEVV3"rih͜ 3Zr,dOTҢ帹@.`"=GE&w%(6gS%^q{n ڛD8k˥DI.A$ONLܞ)+{v^a($2dPz$2a>O{j^CXŬ>EuT*Rp jQ'a<+xG cBlD$< ӂ`Vs28Cj0(#z94/ 1܀NXZ7}n&Fz76}Nq_MyAӨilYի '#z$9$ iimko4F *alt!4A 8ISa.BOZ \qEIOI^FB=k?[G}t>YSfܽe'P#M eԵH!KeÅS< U~@K@~ρ { H]TA~tWQ U+%=- *7ػ=Q\uA[r!kT*ԞEEbS"pzޤkj0}q.JocUPKojE PK}jHSandroid/support/v4/view/accessibility/AccessibilityNodeProviderCompatKitKat$1.classU]OA=J!(EE)|EA 0Im;-Clh >Q;K!;s;w,І4RhG;$&wJUӪI,Cn:%_S̢e %pR` cDtmK^I [߫ j͔Br? 6ձm21tj[fɡgζ hLAƕz9EX>7%o0l;Fuː&BC  eMK[dwͺ;[7/<9R8zQr^$E36E5eS_o|X~󫋖П5c&/ЗBvh?P L+aV *H+|Mozor!wPSzZд׸$is itj A 4<ĐNJaJpH*״VPa{B$?]!ublv]<ή[YI/&rH=UvBoغAV1*@:WVZ4nOQ~7 o`cėS,`Qܥ~ĩ%l!M=܏7ѸAj-T߁&\$; t4U֣H@Y!GmBld4҈# y@wHM^pv[ 8Jb+-4|&&'iA&$N%=k1<>2)ne~?TX+,%FZed,N U 2'Qywؗz600CJ%0_/PK0JPKyjHNandroid/support/v4/view/accessibility/AccessibilityRecordCompatJellyBean.classRKK1~VfUpz>+JnȺ)ٴA`gG [_*0g1ǑA;c }9{cL9bMob=R֨J#B1A{ʲ37\Ơaebve&k9P4>4 fXl{=}ϴ- NI n5ƍ޼az '.,1+N[8{d+xJba*Ҡ)o0T-jx:''_(=%'vB(P*;!gt?fe zS$by(D̵l3")]͆cABvqwx 7r̋pWc ._L& h13:"Pݦda6D͇wie#w?:yrߤ=Ǵre<3;h"K<ɗv3j;sVM k$a6CfK]Nzo$_=Խz^Ao!蕲>}Xv;I}ཤ 0H?| >G~_(S$Mux1 G >I{]'a7S䧲@WiB=K G Y'-K@7H>7BI?݄/Rxmd&X%q6a!w(N0 _ =QuXφwj`"Mׄx.oi^8z ! )cc:`C{RC0G0E'}Ocdb8 1"/="z=PKPKjHIandroid/support/v4/view/accessibility/AccessibilityWindowInfoCompat.classX{Sʥb/ p4@VZs$9Ѓ! 9wQ):6"V-(P7t}yg='MIq}~ox?gXHxXO΀_)vxg </"/-^fWx8 G9Uy5#L2db2NyJӧ8#c2&xs f=jrZ5t'9!27]jFk˹ͼx6-˿1I I hV {G5֌Ivo|_"՛5~6BI r eM>59_}Oe[B^l/~t=1J ]2e;旍VYzM]X&Z.ҖJYZ< waZ`/*25[-T&H F3%4:T vniH`ô4eFꮖgZzJhΏ`47a#cIoGE&EtEt?!sWAZ+}3.Bl; ˨'7X ~\6 5;$J1ӆw{E SwveAxֶS0 AD5e@1wP+L[AVÂkN6l㨲`$[La+Ұ&'uTJAj˂lH]D G™[SǡS83q1QJQeFBm}5V`X(db'FȨVTT1OYvM2,Hj R޲ )\_ rGY 9_9hzy7cq6G͆0J%&DZ8b.y{ȼpΘa*H$g"({]od0Q.AAc3/FNAAq2Q͚C}shk?m֑1mL_1*..vЇː> otԇx v8$f'J:E NưM n0K̅Mv Qt>LERɤ&lGa5rs dquQ,&^*q~Bba:=M68Qc9x_:TvLuve%IxPW hZ,4PuXّ˞}t77n,qB p4||ц[G>1ɂMq8Kyq\⽢vfK;-LjƓXY'j WQN 7ᜧrl[kXGs]O*u2]h NN}4wU9TPKC%PKjHoandroid/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderKitKatImpl.classUmO@~ܵDPCyIl|ØE.h!?mX쵗?hGx>T6e<#R9;|̯?~l :9'mU-łŋz$^KO1]Zi ϓ%iIۼ"ܢ՚Ő۶psȘ?rG|ŠWCO#'J3⊪0\jE gmH30dxztDԕ ^\滕Xx3$bsUwŎ=vm^XѮμd)Q#o{[2;zmpr'n/Y˕thfMm]S,K$LU ]ǃ40cޱ ]S *\ aEnshMtZS[pdIS3tлӏO d7!@ths@zDI|s:j#Ou 1H}]s ƣc(6a\ŵ@hwC:II}G&xknmV#m®х+n IS%qLP"٭%LPKp., PKxjHpandroid/support/v4/view/accessibility/AccessibilityManagerCompatIcs$AccessibilityStateChangeListenerBridge.classJAIλx&`o'q }=do88w͉fPJS  L ZB0kәzdHMN m dˍ=:\wo4f*<d+7kf޴Rtl/xW22f9AX"]8⧌- ]i!/db L 3Iؿ3eZrj4[,4*Phz n.ƣgoe%:=Ѐ3mq7k,с'iѶ#E?'H .IkHb@EoA'.~k-z{F5,&PKuiPKjHmandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoJellybeanMr2Impl.class]SPӖ .D,BLp]aB$)BxG9I: edtvlv7_,`3 ,4KҼ4ҼfEUiީXSAYp щ}XR rՓI\h-3 atX# .aey֎==95eq;cI&rU+;ӊg_t3S+ =e:[̝J.]ㄞ`l"wliXeSݕ&W޵U&@LXJ8px*[]9U }Rg|\a S Gfߞep6e_ի4;,{&Db gmk.(r7N{[.G5(1Wt ZAgɰsP[ND v~: =h|ϻ˿wƮ%E6TnAc8+%= E+hhx̲2h0a-FCfk4`q4Tz2Rpv֕W/Ia:p>*6 iQ6Br3$g?"9/񳸋{͐ߦ)? 䃃 Ѷ,0Q r&#n Wb%M@2Qv%<} MG c -/,4Mlh7Z^\h6w@/,QboPKB))gPKjHgandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoKitKatImpl.classVsUɱݝC!r$\^A#l&1¢#yqvf3$x^_VieY//Xv ٷUHү_=;8)xE@&(aJ$\ITH ̒xYOxOx9ϒx$^ q/xQx% jmՠw\g,Hs)4ff-\Yvr5n8oٗ1Ynqo"ig{ga۬LeKv|-NKVv%+IRܔS(zE> '6&ckٓ2+f2pv弾^I6XíX*0+!"̛r, mcee $8dёB8Y䶈cCJI9e hIJf)7"`wB!l+W`F̙nNγ M=\df0`y =5Hn֛J^,Z#©[7jzwBj~+cvayu]2[X_NW^d1lϰ+ב eB"QL{N\Du+qu7İ3^tb7l-al'3o5[t[Hf*[Λ 5=>K9vSipۨ$nuQx]ͰEmaa.[p) o9M2: 4;:GI\!uq8 MGu8I~(I,1G7k0qr 0˚2*2m~oQP>3JehXF_&U;dw{b&05baګ #`|S _rn9[QñuUоA ֣b0l@]`#ѻl[K!x^8mӏ>{k$QkmUŊX[V#!@nLAUs*;2JrwF'VvlH+pư>h +&cp}/&F( ?QgDTT[5US%`_*QkF7M!Kab"̴ L\(`7+!Y̠Fa)` .^xA/(/PxAHwCܪ rR"%Q)BA)"fDz0(`Fwxp}<𷐬.Q?G/;&cҒ;ŒR|\ TV׸L*8DX#b r'lX S ƟƟ5>~M @H7$\.<* rLNnHO$i\LѓЃ:ޭ 0"4L8.PKgܿCPKjHtandroid/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderJellyBeanImpl$1.classV[SFVq B-%Njnq.Lh Đ4Izp #NsI_҇d!3 %Nӳ+cN{9|gJ/.c#V̋$ڰ"fIE>8q7ESPD+bpKV;*NVIt\nfrzA2pIF7 :yɽ:ZqMViy(s?uA=sǚ߰l{{ҝVfC4M 3,Ïu هI<3c-oM_i}OW'"M `(hĩ$wK6\o+guz|gʎi[dZ^P&D(o2B9ErI*r@t3< CX=ޏYx,4]q>ڒtyGMԆuٕ>nhSaQs#nƞD*Vz)*twhFt2$ z t؟C,׆V=J-%71V盬=6Ama'sw paJCi~<Ĵ1b0}II=GbZØ1ȤI`ɦG6}FҾ+yzkVŽA 'ޛඅ{܉HƼFbØw1=Ø1zw̵͊[ wMx@XXG׿a 8"s}IQ2,RgEG+Q/PK~h$\ PKjHEandroid/support/v4/view/accessibility/AccessibilityRecordCompat.class xNf6 ! "RIUBI $`ĢNv'23@VjZVDʣy4ҪVZjطUV[A93a̴_|_=s{ȉ'Y\a.}8@K=IA2_!U2Oy3dy|7>k>?#'StvEuɐZ.:ڱ_t/> ڸ8pbKFX n7z-B[+7 }Ƙpt^rĈ8-F{h!Ub`x\$Qy={'a((WfSDNK `7tC WZZ zz0!7EAjC`G`1pyXF4bmR_!:LϊՆ(y׀1K~Al-V\EJ|3OQ\EVDqeC]kqֺ@?,7V`X) v#X ۈ`\VX`IrnG(! l#X l4 )JJiG0 ;`ϻia^p;X( [6{lY, صZj#:/ _=$Q :0G&`l ,amʂ]vYtv4, ,Z[f;VÂmB.`O20=`wg\YǂmE]{ {>F΂Mgv9}1~Q>5sQQ>2[|,‚qv "v;; qɂ`m/`cf=6HmGO0&oߕ{Jw}~8٤F[RSVR-C|T[;Cg~`o͇ܵ ӱxo;)n<(1\sC _!>"0/i3D2/y|XSvF/0WMH@p*xlo0V`]^~9z0*ˌʹXYoTNR+/2*Cyj > w;Uq ɽ|D}~bw/r&5\,lF,`I߇ uPPK7̷ PKjHfandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoApi22Impl.classmOAۧ+XOr$M ؠk\kJ_2\/1&w~sdp 2Xc6Kl)))x*Z׶_ ċ}ĦQ3MmA=ٲ(k:miKW< &OXtK[Cu}-löiISjJ:cz~q}sflʣ 3_<}5|=WIkCu^*6PwCk!R Ǫ#r^A 퓩gSe8:Ov(쎴FG@4fA+bXfmR 2E YzpM><-𴀼g//v'0a=ѧA)GI1YMqXU G _S/㄰=遄mʉs]:Bo5֑4^8kJ,.e35Rw);z+:RF-&Xc5p/7 ~>5[R< i{R <,9ҨZ*XFPKےQlPK}jHQandroid/support/v4/view/accessibility/AccessibilityNodeProviderCompatKitKat.classS]KA=ǮwiD-QoFPBd#Jz fEу8̝{g}y}|XPBK*U3Л "ma:L'tB ofwnO[$fyy;'~>)LΧăee$_}HVND%2L"ٟ,"_9Hl"{ Z%*"D!2W<03?" ԒH="D%DH EDZ,&r%DYFd9ÉAdDDڈ "H'DD$r."D"DDzM$F$N$A"k#^xoF"?8b'r|ߒ$"'?$9D~OD$rC\"9 sEb"Iv ?eD.'r+\Ej"uD' '~#n"r3[JDFv"wSwyL^FfmVK%~7 L{$~$|@/$8OP'%1TMPCRğHKW$*Iu*ߐToS+$)q$%1IbS$+1 $6Db$6Lb#$6Fg?DO `޵5а١;Ìծ@/uHا0ꦆZ޴i6BڟzcUeB[shh$D@ō-5(;edE8' Rk1S[255VH.R#V66fĊ.h 4sI{ zF@(.Ni"՟V]oɜ͉X8Y!t$S*\'RFM+pKltʦ%r;v۾L4O_\s袚l\vk\T,m(R4a참i][mNq#EqÍ`O}4RPG{bs':Xj GBmXK+D%j ¬:6ND`Op$V':kg)X %d\P<*#D0apXqj\uV$x7F1ʷX4_%z]sBP@AP38PD!ʼn6ؒ*my8xb^)QP0־Ǵ ,]#GRX⩴)@YpWGu7!<*Įu( vt$EZ uGׄtz[<8iB8@Uz(++!mjj}1.ٚ*,Bz{BUXѩj8HGWH} ;EqcH::͋#]a:x;3a]h[ZZpbyoD6'0erX(ۅ\s77gbawI)< D^ 1B%"J_#pg$!cpfc92\գWC:#$G3΃xySr͎jfYmc1cP$,'^j?J %&7rYJ*^jOM~2O bd Z{U1+ň5_8(ԋ.b8owM&?$ψ^J8%RO:Ђx 'ݡ:,f64"oW&PUwGC)q8Fc:4'2Ó>c=q!t!=Y`9tto8#:1fW.jgS,z6UͣxVHOG# |h3 h0n-[E\׷C0-WZZר54Vfnv'=EmxM)ԩhyWSm4^0X:{ʼnVW6`21NdӺmIƽ>RYx xn*ku0v/GQr,=H^ěsҢ,W͔U=jt\MG1%Y wG "r{,ŕJ:H"P\.kOQ.'{:֣6 b=i E==* 'ΌwoQOČfp9q`"m$umT. )qN,u@Oo6e:uukC؄ r;M ꠭j&u;p xRRFf5zI %n!t\vrU'yq,;ud0Ni5-Ḿ;Cap{QNM4MSJ7uxݓ/ݝ㒺  iu0ZU?rm;M&HT":-T%qz 7UZ.bw|bm^Wr)X.O`Š,N?MHQh$s"l3ɬ! 2[AWT7w2k)V̖YD v%uYI]OD~YZW]Úev Ȭ[pq,,7/U*cz2' "[. - >\f'ShShSșD"r6;Wev!.F]BKvծڕTjPZ]GMTj7R&B[Gۨvf+"{v/T{jQ/Of 2{|yi!-lq^e KdӡcPf8oC,ۋ( aMKq~"Ei2~Hc2b?!3b?_c_ Y0\ ,d(z;Fl'́hDd12wQM$ q#)lb=z߉X|"{qLH"F Ϧ LsΩ n$a.]J"(ya@kgS$D¼ jQWIt4y'Y(u'$*aeEX %2' =OJ~a}zQ(}+*gbo1KCKcI[I戽J~b/ ^Ow A%+;܁)SAӲ<`hju/:MRYW 8Ţ5RcJ`ρ--U֚>KbVrG{Z7y8\1tߋCa0esS?X٥b}i xp]0u^d*z&MԶwM?+VNwq~%iPwObp*Qɸ5\ȉzWD-PPG͚7Jfp?*1Q PGfŵkdO*^Ԝn ࡧ #` ZbAtKsi(F7 Q(g0,#ʨkbkMS3pksj{0 Ftgp ?X .seg yn]ލ| |6l+އ|Co/01Cf#?D~?"?~ $'))R_4~:4!fج;`@/r@|ߊ(Y+6KJP$*@.~JCP&kJF}w.bXzP.pߦV|TCެPkP9|uGKw_J] g ܺl->@X{;~āJ0(ZV+וT2(dd?H )ABF5P]Du$cKR3hF}I@݌\Bn%`$SX3kI` )1Pkv6x=$㣨aECE_R~]&EdSiSJ}EBSY+[!^+g祔T3IbFR]2jqyFJrJ-d\q6ƭЍ[4kYY5A}֬t5Ys2ΚSxִvWV\ |ynන]_BIu{ 5!nU&D=!zzW30`&na0ܬa3V#UG. a;Q"6FG}˻Y&#a)&ښBhǜ5QO>x=6p:܄ cAQ3^{.Q`]o8xBΩf'L'%fw,'f΍y6;m-3WY43>v * co "'`f`!m]lK`8 - ?.u6 CK`9i:+g,]l nַ ip)-'p^ȗL=I~|=unHA2L@ӝl0S2=T?礹u+,̚Ch~^{QK jUtNEGÜ9% .W=|G^t:Vx ^W{Sݐ1eJe;N^wYBabiKpmnMpi[=}Q@Ϳ[ag(`|̔o9X_ fl3n!nu`85etXoL,uákZ{G պ_xk:-foGyӗ!sy2|WM;CtX:i]j36>r>t rnPY{|5֑VXkOL?dw xcmLT7Gӻ̦c?s090#9:_8lQ_e~DQOǤGP~8b 6XQ䞦Swv1-3^t{:߻j|X=6G}&0};Al#`(h)KMo)8쯵m @k9Md vR]~Ǿ U{ϲ NS9[*i?ao''k:AY׹ufc[f~G⋼"3e/(\^drx[J6X.m!N1;Xߙ.ׁ;; 2K`NN3 lP6f`[- M}OQn3H`PuAUJ]sa|AFzpnpsf})<:mP P=RK+a4@y *@1<#'aLV Ӕ\y~쀃 Zeʛ|z HiK3FCؤ|w)'C|*,[|f(__*߰*[@7;T-Sg+l# +?e:7[/.\8AJhz)&n>Brk6rqfHؔxӦFܤN@_ނ*#ri*^jؤNs/aBxp8l~qG{1qxR{?>#}}G?T}?6&LOK}n'վlΗ7p6_>+-po8 Fþ7M|o)o!3ELC!J|"Q.L ;[u\~;<;*` Q*\C|U2 _QAU(|M֒0O7T$Uop< U Vm2"^T("V(h0( q),.yrhWJ8N )b've*Ս ?/O!1+wךZ&ɞ{Μ;g̗?xUVngp'./P&Mj[jVi}ىRoPFmS6KZ`Hn#5Vq$Q<%u767_JPDvqBn!#mևr1 4VZr HCUWo6Y* Lr=s2P%X/iVڤqSfoůgM L [kGR2+o38弘c/Lԇ!9~Wq-"t$GNjw#r:ge6nrPK-tPKjH\android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$CollectionInfoCompat.classU[OQN- \HAAHeC&>A ?em7x^Μ3͜o7q,z&p#niP'2%>ah_'򲲘\]X&q0eӰpVT=<:+̗\".CW“IJpah*+-nQ]5֣M-L2xʹrC6uisTAR Cd]mfxV2P5kQ;˙݊EU3Ͷyy.Ւv-^)Q1*!Ft1L_6R16PK<~i9PKxjHJandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompatIcs.classEߖimj+*J84@T-Z+TQ7Mx'(x+(TDEPP[f4әϧ;oޛ=u\ B!Oɋ$/L ɫ$Nd$o%G$'$y{$I'CC$&OI>#$_%9FUq)0.4C;f(0ٴk$T6ۺ~Bthj֬cNl ք{M/fL@~PĊ vwJ";fZ ѼFIJECC+™Ůvt dCB#MBN!cZ,V{W Jhf(Je&c Qg,S]VRHW /g[>.WG?jY%(/~f\\J]I*\ȶ?\;l$j[]r,wL1-NFߙ= %f9wrN;Ԥ5!foccUSI*̃Z oLC}B#|• W +G'VUV/*HrFDh ~U!N "1I֐%ItX$6Bx $7*N;u7֑Kr?Ƀ40mb#|d3J۠V"_CjCVo+c C5 CF0r8'>3ߵBEJcpRhۙل񟼓C'&Lo9w(p1xUZ4]Pa.A-;jf\Z7BfxUyg֍X-3-oEp9[2sôT>_7GɃPg>:3AW .n x\qs$pwqpx +i}JrS3K}f,þ$w96Q!5M^:ɦ;Z?R(FǭN[U?"K$Xu%OTDp?EQ?/o'k~M| B%_%GeW ,ׄb7_M?!."M4ޟxпVo2- @!%y6ËG?$7a%8=$]),qUqO") |\7mG nn[$=D. V<]ݹ_CA {PZY7PK-+O;DPKjH]android/support/v4/view/accessibility/AccessibilityRecordCompat$AccessibilityRecordImpl.class[W1g[(E@(xb+"jAPoPMl |5~(.U_3dI$P4 !*=F}iU͏}fy&gGc{% ʢ̀lLК9į|nӀ\Kʷ^b+6@٨ׅ͹p˶]KQZ+y juKmW#JuJ =fe|v HaIZN1N;zW\zVUN* \~<>B*mJsYlnóG1(OjPrwX X ^(O`\Vh>{s!ʸQD-'0gq 2 TJZvTAGuT=Q}NK*ң<f{DQ-x>V +<)Y~}k0f<虸&16k\^-g [8G|aٮY*_34 0ip4#LJcۺbSv!vf4$w"&%}dfmnH/Q+OKeV'X8EgQJkXG%]<Ťwbf0 gۀ Em$ۣm/탴pIv20qxx U\Mq[ķ%G,"P>ּ xN5>a AIiӥuھ%aZ--RkU0eB4Ѝ)#Й3ӥ`6m{6iҲ" }M;m:ycgOw

\GlmX8OzITJqU}8~a=IsBS`>C$aˆ($l<8m7$yi ]52zJQiA>pMӆ6jmPKµg\PKjH_android/support/v4/view/accessibility/AccessibilityEventCompat$AccessibilityEventStubImpl.class[o0No[)l 6` X Ei H*T XԉBd ,һ9$6`)~L`t4@3[NPY'$>MYc5YXùv*udi9".將$-)|=Jiku4:c06˰p%]]s͢NP|OPK"PKjHKandroid/support/v4/view/accessibility/AccessibilityNodeProviderCompat.classV[oUvuRR iۅ. 8&nZ(szZ]<RPU/sqc %93|3gά_\z i\MBëc5ObDZB Wx#RHIGeVKT- oynGԸ.u7MA3{˶jH5nۻEn:]3# +~*mXf8=Ҭ 1n*3oiˢ%IG|e=hMD*~ۣyC ,+V*Ăe͙ 4^=܌$e ' LjXx2 1#>Xs~Db ^h 3=ҤZ!M!+5®nU1nn0WM֠93Md*+dL^zNnaZ= >"?(S|l/84ND^!)x~BI|Nt$&4G)SW{ Z {p1PG,f+$e@G sSC.X#w0~WrU7BU `k ý!%ҽ9"㋊mza+=D3wyo1#L2GHf6'>EcO(T(縬,<9$+SՌF풠 =S$̒%` o"7PK3 PKjHFandroid/support/v4/view/accessibility/AccessibilityManagerCompat.classVrEFZi(& 68$"Xdb~8el";r F4`ف|~ OZ WϐjܡʼY-lAו '|K: p6* 7VVDzq4axI}BDt!Թ $ $#<1x&puU;% U<)#6JAXX5B@rhM@)6c7}%T&i60گ==<@@.M|+vVm bw((Cs?#' > K F"yP(#)\ Qlގ,QSX.CG+KHɼkPK6i PKjH^android/support/v4/view/accessibility/AccessibilityEventCompat$AccessibilityEventIcsImpl.classn@MbBBK  $a *)RT$*බWe+Ƕuw'"<uZ %?^~3Y30q% +1d,CfhP0 d,6/g܏h9HmLj^ɜaa=DK_FRXyA ӈXd繠ЗY"C7/4ɔ;玤x~b/\Kx8|*$ i?w#O]n!K4mVUOf8#_u~4d7 c{Is[I^>h*u-,X$N99`M?GoŸrh֎@ jM+\RFjlf:9RVm~Luzk6)Ч=%5ktI5qg)SPQS{j~K5˦avϞ\x ~!bKٷTs}¥uaEQUca畵 $pUT=\(is޵w$%(2vjjIJc3&܏Y]lgTe%".nez{W'lNc$lb?zΐ1E%s)]rmTMA1j7 P$hTa,N;=yvGF 8shLkp1{",F C$0,qS  ~EH! }PK #KPKxjHKandroid/support/v4/view/accessibility/AccessibilityManagerCompatIcs$1.classTn@=qZ(p)mhT BJ.E*C*ݍM*%$>B̚ .HQ,ywfr9P}$4tg*N(P*btR,:Ϥ'ŠK=`x>@S-oB;6LG( 1PX<6;cN8չT}a:qa9<Gx/>x<ޙ݆vmAzNܝ3sc۟y7ep\ PKW8 PKjHbandroid/support/v4/view/accessibility/AccessibilityEventCompat$AccessibilityEventVersionImpl.classOK0n7by Q3$4Ye_̓%搗&OB^ߞ_a.VJb'Jpr42ɕHBS㘌# ; . 1\kp*c02.f4Q O? "kȆJZ#|t 4x'Mwjt-2b8X<qjrӘ!F(Mt[GRRfܘҾ'Pza5e*=4l]Fٻ~5obeoE|PK# > PK}jHMandroid/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.classVmwE~n.*-AH " J-Mv0 MP'_*><;٦0{Z{Nܙ癹3O/4v1FlJl\67M,rdBp{{&V0ᥑ*ljl6!:e6wes&>0񡉏 foѵ<+;A&>6Гe ߳CL|be\v' l Hk`k^xze: eE<D$vJ5_AZkavy4,;YXt@,eO.R"+U'زyθ@o&YveR^ELJlJ-$GFЎѧڊTkvʫA]n]h 4xTu@SkmTm/T.Zɚ'׽@Wg- ԊK 7# lMrƦ{׀I5GF\rW7 ]/e,J(Dk>s`mIRxU׻WU:Cm$Dٻa 51pDYDh6Jq *gh^ טL$z:UTiS&şX!$UEEk" Go&= qUB<x\"&靉إjj<Z1bt(UtN7Ӏl CKQɕ'Ƚ ׿PKx<*PKxjHGandroid/support/v4/view/accessibility/AccessibilityEventCompatIcs.classSn@}& nR( NJcD8QT) E@{UrvG!fm+P <;;o?2.V[.(sWFMXz2',CB;ܗx>:5p9RZT4^ː[1p9~hxcZ-P+wfgvt,g{c8]EƔ\d{ K03b 5 IJ <ɚb-ӵcD\ )_:4=?$5~GY#X Et`&jgMqPg((3tQB#[AU긊LB2a70se49N|'.#@E~+ʂp=GR'GvrjqpldC+X9 ~\\ť)ûs* ?:%S Ӕ)ݣNknLP0W 6p 1O SYm& ܌ǘPKMXU PPKyjHpandroid/support/v4/view/accessibility/AccessibilityNodeProviderCompatJellyBean$AccessibilityNodeInfoBridge.classJ1'˪U^<(A/RRPOtII%ɮ<> @?+〠FV n1HvorF%S }y:)7#m& NZac*6ZT[d*~%8 `yk)cPIg9!iOYju \f8&wnWyNm3!=b۳ѹדG.4L]Xe,Mq4o-೵'_O*偒Y-A-[0["AE6Js-wP{g~u_PK+=PKjHKandroid/support/v4/view/accessibility/AccessibilityWindowInfoCompat$1.class= 1 D EB,,c6j$&&x5 Ĉ, (X Y-4n[pQ*륡.WfFeR6dFl= je*>}uF;^goYTAUJ(\d}/k-$36:즺|OZ*S'bܒ͖ʃ埰@E#]>!#Cbp)T$PKPK}jH.android/support/v4/view/ViewCompatKitKat.classRN1=!!a+)אEgQvTHbTΙE8qR.kQUGksïUb͆u6l,cmJ  ⩎dH4Uz%mFfBvCdhn)2SMq]I} Nu+9si[ʜD2jRאjNggdm!'yjZW( %=77Nѭ5%fGKK#l?}w'*exĮ<,`U 2i;14ʪI1ɋl~q<  q%.=% &xp S(|"/ OcƑk9`+stաkYw OY;c/;c8ld+R.ytCVPK~jĶePKjH-android/support/v4/view/PagerTabStrip$1.classSn0=^Cfa:(:vll?PRB?M%u)?x qV(jko$\88 2шOEZ^1oTJX+SG'Rۑj ZNk'!ERB"jX$6At#AJUx*͗%lH%(!XՁhn=uA ㆷB"nK)SKw;zUtE#p=xy G-9΁^xA:h}p/Gr汁qp$[Y Ȗ p p^3ij52ڒk\wV9Lj-cXBl6&3K.^`M:xb]õY%^܋؁IEAQ@pUT-5}تpQ*tAKZTz#R 2@9= je*89`}uF;~_߲9¹v Y3l)ogE(9yF fR=&nPn9.k=yqNG1_N1LPKQPKjHRandroid/support/v4/view/ViewConfigurationCompat$ViewConfigurationVersionImpl.classN=K@wB@*D[gnnDkQb )+fཙypI-~!˥b*Ke;Lۚpx|-M*DI:Xɠ8I {s*i؄6?{OH2۸/ffbZӅlemWoں _?G+;QZv-gOk-M)ng `HXxQA#;N:%aPKR'PKjH3android/support/v4/view/ViewCompat$OverScroll.classJ@Fck7OHny*AA" Mٲ MWPb<]|}|IIY@\Ɨ ̖RTިV zKK]땡.Οi}7!s d;N`*dG3|q}c+e鵬r>V ({ˬ%_çu&֒Okby?[m g" \HbFrN2~{|ɸ)޴r<-z[{$ōARP~]\w\zG R9'Z*_xM/ ە?hQdHg(100*z?,w8/1]/X]M/[4=gN늆4~mc42)T,dQnB5 x`d!}zΕRy_V&I92O^ ˴ie&jhL2f:fԵ=bBP>V kxBi:Yrp6BNOgq_#Na6'.HFNP|pGݜ!>$mQ83l+'@+ME,$Mezb[b[Ka} m;]Qk; ݵER&ŐNqFAu+x֔E]*so CWQxYE*}#ked'gѨ] )J)/7iK٤U2t(i3op7#- (T5,{0<" nuӣ*Yjz\?#g튣w߾& .KE\RЋ>pW}Pzn~QYgLZ$Cܿ%VQ]o :ifi*ڮ:_1`󬶜fWGZztŏ`ޅZ$9hF,b.7y,B7H.\Eao˓+?A!tUA%5&vz$'"AG1 =DVw!}il (h@D6dr929/C*YEkk 4zPi#FQANS|LNvvD4{PKPKjHWandroid/support/v4/view/AccessibilityDelegateCompat$AccessibilityDelegateStubImpl.classVRAIB]U(e;$PM>U>~edܓ|؝ٙstg_DiV g`4vH -tJ78u)j)WBSCd3"0iԲX>ߡM6MN*zSsP-y2rLW,'䗔#qW~t;,WK:H膫8 L6ov %Lhڈ6z8vrq?Iܥn qxE/sAQzdcxq V-جĝ]ld]Pt&-C;{1;wd,vC8> wPKYV;D PKjHWandroid/support/v4/view/ViewPropertyAnimatorCompat$ViewPropertyAnimatorCompatImpl.classŕYO@g@z@KzBrHI#@E geoOP}B݌8*H)g;;9^ f PvIj\6iXa@YU#kKŕlx-rrñI.UQvE*W3 f=nNW+6G5Ķx. X)\q3>Nq? xD}񳁧xs'7삾ԇxX'hh+.C7p_OS>4Αj+6nJ=?n\6/m)+L :lD C ZЏoyDYop%~YqF\nkv\`˺xŅ/|sZHT0!A/|?6ve,Nୁ d ҆NBpy`ڰH!//ooom;4K#G%S">ψaЂX1xi+ 2k^KgPK>PKjH`android/support/v4/view/GestureDetectorCompat$GestureDetectorCompatImplBase$GestureHandler.classVSWnY,QRԦmB" ZK@EA#XR[$d!n`7gliJ"fbg9oΣG?b .9a&tc*S .}(G$e]𱄫t#މOuF00'ᚄ >ZḄyܲ+&6ٺˆbϗҌbqy^Ӹ,)-tJcV0tӎmN6U^  *ӪW"mE]a&<)NU)qVHoJ)T zEQB. bS&v  onꇈZHźRj inYJ1ٟU8SM0[míj W`Kʦ" 12^Z-Xqw,ឌO*#as_/e(XCbw*ܣ7lUH͌mZa0=چWrM͈ d N 8Г  m<:}uCZi{O?jQQ8|.g8ResM] V&=qAQ}tҺx6f'$&Ɩ!k^]21 ȫEw&XXf3=f4Q׎)Ao,ChQiB8Ѥ=T2G{J.'nWIVS kd<:8Ψpkt¡^ Q-"͉ 18Z0'pHNз}& 0 ț@t9Xt>w-p?TM+Q6 8HH7iՐcރhVBZsv{xb dm=ñ'N,"4M %g`/]_6Ԗm,׼Xy1{dm#*"%B/ѽ;EO Bd"ߢOQe?PK 7 PKjH:android/support/v4/view/ViewPropertyAnimatorCompat$1.classM 0W.\ Q6Ԕ6)Ix5C.|ü0C/@ $}1X\G){Kamd6K]܍3^qB\[ҭpg\%F˄KYjX5g'߸фBo0˜[+,!Na?;zr _6PK LOPKjH^android/support/v4/view/AccessibilityDelegateCompat$AccessibilityDelegateJellyBeanImpl$1.classVQWE& YYPJ+Җ"!PVZbV]6Ctٍ_⃞c9z>|E<>/QLbaC19;f˯\z %n*S]2/EsO4 Y}KxG(XV!Zҭ(>κM{BqtRd'qgo>F~+x?6 ۙŠelycx kcm>NISA\bL:Y>c|E}բЭ%5{e0"C X;E&;MM2U ]ft琾}Yz7 )xw$cg(n ({aHr%=mv.}wu0ݳ WFN9=a{Avba`ryЎM1MwFU.AN$]ZF}&ۤ =I UD{!ٞz ~PŸP'!NR~;N4ħK5BC[|ZD+[UDog伊WpV0!XԙzԖ_*=ÏFNai_!< Z ~H Q%HT 9I0X=/vԶz~@|n j}Y6N|AHAFI]B?e&]8(Z=߈*oyRy m@%>A}F3 KtKwC(Yɺ.o^Wpfz LF/%.,0g* LݐSI95ԆJUYĻxPK\/|PKjHMandroid/support/v4/view/ViewGroupCompat$ViewGroupCompatJellybeanMR2Impl.classN@ɩ1(pFTKMEM$QP7 !w W .x>Tm!RBG;X5QČY̩j`"dl - uח'i)sR-5DXH.ݘpaa-"9wӔ?>v:GߗQq,YԅߎmiQbww.C>+=L&Hcm}n/%qo!/TJ%:ut1< Ҩ%\5>_DWX(0lac>b ?Nӊ{A;o$?DX K(<<%ʜc薓>Uaj0Fr̩{~hT!l}& {=LZe͵f-eJKe>i49V8+`_̌y֗PKڦPKjHCandroid/support/v4/view/MenuItemCompat$OnActionExpandListener.classJA Ժk*BRM<ȢPh7o6Ṵ̏3l|J,Ԃ`H'>?4E/E0fΦAYY..gU+Q+~p[LȖ* ~T:Vۍz-kps Y-W3. Sk={Y(4/b"Nt  c?Ǒ PKrUaPKjH2android/support/v4/view/ViewCompatLollipop$1.classT]oA=EkK[kZB{K!&H4ش,;Dm2ޡ`7;{=gsg~@;q!*R<#v+$$ߚ˷d:K.+6]90v-|CUQa`Sy:J ƌKږUTtl C~bH t T pg3L*F:,MMzbt`Izq*4}cg(U$F ikX5ȷd =z(#}f3X W,|E/(n>ϞťFkld-bie&CVZ&LcC! PKFPKjHDandroid/support/v4/view/ActionProvider$SubUiVisibilityListener.classAK@4=J =x$N ewҿC?4B9~QW;k:xɥh%!zzZ֮TLApR7:0֦tVJʺW l.>aG= LJ{Ϟp1U>%?xL E Dm1St; PKk,PKzjHBandroid/support/v4/view/MarginLayoutParamsCompatJellybeanMr1.classT]oA=C)]mmk,@+[ڦ/T 4.]0OR(㝅lM$s=wfOx IBQÚu O4464x&Cl_;` ъ 5iNCaQd4UJ0>Krdt{ݮ{/&uFICDӓMA7$8k#)%\ꋱSMJPANx0pv ,iHcC Y<3c9].,.iZn'zϩ+-耢yhCLӑ~'A_ .29]` s~ 3F>Ck8B^]J (;3D/N qBj5TЫx@ :PVV. xb*45:xJ΅tA%.V.VõAmV}N24џn#PKNPKjH=android/support/v4/view/ViewCompat$KitKatViewCompatImpl.classAoA,KU*hv5Ƥ4Ң&`m`'%œ?w/zЃ?BJ$kid> Be W,TpUg㺉U}I!YR  ٖēa#]LwyǓɔڗ!T;\xAnz=T-/]'~gDgjđ6vEʎI%GؓPq& eBG qfԠ~ F> ԖEcXaKPėVn*Ż_H`? bKgX{G,7l䐷Q@MK(ڸeL1v3=s85kVu0TK2̗ ~}馞eEFX}HDg p g(2ybBx;c cĬEIG5<"MR$EXk|a|XꐕtvIQSDql*}I1[6fsΖc1Y̯#1 \pb2Udt3PK k~!PK2jH2android/support/v4/view/KeyEventCompatEclair.classTMo@}/7K۴@ 4Z% !UB8vkWk'.E8c'qx=;͛ M5Ttf;45 { 'ҕStl3d^W0L5+^N:BCbӳJCg&8>Cݮd{ ՗R| 7hx'<س.UaPL䵏j< o&S7Ǽ-αbȄ !; p 6\m=ٽvNTU41"1,C┰ PWV}*&$pE}e(+Hai\u_%[^O '4Z@aa=O<@ aGw|g=WTQV[>dL 4PED@1SԙPKNA+PKjH@android/support/v4/view/MenuItemCompat$BaseMenuVersionImpl.classKo@qRR-5)R9ɪuؖN… .\āB yGyf<;~sH"cMƺ ̶nSTdH׭g(4t-Zi.6f45G2邡\%7&wnj6irnhBprnhfDZ"<۶W=Rz:?QHݺյ5weF Y'b풚a'ajj!: 1> 9-1a2&2j߹VM  qP;TpqMAR, 2 |0.m fh ]A&CiJxLtF?DX/tj6 22ǧ tt>K14Po˰5%oU<9SVGij dZY Yzg8C0G+yzRn4 ˡ1 y\4(RM5.GgڻԧI}"ڕDDbT#ocZh Wqm:>S 2n'vKbwWpV]/PS$WqH7iY^&PK dNPKjHIandroid/support/v4/view/GravityCompat$GravityCompatImplJellybeanMr1.classOPǿwmvBщsRA!!C$“M WⓋQsJ D!>===OX)"j˜\esEƐ0h2 m }9i>ox;ĩG0{$sz\5\DK nTxRm@v' (+vW:WV]%C7^oI,C8qxax/"r|aۗ@f@q(vJC1nE $ .%ë+λ12퐮ư\%Y~^۠Ž5ݷBy(h11r0 ,3xYCaakU;5sLA_Jajn2D&rD9mXdrAY]#BʞD$REYqT4,x@RulUre_"Y`*5N%TTq*U4I唒H P d-Q&Q]0vAB b V b V ba|9TPJ} 'S#/>r(a_A$>*P<#2JM*%;*>ˁ! Q<%=PKYPKxjH@android/support/v4/view/ViewPropertyAnimatorUpdateListener.class}̱ 0jꤋ.fI'=ARjҴ9~P7\yOK "DV[%IN&N&jZK+,ڶD,K'לjeeի_ .lEzB:nzPKelPKjH7android/support/v4/view/ViewPager$PageTransformer.classM@j)YF9J$ }Mmݶx(ѕ4|<_)<uM*.9V-Bo@ܘ_|9 .Q,nj;h&ePgIo;0/"t~JJZhBԳ BqL ;6P9UXAn]xPKaܹPK}jH:android/support/v4/view/ViewPropertyAnimatorCompatKK.classTMo@}qkѤM!BHP$$ 6 8eoR $M1ޔ@ix潷;geĊE XPº&n0~y"lW~Wul$ǕhH |3 ?ЈAS,X>cXX,,:cUeed`Xl$,KeIllypIOB;QSK~2- ʢ:nXdzcIw%ZjvB35z]5׬e6#t2i;ndZdз"$ӎwg264vFRsG} R;v4װ-u-9P1/,+8S_Af)eʚ\$hVwHAU[JCUS죖;Iy)+f=ؤy@H}X NиúP}`=-fg+r{^^+YY䞊mN73Ž/Y ݔo6m*+ )l)bč!V?tŏrX^hA3G9">%9NB\i<4!:ִMME3|NPҋ*>7*W&-A!(0mQUs)2222rac%yUз*fZA NDLZL- :J *h.[Ǝ Ďfg-=@F~GÑyFh-yWLlMbF!~TT r7.(wJm@ %h!Jf hC;]+)A)ć7$U 1R)7I)ްwZ͐ևw[ʫx_Ť3>FC?ݑxKOFdx=ٗA6_k iJќT'ĴFuR^o&wCoRkxMIyg?MKy gû+;# i/茼,zFf(3 &o?c_8N_ ι+TnSR^2WQs旼s17b8T.CI}WȾxUQ?PK5W<7PKjHLandroid/support/v4/view/MotionEventCompat$EclairMotionEventVersionImpl.classn@ƿ? @HK(\PFD (G7VڲC!$NTx 1X-4K~o׳_x ʸS:갡=@&L*>'[!uAXH%^O'ky:pGxhR?' ؖ+7ڍ O1+%[9++zVĊh7 +R9{Tk|o EkBlaKdfgߝtoºTeuSƝ#ѓڴudG%T <4Q7t㖉X3?'dqhʼlvf0Wo87W.r0c"Gs&%\Lyy4rLuujҧ7O%L)#*31XLDHS91J闎b`m&8`c}+\haϺ̹oPK "PKjHAandroid/support/v4/view/ViewPropertyAnimatorListenerAdapter.classKK1k> zp*EJnДmiŏIC"{~ɼ8nYl9vtP8J l=du *Tz6 sG!Ey8F2rpq-w:"m;s)![#aSO xrj ejHJl}1@dehf(.UP[ EPŕ7hb£1(0t)-ܐ{f4>- 794I<4#?|{$uE \6&x46eclQIޫ#)Z:8QOPKIPKyjH<android/support/v4/view/ViewPropertyAnimatorCompatJB$1.classn@uBnJ!)u@H%j%AHE]F&BjnNRKPcɳ̷3߿1ydirWDy`V, P:AҏxHÖ;CCAJZE(CpL cHB -v9z!V~P zAYT Cyqy ju}aܜWg>4= U,w>R~mQQmc رQk1kpI$6ǟy}mZbho"MU7KՌPeZ*RIh~Y\6h'MI%=%qlr#ͤL6CVɾU\Q{F}h]vr B֡ r="u #Aa^:tsm3s"擹֓[twp%g4xsPK$wE4PKSjH4android/support/v4/view/TintableBackgroundView.classPN1ՑQDŷ.PaCcbLHPe`qhIͅGo!AAhs;Á>826~_i{͂V 5a,BP+@I˥ZQ ,wrI K+z*aD+uY[TJ'#=3QQĐZF_N #5fw*>\D SJČnZ:E`hE 2sj3u*rr4l Z겄(Mo7 ,YǣO/Wc$Ey| 35RX%2+XLjiX ܀$7*B PKISYPKjH6android/support/v4/view/MarginLayoutParamsCompat.classoJƿI:v6˫@P(ri@ҖU &V18vd;A!VlM{` [X Eq1i!IEnΙ9'~`ZF G[ЉcŒoa 0'9` Fe$1&a\Q  ZmV߰wxMRyv.cPS;I2l]i(wu )M+E1DR4d[96r[,lJZЯkah׽rȌO9= gɜvb #}TQ$ita\s&t,7gcy2  A:WRrTK% Z#fZ3*Bu9C/c[~23Jz]")Yϰ-fGw&%Jdexpnv.o%0,;F+]{&fJ|e.:Y!\oU^*^lR]Q[>kb:$>에Q1] ӫ/UךaQ&5G35M]cȬjfK%gX3vѢazfǧgRtKsSjө9t1 4R 4FFYmj9 [,Z6!\MxT@S<"񧈄PA(>`h'vH]T |Ae`ov5Y@E,Es)D>xe \u B Ab-џeivh㧹7tju4 j'WM @YBL&&kl_Dؚ=z Z{ghI;M*v IߢDow("D>DiUFW1a?#t#hT>\bXG|Wsqq4& PKW.& PKjHYandroid/support/v4/view/ViewPropertyAnimatorCompat$JBViewPropertyAnimatorCompatImpl.class]oAY>ҭUZURJv `LZiLiIf ݁w%lg` YR!93眙9^Y:[jxGjxc[G!J8Bf ۴-Ω6d,u 5%lu.پ }UHP$nbEɻۈ*Dch~| ~s :6B;1uv $F-5g!>1'BFEݣN RPKW=$PKxjHXandroid/support/v4/view/AccessibilityDelegateCompatIcs$AccessibilityDelegateBridge.classRN0hh)P=xB .=A)(DEH\g).qRƁI v]w'([0˰JP\h?xH;B1%=׻B!Wnkw%8T?6;IuaQ+GPrO>PaG#풣.5ZVwqն[=Sn#2au"C Б_c!dJv˙W$e(4tڗ1|:Gc7j&<0oNL:VO?jfy7n;V[-:JQP1dJ_ߐaw10 Y KH})1x'XL$_PKVPK jH"android/support/v4/view/animation/PKSjHBandroid/support/v4/view/animation/PathInterpolatorCompatBase.classjAƿI6njkmlST%VBPfilv$ P5 @Ź8͙39}[JpN 9Tya̮=U≈n0k6s*{!rKѠ3ލȳڒ\ Μ!C㞒GI"<\?:TT?axLT5)B/=y\縳L"93l,0k]aK9k.; rwVwf?79RA&;3wtǼ%x e<1/9`8Z27//0 J W0M3FEX3S(! 4'`W$rG43E(*A4{X{ rL!EQxx EtM8#ȳNcnKY 󴺥qxxjiFmYE|elPKļ}PKjHAandroid/support/v4/view/animation/FastOutSlowInInterpolator.classy|Mg&?Y/J;%HSr9r\\HnbREZj_jiQb_j-(m.ڙW[T;SS3} }?眓<9ˏTJUw{w`%!뻟$tZ8ͮ3~81c+ywz[mqKZ&F'+eq(k۟։+^\ﷰu>t+jLXu[X^9(֤lf7kqB3~Z?vb8E5!تtᵪ2Ůa[]Y 6Ec{؃bjP../_=?f؋/{eI{m{a|:M}TϜWa^$T}P!i);usr|ۡF9$3U1ŗU0n7gd6 şNw(+œe1q1>Oo'${ }t xrٞ?P_q449 sed?_پ orz =MD?&P)~VAes4#{nƭ`n_UoTXQq;H]eYTVqꆺTdg׈H36YR_WyrF;;ټʇ̠LU%|Gws ĝ^e!q"ԝ0wzdi(0phd8"hYPF"-HZQ"@#QF2JxVFUZsxVEuZ 5Iڴ&ZKk.zhHmƴ!FhJ#6A3i4ZfhImZhK[# -i^/G{"h;8i{h,M]Bj#jtDщvDgd ]hgtF;Rhw=Ћ7>ܴ7RinO"i 1ct0!Nbt2t$4h&FS/QK MbB͆C.xGE>CG! `-DZIt&ӉB'NT:Ŵ%t*bL%NR0d:^2Y&3Km2 sL^\xd|Wd^5L`ɫXd,Exd1,2ױd)V,JXeMVb*5Yu&kZ7Y &o`zl2ـ&dl6-n;Lav2ف&;dƛ&{^3yM}xd?!QjrMI)1#8nr'Lq29&'q19&gp79 &p<.\xKʱGf}zɧwPKK 3 PKjHCandroid/support/v4/view/animation/FastOutLinearInInterpolator.classiXT3E*FE\#(#*028l.1nq+J\}Pq}j1nim>&>iIM}7=w=9#!'n/MHzߩ( 'E9ڟuo"!bט%](|u]S;c2fx92+M1\l̋oy d޹ɴCI:d>$xIcf"?d2ie$97'H.TH.bɥDrOOTX'$Y6B;$w,@rwbW~ ySIVO*e%n%$kV<9"k/\'yF^IZ"yG3W xH>o'wIݗWe4ž$tR7Iʟr:Oo#?'/W3|M1$?;BPRU R5WH5TPIZ|>Tu$ITIuXԉTpOIs#^RaxD2RZ&JRUMziD$? e\OJkO*7DycQN~G*q}YiO"c}G`ѐmF,Иm&l#6AS6@4g"mlsdЊmlKa[-6h϶;l;t`ۣ#a;" F'6PЅp6 ].Ɔ;=n`'lz=ћD loİQ00H JIJ }>ccяl~@6 bb0Dv&b(;$XءHf! Mp6il*F1MQHVfGa hdc0 8d6dag31D֎I8؉NB6@;Sl9pSb*넋C>; l>a:[4d]vf3.f0}s٘g6s0_g.י:P},Y: ",Y:`,Y:˰Rg9JtV`J)A*|ktJVCY:kAg6Gl&2l)Mتt`Vن:۱Kgv]ثt`^ه:qH*t!ѩ@a9c:9:NuG8S8suj)9:9ZssujqI.\KstU1a|ٖͻ?PKxD PKjH>android/support/v4/view/animation/PathInterpolatorCompat.classoOPƟ 0E6b]?Y+s5bֶ72u,=OO=yq,ư8rxc9䐗*/UA-a o!/"{VSg~i7t7 ʤ-5y$#^K /t,Tݎm[^lBrS',S=^bzc[,gjۤ4GW3-U~9#R-`Uk_ȗ7G58e`: QnΉ9k׺>OKyq4,ds9 L iϰ@8+9CJz7ԣƹy 6}媻a4ko?T+G ʕJTLSw :-Jͻjdը%آD}u.w ` 1AHLq&iSiH%z-1yfg"ZDKCKٰO!&Q 4.K]',#cKq|Mפw|Lo9Xfҷ l>.#rJVd:QXeq߷jTWH)J.~HMfg{UPK%l'iPKjH?android/support/v4/view/animation/LookupTableInterpolator.classSn@=NIRBbT EHRXEH0v6J|@7aHHT oA;)Hlܙ839MerY4FIAz>Vد) 5?#*JKԵmH~ڶBnG[—? dp,ߕyz9)9tGN`opp.sq@V!MM:^(c9(uy 'vĖ,ns' l ]S :BFG4:**x*-ls`>o'0Y2s !D[+uͧe#|DF>3iz/2,T@!Qի8WS O1b6ƫKWo-*ɑo Զ@Up$QyS^-4M/PK%5DPKjHCandroid/support/v4/view/animation/LinearOutSlowInInterpolator.classytg'|ڷM #BbBخ$+ˍDRJji hUTKQ/tFNgF==|t:Ϝ9gy~w顣Jn-uNVM- }Z‚lb* ۖ-aWRŒN-5oLŽ~] Abhr]/ޱbLk2?6,)kK+XDbU?HHo,>7~b]rQ[ĺ7bX/뇤b=2ib!;]Տy(;zh[D=hEYt;kENt9=D7~,YOs\˒xѭnshEkyPtڢ́EwL'׬;sU:n?]GqND\_t'Dؿgg辿:$:l֍F'=l*:уcΈ':jl7Ek*:Ena?=\G;#s#O'e[z /\tJqEp狞yܫ#zbIѓE=M>`ިCk.Zi= mF+hB)mf ӦhA%mVZӖhC[?m?ӶxCm@*:tD; vDg 4]hgtF;zE{7 }ho!G EB0©4 1(:tbhbi4 Nb;tHq0X:X$0&b"It&ӉpIB'#:L &a*MF*MA 'M4tDL,D6tBF.<|B"Z3i ̢3QB1B)-:si)9o2Xh^7YE& uaKLcxd ,EɛXn oar4y LV`Jm kLVcXgMb:l4YwL6`Fl6y[L6afl3قwMb60y;Mcgn}^=gR&{>TG8`RM*Qer|j*4TA|b1Op0>59c&Gɧ8nr 'L>I8erMN)59"glƳ6V%kӏ^{*OPK|oVv9 PKjHCandroid/support/v4/view/animation/PathInterpolatorCompatApi21.class]kAI6nj5M"zWH! ,lffgLҫ?%]39̙3?D{.CŘ}c\8b(M|ape?dXm80Bu{y2Q+a֙1bxq_IG$JWD_|!B'cDF\Ku. qzrFY*䚈ڌux2= y(`V붰@Z蒁n]D=99}y:'],*[ҽ^ <%,7Ucs~a$G@(hqUtTVf)ndY۱arSv1X1Kva55azKC3 ȻscUWʴ9_%s fiƬn mQ@Ueo^Upfj\v+^j/f[_L^1=udѬY[2=ruSPۣM "`2WVn=gxX,gQd 9Օ v<6,hMyfYI2M LF`,WqKoU['s;v- +ͅWb)Zrū ˛΢[B,Ԓ5 pb5Y5 ZP{#\ OJ؂w]es4pk_"憁e &B|FpAx[ \A 8i`l$"#zNkj|~*ДJNZzϮKp[d|fpWM.ؕ_7 kִKK.N(zAEh!>'F JC~|h'#L#eP"H۳ 9|sG&7f:#z_퉪/!P?uE`PQU->pdLo֔  F4k$U]jzOYҏVA<6:}X-G1q !KH#^Gu6ƛy:9$GBԎ0&>b d({c ݼ06u$uqܧH)+k"$~đgm"T"/v`!Ͷ" ؕzdldΘ $? 2y_-x;y#-**/ij*r A*C=L4<Zzx=4cN,Zn՟oՄܱ~BQux8_ܚ2^PcjãYO'ZL@fٚX F<͓vS JNFHQ^ >k}lH9M4&6/ّ0 OlEa}_&,HW$\5 M ڴ ͪجvufݪwI5qvMRR;U'_Oj%BAeKPK^X0 PKjH:android/support/v4/view/PagerTitleStrip$PageListener.classVKoU3NM⤅HjG MJ }838 u["iło`,qď(rsw^3ˆJ0ưF0U[ t-5q˺XnQb#׃xA۫m٬X!1x!Y͆mn,/e ~7r{pFk1'J|yA)&/v@ifdfM >AH 6m&=Mq8.K C1YtTX( #-1\2HܾpRV` *$&jM?7y>`yXȒ0; E-zCoQ儔{Y Ba #/z,KPM$kAps rCDj:/D,.}&1a1!ePFESװx[CAMM=> KfϬPne0Y>FDWdGuJÎ=Y}e=$9t5.i0nQb6?ps:#{KԞ`;AQ?шwms-|O5/ä}"q[o\z0S^slj/d{ҫ*ҹՃ }0Ơ *hT:}S֧=K%iA_TC_N9Z&Y2}s#<^l}njC 0U #=ُ$p ilcVT% %,ӭOQb+)F</1 L,+7l%Y^􈖻a022AD@j?S0ձ]H{X%bR Ch˸WG!%HRH !Bo>.sv!O+ GoVF?['sUf1ɝۉ?Kˤ2e^,=gZ~TMf$P hrx30/PK ! PKjHEandroid/support/v4/view/ViewGroupCompat$ViewGroupCompatStubImpl.classT]OA=OV,BAXcbJ$ AR-`\Bt;)ݺU~>}GN7 jxع39ܙ痯܏$i 4XJCgHH[s]ĚS ei^X*;&v+uLcݕ݆5Ra1dJ-5{2#4zztÄcoE'+ҒzS>v܅?I%/a[bHʠ%T 7:$Z]NMHiL)# Иim ;HJD.?%1Bη2 ASqp6Q;+*}0Wl۴Mpㄠ!Y()r]D2:E^4JЭm$tʹOh/#مo}FXE%kZ%Z\o+QxV#8(,u_T/PKPKyjH*android/support/v4/view/ViewCompatJB.classUmWGlc4hcljShMӒ /KZӗ,숓o?CQ=3Pa x}_4p x{B( !8P#!X'B|'DM1>?>b.;&S5]Ϣ+̥-Nnlkv xF9 WL sP!zAL,fD| N H+n?2ڀP7Khˀ@ x". q&` y&)tEJ.%'K8|cFyaܴ=L)=5 ;v̰I:4 Ncc 0oSkZS?,%"ı#LW+Hd&/]'mĬI]ВxT@ֵk<trye>ҁ)%ek(%s6!jR@-47;U=e?;&{L=#k/Jg,ځJvA[lUzdB >G8ZoiZjnՐFLnL~d%ixza{HmWP䖃Gܮ=FG`C'ڪ~RւjWUnȴڑckϨS 3i?PK?  PKjH9android/support/v4/view/AccessibilityDelegateCompat.classVVW$A5D4D !ar#L>D ]ZE+m]Ե3!z}& Fbs?* N ن^A?Rb4+Ĝ1/DV!>bQB|ֆ%< BH4.Ia!pvLQe˪ڛ\ymsY4)ܜdCO\Ji,1Fp}Ta&!9PsQX(ݸP^s5qGBcX"651t%rT|tfWl j.ZFU]24ljgBy͌qc(MUEe]?9E[e!/-6n3+AHY#qE<)$dqʕ5Nc+%ymD;RT.LYMFu  3`qU/= 4HRfa@seH7_uBL4"t4|q)(FwO %hcLcCqa벇{=e*ps0X)j GZY3xQi9jzd3o~;v V-+krFQ4@J%Qh$NcPNJqV!."/QZpbtGzOf苢k+rQKO&c/}8%&NI:$'i LUl,k+4E܄_Vpdـ݀Qv a0](i ߏrΌ%9b }haJ4X lA,YWhtPz>k5ЇF_![ѯㆃ~\D5z?컁u#>w[5﯈u9=X-W7FkF_cuᾋ@Y#YF _u:БiNS&g$ R1v76ڛ?rB/VBjp#՜yIp1q؊wBg( BoZ|[hgbEǜ7Iϖ`մ [Qhea!"l%cQ~*>8IHGt~-XPKcPKjHMandroid/support/v4/view/GestureDetectorCompat$GestureDetectorCompatImpl.classN@ƿ"8Ak8 n l6n|ʸBd& N< @XEk=l%aݙ*RLex$\u{R`' nZ܌r9YдDelSmCP1,$B5ׄ-k+/T\~Z ˭DܮY<4 ?H*'߾VnvB?^5!+2_[nnvZpq'c>L=3-VpǨX٨S%;[PK70kPKsjH5android/support/v4/view/MenuItemCompatHoneycomb.classn@ϺIܤ.#- JVXܥBTH.rqVV7O* Px(ĬcdAXx=wq\%<#밯62Ne( ^Ph`XiPO]}>e*ϓR 'm"%{~*J $ŵNi+ yVSMEr~ *dpTo d20TxyTZ 4aP)CğNF80`sb$V^L=6jsGWUkq(8tA5\1y yx^ aUNyY?,b_՛| ,O_`_haab)KnáL ;-Ky+4aY_.:a= %Ji71,|f@½=C"]YҾkrItc(9~VPKuPKjHAandroid/support/v4/view/MenuItemCompat$IcsMenuVersionImpl$1.classTn@=;CB )my0%niYT*)HEYsQXʂ?bD`Hlĝ$>0Kw|;g̷__X4Tܕ@ý,2X澆*޷\uˆ{<аPڑ#C:Q |ݵ MaEu76zw[o;d(4PAHFwjNqgI û<bHE3䚎_m.E Ma[n = &* `x|U(?1]wh0av.\C=+z͟;r!S'\>ٮooGc:.᲎4.(Y鸉9 4`IM̝# k $Jcw8@%Ri&js/a&QI<]%ycKΰ0titNRPy<y$0E+4ڀ2*#'$>PHU*(1y G4Vg+T?#)6Ay&{:Y%K?Tdt/!)< bP PKlG$PKjH8android/support/v4/view/ScaleGestureDetectorCompat.classU]OA=ӖnP*KUBVB(hPK0bv fw[O">$1Y ZLlxؽs=￾~0'*zB^ |ݍb{*H+UvNAF}0hN+\DML^vvνWM%Nr[ѳvB%mTuQێ73z5t'Q aC09I,y*K%mlm"%@6cH y *?Ӆ&vaTvKbVor[}}{>nImn4tEñ-!@4ҁh#:)װ-)0\({ղp*!ɳk4F0a :n(x1^ӯtc 5+#2ϛz ei99yǀ/5 \yY.Q7 d70}kBr2*V!dP"y:Y;I}EAIpQ0N6*[qAzH )oɖ|Q*`WWq][ʑd<@(OmHR[X$N꩑$COv,6fP }H/JT躄%H Wxnb[~bKLG6E0E\^EzxyDPK㑩(PKjH=android/support/v4/view/ViewCompat$ICSMr1ViewCompatImpl.classMK@mӦhD<  % *=x۶ ]MOyxIZZ( 3;3ϻ~|~8ÞE (TŐ JA1ݮ`Zw~[mS$on/8=0f?mIb}g3 i TU]5~h Ɛc S3zoLT(<9僚V `bf ** :6Ş_4,YÜȋQ_8C"<5.]>7#î>f37#QWOǜ'@ zâ{zu-zu-z%zu=zuKFRM.׫Im?*֐^$:dVފ"շHf*YdV19.Mm HliǍV2N'Rrpb|TD$*R&A2Mw)]! 1⋳'+TNhCIK,i-ka?{8: 5nX]"̭LP1T"D&&rLdٶs$`yD*&}Ҽq[cIͥ*bRZ[w_Oh}Ҥd6~"Xlc ޢxǩ9GmpS}| %ltJ_I)5 c(4z*ՇZ} 3)dd;Lw"><жáVЂ:y)ːs܆q W" ynN" y=7r y3#ox+#zٮg._.n]n=^{{9!D؏8Q"xyȏq8? x^cA%8ʎ͇SEeb;0Ǧб`[,0͂dY)aV^lk 537'XTm~oœ`H0юb Q43́Z-ӪnCB=ZF7çq>YF{UJX_WkY3BnPڈށ_}U2܀/PKBgn2PKjHOandroid/support/v4/view/LayoutInflaterCompat$LayoutInflaterCompatImplBase.classMO@ɇ !B @^T@EP},ȱ#{'~? 1OH3;~癝Y5 ,ZⓅϘ˼& &/w&!T:$d$䪮/͚ DHԅw(BW`Fs#\UmU{qR L(<{|wkf PKWBxPKjHWandroid/support/v4/view/MarginLayoutParamsCompat$MarginLayoutParamsCompatImplBase.classT]oA=PP,**Z-ejjU  0i]2`OhL|w͖aܹ{N_c=܊ciUG^ǚC쉴!vS0*oPx$bnpƕ3K!UKZomgnw[nwgܡdٲ6BT+j*[6 5zV&do VuruQC1hzngL|A3; )@8,դڜ#1Pz33*+oJ^ܕ=1tw$ܑJ4\i[t8S'c=1]/jx:f0I{ N]y [-c~@2jϐ?qoM)h<= %[4mԪE "Ӡf[dٙ`h!Aswbd?l,1Gd5!p[?gz,Md4".4(& K?jI؇+pyP4Õaĺ9u)`]Հu#ЫlZZxmsLȆhxu(RP@ԍd{'$pG`l ="%NlPK*pPKjHFandroid/support/v4/view/KeyEventCompat$EclairKeyEventVersionImpl.classn@8q\ )miR 3 T*"8$Jvvʁ 'ޅ8<bq M KvvfV09a^-X2lb!Xz2\cH/ 5 vx%K;ܭs%wl4]0Lm8.8/ Ho ֖ Uuyr]q|ٴvW))U@da$ wwJP?BURB |r2a%B2< 0T'QY]엍= 0ǟ!ӌ&Wnbx3(qdl g-b%!;B1̜Ǥk0M2QYM'et{Wt &XqLXU 0mb ڐu;& .Ci`ΝAw@M+o`_IItd"iu0K2ݒ8 tğ\d<`6+ѹ/TsOh_z0`P04\15]*-OGHf)BN'lnRv ;d-F@{iprp#F1PKkZPKjH:android/support/v4/view/LayoutInflaterCompatLollipop.classSMo@}M?M Ak)T\j%C+q8.^v8sH9ց(jj;3o޾7iU ܶpwOO 쪾Gw{98y+ Sq+#:w;?!؎Bх=V]ӯ M7=y}QR~aWr*Pm֩x[XZ(:lQi=>I ;/4|4|-s ߊi/^6֖XG%>{&˲_Z .vXyHA' z@v>ĖVŹ*ԶVPAIwt ʕѥ5: V|1w0l$3m* E~{|dWVXwIu§0_(֤ VԼmWTd/Nո}ذrk  G:Moeg]q>W@eA {TS¨li#GQELkFϘ)=q9H;+jb q%B?R>W>v=FlⓌq 2\Kp3:\-Ap"n X{QuPS]ֆ~l帍*BT@ #ǖr oa8"Uj8o/Dt#n"6?Ji&;1T~7U?L1RU nO?ۂMPOCIa9}eWEe S"0 Ϣ1<&'PhƗG,ZN's{B$fZ&,A`W)x>#F'EvQzR،QX2=ңfLn)=^nYE{}\>z Zw)>T{G8p*mv ݁;?Rqxnٚ}!rюб%wl 8^)s-|p}KZHCdOyV(CL,'xng4q wa¯be3d]Ȼ%i{]RV[T+몬,PJT=G"LP٫T'@e!Xҙ^0kPK!MPKjH@android/support/v4/view/ViewPropertyAnimatorCompatLollipop.classNAƿSڮ+?EA[{W@LWhjݴf:/|xf!`\J73g3'^Xdžxifn啃M[⁌yK]BX BbGOqd" -&7rD8xq(mɛ`"ïAVIa,(}0E2Q>3ZģH/e우r>0јk6aO9/v~Tcֿ331:hxƎC Me++U4 {BɞD"> ¾!,eIpDϵ7f}4PÏ~' vd3v7Ch}3ܠ5bs~2gldM#VrV6wӨQcܯ2#+/s^o:})8-/Av[t0d 21Fdz*1bA +x%?I6[<{LF2dy lWR hѬܧ:9Z5j ay\%9C8Hϰ4͘0`Gy E3AĐk^Ҕ25׼ϩe;PW":^uXEF9KcFF,ax qhTs_~~8G}W-i"1c8:*JBNM! {!?fN,J gseW0K61uD}=ِ9ǫL+WfdL݅~_On&BY$:5AqK,T"(I,C12K jܿLĈv 0PbX7hém cZت=RavPKnVz*PKjH*android/support/v4/view/WindowCompat.classN@ϥ PA~׭1NCRkIH*Mk\6:tHs2qP)ht9g3;:4lU6ٶ}Vvf@BLHz,LMUKc>Duo,Vxe{%Psxt0l =ygCE~,o̒TҘ_s,baɔK~'xyZnd/Ol*J8kzZ1B=p$ΐC]QNʗ2KVC))-^HM͕eB]7J Kj PK4PKjHRandroid/support/v4/view/MotionEventCompat$HoneycombMr1MotionEventVersionImpl.classn@m8C!%,VB  iYe?Ge3c Bq *d=+SCjXm4| T+'NG&h4KFcީw@=a{/6J /f?M~>:I t49 Y0%Y8~|0"D<⫭wZtv u 1Jgvt$'ڱف,[V|llǽPDھ":'?pvOhPKjHBandroid/support/v4/view/ViewPager$SimpleOnPageChangeListener.class]OAS]P "~` 6 n4٤$5 vioye?e<3P xgϙů?UKxy1vʔ;$,5 Qی%a2iz>g1Ԭ<욑+bN%lD^v$NdTd' 4cZɵ]sƉN&&w]2SK2`q &QncޠvR?Xr៴w|lcC+Rkr<4j?;yl}J4ɎzޙʨhZ5ab񌄃|Jzr)Ey91g {b_A l1h+. P}|UX+ӀƷqh:J<Ym`9Y4/F@lD OB6/ 3㞈oPKs2PKjH,android/support/v4/view/ActionProvider.classV[WUNBX VJK@`:$8t253yU}?֥Z.xpg2&,}9>{{o}+0c(:v<Ӫ\эRN}x7lk1q?Gr_1lJ1|(!.l%tfp[ ʲA{Zlah =#۴t`2{ھ5c'u,a3Mv4YgHOf-SS9)2wEf+fvX4-'? si<}pB(7 ! ,0 &5Ly5# Z*lqkMҹif ή Fqs'vS!{ 9/t'L#mqeuBf2q}I1)O&ϊWQZvxa_=mZݘܲ([ NJm2D %%߶[EԶd-dE+zu1";|[+N9;bfW;d m>^,^d] 'ZJta.S5QF6wxzq&WțLײfBqvJ> qMxER ^Er¢C rX8 GvIyL7j;驚j&.7TરUM=.Y\=4KK*ȃ+N U>z9N`D* f;BJHU,pD\-OБB{Jǃ=ZRCc2>ck:ҊEnr9W{|_ZBk 2ɊBQZڶ3yށDv>aDLeL1z(G}b~98D]D?0+r23̀|~tqKg`?"u5h@_"I_emapO䅂$.{)h*p!W 0A&痾2G{9BS-7U4WbiL`&XM`|_H`娵 Zk:y$w=Ah_t =G8rS/h*GH8@bw˿|-qóu${z֐E*`-,Vq#F-rx9FXaցvU`'(%im=' li1)%`h4e^j͖91qD?h9ihб!]OsJ]B 1&B0K!\\.ieW(iժR=re{'zBQ߉PPK" PKjH:android/support/v4/view/ScaleGestureDetectorCompat$1.class;0DgX|nOdAƎl'܍p(5;]4z|fE"Ą`J:WLRxΗ9.̵~|$W:Uc6Y.a.ufM W^TsQ|q Vk %cGH>O$Ka zBjn;q'%7PKS`PKjH/android/support/v4/view/PagerTitleStrip$1.classA1E9`/<,(艄I\x%FܹŻ?HSdloWJi&Lvb}p>VKSh땩6mbHtV7um]hċc:'0Zn ,4\Mau<#."%NЋ9zP޿PKxPKjHXandroid/support/v4/view/AccessibilityDelegateCompat$AccessibilityDelegateIcsImpl$1.classVKSA& YVC@ hEg|F#" UZaٌdpٍ$ZW'P -QTuاbT 㠊0fh."Po,PwN5vZ/ /TCG~߫qYcnuO>XKwM&{ׅFOc$1 ,1 g?ВIx$UwA7vAȨ*0D( .# ᱋[ Yvz{L6j5]/F xnWgG]26T<= B]! `juN̊ePcʅMV`[ vշB#;~ZeV\WѕV>R;(j5[B~Πq}h DZfIATQDB/?PKt\k PKxjH0android/support/v4/view/PagerTitleStripIcs.classR]KA=cnkj1~X<ط>Nvqd3f'ѿB?Jzg i ޏs{^^7!T< jֽU 9C m4mQֵBg?2D{ZKJEɌQiv=b$a~vE?r" ? ݁"mkT³hd|!Ġ\*;Ϊ^5V2nuPhT2wM3̑ǰ6vUn!0|{[ ^zW^cqwfoBiʜi>r[F_ ^ߞIv|l#ͤBG0B8eWLOGa^]WRa2I~<"6%{<%%ϕ(7Np)orըv/2`%Ǭ걌! PKIQFPK{jH4android/support/v4/view/ViewCompatJellybeanMr2.classN@ZA7Ԩ; I\D i6`|+hg* taΜ?uL`I2j)iXѰ0y$]FŐjxmmJW,\sˡLi@A2.^m}/ٗl| ywςC#†#cbz Zeܿv׼vHxJ c cf?+DIty4z9l0ob st1/[!7CшZɈme~'I/ )L' *YNkK@3xNRW>Zeb6#/AEG/D'PKw|PKjHCandroid/support/v4/view/ViewParentCompat$ViewParentCompatImpl.classIK0_tuwr<@iVjRӴWCItpV!k׷X+`u+qX qʸ":ܗ"4`ι:TPQzc&T:]O0$h֖چ x?$׿ &;33`?Tzd%]Zt ~Cj]1M.Tp&]%C(ּiX+)#vhJ X%89I `8ǒB>[ZxPKjHKandroid/support/v4/view/ViewParentCompat$ViewParentCompatLollipopImpl.classTRA=6  ;|(F")(*RXXCf F,\]sGYL&aP$HrtsNwߞG&8qxx$S<0 ҢX>XUjVkZ9~dlvgmi{|sg_3L•T1'YL6S|-|s ,gYa9=ﳜgy9 ,ϳ\dyG,/cg,sK,u_&˯Xby;vƁBw{LwLE\o@$8݁pX KS}`Χ[Cm5ZP- 6dKȮi]Q%ԉQQ]Қ\W"; ú3d5GBߪ5r 5 4IH+ šd%ٞQUeUbE^PYJ_C@֚TEĮWɁz5o&lh&p6hj`@µQ66J?cm} Z!D\-!]S;z wVNHr[P%n <YzBɩb_͔0 :—vPW/C+d$mdgɛf}"OXʚTuTAlͼCh+hhk!};,Ce r vJ*~%o=::j_TmAGRԈɳR4IX d" ɏ'hSɯXF1ci:ߧA-2 K^ԶZ>םZ^,`ܑNSx X5q_$`:ܮ̎i5& GI^?|;ĺ?~ A>-rr%TeSvzZ9[|3[3|YՉuvL5oM@o{Eo1\q!XgK| kqUSV]]K); 13*PIUtgЀ{_~i cDG鷙64џo1Mw/E$H4=ˀ$[C #,Hf>Ԁ9ѯ a$YЎHo7Mn@"z}$:״H~aʼnb'17*ڳkzlvJt# Jitx6M΀>s^!H4}} =D_&Him@} ] 1$.6Mk@?HCD?,H4=ǀ~O ~\y?K L U_?h6Ϯ=ʮ{]`oDYָpm6dؒ1ؖjGf$@38!~mÈ9.Hx$zz >)a<%5I7 3Sp_Ћ"mVLQ j䒡# YzjkzP>$}#gkV341 8B =a::= t[ԛ'QPP98 +^5&U̾zWMrefUӨj+3]NU_ЫΡn#LE9IpXae?ZBaX ZAYk9Fˑh[w]bԗ B>O`Ts#fDL)TQ9TN"*P9Jwr PK (PKyjH:android/support/v4/view/ViewPropertyAnimatorCompatJB.class[OQE `Ze* hK@|<Y,'ٗh~(%eι7sf_CGJ,t#-MFS L0]wʼZ Mu tf!lAqwC˴"/sߑp3*bX-S2Jy7O'*/η\ N*\.3G p m\#RhR31]y,JDFd +vct.DB 39OYqsm,+o? hqgA$I P]#ooVZOPK Y PKjH0android/support/v4/view/ViewPager$ItemInfo.classPJC1=˶ \hBwBBQA]z)II\ .?J .f̙3AYl G(ǖN؂ sWLa%k$B\D,;,J; ;µ'=BP o>׏tYcZcai$P亝0cuh%fJpq._=وawFAu;"WZ}B"9Cdyg9Eޯs;Z:Ö$!k剒>)jqGrq;U ˵/xnEO(q {-bk\HZ(,ۨf0:II}t$;#(Mp#G[Wf-T@_ra&&3ybGHO6wux:9䜄L3SHA>zL]15M־8i4Ë [Z( #><|5[œoPKkPK}jH<android/support/v4/view/ViewPropertyAnimatorCompatKK$1.class[oA-x/t[11&h/0iYfc1C,Cm;s9s?~x ?ءCfQmwTcp<22BaU;ԲGnWo})> BJ7:ms#C K KSw2HoI%KՋ4`Ht[0RwNSy3 b]xࡴ0ed7G ;]nvv*(r f(U~<D\\P>׋!A–ؖ68}NWZIVvPu EY9̺p`20<* @?jmq1;#3RlN *oXO"mj˚}R\Z:6}AP%h*ӿ!A94罵c0o _c<\G!v<p ,8l{GD9B;R (XBǖbRq`=$e\!VWckt" I6[f(PKmk PKjHQandroid/support/v4/view/MotionEventCompat$GingerbreadMotionEventVersionImpl.classKAǿ_fYVVPA C^"v):ĺ+.QUBC;;o3o}x}pB%M[؉wJK-Bt%!PR_řL%דdĻQ.a\=ZncFZREHm[%\WvW;kў9*#%)@'BHw u*JNJ_r?cڙNa iy?ފp6?ֱ?o>giBinv?!@0X?8ϱ yϩqXHSO B376j+-ԯ2CCPKuMk3PKSjHEandroid/support/v4/view/LayoutInflaterCompatBase$FactoryWrapper.classTkOA=Sn[ւADDt["M05OvKv%?emJZ̝{3s'e`EKez f;ϵ*fï]O{+%Q`Ʀ|!]K1,gܛ.2snE0 ,Gl^MT-s=Kΰ|k5mׅǠo9r6o47 9Vr7t7"]k[ur O^]M;UsGzSvxˮ##͜À/-ܐ^2Ă*p;TVn2HB?!&.ð>-^Y-Հ vH!c5긌 eWc Me<&㣎ihx°$YiW5M%/\W0 }JBїy#D? 9~E3B@c2ȽB$5BV6Fq qB%i'ژEDh|AC_:Q YeZ$M$1^ ؒ): Sos<ʺWd65KiPM$Rd7rBhsRM Qn"bT{xPK@ qbPKjHCandroid/support/v4/view/ViewGroupCompat$ViewGroupCompatHCImpl.classRMo@}S$- !II¡RCrGV"^8p Vfײw*(%**jV]{^RIsN7[=B#AH%JGҞ`| ړeד(0mjR" I>v5yIE:6؛[п 6RPʾgD7jm|ҷݭmNYbJbM ']CI^{0 XC~tp@8{RPKjH[android/support/v4/view/ViewPropertyAnimatorCompat$BaseViewPropertyAnimatorCompatImpl.class͘[t\e;s9s9Mm\4-$4M[4N&ɴ3g3iSTѢ x/PAXRĦȒKWY򠾨//@Y3'tJNaZ|{Ng؎7C F<ڃP㬨}Z>y>+)8'è1Q<B5('<)fICW|U? AذOGÙTZϘ3F-M=Mk-93hյtC񘡙N[z^̤͘-ӬMP#?3ah cqsSWJM޸zfXL%ME-mkNy6/KSez56-n,.fS:KL57"yb poZK# ѓ|DT^ddjZҧgZ蚺kIJ-VSYs[!-V nl^ƝYvUDr~0.UX^E *6cAE ΫxϪH~³ ;xNx|7qE{I(~U/>TW 6YwFaJ^U *j|,jp.0жt܎imms}Q*u+*} *ж ʫ8uS&5uWc1kFU]w} 4hu{.};>eq(u%57–K*O{]Z"1?6J@ys6uO:+%/پkkke؄~r 枛ĜP,\,x1 A=l&4[}sr|ň/VqB+ֆ e!n7ƦYx焇+2^Plb0`r `@)]nEK.ݘdK"6*b"6*$X<"_[嬔[r,X\xK̒BS*]W^_ AJ_<qXQJtcO']NO1iCN @Ss x)EpS+ x)ps e%~3;~ɀ_v 5w3<2\[(}X[zNb]F€q 0jycIGU^Ӆ1Cǜ0C5 y\b.Ďj[/mB nG(6II?O)+}ڄ{JiwZJ5.BP*д*|qBkк"B&lB&!* s%8t2?X-ǒE #.KP}\F$(Jh2B5PN^Fe-TA>W/&儆71ÏX9zAzY,X-l:zTS#R6R3ꉗA-A68J]8N"JHniCfHfHZfPSaD{'ا>IwỂjIlT cZZ˯F</bջk`#/zuѢgCaѢ(Q+U'CF~m'dX q;ko߰l ԟצ²Ц=41 45r{(oxclQQEoEuu?κb3ih It}b裩o;rJ_ 3rS%|*^MYk;0qI201mSv>mfgX06*rZnkĊPKz1Eew PKjH?android/support/v4/view/MenuItemCompat$IcsMenuVersionImpl.class[oAi7T,VMX Dߖefwo>W⓾=gv ,)p̙9ٙFa\ , sI\A%%\fU{lЫmScVu[Mmn3!xSU6?k0*F3T?촛z"75j"k RE 8/T”k޲ U;iXNVuIPuxbM,0ǷLʿ8kPC*'ÔsHT%aq*U14M6m\EbHy{#^EPbc0FU8R s#*L(0]wdզl62R7:﫢3RQLcF•(p"0IaRXpϬk GВbC&Znx^S%uo^cfp&յr!FɍUOb4 NyH]#UЬje *sr_#MFCPF>q>[.|"c7Ncj9o32B_ɸ_˸ 8o%$ {svgd{FA$kG𣌻هdi,@A2pZB gjW jXt*NZhL62&|Ɯwz) 7j85vAQt-5KtE9V,yZbuGݱL۲%+ul +/y;=#NY rw8H g|D6qaԮvz;J~EpyE$ h#l!XHN$GIӷHzDDB~" <"W 45q.=aY\* Z9sh6 $f1ZŵKבN2r8}>7 6`_௞QAT9lƫ|6* 'rHHDD. *Dn&|8acoaCh-m$NC>ýgrL,p'B4V[0v4]"q'ĭL]Hk`GݣӍD=Hd]ڗ%^<[,O#"Ur'rrWKS(׮R"~'H91 UxtF8xεڃ:/9b,2UZ^Qvi !-hY;Lu#I^{}*PE>#0=0q<ɓMz5hoZW:SŪ+NfiG}~ZRCxyE`|"8wt5P>FS<ϚPK V7PKjH_android/support/v4/view/ViewPropertyAnimatorCompat$LollipopViewPropertyAnimatorCompatImpl.classjQƿVSU[IWF JJD:m<ٳ=Yi wJ9il\ؙw7pKX5h%.6[26!Wv{v3aGhiɼy.F[UܑZŁ6w"FlmEQH략EJvJ?SfAkUfF( *zJhU2tM;\ f$JcB*>ϖٿpw{zFw{X 38YfU5“ 4-aE>/lʤOX? |RgO6[́l6鼂DZ/1gOV}u+?%p^j!F89q AQ[_Ϗ3Cx~a;8;2GEaW겿_PKc5PKjHAandroid/support/v4/view/ViewGroupCompat$ViewGroupCompatImpl.classQMO1*^L{'Bl;!%]]2ήQc}雙^ۗקg8l184G!v&,.WaEN>=.bDy~yf`ߠVhq~De+԰@b`oa~Ǡ >L X(¶ 5 kXgSee[~!7j˖Y t5哣lrhe2 @7(* 5#&nњޓHv6E\uUUߝ1p%%! rGY]*Uk+"KAuX':θiG4JJ"%Ŷpd9\K?VRLlLͫo[JtOTNT 4VDґtd#3bKYY"7NrҜR'&OE91qPF!/PK}UPKjH5android/support/v4/view/WindowInsetsCompatApi21.classRA*q5  ! ([@ϒ,JwV)ViCYvnBXfU8ݓ_p k1tc q?Y<`y21<QY=WVY*X&XnLS3ƧǟM9O#wW0H_{IQ'J>!@@, "34 $6 Ьٍ38 أArq=9p,=?6#v|vR=H!?>RZQG|Nʧc^~Bʧ6/?-3~|Ja\q ^C<6[lM6"LM_@Wh'%^lٜ *U{=18B94]3Why q7kvo2ɼǟkR~򪔿wz )?L /^O$J Cdk [-py{PKBuj} PKjH]android/support/v4/view/ScaleGestureDetectorCompat$ScaleGestureDetectorCompatKitKatImpl.classSMo@}v0~ iPU@JDIVű#~NT(Ĭ@JFp}_[&4L\Ś2 \7pMHz2zLV\ \KzEqygw G }>DŁx""эNԔQӉv`zYoZ |ٳx0nC)>:a1Xv'JG(VZб]{ku^oW;#ڄNP]Zm@خRf]Ax0`q;Rju:B [8y lFxzD(.K/X~5ˋVL{r*Jsu ?r Ll<\ֿ f'aRΠ59 ][9Bdl0g3Q(Z-`1Qձ"IHuk#>t?,߂Z*Ae2\q\NhW*_5qe/G!YFEPK8`PKjH<android/support/v4/view/ViewPager$OnPageChangeListener.classj@1Z=Z^_@m2Ȳ+kn>t79O//d0&~.+k⚐NEqXq壓LDԛHۊ_7IpKI][w;ch_E0qaWeN9b&0|PK-RPKtjH;android/support/v4/view/MotionEventCompatHoneycombMr1.classQ]KA=זeߦғAE$h=P#:#j^ z; C s3~}|8B!(Haǔ,CB*_2MHYJ]*q3쵄w[]յ˻MIs6#0ֹj{ZמONCRH({}_k%]k5sAWc9hr_Yym3(TƐ0pZ*_x5czJ3ޟ:|m`Yص@Δ<ɿ^!i .Wm#\35/=MYH́"E@ȡ-a1 f q%B QSC]ʼ!2N +Hǰ@ԧBL,ɾ!6a6CTiz!2d(uQ(;']aUR9w& '#kT'X|W)c]ˌ]*ݗ6RDMb_[3=ګ}S6C^&)Cuq?SPN?Xȣa'@ C?`XKP> G&+<>$`(Y=KR">)^E~Fq\A^E"YO?PK!OU.!PKjHJandroid/support/v4/view/MotionEventCompat$BaseMotionEventVersionImpl.classmOAgtP[Rk]O%0cͺ*33jm4U*z'eq%E&`EUѩxvDxWWR- *m2_HPM_(Ȃo{Ih )/`1gr/0)pKCF q?KEZrtnýa8q#H!q'&]w§[C#`IaI qV`igB$yx]KY^NNb^ I wyhCˑ^o9w՗ ynjD㎿uOz4w^{Sڴmjw5c 5)XKǣ_PK[ PKjH>android/support/v4/view/ViewPager$ViewPositionComparator.classS[O@fK(*@].EE@hHV$۰,6OG`b%ߔ XM99s3ǻa&,sH㲎,(C#:FuHٮ $UmY۵5+x$z*^U:2tj]^m]6} E׵G6f*ҭ]3{AhnM[V#jkJ0w=LZ1\EQ0)*m18iwg:dkK%AW'!wx,-& +$2mP)Xs1Eȍ2r :2TJn CGw:Cq)mRvt`^$"Pf?a>ky}!Qw{q Sa%\ k`|c7IEz2V?R<1 9JO/D6=hN_=GXE,dXvPKۛVkgPKjH9android/support/v4/view/ViewCompat$GBViewCompatImpl.classRMO@}8qBKQ XЦ@BRIVȱcT/=U1DJAZy7/XP3ϱh>K.V\ ?HV4$T*Þ_E/LEZ<餧*!tV]2O_FGXzA,NcHfqǂm_mJo W\lj'>9bj{:(9M뵚׫{v*nOce>@.r PKө[PKojH3android/support/v4/view/ViewCompatGingerbread.classQMO1}kuP@$ahHP,YvIY? &(l!t:*&(8IJ H ; Ɏtb:ld:}dp^#ޓ3X+G|1ʳk˗Sh3j5cu}zCnd6F|;ovɴlS `$i܅[LQr}G 88#s ː ,;c;GO"U "ZEGE;=Z{$} 9+(&pE^"\EN"id&C^Z"qZ!B,#89oPK>ePKxjH>android/support/v4/view/AccessibilityDelegateCompatIcs$1.classYSAFoA9<0Fa;&lnbg*- ~(˞Me7Pj*]s{{|2t"$p )L+1ĬWS"|H*O`o4늢m^2<~W`Rj6\ +CZUGX[TקPJyS1鎴1a ) *C,Xρ|ȫ+FѦނcQjܜyeAan]-,?t*5ړs16b$2OnӃs츄;BcN92O# [;醆ZZ˗ÝH/|7C##5zO4^q* Z)̲-#n ߎq^YD+k8OYkNFOZKB3ʨT9iڎ+d>ʎ- Y,jЭ9 qB5 aIèY iLh n6 g'0n1c ӻRK-7oAݩn?(zPc*B+9T{GswJ uЯb"Ԁdz3X "i!q%aO)J&.H# l:Q|ĸ3zM_phS.oƢ-{=Ml=V}Av[Ocg;d$¶m"F H5ב{))S&4KA/l9ݟl}4ACb?D/PDzwVR4W~ ;PK:$ PKjH:android/support/v4/view/ViewCompat$ICSViewCompatImpl.classVkSW~f\DDᢩEU1HIb,M7( :~C::L?t:,% {r>>>''ux))L1aW0^|fMcND*t X%~,B bID&zQ"L9`*rꜪڐ) fh]W& 1Sה mabQI-˪9eQu:vN1FZaUueD &-S;|48"+ [gGw2zkktٝ0R]Ʌ.ҩw C}e9U5r)ph#H ]wnAkiS%88C-(YdmݶXU7zl(Y0NK^bĿvH =oE& E=NU(B ]A䠆{؀X @P C.pa'mS8M tAp!~rT ԸԸ 3[Pq!)D( .pwI!\tBh6@ݼ_ )R;( :n <濃ѽ_ olP%y{E,*`Q0W&VlVŠYVZ?iCxncŊݬ8a|վs*P0F0VZ(9ex.{xDa ^ Q) L + 0_ Kq/+&؛}JŬ⋬+.c.gWjV\ÊkuLYex+ndWxs07^7SԴ5%RFZMO,5::vhwZkQ{fUzR7FJ&iIpMH)-mMM 2=F6)z[j5uF$|PMV&!~5Kz{t3Q&3"V["VSH1k5B}6P#39CpZjߦu,C֘nһu196n_"GPI-WMS>'$hl!x|IK'Ȅ>f[BqZdܘ ֍$C"! jY^zԖT=Y[Ƶfl?K< Uf0F5ԇnŵQբWg֘ b|Q1՜QhF䀑ĩהƵIuڕh6^k$hj\Kq6 %G G4UXe!]ҹ?(MPZM:FK] ԑ'naZ}q2LnIA/1A Ƶx2(P1z*6,HPhbH5{.o$lysɥXu~=[(w+ &2mq DzjBL}lKԤkL6 no)Ĕ6s {mffzh&A`jGѮ3Ǵ4پ~puܹIWc4Ό{[h쀚\ɗ̒6{<ܒRːQ|汴z>[/EeUo%JkYMD)mgvאId" (iMvSv'۴]Yc9׫/w\DS׹qm6ݹ/grbzr'4͹ ,Yd#Rİh0iwjKs`4 CFR'fnp[O[KWj*I$ieQaư[]R6蘝'%|#e'M#vڋcapvL}8HyUiD*&2 >b&{*d#Hh{5<;=p435R5ŜjsMLM̜MBme[ k.,괏ګt&fvv&8h1h[NBύ&8p´Y6=jE ZJ'inC\U\<BbMy.;? c [)sUD]h>d5 d-iZx"i#3{ŦeX[=Iv{ņV- v) '4k66iw3-"P>bsBȆ^^2־Y\dgZٔ2i[!=ZL ⹙^v5ˤ7]ESG^~2͕1&ba"'܏3lUr|7eS9 PB]nA# GYNFT8O#|]`9ō(m;\uJ=f`s8nzlKbWFWmJ"O3Tn"ʢ<ʒ4jts2^-6ddU$m=8<eKC(+d_kzC~$Ts'_av*"F&zuv93w}%L [`ᘢօ8Wx ޅw+x~M{>:)x?>J7X+f#l&جkp&-\e\JHMn>R/BdžtHHt[ O~dNG#C扌#|  ̗hx,j!ohI mMM#?FzO§(8g)X5 FpؤA6xXqϱW1 `R|Q' x <F<F ePGU"y6/%*m (e٫Jv\!Ҫ/%^bghj/tR5eFX_E#F(`eՇ8CWݔsWu!TEV@Ǎ=μqa%MD;=LoH$SO[҆m+fFHNsŒ^ibY p+A.:[!sM-9\SKeR"Rio3ok>*gSo,p'ճGiHJmUnY`C l9!M ~/m_ ;糼m+XK+E Wg; ~X !lg ;`7" (:_P Н=2AK؃ }49a@~ - JL'ݣM;~.%O8ֱʁgoFQqDCx ~:87ΕB ,IvAvhg@JF) r #l`{0GӘjBj\ #iM8Ҋf.ةPI$z't?u*q@K^joegH(R2 a W@V:,+v)ՂExKT+B&z{uvuLB$o <+yNШʆ^sαr8]rtb(Sg5P!'v~9?x]*H\il ̒T6ܘ!/ M *q:ծ Αp nb(Oz0$wO7]B=׊j&OOR_ S uFuK{HRB o xr$>,:$Tmz&aFl>hjsVCI4(i cn*!1D~xR0`; ҦC0k^_:"J[d䵧_̚HgJ/Lf@L>tćK1)sڦ)&!_Z/.9\ I i.O.|^.'@.ӱf6U߀|LBGnSMSMs!KaRw¨ buWXP^Z0.ul!ËgiЉ-rUn [$ȨϷRoqQ_0ˉD*~ Q_Kԯ#MGK"R[ ]3,ZhYHڶM1?/r?U{!X#t"By&)w=BQta^h eRm\ zL0ax^e~PM7x\WE`<%"0J0,9 [E`,"0^`|7%[}!.w$0^I`߇2{?];;c O\PTxGٽ'ov?h_4؏`-r{Wq`( b`$+-'rʢ~*`, /rƢU$gE`,`?)%?v+K %!ky*PA쯮$}oYyGXM?' Py"BP$t{{ojsn$=ᶑ Y| fS~X|:)w-ȡ&poe8͆o~siEA`_(pJGAR`8D &#^3+| W W|ȫ5 r:O0u:Nۜ`j[b/S0/IނY︼ڑV=uARc$Te=}Zb!f06R"t0.c.4sF{#ɱ`&\".2C\d6a'd.@5`׀ v xq*r'Nqഏ06jƩH0 0z}P0w o)-PKĺY FPKjHUandroid/support/v4/view/ViewConfigurationCompat$IcsViewConfigurationVersionImpl.classRNA=-]*X# Hy15lB(*v!ݙfv O?xwhm$;{νsw~* %8,0R+&ⷌwdRqeRo,NE,48=cW1PKZrPKjH4android/support/v4/view/ViewPager$LayoutParams.classSNA-,tW@ j[Q4…\Mw'06B|%/;hb<Cݑ{b}AR/2әMr$}Z- W6FozGAMqwZ}+< =H/֪@\}.Wb/I*{|u |-|[6.5-+%,״X`Й!8҆ωF,r(6U-)K5ij4׹>9FnX-+FoG'14ѨƈUЃZcd3qoa8;F,e(PK6PKjHIandroid/support/v4/view/ViewParentCompat$ViewParentCompatKitKatImpl.classjA'd5ma_I[۽wQAܖfڌlvI$osJij(9?a~ :`OlTFڋgJ PH^(Lrۦin6H8r; ٛM=%ٿ1q{X2 b+x^xu}Y":UQ@=\+S 2:.'xyq t!Z$Y&^e, t[!J%Z-7Y;Wy\3 d7,o['^[QԂh9#qOe4ȍ8q:}^>օG+b/;|4/ yA{^)sg^+57("_Fzv`Q7 Y>h|EC^WrgЁ>}2_>Lg|n{ A~H}G4~Gic?יIoŬd]LMfI-2iry$H+mhùClj,18&{yB@Ǭٌuf*Ri3^aF|RH&5p2 פx2]s\MOZWu Vrk}4{c]$akdYsuɤ}k1F6TX|]LX`f{MT$ǖ,\ਫ͌ZaAԜ0S~¤Ios]g&fSZd2SrpCCr>HknڰH 9bOZ5+-s2mȥ93I/#1(lDuJt%k91Z(vHLX|dD]*_-R _Sˎ#+!mu#8Xi& p0o_ipa5iNXmXN"*٘39nMMQR^H n+%G֑3GY.;/М兹ZBmj5NU~T2L ʯyĄyt8~՞vls6TEw } 2 ?jRebcQtzi#o*Q t"mB0DA%<5:] ]gY$wu~_hK{e*7zuLow4ίK:݃ezj}ȯH/>_7t{Ou\_F1n~*ɿb LZfTYmV}AN+Z "IZm evd1Z[:Ϳwuݦ}ܑBttX_~G>Dnp䀋tP3.rou+O>^h|U\>W!E""U+EҝfZoBXu +]\:]uU<$F x%ۣuCWXlgL,Q1!] aPdMjSu3N9Rf?y%Ct((+l#A;$]1Mʹ#7G rXȨxkڌĚ`4#hs0峛"DeVJ勆Q;Cn6vRH{Cg%;͔nVc'݊ew ~DE98\}hfԾ fhsS0Gj7,' G*k *'cmRK;q_\Րf۝h4GpKdZUpDz1G-3Z\47B(20LGwaNSa Uf9fupf,]Q}Ol{S]d$p13UjLQN-Ô:>C]=4qqi ]sLX!6CVvwH8 +`I/3k]RaO0eV[s<.3Ÿ _parǍvFs1,]Zk`3=R3 ֕iJcIC䙬7&֔T]يf!%!1 G)rCdU 04GMfq΁ ިiՙȍ>N~<K L'׾-`B CP⯧4>G|J|ѓ0ZgN.3ՈT7:oUc@J5GwnWfiQ߅~PGq$ɯbv"IBFv_\ƨ >I)X[ ,vшl T4H@ó߆{py06Qd3M!0}T*2{M}4f'M裱}4n'*l>$YoCW,9ۨfi?9-Հn2ǰFzUۯ9TO)}``ZfN.Zs+=FSNBH1<_CŴ.V:2.WKh ^pp/c0}0` sOKqh3HwQt'OXg=\Ot?/x==q [{`a jbGkd[@;h'lq5>sS+^ޅ6z:IpK[  =4zZg4zVu?~2|$y^pc `wa$. &~#I|Y'ыʳA/98kԜ;hYt^T9hX~!)|1gg'jl,4?z=/d1rǔe'z)9c 7ի6,3.aj,[`EjPL[O ^%L[fr!7WAkxG r|}"_SH&DHn.ڂowH+(~B'ʳ~ yIP~Tv( |4Dao{6Nvvfp 5N}BE{(*p.:I5_Zve'% }4y<(wJ(!+;hg?P%xf[ s/F9dօGӯxCr1G唿- 2gUgͭ?GsR083XE0;A?T |*D(aUp!U&xd<û['MQ"%d*D:)C~<>+,E/V{pܥ^ u-~b-0>Cy k6ӑy3vScH"-SK¡|/ O@̀q4$LZ  JbeA78b;BT H2ؠqGhcR# ebE^!5'7{ᅢZ OGjg1O$-kbXz/lOf:Z.j |",#H/pS SJTT嗋 *qbVBÌ˭l9 6*l`εus=4Bϧ!$4l>"ߟrbfcV_7w=*߹K&F*: uX. (m[x,,R rԮkKiucNCyv.U /^*}B#!]$Td %4(ao !eKMwR9ZEa M6&'&'&*0&#?g|>_|5w5kB|=7|?٣| o[)_-v.? }u? >oR ("~M`h"(uӿM벻7gwo^Zy?JF#Bb ~ hdƛx|Q}X s($أSw)>BJ%BDsMddmr/~_AvE^ľ^D~!ECK!sy8*zُu2U/.FEk"x \ՋZlbTm6#Bq#*f#(o2h^*'s.4zב{i (!#>T8~RvTȼnZOR5 ]҅<]/е"ZܗG[R2ZGnL <@EbR e9vt)y8 )P*J( rt% q"C"uhW. dNG$.r'B=*;-c|'O䃮W)PKzw%)2PKjH3android/support/v4/view/ViewCompatMarshmallow.classMo@͗14M! MVT *!Y *$[lod;%H(ĬmH <;;7DG{:q_(sC 41^P$' e?bJc{^t>yCp#ֹD` yFRVeXRxֈ̩ .mBwAPT\&zKLbɍ~%NRm+@fr9397;(B(X2LiGFA"LֵNۦ/efdnH_/0耰UAyήhu/}7[O'fP%ιT^(:0Mm>: ߊex$ʉ~jmb_cżѶE0r8!Ftb?mّd3R@%L–NK5-%6:|'/_3HG8p 1y_9ͭg{襄m&0 `9 PKxpsPKjHXandroid/support/v4/view/MarginLayoutParamsCompat$MarginLayoutParamsCompatImplJbMr1.classT]S@=[%RDEł-U~)}qۮLt6iM$?x7̀` {=ޓ??XZ(qf^mk͒x̐\1eu&2%7zE^)2\r.s%> 0ծtJ|muoݭ7_7ʦZ`0#Ժ=Odĝre򚍆|db5ձ y>W 2ɼRnSf%_dH B | Fyzc顨og/Q[wF~I}%l˖V{zw$|.u(3v42-q"56UUzNֹ=&.`\7f &&):69xʰuEoScٵVH a}3jmޯz绅Ӆw *RЕ.CE+owr" [Hl'`WhezB2 }V8@[\#Qjfciz4:,4)G iI؏D;+ri&&ȟg틲 bԑVJ_KdT^ӱoΈҸhf5G5ivx'ԘgMEYYY\uxKU<5iPKֹPKjHKandroid/support/v4/view/LayoutInflaterCompat$LayoutInflaterCompatImpl.class1OA J$-4\B Yv/{GkqOL1/yy}{~pI3Bs 5RT܊ LSK;(;_?)m&ǹԽo\qskWtS(BQ7:8Ыo z%<'"@]&M h Q mt&!I4jPKUiPKjH8android/support/v4/view/NestedScrollingChildHelper.classVks~vW+kc[c ƶ S_NbV66|;B; 8 3$5^.e?3)]8ΌgЇs}]__т5hr')0crqf0kZ<<(~*I䐔Cʄm⸀=O DTI|ɛZvd'INN\I uDhvx\N ~;\q!+\t .U1}IsBv.Ɯd"s,QHwoR/ͧ`Ņ|)] OgRL.8"(ZK۝JȬL2Q7UJ$O 2zI|DaShpTz<Ll9XyH1j@C71lND)9y&6߬V© ©+t4._hn:K9q ?Y/@jI fSHE;$ӥe-F|LuKT۠tnvrm6O܂d䦶r I{$- L,&,taB+vY&-\n mZ5zIY k!o8FB% 贰E daE}3܉I;ɫk"س]iɒk p.Uc=hK*ȖH/B.2n]ޖLy< /#!Nnج«ߜ.a+0Z)P]+;Bʸsqzf߼~Gu4ڣ,CdiAD9^ X1÷Vw7U~hl@?00{_V&KCm? Mk nEC͈1)ix*4`Pk̃jrLYO99x~| 3>:Gj?r䙦{!X08$ 8и|Nپ2Y; :^CmWQ;2,3߾$ l&6687P$Db))(Sem\#K=]}aG{#1ݵTz&1"ugP'b_mK嵺 bzp17B :V`u ԢqT jfk2@8 >0hVklqy@K054Ԥ&wqAڤOxiQfA(f9>Cō-xG8=U#şsh'7 ƨ^SWu9.:@+iiʺ[Ȭ5w hy[dU߹IQim Q¦.9ɦf r VY4ȷ~O6_GÐaTy6~}\EQǍܴ9RwvWy_cI oo?wp7e$<> |1~O(3<ϕ$Ne#%1aYIU%Ye'q%YŰ'*~MYR] AU Ѩ__ uKiAzݮQW W^չ5n/&D5nse ( VN.f%rq#uD&>"Eߪ$pItUBwprK>_[g}}M3p 6bD}ߚP5ɐj2 3PKOPKjHAandroid/support/v4/view/GravityCompat$GravityCompatImplBase.classOPǿw?ڱU 9PPЁ@&NMpDmB4QÓ>m \Rڦ톼_OsJF0Ko,aaF.rc^ǂA{(<?ff6rM0s^twZN\ mnP$DP~Nc{'p9aQP٣uuB_t~[ήuD!l:j+n$5 m aѝ=iKONA)zEuSҶ<K@bN2@4 (ޯv)`(5j K9/_ȹmPc>r ۼ 㞁Q,e$:1QAio۴\4d:!7rCyz+l\O͊J,Lfg}!AS@/E QRܤ|V||'IT#('IeHJbإNT*}d3) -e_ZR*ZJU1RJIM!\K>ES!e#wN.+L1L1u啚PRI4])V IHޑ׫sGz@ THz&mQjTV)U)0G VPK4PKxjH<android/support/v4/view/AccessibilityDelegateCompatIcs.classks@ w Pm[Z[mk1mYM$GK(dzK*Vvyr~+Ma3.'p%u,n&pK㲘Ť,nNS ˰irEa [UQZ Z޶yu2\ `_1D3ظS"悰tmȫFѢm4*o1aF ]:Jũzz^jfY^tl&T4+ÎLҨeKL%7=|3tvdXxя9/B2. fSY|mqZӎ@EX [c٧ %ߐ< !ӏ l? L;y:°Ļ9cʄ)Ep-"jB1{-=سuKa #wNҥð, İm6ْ %չ[SGY+NaSsNjBOCLɰM4Q0f8鞬E Ø0i :hPEɺ1W [0W֖Wi/^t&3Ɂp_qV_ìRb"?)RBѽbPRݓ"حD*zs{IkNb?5J ~A'D?@^Y$#H}(ᢪ5'C3dC>&\/ASJo XA}X* ?8S8 HR|j!HMwL < 7  Mx O$IH{۩ yKq'ǨkO/ί6א5}=pDԓ gq.z _y\P/_t\#}E`TmT#ʤ7{U$PK% PKjH9android/support/v4/view/ViewCompat$JBViewCompatImpl.classUSg}4\`JcEK+$jբV@`(Bi%%3}KЗ `dڙ>SM Lav~5JI,`OY,Xf ,^XU/aŗ,b5,te2{#W)J]K&Y vY7WtoCkk)-KӦy \!f4<,,\C*ocD姗hd:|*sM \ߑ&?rwbDBhxNe-įt,I 5ާSFwt ~4`.bv*%Nfvй[DcY(J>jz!k!y ^⺢t)6ҪPȚ;9կ@."< 5][ݭ*qAndI 1Һ i ySќ̳|3t_C%ʧЩf.@¾@BY `-X] J/['{ކ[A8ü Oeԟջ'SUـTjAZUY0Cd g[ Sn6seh[`d4d>ܤg*Mt;ձHo"Yԑ>Q5fUЃ)L}*l7^ţؑD l,̩(wm`w7 \7/Zяm`œt$L y)q_PK PKjH6android/support/v4/view/ViewParentCompatLollipop.classVWW6ǒQhڢM4!ZA-RQl@D]Vt zP_|Ҟӗw7Zz ;wdL_ (>l sx<"Y b_SK2 yX2^oJwbWe($vM#ؐе8y_BFR3eX,ؖnK86ejKjIOng% )Dnhsʚf-keM83jyItqvA{Cߔp%KZjZvf+ҵw%.Yv,Y%`=mZihJT`E;LjEDl}'6mK-ڳaYiA801PqC/M, u.z mYfmR,UNMOyXԪ ^uG_[/ n&a+4Sؒpy<3xG|fъ:ԇt8j,Ƀ*[mbڒ iۀXr`r C5NQ2!X\iw^.seݥ;ߥVg1O!sgR) w{( cװ\q{7 ?]HH\C 7o-"|xB<#/8Qt_2Jt ]V1I,.;~B)H:\c,!B!ҝAbV<uM;yswq_1]?8AzD&/uרSE ~M/-@ XQ(;Lq~#=rv/C)=~йH.a=:yq䙐:!y{>;|ȺdK D3Zo[(aQu*9vPܧ_ۄTa z,ڞ-Vf.2 DG Ĉyvn^7D. X -_R6E)\}!~K>U,g֑e jh|h]BzCb` [g% ,bwaqIӱܧfc/6ψGpiHKr;7uОWSk]imA]/*_E+M"+ rZlzav(zq /܋v#$~e\xo3w=L\.x),/3eu9,LٿPKv> PKjH=android/support/v4/view/GravityCompat$GravityCompatImpl.classQJ1uU~B{1*>V&&8Jg*|iuxg 1ZCxܕzs'<RYQ_t/euI&اe2uAM˥|!k`@𕵥 Vo|t5 -( FzsIBy# }׆K e/:rVٜ>rBϐ;*}_Î/EUdЌ :Щary1&ţ'//o:G=.N3 vmn>Cť$n엱=[+ۣ[㼆hC?M66f&9NPK!PKjHWandroid/support/v4/view/ViewConfigurationCompat$FroyoViewConfigurationVersionImpl.classRMK@}SkSkZ`*( BڮJ IGx\Ⱦٗyof'y{y `9 y KԉrUpJ* ɒnJTU<\Ks%ff!0*:dV>al+yWjFJui|JsيJSrKԪmz6ݷ`)'́5 jܦl^r[W:lk[.s\41E( &dj:4 YV$Vi{VtEHYXbSV ~{Űg—[Y5RFߍV'G6#E1~==K@}1yF0z(L,'E>ɸPKldvuPKjH.android/support/v4/view/ViewParentCompat.classW[SF*dlC BC[@(*õ1mRYV@H,S7:i^!f?N/}t:=aH粻w~`{ڱBk:>m>la[|Nwi0VHKpIqƥ=uClEuIX{9YY#g{,4MNd|^3tկ7y)L&R噗gRN!ͧf8װOl&W.fƶ/r(>P32Lù48N +3D S_*u{UMgӺ`ijv] nNcPwR4=7Fp3`bqΥ)CqN1tHvlN˱*'-:pc6kt/ vvvh_Zm:{l$򌝒 c+ i#5MV֨)Hpsd`7,E5)%܈]u_ыWe2 oɘ%o=WpU ȸy !>f(ęqj޶+3CT/ ϋAOA*]]#z}+\/P[J./QjwKDr@;}/ҚBRH2'`_4O($C7E_ #%4y/[^! B1/MҎXwؼ$eܞz`& *p Gh.QQ}p)6(|>/,hJ75Bb i//4 h\{9DYR^)o\XO{6)bi^]&G7\z|$#.}\E K *yJ57ZFnc͒7M-[%,f<3ƞE"BH0[UlLH&m4-I 4!$@ yf4G9s=={9"=vjWL8SΣ[/e5^B~m7mB~Go ޑ?G$]}>pПC9/& V4y`lg1 P+v.%2u Xdm &$& "SWmd~\`$..Q8FE-idœXgANCRw}[})Ə0.M(֮oO l-nB윍cX+ad4r%>Id̛0Rkڰ˦˙HW$2>HPg~%&Wi[8cO--Õԣ`rF9Ơ%SGgMv7E=\&e]^?lmjo'&%zæ5EC =ï;ĉŚ=% _"dN/uipF`z- oN hE«¼ü)dK&V>ʵ!##5%`\? 3Gt(kv(DZ \tHnjAǵue]2sΜ9LzF+ʞ9?6gv8(ȿݜiЂ[=gސ?_|*dh 35!'oNoWn:tꤝ:%(h̡,4BCtlEh>%NW!o) tzC+b0Y_;Uƙ޴p2&mL$C!10 ZyRrWX @[humFlM(:t ܨ*^m&Z:[h2 \pF鼞ۭܡި&<:o:#"}[8e"P ]$B\%BcN q@wNrP睼K琬DKc"- N-n\«uI|I1\} aZueW&:SSHOњg&-3#͓F iOqaMDGX?;-v`c3[CəܟY9?_avZy oP:֞-ў@{Ql"L Ef7pĦ m.jXA7eƦzñ>N&`p,Mb>A¢#;,iEAKGal2 x.olQ$X Y_((JY3z⁈!ܝkB޽IЬX'owh4ZK'v#cCHHmH ^o ۍ5J$Ӈ%Ys 51UT g0g.dBM"Xe<ꈉ4ji=M$Ev=Kwcb[qEK>EˮMeȢaHYad?BvǾV>Q[d~rw_|~c2k-5].rܮkJ%X0-bKStVZƉh  -: cZMjrN)2{x'i"aW$GIÕ1EK0WgS"KaSNBzi"W*Z2ޕ]Inw ql363+SA*WX>?1CKVnϯ%k(c@'kBz度ަV=xA|QDһ@'C}Dǐ6@oB'Yq!y*ֹy%l".s >|+A Z7q@;K}!ީ]13orL}껜NjNޭ\竴|6>mߠ]|ͷhIU?]Ƿi7[Ny/jex ^f8_EgU;Ќ< GWKG!"G8#y2pX߅@QQ+| K9kJS{Aw^+RF:.riNduk%ބz W,j4ɢ݌e ͏>%,_mD"}CimtX5hk(v![5ŮP?osЗ!) ҏ,rF33+ (t,k }& *nn.8@O#[;kkƇ4>N8>c c}W\4=NshhyF,PfF?n&2@EHLuf15\srjUTR/<("o0KuŨ67Tf2+T|*2!燩dkh iO#?֯i2tJ~Ogٷ0>H6qdQOMh HOsu-k4Ld-&+mq[.Im١)8hb|4Iyp_ Ӝ\{ 3b8$^f\R0Ȅif鸖ޣԬX Q^Bm2fO}YSvnO2'M5j)Bǐjf2شbPS.OĊ]b25^ 좒 vHTjmy—`pvdnKU݀`W<*w?$r6Q%׽Sk:S!X3P _CHIsܩ{E Ńh U,Vϼ.ux0U#X EI6O"}uuȝ3e͞M`1l e6|nRz4 9qQ{&{a͟=!yC@ټ BRmhQa,+X|l\  Ѱ /q%pQ)!\`+q +Qquγa0_i@В _Z[K1?Z?yO=PK"ZPKjHNandroid/support/v4/view/LayoutInflaterCompat$LayoutInflaterCompatImplV11.classRMO@} HVQD@9x@E*ƃ$L J\Sڦ-~'?8[PcB$4ٝ7L *bR 2 a!Z8l2ĪNdH6oEHpjrO1 ϐ1 kNAZrAk6};#:w] >>I`tA͠ہ n ?*U4V?)T}}F;{m&dӓ.=װ$15P\O-dKXի Y GuDh@_N:E&0='X]b"s׉+"jhrJ5P!?bikj);z@McbH|PKӗ^zPKjHZandroid/support/v4/view/ViewPropertyAnimatorCompat$ICSViewPropertyAnimatorCompatImpl.classIpe7dI$b%,IBd„% aˤM:=COOB1*. *haZJʭJZ=AzЃ^LgIW8̿}[~(y0t?tA% 1I$Id'I=$HvK>~$<@ >H&y$kGaa2xPMS11մZ7fM;ƭPwLg C5ktqcZb<-uyj6 m[U}waGZ5i`jqfT`DA *is32,S5 -gfhYe- k uU]5y-h-Ԩj̶4G.Ţ,OHӅU6arK VS%IE=4'TAɄA@gzX(vVk2ܬZmuff~ 2cQDG ?8`*JaYX,nZ9KfJe˟.R{^9wCz2zڨ''[Ert[.؉{'TX;oҒr'Rp0ipru'(8յXIQژ0 :A" " ^žp 6EfDӈ01"a0*S`)U)P+z0I*$kH֒444#i R$H6l>xApAոJܜ /eNi^>xUguW#\`y\wp9 .J/u-J{KXCBcG.jSTy'W۷~kMo/;[sG{xs]R#I~!oS/+}D5K]h\IE* wtg>gaA:e.cg6 lyY7y ۔5̇6}!,JڋϜ)5H2&̀lJt+fSsʎJg:=7+ @A~l~F_ So9zѓ렽)4ë΋9b1# xpH`D!v` 92섬`Q  ?. r ]W" Ϻ௖NJs௕ljpo EIO ~_$?C."K)>X7>t˃ RŠst?o?"'rD3q$_/Eo.DtQ(EװV+`(›f*>m/c@1"OFphAOI@r0Utx"iaqpPna=٠x0Dž.g6{48SniLV=~"χ%cŎOh6a0A(\B9>K PKR;DT[PKjH3android/support/v4/view/NestedScrollingParent.classJ@jk483H`@=sϯ8sp% T(YFRdX4zkU-ObC t/5T"-AӣvOKIM$l g)O.loF/~n5=KX믗o ؛/A(v2b~15_R+*/ou|s!Kd5(O-A?}:=!12z *Amj'PK r_PKsjHEandroid/support/v4/view/LayoutInflaterCompatHC$FactoryWrapperHC.classT]oA=.,P/ߢ-kj05uM& >0ev3̢W}wAl$>Ν{Ϝ{7ag4 5q 't~˞ݞ= W#W|q- j_7Jm{jR^G^+7O~rWPCLJ(קNqAz}BMb ,_:JM(9kQ|,6o}+)K-h}gP} h (-a\Ұ! 8)Cz@ 2-?T]qF,,D=TeldU9TIXa؜w3;O!53sbGNc0z z#FMvihygqWoFc7'Y\..f6<Շmw޶;o[-.m;ͫN[~s7&4zu,;tÊH%{'n\]?ΞYm1N'nG-YܗTu+jI5͠8l:$z!FCѽc.\` vipXG35~UUjB{RF"Ic?!Z5q8vQO֌i>*8Û?PF^\=D- TalӍ>a{|E]据qaԑbp9lW=6!J%"4~~ܤqhA-Q!]D^S 1d$ӷY}ijkkǝLgoe!(r ')}Y[Vv^.ꤝ"|&`52#-}$sfjgGT#s(8ubl&dzG¹Z>uuUŲ׿s?~GAeS(ֺc/]k=rsqMC,A&NI5TW73r=S஢̫l }eOz Ǜ?#oN昘[3 ྭ7֎]]ONq=w݄{} =NFCp !8L8 #G(p>V{??G$1L"|埂gN3>/0y Kl8$4c,99xf/,<ˌ EEW/1.3^f\aW_ek)럃 \ap ʄ_q|5ˆsh>uB0e: ބ  "5|m>X.G>|@#τs q;x_3?K?9PK+PKxjH8android/support/v4/view/ViewConfigurationCompatICS.classQN1=qBI܀ 'Qv7`X+ChI`'7n4qQhL|&sۧGXuAE,{B OHW-B&P]N7'Ѱ9 ,Rh ZL~3R ]DGƟW~ˆ@ ы43B@ G:xg\skf]{zmm D(Mp*!?卟-mلyB%'#Bɞw<e+e5cO#[:gc6K{msBCyyyI PKb2kI98PKxjHKandroid/support/v4/view/PagerTitleStripIcs$SingleLineAllCapsTransform.classT]S@=!U|UAD?V`)X.%&5٢WA}ݴTC6{s{~ el1:H"^7tĒ { ù6wf^zS0\Ⱥ/# ܮJ-r[45if1W-ǒk SwJk̢HH3oe&Y`di ,GuDC2c^]^ pA[7ɢd`w45p5d ⁆51h`@ SkX7G$l 6?ҙ2ڬy̟ofjX |.NS$- lB $\-1Ag 'q}W0HD j ?h=mi\Lݣ`1 t~0LcM' |Gt}#4#L,"#<.R4Z{f$Qz!nO1)s鯈?Auwi@2.QbNn.McWh!4*01b1?PKe}PKyjH.android/support/v4/view/ViewCompatICSMr1.classPMO@}HVK* nKbiI[EqZ Mv>̛yϷwg84Q@D {u|I7[BƊPvn c2i≎mG0c- Er.ŀ27iPȨ۞vW!h43nkH)f?XҩT&-X8 4+҅'{M*k" q<5}9#R zʛlx[&ؗ8^# B>EA=kκ*_PKC!;*PKjHIandroid/support/v4/view/ViewGroupCompat$ViewGroupCompatLollipopImpl.classn@ƿIbCiZJ(R K!JPV9IVeN# /ÅBڦ@Co~ޙo>\,h lXp%{W*GuKt[rk8J#ۺ'0>J %_l=LzH%:i Xf$TA[}U?LIMn#c P*#TeJǹX=ص9{m0aFQi{G7_|:Jve#vW{jx5osr* 6c6|dMJV[alKr|a8:%SǼl?uePKO9PKjH@android/support/v4/view/KeyEventCompat$KeyEventVersionImpl.classQN1""(*'=Iq#Iet}4>e,+𧡇N;fүO((#:P)dъeXAH]Svg#U,x (LߺScU$^ wzJ:]sjްK9j[R{L*{C*;LLbωZj>iSg޵#Dc_jԶ۪5Ar&j6cNvvvF<ǂ̼rϼԒbF\ ļr0 a *.-(/*/3"u-H,$E1#Wp~iQr[fN*#?|2KRRqG+#H~Nb^RVjr ##00203X @ PK -K-PKjH)android/support/v4/view/ViewPager$4.classUsUncvִE iK*-UڊH4m-E?nnf-Ó/蓣98̠CJGx?rAϹAg>w{=ܓ'\!::0aDǦxtx8M0:Fgt<G: ) jX Nuƶ΂}O%߬Uˆ@4(ZW-ImZ 2]< g͒|T28%p$}@8mDAԲhsbgyӗ< ~bj61_|\ 9cjyyfdG->֕I«aN@}>-9-[;d תx5&598'lXN"aa '.ࢁd |3F3%F&EFl6l%d_on);*yIAN |o~647rs{]InmQiMw_[k䬳͟}9P*pEM:*SgdLw_#͸C`qIϭ׬9IG:ݰ~SEY;ZOi.'͠L.;%<0ՊiϸFIujv:ф6c+:w}* L2Sandroid/support/v4/view/ScaleGestureDetectorCompatKitKat.classKO1O@| puaĘ|, q@U!3Snh¸GoĘ8Ns{+Mb|!,%k`CtWZR1 KHn TMZĻk7t2U}È.N[-ǖ-:QfwJqoq,\9@(TS:\UrUfHByyk-Mn1d f[Wiㆮa~ DZ:ôt$HĹ9Mq$CzȺf$a ƐW$1$Xao&SG ~u0Bm_:NAq H2Fi(QT'Koe)ת$)L4fF'qO<|Ѣ7;Df?PKuSPKjHEandroid/support/v4/view/MenuItemCompat$HoneycombMenuVersionImpl.classSPǿ/mA**"H(/Й*&$-8/z#A/_}{o~X$,%qBhBbEuGexOb vJR -B#$- $0d ۤ-E;optF8ܨsnKyc7/.a18 :1L R1LL㶎;rAua<[Y|P-.)=VbfTJ%V GFW"V8Zڪ,6=Jf+c}x,WM5_l$3j5k@lN6\d`b4N˪܍6/6-invnxÇN Y3=c38R. ΢CړSfY3lKፌ()9RQr>ouEHj6GU)ʨ)M#"qaztL[.S{d:Ab\Zcj4rlVzsٮz]ɰU;7K\4dI]?Aj IJ.6O b) mlvil1v.g-_5CFw$$N_BH]HMJkbwW6U%tyx_Cf8賯 MGGOgopt>Ε'M+UpA)83 &d  Lbچdt0y6e3Zǧg8ѷ3BᗈڟB_o=x4,Bp0<>ܠU@PR"}!R.5/cVHY'i6)&ׇIOng14DA'2BK %`Ʉgl24{j1RInB_DZR8uVV eDJJsE$X% x#T*5}HXf?&'L0c558f!4Vs3>ruH|]ɦDd ROu6&;$WR1]6Fߋ!߃aq ^9jqe?Zu7"ڥuv#圣ugPbPK# PKjH-android/support/v4/view/ViewPager$Decor.class;o>cnv6vNFҢT̜TF"IJDv.Fļ҂222}Z"FV(㙗Z䜓X\Z 2J?'1/]?)+5Al L  &Y84Ph;PK^:PKjH5android/support/v4/view/ViewGroupCompatLollipop.classRN@=7Ѹ!4E(R ) PMQ:Iڏ Q $Hs3w8Gy:xZ  /-!BeڄfKȞi_;*q_WptEf|W SG~%(ұT^k'љGttHG'Hs0QFrӼ g2g#Ԗ:P _bL^ԜK=JQ_Ñ *b \6>a#oBeqF>k9MH, "<5wt͋ Ƽmn5;#p]Z䝬?Τ 2ԎVYk*36Pc"%1Q$gTUau~dfLjIE&W\az<ɚ.M`h-1c /9qAtD% Y3Xܪ颢XRb 'g\;ό{Bc kTSƽfc|1(u&]>h8'љg*>k"YC^$P1Q U8d<&tv,uxuiޭE}mQF(VMճ.-G~{ZaPn5BiɊ4Aq6&a123u2-d`,=%I,] -U1+yV'UD-uYİr '$!wndk,=DB]#RajوB URZ#'NsYmR槯j,&5)EtAwĖ/ #'D7\-,cBʊfONU= roctaCPtH懰p&^;6!Ȋ.%1FK MK{[៫3*柣 -h%GdW[D464/)?lʋ x1)я`3!ɄBFmJ2p O@@KA.q.}kBpϙd].(wqmuO j[L;wg0u{ϰytJ쩛Hs$)"kĜ8xt;tljzЇ HçKz ά-yVҷIߑ$<}zx%Y@44F}6`0p0:H3M1b?}gbkJ3pW.I#)?Lal", v6Hc[8v" ͡$6q; ml6X<@8C8)hEfHl, NfB4Q۟Y8oM,[PB!̓ ZN 2 ܥ9l&Vj-$MdB:>v*/ٰVb°&lylX,ElDVL8Ga)ej򫚾v[Y UKĹLSTgatdkĸNǗa2J_b|[AOG;wzJ=|G [{ {_ܩz}InwRp9qM<]|5w7>Vރ&l}mͩZ< )6RrEhK7`Ì?Ѝpof9CSg/t3Cʟ/\ڭ~?U5OR;H:jSkohF\u@PKE&8;:PK jHandroid/support/v4/util/PKjH#android/support/v4/util/Pools.classQMK1}i]z"zSP= 6%MJvʓ'["L03_N PƆM[>gE$(VkaH}0}9*}2xsbBj]2GvM"=]> ȇ2͞p+]L{\'$J,Ey&Uj箸m,SE33b!B=5踑)E1tJNw: %ڂ; ~BKe4`ux@LF_9JjW> ף߯]p_=?qΓ46[7w ÑUx<xîs(j a*IOM'F@;xy.+Z+rX:> WQzQ&ڎ20sz 6)1T^.Ev}%m=<5?UWQkHvzE8,f;gġ"tXdbсjëPW^rN,v:N~8<d $K4ŠZ7/p%ۃ@H0(%e@ q8]'0@E~ŦF~ Tߣڨ=y}(07#S uAxMū夻N: mIZj!|9o}7Za,dȬHS#wRF> /+kUDgءE'BүRrpQ77vxC҈.㴭Ku8XC4s7We-M[Wvʫرv'7r ߉3ɲyrw#Eyzn&1e=_~>(OhdkxWPK_vѽ PKjH=android/support/v4/util/MapCollections$ValuesCollection.classWsUd$ MX!MKKmKK*-_d[$n6/?)~:bU:/%>0swMt=9w>7x=zLh?Ί⌈sh x/" 3"$a~~qJUmr&eTwe5{r.Mf33fT}MS B/d=ZOݝ6Wà%Og(9I)X\4& T,B% ?'+zvXE^9j#uYvw&TU8?9IɃ(.{b[҈قT[oЄf [ðgtj7%5jӆI%'~KȠGB9 0D.U JА$8W`( !T$,5 ( 5y}V*$5t/WYC M$^{r~LL147@q嘢OeYG / ҧq"dtmc"7dq%Ma8}[b}6@ü*û~[GhvXO`;|Oo^lgh;il-ZE#ch#$y ܃ *bc!M".۶lQ{N:*6IN4Z'h4ۇK4VPK(, ?9 )Pgj9j*n/܄]EHN! K?<59>[/hrVw/NvE:b4 2B\^ۻ"lo"Er0v7 >;M0O%=kuz8 < `RꌥԿzyg.;p<[ yεIw ݃t trDEYiiY?C-7/-"xKUػhdP=b H KuNđ0ѿO  ª1nz;/4 -/5 *j:"{Te"$-yք%\k.TCd˨g7d>D}^1O>نFRCDc͎P*>R#ىƭ P]5яcԛ^G?B[k}@J2_ߨCK\__HIqY HeWR?Wxэ⤅8B1-ht*jKWd 2k_0MYWmD㹢/hm_Sf&0i`:ӿRI4&PK2PKjH,android/support/v4/util/SimpleArrayMap.classX |Tՙ;wLB 0d%a& 1@JTЉ ! y hE|jP|kAQQGnm붿n޹3ys==w;gp >~Bs^|͋y2|^C,:=??{9*1i^%Ҽ";^ i^uiސGҜM/~,[(SҼ{ы1$?S&ͻ>\_x{,TP+*[[::-M] Wm_.o0-زaTȘ[6Vr>ITS_YQȚ?g; ҈-Y)Q9S!;$ָˆ5XH8`Zm^ؘP0VU)x+xf}cKCb # 3=fekp\ؒj^hkJȱ[MlOFH- :Z;mtZWgcӴXcs[SBRo\9  DK~[[RΓ+g.YN&Yxf$2mt55_!3Z7pfae)n:^OoO$ Ch3\⼯>oܦ0zފxoJ8vzEouxڲk&Ԣ֖xcKǕb1ᷕtEZ^rm${޵!ѩpPDrX㆖x'O0jM+ѫ*:E!x8ORTTO^GŠtRXuZDFa{c~s\m];cϓj6"|f# WӖoD=jeji#'[>kU+$B+ODc{bCKk{z*H;ƖDJ46mIeS2љ`@hU+լH|&Ӄ (Dnj ;9:j!ˆ©Ku_`Ԑh'(̚ue`.b|(;c]KDeTb!> `>}he$*d\Q.9(S(IhWeJ @^K_h\f!.sd6l&in܋D~6 `–Zc(pv1ݸ8q3(o /LV@8/<@ڍ7X)\*-dL/J4%$`[bDzB/3JNKkWQÒ__)i6mL44׷"C!DokI7\X98P\88T{p=Ƴ|bє1(ep) \y+P[K{ޭT7tt#ltV{vUa'k/-{zvyv(6vH-0ݺy 0CcQP1b8#`x_Wr"t TG}5Z1a R!2o Ws"[Hr\5^wpe^_e?>d>>DAR2C4>+x~p)l֕D6L=\'a&ڋZ&ׅcvT5@6PBhv;06:vhs ̧;f/ZL֚5hfتcZtlJ;0ʣM5VhdZ,b گ10?d_ۨс j⨋EHa9ASŴc1JCMhbKŢn\9A^$pf-bO X=$y0EZae{"]Vk0bRq 9L'Z]Ǖ'!1'r6$ax^ q-c-c6e̍E&W3Ni>3>MlGl"eM? $u[EFˆdݘ,y@<8Pi5FSeHbG dHp>Gr" f92j,Q}_y%gX~2>@J 3l"b W qʅbeb`9zj3#p\M6-t̾Oc^:fGhRUBs*;65]`tr!B"=Dxt/By : y`2y'Ok'zWX5d2h6pm*duz$t/Iƒ#(Y!1cE[;!j+#pb;/,7+v nx LUTD T;D݅-ܤNuR~]SG=cj^?`ZugASOI5V= F[ҷi)܄ ܩo7?]WlV/GPrԢ|[%hw94nK&[.>ōp w< _x_ ?SA}RU*Tt_VNm9נߥ0NaXJU3X$T0pPJ˶KVW=9/5d!F-=}n' v9: Dq]=Dˢ" t*A2wQ|  yBSQ${p'yX"Dw#ßN'ڳ^iew^7S_1mp#c &cQd& PfA3cd=׋c|]eta%]馩54(, K˼ѠY _*fg].Sr~~v T+h20ΘF9fr<Ϙuy5M://\Gu=f\^|`Y!w2 tAV/>T[$PK=:o$PKjH(android/support/v4/util/AtomicFile.classVs=k6d@HJJCh1ƉZ٤k@^,iIIEh m48mmA ctv?s}?/B؍L3*̄ #b6!'ʼ0ˉY^ 1A(``!XTpJ, O"#=-Npz6(x.mX_b87M;ZI򺄶ImAKfr$p33_.ہFHhkB|f](nk9fFMh!޽Eٞ3JJked\,\ؓ,F.y6FƃAlmXHhZ 9s=_L lͲ'-C,u1?Re{̶t-O& [o5a\>ыa E IR- w.lMG K^-0h!9lwAEfj۩#(tF3r^Y ZV5 0s%'ؠ/iFjīOˋSM3uֱ P"KPHlь`euKKxY+xygxME*pVw=|V$r.[iG1:wbftw]y?%lUOjN_ŏpV Npe֣Y3i-EkqBŏo@Aӎհ,gMMk 4\iG𰊟M?Ueog ~.mZ#'Ugi|2;EfIX_z]EjE/vbcu¹mր~(lqeyt 7h尓6 DZذ7rI )ÙY+TNf*p@*"[ΝĎ RbcK%[ /Җ2fWN%K) Qkn!"O:݄:8 Gm~eH{h7y2 ( /#Z~鄽 );_20'^΀N7$WS<};8hT pp¨F⥵s>9w I>[s[&!q Q 5ڇnsuC~澵:p;Yq;awNuwL.NJ`xeqw6US!@_Q.M +%)x?Q>HVOs^_ГItKUh o}Q .ۨ };OOsYZ3I.J-"$[ro]" U0":jM_Մ۪X{|'8{X_"֗isV`;ux5f=r-ac#+扰Wt@=]-F{ {iK}x7L!d~+ ]t+"_VcuE vF6OxƎl>A@DE+EG#Qdb.qW]J* >UfwWoi;{&?D/1C&U$gM<0qcu+Xu.9.K?:cs\cu>S(=BdxWqD*UU[s1;c"+h; J-9PKh\tx-PKjH&android/support/v4/util/LruCache.classX w~̚Ʉl 0Xd)4c P`BG20Pjk֕ZZ\jQj H\=Gߒdm4i(L |Fװ䚻ڛzZ)&T3H>>O4TgxN7gh?H̹w(a 'YsnNVb=֧[E%N=ߙJ`S?g27DVu⩁L:1P͏3M[2!ϙ  _t*;s=t:<RI:~P]BG`\S%<ēyeb}}SAmmZcdhX]*3!m<}57rpWޓKo)!ˈTJϬOƳYD]v"RNGӣ65ꛎi%edyT@ GKI˙ޔV -`qn.]޲K98fF>)!oyQ&Wi.$s!-T|$;Yɮs8ΛKwԠ١BU;>fV=ӯK677NZkuDژfHĩX2Ɠ2a^a_>"|u%\)OфF s<̘M~܁[5,#LnM'R9=SpE)Lq׏-4V=2m%1hWMA!X"=OU#h1[䀞 >?{"+ `FD?&90hZv{9~_pуK)x~|~|$M8U 0p%F>f?ѰbzI\ҁ*+Ty=kl.]z_y7?5V-SJP&(׆FPlc*~mRXU"⤔Q %BHG|##z%%Z)K&ɒuSrKLZxAϥGs&u L]劋аvyn)fI^!+,*XY |ԩG9|7Mh(VYsJg41UQ " n ǔW&o6o55fV]3(+c<<%TEYo1p)gumI!z|3"R<'-үb&K QvJ3u3i؁p'c`\N#ӯOyfm#LI'1d}>inoWhq5AC5 hy) ӭ/CC,Eu8jcX8EcXÒq\wN3劓c3^R cyrr i)U5b*ӰDCd- υFv\t^qciC^VazVT}Ra!p(dgޝ݇6߇f{FF%q37NcSqz!Ů'暌vpڎ)`zlnFѬbRmp?Bͺfq|L~:ku-b7VZ) rȮ*z~V1Yü, %VY? |B_|J?|F>WS5ϬsmLj$6Iۖ=슿bM[l%5®_Na8OɧCYV/{nawiFth| ޲83Oݶ|7 vsbVyH˴E/5+36i/f^_Z=:gXq]:a Y C#翤˯,`PeR$(A[p66 Qo#1(-eΣyF SUP>CJŒ1?6rEd2g/ <3wԡmJLz &:r\lҼ88 TU dpkJUNnmIp ӄk) ([=MpL$\n4r4e/w򏣾nh)~QW )).D ;w5WBLNXy Ӹ)jU#wt]=f >5ZWAJ걭am Q/.cVaȽ|!RH+;PK-[ PKjH4android/support/v4/util/Pools$SynchronizedPool.classSMOQ= -Z(tZ:(†hBRФ ncә:Vun]hi&tRż{8)э-1((Ȅ\ &\gJ0P\Y75gڦ7Qs+d`HL[,7kk­5Kn wM F Wޱ ױǢzq,Ģm w⍆cVF^w\Oߞ֛i鲼1 @zjtc` v2fN>WM%tz7x')+67\a ޠȠz,w*&l!wtdd"^6kuK5Wy_g<@?]4]C6~UQ&Ѓ]Z'O%C<38E)f(n JeHBê|#=n mJɧK׆[`WV[F_5Ix(ϑ]6h}  2KrC>1%+}Bdͷ?,ۅR( mKʾ"OP"}`!`Xy0#@FmO _Үo~\hO>gSP-EJ`(\C9 .SN*JSJ;ĎQ]5ߪ$DOJiT1`W PK=PKjH'android/support/v4/util/LogWriter.classSNQ]3qJM@eZ.S&P4!@vZF~1l"`0uLg=0*1ƣ0IIns3"i#aڲ=pLd-14l`U/Ńmbe&qdМ]Ymw۪lێYlC߱ Y-T<`TkW񍽔Qmz\UT9RtjY }+#,O+C˦F۵"Ch^X|\6fS sAuV[Olk:oi = H5UC509 XаG,iXƊ V) l8;uqY.[nW!"I{ vr5u iJ4#W cAXtzk[ r#zt#GWnmsG`h#HP>6FqtIG]𺉏[ XĞ%|1PZ=)" IW?80ϕrLhoL~J+1E1rL9D$Nf~p.Ip#B6(I41̐B "qRd 6![\$qG]op<| p )&xhx!.jR=č /D-P:,Nq!t7PK㦗6PKjH-android/support/v4/util/LongSparseArray.classXmpT~~& 6P&D>ln " l%,lv&(Z-ZCHjlDNgڱN;Li;i[g:U>70 sy?.}VU8A9)r| ~}Yf==WAy5=xX&7UE~C[exp))Oi3e8#ㄬr P)jmJ , [81_l~k7A_%5妟"ktGJ0v-Ƶj sf]GYPMϹMEnbf03/q<Pu#x >S5{s93~@8Hc 25E D2fWPJ]Rv%,VjȖF>Mb ~]S5\9G+)nƘ aC|5xvzh0QZ Kr28c3FҢB`fgetefَQ QCcf9Rʣt i:/v#jd$kmap#\ 4g Íg#tEBŏqFApi b;3*=I"`I=O'bFMe/a'oX9f^kYtj@cZ->p;4hm 7~m$hC}qj]Q,9n(rI]!+B~_ZskO⣀vs<pn),YOJZmdG~] 8nnJ=wtetU@)O>cحK֋Nݥ)Af,:9ma+^eo.'ˬqûAz"<cc1lv&bJq9e3,wr5W(t/B.}$+E}Ea/ PK9z xPKjH'android/support/v4/util/TimeUtils.classWsW}on6K LCC Kdi$@i!/썽8iR#)qC/㌟_qO &Y.f==9էG)4~xijl簔x^RGl 6pmEBI?R6`1 ή'v&D\?-l逛.4:]*02ؿwx`db=ArvO<F Xܙ+dșl/i7{wDi='R*qrfJ'xP|-s yp9s4UuSl.OΫfJf<5&Og\!|)U]Z){|Ul<zג1?T(P`j!lD+@\Hєѹ[Y1󅔊ȜJei0YݜNI׊cftڣIJIRzR-Ε RJ"*, +w ]ve({deƝެHiQi *sF\>D˟b=o=,,uixq^2rk9:0Wz oZ&}& 9/[,BM咗"kߗHW&3 BhJ 2Ȇ CX!"F<ΌC-qoB؇\nC؆]LLwKEF`gt;U)$W -h0ҹz+|*˪]) vтݩ$Cu>q㑴&,E#(șЌ57̛R378gIrs8Hqg(9.QF縩9ַ?eL^m AX3tUyآ6X>zt2.Z^5F7b!Zԣ)j-1f,*ghOYGg(-3(Ecch$.#IcVWԱ"&7v^-$ɾq -rynj^ l*tWr.{ڣ31$/0"OaOc[q5=F?@8E /awϢBYld%pմReʣPfاT$%3+JfP?puhM-{XtU#O[_!8n ڵ~7䅺=j?%aɫYӯpmm|9f1'_Zb~-7(1Nr7%e,Sc:'OŠ)H_Zp>k1ezֱtMf,e^'x j>5 lJ>p&۱8WfΩ˩cǂb)<[en/VE}%d"aF0 ]᪜Yõ#1'b_Am\92cu`->Rg}a=ڝD.Jq6kHz%NCD7(q;Xb%,:_=8'6،o-/!\8W ^ŷş :-Vx {Pv\&L{=7; @$C* q~UcT.}3).e%7Ѳ#GOc7soʯH }fN=/[*·aЅм|9`D5~f=.?w7`F4mY]"ԭ̤,:eZݯgfy7kYxTnyt|6 Ǽ>FgMo=L> ! z7yk|؂o6\E:6Նt5rye YԘA mړ{snoEe}&A7LEKQK{A[]=Ȩ6 \U&}z:Br'~u|~whϒKʧ(SW?a|{Wr " 0 e9T1o=>pKƩ DMF9>BdLy8PKL|]PKjH"android/support/v4/util/Pair.classT]OQ=miP>GEZT!DjR۶]avvKb|0_M&F_47ufÝ{gv3so/0Dd$Lq)H̰{ُ+džU6--QЊ{M=g_dfW,]@ZISWsf1nݔQ49تh .dYZѲ9YՠloesKZ1oF>QJe'($kEBLQSuϬѧ?hmVlM@JCU3{It`o1ϱ@ۆVp')EcIr5ܛ@y.}vbIl.JeT>&lRtL3f41+VNO|ƞ, [QpC N㌂rq@a:gҫ+/ @xwaxLPtK%>߅G߅Bzv00D NPd-C/ =4deײ”:E1,ڮJƿA~ yy%O"Or< YڡN$Y qԩNT'RUͻŪo(6@^RAwT,8ӊW͝2Eh!GO&zjp``H÷XZ{Eg PK[KPKjH,android/support/v4/util/MapCollections.classXytTW˼dH& IB4LfBҒBFB&H00IgaK]n]j]K+VZ R7(szzǪ>.?'UH)ᨎ$>χ01 RA9:$tre_ ٞ|UBXi'ɼK'x.N K٬nX9-\Fu|]j952f. LX|2#H1P[[SNVxXT39/NTieU֪b);jK w}S3nݚWHX) :' | f48N:4pOx $ D@=l ܀|\Dm~iX;Of3Ƨ)fm•+n4_gf9FByU,f!ufU 74g.xT/ᐝQQZ^ B')+/8>]f2N禭;x~Yy7+.>g8a* s@w_;__O+9B*HcO@T h&Lȑa3&l .Z!FRFML_kc%rGğhh!].^PRkq;ڜUFc4hڜa^F0jKLi ? Yr ,%O,jo֨ )Tܮ+Tͳ((1W#lAld 1o#/b*G 84, pN 5Qy2jJ;))ErEhT:Iz$Noŏ\;舟嫉)Z\PGP-N]IԵSn5.:"FlPpa @:*.Pn LXCmomݪn8GP XDڭCrurA":mt$nѱ%r8@ IL:Ѱ蹌zCq4hS/e&bz'C ")nk6,M/9&TjijLgEtmz4z -rRnkt^5^VYѷ8x{wN߅9ޫ~A,e#D:6Qv@:XPK PKjH+android/support/v4/util/CircularArray.classV[lWfw]e&8qKn54f3q[fgSh  %5-ATG $^P> |nÃVŵv91 |=Ï A|&b Kazu "]#F\🠙za\Ƭ5+00#6bUg̬s Z/힘3ԘfsSMꅩaMPne4e􂞶,'l1򥙌v蚦A[1+g>SΘN5-cmKCչbcczΰ], y]rLeK>iuSr4UVt_/q)8q]r"ĨpQڕVnm'I cIJf΅ EQ#~'1]X/ ѭZWlXPHtI+A^q/+41ul9btRNJى˕d9f@媱rkXJHk~txuܛw9 ^p@;PV4N>cPSR[~P;TmֿCGbC9ꗩ_/-#2oK눰M"C%!ư1L#DwL"is|]hf+c?W~Z u#Qyn)Q.x6f0T/a)먼l0A9Dqb)1HHœF( %#4"~qƪk_E[C28N鮤H!ˠb5e2Q'}螓]e]\';TS-$8Σ'jtLL%qHևxAl9O Mg_;h;k[[).kN {L #c @FVAXC <讬 z<:Qc/{kCy1 kGDD8Pk x?O/17o˄Ef$]42atWD+Y 'N;èB.o _eKY M!T CkA9K"nh7 o&Y4(Tc MB!L~ůCW-ŌX9ګ?ŝ6T^@3:w o~,~寰dޤ;Klg961#ńA {hʕ*. Hiw0BOoTuD]U pnUy̯"k߻lH3;R1` q>f h_6I,wˤzT{؆;tnl$tWl߰_eI'IiK\iSLv_,葿;#'_"_soRD߿pP{|DWf缲U)-" UTN*<vU!M0F'Tػ އgi0^w dRmX5Vr:ADU0 MP_^C{ڋLtVI񿫜nr>Znigv 7W}[Pot&U^Phud?<_PKK%'^PKjH(android/support/v4/util/ArrayMap$1.classU[SVJ&-%- LCҴԁD$vt"lADd&7IL:ݕTY2f\p>Agsm7D ,DHk^ (#& .ෆg{ =| *Y3VT m虾g"ezP$܉`s^wgD&_sߵUHDDViEDd&2KDI'8199!s/DGAԶz|K89 8ԑvwݙEx3{}й 3|h nG 3PKK, PKjH(android/support/v4/util/Pools$Pool.class}N1g_I(| . &R$$$rXEsv"Qϳ_qppD`y SS^=.J -\<.Ti;7 9qrƷT/G05m#]*6FMZgkJ1BL\Z׉Ս]ŚL/!Zzt\VVz9WeOWƣmnt%7pkܷz}JV˹nVOu?-T+uP^ذ/{nTk5`Hd+WtUVZyp*,ԽSASZ-x 79/|d>Q\)m`J%ش$*`IY;#dgNDȠOT`K%!> "ֆH!҉HxѦsG~OH$Ű˄KR eFjB"7#š&wT Гٟ~ sXty㦢v#]'T#Zv^i5mAOo뇘亥717mclCߚ?Ws#q|'1Fo'8)rɓ4Yao(wmc#(0LO^K'LkBYӕ.Oa BtPH %OEh-K冭!fͬm榭5k[RnRT{H&boXqޥ6Y$ަef2^[/U )WHv8W~ש^#rVmTͮ?, N*$z$vC , %!ԏPKzdPKjH3android/support/v4/util/MapCollections$KeySet.classWmsU~n&aCB }QE %mQ X DB$ڂM݄ͦR|AgO tF#3?Ψxfy)gw99 /vZ}Xq= ?¸E>,a7pKGaq/d>ć2>]*BqO}3$^6*j9SW̬MU\3MSJZkSt'9͐RrAh^Օڊbvz󕒬-ȆΦ#FA1+ƌ&j gd>h'Y6 ]Be SG]H\.3%u9=:>\"+֜f*wu٬G A[E=i ̖'֡p]:chȦi:8vNS w#P:Zb>S&#A](Uj7jR`kuʹ[P;G)*HBn *O?oH؃0px tRrÒR5LJQP jL0ﲅ*4Gu DCGI_Hx/%9z6b~$qwVh3v;Eh8yCxҹIH "-ZuKث/X%<jxC$6nW5؉8MXՎ-[ۜMA iL4TWz-n/dAӏqF `vhv>SRV~ 3`!AsMH(}qEH|s F,~gp#MQז [>hƑ.zamƐqi6IGKqS?mgK0Ga{ łm]Ŏb6C7xQx 6O0Þ`n0qt6I!9N=ErOt$; 1Zt;ࡒ\*M7Սvu;IwuGvyikk)L9a@xcw-q= 6`ǐn?FN?A/7! <{` D$1a',6+逓73D΀'`roJd5^C"Ͻqg+f7\|v,qp[ȪMD[k$ A<Y X+G+J7e6r([y' % *IpmD$! g2/)޹ BĮٯ}/sh f[ [Fvr,ErObΪ{"uc.vDŽ=:vȲ:jeWq"wZ#nKwdu E+?c>JDV@ZissQEʜrRJ`,W,غY`lZzQ(ʊl`"f!o\SJ!?:N[Z$B]&~{.s$8U~b+ L\*؇{y "E:+H)" ;?\^>#zc܉`?u" o\o%-,}p7>!'nJ7xӕ(?s+kh*FG>1eRcnVK8I|*^0ȧ[/CHx)6-cX !i]2ZIHf^o،_I g ;8s.fN6@'Uje Õu=&!~ ;T+tدKZ8U˖tS.rq%em)@Aҙ_Qկ]e{E#y<ņu75'@OzNHdu z9LOvmǤ5mZ3a>Wrg|`7>\GWqQZcik?Xs^kiU!kZ&3u2r'q|ȃ<}ܯ)Z4y5hCE8Zzp2m]ɫk-^5YRQ|-DE`xN"*t &O~mmHfodu۽lp 긲7]<_p lᷝF)v:m?,m-~)h?fgm1˵ ǫ]~DJ@7O)+,&8B@d5@Ϩ%:M :a壘KW#he jtדR8T-\ @aQjGԈ^FǮl춤~]i{MìfAhnHs<844b"Cx%"GGzB9hjݸi:7e/Gz8푏AXT$|o}1 ]$Pb3.}[xqЃ||3Cn}[fx8׍#8*փx B,<*O2{R>Oi7yAd^ًyxIƗe(׵lmYPԾ?2G}=5 y"H_TAPpPlH|0e:ñ"i]D,Ө`Tp4'{y2=nFQ;#阬MGf_R#t2[;0J%әڡ嵃X6Mtd9ٟɜFRܮl귶i$/GE%:"H*ˌ(3")͛=dbVT~ B~8֗dӼ\ifJv:D S+,hJ5Ţ!s^~s2>9WcWr2uF 'yI9c06u+ŐYѪb [_LQ %y~@K`2 5HƥӪBD;1dSOݩ#$`WKlk2 D3Bk!P,ǒi0HWVHTJhO%(/ gұDœ b{rv0?r DSX2!{F*6ԷMoeiX`'>&YU:%(k ^Tczq^5\ŵ9 Xh BWTRs`3vٻ^$^G?ʋ4#/~S^"໰x:vb$@R"Ӥ^ 3^DWfgIVHa[QE wkYM1BߜLd"x*oڲu[Lcm[Kgzxk"-X" 3<{Inlmi0sʦHGXHI d,aۓL1ABd4M@Eo)S3MB|0<}zd$A˱{~Wr;Ty vbp5Vq͒a58SQ6LxWn [~v3: 989}s!|n GP84q8࣢5(JsUTf1 Q2ʼiJ5JY2ȿ]!+7 trqsVbx۫Bo9 IIJrajuN tlU(F,MJA"<ɄyeiOGY cz>N__rjlE,1F]t t,1RF{M]Hr6-C5I8NLx{N>Âα1 aKW}} ~xI,~ O;|_>ʎ?AqNܮ3({l'T#BgCÙQ96jrk7Z\%U5c\"F0PD)<3uUApprM>y Q1M asEf=)R犦3e=#"~Fb'z=F+$\ndTVlb(6Ki lŖLo$TCp%Wf1N lJ+8`ܫ7e$&pe䕿&7*'%DB|U/Q5 ٦O1&Jm6k4[6[7TЬ<7_oX`zAd*؉u˄!WV£VaZMV1K@5h>ɼe$ɚtoj-F/sx>O6GG(|q;zk^!d%R; J>sssj=j_mV݌j3VRނ6u+0T'TTqHjP1wY|&/xQ#V>5ChԒyyK/aO:Jwph * ~bqU{U12Ӣ%+*Q%t+2e{i1M7(6 ;6 eU"Si( ;T޾^4d/|3!{˒ śoaF&E^?\ӳ':@ zrwt 18D4@p$'Fxz:$QYȗ{{}[h||F ̰iearBCm}'1oQ.Q\_%j;6\Fv LK E_RO]zHǍlYu'H5 =_ۅy M:SVQ]=fiHZ l=SQ*ۆ&!0F8@L;/EqMt^? S[c:Op,>30:P>J  bRtBgbʴO}Neqq oy11e^H:dXx?fn%մՖ9DEUR: ;l^%H/q ͪ hM6/ Wvm`}] L\-I3;쳻#Qi`C4:_Ks>}PWO،6m;9ބ[M,?{4ћ-|߄ E@<Q0E&Agi,a.J7]ӈΣĘ# R! FqRǔ-ڑ8edd$L&~אPKa(PKjH.android/support/v4/util/CircularIntArray.classTse=liRl#[lBKJAmdlJ/򬣣Oꀊ0cm =fiwϹG`+fDhb,q'E3f36R0ab,8H!iLfF;`67U0'&;ѳi$ΏKʎZZg4##asvXk)^2V&aS:Wu Y p.KM~׭$ORZfV 1weAf,ew.tm#9lXbFMei˽e$Ԛ INZڤwWam\pjDNm03!!ӣUiN'41 sywnRjK/3*)W2Fn1KQ+%';Q\:Z91bRЂn %AJ )Q]IOVJ5Ǎ긪-jlGS;J!:3yhFVYJyQVuR[J8"%R. zZOG9#x oG 5h#vUlg29j->FrXzb?%a \""$R[qsLKSE{Ph ^Gϛ_Lzhl,Nۖa.|^.z䨗i)V;%'7|ڙ3B=LD\1{Ybocruf;}#J B?\HTvt*/ ?Gs*8$P-&Y,i IuJ2.WOAUDQM(ԑA#wv%F+cxh+Վ9&.)tNfxVQQyO%V ڨՓ0C$֣HJ!WC$HGJ\`abkBُ%WV寄\sUD3_fb^CUjQ(0L*BuсèA/_~3@Lj>8I&IW1e,F{1]!KtXp[1a=)+نu@-6U\E}V'Xě!31ˀuxn-صSkݹ-q%P/l1<"y4͓Cy9g68\w9tTܭ}Uly\֢$RV@g` 2M #ᒈ3{$8<">↧OR;kN%rXf`żCux$J];#xfx2:Aj gPWBy-k۪~w|9iiZV'}Gc>$>i[2򁬊q|Pr+Iܢ$$ !}n^g\9e$'qC8/ط<Fe#P?ߧЂgur/nX7 -(;+?%OyIYls"% fF]3uM 9XY2PO*b!rPKB PKjH:android/support/v4/util/MapCollections$ArrayIterator.classTOV=7q[26- i U 3ǎq/{C:i{5\Ht}߽\4|}Sx%9{(GrXr #'%e+**jg @ej $;um :5츇@a8mϵڅ^u=pX,}.|otkm-r^gʖc$sCRsۤoX^ص4ܖao%QPƪg}3|cz5L`vFF>M w ].U(fBumG$4|l7ZmN7C`^as<dR#[֞c} \t,ʋ\4Zn._,Ac$6AMDc&!NKWRخS319N 1=>@\@O y|7O359u"&\L&`Ɨ!˹F0wmF?rM诡%%2*Iŀ$HC [U~0GGR] "bRNQCBy²_+5%,q|Εɜ!✖lEg ~p7*7=E 9CPG[,)g""=yPٯW\O/sN/8U< .RܬϑPK XS@KPK jHandroid/support/v4/graphics/PKjH=android/support/v4/graphics/BitmapCompat$BaseBitmapImpl.classn@ƿMܸqMK -ҖHH$Hi&Y[9^˻)❸pC!fm+HAݙ3vp6tEC[ 2戡h18m5 +]'N<:㩴b1 -EKO:$b;q,vĵtA $Q .Ô' lQߤCjLNx°јfJ/^Ҵ0VFܵ07RŭOF86W/H+tpbv}<ƮԊấޅSWM9-2,.G+\c!!%o\59p ouh0ed}!J%$VgdK6y&r'\OHp#Kx +e˝M! D^M@ N;M=r8[TaބֳwLFFjU>,PKvPKjH,android/support/v4/graphics/ColorUtils.classY |Tՙ;w3L^$A7&03L&̤ F|jmb-VjAVj_v[ikٟ?ڮs<@_{ws眛N?w;t;%V1KZE= kbְ",N*Zjm;u,Zv%X \lTv1^E"zקGiT!|9z[7s5b[:s sqqWZUZC7Rc7N}^ְgKV//}BOo=MAY&u+O6ts䦷@zxr/׊1c8Ӷ/id[+ƃ>Zd2>b81):"Q " C1Ogk"dUp(^s8qna=#]ZPQ4a&Ty‡g O{1~5{tt6giYN7lNHxl5;㲒grG31Jss>CC5\Lq%66rQmkEGrx'Ekp}I_(8m Y y ]!ack g ^Pjú?00 )rp/ɲ!L:Jk՝ G'[e'v|[k *^3A!EkzQRNx/0ݤgDf1WsXl92eѨ`!'*j@7do rLnyrЗqлbAߠ:'zu ;(bb!>'$uB' LL3vͤPo, NC~xFY2}t{n?<aKܥC\mSNʊtJҷErЛb/~ǼwC ntQ蠟ӿ;Mfs[ x97l3>sӬZx#@v"\iHJĢc"@bH^9%vybü#4ˤcӖN0b66RJo7+j30[7ó<>zoa¢ -SƖg)3'FK^PwP{lhpB}N|ERȇȲPBIqm 85H UUb%P&b[ k|> (M@Wy.'Gi*ZaSRW d6Vj5ւZS˲?LVeP=H˜u9Tk+tkwj-ȂfM:siMzW7墼JNhno6:v`xI! dO [ݎ u#y S m`ԏ_R5oWWP?D * Q ND@Y%sprBiP`kd*3CB^'gnN-/s|i.evi4uxm>.:Tg7=\`3#mK]ZC#; lwo?Džᙟev..@es#i* ץ9#9eeO,lFsʦi*Օ搔Щ'f oA@sRQWtԈсt8"# 'D7 oc#y8z~,?&>_~#ڑKPiHv%(-0Iq^Y$7-WBkPfAo` [XP6nv\]FlorHi# woMI\s@d39\VXINs?uǨ;:ٗ7R;yD.Onz?sd^:1; 3y̔+J\H_`8\H B:2TgvҜpzToJeZZRkSjN0V? i<'g.wd*jh  R[}f}!Q9K&b3f̨uϞ|N g/|IVEͶ8is$NSe2~ `#VҟcܟC]ZMQ =]G^~S'&zR(P8Š~+C.l"C8DnZ٢wj \bEb ).6{ 4:% tSƕpqP[*?GrхUpĮX 'z$3֊l"..,gxD!%W*݉l?>O" yY$՜veO]<ڈ&*).J Kp264Ya73mWCjVZ+s:nL~gaQq4-RjԚ*s *Kk HvQXrr&r3\Z(h|#Y<Ώ827UG רqkDGp`r0%Џ[G1뻹@Kw<6Fo6 :>VG*F?COƭQ %pV|sŗ-#u$QK7#_mf rDi ZvkYY$Z:{6EVZHŷҖoaV*v<~ltɷIdYqјo\d秝;?`p|4Isa}[HG[Dej+^ߥ5^rmmGq>Y m81 ۱G[%vP8 z ~$4_ʕfudUB{Q"vf}K9QFsu\}) ?4 s#aȑQJ :೥̏Ήc z%;aFXlE-kv2Qd9{l>W4]{q2f23L/@ ;AWpޤ{[x[y"=S;e\>.UM#qj i$4;(sY~Pȋ[Xg+\4mh1h^A`1LӮW&}~⯔!KI.>/ _7S2}6sd`mG705cK!()"䱄ȩLf9[!p:%I˓-SsCLrylW=DC^X|,Řl)sa.S 7/R˦閍U*Ѹ1/R*3\5jr')˴4:p VQi55.Mۈ B#.$'$  L-It#E7K.T*G.UR3Kmjr{x7"+F}=*+Qǂe,ISe}#՚zԛ66D=S $ֆeΡ,x?z8!}[tƂoD_s+Ŝi%9e&fJn.)"ޜh2a}Ma*4mR"R&I #TDVALqKe6[&.6bG&٣)E{Y嫔` gh_ S);'/J`yb+ Ap,*=OwlyD1/ Yˁ,VPK^^P(PK jH%android/support/v4/graphics/drawable/PKjHBandroid/support/v4/graphics/drawable/RoundedBitmapDrawable21.classU[OA^֖ KQVn^(բI1tW:v7~_LL5>?xfv)Fm&;gf}|s~3EKzd4v)e\g7dt3[XѐӰ_512f0]󭚯-Ϯ%˵^S%O_~pr3 ۴z f=W,qP]2*+>釅C~bA<"`(Yz&iF(z)`9E!~"҄&1Zf;GhSnE8AL~J I-^ a(i`9a$*E/phd(>.Sqʚ!R-.`U&;G!s(UP@:KТomLW вCQZqZCvݫqBJ8Mcn}U"OY}(Hg%A)-G(Mp=qr8&%%L~ٸȽ'NqNAzTNGcb@ yL\v#kPKaPKSjH:android/support/v4/graphics/drawable/DrawableWrapper.classAO0`:AQcxP{ FVpdK[y6}~Ʊ+_3faz"V[z KskEI/Į[I4Rd[dIZ2/x"?ZU\ǍDj:;ߞΑh!GCBrx,59|%'kF0޶Ka406(]t&U1W[75[9\L3:9ofrlF㘼ho¢\5wro]ӛZV=Czag@c3h{n1tĊ0h\_9#7bQ!2b.ŹDUvh,\h<[,)\e\8}y7p8DP:lsYr(íf +ٚ+f!Ī判i }#(B禯]JJ|!>ETFAmQCl!uhPmct/Vzn J7rH]n1K(Zhds2_ՖF^QSFգ#w^7ReD֌ g.b[%e.,gV}װٙ˛NַO2.resBpsɓ8e>E縉 m0?84rwnzRdF5XZQѐK͗?W6-#OCӨq]ΓNɤyKe6p)#4&Q{>ݏ5uZ.$8O$Mq\5r؁ukt!}p>Vw:/bf|\¬Cd 0Uv )^m6bfiRd@ڣ^o<5݋Zj#u)%P=ޓV2`&(ԦOݢbc9c;66b75 75~!TD`'S >jxd&.G2{ϸUk0m1G_*EYT>GZd2-a\Vqzv*$Ǩ#9}zҳPKPKjH9android/support/v4/graphics/drawable/DrawableCompat.classWsW]IZږ楼$dpyԉ?bqcᦡPZdñKK˫MˣRhIc:P> ܫjY <{=w~瞳^9܋"/ȇ/><ˇ/+|*Ƈ|&Ňo#{1 QKx)]xA11$a Q q \$>.*6CGfQo *2M7T\ҖaeWZֽiիTꂦL嫝,S[YŅM89C՝Je|Lu* JH*3fKfޱm9<^s(kǭzɍP_ָ8y1/C>>OTƭ<-J64'nʩƼ9P0$@fW}ӋZ\]t5OsFM"eַB-Vf,s7x]rgxҲElNd\sꆺ832✌a .Ř)|TF32U5ǣ2>Oq<4(p@ԇ鍭92~_Xސ[Pm掲^vڗfj]{տ$sl*Zщf6_`5C^݊ .] cXpp zmsҗ+-%ue7G_gp).X7\e׍|r~\ fsӏf񷈜SB6;w~ޅhփ(oEa>LXC(\Glgd*#/ $[90_vo pf16#0wLL>PgC#!Qooأӥ BɜN%Rŷv$@{Ke2;!ɘYH8jsgg1l!%f5Vs\od^ׅKH ]LM,,fiE J<蠩#:GOP+ұ#YαZ>Vl]s>u|D}\N7:i^r{5D:U,P Mj~w6!ܭ@Zu=x^~Ouz zP7p3kŠM ;K Ƨ=݇j:l"AK`°g_B_C7Ý+!^}~O [֍eAf+u?5֩X2W=%vwƠ^`Z}w+BlR gI\Éۈ]oNCBu- d"-TIu@&BuTPMhzIx}pM^CMNb7;(1fc ` b@;Sl9mӔR;!v_)!|NquJ.:!O2A8$H$9H }$O#>}Г.[uӧQOe3s !qp.ny pDeMoXU߼}CSK6,m =Xz!^j$=E@mgLvxyP ]upHlpw/^ {+m]z -0V=3rgɻ X8[8 6~l0*bnR-DүFC[-zxtGS5'.MSJ*6 @PWtwczGv["~=FVDA.{H,Q;'E:7Ցp 0kT-vYNPtG{Ae)\Q=m@aE'ajx4om &W;]VuD$9%t9zTGCXV&(h<-WgʥV#ZsV"uunOrnׇF}`tV^]i2B`,irih$ (X@6?T777Cl3yΫu ݜvQD 0wn4aɹ2g$u,vH*qBg턢]lݜݑP@mq(0.6DBX;'K]e7  =.1;V p-Ol`r* %Oi@ʘnb94Hx4LC rbT1)~sBb¯_Q/?,:p"NƂv<6R s栻KP+2cARld^ELP&"2b6Dfbئ^]C5* \Qn-0aMЀռ܀2h0'Hx0ʀb\BlӰ \΃]Hb&F _—55QJL2FhQak8giwi8k4Cb,D%ޭ&&0T%&jb)bXj1M>Q8 7ףWÍb[p3{p8NzWb@}Yci{QJl֎ 8^̤?l51Ẉ|")P5% #uiL>vX6O`@ תf3jd~AQOzUUm~| Q3R~ʧ^Ěi*4̡lT}m1U,K ٲ*(r  n+H,1=|IfC JỸhEcA1usY4d7DLmMcsUu6Z+s$aRY7=GI7yMe/VӇj0Y?sW4S+yAu=V~yYpHyQa+I"&r9CKGUT䦲7cb7rp8+`˷y~hC>VNqc3ʰU8SpASǧ=-c9ls`zf2 oPb12U#a>2k@1J ĸ^\KλKDK@U\^\^tD(|8H-n|bYtG C4ݖ[4+NI$8{$ԇily-Dui[=Lř&yԖC5Ž' )"l]غ|H#>Cd% A I9P0Fx^R3酪$47rH蛔 AyW%Ӑha$>d6:E>$!~6rs;jkL ;tcԺB:y=ُqcoCTHɷ>0'TukiȻ0ɜ&cJr_&~Toiڷ=܏Fe?[sP`rF"\x 2W0_)[A*r[&Cf}x/#1>a|#TsHT- TG뫥V`>hz-Alʤ[dZuAOJwE6kK:8#0ã27߃J)-c& 'E6FLn,)xUT|JRq7ή2c|17@lHn(We(h1"rr쒚uS -5 B7X=#H$Wɍ?EE|>SM&5W ||D|=_'db-_߳2ѥ5%T"=NG1/? #נXL8q&ӧOLv"k2c:%2)emhs*Qi<.+$L3.-􍯐Q0qnJ%`.l4^ @75u@z[фRt֌E+i"ͷt7?M*w)R_~,0ӱKZ9 [QLFlGXC6RRYFdL[uc.egڢGȮLN[{-k깄nSW9Rs(NOm%"UJilwnfTZIXH!"iWL4jV)uz]uS(90ǪV$vƶp3wh]~,#o;:*'Q);%Q>6Qf07Rdɏj+e|~4~,Crg_z A;)i$p-pՋ8,TuުwHYt܋O@3HYx@A)\%,?PKǡG PKjHBandroid/support/v4/graphics/drawable/DrawableWrapperLollipop.classWksU~Nvp)!4E"XA(VJ/lKTmmݰ)7A3~'9sN6B/LNyy/ox?Dуy9\ Q|pC7IeVZ9iZֲXVp[C^CA(p] 1׸k,kQ,of筢SSmyLOe[@_B:8vˬQ >aX4J9vX5t~9鸇w(q't:=Wx_ǰ>ŘQ9| 91s|K_cP qk94Vmd[1oC}{lƻ;2v(U&u|~/C pEOZkV jSPw*VbjvFVcv̽F2Qey+5rN <_Lrf͌MLefٓ Aݞ~cynr2|X4*A$| a4 $BL 8FhFke/C s1bH+߀8M`}\W`nU0-d6ñ5"Br1$roђ(rkwDݷ+F< |\oAކwh5)$N s+  N;" Hv\V;H,3"}0;V *Z?O$d /rkh}m J4;Ĩ̊뇒3(Cj2縎 uʺVڥ<.qKzd[Eko*p1P2gg Lc &]n weʭ5Ԩ 3p̓:M[ ɮw]OfmaѕXx#+}I<wiF.Rf NE p',=+Fؘ*c!љ>=K=PBed+4{'ޏ'DX&r7v T#Ж!}10Zogp aV*T$Tll#)>% 7r|7@~|L (TI=>2E9SF"a#2o IjVP&:qEPK h PKSjHYandroid/support/v4/graphics/drawable/DrawableWrapperDonut$DrawableWrapperStateDonut.classn1ϝ:M)R(]?Y Z"EQ@K7ZW;x]`U $B\,JQ%f4};_~Sxab<&^+&a~ۊcɏV vpPoi-V&\oy19T<%ضх[JYzF;]je'ele}i{ 6= xLxaƪΤkDX_[hG*WV ֆah=á(Nef18. roPK<=PK|jH[android/support/v4/graphics/drawable/DrawableWrapperKitKat$DrawableWrapperStateKitKat.class]OA߳-]]hET DjH h4N`v-KH4gg^` Iٙ3>s~ fA xLUZM¶(V ;$ZڭDL v#tՏl84ţ1rzi3 m32ɡUzF;]led'Bu9Ҳ c@XDqKvp8)'j꘰{e"TlހKEX˴S#* +INk0e4(n$y <#ULʡ[U1up1}KC,We›h?x~px8j7?;}Y#/|רs,düDŽ[ 5p tqwNA~ ~ҬAhPFǓLJ4sxԩ M<[IFGaVt`9h, q-|stzt}ss`eяY,*͢47)͒4oKsKeiVyGûn3/ۮ-f [ kh6,\shfkjN&Ŧ0rDZ=]⺖_r (zt>F< c뢱ަ] zjbx3E "C6<DxrY.0!'mS{WyӭӲ +QuuR*Tp=oolLz  Q.MMCVBeܪзXQLr[Q), U8{4OUGU(fy dDH0=$ܥwԀ$L늬yνMAQ ދ~ xӯYA^a2X 5ӝT1; 6,vl;T;^K )lwݡ` Xp[ g%.Jn 1f- _T')N5Q_$0wGtmfyn# ԲbZR~jn:ᰆOtDDOv}+c02y$..WGM_]}xL}w3C!\BYYڥoJإvi sa2pM CoW< 8Fq:VqL\E|IG?Ϳ̿PK9KD@ PKsjHCandroid/support/v4/graphics/drawable/DrawableWrapperHoneycomb.classqO1ƟmCD@&9FcP1.Yc)ٵKHe|.1{}(bqM梀E+V6u)w:ގQnpkHuDυRȎV˻ pѦ{\aAw7ӗx_ˌ%ұFiY ÃA }vAWΦI\9 J\ƾ摿#6f(9$aoGINdjbӭ+IfVucJd,Bސ"R& L[Jn ]kadgٳvCrO-̾ą?DcB;eJ0<4pZQ#aj׾mר5"1A ÚJY Qr'a4la &--2Q#>цk0 Z\?.&Ϝe hzdh!H/B3>'`5jPL2=a #{=< gbpyX!Dţ6xѳit\:&3KY@a=LȔ#^df9qmi*(7`*oC(.&J0tX&*hG?kL8ݪ1<`(mUsȴS[ZqoPG2+ߤӫQ T0\M3SG1KZF9ݪ! m넹3'QR,˸B%\=#,Дp )|쫶B{37t!Y1SfNuX#4~cq,uԟU8k]nPK-pDFPKjHAandroid/support/v4/graphics/drawable/DrawableCompatLollipop.classV[SF BiRiI&q\(FDj5!o}c2tQ]+S3ZV|s⯿ Ԓǽ$XÊV&4OBú|ؐrؔ 5R8Y5E8ǽC2. Lx\>/za -^w+H Sr-b ==n߮nJą5Bl.-e!+XGW+gtò%2bj62HbfqNF9VljHtaՌ*R/4װ&݊5EX5 Vc,*)Z˜ fߡ=Ⱦ.ۯ29Ud1{! ezJsh.C3 &i EKG؀%78hqy_QM^6D[wBHnؐ-,[Prrq$.1~%$[*PKs PKSjH?android/support/v4/graphics/drawable/DrawableWrapperDonut.classY xTl/3$,&!$D*%L؄Zx̼$#Y@ZZ].Pֺ-*VkZvZ/vnjwgc"|g{s={&Z|hX|$Ϗ Oz?S>yݏxɏY,h=Y?e>(?I"OG/%_5߰-߱=?#aЗПy/C3sgܫl//X1^c:7xXɺoH`K8ipi­ &4Mxvƍ=ΰ9n 4;cTR@FxGH$̄&|V wXپ{ ]\ ] n#BA mcL őT߶TQ'à7^#P􆎂'mϒP4\*o:2xX4Ij>3Kfbq&r~PMEv Xo2!~V/]ɡPB` V$F`qHRd(bvEw![očImFcEN22mho*&\FkTX>v#HXL1#P^+UlGT*枴񘕻(&;٤M*=Y?Fi1 S<;nھ' Tv .(ER`cȈ+NgNV`rEd,a&%V(:HBx:\M]{f6Xxʒ:C!3[%z.9@-%#0M G*&42o 4|.20 024M(&Cd xbMXUFuIyevl#h`d`f|M 443}dtU֭lV۝~٤OZ농@(y-z7a5|VI2UNJ]d<MCUw*=T#ZV}^| αҖw3^0`\: V2ꉤbfhܗc]!:%3'Cf0ΙX2}h 9m]G53dP$*-&XCRѣ/>al86wtBv%M6B|\IS>u@_&6j[<\++.4;vdg g0륊DC,)\ª *D6}6Be}4ձ :A!t܆[Q#2ܠc6aXH؃ u^\+~UTt\Ixqq#nbq3[X܊b"UGG?73$g#uQEnd]LSuq FtQ-jt܏tQFt1C:fziW:]dY^y/nMh%VOmϏ@x֋r(;\p"zOn<4q#&0>7d>FքjPtu^6S#l7nJi=Ni*X~KUZ;9ZY}fU522 pjO/l˹0RurGKzk|,6mL:_KLo(im_Rfc65"{YO_^쏱2Á耆)|zR {3*_=S9%H-4:~[? o90wKsIVETi(l73,5l;%x)#sBޅze@`\ ey7:跖-eQLGfaNc$ЉXrV9'2\j x;4 hLK!EhY=1s(jHzcxYYrr4,rs?ӈ33avv?;'Ĭ\|)Fɰ@&$m9apgwK$hWa}dgB6>9Eu7cˋ 'H* 8uXdlRPpr2eHQ~d\D1?q謰Pp;AyXbFPz'\KJ.ݟف ULݱR;tFJev ]Rw1ܥ ]NpW\5w%]UVNy\{(DEItSa {8ٸY6D\cs9VYWO2 ~Hz˘ 5J\Z)g[c>h5igd-[9zrC-ZG ދJ|"5JT_]s5A~ ouufB}:kU֖fΩr~R.?4WBߓWB3\Hή`^[ ϴ?W>Y<0Z Nrz^۩J,Z~/&NChqc/d>AS9gT`1pewwd2eaU&8;vd4mH^rFNqqu,˙;iَP ϢaJ@ڋ9{rXOa;W=hۦOdEV-~2-q~{.cXH;jD}X:VUuZjN\stpi5winv/ce4ƣ\n 2Z]7asltӺ#x2 Gp#2n}Xx@C9N@ }M7UphjΫ8KOfsW-pQ.*r+2}~_/= $žLS `?DY9pz$&Ns{X9g[[(`BMUt 9#YN-D5ԵDSͮh)V4фMۄVs`yr h ̖wQWu9AO)r7q&5a~3ST)vsήXsvǭyL)?JF8[r*6hi L]toѩԡn98zrɞ`9r!ȳQ*RtbXZ b5.!֠]jэ~99Y%-9d8J_Ure?|sPxE]7t9C,g-[6wX!K! eXErͲv]^ʠvQPKYB* PKSjH=android/support/v4/graphics/drawable/DrawableCompatBase.classUkSK=l " A]Q|h"(ʪ% ݚE?ZeWe$HVTeg}Ιη?8{iqĹ.84%Ll Cr#;v!Q9Β;΂GRPqp1!W܈BE.Q- ! {Y8[EFqzϏl[x]NCo./0H Z2c;oS}k*lZ`oicG;) vӪ;}@?v5Ak+}1RF ш{Q؍=ZR?Y?:~ۂdIƈ$G$G$XO%IFHNɩ-Hch=0NRVIOH>oro~#9C$g))Ot/A K T! ?zRkLkxYMS&HaRg1BRt}R N} iY*^O-`}?PKjy PKjHOandroid/support/v4/graphics/drawable/DrawableCompat$HoneycombDrawableImpl.classN@Iƅ6ܡ-P!^qلFF8 g x(c @Q%9>\Zл*HvJUBӰ&od4ǃ΍dO #J<64?4z N]J_W>jpJ8"`<^TQUaēg+~FMUMe:_e|gf$=$/Vg%:c*՞_Ǔ/ix`foIZ7^~ l !ip7;svwxV,xO<'+aaNJsq#+Ciph-v"ToGi6Ftʟ8cFtctV&9K^lEVѾLMfc_jۦϩwlp"ChvM,CaU>^3ř.Mrۍ,hS_O^X&n˄ͩh?x~UrAo X[u<^ҷhp*dü{ 5q Ux"8@xmկY݃6b*B'h Mf AM PK*rPKjHRandroid/support/v4/graphics/drawable/DrawableCompat$JellybeanMr1DrawableImpl.classn@;q0B@R [RmU)ʳʅ3!Ĭ~!"U3ب▍n厅 g2 `P0}p &DiG[\ImN3ߑEzӈX^ijLPzǡJde4MT>Owdya1z0yg~DdH%\&1C{\f2{:IDc>>J7B9ޯT ^Km̜w|;p߁fqBb$Y)jai 0ЏA/ Id$R"@;?æ9 E\" ɫ][d$=ReC֮YPkUfJK[:]<VLcO1-Uڽ^[G#FM4H6I31OǬS<PKE LPKjH>android/support/v4/graphics/drawable/DrawableCompatApi23.class_K`Ɵ˥iVfE"2PA0Uئw.}lZI9~y Ve.i  c j bUk^]yKDhn[}N[ GU7-C4Uo娃eq-ڴ+WdLbP$c[s;eai G:|ZSsuYhsϱ qpđv[ \ )[ IFQlF-!z]zsS*[+"@{&=[LA52`2(ͧQ!|7_WNMƳHd!1HXDL4$C&H6lVGڐT73;|;{nC YdN7qXF,n{}=,=JU~(b@SZ(|XBɻ EcnօnjUڡfTi%!R\BP:&F@RǕPFj BZWQY _̼ k݂UarX̧ܓa&,P~c$ѵ@QGR/6e\zrݑa&AStPdЍ/֫.P 4 Ng ۉpcՕz_E'D(uf1dL'r sK/[+/3ӖTd)sG'>'T)|>x,)| &^7b <}\CGWsw zfO:l v.oLmzct玩޽WdRn̏`5j4?Sl}:z+Lp?#uwAi3.ā)c9Ap dKW$5qZX]gAB\[%\d5K\e'@a)WcW )]k(4\XL-1&䮉.h6,.JyQOwpS0ZAo%N6p2`2p_I d\0:wK2J^bVfLnVf;Jn>IS`ID+*NjU¤lo3> %`a0V6ϻ^-:xJK[vj z2ŜMa@:i_imfн?r;iZX`ܴǎOk\257YYχHQE{EB|;Nx~f __\?\?#ڨ +A_` 15dDŽ+ 5pstqǙA#xzʗ2Y̓DG2Q!n&.h uPK?1PKjHGandroid/support/v4/graphics/drawable/DrawableCompat$MDrawableImpl.classQOPQ Qe%6qh0&d0Sy~mwA$>Ps2݂2swNm@lqC u [+5=! Oppiz"8(., ;*Xqpk>< OT4Z9b |&HE&*2?\Ķ  }g$"ْv|V6`ɶقQJFT9A{Pͨ%԰jǗS_5t Ze` ,@1s2XaF0am=<2W6ʿPK ~PKzjHEandroid/support/v4/graphics/drawable/DrawableCompatJellybeanMr1.classVkSW~ F!/H%$X"- T hp)PK&a7٤q:iNY tvy=_B ,fǜ1í.`>Nܖ E҇~,|HitG#+MWU?|'M#úg}aY yDY5mFqV 4Zi9ΨO͚--o릑Yo `i&:u\|IؑƏ ݾEJئ7i\F7f{eMr5jyStw^W2QL*ӲDR+%=_M,giH_Q;ZtOS5Aj Rh8rpi \PS9W\-_j]%UdDki \Q]"l5$V@]v.ƠYڒ. *)`H` H"ɲZ*- }V]f@yycXRI͈q\izT7Xd߉Ï @IսǴT?)P'{JKOF|>Y]W#Q`C 1(0~ VF @IV,yP1ق8oC]^[$;] !/V82nG[/.t`!תUqDwvz91%]`,,dj|uy>8M7#v;C]^lN8aovEV%3~rqAPA9{ݏ8{9BƉ 1g#(J1` ]h'=8@3'mr~!ʆτBo0gg=_]GZK/$Mdf(gĨc> Z[XOc>36%10K=Ǚc !\#M lqB'oۯ]BtfF)&$qgR|a)"-BΏE F, BZ]s ©$+0EL< OdSq wYVa9sG#6^o][gbPKZ)S PKjHdandroid/support/v4/graphics/drawable/RoundedBitmapDrawableFactory$DefaultRoundedBitmapDrawable.classT]OA=C?a(H\PH#HLH4@WfwZO⋉Q⃏>wfb0jlΝs~,%ъlƒDIM%1 -⋶c%\%:R8om ߭{E皏<^+EZerc; U$: #ի{{*I"pV0egȬ}^-D)[aPeGx7뵚I1{2K)lN[EzG""$ڞdH) CrӮmhvW{ж-y%mCQedǨ"E [˵Za6Ey鴢6B,3\.2 6}PtJ PK1"U͑L%:0Ex z\]4!_ѝL!RhUcTj9FkD#olq]6̠%dP1t?P~\ +W}t4#@TӐKAmsh$E-: i:MPD$ENM% PK3AcfPKjHFandroid/support/v4/graphics/drawable/DrawableCompat$DrawableImpl.classS]S1=E>+Q" ZF-,nv,_?x7 3N]aO͹{~QҊwu91FFn 'k"j6xӈ0[0D++'+])qz34F`KáͨOﴳv hf7NeXDғ(nN=iM2;w:rne]JC]2gȥ+jISVZI.&ڹӸ^2wB_gkWK4d&t*u88%U>T|(h`DC٢"-3%C(NL]ڢ+3TQD#NmPj.Djat?Xi-'Jei-%9q%L8#]=8m " [lZ֭X#q,2+$ijm+.o ilD[G4U^2LmbxPV6l"_r ]gw`Ar א{u{! {A xwA淺HD1aejWbXlP7k}v웉aO ױt&v'WL7[%_{+(PK4.|x[ Bl2s盛|~(P ȪV;.BA!q;MR _K{Wm Y|c2$eȊ]נ:8-=}xw%wzЪOp/7 .ө#1sʅy!7CmԄL}<%T%\Cu%wiLjV*-@wȰ-e(.H/B *e:E+ǯ`/vX >bĸV)RCHw/S`Ά~z`Kƒ+wPK0m^PK2jH@android/support/v4/graphics/drawable/DrawableWrapperEclair.classUmOP~VZ6(L@|a‽ILBKBcnfkێ-MK4݂`Yޞs<lc;IÚ Hb-'(((ݶ/⃴M!;պHyBm>Au\'#+[S>qڥSZcuw m߫ LqIq!GxU~nK'|'aj6=XW/C+D&Mt(2@xn+ U=7n`I[Z*|U ?";nnBJvC0osY59/,vu$7Aoߤr$ -Mڄu{bBRN)#'o.܍mb f8ޏ r2aa&^ތ@x=rN?}95bcZ闕Q<xЋ+[ 1fS̰5E.)ck>:]?wX*wqҪFa=0 XT``IE?ģK}?X:ו(0"IR;kqWOZQx2s$L^)~puk&cYKPK6`QPKjH.android/support/v4/graphics/BitmapCompat.classU]OA=ӯm[)E PTDHQ*-(E hefwK1> @O2xgƗ9s=ޙ} L- "1>!)"T+`R{"r| "qJ4CtQe;eeCeCcbf4ٲY1o־PʛcKiRiNa6'cɕMSW7V0tN[ll%+qZ=ؖjegDE CKN(KrQ1WFL$dmM6UnH_dj(Jc)B= l 10"FmUJFVg|Պj2V:Me.i5aW1-O9v`˥wTBvW͒DFiyWЉ. 1tKqSn 0aoQ6Р#hкw_=0ج'ChB+[R"*_4CGUUmJ!D]-.R1FOI^WL3/tf?˅dKYy+4Jǻp}$5 ܴrC;"aɑoY 7H(~OH %:T_,,9T(ɡBGq 2`.Ŀk+`O$?m u9*OtHɸ0┕@pJ[h# ^& F ;|~!PKAVmPK{jH:android/support/v4/graphics/BitmapCompatJellybeanMR2.classQN@B@#p/c@5mKKJ۴w>e.nΗ|{p= )4# CT":cH] ٶpشn:Th{w<<+*P mOp^-cp(8јѵ8O ꐇwP,HO=iƘƠvt5-fj$K!-z0\G j:ehs3[sd[CqIg(R؇B(G& !vF{ B ՘ [hտ X#`xKE.1;!Z\-;Y32،ފ5_PKnXnPKjH9android/support/v4/graphics/BitmapCompat$BitmapImpl.classN=O0}nK҆ClL4"C#RWmN%?U6B $7{߾b#q0XK;'p6'\&3+דCꄿr^LNKo2yiŋ4}&gHMSV*0jT6c0,p|s\$ Ik-Ls1 w\.ZDC1F?( ;q$$q?PKXagJPKjHDandroid/support/v4/graphics/BitmapCompat$JbMr2BitmapCompatImpl.classN@ 6i"?(*DwV,؍A; ĂCUqLAjsε&X3Q+6n C%}c\' R_d U`c~Nx$#ʑ먝Cy !{cz/TQ$H Ў&a>߳OGr6FLZxN鑦}n s+1LYg('{GI1]$]mծoL9LX;V!e鬄 3v2e9Eb[PKСPKjHEandroid/support/v4/graphics/BitmapCompat$KitKatBitmapCompatImpl.classR]KA=YeЃPaRdZ/ﳺ~; ^z*zX{=;%f9l+(0υ-d!Z*wb3mawCK7dN<cr ||K !-jrٴ\!մmLyuOSqw :M?[J&5#c79C[2K?3rØ07!y q%2+|SXB\n kH+(2\s 7w[ 7,*<(? V6 T,@_^x^blj$Vɳ!'3"3된h 7HG-?F~oPKEkPKjHDandroid/support/v4/graphics/BitmapCompat$HcMr1BitmapCompatImpl.classMK@ߩZ_CU0h bzQoӥ]I! O^<)(qP0ɛyޝY!6Hc1-2W0Vھ'k% MkN4]VU=Ӧ"Bʹx"i/.!W}\EoZ:4cn"('[VŶ4˭jȚ^6rlyfV,#dou7t䥊Gރx9#ca--7xdd_3ByT+˞&wD(ac|y'/g6Gs{L"`\YLs n!(pb.)o Ş9b PK쭏wrPK|jH4android/support/v4/graphics/BitmapCompatKitKat.classPMO@} HVQT*ƃG c"$& mmʖŋ&(!d̛5Tl¡*CR\1!CGPKsbf B7PK xjHandroid/support/v4/speech/PK yjHandroid/support/v4/speech/tts/PKyjHVandroid/support/v4/speech/tts/TextToSpeechICSMR1$UtteranceProgressListenerICSMR1.classON1pGx4 2)zsYzm|H-vv5375ூ'­/|yyfMVBnÒ dܟы̂mޟ49TsMXNiDm=|!v))I<pb8i.F ?T}+VCHY]`0O lfOPK|wPKyjH6android/support/v4/speech/tts/TextToSpeechICSMR1.classVRA=C P  ɢ1!XD-T\YvSw||*(MQ9}z $R!4~# AƘpI S!HいIxз,53_V掓Mn'bv!4Ɋp )em}A-gueq^I$ĺɽTԤЖ~mkEYnc -qtf(0ܨ'(եl' MӺ_tp 5д %![F8Ѐ,&D D <$oDxW A,MPK m6_PKxjH3android/support/v4/speech/tts/TextToSpeechICS.classTn@=ۤqb6 7(5ZEH. ^@.P^/(+uRmQU 3{43xnPq>IL)"iLQP1YS0`!Q!_ٴZZ^0ExlBayfgHr.!PK)PKyjH8android/support/v4/speech/tts/TextToSpeechICSMR1$1.classmo0nKӥ+`}*2Jj]nB!xC!YaH*D9ws~.=籀V< 8Kq \9x°yb W#%kݚL73Zkġ\3BS2z4JeqCλӖA2p(]Cv?ypRz|ÐigXjE?4ܒ0*` "  ݱ8R&TazC?ED?q(?ug2J JIE#k)17#o"O-<Xjr3=eT=yXD n["q2lYtn93 GWZy 뚛 UM+l v2B抴 }NDZ)zZ VGP % ^XMKٞB n2Cn)N0V[-1[93isj_\\ 6 ܧ4$kq79 l.E? }rPK@PKyjH8android/support/v4/speech/tts/TextToSpeechICSMR1$2.classT]OA=n?E*EK,b Ƥ@Ҥ|H IdivDc2Y0J&{;sΜsg~[XYÓ(Smxn`*9 xP>QpQ vWe(Vgض|[ <яG[tcC#G[_g/ir`_L^Y˺)K[9^ʢ#xOo+|@=U3OŒ3'pm@b>:c<6#D]ߊ-`PK/> PKjHGandroid/support/v4/media/MediaBrowserServiceCompat$ServiceHandler.classWUdސd[BRkn˦P@4[[MZ)>dwL;̦->@@E) HlٴD* T-ZQ|O~>~xΝMIg{9?}U-`#5#:ƭFuDeJN5RaL1C{[h˂7~ yxc?#<(7>cnO?Xyw K*a_|]7p |kxP@Lwʚb bLfltLOιKyRqTYFj~9nUE) Fa@^-KYo{) z DNhlrqtQN(3\Ub \TճLW *4yF]2+KU u} [&97V֮Y3AviaHCM깅ܠ\g*,.ƒ=e[>`aSDَ-\H7yݳ\KYm^n6 \4%Sv ܺ^g)q.}dQۡ565GPB|@ڄz5$֙S5 暥7\^O]:TjdJeat !3!ZSU1 \n6Py˺=ZYt Qn2*eOeAB*w](u*|j_d̀7 /ͨ/.6 ejKVT^su5C)+5DFէh 4V'KL>1 ԉ)4L"z\iyzuhaGTھ|81rU_BT:<38A"r3bBw'ڻ7OU ԯ鈬DO XDs W8ѽ>VKqFI(84 F&((`ބT1i}mIroL•1AЮ9Y4E%R2(5nLDP@aMQv-j@,5/@gePIn4TlE&T܋bXXy2GxEG+d2gUtxMxQv9rrZQ&R6.;*Wjж.]GRxd|İ$e\V1>Q)>S9PƗ TqtρH3JYaga}A֓KIOwi%``(ߵX4t\\A3%5莮k* OinӊIuE\i{rV27΂ޛ'bY@EE9s7v L.[ )RF~ksK”r!Zqj49_XgM8A]I8g1+]ẗ́L-8K猷Do(=%b,m6u p<uwѯ j -2|,pR {EM^2C?jn;P0/$lO! KN^N+^jRe 氖l ğ1^5{e/{fsKSnDRϸa|mD^JNƘ,숐(Oǰg#&?EoH}'iGSEPKM%PKjHwandroid/support/v4/media/MediaBrowserServiceCompatApi23$MediaBrowserServiceAdaptorApi23$ServiceBinderProxyApi23$1.classV]sD=V*4mSLRױ4 pl؉kNHF 9`H:?ᮬqM!s?ǟm* nVx4>mw2r >FI*ùwízbj6kywV*0>TٖzS]'l ف6+ua|~~6EK{~6\We|ʐq+k {?>NCH =eoU TJkخ.3;qCqogH=K0ٮXێQL-fR IݔYF(vjŐϚu-G}S8}Kmz!7"8#V pR#]ƌF.j4Z!7NLңU[.Yp_5/EȳT<&U8"TԱbTl3䟙*>C_#Tj;`ca a*i];N""[́>=GmR˽tHxPFS6"edh{._ΐ`ܬ67R'ׄ,-,)bt$XA] kӅo?hfn;p Cc#)y|b'_h\ \ju١NsSPnxc_Cb|\vfj L-#)BI24Ogv]䋻H"KDc*.ӨF`s6gNѬVLԯH20z;Y=dh*T\BA"5dMc,EIE ND\H:r97 1^tk4FV阣9IG$͓ݦ$ Yb(Ii4]hPKG ` PKjHMandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase.class\ |f&fB`!#H"CP nݰPm-jj[GZJkփZjjޛ|>̼yw6Z P<.X$Ï%1:-:`--+uРf*>nE:}B A-Ճ|ZSAփb:V.=A!uC󠊺7mgDg Cm]eb=yji(}QA _5_5_5:q5n7 AtQ,:}ӃmA7˃UàGtAyp}GP=v]S<=:}_fӽtNϠu=  P(iG(!_v{Մ5  h![1(PYfqcc]Ͳ5M.[S[dWzՊf%iyӒZY%T,%F(h"`[U Ū!::!n$vo76;<*oD2zJ3UmR!J Si<K؂`(Ƣ Gu'F(FNͬ:hjF1aXjn$ߺ)i c]N?aZWCf h͘dv41f1,P󂡶@de 0¼؅O;Qsh DN=wץշ1NFyU—q?CȐE1T%Yu[b?+X`(MO=uٹY Vnc-m]-2KЫp}?v[ԟ* h2ZIKج$[2-ux,p,lGS;%&ȡj@,).9+ vC 97 kӯCDb7k }%XFa$^j !$ݨ "[Dq챋d5"_6S< )gkgu;7rݝ[1όâ.@I4֊Jn $|IEo _2VA+aߠg `& 0omAES'L |xk#Y9F9UYeBiS>*l\6k'NDxveb"'En> LuJ}m>& E#Sy@̘t0SHKꓨ#v:֓~Lztiܓ,GL}؝ÝNR7WIu]9k)"9NRK·M܂[M7M܅M܆M܇}&{M&^“&=&~'uz0$,+L^XoYH:{bޡGh; _K⍡7L>@Ő֓7-NO\NcHf3I OU%r@o3'9Ae3ˤҫ&EfSIuBaJ{/>_D>m`4#Lr9A;HNFx;g7TJŨl:Q颺&No/:)Є?I?pZ>mF鋅}qu&ޢ79O>UOŤ[&^| Yj|m]B|*ֆ#,޿ћ&6&zpV8tM9]5;c!T:g=p+arS̥?MzeƏJ::c[+y7WJ?A3'A4=IM>$okK[k)`l,2:THᮛ'B”Ī؇d_1,uZhz$,>g;Wi*)I:@N:g)f JɑDXb@$UbSɼ_uLG98p<,nk8J_p](,TtdTSL%e Rruep܅z%w2k*Ca gHXl*= OCUmRXHo*Õ3ģw/h\Bk"d9;\Jϗv)0%g:KL!4D}JH|_zy-iwI\?\lW3Oӫ.8Iv@YF;5 9 )}l= 9%@zo1`oZԟkGlnm2Od0(",9Wʣ<Q)*FpVeoĕA,lUj1@,o5s4}j_veD_Hm:җbڏ;zq@<8R0;(]V(WwzOfw2W޴c\W(q(Sv#g'ͥ{? ŷ-pdq+^)G {Lk2(voxhpGd)Wΰ^&?2uIbF?'MInuw!6HGU9)=IwzQfZHR4vNJ« &hpA&"m¢7eVn0{]J+]T`0wTwv6@}Z?aO {;*(B\/M|)_K:#1{;S܁or+k{'n[p|߆{7qߕb:|7^{?!Io4~@C^\ڃC0\܃\Vv90_WE+O|ZZTZFi^VhQr͇09[ЍBQ}#ɏnrhwc4Vt,SfR'M;f=Pƭԍ+Y,ͥԃUYUznލ1՚G :PVT@RJ8Ϥ\Fe>Jw^в?DNcX>(f#~b v~9NJ?bw,={c{]{x7Ky :EF E"cqVqZ9JJk29t[xO?Pč-LN0@8`՛XP ^ئy/帮6T)sCm m:rjjpvdgHf#T,%sXfPQ+Qj%]%,~..*a e;mgnm+R ,VmgRmMꦶ]iMusV۴8a%J"3 Pci27hpֈxS[B`*0ؠR* $!,سǙD$ ˠr{pU { .3y"Mϰ]^Z-Ϧ[ei2bBSWB'O:,'˙F]C4#m #x 2f,.q1so2aKN|Ͼ^l&|CP1<~?wl;5@iM _~.dfљDy ~_'2 \'3 =?PKNBPK jH!android/support/v4/media/session/PKjHPandroid/support/v4/media/session/MediaControllerCompat$MediaControllerImpl.classU[S@( (^Q[/-H "0oK`ɦڿ?xMa ys\7/sX10o`a%'Pqů3$CҒN]"`=6R|eVͲnAx܎'.z W L=T([axsx,hP)OUT "T|'j$0ZsDEjtlOei6|嵨J0,%1:?۶P3Hذ="HPFyr\sfF=y$ }a%R̐?atFBܥ# o$Tp+IQ\.Kfy-(x9ixZ0Z˜p+G ^Jgz./Z04K~ oYUh&"E𺨜j SMR1w)mKhsi؁P{EJ[Vt > @I!nj^^D=Kͪ]ETK<%GAF %WR~CQ{_M ,1lU #jZsdxuΏE}8 ,3,v2\2,0FC'IW@@^iLbP50qc;0A$j5Lio۸kL#AHch41SPdۅN7A?8&piBju'PKPK}jHWandroid/support/v4/media/session/MediaSessionCompatApi19$OnMetadataUpdateListener.classT]OA=n[ E jQRDMHHD[:m'8lo_Q;ȗ&ܹwsw i$q+>0 >d0ma¬9t[] 6CcȮ+%孖h18e'v^E)tޒ7Wk\r!Cmu; j"e^aX+az!x ϕ͚Rdչi`BT^~Sm\>,[ZP*: N;=k?"mLc^{vڶ7k;g`^[m]=_T*y\]Z|WNuP#'ixJR(1d*^;5iTN?gHĝ,g>ɵTY㮅=x/aAWh.F*BGm`)~}w0:Qߜ1` 9z8Ov"51g>Zϑ xIHh._}Y "(['c,X~|&18GI,GNL(,ucʴFPµ ØV&cx9\6/H88eEAYm!SÊs8CgOL֢ c+ ե'cI-iD"MH5*[R:iLVH!ɒ̐ +5)q"9x˜.dLO(aKHk#}\N3=Is">>\j-MS">K_|-\Jv.C  zE|D|]? ? EįB3&QbȑlZFE=7^v_Qubeئ[Ro}.3s)IVƌk嬩2,#-X; ք=X$[vӵ4S1&'ahk`R6ei %* C-S P=%f(K'_.3K$_$ö|_'mexS;zRP yUS6E6_fQrbV.`rVqC37biJkTN\ܐ6Pt )rMnv4ȫ(EzKdijԱPK*x*:PKjH>android/support/v4/media/session/MediaSessionCompatApi22.classKAƟks43C!2a+H>`#BVѹ?*zg4K3^ )6صPCBCR*b׎ 7 9Gv>Ƞ'So(}(A pGF<D|qgre*7:.7EUdȆ2rǽGGM8dɔFEشfl TbkuEjm#V}F62*Ƥ"HcSPKPFPKjHHandroid/support/v4/media/session/MediaSessionCompatApi21$QueueItem.classSkPn5MLY:uK\p L!ldl |M.6J >'i]h {~|aEG*T4u(V"nXRa2TJOFJf{]P'ƃvyO;3c9z)Cy,by"y rms_V~Yp%J߳hC1GY%>soz;N0VaD9 2-}_,UXJ{PFtw8p9{ +I 6/2Og5692܎+mUax"fh9C˴1hl^g"7I240Ed%5~A1 $Χ \ȒZ"]EJG(Ɂ|MA.qi5\&FV^DX;U\M4q2_y^z*0EA+_! PKx-}PKjH?android/support/v4/media/session/PlaybackStateCompatApi22.classVmSU~6,I ֶTh V-A,DR`kn ,lv㾠USqٟpƗ.17{{sϹ/nÌaU,¢| /R,I,Ń8>D!">bEJ&#)>VQV 2gX7'PЛkBAdXbկWWLZΕnn![^opJUsluFvl] = 5l+f+zu-4ӥ]}_Ϛ}XU/i{oLAT+=q FA_,. t#]( ;S_քfd ̜V]Ϯ/T=f d)V{å bJ7l8(EɊ%Q[knCzhqlgtuFۭunGgǾ`։ ȾfM8,K8ySwy nt4r7)#!g~:ƶP Kll[;tџϙ!V}* ӊ~J"5 㼂gK%7'V&>O4|&S|♆/%)Ҡ YA`Köe])Qf[Jiwv\tYCtx%];Q|X:Կҧe br`iIgN]e^V0>zv up}~~4'gw+Ro3!sxF ԲP~H`L"vOZoBMp߇x=_a0!WBeX3z@x6J^6L%6r41Dt3d*qaTjM\3 M l~oDjM$$r$&eASS^cDIA&nb$i& آU-9U29{# ؤ g^M R-h7qHmhe1e-ڴ@m$NU]G}?pWŬx.8/wpqPKH߰- PKjH?android/support/v4/media/session/MediaSessionCompat$Token.classU]oG=㵽zq!؀4,!|ihb4"!U6w:?􅇄*R'ZΎ(xܹs04n'ŝ$➎B ˼ ,ࡎoqQǒT^!p_p:*}0ĻĐ]}nVvVm9oeبmX}|'Vm幢e}5yrɺh;?8?z=Vmɾpk-XEl */Pp0Z<&CⶈȪpŽF?HmڝMZOJSVTo5]U0z}b2-ozbW\Ohũ*9n\dkUY3 ٧M mi(1 6VD5w21k"s:bFw N]/OLb-DbI&jxJ8_]N{&) yA0;p@tZzNWpVza<bOܚeHpOlpm܇Jց;\JN;lqq!s2MIDGgAvZi$ef+dhJ '? #JCRclBoL2 gW( D^an9D)G /w>+>⻴F`jaQ/Ip]LzE|E( EL/)"D^2{twD>_V[{4D8r&+2y0-C PsYoR$,E?=̑ b :p^HC>pfO&yq\nI9dh$Q %'0J2>+PK3PKjHIandroid/support/v4/media/session/PlaybackStateCompat$CustomAction$1.classk@ǿ׵I+:CN R69 ſi2\Iߓ/%>Q ҕs%ǡ1[C1|ѫ%;6ly&͂aɝ+ }p[$K?iZ>͆淪 5izCp>VGX!AgH:ǺS{gSϰ@O6wiو^m2ղ.!Ն62 @Y|3(%޳Ypٔu [x}eǢF ' K'"Yz7Fs8t('TEaʰ=Kl V#Sex>bTt% tjQ'4}iJHkX:Ps qޕ9N>*$2d`|zAT%r?|$.0 -L0S!T)ⓜ)#ygq{ʻ|xX%%CzcrUqGIas4 PKpiSPKjHBandroid/support/v4/media/session/MediaSessionCompat$Callback.classWwEf!,P&ibMk!TiZClLhP;l֪2..㣏>{|_O{((9swܙ/8VCHcC&5Ɔ0T1m* ^(t!, z !r%l=CˊuvahP艂n$hn!hZ{gO<\֎Ҟqg\^ bF("[5RkqRcZ/iy;V?5S4.9=lnVwB X歮,PS1 P{e...>q5#ٞc)NrClgCb/}gHM_""bQX*,vRayB~r5ewvwqfkz* ^էzBƹv]^W~OHF:^ },f큗U{D3wFqW!~7ncK tGk>? Jt' 8{ ϸ&\.U/`+qpߠ"PFWFQ.aBÔ22 ӚH5&F0J% /&Tc~ }"DA@.UgLXIBXW& d-O;'yc^omm~\뒻dSuit $"WΦ-91ݕl;'ōndxl7kd4lsKmouw:̛#2o›eu |7_y$oF⫎曼y7y-ߖe E#F< iU=[&tlQS/Fz}4zD71iP#xT$E E"E/5ւKh:E1hi\ EYzD[ hn'qU5^|lMJF`"uc֘>wDx[HM1| cb hڂU>E9G\ѭAl[ XZeP[;՘.h?vA'Z0j(C2~P1\P쮥1͈&|+虖s6KIhLlj'Fd$Z2%'h2'.IF4,E0u!kW.ěMThr5dLftHf)#y  ړF䞀L H$2:m3(\t&Y2 H?A|ݩjP5T# #km8Oؚ>!IjINԤ|$ f(y SFM6.O#5Ir N92 "J1?&&_h&Ol` )tHI7?#i봲p rG/,+,rVt<ÜZQ"sAu9:2kr>5YjFyN}"I"bP1T=Kܢ2bK\j0X`c69G%Sh:ŽEt#kڬi֫ZA=JDJ`v5ڟ]d:!!R=*AH0KzBiP;hgb}E.Mx;`+[FaP]~:=(=*D+#*&CDZVp"4|`N4Z*K#Fwgv8O= G{mLˑQ~;gɧ 3xsu)Ρ߹{+8)]>|^{q}v5n}p~O8p#n; BCnzsm7n'[ #7"?O/@5y'>d!oh3Д }nEd.=0>"= Nc8p;pvlsc_vte,yq !첄K ޅo:XtkqTAGi>Fu4J^Y94_PKP PKjHAandroid/support/v4/media/session/MediaSessionCompat$Token$1.classU[OAm-DDJ[-r;Bb MYhw|E 7m#m6;{ws3?`;A `A< Ax4f0+aN¼ߊn;8dMUcꆶ[+i?*7kx[7X2xS`3YV5Ra5 2uU*ӲY:WRMCɉY>I c]lz!T4nk;YVA+1rU]Ny"n ?JEeD+ش)>6/x)Uoho7,cXgo* C!xrt@zv"J)-jRF;Pk"5 #7kĹoNxBɬF1Ǧ*CF%+*c 2cC&⍃i1"#~[,ulk9%l1  5_3{[WR~śC+zTiws5YMZ@ mFqS7Tq,kL]OؙT!oo9qD:D)Âtܡ;KuͺIR̈́6y zh {!F⊢=oB'ܹOO\K vh{!q~<DK$ 3A ΄tC@cip#a,[{{ޒ̺)ZFBD3֠IF|ɡ H(kQz;w Yhb,?q<˙}<K̫ +5͐o]|PK 03PK}jHGandroid/support/v4/media/session/MediaSessionCompatApi19$Callback.class1KAx{&Q`gihq*n 7&?b6W+7o`>>D@`$pD:[A%a[R>-V  pT3mQU v6wô7-*pL+7u wrBS9Z} ' gf2wwG bP~T=$.0tH1H;lPK,5OPKjHNandroid/support/v4/media/session/MediaControllerCompatApi21$PlaybackInfo.classTKoU_ 64Ph --8.4)Rp\IdUu]ۃ;ab9*MY%tbЅ}0vs v`x亞g:}լ7h>Dd*>Ypzzu`)aaXNK7aV <"' RJH.+KkMb/GG,p_UXj;q9Ђ-v#MӎIARJŁ>Bp2"7o Lx5}2t,W#zS+=|μoQa8yx'ԣsgl>QcIQWczQ!gz+[/}zzC$fJx#*~k2WmpOch&~?$|.w=}k6~t+oC"ԧQBDXf&hK8J>˝ 8fF#8'PK~!PKjHLandroid/support/v4/media/session/MediaSessionCompatApi23$CallbackProxy.classTKo@6v:-$MJM(DJ)CoĂǎv~̏BGI)$>|ΎQװak kxV#C杏 XҴYCNWkxAJ8{a9xupnLA_<VO۵2 Xo^ے-޶ɲp;>R1ĞCK|zd" ܙl.Dt%]m*5"RH2$@Z Sxi9.[)s, [ebp=Utm+Tw}ɩkW2|pY1WRcE؂~g\v4 %4E Q8_'tqgl>.G"Ub^o_xGx$dR֟,>Bپ-ViIxRq&S֤pizU#L 6ekU)\4MH_J1OY뜋?g6}hתg2@ 3ۂ3{goZv#y &^d%&.02 L$L\@%ĂRe 0$#F=/aaI͌ Xd+"DA.m%+;{C%eQI}>E jxX5(MwEC󸦫NyV5slIepFA)ULͽE޾Y:r[ôWk-]ԩD|!е/e/ν"k}=<]QKL_v}nۅAF+=IY-UtVYijwrzK >XN} ꉝP4?Ҩ]Ar'F?@J i.Mh?"hn$wCQA]t@8#BhXC57DMyh5 6D C C؃F8 aۗXAXjR>^f.$֣5K;. a1c0#R0b1F/0kg=A깁 ߁!3. ;kn_e0Di#3"x=?)|V7*.#Z~!njq !Ix#9 DnGNObCyF<U7|kMz 4AkKD떾غFg'DM6 [80YӪg8Ӯgg1J8\y">xrtəNNS\փz‰fQL#Ōl68gOS29;*rϹ簛]M'"N` v_#KM\C?PK[.]PKjH;android/support/v4/media/session/ParcelableVolumeInfo.classSNQ]N;m[Qؖˠ(VM1@>C3d:EgbhQuΌ`@圽^{u_~Xē,p+YfIeidqYq?LTZQ@ǪAŵM)=V>>DUj+ l{a}aQ]V^'+O׶76u< C?_z;uo/f֮kO%n9<|rlAh- X&i:WSDUI A:_ŽVziƮlNuf;V<>{A+){OϸZ7~T)p,hOB=g{\sc1b3G9t20y3/̀49iѧ㑁5 1סa\n4$bU$W"ɶCEoBE"9b83T-~rE'lAНJ/R>1r&>`ɒ0CQ;5 3Z쓱O>AJ~X鸂4%\AHjU O"R asD&՚@wd^}E:?*T8 "ލT7PK5*PKjHOandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi21.classWyt\ULK6K)i2Yڡbiڔ4m$Mڂk٘yhUT]\ %REQsG̼&9wyo{>OшOs|FLn ϊ/щ/E%|961h&97-1vb]|O~_3qsp EsdOw܋}? Oz&c$3,>j=b3#zgj8]1-3 Z"NP&J%fhtm(G -C=bf JS }A\.n͚f2Nzds T*ԧ'FbZ6 `XtEr5%E4nodv%:ün#fCzz@BdD jiC^ASΔѶa׬.FJŜaQQJLm8MvD38yBg5#.-"#3(3Ri`2NDs@v:d3TPo-%(㜢~)# W;hMYJ}1mLHHʈ`˘i]3TIbm &cٸޗNQ/RiѼ AĦ3b"NjfL,CenmS(צ[¦OR5WU䜴ӵ%M[;6.*s@.6H'| c 8nTnjQVOF55wi[qk[K0ɡB'e0JNݫGtȒPں/{z,TeK4bn#cx SᗛreVuAIq=Iq}Yt*l\GYR9}ܦ9 S0$Cge1l m:w6)^0Ls]=:u2U"b_muQM^ͤDn`!R@O TM)@QK*t23d '鈾,NSц*/T4Q 5*xLvhEHxB/Ѣ JETOr<8gUoT\ߪx~b#T<-6Y*;gU\*2xd4tژ s<*9?<Nj*>Qhw^bjE]Z<ӦT kgJ0$SyTȰa~(PIc΍2:]3 2 -J<ҧŏ-=L1O '*8WKZiYIQ 0ݠl jh*6N=}W9<ꪜ듥eDL_(ϵςɧ?+yS*8JhᒢW,+yx^<hݽmrԿm׵ށ_6WE^ s]&ehD M(EG2zPf᥽`-GIix7C8|'QzNNy è`@pZ\F1ڄtZ^K(-P.H$1q,OH#[HmDot-l r5Apvn@("?j~6G۰]jTxIts Wr膇>яvצI'YQ7I,OChwD}R=ET79{^66NF:qT_@1QuU\A/Bp%"rq@]kl69+B*owJwZ{l$F5vn>+OvDMd0!b#g 8wAvQ6Få>A ۇGlKW TߊUJiѫ%q!^4ՇsBz#fnGd+[>lTBQ4%Baw Wr٘&1+D<(5)(WV80m,FiM>GOe=-`i\$zZNa1CSXp+`)}m]=e=NPP].x%>PQTY e)zSaW(+$$b͔V-x Nr'ކa%wȃw:w9kﶳ]android/support/v4/media/session/MediaSessionCompatApi14.classX|[U?7i4m]u͑/k[IڮS(ɳH_ ? *Q1dtL(?O_{{ktt/9s=s=]Dt k!~ĩ׃AC=uP_UDAH2 I4 = )~Y?i$zF=+|y>|V_HtN$zQ_W6:Ϩ1MFF7 C~F,ΗU99a0Ki G".95f;x L+f9'qot@bԠ Fぐ.GQ]èVcpl\.*1j}D"NÓ,q ;UK%Bq>`ԶtQ2L-b8$XчDx<2U3̎2ZY2,r!edð o;9쟘F1q|X̴'R]bѠ.h @RDqF^N {p;E}`$%nsb_R?CQ0W/^_ |%Psm*Rcduw1e *z$QBs3SJ..OlBNɹ:J=39"gl*fs6όLɞϧO|ٙYYΦ6oC2W'r*Qe&*j.SJFeq4˳I:M:$ThfaFk TF9j ` UX0Uצ@S<'g|t!&'TD"TT9)2#w edL2*¬eD=Bfs6zQa/xLFrbAK[llXT^Ures[]"+hEL0JLFy]\2 yZᎭ,ZӴF \vF*w0^5)泽F*C|~Z\Sst2Nӽ]&s?+Q?* Gu.>*E8w\i=ZڙmzۑQ6 M?NјFi5Ӛa7}Դ+J)\@WE)éot/[eٍK$ֹbIwÁ]S<& "2UZke}3/ A윪mF!wbSyYᕕ{dL YpWF\g8Av5e\!㚰ZCN]%Z | 3a/p Ld[o6᫁&|9 po&@9K04OJV1f,P`}DI':+Kt#˚T@E'b,Dg&KO+>-*Ɏw4Zjh\wjV1 oactp+F8({NgEōTqLz`#Ц }vg}E75Q0X@sT/"7B$ܴWkn/PuC=i(5$(ьBu3:zRHQg/Q<oW]5UyZ[:FCPރ`|B͎^C(.CGy@ѷ4Pba, ڒi!8b~q\v7ˀ.jQ@tT%P@tLVCQmn+.xI! !"%};Fۑ7у|oG2vbQX>y7~q{'7#y ԑj`uuu .Z@,{ Tu}At+ݮ$vʕ'qmĢѮ bJ\`^%nWu[ԙ5ԙ]An]&H٣3%A̫]  ]:wa ZnՍY+f Z*DD ?0ζؒ 'p?2x gS8'F^@? 9*x?K }y@/2n kLY3nt&릷6z;,@1.xӂrdrmuH국MM>M͐{!LiPKK dPKjHDandroid/support/v4/media/session/IMediaControllerCallback$Stub.classWs[W][ҳ8`yՐvb(uIqP>K϶yzrҦ Z@Sl-4!m L?0L? {ɒ8{wι޷oś؅DNVjĬKC>U4lHˇ#?6`gaQJ="güJs~4JDAK$zޏ'%||ޏf\|Q×4<-8I=lYJ鴙ةEXx$:<&P7xҘ7IÚF;aM JYiǰq#1cCP4<<4MJB(1D:l;h%W@K1HX4b"ᘡ^yt.&bьq 4&'mHXMdf [S)[@J8}qF!y NvԘL穘7sD3`4Jă\vf3 5|x$Gb48 pwάYY9Ңx߸᛫Vո冾[:E/Ƿt5@#[K\ǏWK'u?05\3\/u2|%~Xؿq1}7U^̵Ʋ]MU7U 1gk1s6嘅z\16W tKgOu}]Upi QfT~4?3v1[n,%7 /\FUۦ5pn9h',O+a⡱~uB䤸(5{о#vj6@rx_+e_fj𕞴XYVl_73WnI= YɡmcQ2~* @k >&o`d6;q9CMDF & Q_sb`-]ur'5W_)ATnK{{ ՞Oc+ .mF: x.xЅA| .|.NwۅCsσw\訫XA늳 z47OH,?A?~B(QrW3/Tv v ":&|ǹ,*jq턚QIIhZ)1w->UUTc0 }9cVt,S66*rrdM#iikW@ZKtࢼIy_jI:嵆&GGvKWy.uɷk=Jj^De]WqKaCެz|[[|+Kt`c!wk^ǫ伔[A})gC`elZyKyۊVڌs{)>\$0Q@ں^i8]hdandroid/support/v4/media/session/MediaControllerCompat$1.class1n@Eש(Y!Jo)"'X,ZvŇ e2?zs\o(2drB͗И 69*>w:-OׄםoGIGn3޵oVKqHߨQh٫?{99%M(GiP_ia?~9~k$ qcLd,:%C+?PK۔ׅPKjHOandroid/support/v4/media/session/MediaControllerCompat$Callback$StubApi21.classO@ǿ7ƺ "΁D~) 5U(v-i;oL/\W!%_y`92cԤd`DF+FEqcº#cš0%aZ=;}3D30 |9h1hlEҋSڲlWN%s2Xk[ARݾ)ZiZ7u&\egЖMQT짼`HGZ*u"OX-fra0J2UVsW+24%Df Dnos溚wm\ ul ˲YH:~WЖj;)jn|b]j宖 zDžMm:{0["wyj)#%a /?z)/ΰh'1yli ~CbtS) 뼂\E s 2*y ࡂ~,2,c=, Kt[cHXfi0Vz!}p{B*gU:4ðTO|ZJQ*mnYu.f-\!j)DS X}jBFBiꥩ99>^R/.S<M"l9]V0Fb.|¿ ػ*F4`]94ODA=4|@}(O"KG׋+q'g|Oha=vw7`+u/c=Ď1A(r$j%H`Ny ny1 yJG gi&A}ģ]Eb?PK+ PK{jH_android/support/v4/media/session/MediaSessionCompatApi18$OnPlaybackPositionUpdateListener.classT]OA=Ӗ"*(El>h&$[H/N lgm `1Gﶴ 0sΝ9sw?w2#o۫ˠBͨF("R1OA6Y%> C$!`Xi\Bվ<pu:z%::V]ǕB#s~KHSX-l@"f /(E@{15EY Yz3Avvdcd`/}"/k44ayH= N:nU~D,?eG̷hu34Ggʟx}._isLjahpJTL$1IGN~PK!X>PKjHLandroid/support/v4/media/session/MediaSessionCompat$Callback$StubApi23.classn@q-P ڥn'UR PB*q0\=b )R{T;3w -mw,k`dR2pPi " $n[8}/Ɇ(NKy"{XhFHkpt6j}PnF}0ߖ*tYlG=X*y, f'ͺ{CyAx>S )ׁ؊XV cT%,~E)S ̹X(ǼtM YȻGR'<.H!X({~K-7|/9Qvl\EBtwZU7!M;GxX5'E'4 b&U|oֳvG ؎!JXPUBe.20βVnPK2yvPKjHQandroid/support/v4/media/session/MediaSessionCompat$ResultReceiverWrapper$1.class]kAn i~FFꍴJLZi"8Itf7n"zoқ g3$HXvg9sf )cn KMLMg0#mht]АBm, C^Ƙ$=܁ :Bz` C8Blyd`h6UZp0poY}ѕ"']jUk 3񅇛 ?#l7ųgťl!ሷ;Jw z\< KesgE'P&[pit]:i>B5-nSZ;RΏӱ]O:nIL<`-\OcW2ó96/dMX 6eV[} *%L ,O>$4} mE\"څO%= r 4=ˑMǥQiԪx+"_03LW!L>TIUF9$~16]9Ajx d] *TmNSu#PK8IKPKjHOandroid/support/v4/media/session/MediaControllerCompatApi21$CallbackProxy.classkOAzVEPAܼ$" ZN ,nwL4?Q3B)533Ϝ3˯?~Xŋ$0᱊$"U9$g r[V?!\1h-܌ūUQer:fɨ*5,J&7j:'W\Dz()Wb,4e73m\ӻiڦŰw2w8%" dM[ E;SwM6ޑII5%]+(x1,h%EGܤndȒ;U=.ã't={OaqyinWsƫ]/#!ST+osQ~ ٷ7Ui2^+aȱK!n2/ra 7twZyCRb/9Ŏ)i3dI>ALjHOmi5H@z]6-!nF.v±(zn<5ݵ4}]f!kMs?"@ك";E;#4'翃/|C  O3}X 0ߺ髏Mg!Y4JsnR1>Skx ?G@#i(C.,!)kPY< h箯9M i3 h> =6,utjS5j1ێ%X<D(,?/9D!u:)hPK&4PKjHLandroid/support/v4/media/session/MediaSessionCompat$Callback$StubApi21.classsUǿ3g&"`d0Ò@&!l$l.3iB;}׻ARRK=hzЃ7ILSX{[} `;^ c-'[p:f˅E \h\b\\bl9|d< a- bJv2qzej6V3 JahVoV-þL}w7PZfX@>HVP՗ [r%Q4ʜ%Sw@FRz~cw RTN6sM莥StH TwomʦK:Yw *•pAuspty+o,*hFp+ݮtK.>ݍn{\q ~*x +8'\磮ѩΨp>:-Atd ~t"9'}~ _Ud$^7"c18Xe@+2} gc>%q/H!*eA p_"г^ƎE +:Vno@7bXMW MoT QMҟH]?N2ԈQj"wTu]B-.hFi {:P<>'PKjH?android/support/v4/media/session/IMediaControllerCallback.classSn@mCݔ[h-嚂yB} R*(z7xwx(؎`_̙{fvx#3h,Rb\k %( 1C/0i,>QS{oe$y#+x`Mj ̺Yj[ς_ x50`nIhR_y F1tAY7yU%^ A.+*%%-JTkrǺHkPrcSt`¼708zWyb:%cw/ؚ;mM;Dh72,LY&'Muv/菕 Do(@`EPD,-}2=aƍ "O_2+|aH>Kߗkwes &`9ߠ 5)jwXCnR Y k["Тh[݀}3 hm:lPKX-OsPKjHAandroid/support/v4/media/session/MediaControllerCompatApi21.classWSGX]EDc,G\ĘUP$Afwݙuv@}QU*oߔJu3؉_#XGCNC>$e:%&CvNVaס*4diwH[XYNG=+!GWn8ú*&dKL'c\x#h;2l睤g)U`ulUj2Xû ߲Br&cH,EÐ A>\؅t:cn|J]>9ωCX\ 1/u+ZǽVq8t:,D}lq\m" Cs3*E캖^BEriϰxT-tsC T-:f[+^v^3SŽ}ȲFX }}47iJG\¢{Lk[pD2*zUTݐDB#~aصKkxnVokdrѥhKQeK.+Îk6bO;/Tj4:.N5?!6_ӓ)4х[}nQ3y P[hn$=ElELz҈mNa~xJD"BcӲ UG?a܆ wljVw\B uͣ>V"&6zMx>)}Ow!NajO{}]Mn@5Z\ +9H Q7иqY7ѸLcRͶm[zc]F PK:LPKjH_android/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase$MediaSessionStub.classW |Uf&ٝIB ISޤ'm64i^Φ-<@ Vh(UA<@DL6mZR_߾y'ya3ĈJ4!4 *n _ !/Ќ/p lWU~-5;yyN=!܋xn~xU|#qx_?#<|o(<|=OxR@zISU@݈'N#KdT6XZ46֤9emz:nfH.fL+;3❑5iͤ5zOBmZ-lIDXO3.}]Եebzrn&yd+z::3MI~8g'E$*~(а*#f$z sg)kl۠ꑤtZf"=ϕ,,ΧIcA K)ljjMV#@Y|"gyʤGMoYnlYkyMqSޥJ${t9A&!TuZzlcudit'r%LOmf-BF%Ck&mdEz+ ".-^uauXH&?*KI6=tc4MPXy,+ϨxV&u@c23XIS"-.'sn9+CN IJi&2y6z=g-͘t0M .MJ06  E(t8ePL4&:*dPȤ9nVtg@o7,=[}tdam?8g+ysJ24q8ݩ>o%.3ѝ֭I!zsU]y PW:v@-jN1s/qcYÀJ@C`L"eK%i!q{3²4R,q,8NC:h-3S<4k~xA/+fo$0hxU; 4xEy/xU_o<R5\ukuPeU? 𰗪LME&jBTj"$šD&E&jCp0Cm갣Ss-Z 2[Ncq7m"OSL:p } -rlcQBGêO`:i$wBC7MHjYx}zHݣֽ&9@'7­W7+עE%ݏEx"ӮÕtl鯔$hIy6FԸ *2E\\udi]9u ~ˀ藅upyɦʚ;DYu¶ tTy\ I鲾dmEVKQ}rEᯗK&VkK*9|up.,`X>~xF4|ǣّ?F(@hr}wl'=a: 2 3h!Z?p<:1K̓mrlQ.DLTfK6g0_Ҍ,b 8z­ߣjm4lG0`6TI1z* K:e>/D)Dɕ($C-`)p2DD6}:QGg P|?*Hp.hkvҎA@p)VYJJ8RY))M,#$N˥/\8ެ'ZSNhJ%r7 r|&BzGQxG+K)9Fc]=;yώ=S!l=-w^8 ۮqN!Ҧ2TtB5a#b˛/A=hUF_I EM{$[,ACk[b֝_`P7ܞ'sՆ1z yQ ]0vevVGĸ q'aRs_[4P6#l! V\)ʅ\b,R.Ar):pr96*WHK\¨ZNgЬR?3iTg٤+|m;B^7R +IQrlg^{yfe#NyCB {1*7̡L),1fj ʢK7sJT*WydRRU4W5%8zz/d`(ut=HY\fZʛab1 J7z*wؕ9sz<ŗ&" dCèΗʙ[=,ˢ!^ɢlf,ګGH6[(E:ohG"޽^µ8p2k{(0xS}]TO߇]\Q۳ =Fxo1/\"dD'dݐƥ2d>[ ZZ3Zzu9(7zDB}I[,,{{|{+qw G˒Τ!:/y_su>)4>yWkqkԴ}Z _H|؆';WzNa# y$,GFI9+RЌ1Hi6KӘC]{ )7S؄`PKCĖ %PKjHSandroid/support/v4/media/session/MediaControllerCompatApi21$TransportControls.classoEƿq.;KҤiJ%--qKKHpv>2 $@ $@H /g(9M(ݪ7sϟS^8[PBY^cY`Re:ˢ7x|ee-U k] /goWuK \OFuerM;5ݔҸN6[n 00 |mªB۴g/y2ն E˵ZF-'|t6O;rC^%6/=y`Bgmy_`Ov[nJ- gmjNdGc$0(,PT@; -0\ff)L2+Md4 b|ɘcҌu&ced[bx v ;qf\8kɅb-<}KżD}<8IEzIr#idCtLn?R~iI+ @_-\]34l +G^qoPK !z PK{jH>android/support/v4/media/session/MediaSessionCompatApi18.classWYSW=- #`,/qd(q<1^e!BqiZN[ja{,3LY+ypRy ihWD邎pcBӶ[o!_ l9лIWMqX#VQ^c,c% ݛgCxM@&˞JT7@JKMIܫl^OyNSR-m#xS=6;5ط,/:gOp4"p~#5mX`elFVi&0|XmmTcS_ ?a}4i|e:n&糟zH&Y7`$WDJF%;k+8--7GOjK7{ծ]?SpBWҴӚf͟"|[%YkⅱWf>gMs~7\a8?HK,wKgw>ՅTmȬ k ᾇ}J5Cqc(@0gi#hn}Ydh9t<Ɔga,HUqtӱ|d1&CID(ADBNah6#,-7X+x,.#/bPqj randroid/support/v4/media/session/MediaSessionCompatApi23.classRKA_癦YY=hdȁ0sNNhLuٙ|}PvQ)XO  )fȐ<,&w]*(0 ciI!\*" 醴otm7[ܑ}pVDZeGsx@뉎1Ҷ3_kWJapOFץv(rˇ\3u]*nLH9p0$1D](gD Y?ѶEc-Cm}sPg#DJjM S"9! IKNM}C@OX`P9($t U q1 PKA5kzPKjH9android/support/v4/media/session/IMediaSession$Stub.class9 xUՑ3nrI$kG GIyy/w_bhVm+vEJlD.Vѵ[k-mikQֺ.?X:s77/JIsf̙3gΜs&/г0{PB?t? ?psrLȆgq̄\_3D&<&z76w7rܼ 3>`^C|?ss3 }_9C 7ȍʍȄY45Fhc?"5A3ڲHx6ңɿei°-fY4Cv$BREm3d41 iAc Q+^2CWae0l;b:Q3V1ҖyU'q ܁hK EaGڙYC(v2wX2gvXf`0Ei0chd8t3!Y4;LD㢭 c]-E(t[b'ӳJf$wm<>uR<^ wKeLdiHfqX 09 ,C`Ƣr[\zGp7wy.KwG@8&MRsÑ^3"9u7%jYqԈTd w-hPR~em2yK{&ٕN,S2ЯY6 (%`%h|CV;bJ[8ɴɩ[uc<]& SnؐYV+׌CJ 4#4F$hƺV̨b竤%SU/6DZRd(_&RbJB:7+R5Mbpi:iD Btf)%8Jҹu.1u$0#|ӋC2@m6$ɵJ3䟸"31aaa,d}$ur& XU5Zqb|i\m>=r qtx.}%Hs=D$kdj6_/!tqSaC?)Lf,M\Z^ĭCs$xj¬sPySsV _fٺ*fw6y=KF e&:tM134w^XU]c!eqQ&"uIIeG:8JG a޺mV7Ԩ> #O]JEN)`EBR88 s ,G+ 'f"8 :N5pN'trg,gr*:Vp/^j|\`Bnj:A=7tq%hR\Fmrl2UH_5p51(+X:_͕lf#7&7Hi9i۰@ބ{n9[ v°ݼ `@!L?wg#ۃpgoJIRA.}+"=aKd"m6Y]aJv2>. lh/>^Z;MVI4"5 gCo2ۓ([IԙM9𚦺ݨ &(F}"ƐůMf9\ںj߷ !Mc4Y?0"1A\7H)a HRf\á؃P}W"<-l9wڬpw= ŃW ( Z$Nv_O@%|ܑRȣj ' #H 6fʩ a·y)P;$65VOL҇䷮RܮK_d%5~ "(?nW݁\Yf @hJf@:Jt>B'fׁZ-x#L#Ku}Wd$KJ~kG|n&~[HjTPT4eL271cu;[W 瞷9bA=8(E.|o!,e|9M|:+_~@Fnowxx-"<Äw;8({ Oj¯q݁_G?y HMfoqӁ`zR{TJQ$`Y0oI; JsC]燻 ý/ YBEp? {  @Τ@i\^N A!Xj5G—>.BݴRsexL(SN=+Om#G.rlCIlD4dh|J35$H90,U|keFߐ"K3?RKEAp՜&pߗF:6A%l껸NMS8@5yl,CrL=]HT S+ /mi 6K==zRՆqd >T겾y)aȞ9KIr꒦iE"NaIgg OțY柜B Ba)?I*냑T-T̍ync| E" Uq7fdLv;;.b^nlYl=D|-_Ze.tY"׼O9ey}0fҟ$YA$峵26d*\x §i.| HIge9.].N6sx^UzyiX/qyct"4"_ma֚me#''vi eX Xqr+/US~ pH9O+w<*y*pT̀lxES'jF6: ~΁cj5LjB]ᄺPUQߩ{z|~?z>Ta8+ꫨG=z31S=CԷ1[}sԓ~y)WOd #EG\-xQ,cB1' r$`S&qr)bV8OR" Eֈx؎~q=.;AC}$AUS7wH%8jyi(Ӡl䌖3Y:<:|;L5Ag8u>Q\|2N|vRQ9#ѽ NTCBjgaj G G>H,\BGϮRjY9/Y_L4xȔ\|C?M?S“ KoPKj'_&PKjHRandroid/support/v4/media/session/MediaControllerCompat$TransportControlsBase.classi{U $ -)(A)mPVZӲi2d&L}7yE]7noDg2-$"sg===?؄/ G7 0p,I c0hA^ Oʣ(Rl,k8%P32jxL5QLGCcw0zMW1G\b<.e@Qw|Hv{k뚗N~REѴAΫAd{yUp'O,&# ӹi=[ԭ䎙f+Vh^:juiXRG_A֣+Xpb8>L|mՔ3D}@V7։ S^dvPܰxgYؖTjR#OX^+GnrF%".ԗHxj'vZO,a *T$>ù$%kjfW_*\{]ew`\ _(RATol[tL#$5ϮN)&]+({3ٜ=wnW9{<}Myg%4HT",9| % "F>?CxWĹeUi܌-V! l>M=|ED|r ~@uh﹀~THBF7n'߱0GX4oŰsx4hT-4=.Қ\i#Ooǟ7Zi-z V'C'*LwNV7vH8И≠)Fq1s2ueFFÌ1ȸKd{>00qE, #^S1x-J,TW]*1Ei4AlQI, IJJB]I݈H#akɐ4=@ku6M Vlfж0h zA;M+nZ՜vMwMv`#47G4{H3HI34F44hFHs4H4C3ֈfMhi#8i·^ѩ#3|g-q6̝3DљLBMBGB }&8rX"Cg#>!9F @oaܰu8\sN6C9<<m 'c'Mpo(MfPKykPKjHTandroid/support/v4/media/session/MediaControllerCompat$MediaControllerImplBase.classZy|Tn$3 D 5&$V2H̼3o"uZkZK -Һu.]~7w}s.?yG4/R8y/}r n|уp2ƞ2!7ʝ}r%9|ٍpۃA9a9<|ER~U|Dzԃ5L tYȶfעmMfXݯ! vqڀvD~=羴ԎO)T3B$d[kRMX,I_mLzPDm"9KO(X,ԩOIJ FO^5֡]Ќ$^ˌ4 M=}dlׄ՞'/c{=s t8]W]p"9 tգ;Y4~NSVkܨ!Z0`])k naI.n[קv$1=cZP*"% Is<L~Dܤ5I`_y.;֫>5F"oCX'ڣmPF24%ܺϦ={MAZoG4AGѭẏ IC7ÿFYC֣Wz L dG5ݚH RxۇѕBO%(iz82|-<>Q ' _xZi< =lO3Wijl v%jOXb(uED7AL85SnUk TZ%rLY<^DsXqFحļC \e( DG.aH~\`ʥ$> %FE.%e~|X3f]pu8O?Pdu *Ĥ|U[x>%XS(Ln@7p#*c҄e!EXRYeRM9R0 :!h!mGo7j~فGyVU7"d?MzV}Hj:oHU[EhN9Ts9\ 3æ̣(;Y[%Az3^~)VGr%gwDZ| td\b4zv&{ۆlpmP?dbX o h1[Mݪ2GoayyD z*}4㘓cn8 GqFYQ@c8.!K ƱUw>ۥ.O}x +*zvISXKBZ^YY|}Rbby>)SxI锭;xku8xef7qJ yV6w| %AވO9 })MVmw+DaXg`\h+1틨P^a]{ qv!MfJ*M)M֯[i+p+>C.Jp{H\k 3`$9H,pp̳OD(o"Kƻy%6itxMr~~ 7qAγ8% O%܋T%PK4 U"PKjH9android/support/v4/media/session/MediaSessionCompat.classZy|\}v%ٖd[| $k1 Hl/eI(Ogy^>8mӦwMhC[.nҔ N4i BBI4{oW{ʶff|Ͼ֧?&Iq^O~xZ5_¿S>G_xSF?5>W5?ު*\j'~眿ZjDdYNⲢV:?ZM^VUVVkG^SVfjU䓵_G6F٤&kKu>iQ\TӪiSMj:TM5 F]5;4IS1v&4ETOHʹ`u91Ft:bhW2c`Un#mjrpF0 [2"i$Mn#a3=Ͱc)#TSՇi3d1MnT&0zht?^5Ȳw뀠1c$R!$iˈ[GIȘ>#>JD&d23;1%G0`sIĒR(( HJ%Q3%2a89J#ѴR)㴚TܰS(\j͞%IWn:0~k`__xPxzLKU]#Cã=Ã}#j3?w1^7k@8ku$%"Xu.bgu|4dZ x)wC1>EK:$|M[ B)uKBЉnZHJ*U$qhi`ߵ0%}ɗ.WY-v칚஥ld 9ӆt!2/}Hr nmsg|g΍A%.͗|5,c)V"%:Q6G"^'.RbŖ˒JEY=՟ήtP[Z7z!Zc:%PJ^\wC[ߑ*ґw:3 )J֟`s)>rp˦K,&䊸P Zk34(,iG?&Ixlw9Nh6,*Ae,GX6 ߳Q>l =Է4 0kI(Q6N[G):ڠ])V2ތ/XnsrیRrůjeZ[BxZ ,juAc.ɐ"VK{:?6>/7Ws^_oⷸZiy;< ^[ax4|Ëd4D.=Qdkl̓B_gF5Yj<%e+ YRY pYRݯ `p-ו [ 8ZLS6ݨb힜3=@B:\nDl!Ӕ%ӄO Ss K\L]vxQ918Y"v l-_]v颐"=h}^6Bo|1K~F]0]q] K%e%5_ D<ùt*Z9+/0ƽŶ{0(/2w~"CCG_W~W1O'ːKXFbg \e&vH*kV+ͯ^Ua_1«%"nxl̆^ׇUuXX#͑1+i^&E.˗{^Pc:Ao-f?\.#Ue<ec쏳zoIFPK:*a'PKjH>android/support/v4/media/session/MediaSessionCompatApi21.classW{W=cI;i(iK,-MJL]ԉS2&$FLm!} ^ LjhSBw7#Y7{{ߛ3vQtりwGрxo"@R(" ᢊODц b>%?3b>'ϋ bb~RWU1|M _S*F\h7\e1|[wwPR1*()Ec1 \ΰP0 Zz&3ΌֹYO+47ss@Ac|PJ 9p1;eT++mSC) 빴mD[ٓiSOArkyϛv^6t)3^AKY999}ç=sӉ16s}۫:m")W^Pѧ[`Ҍa'g+Ư5hYVY ^is=Ű56n1s<p7QIS#ɗ=* []|v؏Q.\f*\9+y fiC+ 1]¸%;0)kFf&-nu_LV=4UtfOʀ,.ZeBH( )Zfq`V7@3uӷ~iϨl]u%#"Y1!Zm62t-[gC- u^uNsdJ 4}7+8{hU|آ`_gu*ؓW1A$$l Hf4̰u0^<6 I-%&JOR`O~ -'޽!> AUmÝ'ܣuD})\}B꼋΀aQDoἳ9̖qVJ?ȹPPK&GPKjH=android/support/v4/media/session/ParcelableVolumeInfo$1.classkkAe7Fkm.VTjh!/P &ɘN̔ME~iAlR >Ш k1#АqjE/$cQf@pnha8+cM[1-Is+dn쪞Dx1<](q;(⊃-]M'}&\޼;JY / "CFgs4sidZ;Fw&\%ϑ$=~ ~)n(ND1.c9KQ4Ui3)&XʵAjc9gcM8]@iƻ~x#֢&nEVCS+̊vU+PKc5!PKjHJandroid/support/v4/media/session/IMediaControllerCallback$Stub$Proxy.classoǿwwY'&&y!+C NK+݉3z:q_{eD(v`-N0i#{eslWZ1)(QY1K@ۜvrUܲ:ݎ=fzq5Ħ1ʴdͬ# r^J~ԪI!'ۅ'bn t(dN.R=aS.i=  ֔mx5~kHdUx88ꎛ ] ΃'6bq9S"4ٛjqSMͫLH݀_, _ qƉcҪn$~/$r9^.X/~M /lfqQǥuHV>x$G1198(<5y,yu[6XINssѿ65HdXHu<.p Lze _x?=yѵ<3SI/HT+߼.s 绗 JGf8{^\gԮ2TjSk"u\Iz0B be?/|)&pejWU&b?ave?;G/G1G<'+DWv!{}tȯ^_PohUo;v6h,"OL|r6PDA5 `D-kjWg]FLN tt ◑8ɗ+OӨ >.?쏉ks =[Kj=xV9t. v!`Fr;FdU`Vԑa/4&$L.BkKσX|Lkra+s5tqbȱЇ)^iAcڤBD}7鳌}(QF(g'sQ{f%}6@*WSUξF_3ӽ7[fMM)m*CavhVzဂs7|Q*|*~?O3BR]Gw|8"Cԉ*WL/u-K|v^S4;cw~Ox>G$Rѻ4dB vE ˃;l^1Us< ͗e0n՘õoҽLԆڿ(pSbo&au'"~HDBp8 a܉ 6M%I.m (ڡ]"DvI#`[ʸj,\~@ML>n!6 N@N0fD{&5<#1PK=Z PKjHNandroid/support/v4/media/session/MediaControllerCompat$TransportControls.classS]o0=RJvss0X6y`eiTTƠenb6?'$(MI0{|_xG L`5,1[J .֑`hvY211]x%}if+jZ9c:J ӎ̭.W2 ljH֭*x[eBur[56'*2M"1~ j/9#/2b-?8' &;#8%\@iv(Ӿ~LtH<+_R t[ߙ TW}I5ë0tL!(&]:T\DE\#G1W+|rʋp=N#ψHHAmcp )QMYBQTa~x]"PDW:kH\2, 93dIS$(KkYV?PK15ħePKjHIandroid/support/v4/media/session/MediaControllerCompat$PlaybackInfo.classRNQhAVeE IԤ W]qq!>7&6&^x>o1.2673;9Uc r%ma,JJ۵㲱WݮնrU B nGL2ǟU6Ql[/AuQvBCixvtm{6CG-IdY3 ,Vo^J55FNiSXZnpqTV@2NUUU5}A`RRnDmT,7E9.hϜ is1e77hʟ[%VRĉwoO,TêaU3*LQ91a3Qtm[|oc&0<.3qńM|1g?qb^$"3Ӧ.dm EWh.o.ΒHH0(}FAt +oLg{wi F3Vf I1#$h+2t*% uBzWFV$q;w iǰJ?RSJ?g*F*9OJJL Ybl0E^:PK(\"PKjHCandroid/support/v4/media/session/MediaSessionCompat$QueueItem.classWWW^&daAj JiHҢ A%Z!@2Z}{k9j+X=o?iHCTÛ^{w'Q -D{1!t-Njt|PcXNsqN =)i}KOxVsy<3^ȟ/y2"U ˠpD&u΂ZPcacJ> >8{Pj =o*9 S!LO䴬:CG's ٬3;5)(wbv1X"1Tel0qM+z*:46&=ܻidhX|S1,)F4J.(/t,Tw^{T<ӈ;b6R8zM̍ Cw]Qw$2cjnce1JzTi|ͣ5{DP,mKl lrZMTJb0rٝ.nѼ 2I*: J.-Z54S1P *Y ʙ#i%EiBt_LU7G@r1U:BZcTx տCnsJLN7s*GdvxaRaw t0>v՗Jq@h^,isх>t:X:\H+!M0aԠ^H۹bɒO%o2X!+ezUtXu2; ѻwe;1,񁌏&c|!K|%c_a=1 [ΐ-Ti7MJoqݘ`,i\$6O؏4 a3_~q+~w|!=)^hU$'guK;Y8*͆3dY#Wͣ FxC4I'{ͳĦ=y„뫘xKYXA>C۾'+ŪYԆFͪ:͠2ȹh]eQ(w aX7fxko\TA˱  HrA =($!܅n>䦧7").[Zi aDa?}ãXf,"&wVѳ3<&jn7P5Mu}I*pUT#W!s >-W80F!fAEpB Gg_'zYG$TEr*bϵrsr8@j1!|50Dž?QF&' p8@"!co;)[Ǡ*j큪.]zȶ;gGU4dEcz9{(IY4fp ig0WxbVĭ4Bvaԉ~*wo7-'3J+JKazu5w"얜EpZ>ZKEp njĶx @5\o=$-| {mMtWdUyHwyZT;d@Ne%L- Hr\/2dg1㿇y<2m% a(# /H:Y$o`!n\:9GRQPGJϕtހg`#e 9^}|Hm9OHPK1 PKjHOandroid/support/v4/media/session/MediaSessionCompat$ResultReceiverWrapper.classNQi;P GJRQHXE`[2iSODM7&J'F_øt 4iao^߾(L+h(Bȴ 32.*4$Y(ȸ"㪌9TMwG+[wrV KC Utl&ҭ+K2bp͵)i>L(1l[[u8y|tvoR-mUf/J+mwl6٥&Za3s_ux@݁:J`6Y_Tk%S3XEfC~RjLhxLN#I4:|Rx `7$ } 8̛+tRfeo3S@fxhxᛊ%rtwtƣ؆ L3>/3f+KQ&=; t>D]MЁ&ms JcA/jE&F>ǪU(BQ귘K¤' i$uOS SFdm,y֬9kg|^RT߷0Z9ہV{<$L}fo3e6.cY",O1`p~NiwvZg\+V,rDnYy)[n.;^J܅{rY-i~P"G%qW>)OKQã >//1:Ol~_&l I2o`e./Tf`|*IkPKʍz PKjHPandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase$2.classmS@GcӆP*b ) `GF:Afy5=h u匎/~(ǽ')m??40㉁& xj:tt z%/9sw}zX9tz"[ W+j8N]uʋ$:a;I-J _znt̹זJ~Ľcֶ}M.BVsxDCWAfE8A ɛ%k}tF}_7鞤56M3{#Pic'u"*Bc$S Vc@;I'$eb1Ž33Ǐq'y8f*AvL 2I)1LaZ/PKȀPKjHEandroid/support/v4/media/session/MediaControllerCompat$Callback.classX[wUNf  JzŒ$9$LLj+R|rT\t-?Jg2\HlI3so>{f?`㦇7D#&EIRny!!E^x;^zM] 3*g 9ySUGr ݓP\gƍb"2bJb-Z-a ZV sHRLRIM(jc[]$UżV)fK%#'$"8<8uӱElڱ4X9"tABmGuekN 'W[wN;*wiug 6ŵgqwE0).v`a|x0ZMEB'}> .|؇ Sc >?)éS6$|ư"Ab͒㲑I%jNTmrr_Ul*87>]rV.܌M \L— `x) 3imw'u.k,-bXh6$sd)w47&4-/[*ML+-cVbe=H)MǪ64jsϲxKxQ`+U[i-{htC9Wno|wxV~2yM&?"{7kpyxu M 1Ӽ/ Hǿ4`^*M쑩Y𻅁e*E4L=u4iE/DJd/B<!=4YA6!jE9Ń "(Ug+)PQjUItzat=}4 ?o Q|׀Bl8z?48 &ń1#+xDYsEtXTb*(C{W%[=upi6l _j›4l pر1W1oj;2[6{ VpPƊURhJJDM$.wul4My2u{PKC˱PKjHNandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase.class:XTW^73<AP( j5bFS#NfpfM6eM!mSKSMo͖lf7nꟲ9ys9v~LqfvRqSq Zu9WP*WQq5P[*wTlb+MFv^GT vnRݐn[Uvܮ;N7`wQn7zx@eHkUYƱb7{=LGl/{c 7L!Z)7}4i=>Kso7/ً%| aF_DTfR7*ޠD˛TfowxPOj~o9n$?vOاT|FcST|IS"$_bop~f߳#~)?S -ΨŕƟ!Z.\n8;pQ Th*'R?:L*PJEU>H Ty}p T66gz^Ze U=7 'xs=z7 jE 8GkgcE0n0HBA_}q "ƧRD4Cl&[& Hz\pfs$ ^y뼾u T*%T-=A=<~OuYiԩ?Dnnj "&7Қab+D'2N#:Wq4xQi9 ƅ59-{#k< 57UNGae YJq֭A; q+<~-nT0\x_$Ob_Ci!]w?2B8饤mn2X~(wϪq7o޳bce.ʇvmԯEd)4TU< LH= 5֩F"ڇw \]4Ej!.:H5U&ICT*[Fb$٫Ud , }Y҄V GVju㦨|8S7Ū:=uuԡtZ~Mp&ha[)w~j7-K6FaN):&(u †5 _`U4O'%ЭQ<)Qn֦u qG#]!9} 2qc0$Čn~+'6x#R#5;W%'IeJ.H4 RhQ\USx=vC jm/7Omu0m0(Y"3}FO5anyVp'J=d<|b\oTU[;; 7% ffKPП9}]M 03دOh~QŒ #@RgXD39OD#ճ Ok1DDVFZnWYD2J:1yOnGtVJI')Ty$ 2{2ej=G#f=q9(c1zkPzYK9BpJN2q? "u=:uyZAY^ƣ;Q,rpѰepc.?IEVtfkZ<{pƎepj<U"HTJz*cȏkYiu=+0.Fd"CHXy0>Z4{wa8¾\Wi2ޯmsb.7X'͘c.'սj\WZò-rd4tMi2Շr5b "&Xz&D'&)zߐ}bc$-Coӱزt Ɂt.*ch&Yl[Itfs|e <Sa<4xTVxH/*X #]];z n[5;. v}"_`4xQ̍ ۰*;d&K a1*1OnC $TmS"ceN].ѳ1'zs~/ZʇhaCaCmpE-Um p1r7*I[5^§hp\/e* ^1XvK4>I$kp{(ggYh(46)*{#k|?Z@dwd 4c`,KcXƪ̭jE|kR|Ə"~OH[i**_T?dդ3Tĩ &Hp`ګ@u/0_b;CnwNCWSgk J{k%,_\Qʳ!`C#̭?$bD70tPIpѼ }6^y᜚'&v;yl0Ɠ4|y:%fhң~tT75'ݎysz<#G{7vWvLVEI5 .FsHMHWY薄f^;Цȇmc{6Bx'f^sN۝۳45' j-Хu>ړJ:x.Izásbz30K<̊i0!(Lwj\ç [Ý*$϶^&͢zUᗿ]Ms~S[Go+=ɯ}NEf:ErtfW?R FnJҧ*aM lަWj dOwaG߯I :E?1s 7H|́c@ISǾAJ,G}!fC$%[`vY_Q7ͲnmFcpFQ?(t{+`C~^I] gYY?g7EY`]8^?b(8ږ@r[!j伸VH"!TdX+ /|˓Gèy0:/ r[^~6{p_^A+$BA^hBQD8[a,-{ƶV@kAyVx?LLJ J;V<5/*HSAdH)0X !b0F "SE)Le0Ẁ*q$,P+fB!1s21pp;D5:5Q [EMb7 .[oVqLsAQ-VU?"=/\VQ$ 4O "F|OC6 W?-]We0>tw YmN. S!$AGWᙱp y5pM܄PpcX8*ƊD Q D='P/֢C(*9b\$N 8C {:8 .lC|"܊?hLL>RhPӓ )OTTπ ?@NwY;evHMtӰ8 lIa>ͤADlRL:2 3@Kl9<F"\{+u`+{xְ((lt;?U:]K{D50C`K#uS\t e0Zl4*ISISAE'q?A:JxS3z)N't2Za@.xp/*eD y%*Ր& [Fddf7oM?m=&eQ4$r,}t#sA $mpT9* !-H6u0@\pٲ& R ~67X" JtpE~Γ 9PVkW$=#l@Șp%'F>sagC9Ys939Fra.6VSz R8&n/~UT?BxBx;aG'~bo?`&$̹=jΨob75ۈS8 v턚Xo,b /eNXipBtA1b'*hMמf)"o龅m4y=.>`fag>+8S| ,w&~chߏ8\QO$Ҳ&&2c7҄Zv''n䕲'-MTɏՅ B uqmPerzT.AjAtlY>cj)  xJ)P$t%(h%NPYҦZZ.0 `$<(a \gdtliS G0\eUdcJɲ %DR³\z -j쨖jiPT[@E 3&œbpZ;a S:p$f8:&A?ZhHVB2 ᐫBe$LTFW)F6),r) ɑlւ }ɶb,FDg*\Q!9 pK9l|E 19wm^2 e2Ҕ M^au^ł8f8nt`1(]2Ͳ3`2?GFP,7bQ< eN9ne& xi<E564drgP|u9kѮ/w.7b| Vh -0:g\ReqtE&5L 6ItENOYlkaa`ce86\({\2bN0\z:Ǖo7Ȍ7) S cؑ vBEƍz/H8oB{k"7ؐ00( cޤE8&mDOcrX[R Bt]̸ѳԂKBe U`J5PD V)kaspr\7*g]ʙCYr>\U.wCe3|\Θr%ӔkXZ6Hr,_Un7jߜet?h?A)scܢ /-qѯ d4ѥ93qHfm3{[)S0,&$:9` ))5:Q35 5"5;. 5SYa"/ % T%IlZɦ (;s)T7@<a5rpd5Mq]}a{ɗRq] \9ݕ3p4pMv)9cj|Ě1`1[Iځj0J@ڒ)0ٍh*C0ByӗGLyf(O@$< Tg1(?ALyR^p*(.uxT܁86Mñ< `s{`%c ґRIƞgX9~+LSrpE yAQ 1ʌ)LUp}Üŭ` E|{68kZ S? ~!s9LU΍߲}d}Y0G~?C/0}W}J&m7*ru~0Qr=D$ن*1T-,ND#i$U+8ɩ\i9DY GOP- ߼ˁɞihɞ-[fv,WH#~r68d6WGO{$IYt8!)ñ7Lv\ >' z 3LB>g |!{~>96|.4OzSX|f g6|A>3a^)|9?ΣNuN_֛E  P(("@q/$X<5Mr%ĺ#j=pe0˖|[gx`πcSc@hhC ωlgyeQȸ.4HOTv#J-_zW-Np='l/W=^m 3`;8gk8g,`ܯgAtd-Ugm(2JF;{n(DXgqGh^Ѭ*i-Ekb_6lgrp;=>fg3l6edz'f|t`| l3k+0nfj EX_ 5q8mX <(ǠsÓ,Ogor;l7`{ic#PKW KvSPKjHSandroid/support/v4/media/session/MediaControllerCompat$TransportControlsApi23.classSj@=ٸiU~ڵRЅLud&LRK>NFνs_3?}`]n@*n.CT2eX#~ƒ`$yC#0J%@C>ɲ!VG1TUntJmRcep!W2 "d'HD$y@*طژ%&W<_1 *8UAҘ3:yo$Fwy@ĠuX??&pg•{ 3{v]Y&\0b<xȰ=d6I0\l\8tt|FEE?X~ ( zWw+^# ErHvH>:PK() PKjHBandroid/support/v4/media/session/PlaybackStateCompat$Builder.classXYwDdQ*I0@ݔ4B-슬j$R v8Ӗ38'^Y&Hh|suG-igcFF3MX 3ǭl}˱w{6{y\fٻ؛cFnbfnY[q{~pgw0yH7^Ǎ%<ЈÈ!GxΣ3U,?* эĸw4MYqmM\9lVk4y߂ɛǕlƶL"r$fѕDbM)̄8Z2sW1/mkX*=EE\) s'g4b&Jv+Rֱ8GcӴLye0QrYV2_V%njpl=;='Uw Oؾ9JA]!wՖ+B|hlՆɯքWmt.*cֲl26Bzʧͨm+s|ȈaM Oi<+q=(9}ϳދxIΗ2^7xxKw4v;_wޗW 8)ЦcԞvM-KjbOZnTYˉR*ѬkJШcE d %tXz҇>85uDSJEUijyqv@u@+ 1M]+|*[ɌXE)W+pJ^sp>:+:'TP %2{4p>>s}S7U;}")i$>u[g .?G=T\ ЯU\I88.*Oܳ}Kܳ6mnۛ|K%Lt|}Z; tŏB@`( }MBvQB0ZE$p4H> TCcMl,c+̶V hc^@$=u ~A Xq^#h%wyEm8UljkWbDĘ؅bnbfS{5vᯑmU^B~+" R )O4셄vKYINQY^$qmv;{/D/?-HV{V@tvBKD%a*Uc`|8MыRw^A8Si 7h+A Aw9yϻ=+}=?yizgr~VJt A8Zį-~Sy~] TWIZҏOKtX8*O"_%T*7 V l"%Qbi X$9ZFWf:ZIW+KMD*j @GpUg/Џn*}J=/PK PKjH:android/support/v4/media/session/PlaybackStateCompat.class: xՑ3_؊HQ"Iىcǹ/YJCvdnBm+@@RhvK)=zv9J.@cHv7ݶ{[e^]μ_/CyofޛyfIO=1'+\`K\`53XlkF 9Nlflp` n'U6M<`[ ^f`ӎm=v+>;c۰w#,An'-ý(^A'ư8P 18줽]cow;* j  $ap qL$Kᔄi'ta#Oo0#aVqiD25!ȑL& |>'48/dӁB2K!TBBBa"@0{aA J0&ՠCCqˌⰕhñP$Q#xKvE}`wHBXTF -^Lpol(aa;dDk3z(iIq2 uDÑVl?h$vfBH<ԣh,96yg< G{!xJ`:'M1N6j52C9A$)E%0x߂R瓕'%4psbm%/V:wld̏I2f!_%3G)63衎EHvȬ)-k,%GhB"S U5y%Yu5fO":#)(ۏ& ZD\Jҋhlvlr1ezj"rSN/Ơ34kb:Ai\Vbx%n%=kOHL~4]BD@B"|9di۪I.OD'R"$Grt^: ֒l gCZiAUHJ5:Yv0zm2. '$<^`wFqqE홟V5,ܰA+¶˥Iæ$l{ΙEQP gmaAEdfD37(4y1[Y15êM0OocA; 0`/nh%FSZD d_ m$&l0 O(4g2?AFg 2ǽ{f6RbC SN1xz o|OƓKx7wS77 _bd|Rp4ND/;yR $Bnf_8+b3sdf_3 S˯ъ4RiX}j ix61i>*-Ҿ$K›d[;?o;dx?*N»%[$ /g(x$(yWd <>9$xG ^/ ?<#.8U0onB//ԿR|v¯2Ä+|1 ?F5Zz.ʩ_hZ*ESi/ڇnz<,i^b;j\tgp փ\4PMN&wK`{Pw;'?Y߽`^[_76Ujq ,F7,Cp)A/42؎a//8nixFq﫤EF=9&z_1I1'[ɢ [ zEvoR_V8Ov5K}K4CvW ;5MMEBl)k6㰨IXl!1K؊M{ At4? '!Gjjmߡ]J[">,k9JWCQ|Kp _ 3/zt}ob ."*iC$%.(kɨB3M-?BcEsK&J;XU{C h;'ʧmng v/ltޭ"df˛ M@/kos xךo'_2vsVy!B>MoN> D*6^:-Wl Kt0H H0m'.##oKűpxZX7UWűu [5X]oZi?7;k ڦGhEh^o!BkU# ʛG z At(Uڔ<7T`2 Bd@ syn\c\Y&+GfSfY՘%t!r걹%:N|R݉=$X-=j:@K+=P+=,P t=CO@CA-=\{.*ҳAj@9:t=aٰ,?ZN5Z^M~jq Cv vxғ6Ï'PKIysa0@6CZӞQ`0-M+ ) C7PKٚPKjHEandroid/support/v4/media/session/MediaSessionCompat$QueueItem$1.classooAƟpxZRljhjP1 'm_-wdѯ 51m24@)!w;;<ٝ/x&4 WDDxNX& xoIl^!Ձh왫M zӓ=Y e`U=O+@_ քRӲ^>ylwd6K캞'anm"{)]A~-řKN |@䫅wSGD"8+q,!k&r2=#͊._ԸtL2{sYo8^.#e!gSՠ9E\U) ]4IXN/OU_B O.&!&Idpxv-G+1X6s.m9;4.#z t/#a5>[>`=QK?|;I 1 \ Pf1K&k(.">9 ,nE"Ѽˑ6VV#dYfV#:ɿu2R`?2ɿPKt)MPKjHGandroid/support/v4/media/session/PlaybackStateCompat$CustomAction.classWwU4$@KKEPiҒTDZ%ւMV:M2%d-'=?o!I<ܷK?`+~ &f ,^&y& ,8؎ N}Lg@CLQX &O2yӬ= "[ͤ.XIm 43h]4Siϔ?9]i6E|Jldp`ty5+Kx,Wz@'6[ $)Syœ4w_Py+z'S%O_@Z;9%&,cLNZ/O&}}iXB=x1]6&˦zݐa̤elRKiyΦ:bcMS<ӝ]ZOY6̪v%87,}4koHMG\\<, im27& iE[56l2o -%uhUWp-Zy)ݪ:btg)B,v`J?fu._S5V6 nϱ4u١pIKU#2hyfI/v΃lnvѳ؉~&LQtBJG*øU|*>GdT1w_ _+Vit+в_*nnj;pF!Uq~Rq-Tj)+m;Dy939Gl ZN^B3q*dڥ::Qt@B|VK_ɖ#abSªEɁ5e"g%r2\ bF& N8<*g)/༄K]=ckCsiX*/@GK#F-|Z.) nѳhɼ*z: KWT>4p; HMAh•dz^VB;h4 ϣ&'> ox,|,3B!#X)oAE܉:5a7b! FY1$<>MNF3?:@eZF*ãq n܃uh]N\c+/@7i!GHgx氒od9o<*sXu ܇UNrH?w lm%qɳarL8XohHYv1MOm܊G~]'jϢSxhj[Ce"Y4GfH #6w""p[Bf(%ҊOec@Inj˙0;=ʾd?C g%, 4X-#WvN:ގqzq T25^GD?OtV *|t¾{U."T[UxtqD<֎usXW Ws Ws +氱23QL=(95Xě|JzCYJä>$,4tXÑ-Kw6WIkڅ%HZ$mva bJ!^a`ڱIp籉>#yhX iѸ""D4L?S-=woPK8'PKjHVandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase$Command.classRN1=NqW£"D>Tx"*uQm@]TҽǏ{/~E6ZM1y.t3O3Jeho!S4.l|X4XW6o-37TSi<Ǿ0oJBcdX(-?X/bI"ٱ0ϯUYg +dHZ }OCa@x4LX+鞼ۏ>l9ӽ8$3RDm"+N=*Jw~7(FkϐP +{lg?w'mCXRp#%Y-InJrKے-;Qn*$yO.IN|Dw*ɚ]%'IAAQĠ&.g^26mLe sF7tU>g؍QUT45a8ˑNYsRm. 9bS ҌKira=qj $^kQOl(`/lya_lBE赒4Ԩn)kܚmҷij7}q! sMkl *zО<9p,+8(O~H&_h?ӵc1/p|ڱSJ8]h3e`rˀ)|P8H Ƿ؄?mh ow5?剟ruN361ǫdߍ`ɴߠ|P44th@WhHa tVk_h z2|!(B:``$Z~BV0(slwc$) E:):1k"2XA֍epF qS<^wqm.79%IrȣR$rK}ɥI6U BvUҚ'>8`A\ߛ.$Z0H6|U}?PK` & PKjHPandroid/support/v4/media/session/MediaControllerCompat$Callback$StubCompat.classKsE#ZY%& !8 [!㼝8$ĎK{ծc_$TLAG8p (zv7뵐 Hvv5?0qmRO T`q/ln%ټ,WU)x!fhAC5m2$K.̢-KX \Z[Niv~X!*wK5|Enhr{q,Uu՞fL`HU]\uK\K ʱ?dHTmgɣ1{ctF T }vUfw*k\_WmS՗[uM?5(db69=F3VMԈaUaЗfF }NmQ\!C،r]-9p;NC׹&!w7MG fH#͈ ت/{R!b5֛^) ]EaEC~i=PUun;&Zhuwr~T[یҾpӬY!g-vwsK9pFȃECs63ܬ M&+*Nv`4UҺf'a VxϣEV[ZA6A E*sdo[#2q؅GHcX% U$BS@G W'a=٘dcf+;`a?yV`D?t⮋a7E z J^\B],* l%NQb_^"mtӰv0-*鵥;fwQ]BڨUw XawA)v! ],%:EjzC"P1Ex=D7."xdu }݃}8IIwj>" ~is /\O'SI>H(f@J)'9XYbBǛPvuռ!zugp; җIԩ!(2SNrU[AoPK)PKjH]android/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase$MessageHandler.classX x\E'dt&-%-4IӤ4/v7$l鶛anJywR-AV("PXAAQQT'*Tw7m ~Ps?3s^s̽{0Ox@ƇVlQ>܅`x/Ľ^}ه-{KCdS%3o3=/]=L^8{?EᏋ`˃](UedJ2@sD T4D yd@:>GeNc0T*f&#z ]DHTH wlJGF̤4:iW6Mn9-nMOi~A, S{%1 xZArgBG,atdDÌ>=c0=? ׇ67+ш+ki+qL1S@]mc>7PS#j4 }^Z%es@>H#V(3c48i+#D^lԄOj3]cf&ME&*LdX%ɴ(Lt9.\:Ar08ZHiXA\ge急9P7~Y7h:.ERly#4I, OL΂hEc+!Q"Q.0QQk$s^Z*\HW>^/++mSym6%_H>f~Y_#(>X($b#}!5rCOC\$sZ>,!^!quN~'nt6Y;ὋPƃ26j;=@mǩ=E9j/S{^*uR pyˈ) 1h(ˢ<#䁜 p6"WNeyV3^zAS2Gg5lq 6 ٌԕvxNSN_:&gә8!={E SvI,fePѵ9 w1CMjG"Jfhr1˥0ed;.^vٍeade/>+\'xJgxNy9 ^^CxMr ˵xGrrDL2)fKK$t^řqH "%/WU.D \B15$>h+6沔z|<l,Ǡj?9ª⊊ *f[UACIvJ$|wsRک|>Hoebb0nl죶I:J*.jQ ?Ua[Jn#I,1NQvn#Ʃq+i|b\`gE֓v)SxRoVF11Z Y]Vl#+nV]L3؍Tr#(7akQ^k "7Vy#{-xHފrwPe(ͶSJ_dV̕h{D|X!U))hƭ4m+sq>O=OnTg 1w d='S-JQr~~"oVnSNp;2+!Eޅ42)[PK\t2 PKjHJandroid/support/v4/media/session/MediaControllerCompatApi21$Callback.classK0_[sՋz1֓v †BwmYRl̓T"x2/O^ {! C2aLH[(Gf%eV%l]Y2&UԺ,6z )=AzJsfɊe<'~:m&RA6(: GQjJJ)T/*WUy~v! 6fYU|u>Kߔ㔤cN)gda! #mbR*Fh$PK{PKjHAandroid/support/v4/media/session/MediaControllerCompatApi23.classQMK@}ۦ~('=xP.XoUA &YtK6/O?JSvfxo0~6>>F>s+tYF;k!|Zi&R^+-ClMM*ci"Ψ3L"^iDB֨UQe'Rg5Z u90ϦWԋX>&X2 j% ŸLl?{1Zt142^s%t:`oTP (pm_坆> 6OPK/PKjHBandroid/support/v4/media/session/PlaybackStateCompat$Actions.classAK1_ZصIOz=, вgwf.y,H{)^f|p31 ǔ!lSQipԴɩxP'a΃%FpF&PKb.PK}jH>android/support/v4/media/session/MediaSessionCompatApi19.classVrG=cK^Io2266`.- @qlK\jcVj CJR*D3XRٞgzz~.1ptaC#2Ȋowv,k=c݇yLJO냦 @W/MskԔeNŢ^dٰtFIa0Ȧ6w2f T !҂Qr^ʗ/jCdwv1|$ M=ozu[kbo%k D#dx^ MFdMMkFK^Bncr}-h{sZ@.9d3fgD*&f8Z7*n"btiF0T<# 4DnD-FTQH'ŠSn/%m}t Ӣ ^^(ԏ6% f}xP557 !~ ܧ{Zڶh3A)W©Tʕ>UcW~PKpEn$ij zcCNN-I]/8SGI~A&lB=N!t~*Qj!w:hVpƪn}])"ΰUTqQ' ꐺ7n_zH~\ٿ٪ xN H29Ԩ9|OF{2zGIO`NK4(auOsN8 rPcW1'MˈƜ<"^bR4u*|S/_@3wW6%hDqU-jOƑ1p| OO '! BlxKEs_"$Vu+ѾG2a`j'LJ݋k)cfGAU6%E KV)}f3 &@ B"] )8c=>~'(kuVS/PK@J(M3 PKjHTandroid/support/v4/media/session/MediaControllerCompat$Callback$MessageHandler.classVkSU~&HJ ޤ6\C/V{7$i&^ēd KtwC֪pqƯ/Ng0uM)fy9S'e;n8 +32fC Ub_q]7d-}#!2]6QɸО]ɧRYM3JXr]FE PXO&j*?ߜ's*B@b2)5N/q 0tX 3d1SU(x<š/!2̈́/:xi^ї/ɉ οCP?s&j:ܝpZL:%hI}ٹcI'~Q+1)5hheGvd=;\E4 o&VzO,)ѐB>z> (C>z=%B |Ǘ+_G@z)INBh:&x$pa qa(ːz6#xd E\mIZ9Z*ѪOBZ@H r mU`{ * lUCgm5T15D =jBdBtQ!q^ڃԋ9i]4px$Wa|# ~G֥(Hx* ^gKTw]Ac"Z S6eSD-GFk= հ/XþFz I)@X:<[^;"ڋ(4jE$tʳ< 7 8&N<PK. PKjHJandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImpl.classT[OA>\)/[/Ph"bVx:0Ywgk>QƳRP)ݤ9e?Xq`<̕:k F'EXVQr-3nl \zS\7 n*j#N"'J SMadus$ +7naRx:SP{Koq+.B]QÚ 틞.Xn8fC+wB+a0{rPǟGu(mCJҾLbK& j9IKI NtV0|Ep*x.4{G Ae{(d R IL8ӄW`0WuA87 obE!nWG w=@ gGC{K qud7PK)nRPKjHLandroid/support/v4/media/session/PlaybackStateCompatApi21$CustomAction.class[S@[҆(E x!-pM*hg:Vqef &5IQ3:r<[Pf7s_LQ eL¤0SL q;w+̌9 ˶E- eОlsZ.s^(Hg1xixJ1(;%÷m̿{̇?4|?_q{0ț}I4KUCH"Zr3ЅbM8}&j;bj0!I8q@њM\fI3Nc:*}JCHA PK`MPKjHOandroid/support/v4/media/session/PlaybackStateCompat$CustomAction$Builder.classUmSG~fnacAD!n q 5FEJ|BO퍸/XSWhX|MT2H>D{N aȇ~q\.`p _cq"7JR[%N8[ILg0)7wx(_ \{i$"2RʟUTGi,"őWsęšζB/d+~m F9m^(dAUE՗ v =oG r*C2."I$}53ϯɘ*$ף8uN8y¡ 9;|XTu> Hf>#Ե3<].ru_uQ4CG"e]lQᭅ'ڪ0S܂0e+/zZ8 0 fpڥW|_.*^RI\Yor|>쀲I]ޝE[vvٚ-xT~jkЏllX&>-bЏ/L|05V.1TzzOLdo= Rj-: 󒙠.2)n4÷Z*fe<1-ns3qL?9_zŻ9 j#ZNҚ4WD:G nF{ ^cm?3+T2etQx!9NJ +: _% N?0DSj `z1z5D\r8HkoI&8lHG{aB5ȝ#'f* _AhcMP 8 V&B9_"K(Яk-|:աS34(`?DݴIW4;PKmogPKjH<android/support/v4/media/session/MediaControllerCompat.classW {~OnLb@@07r0&!B.Ce;uw6Rޯ֪j+VV[{[k ̬ϒ'OΙ9sw۾K/؄э3 ¨xߨExB7I-O*x*z<wg3u؎=9?+}9@^f”r!Y/g9\ Gq/˗W9T ~PgM-ճ ~!t$Yt*c,;2ic&eCzESDim(}l}.pJ~Q+2 -~2jK)x]`_~4Fʑ޽M')-bjd$fg kGgf٣C@uR ԬD&e$"\ZG6GV)+2d*)&9:[ ~KΣ@e_\|DaXKpXݲ#R~.K@OǨ@U*Kjа\rBόh.͘k樖1任Xe74QyK*].4lĐv;;[X#$M.xT\O\A`ɤnQШqH_ζSh9O;_z:2NoB!4ibNr9Rцt[Kh&p9z!9X a׬"h:?T<]?Oծ;ږʴɴ=/*/ P!;3tZx`Ss_&JCU)tˮ M2P甊d֋R+kTvl1_L@sja&֎E]z>pGtxD`R?=KEF8¦YX7y)p[h DkT}PƖ'"B]3=:g]u֛^?"jfasCf{v v#y'qЖgC8 8OG#o4[(Q*)Uinip<-RɿJQ򪼒A6pei8N Rmha[;a1ۊ Bi8i8i8Kh~`%5v_xM; 8_,GҺT]K-v,EY, iҸ9K=#NI%`~e7ԏ@wCh#<pcqH0e~ FXGx*P0BpB (as^ D`a_7#\Gx3qe?aazlU_r*q;_EQz~<`9O^~AI&7t.qm`E5(HVGQb].^FϺX3> 99{k*B+ U )hݰĊ"{׻ 7U{M5Eyi]E&XK HZFb{ܔ]һ%a֟qE^nU>-X$i9B;(UH}|֡ YY `7#t*g|\.!\y+\F8o\y 8o\yo Z}']U.ye&$YPK@zPKjH?android/support/v4/media/session/IMediaSession$Stub$Proxy.classś |ս3c Bx((d%@(0$CXGk^֪V U 8vп4b5FM_vݿLĵu]狸KZ5 HĈ(p. 6mVPmuu +FOd/lX,.[ͫѢѲ;lGLQ ?!;hC0j|#uECl1-.7~=Y 1P \xV#G[v÷/]]bE v 4MTw)j7EBz$(P9#P N=wpn7bV.?_kTo_Xvs.JAnݦv+Qh3z9X4 *z.7u`z `B*  ӴS0$lt"T*(ff|f 99uo :L/d/փѽM]PPIh|Z;&=@/r&xM4ݡQ-!l Hp necrv ^tF\ׅQxsY QXT c%eZ!6D9bKT#XĒH^$f26څiRZSc`ej A˯oQqQ7pg7Dc-qMѰ}pDds!dh0h%GE}]m3F _(*>Jcd$)H8]ac/h}(QwP'UV0 JZWa]x\nffa#2sCZk6tG\lDu3Mh%eBbP7Jvc<& +,+6LjWXjW$+n*x xÍ7EB؅?vperc*6x3 s}7>\.n Rv#7>s2`>qSGO= =擫)c"<&6u 𙂆>3$rm¡^ahBMsbHazZ^ .KKhMT{8m i 7d9H6;V y?y-ǜ5"cԅUoHQ+eC†ޑ0-.&E J_z0.~(\4RWvN(ܔSa- 3U=nu("Mbp7s-Dl wKեN1%!g҈QMcB$U8X͢L*xX*b.mYZ׼[ʦ{&8I:w<,sn>(eB++5,.,qSSv6+X0 qXuo3f7٬ŦJ;tޖpt1@ "(JpyJ9*9Q ׉%νw w`̬c%YvS\FgM*тr7*P^ Sae}RbCS"T~c0q,GYLRClY+a+|#1Fy g)/c[+b{4)@3YPy&W<4*Jl:p<o\ y IΓ4\ȟB+F#*D9Jo#=',J!>wW@,4.+ \")Z\6& y69M@y"T($0*cfeyR*i,i,"6Ilע!L(R,pT0HUNBt!r ߈}BDgD%Lzl,/TD,Dx2Hɦ@<< V#h/gj Z8T'l9b 5`sXW %ɔr-MHjrå^kYO,*RJ h8lDAV (%EYB b: Ru$ABXQϰHsml-5 0G)&"n' 'a # >0C)1ʉa"1T$bH Up ʊc, KS`HE{0sE*~M;4M5eYy6duL'jE6ɦ9yVgë%ˍ, 24U.*yVKZKmZ6VIkETx 炔~K|*xz5hl]k]OeC9mhмDNNШ'DNBNFZN:6tmh9m\Њn6ԑ uXTJِ# XPn ^ZЅ$d֐L;tA ZK т -(D Ђni[Pgԙ΂֥uI|ȂWlRØ 8#,I4iETcU7P݄bu3 ^3K1Y/=} ˱ 2{-_zwL!(#(ƄZFzdBP'0KNCݗ"q}%ĵn k+q@\u1V|zME5=k,̷Gɾ<᱁<w}7nB1vzm\4H]ml{D˻NaQ R$(ʥ浰\]ͭkvy|U/v]8]( QYWBh#i7"iOAwc T!]z= Cwcu>42 m+v+AFX.aiE2wR"vRɩ_Կ8ON91uP$arz#Irz&')rz& M}i3.8M8q*#*h\IkaVV˳I E$,=Grϓܯn@r/^&1x꫘;oE3oa6.d`ٯ@1`S .6EM#|1ʢXebE7,{Q"$ `<'fGax>aՏw[` ˳X&D_؉εNPXĤ)rHJ_Qa *%M5#44'hYaiZvݯdaQ5vS|j؟P"j8Ep 5^@j8#ip5<I ;fy652# xjȍƍƍVI 'R*jXI &@)m,5N gPÙp65VC gSj854, p>5gSp5l"jH XëNb!W Lm[e-Rk+1I;SXŲW BruK#ֺx &~7((9e^JIԠ?IB缷s;0@3PHǹ_ɚӵItav1i-@٦ҌbEoC‹^ſ\bUHfmdmd3m .g\(Ӯ@v%\:jR*YneyvM,:;ۛ,fdy%3,Jfx^hגui;[IƌE0C6|si3F[[~%s}dOJfkW2[݀37< qxv»n';o$o&=vmm޶t޷;| 7 0< RTTXGT>D ʵ.=8 f\=b{ ?$ f S+z1,ͼc>9䔔nIt+ėd{~vZS73zij$=E~O3Ks4hj/~CS{"Mn6tv[:S-~f4ΌKxޫ^'7M{%0Kxj3WݞHrcr}DNr>%ϸT~R(*'T<˅?/}HG豈8T9Ŀ=M⿛C M<>x7+PKOK'5PKjHLandroid/support/v4/media/session/MediaSessionCompatApi21$CallbackProxy.classVKWEjfgC41&:@11*b`$  񕚞ftX"DžY99Bf ]B_Vu ش {n{SXOb؋B }( $lKb,KB,+B\U!^u!M! (P-D5+WcXa7f ʴahVuΐF2rjeg7fZYYZ릑;ə'jDWW n;. ggva`!3gV7yZҬ"/U3k-]=c^)Ăe^p#(¸iܨ5N1Ktcq D2rsЩ5nd +M^trEfZN^TM,Sx=~8'V({wf%iH'6 ͰɥidNK5:prp 5k^PTpFNYH3eNpjmS.Z[*սZѼh6mkUJafF ta3IܩQy–t6t}ly^u[7E.Eql&mM[/r$0؋ܦv Zަ0tR9u۬N6u*poGFvᎏz'ǭoN`:uQS ^T'qZ, <Lۤ9! 1%D^ b4Va8ݹ11 &wk_*i-ab38B}!d$&,4h܏iYJCwn# 8L4"x Gϟ܍G G$mk7M'hDARjt 8p-꤄z >&mYJN݁SȒ8⭹Ga=pZo"SCwۈE>D$|#Q ssK8Cl ]ڣ;+=ýs+Qπ}IA(dsy>DýJRM>䏑"@>mơ9/ Fd*[lJm4-gMlFǶ䱥M$dwID&Y /ܶAeI%62~Mߴ U6qvq#ĝ @?pnB`u[ҿGv)G(=B!oxs״g55 {%,H}H"{T( Ұ?PKt7 PKjHPandroid/support/v4/media/session/MediaSessionCompat$OnActiveChangeListener.classO;n@}(T)H- KH =E]pCEY E4L1oy 8Sg‹̩Ӄ{ 7!ޚx &Lל+ekѩ)+>FymT.lSUve*o/ek zW¤ș#$=tEz'0PKl>PKjHGandroid/support/v4/media/session/MediaSessionCompatApi23$Callback.classQ=KA}c֖ڸXA@PE6w?%n"~46W̼ax`}@_`0p̻k ;K*w 򪶹QGBoj6pxrsv6uEմ%X`0v uY:_Յ,"TYpLH~ʧCi>ɻReyOeӻKGk+ڄ6Bgv"l zn "&PKi$PKjH:android/support/v4/media/MediaDescriptionCompatApi21.classWAƟAtח2JMMPKQ},֟U}S:}tgA#.3;Xn}X2E)@*H*#V{uO <5`le(\3k͔y&j ]Җ:CG;^F:IvtYTW10~vLĦ1dLLS6LLb4vLxa gR0@pk 4u^k]?oc[v,5כfXw%{U;qQ SMAkjbANe/'J?a{Hɯ`h/ Q0qFfCaZ"䔑ffOy.V MQø -?/3Ojؿ ?o㎞7Ĕ򖖟]= Z~&C}?;Ѡ ~k(Y(9J#uXhfNܿ=o0C֖n-J;MqW|OٮPK ΀ PKjH1android/support/v4/media/TransportPerformer.classUKoUǏ84 i6)4i(C)Ph;vn=IqFDP@ XvĂnI/ `/9邱9s=|wfǓoaD0F/Xb"!a=i+!BFLdT]Id }Ii5rkhB\8Z-(2˅2ƟP'nWS*Gj]sRI@O;tTBE-3-YТIY7oδie}Au6~Vݖf=tgFe[Z3-i@HZ%GuM*d#Jb8)SZ WV`Hym&fk c, K4J+\k臦UK֖aз KgxQ҉:l66 <:R򀚎D3M j.SYn1[$ v ?%f%j;y}LW 1([7eZfn1n4ãj<ǐKCM.9`4qWXLa2lJ sq=.0?_4^ܼgԈsd3^:l:n!k;w1whcf3?Fofސ Σς7 dt؃d?a}>Gp}~:T]0Q$/'NZC#IjJ <@]_!W4'g}>>">F Og(s/-tr{E߽JH|T Ap&is=ۢ7[ C'+_vkڵ}^mW33)m^q5Lw@wF93 ow^ QKP S08(_:V _EÞ֙^EXW2Uf@ p(y$znnܻ.j Q* ,BUtf@'aePRuq"u_nk aX&_T?7Yc(XaXa؝*+&]^?Rf4,3cN휃Ik 0Fɦi>#Bk:s '$o8@Ř%F"\,&'8'bS!͐MSE}PKʞPKjH8android/support/v4/media/VolumeProviderCompatApi21.classS[kA&Iwak/ZXvѥoQ ACIv[f/łLBj}Y3sۜ_ 0qă*xhb7q`a8`$ҋx 0 U=`yi@v+0ox(d#Y# yzY?DsI 0pb-HU#vSact4КahpT˹1t(7QP; K}UZTDzѾ9BB+WܢkcUְNؘ􉸦,azr C4GgbG:IbZ";ئ-Ck4Jgu~abBb.v K31P!˰=PPKPKjHuandroid/support/v4/media/MediaBrowserServiceCompatApi23$MediaBrowserServiceAdaptorApi23$ServiceBinderProxyApi23.classVmWE~H+J$-EڂԼڴ TE[7tϜmmYeHs;s}f_05(*L">u7Øc!q+A|43Hsd8rΕn!슰[&ҺZw,;]70HZMYSm4DaW5]l"HumzwJjM-CzݲuM0#dj]uO'ښg-5Yd(O}`pɨfuSثIXTjmy3ca@c>p<[EGTL-U6ږ\ql5j+4 %l/r>WLoWe7C&n{E|c !+uaa5Qw=:C1wA0XqT۲Z(Cǚ!R& ^vIGYbHήnX{HXAAi-Lkh4gWY*Mm`SoW*w8mm͉ 5n[5U 6k>z9٩rSSکt, Ǘ kK O2\w)ZĢxixב)eENhj K.#^M'S A͇=_DX\F0a"MMUo>A-;[]dž{OUMs6ÃnUh796zs> Xf9Ryҳx$cd)X23v< z!@9uDph:~FY?`4bO |GDoc3Hk;ɟЗzx8'. 2 "Yi"C@o!Z.sཏ)]0{gU捒H8i~q*BLGA k.(FwgH~6O O#4*.K$CPK; PKjH-android/support/v4/media/RatingCompat$1.classSkkP~N&mKMS(+'is2Ҥ$}GlGI(m%y๎,6tdH5=K0,vlW ǻ!ǝC?S0hAH]l Fáɶ9ͷ<~ yXa|C(]{9Fwj"2Q`ȸӞ R1d*OafGwIWafÑOZS f2ؤt~Ⱦe˼-M䭔}x/ExY.`Ht-<6Sp*(0 ja:17 9FMY1bڭe*{cm3PR |i6L;V&9w(BX dQb}BZ~By_=;G۔ЗH(?&Br~C,VJ~A:!I `=ԉ#YjpuZg/uC֫X&HVĺz#T,GC/CIᶚPKF E PKjHIandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImplApi23.classVR@6-  !(VH jT.a*Mw04$:3*x8 ⌦9g|'g?Ǧ>LѐQ`JFV4f1+Ě2dXdHpel797Ϡu-9^YӼfḾZl^3u] /Y7(S S ηvEc֮q0Fk?B{>e,U- '+OYz}O]W`;>.j dT6c7[TO^H7_ĐgX 8MW1!HQmutoyE=XW1"C oox~Ԛkwԍ _*G7 y|.'cNN.bqEnA MSYbЁ ޽Ʀ 2$V; Cz{;-ELZ%q`hY@AIDs2})=@И?,Ѫ C<1B죇^e2!C[DٙO`}@gChA]ĥ(p9WX F ZhD+1p#ҔP1D#М)K9p+],$p5kj֓$WI^ ~2yPK PKjH<android/support/v4/media/ParceledListSliceAdapterApi21.classTrD=kqܤMӔ~'mJ)$$ap3_yȒFZ< [) 3<p$v2-vu{ϹoX^YY[g |XGXc |l`f:,sHt;f8`M`lvm!-/=m{m%0U]Ռ*ؗ-uϒ#ϘӇv(p.vm3| yt쪶-/e`)Gv[m |mpճHbr7Ҷc.kK?偃ReU oZuhgG #G OI5;,{?nx#Xcpֽ*vd*Vds$zBiYY$d,m0񙖖ַ mr 0kRGכ9u E^Dhb""wǎaᕽEL0Vf6Y@( m7u8#-Ra8Y>95팏eQfea#+`\\]Š%#nGu4&wm;4Vd6>3d)`*n%p핏7'ԑ2~ '^Д]^&3adCүOaK}_[+:gZij]Ѧw&W//`߱x-K\+Di]l7V^" 3c@.IuLk[Ne)+B:D`R@Tʥh/HHncm~~c7*R|9>'' :V6j-'NU\ʴ<wY]x±T.L=Bjj.o4&BASfEh_\ʀI&A-aĬOƙ og'$*% &`Z˸Y|Ea˨}DS#cg>c ?GHDAg@? %fn_|ˇN]"#/azx&Lޠ2rdIQB\<PK#d&lPKjH:android/support/v4/media/MediaDescriptionCompatApi23.classRN1=58  M(u/L%̤3]ąqG;6io9 qk(`Cæ-tm qhOhqrKk#HwîƣgLݥi2sqԡ%\n\9beϘ ݑGM NprgH'Դ30{CS$+eIXmG߸cg<4ZAH6` dMX下 ZTۛw<(!&?J"rȣC>ADv ^}yTۆ%93@H!NjSd 'Df /'EN(+ YICPKrZkPKjHGandroid/support/v4/media/MediaBrowserCompat$MediaBrowserImplApi23.classU]OA=SZVDTP ,_~bؤ#g 4iW_LH4>Ζ%>ܹ=;B?n%$Kas,0?J .HA+%<#*p߯p CHz]J\UC-ʸ;3]AM+<GzVӡqkn u=d;h[ U.Ca$(7_OCiׇθy-c!A߬ɕUBO/ٵ:a8,aՔh0 vcPdy*!JFKGhD`J#elJ]=wDŽR^:QjjQ!U! DQ5e]"5"TJn!.vZOR;z≴D_gH`XI]\q$8˰yf'\oMa O+0]S {[ |E9V0C_~'fUAcVȟ95_df$"s^kebJ)`Xl6Y1Ⱥ )>+hb}BoH'~D36`W1f(v[۞1l4ޤr8G0hOy,b7PKFPKjH6android/support/v4/media/MediaBrowserCompatApi21.classW[SF iIJ!B.ĄLͥ1qJҖʶJH2%9}h_fNϮTngjw˞=?܂6lFс- "'\).q- Gq/|s=?2Ed5װ̴VmfP8@ci3 -,5Q((2\lޤͧFf5leWi1wiɊf'm)^;fR(1t/.1t' Ub97բnhŊV)hb}i8 s9,ۖQN:ZͲ| -mPV5cn|.ٺ검vM8̓<pa&+U/_KꦛLMI65ˤMn,'R7]R,Pi;2Jz*.mYat'=x ֢E/cD4=gZ/Nvu7-v zB!1\%)Ḇkk4z2-1P X`#痼:32&m"9zǂ$Lˋ'\l:,ǐ#1%n0';fs2V. 䮜aCU\(Z:B#.).:#_W1QwTǾETCCTr⍠ wZ;,P" acoݴ<^;}BzߠDž61R|qI;|ɂj#XkU 3?YFj2&>` /Hm,'S—Nc7:^:vI;A$x>3 {ԍA A3&xJbFLjzVxÜ*Bofx=Bo#B bV8z,pc8s̝)9>҂r|O@_Wr/Ki] 3R vW[,H T۪3xlA'^l}oI$7`[]}OcqӚ\ɗiAy jG)6#>qi^$a/x]t%.P#PK ZoCPKjHMandroid/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21$Stub.classTnGv4JS(!\8VnI[@$!Eƻgͮ5vy>пH>@c88DHϞswQLb) Zfz7e7 ínnK}׿UJď]N)MK` kw$bG XmCZF }ɦ2 aTLQhDe?9$>#Kӝ_?]ϖʥװ~a gLcS<-j}Zߦ~ԝ6~9|3ajS9b8oZЌ .BX"G73SGb8ֳ`W24e>er|zI'9B((vwTs% 5.PK HPKjHuandroid/support/v4/media/MediaBrowserServiceCompatApi21$MediaBrowserServiceAdaptorApi21$ServiceBinderProxyApi21.class[S@ֆ(؂V;ޠˈimR7iGAA_Pg):ln{Nn~0qq8 f89N8`<KgZ M:ycC^mKhl{R|: E7$lVB9}ϋ WH ¥f{fII 8QSnYPwJ8cM4(m5ni{q%cLE0n"q?K g.忱~9G{Xc?mܙyW} |!rAUWګt?;Npji$A8Bt9}^Ij7x9ڨ:hk;Q]0Fm}s|& &U}j6."*ik7ЪCOvԻRSZZ+Z~=/DM+*}poa?P 6‹owW]j$A$DԊM%ޑlV%ѭ:0'c5CcA]g"oڍ%qh3L#Fp&҄1g_PKuVE PKjHUandroid/support/v4/media/MediaBrowserCompat$SubscriptionCallbackApi21$StubApi21.classWsUݛ4 b5EJi)v,@Ivan T||Bu䌏s7KfQt&ss={Ȕꋦ9y[& 3_7=e⊚F0O yB(hsE, /2rv8ջ` +mi3$\T5%Neql: cVox$D<#VdF(*tܤlCE T[Z‚S *q[%p_g*>*z@.T×rav۝y^g7 T&C.f;Vu{7:Ջ _n):cabdeMloS[xeLty'',W |6/R&&Kwirdp0WU l u7"uC#PGTd%Enfdꆽ/ynV |jyJIUض2kSs4 q:g%Jo6L5\oCZ1K? J1I?rjڍЎwGLj<'M F2%C*i$.2 >Dv,>+<ψ^zb`F5( ဤ "q#YEr{t;֠raDžt*6?)J0?A*V!s[qISp~1~% W0ɯa_~繀5~|YtfE٤%O2̧qG.rDE0D>w!( +x㈏8J NfK|L!c4crm韬b cUo! &2clӌ'gQJK T&erT#$ކD| (!9kN-?U1'QƛSqi}7Rq*܏!bqg%PK{ƕ/ PK{jH8android/support/v4/media/TransportMediatorCallback.classuJAM֬ X4F@~sǝevvC^ę[ܟs9R'_d 0VѼAngVNl^W ,d7d?3rVk[hTҷ4]mvdQ{VZzҘHd# g!6\h "ŊsJIX[3v; :wB`PK r<APKjHDandroid/support/v4/media/MediaBrowserServiceCompat$ResultFlags.classJ1k՛ {4ބ.ݡd%ɮj|J^,"d sGE؍0as؂fR=R\[d[YPjZӕhAh/+zN.]imhp|p%'e V&Ih}0J/a`&TYrԵx'FMn&*9Y+*<я }tF= 0 8ԣ- 8ȀqPKFPKjH;android/support/v4/media/MediaBrowserCompat$MediaItem.classW[WW' dԲ.R5Pp) CrL j[{˓}KֵھuߙC/'sn.?t;P%FDNF5˘`>J'41\qzh!+q+[F-r8(E1[n U@%ܒp[; a!!VZWme9 ͆dYK+8i0tdT#gZ.i r Gyt|Auz33G'R}i؎j8S^AѢ{j,bAJ&G%'&Wb_ba1r]ylF'9Cug8ggmu$b)2 9ݼiaZBi3Ge4ܚb3Sr5˱-2f̕ G^V 7eLhy>d,hF$Ra:MkȨiu,5BP l2xlg9 [()['M$CzாA0Ds±4caGsK抺&u՘Kzd*`2^W _*ń]`5{LWoxKt"2Bpa`= 6(^<m7AV (HS]|Jl2 > ΂R)E &8TW˛Q 2:$|KkK8W³Tf; =[[ݛmSYJ,6uq$'i8h:4{@d UdjMkEndyk]b2H -3xк=1ZXQXnsqBnc 7rzlO7KoDpX$ D%# &~w(8LSҼ˝BxOe32*L,!+{ZD6o#&%FjTQi ] ݾ)B2 fsʅ TGx*g!N`/r>Yu@/Xw˰"kj@EpM)2؃UH;r)\ʥ0f\ ^N!>|3h\V`/Б7\SP5UE>4Zp鷓pӴPKF*,PKjHFandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImpl$3.classVmsF~.6(M(ihqp M@Pbgы~`vĦ/vMS͔rv\u:seLBǴ,?qQmD(6|r`Ύ^pͻ`8Yq3WwWl!yvwg2*9dCnm&V:4&%Va8*BrLᑙjS<]Jˇa9`uCq&WnPPznI^dv j;Y qF;.4CZ×pVoC60ZcI -(gMV|óZE?p=[zPn|{j[fVAGEUaC$j'tvB[o0adJ#2ԓR'*q tr2ai27H]_( ,;sTN7GUAοz2팣=*7¡R[s =NXUdm!,d3KЅCVHVamOIj-I]%ug hvO,4 LJ 9(T`YCtjm*n` ,֐E%ҏ`˸B2J.> |.uod3O^17PKf2^K PKjH5android/support/v4/media/TransportStateListener.classQJ1=Sjk}Fj 7W -ݧ3M&%,W ?o j{ν7zpy+ctr E6dJ/mWBꡉDV|P б5*t215X ޵B8dR' 5=z ciCm&IHD˜rc6oz" (25yBS$#3sɃyY%8yF|{ 2Rale^M2ēoz&fmd%WHԲoPK C33}PKjH<android/support/v4/media/MediaMetadataCompat$RatingKey.classJ@V'=Arx$AATR>MeMz(|DsT?(SMQN[.WԒI ~SZh3:rʚZ둚ROAtѭ% /Vlk8!WVnVNײHԯ4'^ ѣ1\%=d1[8s[6 \$y>vac2BDq PKwcPKjH2android/support/v4/media/TransportController.classMO1_M!m($n)Bܨz  HrI&[Ŏl/?~@TR?3>k(] PJ#k$)MPSάD#iUd)'I=FMc JBk&M lXJd^zꅭ&+pNiåKú~*N8c켙?E J8WsYKS#>4Yc}XgQ6퓝X&NZ\ӕT&cm`2>~g\+YGx.q*u_G;X!|uГ G1S0˻7Y@Jb!XB :(k+Ws+lD3gooPK99PKjHfandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase$MediaServiceConnection$1.classW[WUN3e0"Wm؆PԶ&4BL0t2g&@Zm}?s\Z_9gB,,Yfg=}Μ?$~G9yやN (?ޔ["|Ѐ]m `sc\]N"cRU sS|$)4N1&3m[z:Zn$neI)D$ʹf3-'yRҩsgt'+,ҺyG4{NWD&kfijvPGsD\ghqUWLk%_N.l72w*ʑr<^ >kݳ ?jl1 ĭ4֔Mm$Ii%e%i1:.  `H8`/sNtp2m_⎌5áAt7L@r"bNG\Pt 9>P X[v@]ܓ s[{?Xď-k]X _N͒\LPC TmbAQU`oOOC҉v\];)nsCB׮Aݑj'BMw@S\1ȴxgn$qiT~Җ%R!TjѧakBd`h˱>ű޽F._ŷf9ͣK"i?w:UxM;fY)DUͭ+gRk$*^'3Ƥ"=ቡ] Yh"i4̹IZW J 1_s34Jmw#BZ=ĝgp2X2 Bxi#m#^android/support/v4/media/TransportMediatorJellybeanMR2$2.classSn@=qm4/`CbQ;Y!eKsg:$RasQCCґJ aDH 6+iG<!аjNP`\DH5O1 vP>Nx'$XOM M^8RuE2֡%,-N ib΢51TU~D OΤ8dF0f&L:2N5>ww1 ;ReMv7"7 yVm is<\#B)Һ /;e[*dKia5Ռ=y˰^r+w?_,[>e?Rh1\\HgzF^U#l m4_PKUPKjH=android/support/v4/media/MediaBrowserCompat$MediaItem$1.class}kAƟI$F۪Zkyjh!P)HA$Kz! {TO ~?8{ $AL8vn?xqxlI!<~dMLM<#{ceB6wBWܦ$,lGwR}{jnCtNl;M[T}%$#U#m V'z.Flu$3@ q[WsV@ YƫeHXg4Ӻ:SwJX.Oak&|M>Q;Dv ,I{Wolৡtl' @cu) TC٧sK]*8q a"{>cCL5Zeӗs/p8:хKD# b=a#jJ`knU#m# PKw FPKjHOandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase$1.classUNA, +?ѪPdP!\Ј!MR3N`q;nCxej|(㙶$q9g|wf}=l&1̦\ (hrK)̣hbD"~!:ªR$\f* 9"zm0m; =vބ"6KۂP aVAtWpTUͣ/u-U 7_3$IɥkO;^O\yiyLj1I\VFСE~{=W;f΃/. IW'Dt0K.²`k]k}XPA[2 sy !44fpYw1}F&܅25z6N`Hc gg|>Y^H75A߱<2% jL'$PKuU"tPKjH0android/support/v4/media/TransportMediator.classYy|U&L4'mmi9 m$MHiv,݉ RD* rVlQ@"H AAA@PAPPQ.|dwma>|u!kDM/N~\ܤ[^Y-^^|ո&<8;$|]^%x?G1~/r^h`w6`r6&f_\md~  D6*WPpP3(P^&@4du*Il?fVa}^_E7G;՝ ˔6}f l>T sԇd1T ߬'z g,AJ*k}+w`Eݱ0(1u^X <0 Y>+4ӬÜ\J ,I,ˎĉGbDr:jꃦYsr h"Qc$W1S5LaD6*'뻻 YIHF :Fxn gG b2Mٰ[8K9 #.#F怬5eD*F0&K(0MkTqNgAy=\-]VΎNa櫊 ب]`}+9'S0EŻq*^ſ^UQ ^S:PрFU ;LJ4&4؃ TޢPKd-l\/P;xWEPT|QG+rYTkU Uz{qr 1G.B\ʡH*UH}W]d"Q4`JSɝ4O#Gǽi^z(cњߙ8rv:9 AFy!ajrz~>nSU-jBbY(yz9mSaÔ`"bYA1K}i|Eu:WI뒏0MުpU݉p#˜>)y'r;{TR3uUQPUuLnG<_= 샻fH(G8j,!y}_DqTvr%~#*sNTV넮f\è1wczZ~.|OZ>ߢi$%ߪ?T+-nvwaI#fiou­e0=B>z$K4ŔeX| dCZLe em56es\ҚRw)&Py[;B"[OP'iSXJOE\ |' sqȕ n[lp9\y,ۊ/&}Fވg)/49*,p3">Ճ!䧜#dH`[NIk($\y^Iӡ"MF,_w>ȂU2mװ[:2Xх^::H~dJ*t\CVu]4u@NG:nb- kC5kx2z 6еZ޶nSKZKB8C>׫oqK&Fk!:LĐ)5eɋ#h*[YɨbzFJ=동HNBs%%Wd9-oh*dtRŁ5N5f,_'&YqSXhUw܈f׊Ԕl+6MĽTt5Ee01ÚFiKAandroid/support/v4/media/MediaBrowserCompat$ItemCallback.classU[OAf[:m]\"Za*^1i,PBⓙv'8m[L}/3&?Y*rQև=sf~߹η|q $pW92ID0DC p\\ +qJTo]Wd!Q `HURdo-a9ݰ@Y؜rUp!\g.zd+WꕒDɑ악.|hDߙpmSUWX[VEJXZ.޳U"Hh/,jIRZ_{–6(eCir6O7Z`ЕWb+w#t.rz7 պ̹[(Hu@s#=UBq-Y~YW=GC8qbXhYҌ7M`΄v[ ӭ2ӸMZA լrj1R֊<Xf(7 Ge H,Rrg Ŧ"?Pegn( +h?,НL$-B:xh# }yDeg!nx}+$K8Ek=0~Q2L=o;T4wL7!W8G<.oBч$IR40q,? }ݔu#,]IX'PK׈PKjHDandroid/support/v4/media/IMediaBrowserServiceAdapterApi21$Stub.classVsSEmVJAIChE.m`o$IxI/7^}Rx>Eq|G' a6 &(~mv營!c2p2$Rr)94 Ӎxg4s!R.59ആ\d㬆Y 9 s c#x215=dhy,\,چ`XL;%~:94O'&'.f,%)rg !u ]^)fSkXTgآ`͋Z> ^pE W4L=Paŭ,žn0D4O<nr]f9!rKi=A'y8GǸ-#sJŢe>PQ G)a1EWCEc!4ȝa XbO:!˜pWkJEʇKf6/}dL_BJ0)RM0U`J/]jr ~[׈yr&q^T `h tʑhĠUcU/z +ንjDZ6 s"![]+Z:dzA'GT_$r-ΦU~[ eX/#gbԶ +zj5I,3\2 1OP|>M ũFx5;0daLv;A؀w۔~³Fݞ&=>Yo!ճކ ͩ&a #w"E4{h\ yF4"J3\'N| (Q)|YP>e]ŒzȲ)./ E~e W6^q@o#_%:S-D%G藣)ZF0=3B`5~_t;"xlOEreu7="敀ZV*-D;|wк,x* `)R'>)iq/ץN}qq| |N`g#]o?'Ҹ,}글fv/Ui"!Oy.zC$OX_[wh oZKJ*y2~Bc@f4WC$A~%lpd uGk{w3t B -8=>SPOeJ'h'mFj@=ߦ#/PKaSWn PKjH4android/support/v4/media/MediaMetadataCompat$1.classko0_gm҆cePƵ^R._ЦJSEJ@6Q)M*'__6 ?8Nij#D>9~c3xlBC̈́'{j`@@A: +C8.݃d|葧<F;RęEz< ]wd ;MWuUF)jwJ#`ˑ1t ޝF&453 iOJE J3T~mc)f!RC1y44*deZZ< f u)mzoqX(dʲiY2V=z) 1ŰϤ0!3U%TXUB*/O2CWȲid<Fz]9:mPDI;ȑ0@bX@hń\uk”FWB[Ĵ/aIŚqKaC$>$[ =[d a/zO9h6u44y t,gݜ:d~̬Cpc$Ex(0ߋL.nz3މ3tB.sqwJVzq|NP 3O5/Rt?4ts&j|,4QߚE#V(a,c `3rUGGj67Q菶갥]5iK7 z3ZqׅPKOk-)PKjHBandroid/support/v4/media/MediaBrowserCompat$MediaBrowserImpl.classTMo@}qЖ%> A#"56w#m(N@BՆ<3y]?㩅 nİh~fxPUV^a'- 2\yMv8 )<њ)χ(G ['d j(t|V)Hje김F6ϷVL*E! W؇TÝ4j P aM [>7F 1Ord$Q6bLãzʖ6cP:BF^} fo'P5=s0K #(#q0Xs'd:kKCɯRX%XA)Dq%Ů6UlL]MZ"@ۣraI+c8{>wNq^#&FQdwKKPK@.(PKjH1android/support/v4/media/RatingCompat$Style.classOMK@}ۚ=#!ĂĚŰfo!.o0[b"t UlȨ*t+⥒QYzjTUQKNŶʆ(˥w;t*Nlﮯ)P [uӭᥟԟZOduq?^YP:T\nж8#sypMVNxyG[^]Q-#/$xCr+x)e^s)nRGh#kyNrvLX_b6'1".'z7o{'8@P?*[>R߱ gw@$X^)ZmYA]K=|r|/8PK U` PKjH>android/support/v4/media/MediaBrowserCompat$Subscription.classoUƿ;I6&)i&M61 4vc{kfT[6)T ĪX`C$^ s'gOڰg9sǷSX Ƣ0چpy1L*ELɴ2bxR<dr_q:W]fY˚;Ж7[ z)f5f:Wv;AIv$Ւm%ͩjj+Q2u-'izաxRca[OZp|],GWKe k0; '"DJ:fոT {A/ZEۦΈI ùDH;16zh܄0#hs0|^* -u8LNzHnu7&'0 J42dM^ ]DL~ 1"?Ag'dSOӷxM:ZVZy C(*A3pv~{<4Idg/wM2@}-Q~~M$ݿV m=D@ا?vK7 yT K"bh+{0GJE5oo1B;L?xI`HQ$ OFäYˆw=H_$=—6=|_@amIyrz;Y.ՆC^?H 8oôVO$26 bm`:I`ccB|l[`ﰵl7` ?qQ~/yn@!X-,xWEB 3f(PK5# PKjH5android/support/v4/media/MediaDescriptionCompat.classXi`\Un27kN۴Ӆ.tIf&IRHMR-H|L0R RE@YAD҅LS- ȮyN&/I#瞹s~ˣ}/*QBv1B#8 . R 1c.=NO;+}ЉpC."{G<ʏ=xB]= t)^.<_150-1=?(xօ2q>x3ŅcU c/3 c2c37?'co0&co1_ 3xυc*ȅPڜ+DeeIX,ҒHRμu-͡SZCkojk մo xжi.-l1zsx,ih1cˠi}CCMu mjNpD#G[uM]UKDzmi7dj. 'nC 0-֑΄ֽU'+u#u/טf}B!E mѱn$4~\4N{b]ץ~Qkhڈ fY\_ӺY$"'cOGlj;cѓ ML` юa]`ȞK2Вa]/Z0r2aXFԃ9s}oXFR F\Fw2Kܹ˸L(/.fq3\7@9 ՓCTKF5xx HxR>#-^&Nŕ2*R&5{ZV:c:LٴSYyIa:++0#Ogϗ156Xjb1ܦ sWfهcy-l^}ë҇Ғ}' هR6c#sx -bF<PF?&a @pKa߇^̔bdߌG^ ELKdܗQW0b ^ÉxjޤZⷩCK%=)>,`gBlIMKyAE((} 17M;^FD@:uY),ȢKdI Idi $<%"6zb#Odj72c +Gq ڂ;Q$iYN7Uwbie8nR#S{m NF(*P(<(0]GJQb"j$4/։Ӱ]1yTpj !c~Q% _>,7EQJ>L{h>EIgLCd&gV0S\ ja7TG<عvvdnj,3if~q ݝl3bB524iBa3s{ϝ~9 /lxiᕅ5|_Fee*4Rթ(jI+/V$QNSa%n76uTJGKw2-OR(r!,4d_zm(5n$Iyº$cӍkڥr#}u |\Lv"ŁoZ}uZVXؗщ4!G,&k):d+}C2!499#1͑s(>M8# OPFcE1[ -PK+PKjHAandroid/support/v4/media/MediaBrowserCompat$CallbackHandler.classVsU6݆M)m) Zhb@iE4nCt6Vo@QޜQ$;i6ia$o?q0"^Oq)~Q?vic NIqOezVAFPS Y9?zqHY+p2%> ? 0eXcQ-($5ØвS#!aX?s6l[3%n=gy:bNZhJ3srhN|z[ M%BQsnc=]4Ʌq~a]6 m~6~kS67zԜ/%U|$ in^c{V$/i(`J7racڄAPjIws&uB78kcpa]@RTt9-O~{" !kgXٌM4GcebՊnn2 ]%4֙W@(D*X l{N]v@PYYRHHAH˱M ԍ ԋQڀ.zaz{~^F$odLa$:[ ǮaUU/RV/@ S-Tw8bt8I9Nm8PKUGg PKjHAandroid/support/v4/media/VolumeProviderCompatApi21$Delegate.classN0+!iC[`1UT:ԪO#ǎl'PTb O&n8ik:Bv.TՆӅPn\&-_ͻN+/nd\4zp_NUi%B4G=͒ dL ÿq6ٌ OFڃx}x G} 22 gc/pXQPK?PKjHPandroid/support/v4/media/MediaBrowserServiceCompat$MediaBrowserServiceImpl.classP=O0}WJBn:1K-[FB PINUbG?+Dn'}~M<!3,=f!1ztMX̞Q(Ɖ2. lŏ a5Jysll{UqaN^25'X/F8i)ۮ!LN2RkE#cGUl^\yI; Hg̃p$PKLPK{jH>android/support/v4/media/TransportMediatorJellybeanMR2$4.classTjA=YY۸֪J"V R L̔M*?|JIU(3k?<ţ2⢄[ep;`= l(=8HGG&ɂV0~ujU3>mx%^3l7f%8`(M,{RwQ(}*B:,ZuI[4чzB~3h\Q_u=3N"ё֡scNՑ2ԃȆ&vp%=x!6<4D5=-Pi(sp’DR=wO2[|> s Tg[+#C%m;%fw[&~D?xg۔m3;sϝ;">̈ASbV#aE1/朂,xDz'2VԖn&^F; Eݪ:Qfa;iqiz{?xek;bpw.wJi exk v*@Ho隩[5eWrħyw2w^e,]}11,2JU,Mu9Y̑YuN1V;H"iХpUe* 'szUzCh)f/H "AO6לәct)"h'ȗ1_o ։A @)f 7 g (}DT3F/>M Хm]ܣ(I$:Q?PZSҙ_M@-C~W SFƷN#QZfڿpFv"PKzܮ.PKjHNandroid/support/v4/media/MediaBrowserCompat$ConnectionCallback$StubApi21.classUmOP~6QS@ƢSa:^|G1fHbiC?DEI$A&CsK!LsszoL↊. w2" J]QF%hL"]"]AYIB2xj6/qz`NEvMԟs*}'ܜṖF}eJ_6 K r}w"(%ΒӖc3pd#D5LBfr=5=y.EYҎ y_Bg5hn5q½6(lsؿa̰p؂hD9l0K7LǐٝYa٦Rt~1z)bĊb+^^3>ƒpϓ1<]>_# d"")Y!k &K1Hv("0zٙ;%-6X K}=XjPR[Rn,~ԯ4p NGRwqYbiDgXBTٰ“q"9 'yeL_7+PKiPKjH:android/support/v4/media/MediaMetadataCompat$Builder.classsUǿwvӰB1i B!!ѶiARE$km6 =Q'G}M;0w4{s9wsp8 o8#稘9&dus2qށ3䀂DŽvLqQLR3e`Za;"2‚*)191֣qacQТn#-zNhfdhpl^#J{[Na՜a!0"<&~m֌"i-UMeDU穔J/MɤaZAwBj=.qQnqHr'\uIzzSH:P,'֤zb|\1OيwAjZ#w5̩E@+,14x\R(CK⑹q̟A3._Y3Sƪ]^6stFPKjPKjHAandroid/support/v4/media/MediaBrowserCompat$MediaItem$Flags.classOK1ߴkkIe/9TO=iPP*[nCI&nWC"AA407䗷W=h%4'b5FL=*$|!pD=\Oy鵳9#\ȕ!"->_C4 OK;˜HSyI *v?WIwh+YI#\UtPBŞp##a/$*\U Pz@,Ž 6>PK'}PK{jH>android/support/v4/media/TransportMediatorJellybeanMR2$5.classSmOA~=R mk/.j$4Hwݔ.ے`12C$ۙyvf2qG ʸ3%7=P2?dxẟImx/yr9U׉֤BXpZ['RKa1)Hs`H-ޏHyHS똄J'E8wZ4R<iOC1h[a{<3hB04o `hd^Dt5}w\@9zUp/@ q3Tkzn{" NQXmS"% *)O& 9'1^1R 9;"0?834U%=ZuҰM1:NmsMWL}m*;O,jp<1Svw PdH/.ӿHcѪnǟG)ʿPKP(PKjH+android/support/v4/media/RatingCompat.classX{|S6MnoiR-VIC!ҍ--̀¦-4m:{8=cE:tO|Lөs:νInKd{ϾV*|KpRpXyp?/x:f*omG\O ֭EB|ǃzpTp̃:fqV/&qB#xx9:C?dq0#^~F>Г~9C` )O*1S}IC@RF3gFVn{ M =S#ᘙIF:өmzrNWF }]"RN& Mjƙ&f۴x[{e콕x{4uU*PWn+KtF/oadf7Y8M)Rmo`@4)Jg=UkF2n3 7驡L:1Ng[̠F}Ixvv|l,1£PB[uGtr[6JSmޔHF&βٝܦgD+X\FJ| iV(DU8xf}LjR%jfiL2<!=iV [[DL"L">BB})cl EmCHKɞ"CϘs{vYMeE E[q-Ԗ:gf¼\K>6e'(rpeÜf7.Ki"PsjۓP3<%K7՛)7ôw2ziq弬(0bX;"AXzН8/cS5|Wr(:4p5̶O$ OU5Ye^oxy4\F R ^*^;.paI>`wN$5'C k 3=i*?>>nY SC2,гFN(2i'<5| Y_ѡonߙ p;C{4p=np'CV d5ı93Ǜ̱9d;-渃9oӀ-4lj(PW&XR*<}yin }lH X<&8r`Vd)<\25K.(WP^t'Ӻkӗ74!E<(SD9KuEc^2oiΖĊqyw3}gi7G1E6fAf=z =/Eq9>J:~юog]T>\-Vi*\I_㋅smNaĖX QM!,) F) D>EbuX/&bW K 7&q+nqQLw;jWF>RɓL 54Ӏ+hB֑lur2%|yc o; VEQ>!YPJ%YG.RB<4C.ŕ?/PK>^$ PKjHIandroid/support/v4/media/MediaBrowserCompatApi21$ConnectionCallback.classN1$܅K%4 "Q H۲»Q<bP@ f׷ح |1x]|FCZ3IhKI*"u^.iM5vy21\#z]ȹopr,)G9,W:# K~,p @9!VEٯ?& '0FPKCnPK{jH>android/support/v4/media/TransportMediatorJellybeanMR2$3.classT[OAEjQ\Զ@"RVk/fn3L?xf)(&9s\3Ϗf<PoG" L* !0Q;fdRXCW5iʎm5wVOQ6VtJU^n=y)L$ߜISE2%t m`. aNI8E^2I:7c}C[p2&w]A&1R)!0!uVn[<-#}/e: FuCi +( ù5[͇ᮗE3l8C0z0]s< 0]G/ 蕴$%NJM5J^xUNPхy*=y'IȾy X}!)*Le0 ] C)`IcpXC3cNC05%&6ѐWSSL٥a"vnL M}ChGbKxk_ V16V:$ y BDP4~1*w VB~E @_ #n_PK HyPKjHKandroid/support/v4/media/MediaBrowserCompat$SubscriptionCallbackApi21.classVrD=+;-+MH' Ц P^Ci1$K3k[Ȓ#п3$e`:] ʴnx|w{9g]`Y( a޺a[bx[υ枊/TTºv`6:oٶ޶} GSc8+[U4=j~n^`-iqka%t"-GnA#7#IJT]4 ];#a`FLSTPiy!"`bgȟ^vg*+fW>bzYTu (_ʷ)< j-Z>x Loq܀GPxƮ솶MW`S(ϕΎ £*L㍆>;b:P~!\D*8ƄI0Y̏7/bt\P .jwYGHɆgL~5*wXb0WFc>zs(E:'dGV'Z}WNS> J3Hfw%C={.%(cH+ydq\R&pM{47Ձudo"/AZᯕ~CzfR`-b(t!k1[(Hd yH}=)Ȍ<9jz^ F$B~@#a^ KДeq5b HuNYjS]j楥[MjTVj:_;"lHEc()*^)S64CJLCv$܉JD AeDrtR4Մ`4b%q!#43Dϣ,NS ?PKsH PKjHSandroid/support/v4/media/MediaBrowserServiceCompatApi21$ServiceCallbacksApi21.classWiw~%{,i;SHBpM&nmDe挥!d4Ό @Jw~+i49=|?+Y-.{ϻܙ}*nhx$<.<&W 9j8E;fb00C9bEp+cp^ \{vE8Q(Iߔj< Aݘiǂ/ᒆW7Ep;JszqL/ioRlrmKF46Ow򎢱G-`z]5kHUy$:Izrg96Rgl”UUh׷\Ϡۚa/R@Q_但n°&4uҮQH[v3wߚˁe'dz)m%pW[^uSL#p%SMܚ PH6 t6Cޭ,տ]+ri9U:61 Qo3S "yzkǕMّ1J 4|I39ce\/ 6@+ n՚pXc,R=+puP1Rӝ:@q78"oI%VFDw]E𢎗}::~h:^+:~~7=v14.p1-dj#CU" CNfR#?O:hxC3S2_jdѮjǿ,޸.Sf Zc7[Y\l UK͛(mlO>娇+o ƌ8VI8E%G'3+6w7!Uf֕!3y2'e±\AߗPz.Cix݁Ɂ֒ >\T)({d3qsEh+Z6C UGG ҕ%}|PN:ˑ ,kܗ9~$E1"iO]W-sA4z~hl)9C$JprзɓnL7 JGuH.#B_u]/j;Ur"OȶՁPqOy!VMmjL;iAU[fhYg%zΦI3/+q4-N=թz(7ez S1!HLrfXo;y׋!URB2qM.DMYe>&b8` 1XLEϓbؽ;7)7jl}xRKE?4緽 eQ 9QTSXxG 0 qc `ϴQ픣N<@cl>jN\|a4O>GӈcfR J 4;dN$Dy:9 ?" iSB3 [i/)bO3_N# D-5)*A>iR; CMϥ\N q!]TP9"IH|#Ɉ:Q3B@u3vQmdo08ni̤LSU_PK k PKjHOandroid/support/v4/media/MediaBrowserServiceCompat$ServiceCallbacksCompat.classVsU6)>ViBx*I ɚ.lv/|ſ:c[Gg35_tMM@*f{=s`>bgC9>z,x\!t\OrTOsY.,hG)5g%Cck!\ =tW6%\`IBJ" dZ)iIնUZ)3c;64ZjZNrvo4陶˶jUkV+RUB34dt5ڇ,1K*C_N3ԩZeZ(:̈́sfQ*"O\Ajet\މU:6a,6(vZ3JED湾idLHe:c^T b4wAUbyҌrso{[mft2MT7)+mJ0lR0tJQ:dΖp1E/3523^8pMO4鄆%ۢNj 7ūh V=IJu CvF: CC)%S) #-G|Z>cط*/ /KK%EXV^֜𦿲& a[mzh) DzdfwM-:],-pdL]'j fݼ54]+̀@4E3Vܜn!*Vy{O9G;d0ک>T;C\aa*0~&Cyy㣯I?6c M#-@ѳ/d{hnh b;bB &Hb7E?gdOn!/._=/?K<׃#D;>dOb1ZY@܊ "2:"(@scmd7]n6|VwAzDw}1h AHKg_$)źX=2'DVc!cLyv@s?cB ?"%1|>yn'nk aat4#ƃ8"?Lb4ZHcE@]p\BVoP8+|ޚB 29IZ Id[Ja0!a}H2 I 7`0QXOWƀ@%$y<[#^u39r_C8<݌KãB#b@R(!yPK PKjHBandroid/support/v4/media/MediaDescriptionCompatApi21$Builder.classkS@-%\Dk!rQTXp3:6XMj? 8~G9MS퐦v439}svϞ͏_vX8y"̢0K, 0+¬JHIXgh]&w MCs 7R!k{Z6O#]+4o=Vyݰiv*9AɌfu)jйn n89]n)P"q m1m:f(\29N55G쉑sIw åu1%(umn-Ƴ0$۫ jW{5uٻƇAbe0m軥/:Wh*qԫi:5"tU֊L9gzw<0,d"("üGj׮yZ{o D2YH X* cƫd$HFn0 x3N|fC}G=3M) 9@f)9/N1Lt {D25IA}.U&M#MBdjvȢx[6E+遵 OtNfPv^@4+Z Tq$¿PK}PKjHTandroid/support/v4/media/MediaBrowserServiceCompat$MediaBrowserServiceImplBase.classUnA KzC]UU1`e.]G-jb[C.k/LĆ0g\36x#HPpMu7 Sa˸[2\E KƺhY6CͪmUji,Cyk CVٴlGdiֻ(X&w\U45u&C`Ux0FQK^'Zt^}fн0F-RR)BSb%[f!gՍ"dyì2[#LG+z$<^-2olm]<1\ [DpVA @mwpչ` Ҕx$d⁄5HAy8C Es=l |0L,*uobqf0˟Xݝ |5uӔz.@prcX&5,U؆Y˞PG-vڼN8_6 njK\*9ÁHĝ zd"F e< @N0NC5D:6*^f҃Y!'!* 89icpC G}1#ڇv\1/6!uZ"g&H|ŻU#:CL~h& ѻPKҵdPKjHDandroid/support/v4/media/MediaBrowserServiceCompat$BrowserRoot.classT[OA R[*^W@]wX iRڱng-;HL4GO$Ƈ=sΙs8q0ed 8"{`&M5q![^eT!_U.T ɬ`;MyRgf b. -Uiqv6W*np/nj.$0P깲j}$kR:xL,1teݪDY_^/;BV=嶲+X~-} OF^/F&h^`*=i۷$UYYئqTN^y4a!/0TZ;uoZ)7ٶƵ]tU8؎fd]OM OtB\5D&ܦW3R_ߩ#jܙjͺP"PByEXzZRYkUŽea)'c--zz[ %W3:O n }ۘ_~(*i-L0ɨJNV۵n pb0 uX*?iz?btc=V$'7ɻI$) bdC`|Ƚ]oӓkNO!PKjH3android/support/v4/media/MediaBrowserCompat$1.class1 @Eht5Lgka'@Q O&KLHvnfandroid/support/v4/media/TransportMediatorJellybeanMR2$6.classTmOA~=R+"Rj)3#6شB2GgV?!wٽgw_ m9V<=zϐ2??~Ľ$BŲd􆂛*ƐyVP`orc[|0]7;0l1ăwYc(VMvۭFh4y6Bn{{{!`mLC> @v!Vvaj;EY+k=ӳ<4a0+VqXg*ýr}N/9-}yu7w]gv RXp'0*RmeoTD4DmfAu۞şآYkĔ 4cC  !*_TId h]Wr/ó%a)?My’-fIp hC$۷p>:-ٺan<t!Bu4J^1J/ yiTF.S 3$59Vq so:D"&}\rפ!](Lj2G(DCN!c!D~rK |=ԾHB1EGpM/a2LbAU'mT_PK ȞUAPKjH2android/support/v4/media/TransportMediator$2.classT]OA=mY.RPZDԪ|,"~b 1J۴aa;nKx5_| ~?xg[+j̝3s=sN},&a₅~\ť$u9 nĴC_9"U_U'hSU;>Jz*n 08^'7˪*y#d*ྫm02.H)e dyTػv%C&W7]JSpq)#Z"Y!!_<B^)z[(RQU_Ռ'uYuu,WDE5tlkM5jMN)]O,pKUM\a 6łf⺍H۸4|/ydHX-*[ ]ϐ]W02隱 J"Wйj?))3U?S~Ma;ÐQ0TUBanG~`z j6=J +?, {ϑހs!PQWp %*4GJLFؑlY!jxQE3:mD5>Lt.+*mɢVʍnvˏ_,I`G}Ok*g&{t=D:_h0 h9Yi)mģQ1$0 ?1tctU5060yz4NmNN$.vڕ8FV1dp*N;,&cPc8MHm*ЪrU8ׄw(;_^еmdVV#ΠWqli}pPY9Ϡ_EZ\VZ.ĜGpy9݁`;c=bOlĶ?a+MluTȦz /kL)p-W[FJ[ m5ƕmDj&aL#hwRǭ:><36y$Z<} 3߲h_:Ҫi<Yu|@j;0t}9h/؋la\+ceB1mW<܎~tNkIZ*ѲZӭRܼΉ8ENZO3 *mTiJUڢ%*]W6UPɧVtJ~CN*uԭRB; :I!  hXLOGI=.#IEc{vͰ-r7)a6s_du8J%XFBY{=;=YUے0+7'.PS#=*V/_lL׶|kgZk6`/]P. }zB;m ;Bo娅IaaC68}I}i>kۄ ¢Hl(&FGT5cp7 DF|9B=Hj,g?M⏥=zb[m3,tgOI<+>fϴ*#mca&>3K5([4>+ĸQR~( }>эxN ^ ~G&>fnѭt$} kee)'S8"F3lAt'' Ed3Z1vfE'03b\x$qmurt\lfsE䌺Jrrbf50xg:j>//d-97*LdՕNAwH_Z)nLKwuelMkteW\"={]gިNʼ+{=xfsj? H97nWyCF@Q&j.>eHbimCG%DR2Nf&'HuȞؗXy8;*1A)OH(O%et1 Oj8Y3.bƠ}0ڏJư<7K<ruJZCWz[VhPUC\e?l':rEXe?,^ ,hVVG\̂Xp!2`ȿtKNbll bJpÕh6}O`a`Bws4fҽϢ2t{XNg=0 @_Hѣ㸗}t8G4N7ps^o2}3-}YMϒKͤi.`]XüiR[nX/pM]ʽp,3^ϳ#)떛mͤrf}vYWnNKp?yK*.c"q-J{_bTKJZ߄P"K#^6V*mƲtIafpt49[)b~"A9̸Dl ʤ{E"f_T+o OߣmW&VSOГ5IIāJK,z,JSXiª&4=K5{N`}X*B bCEˢ<K)Z,V[$+ſ/׆=엿aƷ&[I.'5f aO)l`џej ZTXbe|B,,Be1Z-} 1dirUar0M@n{ֻ]v' Jyj*Bx+S\>ꫴ|㨳EBKpBWv֝ܺ4v^~k `Osa/_{HG\7MNmN'1}]8ivgd#*rS-)@*%r4RCn -3eo0􏸚9\Ul|KwC_ Nc4PKP!\)PKjH2android/support/v4/media/TransportMediator$1.classN@%!&%@BcZI J9B 9NՋJ4zCÚ̷3Ï 1?"˘ƌP1'y,HX$abxa:YcZ, %5uCS* ѾJvke j17a뜡7mXc)Α1Iҟy9ׅ^ NԊEN6 ϓ|_2 7.B%a-\ ݣ6Kal 5#'7lavѲvp ۢu"]Z&>tͭv}|h,O"C[Ξie2&YZٺW t+ko*aM:6{ 0 ۘh' AjRϺ '$4`=ܨFJS_C $CW:qwjE7&&6(ҎVOR@q++x*~iB:6PM'&F:R;G*7)D嚨)ƪqJ@L:"@X%ԍ8q>SzNWYqj a"yj/{d~ԞtPKaPKjHIandroid/support/v4/media/MediaBrowserCompat$MediaBrowserImplApi21$2.classTn@=8q I R.-%4)u[@@AH%*R%E}_۫ٵ| >B̦ ^Ԓggfgϙ~JXb2Vaa= }5|{7")H:OS2l\ 'X%DrgO׉RR'a2uB3ˤ='Hc>kA%sZG Wqb)E9 KP) EDm \\ mt ˨B{:V~/PK,. UPKjHCandroid/support/v4/media/MediaBrowserCompatApi23$ItemCallback.classPN1}EdQ'bx#hBh$> )v;?2v9`Bf:o'>NNʦLJ{ScJR@9vgJJ΃vat_ t&^/jTPnd- /]mYa@5"?h.UWnYNaU5HpVfH!V[ɘ-7F!UbA @{1$f&ps ~FPKZPKjH>android/support/v4/media/MediaBrowserCompat$ItemReceiver.classU[OA..-^PJ^oJvw 1>HM|0bLKipd[;9!qC$gRfe28n)ֵ̀az5+F0 4z}IZ/|{^PuJL3ױk1V l }l#t,RJHU(7fzxPXRe K>1]䛲2*SUL9STNgDG:0vک֑YsxJ9%`cYϰ#EJ`ֱ<ý^/PW*e`'p0{MoHq-9+9 u×fE?6;w  0;_-;$ɜpYuch7Jk;uh&b'z 7LRg-[ *:@x+X|l-Zw%_ѹmhRǫ~ 0i]L-e 2e'z% _jA+=-w妶Gt*?#܈0D-uU| +e(H)0 ǒAD."!\"gH@ ;N!0!zDԪ' 8M1PK_(PKjHIandroid/support/v4/media/MediaBrowserServiceCompat$ConnectionRecord.classUmOA~=z) * *Q(Q4hB MY6{w!??8<<;޷X3hxb<9͂EK ^a5c-*CA+o0Q/9' '.[`xAEI+R6sKM&̛8тo9Z!hz"&CVfnuզĆ2onKw'J͵4eLկ+oh` T bY|\0Ca9T+ix"nv7pH+ #}mUU\su.K3OC8f3p):S s h!yv"P;^')RɔSAz3I:sȔ-A!. ɚߡѵFGw !3N@ YfMC38SVzH1+`ֆXswp ]|DCL[PKvqdNPK{jH>android/support/v4/media/TransportMediatorJellybeanMR2$1.classSn@=qbLB(r)BpB!(!Dyj$ⳐH<|b6-T;gg̎#)c|qE\qfU<(fCQqq+ܥXɰgNqnfJIyp9*gyI [&&v(a6UΟ'\kn%2M'sj?T*FP:6Y&C ' {3zȱ^AbR; Mf|ԝ_Ƽ)PwTHvdn NJ&aMulOmf%X>.1@ɑ΀ò(׹|Dm^㗑7>in'"`*\Emư!"ߟ2(p;p#dp<<.\8ã<WaX PKJ PKjHQandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceCallbackImpl.classQN0RJ)奞B‡rA5R nb9IƁNxUBPvݙ3>l;!hp!RS":WfՄן R#K8&s:`6JVm=\ ABYuDSäp6ǒ׊>ڟBp5&SױQ6K ncokZl Z PKIPKjHfandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase$MediaServiceConnection$2.classV[oG&^i- N'  v1^}D@"A~/#N[X3gΞo/_?`zI%RQ0ڃ(pxH+Qb",&6/QƯ:~1C wWϵ+i˕A:Vk$mL`:fvEs{^Ix+%՚3}e^/|Gv4CǜKV`滴~Vs 2*tk /d:=cK;e=d_0yA˹(VW»Y ŝܳU?4js0;=%"^$P@Jny--um8-©Sr\ߖKE,c5 bNcI۷ނ{ N88`y `"J:.B4/ Rz|4[Mӥ"ڙ6aٺF߰\7s>mL&PjZ _ kav7g}$79jM`iYn&S!~ -x;ISPO[޴&. PKhk! PKjH8android/support/v4/media/MediaBrowserServiceCompat.class[ xTuϼ7F B+!0` !K2lG~yHcY0xcb'^zi'NB"vntIۤIi&vMνH )7.ssνO|W^{q~DM*R5rP"JNo ORf.Ҩ̇r5|K!xhh>TQ@KTh˨JHXj/-(C--]|t)h9(A.Q-ԨNzVZfj(FjfVh55bl6\z6pF|Bf̕^"^ZRh ]>tSP]uk[BT▴K$2=NifP$dv)ݞֈQ1UG"42]E3F}`,th?0^&xt6 e"xJ„2{#)¢f8bM\x׫ USjML1@ku[N@Pڷʻo4Q#>ܟIE tƈgѬF{ũ~Q8^VxX֯EX0 mͲD,3&˦3,ioڕDM L+@dSqfN!'hH;m>$!F/NDBDqEϜZ. $Nw|p[?ŽO:@1n;fEQ3TV<*ˋ-)ɟv\p+N㾊UNYj 8]CFON's]hV:~%,= 'H#}1h1[^?Md\ Q?OعZ.񱐰#8_raZל9;VlЍlrZjEr^j2!|L}x\~)f005wL4yב]MoKw\,$Qg'yxYʙ1e=ouy{67狦u/a)TEbY/ յ UݸՃW/6՟m1TUrx†y%3T,T \x#(Ij0}c6 IuKI, +$b-9#9,k =T}r+gB0j8Mb׳-\Am=^' m5bh_>-l[G䢼,Ql~e~w&j>RIl[;U\nfT+ar?xg=Ȼ!)GJvvuaY$kzV\pM D+=M#錄iAv0,."ϿǶqg:"+i=ngKiPo{R~P({'q (e=Nъ+sY">3Xê?m]9$'w!9'*[1Lڿ5Lɽ,$!&[: /-SoaJav/Oɲdq>,ߖ|g*qL<\8`L8l{Voqy #\FM\FW &ev]g}UB(9PKB%H4PKjH=android/support/v4/media/MediaBrowserServiceCompatApi23.classT]o0=W.cc1lv6 !lSOM&㖿/L ̏B\TUrs9vYA U r׆n2틾U>#H籎T+0 x-b q:|AF1,ݎV2Ԙ@#I ߦ ;ev h($>tJ@~ٙ3g~?sΏ_bqLYN 7qƭ$Ō;r *V;e+(Ԑs ɢ ]q>H,ϵ}9ZaĽVFC^pa pUo@fyғCtf!VPU[xڬW~+5 kk\pcIg[(ݤV^A  C_T7j8,@dexr~pk`iv/p.()fa 倻oWy#JN+2L]mno$zǠsaǒFC:3s˝p7YYOb,gxyAʩqjpU6Kx|^0|rN"Xr|K> Ed 2aEGvgm}_; p9q\2.J|E>Wjcf>~FlsҦSbfkFutw6nFl6l|3 PK[6z^PKjHHandroid/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21.classVmsU~6/4R64B$RP^Z65@JTmrinvfSUg:>wwIDf={=/yιw߿28R(=bXCŝpU=*˭UW@ŖT -׊e:t2ZN'X1 `&0c`sYc BN^ܷy[Cp!WTST|$IkMa’b0L@qOZ%2(Ҿɧ,qhQ4tl1ˣm?d>y"aS>29^m&Ǒg.>!-Ǥ)|;4!H PK7f[ PKjHDandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImpl.classXksU~Nvh %XZ@4iI ҖB` "^6c]Rx'gt/ÌGgQjc3=yK_g*^d+S*bZ .)xC[0'ˢ҂yQb+ T-wT$TtBSPTBGIWхUt ECw */XwMhx}C I*Qzخ]e+dh)Qmnܮ8?BOa8j!:n8CaRehHb5s^s їQ!ll}|A&o辂 O,5,jMR7%-kjV9;细U)hm/;X%4:bs,V%V`humO wQC&8ms>4GFA-tCwAV*EOw 7lR' g"ъ.KeWF6N^`\wCB%Y W3| ;\^6<* @66זD1p1yC|_{pu}x98 ZJlk(&/k[*'u.÷ $ܾyP+,[o*/8:U; AKX}moXQAwqy#q )ycBҧg8kLF+?F΋#}2塦rCG6讠9haoYI[COٟ[|:ЙhHίuXu\D^' Ʈ4ME̙^pɉTgoArUӾ؞?b^2# χk)O%7d~2W|ämF1a^Eo Rn n^zFgG?Z7z^ztgDҫz29 H 4dpX"H[ΧB$8[# %Ѳ֞6jdwUlM?h_HwE3=X%O JTg) \`WB-*"ZY Ƒp=yzHG8!q0|J%;kb *vJ!UG†]NV $sj:C zk$F$rd s!=XŮ:؋ŖCtuV ;SrqI%Z3%%:|^Y!ܻ//<2;k]ES'_}E1PBo&aF}8I"&s; ^OKnuD>G6BSLQUp򋅭< a^] gN,5ft%뤬;euh`Lń_m$PK?xG3PK{jH<android/support/v4/media/TransportMediatorJellybeanMR2.classWx[-c;8"$N 8&P>KG`:eJ7ДU(@F%e@{]% =w{=X\؄C>Bp 5nr65]O >R[5BSjp4i|N]. w}@R9"i߅yx@J rda=£x̅Ei.6_<%8OW7RQ/Ki*_s(Kre2|KoKY,|OK`H`~'~}(2#3c p(nWhl|ѰP^Z0-zo1BƨUM]%{}p5g 32$y;F( GF 0bx) dXccY!2[dGӻx6kjo{q?`Fǣhz .ztMy"unт %XWwffTZCM6ʎi(_|f`ޱX =nfWNf 2x<J[QIFY >-=EN PuD`Ʒ _"# K9á2, pܔ[ ~;`ϱs),O8 f̹F$ҟh:+%X.1} j˲8EP&d!d %PS$TH (yXpČ0FdaD2'=~fMf 01b-KnXǸ<⓳d+SkᑽWܡ&ݢc"5! ݷň*6č}f]37V3R8D۽NMCE;My ţ"!sr},dF 4p$W@8f}[fZ#Xk2SyeH,|\FGV_8 o:ފ/ᅯ:L|R~*߿LGq-8M0x؎ 5cOrot{q"a+4QǟgY `xŏ/؅:)W ؉3u/kxEǫg2])Ω*ΩjTJq.xջ_3R,U:Uua\St U+ I5j)MZ5",5[WmdV܆2ס:u5GbYenSh,L9*)nW\(,NP8u k^]"vVWoVAN>|ܓ'o@)l-]ZXrYeFY~"P6L 5>5tJ \7γAё"nh玱-WnwА;څmNЦҙM1T3Bp(W $ 5ռzik7ʽ!\ af4J ^qe%gQse!fQQeeQVe5lQV?V֘UEY-Z,,jˢ,,BҢx}bp/yCy@.vp_#}gүK~ 4$fxxLOo-h%0H`6vF{oIt6a9$Ibnds/7?\ 9-$JCH7;Ғ8n*Vw݆l *[N08،:K6澑ƾ{qpfFOHB/tzEįz_KS@?P澻2;ɈlE.7$u{XgI?32 hNG>N;'?g-\+F I"F8PaʸWW_~uZ:P U-.^IRrGH+=$N껺P]Z7&f cfKS[ugzi'b>HʡPűz'e‘fyU3mFOm1ckZJhi;$]{Z8yuq(xaC9J@E u%TSֆ:ӆj <@ LbpROc6AiGMr Q쩈ٻ6{D~x19 {Hfc6(Ig$6 7z 6}I?Eig0?Gsp Z|ף ۴֦56雩:& =/`t/bi4PKJ PKjH:android/support/v4/media/MediaBrowserServiceCompat$1.classVSU%yl%h#ZZTڂQ >mXXvj~/δOq|5:Mx|wfs~Ο_}_/!moN9%fYib]fr#̚ kIf$M3jU-7blf6vmb'dik Fk꯴Nj /bu,VKኊ/"Θ0)./;+|=Zt܄{=*j`ACW4,⚆+n2,D߱5h.EҰ5 ,iki KSs7_`9k;#HG7 y1ʵ]afqiD 6 B-s]R͈ \&lS2u\:wY%3<V)ߚqZwcME ~S&4]2QJ ȴMvo4"Fv y ~X:7h{N*s$ EmAD^c _:F>@Ov-Ab Xvhzub*" Zp)(C%%/Lnd>)`\Sc2 6.ΰ_#֍؟1ëj؃8z2{ts,Fo},1dc#{xz Z:!;w$8M=T?A+4HvPK3qɂ PKjHdandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase$MediaServiceConnection.classWmsU~nͦm-"$-*(B(bQm[ݸ)(*7g8*3"83nb:쇻{ss N|`#s1BO਌fc "8$F1 WT+^WOsk\2(X\Yk\ AE]9LɘaȘQXLڐm]pt{Lg>\,4GPMSqtG3\2 v O`8y2)\*YHcW*4> ta~Laq)m)a#ngib+5z84]?o2Vy],M7>,V-'5)7,{)VƬ<&kk=m^*W$TG*.c(K=T6 U|O,;3|Κ+ w:^ᶏv7}*2Rq_ YٜDc8>9CZMKn28 jۖ0[pGMITkv5:rfP_HfZUoZ;yHk$mg+ <ᗅ Qx݌eXL$WU5JBc!OP{/yQe?92yP+tޝFlլάDFu1ޗjĵ|eI xe5 4R[N(ˈ qCWPh/iTZJOM4Cj!P&H%Ye b$5ѿ'zz#<yFaڥ+T7XC߁> Fz!ٜ'J*fzc[Cʁ@*>DtG!y=G|ΊBǂaF7lZT vN6 ח s\"p_/pele1M wIV|+X^x\PK1^PKjHHandroid/support/v4/media/MediaBrowserCompat$ItemCallback$StubApi23.classVmSF~N 8И446NpR i։y 0|?7c$3Ih'I?t9?*]ۚɗgݽ]?01a&)ڛO a—yXC _ wnPˑjUe{^ J C ,U_[wё"xi+MW,$}8QPdfa8Ht] U\_m5U˪G;]ށ \^6@r/jVGڅg(䐭}>hYWuL-:;2)/rIl-0rd"/bO$ z@Jr(I4fdr#u.mPe ] "є :}hl;[jz޲Uxn?L0#xҵ _h>Y*YX ߠlᱍ 6-l ,\(6Q1ꇿXt'CU,m!4HB `mN0  I|~1^*63q2u C8>#uF3.'KuƯ1pL D6ɖ aHorN![ j@LE,ݘT|egiN>D'G3WH3ߚɿPK U9PKjHPandroid/support/v4/media/MediaBrowserCompatApi21$SubscriptionCallbackProxy.classV[sSUvR(*hpR pi( $vcrN8$3 }A7|vF>kFn86 df_^{o}k? %Ѓ.l$pL N@ taRJ4Ψl3(*s`6^Wk.x]`KmQ˞u&jd1@O387Y|-⹲lͮH+;#{ͧ[[D]ߗjgwTkF9VlhHI|L:28^#/IBұ ZVJɦ['վ%a3wsȯ 8mQwC{"Q#4TՊg;yתie3 <̍.JfxvU`s.b2k̴ƘPq~I#_E*TL4Mgw rxrEj'pxت`*:w?F޺:4pIxW I'uf眦%UI㸁՞ZQ^Vx-NQU4\g\m \Xqdd%16H1ΎGxͨLe[Lx P6\&l-0ںiiۙ 2s驣˹[yH><ʾ+`d zs τ7 Mob 4E׮gZ뗾>=$i}79sv*6P͞*]˪ zl\,'ZX4'߮l\.cs6쵴<_d!V8c @G;1Go^s-t]z5@ [mrרU!{WQL?n[=5۸`;\\?EIssb.|]Os\x1h؅gJi뻗=m=኏6WLq6(oH - JŶb;o26:4 w(P9)0-ZHOX?G`p_|#8?P1tarI|ÇD1? ?|Y|KsK4'~vB_[RIԜxfnf2Z/EJ=^FEjG1ƹ+B/BuVϵ2/`SRG_s&Cp$[ GC:P mt._Ef% ¼Ȅa8XmT>(x*%;C3J),f2NQ)O Dؤ3;[5 Usag%@n6UA> PKNq=% PKjH?android/support/v4/media/IMediaBrowserServiceAdapterApi21.classQAK0}ٺuԃaE&9U6hFז4˓T=^^'Kyhcb.*bY&,e)Kʔ0ON$d**ID)1 EZt̋"q D*eU6+(nuZJJ]XNQ0t,u¦~PK۬%PKjHIandroid/support/v4/media/MediaBrowserServiceCompat$ServiceCallbacks.classS]o0=wf-c| C'$,1@A<4bR"$xxKv ~? qP*X#%{?~pGnECXMaw0&wVz11cMbQYpp=U6`%^yBȩ4#(n|9X\K%^[#VY:K;Ȱ_R |$t|$nav$ur¥^oݚyIԾ8O^Ab}XNGFҩ֚A gf} 5QϞ83pǯ %sMufTO* xW&Xfj5kX@6!=Z ba$vb8 vJ!u'=r4iIH[^ޛH;y{;[}7_y |Bw} R;-ǿp~Y̏ ߲dWk?@>>߄kʉ$hTx/?"HNx gq+(QItJC44R>Fh4h ]QyƆPE$&H4IΝ,M EtFBTьiFׄH亪 UkTRͦ:ȡ a͗ȞA6ĻN2YTBS} 5P|,-rM򱢄V*QAj7ѨEj^ fgXn%wM;_͛KЛ j鴙&PbPڔL3F"sϚj.vF(kf4F;h0ʹvƢfS;L aj贒κh2:w6yU{5Oadwnbwy:J%LuR:`nvV,Pi~Xne=k*nu%L2(/B=дeXaPBx5,ݔ`]Kt&H$6Ww/7sLwZM$%IuI'un?O/j%b%md`IAs4Wwg.a 3< -/Jhu !Дdl%lwim4:⦴dԈbX1t2[clVyP'Ӑk,Rn0K}̘ٝ+wGM;T} U??:Ԓ(<'vhdaa'8l>9-d9e gTႆ ށް-2K ,|:\[[eV K榦J#(~آ,3jv3ӏ2t6kP2TvLzLKެeJ5Y 4X mtaiUF,tS7%tO1̺uJ8ll9ҎE0z.3F=2z89rj UH@YI2!`9#0oES%RvO*g $Yam!'I6.5O|/`lF$-Iik,i/'uz@ M4D<l :#X>Is<3ٲ#hLp_sZH4Mj"ل nL2b[͈iǔ e(7O{x6'VS۫#(0Y;qEDE8wuF8=@Orr)={~[fOSzL$E:hNs2t:LA^sOE:E@N82"Cʫlyit^#, b_XC)w*\jKh-OW3ϭ'\lpk:!~u7#QҊ9p:?Upg~e̅YJHPJ!B W亰9prϚ]nA٬ z :14jFt;~w]W@]WJ9}:߯o3}W\sQދKt%,b%^wQC9$:8=]Sȥ?SY~.e_.c*i](T1wќ9ۇ/}廇/~8r@~0ȋɼj,ZJvQ&iwqzQjk˔Q$X:wF~TX)Z,R鄉^bM[ ;f"j*7,۸>40J?i@|ľ*~o@*)38x uQݵcކzIRr9nxrve2ͿĦdq,*/ RTϧ_W`oΝ\r)O!m2Ž$hgi ˋzFz;:JlZcN⪁~!W⻵8d~6+=ښ[`Zu/ƽ",big; 5),^Lts5 _b쬖&<$l]]ϩi1VLf7לDcl \F*@p`/ 1Y0SA4b6.BBBJbBb5KB%ƀeuc/>N{ Bj=Ws =#xXx k}As5F{?Ё._ė3ObFU{Y |vk;0jhs b,UОD'js$>pz9 08_ $hb2Fi%*@r1kE%6*b.:E :/`Xun$wA>æP<0- "-.{ o:naJd{] 9|-tpfq^ {]q o;{Ir`ѥϳᴆj}F5mV.SJ~Z"plI4e[^ùnE;8ij8ʖm%vVVM IGDX#!\f &b tхRUbqX"hZDwni- *Z2  5O.xm|! |R gF Tg4)&(p5I1/D!qO&|%U_Br‚,^)ߋSXKqZ pT (\t  l`8؋us}ڒp)p2KNaS"!mr@-C`jEAa/f=PBG;r~1%xC1GEx=Q$Cxx{~ 8couqH!s+?~⛬9\L|E Oip/b [!TpjS|1o Gg\;d+PU ܎p.9HkFh4?]_)gk<fgN>֮̑j MƇpQ8Ћjd-ы%C[lا_G8~e"Npx}_vv F dxk 5E(-Hg*>cx>!2Iu:2HDt 2_DK 9R۲9⩧6t|*!8zԚ&P5 1Ӈ}M1<~;mkgjWϡ.:Nd}O$pFu+#0 \bFPJ@+ro1G2K9gKVxP@9 0. \U1Kݣ!ɽAcVp9x#H'Y )Қ4Ok<PKf97PKjHDandroid/support/v4/media/MediaBrowserCompat$ConnectionCallback.classV[OQݶ,R/U[.VHlXBBL4Ffw EF?J] 44=9}3gfΞF1F"/~$% D1)%b0ԂFFz^,ˬ.l[ SOk!YQ-CZb~ ͭux5 =u hZQ˥i95̻e>Os$D-S9Nh -},9PkyZy]u7 B_+BXL6!іsx0/J3 ۪jб;c`lTcY:;{}S7݅$4" ն#mj}Y &u-jGq %\q wtmȹ&d-J$'DGʡSk޴鲦˷岋 io0DiVӕ~+T/Q G/!mhehS?D,_Da|u$qqⶃg<.$·h;ē?_IW!BI~[60 %dz^zA'ۄ kzAzW%S xA*.p?`Ǻ?>SYj'Wmz m] '#p+\ @ >GW9@£q@"ZaRp_!~ɼ`$c6wI$ PKƢPKjH_android/support/v4/media/MediaBrowserCompat$ConnectionCallback$ConnectionCallbackInternal.classQJ1}cnm<{Pss.zt74Yo~8Q"7yof RSZ]4$tN\ eT+̵ntYwOA-kn.#=һTkRد6N٘9?PShoوM0 s!@8m(T탺/+a?˄?,Ht/?0(z{ fR`$PKXU"PKjH:android/support/v4/media/MediaMetadataCompat$TextKey.classJ@ƿmmV'=Ar`oJP-TOA$!j|J\KA?| N$Gc`a2{SM3v5$ptOxj5*#h3P1;6mu+k(t 5$&dTUuQҩfT|kpeTUy[rʼn8߉R?@{uc=} oh0 PK`&5PKjHIandroid/support/v4/media/MediaBrowserCompat$MediaBrowserImplApi21$3.classTn@=\LCCi)r :m]ZPAJH)(H{8hOx(L)җ٣33g* l)۰Ԇg[XXeT[N#UQkejL4L^vcz^Gپ5q+$k"BDIS6Aq7- ,M26:hn+3I8қP%.˴ GZ(f^Gي2:&șe Px-mR'czlsջC-Olpk]QP*=pP \up &2(;h(?%Ʈ=Y" 47fޅs*eVtg n1 nFTh S*qZÕ:J9zDۥs=nu 0q!c^&'4rp6'VF\;;=%Sd~"79r3/<Έ'Cu<;pQbjcPK+PKjHOandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase$3.classTmkA~srX[5j UJ0`7wKz.7FA~G3!@Dh><3̰8#6e6;pSkAFUe/)O|ZXJvR&B?v: HlL9%|Ȭ;uqƆ2(Pv'ޓ](Pqב]/N/Ԥ.k$V2e3VcXCguYUT&,d+H&Po"3W4y>tfl<x9m(c28L#}`Lb͈q^2R J]z~_nn9x>ǜч}GL6OhX?q.V|CBo4p8ukX;@ޣ@]nӍ\|<_PKbOPKjH3android/support/v4/media/MediaBrowserProtocol.classN@ϔͥPt@ )-C6QF&!оV*Їza,nH̙_%om#0l ݲ]{$0jAD%/KA#,Zl ޣ/Z&9i3SuyG=96jk@uv}= >ɕ]+zjK=F] D1N`~]YЭ1r LtBU\ +ef2 :ºp bs]j^<d^xԏ%Γ&"m73"< nrGQ,Ӱ.8d%tvVсH`n :'M~D)T 9RTjd^!Ei#ં1:pioY##+Q&$ 퉡?OPK$VPKjH7android/support/v4/media/MediaMetadataCompatApi21.class]S1ߔҥe/K E`2܇6`[[z(Ǔ`PhgsN6o_搊@ǬW OTXPaQ0͈cYÊUm,Ya3[)dJZt C0ieCkJup(}~nKt$1Rږrhَq2oDVrcG,wx*V3qN|J a(:'s3gy]eG PBS3!9;eJ[]H;4s䄳./2PjgןyHfJ Zr+hO(Q2s Kf-^87R7$B|#7ΈNуg:u !cubC&^j1)Zp:n>?5dɱ+9^e 2+\=~ZGKmE{j"kxE{;A4>M#N{-C8+5褨nbXmj]gs 0[4F _!&%&jӏBVG +)ՠA* X/A5~B A *Z5GjD(%P)4ЗvEzI?=9/|S)WFu@$*/ggJjn9-PK`%~xPKjH1android/support/v4/media/MediaBrowserCompat.classXks~%{ey;cHq*$D2 mJZ&Ү]Ӕk 7zoi)Й220N?}ٕ,[˻{={}_؆'Z'5|-._T-$؆xF991R'I7a/+jlFHVȧ.1xgM}l. ܽ^m+PB~L&==><>>iZnmGė.f}њA2di缝{Ǹ;44 '4uW;gh7~| #;8jih VnO9LpGv`@X'x/Ø~rrvi.gS8I)5 t{6/9g4_AR}aRЊǕ6zj#d׉FiGY"ʓ(l[!ʾ((-H~=DPK9#rPKjH6android/support/v4/media/MediaBrowserCompatUtils.classU]sU~NfЖ ńoI$R(P>-8¶Yt7A`[( az:㍿!ݦP(6y9ߟ+a@ ӨP ---}bTGGlC+Ii M؎u3?myOٺbzGZ0g#\Y;[V6R{tfp9[@Y s޺lסP׍W;NeqT&rç6ab0"3OJLTݲgjk{Ӵnӧ \޿p; DۜUɵaߕg]}DΘvN܃C7[fC<Yjb*.ddssUҴ H2w!: EL߷达*W6cI.,Of3l?4yE Q PVvgOC28q]byQF ם?e: 7W0qF02kqdwcD <ۙ9b/¾UU[LCP~İ)GPfr\!~:ʤ:܌^Lh>s5?,-GB{R [X'c-gHյh'eޗVKCl,"G];4@3-j+2v,v7)bT.rH ^z.ğ"c HG-G(ͱ{eM[1ط0r ]%Zj9-5j\2КTAY%B+VJs ie\ s 7 g^V62=̿q$pWCx11[8/9i5>Ph>"֛0&H˳<5m]#\ m z iu\b'9F{@~QΈ+P2#g%N}>؁LGRb2I:LuX bAѦ 2=†EoN+=ERb17ߡ'TjRN%B 9 E˺|.+*c|khEX!vw)Q!NBSTI{I[NQU"A{:YSoa~G .d/PK(hi PKjH0android/support/v4/media/RatingCompatApi21.class[OAå7EMBAQ\D+F tS]0~'cEg?t+0Ml39??~Xěz80,Sa FV<fMua6 E[ U۱5dꐡcRz c8?}~tF; c~v][f )dzj%*픷*UoVBϱ>8.R0̧ g)Nc]q,C]-j b?\wPe&ݺ/A{ C?zoǖu>`ZvEK2E%Dk͠q`=eoJN= Y;AktA]uilrA9[qs">20F0x ^ m`ybf 30Qj9  _/!EK>yisG}gПZS42;Ҥ dCrsZiRkp m_x FB#Ux = ikxB6NL]S$)h] >?JhZMb !5tSIE)8/!I5ZH~sz>mu~LيRe-?=o|VϴU~]3JPKq PKjHFandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImpl$5.classTn@=I IJ)ŔԽ8"hRHIJ]9v߁G!fHHP[ڙٙv[GJ(9EUh2 HU ^HG񔟥blR#롈OC6 @@  \7Pâ-:C5Ce3ĔV7 \5" Dc1dدg {W(jOQM(27qxg6U* 4#Dj<95,-5i'`O rϩmwhn9{P>Xj99о0*GT٧Kp)S3\MUiL]BZGPK%A)HPKjHFandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImpl$6.classVRPN[6)x+Z-AVp@iiI:gOgx|(=m 8jKfrrvgo_b C|hÍV0G7dq$D`T4LpMbh++FHU #O u.몖:2H˦)CqąblKNP\ZNW C?VCG;|`k{gtS/2<7Tl0xRVNchO릶Rg55Hә( saCB!ndDXSˑl2UušZvuq֭j:(elT ͭVܶrncVBIhY psp %CHsp42Tf6'%fwjZ+f6GֶtHt)Nz‘4 c _EŪ88?j$IJEݐm[yQ D.7VJb0>Z<rL%`!F$ү](NS }ۨw+='5 pEnzG=p ޅ_%>'wq51PsdtO0ivޡ}_{\S31n>Z7 "{> }MXc#bWpD(+]x İQt,W+.P|$_#y*PK ָnPKjH7android/support/v4/media/MediaDescriptionCompat$1.classUNA^]@*-U HKH?]amv}DC,mX6ٙ3f7L P01ddU0"Gz" 9 &L)a. Kx+ Lv!\ wSXvz`:BM+{r8ޑp-t &yf6e80t^َU\ߒQmj{et,"~# /lf #tna,J4IHTts|/~?|bt<Ӹ#xoPK<_PKjH6android/support/v4/media/MediaBrowserCompatApi23.classk@ǿdMS毹MԴlW ^^LS+} {%>֒n)M>O?bD kMp&t3p߀ͰJEɃ7q C7$"1PSC eAk z3å A[$YlGx,Hd°GwAũs _rCB^D>On^,x*&S6F}ďGKTx6#Tg$4aqVGtHr{FU˟AB2VF1; K*hBfJE`TgXK6Aa6L#d2<)RmI%a%8 {@=}THP3dBiTp]X5T".\\lChǐbqj^`O['iOnvXJN+BTtVhm.lKK/PKT $PKjH:android/support/v4/media/VolumeProviderCompatApi21$1.classSmoA~J*B[[Q_`㗪I\ڤ K]rw?8{||ٙyݙ7O(+Ɲq7{gp<"# ٗ9=D xy+?wt؝Ku0PϥO=x*ۭE#T2~Щ{RFh\J%& FWy4ъ$ O/ܢSgm5NE|ib{4{XZYa2XjŐ?ճp(^KSRc>tWjHi?&8ȠC7QvPA2wd^pBfV[}]݀n05i}ЗR|  *9J▤Fi\+}FL8wqQowm2U (5Bc! fI2N(1 (B\{ăH>86"E!҇_ċsd41DuuYa':"P$[ U*㍨o6WPYxt`~ P61zs%5vf_v%,1 \g#O3-6Z ƹ4s")tJ º$)oWU&cXv';Hx+DWG+^:_ Hh5n"QC_peP(Q?qPtN%() L`EY|@۔#JGlaPKOPKjH]android/support/v4/media/MediaBrowserServiceCompatApi23$MediaBrowserServiceAdaptorApi23.classTQkA&MIZUjg*B"T*}\ݞ{?)(Q@(q7̯D2nܬ*JX%FX=P;|=Tv{jۗQbvl{al/Xń C_7|u1"vu^eʓ>k滦'Gjϔf }kߎQdl>mcH&Ƅ8vC@{A8h~le`:%9vh^i?  SB'p 4s{ȽMˆ[FImcr2bs5ew xcJSL p(2Kp K8L N1 ųDDFIWYG]Lmk]KUrXm7XPKQ6PKjH:android/support/v4/media/MediaBrowserServiceCompat$2.classVSUld@)JE!|RJ)I)Z Z,ݸA3Gxv`gG>2CLELD튣ۦy(3^]ymC U xMC.ixOU|s|k(@5VJшYC&C%a *J 'bl Kņ 2 wJ ݘ5 0+UU0dl\ffɠ~)AZ=x[.\7~u:΃Suf|7W $͆źLTbXJ CyHa c^-rwA|]! ˟t??2OVߋƵmny̏bnA0BȽO;y!U)L{hOlr $LУ4_ye a6-L+}^(l2}_<Miowrp,9@rxJ'i 5"Y26.@LqȓJ"!7pIheGdn2>'h#U` E7d7Tws2LJo)SN3o}%}#+a)*J fV(8J(UJtЬW /{a K0Rtye+o'Yڪ4Vf}mOT\= ULxd8 MLY)u`.uvPK8` PKjHNandroid/support/v4/media/MediaBrowserCompatApi21$ConnectionCallbackProxy.classT[oA-RK/jZJxS%$/ βk{Ϳd1G,Jf_9͜;sd ]lJY\- 5e)s]CMCaoH׵{C IALo8<a/"ϓ~8nmKem@e}Oܹ6z!.#fHwdaQ-}!-(k?wRnw/zfW8?Iܗ(,{'t2^ױgI7-I:08j[4Fw]KwpIdf ՌN~N+TS^#:.hYT5ܠT5d0 p_YVZ/.ǢGpAYZPDQ||| JLHxA% Nk}aloqW)/QD$tȳX#R_fy^AN5Ɛx3뜒weI^xߜ7=%(_"}|F~8x@n!-E '9qB0B;1'T.rL :?'|En0_9'<0oLR4:(ɣ O2T)Ƙ*+),4EDBcgE ]x\7XK DT)נN(Ɣ=̚R<>:PuHQI el3tܡj&]8VOI-U$ 3#jYS eX<ʰeFs tf3޹ %uZ}!=0wғ *^i0p}sNdCpi&IV/7Hg6W9.+qS5g IAU(=r:e )QCӇ^E֍H9[ O><--X4+Ч' Y٦rWFry&G[1Gg*hBX#b:D)㔀OD|D|eO&أkm(3ds=\E|%D|o|'{( ~bhΑ%m})[(V%3w "T3ޤXHsL$LE2h~ӐnI)%XoĿsQdYI$j6+| tt: ߠ͎m$%gXA&f+ zջ䙼uv쥇JHO#7qyy#Ӳ4!ZIR4=} hɗTS-T_Z aM7R ^TT"ki:vsJ\ IR=!"U%)ϡFk)|ۀM&+qл\v%p^@$.Oe2(vbwSw=E)7dciTa @e,peiijmHӟ9O?Pqi9wMA Y Q0gɹGqƻE\rpըb-hf׊l3@;qq&x_,N\d{pu2aKnVPNAHeg ]&n[I a&|5Jv W@AZIB2v}AA!зK݄:+voyߠ_PK79+20PKjHNandroid/support/v4/media/MediaBrowserServiceCompatApi21$ServiceCallbacks.classJ1z,ުPo\<`PQ,xwCɒdCKQ셁?&o`1 lCj}TjH#m VMOzb=I:Q:yxiN#W=a5ZzWժY|25\;! lb.7|aɨ[f.ӁӶ»"l^T/67=f̝ebo Fyia-j [߈IgKFW~T)9usxW;>8U*B)= ϊ'!!&P{հϢrPK>e^PKjHFandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImpl$4.classVnEv&m&$NKZZrij7B\ Ԟdk!!!@U G&MABC-xgN.8'l/0fґ-K׹My>,S[*%4u&;#la!N0-i:Mۨ^/T!?{.{3LqDqqtJ}7Ԏi!q]!鳛`MғG"У@&Ic%R4WHcfTK L~S1hCFE8}pxDƋD m D[H[d.92=(%b+o0Ǿ'>:85:,hfc#r Exfu}M_`0360S]ʞaz~(]'8 ŸLqK{6j!Һ}YҞGmHlL]»FW*\oT6VkC3k]PA;[ \<i}!S d߽@Q? gtr&Nͽӹt>Vk%_&˶_ZUU|,۝+p/@ֺ8A}~\ 8Rv`t PKqPK jHandroid/support/v4/text/PKjHAandroid/support/v4/text/TextUtilsCompat$TextUtilsCompatImpl.classV[oU/nIzKkر['mZZ[6!7 ۮwuHx@B#/[#H#jIK ,9s̙oYR{0^ /qE#a\0raaI&1t3E(.m$wCތnp'w^Y MmZYuޠ$:fUjrܒj5W7Un\1ijvPG1,ؖ^H;JŲ`ڥazu&2[6y/X|쾺 ,g\[7Fȓ$#9&>g^`n4 g\5`\xy7^蜮^r2MU]2yXvz2'D׳ɔje5޺;.YUnky)w7LKG#-Uf/]fD8y[3yW-΍~tFp%~<vlIC+-@F֠o_ X3Vk#DվN`:vy  7Y䱢#1\#'rn=Zu{ (+3!ehf-y˧)@<`35fm W{+}vصmoν[P''gR3zaU5ny)s-C:$쾽.eC6{x.ìD|^s~zk=\Ѵl-:ںؖ3{kn@ڵ/NP&X!^~-$o2A~"Bavq,)|?c'~J]pˏ )j)Wo>E`:H%;54];<|!H^4itAR*!<hsME#u;VRQ5k>y@<~IANfqcp_^I8v=E8Oq_+dT B'Z}>}Fz$78Stңh~m!xot0t\ &Y@#'@H4g/ygphc 3vg9a2'gh -''+yǃh%a6a=H6:M4>2A?")Y ,| !8*Jհx+c}ZjS}}*S^(B k*QoTYξF+}W]ahx3|(=u3PK PKjHVandroid/support/v4/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicImpl.classU]oG=cowK JI돀q1_3Z2% x'zם/<+yARDߨU,"-{Ι;gwc6 #'d)gM"x,⠉&!۝ځtUk(EqKqp}!C2or%U ^/rVQtme"n\'WW'1w _ k ]`ԃ`m8u煼=k.q`PuuF1\X +0#|Y[bGj + Cn?3 W덆R uJBťb` ) ^߮;\Ή_#;h V'9ŝxo;¹N֙ "Y7mm㘍u3q S&N٘i,yͽَWgo9!o)u1q> 4JjO°^6 ;ky:-,I)ya y\"W?f) tsӓED䓁j,!kF#:hE4Q8cR=]&ꪉJuF#0-cOgW!l1PKHBPKxjH*android/support/v4/text/ICUCompatIcs.classUmSW=6A&%P/ /Yauf"ȯP33?8~޻y;co꘳n2ٞ#R0@`зYv$+ D ^DNR#`nn`9m~*+;ͺa^`Ǔ2DGX4&ThȨ8Q"?(e+X} .pCj T|uX⸄Méќdv>{Y袧 GwY9>R[KwɁ6≀e'ulnKq\ݡdEQ8}O}nS6]b~'G:Q co="&l6WA'e y 9gwxiw8&Zp^yH%Bs̓@嵈Kƌs؏)rg"qXAyZ`&3#¯p4+zCZWGP?ND! [‘L44.GDY:; 5E)O+ `Jat3S댶$;2_ř7J6#989uIOoqwI! zY}ngq<^NxßK7io ¯qzC3>$]M~ ZbDCCPH:p C7M'tMXc V=M{iR99=u}tp EERrePKw`PKjHXandroid/support/v4/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicLocale.classTMo@}q$6hi M HS#DR)DЄ m]p^WoqRqh?@cmW(ݙތg'pӀIS6ptpy̜yll5[Z"e]l¯:<DЪs{ma[!X ^B?ULERaH[ Zkw.];oHM!⾌ɡv%I39U 6#=#ۖ>16_>$+[hzo -d&L,((cI5qC2b k.iߗ:,/kΎKaxT(F=u t3vZ}XR{Mb?!)4a':텧 6|sL6^Ls*eh0KW0yii:Ȟ R%vO^ 9 +䙽(14p.AxBQlC+Sw1)7R=F+24 H,R?gw8#eE(B\DaÈ~m3X \j PKҒ3PKjH+android/support/v4/text/BidiFormatter.classX[X\W7seڒKӴfH &$f$! `[< 'pa̜ެ^UUcVBMwU??|G_}g\kar C^؉xó$cE_/xWY|=hl /xуo[^<{^o /yX>\}~?d"~ģ<1~lOYg9%̹pم7]"%Nki9z4LZMt0g ԨF`?NL cD/:|0 n5=md&kfhSF[DqZ>Ĥjt" L:gic@M5ױH U:a4*pW&rB=X[1ZCE %xhXQEzI Ƃ}ñ`< ZaFq[H"6]nƖUp@C,вX"bbF"^D`H9JctT'zj>e,tnt@v~^V}Q L ZUAcOj-5ޝ%֢-PGR4 &I5Z+fSmZX;3%=9aE{nt"C[ 1)s/] eN'~Z&42ju @=u|wc6"ReaCR,/2z0N <7TKeRA|Y-G xٮ:dwa)d jVKze"fR՗M88VW cPzV5reSg}7epv[@w2zYۼ]Gv;X&fgd,Q*3lR 鲦9l(+x+"EƧmY3蜂EyDX!Ppi J7)L+x 8idWUI_0_q\kQ[NI\ֶU.w;L?B{MoCնc]\[IC[#Lt R| h1c\&UFkEvٸv* s^DT''4Qۺֲ|7{62K1lQ6QәMv7 _CɶJa c}TE{ejYM/l/,˖Fin}/1"^=>?n'â%S3.(ݤЩH^ ;b;BzԢ~Ԣ>Cf4s{c p=+I: K: a ;_@Qf7٢`%m8V ÿO a =5\#EuotWPymp\8Q)\n/^ S!GɑJAf1Wn'[9oBy}9mQi0w_ӹ]9W8~v,0 *fTBU짻۵m7QΣvyx jfyzg ervnH`=QhAYb؆= b%lŰubL58XeJP$r? [^v}i!!Zh!M(]&:Tia2 E_DWB$x1k nU4PUv?gLhjtϡ"s*0z;%}usW7FGmͭ_ǜZ cyq=ʻ)JSpiԉ3(OExxD< MWu^#[ .r!+s(L]#oŷ4'U @5 ^RhׯhϣyE/4lq#6#Ø61'aS W-"a.M}ffؾaIa=kumҿ;KvEK4{ H߸&bfW߶ye͂W!Y@ dv/±™^k?;"P>{6J/f,nݙOC M[ .d`cƳSgȣ9u:={.^\-QS\%W;fsP ej/KM6)Ov[yI hF'C[a PKsXPKjHCandroid/support/v4/text/BidiFormatter$DirectionalityEstimator.classV{pTg+͓WG+lM(Y,h ٽ$7,Mx %-uZ(UȠjA 2S3Ԕ w{{;)Q<[GE~lv퐮]t;eˏe,Htn(nt2&Ef׳{[}ݑN ZDCY8H[zڥG x|ӡeG, ]-VLj֡8EH[|lIt@OE1_\O[a^NuBf´5n^o.>.*̄eP8]zʔ#tYfZÜufʈZf2Mhg2V D #f/+TҌG)etEao‡K 3ahg,1tQ5]`0SikK2yhM޳ңՇrӲ95ztMwi 71Z=CGxXOl;){[L0^G7 g^v]ބ{x$izPjp GJ$;7P]}hB3H*j7DF2I `.0 0X ]m1<`:8H H!-OVJlZ/݈J,7͋b 7ak L._H֣lqe#o% iKWIP&74Oh_I91Q5"Inpr$Εr3ݕ<\d"o8L |TrMWLϫd{d)u䱔[#Bg˹aNЂ͓`$T:\p_76s?n6EM( ʩi 9X'{&hL!=#.@$<*T{=x/ 8b,rjn#N9CZ^9mpLӨ-2._ӲǓ pDm{&;$S=5~Dy&݋nl7 rN{imb`p TlHOY^ 1m(v0N: 8ME mYڊ/6F;:yzoH{p^wh/ޢ}xpp=J }Ňt1ܢM': )C^҈hnzEѫ6^M]DӰg)gx4kcjoĽ^/]n^q{$nh'p%wP"4qj pژӾ˃ ٯrfVW`0{:< fmBLv/?;M=oG߂."@01Daѷ1Jo; LWE>^kLMtI&ޤ!&#\b~djtCN?gmrY(a$yv#sw EUFϬ2J 57/}t V1nPEXq0LBuN" Ǽ(w*Oc-q2*ģ: [u(*[h>|~ePAǣ4>Jҟ`6tH?6p۠r.k*hv>Shy:{#bϜB|{.[sϹ/xER ʅʍʃUʇ*fU*G\U`XU_\| ~Olũ"yZ @ צ LG2i+ؘlA#ngJ@B Xt0BE`0t<0A*L+(Z^ljՊ*fՎeTkZѥ:]ǨڀjNS]yp% Ι<8/SX꠳PY+F`iaՎ N>v"v'r\&nG^r"B/,*"u< Em cD F#ψ0(|֨$:ՐyEƳ9$p꛾wQZseZ6ʞ1ռS6);q.{.?kl?$,m3# -62PKҘ)PKjH:android/support/v4/text/TextDirectionHeuristicCompat.class}N?@}?G'_`cq"$ιpMzm|6C-/yCCh|r#X'"=,lJ~ 2jEoʹUi.Tau %nT&"=Z*@O2xv|s39g\~3tpC\yTDN-JUdBQ3 %Vٱ,:.fضJ}3dۖ&Q}mY6!a?+3̕]Y:^-6eae]TLOt[?,n%&zzUx[j'CI(T/0hNVQˑA' }gį'1ki(d?"{#}H'} *ndP$L5P;. (﵊H` k2MXOH SQCXbԹn*V!% ,aa*M}T?PKcrPKjH,android/support/v4/text/ICUCompatApi23.classT[WWNnA j1aBi* 6eF&3Y3'_kJ]v=Gm0s.{}~;EӨ# 3GUŏ7yBE (IΟ@IqC7`2nARneOiHº9|{?ƲaϵW&݁ INmDZ{ʹ'Fyj;Ed/BZ#vH1@pn[@h&1Ӡ[ᯒH{K(}dfxW_:c1EFv4_(+"'HP$ zi<:?^"rQ"f e1'Ei,>y\A}>Rգz%3z>3Hڎ"^=O&22ab1< b2AS1!l5AE@;)vmz<D(XHa/PK$ؾ$PKjH3android/support/v4/text/BidiFormatter$Builder.classoEǿxN\JJ :I)Ʃ(*5=$ֻBW\*@Bí5pDU/f]f}ݿ7sxLƫ&,112UJzC-J=7Փ.(- ,0dp)rK"̐n]Rao\EZ$}h2d768r͍ ?[rd7O^IOF S7%.W\+.~7x &5I ME`U5qfgO+ \eC\y:v8=> Oڇj9I kSi裡iFh}tN`[HзO`AB!IY&qGc\7K"} ֤#c^f3+13 af,%%ԷcblFROoðSD~@ wIϷy~'b01v9=>%&-ݫng<{]&ھ&ȇU 9!)ر qV=jb)?Fk`ƃf\ b%x62-d6n서ַvĠ.tp悁St#c#VôT; )֯n*YPlَvߧr= |;q.R3:Y&آJ;rPK$vPKjH;android/support/v4/text/TextDirectionHeuristicsCompat.classVmsF~o$Rb) 8#8o EV,y$~ N/u=IJ'0Es=o|"*"񽀪sYC-SX;|0n'{dM -Kw꺺+ô? G<öp=CkؚjZ 9'0R3{P&x{cx{ 3[X)`!=ɐl(zCmt߷j#UW2Xj+mՔWw|4"o$X4Jcm(5Xaj[Z^G5z$JHViWѾ@&ߕ2W&(L!5W' OðtyE5IsOGu .cwGUM{x=nFh:5)hXDɘPD2ȔÜ[(-,0$-5E m{hzf?*2:n0(0>\twG8#,ֱ!aa[ ¶8p-q~Z{EwC(nC%HaWGZ7~#eH1b+%ej0rR5HUݰ,ȥr OQmiS lbφ\ !?<scK/E䯐iHxӧ/At3N~zfI hb0Ptp4R įWC/ kQp; 8ANGzPC(V@/l!b7}5/pd4n`<[ó#И5.sdx1!I6YF2  / r8J/1 ^%24(Ey|KX2\'oQ^'N?I0IS⧈O?_ct2n 3)PKk/ PKjH9android/support/v4/text/ICUCompat$ICUCompatImplBase.classQ]KA=kA`#(ү~@?*>D{Ͻٽ_jK!- ƅrdWDKlK9ndw !VG*g`B?!l<6\i{ֵn:I#g-}Wy0<||ʵh>.([=++u+O3UZC1|#g^Bp} oXQ3D`Hݑߓ7*X$Mc]?3~wG2 (#F+оct+2)YF$Fct)d(?AvF>1Kf9#N!6Hw7PKedfPKjHGandroid/support/v4/text/TextDirectionHeuristicsCompat$FirstStrong.classSMOA}=;"_. z(axi::K1^x1I0^ /h͸IfU^UWO=t `2ø ܴ1[6[(0 1eamte=mJc]A.C7MQLJa;ȅ!CO]rUaB'KB(UQzi0tGF&EHvu Ye3g̷ NC0-<@6ػrd?:']zɻJ&yp2G]ڇtf'\ǔ9&+]9I$U8lGX1 Kh$ &7D$5e|'qǴPKePKjH5android/support/v4/text/ICUCompat$ICUCompatImpl.classJ@EkkT\B)u,tU (LG2&P.?4UƷˁwOOǸ&rj+Sr(J\ hbmKyii^풐%(̈́<{ldx<1RVUi>YqagN47}epCmqٷG8Cz7շFH!$PKs>'PKjHZandroid/support/v4/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicInternal.classTn@6qƤm(JH $)?B( BD -Qm.rh<K%P%<uBCd.3|;_ %\ϑUnpSc_3C ~bJGMD_0x {C|+j@+MQ @\$0c-S5\#m2Wg0?I@.O_P+֋G`I1S>6|Uk15 2 Wpx4 sDPK* PKjHMandroid/support/v4/text/TextUtilsCompat$TextUtilsCompatJellybeanMr1Impl.classSn@= -<6[ BB4Bt?IF {'*bQ;A {9G>FmPmsX([0H%St|i=ؒJ##cqSh]@;qf 6ř~6|o+a.C.CAf^˞Fhg$L(\gOP8Rr@~$d*`+ n5K fKI }F:(a=Ic삧57v7Uר YptciTJPK9/^PKjH/android/support/v4/text/TextUtilsCompat$1.classA 0EhZ v\ zB\m-1)MZ< Ĉ;Wg{K>|!8J&0:=\X_Kno8aV&WN؋N} 3J)3uYʲfŬ{g?ɂBUJTC>d&\õ\q6p7iQ PK'@ePKzjH9android/support/v4/text/TextUtilsCompatJellybeanMr1.classR]OA=C + A*PĠ$@ާ ifgO>?xd޽wΙs_ZXa{a5Ba206 ]ۓ s2%DGSۮ©D cazΪO:χϸ=?W:ݵ@7R볎{bD6ѕ7S1\ )o5'K , jX9ȌW7C*WdcBmkڙD1N^8H/]ޗ>g6󯔓Ps6D,d4;,& HCSqO%U Ym,2lcc)MͰ􋨈FeSEp2NQ޺;d XG\ %DR1jXpoeJ ~PK jPKjH8android/support/v4/text/ICUCompat$ICUCompatImplIcs.classR]KA=kfZVfRY@FB!,`>CMD* =̜{Ϝsa?>`+$(aUk 3*}ΐد + rutdp+:61Eӳ!0l5<v"*t]4lIJkphޯq-4Y+E xRznJ(_3/xd:l{OF10d[^XREQ$! Fd8 e~aGH".C7ht Iz"=q N"<+\BG>) T|3 PO&k ePɾGLF-ԐPK [PKjHEandroid/support/v4/text/TextDirectionHeuristicsCompat$AnyStrong.classTKoGl68qJ)y$Kn&\`K6aw/G. ^R?@Zj/*Uz:!뗏0R;1=6b_Ll&倲*r! Ej.MьSKgURp&0XT Qq,bR Q-JeNS/`J4#/.^U{[>Ðv]Jf^䫼`Pn&.5EJ%_a`T@L7ET>Y,<Gɺlx4~%]MXHwf@m#jԍ^">I sXwVmw{dUOZ/>Ix$o3Ni PKZzBPKjH=android/support/v4/text/TextDirectionHeuristicsCompat$1.classMn1 i`U]!@= z4cAА XpQ]'z#Pmh卫_Emt);Vؘ<=!!9{=*W6MY: LK|@zqU8]I7ˉ;zNPr PKջ?PKjH-android/support/v4/text/BidiFormatter$1.classA 0EhZ'p3@B\x؆R\x%r ?z?VCDkD0L&\s, ~NR.: 1 ̹ʌuUim͜;c. J -Jrv<"u_xe-|t|5>PK3v ԬPKjH=android/support/v4/text/ICUCompat$ICUCompatImplLollipop.classJA2M lPAD$=DwF[z&JOk&AA_U5UC=Bi1 SZY AKIkL6 WnДBF:{ŴmXz^ Pښ70FAH(|_EU#mخ+Vhw0VZ_aԓwo+[oF:ʊ{-oXmD2[햌/EFڿd$ |=DMucbc- \Ev9iZ5yf/y", b)$ ߘ>~xur Vbͬ=0!VWϼˬ?y#1w9QNl# ?A2G)LrLLb6{PKPK jHandroid/support/v4/internal/PK jH!android/support/v4/internal/view/PKjH2android/support/v4/internal/view/SupportMenu.classMj@&IĽ/ %V\zUV]J$l$nwEj:YQr10ߙs/^<z V3곑le+(K5l&lu\}ب[Ż5Hl&qBNifJã,$@C~xcmTe‹X}Z6T)HŗmuW=T7q-v;O5PKqLCPKjH5android/support/v4/internal/view/SupportSubMenu.class10C 4P$8F l!'H*4n C!Z {2GV"rUrgZ}a5*wSQ%BF;TopJ $'bagPK'PKjH6android/support/v4/internal/view/SupportMenuItem.classTmOP=_@A~#~h.4eSc\YRnnG#b|ОssoK 5 %t k[}=59VT!a{#(6V{n'sH\`̉um[g؝vi95o[f'.2):_+C%C^mjgNhx|Hmr|_v" I&PF&>jFڏ6`b? çiE8jIx֚FP{IbR#zY!Gy|DZ*jJ8) ;121$bU"YưbIºǕa͓@ݿp/{hGAO4\OD7hVuǜ߮K5A|!z!S5ΰg`n>f(D7_"^Le+)Jva:wc,ģOp{;؋WKXOp_PK% PKjH META-INF/PKjHCD=META-INF/MANIFEST.MFPK jHandroid/PK SjHandroid/support/PK jHandroid/support/annotation/PKjHY$+Oandroid/support/annotation/IntegerRes.classPKjH2[4 9android/support/annotation/RequiresPermission$Write.classPKjH& $)>android/support/annotation/ArrayRes.classPKjHz%%android/support/annotation/Keep.classPKjHy9M-1android/support/annotation/WorkerThread.classPKjH%"(android/support/annotation/AnimRes.classPKjHw/%, android/support/annotation/DrawableRes.classPKjHj]+++ android/support/annotation/MainThread.classPKjHR:&- android/support/annotation/StyleableRes.classPKjHɻd)android/support/annotation/ColorInt.classPKjHF;A$)android/support/annotation/StyleRes.classPKjH'$)xandroid/support/annotation/ColorRes.classPKjH,.android/support/annotation/TransitionRes.classPKjHe(fandroid/support/annotation/NonNull.classPKjH;f)'android/support/annotation/IntDef.classPKjHo%) 8Mandroid/support/annotation/RequiresPermission$Read.classPKjH"(android/support/annotation/AttrRes.classPKjHlBe)4android/support/annotation/UiThread.classPKjH^2$,android/support/annotation/FractionRes.classPKjH5P#'/3 android/support/annotation/RequiresPermission.classPKjHƴ3"'9android/support/annotation/AnyRes.classPKjHH"( android/support/annotation/MenuRes.classPKjH+jCnf%("android/support/annotation/Size.classPKjH_ql*#android/support/annotation/CallSuper.classPKjHg-?%android/support/annotation/BinderThread.classPKjH *,&android/support/annotation/CheckResult.classPKjHV%*8(android/support/annotation/StringRes.classPKjH~d0I))android/support/annotation/IntRange.classPKjH &U+android/support/annotation/IdRes.classPKjH4/%+,android/support/annotation/PluralsRes.classPKjH "(G.android/support/annotation/BoolRes.classPKjHI>$'/android/support/annotation/XmlRes.classPKjH[)#)81android/support/annotation/DimenRes.classPKjHa3)2android/support/annotation/Nullable.classPKjH|;%,4android/support/annotation/AnimatorRes.classPKjH/3F*05android/support/annotation/InterpolatorRes.classPKjH/$*#7android/support/annotation/LayoutRes.classPKjH*8android/support/annotation/StringDef.classPKjHa&r@2:android/support/annotation/VisibleForTesting.classPKjHZf"'7;android/support/annotation/RawRes.classPKjHlYo~~+<android/support/annotation/FloatRange.classPK jH>android/support/v4/PK jH>android/support/v4/print/PK}jHÖ 2>android/support/v4/print/PrintHelperKitkat$2.classPK}jH|I%0Kandroid/support/v4/print/PrintHelperKitkat.classPKjH B>K[android/support/v4/print/PrintHelper$PrintHelperStubImpl.classPK}jH7"F^android/support/v4/print/PrintHelperKitkat$OnPrintFinishCallback.classPKjH~7 @_android/support/v4/print/PrintHelper$PrintHelperKitkatImpl.classPK}jH<ӣ\ 2Rdandroid/support/v4/print/PrintHelperKitkat$1.classPKjHT_!Bnandroid/support/v4/print/PrintHelper$PrintHelperKitkatImpl$2.classPKjHEK@pandroid/support/v4/print/PrintHelper$OnPrintFinishCallback.classPKjHCfAqandroid/support/v4/print/PrintHelper$PrintHelperVersionImpl.classPK}jH%9J4xsandroid/support/v4/print/PrintHelperKitkat$2$1.classPKjHn *yandroid/support/v4/print/PrintHelper.classPK}jHt6,android/support/v4/print/PrintHelperKitkat$2$1$1.classPKjH>z`ک,android/support/v4/print/PrintHelper$1.classPKjH)Bandroid/support/v4/print/PrintHelper$PrintHelperKitkatImpl$1.classPK jHandroid/support/v4/net/PKjHA15android/support/v4/net/TrafficStatsCompat$1.classPKjH\ uTCandroid/support/v4/net/ConnectivityManagerCompat$ConnectivityManagerCompatImpl.classPKjHXandroid/support/v4/net/ConnectivityManagerCompat$BaseConnectivityManagerCompatImpl.classPKjHmz]:android/support/v4/net/ConnectivityManagerCompat$JellyBeanConnectivityManagerCompatImpl.classPKjHY%8:/Pandroid/support/v4/net/TrafficStatsCompat.classPKyjH~ײ>P?android/support/v4/net/ConnectivityManagerCompatJellyBean.classPKojH6x~Aandroid/support/v4/net/ConnectivityManagerCompatGingerbread.classPKjH3`Jandroid/support/v4/net/TrafficStatsCompat$BaseTrafficStatsCompatImpl.classPKjH"1zLandroid/support/v4/net/TrafficStatsCompat$BaseTrafficStatsCompatImpl$1.classPKjH/%Fandroid/support/v4/net/TrafficStatsCompat$TrafficStatsCompatImpl.classPKjHX`3android/support/v4/net/ConnectivityManagerCompat$HoneycombMR2ConnectivityManagerCompatImpl.classPKjHR&_Nandroid/support/v4/net/ConnectivityManagerCompat$GingerbreadConnectivityManagerCompatImpl.classPKvjHңBfandroid/support/v4/net/ConnectivityManagerCompatHoneycombMR2.classPKxjH?~~+o2áandroid/support/v4/net/TrafficStatsCompatIcs.classPKjHcƏ6Nandroid/support/v4/net/ConnectivityManagerCompat.classPKjH@ȆuUAandroid/support/v4/net/TrafficStatsCompat$BaseTrafficStatsCompatImpl$SocketTags.classPKjHrjgkqIJandroid/support/v4/net/TrafficStatsCompat$IcsTrafficStatsCompatImpl.classPK jH2android/support/v4/content/PKjHGʯ 0kandroid/support/v4/content/AsyncTaskLoader.classPKjHCPTandroid/support/v4/content/ContentResolverCompat$ContentResolverCompatImplBase.classPKjH׀RQandroid/support/v4/content/ContentResolverCompat$ContentResolverCompatImplJB.classPKjH^>android/support/v4/content/IntentCompat$IntentCompatImpl.classPKjHkWn2Mandroid/support/v4/content/ModernAsyncTask$1.classPK jHandroid/support/v4/content/pm/PKjH3«6@android/support/v4/content/pm/ActivityInfoCompat.classPKjHO;)jZandroid/support/v4/content/SharedPreferencesCompat$EditorCompat$EditorHelperBaseImpl.classPKyjHwPP3kandroid/support/v4/content/IntentCompatIcsMr1.classPKjHU%Eandroid/support/v4/content/SharedPreferencesCompat$EditorCompat.classPKjH@,@android/support/v4/content/ModernAsyncTask$InternalHandler.classPKjH3Fuandroid/support/v4/content/LocalBroadcastManager$BroadcastRecord.classPKsjHm$SLX6android/support/v4/content/IntentCompatHoneycomb.classPKjHSQ I-oandroid/support/v4/content/CursorLoader.classPKjHBްV2android/support/v4/content/ModernAsyncTask$2.classPKjHpd:+android/support/v4/content/FileProvider$PathStrategy.classPKjHd 9oandroid/support/v4/content/WakefulBroadcastReceiver.classPKmjH)y3android/support/v4/content/ContextCompatFroyo.classPKjHZ2@android/support/v4/content/ModernAsyncTask$AsyncTaskResult.classPKjH촔,kLandroid/support/v4/content/SharedPreferencesCompat$EditorCompat$Helper.classPK jHdandroid/support/v4/content/res/PKyjHPy%:android/support/v4/content/res/ResourcesCompatIcsMr1.classPKjH{~S9Q4android/support/v4/content/res/TypedArrayUtils.classPKjHB 9aandroid/support/v4/content/res/ResourcesCompatApi21.classPKjH`9android/support/v4/content/res/ResourcesCompatApi23.classPKjH&m& 4android/support/v4/content/res/ResourcesCompat.classPKjH!>android/support/v4/content/Loader$OnLoadCanceledListener.classPKjH+"|I 9bandroid/support/v4/content/AsyncTaskLoader$LoadTask.classPKsjH<7Eandroid/support/v4/content/ContextCompatHoneycomb.classPKjHI3:3android/support/v4/content/SharedPreferencesCompat$1.classPKjHgBf7Sandroid/support/v4/content/ModernAsyncTask$Status.classPK|jH2C0p4android/support/v4/content/ContextCompatKitKat.classPKjHX6 android/support/v4/content/ContentResolverCompat.classPKjHR7U android/support/v4/content/ParallelExecutorCompat.classPKjH6fBandroid/support/v4/content/IntentCompat$IntentCompatImplBase.classPKjHǷ-!  0android/support/v4/content/ModernAsyncTask.classPKjHx [ .:android/support/v4/content/ContextCompat.classPKjHjz-)android/support/v4/content/IntentCompat.classPKjH@B.android/support/v4/content/Loader$ForceLoadContentObserver.classPKjHr30android/support/v4/content/ContextCompatApi23.classPKjHkUP}2android/support/v4/content/ContentResolverCompat$ContentResolverCompatImpl.classPKjHN63android/support/v4/content/LocalBroadcastManager.classPKyjHэw51?Candroid/support/v4/content/ContentResolverCompatJellybean.classPKjH˚2Eandroid/support/v4/content/ModernAsyncTask$3.classPKjH@_ǒ>Iandroid/support/v4/content/Loader$OnLoadCompleteListener.classPKjHSl9DJandroid/support/v4/content/IntentCompat$IntentCompatImplIcsMr1.classPKjH /8Landroid/support/v4/content/LocalBroadcastManager$1.classPKjHK)?bOandroid/support/v4/content/ModernAsyncTask$WorkerRunnable.classPKyjHDZp7Qandroid/support/v4/content/ContextCompatJellybean.classPKjH$b!8qSandroid/support/v4/content/SharedPreferencesCompat.classPKjHG2Tandroid/support/v4/content/ModernAsyncTask$4.classPKjHPcU D'=Wandroid/support/v4/content/Loader.classPKojHL8(bandroid/support/v4/content/EditorCompatGingerbread.classPKsjH@|S8dandroid/support/v4/content/ExecutorCompatHoneycomb.classPKjH3@Dfandroid/support/v4/content/FileProvider$SimplePathStrategy.classPKjHb,[3goandroid/support/v4/content/ContextCompatApi21.classPKjHʰCqqandroid/support/v4/content/PermissionChecker$PermissionResult.classPKjHr@randroid/support/v4/content/IntentCompat$IntentCompatImplHC.classPKjH+3T 2tandroid/support/v4/content/PermissionChecker.classPKjHid.'#-yandroid/support/v4/content/FileProvider.classPKjH9UE8android/support/v4/content/LocalBroadcastManager$ReceiverRecord.classPKjHEM(Zandroid/support/v4/content/SharedPreferencesCompat$EditorCompat$EditorHelperApi9Impl.classPK jHandroid/support/v4/os/PKvjHp@dRCandroid/support/v4/os/ParcelableCompatCreatorHoneycombMR2Stub.classPKsjHPL4android/support/v4/os/AsyncTaskCompatHoneycomb.classPKjHmc2,nandroid/support/v4/os/ParcelableCompat.classPKjHn''android/support/v4/os/TraceCompat.classPKjHKVX6android/support/v4/os/IResultReceiver$Stub$Proxy.classPK}jHAL03͝android/support/v4/os/EnvironmentCompatKitKat.classPKjHf&V .^android/support/v4/os/CancellationSignal.classPKjH3%X+android/support/v4/os/IResultReceiver.classPKjH˴BS+Randroid/support/v4/os/AsyncTaskCompat.classPKjHG*;Gandroid/support/v4/os/ResultReceiver$MyResultReceiver.classPKjH S`0ګandroid/support/v4/os/IResultReceiver$Stub.classPKvjHXy?android/support/v4/os/ParcelableCompatCreatorHoneycombMR2.classPKjHM5me6android/support/v4/os/OperationCanceledException.classPKyjH̚L]=eandroid/support/v4/os/CancellationSignalCompatJellybean.classPKjH85c::-android/support/v4/os/ParcelableCompat$CompatCreator.classPKjHR *android/support/v4/os/ResultReceiver.classPKjHHJl5android/support/v4/os/ResultReceiver$MyRunnable.classPK{jHCb`G,-Aandroid/support/v4/os/TraceJellybeanMR2.classPKjHhʹ?android/support/v4/os/CancellationSignal$OnCancelListener.classPKvjH)<android/support/v4/os/ParcelableCompatCreatorCallbacks.classPKjH,eandroid/support/v4/os/ResultReceiver$1.classPKjH:yO-android/support/v4/os/EnvironmentCompat.classPK jHandroid/support/v4/hardware/PK jH(android/support/v4/hardware/fingerprint/PKjHa j+android/support/v4/hardware/fingerprint/FingerprintManagerCompat$Api23FingerprintManagerCompatImpl$1.classPKjH0Uh$android/support/v4/hardware/fingerprint/FingerprintManagerCompatApi23$AuthenticationResultInternal.classPKjH flidandroid/support/v4/hardware/fingerprint/FingerprintManagerCompat$LegacyFingerprintManagerCompatImpl.classPKjHnu baandroid/support/v4/hardware/fingerprint/FingerprintManagerCompatApi23$AuthenticationCallback.classPKjHVE90Sandroid/support/v4/hardware/fingerprint/FingerprintManagerCompat$CryptoObject.classPKjH G- Mandroid/support/v4/hardware/fingerprint/FingerprintManagerCompatApi23$1.classPKjHG]xandroid/support/v4/hardware/fingerprint/FingerprintManagerCompat$AuthenticationCallback.classPKjHܩ khandroid/support/v4/hardware/fingerprint/FingerprintManagerCompat$Api23FingerprintManagerCompatImpl.classPKjH?kKKandroid/support/v4/hardware/fingerprint/FingerprintManagerCompatApi23.classPKjH]>DXandroid/support/v4/hardware/fingerprint/FingerprintManagerCompatApi23$CryptoObject.classPKjHop Fandroid/support/v4/hardware/fingerprint/FingerprintManagerCompat.classPKjHmbUcRandroid/support/v4/hardware/fingerprint/FingerprintManagerCompat$FingerprintManagerCompatImpl.classPKjHM[8android/support/v4/hardware/fingerprint/FingerprintManagerCompat$AuthenticationResult.classPK jH$aandroid/support/v4/hardware/display/PKzjH/V$'Dandroid/support/v4/hardware/display/DisplayManagerJellybeanMr1.classPKjH)$8OO9android/support/v4/hardware/display/DisplayManagerCompat$JellybeanMr1Impl.classPKjH`yqm>android/support/v4/hardware/display/DisplayManagerCompat.classPKjH?uIandroid/support/v4/hardware/display/DisplayManagerCompat$LegacyImpl.classPK jH3android/support/v4/app/PKjH] e<handroid/support/v4/app/NotificationCompatApi21$Builder.classPKSjHX]6/android/support/v4/app/BaseFragmentActivityDonut.classPKsjHY< :android/support/v4/app/BaseFragmentActivityHoneycomb.classPKSjHAI>'android/support/v4/app/RemoteInputCompatBase$RemoteInput.classPKjH5Ne:<android/support/v4/app/ShareCompat$ShareCompatImplJB.classPKjHx|\c/\android/support/v4/app/FragmentActivity$1.classPKjHW5android/support/v4/app/NotificationCompatExtras.classPKjHAa2;android/support/v4/app/ActivityOptionsCompat.classPKjHpG."android/support/v4/app/BackStackRecord$3.classPKjH]Ao,G&android/support/v4/app/FragmentManager$OnBackStackChangedListener.classPKjH >i <J'android/support/v4/app/NotificationCompatApi20$Builder.classPKjHTh۾ 20android/support/v4/app/SharedElementCallback.classPKjH86-<android/support/v4/app/RemoteInput$Impl.classPKjH 39>android/support/v4/app/FragmentTransitionCompat21$1.classPKjHFFN@android/support/v4/app/TaskStackBuilder$TaskStackBuilderImplBase.classPKjH5'ۀ} m6Candroid/support/v4/app/FragmentStatePagerAdapter.classPKjHV)SNandroid/support/v4/app/FragmentManagerImpl$AnimateOnHWLayerIfNeededListener$1.classPKjH 4zQandroid/support/v4/app/SuperNotCalledException.classPKjH 8Randroid/support/v4/app/NotificationCompat$Extender.classPKyjHh>4"Tandroid/support/v4/app/ActivityOptionsCompatJB.classPKjH+s:0;wWandroid/support/v4/app/FragmentManager$BackStackEntry.classPKjH@g'Yandroid/support/v4/app/Fragment$1.classPKjH76/ +[android/support/v4/app/DialogFragment.classPKjHs*s",jandroid/support/v4/app/FragmentTabHost.classPKjHțaWyandroid/support/v4/app/ActivityCompatApi23$RequestPermissionsRequestCodeValidator.classPKjHg E4{android/support/v4/app/ActivityCompat21$SharedElementCallback21.classPKjHT*Q}android/support/v4/app/NotificationCompat$NotificationCompatImplGingerbread.classPKjHqH-3.android/support/v4/app/BackStackRecord$1.classPKjHq8Pe<fandroid/support/v4/app/Fragment$InstantiationException.classPKjH%ʲ4 android/support/v4/app/ActionBarDrawerToggle$1.classPKyjH<;4android/support/v4/app/NotificationBuilderWithActions.classPKjHZ۴NPqandroid/support/v4/app/NotificationCompat$CarExtender$UnreadConversation$1.classPKjH+pSandroid/support/v4/app/FragmentManagerImpl$AnimateOnHWLayerIfNeededListener$2.classPKjHs?5android/support/v4/app/NotificationCompat$Action$Extender.classPKjH)\4Z-android/support/v4/app/ActivityCompat$1.classPKjHV3T4 3android/support/v4/app/RemoteInputCompatApi20.classPKjHQ+j ,android/support/v4/app/FragmentManager.classPKjHUF1Eandroid/support/v4/app/RemoteInput$ImplBase.classPKjHg-android/support/v4/app/BackStackState$1.classPKjHj{ 64android/support/v4/app/NotificationCompat$Action.classPKjH cGJGandroid/support/v4/app/NotificationCompat$NotificationCompatImplBase.classPKjH`>xLandroid/support/v4/app/NotificationManagerCompat$ServiceConnectedEvent.classPKjHDD Nandroid/support/v4/app/NotificationCompat$CarExtender$UnreadConversation.classPKyjH\>e98aandroid/support/v4/app/NotificationCompatJellybean.classPKjHA2,android/support/v4/app/FragmentManagerImpl$2.classPKjHQv,+u%qandroid/support/v4/app/Fragment.classPKjH]8+:android/support/v4/app/ListFragment$2.classPKjHh,-android/support/v4/app/ActivityCompat22.classPKjHF\1 QRandroid/support/v4/app/FragmentManagerImpl$AnimateOnHWLayerIfNeededListener.classPKjH ,zAandroid/support/v4/app/NotificationManagerCompat$NotifyTask.classPKsjH34pandroid/support/v4/app/ActivityCompatHoneycomb.classPKjH ;android/support/v4/app/NotificationManagerCompat$Impl.classPKjHT( >android/support/v4/app/NotificationCompat$Action$Builder.classPKjH% + android/support/v4/app/BackStackState.classPKjHOandroid/support/v4/app/NotificationCompat$NotificationCompatImplJellybean.classPKjH6 )xandroid/support/v4/app/ListFragment.classPKjHlvG{9,android/support/v4/app/FragmentTransitionCompat21$3.classPKjH~ (.android/support/v4/app/ShareCompat.classPKjHh\F+ V4android/support/v4/app/NotificationCompat$NotificationCompatImplIceCreamSandwich.classPKSjH\0R.l8android/support/v4/app/BundleCompatDonut.classPKSjH 0F=android/support/v4/app/NotificationCompatBase$UnreadConversation.classPKjH86 ]p?android/support/v4/app/NotificationCompatSideChannelService$NotificationSideChannelStub.classPKjHLW NCandroid/support/v4/app/ActivityCompat$OnRequestPermissionsResultCallback.classPKsjHúQ,F ;{Eandroid/support/v4/app/ActionBarDrawerToggleHoneycomb.classPKjH:B8Landroid/support/v4/app/NotificationCompat$Action$1.classPKyjHg'\Oandroid/support/v4/app/BundleUtil.classPK|jH¯4'8Qandroid/support/v4/app/ActivityManagerCompatKitKat.classPKjHn}x;`Sandroid/support/v4/app/NotificationManagerCompat$Task.classPKjHtqJ-Tandroid/support/v4/app/ActivityCompat21.classPKjH %6Xandroid/support/v4/app/ShareCompat$IntentBuilder.classPKjHe>W. .}dandroid/support/v4/app/BackStackRecord$2.classPKjH(4w<0iandroid/support/v4/app/ShareCompat$ShareCompatImplBase.classPKjHJ1Cmandroid/support/v4/app/NotificationCompatSideChannelService$1.classPKjHaK70oandroid/support/v4/app/RemoteInput$Builder.classPKxjH &Frandroid/support/v4/app/NotificationManagerCompatIceCreamSandwich.classPKjH! 1tandroid/support/v4/app/FragmentPagerAdapter.classPKsjHC{android/support/v4/app/NotificationBuilderWithBuilderAccessor.classPKjHWi.}android/support/v4/app/FragmentTabHost$1.classPKsjHkV'6 ~android/support/v4/app/TaskStackBuilderHoneycomb.classPKjH'?android/support/v4/app/NotificationCompat$BuilderExtender.classPK{jH~g >android/support/v4/app/ActionBarDrawerToggleJellybeanMR2.classPKyjH y-iandroid/support/v4/app/ActivityCompatJB.classPKjHL2Έandroid/support/v4/app/AppOpsManagerCompat23.classPKjH|>b W/zandroid/support/v4/app/FragmentController.classPKjHV`8+android/support/v4/app/ActivityCompat.classPKjH9Kv 0android/support/v4/app/FragmentTransaction.classPKjHuBJ:android/support/v4/app/NotificationCompat$InboxStyle.classPKjHB*?"android/support/v4/app/NotificationManagerCompat$ImplBase.classPKjH|9k 5android/support/v4/app/ShareCompat$IntentReader.classPKjHuFC9android/support/v4/app/FragmentTabHost$SavedState$1.classPKjH-" 6android/support/v4/app/NotificationManagerCompat.classPKjHK6android/support/v4/app/NotificationCompat$NotificationCompatImplApi21.classPKjHh54android/support/v4/app/NavUtils$NavUtilsImplJB.classPKjHDJP7android/support/v4/app/FragmentTabHost$SavedState.classPKjH4`!30android/support/v4/app/FragmentManagerState$1.classPKjH$4B*android/support/v4/app/FragmentState.classPKjH%Qr*Landroid/support/v4/app/ServiceCompat.classPKjHck60android/support/v4/app/AppOpsManagerCompat.classPKsjH 'Ws 8tandroid/support/v4/app/NotificationCompatHoneycomb.classPKjH>p#%11android/support/v4/app/FragmentHostCallback.classPKjH Q9 28android/support/v4/app/Fragment$SavedState$1.classPKjH/h0android/support/v4/app/ActivityCompatApi23.classPKxjH*G^android/support/v4/app/NotificationCompatIceCreamSandwich$Builder.classPKjHȓ"(<2android/support/v4/app/NavUtils$NavUtilsImpl.classPK2jHBct<Tandroid/support/v4/app/NotificationManagerCompatEclair.classPKjHz?L% )android/support/v4/app/BundleCompat.classPKjHpR0android/support/v4/app/FragmentManagerImpl.classPKjHg\ GuVandroid/support/v4/app/ActivityCompat21$SharedElementCallbackImpl.classPKjHڡ+HZandroid/support/v4/app/ActivityOptionsCompat$ActivityOptionsImpl21.classPKjHI,]android/support/v4/app/FragmentState$1.classPKjH<`w8A_android/support/v4/app/NotificationManagerCompat$CancelTask.classPKjH7vCqcandroid/support/v4/app/ActionBarDrawerToggle$DelegateProvider.classPKyjHQ$L]7dandroid/support/v4/app/RemoteInputCompatJellybean.classPKjHzc 9mandroid/support/v4/app/FragmentTransitionCompat21$4.classPKyjHJa6 @sandroid/support/v4/app/NotificationCompatJellybean$Builder.classPKSjH0&Nandroid/support/v4/app/NotificationCompatBase$UnreadConversation$Factory.classPKjHMl/android/support/v4/app/BackStackRecord$Op.classPK|jHNڂ =android/support/v4/app/NotificationCompatKitKat$Builder.classPKjHՏC :randroid/support/v4/app/INotificationSideChannel$Stub.classPKjH0_BPandroid/support/v4/app/ActionBarDrawerToggle$ActionBarDrawerToggleImplBase.classPK{jHJg5ݕandroid/support/v4/app/BundleCompatJellybeanMR2.classPKjHNt 2ܗandroid/support/v4/app/RemoteInput$ImplApi20.classPKjH(i^LGandroid/support/v4/app/ActionBarDrawerToggle$ActionBarDrawerToggleImpl.classPKjH؁Gԛandroid/support/v4/app/NotificationCompat$Action$WearableExtender.classPKjHqx Gʢandroid/support/v4/app/ActivityCompat$SharedElementCallback21Impl.classPKjHC(W _2android/support/v4/app/ActionBarDrawerToggle.classPKjH_ dOL;android/support/v4/app/NotificationCompat$NotificationCompatImplKitKat.classPKjHrnN+7android/support/v4/app/NotificationCompat$Builder.classPKjHź68android/support/v4/app/FragmentTransaction$Transit.classPKjHHĬj Fandroid/support/v4/app/NotificationCompat$NotificationCompatImpl.classPKjH>cN[9.6android/support/v4/app/FragmentContainer.classPKjH##6*android/support/v4/app/LoaderManager.classPKjHK47{android/support/v4/app/FragmentTransitionCompat21.classPKjHb{R 6android/support/v4/app/NavUtils$NavUtilsImplBase.classPKojH?J#:Aandroid/support/v4/app/NotificationCompatGingerbread.classPKSjH<3android/support/v4/app/NotificationCompatBase.classPKSjHJ:android/support/v4/app/NotificationCompatBase$Action.classPKjH)/ android/support/v4/app/NotificationCompat.classPKjH7y?android/support/v4/app/NotificationCompat$BigPictureStyle.classPKjH!3 ;(android/support/v4/app/FragmentActivity$HostCallbacks.classPKjHM%Xandroid/support/v4/app/NotificationManagerCompat$SideChannelManager$ListenerRecord.classPKSjH;L8Bandroid/support/v4/app/NotificationCompatBase$Action$Factory.classPKjH;ܯC<mandroid/support/v4/app/BackStackRecord$TransitionState.classPKjHh[(1android/support/v4/app/FragmentManagerState.classPKjH*`Kandroid/support/v4/app/NotificationCompat$NotificationCompatImplApi20.classPKjHك{|N !android/support/v4/app/ActionBarDrawerToggle$ActionBarDrawerToggleImplHC.classPKjHesvO$android/support/v4/app/NotificationCompat$NotificationCompatImplHoneycomb.classPKjHҪ*5'android/support/v4/app/RemoteInput$1.classPKjHp=lA!*android/support/v4/app/NotificationManagerCompat$ImplEclair.classPKjH /;s,android/support/v4/app/ShareCompat$ShareCompatImplICS.classPKyjHr 86o/android/support/v4/app/TaskStackBuilderJellybean.classPKjH+H{1android/support/v4/app/ActivityOptionsCompat$ActivityOptionsImplJB.classPKsjHڇL3android/support/v4/app/ActionBarDrawerToggleHoneycomb$SetIndicatorInfo.classPKjHD1H8android/support/v4/app/NotificationCompat$1.classPKjHGV9android/support/v4/app/FragmentActivity$NonConfigurationInstances.classPKSjH&OFX;android/support/v4/app/RemoteInputCompatBase$RemoteInput$Factory.classPKjHހH3:<android/support/v4/app/LoaderManager$LoaderCallbacks.classPKSjH]2 2u>android/support/v4/app/RemoteInputCompatBase.classPKjHJ7δV@?android/support/v4/app/AppOpsManagerCompat$AppOpsManager23.classPKjHJo]2Bandroid/support/v4/app/FragmentManagerImpl$5.classPKjH˚- 4uEandroid/support/v4/app/NotificationCompatApi21.classPKjH߶20Pandroid/support/v4/app/Fragment$SavedState.classPKjH8d!2Sandroid/support/v4/app/ActivityManagerCompat.classPKjH쏤UMK(Vandroid/support/v4/app/NotificationManagerCompat$ImplIceCreamSandwich.classPKjHj%*3Wandroid/support/v4/app/NoSaveStateFrameLayout.classPKjHe>{C&9t[android/support/v4/app/LoaderManagerImpl$LoaderInfo.classPKjH5Gק+landroid/support/v4/app/ListFragment$1.classPKjHuu7nandroid/support/v4/app/DialogFragment$DialogStyle.classPKjHwIr; pandroid/support/v4/app/ActionBarDrawerToggle$Delegate.classPKjH>r|Xqandroid/support/v4/app/ActionBarDrawerToggle$ActionBarDrawerToggleImplJellybeanMR2.classPKjH^ԊV2tandroid/support/v4/app/AppOpsManagerCompat$1.classPKjHPBuandroid/support/v4/app/TaskStackBuilder$TaskStackBuilderImpl.classPKjH;—0M%$wandroid/support/v4/app/NavUtils.classPKjHY<android/support/v4/app/NotificationCompat$BigTextStyle.classPKjHdA7Kjandroid/support/v4/app/TaskStackBuilder$TaskStackBuilderImplJellybean.classPKjH=֩@?android/support/v4/app/TaskStackBuilder$SupportParentable.classPKjH'e] @Zandroid/support/v4/app/INotificationSideChannel$Stub$Proxy.classPKjHx ,z2oandroid/support/v4/app/FragmentManagerImpl$4.classPKjHx7e Aandroid/support/v4/app/NotificationCompatSideChannelService.classPKjHK (ޒandroid/support/v4/app/RemoteInput.classPKjHyh%S-android/support/v4/app/FragmentActivity.classPK|jH8|R 5Bandroid/support/v4/app/NotificationCompatKitKat.classPKjH{Qyt Vnandroid/support/v4/app/NotificationCompat$CarExtender$UnreadConversation$Builder.classPKjH3-MF @android/support/v4/app/ActionBarDrawerToggle$SlideDrawable.classPKjHZ6a}6android/support/v4/app/RemoteInput$ImplJellybean.classPKjH"R&<2android/support/v4/app/FragmentManagerImpl$3.classPKyjHad'android/support/v4/app/NavUtilsJB.classPKjH":<android/support/v4/app/FragmentManagerImpl$FragmentTag.classPKjHO7\,android/support/v4/app/BackStackRecord.classPKjH`5 android/support/v4/app/NotificationCompat$Style.classPKjH !5android/support/v4/app/INotificationSideChannel.classPKjH2$BQandroid/support/v4/app/AppOpsManagerCompat$AppOpsManagerImpl.classPKjHFA8~4android/support/v4/app/FragmentTabHost$TabInfo.classPKjHz:Eandroid/support/v4/app/FragmentTransitionCompat21$ViewRetriever.classPKjHɯ<android/support/v4/app/FragmentTabHost$DummyTabFactory.classPKjHOL9android/support/v4/app/FragmentTransitionCompat21$2.classPKjHS]54#android/support/v4/app/NotificationCompatApi20.classPKxjHg%@+1,android/support/v4/app/ShareCompatICS.classPKjHnF8/android/support/v4/app/ShareCompat$ShareCompatImpl.classPKjH6 -1android/support/v4/app/TaskStackBuilder.classPKjHqa-@<android/support/v4/app/NotificationCompat$WearableExtender.classPKyjH1L0*Landroid/support/v4/app/ShareCompatJB.classPKjHWY)IuNandroid/support/v4/app/NotificationManagerCompat$SideChannelManager.classPKjHR(g4Eaandroid/support/v4/app/ActivityOptionsCompat21.classPKjHJpW2dandroid/support/v4/app/FragmentManagerImpl$1.classPKjHnv*.fandroid/support/v4/app/LoaderManagerImpl.classPKjHj;1yandroid/support/v4/app/NotificationCompat$CarExtender.classPKxjHJ?android/support/v4/app/NotificationCompatIceCreamSandwich.classPKjH8,:Kandroid/support/v4/app/TaskStackBuilder$TaskStackBuilderImplHoneycomb.classPKjH-' Ë́android/support/v4/app/FragmentTransitionCompat21$EpicenterView.classPK jH(gandroid/support/v4/accessibilityservice/PKjH2= Landroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.classPKjH, *rv qGandroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$AccessibilityServiceInfoJellyBeanMr2.classPKjH4f lm\android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$AccessibilityServiceInfoStubImpl.classPK{jHoBXandroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompatJellyBeanMr2.classPKjHWa l̔android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$AccessibilityServiceInfoIcsImpl.classPKjHqO2pǗandroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$AccessibilityServiceInfoVersionImpl.classPKxjHFMOandroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompatIcs.classPK jHandroid/support/v4/animation/PKSjH7?Randroid/support/v4/animation/AnimatorUpdateListenerCompat.classPKtjHBuVcbandroid/support/v4/animation/HoneycombMr1AnimatorCompatProvider$AnimatorListenerCompatWrapper.classPKtjHYa5߶ bIandroid/support/v4/animation/HoneycombMr1AnimatorCompatProvider$HoneycombValueAnimatorCompat.classPKSjH_bR>android/support/v4/animation/DonutAnimatorCompatProvider.classPKSjH}Vandroid/support/v4/animation/DonutAnimatorCompatProvider$DonutFloatValueAnimator.classPKSjHgK 6android/support/v4/animation/ValueAnimatorCompat.classPKSjH[d>9(android/support/v4/animation/AnimatorListenerCompat.classPKjHQ_Jm7Uandroid/support/v4/animation/AnimatorCompatHelper.classPKSjH"'3android/support/v4/animation/AnimatorProvider.classPKSjHSuX)android/support/v4/animation/DonutAnimatorCompatProvider$DonutFloatValueAnimator$1.classPKtjH34[Eandroid/support/v4/animation/HoneycombMr1AnimatorCompatProvider.classPKtjH"({dͺandroid/support/v4/animation/HoneycombMr1AnimatorCompatProvider$HoneycombValueAnimatorCompat$1.classPK jHandroid/support/v4/provider/PKjHt? 4android/support/v4/provider/SingleDocumentFile.classPKjH1ٯ 8android/support/v4/provider/DocumentsContractApi21.classPKjH/1android/support/v4/provider/RawDocumentFile.classPK}jH%l"8android/support/v4/provider/DocumentsContractApi19.classPKjH 2android/support/v4/provider/TreeDocumentFile.classPKjHGn| .android/support/v4/provider/DocumentFile.classPK jHaandroid/support/v4/widget/PKjHhK~(Gandroid/support/v4/widget/DrawerLayout$DrawerLayoutCompatImplBase.classPKjH%K Gandroid/support/v4/widget/SlidingPaneLayout$AccessibilityDelegate.classPKjHory.android/support/v4/widget/ScrollerCompat.classPKSjHAbr 9android/support/v4/widget/CompoundButtonCompatDonut.classPKjHH-4wandroid/support/v4/widget/SwipeRefreshLayout$1.classPKjH.,2android/support/v4/widget/AutoScrollHelper$1.classPKjHXe; 3android/support/v4/widget/TextViewCompatDonut.classPKjHc_@{>android/support/v4/widget/SlidingPaneLayout$LayoutParams.classPKjH`ޣH android/support/v4/widget/AutoScrollHelper$ScrollAnimationRunnable.classPKjH'{;android/support/v4/widget/NestedScrollView$SavedState.classPKjHz=android/support/v4/widget/PopupMenuCompat$PopupMenuImpl.classPKjHM<34$android/support/v4/widget/SwipeRefreshLayout$5.classPKjH14android/support/v4/widget/SwipeRefreshLayout$3.classPKjHI, <android/support/v4/widget/CompoundButtonCompatLollipop.classPKxjH0 91android/support/v4/widget/ScrollerCompatIcs.classPKjHKZ J<android/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImplJB.classPKjHTZ  @"android/support/v4/widget/AutoScrollHelper$ClampedScroller.classPKjH.5(android/support/v4/widget/DrawerLayout$LockMode.classPKjHꙧ~6)android/support/v4/widget/PopupWindowCompatApi21.classPKjH `~8.android/support/v4/widget/DrawerLayout$EdgeGravity.classPKjHgV*8q/android/support/v4/widget/MaterialProgressDrawable.classPKjHMBandroid/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImplJBMR1.classPKjHZ*QQ:7Eandroid/support/v4/widget/MaterialProgressDrawable$2.classPKjH(N\JHandroid/support/v4/widget/SearchViewCompat$OnQueryTextListenerCompat.classPKjH^׀0TKandroid/support/v4/widget/ViewDragHelper$2.classPKojHzp`[t<2Mandroid/support/v4/widget/PopupWindowCompatGingerbread.classPKojH+ 9Pandroid/support/v4/widget/ScrollerCompatGingerbread.classPKjH'f:$Vandroid/support/v4/widget/MaterialProgressDrawable$3.classPKjHc ( ?Xandroid/support/v4/widget/CursorAdapter$MyDataSetObserver.classPKsjHׂ9i[android/support/v4/widget/SearchViewCompatHoneycomb.classPKjH&JRaandroid/support/v4/widget/SlidingPaneLayout$SimplePanelSlideListener.classPKjH`qFAucandroid/support/v4/widget/DrawerLayout$SimpleDrawerListener.classPKjHA?iP8eandroid/support/v4/widget/ListViewAutoScrollHelper.classPKjHYr?4|iandroid/support/v4/widget/SwipeRefreshLayout$6.classPKxjHHR@mandroid/support/v4/widget/SearchViewCompatIcs$MySearchView.classPKjHD/#oandroid/support/v4/widget/CursorAdapter$1.classPKjH$˜ L.pandroid/support/v4/widget/ScrollerCompat$ScrollerCompatImplGingerbread.classPKjHEglhOuandroid/support/v4/widget/ExploreByTouchHelper$ExploreByTouchNodeProvider.classPKjHr+eKxandroid/support/v4/widget/SimpleCursorAdapter$CursorToStringConverter.classPKjHkVWPyandroid/support/v4/widget/SearchViewCompat$SearchViewCompatHoneycombImpl$1.classPKxjH<3|android/support/v4/widget/SearchViewCompatIcs.classPKjH 1~android/support/v4/widget/PopupWindowCompat.classPKjHޢ9TF4android/support/v4/widget/TextViewCompat$JbMr1TextViewCompatImpl.classPKjH<<Oandroid/support/v4/widget/CompoundButtonCompat$LollipopCompoundButtonImpl.classPKjHl߲Fandroid/support/v4/widget/SearchViewCompat$OnCloseListenerCompat.classPKjHmJLۊandroid/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImplBase.classPKjH+5I=\android/support/v4/widget/NestedScrollView$SavedState$1.classPK{jHif93android/support/v4/widget/TextViewCompatJbMr2.classPKjHo 4android/support/v4/widget/CompoundButtonCompat.classPKjHr%Fandroid/support/v4/widget/TextViewCompat$Api23TextViewCompatImpl.classPKjHHMandroid/support/v4/widget/ListPopupWindowCompat$BaseListPopupWindowImpl.classPKjHE ? android/support/v4/widget/CursorFilter$CursorFilterClient.classPKjH D94android/support/v4/widget/SwipeRefreshLayout$8.classPKjH LA)android/support/v4/widget/PopupWindowCompat$PopupWindowImpl.classPKxjH r3android/support/v4/widget/EdgeEffectCompatIcs.classPKSjHƕ{3android/support/v4/widget/ListViewCompatDonut.classPKjHg|Ȟi;android/support/v4/widget/ContentLoadingProgressBar$2.classPKjHﳅ Gandroid/support/v4/widget/DrawerLayout$ChildAccessibilityDelegate.classPKjH^kDandroid/support/v4/widget/ScrollerCompat$ScrollerCompatImplIcs.classPKjHy?"? Eeandroid/support/v4/widget/TextViewCompat$BaseTextViewCompatImpl.classPKjH *7android/support/v4/widget/ViewDragHelper$Callback.classPKjHtϐ*Gandroid/support/v4/widget/EdgeEffectCompat$EdgeEffectLollipopImpl.classPKjH0E>android/support/v4/widget/SimpleCursorAdapter$ViewBinder.classPKjH9fp 9ҷandroid/support/v4/widget/DrawerLayout$LayoutParams.classPKjHOandroid/support/v4/widget/ListPopupWindowCompat$KitKatListPopupWindowImpl.classPKjHVJ .android/support/v4/widget/TextViewCompat.classPKjHԙS5android/support/v4/widget/ResourceCursorAdapter.classPKjHNK.AKandroid/support/v4/widget/PopupMenuCompat$BasePopupMenuImpl.classPKjHxDMandroid/support/v4/widget/SlidingPaneLayout$DragHelperCallback.classPKyjHi@=X0android/support/v4/widget/TextViewCompatJb.classPKjHu({F-android/support/v4/widget/SlidingPaneLayout$DisableLayerRunnable.classPKjHP~. 0android/support/v4/widget/EdgeEffectCompat.classPKjHYMy5android/support/v4/widget/ListPopupWindowCompat.classPKjHL _Landroid/support/v4/widget/PopupWindowCompat$GingerbreadPopupWindowImpl.classPKjHIZ3android/support/v4/widget/SlidingPaneLayout$1.classPKjH5I2android/support/v4/widget/DrawerLayout$State.classPKjH+_2,android/support/v4/widget/SwipeRefreshLayout.classPKjH7&iR0android/support/v4/widget/ViewDragHelper$1.classPKjH?`j :android/support/v4/widget/MaterialProgressDrawable$1.classPKjHY_R Pandroid/support/v4/widget/SearchViewCompat$SearchViewCompatHoneycombImpl$2.classPKjH_9@,=android/support/v4/widget/DrawerLayout.classPK}jH#D)4Xandroid/support/v4/widget/ListViewCompatKitKat.classPKjHoEޖ40Zandroid/support/v4/widget/SwipeRefreshLayout$7.classPKjHox0V\android/support/v4/widget/SearchViewCompat.classPKjH>u]LUbandroid/support/v4/widget/CompoundButtonCompat$Api23CompoundButtonImpl.classPKjHKGDdandroid/support/v4/widget/PopupWindowCompat$KitKatPopupWindowImpl.classPKjH(2FNufandroid/support/v4/widget/SearchViewCompat$SearchViewCompatHoneycombImpl.classPKsjH_X}GUkandroid/support/v4/widget/SearchViewCompatHoneycomb$OnCloseListenerCompatBridge.classPKjH"} AB8mandroid/support/v4/widget/DrawerLayout$AccessibilityDelegate.classPKjH= lv<%wandroid/support/v4/widget/CursorAdapter$ChangeObserver.classPKjH]zBnyandroid/support/v4/widget/EdgeEffectCompat$EdgeEffectIcsImpl.classPKjHB?z9|android/support/v4/widget/CompoundButtonCompatApi23.classPKSjH"{iY6~android/support/v4/widget/TintableCompoundButton.classPKjH858# <Bandroid/support/v4/widget/SlidingPaneLayout$SavedState.classPKjHvYKbMσandroid/support/v4/widget/CompoundButtonCompat$CompoundButtonCompatImpl.classPKjHVIandroid/support/v4/widget/ListPopupWindowCompat$ListPopupWindowImpl.classPKjHgyYe 7android/support/v4/widget/DrawerLayoutCompatApi21.classPKjHxt 9Ќandroid/support/v4/widget/ContentLoadingProgressBar.classPKjH2,R?android/support/v4/widget/EdgeEffectCompat$EdgeEffectImpl.classPKjH'n}Candroid/support/v4/widget/DrawerLayout$DrawerLayoutCompatImpl.classPKjH;YGandroid/support/v4/widget/NestedScrollView$OnScrollChangeListener.classPKjHDfl/android/support/v4/widget/PopupMenuCompat.classPKjH[Eandroid/support/v4/widget/PopupWindowCompat$BasePopupWindowImpl.classPKjHvL>{android/support/v4/widget/SlidingPaneLayout$SavedState$1.classPKjHQ7-93android/support/v4/widget/DrawerLayout$SavedState$1.classPKjHOՁ 0ѡandroid/support/v4/widget/SwipeProgressBar.classPKjHFk,android/support/v4/widget/CursorFilter.classPKjH>6android/support/v4/widget/PopupWindowCompatApi23.classPKjHc;ڲandroid/support/v4/widget/DrawerLayout$DrawerListener.classPKjHVfC7android/support/v4/widget/PopupMenuCompat$KitKatPopupMenuImpl.classPKjH},4Jandroid/support/v4/widget/ExploreByTouchHelper.classPKsjH?zo;android/support/v4/widget/SearchViewCompatHoneycomb$2.classPKjH@Rs| Iandroid/support/v4/widget/SearchViewCompat$SearchViewCompatStubImpl.classPKjHsHandroid/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImpl.classPK}jH0V@v5Sandroid/support/v4/widget/PopupMenuCompatKitKat.classPKjHhMH,android/support/v4/widget/DrawerLayout$DrawerLayoutCompatImplApi21.classPKjH@=\Fiandroid/support/v4/widget/TextViewCompat$JbMr2TextViewCompatImpl.classPKjH/ android/support/v4/widget/CircleImageView.classPKsjHGH; android/support/v4/widget/SearchViewCompatHoneycomb$1.classPKjHd4tJ:* android/support/v4/widget/CircleImageView$OvalShadow.classPKjH3A android/support/v4/widget/ScrollerCompat$ScrollerCompatImpl.classPKjH 6C android/support/v4/widget/ExploreByTouchHelper$1.classPKjHGw -\ android/support/v4/widget/CursorAdapter.classPKjHݹM A android/support/v4/widget/TextViewCompat$TextViewCompatImpl.classPKjH"c} m= android/support/v4/widget/MaterialProgressDrawable$Ring.classPKjH7ó 0= android/support/v4/widget/DrawerLayout$ViewDragCallback.classPKjH^ F android/support/v4/widget/NestedScrollView$AccessibilityDelegate.classPKjH]KJH,+T. android/support/v4/widget/ViewDragHelper.classPKjH$nQ&0S android/support/v4/widget/AutoScrollHelper.classPKjHi 3 android/support/v4/widget/SimpleCursorAdapter.classPKjH ;> android/support/v4/widget/ContentLoadingProgressBar$1.classPKjH[T/0z android/support/v4/widget/DrawerLayoutImpl.classPKjH>jMr android/support/v4/widget/CompoundButtonCompat$BaseCompoundButtonCompat.classPK jHW android/support/v4/database/PKjHԱ5 android/support/v4/database/DatabaseUtilsCompat.classPK jH android/support/v4/view/PKjH_G@ android/support/v4/view/ViewCompat$EclairMr1ViewCompatImpl.classPKjHZd֫2 android/support/v4/view/LayoutInflaterCompat.classPKjH:VTy< android/support/v4/view/MenuItemCompat$MenuVersionImpl.classPKjHp* V android/support/v4/view/AccessibilityDelegateCompat$AccessibilityDelegateIcsImpl.classPKjHe2#D9 android/support/v4/view/ViewCompat$ResolvedLayoutDirectionMode.classPKyjHTg android/support/v4/view/AccessibilityDelegateCompatJellyBean$AccessibilityDelegateBridgeJellyBean.classPKjH"# G! android/support/v4/view/ViewParentCompat$ViewParentCompatStubImpl.classPKjHU|)t' android/support/v4/view/ViewPager$1.classPKjH1lq 2) android/support/v4/view/ViewPager$SavedState.classPKsjH$Eoe<. android/support/v4/view/VelocityTrackerCompatHoneycomb.classPKjH7&h4'0 android/support/v4/view/ViewPager.classPKjHrAJ( android/support/v4/view/ViewCompat.classPKjHu6Aw/ android/support/v4/view/InputDeviceCompat.classPKjHvdK\ android/support/v4/view/ViewPropertyAnimatorCompat$JBMr2ViewPropertyAnimatorCompatImpl.classPKjH6Qd] -ƴ android/support/v4/view/ViewGroupCompat.classPKxjH4nH android/support/v4/view/MenuItemCompatIcs$SupportActionExpandProxy.classPKjH f7[غ android/support/v4/view/ViewConfigurationCompat$HoneycombViewConfigurationVersionImpl.classPKjHvJ]WǼ android/support/v4/view/VelocityTrackerCompat$HoneycombVelocityTrackerVersionImpl.classPKjHLZ5 android/support/v4/view/ViewPager$PagerObserver.classPKjH : \* android/support/v4/view/AccessibilityDelegateCompat$AccessibilityDelegateJellyBeanImpl.classPKjHHK-B android/support/v4/view/ViewCompat$MarshmallowViewCompatImpl.classPK}jHv4U android/support/v4/view/ViewParentCompatKitKat.classPKjH1K+8 android/support/v4/view/GravityCompat.classPKjH(G0i*k android/support/v4/view/PagerAdapter.classPKjHX ? android/support/v4/view/ViewPager$MyAccessibilityDelegate.classPKjH0m9 android/support/v4/view/ViewCompat$ScrollIndicators.classPKyjHyy Dy android/support/v4/view/AccessibilityDelegateCompatJellyBean$1.classPKjH2 android/support/v4/view/ViewCompat$LayerType.classPKxjHmV1/ android/support/v4/view/MenuItemCompatIcs.classPKsjH?P0/ android/support/v4/view/ViewGroupCompatHC.classPKjH2E android/support/v4/view/NestedScrollingChild.classPKjH3s`? android/support/v4/view/ViewPager$OnAdapterChangeListener.classPKjH(x' 9 android/support/v4/view/ViewCompat$HCViewCompatImpl.classPKjH pNZ android/support/v4/view/VelocityTrackerCompat$VelocityTrackerVersionImpl.classPK{jH'}D android/support/v4/view/ViewPropertyAnimatorCompatJellybeanMr2.classPKzjHR 4 android/support/v4/view/ViewCompatJellybeanMr1.classPKjH8*c android/support/v4/view/ViewPropertyAnimatorCompat$BaseViewPropertyAnimatorCompatImpl$Starter.classPKjHBƌ)1 android/support/v4/view/ViewPager$3.classPKjHyNj h android/support/v4/view/ViewPropertyAnimatorCompat$ICSViewPropertyAnimatorCompatImpl$MyVpaListener.classPKsjHMa_*5 android/support/v4/view/ViewCompatHC.classPKsjHh 4 android/support/v4/view/LayoutInflaterCompatHC.classPKjHR|4+ android/support/v4/view/ViewPager$SavedState$1.classPKjHyp$Y android/support/v4/view/GestureDetectorCompat$GestureDetectorCompatImplJellybeanMr2.classPKSjH҇Nw6< android/support/v4/view/LayoutInflaterCompatBase.classPKjHU android/support/v4/view/ScaleGestureDetectorCompat$BaseScaleGestureDetectorImpl.classPKjHq6x-N android/support/v4/view/PagerTabStrip$2.classPKjH ;A android/support/v4/view/PagerTitleStrip$PagerTitleStripImpl.classPKSjH<&3 android/support/v4/view/LayoutInflaterFactory.classPKjHi9 android/support/v4/view/NestedScrollingParentHelper.classPKjH-6?X android/support/v4/view/ViewCompat$LollipopViewCompatImpl.classPKjH`<" android/support/v4/view/ViewCompat$JbMr1ViewCompatImpl.classPKxjHt"'t 16& android/support/v4/view/ViewParentCompatICS.classPKjH $< ( android/support/v4/view/ViewCompat$JbMr2ViewCompatImpl.classPKjHgdE ,)* android/support/v4/view/KeyEventCompat.classPKjHW<. android/support/v4/view/ViewCompat$LayoutDirectionMode.classPKCjHRUz1/ android/support/v4/view/ViewCompatEclairMr1.classPKjH<V4 android/support/v4/view/ViewConfigurationCompat$BaseViewConfigurationVersionImpl.classPKmjH$T^=C:e6 android/support/v4/view/ViewConfigurationCompatFroyo.classPKsjH 5 8 android/support/v4/view/KeyEventCompatHoneycomb.classPKyjHgz\pB9 android/support/v4/view/AccessibilityDelegateCompatJellyBean.classPKjH^mŞI< android/support/v4/view/KeyEventCompat$HoneycombKeyEventVersionImpl.classPKxjH\+!]+? android/support/v4/view/ViewCompatICS.classPKjH QdB android/support/v4/view/ScaleGestureDetectorCompat$ScaleGestureDetectorImpl.classPKjH #PG3C android/support/v4/view/VelocityTrackerCompat.classPKjH<SsF android/support/v4/view/MarginLayoutParamsCompat$MarginLayoutParamsCompatImpl.classPKjHq +0H android/support/v4/view/PagerTabStrip.classPKjHcFV android/support/v4/view/ViewParentCompat$ViewParentCompatICSImpl.classPK jH&*X android/support/v4/view/accessibility/PK}jH]Q`nX android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat$CollectionItemInfo.classPKxjHp  IZ android/support/v4/view/accessibility/AccessibilityManagerCompatIcs.classPKjHG3zIei^ android/support/v4/view/accessibility/AccessibilityWindowInfoCompat$AccessibilityWindowInfoImpl.classPKjH?  qv` android/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderKitKatImpl$1.classPKzjH~*T(,gSe android/support/v4/view/accessibility/AccessibilityNodeInfoCompatJellybeanMr1.classPKjHr) i]h android/support/v4/view/accessibility/AccessibilityWindowInfoCompat$AccessibilityWindowInfoStubImpl.classPKjHowfk android/support/v4/view/accessibility/AccessibilityManagerCompat$AccessibilityManagerVersionImpl.classPKjHiح_Wn android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$RangeInfoCompat.classPKyjH"pVPq android/support/v4/view/accessibility/AccessibilityNodeProviderCompatJellyBean$1.classPKyjH~Y Pt android/support/v4/view/accessibility/AccessibilityNodeInfoCompatJellyBean.classPKjHHrx android/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderJellyBeanImpl.classPKjH> d/{ android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoIcsImpl.classPKjHQ Rb a͂ android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityActionCompat.classPKjH,HTL android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.classPKjH]>hR bp android/support/v4/view/accessibility/AccessibilityManagerCompat$AccessibilityManagerIcsImpl.classPKjH'b jh android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoJellybeanImpl.classPKjHb&D– android/support/v4/view/accessibility/AccessibilityEventCompat.classPKjH(Ta android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoImpl.classPK}jH]CW& android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat$RangeInfo.classPKjHthR`v android/support/v4/view/accessibility/AccessibilityRecordCompat$AccessibilityRecordIcsImpl.classPKxjHPe5Hl android/support/v4/view/accessibility/AccessibilityRecordCompatIcs.classPKjH6XZ{m android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoJellybeanMr1Impl.classPK}jH,Gm android/support/v4/view/accessibility/AccessibilityNodeProviderCompatKitKat$AccessibilityNodeInfoBridge.classPKjHwd}K_ android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21$CollectionItemInfo.classPKjH{ rs m android/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderStubImpl.classPKjH™ N android/support/v4/view/accessibility/AccessibilityWindowInfoCompatApi21.classPKjHH4I android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$1.classPKjHf android/support/v4/view/accessibility/AccessibilityRecordCompat$AccessibilityRecordJellyBeanImpl.classPKjHojE ` android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$CollectionItemInfoCompat.classPK}jHJ S android/support/v4/view/accessibility/AccessibilityNodeProviderCompatKitKat$1.classPKjH0Jch android/support/v4/view/accessibility/AccessibilityRecordCompat$AccessibilityRecordIcsMr1Impl.classPKyjH'wN android/support/v4/view/accessibility/AccessibilityRecordCompatJellyBean.classPKjHNɳ %7e android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoStubImpl.classPKjHi& android/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderImpl.classPKjHC%I android/support/v4/view/accessibility/AccessibilityWindowInfoCompat.classPKjHMzqo android/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderKitKatImpl.classPKjHp., c: android/support/v4/view/accessibility/AccessibilityManagerCompat$AccessibilityManagerStubImpl.classPKxjHM|Dp android/support/v4/view/accessibility/AccessibilityManagerCompatIcs$AccessibilityStateChangeListenerBridge.classPKjHuim android/support/v4/view/accessibility/AccessibilityManagerCompat$AccessibilityStateChangeListenerCompat.classPKjHP4mh android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoJellybeanMr2Impl.classPKjHB))gL android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi22.classPKjHgܿCg android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoKitKatImpl.classPKjHxooA t android/support/v4/view/accessibility/AccessibilityNodeProviderCompat$AccessibilityNodeProviderJellyBeanImpl$1.classPKyjHԔT android/support/v4/view/accessibility/AccessibilityNodeProviderCompatJellyBean.classPKjH~h$\ j android/support/v4/view/accessibility/AccessibilityWindowInfoCompat$AccessibilityWindowInfoApi21Impl.classPKjH7̷ E android/support/v4/view/accessibility/AccessibilityRecordCompat.classPKjHےQlf/ android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoApi22Impl.classPK}jH AN|Q android/support/v4/view/accessibility/AccessibilityNodeProviderCompatKitKat.classPKjH~:`G android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.classPKjH0uya4 android/support/v4/view/accessibility/AccessibilityEventCompat$AccessibilityEventKitKatImpl.classPK}jH-t\6 android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat$CollectionInfo.classPKjH<~i9\79 android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$CollectionInfoCompat.classPKxjH-+O;DJ< android/support/v4/view/accessibility/AccessibilityNodeInfoCompatIcs.classPKjH2]D android/support/v4/view/accessibility/AccessibilityRecordCompat$AccessibilityRecordImpl.classPKjHµg\aH android/support/v4/view/accessibility/AccessibilityRecordCompat$AccessibilityRecordStubImpl.classPKjH"_M android/support/v4/view/accessibility/AccessibilityEventCompat$AccessibilityEventStubImpl.classPKjH3 KP android/support/v4/view/accessibility/AccessibilityNodeProviderCompat.classPKjH6i FT android/support/v4/view/accessibility/AccessibilityManagerCompat.classPKjH Q=^X android/support/v4/view/accessibility/AccessibilityEventCompat$AccessibilityEventIcsImpl.classPKjHa2cdm[ android/support/v4/view/accessibility/AccessibilityManagerCompat$AccessibilityManagerIcsImpl$1.classPK{jH#S̆S1^ android/support/v4/view/accessibility/AccessibilityNodeInfoCompatJellybeanMr2.classPK}jH #KJa android/support/v4/view/accessibility/AccessibilityEventCompatKitKat.classPKxjHn½LKc android/support/v4/view/accessibility/AccessibilityManagerCompatIcs$1.classPKjHW8 ff android/support/v4/view/accessibility/AccessibilityNodeInfoCompat$AccessibilityNodeInfoApi21Impl.classPKjH# > bk android/support/v4/view/accessibility/AccessibilityEventCompat$AccessibilityEventVersionImpl.classPK}jH;&M6m android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.classPKyjHx<*Kr android/support/v4/view/accessibility/AccessibilityRecordCompatIcsMr1.classPKxjHMXU PG u android/support/v4/view/accessibility/AccessibilityEventCompatIcs.classPKyjH+=pw android/support/v4/view/accessibility/AccessibilityNodeProviderCompatJellyBean$AccessibilityNodeInfoBridge.classPKjHVھ:Kyy android/support/v4/view/accessibility/AccessibilityWindowInfoCompat$1.classPKjH@z android/support/v4/view/ViewCompat$AccessibilityLiveRegion.classPK}jH~jĶe.| android/support/v4/view/ViewCompatKitKat.classPKjHݯ'-.~ android/support/v4/view/PagerTabStrip$1.classPKjH2 &] android/support/v4/view/ViewPropertyAnimatorCompat$KitKatViewPropertyAnimatorCompatImpl.classPKjHQB android/support/v4/view/ViewCompat$ImportantForAccessibility.classPKjHR'R3 android/support/v4/view/ViewConfigurationCompat$ViewConfigurationVersionImpl.classPKjHm3 android/support/v4/view/ViewCompat$OverScroll.classPK2jH&W5 android/support/v4/view/MotionEventCompatEclair.classPKjH5" android/support/v4/view/ViewConfigurationCompat.classPKjHYV;D W8 android/support/v4/view/AccessibilityDelegateCompat$AccessibilityDelegateStubImpl.classPKjH>W android/support/v4/view/ViewPropertyAnimatorCompat$ViewPropertyAnimatorCompatImpl.classPKjH 7 `Ē android/support/v4/view/GestureDetectorCompat$GestureDetectorCompatImplBase$GestureHandler.classPKjH LO:Z android/support/v4/view/ViewPropertyAnimatorCompat$1.classPKjH\/|^y android/support/v4/view/AccessibilityDelegateCompat$AccessibilityDelegateJellyBeanImpl$1.classPKjHڦM android/support/v4/view/ViewGroupCompat$ViewGroupCompatJellybeanMR2Impl.classPKjHrUaC android/support/v4/view/MenuItemCompat$OnActionExpandListener.classPKjHF2 android/support/v4/view/ViewCompatLollipop$1.classPKjHk,D android/support/v4/view/ActionProvider$SubUiVisibilityListener.classPKzjHNBѤ android/support/v4/view/MarginLayoutParamsCompatJellybeanMr1.classPKjH k~!= android/support/v4/view/ViewCompat$KitKatViewCompatImpl.classPK2jHH2nL2 android/support/v4/view/KeyEventCompatEclair.classPKjHNA+?Ǭ android/support/v4/view/ActionProvider$VisibilityListener.classPKjH dN@ android/support/v4/view/MenuItemCompat$BaseMenuVersionImpl.classPKjHYIΰ android/support/v4/view/GravityCompat$GravityCompatImplJellybeanMr1.classPKxjHel@ android/support/v4/view/ViewPropertyAnimatorUpdateListener.classPKjHaܹ7 android/support/v4/view/ViewPager$PageTransformer.classPK}jH64: android/support/v4/view/ViewPropertyAnimatorCompatKK.classPKjH6fkE android/support/v4/view/PagerTitleStrip$PagerTitleStripImplBase.classPKxjH5W<7; android/support/v4/view/ViewPropertyAnimatorCompatICS.classPKjH "L̿ android/support/v4/view/MotionEventCompat$EclairMotionEventVersionImpl.classPKjHIA< android/support/v4/view/ViewPropertyAnimatorListenerAdapter.classPKyjH$wE4< android/support/v4/view/ViewPropertyAnimatorCompatJB$1.classPKSjHISY4v android/support/v4/view/TintableBackgroundView.classPKjHW.& 61 android/support/v4/view/MarginLayoutParamsCompat.classPKjHW=$Y( android/support/v4/view/ViewPropertyAnimatorCompat$JBViewPropertyAnimatorCompatImpl.classPKxjHVX android/support/v4/view/AccessibilityDelegateCompatIcs$AccessibilityDelegateBridge.classPK jH" android/support/v4/view/animation/PKSjHļ}B android/support/v4/view/animation/PathInterpolatorCompatBase.classPKjHK 3 AW android/support/v4/view/animation/FastOutSlowInInterpolator.classPKjHxD CT android/support/v4/view/animation/FastOutLinearInInterpolator.classPKjH%l'i> android/support/v4/view/animation/PathInterpolatorCompat.classPKjH%5D?e android/support/v4/view/animation/LookupTableInterpolator.classPKjH|oVv9 C android/support/v4/view/animation/LinearOutSlowInInterpolator.classPKjHYC android/support/v4/view/animation/PathInterpolatorCompatApi21.classPKSjH^X0 =J android/support/v4/view/animation/PathInterpolatorDonut.classPKjH ! : android/support/v4/view/PagerTitleStrip$PageListener.classPKjHE android/support/v4/view/ViewGroupCompat$ViewGroupCompatStubImpl.classPKyjH?  * android/support/v4/view/ViewCompatJB.classPKjHc91 android/support/v4/view/AccessibilityDelegateCompat.classPKjH70kM android/support/v4/view/GestureDetectorCompat$GestureDetectorCompatImpl.classPKsjHu5R android/support/v4/view/MenuItemCompatHoneycomb.classPKjHlG$A android/support/v4/view/MenuItemCompat$IcsMenuVersionImpl$1.classPKjH㑩(8O android/support/v4/view/ScaleGestureDetectorCompat.classPKjH t_=^ android/support/v4/view/ViewCompat$ICSMr1ViewCompatImpl.classPKjHBgn2/( android/support/v4/view/MotionEventCompat.classPKjHWBxO android/support/v4/view/LayoutInflaterCompat$LayoutInflaterCompatImplBase.classPKjH*pW- android/support/v4/view/MarginLayoutParamsCompat$MarginLayoutParamsCompatImplBase.classPKjHkZF"" android/support/v4/view/KeyEventCompat$EclairKeyEventVersionImpl.classPKjH$hpz:$ android/support/v4/view/LayoutInflaterCompatLollipop.classPKSjH!M,F' android/support/v4/view/ViewCompatBase.classPKjHGM4@E. android/support/v4/view/ViewPropertyAnimatorCompatLollipop.classPKxjH 8=o0 android/support/v4/view/ViewPropertyAnimatorCompatICS$1.classPKjH@ Rb8)2 android/support/v4/view/ViewPager$2.classPKjHnVz*F4 android/support/v4/view/MotionEventCompat$MotionEventVersionImpl.classPKjH4*?6 android/support/v4/view/WindowCompat.classPKjH>hR7 android/support/v4/view/MotionEventCompat$HoneycombMr1MotionEventVersionImpl.classPKjHs2B: android/support/v4/view/ViewPager$SimpleOnPageChangeListener.classPKjH" ,2< android/support/v4/view/ActionProvider.classPKjHS`:A android/support/v4/view/ScaleGestureDetectorCompat$1.classPKjHx/B android/support/v4/view/PagerTitleStrip$1.classPKjHt\k XC android/support/v4/view/AccessibilityDelegateCompat$AccessibilityDelegateIcsImpl$1.classPKxjHIQF0G android/support/v4/view/PagerTitleStripIcs.classPK{jHw|4I android/support/v4/view/ViewCompatJellybeanMr2.classPKjH#>xCK android/support/v4/view/ViewParentCompat$ViewParentCompatImpl.classPKjH8']0KM android/support/v4/view/ViewParentCompat$ViewParentCompatLollipopImpl.classPKjH (8eQ android/support/v4/view/ViewPropertyAnimatorCompat.classPKyjHd*~~:[ android/support/v4/view/ViewPropertyAnimatorCompatJB.classPKxjH Y :^ android/support/v4/view/ViewPropertyAnimatorListener.classPKjH F0_ android/support/v4/view/ViewPager$ItemInfo.classPKjHZػ{Dpa android/support/v4/view/PagerTitleStrip$PagerTitleStripImplIcs.classPKjHkS]c android/support/v4/view/AccessibilityDelegateCompat$AccessibilityDelegateImpl.classPK}jHmk <e android/support/v4/view/ViewPropertyAnimatorCompatKK$1.classPKjHuMk3Qh android/support/v4/view/MotionEventCompat$GingerbreadMotionEventVersionImpl.classPKSjH@ qbEj android/support/v4/view/LayoutInflaterCompatBase$FactoryWrapper.classPKjHmI<>C4m android/support/v4/view/ViewGroupCompat$ViewGroupCompatHCImpl.classPKjHz1Eew [)o android/support/v4/view/ViewPropertyAnimatorCompat$BaseViewPropertyAnimatorCompatImpl.classPKjHi_,w8?Sx android/support/v4/view/MenuItemCompat$IcsMenuVersionImpl.classPKxjHemM7{ android/support/v4/view/MenuItemCompatIcs$OnActionExpandListenerWrapper.classPK{jHN"O|m9} android/support/v4/view/ViewGroupCompatJellybeanMR2.classPKjH V70} android/support/v4/view/ViewCompatLollipop.classPKjHc5_ android/support/v4/view/ViewPropertyAnimatorCompat$LollipopViewPropertyAnimatorCompatImpl.classPKjHnTA android/support/v4/view/ViewGroupCompat$ViewGroupCompatImpl.classPKzjH}U7L android/support/v4/view/GravityCompatJellybeanMr1.classPKjHBuj} 5 android/support/v4/view/WindowInsetsCompatApi21.classPKjH8`]ӑ android/support/v4/view/ScaleGestureDetectorCompat$ScaleGestureDetectorCompatKitKatImpl.classPKjH-R<T android/support/v4/view/ViewPager$OnPageChangeListener.classPKtjHw; android/support/v4/view/MotionEventCompatHoneycombMr1.classPKojH!OU.!: android/support/v4/view/MotionEventCompatGingerbread.classPKjH[ J android/support/v4/view/MotionEventCompat$BaseMotionEventVersionImpl.classPKjH0NK+> android/support/v4/view/ViewPager$ViewPositionComparator.classPKjHۛVkg(w android/support/v4/view/MenuCompat.classPKjHө[98 android/support/v4/view/ViewCompat$GBViewCompatImpl.classPKojH>e3H android/support/v4/view/ViewCompatGingerbread.classPKxjH:$ > android/support/v4/view/AccessibilityDelegateCompatIcs$1.classPKjH N: android/support/v4/view/ViewCompat$ICSViewCompatImpl.classPKjHaw+{N android/support/v4/view/LayoutInflaterCompat$LayoutInflaterCompatImplV21.classPKjHĺY F; android/support/v4/view/ViewCompat$BaseViewCompatImpl.classPKjHZrU android/support/v4/view/ViewConfigurationCompat$IcsViewConfigurationVersionImpl.classPKjH64 android/support/v4/view/ViewPager$LayoutParams.classPKjH^I android/support/v4/view/ViewParentCompat$ViewParentCompatKitKatImpl.classPKjHzw%)2- android/support/v4/view/PagerTitleStrip.classPKjH3 android/support/v4/view/ViewCompatMarshmallow.classPKxjHxps0 android/support/v4/view/ViewGroupCompatIcs.classPKjHֹX android/support/v4/view/MarginLayoutParamsCompat$MarginLayoutParamsCompatImplJbMr1.classPKjHUiK android/support/v4/view/LayoutInflaterCompat$LayoutInflaterCompatImpl.classPKjHO8# android/support/v4/view/NestedScrollingChildHelper.classPKjH4A android/support/v4/view/GravityCompat$GravityCompatImplBase.classPKxjH% < android/support/v4/view/AccessibilityDelegateCompatIcs.classPKjH 9 android/support/v4/view/ViewCompat$JBViewCompatImpl.classPKjH,mR 6android/support/v4/view/ViewParentCompatLollipop.classPKjHv> 3android/support/v4/view/GestureDetectorCompat.classPKjHc= android/support/v4/view/GravityCompat$GravityCompatImpl.classPKjHR android/support/v4/view/VelocityTrackerCompat$BaseVelocityTrackerVersionImpl.classPKjHJV.q+android/support/v4/view/ScrollingView.classPKjH!D6android/support/v4/view/ViewGroupCompat$ViewGroupCompatIcsImpl.classPKjHldvuW@android/support/v4/view/ViewConfigurationCompat$FroyoViewConfigurationVersionImpl.classPKjHP# .;android/support/v4/view/ViewParentCompat.classPKjH@L&Qandroid/support/v4/view/GestureDetectorCompat$GestureDetectorCompatImplBase.classPKSjH"Z0,android/support/v4/view/WindowInsetsCompat.classPKjHӗ^zN/android/support/v4/view/LayoutInflaterCompat$LayoutInflaterCompatImplV11.classPKjHR;DT[Z1android/support/v4/view/ViewPropertyAnimatorCompat$ICSViewPropertyAnimatorCompatImpl.classPKjH r_3l8android/support/v4/view/NestedScrollingParent.classPKsjHE9android/support/v4/view/LayoutInflaterCompatHC$FactoryWrapperHC.classPKjH+7w<android/support/v4/view/ViewCompat$ViewCompatImpl.classPKxjHb2kI988Candroid/support/v4/view/ViewConfigurationCompatICS.classPKxjHe}KEandroid/support/v4/view/PagerTitleStripIcs$SingleLineAllCapsTransform.classPKyjHC!;*.Handroid/support/v4/view/ViewCompatICSMr1.classPKjHO9IPJandroid/support/v4/view/ViewGroupCompat$ViewGroupCompatLollipopImpl.classPKjH(#j@Landroid/support/v4/view/KeyEventCompat$KeyEventVersionImpl.classPKSjH -K-9xNandroid/support/v4/view/OnApplyWindowInsetsListener.classPKjH~/ )Oandroid/support/v4/view/ViewPager$4.classPK}jHuS>Sandroid/support/v4/view/ScaleGestureDetectorCompatKitKat.classPKjHEUandroid/support/v4/view/MenuItemCompat$HoneycombMenuVersionImpl.classPKjH# DYandroid/support/v4/view/KeyEventCompat$BaseKeyEventVersionImpl.classPKjH^:-^android/support/v4/view/ViewPager$Decor.classPKjHs*5_android/support/v4/view/ViewGroupCompatLollipop.classPKjHE&8;:,aandroid/support/v4/view/MenuItemCompat.classPK jHgandroid/support/v4/util/PKjHn.#gandroid/support/v4/util/Pools.classPKjH_vѽ 8Jiandroid/support/v4/util/MapCollections$MapIterator.classPKjH2=oandroid/support/v4/util/MapCollections$ValuesCollection.classPKjH=:o$,vandroid/support/v4/util/SimpleArrayMap.classPKjHh\tx-(android/support/v4/util/AtomicFile.classPKjH-[ &android/support/v4/util/LruCache.classPKjH=4android/support/v4/util/Pools$SynchronizedPool.classPKjH㦗6'android/support/v4/util/LogWriter.classPKjH9z x-ˢandroid/support/v4/util/LongSparseArray.classPKjHL|]'android/support/v4/util/TimeUtils.classPKjH[K"mandroid/support/v4/util/Pair.classPKjH ,android/support/v4/util/MapCollections.classPKjHK%'^+android/support/v4/util/CircularArray.classPKjHK, (\android/support/v4/util/ArrayMap$1.classPKjH=t(bandroid/support/v4/util/Pools$Pool.classPKjHzd.android/support/v4/util/ContainerHelpers.classPKjH=/޽3android/support/v4/util/MapCollections$KeySet.classPKjH/yf5android/support/v4/util/MapCollections$EntrySet.classPKjH.android/support/v4/util/Pools$SimplePool.classPKjHJ /android/support/v4/util/SparseArrayCompat.classPKjHX^ &android/support/v4/util/ArrayMap.classPKjHa((oandroid/support/v4/util/DebugUtils.classPKjHB .android/support/v4/util/CircularIntArray.classPKjH XS@K:android/support/v4/util/MapCollections$ArrayIterator.classPK jH~android/support/v4/graphics/PKjHv=android/support/v4/graphics/BitmapCompat$BaseBitmapImpl.classPKjH^^P(, android/support/v4/graphics/ColorUtils.classPK jH%Eandroid/support/v4/graphics/drawable/PKjHJmj*Bandroid/support/v4/graphics/drawable/RoundedBitmapDrawable21.classPK|jHa@"#android/support/v4/graphics/drawable/DrawableWrapperKitKat.classPKSjH!P)7:%&android/support/v4/graphics/drawable/DrawableWrapper.classPKjHJ'android/support/v4/graphics/drawable/DrawableCompat$BaseDrawableImpl.classPKjHۧ9,android/support/v4/graphics/drawable/DrawableCompat.classPKSjHǡG @4android/support/v4/graphics/drawable/RoundedBitmapDrawable.classPKjHg BBandroid/support/v4/graphics/drawable/DrawableWrapperLollipop.classPKjH79_Handroid/support/v4/graphics/drawable/DrawableWrapperLollipop$DrawableWrapperStateLollipop.classPKjH h GKandroid/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.classPKSjH<=YOandroid/support/v4/graphics/drawable/DrawableWrapperDonut$DrawableWrapperStateDonut.classPK|jHsa[jQandroid/support/v4/graphics/drawable/DrawableWrapperKitKat$DrawableWrapperStateKitKat.classPKjH9KD@ NSandroid/support/v4/graphics/drawable/DrawableCompat$LollipopDrawableImpl.classPKsjHEeU3CXandroid/support/v4/graphics/drawable/DrawableWrapperHoneycomb.classPKsjH/jBS[android/support/v4/graphics/drawable/DrawableCompatHoneycomb.classPK2jHi?g]android/support/v4/graphics/drawable/DrawableCompatEclair.classPK|jH-pDF?=_android/support/v4/graphics/drawable/DrawableCompatKitKat.classPKjHs Aaandroid/support/v4/graphics/drawable/DrawableCompatLollipop.classPKSjHYB* ?fandroid/support/v4/graphics/drawable/DrawableWrapperDonut.classPKSjHjy =tandroid/support/v4/graphics/drawable/DrawableCompatBase.classPKjH`żOwandroid/support/v4/graphics/drawable/DrawableCompat$HoneycombDrawableImpl.classPK2jH*r[zandroid/support/v4/graphics/drawable/DrawableWrapperEclair$DrawableWrapperStateEclair.classPKjHE LRw|android/support/v4/graphics/drawable/DrawableCompat$JellybeanMr1DrawableImpl.classPKjHAi>~android/support/v4/graphics/drawable/DrawableCompatApi23.classPKSjHJ^_SuTɀandroid/support/v4/graphics/drawable/DrawableWrapperDonut$DrawableWrapperState.classPKsjH?1aandroid/support/v4/graphics/drawable/DrawableWrapperHoneycomb$DrawableWrapperStateHoneycomb.classPKjH ~G android/support/v4/graphics/drawable/DrawableCompat$MDrawableImpl.classPKzjHZ)S ETandroid/support/v4/graphics/drawable/DrawableCompatJellybeanMr1.classPKjH3Acfdandroid/support/v4/graphics/drawable/RoundedBitmapDrawableFactory$DefaultRoundedBitmapDrawable.classPKjHСpFandroid/support/v4/graphics/drawable/DrawableCompat$DrawableImpl.classPKjH <pandroid/support/v4/media/MediaMetadataCompat$BitmapKey.classPKjHjU_2Gػandroid/support/v4/media/MediaBrowserServiceCompat$ServiceHandler.classPKjHM%Nandroid/support/v4/media/MediaBrowserServiceCompat$ServiceCallbacksApi21.classPKjHG ` wMandroid/support/v4/media/MediaBrowserServiceCompatApi23$MediaBrowserServiceAdaptorApi23$ServiceBinderProxyApi23$1.classPKjHNBMandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase.classPK jH!android/support/v4/media/session/PKjHP\android/support/v4/media/session/MediaControllerCompat$MediaControllerImpl.classPK}jHA7xDWandroid/support/v4/media/session/MediaSessionCompatApi19$OnMetadataUpdateListener.classPKjHh2,޲;android/support/v4/media/session/MediaSessionCompat$1.classPKjHRWCZ:android/support/v4/media/session/MediaButtonReceiver.classPKjH*x*:<fandroid/support/v4/media/session/PlaybackStateCompat$1.classPKjHPF>android/support/v4/media/session/MediaSessionCompatApi22.classPKjHx-}Handroid/support/v4/media/session/MediaSessionCompatApi21$QueueItem.classPKjHH߰- ?/android/support/v4/media/session/PlaybackStateCompatApi22.classPKjH3?android/support/v4/media/session/MediaSessionCompat$Token.classPKjHpiSIandroid/support/v4/media/session/PlaybackStateCompat$CustomAction$1.classPKjH-}ţxh Bandroid/support/v4/media/session/MediaSessionCompat$Callback.classPKjHP Um android/support/v4/media/session/MediaControllerCompat$MediaControllerImplApi21.classPKjH׋bAandroid/support/v4/media/session/MediaSessionCompat$Token$1.classPKjH 03S"android/support/v4/media/session/MediaControllerCompatApi23$TransportControls.classPK}jH,5OGgandroid/support/v4/media/session/MediaSessionCompatApi19$Callback.classPKjH~!Nandroid/support/v4/media/session/MediaControllerCompatApi21$PlaybackInfo.classPKjHE&L android/support/v4/media/session/MediaSessionCompatApi23$CallbackProxy.classPKjH0jG[#android/support/v4/media/session/MediaSessionCompatApi21$Callback.classPK{jH^1+G%android/support/v4/media/session/MediaSessionCompatApi18$Callback.classPKjH[.]S&android/support/v4/media/session/MediaControllerCompat$TransportControlsApi21.classPKjH5*;$-android/support/v4/media/session/ParcelableVolumeInfo.classPKjHj O0android/support/v4/media/session/MediaSessionCompat$MediaSessionImplApi21.classPKxjHK d>&:android/support/v4/media/session/MediaSessionCompatApi14.classPKjHegDPDandroid/support/v4/media/session/IMediaControllerCallback$Stub.classPKjH@Landroid/support/v4/media/session/PlaybackStateCompat$State.classPKjH۔ׅ>Nandroid/support/v4/media/session/MediaControllerCompat$1.classPKjH+ O=Oandroid/support/v4/media/session/MediaControllerCompat$Callback$StubApi21.classPK{jH!X>_Randroid/support/v4/media/session/MediaSessionCompatApi18$OnPlaybackPositionUpdateListener.classPKjH2yvLUandroid/support/v4/media/session/MediaSessionCompat$Callback$StubApi23.classPKjH8IKQ6Xandroid/support/v4/media/session/MediaSessionCompat$ResultReceiverWrapper$1.classPKjH&4O[android/support/v4/media/session/MediaControllerCompatApi21$CallbackProxy.classPKjHs>L_^android/support/v4/media/session/MediaSessionCompat$Callback$StubApi21.classPKjHX-Os?dandroid/support/v4/media/session/IMediaControllerCallback.classPKjH:LAgandroid/support/v4/media/session/MediaControllerCompatApi21.classPKjHCĖ %_knandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase$MediaSessionStub.classPKjH !z S{android/support/v4/media/session/MediaControllerCompatApi21$TransportControls.classPK{jHs7B>android/support/v4/media/session/MediaSessionCompatApi18.classPKmjH=@android/support/v4/media/session/MediaSessionCompatApi8.classPKjHA5kz>android/support/v4/media/session/MediaSessionCompatApi23.classPKjHj'_&9zandroid/support/v4/media/session/IMediaSession$Stub.classPKjHykRandroid/support/v4/media/session/MediaControllerCompat$TransportControlsBase.classPKjH4 U"Tandroid/support/v4/media/session/MediaControllerCompat$MediaControllerImplBase.classPKjH:*a'9Nandroid/support/v4/media/session/MediaSessionCompat.classPKjH&G>Vandroid/support/v4/media/session/MediaSessionCompatApi21.classPKjHc5!=Handroid/support/v4/media/session/ParcelableVolumeInfo$1.classPKjH=Z Jandroid/support/v4/media/session/IMediaControllerCallback$Stub$Proxy.classPKjHj&N^android/support/v4/media/session/MediaControllerCompat$TransportControls.classPKjH15ħePandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase$1.classPKjH(\"I%android/support/v4/media/session/MediaControllerCompat$PlaybackInfo.classPKjH1 Candroid/support/v4/media/session/MediaSessionCompat$QueueItem.classPKjH7O6android/support/v4/media/session/MediaSessionCompat$ResultReceiverWrapper.classPKjHʍz 4android/support/v4/media/session/IMediaSession.classPKjHȀPandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase$2.classPKjHC˱Eandroid/support/v4/media/session/MediaControllerCompat$Callback.classPKjHW KvSNandroid/support/v4/media/session/MediaSessionCompat$MediaSessionImplBase.classPKjH() Sandroid/support/v4/media/session/MediaControllerCompat$TransportControlsApi23.classPKjH B.android/support/v4/media/session/PlaybackStateCompat$Builder.classPKjHTandroid/support/v4/media/session/MediaSessionCompatApi19.classPKjH. TZandroid/support/v4/media/session/MediaControllerCompat$Callback$MessageHandler.classPKjH)nRJP`android/support/v4/media/session/MediaSessionCompat$MediaSessionImpl.classPKjH`MLXcandroid/support/v4/media/session/PlaybackStateCompatApi21$CustomAction.classPKjHmogOfandroid/support/v4/media/session/PlaybackStateCompat$CustomAction$Builder.classPKjH@z<jandroid/support/v4/media/session/MediaControllerCompat.classPKjHOK'5?sandroid/support/v4/media/session/IMediaSession$Stub$Proxy.classPKjHt7 Landroid/support/v4/media/session/MediaSessionCompatApi21$CallbackProxy.classPKjHl>PӋandroid/support/v4/media/session/MediaSessionCompat$OnActiveChangeListener.classPKjHi$Gandroid/support/v4/media/session/MediaSessionCompatApi23$Callback.classPKjH ΀ :zandroid/support/v4/media/MediaDescriptionCompatApi21.classPKjH&K1android/support/v4/media/TransportPerformer.classPKjHʞB>android/support/v4/media/MediaDescriptionCompatApi23$Builder.classPKjH8Landroid/support/v4/media/VolumeProviderCompatApi21.classPKjH; uandroid/support/v4/media/MediaBrowserServiceCompatApi23$MediaBrowserServiceAdaptorApi23$ServiceBinderProxyApi23.classPKjHF E -<android/support/v4/media/RatingCompat$1.classPKjH Iܡandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImplApi23.classPKjHݵp<)android/support/v4/media/ParceledListSliceAdapterApi21.classPKjH, ;yFandroid/support/v4/media/MediaBrowserCompat$SubscriptionCallback.classPKjH#d&lU android/support/v4/media/MediaBrowserServiceCompat$MediaBrowserServiceImplApi23.classPKjHrZk:android/support/v4/media/MediaDescriptionCompatApi23.classPKjHFGͰandroid/support/v4/media/MediaBrowserCompat$MediaBrowserImplApi23.classPKjH ZoC6̳android/support/v4/media/MediaBrowserCompatApi21.classPKjH HMȸandroid/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21$Stub.classPKjHuVE uandroid/support/v4/media/MediaBrowserServiceCompatApi21$MediaBrowserServiceAdaptorApi21$ServiceBinderProxyApi21.classPKjH{ƕ/ Uandroid/support/v4/media/MediaBrowserCompat$SubscriptionCallbackApi21$StubApi21.classPK{jH r<A86android/support/v4/media/TransportMediatorCallback.classPKjHFDandroid/support/v4/media/MediaBrowserServiceCompat$ResultFlags.classPKjHF*,;android/support/v4/media/MediaBrowserCompat$MediaItem.classPKjHf2^K Fandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImpl$3.classPKjH C33}5android/support/v4/media/TransportStateListener.classPKjHwc<kandroid/support/v4/media/MediaMetadataCompat$RatingKey.classPKjH992android/support/v4/media/TransportController.classPKjHp-fandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase$MediaServiceConnection$1.classPK{jHU>'android/support/v4/media/TransportMediatorJellybeanMR2$2.classPKjHw F=android/support/v4/media/MediaBrowserCompat$MediaItem$1.classPKjHuU"tO<android/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase$1.classPKjH' 0-android/support/v4/media/TransportMediator.classPKjHH<lKandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImplApi23$1.classPKjH C'Ngandroid/support/v4/media/MediaBrowserServiceCompatApi21$ServiceImplApi21.classPKjH׈> android/support/v4/media/MediaBrowserCompat$ItemCallback.classPKjHaSWn D7android/support/v4/media/IMediaBrowserServiceAdapterApi21$Stub.classPKjHj4>android/support/v4/media/MediaMetadataCompat$1.classPKjHOk-)?android/support/v4/media/MediaMetadataCompatApi21$Builder.classPKjH@.(Bandroid/support/v4/media/MediaBrowserCompat$MediaBrowserImpl.classPKjHn&1android/support/v4/media/RatingCompat$Style.classPKjH U` 3 android/support/v4/media/VolumeProviderCompat.classPKjH5# >android/support/v4/media/MediaBrowserCompat$Subscription.classPKjH[ 85Wandroid/support/v4/media/MediaDescriptionCompat.classPKjH+Iandroid/support/v4/media/MediaBrowserCompat$MediaBrowserImplApi21$1.classPKjHUGg AQ"android/support/v4/media/MediaBrowserCompat$CallbackHandler.classPKjH?A'(android/support/v4/media/VolumeProviderCompatApi21$Delegate.classPKjHLPf)android/support/v4/media/MediaBrowserServiceCompat$MediaBrowserServiceImpl.classPK{jHC6>*android/support/v4/media/TransportMediatorJellybeanMR2$4.classPKjHzܮ.:-android/support/v4/media/MediaBrowserServiceCompat$4.classPKjHiN^0android/support/v4/media/MediaBrowserCompat$ConnectionCallback$StubApi21.classPKjHj:w3android/support/v4/media/MediaMetadataCompat$Builder.classPKjH'}Ad9android/support/v4/media/MediaBrowserCompat$MediaItem$Flags.classPK{jHP(>:android/support/v4/media/TransportMediatorJellybeanMR2$5.classPKjH>^$ +2=android/support/v4/media/RatingCompat.classPKjHCnIGandroid/support/v4/media/MediaBrowserCompatApi21$ConnectionCallback.classPK{jH Hy>Iandroid/support/v4/media/TransportMediatorJellybeanMR2$3.classPKjHsH KVLandroid/support/v4/media/MediaBrowserCompat$SubscriptionCallbackApi21.classPKjHDp SPandroid/support/v4/media/MediaBrowserServiceCompatApi21$ServiceCallbacksApi21.classPKjH k =Wandroid/support/v4/media/MediaDescriptionCompat$Builder.classPKjH O[android/support/v4/media/MediaBrowserServiceCompat$ServiceCallbacksCompat.classPKjHY B;bandroid/support/v4/media/MediaDescriptionCompatApi21$Builder.classPKjH}Heandroid/support/v4/media/MediaBrowserCompatApi23$ItemCallbackProxy.classPKjHҵdT+iandroid/support/v4/media/MediaBrowserServiceCompat$MediaBrowserServiceImplBase.classPKjH>Dclandroid/support/v4/media/MediaBrowserServiceCompat$BrowserRoot.classPKjHL`3oandroid/support/v4/media/MediaBrowserCompat$1.classPK{jH$ X>pandroid/support/v4/media/TransportMediatorJellybeanMR2$6.classPKjH ȞUAUKsandroid/support/v4/media/MediaBrowserServiceCompat$MediaBrowserServiceImplApi21.classPKjHJQ2#vandroid/support/v4/media/TransportMediator$2.classPKjH Fyandroid/support/v4/media/MediaBrowserCompat$ServiceBinderWrapper.classPKjHP!\)2~android/support/v4/media/MediaMetadataCompat.classPKjHa2Mandroid/support/v4/media/TransportMediator$1.classPKjH,. UIvandroid/support/v4/media/MediaBrowserCompat$MediaBrowserImplApi21$2.classPKjHZCandroid/support/v4/media/MediaBrowserCompatApi23$ItemCallback.classPKjH_(>android/support/v4/media/MediaBrowserCompat$ItemReceiver.classPKjHvqdNIandroid/support/v4/media/MediaBrowserServiceCompat$ConnectionRecord.classPK{jHJ >android/support/v4/media/TransportMediatorJellybeanMR2$1.classPKjHIQ.android/support/v4/media/MediaBrowserCompat$MediaBrowserServiceCallbackImpl.classPKjHhk! fandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase$MediaServiceConnection$2.classPKjHB%H48android/support/v4/media/MediaBrowserServiceCompat.classPKjHL=9android/support/v4/media/MediaBrowserServiceCompatApi23.classPKjHw:android/support/v4/media/MediaMetadataCompat$LongKey.classPKjH[6z^]android/support/v4/media/MediaBrowserServiceCompatApi21$MediaBrowserServiceAdaptorApi21.classPKjH7f[ Handroid/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21.classPKjH?xG3Dandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImpl.classPK{jHJ <android/support/v4/media/TransportMediatorJellybeanMR2.classPKjH4 :android/support/v4/media/MediaBrowserServiceCompat$1.classPKjH3qɂ Iandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImplApi21.classPKjH*P% dandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase$MediaServiceConnection.classPKjHb?bJandroid/support/v4/media/MediaBrowserServiceCompatApi23$ItemCallback.classPKjH5android/support/v4/media/RatingCompat$StarStyle.classPKjH1^53android/support/v4/media/VolumeProviderCompat$1.classPKjH U9Handroid/support/v4/media/MediaBrowserCompat$ItemCallback$StubApi23.classPKjHCfk PLandroid/support/v4/media/MediaBrowserCompatApi21$SubscriptionCallbackProxy.classPKjHhOOandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase$2.classPKjHNq=% <?android/support/v4/media/VolumeProviderCompat$Callback.classPKjH۬%?android/support/v4/media/IMediaBrowserServiceAdapterApi21.classPKjH\rK IPandroid/support/v4/media/MediaBrowserServiceCompat$ServiceCallbacks.classPKjHi#,/Gpandroid/support/v4/media/MediaBrowserCompat$MediaBrowserImplApi21.classPKjHf97= android/support/v4/media/MediaBrowserServiceCompatApi21.classPKjHƢD android/support/v4/media/MediaBrowserCompat$ConnectionCallback.classPKjHXU"_android/support/v4/media/MediaBrowserCompat$ConnectionCallback$ConnectionCallbackInternal.classPKjH`&5:android/support/v4/media/MediaMetadataCompat$TextKey.classPKjH+Iandroid/support/v4/media/MediaBrowserCompat$MediaBrowserImplApi21$3.classPKjHbOOandroid/support/v4/media/MediaBrowserCompat$MediaBrowserServiceImplBase$3.classPKjH\ .3android/support/v4/media/MediaBrowserProtocol.classPKjH$VFandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImpl$2.classPKjH`%~x7android/support/v4/media/MediaMetadataCompatApi21.classPKjH9#r1B#android/support/v4/media/MediaBrowserCompat.classPKjH(hi 6*android/support/v4/media/MediaBrowserCompatUtils.classPKjHq 0/android/support/v4/media/RatingCompatApi21.classPKjH%A)HF2android/support/v4/media/MediaBrowserServiceCompat$ServiceImpl$5.classPKjH ָnFD5android/support/v4/media/MediaBrowserServiceCompat$ServiceImpl$6.classPKjH<_78android/support/v4/media/MediaDescriptionCompat$1.classPKjHT $6;android/support/v4/media/MediaBrowserCompatApi23.classPKjHC:9>android/support/v4/media/VolumeProviderCompatApi21$1.classPKjHO?@android/support/v4/media/MediaBrowserServiceCompat$Result.classPKjHQ6]Dandroid/support/v4/media/MediaBrowserServiceCompatApi23$MediaBrowserServiceAdaptorApi23.classPKjH8` :_Gandroid/support/v4/media/MediaBrowserServiceCompat$2.classPKjH&5N'Landroid/support/v4/media/MediaBrowserCompatApi21$ConnectionCallbackProxy.classPKjH79+20FNandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImpl$1.classPKjH>e^NoUandroid/support/v4/media/MediaBrowserServiceCompatApi21$ServiceCallbacks.classPKjHd FIWandroid/support/v4/media/MediaBrowserServiceCompat$ServiceImpl$4.classPKjHfy N[android/support/v4/media/MediaBrowserServiceCompatApi23$ServiceImplApi23.classPKjHqRd :c]android/support/v4/media/MediaBrowserServiceCompat$3.classPKjHq?bandroid/support/v4/media/VolumeProviderCompat$ControlType.classPK jHRdandroid/support/v4/text/PKjH Adandroid/support/v4/text/TextUtilsCompat$TextUtilsCompatImpl.classPKjHHBVjandroid/support/v4/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicImpl.classPKxjH\I *mandroid/support/v4/text/ICUCompatIcs.classPKjHw`Rrandroid/support/v4/text/TextDirectionHeuristicsCompat$TextDirectionAlgorithm.classPKjHҒ3Xtandroid/support/v4/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicLocale.classPKjHsX+vandroid/support/v4/text/BidiFormatter.classPKjHҘ)Candroid/support/v4/text/BidiFormatter$DirectionalityEstimator.classPKjHN`:,android/support/v4/text/TextDirectionHeuristicCompat.classPKjHcr'@android/support/v4/text/ICUCompat.classPKjH$ؾ$,android/support/v4/text/ICUCompatApi23.classPKjH$v3android/support/v4/text/BidiFormatter$Builder.classPKjHk/ ;android/support/v4/text/TextDirectionHeuristicsCompat.classPKjHedf9[android/support/v4/text/ICUCompat$ICUCompatImplBase.classPKjHeG(android/support/v4/text/TextDirectionHeuristicsCompat$FirstStrong.classPKjHs>'5-android/support/v4/text/ICUCompat$ICUCompatImpl.classPKjH* Zdandroid/support/v4/text/TextDirectionHeuristicsCompat$TextDirectionHeuristicInternal.classPKjH9/^Mandroid/support/v4/text/TextUtilsCompat$TextUtilsCompatJellybeanMr1Impl.classPKjH'@e/android/support/v4/text/TextUtilsCompat$1.classPKzjH j9android/support/v4/text/TextUtilsCompatJellybeanMr1.classPKjH|igx8ӧandroid/support/v4/text/ICUCompat$ICUCompatImplIcs.classPKjH [-android/support/v4/text/TextUtilsCompat.classPKjHZzBEandroid/support/v4/text/TextDirectionHeuristicsCompat$AnyStrong.classPKjHջ?=Kandroid/support/v4/text/TextDirectionHeuristicsCompat$1.classPKjH3v Ԭ-randroid/support/v4/text/BidiFormatter$1.classPKjH=yandroid/support/v4/text/ICUCompat$ICUCompatImplLollipop.classPK jHdandroid/support/v4/internal/PK jH!android/support/v4/internal/view/PKjHqLC2ݵandroid/support/v4/internal/view/SupportMenu.classPKjH'5android/support/v4/internal/view/SupportSubMenu.classPKjH% 6android/support/v4/internal/view/SupportMenuItem.classPKi2pd-2.17.0/qt/i2pd_qt/android/project.properties000066400000000000000000000010631321131324000216170ustar00rootroot00000000000000# This file is automatically generated by Android Tools. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! # # This file must be checked in Version Control Systems. # # To customize properties used by the Ant build system edit # "ant.properties", and override values to adapt the script to your # project structure. # # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. target=android-11 i2pd-2.17.0/qt/i2pd_qt/android/res/000077500000000000000000000000001321131324000166245ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/drawable-hdpi/000077500000000000000000000000001321131324000213275ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/drawable-hdpi/icon.png000066400000000000000000001104711321131324000227710ustar00rootroot00000000000000PNG  IHDRXbKGD(oT| pHYs.#.#x?vtIME 5&k IDATxw|UL%'PB]E@uŲ뺶U׮څUTޑ^SH9&(. *y䖙9|SOOOOOO~N9e„sdxhw9'&KFO~2}!rc$ZC;o촬5wS L;e">MIbԝBG{Z.suW9Aο3ɶ]R@6mr ]'(lTpde!O$߁# !_%$q(\d9wϮ+~0Hnd(f6`@;eu?X#GߛoZm._a&7'h$k,;FLlvӃ>/3XQӧNJm32+Uy Rfո _~sRoDx5;-8?_vAlgnF܌!SzOd.aRj^zZozSs.9G^(3sNc)kr!tBc, IL]HWiN95BZFILvɰooi3֢g1O(xH0@ew^(a၏>i@Q&%ɧ\1|H{yߴRO1l0hI9-yS@EfD8屖I<7 ݴ =iI yK7aIp%fy^xn<7<=bn7)s7>9Mt޸9OK <==ҁ8$#D8vSKs\bn=pDcnf&9E:t_ ;;7 !1hjZ0!5 nj7RWOOKOׯߤq}QqsN%z ՞S 3SyֿKK>@P\wKQMo:YseE:^zժ;gViǷphUB~~!EJy ()) -Mֺ( ]ۋC'i~ڼdm` P=kZ&$Dz/2337'G/m-m/jOY.*H8k/gdU?)O-t 98LD psq ~~ {fŜh$@kS;)AO.@Fn3`?-ȹ\9h^ imܲe[kB<ԉ3f U(KKQ*6MV^ܵ.Бzfrę!Ƃ%+?زyk'Aqd#3͝= 4 @ES$D`x * %o/Xt7%WMge_$mҫ#cԐ!ڧqAmlMQm>,NU8hݼ6i泒t{PMͰ/LMWt8}:O'fJ;jg3M(J'"B"VѢۻ~.+Yۼ.QI$EC;P)^߀ċ֬CXs&Q0+Z^8ElX 9O[gLFZIBܴ$5h!Tw\3<{ɺssy"PId\:[݀$5nRUU)` SQaN k5WXb|MZuu対5r@pSהl9ej!^YSU$QsZ8@Ϣ O͈:)|㬓URCM4Ek$x|ߞo9IN <+q211A(K\"U2idҬѳxSiB,5 FUHyJTuNNe.uŊqۻ[M0B@ ( lt?B̬4{uꞈꢘ5ǏiSmBF%_WٮlRmSh,x4@@ 7)Q 7dԋ#t@HJovfKye}eADQO킦spE0CӚv.7@iҤIƺ Ȁcb MՠCk ?KIτ0$vH.}wq~4$ 8" "ի]rǷ7eE^g*zt Hfvf5XEo/"Ftۍfi`'l?,,4l2k%6>6q$Kqa~J@\`pC1ד?{2aH Y_nPW*7(vi۷o.[apx9`}3{vsW. FWZ^uyh-5m(rՐZsw)\eŕ pђJ9 qnp ƹFhLA{` $KMS&ѥ_/?% GhL m(.1?Z}=! g = ֮;n1: {D JԠ~-~nF8EԴ$RUY/|.`PΡusn#~m{*!$ES G#[ٽkXwӎLL\0K9Hc6^z_ҒUոn:}rs ;Sx|>"yM`48| /o ͛5"L<#+rs)뉢yA̺T금Wν˶s_z`"ro˅DJ*ܥEM6{ObeKEwlZe3 [cs XzfC+W.O~͛7}~rm n'PUgPӿpξh D)LT}W/H&v`9ДXWުv D$w"%|3w|k8STq~âJD8ɻ{nٳGS#3!S/-Hvvoo> #y:ù][K;o?נW]ꩱ~G{lcA:TBL)=L1@P3XLNh0P"NvBjjj \]>5??\ 8@z `-!D8A@Ӑhê+tt?'$ė֎ %;.hѥ؏9S'-fXǙgB@٥@d7^2,B_fs5;wRVdٮ= mM! QNr$ժ?7K?󓂒?'ڨ&5rK"uẁl/g禟R'csrn_Z{` xTοZ>jLa_wM 'c]BB $XPBE|GS{M7r!ׯ`<< wwl؂54Tw,tq4PG{ Vkqz{=[% &ɽJ6;遻|}pZykZlS~w_aBBc-B囝oO>e$xg﮾ARZZ{hW/G+> Pe 9Ԕ Wm((5;D;=%1tKvS ܪ(Hi涍*A]^> $ ,|~iicmزe 9,NMDmL*W@yP oV۞zJQ[ٿ90"Q'Fi+Hx/PRLH5߸qy睧YKƫqްkmHJ~7vuxˏeJ+ʦ#z½O #c(P:6bpN,r:k% &ɽZg;{޴ ??oM .cޔ4]!n3iHjsu ̙5Q-ug%JLFPPY 1 ޟ!0wCR$,(yDMnG|[^\} @ܻ쏂Χ41-TY͋⋯ߙ5kR6o޼㺞4r:C.'BAӑ,7wE g[\Ρ3|!+f]523ɠGSRRhRRMLH4))fff Ǔh;FXz6f̨7ox;#"M]ޣ*Tkus[o<,NvVPZVeeg9߆{go=i|—-ź+IKKa4=#}^Wu8 c`3kHaώeVtuy~4 yyJ80&j./lG>ψ7@c!V ^*S jpQ^%%;n[@/]*~( DQdG^~&]n-::f:n!k? 򦮥UmW_u턿?{ouן(z}>Uop㚙;k;Wچ esQϪcIaPQPAWGXAY HfQZK1Z3mf;F8p ill`\>\18|X::zUSe2d2 V;isE9l(bOUcy[{۠FNW˗k)@:ky3N+\7ߤX/QB9J+Bt wqnGo妣R)ZWZ8vgU =0p^)M, :XKXpHύ"}^12j9MTS"P9z[?@![glD9B,B,NGh&ᖽS) jꃍXr:g"<'G6gt+/w\5_}rHpNOK+j huqԭ)6EU(gpԐ`w&L%| !D8i"Yl!5=eYgMiGܝHD1BcO==ܶuLUed!F[0wYݰaá 2qGeT)>y4EqMc`D0ph\;("] ץC*ThQ:xڦ.]:.!VI/UUm'z5k $T:D'sa^ .B2v5eWvyNsE@֭, x[K|!ˢs~P"":ug˖ctV(S`)!  0ZJޝuya N6H<Ƚ m۶Bnn.-..z -\juT0d8'PRB/9@*, Sܨ5+u:(.)HvŖ;bK(&h ՗Nλ_~2kF`z]ڛ+I 5y\;oZoch9pI&ęv_~6{lOsHc v30b zHA1 ܱ oBmpU%GQX9vxޙ3[m(..p195Ggq 6"[2PS!am gĭzżhڰWޱ/TUqq;6hoK7PF! z` `IOHUUpι DEp3Ntޅ OΤa!^HS3%ihZs cƎ+֬ҿ/Y$g6:6Ҥs^:E'PGZ~ݶu=pe_7Q2Rd Ii*{vT =BspPBRCkO]Zx_~d=BII#=rؾHaV OAYs;wճfc~1('eb rǂB+ 0߽>2O& MZ;C{B5^ѻa\Q۶{[I$js88g9v.22d7yv?wa4lgWTxe(O IDAT%ݒyxitn=ss.dfeX匐º9ХD`}ԭ  ʟ~7/ y\lԤY3N=A{]OJճnK;MacbF !Hfلً/xBݜIF V)?hxRXIe䙗M7})Vc2X𘔎qڍyؾI&T-[bŗ\H`(D dD( zAGtLj?!(]rNm5؏OR`<0SQ*ZGFd yv^25-$3=WmJl os_1וԀBvNѣG, ׇ//?:l:m٤x^)lȢB \pWc5oʳB1K9ppŻBl)XJñ'1#MN_qѪ.lDezә{uZbcCыNr↕@Oi@=LDc,\5:/ `7[)ZH&%@f=突 `T|UTюG j+,*/~kǣ'7r~OII N x.wDbUoޝm}lxgQVÍFdD:Mjy\Z^GՖɏYu@A:c7ꃝP-HI&s@ "rڰɽ_D&!cg>rr FY+ziYhoJGWm確NAP]Snw_T_'!3r<e2oJDO_*+`Q&[".j$z싅[yٳDSvTwhAv4X6ۧ$:Q.*mbTGB.M"Q`ƊupЉ8rƝx`v PzVZGǯ@Q0i7wKXƺwhE(8dv\uV8%*X^""G8%B<:V¯ٳ\]' /ǝpH}\>\لc\Mà34#0sG (Dޏ4lu?`3k­]Jrz쯿^~P3"]иMF֯_9E%QU%fF?d @~?EHDy"뜈D]"I+P=4ɢ'D 45a{3. ;9! |sqY)pT7q5hN*f[ h,RH·k{3XAIDywP>\7)92N= scnuOb)m;ﯻ>35H *}$ѣGӵkײ{GzC0ȬK+?D(K!ެ!+A@HwPө[@'d$j48ti`\9nHɲQR,?8Typ{gCgnR jۃhVQ:8z\3Lf@N"IJJjkk$˟w_(sQN^lDa)yc<\g)]*kg:#?C2`9 @R:|wE^~ᅧԝ: rc"( / ۯn!`ހ`7ॵm(k2!B7 A?<#!_!)nvE6} 'THS-dG;"L04Ō(2ʀJPэ "F!),W09ߎ;NODY ~iPއ4p @ ¯Ub{K﨤akeS7؆P_r/'2D}D"(!Za B6X/΂QNJ;l@2c + >E + qub[#lx(!! ?z ^u8?H<}A"EMG/ƦJ.;H~$e"%: *ap @an6bkME% I1/ yqFYޖZP%`5!' rR$4̕a^wfٞޭFԳJ˒K9dIt]/lܬziſ:#M`8=VcX j:X[<bFEBGARӬhpcGC-"$0^7܉j;وHh ƅPc;4tT@Űq5x2&ڐk7؊FX%$Eǃ"+ր~F$.AXOŷ\2*d,rjZ2溚NeR;#*I|~_ ?C^^C%">xF"Pz3+ >,ҎEE8kh$&ؐk27X߂ &94،YucCubeh8 0@h99' 5An<;#3n▷+! (2C"2ч-(GZ:Sj@Fl(o eȉ5 .#7ofƈt+onb= 0aX Ef(zRբUC:|Aw~m3g6+䧐ܜB*]g"(ڽ(t7& 85Jq{UbL :(VNɳ55 /Έ4A52fAHaEY)b PqphPQY;OlF25Mn|Y ^, f N:W|\|PvwbSe7ЊC( S-B*Y/`rmO~݀x#2F[Ƌ F$9tp4lmUX a C"g&]j4e{<9F9rc=jOnbaQZRȉ flg(uSR6Aڃ3 h @〢rdSxz N6#¯ M@Br>8\{Nk 'ln<%KhS+}Y68/wwu-x X+BA+qӢJ\>6i яSTA ZCxfYZ!cXqËjaʂH8A~1 AA-UR- ju45jbf3V*-)3)3WWr+-oۂRhi 0[8:{p$E8vh dl,< "X71׆FwO-mĄ\F%k#dج2`8gL{DRICg )R߽珈<jۃ*\4*g DO)ܽk ar LɈ404Ռ *qƠHVx9&ٱֵ`Bq6]xKJt+K*fh!~RYxPU%D(iUM%"(~ڲgZTWp@ V@NE!qI%G] 4?pZ䞠ɏ$4Av|!g&(͌*Gw $g/LwX՞!oG;& cxm}+lOGWN$ SuYWE/BcyH M0(ɌKGi|X A U=W{(!eO+xbs0D(Ɓ]P[1 sS $%% }7bTU,n,nI:4G!7yin8½rlxlLFg y5dN(/^Y6ϯhĥ! MڎS2UԠ%/3B*;ST8*CnwHF}ڎ 䞮6MCq6BcdBw0q1OCez(zfz'>0V[[d0`pZ0{f&##ZY$xkVR!eM(HB_@w 2g/~!*Ht0kp$t"=p^aWaÃa|Qfz-e#tuc) %pЉ$*_ ' :WaFNT]SCGumm-OOO:;]u |z$4Ԟ={Fq;;9 "~]{oϫԭ^1z&Mŵoǰ4 %z{!#F?%sTơ)Yހ FDC/R>с.H'A/QlkǓ秣 O@r+>>M%4LAJ|`LLjDF<?Ý 7 'g˟ƌOxmٽWdzR~V@ov7*dLp(w45qնv]v:S\NzC J0QUaDT,J)҂$^"]~ 7-7uJ` x 4Ehp`50($"raQڽ*8= z&xj< V7᯳S`5x]@ jn`7  mlƒxm] ܡp{qzGg7+E)@d&Q"2FȾ}kD]Gi*#cb#ED'éptu; $ @PeÜB2m(PMif 2D.SP"On3D\oChD,j2i]NVݷ'+sdSvNnGEy 9qMS&ǖ]mYh8Ţ uؾhmC*JhAm3Q!6M @l~s&L׽ξ,G ?߂$q́ty4_ -0ErP@9'"y ]d9ֶjwdm[۴O$MZ^ 4έkK3L2=NK@$iׯcKs [्we'Ҝ"nӆ˫u*-tK;] \P4+^_|~G! QOnbL&;1؅Ng(/X閪H@TG?< F`Xa=$paa鶲gϚ3}yOLv Ri3bY~Ҝr)J" VDjZ? /Yc~̿_ |lE g޺Ι9Hŗ IDAT{G߾|g}i?7ͱO*A) "2\c:;ujР!LlR*UW$JaGrd~vBwwGu9xQu"o2FY%=Xñ I|ĄȺ^<KoO}R U^\F6&i2a>o?لٕEpeGn-ǻ}Gyq^M{u4t'H/!VMhJznMj ]P pn@4BOlCªGS͘OSœ3h,eɐnR͙@wjL-Oj@8P[ h2୺] M/.*xe]߇f 8wl9`2$c .,ȇ&`ž T3QM^hņ(. ˎ=-ݏm &%Mu Vn]TWP0Yt3~U 7v#tO1Sz̄J!//:3=/EѦ&JpsK[.LzrUMS(wC= Dsѽ!3[.zTq=Ȝ3u)!MƏv5 厶 5cⴚk0bLegNΓL#|C  ژt@UܟlHJ:tܛ !Lj r{;h%EB|>߻w }gW]Vki1ÒhC{}%~E9T݂ɀ` ,I 9Dd8%dx%@LƋ;3ƒ-@I "xuW;nj" ̲X0X'GYG r/v♍xlM;zb :;T|(`Չ@[& ߘ_ L>(ĪQqν{'N{7_ܣ-^rǚSΚ54 ?^ Še-s}Ҽ JF,--=S{ 2:Rl#]4QVN`Y^igȲL.FvV_=U Vz'$W{y^QCam?koE"Qq,ӖzN4$,;fV1*Lf?VaDǧF=ڃ:"")"%"#d`VD'ث_T7ۃ-!:A,  "aۗI9'ڀ.oI\./$O6&HH'd_|1ݽ{7mnn{,޵kbT:N˪ K],(,Z_lMptosx?jnx.2sϱ  5=D {³/}\3V=bm,ϔXԊ G FF6VsCi:a"JP=g1 YSD4ㆅ״??V2&ffffq.hH7F;1 `F9݅8`n:BFN,ht͈$-IRx8N O 'oMbO*wg8 ӛ*7 g ^a3-kNk& n"#?Vyhm~0.o\'EV8p)eDEbDw*LjLPGCBS&۶4?v o---& |ʣ4Nzk r,!yJ&Ȍ>#Wc TQ<0p mrJ'پt+MZG|K/E^SJiȐ6X>k(ΰC( Yn @5Tެ cվ@8^/'7cZ y> INgHGs_5vLۇcE&qgVg7O@C*މ-ۼ81\6vu3n@y.Ȉ}:(\%R[Wؾ}J9ZV 3|bf¥TpG%jnutkrmO^~5}"s"o}[B62soSFNgl9,dE>y0 V Db0z ˆQ3㧑Nԛ!6`9=sC/v`>y: R{ax8m@RU @d{e12ׅQ.Qc[^4|yN.Vt3/WWЁ޲Pך𛥍x<0߇puhtn]Ti? }fk88lDucÆimע2"S>kF}'λX@KlbtD_]+r+* ~iΝ'!̤ "[͕(DJ9;5r]јNXA@ $"r=gD#F2\b~q9)c+A3"ГXfGuT)(D"@  n'!3J#V a+>+ "DbZ[?ɵ6HH|"(Wc5f%MU%< xl$EC!TneYH(pլB|sA1RM\z;6 ߞJ]C9"&o}.sRCa pHYTίJ+AJ،c{pCڥӡCBH#^Uۍ]zĮv]/_,Ees?'r~YWm0hGo ۼ=?h?.\pມKntHdž@Ʊ2-@CsG:-sbFQm+('H/3wxo&ѹA,ΨL%]u r6-`wK~9#_?n}r̴opA]#'M^e"Py\/+}ٺ3VW_w FWo֎N^3j7> ujCk+d<9и!pD 1I nBl Fl9C}g]T":Q0ԉ "`>h&,ǝxp{_6a| Yv~4i8\jC\`)Nh gʸjf!y,J[1 lGgD)=Yﲗw^ȸd찼~B[J?#*L?67 ,|%b]=]?:'cWI!`Ǽ MAdIqT Mk IGBRtxuV)J3ߞ_]cҐ"HFFc?Qb1f`T˧Oœs Tbn~{a pA/&#! Uċtd >z,"]'acqѠ|V%@ L+bvdgqB_>ZotȑdmԤ9)틿_Ryg*$PĬFw4pe  v14(Y&s~9Uw5P]N=U81ԇ)KG68i0'aqï!C- [qۄL!N"0Itk4$쾺ʖkhx='1*BWᇮD⤼A¹:@8XEE[?ݻnwVD(AD{ D@̊%K￐ vt ;;bjv6zd *P` A .KC0ns! 9DHj&IgOK MnaGkC[Chh}_gD`h.XLM6u?pN;EEATa{]$o~euP\sоoIc\d0[ 2<\?ЅO%:ڭY3fRB 6ekc3X5#4ܜuHb ee@ 8F Cƀp@(@TT@0 DCB@ (jnSCz? "C ;+5 Ld8`c]aҴzϓ>t [vh[=r㟳fLAۄWС4w7n`̌ _VC1/?)`!b h-PܔZ]pˍpIC)C8HsttPr?8?QLX!3dÆB@zm NdD@Ŕ%fv[Ҿ޾&؉9m_)p/- Cs]nqF=]W$Bk$dDL$&pea n\6f*9 LO''!S!H)|!gi*whjpn.gE64ERiP@-O&5H'r혯L_K $m/7ntoeܚw~9rY1U+_hT/.+@SdX=.G_Xj|G0qP;?H,09=}A [[@OBg^bK6l뱋h hGo@KH1LN9БDBG[ 9MEޛ|Pe"at84s\q3>j\iMU;936@>YM[ا52ZZfB[[^RP Us+Jrshܠ֙ 0J&n46GEWi/TeqbN0DB'PBxaƐlE\06߉]XqEb2`H9̓x+rKAօ|9wP|@%"`K'XZ4՞$6l2zM$ӂB?zVA˛|y`ݲlC΅;WLdirJly4quB1IDmYčT YQqF H1:DXޔkC`}I :ٍ=ׂ@e  R$EJ- AI`g?z\ό>H86Ņ"AT/o ;y)^%vG=L$s:m&(_@Z]nlzgv=^)&t1*?^0Z)A]{xe=j_8-/n`xx39} 1fF6pD_-Q/r`w{HC`1QhH?j@@1 Tą% _3H0UXHۿü{7 9@>IwYxNnn/Z,+W& s;^EuYNj78"uJi6z 7roOl:jH!6{_z#CcB(18pF49&9[@q[](JSpf1$CpPc`TncⅦJFO"@"OB&"=W'3K 0<##=YbRk헾I,}(qStn3[҇La)C}ӱ5.@w؀fr&GD5ܟĝKpR\4!:hl =Uf0wwPyP! ͏7bOGYH^ V(!G0|IGϷ"%aLDMQ Et"!°8b]C{qEC'EcppT̉Ɗ_b~!$;c_5!rkP$c `@9bÑp`\ǚ`?\Q?sP@KBwH;MQ+W0:߁P„,9{C)PP&e@(aRÝMwavu~rABI̱ p*|Iaw[{vvtaR>i 6.Ȥޮ4H7ADr-cvJ ߾?L?/˽ڀ܆̡ul+Ux>$UڙY&̦^Xk+QeC\ ^frDyq.%Z1HY_aC 9o? ·fr h(N E `i7 bDrɃ$CasxYٟPKREcG(L5mA+(ջz1"9vfrpX# IDATk1#ܸ|Nrxl|vPBR";86~e6L-;ݫH@-{xuW d$|if6lfVH Bq<}I*);@qˍD7]fdx\"Q4xi󪼘QAOCkWC`X\GX0M_mM|2>mZkV#>0}'@"L=ٽ8p:0XKk+ck_v-֢۟i%s@%g/Fk :C1\0.=uo#1܇{ԻhK8ӎB`jc Qq]x>mP<ܘQ$ivwçjͰFdWV!w_=i=ƃQ\9- Y6o0U8_uHj^Ko69<]"W\:yg`$M0aZs|&a,9s]n[-XL#Ԕ%#Óݝ(͔12ǑR` U.̫D};{t412ua| ,֧"[&,;Ƈfit(o^ݯ`M]?2@%;{I)9O=Ͽ7q湹v4@A>8''1 #d=׼ާ''r:)"J mra IɌVs4LLԄ?ih'N===1˾py>#u-DUoUL+NS_1܂#ԅ%2  #ϧ11 ^b^]ŀnuyN.Gp<k{ݿo+wYri?ZB;("ES_n^wjCxqWh5(sJڪfwe?}=ߵk4˅}p4ʱNOZZRJ # o]9bYruPsF6w>ҍ(=YP5H1"g!+0})I v㖰xB7ENS ,3m360MyӖɓ_?۶ g&w:%:ɭ#^O-X[#Ry6}b}v7PEe˖Y ι4#|>nF\h$91^!0t <;ڣoU{A:c0xc oD4{uB51) 660ԍ]Q<+v"NjAˀ7p,0b?vw:O{ iqxdȶmfԈ/`uy՗֚D)؏!>B.h#YxIIiT.IOJ:;;իW[eY?O'l{occm9jG4XKl%OyDvw4cT#r_*` jFGF#Cs2'NCH 7.q'-/Rï//öygP4!8;WE4F3r~Wocm#t;UġIARR gzh@TM =]|H02K_o|kŝ(}fA9,#\8 =ҋiHsGThbZR(DET K]\1S#L@DW@vw&]()xt}^ݩ 6I91YX˒ [QHR $==OR[#zhIC)ѕV#N7pwAt~!䜓W_}w^iϧL9()S'Xڄ]aWepq,t!:`Zm̀aiGI^w-*7B Ac}Q?yyXǏj`AS9 Mig].XuҍI;{&&Q\-%H,p v֣:V!v>_|1ikkn`PtƌNKw[KiC.$88@I''k%֖7emKJ PY)BRA32q ?} Ēr˘Y^P1UE /,Fw_Oo;- >L!)! WKnl<{BJ)A:6Mtx7 k&aLy4M/߿==\Y|ȓm/x)ٙ٢]ʃ@*'&-DɁ] )ŌrS$E<Ѕ3Ga ̬"%!` 'b9 g"+ɍxt}y0 CC_Gnb[d}'*K PpΉ]PGH(ݢYiuN{,Yl6b&}7-b7p8u=ۥ]e9 v)ȧXp6R&!#c\3>J˜ҒhYHh&Of2t0J|q$8{#E`0*8^ZG#I74M A'ebK:8hٕۂDs֢DqUXflݲggR,[c:QORꍤb>Z/vY'AΞ;vYgo`̘1d߾}w17ɷm4;-odՈ)jΔXDW㐅إ؎̴3mhնV֥jE PP l!, ! &}9g76;'}'yu QHHh_0 iЙ:ꤾ'8e`Eu7廧Goޱks}i=mdȜ\7C"Lm U ԟk'NT\qI/z_RkG \{Ud➻=\tMV,]8ۥ*+r2$m(jב }Z!N 9"|KQ98``:Lp$4BP0O mthROdT!Ӌ]ȕ4E&VoEvfy7ujt*!(kGcMPwG07Tǿ4n0TՇ-+Lﯾ{ZPL15'^-tߚ}>8GA]X0J;֐u'\gBgkc2wPe[b8;rxX=={W~kt[c#DU5-/w؇ah!TB)3, A(%TfLPB"K"$!$,K.8tnP04Ci<d#fП0~C8}3*|W['ԣDk#+yyYBQd#jjjXĉ^i]! [ɔfrM 9ͭ;盐0}C T"`h%9b7ϺjX}xds"͟]:ڬ|laImL1Q&Imfŷtǃwe+"$PY {їLSq!qAbY(s~F+݌ uII$I=I"ABeYLӐt]mbL&`"2K*xhh9ax}dzUIaHڷcIR^~6 pB\Wn\{w1UU"b)3Ob!茶F_ӳI B&2#?tbxϿ:^$lT `C 6@VVV>q3=K׋&rrHp*A.Qد}Uk3ҙI)2Ʃdwk7Eן:!$yJ87nt/^ )--ץjK1_n`ʕdv2 $7_{ (Fо/.(&y/$ޱxnC` _BJizz:NM0XB"~s+ջ"֖umܜ:ʌ;v.q3Ro>,|?)=ۀ=5_zv-h*F/lJ:k4z)"!A|ºΖ&.s=lT `C٠ֳTlf\"8]")Ĺ[.>{COcw߸>utTy|R7ʧT; WrMPxDYR;\mMKhF )&G/95$y'(PV\7qI˗\#O BNNXqI߸-m~!H'ӌnO}Ck g%~8޳>h%7ѧo&qd~oaQ4a ';A흱Ї|3v]g>L&Qe->vyÌm=kR=TB8,@@EQͩUS`^A>RvphC.˾z465CfZIJ=-=)jܼ6&I,qqPvd8wd|0 H@$"{F^^U^׉WJD$q2FyU^%m{SIZOh𱁁x2R f r[twČv̘1c2ydadj/eOw 4x-TOdVv?ut `nфa@[G+1MN(aE/6-wb |-Q1~B[*V.̺fړ؝ݗHA2ڵwH?tt@?`\T.ѼH]لYČ`:Mq 1O6B]yOϜ@o@rQZUec0O5PrXGgO/YfR?Le!9)Q30yT6EOcH6K(23L&P>ڒ4ZMP54E 2,_(Oy5S)9]n8msΝi@_arJT!kt+fr>ATE/Rz1d)Ѓ>ܿڢq;r W]g\}:1"D%qF?2UgQQ<;ƀD}]klm |؊:HW;!6OdH_6m}}( N2SCntȣM]JWTf0pgmݾ\ V}{ojzz3 cDwt Gr_?|l\ ѓT`]4}]JNQn|-f "x5{,`B`ęNF#`FfV֣YΫDYCƘebL2>.w,e#Ye"ݙ}VgDAs/{rL:;\׋c+JEbXQpZeXvg^I Aź6eH(ly v (,&,} %!A`;>[A3ɡP.ΆdB0"QDN7`EŠ8,GBH2ueC5c04G}>KRA$S*ydI͗]P@찪{4͒EA?ht"L9zȎvXR2haO7/7)fO9[FPRRB,IYB%n$@Ĉ".>!gKkC9DE5as#^!*k9{-3>DÏ%D(DE~+i!*!qBrV6_bTͲ Q/(\ě{E-(ukϔ1"D狊x"Qs=RE<,Cih8;iYE\le3&DCw8D*x-DzY?EI[)D qwiuYhAKkC9D J!:!rqoҶz?#'"тRC9DE,mV J! Qcn :Ȗݏcb" esrSO5l9}m6i Q>MۗYy]`l 4Czszfv?x!bk.C_Gϰ=DCl Dqlaf̘ Q_Ʊםr Gq|:oN̩CmZ3CϜpķ,_J89Sk9;D3[s~Nglq0\ċ\!:5YmLQ;h{ǖrC{ho..cKǖ=ڻg6ԥ5D c{{> J "^22)e٬D_}ڥu1>Sz[~~:|gidr-םeAKk/(-jӾ4۲GI[7n9 rqwi]U*]ڋx>K)-D3>-D5g[~O"ޤ<mV}νn^VyA4ui^Cg_]CP^P5LK{[r{am Jh.k9pmҶPQCy.k9[qmPҺ깞{R׸{Rě>dk9 Sp1! .3RW>̶"..ko?]Y?Q .o9=SdKvi.?|SMB4Ė3RI|WREK{[B4Ė! %{*ar1??})W7KB~9=8>s\~7oqLn$D[Bt{mBK{nkQ=لBo =fx"ޥoGxCv[ 7IENDB`i2pd-2.17.0/qt/i2pd_qt/android/res/layout/000077500000000000000000000000001321131324000201415ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/layout/splash.xml000066400000000000000000000003021321131324000221500ustar00rootroot00000000000000 i2pd-2.17.0/qt/i2pd_qt/android/res/values-de/000077500000000000000000000000001321131324000205115ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-de/strings.xml000066400000000000000000000007161321131324000227300ustar00rootroot00000000000000 Ministro-Dienst wurde nicht gefunden.\nAnwendung kann nicht gestartet werden Diese Anwendung benötigt den Ministro-Dienst. Möchten Sie ihn installieren? In Ihrer Anwendung ist ein schwerwiegender Fehler aufgetreten, sie kann nicht fortgesetzt werden i2pd-2.17.0/qt/i2pd_qt/android/res/values-el/000077500000000000000000000000001321131324000205215ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-el/strings.xml000066400000000000000000000011541321131324000227350ustar00rootroot00000000000000 Δεν ήταν δυνατή η εύρεση της υπηρεσίας Ministro. Δεν είναι δυνατή η εκκίνηση της εφαρμογής. Η εφαρμογή απαιτεί την υπηρεσία Ministro. Να εγκατασταθεί η υπηρεσία? Παρουσιάστηκε ένα κρίσιμο σφάλμα και η εφαρμογή δεν μπορεί να συνεχίσει. i2pd-2.17.0/qt/i2pd_qt/android/res/values-es/000077500000000000000000000000001321131324000205305ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-es/strings.xml000066400000000000000000000006211321131324000227420ustar00rootroot00000000000000 Servicio Ministro inesistente. Imposible ejecutar la aplicación. Esta aplicación requiere el servicio Ministro. Instalarlo? La aplicación ha causado un error grave y no es posible continuar. i2pd-2.17.0/qt/i2pd_qt/android/res/values-et/000077500000000000000000000000001321131324000205315ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-et/strings.xml000066400000000000000000000006101321131324000227410ustar00rootroot00000000000000 Ei suuda leida Ministro teenust.\nProgrammi ei saa käivitada. See programm vajab Ministro teenust.\nKas soovite paigaldada? Programmiga juhtus fataalne viga.\nKahjuks ei saa jätkata. i2pd-2.17.0/qt/i2pd_qt/android/res/values-fa/000077500000000000000000000000001321131324000205075ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-fa/strings.xml000066400000000000000000000010721321131324000227220ustar00rootroot00000000000000 سرویس Ministro را پیدا نمی‌کند. برنامه نمی‌تواند آغاز شود. این نرم‌افزار به سرویس Ministro احتیاج دارد. آیا دوست دارید آن را نصب کنید؟ خطایی اساسی در برنامه‌تان رخ داد و اجرای برنامه نمی‌تواند ادامه یابد. i2pd-2.17.0/qt/i2pd_qt/android/res/values-fr/000077500000000000000000000000001321131324000205305ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-fr/strings.xml000066400000000000000000000006601321131324000227450ustar00rootroot00000000000000 Le service Ministro est introuvable.\nL\'application ne peut pas démarrer. Cette application requiert le service Ministro. Voulez-vous l\'installer? Votre application a rencontré une erreur fatale et ne peut pas continuer. i2pd-2.17.0/qt/i2pd_qt/android/res/values-id/000077500000000000000000000000001321131324000205155ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-id/strings.xml000066400000000000000000000006451321131324000227350ustar00rootroot00000000000000 Layanan Ministro tidak bisa ditemukan.\nAplikasi tidak bisa dimulai. Aplikasi ini membutuhkan layanan Ministro. Apakah Anda ingin menginstalnya? Aplikasi Anda mengalami kesalahan fatal dan tidak dapat melanjutkan. i2pd-2.17.0/qt/i2pd_qt/android/res/values-it/000077500000000000000000000000001321131324000205355ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-it/strings.xml000066400000000000000000000006321321131324000227510ustar00rootroot00000000000000 Servizio Ministro inesistente. Impossibile eseguire \nl\'applicazione. Questa applicazione richiede il servizio Ministro.Installarlo? L\'applicazione ha provocato un errore grave e non puo\' continuare. i2pd-2.17.0/qt/i2pd_qt/android/res/values-ja/000077500000000000000000000000001321131324000205135ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-ja/strings.xml000066400000000000000000000010211321131324000227200ustar00rootroot00000000000000 Ministroサービスが見つかりません。\nアプリケーションが起動できません。 このアプリケーションにはMinistroサービスが必要です。 インストールしてもよろしいですか? アプリケーションで致命的なエラーが発生したため続行できません。 i2pd-2.17.0/qt/i2pd_qt/android/res/values-ms/000077500000000000000000000000001321131324000205405ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-ms/strings.xml000066400000000000000000000006301321131324000227520ustar00rootroot00000000000000 Tidak jumpa servis Ministro.\nAplikasi tidak boleh dimulakan. Aplikasi ini memerlukan servis Ministro. Adakah anda ingin pasang servis itu? Aplikasi anda menemui ralat muat dan tidak boleh diteruskan. i2pd-2.17.0/qt/i2pd_qt/android/res/values-nb/000077500000000000000000000000001321131324000205205ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-nb/strings.xml000066400000000000000000000006221321131324000227330ustar00rootroot00000000000000 Kan ikke finne tjenesten Ministro. Applikasjonen kan ikke starte. Denne applikasjonen krever tjenesten Ministro. Vil du installere denne? Applikasjonen fikk en kritisk feil og kan ikke fortsette i2pd-2.17.0/qt/i2pd_qt/android/res/values-nl/000077500000000000000000000000001321131324000205325ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-nl/strings.xml000066400000000000000000000006751321131324000227550ustar00rootroot00000000000000 De Ministro service is niet gevonden.\nDe applicatie kan niet starten. Deze applicatie maakt gebruik van de Ministro service. Wilt u deze installeren? Er is een fatale fout in de applicatie opgetreden. De applicatie kan niet verder gaan. i2pd-2.17.0/qt/i2pd_qt/android/res/values-pl/000077500000000000000000000000001321131324000205345ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-pl/strings.xml000066400000000000000000000006401321131324000227470ustar00rootroot00000000000000 Usługa Ministro nie została znaleziona.\nAplikacja nie może zostać uruchomiona. Aplikacja wymaga usługi Ministro. Czy chcesz ją zainstalować? Wystąpił błąd krytyczny. Aplikacja zostanie zamknięta. i2pd-2.17.0/qt/i2pd_qt/android/res/values-pt-rBR/000077500000000000000000000000001321131324000212275ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-pt-rBR/strings.xml000066400000000000000000000006501321131324000234430ustar00rootroot00000000000000 Não foi possível encontrar o serviço Ministro.\nA aplicação não pode iniciar. Essa aplicação requer o serviço Ministro. Gostaria de instalá-lo? Sua aplicação encontrou um erro fatal e não pode continuar. i2pd-2.17.0/qt/i2pd_qt/android/res/values-ro/000077500000000000000000000000001321131324000205415ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-ro/strings.xml000066400000000000000000000006601321131324000227560ustar00rootroot00000000000000 Serviciul Ministro nu poate fi găsit.\nAplicaţia nu poate porni. Această aplicaţie necesită serviciul Ministro.\nDoriţi să-l instalaţi? Aplicaţia dumneavoastră a întâmpinat o eroare fatală şi nu poate continua. i2pd-2.17.0/qt/i2pd_qt/android/res/values-rs/000077500000000000000000000000001321131324000205455ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-rs/strings.xml000066400000000000000000000006501321131324000227610ustar00rootroot00000000000000 Ministro servise nije pronađen. Aplikacija ne može biti pokrenuta. Ova aplikacija zahteva Ministro servis. Želite li da ga instalirate? Vaša aplikacija je naišla na fatalnu grešku i ne može nastaviti sa radom. i2pd-2.17.0/qt/i2pd_qt/android/res/values-ru/000077500000000000000000000000001321131324000205475ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-ru/strings.xml000066400000000000000000000010621321131324000227610ustar00rootroot00000000000000 Сервис Ministro не найден.\nПриложение нельзя запустить. Этому приложению необходим сервис Ministro. Вы хотите его установить? Ваше приложение столкнулось с фатальной ошибкой и не может более работать. i2pd-2.17.0/qt/i2pd_qt/android/res/values-zh-rCN/000077500000000000000000000000001321131324000212225ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-zh-rCN/strings.xml000066400000000000000000000006111321131324000234330ustar00rootroot00000000000000 无法找到Ministro服务。\n应用程序无法启动。 此应用程序需要Ministro服务。您想安装它吗? 您的应用程序遇到一个致命错误导致它无法继续。 i2pd-2.17.0/qt/i2pd_qt/android/res/values-zh-rTW/000077500000000000000000000000001321131324000212545ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values-zh-rTW/strings.xml000066400000000000000000000006111321131324000234650ustar00rootroot00000000000000 無法找到Ministro服務。\n應用程序無法啟動。 此應用程序需要Ministro服務。您想安裝它嗎? 您的應用程序遇到一個致命錯誤導致它無法繼續。 i2pd-2.17.0/qt/i2pd_qt/android/res/values/000077500000000000000000000000001321131324000201235ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/res/values/libs.xml000066400000000000000000000012251321131324000215760ustar00rootroot00000000000000 https://download.qt-project.org/ministro/android/qt5/qt-5.4 i2pd-2.17.0/qt/i2pd_qt/android/res/values/strings.xml000066400000000000000000000011471321131324000223410ustar00rootroot00000000000000 Can\'t find Ministro service.\nThe application can\'t start. This application requires Ministro service. Would you like to install it? Your application encountered a fatal error and cannot continue. i2pd started i2pd stopped i2pd i2pd-2.17.0/qt/i2pd_qt/android/src/000077500000000000000000000000001321131324000166225ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/src/org/000077500000000000000000000000001321131324000174115ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/src/org/kde/000077500000000000000000000000001321131324000201545ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/src/org/kde/necessitas/000077500000000000000000000000001321131324000223155ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/000077500000000000000000000000001321131324000241615ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistro.aidl000066400000000000000000000066021321131324000267350ustar00rootroot00000000000000/* Copyright (c) 2011-2013, BogDan Vatra Contact: http://www.qt.io/licensing/ Commercial License Usage Licensees holding valid commercial Qt licenses may use this file in accordance with the commercial license agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company. For licensing terms and conditions see http://www.qt.io/terms-conditions. For further information use the contact form at http://www.qt.io/contact-us. BSD License Usage Alternatively, this file may be used under the BSD license as follows: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.kde.necessitas.ministro; import org.kde.necessitas.ministro.IMinistroCallback; interface IMinistro { /** * Check/download required libs to run the application * * param callback - interface used by Minsitro service to notify the client when the loader is ready * param parameters * parameters fields: * * Key Name Key type Explanations * "sources" StringArray Sources list from where Ministro will download the libs. Make sure you are using ONLY secure locations. * "repository" String Overwrites the default Ministro repository. Possible values: default, stable, testing and unstable * "required.modules" StringArray Required modules by your application * "application.title" String Application name, used to show more informations to user * "qt.provider" String Qt libs provider, currently only "necessitas" is supported. * "minimum.ministro.api" Integer Minimum Ministro API level, used to check if Ministro service compatible with your application. Current API Level is 3 ! * "minimum.qt.version" Integer Minimim Qt version (e.g. 0x040800, which means Qt 4.8.0, check http://qt-project.org/doc/qt-4.8/qtglobal.html#QT_VERSION)! */ void requestLoader(in IMinistroCallback callback, in Bundle parameters); } i2pd-2.17.0/qt/i2pd_qt/android/src/org/kde/necessitas/ministro/IMinistroCallback.aidl000066400000000000000000000064521321131324000303550ustar00rootroot00000000000000/* Copyright (c) 2011-2013, BogDan Vatra Contact: http://www.qt.io/licensing/ Commercial License Usage Licensees holding valid commercial Qt licenses may use this file in accordance with the commercial license agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company. For licensing terms and conditions see http://www.qt.io/terms-conditions. For further information use the contact form at http://www.qt.io/contact-us. BSD License Usage Alternatively, this file may be used under the BSD license as follows: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.kde.necessitas.ministro; oneway interface IMinistroCallback { /** * This method is called by the Ministro service back into the application which * implements this interface. * * param in - loaderParams * loaderParams fields: * * Key Name Key type Explanations * * "error.code" Integer See below * * "error.message" String Missing if no error, otherwise will contain the error message translated into phone language where available. * * "dex.path" String The list of jar/apk files containing classes and resources, needed to be passed to application DexClassLoader * * "lib.path" String The list of directories containing native libraries; may be missing, needed to be passed to application DexClassLoader * * "loader.class.name" String Loader class name. * * "error.code" field possible errors: * - 0 no error. * - 1 incompatible Ministro version. Ministro needs to be upgraded. * - 2 not all modules could be satisfy. * - 3 invalid parameters * - 4 invalid qt version * - 5 download canceled * * The parameter contains additional fields which are used by the loader to start your application, so it must be passed to the loader. */ void loaderReady(in Bundle loaderParams); } i2pd-2.17.0/qt/i2pd_qt/android/src/org/purplei2p/000077500000000000000000000000001321131324000213335ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/src/org/purplei2p/i2pd/000077500000000000000000000000001321131324000221715ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/src/org/purplei2p/i2pd/I2PDMainActivity.java000066400000000000000000000057701321131324000260650ustar00rootroot00000000000000package org.purplei2p.i2pd; import org.qtproject.qt5.android.bindings.QtActivity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; public class I2PDMainActivity extends QtActivity { private static I2PDMainActivity instance; public I2PDMainActivity() {} /* (non-Javadoc) * @see org.qtproject.qt5.android.bindings.QtActivity#onCreate(android.os.Bundle) */ @Override public void onCreate(Bundle savedInstanceState) { I2PDMainActivity.setInstance(this); super.onCreate(savedInstanceState); //set the app be foreground (do not unload when RAM needed) doBindService(); } /* (non-Javadoc) * @see org.qtproject.qt5.android.bindings.QtActivity#onDestroy() */ @Override protected void onDestroy() { I2PDMainActivity.setInstance(null); doUnbindService(); super.onDestroy(); } public static I2PDMainActivity getInstance() { return instance; } private static void setInstance(I2PDMainActivity instance) { I2PDMainActivity.instance = instance; } // private LocalService mBoundService; private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the service object we can use to // interact with the service. Because we have bound to a explicit // service that we know is running in our own process, we can // cast its IBinder to a concrete class and directly access it. // mBoundService = ((LocalService.LocalBinder)service).getService(); // Tell the user about this for our demo. // Toast.makeText(Binding.this, R.string.local_service_connected, // Toast.LENGTH_SHORT).show(); } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. // Because it is running in our same process, we should never // see this happen. // mBoundService = null; // Toast.makeText(Binding.this, R.string.local_service_disconnected, // Toast.LENGTH_SHORT).show(); } }; private boolean mIsBound; private void doBindService() { // Establish a connection with the service. We use an explicit // class name because we want a specific service implementation that // we know will be running in our own process (and thus won't be // supporting component replacement by other applications). bindService(new Intent(this, LocalService.class), mConnection, Context.BIND_AUTO_CREATE); mIsBound = true; } void doUnbindService() { if (mIsBound) { // Detach our existing connection. unbindService(mConnection); mIsBound = false; } } } i2pd-2.17.0/qt/i2pd_qt/android/src/org/purplei2p/i2pd/LocalService.java000066400000000000000000000062631321131324000254160ustar00rootroot00000000000000package org.purplei2p.i2pd; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.Toast; public class LocalService extends Service { // private NotificationManager mNM; // Unique Identification Number for the Notification. // We use it on Notification start, and to cancel it. private int NOTIFICATION = R.string.local_service_started; /** * Class for clients to access. Because we know this service always * runs in the same process as its clients, we don't need to deal with * IPC. */ public class LocalBinder extends Binder { LocalService getService() { return LocalService.this; } } @Override public void onCreate() { // mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); // Display a notification about us starting. We put an icon in the status bar. showNotification(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("LocalService", "Received start id " + startId + ": " + intent); return START_NOT_STICKY; } @Override public void onDestroy() { // Cancel the persistent notification. //mNM.cancel(NOTIFICATION); stopForeground(true); // Tell the user we stopped. Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show(); } @Override public IBinder onBind(Intent intent) { return mBinder; } // This is the object that receives interactions from clients. See // RemoteService for a more complete example. private final IBinder mBinder = new LocalBinder(); /** * Show a notification while this service is running. */ private void showNotification() { // In this sample, we'll use the same text for the ticker and the expanded notification CharSequence text = getText(R.string.local_service_started); // The PendingIntent to launch our activity if the user selects this notification PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, I2PDMainActivity.class), 0); // Set the info for the views that show in the notification panel. Notification notification = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.itoopie_notification_icon) // the status icon .setTicker(text) // the status text .setWhen(System.currentTimeMillis()) // the time stamp .setContentTitle(getText(R.string.local_service_label)) // the label of the entry .setContentText(text) // the contents of the entry .setContentIntent(contentIntent) // The intent to send when the entry is clicked .build(); // Send the notification. //mNM.notify(NOTIFICATION, notification); startForeground(NOTIFICATION, notification); } }i2pd-2.17.0/qt/i2pd_qt/android/src/org/qtproject/000077500000000000000000000000001321131324000214245ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/src/org/qtproject/qt5/000077500000000000000000000000001321131324000221355ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/src/org/qtproject/qt5/android/000077500000000000000000000000001321131324000235555ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/000077500000000000000000000000001321131324000253525ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtActivity.java000066400000000000000000002015341321131324000303230ustar00rootroot00000000000000/* Copyright (c) 2012-2013, BogDan Vatra Contact: http://www.qt.io/licensing/ Commercial License Usage Licensees holding valid commercial Qt licenses may use this file in accordance with the commercial license agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company. For licensing terms and conditions see http://www.qt.io/terms-conditions. For further information use the contact form at http://www.qt.io/contact-us. BSD License Usage Alternatively, this file may be used under the BSD license as follows: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.qtproject.qt5.android.bindings; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.InputStream; import java.io.FileOutputStream; import java.io.FileInputStream; import java.io.DataOutputStream; import java.io.DataInputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import org.kde.necessitas.ministro.IMinistro; import org.kde.necessitas.ministro.IMinistroCallback; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageInfo; import android.content.res.Configuration; import android.content.res.Resources.Theme; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.AttributeSet; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager.LayoutParams; import android.view.accessibility.AccessibilityEvent; import dalvik.system.DexClassLoader; //@ANDROID-11 import android.app.Fragment; import android.view.ActionMode; import android.view.ActionMode.Callback; //@ANDROID-11 public class QtActivity extends Activity { private final static int MINISTRO_INSTALL_REQUEST_CODE = 0xf3ee; // request code used to know when Ministro instalation is finished private static final int MINISTRO_API_LEVEL = 5; // Ministro api level (check IMinistro.aidl file) private static final int NECESSITAS_API_LEVEL = 2; // Necessitas api level used by platform plugin private static final int QT_VERSION = 0x050100; // This app requires at least Qt version 5.1.0 private static final String ERROR_CODE_KEY = "error.code"; private static final String ERROR_MESSAGE_KEY = "error.message"; private static final String DEX_PATH_KEY = "dex.path"; private static final String LIB_PATH_KEY = "lib.path"; private static final String LOADER_CLASS_NAME_KEY = "loader.class.name"; private static final String NATIVE_LIBRARIES_KEY = "native.libraries"; private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables"; private static final String APPLICATION_PARAMETERS_KEY = "application.parameters"; private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries"; private static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id"; private static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id"; private static final String MAIN_LIBRARY_KEY = "main.library"; private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes"; private static final String NECESSITAS_API_LEVEL_KEY = "necessitas.api.level"; private static final String EXTRACT_STYLE_KEY = "extract.android.style"; /// Ministro server parameter keys private static final String REQUIRED_MODULES_KEY = "required.modules"; private static final String APPLICATION_TITLE_KEY = "application.title"; private static final String MINIMUM_MINISTRO_API_KEY = "minimum.ministro.api"; private static final String MINIMUM_QT_VERSION_KEY = "minimum.qt.version"; private static final String SOURCES_KEY = "sources"; // needs MINISTRO_API_LEVEL >=3 !!! // Use this key to specify any 3rd party sources urls // Ministro will download these repositories into their // own folders, check http://community.kde.org/Necessitas/Ministro // for more details. private static final String REPOSITORY_KEY = "repository"; // use this key to overwrite the default ministro repsitory private static final String ANDROID_THEMES_KEY = "android.themes"; // themes that your application uses public String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application, // the parameters must not contain any white spaces // and must be separated with "\t" // e.g "-param1\t-param2=value2\t-param3\tvalue3" public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_STYLE=1\tQT_USE_ANDROID_NATIVE_DIALOGS=1\t"; // use this variable to add any environment variables to your application. // the env vars must be separated with "\t" // e.g. "ENV_VAR1=1\tENV_VAR2=2\t" // Currently the following vars are used by the android plugin: // * QT_USE_ANDROID_NATIVE_STYLE - 1 to use the android widget style if available. // * QT_USE_ANDROID_NATIVE_DIALOGS -1 to use the android native dialogs. public String[] QT_ANDROID_THEMES = null; // A list with all themes that your application want to use. // The name of the theme must be the same with any theme from // http://developer.android.com/reference/android/R.style.html // The most used themes are: // * "Theme" - (fallback) check http://developer.android.com/reference/android/R.style.html#Theme // * "Theme_Black" - check http://developer.android.com/reference/android/R.style.html#Theme_Black // * "Theme_Light" - (default for API <=10) check http://developer.android.com/reference/android/R.style.html#Theme_Light // * "Theme_Holo" - check http://developer.android.com/reference/android/R.style.html#Theme_Holo // * "Theme_Holo_Light" - (default for API 11-13) check http://developer.android.com/reference/android/R.style.html#Theme_Holo_Light // * "Theme_DeviceDefault" - check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault // * "Theme_DeviceDefault_Light" - (default for API 14+) check http://developer.android.com/reference/android/R.style.html#Theme_DeviceDefault_Light public String QT_ANDROID_DEFAULT_THEME = null; // sets the default theme. private static final int INCOMPATIBLE_MINISTRO_VERSION = 1; // Incompatible Ministro version. Ministro needs to be upgraded. private static final int BUFFER_SIZE = 1024; private ActivityInfo m_activityInfo = null; // activity info object, used to access the libs and the strings private DexClassLoader m_classLoader = null; // loader object private String[] m_sources = {"https://download.qt-project.org/ministro/android/qt5/qt-5.2"}; // Make sure you are using ONLY secure locations private String m_repository = "default"; // Overwrites the default Ministro repository // Possible values: // * default - Ministro default repository set with "Ministro configuration tool". // By default the stable version is used. Only this or stable repositories should // be used in production. // * stable - stable repository, only this and default repositories should be used // in production. // * testing - testing repository, DO NOT use this repository in production, // this repository is used to push a new release, and should be used to test your application. // * unstable - unstable repository, DO NOT use this repository in production, // this repository is used to push Qt snapshots. private String[] m_qtLibs = null; // required qt libs private int m_displayDensity = -1; public QtActivity() { if (Build.VERSION.SDK_INT <= 10) { QT_ANDROID_THEMES = new String[] {"Theme_Light"}; QT_ANDROID_DEFAULT_THEME = "Theme_Light"; } else if ((Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= 13) || Build.VERSION.SDK_INT >= 21){ QT_ANDROID_THEMES = new String[] {"Theme_Holo_Light"}; QT_ANDROID_DEFAULT_THEME = "Theme_Holo_Light"; } else { QT_ANDROID_THEMES = new String[] {"Theme_DeviceDefault_Light"}; QT_ANDROID_DEFAULT_THEME = "Theme_DeviceDefault_Light"; } } // this function is used to load and start the loader private void loadApplication(Bundle loaderParams) { try { final int errorCode = loaderParams.getInt(ERROR_CODE_KEY); if (errorCode != 0) { if (errorCode == INCOMPATIBLE_MINISTRO_VERSION) { downloadUpgradeMinistro(loaderParams.getString(ERROR_MESSAGE_KEY)); return; } // fatal error, show the error and quit AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create(); errorDialog.setMessage(loaderParams.getString(ERROR_MESSAGE_KEY)); errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }); errorDialog.show(); return; } // add all bundled Qt libs to loader params ArrayList libs = new ArrayList(); if ( m_activityInfo.metaData.containsKey("android.app.bundled_libs_resource_id") ) libs.addAll(Arrays.asList(getResources().getStringArray(m_activityInfo.metaData.getInt("android.app.bundled_libs_resource_id")))); String libName = null; if ( m_activityInfo.metaData.containsKey("android.app.lib_name") ) { libName = m_activityInfo.metaData.getString("android.app.lib_name"); loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function } loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs); loaderParams.putInt(NECESSITAS_API_LEVEL_KEY, NECESSITAS_API_LEVEL); // load and start QtLoader class m_classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written. loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists) getClassLoader()); // parent loader @SuppressWarnings("rawtypes") Class loaderClass = m_classLoader.loadClass(loaderParams.getString(LOADER_CLASS_NAME_KEY)); // load QtLoader class Object qtLoader = loaderClass.newInstance(); // create an instance Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication", Activity.class, ClassLoader.class, Bundle.class); if (!(Boolean)prepareAppMethod.invoke(qtLoader, this, m_classLoader, loaderParams)) throw new Exception(""); QtApplication.setQtActivityDelegate(qtLoader); // now load the application library so it's accessible from this class loader if (libName != null) System.loadLibrary(libName); Method startAppMethod=qtLoader.getClass().getMethod("startApplication"); if (!(Boolean)startAppMethod.invoke(qtLoader)) throw new Exception(""); } catch (Exception e) { e.printStackTrace(); AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create(); if (m_activityInfo.metaData.containsKey("android.app.fatal_error_msg")) errorDialog.setMessage(m_activityInfo.metaData.getString("android.app.fatal_error_msg")); else errorDialog.setMessage("Fatal error, your application can't be started."); errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }); errorDialog.show(); } } private ServiceConnection m_ministroConnection=new ServiceConnection() { private IMinistro m_service = null; @Override public void onServiceConnected(ComponentName name, IBinder service) { m_service = IMinistro.Stub.asInterface(service); try { if (m_service != null) { Bundle parameters = new Bundle(); parameters.putStringArray(REQUIRED_MODULES_KEY, m_qtLibs); parameters.putString(APPLICATION_TITLE_KEY, (String)QtActivity.this.getTitle()); parameters.putInt(MINIMUM_MINISTRO_API_KEY, MINISTRO_API_LEVEL); parameters.putInt(MINIMUM_QT_VERSION_KEY, QT_VERSION); parameters.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES); if (APPLICATION_PARAMETERS != null) parameters.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS); parameters.putStringArray(SOURCES_KEY, m_sources); parameters.putString(REPOSITORY_KEY, m_repository); if (QT_ANDROID_THEMES != null) parameters.putStringArray(ANDROID_THEMES_KEY, QT_ANDROID_THEMES); m_service.requestLoader(m_ministroCallback, parameters); } } catch (RemoteException e) { e.printStackTrace(); } } private IMinistroCallback m_ministroCallback = new IMinistroCallback.Stub() { // this function is called back by Ministro. @Override public void loaderReady(final Bundle loaderParams) throws RemoteException { runOnUiThread(new Runnable() { @Override public void run() { unbindService(m_ministroConnection); loadApplication(loaderParams); } }); } }; @Override public void onServiceDisconnected(ComponentName name) { m_service = null; } }; private void downloadUpgradeMinistro(String msg) { AlertDialog.Builder downloadDialog = new AlertDialog.Builder(this); downloadDialog.setMessage(msg); downloadDialog.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { try { Uri uri = Uri.parse("market://search?q=pname:org.kde.necessitas.ministro"); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivityForResult(intent, MINISTRO_INSTALL_REQUEST_CODE); } catch (Exception e) { e.printStackTrace(); ministroNotFound(); } } }); downloadDialog.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { QtActivity.this.finish(); } }); downloadDialog.show(); } private void ministroNotFound() { AlertDialog errorDialog = new AlertDialog.Builder(QtActivity.this).create(); if (m_activityInfo.metaData.containsKey("android.app.ministro_not_found_msg")) errorDialog.setMessage(m_activityInfo.metaData.getString("android.app.ministro_not_found_msg")); else errorDialog.setMessage("Can't find Ministro service.\nThe application can't start."); errorDialog.setButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }); errorDialog.show(); } static private void copyFile(InputStream inputStream, OutputStream outputStream) throws IOException { byte[] buffer = new byte[BUFFER_SIZE]; int count; while ((count = inputStream.read(buffer)) > 0) outputStream.write(buffer, 0, count); } private void copyAsset(String source, String destination) throws IOException { // Already exists, we don't have to do anything File destinationFile = new File(destination); if (destinationFile.exists()) return; File parentDirectory = destinationFile.getParentFile(); if (!parentDirectory.exists()) parentDirectory.mkdirs(); destinationFile.createNewFile(); AssetManager assetsManager = getAssets(); InputStream inputStream = assetsManager.open(source); OutputStream outputStream = new FileOutputStream(destinationFile); copyFile(inputStream, outputStream); inputStream.close(); outputStream.close(); } private static void createBundledBinary(String source, String destination) throws IOException { // Already exists, we don't have to do anything File destinationFile = new File(destination); if (destinationFile.exists()) return; File parentDirectory = destinationFile.getParentFile(); if (!parentDirectory.exists()) parentDirectory.mkdirs(); destinationFile.createNewFile(); InputStream inputStream = new FileInputStream(source); OutputStream outputStream = new FileOutputStream(destinationFile); copyFile(inputStream, outputStream); inputStream.close(); outputStream.close(); } private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) { File versionFile = new File(pluginsPrefix + "cache.version"); long cacheVersion = 0; if (versionFile.exists() && versionFile.canRead()) { try { DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile)); cacheVersion = inputStream.readLong(); inputStream.close(); } catch (Exception e) { e.printStackTrace(); } } if (cacheVersion != packageVersion) { deleteRecursively(new File(pluginsPrefix)); return true; } else { return false; } } private void extractBundledPluginsAndImports(String pluginsPrefix) throws IOException { ArrayList libs = new ArrayList(); String libsDir = getApplicationInfo().nativeLibraryDir + "/"; long packageVersion = -1; try { PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); packageVersion = packageInfo.lastUpdateTime; } catch (Exception e) { e.printStackTrace(); } if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) return; { File versionFile = new File(pluginsPrefix + "cache.version"); File parentDirectory = versionFile.getParentFile(); if (!parentDirectory.exists()) parentDirectory.mkdirs(); versionFile.createNewFile(); DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile)); outputStream.writeLong(packageVersion); outputStream.close(); } { String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY; java.util.Set keys = m_activityInfo.metaData.keySet(); if (m_activityInfo.metaData.containsKey(key)) { String[] list = getResources().getStringArray(m_activityInfo.metaData.getInt(key)); for (String bundledImportBinary : list) { String[] split = bundledImportBinary.split(":"); String sourceFileName = libsDir + split[0]; String destinationFileName = pluginsPrefix + split[1]; createBundledBinary(sourceFileName, destinationFileName); } } } { String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY; if (m_activityInfo.metaData.containsKey(key)) { String[] list = getResources().getStringArray(m_activityInfo.metaData.getInt(key)); for (String fileName : list) { String[] split = fileName.split(":"); String sourceFileName = split[0]; String destinationFileName = pluginsPrefix + split[1]; copyAsset(sourceFileName, destinationFileName); } } } } private void deleteRecursively(File directory) { File[] files = directory.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) deleteRecursively(file); else file.delete(); } directory.delete(); } } private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix) { File newCache = new File(localPrefix); if (!newCache.exists()) { { File oldPluginsCache = new File(oldLocalPrefix + "plugins/"); if (oldPluginsCache.exists() && oldPluginsCache.isDirectory()) deleteRecursively(oldPluginsCache); } { File oldImportsCache = new File(oldLocalPrefix + "imports/"); if (oldImportsCache.exists() && oldImportsCache.isDirectory()) deleteRecursively(oldImportsCache); } { File oldQmlCache = new File(oldLocalPrefix + "qml/"); if (oldQmlCache.exists() && oldQmlCache.isDirectory()) deleteRecursively(oldQmlCache); } } } private void startApp(final boolean firstStart) { try { if (m_activityInfo.metaData.containsKey("android.app.qt_sources_resource_id")) { int resourceId = m_activityInfo.metaData.getInt("android.app.qt_sources_resource_id"); m_sources = getResources().getStringArray(resourceId); } if (m_activityInfo.metaData.containsKey("android.app.repository")) m_repository = m_activityInfo.metaData.getString("android.app.repository"); if (m_activityInfo.metaData.containsKey("android.app.qt_libs_resource_id")) { int resourceId = m_activityInfo.metaData.getInt("android.app.qt_libs_resource_id"); m_qtLibs = getResources().getStringArray(resourceId); } if (m_activityInfo.metaData.containsKey("android.app.use_local_qt_libs") && m_activityInfo.metaData.getInt("android.app.use_local_qt_libs") == 1) { ArrayList libraryList = new ArrayList(); String localPrefix = "/data/local/tmp/qt/"; if (m_activityInfo.metaData.containsKey("android.app.libs_prefix")) localPrefix = m_activityInfo.metaData.getString("android.app.libs_prefix"); String pluginsPrefix = localPrefix; boolean bundlingQtLibs = false; if (m_activityInfo.metaData.containsKey("android.app.bundle_local_qt_libs") && m_activityInfo.metaData.getInt("android.app.bundle_local_qt_libs") == 1) { localPrefix = getApplicationInfo().dataDir + "/"; pluginsPrefix = localPrefix + "qt-reserved-files/"; cleanOldCacheIfNecessary(localPrefix, pluginsPrefix); extractBundledPluginsAndImports(pluginsPrefix); bundlingQtLibs = true; } if (m_qtLibs != null) { for (int i=0;i 0) { if (lib.startsWith("lib/")) libraryList.add(localPrefix + lib); else libraryList.add(pluginsPrefix + lib); } } } String dexPaths = new String(); String pathSeparator = System.getProperty("path.separator", ":"); if (!bundlingQtLibs && m_activityInfo.metaData.containsKey("android.app.load_local_jars")) { String[] jarFiles = m_activityInfo.metaData.getString("android.app.load_local_jars").split(":"); for (String jar:jarFiles) { if (jar.length() > 0) { if (dexPaths.length() > 0) dexPaths += pathSeparator; dexPaths += localPrefix + jar; } } } Bundle loaderParams = new Bundle(); loaderParams.putInt(ERROR_CODE_KEY, 0); loaderParams.putString(DEX_PATH_KEY, dexPaths); loaderParams.putString(LOADER_CLASS_NAME_KEY, "org.qtproject.qt5.android.QtActivityDelegate"); if (m_activityInfo.metaData.containsKey("android.app.static_init_classes")) { loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY, m_activityInfo.metaData.getString("android.app.static_init_classes").split(":")); } loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList); String themePath = getApplicationInfo().dataDir + "/qt-reserved-files/android-style/"; String stylePath = themePath + m_displayDensity + "/"; if (!(new File(stylePath)).exists()) loaderParams.putString(EXTRACT_STYLE_KEY, stylePath); ENVIRONMENT_VARIABLES += "\tMINISTRO_ANDROID_STYLE_PATH=" + stylePath + "\tQT_ANDROID_THEMES_ROOT_PATH=" + themePath; loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES + "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml" + "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports" + "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins"); if (APPLICATION_PARAMETERS != null) { loaderParams.putString(APPLICATION_PARAMETERS_KEY, APPLICATION_PARAMETERS); } else { Intent intent = getIntent(); if (intent != null) { String parameters = intent.getStringExtra("applicationArguments"); if (parameters != null) loaderParams.putString(APPLICATION_PARAMETERS_KEY, parameters.replace(' ', '\t')); } } loadApplication(loaderParams); return; } try { if (!bindService(new Intent(org.kde.necessitas.ministro.IMinistro.class.getCanonicalName()), m_ministroConnection, Context.BIND_AUTO_CREATE)) { throw new SecurityException(""); } } catch (Exception e) { if (firstStart) { String msg = "This application requires Ministro service. Would you like to install it?"; if (m_activityInfo.metaData.containsKey("android.app.ministro_needed_msg")) msg = m_activityInfo.metaData.getString("android.app.ministro_needed_msg"); downloadUpgradeMinistro(msg); } else { ministroNotFound(); } } } catch (Exception e) { Log.e(QtApplication.QtTAG, "Can't create main activity", e); } } /////////////////////////// forward all notifications //////////////////////////// /////////////////////////// Super class calls //////////////////////////////////// /////////////// PLEASE DO NOT CHANGE THE FOLLOWING CODE ////////////////////////// ////////////////////////////////////////////////////////////////////////////////// @Override public boolean dispatchKeyEvent(KeyEvent event) { if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyEvent != null) return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyEvent, event); else return super.dispatchKeyEvent(event); } public boolean super_dispatchKeyEvent(KeyEvent event) { return super.dispatchKeyEvent(event); } //--------------------------------------------------------------------------- @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { if (QtApplication.m_delegateObject != null && QtApplication.dispatchPopulateAccessibilityEvent != null) return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchPopulateAccessibilityEvent, event); else return super.dispatchPopulateAccessibilityEvent(event); } public boolean super_dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { return super_dispatchPopulateAccessibilityEvent(event); } //--------------------------------------------------------------------------- @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (QtApplication.m_delegateObject != null && QtApplication.dispatchTouchEvent != null) return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTouchEvent, ev); else return super.dispatchTouchEvent(ev); } public boolean super_dispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); } //--------------------------------------------------------------------------- @Override public boolean dispatchTrackballEvent(MotionEvent ev) { if (QtApplication.m_delegateObject != null && QtApplication.dispatchTrackballEvent != null) return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTrackballEvent, ev); else return super.dispatchTrackballEvent(ev); } public boolean super_dispatchTrackballEvent(MotionEvent event) { return super.dispatchTrackballEvent(event); } //--------------------------------------------------------------------------- @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (QtApplication.m_delegateObject != null && QtApplication.onActivityResult != null) { QtApplication.invokeDelegateMethod(QtApplication.onActivityResult, requestCode, resultCode, data); return; } if (requestCode == MINISTRO_INSTALL_REQUEST_CODE) startApp(false); super.onActivityResult(requestCode, resultCode, data); } public void super_onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); } //--------------------------------------------------------------------------- @Override protected void onApplyThemeResource(Theme theme, int resid, boolean first) { if (!QtApplication.invokeDelegate(theme, resid, first).invoked) super.onApplyThemeResource(theme, resid, first); } public void super_onApplyThemeResource(Theme theme, int resid, boolean first) { super.onApplyThemeResource(theme, resid, first); } //--------------------------------------------------------------------------- @Override protected void onChildTitleChanged(Activity childActivity, CharSequence title) { if (!QtApplication.invokeDelegate(childActivity, title).invoked) super.onChildTitleChanged(childActivity, title); } public void super_onChildTitleChanged(Activity childActivity, CharSequence title) { super.onChildTitleChanged(childActivity, title); } //--------------------------------------------------------------------------- @Override public void onConfigurationChanged(Configuration newConfig) { if (!QtApplication.invokeDelegate(newConfig).invoked) super.onConfigurationChanged(newConfig); } public void super_onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } //--------------------------------------------------------------------------- @Override public void onContentChanged() { if (!QtApplication.invokeDelegate().invoked) super.onContentChanged(); } public void super_onContentChanged() { super.onContentChanged(); } //--------------------------------------------------------------------------- @Override public boolean onContextItemSelected(MenuItem item) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(item); if (res.invoked) return (Boolean)res.methodReturns; else return super.onContextItemSelected(item); } public boolean super_onContextItemSelected(MenuItem item) { return super.onContextItemSelected(item); } //--------------------------------------------------------------------------- @Override public void onContextMenuClosed(Menu menu) { if (!QtApplication.invokeDelegate(menu).invoked) super.onContextMenuClosed(menu); } public void super_onContextMenuClosed(Menu menu) { super.onContextMenuClosed(menu); } //--------------------------------------------------------------------------- @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); try { m_activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); for (Field f : Class.forName("android.R$style").getDeclaredFields()) { if (f.getInt(null) == m_activityInfo.getThemeResource()) { QT_ANDROID_THEMES = new String[] {f.getName()}; QT_ANDROID_DEFAULT_THEME = f.getName(); } } } catch (Exception e) { e.printStackTrace(); finish(); return; } try { setTheme(Class.forName("android.R$style").getDeclaredField(QT_ANDROID_DEFAULT_THEME).getInt(null)); } catch (Exception e) { e.printStackTrace(); } if (Build.VERSION.SDK_INT > 10) { try { requestWindowFeature(Window.class.getField("FEATURE_ACTION_BAR").getInt(null)); } catch (Exception e) { e.printStackTrace(); } } else { requestWindowFeature(Window.FEATURE_NO_TITLE); } if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) { QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState); return; } m_displayDensity = getResources().getDisplayMetrics().densityDpi; ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME + "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t"; if (null == getLastNonConfigurationInstance()) { // if splash screen is defined, then show it if (m_activityInfo.metaData.containsKey("android.app.splash_screen_drawable")) getWindow().setBackgroundDrawableResource(m_activityInfo.metaData.getInt("android.app.splash_screen_drawable")); else getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000)); if (m_activityInfo.metaData.containsKey("android.app.background_running") && m_activityInfo.metaData.getBoolean("android.app.background_running")) { ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t"; } else { ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t"; } if (m_activityInfo.metaData.containsKey("android.app.auto_screen_scale_factor") && m_activityInfo.metaData.getBoolean("android.app.auto_screen_scale_factor")) { ENVIRONMENT_VARIABLES += "QT_AUTO_SCREEN_SCALE_FACTOR=1\t"; } startApp(true); } } //--------------------------------------------------------------------------- @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { if (!QtApplication.invokeDelegate(menu, v, menuInfo).invoked) super.onCreateContextMenu(menu, v, menuInfo); } public void super_onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); } //--------------------------------------------------------------------------- @Override public CharSequence onCreateDescription() { QtApplication.InvokeResult res = QtApplication.invokeDelegate(); if (res.invoked) return (CharSequence)res.methodReturns; else return super.onCreateDescription(); } public CharSequence super_onCreateDescription() { return super.onCreateDescription(); } //--------------------------------------------------------------------------- @Override protected Dialog onCreateDialog(int id) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(id); if (res.invoked) return (Dialog)res.methodReturns; else return super.onCreateDialog(id); } public Dialog super_onCreateDialog(int id) { return super.onCreateDialog(id); } //--------------------------------------------------------------------------- @Override public boolean onCreateOptionsMenu(Menu menu) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu); if (res.invoked) return (Boolean)res.methodReturns; else return super.onCreateOptionsMenu(menu); } public boolean super_onCreateOptionsMenu(Menu menu) { return super.onCreateOptionsMenu(menu); } //--------------------------------------------------------------------------- @Override public boolean onCreatePanelMenu(int featureId, Menu menu) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu); if (res.invoked) return (Boolean)res.methodReturns; else return super.onCreatePanelMenu(featureId, menu); } public boolean super_onCreatePanelMenu(int featureId, Menu menu) { return super.onCreatePanelMenu(featureId, menu); } //--------------------------------------------------------------------------- @Override public View onCreatePanelView(int featureId) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId); if (res.invoked) return (View)res.methodReturns; else return super.onCreatePanelView(featureId); } public View super_onCreatePanelView(int featureId) { return super.onCreatePanelView(featureId); } //--------------------------------------------------------------------------- @Override public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(outBitmap, canvas); if (res.invoked) return (Boolean)res.methodReturns; else return super.onCreateThumbnail(outBitmap, canvas); } public boolean super_onCreateThumbnail(Bitmap outBitmap, Canvas canvas) { return super.onCreateThumbnail(outBitmap, canvas); } //--------------------------------------------------------------------------- @Override public View onCreateView(String name, Context context, AttributeSet attrs) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(name, context, attrs); if (res.invoked) return (View)res.methodReturns; else return super.onCreateView(name, context, attrs); } public View super_onCreateView(String name, Context context, AttributeSet attrs) { return super.onCreateView(name, context, attrs); } //--------------------------------------------------------------------------- @Override protected void onDestroy() { super.onDestroy(); QtApplication.invokeDelegate(); } //--------------------------------------------------------------------------- @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyDown, keyCode, event); else return super.onKeyDown(keyCode, event); } public boolean super_onKeyDown(int keyCode, KeyEvent event) { return super.onKeyDown(keyCode, event); } //--------------------------------------------------------------------------- @Override public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { if (QtApplication.m_delegateObject != null && QtApplication.onKeyMultiple != null) return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyMultiple, keyCode, repeatCount, event); else return super.onKeyMultiple(keyCode, repeatCount, event); } public boolean super_onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { return super.onKeyMultiple(keyCode, repeatCount, event); } //--------------------------------------------------------------------------- @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyUp, keyCode, event); else return super.onKeyUp(keyCode, event); } public boolean super_onKeyUp(int keyCode, KeyEvent event) { return super.onKeyUp(keyCode, event); } //--------------------------------------------------------------------------- @Override public void onLowMemory() { if (!QtApplication.invokeDelegate().invoked) super.onLowMemory(); } //--------------------------------------------------------------------------- @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, item); if (res.invoked) return (Boolean)res.methodReturns; else return super.onMenuItemSelected(featureId, item); } public boolean super_onMenuItemSelected(int featureId, MenuItem item) { return super.onMenuItemSelected(featureId, item); } //--------------------------------------------------------------------------- @Override public boolean onMenuOpened(int featureId, Menu menu) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu); if (res.invoked) return (Boolean)res.methodReturns; else return super.onMenuOpened(featureId, menu); } public boolean super_onMenuOpened(int featureId, Menu menu) { return super.onMenuOpened(featureId, menu); } //--------------------------------------------------------------------------- @Override protected void onNewIntent(Intent intent) { if (!QtApplication.invokeDelegate(intent).invoked) super.onNewIntent(intent); } public void super_onNewIntent(Intent intent) { super.onNewIntent(intent); } //--------------------------------------------------------------------------- @Override public boolean onOptionsItemSelected(MenuItem item) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(item); if (res.invoked) return (Boolean)res.methodReturns; else return super.onOptionsItemSelected(item); } public boolean super_onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } //--------------------------------------------------------------------------- @Override public void onOptionsMenuClosed(Menu menu) { if (!QtApplication.invokeDelegate(menu).invoked) super.onOptionsMenuClosed(menu); } public void super_onOptionsMenuClosed(Menu menu) { super.onOptionsMenuClosed(menu); } //--------------------------------------------------------------------------- @Override public void onPanelClosed(int featureId, Menu menu) { if (!QtApplication.invokeDelegate(featureId, menu).invoked) super.onPanelClosed(featureId, menu); } public void super_onPanelClosed(int featureId, Menu menu) { super.onPanelClosed(featureId, menu); } //--------------------------------------------------------------------------- @Override protected void onPause() { super.onPause(); QtApplication.invokeDelegate(); } //--------------------------------------------------------------------------- @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); QtApplication.invokeDelegate(savedInstanceState); } //--------------------------------------------------------------------------- @Override protected void onPostResume() { super.onPostResume(); QtApplication.invokeDelegate(); } //--------------------------------------------------------------------------- @Override protected void onPrepareDialog(int id, Dialog dialog) { if (!QtApplication.invokeDelegate(id, dialog).invoked) super.onPrepareDialog(id, dialog); } public void super_onPrepareDialog(int id, Dialog dialog) { super.onPrepareDialog(id, dialog); } //--------------------------------------------------------------------------- @Override public boolean onPrepareOptionsMenu(Menu menu) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu); if (res.invoked) return (Boolean)res.methodReturns; else return super.onPrepareOptionsMenu(menu); } public boolean super_onPrepareOptionsMenu(Menu menu) { return super.onPrepareOptionsMenu(menu); } //--------------------------------------------------------------------------- @Override public boolean onPreparePanel(int featureId, View view, Menu menu) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, view, menu); if (res.invoked) return (Boolean)res.methodReturns; else return super.onPreparePanel(featureId, view, menu); } public boolean super_onPreparePanel(int featureId, View view, Menu menu) { return super.onPreparePanel(featureId, view, menu); } //--------------------------------------------------------------------------- @Override protected void onRestart() { super.onRestart(); QtApplication.invokeDelegate(); } //--------------------------------------------------------------------------- @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { if (!QtApplication.invokeDelegate(savedInstanceState).invoked) super.onRestoreInstanceState(savedInstanceState); } public void super_onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); } //--------------------------------------------------------------------------- @Override protected void onResume() { super.onResume(); QtApplication.invokeDelegate(); } //--------------------------------------------------------------------------- @Override public Object onRetainNonConfigurationInstance() { QtApplication.InvokeResult res = QtApplication.invokeDelegate(); if (res.invoked) return res.methodReturns; else return super.onRetainNonConfigurationInstance(); } public Object super_onRetainNonConfigurationInstance() { return super.onRetainNonConfigurationInstance(); } //--------------------------------------------------------------------------- @Override protected void onSaveInstanceState(Bundle outState) { if (!QtApplication.invokeDelegate(outState).invoked) super.onSaveInstanceState(outState); } public void super_onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } //--------------------------------------------------------------------------- @Override public boolean onSearchRequested() { QtApplication.InvokeResult res = QtApplication.invokeDelegate(); if (res.invoked) return (Boolean)res.methodReturns; else return super.onSearchRequested(); } public boolean super_onSearchRequested() { return super.onSearchRequested(); } //--------------------------------------------------------------------------- @Override protected void onStart() { super.onStart(); QtApplication.invokeDelegate(); } //--------------------------------------------------------------------------- @Override protected void onStop() { super.onStop(); QtApplication.invokeDelegate(); } //--------------------------------------------------------------------------- @Override protected void onTitleChanged(CharSequence title, int color) { if (!QtApplication.invokeDelegate(title, color).invoked) super.onTitleChanged(title, color); } public void super_onTitleChanged(CharSequence title, int color) { super.onTitleChanged(title, color); } //--------------------------------------------------------------------------- @Override public boolean onTouchEvent(MotionEvent event) { if (QtApplication.m_delegateObject != null && QtApplication.onTouchEvent != null) return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTouchEvent, event); else return super.onTouchEvent(event); } public boolean super_onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } //--------------------------------------------------------------------------- @Override public boolean onTrackballEvent(MotionEvent event) { if (QtApplication.m_delegateObject != null && QtApplication.onTrackballEvent != null) return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTrackballEvent, event); else return super.onTrackballEvent(event); } public boolean super_onTrackballEvent(MotionEvent event) { return super.onTrackballEvent(event); } //--------------------------------------------------------------------------- @Override public void onUserInteraction() { if (!QtApplication.invokeDelegate().invoked) super.onUserInteraction(); } public void super_onUserInteraction() { super.onUserInteraction(); } //--------------------------------------------------------------------------- @Override protected void onUserLeaveHint() { if (!QtApplication.invokeDelegate().invoked) super.onUserLeaveHint(); } public void super_onUserLeaveHint() { super.onUserLeaveHint(); } //--------------------------------------------------------------------------- @Override public void onWindowAttributesChanged(LayoutParams params) { if (!QtApplication.invokeDelegate(params).invoked) super.onWindowAttributesChanged(params); } public void super_onWindowAttributesChanged(LayoutParams params) { super.onWindowAttributesChanged(params); } //--------------------------------------------------------------------------- @Override public void onWindowFocusChanged(boolean hasFocus) { if (!QtApplication.invokeDelegate(hasFocus).invoked) super.onWindowFocusChanged(hasFocus); } public void super_onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); } //--------------------------------------------------------------------------- //////////////// Activity API 5 ///////////// //@ANDROID-5 @Override public void onAttachedToWindow() { if (!QtApplication.invokeDelegate().invoked) super.onAttachedToWindow(); } public void super_onAttachedToWindow() { super.onAttachedToWindow(); } //--------------------------------------------------------------------------- @Override public void onBackPressed() { if (!QtApplication.invokeDelegate().invoked) super.onBackPressed(); } public void super_onBackPressed() { super.onBackPressed(); } //--------------------------------------------------------------------------- @Override public void onDetachedFromWindow() { if (!QtApplication.invokeDelegate().invoked) super.onDetachedFromWindow(); } public void super_onDetachedFromWindow() { super.onDetachedFromWindow(); } //--------------------------------------------------------------------------- @Override public boolean onKeyLongPress(int keyCode, KeyEvent event) { if (QtApplication.m_delegateObject != null && QtApplication.onKeyLongPress != null) return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyLongPress, keyCode, event); else return super.onKeyLongPress(keyCode, event); } public boolean super_onKeyLongPress(int keyCode, KeyEvent event) { return super.onKeyLongPress(keyCode, event); } //--------------------------------------------------------------------------- //@ANDROID-5 //////////////// Activity API 8 ///////////// //@ANDROID-8 @Override protected Dialog onCreateDialog(int id, Bundle args) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(id, args); if (res.invoked) return (Dialog)res.methodReturns; else return super.onCreateDialog(id, args); } public Dialog super_onCreateDialog(int id, Bundle args) { return super.onCreateDialog(id, args); } //--------------------------------------------------------------------------- @Override protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { if (!QtApplication.invokeDelegate(id, dialog, args).invoked) super.onPrepareDialog(id, dialog, args); } public void super_onPrepareDialog(int id, Dialog dialog, Bundle args) { super.onPrepareDialog(id, dialog, args); } //--------------------------------------------------------------------------- //@ANDROID-8 //////////////// Activity API 11 ///////////// //@ANDROID-11 @Override public boolean dispatchKeyShortcutEvent(KeyEvent event) { if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyShortcutEvent != null) return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyShortcutEvent, event); else return super.dispatchKeyShortcutEvent(event); } public boolean super_dispatchKeyShortcutEvent(KeyEvent event) { return super.dispatchKeyShortcutEvent(event); } //--------------------------------------------------------------------------- @Override public void onActionModeFinished(ActionMode mode) { if (!QtApplication.invokeDelegate(mode).invoked) super.onActionModeFinished(mode); } public void super_onActionModeFinished(ActionMode mode) { super.onActionModeFinished(mode); } //--------------------------------------------------------------------------- @Override public void onActionModeStarted(ActionMode mode) { if (!QtApplication.invokeDelegate(mode).invoked) super.onActionModeStarted(mode); } public void super_onActionModeStarted(ActionMode mode) { super.onActionModeStarted(mode); } //--------------------------------------------------------------------------- @Override public void onAttachFragment(Fragment fragment) { if (!QtApplication.invokeDelegate(fragment).invoked) super.onAttachFragment(fragment); } public void super_onAttachFragment(Fragment fragment) { super.onAttachFragment(fragment); } //--------------------------------------------------------------------------- @Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(parent, name, context, attrs); if (res.invoked) return (View)res.methodReturns; else return super.onCreateView(parent, name, context, attrs); } public View super_onCreateView(View parent, String name, Context context, AttributeSet attrs) { return super.onCreateView(parent, name, context, attrs); } //--------------------------------------------------------------------------- @Override public boolean onKeyShortcut(int keyCode, KeyEvent event) { if (QtApplication.m_delegateObject != null && QtApplication.onKeyShortcut != null) return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyShortcut, keyCode,event); else return super.onKeyShortcut(keyCode, event); } public boolean super_onKeyShortcut(int keyCode, KeyEvent event) { return super.onKeyShortcut(keyCode, event); } //--------------------------------------------------------------------------- @Override public ActionMode onWindowStartingActionMode(Callback callback) { QtApplication.InvokeResult res = QtApplication.invokeDelegate(callback); if (res.invoked) return (ActionMode)res.methodReturns; else return super.onWindowStartingActionMode(callback); } public ActionMode super_onWindowStartingActionMode(Callback callback) { return super.onWindowStartingActionMode(callback); } //--------------------------------------------------------------------------- //@ANDROID-11 //////////////// Activity API 12 ///////////// ////@ANDROID-12 // @Override // public boolean dispatchGenericMotionEvent(MotionEvent ev) // { // if (QtApplication.m_delegateObject != null && QtApplication.dispatchGenericMotionEvent != null) // return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchGenericMotionEvent, ev); // else // return super.dispatchGenericMotionEvent(ev); // } // public boolean super_dispatchGenericMotionEvent(MotionEvent event) // { // return super.dispatchGenericMotionEvent(event); // } // //--------------------------------------------------------------------------- // // @Override // public boolean onGenericMotionEvent(MotionEvent event) // { // if (QtApplication.m_delegateObject != null && QtApplication.onGenericMotionEvent != null) // return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onGenericMotionEvent, event); // else // return super.onGenericMotionEvent(event); // } // public boolean super_onGenericMotionEvent(MotionEvent event) // { // return super.onGenericMotionEvent(event); // } // //--------------------------------------------------------------------------- ////@ANDROID-12 } i2pd-2.17.0/qt/i2pd_qt/android/src/org/qtproject/qt5/android/bindings/QtApplication.java000066400000000000000000000151111321131324000307640ustar00rootroot00000000000000/* Copyright (c) 2012-2013, BogDan Vatra Contact: http://www.qt.io/licensing/ Commercial License Usage Licensees holding valid commercial Qt licenses may use this file in accordance with the commercial license agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company. For licensing terms and conditions see http://www.qt.io/terms-conditions. For further information use the contact form at http://www.qt.io/contact-us. BSD License Usage Alternatively, this file may be used under the BSD license as follows: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.qtproject.qt5.android.bindings; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import android.app.Application; public class QtApplication extends Application { public final static String QtTAG = "Qt"; public static Object m_delegateObject = null; public static HashMap> m_delegateMethods= new HashMap>(); public static Method dispatchKeyEvent = null; public static Method dispatchPopulateAccessibilityEvent = null; public static Method dispatchTouchEvent = null; public static Method dispatchTrackballEvent = null; public static Method onKeyDown = null; public static Method onKeyMultiple = null; public static Method onKeyUp = null; public static Method onTouchEvent = null; public static Method onTrackballEvent = null; public static Method onActivityResult = null; public static Method onCreate = null; public static Method onKeyLongPress = null; public static Method dispatchKeyShortcutEvent = null; public static Method onKeyShortcut = null; public static Method dispatchGenericMotionEvent = null; public static Method onGenericMotionEvent = null; public static void setQtActivityDelegate(Object listener) { QtApplication.m_delegateObject = listener; ArrayList delegateMethods = new ArrayList(); for (Method m : listener.getClass().getMethods()) { if (m.getDeclaringClass().getName().startsWith("org.qtproject.qt5.android")) delegateMethods.add(m); } ArrayList applicationFields = new ArrayList(); for (Field f : QtApplication.class.getFields()) { if (f.getDeclaringClass().getName().equals(QtApplication.class.getName())) applicationFields.add(f); } for (Method delegateMethod : delegateMethods) { try { QtActivity.class.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes()); if (QtApplication.m_delegateMethods.containsKey(delegateMethod.getName())) { QtApplication.m_delegateMethods.get(delegateMethod.getName()).add(delegateMethod); } else { ArrayList delegateSet = new ArrayList(); delegateSet.add(delegateMethod); QtApplication.m_delegateMethods.put(delegateMethod.getName(), delegateSet); } for (Field applicationField:applicationFields) { if (applicationField.getName().equals(delegateMethod.getName())) { try { applicationField.set(null, delegateMethod); } catch (Exception e) { e.printStackTrace(); } } } } catch (Exception e) { } } } @Override public void onTerminate() { if (m_delegateObject != null && m_delegateMethods.containsKey("onTerminate")) invokeDelegateMethod(m_delegateMethods.get("onTerminate").get(0)); super.onTerminate(); } public static class InvokeResult { public boolean invoked = false; public Object methodReturns = null; } private static int stackDeep=-1; public static InvokeResult invokeDelegate(Object... args) { InvokeResult result = new InvokeResult(); if (m_delegateObject == null) return result; StackTraceElement[] elements = Thread.currentThread().getStackTrace(); if (-1 == stackDeep) { String activityClassName = QtActivity.class.getCanonicalName(); for (int it=0;it

OpenSSL под Android в Qt

Запись от Wyn размещена 18.01.2016 в 18:22
Метки android, openssl, qt

Мини-руководство по тому, как быстро скомпилировать OpenSSL для Android и связать его с проектом Qt.
Для Linux.

Вначале действия полностью идентичны "расово-верному" руководству по компилянию OpenSSL для Android:
Качаем исходники openssl нужной версии с их сайта, качаем setenv-android.sh(все ссылки на закачку выше по ссылке).
Ложим их в одну папку. Запускаем консоль, переходим в ней в эту самую папку.
Далее:
BashВыделить код
1
2
3
$ rm -rf openssl-1.0.1g/   # удаляем исходники(вместо версии 1.0.1g - подставляем свою), если они уже были распакованы
$ tar xzf openssl-1.0.1g.tar.gz    # распаковываем исходники в подпапку
$ chmod a+x setenv-android.sh    # разрешаем setenv-android.sh исполняться
Редактируем setenv-android.sh, настраивая там _ANDROID_EABI, _ANDROID_ARCH, _ANDROID_API на нужные значения.
Дальше возвращаемся в консоль:
BashВыделить код
1
2
3
4
5
6
7
8
9
10
11
$ export ANDROID_NDK_ROOT=путь_до_ANDROID_NDK # указываем путь до Android NDK для setenv-android.sh
$ . ./setenv-android.sh # запускаем скрипт, чтобы он нам в окружение проставил необходимые далее переменные
$ cd openssl-1.0.1g/
$ perl -pi -e 's/install: all install_docs install_sw/install: install_docs install_sw/g' Makefile.org
# конфигурируем
$ ./config shared no-ssl2 no-ssl3 no-comp no-hw no-engine --openssldir=/usr/local/ssl/$ANDROID_API
# собираем
$ make depend
$ make all
# устанавливаем
$ sudo -E make install CC=$ANDROID_TOOLCHAIN/arm-linux-androideabi-gcc RANLIB=$ANDROID_TOOLCHAIN/arm-linux-androideabi-ranlib
И тут начинается интересное. Андроид не принимает versioned shared object (это *.so.x и подобные). Казалось бы 2016 год, космические корабли уже давно бороздят просторы Большого театра, но вот те на.

Однако, есть обходной приём - нужно заменить *.so.x.x.x на *_x_x_x.so. Простым переименованием файлов данную проблему здесь, разумеется, не решить. Нужно лезть внутрь и переименовывать soname и внутренние ссылки на другие versioned shared object. В интернете есть много способов по подобному переименованию. Большинство из них обещают райскую жизнь с rpl, забывая упомянуть, что утилита уже давно отпета и закопана на большинстве дистрибутивов. Или хитро-хитро редактируют makefile, что в итоге на место левой руки собирается правая нога. В целом множество путей из разряда "как потратить много времени на полную фигню".

В итоге предлагаю решить данную проблему методом топора:
Качаем hex-редактор, если ещё нет(в моём случае таковым оказался Okteta). Запускаем его из под рута(kdesu okteta), открываем в нём файлы openssldir/lib/libcrypto.so.1.0.0. Заменяем(ctrl+r) в нём символы ".so.1.0.0" на char "_1_0_0.so". Проделываем тоже самое с libssl.so.1.0.0. Всё, теперь осталось только переименовать сами файлы(в libcrypto_1_0_0.so и libssl_1_0_0.so) и поправить ссылки libssl.so и libcrypto.so, чтобы они вели на них.

Чтобы подключить и использовать данную библиотеку в проекте нужно добавить в .pro:
BashВыделить код
1
2
3
4
5
android: {
    INCLUDEPATH += /usr/local/ssl/android-21/include
    LIBS += -L/usr/local/ssl/android-21/lib
}
LIBS += -lcrypto
А затем в настройках проекта, в Buld/Build Steps/Bulild Android Apk добавить libcrypto_1_0_0.so и libssl_1_0_0.so в список Additional Libraries.

На этом всё.

Original: http://www.cyberforum.ru/blogs/748276/blog4086.html

i2pd-2.17.0/qt/i2pd_qt/generalsettingswidget.ui000066400000000000000000002403431321131324000213620ustar00rootroot00000000000000 GeneralSettingsContentsForm 0 0 679 3033 0 0 GeneralSettingsContentsForm 0 0 679 3052 QLayout::SetMinAndMaxSize 0 0 0 51 16777215 51 Configuration file: 0 18 661 31 QLayout::SetMaximumSize 0 0 0 27 16777215 27 Browse… 0 0 0 51 16777215 51 Tunnels configuration file: 0 18 661 31 QLayout::SetMaximumSize 0 0 0 27 16777215 27 Browse… 0 0 0 51 16777215 51 Pid file: 0 18 661 31 QLayout::SetMaximumSize 0 0 0 27 16777215 27 Browse… 0 98 16777215 98 SAM interface 0 20 97 22 Enabled 0 40 661 31 IP address to listen on: Qt::Horizontal 40 20 0 70 661 31 Port to listen on: 80 16777215 Qt::Horizontal 40 20 0 60 16777215 60 13 Windows-specific options 0 44 16777215 44 Cryptography 0 20 661 22 Use ElGamal precomputed tables 0 107 16777215 107 Logging Qt::AlignJustify|Qt::AlignTop -1 19 661 91 QLayout::SetMinimumSize QLayout::SetMaximumSize Destination: Edit Log file: Browse… QLayout::SetMinimumSize 0 0 Log level: Error Warn Info Debug Edit Qt::Horizontal 40 20 0 68 16777215 68 UPnP 0 20 97 22 Enable 0 40 661 31 Name i2pd appears in UPnP forwardings list: Qt::Horizontal 40 20 0 98 16777215 98 I2CP interface 0 20 97 22 Enabled 0 40 661 31 IP address to listen on: Qt::Horizontal 40 20 0 70 661 31 Port to listen on: 80 16777215 Qt::Horizontal 40 20 0 98 16777215 98 BOB interface 0 20 97 22 Enabled 0 40 661 31 IP address to listen on: Qt::Horizontal 40 20 0 70 661 31 Port to listen on: 80 16777215 Qt::Horizontal 40 20 0 60 16777215 60 13 General options 0 0 0 98 16777215 98 Router external address (for incoming connections) Qt::AlignJustify|Qt::AlignTop 0 20 661 81 QLayout::SetMinAndMaxSize QLayout::SetMinAndMaxSize Host: Qt::Horizontal 40 20 QLayout::SetMinAndMaxSize Port (leave 0 to auto-assign): 80 16777215 Qt::Horizontal 40 20 0 78 16777215 78 Addressbook settings 0 20 661 31 Addressbook default subscription URL for initial setup: 0 50 661 31 Addressbook subscriptions URLs, separated by comma: 0 280 16777215 280 HTTP proxy 0 20 97 22 Enabled 0 40 661 31 IP address to listen on: Qt::Horizontal 40 20 0 70 661 31 Port to listen on: 80 16777215 Qt::Horizontal 40 20 0 100 661 31 Keys file: Browse… 0 160 661 31 Inbound tunnels length: 80 16777215 Qt::Horizontal 40 20 0 190 661 31 Inbound tunnels quantity: 80 16777215 Qt::Horizontal 40 20 0 220 661 31 Outbound tunnels length: 80 16777215 Qt::Horizontal 40 20 0 250 661 31 Outbound tunnels quantity: 80 16777215 Qt::Horizontal 40 20 0 130 661 31 Signature type: Edit Qt::Horizontal 40 20 0 60 16777215 60 13 Various options 0 51 16777215 51 Data folder (for storage of i2pd data — RI, keys, peer profiles, …): 0 20 661 31 QLayout::SetMaximumSize Browse… 0 0 0 215 16777215 215 Router options 0 20 661 188 Enable communication through ipv6 Router will not accept transit tunnels at startup Router will be floodfill Bandwidth limit (integer or a letter): KBps Qt::Horizontal 40 20 Family (name of a family router belongs to): Qt::Horizontal 40 20 QLayout::SetMaximumSize NetID (network ID router belongs to. The main I2P ID is 2): Qt::Horizontal 40 20 0 108 16777215 108 Limits 0 20 661 31 Maximum number of transit tunnels: Qt::Horizontal 40 20 0 50 661 31 Maximum number of open files (0 — use system limit): Qt::Horizontal 40 20 0 80 661 31 Maximum size of core file in Kb (0 — use system limit): Qt::Horizontal 40 20 0 98 16777215 98 Reseeding 0 20 661 22 Request SU3 signature verification 0 40 661 31 SU3 file to reseed from: Browse… 0 70 661 31 Reseed URLs, separated by comma: 0 170 16777215 170 Trust options 0 20 661 21 Enable explicit trust options 390 40 271 23 0 40 391 42 Make direct I2P connections only to routers in specified Family: 0 82 661 42 Make direct I2P connections only to routers specified here. Comma separated list of base64 identities: 0 124 661 23 0 147 661 21 Should we hide our router from other routers? 0 60 16777215 60 13 Ports 0 22 16777215 22 Insomnia (prevent system from sleeping) 0 189 16777215 189 I2PControl interface 0 20 97 22 Enabled 0 40 661 31 IP address to listen on: Qt::Horizontal 40 20 0 70 661 31 Port to listen on: 80 16777215 Qt::Horizontal 40 20 0 100 661 31 Password: Qt::Horizontal 40 20 0 130 661 31 Certificate file: Browse… 0 160 661 31 Key file: Browse… 0 0 0 105 16777215 105 Websockets server 0 20 85 21 Enable 0 40 661 31 Address to bind websocket server on: Qt::Horizontal 40 20 0 70 661 31 Port to bind websocket server on: 80 16777215 Qt::Horizontal 40 20 0 179 16777215 179 HTTP webconsole 0 20 97 22 Enabled 0 40 661 31 IP address to listen on: Qt::Horizontal 40 20 0 70 661 31 Port to listen on: 80 16777215 Qt::Horizontal 40 20 0 100 321 22 Enable basic HTTP auth 60 120 601 31 Username: Qt::Horizontal 40 20 60 150 601 31 Password: Qt::Horizontal 40 20 0 335 16777215 335 Socks proxy 0 20 97 22 Enabled 0 40 661 31 IP address to listen on: Qt::Horizontal 40 20 0 70 661 31 Port to listen on: 80 16777215 Qt::Horizontal 40 20 0 100 661 31 Keys file: Browse… 0 160 661 31 Inbound tunnels length: 80 16777215 Qt::Horizontal 40 20 0 190 661 31 Inbound tunnels quantity: 80 16777215 Qt::Horizontal 40 20 0 220 661 31 Outbound tunnels length: 80 16777215 Qt::Horizontal 40 20 0 250 661 31 Outbound tunnels quantity: 80 16777215 Qt::Horizontal 40 20 0 280 661 31 Outproxy address: Qt::Horizontal 40 20 0 310 661 31 Outproxy port: 80 16777215 Qt::Horizontal 40 20 0 130 661 31 Signature type: Edit Qt::Horizontal 40 20 i2pd-2.17.0/qt/i2pd_qt/i2pd.qrc000066400000000000000000000001551321131324000157610ustar00rootroot00000000000000 images/icon.png i2pd-2.17.0/qt/i2pd_qt/i2pd_qt.pro000066400000000000000000000220421321131324000164770ustar00rootroot00000000000000QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = i2pd_qt TEMPLATE = app QMAKE_CXXFLAGS *= -std=c++11 DEFINES += USE_UPNP # change to your own path, where you will store all needed libraries with 'git clone' commands below. MAIN_PATH = /path/to/libraries # git clone https://github.com/PurpleI2P/Boost-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/OpenSSL-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/MiniUPnP-for-Android-Prebuilt.git # git clone https://github.com/PurpleI2P/android-ifaddrs.git BOOST_PATH = $$MAIN_PATH/Boost-for-Android-Prebuilt OPENSSL_PATH = $$MAIN_PATH/OpenSSL-for-Android-Prebuilt MINIUPNP_PATH = $$MAIN_PATH/MiniUPnP-for-Android-Prebuilt IFADDRS_PATH = $$MAIN_PATH/android-ifaddrs # Steps in Android SDK manager: # 1) Check Extras/Google Support Library https://developer.android.com/topic/libraries/support-library/setup.html # 2) Check API 11 # Finally, click Install. SOURCES += DaemonQT.cpp mainwindow.cpp \ ../../libi2pd/api.cpp \ ../../libi2pd/Base.cpp \ ../../libi2pd/BloomFilter.cpp \ ../../libi2pd/Config.cpp \ ../../libi2pd/Crypto.cpp \ ../../libi2pd/CryptoKey.cpp \ ../../libi2pd/Datagram.cpp \ ../../libi2pd/Destination.cpp \ ../../libi2pd/Event.cpp \ ../../libi2pd/Family.cpp \ ../../libi2pd/FS.cpp \ ../../libi2pd/Garlic.cpp \ ../../libi2pd/Gost.cpp \ ../../libi2pd/Gzip.cpp \ ../../libi2pd/HTTP.cpp \ ../../libi2pd/I2NPProtocol.cpp \ ../../libi2pd/I2PEndian.cpp \ ../../libi2pd/Identity.cpp \ ../../libi2pd/LeaseSet.cpp \ ../../libi2pd/Log.cpp \ ../../libi2pd/NetDb.cpp \ ../../libi2pd/NetDbRequests.cpp \ ../../libi2pd/NTCPSession.cpp \ ../../libi2pd/Profiling.cpp \ ../../libi2pd/Reseed.cpp \ ../../libi2pd/RouterContext.cpp \ ../../libi2pd/RouterInfo.cpp \ ../../libi2pd/Signature.cpp \ ../../libi2pd/SSU.cpp \ ../../libi2pd/SSUData.cpp \ ../../libi2pd/SSUSession.cpp \ ../../libi2pd/Streaming.cpp \ ../../libi2pd/Timestamp.cpp \ ../../libi2pd/TransitTunnel.cpp \ ../../libi2pd/Transports.cpp \ ../../libi2pd/Tunnel.cpp \ ../../libi2pd/TunnelEndpoint.cpp \ ../../libi2pd/TunnelGateway.cpp \ ../../libi2pd/TunnelPool.cpp \ ../../libi2pd/util.cpp \ ../../libi2pd_client/AddressBook.cpp \ ../../libi2pd_client/BOB.cpp \ ../../libi2pd_client/ClientContext.cpp \ ../../libi2pd_client/HTTPProxy.cpp \ ../../libi2pd_client/I2CP.cpp \ ../../libi2pd_client/I2PService.cpp \ ../../libi2pd_client/I2PTunnel.cpp \ ../../libi2pd_client/MatchedDestination.cpp \ ../../libi2pd_client/SAM.cpp \ ../../libi2pd_client/SOCKS.cpp \ ../../libi2pd_client/Websocket.cpp \ ../../libi2pd_client/WebSocks.cpp \ ClientTunnelPane.cpp \ MainWindowItems.cpp \ ServerTunnelPane.cpp \ SignatureTypeComboboxFactory.cpp \ TunnelConfig.cpp \ TunnelPane.cpp \ ../../daemon/Daemon.cpp \ ../../daemon/HTTPServer.cpp \ ../../daemon/i2pd.cpp \ ../../daemon/I2PControl.cpp \ ../../daemon/UnixDaemon.cpp \ ../../daemon/UPnP.cpp \ textbrowsertweaked1.cpp \ pagewithbackbutton.cpp \ widgetlock.cpp \ widgetlockregistry.cpp #qt creator does not handle this well #SOURCES += $$files(../../libi2pd/*.cpp) #SOURCES += $$files(../../libi2pd_client/*.cpp) #SOURCES += $$files(../../daemon/*.cpp) #SOURCES += $$files(./*.cpp) SOURCES -= ../../daemon/UnixDaemon.cpp HEADERS += DaemonQT.h mainwindow.h \ ../../libi2pd/api.h \ ../../libi2pd/Base.h \ ../../libi2pd/BloomFilter.h \ ../../libi2pd/Config.h \ ../../libi2pd/Crypto.h \ ../../libi2pd/CryptoKey.h \ ../../libi2pd/Datagram.h \ ../../libi2pd/Destination.h \ ../../libi2pd/Event.h \ ../../libi2pd/Family.h \ ../../libi2pd/FS.h \ ../../libi2pd/Garlic.h \ ../../libi2pd/Gost.h \ ../../libi2pd/Gzip.h \ ../../libi2pd/HTTP.h \ ../../libi2pd/I2NPProtocol.h \ ../../libi2pd/I2PEndian.h \ ../../libi2pd/Identity.h \ ../../libi2pd/LeaseSet.h \ ../../libi2pd/LittleBigEndian.h \ ../../libi2pd/Log.h \ ../../libi2pd/NetDb.hpp \ ../../libi2pd/NetDbRequests.h \ ../../libi2pd/NTCPSession.h \ ../../libi2pd/Profiling.h \ ../../libi2pd/Queue.h \ ../../libi2pd/Reseed.h \ ../../libi2pd/RouterContext.h \ ../../libi2pd/RouterInfo.h \ ../../libi2pd/Signature.h \ ../../libi2pd/SSU.h \ ../../libi2pd/SSUData.h \ ../../libi2pd/SSUSession.h \ ../../libi2pd/Streaming.h \ ../../libi2pd/Tag.h \ ../../libi2pd/Timestamp.h \ ../../libi2pd/TransitTunnel.h \ ../../libi2pd/Transports.h \ ../../libi2pd/TransportSession.h \ ../../libi2pd/Tunnel.h \ ../../libi2pd/TunnelBase.h \ ../../libi2pd/TunnelConfig.h \ ../../libi2pd/TunnelEndpoint.h \ ../../libi2pd/TunnelGateway.h \ ../../libi2pd/TunnelPool.h \ ../../libi2pd/util.h \ ../../libi2pd/version.h \ ../../libi2pd_client/AddressBook.h \ ../../libi2pd_client/BOB.h \ ../../libi2pd_client/ClientContext.h \ ../../libi2pd_client/HTTPProxy.h \ ../../libi2pd_client/I2CP.h \ ../../libi2pd_client/I2PService.h \ ../../libi2pd_client/I2PTunnel.h \ ../../libi2pd_client/MatchedDestination.h \ ../../libi2pd_client/SAM.h \ ../../libi2pd_client/SOCKS.h \ ../../libi2pd_client/Websocket.h \ ../../libi2pd_client/WebSocks.h \ ClientTunnelPane.h \ MainWindowItems.h \ ServerTunnelPane.h \ SignatureTypeComboboxFactory.h \ TunnelConfig.h \ TunnelPane.h \ TunnelsPageUpdateListener.h \ ../../daemon/Daemon.h \ ../../daemon/HTTPServer.h \ ../../daemon/I2PControl.h \ ../../daemon/UPnP.h \ textbrowsertweaked1.h \ pagewithbackbutton.h \ widgetlock.h \ widgetlockregistry.h INCLUDEPATH += ../../libi2pd INCLUDEPATH += ../../libi2pd_client INCLUDEPATH += ../../daemon INCLUDEPATH += . FORMS += mainwindow.ui \ tunnelform.ui \ statusbuttons.ui \ routercommandswidget.ui \ generalsettingswidget.ui LIBS += -lz macx { message("using mac os x target") BREWROOT=/usr/local BOOSTROOT=$$BREWROOT/opt/boost SSLROOT=$$BREWROOT/opt/libressl UPNPROOT=$$BREWROOT/opt/miniupnpc INCLUDEPATH += $$BOOSTROOT/include INCLUDEPATH += $$SSLROOT/include INCLUDEPATH += $$UPNPROOT/include LIBS += $$SSLROOT/lib/libcrypto.a LIBS += $$SSLROOT/lib/libssl.a LIBS += $$BOOSTROOT/lib/libboost_system.a LIBS += $$BOOSTROOT/lib/libboost_date_time.a LIBS += $$BOOSTROOT/lib/libboost_filesystem.a LIBS += $$BOOSTROOT/lib/libboost_program_options.a LIBS += $$UPNPROOT/lib/libminiupnpc.a } android { message("Using Android settings") DEFINES += ANDROID=1 DEFINES += __ANDROID__ CONFIG += mobility MOBILITY = INCLUDEPATH += $$BOOST_PATH/boost_1_53_0/include \ $$OPENSSL_PATH/openssl-1.0.2/include \ $$MINIUPNP_PATH/miniupnp-2.0/include \ $$IFADDRS_PATH DISTFILES += android/AndroidManifest.xml ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android SOURCES += $$IFADDRS_PATH/ifaddrs.c HEADERS += $$IFADDRS_PATH/ifaddrs.h equals(ANDROID_TARGET_ARCH, armeabi-v7a){ DEFINES += ANDROID_ARM7A # http://stackoverflow.com/a/30235934/529442 LIBS += -L$$BOOST_PATH/boost_1_53_0/armeabi-v7a/lib \ -lboost_system-gcc-mt-1_53 -lboost_date_time-gcc-mt-1_53 \ -lboost_filesystem-gcc-mt-1_53 -lboost_program_options-gcc-mt-1_53 \ -L$$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/ -lcrypto -lssl \ -L$$MINIUPNP_PATH/miniupnp-2.0/armeabi-v7a/lib/ -lminiupnpc PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto.a \ $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl.a DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libcrypto_1_0_0.so \ $$OPENSSL_PATH/openssl-1.0.2/armeabi-v7a/lib/libssl_1_0_0.so \ $$MINIUPNP_PATH/miniupnp-2.0/armeabi-v7a/lib/libminiupnpc.so } equals(ANDROID_TARGET_ARCH, x86){ # http://stackoverflow.com/a/30235934/529442 LIBS += -L$$BOOST_PATH/boost_1_53_0/x86/lib \ -lboost_system-gcc-mt-1_53 -lboost_date_time-gcc-mt-1_53 \ -lboost_filesystem-gcc-mt-1_53 -lboost_program_options-gcc-mt-1_53 \ -L$$OPENSSL_PATH/openssl-1.0.2/x86/lib/ -lcrypto -lssl \ -L$$MINIUPNP_PATH/miniupnp-2.0/x86/lib/ -lminiupnpc PRE_TARGETDEPS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto.a \ $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl.a DEPENDPATH += $$OPENSSL_PATH/openssl-1.0.2/include ANDROID_EXTRA_LIBS += $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libcrypto_1_0_0.so \ $$OPENSSL_PATH/openssl-1.0.2/x86/lib/libssl_1_0_0.so \ $$MINIUPNP_PATH/miniupnp-2.0/x86/lib/libminiupnpc.so } } linux:!android { message("Using Linux settings") LIBS += -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_program_options -lpthread -lminiupnpc } !android:!symbian:!maemo5:!simulator { message("Build with a system tray icon") # see also http://doc.qt.io/qt-4.8/qt-desktop-systray-systray-pro.html for example on wince* #sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS i2pd_qt.pro resources images RESOURCES = i2pd.qrc QT += xml #INSTALLS += sources } i2pd-2.17.0/qt/i2pd_qt/images/000077500000000000000000000000001321131324000156605ustar00rootroot00000000000000i2pd-2.17.0/qt/i2pd_qt/images/icon.png000066400000000000000000000210101321131324000173100ustar00rootroot00000000000000PNG  IHDR>abKGDC pHYs B(xtIME ]PhJ IDATxy|TϽw&{v "n?+*m]j)mBڢZkK(UB]PAP@%KXB>fzAEwc㭓g Ѐ&p}<U {sMF?oC8w0 'NKRP5n$$(_#|U{v0 Oɶ SD>}$Ϣn} qpv@q &TO,#tghFKOn>LF>1s1Yk_K:cy ќ*9egp6MamDQl <ê/Skږ4Xdg lV]?GΏit NgkйC~:*w"1޶_Eӄ7ܠ|1.\ GˁX,ZYMmҁR'fM'tCEГ`^ѽ~`Dh|POڰ!z+ahNc|g3]6`oqk##6<å- 1F`m>% VԏGǜ)Ǹc~: x84gߍpiǼ跈 O?,Ǐ/`i0o .rС|^lT ;[4 0I9^]?g*Z{̘1E]vٚ1cl. e쒯цLF&3BI+^le˖,Tٳ !Jڸ1~:H 9?u8d̹CTk㘁υ ؽpJ)]#FKH-S_3>F'#!!A_~O}-#n.BBC^[!+#S-E1p!2]+J)o~=<-뙩aٴiZU?:~ۯ+~4 Q 8.?qReԪpMFyl+]v{nڻ>1cƸ'B;F9nf~>}Wojժ# YRhӦ6Jr HD.FMhڹsgaϞ=>>!Q hd?aYramGכ23371 5pn8g [{`Ui5?2-5|\+#mᓝ2e?F۝MWnÌ/c{C xŨPZv\sqy.eDp.M3fqe\y9wD:qڑ1 ;+ωRl\OŠAd݉UFV9<^{_u\999{$\ĨBpS/w8.g-%H\aÆ=AF*G+%NJ)SLةS!ė&Lئ*wCyyyzHGw0tG-pFk@7Cg|O}QE ,88jԨd2dH ضm[p)5 )>'뺮Ϝ9sPaaa.z+OӴ|UtG*C& ST@ӛۡ8oHToɒÃW\qEj/׀^z)(ޝg"ƜitwR"*%;u7{衇; tX91 VG[5Csk[TTiBf8Y@og}]@8#Ow,tL =Cz:Jz*j_hW7nѱ]IJcrZ ;9- .Y ++ϏunY8׎A R*^AQo`+;o`LD)Sc$,hGMxfj(zegg,8p`z=NsQ>f̘oG2)MļGFk㾜ό w[RJuJMMM9sc<; c2O\t$yCS\\,:㯷 'mΜ9#gBZR7W0 APހ5HH) No)i7`,zcQ,da}~ F/!}N\; =)>8ق0Ŏ(D>Cǜ^>71~(I O/G\5*=q)k&Ў!|ڼQZK"Ҭ| [ P1C'[Mlk0tL<F}9=a.Dـ/;a k9:炋~\1=8@Sn;n,@j"HK0QZ[ Vg}548t>Xe&wzJ﯊ilmX h 4Rqk#+J*Vy*A.][>jN5~\mZjEo4ODyNLrSX"Se1M5Xq?U3i-ԡgY]UjGbQCBBs1/M:f޺kYi&aqML^o:Ԃ֬jGc{Pe H@/csYӰbQtڍoi 5xU7ԲM>fijhNYg塩y#1':ln/{ uic5nm4957jl@2v#?3m4aI30'fB0R)^:޵N% +P?:뗯ʪl:!PYOc}jJq5DZ5:/j?pO ߵ?* (t4tt*u{խWs<> H%1x"~fQ=D&Bzw|8϶OU@b-1wg:KGS!EFgBnW8Я4my+朥+*CG54 8(WQ\ %?0: "+䯦&lo2?mSmᅙfp5JJD\Ԡ9Td&VCȭh S:/))${#ju;wawJ~w*H&IMF||2o#jsP!@᥆{U\XFi^|'tMl3Dp3jӜdb b _CCABU҉= "FГNTCN'(3xv5DTt3픯'mpwGDe$o4{9~G`%$YH1#(j!^|\e<ؒݛ"-s:yk"@/4p"MÀy Wa"1БHUNAnN.e!Icp]ӏ[2FK숞rw!D[oK=\s(8AEq`c՜l2E{K(074FOaQjȦT|N/^U;;3YI$t+%i XVVyChY|ӓQ G:(Q\aK Oɒ3uU)xDzAנLO&Qsj ŌfZm&8A$ژoH8._̨߳H#E 8%V"[/hPe=!:C!hE,Hx;F鿆F!i 09enj+U"=MC9^O.Yj aߥg#ۥ/Zdz0YgƘقm3xRT&νZlДR%@੢ZZ ¯JSʨ Q|ʗO2̠̈́h]i$תtHˣ^>KXF {O#\cEt:8SS"ۥ.9/`xkYZ2sK$GRh#[w[ NBg5 gK '  Ÿ/B'0 E[% 8ŋB =ZGk6'c /{co٤踾"pZZWW1SFD3$ߚn{$]+T1AcŮ}U>q JH#U kHۖ )+z-3$f1IGg'?sa=W|'cL֦Y#LG)SS"^O"@tEэDx{Ùl_G GBN.ocGkoDL DAՊ]B¯1~DYso8$R;r{?Z£LZΚX-gA^AR7M#R[1`k08꿈#FmxK̓n\|'ž~۞:i%l` ( +.aw2?xsR,2ޛp[32'>߹nL[y92zqZJ)w< SP4&3Y. ?Sյlb>9M&ePEʧC2T=q%v/tC?|/)5]k'biPR~Ҝ+XV/S9tIzһr v;r2gnD@`2+{vc/;K'K8I:zX8wx_.lH|/ 憼?\fGCM<274 w/*:)Z hTw+Ў>wt:@pE`4Ypӱg[ϐ!M6b5AA@>[ZAWFMyuuQYI:LO0(s=-K@ jm \oO7ǩwa#/дdZ; TB_ě$qvmJϸWSNɈM2F#PRmE`Xh9ĠZ7QBw>] GJ!6RP:l'Q UIOgF69 1?&M]i DԷHՋn~(􎉧L!fb߹ U[%v1Jfwhfq,@[+òaT>sXA{W-@U%.f3Gn͏I}:: >fXjƁVD.& qL[.t8]դV2$7Sq%!(T̀ *V(ֹ:6Z:>D/щLՉL-IP?TÔ[(5e9ݐ%ޗdk蹤WK&7υ^dϧN', Wcz̆`d:=SR?PD(@w^,1}!%Oh%UP""9R(tA)w/(f)t0ג?_}rJE;>VQ`uIk9`'h;FMp?Y|jb~&Xl6 ăT>42(9TQJHٷ+%$RiO Mf9O$G2hD?(&A* dgU1 4!*{Ð  ARbda0KmfDAj+tNIE2'௧S$:pMj!d;XTv ;s+"ئ'Bw#k /񴤲DZx@,:uM)SkͣJdtڎ:}>~mAQFyp~qJ=cp,ߵ=3A 0>8 8h%%b(E*/(K) k!7?l=OV=Dy)bh9P(SNy{cN$R5#.,(~]C, v-Eaz;£e6xˌHEBDT0@,Z^ o`Kd\u!҈^tt!s KA],:b$Ah"^ Y7HCQ:a!xtapH F(mx)R[VUs z/{(ڸ!|)H sFqJ2Av0.vnҜ0uH$ EK)h0ekzvRQd:V6s (R񑂋r|`ѿa[R@=s9i#d|#RvFb(d%okmuAPE{|>̜T1^]HrCmx@.mtd5AT6E!a>4JjBS3u s ?8.(5)#lIz21eәh/|@:,\Hd*b Pؔ onUbuV{kQ$%;wP@dz+>$扆 ?lBH%(sjtA-s六D eM:Eq^( 4NX(ہQJf#}Wʾr!GL{$n4|a>!I*֮5G _`@:jkTS`$\ Bwj( %ޞwN نI =.5`a2I0+\ n$Jj8 \݌OC3j)/]Hz.aV.4(MqI9~`/R/K1Fl0Bn@䨣 l;0hrxMG7=IDATRiVNo2tt$-PÀ7"08I琖^n#? $B]`r?._4@|Jq3HCncF#00p ] ^(G/i茁G"LRhqEn}8 1*xC=ǯBUX .ckꓟ}di2MT (OM_]c~DVN8R̲0 $"IDӉ]'mʃMjA4st-S'!dKV¶'ŭXiia5PM<+Q)xQ6 dگlmv`Xok*RjFi<AZȲGj GswH reIENDB`i2pd-2.17.0/qt/i2pd_qt/mainwindow.cpp000066400000000000000000001233051321131324000172770ustar00rootroot00000000000000#include #include #include "mainwindow.h" #include "ui_mainwindow.h" #include "ui_statusbuttons.h" #include "ui_routercommandswidget.h" #include "ui_generalsettingswidget.h" #include #include #include #include #include #include #include "RouterContext.h" #include "Config.h" #include "FS.h" #include "Log.h" #include "RouterContext.h" #include "Transports.h" #include "HTTPServer.h" #ifndef ANDROID # include #endif #include "DaemonQT.h" #include "SignatureTypeComboboxFactory.h" std::string programOptionsWriterCurrentSection; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) #ifndef ANDROID ,quitting(false) #endif ,wasSelectingAtStatusMainPage(false) ,showHiddenInfoStatusMainPage(false) ,ui(new Ui::MainWindow) ,statusButtonsUI(new Ui::StatusButtonsForm) ,routerCommandsUI(new Ui::routerCommandsWidget) ,uiSettings(new Ui::GeneralSettingsContentsForm) ,routerCommandsParent(new QWidget(this)) ,widgetlocks() ,i2pController(nullptr) ,configItems() ,datadir() ,confpath() ,tunconfpath() ,tunnelsPageUpdateListener(this) { ui->setupUi(this); statusButtonsUI->setupUi(ui->statusButtonsPane); routerCommandsUI->setupUi(routerCommandsParent); uiSettings->setupUi(ui->settingsContents); routerCommandsParent->hide(); ui->verticalLayout_2->addWidget(routerCommandsParent); //,statusHtmlUI(new Ui::StatusHtmlPaneForm) //statusHtmlUI->setupUi(lastStatusWidgetui->statusWidget); ui->statusButtonsPane->setFixedSize(171,300); ui->verticalLayout->setGeometry(QRect(0,0,171,ui->verticalLayout->geometry().height())); //ui->statusButtonsPane->adjustSize(); //ui->centralWidget->adjustSize(); setWindowTitle(QApplication::translate("AppTitle","I2PD")); //TODO handle resizes and change the below into resize() call setFixedHeight(550); ui->centralWidget->setFixedHeight(550); onResize(); ui->stackedWidget->setCurrentIndex(0); ui->settingsScrollArea->resize(uiSettings->settingsContentsGridLayout->sizeHint().width()+10,380); QScrollBar* const barSett = ui->settingsScrollArea->verticalScrollBar(); int w = 683; int h = 3060; ui->settingsContents->setFixedSize(w, h); ui->settingsContents->setGeometry(QRect(0,0,w,h)); /* QPalette pal(palette()); pal.setColor(QPalette::Background, Qt::red); ui->settingsContents->setAutoFillBackground(true); ui->settingsContents->setPalette(pal); */ QPalette pal(palette()); pal.setColor(QPalette::Background, Qt::red); ui->wrongInputLabel->setAutoFillBackground(true); ui->wrongInputLabel->setPalette(pal); ui->wrongInputLabel->setMaximumHeight(ui->wrongInputLabel->sizeHint().height()); ui->wrongInputLabel->setVisible(false); settingsTitleLabelNominalHeight = ui->settingsTitleLabel->height(); #ifndef ANDROID createActions(); createTrayIcon(); #endif textBrowser = new TextBrowserTweaked1(this); //textBrowser->setOpenExternalLinks(false); textBrowser->setOpenLinks(false); /*textBrowser->setTextInteractionFlags(textBrowser->textInteractionFlags()| Qt::LinksAccessibleByMouse|Qt::LinksAccessibleByKeyboard| Qt::TextSelectableByMouse|Qt::TextSelectableByKeyboard);*/ ui->verticalLayout_2->addWidget(textBrowser); childTextBrowser = new TextBrowserTweaked1(this); //childTextBrowser->setOpenExternalLinks(false); childTextBrowser->setOpenLinks(false); connect(textBrowser, SIGNAL(anchorClicked(const QUrl&)), this, SLOT(anchorClickedHandler(const QUrl&))); pageWithBackButton = new PageWithBackButton(this, childTextBrowser); ui->verticalLayout_2->addWidget(pageWithBackButton); pageWithBackButton->hide(); connect(pageWithBackButton, SIGNAL(backReleased()), this, SLOT(backClickedFromChild())); scheduleStatusPageUpdates(); QObject::connect(ui->statusPagePushButton, SIGNAL(released()), this, SLOT(showStatusMainPage())); showStatusMainPage(); QObject::connect(statusButtonsUI->mainPagePushButton, SIGNAL(released()), this, SLOT(showStatusMainPage())); QObject::connect(statusButtonsUI->routerCommandsPushButton, SIGNAL(released()), this, SLOT(showStatus_commands_Page())); QObject::connect(statusButtonsUI->localDestinationsPushButton, SIGNAL(released()), this, SLOT(showStatus_local_destinations_Page())); QObject::connect(statusButtonsUI->leasesetsPushButton, SIGNAL(released()), this, SLOT(showStatus_leasesets_Page())); QObject::connect(statusButtonsUI->tunnelsPushButton, SIGNAL(released()), this, SLOT(showStatus_tunnels_Page())); QObject::connect(statusButtonsUI->transitTunnelsPushButton, SIGNAL(released()), this, SLOT(showStatus_transit_tunnels_Page())); QObject::connect(statusButtonsUI->transportsPushButton, SIGNAL(released()), this, SLOT(showStatus_transports_Page())); QObject::connect(statusButtonsUI->i2pTunnelsPushButton, SIGNAL(released()), this, SLOT(showStatus_i2p_tunnels_Page())); QObject::connect(statusButtonsUI->samSessionsPushButton, SIGNAL(released()), this, SLOT(showStatus_sam_sessions_Page())); QObject::connect(textBrowser, SIGNAL(mouseReleased()), this, SLOT(statusHtmlPageMouseReleased())); QObject::connect(textBrowser, SIGNAL(selectionChanged()), this, SLOT(statusHtmlPageSelectionChanged())); QObject::connect(routerCommandsUI->runPeerTestPushButton, SIGNAL(released()), this, SLOT(runPeerTest())); QObject::connect(routerCommandsUI->acceptTransitTunnelsPushButton, SIGNAL(released()), this, SLOT(enableTransit())); QObject::connect(routerCommandsUI->declineTransitTunnelsPushButton, SIGNAL(released()), this, SLOT(disableTransit())); QObject::connect(ui->settingsPagePushButton, SIGNAL(released()), this, SLOT(showSettingsPage())); QObject::connect(ui->tunnelsPagePushButton, SIGNAL(released()), this, SLOT(showTunnelsPage())); QObject::connect(ui->restartPagePushButton, SIGNAL(released()), this, SLOT(showRestartPage())); QObject::connect(ui->quitPagePushButton, SIGNAL(released()), this, SLOT(showQuitPage())); QObject::connect(ui->fastQuitPushButton, SIGNAL(released()), this, SLOT(handleQuitButton())); QObject::connect(ui->gracefulQuitPushButton, SIGNAL(released()), this, SLOT(handleGracefulQuitButton())); QObject::connect(ui->doRestartI2PDPushButton, SIGNAL(released()), this, SLOT(handleDoRestartButton())); # define OPTION(section,option,defaultValueGetter) ConfigOption(QString(section),QString(option)) initFileChooser( OPTION("","conf",[](){return "";}), uiSettings->configFileLineEdit, uiSettings->configFileBrowsePushButton); initFolderChooser( OPTION("","datadir",[]{return "";}), uiSettings->dataFolderLineEdit, uiSettings->dataFolderBrowsePushButton); initFileChooser( OPTION("","tunconf",[](){return "";}), uiSettings->tunnelsConfigFileLineEdit, uiSettings->tunnelsConfigFileBrowsePushButton); initFileChooser( OPTION("","pidfile",[]{return "";}), uiSettings->pidFileLineEdit, uiSettings->pidFileBrowsePushButton); daemonOption=initNonGUIOption( OPTION("","daemon",[]{return "";})); serviceOption=initNonGUIOption( OPTION("","service",[]{return "";})); uiSettings->logDestinationComboBox->clear(); uiSettings->logDestinationComboBox->insertItems(0, QStringList() << QApplication::translate("MainWindow", "syslog", 0) << QApplication::translate("MainWindow", "stdout", 0) << QApplication::translate("MainWindow", "file", 0) ); initLogDestinationCombobox( OPTION("","log",[]{return "";}), uiSettings->logDestinationComboBox); logFileNameOption=initFileChooser( OPTION("","logfile",[]{return "";}), uiSettings->logFileLineEdit, uiSettings->logFileBrowsePushButton); initLogLevelCombobox(OPTION("","loglevel",[]{return "";}), uiSettings->logLevelComboBox); initIPAddressBox( OPTION("","host",[]{return "";}), uiSettings->routerExternalHostLineEdit, tr("Router external address -> Host")); initTCPPortBox( OPTION("","port",[]{return "";}), uiSettings->routerExternalPortLineEdit, tr("Router external address -> Port")); initCheckBox( OPTION("","ipv6",[]{return "false";}), uiSettings->ipv6CheckBox); initCheckBox( OPTION("","notransit",[]{return "false";}), uiSettings->notransitCheckBox); initCheckBox( OPTION("","floodfill",[]{return "false";}), uiSettings->floodfillCheckBox); initStringBox( OPTION("","bandwidth",[]{return "";}), uiSettings->bandwidthLineEdit); initStringBox( OPTION("","family",[]{return "";}), uiSettings->familyLineEdit); initIntegerBox( OPTION("","netid",[]{return "2";}), uiSettings->netIdLineEdit, tr("NetID")); #ifdef Q_OS_WIN initCheckBox( OPTION("","insomnia",[]{return "";}), uiSettings->insomniaCheckBox); initNonGUIOption( OPTION("","svcctl",[]{return "";})); initNonGUIOption( OPTION("","close",[]{return "";})); #else uiSettings->insomniaCheckBox->setEnabled(false); #endif initCheckBox( OPTION("http","enabled",[]{return "true";}), uiSettings->webconsoleEnabledCheckBox); initIPAddressBox( OPTION("http","address",[]{return "";}), uiSettings->webconsoleAddrLineEdit, tr("HTTP webconsole -> IP address")); initTCPPortBox( OPTION("http","port",[]{return "7070";}), uiSettings->webconsolePortLineEdit, tr("HTTP webconsole -> Port")); initCheckBox( OPTION("http","auth",[]{return "";}), uiSettings->webconsoleBasicAuthCheckBox); initStringBox( OPTION("http","user",[]{return "i2pd";}), uiSettings->webconsoleUserNameLineEditBasicAuth); initStringBox( OPTION("http","pass",[]{return "";}), uiSettings->webconsolePasswordLineEditBasicAuth); initCheckBox( OPTION("httpproxy","enabled",[]{return "";}), uiSettings->httpProxyEnabledCheckBox); initIPAddressBox( OPTION("httpproxy","address",[]{return "";}), uiSettings->httpProxyAddressLineEdit, tr("HTTP proxy -> IP address")); initTCPPortBox( OPTION("httpproxy","port",[]{return "4444";}), uiSettings->httpProxyPortLineEdit, tr("HTTP proxy -> Port")); initFileChooser( OPTION("httpproxy","keys",[]{return "";}), uiSettings->httpProxyKeyFileLineEdit, uiSettings->httpProxyKeyFilePushButton); initSignatureTypeCombobox(OPTION("httpproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_httpPorxySignatureType); initStringBox( OPTION("httpproxy","inbound.length",[]{return "3";}), uiSettings->httpProxyInboundTunnelsLenLineEdit); initStringBox( OPTION("httpproxy","inbound.quantity",[]{return "5";}), uiSettings->httpProxyInboundTunnQuantityLineEdit); initStringBox( OPTION("httpproxy","outbound.length",[]{return "3";}), uiSettings->httpProxyOutBoundTunnLenLineEdit); initStringBox( OPTION("httpproxy","outbound.quantity",[]{return "5";}), uiSettings->httpProxyOutboundTunnQuantityLineEdit); initCheckBox( OPTION("socksproxy","enabled",[]{return "";}), uiSettings->socksProxyEnabledCheckBox); initIPAddressBox( OPTION("socksproxy","address",[]{return "";}), uiSettings->socksProxyAddressLineEdit, tr("Socks proxy -> IP address")); initTCPPortBox( OPTION("socksproxy","port",[]{return "4447";}), uiSettings->socksProxyPortLineEdit, tr("Socks proxy -> Port")); initFileChooser( OPTION("socksproxy","keys",[]{return "";}), uiSettings->socksProxyKeyFileLineEdit, uiSettings->socksProxyKeyFilePushButton); initSignatureTypeCombobox(OPTION("socksproxy","signaturetype",[]{return "7";}), uiSettings->comboBox_socksProxySignatureType); initStringBox( OPTION("socksproxy","inbound.length",[]{return "";}), uiSettings->socksProxyInboundTunnelsLenLineEdit); initStringBox( OPTION("socksproxy","inbound.quantity",[]{return "";}), uiSettings->socksProxyInboundTunnQuantityLineEdit); initStringBox( OPTION("socksproxy","outbound.length",[]{return "";}), uiSettings->socksProxyOutBoundTunnLenLineEdit); initStringBox( OPTION("socksproxy","outbound.quantity",[]{return "";}), uiSettings->socksProxyOutboundTunnQuantityLineEdit); initIPAddressBox( OPTION("socksproxy","outproxy",[]{return "";}), uiSettings->outproxyAddressLineEdit, tr("Socks proxy -> Outproxy address")); initTCPPortBox( OPTION("socksproxy","outproxyport",[]{return "";}), uiSettings->outproxyPortLineEdit, tr("Socks proxy -> Outproxy port")); initCheckBox( OPTION("sam","enabled",[]{return "false";}), uiSettings->samEnabledCheckBox); initIPAddressBox( OPTION("sam","address",[]{return "";}), uiSettings->samAddressLineEdit, tr("SAM -> IP address")); initTCPPortBox( OPTION("sam","port",[]{return "7656";}), uiSettings->samPortLineEdit, tr("SAM -> Port")); initCheckBox( OPTION("bob","enabled",[]{return "false";}), uiSettings->bobEnabledCheckBox); initIPAddressBox( OPTION("bob","address",[]{return "";}), uiSettings->bobAddressLineEdit, tr("BOB -> IP address")); initTCPPortBox( OPTION("bob","port",[]{return "2827";}), uiSettings->bobPortLineEdit, tr("BOB -> Port")); initCheckBox( OPTION("i2cp","enabled",[]{return "false";}), uiSettings->i2cpEnabledCheckBox); initIPAddressBox( OPTION("i2cp","address",[]{return "";}), uiSettings->i2cpAddressLineEdit, tr("I2CP -> IP address")); initTCPPortBox( OPTION("i2cp","port",[]{return "7654";}), uiSettings->i2cpPortLineEdit, tr("I2CP -> Port")); initCheckBox( OPTION("i2pcontrol","enabled",[]{return "false";}), uiSettings->i2pControlEnabledCheckBox); initIPAddressBox( OPTION("i2pcontrol","address",[]{return "";}), uiSettings->i2pControlAddressLineEdit, tr("I2PControl -> IP address")); initTCPPortBox( OPTION("i2pcontrol","port",[]{return "7650";}), uiSettings->i2pControlPortLineEdit, tr("I2PControl -> Port")); initStringBox( OPTION("i2pcontrol","password",[]{return "";}), uiSettings->i2pControlPasswordLineEdit); initFileChooser( OPTION("i2pcontrol","cert",[]{return "i2pcontrol.crt.pem";}), uiSettings->i2pControlCertFileLineEdit, uiSettings->i2pControlCertFileBrowsePushButton); initFileChooser( OPTION("i2pcontrol","key",[]{return "i2pcontrol.key.pem";}), uiSettings->i2pControlKeyFileLineEdit, uiSettings->i2pControlKeyFileBrowsePushButton); initCheckBox( OPTION("upnp","enabled",[]{return "true";}), uiSettings->enableUPnPCheckBox); initStringBox( OPTION("upnp","name",[]{return "I2Pd";}), uiSettings->upnpNameLineEdit); initCheckBox( OPTION("precomputation","elgamal",[]{return "false";}), uiSettings->useElGamalPrecomputedTablesCheckBox); initCheckBox( OPTION("reseed","verify",[]{return "";}), uiSettings->reseedVerifyCheckBox); initFileChooser( OPTION("reseed","file",[]{return "";}), uiSettings->reseedFileLineEdit, uiSettings->reseedFileBrowsePushButton); initStringBox( OPTION("reseed","urls",[]{return "";}), uiSettings->reseedURLsLineEdit); initStringBox( OPTION("addressbook","defaulturl",[]{return "";}), uiSettings->addressbookDefaultURLLineEdit); initStringBox( OPTION("addressbook","subscriptions",[]{return "";}), uiSettings->addressbookSubscriptionsURLslineEdit); initUInt16Box( OPTION("limits","transittunnels",[]{return "2500";}), uiSettings->maxNumOfTransitTunnelsLineEdit, tr("maxNumberOfTransitTunnels")); initUInt16Box( OPTION("limits","openfiles",[]{return "0";}), uiSettings->maxNumOfOpenFilesLineEdit, tr("maxNumberOfOpenFiles")); initUInt32Box( OPTION("limits","coresize",[]{return "0";}), uiSettings->coreFileMaxSizeNumberLineEdit, tr("coreFileMaxSize")); initCheckBox( OPTION("trust","enabled",[]{return "false";}), uiSettings->checkBoxTrustEnable); initStringBox( OPTION("trust","family",[]{return "";}), uiSettings->lineEditTrustFamily); initStringBox( OPTION("trust","routers",[]{return "";}), uiSettings->lineEditTrustRouters); initCheckBox( OPTION("trust","hidden",[]{return "false";}), uiSettings->checkBoxTrustHidden); initCheckBox( OPTION("websockets","enabled",[]{return "false";}), uiSettings->checkBoxWebsocketsEnable); initIPAddressBox( OPTION("websockets","address",[]{return "127.0.0.1";}), uiSettings->lineEdit_webSock_addr, tr("Websocket server -> IP address")); initTCPPortBox( OPTION("websockets","port",[]{return "7666";}), uiSettings->lineEdit_webSock_port, tr("Websocket server -> Port")); # undef OPTION //widgetlocks.add(new widgetlock(widget,lockbtn)); widgetlocks.add(new widgetlock(uiSettings->logDestinationComboBox,uiSettings->logDestComboEditPushButton)); widgetlocks.add(new widgetlock(uiSettings->logLevelComboBox,uiSettings->logLevelComboEditPushButton)); widgetlocks.add(new widgetlock(uiSettings->comboBox_httpPorxySignatureType,uiSettings->httpProxySignTypeComboEditPushButton)); widgetlocks.add(new widgetlock(uiSettings->comboBox_socksProxySignatureType,uiSettings->socksProxySignTypeComboEditPushButton)); loadAllConfigs(); QObject::connect(uiSettings->logDestinationComboBox, SIGNAL(currentIndexChanged(const QString &)), this, SLOT(logDestinationComboBoxValueChanged(const QString &))); logDestinationComboBoxValueChanged(uiSettings->logDestinationComboBox->currentText()); ui->tunnelsScrollAreaWidgetContents->setGeometry(QRect(0, 0, 621, 451)); appendTunnelForms(""); uiSettings->configFileLineEdit->setEnabled(false); uiSettings->configFileBrowsePushButton->setEnabled(false); uiSettings->configFileLineEdit->setText(confpath); uiSettings->tunnelsConfigFileLineEdit->setText(tunconfpath); for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { MainWindowItem* item = *it; item->installListeners(this); } QObject::connect(uiSettings->tunnelsConfigFileLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(reloadTunnelsConfigAndUI())); QObject::connect(ui->addServerTunnelPushButton, SIGNAL(released()), this, SLOT(addServerTunnelPushButtonReleased())); QObject::connect(ui->addClientTunnelPushButton, SIGNAL(released()), this, SLOT(addClientTunnelPushButtonReleased())); #ifndef ANDROID QObject::connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); setIcon(); trayIcon->show(); #endif //QMetaObject::connectSlotsByName(this); } void MainWindow::logDestinationComboBoxValueChanged(const QString & text) { bool fileEnabled = text==QString("file"); uiSettings->logFileLineEdit->setEnabled(fileEnabled); uiSettings->logFileBrowsePushButton->setEnabled(fileEnabled); } void MainWindow::updateRouterCommandsButtons() { bool acceptsTunnels = i2p::context.AcceptsTunnels (); routerCommandsUI->declineTransitTunnelsPushButton->setEnabled(acceptsTunnels); routerCommandsUI->acceptTransitTunnelsPushButton->setEnabled(!acceptsTunnels); } void MainWindow::showStatusPage(StatusPage newStatusPage){ ui->stackedWidget->setCurrentIndex(0); setStatusButtonsVisible(true); statusPage=newStatusPage; showHiddenInfoStatusMainPage=false; if(newStatusPage!=StatusPage::commands){ textBrowser->setHtml(getStatusPageHtml(false)); textBrowser->show(); routerCommandsParent->hide(); pageWithBackButton->hide(); }else{ routerCommandsParent->show(); textBrowser->hide(); pageWithBackButton->hide(); updateRouterCommandsButtons(); } wasSelectingAtStatusMainPage=false; } void MainWindow::showSettingsPage(){ui->stackedWidget->setCurrentIndex(1);setStatusButtonsVisible(false);} void MainWindow::showTunnelsPage(){ui->stackedWidget->setCurrentIndex(2);setStatusButtonsVisible(false);} void MainWindow::showRestartPage(){ui->stackedWidget->setCurrentIndex(3);setStatusButtonsVisible(false);} void MainWindow::showQuitPage(){ui->stackedWidget->setCurrentIndex(4);setStatusButtonsVisible(false);} void MainWindow::setStatusButtonsVisible(bool visible) { ui->statusButtonsPane->setVisible(visible); } // see also: HTTPServer.cpp QString MainWindow::getStatusPageHtml(bool showHiddenInfo) { std::stringstream s; s << ""; switch (statusPage) { case main_page: i2p::http::ShowStatus(s, showHiddenInfo);break; case commands: break; case local_destinations: i2p::http::ShowLocalDestinations(s);break; case leasesets: i2p::http::ShowLeasesSets(s); break; case tunnels: i2p::http::ShowTunnels(s); break; case transit_tunnels: i2p::http::ShowTransitTunnels(s); break; case transports: i2p::http::ShowTransports(s); break; case i2p_tunnels: i2p::http::ShowI2PTunnels(s); break; case sam_sessions: i2p::http::ShowSAMSessions(s); break; default: assert(false); break; } std::string str = s.str(); return QString::fromStdString(str); } void MainWindow::showStatusMainPage() { showStatusPage(StatusPage::main_page); } void MainWindow::showStatus_commands_Page() { showStatusPage(StatusPage::commands); } void MainWindow::showStatus_local_destinations_Page() { showStatusPage(StatusPage::local_destinations); } void MainWindow::showStatus_leasesets_Page() { showStatusPage(StatusPage::leasesets); } void MainWindow::showStatus_tunnels_Page() { showStatusPage(StatusPage::tunnels); } void MainWindow::showStatus_transit_tunnels_Page() { showStatusPage(StatusPage::transit_tunnels); } void MainWindow::showStatus_transports_Page() { showStatusPage(StatusPage::transports); } void MainWindow::showStatus_i2p_tunnels_Page() { showStatusPage(StatusPage::i2p_tunnels); } void MainWindow::showStatus_sam_sessions_Page() { showStatusPage(StatusPage::sam_sessions); } void MainWindow::scheduleStatusPageUpdates() { statusPageUpdateTimer = new QTimer(this); connect(statusPageUpdateTimer, SIGNAL(timeout()), this, SLOT(updateStatusPage())); statusPageUpdateTimer->start(10*1000/*millis*/); } void MainWindow::statusHtmlPageMouseReleased() { if(wasSelectingAtStatusMainPage){ QString selection = textBrowser->textCursor().selectedText(); if(!selection.isEmpty()&&!selection.isNull())return; } showHiddenInfoStatusMainPage=!showHiddenInfoStatusMainPage; textBrowser->setHtml(getStatusPageHtml(showHiddenInfoStatusMainPage)); } void MainWindow::statusHtmlPageSelectionChanged() { wasSelectingAtStatusMainPage=true; } void MainWindow::updateStatusPage() { showHiddenInfoStatusMainPage=false; textBrowser->setHtml(getStatusPageHtml(showHiddenInfoStatusMainPage)); } //TODO void MainWindow::resizeEvent(QResizeEvent *event) { QMainWindow::resizeEvent(event); onResize(); } //TODO void MainWindow::onResize() { if(isVisible()){ ui->horizontalLayoutWidget->resize(ui->horizontalLayoutWidget->width(), height()); //status ui->statusPage->resize(ui->statusPage->width(), height()); //tunnels ui->tunnelsPage->resize(ui->tunnelsPage->width(), height()); ui->verticalLayoutWidget_6->resize(ui->verticalLayoutWidget_6->width(), height()-20); /*ui->tunnelsScrollArea->resize(ui->tunnelsScrollArea->width(), ui->verticalLayoutWidget_6->height()-ui->label_5->height());*/ } } #ifndef ANDROID void MainWindow::createActions() { toggleWindowVisibleAction = new QAction(tr("&Toggle the window"), this); connect(toggleWindowVisibleAction, SIGNAL(triggered()), this, SLOT(toggleVisibilitySlot())); //quitAction = new QAction(tr("&Quit"), this); //connect(quitAction, SIGNAL(triggered()), QApplication::instance(), SLOT(quit())); } void MainWindow::toggleVisibilitySlot() { setVisible(!isVisible()); } void MainWindow::createTrayIcon() { trayIconMenu = new QMenu(this); trayIconMenu->addAction(toggleWindowVisibleAction); //trayIconMenu->addSeparator(); //trayIconMenu->addAction(quitAction); trayIcon = new QSystemTrayIcon(this); trayIcon->setContextMenu(trayIconMenu); } void MainWindow::setIcon() { QIcon icon(":/images/icon.png"); trayIcon->setIcon(icon); setWindowIcon(icon); trayIcon->setToolTip(QApplication::translate("MainWindow", "i2pd", 0)); } void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) { switch (reason) { case QSystemTrayIcon::Trigger: case QSystemTrayIcon::DoubleClick: case QSystemTrayIcon::MiddleClick: setVisible(!isVisible()); break; default: qDebug() << "MainWindow::iconActivated(): unknown reason: " << reason << endl; break; } } void MainWindow::closeEvent(QCloseEvent *event) { if(quitting){ QMainWindow::closeEvent(event); return; } if (trayIcon->isVisible()) { QMessageBox::information(this, tr("i2pd"), tr("The program will keep running in the " "system tray. To gracefully terminate the program, " "choose Graceful Quit at the main i2pd window.")); hide(); event->ignore(); } } #endif void MainWindow::handleQuitButton() { qDebug("Quit pressed. Hiding the main window"); #ifndef ANDROID quitting=true; #endif close(); QApplication::instance()->quit(); } void MainWindow::handleGracefulQuitButton() { qDebug("Graceful Quit pressed."); ui->gracefulQuitPushButton->setText(QApplication::translate("MainWindow", "Graceful quit is in progress", 0)); ui->gracefulQuitPushButton->setEnabled(false); ui->gracefulQuitPushButton->adjustSize(); ui->quitPage->adjustSize(); i2p::context.SetAcceptsTunnels (false); // stop accpting tunnels QTimer::singleShot(10*60*1000//millis , this, SLOT(handleGracefulQuitTimerEvent())); } void MainWindow::handleDoRestartButton() { qDebug()<<"Do Restart pressed."; emit i2pController->restartDaemon(); } void MainWindow::handleGracefulQuitTimerEvent() { qDebug("Hiding the main window"); #ifndef ANDROID quitting=true; #endif close(); qDebug("Performing quit"); QApplication::instance()->quit(); } MainWindow::~MainWindow() { qDebug("Destroying main window"); delete statusPageUpdateTimer; for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { MainWindowItem* item = *it; item->deleteLater(); } configItems.clear(); //QMessageBox::information(0, "Debug", "mw destructor 1"); //delete ui; //QMessageBox::information(0, "Debug", "mw destructor 2"); } FileChooserItem* MainWindow::initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton){ FileChooserItem* retVal; retVal=new FileChooserItem(option, fileNameLineEdit, fileBrowsePushButton); MainWindowItem* super=retVal; configItems.append(super); return retVal; } void MainWindow::initFolderChooser(ConfigOption option, QLineEdit* folderLineEdit, QPushButton* folderBrowsePushButton){ configItems.append(new FolderChooserItem(option, folderLineEdit, folderBrowsePushButton)); } /*void MainWindow::initCombobox(ConfigOption option, QComboBox* comboBox){ configItems.append(new ComboBoxItem(option, comboBox)); QObject::connect(comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveAllConfigs())); }*/ void MainWindow::initLogDestinationCombobox(ConfigOption option, QComboBox* comboBox){ configItems.append(new LogDestinationComboBoxItem(option, comboBox)); } void MainWindow::initLogLevelCombobox(ConfigOption option, QComboBox* comboBox){ configItems.append(new LogLevelComboBoxItem(option, comboBox)); } void MainWindow::initSignatureTypeCombobox(ConfigOption option, QComboBox* comboBox){ configItems.append(new SignatureTypeComboBoxItem(option, comboBox)); } void MainWindow::initIPAddressBox(ConfigOption option, QLineEdit* addressLineEdit, QString fieldNameTranslated){ configItems.append(new IPAddressStringItem(option, addressLineEdit, fieldNameTranslated)); } void MainWindow::initTCPPortBox(ConfigOption option, QLineEdit* portLineEdit, QString fieldNameTranslated){ configItems.append(new TCPPortStringItem(option, portLineEdit, fieldNameTranslated)); } void MainWindow::initCheckBox(ConfigOption option, QCheckBox* checkBox) { configItems.append(new CheckBoxItem(option, checkBox)); } void MainWindow::initIntegerBox(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ configItems.append(new IntegerStringItem(option, numberLineEdit, fieldNameTranslated)); } void MainWindow::initUInt32Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ configItems.append(new UInt32StringItem(option, numberLineEdit, fieldNameTranslated)); } void MainWindow::initUInt16Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated){ configItems.append(new UInt16StringItem(option, numberLineEdit, fieldNameTranslated)); } void MainWindow::initStringBox(ConfigOption option, QLineEdit* lineEdit){ configItems.append(new BaseStringItem(option, lineEdit, QString())); } NonGUIOptionItem* MainWindow::initNonGUIOption(ConfigOption option) { NonGUIOptionItem * retValue; configItems.append(retValue=new NonGUIOptionItem(option)); return retValue; } void MainWindow::loadAllConfigs(){ //BORROWED FROM ??? //TODO move this code into single location std::string config; i2p::config::GetOption("conf", config); std::string datadir; i2p::config::GetOption("datadir", datadir); bool service = false; #ifndef _WIN32 i2p::config::GetOption("service", service); #endif i2p::fs::DetectDataDir(datadir, service); i2p::fs::Init(); datadir = i2p::fs::GetDataDir(); // TODO: drop old name detection in v2.8.0 if (config == "") { config = i2p::fs::DataDirPath("i2p.conf"); if (i2p::fs::Exists (config)) { LogPrint(eLogWarning, "Daemon: please rename i2p.conf to i2pd.conf here: ", config); } else { config = i2p::fs::DataDirPath("i2pd.conf"); /*if (!i2p::fs::Exists (config)) {}*/ } } //BORROWED FROM ClientContext.cpp //TODO move this code into single location std::string tunConf; i2p::config::GetOption("tunconf", tunConf); if (tunConf == "") { // TODO: cleanup this in 2.8.0 tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); if (i2p::fs::Exists(tunConf)) { LogPrint(eLogWarning, "FS: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); } else { tunConf = i2p::fs::DataDirPath ("tunnels.conf"); } } this->confpath = config.c_str(); this->datadir = datadir.c_str(); this->tunconfpath = tunConf.c_str(); for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { MainWindowItem* item = *it; item->loadFromConfigOption(); } ReadTunnelsConfig(); } /** returns false iff not valid items present and save was aborted */ bool MainWindow::saveAllConfigs(){ QString cannotSaveSettings = QApplication::tr("Cannot save settings."); programOptionsWriterCurrentSection=""; /*if(!logFileNameOption->lineEdit->text().trimmed().isEmpty())logOption->optionValue=boost::any(std::string("file")); else logOption->optionValue=boost::any(std::string("stdout"));*/ daemonOption->optionValue=boost::any(false); serviceOption->optionValue=boost::any(false); std::stringstream out; for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { MainWindowItem* item = *it; if(!item->isValid()){ highlightWrongInput(QApplication::tr("Invalid value for")+" "+item->getConfigOption().section+"::"+item->getConfigOption().option+". "+item->getRequirementToBeValid()+" "+cannotSaveSettings, item->getWidgetToFocus()); return false; } } for(QList::iterator it = configItems.begin(); it!= configItems.end(); ++it) { MainWindowItem* item = *it; item->saveToStringStream(out); } using namespace std; QString backup=confpath+"~"; if(QFile::exists(backup)) QFile::remove(backup);//TODO handle errors if(QFile::exists(confpath)) QFile::rename(confpath, backup);//TODO handle errors ofstream outfile; outfile.open(confpath.toStdString());//TODO handle errors outfile << out.str().c_str(); outfile.close(); SaveTunnelsConfig(); return true; } void FileChooserItem::pushButtonReleased() { QString fileName = lineEdit->text().trimmed(); fileName = QFileDialog::getOpenFileName(nullptr, tr("Open File"), fileName, tr("All Files (*.*)")); if(fileName.length()>0)lineEdit->setText(fileName); } void FolderChooserItem::pushButtonReleased() { QString fileName = lineEdit->text().trimmed(); fileName = QFileDialog::getExistingDirectory(nullptr, tr("Open Folder"), fileName); if(fileName.length()>0)lineEdit->setText(fileName); } void BaseStringItem::installListeners(MainWindow *mainWindow) { QObject::connect(lineEdit, SIGNAL(textChanged(const QString &)), mainWindow, SLOT(updated())); } void ComboBoxItem::installListeners(MainWindow *mainWindow) { QObject::connect(comboBox, SIGNAL(currentIndexChanged(int)), mainWindow, SLOT(updated())); } void CheckBoxItem::installListeners(MainWindow *mainWindow) { QObject::connect(checkBox, SIGNAL(stateChanged(int)), mainWindow, SLOT(updated())); } void MainWindow::updated() { ui->wrongInputLabel->setVisible(false); adjustSizesAccordingToWrongLabel(); applyTunnelsUiToConfigs(); saveAllConfigs(); } void MainWindowItem::installListeners(MainWindow *mainWindow) {} void MainWindow::appendTunnelForms(std::string tunnelNameToFocus) { int height=0; ui->tunnelsScrollAreaWidgetContents->setGeometry(0,0,0,0); for(std::map::iterator it = tunnelConfigs.begin(); it != tunnelConfigs.end(); ++it) { const std::string& name=it->first; TunnelConfig* tunconf = it->second; ServerTunnelConfig* stc = tunconf->asServerTunnelConfig(); if(stc){ ServerTunnelPane * tunnelPane=new ServerTunnelPane(&tunnelsPageUpdateListener, stc, ui->wrongInputLabel, ui->wrongInputLabel, this); int h=tunnelPane->appendServerTunnelForm(stc, ui->tunnelsScrollAreaWidgetContents, tunnelPanes.size(), height); height+=h; //qDebug() << "tun.height:" << height << "sz:" << tunnelPanes.size(); tunnelPanes.push_back(tunnelPane); if(name==tunnelNameToFocus){ tunnelPane->getNameLineEdit()->setFocus(); ui->tunnelsScrollArea->ensureWidgetVisible(tunnelPane->getNameLineEdit()); } continue; } ClientTunnelConfig* ctc = tunconf->asClientTunnelConfig(); if(ctc){ ClientTunnelPane * tunnelPane=new ClientTunnelPane(&tunnelsPageUpdateListener, ctc, ui->wrongInputLabel, ui->wrongInputLabel, this); int h=tunnelPane->appendClientTunnelForm(ctc, ui->tunnelsScrollAreaWidgetContents, tunnelPanes.size(), height); height+=h; //qDebug() << "tun.height:" << height << "sz:" << tunnelPanes.size(); tunnelPanes.push_back(tunnelPane); if(name==tunnelNameToFocus){ tunnelPane->getNameLineEdit()->setFocus(); ui->tunnelsScrollArea->ensureWidgetVisible(tunnelPane->getNameLineEdit()); } continue; } throw "unknown TunnelConfig subtype"; } //qDebug() << "tun.setting height:" << height; ui->tunnelsScrollAreaWidgetContents->setGeometry(QRect(0, 0, 621, height)); QList childWidgets = ui->tunnelsScrollAreaWidgetContents->findChildren(); foreach(QWidget* widget, childWidgets) widget->show(); } void MainWindow::deleteTunnelForms() { for(std::list::iterator it = tunnelPanes.begin(); it != tunnelPanes.end(); ++it) { TunnelPane* tp = *it; ServerTunnelPane* stp = tp->asServerTunnelPane(); if(stp){ stp->deleteServerTunnelForm(); delete stp; continue; } ClientTunnelPane* ctp = tp->asClientTunnelPane(); if(ctp){ ctp->deleteClientTunnelForm(); delete ctp; continue; } throw "unknown TunnelPane subtype"; } tunnelPanes.clear(); } bool MainWindow::applyTunnelsUiToConfigs() { for(std::list::iterator it = tunnelPanes.begin(); it != tunnelPanes.end(); ++it) { TunnelPane* tp = *it; if(!tp->applyDataFromUIToTunnelConfig())return false; } return true; } void MainWindow::reloadTunnelsConfigAndUI(std::string tunnelNameToFocus) { deleteTunnelForms(); for (std::map::iterator it=tunnelConfigs.begin(); it!=tunnelConfigs.end(); ++it) { TunnelConfig* tunconf = it->second; delete tunconf; } tunnelConfigs.clear(); ReadTunnelsConfig(); appendTunnelForms(tunnelNameToFocus); } void MainWindow::SaveTunnelsConfig() { std::stringstream out; for (std::map::iterator it=tunnelConfigs.begin(); it!=tunnelConfigs.end(); ++it) { const std::string& name = it->first; TunnelConfig* tunconf = it->second; tunconf->saveHeaderToStringStream(out); tunconf->saveToStringStream(out); tunconf->saveI2CPParametersToStringStream(out); } using namespace std; QString backup=tunconfpath+"~"; if(QFile::exists(backup)) QFile::remove(backup);//TODO handle errors if(QFile::exists(tunconfpath)) QFile::rename(tunconfpath, backup);//TODO handle errors ofstream outfile; outfile.open(tunconfpath.toStdString());//TODO handle errors outfile << out.str().c_str(); outfile.close(); i2p::client::context.ReloadConfig(); } void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::updated(std::string oldName, TunnelConfig* tunConf) { if(oldName!=tunConf->getName()) { //name has changed std::map::const_iterator it=mainWindow->tunnelConfigs.find(oldName); if(it!=mainWindow->tunnelConfigs.end())mainWindow->tunnelConfigs.erase(it); mainWindow->tunnelConfigs[tunConf->getName()]=tunConf; } mainWindow->saveAllConfigs(); } void MainWindow::TunnelsPageUpdateListenerMainWindowImpl::needsDeleting(std::string oldName){ mainWindow->DeleteTunnelNamed(oldName); } void MainWindow::addServerTunnelPushButtonReleased() { CreateDefaultServerTunnel(); } void MainWindow::addClientTunnelPushButtonReleased() { CreateDefaultClientTunnel(); } void MainWindow::setI2PController(i2p::qt::Controller* controller_) { this->i2pController = controller_; } void MainWindow::runPeerTest() { i2p::transport::transports.PeerTest(); } void MainWindow::enableTransit() { i2p::context.SetAcceptsTunnels(true); updateRouterCommandsButtons(); } void MainWindow::disableTransit() { i2p::context.SetAcceptsTunnels(false); updateRouterCommandsButtons(); } void MainWindow::anchorClickedHandler(const QUrl & link) { QString debugStr=QString()+"anchorClicked: "+"\""+link.toString()+"\""; qDebug()<show(); textBrowser->hide(); std::stringstream s; i2p::http::ShowLocalDestination(s,str.toStdString()); childTextBrowser->setHtml(QString::fromStdString(s.str())); } } void MainWindow::backClickedFromChild() { showStatusPage(statusPage); } void MainWindow::adjustSizesAccordingToWrongLabel() { if(ui->wrongInputLabel->isVisible()) { int dh = ui->wrongInputLabel->height()+ui->verticalLayout_7->layout()->spacing(); ui->verticalLayout_7->invalidate(); ui->wrongInputLabel->adjustSize(); ui->stackedWidget->adjustSize(); ui->stackedWidget->setFixedHeight(531-dh); ui->settingsPage->setFixedHeight(531-dh); ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, 711, 531-dh)); ui->stackedWidget->setFixedHeight(531-dh); ui->settingsScrollArea->setFixedHeight(531-dh-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); ui->settingsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); ui->tunnelsScrollArea->setFixedHeight(531-dh-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); ui->tunnelsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); }else{ ui->verticalLayout_7->invalidate(); ui->wrongInputLabel->adjustSize(); ui->stackedWidget->adjustSize(); ui->stackedWidget->setFixedHeight(531); ui->settingsPage->setFixedHeight(531); ui->verticalLayoutWidget_4->setGeometry(QRect(0, 0, 711, 531)); ui->stackedWidget->setFixedHeight(531); ui->settingsScrollArea->setFixedHeight(531-settingsTitleLabelNominalHeight-ui->verticalLayout_4->spacing()); ui->settingsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); ui->tunnelsScrollArea->setFixedHeight(531-settingsTitleLabelNominalHeight-ui->horizontalLayout_42->geometry().height()-2*ui->verticalLayout_4->spacing()); ui->tunnelsTitleLabel->setFixedHeight(settingsTitleLabelNominalHeight); } } void MainWindow::highlightWrongInput(QString warningText, QWidget* widgetToFocus) { bool redVisible = ui->wrongInputLabel->isVisible(); ui->wrongInputLabel->setVisible(true); ui->wrongInputLabel->setText(warningText); if(!redVisible)adjustSizesAccordingToWrongLabel(); if(widgetToFocus){ui->settingsScrollArea->ensureWidgetVisible(widgetToFocus);widgetToFocus->setFocus();} showSettingsPage(); } i2pd-2.17.0/qt/i2pd_qt/mainwindow.h000066400000000000000000001023411321131324000167410ustar00rootroot00000000000000#ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "QVBoxLayout" #include "QUrl" #ifndef ANDROID # include # include # include #endif #include #include #include "MainWindowItems.h" #include "TunnelPane.h" #include "ServerTunnelPane.h" #include "ClientTunnelPane.h" #include "TunnelConfig.h" #include "textbrowsertweaked1.h" #include "Config.h" #include "FS.h" #include #include #include #include "TunnelsPageUpdateListener.h" #include "DaemonQT.h" #include "SignatureTypeComboboxFactory.h" #include "pagewithbackbutton.h" #include #include "widgetlockregistry.h" #include "widgetlock.h" template bool isType(boost::any& a) { return #ifdef BOOST_AUX_ANY_TYPE_ID_NAME std::strcmp(a.type().name(), typeid(ValueType).name()) == 0 #else a.type() == typeid(ValueType) #endif ; } class ConfigOption { public: QString section; QString option; //MainWindow::DefaultValueGetter defaultValueGetter; ConfigOption(QString section_, QString option_/*, DefaultValueGetter defaultValueGetter_*/): section(section_) , option(option_) //, defaultValueGetter(defaultValueGetter_) {} }; extern std::string programOptionsWriterCurrentSection; class MainWindow; class MainWindowItem : public QObject { Q_OBJECT ConfigOption option; QWidget* widgetToFocus; QString requirementToBeValid; public: MainWindowItem(ConfigOption option_, QWidget* widgetToFocus_, QString requirementToBeValid_) : option(option_), widgetToFocus(widgetToFocus_), requirementToBeValid(requirementToBeValid_) {} QWidget* getWidgetToFocus(){return widgetToFocus;} QString& getRequirementToBeValid() { return requirementToBeValid; } ConfigOption& getConfigOption() { return option; } boost::any optionValue; virtual ~MainWindowItem(){} virtual void installListeners(MainWindow *mainWindow); virtual void loadFromConfigOption(){ std::string optName=""; if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); optName+=option.option.toStdString(); //qDebug() << "loadFromConfigOption[" << optName.c_str() << "]"; boost::any programOption; i2p::config::GetOptionAsAny(optName, programOption); optionValue=programOption.empty()?boost::any(std::string("")) :boost::any_cast(programOption).value(); } virtual void saveToStringStream(std::stringstream& out){ if(isType(optionValue)) { std::string v = boost::any_cast(optionValue); if(v.empty())return; } if(optionValue.empty())return; std::string rtti = optionValue.type().name(); std::string optName=""; if(!option.section.isEmpty())optName=option.section.toStdString()+std::string("."); optName+=option.option.toStdString(); qDebug() << "Writing option" << optName.c_str() << "of type" << rtti.c_str(); std::string sectionAsStdStr = option.section.toStdString(); if(!option.section.isEmpty() && sectionAsStdStr!=programOptionsWriterCurrentSection) { out << "[" << sectionAsStdStr << "]\n"; programOptionsWriterCurrentSection=sectionAsStdStr; } out << option.option.toStdString() << "="; if(isType(optionValue)) { out << boost::any_cast(optionValue); }else if(isType(optionValue)) { out << (boost::any_cast(optionValue) ? "true" : "false"); }else if(isType(optionValue)) { out << boost::any_cast(optionValue); }else if(isType(optionValue)) { out << boost::any_cast(optionValue); }else if(isType(optionValue)) { out << boost::any_cast(optionValue); }else if(isType(optionValue)) { out << boost::any_cast(optionValue); }else out << boost::any_cast(optionValue); //let it throw out << "\n\n"; } virtual bool isValid(){return true;} }; class NonGUIOptionItem : public MainWindowItem { public: NonGUIOptionItem(ConfigOption option_) : MainWindowItem(option_, nullptr, QString()) {}; virtual ~NonGUIOptionItem(){} virtual bool isValid() { return true; } }; class BaseStringItem : public MainWindowItem { Q_OBJECT public: QLineEdit* lineEdit; BaseStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString requirementToBeValid_) : MainWindowItem(option_, lineEdit_, requirementToBeValid_), lineEdit(lineEdit_){}; virtual ~BaseStringItem(){} virtual void installListeners(MainWindow *mainWindow); virtual QString toString(){ return boost::any_cast(optionValue).c_str(); } virtual boost::any fromString(QString s){return boost::any(s.toStdString());} virtual void loadFromConfigOption(){ MainWindowItem::loadFromConfigOption(); lineEdit->setText(toString()); } virtual void saveToStringStream(std::stringstream& out){ optionValue=fromString(lineEdit->text()); MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } }; class FileOrFolderChooserItem : public BaseStringItem { public: QPushButton* browsePushButton; FileOrFolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : BaseStringItem(option_, lineEdit_, QString()), browsePushButton(browsePushButton_) {} virtual ~FileOrFolderChooserItem(){} }; class FileChooserItem : public FileOrFolderChooserItem { Q_OBJECT private slots: void pushButtonReleased(); public: FileChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_) { QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); } }; class FolderChooserItem : public FileOrFolderChooserItem{ Q_OBJECT private slots: void pushButtonReleased(); public: FolderChooserItem(ConfigOption option_, QLineEdit* lineEdit_, QPushButton* browsePushButton_) : FileOrFolderChooserItem(option_, lineEdit_, browsePushButton_) { QObject::connect(browsePushButton, SIGNAL(released()), this, SLOT(pushButtonReleased())); } }; class ComboBoxItem : public MainWindowItem { public: QComboBox* comboBox; ComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : MainWindowItem(option_,comboBox_,QString()), comboBox(comboBox_){}; virtual ~ComboBoxItem(){} virtual void installListeners(MainWindow *mainWindow); virtual void loadFromConfigOption()=0; virtual void saveToStringStream(std::stringstream& out)=0; virtual bool isValid() { return true; } }; class LogDestinationComboBoxItem : public ComboBoxItem { public: LogDestinationComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {}; virtual ~LogDestinationComboBoxItem(){} virtual void loadFromConfigOption(){ MainWindowItem::loadFromConfigOption(); const char * ld = boost::any_cast(optionValue).c_str(); comboBox->setCurrentText(QString(ld)); } virtual void saveToStringStream(std::stringstream& out){ std::string logDest = comboBox->currentText().toStdString(); optionValue=logDest; MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } }; class LogLevelComboBoxItem : public ComboBoxItem { public: LogLevelComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {}; virtual ~LogLevelComboBoxItem(){} virtual void loadFromConfigOption(){ MainWindowItem::loadFromConfigOption(); const char * ll = boost::any_cast(optionValue).c_str(); comboBox->setCurrentText(QString(ll)); } virtual void saveToStringStream(std::stringstream& out){ optionValue=comboBox->currentText().toStdString(); MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } }; class SignatureTypeComboBoxItem : public ComboBoxItem { public: SignatureTypeComboBoxItem(ConfigOption option_, QComboBox* comboBox_) : ComboBoxItem(option_, comboBox_) {}; virtual ~SignatureTypeComboBoxItem(){} virtual void loadFromConfigOption(){ MainWindowItem::loadFromConfigOption(); while(comboBox->count()>0)comboBox->removeItem(0); uint16_t selected = (uint16_t) boost::any_cast(optionValue); SignatureTypeComboBoxFactory::fillComboBox(comboBox, selected); } virtual void saveToStringStream(std::stringstream& out){ uint16_t selected = SignatureTypeComboBoxFactory::getSigType(comboBox->currentData()); optionValue=(unsigned short)selected; MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } }; class CheckBoxItem : public MainWindowItem { public: QCheckBox* checkBox; CheckBoxItem(ConfigOption option_, QCheckBox* checkBox_) : MainWindowItem(option_,checkBox_,QString()), checkBox(checkBox_){}; virtual ~CheckBoxItem(){} virtual void installListeners(MainWindow *mainWindow); virtual void loadFromConfigOption(){ MainWindowItem::loadFromConfigOption(); checkBox->setChecked(boost::any_cast(optionValue)); } virtual void saveToStringStream(std::stringstream& out){ optionValue=checkBox->isChecked(); MainWindowItem::saveToStringStream(out); } virtual bool isValid() { return true; } }; class BaseFormattedStringItem : public BaseStringItem { public: QString fieldNameTranslated; BaseFormattedStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_, QString requirementToBeValid_) : BaseStringItem(option_, lineEdit_, requirementToBeValid_), fieldNameTranslated(fieldNameTranslated_) {}; virtual ~BaseFormattedStringItem(){} virtual bool isValid()=0; }; class IntegerStringItem : public BaseFormattedStringItem { public: IntegerStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be a valid integer.")) {}; virtual ~IntegerStringItem(){} virtual bool isValid(){ auto str=lineEdit->text(); bool ok; str.toInt(&ok); return ok; } virtual QString toString(){return QString::number(boost::any_cast(optionValue));} virtual boost::any fromString(QString s){return boost::any(std::stoi(s.toStdString()));} }; class UShortStringItem : public BaseFormattedStringItem { public: UShortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned short integer.")) {}; virtual ~UShortStringItem(){} virtual bool isValid(){ auto str=lineEdit->text(); bool ok; str.toUShort(&ok); return ok; } virtual QString toString(){return QString::number(boost::any_cast(optionValue));} virtual boost::any fromString(QString s){return boost::any((unsigned short)std::stoi(s.toStdString()));} }; class UInt32StringItem : public BaseFormattedStringItem { public: UInt32StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 32-bit integer.")) {}; virtual ~UInt32StringItem(){} virtual bool isValid(){ auto str=lineEdit->text(); bool ok; str.toUInt(&ok); return ok; } virtual QString toString(){return QString::number(boost::any_cast(optionValue));} virtual boost::any fromString(QString s){return boost::any((uint32_t)std::stoi(s.toStdString()));} }; class UInt16StringItem : public BaseFormattedStringItem { public: UInt16StringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be unsigned 16-bit integer.")) {}; virtual ~UInt16StringItem(){} virtual bool isValid(){ auto str=lineEdit->text(); bool ok; str.toUShort(&ok); return ok; } virtual QString toString(){return QString::number(boost::any_cast(optionValue));} virtual boost::any fromString(QString s){return boost::any((uint16_t)std::stoi(s.toStdString()));} }; class IPAddressStringItem : public BaseFormattedStringItem { public: IPAddressStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : BaseFormattedStringItem(option_, lineEdit_, fieldNameTranslated_, QApplication::tr("Must be an IPv4 address")) {}; virtual bool isValid(){return true;}//todo }; class TCPPortStringItem : public UShortStringItem { public: TCPPortStringItem(ConfigOption option_, QLineEdit* lineEdit_, QString fieldNameTranslated_) : UShortStringItem(option_, lineEdit_, fieldNameTranslated_) {}; }; namespace Ui { class MainWindow; class StatusButtonsForm; class routerCommandsWidget; class GeneralSettingsContentsForm; } using namespace i2p::client; class TunnelPane; using namespace i2p::qt; class Controller; class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent=0); ~MainWindow(); void setI2PController(i2p::qt::Controller* controller_); void highlightWrongInput(QString warningText, QWidget* widgetToFocus); //typedef std::function DefaultValueGetter; //#ifndef ANDROID // void setVisible(bool visible); //#endif private: enum StatusPage {main_page, commands, local_destinations, leasesets, tunnels, transit_tunnels, transports, i2p_tunnels, sam_sessions}; private slots: void updated(); void handleQuitButton(); void handleGracefulQuitButton(); void handleDoRestartButton(); void handleGracefulQuitTimerEvent(); #ifndef ANDROID void setIcon(); void iconActivated(QSystemTrayIcon::ActivationReason reason); void toggleVisibilitySlot(); #endif void scheduleStatusPageUpdates(); void statusHtmlPageMouseReleased(); void statusHtmlPageSelectionChanged(); void updateStatusPage(); void showStatusMainPage(); void showStatus_commands_Page(); void runPeerTest(); void enableTransit(); void disableTransit(); public slots: void showStatus_local_destinations_Page(); void showStatus_leasesets_Page(); void showStatus_tunnels_Page(); void showStatus_transit_tunnels_Page(); void showStatus_transports_Page(); void showStatus_i2p_tunnels_Page(); void showStatus_sam_sessions_Page(); void showSettingsPage(); void showTunnelsPage(); void showRestartPage(); void showQuitPage(); private: StatusPage statusPage; QTimer * statusPageUpdateTimer; bool wasSelectingAtStatusMainPage; bool showHiddenInfoStatusMainPage; void showStatusPage(StatusPage newStatusPage); #ifndef ANDROID void createActions(); void createTrayIcon(); bool quitting; QAction *toggleWindowVisibleAction; QSystemTrayIcon *trayIcon; QMenu *trayIconMenu; #endif public: Ui::MainWindow* ui; Ui::StatusButtonsForm* statusButtonsUI; Ui::routerCommandsWidget* routerCommandsUI; Ui::GeneralSettingsContentsForm* uiSettings; void adjustSizesAccordingToWrongLabel(); bool applyTunnelsUiToConfigs(); private: int settingsTitleLabelNominalHeight; TextBrowserTweaked1 * textBrowser; QWidget * routerCommandsParent; PageWithBackButton * pageWithBackButton; TextBrowserTweaked1 * childTextBrowser; widgetlockregistry widgetlocks; i2p::qt::Controller* i2pController; protected: void updateRouterCommandsButtons(); #ifndef ANDROID void closeEvent(QCloseEvent *event); #endif void resizeEvent(QResizeEvent* event); void onResize(); void setStatusButtonsVisible(bool visible); QString getStatusPageHtml(bool showHiddenInfo); QList configItems; NonGUIOptionItem* daemonOption; NonGUIOptionItem* serviceOption; //LogDestinationComboBoxItem* logOption; FileChooserItem* logFileNameOption; FileChooserItem* initFileChooser(ConfigOption option, QLineEdit* fileNameLineEdit, QPushButton* fileBrowsePushButton); void initFolderChooser(ConfigOption option, QLineEdit* folderLineEdit, QPushButton* folderBrowsePushButton); //void initCombobox(ConfigOption option, QComboBox* comboBox); void initLogDestinationCombobox(ConfigOption option, QComboBox* comboBox); void initLogLevelCombobox(ConfigOption option, QComboBox* comboBox); void initSignatureTypeCombobox(ConfigOption option, QComboBox* comboBox); void initIPAddressBox(ConfigOption option, QLineEdit* addressLineEdit, QString fieldNameTranslated); void initTCPPortBox(ConfigOption option, QLineEdit* portLineEdit, QString fieldNameTranslated); void initCheckBox(ConfigOption option, QCheckBox* checkBox); void initIntegerBox(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated); void initUInt32Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated); void initUInt16Box(ConfigOption option, QLineEdit* numberLineEdit, QString fieldNameTranslated); void initStringBox(ConfigOption option, QLineEdit* lineEdit); NonGUIOptionItem* initNonGUIOption(ConfigOption option); void loadAllConfigs(); public slots: /** returns false iff not valid items present and save was aborted */ bool saveAllConfigs(); void SaveTunnelsConfig(); void reloadTunnelsConfigAndUI(std::string tunnelNameToFocus); //focus none void reloadTunnelsConfigAndUI() { reloadTunnelsConfigAndUI(""); } void addServerTunnelPushButtonReleased(); void addClientTunnelPushButtonReleased(); void anchorClickedHandler(const QUrl & link); void backClickedFromChild(); void logDestinationComboBoxValueChanged(const QString & text); private: QString datadir; QString confpath; QString tunconfpath; std::map tunnelConfigs; std::list tunnelPanes; void appendTunnelForms(std::string tunnelNameToFocus); void deleteTunnelForms(); /* TODO signaturetype */ template std::string GetI2CPOption (const Section& section, const std::string& name, const Type& value) const { return section.second.get (boost::property_tree::ptree::path_type (name, '/'), std::to_string (value)); } template void ReadI2CPOptions (const Section& section, std::map& options, I2CPParameters& param /*TODO fill param*/) const { std::string _INBOUND_TUNNEL_LENGTH = options[I2CP_PARAM_INBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNEL_LENGTH, DEFAULT_INBOUND_TUNNEL_LENGTH); param.setInbound_length(QString(_INBOUND_TUNNEL_LENGTH.c_str())); std::string _OUTBOUND_TUNNEL_LENGTH = options[I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, DEFAULT_OUTBOUND_TUNNEL_LENGTH); param.setOutbound_length(QString(_OUTBOUND_TUNNEL_LENGTH.c_str())); std::string _INBOUND_TUNNELS_QUANTITY = options[I2CP_PARAM_INBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, DEFAULT_INBOUND_TUNNELS_QUANTITY); param.setInbound_quantity( QString(_INBOUND_TUNNELS_QUANTITY.c_str())); std::string _OUTBOUND_TUNNELS_QUANTITY = options[I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY] = GetI2CPOption (section, I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, DEFAULT_OUTBOUND_TUNNELS_QUANTITY); param.setOutbound_quantity(QString(_OUTBOUND_TUNNELS_QUANTITY.c_str())); std::string _TAGS_TO_SEND = options[I2CP_PARAM_TAGS_TO_SEND] = GetI2CPOption (section, I2CP_PARAM_TAGS_TO_SEND, DEFAULT_TAGS_TO_SEND); param.setCrypto_tagsToSend(QString(_TAGS_TO_SEND.c_str())); options[I2CP_PARAM_MIN_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MIN_TUNNEL_LATENCY, DEFAULT_MIN_TUNNEL_LATENCY);//TODO include into param options[I2CP_PARAM_MAX_TUNNEL_LATENCY] = GetI2CPOption(section, I2CP_PARAM_MAX_TUNNEL_LATENCY, DEFAULT_MAX_TUNNEL_LATENCY);//TODO include into param } void CreateDefaultI2CPOptions (I2CPParameters& param /*TODO fill param*/) const { const int _INBOUND_TUNNEL_LENGTH = DEFAULT_INBOUND_TUNNEL_LENGTH; param.setInbound_length(QString::number(_INBOUND_TUNNEL_LENGTH)); const int _OUTBOUND_TUNNEL_LENGTH = DEFAULT_OUTBOUND_TUNNEL_LENGTH; param.setOutbound_length(QString::number(_OUTBOUND_TUNNEL_LENGTH)); const int _INBOUND_TUNNELS_QUANTITY = DEFAULT_INBOUND_TUNNELS_QUANTITY; param.setInbound_quantity( QString::number(_INBOUND_TUNNELS_QUANTITY)); const int _OUTBOUND_TUNNELS_QUANTITY = DEFAULT_OUTBOUND_TUNNELS_QUANTITY; param.setOutbound_quantity(QString::number(_OUTBOUND_TUNNELS_QUANTITY)); const int _TAGS_TO_SEND = DEFAULT_TAGS_TO_SEND; param.setCrypto_tagsToSend(QString::number(_TAGS_TO_SEND)); } void DeleteTunnelNamed(std::string name) { std::map::const_iterator it=tunnelConfigs.find(name); if(it!=tunnelConfigs.end()){ TunnelConfig* tc=it->second; tunnelConfigs.erase(it); delete tc; } saveAllConfigs(); reloadTunnelsConfigAndUI(""); } std::string GenerateNewTunnelName() { int i=1; while(true){ std::stringstream name; name << "name" << i; const std::string& str=name.str(); if(tunnelConfigs.find(str)==tunnelConfigs.end())return str; ++i; } } void CreateDefaultClientTunnel() {//TODO dedup default values with ReadTunnelsConfig() and with ClientContext.cpp::ReadTunnels () std::string name=GenerateNewTunnelName(); std::string type = I2P_TUNNELS_SECTION_TYPE_CLIENT; std::string dest = "127.0.0.1"; int port = 0; std::string keys = ""; std::string address = "127.0.0.1"; int destinationPort = 0; i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; // I2CP I2CPParameters i2cpParameters; CreateDefaultI2CPOptions (i2cpParameters); tunnelConfigs[name]=new ClientTunnelConfig(name, QString(type.c_str()), i2cpParameters, dest, port, keys, address, destinationPort, sigType); saveAllConfigs(); reloadTunnelsConfigAndUI(name); } void CreateDefaultServerTunnel() {//TODO dedup default values with ReadTunnelsConfig() and with ClientContext.cpp::ReadTunnels () std::string name=GenerateNewTunnelName(); std::string type=I2P_TUNNELS_SECTION_TYPE_SERVER; std::string host = "127.0.0.1"; int port = 0; std::string keys = ""; int inPort = 0; std::string accessList = ""; std::string hostOverride = ""; std::string webircpass = ""; bool gzip = true; i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256; uint32_t maxConns = i2p::stream::DEFAULT_MAX_CONNS_PER_MIN; std::string address = "127.0.0.1"; bool isUniqueLocal = true; // I2CP I2CPParameters i2cpParameters; CreateDefaultI2CPOptions (i2cpParameters); tunnelConfigs[name]=new ServerTunnelConfig(name, QString(type.c_str()), i2cpParameters, host, port, keys, inPort, accessList, hostOverride, webircpass, gzip, sigType, maxConns, address, isUniqueLocal); saveAllConfigs(); reloadTunnelsConfigAndUI(name); } void ReadTunnelsConfig() //TODO deduplicate the code with ClientContext.cpp::ReadTunnels () { boost::property_tree::ptree pt; std::string tunConf=tunconfpath.toStdString(); if (tunConf == "") { // TODO: cleanup this in 2.8.0 tunConf = i2p::fs::DataDirPath ("tunnels.cfg"); if (i2p::fs::Exists(tunConf)) { LogPrint(eLogWarning, "FS: please rename tunnels.cfg -> tunnels.conf here: ", tunConf); } else { tunConf = i2p::fs::DataDirPath ("tunnels.conf"); } } LogPrint(eLogDebug, "tunnels config file: ", tunConf); try { boost::property_tree::read_ini (tunConf, pt); } catch (std::exception& ex) { LogPrint (eLogWarning, "Clients: Can't read ", tunConf, ": ", ex.what ());//TODO show err box and disable tunn.page return; } for (auto& section: pt) { std::string name = section.first; try { std::string type = section.second.get (I2P_TUNNELS_SECTION_TYPE); if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_SOCKS || type == I2P_TUNNELS_SECTION_TYPE_WEBSOCKS || type == I2P_TUNNELS_SECTION_TYPE_HTTPPROXY || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { // mandatory params std::string dest; if (type == I2P_TUNNELS_SECTION_TYPE_CLIENT || type == I2P_TUNNELS_SECTION_TYPE_UDPCLIENT) { dest = section.second.get (I2P_CLIENT_TUNNEL_DESTINATION); std::cout << "had read tunnel dest: " << dest << std::endl; } int port = section.second.get (I2P_CLIENT_TUNNEL_PORT); std::cout << "had read tunnel port: " << port << std::endl; // optional params std::string keys = section.second.get (I2P_CLIENT_TUNNEL_KEYS, ""); std::string address = section.second.get (I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"); int destinationPort = section.second.get(I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0); std::cout << "had read tunnel destinationPort: " << destinationPort << std::endl; i2p::data::SigningKeyType sigType = section.second.get (I2P_CLIENT_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); // I2CP std::map options; I2CPParameters i2cpParameters; ReadI2CPOptions (section, options, i2cpParameters); tunnelConfigs[name]=new ClientTunnelConfig(name, QString(type.c_str()), i2cpParameters, dest, port, keys, address, destinationPort, sigType); } else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC || type == I2P_TUNNELS_SECTION_TYPE_UDPSERVER) { // mandatory params std::string host = section.second.get (I2P_SERVER_TUNNEL_HOST); int port = section.second.get (I2P_SERVER_TUNNEL_PORT); std::string keys = section.second.get (I2P_SERVER_TUNNEL_KEYS); // optional params int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0); std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, ""); std::string hostOverride = section.second.get (I2P_SERVER_TUNNEL_HOST_OVERRIDE, ""); std::string webircpass = section.second.get (I2P_SERVER_TUNNEL_WEBIRC_PASSWORD, ""); bool gzip = section.second.get (I2P_SERVER_TUNNEL_GZIP, true); i2p::data::SigningKeyType sigType = section.second.get (I2P_SERVER_TUNNEL_SIGNATURE_TYPE, i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256); uint32_t maxConns = section.second.get(i2p::stream::I2CP_PARAM_STREAMING_MAX_CONNS_PER_MIN, i2p::stream::DEFAULT_MAX_CONNS_PER_MIN); std::string address = section.second.get (I2P_SERVER_TUNNEL_ADDRESS, "127.0.0.1"); bool isUniqueLocal = section.second.get(I2P_SERVER_TUNNEL_ENABLE_UNIQUE_LOCAL, true); // I2CP std::map options; I2CPParameters i2cpParameters; ReadI2CPOptions (section, options, i2cpParameters); /* std::set idents; if (accessList.length () > 0) { size_t pos = 0, comma; do { comma = accessList.find (',', pos); i2p::data::IdentHash ident; ident.FromBase32 (accessList.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos)); idents.insert (ident); pos = comma + 1; } while (comma != std::string::npos); } */ tunnelConfigs[name]=new ServerTunnelConfig(name, QString(type.c_str()), i2cpParameters, host, port, keys, inPort, accessList, hostOverride, webircpass, gzip, sigType, maxConns, address, isUniqueLocal); } else LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", tunConf);//TODO show err box and disable the tunn gui } catch (std::exception& ex) { LogPrint (eLogError, "Clients: Can't read tunnel ", name, " params: ", ex.what ());//TODO show err box and disable the tunn gui } } } private: class TunnelsPageUpdateListenerMainWindowImpl : public TunnelsPageUpdateListener { MainWindow* mainWindow; public: TunnelsPageUpdateListenerMainWindowImpl(MainWindow* mainWindow_):mainWindow(mainWindow_){} virtual void updated(std::string oldName, TunnelConfig* tunConf); virtual void needsDeleting(std::string oldName); }; TunnelsPageUpdateListenerMainWindowImpl tunnelsPageUpdateListener; }; #endif // MAINWINDOW_H i2pd-2.17.0/qt/i2pd_qt/mainwindow.ui000066400000000000000000000723021321131324000171320ustar00rootroot00000000000000 MainWindow 0 0 908 554 908 0 908 16777215 MainWindow 0 0 908 550 908 550 10 10 888 531 QLayout::SetMaximumSize QLayout::SetMinimumSize 0 0 170 496 true Status 0 0 172 0 true General settings true Tunnels settings true Restart true Quit Qt::Horizontal QSizePolicy::Fixed 171 0 Qt::Vertical 20 40 QLayout::SetMinAndMaxSize 0 30 0 0 0 255 0 0 255 127 127 255 63 63 127 0 0 170 0 0 0 0 0 255 255 255 0 0 0 255 255 255 255 0 0 0 0 0 255 127 127 255 255 220 0 0 0 0 0 0 255 0 0 255 127 127 255 63 63 127 0 0 170 0 0 0 0 0 255 255 255 0 0 0 255 255 255 255 0 0 0 0 0 255 127 127 255 255 220 0 0 0 127 0 0 255 0 0 255 127 127 255 63 63 127 0 0 170 0 0 127 0 0 255 255 255 127 0 0 255 0 0 255 0 0 0 0 0 255 0 0 255 255 220 0 0 0 TextLabel true 10 0 0 0 0 713 713 2 0 0 0 0 713 531 QLayout::SetMaximumSize 15 Status QLayout::SetMaximumSize 0 0 0 0 711 531 QLayout::SetMinAndMaxSize 15 General settings 0 0 Qt::ScrollBarAlwaysOn Qt::ScrollBarAsNeeded QAbstractScrollArea::AdjustIgnored true 0 0 689 496 0 0 0 0 711 531 QLayout::SetMinAndMaxSize 15 Tunnels settings Add Client Tunnel Add Server Tunnel Qt::Horizontal 40 20 Qt::ScrollBarAlwaysOn false Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 0 0 699 425 0 0 711 531 QLayout::SetMinAndMaxSize 15 Restart Restart i2pd Qt::Vertical 20 40 0 0 0 0 711 531 QLayout::SetMinAndMaxSize 15 Quit Quit Now Graceful Quit Qt::Vertical 20 40 handleQuitButton() handleGracefulQuitButton() i2pd-2.17.0/qt/i2pd_qt/pagewithbackbutton.cpp000066400000000000000000000013411321131324000210030ustar00rootroot00000000000000#include "pagewithbackbutton.h" #include "QVBoxLayout" #include "QHBoxLayout" #include "QPushButton" PageWithBackButton::PageWithBackButton(QWidget *parent, QWidget* child) : QWidget(parent) { QVBoxLayout * layout = new QVBoxLayout(); setLayout(layout); QWidget * topBar = new QWidget(); QHBoxLayout * topBarLayout = new QHBoxLayout(); topBar->setLayout(topBarLayout); layout->addWidget(topBar); layout->addWidget(child); QPushButton * backButton = new QPushButton(topBar); backButton->setText("< Back"); topBarLayout->addWidget(backButton); connect(backButton, SIGNAL(released()), this, SLOT(backReleasedSlot())); } void PageWithBackButton::backReleasedSlot() { emit backReleased(); } i2pd-2.17.0/qt/i2pd_qt/pagewithbackbutton.h000066400000000000000000000005071321131324000204530ustar00rootroot00000000000000#ifndef PAGEWITHBACKBUTTON_H #define PAGEWITHBACKBUTTON_H #include class PageWithBackButton : public QWidget { Q_OBJECT public: explicit PageWithBackButton(QWidget *parent, QWidget* child); signals: void backReleased(); private slots: void backReleasedSlot(); }; #endif // PAGEWITHBACKBUTTON_H i2pd-2.17.0/qt/i2pd_qt/routercommandswidget.ui000066400000000000000000000066071321131324000212310ustar00rootroot00000000000000 routerCommandsWidget 0 0 711 300 0 0 Form 0 0 711 301 QLayout::SetMaximumSize 0 0 75 true Router Commands 0 0 Run peer test 0 0 Decline transit tunnels 0 0 Accept transit tunnels false 0 0 Cancel graceful quit Qt::Vertical 20 40 i2pd-2.17.0/qt/i2pd_qt/statusbuttons.ui000066400000000000000000000077041321131324000177240ustar00rootroot00000000000000 StatusButtonsForm 0 0 171 295 0 0 171 295 Form 21 0 171 300 QLayout::SetDefaultConstraint 150 16777215 Main page 150 16777215 Router commands 150 16777215 Local destinations 150 16777215 Leasesets 150 16777215 Tunnels 150 16777215 Transit tunnels 150 16777215 Transports 150 16777215 I2P tunnels 150 16777215 SAM sessions i2pd-2.17.0/qt/i2pd_qt/textbrowsertweaked1.cpp000066400000000000000000000003211321131324000211310ustar00rootroot00000000000000#include "textbrowsertweaked1.h" TextBrowserTweaked1::TextBrowserTweaked1(QWidget * parent): QTextBrowser(parent) { } /*void TextBrowserTweaked1::setSource(const QUrl & url) { emit navigatedTo(url); }*/ i2pd-2.17.0/qt/i2pd_qt/textbrowsertweaked1.h000066400000000000000000000010151321131324000205770ustar00rootroot00000000000000#ifndef TEXTBROWSERTWEAKED1_H #define TEXTBROWSERTWEAKED1_H #include #include class TextBrowserTweaked1 : public QTextBrowser { Q_OBJECT public: TextBrowserTweaked1(QWidget * parent); //virtual void setSource(const QUrl & url); signals: void mouseReleased(); //void navigatedTo(const QUrl & link); protected: void mouseReleaseEvent(QMouseEvent *event) { QTextBrowser::mouseReleaseEvent(event); emit mouseReleased(); } }; #endif // TEXTBROWSERTWEAKED1_H i2pd-2.17.0/qt/i2pd_qt/tunnelform.ui000066400000000000000000000054531321131324000171520ustar00rootroot00000000000000 Form 0 0 527 452 Form 0 0 521 451 server_tunnel_name 0 20 511 421 Qt::Horizontal 40 20 Delete Host: Qt::Horizontal 40 20 i2pd-2.17.0/qt/i2pd_qt/widgetlock.cpp000066400000000000000000000000301321131324000172440ustar00rootroot00000000000000#include "widgetlock.h" i2pd-2.17.0/qt/i2pd_qt/widgetlock.h000066400000000000000000000017121321131324000167210ustar00rootroot00000000000000#ifndef WIDGETLOCK_H #define WIDGETLOCK_H #include #include #include #include class widgetlock : public QObject { Q_OBJECT private: QWidget* widget; QPushButton* lockButton; public slots: void lockButtonClicked(bool) { bool wasEnabled = widget->isEnabled(); widget->setEnabled(!wasEnabled); lockButton->setText(widget->isEnabled()?lockButton->tr("Lock"):lockButton->tr("Edit")); } public: widgetlock(QWidget* widget_, QPushButton* lockButton_): widget(widget_),lockButton(lockButton_) { widget->setEnabled(false); lockButton->setText(lockButton->tr("Edit")); QObject::connect(lockButton,SIGNAL(clicked(bool)), this, SLOT(lockButtonClicked(bool))); } virtual ~widgetlock() {} void deleteListener() { QObject::disconnect(lockButton,SIGNAL(clicked(bool)), this, SLOT(lockButtonClicked(bool))); } }; #endif // WIDGETLOCK_H i2pd-2.17.0/qt/i2pd_qt/widgetlockregistry.cpp000066400000000000000000000000411321131324000210370ustar00rootroot00000000000000#include "widgetlockregistry.h" i2pd-2.17.0/qt/i2pd_qt/widgetlockregistry.h000066400000000000000000000010701321131324000205070ustar00rootroot00000000000000#ifndef WIDGETLOCKREGISTRY_H #define WIDGETLOCKREGISTRY_H #include #include class widgetlockregistry { std::vector locks; public: widgetlockregistry() : locks() {} virtual ~widgetlockregistry() {} void add(widgetlock* lock) { locks.push_back(lock); } void deleteListeners() { while(!locks.empty()) { widgetlock* lock = locks.back(); lock->deleteListener(); delete lock; locks.pop_back(); } } }; #endif // WIDGETLOCKREGISTRY_H i2pd-2.17.0/tests/000077500000000000000000000000001321131324000135675ustar00rootroot00000000000000i2pd-2.17.0/tests/Makefile000066400000000000000000000015161321131324000152320ustar00rootroot00000000000000CXXFLAGS += -Wall -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 -I../libi2pd/ -pthread TESTS = test-gost test-gost-sig test-base-64 all: $(TESTS) run test-http-%: ../libi2pd/HTTP.cpp test-http-%.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ test-base-%: ../libi2pd/Base.cpp test-base-%.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ test-gost: ../libi2pd/Gost.cpp ../libi2pd/I2PEndian.cpp test-gost.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto test-gost-sig: ../libi2pd/Gost.cpp ../libi2pd/I2PEndian.cpp ../libi2pd/Signature.cpp ../libi2pd/Crypto.cpp ../libi2pd/Log.cpp test-gost-sig.cpp $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ -lcrypto -lssl -lboost_system run: $(TESTS) @for TEST in $(TESTS); do ./$$TEST ; done clean: rm -f $(TESTS) i2pd-2.17.0/tests/test-base-64.cpp000066400000000000000000000026671321131324000164240ustar00rootroot00000000000000#include #include #include "Base.h" using namespace i2p::data; int main() { const char *in = "test"; size_t in_len = strlen(in); char out[16]; /* bytes -> b64 */ assert(ByteStreamToBase64(NULL, 0, NULL, 0) == 0); assert(ByteStreamToBase64(NULL, 0, out, sizeof(out)) == 0); assert(Base64EncodingBufferSize(2) == 4); assert(Base64EncodingBufferSize(4) == 8); assert(Base64EncodingBufferSize(6) == 8); assert(Base64EncodingBufferSize(7) == 12); assert(Base64EncodingBufferSize(9) == 12); assert(Base64EncodingBufferSize(10) == 16); assert(Base64EncodingBufferSize(12) == 16); assert(Base64EncodingBufferSize(13) == 20); assert(ByteStreamToBase64((uint8_t *) in, in_len, out, sizeof(out)) == 8); assert(memcmp(out, "dGVzdA==", 8) == 0); /* b64 -> bytes */ assert(Base64ToByteStream(NULL, 0, NULL, 0) == 0); assert(Base64ToByteStream(NULL, 0, (uint8_t *) out, sizeof(out)) == 0); in = "dGVzdA=="; /* valid b64 */ assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 4); assert(memcmp(out, "test", 4) == 0); in = "dGVzdA="; /* invalid b64 : not padded */ assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 0); in = "dG/z.A=="; /* invalid b64 : char not from alphabet */ // assert(Base64ToByteStream(in, strlen(in), (uint8_t *) out, sizeof(out)) == 0); // ^^^ fails, current implementation not checks acceptable symbols return 0; } i2pd-2.17.0/tests/test-gost-sig.cpp000066400000000000000000000023751321131324000170130ustar00rootroot00000000000000#include #include #include #include "Gost.h" #include "Signature.h" const uint8_t example2[72] = { 0xfb,0xe2,0xe5,0xf0,0xee,0xe3,0xc8,0x20,0xfb,0xea,0xfa,0xeb,0xef,0x20,0xff,0xfb, 0xf0,0xe1,0xe0,0xf0,0xf5,0x20,0xe0,0xed,0x20,0xe8,0xec,0xe0,0xeb,0xe5,0xf0,0xf2, 0xf1,0x20,0xff,0xf0,0xee,0xec,0x20,0xf1,0x20,0xfa,0xf2,0xfe,0xe5,0xe2,0x20,0x2c, 0xe8,0xf6,0xf3,0xed,0xe2,0x20,0xe8,0xe6,0xee,0xe1,0xe8,0xf0,0xf2,0xd1,0x20,0x2c, 0xe8,0xf0,0xf2,0xe5,0xe2,0x20,0xe5,0xd1 }; int main () { uint8_t priv[64], pub[128], signature[128]; i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410TC26A512, priv, pub); i2p::crypto::GOSTR3410_512_Signer signer (i2p::crypto::eGOSTR3410TC26A512, priv); signer.Sign (example2, 72, signature); i2p::crypto::GOSTR3410_512_Verifier verifier (i2p::crypto::eGOSTR3410TC26A512, pub); assert (verifier.Verify (example2, 72, signature)); i2p::crypto::CreateGOSTR3410RandomKeys (i2p::crypto::eGOSTR3410CryptoProA, priv, pub); i2p::crypto::GOSTR3410_256_Signer signer1 (i2p::crypto::eGOSTR3410CryptoProA, priv); signer1.Sign (example2, 72, signature); i2p::crypto::GOSTR3410_256_Verifier verifier1 (i2p::crypto::eGOSTR3410CryptoProA, pub); assert (verifier1.Verify (example2, 72, signature)); } i2pd-2.17.0/tests/test-gost.cpp000066400000000000000000000046621321131324000162340ustar00rootroot00000000000000#include #include #include #include "Gost.h" const uint8_t example1[63] = { 0x32,0x31,0x30,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30,0x39,0x38,0x37, 0x36,0x35,0x34,0x33,0x32,0x31,0x30,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31, 0x30,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30,0x39,0x38,0x37,0x36,0x35, 0x34,0x33,0x32,0x31,0x30,0x39,0x38,0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30 }; const uint8_t example2[72] = { 0xfb,0xe2,0xe5,0xf0,0xee,0xe3,0xc8,0x20,0xfb,0xea,0xfa,0xeb,0xef,0x20,0xff,0xfb, 0xf0,0xe1,0xe0,0xf0,0xf5,0x20,0xe0,0xed,0x20,0xe8,0xec,0xe0,0xeb,0xe5,0xf0,0xf2, 0xf1,0x20,0xff,0xf0,0xee,0xec,0x20,0xf1,0x20,0xfa,0xf2,0xfe,0xe5,0xe2,0x20,0x2c, 0xe8,0xf6,0xf3,0xed,0xe2,0x20,0xe8,0xe6,0xee,0xe1,0xe8,0xf0,0xf2,0xd1,0x20,0x2c, 0xe8,0xf0,0xf2,0xe5,0xe2,0x20,0xe5,0xd1 }; const uint8_t example1_hash_512[64] = { 0x48,0x6f,0x64,0xc1,0x91,0x78,0x79,0x41,0x7f,0xef,0x08,0x2b,0x33,0x81,0xa4,0xe2, 0x11,0xc3,0x24,0xf0,0x74,0x65,0x4c,0x38,0x82,0x3a,0x7b,0x76,0xf8,0x30,0xad,0x00, 0xfa,0x1f,0xba,0xe4,0x2b,0x12,0x85,0xc0,0x35,0x2f,0x22,0x75,0x24,0xbc,0x9a,0xb1, 0x62,0x54,0x28,0x8d,0xd6,0x86,0x3d,0xcc,0xd5,0xb9,0xf5,0x4a,0x1a,0xd0,0x54,0x1b }; const uint8_t example1_hash_256[32] = { 0x00,0x55,0x7b,0xe5,0xe5,0x84,0xfd,0x52,0xa4,0x49,0xb1,0x6b,0x02,0x51,0xd0,0x5d, 0x27,0xf9,0x4a,0xb7,0x6c,0xba,0xa6,0xda,0x89,0x0b,0x59,0xd8,0xef,0x1e,0x15,0x9d }; const uint8_t example2_hash_512[64] = { 0x28,0xfb,0xc9,0xba,0xda,0x03,0x3b,0x14,0x60,0x64,0x2b,0xdc,0xdd,0xb9,0x0c,0x3f, 0xb3,0xe5,0x6c,0x49,0x7c,0xcd,0x0f,0x62,0xb8,0xa2,0xad,0x49,0x35,0xe8,0x5f,0x03, 0x76,0x13,0x96,0x6d,0xe4,0xee,0x00,0x53,0x1a,0xe6,0x0f,0x3b,0x5a,0x47,0xf8,0xda, 0xe0,0x69,0x15,0xd5,0xf2,0xf1,0x94,0x99,0x6f,0xca,0xbf,0x26,0x22,0xe6,0x88,0x1e }; const uint8_t example2_hash_256[32] = { 0x50,0x8f,0x7e,0x55,0x3c,0x06,0x50,0x1d,0x74,0x9a,0x66,0xfc,0x28,0xc6,0xca,0xc0, 0xb0,0x05,0x74,0x6d,0x97,0x53,0x7f,0xa8,0x5d,0x9e,0x40,0x90,0x4e,0xfe,0xd2,0x9d }; int main () { uint8_t digest[64]; i2p::crypto::GOSTR3411_2012_512 (example1, 63, digest); assert(memcmp (digest, example1_hash_512, 64) == 0); i2p::crypto::GOSTR3411_2012_256 (example1, 63, digest); assert(memcmp (digest, example1_hash_256, 32) == 0); i2p::crypto::GOSTR3411_2012_512 (example2, 72, digest); assert(memcmp (digest, example2_hash_512, 64) == 0); i2p::crypto::GOSTR3411_2012_256 (example2, 72, digest); assert(memcmp (digest, example2_hash_256, 32) == 0); } i2pd-2.17.0/tests/test-http-merge_chunked.cpp000066400000000000000000000006351321131324000210310ustar00rootroot00000000000000#include #include "../HTTP.h" using namespace i2p::http; int main() { const char *buf = "4\r\n" "HTTP\r\n" "A\r\n" " response \r\n" "E\r\n" "with \r\n" "chunks.\r\n" "0\r\n" "\r\n" ; std::stringstream in(buf); std::stringstream out; assert(MergeChunkedResponse(in, out) == true); assert(out.str() == "HTTP response with \r\nchunks."); return 0; } i2pd-2.17.0/tests/test-http-req.cpp000066400000000000000000000044611321131324000170210ustar00rootroot00000000000000#include #include "../HTTP.h" using namespace i2p::http; int main() { HTTPReq *req; int ret = 0, len = 0; const char *buf; /* test: parsing request with body */ buf = "GET / HTTP/1.0\r\n" "User-Agent: curl/7.26.0\r\n" "Host: inr.i2p\r\n" "Accept: */*\r\n" "\r\n" "test"; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) == len - 4); assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); assert(req->headers.size() == 3); assert(req->headers.count("Host") == 1); assert(req->headers.count("Accept") == 1); assert(req->headers.count("User-Agent") == 1); assert(req->headers.find("Host")->second == "inr.i2p"); assert(req->headers.find("Accept")->second == "*/*"); assert(req->headers.find("User-Agent")->second == "curl/7.26.0"); delete req; /* test: parsing request without body */ buf = "GET / HTTP/1.0\r\n" "\r\n"; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) == len); assert(req->version == "HTTP/1.0"); assert(req->method == "GET"); assert(req->uri == "/"); assert(req->headers.size() == 0); delete req; /* test: parsing request without body */ buf = "GET / HTTP/1.1\r\n" "\r\n"; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) > 0); delete req; /* test: parsing incomplete request */ buf = "GET / HTTP/1.0\r\n" ""; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) == 0); /* request not completed */ delete req; /* test: parsing slightly malformed request */ buf = "GET http://inr.i2p HTTP/1.1\r\n" "Host: stats.i2p\r\n" "Accept-Encoding: \r\n" "Accept: */*\r\n" "\r\n"; len = strlen(buf); req = new HTTPReq; assert((ret = req->parse(buf, len)) == len); /* no host header */ assert(req->method == "GET"); assert(req->uri == "http://inr.i2p"); assert(req->headers.size() == 3); assert(req->headers.count("Host") == 1); assert(req->headers.count("Accept") == 1); assert(req->headers.count("Accept-Encoding") == 1); assert(req->headers["Host"] == "stats.i2p"); assert(req->headers["Accept"] == "*/*"); assert(req->headers["Accept-Encoding"] == ""); delete req; return 0; } /* vim: expandtab:ts=2 */ i2pd-2.17.0/tests/test-http-res.cpp000066400000000000000000000025231321131324000170200ustar00rootroot00000000000000#include #include "../HTTP.h" using namespace i2p::http; int main() { HTTPRes *res; int ret = 0, len = 0; const char *buf; /* test: parsing valid response without body */ buf = "HTTP/1.1 304 Not Modified\r\n" "Date: Thu, 14 Apr 2016 00:00:00 GMT\r\n" "Server: nginx/1.2.1\r\n" "Content-Length: 536\r\n" "\r\n"; len = strlen(buf); res = new HTTPRes; assert((ret = res->parse(buf, len)) == len); assert(res->version == "HTTP/1.1"); assert(res->status == "Not Modified"); assert(res->code == 304); assert(res->headers.size() == 3); assert(res->headers.count("Date") == 1); assert(res->headers.count("Server") == 1); assert(res->headers.count("Content-Length") == 1); assert(res->headers.find("Date")->second == "Thu, 14 Apr 2016 00:00:00 GMT"); assert(res->headers.find("Server")->second == "nginx/1.2.1"); assert(res->headers.find("Content-Length")->second == "536"); assert(res->is_chunked() == false); assert(res->content_length() == 536); delete res; /* test: building request */ buf = "HTTP/1.0 304 Not Modified\r\n" "Content-Length: 0\r\n" "\r\n"; res = new HTTPRes; res->version = "HTTP/1.0"; res->code = 304; res->status = "Not Modified"; res->add_header("Content-Length", "0"); assert(res->to_string() == buf); return 0; } /* vim: expandtab:ts=2 */ i2pd-2.17.0/tests/test-http-url.cpp000066400000000000000000000070321321131324000170310ustar00rootroot00000000000000#include #include "../HTTP.h" using namespace i2p::http; int main() { std::map params; URL *url; url = new URL; assert(url->parse("https://127.0.0.1:7070/asdasd?12345") == true); assert(url->schema == "https"); assert(url->user == ""); assert(url->pass == ""); assert(url->host == "127.0.0.1"); assert(url->port == 7070); assert(url->path == "/asdasd"); assert(url->query == "12345"); assert(url->to_string() == "https://127.0.0.1:7070/asdasd?12345"); delete url; url = new URL; assert(url->parse("http://user:password@site.com:8080/asdasd?123456") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == "password"); assert(url->host == "site.com"); assert(url->port == 8080); assert(url->path == "/asdasd"); assert(url->query == "123456"); delete url; url = new URL; assert(url->parse("http://user:password@site.com/asdasd?name=value") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == "password"); assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); assert(url->query == "name=value"); delete url; url = new URL; assert(url->parse("http://user:@site.com/asdasd?name=value1&name=value2") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == ""); assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); assert(url->query == "name=value1&name=value2"); delete url; url = new URL; assert(url->parse("http://user@site.com/asdasd?name1=value1&name2&name3=value2") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == ""); assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == "/asdasd"); assert(url->query == "name1=value1&name2&name3=value2"); assert(url->parse_query(params)); assert(params.size() == 3); assert(params.count("name1") == 1); assert(params.count("name2") == 1); assert(params.count("name3") == 1); assert(params.find("name1")->second == "value1"); assert(params.find("name2")->second == ""); assert(params.find("name3")->second == "value2"); delete url; url = new URL; assert(url->parse("http://@site.com:800/asdasd?") == true); assert(url->schema == "http"); assert(url->user == ""); assert(url->pass == ""); assert(url->host == "site.com"); assert(url->port == 800); assert(url->path == "/asdasd"); assert(url->query == ""); delete url; url = new URL; assert(url->parse("http://@site.com:17") == true); assert(url->schema == "http"); assert(url->user == ""); assert(url->pass == ""); assert(url->host == "site.com"); assert(url->port == 17); assert(url->path == ""); assert(url->query == ""); delete url; url = new URL; assert(url->parse("http://user:password@site.com:err_port/asdasd") == false); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == "password"); assert(url->host == "site.com"); assert(url->port == 0); assert(url->path == ""); assert(url->query == ""); delete url; url = new URL; assert(url->parse("http://user:password@site.com:84/asdasd/@17#frag") == true); assert(url->schema == "http"); assert(url->user == "user"); assert(url->pass == "password"); assert(url->host == "site.com"); assert(url->port == 84); assert(url->path == "/asdasd/@17"); assert(url->query == ""); assert(url->frag == "frag"); delete url; return 0; } /* vim: expandtab:ts=2 */ i2pd-2.17.0/tests/test-http-url_decode.cpp000066400000000000000000000006541321131324000203370ustar00rootroot00000000000000#include #include "../HTTP.h" using namespace i2p::http; int main() { std::string in("/%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0/"); std::string out = UrlDecode(in); assert(strcmp(out.c_str(), "/страница/") == 0); in = "/%00/"; out = UrlDecode(in, false); assert(strcmp(out.c_str(), "/%00/") == 0); out = UrlDecode(in, true); assert(strcmp(out.c_str(), "/\0/") == 0); return 0; }