From ea91cbd8ca16ce6780de13fcb14fae98b79a1e24 Mon Sep 17 00:00:00 2001 From: Simon Eisenmann Date: Sun, 25 May 2014 00:37:19 +0200 Subject: [PATCH] Defined and implemented contact request/response protocol and data structure. --- doc/CHANNELING-API.txt | 79 ++++++++++++++++++++-- src/app/spreed-webrtc-server/channeling.go | 30 ++++++-- src/app/spreed-webrtc-server/contact.go | 30 ++++++++ src/app/spreed-webrtc-server/hub.go | 79 ++++++++++++++++++++++ src/app/spreed-webrtc-server/server.go | 15 +++- 5 files changed, 221 insertions(+), 12 deletions(-) create mode 100644 src/app/spreed-webrtc-server/contact.go diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index b4624662..50a07ba6 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -303,11 +303,7 @@ Additional types for session listing and notifications Users (Request uses empty data) { - "Type": "Users", - "Users": { - "Type": "Users", - "Users": {} - } + "Type": "Users" } Users (Response with data) @@ -385,6 +381,37 @@ User authorization and session authentication the session (disconnect) and forget the token. +Information retrieval + + Sessions (Request uses Id, Token and Type) + + { + "Type": "Sessions", + "Sessions": { + "Id": "Client generated request ID", + "Token": "Request token", + "TokenType": "Token type" + } + } + + Valid known token types are: "contact", "token". + + Sesions (Response with Id, Token and Type from request and + populated Session list). + + { + "Type": "Sessions", + "Sessions": { + "Id": "ID as from request", + "Token": "Token as from request", + "TokenType": "Type as from request", + "Sessions": [ + "1", "2", "3", ... + ] + } + } + + Chat messages and status information The chat is used to transfer simple messages ore more complex structures @@ -457,6 +484,48 @@ Chat messages and status information message is shown or not. For file transfer information the message is always "File". + Chat with contact request/confirm information + + Request to create a contact token with Id. + + { + "Message": "Some message", + "Time": "2013-11-20T16:28:42+01:00", + "Status": { + "ContactRequest": { + "Id": "client-generated-id" + } + } + } + + Reply with success (Sever generates and inserts token). + + { + "Message": "Some response message", + "Time": "2013-11-20T16:28:59+01:00", + "Status": { + "ContactRequest": { + "Id": "request-id", + "Success": true, + "Token": "server-generated-token-on-success" + } + } + } + + Or reject (no reply is also possible). + + { + "Message": "Some response message", + "Time": "2013-11-20T16:28:59+01:00", + "Status": { + "ContactRequest": { + "Id": "request-id", + "Success": false + } + } + } + + Chat message deliver status extensions Send chat messages as normal, but add the "Mid" field which is a diff --git a/src/app/spreed-webrtc-server/channeling.go b/src/app/spreed-webrtc-server/channeling.go index c5e96f3a..b281a785 100644 --- a/src/app/spreed-webrtc-server/channeling.go +++ b/src/app/spreed-webrtc-server/channeling.go @@ -92,16 +92,34 @@ type DataChat struct { } type DataChatMessage struct { - Mid string Message string Time string - NoEcho bool `json:"NoEcho,omitempty"` - Status interface{} + NoEcho bool `json:",omitempty"` + Mid string `json:",omitempty"` + Status *DataChatStatus +} + +type DataChatStatus struct { + Typing string `json:",omitempty"` + State string `json:",omitempty"` + Mid string `json:",omitempty"` + SeenMids []string `json:",omitempty"` + FileInfo *DataFileInfo `json:",omitempty"` + ContactRequest *DataContactRequest `json:",omitempty"` } -type DataChatMessageStatus struct { - State string - Mid string +type DataFileInfo struct { + Id string `json:"id"` + Chunks uint64 `json:"chunks"` + Name string `json:"name"` + Size uint64 `json:"size"` + Type string `json:"type"` +} + +type DataContactRequest struct { + Id string + Success bool + Token string `json:",omitempty"` } type DataIncoming struct { diff --git a/src/app/spreed-webrtc-server/contact.go b/src/app/spreed-webrtc-server/contact.go new file mode 100644 index 00000000..f3ebaa09 --- /dev/null +++ b/src/app/spreed-webrtc-server/contact.go @@ -0,0 +1,30 @@ +/* + * Spreed WebRTC. + * Copyright (C) 2013-2014 struktur AG + * + * This file is part of Spreed WebRTC. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +package main + +import () + +type Contact struct { + a string + b string + ok bool +} diff --git a/src/app/spreed-webrtc-server/hub.go b/src/app/spreed-webrtc-server/hub.go index 68ab5b26..f9848303 100644 --- a/src/app/spreed-webrtc-server/hub.go +++ b/src/app/spreed-webrtc-server/hub.go @@ -83,6 +83,7 @@ type Hub struct { realm string tokenName string useridRetriever func(*http.Request) (string, error) + contacts *securecookie.SecureCookie } func NewHub(version string, config *Config, sessionSecret, turnSecret, realm string) *Hub { @@ -106,6 +107,8 @@ func NewHub(version string, config *Config, sessionSecret, turnSecret, realm str h.buffers = NewBufferCache(1024, bytes.MinRead) h.buddyImages = NewImageCache() h.tokenName = fmt.Sprintf("token@%s", h.realm) + h.contacts = securecookie.New(h.sessionSecret, nil) + h.contacts.MaxAge(0) return h } @@ -423,3 +426,79 @@ func (h *Hub) sessiontokenHandler(st *SessionToken) (string, error) { return nonce, nil } + +func (h *Hub) contactrequestHandler(c *Connection, to string, cr *DataContactRequest) error { + + var err error + + if cr.Success { + // Client replied with success. + // Decode Token and make sure c.Session.Userid and the to Session.UserId are a match. + contact := &Contact{} + err = h.contacts.Decode("contactRequest", cr.Token, contact) + if err != nil { + return err + } + if contact.ok { + return errors.New("received success with ok state set") + } + bSessionData := c.Session.Data() + if bSessionData.Userid == "" { + return errors.New("no userid") + } + h.mutex.RLock() + aSession, ok := h.sessionTable[to] + h.mutex.RUnlock() + if !ok { + return errors.New("unknown to session for confirm") + } + aSessionData := aSession.Data() + if aSessionData.Userid == "" { + return errors.New("to has no userid for confirm") + } + if aSessionData.Userid != contact.a { + return errors.New("contact mismatch in a") + } + if bSessionData.Userid != contact.b { + return errors.New("contact mismatch in b") + } + contact.ok = true + cr.Token, err = h.contacts.Encode("contactConfirmed", contact) + } else { + if cr.Token != "" { + // Client replied with no success. + // Decode Token. + contact := &Contact{} + err = h.contacts.Decode("contactRequest", cr.Token, contact) + if err != nil { + return err + } + // Remove token. + cr.Token = "" + } else { + // New request. + // Create Token with flag and c.Session.Userid and the to Session.Userid. + aSessionData := c.Session.Data() + if aSessionData.Userid == "" { + return errors.New("no userid") + } + h.mutex.RLock() + bSession, ok := h.sessionTable[to] + h.mutex.RUnlock() + if !ok { + return errors.New("unknown to session") + } + bSessionData := bSession.Data() + if bSessionData.Userid == "" { + return errors.New("to has no userid") + } + // Create object. + contact := &Contact{aSessionData.Userid, bSessionData.Userid, false} + // Serialize. + cr.Token, err = h.contacts.Encode("contactRequest", contact) + } + } + + return err + +} diff --git a/src/app/spreed-webrtc-server/server.go b/src/app/spreed-webrtc-server/server.go index fb45e090..4cd77ab1 100644 --- a/src/app/spreed-webrtc-server/server.go +++ b/src/app/spreed-webrtc-server/server.go @@ -137,11 +137,18 @@ func (s *Server) OnText(c *Connection, b Buffer) { s.Broadcast(c, msg.Chat) } } else { + if msg.Chat.Chat.Status.ContactRequest != nil { + err = s.ContactRequest(c, msg.Chat.To, msg.Chat.Chat.Status.ContactRequest) + if err != nil { + log.Println("Ignoring invalid contact request.", err) + return + } + } atomic.AddUint64(&c.h.unicastChatMessages, 1) s.Unicast(c, msg.Chat.To, msg.Chat) if msg.Chat.Chat.Mid != "" { // Send out delivery confirmation status chat message. - s.Unicast(c, c.Id, &DataChat{To: msg.Chat.To, Type: "Chat", Chat: &DataChatMessage{Mid: msg.Chat.Chat.Mid, Status: &DataChatMessageStatus{State: "sent"}}}) + s.Unicast(c, c.Id, &DataChat{To: msg.Chat.To, Type: "Chat", Chat: &DataChatMessage{Mid: msg.Chat.Chat.Mid, Status: &DataChatStatus{State: "sent"}}}) } } case "Conference": @@ -194,6 +201,12 @@ func (s *Server) UpdateSession(c *Connection, su *SessionUpdate) uint64 { } +func (s *Server) ContactRequest(c *Connection, to string, cr *DataContactRequest) (err error) { + + return c.h.contactrequestHandler(c, to, cr) + +} + func (s *Server) Broadcast(c *Connection, m interface{}) { b := c.h.buffers.New()