actr-framework 0.3.1

Actor-RTC framework core (stub for code generation testing)
Documentation
# actr-framework Utilities

This module provides optional utility functions, independent of the core framework interfaces.

## GeoIP Geolocation Lookup

Provides IP address to geographic coordinate conversion based on MaxMind GeoLite2 database.

### Quick Start

#### 1. Enable geoip feature

Add to `Cargo.toml`:

```toml
[dependencies]
actr-framework = { path = "../actr/crates/framework", features = ["geoip"] }
```

#### 2. Prepare GeoIP Database

**Automatic Download (Recommended):**

Automatically downloads on first call to `GeoIpService::new()` (requires environment variable):

```bash
# Get License Key: https://www.maxmind.com/en/geolite2/signup
export MAXMIND_LICENSE_KEY="your-key-here"

# Automatically downloads (~70MB, approx 30 seconds) on first run
cargo run --features geoip
```

**Manual Download (Production):**

```bash
curl -o GeoLite2-City.tar.gz \
  "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=YOUR_KEY&suffix=tar.gz"
tar -xzf GeoLite2-City.tar.gz --strip-components=1 -C data/geoip/ "*/GeoLite2-City.mmdb"
```

#### 3. Use in Actor

```rust
use actr_framework::util::geoip::GeoIpService;
use actr_protocol::{RegisterRequest, ServiceLocation};

// Initialize GeoIP service
let geoip = GeoIpService::new("data/geoip/GeoLite2-City.mmdb")?;

// Lookup coordinates for local IP
let my_ip = local_ip_address::local_ip()?;
let location = geoip.lookup(my_ip).map(|(lat, lon)| ServiceLocation {
    region: "auto-detected".to_string(),
    latitude: Some(lat),
    longitude: Some(lon),
});

// Provide coordinates during registration
let request = RegisterRequest {
    realm: Realm { realm_id: 1 },
    actr_type: my_type,
    geo_location: location,
    // ... other fields
};
```

### API Documentation

#### `GeoIpService::new(db_path)`

Initialize GeoIP service.

**Arguments:**
- `db_path` - Path to GeoLite2-City.mmdb database file

**Returns:**
- `Result<GeoIpService>` - Service instance on success, error on failure

**Errors:**
- Database file does not exist
- Database format error

#### `GeoIpService::lookup(ip)`

Lookup geographic coordinates for an IP address.

**Arguments:**
- `ip: IpAddr` - IP address to lookup

**Returns:**
- `Option<(f64, f64)>` - `(latitude, longitude)` on success, `None` on failure

**Notes:**
- Accuracy is city-level (50-100km error)
- Intranet IPs usually cannot be looked up
- Some public IPs may not be in the database

### Full Example

```rust
use actr_framework::util::geoip::GeoIpService;
use actr_framework::{Workload, Context};
use actr_protocol::*;
use anyhow::Result;

pub struct MyService {
    geoip: GeoIpService,
}

impl MyService {
    pub fn new() -> Result<Self> {
        let geoip = GeoIpService::new("data/geoip/GeoLite2-City.mmdb")?;
        Ok(Self { geoip })
    }

    fn get_my_location(&self) -> Option<ServiceLocation> {
        // Get local IP
        let my_ip = local_ip_address::local_ip().ok()?;

        // Lookup coordinates
        self.geoip.lookup(my_ip).map(|(lat, lon)| ServiceLocation {
            region: format!("auto-{}", my_ip),
            latitude: Some(lat),
            longitude: Some(lon),
        })
    }
}

#[async_trait]
impl Workload for MyService {
    type Dispatcher = MyServiceDispatcher;

    async fn on_start<C: Context>(&mut self, ctx: &C) -> ActorResult<()> {
        // Get coordinates
        let location = self.get_my_location();
        let actor_type = ctx.self_id().r#type;
        // Register to signaling service (with coordinates)
        let register_req = RegisterRequest {
            realm: Realm { realm_id: 1 },
            actr_type,
            geo_location: location,
            // ... other fields
        };

        // Send registration request
        // ctx.call(&signaling_dest, register_req).await?;

        Ok(())
    }
}
```

### Performance Characteristics

- **Database Size** - ~70MB (GeoLite2-City)
- **Lookup Latency** - In-memory lookup, < 1ms
- **Accuracy** - City-level, 50-100km error range
- **Coverage** - Most public IPs, intranet IPs cannot be looked up

### Troubleshooting

#### Database Load Failure

```
Error: Failed to open GeoIP database at "data/geoip/GeoLite2-City.mmdb"
```

**Solution:**
1. Confirm file exists: `ls -lh data/geoip/GeoLite2-City.mmdb`
2. Check file permissions: `chmod 644 data/geoip/GeoLite2-City.mmdb`
3. Set environment variable and rerun (auto download): `export MAXMIND_LICENSE_KEY="your-key" && cargo run --features geoip`
4. Or follow "Manual Download" steps above

#### IP Address Not Found

```
debug: GeoIP lookup: 192.168.1.1 not in database
```

**Explanation:**
- Intranet IPs (192.168.x.x, 10.x.x.x, 172.16-31.x.x) are not in the database
- Some public IPs may also not be in the database
- This is normal, returning `None` is expected

#### Inaccurate Coordinates

**Cause:** GeoLite2 accuracy is city-level (50-100km error)

**Solution:**
- Upgrade to GeoIP2 Precision (paid, accuracy up to 10km)
- Or provide accurate coordinates via configuration file
- Use GPS for mobile devices

### Feature Flag

GeoIP functionality is optional, controlled via feature flag:

```toml
# Default (without GeoIP)
actr-framework = { path = "..." }

# Enable GeoIP
actr-framework = { path = "...", features = ["geoip"] }
```

When `geoip` feature is disabled:
- `GeoIpService::new()` returns error
- `GeoIpService::lookup()` always returns `None`
- No dependency on `maxminddb` crate
- Reduces compilation time and binary size

### Best Practices

1. **Initialization Timing** - Initialize GeoIpService at Actor startup (not on every lookup)
2. **Error Handling** - Log database load failures but do not prevent Actor startup
3. **Fallback Strategy** - Use coordinates from configuration file on lookup failure
4. **Database Updates** - Update GeoLite2 database monthly (MaxMind updates weekly)

### Related Documentation

- MaxMind GeoLite2: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
- Signaling Service Geographic Load Balancing: `actrix-signaling/crates/signaling/README_GEOIP.md`