upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/BE.md
diff options
context:
space:
mode:
Diffstat (limited to 'BE.md')
-rw-r--r--BE.md137
1 files changed, 137 insertions, 0 deletions
diff --git a/BE.md b/BE.md
new file mode 100644
index 0000000..50062d9
--- /dev/null
+++ b/BE.md
@@ -0,0 +1,137 @@
1NIP-BE
2======
3
4Nostr BLE Communications Protocol
5---------------------------------
6
7`draft` `optional`
8
9This NIP specifies how Nostr apps can use BLE to communicate and synchronize with each other. The BLE protocol follows a client-server pattern, so this NIP emulates the WS structure in a similar way, but with some adaptations to its limitations.
10
11## Device advertisement
12A device advertises itself with:
13- Service UUID: `0000180f-0000-1000-8000-00805f9b34fb`
14- Data: Device UUID in ByteArray format
15
16## GATT service
17The device exposes a Nordic UART Service with the following characteristics:
18
191. Write Characteristic
20 - UUID: `87654321-0000-1000-8000-00805f9b34fb`
21 - Properties: Write
22
232. Read Characteristic
24 - UUID: `12345678-0000-1000-8000-00805f9b34fb`
25 - Properties: Notify, Read
26
27## Role assignment
28
29When one device initially finds another advertising the service, it will read the service's data to get the device UUID and compare it with its own advertised device UUID. For this communication, the device with the highest ID will take the role of GATT Server (Relay), the other will be considered the GATT Client (Client) and will proceed to establish the connection.
30
31For devices whose purpose will require a single role, its device UUID will always be:
32
33- GATT Server: `FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF`
34- GATT Client: `00000000-0000-0000-0000-000000000000`
35
36## Messages
37
38All messages will follow [NIP-01](/01.md) message structure. For a given message, a compression stream (DEFLATE) is applied to the message to generate a byte array. Depending on the BLE version, the byte array can be too large for a single message (20-23 bytes in BLE 4.2, 256 bytes in BLE > 4.2). In that case, this byte array is split into any number of batches following the structure:
39
40```
41[batch index (first 2 bytes)][batch n][is last batch (last byte)]
42```
43After reception of all batches, the other device can then join them and decompress. To ensure reliability, only 1 message will be read/written at a time. MTU can be negotiated in advance. The maximum size for a message is 64KB; bigger messages will be rejected.
44
45## Examples
46
47This example implements a function to split and compress a byte array into chunks, as well as another function to join and decompress them in order to obtain the initial result:
48
49```kotlin
50fun splitInChunks(message: ByteArray): Array<ByteArray> {
51 val chunkSize = 500 // define the chunk size
52 var byteArray = compressByteArray(message)
53 val numChunks = (byteArray.size + chunkSize - 1) / chunkSize // calculate the number of chunks
54 var chunkIndex = 0
55 val chunks = Array(numChunks) { ByteArray(0) }
56
57 for (i in 0 until numChunks) {
58 val start = i * chunkSize
59 val end = minOf((i + 1) * chunkSize, byteArray.size)
60 val chunk = byteArray.copyOfRange(start, end)
61
62 // add chunk index to the first 2 bytes and last chunk flag to the last byte
63 val chunkWithIndex = ByteArray(chunk.size + 2)
64 chunkWithIndex[0] = chunkIndex.toByte() // chunk index
65 chunk.copyInto(chunkWithIndex, 1)
66 chunkWithIndex[chunkWithIndex.size - 1] = numChunks.toByte()
67
68 // store the chunk in the array
69 chunks[i] = chunkWithIndex
70
71 chunkIndex++
72 }
73
74 return chunks
75}
76
77fun joinChunks(chunks: Array<ByteArray>): ByteArray {
78 val sortedChunks = chunks.sortedBy { it[0] }
79 var reassembledByteArray = ByteArray(0)
80 for (chunk in sortedChunks) {
81 val chunkData = chunk.copyOfRange(1, chunk.size - 1)
82 reassembledByteArray = reassembledByteArray.copyOf(reassembledByteArray.size + chunkData.size)
83 chunkData.copyInto(reassembledByteArray, reassembledByteArray.size - chunkData.size)
84 }
85
86 return decompressByteArray(reassembledByteArray)
87}
88
89```
90
91## Workflows
92
93### Client to relay
94
95- Any message the client wants to send to a relay will be a write message.
96- Any message the client receives from a relay will be a read message.
97
98### Relay to client
99
100The relay should notify the client about any new event matching subscription's filters by using the Notify action of the Read Characteristic. After that, the client can proceed to read messages from the relay.
101
102### Device synchronization
103
104Given the nature of BLE, it is expected that the direct connection between two devices might be extremely intermittent, with gaps of hours or even days. That's why it's crucial to define a synchronization process by following [NIP-77](./77.md) but with an adaptation to the limitations of the technology.
105
106After two devices have successfully connected and established the Client-Server roles, the devices will use half-duplex communication to intermittently send and receive messages.
107
108#### Half-duplex synchronization
109
110Right after the 2 devices connect, the Client starts the workflow by sending the first message.
111
1121. Client - Writes ["NEG-OPEN"](/77.md#initial-message-client-to-relay) message.
1132. Server - Sends `write-success`.
1143. Client - Sends `read-message`.
1154. Server - Responds with ["NEG-MSG"](./77.md#subsequent-messages-bidirectional) message.
1165. Client -
117 1. If the Client has messages missing on the Server, it writes one `EVENT`.
118 2. If the Client doesn't have any messages missing on the Server, it writes `EOSE`. In this case, subsequent messages to the Server will be empty while the Server claims to have more notes for the Client.
1196. Server - Sends `write-success`.
1207. Client - Sends `read-message`.
1218. Server -
122 1. If the Server has messages missing on the Client, it responds with one `EVENT`.
123 2. If the Client doesn't have any messages missing on the Server, it responds with `EOSE`. In this case, subsequent responses to the Client will be empty.
1249. If the Client detects that the devices are not synchronized yet, jump to step 5.
12510. After the two devices detect that there are no more missing events on both ends, the workflow will pause at this point.
126
127#### Half-duplex event spread
128
129While two devices are connected and synchronized, it might happen that one of them receives a new message from another connected peer. Devices MUST keep track of which notes have been sent to its peers while they are connected. If the newly received event is detected as missing in one of the connected and synchronized peers:
130
1311. If the peer is a Server:
132 1. Client - It writes the `EVENT`.
133 2. Server - Sends `write-success`.
1342. If the peer is a Client:
135 1. Server - It will send an empty notification to the Client.
136 2. Client - Sends `read-message`.
137 3. Server - Responds with the `EVENT`.