channels

package
v4.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2022 License: BSD-2-Clause, BSD-2-Clause Imports: 33 Imported by: 3

README

Channels provide a channels implementation on top of broadcast which is capable of handing the user facing features of channels, including replies, reactions, and eventually admin commands.

on sending, data propagates as follows:

Send function (Example: SendMessage) - > SendGeneric ->
Broadcast.BroadcastWithAssembler -> cmix.SendWithAssembler

on receiving messages propagate as follows:

cmix message pickup (by service)- > broadcast.Processor ->
userListener ->  events.triggerEvent ->
messageTypeHandler (example: Text) ->
eventModel (example: ReceiveMessage)

on sendingAdmin, data propagates as follows:

Send function - > SendAdminGeneric ->
Broadcast.BroadcastAsymmetricWithAssembler -> cmix.SendWithAssembler

on receiving admin messages propagate as follows:

cmix message pickup (by service)- > broadcast.Processor -> adminListener ->
events.triggerAdminEvent -> messageTypeHandler (example: Text) ->
eventModel (example: ReceiveMessage)

Documentation

Overview

Package channels provides a channels implementation on top of broadcast which is capable of handing the user facing features of channels, including replies, reactions, and eventually admin commands.

Index

Constants

View Source
const (

	// SendMessageTag is the base tag used when generating a debug tag for
	// sending a message.
	SendMessageTag = "ChMessage"

	// SendReplyTag is the base tag used when generating a debug tag for
	// sending a reply.
	SendReplyTag = "ChReply"

	// SendReactionTag is the base tag used when generating a debug tag for
	// sending a reaction.
	SendReactionTag = "ChReaction"
)
View Source
const AdminUsername = "Admin"

AdminUsername defines the displayed username of admin messages, which are unique users for every channel defined by the channel's private key.

Variables

View Source
var (
	// ChannelAlreadyExistsErr is returned when attempting to join a channel
	// that the user is already in.
	ChannelAlreadyExistsErr = errors.New(
		"the channel cannot be added because it already exists")

	// ChannelDoesNotExistsErr is returned when a channel does not exist.
	ChannelDoesNotExistsErr = errors.New("the channel cannot be found")

	// MessageTooLongErr is returned when attempting to send a message that is
	// too large.
	MessageTooLongErr = errors.New("the passed message is too long")

	// WrongPrivateKey is returned when the private key does not match the
	// channel's public key.
	WrongPrivateKey = errors.New(
		"the passed private key does not match the channel")

	// MessageTypeAlreadyRegistered is returned if a handler has already been
	// registered with the supplied message type. Only one handler can be
	// registered per type.
	MessageTypeAlreadyRegistered = errors.New(
		"the given message type has already been registered")

	// InvalidReaction is returned if the passed reaction string is an invalid
	// emoji.
	InvalidReaction = errors.New(
		"The reaction is not valid, it must be a single emoji")
)
View Source
var AdminFakePubKey = ed25519.PublicKey{}
View Source
var File_channelMessages_proto protoreflect.FileDescriptor
View Source
var ValidForever = time.Duration(math.MaxInt64)

ValidForever is used as a validUntil lease when sending to denote the message or operation never expires.

Note: A message relay must be present to enforce this otherwise things expire after 3 weeks due to network retention.

Functions

func IsNicknameValid

func IsNicknameValid(nick string) error

IsNicknameValid checks if a nickname is valid.

Rules:

  • A nickname must not be longer than 24 characters.
  • A nickname must not be shorter than 1 character.

TODO: Add character filtering.

func ValidateReaction

func ValidateReaction(reaction string) error

ValidateReaction checks that the reaction only contains a single emoji.

Types

type CMIXChannelReaction

type CMIXChannelReaction struct {
	Version           uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
	Reaction          string `protobuf:"bytes,2,opt,name=reaction,proto3" json:"reaction,omitempty"`
	ReactionMessageID []byte `protobuf:"bytes,3,opt,name=reactionMessageID,proto3" json:"reactionMessageID,omitempty"`
	// contains filtered or unexported fields
}

