diff --git a/sys/apps/Network.lua b/sys/apps/Network.lua index 52be626..c437fe9 100644 --- a/sys/apps/Network.lua +++ b/sys/apps/Network.lua @@ -44,7 +44,7 @@ local page = UI.Page { x = -3, dropdown = { { text = 'Port Status', event = 'ports', modem = true }, - { spacer = true }, + { spacer = true }, { text = 'Help', event = 'help', noCheck = true }, }, }, @@ -127,14 +127,12 @@ local function sendCommand(host, command) end end ---[[ function page.ports:eventHandler(event) if event.type == 'grid_select' then shell.openForegroundTab('sniff ' .. event.selected.port) end return UI.SlideOut.eventHandler(self, event) end -]] function page.ports.grid:update() local transport = network:getTransport() diff --git a/sys/apps/Sniff.lua b/sys/apps/Sniff.lua new file mode 100644 index 0000000..8499c72 --- /dev/null +++ b/sys/apps/Sniff.lua @@ -0,0 +1,361 @@ +local UI = require('opus.ui') +local Event = require('opus.event') +local Util = require('opus.util') + +local colors = _G.colors +local device = _G.device +local textutils = _G.textutils +local peripheral = _G.peripheral +local multishell = _ENV.multishell + +local gridColumns = {} +table.insert(gridColumns, { heading = '#', key = 'id', width = 4, align = 'right' }) +table.insert(gridColumns, { heading = 'Port', key = 'portid', width = 5, align = 'right' }) +table.insert(gridColumns, { heading = 'Reply', key = 'replyid', width = 5, align = 'right' }) +if UI.defaultDevice.width > 50 then table.insert(gridColumns, { heading = 'Dist', key = 'distance', width = 6, align = 'right' }) end +table.insert(gridColumns, { heading = 'Msg', key = 'message' }) + +local page = UI.Page { + paused = false, + index = 0, + notification = UI.Notification { }, + accelerators = { ['control-q'] = 'quit' }, + + menuBar = UI.MenuBar { + buttons = { + { text = 'Pause', event = 'pause_click', name = 'pauseButton' }, + { text = 'Clear', event = 'clear_click' }, + { text = 'Config', event = 'config_click' }, + }, + }, + + packetGrid = UI.ScrollingGrid { + y = 2, + maxPacket = 300, + inverseSort = true, + sortColumn = 'id', + columns = gridColumns, + accelerators = { ['space'] = 'pause_click' }, + }, + + configSlide = UI.SlideOut { + y = -11, + titleBar = UI.TitleBar { title = 'Sniffer Config', event = 'config_close' }, + accelerators = { ['backspace'] = 'config_close' }, + configTabs = UI.Tabs { + y = 2, + filterTab = UI.Tab { + tabTitle = 'Filter', + filterGridText = UI.Text { + x = 2, y = 2, + value = 'ID filter', + }, + filterGrid = UI.ScrollingGrid { + x = 2, y = 3, + width = 10, height = 4, + disableHeader = true, + columns = { + { key = 'id', width = 5 }, + }, + }, + filterEntry = UI.TextEntry { + x = 2, y = 8, + width = 7, + shadowText = 'ID', + limit = 5, + accelerators = { enter = 'filter_add' }, + }, + filterAdd = UI.Button { + x = 10, y = 8, + text = '+', + event = 'filter_add', + }, + filterAllCheck = UI.Checkbox { + x = 13, y = 4, + value = false, + }, + filterAddText = UI.Text { + x = 17, y = 4, + value = "Use ID filter", + }, + }, + modemTab = UI.Tab { + tabTitle = 'Modem', + channelGrid = UI.ScrollingGrid { + x = 2, y = 2, + width = 12, height = 5, + autospace = true, + columns = {{ heading = 'Open Ports', key = 'port' }}, + }, + modemGrid = UI.ScrollingGrid { + x = 15, y = 2, + ex = -2, height = 5, + autospace = true, + columns = { + { heading = 'Side', key = 'side' }, + { heading = 'Type', key = 'type' }, + }, + }, + channelEntry = UI.TextEntry { + x = 2, y = 8, + width = 7, + shadowText = 'ID', + limit = 5, + accelerators = { enter = 'channel_add' }, + }, + channelAdd = UI.Button { + x = 10, y = 8, + text = '+', + event = 'channel_add', + }, + }, + }, + }, + + packetSlide = UI.SlideOut { + titleBar = UI.TitleBar { + title = 'Packet Information', + event = 'packet_close', + }, + backgroundColor = colors.cyan, + accelerators = { + ['backspace'] = 'packet_close', + ['left'] = 'prev_packet', + ['right'] = 'next_packet', + }, + packetMeta = UI.Grid { + x = 2, y = 2, + ex = 23, height = 4, + inactive = true, + columns = { + { key = 'text' }, + { key = 'value', align = 'right', textColor = colors.yellow }, + }, + values = { + port = { text = 'Port' }, + reply = { text = 'Reply' }, + dist = { text = 'Distance' }, + } + }, + packetButton = UI.Button { + x = 25, y = 5, + text = 'Open in Lua', + event = 'packet_lua', + }, + packetData = UI.TextArea { + y = 7, ey = -1, + backgroundColor = colors.black, + }, + }, +} + +local filterConfig = page.configSlide.configTabs.filterTab +local modemConfig = page.configSlide.configTabs.modemTab + +function filterConfig:eventHandler(event) + if event.type == 'filter_add' then + local id = tonumber(self.filterEntry.value) + if id then self.filterGrid.values[id] = { id = id } + self.filterGrid:update() + self.filterEntry:reset() + self:draw() + end + + elseif event.type == 'grid_select' then + self.filterGrid.values[event.selected.id] = nil + self.filterGrid:update() + self.filterGrid:draw() + + else return UI.Tab.eventHandler(self, event) + end + return true +end + +function modemConfig:loadChannel() + for chan = 0, 65535 do + self.currentModem.openChannels[chan] = self.currentModem.device.isOpen(chan) and { port = chan } or nil + end + self.channelGrid:setValues(self.currentModem.openChannels) + self.currentModem.loaded = true +end + +function modemConfig:enable() + if not self.currentModem.loaded then + self:loadChannel() + end + + UI.Tab.enable(self) +end + +function modemConfig:eventHandler(event) + if event.type == 'channel_add' then + local id = tonumber(modemConfig.channelEntry.value) + if id then + self.currentModem.openChannels[id] = { port = id } + self.currentModem.device.open(id) + self.channelGrid:setValues(self.currentModem.openChannels) + self.channelGrid:update() + self.channelEntry:reset() + self:draw() + end + + elseif event.type == 'grid_select' then + if event.element == self.channelGrid then + self.currentModem.openChannels[event.selected.port] = nil + self.currentModem.device.close(event.selected.port) + self.channelGrid:setValues(self.currentModem.openChannels) + page.configSlide.configTabs.modemTab.channelGrid:update() + page.configSlide.configTabs.modemTab.channelGrid:draw() + + elseif event.element == self.modemGrid then + self.currentModem = event.selected + page.notification:info("Loading channel list") + page:sync() + modemConfig:loadChannel() + page.notification:success("Now using modem on " .. self.currentModem.side) + self.channelGrid:draw() + end + + else return UI.Tab.eventHandler(self, event) + end + return true +end + +function page.packetSlide:setPacket(packet) + self.currentPacket = packet + local p, res = pcall(textutils.serialize, page.packetSlide.currentPacket.message) + self.packetData.textColor = p and colors.white or colors.red + self.packetData:setText(res) + self.packetMeta.values.port.value = page.packetSlide.currentPacket.portid + self.packetMeta.values.reply.value = page.packetSlide.currentPacket.replyid + self.packetMeta.values.dist.value = Util.round(page.packetSlide.currentPacket.distance, 2) +end + +function page.packetSlide:show(packet) + self:setPacket(packet) + + UI.SlideOut.show(self) +end + +function page.packetSlide:eventHandler(event) + if event.type == 'packet_close' then + self:hide() + page:setFocus(page.packetGrid) + + elseif event.type == 'packet_lua' then + multishell.openTab({ path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true }) + + elseif event.type == 'prev_packet' then + local c = self.currentPacket + local n = page.packetGrid.values[c.id - 1] + if n then + self:setPacket(n) + self:draw() + end + + elseif event.type == 'next_packet' then + local c = self.currentPacket + local n = page.packetGrid.values[c.id + 1] + if n then + self:setPacket(n) + self:draw() + end + + else return UI.SlideOut.eventHandler(self, event) + end + return true +end + +function page:enable() + modemConfig.modems = {} + peripheral.find('modem', function(side, dev) + modemConfig.modems[side] = { + type = dev.isWireless() and 'Wireless' or 'Wired', + side = side, + openChannels = { }, + device = dev, + loaded = false + } + end) + modemConfig.currentModem = device.wireless_modem and + modemConfig.modems[device.wireless_modem.side] or + device.wired_modem and + modemConfig.modems[device.wired_modem.side] or + nil + + modemConfig.modemGrid.values = modemConfig.modems + modemConfig.modemGrid:update() + modemConfig.modemGrid:setSelected(modemConfig.currentModem) + + UI.Page.enable(self) +end + + +function page:eventHandler(event) + if event.type == 'pause_click' then + self.paused = not self.paused + self.menuBar.pauseButton.text = self.paused and 'Resume' or 'Pause' + self.notification:success(self.paused and 'Paused' or 'Resumed', 2) + self.menuBar:draw() + + elseif event.type == 'clear_click' then + self.packetGrid:setValues({ }) + self.notification:success('Cleared', 2) + self.packetGrid:draw() + + elseif event.type == 'config_click' then + self.configSlide:show() + self:setFocus(filterConfig.filterEntry) + + elseif event.type == 'config_close' then + self.configSlide:hide() + self:setFocus(self.packetGrid) + + elseif event.type == 'grid_select' then + self.packetSlide:show(event.selected) + + elseif event.type == 'quit' then + Event.exitPullEvents() + + else return UI.Page.eventHandler(self, event) + end + return true +end + +Event.on('modem_message', function(event, side, chan, reply, msg, dist) + if not page.paused and modemConfig.currentModem.side == side and (not filterConfig.filterAllCheck.value or filterConfig.filterGrid.values[chan]) then + page.index = page.index + 1 + table.insert(page.packetGrid.values, { + id = page.index, + portid = chan, + replyid = reply, + message = msg, + distance = dist, + }) + + if #page.packetGrid.values > page.packetGrid.maxPacket then + local t = { } + for i = 10, #page.packetGrid.values do + t[i - 9] = page.packetGrid.values[i] + end + page.packetGrid:setValues(t) + end + + page.packetGrid:update() + page.packetGrid:draw() + page:sync() + end +end) + +local args = {...} +if args[1] then + local id = tonumber(args[1]) + if id then + filterConfig.filterGrid.values[id] = { id = id } + filterConfig.filterAllCheck:setValue(true) + filterConfig.filterGrid:update() + end +end + +UI:setPage(page) +UI:pullEvents()