Java Control Client

A typed Java client for the emulator’s control REST API. Test code uses it to drive one emulator process: start and stop devices, send events, read and write parameters, read logs and change the process-wide properties. It wraps the endpoints described in REST API.

Overview

Item Value

Maven coordinates

com.friendly.qa:ft-tr069-emulator-client (current development version 0.1.0-SNAPSHOT)

Java

25

Compile dependencies

tools.jackson.core:jackson-databind (Jackson 3) and org.slf4j:slf4j-api. There is no Spring dependency.

Entry point

com.friendly.qa.emulator.tr069.client.Tr069EmulatorClient

Packages

com.friendly.qa.emulator.tr069.client (the client), …​model (records and enums), …​error (the exception)

Publication

Releases are published to GitHub Packages of the TR069_Emulator repository by a tag-driven release workflow. No release has been published yet; build from the emulator-client/ module of the repository until one is.

One Tr069EmulatorClient object serves one emulator process. It holds one HTTP client and no other state, and it is safe to share between threads. The control API it calls has no authentication, so point it at an emulator on an internal network only.

Operations

Two static factories and 21 instance methods. Endpoint groups are described in Load Test API (/api) and Single Mode API (/apiV2).

Method HTTP operation Returns

create(URI baseUri)

none; exchange timeout 30 s, connect timeout 5 s

the client

create(URI baseUri, Duration requestTimeout)

none; the timeout bounds one whole exchange, body included

the client

batchStatus()

GET /api/status

BatchStatusEnum (RUNNING, STOPPED)

version()

GET /api/version

EmulatorVersion

properties()

GET /api/properties

EmulatorProperties

updateProperties(PropertiesPatch patch)

GET then POST /api/properties

nothing

parameterTrees()

GET /apiV2/paramsXmlList

List<String>, the parameter tree file names

stopAll(boolean clearCaches)

POST /api/stop

nothing

startBatch()

POST /api/start

nothing

batchDeviceCounts()

GET /api/deviceCounts

DeviceCounts (total, active)

startDevice(DeviceSpec spec)

POST /apiV2/start?saveConfig=false

nothing

stopDevice(String serial)

POST /apiV2/stop

nothing

inSession(String serial)

GET /apiV2/sessionStatus

boolean

invoke(String serial, CpeEventEnum event)

POST /apiV2/invoke; 204 or 409

InvokeOutcomeEnum (ACCEPTED, IN_SESSION)

invokeWhenIdle(String serial, CpeEventEnum event, Duration idleBudget)

POST /apiV2/invoke, polling GET /apiV2/sessionStatus after a 409

nothing

parameterValue(String serial, String name)

GET /apiV2/parameterValue; 404 gives empty

Optional<ParameterValue>

parameterInfo(String serial, String name)

GET /apiV2/parameterFullInfo; 404 gives empty

Optional<ParameterInfo>

parametersByPrefix(String serial, String prefix)

GET /apiV2/parametersByPrefix

List<ParameterInfo>

changeParameter(String serial, String name, String value)

POST /apiV2/changeParameter; no event is sent, the type becomes xsd:string

nothing

setParameterValue(String serial, String name, String value)

POST /apiV2/setParameterValue; 200 or 204

boolean, true for 200

changePeriodicInterval(String serial, int seconds)

POST /apiV2/changePeriodic; seconds of at least 1

nothing

readLog(String serial, long fromPosition)

position 0: GET /apiV2/getLog; above 0: GET /apiV2/getLogContent

LogChunk (content, nextPosition)

downloadLog(String serial)

GET /apiV2/getLog

byte[], the exact bytes

Model and Error Types

The model package holds plain records and enums, with member names equal to the JSON member names of the API (see Data Models).

Type Content

DeviceSpec

serial, acsUrl and parameterFile are required; acsUsername and acsPassword may be null. A credential that is set but blank is rejected. toString() prints serial and parameterFile only.

ParameterValue

name, value, type; value and type may be null. toString() prints the value.

ParameterInfo

name, value, type, notification (a string: 0, 1 or 2), writable.

EmulatorVersion

the members of /api/version; some are nullable.

DeviceCounts, LogChunk

total and active; content and nextPosition.

EmulatorProperties

the 19 general members of /api/properties plus taskDelays (Map<CwmpRpcEnum, Integer>) and taskResults (Map<CwmpRpcEnum, TaskResultEnum>).

PropertiesPatch

built with PropertiesPatch.builder(); only the members that were set are sent.

BatchStatusEnum, InvokeOutcomeEnum

RUNNING, STOPPED; ACCEPTED, IN_SESSION.

CpeEventEnum

BOOTSTRAP, BOOT, PERIODIC, VALUE_CHANGE, CONNECTION_REQUEST, TRANSFER_COMPLETE, DIAGNOSTICS, DU_STATE_CHANGE_COMPLETE. See TR-069 Event Codes.

TaskResultEnum, CwmpRpcEnum

DEFAULT, COMPLETED, FAILED, REJECTED; the 13 RPCs whose delay and result the properties can set.

Errors:

  • Tr069EmulatorException is unchecked. Its kind() is an ErrorKindEnum: UNREACHABLE (timeout or I/O failure), UNEXPECTED_STATUS (an HTTP status the operation does not expect), CONTRACT (the response does not have the declared shape) or BUSY (invokeWhenIdle ran out of budget). It also offers operation(), serial() and httpStatus().

  • An argument rejected before any request is an IllegalArgumentException, not this exception: nothing was exchanged.

  • A message carries the kind, the operation, the serial and the HTTP status, never a request body, a response body or a parameter value.

See Error Handling for the HTTP statuses of the API itself.

