opus/sys/apps/Sniff.lua

389 lines
9.8 KiB
Lua

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 multishell = _ENV.multishell
local gridColumns = {}
table.insert(gridColumns, { heading = '#', key = 'id', width = 5, 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.term.width > 50 then
table.insert(gridColumns, { heading = 'Dist', key = 'distance', width = 6, align = 'right' })
end
table.insert(gridColumns, { heading = 'Msg', key = 'packetStr' })
local page = UI.Page {
paused = false,
index = 1,
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', backgroundColor = colors.black },
accelerators = { ['backspace'] = 'config_close' },
configTabs = UI.Tabs {
y = 2,
filterTab = UI.Tab {
tabTitle = 'Filter',
noFill = true,
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 = 14, y = 8,
value = false,
},
filterAddText = UI.Text {
x = 18, y = 8,
value = "Use ID filter",
},
rangeText = UI.Text {
x = 15, y = 2,
value = "Distance filter",
},
rangeEntry = UI.TextEntry {
x = 15, y = 3,
width = 10,
limit = 8,
shadowText = 'Range',
transform = 'number',
},
},
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',
},
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.packetGrid:getDisplayValues(row)
row = Util.shallowCopy(row)
row.distance = Util.toBytes(Util.round(row.distance), 2)
return row
end
function page.packetGrid:addPacket(packet)
if not page.paused and (packet.distance <= (filterConfig.rangeEntry.value or math.huge)) and (not filterConfig.filterAllCheck.value or filterConfig.filterGrid.values[packet.portid]) then
page.index = page.index + 1
local _, res = pcall(textutils.serialize, packet.message)
packet.packetStr = res:gsub("\n%s*", "")
table.insert(self.values, packet)
end
if #self.values > self.maxPacket then
local t = { }
for i = 10, #self.values do
t[i - 9] = self.values[i]
end
self:setValues(t)
end
self:update()
self:draw()
page:sync()
end
function page:enable()
modemConfig.modems = {}
Util.each(_G.device, function(dev)
if dev.type == "modem" then
modemConfig.modems[dev.side] = {
type = dev.isWireless() and 'Wireless' or 'Wired',
side = dev.side,
openChannels = { },
device = dev,
loaded = false
}
end
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
UI:quit()
else return UI.Page.eventHandler(self, event)
end
return true
end
Event.on('modem_message', function(_, side, chan, reply, msg, dist)
if modemConfig.currentModem.side == side then
page.packetGrid:addPacket({
id = page.index,
portid = chan,
replyid = reply,
message = msg,
distance = dist or -1,
})
end
end)
local args = Util.parse(...)
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:start()