// Copyright 2020 The Pigweed Authors // // 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 // // https://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. /** * This is a Java Native Interface (JNI) wrapper for the Detokenizer class. * *

This classes uses the Android Base64 library instead of the standard Java Base64, which is not * yet available on Android. To use this class outside of Android, replace android.util.Base64 with * java.util.Base64. */ package dev.pigweed.tokenizer; import android.util.Base64; import java.util.regex.Matcher; import java.util.regex.Pattern; /** This class provides the Java interface for the C++ Detokenizer class. */ public class Detokenizer { // Android's Base64 library doesn't seem to check if the Base64-encoded data is valid. This // regular expression checks that it is. Does not match URL-safe or unpadded Base64. private static final Pattern TOKENIZED_STRING = Pattern.compile("\\$([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?"); static { System.loadLibrary("detokenizer"); } // The handle to the C++ detokenizer instance. private final long handle; public Detokenizer() { this(new byte[0]); } public Detokenizer(byte[] tokenDatabase) { handle = newNativeDetokenizer(tokenDatabase); } /** * Detokenizes and replaces all recognized tokenized messages ($ followed by Base64) in the * provided string. Unrecognized tokenized strings are left unchanged. */ public String detokenize(String message) { Matcher matcher = TOKENIZED_STRING.matcher(message); StringBuilder result = new StringBuilder(); int lastIndex = 0; while (matcher.find()) { result.append(message, lastIndex, matcher.start()); String decoded = detokenizeNative(handle, Base64.decode(matcher.group().substring(1), Base64.DEFAULT)); result.append(decoded != null ? decoded : matcher.group()); lastIndex = matcher.end(); } result.append(message, lastIndex, message.length()); return result.toString(); } /** Deletes memory allocated in C++ when this class is garbage collected. */ @Override protected void finalize() { deleteNativeDetokenizer(handle); } /** Creates a new detokenizer using the provided data as the database. */ private static native long newNativeDetokenizer(byte[] data); /** Deletes the detokenizer object with the provided handle, which MUST be valid. */ private static native void deleteNativeDetokenizer(long handle); /** * Returns the detokenized version of the provided data. This is non-static so this object has a * reference held while the function is running, which prevents finalize from running before * detokenizeNative finishes. */ private native String detokenizeNative(long handle, byte[] data); }