Rules for Test Authors

Topic Rule

Response shape

A response that is not valid JSON, lacks a declared member, or holds a member of the wrong type is a CONTRACT error naming the member. An extra member is ignored, so an emulator that gains a member does not break the client.

invoke

ACCEPTED means accepted, not sent. The emulator also answers 204 and then ignores the event for a serial that is not running, while the device’s first Inform is still pending, and after stopAll. A device’s first Inform is pending from its start until its first session, the one its 0 BOOTSTRAP Inform opens, has started. While it is pending, the emulator accepts and then ignores every event handed to the device, by invoke and by setParameterValue alike. Once that session runs, the device is in a session like at any later time. A caller that needs its event delivered waits until the first session has ended. When the device enters a session at the same moment, the answer takes up to 20 s and the event waits for the next Inform. IN_SESSION is a 409; invoke never retries. Observe the Inform you need.

invokeWhenIdle

After a 409 it polls inSession every 250 ms and sends again. idleBudget is the only bound; when it elapses the call throws BUSY. The budget is checked between requests, so the call can overrun it by one exchange.

readLog

Needs the emulator to run with fileLogEnabled=true; a device without a log file is UNEXPECTED_STATUS. Position 0 reads the whole file, decoded as UTF-8, and returns its byte length as nextPosition. A positive position reads what follows it; that text is ISO-8859-1, so a character outside Latin-1 arrives as ? (use downloadLog for exact bytes). A nextPosition below the position you asked for means the log rolled: read again from 0. That signal is necessary, not sufficient: once the new file has grown past your old position, bytes from its middle are returned and nothing tells you. A negative position is rejected before any request.

parameterValue, parameterInfo

404 gives an empty Optional. Neither answer tells a running device from a missing one: a serial that is not running reads the default tree’s values until the process has cleared its caches, and reads empty from then until the next startBatch. Recognise your own device by its serial in <root>DeviceInfo.SerialNumber.

URL and JSON encoding

The client URL-encodes serials and parameter names in query strings and JSON-escapes them in bodies. Pass them raw.

Timeouts

A timeout or I/O failure is UNREACHABLE and names the base URI. The timeout bounds the whole exchange, body included.

updateProperties

It reads the current properties first and sends the current startDateTime when the patch does not set one, because the emulator resets an absent startDateTime to empty. PropertiesPatch.Builder refuses the empty text for protocol, host, url, paramsXml and transferEmulationDelay with an IllegalArgumentException before any request: the emulator skips an empty value of these five and still answers 204, so the update would silently change nothing. Only the empty text is refused; a text of spaces is sent. startDateTime accepts the empty text. Known limit: on a fresh emulator process whose start_params.properties has no startDateTime line the member reads null, and the first updateProperties of any member turns it into the empty text; PropertiesPatch cannot write null back.

connectionRequestEnabled

connectionRequestEnabled=true followed by startBatch opens a listener on every interface of the emulator host. Keep the property false unless the emulator runs in a network-isolated environment.

stopAll

It stops and unschedules every device of the process, single devices included. Afterwards a single device still reads its own values, but invoke on it is accepted and nothing is sent. With clearCaches=true the data of every device and the default tree are wiped: reads answer 404 for every serial not started again, until the next startBatch.

stopDevice

It returns before the stop is complete (the emulator stops asynchronously, within 3 s). Before you reuse the serial, wait until parameterValue(serial, <root>DeviceInfo.SerialNumber) no longer equals the serial; an empty read counts.

changePeriodicInterval

The new interval is used from the end of the next session. The timer armed at the previous session end still fires, so a device left alone sends 2 PERIODIC once more at its old interval.

setParameterValue

True means the value changed, the parameter’s notification is 2, and the emulator handed a 4 VALUE CHANGE to its event path. On an idle started device the session starts now. For a device in a session the answer takes up to 20 s and the event waits for the next Inform; for a serial that is not running, or while the device’s first Inform is still pending, the event is ignored. So true proves neither that an Inform follows now nor that the device exists. False means no event was handed over: the value is unchanged, the notification is 0 or 1 (with 1 the change waits for the next Inform), or the emulator failed internally.

startDevice

It always sends saveConfig=false: the device does not outlive the emulator process. Any status other than 200 is UNEXPECTED_STATUS. A second start of a serial that is already running is not refused; the device sends 0 BOOTSTRAP again.

Argument checks

The client rejects, before any request, a base URI that is not an absolute http or https URI with a host and without user info, query or fragment; a serial that is blank or holds /, \ or a control character; changePeriodicInterval seconds below 1; and a negative log position.

Properties Are Process-Global and Order-Sensitive

The properties belong to the emulator process, not to a device. Every device and every caller shares them. A member that is read when a device is created, for example periodicInformEnabled, must be set before the device starts: call updateProperties before startDevice. Changing it afterwards does not change a device that already exists.

Usage Example

Tr069EmulatorClient emulator = Tr069EmulatorClient.create(URI.create("http://127.0.0.1:8080"));

emulator.updateProperties(PropertiesPatch.builder().periodicInformEnabled(true).build());

String serial = "TEST-0001";
emulator.startDevice(new DeviceSpec(serial, "https://acs.example.invalid/cwmp", "example-tree.xml", null, null));

emulator.invokeWhenIdle(serial, CpeEventEnum.PERIODIC, Duration.ofSeconds(30));

Optional<ParameterValue> reading = emulator.parameterValue(serial, "Device.DeviceInfo.SerialNumber");

emulator.stopDevice(serial);

The parameter name depends on the root of the tree the device was started from. example-tree.xml stands for a file listed by parameterTrees().