Skip to content

Commit

Permalink
add ethereum protocol dissector.
Browse files Browse the repository at this point in the history
as explained here for bitcoin https://www.ntop.org/guides/nDPI/protocols.html#ndpi-protocol-bitcoin
the same is applicable for ethereum.
ethereum detection was removed from mining protocol and is now handled separately.

Signed-off-by: Mahmoud Maatuq <[email protected]>
  • Loading branch information
mmaatuq committed Oct 24, 2023
1 parent e70333d commit 6bb654e
Show file tree
Hide file tree
Showing 156 changed files with 436 additions and 333 deletions.
14 changes: 14 additions & 0 deletions doc/protocols.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,17 @@ References: `Protocol Specs: <https://www.iso.org/standard/63648.html>`_.
Protocol Buffers (Protobuf) is a free and open-source cross-platform data format used to serialize structured data.

References: `Encoding: <https://protobuf.dev/programming-guides/encoding>`_.


.. _Proto 354:

`NDPI_PROTOCOL_ETHEREUM`
=======================
Ethereum is a decentralized, open-source blockchain with smart contract functionality.

References: `Main site <https://ethereum.org/en/developers/docs/intro-to-ethereum/>`_.


Notes:

- same as Bitcoin, not each crypto exchange is a mining, it could be a normal transaction, sending or receving or even blockchain exploration.
1 change: 1 addition & 0 deletions src/include/ndpi_protocol_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ typedef enum {
NDPI_PROTOCOL_RMCP = 351,
NDPI_PROTOCOL_CAN = 352,
NDPI_PROTOCOL_PROTOBUF = 353,
NDPI_PROTOCOL_ETHEREUM = 354,

#ifdef CUSTOM_NDPI_PROTOCOLS
#include "../../../nDPI-custom/custom_ndpi_protocol_ids.h"
Expand Down
1 change: 1 addition & 0 deletions src/include/ndpi_protocols.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ void init_haproxy_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_
void init_rmcp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id);
void init_can_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id);
void init_protobuf_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id);
void init_ethereum_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id);

/* ndpi_main.c */
extern u_int32_t ndpi_ip_port_hash_funct(u_int32_t ip, u_int16_t port);
Expand Down
38 changes: 19 additions & 19 deletions src/lib/inc_generated/ndpi_ethereum_match.c.inc
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,25 @@
/* ****************************************************** */


