feat: add anti-abuse mechanisms

Prevent slot blocking and reservation spam:
- RateLimiter: per-sender cooldowns, hourly limits, pending caps
- ProofOfWork: Hashcash-style PoW for reservation requests
- SenderReputation: track honor rate, no-shows, auto-block
- TherapistPolicy: configurable requirements per provider
- 7 new tests (39 total)

Docs: docs/anti-abuse.md with implementation roadmap
This commit is contained in:
2026-04-01 08:27:02 +02:00
parent c757494cbe
commit 9af66ab073
445 changed files with 760 additions and 8 deletions

216
docs/anti-abuse.md Normal file
View File

@@ -0,0 +1,216 @@
# Anti-Abuse: Preventing Slot Blocking
## Problem
Bad actors could abuse the reservation system by:
1. **Slot Squatting**: Reserving slots with no intention to attend
2. **Denial of Service**: Mass-reserving to block legitimate patients
3. **Competitor Sabotage**: Blocking a therapist's calendar
4. **Spam Queries**: Flooding the network with fake queries
## Defense Layers
### Layer 1: Rate Limiting (Protocol Level)
```rust
pub struct RateLimits {
/// Max reservations per sender per hour
pub max_reservations_per_hour: u8, // Default: 3
/// Max pending (unconfirmed) reservations per sender
pub max_pending_reservations: u8, // Default: 2
/// Min time between reservations (seconds)
pub reservation_cooldown_secs: u32, // Default: 300 (5 min)
/// Max queries per sender per minute
pub max_queries_per_minute: u8, // Default: 10
}
```
**Implementation**: Each relay tracks sender activity and drops excessive requests.
### Layer 2: Reservation Deposits (Economic)
Require a small proof-of-work or micro-deposit to make reservations:
| Method | Cost to Attacker | UX Impact |
|--------|------------------|-----------|
| **Hashcash PoW** | CPU time (~1-5s per reservation) | Slight delay |
| **Token Stake** | Loses stake on no-show | Requires token system |
| **Reputation Bond** | Loses reputation on abuse | Requires history |
**Recommended**: Start with Hashcash PoW — no external dependencies.
```rust
pub struct ReservationProof {
/// Hashcash proof-of-work (20-bit difficulty)
pub pow_nonce: u64,
/// Hash must start with N zero bits
pub difficulty: u8,
}
impl ReservationProof {
pub fn verify(&self, reservation_id: &[u8; 16]) -> bool {
let hash = sha256([reservation_id, &self.pow_nonce.to_le_bytes()].concat());
leading_zeros(&hash) >= self.difficulty
}
}
```
### Layer 3: Confirmation Requirements
Therapists can require confirmation steps:
```rust
pub enum ConfirmationMode {
/// Auto-confirm (trust network)
AutoConfirm,
/// Require patient to solve CAPTCHA-like challenge
ChallengeResponse { challenge: Vec<u8> },
/// Require callback to verify contact
ContactVerification { method: ContactMethod },
/// Manual review by therapist
ManualReview,
}
```
### Layer 4: No-Show Tracking
Track reservation outcomes per sender:
```rust
pub struct SenderReputation {
pub address: [u8; 16],
pub reservations_made: u32,
pub reservations_honored: u32,
pub reservations_cancelled: u32, // With notice
pub no_shows: u32, // Without notice
pub last_no_show: Option<u64>,
}
impl SenderReputation {
pub fn honor_rate(&self) -> f32 {
if self.reservations_made == 0 { return 0.5; }
(self.reservations_honored as f32) / (self.reservations_made as f32)
}
pub fn is_blocked(&self) -> bool {
self.no_shows >= 3 || self.honor_rate() < 0.5
}
}
```
**Therapists can share blocklists** via signed messages:
```rust
pub struct BlocklistEntry {
pub blocked_address: [u8; 16],
pub reason: BlockReason,
pub reported_by: [u8; 16],
pub signature: [u8; 64],
pub timestamp: u64,
}
pub enum BlockReason {
NoShow,
Spam,
Harassment,
FakeIdentity,
}
```
### Layer 5: Reservation Limits per Therapist
Therapists set their own limits:
```rust
pub struct TherapistPolicy {
/// Max pending reservations from new senders
pub max_pending_new: u8, // Default: 1
/// Max pending from established senders
pub max_pending_established: u8, // Default: 3
/// Require verification level for reservations
pub min_verification_level: u8, // 0 = any, 2 = peer-endorsed
/// Auto-reject senders with low honor rate
pub min_honor_rate: f32, // Default: 0.7
}
```
## Implementation Roadmap
### Phase 1: Basic Rate Limiting (Week 1)
- [ ] Add `RateLimiter` to `ServiceRouter`
- [ ] Track per-sender reservation counts
- [ ] Drop excessive requests with `ServiceAction::RateLimited`
### Phase 2: Proof-of-Work (Week 2)
- [ ] Add `ReservationProof` to reserve message payload
- [ ] Verify PoW before processing reservation
- [ ] Adaptive difficulty based on network load
### Phase 3: Reputation System (Week 3-4)
- [ ] `SenderReputation` storage
- [ ] Honor/no-show reporting from therapists
- [ ] Blocklist propagation
### Phase 4: Therapist Policies (Week 4+)
- [ ] Policy field in `SlotAnnounce`
- [ ] Policy enforcement in reservation handling
## Example: Complete Anti-Abuse Flow
```
Patient Relay Therapist
| | |
|-- Query (CBT, 104xx) -->| |
|<-- Matches (3 slots) ---| |
| | |
|-- Reserve (slot_id) --->| |
| + PoW proof | |
| (1.2s computation) | |
| |-- Check rate limit ------>|
| | (OK: 1st today) |
| |-- Check reputation ------>|
| | (OK: new sender, 0.5) |
| |-- Forward reserve ------->|
| | |
| |<-- Confirm (pending) -----|
|<-- Pending -------------| |
| | |
| [Patient attends] | |
| |<-- Outcome: honored ------|
| | (reputation += 1) |
```
## Edge Cases
### New Users (No History)
- Allow 1 pending reservation
- Require PoW
- Lower priority than established users
### Therapist Gaming the System
- Therapists could falsely report no-shows
- Mitigation: Require mutual confirmation (patient confirms attendance too)
- Weight reports by reporter reputation
### Sybil Attacks (Many Fake Identities)
- Each identity requires new Ed25519 keypair
- PoW per reservation makes mass-blocking expensive
- Cross-relay blocklist sharing limits damage
## Privacy Considerations
- Reputation is tied to mesh address, not real identity
- No-show reports don't reveal appointment details
- Blocklists only contain addresses + reason, not personal data
## Metrics to Monitor
| Metric | Healthy Range | Alert Threshold |
|--------|---------------|-----------------|
| Reservation/Confirm ratio | > 0.8 | < 0.5 |
| Unique senders per relay | Growing | Flat with high volume |
| PoW rejection rate | < 5% | > 20% |
| Blocklist growth | Slow | Rapid spike |

