diff --git a/bindings/channels.go b/bindings/channels.go index be8e10f3ebf88ba8bff496f1c577e46b3c57445d..2d74b293ac450c4756e3f2b136cb55b8f9a602d7 100644 --- a/bindings/channels.go +++ b/bindings/channels.go @@ -103,7 +103,7 @@ func (cm *ChannelsManager) GetID() int { // - e2eID - The tracked e2e object ID. This can be retrieved using [E2e.GetID]. // - udID - The tracked UD object ID. This can be retrieved using // [UserDiscovery.GetID]. -func NewChannelsManager(e2eID, udID int) (*ChannelsManager, error) { +func NewChannelsManager(e2eID, udID, eventModelId int) (*ChannelsManager, error) { // Get user from singleton user, err := e2eTrackerSingleton.get(e2eID) if err != nil { @@ -115,14 +115,19 @@ func NewChannelsManager(e2eID, udID int) (*ChannelsManager, error) { return nil, err } + eventModel, err := eventModelTrackerSingleton.get(eventModelId) + if err != nil { + return nil, err + } + nameService, err := udMan.api.StartChannelNameService() if err != nil { return nil, err } - // fixme: there is nothing adhering to event model + // Construct new channels manager m := channels.NewManager(user.api.GetStorage().GetKV(), user.api.GetCmix(), - user.api.GetRng(), nameService, nil) + user.api.GetRng(), nameService, eventModel.api) // Add channel to singleton and return return channelManagerTrackerSingleton.make(m), nil @@ -132,8 +137,7 @@ func NewChannelsManager(e2eID, udID int) (*ChannelsManager, error) { // been joined. // // Parameters: -// - channelJson - A JSON encoded [ChannelDef]. This may be retrieved from -// [Channel.Get], for example.. +// - channelJson - A JSON encoded [ChannelDef]. func (cm *ChannelsManager) JoinChannel(channelJson []byte) error { // Unmarshal channel definition def := ChannelDef{} @@ -191,6 +195,7 @@ func (cm *ChannelsManager) GetChannels() ([]byte, error) { // // Returns: // - []byte - A JSON encoded channel ID ([id.ID]). +// // JSON Example: // "dGVzdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD" func (cm *ChannelsManager) GetChannelId(channelJson []byte) ([]byte, error) { @@ -284,17 +289,13 @@ func (cm *ChannelsManager) ReplayChannel(marshalledChanId []byte) error { // JSON Example: // { // "MessageId": "0kitNxoFdsF4q1VMSI/xPzfCnGB2l+ln2+7CTHjHbJw=", -// "RoundId": { -// "Rounds": [ -// 123 -// ] -// }, +// "Rounds":[1,5,9], // "EphId": 0 // } type ChannelSendReport struct { MessageId []byte - RoundId RoundsList - EphId int64 + RoundsList + EphId int64 } // SendGeneric is used to send a raw message over a channel. In general, it @@ -571,9 +572,9 @@ func constructChannelSendReport(channelMessageId cryptoChannel.MessageID, roundId id.Round, ephId ephemeral.Id) ([]byte, error) { // Construct send report chanSendReport := ChannelSendReport{ - MessageId: channelMessageId.Bytes(), - RoundId: makeRoundsList(roundId), - EphId: ephId.Int64(), + MessageId: channelMessageId.Bytes(), + RoundsList: makeRoundsList(roundId), + EphId: ephId.Int64(), } // Marshal send report @@ -584,7 +585,7 @@ func constructChannelSendReport(channelMessageId cryptoChannel.MessageID, // Channel Receiving Logic & Callback Registration // //////////////////////////////////////////////////////////////////////////////// -// ReceivedChannelMessage is a report structure returned via the +// ReceivedChannelMessageReport is a report structure returned via the // ChannelMessageReceptionCallback. This report gives the context // for the channel the message was sent to and the message itself. // This is returned via the callback as JSON marshalled bytes. @@ -593,26 +594,24 @@ func constructChannelSendReport(channelMessageId cryptoChannel.MessageID, // { // "ChannelId": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", // "MessageId": "3S6DiVjWH9mLmjy1oaam/3x45bJQzOW6u2KgeUn59wA=", +// "ReplyTo":"cxMyGUFJ+Ff1Xp2X+XkIpOnNAQEZmv8SNP5eYH4tCik=", // "MessageType": 42, // "SenderUsername": "hunter2", // "Content": "YmFuX2JhZFVTZXI=", // "Timestamp": 1662502150335283000, // "Lease": 25, -// "RoundIds": { -// "Rounds": [ -// 11420 -// ] -// } +// "Rounds": [ 1, 4, 9], // } -type ReceivedChannelMessage struct { +type ReceivedChannelMessageReport struct { ChannelId []byte MessageId []byte + ReplyTo []byte MessageType int SenderUsername string Content []byte Timestamp int64 Lease int64 - RoundIds RoundsList + RoundsList } // ChannelMessageReceptionCallback is the callback that returns the @@ -642,7 +641,7 @@ func (cm *ChannelsManager) RegisterReceiveHandler(messageType int, content []byte, timestamp time.Time, lease time.Duration, round rounds.Round) { - rcm := ReceivedChannelMessage{ + rcm := ReceivedChannelMessageReport{ ChannelId: channelID.Marshal(), MessageId: messageID.Bytes(), MessageType: int(messageType), @@ -650,7 +649,7 @@ func (cm *ChannelsManager) RegisterReceiveHandler(messageType int, Content: content, Timestamp: timestamp.UnixNano(), Lease: int64(lease), - RoundIds: makeRoundsList(round.ID), + RoundsList: makeRoundsList(round.ID), } listenerCb.Callback(json.Marshal(rcm)) diff --git a/bindings/eventModel.go b/bindings/eventModel.go new file mode 100644 index 0000000000000000000000000000000000000000..cc3e95a82b41d814cf4f1c43fe5eca1463f7ce6e --- /dev/null +++ b/bindings/eventModel.go @@ -0,0 +1,300 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2022 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package bindings + +import ( + "encoding/json" + "github.com/pkg/errors" + jww "github.com/spf13/jwalterweatherman" + "gitlab.com/elixxir/client/channels" + "gitlab.com/elixxir/client/cmix/rounds" + cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast" + cryptoChannel "gitlab.com/elixxir/crypto/channel" + "gitlab.com/xx_network/crypto/signature/rsa" + "gitlab.com/xx_network/primitives/id" + "sync" + "time" +) + +//////////////////////////////////////////////////////////////////////////////// +// Singleton Tracker // +//////////////////////////////////////////////////////////////////////////////// + +// eventModelTrackerSingleton is used to track EventModel objects +// so that they can be referenced by ID back over the bindings. +var eventModelTrackerSingleton = &eventModelTracker{ + tracked: make(map[int]*EventModel), + count: 0, +} + +// eventModelTracker is a singleton used to keep track of extant +// EventModel objects, preventing race conditions created by passing it +// over the bindings. +type eventModelTracker struct { + tracked map[int]*EventModel + count int + mux sync.RWMutex +} + +// make create an EventModel from an [channels.EventModel], assigns it a +// unique ID, and adds it to the eventModelTracker. +func (emt *eventModelTracker) make(eventModel channels.EventModel) *EventModel { + emt.mux.Lock() + defer emt.mux.Unlock() + + id := emt.count + emt.count++ + + emt.tracked[id] = &EventModel{ + api: eventModel, + id: id, + } + + return emt.tracked[id] +} + +// get an EventModel from the eventModelTracker given its ID. +func (emt *eventModelTracker) get(id int) (*EventModel, error) { + emt.mux.RLock() + defer emt.mux.RUnlock() + + c, exist := emt.tracked[id] + if !exist { + return nil, errors.Errorf( + "Cannot get EventModel for ID %d, does not exist", id) + } + + return c, nil +} + +// delete removes a EventModel from the eventModelTracker. +func (emt *eventModelTracker) delete(id int) { + emt.mux.Lock() + defer emt.mux.Unlock() + + delete(emt.tracked, id) +} + +//////////////////////////////////////////////////////////////////////////////// +// Basic EventModel API // +//////////////////////////////////////////////////////////////////////////////// + +type EventModel struct { + api channels.EventModel + id int +} + +// NewEventModel IS CURRENTLY UNIMPLEMENTED. +func NewEventModel() *EventModel { + return eventModelTrackerSingleton.make(nil) +} + +// JoinChannel is called whenever a channel is joined locally. +// +// Parameters: +// - channelJson - A JSON encoded [ChannelDef]. +func (e *EventModel) JoinChannel(channelJson []byte) { + // Unmarshal channel definition + def := ChannelDef{} + err := json.Unmarshal(channelJson, &def) + if err != nil { + jww.ERROR.Printf("Could not parse channel JSON: %+v", err) + return + } + + // Construct ID using the embedded cryptographic information + channelId, err := cryptoBroadcast.NewChannelID(def.Name, def.Description, + def.Salt, def.PubKey) + if err != nil { + jww.ERROR.Printf("Could not construct channel ID: %+v", err) + return + } + + // Construct public key into object + rsaPubKey, err := rsa.LoadPublicKeyFromPem(def.PubKey) + if err != nil { + jww.ERROR.Printf("Could not read public key: %+v", err) + return + } + + // Construct cryptographic channel object + channel := &cryptoBroadcast.Channel{ + ReceptionID: channelId, + Name: def.Name, + Description: def.Description, + Salt: def.Salt, + RsaPubKey: rsaPubKey, + } + + e.api.JoinChannel(channel) + return +} + +// LeaveChannel is called whenever a channel is left locally. +// +// Parameters: +// - []byte - A JSON marshalled channel ID ([id.ID]). This may be retrieved +// using ChannelsManager.GetChannelId. +func (e *EventModel) LeaveChannel(marshalledChanId []byte) { + // Unmarshal channel ID + channelId, err := id.Unmarshal(marshalledChanId) + if err != nil { + jww.ERROR.Printf("Could not parse channel ID (%s): %+v", + marshalledChanId, err) + return + } + + e.api.LeaveChannel(channelId) + return +} + +// ReceiveMessage is called whenever a message is received on a given channel +// It may be called multiple times on the same message, it is incumbent on +// the user of the API to filter such called by message ID. +// +// Parameters: +// - reportJson - A JSON marshalled ReceivedChannelMessageReport. +func (e *EventModel) ReceiveMessage(reportJson []byte) { + report, err := parseChannelMessageReport(reportJson) + if err != nil { + jww.ERROR.Printf("%+v", err) + return + } + + // fixme: the internal API should accept an object, probably + // just use receivedChannelMessageReport in the channels package + e.api.ReceiveMessage(report.ChannelID, report.MessageID, + report.SenderUsername, report.Content, report.Timestamp, + report.Lease, report.Round) + + return +} + +// ReceiveReply is called whenever a message is received which is a reply +// on a given channel. It may be called multiple times on the same message, +// it is incumbent on the user of the API to filter such called by message ID +// Messages may arrive our of order, so a reply in theory can arrive before +// the initial message, as a result it may be important to buffer replies. +// +// Parameters: +// - reportJson - A JSON marshalled ReceivedChannelMessageReport. +func (e *EventModel) ReceiveReply(reportJson []byte) { + report, err := parseChannelMessageReport(reportJson) + if err != nil { + jww.ERROR.Printf("%+v", err) + return + } + + // fixme: the internal API should accept an object, probably + // just use receivedChannelMessageReport in the channels package. This i + e.api.ReceiveReply(report.ChannelID, report.MessageID, report.ReplyTo, + report.SenderUsername, report.Content, report.Timestamp, + report.Lease, report.Round) +} + +// ReceiveReaction is called whenever a Content to a message is received +// on a given channel. It may be called multiple times on the same Content, +// it is incumbent on the user of the API to filter such called by message ID +// Messages may arrive our of order, so a reply in theory can arrive before +// the initial message, as a result it may be important to buffer reactions. +// +// Parameters: +// - reportJson - A JSON marshalled ReceivedChannelMessageReport. +func (e *EventModel) ReceiveReaction(reportJson []byte) { + report, err := parseChannelMessageReport(reportJson) + if err != nil { + jww.ERROR.Printf("%+v", err) + return + } + + // fixme: the internal API should accept an object, probably + // just move receivedChannelMessageReport to the channels package and export it. + e.api.ReceiveReaction(report.ChannelID, report.MessageID, report.ReplyTo, + report.SenderUsername, report.Content, report.Timestamp, report.Lease, + report.Round) +} + +// receivedChannelMessageReport is the Golang representation of +// a channel message report. +type receivedChannelMessageReport struct { + + // Channel ID is the message of the channel this message was received on. + ChannelID *id.ID + + // MessageID is the ID of the channel message received. + MessageID cryptoChannel.MessageID + + // ReplyTo is overloaded to be a reply or react to, + // depending on the context of the received message + // (EventModel.ReceiveReaction or EventModel.ReceiveReply). + ReplyTo cryptoChannel.MessageID + + // SenderUsername is the username of the sender. + SenderUsername string + + // Content is the payload of the message. This is overloaded with + // reaction in the [EventModel.ReceiveReaction]. This may + // either be text or an emoji. + Content string + + // The timestamp of the message. + Timestamp time.Time + + // The duration of this channel message. + Lease time.Duration + + // The round this message was sent on. + Round rounds.Round +} + +// parseChannelMessageReport converts the JSON representation of +// a ReceivedChannelMessageReport into the Golang representation, +// receivedChannelMessageReport. +func parseChannelMessageReport(reportJson []byte) ( + receivedChannelMessageReport, error) { + + // Unmarshal message report + messageReport := ReceivedChannelMessageReport{} + err := json.Unmarshal(reportJson, &messageReport) + if err != nil { + return receivedChannelMessageReport{}, + errors.Errorf("Could not parse received message report (%s): %+v", + reportJson, err) + } + + // Unmarshal channel ID + chanId, err := id.Unmarshal(messageReport.ChannelId) + if err != nil { + return receivedChannelMessageReport{}, + errors.Errorf("Could not parse channel ID (%s): %+v", + messageReport.ChannelId, err) + } + + // Unmarshal message ID + msgId := cryptoChannel.MessageID{} + copy(msgId[:], messageReport.MessageId) + + // Unmarshal reply to/react to message ID + replyTo := cryptoChannel.MessageID{} + copy(replyTo[:], messageReport.ReplyTo) + + // Construct Round + rnd := rounds.Round{ID: id.Round(messageReport.Rounds[0])} + + return receivedChannelMessageReport{ + ChannelID: chanId, + MessageID: msgId, + ReplyTo: replyTo, + SenderUsername: messageReport.SenderUsername, + Content: string(messageReport.Content), + Timestamp: time.Unix(0, messageReport.Timestamp), + Lease: time.Duration(messageReport.Lease), + Round: rnd, + }, nil + +}