diff --git a/source/formatter-config.xml b/source/formatter-config.xml new file mode 100644 index 0000000..b9cb895 --- /dev/null +++ b/source/formatter-config.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/source/pom.xml b/source/pom.xml index 4fafe59..a63a5b2 100644 --- a/source/pom.xml +++ b/source/pom.xml @@ -1,110 +1,227 @@ - - 4.0.0 - ch.threema.apitool - msgapi-sdk-java - 1.1.4 - Threema MsgApi SDK - - UTF-8 - ${source.encoding} - ${source.encoding} - - - Threema GmbH, Staldenbachstrasse 11, 8808 Pfäffikon SZ, Schweiz - https://www.threema.ch - - https://gateway.threema.ch/ - - - MIT-License - http://opensource.org/licenses/mit-license.php - repo - - - - - junit - junit - 4.13.1 - test - - - commons-io - commons-io - 2.10.0 - - - org.apache.commons - commons-lang3 - 3.12.0 - - - com.google.code.gson - gson - 2.8.9 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${source.encoding} - 11 - 11 - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.0 - - - - true - lib/ - ch.threema.apitool.Console - - - - - - - maven-assembly-plugin - 3.3.0 - - - - ch.threema.apitool.ConsoleMain - - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - - - - github - GitHub lordyavin Apache Maven Packages - https://maven.pkg.github.com/lordyavin/threema-msgapi-sdk-java - - + + 4.0.0 + + ch.threema.apitool + msgapi-sdk-java + 2.1.0 + Threema MsgApi SDK + This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + + UTF-8 + ${source.encoding} + ${source.encoding} + + + + Threema GmbH, Churerstrasse 82, 8808 Pfäffikon SZ, Schweiz + https://www.threema.ch + + + https://gateway.threema.ch/ + + + + MIT-License + http://opensource.org/licenses/mit-license.php + repo + + + + + + junit + junit + 4.13.2 + test + + + + org.assertj + assertj-core + 3.25.2 + test + + + + org.mockito + mockito-core + 5.10.0 + test + + + org.apache.maven + maven-artifact + 3.9.6 + + + net.revelc.code.formatter + formatter-maven-plugin + 2.23.0 + maven-plugin + + + org.apache.commons + commons-lang3 + 3.14.0 + + + org.apache.commons + commons-text + 1.11.0 + + + org.json + json + 20240303 + + + org.apache.tika + tika-core + 2.9.1 + + + javax.annotation + javax.annotation-api + 1.3.2 + + + org.apache.maven + maven-model + 3.9.6 + + + org.junit.jupiter + junit-jupiter + 5.10.2 + test + + + org.mockito + mockito-junit-jupiter + 5.11.0 + test + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.4.1 + + + enforce-maven + + enforce + + + + + 3.5.0 + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 3.12.1 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.4.3 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${source.encoding} + 11 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + true + lib/ + ch.threema.apitool.Console + + + + + + + maven-assembly-plugin + 3.5.0 + + + + ch.threema.apitool.ConsoleMain + true + true + + + + bin + project + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + net.revelc.code.formatter + formatter-maven-plugin + 2.23.0 + + LF + UTF-8 + ${project.basedir}/formatter-config.xml + + + + + format + + + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.5.0 + + + + + + maven2 + https://repo.maven.apache.org/maven2/ + + diff --git a/source/src/main/java/ch/threema/apitool/APIConnector.java b/source/src/main/java/ch/threema/apitool/APIConnector.java index a310191..a75c203 100644 --- a/source/src/main/java/ch/threema/apitool/APIConnector.java +++ b/source/src/main/java/ch/threema/apitool/APIConnector.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool; @@ -27,55 +37,71 @@ import ch.threema.apitool.results.CapabilityResult; import ch.threema.apitool.results.EncryptResult; import ch.threema.apitool.results.UploadResult; +import ch.threema.apitool.exceptions.ApiException; +import ch.threema.apitool.utils.ApiResponse; +import ch.threema.apitool.utils.DataUtils; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.json.JSONArray; -import javax.net.ssl.HttpsURLConnection; import java.io.*; +import java.net.URI; import java.net.URL; import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.SecureRandom; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import java.util.function.Consumer; /** * Facilitates HTTPS communication with the Threema Message API. */ public class APIConnector { - private static final int BUFFER_SIZE = 16384; - - public interface ProgressListener { + private final String apiUrl; + private final PublicKeyStore publicKeyStore; + private final String apiIdentity; + private final String secret; + private final HttpClient httpClient; + private final Consumer requestInterceptor; - /** - * Update the progress of an upload/download process. - * - * @param progress in percent (0..100) - */ - void updateProgress(int progress); + public String getUserAgent() { + return userAgent; } - public class InputStreamLength { - public final InputStream inputStream; - public final int length; - - public InputStreamLength(InputStream inputStream, int length) { - this.inputStream = inputStream; - this.length = length; - } + public void setUserAgent(String userAgent) { + this.userAgent = userAgent; } - private final String apiUrl; - private final PublicKeyStore publicKeyStore; - private final String apiIdentity; - private final String secret; + public String userAgent; public APIConnector(String apiIdentity, String secret, PublicKeyStore publicKeyStore) { - this(apiIdentity, secret, "https://msgapi.threema.ch/", publicKeyStore); + this(apiIdentity, secret, null, publicKeyStore); } - public APIConnector(String apiIdentity, String secret, String apiUrl, PublicKeyStore publicKeyStore) { + public APIConnector(String apiIdentity, String secret, String apiUrl, + PublicKeyStore publicKeyStore) { + if (apiUrl != null && !apiUrl.endsWith("/")) { + apiUrl += "/"; + } + var reader = new MavenXpp3Reader(); + try { + var model = reader.read(new FileReader("pom.xml")); + setUserAgent("threema-msgapi-sdk-java/" + model.getVersion()); + } catch (IOException | XmlPullParserException e) { + setUserAgent("threema-msgapi-sdk-java"); + } this.apiIdentity = apiIdentity; this.secret = secret; - this.apiUrl = apiUrl; + this.apiUrl = apiUrl != null ? apiUrl : "https://msgapi.threema.ch/"; this.publicKeyStore = publicKeyStore; + + httpClient = HttpClient.newHttpClient(); + requestInterceptor = null; } /** @@ -83,76 +109,193 @@ public APIConnector(String apiIdentity, String secret, String apiUrl, PublicKeyS * * @param to recipient ID * @param text message text (max. 3500 bytes) + * * @return message ID - * @throws IOException if a communication or server error occurs + * + * @throws ApiException if a communication or server error occurs */ - public String sendTextMessageSimple(String to, String text) throws IOException { + public ApiResponse sendTextMessageSimple(String to, String text) throws ApiException { - Map postParams = makeRequestParams(); + Map postParams = makeRequestParams(); postParams.put("to", to); postParams.put("text", text); - return doPost(new URL(this.apiUrl + "send_simple"), postParams); + HttpRequest.Builder builder = httpPostRequestBuilder("send_simple", postParams); + try { + HttpResponse response = httpClient.send(builder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + return new ApiResponse<>(response.statusCode(), response.headers().map(), + new String(response.body().readAllBytes())); + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } } - /** - * Send an end-to-end encrypted message. - * - * @param to recipient ID - * @param nonce nonce used for encryption (24 bytes) - * @param box encrypted message data (max. 4000 bytes) - * @return message ID - * @throws IOException if a communication or server error occurs - */ - public String sendE2EMessage(String to, byte[] nonce, byte[] box) throws IOException { + public ApiResponse sendE2EMessage(String to, byte[] nonce, byte[] box) + throws ApiException { + return sendE2EMessage(to, nonce, box, Map.of()); + } - Map postParams = makeRequestParams(); + public ApiResponse sendE2EMessage(String to, byte[] nonce, byte[] box, + Map options) throws ApiException { + + Map postParams = makeRequestParams(); postParams.put("to", to); postParams.put("nonce", DataUtils.byteArrayToHexString(nonce)); postParams.put("box", DataUtils.byteArrayToHexString(box)); + if (!options.isEmpty()) + postParams.putAll(options); - return doPost(new URL(this.apiUrl + "send_e2e"), postParams); + HttpRequest.Builder requestBuilder = httpPostRequestBuilder("send_e2e", postParams); + try { + HttpResponse response = httpClient.send(requestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + try { + if (response.statusCode() / 100 != 2) { + throw getApiException("sendMsgE2E", response); + } + return new ApiResponse<>(response.statusCode(), response.headers().map(), + new String(response.body().readAllBytes())); + } finally { + response.body().close(); + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + public ApiResponse sendE2EBulkMessage(String[] toArr, byte[][] nonces, + byte[][] boxes) throws ApiException { + return sendE2EBulkMessage(toArr, nonces, boxes, Map.of()); + } + + public ApiResponse sendE2EBulkMessage(String[] toArr, byte[][] nonces, + byte[][] boxes, Map options) throws ApiException { + + var payloads = new JSONArray(); + for (int i = 0; i < toArr.length; i++) { + Map postParams = new HashMap<>(); + postParams.put("to", toArr[i]); + postParams.put("nonce", Base64.getEncoder().encodeToString(nonces[i])); + postParams.put("box", Base64.getEncoder().encodeToString(boxes[i])); + postParams.put("group", true); + postParams.putAll(options); + payloads.put(postParams); + } + + HttpRequest.Builder requestBuilder = httpPostRequestBuilder("send_e2e_bulk", payloads); + try { + HttpResponse response = httpClient.send(requestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + try { + if (response.statusCode() / 100 != 2) { + throw getApiException("sendMsgE2EBulk", response); + } + return new ApiResponse<>(response.statusCode(), response.headers().map(), + new JSONArray(new String(response.body().readAllBytes()))); + } finally { + response.body().close(); + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder createDefaultHttpBuilder() { + HttpRequest.Builder builder = HttpRequest.newBuilder(); + + builder.header("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"); + builder.header("Charset", "utf-8"); + builder.header("Cache-control", "no-cache"); + builder.header("Pragma", "no-cache"); + builder.setHeader("User-Agent", userAgent); + + return builder; } /** - * Lookup an ID by phone number. The phone number will be hashed before - * being sent to the server. + * Lookup an ID by phone number. The phone number will be hashed before being sent to the + * server. * * @param phoneNumber the phone number in E.164 format * @return the ID, or null if not found - * @throws IOException if a communication or server error occurs + * @throws ApiException if a communication or server error occurs */ - public String lookupPhone(String phoneNumber) throws IOException { + public ApiResponse lookupPhone(String phoneNumber) throws ApiException { - try { - Map getParams = makeRequestParams(); + Map getParams = makeRequestParams(); - byte[] phoneHash = CryptTool.hashPhoneNo(phoneNumber); + byte[] phoneHash = CryptTool.hashPhoneNo(phoneNumber); - return doGet(new URL(this.apiUrl + "lookup/phone_hash/" + DataUtils.byteArrayToHexString(phoneHash)), getParams); - } catch (FileNotFoundException e) { - return null; + HttpRequest.Builder builder = httpGetRequestBuilder("lookup/phone_hash/" + DataUtils + .byteArrayToHexString(phoneHash != null ? phoneHash : new byte[0]), + getParams); + try { + HttpResponse response = httpClient.send(builder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (response.statusCode() / 100 != 2) { + throw getApiException("lookup/phone_hash", response); + } + return new ApiResponse<>(response.statusCode(), response.headers().map(), + new String(response.body().readAllBytes())); + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); } } /** - * Lookup an ID by email address. The email address will be hashed before - * being sent to the server. + * Lookup an ID by email address. The email address will be hashed before being sent to the + * server. * * @param email the email address + * * @return the ID, or null if not found - * @throws IOException if a communication or server error occurs + * + * @throws ApiException if a communication or server error occurs */ - public String lookupEmail(String email) throws IOException { + public ApiResponse lookupEmail(String email) throws ApiException { - try { - Map getParams = makeRequestParams(); + Map getParams = makeRequestParams(); - byte[] emailHash = CryptTool.hashEmail(email); + byte[] emailHash = CryptTool.hashEmail(email); - return doGet(new URL(this.apiUrl + "lookup/email_hash/" + DataUtils.byteArrayToHexString(emailHash)), getParams); - } catch (FileNotFoundException e) { - return null; + HttpRequest.Builder builder = null; + if (emailHash != null) { + builder = httpGetRequestBuilder( + "lookup/email_hash/" + DataUtils.byteArrayToHexString(emailHash), + getParams); + } + try { + HttpResponse response = null; + if (builder != null) { + response = httpClient.send(builder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + } + if (response != null) { + if (response.statusCode() / 100 != 2) { + throw getApiException("lookup/email_hash", response); + } + return new ApiResponse<>(response.statusCode(), response.headers().map(), + new String(response.body().readAllBytes())); + } + throw new ApiException("emailHash was empty or something."); + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); } } @@ -160,24 +303,31 @@ public String lookupEmail(String email) throws IOException { * Lookup a public key by ID. * * @param id the ID whose public key is desired + * * @return the corresponding public key, or null if not found - * @throws IOException if a communication or server error occurs + * + * @throws ApiException if a communication or server error occurs */ - public byte[] lookupKey(String id) throws IOException { + public byte[] lookupKey(String id) throws ApiException { byte[] key = this.publicKeyStore.getPublicKey(id); - if(key == null) { + if (key == null) { + Map getParams = makeRequestParams(); + HttpRequest.Builder builder = httpGetRequestBuilder("pubkeys/" + id, getParams); try { - Map getParams = makeRequestParams(); - String pubkeyHex = doGet(new URL(this.apiUrl + "pubkeys/" + id), getParams); - key = DataUtils.hexStringToByteArray(pubkeyHex); - - if(key != null) { - this.publicKeyStore.save(id, key); + HttpResponse response = httpClient.send(builder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (response.statusCode() / 100 != 2) { + throw getApiException("pubkeys", response); } - } catch (FileNotFoundException e) { - return null; + return DataUtils.hexStringToByteArray(new String(response.body().readAllBytes())); + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); } } + this.publicKeyStore.save(id, key); return key; } @@ -185,236 +335,259 @@ public byte[] lookupKey(String id) throws IOException { * Lookup the capabilities of a ID * * @param threemaId The ID whose capabilities should be checked + * * @return The capabilities, or null if not found + * * @throws IOException + * @throws ApiException */ - public CapabilityResult lookupKeyCapability(String threemaId) throws IOException { - String res = doGet(new URL(this.apiUrl + "capabilities/" + threemaId), - makeRequestParams()); - if(res != null) { - return new CapabilityResult(threemaId, res.split(",")); + public CapabilityResult lookupKeyCapability(String threemaId) throws IOException, ApiException { + HttpRequest.Builder requestBuilder = + httpGetRequestBuilder("capabilities/" + threemaId, makeRequestParams()); + try { + HttpResponse response = httpClient.send(requestBuilder.build(), + HttpResponse.BodyHandlers.ofString()); + return new CapabilityResult(threemaId, response.body().split(",")); + + } catch (InterruptedException e) { + throw new RuntimeException(e); } - return null; } - public Integer lookupCredits() throws IOException { - String res = doGet(new URL(this.apiUrl + "credits"), - makeRequestParams()); - if(res != null) { - return Integer.valueOf(res); + public ApiResponse lookupCredits() throws ApiException { + HttpRequest.Builder builder = httpGetRequestBuilder("credits", makeRequestParams()); + try { + HttpResponse response = httpClient.send(builder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (response.statusCode() / 100 != 2) { + throw getApiException("credits", response); + } + return new ApiResponse<>(response.statusCode(), response.headers().map(), + Integer.parseInt(new String(response.body().readAllBytes()))); + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); } - return null; } + /** * Upload a file. * * @param fileEncryptionResult The result of the file encryption (i.e. encrypted file data) + * * @return the result of the upload + * * @throws IOException */ - public UploadResult uploadFile(EncryptResult fileEncryptionResult) throws IOException{ + public UploadResult uploadFile(EncryptResult fileEncryptionResult) throws IOException { + return uploadFile(fileEncryptionResult, false); + } + + /** + * Upload a file. + * + * @param fileEncryptionResult The result of the file encryption (i.e. encrypted file data) + * @param persist Whether the uploaded file blob should be persisted + * @return the result of the upload + * @throws IOException + */ + public UploadResult uploadFile(EncryptResult fileEncryptionResult, boolean persist) + throws IOException { String attachmentName = "blob"; String attachmentFileName = "blob.file"; - String crlf = "\r\n"; - String twoHyphens = "--"; - - char[] chars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); - String boundary = ""; + char[] chars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + .toCharArray(); + StringBuilder boundary = new StringBuilder(); SecureRandom rand = new SecureRandom(); int count = rand.nextInt(11) + 30; for (int i = 0; i < count; i++) { - boundary += chars[rand.nextInt(chars.length)]; + boundary.append(chars[rand.nextInt(chars.length)]); } - - String queryString = makeUrlEncoded(makeRequestParams()); - URL url = new URL(this.apiUrl + "upload_blob?" + queryString); - - HttpsURLConnection connection = (HttpsURLConnection)url.openConnection(); - connection.setDoOutput(true); - connection.setDoInput(true); - connection.setUseCaches(false); - - connection.setRequestMethod("POST"); - connection.setRequestProperty("Connection", "Keep-Alive"); - connection.setRequestProperty("Cache-Control", "no-cache"); - connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary); - - DataOutputStream request = new DataOutputStream(connection.getOutputStream()); - - request.writeBytes(twoHyphens + boundary + crlf); - request.writeBytes("Content-Disposition: form-data; name=\"" + attachmentName + "\";filename=\"" + attachmentFileName + "\"" + crlf); - request.writeBytes(crlf); - request.write(fileEncryptionResult.getResult()); - request.writeBytes(crlf); - request.writeBytes(twoHyphens + boundary + twoHyphens + crlf); - - String response = null; - int responseCode = connection.getResponseCode(); - - if(responseCode == 200) { - InputStream is = connection.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - response = br.readLine(); - br.close(); + var params = makeRequestParams(); + if (persist) + params.put("persist", "true"); + String queryString = makeUrlEncoded(params); + URI url = URI.create(this.apiUrl + "upload_blob?" + queryString); + + HttpRequest.Builder builder = createDefaultHttpBuilder(); + builder.uri(url); + builder.setHeader("Content-Type", "multipart/form-data;boundary=" + boundary); + Map multipartMap = Map.of(attachmentName, attachmentFileName); + HttpRequest.BodyPublisher content = ofMimeMultipartData(multipartMap, boundary.toString(), + fileEncryptionResult.getResult()); + HttpRequest request = builder.POST(content).build(); + int responseCode; + HttpResponse response; + try { + response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + responseCode = response.statusCode(); + } catch (InterruptedException e) { + throw new RuntimeException(e); } - connection.disconnect(); - - return new UploadResult(responseCode, response != null ? DataUtils.hexStringToByteArray(response) : null); + return new UploadResult(responseCode, + responseCode == 200 ? DataUtils.hexStringToByteArray(response.body()) + : null); } - /** - * Download a file given its blob ID. - * - * @param blobId The blob ID of the file - * @return Encrypted file data - * @throws IOException - */ - public byte[] downloadFile(byte[] blobId) throws IOException { - return this.downloadFile(blobId, null); + public static HttpRequest.BodyPublisher ofMimeMultipartData(Map data, + String boundary, byte[] fileEncryptionResultData) throws IOException { + var byteArrays = new ArrayList(); + byte[] separator = ("--" + boundary + "\r\nContent-Disposition: form-data; name=") + .getBytes(StandardCharsets.UTF_8); + for (Map.Entry entry : data.entrySet()) { + byteArrays.add(separator); + + if (entry.getValue() instanceof Path) { + var path = (Path) entry.getValue(); + String mimeType = Files.probeContentType(path); + byteArrays.add(("\"" + entry.getKey() + "\"; filename=\"" + path.getFileName() + + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n") + .getBytes(StandardCharsets.UTF_8)); + byteArrays.add(Files.readAllBytes(path)); + byteArrays.add("\r\n".getBytes(StandardCharsets.UTF_8)); + } else { + byteArrays.add(("\"" + entry.getKey() + "\"; filename=\"" + entry.getValue() + + "\"\r\n").getBytes(StandardCharsets.UTF_8)); + } + } + byteArrays.add("\r\n".getBytes(StandardCharsets.UTF_8)); + byteArrays.add(fileEncryptionResultData); + byteArrays.add("\r\n".getBytes(StandardCharsets.UTF_8)); + byteArrays.add(("--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8)); + + return HttpRequest.BodyPublishers.ofByteArrays(byteArrays); } /** * Download a file given its blob ID. * * @param blobId The blob ID of the file - * @param progressListener An object that will receive progress information, or null + * * @return Encrypted file data + * * @throws IOException */ - public byte[] downloadFile(byte[] blobId, ProgressListener progressListener) throws IOException { + public byte[] downloadFile(byte[] blobId) throws IOException { String queryString = makeUrlEncoded(makeRequestParams()); URL blobUrl = new URL(String.format(this.apiUrl + "blobs/%s?%s", - DataUtils.byteArrayToHexString(blobId), - queryString)); + DataUtils.byteArrayToHexString(blobId), queryString)); - HttpsURLConnection connection = (HttpsURLConnection)blobUrl.openConnection(); - connection.setConnectTimeout(20*1000); - connection.setReadTimeout(20*1000); - connection.setDoOutput(false); + return blobUrl.openConnection().getInputStream().readAllBytes(); + } - InputStream inputStream = connection.getInputStream(); - int contentLength = connection.getContentLength(); - InputStreamLength isl = new InputStreamLength(inputStream, contentLength); + private Map makeRequestParams() { + Map postParams = new HashMap<>(); + postParams.put("from", apiIdentity); + postParams.put("secret", secret); + return postParams; + } - /* Content length known? */ - byte[] blob; - if (isl.length != -1) { - blob = new byte[isl.length]; - int offset = 0; - int readed; + private HttpRequest.Builder httpPostRequestBuilder(String uri, Map postData) + throws ApiException { + // verify the required parameter 'postData' is set + if (postData == null) { + throw new ApiException(400, + "Missing the required parameter 'postData' when making post request"); + } - while (offset < isl.length && (readed = isl.inputStream.read(blob, offset, isl.length - offset)) != -1) { - offset += readed; + HttpRequest.Builder requestBuilder = createDefaultHttpBuilder(); - if (progressListener != null) { - progressListener.updateProgress(100 * offset / isl.length); - } - } - - if (offset != isl.length) { - throw new IOException("Unexpected read size. current: " + offset + ", excepted: " + isl.length); - } - } else { - /* Content length is unknown - need to read until EOF */ + String path = String.format("%s", uri); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - byte[] buffer = new byte[BUFFER_SIZE]; + requestBuilder.uri(URI.create(apiUrl + path)); - int read; - while ((read = isl.inputStream.read(buffer)) != -1) { - bos.write(buffer, 0, read); - } + requestBuilder.header("Content-Type", "application/x-www-form-urlencoded"); + byte[] urlencoded = makeUrlEncoded(postData).getBytes(StandardCharsets.UTF_8); - blob = bos.toByteArray(); + // byte[] localVarPostBody = memberVarObjectMapper.writeValueAsBytes(postData); + requestBuilder.method("POST", HttpRequest.BodyPublishers.ofByteArray(urlencoded)); + // if (memberVarReadTimeout != null) { + // requestBuilder.timeout(memberVarReadTimeout); + // } + if (requestInterceptor != null) { + requestInterceptor.accept(requestBuilder); } - if (progressListener != null) { - progressListener.updateProgress(100); - } - - return blob; + return requestBuilder; } - private Map makeRequestParams() { - Map postParams = new HashMap(); - - postParams.put("from", apiIdentity); - postParams.put("secret", secret); - return postParams; - } - - private String doGet(URL url, Map getParams) throws IOException { - - if (getParams != null) { - String queryString = makeUrlEncoded(getParams); - - url = new URL(url.toString() + "?" + queryString); + private HttpRequest.Builder httpPostRequestBuilder(String path, JSONArray postData) + throws ApiException { + // verify the required parameter 'postData' is set + if (postData == null) { + throw new ApiException(400, + "Missing the required parameter 'urlencoded' when making post request"); } - HttpsURLConnection connection = (HttpsURLConnection)url.openConnection(); - connection.setDoOutput(false); - connection.setDoInput(true); - connection.setInstanceFollowRedirects(false); - connection.setRequestMethod("GET"); - connection.setUseCaches(false); + HttpRequest.Builder requestBuilder = createDefaultHttpBuilder(); - InputStream is = connection.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - String response = br.readLine(); - br.close(); + var params = makeUrlEncoded(makeRequestParams()); + path = String.format("%s?%s", path, params); - connection.disconnect(); + requestBuilder.uri(URI.create(apiUrl + path)); - return response; - } + requestBuilder.header("Content-Type", "application/json"); - private String doPost(URL url, Map postParams) throws IOException { + requestBuilder.method("POST", HttpRequest.BodyPublishers.ofString(postData.toString())); - byte[] postData = makeUrlEncoded(postParams).getBytes("UTF-8"); + if (requestInterceptor != null) { + requestInterceptor.accept(requestBuilder); + } + return requestBuilder; + } - HttpsURLConnection connection = (HttpsURLConnection)url.openConnection(); - connection.setDoOutput(true); - connection.setDoInput(true); - connection.setInstanceFollowRedirects(false); - connection.setRequestMethod("POST"); - connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - connection.setRequestProperty("Charset", "utf-8"); - connection.setRequestProperty("Content-Length", Integer.toString(postData.length)); - connection.setUseCaches(false); + private HttpRequest.Builder httpGetRequestBuilder(String url, Map getParams) + throws ApiException { + // verify the required parameter 'postData' is set + if (getParams == null) { + throw new ApiException(400, + "Missing the required parameter 'getParams' when making get request"); + } - OutputStream os = connection.getOutputStream(); - os.write(postData); - os.flush(); - os.close(); + HttpRequest.Builder requestBuilder = createDefaultHttpBuilder(); - InputStream is = connection.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - String response = br.readLine(); - br.close(); + String path = String.format("%s?%s", url, makeUrlEncoded(getParams)); - connection.disconnect(); + requestBuilder.uri(URI.create(apiUrl + path)); - return response; + requestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (requestInterceptor != null) { + requestInterceptor.accept(requestBuilder); + } + return requestBuilder; } - private String makeUrlEncoded(Map params) { + private String makeUrlEncoded(Map params) { StringBuilder s = new StringBuilder(); - for (Map.Entry param : params.entrySet()) { + for (Map.Entry param : params.entrySet()) { if (s.length() > 0) s.append('&'); s.append(param.getKey()); s.append('='); - try { - s.append(URLEncoder.encode(param.getValue(), "UTF-8")); - } catch (UnsupportedEncodingException ignored) {} + s.append(URLEncoder.encode(String.valueOf(param.getValue()), StandardCharsets.UTF_8)); } return s.toString(); } + protected ApiException getApiException(String operationId, HttpResponse response) + throws IOException { + String body = response.body() == null ? null : new String(response.body().readAllBytes()); + String message = formatExceptionMessage(operationId, response.statusCode(), body); + return new ApiException(response.statusCode(), message, response.headers(), body); + } + + private String formatExceptionMessage(String operationId, int statusCode, String body) { + if (body == null || body.isEmpty()) { + body = "[no body]"; + } + return operationId + " call failed with: " + statusCode + " - " + body; + } } diff --git a/source/src/main/java/ch/threema/apitool/ConsoleMain.java b/source/src/main/java/ch/threema/apitool/ConsoleMain.java index fff8668..4f4d9a1 100644 --- a/source/src/main/java/ch/threema/apitool/ConsoleMain.java +++ b/source/src/main/java/ch/threema/apitool/ConsoleMain.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,189 +26,187 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool; -import java.util.ArrayList; -import java.util.List; - +import ch.threema.apitool.console.commands.*; import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.text.WordUtils; - -import ch.threema.apitool.console.commands.CapabilityCommand; -import ch.threema.apitool.console.commands.Command; -import ch.threema.apitool.console.commands.CreditsCommand; -import ch.threema.apitool.console.commands.DecryptAndDownloadCommand; -import ch.threema.apitool.console.commands.DecryptCommand; -import ch.threema.apitool.console.commands.DerivePublicKeyCommand; -import ch.threema.apitool.console.commands.EncryptCommand; -import ch.threema.apitool.console.commands.FetchPublicKey; -import ch.threema.apitool.console.commands.GenerateKeyPairCommand; -import ch.threema.apitool.console.commands.HashEmailCommand; -import ch.threema.apitool.console.commands.HashPhoneCommand; -import ch.threema.apitool.console.commands.IDLookupByEmail; -import ch.threema.apitool.console.commands.IDLookupByPhoneNo; -import ch.threema.apitool.console.commands.SendE2EFileMessageCommand; -import ch.threema.apitool.console.commands.SendE2EImageMessageCommand; -import ch.threema.apitool.console.commands.SendE2ETextMessageCommand; -import ch.threema.apitool.console.commands.SendSimpleMessageCommand; +import org.apache.commons.text.StringEscapeUtils; +import org.apache.commons.text.WordUtils; + +import java.util.ArrayList; +import java.util.List; /** - * Command line interface for {@link CryptTool} and {@link APIConnector} operations - * for testing purposes and simple invocation from other programming languages. + * Command line interface for {@link CryptTool} and {@link APIConnector} operations for testing + * purposes and simple invocation from other programming languages. */ public class ConsoleMain { - static class Commands { - protected final List commandGroups = new ArrayList<>(); - - public CommandGroup create(String description) { - CommandGroup g = new CommandGroup(description); - this.commandGroups.add(g); - return g; - } - - public ArgumentCommand find(String... arguments) { - if (arguments.length > 0) { - for (CommandGroup g : this.commandGroups) { - ArgumentCommand c = g.find(arguments); - if (c != null) { - return c; - } - } - } - return null; - } - } - - static class CommandGroup { - protected final String description; - protected List argumentCommands = new ArrayList<>(); - - CommandGroup(String description) { - this.description = description; - } - - public CommandGroup add(Command command, String... arguments) { - this.argumentCommands.add(new ArgumentCommand(arguments, command)); - return this; - } - - public ArgumentCommand find(String... arguments) { - ArgumentCommand matchedArgumentCommand = null; - int argMatchedSize = -1; - for (ArgumentCommand c : this.argumentCommands) { - boolean matched = true; - int matchedSize = 0; - for (int n = 0; n < c.arguments.length; n++) { - if (n > arguments.length || !c.arguments[n].equals(arguments[n])) { - matched = false; - break; - } else { - matchedSize++; - } - } - - if (matched && matchedSize > argMatchedSize) { - matchedArgumentCommand = c; - argMatchedSize = matchedSize; - } - - } - return matchedArgumentCommand; - } - } - - static class ArgumentCommand { - protected final String[] arguments; - protected final Command command; - - ArgumentCommand(String[] arguments, Command command) { - this.arguments = arguments; - this.command = command; - } - - public void run(String[] givenArguments) throws Exception { - if (givenArguments.length < this.arguments.length) { - throw new Exception("invalid arguments"); - } - - this.command - .run((String[]) ArrayUtils.subarray(givenArguments, this.arguments.length, givenArguments.length)); - } - } - - private static final Commands commands = new Commands(); - - public static void main(String[] args) throws Exception { - - commands.create("Local operations (no network communication)").add(new EncryptCommand(), "-e") - .add(new DecryptCommand(), "-d").add(new HashEmailCommand(), "-h", "-e") - .add(new HashPhoneCommand(), "-h", "-p").add(new GenerateKeyPairCommand(), "-g") - .add(new DerivePublicKeyCommand(), "-p"); - - commands.create("Network operations").add(new SendSimpleMessageCommand(), "-s") - .add(new SendE2ETextMessageCommand(), "-S").add(new SendE2EImageMessageCommand(), "-S", "-i") - .add(new SendE2EFileMessageCommand(), "-S", "-f").add(new IDLookupByEmail(), "-l", "-e") - .add(new IDLookupByPhoneNo(), "-l", "-p").add(new FetchPublicKey(), "-l", "-k") - .add(new CapabilityCommand(), "-c").add(new DecryptAndDownloadCommand(), "-D") - .add(new CreditsCommand(), "-C"); - - ArgumentCommand argumentCommand = commands.find(args); - if (argumentCommand == null) { - usage(args.length == 1 && args[0].equals("html")); - } else { - argumentCommand.run(args); - } - } - - private static void usage(boolean htmlOutput) { - if (!htmlOutput) { - System.out.println("version:" + ConsoleMain.class.getPackage().getImplementationVersion()); - - System.out.println("usage:\n"); - - System.out.println("General information"); - System.out.println("-------------------\n"); - - System.out.println("Where a key needs to be specified, it can either be given directly as"); - System.out.println("a command line parameter (in hex with a prefix indicating the type;"); - System.out.println("not recommended on shared machines as other users may be able to see"); - System.out.println("the arguments), or as the path to a file that it should be read from"); - System.out.println("(file contents also in hex with the prefix).\n"); - } - - String groupDescriptionTemplate = htmlOutput ? "

%s

\n" - : "\n%s\n" + StringUtils.repeat("-", 80) + "\n\n"; - String commandTemplate = htmlOutput ? "
java -jar threema-msgapi-tool.jar %s
\n" : "%s\n"; - - for (CommandGroup commandGroup : commands.commandGroups) { - System.out.format(groupDescriptionTemplate, commandGroup.description); - - for (ArgumentCommand argumentCommand : commandGroup.argumentCommands) { - StringBuilder command = new StringBuilder(); - for (int n = 0; n < argumentCommand.arguments.length; n++) { - command.append(argumentCommand.arguments[n]).append(" "); - } - String argumentDescription = argumentCommand.command.getUsageArguments(); - if (htmlOutput) { - System.out.format("

%s

\n", argumentCommand.command.getSubject()); - argumentDescription = StringEscapeUtils.escapeHtml3(argumentDescription); - } - command.append(argumentDescription); - - System.out.format(commandTemplate, command.toString().trim()); - - String description = argumentCommand.command.getUsageDescription(); - if (htmlOutput) { - System.out.format("

%s

\n\n", description); - } else { - System.out.println(" " + WordUtils.wrap(description, 76, "\n ", false)); - System.out.println(""); - } - } - } - } + static class Commands { + protected final List commandGroups = new ArrayList<>(); + + public CommandGroup create(String description) { + CommandGroup g = new CommandGroup(description); + this.commandGroups.add(g); + return g; + } + + public ArgumentCommand find(String... arguments) { + if (arguments.length > 0) { + for (CommandGroup g : this.commandGroups) { + ArgumentCommand c = g.find(arguments); + if (c != null) { + return c; + } + } + } + return null; + } + } + + static class CommandGroup { + protected final String description; + protected List argumentCommands = new ArrayList<>(); + + CommandGroup(String description) { + this.description = description; + } + + public CommandGroup add(Command command, String... arguments) { + this.argumentCommands.add(new ArgumentCommand(arguments, command)); + return this; + } + + public ArgumentCommand find(String... arguments) { + ArgumentCommand matchedArgumentCommand = null; + int argMatchedSize = -1; + for (ArgumentCommand c : this.argumentCommands) { + boolean matched = true; + int matchedSize = 0; + for (int n = 0; n < c.arguments.length; n++) { + if (n > arguments.length || !c.arguments[n].equals(arguments[n])) { + matched = false; + break; + } else { + matchedSize++; + } + } + + if (matched && matchedSize > argMatchedSize) { + matchedArgumentCommand = c; + argMatchedSize = matchedSize; + } + + } + return matchedArgumentCommand; + } + } + + static class ArgumentCommand { + protected final String[] arguments; + protected final Command command; + + ArgumentCommand(String[] arguments, Command command) { + this.arguments = arguments; + this.command = command; + } + + public void run(String[] givenArguments) throws Exception { + if (givenArguments.length < this.arguments.length) { + throw new Exception("invalid arguments"); + } + + this.command.run((String[]) ArrayUtils.subarray(givenArguments, this.arguments.length, + givenArguments.length)); + } + } + + private static final Commands commands = new Commands(); + + public static void main(String[] args) throws Exception { + + commands.create("Local operations (no network communication)") + .add(new EncryptCommand(), "-e").add(new DecryptCommand(), "-d") + .add(new HashEmailCommand(), "-h", "-e") + .add(new HashPhoneCommand(), "-h", "-p") + .add(new GenerateKeyPairCommand(), "-g") + .add(new DerivePublicKeyCommand(), "-p"); + + commands.create("Network operations").add(new SendSimpleMessageCommand(), "-s") + .add(new SendE2ETextMessageCommand(), "-S") + .add(new SendE2EImageMessageCommand(), "-S", "-i") + .add(new SendE2EFileMessageCommand(), "-S", "-f") + .add(new IDLookupByEmail(), "-l", "-e") + .add(new IDLookupByPhoneNo(), "-l", "-p") + .add(new FetchPublicKey(), "-l", "-k").add(new CapabilityCommand(), "-c") + .add(new DecryptAndDownloadCommand(), "-D").add(new CreditsCommand(), "-C"); + + ArgumentCommand argumentCommand = commands.find(args); + if (argumentCommand == null) { + usage(args.length == 1 && args[0].equals("html")); + } else { + argumentCommand.run(args); + } + } + + private static void usage(boolean htmlOutput) { + if (!htmlOutput) { + System.out.println( + "version:" + ConsoleMain.class.getPackage().getImplementationVersion()); + + System.out.println("usage:\n"); + + System.out.println("General information"); + System.out.println("-------------------\n"); + + System.out.println( + "Where a key needs to be specified, it can either be given directly as"); + System.out.println( + "a command line parameter (in hex with a prefix indicating the type;"); + System.out.println( + "not recommended on shared machines as other users may be able to see"); + System.out.println( + "the arguments), or as the path to a file that it should be read from"); + System.out.println("(file contents also in hex with the prefix).\n"); + } + + String groupDescriptionTemplate = htmlOutput ? "

%s

\n" + : "\n%s\n" + StringUtils.repeat("-", 80) + "\n\n"; + String commandTemplate = htmlOutput + ? "
java -jar threema-msgapi-tool.jar %s
\n" + : "%s\n"; + + for (CommandGroup commandGroup : commands.commandGroups) { + System.out.format(groupDescriptionTemplate, commandGroup.description); + + for (ArgumentCommand argumentCommand : commandGroup.argumentCommands) { + StringBuilder command = new StringBuilder(); + for (int n = 0; n < argumentCommand.arguments.length; n++) { + command.append(argumentCommand.arguments[n]).append(" "); + } + String argumentDescription = argumentCommand.command.getUsageArguments(); + if (htmlOutput) { + System.out.format("

%s

\n", argumentCommand.command.getSubject()); + argumentDescription = StringEscapeUtils.escapeHtml4(argumentDescription); + } + command.append(argumentDescription); + + System.out.format(commandTemplate, command.toString().trim()); + + String description = argumentCommand.command.getUsageDescription(); + if (htmlOutput) { + System.out.format("

%s

\n\n", description); + } else { + System.out.println(" " + WordUtils.wrap(description, 76, "\n ", false)); + System.out.println(); + } + } + } + } } diff --git a/source/src/main/java/ch/threema/apitool/CryptTool.java b/source/src/main/java/ch/threema/apitool/CryptTool.java index 688a798..5d49a0a 100644 --- a/source/src/main/java/ch/threema/apitool/CryptTool.java +++ b/source/src/main/java/ch/threema/apitool/CryptTool.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,7 +26,16 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ +/* + * NOTE: This class is auto generated by MsgApi SDK Generator (https://gateway.threema.ch/en/developer). + * https://gateway.threema.ch/en/developer + * Any changes made to this file will be overwritten upon re-generating. +*/ package ch.threema.apitool; @@ -29,6 +44,8 @@ import ch.threema.apitool.exceptions.MessageParseException; import ch.threema.apitool.exceptions.UnsupportedMessageTypeException; import ch.threema.apitool.messages.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.types.voting.*; import ch.threema.apitool.results.EncryptResult; import ch.threema.apitool.results.UploadResult; import com.neilalexander.jnacl.NaCl; @@ -36,10 +53,14 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.security.SecureRandom; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import java.util.Map; + +import static ch.threema.apitool.utils.ProtocolConstants.MIN_MESSAGE_PADDED_LEN; /** * Contains static methods to do various Threema cryptography related tasks. @@ -47,78 +68,387 @@ public class CryptTool { /* HMAC-SHA256 keys for email/mobile phone hashing */ - private static final byte[] EMAIL_HMAC_KEY = new byte[] {(byte)0x30,(byte)0xa5,(byte)0x50,(byte)0x0f,(byte)0xed,(byte)0x97,(byte)0x01,(byte)0xfa,(byte)0x6d,(byte)0xef,(byte)0xdb,(byte)0x61,(byte)0x08,(byte)0x41,(byte)0x90,(byte)0x0f,(byte)0xeb,(byte)0xb8,(byte)0xe4,(byte)0x30,(byte)0x88,(byte)0x1f,(byte)0x7a,(byte)0xd8,(byte)0x16,(byte)0x82,(byte)0x62,(byte)0x64,(byte)0xec,(byte)0x09,(byte)0xba,(byte)0xd7}; - private static final byte[] PHONENO_HMAC_KEY = new byte[] {(byte)0x85,(byte)0xad,(byte)0xf8,(byte)0x22,(byte)0x69,(byte)0x53,(byte)0xf3,(byte)0xd9,(byte)0x6c,(byte)0xfd,(byte)0x5d,(byte)0x09,(byte)0xbf,(byte)0x29,(byte)0x55,(byte)0x5e,(byte)0xb9,(byte)0x55,(byte)0xfc,(byte)0xd8,(byte)0xaa,(byte)0x5e,(byte)0xc4,(byte)0xf9,(byte)0xfc,(byte)0xd8,(byte)0x69,(byte)0xe2,(byte)0x58,(byte)0x37,(byte)0x07,(byte)0x23}; - - private static final byte[] FILE_NONCE = new byte[]{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}; - private static final byte[] FILE_THUMBNAIL_NONCE = new byte[]{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02}; + private static final byte[] EMAIL_HMAC_KEY = new byte[] {(byte) 0x30, (byte) 0xa5, (byte) 0x50, + (byte) 0x0f, (byte) 0xed, (byte) 0x97, (byte) 0x01, (byte) 0xfa, (byte) 0x6d, + (byte) 0xef, (byte) 0xdb, (byte) 0x61, (byte) 0x08, (byte) 0x41, (byte) 0x90, + (byte) 0x0f, (byte) 0xeb, (byte) 0xb8, (byte) 0xe4, (byte) 0x30, (byte) 0x88, + (byte) 0x1f, (byte) 0x7a, (byte) 0xd8, (byte) 0x16, (byte) 0x82, (byte) 0x62, + (byte) 0x64, (byte) 0xec, (byte) 0x09, (byte) 0xba, (byte) 0xd7}; + private static final byte[] PHONENO_HMAC_KEY = new byte[] {(byte) 0x85, (byte) 0xad, + (byte) 0xf8, (byte) 0x22, (byte) 0x69, (byte) 0x53, (byte) 0xf3, (byte) 0xd9, + (byte) 0x6c, (byte) 0xfd, (byte) 0x5d, (byte) 0x09, (byte) 0xbf, (byte) 0x29, + (byte) 0x55, (byte) 0x5e, (byte) 0xb9, (byte) 0x55, (byte) 0xfc, (byte) 0xd8, + (byte) 0xaa, (byte) 0x5e, (byte) 0xc4, (byte) 0xf9, (byte) 0xfc, (byte) 0xd8, + (byte) 0x69, (byte) 0xe2, (byte) 0x58, (byte) 0x37, (byte) 0x07, (byte) 0x23}; + + private static final byte[] FILE_NONCE = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01}; + private static final byte[] FILE_THUMBNAIL_NONCE = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02}; private static final SecureRandom random = new SecureRandom(); /** - * Encrypt a text message. + * Encrypt a Text * - * @param text the text to be encrypted (max. 3500 bytes) - * @param senderPrivateKey the private key of the sending ID - * @param recipientPublicKey the public key of the receiving ID - * @return encrypted result - */ - public static EncryptResult encryptTextMessage(String text, byte[] senderPrivateKey, byte[] recipientPublicKey) { + * @param text The message text + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Text encryption + **/ + public static EncryptResult encryptTextMessage(String text, byte[] senderPrivateKey, + byte[] recipientPublicKey) { return encryptMessage(new TextMessage(text), senderPrivateKey, recipientPublicKey); } + /** + * Encrypt a Location + * + * @param latitude The geographic latitude + * @param longitude The geographic longitude + * @param accuracy The location accuracy + * @param poiName The location name + * @param address The location address + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Location encryption + **/ + public static EncryptResult encryptLocationMessage(String latitude, String longitude, + Float accuracy, String poiName, String address, byte[] senderPrivateKey, + byte[] recipientPublicKey) { + return encryptMessage(new LocationMessage(latitude, longitude, accuracy, poiName, address), + senderPrivateKey, recipientPublicKey); + } /** - * Encrypt an image message. + * Encrypt a File * - * @param encryptResult result of the image encryption - * @param uploadResult result of the upload - * @param senderPrivateKey the private key of the sending ID - * @param recipientPublicKey the public key of the receiving ID - * @return encrypted result - */ - public static EncryptResult encryptImageMessage(EncryptResult encryptResult, UploadResult uploadResult, byte[] senderPrivateKey, byte[] recipientPublicKey) { + * @param blobId The blob ID + * @param thumbnailBlobId The thumbnail blob ID + * @param thumbnailMediaType The thumbnail media type + * @param encryptionKey The encryption key + * @param mimeType The mime type + * @param fileName The filename + * @param size The file size + * @param caption The file caption + * @param renderingType The rendering type + * @param correlationId The correlation identifier + * @param metadata The metadata + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the File encryption + **/ + public static EncryptResult encryptFileMessage(byte[] blobId, byte[] thumbnailBlobId, + String thumbnailMediaType, byte[] encryptionKey, String mimeType, + String fileName, int size, String caption, FileRenderingType renderingType, + String correlationId, Map metadata, byte[] senderPrivateKey, + byte[] recipientPublicKey) { return encryptMessage( - new ImageMessage(uploadResult.getBlobId(), - encryptResult.getSize(), - encryptResult.getNonce()), - senderPrivateKey, - recipientPublicKey); + new FileMessage(blobId, thumbnailBlobId, thumbnailMediaType, encryptionKey, + mimeType, fileName, size, caption, renderingType, + correlationId, metadata), + senderPrivateKey, recipientPublicKey); } /** - * Encrypt a file message. + * Encrypt a Ballot * - * @param encryptResult result of the file data encryption - * @param uploadResult result of the upload - * @param mimeType MIME type of the file - * @param fileName File name - * @param fileSize Size of the file, in bytes - * @param uploadResultThumbnail result of thumbnail upload + * @param ballotId The poll ballot identifier + * @param description The group poll description + * @param state The group poll state + * @param votingMode The voting mode + * @param resultsDisclosureType The poll results disclosure type + * @param order The poll results order (deprecated) + * @param displayMode The display mode + * @param choices The available vote choices + * @param participants The poll participants * @param senderPrivateKey Private key of sender * @param recipientPublicKey Public key of recipient - * @return Result of the file message encryption (not the same as the file data encryption!) - */ - public static EncryptResult encryptFileMessage(EncryptResult encryptResult, - UploadResult uploadResult, - String mimeType, - String fileName, - int fileSize, - UploadResult uploadResultThumbnail, - byte[] senderPrivateKey, byte[] recipientPublicKey) { + * @return Result of the Ballot encryption + **/ + public static EncryptResult encryptBallotCreateMessage(byte[] ballotId, String description, + State state, VotingMode votingMode, ResultsDisclosureType resultsDisclosureType, + int order, DisplayMode displayMode, List choices, + List participants, byte[] senderPrivateKey, byte[] recipientPublicKey) { + return encryptMessage(new BallotCreateMessage(ballotId, description, state, votingMode, + resultsDisclosureType, order, displayMode, choices, participants), + senderPrivateKey, recipientPublicKey); + } + + /** + * Encrypt a Ballot + * + * @param creator The group poll creator + * @param ballotId The poll ballot identifier + * @param votes The votes tuple + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Ballot encryption + **/ + public static EncryptResult encryptBallotVoteMessage(byte[] creator, byte[] ballotId, + List votes, byte[] senderPrivateKey, byte[] recipientPublicKey) { + return encryptMessage(new BallotVoteMessage(creator, ballotId, votes), senderPrivateKey, + recipientPublicKey); + } + + /** + * Encrypt a Delivery + * + * @param receiptType The message receipt type + * @param ackedMessageIds The acked message ids + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Delivery encryption + **/ + public static EncryptResult encryptDeliveryReceipt(DeliveryReceipt.Type receiptType, + List ackedMessageIds, byte[] senderPrivateKey, + byte[] recipientPublicKey) { + return encryptMessage(new DeliveryReceipt(receiptType, ackedMessageIds), senderPrivateKey, + recipientPublicKey); + } + + /** + * Encrypt a Group + * + * @param groupId The group identifier + * @param receiptType The message receipt type + * @param ackedMessageIds The acked message ids + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Group encryption + **/ + public static EncryptResult encryptGroupDeliveryReceipt(GroupId groupId, + DeliveryReceipt.Type receiptType, List ackedMessageIds, + byte[] senderPrivateKey, byte[] recipientPublicKey) { + return encryptMessage(new GroupDeliveryReceipt(groupId, receiptType, ackedMessageIds), + senderPrivateKey, recipientPublicKey); + } + + /** + * Encrypt a Group + * + * @param groupId The group identifier + * @param members The group members + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Group encryption + **/ + public static EncryptResult encryptGroupCreateMessage(GroupId groupId, List members, + byte[] senderPrivateKey, byte[] recipientPublicKey) { + return encryptMessage(new GroupCreateMessage(groupId, members), senderPrivateKey, + recipientPublicKey); + } + + /** + * Encrypt a Group + * + * @param groupId The group identifier + * @param groupName The group name + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Group encryption + **/ + public static EncryptResult encryptGroupRenameMessage(GroupId groupId, String groupName, + byte[] senderPrivateKey, byte[] recipientPublicKey) { + return encryptMessage(new GroupRenameMessage(groupId, groupName), senderPrivateKey, + recipientPublicKey); + } + + /** + * Encrypt a Group + * + * @param groupId The group identifier + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Group encryption + **/ + public static EncryptResult encryptGroupLeaveMessage(GroupId groupId, byte[] senderPrivateKey, + byte[] recipientPublicKey) { + return encryptMessage(new GroupLeaveMessage(groupId), senderPrivateKey, recipientPublicKey); + } + + /** + * Encrypt a Group + * + * @param groupId The group identifier + * @param blobId The blob ID + * @param size The file size + * @param encryptionKey The encryption key + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Group encryption + **/ + public static EncryptResult encryptGroupSetPhoto(GroupId groupId, byte[] blobId, int size, + byte[] encryptionKey, byte[] senderPrivateKey, byte[] recipientPublicKey) { + return encryptMessage(new GroupSetPhoto(groupId, blobId, size, encryptionKey), + senderPrivateKey, recipientPublicKey); + } + + /** + * Encrypt a Group + * + * @param groupId The group identifier + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Group encryption + **/ + public static EncryptResult encryptGroupDeletePhoto(GroupId groupId, byte[] senderPrivateKey, + byte[] recipientPublicKey) { + return encryptMessage(new GroupDeletePhoto(groupId), senderPrivateKey, recipientPublicKey); + } + + /** + * Encrypt a Group + * + * @param groupId The group identifier + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Group encryption + **/ + public static EncryptResult encryptGroupRequestSyncMessage(GroupId groupId, + byte[] senderPrivateKey, byte[] recipientPublicKey) { + return encryptMessage(new GroupRequestSyncMessage(groupId), senderPrivateKey, + recipientPublicKey); + } + + /** + * Encrypt a Group + * + * @param groupId The group identifier + * @param text The message text + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Group encryption + **/ + public static EncryptResult encryptGroupTextMessage(GroupId groupId, String text, + byte[] senderPrivateKey, byte[] recipientPublicKey) { + return encryptMessage(new GroupTextMessage(groupId, text), senderPrivateKey, + recipientPublicKey); + } + + /** + * Encrypt a Group + * + * @param groupId The group identifier + * @param latitude The geographic latitude + * @param longitude The geographic longitude + * @param accuracy The location accuracy + * @param poiName The location name + * @param address The location address + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Group encryption + **/ + public static EncryptResult encryptGroupLocationMessage(GroupId groupId, String latitude, + String longitude, Float accuracy, String poiName, String address, + byte[] senderPrivateKey, byte[] recipientPublicKey) { + return encryptMessage(new GroupLocationMessage(groupId, latitude, longitude, accuracy, + poiName, address), senderPrivateKey, recipientPublicKey); + } + + /** + * Encrypt a Group + * + * @param groupId The group identifier + * @param blobId The blob ID + * @param thumbnailBlobId The thumbnail blob ID + * @param thumbnailMediaType The thumbnail media type + * @param encryptionKey The encryption key + * @param mimeType The mime type + * @param fileName The filename + * @param size The file size + * @param caption The file caption + * @param renderingType The rendering type + * @param correlationId The correlation identifier + * @param metadata The metadata + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Group encryption + **/ + public static EncryptResult encryptGroupFileMessage(GroupId groupId, byte[] blobId, + byte[] thumbnailBlobId, String thumbnailMediaType, byte[] encryptionKey, + String mimeType, String fileName, int size, String caption, + FileRenderingType renderingType, String correlationId, + Map metadata, byte[] senderPrivateKey, + byte[] recipientPublicKey) { return encryptMessage( - new FileMessage(uploadResult.getBlobId(), - encryptResult.getSecret(), - mimeType, - fileName, - fileSize, - uploadResultThumbnail != null ? uploadResultThumbnail.getBlobId() : null), - senderPrivateKey, - recipientPublicKey); + new GroupFileMessage(groupId, blobId, thumbnailBlobId, thumbnailMediaType, + encryptionKey, mimeType, fileName, size, caption, + renderingType, correlationId, metadata), + senderPrivateKey, recipientPublicKey); } + /** + * Encrypt a Group + * + * @param groupId The group identifier + * @param ballotId The poll ballot identifier + * @param description The group poll description + * @param state The group poll state + * @param votingMode The voting mode + * @param resultsDisclosureType The poll results disclosure type + * @param order The poll results order (deprecated) + * @param displayMode The display mode + * @param choices The available vote choices + * @param participants The poll participants + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Group encryption + **/ + public static EncryptResult encryptGroupBallotCreateMessage(GroupId groupId, byte[] ballotId, + String description, State state, VotingMode votingMode, + ResultsDisclosureType resultsDisclosureType, int order, DisplayMode displayMode, + List choices, List participants, byte[] senderPrivateKey, + byte[] recipientPublicKey) { + return encryptMessage( + new GroupBallotCreateMessage(groupId, ballotId, description, state, + votingMode, resultsDisclosureType, order, displayMode, + choices, participants), + senderPrivateKey, recipientPublicKey); + } - private static EncryptResult encryptMessage(ThreemaMessage threemaMessage, byte[] privateKey, byte[] publicKey) { + /** + * Encrypt a Group + * + * @param groupId The group identifier + * @param creator The group poll creator + * @param ballotId The poll ballot identifier + * @param votes The votes array + * @param senderPrivateKey Private key of sender + * @param recipientPublicKey Public key of recipient + * @return Result of the Group encryption + **/ + public static EncryptResult encryptGroupBallotVoteMessage(GroupId groupId, byte[] creator, + byte[] ballotId, List votes, byte[] senderPrivateKey, + byte[] recipientPublicKey) { + return encryptMessage(new GroupBallotVoteMessage(groupId, creator, ballotId, votes), + senderPrivateKey, recipientPublicKey); + } + + + /** + * Encrypt an image message. + * + * @param encryptResult result of the image encryption + * @param uploadResult result of the upload + * @param senderPrivateKey the private key of the sending ID + * @param recipientPublicKey the public key of the receiving ID + * @return encrypted result + * @deprecated + */ + @Deprecated + public static EncryptResult encryptImageMessage(EncryptResult encryptResult, + UploadResult uploadResult, byte[] senderPrivateKey, byte[] recipientPublicKey) { + return encryptMessage( + new ImageMessage(uploadResult.getBlobId(), encryptResult.getSize(), + encryptResult.getNonce()), + senderPrivateKey, recipientPublicKey); + } + + private static EncryptResult encryptMessage(ThreemaMessage threemaMessage, byte[] privateKey, + byte[] publicKey) { /* determine random amount of PKCS7 padding */ int padbytes = random.nextInt(254) + 1; @@ -129,15 +459,74 @@ private static EncryptResult encryptMessage(ThreemaMessage threemaMessage, byte[ return null; } + byte[] data; + if (1 + messageBytes.length + padbytes < MIN_MESSAGE_PADDED_LEN) { + /* Ensure the message data incl. padding amounts to at least MIN_MESSAGE_PADDED_LEN bytes for security reasons */ + padbytes = MIN_MESSAGE_PADDED_LEN - (1 + messageBytes.length); + } + /* prepend type byte (0x02) to message data */ - byte[] data = new byte[1 + messageBytes.length + padbytes]; - data[0] = (byte)threemaMessage.getTypeCode(); + data = new byte[1 + messageBytes.length + padbytes]; + data[0] = (byte) threemaMessage.getTypeCode(); System.arraycopy(messageBytes, 0, data, 1, messageBytes.length); /* append padding */ for (int i = 0; i < padbytes; i++) { - data[i + 1 + messageBytes.length] = (byte)padbytes; + data[i + 1 + messageBytes.length] = (byte) padbytes; + } + + return encrypt(data, privateKey, publicKey); + } + + private static EncryptResult encryptMessage(ThreemaGroupMessage groupMessage, byte[] privateKey, + byte[] publicKey) { + /* determine random amount of PKCS7 padding */ + int padbytes = random.nextInt(254) + 1; + + byte[] messageBytes; + try { + messageBytes = groupMessage.getData(); + } catch (BadMessageException e) { + return null; + } + + boolean needsCreatorIdentity = true; + try { + needsCreatorIdentity = !groupMessage.getClass().getField("noPrependGroupCreator") + .getBoolean(groupMessage); + } catch (IllegalAccessException | NoSuchFieldException ignored) { + } + + byte[] groupIdentifier = new byte[GroupId.GROUP_ID_LEN + + (needsCreatorIdentity ? GroupId.CREATOR_ID_LEN : 0)]; + if (needsCreatorIdentity) { + System.arraycopy(groupMessage.getGroupId().getGroupCreator(), 0, groupIdentifier, 0, + GroupId.CREATOR_ID_LEN); + System.arraycopy(groupMessage.getGroupId().getGroupId(), 0, groupIdentifier, + GroupId.CREATOR_ID_LEN, GroupId.GROUP_ID_LEN); + } else { + System.arraycopy(groupMessage.getGroupId().getGroupId(), 0, groupIdentifier, 0, + GroupId.GROUP_ID_LEN); // groupMessage.getGroupId().getGroupCreator().concat(groupMessage.getGroupId().getGroupId()).getBytes(StandardCharsets.UTF_8); + } + + /* prepend type byte (0x02) to message data */ + byte[] data; + if (1 + groupIdentifier.length + messageBytes.length + padbytes < MIN_MESSAGE_PADDED_LEN) { + /* Ensure the message data incl. padding amounts to at least MIN_MESSAGE_PADDED_LEN bytes for security reasons */ + padbytes = MIN_MESSAGE_PADDED_LEN - (1 + groupIdentifier.length + messageBytes.length); + } + + /* prepend type byte (0x02) to message data */ + data = new byte[1 + groupIdentifier.length + messageBytes.length + padbytes]; + data[0] = (byte) groupMessage.getTypeCode(); + + System.arraycopy(groupIdentifier, 0, data, 1, groupIdentifier.length); + System.arraycopy(messageBytes, 0, data, 1 + groupIdentifier.length, messageBytes.length); + + /* append padding */ + for (int i = 0; i < padbytes; i++) { + data[i + 1 + groupIdentifier.length + messageBytes.length] = (byte) padbytes; } return encrypt(data, privateKey, publicKey); @@ -187,73 +576,93 @@ public static byte[] decryptFileThumbnailData(byte[] fileData, byte[] secret) { * @param nonce the nonce that was used for the encryption * @return decrypted message (text or delivery receipt) */ - public static ThreemaMessage decryptMessage(byte[] box, byte[] recipientPrivateKey, byte[] senderPublicKey, byte[] nonce) throws MessageParseException { + public static ThreemaMessage decryptMessage(byte[] box, byte[] recipientPrivateKey, + byte[] senderPublicKey, byte[] nonce) throws MessageParseException { byte[] data = decrypt(box, recipientPrivateKey, senderPublicKey, nonce); if (data == null) throw new DecryptionFailedException(); /* remove padding */ - int padbytes = data[data.length-1] & 0xFF; + int padbytes = data[data.length - 1] & 0xFF; int realDataLength = data.length - padbytes; if (realDataLength < 1) - throw new BadMessageException(); /* Bad message padding */ + throw new BadMessageException(); /* Bad message padding */ /* first byte of data is type */ int type = data[0] & 0xFF; switch (type) { case TextMessage.TYPE_CODE: - /* Text message */ - if (realDataLength < 2) - throw new BadMessageException(); + return TextMessage.fromString(data, realDataLength); - try { - return new TextMessage(new String(data, 1, realDataLength - 1, "UTF-8")); - } catch (UnsupportedEncodingException e) { - /* should never happen, UTF-8 is always supported */ - throw new RuntimeException(e); - } + case LocationMessage.TYPE_CODE: + return LocationMessage.fromString(data, realDataLength); + + case FileMessage.TYPE_CODE: + return FileMessage.fromString(data, realDataLength); + + case BallotCreateMessage.TYPE_CODE: + return BallotCreateMessage.fromString(data, realDataLength); + + case BallotVoteMessage.TYPE_CODE: + return BallotVoteMessage.fromString(data, realDataLength); case DeliveryReceipt.TYPE_CODE: - /* Delivery receipt */ - if (realDataLength < MessageId.MESSAGE_ID_LEN + 2 || ((realDataLength - 2) % MessageId.MESSAGE_ID_LEN) != 0) - throw new BadMessageException(); + return DeliveryReceipt.fromString(data, realDataLength); - DeliveryReceipt.Type receiptType = DeliveryReceipt.Type.get(data[1] & 0xFF); - if (receiptType == null) - throw new BadMessageException(); + case GroupCreateMessage.TYPE_CODE: + return GroupCreateMessage.fromString(data, realDataLength); - List messageIds = new LinkedList(); + case GroupRenameMessage.TYPE_CODE: + return GroupRenameMessage.fromString(data, realDataLength); - int numMsgIds = ((realDataLength - 2) / MessageId.MESSAGE_ID_LEN); - for (int i = 0; i < numMsgIds; i++) { - messageIds.add(new MessageId(data, 2 + i*MessageId.MESSAGE_ID_LEN)); - } + case GroupLeaveMessage.TYPE_CODE: + return GroupLeaveMessage.fromString(data, realDataLength); + + case GroupSetPhoto.TYPE_CODE: + return GroupSetPhoto.fromString(data, realDataLength); + + case GroupDeletePhoto.TYPE_CODE: + return GroupDeletePhoto.fromString(data, realDataLength); + + case GroupDeliveryReceipt.TYPE_CODE: + return GroupDeliveryReceipt.fromString(data, realDataLength); + + case GroupRequestSyncMessage.TYPE_CODE: + return GroupRequestSyncMessage.fromString(data, realDataLength); + + case GroupTextMessage.TYPE_CODE: + return GroupTextMessage.fromString(data, realDataLength); + + case GroupLocationMessage.TYPE_CODE: + return GroupLocationMessage.fromString(data, realDataLength); + + case GroupFileMessage.TYPE_CODE: + return GroupFileMessage.fromString(data, realDataLength); + + case GroupBallotCreateMessage.TYPE_CODE: + return GroupBallotCreateMessage.fromString(data, realDataLength); + + case GroupBallotVoteMessage.TYPE_CODE: + return GroupBallotVoteMessage.fromString(data, realDataLength); - return new DeliveryReceipt(receiptType, messageIds); case ImageMessage.TYPE_CODE: - if(realDataLength != (1 + ThreemaMessage.BLOB_ID_LEN + 4 + NaCl.NONCEBYTES)) { - System.out.println(String.valueOf(realDataLength)); - System.out.println(String.valueOf(1 + ThreemaMessage.BLOB_ID_LEN + 4 + NaCl.NONCEBYTES)); + if (realDataLength != (1 + ThreemaMessage.BLOB_ID_LEN + 4 + NaCl.NONCEBYTES)) { + System.out.println(realDataLength); + System.out.println(1 + ThreemaMessage.BLOB_ID_LEN + 4 + NaCl.NONCEBYTES); throw new BadMessageException(); } byte[] blobId = new byte[ThreemaMessage.BLOB_ID_LEN]; System.arraycopy(data, 1, blobId, 0, ThreemaMessage.BLOB_ID_LEN); int size = EndianUtils.readSwappedInteger(data, 1 + ThreemaMessage.BLOB_ID_LEN); byte[] fileNonce = new byte[NaCl.NONCEBYTES]; - System.arraycopy(data, 1 + 4 + ThreemaMessage.BLOB_ID_LEN, fileNonce, 0, nonce.length); + System.arraycopy(data, 1 + 4 + ThreemaMessage.BLOB_ID_LEN, fileNonce, 0, + nonce.length); return new ImageMessage(blobId, size, fileNonce); - case FileMessage.TYPE_CODE: - try { - return FileMessage.fromString(new String(data, 1, realDataLength-1, "UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new BadMessageException(); - } - default: throw new UnsupportedMessageTypeException(); } @@ -262,8 +671,10 @@ public static ThreemaMessage decryptMessage(byte[] box, byte[] recipientPrivate /** * Generate a new key pair. * - * @param privateKey is used to return the generated private key (length must be NaCl.PRIVATEKEYBYTES) - * @param publicKey is used to return the generated public key (length must be NaCl.PUBLICKEYBYTES) + * @param privateKey is used to return the generated private key (length must be + * NaCl.PRIVATEKEYBYTES) + * @param publicKey is used to return the generated public key (length must be + * NaCl.PUBLICKEYBYTES) */ public static void generateKeyPair(byte[] privateKey, byte[] publicKey) { if (publicKey.length != NaCl.PUBLICKEYBYTES || privateKey.length != NaCl.SECRETKEYBYTES) { @@ -277,10 +688,12 @@ public static void generateKeyPair(byte[] privateKey, byte[] publicKey) { * Encrypt data using NaCl asymmetric ("box") encryption. * * @param data the data to be encrypted - * @param privateKey is used to return the generated private key (length must be NaCl.PRIVATEKEYBYTES) - * @param publicKey is used to return the generated public key (length must be NaCl.PUBLICKEYBYTES) + * @param privateKey is used to return the generated private key (length must be + * NaCl.PRIVATEKEYBYTES) + * @param publicKey is used to return the generated public key (length must be + * NaCl.PUBLICKEYBYTES) */ - public static EncryptResult encrypt(byte[] data,byte[] privateKey, byte[] publicKey) { + public static EncryptResult encrypt(byte[] data, byte[] privateKey, byte[] publicKey) { if (publicKey.length != NaCl.PUBLICKEYBYTES || privateKey.length != NaCl.SECRETKEYBYTES) { throw new IllegalArgumentException("Wrong key length"); } @@ -297,12 +710,12 @@ public static EncryptResult encrypt(byte[] data,byte[] privateKey, byte[] public * @return the encryption result including the random key */ public static EncryptResult encryptFileData(byte[] data) { - //create random key + // create random key SecureRandom rnd = new SecureRandom(); byte[] encryptionKey = new byte[NaCl.SYMMKEYBYTES]; rnd.nextBytes(encryptionKey); - //encrypt file data in-place + // encrypt file data in-place NaCl.symmetricEncryptDataInplace(data, encryptionKey, FILE_NONCE); return new EncryptResult(data, encryptionKey, FILE_NONCE); @@ -332,7 +745,7 @@ public static byte[] hashEmail(String email) { Mac emailMac = Mac.getInstance("HmacSHA256"); emailMac.init(new SecretKeySpec(EMAIL_HMAC_KEY, "HmacSHA256")); String normalizedEmail = email.toLowerCase().trim(); - return emailMac.doFinal(normalizedEmail.getBytes("US-ASCII")); + return emailMac.doFinal(normalizedEmail.getBytes(StandardCharsets.US_ASCII)); } catch (Exception e) { e.printStackTrace(); return null; @@ -350,7 +763,7 @@ public static byte[] hashPhoneNo(String phoneNo) { Mac phoneMac = Mac.getInstance("HmacSHA256"); phoneMac.init(new SecretKeySpec(PHONENO_HMAC_KEY, "HmacSHA256")); String normalizedPhoneNo = phoneNo.replaceAll("[^0-9]", ""); - return phoneMac.doFinal(normalizedPhoneNo.getBytes("US-ASCII")); + return phoneMac.doFinal(normalizedPhoneNo.getBytes(StandardCharsets.US_ASCII)); } catch (Exception e) { e.printStackTrace(); return null; diff --git a/source/src/main/java/ch/threema/apitool/Key.java b/source/src/main/java/ch/threema/apitool/Key.java deleted file mode 100644 index d88cd79..0000000 --- a/source/src/main/java/ch/threema/apitool/Key.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * $Id$ - * - * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE - */ - -package ch.threema.apitool; - -import ch.threema.apitool.exceptions.InvalidKeyException; - -/** - * Encapsulates an asymmetric key, either public or private. - */ -public class Key { - public static final String separator = ":"; - - public static class KeyType { - public static final String PRIVATE = "private"; - public static final String PUBLIC = "public"; - } - - /* Attributes */ - public byte[] key; - public String type; - - public Key(String type, byte[] key) { - this.key = key; - this.type = type; - } - - /** - * Decodes and validates an encoded key. - * Encoded key format: type:hex_key - * - * @param encodedKey an encoded key - * @throws ch.threema.apitool.exceptions.InvalidKeyException - */ - public static Key decodeKey(String encodedKey) throws InvalidKeyException { - // Split key and check length - String[] keyArray = encodedKey.split(Key.separator); - if (keyArray.length != 2) { - throw new InvalidKeyException("Does not contain a valid key format"); - } - - // Unpack key - String keyType = keyArray[0]; - String keyContent = keyArray[1]; - - // Is this a valid hex key? - if (!keyContent.matches("[0-9a-fA-F]{64}")) { - throw new InvalidKeyException("Does not contain a valid key"); - } - - return new Key(keyType, DataUtils.hexStringToByteArray(keyContent)); - } - - /** - * Decodes and validates an encoded key. - * Encoded key format: type:hex_key - * - * @param encodedKey an encoded key - * @param expectedKeyType the expected type of the key - * @throws InvalidKeyException - */ - public static Key decodeKey(String encodedKey, String expectedKeyType) throws InvalidKeyException { - Key key = decodeKey(encodedKey); - - // Check key type - if (!key.type.equals(expectedKeyType)) { - throw new InvalidKeyException("Expected key type: " + expectedKeyType + ", got: " + key.type); - } - - return key; - } - - /** - * Encodes a key. - * - * @return an encoded key - */ - public String encode() { - return this.type + Key.separator + DataUtils.byteArrayToHexString(this.key); - } -} diff --git a/source/src/main/java/ch/threema/apitool/PublicKeyStore.java b/source/src/main/java/ch/threema/apitool/PublicKeyStore.java index 741a0a0..2bb2a7e 100644 --- a/source/src/main/java/ch/threema/apitool/PublicKeyStore.java +++ b/source/src/main/java/ch/threema/apitool/PublicKeyStore.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool; @@ -28,15 +38,15 @@ import java.util.Map; /** - * Stores and caches public keys for Threema users. Extend this class to provide your - * own storage implementation, e.g. in a file or database. + * Stores and caches public keys for Threema users. Extend this class to provide your own storage + * implementation, e.g. in a file or database. */ public abstract class PublicKeyStore { private final Map cache = new HashMap<>(); /** - * Get the public key for a given Threema ID. The cache is checked first; if it - * is not found in the cache, fetchPublicKey() is called. + * Get the public key for a given Threema ID. The cache is checked first; if it is not found in + * the cache, fetchPublicKey() is called. * * @param threemaId The Threema ID whose public key should be obtained * @return The public key, or null if not found. @@ -61,7 +71,7 @@ public final byte[] getPublicKey(String threemaId) { * @param publicKey The corresponding public key. */ public final void setPublicKey(String threemaId, byte[] publicKey) { - if(publicKey != null) { + if (publicKey != null) { synchronized (this.cache) { this.cache.put(threemaId, publicKey); this.save(threemaId, publicKey); @@ -70,17 +80,18 @@ public final void setPublicKey(String threemaId, byte[] publicKey) { } /** - * Fetch the public key for the given Threema ID from the store. Override to provide - * your own implementation to read from the store. + * Fetch the public key for the given Threema ID from the store. Override to provide your own + * implementation to read from the store. * * @param threemaId The Threema ID whose public key should be obtained + * * @return The public key, or null if not found. */ abstract protected byte[] fetchPublicKey(String threemaId); /** - * Save the public key for a given Threema ID in the store. Override to provide - * your own implementation to write to the store. + * Save the public key for a given Threema ID in the store. Override to provide your own + * implementation to write to the store. * * @param threemaId The Threema ID whose public key should be stored * @param publicKey The corresponding public key. diff --git a/source/src/main/java/ch/threema/apitool/console/commands/CapabilityCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/CapabilityCommand.java index 8037f11..ec58838 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/CapabilityCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/CapabilityCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; @@ -34,11 +44,10 @@ public class CapabilityCommand extends Command { private final TextField secretField; public CapabilityCommand() { - super("Fetch Capability", - "Fetch the capability of a Threema ID"); + super("Fetch Capability", "Fetch the capability of a Threema ID"); this.threemaIdField = this.createThreemaId("id"); - this.fromField = this.createThreemaId("from"); + this.fromField = this.createThreemaId("gatewayId"); this.secretField = this.createTextField("secret"); } @@ -48,8 +57,8 @@ protected void execute() throws Exception { String from = this.fromField.getValue(); String secret = this.secretField.getValue(); - CapabilityResult capabilities = this.createConnector(from, secret) - .lookupKeyCapability(threemaId); + CapabilityResult capabilities = + this.createConnector(from, secret).lookupKeyCapability(threemaId); System.out.println(capabilities); } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/Command.java b/source/src/main/java/ch/threema/apitool/console/commands/Command.java index f8e136f..a144e5f 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/Command.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/Command.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; @@ -43,24 +53,25 @@ public Command(String subject, String description) { } private void addField(Field f) { - if(f.isRequired()) { + if (f.isRequired()) { int pos = this.fields.size(); - //add after last required - for(int n = 0; n < this.fields.size(); n++) { - if(!this.fields.get(n).isRequired()) { + // add after last required + for (int n = 0; n < this.fields.size(); n++) { + if (!this.fields.get(n).isRequired()) { pos = n; break; } } this.fields.add(pos, f); - } - else { + } else { this.fields.add(f); } } + protected TextField createTextField(String key) { return this.createTextField(key, true); } + protected TextField createTextField(String key, boolean required) { TextField field = new TextField(key, required); this.addField(field); @@ -127,7 +138,6 @@ protected ByteArrayField createByteArrayField(String key, boolean required) { return field; } - protected APIConnector createConnector(String gatewayId, String secret) { return new APIConnector(gatewayId, secret, new PublicKeyStore() { @Override @@ -137,7 +147,7 @@ protected byte[] fetchPublicKey(String threemaId) { @Override protected void save(String threemaId, byte[] publicKey) { - //do nothing + // do nothing } }); } @@ -159,16 +169,16 @@ protected String readStream(InputStream stream, String charset) throws IOExcepti public final void run(String[] arguments) throws Exception { int pos = 0; - for(Field f: this.fields) { - if(arguments.length > pos) { + for (Field f : this.fields) { + if (arguments.length > pos) { f.setValue(arguments[pos]); } pos++; } - //validate - for(Field f: this.fields) { - if(!f.isValid()) { + // validate + for (Field f : this.fields) { + if (!f.isValid()) { return; } } @@ -182,11 +192,9 @@ public final String getSubject() { public final String getUsageArguments() { StringBuilder usage = new StringBuilder(); - for(Field f: this.fields) { - usage.append(" ") - .append(f.isRequired() ? "<" : "[") - .append(f.getKey()) - .append(f.isRequired() ? ">" : "]"); + for (Field f : this.fields) { + usage.append(" ").append(f.isRequired() ? "<" : "[").append(f.getKey()) + .append(f.isRequired() ? ">" : "]"); } return usage.toString().trim(); } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/CompareVersionsCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/CompareVersionsCommand.java new file mode 100644 index 0000000..74b5645 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/console/commands/CompareVersionsCommand.java @@ -0,0 +1,25 @@ +package ch.threema.apitool.console.commands; + +import org.apache.maven.artifact.versioning.ComparableVersion; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +import java.io.FileReader; +import java.io.IOException; + +public class CompareVersionsCommand { + public static void main(String[] args) { + String newVersion = args[0]; + + var reader = new MavenXpp3Reader(); + String currentVersion; + try { + currentVersion = reader.read(new FileReader("pom.xml")).getVersion(); + } catch (IOException | XmlPullParserException e) { + throw new RuntimeException(e); + } + ComparableVersion currentVersionComp = new ComparableVersion(currentVersion); + + System.exit((currentVersionComp.compareTo(new ComparableVersion(newVersion)) < 0) ? 0 : -1); + } +} diff --git a/source/src/main/java/ch/threema/apitool/console/commands/CreditsCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/CreditsCommand.java index 1cb09e8..157b0be 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/CreditsCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/CreditsCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; @@ -28,28 +38,27 @@ import ch.threema.apitool.console.commands.fields.ThreemaIDField; public class CreditsCommand extends Command { - private final ThreemaIDField fromField; - private final TextField secretField; - - public CreditsCommand() { - super("Credits", "Fetch the remaining credits"); - - this.fromField = this.createThreemaId("from"); - this.secretField = this.createTextField("secret"); - } - - @Override - protected void execute() throws Exception { - String from = this.fromField.getValue(); - String secret = this.secretField.getValue(); - - Integer credits = this.createConnector(from, secret).lookupCredits(); - - if (credits != null) { - System.out.println("Remaining credits: " + credits); - } else { - System.out.println("Error fetching credits"); - ; - } - } + private final ThreemaIDField fromField; + private final TextField secretField; + + public CreditsCommand() { + super("Credits", "Fetch the remaining credits"); + + this.fromField = this.createThreemaId("gatewayId"); + this.secretField = this.createTextField("secret"); + } + + @Override + protected void execute() throws Exception { + String from = this.fromField.getValue(); + String secret = this.secretField.getValue(); + + Integer credits = this.createConnector(from, secret).lookupCredits().getData(); + + if (credits != null) { + System.out.println("Remaining credits: " + credits); + } else { + System.out.println("Error fetching credits"); + } + } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/DecryptAndDownloadCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/DecryptAndDownloadCommand.java index 2dd42f4..b29b261 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/DecryptAndDownloadCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/DecryptAndDownloadCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,11 +26,15 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; -import ch.threema.apitool.DataUtils; +import ch.threema.apitool.utils.DataUtils; import ch.threema.apitool.console.commands.fields.*; import ch.threema.apitool.helpers.E2EHelper; @@ -41,10 +51,9 @@ public class DecryptAndDownloadCommand extends Command { public DecryptAndDownloadCommand() { super("Decrypt and download", - "Decrypt a box (box from the stdin) message and download (if the message is a image or file message) the file(s) to the defined directory" - ); + "Decrypt a box (box from the stdin) message and download (if the message is a image or file message) the file(s) to the defined directory"); this.threemaId = this.createThreemaId("id"); - this.fromField = this.createThreemaId("from"); + this.fromField = this.createThreemaId("gatewayId"); this.secretField = this.createTextField("secret"); this.privateKeyField = this.createPrivateKeyField("privateKey"); this.messageIdField = this.createTextField("messageId"); @@ -66,8 +75,8 @@ protected void execute() throws Exception { byte[] box = DataUtils.hexStringToByteArray(this.readStream(System.in, "UTF-8").trim()); - E2EHelper.ReceiveMessageResult res = e2EHelper.receiveMessage(id, messageId, box, nonce, outputFolder); + E2EHelper.ReceiveMessageResult res = + e2EHelper.receiveMessage(id, messageId, box, nonce, outputFolder); System.out.println(res.toString()); - System.out.println(res.getFiles().toString()); } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/DecryptCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/DecryptCommand.java index fd74ad1..d04b161 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/DecryptCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/DecryptCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; @@ -28,7 +38,7 @@ import ch.threema.apitool.console.commands.fields.PrivateKeyField; import ch.threema.apitool.console.commands.fields.PublicKeyField; import ch.threema.apitool.CryptTool; -import ch.threema.apitool.DataUtils; +import ch.threema.apitool.utils.DataUtils; import ch.threema.apitool.messages.ThreemaMessage; public class DecryptCommand extends Command { @@ -37,8 +47,7 @@ public class DecryptCommand extends Command { private final ByteArrayField nonceField; public DecryptCommand() { - super("Decrypt", - "Decrypt standard input using the given recipient private key and sender public key. The nonce must be given on the command line, and the box (hex) on standard input. Prints the decrypted message to standard output."); + super("Decrypt", "Decrypt standard input using the given recipient private key and sender public key. The nonce must be given on the command line, and the box (hex) on standard input. Prints the decrypted message to standard output."); this.privateKeyField = this.createPrivateKeyField("privateKey"); this.publicKeyField = this.createPublicKeyField("publicKey"); diff --git a/source/src/main/java/ch/threema/apitool/console/commands/DerivePublicKeyCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/DerivePublicKeyCommand.java index 32c9e1e..29a936a 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/DerivePublicKeyCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/DerivePublicKeyCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,20 +26,24 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; import ch.threema.apitool.console.commands.fields.PrivateKeyField; import ch.threema.apitool.CryptTool; -import ch.threema.apitool.Key; +import ch.threema.apitool.types.Key; public class DerivePublicKeyCommand extends Command { private final PrivateKeyField privateKeyField; public DerivePublicKeyCommand() { super("Derive Public Key", - "Derive the public key that corresponds with the given private key."); + "Derive the public key that corresponds with the given private key."); this.privateKeyField = this.createPrivateKeyField("privateKey"); } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/EncryptCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/EncryptCommand.java index 8abaf04..5390ae7 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/EncryptCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/EncryptCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; @@ -27,7 +37,7 @@ import ch.threema.apitool.console.commands.fields.PrivateKeyField; import ch.threema.apitool.console.commands.fields.PublicKeyField; import ch.threema.apitool.CryptTool; -import ch.threema.apitool.DataUtils; +import ch.threema.apitool.utils.DataUtils; import ch.threema.apitool.results.EncryptResult; public class EncryptCommand extends Command { @@ -35,8 +45,7 @@ public class EncryptCommand extends Command { private final PublicKeyField publicKeyField; public EncryptCommand() { - super("Encrypt", - "Encrypt standard input using the given sender private key and recipient public key. Prints two lines to standard output: first the nonce (hex), and then the box (hex)."); + super("Encrypt", "Encrypt standard input using the given sender private key and recipient public key. Prints two lines to standard output: first the nonce (hex), and then the box (hex)."); this.privateKeyField = this.createPrivateKeyField("privateKey"); this.publicKeyField = this.createPublicKeyField("publicKey"); diff --git a/source/src/main/java/ch/threema/apitool/console/commands/FetchPublicKey.java b/source/src/main/java/ch/threema/apitool/console/commands/FetchPublicKey.java index d586fd9..838aa38 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/FetchPublicKey.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/FetchPublicKey.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; @@ -27,7 +37,7 @@ import ch.threema.apitool.APIConnector; import ch.threema.apitool.console.commands.fields.TextField; import ch.threema.apitool.console.commands.fields.ThreemaIDField; -import ch.threema.apitool.Key; +import ch.threema.apitool.types.Key; public class FetchPublicKey extends Command { private final ThreemaIDField threemaIdField; @@ -35,11 +45,10 @@ public class FetchPublicKey extends Command { private final TextField secretField; public FetchPublicKey() { - super("Fetch Public Key", - "Lookup the public key for the given ID."); + super("Fetch Public Key", "Lookup the public key for the given ID."); this.threemaIdField = this.createThreemaId("id"); - this.fromField = this.createThreemaId("from"); + this.fromField = this.createThreemaId("gatewayId"); this.secretField = this.createTextField("secret"); } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/GenerateKeyPairCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/GenerateKeyPairCommand.java index d3f176b..4b61ad4 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/GenerateKeyPairCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/GenerateKeyPairCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,14 +26,18 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; import ch.threema.apitool.console.commands.fields.TextField; import ch.threema.apitool.CryptTool; -import ch.threema.apitool.DataUtils; -import ch.threema.apitool.Key; +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.Key; import com.neilalexander.jnacl.NaCl; import java.io.File; @@ -38,7 +48,7 @@ public class GenerateKeyPairCommand extends Command { public GenerateKeyPairCommand() { super("Generate Key Pair", - "Generate a new key pair and write the private and public keys to the respective files (in hex)."); + "Generate a new key pair and write the private and public keys to the respective files (in hex)."); this.privateKeyPath = this.createTextField("privateKeyFile"); this.publicKeyPath = this.createTextField("publicKeyPath"); } @@ -51,7 +61,9 @@ protected void execute() throws Exception { CryptTool.generateKeyPair(privateKey, publicKey); // Write both keys to file - DataUtils.writeKeyFile(new File(this.privateKeyPath.getValue()), new Key(Key.KeyType.PRIVATE, privateKey)); - DataUtils.writeKeyFile(new File(this.publicKeyPath.getValue()), new Key(Key.KeyType.PUBLIC, publicKey)); + DataUtils.writeKeyFile(new File(this.privateKeyPath.getValue()), + new Key(Key.KeyType.PRIVATE, privateKey)); + DataUtils.writeKeyFile(new File(this.publicKeyPath.getValue()), + new Key(Key.KeyType.PUBLIC, publicKey)); } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/HashEmailCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/HashEmailCommand.java index 4aa7311..c27019e 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/HashEmailCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/HashEmailCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,20 +26,24 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; import ch.threema.apitool.console.commands.fields.TextField; import ch.threema.apitool.CryptTool; -import ch.threema.apitool.DataUtils; +import ch.threema.apitool.utils.DataUtils; public class HashEmailCommand extends Command { private final TextField emailField; public HashEmailCommand() { super("Hash Email Address", - "Hash an email address for identity lookup. Prints the hash in hex."); + "Hash an email address for identity lookup. Prints the hash in hex."); this.emailField = this.createTextField("email", true); } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/HashPhoneCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/HashPhoneCommand.java index 1e349f7..3e00422 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/HashPhoneCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/HashPhoneCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,20 +26,24 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; import ch.threema.apitool.console.commands.fields.TextField; import ch.threema.apitool.CryptTool; -import ch.threema.apitool.DataUtils; +import ch.threema.apitool.utils.DataUtils; public class HashPhoneCommand extends Command { private final TextField phoneNo; public HashPhoneCommand() { super("Hash Phone Number", - "Hash a phone number for identity lookup. Prints the hash in hex."); + "Hash a phone number for identity lookup. Prints the hash in hex."); this.phoneNo = this.createTextField("phoneNo", true); } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/IDLookupByEmail.java b/source/src/main/java/ch/threema/apitool/console/commands/IDLookupByEmail.java index 39a5ee1..d22069a 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/IDLookupByEmail.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/IDLookupByEmail.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; @@ -35,10 +45,10 @@ public class IDLookupByEmail extends Command { public IDLookupByEmail() { super("ID Lookup By Email Address", - "Lookup the ID linked to the given email address (will be hashed locally)."); + "Lookup the ID linked to the given email address (will be hashed locally)."); this.emailField = this.createTextField("email"); - this.fromField = this.createThreemaId("from"); + this.fromField = this.createThreemaId("gatewayId"); this.secretField = this.createTextField("secret"); } @@ -49,7 +59,7 @@ protected void execute() throws Exception { String secret = this.secretField.getValue(); APIConnector apiConnector = this.createConnector(from, secret); - String id = apiConnector.lookupEmail(email); + String id = apiConnector.lookupEmail(email).getData(); if (id != null) { System.out.println(id); } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/IDLookupByPhoneNo.java b/source/src/main/java/ch/threema/apitool/console/commands/IDLookupByPhoneNo.java index e02fb7e..f762c72 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/IDLookupByPhoneNo.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/IDLookupByPhoneNo.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; @@ -35,10 +45,10 @@ public class IDLookupByPhoneNo extends Command { public IDLookupByPhoneNo() { super("ID Lookup By Phone Number", - "Lookup the ID linked to the given phone number (will be hashed locally)."); + "Lookup the ID linked to the given phone number (will be hashed locally)."); this.phoneNoField = this.createTextField("phoneNo"); - this.fromField = this.createThreemaId("from"); + this.fromField = this.createThreemaId("gatewayId"); this.secretField = this.createTextField("secret"); } @@ -49,7 +59,7 @@ protected void execute() throws Exception { String secret = this.secretField.getValue(); APIConnector apiConnector = this.createConnector(from, secret); - String id = apiConnector.lookupPhone(phoneNo); + String id = apiConnector.lookupPhone(phoneNo).getData(); if (id != null) { System.out.println(id); } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/SendE2EFileMessageCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/SendE2EFileMessageCommand.java index 3029618..1086e2a 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/SendE2EFileMessageCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/SendE2EFileMessageCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; @@ -39,17 +49,18 @@ public class SendE2EFileMessageCommand extends Command { private final PrivateKeyField privateKeyField; private final FileField fileField; private final FileField thumbnailField; + private final TextField caption; public SendE2EFileMessageCommand() { super("Send End-to-End Encrypted File Message", - "Encrypt the file (and thumbnail) and send a file message to the given ID. 'from' is the API identity and 'secret' is the API secret. Prints the message ID on success." - ); + "Encrypt the file (and thumbnail) and send a file message to the given ID. 'gatewayId' is the API identity and 'secret' is the API secret. Prints the message ID on success."); this.threemaId = this.createThreemaId("to"); - this.fromField = this.createThreemaId("from"); + this.fromField = this.createThreemaId("gatewayId"); this.secretField = this.createTextField("secret"); this.privateKeyField = this.createPrivateKeyField("privateKey"); this.fileField = this.createFileField("file"); this.thumbnailField = this.createFileField("thumbnail", false); + this.caption = this.createTextField("caption", false); } @@ -60,10 +71,11 @@ protected void execute() throws Exception { String secret = this.secretField.getValue(); byte[] privateKey = this.privateKeyField.getValue(); File file = this.fileField.getValue(); - File thumbnail = this.thumbnailField.getValue(); + File thumbnail = this.thumbnailField.getValue(); + String caption = this.caption.getValue(); E2EHelper e2EHelper = new E2EHelper(this.createConnector(from, secret), privateKey); - String messageId = e2EHelper.sendFileMessage(to, file, thumbnail); + String messageId = e2EHelper.sendFileMessage(to, file, thumbnail, caption).getData(); System.out.println("MessageId: " + messageId); } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/SendE2EImageMessageCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/SendE2EImageMessageCommand.java index 14a248f..754d2e2 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/SendE2EImageMessageCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/SendE2EImageMessageCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; @@ -40,14 +50,14 @@ public class SendE2EImageMessageCommand extends Command { private final FolderField imageFilePath; public SendE2EImageMessageCommand() { - super("Send End-to-End Encrypted Image Message", - "Encrypt standard input and send the message to the given ID. 'from' is the API identity and 'secret' is the API secret. Prints the message ID on success."); + super("[DEPRECATED] Send End-to-End Encrypted Image Message", + "Encrypt standard input and send the message to the given ID. 'gatewayId' is the API identity and 'secret' is the API secret. Prints the message ID on success."); - this.toField = this.createThreemaId("to", true); - this.fromField = this.createThreemaId("from", true); - this.secretField = this.createTextField("secret", true); - this.privateKeyField = this.createPrivateKeyField("privateKey", true); - this.imageFilePath = this.createFolderField("imageFilePath", true); + this.toField = this.createThreemaId("to"); + this.fromField = this.createThreemaId("gatewayId"); + this.secretField = this.createTextField("secret"); + this.privateKeyField = this.createPrivateKeyField("privateKey"); + this.imageFilePath = this.createFolderField("imageFilePath"); } @Override @@ -59,7 +69,7 @@ protected void execute() throws Exception { Path imageFilePath = this.imageFilePath.getValue(); E2EHelper e2EHelper = new E2EHelper(this.createConnector(from, secret), privateKey); - String messageId = e2EHelper.sendImageMessage(to, imageFilePath.toString()); + String messageId = e2EHelper.sendImageMessage(to, imageFilePath.toString()).getData(); System.out.println("MessageId: " + messageId); } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/SendE2ETextMessageCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/SendE2ETextMessageCommand.java index 1aaf576..71d9d8a 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/SendE2ETextMessageCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/SendE2ETextMessageCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; @@ -37,9 +47,9 @@ public class SendE2ETextMessageCommand extends Command { public SendE2ETextMessageCommand() { super("Send End-to-End Encrypted Text Message", - "Encrypt standard input and send the message to the given ID. 'from' is the API identity and 'secret' is the API secret. Prints the message ID on success."); + "Encrypt standard input and send the message to the given ID. 'gatewayId' is the API identity and 'secret' is the API secret. Prints the message ID on success."); this.threemaId = this.createThreemaId("to"); - this.fromField = this.createThreemaId("from"); + this.fromField = this.createThreemaId("gatewayId"); this.secretField = this.createTextField("secret"); this.privateKeyField = this.createPrivateKeyField("privateKey"); } @@ -54,7 +64,7 @@ protected void execute() throws Exception { String text = this.readStream(System.in, "UTF-8").trim(); E2EHelper e2EHelper = new E2EHelper(this.createConnector(from, secret), privateKey); - String messageId = e2EHelper.sendTextMessage(to, text); + String messageId = e2EHelper.sendTextMessage(to, text).getData(); System.out.println("MessageId: " + messageId); } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/SendSimpleMessageCommand.java b/source/src/main/java/ch/threema/apitool/console/commands/SendSimpleMessageCommand.java index 1fbef43..465b5b2 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/SendSimpleMessageCommand.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/SendSimpleMessageCommand.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands; @@ -35,10 +45,10 @@ public class SendSimpleMessageCommand extends Command { public SendSimpleMessageCommand() { super("Send Simple Message", - "Send a message from standard input with server-side encryption to the given ID. 'from' is the API identity and 'secret' is the API secret. Returns the message ID on success."); + "Send a message from standard input with server-side encryption to the given ID. 'gatewayId' is the API identity and 'secret' is the API secret. Returns the message ID on success."); this.threemaId = this.createThreemaId("to"); - this.fromField = this.createThreemaId("from"); + this.fromField = this.createThreemaId("gatewayId"); this.secretField = this.createTextField("secret"); } @@ -51,7 +61,7 @@ protected void execute() throws Exception { String text = readStream(System.in, "UTF-8").trim(); APIConnector apiConnector = this.createConnector(from, secret); - String messageId = apiConnector.sendTextMessageSimple(to, text); + String messageId = apiConnector.sendTextMessageSimple(to, text).getData(); System.out.println(messageId); } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/ByteArrayField.java b/source/src/main/java/ch/threema/apitool/console/commands/fields/ByteArrayField.java index f200c13..c3556ac 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/ByteArrayField.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/fields/ByteArrayField.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,11 +26,15 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands.fields; -import ch.threema.apitool.DataUtils; +import ch.threema.apitool.utils.DataUtils; public class ByteArrayField extends Field { public ByteArrayField(String key, boolean required) { diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/Field.java b/source/src/main/java/ch/threema/apitool/console/commands/fields/Field.java index 7655c5c..e909cdc 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/Field.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/fields/Field.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands.fields; @@ -50,12 +60,14 @@ public String getKey() { return this.key; } - public boolean isValid() throws RequiredCommandFieldMissingException, InvalidCommandFieldValueException { - if(this.isRequired() && this.value == null) { - throw new RequiredCommandFieldMissingException("required field " + this.key + " not set"); + public boolean isValid() + throws RequiredCommandFieldMissingException, InvalidCommandFieldValueException { + if (this.isRequired() && this.value == null) { + throw new RequiredCommandFieldMissingException( + "required field " + this.key + " not set"); } - if(!this.validate()) { + if (!this.validate()) { throw new InvalidCommandFieldValueException("field " + this.key + " value invalid"); } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/FileField.java b/source/src/main/java/ch/threema/apitool/console/commands/fields/FileField.java index 8b9b649..64aa590 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/FileField.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/fields/FileField.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands.fields; @@ -33,7 +43,7 @@ public FileField(String key, boolean required) { } public File getValue() { - if(this.value != null) { + if (this.value != null) { return new File(this.value); } @@ -42,8 +52,6 @@ public File getValue() { @Override protected boolean validate() { - return !this.isRequired() - || (this.value != null - && new File(this.value).isFile()); + return !this.isRequired() || (this.value != null && new File(this.value).isFile()); } } diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/FolderField.java b/source/src/main/java/ch/threema/apitool/console/commands/fields/FolderField.java index 194801d..af2c898 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/FolderField.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/fields/FolderField.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands.fields; diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/KeyField.java b/source/src/main/java/ch/threema/apitool/console/commands/fields/KeyField.java index b816dcd..99aaec1 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/KeyField.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/fields/KeyField.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,12 +26,16 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands.fields; -import ch.threema.apitool.DataUtils; -import ch.threema.apitool.Key; +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.Key; import ch.threema.apitool.exceptions.InvalidKeyException; import java.io.File; @@ -36,8 +46,8 @@ public KeyField(String key, boolean required) { super(key, required); } - - byte[] readKey(String argument, String expectedKeyType) throws IOException, InvalidKeyException { + byte[] readKey(String argument, String expectedKeyType) + throws IOException, InvalidKeyException { Key key; // Try to open a file with that name diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/PrivateKeyField.java b/source/src/main/java/ch/threema/apitool/console/commands/fields/PrivateKeyField.java index 9e87e6b..9a15228 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/PrivateKeyField.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/fields/PrivateKeyField.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,11 +26,15 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands.fields; -import ch.threema.apitool.Key; +import ch.threema.apitool.types.Key; import ch.threema.apitool.exceptions.InvalidKeyException; public class PrivateKeyField extends KeyField { diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/PublicKeyField.java b/source/src/main/java/ch/threema/apitool/console/commands/fields/PublicKeyField.java index 42b8c5a..a033c13 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/PublicKeyField.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/fields/PublicKeyField.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,17 +26,22 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands.fields; -import ch.threema.apitool.Key; +import ch.threema.apitool.types.Key; import ch.threema.apitool.exceptions.InvalidKeyException; public class PublicKeyField extends KeyField { public PublicKeyField(String key, boolean required) { super(key, required); } + public byte[] getValue() throws InvalidKeyException { try { return this.readKey(this.value, Key.KeyType.PUBLIC); diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/TextField.java b/source/src/main/java/ch/threema/apitool/console/commands/fields/TextField.java index 271e0fc..ccde212 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/TextField.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/fields/TextField.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands.fields; diff --git a/source/src/main/java/ch/threema/apitool/console/commands/fields/ThreemaIDField.java b/source/src/main/java/ch/threema/apitool/console/commands/fields/ThreemaIDField.java index a874cc0..46573ac 100644 --- a/source/src/main/java/ch/threema/apitool/console/commands/fields/ThreemaIDField.java +++ b/source/src/main/java/ch/threema/apitool/console/commands/fields/ThreemaIDField.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.console.commands.fields; diff --git a/source/src/main/java/ch/threema/apitool/exceptions/ApiException.java b/source/src/main/java/ch/threema/apitool/exceptions/ApiException.java new file mode 100644 index 0000000..622b865 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/exceptions/ApiException.java @@ -0,0 +1,114 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.exceptions; + +import java.net.http.HttpHeaders; + +public class ApiException extends Exception { + private int code = 0; + private HttpHeaders responseHeaders = null; + private String responseBody = null; + + public ApiException() {} + + public ApiException(Throwable throwable) { + super(throwable); + } + + public ApiException(String message) { + super(message); + } + + public ApiException(String message, Throwable throwable, int code, HttpHeaders responseHeaders, + String responseBody) { + super(message, throwable); + this.code = code; + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + public ApiException(String message, int code, HttpHeaders responseHeaders, + String responseBody) { + this(message, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException(String message, Throwable throwable, int code, + HttpHeaders responseHeaders) { + this(message, throwable, code, responseHeaders, null); + } + + public ApiException(int code, HttpHeaders responseHeaders, String responseBody) { + this((String) null, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException(int code, String message) { + super(message); + this.code = code; + } + + public ApiException(int code, String message, HttpHeaders responseHeaders, + String responseBody) { + this(code, message); + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + /** + * Get the HTTP status code. + * + * @return HTTP status code + */ + public int getCode() { + return code; + } + + /** + * Get the HTTP response headers. + * + * @return Headers as an HttpHeaders object + */ + public HttpHeaders getResponseHeaders() { + return responseHeaders; + } + + /** + * Get the HTTP response body. + * + * @return Response body in the form of string + */ + public String getResponseBody() { + return responseBody; + } +} diff --git a/source/src/main/java/ch/threema/apitool/exceptions/BadMessageException.java b/source/src/main/java/ch/threema/apitool/exceptions/BadMessageException.java index 1b4099d..e2053ae 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/BadMessageException.java +++ b/source/src/main/java/ch/threema/apitool/exceptions/BadMessageException.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.exceptions; @@ -28,6 +38,11 @@ * Exception that gets thrown if a message has a bad/illegal format after it has been decrypted. */ public class BadMessageException extends MessageParseException { + public BadMessageException() { + + } - private static final long serialVersionUID = 4812096297596964107L; + public BadMessageException(String msg) { + super(msg); + } } diff --git a/source/src/main/java/ch/threema/apitool/exceptions/DecryptionFailedException.java b/source/src/main/java/ch/threema/apitool/exceptions/DecryptionFailedException.java index 1992d37..c8c2147 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/DecryptionFailedException.java +++ b/source/src/main/java/ch/threema/apitool/exceptions/DecryptionFailedException.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,14 +26,17 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.exceptions; /** - * Exception that gets thrown when decryption fails (because the keys are incorrect, or the data is corrupted). + * Exception that gets thrown when decryption fails (because the keys are incorrect, or the data is + * corrupted). */ public class DecryptionFailedException extends MessageParseException { - - private static final long serialVersionUID = 2523453399446307538L; } diff --git a/source/src/main/java/ch/threema/apitool/exceptions/InvalidCommandFieldValueException.java b/source/src/main/java/ch/threema/apitool/exceptions/InvalidCommandFieldValueException.java index 3aa47be..9a0f808 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/InvalidCommandFieldValueException.java +++ b/source/src/main/java/ch/threema/apitool/exceptions/InvalidCommandFieldValueException.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.exceptions; @@ -28,9 +38,7 @@ * Exception that gets thrown on a illegal call. */ public class InvalidCommandFieldValueException extends Exception { - private static final long serialVersionUID = -6293436769165519745L; - - public InvalidCommandFieldValueException(String message) { - super(message); - } + public InvalidCommandFieldValueException(String message) { + super(message); + } } diff --git a/source/src/main/java/ch/threema/apitool/exceptions/InvalidHexException.java b/source/src/main/java/ch/threema/apitool/exceptions/InvalidHexException.java new file mode 100644 index 0000000..3ea8bd7 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/exceptions/InvalidHexException.java @@ -0,0 +1,45 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.exceptions; + +/** + * Exception that gets thrown when a hex-encoded value could not be decoded (e.g. because it + * contains non-hex characters or because the character length is not even). + */ +public class InvalidHexException extends RuntimeException { + public InvalidHexException(String s) { + super(s); + } +} diff --git a/source/src/main/java/ch/threema/apitool/exceptions/InvalidKeyException.java b/source/src/main/java/ch/threema/apitool/exceptions/InvalidKeyException.java index f213b55..79717ff 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/InvalidKeyException.java +++ b/source/src/main/java/ch/threema/apitool/exceptions/InvalidKeyException.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.exceptions; @@ -28,9 +38,7 @@ * Exception that gets thrown when an invalid key has been specified (e.g. wrong length). */ public class InvalidKeyException extends Exception { - private static final long serialVersionUID = 7585373757748175309L; - - public InvalidKeyException(String s) { - super(s); - } + public InvalidKeyException(String s) { + super(s); + } } diff --git a/source/src/main/java/ch/threema/apitool/exceptions/MessageParseException.java b/source/src/main/java/ch/threema/apitool/exceptions/MessageParseException.java index aaed853..e6aa212 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/MessageParseException.java +++ b/source/src/main/java/ch/threema/apitool/exceptions/MessageParseException.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.exceptions; @@ -28,6 +38,11 @@ * Base class for exceptions that may occur when parsing/decrypting an encrypted Threema message. */ public class MessageParseException extends Exception { + public MessageParseException() { + + } - private static final long serialVersionUID = 6829629439344637547L; + public MessageParseException(String msg) { + super(msg); + } } diff --git a/source/src/main/java/ch/threema/apitool/exceptions/NotAllowedException.java b/source/src/main/java/ch/threema/apitool/exceptions/NotAllowedException.java index 9927a0f..4223579 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/NotAllowedException.java +++ b/source/src/main/java/ch/threema/apitool/exceptions/NotAllowedException.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.exceptions; @@ -28,6 +38,4 @@ * Exception that gets thrown on a illegal call. */ public class NotAllowedException extends Exception { - - private static final long serialVersionUID = 3032360799153840206L; } diff --git a/source/src/main/java/ch/threema/apitool/exceptions/RequiredCommandFieldMissingException.java b/source/src/main/java/ch/threema/apitool/exceptions/RequiredCommandFieldMissingException.java index 559eabe..1737b60 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/RequiredCommandFieldMissingException.java +++ b/source/src/main/java/ch/threema/apitool/exceptions/RequiredCommandFieldMissingException.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.exceptions; @@ -28,9 +38,7 @@ * Exception that gets thrown on a illegal call. */ public class RequiredCommandFieldMissingException extends Exception { - private static final long serialVersionUID = 2273462399743084938L; - - public RequiredCommandFieldMissingException(String message) { - super(message); - } + public RequiredCommandFieldMissingException(String message) { + super(message); + } } diff --git a/source/src/main/java/ch/threema/apitool/exceptions/UnsupportedMessageTypeException.java b/source/src/main/java/ch/threema/apitool/exceptions/UnsupportedMessageTypeException.java index 76c9260..069c1bc 100644 --- a/source/src/main/java/ch/threema/apitool/exceptions/UnsupportedMessageTypeException.java +++ b/source/src/main/java/ch/threema/apitool/exceptions/UnsupportedMessageTypeException.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,15 +26,17 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.exceptions; /** - * Exception that gets thrown when an attempt has been made to decrypt a message - * of a type that is not supported by this library. + * Exception that gets thrown when an attempt has been made to decrypt a message of a type that is + * not supported by this library. */ public class UnsupportedMessageTypeException extends MessageParseException { - - private static final long serialVersionUID = -686063411249892256L; } diff --git a/source/src/main/java/ch/threema/apitool/helpers/E2EHelper.java b/source/src/main/java/ch/threema/apitool/helpers/E2EHelper.java index c8a2d7f..185b3d7 100644 --- a/source/src/main/java/ch/threema/apitool/helpers/E2EHelper.java +++ b/source/src/main/java/ch/threema/apitool/helpers/E2EHelper.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,10 +26,32 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.helpers; +import ch.threema.apitool.APIConnector; +import ch.threema.apitool.CryptTool; +import ch.threema.apitool.exceptions.*; +import ch.threema.apitool.messages.*; +import ch.threema.apitool.types.FileRenderingType; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.results.CapabilityResult; +import ch.threema.apitool.results.EncryptResult; +import ch.threema.apitool.results.UploadResult; +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.utils.ApiResponse; +import ch.threema.apitool.utils.ProtocolConstants; +import com.neilalexander.jnacl.NaCl; +import org.apache.commons.io.IOUtils; +import org.apache.tika.Tika; +import org.json.JSONArray; + import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -32,286 +60,1207 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; - -import org.apache.commons.io.IOUtils; - -import com.neilalexander.jnacl.NaCl; - -import ch.threema.apitool.APIConnector; -import ch.threema.apitool.CryptTool; -import ch.threema.apitool.exceptions.InvalidKeyException; -import ch.threema.apitool.exceptions.MessageParseException; -import ch.threema.apitool.exceptions.NotAllowedException; -import ch.threema.apitool.messages.FileMessage; -import ch.threema.apitool.messages.ImageMessage; -import ch.threema.apitool.messages.ThreemaMessage; -import ch.threema.apitool.results.CapabilityResult; -import ch.threema.apitool.results.EncryptResult; -import ch.threema.apitool.results.UploadResult; +import java.util.Map; +import java.util.stream.Collectors; /** * Helper to handle Threema end-to-end encryption. */ public class E2EHelper { - private final APIConnector apiConnector; - private final byte[] privateKey; - - public class ReceiveMessageResult { - private final String messageId; - private final ThreemaMessage message; - protected List files = new ArrayList<>(); - protected List errors = new ArrayList<>(); - - public ReceiveMessageResult(String messageId, ThreemaMessage message) { - this.messageId = messageId; - this.message = message; - } - - public List getFiles() { - return this.files; - } - - public List getErrors() { - return this.errors; - } - - public String getMessageId() { - return messageId; - } - - public ThreemaMessage getMessage() { - return message; - } - } - - public E2EHelper(APIConnector apiConnector, byte[] privateKey) { - this.apiConnector = apiConnector; - this.privateKey = privateKey; - } - - /** - * Encrypt a text message and send it to the given recipient. - * - * @param threemaId target Threema ID - * @param text the text to send - * @return generated message ID - */ - public String sendTextMessage(String threemaId, String text) throws Exception { - // fetch public key - byte[] publicKey = this.apiConnector.lookupKey(threemaId); - - if (publicKey == null) { - throw new Exception("invalid threema id"); - } - EncryptResult res = CryptTool.encryptTextMessage(text, this.privateKey, publicKey); - - return this.apiConnector.sendE2EMessage(threemaId, res.getNonce(), res.getResult()); - } - - /** - * Encrypt an image message and send it to the given recipient. - * - * @param threemaId target Threema ID - * @param imageFilePath path to read image data from - * @return generated message ID - * @throws NotAllowedException - * @throws IOException - * @throws InvalidKeyException - */ - public String sendImageMessage(String threemaId, String imageFilePath) - throws NotAllowedException, IOException, InvalidKeyException { - // fetch public key - byte[] publicKey = this.apiConnector.lookupKey(threemaId); - - if (publicKey == null) { - throw new InvalidKeyException("invalid threema id"); - } - - // check capability of a key - CapabilityResult capabilityResult = this.apiConnector.lookupKeyCapability(threemaId); - if (capabilityResult == null || !capabilityResult.canImage()) { - throw new NotAllowedException(); - } - - byte[] fileData = Files.readAllBytes(Paths.get(imageFilePath)); - if (fileData == null) { - throw new IOException("invalid file"); - } - - // encrypt the image - EncryptResult encryptResult = CryptTool.encrypt(fileData, this.privateKey, publicKey); - - // upload the image - UploadResult uploadResult = apiConnector.uploadFile(encryptResult); - - if (!uploadResult.isSuccess()) { - throw new IOException("could not upload file (upload response " + uploadResult.getResponseCode() + ")"); - } - - // send it - EncryptResult imageMessage = CryptTool.encryptImageMessage(encryptResult, uploadResult, privateKey, publicKey); - - return apiConnector.sendE2EMessage(threemaId, imageMessage.getNonce(), imageMessage.getResult()); - } - - /** - * Encrypt a file message and send it to the given recipient. - * The thumbnailMessagePath can be null. - * - * @param threemaId target Threema ID - * @param fileMessageFile the file to be sent - * @param thumbnailMessagePath file for thumbnail; if not set, no thumbnail will be sent - * @return generated message ID - * @throws InvalidKeyException - * @throws IOException - * @throws NotAllowedException - */ - public String sendFileMessage(String threemaId, File fileMessageFile, File thumbnailMessagePath) - throws InvalidKeyException, IOException, NotAllowedException { - // fetch public key - byte[] publicKey = this.apiConnector.lookupKey(threemaId); - - if (publicKey == null) { - throw new InvalidKeyException("invalid threema id"); - } - - // check capability of a key - CapabilityResult capabilityResult = this.apiConnector.lookupKeyCapability(threemaId); - if (capabilityResult == null || !capabilityResult.canImage()) { - throw new NotAllowedException(); - } - - if (!fileMessageFile.isFile()) { - throw new IOException("invalid file"); - } - - byte[] fileData = this.readFile(fileMessageFile); - - if (fileData == null) { - throw new IOException("invalid file"); - } - - // encrypt the image - EncryptResult encryptResult = CryptTool.encryptFileData(fileData); - - // upload the image - UploadResult uploadResult = apiConnector.uploadFile(encryptResult); - - if (!uploadResult.isSuccess()) { - throw new IOException("could not upload file (upload response " + uploadResult.getResponseCode() + ")"); - } - - UploadResult uploadResultThumbnail = null; - - if (thumbnailMessagePath != null && thumbnailMessagePath.isFile()) { - byte[] thumbnailData = this.readFile(thumbnailMessagePath); - if (thumbnailData == null) { - throw new IOException("invalid thumbnail file"); - } - - // encrypt the thumbnail - EncryptResult encryptResultThumbnail = CryptTool.encryptFileThumbnailData(fileData, - encryptResult.getSecret()); - - // upload the thumbnail - uploadResultThumbnail = this.apiConnector.uploadFile(encryptResultThumbnail); - } - - // send it - EncryptResult fileMessage = CryptTool.encryptFileMessage(encryptResult, uploadResult, - Files.probeContentType(fileMessageFile.toPath()), fileMessageFile.getName(), - (int) fileMessageFile.length(), uploadResultThumbnail, privateKey, publicKey); - - return this.apiConnector.sendE2EMessage(threemaId, fileMessage.getNonce(), fileMessage.getResult()); - } - - /** - * Decrypt a Message and download the blobs of the Message (e.g. image or file) - * - * @param threemaId Threema ID of the sender - * @param messageId Message ID - * @param box Encrypted box data of the file/image message - * @param nonce Nonce that was used for message encryption - * @param outputFolder Output folder for storing decrypted images/files - * @return result of message reception - * @throws IOException - * @throws InvalidKeyException - * @throws MessageParseException - */ - public ReceiveMessageResult receiveMessage(String threemaId, String messageId, byte[] box, byte[] nonce, - Path outputFolder) throws IOException, InvalidKeyException, MessageParseException { - // fetch public key - byte[] publicKey = this.apiConnector.lookupKey(threemaId); - - if (publicKey == null) { - throw new InvalidKeyException("invalid threema id"); - } - - ThreemaMessage message = CryptTool.decryptMessage(box, this.privateKey, publicKey, nonce); - if (message == null) { - return null; - } - - ReceiveMessageResult result = new ReceiveMessageResult(messageId, message); - - if (message instanceof ImageMessage) { - // download image - ImageMessage imageMessage = (ImageMessage) message; - byte[] fileData = this.apiConnector.downloadFile(imageMessage.getBlobId()); - - if (fileData == null) { - throw new MessageParseException(); - } - - byte[] decryptedFileContent = CryptTool.decrypt(fileData, privateKey, publicKey, imageMessage.getNonce()); - File imageFile = new File(outputFolder.toString() + "/" + messageId + ".jpg"); - FileOutputStream fos = new FileOutputStream(imageFile); - fos.write(decryptedFileContent); - fos.close(); - - result.files.add(imageFile); - } else if (message instanceof FileMessage) { - // download file - FileMessage fileMessage = (FileMessage) message; - byte[] fileData = this.apiConnector.downloadFile(fileMessage.getBlobId()); - - byte[] decryptedFileData = CryptTool.decryptFileData(fileData, fileMessage.getEncryptionKey()); - File file = new File(outputFolder.toString() + "/" + messageId + "-" + fileMessage.getFileName()); - FileOutputStream fos = new FileOutputStream(file); - fos.write(decryptedFileData); - fos.close(); - - result.files.add(file); - - if (fileMessage.getThumbnailBlobId() != null) { - byte[] thumbnailData = this.apiConnector.downloadFile(fileMessage.getThumbnailBlobId()); - - byte[] decryptedThumbnailData = CryptTool.decryptFileThumbnailData(thumbnailData, - fileMessage.getEncryptionKey()); - File thumbnailFile = new File(outputFolder.toString() + "/" + messageId + "-thumbnail.jpg"); - fos = new FileOutputStream(thumbnailFile); - fos.write(decryptedThumbnailData); - fos.close(); - - result.files.add(thumbnailFile); - } - } - - return result; - } - - /** - * Read file data from file - store at offset in byte array for in-place encryption - * - * @param file input file - * @return file data with padding/offset for NaCl - * @throws IOException - */ - private byte[] readFile(File file) throws IOException { - int fileLength = (int) file.length(); - byte[] fileData = new byte[fileLength + NaCl.BOXOVERHEAD]; - IOUtils.readFully(new FileInputStream(file), fileData, NaCl.BOXOVERHEAD, fileLength); - return fileData; - } + private final APIConnector apiConnector; + private final byte[] privateKey; + + public static class ReceiveMessageResult { + private final String messageId; + private final ThreemaMessage message; + protected List files = new ArrayList<>(); + protected List errors = new ArrayList<>(); + + public ReceiveMessageResult(String messageId, ThreemaMessage message) { + this.messageId = messageId; + this.message = message; + } + + public List getFiles() { + return this.files; + } + + public List getErrors() { + return this.errors; + } + + public String getMessageId() { + return messageId; + } + + public ThreemaMessage getMessage() { + return message; + } + + @Override + public String toString() { + return "ReceiveMessageResult{" + "messageId='" + messageId + '\'' + ", message=" + + message + ", files=" + files + ", errors=" + errors + '}'; + } + } + + public E2EHelper(APIConnector apiConnector, byte[] privateKey) { + this.apiConnector = apiConnector; + this.privateKey = privateKey; + } + + /* + * ===================== Conversation Messages ===================== + */ + + /** + * Encrypt a text message and send it to the given recipient. + * + * @param threemaId target Threema ID + * @param text the text to send + * @return generated message ID + */ + public ApiResponse sendTextMessage(String threemaId, String text) throws Exception { + // fetch public key + byte[] publicKey = this.apiConnector.lookupKey(threemaId); + + if (publicKey == null) { + throw new Exception("invalid threema id"); + } + if (text.isEmpty()) { + throw new Exception("no text provided"); + } + EncryptResult res = CryptTool.encryptTextMessage(text, this.privateKey, publicKey); + + return this.apiConnector.sendE2EMessage(threemaId, res.getNonce(), res.getResult()); + } + + /** + * Encrypt a text message and send it to the given recipient. + * + * @param threemaId target Threema ID + * @param lat the location latitude + * @param lng the location longitude + * @param poiName the poi name + * @param poiAddress the location address + * @return generated message ID + */ + public ApiResponse sendLocationMessage(String threemaId, String lat, String lng, + String poiName, String poiAddress) throws Exception { + return sendLocationMessage(threemaId, lat, lng, null, poiName, poiAddress); + } + + /** + * Encrypt a text message and send it to the given recipient. + * + * @param threemaId target Threema ID + * @param lat the location latitude + * @param lng the location longitude + * @param accuracy the location accuracy + * @param poiName the poi name + * @param poiAddress the location address + * @return generated message ID + */ + public ApiResponse sendLocationMessage(String threemaId, String lat, String lng, + Float accuracy, String poiName, String poiAddress) throws Exception { + // fetch public key + byte[] publicKey = this.apiConnector.lookupKey(threemaId); + + if (publicKey == null) { + throw new Exception("invalid threema id"); + } + if (lat.isEmpty()) { + throw new IOException("invalid latitude"); + } + if (lng.isEmpty()) { + throw new IOException("invalid longitude"); + } + if (poiAddress.isEmpty()) { + throw new IOException("invalid address"); + } + + EncryptResult res = CryptTool.encryptLocationMessage(lat, lng, accuracy, poiName, + poiAddress, this.privateKey, publicKey); + + return this.apiConnector.sendE2EMessage(threemaId, res.getNonce(), res.getResult()); + + } + + /** + * Encrypt a file message and send it to the given group. The thumbnailMessagePath can be null. + * + * @param threemaIds target Threema IDs + * @param groupId target Threema Group ID + * @param text Message text + * @return generated message ID + */ + public ApiResponse sendGroupTextMessage(List threemaIds, GroupId groupId, + String text) throws InvalidKeyException, IOException, ApiException { + if (groupId == null) { + throw new IOException("Invalid groupId"); + } + + threemaIds = threemaIds.stream().distinct().collect(Collectors.toList()); + + HashMap pubkeys = lookupPubkeys(threemaIds); + + if (text.isEmpty()) { + throw new IOException("invalid text"); + } + + var nonces = new byte[threemaIds.size()][]; + var boxes = new byte[threemaIds.size()][]; + + for (int i = 0; i < threemaIds.size(); i++) { + var res = CryptTool.encryptGroupTextMessage(groupId, text, this.privateKey, + pubkeys.get(threemaIds.get(i))); + + nonces[i] = res.getNonce(); + boxes[i] = res.getResult(); + } + + return this.apiConnector.sendE2EBulkMessage(threemaIds.toArray(String[]::new), nonces, + boxes); + } + + /** + * Encrypt a file message and send it to the given group. The thumbnailMessagePath can be null. + * + * @param threemaIds target Threema IDs + * @param groupId target Threema Group ID + * @param lat The geographic latitude + * @param lng The geographic longitude + * @param poiName The location name + * @param poiAddress The location address + * @return generated message ID + */ + public ApiResponse sendGroupLocationMessage(List threemaIds, GroupId groupId, + String lat, String lng, String poiName, String poiAddress) + throws InvalidKeyException, IOException, ApiException { + return sendGroupLocationMessage(threemaIds, groupId, lat, lng, null, poiName, poiAddress); + } + + /** + * Encrypt a file message and send it to the given group. The thumbnailMessagePath can be null. + * + * @param threemaIds target Threema IDs + * @param groupId target Threema Group ID + * @param lat The geographic latitude + * @param lng The geographic longitude + * @param accuracy The location accuracy + * @param poiName The location name + * @param poiAddress The location address + * @return generated message ID + */ + public ApiResponse sendGroupLocationMessage(List threemaIds, GroupId groupId, + String lat, String lng, Float accuracy, String poiName, String poiAddress) + throws InvalidKeyException, IOException, ApiException { + if (groupId == null) { + throw new IOException("Invalid groupId"); + } + + threemaIds = threemaIds.stream().distinct().collect(Collectors.toList()); + + HashMap pubkeys = lookupPubkeys(threemaIds); + + if (lat.isEmpty()) { + throw new IOException("invalid latitude"); + } + if (lng.isEmpty()) { + throw new IOException("invalid longitude"); + } + if (poiAddress.isEmpty()) { + throw new IOException("invalid address"); + } + + var nonces = new byte[threemaIds.size()][]; + var boxes = new byte[threemaIds.size()][]; + + for (int i = 0; i < threemaIds.size(); i++) { + var res = CryptTool.encryptGroupLocationMessage(groupId, lat, lng, accuracy, poiName, + poiAddress, this.privateKey, pubkeys.get(threemaIds.get(i))); + + nonces[i] = res.getNonce(); + boxes[i] = res.getResult(); + } + + return this.apiConnector.sendE2EBulkMessage(threemaIds.toArray(String[]::new), nonces, + boxes); + } + + /** + * Encrypt an image message and send it to the given recipient. + * + * @param threemaId target Threema ID + * @param imageFilePath path to read image data from + * @return generated message ID + * @deprecated + */ + @Deprecated + public ApiResponse sendImageMessage(String threemaId, String imageFilePath) + throws NotAllowedException, IOException, InvalidKeyException, ApiException { + // fetch public key + byte[] publicKey = this.apiConnector.lookupKey(threemaId); + + if (publicKey == null) { + throw new InvalidKeyException("invalid threema id"); + } + + // check capability of a key + CapabilityResult capabilityResult = this.apiConnector.lookupKeyCapability(threemaId); + if (capabilityResult == null || !capabilityResult.canImage()) { + throw new NotAllowedException(); + } + + byte[] fileData = Files.readAllBytes(Paths.get(imageFilePath)); + + // encrypt the image + EncryptResult encryptResult = CryptTool.encrypt(fileData, this.privateKey, publicKey); + + // upload the image + UploadResult uploadResult = apiConnector.uploadFile(encryptResult); + + if (!uploadResult.isSuccess()) { + throw new IOException("could not upload file (upload response " + + uploadResult.getResponseCode() + ")"); + } + + // send it + EncryptResult imageMessage = CryptTool.encryptImageMessage(encryptResult, uploadResult, + privateKey, publicKey); + + return apiConnector.sendE2EMessage(threemaId, imageMessage.getNonce(), + imageMessage.getResult()); + } + + /** + * Encrypt a file message and send it to the given recipient. The thumbnailMessagePath can be + * null. + * + * @param threemaId target Threema ID + * @param fileMessageFile the file to be sent + * @return generated message ID + */ + public ApiResponse sendFileMessage(String threemaId, File fileMessageFile) + throws InvalidKeyException, IOException, NotAllowedException, ApiException { + return this.sendFileMessage(threemaId, fileMessageFile, null, null); + } + + /** + * Encrypt a file message and send it to the given recipient. The thumbnailMessagePath can be + * null. + * + * @param threemaId target Threema ID + * @param fileMessageFile the file to be sent + * @param thumbnailMessagePath file for thumbnail; if not set, no thumbnail will be sent + * @param caption caption for the file message; if not set, no caption will be attached + * @return generated message ID + */ + public ApiResponse sendFileMessage(String threemaId, File fileMessageFile, + File thumbnailMessagePath, String caption) + throws InvalidKeyException, IOException, NotAllowedException, ApiException { + return this.sendFileMessage(threemaId, fileMessageFile, thumbnailMessagePath, caption, + FileRenderingType.FILE); + } + + /** + * Encrypt a file message and send it to the given recipient. The thumbnailMessagePath can be + * null. + * + * @param threemaId target Threema ID + * @param fileMessageFile the file to be sent + * @param thumbnailMessagePath file for thumbnail; if not set, no thumbnail will be sent + * @param caption caption for the file message; if not set, no caption will be attached + * @param renderingType file rendering type + * @return generated message ID + */ + public ApiResponse sendFileMessage(String threemaId, File fileMessageFile, + File thumbnailMessagePath, String caption, FileRenderingType renderingType) + throws InvalidKeyException, IOException, NotAllowedException, ApiException { + return this.sendFileMessage(threemaId, fileMessageFile, thumbnailMessagePath, caption, + renderingType, null, null); + } + + /** + * Encrypt a file message and send it to the given recipient. The thumbnailMessageFile can be + * null. + * + * @param threemaId target Threema ID + * @param fileMessageFile the file to be sent + * @param thumbnailMessageFile file for thumbnail; if not set, no thumbnail will be sent + * @param caption caption for the file message; if not set, no caption will be attached + * @param renderingType file rendering type + * @param correlationId media correlation ID + * @param metadata FileMessage metadata + * @return generated message ID + */ + public ApiResponse sendFileMessage(String threemaId, File fileMessageFile, + File thumbnailMessageFile, String caption, FileRenderingType renderingType, + String correlationId, Map metadata) + throws InvalidKeyException, IOException, NotAllowedException, ApiException { + // fetch public key + byte[] publicKey = this.apiConnector.lookupKey(threemaId); + + if (publicKey == null) { + throw new InvalidKeyException("invalid threema id"); + } + if (!(renderingType == FileRenderingType.FILE || renderingType == FileRenderingType.MEDIA + || renderingType == FileRenderingType.STICKER)) { + throw new IOException("invalid rendering type"); + } + if (!fileMessageFile.isFile()) { + throw new IOException("invalid file"); + } + + // check capability of a key + CapabilityResult capabilityResult = this.apiConnector.lookupKeyCapability(threemaId); + if (capabilityResult == null || !capabilityResult.canFile()) { + throw new NotAllowedException(); + } + + byte[] fileData = this.readFile(fileMessageFile); + + // encrypt the image + EncryptResult encryptResult = CryptTool.encryptFileData(fileData); + + // upload the image + UploadResult uploadResult = apiConnector.uploadFile(encryptResult); + + if (!uploadResult.isSuccess()) { + throw new IOException("could not upload file (upload response " + + uploadResult.getResponseCode() + ")"); + } + + UploadResult uploadResultThumbnail = null; + String thumbnailMimeType = null; + + if (thumbnailMessageFile != null && thumbnailMessageFile.isFile()) { + byte[] thumbnailData = this.readFile(thumbnailMessageFile); + + // encrypt the thumbnail + EncryptResult encryptResultThumbnail = CryptTool.encryptFileThumbnailData(thumbnailData, + encryptResult.getSecret()); + + // upload the thumbnail + uploadResultThumbnail = this.apiConnector.uploadFile(encryptResultThumbnail); + thumbnailMimeType = getMimeType(thumbnailMessageFile); + } + + String mimeType = getMimeType(fileMessageFile); + + // send it + EncryptResult fileMessage = CryptTool.encryptFileMessage(uploadResult.getBlobId(), + uploadResultThumbnail != null ? uploadResultThumbnail.getBlobId() : null, + thumbnailMimeType, encryptResult.getSecret(), mimeType, + fileMessageFile.getName(), (int) fileMessageFile.length(), caption, + renderingType, correlationId, metadata, privateKey, publicKey); + + return this.apiConnector.sendE2EMessage(threemaId, fileMessage.getNonce(), + fileMessage.getResult()); + } + + /** + * Encrypt a ballot create message and send it to the given recipient. + * + * @param threemaId target Threema ID + * @param ballotId The poll ballot identifier + * @param description The group poll description + * @param state The group poll state + * @param votingMode The voting mode + * @param resultsDisclosureType The poll results disclosure type + * @param displayMode The display mode + * @param choices The available vote choices + * @param participants The poll participants + * @return generated message ID + */ + public ApiResponse sendBallotCreateMessage(String threemaId, byte[] ballotId, + String description, State state, VotingMode votingMode, + ResultsDisclosureType resultsDisclosureType, DisplayMode displayMode, + List choices, List participants) + throws InvalidKeyException, ApiException, IOException { + + // fetch public key + byte[] publicKey = this.apiConnector.lookupKey(threemaId); + + if (publicKey == null) { + throw new InvalidKeyException("invalid threema id"); + } + if (ballotId.length != ProtocolConstants.BALLOT_ID_LEN) { + throw new IOException("invalid ballotId"); + } + if (!(state == State.OPEN || state == State.CLOSED)) { + throw new IOException("invalid state"); + } + if (!(votingMode == VotingMode.SINGLE_CHOICE || votingMode == VotingMode.MULTIPLE_CHOICE)) { + throw new IOException("invalid votingMode"); + } + if (!(resultsDisclosureType == ResultsDisclosureType.CLOSED + || resultsDisclosureType == ResultsDisclosureType.INTERMEDIATE)) { + throw new IOException("invalid resultsDisclosureType"); + } + if (!(displayMode == DisplayMode.LIST || displayMode == DisplayMode.SUMMARY)) { + throw new IOException("invalid displayMode"); + } + if (choices.size() == 0) { + throw new IOException("please provide at least one choice"); + } + + var message = CryptTool.encryptBallotCreateMessage(ballotId, description, state, votingMode, + resultsDisclosureType, 0, displayMode, choices, participants, + this.privateKey, publicKey); + + return this.apiConnector.sendE2EMessage(threemaId, message.getNonce(), message.getResult()); + } + + /** + * Encrypt a ballot vote message and send it to the given recipient. + * + * @param threemaId target Threema ID + * @param creator Poll creator + * @param ballotId the poll ballot identifier + * @param votes the group poll votes + * @return generated message ID + */ + public ApiResponse sendBallotVoteMessage(String threemaId, byte[] creator, + byte[] ballotId, List votes) + throws InvalidKeyException, ApiException, IOException { + // fetch public key + byte[] publicKey = this.apiConnector.lookupKey(threemaId); + + if (publicKey == null) { + throw new InvalidKeyException("invalid threema id"); + } + if (creator.length != GroupId.CREATOR_ID_LEN) { + throw new IOException("invalid creator"); + } + if (ballotId.length != ProtocolConstants.BALLOT_ID_LEN) { + throw new IOException("invalid ballotId"); + } + if (votes.size() == 0) { + throw new IOException("please make at least one vote"); + } + + var message = CryptTool.encryptBallotVoteMessage(creator, ballotId, votes, this.privateKey, + publicKey); + + return this.apiConnector.sendE2EMessage(threemaId, message.getNonce(), message.getResult()); + } + + /** + * Encrypt a file message and send it to the given group. The thumbnailMessagePath can be null. + * + * @param threemaId target threema ID + * @param groupId target Threema Group ID + * @param fileMessageFile the file to be sent + * @return generated message ID + */ + public ApiResponse sendGroupFileMessage(List threemaId, GroupId groupId, + File fileMessageFile) + throws InvalidKeyException, IOException, NotAllowedException, ApiException { + return sendGroupFileMessage(threemaId, groupId, fileMessageFile, null, null, + FileRenderingType.FILE); + } + + /** + * Encrypt a file message and send it to the given group. The thumbnailMessagePath can be null. + * + * @param threemaId target threema ID + * @param groupId target Threema Group ID + * @param fileMessageFile the file to be sent + * @param thumbnailMessageFile file for thumbnail; if not set, no thumbnail will be sent + * @param caption caption for the file message; if not set, no caption will be attached + * @param renderingType file rendering type + * @return generated message ID + */ + public ApiResponse sendGroupFileMessage(List threemaId, GroupId groupId, + File fileMessageFile, File thumbnailMessageFile, String caption, + FileRenderingType renderingType) + throws InvalidKeyException, IOException, NotAllowedException, ApiException { + return sendGroupFileMessage(threemaId, groupId, fileMessageFile, thumbnailMessageFile, + caption, renderingType, null, null); + } + + /** + * Encrypt a file message and send it to the given group. The thumbnailMessageFile can be null. + * + * @param threemaIds target Threema IDs + * @param groupId target Threema Group ID + * @param fileMessageFile the file to be sent + * @param thumbnailMessageFile file for thumbnail; if not set, no thumbnail will be sent + * @param caption caption for the file message; if not set, no caption will be attached + * @param renderingType file rendering type + * @param correlationId media correlation ID + * @param metadata FileMessage metadata + * @return generated message ID + */ + public ApiResponse sendGroupFileMessage(List threemaIds, GroupId groupId, + File fileMessageFile, File thumbnailMessageFile, String caption, + FileRenderingType renderingType, String correlationId, + Map metadata) + throws InvalidKeyException, IOException, ApiException { + if (groupId == null) { + throw new IOException("Invalid groupId"); + } + + threemaIds = threemaIds.stream().distinct().collect(Collectors.toList()); + + HashMap pubkeys = lookupPubkeys(threemaIds); + + if (pubkeys.isEmpty()) { + throw new InvalidKeyException("invalid threema ids"); + } + + if (!fileMessageFile.isFile()) { + throw new IOException("invalid file"); + } + + byte[] fileData = this.readFile(fileMessageFile); + + // encrypt the file + EncryptResult encryptResult = CryptTool.encryptFileData(fileData); + + // upload the file + UploadResult uploadResult = apiConnector.uploadFile(encryptResult, true); + + if (!uploadResult.isSuccess()) { + throw new IOException("could not upload file (upload response " + + uploadResult.getResponseCode() + ")"); + } + + UploadResult uploadResultThumbnail = null; + String thumbnailMimeType = null; + + if (thumbnailMessageFile != null && thumbnailMessageFile.isFile()) { + byte[] thumbnailData = this.readFile(thumbnailMessageFile); + + // encrypt the thumbnail + EncryptResult encryptResultThumbnail = CryptTool.encryptFileThumbnailData(thumbnailData, + encryptResult.getSecret()); + + // upload the thumbnail + uploadResultThumbnail = this.apiConnector.uploadFile(encryptResultThumbnail, true); + thumbnailMimeType = getMimeType(thumbnailMessageFile); + } + + String mimeType = getMimeType(fileMessageFile); + + var nonces = new byte[threemaIds.size()][]; + var boxes = new byte[threemaIds.size()][]; + + for (int i = 0; i < threemaIds.size(); i++) { + var res = CryptTool.encryptGroupFileMessage(groupId, uploadResult.getBlobId(), + uploadResultThumbnail != null ? uploadResultThumbnail.getBlobId() + : null, + thumbnailMimeType, encryptResult.getSecret(), mimeType, + fileMessageFile.getName(), (int) fileMessageFile.length(), caption, + renderingType, correlationId, metadata, privateKey, + pubkeys.get(threemaIds.get(i))); + nonces[i] = res.getNonce(); + boxes[i] = res.getResult(); + } + + return this.apiConnector.sendE2EBulkMessage(threemaIds.toArray(String[]::new), nonces, + boxes); + } + + /** + * Encrypt a ballot create message and send it to the given group. + * + * @param groupId target Threema Group ID + * @param ballotId The poll ballot identifier + * @param description The group poll description + * @param state The group poll state + * @param votingMode The voting mode + * @param resultsDisclosureType The poll results disclosure type + * @param displayMode The display mode + * @param choices The available vote choices + * @param participants The poll participants + * @return generated message ID + */ + public ApiResponse sendGroupBallotCreateMessage(List threemaIds, + GroupId groupId, byte[] ballotId, String description, State state, + VotingMode votingMode, ResultsDisclosureType resultsDisclosureType, + DisplayMode displayMode, List choices, List participants) + throws InvalidKeyException, ApiException, IOException { + if (groupId == null) { + throw new IOException("Invalid groupId"); + } + + threemaIds = threemaIds.stream().distinct().collect(Collectors.toList()); + + HashMap pubkeys = lookupPubkeys(threemaIds); + + if (pubkeys.isEmpty()) { + throw new InvalidKeyException("invalid threema ids"); + } + if (ballotId.length != ProtocolConstants.BALLOT_ID_LEN) { + throw new IOException("invalid ballotId"); + } + if (!(state == State.OPEN || state == State.CLOSED)) { + throw new IOException("invalid state"); + } + if (!(votingMode == VotingMode.SINGLE_CHOICE || votingMode == VotingMode.MULTIPLE_CHOICE)) { + throw new IOException("invalid votingMode"); + } + if (!(resultsDisclosureType == ResultsDisclosureType.CLOSED + || resultsDisclosureType == ResultsDisclosureType.INTERMEDIATE)) { + throw new IOException("invalid resultsDisclosureType"); + } + if (!(displayMode == DisplayMode.LIST || displayMode == DisplayMode.SUMMARY)) { + throw new IOException("invalid displayMode"); + } + if (choices.size() == 0) { + throw new IOException("invalid choices"); + } + + var nonces = new byte[threemaIds.size()][]; + var boxes = new byte[threemaIds.size()][]; + + for (int i = 0; i < threemaIds.size(); i++) { + var res = CryptTool.encryptGroupBallotCreateMessage(groupId, ballotId, description, + state, votingMode, resultsDisclosureType, 0, displayMode, choices, + participants, this.privateKey, pubkeys.get(threemaIds.get(i))); + + nonces[i] = res.getNonce(); + boxes[i] = res.getResult(); + } + + return this.apiConnector.sendE2EBulkMessage(threemaIds.toArray(String[]::new), nonces, + boxes); + } + + /** + * Encrypt a ballot vote message and send it to the given group. + * + * @param threemaIds target Threema IDs + * @param groupId target Threema Group ID + * @param creator Poll creator + * @param ballotId the poll ballot identifier + * @param votes the group poll state + * @return generated message ID + */ + public ApiResponse sendGroupBallotVoteMessage(List threemaIds, + byte[] creator, GroupId groupId, byte[] ballotId, List votes) + throws InvalidKeyException, ApiException, IOException { + if (groupId == null) { + throw new IOException("Invalid groupId"); + } + + threemaIds = threemaIds.stream().distinct().collect(Collectors.toList()); + + HashMap pubkeys = lookupPubkeys(threemaIds); + + if (pubkeys.isEmpty()) { + throw new InvalidKeyException("invalid threema ids"); + } + + if (creator.length == 0) { + throw new IOException("invalid creator"); + } + if (ballotId.length != ProtocolConstants.BALLOT_ID_LEN) { + throw new IOException("invalid ballotId"); + } + if (votes.size() == 0) { + throw new IOException("invalid votes"); + } + + var nonces = new byte[threemaIds.size()][]; + var boxes = new byte[threemaIds.size()][]; + + for (int i = 0; i < threemaIds.size(); i++) { + var res = CryptTool.encryptGroupBallotVoteMessage(groupId, creator, ballotId, votes, + this.privateKey, pubkeys.get(threemaIds.get(i))); + + nonces[i] = res.getNonce(); + boxes[i] = res.getResult(); + } + + return this.apiConnector.sendE2EBulkMessage(threemaIds.toArray(String[]::new), nonces, + boxes); + } + + /* + * ============== Status Updates ============== + */ + + /** + * Encrypt a delivery receipt and send it to the original sender. + * + * @param threemaId target Threema ID + * @param ackedMessageIds the acknowledged message ids + * @param receiptType the delivery receipt type + * @return generated message ID + */ + public ApiResponse sendDeliveryReceipt(String threemaId, + List ackedMessageIds, DeliveryReceipt.Type receiptType) + throws InvalidKeyException, ApiException, IOException { + if (ackedMessageIds.isEmpty()) { + throw new IOException("Empty ackedMessageIds"); + } + + // fetch public key + byte[] publicKey = this.apiConnector.lookupKey(threemaId); + + if (publicKey == null) { + throw new InvalidKeyException("invalid threema id"); + } + + var message = CryptTool.encryptDeliveryReceipt(receiptType, ackedMessageIds, + this.privateKey, publicKey); + + return this.apiConnector.sendE2EMessage(threemaId, message.getNonce(), message.getResult()); + } + + /** + * Encrypt a group delivery receipt and send it to all group members + * + * @param threemaIds target Threema IDs + * @param groupId the group identifier + * @param ackedMessageIds the acknowledged message ids + * @param receiptType the delivery receipt type + * @return generated message IDs + */ + public ApiResponse sendGroupDeliveryReceipt(List threemaIds, GroupId groupId, + List ackedMessageIds, DeliveryReceipt.Type receiptType) + throws InvalidKeyException, ApiException, IOException { + if (ackedMessageIds.isEmpty()) { + throw new IOException("Empty ackedMessageIds"); + } + + threemaIds = threemaIds.stream().distinct().collect(Collectors.toList()); + + // fetch public keys + HashMap pubkeys = lookupPubkeys(threemaIds); + + if (pubkeys.isEmpty()) { + throw new InvalidKeyException("invalid threema ids"); + } + + var nonces = new byte[threemaIds.size()][]; + var boxes = new byte[threemaIds.size()][]; + + for (int i = 0; i < threemaIds.size(); i++) { + var res = CryptTool.encryptGroupDeliveryReceipt(groupId, receiptType, ackedMessageIds, + this.privateKey, pubkeys.get(threemaIds.get(i))); + + nonces[i] = res.getNonce(); + boxes[i] = res.getResult(); + } + + return this.apiConnector.sendE2EBulkMessage(threemaIds.toArray(String[]::new), nonces, + boxes); + } + + /* + * ========================= Contact and Group Control ========================= + */ + + /** + * Encrypt a group set photo message and send it to the given group. + * + * @param threemaIds target Threema IDs + * @param groupId target Threema Group ID + * @param groupPhotoInJpg the new jpg image + * @return generated message ID + */ + public ApiResponse sendGroupSetPhotoMessage(List threemaIds, GroupId groupId, + File groupPhotoInJpg) throws InvalidKeyException, ApiException, IOException { + if (groupId == null) { + throw new IOException("Invalid groupId"); + } + + threemaIds = threemaIds.stream().distinct().collect(Collectors.toList()); + + HashMap pubkeys = lookupPubkeys(threemaIds); + + if (pubkeys.isEmpty()) { + throw new InvalidKeyException("invalid threema ids"); + } + + if (!groupPhotoInJpg.isFile()) { + throw new IOException("invalid file"); + } + + byte[] fileData = this.readFile(groupPhotoInJpg); + + // encrypt the image + EncryptResult encryptResult = CryptTool.encryptFileData(fileData); + + // upload the image + UploadResult uploadResult = apiConnector.uploadFile(encryptResult, true); + + if (!uploadResult.isSuccess()) { + throw new IOException("could not upload file (upload response " + + uploadResult.getResponseCode() + ")"); + } + + Map moreOptions = Map.of("noPush", true); + var nonces = new byte[threemaIds.size()][]; + var boxes = new byte[threemaIds.size()][]; + + for (int i = 0; i < threemaIds.size(); i++) { + var res = CryptTool.encryptGroupSetPhoto(groupId, uploadResult.getBlobId(), + (int) groupPhotoInJpg.length(), // FIXME: Lossy conversion + encryptResult.getSecret(), this.privateKey, + pubkeys.get(threemaIds.get(i))); + + nonces[i] = res.getNonce(); + boxes[i] = res.getResult(); + } + + return this.apiConnector.sendE2EBulkMessage(threemaIds.toArray(String[]::new), nonces, + boxes, moreOptions); + } + + /** + * Encrypt a group delete photo message and send it to the given group. + * + * @param threemaIds target Threema IDs + * @param groupId target Threema Group ID + * @return generated message ID + */ + public ApiResponse sendGroupDeletePhotoMessage(List threemaIds, + GroupId groupId) throws InvalidKeyException, ApiException, IOException { + if (groupId == null) { + throw new IOException("Invalid groupId"); + } + + threemaIds = threemaIds.stream().distinct().collect(Collectors.toList()); + + HashMap pubkeys = lookupPubkeys(threemaIds); + + if (pubkeys.isEmpty()) { + throw new InvalidKeyException("invalid threema ids"); + } + + Map moreOptions = Map.of("noPush", true); + var nonces = new byte[threemaIds.size()][]; + var boxes = new byte[threemaIds.size()][]; + + for (int i = 0; i < threemaIds.size(); i++) { + var res = CryptTool.encryptGroupDeletePhoto(groupId, this.privateKey, + pubkeys.get(threemaIds.get(i))); + + nonces[i] = res.getNonce(); + boxes[i] = res.getResult(); + } + + return this.apiConnector.sendE2EBulkMessage(threemaIds.toArray(String[]::new), nonces, + boxes, moreOptions); + } + + /** + * Encrypt a group create message and send it to the given group. + * + * @param members target Threema IDs + * @param groupId target Threema Group ID + * @return generated message ID + */ + public ApiResponse sendGroupCreateMessage(List threemaIds, + List members, GroupId groupId) + throws InvalidKeyException, ApiException, IOException { + if (groupId == null) { + throw new IOException("Invalid groupId"); + } + + threemaIds = threemaIds.stream().distinct().collect(Collectors.toList()); + members = members.stream().distinct().collect(Collectors.toList()); + + HashMap pubkeys = lookupPubkeys(threemaIds); + + if (pubkeys.isEmpty()) { + throw new InvalidKeyException("invalid threema ids"); + } + + Map moreOptions = Map.of("noPush", true); + var nonces = new byte[threemaIds.size()][]; + var boxes = new byte[threemaIds.size()][]; + + for (int i = 0; i < threemaIds.size(); i++) { + var res = CryptTool.encryptGroupCreateMessage(groupId, members, this.privateKey, + pubkeys.get(threemaIds.get(i))); + + nonces[i] = res.getNonce(); + boxes[i] = res.getResult(); + } + + return this.apiConnector.sendE2EBulkMessage(threemaIds.toArray(String[]::new), nonces, + boxes, moreOptions); + } + + /** + * Encrypt a group request sync message and send it to the given group. + * + * @param threemaIds target Threema IDs + * @param groupId target Threema Group ID + * @return generated message ID + */ + public ApiResponse sendGroupRequestSyncMsg(List threemaIds, GroupId groupId) + throws InvalidKeyException, ApiException, IOException { + if (groupId == null) { + throw new IOException("Invalid groupId"); + } + + threemaIds = threemaIds.stream().distinct().collect(Collectors.toList()); + + HashMap pubkeys = lookupPubkeys(threemaIds); + + if (pubkeys.isEmpty()) { + throw new InvalidKeyException("invalid threema ids"); + } + + var nonces = new byte[threemaIds.size()][]; + var boxes = new byte[threemaIds.size()][]; + + for (int i = 0; i < threemaIds.size(); i++) { + var res = CryptTool.encryptGroupRequestSyncMessage(groupId, this.privateKey, + pubkeys.get(threemaIds.get(i))); + + nonces[i] = res.getNonce(); + boxes[i] = res.getResult(); + } + + return this.apiConnector.sendE2EBulkMessage(threemaIds.toArray(String[]::new), nonces, + boxes); + } + + /** + * Encrypt a group rename message and send it to the given group. + * + * @param threemaIds target Threema IDs + * @param groupId target Threema Group ID + * @param newGroupName the new group name + * @return generated message ID + */ + public ApiResponse sendGroupRenameMessage(List threemaIds, GroupId groupId, + String newGroupName) throws InvalidKeyException, ApiException, IOException { + if (groupId == null) { + throw new IOException("Invalid groupId"); + } + + threemaIds = threemaIds.stream().distinct().collect(Collectors.toList()); + + HashMap pubkeys = lookupPubkeys(threemaIds); + + if (pubkeys.isEmpty()) { + throw new InvalidKeyException("invalid threema ids"); + } + + if (newGroupName.isEmpty()) { + throw new IOException("Invalid new group name"); + } + + Map moreOptions = Map.of("noPush", true); + var nonces = new byte[threemaIds.size()][]; + var boxes = new byte[threemaIds.size()][]; + + for (int i = 0; i < threemaIds.size(); i++) { + var res = CryptTool.encryptGroupRenameMessage(groupId, newGroupName, this.privateKey, + pubkeys.get(threemaIds.get(i))); + + nonces[i] = res.getNonce(); + boxes[i] = res.getResult(); + } + + return this.apiConnector.sendE2EBulkMessage(threemaIds.toArray(String[]::new), nonces, + boxes, moreOptions); + } + + /** + * Encrypt a group leave message and send it to the given group. + * + * @param threemaIds target Threema IDs + * @param groupId target Threema Group ID + * @return generated message ID + */ + public ApiResponse sendGroupLeaveMessage(List threemaIds, GroupId groupId) + throws InvalidKeyException, ApiException, IOException { + if (groupId == null) { + throw new IOException("Invalid groupId"); + } + + threemaIds = threemaIds.stream().distinct().collect(Collectors.toList()); + + HashMap pubkeys = lookupPubkeys(threemaIds); + + if (pubkeys.isEmpty()) { + throw new InvalidKeyException("invalid threema ids"); + } + + var nonces = new byte[threemaIds.size()][]; + var boxes = new byte[threemaIds.size()][]; + + for (int i = 0; i < threemaIds.size(); i++) { + var res = CryptTool.encryptGroupLeaveMessage(groupId, this.privateKey, + pubkeys.get(threemaIds.get(i))); + + nonces[i] = res.getNonce(); + boxes[i] = res.getResult(); + } + + return this.apiConnector.sendE2EBulkMessage(threemaIds.toArray(String[]::new), nonces, + boxes); + } + + /** + * Decrypt a Message and download the blobs of the Message (e.g. image or file) + * + * @param threemaId Threema ID of the sender + * @param messageId Message ID + * @param box Encrypted box data of the file/image message + * @param nonce Nonce that was used for message encryption + * @param outputFolder Output folder for storing decrypted images/files + * @return result of message reception + */ + public ReceiveMessageResult receiveMessage(String threemaId, String messageId, byte[] box, + byte[] nonce, Path outputFolder) + throws IOException, InvalidKeyException, MessageParseException, ApiException { + // fetch public key + byte[] publicKey = this.apiConnector.lookupKey(threemaId); + + if (publicKey == null) { + throw new InvalidKeyException("invalid threema id"); + } + + ThreemaMessage message = CryptTool.decryptMessage(box, this.privateKey, publicKey, nonce); + + ReceiveMessageResult result = new ReceiveMessageResult(messageId, message); + + if (message instanceof ImageMessage) { + // download image + var imageMessage = (ImageMessage) message; + byte[] fileData = this.apiConnector.downloadFile(imageMessage.getBlobId()); + + if (fileData == null) { + throw new MessageParseException(); + } + + byte[] decryptedFileContent = CryptTool.decrypt(fileData, privateKey, publicKey, + imageMessage.getNonce()); + File imageFile = new File(outputFolder.toString() + "/" + messageId + ".jpg"); + FileOutputStream fos = new FileOutputStream(imageFile); + fos.write(decryptedFileContent); + fos.close(); + + result.files.add(imageFile); + } else if (message instanceof FileMessage) { + // download file + var fileMessage = (FileMessage) message; + var fileData = this.apiConnector.downloadFile(fileMessage.getBlobId()); + + byte[] decryptedFileData = + CryptTool.decryptFileData(fileData, fileMessage.getEncryptionKey()); + File file = new File(outputFolder.toString() + "/" + messageId + "-" + + fileMessage.getFilename()); + FileOutputStream fos = new FileOutputStream(file); + fos.write(decryptedFileData); + fos.close(); + + result.files.add(file); + + if (fileMessage.getThumbnailBlobId() != null + && fileMessage.getThumbnailBlobId().length > 0) { + byte[] thumbnailData = + this.apiConnector.downloadFile(fileMessage.getThumbnailBlobId()); + + byte[] decryptedThumbnailData = CryptTool.decryptFileThumbnailData(thumbnailData, + fileMessage.getEncryptionKey()); + File thumbnailFile = new File(outputFolder + "/" + messageId + "-thumbnail.jpg"); + fos = new FileOutputStream(thumbnailFile); + fos.write(decryptedThumbnailData); + fos.close(); + + result.files.add(thumbnailFile); + } + } else if (message instanceof GroupFileMessage) { + // download file + var groupFileMessage = (GroupFileMessage) message; + var fileData = this.apiConnector.downloadFile(groupFileMessage.getBlobId()); + + byte[] decryptedFileData = CryptTool.decryptFileData(fileData, + groupFileMessage.getEncryptionKey()); + File file = new File(outputFolder.toString() + "/" + messageId + "-" + + groupFileMessage.getFilename()); + FileOutputStream fos = new FileOutputStream(file); + if (decryptedFileData == null) + throw new DecryptionFailedException(); + fos.write(decryptedFileData); + fos.close(); + + result.files.add(file); + + if (groupFileMessage.getThumbnailBlobId() != null + && groupFileMessage.getThumbnailBlobId().length > 0) { + byte[] thumbnailData = this.apiConnector + .downloadFile(groupFileMessage.getThumbnailBlobId()); + + byte[] decryptedThumbnailData = CryptTool.decryptFileThumbnailData(thumbnailData, + groupFileMessage.getEncryptionKey()); + File thumbnailFile = new File(outputFolder + "/" + messageId + "-thumbnail.jpg"); + fos = new FileOutputStream(thumbnailFile); + fos.write(decryptedThumbnailData); + fos.close(); + + result.files.add(thumbnailFile); + } + } + + return result; + } + + private HashMap lookupPubkeys(List threemaIds) + throws ApiException, InvalidKeyException, IOException { + var pubkeys = new HashMap(threemaIds.size()); + if (threemaIds.isEmpty()) { + throw new IOException("invalid threemaIds"); + } + for (var threemaId : threemaIds) { + var pubkey = this.apiConnector.lookupKey(threemaId); + + if (pubkey == null) { + throw new InvalidKeyException("invalid threema id"); + } + + // fetch public key + pubkeys.put(threemaId, pubkey); + } + return pubkeys; + } + + /** + * Read file data from file - store at offset in byte array for in-place encryption + * + * @param file input file + * @return file data with padding/offset for NaCl + */ + private byte[] readFile(File file) throws IOException { + int fileLength = (int) file.length(); + byte[] fileData = new byte[fileLength + NaCl.BOXOVERHEAD]; + IOUtils.readFully(new FileInputStream(file), fileData, NaCl.BOXOVERHEAD, fileLength); + return fileData; + } + + private String getMimeType(File messageFile) { + String mimeType; + try { + mimeType = Files.probeContentType(messageFile.toPath()); + + if (mimeType == null || mimeType.length() == 0) { + // Try to using the TIka Library for mime type detection + Tika tika = new Tika(); + mimeType = tika.detect(messageFile); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + // System.out.println("Mime type: " + mimeType); + + if (mimeType == null || mimeType.length() == 0) { + mimeType = "application/octet-stream"; + } + + return mimeType; + } } diff --git a/source/src/main/java/ch/threema/apitool/messages/BallotCreateMessage.java b/source/src/main/java/ch/threema/apitool/messages/BallotCreateMessage.java new file mode 100644 index 0000000..21ce78e --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/BallotCreateMessage.java @@ -0,0 +1,282 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.BallotCreateMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A poll create message that can be sent/received with end-to-end encryption via Threema. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T14:00:04.180868814+00:00") +public class BallotCreateMessage extends ThreemaMessage { + public static final int TYPE_CODE = 0x15; + + private final static String KEY_DESCRIPTION = "d"; + private final static String KEY_STATE = "s"; + private final static String KEY_VOTING_MODE = "a"; + private final static String KEY_RESULTS_DISCLOSURE_TYPE = "t"; + private final static String KEY_ORDER = "o"; + private final static String KEY_DISPLAY_MODE = "u"; + private final static String KEY_CHOICES = "c"; + private final static String KEY_PARTICIPANTS = "p"; + + private final byte[] ballotId; + private final String description; + private final State state; + private final VotingMode votingMode; + private final ResultsDisclosureType resultsDisclosureType; + private final int order; + private final DisplayMode displayMode; + private final List choices; + private final List participants; + + public BallotCreateMessage(byte[] ballotId, String description, State state, + VotingMode votingMode, ResultsDisclosureType resultsDisclosureType, int order, + DisplayMode displayMode, List choices, + List participants) { + this.ballotId = ballotId; + this.description = description; + this.state = state; + this.votingMode = votingMode; + this.resultsDisclosureType = resultsDisclosureType; + this.order = order; + this.displayMode = displayMode; + this.choices = choices; + this.participants = participants; + } + + /** + * The poll ballot identifier + * + * @return ballotId + **/ + public byte[] getBallotId() { + return ballotId; + } + + + /** + * The group poll description + * + * @return description + **/ + public String getDescription() { + return description; + } + + + /** + * The group poll state + * + * @return state + **/ + public State getState() { + return state; + } + + + /** + * The voting mode + * + * @return votingMode + **/ + public VotingMode getVotingMode() { + return votingMode; + } + + + /** + * The poll results disclosure type + * + * @return resultsDisclosureType + **/ + public ResultsDisclosureType getResultsDisclosureType() { + return resultsDisclosureType; + } + + + /** + * The poll results order + * + * @return order + * @deprecated + **/ + @Deprecated + public int getOrder() { + return order; + } + + + /** + * The display mode + * + * @return displayMode + **/ + public DisplayMode getDisplayMode() { + return displayMode; + } + + + /** + * The available vote choices + * + * @return choices + **/ + public List getChoices() { + return choices; + } + + + /** + * The poll participants + * + * @return participants + **/ + public List getParticipants() { + return participants; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + JSONObject o = new JSONObject(); + try { + + o.put(KEY_DESCRIPTION, this.description); + o.put(KEY_STATE, this.state.getValue()); + o.put(KEY_VOTING_MODE, this.votingMode.getValue()); + o.put(KEY_RESULTS_DISCLOSURE_TYPE, this.resultsDisclosureType.getValue()); + o.put(KEY_ORDER, this.order); + if (this.displayMode != null) + o.put(KEY_DISPLAY_MODE, this.displayMode.getValue()); + JSONArray choicesO = new JSONArray(); + for (var choicesItem : this.choices) { + choicesO.put(new JSONObject(new String(choicesItem.getData()))); + } + o.put(KEY_CHOICES, choicesO); + if (this.participants != null) + o.put(KEY_PARTICIPANTS, this.participants); + } catch (Exception e) { + throw new BadMessageException(); + } + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + bos.write(ballotId); + if (o.length() > 0) { + bos.write(o.toString().getBytes(StandardCharsets.UTF_8)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return bos.toByteArray(); + } + + public static BallotCreateMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + BallotCreateMessageSerializer.validate(data, realDataLength); + return BallotCreateMessageSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(Arrays.hashCode(ballotId), description, state, votingMode, + resultsDisclosureType, order, displayMode, + Arrays.hashCode(choices.toArray()), Arrays.hashCode(participants.toArray()), + super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class BallotCreateMessage {\n"); + sb.append(" ballotId: ") + .append(toIndentedString(DataUtils.byteArrayToHexString(getBallotId()))) + .append("\n"); + sb.append(" description: ").append(toIndentedString(getDescription())).append("\n"); + sb.append(" state: ").append(toIndentedString(getState())).append("\n"); + sb.append(" votingMode: ").append(toIndentedString(getVotingMode())).append("\n"); + sb.append(" resultsDisclosureType: ") + .append(toIndentedString(getResultsDisclosureType())).append("\n"); + sb.append(" order: ").append(toIndentedString(getOrder())).append("\n"); + sb.append(" displayMode: ").append(toIndentedString(getDisplayMode())).append("\n"); + sb.append(" choices: ").append(toIndentedString(getChoices())).append("\n"); + sb.append(" participants: ").append(toIndentedString(getParticipants())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/BallotVoteMessage.java b/source/src/main/java/ch/threema/apitool/messages/BallotVoteMessage.java new file mode 100644 index 0000000..eb81d3d --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/BallotVoteMessage.java @@ -0,0 +1,158 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.BallotVoteMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A poll vote message that can be sent/received with end-to-end encryption via Threema. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.485807841+00:00") +public class BallotVoteMessage extends ThreemaMessage { + public static final int TYPE_CODE = 0x16; + + + private final byte[] creator; + private final byte[] ballotId; + private final List votes; + + public BallotVoteMessage(byte[] creator, byte[] ballotId, List votes) { + this.creator = creator; + this.ballotId = ballotId; + this.votes = votes; + } + + /** + * The group poll creator + * + * @return creator + **/ + public byte[] getCreator() { + return creator; + } + + /** + * The poll ballot identifier + * + * @return ballotId + **/ + public byte[] getBallotId() { + return ballotId; + } + + /** + * The votes tuple + * + * @return votes + **/ + public List getVotes() { + return votes; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + + return BallotVoteMessageSerializer.serialize(creator, ballotId, votes); + } + + public static BallotVoteMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + BallotVoteMessageSerializer.validate(data, realDataLength); + return BallotVoteMessageSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(Arrays.hashCode(creator), ballotId, Arrays.hashCode(votes.toArray()), + super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class BallotVoteMessage {\n"); + sb.append(" creator: ") + .append(toIndentedString(DataUtils.byteArrayToHexString(getCreator()))) + .append("\n"); + sb.append(" ballotId: ").append(toIndentedString(getBallotId())).append("\n"); + sb.append(" votes: ").append(toIndentedString(getVotes())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/DeliveryReceipt.java b/source/src/main/java/ch/threema/apitool/messages/DeliveryReceipt.java index 6f79f76..9e999d1 100644 --- a/source/src/main/java/ch/threema/apitool/messages/DeliveryReceipt.java +++ b/source/src/main/java/ch/threema/apitool/messages/DeliveryReceipt.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,71 +26,93 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.messages; -import ch.threema.apitool.MessageId; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; -import java.util.List; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.DeliveryReceiptSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; /** - * A delivery receipt message that can be sent/received with end-to-end encryption via Threema. - * Each delivery receipt message confirms the receipt of one or multiple regular messages. + * A delivery receipt message that can be sent/received with end-to-end encryption via Threema. Each + * delivery receipt message confirms the receipt of one or multiple regular messages. */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T14:13:28.152752276+00:00") public class DeliveryReceipt extends ThreemaMessage { - public static final int TYPE_CODE = 0x80; - private final Type receiptType; + + private final DeliveryReceipt.Type receiptType; private final List ackedMessageIds; - public DeliveryReceipt(Type receiptType, List ackedMessageIds) { + public DeliveryReceipt(DeliveryReceipt.Type receiptType, List ackedMessageIds) { this.receiptType = receiptType; this.ackedMessageIds = ackedMessageIds; } - public Type getReceiptType() { + /** + * The message receipt type + * + * @return receiptType + **/ + public DeliveryReceipt.Type getReceiptType() { return receiptType; } + /** + * The acked message ids + * + * @return ackedMessageIds + **/ public List getAckedMessageIds() { return ackedMessageIds; } + + @Override public int getTypeCode() { return TYPE_CODE; } - @Override - public String toString() { - StringBuilder sb = new StringBuilder("Delivery receipt ("); - sb.append(receiptType); - sb.append("): "); - int i = 0; - for (MessageId messageId : ackedMessageIds) { - if (i != 0) - sb.append(", "); - sb.append(messageId); - i++; - } - return sb.toString(); - } - /** * A delivery receipt type. The following types are defined: * - *
    - *
  • RECEIVED: the message has been received and decrypted on the recipient's device
  • - *
  • READ: the message has been shown to the user in the chat view - * (note that this status can be disabled)
  • - *
  • USER_ACK: the user has explicitly acknowledged the message (usually by - * long-pressing it and choosing the "acknowledge" option)
  • - *
+ * RECEIVED: the message has been received and decrypted on the recipient's device + * READ: the message has been shown to the user in the chat view (note that this status can be + * disabled) + * USER_ACK: the user has explicitly acknowledged the message (usually by + * long-pressing it and choosing the "acknowledge" option) + * USER_DEC: the user has explicitly declined the message */ public enum Type { - RECEIVED(1), READ(2), USER_ACK(3); + RECEIVED(1), READ(2), USER_ACK(3), USER_DEC(4); private final int code; @@ -98,16 +126,54 @@ public int getCode() { public static Type get(int code) { for (Type t : values()) { - if (t.code == code) + if (t.code == code) { return t; + } } return null; } } @Override - public byte[] getData() { - //Not implemented yet - return new byte[0]; + public byte[] getData() throws BadMessageException { + + return DeliveryReceiptSerializer.serialize(receiptType, ackedMessageIds); + } + + public static DeliveryReceipt fromString(byte[] data, int realDataLength) + throws BadMessageException { + DeliveryReceiptSerializer.validate(data, realDataLength); + return DeliveryReceiptSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(receiptType, Arrays.hashCode(ackedMessageIds.toArray()), + super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class DeliveryReceipt {\n"); + sb.append(" receiptType: ").append(toIndentedString(getReceiptType())).append("\n"); + sb.append(" ackedMessageIds: ").append(toIndentedString(getAckedMessageIds())) + .append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); } } diff --git a/source/src/main/java/ch/threema/apitool/messages/FileMessage.java b/source/src/main/java/ch/threema/apitool/messages/FileMessage.java index f630c08..51df767 100644 --- a/source/src/main/java/ch/threema/apitool/messages/FileMessage.java +++ b/source/src/main/java/ch/threema/apitool/messages/FileMessage.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,132 +26,286 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.messages; -import java.io.UnsupportedEncodingException; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; -import ch.threema.apitool.DataUtils; +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.FileMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + /** - * A file message that can be sent/received with end-to-end encryption via Threema. + * A File Message */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.475245996+00:00") public class FileMessage extends ThreemaMessage { - private final static String KEY_BLOB_ID = "b"; - private final static String KEY_THUMBNAIL_BLOB_ID = "t"; - private final static String KEY_ENCRYPTION_KEY = "k"; - private final static String KEY_MIME_TYPE = "m"; - private final static String KEY_FILE_NAME = "n"; - private final static String KEY_FILE_SIZE = "s"; - private final static String KEY_TYPE = "i"; - - public static final int TYPE_CODE = 0x17; - - private final byte[] blobId; - private final byte[] encryptionKey; - private final String mimeType; - private final String fileName; - private final int fileSize; - private final byte[] thumbnailBlobId; - - public FileMessage(byte[] blobId, byte[] encryptionKey, String mimeType, String fileName, int fileSize, - byte[] thumbnailBlobId) { - this.blobId = blobId; - this.encryptionKey = encryptionKey; - this.mimeType = mimeType; - this.fileName = fileName; - this.fileSize = fileSize; - this.thumbnailBlobId = thumbnailBlobId; - } - - public byte[] getBlobId() { - return this.blobId; - } - - public byte[] getEncryptionKey() { - return this.encryptionKey; - } - - public String getMimeType() { - return this.mimeType; - } - - public String getFileName() { - return this.fileName; - } - - public int getFileSize() { - return this.fileSize; - } - - public byte[] getThumbnailBlobId() { - return this.thumbnailBlobId; - } - - @Override - public int getTypeCode() { - return TYPE_CODE; - } - - @Override - public String toString() { - return "file message " + this.fileName; - } - - @Override - public byte[] getData() throws BadMessageException { - JsonObject o = new JsonObject(); - try { - o.addProperty(KEY_BLOB_ID, DataUtils.byteArrayToHexString(this.blobId)); - o.addProperty(KEY_THUMBNAIL_BLOB_ID, - this.thumbnailBlobId != null ? DataUtils.byteArrayToHexString(this.thumbnailBlobId) : null); - o.addProperty(KEY_ENCRYPTION_KEY, DataUtils.byteArrayToHexString(this.encryptionKey)); - o.addProperty(KEY_MIME_TYPE, this.mimeType); - o.addProperty(KEY_FILE_NAME, this.fileName); - o.addProperty(KEY_FILE_SIZE, this.fileSize); - o.addProperty(KEY_TYPE, 0); - } catch (Exception e) { - throw new BadMessageException(); - } - - try { - return o.toString().getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - return null; - } - } - - public static FileMessage fromString(String json) throws BadMessageException { - try { - JsonObject o = new Gson().fromJson(json, JsonObject.class); - byte[] encryptionKey = DataUtils.hexStringToByteArray(o.get(KEY_ENCRYPTION_KEY).getAsString()); - String mimeType = o.get(KEY_MIME_TYPE).getAsString(); - int fileSize = o.get(KEY_FILE_SIZE).getAsInt(); - byte[] blobId = DataUtils.hexStringToByteArray(o.get(KEY_BLOB_ID).getAsString()); - - String fileName; - byte[] thumbnailBlobId = null; - - // optional field - if (o.has(KEY_THUMBNAIL_BLOB_ID)) { - thumbnailBlobId = DataUtils.hexStringToByteArray(o.get(KEY_THUMBNAIL_BLOB_ID).getAsString()); - } - - if (o.has(KEY_FILE_NAME)) { - fileName = o.get(KEY_FILE_NAME).getAsString(); - } else { - fileName = "unnamed"; - } - - return new FileMessage(blobId, encryptionKey, mimeType, fileName, fileSize, thumbnailBlobId); - } catch (JsonSyntaxException e) { - throw new BadMessageException(); - } - } + public static final int TYPE_CODE = 0x17; + + private final static String KEY_BLOB_ID = "b"; + private final static String KEY_THUMBNAIL_BLOB_ID = "t"; + private final static String KEY_THUMBNAIL_MEDIA_TYPE = "p"; + private final static String KEY_ENCRYPTION_KEY = "k"; + private final static String KEY_MIME_TYPE = "m"; + private final static String KEY_FILE_NAME = "n"; + private final static String KEY_SIZE = "s"; + private final static String KEY_CAPTION = "d"; + private final static String KEY_RENDERING_TYPE = "j"; + private final static String KEY_CORRELATION_ID = "c"; + private final static String KEY_METADATA = "x"; + + private final byte[] blobId; + private final byte[] thumbnailBlobId; + private final String thumbnailMediaType; + private final byte[] encryptionKey; + private final String mimeType; + private final String fileName; + private final int size; + private final String caption; + private final FileRenderingType renderingType; + private final String correlationId; + private final Map metadata; + + public FileMessage(byte[] blobId, byte[] thumbnailBlobId, String thumbnailMediaType, + byte[] encryptionKey, String mimeType, String fileName, int size, + String caption, FileRenderingType renderingType, String correlationId, + Map metadata) { + this.blobId = blobId; + this.thumbnailBlobId = thumbnailBlobId; + this.thumbnailMediaType = thumbnailMediaType; + this.encryptionKey = encryptionKey; + this.mimeType = mimeType; + this.fileName = fileName; + this.size = size; + this.caption = caption; + this.renderingType = renderingType; + this.correlationId = correlationId; + this.metadata = metadata; + } + + /** + * The blob ID + * + * @return blobId + **/ + public byte[] getBlobId() { + return blobId; + } + + + /** + * The thumbnail blob ID + * + * @return thumbnailBlobId + **/ + public byte[] getThumbnailBlobId() { + return thumbnailBlobId; + } + + + /** + * The thumbnail media type + * + * @return thumbnailMediaType + **/ + public String getThumbnailMediaType() { + return thumbnailMediaType; + } + + + /** + * The encryption key + * + * @return encryptionKey + **/ + public byte[] getEncryptionKey() { + return encryptionKey; + } + + + /** + * The mime type + * + * @return mimeType + **/ + public String getMimeType() { + return mimeType; + } + + + /** + * The filename + * + * @return fileName + **/ + public String getFilename() { + return fileName; + } + + + /** + * The file size + * + * @return size + **/ + public int getSize() { + return size; + } + + + /** + * The file caption + * + * @return caption + **/ + public String getCaption() { + return caption; + } + + + /** + * The rendering type + * + * @return renderingType + **/ + public FileRenderingType getRenderingType() { + return renderingType; + } + + + /** + * The correlation identifier + * + * @return correlationId + **/ + public String getCorrelationId() { + return correlationId; + } + + + /** + * The metadata + * + * @return metadata + **/ + public Map getMetadata() { + return metadata; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + JSONObject o = new JSONObject(); + try { + + o.put(KEY_BLOB_ID, DataUtils.byteArrayToHexString(this.blobId)); + if (this.thumbnailBlobId != null) + o.put(KEY_THUMBNAIL_BLOB_ID, DataUtils.byteArrayToHexString(this.thumbnailBlobId)); + if (this.thumbnailMediaType != null) + o.put(KEY_THUMBNAIL_MEDIA_TYPE, this.thumbnailMediaType); + o.put(KEY_ENCRYPTION_KEY, DataUtils.byteArrayToHexString(this.encryptionKey)); + o.put(KEY_MIME_TYPE, this.mimeType); + if (this.fileName != null) + o.put(KEY_FILE_NAME, this.fileName); + o.put(KEY_SIZE, this.size); + if (this.caption != null) + o.put(KEY_CAPTION, this.caption); + if (this.renderingType != null) + o.put(KEY_RENDERING_TYPE, this.renderingType.getValue()); + if (this.correlationId != null) + o.put(KEY_CORRELATION_ID, this.correlationId); + if (this.metadata != null) + o.put(KEY_METADATA, this.metadata); + } catch (Exception e) { + throw new BadMessageException(); + } + + return o.toString().getBytes(StandardCharsets.UTF_8); + + } + + public static FileMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + FileMessageSerializer.validate(data, realDataLength); + return FileMessageSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(Arrays.hashCode(blobId), Arrays.hashCode(thumbnailBlobId), + thumbnailMediaType, Arrays.hashCode(encryptionKey), mimeType, fileName, + size, caption, renderingType, correlationId, metadata, super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class FileMessage {\n"); + sb.append(" blobId: ") + .append(toIndentedString(DataUtils.byteArrayToHexString(getBlobId()))) + .append("\n"); + sb.append(" thumbnailBlobId: ") + .append(toIndentedString( + DataUtils.byteArrayToHexString(getThumbnailBlobId()))) + .append("\n"); + sb.append(" thumbnailMediaType: ").append(toIndentedString(getThumbnailMediaType())) + .append("\n"); + sb.append(" encryptionKey: ") + .append(toIndentedString( + DataUtils.byteArrayToHexString(getEncryptionKey()))) + .append("\n"); + sb.append(" mimeType: ").append(toIndentedString(getMimeType())).append("\n"); + sb.append(" fileName: ").append(toIndentedString(getFilename())).append("\n"); + sb.append(" size: ").append(toIndentedString(getSize())).append("\n"); + sb.append(" caption: ").append(toIndentedString(getCaption())).append("\n"); + sb.append(" renderingType: ").append(toIndentedString(getRenderingType())).append("\n"); + sb.append(" correlationId: ").append(toIndentedString(getCorrelationId())).append("\n"); + sb.append(" metadata: ").append(toIndentedString(getMetadata())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } } diff --git a/source/src/main/java/ch/threema/apitool/messages/GroupBallotCreateMessage.java b/source/src/main/java/ch/threema/apitool/messages/GroupBallotCreateMessage.java new file mode 100644 index 0000000..b073c09 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/GroupBallotCreateMessage.java @@ -0,0 +1,294 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.GroupBallotCreateMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A group poll create message that can be sent/received with end-to-end encryption via Threema. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.508614517+00:00") +public class GroupBallotCreateMessage extends ThreemaGroupMessage { + public static final int TYPE_CODE = 0x52; + + private final static String KEY_DESCRIPTION = "d"; + private final static String KEY_STATE = "s"; + private final static String KEY_VOTING_MODE = "a"; + private final static String KEY_RESULTS_DISCLOSURE_TYPE = "t"; + private final static String KEY_ORDER = "o"; + private final static String KEY_DISPLAY_MODE = "u"; + private final static String KEY_CHOICES = "c"; + private final static String KEY_PARTICIPANTS = "p"; + + private final GroupId groupId; + private final byte[] ballotId; + private final String description; + private final State state; + private final VotingMode votingMode; + private final ResultsDisclosureType resultsDisclosureType; + private final int order; + private final DisplayMode displayMode; + private final List choices; + private final List participants; + + public GroupBallotCreateMessage(GroupId groupId, byte[] ballotId, String description, + State state, VotingMode votingMode, ResultsDisclosureType resultsDisclosureType, + int order, DisplayMode displayMode, List choices, + List participants) { + super(groupId); + this.groupId = groupId; + this.ballotId = ballotId; + this.description = description; + this.state = state; + this.votingMode = votingMode; + this.resultsDisclosureType = resultsDisclosureType; + this.order = order; + this.displayMode = displayMode; + this.choices = choices; + this.participants = participants; + } + + /** + * The group identifier + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + /** + * The poll ballot identifier + * + * @return ballotId + **/ + public byte[] getBallotId() { + return ballotId; + } + + + /** + * The group poll description + * + * @return description + **/ + public String getDescription() { + return description; + } + + + /** + * The group poll state + * + * @return state + **/ + public State getState() { + return state; + } + + + /** + * The voting mode + * + * @return votingMode + **/ + public VotingMode getVotingMode() { + return votingMode; + } + + + /** + * The poll results disclosure type + * + * @return resultsDisclosureType + **/ + public ResultsDisclosureType getResultsDisclosureType() { + return resultsDisclosureType; + } + + + /** + * The poll results order + * + * @return order + * @deprecated + **/ + @Deprecated + public int getOrder() { + return order; + } + + + /** + * The display mode + * + * @return displayMode + **/ + public DisplayMode getDisplayMode() { + return displayMode; + } + + + /** + * The available vote choices + * + * @return choices + **/ + public List getChoices() { + return choices; + } + + + /** + * The poll participants + * + * @return participants + **/ + public List getParticipants() { + return participants; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + JSONObject o = new JSONObject(); + try { + + o.put(KEY_DESCRIPTION, this.description); + o.put(KEY_STATE, this.state.getValue()); + o.put(KEY_VOTING_MODE, this.votingMode.getValue()); + o.put(KEY_RESULTS_DISCLOSURE_TYPE, this.resultsDisclosureType.getValue()); + o.put(KEY_ORDER, this.order); + if (this.displayMode != null) + o.put(KEY_DISPLAY_MODE, this.displayMode.getValue()); + JSONArray choicesO = new JSONArray(); + for (var choicesItem : this.choices) { + choicesO.put(new JSONObject(new String(choicesItem.getData()))); + } + o.put(KEY_CHOICES, choicesO); + if (this.participants != null) + o.put(KEY_PARTICIPANTS, this.participants); + } catch (Exception e) { + throw new BadMessageException(); + } + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + bos.write(ballotId); + if (o.length() > 0) { + bos.write(o.toString().getBytes(StandardCharsets.UTF_8)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return bos.toByteArray(); + } + + public static GroupBallotCreateMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + GroupBallotCreateMessageSerializer.validate(data, realDataLength); + return GroupBallotCreateMessageSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, Arrays.hashCode(ballotId), description, state, votingMode, + resultsDisclosureType, order, displayMode, + Arrays.hashCode(choices.toArray()), Arrays.hashCode(participants.toArray()), + super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GroupBallotCreateMessage {\n"); + sb.append(" ballotId: ") + .append(toIndentedString(DataUtils.byteArrayToHexString(getBallotId()))) + .append("\n"); + sb.append(" description: ").append(toIndentedString(getDescription())).append("\n"); + sb.append(" state: ").append(toIndentedString(getState())).append("\n"); + sb.append(" votingMode: ").append(toIndentedString(getVotingMode())).append("\n"); + sb.append(" resultsDisclosureType: ") + .append(toIndentedString(getResultsDisclosureType())).append("\n"); + sb.append(" order: ").append(toIndentedString(getOrder())).append("\n"); + sb.append(" displayMode: ").append(toIndentedString(getDisplayMode())).append("\n"); + sb.append(" choices: ").append(toIndentedString(getChoices())).append("\n"); + sb.append(" participants: ").append(toIndentedString(getParticipants())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/GroupBallotVoteMessage.java b/source/src/main/java/ch/threema/apitool/messages/GroupBallotVoteMessage.java new file mode 100644 index 0000000..0722c92 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/GroupBallotVoteMessage.java @@ -0,0 +1,173 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.GroupBallotVoteMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A group poll vote message that can be sent/received with end-to-end encryption via Threema. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.514702006+00:00") +public class GroupBallotVoteMessage extends ThreemaGroupMessage { + public static final int TYPE_CODE = 0x53; + + + private final GroupId groupId; + private final byte[] creator; + private final byte[] ballotId; + private final List votes; + + public GroupBallotVoteMessage(GroupId groupId, byte[] creator, byte[] ballotId, + List votes) { + super(groupId); + this.groupId = groupId; + this.creator = creator; + this.ballotId = ballotId; + this.votes = votes; + } + + /** + * The group identifier + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + /** + * The group poll creator + * + * @return creator + **/ + public byte[] getCreator() { + return creator; + } + + /** + * The poll ballot identifier + * + * @return ballotId + **/ + public byte[] getBallotId() { + return ballotId; + } + + /** + * The votes array + * + * @return votes + **/ + public List getVotes() { + return votes; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + + return GroupBallotVoteMessageSerializer.serialize(groupId, creator, ballotId, votes); + } + + public static GroupBallotVoteMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + GroupBallotVoteMessageSerializer.validate(data, realDataLength); + return GroupBallotVoteMessageSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, Arrays.hashCode(creator), Arrays.hashCode(ballotId), + Arrays.hashCode(votes.toArray()), super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GroupBallotVoteMessage {\n"); + sb.append(" creator: ") + .append(toIndentedString(DataUtils.byteArrayToHexString(getCreator()))) + .append("\n"); + sb.append(" ballotId: ") + .append(toIndentedString(DataUtils.byteArrayToHexString(getBallotId()))) + .append("\n"); + sb.append(" votes: ").append(toIndentedString(getVotes())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/GroupCreateMessage.java b/source/src/main/java/ch/threema/apitool/messages/GroupCreateMessage.java new file mode 100644 index 0000000..bf7f2e9 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/GroupCreateMessage.java @@ -0,0 +1,144 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.GroupCreateMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A create group message that can be sent/received with end-to-end encryption via Threema. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.489872171+00:00") +public class GroupCreateMessage extends ThreemaGroupMessage { + public static final boolean noPrependGroupCreator = true; + public static final int TYPE_CODE = 0x4a; + + + private final GroupId groupId; + private final List members; + + public GroupCreateMessage(GroupId groupId, List members) { + super(groupId); + this.groupId = groupId; + this.members = members; + } + + /** + * The group identifier + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + /** + * The group members + * + * @return members + **/ + public List getMembers() { + return members; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + + return GroupCreateMessageSerializer.serialize(groupId, members); + } + + public static GroupCreateMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + GroupCreateMessageSerializer.validate(data, realDataLength); + return GroupCreateMessageSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, Arrays.hashCode(members.toArray()), super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GroupCreateMessage {\n"); + sb.append(" members: ").append(toIndentedString(getMembers())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/GroupDeletePhoto.java b/source/src/main/java/ch/threema/apitool/messages/GroupDeletePhoto.java new file mode 100644 index 0000000..6d43326 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/GroupDeletePhoto.java @@ -0,0 +1,133 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.GroupDeletePhotoSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A group delete profile picture message that can be sent/received with end-to-end encryption via + * Threema. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.494578505+00:00") +public class GroupDeletePhoto extends ThreemaGroupMessage { + public static final boolean noPrependGroupCreator = true; + public static final int TYPE_CODE = 0x54; + + + private final GroupId groupId; + + public GroupDeletePhoto(GroupId groupId) { + super(groupId); + this.groupId = groupId; + } + + /** + * The group identifier + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + + return GroupDeletePhotoSerializer.serialize(groupId); + } + + public static GroupDeletePhoto fromString(byte[] data, int realDataLength) + throws BadMessageException { + + throw new UnsupportedOperationException( + "GroupDeletePhoto cannot be received from a Gateway ID!"); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GroupDeletePhoto {\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/GroupDeliveryReceipt.java b/source/src/main/java/ch/threema/apitool/messages/GroupDeliveryReceipt.java new file mode 100644 index 0000000..8d03af1 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/GroupDeliveryReceipt.java @@ -0,0 +1,192 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.GroupDeliveryReceiptSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A group delivery receipt message that can be sent/received with end-to-end encryption via + * Threema. Each delivery receipt message confirms the receipt of one or multiple group messages. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T14:13:28.154411894+00:00") +public class GroupDeliveryReceipt extends ThreemaGroupMessage { + public static final int TYPE_CODE = 0x81; + + + private final GroupId groupId; + private final DeliveryReceipt.Type receiptType; + private final List ackedMessageIds; + + public GroupDeliveryReceipt(GroupId groupId, DeliveryReceipt.Type receiptType, + List ackedMessageIds) { + super(groupId); + this.groupId = groupId; + this.receiptType = receiptType; + this.ackedMessageIds = ackedMessageIds; + } + + /** + * The group identifier + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + /** + * The message receipt type + * + * @return receiptType + **/ + public DeliveryReceipt.Type getReceiptType() { + return receiptType; + } + + /** + * The acked message ids + * + * @return ackedMessageIds + **/ + public List getAckedMessageIds() { + return ackedMessageIds; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + /** + * A delivery receipt type. The following types are defined: + * + * RECEIVED: the message has been received and decrypted on the recipient's device + * READ: the message has been shown to the user in the chat view (note that this status can be + * disabled) + * USER_ACK: the user has explicitly acknowledged the message (usually by + * long-pressing it and choosing the "acknowledge" option) + * USER_DEC: the user has explicitly declined the message + */ + public enum Type { + RECEIVED(1), READ(2), USER_ACK(3), USER_DEC(4); + + private final int code; + + Type(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public static Type get(int code) { + for (Type t : values()) { + if (t.code == code) { + return t; + } + } + return null; + } + } + + @Override + public byte[] getData() throws BadMessageException { + + return GroupDeliveryReceiptSerializer.serialize(groupId, receiptType, ackedMessageIds); + } + + public static GroupDeliveryReceipt fromString(byte[] data, int realDataLength) + throws BadMessageException { + GroupDeliveryReceiptSerializer.validate(data, realDataLength); + return GroupDeliveryReceiptSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, receiptType, Arrays.hashCode(ackedMessageIds.toArray()), + super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GroupDeliveryReceipt {\n"); + sb.append(" receiptType: ").append(toIndentedString(getReceiptType())).append("\n"); + sb.append(" ackedMessageIds: ").append(toIndentedString(getAckedMessageIds())) + .append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/GroupFileMessage.java b/source/src/main/java/ch/threema/apitool/messages/GroupFileMessage.java new file mode 100644 index 0000000..705e90e --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/GroupFileMessage.java @@ -0,0 +1,324 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.GroupFileMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A File Message + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.501232142+00:00") +public class GroupFileMessage extends ThreemaGroupMessage { + public static final int TYPE_CODE = 0x46; + + private final static String KEY_BLOB_ID = "b"; + private final static String KEY_THUMBNAIL_BLOB_ID = "t"; + private final static String KEY_THUMBNAIL_MEDIA_TYPE = "p"; + private final static String KEY_ENCRYPTION_KEY = "k"; + private final static String KEY_MIME_TYPE = "m"; + private final static String KEY_FILE_NAME = "n"; + private final static String KEY_SIZE = "s"; + private final static String KEY_CAPTION = "d"; + private final static String KEY_RENDERING_TYPE = "j"; + private final static String KEY_CORRELATION_ID = "c"; + private final static String KEY_METADATA = "x"; + + private final GroupId groupId; + private final byte[] blobId; + private final byte[] thumbnailBlobId; + private final String thumbnailMediaType; + private final byte[] encryptionKey; + private final String mimeType; + private final String fileName; + private final int size; + private final String caption; + private final FileRenderingType renderingType; + private final String correlationId; + private final Map metadata; + + public GroupFileMessage(GroupId groupId, byte[] blobId, byte[] thumbnailBlobId, + String thumbnailMediaType, byte[] encryptionKey, String mimeType, + String fileName, int size, String caption, FileRenderingType renderingType, + String correlationId, Map metadata) { + super(groupId); + this.groupId = groupId; + this.blobId = blobId; + this.thumbnailBlobId = thumbnailBlobId; + this.thumbnailMediaType = thumbnailMediaType; + this.encryptionKey = encryptionKey; + this.mimeType = mimeType; + this.fileName = fileName; + this.size = size; + this.caption = caption; + this.renderingType = renderingType; + this.correlationId = correlationId; + this.metadata = metadata; + } + + /** + * The group identifier + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + + /** + * The blob ID + * + * @return blobId + **/ + public byte[] getBlobId() { + return blobId; + } + + + /** + * The thumbnail blob ID + * + * @return thumbnailBlobId + **/ + public byte[] getThumbnailBlobId() { + return thumbnailBlobId; + } + + + /** + * The thumbnail media type + * + * @return thumbnailMediaType + **/ + public String getThumbnailMediaType() { + return thumbnailMediaType; + } + + + /** + * The encryption key + * + * @return encryptionKey + **/ + public byte[] getEncryptionKey() { + return encryptionKey; + } + + + /** + * The mime type + * + * @return mimeType + **/ + public String getMimeType() { + return mimeType; + } + + + /** + * The filename + * + * @return fileName + **/ + public String getFilename() { + return fileName; + } + + + /** + * The file size + * + * @return size + **/ + public int getSize() { + return size; + } + + + /** + * The file caption + * + * @return caption + **/ + public String getCaption() { + return caption; + } + + + /** + * The rendering type + * + * @return renderingType + **/ + public FileRenderingType getRenderingType() { + return renderingType; + } + + + /** + * The correlation identifier + * + * @return correlationId + **/ + public String getCorrelationId() { + return correlationId; + } + + + /** + * The metadata + * + * @return metadata + **/ + public Map getMetadata() { + return metadata; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + JSONObject o = new JSONObject(); + try { + + o.put(KEY_BLOB_ID, DataUtils.byteArrayToHexString(this.blobId)); + if (this.thumbnailBlobId != null) + o.put(KEY_THUMBNAIL_BLOB_ID, DataUtils.byteArrayToHexString(this.thumbnailBlobId)); + if (this.thumbnailMediaType != null) + o.put(KEY_THUMBNAIL_MEDIA_TYPE, this.thumbnailMediaType); + o.put(KEY_ENCRYPTION_KEY, DataUtils.byteArrayToHexString(this.encryptionKey)); + o.put(KEY_MIME_TYPE, this.mimeType); + if (this.fileName != null) + o.put(KEY_FILE_NAME, this.fileName); + o.put(KEY_SIZE, this.size); + if (this.caption != null) + o.put(KEY_CAPTION, this.caption); + if (this.renderingType != null) + o.put(KEY_RENDERING_TYPE, this.renderingType.getValue()); + if (this.correlationId != null) + o.put(KEY_CORRELATION_ID, this.correlationId); + if (this.metadata != null) + o.put(KEY_METADATA, this.metadata); + } catch (Exception e) { + throw new BadMessageException(); + } + + return o.toString().getBytes(StandardCharsets.UTF_8); + + } + + public static GroupFileMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + GroupFileMessageSerializer.validate(data, realDataLength); + return GroupFileMessageSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, Arrays.hashCode(blobId), Arrays.hashCode(thumbnailBlobId), + thumbnailMediaType, Arrays.hashCode(encryptionKey), mimeType, fileName, + size, caption, renderingType, correlationId, metadata, super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GroupFileMessage {\n"); + sb.append(" blobId: ") + .append(toIndentedString(DataUtils.byteArrayToHexString(getBlobId()))) + .append("\n"); + sb.append(" thumbnailBlobId: ") + .append(toIndentedString( + DataUtils.byteArrayToHexString(getThumbnailBlobId()))) + .append("\n"); + sb.append(" thumbnailMediaType: ").append(toIndentedString(getThumbnailMediaType())) + .append("\n"); + sb.append(" encryptionKey: ") + .append(toIndentedString( + DataUtils.byteArrayToHexString(getEncryptionKey()))) + .append("\n"); + sb.append(" mimeType: ").append(toIndentedString(getMimeType())).append("\n"); + sb.append(" fileName: ").append(toIndentedString(getFilename())).append("\n"); + sb.append(" size: ").append(toIndentedString(getSize())).append("\n"); + sb.append(" caption: ").append(toIndentedString(getCaption())).append("\n"); + sb.append(" renderingType: ").append(toIndentedString(getRenderingType())).append("\n"); + sb.append(" correlationId: ").append(toIndentedString(getCorrelationId())).append("\n"); + sb.append(" metadata: ").append(toIndentedString(getMetadata())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/GroupLeaveMessage.java b/source/src/main/java/ch/threema/apitool/messages/GroupLeaveMessage.java new file mode 100644 index 0000000..3f2c894 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/GroupLeaveMessage.java @@ -0,0 +1,139 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.GroupLeaveMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A leave group message that can be sent/received with end-to-end encryption via Threema. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.491631911+00:00") +public class GroupLeaveMessage extends ThreemaGroupMessage { + public static final int TYPE_CODE = 0x4c; + + + private final GroupId groupId; + + public GroupLeaveMessage(GroupId groupId) { + super(groupId); + this.groupId = groupId; + } + + /** + * The group identifier + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + + return GroupLeaveMessageSerializer.serialize(groupId); + } + + public static GroupLeaveMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + GroupLeaveMessageSerializer.validate(data, realDataLength); + var jsonData = GroupLeaveMessageSerializer.extractJson(data, realDataLength); + try { + // JSONObject o = new JSONObject(new String(jsonData)); + + + return new GroupLeaveMessage(GroupLeaveMessageSerializer.extractGroupId(data)); + } catch (JSONException e) { + throw new BadMessageException(); + } + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GroupLeaveMessage {\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/GroupLocationMessage.java b/source/src/main/java/ch/threema/apitool/messages/GroupLocationMessage.java new file mode 100644 index 0000000..9f47c6c --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/GroupLocationMessage.java @@ -0,0 +1,208 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.GroupLocationMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A Location Message + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.498793345+00:00") +public class GroupLocationMessage extends ThreemaGroupMessage { + public static final int TYPE_CODE = 0x42; + + + private final GroupId groupId; + private final String latitude; + private final String longitude; + private final Float accuracy; + private final String poiName; + private final String address; + + public GroupLocationMessage(GroupId groupId, String latitude, String longitude, Float accuracy, + String poiName, String address) { + super(groupId); + this.groupId = groupId; + this.latitude = latitude; + this.longitude = longitude; + this.accuracy = accuracy; + this.poiName = poiName; + this.address = address; + } + + /** + * The group identifier + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + /** + * The geographic latitude + * + * @return latitude + **/ + public String getLatitude() { + return latitude; + } + + /** + * The geographic longitude + * + * @return longitude + **/ + public String getLongitude() { + return longitude; + } + + /** + * The location accuracy + * + * @return accuracy + **/ + public Float getAccuracy() { + return accuracy; + } + + /** + * The location name + * + * @return poiName + **/ + public String getPoiName() { + return poiName; + } + + /** + * The location address + * + * @return address + **/ + public String getPoiAddress() { + return address; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + String locationString; + if (accuracy == null) { + locationString = String.format("%s,%s", latitude, longitude); + } else { + locationString = String.format("%s,%s,%f", latitude, longitude, accuracy); + } + + if (poiName != null) { + locationString += "\n" + poiName; + } + + if (address != null) { + locationString += "\n" + address.replace("\n", "\\n"); + } + + return locationString.getBytes(StandardCharsets.UTF_8); + + } + + public static GroupLocationMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + GroupLocationMessageSerializer.validate(data, realDataLength); + return GroupLocationMessageSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, latitude, longitude, accuracy, poiName, address, + super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GroupLocationMessage {\n"); + sb.append(" latitude: ").append(toIndentedString(getLatitude())).append("\n"); + sb.append(" longitude: ").append(toIndentedString(getLongitude())).append("\n"); + sb.append(" accuracy: ").append(toIndentedString(getAccuracy())).append("\n"); + sb.append(" poiName: ").append(toIndentedString(getPoiName())).append("\n"); + sb.append(" address: ").append(toIndentedString(getPoiAddress())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/GroupRenameMessage.java b/source/src/main/java/ch/threema/apitool/messages/GroupRenameMessage.java new file mode 100644 index 0000000..f074861 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/GroupRenameMessage.java @@ -0,0 +1,144 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.GroupRenameMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A group rename message that can be sent/received with end-to-end encryption via Threema. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.490762208+00:00") +public class GroupRenameMessage extends ThreemaGroupMessage { + public static final boolean noPrependGroupCreator = true; + public static final int TYPE_CODE = 0x4b; + + + private final GroupId groupId; + private final String groupName; + + public GroupRenameMessage(GroupId groupId, String groupName) { + super(groupId); + this.groupId = groupId; + this.groupName = groupName; + } + + /** + * The group identifier + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + /** + * The group name + * + * @return groupName + **/ + public String getGroupName() { + return groupName; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + + return GroupRenameMessageSerializer.serialize(groupId, groupName); + } + + public static GroupRenameMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + GroupRenameMessageSerializer.validate(data, realDataLength); + return GroupRenameMessageSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, groupName, super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GroupRenameMessage {\n"); + sb.append(" groupName: ").append(toIndentedString(getGroupName())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/GroupRequestSyncMessage.java b/source/src/main/java/ch/threema/apitool/messages/GroupRequestSyncMessage.java new file mode 100644 index 0000000..0113940 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/GroupRequestSyncMessage.java @@ -0,0 +1,141 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.GroupRequestSyncMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A group request sync message that can be sent/received with end-to-end encryption via Threema. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.495782551+00:00") +public class GroupRequestSyncMessage extends ThreemaGroupMessage { + public static final boolean noPrependGroupCreator = true; + public static final int TYPE_CODE = 0x51; + + + private final GroupId groupId; + + public GroupRequestSyncMessage(GroupId groupId) { + super(groupId); + this.groupId = groupId; + } + + /** + * The group identifier + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + + return GroupRequestSyncMessageSerializer.serialize(groupId); + } + + public static GroupRequestSyncMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + GroupRequestSyncMessageSerializer.validate(data, realDataLength); + var jsonData = GroupRequestSyncMessageSerializer.extractJson(data, realDataLength); + try { + JSONObject o = new JSONObject(new String(jsonData)); + + + return new GroupRequestSyncMessage( + GroupRequestSyncMessageSerializer.extractGroupId(data)); + } catch (JSONException e) { + throw new BadMessageException(); + } + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GroupRequestSyncMessage {\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/GroupSetPhoto.java b/source/src/main/java/ch/threema/apitool/messages/GroupSetPhoto.java new file mode 100644 index 0000000..60e55e8 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/GroupSetPhoto.java @@ -0,0 +1,191 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.GroupSetPhotoSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A group set profile picture message that can be sent/received with end-to-end encryption via + * Threema. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.492572411+00:00") +public class GroupSetPhoto extends ThreemaGroupMessage { + public static final boolean noPrependGroupCreator = true; + public static final int TYPE_CODE = 0x50; + + + private final GroupId groupId; + private final byte[] blobId; + private final int size; + private final byte[] encryptionKey; + + public GroupSetPhoto(GroupId groupId, byte[] blobId, int size, byte[] encryptionKey) { + super(groupId); + this.groupId = groupId; + this.blobId = blobId; + this.size = size; + this.encryptionKey = encryptionKey; + } + + /** + * The group identifier + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + /** + * The blob ID + * + * @return blobId + **/ + public byte[] getBlobId() { + return blobId; + } + + /** + * The file size + * + * @return size + **/ + public int getSize() { + return size; + } + + /** + * The encryption key + * + * @return encryptionKey + **/ + public byte[] getEncryptionKey() { + return encryptionKey; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + JSONObject o = new JSONObject(); + try { + + } catch (Exception e) { + throw new BadMessageException(); + } + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + bos.write(blobId); + EndianUtils.writeSwappedInteger(bos, Math.toIntExact(size)); + bos.write(encryptionKey); + if (o.length() > 0) { + bos.write(o.toString().getBytes(StandardCharsets.UTF_8)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return bos.toByteArray(); + } + + public static GroupSetPhoto fromString(byte[] data, int realDataLength) + throws BadMessageException { + GroupSetPhotoSerializer.validate(data, realDataLength); + return GroupSetPhotoSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, Arrays.hashCode(blobId), size, Arrays.hashCode(encryptionKey), + super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GroupSetPhoto {\n"); + sb.append(" blobId: ") + .append(toIndentedString(DataUtils.byteArrayToHexString(getBlobId()))) + .append("\n"); + sb.append(" size: ").append(toIndentedString(getSize())).append("\n"); + sb.append(" encryptionKey: ") + .append(toIndentedString( + DataUtils.byteArrayToHexString(getEncryptionKey()))) + .append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/GroupTextMessage.java b/source/src/main/java/ch/threema/apitool/messages/GroupTextMessage.java new file mode 100644 index 0000000..ea5e8c7 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/GroupTextMessage.java @@ -0,0 +1,171 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.GroupTextMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A group text message that can be sent/received with end-to-end encryption via Threema. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.497104708+00:00") +public class GroupTextMessage extends ThreemaGroupMessage { + public static final int TYPE_CODE = 0x41; + + + private final GroupId groupId; + private final String text; + + public GroupTextMessage(GroupId groupId, String text) { + super(groupId); + this.groupId = groupId; + this.text = text; + } + + /** + * The group identifier + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + /** + * The message text + * + * @return text + **/ + public String getText() { + return text; + } + + /** + * Whether or not the message is a quote + * + * @return Whether or not the message is a quote + */ + public boolean IsQuote() { + var pattern = Pattern.compile(DataUtils.QUOTE_PATTERN, Pattern.DOTALL); + return pattern.matcher(text).matches(); + } + + /** + * Gets quoted message id if the message is a quote + * + * @return Quoted message id + */ + public String getQuotedMessageId() { + return DataUtils.extractQuote(text, QuotePart.QUOTED_MESSAGE_ID); + } + + /** + * Gets quote text if the message is a quote + * + * @return Quote text + */ + public String getQuoteText() { + return DataUtils.extractQuote(text, QuotePart.QUOTE_TEXT); + } + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + + return text.getBytes(StandardCharsets.UTF_8); + + } + + public static GroupTextMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + GroupTextMessageSerializer.validate(data, realDataLength); + return GroupTextMessageSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, text, super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class GroupTextMessage {\n"); + sb.append(" text: ").append(toIndentedString(getText())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/ImageMessage.java b/source/src/main/java/ch/threema/apitool/messages/ImageMessage.java index 40e6369..1c18a53 100644 --- a/source/src/main/java/ch/threema/apitool/messages/ImageMessage.java +++ b/source/src/main/java/ch/threema/apitool/messages/ImageMessage.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,17 +26,26 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.messages; -import ch.threema.apitool.DataUtils; +import ch.threema.apitool.utils.DataUtils; import com.neilalexander.jnacl.NaCl; import org.apache.commons.io.EndianUtils; /** * An image message that can be sent/received with end-to-end encryption via Threema. + * + * @deprecated */ +@Deprecated +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.677063353+00:00") public class ImageMessage extends ThreemaMessage { public static final int TYPE_CODE = 0x02; @@ -39,6 +54,14 @@ public class ImageMessage extends ThreemaMessage { private final byte[] nonce; + /** + * + * @param blobId + * @param size + * @param nonce + * @deprecated + */ + @Deprecated public ImageMessage(byte[] blobId, int size, byte[] nonce) { this.blobId = blobId; @@ -67,7 +90,7 @@ public int getTypeCode() { @Override public String toString() { - return "blob " + DataUtils.byteArrayToHexString(this.blobId); + return "image{blob " + DataUtils.byteArrayToHexString(this.blobId) + "}"; } @Override @@ -80,7 +103,7 @@ public byte[] getData() { EndianUtils.writeSwappedInteger(data, pos, this.size); pos += 4; - System.arraycopy(this.nonce, 0, data, pos, NaCl.NONCEBYTES); + System.arraycopy(this.nonce, 0, data, pos, NaCl.NONCEBYTES); return data; } diff --git a/source/src/main/java/ch/threema/apitool/messages/LocationMessage.java b/source/src/main/java/ch/threema/apitool/messages/LocationMessage.java new file mode 100644 index 0000000..7af7f02 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/LocationMessage.java @@ -0,0 +1,191 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.serializers.LocationMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A Location Message + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.474090431+00:00") +public class LocationMessage extends ThreemaMessage { + public static final int TYPE_CODE = 0x10; + + + private final String latitude; + private final String longitude; + private final Float accuracy; + private final String poiName; + private final String address; + + public LocationMessage(String latitude, String longitude, Float accuracy, String poiName, + String address) { + this.latitude = latitude; + this.longitude = longitude; + this.accuracy = accuracy; + this.poiName = poiName; + this.address = address; + } + + /** + * The geographic latitude + * + * @return latitude + **/ + public String getLatitude() { + return latitude; + } + + /** + * The geographic longitude + * + * @return longitude + **/ + public String getLongitude() { + return longitude; + } + + /** + * The location accuracy + * + * @return accuracy + **/ + public Float getAccuracy() { + return accuracy; + } + + /** + * The location name + * + * @return poiName + **/ + public String getPoiName() { + return poiName; + } + + /** + * The location address + * + * @return address + **/ + public String getPoiAddress() { + return address; + } + + + + @Override + public int getTypeCode() { + return TYPE_CODE; + } + + @Override + public byte[] getData() throws BadMessageException { + String locationString; + if (accuracy == null) { + locationString = String.format("%s,%s", latitude, longitude); + } else { + locationString = String.format("%s,%s,%f", latitude, longitude, accuracy); + } + + if (poiName != null) { + locationString += "\n" + poiName; + } + + if (address != null) { + locationString += "\n" + address.replace("\n", "\\n"); + } + + return locationString.getBytes(StandardCharsets.UTF_8); + + } + + public static LocationMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + LocationMessageSerializer.validate(data, realDataLength); + return LocationMessageSerializer.deserialize(data, realDataLength); + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(latitude, longitude, accuracy, poiName, address, super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class LocationMessage {\n"); + sb.append(" latitude: ").append(toIndentedString(getLatitude())).append("\n"); + sb.append(" longitude: ").append(toIndentedString(getLongitude())).append("\n"); + sb.append(" accuracy: ").append(toIndentedString(getAccuracy())).append("\n"); + sb.append(" poiName: ").append(toIndentedString(getPoiName())).append("\n"); + sb.append(" address: ").append(toIndentedString(getPoiAddress())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/TextMessage.java b/source/src/main/java/ch/threema/apitool/messages/TextMessage.java index 3a22e06..38a1f41 100644 --- a/source/src/main/java/ch/threema/apitool/messages/TextMessage.java +++ b/source/src/main/java/ch/threema/apitool/messages/TextMessage.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,45 +26,129 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.messages; -import java.io.UnsupportedEncodingException; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.serializers.TextMessageSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; /** * A text message that can be sent/received with end-to-end encryption via Threema. */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.473407065+00:00") public class TextMessage extends ThreemaMessage { - public static final int TYPE_CODE = 0x01; + private final String text; public TextMessage(String text) { this.text = text; } + /** + * The message text + * + * @return text + **/ public String getText() { return text; } + /** + * Whether or not the message is a quote + * + * @return Whether or not the message is a quote + */ + public boolean IsQuote() { + var pattern = Pattern.compile(DataUtils.QUOTE_PATTERN, Pattern.DOTALL); + return pattern.matcher(text).matches(); + } + + /** + * Gets quoted message id if the message is a quote + * + * @return Quoted message id + */ + public String getQuotedMessageId() { + return DataUtils.extractQuote(text, QuotePart.QUOTED_MESSAGE_ID); + } + + /** + * Gets quote text if the message is a quote + * + * @return Quote text + */ + public String getQuoteText() { + return DataUtils.extractQuote(text, QuotePart.QUOTE_TEXT); + } + + @Override public int getTypeCode() { return TYPE_CODE; } @Override - public String toString() { - return text; + public byte[] getData() throws BadMessageException { + + return TextMessageSerializer.serialize(text); + } + + public static TextMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + TextMessageSerializer.validate(data, realDataLength); + return TextMessageSerializer.deserialize(data, realDataLength); + } @Override - public byte[] getData() { - try { - return text.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - return null; + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(text, super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class TextMessage {\n"); + sb.append(" text: ").append(toIndentedString(getText())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); } } diff --git a/source/src/main/java/ch/threema/apitool/messages/ThreemaGroupMessage.java b/source/src/main/java/ch/threema/apitool/messages/ThreemaGroupMessage.java new file mode 100644 index 0000000..7b5095c --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/messages/ThreemaGroupMessage.java @@ -0,0 +1,130 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.messages; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * ThreemaGroupMessage + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.472733289+00:00") +public abstract class ThreemaGroupMessage extends ThreemaMessage { + + + + private final GroupId groupId; + + public ThreemaGroupMessage(GroupId groupId) { + this.groupId = groupId; + } + + /** + * The group ID + * + * @return groupId + **/ + public GroupId getGroupId() { + return groupId; + } + + + + @Override + public byte[] getData() throws BadMessageException { + JSONObject o = new JSONObject(); + try { + + } catch (Exception e) { + throw new BadMessageException(); + } + + return o.toString().getBytes(StandardCharsets.UTF_8); + + } + + public static ThreemaGroupMessage fromString(byte[] data, int realDataLength) + throws BadMessageException { + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, super.hashCode()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ThreemaGroupMessage {\n"); + sb.append(" groupId: ").append(toIndentedString(getGroupId())).append("\n"); + + sb.append(" ").append(toIndentedString(super.toString())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/messages/ThreemaMessage.java b/source/src/main/java/ch/threema/apitool/messages/ThreemaMessage.java index f96265b..223df65 100644 --- a/source/src/main/java/ch/threema/apitool/messages/ThreemaMessage.java +++ b/source/src/main/java/ch/threema/apitool/messages/ThreemaMessage.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending, and receiving Threema messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,17 +26,39 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.messages; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; +import java.util.regex.Pattern; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.QuotePart; import ch.threema.apitool.exceptions.BadMessageException; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + /** - * Abstract base class of messages that can be sent with end-to-end encryption via Threema. + * ThreemaMessage */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2024-03-15T13:44:24.472344386+00:00") public abstract class ThreemaMessage { - public static final int BLOB_ID_LEN = 16; /** @@ -38,6 +66,22 @@ public abstract class ThreemaMessage { */ public abstract byte[] getData() throws BadMessageException; + public static ThreemaMessage fromString(byte[] jsonData, int realDataLength) + throws BadMessageException { + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return super.equals(o); + } + /** * @return the message's type code */ diff --git a/source/src/main/java/ch/threema/apitool/results/CapabilityResult.java b/source/src/main/java/ch/threema/apitool/results/CapabilityResult.java index 65284c9..8cf25f4 100644 --- a/source/src/main/java/ch/threema/apitool/results/CapabilityResult.java +++ b/source/src/main/java/ch/threema/apitool/results/CapabilityResult.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.results; @@ -79,8 +89,8 @@ public boolean canFile() { } private boolean can(String key) { - for(String k: this.capabilities) { - if(k.equals(key)) { + for (String k : this.capabilities) { + if (k.equals(key)) { return true; } } @@ -91,8 +101,8 @@ private boolean can(String key) { public String toString() { StringBuilder b = new StringBuilder(); b.append(this.key).append(": "); - for(int n = 0; n < this.capabilities.length; n++) { - if(n > 0) { + for (int n = 0; n < this.capabilities.length; n++) { + if (n > 0) { b.append(","); } b.append(this.capabilities[n]); diff --git a/source/src/main/java/ch/threema/apitool/results/EncryptResult.java b/source/src/main/java/ch/threema/apitool/results/EncryptResult.java index 71fb91a..85e26fc 100644 --- a/source/src/main/java/ch/threema/apitool/results/EncryptResult.java +++ b/source/src/main/java/ch/threema/apitool/results/EncryptResult.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.results; diff --git a/source/src/main/java/ch/threema/apitool/results/UploadResult.java b/source/src/main/java/ch/threema/apitool/results/UploadResult.java index 055b9ad..f3e4aa9 100644 --- a/source/src/main/java/ch/threema/apitool/results/UploadResult.java +++ b/source/src/main/java/ch/threema/apitool/results/UploadResult.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool.results; diff --git a/source/src/main/java/ch/threema/apitool/serializers/BallotChoiceSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/BallotChoiceSerializer.java new file mode 100644 index 0000000..ac68261 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/BallotChoiceSerializer.java @@ -0,0 +1,50 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.exceptions.BadMessageException; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class BallotChoiceSerializer implements CustomMessageSerializer { + + public static byte[] extractJson(byte[] data, int realDataLength) { + return data; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + // true + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/BallotCreateMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/BallotCreateMessageSerializer.java new file mode 100644 index 0000000..a2afa12 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/BallotCreateMessageSerializer.java @@ -0,0 +1,117 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.types.voting.*; +import org.json.JSONException; +import org.json.JSONObject; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.messages.BallotCreateMessage; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class BallotCreateMessageSerializer implements CustomMessageSerializer { + + private final static String KEY_DESCRIPTION = "d"; + private final static String KEY_STATE = "s"; + private final static String KEY_VOTING_MODE = "a"; + private final static String KEY_RESULTS_DISCLOSURE_TYPE = "t"; + private final static String KEY_ORDER = "o"; + private final static String KEY_DISPLAY_MODE = "u"; + private final static String KEY_CHOICES = "c"; + private final static String KEY_PARTICIPANTS = "p"; + + public static BallotCreateMessage deserialize(byte[] data, int realDataLength) + throws BadMessageException { + var ballotId = Arrays.copyOfRange(data, 1, 9); + var json = extractJson(data, realDataLength); + try { + var o = new JSONObject(new String(json)); + + String description = o.getString(KEY_DESCRIPTION); + State state = State.valueOf(o.getInt(KEY_STATE)); + VotingMode votingMode = VotingMode.valueOf(o.getInt(KEY_VOTING_MODE)); + ResultsDisclosureType resultsDisclosureType = + ResultsDisclosureType.valueOf(o.getInt(KEY_RESULTS_DISCLOSURE_TYPE)); + int order = o.getInt(KEY_ORDER); + DisplayMode displayMode; + if (o.has(KEY_DISPLAY_MODE)) { + displayMode = DisplayMode.valueOf(o.getInt(KEY_DISPLAY_MODE)); + } else { + displayMode = DisplayMode.LIST; + } + var choicesJ = o.getJSONArray(KEY_CHOICES); + var choices = new ArrayList(); + if (choicesJ != null) { + for (int i = 0; i < choicesJ.length(); i++) { + choices.add(BallotChoice.fromString(choicesJ.getJSONObject(i).toString() + .getBytes(StandardCharsets.UTF_8), realDataLength)); + } + } + var participantsJ = o.getJSONArray(KEY_PARTICIPANTS); + var participants = new ArrayList(); + if (participantsJ != null) { + for (int i = 0; i < participantsJ.length(); i++) { + participants.add(participantsJ.getString(i)); + } + } + + return new BallotCreateMessage(ballotId, description, state, votingMode, + resultsDisclosureType, order, displayMode, choices, participants); + } catch (JSONException e) { + throw new BadMessageException(); + } + } + + public static GroupId extractGroupId(byte[] data) { + throw new UnsupportedOperationException( + "BallotCreateMessage GroupId extraction not implemented yet!"); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new String(data, 9, realDataLength - 9, StandardCharsets.UTF_8) + .getBytes(StandardCharsets.UTF_8); + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + // true + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/BallotVoteMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/BallotVoteMessageSerializer.java new file mode 100644 index 0000000..9247d97 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/BallotVoteMessageSerializer.java @@ -0,0 +1,110 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.types.voting.VoteChoice; +import ch.threema.apitool.utils.ProtocolConstants; +import org.json.JSONException; +import org.json.JSONArray; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.messages.BallotVoteMessage; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class BallotVoteMessageSerializer implements CustomMessageSerializer { + + public static byte[] serialize(byte[] creator, byte[] ballotId, List votes) + throws BadMessageException { + JSONArray a = new JSONArray(); + try { + for (var item : votes) { + a.put(new JSONArray(new String(item.getData()))); + } + } catch (Exception e) { + throw new BadMessageException(); + } + byte[] preamble = new byte[GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN]; + System.arraycopy(creator, 0, preamble, 0, GroupId.CREATOR_ID_LEN); + System.arraycopy(ballotId, 0, preamble, GroupId.CREATOR_ID_LEN, + ProtocolConstants.BALLOT_ID_LEN); + byte[] arrayBytes = a.toString().getBytes(StandardCharsets.UTF_8); + var data = new byte[arrayBytes.length + preamble.length]; + System.arraycopy(preamble, 0, data, 0, preamble.length); + System.arraycopy(arrayBytes, 0, data, preamble.length, arrayBytes.length); + return data; + } + + public static BallotVoteMessage deserialize(byte[] data, int realDataLength) + throws BadMessageException { + var preamble = Arrays.copyOfRange(data, 1, 1 + GroupId.CREATOR_ID_LEN + 8); + var creator = Arrays.copyOfRange(preamble, 0, GroupId.CREATOR_ID_LEN); + var ballotId = Arrays.copyOfRange(data, + GroupId.GROUP_ID_LEN + 2 * GroupId.CREATOR_ID_LEN + 1, + GroupId.GROUP_ID_LEN + 2 * GroupId.CREATOR_ID_LEN + 1 + + ProtocolConstants.BALLOT_ID_LEN); + var json = extractJson(data, realDataLength); + + try { + JSONArray a = new JSONArray(new String(json)); + + var votes = new ArrayList(); + for (int i = 0; i < a.length(); i++) { + var choiceEntry = a.getJSONArray(i); + votes.add(new VoteChoice(choiceEntry.getInt(0), choiceEntry.getInt(1) == 1)); + } + + return new BallotVoteMessage(creator, ballotId, votes); + } catch (JSONException e) { + throw new BadMessageException(); + } + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new String(data, GroupId.CREATOR_ID_LEN + 9, + realDataLength - (GroupId.CREATOR_ID_LEN + 9), StandardCharsets.UTF_8) + .getBytes(StandardCharsets.UTF_8); + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + // true + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/CustomMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/CustomMessageSerializer.java new file mode 100644 index 0000000..5cfbaff --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/CustomMessageSerializer.java @@ -0,0 +1,62 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.messages.ThreemaMessage; +import ch.threema.apitool.types.GroupId; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.963709491+00:00") +public interface CustomMessageSerializer { + + static byte[] serialize() { + return null; + } + + static ThreemaMessage deserialize(byte[] jsonData, int realDataLength) { + return null; + } + + static GroupId extractGroupId(byte[] data) { + return null; + } + + static byte[] extractJson(byte[] data, int realDataLength) { + return null; + } + + static void validate(byte[] data, int realDataLength) throws BadMessageException {} +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/DeliveryReceiptSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/DeliveryReceiptSerializer.java new file mode 100644 index 0000000..29c9fc7 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/DeliveryReceiptSerializer.java @@ -0,0 +1,95 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.utils.ProtocolConstants; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.messages.DeliveryReceipt; + +public class DeliveryReceiptSerializer implements CustomMessageSerializer { + + @javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") + public static byte[] serialize(DeliveryReceipt.Type receiptType, + List ackedMessageIds) { + var bytes = new byte[ackedMessageIds.size() * ProtocolConstants.MESSAGE_ID_LEN + 1]; + ByteBuffer dataBuf = ByteBuffer.wrap(bytes); + dataBuf.order(ByteOrder.LITTLE_ENDIAN); + int offset = 1; + for (var msgId : ackedMessageIds) { + System.arraycopy(msgId.getMessageId(), 0, bytes, offset, + ProtocolConstants.MESSAGE_ID_LEN); + offset += ProtocolConstants.MESSAGE_ID_LEN; + } + dataBuf.rewind(); + dataBuf.put((byte) receiptType.getCode()); + return dataBuf.array(); + } + + public static DeliveryReceipt deserialize(byte[] data, int realDataLength) { + DeliveryReceipt.Type type = DeliveryReceipt.Type.get(data[1] & 0xFF); + int numMsgIds = ((realDataLength - 2) / MessageId.MESSAGE_ID_LEN); + + List messageIds = new ArrayList<>(); + for (int i = 0; i < numMsgIds; i++) { + int offset = 2 + i * MessageId.MESSAGE_ID_LEN; + messageIds.add(new MessageId(data, offset)); + } + + return new DeliveryReceipt(type, messageIds); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + if (realDataLength < ProtocolConstants.MESSAGE_ID_LEN + 2 + || ((realDataLength - 2) % ProtocolConstants.MESSAGE_ID_LEN) != 0) { + throw new BadMessageException( + "Bad length (" + realDataLength + ") for delivery receipt"); + } + DeliveryReceipt.Type receiptType = DeliveryReceipt.Type.get(data[1] & 0xFF); + if (receiptType == null) + throw new BadMessageException(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/FileMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/FileMessageSerializer.java new file mode 100644 index 0000000..6a8fdb3 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/FileMessageSerializer.java @@ -0,0 +1,107 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.messages.FileMessage; +import ch.threema.apitool.types.FileRenderingType; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.utils.DataUtils; +import org.json.JSONException; +import org.json.JSONObject; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class FileMessageSerializer implements CustomMessageSerializer { + + private final static String KEY_BLOB_ID = "b"; + private final static String KEY_THUMBNAIL_BLOB_ID = "t"; + private final static String KEY_THUMBNAIL_MEDIA_TYPE = "p"; + private final static String KEY_ENCRYPTION_KEY = "k"; + private final static String KEY_MIME_TYPE = "m"; + private final static String KEY_FILE_NAME = "n"; + private final static String KEY_SIZE = "s"; + private final static String KEY_CAPTION = "d"; + private final static String KEY_RENDERING_TYPE = "j"; + private final static String KEY_CORRELATION_ID = "c"; + private final static String KEY_METADATA = "x"; + + public static FileMessage deserialize(byte[] data, int realDataLength) + throws BadMessageException { + var jsonData = FileMessageSerializer.extractJson(data, realDataLength); + try { + JSONObject o = new JSONObject(new String(jsonData)); + + byte[] blobId = DataUtils.hexStringToByteArray(o.getString(KEY_BLOB_ID)); + byte[] thumbnailBlobId = + DataUtils.hexStringToByteArray(o.optString(KEY_THUMBNAIL_BLOB_ID, "")); + String thumbnailMediaType = o.optString(KEY_THUMBNAIL_MEDIA_TYPE, ""); + byte[] encryptionKey = DataUtils.hexStringToByteArray(o.getString(KEY_ENCRYPTION_KEY)); + String mimeType = o.getString(KEY_MIME_TYPE); + String fileName = o.optString(KEY_FILE_NAME, "unnamed"); + int size = o.getInt(KEY_SIZE); + String caption = o.optString(KEY_CAPTION); + FileRenderingType renderingType = + FileRenderingType.valueOf(o.optInt(KEY_RENDERING_TYPE, 0)); + String correlationId = o.optString(KEY_CORRELATION_ID, ""); + Map metadata = o.optJSONObject(KEY_METADATA) != null + ? o.optJSONObject(KEY_METADATA).toMap() + : null; + + return new FileMessage(blobId, thumbnailBlobId, thumbnailMediaType, encryptionKey, + mimeType, fileName, size, caption, renderingType, correlationId, + metadata); + } catch (JSONException e) { + throw new BadMessageException(); + } + } + + public static GroupId extractGroupId(byte[] data) { + throw new UnsupportedOperationException( + "FileMessage GroupId extraction not implemented yet!"); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new String(data, 1, realDataLength - 1, StandardCharsets.UTF_8) + .getBytes(StandardCharsets.UTF_8); + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + // true + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/GroupBallotCreateMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/GroupBallotCreateMessageSerializer.java new file mode 100644 index 0000000..49827be --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/GroupBallotCreateMessageSerializer.java @@ -0,0 +1,124 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.utils.ProtocolConstants; +import org.json.JSONException; +import org.json.JSONObject; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.messages.GroupBallotCreateMessage; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class GroupBallotCreateMessageSerializer implements CustomMessageSerializer { + + private final static String KEY_DESCRIPTION = "d"; + private final static String KEY_STATE = "s"; + private final static String KEY_VOTING_MODE = "a"; + private final static String KEY_RESULTS_DISCLOSURE_TYPE = "t"; + private final static String KEY_ORDER = "o"; + private final static String KEY_DISPLAY_MODE = "u"; + private final static String KEY_CHOICES = "c"; + private final static String KEY_PARTICIPANTS = "p"; + + public static GroupBallotCreateMessage deserialize(byte[] data, int realDataLength) + throws BadMessageException { + var groupId = extractGroupId(data); + var ballotId = Arrays.copyOfRange(data, GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN + 1, + GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN + 1 + + ProtocolConstants.BALLOT_ID_LEN); + var json = extractJson(data, realDataLength); + try { + JSONObject o = new JSONObject(new String(json)); + + String description = o.getString(KEY_DESCRIPTION); + State state = State.valueOf(o.getInt(KEY_STATE)); + VotingMode votingMode = VotingMode.valueOf(o.getInt(KEY_VOTING_MODE)); + ResultsDisclosureType resultsDisclosureType = + ResultsDisclosureType.valueOf(o.getInt(KEY_RESULTS_DISCLOSURE_TYPE)); + int order = o.getInt(KEY_ORDER); + DisplayMode displayMode; + if (o.has(KEY_DISPLAY_MODE)) { + displayMode = DisplayMode.valueOf(o.getInt(KEY_DISPLAY_MODE)); + } else { + displayMode = DisplayMode.LIST; + } + var choicesJ = o.getJSONArray(KEY_CHOICES); + var choices = new ArrayList(); + if (choicesJ != null) { + for (int i = 0; i < choicesJ.length(); i++) { + choices.add(BallotChoice.fromString(choicesJ.getJSONObject(i).toString() + .getBytes(StandardCharsets.UTF_8), realDataLength)); + } + } + var participantsJ = o.getJSONArray(KEY_PARTICIPANTS); + var participants = new ArrayList(); + if (participantsJ != null) { + for (int i = 0; i < participantsJ.length(); i++) { + participants.add(participantsJ.getString(i)); + } + } + + return new GroupBallotCreateMessage(groupId, ballotId, description, state, votingMode, + resultsDisclosureType, order, displayMode, choices, participants); + } catch (JSONException e) { + throw new BadMessageException(); + } + } + + public static GroupId extractGroupId(byte[] data) { + return new GroupId( + Arrays.copyOfRange(data, 1 + GroupId.CREATOR_ID_LEN, + 1 + GroupId.CREATOR_ID_LEN + GroupId.CREATOR_ID_LEN), + Arrays.copyOfRange(data, 1, 1 + GroupId.CREATOR_ID_LEN)); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new String(data, GroupId.GROUP_ID_LEN + 2 * GroupId.CREATOR_ID_LEN + 1, + realDataLength - (GroupId.GROUP_ID_LEN + 2 * GroupId.CREATOR_ID_LEN + 1), + StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8); + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + // true + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/GroupBallotVoteMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/GroupBallotVoteMessageSerializer.java new file mode 100644 index 0000000..455ad3e --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/GroupBallotVoteMessageSerializer.java @@ -0,0 +1,115 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.types.voting.VoteChoice; +import ch.threema.apitool.utils.ProtocolConstants; +import org.json.JSONException; +import org.json.JSONArray; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.messages.GroupBallotVoteMessage; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class GroupBallotVoteMessageSerializer implements CustomMessageSerializer { + + public static byte[] serialize(GroupId groupId, byte[] creator, byte[] ballotId, + List votes) throws BadMessageException { + JSONArray a = new JSONArray(); + try { + for (var item : votes) { + a.put(new JSONArray(new String(item.getData()))); + } + } catch (Exception e) { + throw new BadMessageException(); + } + byte[] preamble = new byte[GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN]; + System.arraycopy(creator, 0, preamble, 0, GroupId.CREATOR_ID_LEN); + System.arraycopy(ballotId, 0, preamble, GroupId.CREATOR_ID_LEN, + ProtocolConstants.BALLOT_ID_LEN); + byte[] arrayBytes = a.toString().getBytes(StandardCharsets.UTF_8); + var data = new byte[arrayBytes.length + preamble.length]; + System.arraycopy(preamble, 0, data, 0, preamble.length); + System.arraycopy(arrayBytes, 0, data, preamble.length, arrayBytes.length); + return data; + } + + public static GroupBallotVoteMessage deserialize(byte[] data, int realDataLength) + throws BadMessageException { + var groupId = extractGroupId(data); + var ballotId = Arrays.copyOfRange(data, + GroupId.GROUP_ID_LEN + 2 * GroupId.CREATOR_ID_LEN + 1, + GroupId.GROUP_ID_LEN + 2 * GroupId.CREATOR_ID_LEN + 1 + + ProtocolConstants.BALLOT_ID_LEN); + var json = extractJson(data, realDataLength); + try { + var a = new JSONArray(new String(json)); + + var votes = new ArrayList(); + for (int i = 0; i < a.length(); i++) { + votes.add(new VoteChoice(a.getJSONArray(i).getInt(0), + a.getJSONArray(i).getInt(1) == 1)); + } + + return new GroupBallotVoteMessage(groupId, groupId.getGroupCreator(), ballotId, votes); + } catch (JSONException e) { + throw new BadMessageException(); + } + } + + public static GroupId extractGroupId(byte[] data) { + return new GroupId( + Arrays.copyOfRange(data, 1 + GroupId.CREATOR_ID_LEN, + 1 + GroupId.CREATOR_ID_LEN + GroupId.GROUP_ID_LEN), + Arrays.copyOfRange(data, 1, 1 + GroupId.CREATOR_ID_LEN)); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new String(data, GroupId.GROUP_ID_LEN + 2 * GroupId.CREATOR_ID_LEN + 9, + realDataLength - (GroupId.GROUP_ID_LEN + 2 * GroupId.CREATOR_ID_LEN + 9), + StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8); + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + // true + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/GroupCreateMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/GroupCreateMessageSerializer.java new file mode 100644 index 0000000..db279a4 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/GroupCreateMessageSerializer.java @@ -0,0 +1,87 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.utils.ProtocolConstants; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.messages.GroupCreateMessage; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class GroupCreateMessageSerializer implements CustomMessageSerializer { + + public static byte[] serialize(GroupId groupId, List members) { + var preambleStr = String.join("", members); + return preambleStr.getBytes(StandardCharsets.UTF_8); + } + + public static GroupCreateMessage deserialize(byte[] data, int realDataLength) + throws BadMessageException { + var groupId = extractGroupId(data); + + int membersIdLen = (realDataLength - (GroupId.GROUP_ID_LEN + 1)) / GroupId.GROUP_ID_LEN; + List members = new ArrayList<>(); + for (int i = 0; i < membersIdLen; i += ProtocolConstants.IDENTITY_LEN) { + members.add(new String(data, GroupId.GROUP_ID_LEN + i + 1, + ProtocolConstants.IDENTITY_LEN, StandardCharsets.UTF_8)); + } + + return new GroupCreateMessage(groupId, members); + } + + public static GroupId extractGroupId(byte[] data) { + return new GroupId(Arrays.copyOfRange(data, 1, 1 + GroupId.GROUP_ID_LEN)); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + if (realDataLength < (1 + ProtocolConstants.GROUP_ID_LEN + ProtocolConstants.IDENTITY_LEN) + || ((realDataLength - 1 - ProtocolConstants.GROUP_ID_LEN) + % ProtocolConstants.IDENTITY_LEN) != 0) { + throw new BadMessageException( + "Bad length (" + realDataLength + ") for group create message"); + } + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/GroupDeletePhotoSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/GroupDeletePhotoSerializer.java new file mode 100644 index 0000000..db6cf8b --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/GroupDeletePhotoSerializer.java @@ -0,0 +1,67 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.messages.GroupDeletePhoto; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class GroupDeletePhotoSerializer implements CustomMessageSerializer { + + public static byte[] serialize(GroupId groupId) { + return new byte[0]; + } + + public static GroupDeletePhoto deserialize(byte[] jsonData, int realDataLength) + throws BadMessageException { + throw new UnsupportedOperationException( + "GroupDeletePhoto deserialization not implemented yet!"); + } + + public static GroupId extractGroupId(byte[] data) { + throw new UnsupportedOperationException( + "GroupDeletePhoto GroupId extraction not implemented yet!"); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + // true + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/GroupDeliveryReceiptSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/GroupDeliveryReceiptSerializer.java new file mode 100644 index 0000000..bfe285c --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/GroupDeliveryReceiptSerializer.java @@ -0,0 +1,119 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.messages.DeliveryReceipt; +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.utils.ProtocolConstants; +import org.json.JSONException; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.messages.GroupDeliveryReceipt; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2023-04-26T11:16:11.724535101+00:00") +public class GroupDeliveryReceiptSerializer implements CustomMessageSerializer { + + public static byte[] serialize(GroupId groupId, DeliveryReceipt.Type receiptType, + List ackedMessageIds) { + var bytes = new byte[GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN + + ackedMessageIds.size() * ProtocolConstants.MESSAGE_ID_LEN + 1]; + ByteBuffer dataBuf = ByteBuffer.wrap(bytes); + dataBuf.put(groupId.getGroupCreator()); + dataBuf.put(groupId.getGroupId()); + dataBuf.order(ByteOrder.LITTLE_ENDIAN); + int offset = 1; + for (var msgId : ackedMessageIds) { + System.arraycopy(msgId.getMessageId(), 0, bytes, offset, + ProtocolConstants.MESSAGE_ID_LEN); + offset += ProtocolConstants.MESSAGE_ID_LEN; + } + dataBuf.rewind(); + dataBuf.put((byte) receiptType.getCode()); + return dataBuf.array(); + } + + public static GroupDeliveryReceipt deserialize(byte[] data, int realDataLength) + throws BadMessageException { + var type = DeliveryReceipt.Type + .get(data[GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN + 1] & 0xFF); + int numMsgIds = ((realDataLength - GroupId.GROUP_ID_LEN - GroupId.CREATOR_ID_LEN - 2) + / MessageId.MESSAGE_ID_LEN); + + List messageIds = new ArrayList<>(); + for (int i = 0; i < numMsgIds; i++) { + int offset = GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN + 2 + + i * MessageId.MESSAGE_ID_LEN; + messageIds.add(new MessageId(data, offset)); + } + + return new GroupDeliveryReceipt(extractGroupId(data), type, messageIds); + } + + public static GroupId extractGroupId(byte[] data) { + return new GroupId( + Arrays.copyOfRange(data, 1 + GroupId.CREATOR_ID_LEN, + 1 + GroupId.CREATOR_ID_LEN + GroupId.GROUP_ID_LEN), + Arrays.copyOfRange(data, 1, 1 + GroupId.CREATOR_ID_LEN)); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + if (realDataLength < (GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN + + ProtocolConstants.MESSAGE_ID_LEN - 2) + || 0 != ((realDataLength - 2) % ProtocolConstants.MESSAGE_ID_LEN)) { + throw new BadMessageException(); + } + + var receiptType = DeliveryReceipt.Type + .get(data[GroupId.CREATOR_ID_LEN + GroupId.GROUP_ID_LEN + 1] & 0xFF); + if (receiptType == null) + throw new BadMessageException(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/GroupFileMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/GroupFileMessageSerializer.java new file mode 100644 index 0000000..912d771 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/GroupFileMessageSerializer.java @@ -0,0 +1,112 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.messages.GroupFileMessage; +import ch.threema.apitool.types.FileRenderingType; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.utils.DataUtils; +import org.json.JSONException; +import org.json.JSONObject; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Map; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class GroupFileMessageSerializer implements CustomMessageSerializer { + + private final static String KEY_BLOB_ID = "b"; + private final static String KEY_THUMBNAIL_BLOB_ID = "t"; + private final static String KEY_THUMBNAIL_MEDIA_TYPE = "p"; + private final static String KEY_ENCRYPTION_KEY = "k"; + private final static String KEY_MIME_TYPE = "m"; + private final static String KEY_FILE_NAME = "n"; + private final static String KEY_SIZE = "s"; + private final static String KEY_CAPTION = "d"; + private final static String KEY_RENDERING_TYPE = "j"; + private final static String KEY_CORRELATION_ID = "c"; + private final static String KEY_METADATA = "x"; + + public static GroupFileMessage deserialize(byte[] data, int realDataLength) + throws BadMessageException { + var jsonData = GroupFileMessageSerializer.extractJson(data, realDataLength); + try { + JSONObject o = new JSONObject(new String(jsonData)); + + byte[] blobId = DataUtils.hexStringToByteArray(o.getString(KEY_BLOB_ID)); + byte[] thumbnailBlobId = + DataUtils.hexStringToByteArray(o.optString(KEY_THUMBNAIL_BLOB_ID, "")); + String thumbnailMediaType = o.optString(KEY_THUMBNAIL_MEDIA_TYPE, ""); + byte[] encryptionKey = DataUtils.hexStringToByteArray(o.getString(KEY_ENCRYPTION_KEY)); + String mimeType = o.getString(KEY_MIME_TYPE); + String fileName = o.optString(KEY_FILE_NAME, "unnamed"); + int size = o.getInt(KEY_SIZE); + String caption = o.optString(KEY_CAPTION, ""); + FileRenderingType renderingType = + FileRenderingType.valueOf(o.optInt(KEY_RENDERING_TYPE, 0)); + String correlationId = o.optString(KEY_CORRELATION_ID, ""); + Map metadata = null; + if (o.optJSONObject(KEY_METADATA) != null) + metadata = o.optJSONObject(KEY_METADATA).toMap(); + + return new GroupFileMessage(extractGroupId(data), blobId, thumbnailBlobId, + thumbnailMediaType, encryptionKey, mimeType, fileName, size, caption, + renderingType, correlationId, metadata); + } catch (JSONException e) { + throw new BadMessageException(); + } + } + + public static GroupId extractGroupId(byte[] data) { + return new GroupId( + Arrays.copyOfRange(data, 1 + GroupId.CREATOR_ID_LEN, + 1 + GroupId.CREATOR_ID_LEN + GroupId.GROUP_ID_LEN), + Arrays.copyOfRange(data, 1, 1 + GroupId.CREATOR_ID_LEN)); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new String(data, GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN + 1, + realDataLength - (GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN + 1), + StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8); + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + // true + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/GroupLeaveMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/GroupLeaveMessageSerializer.java new file mode 100644 index 0000000..3198461 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/GroupLeaveMessageSerializer.java @@ -0,0 +1,66 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.GroupId; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class GroupLeaveMessageSerializer implements CustomMessageSerializer { + + public static byte[] serialize(GroupId groupId) { + return new byte[0]; + } + + public static GroupId extractGroupId(byte[] data) { + return new GroupId( + Arrays.copyOfRange(data, 1 + GroupId.CREATOR_ID_LEN, + 1 + GroupId.CREATOR_ID_LEN + GroupId.GROUP_ID_LEN), + Arrays.copyOfRange(data, 1, 1 + GroupId.CREATOR_ID_LEN)); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return data; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + // true + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/GroupLocationMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/GroupLocationMessageSerializer.java new file mode 100644 index 0000000..6542477 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/GroupLocationMessageSerializer.java @@ -0,0 +1,98 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.utils.ProtocolConstants; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.messages.GroupLocationMessage; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class GroupLocationMessageSerializer implements CustomMessageSerializer { + public static GroupLocationMessage deserialize(byte[] data, int realDataLength) + throws BadMessageException { + GroupId groupId = extractGroupId(data); + String poiName = null, address = null; + String strData = new String(data, GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN + 1, + realDataLength - (GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN + 1), + StandardCharsets.UTF_8); + String[] all = strData.split("\n"); + String[] first = all[0].split(","); + String second = all.length >= 2 ? all[1] : null; + String third = all.length == 3 ? all[2] : null; + String lat = first[0]; + String lng = first[1]; + String accuracy = null; + if (first.length == 3) + accuracy = first[2]; + if (all.length == 2) { + address = second; + } else if (all.length == 3) { + poiName = second; + address = third; + } + + if (Math.abs(Float.parseFloat(lat)) > 90.0 || Math.abs(Float.parseFloat(lng)) > 180.0) { + throw new BadMessageException("Invalid coordinate values in group location message"); + } + + return new GroupLocationMessage(groupId, lat, lng, + accuracy != null ? Float.parseFloat(accuracy) : null, poiName, address); + } + + public static GroupId extractGroupId(byte[] data) { + return new GroupId( + Arrays.copyOfRange(data, 1 + GroupId.CREATOR_ID_LEN, + 1 + GroupId.CREATOR_ID_LEN + GroupId.GROUP_ID_LEN), + Arrays.copyOfRange(data, 1, 1 + GroupId.CREATOR_ID_LEN)); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + if (realDataLength < (1 + ProtocolConstants.IDENTITY_LEN + ProtocolConstants.GROUP_ID_LEN + + 3)) { + throw new BadMessageException( + "Bad length (" + realDataLength + ") for group location message"); + } + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/GroupRenameMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/GroupRenameMessageSerializer.java new file mode 100644 index 0000000..a144131 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/GroupRenameMessageSerializer.java @@ -0,0 +1,75 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.utils.ProtocolConstants; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.messages.GroupRenameMessage; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class GroupRenameMessageSerializer implements CustomMessageSerializer { + + public static byte[] serialize(GroupId groupId, String groupName) { + return groupName.getBytes(StandardCharsets.UTF_8); + } + + public static GroupRenameMessage deserialize(byte[] jsonData, int realDataLength) { + String groupName = new String(jsonData, GroupId.GROUP_ID_LEN + 1, + realDataLength - (GroupId.GROUP_ID_LEN + 1)); + + return new GroupRenameMessage(extractGroupId(jsonData), groupName); + } + + public static GroupId extractGroupId(byte[] data) { + return new GroupId(Arrays.copyOfRange(data, 1, 1 + GroupId.GROUP_ID_LEN)); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + if (realDataLength < (1 + ProtocolConstants.GROUP_ID_LEN)) { + throw new BadMessageException( + "Bad length (" + realDataLength + ") for group rename message"); + } + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/GroupRequestSyncMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/GroupRequestSyncMessageSerializer.java new file mode 100644 index 0000000..ca3eed5 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/GroupRequestSyncMessageSerializer.java @@ -0,0 +1,67 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.utils.ProtocolConstants; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.GroupId; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class GroupRequestSyncMessageSerializer implements CustomMessageSerializer { + + public static byte[] serialize(GroupId groupId) { + return new byte[0]; + } + + public static GroupId extractGroupId(byte[] data) { + return new GroupId(Arrays.copyOfRange(data, 1, 1 + GroupId.CREATOR_ID_LEN)); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return "{}".getBytes(StandardCharsets.UTF_8); + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + if (realDataLength != (1 + ProtocolConstants.GROUP_ID_LEN)) { + throw new BadMessageException( + "Bad length (" + realDataLength + ") for group request sync message"); + } + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/GroupSetPhotoSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/GroupSetPhotoSerializer.java new file mode 100644 index 0000000..3980612 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/GroupSetPhotoSerializer.java @@ -0,0 +1,81 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.utils.ProtocolConstants; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.messages.GroupSetPhoto; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class GroupSetPhotoSerializer implements CustomMessageSerializer { + + public static GroupSetPhoto deserialize(byte[] data, int realDataLength) + throws BadMessageException { + var groupId = extractGroupId(data); + byte[] blobId = new byte[16]; + System.arraycopy(data, GroupId.GROUP_ID_LEN + 1, blobId, 0, 16); + ByteBuffer dataBuf = ByteBuffer.wrap(data); + dataBuf.order(ByteOrder.LITTLE_ENDIAN); + dataBuf.rewind(); + int size = dataBuf.getInt(GroupId.GROUP_ID_LEN + 17); + byte[] encryptionKey = new byte[32]; + System.arraycopy(data, GroupId.GROUP_ID_LEN + 17 + Integer.BYTES, encryptionKey, 0, 32); + + return new GroupSetPhoto(groupId, blobId, size, encryptionKey); + } + + public static GroupId extractGroupId(byte[] data) { + return new GroupId(Arrays.copyOfRange(data, 1, 1 + GroupId.GROUP_ID_LEN)); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + if (realDataLength != (1 + ProtocolConstants.GROUP_ID_LEN + ProtocolConstants.BLOB_ID_LEN + + 4 + ProtocolConstants.BLOB_KEY_LEN)) { + throw new BadMessageException( + "Bad length (" + realDataLength + ") for group set photo message"); + } + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/GroupTextMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/GroupTextMessageSerializer.java new file mode 100644 index 0000000..781a50d --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/GroupTextMessageSerializer.java @@ -0,0 +1,77 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.utils.ProtocolConstants; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.messages.GroupTextMessage; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class GroupTextMessageSerializer implements CustomMessageSerializer { + + public static GroupTextMessage deserialize(byte[] data, int realDataLength) + throws BadMessageException { + var groupId = extractGroupId(data); + String text = new String(data, GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN + 1, + realDataLength - (GroupId.GROUP_ID_LEN + GroupId.CREATOR_ID_LEN + 1), + StandardCharsets.UTF_8); + return new GroupTextMessage(groupId, text); + } + + public static GroupId extractGroupId(byte[] data) { + return new GroupId( + Arrays.copyOfRange(data, 1 + GroupId.CREATOR_ID_LEN, + 1 + GroupId.CREATOR_ID_LEN + GroupId.GROUP_ID_LEN), + Arrays.copyOfRange(data, 1, 1 + GroupId.CREATOR_ID_LEN)); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + if (realDataLength < (1 + ProtocolConstants.IDENTITY_LEN + + ProtocolConstants.GROUP_ID_LEN)) { + throw new BadMessageException( + "Bad length (" + realDataLength + ") for group text message"); + } + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/LocationMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/LocationMessageSerializer.java new file mode 100644 index 0000000..a12d313 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/LocationMessageSerializer.java @@ -0,0 +1,88 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import java.nio.charset.StandardCharsets; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.messages.LocationMessage; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class LocationMessageSerializer implements CustomMessageSerializer { + public static LocationMessage deserialize(byte[] data, int realDataLength) + throws BadMessageException { + String strData = new String(data, 1, realDataLength - 1, StandardCharsets.UTF_8); + String poiName = null, address = null; + String[] all = strData.split("\n"); + String[] first = all[0].split(","); + String second = all.length >= 2 ? all[1] : null; + String third = all.length == 3 ? all[2] : null; + String lat = first[0]; + String lng = first[1]; + String accuracy = null; + if (first.length == 3) + accuracy = first[2]; + if (all.length == 2) { + address = second; + } else if (all.length == 3) { + poiName = second; + address = third; + } + + if (Math.abs(Float.parseFloat(lat)) > 90.0 || Math.abs(Float.parseFloat(lng)) > 180.0) { + throw new BadMessageException("Invalid coordinate values in group location message"); + } + + return new LocationMessage(lat, lng, accuracy != null ? Float.parseFloat(accuracy) : null, + poiName, address); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + if (realDataLength < 4) { + throw new BadMessageException( + "Bad length (" + realDataLength + ") for location message"); + } + String strData = new String(data, 1, realDataLength - 1, StandardCharsets.UTF_8); + String[] all = strData.split("\n"); + if (all.length > 3) { + throw new BadMessageException(); + } + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/TextMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/TextMessageSerializer.java new file mode 100644 index 0000000..9862e82 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/TextMessageSerializer.java @@ -0,0 +1,63 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import java.nio.charset.StandardCharsets; + +import ch.threema.apitool.exceptions.BadMessageException; +import ch.threema.apitool.messages.TextMessage; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class TextMessageSerializer implements CustomMessageSerializer { + + public static byte[] serialize(String text) { + return text.getBytes(StandardCharsets.UTF_8); + } + + public static TextMessage deserialize(byte[] jsonData, int realDataLength) + throws BadMessageException { + return new TextMessage(new String(jsonData, 1, realDataLength - 1, StandardCharsets.UTF_8)); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + if (realDataLength < 2) + throw new BadMessageException("Bad length (" + realDataLength + ") for text message"); + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/ThreemaGroupMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/ThreemaGroupMessageSerializer.java new file mode 100644 index 0000000..fbc5daa --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/ThreemaGroupMessageSerializer.java @@ -0,0 +1,51 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.exceptions.BadMessageException; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class ThreemaGroupMessageSerializer implements CustomMessageSerializer { + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + throw new UnsupportedOperationException( + "ThreemaGroupMessage validation not implemented yet!"); + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/ThreemaMessageSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/ThreemaMessageSerializer.java new file mode 100644 index 0000000..463c884 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/ThreemaMessageSerializer.java @@ -0,0 +1,50 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.exceptions.BadMessageException; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class ThreemaMessageSerializer implements CustomMessageSerializer { + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + throw new UnsupportedOperationException("ThreemaMessage validation not implemented yet!"); + } +} diff --git a/source/src/main/java/ch/threema/apitool/serializers/VoteChoiceSerializer.java b/source/src/main/java/ch/threema/apitool/serializers/VoteChoiceSerializer.java new file mode 100644 index 0000000..7de4381 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/serializers/VoteChoiceSerializer.java @@ -0,0 +1,59 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.serializers; + +import ch.threema.apitool.exceptions.BadMessageException; +import org.json.JSONArray; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class VoteChoiceSerializer implements CustomMessageSerializer { + + public static byte[] serialize(int ballotId, boolean selected) { + return new JSONArray(List.of(ballotId, selected ? 1 : 0)).toString() + .getBytes(StandardCharsets.UTF_8); + } + + public static byte[] extractJson(byte[] data, int realDataLength) { + return new byte[0]; + } + + public static void validate(byte[] data, int realDataLength) throws BadMessageException { + throw new UnsupportedOperationException("VoteChoice validation not implemented yet!"); + } +} diff --git a/source/src/main/java/ch/threema/apitool/types/FileRenderingType.java b/source/src/main/java/ch/threema/apitool/types/FileRenderingType.java new file mode 100644 index 0000000..3fdade9 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/types/FileRenderingType.java @@ -0,0 +1,63 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.types; + +import java.util.HashMap; +import java.util.Map; + +public enum FileRenderingType { + FILE(0), MEDIA(1), STICKER(2); + + private final int value; + private static final Map map = new HashMap<>(); + + FileRenderingType(int value) { + this.value = value; + } + + static { + for (FileRenderingType renderingType : FileRenderingType.values()) { + map.put(renderingType.value, renderingType); + } + } + + public static FileRenderingType valueOf(int renderingType) { + return map.get(renderingType); + } + + public int getValue() { + return value; + } +} diff --git a/source/src/main/java/ch/threema/apitool/types/GroupId.java b/source/src/main/java/ch/threema/apitool/types/GroupId.java new file mode 100644 index 0000000..bf6f570 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/types/GroupId.java @@ -0,0 +1,96 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.types; + +import ch.threema.apitool.utils.DataUtils; + +import java.nio.charset.StandardCharsets; + +/** + * Encapsulates the 8-byte message IDs that Threema uses. + */ +public class GroupId { + + public static final int GROUP_ID_LEN = 8; + public static final int CREATOR_ID_LEN = 8; + + private final byte[] groupCreator; + private final byte[] groupId; + + public GroupId(String groupId) { + this(groupId.getBytes(StandardCharsets.UTF_8), null); + } + + public GroupId(String groupId, String groupCreator) { + this(groupId.getBytes(StandardCharsets.UTF_8), + groupCreator.getBytes(StandardCharsets.UTF_8)); + } + + public GroupId(byte[] groupId) { + this(groupId, null); + } + + public GroupId(byte[] groupId, byte[] groupCreator) { + if (groupId == null || groupId.length > 0 && groupId.length != GROUP_ID_LEN) + throw new IllegalArgumentException("Bad group ID length"); + if (groupCreator != null && groupCreator.length > 0 + && groupCreator.length != CREATOR_ID_LEN) + throw new IllegalArgumentException("Bad creator ID length"); + + this.groupCreator = groupCreator; + this.groupId = groupId; + } + + public byte[] getGroupCreator() { + return groupCreator; + } + + public byte[] getGroupId() { + return groupId; + } + + public long toLong() { + var longVal = new byte[GROUP_ID_LEN + CREATOR_ID_LEN]; + System.arraycopy(groupCreator, 0, longVal, 0, CREATOR_ID_LEN); + System.arraycopy(groupId, 0, longVal, CREATOR_ID_LEN, GROUP_ID_LEN); + return DataUtils.byteArrayToLongBigEndian(longVal); + } + + @Override + public String toString() { + return "GroupId {\n groupCreator: " + new String(groupCreator) + "\n groupId: " + + new String(groupId) + "\n}"; + } +} diff --git a/source/src/main/java/ch/threema/apitool/types/Key.java b/source/src/main/java/ch/threema/apitool/types/Key.java new file mode 100644 index 0000000..333e292 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/types/Key.java @@ -0,0 +1,113 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.types; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.exceptions.InvalidKeyException; + +/** + * Encapsulates an asymmetric key, either public or private. + */ +public class Key { + public static final String separator = ":"; + + public static class KeyType { + public static final String PRIVATE = "private"; + public static final String PUBLIC = "public"; + } + + /* Attributes */ + public byte[] key; + public String type; + + public Key(String type, byte[] key) { + this.key = key; + this.type = type; + } + + /** + * Decodes and validates an encoded key. Encoded key format: type:hex_key + * + * @param encodedKey an encoded key + * @throws ch.threema.apitool.exceptions.InvalidKeyException + */ + public static Key decodeKey(String encodedKey) throws InvalidKeyException { + // Split key and check length + String[] keyArray = encodedKey.split(Key.separator); + if (keyArray.length != 2) { + throw new InvalidKeyException("Does not contain a valid key format"); + } + + // Unpack key + String keyType = keyArray[0]; + String keyContent = keyArray[1]; + + // Is this a valid hex key? + if (!keyContent.matches("[0-9a-fA-F]{64}")) { + throw new InvalidKeyException("Does not contain a valid key"); + } + + return new Key(keyType, DataUtils.hexStringToByteArray(keyContent)); + } + + /** + * Decodes and validates an encoded key. Encoded key format: type:hex_key + * + * @param encodedKey an encoded key + * @param expectedKeyType the expected type of the key + * @throws InvalidKeyException + */ + public static Key decodeKey(String encodedKey, String expectedKeyType) + throws InvalidKeyException { + Key key = decodeKey(encodedKey); + + // Check key type + if (!key.type.equals(expectedKeyType)) { + throw new InvalidKeyException( + "Expected key type: " + expectedKeyType + ", got: " + key.type); + } + + return key; + } + + /** + * Encodes a key. + * + * @return an encoded key + */ + public String encode() { + return this.type + Key.separator + DataUtils.byteArrayToHexString(this.key); + } +} diff --git a/source/src/main/java/ch/threema/apitool/MessageId.java b/source/src/main/java/ch/threema/apitool/types/MessageId.java similarity index 75% rename from source/src/main/java/ch/threema/apitool/MessageId.java rename to source/src/main/java/ch/threema/apitool/types/MessageId.java index 2c3d5b1..31f817c 100644 --- a/source/src/main/java/ch/threema/apitool/MessageId.java +++ b/source/src/main/java/ch/threema/apitool/types/MessageId.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,9 +26,15 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ -package ch.threema.apitool; +package ch.threema.apitool.types; + +import ch.threema.apitool.utils.DataUtils; /** * Encapsulates the 8-byte message IDs that Threema uses. @@ -40,6 +52,14 @@ public MessageId(byte[] messageId) { this.messageId = messageId; } + public MessageId(String messageId) { + byte[] data = DataUtils.hexStringToByteArray(messageId); + if (data.length != MESSAGE_ID_LEN) + throw new IllegalArgumentException("Bad message ID length"); + + this.messageId = data; + } + public MessageId(byte[] data, int offset) { if ((offset + MESSAGE_ID_LEN) > data.length) throw new IllegalArgumentException("Bad message ID buffer length"); diff --git a/source/src/main/java/ch/threema/apitool/types/QuotePart.java b/source/src/main/java/ch/threema/apitool/types/QuotePart.java new file mode 100644 index 0000000..2a640c9 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/types/QuotePart.java @@ -0,0 +1,5 @@ +package ch.threema.apitool.types; + +public enum QuotePart { + QUOTED_MESSAGE_ID, QUOTE_TEXT +} diff --git a/source/src/main/java/ch/threema/apitool/types/voting/BallotChoice.java b/source/src/main/java/ch/threema/apitool/types/voting/BallotChoice.java new file mode 100644 index 0000000..fd57aab --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/types/voting/BallotChoice.java @@ -0,0 +1,215 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.types.voting; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Objects; +import java.util.Arrays; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.exceptions.BadMessageException; + +import java.util.List; +import java.util.ArrayList; +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.voting.*; +import ch.threema.apitool.types.*; +import ch.threema.apitool.serializers.BallotChoiceSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A poll choice item that is part of a poll message. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.786299819+00:00") +public class BallotChoice { + + + private final static String KEY_IDENTIFIER = "i"; + private final static String KEY_NAME = "n"; + private final static String KEY_ORDER = "o"; + private final static String KEY_RESULT = "r"; + private final static String KEY_TOTAL_VOTES = "t"; + + private final Integer identifier; + private final String name; + private final int order; + private final List result; + private final Integer totalVotes; + + public BallotChoice(Integer identifier, String name, int order, List result, + Integer totalVotes) { + this.identifier = identifier; + this.name = name; + this.order = order; + this.result = result; + this.totalVotes = totalVotes; + } + + /** + * The poll choice identifier + * + * @return identifier + **/ + public Integer getIdentifier() { + return identifier; + } + + + /** + * The poll choice name + * + * @return name + **/ + public String getName() { + return name; + } + + + /** + * The poll choice order number + * + * @return order + * @deprecated + **/ + @Deprecated + public int getOrder() { + return order; + } + + + /** + * The poll vote result index array + * + * @return result + **/ + public List getResult() { + return result; + } + + + /** + * The total poll votes + * + * @return totalVotes + **/ + public Integer getTotalVotes() { + return totalVotes; + } + + + + public byte[] getData() throws BadMessageException { + JSONObject o = new JSONObject(); + try { + + o.put(KEY_IDENTIFIER, this.identifier); + o.put(KEY_NAME, this.name); + o.put(KEY_ORDER, this.order); + o.put(KEY_RESULT, this.result); + if (this.totalVotes != null) + o.put(KEY_TOTAL_VOTES, this.totalVotes); + } catch (Exception e) { + throw new BadMessageException(); + } + + return o.toString().getBytes(StandardCharsets.UTF_8); + + } + + public static BallotChoice fromString(byte[] data, int realDataLength) + throws BadMessageException { + BallotChoiceSerializer.validate(data, realDataLength); + var jsonData = BallotChoiceSerializer.extractJson(data, realDataLength); + try { + JSONObject o = new JSONObject(new String(jsonData)); + + Integer identifier = o.getInt(KEY_IDENTIFIER); + String name = o.getString(KEY_NAME); + int order = o.getInt(KEY_ORDER); + var resultJ = o.getJSONArray(KEY_RESULT); + var result = new ArrayList(); + if (resultJ != null) { + for (int i = 0; i < resultJ.length(); i++) { + result.add(resultJ.getInt(i)); + } + } + Integer totalVotes = o.optInt(KEY_TOTAL_VOTES, 0); + + return new BallotChoice(identifier, name, order, result, totalVotes); + } catch (JSONException e) { + throw new BadMessageException(); + } + + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return Objects.hash(identifier, name, order, Arrays.hashCode(result.toArray()), totalVotes); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class BallotChoice {\n"); + sb.append(" identifier: ").append(toIndentedString(getIdentifier())).append("\n"); + sb.append(" name: ").append(toIndentedString(getName())).append("\n"); + sb.append(" order: ").append(toIndentedString(getOrder())).append("\n"); + sb.append(" result: ").append(toIndentedString(getResult())).append("\n"); + sb.append(" totalVotes: ").append(toIndentedString(getTotalVotes())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/types/voting/DisplayMode.java b/source/src/main/java/ch/threema/apitool/types/voting/DisplayMode.java new file mode 100644 index 0000000..5cd0784 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/types/voting/DisplayMode.java @@ -0,0 +1,63 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.types.voting; + +import java.util.HashMap; +import java.util.Map; + +public enum DisplayMode { + LIST(0), SUMMARY(1); + + private final int value; + private static final Map map = new HashMap<>(); + + DisplayMode(int value) { + this.value = value; + } + + static { + for (DisplayMode displayMode : DisplayMode.values()) { + map.put(displayMode.value, displayMode); + } + } + + public static DisplayMode valueOf(int displayMode) { + return map.get(displayMode); + } + + public int getValue() { + return value; + } +} diff --git a/source/src/main/java/ch/threema/apitool/types/voting/ResultsDisclosureType.java b/source/src/main/java/ch/threema/apitool/types/voting/ResultsDisclosureType.java new file mode 100644 index 0000000..f1c91f5 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/types/voting/ResultsDisclosureType.java @@ -0,0 +1,63 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.types.voting; + +import java.util.HashMap; +import java.util.Map; + +public enum ResultsDisclosureType { + CLOSED(0), INTERMEDIATE(1); + + private final int value; + private static final Map map = new HashMap<>(); + + ResultsDisclosureType(int value) { + this.value = value; + } + + static { + for (ResultsDisclosureType disclosureType : ResultsDisclosureType.values()) { + map.put(disclosureType.value, disclosureType); + } + } + + public static ResultsDisclosureType valueOf(int disclosureType) { + return map.get(disclosureType); + } + + public int getValue() { + return value; + } +} diff --git a/source/src/main/java/ch/threema/apitool/types/voting/State.java b/source/src/main/java/ch/threema/apitool/types/voting/State.java new file mode 100644 index 0000000..be84e6c --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/types/voting/State.java @@ -0,0 +1,63 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.types.voting; + +import java.util.HashMap; +import java.util.Map; + +public enum State { + OPEN(0), CLOSED(1); + + private final int value; + private static final Map map = new HashMap<>(); + + State(int value) { + this.value = value; + } + + static { + for (State state : State.values()) { + map.put(state.value, state); + } + } + + public static State valueOf(int state) { + return map.get(state); + } + + public int getValue() { + return value; + } +} diff --git a/source/src/main/java/ch/threema/apitool/types/voting/VoteChoice.java b/source/src/main/java/ch/threema/apitool/types/voting/VoteChoice.java new file mode 100644 index 0000000..17a725d --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/types/voting/VoteChoice.java @@ -0,0 +1,128 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.types.voting; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Objects; +import java.util.Arrays; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.EndianUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONArray; + +import ch.threema.apitool.utils.DataUtils; +import ch.threema.apitool.exceptions.BadMessageException; + +import ch.threema.apitool.serializers.VoteChoiceSerializer; + +import static ch.threema.apitool.utils.StringUtils.toIndentedString; + +/** + * A vote choice item that is part of a vote message. + */ +@javax.annotation.Generated(value = "msgapi-sdk-codegen", + date = "2022-09-12T15:53:56.793026168+00:00") +public class VoteChoice { + + + + private final int ballotId; + private final boolean selected; + + public VoteChoice(int ballotId, boolean selected) { + this.ballotId = ballotId; + this.selected = selected; + } + + /** + * The ballot identifier + * + * @return ballotId + **/ + public int getBallotId() { + return ballotId; + } + + /** + * The vote choice name + * + * @return selected + **/ + public boolean getSelected() { + return selected; + } + + + + public byte[] getData() throws BadMessageException { + + return VoteChoiceSerializer.serialize(ballotId, selected); + } + + public static VoteChoice fromString(byte[] data, int realDataLength) + throws BadMessageException { + + throw new UnsupportedOperationException("VoteChoice cannot be received from a Gateway ID!"); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return Objects.hash(ballotId, selected); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class VoteChoice {\n"); + sb.append(" ballotId: ").append(toIndentedString(getBallotId())).append("\n"); + sb.append(" selected: ").append(toIndentedString(getSelected())).append("\n"); + sb.append("}"); + return sb.toString(); + } +} diff --git a/source/src/main/java/ch/threema/apitool/types/voting/VotingMode.java b/source/src/main/java/ch/threema/apitool/types/voting/VotingMode.java new file mode 100644 index 0000000..29511eb --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/types/voting/VotingMode.java @@ -0,0 +1,63 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.types.voting; + +import java.util.HashMap; +import java.util.Map; + +public enum VotingMode { + SINGLE_CHOICE(0), MULTIPLE_CHOICE(1); + + private final int value; + private static final Map map = new HashMap<>(); + + VotingMode(int value) { + this.value = value; + } + + static { + for (VotingMode votingMode : VotingMode.values()) { + map.put(votingMode.value, votingMode); + } + } + + public static VotingMode valueOf(int votingMode) { + return map.get(votingMode); + } + + public int getValue() { + return value; + } +} diff --git a/source/src/main/java/ch/threema/apitool/utils/ApiResponse.java b/source/src/main/java/ch/threema/apitool/utils/ApiResponse.java new file mode 100644 index 0000000..c8c1b9a --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/utils/ApiResponse.java @@ -0,0 +1,80 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.utils; + +import java.util.List; +import java.util.Map; + +/** + * API response returned by API call. + * + * @param The type of data that is deserialized from response body + */ +public class ApiResponse { + final private int statusCode; + final private Map> headers; + final private T data; + + /** + * @param statusCode The status code of HTTP response + * @param headers The headers of HTTP response + */ + public ApiResponse(int statusCode, Map> headers) { + this(statusCode, headers, null); + } + + /** + * @param statusCode The status code of HTTP response + * @param headers The headers of HTTP response + * @param data The object deserialized from response body + */ + public ApiResponse(int statusCode, Map> headers, T data) { + this.statusCode = statusCode; + this.headers = headers; + this.data = data; + } + + public int getStatusCode() { + return statusCode; + } + + public Map> getHeaders() { + return headers; + } + + public T getData() { + return data; + } +} diff --git a/source/src/main/java/ch/threema/apitool/DataUtils.java b/source/src/main/java/ch/threema/apitool/utils/DataUtils.java similarity index 64% rename from source/src/main/java/ch/threema/apitool/DataUtils.java rename to source/src/main/java/ch/threema/apitool/utils/DataUtils.java index f9ffa68..cfbdd20 100644 --- a/source/src/main/java/ch/threema/apitool/DataUtils.java +++ b/source/src/main/java/ch/threema/apitool/utils/DataUtils.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,29 +26,49 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ -package ch.threema.apitool; +package ch.threema.apitool.utils; +import ch.threema.apitool.exceptions.InvalidHexException; import ch.threema.apitool.exceptions.InvalidKeyException; +import ch.threema.apitool.types.Key; +import ch.threema.apitool.types.QuotePart; import java.io.*; +import java.util.regex.Pattern; public class DataUtils { + public static final String QUOTE_PATTERN = "^> quote #([0-9a-f]{16})(?:\\r?\\n){2}(.+)$"; + /** * Convert a string in hexadecimal representation to a byte array. + *

+ * Whitespace (RegEx \s) is stripped before decoding, but if other invalid characters are + * contained, an error is thrown. * * @param s hex string * @return decoded byte array + * @throws InvalidHexException if the string is not a valid hex string */ - public static byte[] hexStringToByteArray(String s) { - String sc = s.replaceAll("[^0-9a-fA-F]", ""); + public static byte[] hexStringToByteArray(String s) throws InvalidHexException { + String sc = s.replaceAll("\\s", ""); int len = sc.length(); + if (len % 2 != 0) { + throw new InvalidHexException("Hex string length is not divisible by 2"); + } + if (sc.matches(".*[^0-9a-fA-F].*")) { + throw new InvalidHexException("Hex string contains non-hex characters"); + } byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(sc.charAt(i), 16) << 4) - + Character.digit(sc.charAt(i+1), 16)); + + Character.digit(sc.charAt(i + 1), 16)); } return data; } @@ -51,10 +77,12 @@ public static byte[] hexStringToByteArray(String s) { * Convert a byte array into a hexadecimal string (lowercase). * * @param bytes the bytes to encode + * * @return hex encoded string */ public static String byteArrayToHexString(byte[] bytes) { - final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; + final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', + 'd', 'e', 'f'}; char[] hexChars = new char[bytes.length * 2]; int v; for (int j = 0; j < bytes.length; j++) { @@ -65,11 +93,31 @@ public static String byteArrayToHexString(byte[] bytes) { return new String(hexChars); } + public static byte[] longToByteArrayBigEndian(long value) { + byte[] result = new byte[8]; + for (int i = 7; i >= 0; i--) { + result[i] = (byte) (value & 0xFF); + value >>= 8; + } + return result; + } + + public static long byteArrayToLongBigEndian(final byte[] bytes) { + long result = 0; + for (int i = 0; i < 8; i++) { + result <<= 8; + result |= (bytes[i] & 0xFF); + } + return result; + } + /** * Read hexadecimal data from a file and return it as a byte array. * * @param inFile input file + * * @return the decoded data + * * @throws java.io.IOException */ public static byte[] readHexFile(File inFile) throws IOException { @@ -96,7 +144,9 @@ public static void writeHexFile(File outFile, byte[] data) throws IOException { * Read an encoded key from a file and return it as a key instance. * * @param inFile input file + * * @return the decoded key + * * @throws java.io.IOException */ public static Key readKeyFile(File inFile) throws IOException, InvalidKeyException { @@ -114,7 +164,8 @@ public static Key readKeyFile(File inFile) throws IOException, InvalidKeyExcepti * @return the decoded key * @throws java.io.IOException */ - public static Key readKeyFile(File inFile, String expectedKeyType) throws IOException, InvalidKeyException { + public static Key readKeyFile(File inFile, String expectedKeyType) + throws IOException, InvalidKeyException { BufferedReader br = new BufferedReader(new FileReader(inFile)); String encodedKey = br.readLine().trim(); br.close(); @@ -122,8 +173,7 @@ public static Key readKeyFile(File inFile, String expectedKeyType) throws IOExce } /** - * Write an encoded key to a file - * Encoded key format: type:hex_key. + * Write an encoded key to a file Encoded key format: type:hex_key. * * @param outFile output file * @param key a key that will be encoded and written to a file @@ -134,4 +184,15 @@ public static void writeKeyFile(File outFile, Key key) throws IOException { fw.write('\n'); fw.close(); } + + public static String extractQuote(String text, QuotePart part) { + var pattern = Pattern.compile(QUOTE_PATTERN, Pattern.DOTALL); + var matcher = pattern.matcher(text); + + if (matcher.matches()) { + return matcher.group(part.ordinal() + 1); + } + + return null; + } } diff --git a/source/src/main/java/ch/threema/apitool/utils/ProtocolConstants.java b/source/src/main/java/ch/threema/apitool/utils/ProtocolConstants.java new file mode 100644 index 0000000..aacab70 --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/utils/ProtocolConstants.java @@ -0,0 +1,68 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool.utils; + +public final class ProtocolConstants { + // Taken from Android repo's `ProtocolDefines.java` + + /* object lengths */ + public static final int PUSH_FROM_LEN = 32; + public static final int IDENTITY_LEN = 8; + public static final int MESSAGE_ID_LEN = 8; + public static final int BLOB_ID_LEN = 16; + public static final int BLOB_KEY_LEN = 32; + public static final int GROUP_ID_LEN = 8; + public static final int GROUP_INVITE_TOKEN_LEN = 16; + public static final int BALLOT_ID_LEN = 8; + public static final int GROUP_JOIN_MESSAGE_LEN = 100; + + /* max message size */ + public static final int MAX_PKT_LEN = 8192; + public static final int OVERHEAD_NACL_BOX = 16; // Excluding nonce + public static final int OVERHEAD_PKT_HDR = 4; + public static final int OVERHEAD_MSG_HDR = 88; + public static final int OVERHEAD_BOX_HDR = 1; + public static final int OVERHEAD_MAXPADDING = 255; + public static final int MAX_MESSAGE_LEN = MAX_PKT_LEN - OVERHEAD_NACL_BOX * 2 // Both + // app-to-server + // and + // end-to-end + - OVERHEAD_PKT_HDR - OVERHEAD_MSG_HDR - OVERHEAD_BOX_HDR - OVERHEAD_MAXPADDING; + public static final int MIN_MESSAGE_PADDED_LEN = 32; + + private ProtocolConstants() { + + } +} diff --git a/source/src/main/java/ch/threema/apitool/utils/StringUtils.java b/source/src/main/java/ch/threema/apitool/utils/StringUtils.java new file mode 100644 index 0000000..aa52dcd --- /dev/null +++ b/source/src/main/java/ch/threema/apitool/utils/StringUtils.java @@ -0,0 +1,15 @@ +package ch.threema.apitool.utils; + +public final class StringUtils { + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + public static String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +} diff --git a/source/src/main/java/com/neilalexander/jnacl/NaCl.java b/source/src/main/java/com/neilalexander/jnacl/NaCl.java index 5cddd5d..f671d33 100644 --- a/source/src/main/java/com/neilalexander/jnacl/NaCl.java +++ b/source/src/main/java/com/neilalexander/jnacl/NaCl.java @@ -1,28 +1,28 @@ // -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// Copyright (c) 2011, Neil Alexander T. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with +// or without modification, are permitted provided that the following +// conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. // package com.neilalexander.jnacl; @@ -31,300 +31,318 @@ import java.util.Arrays; import java.util.Formatter; -import com.neilalexander.jnacl.crypto.curve25519xsalsa20poly1305; -import com.neilalexander.jnacl.crypto.xsalsa20; -import com.neilalexander.jnacl.crypto.xsalsa20poly1305; +import com.neilalexander.jnacl.crypto.*; public class NaCl { - public static final int PUBLICKEYBYTES = 32; - public static final int SECRETKEYBYTES = 32; - public static final int BEFORENMBYTES = 32; - public static final int NONCEBYTES = 24; - public static final int ZEROBYTES = 32; - public static final int BOXZEROBYTES = 16; - public static final int BOXOVERHEAD = ZEROBYTES - BOXZEROBYTES; - public static final int SYMMKEYBYTES = 32; - public static final int STREAMKEYBYTES = 32; - - private final byte[] precomputed = new byte[BEFORENMBYTES]; - - /* Perform self test before anything else */ - static { - selfTest(); - } - - public NaCl(byte[] privatekey, byte[] publickey) { - if (privatekey.length != SECRETKEYBYTES) - throw new Error("Invalid private key length"); - - if (publickey.length != PUBLICKEYBYTES) - throw new Error("Invalid public key length"); - - curve25519xsalsa20poly1305.crypto_box_beforenm(this.precomputed, publickey, privatekey); - } - - public NaCl(String privatekey, String publickey) { - this(getBinary(privatekey), getBinary(publickey)); - } - - public byte[] encrypt(byte[] input, byte[] nonce) { - return encrypt(input, input.length, nonce); - } - - public byte[] encrypt(byte[] input, int inputlength, byte[] nonce) { - if (nonce.length != NONCEBYTES) - throw new Error("Invalid nonce length"); - - byte[] output = new byte[inputlength + BOXOVERHEAD]; - curve25519xsalsa20poly1305.crypto_box_afternm_nopad(output, 0, input, 0, input.length, nonce, this.precomputed); - - return output; - } + public static final int PUBLICKEYBYTES = 32; + public static final int SECRETKEYBYTES = 32; + public static final int BEFORENMBYTES = 32; + public static final int NONCEBYTES = 24; + public static final int ZEROBYTES = 32; + public static final int BOXZEROBYTES = 16; + public static final int BOXOVERHEAD = ZEROBYTES - BOXZEROBYTES; + public static final int SYMMKEYBYTES = 32; + public static final int STREAMKEYBYTES = 32; - public byte[] decrypt(byte[] input, byte[] nonce) { - return decrypt(input, input.length, nonce); - } + private final byte[] precomputed = new byte[BEFORENMBYTES]; - public byte[] decrypt(byte[] input, int inputlength, byte[] nonce) { - if (nonce.length != NONCEBYTES) - throw new Error("Invalid nonce length"); - - if (inputlength < BOXOVERHEAD) - return null; - - byte[] output = new byte[inputlength - BOXOVERHEAD]; - if (curve25519xsalsa20poly1305.crypto_box_open_afternm_nopad(output, 0, input, 0, input.length, nonce, - this.precomputed) != 0) - return null; - - return output; - } - - public static void genkeypair(byte[] publickey, byte[] privatekey) { - genkeypair(publickey, privatekey, null); - } - - public static void genkeypair(byte[] publickey, byte[] privatekey, byte[] seed) { - SecureRandom random = new SecureRandom(); + /* Perform self test before anything else */ + static { + selfTest(); + } + + public NaCl(byte[] privatekey, byte[] publickey) { + if (privatekey.length != SECRETKEYBYTES) + throw new Error("Invalid private key length"); + + if (publickey.length != PUBLICKEYBYTES) + throw new Error("Invalid public key length"); + + curve25519xsalsa20poly1305.crypto_box_beforenm(this.precomputed, publickey, privatekey); + } - random.nextBytes(privatekey); - - if (seed != null) { - if (seed.length != SECRETKEYBYTES) - throw new Error("Invalid seed length"); - - for (int i = 0; i < SECRETKEYBYTES; i++) - privatekey[i] ^= seed[i]; - } + public NaCl(String privatekey, String publickey) { + this(getBinary(privatekey), getBinary(publickey)); + } - curve25519xsalsa20poly1305.crypto_box_getpublickey(publickey, privatekey); - } + public byte[] encrypt(byte[] input, byte[] nonce) { + return encrypt(input, input.length, nonce); + } - public static byte[] derivePublicKey(byte[] privatekey) { - if (privatekey.length != SECRETKEYBYTES) - throw new Error("Invalid private key length"); + public byte[] encrypt(byte[] input, int inputlength, byte[] nonce) { + if (nonce.length != NONCEBYTES) + throw new Error("Invalid nonce length"); - byte[] publickey = new byte[PUBLICKEYBYTES]; - curve25519xsalsa20poly1305.crypto_box_getpublickey(publickey, privatekey); - return publickey; - } + byte[] output = new byte[inputlength + BOXOVERHEAD]; + curve25519xsalsa20poly1305.crypto_box_afternm_nopad(output, 0, input, 0, input.length, + nonce, this.precomputed); - public static byte[] symmetricEncryptData(byte[] input, byte[] key, byte[] nonce) { - if (key.length != SYMMKEYBYTES) - throw new Error("Invalid symmetric key length"); + return output; + } - if (nonce.length != NONCEBYTES) - throw new Error("Invalid nonce length"); - - byte[] output = new byte[input.length + BOXOVERHEAD]; - xsalsa20poly1305.crypto_secretbox_nopad(output, 0, input, 0, input.length, nonce, key); + public byte[] decrypt(byte[] input, byte[] nonce) { + return decrypt(input, input.length, nonce); + } - return output; - } + public byte[] decrypt(byte[] input, int inputlength, byte[] nonce) { + if (nonce.length != NONCEBYTES) + throw new Error("Invalid nonce length"); + + if (inputlength < BOXOVERHEAD) + return null; + + byte[] output = new byte[inputlength - BOXOVERHEAD]; + if (curve25519xsalsa20poly1305.crypto_box_open_afternm_nopad(output, 0, input, 0, + input.length, nonce, this.precomputed) != 0) + return null; + + return output; + } + + public static void genkeypair(byte[] publickey, byte[] privatekey) { + genkeypair(publickey, privatekey, null); + } + + public static void genkeypair(byte[] publickey, byte[] privatekey, byte[] seed) { + SecureRandom random = new SecureRandom(); + + random.nextBytes(privatekey); + + if (seed != null) { + if (seed.length != SECRETKEYBYTES) + throw new Error("Invalid seed length"); + + for (int i = 0; i < SECRETKEYBYTES; i++) + privatekey[i] ^= seed[i]; + } + + curve25519xsalsa20poly1305.crypto_box_getpublickey(publickey, privatekey); + } + + public static byte[] derivePublicKey(byte[] privatekey) { + if (privatekey.length != SECRETKEYBYTES) + throw new Error("Invalid private key length"); + + byte[] publickey = new byte[PUBLICKEYBYTES]; + curve25519xsalsa20poly1305.crypto_box_getpublickey(publickey, privatekey); + return publickey; + } + + public static byte[] symmetricEncryptData(byte[] input, byte[] key, byte[] nonce) { + if (key.length != SYMMKEYBYTES) + throw new Error("Invalid symmetric key length"); + + if (nonce.length != NONCEBYTES) + throw new Error("Invalid nonce length"); + + byte[] output = new byte[input.length + BOXOVERHEAD]; + xsalsa20poly1305.crypto_secretbox_nopad(output, 0, input, 0, input.length, nonce, key); + + return output; + } - /** - * In-place version of {@link #symmetricEncryptData(byte[], byte[], byte[])} that stores the output - * in the same byte array as the input. The input data must begin at offset {@link #BOXOVERHEAD} in - * the array (the first BOXOVERHEAD bytes are ignored and will be overwritten with the message - * authentication code during encryption). - * - * @param io plaintext on input (starting at offset BOXOVERHEAD), ciphertext on return (full array) - * @param key encryption key - * @param nonce encryption nonce - */ - public static void symmetricEncryptDataInplace(byte[] io, byte[] key, byte[] nonce) { - - if (key.length != SYMMKEYBYTES) - throw new Error("Invalid symmetric key length"); - - if (nonce.length != NONCEBYTES) - throw new Error("Invalid nonce length"); - - if (io.length < BOXOVERHEAD) - throw new Error("Invalid I/O length"); - - xsalsa20poly1305.crypto_secretbox_nopad(io, 0, io, BOXOVERHEAD, io.length - BOXOVERHEAD, nonce, key); - } - - public static byte[] symmetricDecryptData(byte[] input, byte[] key, byte[] nonce) { - if (key.length != SYMMKEYBYTES) - throw new Error("Invalid symmetric key length"); - - if (nonce.length != NONCEBYTES) - throw new Error("Invalid nonce length"); - - byte[] output = new byte[input.length - BOXOVERHEAD]; - if (xsalsa20poly1305.crypto_secretbox_open_nopad(output, 0, input, 0, input.length, nonce, key) != 0) - return null; - - return output; - } - - /** - * In-place version of {@link #symmetricDecryptData(byte[], byte[], byte[])} that stores the output in - * the same byte array as the input. Note that the decrypted output is shorter than the input, so the - * last {@link #BOXOVERHEAD} bytes should be ignored in the decrypted output. - * - * @param io ciphertext on input (full array), plaintext on output (last BOXOVERHEAD bytes set to zero) - * @param key encryption key - * @param nonce encryption nonce - * @return decryption successful true/false - */ - public static boolean symmetricDecryptDataInplace(byte[] io, byte[] key, byte[] nonce) { - if (key.length != SYMMKEYBYTES) - throw new Error("Invalid symmetric key length"); - - if (nonce.length != NONCEBYTES) - throw new Error("Invalid nonce length"); - - if (io.length < BOXOVERHEAD) - throw new Error("Invalid I/O length"); - - if (xsalsa20poly1305.crypto_secretbox_open_nopad(io, 0, io, 0, io.length, nonce, key) != 0) - return false; - - /* zeroize last bytes */ - for (int i = io.length - BOXOVERHEAD; i < io.length; i++) - io[i] = 0; - - return true; - } - - public static byte[] streamCryptData(byte[] input, byte[] key, byte[] nonce) { - if (key.length != STREAMKEYBYTES) - throw new Error("Invalid symmetric key length"); - - byte[] output = new byte[input.length]; - xsalsa20.crypto_stream_xor(output, input, input.length, nonce, key); - - return output; - } - - public static byte[] getBinary(String s) { - int len = s.length(); - byte[] data = new byte[len / 2]; - - for (int i = 0; i < len; i += 2) - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); - - return data; - } - - public static String asHex(byte[] buf) { - try (Formatter formatter = new Formatter()) { - for (byte b : buf) - formatter.format("%02x", b); - return formatter.toString(); - } - } - - public static String asHex(int[] buf) { - try (Formatter formatter = new Formatter()) { - for (int b : buf) - formatter.format("%02x", b); - return formatter.toString(); - } - } - - public static void selfTest() { - /* test vectors from tests/box.* in nacl distribution */ - byte alicepk[] = new byte[] { (byte) 0x85, (byte) 0x20, (byte) 0xf0, (byte) 0x09, (byte) 0x89, (byte) 0x30, - (byte) 0xa7, (byte) 0x54, (byte) 0x74, (byte) 0x8b, (byte) 0x7d, (byte) 0xdc, (byte) 0xb4, (byte) 0x3e, - (byte) 0xf7, (byte) 0x5a, (byte) 0x0d, (byte) 0xbf, (byte) 0x3a, (byte) 0x0d, (byte) 0x26, (byte) 0x38, - (byte) 0x1a, (byte) 0xf4, (byte) 0xeb, (byte) 0xa4, (byte) 0xa9, (byte) 0x8e, (byte) 0xaa, (byte) 0x9b, - (byte) 0x4e, (byte) 0x6a }; - - byte alicesk[] = { (byte) 0x77, (byte) 0x07, (byte) 0x6d, (byte) 0x0a, (byte) 0x73, (byte) 0x18, (byte) 0xa5, - (byte) 0x7d, (byte) 0x3c, (byte) 0x16, (byte) 0xc1, (byte) 0x72, (byte) 0x51, (byte) 0xb2, (byte) 0x66, - (byte) 0x45, (byte) 0xdf, (byte) 0x4c, (byte) 0x2f, (byte) 0x87, (byte) 0xeb, (byte) 0xc0, (byte) 0x99, - (byte) 0x2a, (byte) 0xb1, (byte) 0x77, (byte) 0xfb, (byte) 0xa5, (byte) 0x1d, (byte) 0xb9, (byte) 0x2c, - (byte) 0x2a }; - - byte bobpk[] = { (byte) 0xde, (byte) 0x9e, (byte) 0xdb, (byte) 0x7d, (byte) 0x7b, (byte) 0x7d, (byte) 0xc1, - (byte) 0xb4, (byte) 0xd3, (byte) 0x5b, (byte) 0x61, (byte) 0xc2, (byte) 0xec, (byte) 0xe4, (byte) 0x35, - (byte) 0x37, (byte) 0x3f, (byte) 0x83, (byte) 0x43, (byte) 0xc8, (byte) 0x5b, (byte) 0x78, (byte) 0x67, - (byte) 0x4d, (byte) 0xad, (byte) 0xfc, (byte) 0x7e, (byte) 0x14, (byte) 0x6f, (byte) 0x88, (byte) 0x2b, - (byte) 0x4f }; - - byte bobsk[] = { (byte) 0x5d, (byte) 0xab, (byte) 0x08, (byte) 0x7e, (byte) 0x62, (byte) 0x4a, (byte) 0x8a, - (byte) 0x4b, (byte) 0x79, (byte) 0xe1, (byte) 0x7f, (byte) 0x8b, (byte) 0x83, (byte) 0x80, (byte) 0x0e, - (byte) 0xe6, (byte) 0x6f, (byte) 0x3b, (byte) 0xb1, (byte) 0x29, (byte) 0x26, (byte) 0x18, (byte) 0xb6, - (byte) 0xfd, (byte) 0x1c, (byte) 0x2f, (byte) 0x8b, (byte) 0x27, (byte) 0xff, (byte) 0x88, (byte) 0xe0, - (byte) 0xeb }; - - byte nonce[] = { (byte) 0x69, (byte) 0x69, (byte) 0x6e, (byte) 0xe9, (byte) 0x55, (byte) 0xb6, (byte) 0x2b, - (byte) 0x73, (byte) 0xcd, (byte) 0x62, (byte) 0xbd, (byte) 0xa8, (byte) 0x75, (byte) 0xfc, (byte) 0x73, - (byte) 0xd6, (byte) 0x82, (byte) 0x19, (byte) 0xe0, (byte) 0x03, (byte) 0x6b, (byte) 0x7a, (byte) 0x0b, - (byte) 0x37 }; - - byte m[] = { (byte) 0xbe, (byte) 0x07, (byte) 0x5f, (byte) 0xc5, (byte) 0x3c, (byte) 0x81, (byte) 0xf2, - (byte) 0xd5, (byte) 0xcf, (byte) 0x14, (byte) 0x13, (byte) 0x16, (byte) 0xeb, (byte) 0xeb, (byte) 0x0c, - (byte) 0x7b, (byte) 0x52, (byte) 0x28, (byte) 0xc5, (byte) 0x2a, (byte) 0x4c, (byte) 0x62, (byte) 0xcb, - (byte) 0xd4, (byte) 0x4b, (byte) 0x66, (byte) 0x84, (byte) 0x9b, (byte) 0x64, (byte) 0x24, (byte) 0x4f, - (byte) 0xfc, (byte) 0xe5, (byte) 0xec, (byte) 0xba, (byte) 0xaf, (byte) 0x33, (byte) 0xbd, (byte) 0x75, - (byte) 0x1a, (byte) 0x1a, (byte) 0xc7, (byte) 0x28, (byte) 0xd4, (byte) 0x5e, (byte) 0x6c, (byte) 0x61, - (byte) 0x29, (byte) 0x6c, (byte) 0xdc, (byte) 0x3c, (byte) 0x01, (byte) 0x23, (byte) 0x35, (byte) 0x61, - (byte) 0xf4, (byte) 0x1d, (byte) 0xb6, (byte) 0x6c, (byte) 0xce, (byte) 0x31, (byte) 0x4a, (byte) 0xdb, - (byte) 0x31, (byte) 0x0e, (byte) 0x3b, (byte) 0xe8, (byte) 0x25, (byte) 0x0c, (byte) 0x46, (byte) 0xf0, - (byte) 0x6d, (byte) 0xce, (byte) 0xea, (byte) 0x3a, (byte) 0x7f, (byte) 0xa1, (byte) 0x34, (byte) 0x80, - (byte) 0x57, (byte) 0xe2, (byte) 0xf6, (byte) 0x55, (byte) 0x6a, (byte) 0xd6, (byte) 0xb1, (byte) 0x31, - (byte) 0x8a, (byte) 0x02, (byte) 0x4a, (byte) 0x83, (byte) 0x8f, (byte) 0x21, (byte) 0xaf, (byte) 0x1f, - (byte) 0xde, (byte) 0x04, (byte) 0x89, (byte) 0x77, (byte) 0xeb, (byte) 0x48, (byte) 0xf5, (byte) 0x9f, - (byte) 0xfd, (byte) 0x49, (byte) 0x24, (byte) 0xca, (byte) 0x1c, (byte) 0x60, (byte) 0x90, (byte) 0x2e, - (byte) 0x52, (byte) 0xf0, (byte) 0xa0, (byte) 0x89, (byte) 0xbc, (byte) 0x76, (byte) 0x89, (byte) 0x70, - (byte) 0x40, (byte) 0xe0, (byte) 0x82, (byte) 0xf9, (byte) 0x37, (byte) 0x76, (byte) 0x38, (byte) 0x48, - (byte) 0x64, (byte) 0x5e, (byte) 0x07, (byte) 0x05 }; - - byte c_expected[] = { (byte) 0xf3, (byte) 0xff, (byte) 0xc7, (byte) 0x70, (byte) 0x3f, (byte) 0x94, (byte) 0x00, - (byte) 0xe5, (byte) 0x2a, (byte) 0x7d, (byte) 0xfb, (byte) 0x4b, (byte) 0x3d, (byte) 0x33, (byte) 0x05, - (byte) 0xd9, (byte) 0x8e, (byte) 0x99, (byte) 0x3b, (byte) 0x9f, (byte) 0x48, (byte) 0x68, (byte) 0x12, - (byte) 0x73, (byte) 0xc2, (byte) 0x96, (byte) 0x50, (byte) 0xba, (byte) 0x32, (byte) 0xfc, (byte) 0x76, - (byte) 0xce, (byte) 0x48, (byte) 0x33, (byte) 0x2e, (byte) 0xa7, (byte) 0x16, (byte) 0x4d, (byte) 0x96, - (byte) 0xa4, (byte) 0x47, (byte) 0x6f, (byte) 0xb8, (byte) 0xc5, (byte) 0x31, (byte) 0xa1, (byte) 0x18, - (byte) 0x6a, (byte) 0xc0, (byte) 0xdf, (byte) 0xc1, (byte) 0x7c, (byte) 0x98, (byte) 0xdc, (byte) 0xe8, - (byte) 0x7b, (byte) 0x4d, (byte) 0xa7, (byte) 0xf0, (byte) 0x11, (byte) 0xec, (byte) 0x48, (byte) 0xc9, - (byte) 0x72, (byte) 0x71, (byte) 0xd2, (byte) 0xc2, (byte) 0x0f, (byte) 0x9b, (byte) 0x92, (byte) 0x8f, - (byte) 0xe2, (byte) 0x27, (byte) 0x0d, (byte) 0x6f, (byte) 0xb8, (byte) 0x63, (byte) 0xd5, (byte) 0x17, - (byte) 0x38, (byte) 0xb4, (byte) 0x8e, (byte) 0xee, (byte) 0xe3, (byte) 0x14, (byte) 0xa7, (byte) 0xcc, - (byte) 0x8a, (byte) 0xb9, (byte) 0x32, (byte) 0x16, (byte) 0x45, (byte) 0x48, (byte) 0xe5, (byte) 0x26, - (byte) 0xae, (byte) 0x90, (byte) 0x22, (byte) 0x43, (byte) 0x68, (byte) 0x51, (byte) 0x7a, (byte) 0xcf, - (byte) 0xea, (byte) 0xbd, (byte) 0x6b, (byte) 0xb3, (byte) 0x73, (byte) 0x2b, (byte) 0xc0, (byte) 0xe9, - (byte) 0xda, (byte) 0x99, (byte) 0x83, (byte) 0x2b, (byte) 0x61, (byte) 0xca, (byte) 0x01, (byte) 0xb6, - (byte) 0xde, (byte) 0x56, (byte) 0x24, (byte) 0x4a, (byte) 0x9e, (byte) 0x88, (byte) 0xd5, (byte) 0xf9, - (byte) 0xb3, (byte) 0x79, (byte) 0x73, (byte) 0xf6, (byte) 0x22, (byte) 0xa4, (byte) 0x3d, (byte) 0x14, - (byte) 0xa6, (byte) 0x59, (byte) 0x9b, (byte) 0x1f, (byte) 0x65, (byte) 0x4c, (byte) 0xb4, (byte) 0x5a, - (byte) 0x74, (byte) 0xe3, (byte) 0x55, (byte) 0xa5 }; - - /* encrypt data and compare with expected result */ - NaCl nacl = new NaCl(alicesk, bobpk); - byte[] c = nacl.encrypt(m, nonce); - if (!Arrays.equals(c, c_expected)) - throw new RuntimeException("Crypto self-test failed (1)"); - - /* decrypt data and compare with plaintext */ - nacl = new NaCl(bobsk, alicepk); - byte[] p_d = nacl.decrypt(c, nonce); - if (!Arrays.equals(p_d, m)) - throw new RuntimeException("Crypto self-test failed (2)"); - } + /** + * In-place version of {@link #symmetricEncryptData(byte[], byte[], byte[])} that stores the + * output in the same byte array as the input. The input data must begin at offset + * {@link #BOXOVERHEAD} in the array (the first BOXOVERHEAD bytes are ignored and will be + * overwritten with the message authentication code during encryption). + * + * @param io plaintext on input (starting at offset BOXOVERHEAD), ciphertext on return (full + * array) + * @param key encryption key + * @param nonce encryption nonce + */ + public static void symmetricEncryptDataInplace(byte[] io, byte[] key, byte[] nonce) { + + if (key.length != SYMMKEYBYTES) + throw new Error("Invalid symmetric key length"); + + if (nonce.length != NONCEBYTES) + throw new Error("Invalid nonce length"); + + if (io.length < BOXOVERHEAD) + throw new Error("Invalid I/O length"); + + xsalsa20poly1305.crypto_secretbox_nopad(io, 0, io, BOXOVERHEAD, io.length - BOXOVERHEAD, + nonce, key); + } + + public static byte[] symmetricDecryptData(byte[] input, byte[] key, byte[] nonce) { + if (key.length != SYMMKEYBYTES) + throw new Error("Invalid symmetric key length"); + + if (nonce.length != NONCEBYTES) + throw new Error("Invalid nonce length"); + + byte[] output = new byte[input.length - BOXOVERHEAD]; + if (xsalsa20poly1305.crypto_secretbox_open_nopad(output, 0, input, 0, input.length, nonce, + key) != 0) + return null; + + return output; + } + + /** + * In-place version of {@link #symmetricDecryptData(byte[], byte[], byte[])} that stores the + * output in the same byte array as the input. Note that the decrypted output is shorter than + * the input, so the last {@link #BOXOVERHEAD} bytes should be ignored in the decrypted output. + * + * @param io ciphertext on input (full array), plaintext on output (last BOXOVERHEAD bytes set + * to zero) + * @param key encryption key + * @param nonce encryption nonce + * @return decryption successful true/false + */ + public static boolean symmetricDecryptDataInplace(byte[] io, byte[] key, byte[] nonce) { + if (key.length != SYMMKEYBYTES) + throw new Error("Invalid symmetric key length"); + + if (nonce.length != NONCEBYTES) + throw new Error("Invalid nonce length"); + + if (io.length < BOXOVERHEAD) + throw new Error("Invalid I/O length"); + + if (xsalsa20poly1305.crypto_secretbox_open_nopad(io, 0, io, 0, io.length, nonce, key) != 0) + return false; + + /* zeroize last bytes */ + for (int i = io.length - BOXOVERHEAD; i < io.length; i++) + io[i] = 0; + + return true; + } + + public static byte[] streamCryptData(byte[] input, byte[] key, byte[] nonce) { + if (key.length != STREAMKEYBYTES) + throw new Error("Invalid symmetric key length"); + + byte[] output = new byte[input.length]; + xsalsa20.crypto_stream_xor(output, input, input.length, nonce, key); + + return output; + } + + public static byte[] getBinary(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + + for (int i = 0; i < len; i += 2) + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i + 1), 16)); + + return data; + } + + public static String asHex(byte[] buf) { + Formatter formatter = new Formatter(); + for (byte b : buf) + formatter.format("%02x", b); + return formatter.toString(); + } + + public static String asHex(int[] buf) { + Formatter formatter = new Formatter(); + for (int b : buf) + formatter.format("%02x", b); + return formatter.toString(); + } + + public static void selfTest() { + /* test vectors from tests/box.* in nacl distribution */ + byte alicepk[] = new byte[] {(byte) 0x85, (byte) 0x20, (byte) 0xf0, (byte) 0x09, + (byte) 0x89, (byte) 0x30, (byte) 0xa7, (byte) 0x54, (byte) 0x74, (byte) 0x8b, + (byte) 0x7d, (byte) 0xdc, (byte) 0xb4, (byte) 0x3e, (byte) 0xf7, (byte) 0x5a, + (byte) 0x0d, (byte) 0xbf, (byte) 0x3a, (byte) 0x0d, (byte) 0x26, (byte) 0x38, + (byte) 0x1a, (byte) 0xf4, (byte) 0xeb, (byte) 0xa4, (byte) 0xa9, (byte) 0x8e, + (byte) 0xaa, (byte) 0x9b, (byte) 0x4e, (byte) 0x6a}; + + byte alicesk[] = {(byte) 0x77, (byte) 0x07, (byte) 0x6d, (byte) 0x0a, (byte) 0x73, + (byte) 0x18, (byte) 0xa5, (byte) 0x7d, (byte) 0x3c, (byte) 0x16, (byte) 0xc1, + (byte) 0x72, (byte) 0x51, (byte) 0xb2, (byte) 0x66, (byte) 0x45, (byte) 0xdf, + (byte) 0x4c, (byte) 0x2f, (byte) 0x87, (byte) 0xeb, (byte) 0xc0, (byte) 0x99, + (byte) 0x2a, (byte) 0xb1, (byte) 0x77, (byte) 0xfb, (byte) 0xa5, (byte) 0x1d, + (byte) 0xb9, (byte) 0x2c, (byte) 0x2a}; + + byte bobpk[] = {(byte) 0xde, (byte) 0x9e, (byte) 0xdb, (byte) 0x7d, (byte) 0x7b, + (byte) 0x7d, (byte) 0xc1, (byte) 0xb4, (byte) 0xd3, (byte) 0x5b, (byte) 0x61, + (byte) 0xc2, (byte) 0xec, (byte) 0xe4, (byte) 0x35, (byte) 0x37, (byte) 0x3f, + (byte) 0x83, (byte) 0x43, (byte) 0xc8, (byte) 0x5b, (byte) 0x78, (byte) 0x67, + (byte) 0x4d, (byte) 0xad, (byte) 0xfc, (byte) 0x7e, (byte) 0x14, (byte) 0x6f, + (byte) 0x88, (byte) 0x2b, (byte) 0x4f}; + + byte bobsk[] = {(byte) 0x5d, (byte) 0xab, (byte) 0x08, (byte) 0x7e, (byte) 0x62, + (byte) 0x4a, (byte) 0x8a, (byte) 0x4b, (byte) 0x79, (byte) 0xe1, (byte) 0x7f, + (byte) 0x8b, (byte) 0x83, (byte) 0x80, (byte) 0x0e, (byte) 0xe6, (byte) 0x6f, + (byte) 0x3b, (byte) 0xb1, (byte) 0x29, (byte) 0x26, (byte) 0x18, (byte) 0xb6, + (byte) 0xfd, (byte) 0x1c, (byte) 0x2f, (byte) 0x8b, (byte) 0x27, (byte) 0xff, + (byte) 0x88, (byte) 0xe0, (byte) 0xeb}; + + byte nonce[] = {(byte) 0x69, (byte) 0x69, (byte) 0x6e, (byte) 0xe9, (byte) 0x55, + (byte) 0xb6, (byte) 0x2b, (byte) 0x73, (byte) 0xcd, (byte) 0x62, (byte) 0xbd, + (byte) 0xa8, (byte) 0x75, (byte) 0xfc, (byte) 0x73, (byte) 0xd6, (byte) 0x82, + (byte) 0x19, (byte) 0xe0, (byte) 0x03, (byte) 0x6b, (byte) 0x7a, (byte) 0x0b, + (byte) 0x37}; + + byte m[] = {(byte) 0xbe, (byte) 0x07, (byte) 0x5f, (byte) 0xc5, (byte) 0x3c, (byte) 0x81, + (byte) 0xf2, (byte) 0xd5, (byte) 0xcf, (byte) 0x14, (byte) 0x13, (byte) 0x16, + (byte) 0xeb, (byte) 0xeb, (byte) 0x0c, (byte) 0x7b, (byte) 0x52, (byte) 0x28, + (byte) 0xc5, (byte) 0x2a, (byte) 0x4c, (byte) 0x62, (byte) 0xcb, (byte) 0xd4, + (byte) 0x4b, (byte) 0x66, (byte) 0x84, (byte) 0x9b, (byte) 0x64, (byte) 0x24, + (byte) 0x4f, (byte) 0xfc, (byte) 0xe5, (byte) 0xec, (byte) 0xba, (byte) 0xaf, + (byte) 0x33, (byte) 0xbd, (byte) 0x75, (byte) 0x1a, (byte) 0x1a, (byte) 0xc7, + (byte) 0x28, (byte) 0xd4, (byte) 0x5e, (byte) 0x6c, (byte) 0x61, (byte) 0x29, + (byte) 0x6c, (byte) 0xdc, (byte) 0x3c, (byte) 0x01, (byte) 0x23, (byte) 0x35, + (byte) 0x61, (byte) 0xf4, (byte) 0x1d, (byte) 0xb6, (byte) 0x6c, (byte) 0xce, + (byte) 0x31, (byte) 0x4a, (byte) 0xdb, (byte) 0x31, (byte) 0x0e, (byte) 0x3b, + (byte) 0xe8, (byte) 0x25, (byte) 0x0c, (byte) 0x46, (byte) 0xf0, (byte) 0x6d, + (byte) 0xce, (byte) 0xea, (byte) 0x3a, (byte) 0x7f, (byte) 0xa1, (byte) 0x34, + (byte) 0x80, (byte) 0x57, (byte) 0xe2, (byte) 0xf6, (byte) 0x55, (byte) 0x6a, + (byte) 0xd6, (byte) 0xb1, (byte) 0x31, (byte) 0x8a, (byte) 0x02, (byte) 0x4a, + (byte) 0x83, (byte) 0x8f, (byte) 0x21, (byte) 0xaf, (byte) 0x1f, (byte) 0xde, + (byte) 0x04, (byte) 0x89, (byte) 0x77, (byte) 0xeb, (byte) 0x48, (byte) 0xf5, + (byte) 0x9f, (byte) 0xfd, (byte) 0x49, (byte) 0x24, (byte) 0xca, (byte) 0x1c, + (byte) 0x60, (byte) 0x90, (byte) 0x2e, (byte) 0x52, (byte) 0xf0, (byte) 0xa0, + (byte) 0x89, (byte) 0xbc, (byte) 0x76, (byte) 0x89, (byte) 0x70, (byte) 0x40, + (byte) 0xe0, (byte) 0x82, (byte) 0xf9, (byte) 0x37, (byte) 0x76, (byte) 0x38, + (byte) 0x48, (byte) 0x64, (byte) 0x5e, (byte) 0x07, (byte) 0x05}; + + byte c_expected[] = {(byte) 0xf3, (byte) 0xff, (byte) 0xc7, (byte) 0x70, (byte) 0x3f, + (byte) 0x94, (byte) 0x00, (byte) 0xe5, (byte) 0x2a, (byte) 0x7d, (byte) 0xfb, + (byte) 0x4b, (byte) 0x3d, (byte) 0x33, (byte) 0x05, (byte) 0xd9, (byte) 0x8e, + (byte) 0x99, (byte) 0x3b, (byte) 0x9f, (byte) 0x48, (byte) 0x68, (byte) 0x12, + (byte) 0x73, (byte) 0xc2, (byte) 0x96, (byte) 0x50, (byte) 0xba, (byte) 0x32, + (byte) 0xfc, (byte) 0x76, (byte) 0xce, (byte) 0x48, (byte) 0x33, (byte) 0x2e, + (byte) 0xa7, (byte) 0x16, (byte) 0x4d, (byte) 0x96, (byte) 0xa4, (byte) 0x47, + (byte) 0x6f, (byte) 0xb8, (byte) 0xc5, (byte) 0x31, (byte) 0xa1, (byte) 0x18, + (byte) 0x6a, (byte) 0xc0, (byte) 0xdf, (byte) 0xc1, (byte) 0x7c, (byte) 0x98, + (byte) 0xdc, (byte) 0xe8, (byte) 0x7b, (byte) 0x4d, (byte) 0xa7, (byte) 0xf0, + (byte) 0x11, (byte) 0xec, (byte) 0x48, (byte) 0xc9, (byte) 0x72, (byte) 0x71, + (byte) 0xd2, (byte) 0xc2, (byte) 0x0f, (byte) 0x9b, (byte) 0x92, (byte) 0x8f, + (byte) 0xe2, (byte) 0x27, (byte) 0x0d, (byte) 0x6f, (byte) 0xb8, (byte) 0x63, + (byte) 0xd5, (byte) 0x17, (byte) 0x38, (byte) 0xb4, (byte) 0x8e, (byte) 0xee, + (byte) 0xe3, (byte) 0x14, (byte) 0xa7, (byte) 0xcc, (byte) 0x8a, (byte) 0xb9, + (byte) 0x32, (byte) 0x16, (byte) 0x45, (byte) 0x48, (byte) 0xe5, (byte) 0x26, + (byte) 0xae, (byte) 0x90, (byte) 0x22, (byte) 0x43, (byte) 0x68, (byte) 0x51, + (byte) 0x7a, (byte) 0xcf, (byte) 0xea, (byte) 0xbd, (byte) 0x6b, (byte) 0xb3, + (byte) 0x73, (byte) 0x2b, (byte) 0xc0, (byte) 0xe9, (byte) 0xda, (byte) 0x99, + (byte) 0x83, (byte) 0x2b, (byte) 0x61, (byte) 0xca, (byte) 0x01, (byte) 0xb6, + (byte) 0xde, (byte) 0x56, (byte) 0x24, (byte) 0x4a, (byte) 0x9e, (byte) 0x88, + (byte) 0xd5, (byte) 0xf9, (byte) 0xb3, (byte) 0x79, (byte) 0x73, (byte) 0xf6, + (byte) 0x22, (byte) 0xa4, (byte) 0x3d, (byte) 0x14, (byte) 0xa6, (byte) 0x59, + (byte) 0x9b, (byte) 0x1f, (byte) 0x65, (byte) 0x4c, (byte) 0xb4, (byte) 0x5a, + (byte) 0x74, (byte) 0xe3, (byte) 0x55, (byte) 0xa5}; + + /* encrypt data and compare with expected result */ + NaCl nacl = new NaCl(alicesk, bobpk); + byte[] c = nacl.encrypt(m, nonce); + if (!Arrays.equals(c, c_expected)) + throw new RuntimeException("Crypto self-test failed (1)"); + + /* decrypt data and compare with plaintext */ + nacl = new NaCl(bobsk, alicepk); + byte[] p_d = nacl.decrypt(c, nonce); + if (!Arrays.equals(p_d, m)) + throw new RuntimeException("Crypto self-test failed (2)"); + } } diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519.java b/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519.java index 3079e04..298e344 100644 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519.java +++ b/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519.java @@ -1,208 +1,189 @@ // -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// Copyright (c) 2011, Neil Alexander T. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with +// or without modification, are permitted provided that the following +// conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. // package com.neilalexander.jnacl.crypto; -public class curve25519 -{ +public class curve25519 { final int CRYPTO_BYTES = 32; final int CRYPTO_SCALARBYTES = 32; - - static byte[] basev = { 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - static int[] minusp = { 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128 }; - public static int crypto_scalarmult_base(byte[] q, byte[] n) - { + static byte[] basev = {9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + static int[] minusp = {19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 128}; + + public static int crypto_scalarmult_base(byte[] q, byte[] n) { byte[] basevp = basev; return crypto_scalarmult(q, n, basevp); } - - static void add(int[] outv, int outvoffset, int[] a, int aoffset, int[] b, int boffset) - { + + static void add(int[] outv, int outvoffset, int[] a, int aoffset, int[] b, int boffset) { int u = 0; - - for (int j = 0; j < 31; ++j) - { + + for (int j = 0; j < 31; ++j) { u += a[aoffset + j] + b[boffset + j]; outv[outvoffset + j] = u & 255; u >>>= 8; } - + u += a[aoffset + 31] + b[boffset + 31]; outv[outvoffset + 31] = u; } - static void sub(int[] outv, int outvoffset, int[] a, int aoffset, int[] b, int boffset) - { + static void sub(int[] outv, int outvoffset, int[] a, int aoffset, int[] b, int boffset) { int u = 218; - - for (int j = 0; j < 31; ++j) - { + + for (int j = 0; j < 31; ++j) { u += a[aoffset + j] + 65280 - b[boffset + j]; outv[outvoffset + j] = u & 255; u >>>= 8; } - + u += a[aoffset + 31] - b[boffset + 31]; outv[outvoffset + 31] = u; } - static void squeeze(int[] a, int aoffset) - { + static void squeeze(int[] a, int aoffset) { int u = 0; - - for (int j = 0; j < 31; ++j) - { + + for (int j = 0; j < 31; ++j) { u += a[aoffset + j]; a[aoffset + j] = u & 255; u >>>= 8; } - + u += a[aoffset + 31]; a[aoffset + 31] = u & 127; u = 19 * (u >>> 7); - - for (int j = 0; j < 31; ++j) - { + + for (int j = 0; j < 31; ++j) { u += a[aoffset + j]; a[aoffset + j] = u & 255; u >>>= 8; } - + u += a[aoffset + 31]; a[aoffset + 31] = u; } - static void freeze(int[] a, int aoffset) - { + static void freeze(int[] a, int aoffset) { int[] aorig = new int[32]; - + for (int j = 0; j < 32; ++j) aorig[j] = a[aoffset + j]; - + int[] minuspp = minusp; - + add(a, 0, a, 0, minuspp, 0); - + int negative = (int) (-((a[aoffset + 31] >>> 7) & 1)); - + for (int j = 0; j < 32; ++j) a[aoffset + j] ^= negative & (aorig[j] ^ a[aoffset + j]); } - static void mult(int[] outv, int outvoffset, int[] a, int aoffset, int[] b, int boffset) - { + static void mult(int[] outv, int outvoffset, int[] a, int aoffset, int[] b, int boffset) { int j; - - for (int i = 0; i < 32; ++i) - { + + for (int i = 0; i < 32; ++i) { int u = 0; - + for (j = 0; j <= i; ++j) u += a[aoffset + j] * b[boffset + i - j]; - + for (j = i + 1; j < 32; ++j) u += 38 * a[aoffset + j] * b[boffset + i + 32 - j]; - + outv[outvoffset + i] = u; } - + squeeze(outv, outvoffset); } - static void mult121665(int[] outv, int[] a) - { + static void mult121665(int[] outv, int[] a) { int j; int u = 0; - - for (j = 0; j < 31; ++j) - { + + for (j = 0; j < 31; ++j) { u += 121665 * a[j]; outv[j] = u & 255; u >>>= 8; } - + u += 121665 * a[31]; outv[31] = u & 127; u = 19 * (u >>> 7); - - for (j = 0; j < 31; ++j) - { + + for (j = 0; j < 31; ++j) { u += outv[j]; outv[j] = u & 255; u >>>= 8; } - + u += outv[j]; outv[j] = u; } - - static void square(int[] outv, int outvoffset, int[] a, int aoffset) - { + + static void square(int[] outv, int outvoffset, int[] a, int aoffset) { int j; - - for (int i = 0; i < 32; ++i) - { + + for (int i = 0; i < 32; ++i) { int u = 0; - + for (j = 0; j < i - j; ++j) u += a[aoffset + j] * a[aoffset + i - j]; - + for (j = i + 1; j < i + 32 - j; ++j) u += 38 * a[aoffset + j] * a[aoffset + i + 32 - j]; - + u *= 2; - - if ((i & 1) == 0) - { + + if ((i & 1) == 0) { u += a[aoffset + i / 2] * a[aoffset + i / 2]; u += 38 * a[aoffset + i / 2 + 16] * a[aoffset + i / 2 + 16]; } - + outv[outvoffset + i] = u; } - + squeeze(outv, outvoffset); } - static void select(int[] p, int[] q, int[] r, int[] s, int b) - { + static void select(int[] p, int[] q, int[] r, int[] s, int b) { int bminus1 = b - 1; - - for (int j = 0; j < 64; ++j) - { + + for (int j = 0; j < 64; ++j) { int t = bminus1 & (r[j] ^ s[j]); p[j] = s[j] ^ t; q[j] = r[j] ^ t; } } - static void mainloop(int[] work, byte[] e) - { + static void mainloop(int[] work, byte[] e) { int[] xzm1 = new int[64]; int[] xzm = new int[64]; int[] xzmb = new int[64]; @@ -221,14 +202,14 @@ static void mainloop(int[] work, byte[] e) for (int j = 0; j < 32; ++j) xzm1[j] = work[j]; - + xzm1[32] = 1; - + for (int j = 33; j < 64; ++j) xzm1[j] = 0; xzm[0] = 1; - + for (int j = 1; j < 64; ++j) xzm[j] = 0; @@ -237,29 +218,28 @@ static void mainloop(int[] work, byte[] e) int[] xznbp = xznb, up = u, xzn1bp = xzn1b; int[] workp = work, sp = s, rp = r; - for (int pos = 254; pos >= 0; --pos) - { + for (int pos = 254; pos >= 0; --pos) { int b = ((int) ((e[pos / 8] & 0xFF) >>> (pos & 7))); b &= 1; select(xzmb, xzm1b, xzm, xzm1, b); - add(a0, 0, xzmb, 0, xzmbp, 32); - sub(a0p, 32, xzmb, 0, xzmbp, 32); - add(a1, 0, xzm1b, 0, xzm1bp, 32); - sub(a1p, 32, xzm1b, 0, xzm1bp, 32); - square(b0p, 0, a0p, 0); - square(b0p, 32, a0p, 32); - mult(b1p, 0, a1p, 0, a0p, 32); - mult(b1p, 32, a1p, 32, a0p, 0); - add(c1, 0, b1, 0, b1p, 32); - sub(c1p, 32, b1, 0, b1p, 32); - square(rp, 0, c1p, 32); - sub(sp, 0, b0, 0, b0p, 32); + add(a0, 0, xzmb, 0, xzmbp, 32); + sub(a0p, 32, xzmb, 0, xzmbp, 32); + add(a1, 0, xzm1b, 0, xzm1bp, 32); + sub(a1p, 32, xzm1b, 0, xzm1bp, 32); + square(b0p, 0, a0p, 0); + square(b0p, 32, a0p, 32); + mult(b1p, 0, a1p, 0, a0p, 32); + mult(b1p, 32, a1p, 32, a0p, 0); + add(c1, 0, b1, 0, b1p, 32); + sub(c1p, 32, b1, 0, b1p, 32); + square(rp, 0, c1p, 32); + sub(sp, 0, b0, 0, b0p, 32); mult121665(t, s); - add(u, 0, t, 0, b0p, 0); - mult(xznbp, 0, b0p, 0, b0p, 32); - mult(xznbp, 32, sp, 0, up, 0); - square(xzn1bp, 0, c1p, 0); - mult(xzn1bp, 32, rp, 0, workp, 0); + add(u, 0, t, 0, b0p, 0); + mult(xznbp, 0, b0p, 0, b0p, 32); + mult(xznbp, 32, sp, 0, up, 0); + square(xzn1bp, 0, c1p, 0); + mult(xzn1bp, 32, rp, 0, workp, 0); select(xzm, xzm1, xznb, xzn1b, b); } @@ -267,8 +247,7 @@ static void mainloop(int[] work, byte[] e) work[j] = xzm[j]; } - static void recip(int[] outv, int outvoffset, int[] z, int zoffset) - { + static void recip(int[] outv, int outvoffset, int[] z, int zoffset) { int[] z2 = new int[32]; int[] z9 = new int[32]; int[] z11 = new int[32]; @@ -283,184 +262,177 @@ static void recip(int[] outv, int outvoffset, int[] z, int zoffset) /* 2 */ int[] z2p = z2; square(z2p, 0, z, zoffset); - + /* 4 */ square(t1, 0, z2, 0); - + /* 8 */ square(t0, 0, t1, 0); - + /* 9 */ int[] z9p = z9, t0p = t0; mult(z9p, 0, t0p, 0, z, zoffset); - + /* 11 */ mult(z11, 0, z9, 0, z2, 0); - + /* 22 */ square(t0, 0, z11, 0); - + /* 2^5 - 2^0 = 31 */ mult(z2_5_0, 0, t0, 0, z9, 0); /* 2^6 - 2^1 */ square(t0, 0, z2_5_0, 0); - + /* 2^7 - 2^2 */ square(t1, 0, t0, 0); - + /* 2^8 - 2^3 */ square(t0, 0, t1, 0); - + /* 2^9 - 2^4 */ square(t1, 0, t0, 0); - + /* 2^10 - 2^5 */ square(t0, 0, t1, 0); - + /* 2^10 - 2^0 */ mult(z2_10_0, 0, t0, 0, z2_5_0, 0); /* 2^11 - 2^1 */ square(t0, 0, z2_10_0, 0); - + /* 2^12 - 2^2 */ square(t1, 0, t0, 0); - + /* 2^20 - 2^10 */ - for (int i = 2; i < 10; i += 2) - { + for (int i = 2; i < 10; i += 2) { square(t0, 0, t1, 0); square(t1, 0, t0, 0); } - + /* 2^20 - 2^0 */ mult(z2_20_0, 0, t1, 0, z2_10_0, 0); /* 2^21 - 2^1 */ square(t0, 0, z2_20_0, 0); - + /* 2^22 - 2^2 */ square(t1, 0, t0, 0); - + /* 2^40 - 2^20 */ - for (int i = 2; i < 20; i += 2) - { - square(t0, 0, t1, 0); - square(t1, 0, t0, 0); + for (int i = 2; i < 20; i += 2) { + square(t0, 0, t1, 0); + square(t1, 0, t0, 0); } - + /* 2^40 - 2^0 */ mult(t0, 0, t1, 0, z2_20_0, 0); /* 2^41 - 2^1 */ square(t1, 0, t0, 0); - + /* 2^42 - 2^2 */ square(t0, 0, t1, 0); - + /* 2^50 - 2^10 */ - for (int i = 2; i < 10; i += 2) - { - square(t1, 0, t0, 0); - square(t0, 0, t1, 0); + for (int i = 2; i < 10; i += 2) { + square(t1, 0, t0, 0); + square(t0, 0, t1, 0); } - + /* 2^50 - 2^0 */ mult(z2_50_0, 0, t0, 0, z2_10_0, 0); /* 2^51 - 2^1 */ square(t0, 0, z2_50_0, 0); - + /* 2^52 - 2^2 */ square(t1, 0, t0, 0); - + /* 2^100 - 2^50 */ - for (int i = 2; i < 50; i += 2) - { - square(t0, 0, t1, 0); - square(t1, 0, t0, 0); + for (int i = 2; i < 50; i += 2) { + square(t0, 0, t1, 0); + square(t1, 0, t0, 0); } - + /* 2^100 - 2^0 */ mult(z2_100_0, 0, t1, 0, z2_50_0, 0); /* 2^101 - 2^1 */ square(t1, 0, z2_100_0, 0); - + /* 2^102 - 2^2 */ square(t0, 0, t1, 0); - + /* 2^200 - 2^100 */ - for (int i = 2; i < 100; i += 2) - { + for (int i = 2; i < 100; i += 2) { square(t1, 0, t0, 0); square(t0, 0, t1, 0); } - + /* 2^200 - 2^0 */ mult(t1, 0, t0, 0, z2_100_0, 0); /* 2^201 - 2^1 */ square(t0, 0, t1, 0); - + /* 2^202 - 2^2 */ square(t1, 0, t0, 0); - + /* 2^250 - 2^50 */ - for (int i = 2; i < 50; i += 2) - { + for (int i = 2; i < 50; i += 2) { square(t0, 0, t1, 0); square(t1, 0, t0, 0); } - + /* 2^250 - 2^0 */ mult(t0, 0, t1, 0, z2_50_0, 0); /* 2^251 - 2^1 */ square(t1, 0, t0, 0); - + /* 2^252 - 2^2 */ square(t0, 0, t1, 0); - + /* 2^253 - 2^3 */ square(t1, 0, t0, 0); - + /* 2^254 - 2^4 */ square(t0, 0, t1, 0); - + /* 2^255 - 2^5 */ square(t1, 0, t0, 0); - + /* 2^255 - 21 */ int[] t1p = t1, z11p = z11; mult(outv, outvoffset, t1p, 0, z11p, 0); } - public static int crypto_scalarmult(byte[] q, byte[] n, byte[] p) - { + public static int crypto_scalarmult(byte[] q, byte[] n, byte[] p) { int[] work = new int[96]; byte[] e = new byte[32]; - + for (int i = 0; i < 32; ++i) e[i] = n[i]; - + e[0] &= 248; e[31] &= 127; e[31] |= 64; - + for (int i = 0; i < 32; ++i) work[i] = p[i] & 0xFF; - + mainloop(work, e); - + recip(work, 32, work, 32); - mult(work, 64, work, 0, work, 32); + mult(work, 64, work, 0, work, 32); freeze(work, 64); - + for (int i = 0; i < 32; ++i) q[i] = (byte) work[64 + i]; - + return 0; } } diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519xsalsa20poly1305.java b/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519xsalsa20poly1305.java index b952deb..4290a5b 100644 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519xsalsa20poly1305.java +++ b/source/src/main/java/com/neilalexander/jnacl/crypto/curve25519xsalsa20poly1305.java @@ -1,110 +1,100 @@ // -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// Copyright (c) 2011, Neil Alexander T. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with +// or without modification, are permitted provided that the following +// conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. // package com.neilalexander.jnacl.crypto; -public class curve25519xsalsa20poly1305 -{ +public class curve25519xsalsa20poly1305 { public static final int crypto_box_PUBLICKEYBYTES = 32; public static final int crypto_box_SECRETKEYBYTES = 32; public static final int crypto_box_BEFORENMBYTES = 32; public static final int crypto_box_NONCEBYTES = 24; public static final int crypto_box_ZEROBYTES = 32; public static final int crypto_box_BOXZEROBYTES = 16; - - public static int crypto_box_getpublickey(byte[] pk, byte[] sk) - { + + public static int crypto_box_getpublickey(byte[] pk, byte[] sk) { return curve25519.crypto_scalarmult_base(pk, sk); } - - public static int crypto_box_afternm(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) - { + + public static int crypto_box_afternm(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { return xsalsa20poly1305.crypto_secretbox(c, m, mlen, n, k); } - public static int crypto_box_afternm_nopad(byte[] c, int coffset, byte[] m, int moffset, long mlen, byte[] n, byte[] k) - { - return xsalsa20poly1305.crypto_secretbox_nopad(c, coffset, m, moffset, mlen, n, k); - } - - public static int crypto_box_beforenm(byte[] k, byte[] pk, byte[] sk) - { + public static int crypto_box_afternm_nopad(byte[] c, int coffset, byte[] m, int moffset, + long mlen, byte[] n, byte[] k) { + return xsalsa20poly1305.crypto_secretbox_nopad(c, coffset, m, moffset, mlen, n, k); + } + + public static int crypto_box_beforenm(byte[] k, byte[] pk, byte[] sk) { byte[] s = new byte[32]; byte[] sp = s, sigmap = xsalsa20.sigma; - + curve25519.crypto_scalarmult(sp, sk, pk); return hsalsa20.crypto_core(k, null, sp, sigmap); } - - public static int crypto_box(byte[] c, byte[] m, long mlen, byte[] n, byte[] pk, byte[] sk) - { + + public static int crypto_box(byte[] c, byte[] m, long mlen, byte[] n, byte[] pk, byte[] sk) { byte[] k = new byte[crypto_box_BEFORENMBYTES]; byte[] kp = k; - + crypto_box_beforenm(kp, pk, sk); return crypto_box_afternm(c, m, mlen, n, kp); } - - public static int crypto_box_open(byte[] m, byte[] c, long clen, byte[] n, byte[] pk, byte[] sk) - { + + public static int crypto_box_open(byte[] m, byte[] c, long clen, byte[] n, byte[] pk, + byte[] sk) { byte[] k = new byte[crypto_box_BEFORENMBYTES]; byte[] kp = k; - + crypto_box_beforenm(kp, pk, sk); return crypto_box_open_afternm(m, c, clen, n, kp); } - - public static int crypto_box_open_afternm(byte[] m, byte[] c, long clen, byte[] n, byte[] k) - { + + public static int crypto_box_open_afternm(byte[] m, byte[] c, long clen, byte[] n, byte[] k) { return xsalsa20poly1305.crypto_secretbox_open(m, c, clen, n, k); } - public static int crypto_box_open_afternm_nopad(byte[] m, int moffset, byte[] c, int coffset, long clen, byte[] n, byte[] k) - { - return xsalsa20poly1305.crypto_secretbox_open_nopad(m, moffset, c, coffset, clen, n, k); - } - - public static int crypto_box_afternm(byte[] c, byte[] m, byte[] n, byte[] k) - { - return crypto_box_afternm(c, m, (long)m.length, n, k); + public static int crypto_box_open_afternm_nopad(byte[] m, int moffset, byte[] c, int coffset, + long clen, byte[] n, byte[] k) { + return xsalsa20poly1305.crypto_secretbox_open_nopad(m, moffset, c, coffset, clen, n, k); } - - public static int crypto_box_open_afternm(byte[] m, byte[] c, byte[] n, byte[] k) - { + + public static int crypto_box_afternm(byte[] c, byte[] m, byte[] n, byte[] k) { + return crypto_box_afternm(c, m, (long) m.length, n, k); + } + + public static int crypto_box_open_afternm(byte[] m, byte[] c, byte[] n, byte[] k) { return crypto_box_open_afternm(m, c, (long) c.length, n, k); } - - public static int crypto_box(byte[] c, byte[] m, byte[] n, byte[] pk, byte[] sk) - { + + public static int crypto_box(byte[] c, byte[] m, byte[] n, byte[] pk, byte[] sk) { return crypto_box(c, m, (long) m.length, n, pk, sk); } - - public static int crypto_box_open(byte[] m, byte[] c, byte[] n, byte[] pk, byte[] sk) - { + + public static int crypto_box_open(byte[] m, byte[] c, byte[] n, byte[] pk, byte[] sk) { return crypto_box_open(m, c, (long) c.length, n, pk, sk); } } diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/hsalsa20.java b/source/src/main/java/com/neilalexander/jnacl/crypto/hsalsa20.java index ae5e429..0fc7d8f 100644 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/hsalsa20.java +++ b/source/src/main/java/com/neilalexander/jnacl/crypto/hsalsa20.java @@ -1,59 +1,56 @@ // -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// Copyright (c) 2011, Neil Alexander T. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with +// or without modification, are permitted provided that the following +// conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. // package com.neilalexander.jnacl.crypto; -public class hsalsa20 -{ +public class hsalsa20 { static final int ROUNDS = 20; - static int rotate(int u, int c) - { + static int rotate(int u, int c) { return (u << c) | (u >>> (32 - c)); } - static int load_littleendian(byte[] x, int offset) - { - return ((int)(x[offset])&0xff) | - ((((int)(x[offset + 1])&0xff)) << 8) | - ((((int)(x[offset + 2])&0xff)) << 16) | - ((((int)(x[offset + 3])&0xff)) << 24); + static int load_littleendian(byte[] x, int offset) { + return ((int) (x[offset]) & 0xff) | ((((int) (x[offset + 1]) & 0xff)) << 8) + | ((((int) (x[offset + 2]) & 0xff)) << 16) + | ((((int) (x[offset + 3]) & 0xff)) << 24); } - static void store_littleendian(byte[] x, int offset, int u) - { - x[offset] = (byte) u; u >>>= 8; - x[offset + 1] = (byte) u; u >>>= 8; - x[offset + 2] = (byte) u; u >>>= 8; - x[offset + 3] = (byte) u; + static void store_littleendian(byte[] x, int offset, int u) { + x[offset] = (byte) u; + u >>>= 8; + x[offset + 1] = (byte) u; + u >>>= 8; + x[offset + 2] = (byte) u; + u >>>= 8; + x[offset + 3] = (byte) u; } - public static int crypto_core(byte[] outv, byte[] inv, byte[] k, byte[] c) - { + public static int crypto_core(byte[] outv, byte[] inv, byte[] k, byte[] c) { int x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; int j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; int i; @@ -65,15 +62,12 @@ public static int crypto_core(byte[] outv, byte[] inv, byte[] k, byte[] c) j4 = x4 = load_littleendian(k, 12); j5 = x5 = load_littleendian(c, 4); - if (inv != null) - { + if (inv != null) { j6 = x6 = load_littleendian(inv, 0); j7 = x7 = load_littleendian(inv, 4); j8 = x8 = load_littleendian(inv, 8); j9 = x9 = load_littleendian(inv, 12); - } - else - { + } else { j6 = x6 = j7 = x7 = j8 = x8 = j9 = x9 = 0; } @@ -84,8 +78,7 @@ public static int crypto_core(byte[] outv, byte[] inv, byte[] k, byte[] c) j14 = x14 = load_littleendian(k, 28); j15 = x15 = load_littleendian(c, 12); - for (i = ROUNDS; i > 0; i -= 2) - { + for (i = ROUNDS; i > 0; i -= 2) { x4 ^= rotate(x0 + x12, 7); x8 ^= rotate(x4 + x0, 9); x12 ^= rotate(x8 + x4, 13); @@ -142,8 +135,7 @@ public static int crypto_core(byte[] outv, byte[] inv, byte[] k, byte[] c) x10 -= load_littleendian(c, 8); x15 -= load_littleendian(c, 12); - if (inv != null) - { + if (inv != null) { x6 -= load_littleendian(inv, 0); x7 -= load_littleendian(inv, 4); x8 -= load_littleendian(inv, 8); diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/poly1305.java b/source/src/main/java/com/neilalexander/jnacl/crypto/poly1305.java index 7db9951..f534c73 100644 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/poly1305.java +++ b/source/src/main/java/com/neilalexander/jnacl/crypto/poly1305.java @@ -1,126 +1,117 @@ // -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// Copyright (c) 2011, Neil Alexander T. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with +// or without modification, are permitted provided that the following +// conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. // package com.neilalexander.jnacl.crypto; -public class poly1305 -{ +public class poly1305 { final int CRYPTO_BYTES = 16; final int CRYPTO_KEYBYTES = 32; - + static final int[] minusp = {5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252}; - public static int crypto_onetimeauth_verify(byte[] h, int hoffset, byte[] inv, int invoffset, long inlen, byte[] k) - { + public static int crypto_onetimeauth_verify(byte[] h, int hoffset, byte[] inv, int invoffset, + long inlen, byte[] k) { byte[] correct = new byte[16]; - + crypto_onetimeauth(correct, 0, inv, invoffset, inlen, k); return verify_16.crypto_verify(h, hoffset, correct); } - static void add(int[] h, int[] c) - { + static void add(int[] h, int[] c) { int j; int u = 0; - - for (j = 0; j < 17; ++j) - { + + for (j = 0; j < 17; ++j) { u += h[j] + c[j]; h[j] = u & 255; u >>>= 8; } } - static void squeeze(int[] h) - { + static void squeeze(int[] h) { int u = 0; - - for (int j = 0; j < 16; ++j) - { - u += h[j]; - h[j] = u & 255; + + for (int j = 0; j < 16; ++j) { + u += h[j]; + h[j] = u & 255; u >>>= 8; } - + u += h[16]; h[16] = u & 3; u = 5 * (u >>> 2); - - for (int j = 0; j < 16; ++j) - { + + for (int j = 0; j < 16; ++j) { u += h[j]; h[j] = u & 255; u >>>= 8; } - + u += h[16]; h[16] = u; } - static void freeze(int[] h) - { + static void freeze(int[] h) { int[] horig = new int[17]; - + for (int j = 0; j < 17; ++j) horig[j] = h[j]; - + add(h, minusp); - - int negative = (int)(-(h[16] >>> 7)); - + + int negative = (int) (-(h[16] >>> 7)); + for (int j = 0; j < 17; ++j) h[j] ^= negative & (horig[j] ^ h[j]); } - static void mulmod(int[] h, int[] r) - { + static void mulmod(int[] h, int[] r) { int[] hr = new int[17]; - - for (int i = 0; i < 17; ++i) - { + + for (int i = 0; i < 17; ++i) { int u = 0; - - for (int j = 0; j <= i; ++j) + + for (int j = 0; j <= i; ++j) u += h[j] * r[i - j]; - - for (int j = i + 1; j < 17; ++j) + + for (int j = i + 1; j < 17; ++j) u += 320 * h[j] * r[i + 17 - j]; - + hr[i] = u; } - + for (int i = 0; i < 17; ++i) h[i] = hr[i]; - + squeeze(h); } - public static int crypto_onetimeauth(byte[] outv, int outvoffset, byte[] inv, int invoffset, long inlen, byte[] k) - { + public static int crypto_onetimeauth(byte[] outv, int outvoffset, byte[] inv, int invoffset, + long inlen, byte[] k) { int j; int[] r = new int[17]; int[] h = new int[17]; @@ -147,14 +138,13 @@ public static int crypto_onetimeauth(byte[] outv, int outvoffset, byte[] inv, in for (j = 0; j < 17; ++j) h[j] = 0; - while (inlen > 0) - { + while (inlen > 0) { for (j = 0; j < 17; ++j) c[j] = 0; - + for (j = 0; (j < 16) && (j < inlen); ++j) - c[j] = inv[invoffset + j]&0xff; - + c[j] = inv[invoffset + j] & 0xff; + c[j] = 1; invoffset += j; inlen -= j; @@ -164,15 +154,15 @@ public static int crypto_onetimeauth(byte[] outv, int outvoffset, byte[] inv, in freeze(h); - for (j = 0; j < 16; ++j) + for (j = 0; j < 16; ++j) c[j] = k[j + 16] & 0xFF; - + c[16] = 0; add(h, c); - - for (j = 0; j < 16; ++j) - outv[j + outvoffset] = (byte)h[j]; - + + for (j = 0; j < 16; ++j) + outv[j + outvoffset] = (byte) h[j]; + return 0; } } diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/salsa20.java b/source/src/main/java/com/neilalexander/jnacl/crypto/salsa20.java index 62c58d3..4fea1c7 100644 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/salsa20.java +++ b/source/src/main/java/com/neilalexander/jnacl/crypto/salsa20.java @@ -1,34 +1,33 @@ // -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// Copyright (c) 2011, Neil Alexander T. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with +// or without modification, are permitted provided that the following +// conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. // package com.neilalexander.jnacl.crypto; -public class salsa20 -{ +public class salsa20 { final int crypto_core_salsa20_ref_OUTPUTBYTES = 64; final int crypto_core_salsa20_ref_INPUTBYTES = 16; final int crypto_core_salsa20_ref_KEYBYTES = 32; @@ -38,29 +37,27 @@ public class salsa20 final static int ROUNDS = 20; - static long rotate(int u, int c) - { + static long rotate(int u, int c) { return (u << c) | (u >>> (32 - c)); } - static int load_littleendian(byte[] x, int offset) - { - return ((int)(x[offset])&0xff) | - ((((int)(x[offset + 1])&0xff)) << 8) | - ((((int)(x[offset + 2])&0xff)) << 16) | - ((((int)(x[offset + 3])&0xff)) << 24); + static int load_littleendian(byte[] x, int offset) { + return ((int) (x[offset]) & 0xff) | ((((int) (x[offset + 1]) & 0xff)) << 8) + | ((((int) (x[offset + 2]) & 0xff)) << 16) + | ((((int) (x[offset + 3]) & 0xff)) << 24); } - static void store_littleendian(byte[] x, int offset, int u) - { - x[offset] = (byte) u; u >>>= 8; - x[offset + 1] = (byte) u; u >>>= 8; - x[offset + 2] = (byte) u; u >>>= 8; + static void store_littleendian(byte[] x, int offset, int u) { + x[offset] = (byte) u; + u >>>= 8; + x[offset + 1] = (byte) u; + u >>>= 8; + x[offset + 2] = (byte) u; + u >>>= 8; x[offset + 3] = (byte) u; } - public static int crypto_core(byte[] outv, byte[] inv, byte[] k, byte[] c) - { + public static int crypto_core(byte[] outv, byte[] inv, byte[] k, byte[] c) { int x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; int j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; int i; @@ -82,8 +79,7 @@ public static int crypto_core(byte[] outv, byte[] inv, byte[] k, byte[] c) j14 = x14 = load_littleendian(k, 28); j15 = x15 = load_littleendian(c, 12); - for (i = ROUNDS; i > 0; i -= 2) - { + for (i = ROUNDS; i > 0; i -= 2) { x4 ^= rotate(x0 + x12, 7); x8 ^= rotate(x4 + x0, 9); x12 ^= rotate(x8 + x4, 13); @@ -154,32 +150,29 @@ public static int crypto_core(byte[] outv, byte[] inv, byte[] k, byte[] c) return 0; } - - public static int crypto_stream(byte[] c, int clen, byte[] n, int noffset, byte[] k) - { + + public static int crypto_stream(byte[] c, int clen, byte[] n, int noffset, byte[] k) { byte[] inv = new byte[16]; byte[] block = new byte[64]; - + int coffset = 0; - + if (clen == 0) return 0; for (int i = 0; i < 8; ++i) inv[i] = n[noffset + i]; - + for (int i = 8; i < 16; ++i) inv[i] = 0; - while (clen >= 64) - { + while (clen >= 64) { salsa20.crypto_core(c, inv, k, xsalsa20.sigma); int u = 1; - - for (int i = 8; i < 16; ++i) - { - u += inv[i]&0xff; + + for (int i = 8; i < 16; ++i) { + u += inv[i] & 0xff; inv[i] = (byte) u; u >>>= 8; } @@ -188,46 +181,43 @@ public static int crypto_stream(byte[] c, int clen, byte[] n, int noffset, byte[ coffset += 64; } - if (clen != 0) - { + if (clen != 0) { salsa20.crypto_core(block, inv, k, xsalsa20.sigma); - + for (int i = 0; i < clen; ++i) c[coffset + i] = block[i]; } - + return 0; } - public static int crypto_stream_xor(byte[] c, byte[] m, int mlen, byte[] n, int noffset, byte[] k) - { + public static int crypto_stream_xor(byte[] c, byte[] m, int mlen, byte[] n, int noffset, + byte[] k) { byte[] inv = new byte[16]; byte[] block = new byte[64]; - + int coffset = 0; int moffset = 0; - + if (mlen == 0) return 0; for (int i = 0; i < 8; ++i) inv[i] = n[noffset + i]; - + for (int i = 8; i < 16; ++i) inv[i] = 0; - while (mlen >= 64) - { + while (mlen >= 64) { salsa20.crypto_core(block, inv, k, xsalsa20.sigma); - + for (int i = 0; i < 64; ++i) - c[coffset + i] = (byte)(m[moffset + i] ^ block[i]); + c[coffset + i] = (byte) (m[moffset + i] ^ block[i]); int u = 1; - - for (int i = 8; i < 16; ++i) - { - u += inv[i]&0xff; + + for (int i = 8; i < 16; ++i) { + u += inv[i] & 0xff; inv[i] = (byte) u; u >>>= 8; } @@ -237,88 +227,83 @@ public static int crypto_stream_xor(byte[] c, byte[] m, int mlen, byte[] n, int moffset += 64; } - if (mlen != 0) - { + if (mlen != 0) { salsa20.crypto_core(block, inv, k, xsalsa20.sigma); - + for (int i = 0; i < mlen; ++i) - c[coffset + i] = (byte)(m[moffset + i] ^ block[i]); + c[coffset + i] = (byte) (m[moffset + i] ^ block[i]); } - + return 0; } - public static int crypto_stream_xor_skip32(byte[] c0, byte[] c, int coffset, byte[] m, int moffset, int mlen, byte[] n, int noffset, byte[] k) - { - /* Variant of crypto_stream_xor that outputs the first 32 bytes of the cipherstream to c0 */ + public static int crypto_stream_xor_skip32(byte[] c0, byte[] c, int coffset, byte[] m, + int moffset, int mlen, byte[] n, int noffset, byte[] k) { + /* Variant of crypto_stream_xor that outputs the first 32 bytes of the cipherstream to c0 */ - int u; - byte[] inv = new byte[16]; - byte[] prevblock = new byte[64]; - byte[] curblock = new byte[64]; + int u; + byte[] inv = new byte[16]; + byte[] prevblock = new byte[64]; + byte[] curblock = new byte[64]; - if (mlen == 0) - return 0; + if (mlen == 0) + return 0; - for (int i = 0; i < 8; ++i) - inv[i] = n[noffset + i]; + for (int i = 0; i < 8; ++i) + inv[i] = n[noffset + i]; - for (int i = 8; i < 16; ++i) - inv[i] = 0; + for (int i = 8; i < 16; ++i) + inv[i] = 0; - /* calculate first block */ - salsa20.crypto_core(prevblock, inv, k, xsalsa20.sigma); + /* calculate first block */ + salsa20.crypto_core(prevblock, inv, k, xsalsa20.sigma); - /* extract first 32 bytes of cipherstream into c0 */ - if (c0 != null) - System.arraycopy(prevblock, 0, c0, 0, 32); + /* extract first 32 bytes of cipherstream into c0 */ + if (c0 != null) + System.arraycopy(prevblock, 0, c0, 0, 32); - while (mlen >= 64) - { - u = 1; - for (int i = 8; i < 16; ++i) - { - u += inv[i]&0xff; - inv[i] = (byte) u; - u >>>= 8; - } + while (mlen >= 64) { + u = 1; + for (int i = 8; i < 16; ++i) { + u += inv[i] & 0xff; + inv[i] = (byte) u; + u >>>= 8; + } - salsa20.crypto_core(curblock, inv, k, xsalsa20.sigma); + salsa20.crypto_core(curblock, inv, k, xsalsa20.sigma); - for (int i = 0; i < 32; ++i) - c[coffset + i] = (byte)(m[moffset + i] ^ prevblock[i+32]); + for (int i = 0; i < 32; ++i) + c[coffset + i] = (byte) (m[moffset + i] ^ prevblock[i + 32]); - for (int i = 32; i < 64; ++i) - c[coffset + i] = (byte)(m[moffset + i] ^ curblock[i-32]); + for (int i = 32; i < 64; ++i) + c[coffset + i] = (byte) (m[moffset + i] ^ curblock[i - 32]); - mlen -= 64; - coffset += 64; - moffset += 64; + mlen -= 64; + coffset += 64; + moffset += 64; - byte[] tmpblock = prevblock; - prevblock = curblock; - curblock = tmpblock; - } + byte[] tmpblock = prevblock; + prevblock = curblock; + curblock = tmpblock; + } - if (mlen != 0) - { - u = 1; - for (int i = 8; i < 16; ++i) - { - u += inv[i]&0xff; - inv[i] = (byte) u; - u >>>= 8; - } + if (mlen != 0) { + u = 1; + for (int i = 8; i < 16; ++i) { + u += inv[i] & 0xff; + inv[i] = (byte) u; + u >>>= 8; + } - salsa20.crypto_core(curblock, inv, k, xsalsa20.sigma); + salsa20.crypto_core(curblock, inv, k, xsalsa20.sigma); - for (int i = 0; i < mlen && i < 32; ++i) - c[coffset + i] = (byte)(m[moffset + i] ^ prevblock[i+32]); + for (int i = 0; i < mlen && i < 32; ++i) + c[coffset + i] = (byte) (m[moffset + i] ^ prevblock[i + 32]); - for (int i = 32; i < mlen && i < 64; ++i) - c[coffset + i] = (byte)(m[moffset + i] ^ curblock[i-32]); - } + for (int i = 32; i < mlen && i < 64; ++i) + c[coffset + i] = (byte) (m[moffset + i] ^ curblock[i - 32]); + } - return 0; - } + return 0; + } } diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/verify_16.java b/source/src/main/java/com/neilalexander/jnacl/crypto/verify_16.java index 443c641..4f637cb 100644 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/verify_16.java +++ b/source/src/main/java/com/neilalexander/jnacl/crypto/verify_16.java @@ -1,43 +1,41 @@ // -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// Copyright (c) 2011, Neil Alexander T. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with +// or without modification, are permitted provided that the following +// conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. // package com.neilalexander.jnacl.crypto; -public class verify_16 -{ +public class verify_16 { final int crypto_verify_16_ref_BYTES = 16; - public static int crypto_verify(byte[] x, int xoffset, byte[] y) - { + public static int crypto_verify(byte[] x, int xoffset, byte[] y) { int differentbits = 0; - + for (int i = 0; i < 15; i++) - differentbits |= ((int)(x[xoffset + i] ^ y[i])) & 0xff; - - return (1 & (((int)differentbits - 1) >>> 8)) - 1; + differentbits |= ((int) (x[xoffset + i] ^ y[i])) & 0xff; + + return (1 & (((int) differentbits - 1) >>> 8)) - 1; } } diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20.java b/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20.java index f755787..aae813b 100644 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20.java +++ b/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20.java @@ -1,65 +1,62 @@ // -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// Copyright (c) 2011, Neil Alexander T. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with +// or without modification, are permitted provided that the following +// conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. // package com.neilalexander.jnacl.crypto; -public class xsalsa20 -{ +public class xsalsa20 { final int crypto_stream_xsalsa20_ref_KEYBYTES = 32; final int crypto_stream_xsalsa20_ref_NONCEBYTES = 24; - - public final static byte[] sigma = {(byte) 'e', (byte) 'x', (byte) 'p', (byte) 'a', - (byte) 'n', (byte) 'd', (byte) ' ', (byte) '3', - (byte) '2', (byte) '-', (byte) 'b', (byte) 'y', - (byte) 't', (byte) 'e', (byte) ' ', (byte) 'k'}; - - public static int crypto_stream(byte[] c, int clen, byte[] n, byte[] k) - { + + public final static byte[] sigma = {(byte) 'e', (byte) 'x', (byte) 'p', (byte) 'a', (byte) 'n', + (byte) 'd', (byte) ' ', (byte) '3', (byte) '2', (byte) '-', (byte) 'b', (byte) 'y', + (byte) 't', (byte) 'e', (byte) ' ', (byte) 'k'}; + + public static int crypto_stream(byte[] c, int clen, byte[] n, byte[] k) { byte[] subkey = new byte[32]; - + hsalsa20.crypto_core(subkey, n, k, sigma); return salsa20.crypto_stream(c, clen, n, 16, subkey); } - - public static int crypto_stream_xor(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) - { + + public static int crypto_stream_xor(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { byte[] subkey = new byte[32]; - + hsalsa20.crypto_core(subkey, n, k, sigma); return salsa20.crypto_stream_xor(c, m, (int) mlen, n, 16, subkey); } - public static int crypto_stream_xor_skip32(byte[] c0, byte[] c, int coffset, byte[] m, int moffset, long mlen, byte[] n, byte[] k) - { - /* Variant of crypto_stream_xor that outputs the first 32 bytes of the cipherstream to c0 */ + public static int crypto_stream_xor_skip32(byte[] c0, byte[] c, int coffset, byte[] m, + int moffset, long mlen, byte[] n, byte[] k) { + /* Variant of crypto_stream_xor that outputs the first 32 bytes of the cipherstream to c0 */ - byte[] subkey = new byte[32]; + byte[] subkey = new byte[32]; - hsalsa20.crypto_core(subkey, n, k, sigma); - return salsa20.crypto_stream_xor_skip32(c0, c, coffset, m, moffset, (int) mlen, n, 16, subkey); - } + hsalsa20.crypto_core(subkey, n, k, sigma); + return salsa20.crypto_stream_xor_skip32(c0, c, coffset, m, moffset, (int) mlen, n, 16, + subkey); + } } diff --git a/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20poly1305.java b/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20poly1305.java index 7f94532..5ad44d3 100644 --- a/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20poly1305.java +++ b/source/src/main/java/com/neilalexander/jnacl/crypto/xsalsa20poly1305.java @@ -1,102 +1,104 @@ // -// Copyright (c) 2011, Neil Alexander T. -// All rights reserved. -// -// Redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: -// -// - Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// Copyright (c) 2011, Neil Alexander T. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with +// or without modification, are permitted provided that the following +// conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. // package com.neilalexander.jnacl.crypto; -public class xsalsa20poly1305 -{ +public class xsalsa20poly1305 { final int crypto_secretbox_KEYBYTES = 32; final int crypto_secretbox_NONCEBYTES = 24; final int crypto_secretbox_ZEROBYTES = 32; final int crypto_secretbox_BOXZEROBYTES = 16; - static public int crypto_secretbox(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) - { + static public int crypto_secretbox(byte[] c, byte[] m, long mlen, byte[] n, byte[] k) { if (mlen < 32) return -1; xsalsa20.crypto_stream_xor(c, m, mlen, n, k); poly1305.crypto_onetimeauth(c, 16, c, 32, mlen - 32, c); - + for (int i = 0; i < 16; ++i) c[i] = 0; - + return 0; } - static public int crypto_secretbox_nopad(byte[] c, int coffset, byte[] m, int moffset, long mlen, byte[] n, byte[] k) - { - /* variant of crypto_secretbox that doesn't require 32 zero bytes before m and doesn't output - * 16 zero bytes before c */ - byte[] c0 = new byte[32]; + static public int crypto_secretbox_nopad(byte[] c, int coffset, byte[] m, int moffset, + long mlen, byte[] n, byte[] k) { + /* + * variant of crypto_secretbox that doesn't require 32 zero bytes before m and doesn't + * output 16 zero bytes before c + */ + byte[] c0 = new byte[32]; - xsalsa20.crypto_stream_xor_skip32(c0, c, coffset+16, m, moffset, mlen, n, k); - poly1305.crypto_onetimeauth(c, coffset, c, coffset+16, mlen, c0); + xsalsa20.crypto_stream_xor_skip32(c0, c, coffset + 16, m, moffset, mlen, n, k); + poly1305.crypto_onetimeauth(c, coffset, c, coffset + 16, mlen, c0); - return 0; - } + return 0; + } - static public int crypto_secretbox_open(byte[] m, byte[] c, long clen, byte[] n, byte[] k) - { + static public int crypto_secretbox_open(byte[] m, byte[] c, long clen, byte[] n, byte[] k) { if (clen < 32) return -1; - + byte[] subkeyp = new byte[32]; - + xsalsa20.crypto_stream(subkeyp, 32, n, k); - + if (poly1305.crypto_onetimeauth_verify(c, 16, c, 32, clen - 32, subkeyp) != 0) return -1; - + xsalsa20.crypto_stream_xor(m, c, clen, n, k); - + for (int i = 0; i < 32; ++i) m[i] = 0; - + return 0; } - static public int crypto_secretbox_open_nopad(byte[] m, int moffset, byte[] c, int coffset, long clen, byte[] n, byte[] k) - { - /* variant of crypto_secretbox_open that doesn't require 16 zero bytes before c and doesn't output - * 32 zero bytes before m */ + static public int crypto_secretbox_open_nopad(byte[] m, int moffset, byte[] c, int coffset, + long clen, byte[] n, byte[] k) { + /* + * variant of crypto_secretbox_open that doesn't require 16 zero bytes before c and doesn't + * output 32 zero bytes before m + */ - if (clen < 16) - return -1; + if (clen < 16) + return -1; - byte[] subkeyp = new byte[32]; + byte[] subkeyp = new byte[32]; - xsalsa20.crypto_stream(subkeyp, 32, n, k); + xsalsa20.crypto_stream(subkeyp, 32, n, k); - if (poly1305.crypto_onetimeauth_verify(c, coffset, c, coffset+16, clen - 16, subkeyp) != 0) - return -1; + if (poly1305.crypto_onetimeauth_verify(c, coffset, c, coffset + 16, clen - 16, + subkeyp) != 0) + return -1; - xsalsa20.crypto_stream_xor_skip32(null, m, moffset, c, coffset+16, clen - 16, n, k); + xsalsa20.crypto_stream_xor_skip32(null, m, moffset, c, coffset + 16, clen - 16, n, k); - return 0; - } + return 0; + } } diff --git a/source/src/test/java/ch/threema/apitool/Assert.java b/source/src/test/java/ch/threema/apitool/Assert.java index e3dce18..1d396fe 100644 --- a/source/src/test/java/ch/threema/apitool/Assert.java +++ b/source/src/test/java/ch/threema/apitool/Assert.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,6 +26,10 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool; @@ -32,6 +42,7 @@ public class Assert extends org.junit.Assert { public static void assertEquals(byte[] expected, byte[] actual) { assertEquals(null, expected, actual); } + public static void assertEquals(String message, byte[] expected, byte[] actual) { assertEquals(message, Arrays.toString(expected), Arrays.toString(actual)); } diff --git a/source/src/test/java/ch/threema/apitool/Common.java b/source/src/test/java/ch/threema/apitool/Common.java index c3af9a0..0d3d8b9 100644 --- a/source/src/test/java/ch/threema/apitool/Common.java +++ b/source/src/test/java/ch/threema/apitool/Common.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,34 +26,53 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool; +import ch.threema.apitool.types.GroupId; +import com.neilalexander.jnacl.NaCl; +import org.apache.commons.io.IOUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + /** * Common Stuff */ public abstract class Common { - public static final String myPrivateKey = "private:94af3260fa2a19adc8e82e82be598be15bc6ad6f47c8ee303cb185ef860e16d2"; - public static final String myPrivateKeyExtract = "94af3260fa2a19adc8e82e82be598be15bc6ad6f47c8ee303cb185ef860e16d2"; + public static final String myPrivateKey = + "private:94af3260fa2a19adc8e82e82be598be15bc6ad6f47c8ee303cb185ef860e16d2"; + public static final String myPrivateKeyExtract = + "94af3260fa2a19adc8e82e82be598be15bc6ad6f47c8ee303cb185ef860e16d2"; - public static final String myPublicKey = "public:3851ad59c96146a05b32e41c0ccd0fd639dc8cd66bf6e1cbd3c8d67e4e8f5531"; - public static final String myPublicKeyExtract = "3851ad59c96146a05b32e41c0ccd0fd639dc8cd66bf6e1cbd3c8d67e4e8f5531"; + public static final String myPublicKey = + "public:3851ad59c96146a05b32e41c0ccd0fd639dc8cd66bf6e1cbd3c8d67e4e8f5531"; + public static final String myPublicKeyExtract = + "3851ad59c96146a05b32e41c0ccd0fd639dc8cd66bf6e1cbd3c8d67e4e8f5531"; - public static final String otherPrivateKey = "private:8318e05220acd38e97ba41a9a6318688214219916075ca060f9339a6d1f7fc29"; - public static final String otherPublicKey = "public:10ac7fd937eafb806f9a05bf9afa340a99387b0063cc9cb0d1ea5505d39cc076"; + public static final String otherPrivateKey = + "private:8318e05220acd38e97ba41a9a6318688214219916075ca060f9339a6d1f7fc29"; + public static final String otherPublicKey = + "public:10ac7fd937eafb806f9a05bf9afa340a99387b0063cc9cb0d1ea5505d39cc076"; - public static final String echochoPublicKey = "public:4a6a1b34dcef15d43cb74de2fd36091be99fbbaf126d099d47d83d919712c72b"; + public static final String echochoPublicKey = + "public:4a6a1b34dcef15d43cb74de2fd36091be99fbbaf126d099d47d83d919712c72b"; public static final String randomNonce = "516f4f1562dda0704a7bae8997cf0b354c6980181152ac32"; + public static final GroupId groupId = new GroupId("*ASDFGHI", "DEADBEEF"); public static boolean isEmpty(byte[] byteArray) { - if(byteArray == null) { + if (byteArray == null) { return true; - } - else { - for(byte b: byteArray) { - if(b != 0) { + } else { + for (byte b : byteArray) { + if (b != 0) { return false; } } @@ -55,4 +80,11 @@ public static boolean isEmpty(byte[] byteArray) { return true; } + + public static byte[] readFile(File file) throws IOException { + int fileLength = (int) file.length(); + byte[] fileData = new byte[fileLength + NaCl.BOXOVERHEAD]; + IOUtils.readFully(new FileInputStream(file), fileData, NaCl.BOXOVERHEAD, fileLength); + return fileData; + } } diff --git a/source/src/test/java/ch/threema/apitool/CryptToolTest.java b/source/src/test/java/ch/threema/apitool/CryptToolTest.java index 141f12f..47fbbbc 100644 --- a/source/src/test/java/ch/threema/apitool/CryptToolTest.java +++ b/source/src/test/java/ch/threema/apitool/CryptToolTest.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,79 +26,85 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool; -import org.junit.Test; - -import com.neilalexander.jnacl.NaCl; - import ch.threema.apitool.messages.TextMessage; import ch.threema.apitool.messages.ThreemaMessage; import ch.threema.apitool.results.EncryptResult; +import ch.threema.apitool.types.Key; +import ch.threema.apitool.utils.DataUtils; +import com.neilalexander.jnacl.NaCl; +import org.junit.Test; public class CryptToolTest { - @Test - public void testRandomNonce() throws Exception { - byte[] randomNonce = CryptTool.randomNonce(); - - // random nonce should be a byte array - Assert.assertNotNull("random nonce", randomNonce); - - // with a length of 24 - Assert.assertSame("nonce length", randomNonce.length, NaCl.NONCEBYTES); - } - - @Test - public void testCreateKeyPair() { - byte[] privateKey = new byte[NaCl.SECRETKEYBYTES]; - byte[] publicKey = new byte[NaCl.PUBLICKEYBYTES]; - - CryptTool.generateKeyPair(privateKey, publicKey); - - Assert.assertFalse("empty private key", Common.isEmpty(privateKey)); - Assert.assertFalse("empty public key", Common.isEmpty(publicKey)); - } - - @Test - public void testDecrypt() throws Exception { - String nonce = "0a1ec5b67b4d61a1ef91f55e8ce0471fee96ea5d8596dfd0"; - String box = "45181c7aed95a1c100b1b559116c61b43ce15d04014a805288b7d14bf3a993393264fe554794ce7d6007233e8ef5a0f1ccdd704f34e7c7b77c72c239182caf1d061d6fff6ffbbfe8d3b8f3475c2fe352e563aa60290c666b2e627761e32155e62f048b52ef2f39c13ac229f393c67811749467396ecd09f42d32a4eb419117d0451056ac18fac957c52b0cca67568e2d97e5a3fd829a77f914a1ad403c5909fd510a313033422ea5db71eaf43d483238612a54cb1ecfe55259b1de5579e67c6505df7d674d34a737edf721ea69d15b567bc2195ec67e172f3cb8d6842ca88c29138cc33e9351dbc1e4973a82e1cf428c1c763bb8f3eb57770f914a"; - - Key privateKey = Key.decodeKey(Common.otherPrivateKey); - Key publicKey = Key.decodeKey(Common.myPublicKey); - - ThreemaMessage message = CryptTool.decryptMessage(DataUtils.hexStringToByteArray(box), privateKey.key, - publicKey.key, DataUtils.hexStringToByteArray(nonce)); - - Assert.assertNotNull(message); - Assert.assertTrue("message is not a instance of text message", message instanceof TextMessage); - Assert.assertEquals(((TextMessage) message).getText(), "Dies ist eine Testnachricht. äöü"); - } - - @Test - public void testEncrypt() throws Exception { - String text = "Dies ist eine Testnachricht. äöü"; - - Key privateKey = Key.decodeKey(Common.myPrivateKey); - Key publicKey = Key.decodeKey(Common.otherPublicKey); - - EncryptResult res = CryptTool.encryptTextMessage(text, privateKey.key, publicKey.key); - Assert.assertNotNull(res); - Assert.assertNotNull(res.getNonce()); - Assert.assertNotNull(res.getResult()); - Assert.assertFalse(Common.isEmpty(res.getNonce())); - Assert.assertFalse(Common.isEmpty(res.getResult())); - } - - @Test - public void testDerivePublicKey() throws Exception { - Key privateKey = Key.decodeKey(Common.myPrivateKey); - Key publicKey = Key.decodeKey(Common.myPublicKey); - byte[] derivedPublicKey = CryptTool.derivePublicKey(privateKey.key); - Assert.assertNotNull("derived public key", derivedPublicKey); - Assert.assertEquals(derivedPublicKey, publicKey.key); - } -} \ No newline at end of file + @Test + public void testRandomNonce() throws Exception { + byte[] randomNonce = CryptTool.randomNonce(); + + // random nonce should be a byte array + Assert.assertNotNull("random nonce", randomNonce); + + // with a length of 24 + Assert.assertSame("nonce length", randomNonce.length, NaCl.NONCEBYTES); + } + + @Test + public void testCreateKeyPair() { + byte[] privateKey = new byte[NaCl.SECRETKEYBYTES]; + byte[] publicKey = new byte[NaCl.PUBLICKEYBYTES]; + + CryptTool.generateKeyPair(privateKey, publicKey); + + Assert.assertFalse("empty private key", Common.isEmpty(privateKey)); + Assert.assertFalse("empty public key", Common.isEmpty(publicKey)); + } + + @Test + public void testDecrypt() throws Exception { + String nonce = "0a1ec5b67b4d61a1ef91f55e8ce0471fee96ea5d8596dfd0"; + String box = "45181c7aed95a1c100b1b559116c61b43ce15d04014a805288b7d14bf3a993393264fe554794ce7d6007233e8ef5a0f1ccdd704f34e7c7b77c72c239182caf1d061d6fff6ffbbfe8d3b8f3475c2fe352e563aa60290c666b2e627761e32155e62f048b52ef2f39c13ac229f393c67811749467396ecd09f42d32a4eb419117d0451056ac18fac957c52b0cca67568e2d97e5a3fd829a77f914a1ad403c5909fd510a313033422ea5db71eaf43d483238612a54cb1ecfe55259b1de5579e67c6505df7d674d34a737edf721ea69d15b567bc2195ec67e172f3cb8d6842ca88c29138cc33e9351dbc1e4973a82e1cf428c1c763bb8f3eb57770f914a"; + + Key privateKey = Key.decodeKey(Common.otherPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + + ThreemaMessage message = CryptTool.decryptMessage(DataUtils.hexStringToByteArray(box), + privateKey.key, publicKey.key, DataUtils.hexStringToByteArray(nonce)); + + Assert.assertNotNull(message); + Assert.assertTrue("message is not a instance of text message", + message instanceof TextMessage); + Assert.assertEquals(((TextMessage) message).getText(), "Dies ist eine Testnachricht. äöü"); + } + + @Test + public void testEncrypt() throws Exception { + String text = "Dies ist eine Testnachricht. äöü"; + String nonce = "0a1ec5b67b4d61a1ef91f55e8ce0471fee96ea5d8596dfd0"; + + Key privateKey = Key.decodeKey(Common.myPrivateKey); + Key publicKey = Key.decodeKey(Common.otherPublicKey); + + EncryptResult res = CryptTool.encryptTextMessage(text, privateKey.key, publicKey.key); + Assert.assertNotNull(res); + Assert.assertNotNull(res.getNonce()); + Assert.assertNotNull(res.getResult()); + Assert.assertFalse(Common.isEmpty(res.getNonce())); + Assert.assertFalse(Common.isEmpty(res.getResult())); + } + + @Test + public void testDerivePublicKey() throws Exception { + Key privateKey = Key.decodeKey(Common.myPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + byte[] derivedPublicKey = CryptTool.derivePublicKey(privateKey.key); + Assert.assertNotNull("derived public key", derivedPublicKey); + Assert.assertEquals(derivedPublicKey, publicKey.key); + } +} diff --git a/source/src/test/java/ch/threema/apitool/DataUtilsTest.java b/source/src/test/java/ch/threema/apitool/DataUtilsTest.java new file mode 100644 index 0000000..6c969f2 --- /dev/null +++ b/source/src/test/java/ch/threema/apitool/DataUtilsTest.java @@ -0,0 +1,76 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool; + +import ch.threema.apitool.exceptions.InvalidHexException; +import ch.threema.apitool.types.QuotePart; +import ch.threema.apitool.utils.DataUtils; +import org.junit.Test; + +public class DataUtilsTest { + @Test + public void testHexStringToByteArraySuccess() throws InvalidHexException { + final byte[] decoded = DataUtils.hexStringToByteArray("0011AAff"); + Assert.assertEquals(new byte[] {(byte) 0x00, (byte) 0x11, (byte) 0xaa, (byte) 0xff}, + decoded); + } + + @Test + public void testHexStringToByteArrayStripWhitespace() throws InvalidHexException { + final byte[] decoded = DataUtils.hexStringToByteArray("0011 \n\rAAff"); + Assert.assertEquals(new byte[] {(byte) 0x00, (byte) 0x11, (byte) 0xaa, (byte) 0xff}, + decoded); + } + + @Test(expected = InvalidHexException.class) + public void testHexStringToByteArrayRejectOddLength() { + DataUtils.hexStringToByteArray("00112"); + } + + @Test(expected = InvalidHexException.class) + public void testHexStringToByteArrayRejectInvalid() { + DataUtils.hexStringToByteArray("0011aaffgg"); + } + + @Test + public void testExtractQuote() { + String msgText = "> quote #f053a613ff24aeb8\n\nTest"; + String quotedMessageId = DataUtils.extractQuote(msgText, QuotePart.QUOTED_MESSAGE_ID); + String quoteText = DataUtils.extractQuote(msgText, QuotePart.QUOTE_TEXT); + + Assert.assertEquals("f053a613ff24aeb8", quotedMessageId); + Assert.assertEquals("Test", quoteText); + } +} diff --git a/source/src/test/java/ch/threema/apitool/E2ETest.java b/source/src/test/java/ch/threema/apitool/E2ETest.java new file mode 100644 index 0000000..71ae155 --- /dev/null +++ b/source/src/test/java/ch/threema/apitool/E2ETest.java @@ -0,0 +1,527 @@ +package ch.threema.apitool; + +import ch.threema.apitool.exceptions.ApiException; +import ch.threema.apitool.exceptions.InvalidKeyException; +import ch.threema.apitool.helpers.E2EHelper; +import ch.threema.apitool.messages.DeliveryReceipt; +import ch.threema.apitool.types.FileRenderingType; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.Key; +import ch.threema.apitool.types.MessageId; +import ch.threema.apitool.types.voting.*; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.json.JSONArray; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.*; + +public class E2ETest { + + private static final byte[] randomGroupId = new byte[8]; + private static APIConnector connector; + private static E2EHelper e2EHelper; + private static final Random random = new Random(); + + public static void main(String[] args) { + if (args == null || args.length < 4 || args.length > 5) { + System.out.printf("Usage: %s Threema-ID Gateway-ID Secret PrivateKey [ApiUrl]%n", + new java.io.File(E2ETest.class.getProtectionDomain().getCodeSource() + .getLocation().getPath()).getName()); + System.exit(-1); + } + random.nextBytes(randomGroupId); + var threemaId = args[0]; + String gatewayId = args[1]; + var secret = args[2]; + var pkey = args[3]; + var apiUrl = args.length > 4 ? args[4] : null; + var reader = new MavenXpp3Reader(); + + try { + connector = new APIConnector(gatewayId, secret, apiUrl, new PublicKeyStore() { + @Override + protected byte[] fetchPublicKey(String threemaId) { + return null; + } + + @Override + protected void save(String threemaId, byte[] publicKey) { + + } + }); + connector.setUserAgent(String.format("threema-msgapi-sdk-java/%s-test", + reader.read(new FileReader("pom.xml")).getVersion())); + } catch (IOException | XmlPullParserException e) { + throw new RuntimeException(e); + } + Key privateKey; + try { + privateKey = new Key(Key.KeyType.PRIVATE, Key.decodeKey(pkey).key); + } catch (InvalidKeyException e) { + throw new RuntimeException(e); + } + List threemaIds = List.of(gatewayId, threemaId); + e2EHelper = new E2EHelper(connector, privateKey.key); + try { + /* Lookups */ + testLookupPhone("41790000000"); // OK + testLookupEmail("abc@example.com"); // OK + /* 1:1 Messages */ + var msgId = sendE2ETextMsg(threemaId); // OK, OK + sendE2EFileMsg(threemaId); // OK, OK + var ballotId = sendE2EBallotCreateMessage(threemaId); // OK, OK + sendE2EBallotVoteMsg(threemaId, gatewayId, ballotId); // OK, OK + sendE2EBallotCloseMessage(threemaId, gatewayId, ballotId); // OK, OK + sendE2ELocationMsg(threemaId); // OK, OK + sendDeliveryReceipt(threemaId, msgId, DeliveryReceipt.Type.RECEIVED); // OK, OK + sendDeliveryReceipt(threemaId, msgId, DeliveryReceipt.Type.USER_ACK); // OK, OK + + /* Group messages */ + sendE2EGroupCreateMsg( + new GroupId(randomGroupId, gatewayId.getBytes(StandardCharsets.UTF_8)), + threemaIds, List.of(threemaId)); // OK, OK + sendE2EGroupRenameMsg(threemaIds, + new GroupId(randomGroupId, gatewayId.getBytes(StandardCharsets.UTF_8))); // OK, + // OK + sendE2EGroupSetPhotoMsg(threemaIds, + new GroupId(randomGroupId, gatewayId.getBytes(StandardCharsets.UTF_8))); // OK, + // OK + var msgIds = sendE2EGroupTextMsg(threemaIds, + new GroupId(randomGroupId, gatewayId.getBytes(StandardCharsets.UTF_8))); // OK, + // OK + sendE2EGroupFileMsg(threemaIds, + new GroupId(randomGroupId, gatewayId.getBytes(StandardCharsets.UTF_8))); // OK, + // OK + sendE2EGroupLocationMsg(threemaIds, + new GroupId(randomGroupId, gatewayId.getBytes(StandardCharsets.UTF_8))); // OK, + // OK + var groupBallotId = sendE2EGroupBallotCreateMsg(threemaIds, + new GroupId(randomGroupId, gatewayId.getBytes(StandardCharsets.UTF_8))); // OK, + // OK + sendE2EGroupBallotVoteMsg(threemaIds, + new GroupId(randomGroupId, gatewayId.getBytes(StandardCharsets.UTF_8)), + groupBallotId); // OK, OK + sendGroupDeliveryReceipt(threemaIds, msgIds.get(1), + new GroupId(randomGroupId, gatewayId.getBytes(StandardCharsets.UTF_8))); // OK, + // OK + sendE2EGroupRequestSyncMsg(threemaIds, + new GroupId(randomGroupId, gatewayId.getBytes(StandardCharsets.UTF_8))); // OK + // (?), + // OK + // (?) + sendE2EGroupDeletePhotoMsg(threemaIds, + new GroupId(randomGroupId, gatewayId.getBytes(StandardCharsets.UTF_8))); // OK(, + // no + // receive?) + sendE2EGroupLeaveMsg( + new GroupId(randomGroupId, gatewayId.getBytes(StandardCharsets.UTF_8)), + threemaIds, List.of(gatewayId)); // OK, + } catch (ApiException e) { + System.out.println(e.getMessage()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void sendDeliveryReceipt(String threemaId, String messageId, + DeliveryReceipt.Type receiptType) { + try { + var response = e2EHelper.sendDeliveryReceipt(threemaId, + List.of(new MessageId(messageId)), receiptType); + + System.out.println( + "Message ID: " + response.getData() + " " + response.getStatusCode()); + } catch (Exception e) { + System.err.println("Sending delivery receipt failed"); + } + } + + private static void sendGroupDeliveryReceipt(List threemaIds, String msgId, + GroupId groupId) { + try { + var response = e2EHelper.sendGroupDeliveryReceipt(threemaIds, groupId, + List.of(new MessageId(msgId)), DeliveryReceipt.Type.USER_DEC); + + System.out.println( + "Message ID: " + response.getData() + " " + response.getStatusCode()); + } catch (Exception e) { + System.err.println("Sending group delivery receipt failed"); + } + } + + private static String sendE2ETextMsg(String threemaId) { + try { + var response = e2EHelper.sendTextMessage(threemaId, "Test Message"); + + System.out.println( + "Message ID: " + response.getData() + " " + response.getStatusCode()); + return response.getData(); + } catch (Exception e) { + System.err.println("Sending text message failed"); + return ""; + } + } + + private static void sendE2ELocationMsg(String threemaId) { + try { + var response = e2EHelper.sendLocationMessage(threemaId, "47.8", "8.3", null, + "Test Building", "Nowhere"); + + System.out.println( + "Message ID: " + response.getData() + " " + response.getStatusCode()); + } catch (Exception e) { + System.err.println("Sending location message failed"); + } + } + + private static List sendE2EGroupLocationMsg(List threemaIds, GroupId groupId) + throws ApiException { + try { + var resArr = e2EHelper.sendGroupLocationMessage(threemaIds, groupId, "47.4", "8.1", + null, "Test Building", "Nowhere"); + + var res = new ArrayList(resArr.getData().length()); + for (int i = 0; i < resArr.getData().length(); i++) { + System.out.println("Message ID: " + + resArr.getData().getJSONObject(i).getString("messageId") + " " + + resArr.getStatusCode()); + res.add(resArr.getData().getJSONObject(i).getString("messageId")); + } + return res; + } catch (Exception e) { + System.err.println("Sending group location message failed"); + return List.of(); + } + } + + private static byte[] sendE2EBallotCreateMessage(String threemaId) { + try { + byte[] ballotId = new byte[8]; + random.nextBytes(ballotId); + var response = e2EHelper.sendBallotCreateMessage(threemaId, ballotId, "Test poll", + State.OPEN, VotingMode.SINGLE_CHOICE, + ResultsDisclosureType.INTERMEDIATE, DisplayMode.LIST, + List.of(new BallotChoice(0, "Pizza", 0, null, null), + new BallotChoice(1, "Ananas", 1, null, null)), + null); + System.out.println( + "Message ID: " + response.getData() + " " + response.getStatusCode()); + return ballotId; + } catch (Exception e) { + System.err.println("Sending ballot create message failed"); + return new byte[0]; + } + } + + private static void sendE2EBallotCloseMessage(String threemaId, String gatewayId, + byte[] ballotId) { + try { + var response = e2EHelper.sendBallotCreateMessage(threemaId, ballotId, "Test poll", + State.CLOSED, VotingMode.SINGLE_CHOICE, + ResultsDisclosureType.INTERMEDIATE, DisplayMode.LIST, + List.of(new BallotChoice(0, "Pizza", 0, List.of(1, 0), null), + new BallotChoice(1, "Ananas", 1, List.of(0, 1), null)), + List.of(threemaId, gatewayId)); + System.out.println( + "Message ID: " + response.getData() + " " + response.getStatusCode()); + } catch (Exception e) { + System.err.println("Sending ballot close message failed"); + } + } + + private static byte[] sendE2EGroupBallotCreateMsg(List threemaIds, GroupId groupId) + throws ApiException { + try { + byte[] ballotId = new byte[8]; + random.nextBytes(ballotId); + System.out.println("BallotID: " + Arrays.toString(ballotId)); + var resArr = e2EHelper.sendGroupBallotCreateMessage(threemaIds, groupId, ballotId, + "Test Poll", State.OPEN, VotingMode.SINGLE_CHOICE, + ResultsDisclosureType.INTERMEDIATE, DisplayMode.LIST, + List.of(new BallotChoice(0, "Pizza", 0, null, null), + new BallotChoice(1, "Ananas", 1, null, null)), + null); + + var res = new ArrayList(resArr.getData().length()); + for (int i = 0; i < resArr.getData().length(); i++) { + System.out.println("Message ID: " + + resArr.getData().getJSONObject(i).getString("messageId") + " " + + resArr.getStatusCode()); + res.add(resArr.getData().getJSONObject(i).getString("messageId")); + } + sendE2EGroupBallotVoteMsg(threemaIds, + new GroupId(randomGroupId, + threemaIds.get(0).getBytes(StandardCharsets.UTF_8)), + ballotId); // OK + sendE2EGroupBallotCloseMsg(threemaIds, + new GroupId(randomGroupId, + threemaIds.get(0).getBytes(StandardCharsets.UTF_8)), + ballotId); // OK + return ballotId; + } catch (Exception e) { + System.err.println("Sending group ballot create message failed"); + return new byte[0]; + } + } + + private static List sendE2EGroupBallotCloseMsg(List threemaIds, GroupId groupId, + byte[] ballotId) { + try { + var resArr = e2EHelper.sendGroupBallotCreateMessage(threemaIds, groupId, ballotId, + "Test poll", State.CLOSED, VotingMode.SINGLE_CHOICE, + ResultsDisclosureType.INTERMEDIATE, DisplayMode.LIST, + List.of(new BallotChoice(0, "Pizza", 0, List.of(1, 0), null), + new BallotChoice(1, "Ananas", 1, List.of(0, 1), null)), + threemaIds); + + for (int i = 0; i < resArr.getData().length(); i++) { + System.out.println("Message ID: " + + resArr.getData().getJSONObject(i).getString("messageId") + " " + + resArr.getStatusCode()); + } + return new ArrayList<>(resArr.getData().length()); + } catch (Exception e) { + System.err.println("Sending group ballot close message failed"); + return List.of(); + } + } + + private static void sendE2EBallotVoteMsg(String threemaId, String creator, byte[] ballotId) + throws ApiException { + try { + try { + var response = e2EHelper.sendBallotVoteMessage(threemaId, + creator.getBytes(StandardCharsets.UTF_8), ballotId, + List.of(new VoteChoice(0, false), new VoteChoice(1, true))); + System.out.println("Message ID: " + response.getData() + " " + + response.getStatusCode()); + } catch (Exception e) { + System.err.println("Sending text message failed"); + } + } catch (Exception e) { + System.err.println("Sending ballot vote message failed"); + } + } + + private static List sendE2EGroupBallotVoteMsg(List threemaIds, GroupId groupId, + byte[] ballotId) throws ApiException { + try { + var resArr = e2EHelper.sendGroupBallotVoteMessage(threemaIds, groupId.getGroupCreator(), + groupId, ballotId, + List.of(new VoteChoice(0, false), new VoteChoice(1, true))); + + var res = new ArrayList(resArr.getData().length()); + for (int i = 0; i < resArr.getData().length(); i++) { + System.out.println("Message ID: " + + resArr.getData().getJSONObject(i).getString("messageId") + " " + + resArr.getStatusCode()); + res.add(resArr.getData().getJSONObject(i).getString("messageId")); + } + return res; + } catch (Exception e) { + System.err.println("Sending group ballot vote message failed"); + return List.of(); + } + } + + private static List sendE2EGroupTextMsg(List threemaId, GroupId groupId) + throws ApiException { + try { + var resArr = e2EHelper.sendGroupTextMessage(threemaId, groupId, "Group Test Message"); + + var res = new ArrayList(resArr.getData().length()); + for (int i = 0; i < resArr.getData().length(); i++) { + System.out.println("Message ID: " + + resArr.getData().getJSONObject(i).getString("messageId") + " " + + resArr.getStatusCode()); + res.add(resArr.getData().getJSONObject(i).getString("messageId")); + } + return res; + } catch (Exception e) { + System.err.println("Sending group text message failed"); + return List.of(); + } + } + + private static void sendE2EFileMsg(String threemaId) throws ApiException { + try { + var file = new File("./threema.jpg"); + var thumb = new File("./thumb.png"); + var response = e2EHelper.sendFileMessage(threemaId, file, thumb, + "End-To-End Encrypted Caption", FileRenderingType.FILE, null, + Map.of("q", 0xff)); + + System.out.println( + "Message ID: " + response.getData() + " " + response.getStatusCode()); + } catch (Exception e) { + System.err.println("Sending file message failed"); + } + } + + private static List sendE2EGroupFileMsg(List threemaIds, GroupId groupId) + throws ApiException { + try { + var file = new File("./threema.jpg"); + var thumb = new File("./thumb.png"); + var resArr = e2EHelper.sendGroupFileMessage(threemaIds, groupId, file, thumb, + "End-To-End Encrypted Caption", FileRenderingType.MEDIA, null, + Map.of("q", 0xff)); + + var res = new ArrayList(resArr.getData().length()); + for (int i = 0; i < resArr.getData().length(); i++) { + System.out.println("Message ID: " + + resArr.getData().getJSONObject(i).getString("messageId") + " " + + resArr.getStatusCode()); + res.add(resArr.getData().getJSONObject(i).getString("messageId")); + } + return res; + } catch (Exception e) { + System.err.println("Sending group file message failed"); + return List.of(); + } + } + + private static List sendE2EGroupCreateMsg(GroupId groupId, List threemaIds, + List members) throws ApiException { + try { + var resArr = e2EHelper.sendGroupCreateMessage(threemaIds, members, groupId); + + var res = new ArrayList(resArr.getData().length()); + for (int i = 0; i < resArr.getData().length(); i++) { + System.out.println("Message ID: " + + resArr.getData().getJSONObject(i).getString("messageId") + " " + + resArr.getStatusCode()); + res.add(resArr.getData().getJSONObject(i).getString("messageId")); + } + return res; + } catch (Exception e) { + System.err.println("Sending group create message failed"); + return List.of(); + } + } + + private static List sendE2EGroupLeaveMsg(GroupId groupId, List threemaIds, + List members) throws ApiException { + try { + e2EHelper.sendGroupCreateMessage(threemaIds, members, groupId); + var resArr = e2EHelper.sendGroupLeaveMessage(threemaIds, groupId); + + var res = new ArrayList(resArr.getData().length()); + for (int i = 0; i < resArr.getData().length(); i++) { + System.out.println("Message ID: " + + resArr.getData().getJSONObject(i).getString("messageId") + " " + + resArr.getStatusCode()); + res.add(resArr.getData().getJSONObject(i).getString("messageId")); + } + return res; + } catch (Exception e) { + System.err.println("Sending group leave message failed"); + return List.of(); + } + } + + private static List sendE2EGroupRenameMsg(List threemaIds, GroupId groupId) + throws ApiException { + try { + var resArr = e2EHelper.sendGroupRenameMessage(threemaIds, groupId, "Java SDK Test"); + + var res = new ArrayList(resArr.getData().length()); + for (int i = 0; i < resArr.getData().length(); i++) { + System.out.println("Message ID: " + + resArr.getData().getJSONObject(i).getString("messageId") + " " + + resArr.getStatusCode()); + res.add(resArr.getData().getJSONObject(i).getString("messageId")); + } + return res; + } catch (Exception e) { + System.err.println("Sending group rename message failed"); + return List.of(); + } + } + + private static List sendE2EGroupSetPhotoMsg(List threemaIds, GroupId groupId) + throws ApiException { + try { + var file = new File("./threema.jpg"); + var resArr = e2EHelper.sendGroupSetPhotoMessage(threemaIds, groupId, file); + + var res = new ArrayList(resArr.getData().length()); + for (int i = 0; i < resArr.getData().length(); i++) { + System.out.println("Message ID: " + + resArr.getData().getJSONObject(i).getString("messageId") + " " + + resArr.getStatusCode()); + res.add(resArr.getData().getJSONObject(i).getString("messageId")); + } + return res; + } catch (Exception e) { + System.err.println("Sending group set photo message failed"); + return List.of(); + } + } + + private static List sendE2EGroupDeletePhotoMsg(List threemaIds, GroupId groupId) + throws ApiException { + try { + var resArr = e2EHelper.sendGroupDeletePhotoMessage(threemaIds, groupId); + + var res = new ArrayList(resArr.getData().length()); + for (int i = 0; i < resArr.getData().length(); i++) { + System.out.println("Message ID: " + + resArr.getData().getJSONObject(i).getString("messageId") + " " + + resArr.getStatusCode()); + res.add(resArr.getData().getJSONObject(i).getString("messageId")); + } + return res; + } catch (Exception e) { + System.err.println("Sending group delete photo message failed"); + return List.of(); + } + } + + private static void sendE2EGroupRequestSyncMsg(List threemaIds, GroupId groupId) { + try { + var response = e2EHelper.sendGroupRequestSyncMsg(threemaIds, groupId); + + System.out.println( + "Message IDs: " + response.getData() + " " + response.getStatusCode()); + } catch (Exception e) { + System.err.println("Sending group request sync message failed"); + } + } + + // WARNING: This test can only be confirmed to work with real data + private static void testLookupPhone(String phoneNo) { + try { + var response = connector.lookupPhone(phoneNo); + + System.out.println( + "Threema IDs: " + response.getData() + " " + response.getStatusCode()); + } catch (ApiException e) { + if (e.getCode() == 404) { + System.out.println("No associated Threema ID was found for phone no: " + phoneNo); + } else { + System.err.println("Looking up phone number failed"); + } + } + } + + // WARNING: This test can only be confirmed to work with real data + private static void testLookupEmail(String emailAddr) { + try { + var response = connector.lookupEmail(emailAddr); + + System.out.println( + "Threema IDs: " + response.getData() + " " + response.getStatusCode()); + } catch (ApiException e) { + if (e.getCode() == 404) { + System.out.println("No associated Threema ID was found for phone no: " + emailAddr); + } else { + System.err.println("Looking up email address failed"); + } + } + } +} diff --git a/source/src/test/java/ch/threema/apitool/IntegrationTest.java b/source/src/test/java/ch/threema/apitool/IntegrationTest.java new file mode 100644 index 0000000..3135f6a --- /dev/null +++ b/source/src/test/java/ch/threema/apitool/IntegrationTest.java @@ -0,0 +1,478 @@ +/* + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. + * + * The MIT License (MIT) + * Copyright (c) 2015-2024 Threema GmbH + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * + */ + +package ch.threema.apitool; + +import ch.threema.apitool.exceptions.InvalidKeyException; +import ch.threema.apitool.exceptions.MessageParseException; +import ch.threema.apitool.messages.*; +import ch.threema.apitool.types.FileRenderingType; +import ch.threema.apitool.types.GroupId; +import ch.threema.apitool.types.Key; +import ch.threema.apitool.types.voting.*; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThatNoException; + +@ExtendWith(MockitoExtension.class) +public class IntegrationTest { + + /* + * Generating unit tests is difficult as there is no guarantee the expected result is generated + * correctly. These tests could pass erroneously when both en- and decryption are flawed in the + * same way. + */ + + @Test + public void testGroupTextMessage() throws MessageParseException, InvalidKeyException { + Key privateKey = Key.decodeKey(Common.otherPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + var expectedText = "> quote #a0a0a0a0a0a0a0a0\n\nTest Message"; + var expectedQuotedMessageId = "a0a0a0a0a0a0a0a0"; + var expectedQuoteText = "Test Message"; + + var encryptResult = CryptTool.encryptGroupTextMessage(Common.groupId, + "> quote #a0a0a0a0a0a0a0a0\n\nTest Message", privateKey.key, publicKey.key); + + var actual = CryptTool.decryptMessage(encryptResult.getResult(), privateKey.key, + publicKey.key, encryptResult.getNonce()); + + Assertions.assertNotNull(actual); + Assertions.assertInstanceOf(GroupTextMessage.class, actual, + "message is not an instance of group text message"); + Assertions.assertArrayEquals(Common.groupId.getGroupCreator(), + ((GroupTextMessage) actual).getGroupId().getGroupCreator()); + Assertions.assertArrayEquals(Common.groupId.getGroupId(), + ((GroupTextMessage) actual).getGroupId().getGroupId()); + Assertions.assertEquals(expectedText, ((GroupTextMessage) actual).getText()); + Assertions.assertEquals(expectedQuotedMessageId, + ((GroupTextMessage) actual).getQuotedMessageId()); + Assertions.assertEquals(expectedQuoteText, ((GroupTextMessage) actual).getQuoteText()); + } + + @Test + public void testFileMessage() throws MessageParseException, InvalidKeyException { + for (int i = 0; i < 2; i++) { + Key privateKey = Key.decodeKey(Common.otherPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + var file = new File("./threema.jpg"); + var expectedCaption = i != 1 ? "My caption" : ""; + var expectedBlobId = "abcdeffffffedcba".getBytes(StandardCharsets.UTF_8); + var expectedRenderingType = FileRenderingType.MEDIA.getValue(); + var expectedFilename = "logo.jpg"; + var expectedMimeType = "image/jpg"; + var expectedThumbnailBlobId = new byte[0]; + int expectedFileSize; + try { + expectedFileSize = Common.readFile(file).length; + } catch (IOException e) { + throw new RuntimeException(e); + } + + try { + var encryptedData = CryptTool.encryptFileData(Common.readFile(file)); + + var encryptResult = CryptTool.encryptFileMessage( + "abcdeffffffedcba".getBytes(StandardCharsets.UTF_8), null, null, + encryptedData.getSecret(), "image/jpg", "logo.jpg", + Common.readFile(file).length, i != 1 ? "My caption" : null, + FileRenderingType.MEDIA, null, null, privateKey.key, publicKey.key); + + var actual = CryptTool.decryptMessage(encryptResult.getResult(), privateKey.key, + publicKey.key, encryptResult.getNonce()); + + Assertions.assertNotNull(actual); + Assertions.assertInstanceOf(FileMessage.class, actual, + "message is not an instance of file message"); + Assertions.assertEquals(expectedCaption, ((FileMessage) actual).getCaption()); + Assertions.assertEquals(expectedRenderingType, + ((FileMessage) actual).getRenderingType().getValue()); + Assertions.assertEquals(expectedMimeType, ((FileMessage) actual).getMimeType()); + Assertions.assertArrayEquals(expectedThumbnailBlobId, + ((FileMessage) actual).getThumbnailBlobId()); + Assertions.assertEquals(expectedFileSize, ((FileMessage) actual).getSize()); + Assertions.assertEquals(expectedFilename, ((FileMessage) actual).getFilename()); + Assertions.assertArrayEquals(expectedBlobId, ((FileMessage) actual).getBlobId()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Test + public void testGroupFileMessage() throws MessageParseException, InvalidKeyException { + for (int i = 0; i < 2; i++) { + Key privateKey = Key.decodeKey(Common.otherPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + var file = new File("./threema.jpg"); + var expectedCaption = i != 1 ? "My caption" : ""; + var expectedBlobId = "abcdeffffffedcba".getBytes(StandardCharsets.UTF_8); + var expectedRenderingType = FileRenderingType.MEDIA.getValue(); + var expectedFilename = "logo.jpg"; + var expectedMimeType = "image/jpg"; + var expectedThumbnailBlobId = new byte[0]; + int expectedFileSize; + try { + expectedFileSize = Common.readFile(file).length; + } catch (IOException e) { + throw new RuntimeException(e); + } + + try { + var encryptedData = CryptTool.encryptFileData(Common.readFile(file)); + + var encryptResult = CryptTool.encryptGroupFileMessage(Common.groupId, + "abcdeffffffedcba".getBytes(StandardCharsets.UTF_8), null, null, + encryptedData.getSecret(), "image/jpg", "logo.jpg", + Common.readFile(file).length, i != 1 ? "My caption" : null, + FileRenderingType.MEDIA, null, null, privateKey.key, publicKey.key); + + var actual = CryptTool.decryptMessage(encryptResult.getResult(), privateKey.key, + publicKey.key, encryptResult.getNonce()); + + Assertions.assertNotNull(actual); + Assertions.assertInstanceOf(GroupFileMessage.class, actual, + "message is not an instance of group file message"); + Assertions.assertArrayEquals(Common.groupId.getGroupCreator(), + ((GroupFileMessage) actual).getGroupId().getGroupCreator()); + Assertions.assertArrayEquals(Common.groupId.getGroupId(), + ((GroupFileMessage) actual).getGroupId().getGroupId()); + Assertions.assertEquals(expectedCaption, ((GroupFileMessage) actual).getCaption()); + Assertions.assertEquals(expectedRenderingType, + ((GroupFileMessage) actual).getRenderingType().getValue()); + Assertions.assertEquals(expectedMimeType, + ((GroupFileMessage) actual).getMimeType()); + Assertions.assertArrayEquals(expectedThumbnailBlobId, + ((GroupFileMessage) actual).getThumbnailBlobId()); + Assertions.assertEquals(expectedFileSize, ((GroupFileMessage) actual).getSize()); + Assertions.assertEquals(expectedFilename, + ((GroupFileMessage) actual).getFilename()); + Assertions.assertArrayEquals(expectedBlobId, + ((GroupFileMessage) actual).getBlobId()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @DisplayName("Should encrypt and then decrypt a BallotCreateMessage") + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testBallotCreateMessage(boolean isDisplayModeNull) + throws MessageParseException, InvalidKeyException { + Key privateKey = Key.decodeKey(Common.otherPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + var expected = "Pizza"; + var expectedState = State.OPEN.getValue(); + var expectedVotingMode = VotingMode.SINGLE_CHOICE.getValue(); + var expectedDisclosureType = ResultsDisclosureType.CLOSED.getValue(); + var expectedDisplayMode = DisplayMode.LIST; + + var message = new BallotCreateMessage("BALL0TID".getBytes(StandardCharsets.UTF_8), + "[JAVA SDK] Test Poll", State.OPEN, VotingMode.SINGLE_CHOICE, + ResultsDisclosureType.CLOSED, 0, + isDisplayModeNull ? null : DisplayMode.LIST, + List.of(new BallotChoice(0, "Pizza", 0, List.of(), 0), + new BallotChoice(1, "Ananas", 1, List.of(), 0)), + List.of()); + + var encryptResult = CryptTool.encryptBallotCreateMessage(message.getBallotId(), + message.getDescription(), message.getState(), message.getVotingMode(), + message.getResultsDisclosureType(), message.getOrder(), + message.getDisplayMode(), message.getChoices(), List.of(), privateKey.key, + publicKey.key); + + assertThatNoException().isThrownBy(message::getData); + + var actual = CryptTool.decryptMessage(encryptResult.getResult(), privateKey.key, + publicKey.key, encryptResult.getNonce()); + + Assertions.assertNotNull(actual); + Assertions.assertInstanceOf(BallotCreateMessage.class, actual, + "message is not an instance of ballot create message"); + Assertions.assertEquals(expected, + ((BallotCreateMessage) actual).getChoices().get(0).getName()); + Assertions.assertEquals(expectedState, + ((BallotCreateMessage) actual).getState().getValue()); + Assertions.assertEquals(expectedVotingMode, + ((BallotCreateMessage) actual).getVotingMode().getValue()); + Assertions.assertEquals(expectedDisclosureType, + ((BallotCreateMessage) actual).getResultsDisclosureType().getValue()); + Assertions.assertEquals(expectedDisplayMode, + ((BallotCreateMessage) actual).getDisplayMode()); + } + + @DisplayName("Should encrypt and then decrypt a GroupBallotCreateMessage") + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testGroupBallotCreateMessage(boolean isDisplayModeNull) + throws MessageParseException, InvalidKeyException { + Key privateKey = Key.decodeKey(Common.otherPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + var expected = "Pizza"; + var expectedState = State.OPEN.getValue(); + var expectedVotingMode = VotingMode.SINGLE_CHOICE.getValue(); + var expectedDisclosureType = ResultsDisclosureType.CLOSED.getValue(); + var expectedDisplayMode = DisplayMode.LIST.getValue(); + + var message = new GroupBallotCreateMessage(new GroupId("ASDFGHHJ", "*ASDFGHI"), + "BALL0TID".getBytes(StandardCharsets.UTF_8), "[JAVA SDK] Test Poll", + State.OPEN, VotingMode.SINGLE_CHOICE, ResultsDisclosureType.CLOSED, 0, + isDisplayModeNull ? null : DisplayMode.LIST, + List.of(new BallotChoice(0, "Pizza", 0, List.of(), 0), + new BallotChoice(1, "Ananas", 1, List.of(), 0)), + List.of()); + + var encryptResult = CryptTool.encryptGroupBallotCreateMessage(message.getGroupId(), + message.getBallotId(), message.getDescription(), message.getState(), + message.getVotingMode(), message.getResultsDisclosureType(), + message.getOrder(), message.getDisplayMode(), message.getChoices(), + List.of(), privateKey.key, publicKey.key); + + assertThatNoException().isThrownBy(message::getData); + + var actual = CryptTool.decryptMessage(encryptResult.getResult(), privateKey.key, + publicKey.key, encryptResult.getNonce()); + + Assertions.assertNotNull(actual); + Assertions.assertInstanceOf(GroupBallotCreateMessage.class, actual, + "message is not an instance of group ballot create message"); + Assertions.assertEquals(expected, + ((GroupBallotCreateMessage) actual).getChoices().get(0).getName()); + Assertions.assertEquals(expectedState, + ((GroupBallotCreateMessage) actual).getState().getValue()); + Assertions.assertEquals(expectedVotingMode, + ((GroupBallotCreateMessage) actual).getVotingMode().getValue()); + Assertions.assertEquals(expectedDisclosureType, + ((GroupBallotCreateMessage) actual).getResultsDisclosureType().getValue()); + Assertions.assertEquals(expectedDisplayMode, + ((GroupBallotCreateMessage) actual).getDisplayMode().getValue()); + } + + @Test + public void testGroupBallotVoteMessage() throws MessageParseException, InvalidKeyException { + Key privateKey = Key.decodeKey(Common.otherPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + var expectedSelection = false; + var expectedBallotId = 1; + + var encryptResult = CryptTool.encryptGroupBallotVoteMessage(Common.groupId, + Common.groupId.getGroupCreator(), + "ffffffff".getBytes(StandardCharsets.UTF_8), + List.of(new VoteChoice(0, false), new VoteChoice(1, true)), privateKey.key, + publicKey.key); + + var actual = CryptTool.decryptMessage(encryptResult.getResult(), privateKey.key, + publicKey.key, encryptResult.getNonce()); + + Assertions.assertNotNull(actual); + Assertions.assertInstanceOf(GroupBallotVoteMessage.class, actual, + "message is not an instance of group ballot vote message"); + Assertions.assertArrayEquals(Common.groupId.getGroupCreator(), + ((GroupBallotVoteMessage) actual).getGroupId().getGroupCreator()); + Assertions.assertArrayEquals(Common.groupId.getGroupId(), + ((GroupBallotVoteMessage) actual).getGroupId().getGroupId()); + Assertions.assertEquals(expectedSelection, + ((GroupBallotVoteMessage) actual).getVotes().get(0).getSelected()); + Assertions.assertEquals(expectedBallotId, + ((GroupBallotVoteMessage) actual).getVotes().get(1).getBallotId()); + } + + @Test + public void testLocationMessage() throws MessageParseException, InvalidKeyException { + Key privateKey = Key.decodeKey(Common.otherPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + var expectedLatitude = "47.4"; + var expectedLongitude = "8.1"; + var expectedAccuracy = Float.valueOf(10.0f); + var expectedPoiName = "Rupperswil, A"; + var expectedPoiAddress = "Bahnhofstrasse 4, 5222 Rupperswil, Switzerland, Rupperswil"; + + var encryptResult = CryptTool.encryptLocationMessage("47.4", "8.1", 10.0f, "Rupperswil, A", + "Bahnhofstrasse 4, 5222 Rupperswil, Switzerland, Rupperswil", + privateKey.key, publicKey.key); + + var actual = CryptTool.decryptMessage(encryptResult.getResult(), privateKey.key, + publicKey.key, encryptResult.getNonce()); + + Assertions.assertNotNull(actual); + Assertions.assertInstanceOf(LocationMessage.class, actual, + "message is not an instance of location message"); + Assertions.assertEquals(expectedLatitude, ((LocationMessage) actual).getLatitude()); + Assertions.assertEquals(expectedLongitude, ((LocationMessage) actual).getLongitude()); + Assertions.assertEquals(expectedAccuracy, ((LocationMessage) actual).getAccuracy()); + Assertions.assertEquals(expectedPoiName, ((LocationMessage) actual).getPoiName()); + Assertions.assertEquals(expectedPoiAddress, ((LocationMessage) actual).getPoiAddress()); + } + + @Test + public void testGroupLocationMessage() throws MessageParseException, InvalidKeyException { + Key privateKey = Key.decodeKey(Common.otherPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + var expectedLatitude = "47.4"; + var expectedLongitude = "8.1"; + var expectedAccuracy = Float.valueOf(10.0f); + var expectedPoiName = "Rupperswil, A"; + var expectedPoiAddress = "Bahnhofstrasse 4, 5222 Rupperswil, Switzerland, Rupperswil"; + + var encryptResult = CryptTool.encryptGroupLocationMessage(Common.groupId, "47.4", "8.1", + 10.0f, "Rupperswil, A", + "Bahnhofstrasse 4, 5222 Rupperswil, Switzerland, Rupperswil", + privateKey.key, publicKey.key); + + var actual = CryptTool.decryptMessage(encryptResult.getResult(), privateKey.key, + publicKey.key, encryptResult.getNonce()); + + Assertions.assertNotNull(actual); + Assertions.assertInstanceOf(GroupLocationMessage.class, actual, + "message is not an instance of group location message"); + Assertions.assertArrayEquals(Common.groupId.getGroupCreator(), + ((GroupLocationMessage) actual).getGroupId().getGroupCreator()); + Assertions.assertArrayEquals(Common.groupId.getGroupId(), + ((GroupLocationMessage) actual).getGroupId().getGroupId()); + Assertions.assertEquals(expectedLatitude, ((GroupLocationMessage) actual).getLatitude()); + Assertions.assertEquals(expectedLongitude, ((GroupLocationMessage) actual).getLongitude()); + Assertions.assertEquals(expectedAccuracy, ((GroupLocationMessage) actual).getAccuracy()); + Assertions.assertEquals(expectedPoiName, ((GroupLocationMessage) actual).getPoiName()); + Assertions.assertEquals(expectedPoiAddress, + ((GroupLocationMessage) actual).getPoiAddress()); + } + + @Test + public void testGroupLocationMessageNoAccuracy() + throws MessageParseException, InvalidKeyException { + Key privateKey = Key.decodeKey(Common.otherPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + var expectedLatitude = "47.4"; + var expectedLongitude = "8.1"; + Float expectedAccuracy = null; + var expectedPoiName = "Rupperswil"; + var expectedPoiAddress = "Switzerland"; + + var encryptResult = CryptTool.encryptGroupLocationMessage(Common.groupId, "47.4", "8.1", + null, "Rupperswil", "Switzerland", privateKey.key, publicKey.key); + + var actual = CryptTool.decryptMessage(encryptResult.getResult(), privateKey.key, + publicKey.key, encryptResult.getNonce()); + + Assertions.assertNotNull(actual); + Assertions.assertInstanceOf(GroupLocationMessage.class, actual, + "message is not an instance of group location message"); + Assertions.assertArrayEquals(Common.groupId.getGroupCreator(), + ((GroupLocationMessage) actual).getGroupId().getGroupCreator()); + Assertions.assertArrayEquals(Common.groupId.getGroupId(), + ((GroupLocationMessage) actual).getGroupId().getGroupId()); + Assertions.assertEquals(expectedLatitude, ((GroupLocationMessage) actual).getLatitude()); + Assertions.assertEquals(expectedLongitude, ((GroupLocationMessage) actual).getLongitude()); + Assertions.assertEquals(expectedAccuracy, ((GroupLocationMessage) actual).getAccuracy()); + Assertions.assertEquals(expectedPoiName, ((GroupLocationMessage) actual).getPoiName()); + Assertions.assertEquals(expectedPoiAddress, + ((GroupLocationMessage) actual).getPoiAddress()); + } + + @Test + public void testGroupLocationMessageNoName() throws MessageParseException, InvalidKeyException { + Key privateKey = Key.decodeKey(Common.otherPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + var expectedLatitude = "47.4"; + var expectedLongitude = "8.1"; + var expectedAccuracy = Float.valueOf(10.0f); + String expectedPoiName = null; + var expectedPoiAddress = "Bahnhofstrasse 4, 5222 Rupperswil, Switzerland, Rupperswil"; + + var encryptResult = CryptTool.encryptGroupLocationMessage(Common.groupId, "47.4", "8.1", + 10.0f, null, "Bahnhofstrasse 4, 5222 Rupperswil, Switzerland, Rupperswil", + privateKey.key, publicKey.key); + + var actual = CryptTool.decryptMessage(encryptResult.getResult(), privateKey.key, + publicKey.key, encryptResult.getNonce()); + + Assertions.assertNotNull(actual); + Assertions.assertInstanceOf(GroupLocationMessage.class, actual, + "message is not an instance of group location message"); + Assertions.assertArrayEquals(Common.groupId.getGroupCreator(), + ((GroupLocationMessage) actual).getGroupId().getGroupCreator()); + Assertions.assertArrayEquals(Common.groupId.getGroupId(), + ((GroupLocationMessage) actual).getGroupId().getGroupId()); + Assertions.assertEquals(expectedLatitude, ((GroupLocationMessage) actual).getLatitude()); + Assertions.assertEquals(expectedLongitude, ((GroupLocationMessage) actual).getLongitude()); + Assertions.assertEquals(expectedAccuracy, ((GroupLocationMessage) actual).getAccuracy()); + Assertions.assertEquals(expectedPoiName, ((GroupLocationMessage) actual).getPoiName()); + Assertions.assertEquals(expectedPoiAddress, + ((GroupLocationMessage) actual).getPoiAddress()); + } + + @Test + public void testGroupLocationMessageNoNameAndAddress() + throws MessageParseException, InvalidKeyException { + Key privateKey = Key.decodeKey(Common.otherPrivateKey); + Key publicKey = Key.decodeKey(Common.myPublicKey); + var expectedLatitude = "47.4"; + var expectedLongitude = "8.1"; + var expectedAccuracy = Float.valueOf(10.0f); + String expectedPoiName = null; + String expectedPoiAddress = null; + + var encryptResult = CryptTool.encryptGroupLocationMessage(Common.groupId, "47.4", "8.1", + 10.0f, null, null, privateKey.key, publicKey.key); + + var actual = CryptTool.decryptMessage(encryptResult.getResult(), privateKey.key, + publicKey.key, encryptResult.getNonce()); + + Assertions.assertNotNull(actual); + Assertions.assertInstanceOf(GroupLocationMessage.class, actual, + "message is not an instance of group location message"); + Assertions.assertArrayEquals(Common.groupId.getGroupCreator(), + ((GroupLocationMessage) actual).getGroupId().getGroupCreator()); + Assertions.assertArrayEquals(Common.groupId.getGroupId(), + ((GroupLocationMessage) actual).getGroupId().getGroupId()); + Assertions.assertEquals(expectedLatitude, ((GroupLocationMessage) actual).getLatitude()); + Assertions.assertEquals(expectedLongitude, ((GroupLocationMessage) actual).getLongitude()); + Assertions.assertEquals(expectedAccuracy, ((GroupLocationMessage) actual).getAccuracy()); + Assertions.assertEquals(expectedPoiName, ((GroupLocationMessage) actual).getPoiName()); + Assertions.assertEquals(expectedPoiAddress, + ((GroupLocationMessage) actual).getPoiAddress()); + } +} diff --git a/source/src/test/java/ch/threema/apitool/KeyTest.java b/source/src/test/java/ch/threema/apitool/KeyTest.java index 104188b..fd3dd58 100644 --- a/source/src/test/java/ch/threema/apitool/KeyTest.java +++ b/source/src/test/java/ch/threema/apitool/KeyTest.java @@ -1,8 +1,14 @@ /* - * $Id$ + * _____ _ + * |_ _| |_ _ _ ___ ___ _ __ __ _ + * | | | ' \| '_/ -_) -_) ' \/ _` |_ + * |_| |_||_|_| \___\___|_|_|_\__,_(_) + * + * Threema Gateway Java SDK + * This SDK allows for preparing, sending and receiving of Threema Messages via Threema Gateway. * * The MIT License (MIT) - * Copyright (c) 2015 Threema GmbH + * Copyright (c) 2015-2024 Threema GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,11 +26,17 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE + * + * + * + * */ package ch.threema.apitool; import ch.threema.apitool.exceptions.InvalidKeyException; +import ch.threema.apitool.types.Key; +import ch.threema.apitool.utils.DataUtils; import org.junit.Test; public class KeyTest { @@ -41,19 +53,24 @@ public void testDecodeWrongKey() { @Test public void testDecodeKeyPrivate() throws Exception { - Key key = Key.decodeKey("private:1234567890123456789012345678901234567890123456789012345678901234"); + Key key = Key.decodeKey( + "private:1234567890123456789012345678901234567890123456789012345678901234"); Assert.assertNotNull("key instance", key); Assert.assertEquals(key.type, Key.KeyType.PRIVATE); - Assert.assertEquals(key.key, DataUtils.hexStringToByteArray("1234567890123456789012345678901234567890123456789012345678901234")); + Assert.assertEquals(key.key, DataUtils.hexStringToByteArray( + "1234567890123456789012345678901234567890123456789012345678901234")); } + @Test public void testDecodeKeyPublic() throws Exception { - Key key = Key.decodeKey("public:1234567890123456789012345678901234567890123456789012345678901234"); + Key key = Key.decodeKey( + "public:1234567890123456789012345678901234567890123456789012345678901234"); Assert.assertNotNull("key instance", key); Assert.assertEquals(key.type, Key.KeyType.PUBLIC); - Assert.assertEquals(key.key, DataUtils.hexStringToByteArray("1234567890123456789012345678901234567890123456789012345678901234")); + Assert.assertEquals(key.key, DataUtils.hexStringToByteArray( + "1234567890123456789012345678901234567890123456789012345678901234")); } @Test @@ -68,4 +85,4 @@ public void testEncodePrivate() throws Exception { Assert.assertEquals(key.encode(), Common.myPrivateKey); } -} \ No newline at end of file +} diff --git a/source/src/test/java/ch/threema/apitool/SimpleTest.java b/source/src/test/java/ch/threema/apitool/SimpleTest.java new file mode 100644 index 0000000..6efe329 --- /dev/null +++ b/source/src/test/java/ch/threema/apitool/SimpleTest.java @@ -0,0 +1,46 @@ +package ch.threema.apitool; + +import ch.threema.apitool.exceptions.ApiException; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +import java.io.FileReader; +import java.io.IOException; + +public class SimpleTest { + + public static void main(String[] args) { + if (args == null || args.length < 3 || args.length > 4) { + System.out.printf("Usage: %s Threema-ID Gateway-ID Secret [ApiUrl]%n", + new java.io.File(E2ETest.class.getProtectionDomain().getCodeSource() + .getLocation().getPath()).getName()); + System.exit(-1); + } + var threemaId = args[0]; + String gatewayId = args[1]; + var secret = args[2]; + var apiUrl = args.length > 3 ? args[3] : null; + var reader = new MavenXpp3Reader(); + APIConnector connector; + try { + connector = new APIConnector(gatewayId, secret, apiUrl, new PublicKeyStore() { + @Override + protected byte[] fetchPublicKey(String threemaId) { + return null; + } + + @Override + protected void save(String threemaId, byte[] publicKey) { + + } + }); + connector.setUserAgent(String.format("threema-msgapi-sdk-java/%s-test", + reader.read(new FileReader("pom.xml")).getVersion())); + + var res = connector.sendTextMessageSimple(threemaId, "Simple text message"); + System.out.println(res.getData() + " " + res.getStatusCode()); // OK (, no receive) + } catch (IOException | XmlPullParserException | ApiException e) { + throw new RuntimeException(e); + } + } +} diff --git a/source/threema.jpg b/source/threema.jpg new file mode 100644 index 0000000..e779292 Binary files /dev/null and b/source/threema.jpg differ diff --git a/source/thumb.png b/source/thumb.png new file mode 100644 index 0000000..c2d2a6c Binary files /dev/null and b/source/thumb.png differ