Files
adif-api-reverse-engineering/docs/API_REQUEST_BODIES.md

13 KiB

Request Body Analysis - ADIF API

Reverse engineering of package com.adif.elcanomovil.serviceNetworking
Date: 2025-12-04

Table of Contents


1. Authentication Headers

1.1 Static Headers

File: ServicePaths.java:67-76

Circulations

User-key: f4ce9fbfa9d721e39b8984805901b5df
Content-Type: application/json;charset=utf-8

Stations

User-key: 0d021447a2fd2ac64553674d5a0c1a6f
Content-Type: application/json;charset=utf-8

AVISA (Login/Refresh)

Authorization: Basic YXZpc3RhX2NsaWVudF9hbmRyb2lkOjh5WzZKNyFmSjwhXypmYXE1NyNnOSohNElwa2MjWC1BTg==

Decoded (Base64):

avista_client_android:8y[6J7!fJ<_*faq57#g9*!4Ipkc#X-AN

1.2 Dynamic Headers (Generated by ElcanoAuth)

File: serviceNetworking/interceptors/auth/ElcanoAuth.java

The interceptor adds the X-Elcano headers produced by the HMAC calculation:

X-Elcano-Host: <host>
X-Elcano-Client: AndroidElcanoApp
X-Elcano-Date: <timestamp>
X-Elcano-UserId: <uuid>
Authorization: HMAC-SHA256 Credential=... SignedHeaders=... Signature=...

Generation algorithm (summary):

  • Canonical headers (order matters): content-type, x-elcano-host, x-elcano-client, x-elcano-date, x-elcano-userid
  • Canonical request: method + path + query + canonical headers + SHA256(payload)
  • String to sign: HMAC-SHA256\n<timestamp>\n<date>/<client>/<userId>/elcano_request\n<hash>
  • Signature key: HMAC(secretKey, date) → HMAC(result, client) → HMAC(result, "elcano_request")

2. Request Bodies

2.1 Circulations - Departures/Arrivals/Between Stations

Endpoints:

  • /portroyalmanager/secure/circulationpaths/departures/traffictype/
  • /portroyalmanager/secure/circulationpaths/arrivals/traffictype/
  • /portroyalmanager/secure/circulationpaths/betweenstations/traffictype/

Model: TrafficCirculationPathRequest
File: circulations/model/request/TrafficCirculationPathRequest.java:10-212

{
  "commercialService": "YES|NOT|BOTH",
  "commercialStopType": "YES|NOT|BOTH",
  "destinationStationCode": "string or null",
  "originStationCode": "string or null",
  "page": {
    "pageNumber": 0
  },
  "stationCode": "string or null",
  "trafficType": "CERCANIAS|AVLDMD|OTHERS|TRAVELERS|GOODS|ALL"
}

Real Example

{
  "commercialService": "BOTH",
  "commercialStopType": "BOTH",
  "page": {
    "pageNumber": 0
  },
  "stationCode": "60000",
  "trafficType": "ALL"
}

Allowed Values

commercialService / commercialStopType (CirculationPathRequest.java:65-67):

  • YES - Only commercial services/stops
  • NOT - Without commercial services/stops
  • BOTH - All types

trafficType (TrafficType.java:16-21):

  • CERCANIAS - Commuter trains
  • AVLDMD - High speed long/medium distance
  • OTHERS - Other types
  • TRAVELERS - Passengers
  • GOODS - Freight
  • ALL - All types

2.2 Circulations - Specific Routes

Endpoints:

  • /portroyalmanager/secure/circulationpathdetails/onepaths/
  • /portroyalmanager/secure/circulationpathdetails/severalpaths/

Model: OneOrSeveralPathsRequest
File: circulations/model/request/OneOrSeveralPathsRequest.java:11-140

{
  "allControlPoints": true/false/null,
  "commercialNumber": "string or null",
  "destinationStationCode": "string or null",
  "launchingDate": 1733356800000,
  "originStationCode": "string or null"
}

Real Example

{
  "allControlPoints": true,
  "commercialNumber": "04138",
  "destinationStationCode": "60000",
  "launchingDate": 1733356800000,
  "originStationCode": "71801"
}

Important notes:

  • launchingDate is a millisecond timestamp (Long in Java)
  • allControlPoints indicates whether to include every control point in the route
  • Optional fields should be omitted rather than set to null

2.3 Train Compositions

Endpoint: /portroyalmanager/secure/circulationpaths/compositions/path/

Model: OneOrSeveralPathsRequest (same as routes)
File: compositions/CompositionsService.java:14-18

{
  "allControlPoints": true/false/null,
  "commercialNumber": "string or null",
  "destinationStationCode": "string or null",
  "launchingDate": 1733356800000,
  "originStationCode": "string or null"
}

2.4 Stations - Single Station Details

Endpoint: /portroyalmanager/secure/stations/onestation/

Model: OneStationRequest
File: stations/model/OneStationRequest.java:9-93

{
  "detailedInfo": {
    "extendedStationInfo": true,
    "stationActivities": true,
    "stationBanner": true,
    "stationCommercialServices": true,
    "stationInfo": true,
    "stationServices": true,
    "stationTransportServices": true
  },
  "stationCode": "60000",
  "token": "string"
}

Important notes:

  • The detailedInfo object controls what is returned
  • All boolean fields default to true (see DetailedInfoDTO.java:149)
  • token is required

DetailedInfo fields

File: stations/model/DetailedInfoDTO.java:10-151

Field Type Description
extendedStationInfo boolean Extended station information
stationActivities boolean Station activities
stationBanner boolean Banner/announcements
stationCommercialServices boolean Commercial services
stationInfo boolean Basic station information
stationServices boolean Available services
stationTransportServices boolean Transport services

2.5 Station Observations

Endpoint: /portroyalmanager/secure/stationsobservations/

Model: StationObservationsRequest
File: stationObservations/model/StationObservationsRequest.java:10-53

{
  "stationCodes": ["60000", "71801"]
}

Real Example

{
  "stationCodes": ["60000", "71801", "79600"]
}

Notes:

  • Array of station codes (strings)
  • Required field
  • Can contain multiple codes

3. Endpoints and Base URLs

3.1 Base URLs

File: di/NetworkModule.java:73-159

Service Base URL Authentication
Circulations https://circulacion.api.adif.es Secured (with AuthHeaderInterceptor)
Stations https://estaciones.api.adif.es Secured (with AuthHeaderInterceptor)
AVISA https://avisa.adif.es Basic (without AuthHeaderInterceptor)
Elcano Web https://elcanoweb.adif.es/api/ -

3.2 Full Paths - Stations

File: ServicePaths.java:106-112

GET  /portroyalmanager/secure/stations/allstations/reducedinfo/{token}/
POST /portroyalmanager/secure/stations/onestation/
POST /portroyalmanager/secure/stationsobservations/

3.3 Full Paths - Circulations

File: ServicePaths.java:41-51

POST /portroyalmanager/secure/circulationpaths/departures/traffictype/
POST /portroyalmanager/secure/circulationpaths/arrivals/traffictype/
POST /portroyalmanager/secure/circulationpaths/betweenstations/traffictype/
POST /portroyalmanager/secure/circulationpathdetails/onepaths/
POST /portroyalmanager/secure/circulationpathdetails/severalpaths/

3.4 Full Paths - Compositions

File: ServicePaths.java:55-61

POST /portroyalmanager/secure/circulationpaths/compositions/path/

3.5 Full Paths - AVISA

Files: ServicePaths.java:82-92 and ServicePaths.java:29-37

POST /avisa-ws/api/token                    (login)
POST /avisa-ws/api/token                    (refresh)
POST /avisa-ws/api/v1/client                (register)
GET  /avisa-ws/api/v1/station               (stations)
GET  /avisa-ws/api/v1/category              (categories)
GET  /avisa-ws/api/v1/incidence             (incidences list)
GET  /avisa-ws/api/v1/incidence/{id}        (incidence details)
POST /avisa-ws/api/v1/incidence             (create incidence)

4. Network Configuration

4.1 OkHttpClient Configuration

File: di/NetworkModule.java:100-132

Basic client

OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .connectTimeout(60, TimeUnit.SECONDS)
    .readTimeout(60, TimeUnit.SECONDS)
    .build()

Secured client (with authentication)

OkHttpClient.Builder()
    .addInterceptor(AuthHeaderInterceptor(userId))
    .certificatePinner(certificatePinner)
    .connectTimeout(60, TimeUnit.SECONDS)
    .readTimeout(60, TimeUnit.SECONDS)
    .build()

Timeouts:

  • Connect timeout: 60 seconds
  • Read timeout: 60 seconds

4.2 Services using the secured client

File: di/NetworkModule.java

  • CirculationService (line 73)
  • StationsService (line 142)
  • StationObservationsService (line 135)
  • CompositionsService (line 156)

4.3 Services using the basic client

  • AvisaLoginService (line 50)
  • AvisaStationsService (line 57)
  • IncidenceService (line 80)
  • SubscriptionsService (line 149)

5. Authentication System

5.1 AuthHeaderInterceptor

File: interceptors/AuthHeaderInterceptor.java:27-84

This interceptor runs on every request for secured services.

Authentication process

  1. Generate persistent User ID

    • Uses GeneratePersistentUserIdUseCase
    • The ID is stored and reused between sessions
  2. Build the token

    ElcanoClientAuth.Builder()
        .host(request.url().host())
        .contentType("application/json;charset=utf-8")
        .path(request.url().encodedPath())
        .params(request.url().encodedQuery())
        .xElcanoClient("AndroidElcanoApp")
        .xElcanoUserId(userId)
        .httpMethodName(request.method())
        .payload(bodyJsonWithoutSpaces)
        .build()
    
  3. Generate headers

    • The ElcanoClientAuth object generates authentication headers
    • They are added automatically to the request

Generated headers

X-CanalMovil-Authentication: <calculated_token>
X-CanalMovil-deviceID: <device_id>
X-CanalMovil-pushID: <push_id>

5.2 GetKeysHelper class

File: AuthHeaderInterceptor.java:44

Provides keys for authentication:

  • getKeysHelper.a() - First key
  • getKeysHelper.b() - Second key

These keys are used by the signing/authentication algorithm.

5.3 Certificate Pinning

File: di/NetworkModule.java:64-70

The app uses Certificate Pinning to prevent MITM attacks:

  • Expected SSL certificates are in PinningRepository
  • Loaded asynchronously at startup
  • All requests validate the server certificate

6. Code References

6.1 Key Files

File Location Description
ServicePaths.java serviceNetworking/ Paths and static headers
AuthHeaderInterceptor.java serviceNetworking/interceptors/ Auth header generation
NetworkModule.java serviceNetworking/di/ Retrofit/OkHttp configuration
CirculationService.java serviceNetworking/circulations/ Circulation API
StationsService.java serviceNetworking/stations/ Stations API
StationObservationsService.java serviceNetworking/stationObservations/ Observations API
CompositionsService.java serviceNetworking/compositions/ Compositions API

6.2 Request Models

Model File Usage
TrafficCirculationPathRequest circulations/model/request/ Departures, Arrivals, BetweenStations
OneOrSeveralPathsRequest circulations/model/request/ OnePaths, SeveralPaths, Compositions
OneStationRequest stations/model/ Station details
DetailedInfoDTO stations/model/ Detailed info configuration
StationObservationsRequest stationObservations/model/ Station observations

6.3 Important Code Lines

  • Static headers: ServicePaths.java:67-76
  • Circulations user-key: ServicePaths.java:67
  • Stations user-key: ServicePaths.java:68
  • AVISA login token: ServicePaths.java:70
  • Auth interceptor: AuthHeaderInterceptor.java:38-83
  • Circulations base URL: NetworkModule.java:76
  • Stations base URL: NetworkModule.java:145
  • Enum TrafficType: TrafficType.java:16-21
  • Enum State: CirculationPathRequest.java:65-67

7. Additional Notes

7.1 JSON Serialization

  • Library used: Moshi (configured in NetworkModule.java:87-96)
  • Format: JSON field names match Java property names exactly
  • Null handling: Optional fields are omitted by the client; sending explicit null values leads to 401 responses
  • Date format: Millisecond timestamps (Long)

7.2 Security Considerations

  1. Hardcoded user-keys: API keys are in the code (easy to extract)
  2. Certificate Pinning: Makes proxy interception harder
  3. Dynamic authentication: X-Elcano headers require the HMAC algorithm to be reproduced
  4. AVISA token: Base64 credentials in code (decodable)

7.3 Testing

Use the Python client (adif_client.py) or the signed test scripts in /tests to validate each endpoint. All endpoints are currently reproducible with the shipped keys and HMAC implementation.


Last update: 2025-12-05
Source: Decompiled ADIF El Cano Movil APK
Tools: JADX, Ghidra, manual Java code analysis