// Path: lib/src/main/java/com/google/capillary/EncrypterManager.java
// public abstract class EncrypterManager {
// 
//   private boolean isLoaded;
//   private CapillaryPublicKey capillaryPublicKey;
//   private HybridEncrypt encrypter;
// 
//   /**
//    * Loads a serialized Capillary public key.
//    *
//    * @param publicKey the serialized Capillary public key.
//    * @throws GeneralSecurityException if the given Capillary public key cannot be loaded.
//    */
//   public synchronized void loadPublicKey(byte[] publicKey) throws GeneralSecurityException {
//     try {
//       capillaryPublicKey = CapillaryPublicKey.parseFrom(publicKey);
//     } catch (InvalidProtocolBufferException e) {
//       throw new GeneralSecurityException("unable to parse public key", e);
//     }
//     encrypter = rawLoadPublicKey(capillaryPublicKey.getKeyBytes().toByteArray());
//     isLoaded = true;
//   }
// 
//   /**
//    * Creates a {@link HybridEncrypt} for a raw public key embedded in a Capillary public key.
//    */
//   abstract HybridEncrypt rawLoadPublicKey(byte[] rawPublicKey) throws GeneralSecurityException;
// 
//   /**
//    * Encryptes the given plaintext into a Capillary ciphertext.
//    *
//    * @param data the plaintext.
//    * @return the Capillary ciphertext.
//    * @throws GeneralSecurityException if the encryption fails.
//    */
//   public synchronized byte[] encrypt(byte[] data) throws GeneralSecurityException {
//     if (!isLoaded) {
//       throw new GeneralSecurityException("public key is not loaded");
//     }
//     byte[] ciphertext = encrypter.encrypt(data, null);
//     return CapillaryCiphertext.newBuilder()
//         .setKeychainUniqueId(capillaryPublicKey.getKeychainUniqueId())
//         .setKeySerialNumber((capillaryPublicKey.getSerialNumber()))
//         .setIsAuthKey(capillaryPublicKey.getIsAuth())
//         .setCiphertext(ByteString.copyFrom(ciphertext))
//         .build().toByteArray();
//   }
// 
//   /**
//    * Clears the loaded Capillary public key.
//    */
//   public synchronized void clearPublicKey() {
//     isLoaded = false;
//     capillaryPublicKey = null;
//     encrypter = null;
//   }
// }
// 
// Path: lib/src/main/java/com/google/capillary/NoSuchKeyException.java
// public final class NoSuchKeyException extends CapillaryException {
// 
//   /**
//    * Creates a new {@link NoSuchKeyException} object.
//    *
//    * @param msg the exception message.
//    */
//   public NoSuchKeyException(String msg) {
//     super(msg);
//   }
// }
// 
// Path: demo/common/src/main/java/com/google/capillary/demo/common/Constants.java
// public class Constants {
// 
//   public static final String CAPILLARY_CIPHERTEXT_KEY = "capillary_ciphertext";
//   public static final String CAPILLARY_KEY_ALGORITHM_KEY = "capillary_key_algorithm";
// }


