From aed9d401abef8dca02ed9ffebb727c995e8d33f2 Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Sun, 10 Dec 2023 13:16:47 +0100 Subject: [PATCH] Reader: Show transaction times. This shows how a transaction takes, from the reader's perspective. We show both the total and also broken down in various phases. Test: Manually tested. Test: All unit tests pass. Signed-off-by: David Zeuthen --- .../fragment/ShowDocumentFragment.kt | 28 +++++-- .../mdl/appreader/transfer/TransferManager.kt | 23 +++++ .../deviceretrieval/VerificationHelper.java | 84 +++++++++++++++++-- 3 files changed, 119 insertions(+), 16 deletions(-) diff --git a/appverifier/src/main/java/com/android/mdl/appreader/fragment/ShowDocumentFragment.kt b/appverifier/src/main/java/com/android/mdl/appreader/fragment/ShowDocumentFragment.kt index c1717de00..6905863a5 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/fragment/ShowDocumentFragment.kt +++ b/appverifier/src/main/java/com/android/mdl/appreader/fragment/ShowDocumentFragment.kt @@ -19,7 +19,6 @@ import androidx.navigation.fragment.findNavController import com.android.identity.internal.Util import com.android.identity.mdoc.response.DeviceResponseParser import com.android.identity.securearea.SecureArea -import com.android.identity.securearea.SecureArea.EcCurve import com.android.mdl.appreader.R import com.android.mdl.appreader.databinding.FragmentShowDocumentBinding import com.android.mdl.appreader.issuerauth.SimpleIssuerTrustStore @@ -195,17 +194,28 @@ class ShowDocumentFragment : Fragment() { } } - sb.append("Number of documents returned: ${documents.size}
") - sb.append("Address: " + transferManager.mdocConnectionMethod + "
") + // Get primary color from theme to use in the HTML formatted document. + val primaryColor = String.format( + "#%06X", + 0xFFFFFF and requireContext().theme.attr(R.attr.colorPrimary).data + ) + + val totalDuration = transferManager.getTapToEngagementDurationMillis() + + transferManager.getEngagementToRequestDurationMillis() + + transferManager.getRequestToResponseDurationMillis() + sb.append("Tap to Engagement Received: ${transferManager.getTapToEngagementDurationMillis()} ms
") + sb.append("Engagement Received to Request Sent: ${transferManager.getEngagementToRequestDurationMillis()} ms
") + sb.append("Request Sent to Response Received: ${transferManager.getRequestToResponseDurationMillis()} ms
") + sb.append("Total transaction time: $totalDuration ms
") + sb.append("
") + + sb.append("Engagement Method: " + transferManager.getEngagementMethod() + "
") + sb.append("Device Retrieval Method: " + transferManager.mdocConnectionMethod + "
") sb.append("Session encryption curve: " + curveNameFor(transferManager.getMdocSessionEncryptionCurve()) + "
") sb.append("
") + for (doc in documents) { - // Get primary color from theme to use in the HTML formatted document. - val color = String.format( - "#%06X", - 0xFFFFFF and requireContext().theme.attr(R.attr.colorPrimary).data - ) - sb.append("

Doctype: ${doc.docType}

") + sb.append("

Doctype: ${doc.docType}

