From 6c511c7a79b12937bc868b794d1b99d47815b41a Mon Sep 17 00:00:00 2001 From: maksis Date: Fri, 12 May 2017 00:28:54 +0300 Subject: [PATCH 1/5] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d295613e..9109e7b1 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ AirDC++ Web Client is a locally installed application, which is designed for frequent sharing of files or directories within groups of people in a local network or over internet. The daemon application can be installed on different types of systems, such as on file servers and NAS devices. +The application uses [Advanced Direct Connect](https://en.wikipedia.org/wiki/Advanced_Direct_Connect) protocol, which allows creating file sharing communities with thousands of users. The application itself is highly optimized even for extreme use cases: a single client can be used to share more than 10 million files or hundreds of terabytes of data. + ### Key functionality - Responsive [web user interface](https://github.com/airdcpp-web/airdcpp-webui) written in [React.js](https://facebook.github.io/react/) From af9de218e5c4c85dd9133f93b8f63a259766f50e Mon Sep 17 00:00:00 2001 From: maksis Date: Fri, 12 May 2017 00:33:43 +0300 Subject: [PATCH 2/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9109e7b1..0d4e3c67 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The application uses [Advanced Direct Connect](https://en.wikipedia.org/wiki/Adv - Save files on disk or view them via the browser - Chatting capabilities (group and private chat) - Browse directories shared by other users with a simple file browser interface -- [Extension support](https://github.com/airdcpp-web/airdcpp-extension-js) +- [Extension support](https://github.com/airdcpp-web/airdcpp-extension-js) using [Node.js](https://nodejs.org/en/) (+others) - [Web API (HTTP REST and WebSockets)](http://apidocs.airdcpp.net) ## Download From bc93162f547cc656cea9d816daac768b96a21816 Mon Sep 17 00:00:00 2001 From: maksis Date: Fri, 12 May 2017 00:34:35 +0300 Subject: [PATCH 3/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d4e3c67..664283d6 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The application uses [Advanced Direct Connect](https://en.wikipedia.org/wiki/Adv - Save files on disk or view them via the browser - Chatting capabilities (group and private chat) - Browse directories shared by other users with a simple file browser interface -- [Extension support](https://github.com/airdcpp-web/airdcpp-extension-js) using [Node.js](https://nodejs.org/en/) (+others) +- [Extension support](https://github.com/airdcpp-web/airdcpp-extensions) using [Node.js](https://nodejs.org/en/) (+others) - [Web API (HTTP REST and WebSockets)](http://apidocs.airdcpp.net) ## Download From 8760b6b2eecc25f6106af05714202ef43ff10f37 Mon Sep 17 00:00:00 2001 From: maksis Date: Tue, 10 Oct 2017 11:20:28 +0300 Subject: [PATCH 4/5] Merge commit 'ebc92bb5b8090408e07940ae488d056117f9d783' into develop --- airdcpp-core/airdcpp/AdcCommand.cpp | 30 ++-- airdcpp-core/airdcpp/AdcCommand.h | 88 +++++----- airdcpp-core/airdcpp/UDPServer.cpp | 162 ++++++++++-------- airdcpp-core/airdcpp/UDPServer.h | 21 ++- .../airdcpp/modules/ShareMonitorManager.cpp | 5 +- 5 files changed, 183 insertions(+), 123 deletions(-) diff --git a/airdcpp-core/airdcpp/AdcCommand.cpp b/airdcpp-core/airdcpp/AdcCommand.cpp index b640fc5e..811e2f2f 100644 --- a/airdcpp-core/airdcpp/AdcCommand.cpp +++ b/airdcpp-core/airdcpp/AdcCommand.cpp @@ -23,9 +23,9 @@ namespace dcpp { -AdcCommand::AdcCommand(uint32_t aCmd, char aType /* = TYPE_CLIENT */) : cmdInt(aCmd), from(0), type(aType) { } -AdcCommand::AdcCommand(uint32_t aCmd, const uint32_t aTarget, char aType) : cmdInt(aCmd), from(0), to(aTarget), type(aType) { } -AdcCommand::AdcCommand(Severity sev, Error err, const string& desc, char aType /* = TYPE_CLIENT */) : cmdInt(CMD_STA), from(0), type(aType) { +AdcCommand::AdcCommand(uint32_t aCmd, char aType /* = TYPE_CLIENT */) noexcept : cmdInt(aCmd), from(0), type(aType) { } +AdcCommand::AdcCommand(uint32_t aCmd, const uint32_t aTarget, char aType) noexcept : cmdInt(aCmd), from(0), to(aTarget), type(aType) { } +AdcCommand::AdcCommand(Severity sev, Error err, const string& desc, char aType /* = TYPE_CLIENT */) noexcept : cmdInt(CMD_STA), from(0), type(aType) { addParam((sev == SEV_SUCCESS && err == SUCCESS) ? "000" : Util::toString(sev * 100 + err)); addParam(desc); } @@ -159,19 +159,19 @@ void AdcCommand::parse(const string& aLine, bool nmdc /* = false */) { } } -string AdcCommand::toString(const CID& aCID) const { +string AdcCommand::toString(const CID& aCID) const noexcept { return getHeaderString(aCID) + getParamString(false); } -string AdcCommand::toString() const { +string AdcCommand::toString() const noexcept { return getHeaderString() + getParamString(false); } -string AdcCommand::toString(uint32_t sid /* = 0 */, bool nmdc /* = false */) const { +string AdcCommand::toString(uint32_t sid /* = 0 */, bool nmdc /* = false */) const noexcept { return getHeaderString(sid, nmdc) + getParamString(nmdc); } -string AdcCommand::escape(const string& str, bool old) { +string AdcCommand::escape(const string& str, bool old) noexcept { string tmp = str; string::size_type i = 0; while( (i = tmp.find_first_of(" \n\\", i)) != string::npos) { @@ -189,7 +189,7 @@ string AdcCommand::escape(const string& str, bool old) { return tmp; } -string AdcCommand::getHeaderString(uint32_t sid, bool nmdc) const { +string AdcCommand::getHeaderString(uint32_t sid, bool nmdc) const noexcept { string tmp; if(nmdc) { tmp += "$ADC"; @@ -216,7 +216,7 @@ string AdcCommand::getHeaderString(uint32_t sid, bool nmdc) const { return tmp; } -string AdcCommand::getHeaderString(const CID& cid) const { +string AdcCommand::getHeaderString(const CID& cid) const noexcept { dcassert(type == TYPE_UDP); string tmp; @@ -227,7 +227,7 @@ string AdcCommand::getHeaderString(const CID& cid) const { return tmp; } -string AdcCommand::getHeaderString() const { +string AdcCommand::getHeaderString() const noexcept { dcassert(type == TYPE_UDP); string tmp; @@ -236,11 +236,11 @@ string AdcCommand::getHeaderString() const { return tmp; } -const string& AdcCommand::getParam(size_t n) const { +const string& AdcCommand::getParam(size_t n) const noexcept { return getParameters().size() > n ? getParameters()[n] : Util::emptyString; } -string AdcCommand::getParamString(bool nmdc) const { +string AdcCommand::getParamString(bool nmdc) const noexcept { string tmp; for(const auto& i: getParameters()) { tmp += ' '; @@ -254,7 +254,7 @@ string AdcCommand::getParamString(bool nmdc) const { return tmp; } -bool AdcCommand::getParam(const char* name, size_t start, string& ret) const { +bool AdcCommand::getParam(const char* name, size_t start, string& ret) const noexcept { for(string::size_type i = start; i < getParameters().size(); ++i) { if(toCode(name) == toCode(getParameters()[i].c_str())) { ret = getParameters()[i].substr(2); @@ -264,7 +264,7 @@ bool AdcCommand::getParam(const char* name, size_t start, string& ret) const { return false; } -bool AdcCommand::getParam(const char* name, size_t start, StringList& ret) const { +bool AdcCommand::getParam(const char* name, size_t start, StringList& ret) const noexcept { for(string::size_type i = start; i < getParameters().size(); ++i) { if(toCode(name) == toCode(getParameters()[i].c_str())) { ret.push_back(getParameters()[i].substr(2)); @@ -273,7 +273,7 @@ bool AdcCommand::getParam(const char* name, size_t start, StringList& ret) const return !ret.empty(); } -bool AdcCommand::hasFlag(const char* name, size_t start) const { +bool AdcCommand::hasFlag(const char* name, size_t start) const noexcept { for(string::size_type i = start; i < getParameters().size(); ++i) { if(toCode(name) == toCode(getParameters()[i].c_str()) && getParameters()[i].size() == 3 && diff --git a/airdcpp-core/airdcpp/AdcCommand.h b/airdcpp-core/airdcpp/AdcCommand.h index 1fd57374..c0ae1df7 100644 --- a/airdcpp-core/airdcpp/AdcCommand.h +++ b/airdcpp-core/airdcpp/AdcCommand.h @@ -118,61 +118,65 @@ class AdcCommand { static const uint32_t HUB_SID = 0xffffffff; // No client will have this sid - static uint32_t toFourCC(const char* x) { return *reinterpret_cast(x); } - static std::string fromFourCC(uint32_t x) { return std::string(reinterpret_cast(&x), sizeof(x)); } + static uint32_t toFourCC(const char* x) noexcept { return *reinterpret_cast(x); } + static std::string fromFourCC(uint32_t x) noexcept { return std::string(reinterpret_cast(&x), sizeof(x)); } - explicit AdcCommand(uint32_t aCmd, char aType = TYPE_CLIENT); - explicit AdcCommand(uint32_t aCmd, const uint32_t aTarget, char aType); - explicit AdcCommand(Severity sev, Error err, const string& desc, char aType = TYPE_CLIENT); + explicit AdcCommand(uint32_t aCmd, char aType = TYPE_CLIENT) noexcept; + explicit AdcCommand(uint32_t aCmd, const uint32_t aTarget, char aType) noexcept; + explicit AdcCommand(Severity sev, Error err, const string& desc, char aType = TYPE_CLIENT) noexcept; + + // Throws ParseException on errors explicit AdcCommand(const string& aLine, bool nmdc = false); + + // Throws ParseException on errors void parse(const string& aLine, bool nmdc = false); - uint32_t getCommand() const { return cmdInt; } - char getType() const { return type; } - void setType(char t) { type = t; } - string getFourCC() const { string tmp(4, 0); tmp[0] = type; tmp[1] = cmd[0]; tmp[2] = cmd[1]; tmp[3] = cmd[2]; return tmp; } + uint32_t getCommand() const noexcept { return cmdInt; } + char getType() const noexcept { return type; } + void setType(char t) noexcept { type = t; } + string getFourCC() const noexcept { string tmp(4, 0); tmp[0] = type; tmp[1] = cmd[0]; tmp[2] = cmd[1]; tmp[3] = cmd[2]; return tmp; } - const string& getFeatures() const { return features; } - AdcCommand& setFeatures(const string& feat) { features = feat; return *this; } + const string& getFeatures() const noexcept { return features; } + AdcCommand& setFeatures(const string& feat) noexcept { features = feat; return *this; } - StringList& getParameters() { return parameters; } - const StringList& getParameters() const { return parameters; } + StringList& getParameters() noexcept { return parameters; } + const StringList& getParameters() const noexcept { return parameters; } - string toString() const; - string toString(const CID& aCID) const; - string toString(uint32_t sid, bool nmdc = false) const; + string toString() const noexcept; + string toString(const CID& aCID) const noexcept; + string toString(uint32_t sid, bool nmdc = false) const noexcept; - AdcCommand& addParam(const string& name, const string& value) { + AdcCommand& addParam(const string& name, const string& value) noexcept { parameters.push_back(name); parameters.back() += value; return *this; } - AdcCommand& addParam(const string& str) { + AdcCommand& addParam(const string& str) noexcept { parameters.push_back(str); return *this; } - const string& getParam(size_t n) const; + const string& getParam(size_t n) const noexcept; /** Return a named parameter where the name is a two-letter code */ - bool getParam(const char* name, size_t start, string& ret) const; - bool getParam(const char* name, size_t start, StringList& ret) const; - bool hasFlag(const char* name, size_t start) const; - static uint16_t toCode(const char* x) { return *((uint16_t*)x); } + bool getParam(const char* name, size_t start, string& ret) const noexcept; + bool getParam(const char* name, size_t start, StringList& ret) const noexcept; + bool hasFlag(const char* name, size_t start) const noexcept; + static uint16_t toCode(const char* x) noexcept { return *((uint16_t*)x); } - bool operator==(uint32_t aCmd) { return cmdInt == aCmd; } + bool operator==(uint32_t aCmd) const noexcept { return cmdInt == aCmd; } - static string escape(const string& str, bool old); - uint32_t getTo() const { return to; } - AdcCommand& setTo(const uint32_t sid) { to = sid; return *this; } - uint32_t getFrom() const { return from; } - void setFrom(const uint32_t sid) { from = sid; } + static string escape(const string& str, bool old) noexcept; + uint32_t getTo() const noexcept { return to; } + AdcCommand& setTo(const uint32_t sid) noexcept { to = sid; return *this; } + uint32_t getFrom() const noexcept { return from; } + void setFrom(const uint32_t sid) noexcept { from = sid; } - static uint32_t toSID(const string& aSID) { return *reinterpret_cast(aSID.data()); } - static string fromSID(const uint32_t aSID) { return string(reinterpret_cast(&aSID), sizeof(aSID)); } + static uint32_t toSID(const string& aSID) noexcept { return *reinterpret_cast(aSID.data()); } + static string fromSID(const uint32_t aSID) noexcept { return string(reinterpret_cast(&aSID), sizeof(aSID)); } private: - string getHeaderString(const CID& cid) const; - string getHeaderString() const; - string getHeaderString(uint32_t sid, bool nmdc) const; - string getParamString(bool nmdc) const; + string getHeaderString(const CID& cid) const noexcept; + string getHeaderString() const noexcept; + string getHeaderString(uint32_t sid, bool nmdc) const noexcept; + string getParamString(bool nmdc) const noexcept; StringList parameters; string features; union { @@ -189,11 +193,16 @@ class AdcCommand { template class CommandHandler { public: - void dispatch(const string& aLine, bool nmdc = false) { + inline void dispatch(const string& aLine) noexcept { + dispatch(aLine, false); + } + + template + void dispatch(const string& aLine, bool aNmdc, ArgT&&... args) noexcept { try { - AdcCommand c(aLine, nmdc); + AdcCommand c(aLine, aNmdc); -#define C(n) case AdcCommand::CMD_##n: ((T*)this)->handle(AdcCommand::n(), c); break; +#define C(n) case AdcCommand::CMD_##n: ((T*)this)->handle(AdcCommand::n(), c, std::forward(args)...); break; switch(c.getCommand()) { C(SUP); C(STA); @@ -215,11 +224,12 @@ class CommandHandler { C(RNT); C(PSR); C(PBD); - C(UBD); C(ZON); C(ZOF); C(TCP); C(PMI); + C(UBN); + C(UBD); default: dcdebug("Unknown ADC command: %.50s\n", aLine.c_str()); break; diff --git a/airdcpp-core/airdcpp/UDPServer.cpp b/airdcpp-core/airdcpp/UDPServer.cpp index 11bfc4f2..6b229917 100644 --- a/airdcpp-core/airdcpp/UDPServer.cpp +++ b/airdcpp-core/airdcpp/UDPServer.cpp @@ -127,76 +127,104 @@ void UDPServer::handlePacket(const ByteVector& aBuf, size_t aLen, const string& COMMAND_DEBUG(x, DebugManager::TYPE_CLIENT_UDP, DebugManager::INCOMING, aRemoteIp); - if(x.compare(0, 4, "$SR ") == 0) { - SearchManager::getInstance()->onSR(x, aRemoteIp); - } else if(x.compare(1, 4, "RES ") == 0 && x[x.length() - 1] == 0x0a) { - AdcCommand c(x.substr(0, x.length()-1)); - if(c.getParameters().empty()) - return; - string cid = c.getParam(0); - if(cid.size() != 39) - return; - - UserPtr user = ClientManager::getInstance()->findUser(CID(cid)); - if(!user) - return; - - // Remove the CID - // This should be handled by AdcCommand really... - c.getParameters().erase(c.getParameters().begin()); - - SearchManager::getInstance()->onRES(c, user, aRemoteIp); - } else if (x.compare(1, 4, "PSR ") == 0 && x[x.length() - 1] == 0x0a) { - AdcCommand c(x.substr(0, x.length()-1)); - if(c.getParameters().empty()) - return; - string cid = c.getParam(0); - if(cid.size() != 39) - return; - - UserPtr user = ClientManager::getInstance()->findUser(CID(cid)); - // when user == NULL then it is probably NMDC user, check it later - - // Remove the CID - c.getParameters().erase(c.getParameters().begin()); - - SearchManager::getInstance()->onPSR(c, user, aRemoteIp); - - } else if (x.compare(1, 4, "PBD ") == 0 && x[x.length() - 1] == 0x0a) { - if (!SETTING(USE_PARTIAL_SHARING)) { - return; - } - //LogManager::getInstance()->message("GOT PBD UDP: " + x); - AdcCommand c(x.substr(0, x.length()-1)); - if(c.getParameters().empty()) - return; - string cid = c.getParam(0); - if(cid.size() != 39) - return; - - UserPtr user = ClientManager::getInstance()->findUser(CID(cid)); - - // Remove the CID - c.getParameters().erase(c.getParameters().begin()); - - if (user) - SearchManager::getInstance()->onPBD(c, user); - - } else if ((x.compare(1, 4, "UBD ") == 0 || x.compare(1, 4, "UBN ") == 0) && x[x.length() - 1] == 0x0a) { - AdcCommand c(x.substr(0, x.length()-1)); - if(c.getParameters().empty()) - return; - - // No CID in UBD/UBN commands - - if (x.compare(1, 4, "UBN ") == 0) { - //LogManager::getInstance()->message("GOT UBN UDP: " + x); - UploadManager::getInstance()->onUBN(c); + if (x.compare(0, 1, "$") == 0) { + // NMDC commands + if (x.compare(1, 3, "SR ") == 0) { + SearchManager::getInstance()->onSR(x, aRemoteIp); } else { - //LogManager::getInstance()->message("GOT UBD UDP: " + x); - UploadManager::getInstance()->onUBD(c); + dcdebug("Unknown NMDC command received via UDP: %s\n", x.c_str()); } + + return; } + + // ADC commands + + // ADC commands must end with \n + if (x[x.length() - 1] != 0x0a) { + dcdebug("Invalid UDP data received: %s (no newline)\n", x.c_str()); + return; + } + + if (!Text::validateUtf8(x)) { + dcdebug("UTF-8 valition failed for received UDP data: %s\n", x.c_str()); + return; + } + + // Dispatch without newline + dispatch(x.substr(0, x.length() - 1), false, aRemoteIp); +} + +void UDPServer::handle(AdcCommand::RES, AdcCommand& c, const string& aRemoteIp) noexcept { + if (c.getParameters().empty()) + return; + + string cid = c.getParam(0); + if (cid.size() != 39) + return; + + UserPtr user = ClientManager::getInstance()->findUser(CID(cid)); + if (!user) + return; + + // Remove the CID + // This should be handled by AdcCommand really... + c.getParameters().erase(c.getParameters().begin()); + + SearchManager::getInstance()->onRES(c, user, aRemoteIp); +} + +void UDPServer::handle(AdcCommand::PSR, AdcCommand& c, const string& aRemoteIp) noexcept { + if (c.getParameters().empty()) + return; + + const auto cid = c.getParam(0); + if (cid.size() != 39) + return; + + UserPtr user = ClientManager::getInstance()->findUser(CID(cid)); + // when user == NULL then it is probably NMDC user, check it later + + // Remove the CID + c.getParameters().erase(c.getParameters().begin()); + + SearchManager::getInstance()->onPSR(c, user, aRemoteIp); +} + +void UDPServer::handle(AdcCommand::PBD, AdcCommand& c, const string&) noexcept { + if (!SETTING(USE_PARTIAL_SHARING)) { + return; + } + + //LogManager::getInstance()->message("GOT PBD UDP: " + x); + if (c.getParameters().empty()) + return; + + const auto cid = c.getParam(0); + if (cid.size() != 39) + return; + + const auto user = ClientManager::getInstance()->findUser(CID(cid)); + + // Remove the CID + c.getParameters().erase(c.getParameters().begin()); + + if (user) + SearchManager::getInstance()->onPBD(c, user); +} + +void UDPServer::handle(AdcCommand::UBD, AdcCommand& c, const string&) noexcept { + if (c.getParameters().empty()) + return; + + UploadManager::getInstance()->onUBD(c); +} + +void UDPServer::handle(AdcCommand::UBN, AdcCommand& c, const string&) noexcept { + if (c.getParameters().empty()) + return; + + UploadManager::getInstance()->onUBN(c); } } \ No newline at end of file diff --git a/airdcpp-core/airdcpp/UDPServer.h b/airdcpp-core/airdcpp/UDPServer.h index 1cd19515..70622b86 100644 --- a/airdcpp-core/airdcpp/UDPServer.h +++ b/airdcpp-core/airdcpp/UDPServer.h @@ -19,12 +19,13 @@ #ifndef DCPLUSPLUS_DCPP_UDP_SERVER_H #define DCPLUSPLUS_DCPP_UDP_SERVER_H +#include "AdcCommand.h" #include "DispatcherQueue.h" #include "Socket.h" namespace dcpp { -class UDPServer : public Thread { +class UDPServer : public Thread, public CommandHandler { public: UDPServer(); virtual ~UDPServer(); @@ -32,7 +33,11 @@ class UDPServer : public Thread { const string& getPort() const { return port; } void disconnect(); void listen(); + + private: + friend class CommandHandler; + virtual int run(); std::unique_ptr socket; @@ -41,6 +46,20 @@ class UDPServer : public Thread { DispatcherQueue pp; void handlePacket(const ByteVector& aBuf, size_t aLen, const string& aRemoteIp); + + // Search results + void handle(AdcCommand::RES, AdcCommand& c, const string& aRemoteIp) noexcept; + + // Partial sharing + void handle(AdcCommand::PSR, AdcCommand& c, const string& aRemoteIp) noexcept; + void handle(AdcCommand::PBD, AdcCommand& c, const string& aRemoteIp) noexcept; + + // Upload bundles + void handle(AdcCommand::UBD, AdcCommand& c, const string& aRemoteIp) noexcept; + void handle(AdcCommand::UBN, AdcCommand& c, const string& aRemoteIp) noexcept; + + // Ignore any other ADC commands for now + template void handle(T, AdcCommand&, const string&) { } }; } diff --git a/airdcpp-core/airdcpp/modules/ShareMonitorManager.cpp b/airdcpp-core/airdcpp/modules/ShareMonitorManager.cpp index eb1a487d..bbc3477b 100644 --- a/airdcpp-core/airdcpp/modules/ShareMonitorManager.cpp +++ b/airdcpp-core/airdcpp/modules/ShareMonitorManager.cpp @@ -69,7 +69,10 @@ namespace dcpp { } void ShareMonitorManager::on(ShareManagerListener::RootCreated, const string& aPath) noexcept { - addMonitoring({ aPath }); + auto rootInfo = ShareManager::getInstance()->getRootInfo(aPath); + if (useMonitoring(rootInfo)) { + addMonitoring({ aPath }); + } } void ShareMonitorManager::on(ShareManagerListener::RootRemoved, const string& aPath) noexcept { From b58859d032ef85b036c773a492cee249c05be346 Mon Sep 17 00:00:00 2001 From: maksis Date: Fri, 13 Oct 2017 18:45:10 +0300 Subject: [PATCH 5/5] 2.2.1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9601ee18..88434135 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,8 @@ endif (APPLE) set (PROJECT_NAME "AirDC++ Web Client") set (TAG_APPLICATION "AirDC++w") -set (VERSION "2.2.0") -set (SOVERSION "2.2.0" ) +set (VERSION "2.2.1") # NOTE: the minor version must match the lastest UI version +set (SOVERSION "2.2.1" ) set (GNUCXX_MINIMUM_VERSION "4.8")