import com.google.capillary.EncrypterManager;
import com.google.capillary.NoSuchKeyException;
import com.google.capillary.demo.common.AddOrUpdatePublicKeyRequest;
import com.google.capillary.demo.common.AddOrUpdateUserRequest;
import com.google.capillary.demo.common.Constants;
import com.google.capillary.demo.common.DemoServiceGrpc;
import com.google.capillary.demo.common.KeyAlgorithm;
import com.google.capillary.demo.common.SendMessageRequest;
import com.google.crypto.tink.subtle.Base64;
import com.google.protobuf.Empty;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
    logger.info(req.toString());

    try {
      // Get public key.
      byte[] publicKey = db.getKeyBytes(req.getUserId(), req.getKeyAlgorithm(), req.getIsAuthKey());

      // Generate ciphertext.
      EncrypterManager encrypterManager = getEncrypterManager(req.getKeyAlgorithm());
      encrypterManager.loadPublicKey(publicKey);
      byte[] ciphertext = encrypterManager.encrypt(req.getData().toByteArray());
      encrypterManager.clearPublicKey();
      String ciphertextString = Base64.encode(ciphertext);

      // Get FCM token.
      String token = db.getToken(req.getUserId());

      // Create the data map to be sent as a JSON object.
      Map<String, String> dataMap = new HashMap<>();
      dataMap.put(Constants.CAPILLARY_CIPHERTEXT_KEY, ciphertextString);
      dataMap.put(Constants.CAPILLARY_KEY_ALGORITHM_KEY, req.getKeyAlgorithm().name());

      // Send the data map after the requested delay.
      executorService.schedule(() -> {
        try {
          fcmSender.sendDataMessage(token, dataMap);
        } catch (IOException e) {
          e.printStackTrace();
        }
      }, req.getDelaySeconds(), TimeUnit.SECONDS);
    } catch (NoSuchUserException | NoSuchKeyException e) {
// Path: lib/src/main/java/com/google/capillary/EncrypterManager.java
// public abstract class EncrypterManager {
// 
//   private boolean isLoaded;
//   private CapillaryPublicKey capillaryPublicKey;
//   private HybridEncrypt encrypter;
// 
//   /**
//    * Loads a serialized Capillary public key.
//    *
//    * @param publicKey the serialized Capillary public key.
//    * @throws GeneralSecurityException if the given Capillary public key cannot be loaded.
//    */
//   public synchronized void loadPublicKey(byte[] publicKey) throws GeneralSecurityException {
//     try {
//       capillaryPublicKey = CapillaryPublicKey.parseFrom(publicKey);
//     } catch (InvalidProtocolBufferException e) {
//       throw new GeneralSecurityException("unable to parse public key", e);
//     }
//     encrypter = rawLoadPublicKey(capillaryPublicKey.getKeyBytes().toByteArray());
//     isLoaded = true;
//   }
// 
//   /**
//    * Creates a {@link HybridEncrypt} for a raw public key embedded in a Capillary public key.
//    */
//   abstract HybridEncrypt rawLoadPublicKey(byte[] rawPublicKey) throws GeneralSecurityException;
// 
//   /**
//    * Encryptes the given plaintext into a Capillary ciphertext.
//    *
//    * @param data the plaintext.
//    * @return the Capillary ciphertext.
//    * @throws GeneralSecurityException if the encryption fails.
//    */
//   public synchronized byte[] encrypt(byte[] data) throws GeneralSecurityException {
//     if (!isLoaded) {
//       throw new GeneralSecurityException("public key is not loaded");
//     }
//     byte[] ciphertext = encrypter.encrypt(data, null);
//     return CapillaryCiphertext.newBuilder()
//         .setKeychainUniqueId(capillaryPublicKey.getKeychainUniqueId())
//         .setKeySerialNumber((capillaryPublicKey.getSerialNumber()))
//         .setIsAuthKey(capillaryPublicKey.getIsAuth())
//         .setCiphertext(ByteString.copyFrom(ciphertext))
//         .build().toByteArray();
//   }
// 
//   /**
//    * Clears the loaded Capillary public key.
//    */
//   public synchronized void clearPublicKey() {
//     isLoaded = false;
//     capillaryPublicKey = null;
//     encrypter = null;
//   }
// }
// 
// Path: lib/src/main/java/com/google/capillary/NoSuchKeyException.java
// public final class NoSuchKeyException extends CapillaryException {
// 
//   /**
//    * Creates a new {@link NoSuchKeyException} object.
//    *
//    * @param msg the exception message.
//    */
//   public NoSuchKeyException(String msg) {
//     super(msg);
//   }
// }
// 
// Path: demo/common/src/main/java/com/google/capillary/demo/common/Constants.java
// public class Constants {
// 
//   public static final String CAPILLARY_CIPHERTEXT_KEY = "capillary_ciphertext";
//   public static final String CAPILLARY_KEY_ALGORITHM_KEY = "capillary_key_algorithm";
// }


// Path: demo/server/src/main/java/com/google/capillary/demo/server/DemoServiceImpl.java
import com.google.capillary.EncrypterManager;
import com.google.capillary.NoSuchKeyException;
import com.google.capillary.demo.common.AddOrUpdatePublicKeyRequest;
import com.google.capillary.demo.common.AddOrUpdateUserRequest;
import com.google.capillary.demo.common.Constants;
import com.google.capillary.demo.common.DemoServiceGrpc;
import com.google.capillary.demo.common.KeyAlgorithm;
import com.google.capillary.demo.common.SendMessageRequest;
import com.google.crypto.tink.subtle.Base64;
import com.google.protobuf.Empty;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

    logger.info(req.toString());

    try {
      // Get public key.
      byte[] publicKey = db.getKeyBytes(req.getUserId(), req.getKeyAlgorithm(), req.getIsAuthKey());

      // Generate ciphertext.
      EncrypterManager encrypterManager = getEncrypterManager(req.getKeyAlgorithm());
      encrypterManager.loadPublicKey(publicKey);
      byte[] ciphertext = encrypterManager.encrypt(req.getData().toByteArray());
      encrypterManager.clearPublicKey();
      String ciphertextString = Base64.encode(ciphertext);

      // Get FCM token.
      String token = db.getToken(req.getUserId());

      // Create the data map to be sent as a JSON object.
      Map<String, String> dataMap = new HashMap<>();
      dataMap.put(Constants.CAPILLARY_CIPHERTEXT_KEY, ciphertextString);
      dataMap.put(Constants.CAPILLARY_KEY_ALGORITHM_KEY, req.getKeyAlgorithm().name());

      // Send the data map after the requested delay.
      executorService.schedule(() -> {
        try {
          fcmSender.sendDataMessage(token, dataMap);
        } catch (IOException e) {
          e.printStackTrace();
        }
      }, req.getDelaySeconds(), TimeUnit.SECONDS);
    } catch (NoSuchUserException | NoSuchKeyException e) {
      e.printStackTrace();