diff --git a/examples/ping-pong/README.md b/examples/ping-pong/README.md new file mode 100644 index 0000000..93710c3 --- /dev/null +++ b/examples/ping-pong/README.md @@ -0,0 +1,34 @@ +# ping-pong + +This example demonstrates how to connect two peers via ICE. Once started they send the current time between each other. + +Currently this example exchanges candidates over a HTTP server running on localhost. In a real world setup `pion/ice` will typically +exchange auth and candidates via a signaling server. + +## Instruction +### Run controlling + +```sh +go run main.go -controlling +``` + + +### Run controlled + +```sh +go run main.go +``` + +### Press enter in both to start the connection! + +You will see terminal output showing the messages being sent back and forth +``` +Local Agent is controlled +Press 'Enter' when both processes have started +ICE Connection State has changed: Checking +ICE Connection State has changed: Connected +Sent: 'fCFXXlnGmXdYjOy' +Received: 'EpqTQYLQMUCjBDX' +Sent: 'yhgOtrufSfVmvrR' +Received: 'xYSTPxBPZKfgnFr' +``` diff --git a/examples/ping-pong/main.go b/examples/ping-pong/main.go new file mode 100644 index 0000000..bd80864 --- /dev/null +++ b/examples/ping-pong/main.go @@ -0,0 +1,177 @@ +package main + +import ( + "bufio" + "context" + "flag" + "fmt" + "net/http" + "net/url" + "os" + "time" + + "github.com/pion/ice/v2" + "github.com/pion/randutil" +) + +var ( + isControlling bool + iceAgent *ice.Agent + remoteAuthChannel chan string + localHTTPPort, remoteHTTPPort int +) + +// HTTP Listener to get ICE Credentials from remote Peer +func remoteAuth(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + panic(err) + } + + remoteAuthChannel <- r.PostForm["ufrag"][0] + remoteAuthChannel <- r.PostForm["pwd"][0] + +} + +// HTTP Listener to get ICE Candidate from remote Peer +func remoteCandidate(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + panic(err) + } + + c, err := ice.UnmarshalCandidate(r.PostForm["candidate"][0]) + if err != nil { + panic(err) + } + + if err := iceAgent.AddRemoteCandidate(c); err != nil { + panic(err) + } +} + +func main() { + var ( + err error + conn *ice.Conn + ) + remoteAuthChannel = make(chan string, 3) + + flag.BoolVar(&isControlling, "controlling", false, "is ICE Agent controlling") + flag.Parse() + + if isControlling { + localHTTPPort = 9000 + remoteHTTPPort = 9001 + } else { + localHTTPPort = 9001 + remoteHTTPPort = 9000 + } + + http.HandleFunc("/remoteAuth", remoteAuth) + http.HandleFunc("/remoteCandidate", remoteCandidate) + go func() { + if err = http.ListenAndServe(fmt.Sprintf(":%d", localHTTPPort), nil); err != nil { + panic(err) + } + }() + + if isControlling { + fmt.Println("Local Agent is controlling") + } else { + fmt.Println("Local Agent is controlled") + } + fmt.Print("Press 'Enter' when both processes have started") + if _, err = bufio.NewReader(os.Stdin).ReadBytes('\n'); err != nil { + panic(err) + } + + iceAgent, err = ice.NewAgent(&ice.AgentConfig{ + NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4}, + }) + if err != nil { + panic(err) + } + + // When we have gathered a new ICE Candidate send it to the remote peer + if err = iceAgent.OnCandidate(func(c ice.Candidate) { + if c == nil { + return + } + + _, err = http.PostForm(fmt.Sprintf("http://localhost:%d/remoteCandidate", remoteHTTPPort), + url.Values{ + "candidate": {c.Marshal()}, + }) + if err != nil { + panic(err) + } + }); err != nil { + panic(err) + } + + // When ICE Connection state has change print to stdout + if err = iceAgent.OnConnectionStateChange(func(c ice.ConnectionState) { + fmt.Printf("ICE Connection State has changed: %s\n", c.String()) + }); err != nil { + panic(err) + } + + // Get the local auth details and send to remote peer + localUfrag, localPwd, err := iceAgent.GetLocalUserCredentials() + if err != nil { + panic(err) + } + + _, err = http.PostForm(fmt.Sprintf("http://localhost:%d/remoteAuth", remoteHTTPPort), + url.Values{ + "ufrag": {localUfrag}, + "pwd": {localPwd}, + }) + if err != nil { + panic(err) + } + + remoteUfrag := <-remoteAuthChannel + remotePwd := <-remoteAuthChannel + + if err = iceAgent.GatherCandidates(); err != nil { + panic(err) + } + + // Start the ICE Agent. One side must be controlled, and the other must be controlling + if isControlling { + conn, err = iceAgent.Dial(context.TODO(), remoteUfrag, remotePwd) + } else { + conn, err = iceAgent.Accept(context.TODO(), remoteUfrag, remotePwd) + } + if err != nil { + panic(err) + } + + // Send messages in a loop to the remote peer + go func() { + for { + time.Sleep(time.Second * 3) + + val, err := randutil.GenerateCryptoRandomString(15, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + if err != nil { + panic(err) + } + if _, err = conn.Write([]byte(val)); err != nil { + panic(err) + } + + fmt.Printf("Sent: '%s'\n", val) + } + }() + + // Receive messages in a loop from the remote peer + buf := make([]byte, 1500) + for { + n, err := conn.Read(buf) + if err != nil { + panic(err) + } + + fmt.Printf("Received: '%s'\n", string(buf[:n])) + } +}