public bool TryParseText(string message, out MessageParseResult result) { var match = Pattern.Match(message); if (match.Success) { Inline converted = Convert(match.Value); result = new MessageParseResult(converted); result.Prefix = message.Substring(0, match.Index); int lastIndex = match.Index + match.Length; result.Suffix = message.Substring(lastIndex); } else { result = null; } return(match.Success); }
public static MessageParseResult ParseDuplexMessage(ArraySegment <byte> message) { var parseResult = new MessageParseResult(); var reader = new JsonReader(message.Array !, message.Offset); if (reader.ReadIsBeginObject()) { try { while (true) { reader.SkipWhiteSpace(); var buffer = reader.GetBufferUnsafe().AsSpan(reader.GetCurrentOffsetUnsafe()); ref var bufferRef = ref MemoryMarshal.GetReference(buffer); const uint id = (('"') | ('i' << 8) | ('d' << 16) | ('"' << 24)); const ulong method = ((ulong)'"') | ((ulong)'m' << 8) | ((ulong)'e' << 16) | ((ulong)'t' << 24) | ((ulong)'h' << 32) | ((ulong)'o' << 40) | ((ulong)'d' << 48) | ((ulong)'"' << 56); const ulong @params = ((ulong)'"') | ((ulong)'p' << 8) | ((ulong)'a' << 16) | ((ulong)'r' << 24) | ((ulong)'a' << 32) | ((ulong)'m' << 40) | ((ulong)'s' << 48) | ((ulong)'"' << 56); const ulong jsonrpc = (((ulong)'"') | ((ulong)'j' << 8) | ((ulong)'s' << 16) | ((ulong)'o' << 24) | ((ulong)'n' << 32) | ((ulong)'r' << 40) | ((ulong)'p' << 48) | ((ulong)'c' << 56)); const ulong ___2_0 = (((ulong)'"') | ((ulong)'2' << 8) | ((ulong)'.' << 16) | ((ulong)'0' << 24) | ((ulong)'"' << 32)) << 24; const ulong result = (((ulong)'"') | ((ulong)'r' << 8) | ((ulong)'e' << 16) | ((ulong)'s' << 24) | ((ulong)'u' << 32) | ((ulong)'l' << 40) | ((ulong)'t' << 48) | ((ulong)'"' << 56)); const ulong error = (((ulong)'"') | ((ulong)'e' << 8) | ((ulong)'r' << 16) | ((ulong)'r' << 24) | ((ulong)'o' << 32) | ((ulong)'r' << 40) | ((ulong)'"' << 48)); switch (buffer.Length) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: goto UnknownProperty; //最短の正常な文字列パターンは"id":1}で7byteある case 7: case 8: case 9: case 10: { if (Unsafe.ReadUnaligned <uint>(ref bufferRef) == id) { goto ID; } else { goto UnknownProperty; } } case 11: { var chars8 = Unsafe.ReadUnaligned <ulong>(ref bufferRef); if ((uint)chars8 == id) { goto ID; } else if ((chars8 & (ulong)0x00FFFFFFFFFFFFFF) == error) { goto Error; } else { goto UnknownProperty; } } //その次に短いのは"params":[]}または"params":{}}または"method":""または"result":1} case 12: case 13: case 14: case 15: { var chars8 = Unsafe.ReadUnaligned <ulong>(ref bufferRef); switch (chars8) { case result: { goto Result; } case method: { goto Method; } case @params: { goto Params; } default: { if ((uint)chars8 == id) { goto ID; } else if ((chars8 & (ulong)0x00FFFFFFFFFFFFFF) == error) { goto Error; } else { goto UnknownProperty; } } } } //その次が"jsonrpc":"2.0"} default: { var chars8 = Unsafe.ReadUnaligned <ulong>(ref bufferRef); switch (chars8) { case jsonrpc: { goto JsonRpc; } case result: { goto Result; } case method: { goto Method; } case @params: { goto Params; } default: { if ((uint)chars8 == id) { goto ID; } else if ((chars8 & (ulong)0x00FFFFFFFFFFFFFF) == error) { goto Error; } else { goto UnknownProperty; } } } } JsonRpc: { if (Unsafe.AddByteOffset(ref bufferRef, (IntPtr)(8)) == (byte)'"') { reader.AdvanceOffset(9); if (reader.ReadIsNameSeparator()) { reader.SkipWhiteSpace(); buffer = reader.GetBufferUnsafe().AsSpan(reader.GetCurrentOffsetUnsafe()); bufferRef = ref MemoryMarshal.GetReference(buffer); if ((Unsafe.ReadUnaligned <ulong>(ref Unsafe.AddByteOffset(ref bufferRef, (IntPtr)(-3))) & 0xFFFFFFFFFF000000) == ___2_0) { reader.AdvanceOffset(5); parseResult[MessagePropertyKind.JsonRpcVersion] = PropertyState.Valid; break; } else { goto UnExpectedFormatProperty; } } else { goto InvalidJson; } } else { goto UnknownProperty; } } Method: { reader.AdvanceOffset(8); if (reader.ReadIsNameSeparator()) { try { parseResult.Method = EscapedUTF8String.FromEscapedNonQuoted(reader.ReadStringSegmentRaw()); } catch (JsonParsingException) { parseResult[MessagePropertyKind.Method] = PropertyState.Invalid; goto UnExpectedFormatProperty; } parseResult[MessagePropertyKind.Method] = PropertyState.Valid; break; } else { goto InvalidJson; } } Params: { reader.AdvanceOffset(8); if (reader.ReadIsNameSeparator()) { try { parseResult.Params = reader.ReadNextBlockSegment(); } catch (JsonParsingException) { parseResult[MessagePropertyKind.Params] = PropertyState.Invalid; goto UnExpectedFormatProperty; } parseResult[MessagePropertyKind.Params] = PropertyState.Valid; break; } else { goto InvalidJson; } } ID: { reader.AdvanceOffset(4); if (reader.ReadIsNameSeparator()) { try { parseResult.id = ID.Formatter.DeserializeNullableSafe(ref reader); } catch (JsonParsingException) { parseResult[MessagePropertyKind.ID] = PropertyState.Invalid; goto UnExpectedFormatProperty; } parseResult[MessagePropertyKind.ID] = PropertyState.Valid; break; } else { goto InvalidJson; } } Result: { reader.AdvanceOffset(8); if (reader.ReadIsNameSeparator()) { try { parseResult.Result = reader.ReadNextBlockSegment(); } catch (JsonParsingException) { parseResult[MessagePropertyKind.Result] = PropertyState.Invalid; goto UnExpectedFormatProperty; } parseResult[MessagePropertyKind.Result] = PropertyState.Valid; break; } else { goto InvalidJson; } } Error: { reader.AdvanceOffset(7); if (reader.ReadIsNameSeparator()) { try { parseResult.Error = reader.ReadNextBlockSegment(); //Client.JsonResolver.GetFormatterWithVerify<Client.ResponseError<object?>>().Deserialize(ref reader, Client.JsonResolver); } catch (JsonParsingException) { parseResult[MessagePropertyKind.Error] = PropertyState.Invalid; goto UnExpectedFormatProperty; } parseResult[MessagePropertyKind.Error] = PropertyState.Valid; break; } else { goto InvalidJson; } } UnknownProperty: { parseResult.ParseErrors |= MessageParseErrors.HasUnknownProperty; try { reader.ReadStringSegmentRaw(); } catch (JsonParsingException) { goto InvalidJson; } if (reader.ReadIsValueSeparator()) { try { reader.ReadNextBlock(); } catch (JsonParsingException) { goto InvalidJson; } break; } else { goto InvalidJson; } } UnExpectedFormatProperty: { parseResult.ParseErrors = MessageParseErrors.HasInvalidProperty; try { reader.ReadNextBlock(); } catch (JsonParsingException) { goto InvalidJson; } break; } } switch (reader.GetCurrentJsonToken()) { case JsonToken.ValueSeparator: { reader.AdvanceOffset(1); continue; } case JsonToken.EndObject: { return(parseResult); } default: goto InvalidJson; } } }
/// <summary> /// Process a message that is categorised as being a numeric reply in the RFC documents. /// </summary> /// <param name="message">The message to process.</param> private void ProcessNumericReply(Message message) { Reply reply = (Reply)int.Parse(message.Command); Action<IRCTabPage> appendMessage = (IRCTabPage tabPage) => { MessageTypeAttribute attributes = IRCMarshal.GetReplyAttributes(reply); MessageType messageType = attributes.MessageType; GlobalSettings settings = GlobalSettings.Instance; string source = attributes.Source; string content; if (attributes.OutputAction == null) { content = message.ToString(attributes.OutputFormat, attributes.ParameterDelimiter, attributes.RemoveFirstParameter); } else { content = attributes.OutputAction.Invoke(message); } if (settings.DebugMode == GlobalSettings.Boolean.Yes) { tabPage.AppendMessage(reply.ToString(), "[RAW]", message.ToString(), MessageType.WarningMessage); } tabPage.AppendMessage(reply.ToString(), source, content, messageType); }; // If we are retrieving list replies we need to populate the channel browser if (reply == Reply.RPL_LISTSTART) { this.parent.InvokeAction(() => this.ChannelBrowser.BeginRefresh(true)); return; } else if (reply == Reply.RPL_LIST) { this.ChannelBrowser.AddChannel(message); return; } else if (reply == Reply.RPL_LISTEND) { this.parent.InvokeAction(() => this.ChannelBrowser.FlushChannels()); return; } // If we are still awaiting the UserHost reply (i.e. we are awaiting confirmation of the full userhost // prefix that we will use to determine the max PRIVMSG lengths) then cache it in the marshal for future // reference. if (this.AwaitingUserHostMessage && reply == Reply.RPL_USERHOST) { this.fullUserHost = message.TrailingParameter; this.AwaitingUserHostMessage = false; // Execute any auto commands. if (!this.hasExecutedAutoCommands && this.AutoCommands.Count > 0) { for (int i = 0; i < this.AutoCommands.Count; i++) { MessageParseResult parseResult = MessageFactory.CreateFromUserInput(this.AutoCommands[i], null); if (parseResult.Success) { this.Send(this.ServerTab, parseResult.IRCMessage); } } this.hasExecutedAutoCommands = true; } if (this.reconnecting) { // Pause the thread for a second to give time for any authentication to // take place and then rejoin the channels. System.Threading.Thread.Sleep(1000); this.Channels.ForEach(i => { if (i.TabPage.TabType == IRCTabType.Channel) { this.Send(this.ServerTab, new JoinMessage(i.Name)); } }); this.reconnecting = false; } return; } // If the user has received a new hidden host then we need to re-evaluate // their full user host mask that will be seen by other clients. if (reply == Reply.RPL_HOSTHIDDEN) { this.AwaitingUserHostMessage = true; this.Send(this.ServerTab, new UserHostMessage(new string[] { this.connection.Nickname })); } // If we have a names reply or an end of names reply, then we need to check the channel // it is in regards to exists in our channel list, and if it does check to see if it // is awaiting a reply from a names request (i.e. it is wanting to refresh the user list). // // If this is indeed the case, we need to force it through to that channel rather than // following the default procedure of going to the selected tab. if ((reply == Reply.RPL_NAMREPLY) || (reply == Reply.RPL_ENDOFNAMES)) { string target = string.Empty; if (reply == Reply.RPL_NAMREPLY) { target = message.Parameters[2]; } else { target = message.Parameters[1]; } IRCChannel channel = this.channels.Find(i => i.Name.Equals(target, StringComparison.OrdinalIgnoreCase)); if (channel != null && channel.ExpectingNamesMessage) { channel.HandleReply(message); return; } } // If the currently selected tab belongs to the channel list for this connection // AND we aren't awaiting a mode message (i.e. connecting to the server) // then marshal the message to the owning channel, otherwise default to the server tab this.TabHost.InvokeAction(() => { IRCChannel selectedChannel = this.Channels.Find(i => this.TabHost.SelectedTab.Equals(i.TabPage)); if ((selectedChannel != null) && (!this.AwaitingModeMessage)) { selectedChannel.HandleReply(message); } else { appendMessage.Invoke(this.ServerTab); } }); // If a nick in use message comes through, we need to revert the nick against the connection // back to the previously assigned nick (if there is one). // If there wasn't one, then we'll append an underscore to the current one and resend the nick message // we couldn't possibly get stuck in a loop, right? if (reply == Reply.ERR_NICKNAMEINUSE) { if (this.previousNickName.Equals(this.Connection.Nickname, StringComparison.OrdinalIgnoreCase)) { this.previousNickName = string.Format("{0}_", this.previousNickName); this.connection.Nickname = this.previousNickName; this.Send(this.ServerTab, new NickMessage(this.previousNickName)); } else { if (!this.AwaitingModeMessage) { this.Connection.Nickname = this.previousNickName; } else { this.previousNickName = string.Format("{0}_", this.connection.Nickname); this.connection.Nickname = this.previousNickName; this.Send(this.ServerTab, new NickMessage(this.previousNickName)); } } } }