@Override
  public void persist() {
    if (hasUpdate) {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      BinaryEncoder encoder = EncoderFactory.get().binaryEncoder(baos, null);
      SpecificDatumWriter<Topic> datumWriter = new SpecificDatumWriter<>(Topic.class);
      try {
        for (Topic topic : topicMap.values()) {
          datumWriter.write(topic, encoder);
          LOG.info("Persisted {}", topic);
        }
        encoder.flush();
        String base64Str =
            new String(base64.encodeBase64(baos.toByteArray()), Charset.forName("UTF-8"));
        state.setProperty(TOPIC_LIST, base64Str);
      } catch (IOException e) {
        LOG.error("Can't persist topic list info", e);
      }

      baos = new ByteArrayOutputStream();
      try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
        oos.writeObject(nfSubscriptions);
        String base64Str =
            new String(base64.encodeBase64(baos.toByteArray()), Charset.forName("UTF-8"));
        state.setProperty(NF_SUBSCRIPTIONS, base64Str);
      } catch (IOException e) {
        LOG.error("Can't persist notification subscription info", e);
      }

      StringBuilder attachedEndpointsString = new StringBuilder();
      for (Map.Entry<EndpointAccessToken, EndpointKeyHash> attached :
          attachedEndpoints.entrySet()) {
        attachedEndpointsString
            .append(attached.getKey().getToken())
            .append(":")
            .append(attached.getValue().getKeyHash())
            .append(',');
      }
      state.setProperty(ATTACHED_ENDPOINTS, attachedEndpointsString.toString());
      state.setProperty(EVENT_SEQ_NUM, "" + eventSequence.get());
      if (topicListHash != null) {
        state.setProperty(TOPIC_LIST_HASH, "" + topicListHash);
      }

      OutputStream os = null;
      try {
        storage.renameTo(stateFileLocation, stateFileLocation + "_bckp");
        os = storage.openForWrite(stateFileLocation);
        state.store(os, null);
        hasUpdate = false;
      } catch (IOException e) {
        LOG.error("Can't persist state file", e);
      } finally {
        IOUtils.closeQuietly(os);
      }
    }
  }
 @Override
 public EndpointObjectHash getProfileHash() {
   return EndpointObjectHash.fromBytes(
       base64.decodeBase64(
           state
               .getProperty(
                   PROFILE_HASH, new String(base64.encodeBase64(new byte[0]), Charsets.UTF_8))
               .getBytes(Charsets.UTF_8)));
 }
  private boolean isSDKPropertiesUpdated(KaaClientProperties sdkProperties) {
    byte[] hashFromSDK = sdkProperties.getPropertiesHash();
    byte[] hashFromStateFile =
        base64.decodeBase64(
            state
                .getProperty(
                    PROPERTIES_HASH, new String(base64.encodeBase64(new byte[0]), Charsets.UTF_8))
                .getBytes(Charsets.UTF_8));

    return !Arrays.equals(hashFromSDK, hashFromStateFile);
  }
  private Map<TransportProtocolId, List<TransportConnectionInfo>> parseBootstrapServers(
      String serversStr) throws InvalidKeySpecException, NoSuchAlgorithmException {
    Map<TransportProtocolId, List<TransportConnectionInfo>> servers = new HashMap<>();
    String[] serversSplit = serversStr.split(";");

    for (String server : serversSplit) {
      if (server != null && !server.trim().isEmpty()) {
        String[] tokens = server.split(":");
        ProtocolMetaData md = new ProtocolMetaData();
        md.setAccessPointId(Integer.valueOf(tokens[0]));
        md.setProtocolVersionInfo(
            new ProtocolVersionPair(Integer.valueOf(tokens[1]), Integer.valueOf(tokens[2])));
        md.setConnectionInfo(ByteBuffer.wrap(base64.decodeBase64(tokens[3])));
        TransportProtocolId key =
            new TransportProtocolId(
                md.getProtocolVersionInfo().getId(), md.getProtocolVersionInfo().getVersion());
        List<TransportConnectionInfo> serverList = servers.get(key);
        if (serverList == null) {
          serverList = new ArrayList<TransportConnectionInfo>();
          servers.put(key, serverList);
        }
        serverList.add(new GenericTransportInfo(ServerType.BOOTSTRAP, md));
      }
    }
    return servers;
  }
 @Override
 public EndpointKeyHash getEndpointKeyHash() {
   if (keyHash == null) {
     EndpointObjectHash publicKeyHash =
         EndpointObjectHash.fromSHA1(getOrInitKeyPair().getPublic().getEncoded());
     keyHash = new EndpointKeyHash(new String(base64.encodeBase64(publicKeyHash.getData())));
   }
   return keyHash;
 }
 @SuppressWarnings("unchecked")
 private void parseNfSubscriptions() {
   if (state.getProperty(NF_SUBSCRIPTIONS) != null) {
     byte[] data = base64.decodeBase64(state.getProperty(NF_SUBSCRIPTIONS));
     ByteArrayInputStream is = new ByteArrayInputStream(data);
     try (ObjectInputStream ois = new ObjectInputStream(is)) {
       nfSubscriptions.putAll((Map<Long, Integer>) ois.readObject());
     } catch (Exception e) {
       LOG.error(
           "Unexpected exception occurred while reading subscription information from state", e);
     }
   } else {
     LOG.info("No subscription info found in state");
   }
 }
 private void parseTopics() {
   if (state.getProperty(TOPIC_LIST) != null) {
     byte[] data = base64.decodeBase64(state.getProperty(TOPIC_LIST));
     BinaryDecoder decoder = DecoderFactory.get().binaryDecoder(data, null);
     SpecificDatumReader<Topic> avroReader = new SpecificDatumReader<>(Topic.class);
     try { // NOSONAR
       Topic decodedTopic;
       while (!decoder.isEnd()) {
         decodedTopic = avroReader.read(null, decoder);
         LOG.debug("Loaded {}", decodedTopic);
         topicMap.put(decodedTopic.getId(), decodedTopic);
       }
     } catch (Exception e) {
       LOG.error("Unexpected exception occurred while reading information from decoder", e);
     }
   } else {
     LOG.info("No topic list found in state");
   }
 }
 @Override
 public void setProfileHash(EndpointObjectHash hash) {
   setStateStringValue(
       PROFILE_HASH, new String(base64.encodeBase64(hash.getData()), Charsets.UTF_8));
 }
 private void setPropertiesHash(byte[] hash) {
   setStateStringValue(PROPERTIES_HASH, new String(base64.encodeBase64(hash), Charsets.UTF_8));
 }
 public byte[] getDefaultConfigData() {
   String config = getProperty(KaaClientProperties.CONFIG_DATA_DEFAULT);
   return (config != null) ? base64.decodeBase64(config.getBytes(Charsets.UTF_8)) : null;
 }