diff --git a/tools/dissectors/hf-avatar.lua b/tools/dissectors/hf-avatar.lua new file mode 100644 index 0000000000..231fd1b354 --- /dev/null +++ b/tools/dissectors/hf-avatar.lua @@ -0,0 +1,297 @@ +-- create the avatar protocol +p_hf_avatar = Proto("hf-avatar", "HF Avatar Protocol") + +-- avatar data fields +local f_avatar_id = ProtoField.guid("hf_avatar.avatar_id", "Avatar ID") +local f_avatar_data_has_flags = ProtoField.string("hf_avatar.avatar_has_flags", "Has Flags") +local f_avatar_data_position = ProtoField.string("hf_avatar.avatar_data_position", "Position") +local f_avatar_data_dimensions = ProtoField.string("hf_avatar.avatar_data_dimensions", "Dimensions") +local f_avatar_data_offset = ProtoField.string("hf_avatar.avatar_data_offset", "Offset") +local f_avatar_data_look_at_position = ProtoField.string("hf_avatar.avatar_data_look_at_position", "Look At Position") +local f_avatar_data_audio_loudness = ProtoField.string("hf_avatar.avatar_data_audio_loudness", "Audio Loudness") +local f_avatar_data_additional_flags = ProtoField.string("hf_avatar.avatar_data_additional_flags", "Additional Flags") +local f_avatar_data_parent_id = ProtoField.guid("hf_avatar.avatar_data_parent_id", "Parent ID") +local f_avatar_data_parent_joint_index = ProtoField.string("hf_avatar.avatar_data_parent_joint_index", "Parent Joint Index") +local f_avatar_data_local_position = ProtoField.string("hf_avatar.avatar_data_local_position", "Local Position") +local f_avatar_data_valid_rotations = ProtoField.string("hf_avatar.avatar_data_valid_rotations", "Valid Rotations") +local f_avatar_data_valid_translations = ProtoField.string("hf_avatar.avatar_data_valid_translations", "Valid Translations") +local f_avatar_data_default_rotations = ProtoField.string("hf_avatar.avatar_data_default_rotations", "Valid Default") +local f_avatar_data_default_translations = ProtoField.string("hf_avatar.avatar_data_default_translations", "Valid Default") + +p_hf_avatar.fields = { + f_avatar_id, f_avatar_data_parent_id +} + +local packet_type_extractor = Field.new('hfudt.type') + +function p_hf_avatar.dissector(buf, pinfo, tree) + pinfo.cols.protocol = p_hf_avatar.name + + avatar_subtree = tree:add(p_hf_avatar, buf()) + + local i = 0 + + local avatar_data + + local packet_type = packet_type_extractor().value + + if packet_type == 6 then + -- AvatarData packet + + -- uint16 sequence_number + local sequence_number = buf(i, 2):le_uint() + i = i + 2 + + local avatar_data_packet_len = buf:len() - i + avatar_data = decode_avatar_data_packet(buf(i, avatar_data_packet_len)) + i = i + avatar_data_packet_len + + add_avatar_data_subtrees(avatar_data) + + else + -- BulkAvatarData packet + while i < buf:len() do + -- avatar_id is first 16 bytes + avatar_subtree:add(f_avatar_id, buf(i, 16)) + i = i + 16 + + local avatar_data_packet_len = buf:len() - i + avatar_data = decode_avatar_data_packet(buf(i, avatar_data_packet_len)) + i = i + avatar_data_packet_len + + add_avatar_data_subtrees(avatar_data) + end + end +end + + +function add_avatar_data_subtrees(avatar_data) + if avatar_data["has_flags"] then + avatar_subtree:add(f_avatar_data_has_flags, avatar_data["has_flags"]) + end + if avatar_data["position"] then + avatar_subtree:add(f_avatar_data_position, avatar_data["position"]) + end + if avatar_data["dimensions"] then + avatar_subtree:add(f_avatar_data_dimensions, avatar_data["dimensions"]) + end + if avatar_data["offset"] then + avatar_subtree:add(f_avatar_data_offset, avatar_data["offset"]) + end + if avatar_data["look_at_position"] then + avatar_subtree:add(f_avatar_data_look_at_position, avatar_data["look_at_position"]) + end + if avatar_data["audio_loudness"] then + avatar_subtree:add(f_avatar_data_audio_loudness, avatar_data["audio_loudness"]) + end + if avatar_data["additional_flags"] then + avatar_subtree:add(f_avatar_data_additional_flags, avatar_data["additional_flags"]) + end + if avatar_data["parent_id"] then + avatar_subtree:add(f_avatar_data_parent_id, avatar_data["parent_id"]) + end + if avatar_data["parent_joint_index"] then + avatar_subtree:add(f_avatar_data_parent_joint_index, avatar_data["parent_joint_index"]) + end + if avatar_data["local_position"] then + avatar_subtree:add(f_avatar_data_local_position, avatar_data["local_position"]) + end + if avatar_data["valid_rotations"] then + avatar_subtree:add(f_avatar_data_valid_rotations, avatar_data["valid_rotations"]) + end + if avatar_data["valid_translations"] then + avatar_subtree:add(f_avatar_data_valid_translations, avatar_data["valid_translations"]) + end + if avatar_data["default_rotations"] then + avatar_subtree:add(f_avatar_data_default_rotations, avatar_data["default_rotations"]) + end + if avatar_data["default_translations"] then + avatar_subtree:add(f_avatar_data_default_translations, avatar_data["default_translations"]) + end +end + +function decode_vec3(buf) + local i = 0 + local x = buf(i, 4):le_float() + i = i + 4 + local y = buf(i, 4):le_float() + i = i + 4 + local z = buf(i, 4):le_float() + i = i + 4 + return {x, y, z} +end + +function decode_validity_bits(buf, num_bits) + -- first pass, decode each bit into an array of booleans + local i = 0 + local bit = 0 + local booleans = {} + for n = 1, num_bits do + local value = (bit32.band(buf(i, 1):uint(), bit32.lshift(1, bit)) ~= 0) + booleans[#booleans + 1] = value + bit = bit + 1 + if bit == 8 then + i = i + 1 + bit = 0 + end + end + + -- second pass, create a list of indices whos booleans are true + local result = {} + for n = 1, #booleans do + if booleans[n] then + result[#result + 1] = n + end + end + + return result +end + +function decode_avatar_data_packet(buf) + + local i = 0 + local result = {} + + -- uint16 has_flags + local has_flags = buf(i, 2):le_uint() + i = i + 2 + + local has_global_position = (bit32.band(has_flags, 1) ~= 0) + local has_bounding_box = (bit32.band(has_flags, 2) ~= 0) + local has_orientation = (bit32.band(has_flags, 4) ~= 0) + local has_scale = (bit32.band(has_flags, 8) ~= 0) + local has_look_at_position = (bit32.band(has_flags, 16) ~= 0) + local has_audio_loudness = (bit32.band(has_flags, 32) ~= 0) + local has_sensor_to_world_matrix = (bit32.band(has_flags, 64) ~= 0) + local has_additional_flags = (bit32.band(has_flags, 128) ~= 0) + local has_parent_info = (bit32.band(has_flags, 256) ~= 0) + local has_local_position = (bit32.band(has_flags, 512) ~= 0) + local has_face_tracker_info = (bit32.band(has_flags, 1024) ~= 0) + local has_joint_data = (bit32.band(has_flags, 2048) ~= 0) + local has_joint_default_pose_flags = (bit32.band(has_flags, 4096) ~= 0) + + result["has_flags"] = string.format("HasFlags: 0x%x", has_flags) + + if has_global_position then + local position = decode_vec3(buf(i, 12)) + result["position"] = string.format("Position: %.3f, %.3f, %.3f", position[1], position[2], position[3]) + i = i + 12 + end + + if has_bounding_box then + local dimensions = decode_vec3(buf(i, 12)) + i = i + 12 + local offset = decode_vec3(buf(i, 12)) + i = i + 12 + result["dimensions"] = string.format("Dimensions: %.3f, %.3f, %.3f", dimensions[1], dimensions[2], dimensions[3]) + result["offset"] = string.format("Offset: %.3f, %.3f, %.3f", offset[1], offset[2], offset[3]) + end + + if has_orientation then + -- TODO: orientation is hard to decode... + i = i + 6 + end + + if has_scale then + -- TODO: scale is hard to decode... + i = i + 2 + end + + if has_look_at_position then + local look_at = decode_vec3(buf(i, 12)) + i = i + 12 + result["look_at_position"] = string.format("Look At Position: %.3f, %.3f, %.3f", look_at[1], look_at[2], look_at[3]) + end + + if has_audio_loudness then + local loudness = buf(i, 1):uint() + i = i + 1 + result["audio_loudness"] = string.format("Audio Loudness: %d", loudness) + end + + if has_sensor_to_world_matrix then + -- TODO: sensor to world matrix is hard to decode + i = i + 20 + end + + if has_additional_flags then + local flags = buf(i, 1):uint() + i = i + 1 + result["additional_flags"] = string.format("Additional Flags: 0x%x", flags) + end + + if has_parent_info then + local parent_id = buf(i, 16) + i = i + 16 + local parent_joint_index = buf(i, 2):le_int() + i = i + 2 + result["parent_id"] = parent_id + result["parent_joint_index"] = string.format("Parent Joint Index: %d", parent_joint_index) + end + + if has_local_position then + local local_pos = decode_vec3(buf(i, 12)) + i = i + 12 + result["local_position"] = string.format("Local Position: %.3f, %.3f, %.3f", local_pos[1], local_pos[2], local_pos[3]) + end + + if has_face_tracker_info then + local left_eye_blink = buf(i, 4):le_float() + i = i + 4 + local right_eye_blink = buf(i, 4):le_float() + i = i + 4 + local average_loudness = buf(i, 4):le_float() + i = i + 4 + local brow_audio_lift = buf(i, 4):le_float() + i = i + 4 + local num_blendshape_coefficients = buf(i, 1):uint() + i = i + 1 + local blendshape_coefficients = {} + for n = 1, num_blendshape_coefficients do + blendshape_coefficients[n] = buf(i, 4):le_float() + i = i + 4 + end + -- TODO: insert blendshapes into result + end + + if has_joint_data then + + local num_joints = buf(i, 1):uint() + i = i + 1 + local num_validity_bytes = math.ceil(num_joints / 8) + + local indices = decode_validity_bits(buf(i, num_validity_bytes), num_joints) + i = i + num_validity_bytes + result["valid_rotations"] = "Valid Rotations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" + + -- TODO: skip rotations for now + i = i + #indices * 6 + + indices = decode_validity_bits(buf(i, num_validity_bytes), num_joints) + i = i + num_validity_bytes + result["valid_translations"] = "Valid Translations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" + + -- TODO: skip translations for now + i = i + #indices * 6 + + -- TODO: skip hand controller data + i = i + 24 + + end + + if has_joint_default_pose_flags then + local num_joints = buf(i, 1):uint() + i = i + 1 + local num_validity_bytes = math.ceil(num_joints / 8) + + local indices = decode_validity_bits(buf(i, num_validity_bytes), num_joints) + i = i + num_validity_bytes + result["default_rotations"] = "Default Rotations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" + + indices = decode_validity_bits(buf(i, num_validity_bytes), num_joints) + i = i + num_validity_bytes + result["default_translations"] = "Default Translations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" + end + + return result +end diff --git a/tools/dissectors/hf-entity.lua b/tools/dissectors/hf-entity.lua new file mode 100644 index 0000000000..f4de5a995d --- /dev/null +++ b/tools/dissectors/hf-entity.lua @@ -0,0 +1,36 @@ +-- create the entity protocol +p_hf_entity = Proto("hf-entity", "HF Entity Protocol") + +-- entity packet fields +local f_entity_sequence_number = ProtoField.uint16("hf_entity.sequence_number", "Sequence Number") +local f_entity_timestamp = ProtoField.uint64("hf_entity.timestamp", "Timestamp") +local f_octal_code_bytes = ProtoField.uint8("hf_entity.octal_code_bytes", "Octal Code Bytes") +local f_entity_id = ProtoField.guid("hf_entity.entity_id", "Entity ID") + +p_hf_entity.fields = { + f_entity_sequence_number, f_entity_timestamp, f_octal_code_bytes, f_entity_id +} + +function p_hf_entity.dissector(buf, pinfo, tree) + pinfo.cols.protocol = p_hf_entity.name + + entity_subtree = tree:add(p_hf_entity, buf()) + + i = 0 + + entity_subtree:add_le(f_entity_sequence_number, buf(i, 2)) + i = i + 2 + + entity_subtree:add_le(f_entity_timestamp, buf(i, 4)) + i = i + 4 + + -- figure out the number of bytes the octal code takes + local octal_code_bytes = buf(i, 1):le_uint() + entity_subtree:add_le(f_octal_code_bytes, buf(i, 1)) + + -- skip over the octal code + i = i + 1 + octal_code_bytes + + -- read the entity ID + entity_subtree:add(f_entity_id, buf(i, 16)) +end diff --git a/tools/dissectors/hfudt.lua b/tools/dissectors/hfudt.lua new file mode 100644 index 0000000000..9d2df801b2 --- /dev/null +++ b/tools/dissectors/hfudt.lua @@ -0,0 +1,296 @@ +print("Loading hfudt") + +-- create the HFUDT protocol +p_hfudt = Proto("hfudt", "HFUDT Protocol") + +-- create fields shared between packets in HFUDT +local f_data = ProtoField.string("hfudt.data", "Data") + +-- create the fields for data packets in HFUDT +local f_length = ProtoField.uint16("hfudt.length", "Length", 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_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_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_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_type = ProtoField.uint8("hfudt.type", "Type", base.DEC) +local f_version = ProtoField.uint8("hfudt.version", "Version", base.DEC) +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_hmac_hash = ProtoField.bytes("hfudt.hmac_hash", "HMAC Hash") + +-- 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_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_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_range_end = ProtoField.uint32("hfudt.nak_range_end", "NAK Range End", base.DEC) + +local SEQUENCE_NUMBER_MASK = 0x07FFFFFF + +p_hfudt.fields = { + f_length, + 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_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_data +} + +local control_types = { + [0] = { "ACK", "Acknowledgement" }, + [1] = { "ACK2", "Acknowledgement of acknowledgement" }, + [2] = { "LightACK", "Light Acknowledgement" }, + [3] = { "NAK", "Loss report (NAK)" }, + [4] = { "TimeoutNAK", "Loss report re-transmission (TimeoutNAK)" }, + [5] = { "Handshake", "Handshake" }, + [6] = { "HandshakeACK", "Acknowledgement of Handshake" }, + [7] = { "ProbeTail", "Probe tail" }, + [8] = { "HandshakeRequest", "Request a Handshake" } +} + +local message_positions = { + [0] = "ONLY", + [1] = "LAST", + [2] = "FIRST", + [3] = "MIDDLE" +} + +local packet_types = { + [0] = "Unknown", + [1] = "StunResponse", + [2] = "DomainList", + [3] = "Ping", + [4] = "PingReply", + [5] = "KillAvatar", + [6] = "AvatarData", + [7] = "InjectAudio", + [8] = "MixedAudio", + [9] = "MicrophoneAudioNoEcho", + [10] = "MicrophoneAudioWithEcho", + [11] = "BulkAvatarData", + [12] = "SilentAudioFrame", + [13] = "DomainListRequest", + [14] = "RequestAssignment", + [15] = "CreateAssignment", + [16] = "DomainConnectionDenied", + [17] = "MuteEnvironment", + [18] = "AudioStreamStats", + [19] = "DomainServerPathQuery", + [20] = "DomainServerPathResponse", + [21] = "DomainServerAddedNode", + [22] = "ICEServerPeerInformation", + [23] = "ICEServerQuery", + [24] = "OctreeStats", + [25] = "Jurisdiction", + [26] = "JurisdictionRequest", + [27] = "AssignmentClientStatus", + [28] = "NoisyMute", + [29] = "AvatarIdentity", + [30] = "AvatarBillboard", + [31] = "DomainConnectRequest", + [32] = "DomainServerRequireDTLS", + [33] = "NodeJsonStats", + [34] = "OctreeDataNack", + [35] = "StopNode", + [36] = "AudioEnvironment", + [37] = "EntityEditNack", + [38] = "ICEServerHeartbeat", + [39] = "ICEPing", + [40] = "ICEPingReply", + [41] = "EntityData", + [42] = "EntityQuery", + [43] = "EntityAdd", + [44] = "EntityErase", + [45] = "EntityEdit", + [46] = "DomainServerConnectionToken", + [47] = "DomainSettingsRequest", + [48] = "DomainSettings", + [49] = "AssetGet", + [50] = "AssetGetReply", + [51] = "AssetUpload", + [52] = "AssetUploadReply", + [53] = "AssetGetInfo", + [54] = "AssetGetInfoReply" +} + +function p_hfudt.dissector(buf, pinfo, tree) + + -- 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 + + -- validate that the packet length is at least the minimum control packet size + if buf:len() < 4 then return end + + -- create a subtree for HFUDT + subtree = tree:add(p_hfudt, buf(0)) + + -- set the packet length + subtree:add(f_length, buf:len()) + + -- pull out the entire first word + local first_word = buf(0, 4):le_uint() + + -- pull out the control bit and add it to the subtree + local control_bit = bit32.rshift(first_word, 31) + subtree:add(f_control_bit, control_bit) + + local data_length = 0 + + if control_bit == 1 then + -- dissect the control packet + pinfo.cols.protocol = p_hfudt.name .. " Control" + + -- 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 type = subtree:add(f_control_type, shifted_type) + + if control_types[shifted_type] ~= nil then + -- if we know this type then add the name + type:append_text(" (".. control_types[shifted_type][1] .. ")") + + subtree:add(f_control_type_text, control_types[shifted_type][1]) + end + + if shifted_type == 0 or shifted_type == 1 then + + -- this has a sub-sequence number + local second_word = buf(4, 4):le_uint() + subtree:add(f_control_sub_sequence, bit32.band(second_word, SEQUENCE_NUMBER_MASK)) + + local data_index = 8 + + if shifted_type == 0 then + -- if this is an ACK let's read out the sequence number + local sequence_number = buf(8, 4):le_uint() + subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) + + data_index = data_index + 4 + end + + data_length = buf:len() - data_index + + -- set the data from whatever is left in the packet + subtree:add(f_data, buf(data_index, data_length)) + + elseif shifted_type == 2 then + -- this is a Light ACK let's read out the sequence number + local sequence_number = buf(4, 4):le_uint() + subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) + + data_length = buf:len() - 4 + + -- set the data from whatever is left in the packet + subtree:add(f_data, buf(4, data_length)) + elseif shifted_type == 3 or shifted_type == 4 then + if buf:len() <= 12 then + -- this is a NAK pull the sequence number or range + local sequence_number = buf(4, 4):le_uint() + subtree:add(f_nak_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) + + data_length = buf:len() - 4 + + if buf:len() > 8 then + local range_end = buf(8, 4):le_uint() + subtree:add(f_nak_range_end, bit32.band(range_end, SEQUENCE_NUMBER_MASK)) + + data_length = data_length - 4 + end + end + else + data_length = buf:len() - 4 + + -- no sub-sequence number, just read the data + subtree:add(f_data, buf(4, data_length)) + end + else + -- dissect the data packet + pinfo.cols.protocol = p_hfudt.name + + -- set the reliability bit + subtree:add(f_reliable_bit, bit32.rshift(first_word, 30)) + + local message_bit = bit32.band(0x01, bit32.rshift(first_word, 29)) + + -- set the message bit + subtree:add(f_message_bit, message_bit) + + -- read the obfuscation level + local obfuscation_bits = bit32.band(0x03, bit32.rshift(first_word, 27)) + subtree:add(f_obfuscation_level, obfuscation_bits) + + -- read the sequence number + subtree:add(f_sequence_number, bit32.band(first_word, SEQUENCE_NUMBER_MASK)) + + local payload_offset = 4 + + -- if the message bit is set, handle the second word + if message_bit == 1 then + payload_offset = 12 + + local second_word = buf(4, 4):le_uint() + + -- read message position from upper 2 bits + local message_position = bit32.rshift(second_word, 30) + local position = subtree:add(f_message_position, message_position) + + if message_positions[message_position] ~= nil then + -- if we know this position then add the name + position:append_text(" (".. message_positions[message_position] .. ")") + end + + -- read message number from lower 30 bits + subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF)) + + -- read the message part number + subtree:add(f_message_part_number, buf(8, 4):le_uint()) + end + + -- read the type + local packet_type = buf(payload_offset, 1):le_uint() + local ptype = subtree:add_le(f_type, buf(payload_offset, 1)) + if packet_types[packet_type] ~= nil then + subtree:add(f_type_text, packet_types[packet_type]) + -- if we know this packet type then add the name + ptype:append_text(" (".. packet_types[packet_type] .. ")") + end + + -- read the version + subtree:add_le(f_version, buf(payload_offset + 1, 1)) + + -- read node local ID + local sender_id = buf(payload_offset + 2, 2) + subtree:add_le(f_sender_id, sender_id) + + local i = payload_offset + 4 + + -- read HMAC MD5 hash + subtree:add(f_hmac_hash, buf(i, 16)) + i = i + 16 + + -- AvatarData or BulkAvatarDataPacket + if packet_types[packet_type] == "AvatarData" or packet_types[packet_type] == "BulkAvatarDataPacket" then + Dissector.get("hf-avatar"):call(buf(i):tvb(), pinfo, tree) + end + + if packet_types[packet_type] == "EntityEdit" then + Dissector.get("hf-entity"):call(buf(i):tvb(), pinfo, tree) + end + end + + -- return the size of the header + return buf:len() + +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 diff --git a/tools/hfudt.lua b/tools/hfudt.lua deleted file mode 100644 index f45315a3de..0000000000 --- a/tools/hfudt.lua +++ /dev/null @@ -1,571 +0,0 @@ -print("Loading hfudt") - --- create the HFUDT protocol -p_hfudt = Proto("hfudt", "HFUDT Protocol") - --- create fields shared between packets in HFUDT -local f_data = ProtoField.string("hfudt.data", "Data") - --- create the fields for data packets in HFUDT -local f_length = ProtoField.uint16("hfudt.length", "Length", 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_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_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_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_type = ProtoField.uint8("hfudt.type", "Type", base.DEC) -local f_version = ProtoField.uint8("hfudt.version", "Version", base.DEC) -local f_type_text = ProtoField.string("hfudt.type_text", "TypeText") -local f_sender_id = ProtoField.guid("hfudt.sender_id", "Sender ID", base.DEC) - --- 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_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_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_range_end = ProtoField.uint32("hfudt.nak_range_end", "NAK Range End", base.DEC) - --- avatar data fields -local f_avatar_data_id = ProtoField.guid("hfudt.avatar_id", "Avatar ID", base.DEC) -local f_avatar_data_has_flags = ProtoField.string("hfudt.avatar_has_flags", "Has Flags") -local f_avatar_data_position = ProtoField.string("hfudt.avatar_data_position", "Position") -local f_avatar_data_dimensions = ProtoField.string("hfudt.avatar_data_dimensions", "Dimensions") -local f_avatar_data_offset = ProtoField.string("hfudt.avatar_data_offset", "Offset") -local f_avatar_data_look_at_position = ProtoField.string("hfudt.avatar_data_look_at_position", "Look At Position") -local f_avatar_data_audio_loudness = ProtoField.string("hfudt.avatar_data_audio_loudness", "Audio Loudness") -local f_avatar_data_additional_flags = ProtoField.string("hfudt.avatar_data_additional_flags", "Additional Flags") -local f_avatar_data_parent_id = ProtoField.guid("hfudt.avatar_data_parent_id", "Parent ID") -local f_avatar_data_parent_joint_index = ProtoField.string("hfudt.avatar_data_parent_joint_index", "Parent Joint Index") -local f_avatar_data_local_position = ProtoField.string("hfudt.avatar_data_local_position", "Local Position") -local f_avatar_data_valid_rotations = ProtoField.string("hfudt.avatar_data_valid_rotations", "Valid Rotations") -local f_avatar_data_valid_translations = ProtoField.string("hfudt.avatar_data_valid_translations", "Valid Translations") -local f_avatar_data_default_rotations = ProtoField.string("hfudt.avatar_data_default_rotations", "Valid Default") -local f_avatar_data_default_translations = ProtoField.string("hfudt.avatar_data_default_translations", "Valid Default") - -local SEQUENCE_NUMBER_MASK = 0x07FFFFFF - -p_hfudt.fields = { - f_length, - f_control_bit, f_reliable_bit, f_message_bit, f_sequence_number, f_type, f_type_text, f_version, - f_sender_id, f_avatar_data_id, f_avatar_data_parent_id, - 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_data -} - -p_hfudt.prefs["udp_port"] = Pref.uint("UDP Port", 40102, "UDP Port for HFUDT Packets (udt::Socket bound port)") - -local control_types = { - [0] = { "ACK", "Acknowledgement" }, - [1] = { "ACK2", "Acknowledgement of acknowledgement" }, - [2] = { "LightACK", "Light Acknowledgement" }, - [3] = { "NAK", "Loss report (NAK)" }, - [4] = { "TimeoutNAK", "Loss report re-transmission (TimeoutNAK)" }, - [5] = { "Handshake", "Handshake" }, - [6] = { "HandshakeACK", "Acknowledgement of Handshake" }, - [7] = { "ProbeTail", "Probe tail" }, - [8] = { "HandshakeRequest", "Request a Handshake" } -} - -local message_positions = { - [0] = "ONLY", - [1] = "LAST", - [2] = "FIRST", - [3] = "MIDDLE" -} - -local packet_types = { - [0] = "Unknown", - [1] = "StunResponse", - [2] = "DomainList", - [3] = "Ping", - [4] = "PingReply", - [5] = "KillAvatar", - [6] = "AvatarData", - [7] = "InjectAudio", - [8] = "MixedAudio", - [9] = "MicrophoneAudioNoEcho", - [10] = "MicrophoneAudioWithEcho", - [11] = "BulkAvatarData", - [12] = "SilentAudioFrame", - [13] = "DomainListRequest", - [14] = "RequestAssignment", - [15] = "CreateAssignment", - [16] = "DomainConnectionDenied", - [17] = "MuteEnvironment", - [18] = "AudioStreamStats", - [19] = "DomainServerPathQuery", - [20] = "DomainServerPathResponse", - [21] = "DomainServerAddedNode", - [22] = "ICEServerPeerInformation", - [23] = "ICEServerQuery", - [24] = "OctreeStats", - [25] = "Jurisdiction", - [26] = "JurisdictionRequest", - [27] = "AssignmentClientStatus", - [28] = "NoisyMute", - [29] = "AvatarIdentity", - [30] = "AvatarBillboard", - [31] = "DomainConnectRequest", - [32] = "DomainServerRequireDTLS", - [33] = "NodeJsonStats", - [34] = "OctreeDataNack", - [35] = "StopNode", - [36] = "AudioEnvironment", - [37] = "EntityEditNack", - [38] = "ICEServerHeartbeat", - [39] = "ICEPing", - [40] = "ICEPingReply", - [41] = "EntityData", - [42] = "EntityQuery", - [43] = "EntityAdd", - [44] = "EntityErase", - [45] = "EntityEdit", - [46] = "DomainServerConnectionToken", - [47] = "DomainSettingsRequest", - [48] = "DomainSettings", - [49] = "AssetGet", - [50] = "AssetGetReply", - [51] = "AssetUpload", - [52] = "AssetUploadReply", - [53] = "AssetGetInfo", - [54] = "AssetGetInfoReply" -} - -function p_hfudt.dissector (buf, pinfo, root) - - -- 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 - - -- validate that the packet length is at least the minimum control packet size - if buf:len() < 4 then return end - - -- create a subtree for HFUDT - subtree = root:add(p_hfudt, buf(0)) - - -- set the packet length - subtree:add(f_length, buf:len()) - - -- pull out the entire first word - local first_word = buf(0, 4):le_uint() - - -- pull out the control bit and add it to the subtree - local control_bit = bit32.rshift(first_word, 31) - subtree:add(f_control_bit, control_bit) - - local data_length = 0 - - if control_bit == 1 then - -- dissect the control packet - pinfo.cols.protocol = p_hfudt.name .. " Control" - - -- 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 type = subtree:add(f_control_type, shifted_type) - - if control_types[shifted_type] ~= nil then - -- if we know this type then add the name - type:append_text(" (".. control_types[shifted_type][1] .. ")") - - subtree:add(f_control_type_text, control_types[shifted_type][1]) - end - - if shifted_type == 0 or shifted_type == 1 then - - -- this has a sub-sequence number - local second_word = buf(4, 4):le_uint() - subtree:add(f_control_sub_sequence, bit32.band(second_word, SEQUENCE_NUMBER_MASK)) - - local data_index = 8 - - if shifted_type == 0 then - -- if this is an ACK let's read out the sequence number - local sequence_number = buf(8, 4):le_uint() - subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) - - data_index = data_index + 4 - end - - data_length = buf:len() - data_index - - -- set the data from whatever is left in the packet - subtree:add(f_data, buf(data_index, data_length)) - - elseif shifted_type == 2 then - -- this is a Light ACK let's read out the sequence number - local sequence_number = buf(4, 4):le_uint() - subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) - - data_length = buf:len() - 4 - - -- set the data from whatever is left in the packet - subtree:add(f_data, buf(4, data_length)) - elseif shifted_type == 3 or shifted_type == 4 then - if buf:len() <= 12 then - -- this is a NAK pull the sequence number or range - local sequence_number = buf(4, 4):le_uint() - subtree:add(f_nak_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) - - data_length = buf:len() - 4 - - if buf:len() > 8 then - local range_end = buf(8, 4):le_uint() - subtree:add(f_nak_range_end, bit32.band(range_end, SEQUENCE_NUMBER_MASK)) - - data_length = data_length - 4 - end - end - else - data_length = buf:len() - 4 - - -- no sub-sequence number, just read the data - subtree:add(f_data, buf(4, data_length)) - end - else - -- dissect the data packet - pinfo.cols.protocol = p_hfudt.name - - -- set the reliability bit - subtree:add(f_reliable_bit, bit32.rshift(first_word, 30)) - - local message_bit = bit32.band(0x01, bit32.rshift(first_word, 29)) - - -- set the message bit - subtree:add(f_message_bit, message_bit) - - -- read the obfuscation level - local obfuscation_bits = bit32.band(0x03, bit32.rshift(first_word, 27)) - subtree:add(f_obfuscation_level, obfuscation_bits) - - -- read the sequence number - subtree:add(f_sequence_number, bit32.band(first_word, SEQUENCE_NUMBER_MASK)) - - local payload_offset = 4 - - -- if the message bit is set, handle the second word - if message_bit == 1 then - payload_offset = 12 - - local second_word = buf(4, 4):le_uint() - - -- read message position from upper 2 bits - local message_position = bit32.rshift(second_word, 30) - local position = subtree:add(f_message_position, message_position) - - if message_positions[message_position] ~= nil then - -- if we know this position then add the name - position:append_text(" (".. message_positions[message_position] .. ")") - end - - -- read message number from lower 30 bits - subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF)) - - -- read the message part number - subtree:add(f_message_part_number, buf(8, 4):le_uint()) - end - - -- read the type - local packet_type = buf(payload_offset, 1):le_uint() - local ptype = subtree:add(f_type, packet_type) - if packet_types[packet_type] ~= nil then - subtree:add(f_type_text, packet_types[packet_type]) - -- if we know this packet type then add the name - ptype:append_text(" (".. packet_types[packet_type] .. ")") - end - - -- read the version - subtree:add(f_version, buf(payload_offset + 1, 1):le_uint()) - - -- read node GUID - local sender_id = buf(payload_offset + 2, 16) - subtree:add(f_sender_id, sender_id) - - local i = payload_offset + 18 - - -- skip MD6 checksum + 16 bytes - i = i + 16 - - -- AvatarData or BulkAvatarDataPacket - if packet_type == 6 or packet_type == 11 then - - local avatar_data - if packet_type == 6 then - -- AvatarData packet - - -- avatar_id is same as sender_id - subtree:add(f_avatar_data_id, sender_id) - - -- uint16 sequence_number - local sequence_number = buf(i, 2):le_uint() - i = i + 2 - - local avatar_data_packet_len = buf:len() - i - avatar_data = decode_avatar_data_packet(buf(i, avatar_data_packet_len)) - i = i + avatar_data_packet_len - - add_avatar_data_subtrees(avatar_data) - - else - -- BulkAvatarData packet - while i < buf:len() do - -- avatar_id is first 16 bytes - subtree:add(f_avatar_data_id, buf(i, 16)) - i = i + 16 - - local avatar_data_packet_len = buf:len() - i - avatar_data = decode_avatar_data_packet(buf(i, avatar_data_packet_len)) - i = i + avatar_data_packet_len - - add_avatar_data_subtrees(avatar_data) - end - end - end - end - - -- return the size of the header - return buf:len() - -end - -function add_avatar_data_subtrees(avatar_data) - if avatar_data["has_flags"] then - subtree:add(f_avatar_data_has_flags, avatar_data["has_flags"]) - end - if avatar_data["position"] then - subtree:add(f_avatar_data_position, avatar_data["position"]) - end - if avatar_data["dimensions"] then - subtree:add(f_avatar_data_dimensions, avatar_data["dimensions"]) - end - if avatar_data["offset"] then - subtree:add(f_avatar_data_offset, avatar_data["offset"]) - end - if avatar_data["look_at_position"] then - subtree:add(f_avatar_data_look_at_position, avatar_data["look_at_position"]) - end - if avatar_data["audio_loudness"] then - subtree:add(f_avatar_data_audio_loudness, avatar_data["audio_loudness"]) - end - if avatar_data["additional_flags"] then - subtree:add(f_avatar_data_additional_flags, avatar_data["additional_flags"]) - end - if avatar_data["parent_id"] then - subtree:add(f_avatar_data_parent_id, avatar_data["parent_id"]) - end - if avatar_data["parent_joint_index"] then - subtree:add(f_avatar_data_parent_joint_index, avatar_data["parent_joint_index"]) - end - if avatar_data["local_position"] then - subtree:add(f_avatar_data_local_position, avatar_data["local_position"]) - end - if avatar_data["valid_rotations"] then - subtree:add(f_avatar_data_valid_rotations, avatar_data["valid_rotations"]) - end - if avatar_data["valid_translations"] then - subtree:add(f_avatar_data_valid_translations, avatar_data["valid_translations"]) - end - if avatar_data["default_rotations"] then - subtree:add(f_avatar_data_default_rotations, avatar_data["default_rotations"]) - end - if avatar_data["default_translations"] then - subtree:add(f_avatar_data_default_translations, avatar_data["default_translations"]) - end -end - -function decode_vec3(buf) - local i = 0 - local x = buf(i, 4):le_float() - i = i + 4 - local y = buf(i, 4):le_float() - i = i + 4 - local z = buf(i, 4):le_float() - i = i + 4 - return {x, y, z} -end - -function decode_validity_bits(buf, num_bits) - -- first pass, decode each bit into an array of booleans - local i = 0 - local bit = 0 - local booleans = {} - for n = 1, num_bits do - local value = (bit32.band(buf(i, 1):uint(), bit32.lshift(1, bit)) ~= 0) - booleans[#booleans + 1] = value - bit = bit + 1 - if bit == 8 then - i = i + 1 - bit = 0 - end - end - - -- second pass, create a list of indices whos booleans are true - local result = {} - for n = 1, #booleans do - if booleans[n] then - result[#result + 1] = n - end - end - - return result -end - -function decode_avatar_data_packet(buf) - - local i = 0 - local result = {} - - -- uint16 has_flags - local has_flags = buf(i, 2):le_uint() - i = i + 2 - - local has_global_position = (bit32.band(has_flags, 1) ~= 0) - local has_bounding_box = (bit32.band(has_flags, 2) ~= 0) - local has_orientation = (bit32.band(has_flags, 4) ~= 0) - local has_scale = (bit32.band(has_flags, 8) ~= 0) - local has_look_at_position = (bit32.band(has_flags, 16) ~= 0) - local has_audio_loudness = (bit32.band(has_flags, 32) ~= 0) - local has_sensor_to_world_matrix = (bit32.band(has_flags, 64) ~= 0) - local has_additional_flags = (bit32.band(has_flags, 128) ~= 0) - local has_parent_info = (bit32.band(has_flags, 256) ~= 0) - local has_local_position = (bit32.band(has_flags, 512) ~= 0) - local has_face_tracker_info = (bit32.band(has_flags, 1024) ~= 0) - local has_joint_data = (bit32.band(has_flags, 2048) ~= 0) - local has_joint_default_pose_flags = (bit32.band(has_flags, 4096) ~= 0) - - result["has_flags"] = string.format("HasFlags: 0x%x", has_flags) - - if has_global_position then - local position = decode_vec3(buf(i, 12)) - result["position"] = string.format("Position: %.3f, %.3f, %.3f", position[1], position[2], position[3]) - i = i + 12 - end - - if has_bounding_box then - local dimensions = decode_vec3(buf(i, 12)) - i = i + 12 - local offset = decode_vec3(buf(i, 12)) - i = i + 12 - result["dimensions"] = string.format("Dimensions: %.3f, %.3f, %.3f", dimensions[1], dimensions[2], dimensions[3]) - result["offset"] = string.format("Offset: %.3f, %.3f, %.3f", offset[1], offset[2], offset[3]) - end - - if has_orientation then - -- TODO: orientation is hard to decode... - i = i + 6 - end - - if has_scale then - -- TODO: scale is hard to decode... - i = i + 2 - end - - if has_look_at_position then - local look_at = decode_vec3(buf(i, 12)) - i = i + 12 - result["look_at_position"] = string.format("Look At Position: %.3f, %.3f, %.3f", look_at[1], look_at[2], look_at[3]) - end - - if has_audio_loudness then - local loudness = buf(i, 1):uint() - i = i + 1 - result["audio_loudness"] = string.format("Audio Loudness: %d", loudness) - end - - if has_sensor_to_world_matrix then - -- TODO: sensor to world matrix is hard to decode - i = i + 20 - end - - if has_additional_flags then - local flags = buf(i, 1):uint() - i = i + 1 - result["additional_flags"] = string.format("Additional Flags: 0x%x", flags) - end - - if has_parent_info then - local parent_id = buf(i, 16) - i = i + 16 - local parent_joint_index = buf(i, 2):le_int() - i = i + 2 - result["parent_id"] = parent_id - result["parent_joint_index"] = string.format("Parent Joint Index: %d", parent_joint_index) - end - - if has_local_position then - local local_pos = decode_vec3(buf(i, 12)) - i = i + 12 - result["local_position"] = string.format("Local Position: %.3f, %.3f, %.3f", local_pos[1], local_pos[2], local_pos[3]) - end - - if has_face_tracker_info then - local left_eye_blink = buf(i, 4):le_float() - i = i + 4 - local right_eye_blink = buf(i, 4):le_float() - i = i + 4 - local average_loudness = buf(i, 4):le_float() - i = i + 4 - local brow_audio_lift = buf(i, 4):le_float() - i = i + 4 - local num_blendshape_coefficients = buf(i, 1):uint() - i = i + 1 - local blendshape_coefficients = {} - for n = 1, num_blendshape_coefficients do - blendshape_coefficients[n] = buf(i, 4):le_float() - i = i + 4 - end - -- TODO: insert blendshapes into result - end - - if has_joint_data then - - local num_joints = buf(i, 1):uint() - i = i + 1 - local num_validity_bytes = math.ceil(num_joints / 8) - - local indices = decode_validity_bits(buf(i, num_validity_bytes), num_joints) - i = i + num_validity_bytes - result["valid_rotations"] = "Valid Rotations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" - - -- TODO: skip rotations for now - i = i + #indices * 6 - - indices = decode_validity_bits(buf(i, num_validity_bytes), num_joints) - i = i + num_validity_bytes - result["valid_translations"] = "Valid Translations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" - - -- TODO: skip translations for now - i = i + #indices * 6 - - -- TODO: skip hand controller data - i = i + 24 - - end - - if has_joint_default_pose_flags then - local num_joints = buf(i, 1):uint() - i = i + 1 - local num_validity_bytes = math.ceil(num_joints / 8) - - local indices = decode_validity_bits(buf(i, num_validity_bytes), num_joints) - i = i + num_validity_bytes - result["default_rotations"] = "Default Rotations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" - - indices = decode_validity_bits(buf(i, num_validity_bytes), num_joints) - i = i + num_validity_bytes - result["default_translations"] = "Default Translations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" - end - - return result -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