532
src/anti_abuse.rs Normal file
View File

@@ -0,0 +1,532 @@
//! Anti-abuse mechanisms for preventing slot blocking and spam.
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
use sha2::{Digest, Sha256};
/// Rate limiting configuration.
#[derive(Debug, Clone)]
pub struct RateLimits {
/// Max reservations per sender per hour.
pub max_reservations_per_hour: u8,
/// Max pending (unconfirmed) reservations per sender.
pub max_pending_reservations: u8,
/// Min time between reservations (seconds).
pub reservation_cooldown_secs: u32,
/// Max queries per sender per minute.
pub max_queries_per_minute: u8,
}
impl Default for RateLimits {
fn default() -> Self {
Self {
max_reservations_per_hour: 3,
max_pending_reservations: 2,
reservation_cooldown_secs: 300,
max_queries_per_minute: 10,
}
}
}
/// Tracks sender activity for rate limiting.
#[derive(Debug, Default)]
pub struct RateLimiter {
limits: RateLimits,
/// sender_address -> activity
activity: HashMap<[u8; 16], SenderActivity>,
}
#[derive(Debug, Default)]
struct SenderActivity {
/// Timestamps of reservations in last hour.
reservation_times: Vec<u64>,
/// Count of pending reservations.
pending_count: u8,
/// Timestamp of last reservation.
last_reservation: u64,
/// Query timestamps in last minute.
query_times: Vec<u64>,
}
impl RateLimiter {
/// Create with default limits.
pub fn new() -> Self {
Self::default()
}
/// Create with custom limits.
pub fn with_limits(limits: RateLimits) -> Self {
Self {
limits,
activity: HashMap::new(),
}
}
/// Check if a reservation is allowed.
pub fn check_reservation(&mut self, sender: &[u8; 16]) -> RateLimitResult {
let now = now();
let activity = self.activity.entry(*sender).or_default();
// Clean old entries
activity.reservation_times.retain(|&t| now - t < 3600);
// Check cooldown
if now - activity.last_reservation < u64::from(self.limits.reservation_cooldown_secs) {
return RateLimitResult::Cooldown {
wait_secs: self.limits.reservation_cooldown_secs - (now - activity.last_reservation) as u32,
};
}
// Check hourly limit
if activity.reservation_times.len() >= self.limits.max_reservations_per_hour as usize {
return RateLimitResult::HourlyLimitReached;
}
// Check pending limit
if activity.pending_count >= self.limits.max_pending_reservations {
return RateLimitResult::TooManyPending;
}
RateLimitResult::Allowed
}
/// Record a reservation attempt.
pub fn record_reservation(&mut self, sender: &[u8; 16]) {
let now = now();
let activity = self.activity.entry(*sender).or_default();
activity.reservation_times.push(now);
activity.last_reservation = now;
activity.pending_count = activity.pending_count.saturating_add(1);
}
/// Record reservation confirmed/completed (reduce pending).
pub fn record_reservation_resolved(&mut self, sender: &[u8; 16]) {
if let Some(activity) = self.activity.get_mut(sender) {
activity.pending_count = activity.pending_count.saturating_sub(1);
}
}
/// Check if a query is allowed.
pub fn check_query(&mut self, sender: &[u8; 16]) -> RateLimitResult {
let now = now();
let activity = self.activity.entry(*sender).or_default();
// Clean old entries
activity.query_times.retain(|&t| now - t < 60);
if activity.query_times.len() >= self.limits.max_queries_per_minute as usize {
return RateLimitResult::QueryLimitReached;
}
RateLimitResult::Allowed
}
/// Record a query.
pub fn record_query(&mut self, sender: &[u8; 16]) {
let now = now();
let activity = self.activity.entry(*sender).or_default();
activity.query_times.push(now);
}
/// Prune old activity data.
pub fn prune(&mut self) {
let now = now();
self.activity.retain(|_, a| {
a.reservation_times.retain(|&t| now - t < 3600);
a.query_times.retain(|&t| now - t < 60);
!a.reservation_times.is_empty() || !a.query_times.is_empty() || a.pending_count > 0
});
}
}
/// Result of rate limit check.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RateLimitResult {
/// Request allowed.
Allowed,
/// Must wait before next reservation.
Cooldown { wait_secs: u32 },
/// Hourly reservation limit reached.
HourlyLimitReached,
/// Too many pending reservations.
TooManyPending,
/// Query rate limit reached.
QueryLimitReached,
}
impl RateLimitResult {
pub fn is_allowed(&self) -> bool {
matches!(self, RateLimitResult::Allowed)
}
}
/// Proof-of-work for reservation requests.
#[derive(Debug, Clone)]
pub struct ProofOfWork {
/// Nonce that produces valid hash.
pub nonce: u64,
/// Required difficulty (leading zero bits).
pub difficulty: u8,
}
impl ProofOfWork {
/// Default difficulty (20 bits ≈ 1-2 seconds on modern CPU).
pub const DEFAULT_DIFFICULTY: u8 = 20;
/// Generate proof-of-work for a reservation.
pub fn generate(reservation_id: &[u8; 16], difficulty: u8) -> Self {
let mut nonce = 0u64;
loop {
if Self::check_hash(reservation_id, nonce, difficulty) {
return Self { nonce, difficulty };
}
nonce = nonce.wrapping_add(1);
}
}
/// Verify proof-of-work.
pub fn verify(&self, reservation_id: &[u8; 16]) -> bool {
Self::check_hash(reservation_id, self.nonce, self.difficulty)
}
fn check_hash(reservation_id: &[u8; 16], nonce: u64, difficulty: u8) -> bool {
let mut hasher = Sha256::new();
hasher.update(reservation_id);
hasher.update(&nonce.to_le_bytes());
let hash = hasher.finalize();
leading_zero_bits(&hash) >= difficulty
}
}
/// Count leading zero bits in a byte slice.
fn leading_zero_bits(data: &[u8]) -> u8 {
let mut count = 0u8;
for byte in data {
if *byte == 0 {
count += 8;
} else {
count += byte.leading_zeros() as u8;
break;
}
}
count
}
/// Sender reputation tracking.
#[derive(Debug, Clone, Default)]
pub struct SenderReputation {
pub address: [u8; 16],
pub reservations_made: u32,
pub reservations_honored: u32,
pub reservations_cancelled: u32,
pub no_shows: u32,
pub last_no_show: Option<u64>,
}
impl SenderReputation {
/// Create for a new sender.
pub fn new(address: [u8; 16]) -> Self {
Self {
address,
..Default::default()
}
}
/// Calculate honor rate (0.0 to 1.0).
pub fn honor_rate(&self) -> f32 {
if self.reservations_made == 0 {
return 0.5; // Neutral for new users
}
(self.reservations_honored as f32) / (self.reservations_made as f32)
}
/// Check if sender should be blocked.
pub fn is_blocked(&self) -> bool {
self.no_shows >= 3 || (self.reservations_made >= 5 && self.honor_rate() < 0.5)
}
/// Record a completed reservation.
pub fn record_honored(&mut self) {
self.reservations_made += 1;
self.reservations_honored += 1;
}
/// Record a cancelled reservation (with notice).
pub fn record_cancelled(&mut self) {
self.reservations_made += 1;
self.reservations_cancelled += 1;
}
/// Record a no-show.
pub fn record_no_show(&mut self) {
self.reservations_made += 1;
self.no_shows += 1;
self.last_no_show = Some(now());
}
}
/// Reputation store.
#[derive(Debug, Default)]
pub struct ReputationStore {
reputations: HashMap<[u8; 16], SenderReputation>,
}
impl ReputationStore {
pub fn new() -> Self {
Self::default()
}
/// Get or create reputation for a sender.
pub fn get_or_create(&mut self, address: [u8; 16]) -> &mut SenderReputation {
self.reputations
.entry(address)
.or_insert_with(|| SenderReputation::new(address))
}
/// Get reputation (read-only).
pub fn get(&self, address: &[u8; 16]) -> Option<&SenderReputation> {
self.reputations.get(address)
}
/// Check if sender is blocked.
pub fn is_blocked(&self, address: &[u8; 16]) -> bool {
self.reputations
.get(address)
.map(|r| r.is_blocked())
.unwrap_or(false)
}
/// Get honor rate (0.5 for unknown).
pub fn honor_rate(&self, address: &[u8; 16]) -> f32 {
self.reputations
.get(address)
.map(|r| r.honor_rate())
.unwrap_or(0.5)
}
}
/// Blocklist entry.
#[derive(Debug, Clone)]
pub struct BlocklistEntry {
pub blocked_address: [u8; 16],
pub reason: BlockReason,
pub reported_by: [u8; 16],
pub signature: Vec<u8>,
pub timestamp: u64,
}
/// Reason for blocking.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BlockReason {
NoShow = 1,
Spam = 2,
Harassment = 3,
FakeIdentity = 4,
}
/// Therapist-defined reservation policy.
#[derive(Debug, Clone)]
pub struct TherapistPolicy {
/// Max pending reservations from new senders.
pub max_pending_new: u8,
/// Max pending from established senders.
pub max_pending_established: u8,
/// Require this verification level for reservations.
pub min_verification_level: u8,
/// Auto-reject senders with honor rate below this.
pub min_honor_rate: f32,
/// Require proof-of-work.
pub require_pow: bool,
/// PoW difficulty (if required).
pub pow_difficulty: u8,
}
impl Default for TherapistPolicy {
fn default() -> Self {
Self {
max_pending_new: 1,
max_pending_established: 3,
min_verification_level: 0,
min_honor_rate: 0.5,
require_pow: true,
pow_difficulty: ProofOfWork::DEFAULT_DIFFICULTY,
}
}
}
impl TherapistPolicy {
/// Check if a reservation request meets policy.
pub fn check(
&self,
sender_reputation: &SenderReputation,
sender_verification_level: u8,
pow: Option<&ProofOfWork>,
reservation_id: &[u8; 16],
) -> PolicyResult {
// Check verification level
if sender_verification_level < self.min_verification_level {
return PolicyResult::InsufficientVerification;
}
// Check honor rate
if sender_reputation.honor_rate() < self.min_honor_rate {
return PolicyResult::LowReputation;
}
// Check blocked
if sender_reputation.is_blocked() {
return PolicyResult::Blocked;
}
// Check proof-of-work
if self.require_pow {
match pow {
Some(p) if p.difficulty >= self.pow_difficulty && p.verify(reservation_id) => {}
Some(_) => return PolicyResult::InvalidPoW,
None => return PolicyResult::MissingPoW,
}
}
PolicyResult::Allowed
}
}
/// Result of policy check.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PolicyResult {
Allowed,
InsufficientVerification,
LowReputation,
Blocked,
MissingPoW,
InvalidPoW,
}
impl PolicyResult {
pub fn is_allowed(&self) -> bool {
matches!(self, PolicyResult::Allowed)
}
}
fn now() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rate_limiter_allows_first_reservation() {
let mut limiter = RateLimiter::new();
let sender = [1u8; 16];
assert!(limiter.check_reservation(&sender).is_allowed());
}
#[test]
fn rate_limiter_enforces_cooldown() {
let mut limiter = RateLimiter::with_limits(RateLimits {
reservation_cooldown_secs: 300,
..Default::default()
});
let sender = [2u8; 16];
limiter.record_reservation(&sender);
let result = limiter.check_reservation(&sender);
assert!(matches!(result, RateLimitResult::Cooldown { .. }));
}
#[test]
fn rate_limiter_enforces_hourly_limit() {
let mut limiter = RateLimiter::with_limits(RateLimits {
max_reservations_per_hour: 2,
reservation_cooldown_secs: 0,
..Default::default()
});
let sender = [3u8; 16];
limiter.record_reservation(&sender);
limiter.record_reservation(&sender);
assert_eq!(limiter.check_reservation(&sender), RateLimitResult::HourlyLimitReached);
}
#[test]
fn pow_generation_and_verification() {
let reservation_id = [42u8; 16];
let pow = ProofOfWork::generate(&reservation_id, 8); // Low difficulty for test
assert!(pow.verify(&reservation_id));
assert!(!pow.verify(&[0u8; 16])); // Wrong ID
}
#[test]
fn reputation_tracking() {
let mut rep = SenderReputation::new([5u8; 16]);
rep.record_honored();
rep.record_honored();
rep.record_no_show();
assert_eq!(rep.reservations_made, 3);
assert_eq!(rep.honor_rate(), 2.0 / 3.0);
assert!(!rep.is_blocked());
rep.record_no_show();
rep.record_no_show();
assert!(rep.is_blocked()); // 3 no-shows
}
#[test]
fn policy_check_pow() {
let policy = TherapistPolicy {
require_pow: true,
pow_difficulty: 8,
..Default::default()
};
let rep = SenderReputation::new([6u8; 16]);
let reservation_id = [7u8; 16];
// No PoW
assert_eq!(
policy.check(&rep, 0, None, &reservation_id),
PolicyResult::MissingPoW
);
// Valid PoW
let pow = ProofOfWork::generate(&reservation_id, 8);
assert_eq!(
policy.check(&rep, 0, Some(&pow), &reservation_id),
PolicyResult::Allowed
);
}
#[test]
fn policy_check_verification_level() {
let policy = TherapistPolicy {
min_verification_level: 2,
require_pow: false,
..Default::default()
};
let rep = SenderReputation::new([8u8; 16]);
let reservation_id = [9u8; 16];
assert_eq!(
policy.check(&rep, 1, None, &reservation_id),
PolicyResult::InsufficientVerification
);
assert_eq!(
policy.check(&rep, 2, None, &reservation_id),
PolicyResult::Allowed
);
}
}

