Initial import of ADIF API reverse-engineering toolkit
This commit is contained in:
237
README.md
Normal file
237
README.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user