QHostAddress: comparator with flexible conversion rules

Add a named comparison method that allows the caller to
specify in how far a translation between IPv4 and IPv6 should be
performed before comparing.

Change-Id: I9aeffcd4539a6c0537c083c4553357b22167df3f
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Felix Braun 2016-04-11 22:16:09 +02:00
parent c0979fe9d6
commit cedc554321
3 changed files with 166 additions and 16 deletions

View File

@ -152,20 +152,37 @@ void QHostAddressPrivate::setAddress(quint32 a_)
/// parses v4-mapped addresses or the AnyIPv6 address and stores in \a a;
/// returns true if the address was one of those
static bool convertToIpv4(quint32& a, const Q_IPV6ADDR &a6)
static bool convertToIpv4(quint32& a, const Q_IPV6ADDR &a6, const QHostAddress::ConversionMode mode)
{
if (mode == QHostAddress::StrictConversion)
return false;
const uchar *ptr = a6.c;
if (qFromUnaligned<quint64>(ptr) != 0)
return false;
if (qFromBigEndian<quint32>(ptr + 8) == 0) {
// is it AnyIPv6?
a = 0;
return qFromBigEndian<quint32>(ptr + 12) == 0;
const quint32 mid = qFromBigEndian<quint32>(ptr + 8);
if ((mid == 0xffff) && (mode & QHostAddress::ConvertV4MappedToIPv4)) {
a = qFromBigEndian<quint32>(ptr + 12);
return true;
}
if (qFromBigEndian<quint32>(ptr + 8) != 0xFFFF)
if (mid != 0)
return false;
a = qFromBigEndian<quint32>(ptr + 12);
return true;
const quint32 low = qFromBigEndian<quint32>(ptr + 12);
if ((low == 0) && (mode & QHostAddress::ConvertUnspecifiedAddress)) {
a = 0;
return true;
}
if ((low == 1) && (mode & QHostAddress::ConvertLocalHost)) {
a = INADDR_LOOPBACK;
return true;
}
if ((low != 1) && (mode & QHostAddress::ConvertV4CompatToIPv4)) {
a = low;
return true;
}
return false;
}
void QHostAddressPrivate::setAddress(const quint8 *a_)
@ -174,7 +191,8 @@ void QHostAddressPrivate::setAddress(const quint8 *a_)
isParsed = true;
memcpy(a6.c, a_, sizeof(a6));
a = 0;
convertToIpv4(a, a6);
convertToIpv4(a, a6, (QHostAddress::ConvertV4MappedToIPv4
| QHostAddress::ConvertUnspecifiedAddress));
}
void QHostAddressPrivate::setAddress(const Q_IPV6ADDR &a_)
@ -386,6 +404,20 @@ void QNetmaskAddress::setPrefixLength(QAbstractSocket::NetworkLayerProtocol prot
\value Any The dual stack any-address. A socket bound with this address will listen on both IPv4 and IPv6 interfaces.
*/
/*! \enum QHostAddress::ConversionModeFlag
\since 5.8
\value StrictConversion Don't convert IPv6 addresses to IPv4 when comparing two QHostAddress objects of different protocols, so they will always be considered different.
\value ConvertV4MappedToIPv4 Convert IPv4-mapped IPv6 addresses (RFC 4291 sect. 2.5.5.2) when comparing. Therefore QHostAddress("::ffff:192.168.1.1") will compare equal to QHostAddress("192.168.1.1").
\value ConvertV4CompatToIPv4 Convert IPv4-compatible IPv6 addresses (RFC 4291 sect. 2.5.5.1) when comparing. Therefore QHostAddress("::192.168.1.1") will compare equal to QHostAddress("192.168.1.1").
\value ConvertLocalHost Convert the IPv6 loopback addresses to its IPv4 equivalent when comparing. Therefore e.g. QHostAddress("::1") will compare equal to QHostAddress("127.0.0.1").
\value ConvertUnspecifiedAddress All unspecified addresses will compare equal, namely AnyIPv4, AnyIPv6 and Any.
\value TolerantConversion Sets all three preceding flags.
\sa isEqual()
*/
/*! Constructs a null host address object, i.e. an address which is not valid for any host or interface.
\sa clear()
@ -691,7 +723,9 @@ quint32 QHostAddress::toIPv4Address(bool *ok) const
quint32 dummy;
if (ok)
*ok = d->protocol == QAbstractSocket::IPv4Protocol || d->protocol == QAbstractSocket::AnyIPProtocol
|| (d->protocol == QAbstractSocket::IPv6Protocol && convertToIpv4(dummy, d->a6));
|| (d->protocol == QAbstractSocket::IPv6Protocol
&& convertToIpv4(dummy, d->a6, ConversionMode(QHostAddress::ConvertV4MappedToIPv4
| QHostAddress::ConvertUnspecifiedAddress)));
return d->a;
}
@ -813,19 +847,73 @@ void QHostAddress::setScopeId(const QString &id)
/*!
Returns \c true if this host address is the same as the \a other address
given; otherwise returns \c false.
given; otherwise returns \c false. This operator just calls isEqual(other, StrictConversion).
\sa isEqual()
*/
bool QHostAddress::operator==(const QHostAddress &other) const
{
return isEqual(other, StrictConversion);
}
/*!
\since 5.8
Returns \c true if this host address is the same as the \a other address
given; otherwise returns \c false.
The parameter \a mode controls which conversions are preformed between addresses
of differing protocols. If no \a mode is given, \c TolerantConversion is performed
by default.
\sa ConversionMode, operator==()
*/
bool QHostAddress::isEqual(const QHostAddress &other, ConversionMode mode) const
{
QT_ENSURE_PARSED(this);
QT_ENSURE_PARSED(&other);
if (d->protocol == QAbstractSocket::IPv4Protocol)
return other.d->protocol == QAbstractSocket::IPv4Protocol && d->a == other.d->a;
if (d->protocol == QAbstractSocket::IPv6Protocol) {
return other.d->protocol == QAbstractSocket::IPv6Protocol
&& memcmp(&d->a6, &other.d->a6, sizeof(Q_IPV6ADDR)) == 0;
if (d->protocol == QAbstractSocket::IPv4Protocol) {
switch (other.d->protocol) {
case QAbstractSocket::IPv4Protocol:
return d->a == other.d->a;
case QAbstractSocket::IPv6Protocol:
quint32 a4;
return convertToIpv4(a4, other.d->a6, mode) && (a4 == d->a);
case QAbstractSocket::AnyIPProtocol:
return (mode & QHostAddress::ConvertUnspecifiedAddress) && d->a == 0;
case QAbstractSocket::UnknownNetworkLayerProtocol:
return false;
}
}
if (d->protocol == QAbstractSocket::IPv6Protocol) {
switch (other.d->protocol) {
case QAbstractSocket::IPv4Protocol:
quint32 a4;
return convertToIpv4(a4, d->a6, mode) && (a4 == other.d->a);
case QAbstractSocket::IPv6Protocol:
return memcmp(&d->a6, &other.d->a6, sizeof(Q_IPV6ADDR)) == 0;
case QAbstractSocket::AnyIPProtocol:
return (mode & QHostAddress::ConvertUnspecifiedAddress)
&& (other.d->a6_64.c[0] == 0) && (other.d->a6_64.c[1] == 0);
case QAbstractSocket::UnknownNetworkLayerProtocol:
return false;
}
}
if ((d->protocol == QAbstractSocket::AnyIPProtocol)
&& (mode & QHostAddress::ConvertUnspecifiedAddress)) {
switch (other.d->protocol) {
case QAbstractSocket::IPv4Protocol:
return other.d->a == 0;
case QAbstractSocket::IPv6Protocol:
return (other.d->a6_64.c[0] == 0) && (other.d->a6_64.c[1] == 0);
default:
break;
}
}
return d->protocol == other.d->protocol;
}

View File

@ -79,6 +79,16 @@ public:
AnyIPv6,
AnyIPv4
};
enum ConversionModeFlag {
ConvertV4MappedToIPv4 = 1,
ConvertV4CompatToIPv4 = 2,
ConvertUnspecifiedAddress = 4,
ConvertLocalHost = 8,
TolerantConversion = 0xff,
StrictConversion = 0
};
Q_DECLARE_FLAGS(ConversionMode, ConversionModeFlag)
QHostAddress();
explicit QHostAddress(quint32 ip4Addr);
@ -118,6 +128,7 @@ public:
QString scopeId() const;
void setScopeId(const QString &id);
bool isEqual(const QHostAddress &address, ConversionMode mode = TolerantConversion) const;
bool operator ==(const QHostAddress &address) const;
bool operator ==(SpecialAddress address) const;
inline bool operator !=(const QHostAddress &address) const
@ -139,6 +150,7 @@ public:
protected:
QScopedPointer<QHostAddressPrivate> d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QHostAddress::ConversionMode)
Q_DECLARE_SHARED_NOT_MOVABLE_UNTIL_QT6(QHostAddress)
inline bool operator ==(QHostAddress::SpecialAddress address1, const QHostAddress &address2)

