indexing description: "Send/parse message to/from wire using binary format v1/v2" license: "MIT license (see ../../license.txt)" author: "Beat Strasser " date: "$Date$" revision: "$Revision$" class P2P_WIRE_MESSAGE_BINARY inherit P2P_WIRE_MESSAGE P2P_EXCEPTION_LOG EXCEPTIONS export {NONE} all end PLATFORM export {NONE} all end create make_from_message, make_from_wire feature {NONE} -- Initialization make_from_wire (medium: NETWORK_SOCKET; max_wait: INTEGER; a_checker: like checker) is -- Create message from wire -- `a_checker' is a query function returning true whenever reading process should stop. local version: INTEGER_8 do if not has_failed then esocket_make_non_blocking (medium, max_wait) if a_checker /= Void then set_checking_restraint (a_checker) end -- read signature read_chars (msg_signature.count) if not msg_signature.is_equal (last_string) then raise ("Invalid message signature") end -- read version read_byte_1 version := last_integer_8 if version /= msg_version_1 and version /= msg_version_2 then raise ("Unsupported message format version") end if version = msg_version_2 then -- read flags (UTF-16BE / UTF-32BE: unsupported) read_byte_1 if last_integer_8 /= 0 then raise ("Unsupported message flags") end end -- create empty message create message.make -- read namestable read_namestable -- read elements if version = msg_version_1 then read_elements_v1 else read_elements_v2 end end rescue has_failed := True log_exceptions last_error_message := developer_exception_name message := Void id_to_namespace := Void retry end feature -- Access Version_1: INTEGER is 1 -- Version number 1, used for is_processable and send_to_wire Version_2: INTEGER is 2 -- Version number 2, used for is_processable and send_to_wire is_processable (version: INTEGER): BOOLEAN is -- Is `version' processable? do Result := version = Version_1 or version = Version_2 end feature -- Basic operations send_to_buffer (version: INTEGER) is -- Send representation of message in given version to wire buffer do if not has_failed then empty_buffer if version = 2 then send_to_buffer_v2 else send_to_buffer_v1 end end rescue has_failed := True namespace_to_id := Void log_exceptions retry end send_buffer (medium: NETWORK_SOCKET) is -- Send buffer to wire do if not has_failed then socket := medium esocket_send_buffer end rescue has_failed := True log_exceptions retry end feature {NONE} -- Implementation send_to_buffer_v1 is -- Send representation of message in version 1 to wire buffer do -- emit signature write_chars (msg_signature) -- emit version write_byte_1 (msg_version_1) -- namestable send_namestable -- elements send_elements (agent send_element_v1) end send_to_buffer_v2 is -- Send representation of message in version 2 to wire buffer do -- emit signature write_chars (msg_signature) -- emit version write_byte_1 (msg_version_2) -- flags (UTF-16BE / UTF-32BE): unsupported write_byte_1 (0) -- namestable send_namestable -- elements send_elements (agent send_element_v2) end Msg_signature: STRING is "jxmg" Msg_version_1: INTEGER_8 is 0 Msg_version_2: INTEGER_8 is 1 Msg_flag2_utf16be: INTEGER_8 is 0x01 Msg_flag2_utf32be: INTEGER_8 is 0x02 El_signature: STRING is "jxel" El_flag1_mimetype: INTEGER_8 is 0x01 El_flag1_encoding: INTEGER_8 is 0x02 El_flag1_signature: INTEGER_8 is 0x04 El_flag2_bodylength_uint64: INTEGER_8 is 0x01 El_flag2_name_literal: INTEGER_8 is 0x02 El_flag2_mimetype: INTEGER_8 is 0x04 El_flag2_signature: INTEGER_8 is 0x08 El_flag2_encodings: INTEGER_8 is 0x10 El_flag2_signature_body: INTEGER_8 is 0x20 Msg_el_name_empty: INTEGER_16 is 0 namespace_id_user: INTEGER_16 is 0 Namespace_id_jxta: INTEGER_16 is 1 Standard_namespaces_count: INTEGER is 2 send_namestable is -- Internalize namespaces and send namestable to wire local list: DS_LIST [STRING] do list := message.special_namespaces init_namespace_to_id (list.count) -- namespaces count write_byte_2 (list.count.as_integer_16) -- loop through namespaces from list.start until list.after loop namespace_to_id.force (namespace_to_id.count, list.item_for_iteration) -- namespace string length write_byte_2 (list.item_for_iteration.count.as_integer_16) -- namespace chars write_chars (list.item_for_iteration) list.forth end end namespace_to_id: DS_HASH_TABLE [INTEGER, STRING] id_to_namespace: ARRAY [STRING] init_namespace_to_id (special_count: INTEGER) is -- Initialize namespaces hash with standard namespaces require Number_valid: special_count >= 0 Message_existent: message /= Void local string_equality_tester: KL_EQUALITY_TESTER [STRING] do create namespace_to_id.make (special_count + standard_namespaces_count) create string_equality_tester namespace_to_id.set_key_equality_tester (string_equality_tester) namespace_to_id.force (namespace_id_user, message.namespace_user) namespace_to_id.force (namespace_id_jxta, message.namespace_jxta) end init_id_to_namespace (special_count: INTEGER) is -- Initialize namespaces array with standard namespaces require Number_valid: special_count >= 0 Message_existent: message /= Void do create id_to_namespace.make (0, special_count + standard_namespaces_count) id_to_namespace[namespace_id_user] := message.namespace_user id_to_namespace[namespace_id_jxta] := message.namespace_jxta end send_elements (send_element: PROCEDURE [ANY, TUPLE [P2P_MESSAGE_ELEMENT]]) is -- Send elements to medium require Agent_set: send_element /= Void local elements: DS_LIST [P2P_MESSAGE_ELEMENT] do elements := message.all_elements -- elements count write_byte_2 (elements.count.as_integer_16) -- send each element from elements.start until elements.after loop send_element.call ([elements.item_for_iteration]) elements.forth end end send_element_v1 (element: P2P_MESSAGE_ELEMENT) is -- Send a single element (version 1) require Element_existent: element /= Void local flags: INTEGER_8 do -- signature write_chars (el_signature) -- namespaceid write_byte_1 (namespace_to_id.item (element.namespace).as_integer_8) -- flags if element.type /= Void then flags := flags.bit_or (el_flag1_mimetype) end if element.signature /= Void then flags := flags.bit_or (el_flag1_signature) end write_byte_1 (flags) -- name if element.name /= Void then write_byte_2 (element.name.count.as_integer_16) write_chars (element.name) else write_byte_2 (msg_el_name_empty) end -- [type] if element.type /= Void then write_byte_2 (element.type.count.as_integer_16) write_chars (element.type) end -- [encodings]: unsupported -- elementLen (len4) write_byte_4 (element.content.count.as_integer_32) -- content write_chars (element.content) -- [signature] if element.signature /= Void then send_element_v1 (element.signature) end end send_element_v2 (element: P2P_MESSAGE_ELEMENT) is -- Send a single element (version 2) require Element_existent: element /= Void local flags: INTEGER_8 do -- signature write_chars (el_signature) -- flags if element.name /= Void then flags := flags.bit_or (el_flag2_name_literal) end if element.type /= Void then flags := flags.bit_or (el_flag2_mimetype) end if element.signature /= Void then flags := flags.bit_or (el_flag2_signature) end write_byte_1 (flags) -- namespaceid write_byte_2 (namespace_to_id.item (element.namespace).as_integer_16) -- name if element.name /= Void then write_byte_2 (element.name.count.as_integer_16) write_chars (element.name) else write_byte_2 (msg_el_name_empty) end -- [type] if element.type /= Void then write_byte_2 (element.type.count.as_integer_16) write_chars (element.type) end -- [encodings]: unsupported -- elementLen (len4) write_byte_4 (element.content.count.as_integer_32) -- content write_chars (element.content) -- [signature] if element.signature /= Void then send_element_v2 (element.signature) end end read_namestable is -- Read namestable from medium and internalize namespaces local nscount: INTEGER i: INTEGER do -- namespaces count read_byte_2 init_id_to_namespace (last_integer_16) nscount := last_integer_16 + standard_namespaces_count -- loop through namespaces from i := standard_namespaces_count until i >= nscount loop -- namespace string length read_byte_2 -- namespace chars read_chars (last_integer_16) id_to_namespace[i] := last_string i := i + 1 end end read_elements_v1 is -- Read elements (version 1) local el_count, i: INTEGER do -- elements count read_byte_2 el_count := last_integer_16 -- read each element from i := 0 until i >= el_count loop read_element_v1 message.extend (last_element) i := i + 1 end end read_elements_v2 is -- Read elements (version 2) local el_count, i: INTEGER do -- elements count read_byte_2 el_count := last_integer_16 -- read each element from i := 0 until i >= el_count loop read_element_v2 message.extend (last_element) i := i + 1 end end read_element_v1 is -- Read a single element and add element to message (version 1) local flags: INTEGER_8 namespace, name, type, content: STRING do -- signature read_chars (el_signature.count) if not el_signature.is_equal (last_string) then raise ("Invalid message element signature") end -- namespaceid read_byte_1 if not id_to_namespace.valid_index (last_integer_8) then raise ("Invalid namespace id") end namespace := id_to_namespace[last_integer_8] -- flags read_byte_1 flags := last_integer_8 if flags.bit_and (el_flag1_encoding).to_boolean then raise ("Unsupported flags set") end -- name read_byte_2 read_chars (last_integer_16) name := last_string -- [type] if flags.bit_and (el_flag1_mimetype).to_boolean then read_byte_2 read_chars (last_integer_16) type := last_string end -- [encodings]: unsupported -- elementLen (len4) read_byte_4 -- content read_chars (last_integer_32) content := last_string -- [signature] last_element := Void if flags.bit_and (el_flag1_signature).to_boolean then read_element_v1 end -- create element create last_element.make (namespace, name, type, last_element, content) end read_element_v2 is -- Read a single element and add element to message (version 2) local flags: INTEGER_8 namespace, name, type, content: STRING do -- signature read_chars (el_signature.count) if not el_signature.is_equal (last_string) then raise ("Invalid message element signature") end -- flags read_byte_1 flags := last_integer_8 if flags.bit_and (el_flag2_bodylength_uint64).to_boolean or flags.bit_and (el_flag2_encodings).to_boolean or flags.bit_and (el_flag2_signature_body).to_boolean then raise ("Unsupported flags set") end -- namespaceid read_byte_2 if not id_to_namespace.valid_index (last_integer_16) then raise ("Invalid namespace id") end namespace := id_to_namespace[last_integer_16] -- name if flags.bit_and (el_flag2_name_literal).to_boolean then read_byte_2 read_chars (last_integer_16) name := last_string else read_byte_2 if msg_el_name_empty /= last_integer_16 then raise ("No name expected") end end -- [type] if flags.bit_and (el_flag2_mimetype).to_boolean then read_byte_2 read_chars (last_integer_16) type := last_string end -- [encodings]: unsupported -- elementLen (len4) read_byte_4 -- content read_chars (last_integer_32) content := last_string -- [signature] last_element := Void if flags.bit_and (el_flag2_signature).to_boolean then read_element_v2 end -- create element create last_element.make (namespace, name, type, last_element, content) end last_element: P2P_MESSAGE_ELEMENT end