Initial import of ADIF API reverse-engineering toolkit

This commit is contained in:
2025-12-16 08:37:56 +01:00
commit 60388529c1
11486 changed files with 1086536 additions and 0 deletions

375
docs/API_DOCUMENTATION.md Normal file
View File

@@ -0,0 +1,375 @@
# ADIF Elcano API - Complete Documentation
Complete documentation of the ADIF API (El Cano Movil) obtained through reverse engineering of the mobile application.
**All 9/9 endpoints functional** | HMAC-SHA256 authentication | 1587 station codes
---
## Base URLs
```
BASE_URL_STATIONS = https://estaciones.api.adif.es
BASE_URL_CIRCULATION = https://circulacion.api.adif.es
BASE_URL_ELCANOWEB = https://elcanoweb.adif.es/api/
BASE_URL_AVISA = https://avisa.adif.es/avisa-ws/api/
```
## Authentication Keys
### HMAC Keys (extracted from libapi-keys.so)
```python
ACCESS_KEY = "and20210615"
SECRET_KEY = "Jthjtr946RTt"
```
### User-keys (different for each service)
```
Circulations: f4ce9fbfa9d721e39b8984805901b5df
Stations: 0d021447a2fd2ac64553674d5a0c1a6f
```
### Other Auth Tokens
```
Avisa Login: Basic YXZpc3RhX2NsaWVudF9hbmRyb2lkOjh5WzZKNyFmSjwhXypmYXE1NyNnOSohNElwa2MjWC1BTg==
Subscriptions: Basic ZGVpbW9zOmRlaW1vc3R0
Registration Token: Bearer b9034774-c6e4-4663-a1a8-74bf7102651b
```
---
## Endpoints (9/9 Working)
### Station Endpoints
#### Get All Stations
```
GET /portroyalmanager/secure/stations/allstations/reducedinfo/{token}/
Base: https://estaciones.api.adif.es
User-key: stations
Token: Initial value is "0"
```
#### Get Station Details
```
POST /portroyalmanager/secure/stations/onestation/
Base: https://estaciones.api.adif.es
User-key: stations
Body:
{
"stationCode": "10200",
"token": "0",
"detailedInfo": {
"extendedStationInfo": true,
"stationActivities": true,
"stationBanner": true,
"stationCommercialServices": true,
"stationInfo": true,
"stationServices": true,
"stationTransportServices": true
}
}
```
#### Station Observations
```
POST /portroyalmanager/secure/stationsobservations/
Base: https://estaciones.api.adif.es
User-key: stations
Body:
{
"stationCodes": ["60000", "71801"]
}
```
---
### Circulation Endpoints
#### Departures
```
POST /portroyalmanager/secure/circulationpaths/departures/traffictype/
Base: https://circulacion.api.adif.es
User-key: circulations
Body:
{
"commercialService": "BOTH",
"commercialStopType": "BOTH",
"page": {"pageNumber": 0},
"stationCode": "10200",
"trafficType": "ALL"
}
```
#### Arrivals
```
POST /portroyalmanager/secure/circulationpaths/arrivals/traffictype/
Base: https://circulacion.api.adif.es
User-key: circulations
Body: Same format as departures
```
#### Between Stations
```
POST /portroyalmanager/secure/circulationpaths/betweenstations/traffictype/
Base: https://circulacion.api.adif.es
User-key: circulations
Body:
{
"commercialService": "BOTH",
"commercialStopType": "BOTH",
"originStationCode": "10200",
"destinationStationCode": "71801",
"page": {"pageNumber": 0},
"trafficType": "ALL"
}
IMPORTANT: Send only origin/destination; do NOT add `stationCode` or explicit `null` fields.
```
#### One Train Route (OnePaths)
```
POST /portroyalmanager/secure/circulationpathdetails/onepaths/
Base: https://circulacion.api.adif.es
User-key: circulations
Body:
{
"allControlPoints": true,
"commercialNumber": "04138",
"destinationStationCode": "60000",
"launchingDate": 1733356800000,
"originStationCode": "71801"
}
Notes:
- `commercialNumber` must be real (grab from departures/arrivals)
- `launchingDate` must be milliseconds
- Omit unused fields instead of sending `null`
```
#### Multiple Routes (SeveralPaths)
```
POST /portroyalmanager/secure/circulationpathdetails/severalpaths/
Base: https://circulacion.api.adif.es
User-key: circulations
Body: Same format as onepaths (omit unused fields)
Returns 204 when there is no data for the requested trains.
```
#### Train Composition
```
POST /portroyalmanager/secure/circulationpaths/compositions/path/
Base: https://circulacion.api.adif.es
User-key: circulations
Body:
{
"commercialNumber": "03194",
"destinationStationCode": "71801",
"launchingDate": 1764889200000,
"originStationCode": "10200"
}
IMPORTANT: Do NOT include `allControlPoints: true` (requires elevated permissions, returns 401)
```
---
## Response Structures
### Departures/Arrivals Response
```json
{
"commercialPaths": [
{
"commercialPathInfo": {
"timestamp": 1764927847100,
"commercialPathKey": {
"commercialCirculationKey": {
"commercialNumber": "90399",
"launchingDate": 1764889200000
},
"originStationCode": "10620",
"destinationStationCode": "60004"
},
"trafficType": "CERCANIAS",
"opeProComPro": {
"operator": "RF",
"product": "C",
"commercialProduct": " "
}
},
"passthroughStep": {
"stopType": "NO_STOP",
"stationCode": "10200",
"departurePassthroughStepSides": {
"plannedTime": 1764927902000,
"forecastedOrAuditedDelay": 2175,
"timeType": "FORECASTED",
"plannedPlatform": "2",
"circulationState": "RUNNING"
}
}
}
]
}
```
**Important fields**:
- `commercialNumber`: Train commercial number
- `launchingDate`: Date in milliseconds
- `plannedTime`: Planned time in milliseconds
- `forecastedOrAuditedDelay`: Delay in seconds
- `circulationState`: RUNNING, PENDING_TO_CIRCULATE, etc.
### OnePaths Response (Complete Route)
```json
{
"commercialPaths": [
{
"commercialPathInfo": { /* Same as departures */ },
"passthroughSteps": [ // Array with ALL stops
{
"stopType": "COMMERCIAL",
"stationCode": "10620",
"departurePassthroughStepSides": {
"plannedTime": 1764918000000,
"forecastedOrAuditedDelay": 430,
"plannedPlatform": "1",
"circulationState": "RUNNING"
}
}
// ... more stops
]
}
]
}
```
**Key difference**:
- `departures/arrivals` -> `passthroughStep` (singular)
- `onepaths` -> `passthroughSteps` (plural, array)
---
## Status Codes
| Code | Meaning | Cause |
|------|---------|-------|
| 200 | Success | Request successful with data |
| 204 | No Content | Authentication OK, no data available |
| 400 | Bad Request | Incorrect payload |
| 401 | Unauthorized | No permissions or wrong auth |
**Note**: 204 is NOT an error. It means authentication and payload are correct.
---
## Data Types
### TrafficType
- `CERCANIAS` - Commuter trains
- `AVLDMD` - High Speed, Long and Medium Distance
- `OTHERS` - Other
- `TRAVELERS` - Passengers
- `GOODS` - Freight
- `ALL` - All types
### State (commercialService, commercialStopType)
- `YES`
- `NOT`
- `BOTH`
---
## Common Issues and Solutions
### Issue 1: NULL Fields Cause Errors
**Problem**: Including `null` fields in JSON causes 401 errors.
```json
// WRONG - causes 401
{"stationCode": null, "trafficType": "ALL"}
// CORRECT - omit the field entirely
{"trafficType": "ALL"}
```
### Issue 2: Header Order for HMAC
The canonical headers order is NOT alphabetical:
```
1. content-type
2. x-elcano-host <- host before client!
3. x-elcano-client
4. x-elcano-date
5. x-elcano-userid
```
### Issue 3: Timestamps
`launchingDate` must be in milliseconds:
```python
# Correct
timestamp = int(datetime.now().timestamp() * 1000)
# Wrong (missing 3 zeros)
timestamp = int(datetime.now().timestamp())
```
### Issue 4: allControlPoints Requires Permissions
`/compositions/path/` with `allControlPoints: true` returns 401. Don't include it.
---
## Station Codes
**Total**: 1587 stations
**File**: `station_codes.txt`
**Source**: `apk_extracted/assets/stations_all.json`
### Top 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
```
---
## Implementation Notes
### Tools Used
- **Ghidra**: Key extraction from `libapi-keys.so`
- **JADX**: APK decompilation
- **Python 3**: Client implementation
### Key Source Files
- `apk_extracted/lib/x86_64/libapi-keys.so` - Authentication keys
- `apk_extracted/assets/stations_all.json` - Station database
- `apk_decompiled/sources/com/adif/elcanomovil/serviceNetworking/interceptors/auth/ElcanoAuth.java` - HMAC algorithm
### Main Analyzed Classes
- `TrafficCirculationPathRequest` - Request model
- `OneOrSeveralPathsRequest` - Path requests
- `ElcanoAuth` - HMAC-SHA256 implementation
- `ServicePaths` - URL and key definitions