View File

@ -62,6 +62,8 @@ private slots:
void specialAddresses();
void compare_data();
void compare();
void isEqual_data();
void isEqual();
void assignment();
void scopeId();
void hashKey();
@ -282,6 +284,7 @@ void tst_QHostAddress::compare_data()
QTest::newRow("5") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::Broadcast) << false;
QTest::newRow("6") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << false;
QTest::newRow("7") << QHostAddress() << QHostAddress(QHostAddress::LocalHostIPv6) << false;
QTest::newRow("any4-any6") << QHostAddress(QHostAddress::AnyIPv4) << QHostAddress(QHostAddress::AnyIPv6) << false;
Q_IPV6ADDR localhostv4mapped = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 127, 0, 0, 1 } };
QTest::newRow("v4-v4mapped") << QHostAddress(QHostAddress::LocalHost) << QHostAddress("::ffff:127.0.0.1") << false;
@ -300,6 +303,53 @@ void tst_QHostAddress::compare()
QCOMPARE(qHash(first), qHash(second));
}
void tst_QHostAddress::isEqual_data()
{
QTest::addColumn<QHostAddress>("first");
QTest::addColumn<QHostAddress>("second");
QTest::addColumn<int>("flags");
QTest::addColumn<bool>("result");
// QHostAddress::StrictConversion is already tested in compare()
QTest::newRow("localhost4to6-local") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertLocalHost << true;
QTest::newRow("localhost4to6-compat") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4CompatToIPv4 << false;
QTest::newRow("localhost4to6-mapped") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4MappedToIPv4 << false;
QTest::newRow("localhost4to6-unspec") << QHostAddress(QHostAddress::LocalHost) << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertUnspecifiedAddress << false;
QTest::newRow("0.0.0.1-::1-local") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("v4-v4compat-local") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("v4-v4mapped-local") << QHostAddress("192.168.1.1") << QHostAddress("::ffff:192.168.1.1") << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("0.0.0.1-::1-unspec") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertUnspecifiedAddress << false;
QTest::newRow("v4-v4compat-unspec") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertUnspecifiedAddress << false;
QTest::newRow("v4-v4mapped-unspec") << QHostAddress("192.168.1.1") << QHostAddress("::ffff:192.168.1.1") << (int)QHostAddress::ConvertUnspecifiedAddress << false;
QTest::newRow("0.0.0.1-::1-compat") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4CompatToIPv4 << false;
QTest::newRow("v4-v4compat-compat") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertV4CompatToIPv4 << true;
QTest::newRow("v4-v4mapped-compat") << QHostAddress("192.168.1.1") << QHostAddress("::ffff:192.168.1.1") << (int)QHostAddress::ConvertV4CompatToIPv4 << false;
QTest::newRow("0.0.0.1-::1-mapped") << QHostAddress("0.0.0.1") << QHostAddress(QHostAddress::LocalHostIPv6) << (int)QHostAddress::ConvertV4MappedToIPv4 << false;
QTest::newRow("v4-v4compat-mapped") << QHostAddress("192.168.1.1") << QHostAddress("::192.168.1.1") << (int)QHostAddress::ConvertV4MappedToIPv4 << false;
QTest::newRow("v4-v4mapped-mapped") << QHostAddress("192.168.1.1") << QHostAddress("::FFFF:192.168.1.1") << (int)QHostAddress::ConvertV4MappedToIPv4 << true;
QTest::newRow("undef-any-local") << QHostAddress() << QHostAddress(QHostAddress::Any) << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("undef-any-unspec") << QHostAddress() << QHostAddress(QHostAddress::Any) << (int)QHostAddress::ConvertUnspecifiedAddress << false;
QTest::newRow("anyv6-anyv4-compat") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertV4CompatToIPv4 << true;
QTest::newRow("anyv6-anyv4-mapped") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertV4MappedToIPv4 << false;
QTest::newRow("anyv6-anyv4-unspec") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertUnspecifiedAddress << true;
QTest::newRow("any-anyv4-unspec") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertUnspecifiedAddress << true;
QTest::newRow("any-anyv6-unspec") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv6) << (int)QHostAddress::ConvertUnspecifiedAddress << true;
QTest::newRow("anyv6-anyv4-local") << QHostAddress(QHostAddress::AnyIPv6) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("any-anyv4-local") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv4) << (int)QHostAddress::ConvertLocalHost << false;
QTest::newRow("any-anyv6-local") << QHostAddress(QHostAddress::Any) << QHostAddress(QHostAddress::AnyIPv6) << (int)QHostAddress::ConvertLocalHost << false;
}
void tst_QHostAddress::isEqual()
{
QFETCH(QHostAddress, first);
QFETCH(QHostAddress, second);
QFETCH(int, flags);
QFETCH(bool, result);
QCOMPARE(first.isEqual(second, QHostAddress::ConversionModeFlag(flags)), result);
QCOMPARE(second.isEqual(first, QHostAddress::ConversionModeFlag(flags)), result);
}
void tst_QHostAddress::assignment()
{
QHostAddress address;