2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.miio.internal.cloud;
15 import static org.openhab.binding.miio.internal.MiIoBindingConstants.BINDING_USERDATA_PATH;
17 import java.io.ByteArrayOutputStream;
18 import java.io.DataOutputStream;
20 import java.io.FileWriter;
21 import java.io.IOException;
23 import java.nio.charset.StandardCharsets;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.Base64;
29 import java.util.List;
31 import java.util.Random;
33 import java.util.TreeMap;
35 import org.eclipse.jdt.annotation.NonNullByDefault;
36 import org.eclipse.jdt.annotation.Nullable;
37 import org.openhab.binding.miio.internal.MiIoCryptoException;
38 import org.slf4j.Logger;
41 * The {@link CloudUtil} class is used for supporting functions for Xiaomi cloud access
43 * @author Marcel Verpaalen - Initial contribution
46 public class CloudUtil {
48 private static final Random RANDOM = new Random();
51 * Saves the Xiaomi cloud device info with tokens to file
53 * @param data file content
54 * @param country county server
57 public static void saveDeviceInfoFile(String data, String country, Logger logger) {
58 File folder = new File(BINDING_USERDATA_PATH);
59 if (!folder.exists()) {
62 File dataFile = new File(folder, "miioTokens-" + country + ".json");
63 try (FileWriter writer = new FileWriter(dataFile)) {
65 logger.debug("Devices token info saved to {}", dataFile.getAbsolutePath());
66 } catch (IOException e) {
67 logger.debug("Failed to write token file '{}': {}", dataFile.getName(), e.getMessage());
72 * Generate signature for the request.
74 * @param method http request method. GET or POST
75 * @param requestUrl the full request url. e.g.: http://api.xiaomi.com/getUser?id=123321
76 * @param params request params. This should be a TreeMap because the
77 * parameters are required to be in lexicographic order.
78 * @param signedNonce secret key for encryption.
79 * @return hash value for the values provided
80 * @throws MiIoCryptoException
82 public static String generateSignature(@Nullable String requestUrl, @Nullable String signedNonce, String nonce,
83 @Nullable Map<String, String> params) throws MiIoCryptoException {
84 if (signedNonce == null || signedNonce.length() == 0) {
85 throw new MiIoCryptoException("key is not nullable");
87 List<String> exps = new ArrayList<String>();
89 if (requestUrl != null) {
90 URI uri = URI.create(requestUrl);
91 exps.add(uri.getPath());
93 exps.add(signedNonce);
96 if (params != null && !params.isEmpty()) {
97 final TreeMap<String, String> sortedParams = new TreeMap<String, String>(params);
98 Set<Map.Entry<String, String>> entries = sortedParams.entrySet();
99 for (Map.Entry<String, String> entry : entries) {
100 exps.add(String.format("%s=%s", entry.getKey(), entry.getValue()));
103 boolean first = true;
104 StringBuilder sb = new StringBuilder();
105 for (String s : exps) {
113 return CloudCrypto.hMacSha256Encode(Base64.getDecoder().decode(signedNonce),
114 sb.toString().getBytes(StandardCharsets.UTF_8));
117 public static String generateNonce(long milli) throws IOException {
118 ByteArrayOutputStream output = new ByteArrayOutputStream();
119 DataOutputStream dataOutputStream = new DataOutputStream(output);
120 dataOutputStream.writeLong(RANDOM.nextLong());
121 dataOutputStream.writeInt((int) (milli / 60000));
122 dataOutputStream.flush();
123 return Base64.getEncoder().encodeToString(output.toByteArray());
126 public static String signedNonce(String ssecret, String nonce) throws IOException, MiIoCryptoException {
127 byte[] byteArrayS = Base64.getDecoder().decode(ssecret.getBytes(StandardCharsets.UTF_8));
128 byte[] byteArrayN = Base64.getDecoder().decode(nonce.getBytes(StandardCharsets.UTF_8));
129 ByteArrayOutputStream output = new ByteArrayOutputStream();
130 output.write(byteArrayS);
131 output.write(byteArrayN);
132 return CloudCrypto.sha256Hash(output.toByteArray());
135 public static void writeBytesToFileNio(byte[] bFile, String fileDest) throws IOException {
136 Path path = Paths.get(fileDest);
137 Files.write(path, bFile);