Skip to content

Entity value types

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

Entity value types

The typed structs entity properties use instead of string, and which RouterOS spelling each one corresponds to. Read this before you compare, parse or round-trip a value: the reason these types exist is that RouterOS writes the same value differently depending on which transport asked.

⚠️ 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.

All four are readonly struct, all convert implicitly to and from string, and all are declared nullable on entities (TikDuration?) because a record carries only the fields the router sent.

The conversions keep the value, not the spelling

Both directions are implicit — string in through Parse, string out through ToString(). What comes back is not the text you put in:

TikDuration time = "10:00:00";     // the CLI spelling, in
TimeSpan? ts = time.Value;         // 10:00:00
string back = time;                // "10h"  ← the API spelling, out

That is deliberate and it is the point of the type. 10:00:00 and 10h are the same duration written by two transports, so they parse to one value and compare equal — and ToString() always writes the compact form, which every transport accepts on write. A value read over the CLI therefore goes back to the router in the same shape it would have had over the API.

The consequence to know: never compare these as strings, and do not expect a value to round-trip character-for-character through the type. Compare Value (or the whole struct — == compares the value, not the text). The same holds for rates: "1M" comes back as "1000000", and "1Gbps" as "1000000000".

Type Used for The divergence it hides
TikDuration time spans and the words used in place of one 5s (API) vs 00:00:05 (CLI)
TikDataRate rates and sizes in bits per second 1000000 (API) vs 1M (CLI), plus 1Gbps
TikRatePair the upload/download pairs on /queue/simple 1000000/2000000 vs 1M/2M
TikRouterAddress where the router is: host, MAC, or both not a transport divergence — see below

TikDuration

A length of time, or one of the words RouterOS uses in place of one.

Value API / REST / WinBox native CLI transports
ten seconds 10s 00:00:10
five minutes 5m 00:05:00
one day 1d 1d00:00:00
200 ms 200ms 00:00:00.200
21:16:40 21h16m40s 21:16:40
no timeout none none

Same router, same field, same moment — the spelling depends only on who asked. A string property hands that straight to you, which is why these are typed.

var lease = connection.LoadSingle<IpDhcpServer>(...);

if (lease.LeaseTime?.Value is TimeSpan ts)      // a real duration
    Console.WriteLine(ts.TotalSeconds);
else
    Console.WriteLine(lease.LeaseTime?.Token);  // "none", "auto", "disabled", …

string forTheRouter = lease.LeaseTime;          // always the compact form

A word is a state, not a failure. lease-time=none, keepalive-timeout=disabled, enabled=auto — these are real router states, and a type that could not hold them would turn one state into another rather than into an error. See Words, and the difference from a gap.

ToString() always writes the compact form, which every transport accepts on write. So a value read over the CLI and written back is not silently reformatted.

TikDataRate

A rate or size in bits per second, written four ways for one value.

Spelling Where you see it
1000000 the binary API, and what ToString() writes
1M the CLI transports (print as-value)
1Mbps, 0bps, 1Gbps wherever RouterOS renders a rate for display — print stats over the CLI, /interface/ethernet monitor
unlimited, auto a word instead of a number

The suffixes are decimal, not binary: 500k is 500 000, not 512 000 — measured by writing limit-at=500k and reading back 500000.

The bps unit is not even consistent within one record: measured on RouterOS 7.24, /queue/simple print stats over the CLI writes rate=0bps/0bps while the single-valued total-rate on the same row is a bare 0.

TikDataRate? r = someEntity.MaxLimit;
long? bitsPerSecond = r?.Value;     // null when the router sent a word
TikDataRateSpecial? state = r?.Special;   // Unlimited / Auto / None — see below

Not every field that looks like a rate is one. rate-limit on a PPP profile packs six values into one string and dst-limit on a firewall rule packs a count, a burst and a mode — those stay string on purpose.

TikRatePair

The upload/download pair RouterOS uses for /queue/simple's max-limit, limit-at, burst-limit and burst-threshold.

API CLI
max-limit 1000000/2000000 1M/2M
var q = connection.LoadSingle<QueueSimple>(connection.CreateParameter("name", "customer-1"));
long? up   = q.MaxLimit?.Upload;
long? down = q.MaxLimit?.Download;

