Skip to content

The report ​

Every host a check probes writes one CheckResultProfile, and the report is those as JSON — {"details":[ … ]}, one object per host and not one per check: a ping and a dns walk both links, so each of them writes one per host of the long link and one per host of the short link.

json
{
  "details": [
    {
      "detectType": 1, "errorCode": 0, "networkType": 1,
      "detectIP": "1.2.3.4", "port": 80, "conntime": 30, "rtt": 42,
      "rttStr": "42ms", "httpStatusCode": 0, "pingCheckCount": 0,
      "pingLossRate": "", "dnsDomain": "example.com", "localDns": "",
      "dnsIP1": "1.2.3.4", "dnsIP2": ""
    }
  ]
}
whatthe fieldwhat it is
which checkdetectTypeone of the integers of the checks page
how the probe wenterrorCode0 and above is one that worked
the network it ran onnetworkTypethe comm::getNetInfo() you handed to the run
what was probeddetectIP, port, dnsDomainthe host, and the port for a tcp check
the timingsconntime, rtt, rttStrms; rttStr is the rtt as a string
the HTTP checkhttpStatusCodewhat the net-check CGI answered
the pingpingCheckCount, pingLossRatehow many went out, and what share was lost
the dnslocalDns, dnsIP1, dnsIP2the resolver, and the first two addresses

Taking it ​

whatRustSwiftshared Kotlin, AndroidC
the reportreport_json(&results)MarsSdt.takeReport()SdtLogic.takeReport()mars_sdt_take_report(buf, len)
a callback insteadsdt.set_callback(…)—SdtLogic.setCallBack(object : SdtLogic.ICallBack { … })—

The two carry the same document, so an app uses one of them: on Android and in the shared Kotlin a run hands the report to the callback and keeps it for a takeReport() that comes later, so an app that does both sends the same diagnosis twice. Taking it empties it — the next call reports what happened since.

On Android and in the shared Kotlin the report is a String?, so there is no buffer to size. The Swift takeReport() starts at 4 KB and doubles up to 1 MB, and answers nil in three cases: there was nothing to take, the C ABI answered a code other than MARS_SDT_ERR_NO_SPACE — a panic, or a NULL buffer — or the report still did not fit 1 MB. Only the first of them is "no diagnosis yet", so an app that treats every nil that way loses a report it already had. mars_sdt_take_report answers MARS_SDT_ERR_NO_SPACE when the report does not fit the buffer — and keeps the results, so a caller that asks again with a bigger one gets the diagnosis rather than an empty one.

Cancelling, and starting over ​

whatRustSwiftKotlinC
stop the runcancel_active_check / cancel_handlecancelActiveCheckcancelActiveCheckmars_sdt_cancel_active_check
is one in flightis_checkingisCheckingisCheckingmars_sdt_is_checking
throw it all awaya new SdtLogicresetresetmars_sdt_reset

A run borrows the logic exclusively in Rust, so cancel_active_check cannot be called while run_checks is on the stack: take a CancelHandle with cancel_handle() first and hand it to whoever has to stop the run — the probe closure, which owns the socket that has to give up.

There is no reset on SdtLogic — the SdtCore behind it is a private field — so the way Rust throws a diagnosis away is a new logic: drop this one, make another, and set the callback on that one again. A run that finished needs none of it: its end clears the plan and the request, and run_checks hands the results back instead of keeping them, so the next start_active_check is a diagnosis made from nothing.

Everywhere else the cancel needs no handle: it sets the flag the run reads and takes no lock the run holds, so it lands from another thread while the probes are still being asked — which is the only moment cancelling means anything. What it does is keep the rest of the plan from running; a probe that is already out is not interrupted.

And a run is one at a time: the diagnosis is one process-wide value, so a second runChecks does not run beside the first. On Android it waits — the second call does not come back until the first is over. On the other Kotlin targets it is refused: runChecks answers false and runs nothing.

Where to go next ​

MIT, like the upstream project.