From b3822771702e3fccedf94049bfc28cd7ee994951 Mon Sep 17 00:00:00 2001 From: Austin Hsieh <77706079+austinh0@users.noreply.github.com> Date: Tue, 6 Apr 2021 18:29:59 -0700 Subject: [PATCH] Throw an exception when SetupPayloadParser scans a non-CHIP QR or entry code. (#5676) --- .../google/chip/chiptool/CHIPToolActivity.kt | 20 +++- .../setuppayloadscanner/BarcodeFragment.kt | 25 ++++- .../java/SetupPayloadParser-JNI.cpp | 97 ++++++++++++++++--- .../chip/setuppayload/SetupPayloadParser.java | 27 +++++- 4 files changed, 147 insertions(+), 22 deletions(-) diff --git a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/CHIPToolActivity.kt b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/CHIPToolActivity.kt index cfe38e8d5d99cd..80b081d997307c 100644 --- a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/CHIPToolActivity.kt +++ b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/CHIPToolActivity.kt @@ -21,10 +21,12 @@ import android.content.Intent import android.nfc.NdefMessage import android.nfc.NfcAdapter import android.os.Bundle +import android.util.Log +import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import chip.setuppayload.SetupPayloadParser +import chip.setuppayload.SetupPayloadParser.UnrecognizedQrCodeException import com.google.chip.chiptool.attestation.AttestationTestFragment import com.google.chip.chiptool.clusterclient.OnOffClientFragment import com.google.chip.chiptool.commissioner.CommissionerActivity @@ -36,6 +38,8 @@ import com.google.chip.chiptool.setuppayloadscanner.CHIPDeviceDetailsFragment import com.google.chip.chiptool.setuppayloadscanner.CHIPDeviceInfo import com.google.chip.chiptool.setuppayloadscanner.QrCodeInfo import chip.devicecontroller.PersistentStorage +import chip.setuppayload.SetupPayload +import chip.setuppayload.SetupPayloadParser class CHIPToolActivity : AppCompatActivity(), @@ -148,8 +152,17 @@ class CHIPToolActivity : val uri = records[0].toUri() if (!uri?.scheme.equals("ch", true)) return - // TODO: Issue #4504 - Remove replacing _ with spaces after problem described in #415 will be fixed. - val setupPayload = SetupPayloadParser().parseQrCode(uri.toString().toUpperCase().replace('_', ' ')) + lateinit var setupPayload: SetupPayload + try { + // TODO: Issue #4504 - Remove replacing _ with spaces after problem described in #415 will be fixed. + setupPayload = + SetupPayloadParser().parseQrCode(uri.toString().toUpperCase().replace('_', ' ')) + } catch (ex: UnrecognizedQrCodeException) { + Log.e(TAG, "Unrecognized QR Code", ex) + Toast.makeText(this, "Unrecognized QR Code", Toast.LENGTH_SHORT).show() + return + } + val deviceInfo = CHIPDeviceInfo( setupPayload.version, setupPayload.vendorId, @@ -179,6 +192,7 @@ class CHIPToolActivity : } companion object { + private const val TAG = "CHIPToolActivity" private const val ARG_PROVISION_NETWORK_TYPE = "provision_network_type" var REQUEST_CODE_COMMISSIONING = 0xB003 diff --git a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/BarcodeFragment.kt b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/BarcodeFragment.kt index 87a27e43de7ec9..220d0b89aac6c1 100644 --- a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/BarcodeFragment.kt +++ b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/setuppayloadscanner/BarcodeFragment.kt @@ -28,11 +28,14 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.annotation.RequiresPermission import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat.checkSelfPermission import androidx.fragment.app.Fragment +import chip.setuppayload.SetupPayload import chip.setuppayload.SetupPayloadParser +import chip.setuppayload.SetupPayloadParser.UnrecognizedQrCodeException import com.google.android.gms.vision.CameraSource import com.google.android.gms.vision.barcode.Barcode import com.google.android.gms.vision.barcode.BarcodeDetector @@ -112,20 +115,36 @@ class BarcodeFragment : Fragment(), CHIPBarcodeProcessor.BarcodeDetectionListene } } + @SuppressLint("MissingPermission") override fun handleScannedQrCode(barcode: Barcode) { Handler(Looper.getMainLooper()).post { stopCamera() - val payload = SetupPayloadParser().parseQrCode(barcode.displayValue) + lateinit var payload: SetupPayload + try { + payload = SetupPayloadParser().parseQrCode(barcode.displayValue) + } catch (ex: UnrecognizedQrCodeException) { + Log.e(TAG, "Unrecognized QR Code", ex) + Toast.makeText(requireContext(), "Unrecognized QR Code", Toast.LENGTH_SHORT).show() + + // Restart camera view. + if (hasCameraPermission() && !cameraStarted) { + startCamera() + } + return@post + } val deviceInfo = CHIPDeviceInfo( payload.version, payload.vendorId, payload.productId, payload.discriminator, payload.setupPinCode, - payload.optionalQRCodeInfo.mapValues { (_, info) -> QrCodeInfo(info.tag, info.type, info.data, info.int32) } + payload.optionalQRCodeInfo.mapValues { (_, info) -> + QrCodeInfo(info.tag, info.type, info.data, info.int32) + } ) - FragmentUtil.getHost(this, Callback::class.java)?.onCHIPDeviceInfoReceived(deviceInfo) + FragmentUtil.getHost(this, Callback::class.java) + ?.onCHIPDeviceInfoReceived(deviceInfo) } } diff --git a/src/setup_payload/java/SetupPayloadParser-JNI.cpp b/src/setup_payload/java/SetupPayloadParser-JNI.cpp index b1a22175a14cf0..23779f61a1ecd5 100644 --- a/src/setup_payload/java/SetupPayloadParser-JNI.cpp +++ b/src/setup_payload/java/SetupPayloadParser-JNI.cpp @@ -2,6 +2,7 @@ #include "QRCodeSetupPayloadParser.h" #include +#include #include #include @@ -10,9 +11,21 @@ using namespace chip; +#define SETUP_PAYLOAD_PARSER_JNI_ERROR_MIN 10000 +#define SETUP_PAYLOAD_PARSER_JNI_ERROR_MAX 10999 + +#define _SETUP_PAYLOAD_PARSER_JNI_ERROR(e) (SETUP_PAYLOAD_PARSER_JNI_ERROR_MIN + (e)) + +#define SETUP_PAYLOAD_PARSER_JNI_ERROR_EXCEPTION_THROWN _SETUP_PAYLOAD_PARSER_JNI_ERROR(0) +#define SETUP_PAYLOAD_PARSER_JNI_ERROR_TYPE_NOT_FOUND _SETUP_PAYLOAD_PARSER_JNI_ERROR(1) +#define SETUP_PAYLOAD_PARSER_JNI_ERROR_METHOD_NOT_FOUND _SETUP_PAYLOAD_PARSER_JNI_ERROR(2) +#define SETUP_PAYLOAD_PARSER_JNI_ERROR_FIELD_NOT_FOUND _SETUP_PAYLOAD_PARSER_JNI_ERROR(3) + #define JNI_METHOD(RETURN, METHOD_NAME) extern "C" JNIEXPORT RETURN JNICALL Java_chip_setuppayload_SetupPayloadParser_##METHOD_NAME static jobject TransformSetupPayload(JNIEnv * env, SetupPayload & payload); +static CHIP_ERROR ThrowUnrecognizedQRCodeException(JNIEnv * env, jstring qrCodeObj); +static CHIP_ERROR ThrowInvalidEntryCodeFormatException(JNIEnv * env, jstring entryCodeObj); jint JNI_OnLoad(JavaVM * jvm, void * reserved) { @@ -23,30 +36,48 @@ jint JNI_OnLoad(JavaVM * jvm, void * reserved) JNI_METHOD(jobject, fetchPayloadFromQrCode)(JNIEnv * env, jobject self, jstring qrCodeObj) { - CHIP_ERROR err; - const char * qrString = NULL; - QRCodeSetupPayloadParser * qrPayloadParser = NULL; + CHIP_ERROR err = CHIP_NO_ERROR; + const char * qrString = NULL; SetupPayload payload; - qrString = env->GetStringUTFChars(qrCodeObj, 0); - qrPayloadParser = new QRCodeSetupPayloadParser(qrString); + qrString = env->GetStringUTFChars(qrCodeObj, 0); - err = qrPayloadParser->populatePayload(payload); + err = QRCodeSetupPayloadParser(qrString).populatePayload(payload); + env->ReleaseStringUTFChars(qrCodeObj, qrString); + + if (err != CHIP_NO_ERROR) + { + err = ThrowUnrecognizedQRCodeException(env, qrCodeObj); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SetupPayload, "Error throwing UnrecognizedQRCodeException: %d", err); + } + return nullptr; + } return TransformSetupPayload(env, payload); } JNI_METHOD(jobject, fetchPayloadFromManualEntryCode)(JNIEnv * env, jobject self, jstring entryCode) { - CHIP_ERROR err; - const char * entryCodeString = NULL; - ManualSetupPayloadParser * manualSetupPayloadParser = NULL; + CHIP_ERROR err = CHIP_NO_ERROR; + const char * entryCodeString = NULL; SetupPayload payload; - entryCodeString = env->GetStringUTFChars(entryCode, 0); - manualSetupPayloadParser = new ManualSetupPayloadParser(entryCodeString); + entryCodeString = env->GetStringUTFChars(entryCode, 0); + env->ReleaseStringUTFChars(entryCode, entryCodeString); - err = manualSetupPayloadParser->populatePayload(payload); + err = ManualSetupPayloadParser(entryCodeString).populatePayload(payload); + + if (err != CHIP_NO_ERROR) + { + err = ThrowInvalidEntryCodeFormatException(env, entryCode); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SetupPayload, "Error throwing InvalidEntryCodeFormatException: %d", err); + } + return nullptr; + } return TransformSetupPayload(env, payload); } @@ -131,3 +162,45 @@ jobject TransformSetupPayload(JNIEnv * env, SetupPayload & payload) return setupPayload; } + +CHIP_ERROR ThrowUnrecognizedQRCodeException(JNIEnv * env, jstring qrCodeObj) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + jclass exceptionCls = nullptr; + jmethodID exceptionConstructor = nullptr; + jthrowable exception = nullptr; + + env->ExceptionClear(); + + exceptionCls = env->FindClass("chip/setuppayload/SetupPayloadParser$UnrecognizedQrCodeException"); + VerifyOrExit(exceptionCls != NULL, err = SETUP_PAYLOAD_PARSER_JNI_ERROR_TYPE_NOT_FOUND); + exceptionConstructor = env->GetMethodID(exceptionCls, "", "(Ljava/lang/String;)V"); + VerifyOrExit(exceptionConstructor != NULL, err = SETUP_PAYLOAD_PARSER_JNI_ERROR_METHOD_NOT_FOUND); + exception = (jthrowable) env->NewObject(exceptionCls, exceptionConstructor, qrCodeObj); + VerifyOrExit(exception != NULL, err = SETUP_PAYLOAD_PARSER_JNI_ERROR_EXCEPTION_THROWN); + + env->Throw(exception); +exit: + return err; +} + +CHIP_ERROR ThrowInvalidEntryCodeFormatException(JNIEnv * env, jstring entryCodeObj) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + jclass exceptionCls = nullptr; + jmethodID exceptionConstructor = nullptr; + jthrowable exception = nullptr; + + env->ExceptionClear(); + + exceptionCls = env->FindClass("chip/setuppayload/SetupPayloadParser$InvalidEntryCodeFormatException"); + VerifyOrExit(exceptionCls != NULL, err = SETUP_PAYLOAD_PARSER_JNI_ERROR_TYPE_NOT_FOUND); + exceptionConstructor = env->GetMethodID(exceptionCls, "", "(Ljava/lang/String;)V"); + VerifyOrExit(exceptionConstructor != NULL, err = SETUP_PAYLOAD_PARSER_JNI_ERROR_METHOD_NOT_FOUND); + exception = (jthrowable) env->NewObject(exceptionCls, exceptionConstructor, entryCodeObj); + VerifyOrExit(exception != NULL, err = SETUP_PAYLOAD_PARSER_JNI_ERROR_EXCEPTION_THROWN); + + env->Throw(exception); +exit: + return err; +} diff --git a/src/setup_payload/java/src/chip/setuppayload/SetupPayloadParser.java b/src/setup_payload/java/src/chip/setuppayload/SetupPayloadParser.java index cffdbefe642832..12774081447f5d 100644 --- a/src/setup_payload/java/src/chip/setuppayload/SetupPayloadParser.java +++ b/src/setup_payload/java/src/chip/setuppayload/SetupPayloadParser.java @@ -4,20 +4,39 @@ public class SetupPayloadParser { /** Returns {@link SetupPayload} parsed from the QR code string. */ - public SetupPayload parseQrCode(String qrCodeString) { + public SetupPayload parseQrCode(String qrCodeString) throws UnrecognizedQrCodeException { return fetchPayloadFromQrCode(qrCodeString); } /** Returns {@link SetupPayload} parsed from the manual entry code string. */ - public SetupPayload parseManualEntryCode(String entryCodeString) { + public SetupPayload parseManualEntryCode(String entryCodeString) + throws InvalidEntryCodeFormatException { return fetchPayloadFromManualEntryCode(entryCodeString); } - private native SetupPayload fetchPayloadFromQrCode(String qrCodeString); + private native SetupPayload fetchPayloadFromQrCode(String qrCodeString) + throws UnrecognizedQrCodeException; - private native SetupPayload fetchPayloadFromManualEntryCode(String entryCodeString); + private native SetupPayload fetchPayloadFromManualEntryCode(String entryCodeString) + throws InvalidEntryCodeFormatException; static { System.loadLibrary("SetupPayloadParser"); } + + public static class UnrecognizedQrCodeException extends Exception { + private static final long serialVersionUID = 1L; + + public UnrecognizedQrCodeException(String qrCode) { + super(String.format("Invalid QR code string: %s", qrCode), null); + } + } + + public static class InvalidEntryCodeFormatException extends Exception { + private static final long serialVersionUID = 1L; + + public InvalidEntryCodeFormatException(String entryCode) { + super(String.format("Invalid format for entry code string: %s", entryCode), null); + } + } }