botnet_comm_processor.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. #include "botnet_comm_processor.h"
  2. #include <algorithm>
  3. #include <sstream>
  4. /**
  5. * Creates a new botnet_comm_processor object.
  6. * The abstract python messages are converted to easier-to-handle C++ data structures.
  7. * @param messages_pyboost The abstract communication messages
  8. * represented as (python) list containing (python) dicts.
  9. */
  10. botnet_comm_processor::botnet_comm_processor(const py::list &messages_pyboost){
  11. set_messages(messages_pyboost);
  12. }
  13. /**
  14. * Creates a new and empty botnet_comm_processor object.
  15. */
  16. botnet_comm_processor::botnet_comm_processor(){
  17. }
  18. /**
  19. * Set the messages of this communication processor.
  20. * @param messages_pyboost The abstract communication messages
  21. * represented as (python) list containing (python) dicts.
  22. */
  23. void botnet_comm_processor::set_messages(const py::list &messages_pyboost){
  24. messages.clear();
  25. for (size_t i = 0; i < len(messages_pyboost); i++){
  26. py::dict msg_pyboost = py::cast<py::dict>(messages_pyboost[i]);
  27. unsigned int src_id = std::stoi(py::cast<std::string>(msg_pyboost["Src"]));
  28. unsigned int dst_id = std::stoi(py::cast<std::string>(msg_pyboost["Dst"]));
  29. unsigned short type = (unsigned short) std::stoi(py::cast<std::string>(msg_pyboost["Type"]));
  30. double time = std::stod(py::cast<std::string>(msg_pyboost["Time"]));
  31. int line_no = std::stoi(msg_pyboost.contains("LineNumber") ? py::cast<std::string>(msg_pyboost["LineNumber"]) : "-1");
  32. abstract_msg msg = {src_id, dst_id, type, time, line_no};
  33. messages.push_back(std::move(msg));
  34. }
  35. }
  36. /**
  37. * Retrieve input information about message count.
  38. * @return the number of existing messages.
  39. */
  40. int botnet_comm_processor::get_message_count(){
  41. return messages.size();
  42. }
  43. /**
  44. * Processes an XML attribute assignment. The result is reflected in the respective change of the given message.
  45. * @param msg The message this attribute refers to.
  46. * @param assignment The XML attribute assignment in notation: attribute="value"
  47. */
  48. void botnet_comm_processor::process_xml_attrib_assign(abstract_msg &msg, const std::string &assignment) {
  49. std::size_t split_pos = assignment.find("=");
  50. if (split_pos != std::string::npos){
  51. std::string key = assignment.substr(0, split_pos);
  52. std::string value = assignment.substr(split_pos + 2, assignment.length() - 1);
  53. process_kv(msg, key, value);
  54. }
  55. }
  56. /**
  57. * Processes a key-value pair. The result is reflected in the respective change of the given message.
  58. * @param msg The message this kv pair refers to.
  59. * @param key The key of the attribute.
  60. * @param value The value of the attribute.
  61. */
  62. void botnet_comm_processor::process_kv(abstract_msg &msg, const std::string &key, const std::string &value){
  63. if (key == "Src")
  64. msg.src = std::stoi(value);
  65. else if (key == "Dst")
  66. msg.dst = std::stoi(value);
  67. else if (key == "Type")
  68. msg.type = (unsigned short) std::stoi(value);
  69. else if (key == "Time")
  70. msg.time = std::stod(value);
  71. else if (key == "LineNumber")
  72. msg.line_no = std::stoi(value);
  73. }
  74. /**
  75. * Parses the packets contained in the given CSV to program structure.
  76. * @param filepath The filepath where the CSV is located.
  77. * @return The number of messages (or lines) contained in the CSV file.
  78. */
  79. unsigned int botnet_comm_processor::parse_csv(const std::string &filepath){
  80. std::ifstream input(filepath);
  81. int line_no = 1; // the first line has number 1
  82. messages.clear();
  83. // iterate over every line
  84. for (std::string line; std::getline(input, line); ){
  85. std::istringstream line_stream(line);
  86. abstract_msg cur_msg;
  87. cur_msg.line_no = line_no;
  88. // iterate over every key:value entry
  89. for (std::string pair; std::getline(line_stream, pair, ','); ){
  90. pair.erase(std::remove(pair.begin(), pair.end(), ' '), pair.end());
  91. std::size_t split_pos = pair.find(":");
  92. if (split_pos != std::string::npos){
  93. std::string key = pair.substr(0, split_pos);
  94. std::string value = pair.substr(split_pos + 1, pair.length());
  95. process_kv(cur_msg, key, value);
  96. }
  97. }
  98. messages.push_back(std::move(cur_msg));
  99. line_no++;
  100. }
  101. return messages.size();
  102. }
  103. /**
  104. * Parses the packets contained in the given XML to program structure.
  105. * @param filepath The filepath where the XML is located.
  106. * @return The number of messages contained in the XML file.
  107. */
  108. unsigned int botnet_comm_processor::parse_xml(const std::string &filepath){
  109. std::ifstream input(filepath);
  110. std::string cur_word = "";
  111. abstract_msg cur_msg;
  112. char c;
  113. int read_packet_open = 0, read_slash = 0;
  114. messages.clear();
  115. // iterate over every character
  116. while (input.get(c)){
  117. if(c == '/') // hints ending of tag
  118. read_slash = 1;
  119. else if (c == '>'){ // definitely closes tag
  120. if (read_packet_open && read_slash){ // handle oustanding attribute
  121. read_slash = 0;
  122. process_xml_attrib_assign(cur_msg, cur_word);
  123. messages.push_back(cur_msg);
  124. read_packet_open = 0;
  125. }
  126. cur_word = "";
  127. }
  128. else if (c == ' '){
  129. if (read_packet_open && cur_word != ""){ // handle new attribute
  130. process_xml_attrib_assign(cur_msg, cur_word);
  131. }
  132. else if (cur_word == "<packet")
  133. read_packet_open = 1;
  134. cur_word = "";
  135. }
  136. else
  137. cur_word += c;
  138. }
  139. return messages.size();
  140. }
  141. /**
  142. * Writes the communication messages contained in the class member messages into an XML file (with respective notation).
  143. * @param out_dir The directory the file is to be put in.
  144. * @param basename The actual name of the file without directories or extension.
  145. * @return The filepath of the written XML file.
  146. */
  147. std::string botnet_comm_processor::write_xml(const std::string &out_dir, const std::string &basename){
  148. std::string filepath;
  149. if (out_dir[out_dir.length() - 1] == '/')
  150. filepath = out_dir + basename + ".xml";
  151. else
  152. filepath = out_dir + "/" + basename + ".xml";
  153. std::ofstream xml_file;
  154. xml_file.open(filepath);
  155. // set number of digits after dot to 11
  156. xml_file << std::fixed << std::setprecision(11);
  157. xml_file << "<trace name=\"" << basename << "\">";
  158. for (const auto &msg : messages){
  159. xml_file << "<packet ";
  160. xml_file << "Src=\"" << msg.src << "\" Dst=\"" << msg.dst << "\" ";
  161. xml_file << "Type=\"" << msg.type << "\" Time=\"" << msg.time << "\" ";
  162. xml_file << "LineNumber=\"" << msg.line_no << "\" />";
  163. }
  164. xml_file << "</trace>";
  165. xml_file.close();
  166. return filepath;
  167. }
  168. /**
  169. * Retrieves all messages contained in the interval between start_idx and end_idx in Python representation.
  170. * @param start_idx The inclusive first index of the interval.
  171. * @param end_idx The inclusive last index of the interval.
  172. * @return A (Python) list of (Python) dicts containing the desired information.
  173. */
  174. py::list botnet_comm_processor::get_messages(unsigned int start_idx, unsigned int end_idx){
  175. py::list py_messages;
  176. for (std::size_t i = start_idx; i <= end_idx; i++){
  177. if (i >= messages.size())
  178. break;
  179. py::dict py_msg;
  180. py_msg["Src"] = messages[i].src;
  181. py_msg["Dst"] = messages[i].dst;
  182. py_msg["Type"] = messages[i].type;
  183. py_msg["Time"] = messages[i].time;
  184. py_msg["LineNumber"] = messages[i].line_no;
  185. py_messages.append(py_msg);
  186. }
  187. return py_messages;
  188. }
  189. /**
  190. * Finds the time interval(s) of maximum the given seconds with the most overall communication
  191. * (i.e. requests and responses) that has at least number_ids communicating initiators in it.
  192. * @param number_ids The number of initiator IDs that have to exist in the interval(s).
  193. * @param max_int_time The maximum time period of the interval.
  194. * @return A (python) list of (python) dicts, where each dict (keys: 'IDs', Start', 'End') represents an interval with its
  195. * list of initiator IDs, a start index and an end index. The indices are with respect to the first abstract message.
  196. */
  197. py::list botnet_comm_processor::find_optimal_interval(int number_ids, double max_int_time){
  198. unsigned int logical_thread_count = std::thread::hardware_concurrency();
  199. std::vector<std::thread> threads;
  200. std::vector<std::future<std::vector<comm_interval> > > futures;
  201. // create as many threads as can run concurrently and assign them respective sections
  202. for (std::size_t i = 0; i < logical_thread_count; i++){
  203. unsigned int start_idx = (i * messages.size() / logical_thread_count);
  204. unsigned int end_idx = (i + 1) * messages.size() / logical_thread_count;
  205. std::promise<std::vector<comm_interval> > p; // use promises to retrieve return values
  206. futures.push_back(p.get_future());
  207. threads.push_back(std::thread(&botnet_comm_processor::find_optimal_interval_helper, this, std::move(p), number_ids, max_int_time, start_idx, end_idx));
  208. }
  209. // synchronize all threads
  210. for (auto &t : threads){
  211. t.join();
  212. }
  213. // accumulate results
  214. std::vector<std::vector<comm_interval> > acc_possible_intervals;
  215. for (auto &f : futures){
  216. acc_possible_intervals.push_back(f.get());
  217. }
  218. // find overall most communicative interval
  219. std::vector<comm_interval> possible_intervals;
  220. unsigned int cur_highest_sum = 0;
  221. for (const auto &single_poss_interval : acc_possible_intervals){
  222. if (single_poss_interval.size() > 0 && single_poss_interval[0].comm_sum >= cur_highest_sum){
  223. // if there is more than one interval, all of them have the same comm_sum
  224. if (single_poss_interval[0].comm_sum > cur_highest_sum){
  225. cur_highest_sum = single_poss_interval[0].comm_sum;
  226. possible_intervals.clear();
  227. }
  228. for (const auto &interval : single_poss_interval){
  229. possible_intervals.push_back(std::move(interval));
  230. }
  231. }
  232. }
  233. // return the result converted into python data structures
  234. return convert_intervals_to_py_repr(possible_intervals);
  235. }
  236. /**
  237. * Finds the time interval(s) of maximum the given seconds within the given start and end index having the most
  238. * overall communication (i.e. requests and responses) as well as at least number_ids communicating initiators in it.
  239. * @param p An rvalue to a promise to return the found intervals.
  240. * @param number_ids The number of initiator IDs that have to exist in the interval(s).
  241. * @param max_int_time The maximum time period of the interval.
  242. * @param start_idx The index of the first message to process with respect to the class member 'messages'.
  243. * @param end_idx The upper index boundary where the search is stopped at (i.e. exclusive index).
  244. */
  245. void botnet_comm_processor::find_optimal_interval_helper(std::promise<std::vector<comm_interval> > && p, int number_ids, double max_int_time, int start_idx, int end_idx){
  246. // setup initial variables
  247. unsigned int idx_low = start_idx, idx_high = start_idx; // the indices spanning the interval
  248. unsigned int comm_sum = 0; // the communication sum of the current interval
  249. unsigned int cur_highest_sum = 0; // the highest communication sum seen so far
  250. double cur_int_time = 0; // the time of the current interval
  251. std::deque<unsigned int> init_ids; // the initiator IDs seen in the current interval in order of appearance
  252. std::vector<comm_interval> possible_intervals; // all intervals that have cur_highest_sum of communication and contain enough IDs
  253. // Iterate over all messages from start to finish and process the info of each message.
  254. // Similar to a Sliding Window approach.
  255. while (1){
  256. if (idx_high < messages.size())
  257. cur_int_time = messages[idx_high].time - messages[idx_low].time;
  258. // if current interval time exceeds maximum time period or all messages have been processed,
  259. // process information of the current interval
  260. if (greater_than(cur_int_time, max_int_time) || idx_high >= messages.size()){
  261. std::set<unsigned int> interval_ids;
  262. for (std::size_t i = 0; i < init_ids.size(); i++)
  263. interval_ids.insert(init_ids[i]);
  264. // if the interval contains enough initiator IDs, add it to possible_intervals
  265. if (interval_ids.size() >= (unsigned int) number_ids){
  266. comm_interval interval = {interval_ids, comm_sum, idx_low, idx_high - 1};
  267. // reset possible intervals if new maximum of communication is found
  268. if (comm_sum > cur_highest_sum){
  269. possible_intervals.clear();
  270. possible_intervals.push_back(std::move(interval));
  271. cur_highest_sum = comm_sum;
  272. }
  273. // append otherwise
  274. else if (comm_sum == cur_highest_sum)
  275. possible_intervals.push_back(std::move(interval));
  276. }
  277. // stop if all messages have been processed
  278. if (idx_high >= messages.size())
  279. break;
  280. }
  281. // let idx_low "catch up" so that the current interval time fits into the maximum time period again
  282. while (greater_than(cur_int_time, max_int_time)){
  283. if (idx_low >= (unsigned int) end_idx)
  284. goto end;
  285. abstract_msg &cur_msg = messages[idx_low];
  286. // if message was not a timeout, delete the first appearance of the initiator ID
  287. // of this message from the initiator list and update comm_sum
  288. if (cur_msg.type != TIMEOUT){
  289. comm_sum--;
  290. init_ids.pop_front();
  291. }
  292. idx_low++;
  293. cur_int_time = messages[idx_high].time - messages[idx_low].time;
  294. }
  295. // consume the new message at idx_high and process its information
  296. abstract_msg &cur_msg = messages[idx_high];
  297. // if message is request, add src to initiator list
  298. if (msgtype_is_request(cur_msg.type)){
  299. init_ids.push_back(cur_msg.src);
  300. comm_sum++;
  301. }
  302. // if message is response, add dst to initiator list
  303. else if (msgtype_is_response(cur_msg.type)){
  304. init_ids.push_back(cur_msg.dst);
  305. comm_sum++;
  306. }
  307. idx_high++;
  308. }
  309. end: p.set_value(possible_intervals);
  310. }
  311. /**
  312. * Finds the time interval of maximum the given seconds starting at the given index. If it does not have at least number_ids
  313. * communicating initiators in it or the index is out of bounds, an empty dict is returned.
  314. * @param start_idx the starting index of the returned interval
  315. * @param number_ids The number of initiator IDs that have to exist in the interval.
  316. * @param max_int_time The maximum time period of the interval.
  317. * @return A (python) dict (keys: 'IDs', Start', 'End'), which represents an interval with its list of initiator IDs,
  318. * a start index and an end index. The indices are with respect to the first abstract message.
  319. */
  320. py::dict botnet_comm_processor::find_interval_from_startidx(int start_idx, int number_ids, double max_int_time){
  321. // setup initial variables
  322. unsigned int cur_idx = start_idx; // the current iteration index
  323. double cur_int_time = 0; // the time of the current interval
  324. std::deque<unsigned int> init_ids; // the initiator IDs seen in the current interval in order of appearance
  325. py::dict comm_interval_py; // the communication interval that is returned
  326. if ((unsigned int) start_idx >= messages.size()){
  327. return comm_interval_py;
  328. }
  329. // Iterate over all messages starting at start_idx until the duration or the current index exceeds a boundary
  330. while (1){
  331. if (cur_idx < messages.size())
  332. cur_int_time = messages[cur_idx].time - messages[start_idx].time;
  333. // if current interval time exceeds maximum time period or all messages have been processed,
  334. // process information of the current interval
  335. if (greater_than(cur_int_time, max_int_time) || cur_idx >= messages.size()){
  336. std::set<unsigned int> interval_ids;
  337. for (std::size_t i = 0; i < init_ids.size(); i++)
  338. interval_ids.insert(init_ids[i]);
  339. // if the interval contains enough initiator IDs, convert it to python representation and return it
  340. if (interval_ids.size() >= (unsigned int) number_ids){
  341. py::list py_ids;
  342. for (const auto &id : interval_ids){
  343. py_ids.append(id);
  344. }
  345. comm_interval_py["IDs"] = py_ids;
  346. comm_interval_py["Start"] = start_idx;
  347. comm_interval_py["End"] = cur_idx - 1;
  348. return comm_interval_py;
  349. }
  350. else {
  351. return comm_interval_py;
  352. }
  353. }
  354. // consume the new message at cur_idx and process its information
  355. abstract_msg &cur_msg = messages[cur_idx];
  356. // if message is request, add src to initiator list
  357. if (msgtype_is_request(cur_msg.type))
  358. init_ids.push_back(cur_msg.src);
  359. // if message is response, add dst to initiator list
  360. else if (msgtype_is_response(cur_msg.type))
  361. init_ids.push_back(cur_msg.dst);
  362. cur_idx++;
  363. }
  364. }
  365. /**
  366. * Finds the time interval of maximum the given seconds ending at the given index. If it does not have at least number_ids
  367. * communicating initiators in it or the index is out of bounds, an empty dict is returned.
  368. * @param end_idx the ending index of the returned interval (inclusive)
  369. * @param number_ids The number of initiator IDs that have to exist in the interval.
  370. * @param max_int_time The maximum time period of the interval.
  371. * @return A (python) dict (keys: 'IDs', Start', 'End'), which represents an interval with its list of initiator IDs,
  372. * a start index and an end index. The indices are with respect to the first abstract message.
  373. */
  374. py::dict botnet_comm_processor::find_interval_from_endidx(int end_idx, int number_ids, double max_int_time){
  375. // setup initial variables
  376. int cur_idx = end_idx; // the current iteration index
  377. double cur_int_time = 0; // the time of the current interval
  378. std::deque<unsigned int> init_ids; // the initiator IDs seen in the current interval in order of appearance
  379. py::dict comm_interval_py; // the communication interval that is returned
  380. if (end_idx < 0){
  381. return comm_interval_py;
  382. }
  383. // Iterate over all messages starting at end_idx until the duration or the current index exceeds a boundary
  384. while (1){
  385. if (cur_idx >= 0)
  386. cur_int_time = messages[end_idx].time - messages[cur_idx].time;
  387. // if current interval time exceeds maximum time period or all messages have been processed,
  388. // process information of the current interval
  389. if (greater_than(cur_int_time, max_int_time) || cur_idx < 0){
  390. std::set<unsigned int> interval_ids;
  391. for (std::size_t i = 0; i < init_ids.size(); i++)
  392. interval_ids.insert(init_ids[i]);
  393. // if the interval contains enough initiator IDs, convert it to python representation and return it
  394. if (interval_ids.size() >= (unsigned int) number_ids){
  395. py::list py_ids;
  396. for (const auto &id : interval_ids){
  397. py_ids.append(id);
  398. }
  399. comm_interval_py["IDs"] = py_ids;
  400. comm_interval_py["Start"] = cur_idx + 1;
  401. comm_interval_py["End"] = end_idx;
  402. return comm_interval_py;
  403. }
  404. else {
  405. return comm_interval_py;
  406. }
  407. }
  408. // consume the new message at cur_idx and process its information
  409. abstract_msg &cur_msg = messages[cur_idx];
  410. // if message is request, add src to initiator list
  411. if (msgtype_is_request(cur_msg.type))
  412. init_ids.push_back(cur_msg.src);
  413. // if message is response, add dst to initiator list
  414. else if (msgtype_is_response(cur_msg.type))
  415. init_ids.push_back(cur_msg.dst);
  416. cur_idx--;
  417. }
  418. }
  419. /**
  420. * Finds all initiator IDs contained in the interval spanned by the two indices.
  421. * @param start_idx The start index of the interval.
  422. * @param end_idx The last index of the interval (inclusive).
  423. * @return A (python) list containing all initiator IDs of the interval.
  424. */
  425. py::list botnet_comm_processor::get_interval_init_ids(int start_idx, int end_idx){
  426. // setup initial variables
  427. unsigned int cur_idx = start_idx; // the current iteration index
  428. std::set<unsigned int> interval_ids;
  429. py::list py_ids; // the communication interval that is returned
  430. if ((unsigned int) start_idx >= messages.size()){
  431. return py_ids;
  432. }
  433. // Iterate over all messages starting at start_idx until the duration or the current index exceeds a boundary
  434. while (1){
  435. // if messages have been processed
  436. if (cur_idx >= messages.size() || cur_idx > (unsigned int) end_idx){
  437. for (const auto &id : interval_ids)
  438. py_ids.append(id);
  439. return py_ids;
  440. }
  441. // consume the new message at cur_idx and process its information
  442. abstract_msg &cur_msg = messages[cur_idx];
  443. // if message is request, add src to initiator list
  444. if (msgtype_is_request(cur_msg.type))
  445. interval_ids.insert(cur_msg.src);
  446. // if message is response, add dst to initiator list
  447. else if (msgtype_is_response(cur_msg.type))
  448. interval_ids.insert(cur_msg.dst);
  449. cur_idx++;
  450. }
  451. }
  452. /**
  453. * Checks whether the given message type corresponds to a request.
  454. * @param mtype The message type to check.
  455. * @return true(1) if the message type is a request, false(0) otherwise.
  456. */
  457. int botnet_comm_processor::msgtype_is_request(unsigned short mtype){
  458. return mtype == SALITY_HELLO || mtype == SALITY_NL_REQUEST;
  459. }
  460. /**
  461. * Checks whether the given message type corresponds to a response.
  462. * @param mtype The message type to check.
  463. * @return true(1) if the message type is a response, false(0) otherwise.
  464. */
  465. int botnet_comm_processor::msgtype_is_response(unsigned short mtype){
  466. return mtype == SALITY_HELLO_REPLY || mtype == SALITY_NL_REPLY;
  467. }
  468. /**
  469. * Converts the given vector of communication intervals to a python representation
  470. * using (python) lists and (python) tuples.
  471. * @param intervals The communication intervals to convert.
  472. * @return A boost::python::list containing the same interval information using boost::python::dict for each interval.
  473. */
  474. py::list botnet_comm_processor::convert_intervals_to_py_repr(const std::vector<comm_interval> &intervals){
  475. py::list py_intervals;
  476. for (const auto &interval : intervals){
  477. py::list py_ids;
  478. for (const auto &id : interval.ids){
  479. py_ids.append(id);
  480. }
  481. py::dict py_interval;
  482. py_interval["IDs"] = py_ids;
  483. py_interval["Start"] = interval.start_idx;
  484. py_interval["End"] = interval.end_idx;
  485. py_intervals.append(py_interval);
  486. }
  487. return py_intervals;
  488. }
  489. // void botnet_comm_processor::print_message(const abstract_msg &message){
  490. // std::cout << "Src: " << message.src << " Dst: " << message.dst << " Type: " << message.type << " Time: " << message.time << " LineNumber: " << message.line_no << std::endl;
  491. // }
  492. PYBIND11_MODULE (libbotnetcomm, m) {
  493. py::class_<botnet_comm_processor>(m, "botnet_comm_processor")
  494. .def(py::init<py::list>())
  495. .def(py::init<>())
  496. .def("find_interval_from_startidx", &botnet_comm_processor::find_interval_from_startidx)
  497. .def("find_interval_from_endidx", &botnet_comm_processor::find_interval_from_endidx)
  498. .def("find_optimal_interval", &botnet_comm_processor::find_optimal_interval)
  499. .def("get_interval_init_ids", &botnet_comm_processor::get_interval_init_ids)
  500. .def("get_messages", &botnet_comm_processor::get_messages)
  501. .def("get_message_count", &botnet_comm_processor::get_message_count)
  502. .def("parse_csv", &botnet_comm_processor::parse_csv)
  503. .def("parse_xml", &botnet_comm_processor::parse_xml)
  504. .def("set_messages", &botnet_comm_processor::set_messages)
  505. .def("write_xml", &botnet_comm_processor::write_xml);
  506. }