Merge pull request #13379 from Atlante45/feat/audio-dissector

Add audio dissector
This commit is contained in:
Clément Brisset 2018-06-15 15:25:58 -07:00 committed by GitHub
commit 0f83230a0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 365 additions and 309 deletions

View file

@ -1,309 +1,315 @@
print("Loading hfudt") print("Loading hfudt")
-- create the HFUDT protocol -- create the HFUDT protocol
p_hfudt = Proto("hfudt", "HFUDT Protocol") p_hfudt = Proto("hfudt", "HFUDT Protocol")
-- create fields shared between packets in HFUDT -- create fields shared between packets in HFUDT
local f_data = ProtoField.string("hfudt.data", "Data") local f_data = ProtoField.string("hfudt.data", "Data")
-- create the fields for data packets in HFUDT -- create the fields for data packets in HFUDT
local f_length = ProtoField.uint16("hfudt.length", "Length", base.DEC) local f_length = ProtoField.uint16("hfudt.length", "Length", base.DEC)
local f_control_bit = ProtoField.uint8("hfudt.control", "Control Bit", base.DEC) local f_control_bit = ProtoField.uint8("hfudt.control", "Control Bit", base.DEC)
local f_reliable_bit = ProtoField.uint8("hfudt.reliable", "Reliability Bit", base.DEC) local f_reliable_bit = ProtoField.uint8("hfudt.reliable", "Reliability Bit", base.DEC)
local f_message_bit = ProtoField.uint8("hfudt.message", "Message Bit", base.DEC) local f_message_bit = ProtoField.uint8("hfudt.message", "Message Bit", base.DEC)
local f_obfuscation_level = ProtoField.uint8("hfudt.obfuscation_level", "Obfuscation Level", base.DEC) local f_obfuscation_level = ProtoField.uint8("hfudt.obfuscation_level", "Obfuscation Level", base.DEC)
local f_sequence_number = ProtoField.uint32("hfudt.sequence_number", "Sequence Number", base.DEC) local f_sequence_number = ProtoField.uint32("hfudt.sequence_number", "Sequence Number", base.DEC)
local f_message_position = ProtoField.uint8("hfudt.message_position", "Message Position", base.DEC) local f_message_position = ProtoField.uint8("hfudt.message_position", "Message Position", base.DEC)
local f_message_number = ProtoField.uint32("hfudt.message_number", "Message Number", base.DEC) local f_message_number = ProtoField.uint32("hfudt.message_number", "Message Number", base.DEC)
local f_message_part_number = ProtoField.uint32("hfudt.message_part_number", "Message Part Number", base.DEC) local f_message_part_number = ProtoField.uint32("hfudt.message_part_number", "Message Part Number", base.DEC)
local f_type = ProtoField.uint8("hfudt.type", "Type", base.DEC) local f_type = ProtoField.uint8("hfudt.type", "Type", base.DEC)
local f_version = ProtoField.uint8("hfudt.version", "Version", base.DEC) local f_version = ProtoField.uint8("hfudt.version", "Version", base.DEC)
local f_type_text = ProtoField.string("hfudt.type_text", "TypeText") local f_type_text = ProtoField.string("hfudt.type_text", "TypeText")
local f_sender_id = ProtoField.uint16("hfudt.sender_id", "Sender ID", base.DEC) local f_sender_id = ProtoField.uint16("hfudt.sender_id", "Sender ID", base.DEC)
local f_hmac_hash = ProtoField.bytes("hfudt.hmac_hash", "HMAC Hash") local f_hmac_hash = ProtoField.bytes("hfudt.hmac_hash", "HMAC Hash")
-- create the fields for control packets in HFUDT -- create the fields for control packets in HFUDT
local f_control_type = ProtoField.uint16("hfudt.control_type", "Control Type", base.DEC) local f_control_type = ProtoField.uint16("hfudt.control_type", "Control Type", base.DEC)
local f_control_type_text = ProtoField.string("hfudt.control_type_text", "Control Type Text", base.ASCII) local f_control_type_text = ProtoField.string("hfudt.control_type_text", "Control Type Text", base.ASCII)
local f_ack_sequence_number = ProtoField.uint32("hfudt.ack_sequence_number", "ACKed Sequence Number", base.DEC) local f_ack_sequence_number = ProtoField.uint32("hfudt.ack_sequence_number", "ACKed Sequence Number", base.DEC)
local f_control_sub_sequence = ProtoField.uint32("hfudt.control_sub_sequence", "Control Sub-Sequence Number", base.DEC) local f_control_sub_sequence = ProtoField.uint32("hfudt.control_sub_sequence", "Control Sub-Sequence Number", base.DEC)
local f_nak_sequence_number = ProtoField.uint32("hfudt.nak_sequence_number", "NAKed Sequence Number", base.DEC) local f_nak_sequence_number = ProtoField.uint32("hfudt.nak_sequence_number", "NAKed Sequence Number", base.DEC)
local f_nak_range_end = ProtoField.uint32("hfudt.nak_range_end", "NAK Range End", base.DEC) local f_nak_range_end = ProtoField.uint32("hfudt.nak_range_end", "NAK Range End", base.DEC)
local SEQUENCE_NUMBER_MASK = 0x07FFFFFF local SEQUENCE_NUMBER_MASK = 0x07FFFFFF
p_hfudt.fields = { p_hfudt.fields = {
f_length, f_length,
f_control_bit, f_reliable_bit, f_message_bit, f_sequence_number, f_type, f_type_text, f_version, f_control_bit, f_reliable_bit, f_message_bit, f_sequence_number, f_type, f_type_text, f_version,
f_sender_id, f_hmac_hash, f_sender_id, f_hmac_hash,
f_message_position, f_message_number, f_message_part_number, f_obfuscation_level, f_message_position, f_message_number, f_message_part_number, f_obfuscation_level,
f_control_type, f_control_type_text, f_control_sub_sequence, f_ack_sequence_number, f_nak_sequence_number, f_nak_range_end, f_control_type, f_control_type_text, f_control_sub_sequence, f_ack_sequence_number, f_nak_sequence_number, f_nak_range_end,
f_data f_data
} }
local control_types = { local control_types = {
[0] = { "ACK", "Acknowledgement" }, [0] = { "ACK", "Acknowledgement" },
[1] = { "ACK2", "Acknowledgement of acknowledgement" }, [1] = { "ACK2", "Acknowledgement of acknowledgement" },
[2] = { "LightACK", "Light Acknowledgement" }, [2] = { "LightACK", "Light Acknowledgement" },
[3] = { "NAK", "Loss report (NAK)" }, [3] = { "NAK", "Loss report (NAK)" },
[4] = { "TimeoutNAK", "Loss report re-transmission (TimeoutNAK)" }, [4] = { "TimeoutNAK", "Loss report re-transmission (TimeoutNAK)" },
[5] = { "Handshake", "Handshake" }, [5] = { "Handshake", "Handshake" },
[6] = { "HandshakeACK", "Acknowledgement of Handshake" }, [6] = { "HandshakeACK", "Acknowledgement of Handshake" },
[7] = { "ProbeTail", "Probe tail" }, [7] = { "ProbeTail", "Probe tail" },
[8] = { "HandshakeRequest", "Request a Handshake" } [8] = { "HandshakeRequest", "Request a Handshake" }
} }
local message_positions = { local message_positions = {
[0] = "ONLY", [0] = "ONLY",
[1] = "LAST", [1] = "LAST",
[2] = "FIRST", [2] = "FIRST",
[3] = "MIDDLE" [3] = "MIDDLE"
} }
local packet_types = { local packet_types = {
[0] = "Unknown", [0] = "Unknown",
[1] = "StunResponse", [1] = "StunResponse",
[2] = "DomainList", [2] = "DomainList",
[3] = "Ping", [3] = "Ping",
[4] = "PingReply", [4] = "PingReply",
[5] = "KillAvatar", [5] = "KillAvatar",
[6] = "AvatarData", [6] = "AvatarData",
[7] = "InjectAudio", [7] = "InjectAudio",
[8] = "MixedAudio", [8] = "MixedAudio",
[9] = "MicrophoneAudioNoEcho", [9] = "MicrophoneAudioNoEcho",
[10] = "MicrophoneAudioWithEcho", [10] = "MicrophoneAudioWithEcho",
[11] = "BulkAvatarData", [11] = "BulkAvatarData",
[12] = "SilentAudioFrame", [12] = "SilentAudioFrame",
[13] = "DomainListRequest", [13] = "DomainListRequest",
[14] = "RequestAssignment", [14] = "RequestAssignment",
[15] = "CreateAssignment", [15] = "CreateAssignment",
[16] = "DomainConnectionDenied", [16] = "DomainConnectionDenied",
[17] = "MuteEnvironment", [17] = "MuteEnvironment",
[18] = "AudioStreamStats", [18] = "AudioStreamStats",
[19] = "DomainServerPathQuery", [19] = "DomainServerPathQuery",
[20] = "DomainServerPathResponse", [20] = "DomainServerPathResponse",
[21] = "DomainServerAddedNode", [21] = "DomainServerAddedNode",
[22] = "ICEServerPeerInformation", [22] = "ICEServerPeerInformation",
[23] = "ICEServerQuery", [23] = "ICEServerQuery",
[24] = "OctreeStats", [24] = "OctreeStats",
[25] = "Jurisdiction", [25] = "Jurisdiction",
[26] = "JurisdictionRequest", [26] = "JurisdictionRequest",
[27] = "AssignmentClientStatus", [27] = "AssignmentClientStatus",
[28] = "NoisyMute", [28] = "NoisyMute",
[29] = "AvatarIdentity", [29] = "AvatarIdentity",
[30] = "AvatarBillboard", [30] = "AvatarBillboard",
[31] = "DomainConnectRequest", [31] = "DomainConnectRequest",
[32] = "DomainServerRequireDTLS", [32] = "DomainServerRequireDTLS",
[33] = "NodeJsonStats", [33] = "NodeJsonStats",
[34] = "OctreeDataNack", [34] = "OctreeDataNack",
[35] = "StopNode", [35] = "StopNode",
[36] = "AudioEnvironment", [36] = "AudioEnvironment",
[37] = "EntityEditNack", [37] = "EntityEditNack",
[38] = "ICEServerHeartbeat", [38] = "ICEServerHeartbeat",
[39] = "ICEPing", [39] = "ICEPing",
[40] = "ICEPingReply", [40] = "ICEPingReply",
[41] = "EntityData", [41] = "EntityData",
[42] = "EntityQuery", [42] = "EntityQuery",
[43] = "EntityAdd", [43] = "EntityAdd",
[44] = "EntityErase", [44] = "EntityErase",
[45] = "EntityEdit", [45] = "EntityEdit",
[46] = "DomainServerConnectionToken", [46] = "DomainServerConnectionToken",
[47] = "DomainSettingsRequest", [47] = "DomainSettingsRequest",
[48] = "DomainSettings", [48] = "DomainSettings",
[49] = "AssetGet", [49] = "AssetGet",
[50] = "AssetGetReply", [50] = "AssetGetReply",
[51] = "AssetUpload", [51] = "AssetUpload",
[52] = "AssetUploadReply", [52] = "AssetUploadReply",
[53] = "AssetGetInfo", [53] = "AssetGetInfo",
[54] = "AssetGetInfoReply" [54] = "AssetGetInfoReply"
} }
local unsourced_packet_types = { local unsourced_packet_types = {
["DomainList"] = true ["DomainList"] = true
} }
function p_hfudt.dissector(buf, pinfo, tree) function p_hfudt.dissector(buf, pinfo, tree)
-- make sure this isn't a STUN packet - those don't follow HFUDT format -- make sure this isn't a STUN packet - those don't follow HFUDT format
if pinfo.dst == Address.ip("stun.highfidelity.io") then return end if pinfo.dst == Address.ip("stun.highfidelity.io") then return end
-- validate that the packet length is at least the minimum control packet size -- validate that the packet length is at least the minimum control packet size
if buf:len() < 4 then return end if buf:len() < 4 then return end
-- create a subtree for HFUDT -- create a subtree for HFUDT
subtree = tree:add(p_hfudt, buf(0)) subtree = tree:add(p_hfudt, buf(0))
-- set the packet length -- set the packet length
subtree:add(f_length, buf:len()) subtree:add(f_length, buf:len())
-- pull out the entire first word -- pull out the entire first word
local first_word = buf(0, 4):le_uint() local first_word = buf(0, 4):le_uint()
-- pull out the control bit and add it to the subtree -- pull out the control bit and add it to the subtree
local control_bit = bit32.rshift(first_word, 31) local control_bit = bit32.rshift(first_word, 31)
subtree:add(f_control_bit, control_bit) subtree:add(f_control_bit, control_bit)
local data_length = 0 local data_length = 0
if control_bit == 1 then if control_bit == 1 then
-- dissect the control packet -- dissect the control packet
pinfo.cols.protocol = p_hfudt.name .. " Control" pinfo.cols.protocol = p_hfudt.name .. " Control"
-- remove the control bit and shift to the right to get the type value -- remove the control bit and shift to the right to get the type value
local shifted_type = bit32.rshift(bit32.lshift(first_word, 1), 17) local shifted_type = bit32.rshift(bit32.lshift(first_word, 1), 17)
local type = subtree:add(f_control_type, shifted_type) local type = subtree:add(f_control_type, shifted_type)
if control_types[shifted_type] ~= nil then if control_types[shifted_type] ~= nil then
-- if we know this type then add the name -- if we know this type then add the name
type:append_text(" (".. control_types[shifted_type][1] .. ")") type:append_text(" (".. control_types[shifted_type][1] .. ")")
subtree:add(f_control_type_text, control_types[shifted_type][1]) subtree:add(f_control_type_text, control_types[shifted_type][1])
end end
if shifted_type == 0 or shifted_type == 1 then if shifted_type == 0 or shifted_type == 1 then
-- this has a sub-sequence number -- this has a sub-sequence number
local second_word = buf(4, 4):le_uint() local second_word = buf(4, 4):le_uint()
subtree:add(f_control_sub_sequence, bit32.band(second_word, SEQUENCE_NUMBER_MASK)) subtree:add(f_control_sub_sequence, bit32.band(second_word, SEQUENCE_NUMBER_MASK))
local data_index = 8 local data_index = 8
if shifted_type == 0 then if shifted_type == 0 then
-- if this is an ACK let's read out the sequence number -- if this is an ACK let's read out the sequence number
local sequence_number = buf(8, 4):le_uint() local sequence_number = buf(8, 4):le_uint()
subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK))
data_index = data_index + 4 data_index = data_index + 4
end end
data_length = buf:len() - data_index data_length = buf:len() - data_index
-- set the data from whatever is left in the packet -- set the data from whatever is left in the packet
subtree:add(f_data, buf(data_index, data_length)) subtree:add(f_data, buf(data_index, data_length))
elseif shifted_type == 2 then elseif shifted_type == 2 then
-- this is a Light ACK let's read out the sequence number -- this is a Light ACK let's read out the sequence number
local sequence_number = buf(4, 4):le_uint() local sequence_number = buf(4, 4):le_uint()
subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK))
data_length = buf:len() - 4 data_length = buf:len() - 4
-- set the data from whatever is left in the packet -- set the data from whatever is left in the packet
subtree:add(f_data, buf(4, data_length)) subtree:add(f_data, buf(4, data_length))
elseif shifted_type == 3 or shifted_type == 4 then elseif shifted_type == 3 or shifted_type == 4 then
if buf:len() <= 12 then if buf:len() <= 12 then
-- this is a NAK pull the sequence number or range -- this is a NAK pull the sequence number or range
local sequence_number = buf(4, 4):le_uint() local sequence_number = buf(4, 4):le_uint()
subtree:add(f_nak_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) subtree:add(f_nak_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK))
data_length = buf:len() - 4 data_length = buf:len() - 4
if buf:len() > 8 then if buf:len() > 8 then
local range_end = buf(8, 4):le_uint() local range_end = buf(8, 4):le_uint()
subtree:add(f_nak_range_end, bit32.band(range_end, SEQUENCE_NUMBER_MASK)) subtree:add(f_nak_range_end, bit32.band(range_end, SEQUENCE_NUMBER_MASK))
data_length = data_length - 4 data_length = data_length - 4
end end
end end
else else
data_length = buf:len() - 4 data_length = buf:len() - 4
-- no sub-sequence number, just read the data -- no sub-sequence number, just read the data
subtree:add(f_data, buf(4, data_length)) subtree:add(f_data, buf(4, data_length))
end end
else else
-- dissect the data packet -- dissect the data packet
pinfo.cols.protocol = p_hfudt.name pinfo.cols.protocol = p_hfudt.name
-- set the reliability bit -- set the reliability bit
subtree:add(f_reliable_bit, bit32.rshift(first_word, 30)) subtree:add(f_reliable_bit, bit32.rshift(first_word, 30))
local message_bit = bit32.band(0x01, bit32.rshift(first_word, 29)) local message_bit = bit32.band(0x01, bit32.rshift(first_word, 29))
-- set the message bit -- set the message bit
subtree:add(f_message_bit, message_bit) subtree:add(f_message_bit, message_bit)
-- read the obfuscation level -- read the obfuscation level
local obfuscation_bits = bit32.band(0x03, bit32.rshift(first_word, 27)) local obfuscation_bits = bit32.band(0x03, bit32.rshift(first_word, 27))
subtree:add(f_obfuscation_level, obfuscation_bits) subtree:add(f_obfuscation_level, obfuscation_bits)
-- read the sequence number -- read the sequence number
subtree:add(f_sequence_number, bit32.band(first_word, SEQUENCE_NUMBER_MASK)) subtree:add(f_sequence_number, bit32.band(first_word, SEQUENCE_NUMBER_MASK))
local payload_offset = 4 local payload_offset = 4
-- if the message bit is set, handle the second word -- if the message bit is set, handle the second word
if message_bit == 1 then if message_bit == 1 then
payload_offset = 12 payload_offset = 12
local second_word = buf(4, 4):le_uint() local second_word = buf(4, 4):le_uint()
-- read message position from upper 2 bits -- read message position from upper 2 bits
local message_position = bit32.rshift(second_word, 30) local message_position = bit32.rshift(second_word, 30)
local position = subtree:add(f_message_position, message_position) local position = subtree:add(f_message_position, message_position)
if message_positions[message_position] ~= nil then if message_positions[message_position] ~= nil then
-- if we know this position then add the name -- if we know this position then add the name
position:append_text(" (".. message_positions[message_position] .. ")") position:append_text(" (".. message_positions[message_position] .. ")")
end end
-- read message number from lower 30 bits -- read message number from lower 30 bits
subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF)) subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF))
-- read the message part number -- read the message part number
subtree:add(f_message_part_number, buf(8, 4):le_uint()) subtree:add(f_message_part_number, buf(8, 4):le_uint())
end end
-- read the type -- read the type
local packet_type = buf(payload_offset, 1):le_uint() local packet_type = buf(payload_offset, 1):le_uint()
local ptype = subtree:add_le(f_type, buf(payload_offset, 1)) local ptype = subtree:add_le(f_type, buf(payload_offset, 1))
local packet_type_text = packet_types[packet_type] local packet_type_text = packet_types[packet_type]
if packet_type_text ~= nil then if packet_type_text ~= nil then
subtree:add(f_type_text, packet_type_text) subtree:add(f_type_text, packet_type_text)
-- if we know this packet type then add the name -- if we know this packet type then add the name
ptype:append_text(" (".. packet_type_text .. ")") ptype:append_text(" (".. packet_type_text .. ")")
end end
-- read the version -- read the version
subtree:add_le(f_version, buf(payload_offset + 1, 1)) subtree:add_le(f_version, buf(payload_offset + 1, 1))
local i = payload_offset + 2 local i = payload_offset + 2
if unsourced_packet_types[packet_type_text] == nil then if unsourced_packet_types[packet_type_text] == nil then
-- read node local ID -- read node local ID
local sender_id = buf(payload_offset + 2, 2) local sender_id = buf(payload_offset + 2, 2)
subtree:add_le(f_sender_id, sender_id) subtree:add_le(f_sender_id, sender_id)
i = i + 2 i = i + 2
-- read HMAC MD5 hash -- read HMAC MD5 hash
subtree:add(f_hmac_hash, buf(i, 16)) subtree:add(f_hmac_hash, buf(i, 16))
i = i + 16 i = i + 16
end end
-- Domain packets -- Domain packets
if packet_type_text == "DomainList" then if packet_type_text == "DomainList" then
Dissector.get("hf-domain"):call(buf(i):tvb(), pinfo, tree) Dissector.get("hf-domain"):call(buf(i):tvb(), pinfo, tree)
end end
-- AvatarData or BulkAvatarDataPacket -- AvatarData or BulkAvatarDataPacket
if packet_type_text == "AvatarData" or packet_type_text == "BulkAvatarData" then if packet_type_text == "AvatarData" or packet_type_text == "BulkAvatarData" then
Dissector.get("hf-avatar"):call(buf(i):tvb(), pinfo, tree) Dissector.get("hf-avatar"):call(buf(i):tvb(), pinfo, tree)
end end
if packet_type_text == "EntityEdit" then if packet_type_text == "EntityEdit" then
Dissector.get("hf-entity"):call(buf(i):tvb(), pinfo, tree) Dissector.get("hf-entity"):call(buf(i):tvb(), pinfo, tree)
end end
end
if packet_types[packet_type] == "MicrophoneAudioNoEcho" or
-- return the size of the header packet_types[packet_type] == "MicrophoneAudioWithEcho" or
return buf:len() packet_types[packet_type] == "SilentAudioFrame" then
Dissector.get("hf-audio"):call(buf(i):tvb(), pinfo, tree)
end end
end
function p_hfudt.init()
local udp_dissector_table = DissectorTable.get("udp.port") -- return the size of the header
return buf:len()
for port=1000, 65000 do
udp_dissector_table:add(port, p_hfudt) end
end
end function p_hfudt.init()
local udp_dissector_table = DissectorTable.get("udp.port")
for port=1000, 65000 do
udp_dissector_table:add(port, p_hfudt)
end
end

