Пример #1
0
// replyGetData is a response to a get.data request - load a list of stored messages, send them to session as {data}
// response goes to a single session rather than all sessions in a topic
func (t *Topic) replyGetData(sess *Session, id string, req *MsgBrowseOpts) error {
	now := time.Now().UTC().Round(time.Millisecond)

	opts := msgOpts2storeOpts(req)

	messages, err := store.Messages.GetAll(t.name, opts)
	if err != nil {
		log.Println("topic: error loading topics ", err)
		sess.queueOut(ErrUnknown(id, t.original, now))
		return err
	}
	log.Println("Loaded messages ", len(messages))

	// Push the list of messages to the client as {data}.
	// Messages are sent in reverse order than fetched from DB to make it easier for
	// clients to process.
	if messages != nil {
		for i := len(messages) - 1; i >= 0; i-- {
			mm := messages[i]
			from := types.ParseUid(mm.From)
			msg := &ServerComMessage{Data: &MsgServerData{
				Topic:     t.original,
				SeqId:     mm.SeqId,
				From:      from.UserId(),
				Timestamp: mm.CreatedAt,
				Content:   mm.Content}}
			sess.queueOut(msg)
		}
	}

	return nil
}
Пример #2
0
// replyGetData is a response to a get.data request - load a list of stored messages, send them to session as {data}
// response goes to a single session rather than all sessions in a topic
func (t *Topic) replyGetData(sess *Session, id string, req *MsgBrowseOpts) error {
	now := time.Now().UTC().Round(time.Millisecond)

	opts := msgOpts2storeOpts(req)

	messages, err := store.Messages.GetAll(t.name, opts)
	if err != nil {
		log.Println("topic: error loading topics ", err)
		reply := ErrUnknown(id, t.original, now)
		simpleByteSender(sess.send, reply)
		return err
	}
	log.Println("Loaded messages ", len(messages))

	// Push the list of updated topics to the client as data messages
	if messages != nil {
		for _, mm := range messages {
			from := types.ParseUid(mm.From)
			msg := &ServerComMessage{Data: &MsgServerData{
				Topic:     t.original,
				SeqId:     mm.SeqId,
				From:      from.UserId(),
				Timestamp: mm.CreatedAt,
				Content:   mm.Content}}
			simpleByteSender(sess.send, msg)
		}
	}

	return nil
}
Пример #3
0
// loadSubscribers loads topic subscribers, sets topic owner
func (t *Topic) loadSubscribers() error {
	subs, err := store.Topics.GetSubs(t.name)
	if err != nil {
		return err
	}

	for _, sub := range subs {
		uid := types.ParseUid(sub.User)
		t.perUser[uid] = perUserData{
			created:   sub.CreatedAt,
			updated:   sub.UpdatedAt,
			clearId:   sub.ClearId,
			readId:    sub.ReadSeqId,
			recvId:    sub.RecvSeqId,
			private:   sub.Private,
			modeWant:  sub.ModeWant,
			modeGiven: sub.ModeGiven}

		if sub.ModeGiven&sub.ModeWant&types.ModeOwner != 0 {
			log.Printf("hub.loadSubscriptions: %s set owner to %s", t.name, uid.String())
			t.owner = uid
		}
	}

	return nil
}
Пример #4
0
// Retrieve user's authentication record
func (a *RethinkDbAdapter) GetAuthRecord(unique string) (t.Uid, []byte, time.Time, error) {
	// Default() is needed to prevent Pluck from returning an error
	rows, err := rdb.DB(a.dbName).Table("auth").Get(unique).Pluck("userid", "secret", "expires").Default(nil).Run(a.conn)
	if err != nil {
		return t.ZeroUid, nil, time.Time{}, err
	}

	var record struct {
		Userid  string    `gorethink:"userid"`
		Secret  []byte    `gorethink:"secret"`
		Expires time.Time `gorethink:"expires"`
	}

	if !rows.Next(&record) {
		return t.ZeroUid, nil, time.Time{}, rows.Err()
	}
	rows.Close()

	//log.Println("loggin in user Id=", user.Uid(), user.Id)
	return t.ParseUid(record.Userid), record.Secret, record.Expires, nil
}
Пример #5
0
// replyGetSub is a response to a get.sub request on a topic - load a list of subscriptions/subscribers,
// send it just to the session as a {meta} packet
func (t *Topic) replyGetSub(sess *Session, id string, opts *MsgGetOpts) error {
	now := time.Now().UTC().Round(time.Millisecond)

	var subs []types.Subscription
	var err error

	if t.cat == types.TopicCat_Me {
		// Fetch user's subscriptions, with Topic.Public denormalized into subscription
		subs, err = store.Users.GetTopics(sess.uid)
	} else if t.cat == types.TopicCat_Fnd {
		// Given a query provided in .private, fetch user's contacts
		if query, ok := t.perUser[sess.uid].private.([]interface{}); ok {
			if query != nil && len(query) > 0 {
				subs, err = store.Users.FindSubs(sess.uid, query)
			}
		}
	} else {
		// Fetch subscriptions, User.Public denormalized into subscription
		// FIXME(gene): don't load subs from DB, use perUserData - it already contains subscriptions.
		subs, err = store.Topics.GetUsers(t.name)
	}

	if err != nil {
		sess.queueOut(ErrUnknown(id, t.original, now))
		return err
	}

	var ifModified time.Time
	var limit int
	if opts != nil {
		if opts.IfModifiedSince != nil {
			ifModified = *opts.IfModifiedSince
		}
		limit = opts.Limit
	} else {
		limit = 1024
	}

	meta := &MsgServerMeta{Id: id, Topic: t.original, Timestamp: &now}
	if subs != nil && len(subs) > 0 {
		meta.Sub = make([]MsgTopicSub, 0, len(subs))
		for idx, sub := range subs {
			if idx == limit {
				break
			}
			// Check if the requester has provided a cut off date for updated ts of pub & priv fields.
			sendPubPriv := ifModified.IsZero() || sub.UpdatedAt.After(ifModified)

			var mts MsgTopicSub
			uid := types.ParseUid(sub.User)
			var clearId int
			if t.cat == types.TopicCat_Me {
				// Reporting user's subscriptions to other topics
				mts.Topic = sub.Topic
				mts.SeqId = sub.GetSeqId()
				// Report whatever is the greatest - soft - or hard- deleted id
				clearId = max(sub.GetHardClearId(), sub.ClearId)
				mts.ClearId = clearId
				mts.With = sub.GetWith()
				if mts.With != "" {
					mts.Online = t.perSubs[mts.With].online
				} else {
					mts.Online = t.perSubs[sub.Topic].online
				}

				mts.UpdatedAt = &sub.UpdatedAt
				lastSeen := sub.GetLastSeen()
				if !lastSeen.IsZero() {
					mts.LastSeen = &MsgLastSeenInfo{
						When:      &lastSeen,
						UserAgent: sub.GetUserAgent()}
				}
			} else {
				// Reporting subscribers to a group or a p2p topic
				mts.User = uid.UserId()
				clearId = max(t.clearId, sub.ClearId)
				if uid == sess.uid {
					// Report deleted messages for own subscriptions only
					mts.ClearId = clearId
				}
				if t.cat == types.TopicCat_Grp {
					pud := t.perUser[uid]
					mts.Online = pud.online > 0
				}
			}

			// Ensure sanity or ReadId and RecvId:
			mts.ReadSeqId = max(clearId, sub.ReadSeqId)
			mts.RecvSeqId = max(clearId, sub.RecvSeqId)
			mts.AcsMode = (sub.ModeGiven & sub.ModeWant).String()
			// Returning public and private only if they have changed since ifModified
			if sendPubPriv {
				mts.Public = sub.GetPublic()
				// Reporting private only if it's user's own supscription or
				// a synthetic 'private' in 'find' topic where it's a list of tags matched on.
				if uid == sess.uid || t.cat == types.TopicCat_Fnd {
					mts.Private = sub.Private
				}
			}

			meta.Sub = append(meta.Sub, mts)
		}
	}

	sess.queueOut(&ServerComMessage{Meta: meta})

	return nil
}
Пример #6
0
func gen_rethink(reset bool, dbsource string, data *Data) {
	var err error

	log.Println("Opening DB...")

	err = store.Open("rethinkdb", dbsource)
	if err != nil {
		log.Fatal("Failed to connect to DB: ", err)
	}
	defer store.Close()

	log.Println("Initializing DB...")

	err = store.InitDb(reset)
	if err != nil {
		if strings.Contains(err.Error(), " already exists") {
			log.Println("DB already exists, NOT reinitializing")
		} else {
			log.Fatal("Failed to init DB: ", err)
		}
	} else {
		log.Println("DB successfully initialized")

	}
	if data.Users == nil {
		log.Println("No data provided, stopping")
		return
	}

	nameIndex := make(map[string]string, len(data.Users))

	log.Println("Generating users...")

	for _, uu := range data.Users {
		if uu["createdAt"] != nil {

		}
		user := types.User{
			State:    int(uu["state"].(float64)),
			Username: uu["username"].(string),
			Access: types.DefaultAccess{
				Auth: types.ModePublic,
				Anon: types.ModeNone,
			},
			Public: uu["public"],
		}
		user.CreatedAt = getCreatedTime(uu["createdAt"])

		// store.Users.Create will subscribe user to !me topic but won't create a !me topic
		if _, err := store.Users.Create(&user, "basic",
			uu["username"].(string)+":"+uu["passhash"].(string), uu["private"]); err != nil {
			log.Fatal(err)
		}

		nameIndex[user.Username] = user.Id

		log.Printf("Created user '%s' as %s (%d)", user.Username, user.Id, user.Uid())
	}

	log.Println("Generating group topics...")

	for _, gt := range data.Grouptopics {
		name := genTopicName()
		nameIndex[gt["name"].(string)] = name

		topic := &types.Topic{Name: name, Public: gt["public"]}
		var owner types.Uid
		if gt["owner"] != nil {
			owner = types.ParseUid(nameIndex[gt["owner"].(string)])
			topic.GiveAccess(owner, types.ModeFull, types.ModeFull)
		}
		topic.CreatedAt = getCreatedTime(gt["createdAt"])

		if err = store.Topics.Create(topic, owner, gt["private"]); err != nil {
			log.Fatal(err)
		}
		log.Printf("Created topic '%s' as %s", gt["name"].(string), name)
	}

	log.Println("Generating P2P subscriptions...")

	p2pIndex := map[string][]map[string]interface{}{}

	for _, ss := range data.Subscriptions {
		u1 := ss["user"].(string)
		u2 := ss["topic"].(string)

		if u2[0] == '*' {
			// skip group topics
			continue
		}

		var pair string
		var idx int
		if u1 < u2 {
			pair = u1 + ":" + u2
			idx = 0
		} else {
			pair = u2 + ":" + u1
			idx = 1
		}
		if _, ok := p2pIndex[pair]; !ok {
			p2pIndex[pair] = make([]map[string]interface{}, 2)
		}

		p2pIndex[pair][idx] = ss
	}

	log.Printf("Collected p2p pairs: %d\n", len(p2pIndex))

	for pair, subs := range p2pIndex {
		uid1 := types.ParseUid(nameIndex[subs[0]["user"].(string)])
		uid2 := types.ParseUid(nameIndex[subs[1]["user"].(string)])
		topic := uid1.P2PName(uid2)
		created0 := getCreatedTime(subs[0]["createdAt"])
		created1 := getCreatedTime(subs[1]["createdAt"])
		var s0want, s0given, s1want, s1given types.AccessMode
		if err := s0want.UnmarshalText([]byte(subs[0]["modeWant"].(string))); err != nil {
			log.Fatal(err)
		}
		if err := s0given.UnmarshalText([]byte(subs[0]["modeHave"].(string))); err != nil {
			log.Fatal(err)
		}

		if err := s1want.UnmarshalText([]byte(subs[1]["modeWant"].(string))); err != nil {
			log.Fatal(err)
		}
		if err := s1given.UnmarshalText([]byte(subs[1]["modeHave"].(string))); err != nil {
			log.Fatal(err)
		}

		log.Printf("Processing %s (%s), %s, %s", pair, topic, uid1.String(), uid2.String())
		err := store.Topics.CreateP2P(
			&types.Subscription{
				ObjHeader: types.ObjHeader{CreatedAt: created0},
				User:      uid1.String(),
				Topic:     topic,
				ModeWant:  s0want,
				ModeGiven: s0given,
				Private:   subs[0]["private"]},
			&types.Subscription{
				ObjHeader: types.ObjHeader{CreatedAt: created1},
				User:      uid2.String(),
				Topic:     topic,
				ModeWant:  s1want,
				ModeGiven: s1given,
				Private:   subs[1]["private"]})

		if err != nil {
			log.Fatal(err)
		}
	}

	log.Println("Generating group subscriptions...")

	for _, ss := range data.Subscriptions {

		u1 := nameIndex[ss["user"].(string)]
		u2 := nameIndex[ss["topic"].(string)]

		var want, given types.AccessMode
		if err := want.UnmarshalText([]byte(ss["modeWant"].(string))); err != nil {
			log.Fatal(err)
		}
		if err := given.UnmarshalText([]byte(ss["modeHave"].(string))); err != nil {
			log.Fatal(err)
		}

		// Define topic name
		name := u2
		if !types.ParseUid(u2).IsZero() {
			// skip p2p subscriptions
			continue
		}

		log.Printf("Sharing '%s' with '%s'", ss["topic"].(string), ss["user"].(string))

		if err = store.Subs.Create(&types.Subscription{
			ObjHeader: types.ObjHeader{CreatedAt: getCreatedTime(ss["createdAt"])},
			User:      u1,
			Topic:     name,
			ModeWant:  want,
			ModeGiven: given,
			Private:   ss["private"]}); err != nil {

			log.Fatal(err)
		}
	}

	log.Println("Generating messages...")

	rand.Seed(time.Now().UnixNano())
	seqIds := map[string]int{}
	var oldFrom types.Uid
	var oldTopic string
	toInsert := 80
	// Starting 4 days ago
	timestamp := time.Now().UTC().Round(time.Millisecond).Add(time.Second * time.Duration(-3600*24*4))
	for i := 0; i < toInsert; i++ {

		sub := data.Subscriptions[rand.Intn(len(data.Subscriptions))]
		topic := nameIndex[sub["topic"].(string)]
		from := types.ParseUid(nameIndex[sub["user"].(string)])
		if topic == oldTopic && from == oldFrom {
			toInsert++
			continue
		}
		oldTopic, oldFrom = topic, from

		if uid := types.ParseUid(topic); !uid.IsZero() {
			topic = uid.P2PName(from)
		}

		seqIds[topic]++
		seqId := seqIds[topic]
		str := data.Messages[rand.Intn(len(data.Messages))]
		// Max time between messages is 2 hours, averate - 1 hour, time is increasing as seqId increases
		timestamp = timestamp.Add(time.Second * time.Duration(rand.Intn(3600*2)))
		msg := types.Message{
			ObjHeader: types.ObjHeader{CreatedAt: timestamp},
			SeqId:     seqId,
			Topic:     topic,
			From:      from.String(),
			Content:   str}
		if err = store.Messages.Save(&msg); err != nil {
			log.Fatal(err)
		}
		log.Printf("Message %d at %v to '%s' '%s'", msg.SeqId, msg.CreatedAt, topic, str)
	}
}
Пример #7
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),
		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
}
Пример #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
}
Пример #9
0
// TopicsForUser loads user's contact list: p2p and grp topics, except for 'me' subscription.
func (a *RethinkDbAdapter) TopicsForUser(uid t.Uid) ([]t.Subscription, error) {
	// Fetch user's subscriptions
	// Subscription have Topic.UpdatedAt denormalized into Subscription.UpdatedAt
	q := rdb.DB(a.dbName).Table("subscriptions").GetAllByIndex("User", uid.String()).Limit(MAX_RESULTS)
	//log.Printf("RethinkDbAdapter.TopicsForUser q: %+v", q)
	rows, err := q.Run(a.conn)
	if err != nil {
		return nil, err
	}

	// Fetch subscriptions. Two queries are needed: users table (me & p2p) and topics table (p2p and grp).
	// Prepare a list of Separate subscriptions to users vs topics
	var sub t.Subscription
	join := make(map[string]t.Subscription) // Keeping these to make a join with table for .private and .access
	topq := make([]interface{}, 0, 16)
	usrq := make([]interface{}, 0, 16)
	for rows.Next(&sub) {
		tcat := t.GetTopicCat(sub.Topic)

		// 'me' subscription, skip
		if tcat == t.TopicCat_Me || tcat == t.TopicCat_Fnd {
			continue

			// p2p subscription, find the other user to get user.Public
		} else if tcat == t.TopicCat_P2P {
			uid1, uid2, _ := t.ParseP2P(sub.Topic)
			if uid1 == uid {
				usrq = append(usrq, uid2.String())
			} else {
				usrq = append(usrq, uid1.String())
			}
			topq = append(topq, sub.Topic)

			// grp subscription
		} else {
			topq = append(topq, sub.Topic)
		}
		join[sub.Topic] = sub
	}

	//log.Printf("RethinkDbAdapter.TopicsForUser topq, usrq: %+v, %+v", topq, usrq)
	var subs []t.Subscription
	if len(topq) > 0 || len(usrq) > 0 {
		subs = make([]t.Subscription, 0, len(join))
	}

	if len(topq) > 0 {
		// Fetch grp & p2p topics
		rows, err = rdb.DB(a.dbName).Table("topics").GetAll(topq...).Run(a.conn)
		if err != nil {
			return nil, err
		}

		var top t.Topic
		for rows.Next(&top) {
			sub = join[top.Id]
			sub.ObjHeader.MergeTimes(&top.ObjHeader)
			sub.SetSeqId(top.SeqId)
			sub.SetHardClearId(top.ClearId)
			if t.GetTopicCat(sub.Topic) == t.TopicCat_Grp {
				// all done with a grp topic
				sub.SetPublic(top.Public)
				subs = append(subs, sub)
			} else {
				// put back the updated value of a p2p subsription, will process further below
				join[top.Id] = sub
			}
		}

		//log.Printf("RethinkDbAdapter.TopicsForUser 1: %#+v", subs)
	}

	// Fetch p2p users and join to p2p tables
	if len(usrq) > 0 {
		rows, err = rdb.DB(a.dbName).Table("users").GetAll(usrq...).Run(a.conn)
		if err != nil {
			return nil, err
		}

		var usr t.User
		for rows.Next(&usr) {
			uid2 := t.ParseUid(usr.Id)
			topic := uid.P2PName(uid2)
			if sub, ok := join[topic]; ok {
				sub.ObjHeader.MergeTimes(&usr.ObjHeader)
				sub.SetWith(uid2.UserId())
				sub.SetPublic(usr.Public)
				sub.SetLastSeenAndUA(usr.LastSeen, usr.UserAgent)
				subs = append(subs, sub)
			}
		}

		//log.Printf("RethinkDbAdapter.TopicsForUser 2: %#+v", subs)
	}

	return subs, nil
}
Пример #10
0
// replyGetSub is a response to a get.sub request on a topic - load a list of subscriptions/subscribers,
// send it just to the session as a {meta} packet
func (t *Topic) replyGetSub(sess *Session, id string) error {
	now := time.Now().UTC().Round(time.Millisecond)

	var subs []types.Subscription
	var err error

	if t.cat == TopicCat_Me {
		// Fetch user's subscriptions, with Topic.Public denormalized into subscription
		subs, err = store.Users.GetTopics(sess.uid)
	} else {
		// Fetch subscriptions, User.Public denormalized into subscription
		subs, err = store.Topics.GetUsers(t.name)
	}

	if err != nil {
		log.Printf("topic(%s): error loading subscriptions %s\n", t.original, err.Error())
		reply := ErrUnknown(id, t.original, now)
		simpleByteSender(sess.send, reply)
		return err
	}

	meta := &MsgServerMeta{Id: id, Topic: t.original, Timestamp: &now}
	if len(subs) > 0 {
		meta.Sub = make([]MsgTopicSub, 0, len(subs))
		for _, sub := range subs {
			var mts MsgTopicSub
			uid := types.ParseUid(sub.User)
			var clearId int
			if t.cat == TopicCat_Me {
				// Reporting user's subscriptions to other topics
				mts.Topic = sub.Topic
				mts.SeqId = sub.GetSeqId()
				// Report whatever is the greatest - soft - or hard- deleted id
				clearId = max(sub.GetHardClearId(), sub.ClearId)
				mts.ClearId = clearId
				mts.With = sub.GetWith()
				mts.UpdatedAt = &sub.UpdatedAt
				lastSeen := sub.GetLastSeen()
				if !lastSeen.IsZero() {
					mts.LastSeen = &MsgLastSeenInfo{
						When:      &lastSeen,
						UserAgent: sub.GetUserAgent()}
				}
			} else {
				// Reporting subscribers to a group or a p2p topic
				mts.User = uid.UserId()
				clearId = max(t.clearId, sub.ClearId)
				if uid == sess.uid {
					// Report deleted messages for own subscriptions only
					mts.ClearId = clearId
				}
				if t.cat == TopicCat_Grp {
					pud := t.perUser[uid]
					if pud.online > 0 {
						mts.Online = "on"
					} else {
						mts.Online = "off"
					}
				}
			}
			// Ensure sanity or ReadId and RecvId:
			mts.ReadSeqId = max(clearId, sub.ReadSeqId)
			mts.RecvSeqId = max(clearId, sub.RecvSeqId)
			mts.AcsMode = (sub.ModeGiven & sub.ModeWant).String()
			mts.Public = sub.GetPublic()
			if uid == sess.uid {
				mts.Private = sub.Private
			}

			meta.Sub = append(meta.Sub, mts)
		}
	}

	simpleByteSender(sess.send, &ServerComMessage{Meta: meta})

	return nil
}