From 7a7b52efde8e737d1af269bebc9fae4bd5e5b0fc Mon Sep 17 00:00:00 2001
From: Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com>
Date: Wed, 8 Apr 2015 17:44:06 -0400
Subject: [PATCH] ContactMethod: Generate a protocol hint

Refs #70363
---
 src/account.cpp       |   5 ++
 src/accountmodel.cpp  |   2 +-
 src/accountmodel.h    |   4 +-
 src/contactmethod.cpp |   8 ++
 src/contactmethod.h   |  42 ++++-----
 src/person.cpp        |  38 ++++++++
 src/person.h          |   1 +
 src/uri.cpp           | 204 +++++++++++++++++++++++++++++++++++++-----
 src/uri.h             |  57 +++++++-----
 9 files changed, 298 insertions(+), 63 deletions(-)

diff --git a/src/account.cpp b/src/account.cpp
index 41b46039..ed4675f9 100644
--- a/src/account.cpp
+++ b/src/account.cpp
@@ -939,16 +939,21 @@ bool Account::supportScheme( URI::SchemeType type )
    switch(type) {
       case URI::SchemeType::NONE :
          return true;
+         break;
       case URI::SchemeType::SIP  :
       case URI::SchemeType::SIPS :
          if (protocol() == Account::Protocol::SIP)
             return true;
+         break;
       case URI::SchemeType::IAX  :
+      case URI::SchemeType::IAX2 :
          if (protocol() == Account::Protocol::IAX)
             return true;
+         break;
       case URI::SchemeType::RING :
          if (protocol() == Account::Protocol::RING)
             return true;
+         break;
    }
    return false;
 }
diff --git a/src/accountmodel.cpp b/src/accountmodel.cpp
index 220e440b..d9ec60d1 100644
--- a/src/accountmodel.cpp
+++ b/src/accountmodel.cpp
@@ -612,7 +612,7 @@ bool AccountModel::isSipSupported() const
    return d_ptr->m_lSupportedProtocols[Account::Protocol::SIP];
 }
 
-bool AccountModel::isAixSupported() const
+bool AccountModel::isIAXSupported() const
 {
    return d_ptr->m_lSupportedProtocols[Account::Protocol::IAX];
 }
diff --git a/src/accountmodel.h b/src/accountmodel.h
index be5011b9..e6f51857 100644
--- a/src/accountmodel.h
+++ b/src/accountmodel.h
@@ -43,7 +43,7 @@ public:
    Q_PROPERTY(bool           presenceSubscribeSupported READ isPresenceSubscribeSupported                     )
    Q_PROPERTY(ProtocolModel* protocolModel              READ protocolModel                                    )
    Q_PROPERTY(bool           isSipSupported             READ isSipSupported   NOTIFY supportedProtocolsChanged)
-   Q_PROPERTY(bool           isAixSupported             READ isAixSupported   NOTIFY supportedProtocolsChanged)
+   Q_PROPERTY(bool           isIAXSupported             READ isIAXSupported   NOTIFY supportedProtocolsChanged)
    Q_PROPERTY(bool           isIP2IPSupported           READ isIP2IPSupported NOTIFY supportedProtocolsChanged)
    Q_PROPERTY(bool           isRingSupported            READ isRingSupported  NOTIFY supportedProtocolsChanged)
 
@@ -66,7 +66,7 @@ public:
    bool                 isPresenceSubscribeSupported(                                      ) const;
    ProtocolModel*       protocolModel               (                                      ) const;
    bool                 isSipSupported              (                                      ) const;
-   bool                 isAixSupported              (                                      ) const;
+   bool                 isIAXSupported              (                                      ) const;
    bool                 isIP2IPSupported            (                                      ) const;
    bool                 isRingSupported             (                                      ) const;
 
diff --git a/src/contactmethod.cpp b/src/contactmethod.cpp
index afc47a29..429e01af 100644
--- a/src/contactmethod.cpp
+++ b/src/contactmethod.cpp
@@ -414,6 +414,12 @@ QString ContactMethod::uid() const
    return d_ptr->m_Uid.isEmpty()?toHash():d_ptr->m_Uid;
 }
 
