Merge "[Thread] add a unicast routing case when infra link switches" into main
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index 61eb766..0b2003f 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -36,6 +36,7 @@
import java.io.IOException;
import java.net.InetSocketAddress;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -134,6 +135,15 @@
mAnnouncer.startSending(info.getServiceId(), announcementInfo,
0L /* initialDelayMs */);
+
+ // Re-announce the services which have the same custom hostname.
+ final String hostname = mRecordRepository.getHostnameForServiceId(info.getServiceId());
+ if (hostname != null) {
+ final List<MdnsAnnouncer.AnnouncementInfo> announcementInfos =
+ new ArrayList<>(mRecordRepository.restartAnnouncingForHostname(hostname));
+ announcementInfos.removeIf((i) -> i.getServiceId() == info.getServiceId());
+ reannounceServices(announcementInfos);
+ }
}
}
@@ -308,17 +318,10 @@
if (hostname != null) {
final List<MdnsProber.ProbingInfo> probingInfos =
mRecordRepository.restartProbingForHostname(hostname);
- for (MdnsProber.ProbingInfo probingInfo : probingInfos) {
- mProber.stop(probingInfo.getServiceId());
- mProber.startProbing(probingInfo);
- }
+ reprobeServices(probingInfos);
final List<MdnsAnnouncer.AnnouncementInfo> announcementInfos =
mRecordRepository.restartAnnouncingForHostname(hostname);
- for (MdnsAnnouncer.AnnouncementInfo announcementInfo : announcementInfos) {
- mAnnouncer.stop(announcementInfo.getServiceId());
- mAnnouncer.startSending(
- announcementInfo.getServiceId(), announcementInfo, 0 /* initialDelayMs */);
- }
+ reannounceServices(announcementInfos);
}
}
@@ -464,4 +467,19 @@
return new byte[0];
}
}
+
+ private void reprobeServices(List<MdnsProber.ProbingInfo> probingInfos) {
+ for (MdnsProber.ProbingInfo probingInfo : probingInfos) {
+ mProber.stop(probingInfo.getServiceId());
+ mProber.startProbing(probingInfo);
+ }
+ }
+
+ private void reannounceServices(List<MdnsAnnouncer.AnnouncementInfo> announcementInfos) {
+ for (MdnsAnnouncer.AnnouncementInfo announcementInfo : announcementInfos) {
+ mAnnouncer.stop(announcementInfo.getServiceId());
+ mAnnouncer.startSending(
+ announcementInfo.getServiceId(), announcementInfo, 0 /* initialDelayMs */);
+ }
+ }
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
index 1fabd49..83ecabc 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
@@ -42,7 +42,7 @@
@NonNull
public final List<MdnsRecord> additionalRecords;
- MdnsPacket(int flags,
+ public MdnsPacket(int flags,
@NonNull List<MdnsRecord> questions,
@NonNull List<MdnsRecord> answers,
@NonNull List<MdnsRecord> authorityRecords,
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index d553210..3c11a24 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity.mdns.util;
+import static com.android.server.connectivity.mdns.MdnsConstants.FLAG_TRUNCATED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Network;
@@ -23,6 +25,7 @@
import android.os.Handler;
import android.os.SystemClock;
import android.util.ArraySet;
+import android.util.Pair;
import com.android.server.connectivity.mdns.MdnsConstants;
import com.android.server.connectivity.mdns.MdnsPacket;
@@ -30,13 +33,18 @@
import com.android.server.connectivity.mdns.MdnsRecord;
import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -226,6 +234,100 @@
}
/**
+ * Writes the possible query content of an MdnsPacket into the data buffer.
+ *
+ * <p>This method is specifically for query packets. It writes the question and answer sections
+ * into the data buffer only.
+ *
+ * @param packetCreationBuffer The data buffer for the query content.
+ * @param packet The MdnsPacket to be written into the data buffer.
+ * @return A Pair containing:
+ * 1. The remaining MdnsPacket data that could not fit in the buffer.
+ * 2. The length of the data written to the buffer.
+ */
+ @Nullable
+ private static Pair<MdnsPacket, Integer> writePossibleMdnsPacket(
+ @NonNull byte[] packetCreationBuffer, @NonNull MdnsPacket packet) throws IOException {
+ MdnsPacket remainingPacket;
+ final MdnsPacketWriter writer = new MdnsPacketWriter(packetCreationBuffer);
+ writer.writeUInt16(packet.transactionId); // Transaction ID
+
+ final int flagsPos = writer.getWritePosition();
+ writer.writeUInt16(0); // Flags, written later
+ writer.writeUInt16(0); // questions count, written later
+ writer.writeUInt16(0); // answers count, written later
+ writer.writeUInt16(0); // authority entries count, empty session for query
+ writer.writeUInt16(0); // additional records count, empty session for query
+
+ int writtenQuestions = 0;
+ int writtenAnswers = 0;
+ int lastValidPos = writer.getWritePosition();
+ try {
+ for (MdnsRecord record : packet.questions) {
+ // Questions do not have TTL or data
+ record.writeHeaderFields(writer);
+ writtenQuestions++;
+ lastValidPos = writer.getWritePosition();
+ }
+ for (MdnsRecord record : packet.answers) {
+ record.write(writer, 0L);
+ writtenAnswers++;
+ lastValidPos = writer.getWritePosition();
+ }
+ remainingPacket = null;
+ } catch (IOException e) {
+ // Went over the packet limit; truncate
+ if (writtenQuestions == 0 && writtenAnswers == 0) {
+ // No space to write even one record: just throw (as subclass of IOException)
+ throw e;
+ }
+
+ // Set the last valid position as the final position (not as a rewind)
+ writer.rewind(lastValidPos);
+ writer.clearRewind();
+
+ remainingPacket = new MdnsPacket(packet.flags,
+ packet.questions.subList(
+ writtenQuestions, packet.questions.size()),
+ packet.answers.subList(
+ writtenAnswers, packet.answers.size()),
+ Collections.emptyList(), /* authorityRecords */
+ Collections.emptyList() /* additionalRecords */);
+ }
+
+ final int len = writer.getWritePosition();
+ writer.rewind(flagsPos);
+ writer.writeUInt16(packet.flags | (remainingPacket == null ? 0 : FLAG_TRUNCATED));
+ writer.writeUInt16(writtenQuestions);
+ writer.writeUInt16(writtenAnswers);
+ writer.unrewind();
+
+ return Pair.create(remainingPacket, len);
+ }
+
+ /**
+ * Create Datagram packets from given MdnsPacket and InetSocketAddress.
+ *
+ * <p> If the MdnsPacket is too large for a single DatagramPacket, it will be split into
+ * multiple DatagramPackets.
+ */
+ public static List<DatagramPacket> createQueryDatagramPackets(
+ @NonNull byte[] packetCreationBuffer, @NonNull MdnsPacket packet,
+ @NonNull InetSocketAddress destination) throws IOException {
+ final List<DatagramPacket> datagramPackets = new ArrayList<>();
+ MdnsPacket remainingPacket = packet;
+ while (remainingPacket != null) {
+ final Pair<MdnsPacket, Integer> result =
+ writePossibleMdnsPacket(packetCreationBuffer, remainingPacket);
+ remainingPacket = result.first;
+ final int len = result.second;
+ final byte[] outBuffer = Arrays.copyOfRange(packetCreationBuffer, 0, len);
+ datagramPackets.add(new DatagramPacket(outBuffer, 0, outBuffer.length, destination));
+ }
+ return datagramPackets;
+ }
+
+ /**
* Checks if the MdnsRecord needs to be renewed or not.
*
* <p>As per RFC6762 7.1 no need to query if remaining TTL is more than half the original one,
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
deleted file mode 100644
index e92c906..0000000
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.net.cts
-
-import android.content.pm.PackageManager.FEATURE_WIFI
-import android.net.ConnectivityManager
-import android.net.NetworkCapabilities
-import android.net.NetworkRequest
-import android.os.Build
-import android.system.OsConstants
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.compatibility.common.util.PropertyUtil.isVendorApiLevelNewerThan
-import com.android.compatibility.common.util.SystemUtil
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.NetworkStackModuleTest
-import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
-import com.android.testutils.TestableNetworkCallback
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-private const val TIMEOUT_MS = 2000L
-
-@RunWith(DevSdkIgnoreRunner::class)
-@NetworkStackModuleTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
-class ApfIntegrationTest {
- private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
- private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
- private val pm by lazy { context.packageManager }
- private lateinit var wifiIfaceName: String
- @Before
- fun setUp() {
- assumeTrue(pm.hasSystemFeature(FEATURE_WIFI))
- assumeTrue(isVendorApiLevelNewerThan(Build.VERSION_CODES.TIRAMISU))
- val cb = TestableNetworkCallback()
- cm.requestNetwork(
- NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
- .build(),
- cb
- )
- cb.eventuallyExpect<LinkPropertiesChanged>(TIMEOUT_MS) {
- wifiIfaceName = assertNotNull(it.lp.interfaceName)
- true
- }
- assertNotNull(wifiIfaceName)
- }
-
- @Test
- fun testGetApfCapabilities() {
- val capabilities = SystemUtil
- .runShellCommand("cmd network_stack apf $wifiIfaceName capabilities").trim()
- val (version, maxLen, packetFormat) = capabilities.split(",").map { it.toInt() }
- assertEquals(4, version)
- assertThat(maxLen).isAtLeast(1024)
- if (isVendorApiLevelNewerThan(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) {
- assertThat(maxLen).isAtLeast(2000)
- }
- assertEquals(OsConstants.ARPHRD_ETHER, packetFormat)
- }
-}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
index 7ac7bee..629ac67 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -47,7 +47,6 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString
import org.mockito.Mockito.argThat
-import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
@@ -55,7 +54,6 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
private const val LOG_TAG = "testlogtag"
@@ -234,19 +232,49 @@
addServiceAndFinishProbing(TEST_SERVICE_ID_2, TEST_SERVICE_1_CUSTOM_HOST)
doReturn("MyTestHost")
.`when`(repository).getHostnameForServiceId(TEST_SERVICE_ID_1)
- doReturn(TEST_SERVICE_ID_2).`when`(announcementInfo).serviceId
doReturn(listOf(announcementInfo))
.`when`(repository).restartAnnouncingForHostname("MyTestHost")
- clearInvocations(announcer)
+ val inOrder = inOrder(prober, announcer)
// Remove the custom host: the custom host's announcement is stopped and the probed services
// which use that hostname are re-announced.
advertiser.removeService(TEST_SERVICE_ID_1)
- verify(prober).stop(TEST_SERVICE_ID_1)
- verify(announcer, atLeastOnce()).stop(TEST_SERVICE_ID_1)
- verify(announcer).stop(TEST_SERVICE_ID_2)
- verify(announcer).startSending(TEST_SERVICE_ID_2, announcementInfo, 0L /* initialDelayMs */)
+ inOrder.verify(prober).stop(TEST_SERVICE_ID_1)
+ inOrder.verify(announcer).stop(TEST_SERVICE_ID_1)
+ inOrder.verify(announcer).stop(TEST_SERVICE_ID_2)
+ inOrder.verify(announcer).startSending(TEST_SERVICE_ID_2, announcementInfo, 0L /* initialDelayMs */)
+ }
+
+ @Test
+ fun testAddMoreAddressesForCustomHost_restartAnnouncingForProbedServices() {
+ val customHost = NsdServiceInfo().apply {
+ hostname = "MyTestHost"
+ hostAddresses = listOf(
+ parseNumericAddress("192.0.2.23"),
+ parseNumericAddress("2001:db8::1"))
+ }
+ doReturn("MyTestHost")
+ .`when`(repository).getHostnameForServiceId(TEST_SERVICE_ID_1)
+ doReturn("MyTestHost")
+ .`when`(repository).getHostnameForServiceId(TEST_SERVICE_ID_2)
+ val announcementInfo1 =
+ addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1_CUSTOM_HOST)
+
+ val probingInfo2 = addServiceAndStartProbing(TEST_SERVICE_ID_2, customHost)
+ val announcementInfo2 = AnnouncementInfo(TEST_SERVICE_ID_2, emptyList(), emptyList())
+ doReturn(announcementInfo2).`when`(repository).onProbingSucceeded(probingInfo2)
+ doReturn(listOf(announcementInfo1, announcementInfo2))
+ .`when`(repository).restartAnnouncingForHostname("MyTestHost")
+ probeCb.onFinished(probingInfo2)
+
+ val inOrder = inOrder(prober, announcer)
+
+ inOrder.verify(announcer)
+ .startSending(TEST_SERVICE_ID_2, announcementInfo2, 0L /* initialDelayMs */)
+ inOrder.verify(announcer).stop(TEST_SERVICE_ID_1)
+ inOrder.verify(announcer)
+ .startSending(TEST_SERVICE_ID_1, announcementInfo1, 0L /* initialDelayMs */)
}
@Test
@@ -489,8 +517,8 @@
verify(prober, never()).startProbing(any())
}
- private fun addServiceAndFinishProbing(serviceId: Int, serviceInfo: NsdServiceInfo):
- AnnouncementInfo {
+ private fun addServiceAndStartProbing(serviceId: Int, serviceInfo: NsdServiceInfo):
+ ProbingInfo {
val testProbingInfo = mock(ProbingInfo::class.java)
doReturn(serviceId).`when`(testProbingInfo).serviceId
doReturn(testProbingInfo).`when`(repository).setServiceProbing(serviceId)
@@ -499,8 +527,15 @@
verify(repository).addService(serviceId, serviceInfo, null /* ttl */)
verify(prober).startProbing(testProbingInfo)
+ return testProbingInfo
+ }
+
+ private fun addServiceAndFinishProbing(serviceId: Int, serviceInfo: NsdServiceInfo):
+ AnnouncementInfo {
+ val testProbingInfo = addServiceAndStartProbing(serviceId, serviceInfo)
+
// Simulate probing success: continues to announcing
- val testAnnouncementInfo = mock(AnnouncementInfo::class.java)
+ val testAnnouncementInfo = AnnouncementInfo(serviceId, emptyList(), emptyList())
doReturn(testAnnouncementInfo).`when`(repository).onProbingSucceeded(testProbingInfo)
probeCb.onFinished(testProbingInfo)
return testAnnouncementInfo
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
index f705bcb..b1a7233 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
@@ -17,6 +17,13 @@
package com.android.server.connectivity.mdns.util
import android.os.Build
+import com.android.server.connectivity.mdns.MdnsConstants
+import com.android.server.connectivity.mdns.MdnsConstants.FLAG_TRUNCATED
+import com.android.server.connectivity.mdns.MdnsPacket
+import com.android.server.connectivity.mdns.MdnsPacketReader
+import com.android.server.connectivity.mdns.MdnsPointerRecord
+import com.android.server.connectivity.mdns.MdnsRecord
+import com.android.server.connectivity.mdns.util.MdnsUtils.createQueryDatagramPackets
import com.android.server.connectivity.mdns.util.MdnsUtils.equalsDnsLabelIgnoreDnsCase
import com.android.server.connectivity.mdns.util.MdnsUtils.equalsIgnoreDnsCase
import com.android.server.connectivity.mdns.util.MdnsUtils.toDnsLabelsLowerCase
@@ -24,6 +31,8 @@
import com.android.server.connectivity.mdns.util.MdnsUtils.truncateServiceName
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
+import java.net.DatagramPacket
+import kotlin.test.assertContentEquals
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -102,4 +111,67 @@
arrayOf("a", "_other", "_type", "_tcp", "local"),
arrayOf("a", "_SUB", "_type", "_TCP", "local")))
}
+
+ @Test
+ fun testCreateQueryDatagramPackets() {
+ // Question data bytes:
+ // Name label(17)(duplicated labels) + PTR type(2) + cacheFlush(2) = 21
+ //
+ // Known answers data bytes:
+ // Name label(17)(duplicated labels) + PTR type(2) + cacheFlush(2) + receiptTimeMillis(4)
+ // + Data length(2) + Pointer data(18)(duplicated labels) = 45
+ val questions = mutableListOf<MdnsRecord>()
+ val knownAnswers = mutableListOf<MdnsRecord>()
+ for (i in 1..100) {
+ questions.add(MdnsPointerRecord(arrayOf("_testservice$i", "_tcp", "local"), false))
+ knownAnswers.add(MdnsPointerRecord(
+ arrayOf("_testservice$i", "_tcp", "local"),
+ 0L,
+ false,
+ 4_500_000L,
+ arrayOf("MyTestService$i", "_testservice$i", "_tcp", "local")
+ ))
+ }
+ // MdnsPacket data bytes:
+ // Questions(21 * 100) + Answers(45 * 100) = 6600 -> at least 5 packets
+ val query = MdnsPacket(
+ MdnsConstants.FLAGS_QUERY,
+ questions as List<MdnsRecord>,
+ knownAnswers as List<MdnsRecord>,
+ emptyList(),
+ emptyList()
+ )
+ // Expect the oversize MdnsPacket to be separated into 5 DatagramPackets.
+ val bufferSize = 1500
+ val packets = createQueryDatagramPackets(
+ ByteArray(bufferSize),
+ query,
+ MdnsConstants.IPV4_SOCKET_ADDR
+ )
+ assertEquals(5, packets.size)
+ assertTrue(packets.all { packet -> packet.length < bufferSize })
+
+ val mdnsPacket = createMdnsPacketFromMultipleDatagramPackets(packets)
+ assertEquals(query.flags, mdnsPacket.flags)
+ assertContentEquals(query.questions, mdnsPacket.questions)
+ assertContentEquals(query.answers, mdnsPacket.answers)
+ }
+
+ private fun createMdnsPacketFromMultipleDatagramPackets(
+ packets: List<DatagramPacket>
+ ): MdnsPacket {
+ var flags = 0
+ val questions = mutableListOf<MdnsRecord>()
+ val answers = mutableListOf<MdnsRecord>()
+ for ((index, packet) in packets.withIndex()) {
+ val mdnsPacket = MdnsPacket.parse(MdnsPacketReader(packet))
+ if (index != packets.size - 1) {
+ assertTrue((mdnsPacket.flags and FLAG_TRUNCATED) == FLAG_TRUNCATED)
+ }
+ flags = mdnsPacket.flags
+ questions.addAll(mdnsPacket.questions)
+ answers.addAll(mdnsPacket.answers)
+ }
+ return MdnsPacket(flags, questions, answers, emptyList(), emptyList())
+ }
}
diff --git a/thread/framework/java/android/net/thread/ActiveOperationalDataset.java b/thread/framework/java/android/net/thread/ActiveOperationalDataset.java
index 4a7d3a7..22457f5 100644
--- a/thread/framework/java/android/net/thread/ActiveOperationalDataset.java
+++ b/thread/framework/java/android/net/thread/ActiveOperationalDataset.java
@@ -18,7 +18,7 @@
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkState;
-import static com.android.net.module.util.HexDump.dumpHexString;
+import static com.android.net.module.util.HexDump.toHexString;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
@@ -610,7 +610,7 @@
sb.append("{networkName=")
.append(getNetworkName())
.append(", extendedPanId=")
- .append(dumpHexString(getExtendedPanId()))
+ .append(toHexString(getExtendedPanId()))
.append(", panId=")
.append(getPanId())
.append(", channel=")
@@ -1109,7 +1109,7 @@
sb.append("{rotation=")
.append(mRotationTimeHours)
.append(", flags=")
- .append(dumpHexString(mFlags))
+ .append(toHexString(mFlags))
.append("}");
return sb.toString();
}