1
0
mirror of https://github.com/halleysfifthinc/Toyota-AVC-LAN synced 2025-07-25 18:56:46 +00:00

Add Wireshark packet dissector plugin and associated tools

This commit is contained in:
Allen Hill 2024-08-23 11:02:40 -07:00
parent fadd2c4259
commit e857bbb09f
26 changed files with 1475 additions and 0 deletions

View File

@ -0,0 +1,251 @@
# This file is machine-generated - editing it directly is not advised
julia_version = "1.10.0"
manifest_format = "2.0"
project_hash = "2d299f8e3abb5302e3c8ed5120acc4d9928016b1"
[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
version = "1.1.1"
[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
[[deps.Compat]]
deps = ["UUIDs"]
git-tree-sha1 = "886826d76ea9e72b35fcd000e535588f7b60f21d"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "4.10.1"
[deps.Compat.extensions]
CompatLinearAlgebraExt = "LinearAlgebra"
[deps.Compat.weakdeps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
[[deps.Downloads]]
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
version = "1.6.0"
[[deps.ExprTools]]
git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec"
uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
version = "0.1.10"
[[deps.FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
[[deps.InlineStrings]]
deps = ["Parsers"]
git-tree-sha1 = "9cc2baf75c6d09f9da536ddf58eb2f29dedaf461"
uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
version = "1.4.0"
[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
[[deps.JLLWrappers]]
deps = ["Artifacts", "Preferences"]
git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.5.0"
[[deps.LazyArtifacts]]
deps = ["Artifacts", "Pkg"]
uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3"
[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
version = "0.6.4"
[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
version = "8.4.0+0"
[[deps.LibGit2]]
deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
[[deps.LibGit2_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"]
uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5"
version = "1.6.4+0"
[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
version = "1.11.0+1"
[[deps.LibSerialPort]]
deps = ["Libdl", "libserialport_jll"]
git-tree-sha1 = "69403284a6217f6e60e76553032a103893b7d910"
uuid = "a05a14c7-6e3b-5ba9-90a2-45558833e1df"
version = "0.5.2"
[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
version = "2.28.2+1"
[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
[[deps.Mocking]]
deps = ["Compat", "ExprTools"]
git-tree-sha1 = "4cc0c5a83933648b615c36c2b956d94fda70641e"
uuid = "78c3b35d-d492-501b-9361-3d52fe80e533"
version = "0.7.7"
[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
version = "2023.1.10"
[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
version = "1.2.0"
[[deps.Parsers]]
deps = ["Dates", "PrecompileTools", "UUIDs"]
git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.8.1"
[[deps.PcapTools]]
deps = ["Dates", "Mmap", "UnixTimes", "UnsafeArrays"]
path = "dev/PcapTools"
uuid = "222fe7e8-3f39-464a-bf97-d9bbb753f246"
version = "1.1.1"
[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
version = "1.10.0"
[[deps.PrecompileTools]]
deps = ["Preferences"]
git-tree-sha1 = "03b4c25b43cb84cee5c90aa9b5ea0a78fd848d2f"
uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
version = "1.2.0"
[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00805cd429dcb4870060ff49ef443486c262e38e"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.4.1"
[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
[[deps.Random]]
deps = ["SHA"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0"
[[deps.Scratch]]
deps = ["Dates"]
git-tree-sha1 = "3bac05bc7e74a75fd9cba4295cde4045d9fe2386"
uuid = "6c6a2e73-6563-6170-7368-637461726353"
version = "1.2.1"
[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
version = "1.0.3"
[[deps.TZJData]]
deps = ["Artifacts"]
git-tree-sha1 = "d39314cdbaf5b90a047db33858626f8d1cc973e1"
uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7"
version = "1.0.0+2023c"
[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
version = "1.10.0"
[[deps.TimeZones]]
deps = ["Artifacts", "Dates", "Downloads", "InlineStrings", "LazyArtifacts", "Mocking", "Printf", "Scratch", "TZJData", "Unicode", "p7zip_jll"]
git-tree-sha1 = "89e64d61ef3cd9e80f7fc12b7d13db2d75a23c03"
uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53"
version = "1.13.0"
[deps.TimeZones.extensions]
TimeZonesRecipesBaseExt = "RecipesBase"
[deps.TimeZones.weakdeps]
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
[[deps.UnixTimes]]
deps = ["Dates", "TimeZones"]
git-tree-sha1 = "406de9a6ee22f2030477ed7a50455aef3038bd4b"
uuid = "ab1a18e7-b408-4913-896c-624bb82ed7f4"
version = "1.3.0"
[[deps.UnsafeArrays]]
git-tree-sha1 = "e7f1c67ba99ac6df440de191fa4d5cbfcbdddcd1"
uuid = "c4a57d5a-5b31-53a6-b365-19f8c011fbd6"
version = "1.0.5"
[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
version = "1.2.13+1"
[[deps.libserialport_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "aacc5af8e8ba0b48a6feadac7afd4bca5d4e98d8"
uuid = "220460dc-b50e-5ed0-8176-09b0fd261e90"
version = "0.1.3+0"
[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
version = "1.52.0+1"
[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
version = "17.4.0+2"

View File

@ -0,0 +1,10 @@
name = "AVCLANPipe"
uuid = "bfacebfd-1bd5-49e7-81bd-5edd8f4074de"
authors = ["Allen Hill <allenofthehills@gmail.com>"]
version = "0.1.0"
[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
LibSerialPort = "a05a14c7-6e3b-5ba9-90a2-45558833e1df"
PcapTools = "222fe7e8-3f39-464a-bf97-d9bbb753f246"
UnixTimes = "ab1a18e7-b408-4913-896c-624bb82ed7f4"

View File

@ -0,0 +1,11 @@
# Packet analysis with Wireshark
- Install the Lua IEBUS/AVCLAN packet dissector to the Wireshark Lua Plugins folder
- Look in Help > About Wireshark dialog, Folders tab, "Personal Lua Plugins". It was `~/.local/lib/wireshark/plugins` for me, but it didn't exist until I manually created it.
- Copy or link `avclan_plugin.lua` to the Wireshark Lua Pluginds folder
- Linking is more convenient if modifying/developing the dissector
`ln -s $(pwd)/avclan_plugin.lua ~/.local/lib/wireshark/plugins/avclan_plugin.lua`
- Install Julia and needed packages (I recommend using [juliaup](https://github.com/JuliaLang/juliaup) or an official binary from the Julialang website. Binaries from your distribution are typically out of date and/or built incorrectly.)
- From this folder, start Julia with the local project environment using `julia --project=@.`
- Run `] instantiate` to install the necessary Julia packages
- Run `julia src/pipepackets.jl | wireshark -k -i -` to pipe packets logged by the Mockingboard over serial into Wireshark. If the Lua dissector is installed correctly, the packets should be correctly recognized and dissected as IEBUS/AVCLAN packets

View File

@ -0,0 +1,583 @@
--[[
Copyright (C) 2023 Allen Hill <allenofthehills@gmail.com>
Portions of the following are based on code from libsigrokdecode PR that is
copyright (C) 2023 Maciej Grela <enki@fsck.pl>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
--]]
local iebusproto = Proto("iebus", "IEBus protocol")
local f_broadcast = ProtoField.bool("iebus.broadcast", "Broadcast", base.NONE, { [1] = "false", [2] = "true" })
local f_controller_addr = ProtoField.uint16("iebus.controller", "Controller address", base.HEX, nil, 0x0FFF)
local f_peripheral_addr = ProtoField.uint16("iebus.peripheral", "Peripheral address", base.HEX, nil, 0x0FFF)
local f_control = ProtoField.uint8("iebus.control", "Control field", base.HEX)
local f_length = ProtoField.uint8("iebus.length", "Data length", base.DEC)
local f_data = ProtoField.bytes("iebus.data", "Frame data", base.SPACE)
iebusproto.fields = {
f_broadcast,
f_controller_addr,
f_peripheral_addr,
f_control,
f_length,
f_data
}
local controlbits = {
[0x0] = "READ PERIPH STATUS (SSR)",
[0x1] = "UNDEFINED",
[0x2] = "UNDEFINED",
[0x3] = "READ & LOCK DATA",
[0x4] = "READ LOCK ADDR (lo8)",
[0x5] = "READ LOCK ADDR (hi4)",
[0x6] = "READ & UNLOCK PERIPH STATUS (SSR)",
[0x7] = "READ DATA",
[0x8] = "UNDEFINED",
[0x9] = "UNDEFINED",
[0xA] = "WRITE & LOCK CMD",
[0xB] = "WRITE & LOCK DATA",
[0xC] = "UNDEFINED",
[0xD] = "UNDEFINED",
[0xE] = "WRITE CMD",
[0xF] = "WRITE DATA"
}
local periphstatusbits = {
[0] = "TX BUFFER EMPTY",
[1] = "RX BUFFER EMPTY",
[2] = "UNIT LOCKED",
[4] = "TX ENABLED",
[6] = "IEBUS MODE 1",
[7] = "IEBUS MODE 2",
}
-- Expert info for unknown packet types, set as group PI_UNDECODED, severity PI_NOTE or PI_WARN
-- Expert info for LAN Check [0x3, SW_ID, 0x01, 0x20] as severity PI_CHAT
function iebusproto.dissector(buffer, pinfo, tree)
length = buffer:len()
if length == 0 then return end
pinfo.cols.protocol = iebusproto.name
local subtree = tree:add(iebusproto, buffer(), "IEBus frame")
subtree:add_le(f_broadcast, buffer(0,1))
pinfo.cols.src = buffer(1,2):bytes():tohex():sub(2,-1)
pinfo.cols.dst = buffer(3,2):bytes():tohex():sub(2,-1)
local control_item, control = subtree:add_packet_field(f_control, buffer(5,1), ENC_LITTLE_ENDIAN)
-- print("control val" .. control)
control_item:append_text(" (" .. controlbits[buffer(5,1):uint()] .. ")")
local _, datalen = subtree:add_packet_field(f_length, buffer(6,1), ENC_LITTLE_ENDIAN)
local data = subtree:add(iebusproto, buffer(), "IEBus message")
data:add(f_data, buffer(7, datalen))
end
local udlt = DissectorTable.get("wtap_encap")
udlt:add(wtap.USER15, iebusproto)
local avclanproto = Proto("avclan", "AVCLAN protocol")
local known_devices_names = {
["COMM_CTRL"] = 0x01,
["COMMUNICATION v1"] = 0x11,
["COMMUNICATION v2"] = 0x12,
["SW"] = 0x21,
["SW_NAME"] = 0x23,
["SW_CONVERTING"] = 0x24,
["CMD_SW"] = 0x25,
["STATUS"] = 0x31,
["BEEP_HU"] = 0x28,
["BEEP_SPEAKERS"] = 0x29,
["FRONT_PSNG_MONITOR"] = 0x34,
["CD_CHANGER2"] = 0x43,
["BLUETOOTH_TEL"] = 0x55,
["INFO_DRAWING"] = 0x56,
["NAV_ECU"] = 0x58,
["CAMERA"] = 0x5C,
["CLIMATE_DRAWING"] = 0x5D,
["AUDIO_DRAWING"] = 0x5E,
["TRIP_INFO_DRAWING"] = 0x5F,
["TUNER"] = 0x60,
["TAPE_DECK"] = 0x61,
["CD"] = 0x62,
["CD_CHANGER"] = 0x63,
["AUDIO_AMP"] = 0x74,
["GPS"] = 0x80,
["VOICE_CTRL"] = 0x85,
["CLIMATE_CTRL_DEV"] = 0xE0,
["TRIP_INFO"] = 0xE5,
}
local known_devices = {
[0x01] = "COMM_CTRL",
[0x11] = "COMMUNICATION v1",
[0x12] = "COMMUNICATION v2",
[0x21] = "SW",
[0x23] = "SW_NAME",
[0x24] = "SW_CONVERTING",
[0x25] = "CMD_SW",
[0x31] = "STATUS",
[0x28] = "BEEP_HU",
[0x29] = "BEEP_SPEAKERS",
[0x34] = "FRONT_PSNG_MONITOR",
[0x43] = "CD_CHANGER2",
[0x55] = "BLUETOOTH_TEL",
[0x56] = "INFO_DRAWING",
[0x58] = "NAV_ECU",
[0x5C] = "CAMERA",
[0x5D] = "CLIMATE_DRAWING",
[0x5E] = "AUDIO_DRAWING",
[0x5F] = "TRIP_INFO_DRAWING",
[0x60] = "TUNER",
[0x61] = "TAPE_DECK",
[0x62] = "CD",
[0x63] = "CD_CHANGER",
[0x74] = "AUDIO_AMP",
[0x80] = "GPS",
[0x85] = "VOICE_CTRL",
[0xE0] = "CLIMATE_CTRL_DEV",
[0xE5] = "TRIP_INFO",
}
local f_from_device = ProtoField.uint8("avclan.from_device", "From device", base.HEX, known_devices)
local f_to_device = ProtoField.uint8("avclan.to_device", "To device", base.HEX, known_devices)
local f_active_device = ProtoField.uint8("avclan.active_device", "Active device", base.HEX, known_devices)
local known_actions_names = {
-- LAN related
["LIST_FUNCTIONS_REQ"] = 0x00,
["LIST_FUNCTIONS_RESP"] = 0x10,
["RESTART_LAN"] = 0x01,
["LANCHECK_END_REQ"] = 0x08,
["LANCHECK_END_RESP"] = 0x18,
["LANCHECK_SCAN_REQ"] = 0x0a,
["LANCHECK_SCAN_RESP"] = 0x1a,
["LANCHECK_REQ"] = 0x0c,
["LANCHECK_RESP"] = 0x1c,
["PING_REQ"] = 0x20,
["PING_RESP"] = 0x30,
-- Device switching
["DISABLE_FUNCTION_REQ"] = 0x43,
["DISABLE_FUNCTION_RESP"] = 0x53,
["ENABLE_FUNCTION_REQ"] = 0x42,
["ENABLE_FUNCTION_RESP"] = 0x52,
["ADVERTISE_FUNCTION"] = 0x45,
["GENERAL_QUERY"] = 0x46,
-- Physical interface
["EJECT"] = 0x80,
["DISC_UP"] = 0x90,
["DISC_DOWN"] = 0x91,
["PWRVOL_KNOB_RIGHTHAND_TURN"] = 0x9c,
["PWRVOL_KNOB_LEFTHAND_TURN"] = 0x9d,
["TRACK_SEEK_UP"] = 0x94,
["TRACK_SEEK_DOWN"] = 0x95,
["CD_ENABLE_SCAN"] = 0xa6,
["CD_DISABLE_SCAN"] = 0xa7,
["CD_ENABLE_REPEAT"] = 0xa0,
["CD_DISABLE_REPEAT"] = 0xa1,
["CD_ENABLE_RANDOM"] = 0xb0,
["CD_DISABLE_RANDOM"] = 0xb1,
-- CD functions
-- Events
["INSERTED_CD"] = 0x50,
["REMOVED_CD"] = 0x51,
-- Requests
["REQUEST_REPORT"] = 0xe0,
["REQUEST_REPORT2"] = 0xe2,
["REQUEST_LOADER2"] = 0xe4,
["REQUEST_TRACK_NAME"] = 0xed,
-- Reports
["REPORT"] = 0xf1,
["REPORT2"] = 0xf2,
["REPORT_LOADER"] = 0xf3,
["REPORT_LOADER2"] = 0xf4,
["REPORT_TOC"] = 0xf9,
["REPORT_TRACK_NAME"] = 0xfd,
}
local known_actions = {
-- LAN related
[0x00] = "LIST_FUNCTIONS_REQ",
[0x10] = "LIST_FUNCTIONS_RESP",
[0x01] = "RESTART_LAN",
[0x08] = "LANCHECK_END_REQ",
[0x18] = "LANCHECK_END_RESP",
[0x0a] = "LANCHECK_SCAN_REQ",
[0x1a] = "LANCHECK_SCAN_RESP",
[0x0c] = "LANCHECK_REQ",
[0x1c] = "LANCHECK_RESP",
[0x20] = "PING_REQ",
[0x30] = "PING_RESP",
-- Used when HU is switching between Radio and CD
[0x43] = "DISABLE_FUNCTION_REQ",
[0x53] = "DISABLE_FUNCTION_RESP",
[0x42] = "ENABLE_FUNCTION_REQ",
[0x52] = "ENABLE_FUNCTION_RESP",
[0x45] = "ADVERTISE_FUNCTION",
[0x46] = "GENERAL_QUERY",
-- Physical interface
[0x80] = "EJECT",
[0x90] = "DISC_UP",
[0x91] = "DISC_DOWN",
[0x9c] = "PWRVOL_KNOB_RIGHTHAND_TURN",
[0x9d] = "PWRVOL_KNOB_LEFTHAND_TURN",
[0x94] = "TRACK_SEEK_UP",
[0x95] = "TRACK_SEEK_DOWN",
[0xa6] = "CD_ENABLE_SCAN",
[0xa7] = "CD_DISABLE_SCAN",
[0xa0] = "CD_ENABLE_REPEAT",
[0xa1] = "CD_DISABLE_REPEAT",
[0xb0] = "CD_ENABLE_RANDOM",
[0xb1] = "CD_DISABLE_RANDOM",
-- CD functions
-- Events
[0x50] = "INSERTED_CD",
[0x51] = "REMOVED_CD",
-- Requests
[0xe0] = "REQUEST_REPORT",
[0xe2] = "REQUEST_REPORT2",
[0xe4] = "REQUEST_LOADER2",
[0xed] = "REQUEST_TRACK_NAME",
-- Reports
[0xf1] = "REPORT",
[0xf2] = "REPORT2",
[0xf3] = "REPORT_LOADER",
[0xf4] = "REPORT_LOADER2",
[0xf9] = "REPORT_TOC",
[0xfd] = "REPORT_TRACK_NAME",
}
local f_action = ProtoField.uint8("avclan.action", "Action", base.HEX, known_actions)
local f_functions = ProtoField.bytes("avclan.functions", "Functions", base.SPACE, "Device functions")
local f_ping_count = ProtoField.uint8("avclan.ping.count", "Ping count")
local f_radio_active = ProtoField.bool("avclan.radio.active", "Radio", base.NONE, {"ON", "OFF"})
local f_radio_status = ProtoField.uint8("avclan.radio.status", "Radio status", base.HEX,
{
[0x00] = "OFF",
[0x01] = "READY",
[0x06] = "SCAN UP",
[0x07] = "SCAN DOWN",
[0x0A] = "AST SEARCH",
[0x27] = "MANUAL"
}
)
local f_radio_flags = ProtoField.uint8("avclan.radio.flags", "Radio flags")
local f_radio_flags2 = ProtoField.uint8("avclan.radio.flags2", "Radio flags (byte 2)")
local f_radioflag_st = ProtoField.bool("avclan.radio.flags.st", "Stereo", 8, {"Set", "Not set"}, 0x04)
local f_radioflag_ta = ProtoField.bool("avclan.radio.flags.ta", "TA", 8, {"Set", "Not set"}, 0x08)
local f_radioflag_reg = ProtoField.bool("avclan.radio.flags.reg", "REG", 8, {"Set", "Not set"}, 0x10)
local f_radioflag_af = ProtoField.bool("avclan.radio.flags.af", "AF", 8, {"Set", "Not set"}, 0x40)
local f_radio_band = ProtoField.uint8("avclan.radio.band", "Radio band", base.HEX,
{[0x8] = "FM", [0xC] = "AM (Long-wave)", [0x0] = "AM (Medium-wave)"}, 0xF0)
local f_radio_bandnumber = ProtoField.int8("avclan.radio.bandnumber", "Radio band number", base.DEC, nil, 0x0F)
local f_radio_freq = ProtoField.uint16("avclan.radio.freq", "Radio frequency")
local f_amp_volume = ProtoField.uint8("avclan.amp.volume", "Volume", base.DEC)
local f_amp_bass = ProtoField.uint8("avclan.amp.bass", "Bass", base.HEX, {
[0x0B] = "-5", [0x0C] = "-4", [0x0D] = "-3", [0x0E] = "-2", [0x0F] = "-1",
[0x10] = "0",
[0x11] = "+1", [0x12] = "+2", [0x13] = "+3", [0x14] = "+4", [0x15] = "+5"
})
local f_amp_mid = ProtoField.uint8("avclan.amp.mid", "Mid", base.HEX, {
[0x0B] = "-5", [0x0C] = "-4", [0x0D] = "-3", [0x0E] = "-2", [0x0F] = "-1",
[0x10] = "0",
[0x11] = "+1", [0x12] = "+2", [0x13] = "+3", [0x14] = "+4", [0x15] = "+5"
})
local f_amp_treble = ProtoField.uint8("avclan.amp.treble", "Treble", base.HEX, {
[0x0B] = "-5", [0x0C] = "-4", [0x0D] = "-3", [0x0E] = "-2", [0x0F] = "-1",
[0x10] = "0",
[0x11] = "+1", [0x12] = "+2", [0x13] = "+3", [0x14] = "+4", [0x15] = "+5"
})
local f_amp_fade = ProtoField.uint8("avclan.amp.fade", "Fade (forward/rear)", base.HEX, {
[0x09] = "-7", [0x0A] = "-6", [0x0B] = "-5", [0x0C] = "-4", [0x0D] = "-3", [0x0E] = "-2", [0x0F] = "-1",
[0x10] = "0",
[0x11] = "+1", [0x12] = "+2", [0x13] = "+3", [0x14] = "+4", [0x15] = "+5", [0x16] = "+6", [0x17] = "+7"
})
local f_amp_balance = ProtoField.uint8("avclan.amp.balance", "Balance (right/left)", base.HEX, {
[0x09] = "-7", [0x0A] = "-6", [0x0B] = "-5", [0x0C] = "-4", [0x0D] = "-3", [0x0E] = "-2", [0x0F] = "-1",
[0x10] = "0",
[0x11] = "+1", [0x12] = "+2", [0x13] = "+3", [0x14] = "+4", [0x15] = "+5", [0x16] = "+6", [0x17] = "+7"
})
local f_cd_slots = ProtoField.uint8("avclan.cd.slots", "CD player disc slots")
local f_cd_slot1 = ProtoField.bool("avclan.cd.slot1", "Slot 1", 6, {"Filled", "Empty"}, 0x01)
local f_cd_slot2 = ProtoField.bool("avclan.cd.slot1", "Slot 2", 6, {"Filled", "Empty"}, 0x02)
local f_cd_slot3 = ProtoField.bool("avclan.cd.slot1", "Slot 3", 6, {"Filled", "Empty"}, 0x04)
local f_cd_slot4 = ProtoField.bool("avclan.cd.slot1", "Slot 4", 6, {"Filled", "Empty"}, 0x08)
local f_cd_slot5 = ProtoField.bool("avclan.cd.slot1", "Slot 5", 6, {"Filled", "Empty"}, 0x10)
local f_cd_slot6 = ProtoField.bool("avclan.cd.slot1", "Slot 6", 6, {"Filled", "Empty"}, 0x20)
local f_cd_state = ProtoField.uint8("avclan.cd.state", "CD player state")
local f_cd_open = ProtoField.bool("avclan.cd.state.open", "OPEN", 8, nil, 0x01)
local f_cd_err1 = ProtoField.bool("avclan.cd.state.err1", "ERR1", 8, nil, 0x02)
local f_cd_seeking = ProtoField.bool("avclan.cd.state.seeking", "SEEKING", 8, nil, 0x08)
local f_cd_playback = ProtoField.bool("avclan.cd.state.playback", "PLAYBACK", 8, nil, 0x10)
local f_cd_seeking_track = ProtoField.bool("avclan.cd.state.seeking_track", "SEEKING_TRACK", 8, nil, 0x20)
local f_cd_loading = ProtoField.bool("avclan.cd.state.loading", "LOADING", 8, nil, 0x80)
local f_cd_disc = ProtoField.uint8("avclan.cd.disc", "Current disc", base.DEC)
local f_cd_track = ProtoField.uint8("avclan.cd.track", "Track number", base.HEX)
local f_cd_min = ProtoField.uint8("avclan.cd.mins", "CD track play time, minutes", base.HEX)
local f_cd_sec = ProtoField.uint8("avclan.cd.secs", "CD track play time, seconds", base.HEX)
local f_cd_flags = ProtoField.uint8("avclan.cd.flags", "CD player flags", base.HEX, {
[0x02] = "DISK_RANDOM",
[0x04] = "RANDOM",
[0x08] = "DISK_REPEAT",
[0x10] = "REPEAT",
[0x20] = "DISK_SCAN",
[0x40] = "SCAN",
})
avclanproto.fields = {
f_from_device,
f_to_device,
f_active_device,
f_action,
f_functions,
f_ping_count,
f_radio_active,
f_radio_status,
f_radio_flags,
f_radio_flags2,
f_radio_band,
f_radio_bandnumber,
f_radio_freq,
f_radioflag_af,
f_radioflag_reg,
f_radioflag_st,
f_radioflag_ta,
f_amp_volume,
f_amp_bass,
f_amp_mid,
f_amp_treble,
f_amp_fade,
f_amp_balance,
f_cd_slots,
f_cd_slot1,
f_cd_slot2,
f_cd_slot3,
f_cd_slot4,
f_cd_slot5,
f_cd_slot6,
f_cd_state,
f_cd_open,
f_cd_err1,
f_cd_seeking,
f_cd_playback,
f_cd_seeking_track,
f_cd_loading,
f_cd_disc,
f_cd_track,
f_cd_min,
f_cd_sec,
f_cd_flags,
}
local pe_unhandled_msg = ProtoExpert.new("avclan.expert", "Message not decoded",
expert.group.UNDECODED, expert.severity.WARN)
-- local pe_lan_check = ProtoExpert.new("avclan.lan.expert", "")
local pe_ping_req = ProtoExpert.new("avclan.ping_req.expert", "Ping request",
expert.group.SEQUENCE, expert.severity.CHAT)
local pe_ping_resp = ProtoExpert.new("avclan.ping_resp.expert", "Ping response",
expert.group.SEQUENCE, expert.severity.CHAT)
avclanproto.experts = {
pe_unhandled_msg,
pe_ping_req,
pe_ping_resp,
}
local field_from_device = Field.new("avclan.from_device")
local field_to_device = Field.new("avclan.to_device")
local field_action = Field.new("avclan.action")
local field_radio_freq = Field.new("avclan.radio.freq")
local field_radio_band = Field.new("avclan.radio.band")
local field_cd_disc = Field.new("avclan.cd.disc")
local field_cd_track = Field.new("avclan.cd.track")
local field_cd_min = Field.new("avclan.cd.mins")
local field_cd_sec = Field.new("avclan.cd.secs")
function avclanproto.dissector(buffer, pinfo, tree)
local length = buffer:len()
if length == 0 then
return
end
iebusproto.dissector(buffer, pinfo, tree)
local subtree = tree:add(avclanproto, buffer(7,-1), "AVCLAN message")
local offset = 7
if buffer(7,1):uint() == 0 then
offset = 8
subtree:add(f_from_device, buffer(offset+0,1))
subtree:add(f_to_device, buffer(offset+1,1))
else
subtree:add(f_from_device, buffer(offset+0,1))
subtree:add(f_to_device, buffer(offset+1,1))
end
local from_device = field_from_device().value
local to_device = field_to_device().value
if from_device == known_devices_names["CMD_SW"] then
subtree:add(f_action, buffer(offset+2,1))
elseif from_device == known_devices_names["COMMUNICATION v1"] or
from_device == known_devices_names["COMMUNICATION v2"] then
if to_device == known_devices_names["COMM_CTRL"] then
subtree:add(f_action, buffer(offset+2,1))
local action = field_action().value
if action == known_actions_names["ADVERTISE_FUNCTION"] then
subtree:add(f_active_device, buffer(offset+3,1))
elseif action == known_actions_names["PING_REQ"] then
subtree:add(f_ping_count, buffer(offset+3,1))
subtree:add_proto_expert_info(pe_ping_req, "Ping request " .. buffer(offset+3,1):uint())
elseif known_actions[action] then
else
subtree:add_proto_expert_info(pe_unhandled_msg)
end
else
subtree:add_proto_expert_info(pe_unhandled_msg)
end
elseif from_device == known_devices_names["COMM_CTRL"] then
if to_device == known_devices_names["COMMUNICATION v1"] or
to_device == known_devices_names["COMMUNICATION v2"] then
local action_tree = subtree:add(f_action, buffer(offset+2,1))
local action = field_action().value
if action == known_actions_names["PING_RESP"] then
subtree:add(f_ping_count, buffer(offset+3,1))
subtree:add_proto_expert_info(pe_ping_resp, "Ping response " .. buffer(offset+3,1):uint())
elseif action == known_actions_names["LIST_FUNCTIONS_RESP"] then
local functions = action_tree:add(f_functions, buffer(offset+3))
functions:append_text(" (")
for v = 0,(buffer:bytes(offset+3)):len()-1 do
if known_devices[buffer(offset+3+v,1):uint()] then
functions:append_text(" " .. known_devices[buffer(offset+3+v,1):uint()])
else
functions:append_text(" UNKNOWN_DEVICE")
end
end
functions:append_text(" ) ")
elseif known_actions[action] then
else
subtree:add_proto_expert_info(pe_unhandled_msg)
end
elseif to_device == known_actions_names["LANCHECK_SCAN_REQ"] or
to_device == known_actions_names["LANCHECK_REQ"] or
to_device == known_actions_names["LANCHECK_END_REQ"] then
subtree:add(f_action, buffer(offset+1,1))
elseif to_device == 0x00 then
subtree:add(f_action, buffer(offset+2,1))
else
subtree:add_proto_expert_info(pe_unhandled_msg)
end
elseif from_device == known_devices_names["STATUS"] then
subtree:add(f_action, buffer(offset+2,1))
elseif from_device == known_devices_names["TUNER"] then
subtree:add(f_action, buffer(offset+2,1))
local radiotree = subtree:add(avclanproto, buffer(offset,10), "Device: Radio")
radiotree:add_le(f_radio_active, buffer(offset+3,1))
radiotree:add_le(f_radio_status, buffer(offset+4,1))
radiotree:add_le(f_radio_band, buffer(offset+5,1))
radiotree:add(f_radio_bandnumber, buffer(offset+5,1))
local freqtree = radiotree:add(f_radio_freq, buffer(offset+6,2))
local radio_band = buffer(offset+5,1):uint()
local freq = field_radio_freq().value
if bit32.band(radio_band, 0xF0) == 0x80 then
freqtree:append_text(" (" .. 87.5+(freq-1)*.05 .. " MHz)")
elseif bit32.band(radio_band, 0xF0) == 0xC0 then
freqtree:append_text(" (" .. 153+(freq-1)*1 .. " kHz)")
elseif bit32.band(radio_band, 0xF0) == 0x00 then
freqtree:append_text(" (" .. 522+(freq-1)*9 .. " kHz)")
end
local flags = radiotree:add(f_radio_flags, buffer(15,1))
flags:add(f_radioflag_st, buffer(15,1))
flags:add(f_radioflag_ta, buffer(15,1))
flags:add(f_radioflag_reg, buffer(15,1))
flags:add(f_radioflag_af, buffer(15,1))
radiotree:add(f_radio_flags2, buffer(16,1))
elseif from_device == known_devices_names["AUDIO_AMP"] then
subtree:add(f_action, buffer(offset+2,1))
local amptree = subtree:add(avclanproto, buffer(offset,10), "Device: Audio amplifier")
amptree:add(f_amp_volume, buffer(offset+4,1))
amptree:add(f_amp_balance, buffer(offset+5,1))
amptree:add(f_amp_fade, buffer(offset+6,1))
amptree:add(f_amp_bass, buffer(offset+7,1))
amptree:add(f_amp_mid, buffer(offset+8,1))
amptree:add(f_amp_treble, buffer(offset+9,1))
elseif from_device == known_devices_names["CD"] or
from_device == known_devices_names["CD_CHANGER"] or
from_device == known_devices_names["CD_CHANGER2"] then
subtree:add(f_action, buffer(offset+2,1))
local action = field_action().value
if action == known_actions_names["REPORT"] then
local cdtree = subtree:add(avclanproto, buffer(offset,9), "Device: CD player")
local cd_slots = cdtree:add(f_cd_slots, buffer(offset+3,1))
cd_slots:add(f_cd_slot1, buffer(offset+3,1))
cd_slots:add(f_cd_slot2, buffer(offset+3,1))
cd_slots:add(f_cd_slot3, buffer(offset+3,1))
cd_slots:add(f_cd_slot4, buffer(offset+3,1))
cd_slots:add(f_cd_slot5, buffer(offset+3,1))
cd_slots:add(f_cd_slot6, buffer(offset+3,1))
local cd_state = cdtree:add(f_cd_state, buffer(offset+4,1))
cd_state:add(f_cd_open, buffer(offset+4,1))
cd_state:add(f_cd_err1, buffer(offset+4,1))
cd_state:add(f_cd_seeking, buffer(offset+4,1))
cd_state:add(f_cd_playback, buffer(offset+4,1))
cd_state:add(f_cd_seeking_track, buffer(offset+4,1))
cd_state:add(f_cd_loading, buffer(offset+4,1))
local cd_status = cdtree:add(avclanproto, buffer(offset+5,-1), "")
cd_status:add(f_cd_disc, buffer(offset+5,1))
cd_status:add(f_cd_track, buffer(offset+6,1))
cd_status:add(f_cd_min, buffer(offset+7,1))
cd_status:add(f_cd_sec, buffer(offset+8,1))
cd_status:append_text("Disc " .. field_cd_disc().value .. ", ")
cd_status:append_text("track " .. tostring(buffer(offset+6,1)):gsub("(.)(.)", "%1%2") .. ", ")
cd_status:append_text("time " .. tostring(buffer(offset+7,1)):gsub("0x(.)(.)", "%1%2") .. ":")
cd_status:append_text(tostring(buffer(offset+8,1)):gsub("(.)(.)", "%1%2"))
cdtree:add(f_cd_flags, buffer(offset+9,1))
end
else
subtree:add_proto_expert_info(pe_unhandled_msg)
end
end
-- for i,v in ipairs(DissectorTable.list()) do print(v) end
udlt:add(wtap.USER15, avclanproto)

View File

@ -0,0 +1,19 @@
Copyright (c) 2020 Christian Rorvik <christian.rorvik@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,21 @@
name = "PcapTools"
uuid = "222fe7e8-3f39-464a-bf97-d9bbb753f246"
authors = ["Christian Rorvik <christian.rorvik@gmail.com>"]
version = "1.1.1"
[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Mmap = "a63ad114-7e13-5084-954f-fe012c677804"
UnixTimes = "ab1a18e7-b408-4913-896c-624bb82ed7f4"
UnsafeArrays = "c4a57d5a-5b31-53a6-b365-19f8c011fbd6"
[compat]
UnixTimes = "1"
UnsafeArrays = "1"
julia = "1.4"
[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets]
test = ["Test"]

View File

@ -0,0 +1,5 @@
# PcapTools
[![Build Status](https://travis-ci.com/ancapdev/PcapTools.jl.svg?branch=master)](https://travis-ci.com/ancapdev/PcapTools.jl)
[![Coverage Status](https://coveralls.io/repos/github/ancapdev/PcapTools.jl/badge.svg)](https://coveralls.io/github/ancapdev/PcapTools.jl)

View File

@ -0,0 +1,26 @@
module PcapTools
using Dates
using Mmap
using UnixTimes
using UnsafeArrays
export PcapHeader, RecordHeader
export PcapRecord
export PcapReader, PcapStreamReader, PcapBufferReader
export PcapWriter, PcapStreamWriter
export LINKTYPE_NULL, LINKTYPE_ETHERNET
export splitcap
abstract type PcapReader end
abstract type PcapWriter end
include("pcap_header.jl")
include("record_header.jl")
include("record.jl")
include("buffer_reader.jl")
include("stream_reader.jl")
include("stream_writer.jl")
include("splitcap.jl")
end

View File

@ -0,0 +1,96 @@
"""
Reads pcap data from an array of bytes.
"""
mutable struct PcapBufferReader <: PcapReader
data::Vector{UInt8}
raw_header::Vector{UInt8}
header::PcapHeader
offset::Int64
mark::Int64
usec_mul::Int64
bswapped::Bool
@doc """
PcapBufferReader(data::Vector{UInt8})
Create reader over `data`. Will read and process pcap header,
and yield records through `read(::PcapBufferReader)`.
"""
function PcapBufferReader(data::Vector{UInt8})
length(data) < sizeof(PcapHeader) && throw(EOFError())
rh = data[1:sizeof(PcapHeader)]
h = unsafe_load(Ptr{PcapHeader}(pointer(data)))
h, bswapped, nanotime = process_header(h)
new(data, rh, h, sizeof(h), -1, nanotime ? 1 : 1000, bswapped)
end
end
"""
PcapBufferReader(path::AbstractString)
Memory map file in `path` and create PcapBufferReader over its content.
"""
function PcapBufferReader(path::AbstractString)
io = open(path)
data = Mmap.mmap(io)
PcapBufferReader(data)
end
function Base.close(x::PcapBufferReader)
x.data = UInt8[]
x.offset = 0
nothing
end
Base.length(x::PcapBufferReader) = length(x.data)
Base.position(x::PcapBufferReader) = x.offset; nothing
Base.seek(x::PcapBufferReader, pos) = x.offset = pos; nothing
function Base.mark(x::PcapBufferReader)
x.mark = x.offset
x.mark
end
function Base.unmark(x::PcapBufferReader)
if x.mark >= 0
x.mark = -1
true
else
false
end
end
Base.ismarked(x::PcapBufferReader) = x.mark >= 0
function Base.reset(x::PcapBufferReader)
!ismarked(x) && error("PcapBufferReader not marked")
x.offset = x.mark
x.mark = -1
x.offset
end
Base.eof(x::PcapBufferReader) = (length(x) - x.offset) < sizeof(RecordHeader)
"""
read(x::PcapBufferReader) -> PcapRecord
Read one record from pcap data.
Throws `EOFError` if no more data available.
"""
@inline function Base.read(x::PcapBufferReader)
eof(x) && throw(EOFError())
record_offset = x.offset
p = pointer(x.data) + record_offset
GC.@preserve x begin
h = unsafe_load(Ptr{RecordHeader}(p))
end
if x.bswapped
h = bswap(h)
end
t1 = (h.ts_sec + x.header.thiszone) * 1_000_000_000
t2 = Int64(h.ts_usec) * x.usec_mul
t = UnixTime(Dates.UTInstant(Nanosecond(t1 + t2)))
x.offset += sizeof(RecordHeader) + h.incl_len
x.offset > length(x) && error("Insufficient data in pcap record")
PcapRecord(h, t, x.data, record_offset)
end

View File

@ -0,0 +1,46 @@
# NOTE: Not using @enum because it craps out when displaying unknown values
const LINKTYPE_NULL = UInt32(0)
const LINKTYPE_ETHERNET = UInt32(1)
struct PcapHeader
magic::UInt32
version_major::UInt16
version_minor::UInt16
thiszone::Int32
sigfigs::UInt32
snaplen::UInt32
linktype::UInt32
end
function Base.bswap(x::PcapHeader)
PcapHeader(
bswap(x.magic),
bswap(x.version_major),
bswap(x.version_minor),
bswap(x.thiszone),
bswap(x.sigfigs),
bswap(x.snaplen),
bswap(x.linktype))
end
function process_header(x::PcapHeader)
if x.magic == 0xa1b2c3d4
bswapped = false
nanotime = false
elseif x.magic == 0xd4c3b2a1
bswapped = true
nanotime = false
elseif x.magic == 0xa1b23c4d
bswapped = false
nanotime = true
elseif x.magic == 0x4d3cb2a1
bswapped = true
nanotime = true
else
throw(ArgumentError("Invalid pcap header"))
end
if bswapped
x = bswap(x)
end
x, bswapped, nanotime
end

View File

@ -0,0 +1,25 @@
"""
Record of pcap data
"""
struct PcapRecord
header::RecordHeader
timestamp::UnixTime
underlying_data::Vector{UInt8}
record_offset::Int
end
@inline function record_field_(x::PcapRecord, ::Val{:data})
offset = getfield(x, :record_offset) + sizeof(RecordHeader)
len = Int(getfield(x, :header).incl_len)
UnsafeArray{UInt8, 1}(pointer(getfield(x, :underlying_data)) + offset, (len,))
end
@inline function record_field_(x::PcapRecord, ::Val{:raw})
offset = getfield(x, :record_offset)
len = sizeof(RecordHeader) + getfield(x, :header).incl_len
UnsafeArray{UInt8, 1}(pointer(getfield(x, :underlying_data)) + offset, (len,))
end
@inline record_field_(x::PcapRecord, ::Val{f}) where {f} = getfield(x, f)
@inline Base.getproperty(x::PcapRecord, f::Symbol) = record_field_(x, Val(f))

View File

@ -0,0 +1,14 @@
struct RecordHeader
ts_sec::UInt32
ts_usec::UInt32
incl_len::UInt32
orig_len::UInt32
end
function Base.bswap(x::RecordHeader)
RecordHeader(
bswap(x.ts_sec),
bswap(x.ts_usec),
bswap(x.incl_len),
bswap(x.orig_len))
end

View File

@ -0,0 +1,102 @@
strip_nothing_(::Type{Union{Nothing, T}}) where T = T
strip_nothing_(::Type{T}) where T = T
progress_noop_(n) = nothing
mutable struct SplitCapOutput{S}
work_buffer::Vector{UInt8}
complete_buffers::Channel{Vector{UInt8}}
stream::S
end
# Since Julia (as of 1.5) doesn't support task migration,
# continue with new task after each buffer write, to rebalance across threads
function write_one_and_continue_(output::SplitCapOutput, free_buffers::Channel, pending::Threads.Atomic{Int})
i = iterate(output.complete_buffers)
if i === nothing
Threads.atomic_sub!(pending, 1)
return nothing
end
b, _ = i
write(output.stream, b)
empty!(b)
put!(free_buffers, b)
Threads.@spawn write_one_and_continue_($output, $free_buffers, $pending)
end
function splitcap(
::Type{KeyType},
::Type{StreamType},
reader::PcapReader,
record2key,
key2stream,
progress_callback = progress_noop_;
own_streams::Bool = true
) where {KeyType, StreamType}
buffer_size = 1024 * 1024 * 2
max_pending_buffers = 4
outputs = Dict{KeyType, SplitCapOutput{StreamType}}()
free_buffers = Channel{Vector{UInt8}}(Inf)
n = 0
pending = Threads.Atomic{Int}(0)
try
while !eof(reader)
record = read(reader)
dst = record2key(record)
if dst isa KeyType
output = get!(outputs, dst) do
stream = key2stream(dst)
buffer = sizehint!(UInt8[], buffer_size + 1500)
own_streams && append!(buffer, reader.raw_header)
output = SplitCapOutput{StreamType}(
buffer,
Channel{Vector{UInt8}}(max_pending_buffers),
stream)
Threads.atomic_add!(pending, 1)
Threads.@spawn write_one_and_continue_($output, $free_buffers, $pending)
output
end
append!(output.work_buffer, record.raw)
if length(output.work_buffer) >= buffer_size
put!(output.complete_buffers, output.work_buffer)
if isready(free_buffers)
output.work_buffer = take!(free_buffers)
else
output.work_buffer = sizehint!(UInt8[], buffer_size + 1500)
end
end
end
n += 1
progress_callback(n)
GC.safepoint()
end
for output in values(outputs)
if !isempty(output.work_buffer)
put!(output.complete_buffers, output.work_buffer)
end
close(output.complete_buffers)
end
while pending[] != 0
sleep(0.1)
end
finally
if own_streams
for output in values(outputs)
close(output.stream)
end
end
end
nothing
end
function splitcap(
reader::PcapReader,
record2key,
key2stream,
progress_callback = progress_noop_;
kwargs...
)
KeyType = strip_nothing_(Core.Compiler.return_type(record2key, Tuple{PcapRecord}))
StreamType = Core.Compiler.return_type(key2stream, Tuple{KeyType})
splitcap(KeyType, StreamType, reader, record2key, key2stream, progress_callback; kwargs...)
end

View File

@ -0,0 +1,63 @@
"""
Reads pcap data from a stream.
"""
mutable struct PcapStreamReader{Src <: IO} <: PcapReader
src::Src
raw_header::Vector{UInt8}
header::PcapHeader
usec_mul::Int64
bswapped::Bool
record_buffer::Vector{UInt8}
@doc """
PcapStreamReader(src::IO)
Create reader over `src`. Will read and process pcap header,
and yield records through `read(::PcapStreamReader)`.
"""
function PcapStreamReader(src::Src) where {Src <: IO}
raw_header = read(src, sizeof(PcapHeader))
length(raw_header) != sizeof(PcapHeader) && throw(EOFError())
h = GC.@preserve raw_header unsafe_load(Ptr{PcapHeader}(pointer(raw_header)))
header, bswapped, nanotime = process_header(h)
new{Src}(src, raw_header, header, nanotime ? 1 : 1000, bswapped, zeros(UInt8, 9000 + sizeof(RecordHeader)))
end
end
"""
PcapStreamReader(path)
Open file at `path` and create PcapStreamReader over its content.
"""
PcapStreamReader(path::AbstractString) = PcapStreamReader(open(path))
Base.close(x::PcapStreamReader) = close(x.src)
Base.position(x::PcapStreamReader) = position(x.src)
Base.seek(x::PcapStreamReader, pos) = seek(x.src, pos)
Base.mark(x::PcapStreamReader) = mark(x.src)
Base.unmark(x::PcapStreamReader) = unmark(x.src)
Base.ismarked(x::PcapStreamReader) = ismarked(x.src)
Base.reset(x::PcapStreamReader) = reset(x.src)
Base.eof(x::PcapStreamReader) = eof(x.src)
"""
read(x::PcapStreamReader) -> PcapRecord
Read one record from pcap data. Record is valid until next read().
Throws `EOFError` if no more data available.
"""
function Base.read(x::PcapStreamReader)
p = pointer(x.record_buffer)
GC.@preserve x begin
unsafe_read(x.src, p, sizeof(RecordHeader))
h = unsafe_load(Ptr{RecordHeader}(p))
if x.bswapped
h = bswap(h)
end
unsafe_read(x.src, p + sizeof(RecordHeader), h.incl_len)
end
t1 = (h.ts_sec + x.header.thiszone) * 1_000_000_000
t2 = Int64(h.ts_usec) * x.usec_mul
t = UnixTime(Dates.UTInstant(Nanosecond(t1 + t2)))
PcapRecord(h, t, x.record_buffer, 0)
end

View File

@ -0,0 +1,30 @@
struct PcapStreamWriter{Dst <: IO} <: PcapWriter
dst::Dst
function PcapStreamWriter{Dst}(dst::Dst; thiszone = 0, snaplen = 65535, linktype = LINKTYPE_ETHERNET) where {Dst <: IO}
h = PcapHeader(
0xa1b23c4d,
0x0002,
0x0004,
thiszone,
0,
snaplen,
linktype)
write(dst, reinterpret(UInt8, [h]))
new(dst)
end
end
PcapStreamWriter(io::IO; kwargs...) = PcapStreamWriter{typeof(io)}(io; kwargs...)
PcapStreamWriter(path::AbstractString; kwargs...) = PcapStreamWriter(open(path, "w"); kwargs...)
Base.close(x::PcapStreamWriter) = close(x.dst)
function Base.write(x::PcapStreamWriter, timestamp::UnixTime, data)
sec, nsec = fldmod(Dates.value(timestamp), 1_000_000_000)
data_length = length(data)
h = RecordHeader(sec, nsec, data_length, data_length)
write(x.dst, reinterpret(UInt8, [h]))
write(x.dst, collect(data))
nothing
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,73 @@
module AVCLANPipe
using PcapTools, Dates, UnixTimes
export AVCLANframe, avclan_text_to_pcap, tobytes
mutable struct AVCLANframe
broadcast::Bool
controller_addr::UInt16
peripheral_addr::UInt16
control::UInt8
length::UInt8
data::NTuple{32,UInt8}
end
AVCLANframe() = AVCLANframe(false, 0x0000, 0x0000, 0xf, 0x0, ntuple(x -> 0x0, 32))
function Base.tryparse(::Type{AVCLANframe}, str::String)
vals = split(str)
broadcast = tryparse(Bool, vals[1])
isnothing(broadcast) && return nothing
controller_addr = tryparse(UInt16, vals[2])
isnothing(controller_addr) && return nothing
peripheral_addr = tryparse(UInt16, vals[3])
isnothing(peripheral_addr) && return nothing
control = tryparse(UInt8, vals[4])
isnothing(control) && return nothing
len = tryparse(UInt8, vals[5])
isnothing(len) && return nothing
if (length(vals) - 5) != len || len > 32
return nothing
end
_data = tryparse.(UInt8, vals[6:end])
data = ntuple(i -> checkindex(Bool, axes(_data, 1), i) ? _data[i] : 0x0, 32)
return AVCLANframe(broadcast, controller_addr, peripheral_addr, control, len, data)
end
function tobytes(frame::AVCLANframe)
data = Vector{UInt8}(undef, 0)
push!(data, frame.broadcast)
append!(data, reverse(reinterpret(reshape, UInt8, [frame.controller_addr])),
reverse(reinterpret(reshape, UInt8, [frame.peripheral_addr])),
reinterpret(reshape, UInt8, [frame.control]),
reinterpret(reshape, UInt8, [frame.length]),
frame.data[1:frame.length])
return data
end
function avclan_text_to_pcap(textlog::String, pcap_fn::String)
pcapstream = PcapStreamWriter(pcap_fn; snaplen=64, linktype = 162)
t = UnixTime(now())
for line in eachline(textlog)
frame = tryparse(AVCLANframe, line)
t += Microsecond(rand(1:15))
if !isnothing(frame)
write(pcapstream, t, tobytes(frame))
end
end
close(pcapstream)
return pcap_fn
end
end # module AVCLANPipe

View File

@ -0,0 +1,55 @@
using AVCLANPipe, PcapTools, Dates, UnixTimes
file = ARGS[1]
outfile = ARGS[2]
function convert_fields_to_pcap(file, outfile)
io = open(file, "r")
outio = PcapStreamWriter(outfile; snaplen=64, linktype = 162)
packet = AVCLANframe()
packetcomplete = false
local t::UnixTime
i = 1
for line in readlines(io)
m = match(r"(\d+)-(\d+) IEBus: Raw Fields: (?<field>\w+)(:? ((?<hex>0x[0-9a-f]{2,3})|WRITE_DATA|Length: (?<length>\d+)))?",
line)
if m[:field] == "Broadcast"
packet.broadcast = true
t = UnixTime(1970,1,1,0,0,0,0, something(tryparse(Int, m[1]), 0))
elseif m[:field] == "Unicast"
packet.broadcast = false
t = UnixTime(1970,1,1,0,0,0,0, something(tryparse(Int, m[1]), 0))
elseif m[:field] == "Master"
packet.controller_addr = tryparse(UInt16, m[:hex])
elseif m[:field] == "Slave"
packet.peripheral_addr = tryparse(UInt16, m[:hex])
elseif m[:field] == "Data"
if !isnothing(m[:length])
packet.length = convert(UInt8, tryparse(Int, m[:length]))
else
data = collect(packet.data)
data[i] = tryparse(UInt8, m[:hex])
packet.data = ntuple(n -> data[n], 32)
i += 1
end
end
if i > packet.length
packetcomplete = true
end
if packetcomplete
packetcomplete = false
i = 1
write(outio, t, tobytes(packet))
end
end
close(io)
close(outio)
end

View File

@ -0,0 +1,45 @@
include("AVCLANPipe.jl")
using .AVCLANPipe, PcapTools, Dates, UnixTimes, LibSerialPort
serial_port="/dev/ttyUSB0"
baud=1200000
pcapstream = PcapStreamWriter(stdout; snaplen=64, linktype=162)
serial = LibSerialPort.open(serial_port, baud)
set_flow_control(serial) # Disable flow-control (stops it from eating raw byte 0x11)
write(serial, 'X')
buf = IOBuffer()
function quit(serialio=serial)
close(serialio)
exit()
end
while true
iswritable(stdout) || isopen(stdout) || quit()
if bytesavailable(serial) > 0
t = UnixTime(now())
line = readline(serial)
# allthebytes = Vector{UInt8}(undef, bytesavailable(serial))
# readbytes!(serial, allthebytes)
# write(buf, allthebytes)
# l = findfirst(==('\n'), buf.data)
# if !isnothing(l)
# println(stderr, "fucku")
# bytes = Vector{UInt8}(undef, l)
# seekstart(buf)
# readbytes!(buf, bytes)
# seekend(buf)
bytes = Vector{UInt8}(line)
if bytes[1] == 0x10 && bytes[end] == 0x17
write(pcapstream, t, bytes[2:end-1])
else
@error line
end
# end
# println(stderr, "wtf")
end
yield()
end