1 /++
2 + Module for parsing ISUPPORT replies.
3 +/
4 module virc.numerics.isupport;
5 
6 import std.range.primitives : isForwardRange;
7 
8 import virc.numerics.definitions;
9 
10 enum defaultModePrefixesMap = ['o': '@', 'v': '+'];
11 enum defaultModePrefixes = "@+";
12 /++
13 +
14 +/
15 enum ISupportToken {
16 	///
17 	accept = "ACCEPT",
18 	///
19 	awayLen = "AWAYLEN",
20 	///
21 	callerID = "CALLERID",
22 	///
23 	caseMapping = "CASEMAPPING",
24 	///
25 	chanLimit = "CHANLIMIT",
26 	///
27 	chanModes = "CHANMODES",
28 	///
29 	channelLen = "CHANNELLEN",
30 	///
31 	chanTypes = "CHANTYPES",
32 	///
33 	charSet = "CHARSET",
34 	///
35 	chIdLen = "CHIDLEN",
36 	///
37 	cNotice = "CNOTICE",
38 	///
39 	cPrivmsg = "CPRIVMSG",
40 	///
41 	deaf = "DEAF",
42 	///
43 	eList = "ELIST",
44 	///
45 	eSilence = "ESILENCE",
46 	///
47 	excepts = "EXCEPTS",
48 	///
49 	extBan = "EXTBAN",
50 	///
51 	fnc = "FNC",
52 	///
53 	idChan = "IDCHAN",
54 	///
55 	invEx = "INVEX",
56 	///
57 	kickLen = "KICKLEN",
58 	///
59 	knock = "KNOCK",
60 	///
61 	language = "LANGUAGE",
62 	///
63 	lineLen = "LINELEN",
64 	///
65 	map = "MAP",
66 	///
67 	maxBans = "MAXBANS",
68 	///
69 	maxChannels = "MAXCHANNELS",
70 	///
71 	maxList = "MAXLIST",
72 	///
73 	maxPara = "MAXPARA",
74 	///
75 	maxTargets = "MAXTARGETS",
76 	///
77 	metadata = "METADATA",
78 	///
79 	modes = "MODES",
80 	///
81 	monitor = "MONITOR",
82 	///
83 	namesX = "NAMESX",
84 	///
85 	network = "NETWORK",
86 	///
87 	nickLen = "NICKLEN",
88 	///
89 	noQuit = "NOQUIT",
90 	///
91 	operLog = "OPERLOG",
92 	///
93 	override_ = "OVERRIDE",
94 	///
95 	penalty = "PENALTY",
96 	///
97 	prefix = "PREFIX",
98 	///
99 	remove = "REMOVE",
100 	///
101 	rfc2812 = "RFC2812",
102 	///
103 	safeList = "SAFELIST",
104 	///
105 	secureList = "SECURELIST",
106 	///
107 	silence = "SILENCE",
108 	///
109 	ssl = "SSL",
110 	///
111 	startTLS = "STARTTLS",
112 	///
113 	statusMsg = "STATUSMSG",
114 	///
115 	std = "STD",
116 	///
117 	targMax = "TARGMAX",
118 	///
119 	topicLen = "TOPICLEN",
120 	///
121 	uhNames = "UHNAMES",
122 	///
123 	userIP = "USERIP",
124 	///
125 	userLen = "USERLEN",
126 	///
127 	vBanList = "VBANLIST",
128 	///
129 	vChans = "VCHANS",
130 	///
131 	wallChOps = "WALLCHOPS",
132 	///
133 	wallVoices = "WALLVOICES",
134 	///
135 	watch = "WATCH",
136 	///
137 	whoX = "WHOX"
138 }
139 
140 import std.typecons : Nullable;
141 private void setToken(T : ulong)(ref T opt, Nullable!string value, T defaultIfNotPresent, T negateValue) {
142 	import std.conv : parse;
143 	if (value.isNull) {
144 		opt = negateValue;
145 	} else {
146 		try {
147 			opt = parse!T(value.get);
148 		} catch (Exception) {
149 			opt = defaultIfNotPresent;
150 		}
151 	}
152 }
153 private void setToken(T : ulong)(ref Nullable!T opt, Nullable!string value, T defaultIfNotPresent) {
154 	import std.conv : parse;
155 	if (value.isNull) {
156 		opt.nullify();
157 	} else {
158 		if (value == "") {
159 			opt = defaultIfNotPresent;
160 		} else {
161 			try {
162 				opt = parse!T(value.get);
163 			} catch (Exception) {
164 				opt.nullify();
165 			}
166 		}
167 	}
168 }
169 private void setToken(T: char)(ref Nullable!T opt, Nullable!string value, T defaultIfNotPresent) {
170 	import std.utf : byCodeUnit;
171 	if (value.isNull) {
172 		opt.nullify();
173 	} else {
174 		if (value.get == "") {
175 			opt = defaultIfNotPresent;
176 		} else {
177 			opt = value.get.byCodeUnit.front;
178 		}
179 	}
180 }
181 private void setToken(T : bool)(ref T opt, Nullable!string val, T = T.init) {
182 	opt = !val.isNull;
183 }
184 private void setToken(T : string)(ref T opt, Nullable!string val, T defaultIfNotPresent = "") {
185 	if (val.isNull) {
186 		opt = defaultIfNotPresent;
187 	} else {
188 		opt = val.get;
189 	}
190 }
191 private void setToken(T : string)(ref Nullable!T opt, Nullable!string val) {
192 	if (!val.isNull) {
193 		opt = val.get;
194 	} else {
195 		opt.nullify();
196 	}
197 }
198 private void setToken(T)(ref Nullable!T opt, Nullable!string val) {
199 	if (!val.isNull) {
200 		try {
201 			opt = val.get.to!T;
202 		} catch (Exception) {
203 			opt.nullify();
204 		}
205 	} else {
206 		opt.nullify();
207 	}
208 }
209 
210 private struct TokenPair {
211 	string key;
212 	Nullable!string value;
213 }
214 /++
215 + Extended bans supported by the server and what they look like.
216 +/
217 struct BanExtension {
218 	///Prefix character for the ban, if applicable
219 	Nullable!char prefix;
220 	///Types of extended bans supported by the server
221 	string banTypes;
222 }
223 /++
224 +
225 +/
226 struct ISupport {
227 	import std.typecons : Nullable;
228 	import virc.casemapping : CaseMapping;
229 	import virc.modes : ModeType;
230 	///
231 	char[char] prefixes;
232 	///
233 	string channelTypes = "#&!+"; //RFC2811 specifies four channel types.
234 	///
235 	ModeType[char] channelModeTypes;
236 	///
237 	ulong maxModesPerCommand = 3;
238 	///
239 	ulong[char] chanLimits;
240 	///
241 	ulong nickLength = 9;
242 	///
243 	ulong[char] maxList;
244 	///
245 	string network;
246 	///
247 	Nullable!char banExceptions;
248 	///
249 	Nullable!char inviteExceptions;
250 	///
251 	bool wAllChannelOps;
252 	///
253 	bool wAllChannelVoices;
254 	///
255 	string statusMessage;
256 	///
257 	CaseMapping caseMapping;
258 	///
259 	string extendedList;
260 	///
261 	Nullable!ulong topicLength;
262 	///
263 	ulong kickLength = ulong.max;
264 	///
265 	Nullable!ulong userLength;
266 	///
267 	ulong channelLength = 200;
268 	///
269 	ulong[char] channelIDLengths;
270 	///
271 	Nullable!string standard;
272 	///
273 	Nullable!ulong silence;
274 	///
275 	bool extendedSilence;
276 	///
277 	bool rfc2812;
278 	///
279 	bool penalty;
280 	///
281 	bool forcedNickChanges;
282 	///
283 	bool safeList;
284 	///
285 	ulong awayLength = ulong.max;
286 	///
287 	bool noQuit;
288 	///
289 	bool userIP;
290 	///
291 	bool cPrivmsg;
292 	///
293 	bool cNotice;
294 	///
295 	ulong maxTargets = ulong.max;
296 	///
297 	bool knock;
298 	///
299 	bool virtualChannels;
300 	///
301 	Nullable!ulong maximumWatches;
302 	///
303 	bool whoX;
304 	///
305 	Nullable!char callerID;
306 	///
307 	string[] languages;
308 	///
309 	ulong maxLanguages;
310 	///
311 	bool startTLS; //DANGEROUS!
312 	///
313 	Nullable!BanExtension banExtensions;
314 	///
315 	bool logsOperCommands;
316 	///
317 	string sslServer;
318 	///
319 	bool userhostsInNames;
320 	///
321 	bool namesExtended;
322 	///
323 	bool secureList;
324 	///
325 	bool supportsRemove;
326 	///
327 	bool allowsOperOverride;
328 	///
329 	bool variableBanList;
330 	///
331 	bool supportsMap;
332 	///
333 	ulong maximumParameters = 12;
334 	///
335 	ulong lineLength = 512;
336 	///
337 	Nullable!char deaf;
338 	///
339 	Nullable!ulong metadata;
340 	///
341 	Nullable!ulong monitorTargetLimit;
342 	///
343 	ulong[string] targetMaxByCommand;
344 	///
345 	string charSet;
346 	///
347 	string[string] unknownTokens;
348 	///
349 	void insertToken(string token, Nullable!string val) @safe pure {
350 		import std.algorithm.iteration : splitter;
351 		import std.algorithm.searching : findSplit;
352 		import std.conv : parse, to;
353 		import std.meta : AliasSeq;
354 		import std.range : empty, popFront, zip;
355 		import std.string : toLower;
356 		import std.utf : byCodeUnit;
357 		switch (cast(ISupportToken)token) {
358 			case ISupportToken.chanModes:
359 				channelModeTypes = channelModeTypes.init;
360 				if (!val.isNull) {
361 					auto splitModes = val.get.splitter(",");
362 					foreach (modeType; AliasSeq!(ModeType.a, ModeType.b, ModeType.c, ModeType.d)) {
363 						if (splitModes.empty) {
364 							break;
365 						}
366 						foreach (modeChar; splitModes.front) {
367 							channelModeTypes[modeChar] = modeType;
368 						}
369 						splitModes.popFront();
370 					}
371 				} else {
372 					channelModeTypes = channelModeTypes.init;
373 				}
374 				break;
375 			case ISupportToken.prefix:
376 				if (!val.isNull) {
377 					if (val.get == "") {
378 						prefixes = prefixes.init;
379 					} else {
380 						auto split = val.get.findSplit(")");
381 						split[0].popFront();
382 						foreach (modeChar, prefix; zip(split[0].byCodeUnit, split[2].byCodeUnit)) {
383 							prefixes[modeChar] = prefix;
384 							if (modeChar !in channelModeTypes) {
385 								channelModeTypes[modeChar] = ModeType.d;
386 							}
387 						}
388 					}
389 				} else {
390 					prefixes = defaultModePrefixesMap;
391 				}
392 				break;
393 			case ISupportToken.chanTypes:
394 				setToken(channelTypes, val, "#&!+");
395 				break;
396 			case ISupportToken.wallChOps:
397 				setToken(wAllChannelOps, val);
398 				break;
399 			case ISupportToken.wallVoices:
400 				setToken(wAllChannelVoices, val);
401 				break;
402 			case ISupportToken.statusMsg:
403 				setToken(statusMessage, val);
404 				break;
405 			case ISupportToken.extBan:
406 				if (val.isNull) {
407 					banExtensions.nullify();
408 				} else {
409 					banExtensions = BanExtension();
410 					auto split = val.get.findSplit(",");
411 					if (split[1] == ",") {
412 						if (!split[0].empty) {
413 							banExtensions.get.prefix = split[0].byCodeUnit.front;
414 						}
415 						banExtensions.get.banTypes = split[2];
416 					} else {
417 						banExtensions.nullify();
418 					}
419 				}
420 				break;
421 			case ISupportToken.fnc:
422 				setToken(forcedNickChanges, val);
423 				break;
424 			case ISupportToken.userIP:
425 				setToken(userIP, val);
426 				break;
427 			case ISupportToken.cPrivmsg:
428 				setToken(cPrivmsg, val);
429 				break;
430 			case ISupportToken.cNotice:
431 				setToken(cNotice, val);
432 				break;
433 			case ISupportToken.knock:
434 				setToken(knock, val);
435 				break;
436 			case ISupportToken.vChans:
437 				setToken(virtualChannels, val);
438 				break;
439 			case ISupportToken.whoX:
440 				setToken(whoX, val);
441 				break;
442 			case ISupportToken.awayLen:
443 				setToken(awayLength, val, ulong.max, ulong.max);
444 				break;
445 			case ISupportToken.nickLen:
446 				setToken(nickLength, val, 9, 9);
447 				break;
448 			case ISupportToken.lineLen:
449 				setToken(lineLength, val, 512, 512);
450 				break;
451 			case ISupportToken.channelLen:
452 				setToken(channelLength, val, ulong.max, 200);
453 				break;
454 			case ISupportToken.kickLen:
455 				setToken(kickLength, val, ulong.max, ulong.max);
456 				break;
457 			case ISupportToken.userLen:
458 				setToken(userLength, val, ulong.max);
459 				break;
460 			case ISupportToken.topicLen:
461 				setToken(topicLength, val, ulong.max);
462 				break;
463 			case ISupportToken.maxBans:
464 				if (val.isNull) {
465 					maxList.remove('b');
466 				} else {
467 					maxList['b'] = 0;
468 					setToken(maxList['b'], val, ulong.max, ulong.max);
469 				}
470 				break;
471 			case ISupportToken.modes:
472 				setToken(maxModesPerCommand, val, ulong.max, 3);
473 				break;
474 			case ISupportToken.watch:
475 				setToken(maximumWatches, val, ulong.max);
476 				break;
477 			case ISupportToken.metadata:
478 				setToken(metadata, val, ulong.max);
479 				break;
480 			case ISupportToken.monitor:
481 				setToken(monitorTargetLimit, val, ulong.max);
482 				break;
483 			case ISupportToken.maxList:
484 				if (val.isNull) {
485 					maxList = maxList.init;
486 				} else {
487 					auto splitModes = val.get.splitter(",");
488 					foreach (listEntry; splitModes) {
489 						auto splitArgs = listEntry.findSplit(":");
490 						immutable limit = parse!ulong(splitArgs[2]);
491 						foreach (modeChar; splitArgs[0]) {
492 							maxList[modeChar] = limit;
493 						}
494 					}
495 				}
496 				break;
497 			case ISupportToken.targMax:
498 				targetMaxByCommand = targetMaxByCommand.init;
499 				if (!val.isNull) {
500 					auto splitCmd = val.get.splitter(",");
501 					foreach (listEntry; splitCmd) {
502 						auto splitArgs = listEntry.findSplit(":");
503 						if (splitArgs[2].empty) {
504 							targetMaxByCommand[splitArgs[0]] = ulong.max;
505 						} else {
506 							immutable limit = parse!ulong(splitArgs[2]);
507 							targetMaxByCommand[splitArgs[0]] = limit;
508 						}
509 					}
510 				}
511 				break;
512 			case ISupportToken.chanLimit:
513 				if (!val.isNull) {
514 					auto splitPrefix = val.get.splitter(",");
515 					foreach (listEntry; splitPrefix) {
516 						auto splitArgs = listEntry.findSplit(":");
517 						if (splitArgs[1] != ":") {
518 							chanLimits = chanLimits.init;
519 							break;
520 						}
521 						try {
522 							immutable limit = parse!ulong(splitArgs[2]);
523 							foreach (prefix; splitArgs[0]) {
524 								chanLimits[prefix] = limit;
525 							}
526 						} catch (Exception) {
527 							if (splitArgs[2] == "") {
528 								foreach (prefix; splitArgs[0]) {
529 									chanLimits[prefix] = ulong.max;
530 								}
531 							} else {
532 								chanLimits = chanLimits.init;
533 								break;
534 							}
535 						}
536 					}
537 				} else {
538 					chanLimits = chanLimits.init;
539 				}
540 				break;
541 			case ISupportToken.maxTargets:
542 				setToken(maxTargets, val, ulong.max, ulong.max);
543 				break;
544 			case ISupportToken.maxChannels:
545 				if (val.isNull) {
546 					chanLimits.remove('#');
547 				} else {
548 					chanLimits['#'] = 0;
549 					setToken(chanLimits['#'], val, ulong.max, ulong.max);
550 				}
551 				break;
552 			case ISupportToken.maxPara:
553 				setToken(maximumParameters, val, 12, 12);
554 				break;
555 			case ISupportToken.startTLS:
556 				setToken(startTLS, val);
557 				break;
558 			case ISupportToken.ssl:
559 				setToken(sslServer, val, "");
560 				break;
561 			case ISupportToken.operLog:
562 				setToken(logsOperCommands, val);
563 				break;
564 			case ISupportToken.silence:
565 				setToken(silence, val, ulong.max);
566 				break;
567 			case ISupportToken.network:
568 				setToken(network, val);
569 				break;
570 			case ISupportToken.caseMapping:
571 				if (val.isNull) {
572 					caseMapping = CaseMapping.unknown;
573 				} else {
574 					switch (val.get.toLower()) {
575 						case CaseMapping.rfc1459:
576 							caseMapping = CaseMapping.rfc1459;
577 							break;
578 						case CaseMapping.rfc3454:
579 							caseMapping = CaseMapping.rfc3454;
580 							break;
581 						case CaseMapping.strictRFC1459:
582 							caseMapping = CaseMapping.strictRFC1459;
583 							break;
584 						case CaseMapping.ascii:
585 							caseMapping = CaseMapping.ascii;
586 							break;
587 						default:
588 							caseMapping = CaseMapping.unknown;
589 							break;
590 					}
591 				}
592 				break;
593 			case ISupportToken.charSet:
594 				//Has serious issues and has been removed from drafts
595 				//So we leave this one unparsed
596 				setToken(charSet, val, "");
597 				break;
598 			case ISupportToken.uhNames:
599 				setToken(userhostsInNames, val);
600 				break;
601 			case ISupportToken.namesX:
602 				setToken(namesExtended, val);
603 				break;
604 			case ISupportToken.invEx:
605 				setToken(inviteExceptions, val, 'I');
606 				break;
607 			case ISupportToken.excepts:
608 				setToken(banExceptions, val, 'e');
609 				break;
610 			case ISupportToken.callerID, ISupportToken.accept:
611 				setToken(callerID, val, 'g');
612 				break;
613 			case ISupportToken.deaf:
614 				setToken(deaf, val, 'd');
615 				break;
616 			case ISupportToken.eList:
617 				setToken(extendedList, val, "");
618 				break;
619 			case ISupportToken.secureList:
620 				setToken(secureList, val);
621 				break;
622 			case ISupportToken.noQuit:
623 				setToken(noQuit, val);
624 				break;
625 			case ISupportToken.remove:
626 				setToken(supportsRemove, val);
627 				break;
628 			case ISupportToken.eSilence:
629 				setToken(extendedSilence, val);
630 				break;
631 			case ISupportToken.override_:
632 				setToken(allowsOperOverride, val);
633 				break;
634 			case ISupportToken.vBanList:
635 				setToken(variableBanList, val);
636 				break;
637 			case ISupportToken.map:
638 				setToken(supportsMap, val);
639 				break;
640 			case ISupportToken.safeList:
641 				setToken(safeList, val);
642 				break;
643 			case ISupportToken.chIdLen:
644 				if (val.isNull) {
645 					channelIDLengths.remove('!');
646 				} else {
647 					channelIDLengths['!'] = 0;
648 					setToken(channelIDLengths['!'], val, ulong.max, ulong.max);
649 				}
650 				//channelIDLengths['!'] = parse!ulong(value);
651 				break;
652 			case ISupportToken.idChan:
653 				if (val.isNull) {
654 					channelIDLengths = channelIDLengths.init;
655 				} else {
656 					auto splitPrefix = val.get.splitter(",");
657 					foreach (listEntry; splitPrefix) {
658 						auto splitArgs = listEntry.findSplit(":");
659 						immutable limit = parse!ulong(splitArgs[2]);
660 						foreach (prefix; splitArgs[0]) {
661 							channelIDLengths[prefix] = limit;
662 						}
663 					}
664 				}
665 				break;
666 			case ISupportToken.std:
667 				setToken(standard, val);
668 				break;
669 			case ISupportToken.rfc2812:
670 				setToken(rfc2812, val);
671 				break;
672 			case ISupportToken.penalty:
673 				setToken(penalty, val);
674 				break;
675 			case ISupportToken.language:
676 				if (val.isNull) {
677 					languages = languages.init;
678 					maxLanguages = 0;
679 				} else {
680 					auto splitLangs = val.get.splitter(",");
681 					maxLanguages = to!ulong(splitLangs.front);
682 					splitLangs.popFront();
683 					foreach (lang; splitLangs)
684 						languages ~= lang;
685 				}
686 				break;
687 			default:
688 				if (val.isNull) {
689 					if (token in unknownTokens) {
690 						unknownTokens.remove(token);
691 					}
692 				} else {
693 					unknownTokens[token] = val.get;
694 				}
695 				break;
696 		}
697 	}
698 	private void insertToken(TokenPair pair) pure @safe {
699 		insertToken(pair.key, pair.value);
700 	}
701 }
702 ///
703 @safe pure unittest {
704 	import virc.casemapping : CaseMapping;
705 	import virc.modes : ModeType;
706 	auto isupport = ISupport();
707 	{
708 		assert(isupport.awayLength == ulong.max);
709 		isupport.insertToken(keyValuePair("AWAYLEN=8"));
710 		assert(isupport.awayLength == 8);
711 		isupport.insertToken(keyValuePair("AWAYLEN="));
712 		assert(isupport.awayLength == ulong.max);
713 		isupport.insertToken(keyValuePair("AWAYLEN=8"));
714 		assert(isupport.awayLength == 8);
715 		isupport.insertToken(keyValuePair("-AWAYLEN"));
716 		assert(isupport.awayLength == ulong.max);
717 	}
718 	{
719 		assert(isupport.callerID.isNull);
720 		isupport.insertToken(keyValuePair("CALLERID=h"));
721 		assert(isupport.callerID.get == 'h');
722 		isupport.insertToken(keyValuePair("CALLERID"));
723 		assert(isupport.callerID.get == 'g');
724 		isupport.insertToken(keyValuePair("-CALLERID"));
725 		assert(isupport.callerID.isNull);
726 	}
727 	{
728 		assert(isupport.caseMapping == CaseMapping.unknown);
729 		isupport.insertToken(keyValuePair("CASEMAPPING=rfc1459"));
730 		assert(isupport.caseMapping == CaseMapping.rfc1459);
731 		isupport.insertToken(keyValuePair("CASEMAPPING=ascii"));
732 		assert(isupport.caseMapping == CaseMapping.ascii);
733 		isupport.insertToken(keyValuePair("CASEMAPPING=rfc3454"));
734 		assert(isupport.caseMapping == CaseMapping.rfc3454);
735 		isupport.insertToken(keyValuePair("CASEMAPPING=strict-rfc1459"));
736 		assert(isupport.caseMapping == CaseMapping.strictRFC1459);
737 		isupport.insertToken(keyValuePair("-CASEMAPPING"));
738 		assert(isupport.caseMapping == CaseMapping.unknown);
739 		isupport.insertToken(keyValuePair("CASEMAPPING=something"));
740 		assert(isupport.caseMapping == CaseMapping.unknown);
741 	}
742 	{
743 		assert(isupport.chanLimits.length == 0);
744 		isupport.insertToken(keyValuePair("CHANLIMIT=#+:25,&:"));
745 		assert(isupport.chanLimits['#'] == 25);
746 		assert(isupport.chanLimits['+'] == 25);
747 		assert(isupport.chanLimits['&'] == ulong.max);
748 		isupport.insertToken(keyValuePair("-CHANLIMIT"));
749 		assert(isupport.chanLimits.length == 0);
750 		isupport.insertToken(keyValuePair("CHANLIMIT=q"));
751 		assert(isupport.chanLimits.length == 0);
752 		isupport.insertToken(keyValuePair("CHANLIMIT=!:f"));
753 		assert(isupport.chanLimits.length == 0);
754 	}
755 	{
756 		assert(isupport.channelModeTypes.length == 0);
757 		isupport.insertToken(keyValuePair("CHANMODES=b,k,l,imnpst"));
758 		assert(isupport.channelModeTypes['b'] == ModeType.a);
759 		assert(isupport.channelModeTypes['k'] == ModeType.b);
760 		assert(isupport.channelModeTypes['l'] == ModeType.c);
761 		assert(isupport.channelModeTypes['i'] == ModeType.d);
762 		assert(isupport.channelModeTypes['t'] == ModeType.d);
763 		isupport.insertToken(keyValuePair("CHANMODES=beI,k,l,BCMNORScimnpstz"));
764 		assert(isupport.channelModeTypes['e'] == ModeType.a);
765 		isupport.insertToken(keyValuePair("CHANMODES"));
766 		assert(isupport.channelModeTypes.length == 0);
767 		isupport.insertToken(keyValuePair("CHANMODES=w,,,"));
768 		assert(isupport.channelModeTypes['w'] == ModeType.a);
769 		assert('b' !in isupport.channelModeTypes);
770 		isupport.insertToken(keyValuePair("-CHANMODES"));
771 		assert(isupport.channelModeTypes.length == 0);
772 	}
773 	{
774 		assert(isupport.channelLength == 200);
775 		isupport.insertToken(keyValuePair("CHANNELLEN=50"));
776 		assert(isupport.channelLength == 50);
777 		isupport.insertToken(keyValuePair("CHANNELLEN="));
778 		assert(isupport.channelLength == ulong.max);
779 		isupport.insertToken(keyValuePair("-CHANNELLEN"));
780 		assert(isupport.channelLength == 200);
781 	}
782 	{
783 		assert(isupport.channelTypes == "#&!+");
784 		isupport.insertToken(keyValuePair("CHANTYPES=&#"));
785 		assert(isupport.channelTypes == "&#");
786 		isupport.insertToken(keyValuePair("CHANTYPES"));
787 		assert(isupport.channelTypes == "");
788 		isupport.insertToken(keyValuePair("-CHANTYPES"));
789 		assert(isupport.channelTypes == "#&!+");
790 	}
791 	{
792 		assert(isupport.charSet == "");
793 		isupport.insertToken(keyValuePair("CHARSET=ascii"));
794 		assert(isupport.charSet == "ascii");
795 		isupport.insertToken(keyValuePair("CHARSET"));
796 		assert(isupport.charSet == "");
797 		isupport.insertToken(keyValuePair("-CHARSET"));
798 		assert(isupport.charSet == "");
799 	}
800 	{
801 		assert('!' !in isupport.channelIDLengths);
802 		isupport.insertToken(keyValuePair("CHIDLEN=5"));
803 		assert(isupport.channelIDLengths['!'] == 5);
804 		isupport.insertToken(keyValuePair("-CHIDLEN"));
805 		assert('!' !in isupport.channelIDLengths);
806 	}
807 	{
808 		assert(!isupport.cNotice);
809 		isupport.insertToken(keyValuePair("CNOTICE"));
810 		assert(isupport.cNotice);
811 		isupport.insertToken(keyValuePair("-CNOTICE"));
812 		assert(!isupport.cNotice);
813 	}
814 	{
815 		assert(!isupport.cPrivmsg);
816 		isupport.insertToken(keyValuePair("CPRIVMSG"));
817 		assert(isupport.cPrivmsg);
818 		isupport.insertToken(keyValuePair("-CPRIVMSG"));
819 		assert(!isupport.cPrivmsg);
820 	}
821 	{
822 		assert(isupport.deaf.isNull);
823 		isupport.insertToken(keyValuePair("DEAF=D"));
824 		assert(isupport.deaf.get == 'D');
825 		isupport.insertToken(keyValuePair("DEAF"));
826 		assert(isupport.deaf.get == 'd');
827 		isupport.insertToken(keyValuePair("-DEAF"));
828 		assert(isupport.deaf.isNull);
829 	}
830 	{
831 		assert(isupport.extendedList == "");
832 		isupport.insertToken(keyValuePair("ELIST=CMNTU"));
833 		assert(isupport.extendedList == "CMNTU");
834 		isupport.insertToken(keyValuePair("-ELIST"));
835 		assert(isupport.extendedList == "");
836 	}
837 	{
838 		assert(isupport.banExceptions.isNull);
839 		isupport.insertToken(keyValuePair("EXCEPTS"));
840 		assert(isupport.banExceptions == 'e');
841 		isupport.insertToken(keyValuePair("EXCEPTS=f"));
842 		assert(isupport.banExceptions == 'f');
843 		isupport.insertToken(keyValuePair("EXCEPTS=e"));
844 		assert(isupport.banExceptions == 'e');
845 		isupport.insertToken(keyValuePair("-EXCEPTS"));
846 		assert(isupport.banExceptions.isNull);
847 	}
848 	{
849 		assert(isupport.banExtensions.isNull);
850 		isupport.insertToken(keyValuePair("EXTBAN=~,cqnr"));
851 		assert(isupport.banExtensions.get.prefix == '~');
852 		assert(isupport.banExtensions.get.banTypes == "cqnr");
853 		isupport.insertToken(keyValuePair("EXTBAN=,ABCNOQRSTUcjmprsz"));
854 		assert(isupport.banExtensions.get.prefix.isNull);
855 		assert(isupport.banExtensions.get.banTypes == "ABCNOQRSTUcjmprsz");
856 		isupport.insertToken(keyValuePair("EXTBAN=~,qjncrRa"));
857 		assert(isupport.banExtensions.get.prefix == '~');
858 		assert(isupport.banExtensions.get.banTypes == "qjncrRa");
859 		isupport.insertToken(keyValuePair("-EXTBAN"));
860 		assert(isupport.banExtensions.isNull);
861 		isupport.insertToken(keyValuePair("EXTBAN=8"));
862 		assert(isupport.banExtensions.isNull);
863 	}
864 	{
865 		assert(isupport.forcedNickChanges == false);
866 		isupport.insertToken(keyValuePair("FNC"));
867 		assert(isupport.forcedNickChanges == true);
868 		isupport.insertToken(keyValuePair("-FNC"));
869 		assert(isupport.forcedNickChanges == false);
870 	}
871 	{
872 		assert(isupport.channelIDLengths.length == 0);
873 		isupport.insertToken(keyValuePair("IDCHAN=!:5"));
874 		assert(isupport.channelIDLengths['!'] == 5);
875 		isupport.insertToken(keyValuePair("-IDCHAN"));
876 		assert(isupport.channelIDLengths.length == 0);
877 	}
878 	{
879 		assert(isupport.inviteExceptions.isNull);
880 		isupport.insertToken(keyValuePair("INVEX"));
881 		assert(isupport.inviteExceptions.get == 'I');
882 		isupport.insertToken(keyValuePair("INVEX=q"));
883 		assert(isupport.inviteExceptions.get == 'q');
884 		isupport.insertToken(keyValuePair("INVEX=I"));
885 		assert(isupport.inviteExceptions.get == 'I');
886 		isupport.insertToken(keyValuePair("-INVEX"));
887 		assert(isupport.inviteExceptions.isNull);
888 	}
889 	{
890 		assert(isupport.kickLength == ulong.max);
891 		isupport.insertToken(keyValuePair("KICKLEN=180"));
892 		assert(isupport.kickLength == 180);
893 		isupport.insertToken(keyValuePair("KICKLEN="));
894 		assert(isupport.kickLength == ulong.max);
895 		isupport.insertToken(keyValuePair("KICKLEN=2"));
896 		isupport.insertToken(keyValuePair("-KICKLEN"));
897 		assert(isupport.kickLength == ulong.max);
898 	}
899 	{
900 		assert(!isupport.knock);
901 		isupport.insertToken(keyValuePair("KNOCK"));
902 		assert(isupport.knock);
903 		isupport.insertToken(keyValuePair("-KNOCK"));
904 		assert(!isupport.knock);
905 	}
906 	{
907 		import std.algorithm.searching : canFind;
908 		assert(isupport.languages.length == 0);
909 		isupport.insertToken(keyValuePair("LANGUAGE=2,en,i-klingon"));
910 		assert(isupport.languages.canFind("en"));
911 		assert(isupport.languages.canFind("i-klingon"));
912 		isupport.insertToken(keyValuePair("-LANGUAGE"));
913 		assert(isupport.languages.length == 0);
914 	}
915 	{
916 		assert(isupport.lineLength == 512);
917 		isupport.insertToken(keyValuePair("LINELEN=2048"));
918 		assert(isupport.lineLength == 2048);
919 		isupport.insertToken(keyValuePair("LINELEN=512"));
920 		assert(isupport.lineLength == 512);
921 		isupport.insertToken(keyValuePair("LINELEN=2"));
922 		assert(isupport.lineLength == 2);
923 		isupport.insertToken(keyValuePair("-LINELEN"));
924 		assert(isupport.lineLength == 512);
925 	}
926 	{
927 		assert(!isupport.supportsMap);
928 		isupport.insertToken(keyValuePair("MAP"));
929 		assert(isupport.supportsMap);
930 		isupport.insertToken(keyValuePair("-MAP"));
931 		assert(!isupport.supportsMap);
932 	}
933 	{
934 		assert('b' !in isupport.maxList);
935 		isupport.insertToken(keyValuePair("MAXBANS=5"));
936 		assert(isupport.maxList['b'] == 5);
937 		isupport.insertToken(keyValuePair("-MAXBANS"));
938 		assert('b' !in isupport.maxList);
939 	}
940 	{
941 		assert('#' !in isupport.chanLimits);
942 		isupport.insertToken(keyValuePair("MAXCHANNELS=25"));
943 		assert(isupport.chanLimits['#'] == 25);
944 		isupport.insertToken(keyValuePair("-MAXCHANNELS"));
945 		assert('#' !in isupport.chanLimits);
946 	}
947 	{
948 		assert(isupport.maxList.length == 0);
949 		isupport.insertToken(keyValuePair("MAXLIST=beI:25"));
950 		assert(isupport.maxList['b'] == 25);
951 		assert(isupport.maxList['e'] == 25);
952 		assert(isupport.maxList['I'] == 25);
953 		isupport.insertToken(keyValuePair("MAXLIST=b:25,eI:50"));
954 		assert(isupport.maxList['b'] == 25);
955 		assert(isupport.maxList['e'] == 50);
956 		assert(isupport.maxList['I'] == 50);
957 		isupport.insertToken(keyValuePair("-MAXLIST"));
958 		assert(isupport.maxList.length == 0);
959 	}
960 	{
961 		assert(isupport.maximumParameters == 12);
962 		isupport.insertToken(keyValuePair("MAXPARA=32"));
963 		assert(isupport.maximumParameters == 32);
964 		isupport.insertToken(keyValuePair("-MAXPARA"));
965 		assert(isupport.maximumParameters == 12);
966 	}
967 	{
968 		assert(isupport.maxTargets == ulong.max);
969 		isupport.insertToken(keyValuePair("MAXTARGETS=8"));
970 		assert(isupport.maxTargets == 8);
971 		isupport.insertToken(keyValuePair("-MAXTARGETS"));
972 		assert(isupport.maxTargets == ulong.max);
973 	}
974 	{
975 		assert(isupport.metadata.isNull);
976 		isupport.insertToken(keyValuePair("METADATA=30"));
977 		assert(isupport.metadata == 30);
978 		isupport.insertToken(keyValuePair("METADATA=x"));
979 		assert(isupport.metadata.isNull);
980 		isupport.insertToken(keyValuePair("METADATA"));
981 		assert(isupport.metadata == ulong.max);
982 		isupport.insertToken(keyValuePair("-METADATA"));
983 		assert(isupport.metadata.isNull);
984 	}
985 	{
986 		//As specified in RFC1459, default number of "variable" modes is 3 per command.
987 		assert(isupport.maxModesPerCommand == 3);
988 		isupport.insertToken(keyValuePair("MODES"));
989 		assert(isupport.maxModesPerCommand == ulong.max);
990 		isupport.insertToken(keyValuePair("MODES=3"));
991 		assert(isupport.maxModesPerCommand == 3);
992 		isupport.insertToken(keyValuePair("MODES=5"));
993 		assert(isupport.maxModesPerCommand == 5);
994 		isupport.insertToken(keyValuePair("-MODES"));
995 		assert(isupport.maxModesPerCommand == 3);
996 	}
997 	{
998 		assert(isupport.monitorTargetLimit.isNull);
999 		isupport.insertToken(keyValuePair("MONITOR=6"));
1000 		assert(isupport.monitorTargetLimit == 6);
1001 		isupport.insertToken(keyValuePair("MONITOR"));
1002 		assert(isupport.monitorTargetLimit == ulong.max);
1003 		isupport.insertToken(keyValuePair("-MONITOR"));
1004 		assert(isupport.monitorTargetLimit.isNull);
1005 	}
1006 	{
1007 		assert(!isupport.namesExtended);
1008 		isupport.insertToken(keyValuePair("NAMESX"));
1009 		assert(isupport.namesExtended);
1010 		isupport.insertToken(keyValuePair("-NAMESX"));
1011 		assert(!isupport.namesExtended);
1012 	}
1013 	{
1014 		assert(isupport.network == "");
1015 		isupport.insertToken(keyValuePair("NETWORK=EFNet"));
1016 		assert(isupport.network == "EFNet");
1017 		isupport.insertToken(keyValuePair("NETWORK=Rizon"));
1018 		assert(isupport.network == "Rizon");
1019 		isupport.insertToken(keyValuePair("-NETWORK"));
1020 		assert(isupport.network == "");
1021 	}
1022 	{
1023 		assert(isupport.nickLength == 9);
1024 		isupport.insertToken(keyValuePair("NICKLEN=32"));
1025 		assert(isupport.nickLength == 32);
1026 		isupport.insertToken(keyValuePair("NICKLEN=9"));
1027 		assert(isupport.nickLength == 9);
1028 		isupport.insertToken(keyValuePair("NICKLEN=32"));
1029 		isupport.insertToken(keyValuePair("-NICKLEN"));
1030 		assert(isupport.nickLength == 9);
1031 	}
1032 	{
1033 		assert(!isupport.noQuit);
1034 		isupport.insertToken(keyValuePair("NOQUIT"));
1035 		assert(isupport.noQuit);
1036 		isupport.insertToken(keyValuePair("-NOQUIT"));
1037 		assert(!isupport.noQuit);
1038 	}
1039 	{
1040 		assert(!isupport.allowsOperOverride);
1041 		isupport.insertToken(keyValuePair("OVERRIDE"));
1042 		assert(isupport.allowsOperOverride);
1043 		isupport.insertToken(keyValuePair("-OVERRIDE"));
1044 		assert(!isupport.allowsOperOverride);
1045 	}
1046 	{
1047 		assert(!isupport.penalty);
1048 		isupport.insertToken(keyValuePair("PENALTY"));
1049 		assert(isupport.penalty);
1050 		isupport.insertToken(keyValuePair("-PENALTY"));
1051 		assert(!isupport.penalty);
1052 	}
1053 	{
1054 		//assert(isupport.prefixes == ['o': '@', 'v': '+']);
1055 		isupport.insertToken(keyValuePair("PREFIX"));
1056 		assert(isupport.prefixes.length == 0);
1057 		isupport.insertToken(keyValuePair("PREFIX=(ov)@+"));
1058 		assert(isupport.prefixes == ['o': '@', 'v': '+']);
1059 		isupport.insertToken(keyValuePair("PREFIX=(qaohv)~&@%+"));
1060 		assert(isupport.prefixes == ['o': '@', 'v': '+', 'q': '~', 'a': '&', 'h': '%']);
1061 		isupport.insertToken(keyValuePair("-PREFIX"));
1062 		assert(isupport.prefixes == ['o': '@', 'v': '+']);
1063 	}
1064 	{
1065 		assert(!isupport.rfc2812);
1066 		isupport.insertToken(keyValuePair("RFC2812"));
1067 		assert(isupport.rfc2812);
1068 		isupport.insertToken(keyValuePair("-RFC2812"));
1069 		assert(!isupport.rfc2812);
1070 	}
1071 	{
1072 		assert(!isupport.safeList);
1073 		isupport.insertToken(keyValuePair("SAFELIST"));
1074 		assert(isupport.safeList);
1075 		isupport.insertToken(keyValuePair("-SAFELIST"));
1076 		assert(!isupport.safeList);
1077 	}
1078 	{
1079 		assert(!isupport.secureList);
1080 		isupport.insertToken(keyValuePair("SECURELIST"));
1081 		assert(isupport.secureList);
1082 		isupport.insertToken(keyValuePair("-SECURELIST"));
1083 		assert(!isupport.secureList);
1084 	}
1085 	{
1086 		assert(isupport.silence.isNull);
1087 		isupport.insertToken(keyValuePair("SILENCE=15"));
1088 		assert(isupport.silence == 15);
1089 		isupport.insertToken(keyValuePair("SILENCE"));
1090 		assert(isupport.silence == ulong.max);
1091 		isupport.insertToken(keyValuePair("-SILENCE"));
1092 		assert(isupport.silence.isNull);
1093 	}
1094 	{
1095 		assert(isupport.sslServer == "");
1096 		isupport.insertToken(keyValuePair("SSL=1.2.3.4:6668;4.3.2.1:6669;*:6660;"));
1097 		assert(isupport.sslServer == "1.2.3.4:6668;4.3.2.1:6669;*:6660;");
1098 		isupport.insertToken(keyValuePair("-SSL"));
1099 		assert(isupport.sslServer == "");
1100 	}
1101 	{
1102 		assert(!isupport.startTLS);
1103 		isupport.insertToken(keyValuePair("STARTTLS"));
1104 		assert(isupport.startTLS);
1105 		isupport.insertToken(keyValuePair("-STARTTLS"));
1106 		assert(!isupport.startTLS);
1107 	}
1108 	{
1109 		assert(isupport.statusMessage == "");
1110 		isupport.insertToken(keyValuePair("STATUSMSG=@+"));
1111 		assert(isupport.statusMessage == "@+");
1112 		isupport.insertToken(keyValuePair("-STATUSMSG"));
1113 		assert(isupport.statusMessage == "");
1114 	}
1115 	{
1116 		assert(isupport.standard.isNull);
1117 		isupport.insertToken(keyValuePair("STD=i-d"));
1118 		assert(isupport.standard == "i-d");
1119 		isupport.insertToken(keyValuePair("-STD"));
1120 		assert(isupport.standard.isNull);
1121 	}
1122 	{
1123 		assert(isupport.targetMaxByCommand.length == 0);
1124 		isupport.insertToken(keyValuePair("TARGMAX=PRIVMSG:3,WHOIS:1,JOIN:"));
1125 		assert(isupport.targetMaxByCommand["PRIVMSG"]== 3);
1126 		assert(isupport.targetMaxByCommand["WHOIS"]== 1);
1127 		assert(isupport.targetMaxByCommand["JOIN"]== ulong.max);
1128 		isupport.insertToken(keyValuePair("TARGMAX"));
1129 		assert(isupport.targetMaxByCommand.length == 0);
1130 		isupport.insertToken(keyValuePair("-TARGMAX"));
1131 		assert(isupport.targetMaxByCommand.length == 0);
1132 	}
1133 	{
1134 		assert(isupport.topicLength.isNull);
1135 		isupport.insertToken(keyValuePair("TOPICLEN=120"));
1136 		assert(isupport.topicLength == 120);
1137 		isupport.insertToken(keyValuePair("TOPICLEN="));
1138 		assert(isupport.topicLength == ulong.max);
1139 		isupport.insertToken(keyValuePair("-TOPICLEN"));
1140 		assert(isupport.topicLength.isNull);
1141 	}
1142 	{
1143 		assert(!isupport.userhostsInNames);
1144 		isupport.insertToken(keyValuePair("UHNAMES"));
1145 		assert(isupport.userhostsInNames);
1146 		isupport.insertToken(keyValuePair("-UHNAMES"));
1147 		assert(!isupport.userhostsInNames);
1148 	}
1149 	{
1150 		assert(!isupport.userIP);
1151 		isupport.insertToken(keyValuePair("USERIP"));
1152 		assert(isupport.userIP);
1153 		isupport.insertToken(keyValuePair("-USERIP"));
1154 		assert(!isupport.userIP);
1155 	}
1156 	{
1157 		assert(isupport.userLength.isNull);
1158 		isupport.insertToken(keyValuePair("USERLEN=12"));
1159 		assert(isupport.userLength == 12);
1160 		isupport.insertToken(keyValuePair("USERLEN="));
1161 		assert(isupport.userLength == ulong.max);
1162 		isupport.insertToken(keyValuePair("-USERLEN"));
1163 		assert(isupport.userLength.isNull);
1164 	}
1165 	{
1166 		assert(!isupport.variableBanList);
1167 		isupport.insertToken(keyValuePair("VBANLIST"));
1168 		assert(isupport.variableBanList);
1169 		isupport.insertToken(keyValuePair("-VBANLIST"));
1170 		assert(!isupport.variableBanList);
1171 	}
1172 	{
1173 		assert(!isupport.virtualChannels);
1174 		isupport.insertToken(keyValuePair("VCHANS"));
1175 		assert(isupport.virtualChannels);
1176 		isupport.insertToken(keyValuePair("-VCHANS"));
1177 		assert(!isupport.virtualChannels);
1178 	}
1179 	{
1180 		assert(!isupport.wAllChannelOps);
1181 		isupport.insertToken(keyValuePair("WALLCHOPS"));
1182 		assert(isupport.wAllChannelOps);
1183 		isupport.insertToken(keyValuePair("-WALLCHOPS"));
1184 		assert(!isupport.wAllChannelOps);
1185 	}
1186 	{
1187 		assert(!isupport.wAllChannelVoices);
1188 		isupport.insertToken(keyValuePair("WALLVOICES"));
1189 		assert(isupport.wAllChannelVoices);
1190 		isupport.insertToken(keyValuePair("-WALLVOICES"));
1191 		assert(!isupport.wAllChannelVoices);
1192 	}
1193 	{
1194 		assert(isupport.maximumWatches.isNull);
1195 		isupport.insertToken(keyValuePair("WATCH=100"));
1196 		assert(isupport.maximumWatches == 100);
1197 		isupport.insertToken(keyValuePair("WATCH"));
1198 		assert(isupport.maximumWatches == ulong.max);
1199 		isupport.insertToken(keyValuePair("-WATCH"));
1200 		assert(isupport.maximumWatches.isNull);
1201 	}
1202 	{
1203 		assert(!isupport.whoX);
1204 		isupport.insertToken(keyValuePair("WHOX"));
1205 		assert(isupport.whoX);
1206 		isupport.insertToken(keyValuePair("-WHOX"));
1207 		assert(!isupport.whoX);
1208 	}
1209 	{
1210 		assert(isupport.unknownTokens.length == 0);
1211 		isupport.insertToken(keyValuePair("WHOA"));
1212 		assert(isupport.unknownTokens["WHOA"] == "");
1213 		isupport.insertToken(keyValuePair("WHOA=AOHW"));
1214 		assert(isupport.unknownTokens["WHOA"] == "AOHW");
1215 		isupport.insertToken(keyValuePair("WHOA=0"));
1216 		isupport.insertToken(keyValuePair("WHOA2=1"));
1217 		assert(isupport.unknownTokens["WHOA"] == "0");
1218 		assert(isupport.unknownTokens["WHOA2"] == "1");
1219 		isupport.insertToken(keyValuePair("-WHOA"));
1220 		isupport.insertToken(keyValuePair("-WHOA2"));
1221 		assert(isupport.unknownTokens.length == 0);
1222 	}
1223 }
1224 /++
1225 + Parses an ISUPPORT token.
1226 +/
1227 private auto keyValuePair(string token) pure @safe {
1228 	import std.algorithm : findSplit, skipOver;
1229 	immutable isDisabled = token.skipOver('-');
1230 	auto splitParams = token.findSplit("=");
1231 	Nullable!string param;
1232 	if (!isDisabled) {
1233 		param = splitParams[2];
1234 	}
1235 	return TokenPair(splitParams[0], param);
1236 }
1237 /++
1238 +
1239 +/
1240 void parseNumeric(Numeric numeric: Numeric.RPL_ISUPPORT, T)(T input, ref ISupport iSupport) if (isForwardRange!T) {
1241 	import std.range : drop;
1242 	import std.typecons : Nullable;
1243 	input.popFront();
1244 	while (!input.empty && !input.drop(1).empty) {
1245 		iSupport.insertToken(keyValuePair(input.front));
1246 		input.popFront();
1247 	}
1248 }
1249 /++
1250 +
1251 +/
1252 auto parseNumeric(Numeric numeric: Numeric.RPL_ISUPPORT, T)(T input) {
1253 	ISupport tmp;
1254 	parseNumeric!numeric(input, tmp);
1255 	return tmp;
1256 }
1257 ///
1258 @safe pure /+nothrow @nogc+/ unittest { //Numeric.RPL_ISUPPORT
1259 	import std.exception : assertNotThrown, assertThrown;
1260 	import std.range : only;
1261 	import virc.modes : ModeType;
1262 	{
1263 		auto support = parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone", "STATUSMSG=~&@%+", "CHANLIMIT=#:2", "CHANMODES=a,b,c,d", "CHANTYPES=#", "are supported by this server"));
1264 		assert(support.statusMessage == "~&@%+");
1265 		assert(support.chanLimits == ['#': 2UL]);
1266 		assert(support.channelTypes == "#");
1267 		assert(support.channelModeTypes == ['a':ModeType.a, 'b':ModeType.b, 'c':ModeType.c, 'd':ModeType.d]);
1268 		parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone", "-STATUSMSG", "-CHANLIMIT", "-CHANMODES", "-CHANTYPES", "are supported by this server"), support);
1269 		assert(support.statusMessage == support.statusMessage.init);
1270 		assert(support.chanLimits == support.chanLimits.init);
1271 		assert(support.channelTypes == "#&!+");
1272 		assert(support.channelModeTypes == support.channelModeTypes.init);
1273 	}
1274 	{
1275 		auto support = parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone", "SILENCE=4", "are supported by this server"));
1276 		assert(support.silence == 4);
1277 		parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone", "SILENCE", "are supported by this server"), support);
1278 		assert(support.silence == ulong.max);
1279 		parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone", "SILENCE=6", "are supported by this server"), support);
1280 		parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone" ,"-SILENCE", "are supported by this server"), support);
1281 		assert(support.silence.isNull);
1282 	}
1283 	{
1284 		assertNotThrown(parseNumeric!(Numeric.RPL_ISUPPORT)(only("someone", "are supported by this server")));
1285 	}
1286 }