Files
adif-api-reverse-engineering/README.md

238 lines
6.4 KiB
Markdown

# ADIF API - Reverse Engineering
Complete Python client to access the ADIF API (El Cano Móvil) through reverse engineering.
**All 9/9 endpoints functional** · HMAC-SHA256 authentication implemented · 1587 station codes extracted
---
## Quick Start
```bash
# Install dependencies
pip install requests
# Run demo
python3 adif_client.py
```
### Basic Usage
```python
from adif_client import AdifClient
# Initialize client
client = AdifClient(
access_key="and20210615",
secret_key="Jthjtr946RTt"
)
# Get departures from Madrid Atocha
trains = client.get_departures("10200", "AVLDMD")
for train in trains:
info = train['commercialPathInfo']
print(f"Train {info['commercialPathKey']['commercialCirculationKey']['commercialNumber']}")
# Get complete train route
route = client.get_train_route(
commercial_number="03194",
launching_date=1764889200000,
origin_station_code="10200",
destination_station_code="71801"
)
```
---
## Available Endpoints (9/9 working)
| Endpoint | Status | Description |
|----------|--------|-------------|
| `/departures/traffictype/` | 200 | Departures from a station |
| `/arrivals/traffictype/` | 200 | Arrivals to a station |
| `/stationsobservations/` | 200 | Station observations |
| `/onepaths/` | 200 | Complete route of a train |
| `/betweenstations/traffictype/` | 200 | Trains between two stations |
| `/onestation/` | 200 | Station details |
| `/allstations/reducedinfo/{token}/` | 200 | Complete station list (GET) |
| `/severalpaths/` | 204 | Details of multiple circulations (returns 204 when no data) |
| `/compositions/path/` | 204* | Train compositions |
*`compositions` works without `allControlPoints`; including it requires elevated permissions (401).
**Important notes**
- Omit optional fields instead of sending `null` (the server rejects explicit nulls).
- `betweenstations`: Use `originStationCode` and `destinationStationCode` only.
- `onestation`: Requires `token: "0"` and a `detailedInfo` object.
- `allstations`: GET with the token in the path (initially `"0"`).
---
## Project Structure
```
adif-api-reverse-engineering/
├── README.md # This file
├── LICENSE
├── Core scripts
│ ├── adif_auth.py # HMAC-SHA256 implementation
│ ├── adif_client.py # Complete API client
│ └── query_api.py # Interactive CLI
├── Data
│ ├── station_codes.txt # 1587 station codes
│ └── extracted_keys.txt # Extracted keys
├── Tests (/tests)
│ ├── test_endpoints.py # Smoke test of main endpoints
│ ├── test_endpoints_detailed.py # Exhaustive test with debug
│ ├── test_onepaths_with_real_trains.py # End-to-end with live trains
│ └── test_api_authenticated.py # Minimal signed calls
├── Documentation (/docs)
│ ├── API_DOCUMENTATION.md # API documentation
│ ├── AUTHENTICATION_ALGORITHM.md # HMAC algorithm
│ └── API_REQUEST_BODIES.md # Request body references
└── APK Analysis
├── apk_decompiled/ # Decompiled code (JADX)
└── apk_extracted/ # Extracted APK
├── assets/stations_all.json # Station source
└── lib/x86_64/libapi-keys.so # Library with keys
```
---
## Authentication
### Extracted Keys
```python
ACCESS_KEY = "and20210615"
SECRET_KEY = "Jthjtr946RTt"
USER_KEY_CIRCULATION = "f4ce9fbfa9d721e39b8984805901b5df"
USER_KEY_STATIONS = "0d021447a2fd2ac64553674d5a0c1a6f"
```
**Source**: `apk_extracted/lib/x86_64/libapi-keys.so` (extracted with Ghidra)
### HMAC-SHA256 Algorithm
Implementation based on AWS Signature v4.
**CRITICAL**: Header order is not alphabetical:
```python
canonical_headers = (
f"content-type:application/json;charset=utf-8\n"
f"x-elcano-host:{host}\n" # host before client
f"x-elcano-client:AndroidElcanoApp\n"
f"x-elcano-date:{timestamp}\n"
f"x-elcano-userid:{user_id}\n"
)
```
See `adif_auth.py` for complete implementation.
---
## Station Codes
**Total**: 1587 stations
**File**: `station_codes.txt`
**Format**: `code TAB name TAB traffic_types`
### Top 10 Stations
```
10200 Madrid Puerta de Atocha AVLDMD
10302 Madrid Chamartin-Clara Campoamor AVLDMD
71801 Barcelona Sants AVLDMD,CERCANIAS
60000 Valencia Nord AVLDMD
11401 Sevilla Santa Justa AVLDMD
50003 Alicante Terminal AVLDMD,CERCANIAS
54007 Cordoba Central AVLDMD
79600 Zaragoza Portillo AVLDMD,CERCANIAS
03216 Valencia J.Sorolla AVLDMD
04040 Zaragoza Delicias AVLDMD,CERCANIAS
```
---
## Use Cases
### 1. Delay Monitor
```python
import time
from adif_client import AdifClient
client = AdifClient(ACCESS_KEY, SECRET_KEY)
while True:
trains = client.get_departures("10200", "ALL")
for train in trains:
passthrough = train.get('passthroughStep', {})
dep_sides = passthrough.get('departurePassthroughStepSides', {})
delay = dep_sides.get('forecastedOrAuditedDelay', 0)
if delay > 300: # More than 5 minutes
print(f"Warning: {delay//60} min delay")
time.sleep(30)
```
### 2. Query Complete Routes
```python
trains_with_routes = client.get_all_departures_with_routes(
station_code="10200",
traffic_type="AVLDMD",
max_trains=5
)
for train in trains_with_routes:
print(f"Train {train['commercial_number']}")
print(f" Stops: {len(train['route'])}")
```
### 3. Interactive CLI
```bash
python3 query_api.py
```
---
## Tools Used
- **Ghidra** - Key extraction from `libapi-keys.so`
- **JADX** - APK decompilation
- **Python 3** - Client implementation
- **Frida** (optional) - Dynamic analysis
---
## Documentation
- **[API_DOCUMENTATION.md](docs/API_DOCUMENTATION.md)** - Complete API documentation
- **[AUTHENTICATION_ALGORITHM.md](docs/AUTHENTICATION_ALGORITHM.md)** - Detailed HMAC algorithm
- **[GHIDRA_GUIDE.md](docs/GHIDRA_GUIDE.md)** - Step-by-step Ghidra tutorial
---
## Known Limitations
- `allControlPoints: true` in `/compositions/path/` requires elevated permissions (returns 401). Leave it out to get 204/200 responses.
- 204 responses simply mean “no data right now” with valid auth.
---
## License
MIT License - See [LICENSE](LICENSE)
**Disclaimer**: Project for educational and research purposes. Use responsibly.