CMIXChannelReaction is the payload for reactions. The reaction must be a single emoji and the reactionMessageID must be non nil and a real message in the channel.

func (*CMIXChannelReaction) Descriptor deprecated

func (*CMIXChannelReaction) Descriptor() ([]byte, []int)

Deprecated: Use CMIXChannelReaction.ProtoReflect.Descriptor instead.

func (*CMIXChannelReaction) GetReaction

func (x *CMIXChannelReaction) GetReaction() string

func (*CMIXChannelReaction) GetReactionMessageID

func (x *CMIXChannelReaction) GetReactionMessageID() []byte

func (*CMIXChannelReaction) GetVersion

func (x *CMIXChannelReaction) GetVersion() uint32

func (*CMIXChannelReaction) ProtoMessage

func (*CMIXChannelReaction) ProtoMessage()

func (*CMIXChannelReaction) ProtoReflect

func (x *CMIXChannelReaction) ProtoReflect() protoreflect.Message

func (*CMIXChannelReaction) Reset

func (x *CMIXChannelReaction) Reset()

func (*CMIXChannelReaction) String

func (x *CMIXChannelReaction) String() string

type CMIXChannelText

type CMIXChannelText struct {
	Version        uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
	Text           string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"`
	ReplyMessageID []byte `protobuf:"bytes,3,opt,name=replyMessageID,proto3" json:"replyMessageID,omitempty"`
	// contains filtered or unexported fields
}

CMIXChannelText is the payload for sending normal text messages to channels the replyMessageID is nil when it is not a reply.

func (*CMIXChannelText) Descriptor deprecated

func (*CMIXChannelText) Descriptor() ([]byte, []int)

Deprecated: Use CMIXChannelText.ProtoReflect.Descriptor instead.

func (*CMIXChannelText) GetReplyMessageID

func (x *CMIXChannelText) GetReplyMessageID() []byte

func (*CMIXChannelText) GetText

func (x *CMIXChannelText) GetText() string

func (*CMIXChannelText) GetVersion

func (x *CMIXChannelText) GetVersion() uint32

func (*CMIXChannelText) ProtoMessage

func (*CMIXChannelText) ProtoMessage()

func (*CMIXChannelText) ProtoReflect

func (x *CMIXChannelText) ProtoReflect() protoreflect.Message

func (*CMIXChannelText) Reset

func (x *CMIXChannelText) Reset()

func (*CMIXChannelText) String

func (x *CMIXChannelText) String() string

type ChannelMessage

type ChannelMessage struct {

	// Lease is the length that this channel message will take effect.
	Lease int64 `protobuf:"varint,1,opt,name=Lease,proto3" json:"Lease,omitempty"`
	// The round this message was sent on.
	RoundID uint64 `protobuf:"varint,2,opt,name=RoundID,proto3" json:"RoundID,omitempty"`
	// The type the below payload is. This may be some form of channel command,
	// such as BAN<username1>.
	PayloadType uint32 `protobuf:"varint,3,opt,name=PayloadType,proto3" json:"PayloadType,omitempty"`
	// Payload is the actual message payload. It will be processed differently
	// based on the PayloadType.
	Payload []byte `protobuf:"bytes,4,opt,name=Payload,proto3" json:"Payload,omitempty"`
	// nickname is the name which the user is using for this message it will not
	// be longer than 24 characters.
	Nickname string `protobuf:"bytes,5,opt,name=Nickname,proto3" json:"Nickname,omitempty"`
	// Nonce is 32 bits of randomness to ensure that two messages in the same
	// round with that have the same nickname, payload, and lease will not have
	// the same message ID.
	Nonce []byte `protobuf:"bytes,6,opt,name=Nonce,proto3" json:"Nonce,omitempty"`
	// LocalTimestamp is the timestamp when the "send call" is made based upon
	// the local clock. If this differs by more than 5 seconds +/- from when the
	// round it sent on is queued, then a random mutation on the queued time
	// (+/- 200ms) will be used by local clients instead.
	LocalTimestamp int64 `protobuf:"varint,7,opt,name=LocalTimestamp,proto3" json:"LocalTimestamp,omitempty"`
	// contains filtered or unexported fields
}