Two things worth knowing before you write one:

  • Writing one NUMBER means upload, with download zero. Setting max-limit=1M reads back as 1000000/0 — it does not mean "the same on both sides". That assumption silently halves a configuration. A bare word is different: unlimited describes the whole field, so it applies to both halves and writes back as unlimited/unlimited.
  • There is no conversion to a single number. A pair holds two, and picking one for you would be a guess. Read Upload or Download.

The single-valued max-limit on /queue/tree reads the same on every transport and stays a plain long — it is the pairing that changes the spelling, not the magnitude.


Words, and the difference from a gap

TikDuration and TikDataRate hold three different things, and telling them apart matters:

Kind What it means Read it from
Value a real duration / rate Value
Special a word RouterOS uses instead of a value — a real router state Special
Unknown text tik4net could not read and does not recognise — a gap in the library Token
TikDuration lease = entry.LeaseTime ?? default;

switch (lease.Kind)
{
    case TikValueKind.Value:                       // 3d, 00:30:00, …
        Console.WriteLine(lease.Value);
        break;
    case TikValueKind.Special when lease.Special == TikDurationSpecial.None:
        Console.WriteLine("no lease time set");    // never a string comparison
        break;
    case TikValueKind.Unknown:
        Console.WriteLine($"tik4net could not read '{lease.Token}'");
        break;
}

Writing one is named rather than spelled, so a typo is a compile error:

entry.LeaseTime = TikDuration.FromSpecial(TikDurationSpecial.None);        // sends "none"
queue.MaxLimit  = new TikRatePair(
    TikDataRate.FromSpecial(TikDataRateSpecial.Unlimited),
    TikDataRate.FromSpecial(TikDataRateSpecial.Unlimited));                // "unlimited/unlimited"

The words each type knows: durations take none, disabled, auto, never, immediately and forever; rates take unlimited, auto and none.

Why the enum is per type and not per field

The set of words a field accepts is really a property of that fieldlease-time takes none, ethernet bandwidth takes unlimited. But the property is a shared TikDuration? / TikDataRate?, so the type can only know which words durations use, not which subset this particular menu takes. Naming it TikDurationSpecial rather than something per-menu says exactly that much and no more; the router refuses a word its field does not accept, which is the check that actually decides.

Unknown is reported, not swallowed

Unknown is the honest fallback: the value survives verbatim, writes back unchanged, and the entity still loads — one property degrades instead of the whole row failing. But a token that behaves perfectly is how a missing word stays missing, so parsing one also raises a note on the value.token trace channel:

value.token -- TikDataRate could not read 'made-up-rate' as a value and does not recognise it as a
               RouterOS word — kept verbatim and written back unchanged; if the router really uses it,
               it belongs in TikSpecialWords

If you see one against a real router, that is a bug report worth filing — it means tik4net is missing a spelling the router actually uses.

TikRouterAddress

Where the router is. Unlike the three above this is not about a transport divergence — it exists because a router is reached by IP or by MAC, the two are alternatives rather than a pair, and both are strings so an overload cannot tell them apart.

TikRouterAddress.FromHost("192.168.88.1")                             // IP transports; MAC ones discover via MNDP
TikRouterAddress.FromMac("AA:BB:CC:DD:EE:FF")                         // MAC transports — no IP anywhere
TikRouterAddress.FromHostAndMac("192.168.88.1", "AA:BB:CC:DD:EE:FF")  // MAC transports, skipping the MNDP wait

Both together is a legitimate third case: on a MAC transport the host names the local interface to use and the MAC identifies the router, which saves up to 5 s of MNDP discovery per open.

Parse (and the implicit conversion from string) tells the two apart by shape — six hex pairs separated by : or - is a MAC and cannot be a host name or an IPv6 address, which needs eight groups or a ::.


Enums

Fields with a fixed set of RouterOS words are mapped to C# enums, each member carrying the router's spelling:

[TikEnum("in-interface")]
InInterface,

A value the enum does not know degrades that one property rather than failing the whole entity load — RouterOS adds words between versions, and losing a row because of one unrecognised option would be worse than losing the option.

MacAddress

tik4net.Objects.MacAddress is the MAC in 00:00:00:00:00:00 form, with implicit conversions both ways, used by helpers such as ExecuteWol. It is a convenience type on the entity side, not one of the transport-divergence types above.

See also

Clone this wiki locally