+///Return the URI protocol hint
+URI::ProtocolHint ContactMethod::protocolHint() const
+{
+   return d_ptr->m_Uri.protocolHint();
+}
+
 ///Return all calls from this number
 QList<Call*> ContactMethod::calls() const
 {
@@ -597,3 +603,5 @@ TemporaryContactMethod::TemporaryContactMethod(const ContactMethod* number) :
       setAccount(number->account());
    }
 }
+
+Q_DECLARE_METATYPE(QList<Call*>)
diff --git a/src/contactmethod.h b/src/contactmethod.h
index 99eadd86..8c7833a4 100644
--- a/src/contactmethod.h
+++ b/src/contactmethod.h
@@ -54,26 +54,27 @@ public:
    };
 
    //Properties
-   Q_PROPERTY(Account*      account          READ account           WRITE setAccount              )
-   Q_PROPERTY(Person*       person           READ contact           WRITE setPerson               )
-   Q_PROPERTY(int           lastUsed         READ lastUsed                                        )
-   Q_PROPERTY(QString       uri              READ uri                                             )
-   Q_PROPERTY(int           callCount        READ callCount                                       )
-   Q_PROPERTY(QList<Call*>  calls            READ calls                                           )
-   Q_PROPERTY(int           popularityIndex  READ popularityIndex                                 )
-   Q_PROPERTY(bool          bookmarked       READ isBookmarked                                    )
-   Q_PROPERTY(QString       uid              READ uid               WRITE setUid                  )
-   Q_PROPERTY(bool          isTracked        READ isTracked         NOTIFY trackedChanged         )
-   Q_PROPERTY(bool          isPresent        READ isPresent         NOTIFY presentChanged         )
-   Q_PROPERTY(bool          supportPresence  READ supportPresence                                 )
-   Q_PROPERTY(QString       presenceMessage  READ presenceMessage   NOTIFY presenceMessageChanged )
-   Q_PROPERTY(uint          weekCount        READ weekCount                                       )
-   Q_PROPERTY(uint          trimCount        READ trimCount                                       )
-   Q_PROPERTY(bool          haveCalled       READ haveCalled                                      )
-   Q_PROPERTY(QString       primaryName      READ primaryName                                     )
-   Q_PROPERTY(bool          isBookmarked     READ isBookmarked                                    )
-   Q_PROPERTY(QVariant      icon             READ icon                                            )
-   Q_PROPERTY(int           totalSpentTime   READ totalSpentTime                                  )
+   Q_PROPERTY(Account*          account          READ account           WRITE setAccount              )
+   Q_PROPERTY(Person*           person           READ contact           WRITE setPerson               )
+   Q_PROPERTY(int               lastUsed         READ lastUsed                                        )
+   Q_PROPERTY(QString           uri              READ uri                                             )
+   Q_PROPERTY(int               callCount        READ callCount                                       )
+   Q_PROPERTY(QList<Call*>      calls            READ calls                                           )
+   Q_PROPERTY(int               popularityIndex  READ popularityIndex                                 )
+   Q_PROPERTY(bool              bookmarked       READ isBookmarked                                    )
+   Q_PROPERTY(QString           uid              READ uid               WRITE setUid                  )
+   Q_PROPERTY(bool              isTracked        READ isTracked         NOTIFY trackedChanged         )
+   Q_PROPERTY(bool              isPresent        READ isPresent         NOTIFY presentChanged         )
+   Q_PROPERTY(bool              supportPresence  READ supportPresence                                 )
+   Q_PROPERTY(QString           presenceMessage  READ presenceMessage   NOTIFY presenceMessageChanged )
+   Q_PROPERTY(uint              weekCount        READ weekCount                                       )
+   Q_PROPERTY(uint              trimCount        READ trimCount                                       )
+   Q_PROPERTY(bool              haveCalled       READ haveCalled                                      )
+   Q_PROPERTY(QString           primaryName      READ primaryName                                     )
+   Q_PROPERTY(bool              isBookmarked     READ isBookmarked                                    )
+   Q_PROPERTY(QVariant          icon             READ icon                                            )
+   Q_PROPERTY(int               totalSpentTime   READ totalSpentTime                                  )
+   Q_PROPERTY(URI::ProtocolHint protocolHint     READ protocolHint                                    )
 
 //    Q_PROPERTY(QHash<QString,int> alternativeNames READ alternativeNames         )
 