static ndpi_network ndpi_protocol_mining_protocol_list[] = {
{ 0x128A6C43 /* 18.138.108.67/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x03D12D4F /* 3.209.45.79/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x416C4665 /* 65.108.70.101/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x9D5A23A6 /* 157.90.35.166/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x92BE0D80 /* 146.190.13.128/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0xB28088E9 /* 178.128.136.233/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x8AC533B5 /* 138.197.51.181/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x92BE0167 /* 146.190.1.103/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0xAA40FA58 /* 170.64.250.88/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x8B3B31CE /* 139.59.49.206/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x8A447B98 /* 138.68.123.152/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x338D4E35 /* 51.141.78.53/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x0D5D3689 /* 13.93.54.137/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x5EED3672 /* 94.237.54.114/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x12DAFA42 /* 18.218.250.66/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x030B9343 /* 3.11.147.67/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x2E04637A /* 46.4.99.122/32 */, 32, NDPI_PROTOCOL_MINING },
{ 0x586346B6 /* 88.99.70.182/32 */, 32, NDPI_PROTOCOL_MINING },
static ndpi_network ndpi_protocol_ethereum_protocol_list[] = {
{ 0x128A6C43 /* 18.138.108.67/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x03D12D4F /* 3.209.45.79/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x416C4665 /* 65.108.70.101/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x9D5A23A6 /* 157.90.35.166/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x92BE0D80 /* 146.190.13.128/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0xB28088E9 /* 178.128.136.233/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x8AC533B5 /* 138.197.51.181/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x92BE0167 /* 146.190.1.103/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0xAA40FA58 /* 170.64.250.88/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x8B3B31CE /* 139.59.49.206/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x8A447B98 /* 138.68.123.152/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x338D4E35 /* 51.141.78.53/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x0D5D3689 /* 13.93.54.137/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x5EED3672 /* 94.237.54.114/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x12DAFA42 /* 18.218.250.66/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x030B9343 /* 3.11.147.67/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x2E04637A /* 46.4.99.122/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
{ 0x586346B6 /* 88.99.70.182/32 */, 32, NDPI_PROTOCOL_ETHEREUM },
/* End */
{ 0x0, 0, 0 }
};
11 changes: 9 additions & 2 deletions src/lib/ndpi_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1265,7 +1265,7 @@ static void ndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndp
ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
ndpi_set_proto_defaults(ndpi_str, 1 /* cleartext */, 1 /* app proto */, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_MINING,
"Mining", CUSTOM_CATEGORY_MINING,
ndpi_build_default_ports(ports_a, 30303, 0, 0, 0, 0) /* TCP */,
ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
ndpi_set_proto_defaults(ndpi_str, 1 /* cleartext */, 1 /* app proto */, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NEST_LOG_SINK,
"NestLogSink", NDPI_PROTOCOL_CATEGORY_CLOUD,
Expand Down Expand Up @@ -2171,6 +2171,10 @@ static void ndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndp
"Protobuf", NDPI_PROTOCOL_CATEGORY_NETWORK,
ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
ndpi_set_proto_defaults(ndpi_str, 1 /* cleartext */, 0 /* nw proto */, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_ETHEREUM,
"ETHEREUM", NDPI_PROTOCOL_CATEGORY_CRYPTO_CURRENCY,
ndpi_build_default_ports(ports_a, 30303, 0, 0, 0, 0) /* TCP */,
ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);

#ifdef CUSTOM_NDPI_PROTOCOLS
#include "../../../nDPI-custom/custom_ndpi_main.c"
Expand Down Expand Up @@ -2885,7 +2889,7 @@ struct ndpi_detection_module_struct *ndpi_init_detection_module(ndpi_init_prefs
ndpi_init_ptree_ipv4(ndpi_str, ndpi_str->protocols_ptree, ndpi_protocol_amazon_aws_protocol_list);

if(!(prefs & ndpi_dont_load_ethereum_list))
ndpi_init_ptree_ipv4(ndpi_str, ndpi_str->protocols_ptree, ndpi_protocol_mining_protocol_list);
ndpi_init_ptree_ipv4(ndpi_str, ndpi_str->protocols_ptree, ndpi_protocol_ethereum_protocol_list);

if(!(prefs & ndpi_dont_load_zoom_list))
ndpi_init_ptree_ipv4(ndpi_str, ndpi_str->protocols_ptree, ndpi_protocol_zoom_protocol_list);
Expand Down Expand Up @@ -5257,6 +5261,9 @@ static int ndpi_callback_init(struct ndpi_detection_module_struct *ndpi_str) {
/* Protobuf */
init_protobuf_dissector(ndpi_str, &a);

/* ETHEREUM */
init_ethereum_dissector(ndpi_str, &a);

#ifdef CUSTOM_NDPI_PROTOCOLS
#include "../../../nDPI-custom/custom_ndpi_main_init.c"
#endif
Expand Down
158 changes: 158 additions & 0 deletions src/lib/protocols/ethereum.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* ethereum.c
*
* Copyright (C) 2018-22 - ntop.org
*
* This file is part of nDPI, an open source deep packet inspection
* library based on the OpenDPI and PACE technology by ipoque GmbH
*
* nDPI is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nDPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with nDPI. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ndpi_protocol_ids.h"
#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_ETHEREUM
#include "ndpi_api.h"


/* ************************************************************************** */

static u_int32_t ndpi_ether_make_lru_cache_key(struct ndpi_flow_struct *flow) {
u_int32_t key;

/* network byte order */
if(flow->is_ipv6)
key = ndpi_quick_hash(flow->c_address.v6, 16) + ndpi_quick_hash(flow->s_address.v6, 16);
else
key = flow->c_address.v4 + flow->s_address.v4;

return key;
}

/* ************************************************************************** */

static void ndpi_ether_cache_connection(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow) {
if(ndpi_struct->mining_cache)
ndpi_lru_add_to_cache(ndpi_struct->mining_cache, ndpi_ether_make_lru_cache_key(flow), NDPI_PROTOCOL_ETHEREUM, ndpi_get_current_time(flow));
}

/* ************************************************************************** */

static void ndpi_search_ethereum_udp(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow) {
struct ndpi_packet_struct *packet = &ndpi_struct->packet;
u_int16_t source = ntohs(packet->udp->source);
u_int16_t dest = ntohs(packet->udp->dest);

NDPI_LOG_DBG(ndpi_struct, "search ETHEREUM UDP\n");

/*
Ethereum P2P Discovery Protocol
https://github.com/ConsenSys/ethereum-dissectors/blob/master/packet-ethereum-disc.c
*/
if((packet->payload_packet_len > 98)
&& (packet->payload_packet_len < 1280)
&& ((source == 30303) || (dest == 30303))
&& (packet->payload[97] <= 0x04 /* NODES */)
) {
if((packet->iph) && ((ntohl(packet->iph->daddr) & 0xFF000000 /* 255.0.0.0 */) == 0xFF000000))
;
else if(packet->iphv6 && ntohl(packet->iphv6->ip6_dst.u6_addr.u6_addr32[0]) == 0xFF020000)
;
else {
ndpi_snprintf(flow->protos.mining.currency, sizeof(flow->protos.mining.currency), "%s", "ETH");
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_ETHEREUM, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
ndpi_ether_cache_connection(ndpi_struct, flow);
return;
}
}

NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
}

/* ************************************************************************** */

static u_int8_t ndpi_is_ether_port(u_int16_t dport) {
return(((dport >= 30300) && (dport <= 30305)) ? 1 : 0);
}

/* ************************************************************************** */

static void ndpi_search_ethereum_tcp(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow) {
struct ndpi_packet_struct *packet = &ndpi_struct->packet;

NDPI_LOG_DBG(ndpi_struct, "search ETHEREUM TCP\n");

/* Check connection over TCP */
if(packet->payload_packet_len > 10) {
if((packet->payload_packet_len > 300)
&& (packet->payload_packet_len < 600)
&& (packet->payload[2] == 0x04)) {
if(ndpi_is_ether_port(ntohs(packet->tcp->dest)) /* Ethereum port */) {
ndpi_snprintf(flow->protos.mining.currency, sizeof(flow->protos.mining.currency), "%s", "ETH");
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_ETHEREUM, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
ndpi_ether_cache_connection(ndpi_struct, flow);
return;
}
} else if(ndpi_strnstr((const char *)packet->payload, "{", packet->payload_packet_len)
&& (
ndpi_strnstr((const char *)packet->payload, "\"eth1.0\"", packet->payload_packet_len)
|| ndpi_strnstr((const char *)packet->payload, "\"worker\":", packet->payload_packet_len)
/* || ndpi_strnstr((const char *)packet->payload, "\"id\":", packet->payload_packet_len) - Removed as too generic */
)) {
/*
Ethereum
{"worker": "eth1.0", "jsonrpc": "2.0", "params": ["0x0fccfff9e61a230ff380530c6827caf4759337c6.rig2", "x"], "id": 2, "method": "eth_submitLogin"}
{ "id": 2, "jsonrpc":"2.0","result":true}
{"worker": "", "jsonrpc": "2.0", "params": [], "id": 3, "method": "eth_getWork"}
*/
ndpi_snprintf(flow->protos.mining.currency, sizeof(flow->protos.mining.currency), "%s", "ETH");
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_ETHEREUM, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
ndpi_ether_cache_connection(ndpi_struct, flow);
return;
}
}

NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
}

/* ************************************************************************** */

static void ndpi_search_ethereum(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow) {
struct ndpi_packet_struct *packet = &ndpi_struct->packet;

if(packet->tcp)
return ndpi_search_ethereum_tcp(ndpi_struct, flow);
return ndpi_search_ethereum_udp(ndpi_struct, flow);
}


/* ************************************************************************** */

void init_ethereum_dissector(struct ndpi_detection_module_struct *ndpi_struct,
u_int32_t *id)
{
ndpi_set_bitmask_protocol_detection("Ethereum", ndpi_struct, *id,
NDPI_PROTOCOL_ETHEREUM,
ndpi_search_ethereum,
NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION,
SAVE_DETECTION_BITMASK_AS_UNKNOWN,
ADD_TO_DETECTION_BITMASK);

*id += 1;
}

90 changes: 5 additions & 85 deletions src/lib/protocols/mining.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* mining.c [Bitcoin, Ethereum, ZCash, Monero]
* mining.c [ZCash, Monero]
*
* Copyright (C) 2018-22 - ntop.org
*
Expand Down Expand Up @@ -49,82 +49,14 @@ static void cacheMiningHostTwins(struct ndpi_detection_module_struct *ndpi_struc

/* ************************************************************************** */

static void ndpi_search_mining_udp(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow) {
struct ndpi_packet_struct *packet = &ndpi_struct->packet;
u_int16_t source = ntohs(packet->udp->source);
u_int16_t dest = ntohs(packet->udp->dest);

NDPI_LOG_DBG(ndpi_struct, "search MINING UDP\n");

// printf("==> %s()\n", __FUNCTION__);
/*
Ethereum P2P Discovery Protocol
https://github.com/ConsenSys/ethereum-dissectors/blob/master/packet-ethereum-disc.c
*/
if((packet->payload_packet_len > 98)
&& (packet->payload_packet_len < 1280)
&& ((source == 30303) || (dest == 30303))
&& (packet->payload[97] <= 0x04 /* NODES */)
) {
if((packet->iph) && ((ntohl(packet->iph->daddr) & 0xFF000000 /* 255.0.0.0 */) == 0xFF000000))
;
else if(packet->iphv6 && ntohl(packet->iphv6->ip6_dst.u6_addr.u6_addr32[0]) == 0xFF020000)
;
else {
ndpi_snprintf(flow->protos.mining.currency, sizeof(flow->protos.mining.currency), "%s", "ETH");
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MINING, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
cacheMiningHostTwins(ndpi_struct, flow);
return;
}
}

NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
}

/* ************************************************************************** */

static u_int8_t isEthPort(u_int16_t dport) {
return(((dport >= 30300) && (dport <= 30305)) ? 1 : 0);
}

/* ************************************************************************** */

static void ndpi_search_mining_tcp(struct ndpi_detection_module_struct *ndpi_struct,
static void ndpi_search_mining(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow) {
struct ndpi_packet_struct *packet = &ndpi_struct->packet;

NDPI_LOG_DBG(ndpi_struct, "search MINING TCP\n");
NDPI_LOG_DBG(ndpi_struct, "search MINING\n");

/* Check connection over TCP */
if(packet->payload_packet_len > 10) {
if((packet->payload_packet_len > 300)
&& (packet->payload_packet_len < 600)
&& (packet->payload[2] == 0x04)) {
if(isEthPort(ntohs(packet->tcp->dest)) /* Ethereum port */) {
ndpi_snprintf(flow->protos.mining.currency, sizeof(flow->protos.mining.currency), "%s", "ETH");
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MINING, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
cacheMiningHostTwins(ndpi_struct, flow);
return;
}
} else if(ndpi_strnstr((const char *)packet->payload, "{", packet->payload_packet_len)
&& (
ndpi_strnstr((const char *)packet->payload, "\"eth1.0\"", packet->payload_packet_len)
|| ndpi_strnstr((const char *)packet->payload, "\"worker\":", packet->payload_packet_len)
/* || ndpi_strnstr((const char *)packet->payload, "\"id\":", packet->payload_packet_len) - Removed as too generic */
)) {
/*
Ethereum
{"worker": "eth1.0", "jsonrpc": "2.0", "params": ["0x0fccfff9e61a230ff380530c6827caf4759337c6.rig2", "x"], "id": 2, "method": "eth_submitLogin"}
{ "id": 2, "jsonrpc":"2.0","result":true}
{"worker": "", "jsonrpc": "2.0", "params": [], "id": 3, "method": "eth_getWork"}
*/
ndpi_snprintf(flow->protos.mining.currency, sizeof(flow->protos.mining.currency), "%s", "ETH");
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MINING, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
cacheMiningHostTwins(ndpi_struct, flow);
return;
} else if(ndpi_strnstr((const char *)packet->payload, "{", packet->payload_packet_len)
if(ndpi_strnstr((const char *)packet->payload, "{", packet->payload_packet_len)
&& (ndpi_strnstr((const char *)packet->payload, "\"method\":", packet->payload_packet_len)
|| ndpi_strnstr((const char *)packet->payload, "\"blob\":", packet->payload_packet_len)
/* || ndpi_strnstr((const char *)packet->payload, "\"id\":", packet->payload_packet_len) - Removed as too generic */
Expand Down Expand Up @@ -153,18 +85,6 @@ static void ndpi_search_mining_tcp(struct ndpi_detection_module_struct *ndpi_str
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
}

/* ************************************************************************** */

static void ndpi_search_mining(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow) {
struct ndpi_packet_struct *packet = &ndpi_struct->packet;

if(packet->tcp)
return ndpi_search_mining_tcp(ndpi_struct, flow);
return ndpi_search_mining_udp(ndpi_struct, flow);
}


/* ************************************************************************** */

void init_mining_dissector(struct ndpi_detection_module_struct *ndpi_struct,
Expand All @@ -173,7 +93,7 @@ void init_mining_dissector(struct ndpi_detection_module_struct *ndpi_struct,
ndpi_set_bitmask_protocol_detection("Mining", ndpi_struct, *id,
NDPI_PROTOCOL_MINING,
ndpi_search_mining,
NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION,
NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION,
SAVE_DETECTION_BITMASK_AS_UNKNOWN,
ADD_TO_DETECTION_BITMASK);

Expand Down
Loading

0 comments on commit 6bb654e

Please sign in to comment.