ChannelMessage is transmitted by the channel. Effectively it is a command for the channel sent by a user with admin access of the channel.

func (*ChannelMessage) Descriptor deprecated

func (*ChannelMessage) Descriptor() ([]byte, []int)

Deprecated: Use ChannelMessage.ProtoReflect.Descriptor instead.

func (*ChannelMessage) GetLease

func (x *ChannelMessage) GetLease() int64

func (*ChannelMessage) GetLocalTimestamp

func (x *ChannelMessage) GetLocalTimestamp() int64

func (*ChannelMessage) GetNickname

func (x *ChannelMessage) GetNickname() string

func (*ChannelMessage) GetNonce

func (x *ChannelMessage) GetNonce() []byte

func (*ChannelMessage) GetPayload

func (x *ChannelMessage) GetPayload() []byte

func (*ChannelMessage) GetPayloadType

func (x *ChannelMessage) GetPayloadType() uint32

func (*ChannelMessage) GetRoundID

func (x *ChannelMessage) GetRoundID() uint64

func (*ChannelMessage) ProtoMessage

func (*ChannelMessage) ProtoMessage()

func (*ChannelMessage) ProtoReflect

func (x *ChannelMessage) ProtoReflect() protoreflect.Message

func (*ChannelMessage) Reset

func (x *ChannelMessage) Reset()

func (*ChannelMessage) String

func (x *ChannelMessage) String() string

type Client

type Client interface {
	GetMaxMessageLength() int
	SendWithAssembler(recipient *id.ID, assembler cmix.MessageAssembler,
		cmixParams cmix.CMIXParams) (rounds.Round, ephemeral.Id, error)
	IsHealthy() bool
	AddIdentity(id *id.ID, validUntil time.Time, persistent bool)
	AddIdentityWithHistory(
		id *id.ID, validUntil, beginning time.Time, persistent bool)
	AddService(clientID *id.ID, newService message.Service,
		response message.Processor)
	DeleteClientService(clientID *id.ID)
	RemoveIdentity(id *id.ID)
	GetRoundResults(timeout time.Duration, roundCallback cmix.RoundEventCallback,
		roundList ...id.Round)
	AddHealthCallback(f func(bool)) uint64
	RemoveHealthCallback(uint64)
}

Client contains the methods from cmix.Client that are required by the Manager.

type EventModel

