1 // Copyright 2020 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 15 /** 16 * This is a Java Native Interface (JNI) wrapper for the Detokenizer class. 17 * 18 * <p>This classes uses the Android Base64 library instead of the standard Java Base64, which is not 19 * yet available on Android. To use this class outside of Android, replace android.util.Base64 with 20 * java.util.Base64. 21 */ 22 package dev.pigweed.tokenizer; 23 24 import android.util.Base64; 25 import java.util.regex.Matcher; 26 import java.util.regex.Pattern; 27 28 /** This class provides the Java interface for the C++ Detokenizer class. */ 29 public class Detokenizer { 30 // Android's Base64 library doesn't seem to check if the Base64-encoded data is valid. This 31 // regular expression checks that it is. Does not match URL-safe or unpadded Base64. 32 private static final Pattern TOKENIZED_STRING = 33 Pattern.compile("\\$([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?"); 34 35 static { 36 System.loadLibrary("detokenizer"); 37 } 38 39 // The handle to the C++ detokenizer instance. 40 private final long handle; 41 Detokenizer()42 public Detokenizer() { 43 this(new byte[0]); 44 } 45 Detokenizer(byte[] tokenDatabase)46 public Detokenizer(byte[] tokenDatabase) { 47 handle = newNativeDetokenizer(tokenDatabase); 48 } 49 50 /** 51 * Detokenizes and replaces all recognized tokenized messages ($ followed by Base64) in the 52 * provided string. Unrecognized tokenized strings are left unchanged. 53 */ detokenize(String message)54 public String detokenize(String message) { 55 Matcher matcher = TOKENIZED_STRING.matcher(message); 56 StringBuilder result = new StringBuilder(); 57 int lastIndex = 0; 58 59 while (matcher.find()) { 60 result.append(message, lastIndex, matcher.start()); 61 62 String decoded = 63 detokenizeNative(handle, Base64.decode(matcher.group().substring(1), Base64.DEFAULT)); 64 result.append(decoded != null ? decoded : matcher.group()); 65 66 lastIndex = matcher.end(); 67 } 68 69 result.append(message, lastIndex, message.length()); 70 return result.toString(); 71 } 72 73 /** Deletes memory allocated in C++ when this class is garbage collected. */ 74 @Override finalize()75 protected void finalize() { 76 deleteNativeDetokenizer(handle); 77 } 78 79 /** Creates a new detokenizer using the provided data as the database. */ newNativeDetokenizer(byte[] data)80 private static native long newNativeDetokenizer(byte[] data); 81 82 /** Deletes the detokenizer object with the provided handle, which MUST be valid. */ deleteNativeDetokenizer(long handle)83 private static native void deleteNativeDetokenizer(long handle); 84 85 /** 86 * Returns the detokenized version of the provided data. This is non-static so this object has a 87 * reference held while the function is running, which prevents finalize from running before 88 * detokenizeNative finishes. 89 */ detokenizeNative(long handle, byte[] data)90 private native String detokenizeNative(long handle, byte[] data); 91 } 92