// Unified protocol message parsing
            public static void parseProtocolMessage(ProtocolMessageCode code, byte[] data, RemoteEndpoint endpoint)
            {
                if (endpoint == null)
                {
                    Logging.error("Endpoint was null. parseProtocolMessage");
                    return;
                }

                try
                {
                    switch (code)
                    {
                    case ProtocolMessageCode.hello:
                        handleHello(data, endpoint);
                        break;

                    case ProtocolMessageCode.helloData:
                        handleHelloData(data, endpoint);
                        break;

                    case ProtocolMessageCode.getBlock:
                        BlockProtocolMessages.handleGetBlock(data, endpoint);
                        break;

                    case ProtocolMessageCode.getBalance:
                        WalletStateProtocolMessages.handleGetBalance(data, endpoint);
                        break;

                    case ProtocolMessageCode.getTransaction:
                        TransactionProtocolMessages.handleGetTransaction(data, endpoint);
                        break;

                    case ProtocolMessageCode.getTransaction2:
                        TransactionProtocolMessages.handleGetTransaction2(data, endpoint);
                        break;

                    case ProtocolMessageCode.getTransaction3:
                        TransactionProtocolMessages.handleGetTransaction3(data, endpoint);
                        break;

                    case ProtocolMessageCode.newTransaction:
                    case ProtocolMessageCode.transactionData:
                        TransactionProtocolMessages.handleTransactionData(data, endpoint);
                        break;

                    case ProtocolMessageCode.bye:
                        CoreProtocolMessage.processBye(data, endpoint);
                        break;

                    case ProtocolMessageCode.newBlock:
                    case ProtocolMessageCode.blockData:
                        BlockProtocolMessages.handleBlockData(data, endpoint);
                        break;

                    case ProtocolMessageCode.syncWalletState:
                        WalletStateProtocolMessages.handleSyncWalletState(data, endpoint);
                        break;

                    case ProtocolMessageCode.walletState:
                        WalletStateProtocolMessages.handleWalletState(data, endpoint);
                        break;

                    case ProtocolMessageCode.getWalletStateChunk:
                        WalletStateProtocolMessages.handleGetWalletStateChunk(data, endpoint);
                        break;

                    case ProtocolMessageCode.walletStateChunk:
                        WalletStateProtocolMessages.handleWalletStateChunk(data, endpoint);
                        break;

                    case ProtocolMessageCode.updatePresence:
                        PresenceProtocolMessages.handleUpdatePresence(data, endpoint);
                        break;

                    case ProtocolMessageCode.keepAlivePresence:
                        PresenceProtocolMessages.handleKeepAlivePresence(data, endpoint);
                        break;

                    case ProtocolMessageCode.getPresence:
                        PresenceProtocolMessages.handleGetPresence(data, endpoint);
                        break;

                    case ProtocolMessageCode.getPresence2:
                        PresenceProtocolMessages.handleGetPresence2(data, endpoint);
                        break;

                    case ProtocolMessageCode.getKeepAlives:
                        PresenceProtocolMessages.handleGetKeepAlives(data, endpoint);
                        break;

                    case ProtocolMessageCode.keepAlivesChunk:
                        PresenceProtocolMessages.handleKeepAlivesChunk(data, endpoint);
                        break;

                    // return 10 random presences of the selected type
                    case ProtocolMessageCode.getRandomPresences:
                        PresenceProtocolMessages.handleGetRandomPresences(data, endpoint);
                        break;

                    case ProtocolMessageCode.getUnappliedTransactions:
                        TransactionProtocolMessages.handleGetUnappliedTransactions(data, endpoint);
                        break;

                    case ProtocolMessageCode.blockTransactionsChunk:
                        BlockProtocolMessages.handleBlockTransactionsChunk(data, endpoint);
                        break;

                    case ProtocolMessageCode.attachEvent:
                        NetworkEvents.handleAttachEventMessage(data, endpoint);
                        break;

                    case ProtocolMessageCode.detachEvent:
                        NetworkEvents.handleDetachEventMessage(data, endpoint);
                        break;

                    case ProtocolMessageCode.blockSignature:
                        SignatureProtocolMessages.handleBlockSignature(data, endpoint);
                        break;

                    case ProtocolMessageCode.getBlockSignatures:
                    {
                        using (MemoryStream m = new MemoryStream(data))
                        {
                            using (BinaryReader reader = new BinaryReader(m))
                            {
                                ulong block_num = reader.ReadUInt64();

                                int    checksum_len = reader.ReadInt32();
                                byte[] checksum     = reader.ReadBytes(checksum_len);

                                SignatureProtocolMessages.handleGetBlockSignatures(block_num, checksum, endpoint);
                            }
                        }
                    }
                    break;

                    case ProtocolMessageCode.blockSignatures:
                        SignatureProtocolMessages.handleSigfreezedBlockSignatures(data, endpoint);
                        break;

                    case ProtocolMessageCode.getNextSuperBlock:
                        BlockProtocolMessages.handleGetNextSuperBlock(data, endpoint);
                        break;

                    case ProtocolMessageCode.getBlockHeaders:
                        BlockProtocolMessages.handleGetBlockHeaders(data, endpoint);
                        break;

                    case ProtocolMessageCode.getPIT:
                        BlockProtocolMessages.handleGetPIT(data, endpoint);
                        break;

                    case ProtocolMessageCode.inventory:
                        handleInventory(data, endpoint);
                        break;

                    case ProtocolMessageCode.inventory2:
                        handleInventory2(data, endpoint);
                        break;

                    case ProtocolMessageCode.getSignatures:
                        SignatureProtocolMessages.handleGetSignatures(data, endpoint);
                        break;

                    case ProtocolMessageCode.signaturesChunk:
                        SignatureProtocolMessages.handleSignaturesChunk(data, endpoint);
                        break;

                    case ProtocolMessageCode.getTransactions:
                        TransactionProtocolMessages.handleGetTransactions(data, endpoint);
                        break;

                    case ProtocolMessageCode.getTransactions2:
                        TransactionProtocolMessages.handleGetTransactions2(data, endpoint);
                        break;

                    case ProtocolMessageCode.transactionsChunk:
                        TransactionProtocolMessages.handleTransactionsChunk(data, endpoint);
                        break;

                    case ProtocolMessageCode.transactionsChunk2:
                        TransactionProtocolMessages.handleTransactionsChunk2(data, endpoint);
                        break;

                    case ProtocolMessageCode.getBlockHeaders2:
                        BlockProtocolMessages.handleGetBlockHeaders2(data, endpoint);
                        break;

                    case ProtocolMessageCode.getPIT2:
                        BlockProtocolMessages.handleGetPIT2(data, endpoint);
                        break;

                    case ProtocolMessageCode.getBlock2:
                        BlockProtocolMessages.handleGetBlock2(data, endpoint);
                        break;

                    case ProtocolMessageCode.getBlock3:
                        BlockProtocolMessages.handleGetBlock3(data, endpoint);
                        break;

                    case ProtocolMessageCode.getBalance2:
                        WalletStateProtocolMessages.handleGetBalance2(data, endpoint);
                        break;

                    case ProtocolMessageCode.getBlockSignatures2:
                    {
                        using (MemoryStream m = new MemoryStream(data))
                        {
                            using (BinaryReader reader = new BinaryReader(m))
                            {
                                ulong block_num = reader.ReadIxiVarUInt();

                                int    checksum_len = (int)reader.ReadIxiVarUInt();
                                byte[] checksum     = reader.ReadBytes(checksum_len);

                                SignatureProtocolMessages.handleGetBlockSignatures2(block_num, checksum, endpoint);
                            }
                        }
                    }
                    break;

                    case ProtocolMessageCode.blockSignature2:
                        SignatureProtocolMessages.handleBlockSignature2(data, endpoint);
                        break;

                    default:
                        break;
                    }
                }
                catch (Exception e)
                {
                    Logging.error("Error parsing network message. Details: {0}", e.ToString());
                }
            }