@@ -63,6 +63,7 @@ defmodule ExICE.Priv.ICEAgentTest do
6363 end
6464
6565 @ remote_cand ExICE.Candidate . new ( :host , address: { 192 , 168 , 0 , 2 } , port: 8445 )
66+ @ remote_cand2 ExICE.Candidate . new ( :host , address: { 192 , 168 , 0 , 3 } , port: 8445 )
6667
6768 describe "unmarshal_remote_candidate/1" do
6869 test "with correct candidate" do
@@ -644,6 +645,62 @@ defmodule ExICE.Priv.ICEAgentTest do
644645 assert_bad_request_error_response ( socket , request )
645646 end
646647
648+ test "with role conflict" , % { ice_agent: ice_agent , remote_cand: remote_cand } do
649+ [ socket ] = ice_agent . sockets
650+
651+ binding_request = fn tiebreaker ->
652+ Message . new ( % Type { class: :request , method: :binding } , [
653+ % Username { value: "#{ ice_agent . local_ufrag } :someufrag" } ,
654+ % Priority { priority: 1234 } ,
655+ % ICEControlling { tiebreaker: tiebreaker } ,
656+ % UseCandidate { }
657+ ] )
658+ |> Message . with_integrity ( ice_agent . local_pwd )
659+ |> Message . with_fingerprint ( )
660+ end
661+
662+ # feed binding request with higher tiebreaker
663+ request = binding_request . ( ice_agent . tiebreaker + 1 )
664+ raw_request = Message . encode ( request )
665+
666+ new_ice_agent =
667+ ICEAgent . handle_udp (
668+ ice_agent ,
669+ socket ,
670+ remote_cand . address ,
671+ remote_cand . port ,
672+ raw_request
673+ )
674+
675+ # agent should switch its role and send success response
676+ assert new_ice_agent . role == :controlled
677+ assert new_ice_agent . tiebreaker == ice_agent . tiebreaker
678+ assert packet = Transport.Mock . recv ( socket )
679+ assert { :ok , msg } = ExSTUN.Message . decode ( packet )
680+ assert msg . type == % ExSTUN.Message.Type { class: :success_response , method: :binding }
681+
682+ # feed binding request with smaller tiebreaker
683+ request = binding_request . ( ice_agent . tiebreaker - 1 )
684+ raw_request = Message . encode ( request )
685+
686+ new_ice_agent =
687+ ICEAgent . handle_udp (
688+ ice_agent ,
689+ socket ,
690+ remote_cand . address ,
691+ remote_cand . port ,
692+ raw_request
693+ )
694+
695+ # agent shouldn't switch its role and should send 487 error response
696+ assert new_ice_agent . role == :controlling
697+ assert new_ice_agent . tiebreaker == ice_agent . tiebreaker
698+ assert packet = Transport.Mock . recv ( socket )
699+ assert { :ok , msg } = ExSTUN.Message . decode ( packet )
700+ assert msg . type == % ExSTUN.Message.Type { class: :error_response , method: :binding }
701+ assert { :ok , % ErrorCode { code: 487 , reason: "" } } = Message . get_attribute ( msg , ErrorCode )
702+ end
703+
647704 test "without username" , % { ice_agent: ice_agent , remote_cand: remote_cand } do
648705 [ socket ] = ice_agent . sockets
649706
@@ -1157,6 +1214,80 @@ defmodule ExICE.Priv.ICEAgentTest do
11571214 assert new_pair . responses_received == pair . responses_received + 1
11581215 end
11591216
1217+ test "role conflict error response" do
1218+ ice_agent =
1219+ ICEAgent . new (
1220+ controlling_process: self ( ) ,
1221+ role: :controlling ,
1222+ if_discovery_module: IfDiscovery.Mock ,
1223+ transport_module: Transport.Mock
1224+ )
1225+ |> ICEAgent . set_remote_credentials ( "someufrag" , "somepwd" )
1226+ |> ICEAgent . gather_candidates ( )
1227+ |> ICEAgent . add_remote_candidate ( @ remote_cand )
1228+
1229+ [ socket ] = ice_agent . sockets
1230+
1231+ # trigger check
1232+ ice_agent = ICEAgent . handle_ta_timeout ( ice_agent )
1233+ [ pair1 ] = Map . values ( ice_agent . checklist )
1234+ req1 = read_binding_request ( socket , ice_agent . remote_pwd )
1235+
1236+ # Add the second candidate and trigger another check.
1237+ # We add candidate after generating the first check to be sure
1238+ # it is related to @remote_cand2.
1239+ ice_agent = ICEAgent . add_remote_candidate ( ice_agent , @ remote_cand2 )
1240+ ice_agent = ICEAgent . handle_ta_timeout ( ice_agent )
1241+ [ pair2 ] = Map . values ( ice_agent . checklist ) |> Enum . reject ( & ( & 1 . id == pair1 . id ) )
1242+ req2 = read_binding_request ( socket , ice_agent . remote_pwd )
1243+
1244+ # reply to the first check with role conflict error response
1245+ resp =
1246+ Message . new ( req1 . transaction_id , % Type { class: :error_response , method: :binding } , [
1247+ % ErrorCode { code: 487 }
1248+ ] )
1249+ |> Message . with_integrity ( ice_agent . remote_pwd )
1250+ |> Message . with_fingerprint ( )
1251+ |> Message . encode ( )
1252+
1253+ new_ice_agent =
1254+ ICEAgent . handle_udp (
1255+ ice_agent ,
1256+ socket ,
1257+ @ remote_cand . address ,
1258+ @ remote_cand . port ,
1259+ resp
1260+ )
1261+
1262+ # assert that the agent changed its role and tiebreaker
1263+ assert new_ice_agent . role != ice_agent . role
1264+ assert new_ice_agent . tiebreaker != ice_agent . tiebreaker
1265+ assert Map . fetch! ( new_ice_agent . checklist , pair1 . id ) . state == :waiting
1266+
1267+ # reply to the second check with role conflict error response
1268+ resp =
1269+ Message . new ( req2 . transaction_id , % Type { class: :error_response , method: :binding } , [
1270+ % ErrorCode { code: 487 }
1271+ ] )
1272+ |> Message . with_integrity ( new_ice_agent . remote_pwd )
1273+ |> Message . with_fingerprint ( )
1274+ |> Message . encode ( )
1275+
1276+ new_ice_agent2 =
1277+ ICEAgent . handle_udp (
1278+ new_ice_agent ,
1279+ socket ,
1280+ @ remote_cand2 . address ,
1281+ @ remote_cand2 . port ,
1282+ resp
1283+ )
1284+
1285+ # assert that agent didn't switch its role and tiebreaker
1286+ assert new_ice_agent2 . role == new_ice_agent . role
1287+ assert new_ice_agent2 . tiebreaker == new_ice_agent . tiebreaker
1288+ assert Map . fetch! ( new_ice_agent2 . checklist , pair2 . id ) . state == :waiting
1289+ end
1290+
11601291 test "unauthenticated error response" , % { ice_agent: ice_agent , remote_cand: remote_cand } do
11611292 [ socket ] = ice_agent . sockets
11621293 [ pair ] = Map . values ( ice_agent . checklist )
0 commit comments