") val certPath = simpleIssuerTrustStore.createCertificationTrustPath(doc.issuerCertificateChain.toList()) val isDSTrusted = simpleIssuerTrustStore.validateCertificationTrustPath(certPath) diff --git a/appverifier/src/main/java/com/android/mdl/appreader/transfer/TransferManager.kt b/appverifier/src/main/java/com/android/mdl/appreader/transfer/TransferManager.kt index f4060d71c..551a6741a 100644 --- a/appverifier/src/main/java/com/android/mdl/appreader/transfer/TransferManager.kt +++ b/appverifier/src/main/java/com/android/mdl/appreader/transfer/TransferManager.kt @@ -362,4 +362,27 @@ class TransferManager private constructor(private val context: Context) { fun getMdocSessionEncryptionCurve(): Int { return Util.getCurve(verification!!.eReaderKeyPair.public) } + + fun getTapToEngagementDurationMillis(): Long { + return verification?.tapToEngagementDurationMillis ?: 0 + } + + fun getEngagementToRequestDurationMillis(): Long { + return verification?.engagementToRequestDurationMillis ?: 0 + } + + fun getRequestToResponseDurationMillis(): Long { + return verification?.requestToResponseDurationMillis ?: 0 + } + + fun getEngagementMethod(): String { + when (verification?.engagementMethod) { + VerificationHelper.ENGAGEMENT_METHOD_QR_CODE -> return "QR Code" + VerificationHelper.ENGAGEMENT_METHOD_NFC_STATIC_HANDOVER -> return "NFC Static Handover" + VerificationHelper.ENGAGEMENT_METHOD_NFC_NEGOTIATED_HANDOVER -> return "NFC Negotiated Handover" + VerificationHelper.ENGAGEMENT_METHOD_REVERSE -> return "Reverse" + } + return "N/A" + } + } \ No newline at end of file diff --git a/identity-android/src/main/java/com/android/identity/android/mdoc/deviceretrieval/VerificationHelper.java b/identity-android/src/main/java/com/android/identity/android/mdoc/deviceretrieval/VerificationHelper.java index 90c71c65e..465820573 100644 --- a/identity-android/src/main/java/com/android/identity/android/mdoc/deviceretrieval/VerificationHelper.java +++ b/identity-android/src/main/java/com/android/identity/android/mdoc/deviceretrieval/VerificationHelper.java @@ -28,9 +28,9 @@ import android.util.Base64; import android.util.Log; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; -import com.android.identity.securearea.SecureArea; import com.android.identity.mdoc.sessionencryption.SessionEncryption; import com.android.identity.android.mdoc.transport.DataTransport; import com.android.identity.android.mdoc.transport.DataTransportBle; @@ -44,13 +44,16 @@ import com.android.identity.mdoc.engagement.EngagementParser; import com.android.identity.mdoc.request.DeviceRequestGenerator; import com.android.identity.mdoc.response.DeviceResponseParser; +import com.android.identity.securearea.SecureArea; import com.android.identity.util.Constants; import com.android.identity.util.Logger; import com.android.identity.internal.Util; +import com.android.identity.util.Timestamp; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.security.KeyPair; -import java.security.PrivateKey; import java.security.PublicKey; import java.util.ArrayList; import java.util.Arrays; @@ -80,6 +83,24 @@ public class VerificationHelper { private static final String TAG = "VerificationHelper"; + + public static final int ENGAGEMENT_METHOD_NOT_ENGAGED = 0; + public static final int ENGAGEMENT_METHOD_QR_CODE = 1; + public static final int ENGAGEMENT_METHOD_NFC_STATIC_HANDOVER = 2; + public static final int ENGAGEMENT_METHOD_NFC_NEGOTIATED_HANDOVER = 3; + public static final int ENGAGEMENT_METHOD_REVERSE = 4; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, + value = { + ENGAGEMENT_METHOD_NOT_ENGAGED, + ENGAGEMENT_METHOD_QR_CODE, + ENGAGEMENT_METHOD_NFC_STATIC_HANDOVER, + ENGAGEMENT_METHOD_NFC_NEGOTIATED_HANDOVER, + ENGAGEMENT_METHOD_REVERSE + }) + @interface EngagementMethod {} + private Context mContext; DataTransport mDataTransport; Listener mListener; @@ -103,6 +124,12 @@ public class VerificationHelper { private List mConnectionMethodsForReaderEngagement; private EngagementGenerator mReaderEngagementGenerator; private byte[] mReaderEngagement; + private long mTimestampNfcTap; + private long mTimestampEngagementReceived; + private long mTimestampRequestSent; + private long mTimestampResponseReceived; + private @EngagementMethod int mEngagementMethod; + VerificationHelper() { } @@ -255,6 +282,8 @@ void reverseEngagementPeerHasConnected(@NonNull DataTransport transport) { nfcProcessOnTagDiscovered(@NonNull Tag tag) { Logger.d(TAG, "Tag discovered!"); + mTimestampNfcTap = Timestamp.now().toEpochMilli(); + // Find IsoDep since we're skipping NDEF checks and doing everything ourselves via APDUs for (String tech : tag.getTechList()) { if (tech.equals(IsoDep.class.getName())) { @@ -331,7 +360,7 @@ public void setDeviceEngagementFromQrCode(@NonNull String qrDeviceEngagement) { Logger.dCbor(TAG, "Device Engagement from QR code", encodedDeviceEngagement); DataItem handover = SimpleValue.NULL; - setDeviceEngagement(encodedDeviceEngagement, handover); + setDeviceEngagement(encodedDeviceEngagement, handover, ENGAGEMENT_METHOD_QR_CODE); EngagementParser engagementParser = new EngagementParser(encodedDeviceEngagement); EngagementParser.Engagement engagement = engagementParser.parse(); @@ -612,7 +641,7 @@ public void run() { .add(SimpleValue.NULL) // Handover Request message .end() .build().get(0); - setDeviceEngagement(hs.encodedDeviceEngagement, readerHandover); + setDeviceEngagement(hs.encodedDeviceEngagement, readerHandover, ENGAGEMENT_METHOD_NFC_STATIC_HANDOVER); reportDeviceEngagementReceived(hs.connectionMethods); return; } @@ -705,7 +734,7 @@ public void run() { .add(hrMessage) // Handover Request message .end() .build().get(0); - setDeviceEngagement(encodedDeviceEngagement, handover); + setDeviceEngagement(encodedDeviceEngagement, handover, ENGAGEMENT_METHOD_NFC_NEGOTIATED_HANDOVER); reportDeviceEngagementReceived(parsedCms); @@ -717,11 +746,16 @@ public void run() { transceiverThread.start(); } - private void setDeviceEngagement(@NonNull byte[] deviceEngagement, @NonNull DataItem handover) { + private void setDeviceEngagement( + @NonNull byte[] deviceEngagement, + @NonNull DataItem handover, + @EngagementMethod int engagementMethod) { if (mDeviceEngagement != null) { throw new IllegalStateException("Device Engagement already set"); } mDeviceEngagement = deviceEngagement; + mEngagementMethod = engagementMethod; + mTimestampEngagementReceived = Timestamp.now().toEpochMilli(); EngagementParser engagementParser = new EngagementParser(deviceEngagement); EngagementParser.Engagement engagement = engagementParser.parse(); @@ -871,7 +905,7 @@ private void handleReverseEngagementMessageData(@NonNull byte[] data) { // is available and neither QR or NFC is used. handover = Util.cborBuildTaggedByteString(mReaderEngagement); - setDeviceEngagement(encodedDeviceEngagement, handover); + setDeviceEngagement(encodedDeviceEngagement, handover, ENGAGEMENT_METHOD_REVERSE); // Tell the application it can start sending requests... reportDeviceConnected(); @@ -911,6 +945,7 @@ private void handleOnMessageReceived() { // if (decryptedMessage.getData() != null) { Logger.dCbor(TAG, "DeviceResponse received", decryptedMessage.getData()); + mTimestampResponseReceived = Timestamp.now().toEpochMilli(); reportResponseReceived(decryptedMessage.getData()); } else { // No data, so status must be set... @@ -1080,6 +1115,7 @@ public void sendRequest(@NonNull byte[] deviceRequestBytes) { deviceRequestBytes, OptionalLong.empty()); Logger.dCbor(TAG, "SessionData to send", message); mDataTransport.sendMessage(message); + mTimestampRequestSent = Timestamp.now().toEpochMilli(); } /** @@ -1165,6 +1201,40 @@ public void setSendSessionTerminationMessage( mSendSessionTerminationMessage = sendSessionTerminationMessage; } + /** + * Gets the amount of time from first NFC interaction until Engagement has been received. + * + * @return The duration, in milliseconds or 0 if NFC engagement isn't in use. + */ + public long getTapToEngagementDurationMillis() { + if (mTimestampNfcTap == 0) { + return 0; + } + return mTimestampEngagementReceived - mTimestampNfcTap; + } + + /** + * Gets the amount of time from when Engagement has been received until the request was sent. + * + * @return The duration, in milliseconds. + */ + public long getEngagementToRequestDurationMillis() { + return mTimestampRequestSent - mTimestampEngagementReceived; + } + + /** + * Gets the the amount of time from when the request was sent until the response was received. + * + * @return The duration, in milliseconds. + */ + public long getRequestToResponseDurationMillis() { + return mTimestampResponseReceived - mTimestampRequestSent; + } + + public @EngagementMethod int getEngagementMethod() { + return mEngagementMethod; + } + /** * Interface for listening to messages from the remote mdoc device. */