View file

@ -0,0 +1,46 @@
print("Loading hf-audio")
-- create the audio protocol
p_hf_audio = Proto("hf-audio", "HF Audio Protocol")
-- audio packet fields
local f_audio_sequence_number = ProtoField.uint16("hf_audio.sequence_number", "Sequence Number")
local f_audio_codec_size = ProtoField.uint32("hf_audio.codec_size", "Codec Size")
local f_audio_codec = ProtoField.string("hf_audio.codec", "Codec")
local f_audio_is_stereo = ProtoField.bool("hf_audio.is_stereo", "Is Stereo")
local f_audio_num_silent_samples = ProtoField.uint16("hf_audio.num_silent_samples", "Num Silent Samples")
p_hf_audio.fields = {
f_audio_sequence_number, f_audio_codec_size, f_audio_codec,
f_audio_is_stereo, f_audio_num_silent_samples
}
local packet_type_extractor = Field.new('hfudt.type_text')
function p_hf_audio.dissector(buf, pinfo, tree)
pinfo.cols.protocol = p_hf_audio.name
audio_subtree = tree:add(p_hf_audio, buf())
local i = 0
audio_subtree:add_le(f_audio_sequence_number, buf(i, 2))
i = i + 2
-- figure out the number of bytes the codec name takes
local codec_name_bytes = buf(i, 4):le_uint()
audio_subtree:add_le(f_audio_codec_size, buf(i, 4))
i = i + 4
audio_subtree:add(f_audio_codec, buf(i, codec_name_bytes))
i = i + codec_name_bytes
local packet_type = packet_type_extractor().value
if packet_type == "SilentAudioFrame" then
audio_subtree:add_le(f_audio_num_silent_samples, buf(i, 2))
i = i + 2
else
audio_subtree:add_le(f_audio_is_stereo, buf(i, 1))
i = i + 1
end
end

View file

@ -1,3 +1,5 @@
print("Loading hf-avatar")
-- create the avatar protocol -- create the avatar protocol
p_hf_avatar = Proto("hf-avatar", "HF Avatar Protocol") p_hf_avatar = Proto("hf-avatar", "HF Avatar Protocol")

View file

@ -1,3 +1,5 @@
print("Loading hf-entity")
-- create the entity protocol -- create the entity protocol
p_hf_entity = Proto("hf-entity", "HF Entity Protocol") p_hf_entity = Proto("hf-entity", "HF Entity Protocol")