type EventModel interface {
	// JoinChannel is called whenever a channel is joined locally.
	JoinChannel(channel *cryptoBroadcast.Channel)

	// LeaveChannel is called whenever a channel is left locally.
	LeaveChannel(channelID *id.ID)

	// 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.
	//
	// The API needs to return a UUID of the message that can be referenced at a
	// later time.
	//
	// messageID, timestamp, and round are all nillable and may be updated based
	// upon the UUID at a later date. A time of time.Time{} will be passed for a
	// nilled timestamp.
	//
	// Nickname may be empty, in which case the UI is expected to display the
	// codename.
	//
	// Message type is included in the call; it will always be Text (1) for this
	// call, but it may be required in downstream databases.
	ReceiveMessage(channelID *id.ID, messageID cryptoChannel.MessageID,
		nickname, text string, pubKey ed25519.PublicKey, codeset uint8,
		timestamp time.Time, lease time.Duration, round rounds.Round,
		mType MessageType, status SentStatus) uint64

	// ReceiveReply is called whenever a message is received that 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.
	//
	// The API needs to return a UUID of the message that can be referenced at a
	// later time.
	//
	// messageID, timestamp, and round are all nillable and may be updated based
	// upon the UUID at a later date. A time of time.Time{} will be passed for a
	// nilled timestamp.
	//
	// Nickname may be empty, in which case the UI is expected to display the
	// codename.
	//
	// Message type is included in the call; it will always be Text (1) for this
	// call, but it may be required in downstream databases.
	ReceiveReply(channelID *id.ID, messageID cryptoChannel.MessageID,
		reactionTo cryptoChannel.MessageID, nickname, text string,
		pubKey ed25519.PublicKey, codeset uint8, timestamp time.Time,
		lease time.Duration, round rounds.Round, mType MessageType,
		status SentStatus) uint64

	// ReceiveReaction is called whenever a reaction to a message is received on
	// a given channel. It may be called multiple times on the same reaction. 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.
	//
	// The API needs to return a UUID of the message that can be referenced at a
	// later time.
	//
	// messageID, timestamp, and round are all nillable and may be updated based
	// upon the UUID at a later date. A time of time.Time{} will be passed for a
	// nilled timestamp.
	//
	// Nickname may be empty, in which case the UI is expected to display the
	// codename.
	//
	// Message type is included in the call; it will always be Text (1) for this
	// call, but it may be required in downstream databases.
	ReceiveReaction(channelID *id.ID, messageID cryptoChannel.MessageID,
		reactionTo cryptoChannel.MessageID, nickname, reaction string,
		pubKey ed25519.PublicKey, codeset uint8, timestamp time.Time,
		lease time.Duration, round rounds.Round, mType MessageType,
		status SentStatus) uint64

	// UpdateSentStatus is called whenever the sent status of a message has
	// changed.
	//
	// messageID, timestamp, and round are all nillable and may be updated based
	// upon the UUID at a later date. A time of time.Time{} will be passed for a
	// nilled timestamp. If a nil value is passed, make no update.
	UpdateSentStatus(uuid uint64, messageID cryptoChannel.MessageID,
		timestamp time.Time, round rounds.Round, status SentStatus)
}

EventModel is an interface which an external party which uses the channels system passed an object which adheres to in order to get events on the channel.

type EventModelBuilder

type EventModelBuilder func(path string) (EventModel, error)

EventModelBuilder initialises the event model using the given path.

type Manager

