hive_btle/gatt/
service.rs

1// Copyright (c) 2025-2026 (r)evolve - Revolve Team LLC
2// SPDX-License-Identifier: Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! HIVE GATT Service Implementation
17//!
18//! Provides the GATT service structure and handlers for HIVE Protocol BLE communication.
19//!
20//! Note: This module requires the `std` feature for full functionality.
21
22use std::sync::{Arc, RwLock};
23
24use crate::error::Result;
25use crate::{HierarchyLevel, NodeId, HIVE_SERVICE_UUID};
26
27use super::characteristics::{
28    CharacteristicProperties, Command, CommandType, HiveCharacteristicUuids, NodeInfo, StatusData,
29    StatusFlags, SyncDataHeader, SyncDataOp, SyncState, SyncStateData,
30};
31
32/// GATT service event handler callback type
33pub type GattEventCallback = Box<dyn Fn(GattEvent) + Send + Sync>;
34
35/// Events emitted by the GATT service
36#[derive(Debug, Clone)]
37pub enum GattEvent {
38    /// Client connected
39    ClientConnected {
40        /// Client address
41        address: String,
42    },
43    /// Client disconnected
44    ClientDisconnected {
45        /// Client address
46        address: String,
47    },
48    /// Client subscribed to notifications
49    NotificationSubscribed {
50        /// Characteristic name
51        characteristic: String,
52    },
53    /// Client unsubscribed from notifications
54    NotificationUnsubscribed {
55        /// Characteristic name
56        characteristic: String,
57    },
58    /// Command received
59    CommandReceived {
60        /// Command that was received
61        command: CommandType,
62        /// Command payload
63        payload: Vec<u8>,
64    },
65    /// Sync data received
66    SyncDataReceived {
67        /// Sync data header
68        header: SyncDataHeader,
69        /// Sync data payload
70        payload: Vec<u8>,
71    },
72    /// MTU changed
73    MtuChanged {
74        /// New MTU value
75        mtu: u16,
76    },
77}
78
79/// GATT characteristic descriptor
80#[derive(Debug, Clone)]
81pub struct CharacteristicDescriptor {
82    /// Characteristic UUID
83    pub uuid: uuid::Uuid,
84    /// Human-readable name
85    pub name: &'static str,
86    /// Properties (read, write, notify, etc.)
87    pub properties: CharacteristicProperties,
88    /// Whether encryption is required
89    pub encrypted: bool,
90}
91
92/// Characteristic definitions for HIVE GATT service
93pub struct HiveCharacteristics;
94
95impl HiveCharacteristics {
96    /// Node Info characteristic descriptor
97    pub fn node_info() -> CharacteristicDescriptor {
98        CharacteristicDescriptor {
99            uuid: HiveCharacteristicUuids::node_info(),
100            name: "Node Info",
101            properties: CharacteristicProperties::new(CharacteristicProperties::READ),
102            encrypted: true,
103        }
104    }
105
106    /// Sync State characteristic descriptor
107    pub fn sync_state() -> CharacteristicDescriptor {
108        CharacteristicDescriptor {
109            uuid: HiveCharacteristicUuids::sync_state(),
110            name: "Sync State",
111            properties: CharacteristicProperties::new(
112                CharacteristicProperties::READ | CharacteristicProperties::NOTIFY,
113            ),
114            encrypted: true,
115        }
116    }
117
118    /// Sync Data characteristic descriptor
119    pub fn sync_data() -> CharacteristicDescriptor {
120        CharacteristicDescriptor {
121            uuid: HiveCharacteristicUuids::sync_data(),
122            name: "Sync Data",
123            properties: CharacteristicProperties::new(
124                CharacteristicProperties::WRITE | CharacteristicProperties::INDICATE,
125            ),
126            encrypted: true,
127        }
128    }
129
130    /// Command characteristic descriptor
131    pub fn command() -> CharacteristicDescriptor {
132        CharacteristicDescriptor {
133            uuid: HiveCharacteristicUuids::command(),
134            name: "Command",
135            properties: CharacteristicProperties::new(CharacteristicProperties::WRITE),
136            encrypted: true,
137        }
138    }
139
140    /// Status characteristic descriptor
141    pub fn status() -> CharacteristicDescriptor {
142        CharacteristicDescriptor {
143            uuid: HiveCharacteristicUuids::status(),
144            name: "Status",
145            properties: CharacteristicProperties::new(
146                CharacteristicProperties::READ | CharacteristicProperties::NOTIFY,
147            ),
148            encrypted: true,
149        }
150    }
151
152    /// Get all characteristic descriptors
153    pub fn all() -> Vec<CharacteristicDescriptor> {
154        vec![
155            Self::node_info(),
156            Self::sync_state(),
157            Self::sync_data(),
158            Self::command(),
159            Self::status(),
160        ]
161    }
162}
163
164/// Internal state for the GATT service
165struct ServiceState {
166    /// Node information
167    node_info: NodeInfo,
168    /// Current sync state
169    sync_state: SyncStateData,
170    /// Current status
171    status: StatusData,
172    /// Connected clients (addresses)
173    connected_clients: Vec<String>,
174    /// Clients subscribed to sync state notifications
175    sync_state_subscribers: Vec<String>,
176    /// Clients subscribed to status notifications
177    status_subscribers: Vec<String>,
178    /// Negotiated MTU
179    mtu: u16,
180}
181
182impl ServiceState {
183    fn new(node_id: NodeId, hierarchy_level: HierarchyLevel, capabilities: u16) -> Self {
184        Self {
185            node_info: NodeInfo::new(node_id, hierarchy_level, capabilities),
186            sync_state: SyncStateData::new(SyncState::Idle),
187            status: StatusData::new(),
188            connected_clients: Vec::new(),
189            sync_state_subscribers: Vec::new(),
190            status_subscribers: Vec::new(),
191            mtu: 23, // Default BLE MTU
192        }
193    }
194}
195
196/// HIVE GATT Service
197///
198/// Manages the GATT service lifecycle and provides handlers for characteristic operations.
199pub struct HiveGattService {
200    /// Service UUID
201    pub uuid: uuid::Uuid,
202    /// Internal state
203    state: Arc<RwLock<ServiceState>>,
204    /// Event callback
205    #[allow(dead_code)]
206    event_callback: Option<GattEventCallback>,
207}
208
209impl HiveGattService {
210    /// Create a new HIVE GATT service
211    pub fn new(node_id: NodeId, hierarchy_level: HierarchyLevel, capabilities: u16) -> Self {
212        Self {
213            uuid: HIVE_SERVICE_UUID,
214            state: Arc::new(RwLock::new(ServiceState::new(
215                node_id,
216                hierarchy_level,
217                capabilities,
218            ))),
219            event_callback: None,
220        }
221    }
222
223    /// Set event callback
224    pub fn set_event_callback(&mut self, callback: GattEventCallback) {
225        self.event_callback = Some(callback);
226    }
227
228    /// Get service UUID
229    pub fn service_uuid(&self) -> uuid::Uuid {
230        self.uuid
231    }
232
233    /// Get all characteristic descriptors
234    pub fn characteristics(&self) -> Vec<CharacteristicDescriptor> {
235        HiveCharacteristics::all()
236    }
237
238    // === Read Handlers ===
239
240    /// Handle Node Info read request
241    pub fn read_node_info(&self) -> Vec<u8> {
242        let state = self.state.read().unwrap();
243        state.node_info.encode().to_vec()
244    }
245
246    /// Handle Sync State read request
247    pub fn read_sync_state(&self) -> Vec<u8> {
248        let state = self.state.read().unwrap();
249        state.sync_state.encode().to_vec()
250    }
251
252    /// Handle Status read request
253    pub fn read_status(&self) -> Vec<u8> {
254        let state = self.state.read().unwrap();
255        state.status.encode().to_vec()
256    }
257
258    // === Write Handlers ===
259
260    /// Handle Sync Data write request
261    pub fn write_sync_data(&self, data: &[u8]) -> Result<Option<Vec<u8>>> {
262        let header = SyncDataHeader::decode(data).ok_or_else(|| {
263            crate::error::BleError::GattError("Invalid sync data header".to_string())
264        })?;
265
266        let payload = if data.len() > SyncDataHeader::SIZE {
267            data[SyncDataHeader::SIZE..].to_vec()
268        } else {
269            Vec::new()
270        };
271
272        // Process based on operation type
273        match header.op {
274            SyncDataOp::Document => {
275                // Update sync state to syncing
276                let mut state = self.state.write().unwrap();
277                state.sync_state.state = SyncState::Syncing;
278                state.status.flags =
279                    StatusFlags::new(state.status.flags.flags() | StatusFlags::SYNCING);
280
281                // Return acknowledgement
282                let ack = SyncDataHeader::new(SyncDataOp::Ack, header.seq);
283                Ok(Some(ack.encode().to_vec()))
284            }
285            SyncDataOp::Vector => {
286                // Sync vector update
287                let ack = SyncDataHeader::new(SyncDataOp::Ack, header.seq);
288                Ok(Some(ack.encode().to_vec()))
289            }
290            SyncDataOp::End => {
291                // Sync complete
292                let mut state = self.state.write().unwrap();
293                state.sync_state.state = SyncState::Complete;
294                state.sync_state.progress = 100;
295                state.status.flags =
296                    StatusFlags::new(state.status.flags.flags() & !StatusFlags::SYNCING);
297
298                // Emit event if callback set
299                if let Some(ref callback) = self.event_callback {
300                    callback(GattEvent::SyncDataReceived { header, payload });
301                }
302
303                Ok(None)
304            }
305            SyncDataOp::Ack => {
306                // Acknowledgement received (shouldn't happen on write)
307                Ok(None)
308            }
309        }
310    }
311
312    /// Handle Command write request
313    pub fn write_command(&self, data: &[u8]) -> Result<()> {
314        let command = Command::decode(data)
315            .ok_or_else(|| crate::error::BleError::GattError("Invalid command data".to_string()))?;
316
317        match command.cmd_type {
318            CommandType::StartSync => {
319                let mut state = self.state.write().unwrap();
320                state.sync_state.state = SyncState::Syncing;
321                state.sync_state.progress = 0;
322            }
323            CommandType::StopSync => {
324                let mut state = self.state.write().unwrap();
325                state.sync_state.state = SyncState::Idle;
326            }
327            CommandType::RefreshInfo => {
328                // Trigger info refresh (no-op for now)
329            }
330            CommandType::SetHierarchy => {
331                if !command.payload.is_empty() {
332                    let mut state = self.state.write().unwrap();
333                    state.node_info.hierarchy_level = HierarchyLevel::from(command.payload[0]);
334                }
335            }
336            CommandType::Ping => {
337                // Keepalive - no action needed
338            }
339            CommandType::Reset => {
340                let mut state = self.state.write().unwrap();
341                state.sync_state = SyncStateData::new(SyncState::Idle);
342            }
343        }
344
345        // Emit event if callback set
346        if let Some(ref callback) = self.event_callback {
347            callback(GattEvent::CommandReceived {
348                command: command.cmd_type,
349                payload: command.payload,
350            });
351        }
352
353        Ok(())
354    }
355
356    // === State Updates ===
357
358    /// Update battery percentage
359    pub fn update_battery(&self, percent: u8) {
360        let mut state = self.state.write().unwrap();
361        state.node_info.battery_percent = percent.min(100);
362
363        // Update low battery flag
364        if percent < 20 {
365            state.status.flags =
366                StatusFlags::new(state.status.flags.flags() | StatusFlags::LOW_BATTERY);
367        } else {
368            state.status.flags =
369                StatusFlags::new(state.status.flags.flags() & !StatusFlags::LOW_BATTERY);
370        }
371    }
372
373    /// Update hierarchy level
374    pub fn update_hierarchy_level(&self, level: HierarchyLevel) {
375        let mut state = self.state.write().unwrap();
376        state.node_info.hierarchy_level = level;
377    }
378
379    /// Update sync progress
380    pub fn update_sync_progress(&self, progress: u8, pending_docs: u16) {
381        let mut state = self.state.write().unwrap();
382        state.sync_state.progress = progress.min(100);
383        state.sync_state.pending_docs = pending_docs;
384
385        if progress >= 100 {
386            state.sync_state.state = SyncState::Complete;
387        }
388    }
389
390    /// Update parent connection status
391    pub fn update_parent_status(&self, connected: bool, rssi: Option<i8>) {
392        let mut state = self.state.write().unwrap();
393
394        if connected {
395            state.status.flags =
396                StatusFlags::new(state.status.flags.flags() | StatusFlags::CONNECTED);
397            state.status.parent_rssi = rssi.unwrap_or(0);
398        } else {
399            state.status.flags =
400                StatusFlags::new(state.status.flags.flags() & !StatusFlags::CONNECTED);
401            state.status.parent_rssi = 127; // No parent
402        }
403    }
404
405    /// Update child count
406    pub fn update_child_count(&self, count: u8) {
407        let mut state = self.state.write().unwrap();
408        state.status.child_count = count;
409    }
410
411    /// Update uptime
412    pub fn update_uptime(&self, minutes: u16) {
413        let mut state = self.state.write().unwrap();
414        state.status.uptime_minutes = minutes;
415    }
416
417    // === Connection Management ===
418
419    /// Handle client connection
420    pub fn on_client_connected(&self, address: String) {
421        let mut state = self.state.write().unwrap();
422        if !state.connected_clients.contains(&address) {
423            state.connected_clients.push(address.clone());
424        }
425
426        if let Some(ref callback) = self.event_callback {
427            callback(GattEvent::ClientConnected { address });
428        }
429    }
430
431    /// Handle client disconnection
432    pub fn on_client_disconnected(&self, address: &str) {
433        let mut state = self.state.write().unwrap();
434        state.connected_clients.retain(|a| a != address);
435        state.sync_state_subscribers.retain(|a| a != address);
436        state.status_subscribers.retain(|a| a != address);
437
438        if let Some(ref callback) = self.event_callback {
439            callback(GattEvent::ClientDisconnected {
440                address: address.to_string(),
441            });
442        }
443    }
444
445    /// Handle notification subscription
446    pub fn on_subscribe(&self, address: String, characteristic: &str) {
447        let mut state = self.state.write().unwrap();
448
449        match characteristic {
450            "sync_state" => {
451                if !state.sync_state_subscribers.contains(&address) {
452                    state.sync_state_subscribers.push(address);
453                }
454            }
455            "status" => {
456                if !state.status_subscribers.contains(&address) {
457                    state.status_subscribers.push(address);
458                }
459            }
460            _ => {}
461        }
462
463        if let Some(ref callback) = self.event_callback {
464            callback(GattEvent::NotificationSubscribed {
465                characteristic: characteristic.to_string(),
466            });
467        }
468    }
469
470    /// Handle MTU change
471    pub fn on_mtu_changed(&self, mtu: u16) {
472        let mut state = self.state.write().unwrap();
473        state.mtu = mtu;
474
475        if let Some(ref callback) = self.event_callback {
476            callback(GattEvent::MtuChanged { mtu });
477        }
478    }
479
480    /// Get current MTU
481    pub fn mtu(&self) -> u16 {
482        let state = self.state.read().unwrap();
483        state.mtu
484    }
485
486    /// Get connected client count
487    pub fn connected_client_count(&self) -> usize {
488        let state = self.state.read().unwrap();
489        state.connected_clients.len()
490    }
491
492    /// Get list of addresses subscribed to sync state
493    pub fn sync_state_subscribers(&self) -> Vec<String> {
494        let state = self.state.read().unwrap();
495        state.sync_state_subscribers.clone()
496    }
497
498    /// Get list of addresses subscribed to status
499    pub fn status_subscribers(&self) -> Vec<String> {
500        let state = self.state.read().unwrap();
501        state.status_subscribers.clone()
502    }
503}
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508    use crate::capabilities;
509
510    #[test]
511    fn test_gatt_service_creation() {
512        let service = HiveGattService::new(
513            NodeId::new(0x12345678),
514            HierarchyLevel::Squad,
515            capabilities::CAN_RELAY,
516        );
517
518        assert_eq!(service.service_uuid(), HIVE_SERVICE_UUID);
519        assert_eq!(service.characteristics().len(), 5);
520    }
521
522    #[test]
523    fn test_read_node_info() {
524        let service = HiveGattService::new(
525            NodeId::new(0x12345678),
526            HierarchyLevel::Squad,
527            capabilities::CAN_RELAY,
528        );
529
530        let data = service.read_node_info();
531        assert_eq!(data.len(), NodeInfo::ENCODED_SIZE);
532
533        let info = NodeInfo::decode(&data).unwrap();
534        assert_eq!(info.node_id, NodeId::new(0x12345678));
535        assert_eq!(info.hierarchy_level, HierarchyLevel::Squad);
536    }
537
538    #[test]
539    fn test_write_command() {
540        let service = HiveGattService::new(NodeId::new(0x12345678), HierarchyLevel::Platform, 0);
541
542        // Set hierarchy command
543        let cmd = Command::with_payload(CommandType::SetHierarchy, vec![2]); // Platoon
544        service.write_command(&cmd.encode()).unwrap();
545
546        let data = service.read_node_info();
547        let info = NodeInfo::decode(&data).unwrap();
548        assert_eq!(info.hierarchy_level, HierarchyLevel::Platoon);
549    }
550
551    #[test]
552    fn test_sync_data_flow() {
553        let service = HiveGattService::new(NodeId::new(0x12345678), HierarchyLevel::Platform, 0);
554
555        // Start sync
556        let cmd = Command::new(CommandType::StartSync);
557        service.write_command(&cmd.encode()).unwrap();
558
559        // Check sync state
560        let state_data = service.read_sync_state();
561        let state = SyncStateData::decode(&state_data).unwrap();
562        assert_eq!(state.state, SyncState::Syncing);
563
564        // Send document
565        let mut header = SyncDataHeader::new(SyncDataOp::Document, 1);
566        let mut data = header.encode().to_vec();
567        data.extend_from_slice(b"test document data");
568
569        let response = service.write_sync_data(&data).unwrap();
570        assert!(response.is_some()); // Should get ACK
571
572        // End sync
573        header = SyncDataHeader::new(SyncDataOp::End, 2);
574        service.write_sync_data(&header.encode()).unwrap();
575
576        // Check sync complete
577        let state_data = service.read_sync_state();
578        let state = SyncStateData::decode(&state_data).unwrap();
579        assert_eq!(state.state, SyncState::Complete);
580    }
581
582    #[test]
583    fn test_battery_update() {
584        let service = HiveGattService::new(NodeId::new(0x12345678), HierarchyLevel::Platform, 0);
585
586        service.update_battery(15);
587
588        let data = service.read_node_info();
589        let info = NodeInfo::decode(&data).unwrap();
590        assert_eq!(info.battery_percent, 15);
591
592        let status_data = service.read_status();
593        let status = StatusData::decode(&status_data).unwrap();
594        assert!(status.flags.is_low_battery());
595    }
596
597    #[test]
598    fn test_client_connection() {
599        let service = HiveGattService::new(NodeId::new(0x12345678), HierarchyLevel::Platform, 0);
600
601        service.on_client_connected("AA:BB:CC:DD:EE:FF".to_string());
602        assert_eq!(service.connected_client_count(), 1);
603
604        service.on_client_disconnected("AA:BB:CC:DD:EE:FF");
605        assert_eq!(service.connected_client_count(), 0);
606    }
607
608    #[test]
609    fn test_mtu_negotiation() {
610        let service = HiveGattService::new(NodeId::new(0x12345678), HierarchyLevel::Platform, 0);
611
612        assert_eq!(service.mtu(), 23); // Default
613
614        service.on_mtu_changed(251);
615        assert_eq!(service.mtu(), 251);
616    }
617
618    #[test]
619    fn test_hive_characteristics() {
620        let chars = HiveCharacteristics::all();
621        assert_eq!(chars.len(), 5);
622
623        let node_info = HiveCharacteristics::node_info();
624        assert!(node_info.properties.can_read());
625        assert!(!node_info.properties.can_write());
626
627        let sync_data = HiveCharacteristics::sync_data();
628        assert!(sync_data.properties.can_write());
629        assert!(sync_data.properties.can_indicate());
630    }
631}