Exemplo n.º 1
0
Arquivo: hub.go Projeto: ycaihua/chat
// replyTopicDescBasic loads minimal topic Desc when the requester is not subscribed to the topic
func replyTopicDescBasic(sess *Session, topic string, get *MsgClientGet) {
	log.Printf("hub.replyTopicDescBasic: topic %s", topic)
	now := time.Now().UTC().Round(time.Millisecond)
	desc := &MsgTopicDesc{}

	if strings.HasPrefix(topic, "grp") {
		stopic, err := store.Topics.Get(topic)
		if err == nil {
			desc.CreatedAt = &stopic.CreatedAt
			desc.UpdatedAt = &stopic.UpdatedAt
			desc.Public = stopic.Public
		} else {
			sess.queueOut(ErrUnknown(get.Id, get.Topic, now))
			return
		}
	} else {
		// 'me' and p2p topics
		var uid types.Uid
		if strings.HasPrefix(topic, "usr") {
			// User specified as usrXXX
			uid = types.ParseUserId(topic)
		} else if strings.HasPrefix(topic, "p2p") {
			// User specified as p2pXXXYYY
			uid1, uid2, _ := types.ParseP2P(topic)
			if uid1 == sess.uid {
				uid = uid2
			} else if uid2 == sess.uid {
				uid = uid1
			}
		}

		if uid.IsZero() {
			sess.queueOut(ErrMalformed(get.Id, get.Topic, now))
			return
		}

		suser, err := store.Users.Get(uid)
		if err == nil {
			desc.CreatedAt = &suser.CreatedAt
			desc.UpdatedAt = &suser.UpdatedAt
			desc.Public = suser.Public
		} else {
			log.Printf("hub.replyTopicInfoBasic: sending  error 3")
			sess.queueOut(ErrUnknown(get.Id, get.Topic, now))
			return
		}
	}

	log.Printf("hub.replyTopicDescBasic: sending desc -- OK")
	sess.queueOut(&ServerComMessage{
		Meta: &MsgServerMeta{Id: get.Id, Topic: get.Topic, Timestamp: &now, Desc: desc}})
}
Exemplo n.º 2
0
// validateTopicName expands session specific topic name to global name
// Returns
//   topic: session-specific topic name the message recepient should see
//   routeTo: routable global topic name
//   err: *ServerComMessage with an error to return to the sender
func (s *Session) validateTopicName(msgId, topic string, timestamp time.Time) (string, string, *ServerComMessage) {

	if topic == "" {
		return "", "", ErrMalformed(msgId, "", timestamp)
	}

	if !strings.HasPrefix(topic, "grp") && s.uid.IsZero() {
		// me and p2p topics require authentication
		return "", "", ErrAuthRequired(msgId, topic, timestamp)
	}

	// Topic to route to i.e. rcptto: or s.subs[routeTo]
	routeTo := topic

	if topic == "me" {
		routeTo = s.uid.UserId()
	} else if topic == "fnd" {
		routeTo = s.uid.FndName()
	} else if strings.HasPrefix(topic, "usr") {
		// Initiating a p2p topic
		uid2 := types.ParseUserId(topic)
		if uid2.IsZero() {
			// Ensure the user id is valid
			return "", "", ErrMalformed(msgId, topic, timestamp)
		} else if uid2 == s.uid {
			// Use 'me' to access self-topic
			return "", "", ErrPermissionDenied(msgId, topic, timestamp)
		}
		routeTo = s.uid.P2PName(uid2)
		topic = routeTo
	} else if strings.HasPrefix(topic, "p2p") {
		uid1, uid2, err := types.ParseP2P(topic)
		if err != nil || uid1.IsZero() || uid2.IsZero() || uid1 == uid2 {
			// Ensure the user ids are valid
			return "", "", ErrMalformed(msgId, topic, timestamp)
		} else if uid1 != s.uid && uid2 != s.uid {
			// One can't access someone else's p2p topic
			return "", "", ErrPermissionDenied(msgId, topic, timestamp)
		}
	}

	return topic, routeTo, nil
}
Exemplo n.º 3
0
// Save message
func (MessagesObjMapper) Save(msg *types.Message) error {
	msg.InitTimes()

	// Need a transaction here, RethinkDB does not support transactions

	// An invite (message to 'me') may have a zero SeqId if 'me' was inactive at the time of generating the invite
	if msg.SeqId == 0 {
		if user, err := adaptr.UserGet(types.ParseUserId(msg.Topic)); err != nil {
			return err
		} else {
			msg.SeqId = user.SeqId + 1
		}
	}

	if err := adaptr.TopicUpdateOnMessage(msg.Topic, msg); err != nil {
		return err
	}

	return adaptr.MessageSave(msg)
}
Exemplo n.º 4
0
// replySetSub is a response to new subscription request or an update to a subscription {set.sub}:
// update topic metadata cache, save/update subs, reply to the caller as {ctrl} message, generate an invite
func (t *Topic) replySetSub(h *Hub, sess *Session, set *MsgClientSet) error {
	now := time.Now().UTC().Round(time.Millisecond)

	var uid types.Uid
	if uid = types.ParseUserId(set.Sub.User); uid.IsZero() && set.Sub.User != "" {
		// Invalid user ID
		sess.queueOut(ErrMalformed(set.Id, set.Topic, now))
		return errors.New("invalid user id")
	}

	// if set.User is not set, request is for the current user
	if !uid.IsZero() {
		uid = sess.uid
	}

	if uid == sess.uid {
		return t.requestSub(h, sess, set.Id, set.Sub.Mode, set.Sub.Info, nil, false)
	} else {
		return t.approveSub(h, sess, uid, set)
	}
}
Exemplo n.º 5
0
func (t *Topic) run(hub *Hub) {

	log.Printf("Topic started: '%s'", t.name)

	keepAlive := TOPICTIMEOUT // TODO(gene): read keepalive value from the command line
	killTimer := time.NewTimer(time.Hour)
	killTimer.Stop()

	// 'me' only
	var uaTimer *time.Timer
	var currentUA string
	uaTimer = time.NewTimer(time.Minute)
	uaTimer.Stop()

	for {
		select {
		case sreg := <-t.reg:
			// Request to add a conection to this topic

			// The topic is alive, so stop the kill timer, if it's ticking. We don't want the topic to die
			// while processing the call
			killTimer.Stop()

			if err := t.handleSubscription(hub, sreg); err == nil {
				// give a broadcast channel to the connection (.read)
				// give channel to use when shutting down (.done)
				sreg.sess.subs[t.name] = &Subscription{
					broadcast: t.broadcast,
					done:      t.unreg,
					meta:      t.meta,
					ping:      t.uaChange}

				t.sessions[sreg.sess] = true

			} else if len(t.sessions) == 0 {
				// Failed to subscribe, the topic is still inactive
				killTimer.Reset(keepAlive)
			}
		case leave := <-t.unreg:
			// Remove connection from topic; session may continue to function

			if leave.unsub {
				// User wants to leave and unsubscribe.
				if err := t.replyLeaveUnsub(hub, leave.sess, leave.reqId, leave.topic); err != nil {
					log.Panicln(err)
					continue
				}

			} else {
				now := time.Now().UTC().Round(time.Millisecond)

				// Just leaving the topic without unsubscribing
				delete(t.sessions, leave.sess)

				pud := t.perUser[leave.sess.uid]
				pud.online--
				if t.cat == types.TopicCat_Me {
					mrs := t.mostRecentSession()
					if mrs == nil {
						// Last session
						mrs = leave.sess
					} else {
						// Change UA to the most recent live session
						currentUA = mrs.userAgent
						uaTimer.Reset(UA_TIMER_DELAY)
					}
					// Update user's last online timestamp & user agent
					if err := store.Users.UpdateLastSeen(mrs.uid, mrs.userAgent, now); err != nil {
						log.Println(err)
					}
				} else if t.cat == types.TopicCat_Grp && pud.online == 0 {
					t.presPubChange(leave.sess.uid, "off")
				}

				t.perUser[leave.sess.uid] = pud

				if leave.reqId != "" {
					leave.sess.queueOut(NoErr(leave.reqId, leave.topic, now))
				}
			}

			// If there are no more subscriptions to this topic, start a kill timer
			if len(t.sessions) == 0 {
				killTimer.Reset(keepAlive)
			}

		case msg := <-t.broadcast:
			// Content message intended for broadcasting to recepients

			// log.Printf("topic[%s].run: got message '%v'", t.name, msg)

			// Record last message timestamp
			if msg.Data != nil {
				from := types.ParseUserId(msg.Data.From)

				// msg.sessFrom is not nil when the message originated at the client.
				// for internally generated messages, like invites, the akn is nil
				if msg.sessFrom != nil {
					userData := t.perUser[from]
					if userData.modeWant&userData.modeGiven&types.ModePub == 0 {
						msg.sessFrom.queueOut(ErrPermissionDenied(msg.id, t.original, msg.timestamp))
						continue
					}
				}

				if err := store.Messages.Save(&types.Message{
					ObjHeader: types.ObjHeader{CreatedAt: msg.Data.Timestamp},
					SeqId:     t.lastId + 1,
					Topic:     t.name,
					From:      from.String(),
					Content:   msg.Data.Content}); err != nil {

					log.Printf("topic[%s].run: failed to save message: %v", t.name, err)
					msg.sessFrom.queueOut(ErrUnknown(msg.id, t.original, msg.timestamp))

					continue
				}

				t.lastId++
				msg.Data.SeqId = t.lastId

				if msg.id != "" {
					reply := NoErrAccepted(msg.id, t.original, msg.timestamp)
					reply.Ctrl.Params = map[string]int{"seq": t.lastId}
					msg.sessFrom.queueOut(reply)
				}

				t.presPubMessageSent(t.lastId)

			} else if msg.Pres != nil {
				// log.Printf("topic[%s].run: pres.src='%s' what='%s'", t.name, msg.Pres.Src, msg.Pres.What)
				t.presProcReq(msg.Pres.Src, (msg.Pres.What == "on"), msg.Pres.wantReply)
				if t.original != msg.Pres.Topic {
					// This is just a request for status, don't forward it to sessions
					continue
				}
			} else if msg.Info != nil {
				if msg.Info.SeqId > t.lastId {
					// Skip bogus read notification
					continue
				}

				if msg.Info.What == "read" || msg.Info.What == "recv" {
					uid := types.ParseUserId(msg.Info.From)
					pud := t.perUser[uid]
					var read, recv int
					if msg.Info.What == "read" {
						if msg.Info.SeqId > pud.readId {
							pud.readId = msg.Info.SeqId
							read = pud.readId
						} else {
							// No need to report stale or bogus read status
							continue
						}
					} else if msg.Info.What == "recv" {
						if msg.Info.SeqId > pud.recvId {
							pud.recvId = msg.Info.SeqId
							recv = pud.recvId
						} else {
							continue
						}
					}

					if pud.readId > pud.recvId {
						pud.recvId = pud.readId
						recv = pud.recvId
					}

					if err := store.Subs.Update(t.name, uid,
						map[string]interface{}{
							"RecvSeqId": pud.recvId,
							"ReadSeqId": pud.readId}); err != nil {

						log.Printf("topic[%s].run: failed to update SeqRead/Recv counter: %v", t.name, err)
						continue
					}

					t.presPubMessageCount(msg.sessSkip, 0, recv, read)

					t.perUser[uid] = pud
				}
			}

			// Broadcast the message. Only {data}, {pres}, {ping} are broadcastable.
			// {meta} and {ctrl} are sent to the session only
			if msg.Data != nil || msg.Pres != nil || msg.Info != nil {
				var packet, _ = json.Marshal(msg)
				for sess := range t.sessions {
					if sess == msg.sessSkip {
						continue
					}

					select {
					case sess.send <- packet:
					default:
						log.Printf("topic[%s].run: connection stuck, detaching", t.name)
						t.unreg <- &sessionLeave{sess: sess, unsub: false}
					}
				}
			} else {
				log.Printf("topic[%s].run: wrong message type for broadcasting", t.name)
			}

		case meta := <-t.meta:
			log.Printf("topic[%s].run: got meta message '%v'", t.name, meta)

			// Request to get/set topic metadata
			if meta.pkt.Get != nil {
				// Get request
				if meta.what&constMsgMetaDesc != 0 {
					t.replyGetDesc(meta.sess, meta.pkt.Get.Id, false, meta.pkt.Get.Desc)
				}
				if meta.what&constMsgMetaSub != 0 {
					t.replyGetSub(meta.sess, meta.pkt.Get.Id, meta.pkt.Get.Sub)
				}
				if meta.what&constMsgMetaData != 0 {
					t.replyGetData(meta.sess, meta.pkt.Get.Id, meta.pkt.Get.Data)
				}
			} else if meta.pkt.Set != nil {
				// Set request
				if meta.what&constMsgMetaDesc != 0 {
					t.replySetDesc(meta.sess, meta.pkt.Set)
				}
				if meta.what&constMsgMetaSub != 0 {
					t.replySetSub(hub, meta.sess, meta.pkt.Set)
				}

			} else if meta.pkt.Del != nil {
				// Del request
				if meta.what == constMsgDelMsg {
					t.replyDelMsg(meta.sess, meta.pkt.Del)
				} else if meta.what == constMsgDelTopic {
					t.replyDelTopic(hub, meta.sess, meta.pkt.Del)
				}
			}
		case ua := <-t.uaChange:
			// process an update to user agent from one of the sessions
			currentUA = ua
			uaTimer.Reset(UA_TIMER_DELAY)

		case <-uaTimer.C:
			// Publish user agent changes after a delay
			t.presPubUAChange(currentUA)

		case <-killTimer.C:
			// Topic timeout
			log.Println("Topic timeout: ", t.name)
			hub.unreg <- &topicUnreg{topic: t.name}
			if t.cat == types.TopicCat_Me {
				t.presPubMeChange("off", currentUA)
			}
			return

		case sd := <-t.exit:
			// Perform cleanup and database updates before server shuts down.
			// FIXME(gene): save lastMessage value;
			if t.cat == types.TopicCat_Me {
				t.presPubMeChange("off", currentUA)
			} else if t.cat == types.TopicCat_Grp {
				if sd.del {
					t.presPubTopicOnline("gone")
				} else {
					t.presPubTopicOnline("off")
				}
			} // not publishing online/offline to P2P topics

			// Report completion back to sender, if 'done' is not nil.
			if sd.done != nil {
				sd.done <- true
			}
			return
		}
	}
}
Exemplo n.º 6
0
Arquivo: hub.go Projeto: ycaihua/chat
// topicInit reads an existing topic from database or creates a new topic
func topicInit(sreg *sessionJoin, h *Hub) {
	var t *Topic

	timestamp := time.Now().UTC().Round(time.Millisecond)

	t = &Topic{name: sreg.topic,
		original:  sreg.pkt.Topic,
		sessions:  make(map[*Session]bool),
		broadcast: make(chan *ServerComMessage, 256),
		reg:       make(chan *sessionJoin, 32),
		unreg:     make(chan *sessionLeave, 32),
		meta:      make(chan *metaReq, 32),
		perUser:   make(map[types.Uid]perUserData),
		exit:      make(chan *shutDown, 1),
	}

	// Request to load a 'me' topic. The topic always axists.
	if t.original == "me" {
		log.Println("hub: loading 'me' topic")

		t.cat = types.TopicCat_Me

		// 'me' has no owner, t.owner = nil

		// Ensure all requests to subscribe are automatically rejected
		t.accessAuth = types.ModeBanned
		t.accessAnon = types.ModeBanned

		user, err := store.Users.Get(sreg.sess.uid)
		if err != nil {
			log.Println("hub: cannot load user object for 'me'='" + t.name + "' (" + err.Error() + ")")
			sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp))
			return
		}

		if err = t.loadSubscribers(); err != nil {
			log.Println("hub: cannot load subscribers for '" + t.name + "' (" + err.Error() + ")")
			sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp))
			return
		}

		t.public = user.Public

		t.created = user.CreatedAt
		t.updated = user.UpdatedAt

		t.lastId = user.SeqId
		t.clearId = user.ClearId

		// Initiate User Agent with the UA of the creating session to report it later
		t.userAgent = sreg.sess.userAgent

		// Request to load a 'find' topic. The topic always exists.
	} else if t.original == "fnd" {
		log.Println("hub: loading 'fnd' topic")

		t.cat = types.TopicCat_Fnd

		// 'fnd' has no owner, t.owner = nil

		// The p2p topic cannot be accessed by anyone but the 2 participants. Access is checked at the session level.
		// Set default access in case user leaves - joins.
		t.accessAuth = types.ModeBanned
		t.accessAnon = types.ModeBanned

		if err := t.loadSubscribers(); err != nil {
			log.Println("hub: cannot load subscribers for '" + t.name + "' (" + err.Error() + ")")
			sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp))
			return
		}

		// Public is not set.
		// t.public = nil

		t.created = t.perUser[sreg.sess.uid].created
		t.updated = t.perUser[sreg.sess.uid].updated

		// Publishing to Find is not supported
		// t.lastId = 0

		// Request to attach to a firehause topic. Must be root.
	} else if t.original == "fh" {
		log.Println("hub: loading 'fh' topic")

		t.cat = types.TopicCat_Fh

		// 'fh' has no owner, t.owner = nil

		// The fh can be accessed by anyone with root access
		// t.accessAuth = types.ModeBanned
		// t.accessAnon = types.ModeBanned

		// Subscriptions are ephemeral, no need to load subscribers.

		// TODO(gene): handle public more intelligently
		// t.public = nil

		// Publishing to Fh is not supported, history is not supported.
		// t.lastId = 0

		// Request to load an existing or create a new p2p topic, then attach to it.
	} else if strings.HasPrefix(t.original, "usr") || strings.HasPrefix(t.original, "p2p") {
		log.Println("hub: loading or creating p2p topic")

		// Handle the following cases:
		// 1. Neither topic nor subscriptions exist: create a new p2p topic & subscriptions.
		// 2. Topic exists, one of the subscriptions is missing:
		// 2.1 Requester's subscription is missing, recreate it.
		// 2.2 Other user's subscription is missing, treat like a new request for user 2.
		// 3. Topic exists, both subscriptions are missing: should not happen, fail.
		// 4. Topic and both subscriptions exist: attach to topic

		t.cat = types.TopicCat_P2P

		// Check if the topic already exists
		stopic, err := store.Topics.Get(t.name)
		if err != nil {
			log.Println("hub: error while loading topic '" + t.name + "' (" + err.Error() + ")")
			sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp))
			return
		}

		// If topic exists, load subscriptions
		var subs []types.Subscription
		if stopic != nil {
			// Subs already have Public swapped
			if subs, err = store.Topics.GetSubs(t.name); err != nil {
				log.Println("hub: cannot load subscritions for '" + t.name + "' (" + err.Error() + ")")
				sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp))
				return
			}

			// Case 3, fail
			if len(subs) == 0 {
				log.Println("hub: missing both subscriptions for '" + t.name + "' (SHOULD NEVER HAPPEN!)")
				sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp))
				return
			}

			t.created = stopic.CreatedAt
			t.updated = stopic.UpdatedAt

			t.lastId = stopic.SeqId
		}

		// t.owner is blank for p2p topics

		// Ensure that other users are automatically rejected
		t.accessAuth = types.ModeP2P
		t.accessAnon = types.ModeBanned

		// t.public is not used for p2p topics since each user get a different public

		// Custom default access levels set in sreg.pkt.Init.DefaultAcs are ignored

		if stopic != nil && len(subs) == 2 {
			// Case 4.

			log.Println("hub: existing p2p topic")

			// t.clearId is not used in P2P topics, may change later

			for i := 0; i < 2; i++ {
				uid := types.ParseUid(subs[i].User)
				t.perUser[uid] = perUserData{
					// Adapter already swapped the public values
					public:  subs[i].GetPublic(),
					private: subs[i].Private,
					// lastSeenTag: subs[i].LastSeen,
					modeWant:  subs[i].ModeWant,
					modeGiven: subs[i].ModeGiven}
			}

		} else {
			// Cases 1, 2

			log.Println("hub: p2p new topic or one of the subs missing")

			var userData perUserData

			// Fetching records for both users.
			// Requester.
			userId1 := sreg.sess.uid
			// The other user.
			userId2 := types.ParseUserId(t.original)
			var u1, u2 int
			users, err := store.Users.GetAll(userId1, userId2)
			if err != nil {
				log.Println("hub: failed to load users for '" + t.name + "' (" + err.Error() + ")")
				sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp))
				return
			} else if len(users) != 2 {
				// Invited user does not exist
				log.Println("hub: missing user for '" + t.name + "'")
				sreg.sess.queueOut(ErrUserNotFound(sreg.pkt.Id, t.name, timestamp))
				return
			} else {
				// User records are unsorted, make sure we know who is who.
				if users[0].Uid() == userId1 {
					u1, u2 = 0, 1
				} else {
					u1, u2 = 1, 0
				}
			}
			// Figure out which subscriptions are missing: User1's, User2's or both.
			var sub1, sub2 *types.Subscription
			// Set to true if only requester's subscription was created
			var user1only bool
			if len(subs) == 1 {
				if subs[0].Uid() == userId1 {
					// User2's subscription is missing
					sub1 = &subs[0]
				} else {
					// User1's is missing
					sub2 = &subs[0]
					user1only = true
				}
			}

			if sub1 == nil {
				if sreg.pkt.Set != nil && sreg.pkt.Set.Desc != nil && !isNullValue(sreg.pkt.Set.Desc.Private) {
					userData.private = sreg.pkt.Set.Desc.Private
					// Init.DefaultAcs and Init.Public are ignored for p2p topics
				}

				// User may set non-default access to topic, just make sure it's no higher than the default
				if sreg.pkt.Set != nil && sreg.pkt.Set.Sub != nil && sreg.pkt.Set.Sub.Mode != "" {
					if err := userData.modeWant.UnmarshalText([]byte(sreg.pkt.Set.Sub.Mode)); err != nil {
						log.Println("hub: invalid access mode for topic '" + t.name + "': '" + sreg.pkt.Set.Sub.Mode + "'")
						userData.modeWant = types.ModeP2P
					} else {
						// FIXME(gene): check for Mode.Banned
						userData.modeWant &= types.ModeP2P
					}
				} else if users[u1].Access.Auth != types.ModeNone {
					userData.modeWant = users[u1].Access.Auth
				} else {
					userData.modeWant = types.ModeP2P
				}

				sub1 = &types.Subscription{
					User:      userId1.String(),
					Topic:     t.name,
					ModeWant:  userData.modeWant,
					ModeGiven: types.ModeP2P,
					Private:   userData.private}
				// Swap Public to match swapped Public in subs returned from store.Topics.GetSubs
				sub1.SetPublic(users[u2].Public)
			}

			if sub2 == nil {
				sub2 = &types.Subscription{
					User:      userId2.String(),
					Topic:     t.name,
					ModeWant:  users[u2].Access.Auth,
					ModeGiven: types.ModeP2P,
					Private:   nil}
				// Swap Public to match swapped Public in subs returned from store.Topics.GetSubs
				sub2.SetPublic(users[u1].Public)
			}

			// Create everything
			if stopic == nil {
				if err = store.Topics.CreateP2P(sub1, sub2); err != nil {
					log.Println("hub: databse error in creating subscriptions '" + t.name + "' (" + err.Error() + ")")
					sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp))
					return
				}

				t.created = sub1.CreatedAt
				t.updated = sub1.UpdatedAt

				// t.lastId is not set (default 0) for new topics

			} else {
				// Recreate one of the subscriptions
				var subToMake *types.Subscription
				if user1only {
					subToMake = sub1
				} else {
					subToMake = sub2
				}
				if err = store.Subs.Create(subToMake); err != nil {
					log.Println("hub: databse error in re-subscribing user '" + t.name + "' (" + err.Error() + ")")
					sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp))
					return
				}
			}

			// t.clearId is not currently used for p2p topics

			// Publics is already swapped
			userData.public = sub1.GetPublic()
			userData.modeWant = sub1.ModeWant
			userData.modeGiven = sub1.ModeGiven
			t.perUser[userId1] = userData

			t.perUser[userId2] = perUserData{
				public:    sub2.GetPublic(),
				modeWant:  sub2.ModeWant,
				modeGiven: sub2.ModeGiven,
			}

			sreg.created = !user1only
		}

		t.original = t.name

		// Processing request to create a new generic (group) topic:
	} else if t.original == "new" {
		log.Println("hub: new group topic")

		t.cat = types.TopicCat_Grp

		// Generic topics have parameters stored in the topic object
		t.owner = sreg.sess.uid

		t.accessAuth = DEFAULT_AUTH_ACCESS
		t.accessAnon = DEFAULT_ANON_ACCESS

		// Owner/creator gets full access to the topic. Owner may change the default modeWant through 'set'.
		userData := perUserData{
			modeGiven: types.ModeFull,
			modeWant:  types.ModeFull}

		if sreg.pkt.Set != nil {
			// User sent initialization parameters
			if sreg.pkt.Set.Desc != nil {
				if !isNullValue(sreg.pkt.Set.Desc.Public) {
					t.public = sreg.pkt.Set.Desc.Public
				}
				if !isNullValue(sreg.pkt.Set.Desc.Private) {
					userData.private = sreg.pkt.Set.Desc.Private
				}

				// set default access
				if sreg.pkt.Set.Desc.DefaultAcs != nil {
					if auth, anon, err := parseTopicAccess(sreg.pkt.Set.Desc.DefaultAcs, t.accessAuth, t.accessAnon); err != nil {
						log.Println("hub: invalid access mode for topic '" + t.name + "': '" + err.Error() + "'")
					} else if auth&types.ModeOwner != 0 || anon&types.ModeOwner != 0 {
						log.Println("hub: OWNER default access in topic '" + t.name)
					} else {
						t.accessAuth, t.accessAnon = auth, anon
					}
				}
			}

			// Owner/creator may restrict own access to topic
			if sreg.pkt.Set.Sub == nil || sreg.pkt.Set.Sub.Mode == "" {
				userData.modeWant = types.ModeFull
			} else {
				if err := userData.modeWant.UnmarshalText([]byte(sreg.pkt.Set.Sub.Mode)); err != nil {
					log.Println("hub: invalid access mode for topic '" + t.name + "': '" + sreg.pkt.Set.Sub.Mode + "'")
				}
			}
		}

		t.perUser[t.owner] = userData

		t.created = timestamp
		t.updated = timestamp

		// t.lastId & t.clearId are not set for new topics

		stopic := &types.Topic{
			ObjHeader: types.ObjHeader{Id: sreg.topic, CreatedAt: timestamp},
			Access:    types.DefaultAccess{Auth: t.accessAuth, Anon: t.accessAnon},
			Public:    t.public}
		// store.Topics.Create will add a subscription record for the topic creator
		stopic.GiveAccess(t.owner, userData.modeWant, userData.modeGiven)
		err := store.Topics.Create(stopic, t.owner, t.perUser[t.owner].private)
		if err != nil {
			log.Println("hub: cannot save new topic '" + t.name + "' (" + err.Error() + ")")
			// Error sent on "new" topic
			sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp))
			return
		}

		t.original = t.name // keeping 'new' as original has no value to the client
		sreg.created = true

	} else {
		log.Println("hub: existing group topic")

		t.cat = types.TopicCat_Grp

		// TODO(gene): check and validate topic name
		stopic, err := store.Topics.Get(t.name)
		if err != nil {
			log.Println("hub: error while loading topic '" + t.name + "' (" + err.Error() + ")")
			sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp))
			return
		} else if stopic == nil {
			log.Println("hub: topic '" + t.name + "' does not exist")
			sreg.sess.queueOut(ErrTopicNotFound(sreg.pkt.Id, t.original, timestamp))
			return
		}

		if err = t.loadSubscribers(); err != nil {
			log.Println("hub: cannot load subscribers for '" + t.name + "' (" + err.Error() + ")")
			sreg.sess.queueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp))
			return
		}

		// t.owner is set by loadSubscriptions

		t.accessAuth = stopic.Access.Auth
		t.accessAnon = stopic.Access.Anon

		t.public = stopic.Public

		t.created = stopic.CreatedAt
		t.updated = stopic.UpdatedAt

		t.lastId = stopic.SeqId
		t.clearId = stopic.ClearId
	}

	log.Println("hub: topic created or loaded: " + t.name)

	h.topicPut(t.name, t)
	h.topicsLive.Add(1)
	go t.run(h)

	sreg.loaded = true
	// Topic will check access rights, send invite to p2p user, send {ctrl} message to the initiator session
	t.reg <- sreg
}
Exemplo n.º 7
0
Arquivo: hub.go Projeto: ycaihua/chat
func (h *Hub) run() {

	for {
		select {
		case sreg := <-h.join:
			// Handle a subscription request:
			// 1. Init topic
			// 1.1 If a new topic is requested, create it
			// 1.2 If a new subscription to an existing topic is requested:
			// 1.2.1 check if topic is already loaded
			// 1.2.2 if not, load it
			// 1.2.3 if it cannot be loaded (not found), fail
			// 2. Check access rights and reject, if appropriate
			// 3. Attach session to the topic

			t := h.topicGet(sreg.topic) // is the topic already loaded?
			if t == nil {
				// Topic does not exist or not loaded
				go topicInit(sreg, h)
			} else {
				// Topic found.
				// Topic will check access rights and send appropriate {ctrl}
				t.reg <- sreg
			}

		case msg := <-h.route:
			// This is a message from a connection not subscribed to topic
			// Route incoming message to topic if topic permits such routing

			timestamp := time.Now().UTC().Round(time.Millisecond)
			if dst := h.topicGet(msg.rcptto); dst != nil {
				// Everything is OK, sending packet to known topic
				//log.Printf("Hub. Sending message to '%s'", dst.name)

				if dst.broadcast != nil {
					select {
					case dst.broadcast <- msg:
					default:
						log.Printf("hub: topic's broadcast queue is full '%s'", dst.name)
					}
				}
			} else {
				if msg.Data != nil {
					// Normally the message is persisted at the topic. If the topic is offline,
					// persist message here. The only case of sending to offline topics is invites/info to 'me'
					// The 'me' must receive them, so ignore access settings

					if err := store.Messages.Save(&types.Message{
						ObjHeader: types.ObjHeader{CreatedAt: msg.Data.Timestamp},
						Topic:     msg.rcptto,
						// SeqId is assigned by the store.Mesages.Save
						From:    types.ParseUserId(msg.Data.From).String(),
						Content: msg.Data.Content}); err != nil {

						msg.sessFrom.queueOut(ErrUnknown(msg.id, msg.Data.Topic, timestamp))
						return
					}

					// TODO(gene): validate topic name, discarding invalid topics
					log.Printf("Hub. Topic[%s] is unknown or offline", msg.rcptto)
					for tt, _ := range h.topics {
						log.Printf("Hub contains topic '%s'", tt)
					}
					msg.sessFrom.queueOut(NoErrAccepted(msg.id, msg.rcptto, timestamp))
				}
			}

		case meta := <-h.meta:
			log.Println("hub.meta: got message")
			// Request for topic info from a user who is not subscribed to the topic
			if dst := h.topicGet(meta.topic); dst != nil {
				// If topic is already in memory, pass request to topic
				dst.meta <- meta
			} else if meta.pkt.Get != nil {
				// If topic is not in memory, fetch requested description from DB and reply here
				go replyTopicDescBasic(meta.sess, meta.topic, meta.pkt.Get)
			}

		case unreg := <-h.unreg:
			// The topic is being garbage collected or deleted.
			h.topicUnreg(unreg.sess, unreg.topic, unreg.msg, unreg.fromSession, unreg.del)

		case hubdone := <-h.shutdown:
			topicsdone := make(chan bool)
			for _, topic := range h.topics {
				topic.exit <- &shutDown{done: topicsdone}
			}

			for i := 0; i < len(h.topics); i++ {
				<-topicsdone
			}

			log.Printf("Hub shutdown: terminated %d topics", len(h.topics))

			// let the main goroutine know we are done with the cleanup
			hubdone <- true

			return

		case <-time.After(IDLETIMEOUT):
		}
	}
}
Exemplo n.º 8
0
// topicInit reads an existing topic from database or creates a new topic
func topicInit(sreg *sessionJoin, h *Hub) {
	var t *Topic

	timestamp := time.Now().UTC().Round(time.Millisecond)

	t = &Topic{name: sreg.topic,
		original:  sreg.pkt.Topic,
		sessions:  make(map[*Session]bool),
		broadcast: make(chan *ServerComMessage, 256),
		reg:       make(chan *sessionJoin, 32),
		unreg:     make(chan *sessionLeave, 32),
		meta:      make(chan *metaReq, 32),
		perUser:   make(map[types.Uid]perUserData),
		shutdown:  make(chan chan<- bool, 1),
	}

	// Request to load a me topic. The topic must exist
	if t.original == "me" {
		log.Println("hub: loading me topic")

		t.cat = TopicCat_Me

		// 'me' has no owner, t.owner = nil

		// Ensure all requests to subscribe are automatically rejected
		t.accessAuth = types.ModeBanned
		t.accessAnon = types.ModeBanned

		user, err := store.Users.Get(sreg.sess.uid)
		if err != nil {
			log.Println("hub: cannot load user object for 'me'='" + t.name + "' (" + err.Error() + ")")
			sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp))
			return
		}

		if err = t.loadSubscribers(); err != nil {
			log.Println("hub: cannot load subscribers for '" + t.name + "' (" + err.Error() + ")")
			sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp))
			return
		}

		t.public = user.Public

		t.created = user.CreatedAt
		t.updated = user.UpdatedAt

		t.lastId = user.SeqId
		t.clearId = user.ClearId

		// Initiate User Agent with the UA of the creating session so we don't report it later
		t.userAgent = sreg.sess.userAgent

		// Request to create a new p2p topic, then attach to it
	} else if strings.HasPrefix(t.original, "usr") {
		log.Println("hub: new p2p topic")

		t.cat = TopicCat_P2P

		// t.owner is blank for p2p topics

		// Ensure that other users are automatically rejected
		t.accessAuth = types.ModeBanned
		t.accessAnon = types.ModeBanned

		var userData perUserData
		if sreg.pkt.Init != nil && !isNullValue(sreg.pkt.Init.Private) {
			// t.public is not used for p2p topics since each user get a different public

			userData.private = sreg.pkt.Init.Private
			// Init.DefaultAcs and Init.Public are ignored for p2p topics
		}
		// Custom default access levels set in sreg.pkt.Init.DefaultAcs are ignored

		// Fetch user's public and default access
		userId1 := sreg.sess.uid
		userId2 := types.ParseUserId(t.original)
		users, err := store.Users.GetAll(userId1, userId2)
		if err != nil {
			log.Println("hub: failed to load users for '" + t.name + "' (" + err.Error() + ")")
			sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp))
			return
		} else if len(users) != 2 {
			// invited user does not exist
			log.Println("hub: missing user for '" + t.name + "'")
			sreg.sess.QueueOut(ErrUserNotFound(sreg.pkt.Id, t.name, timestamp))
			return
		}

		var u1, u2 int
		if users[0].Uid() == userId1 {
			u1 = 0
			u2 = 1
		} else {
			u1 = 1
			u2 = 0
		}

		// User may set non-default access to topic, just make sure it's no higher than the default
		if sreg.pkt.Sub != nil && sreg.pkt.Sub.Mode != "" {
			if err := userData.modeWant.UnmarshalText([]byte(sreg.pkt.Sub.Mode)); err != nil {
				log.Println("hub: invalid access mode for topic '" + t.name + "': '" + sreg.pkt.Sub.Mode + "'")
				userData.modeWant = types.ModeP2P
			} else {
				userData.modeWant &= types.ModeP2P
			}
		} else if users[u1].Access.Auth != types.ModeNone {
			userData.modeWant = users[u1].Access.Auth
		} else {
			userData.modeWant = types.ModeP2P
		}

		user1 := &types.Subscription{
			User:      userId1.String(),
			Topic:     t.name,
			ModeWant:  userData.modeWant,
			ModeGiven: types.ModeP2P,
			Private:   userData.private}
		user1.SetPublic(users[u1].Public)
		user2 := &types.Subscription{
			User:      userId2.String(),
			Topic:     t.name,
			ModeWant:  users[u2].Access.Auth,
			ModeGiven: types.ModeP2P,
			Private:   nil}
		user2.SetPublic(users[u2].Public)

		err = store.Topics.CreateP2P(user1, user2)
		if err != nil {
			log.Println("hub: databse error in creating subscriptions '" + t.name + "' (" + err.Error() + ")")
			sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp))
			return
		}

		// t.public is not used for p2p topics since each user gets a different public

		t.created = user1.CreatedAt
		t.updated = user1.UpdatedAt

		// t.lastId is not set (default 0) for new topics
		// t.clearId is not currently used for p2p topics

		userData.public = user2.GetPublic()
		userData.modeWant = user1.ModeWant
		userData.modeGiven = user1.ModeGiven
		t.perUser[userId1] = userData

		t.perUser[userId2] = perUserData{
			public:    user1.GetPublic(),
			modeWant:  user2.ModeWant,
			modeGiven: user2.ModeGiven,
		}

		t.original = t.name
		sreg.created = true

		// Load an existing p2p topic
	} else if strings.HasPrefix(t.name, "p2p") {
		log.Println("hub: existing p2p topic")

		t.cat = TopicCat_P2P

		// t.owner no valid owner for p2p topics, leave blank

		// Ensure that other users are automatically rejected
		t.accessAuth = types.ModeBanned
		t.accessAnon = types.ModeBanned

		// Load the topic object
		stopic, err := store.Topics.Get(t.name)
		if err != nil {
			log.Println("hub: error while loading topic '" + t.name + "' (" + err.Error() + ")")
			sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp))
			return
		} else if stopic == nil {
			log.Println("hub: topic '" + t.name + "' does not exist")
			sreg.sess.QueueOut(ErrTopicNotFound(sreg.pkt.Id, t.original, timestamp))
			return
		}

		subs, err := store.Topics.GetSubs(t.name)
		if err != nil {
			log.Println("hub: cannot load subscritions for '" + t.name + "' (" + err.Error() + ")")
			sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.name, timestamp))
			return
		} else if len(subs) != 2 {
			log.Println("hub: invalid number of subscriptions for '" + t.name + "'")
			sreg.sess.QueueOut(ErrTopicNotFound(sreg.pkt.Id, t.name, timestamp))
			return
		}

		// t.public is not used for p2p topics since each user gets a different public

		t.created = stopic.CreatedAt
		t.updated = stopic.UpdatedAt

		t.lastId = stopic.SeqId
		// t.clearId is not used in P2P topics, may change later

		for i := 0; i < 2; i++ {
			uid := types.ParseUid(subs[i].User)
			t.perUser[uid] = perUserData{
				// Based on other user
				public:  subs[(i+1)%2].GetPublic(),
				private: subs[i].Private,
				// lastSeenTag: subs[i].LastSeen,
				modeWant:  subs[i].ModeWant,
				modeGiven: subs[i].ModeGiven}
		}

		// Processing request to create a new generic (group) topic:
	} else if t.original == "new" {
		log.Println("hub: new group topic")

		t.cat = TopicCat_Grp

		// Generic topics have parameters stored in the topic object
		t.owner = sreg.sess.uid

		t.accessAuth = DEFAULT_AUTH_ACCESS
		t.accessAnon = DEFAULT_ANON_ACCESS

		// Owner/creator gets full access to topic
		userData := perUserData{modeGiven: types.ModeFull}

		// User sent initialization parameters
		if sreg.pkt.Init != nil {
			if !isNullValue(sreg.pkt.Init.Public) {
				t.public = sreg.pkt.Init.Public
			}
			if !isNullValue(sreg.pkt.Init.Private) {
				userData.private = sreg.pkt.Init.Private
			}

			// set default access
			if sreg.pkt.Init.DefaultAcs != nil {
				if auth, anon, err := parseTopicAccess(sreg.pkt.Init.DefaultAcs, t.accessAuth, t.accessAnon); err != nil {
					log.Println("hub: invalid access mode for topic '" + t.name + "': '" + err.Error() + "'")
				} else if auth&types.ModeOwner != 0 || anon&types.ModeOwner != 0 {
					log.Println("hub: OWNER default access in topic '" + t.name)
				} else {
					t.accessAuth, t.accessAnon = auth, anon
				}
			}
		}

		// Owner/creator may restrict own access to topic
		if sreg.pkt.Sub == nil || sreg.pkt.Sub.Mode == "" {
			userData.modeWant = types.ModeFull
		} else {
			if err := userData.modeWant.UnmarshalText([]byte(sreg.pkt.Sub.Mode)); err != nil {
				log.Println("hub: invalid access mode for topic '" + t.name + "': '" + sreg.pkt.Sub.Mode + "'")
			}
		}

		t.perUser[t.owner] = userData

		t.created = timestamp
		t.updated = timestamp

		// t.lastId & t.clearId are not set for new topics

		stopic := &types.Topic{
			ObjHeader: types.ObjHeader{CreatedAt: timestamp},
			Name:      sreg.topic,
			Access:    types.DefaultAccess{Auth: t.accessAuth, Anon: t.accessAnon},
			Public:    t.public}
		// store.Topics.Create will add a subscription record for the topic creator
		stopic.GiveAccess(t.owner, userData.modeWant, userData.modeGiven)
		err := store.Topics.Create(stopic, t.owner, t.perUser[t.owner].private)
		if err != nil {
			log.Println("hub: cannot save new topic '" + t.name + "' (" + err.Error() + ")")
			// Error sent on "new" topic
			sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp))
			return
		}

		t.original = t.name // keeping 'new' as original has no value to the client
		sreg.created = true

	} else {
		log.Println("hub: existing group topic")

		t.cat = TopicCat_Grp

		// TODO(gene): check and validate topic name
		stopic, err := store.Topics.Get(t.name)
		if err != nil {
			log.Println("hub: error while loading topic '" + t.name + "' (" + err.Error() + ")")
			sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp))
			return
		} else if stopic == nil {
			log.Println("hub: topic '" + t.name + "' does not exist")
			sreg.sess.QueueOut(ErrTopicNotFound(sreg.pkt.Id, t.original, timestamp))
			return
		}

		if err = t.loadSubscribers(); err != nil {
			log.Println("hub: cannot load subscribers for '" + t.name + "' (" + err.Error() + ")")
			sreg.sess.QueueOut(ErrUnknown(sreg.pkt.Id, t.original, timestamp))
			return
		}

		// t.owner is set by loadSubscriptions

		t.accessAuth = stopic.Access.Auth
		t.accessAnon = stopic.Access.Anon

		t.public = stopic.Public

		t.created = stopic.CreatedAt
		t.updated = stopic.UpdatedAt

		t.lastId = stopic.SeqId
		t.clearId = stopic.ClearId
	}

	log.Println("hub: topic created or loaded: " + t.name)

	h.topicPut(t.name, t)
	h.topicsLive.Add(1)
	go t.run(h)

	sreg.loaded = true
	// Topic will check access rights, send invite to p2p user, send {ctrl} message to the initiator session
	t.reg <- sreg
}