diff --git a/tools/dissectors/1-hfudt.lua b/tools/dissectors/1-hfudt.lua index 3993b2d7a0..8b9251c739 100644 --- a/tools/dissectors/1-hfudt.lua +++ b/tools/dissectors/1-hfudt.lua @@ -151,13 +151,16 @@ local packet_types = { [96] = "OctreeDataFileReply", [97] = "OctreeDataPersist", [98] = "EntityClone", - [99] = "EntityQueryInitialResultsComplete" + [99] = "EntityQueryInitialResultsComplete", + [100] = "BulkAvatarTraits" } local unsourced_packet_types = { ["DomainList"] = true } +local fragments = {} + function p_hfudt.dissector(buf, pinfo, tree) -- make sure this isn't a STUN packet - those don't follow HFUDT format @@ -235,6 +238,10 @@ function p_hfudt.dissector(buf, pinfo, tree) local payload_offset = 4 + local message_number = 0 + local message_part_number = 0 + local message_position = 0 + -- if the message bit is set, handle the second word if message_bit == 1 then payload_offset = 12 @@ -242,7 +249,7 @@ function p_hfudt.dissector(buf, pinfo, tree) local second_word = buf(4, 4):le_uint() -- read message position from upper 2 bits - local message_position = bit32.rshift(second_word, 30) + message_position = bit32.rshift(second_word, 30) local position = subtree:add(f_message_position, message_position) if message_positions[message_position] ~= nil then @@ -251,10 +258,12 @@ function p_hfudt.dissector(buf, pinfo, tree) end -- read message number from lower 30 bits - subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF)) + message_number = bit32.band(second_word, 0x3FFFFFFF) + subtree:add(f_message_number, message_number) -- read the message part number - subtree:add(f_message_part_number, buf(8, 4):le_uint()) + message_part_number = buf(8, 4):le_uint() + subtree:add(f_message_part_number, message_part_number) end -- read the type @@ -283,25 +292,85 @@ function p_hfudt.dissector(buf, pinfo, tree) i = i + 16 end - -- Domain packets - if packet_type_text == "DomainList" then - Dissector.get("hf-domain"):call(buf(i):tvb(), pinfo, tree) + local payload_to_dissect = nil + + -- check if we have part of a message that we need to re-assemble + -- before it can be dissected + if obfuscation_bits == 0 then + if message_bit == 1 and message_position ~= 0 then + if fragments[message_number] == nil then + fragments[message_number] = {} + end + + if fragments[message_number][message_part_number] == nil then + fragments[message_number][message_part_number] = {} + end + + -- set the properties for this fragment + fragments[message_number][message_part_number] = { + payload = buf(i):bytes() + } + + -- if this is the last part, set our maximum part number + if message_position == 1 then + fragments[message_number].last_part_number = message_part_number + end + + -- if we have the last part + -- enumerate our parts for this message and see if everything is present + if fragments[message_number].last_part_number ~= nil then + local i = 0 + local has_all = true + + local finalMessage = ByteArray.new() + local message_complete = true + + while i <= fragments[message_number].last_part_number do + if fragments[message_number][i] ~= nil then + finalMessage = finalMessage .. fragments[message_number][i].payload + else + -- missing this part, have to break until we have it + message_complete = false + end + + i = i + 1 + end + + if message_complete then + debug("Message " .. message_number .. " is " .. finalMessage:len()) + payload_to_dissect = ByteArray.tvb(finalMessage, message_number) + end + end + + else + payload_to_dissect = buf(i):tvb() + end end - -- AvatarData or BulkAvatarDataPacket - if packet_type_text == "AvatarData" or packet_type_text == "BulkAvatarData" then - Dissector.get("hf-avatar"):call(buf(i):tvb(), pinfo, tree) + if payload_to_dissect ~= nil then + -- Domain packets + if packet_type_text == "DomainList" then + Dissector.get("hf-domain"):call(payload_to_dissect, pinfo, tree) + end + + -- AvatarData or BulkAvatarDataPacket + if packet_type_text == "AvatarData" or + packet_type_text == "BulkAvatarData" or + packet_type_text == "BulkAvatarTraits" then + Dissector.get("hf-avatar"):call(payload_to_dissect, pinfo, tree) + end + + if packet_type_text == "EntityEdit" then + Dissector.get("hf-entity"):call(payload_to_dissect, pinfo, tree) + end + + if packet_types[packet_type] == "MicrophoneAudioNoEcho" or + packet_types[packet_type] == "MicrophoneAudioWithEcho" or + packet_types[packet_type] == "SilentAudioFrame" then + Dissector.get("hf-audio"):call(payload_to_dissect, pinfo, tree) + end end - if packet_type_text == "EntityEdit" then - Dissector.get("hf-entity"):call(buf(i):tvb(), pinfo, tree) - end - - if packet_types[packet_type] == "MicrophoneAudioNoEcho" or - packet_types[packet_type] == "MicrophoneAudioWithEcho" or - packet_types[packet_type] == "SilentAudioFrame" then - Dissector.get("hf-audio"):call(buf(i):tvb(), pinfo, tree) - end end -- return the size of the header diff --git a/tools/dissectors/3-hf-avatar.lua b/tools/dissectors/3-hf-avatar.lua index 0fa551c6f8..f4172b01cb 100644 --- a/tools/dissectors/3-hf-avatar.lua +++ b/tools/dissectors/3-hf-avatar.lua @@ -21,13 +21,31 @@ local f_avatar_data_default_rotations = ProtoField.string("hf_avatar.avatar_data local f_avatar_data_default_translations = ProtoField.string("hf_avatar.avatar_data_default_translations", "Valid Default") local f_avatar_data_sizes = ProtoField.string("hf_avatar.avatar_sizes", "Sizes") +-- avatar trait data fields +local f_avatar_trait_data = ProtoField.bytes("hf_avatar.avatar_trait_data", "Avatar Trait Data") + +local f_avatar_trait_id = ProtoField.guid("hf_avatar.trait_avatar_id", "Trait Avatar ID") +local f_avatar_trait_type = ProtoField.int8("hf_avatar.trait_type", "Trait Type") +local f_avatar_trait_version = ProtoField.int32("hf_avatar.trait_version", "Trait Version") +local f_avatar_trait_instance_id = ProtoField.guid("hf_avatar.trait_instance_id", "Trait Instance ID") +local f_avatar_trait_binary = ProtoField.bytes("hf_avatar.trait_binary", "Trait Binary Data") p_hf_avatar.fields = { - f_avatar_id, f_avatar_data_parent_id + f_avatar_id, f_avatar_data_parent_id, + f_avatar_trait_data, + f_avatar_trait_type, f_avatar_trait_id, + f_avatar_trait_version, f_avatar_trait_binary, + f_avatar_trait_instance_id } local packet_type_extractor = Field.new('hfudt.type') +INSTANCED_TYPES = { + [1] = true +} + +TOTAL_TRAIT_TYPES = 2 + function p_hf_avatar.dissector(buf, pinfo, tree) pinfo.cols.protocol = p_hf_avatar.name @@ -52,7 +70,7 @@ function p_hf_avatar.dissector(buf, pinfo, tree) add_avatar_data_subtrees(avatar_data) - else + elseif packet_type == 11 then -- BulkAvatarData packet while i < buf:len() do -- avatar_id is first 16 bytes @@ -65,9 +83,88 @@ function p_hf_avatar.dissector(buf, pinfo, tree) add_avatar_data_subtrees(avatar_data) end + elseif packet_type == 100 then + -- BulkAvatarTraits packet + + -- loop over the packet until we're done reading it + while i < buf:len() do + i = i + read_avatar_trait_data(buf(i)) + end end end +function read_avatar_trait_data(buf) + local i = 0 + + -- avatar_id is first 16 bytes + local avatar_id_bytes = buf(i, 16) + i = i + 16 + + local traits_map = {} + + -- loop over all of the traits for this avatar + while i < buf:len() do + -- pull out this trait type + local trait_type = buf(i, 1):le_int() + i = i + 1 + + debug("The trait type is " .. trait_type) + + -- bail on our while if the trait type is null (-1) + if trait_type == -1 then break end + + local trait_map = {} + + -- pull out the trait version + trait_map.version = buf(i, 4):le_int() + i = i + 4 + + if INSTANCED_TYPES[trait_type] ~= nil then + -- pull out the trait instance ID + trait_map.instance_ID = buf(i, 16) + i = i + 16 + end + + -- pull out the trait binary size + trait_map.binary_size = buf(i, 2):le_int() + i = i + 2 + + -- unpack the binary data as long as this wasn't a delete + if trait_map.binary_size ~= -1 then + -- pull out the binary data for the trait + trait_map.binary_data = buf(i, trait_map.binary_size) + i = i + trait_map.binary_size + end + + traits_map[trait_type] = trait_map + end + + -- add a subtree including all of the data for this avatar + debug("Adding trait data of " .. i .. " bytes to the avatar tree") + local this_avatar_tree = avatar_subtree:add(f_avatar_trait_data, buf(0, i)) + + this_avatar_tree:add(f_avatar_trait_id, avatar_id_bytes) + + -- enumerate the pulled traits and add them to the tree + local trait_type = 0 + while trait_type < TOTAL_TRAIT_TYPES do + trait = traits_map[trait_type] + + if trait ~= nil then + this_avatar_tree:add(f_avatar_trait_type, trait_type) + this_avatar_tree:add(f_avatar_trait_version, trait.version) + this_avatar_tree:add(f_avatar_trait_binary, trait.binary_data) + + if trait.instance_ID ~= nil then + this_avatar_tree:add(f_avatar_trait_instance_id, trait.instance_ID) + end + end + + trait_type = trait_type + 1 + end + + return i +end function add_avatar_data_subtrees(avatar_data) if avatar_data["has_flags"] then