refactor: reorganize NIP-BE into subpackages and add README
Restructured nipBEBle/ into logical subpackages: - Root: public API surface (BleNostrMesh, BleConfig, BlePeer, BleRole) - protocol/: wire format (BleMessageChunker, BleChunkAssembler) - transport/: platform BLE abstraction (BleTransport, AndroidBleTransport) - relay/: Nostr protocol over BLE (BleNostrClient, BleNostrServer, BleMeshManager) Added README.md with quick start guide, package structure overview, advanced usage examples, and wire protocol reference. https://claude.ai/code/session_01Tz5E73Rj7tL48A3qUGS5DT
This commit is contained in:
+5
-1
@@ -18,7 +18,7 @@
|
||||
* 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 com.vitorpamplona.quartz.nipBEBle
|
||||
package com.vitorpamplona.quartz.nipBEBle.transport
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
@@ -41,6 +41,10 @@ import android.bluetooth.le.ScanResult
|
||||
import android.bluetooth.le.ScanSettings
|
||||
import android.content.Context
|
||||
import android.os.ParcelUuid
|
||||
import com.vitorpamplona.quartz.nipBEBle.AndroidBleTransportContract
|
||||
import com.vitorpamplona.quartz.nipBEBle.BleConfig
|
||||
import com.vitorpamplona.quartz.nipBEBle.BlePeer
|
||||
import com.vitorpamplona.quartz.nipBEBle.BleRole
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import java.util.UUID
|
||||
|
||||
@@ -24,6 +24,11 @@ import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
||||
import com.vitorpamplona.quartz.nipBEBle.relay.BleMeshListener
|
||||
import com.vitorpamplona.quartz.nipBEBle.relay.BleMeshManager
|
||||
import com.vitorpamplona.quartz.nipBEBle.relay.BleNostrServer
|
||||
import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
|
||||
import com.vitorpamplona.quartz.nipBEBle.transport.BleTransportListener
|
||||
|
||||
/**
|
||||
* Easy-to-use facade for NIP-BE Nostr BLE mesh networking.
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
# NIP-BE: Nostr BLE Communications Protocol
|
||||
|
||||
Kotlin Multiplatform implementation of [NIP-BE](https://github.com/nostr-protocol/nips/blob/master/BE.md)
|
||||
for peer-to-peer Nostr event synchronization over Bluetooth Low Energy.
|
||||
|
||||
Compatible with [KoalaSat/samiz](https://github.com/KoalaSat/samiz).
|
||||
|
||||
## Quick Start (Android)
|
||||
|
||||
```kotlin
|
||||
// 1. Create the mesh (auto-wires transport)
|
||||
val mesh = BleNostrMesh(AndroidBleTransport(context))
|
||||
|
||||
// 2. Listen for events from nearby peers
|
||||
mesh.onEvent { event, peer ->
|
||||
Log.d("BLE", "Received kind ${event.kind} from ${peer.deviceUuid}")
|
||||
myDatabase.save(event)
|
||||
}
|
||||
|
||||
// 3. Optional: track peer connectivity
|
||||
mesh.onPeerConnected { peer -> updatePeerCount(mesh.connectedPeers().size) }
|
||||
mesh.onPeerDisconnected { peer -> updatePeerCount(mesh.connectedPeers().size) }
|
||||
mesh.onError { error -> Log.e("BLE", error) }
|
||||
|
||||
// 4. Start (begins advertising + scanning)
|
||||
mesh.start()
|
||||
|
||||
// 5. Broadcast events to all connected peers
|
||||
mesh.broadcast(mySignedEvent)
|
||||
|
||||
// 6. Stop when done
|
||||
mesh.stop()
|
||||
```
|
||||
|
||||
### Required Android Permissions
|
||||
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
```
|
||||
|
||||
Request these at runtime before calling `mesh.start()`.
|
||||
|
||||
## How It Works
|
||||
|
||||
When two devices running NIP-BE discover each other:
|
||||
|
||||
1. **Discovery** - Both devices advertise and scan for the Nostr BLE service UUID
|
||||
2. **Role assignment** - The device with the higher UUID becomes the GATT Server (relay),
|
||||
the other becomes the GATT Client
|
||||
3. **Connection** - The client connects to the server's GATT service
|
||||
4. **Messaging** - NIP-01 JSON messages are DEFLATE-compressed, split into chunks,
|
||||
and exchanged via BLE characteristics
|
||||
5. **Event spreading** - New events are automatically forwarded to all connected peers
|
||||
|
||||
## Package Structure
|
||||
|
||||
```
|
||||
nipBEBle/
|
||||
BleNostrMesh.kt # Easy-to-use facade (start here)
|
||||
BleConfig.kt # NIP-BE constants (UUIDs, sizes)
|
||||
BlePeer.kt # Peer data model
|
||||
BleRole.kt # Role assignment (SERVER/CLIENT)
|
||||
|
||||
protocol/ # Wire format
|
||||
BleMessageChunker.kt # DEFLATE compression + chunk splitting/joining
|
||||
BleChunkAssembler.kt # Reassembles incoming chunks into messages
|
||||
|
||||
transport/ # Platform BLE abstraction
|
||||
BleTransport.kt # Interface for BLE operations
|
||||
AndroidBleTransport.kt # Android implementation (androidMain)
|
||||
|
||||
relay/ # Nostr relay protocol over BLE
|
||||
BleNostrClient.kt # Client side (implements IRelayClient)
|
||||
BleNostrServer.kt # Server side (receives commands, sends messages)
|
||||
BleMeshManager.kt # Orchestrates discovery, connections, broadcasting
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Use a BLE peer as a standard relay client
|
||||
|
||||
`BleNostrClient` implements `IRelayClient`, so you can use BLE peers alongside
|
||||
WebSocket relays in the existing subscription infrastructure:
|
||||
|
||||
```kotlin
|
||||
val relayClient = mesh.getRelayClient(peer.deviceUuid)
|
||||
relayClient?.sendIfConnected(ReqCmd("sub1", listOf(filter)))
|
||||
```
|
||||
|
||||
### Access raw protocol messages
|
||||
|
||||
```kotlin
|
||||
mesh.onMessage { relay, msg ->
|
||||
// Nostr messages received when acting as client (EVENT, EOSE, OK, etc.)
|
||||
}
|
||||
|
||||
mesh.onCommand { server, cmd ->
|
||||
// Nostr commands received when acting as server (REQ, EVENT, CLOSE, etc.)
|
||||
}
|
||||
```
|
||||
|
||||
### Implement a custom BLE transport
|
||||
|
||||
For platforms without a built-in transport, implement `BleTransport`:
|
||||
|
||||
```kotlin
|
||||
class MyPlatformBleTransport : BleTransport {
|
||||
override val deviceUuid = UUID.randomUUID().toString()
|
||||
override fun startAdvertising() { /* ... */ }
|
||||
override fun startScanning() { /* ... */ }
|
||||
// ... other methods
|
||||
}
|
||||
```
|
||||
|
||||
### Use BleMeshManager directly
|
||||
|
||||
For full control over the relay protocol layer:
|
||||
|
||||
```kotlin
|
||||
val transport = AndroidBleTransport(context)
|
||||
val manager = BleMeshManager(transport, object : BleMeshListener {
|
||||
override fun onEventReceived(event: Event, fromPeer: BlePeer) { /* ... */ }
|
||||
})
|
||||
transport.setListener(manager)
|
||||
manager.start()
|
||||
```
|
||||
|
||||
## Wire Protocol
|
||||
|
||||
Messages follow [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) JSON format,
|
||||
compressed with DEFLATE and split into chunks:
|
||||
|
||||
```
|
||||
[chunk index (1 byte)][payload (up to 500 bytes)][total chunks (1 byte)]
|
||||
```
|
||||
|
||||
| GATT Characteristic | UUID | Direction |
|
||||
|---------------------|------|-----------|
|
||||
| Write | `87654321-0000-1000-8000-00805f9b34fb` | Client -> Server |
|
||||
| Read (Notify) | `12345678-0000-1000-8000-00805f9b34fb` | Server -> Client |
|
||||
|
||||
Service UUID: `0000180f-0000-1000-8000-00805f9b34fb`
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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 com.vitorpamplona.quartz.nipBEBle
|
||||
package com.vitorpamplona.quartz.nipBEBle.protocol
|
||||
|
||||
/**
|
||||
* Accumulates incoming BLE chunks for a single message and reassembles
|
||||
+2
-1
@@ -18,8 +18,9 @@
|
||||
* 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 com.vitorpamplona.quartz.nipBEBle
|
||||
package com.vitorpamplona.quartz.nipBEBle.protocol
|
||||
|
||||
import com.vitorpamplona.quartz.nipBEBle.BleConfig
|
||||
import com.vitorpamplona.quartz.utils.Deflate
|
||||
|
||||
/**
|
||||
+7
-1
@@ -18,7 +18,7 @@
|
||||
* 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 com.vitorpamplona.quartz.nipBEBle
|
||||
package com.vitorpamplona.quartz.nipBEBle.relay
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener
|
||||
@@ -27,6 +27,12 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.EventCmd
|
||||
import com.vitorpamplona.quartz.nipBEBle.BlePeer
|
||||
import com.vitorpamplona.quartz.nipBEBle.BleRole
|
||||
import com.vitorpamplona.quartz.nipBEBle.assignRole
|
||||
import com.vitorpamplona.quartz.nipBEBle.protocol.BleMessageChunker
|
||||
import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
|
||||
import com.vitorpamplona.quartz.nipBEBle.transport.BleTransportListener
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
|
||||
/**
|
||||
+6
-1
@@ -18,13 +18,18 @@
|
||||
* 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 com.vitorpamplona.quartz.nipBEBle
|
||||
package com.vitorpamplona.quartz.nipBEBle.relay
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nipBEBle.BleConfig
|
||||
import com.vitorpamplona.quartz.nipBEBle.BlePeer
|
||||
import com.vitorpamplona.quartz.nipBEBle.protocol.BleChunkAssembler
|
||||
import com.vitorpamplona.quartz.nipBEBle.protocol.BleMessageChunker
|
||||
import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
|
||||
/**
|
||||
+6
-1
@@ -18,11 +18,16 @@
|
||||
* 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 com.vitorpamplona.quartz.nipBEBle
|
||||
package com.vitorpamplona.quartz.nipBEBle.relay
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
||||
import com.vitorpamplona.quartz.nipBEBle.BleConfig
|
||||
import com.vitorpamplona.quartz.nipBEBle.BlePeer
|
||||
import com.vitorpamplona.quartz.nipBEBle.protocol.BleChunkAssembler
|
||||
import com.vitorpamplona.quartz.nipBEBle.protocol.BleMessageChunker
|
||||
import com.vitorpamplona.quartz.nipBEBle.transport.BleTransport
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
|
||||
/**
|
||||
+3
-1
@@ -18,7 +18,9 @@
|
||||
* 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 com.vitorpamplona.quartz.nipBEBle
|
||||
package com.vitorpamplona.quartz.nipBEBle.transport
|
||||
|
||||
import com.vitorpamplona.quartz.nipBEBle.BlePeer
|
||||
|
||||
/**
|
||||
* Platform-agnostic interface for BLE transport operations.
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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 com.vitorpamplona.quartz.nipBEBle
|
||||
package com.vitorpamplona.quartz.nipBEBle.protocol
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
* 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 com.vitorpamplona.quartz.nipBEBle
|
||||
package com.vitorpamplona.quartz.nipBEBle.protocol
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
Reference in New Issue
Block a user