Proof of concept mechanical port of ircnet/ircd to Rust as part of a bit about C being insecure for network services
1//! IRC reply and error numerics (RFC 1459 / IRCnet 2.11).
2//!
3//! Every server-to-client numeric reply is identified by a three-digit code: by
4//! convention `001`–`399` are replies (`RPL_*`) and `400`–`599` are errors
5//! (`ERR_*`). On the wire a numeric is sent as `:server NNN target ...`, where
6//! `NNN` is the zero-padded three-digit code — exactly what [`Numeric`]'s
7//! [`Display`](core::fmt::Display) produces.
8//!
9//! The variant set is the complete table from `common/numeric_def.h` in the C
10//! oracle. Variant names are the canonical IRC symbol with the `RPL_`/`ERR_`
11//! prefix kept (so that `RPL_STATSKLINE` = 216 and `ERR_STATSKLINE` = 499 stay
12//! distinct) and the remainder title-cased: `RPL_NOSUCHNICK` becomes
13//! [`Numeric::RplWelcome`]-style `RplNosuchnick`. The original symbol is always
14//! recoverable via [`Numeric::name`].
15//!
16//! ```
17//! use leveva::Numeric;
18//! assert_eq!(Numeric::RplWelcome.code(), 1);
19//! assert_eq!(Numeric::RplWelcome.to_string(), "001");
20//! assert_eq!(Numeric::ErrNicknameinuse.name(), "ERR_NICKNAMEINUSE");
21//! assert!(Numeric::ErrNosuchnick.is_error());
22//! assert_eq!(Numeric::try_from(433), Ok(Numeric::ErrNicknameinuse));
23//! ```
24
25/// An IRC reply or error numeric.
26///
27/// The discriminant of each variant is its three-digit numeric code, so
28/// `numeric as u16` (or [`Numeric::code`]) yields the wire value directly.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
30#[repr(u16)]
31#[non_exhaustive]
32pub enum Numeric {
33 /// `RPL_WELCOME`
34 RplWelcome = 1,
35 /// `RPL_YOURHOST`
36 RplYourhost = 2,
37 /// `RPL_CREATED`
38 RplCreated = 3,
39 /// `RPL_MYINFO`
40 RplMyinfo = 4,
41 /// `RPL_ISUPPORT`
42 RplIsupport = 5,
43 /// `RPL_BOUNCE`
44 RplBounce = 10,
45 /// `RPL_MAP`
46 RplMap = 15,
47 /// `RPL_MAPEND`
48 RplMapend = 17,
49 /// `RPL_MAPSTART`
50 RplMapstart = 18,
51 /// `RPL_HELLO`
52 RplHello = 20,
53 /// `RPL_YOURID`
54 RplYourid = 42,
55 /// `RPL_SAVENICK`
56 RplSavenick = 43,
57 /// `RPL_TRACELINK`
58 RplTracelink = 200,
59 /// `RPL_TRACECONNECTING`
60 RplTraceconnecting = 201,
61 /// `RPL_TRACEHANDSHAKE`
62 RplTracehandshake = 202,
63 /// `RPL_TRACEUNKNOWN`
64 RplTraceunknown = 203,
65 /// `RPL_TRACEOPERATOR`
66 RplTraceoperator = 204,
67 /// `RPL_TRACEUSER`
68 RplTraceuser = 205,
69 /// `RPL_TRACESERVER`
70 RplTraceserver = 206,
71 /// `RPL_TRACESERVICE`
72 RplTraceservice = 207,
73 /// `RPL_TRACENEWTYPE`
74 RplTracenewtype = 208,
75 /// `RPL_TRACECLASS`
76 RplTraceclass = 209,
77 /// `RPL_STATSLINKINFO`
78 RplStatslinkinfo = 211,
79 /// `RPL_STATSCOMMANDS`
80 RplStatscommands = 212,
81 /// `RPL_STATSCLINE`
82 RplStatscline = 213,
83 /// `RPL_STATSNLINE`
84 RplStatsnline = 214,
85 /// `RPL_STATSILINE`
86 RplStatsiline = 215,
87 /// `RPL_STATSKLINE`
88 RplStatskline = 216,
89 /// `RPL_STATSQLINE`
90 RplStatsqline = 217,
91 /// `RPL_STATSYLINE`
92 RplStatsyline = 218,
93 /// `RPL_ENDOFSTATS`
94 RplEndofstats = 219,
95 /// `RPL_UMODEIS`
96 RplUmodeis = 221,
97 /// `RPL_SERVICEINFO`
98 RplServiceinfo = 231,
99 /// `RPL_ENDOFSERVICES`
100 RplEndofservices = 232,
101 /// `RPL_SERVICE`
102 RplService = 233,
103 /// `RPL_SERVLIST`
104 RplServlist = 234,
105 /// `RPL_SERVLISTEND`
106 RplServlistend = 235,
107 /// `RPL_STATSIAUTH`
108 RplStatsiauth = 239,
109 /// `RPL_STATSVLINE`
110 RplStatsvline = 240,
111 /// `RPL_STATSLLINE`
112 RplStatslline = 241,
113 /// `RPL_STATSUPTIME`
114 RplStatsuptime = 242,
115 /// `RPL_STATSOLINE`
116 RplStatsoline = 243,
117 /// `RPL_STATSHLINE`
118 RplStatshline = 244,
119 /// `RPL_STATSSLINE`
120 RplStatssline = 245,
121 /// `RPL_STATSPING`
122 RplStatsping = 246,
123 /// `RPL_STATSBLINE`
124 RplStatsbline = 247,
125 /// `RPL_STATSDEFINE`
126 RplStatsdefine = 248,
127 /// `RPL_STATSDEBUG`
128 RplStatsdebug = 249,
129 /// `RPL_STATSDLINE`
130 RplStatsdline = 250,
131 /// `RPL_LUSERCLIENT`
132 RplLuserclient = 251,
133 /// `RPL_LUSEROP`
134 RplLuserop = 252,
135 /// `RPL_LUSERUNKNOWN`
136 RplLuserunknown = 253,
137 /// `RPL_LUSERCHANNELS`
138 RplLuserchannels = 254,
139 /// `RPL_LUSERME`
140 RplLuserme = 255,
141 /// `RPL_ADMINME`
142 RplAdminme = 256,
143 /// `RPL_ADMINLOC1`
144 RplAdminloc1 = 257,
145 /// `RPL_ADMINLOC2`
146 RplAdminloc2 = 258,
147 /// `RPL_ADMINEMAIL`
148 RplAdminemail = 259,
149 /// `RPL_TRACEEND`
150 RplTraceend = 262,
151 /// `RPL_TRYAGAIN`
152 RplTryagain = 263,
153 /// `RPL_LOCALUSERS`
154 RplLocalusers = 265,
155 /// `RPL_GLOBALUSERS`
156 RplGlobalusers = 266,
157 /// `RPL_WHOISCERTFP` (from oftc-hybrid / charybdis): a WHOIS line carrying the
158 /// target's client-certificate fingerprint (CertFP, P11 slice 78).
159 RplWhoiscertfp = 276,
160 /// `RPL_NONE`
161 RplNone = 300,
162 /// `RPL_AWAY`
163 RplAway = 301,
164 /// `RPL_USERHOST`
165 RplUserhost = 302,
166 /// `RPL_ISON`
167 RplIson = 303,
168 /// `RPL_TEXT`
169 RplText = 304,
170 /// `RPL_UNAWAY`
171 RplUnaway = 305,
172 /// `RPL_NOWAWAY`
173 RplNowaway = 306,
174 /// `RPL_WHOISUSER`
175 RplWhoisuser = 311,
176 /// `RPL_WHOISSERVER`
177 RplWhoisserver = 312,
178 /// `RPL_WHOISOPERATOR`
179 RplWhoisoperator = 313,
180 /// `RPL_WHOWASUSER`
181 RplWhowasuser = 314,
182 /// `RPL_ENDOFWHO`
183 RplEndofwho = 315,
184 /// `RPL_WHOISCHANOP` — redundant and not needed but reserved
185 RplWhoischanop = 316,
186 /// `RPL_WHOISIDLE`
187 RplWhoisidle = 317,
188 /// `RPL_ENDOFWHOIS`
189 RplEndofwhois = 318,
190 /// `RPL_WHOISCHANNELS`
191 RplWhoischannels = 319,
192 /// `RPL_WHOISEXTRA` — catch-all extra WHOIS information reply -- mh 2020-01-11
193 RplWhoisextra = 320,
194 /// `RPL_WHOISACCOUNT` — the target is logged in to a services account (leveva-native, no
195 /// C oracle; the de-facto charybdis/ratbox code). `<nick> <account> :is logged in as`.
196 RplWhoisaccount = 330,
197 /// `RPL_WHOISBOT` — IRCv3 Bot Mode: the target is a bot (leveva-native, no C oracle).
198 RplWhoisbot = 335,
199 /// `RPL_LISTSTART`
200 RplListstart = 321,
201 /// `RPL_LIST`
202 RplList = 322,
203 /// `RPL_LISTEND`
204 RplListend = 323,
205 /// `RPL_ACCEPTLIST` — caller-id (`+g`): one entry of the client's `ACCEPT` list,
206 /// `<nick>` (leveva-native; the charybdis/Hybrid convention, slice 216).
207 RplAcceptlist = 281,
208 /// `RPL_ENDOFACCEPT` — caller-id: terminates an `ACCEPT` list reply,
209 /// `:End of /ACCEPT list.` (slice 216).
210 RplEndofaccept = 282,
211 /// `RPL_CHANNELMODEIS`
212 RplChannelmodeis = 324,
213 /// `RPL_UNIQOPIS`
214 RplUniqopis = 325,
215 /// `RPL_NOTOPIC`
216 RplNotopic = 331,
217 /// `RPL_TOPIC`
218 RplTopic = 332,
219 /// `RPL_TOPIC_WHO_TIME`
220 RplTopicWhoTime = 333,
221 /// `RPL_INVITING`
222 RplInviting = 341,
223 /// `RPL_SUMMONING`
224 RplSummoning = 342,
225 /// `RPL_REOPLIST`
226 RplReoplist = 344,
227 /// `RPL_ENDOFREOPLIST`
228 RplEndofreoplist = 345,
229 /// `RPL_INVITELIST`
230 RplInvitelist = 346,
231 /// `RPL_ENDOFINVITELIST`
232 RplEndofinvitelist = 347,
233 /// `RPL_EXCEPTLIST`
234 RplExceptlist = 348,
235 /// `RPL_ENDOFEXCEPTLIST`
236 RplEndofexceptlist = 349,
237 /// `RPL_VERSION`
238 RplVersion = 351,
239 /// `RPL_WHOREPLY`
240 RplWhoreply = 352,
241 /// `RPL_NAMREPLY`
242 RplNamreply = 353,
243 /// `RPL_WHOSPCRPL`
244 RplWhospcrpl = 354,
245 /// `RPL_KILLDONE`
246 RplKilldone = 361,
247 /// `RPL_CLOSING`
248 RplClosing = 362,
249 /// `RPL_CLOSEEND`
250 RplCloseend = 363,
251 /// `RPL_LINKS`
252 RplLinks = 364,
253 /// `RPL_ENDOFLINKS`
254 RplEndoflinks = 365,
255 /// `RPL_ENDOFNAMES`
256 RplEndofnames = 366,
257 /// `RPL_BANLIST`
258 RplBanlist = 367,
259 /// `RPL_ENDOFBANLIST`
260 RplEndofbanlist = 368,
261 /// `RPL_ENDOFWHOWAS`
262 RplEndofwhowas = 369,
263 /// `RPL_INFO`
264 RplInfo = 371,
265 /// `RPL_MOTD`
266 RplMotd = 372,
267 /// `RPL_INFOSTART`
268 RplInfostart = 373,
269 /// `RPL_ENDOFINFO`
270 RplEndofinfo = 374,
271 /// `RPL_MOTDSTART`
272 RplMotdstart = 375,
273 /// `RPL_ENDOFMOTD`
274 RplEndofmotd = 376,
275 /// `RPL_WHOISMODES` — WHOIS reports the target's user-mode string. leveva-native: the
276 /// IRCnet 2.11 oracle has no such numeric; `379` is the de-facto code used by
277 /// charybdis/ratbox/InspIRCd. Privacy-gated to the target themselves or an operator.
278 RplWhoismodes = 379,
279 /// `RPL_YOUREOPER`
280 RplYoureoper = 381,
281 /// `RPL_REHASHING`
282 RplRehashing = 382,
283 /// `RPL_YOURESERVICE`
284 RplYoureservice = 383,
285 /// `RPL_MYPORTIS`
286 RplMyportis = 384,
287 /// `RPL_NOTOPERANYMORE`
288 RplNotoperanymore = 385,
289 /// `RPL_TIME`
290 RplTime = 391,
291 /// `RPL_USERSSTART`
292 RplUsersstart = 392,
293 /// `RPL_USERS`
294 RplUsers = 393,
295 /// `RPL_ENDOFUSERS`
296 RplEndofusers = 394,
297 /// `RPL_NOUSERS`
298 RplNousers = 395,
299 /// `RPL_HOSTHIDDEN` — confirms a client's host is now cloaked (`+x`).
300 RplHostHidden = 396,
301 /// `ERR_NOSUCHNICK`
302 ErrNosuchnick = 401,
303 /// `ERR_NOSUCHSERVER`
304 ErrNosuchserver = 402,
305 /// `ERR_NOSUCHCHANNEL`
306 ErrNosuchchannel = 403,
307 /// `ERR_CANNOTSENDTOCHAN`
308 ErrCannotsendtochan = 404,
309 /// `ERR_TOOMANYCHANNELS`
310 ErrToomanychannels = 405,
311 /// `ERR_WASNOSUCHNICK`
312 ErrWasnosuchnick = 406,
313 /// `ERR_TOOMANYTARGETS`
314 ErrToomanytargets = 407,
315 /// `ERR_NOSUCHSERVICE`
316 ErrNosuchservice = 408,
317 /// `ERR_NOORIGIN`
318 ErrNoorigin = 409,
319 /// `ERR_INVALIDCAPCMD` (IRCv3 capability negotiation)
320 ErrInvalidcapcmd = 410,
321 /// `ERR_NORECIPIENT`
322 ErrNorecipient = 411,
323 /// `ERR_NOTEXTTOSEND`
324 ErrNotexttosend = 412,
325 /// `ERR_NOTOPLEVEL`
326 ErrNotoplevel = 413,
327 /// `ERR_WILDTOPLEVEL`
328 ErrWildtoplevel = 414,
329 /// `ERR_BADMASK`
330 ErrBadmask = 415,
331 /// `ERR_TOOMANYMATCHES`
332 ErrToomanymatches = 416,
333 /// `ERR_INPUTTOOLONG` (IRCv3 message-tags: tag data over the 8191-byte limit)
334 ErrInputtoolong = 417,
335 /// `ERR_UNKNOWNCOMMAND`
336 ErrUnknowncommand = 421,
337 /// `ERR_NOMOTD`
338 ErrNomotd = 422,
339 /// `ERR_NOADMININFO`
340 ErrNoadmininfo = 423,
341 /// `ERR_FILEERROR`
342 ErrFileerror = 424,
343 /// `ERR_NONICKNAMEGIVEN`
344 ErrNonicknamegiven = 431,
345 /// `ERR_ERRONEOUSNICKNAME`
346 ErrErroneousnickname = 432,
347 /// `ERR_NICKNAMEINUSE`
348 ErrNicknameinuse = 433,
349 /// `ERR_SERVICENAMEINUSE`
350 ErrServicenameinuse = 434,
351 /// `ERR_SERVICECONFUSED`
352 ErrServiceconfused = 435,
353 /// `ERR_NICKCOLLISION`
354 ErrNickcollision = 436,
355 /// `ERR_UNAVAILRESOURCE`
356 ErrUnavailresource = 437,
357 /// `ERR_USERNOTINCHANNEL`
358 ErrUsernotinchannel = 441,
359 /// `ERR_NOTONCHANNEL`
360 ErrNotonchannel = 442,
361 /// `ERR_USERONCHANNEL`
362 ErrUseronchannel = 443,
363 /// `ERR_NOLOGIN`
364 ErrNologin = 444,
365 /// `ERR_SUMMONDISABLED`
366 ErrSummondisabled = 445,
367 /// `ERR_USERSDISABLED`
368 ErrUsersdisabled = 446,
369 /// `ERR_NOTREGISTERED`
370 ErrNotregistered = 451,
371 /// `ERR_ACCEPTFULL` — caller-id: the `ACCEPT` list is at its limit,
372 /// `:Accept list is full` (leveva-native; the charybdis convention, slice 216).
373 ErrAcceptfull = 456,
374 /// `ERR_ACCEPTEXIST` — caller-id: the nick is already on the `ACCEPT` list,
375 /// `<nick> :is already on your accept list.` (slice 216).
376 ErrAcceptexist = 457,
377 /// `ERR_ACCEPTNOT` — caller-id: the nick is not on the `ACCEPT` list (a remove that missed),
378 /// `<nick> :is not on your accept list.` (slice 216).
379 ErrAcceptnot = 458,
380 /// `ERR_NEEDMOREPARAMS`
381 ErrNeedmoreparams = 461,
382 /// `ERR_ALREADYREGISTRED`
383 ErrAlreadyregistred = 462,
384 /// `ERR_NOPERMFORHOST`
385 ErrNopermforhost = 463,
386 /// `ERR_PASSWDMISMATCH`
387 ErrPasswdmismatch = 464,
388 /// `ERR_YOUREBANNEDCREEP`
389 ErrYourebannedcreep = 465,
390 /// `ERR_YOUWILLBEBANNED`
391 ErrYouwillbebanned = 466,
392 /// `ERR_KEYSET`
393 ErrKeyset = 467,
394 /// `ERR_CHANNELISFULL`
395 ErrChannelisfull = 471,
396 /// `ERR_UNKNOWNMODE`
397 ErrUnknownmode = 472,
398 /// `ERR_INVITEONLYCHAN`
399 ErrInviteonlychan = 473,
400 /// `ERR_BANNEDFROMCHAN`
401 ErrBannedfromchan = 474,
402 /// `ERR_BADCHANNELKEY`
403 ErrBadchannelkey = 475,
404 /// `ERR_BADCHANMASK`
405 ErrBadchanmask = 476,
406 /// `ERR_NOCHANMODES`
407 ErrNochanmodes = 477,
408 /// `ERR_BANLISTFULL`
409 ErrBanlistfull = 478,
410 /// `ERR_NOPRIVILEGES`
411 ErrNoprivileges = 481,
412 /// `ERR_CHANOPRIVSNEEDED`
413 ErrChanoprivsneeded = 482,
414 /// `ERR_CANTKILLSERVER`
415 ErrCantkillserver = 483,
416 /// `ERR_RESTRICTED`
417 ErrRestricted = 484,
418 /// `ERR_UNIQOPRIVSNEEDED`
419 ErrUniqoprivsneeded = 485,
420 /// `ERR_NOOPERHOST`
421 ErrNooperhost = 491,
422 /// `ERR_NOSERVICEHOST`
423 ErrNoservicehost = 492,
424 /// `ERR_STATSKLINE`
425 ErrStatskline = 499,
426 /// `ERR_UMODEUNKNOWNFLAG`
427 ErrUmodeunknownflag = 501,
428 /// `ERR_USERSDONTMATCH`
429 ErrUsersdontmatch = 502,
430 /// `ERR_CANTJOINOPERSONLY` — JOIN of an oper-only (`+O`) channel by a non-oper
431 /// (leveva-native; the InspIRCd/charybdis convention).
432 ErrCantjoinopersonly = 520,
433 /// `RPL_ETRACEFULL`
434 RplEtracefull = 708,
435 /// `RPL_KNOCK` — delivered to a channel's operators when a non-member `KNOCK`s on a
436 /// restricted (`+i`/`+k`/`+l`) channel: `<channel> <nick>!<user>@<host> :<reason>`
437 /// (leveva-native; the charybdis/ratbox convention, slice 198).
438 RplKnock = 710,
439 /// `RPL_KNOCKDLVR` — acknowledges to the knocker that the `KNOCK` was delivered:
440 /// `<channel> :Your KNOCK has been delivered` (slice 198).
441 RplKnockdlvr = 711,
442 /// `ERR_TOOMANYKNOCK` — `KNOCK`ing a channel still within its per-channel flood cooldown:
443 /// `<channel> :Too many KNOCKs (<channel>)` (leveva-native; the charybdis convention, slice
444 /// 200). See [`crate::knock_throttle`].
445 ErrToomanyknock = 712,
446 /// `ERR_CHANOPEN` — `KNOCK`ing on a channel that imposes no join restriction (you could
447 /// just `JOIN`): `<channel> :Channel is open` (slice 198).
448 ErrChanopen = 713,
449 /// `ERR_KNOCKONCHAN` — `KNOCK`ing on a channel you are already on:
450 /// `<channel> :You are already on that channel` (slice 198).
451 ErrKnockonchan = 714,
452 /// `ERR_TARGUMODEG` — caller-id: the PRIVMSG target is in `+g` and you are not on its accept
453 /// list, `<nick> :is in +g mode (server-side ignore.)` (leveva-native; the charybdis
454 /// convention, slice 216).
455 ErrTargumodeg = 716,
456 /// `RPL_TARGNOTIFY` — caller-id: confirms to the sender that the `+g` target was told you
457 /// messaged them, `<nick> :has been informed that you messaged them.` (slice 216).
458 RplTargnotify = 717,
459 /// `RPL_UMODEGMSG` — caller-id: tells a `+g` user that someone (not on their accept list)
460 /// tried to message them, `<nick> <user>@<host> :is messaging you, and you have umode +g.`
461 /// (slice 216).
462 RplUmodegmsg = 718,
463 /// `RPL_MONONLINE` — a monitored target is online (IRCv3 `monitor`).
464 RplMononline = 730,
465 /// `RPL_MONOFFLINE` — a monitored target is offline (IRCv3 `monitor`).
466 RplMonoffline = 731,
467 /// `RPL_MONLIST` — a chunk of the client's monitor list (IRCv3 `monitor`).
468 RplMonlist = 732,
469 /// `RPL_ENDOFMONLIST` — terminates a `MONITOR L` reply (IRCv3 `monitor`).
470 RplEndofmonlist = 733,
471 /// `ERR_MONLISTFULL` — the monitor list is full; the add failed (IRCv3 `monitor`).
472 ErrMonlistfull = 734,
473 /// `RPL_ETRACEEND`
474 RplEtraceend = 759,
475 /// `RPL_KEYVALUE` — IRCv3 metadata: a key's value on a target (slice 163).
476 /// `<client> <target> <key> <visibility> :<value>`.
477 RplKeyvalue = 761,
478 /// `RPL_KEYNOTSET` — IRCv3 metadata: a key has no value on a target (slice 163).
479 /// `<client> <target> <key> :key not set`.
480 RplKeynotset = 766,
481 /// `RPL_METADATASUBOK` — IRCv3 metadata: keys successfully subscribed to (slice 163).
482 /// `<client> <key1> [<key2> ...]`.
483 RplMetadatasubok = 770,
484 /// `RPL_METADATAUNSUBOK` — IRCv3 metadata: keys successfully unsubscribed from (slice 163).
485 /// `<client> <key1> [<key2> ...]`.
486 RplMetadataunsubok = 771,
487 /// `RPL_METADATASUBS` — IRCv3 metadata: the client's current key subscriptions (slice 163).
488 /// `<client> <key1> [<key2> ...]`.
489 RplMetadatasubs = 772,
490 /// `RPL_LOGGEDIN` — SASL: the client is now logged in to a services account (IRCv3 SASL).
491 /// `<nick> <nick>!<user>@<host> <account> :You are now logged in as <account>`.
492 RplLoggedin = 900,
493 /// `RPL_LOGGEDOUT` — SASL: the client is now logged out (IRCv3 SASL).
494 RplLoggedout = 901,
495 /// `ERR_NICKLOCKED` — SASL: the account requires a specific (assigned) nick (IRCv3 SASL).
496 ErrNicklocked = 902,
497 /// `RPL_SASLSUCCESS` — SASL authentication succeeded (IRCv3 SASL).
498 RplSaslsuccess = 903,
499 /// `ERR_SASLFAIL` — SASL authentication failed (IRCv3 SASL).
500 ErrSaslfail = 904,
501 /// `ERR_SASLTOOLONG` — a SASL `AUTHENTICATE` payload exceeded the size limit (IRCv3 SASL).
502 ErrSasltoolong = 905,
503 /// `ERR_SASLABORTED` — the client aborted SASL with `AUTHENTICATE *` (IRCv3 SASL).
504 ErrSaslaborted = 906,
505 /// `ERR_SASLALREADY` — the client has already authenticated via SASL (IRCv3 SASL).
506 ErrSaslalready = 907,
507 /// `RPL_SASLMECHS` — the list of available SASL mechanisms (IRCv3 SASL).
508 /// `<nick> <mechanisms> :are available SASL mechanisms`.
509 RplSaslmechs = 908,
510}
511
512impl Numeric {
513 /// `RPL_WHOISTLS` — client is connected with SSL/TLS -- mh 2020-04-27.
514 ///
515 /// An alias for [`Numeric::RplWhoisextra`]: the C oracle defines
516 /// `#define RPL_WHOISTLS RPL_WHOISEXTRA`, so both names share code
517 /// 320.
518 pub const RPL_WHOISTLS: Numeric = Numeric::RplWhoisextra;
519
520 /// The three-digit numeric code (this variant's discriminant).
521 #[inline]
522 pub const fn code(self) -> u16 {
523 self as u16
524 }
525
526 /// `true` if this is an error numeric.
527 ///
528 /// By IRC convention codes in the range `400`–`599` are errors (`ERR_*`);
529 /// everything else is an informational reply (`RPL_*`).
530 #[inline]
531 pub const fn is_error(self) -> bool {
532 let c = self as u16;
533 c >= 400 && c < 600
534 }
535
536 /// The canonical IRC symbol for this numeric, e.g. `"RPL_WELCOME"`.
537 pub const fn name(self) -> &'static str {
538 match self {
539 Numeric::RplWelcome => "RPL_WELCOME",
540 Numeric::RplYourhost => "RPL_YOURHOST",
541 Numeric::RplCreated => "RPL_CREATED",
542 Numeric::RplMyinfo => "RPL_MYINFO",
543 Numeric::RplIsupport => "RPL_ISUPPORT",
544 Numeric::RplBounce => "RPL_BOUNCE",
545 Numeric::RplMap => "RPL_MAP",
546 Numeric::RplMapend => "RPL_MAPEND",
547 Numeric::RplMapstart => "RPL_MAPSTART",
548 Numeric::RplHello => "RPL_HELLO",
549 Numeric::RplYourid => "RPL_YOURID",
550 Numeric::RplSavenick => "RPL_SAVENICK",
551 Numeric::RplTracelink => "RPL_TRACELINK",
552 Numeric::RplTraceconnecting => "RPL_TRACECONNECTING",
553 Numeric::RplTracehandshake => "RPL_TRACEHANDSHAKE",
554 Numeric::RplTraceunknown => "RPL_TRACEUNKNOWN",
555 Numeric::RplTraceoperator => "RPL_TRACEOPERATOR",
556 Numeric::RplTraceuser => "RPL_TRACEUSER",
557 Numeric::RplTraceserver => "RPL_TRACESERVER",
558 Numeric::RplTraceservice => "RPL_TRACESERVICE",
559 Numeric::RplTracenewtype => "RPL_TRACENEWTYPE",
560 Numeric::RplTraceclass => "RPL_TRACECLASS",
561 Numeric::RplStatslinkinfo => "RPL_STATSLINKINFO",
562 Numeric::RplStatscommands => "RPL_STATSCOMMANDS",
563 Numeric::RplStatscline => "RPL_STATSCLINE",
564 Numeric::RplStatsnline => "RPL_STATSNLINE",
565 Numeric::RplStatsiline => "RPL_STATSILINE",
566 Numeric::RplStatskline => "RPL_STATSKLINE",
567 Numeric::RplStatsqline => "RPL_STATSQLINE",
568 Numeric::RplStatsyline => "RPL_STATSYLINE",
569 Numeric::RplEndofstats => "RPL_ENDOFSTATS",
570 Numeric::RplUmodeis => "RPL_UMODEIS",
571 Numeric::RplServiceinfo => "RPL_SERVICEINFO",
572 Numeric::RplEndofservices => "RPL_ENDOFSERVICES",
573 Numeric::RplService => "RPL_SERVICE",
574 Numeric::RplServlist => "RPL_SERVLIST",
575 Numeric::RplServlistend => "RPL_SERVLISTEND",
576 Numeric::RplStatsiauth => "RPL_STATSIAUTH",
577 Numeric::RplStatsvline => "RPL_STATSVLINE",
578 Numeric::RplStatslline => "RPL_STATSLLINE",
579 Numeric::RplStatsuptime => "RPL_STATSUPTIME",
580 Numeric::RplStatsoline => "RPL_STATSOLINE",
581 Numeric::RplStatshline => "RPL_STATSHLINE",
582 Numeric::RplStatssline => "RPL_STATSSLINE",
583 Numeric::RplStatsping => "RPL_STATSPING",
584 Numeric::RplStatsbline => "RPL_STATSBLINE",
585 Numeric::RplStatsdefine => "RPL_STATSDEFINE",
586 Numeric::RplStatsdebug => "RPL_STATSDEBUG",
587 Numeric::RplStatsdline => "RPL_STATSDLINE",
588 Numeric::RplLuserclient => "RPL_LUSERCLIENT",
589 Numeric::RplLuserop => "RPL_LUSEROP",
590 Numeric::RplLuserunknown => "RPL_LUSERUNKNOWN",
591 Numeric::RplLuserchannels => "RPL_LUSERCHANNELS",
592 Numeric::RplLuserme => "RPL_LUSERME",
593 Numeric::RplAdminme => "RPL_ADMINME",
594 Numeric::RplAdminloc1 => "RPL_ADMINLOC1",
595 Numeric::RplAdminloc2 => "RPL_ADMINLOC2",
596 Numeric::RplAdminemail => "RPL_ADMINEMAIL",
597 Numeric::RplTraceend => "RPL_TRACEEND",
598 Numeric::RplTryagain => "RPL_TRYAGAIN",
599 Numeric::RplLocalusers => "RPL_LOCALUSERS",
600 Numeric::RplGlobalusers => "RPL_GLOBALUSERS",
601 Numeric::RplNone => "RPL_NONE",
602 Numeric::RplAway => "RPL_AWAY",
603 Numeric::RplUserhost => "RPL_USERHOST",
604 Numeric::RplIson => "RPL_ISON",
605 Numeric::RplText => "RPL_TEXT",
606 Numeric::RplUnaway => "RPL_UNAWAY",
607 Numeric::RplNowaway => "RPL_NOWAWAY",
608 Numeric::RplWhoisuser => "RPL_WHOISUSER",
609 Numeric::RplWhoisserver => "RPL_WHOISSERVER",
610 Numeric::RplWhoisoperator => "RPL_WHOISOPERATOR",
611 Numeric::RplWhowasuser => "RPL_WHOWASUSER",
612 Numeric::RplEndofwho => "RPL_ENDOFWHO",
613 Numeric::RplWhoischanop => "RPL_WHOISCHANOP",
614 Numeric::RplWhoisidle => "RPL_WHOISIDLE",
615 Numeric::RplEndofwhois => "RPL_ENDOFWHOIS",
616 Numeric::RplWhoischannels => "RPL_WHOISCHANNELS",
617 Numeric::RplWhoiscertfp => "RPL_WHOISCERTFP",
618 Numeric::RplWhoisextra => "RPL_WHOISEXTRA",
619 Numeric::RplWhoisaccount => "RPL_WHOISACCOUNT",
620 Numeric::RplWhoisbot => "RPL_WHOISBOT",
621 Numeric::RplListstart => "RPL_LISTSTART",
622 Numeric::RplList => "RPL_LIST",
623 Numeric::RplListend => "RPL_LISTEND",
624 Numeric::RplChannelmodeis => "RPL_CHANNELMODEIS",
625 Numeric::RplUniqopis => "RPL_UNIQOPIS",
626 Numeric::RplNotopic => "RPL_NOTOPIC",
627 Numeric::RplTopic => "RPL_TOPIC",
628 Numeric::RplTopicWhoTime => "RPL_TOPIC_WHO_TIME",
629 Numeric::RplInviting => "RPL_INVITING",
630 Numeric::RplSummoning => "RPL_SUMMONING",
631 Numeric::RplReoplist => "RPL_REOPLIST",
632 Numeric::RplEndofreoplist => "RPL_ENDOFREOPLIST",
633 Numeric::RplInvitelist => "RPL_INVITELIST",
634 Numeric::RplEndofinvitelist => "RPL_ENDOFINVITELIST",
635 Numeric::RplExceptlist => "RPL_EXCEPTLIST",
636 Numeric::RplEndofexceptlist => "RPL_ENDOFEXCEPTLIST",
637 Numeric::RplVersion => "RPL_VERSION",
638 Numeric::RplWhoreply => "RPL_WHOREPLY",
639 Numeric::RplNamreply => "RPL_NAMREPLY",
640 Numeric::RplWhospcrpl => "RPL_WHOSPCRPL",
641 Numeric::RplKilldone => "RPL_KILLDONE",
642 Numeric::RplClosing => "RPL_CLOSING",
643 Numeric::RplCloseend => "RPL_CLOSEEND",
644 Numeric::RplLinks => "RPL_LINKS",
645 Numeric::RplEndoflinks => "RPL_ENDOFLINKS",
646 Numeric::RplEndofnames => "RPL_ENDOFNAMES",
647 Numeric::RplBanlist => "RPL_BANLIST",
648 Numeric::RplEndofbanlist => "RPL_ENDOFBANLIST",
649 Numeric::RplEndofwhowas => "RPL_ENDOFWHOWAS",
650 Numeric::RplInfo => "RPL_INFO",
651 Numeric::RplMotd => "RPL_MOTD",
652 Numeric::RplInfostart => "RPL_INFOSTART",
653 Numeric::RplEndofinfo => "RPL_ENDOFINFO",
654 Numeric::RplMotdstart => "RPL_MOTDSTART",
655 Numeric::RplEndofmotd => "RPL_ENDOFMOTD",
656 Numeric::RplWhoismodes => "RPL_WHOISMODES",
657 Numeric::RplYoureoper => "RPL_YOUREOPER",
658 Numeric::RplRehashing => "RPL_REHASHING",
659 Numeric::RplYoureservice => "RPL_YOURESERVICE",
660 Numeric::RplMyportis => "RPL_MYPORTIS",
661 Numeric::RplNotoperanymore => "RPL_NOTOPERANYMORE",
662 Numeric::RplTime => "RPL_TIME",
663 Numeric::RplUsersstart => "RPL_USERSSTART",
664 Numeric::RplUsers => "RPL_USERS",
665 Numeric::RplEndofusers => "RPL_ENDOFUSERS",
666 Numeric::RplNousers => "RPL_NOUSERS",
667 Numeric::RplHostHidden => "RPL_HOSTHIDDEN",
668 Numeric::ErrNosuchnick => "ERR_NOSUCHNICK",
669 Numeric::ErrNosuchserver => "ERR_NOSUCHSERVER",
670 Numeric::ErrNosuchchannel => "ERR_NOSUCHCHANNEL",
671 Numeric::ErrCannotsendtochan => "ERR_CANNOTSENDTOCHAN",
672 Numeric::ErrToomanychannels => "ERR_TOOMANYCHANNELS",
673 Numeric::ErrWasnosuchnick => "ERR_WASNOSUCHNICK",
674 Numeric::ErrToomanytargets => "ERR_TOOMANYTARGETS",
675 Numeric::ErrNosuchservice => "ERR_NOSUCHSERVICE",
676 Numeric::ErrNoorigin => "ERR_NOORIGIN",
677 Numeric::ErrInvalidcapcmd => "ERR_INVALIDCAPCMD",
678 Numeric::ErrNorecipient => "ERR_NORECIPIENT",
679 Numeric::ErrNotexttosend => "ERR_NOTEXTTOSEND",
680 Numeric::ErrNotoplevel => "ERR_NOTOPLEVEL",
681 Numeric::ErrWildtoplevel => "ERR_WILDTOPLEVEL",
682 Numeric::ErrBadmask => "ERR_BADMASK",
683 Numeric::ErrToomanymatches => "ERR_TOOMANYMATCHES",
684 Numeric::ErrInputtoolong => "ERR_INPUTTOOLONG",
685 Numeric::ErrUnknowncommand => "ERR_UNKNOWNCOMMAND",
686 Numeric::ErrNomotd => "ERR_NOMOTD",
687 Numeric::ErrNoadmininfo => "ERR_NOADMININFO",
688 Numeric::ErrFileerror => "ERR_FILEERROR",
689 Numeric::ErrNonicknamegiven => "ERR_NONICKNAMEGIVEN",
690 Numeric::ErrErroneousnickname => "ERR_ERRONEOUSNICKNAME",
691 Numeric::ErrNicknameinuse => "ERR_NICKNAMEINUSE",
692 Numeric::ErrServicenameinuse => "ERR_SERVICENAMEINUSE",
693 Numeric::ErrServiceconfused => "ERR_SERVICECONFUSED",
694 Numeric::ErrNickcollision => "ERR_NICKCOLLISION",
695 Numeric::ErrUnavailresource => "ERR_UNAVAILRESOURCE",
696 Numeric::ErrUsernotinchannel => "ERR_USERNOTINCHANNEL",
697 Numeric::ErrNotonchannel => "ERR_NOTONCHANNEL",
698 Numeric::ErrUseronchannel => "ERR_USERONCHANNEL",
699 Numeric::ErrNologin => "ERR_NOLOGIN",
700 Numeric::ErrSummondisabled => "ERR_SUMMONDISABLED",
701 Numeric::ErrUsersdisabled => "ERR_USERSDISABLED",
702 Numeric::ErrNotregistered => "ERR_NOTREGISTERED",
703 Numeric::ErrNeedmoreparams => "ERR_NEEDMOREPARAMS",
704 Numeric::ErrAlreadyregistred => "ERR_ALREADYREGISTRED",
705 Numeric::ErrNopermforhost => "ERR_NOPERMFORHOST",
706 Numeric::ErrPasswdmismatch => "ERR_PASSWDMISMATCH",
707 Numeric::ErrYourebannedcreep => "ERR_YOUREBANNEDCREEP",
708 Numeric::ErrYouwillbebanned => "ERR_YOUWILLBEBANNED",
709 Numeric::ErrKeyset => "ERR_KEYSET",
710 Numeric::ErrChannelisfull => "ERR_CHANNELISFULL",
711 Numeric::ErrUnknownmode => "ERR_UNKNOWNMODE",
712 Numeric::ErrInviteonlychan => "ERR_INVITEONLYCHAN",
713 Numeric::ErrBannedfromchan => "ERR_BANNEDFROMCHAN",
714 Numeric::ErrBadchannelkey => "ERR_BADCHANNELKEY",
715 Numeric::ErrBadchanmask => "ERR_BADCHANMASK",
716 Numeric::ErrNochanmodes => "ERR_NOCHANMODES",
717 Numeric::ErrBanlistfull => "ERR_BANLISTFULL",
718 Numeric::ErrNoprivileges => "ERR_NOPRIVILEGES",
719 Numeric::ErrChanoprivsneeded => "ERR_CHANOPRIVSNEEDED",
720 Numeric::ErrCantkillserver => "ERR_CANTKILLSERVER",
721 Numeric::ErrRestricted => "ERR_RESTRICTED",
722 Numeric::ErrUniqoprivsneeded => "ERR_UNIQOPRIVSNEEDED",
723 Numeric::ErrNooperhost => "ERR_NOOPERHOST",
724 Numeric::ErrNoservicehost => "ERR_NOSERVICEHOST",
725 Numeric::ErrStatskline => "ERR_STATSKLINE",
726 Numeric::ErrUmodeunknownflag => "ERR_UMODEUNKNOWNFLAG",
727 Numeric::ErrUsersdontmatch => "ERR_USERSDONTMATCH",
728 Numeric::ErrCantjoinopersonly => "ERR_CANTJOINOPERSONLY",
729 Numeric::RplEtracefull => "RPL_ETRACEFULL",
730 Numeric::RplMononline => "RPL_MONONLINE",
731 Numeric::RplMonoffline => "RPL_MONOFFLINE",
732 Numeric::RplMonlist => "RPL_MONLIST",
733 Numeric::RplKnock => "RPL_KNOCK",
734 Numeric::RplKnockdlvr => "RPL_KNOCKDLVR",
735 Numeric::ErrToomanyknock => "ERR_TOOMANYKNOCK",
736 Numeric::ErrChanopen => "ERR_CHANOPEN",
737 Numeric::ErrKnockonchan => "ERR_KNOCKONCHAN",
738 Numeric::RplAcceptlist => "RPL_ACCEPTLIST",
739 Numeric::RplEndofaccept => "RPL_ENDOFACCEPT",
740 Numeric::ErrAcceptfull => "ERR_ACCEPTFULL",
741 Numeric::ErrAcceptexist => "ERR_ACCEPTEXIST",
742 Numeric::ErrAcceptnot => "ERR_ACCEPTNOT",
743 Numeric::ErrTargumodeg => "ERR_TARGUMODEG",
744 Numeric::RplTargnotify => "RPL_TARGNOTIFY",
745 Numeric::RplUmodegmsg => "RPL_UMODEGMSG",
746 Numeric::RplEndofmonlist => "RPL_ENDOFMONLIST",
747 Numeric::ErrMonlistfull => "ERR_MONLISTFULL",
748 Numeric::RplEtraceend => "RPL_ETRACEEND",
749 Numeric::RplKeyvalue => "RPL_KEYVALUE",
750 Numeric::RplKeynotset => "RPL_KEYNOTSET",
751 Numeric::RplMetadatasubok => "RPL_METADATASUBOK",
752 Numeric::RplMetadataunsubok => "RPL_METADATAUNSUBOK",
753 Numeric::RplMetadatasubs => "RPL_METADATASUBS",
754 Numeric::RplLoggedin => "RPL_LOGGEDIN",
755 Numeric::RplLoggedout => "RPL_LOGGEDOUT",
756 Numeric::ErrNicklocked => "ERR_NICKLOCKED",
757 Numeric::RplSaslsuccess => "RPL_SASLSUCCESS",
758 Numeric::ErrSaslfail => "ERR_SASLFAIL",
759 Numeric::ErrSasltoolong => "ERR_SASLTOOLONG",
760 Numeric::ErrSaslaborted => "ERR_SASLABORTED",
761 Numeric::ErrSaslalready => "ERR_SASLALREADY",
762 Numeric::RplSaslmechs => "RPL_SASLMECHS",
763 }
764 }
765}
766
767impl From<Numeric> for u16 {
768 #[inline]
769 fn from(n: Numeric) -> u16 {
770 n as u16
771 }
772}
773
774/// Error returned by [`Numeric::try_from`] when a code matches no known numeric.
775#[derive(Debug, Clone, Copy, PartialEq, Eq)]
776pub struct UnknownNumeric(pub u16);
777
778impl core::fmt::Display for UnknownNumeric {
779 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
780 write!(f, "unknown IRC numeric {}", self.0)
781 }
782}
783
784impl std::error::Error for UnknownNumeric {}
785
786impl TryFrom<u16> for Numeric {
787 type Error = UnknownNumeric;
788
789 fn try_from(code: u16) -> Result<Self, Self::Error> {
790 match code {
791 1 => Ok(Numeric::RplWelcome),
792 2 => Ok(Numeric::RplYourhost),
793 3 => Ok(Numeric::RplCreated),
794 4 => Ok(Numeric::RplMyinfo),
795 5 => Ok(Numeric::RplIsupport),
796 10 => Ok(Numeric::RplBounce),
797 15 => Ok(Numeric::RplMap),
798 17 => Ok(Numeric::RplMapend),
799 18 => Ok(Numeric::RplMapstart),
800 20 => Ok(Numeric::RplHello),
801 42 => Ok(Numeric::RplYourid),
802 43 => Ok(Numeric::RplSavenick),
803 200 => Ok(Numeric::RplTracelink),
804 201 => Ok(Numeric::RplTraceconnecting),
805 202 => Ok(Numeric::RplTracehandshake),
806 203 => Ok(Numeric::RplTraceunknown),
807 204 => Ok(Numeric::RplTraceoperator),
808 205 => Ok(Numeric::RplTraceuser),
809 206 => Ok(Numeric::RplTraceserver),
810 207 => Ok(Numeric::RplTraceservice),
811 208 => Ok(Numeric::RplTracenewtype),
812 209 => Ok(Numeric::RplTraceclass),
813 211 => Ok(Numeric::RplStatslinkinfo),
814 212 => Ok(Numeric::RplStatscommands),
815 213 => Ok(Numeric::RplStatscline),
816 214 => Ok(Numeric::RplStatsnline),
817 215 => Ok(Numeric::RplStatsiline),
818 216 => Ok(Numeric::RplStatskline),
819 217 => Ok(Numeric::RplStatsqline),
820 218 => Ok(Numeric::RplStatsyline),
821 219 => Ok(Numeric::RplEndofstats),
822 221 => Ok(Numeric::RplUmodeis),
823 231 => Ok(Numeric::RplServiceinfo),
824 232 => Ok(Numeric::RplEndofservices),
825 233 => Ok(Numeric::RplService),
826 234 => Ok(Numeric::RplServlist),
827 235 => Ok(Numeric::RplServlistend),
828 239 => Ok(Numeric::RplStatsiauth),
829 240 => Ok(Numeric::RplStatsvline),
830 241 => Ok(Numeric::RplStatslline),
831 242 => Ok(Numeric::RplStatsuptime),
832 243 => Ok(Numeric::RplStatsoline),
833 244 => Ok(Numeric::RplStatshline),
834 245 => Ok(Numeric::RplStatssline),
835 246 => Ok(Numeric::RplStatsping),
836 247 => Ok(Numeric::RplStatsbline),
837 248 => Ok(Numeric::RplStatsdefine),
838 249 => Ok(Numeric::RplStatsdebug),
839 250 => Ok(Numeric::RplStatsdline),
840 251 => Ok(Numeric::RplLuserclient),
841 252 => Ok(Numeric::RplLuserop),
842 253 => Ok(Numeric::RplLuserunknown),
843 254 => Ok(Numeric::RplLuserchannels),
844 255 => Ok(Numeric::RplLuserme),
845 256 => Ok(Numeric::RplAdminme),
846 257 => Ok(Numeric::RplAdminloc1),
847 258 => Ok(Numeric::RplAdminloc2),
848 259 => Ok(Numeric::RplAdminemail),
849 262 => Ok(Numeric::RplTraceend),
850 263 => Ok(Numeric::RplTryagain),
851 265 => Ok(Numeric::RplLocalusers),
852 266 => Ok(Numeric::RplGlobalusers),
853 300 => Ok(Numeric::RplNone),
854 301 => Ok(Numeric::RplAway),
855 302 => Ok(Numeric::RplUserhost),
856 303 => Ok(Numeric::RplIson),
857 304 => Ok(Numeric::RplText),
858 305 => Ok(Numeric::RplUnaway),
859 306 => Ok(Numeric::RplNowaway),
860 311 => Ok(Numeric::RplWhoisuser),
861 312 => Ok(Numeric::RplWhoisserver),
862 313 => Ok(Numeric::RplWhoisoperator),
863 314 => Ok(Numeric::RplWhowasuser),
864 315 => Ok(Numeric::RplEndofwho),
865 316 => Ok(Numeric::RplWhoischanop),
866 317 => Ok(Numeric::RplWhoisidle),
867 318 => Ok(Numeric::RplEndofwhois),
868 319 => Ok(Numeric::RplWhoischannels),
869 320 => Ok(Numeric::RplWhoisextra),
870 330 => Ok(Numeric::RplWhoisaccount),
871 335 => Ok(Numeric::RplWhoisbot),
872 321 => Ok(Numeric::RplListstart),
873 322 => Ok(Numeric::RplList),
874 323 => Ok(Numeric::RplListend),
875 324 => Ok(Numeric::RplChannelmodeis),
876 325 => Ok(Numeric::RplUniqopis),
877 331 => Ok(Numeric::RplNotopic),
878 332 => Ok(Numeric::RplTopic),
879 333 => Ok(Numeric::RplTopicWhoTime),
880 341 => Ok(Numeric::RplInviting),
881 342 => Ok(Numeric::RplSummoning),
882 344 => Ok(Numeric::RplReoplist),
883 345 => Ok(Numeric::RplEndofreoplist),
884 346 => Ok(Numeric::RplInvitelist),
885 347 => Ok(Numeric::RplEndofinvitelist),
886 348 => Ok(Numeric::RplExceptlist),
887 349 => Ok(Numeric::RplEndofexceptlist),
888 351 => Ok(Numeric::RplVersion),
889 352 => Ok(Numeric::RplWhoreply),
890 353 => Ok(Numeric::RplNamreply),
891 354 => Ok(Numeric::RplWhospcrpl),
892 361 => Ok(Numeric::RplKilldone),
893 362 => Ok(Numeric::RplClosing),
894 363 => Ok(Numeric::RplCloseend),
895 364 => Ok(Numeric::RplLinks),
896 365 => Ok(Numeric::RplEndoflinks),
897 366 => Ok(Numeric::RplEndofnames),
898 367 => Ok(Numeric::RplBanlist),
899 368 => Ok(Numeric::RplEndofbanlist),
900 369 => Ok(Numeric::RplEndofwhowas),
901 371 => Ok(Numeric::RplInfo),
902 372 => Ok(Numeric::RplMotd),
903 373 => Ok(Numeric::RplInfostart),
904 374 => Ok(Numeric::RplEndofinfo),
905 375 => Ok(Numeric::RplMotdstart),
906 376 => Ok(Numeric::RplEndofmotd),
907 381 => Ok(Numeric::RplYoureoper),
908 382 => Ok(Numeric::RplRehashing),
909 383 => Ok(Numeric::RplYoureservice),
910 384 => Ok(Numeric::RplMyportis),
911 385 => Ok(Numeric::RplNotoperanymore),
912 391 => Ok(Numeric::RplTime),
913 392 => Ok(Numeric::RplUsersstart),
914 393 => Ok(Numeric::RplUsers),
915 394 => Ok(Numeric::RplEndofusers),
916 395 => Ok(Numeric::RplNousers),
917 396 => Ok(Numeric::RplHostHidden),
918 401 => Ok(Numeric::ErrNosuchnick),
919 402 => Ok(Numeric::ErrNosuchserver),
920 403 => Ok(Numeric::ErrNosuchchannel),
921 404 => Ok(Numeric::ErrCannotsendtochan),
922 405 => Ok(Numeric::ErrToomanychannels),
923 406 => Ok(Numeric::ErrWasnosuchnick),
924 407 => Ok(Numeric::ErrToomanytargets),
925 408 => Ok(Numeric::ErrNosuchservice),
926 409 => Ok(Numeric::ErrNoorigin),
927 410 => Ok(Numeric::ErrInvalidcapcmd),
928 411 => Ok(Numeric::ErrNorecipient),
929 412 => Ok(Numeric::ErrNotexttosend),
930 413 => Ok(Numeric::ErrNotoplevel),
931 414 => Ok(Numeric::ErrWildtoplevel),
932 415 => Ok(Numeric::ErrBadmask),
933 416 => Ok(Numeric::ErrToomanymatches),
934 417 => Ok(Numeric::ErrInputtoolong),
935 421 => Ok(Numeric::ErrUnknowncommand),
936 422 => Ok(Numeric::ErrNomotd),
937 423 => Ok(Numeric::ErrNoadmininfo),
938 424 => Ok(Numeric::ErrFileerror),
939 431 => Ok(Numeric::ErrNonicknamegiven),
940 432 => Ok(Numeric::ErrErroneousnickname),
941 433 => Ok(Numeric::ErrNicknameinuse),
942 434 => Ok(Numeric::ErrServicenameinuse),
943 435 => Ok(Numeric::ErrServiceconfused),
944 436 => Ok(Numeric::ErrNickcollision),
945 437 => Ok(Numeric::ErrUnavailresource),
946 441 => Ok(Numeric::ErrUsernotinchannel),
947 442 => Ok(Numeric::ErrNotonchannel),
948 443 => Ok(Numeric::ErrUseronchannel),
949 444 => Ok(Numeric::ErrNologin),
950 445 => Ok(Numeric::ErrSummondisabled),
951 446 => Ok(Numeric::ErrUsersdisabled),
952 451 => Ok(Numeric::ErrNotregistered),
953 461 => Ok(Numeric::ErrNeedmoreparams),
954 462 => Ok(Numeric::ErrAlreadyregistred),
955 463 => Ok(Numeric::ErrNopermforhost),
956 464 => Ok(Numeric::ErrPasswdmismatch),
957 465 => Ok(Numeric::ErrYourebannedcreep),
958 466 => Ok(Numeric::ErrYouwillbebanned),
959 467 => Ok(Numeric::ErrKeyset),
960 471 => Ok(Numeric::ErrChannelisfull),
961 472 => Ok(Numeric::ErrUnknownmode),
962 473 => Ok(Numeric::ErrInviteonlychan),
963 474 => Ok(Numeric::ErrBannedfromchan),
964 475 => Ok(Numeric::ErrBadchannelkey),
965 476 => Ok(Numeric::ErrBadchanmask),
966 477 => Ok(Numeric::ErrNochanmodes),
967 478 => Ok(Numeric::ErrBanlistfull),
968 481 => Ok(Numeric::ErrNoprivileges),
969 482 => Ok(Numeric::ErrChanoprivsneeded),
970 483 => Ok(Numeric::ErrCantkillserver),
971 484 => Ok(Numeric::ErrRestricted),
972 485 => Ok(Numeric::ErrUniqoprivsneeded),
973 491 => Ok(Numeric::ErrNooperhost),
974 492 => Ok(Numeric::ErrNoservicehost),
975 499 => Ok(Numeric::ErrStatskline),
976 501 => Ok(Numeric::ErrUmodeunknownflag),
977 502 => Ok(Numeric::ErrUsersdontmatch),
978 520 => Ok(Numeric::ErrCantjoinopersonly),
979 708 => Ok(Numeric::RplEtracefull),
980 710 => Ok(Numeric::RplKnock),
981 711 => Ok(Numeric::RplKnockdlvr),
982 712 => Ok(Numeric::ErrToomanyknock),
983 713 => Ok(Numeric::ErrChanopen),
984 714 => Ok(Numeric::ErrKnockonchan),
985 281 => Ok(Numeric::RplAcceptlist),
986 282 => Ok(Numeric::RplEndofaccept),
987 456 => Ok(Numeric::ErrAcceptfull),
988 457 => Ok(Numeric::ErrAcceptexist),
989 458 => Ok(Numeric::ErrAcceptnot),
990 716 => Ok(Numeric::ErrTargumodeg),
991 717 => Ok(Numeric::RplTargnotify),
992 718 => Ok(Numeric::RplUmodegmsg),
993 730 => Ok(Numeric::RplMononline),
994 731 => Ok(Numeric::RplMonoffline),
995 732 => Ok(Numeric::RplMonlist),
996 733 => Ok(Numeric::RplEndofmonlist),
997 734 => Ok(Numeric::ErrMonlistfull),
998 759 => Ok(Numeric::RplEtraceend),
999 761 => Ok(Numeric::RplKeyvalue),
1000 766 => Ok(Numeric::RplKeynotset),
1001 770 => Ok(Numeric::RplMetadatasubok),
1002 771 => Ok(Numeric::RplMetadataunsubok),
1003 772 => Ok(Numeric::RplMetadatasubs),
1004 900 => Ok(Numeric::RplLoggedin),
1005 901 => Ok(Numeric::RplLoggedout),
1006 902 => Ok(Numeric::ErrNicklocked),
1007 903 => Ok(Numeric::RplSaslsuccess),
1008 904 => Ok(Numeric::ErrSaslfail),
1009 905 => Ok(Numeric::ErrSasltoolong),
1010 906 => Ok(Numeric::ErrSaslaborted),
1011 907 => Ok(Numeric::ErrSaslalready),
1012 908 => Ok(Numeric::RplSaslmechs),
1013 other => Err(UnknownNumeric(other)),
1014 }
1015 }
1016}
1017
1018impl core::fmt::Display for Numeric {
1019 /// Formats as the zero-padded three-digit wire code, e.g. `001`.
1020 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1021 write!(f, "{:03}", *self as u16)
1022 }
1023}
1024
1025#[cfg(test)]
1026mod tests {
1027 use super::*;
1028
1029 #[test]
1030 fn code_is_discriminant() {
1031 assert_eq!(Numeric::RplWelcome.code(), 1);
1032 assert_eq!(Numeric::RplWelcome as u16, 1);
1033 assert_eq!(Numeric::ErrNicknameinuse.code(), 433);
1034 assert_eq!(u16::from(Numeric::ErrNosuchnick), 401);
1035 }
1036
1037 #[test]
1038 fn display_is_three_digit_padded() {
1039 assert_eq!(Numeric::RplWelcome.to_string(), "001");
1040 assert_eq!(Numeric::RplBounce.to_string(), "010");
1041 assert_eq!(Numeric::ErrNicknameinuse.to_string(), "433");
1042 assert_eq!(Numeric::RplEtraceend.to_string(), "759");
1043 }
1044
1045 #[test]
1046 fn name_round_trips_symbol() {
1047 assert_eq!(Numeric::RplWelcome.name(), "RPL_WELCOME");
1048 assert_eq!(Numeric::ErrNosuchnick.name(), "ERR_NOSUCHNICK");
1049 }
1050
1051 #[test]
1052 fn try_from_known_and_unknown() {
1053 assert_eq!(Numeric::try_from(1), Ok(Numeric::RplWelcome));
1054 assert_eq!(Numeric::try_from(433), Ok(Numeric::ErrNicknameinuse));
1055 assert_eq!(Numeric::try_from(999), Err(UnknownNumeric(999)));
1056 // round-trip every code through u16 and back
1057 for n in [
1058 Numeric::RplWelcome,
1059 Numeric::RplStatskline,
1060 Numeric::ErrStatskline,
1061 Numeric::RplEtraceend,
1062 ] {
1063 assert_eq!(Numeric::try_from(n.code()), Ok(n));
1064 }
1065 }
1066
1067 #[test]
1068 fn sasl_numerics_round_trip() {
1069 for (n, code, name) in [
1070 (Numeric::RplLoggedin, 900u16, "RPL_LOGGEDIN"),
1071 (Numeric::RplLoggedout, 901, "RPL_LOGGEDOUT"),
1072 (Numeric::ErrNicklocked, 902, "ERR_NICKLOCKED"),
1073 (Numeric::RplSaslsuccess, 903, "RPL_SASLSUCCESS"),
1074 (Numeric::ErrSaslfail, 904, "ERR_SASLFAIL"),
1075 (Numeric::ErrSasltoolong, 905, "ERR_SASLTOOLONG"),
1076 (Numeric::ErrSaslaborted, 906, "ERR_SASLABORTED"),
1077 (Numeric::ErrSaslalready, 907, "ERR_SASLALREADY"),
1078 (Numeric::RplSaslmechs, 908, "RPL_SASLMECHS"),
1079 ] {
1080 assert_eq!(n.code(), code);
1081 assert_eq!(Numeric::try_from(code), Ok(n));
1082 assert_eq!(n.name(), name);
1083 // The 9xx SASL block sits outside the 400..600 error range, so `is_error()` is
1084 // false even for the conceptual ERR_* codes (the established 7xx/9xx convention).
1085 assert!(!n.is_error());
1086 }
1087 }
1088
1089 #[test]
1090 fn callerid_numerics_round_trip() {
1091 for (n, code, name) in [
1092 (Numeric::RplAcceptlist, 281u16, "RPL_ACCEPTLIST"),
1093 (Numeric::RplEndofaccept, 282, "RPL_ENDOFACCEPT"),
1094 (Numeric::ErrAcceptfull, 456, "ERR_ACCEPTFULL"),
1095 (Numeric::ErrAcceptexist, 457, "ERR_ACCEPTEXIST"),
1096 (Numeric::ErrAcceptnot, 458, "ERR_ACCEPTNOT"),
1097 (Numeric::ErrTargumodeg, 716, "ERR_TARGUMODEG"),
1098 (Numeric::RplTargnotify, 717, "RPL_TARGNOTIFY"),
1099 (Numeric::RplUmodegmsg, 718, "RPL_UMODEGMSG"),
1100 ] {
1101 assert_eq!(n.code(), code);
1102 assert_eq!(Numeric::try_from(code), Ok(n));
1103 assert_eq!(n.name(), name);
1104 }
1105 // The 45x accept errors sit in the error range; the 28x/71x replies do not.
1106 assert!(Numeric::ErrAcceptfull.is_error());
1107 assert!(!Numeric::RplAcceptlist.is_error());
1108 assert!(!Numeric::ErrTargumodeg.is_error());
1109 }
1110
1111 #[test]
1112 fn statskline_codes_stay_distinct() {
1113 assert_eq!(Numeric::RplStatskline.code(), 216);
1114 assert_eq!(Numeric::ErrStatskline.code(), 499);
1115 }
1116
1117 #[test]
1118 fn is_error_follows_convention() {
1119 assert!(!Numeric::RplWelcome.is_error());
1120 assert!(!Numeric::RplEtracefull.is_error()); // 708 is a reply
1121 assert!(Numeric::ErrNosuchnick.is_error());
1122 assert!(Numeric::ErrUsersdontmatch.is_error());
1123 }
1124
1125 #[test]
1126 fn whois_tls_aliases_whois_extra() {
1127 assert_eq!(Numeric::RPL_WHOISTLS, Numeric::RplWhoisextra);
1128 assert_eq!(Numeric::RPL_WHOISTLS.code(), 320);
1129 }
1130
1131 #[test]
1132 fn whois_bot_round_trips() {
1133 assert_eq!(Numeric::RplWhoisbot.code(), 335);
1134 assert_eq!(Numeric::try_from(335), Ok(Numeric::RplWhoisbot));
1135 assert_eq!(Numeric::RplWhoisbot.name(), "RPL_WHOISBOT");
1136 assert!(!Numeric::RplWhoisbot.is_error());
1137 }
1138
1139 #[test]
1140 fn host_hidden_round_trips() {
1141 assert_eq!(Numeric::RplHostHidden.code(), 396);
1142 assert_eq!(Numeric::try_from(396), Ok(Numeric::RplHostHidden));
1143 assert_eq!(Numeric::RplHostHidden.name(), "RPL_HOSTHIDDEN");
1144 assert!(
1145 !Numeric::RplHostHidden.is_error(),
1146 "396 is a reply, not an error"
1147 );
1148 }
1149}