diff --git a/.gitignore b/.gitignore index 73fab07..1f5aba1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ debug/ target/ +demo.tape + # These are backup files generated by rustfmt **/*.rs.bk diff --git a/demo.gif b/demo.gif new file mode 100644 index 0000000..9561d55 Binary files /dev/null and b/demo.gif differ diff --git a/src/action.rs b/src/action.rs index 4a13504..d661ac7 100644 --- a/src/action.rs +++ b/src/action.rs @@ -45,6 +45,8 @@ pub enum Action { CountIp, CidrError, PacketDump(DateTime, PacketsInfoTypesEnum, PacketTypeEnum), + PortScan(usize, u16), + PortScanDone(usize), } impl<'de> Deserialize<'de> for Action { diff --git a/src/app.rs b/src/app.rs index c741238..e0caa6a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -40,6 +40,7 @@ impl App { let tabs = Tabs::default(); let discovery = Discovery::default(); let packetdump = PacketDump::default(); + let ports = Ports::default(); let config = Config::new()?; let mode = Mode::Normal; @@ -57,6 +58,7 @@ impl App { Box::new(tabs), Box::new(discovery), Box::new(packetdump), + Box::new(ports), ], should_quit: false, should_suspend: false, diff --git a/src/components/discovery.rs b/src/components/discovery.rs index 7711080..5bca3a3 100644 --- a/src/components/discovery.rs +++ b/src/components/discovery.rs @@ -47,10 +47,10 @@ const SPINNER_SYMBOLS: [&str; 6] = ["⠷", "⠯", "⠟", "⠻", "⠽", "⠾"]; #[derive(Clone)] pub struct ScannedIp { - ip: String, - mac: String, - hostname: String, - vendor: String, + pub ip: String, + pub mac: String, + pub hostname: String, + pub vendor: String, } pub struct Discovery { @@ -230,14 +230,12 @@ impl Discovery { if let Some(oui) = &self.oui { let oui_res = oui.lookup_by_mac(&n.mac); - match oui_res { - Ok(e) => { - if let Some(oui_res) = e { + // match oui_res { + if let Ok(oui_res) = oui_res { + if let Some(oui_res) = oui_res { let cn = oui_res.company_name.clone(); n.vendor = cn; } - } - Err(_) => {} } } } @@ -266,7 +264,7 @@ impl Discovery { self.scanned_ips.sort_by(|a, b| { let a_ip: Ipv4Addr = a.ip.parse::().unwrap(); let b_ip: Ipv4Addr = b.ip.parse::().unwrap(); - return a_ip.partial_cmp(&b_ip).unwrap(); + a_ip.partial_cmp(&b_ip).unwrap() }); } @@ -286,7 +284,7 @@ impl Discovery { fn set_scrollbar_height(&mut self) { let mut ip_len = 0; - if self.scanned_ips.len() > 0 { + if !self.scanned_ips.is_empty() { ip_len = self.scanned_ips.len() - 1; } self.scrollbar_state = self.scrollbar_state.content_length(ip_len); @@ -296,10 +294,10 @@ impl Discovery { let index = match self.table_state.selected() { Some(index) => { if index == 0 { - if self.scanned_ips.len() > 0 { - self.scanned_ips.len() - 1 - } else { + if self.scanned_ips.is_empty() { 0 + } else { + self.scanned_ips.len() - 1 } } else { index - 1 @@ -315,7 +313,7 @@ impl Discovery { let index = match self.table_state.selected() { Some(index) => { let mut s_ip_len = 0; - if self.scanned_ips.len() > 0 { + if !self.scanned_ips.is_empty() { s_ip_len = self.scanned_ips.len() - 1; } if index >= s_ip_len { diff --git a/src/components/packetdump.rs b/src/components/packetdump.rs index 1ee75cb..187ba43 100644 --- a/src/components/packetdump.rs +++ b/src/components/packetdump.rs @@ -39,6 +39,7 @@ use tokio::{ use super::{Component, Frame}; use crate::{ action::Action, + config::DEFAULT_BORDER_STYLE, config::{Config, KeyBindings}, enums::{ ARPPacketInfo, ICMP6PacketInfo, ICMPPacketInfo, PacketTypeEnum, PacketsInfoTypesEnum, @@ -46,7 +47,6 @@ use crate::{ }, layout::get_vertical_layout, utils::MaxSizeVec, - config::DEFAULT_BORDER_STYLE, }; use strum::{EnumCount, IntoEnumIterator}; @@ -493,10 +493,14 @@ impl PacketDump { let index = match self.table_state.selected() { Some(index) => { let logs = self.get_array_by_packet_type(self.packet_type); - if index >= logs.len() - 1 { + if logs.is_empty() { 0 } else { - index + 1 + if index >= logs.len() - 1 { + 0 + } else { + index + 1 + } } } None => 0, @@ -847,7 +851,7 @@ impl PacketDump { ) .border_style(Style::default().fg(Color::Rgb(100, 100, 100))) .borders(Borders::ALL) // .padding(Padding::new(1, 0, 2, 0)), - .border_type(DEFAULT_BORDER_STYLE) + .border_type(DEFAULT_BORDER_STYLE), ) .highlight_symbol(Span::styled( String::from(char::from_u32(0x25b6).unwrap_or('>')), @@ -909,15 +913,15 @@ impl Component for PacketDump { self.table_state.select(Some(0)); self.set_scrollbar_height(); } - } - // -- dumping toggle - if let Action::DumpToggle = action { - if self.dump_paused.load(Ordering::Relaxed) { - self.dump_paused.store(false, Ordering::Relaxed); - self.start_loop(); - } else { - self.dump_paused.store(true, Ordering::Relaxed); - self.loop_thread = None; + // -- dumping toggle + if let Action::DumpToggle = action { + if self.dump_paused.load(Ordering::Relaxed) { + self.dump_paused.store(false, Ordering::Relaxed); + self.start_loop(); + } else { + self.dump_paused.store(true, Ordering::Relaxed); + self.loop_thread = None; + } } } // -- packet recieved diff --git a/src/components/ports.rs b/src/components/ports.rs index 415816c..c0f18de 100644 --- a/src/components/ports.rs +++ b/src/components/ports.rs @@ -2,7 +2,8 @@ use cidr::Ipv4Cidr; use color_eyre::eyre::Result; use color_eyre::owo_colors::OwoColorize; use dns_lookup::{lookup_addr, lookup_host}; -use futures::future::join_all; +use futures::StreamExt; +use futures::{future::join_all, stream}; use pnet::datalink::{Channel, NetworkInterface}; use pnet::packet::{ @@ -10,41 +11,48 @@ use pnet::packet::{ ethernet::{EtherTypes, MutableEthernetPacket}, MutablePacket, Packet, }; +use ratatui::style::Stylize; use core::str; use ratatui::{prelude::*, widgets::*}; -use std::net::{IpAddr, Ipv4Addr}; -use std::string; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::{string, time::Duration}; use tokio::{ + net::TcpStream, sync::mpsc::{self, UnboundedSender}, task::{self, JoinHandle}, }; use super::Component; +use crate::enums::COMMON_PORTS; use crate::{ action::Action, components::discovery::ScannedIp, - enums::TabsEnum, + config::DEFAULT_BORDER_STYLE, + enums::{PortsScanState, TabsEnum}, + layout::get_vertical_layout, mode::Mode, tui::Frame, - layout::get_vertical_layout, }; use crossterm::event::{KeyCode, KeyEvent}; use rand::random; use tui_input::backend::crossterm::EventHandler; use tui_input::Input; -static POOL_SIZE: usize = 32; +static POOL_SIZE: usize = 64; const SPINNER_SYMBOLS: [&str; 6] = ["⠷", "⠯", "⠟", "⠻", "⠽", "⠾"]; +struct ScannedIpPorts { + ip: String, + state: PortsScanState, + ports: Vec, +} + pub struct Ports { active_tab: TabsEnum, - active_interface: Option, action_tx: Option>, - scanned_ips: Vec, - cidr: Option, - task: JoinHandle<()>, - table_state: TableState, + ip_ports: Vec, + list_state: ListState, scrollbar_state: ScrollbarState, spinner_index: usize, } @@ -59,151 +67,45 @@ impl Ports { pub fn new() -> Self { Self { active_tab: TabsEnum::Discovery, - active_interface: None, - task: tokio::spawn(async {}), action_tx: None, - scanned_ips: Vec::new(), - cidr: None, - table_state: TableState::default().with_selected(0), + ip_ports: Vec::new(), + list_state: ListState::default().with_selected(Some(0)), scrollbar_state: ScrollbarState::new(0), spinner_index: 0, } } - fn set_scrollbar_height(&mut self) { - let mut ip_len = 0; - if self.scanned_ips.len() > 0 { - ip_len = self.scanned_ips.len() - 1; + fn process_ip(&mut self, ip: &str) { + let ipv4: Ipv4Addr = ip.parse().unwrap(); + + if let Some(n) = self.ip_ports.iter_mut().find(|item| item.ip == ip) { + n.ip = ip.to_string(); + } else { + self.ip_ports.push(ScannedIpPorts { + ip: ip.to_string(), + state: PortsScanState::Waiting, + ports: Vec::new(), + }); + + self.ip_ports.sort_by(|a, b| { + let a_ip: Ipv4Addr = a.ip.parse::().unwrap(); + let b_ip: Ipv4Addr = b.ip.parse::().unwrap(); + a_ip.partial_cmp(&b_ip).unwrap() + }); } - self.scrollbar_state = self.scrollbar_state.content_length(ip_len); - } - fn previous_in_table(&mut self) { - // let index = match self.table_state.selected() { - // Some(index) => { - // if index == 0 { - // if self.scanned_ips.len() > 0 { - // self.scanned_ips.len() - 1 - // } else { - // 0 - // } - // } else { - // index - 1 - // } - // } - // None => 0, - // }; - // self.table_state.select(Some(index)); - // self.scrollbar_state = self.scrollbar_state.position(index); + self.set_scrollbar_height(); } - fn next_in_table(&mut self) { - // let index = match self.table_state.selected() { - // Some(index) => { - // let mut s_ip_len = 0; - // if self.scanned_ips.len() > 0 { - // s_ip_len = self.scanned_ips.len() - 1; - // } - // if index >= s_ip_len { - // 0 - // } else { - // index + 1 - // } - // } - // None => 0, - // }; - // self.table_state.select(Some(index)); - // self.scrollbar_state = self.scrollbar_state.position(index); + fn set_scrollbar_height(&mut self) { + let mut ip_len = 0; + if !self.ip_ports.is_empty() { + ip_len = self.ip_ports.len() - 1; + } + self.scrollbar_state = self.scrollbar_state.content_length(ip_len); } - // fn make_table( - // scanned_ips: Vec, - // cidr: Option, - // ip_num: i32, - // ) -> Table<'static> { - // let header = Row::new(vec!["ip", "mac", "hostname", "vendor"]) - // .style(Style::default().fg(Color::Yellow)) - // .top_margin(1) - // .bottom_margin(1); - // let mut rows = Vec::new(); - // let cidr_length = match cidr { - // Some(c) => count_ipv4_net_length(c.network_length() as u32), - // None => 0, - // }; - - // for sip in scanned_ips { - // let ip = &sip.ip; - // rows.push(Row::new(vec![ - // Cell::from(Span::styled( - // format!("{ip:<2}"), - // Style::default().fg(Color::Blue), - // )), - // Cell::from(sip.mac.clone().green()), - // Cell::from(sip.hostname.clone()), - // Cell::from(sip.vendor.clone().yellow()), - // ])); - // } - - // let table = Table::new( - // rows, - // [ - // Constraint::Length(16), - // Constraint::Length(19), - // Constraint::Fill(1), - // Constraint::Fill(1), - // ], - // ) - // .header(header) - // .block( - // Block::new() - // .title( - // ratatui::widgets::block::Title::from("|Ports|".yellow()) - // .position(ratatui::widgets::block::Position::Top) - // .alignment(Alignment::Right), - // ) - // .title( - // ratatui::widgets::block::Title::from(Line::from(vec![ - // Span::styled("|", Style::default().fg(Color::Yellow)), - // Span::styled(format!("{}", ip_num), Style::default().fg(Color::Green)), - // Span::styled( - // format!("/{}", cidr_length), - // Style::default().fg(Color::Green), - // ), - // Span::styled(" ip|", Style::default().fg(Color::Yellow)), - // ])) - // .position(ratatui::widgets::block::Position::Top) - // .alignment(Alignment::Left), - // ) - // .title( - // ratatui::widgets::block::Title::from(Line::from(vec![ - // Span::styled("|", Style::default().fg(Color::Yellow)), - // String::from(char::from_u32(0x25b2).unwrap_or('>')).red(), - // String::from(char::from_u32(0x25bc).unwrap_or('>')).red(), - // Span::styled("select|", Style::default().fg(Color::Yellow)), - // ])) - // .position(ratatui::widgets::block::Position::Bottom) - // .alignment(Alignment::Left), - // ) - // .title( - // ratatui::widgets::block::Title::from(Line::from(vec![ - // Span::styled("|show ", Style::default().fg(Color::Yellow)), - // Span::styled("p", Style::default().fg(Color::Red)), - // Span::styled("ackets|", Style::default().fg(Color::Yellow)), - // ])) - // .position(ratatui::widgets::block::Position::Bottom) - // .alignment(Alignment::Right), - // ) - // .border_style(Style::default().fg(Color::Rgb(100, 100, 100))) - // .borders(Borders::ALL), // .padding(Padding::new(1, 0, 2, 0)), - // ) - // .highlight_symbol(String::from(char::from_u32(0x25b6).unwrap_or('>')).red()) - // .column_spacing(1); - // table - // } - pub fn make_scrollbar<'a>() -> Scrollbar<'a> { - // let s_start = String::from(char::from_u32(0x25b2).unwrap_or('#')); - // let s_end = String::from(char::from_u32(0x25bc).unwrap_or('#')); let scrollbar = Scrollbar::default() .orientation(ScrollbarOrientation::VerticalRight) .style(Style::default().fg(Color::Rgb(100, 100, 100))) @@ -212,12 +114,154 @@ impl Ports { scrollbar } - fn make_spinner(&self) -> Span { - let spinner = SPINNER_SYMBOLS[self.spinner_index]; - Span::styled( - format!("{spinner}scanning.."), - Style::default().fg(Color::Yellow), - ) + fn previous_in_list(&mut self) { + let index = match self.list_state.selected() { + Some(index) => { + if index == 0 { + if self.ip_ports.is_empty() { + 0 + } else { + self.ip_ports.len() - 1 + } + } else { + index - 1 + } + } + None => 0, + }; + self.list_state.select(Some(index)); + self.scrollbar_state = self.scrollbar_state.position(index); + } + + fn next_in_list(&mut self) { + let index = match self.list_state.selected() { + Some(index) => { + let mut s_ip_len = 0; + if !self.ip_ports.is_empty() { + s_ip_len = self.ip_ports.len() - 1; + } + if index >= s_ip_len { + 0 + } else { + index + 1 + } + } + None => 0, + }; + self.list_state.select(Some(index)); + self.scrollbar_state = self.scrollbar_state.position(index); + } + + fn scan_ports(&mut self, index: usize) { + self.ip_ports[index].state = PortsScanState::Scanning; + + let tx = self.action_tx.clone().unwrap(); + let ip: IpAddr = self.ip_ports[index].ip.parse().unwrap(); + let ports_box = Box::new(COMMON_PORTS.to_owned().into_iter()); + + let h = tokio::spawn(async move { + let ports = stream::iter(ports_box); + ports + .for_each_concurrent(1002, |port| Self::scan(tx.clone(), index, ip, port, 2)) + .await; + tx.send(Action::PortScanDone(index)).unwrap(); + }); + } + + async fn scan(tx: UnboundedSender, index: usize, ip: IpAddr, port: u16, timeout: u64) { + let timeout = Duration::from_secs(2); + let soc_addr = SocketAddr::new(ip, port); + match tokio::time::timeout(timeout, TcpStream::connect(&soc_addr)).await { + Ok(Ok(_)) => { + tx.send(Action::PortScan(index, port)).unwrap(); + // println!("port: {:?}", port); + } + _ => {} + } + } + + fn scan_selected(&mut self) { + let index = match self.list_state.selected() { + Some(index) => { + self.scan_ports(index); + } + None => {} + }; + } + + fn store_scanned_port(&mut self, index: usize, port: u16) { + let ip_ports = &mut self.ip_ports[index]; + if !ip_ports.ports.contains(&port) { + ip_ports.ports.push(port); + } + } + + fn make_list(&self) -> List { + let mut items = Vec::new(); + for ip in &self.ip_ports { + let ip_line = Line::from(vec!["ip: ".yellow(), ip.ip.clone().blue()]); + + let mut ports_spans = vec!["ports: ".yellow()]; + if ip.state == PortsScanState::Waiting { + ports_spans.push("?".red()); + } else if ip.state == PortsScanState::Scanning { + let spinner = SPINNER_SYMBOLS[self.spinner_index]; + ports_spans.push(spinner.magenta()); + } else { + for p in &ip.ports { + ports_spans.push(p.to_string().green()); + ports_spans.push(", ".yellow()); + } + } + + let ports = Line::from(ports_spans); + let p = Text::from(vec![ip_line, ports]); + + items.push(p); + } + + List::new(items) + .block( + Block::new() + .title( + ratatui::widgets::block::Title::from("|Ports|".yellow()) + .position(ratatui::widgets::block::Position::Top) + .alignment(Alignment::Right), + ) + .title( + ratatui::widgets::block::Title::from(Line::from(vec![ + Span::raw("|"), + Span::styled( + "s", + Style::default().add_modifier(Modifier::BOLD).fg(Color::Red), + ), + Span::styled("can selected", Style::default().fg(Color::Yellow)), + Span::raw("|"), + ])) + .alignment(Alignment::Left) + .position(ratatui::widgets::block::Position::Bottom), + ) + .title( + ratatui::widgets::block::Title::from(Line::from(vec![ + Span::styled("|", Style::default().fg(Color::Yellow)), + String::from(char::from_u32(0x25b2).unwrap_or('>')).red(), + String::from(char::from_u32(0x25bc).unwrap_or('>')).red(), + Span::styled("select|", Style::default().fg(Color::Yellow)), + ])) + .position(ratatui::widgets::block::Position::Bottom) + .alignment(Alignment::Right), + ) + .border_style(Style::default().fg(Color::Rgb(100, 100, 100))) + .borders(Borders::ALL) + .border_type(DEFAULT_BORDER_STYLE) + .padding(Padding::new(1, 3, 1, 1)), + ) + .highlight_symbol("*") + .highlight_style( + Style::default() + .add_modifier(Modifier::BOLD) + .bg(Color::Rgb(100, 100, 100)), + ) } } @@ -231,93 +275,79 @@ impl Component for Ports { Ok(()) } + fn tab_changed(&mut self, tab: TabsEnum) -> Result<()> { + self.active_tab = tab; + Ok(()) + } + fn update(&mut self, action: Action) -> Result> { + if let Action::Tick = action { + let mut s_index = self.spinner_index + 1; + s_index %= SPINNER_SYMBOLS.len() - 1; + self.spinner_index = s_index; + } + + // -- tab change + if let Action::TabChange(tab) = action { + self.tab_changed(tab).unwrap(); + } + + if self.active_tab == TabsEnum::Ports { + // -- prev & next select item in list + if let Action::Down = action { + self.next_in_list(); + } + if let Action::Up = action { + self.previous_in_list(); + } + + if let Action::ScanCidr = action { + self.scan_selected(); + } + } + + if let Action::PortScan(index, port) = action { + self.store_scanned_port(index, port); + } + + if let Action::PortScanDone(index) = action { + self.ip_ports[index].state = PortsScanState::Done; + } + + // -- PING IP + if let Action::PingIp(ref ip) = action { + self.process_ip(ip); + } + Ok(None) } fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()> { if self.active_tab == TabsEnum::Ports { let layout = get_vertical_layout(area); - // -- LIST + let mut list_rect = layout.bottom; list_rect.y += 1; list_rect.height -= 1; - // let list = self.make_list(); - } + // -- LIST + let list = self.make_list(); + f.render_stateful_widget(list, list_rect, &mut self.list_state.clone()); - // if !self.show_packets { - // // let layout = Layout::default() - // // .direction(Direction::Vertical) - // // .constraints([Constraint::Percentage(40), Constraint::Percentage(60)]) - // // .split(area); - // let layout = get_vertical_layout(area); - - // // -- TABLE - // // let mut table_rect = layout[1]; - // let mut table_rect = layout.bottom; - // table_rect.y += 1; - // table_rect.height -= 1; - - // let table = Self::make_table(self.scanned_ips.clone(), self.cidr, self.ip_num); - // f.render_stateful_widget(table, table_rect, &mut self.table_state.clone()); - - // // -- SCROLLBAR - // let scrollbar = Self::make_scrollbar(); - // let mut scroll_rect = table_rect; - // scroll_rect.y += 3; - // scroll_rect.height -= 3; - // f.render_stateful_widget( - // scrollbar, - // scroll_rect.inner(&Margin { - // vertical: 1, - // horizontal: 1, - // }), - // &mut self.scrollbar_state, - // ); - - // // -- ERROR - // if self.cidr_error { - // let error_rect = Rect::new(table_rect.width - (19 + 41), table_rect.y + 1, 18, 3); - // let block = self.make_error(); - // f.render_widget(block, error_rect); - // } - - // // -- INPUT - // let input_size: u16 = INPUT_SIZE as u16; - // let input_rect = Rect::new( - // table_rect.width - (input_size + 1), - // table_rect.y + 1, - // input_size, - // 3, - // ); - // // -- INPUT_SIZE - 3 is offset for border + 1char for cursor - // let scroll = self.input.visual_scroll(INPUT_SIZE - 3); - // let mut block = self.make_input(scroll); - // if self.is_scanning { - // block = block.clone().add_modifier(Modifier::DIM); - // } - // f.render_widget(block, input_rect); - // // -- cursor - // match self.mode { - // Mode::Input => { - // f.set_cursor( - // input_rect.x - // + ((self.input.visual_cursor()).max(scroll) - scroll) as u16 - // + 1, - // input_rect.y + 1, - // ); - // } - // Mode::Normal => {} - // } - - // // -- THROBBER - // if self.is_scanning { - // let throbber = self.make_spinner(); - // let throbber_rect = Rect::new(input_rect.x + 1, input_rect.y, 12, 1); - // f.render_widget(throbber, throbber_rect); - // } - // } + // -- SCROLLBAR + let scrollbar = Self::make_scrollbar(); + let mut scroll_rect = list_rect; + scroll_rect.y += 1; + scroll_rect.height -= 2; + f.render_stateful_widget( + scrollbar, + scroll_rect.inner(&Margin { + vertical: 1, + horizontal: 1, + }), + &mut self.scrollbar_state, + ); + } Ok(()) } diff --git a/src/enums.rs b/src/enums.rs index 23645b8..4256883 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -94,6 +94,13 @@ pub enum PacketTypeEnum { Icmp6, } +#[derive(PartialEq)] +pub enum PortsScanState { + Waiting, + Scanning, + Done, +} + impl PacketTypeEnum { pub fn previous(&self) -> Self { let current_index: usize = *self as usize; @@ -107,3 +114,72 @@ impl PacketTypeEnum { Self::from_repr(next_index).unwrap_or(*self) } } + +pub const COMMON_PORTS: &[u16] = &[ + 5601, 9300, 80, 23, 443, 21, 22, 25, 3389, 110, 445, 139, 143, 53, 135, 3306, 8080, 1723, 111, + 995, 993, 5900, 1025, 587, 8888, 199, 1720, 465, 548, 113, 81, 6001, 10000, 514, 5060, 179, + 1026, 2000, 8443, 8000, 32768, 554, 26, 1433, 49152, 2001, 515, 8008, 49154, 1027, 5666, 646, + 5000, 5631, 631, 49153, 8081, 2049, 88, 79, 5800, 106, 2121, 1110, 49155, 6000, 513, 990, 5357, + 427, 49156, 543, 544, 5101, 144, 7, 389, 8009, 3128, 444, 9999, 5009, 7070, 5190, 3000, 5432, + 1900, 3986, 13, 1029, 9, 5051, 6646, 49157, 1028, 873, 1755, 2717, 4899, 9100, 119, 37, 1000, + 3001, 5001, 82, 10010, 1030, 9090, 2107, 1024, 2103, 6004, 1801, 5050, 19, 8031, 1041, 255, + 2967, 1049, 1048, 1053, 3703, 1056, 1065, 1064, 1054, 17, 808, 3689, 1031, 1044, 1071, 5901, + 9102, 100, 8010, 2869, 1039, 5120, 4001, 9000, 2105, 636, 1038, 2601, 7000, 1, 1066, 1069, 625, + 311, 280, 254, 4000, 5003, 1761, 2002, 2005, 1998, 1032, 1050, 6112, 3690, 1521, 2161, 6002, + 1080, 2401, 4045, 902, 7937, 787, 1058, 2383, 32771, 1033, 1040, 1059, 50000, 5555, 10001, + 1494, 2301, 593, 3, 3268, 7938, 1234, 1022, 1035, 9001, 1074, 8002, 1036, 1037, 464, 1935, + 6666, 2003, 497, 6543, 1352, 24, 3269, 1111, 407, 500, 20, 2006, 3260, 1034, 15000, 1218, 4444, + 264, 2004, 33, 1042, 42510, 999, 3052, 1023, 1068, 222, 888, 7100, 563, 1717, 2008, 992, 32770, + 32772, 7001, 8082, 2007, 5550, 2009, 1043, 512, 5801, 7019, 2701, 50001, 1700, 4662, 2065, + 2010, 42, 9535, 2602, 3333, 161, 5100, 2604, 4002, 5002, 8192, 6789, 8194, 6059, 1047, 8193, + 2702, 9595, 1051, 9594, 9593, 16993, 16992, 5226, 5225, 32769, 1052, 1055, 3283, 1062, 9415, + 8701, 8652, 8651, 8089, 65389, 65000, 64680, 64623, 55600, 55555, 52869, 35500, 33354, 23502, + 20828, 1311, 1060, 4443, 1067, 13782, 5902, 366, 9050, 1002, 85, 5500, 1864, 5431, 1863, 8085, + 51103, 49999, 45100, 10243, 49, 6667, 90, 27000, 1503, 6881, 8021, 1500, 340, 5566, 8088, 2222, + 9071, 8899, 1501, 5102, 32774, 32773, 9101, 6005, 9876, 5679, 163, 648, 146, 1666, 901, 83, + 9207, 8001, 8083, 5004, 3476, 8084, 5214, 14238, 12345, 912, 30, 2605, 2030, 6, 541, 8007, + 3005, 4, 1248, 2500, 880, 306, 4242, 1097, 9009, 2525, 1086, 1088, 8291, 52822, 6101, 900, + 7200, 2809, 800, 32775, 12000, 1083, 211, 987, 705, 20005, 711, 13783, 6969, 3071, 3801, 3017, + 8873, 5269, 5222, 1046, 1085, 5987, 5989, 5988, 2190, 11967, 8600, 8087, 30000, 9010, 7741, + 3367, 3766, 7627, 14000, 3031, 1099, 1098, 6580, 2718, 15002, 4129, 6901, 3827, 3580, 2144, + 8181, 9900, 1718, 9080, 2135, 2811, 1045, 2399, 1148, 10002, 9002, 8086, 3998, 2607, 11110, + 4126, 2875, 5718, 9011, 5911, 5910, 9618, 2381, 1096, 3300, 3351, 1073, 8333, 15660, 6123, + 3784, 5633, 3211, 1078, 3659, 3551, 2100, 16001, 3325, 3323, 2260, 2160, 1104, 9968, 9503, + 9502, 9485, 9290, 9220, 8994, 8649, 8222, 7911, 7625, 7106, 65129, 63331, 6156, 6129, 60020, + 5962, 5961, 5960, 5959, 5925, 5877, 5825, 5810, 58080, 57294, 50800, 50006, 50003, 49160, + 49159, 49158, 48080, 40193, 34573, 34572, 34571, 3404, 33899, 3301, 32782, 32781, 31038, 30718, + 28201, 27715, 25734, 24800, 22939, 21571, 20221, 20031, 19842, 19801, 19101, 17988, 1783, + 16018, 16016, 15003, 14442, 13456, 10629, 10628, 10626, 10621, 10617, 10616, 10566, 10025, + 10024, 10012, 1169, 5030, 5414, 1057, 6788, 1947, 1094, 1075, 1108, 4003, 1081, 1093, 4449, + 1687, 1840, 1100, 1063, 1061, 1107, 1106, 9500, 20222, 7778, 1077, 1310, 2119, 2492, 1070, + 20000, 8400, 1272, 6389, 7777, 1072, 1079, 1082, 8402, 691, 89, 32776, 1999, 1001, 212, 2020, + 7002, 2998, 6003, 50002, 3372, 898, 5510, 32, 2033, 5903, 99, 749, 425, 43, 5405, 6106, 13722, + 6502, 7007, 458, 1580, 9666, 8100, 3737, 5298, 1152, 8090, 2191, 3011, 5200, 3851, 3371, 3370, + 3369, 7402, 5054, 3918, 3077, 7443, 3493, 3828, 1186, 2179, 1183, 19315, 19283, 5963, 3995, + 1124, 8500, 1089, 10004, 2251, 1087, 5280, 3871, 3030, 62078, 9091, 4111, 1334, 3261, 2522, + 5859, 1247, 9944, 9943, 9877, 9110, 8654, 8254, 8180, 8011, 7512, 7435, 7103, 61900, 61532, + 5922, 5915, 5904, 5822, 56738, 55055, 51493, 50636, 50389, 49175, 49165, 49163, 3546, 32784, + 27355, 27353, 27352, 24444, 19780, 18988, 16012, 15742, 10778, 4006, 2126, 4446, 3880, 1782, + 1296, 9998, 32777, 9040, 32779, 1021, 2021, 666, 32778, 616, 700, 1524, 1112, 5802, 4321, 545, + 49400, 84, 38292, 2040, 3006, 2111, 32780, 1084, 1600, 2048, 2638, 9111, 6547, 6699, 16080, + 2106, 667, 6007, 1533, 5560, 1443, 720, 2034, 555, 801, 3826, 3814, 7676, 3869, 1138, 6567, + 10003, 3221, 6025, 2608, 9200, 7025, 11111, 4279, 3527, 1151, 8300, 6689, 9878, 8200, 10009, + 8800, 5730, 2394, 2393, 2725, 5061, 6566, 9081, 5678, 3800, 4550, 5080, 1201, 3168, 1862, 1114, + 3905, 6510, 8383, 3914, 3971, 3809, 5033, 3517, 4900, 9418, 2909, 3878, 8042, 1091, 1090, 3920, + 3945, 1175, 3390, 3889, 1131, 8292, 1119, 5087, 7800, 4848, 16000, 3324, 3322, 1117, 5221, + 4445, 9917, 9575, 9099, 9003, 8290, 8099, 8093, 8045, 7921, 7920, 7496, 6839, 6792, 6779, 6692, + 6565, 60443, 5952, 5950, 5907, 5906, 5862, 5850, 5815, 5811, 57797, 56737, 5544, 55056, 5440, + 54328, 54045, 52848, 52673, 50500, 50300, 49176, 49167, 49161, 44501, 44176, 41511, 40911, + 32785, 32783, 30951, 27356, 26214, 25735, 19350, 18101, 18040, 17877, 16113, 15004, 14441, + 12265, 12174, 10215, 10180, 4567, 6100, 4004, 4005, 8022, 9898, 7999, 1271, 1199, 3003, 1122, + 2323, 2022, 4224, 617, 777, 417, 714, 6346, 981, 722, 1009, 4998, 70, 1076, 5999, 10082, 765, + 301, 524, 668, 2041, 259, 1984, 2068, 6009, 1417, 1434, 44443, 7004, 1007, 4343, 416, 2038, + 4125, 1461, 9103, 6006, 109, 911, 726, 1010, 2046, 2035, 7201, 687, 2013, 481, 903, 125, 6669, + 6668, 1455, 683, 1011, 2043, 2047, 256, 31337, 9929, 5998, 406, 44442, 783, 843, 2042, 2045, + 1875, 1556, 5938, 8675, 1277, 3972, 3968, 3870, 6068, 3050, 5151, 3792, 8889, 5063, 1198, 1192, + 4040, 1145, 6060, 6051, 3916, 7272, 9443, 9444, 7024, 13724, 4252, 4200, 1141, 1233, 8765, + 3963, 1137, 9191, 3808, 8686, 3981, 9988, 1163, 4164, 3820, 6481, 3731, 40000, 2710, 3852, + 3849, 3853, 5081, 8097, 3944, 1287, 3863, 4555, 4430, 7744, 1812, 7913, 1166, 1164, 1165, + 10160, 8019, 4658, 7878, 1259, 1092, 10008, 3304, 3307, +]; +