diff --git a/.idea/dictionaries/t.xml b/.idea/dictionaries/t.xml
index 4366d93e72..55684f7f5d 100644
--- a/.idea/dictionaries/t.xml
+++ b/.idea/dictionaries/t.xml
@@ -148,6 +148,7 @@
shahrabani
shimokawa
shokz
+ sint
skype
slezak
sophanimus
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/GarminJson.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/GarminJson.java
new file mode 100644
index 0000000000..2eab27e7c2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/garmin/http/GarminJson.java
@@ -0,0 +1,356 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.http;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class GarminJson {
+ private static final byte[] STRING_SECTION_MAGIC = {(byte) 0xab, (byte) 0xcd, (byte) 0xab, (byte) 0xcd};
+ private static final byte[] DATA_SECTION_MAGIC = {(byte) 0xda, (byte) 0x7a, (byte) 0xda, (byte) 0x7a};
+
+ private static final byte TYPE_NULL = 0x00;
+ private static final byte TYPE_SINT32 = 0x01;
+ private static final byte TYPE_FLOAT = 0x02;
+ private static final byte TYPE_STRING = 0x03;
+ private static final byte TYPE_ARRAY = 0x05;
+ private static final byte TYPE_BOOL = 0x09;
+ private static final byte TYPE_MAP = 0x0b;
+ private static final byte TYPE_SINT64 = 0x0e;
+ private static final byte TYPE_DOUBLE = 0x0f;
+
+ public static byte[] encode(final Object object) throws GarminJsonException {
+ try {
+ // First pass: collect all strings
+ Set strings = new LinkedHashSet<>();
+ collectStrings(object, strings);
+
+ // Build string section
+ final ByteArrayOutputStream stringSection = new ByteArrayOutputStream();
+ int currentOffset = 0;
+ final Map finalOffsets = new LinkedHashMap<>();
+ for (final String str : strings) {
+ finalOffsets.put(str, currentOffset);
+ final byte[] strBytes = str.getBytes(StandardCharsets.UTF_8);
+ final int length = strBytes.length + 1; // +1 for null terminator
+ stringSection.write((length >> 8) & 0xFF);
+ stringSection.write(length & 0xFF);
+ stringSection.write(strBytes);
+ stringSection.write(0x00);
+ currentOffset += 2 + strBytes.length + 1;
+ }
+
+ // Build data section (breadth-first)
+ final ByteArrayOutputStream dataSection = new ByteArrayOutputStream();
+ encodeValueBreadthFirst(object, dataSection, finalOffsets);
+
+ final ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ // Write string section, if any
+ if (stringSection.size() > 0) {
+ output.write(STRING_SECTION_MAGIC);
+ writeUint32BE(output, stringSection.size());
+ output.write(stringSection.toByteArray());
+ }
+
+ // Write data section
+ output.write(DATA_SECTION_MAGIC);
+ writeUint32BE(output, dataSection.size());
+ output.write(dataSection.toByteArray());
+
+ return output.toByteArray();
+ } catch (final IOException e) {
+ throw new GarminJsonException("Failed to encode GarminJson", e);
+ }
+ }
+
+ private static void encodeValueBreadthFirst(final Object rootObj,
+ final ByteArrayOutputStream out,
+ final Map stringOffsets) throws GarminJsonException {
+ final Queue