type Manager interface {
	// GetIdentity returns the public identity associated with this channel
	// manager.
	GetIdentity() cryptoChannel.Identity

	// ExportPrivateIdentity encrypts and exports the private identity to a
	// portable string.
	ExportPrivateIdentity(password string) ([]byte, error)

	// GetStorageTag returns the tag at where this manager is stored. To be used
	// when loading the manager. The storage tag is derived from the public key.
	GetStorageTag() string

	// JoinChannel joins the given channel. It will fail if the channel has
	// already been joined.
	JoinChannel(channel *cryptoBroadcast.Channel) error

	// LeaveChannel leaves the given channel. It will return an error if the
	// channel was not previously joined.
	LeaveChannel(channelID *id.ID) error

	// SendGeneric is used to send a raw message over a channel. In general, it
	// should be wrapped in a function that defines the wire protocol.
	//
	// If the final message, before being sent over the wire, is too long, this
	// will return an error. Due to the underlying encoding using compression,
	// it is not possible to define the largest payload that can be sent, but it
	// will always be possible to send a payload of 802 bytes at minimum.
	//
	// The meaning of validUntil depends on the use case.
	SendGeneric(channelID *id.ID, messageType MessageType,
		msg []byte, validUntil time.Duration, params cmix.CMIXParams) (
		cryptoChannel.MessageID, rounds.Round, ephemeral.Id, error)

	// SendAdminGeneric is used to send a raw message over a channel encrypted
	// with admin keys, identifying it as sent by the admin. In general, it
	// should be wrapped in a function that defines the wire protocol.
	//
	// If the final message, before being sent over the wire, is too long, this
	// will return an error. The message must be at most 510 bytes long.
	SendAdminGeneric(privKey rsa.PrivateKey, channelID *id.ID,
		messageType MessageType, msg []byte, validUntil time.Duration,
		params cmix.CMIXParams) (cryptoChannel.MessageID,
		rounds.Round, ephemeral.Id, error)

	// SendMessage is used to send a formatted message over a channel.
	//
	// Due to the underlying encoding using compression, it is not possible to
	// define the largest payload that can be sent, but it will always be
	// possible to send a payload of 798 bytes at minimum.
	//
	// The message will auto delete validUntil after the round it is sent in,
	// lasting forever if ValidForever is used.
	SendMessage(channelID *id.ID, msg string, validUntil time.Duration,
		params cmix.CMIXParams) (
		cryptoChannel.MessageID, rounds.Round, ephemeral.Id, error)

	// SendReply is used to send a formatted message over a channel.
	//
	// Due to the underlying encoding using compression, it is not possible to
	// define the largest payload that can be sent, but it will always be
	// possible to send a payload of 766 bytes at minimum.
	//
	// If the message ID that the reply is sent to does not exist, then the
	// other side will post the message as a normal message and not as a reply.
	//
	// The message will auto delete validUntil after the round it is sent in,
	// lasting forever if ValidForever is used.
	SendReply(channelID *id.ID, msg string, replyTo cryptoChannel.MessageID,
		validUntil time.Duration, params cmix.CMIXParams) (
		cryptoChannel.MessageID, rounds.Round, ephemeral.Id, error)

	// SendReaction is used to send a reaction to a message over a channel. The
	// reaction must be a single emoji with no other characters, and will be
	// rejected otherwise.
	//
	// Clients will drop the reaction if they do not recognize the reactTo
	// message.
	SendReaction(channelID *id.ID, reaction string,
		reactTo cryptoChannel.MessageID, params cmix.CMIXParams) (
		cryptoChannel.MessageID, rounds.Round, ephemeral.Id, error)

	// RegisterReceiveHandler is used to register handlers for non default
	// message types so that they can be processed by modules. It is important
	// that such modules sync up with the event model implementation.
	//
	// There can only be one handler per message type, and this will return an
	// error on a multiple registration.
	RegisterReceiveHandler(
		messageType MessageType, listener MessageTypeReceiveMessage) error

	// GetChannels returns the IDs of all channels that have been joined. Use
	// getChannelsUnsafe if you already have taken the mux.
	GetChannels() []*id.ID

	// GetChannel returns the underlying cryptographic structure for a given
	// channel.
	GetChannel(chID *id.ID) (*cryptoBroadcast.Channel, error)

	// ReplayChannel replays all messages from the channel within the network's
	// memory (~3 weeks) over the event model. It does this by wiping the
	// underlying state tracking for message pickup for the channel, causing all
	// messages to be re-retrieved from the network.
	ReplayChannel(chID *id.ID) error

	// SetNickname sets the nickname for a channel after checking that the
	// nickname is valid using [IsNicknameValid].
	SetNickname(newNick string, chID *id.ID) error

	// DeleteNickname removes the nickname for a given channel, using the
	// codename for that channel instead.
	DeleteNickname(chID *id.ID) error

	// GetNickname returns the nickname for the given channel, if it exists.
	GetNickname(chID *id.ID) (nickname string, exists bool)
}

Manager provides an interface to manager channels.

func LoadManager

func LoadManager(storageTag string, kv *versioned.KV, net Client,
	rng *fastRNG.StreamGenerator, modelBuilder EventModelBuilder) (Manager, error)

LoadManager restores a channel Manager from disk stored at the given storage tag.

func NewManager

func NewManager(identity cryptoChannel.PrivateIdentity, kv *versioned.KV,
	net Client, rng *fastRNG.StreamGenerator, modelBuilder EventModelBuilder) (
	Manager, error)

NewManager creates a new channel Manager from a channel.PrivateIdentity. It prefixes the KV with a tag derived from the public key that can be retried for reloading using [Manager.GetStorageTag].

type MessageType

type MessageType uint32

MessageType is the type of message being sent to a channel.

const (
	// Text is the default type for a message. It denotes that the message only
	// contains text.
	Text MessageType = 1

	// AdminText denotes that the message only contains text and that it comes
	// from the channel admin.
	AdminText MessageType = 2

	// Reaction denotes that the message is a reaction to another message.
	Reaction MessageType = 3
)

func (MessageType) String

func (mt MessageType) String() string

