Skip to content

Communication debugging

Daniel Frantík edited this page Aug 29, 2026 · 3 revisions

Communication debugging

Watch what your code and the router are actually saying to each other. There are two levels: the protocol words each command exchanges, and the raw bytes underneath them. Reach for this when a call does something you did not expect and the exception is not enough — a field that arrives empty, a transport that disagrees with the API baseline, or a terminal that hangs. To run tests with no router at all, see Unit testing without a router instead.

⚠️ Alpha — ships in v4.0.0-alpha: tested and functional, but the API may still change before the final 4.0 release. See Connection types & capabilities.

Watching the exchange (OnReadRow / OnWriteRow)

When a debugger is attached the complete communication is written to the Output window — ITikConnection.DebugEnabled defaults to Debugger.IsAttached, and TikConnectionSetup.DebugEnabled overrides it.

To handle the traffic yourself, hook OnWriteRow and OnReadRow on the ITikConnection object.

        // ....
        connection.OnWriteRow += Connection_OnWriteRow;
        // ....

        private void Connection_OnWriteRow(object sender, TikConnectionCommCallbackEventArgs e)
        {
            Console.WriteLine(e.Word);
        }

The same OnReadRow / OnWriteRow events back the MCP server's traceLevel='words' option (also reachable as the legacy includeRawTrace=true), which renders the raw words for every transport (API words, REST HTTP, CLI text, or WinBox M2). See the MCP server page for the per-transport trace format — it is the easiest way to compare a transport against the API baseline and spot a mis-mapped field.

Trying out API syntax

If you are not sure about API syntax and want to test it, use the sample app's console command — it takes raw API words at a prompt and echoes every word in both directions:

dotnet run --project samples/tik4net.samples -- console --host 192.168.88.1 --user admin

See samples/tik4net.samples (it also has torch for a streaming read and crud for the O/R mapper, all over any transport via --transport).

Alternatively, the MCP server (Tools/tik4net.mcp) lets you run any command over any transport and see the response (and, optionally, the raw protocol trace) from an MCP client such as Claude Code.


Byte/frame-level wire tracing (TikWireTrace)

OnReadRow/OnWriteRow fire at the word/sentence abstraction. For the CLI family that word is already the ANSI-stripped, cleaned command/response text, and for WinBox-native it is the decoded M2 record — so the exact bytes on the wire, the mepty terminal-output pull cadence, VT100 negotiation, M2 frame chunking and the prompt/settle/timeout decisions are all invisible above that line. When you are chasing a transport-level hang or desync (not a mis-mapped field), you need the layer underneath.

tik4net.Diagnostics.TikWireTrace is a process-wide, no-op-by-default wire-trace sink for exactly that. It costs a single null check while no sink is installed, so the emit points live permanently in the transports. Install a sink around the code you want to trace:

using tik4net.Diagnostics;

sealed class ConsoleSink : ITikWireTraceSink
{
    public void Emit(string channel, TikWireDir dir, byte[] data, int offset, int count, string note)
    {
        string arrow = dir == TikWireDir.Send ? ">>" : dir == TikWireDir.Recv ? "<<" : "--";
        string body  = TikWireTrace.Escape(data, offset, count);   // printable ASCII + <ESC>/<CR>/<LF>/<XX>
        Console.WriteLine($"{channel} {arrow} {body}{(note is null ? "" : $"  ({note})")}");
    }
}

using (TikWireTrace.Capture(new ConsoleSink()))
{
    using var conn = new TikConnectionSetup(host, user, pass).CreateWinboxCliConnection();
    conn.CallCommandSync(new[] { "/interface/print", "detail" });
}   // sink uninstalled on dispose

Channels (the channel argument identifies the emit site):

Channel Layer Emitted by
wbxcli.mepty WinBox CLI terminal payload (keystrokes, pulls, VT100, prompt/settle notes) WinboxCli/WinboxCliMac
wbxtcp.frame WinBox M2 chunked frames (tag 0x06 encrypted / 0x01 raw) all WinBox transports
telnet.sock raw Telnet socket bytes (pre-IAC-filtering) Telnet
mactelnet.udp MAC-Telnet UDP packet payloads (by packet type) MacTelnet/WinboxCliMac (MAC layer)
ssh.pty raw SSH shell-stream bytes, plus prompt-settle / truncation notes Ssh
api.word binary-API word bodies Api/ApiSsl
wbx.codec notes only — a value the decoder could not read as a number (an enum, a flag set, a reference, an .id, a subtype discriminator) and therefore left as raw wire text, or a reference table it could not read WinboxNative/WinboxNativeMac
wbx.catalog notes only — .jg catalog entries that could not be parsed, and a catalog that ended up empty all WinBox transports
wbxclimac.session notes only — a session that had to be reopened, and undecryptable/malformed M2 frames dropped by the MAC receive path WinboxCliMac/WinboxNativeMac
rest.http notes only — an HTTP error body that is not JSON and is therefore reported verbatim Rest/RestSsl
cli.json notes only — the router refused :serialize to=json, so this connection falls back to as-value (pre-7.13) all CLI transports
cli.cancel notes only — an in-flight cancel under TikCancellationMode.AbandonAndClose closed the connection all CLI transports
value.token notes only — a value type read text it could not parse and does not recognise as a RouterOS word. The value is kept verbatim, so nothing breaks; the note is how the gap becomes findable every transport

TikWireDir is Send / Recv / Note (a Note has no payload — e.g. prompt seen @59ms, settled -> return @225ms, TIMEOUT @…). The sink's Emit is called on whatever thread does the I/O (for WinBox-native that is the multiplexer reader thread), so keep the implementation thread-safe; the passed buffer is not retained after the call, so copy out (via TikWireTrace.Escape) if you need to keep it. The MCP server's traceLevel='bytes' option is just this sink wrapped around one call, with a --- WIRE TRACE (bytes) --- section appended to the response.

See also

Clone this wiki locally