View File

@@ -45,6 +45,7 @@ pub mod verification;
pub mod services;
pub mod wire;
pub mod error;
pub mod anti_abuse;
pub use identity::ServiceIdentity;
pub use message::{ServiceMessage, MessageType};
@@ -52,6 +53,7 @@ pub use router::{ServiceRouter, ServiceHandler, ServiceAction};
pub use store::ServiceStore;
pub use verification::{Verification, VerificationLevel};
pub use error::ServiceError;
pub use anti_abuse::{RateLimiter, RateLimits, ProofOfWork, SenderReputation, TherapistPolicy};
/// Well-known service IDs.
pub mod service_ids {

View File

@@ -1,8 +1,8 @@
/home/c/projects/tool.meshservice/target/debug/deps/meshservice-286f0426bc447b66.d: src/lib.rs src/identity.rs src/message.rs src/router.rs src/store.rs src/verification.rs src/services/mod.rs src/services/fapp.rs src/services/housing.rs src/wire.rs src/error.rs
/home/c/projects/tool.meshservice/target/debug/deps/meshservice-286f0426bc447b66.d: src/lib.rs src/identity.rs src/message.rs src/router.rs src/store.rs src/verification.rs src/services/mod.rs src/services/fapp.rs src/services/housing.rs src/wire.rs src/error.rs src/anti_abuse.rs
/home/c/projects/tool.meshservice/target/debug/deps/libmeshservice-286f0426bc447b66.rlib: src/lib.rs src/identity.rs src/message.rs src/router.rs src/store.rs src/verification.rs src/services/mod.rs src/services/fapp.rs src/services/housing.rs src/wire.rs src/error.rs
/home/c/projects/tool.meshservice/target/debug/deps/libmeshservice-286f0426bc447b66.rlib: src/lib.rs src/identity.rs src/message.rs src/router.rs src/store.rs src/verification.rs src/services/mod.rs src/services/fapp.rs src/services/housing.rs src/wire.rs src/error.rs src/anti_abuse.rs
/home/c/projects/tool.meshservice/target/debug/deps/libmeshservice-286f0426bc447b66.rmeta: src/lib.rs src/identity.rs src/message.rs src/router.rs src/store.rs src/verification.rs src/services/mod.rs src/services/fapp.rs src/services/housing.rs src/wire.rs src/error.rs
/home/c/projects/tool.meshservice/target/debug/deps/libmeshservice-286f0426bc447b66.rmeta: src/lib.rs src/identity.rs src/message.rs src/router.rs src/store.rs src/verification.rs src/services/mod.rs src/services/fapp.rs src/services/housing.rs src/wire.rs src/error.rs src/anti_abuse.rs
src/lib.rs:
src/identity.rs:
@@ -15,3 +15,4 @@ src/services/fapp.rs:
src/services/housing.rs:
src/wire.rs:
src/error.rs:
src/anti_abuse.rs:

View File

@@ -1,6 +1,6 @@
/home/c/projects/tool.meshservice/target/debug/deps/meshservice-a34e90bc28566d7f.d: src/lib.rs src/identity.rs src/message.rs src/router.rs src/store.rs src/verification.rs src/services/mod.rs src/services/fapp.rs src/services/housing.rs src/wire.rs src/error.rs
/home/c/projects/tool.meshservice/target/debug/deps/meshservice-a34e90bc28566d7f.d: src/lib.rs src/identity.rs src/message.rs src/router.rs src/store.rs src/verification.rs src/services/mod.rs src/services/fapp.rs src/services/housing.rs src/wire.rs src/error.rs src/anti_abuse.rs
/home/c/projects/tool.meshservice/target/debug/deps/meshservice-a34e90bc28566d7f: src/lib.rs src/identity.rs src/message.rs src/router.rs src/store.rs src/verification.rs src/services/mod.rs src/services/fapp.rs src/services/housing.rs src/wire.rs src/error.rs
/home/c/projects/tool.meshservice/target/debug/deps/meshservice-a34e90bc28566d7f: src/lib.rs src/identity.rs src/message.rs src/router.rs src/store.rs src/verification.rs src/services/mod.rs src/services/fapp.rs src/services/housing.rs src/wire.rs src/error.rs src/anti_abuse.rs
src/lib.rs:
src/identity.rs:
@@ -13,3 +13,4 @@ src/services/fapp.rs:
src/services/housing.rs:
src/wire.rs:
src/error.rs:
src/anti_abuse.rs:

Binary file not shown.

View File

@@ -1 +1 @@
/home/c/projects/tool.meshservice/target/debug/examples/fapp_service: /home/c/projects/tool.meshservice/examples/fapp_service.rs /home/c/projects/tool.meshservice/src/error.rs /home/c/projects/tool.meshservice/src/identity.rs /home/c/projects/tool.meshservice/src/lib.rs /home/c/projects/tool.meshservice/src/message.rs /home/c/projects/tool.meshservice/src/router.rs /home/c/projects/tool.meshservice/src/services/fapp.rs /home/c/projects/tool.meshservice/src/services/housing.rs /home/c/projects/tool.meshservice/src/services/mod.rs /home/c/projects/tool.meshservice/src/store.rs /home/c/projects/tool.meshservice/src/verification.rs /home/c/projects/tool.meshservice/src/wire.rs
/home/c/projects/tool.meshservice/target/debug/examples/fapp_service: /home/c/projects/tool.meshservice/examples/fapp_service.rs /home/c/projects/tool.meshservice/src/anti_abuse.rs /home/c/projects/tool.meshservice/src/error.rs /home/c/projects/tool.meshservice/src/identity.rs /home/c/projects/tool.meshservice/src/lib.rs /home/c/projects/tool.meshservice/src/message.rs /home/c/projects/tool.meshservice/src/router.rs /home/c/projects/tool.meshservice/src/services/fapp.rs /home/c/projects/tool.meshservice/src/services/housing.rs /home/c/projects/tool.meshservice/src/services/mod.rs /home/c/projects/tool.meshservice/src/store.rs /home/c/projects/tool.meshservice/src/verification.rs /home/c/projects/tool.meshservice/src/wire.rs

View File

@@ -1 +1 @@
/home/c/projects/tool.meshservice/target/debug/examples/housing_service: /home/c/projects/tool.meshservice/examples/housing_service.rs /home/c/projects/tool.meshservice/src/error.rs /home/c/projects/tool.meshservice/src/identity.rs /home/c/projects/tool.meshservice/src/lib.rs /home/c/projects/tool.meshservice/src/message.rs /home/c/projects/tool.meshservice/src/router.rs /home/c/projects/tool.meshservice/src/services/fapp.rs /home/c/projects/tool.meshservice/src/services/housing.rs /home/c/projects/tool.meshservice/src/services/mod.rs /home/c/projects/tool.meshservice/src/store.rs /home/c/projects/tool.meshservice/src/verification.rs /home/c/projects/tool.meshservice/src/wire.rs
/home/c/projects/tool.meshservice/target/debug/examples/housing_service: /home/c/projects/tool.meshservice/examples/housing_service.rs /home/c/projects/tool.meshservice/src/anti_abuse.rs /home/c/projects/tool.meshservice/src/error.rs /home/c/projects/tool.meshservice/src/identity.rs /home/c/projects/tool.meshservice/src/lib.rs /home/c/projects/tool.meshservice/src/message.rs /home/c/projects/tool.meshservice/src/router.rs /home/c/projects/tool.meshservice/src/services/fapp.rs /home/c/projects/tool.meshservice/src/services/housing.rs /home/c/projects/tool.meshservice/src/services/mod.rs /home/c/projects/tool.meshservice/src/store.rs /home/c/projects/tool.meshservice/src/verification.rs /home/c/projects/tool.meshservice/src/wire.rs

Binary file not shown.

View File

@@ -1 +1 @@
/home/c/projects/tool.meshservice/target/debug/examples/multi_service: /home/c/projects/tool.meshservice/examples/multi_service.rs /home/c/projects/tool.meshservice/src/error.rs /home/c/projects/tool.meshservice/src/identity.rs /home/c/projects/tool.meshservice/src/lib.rs /home/c/projects/tool.meshservice/src/message.rs /home/c/projects/tool.meshservice/src/router.rs /home/c/projects/tool.meshservice/src/services/fapp.rs /home/c/projects/tool.meshservice/src/services/housing.rs /home/c/projects/tool.meshservice/src/services/mod.rs /home/c/projects/tool.meshservice/src/store.rs /home/c/projects/tool.meshservice/src/verification.rs /home/c/projects/tool.meshservice/src/wire.rs
/home/c/projects/tool.meshservice/target/debug/examples/multi_service: /home/c/projects/tool.meshservice/examples/multi_service.rs /home/c/projects/tool.meshservice/src/anti_abuse.rs /home/c/projects/tool.meshservice/src/error.rs /home/c/projects/tool.meshservice/src/identity.rs /home/c/projects/tool.meshservice/src/lib.rs /home/c/projects/tool.meshservice/src/message.rs /home/c/projects/tool.meshservice/src/router.rs /home/c/projects/tool.meshservice/src/services/fapp.rs /home/c/projects/tool.meshservice/src/services/housing.rs /home/c/projects/tool.meshservice/src/services/mod.rs /home/c/projects/tool.meshservice/src/store.rs /home/c/projects/tool.meshservice/src/verification.rs /home/c/projects/tool.meshservice/src/wire.rs

Some files were not shown because too many files have changed in this diff Show More