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
|
* 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.
|
* 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.annotation.SuppressLint
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
@@ -41,6 +41,10 @@ import android.bluetooth.le.ScanResult
|
|||||||
import android.bluetooth.le.ScanSettings
|
import android.bluetooth.le.ScanSettings
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.ParcelUuid
|
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 com.vitorpamplona.quartz.utils.Log
|
||||||
import java.util.UUID
|
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.client.single.IRelayClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
|
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.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.
|
* 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
|
* 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.
|
* 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
|
* 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
|
* 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.
|
* 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
|
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
|
* 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.
|
* 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.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener
|
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.toClient.Message
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.EventCmd
|
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
|
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
|
* 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.
|
* 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.core.OptimizedJsonMapper
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener
|
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
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
|
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
|
* 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.
|
* 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.core.OptimizedJsonMapper
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
|
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.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
|
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
|
* 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.
|
* 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.
|
* 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
|
* 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.
|
* 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.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* 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.
|
* 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.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
Reference in New Issue
Block a user