@@ -2217,8 +2217,10 @@ initialize_receiver_session_from_x3dh(FromUsername, MessagePayload, State) ->
22172217 maps :get (<<" metadata" >>, MessagePayload )
22182218 ),
22192219
2220- % Decode metadata to extract sender identity and other info
2221- Metadata = erlang :binary_to_term (EncodedMetadata ),
2220+ % Decode metadata - try ETF first, then JSON for mobile clients
2221+ {Metadata , MetadataFormat } = decode_x3dh_metadata (EncodedMetadata ),
2222+ ? dbg (" Decoded X3DH metadata (format: ~p ): ~p~n " , [MetadataFormat , Metadata ]),
2223+
22222224 SenderIdPub = maps :get (sender_identity_sign_public , Metadata ),
22232225
22242226 % Extract receiver keys from state
@@ -2262,6 +2264,8 @@ initialize_receiver_session_from_x3dh(FromUsername, MessagePayload, State) ->
22622264
22632265 MessageBlob = #{
22642266 metadata => Metadata ,
2267+ metadata_bytes => EncodedMetadata , % Original bytes for signature verification
2268+ metadata_format => MetadataFormat , % 'etf' or 'json'
22652269 signature => Signature ,
22662270 ciphertext => Ciphertext ,
22672271 nonce => Nonce
@@ -2342,6 +2346,41 @@ initialize_receiver_session_from_x3dh(FromUsername, MessagePayload, State) ->
23422346 {error , {exception , ErrorClass , ErrorReason }}
23432347 end .
23442348
2349+ % % @private
2350+ % % @doc Decode X3DH metadata from either ETF (Erlang clients) or JSON (mobile clients)
2351+ % % Returns {Metadata, Format} where Format is 'etf' or 'json'
2352+ decode_x3dh_metadata (EncodedMetadata ) ->
2353+ % Try ETF first (Erlang native format)
2354+ try
2355+ EtfMetadata = erlang :binary_to_term (EncodedMetadata ),
2356+ {EtfMetadata , etf }
2357+ catch
2358+ error :badarg ->
2359+ % Try JSON (mobile client format)
2360+ JsonMap = jsx :decode (EncodedMetadata , [return_maps ]),
2361+ JsonMetadata = convert_json_metadata_to_erlang (JsonMap ),
2362+ {JsonMetadata , json }
2363+ end .
2364+
2365+ % % @private
2366+ % % @doc Convert JSON metadata map with binary keys to Erlang metadata with atom keys
2367+ convert_json_metadata_to_erlang (JsonMap ) ->
2368+ #{
2369+ version => maps :get (<<" version" >>, JsonMap ),
2370+ type => maps :get (<<" type" >>, JsonMap ),
2371+ sender_id => base64 :decode (maps :get (<<" sender_id" >>, JsonMap )),
2372+ sender_identity_dh_public => base64 :decode (maps :get (<<" sender_identity_dh_public" >>, JsonMap )),
2373+ sender_identity_sign_public => base64 :decode (maps :get (<<" sender_identity_sign_public" >>, JsonMap )),
2374+ recipient_id => base64 :decode (maps :get (<<" recipient_id" >>, JsonMap )),
2375+ ephemeral_public => base64 :decode (maps :get (<<" ephemeral_public" >>, JsonMap )),
2376+ otpk_id => case maps :get (<<" otpk_id" >>, JsonMap , null ) of
2377+ null -> undefined ;
2378+ OtpkIdB64 -> base64 :decode (OtpkIdB64 )
2379+ end ,
2380+ message_id => base64 :decode (maps :get (<<" message_id" >>, JsonMap )),
2381+ timestamp => maps :get (<<" timestamp" >>, JsonMap )
2382+ }.
2383+
23452384% % @private
23462385% % @doc Initialize ratchet session as receiver after X3DH
23472386% % If SenderRatchetDHPub is provided, the receiver can immediately send (bidirectional)
0 commit comments