From 2532530bd6a908ff0df7d5626c4b5fd7731ab504 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Fri, 26 May 2023 23:48:47 -0400 Subject: [PATCH] Make java UTF-8 decode more lenient (#26886) * Start asking for replace in the java UTF logic * Fix exception reporting: make sure exception is cleared, print out a nice stack trace * Restyle * Fix for compilation * Fix signature for repolace action field * Restyled by clang-format --------- Co-authored-by: Andrei Litvin Co-authored-by: Restyled.io --- src/lib/support/JniReferences.cpp | 33 ++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/lib/support/JniReferences.cpp b/src/lib/support/JniReferences.cpp index 66c16eb74dc70a..de61a98d74641d 100644 --- a/src/lib/support/JniReferences.cpp +++ b/src/lib/support/JniReferences.cpp @@ -402,6 +402,29 @@ CHIP_ERROR JniReferences::CharToStringUTF(const chip::CharSpan & charSpan, jobje jmethodID newDocoderMethod = env->GetMethodID(charSetClass, "newDecoder", "()Ljava/nio/charset/CharsetDecoder;"); jobject decoderObject = env->CallObjectMethod(charsetObject, newDocoderMethod); + // Even though spec requires UTF-8 strings, we have seen instances in the field of certified devices sending + // invalid strings like "startup?" (0x73 0x74 0x61 0x72 0x74 0x75 0x70 <0x91>) and we want to actually + // be lenient on those rather than failing an entire decode (which may fail an entire report for one invalid string, + // like in a very common 'subscribe *') + // + // As a result call: + // onMalformedInput(CodingErrorAction.REPLACE) + // onUnmappableCharacter(CodingErrorAction.REPLACE) + jclass codingErrorActionClass = env->FindClass("java/nio/charset/CodingErrorAction"); + jobject replaceAction = env->GetStaticObjectField( + codingErrorActionClass, env->GetStaticFieldID(codingErrorActionClass, "REPLACE", "Ljava/nio/charset/CodingErrorAction;")); + { + jmethodID onMalformedInput = env->GetMethodID(charSetDocoderClass, "onMalformedInput", + "(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;"); + decoderObject = env->CallObjectMethod(decoderObject, onMalformedInput, replaceAction); + } + { + jmethodID onUnmappableCharacter = + env->GetMethodID(charSetDocoderClass, "onUnmappableCharacter", + "(Ljava/nio/charset/CodingErrorAction;)Ljava/nio/charset/CharsetDecoder;"); + decoderObject = env->CallObjectMethod(decoderObject, onUnmappableCharacter, replaceAction); + } + jmethodID charSetDecodeMethod = env->GetMethodID(charSetDocoderClass, "decode", "(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;"); jobject decodeObject = env->CallObjectMethod(decoderObject, charSetDecodeMethod, jbyteBuffer); env->DeleteLocalRef(jbyteBuffer); @@ -409,7 +432,15 @@ CHIP_ERROR JniReferences::CharToStringUTF(const chip::CharSpan & charSpan, jobje // If decode exception occur, outStr will be set null. outStr = nullptr; - VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN); + if (env->ExceptionCheck()) + { + // If there is an exception, decode will not fail. Instead just + // an error will be reported. + ChipLogError(Support, "Exception encountered trying to decode a UTF string."); + env->ExceptionDescribe(); + env->ExceptionClear(); + return CHIP_JNI_ERROR_EXCEPTION_THROWN; + } jclass charBufferClass = env->FindClass("java/nio/CharBuffer"); jmethodID charBufferToString = env->GetMethodID(charBufferClass, "toString", "()Ljava/lang/String;");