@@ -110,6 +111,7 @@ public:
    QVariant            icon            () const;
    int                 totalSpentTime  () const;
    QString             uid             () const;
+   URI::ProtocolHint   protocolHint    () const;
 
    QVariant roleData(int role) const;
 
diff --git a/src/person.cpp b/src/person.cpp
index 521eb240..93152db8 100644
--- a/src/person.cpp
+++ b/src/person.cpp
@@ -25,6 +25,7 @@
 
 //Ring library
 #include "contactmethod.h"
+#include "accountmodel.h"
 #include "collectioninterface.h"
 #include "transitionalpersonbackend.h"
 #include "account.h"
@@ -450,6 +451,43 @@ bool Person::supportPresence() const
    return false;
 }
 
+///Return true if there is a change one if the account can be used to reach that person
+bool Person::isReachable() const
+{
+   if (!d_ptr->m_Numbers.size())
+      return false;
+
+   AccountModel* m = AccountModel::instance();
+
+   const bool hasSip   = m->isSipSupported  ();
+   const bool hasIAX   = m->isIAXSupported  ();
+   const bool hasIP2IP = m->isIP2IPSupported();
+   const bool hasRing  = m->isRingSupported ();
+
+   for (const ContactMethod* n : d_ptr->m_Numbers) {
+      switch (n->protocolHint()) {
+         case URI::ProtocolHint::SIP_HOST :
+         case URI::ProtocolHint::IP       :
+            if (hasIP2IP)
+               return true;
+            //no break
+         case URI::ProtocolHint::SIP_OTHER:
+            if (hasSip)
+               return true;
+            break;
+         case URI::ProtocolHint::IAX      :
+            if (hasIAX)
+               return true;
+            break;
+         case URI::ProtocolHint::RING     :
+            if (hasRing)
+               return true;
+            break;
+      }
+   }
+   return false;
+}
+
 ///Recomputing the filter string is heavy, cache it
 QString Person::filterString() const
 {
diff --git a/src/person.h b/src/person.h
index 6642b351..e5f3816d 100644
--- a/src/person.h
+++ b/src/person.h
@@ -144,6 +144,7 @@ public:
    bool isPresent                  () const;
    bool isTracked                  () const;
    bool supportPresence            () const;
+   bool isReachable                () const;
 
    //Setters
    void setContactMethods ( ContactMethods           );
diff --git a/src/uri.cpp b/src/uri.cpp
index b745bd63..a3cf025e 100644
--- a/src/uri.cpp
+++ b/src/uri.cpp
@@ -17,30 +17,45 @@
  ***************************************************************************/
 #include "uri.h"
 
-constexpr const char* URI::schemeNames[];
 
 class URIPrivate
 {
 public:
+   ///Strings associated with SchemeType
+   constexpr static const char* schemeNames[] = {
+      /*NONE = */ ""     ,
+      /*SIP  = */ "sip:" ,
+      /*SIPS = */ "sips:",
+      /*IAX  = */ "iax:" ,
+      /*IAX2 = */ "iax2:",
+      /*RING = */ "ring:",
+   };
+
    URIPrivate(QString* uri);
    //Attributes
-   QString          m_Hostname    ;
-   QString          m_Userinfo    ;
-   QStringList      m_lAttributes ;
-   QString          m_Stripped    ;
-   URI::SchemeType  m_HeaderType  ;
-   bool             m_hasChevrons ;
-   bool             m_Parsed      ;
+   QString           m_Hostname    ;
+   QString           m_Userinfo    ;
+   QStringList       m_lAttributes ;
+   QString           m_Stripped    ;
+   URI::SchemeType   m_HeaderType  ;
+   bool              m_hasChevrons ;
+   bool              m_Parsed      ;
+   bool              m_HasAt       ;
+   URI::ProtocolHint m_ProtocolHint;
+   bool              m_HintParsed  ;
 
    //Helper
    static QString strip(const QString& uri, URI::SchemeType& scheme);
    void parse();
+   static bool checkIp(const QString& str, bool &isHash);
 private:
    QString* q_ptr;
 };
 
+constexpr const char* URIPrivate::schemeNames[];
+
 URIPrivate::URIPrivate(QString* uri) : m_Parsed(false),m_HeaderType(URI::SchemeType::NONE),q_ptr(uri),
-m_hasChevrons(false)
+m_hasChevrons(false),m_HasAt(false),m_ProtocolHint(URI::ProtocolHint::SIP_OTHER),m_HintParsed(false)
 {
 }
 
@@ -89,26 +104,50 @@ QString URIPrivate::strip(const QString& uri, URI::SchemeType& scheme)
 {
    if (uri.isEmpty())
       return QString();
-   int start(0),end(uri.size()-1); //Other type of comparisons were too slow
-   if (end > 5 && uri[0] == '<' ) {
-      if (uri[4] == ':') {
-         scheme = uri[1] == 's'?URI::SchemeType::SIP : URI::SchemeType::IAX;
-         start = 5;
+
+   int start(uri[0] == '<'?1:0),end(uri.size()-1); //Other type of comparisons were too slow
+   uchar c;
+
+   //Assume the scheme is either iax, sip or ring using the first letter and length, this
+   //is dangerous and can cause undefined behaviour that will cause the call to fail
+   //later on, but this is not really a problem for now
+   if (end > start+3 && (c = uri[start+3].toLatin1()) == ':') {
+      switch (c) {
+         case 'i':
+            scheme = URI::SchemeType::IAX;
+            break;
+         case 's':
+            scheme = URI::SchemeType::SIP;
+            break;
       }
-      else if (uri[5] == ':') {
-         scheme = URI::SchemeType::SIPS;
-         start = 6;
+      start = 5;
+   }
+   else if (end > start+4 && (c = uri[start+4].toLatin1()) == ':') {
+      switch (c) {
+         case 'i':
+            scheme = URI::SchemeType::IAX2;
+            break;
+         case 'r':
+            scheme = URI::SchemeType::RING;
+            break;
+         case 's':
+            scheme = URI::SchemeType::SIPS;
+            break;
       }
+      start = 6;
    }
+
    if (end && uri[end] == '>')
       end--;
    else if (start) {
       //TODO there may be a ';' section with arguments, check
    }
+
    return uri.mid(start,end-start+1);
 }
 
-/**Return the domaine of an URI
+/**
+ * Return the domaine of an URI
  *
  * For example, example.com in <sip:12345@example.com>
  */
@@ -141,14 +180,117 @@ URI::SchemeType URI::schemeType() const
    return d_ptr->m_HeaderType;
 }
 
+/**
+ * "Fast" Ipv4 and Ipv6 check, accept 999.999.999.999, :::::::::FF and other
+ * atrocities, but at least perform a O(N) ish check and validate the hash
+ *
+ * @param str an uservalue (faster the scheme and before the "at" sign)
+ * @param [out] isHash if the content is pure hexadecimal ASCII
+ */
+bool URIPrivate::checkIp(const QString& str, bool &isHash)
+{
+   char* raw = str.toLatin1().data();
+   ushort max = str.size();
+
+   if (max < 3 || max > 45)
+      return false;
+
+   uchar dc(0),sc(0),i(0),d(0),hx(1);
+
+   while (i < max) {
+      switch(raw[i]) {
+         case '.':
+            isHash = false;
+            d = 0;
+            dc++;
+            break;
+         case '0':
+         case '1':
+         case '2':
+         case '3':
+         case '4':
+         case '5':
+         case '6':
+         case '7':
+         case '8':
+         case '9':
+            if (++d > 3 && dc)
+               return false;
+            break;
+         case ':':
+            isHash = false;
+            sc++;
+            //No break
+         case 'A':
+         case 'B':
+         case 'C':
+         case 'D':
+         case 'E':
+         case 'F':
+         case 'a':
+         case 'b':
+         case 'c':
+         case 'd':
+         case 'e':
+         case 'f':
+            hx = 0;
+            break;
+         default:
+            return false;
+      };
+      i++;
+   }
+   return (hx && dc == 3 && d < 4) ^ (~(sc < 2 || dc));
+}
+
+/**
+ * This method return an hint to guess the protocol that could be used to call
+ * this URI. It is a quick guess, not something that should be trusted
+ *
+ * @warning, this method is O(N) when called for the first time on an URI
+ */
+URI::ProtocolHint URI::protocolHint() const
+{
+   if (!d_ptr->m_Parsed)
+      const_cast<URI*>(this)->d_ptr->parse();
+
+   if (!d_ptr->m_HintParsed) {
+      bool isHash = d_ptr->m_Userinfo.size() == 40;
+      d_ptr->m_ProtocolHint = \
+        (
+         //Step one    : Check IAX protocol, is has already been detected at this point
+         d_ptr->m_HeaderType == URI::SchemeType::IAX2 || d_ptr->m_HeaderType == URI::SchemeType::IAX
+            ? URI::ProtocolHint::IAX
+
+      : (
+         //Step two  : check IP
+         URIPrivate::checkIp(d_ptr->m_Userinfo,isHash) ? URI::ProtocolHint::IP
+
+      : (
+         //Step three    : Check RING protocol, is has already been detected at this point
+         d_ptr->m_HeaderType == URI::SchemeType::RING && isHash ? URI::ProtocolHint::RING
+
+      : (
+         //Step four   : Differentiate between ***@*** and *** type URIs
+         d_ptr->m_HasAt ? URI::ProtocolHint::SIP_HOST : URI::ProtocolHint::SIP_OTHER
+
+        ))));
+
+        d_ptr->m_HintParsed = true;
+   qDebug() << "ICI" << (int)d_ptr->m_ProtocolHint;
+   }
+   return d_ptr->m_ProtocolHint;
+}
+
 ///Keep a cache of the values to avoid re-parsing them
 void URIPrivate::parse()
 {
-   //TODO DHT hashes have 40 chars or 45 with sips:/ring: is set
+   //FIXME the indexOf is done twice, the second time could be avoided
    if (q_ptr->indexOf('@') != -1) {
       const QStringList split = q_ptr->split('@');
       m_Hostname = split[1];//split[1].left(split[1].size())
       m_Userinfo = split[0];
+      m_HasAt    = split.size();
       m_Parsed = true;
    }
 }
@@ -171,6 +313,28 @@ QString URI::userinfo() const
 QString URI::fullUri() const
 {
    return QString("<%1%2>")
-      .arg(schemeNames[static_cast<int>(d_ptr->m_HeaderType == SchemeType::NONE?SchemeType::SIP:d_ptr->m_HeaderType)])
+      .arg(URIPrivate::schemeNames[static_cast<int>(d_ptr->m_HeaderType == SchemeType::NONE?SchemeType::SIP:d_ptr->m_HeaderType)])
       .arg(*this);
 }
+
+QDataStream& operator<<( QDataStream& stream, const URI::ProtocolHint& ph )
+{
+   switch(ph) {
+      case URI::ProtocolHint::SIP_OTHER:
+         stream << "SIP_OTHER";
+         break;
+      case URI::ProtocolHint::IAX      :
+         stream << "IAX";
+         break;
+      case URI::ProtocolHint::RING     :
+         stream << "RING";
+         break;
+      case URI::ProtocolHint::IP       :
+         stream << "IP";
+         break;
+      case URI::ProtocolHint::SIP_HOST :
+         stream << "SIP_HOST";
+         break;
+   }
+   return stream;
+}
diff --git a/src/uri.h b/src/uri.h
index f3353b47..99c219e6 100644
--- a/src/uri.h
+++ b/src/uri.h
@@ -23,13 +23,14 @@
 #include <QStringList>
 
 class URIPrivate;
+class QDataStream;
 
 /**
     * @class URI A specialized string with multiple attributes
-    * 
-    * Most of Ring-KDE handle uri as strings, but more
+    *
+    * Most of LibRingClient handle uri as strings, but more
     * advanced algorithms need to access the various sections.
-    * 
+    *
     * Here is some example of common numbers/URIs:
     *  * 123
     *  * 123@192.168.123.123
@@ -43,12 +44,12 @@ class URIPrivate;
     *  * iax:example.com/alice
     *  * iax:johnQ@example.com/12022561414
     *  * iax:example.com:4570/alice?friends
-    * 
+    *
     * @ref http://tools.ietf.org/html/rfc5456#page-8
     * @ref http://tools.ietf.org/html/rfc3986
     * @ref http://tools.ietf.org/html/rfc3261
     * @ref http://tools.ietf.org/html/rfc5630
-    * 
+    *
     * From the RFC:
     *    foo://example.com:8042/over/there?name=ferret#nose
     *    \_/   \______________/\_________/ \_________/ \__/
@@ -85,17 +86,10 @@ public:
       SIP  ,
       SIPS ,
       IAX  ,
+      IAX2 ,
       RING ,
    };
-
-   ///Strings associated with SchemeType
-   constexpr static const char* schemeNames[] = {
-      /*NONE = */ ""     ,
-      /*SIP  = */ "sip:" ,
-      /*SIPS = */ "sips:",
-      /*IAX  = */ "iax:" ,
-      /*RING = */ "ring:",
-   };
+   Q_ENUMS(URI::SchemeType)
 
    /**
     * @enum Transport each known valid transport types
@@ -112,18 +106,41 @@ public:
       SCTP   , /*!<                                                       */
       sctp   , /*!<                                                       */
    };
+   Q_ENUMS(URI::Transport)
+
+   /**
+    * @enum ProtocolHint Expanded version of Account::Protocol
+    *
+    * This is used to make better choice when it come to choose an account or
+    * guess if the URI can be used with the current set et configured accounts.
+    *
+    * @warning This is an approximation. Those values are guessed using partial
+    * parsing (for performance) and are not definitive.
+    */
+   enum class ProtocolHint {
+      SIP_OTHER = 0, /*!< Anything non empty that doesn't fit in other categories */
+      IAX       = 1, /*!< Start with "iax:" or "iax2:"                            */
+      RING      = 2, /*!< Start with "ring:" and has 45 ASCII characters          */
+      IP        = 3, /*!< Match an IPv4 address                                   */
+      SIP_HOST  = 4, /*!< Has an @ and no "ring:" prefix                          */
+   };
+   Q_ENUMS(URI::ProtocolHint)
 
-   QString    hostname    () const;
-   QString    fullUri     () const;
-   QString    userinfo    () const;
-   bool       hasHostname () const;
-   SchemeType schemeType  () const;
+   QString    hostname      () const;
+   QString    fullUri       () const;
+   QString    userinfo      () const;
+   bool       hasHostname   () const;
+   SchemeType schemeType    () const;
+   ProtocolHint protocolHint() const;
 
    URI& operator=(const URI&);
 
 private:
    const QScopedPointer<URIPrivate> d_ptr;
 };
-// Q_DECLARE_METATYPE(URI*)
+
+Q_DECLARE_METATYPE(URI::ProtocolHint)
+
+QDataStream& operator<< ( QDataStream& stream, const URI::ProtocolHint& ph );
 
 #endif //URI_H
-- 
GitLab