-
Notifications
You must be signed in to change notification settings - Fork 98
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 inv4.0.0-alpha: tested and functional, but the API may still change before the final 4.0 release. See Connection types & capabilities.
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.
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 adminSee 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.
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 disposeChannels (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.
-
Unit testing without a router —
TikFakeConnection, tests with no hardware - MCP server — the same traces from an MCP client, over any transport
- Command translation on non-API transports — what the words become on each transport
- Exception handling — when the exception is enough