String returns a human-readable version of MessageType, used for debugging and logging. This function adheres to the fmt.Stringer interface.

type MessageTypeReceiveMessage

type MessageTypeReceiveMessage func(channelID *id.ID,
	messageID cryptoChannel.MessageID, messageType MessageType,
	nickname string, content []byte, pubKey ed25519.PublicKey, codeset uint8,
	timestamp time.Time, lease time.Duration, round rounds.Round,
	status SentStatus) uint64

MessageTypeReceiveMessage defines handlers for messages of various message types. Default ones for Text, Reaction, and AdminText.

A unique UUID must be returned by which the message can be referenced later via [EventModel.UpdateSentStatus].

It must return a unique UUID for the message by which it can be referenced later.

type NameService

type NameService interface {
	// GetUsername returns the username.
	GetUsername() string

	// GetChannelValidationSignature returns the validation signature and the
	// time it was signed.
	GetChannelValidationSignature() ([]byte, time.Time)

	// GetChannelPubkey returns the user's public key.
	GetChannelPubkey() ed25519.PublicKey

	// SignChannelMessage returns the signature of the given message.
	SignChannelMessage(message []byte) (signature []byte, err error)

	// ValidateChannelMessage validates that a received channel message's
	// username lease is signed by the NameService.
	ValidateChannelMessage(username string, lease time.Time,
		pubKey ed25519.PublicKey, authorIDSignature []byte) bool
}

NameService is an interface which encapsulates the user identity channel tracking service.

NameService is currently unused.

func NewDummyNameService

func NewDummyNameService(username string, rng io.Reader) (NameService, error)

NewDummyNameService returns a dummy object adhering to the name service. This neither produces valid signatures nor validates passed signatures.

THIS IS FOR DEVELOPMENT AND DEBUGGING PURPOSES ONLY.

type SentStatus

type SentStatus uint8

SentStatus represents the current status of a channel message.

const (
	// Unsent is the status of a message when it is pending to be sent.
	Unsent SentStatus = iota

	// Sent is the status of a message once the round it is sent on completed.
	Sent

	// Delivered is the status of a message once is has been received.
	Delivered

	// Failed is the status of a message if it failed to send.
	Failed
)

func (SentStatus) String

func (ss SentStatus) String() string

String returns a human-readable version of SentStatus, used for debugging and logging. This function adheres to the fmt.Stringer interface.

type UserMessage

type UserMessage struct {

	// Message contains the contents of the message. This is typically what the
	// end-user has submitted to the channel. This is a serialization of the
	// ChannelMessage.
	Message []byte `protobuf:"bytes,1,opt,name=Message,proto3" json:"Message,omitempty"`
	// Signature is the signature proving this message has been sent by the
	// owner of this user's public key.
	//
	//	Signature = Sig(User_ECCPublicKey, Message)
	Signature []byte `protobuf:"bytes,3,opt,name=Signature,proto3" json:"Signature,omitempty"`
	// ECCPublicKey is the user's EC Public key. This is provided by the
	// network.
	ECCPublicKey []byte `protobuf:"bytes,5,opt,name=ECCPublicKey,proto3" json:"ECCPublicKey,omitempty"`
	// contains filtered or unexported fields
}

UserMessage is a message sent by a user who is a member within the channel.

func (*UserMessage) Descriptor deprecated

func (*UserMessage) Descriptor() ([]byte, []int)

Deprecated: Use UserMessage.ProtoReflect.Descriptor instead.

func (*UserMessage) GetECCPublicKey

func (x *UserMessage) GetECCPublicKey() []byte

func (*UserMessage) GetMessage

func (x *UserMessage) GetMessage() []byte

func (*UserMessage) GetSignature

func (x *UserMessage) GetSignature() []byte

func (*UserMessage) ProtoMessage

func (*UserMessage) ProtoMessage()

func (*UserMessage) ProtoReflect

func (x *UserMessage) ProtoReflect() protoreflect.Message

func (*UserMessage) Reset

func (x *UserMessage) Reset()

func (*UserMessage) String

func (x *UserMessage) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL