1 module snmp.oid;
2 
3 import c.net_snmp;
4 
5 private alias size_t = object.size_t;
6 
7 /// Fixed-capacity OID with inline storage.
8 struct OID
9 {
10     oid[MAX_OID_LEN] data = 0;
11     size_t length = 0;
12 
13     /// Returns invalid OID (length == 0) on failure.
14     static OID fromString(scope const(char)* str) @trusted @nogc nothrow
15     {
16         OID o;
17         o.length = MAX_OID_LEN;
18         if (!snmp_parse_oid(str, o.data.ptr, &o.length))
19             o.length = 0;
20         return o;
21     }
22 
23     static OID fromString(string s) @trusted nothrow
24     {
25         import std.string : toStringz;
26 
27         return fromString(s.toStringz);
28     }
29 
30     /// Truncates silently to MAX_OID_LEN.
31     static OID fromSlice(scope const(oid)[] src) @trusted @nogc nothrow
32     {
33         import core.stdc.string : memcpy;
34 
35         OID o;
36         o.length = src.length < MAX_OID_LEN ? src.length : MAX_OID_LEN;
37         memcpy(o.data.ptr, src.ptr, o.length * oid.sizeof);
38         return o;
39     }
40 
41     bool isValid() const @nogc nothrow @safe
42     {
43         return length > 0;
44     }
45 
46     inout(oid)* ptr() inout @trusted @nogc nothrow
47     {
48         return data.ptr;
49     }
50 
51     /// Returns 0 for out-of-bounds indices.
52     oid opIndex(size_t i) const @nogc nothrow @safe
53     {
54         return i < length ? data[i] : 0;
55     }
56 
57     int toString(scope char[] buf) const @trusted @nogc nothrow
58     {
59         if (!isValid || buf.length == 0)
60             return 0;
61         return snprint_objid(buf.ptr, buf.length, data.ptr, length);
62     }
63 
64     string toString() const @trusted nothrow
65     {
66         char[512] buf = void;
67         int n = toString(buf[]);
68         return n > 0 ? buf[0 .. n].idup : "(invalid)";
69     }
70 
71     bool opEquals(scope const OID rhs) const @trusted @nogc nothrow
72     {
73         if (length != rhs.length)
74             return false;
75         import core.stdc.string : memcmp;
76 
77         return memcmp(data.ptr, rhs.data.ptr, length * oid.sizeof) == 0;
78     }
79 
80     int opCmp(scope const OID rhs) const @trusted @nogc nothrow
81     {
82         import core.stdc.string : memcmp;
83 
84         size_t common = length < rhs.length ? length : rhs.length;
85         if (common)
86         {
87             int c = memcmp(data.ptr, rhs.data.ptr, common * oid.sizeof);
88             if (c != 0) return c;
89         }
90         return length < rhs.length ? -1 : length > rhs.length ? 1 : 0;
91     }
92 
93     /// True when this OID is a proper prefix of `other`.
94     bool isPrefixOf(scope const OID other) const @trusted @nogc nothrow
95     {
96         if (!isValid || length >= other.length)
97             return false;
98         import core.stdc.string : memcmp;
99 
100         return memcmp(data.ptr, other.data.ptr, length * oid.sizeof) == 0;
101     }
102 }
103 
104 unittest
105 {
106     init_snmp("snmp-d-test");
107 
108     auto o = OID.fromString("1.3.6.1.2.1.1.1.0");
109     assert(o.isValid);
110     assert(o.length == 9);
111     assert(o.data[0] == 1);
112     assert(o.data[1] == 3);
113     assert(o.data[2] == 6);
114 
115     assert(o[0] == 1);
116     assert(o[2] == 6);
117     assert(o[100] == 0);
118 
119     auto o2 = OID.fromString("1.3.6.1.2.1.1.1.0");
120     assert(o == o2);
121 
122     auto prefix = OID.fromString("1.3.6.1.2.1.1");
123     assert(prefix < o);
124     assert(o > prefix);
125 
126     auto sliced = OID.fromSlice(o.data[0 .. o.length]);
127     assert(sliced == o);
128 
129     assert(prefix.isValid);
130     assert(prefix.isPrefixOf(o));
131     assert(!o.isPrefixOf(prefix));
132 
133     OID empty;
134     assert(!empty.isValid);
135     assert(!empty.isPrefixOf(o));
136 }