Files
tinode-chat/server/datamodel.go
T
2023-05-27 17:05:42 -07:00

1728 lines
52 KiB
Go

package main
/******************************************************************************
*
* Description :
*
* Wire protocol structures
*
*****************************************************************************/
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
"github.com/tinode/chat/server/store/types"
)
// MsgGetOpts defines Get query parameters.
type MsgGetOpts struct {
// Optional User ID to return result(s) for one user.
User string `json:"user,omitempty"`
// Optional topic name to return result(s) for one topic.
Topic string `json:"topic,omitempty"`
// Return results modified since this timespamp.
IfModifiedSince *time.Time `json:"ims,omitempty"`
// Load messages/ranges with IDs equal or greater than this (inclusive or closed).
SinceId int `json:"since,omitempty"`
// Load messages/ranges with IDs lower than this (exclusive or open).
BeforeId int `json:"before,omitempty"`
// Limit the number of messages loaded.
Limit int `json:"limit,omitempty"`
// Fetch messages with IDs in these ranges.
IdRanges []MsgRange `json:"ranges,omitempty"`
}
// MsgGetQuery is a topic metadata or data query.
type MsgGetQuery struct {
What string `json:"what"`
// Parameters of "desc" request: IfModifiedSince
Desc *MsgGetOpts `json:"desc,omitempty"`
// Parameters of "sub" request: User, Topic, IfModifiedSince, Limit.
Sub *MsgGetOpts `json:"sub,omitempty"`
// Parameters of "data" request: Since, Before, Limit.
Data *MsgGetOpts `json:"data,omitempty"`
// Parameters of "del" request: Since, Before, Limit.
Del *MsgGetOpts `json:"del,omitempty"`
}
// MsgSetSub is a payload in set.sub request to update current subscription or invite another user, {sub.what} == "sub".
type MsgSetSub struct {
// User affected by this request. Default (empty): current user.
User string `json:"user,omitempty"`
// Access mode change, either Given or Want depending on context.
Mode string `json:"mode,omitempty"`
}
// MsgSetDesc is a C2S in set.what == "desc", acc, sub message.
type MsgSetDesc struct {
// Default access mode.
DefaultAcs *MsgDefaultAcsMode `json:"defacs,omitempty"`
// Description of the user or topic.
Public any `json:"public,omitempty"`
// Trusted (system-provided) user or topic data.
Trusted any `json:"trusted,omitempty"`
// Per-subscription private data.
Private any `json:"private,omitempty"`
}
// MsgCredClient is an account credential such as email or phone number.
type MsgCredClient struct {
// Credential type, i.e. `email` or `tel`.
Method string `json:"meth,omitempty"`
// Value to verify, i.e. `user@example.com` or `+18003287448`
Value string `json:"val,omitempty"`
// Verification response
Response string `json:"resp,omitempty"`
// Request parameters, such as preferences. Passed to valiator without interpretation.
Params map[string]any `json:"params,omitempty"`
}
// MsgSetQuery is an update to topic or user metadata: description, subscriptions, tags, credentials.
type MsgSetQuery struct {
// Topic/user description, new object & new subscriptions only
Desc *MsgSetDesc `json:"desc,omitempty"`
// Subscription parameters
Sub *MsgSetSub `json:"sub,omitempty"`
// Indexable tags for user discovery
Tags []string `json:"tags,omitempty"`
// Update to account credentials.
Cred *MsgCredClient `json:"cred,omitempty"`
// Update auxiliary data
Aux map[string]any
}
// MsgRange is either an individual ID (HiId=0) or a randge of IDs, low end inclusive (closed),
// high-end exclusive (open): [LowId .. HiId), e.g. 1..5 -> 1, 2, 3, 4.
type MsgRange struct {
LowId int `json:"low,omitempty"`
HiId int `json:"hi,omitempty"`
}
/****************************************************************
* Client to Server (C2S) messages.
****************************************************************/
// MsgClientHi is a handshake {hi} message.
type MsgClientHi struct {
// Message Id
Id string `json:"id,omitempty"`
// User agent
UserAgent string `json:"ua,omitempty"`
// Protocol version, i.e. "0.13"
Version string `json:"ver,omitempty"`
// Client's unique device ID
DeviceID string `json:"dev,omitempty"`
// ISO 639-1 human language of the connected device
Lang string `json:"lang,omitempty"`
// Platform code: ios, android, web.
Platform string `json:"platf,omitempty"`
// Session is initially in non-iteractive, i.e. issued by a service. Presence notifications are delayed.
Background bool `json:"bkg,omitempty"`
}
// MsgClientAcc is an {acc} message for creating or updating a user account.
type MsgClientAcc struct {
// Message Id
Id string `json:"id,omitempty"`
// "newXYZ" to create a new user or UserId to update a user; default: current user.
User string `json:"user,omitempty"`
// Temporary authentication parameters for one-off actions, like password reset.
TmpScheme string `json:"tmpscheme,omitempty"`
TmpSecret []byte `json:"tmpsecret,omitempty"`
// Account state: normal, suspended.
State string `json:"status,omitempty"`
// Authentication level of the user when UserID is set and not equal to the current user.
// Either "", "auth" or "anon". Default: ""
AuthLevel string `json:"authlevel,omitempty"`
// The initial authentication scheme the account can use
Scheme string `json:"scheme,omitempty"`
// Shared secret
Secret []byte `json:"secret,omitempty"`
// Authenticate session with the newly created account
Login bool `json:"login,omitempty"`
// Indexable tags for user discovery
Tags []string `json:"tags,omitempty"`
// User initialization data when creating a new user, otherwise ignored
Desc *MsgSetDesc `json:"desc,omitempty"`
// Credentials to verify (email or phone or captcha)
Cred []MsgCredClient `json:"cred,omitempty"`
}
// MsgClientLogin is a login {login} message.
type MsgClientLogin struct {
// Message Id
Id string `json:"id,omitempty"`
// Authentication scheme
Scheme string `json:"scheme,omitempty"`
// Shared secret
Secret []byte `json:"secret"`
// Credntials being verified (email or phone or captcha etc.)
Cred []MsgCredClient `json:"cred,omitempty"`
}
// MsgClientSub is a subscription request {sub} message.
type MsgClientSub struct {
Id string `json:"id,omitempty"`
Topic string `json:"topic"`
// Mirrors {set}.
Set *MsgSetQuery `json:"set,omitempty"`
// Mirrors {get}.
Get *MsgGetQuery `json:"get,omitempty"`
// Intra-cluster fields.
// True if this subscription created a new topic.
// In case of p2p topics, it's true if the other user's subscription was
// created (as a part of new topic creation or just alone).
Created bool `json:"-"`
// True if this is a new subscription.
Newsub bool `json:"-"`
}
const (
constMsgMetaDesc = 1 << iota
constMsgMetaSub
constMsgMetaData
constMsgMetaTags
constMsgMetaDel
constMsgMetaCred
constMsgMetaAux
)
const (
constMsgDelTopic = iota + 1
constMsgDelMsg
constMsgDelSub
constMsgDelUser
constMsgDelCred
)
func parseMsgClientMeta(params string) int {
var bits int
parts := strings.SplitN(params, " ", 8)
for _, p := range parts {
switch p {
case "desc":
bits |= constMsgMetaDesc
case "sub":
bits |= constMsgMetaSub
case "data":
bits |= constMsgMetaData
case "tags":
bits |= constMsgMetaTags
case "del":
bits |= constMsgMetaDel
case "cred":
bits |= constMsgMetaCred
case "aux":
bits |= constMsgMetaAux
default:
// ignore unknown
}
}
return bits
}
func parseMsgClientDel(params string) int {
switch params {
case "", "msg":
return constMsgDelMsg
case "topic":
return constMsgDelTopic
case "sub":
return constMsgDelSub
case "user":
return constMsgDelUser
case "cred":
return constMsgDelCred
default:
// ignore
}
return 0
}
// MsgDefaultAcsMode is a topic default access mode.
type MsgDefaultAcsMode struct {
Auth string `json:"auth,omitempty"`
Anon string `json:"anon,omitempty"`
}
// MsgClientLeave is an unsubscribe {leave} request message.
type MsgClientLeave struct {
Id string `json:"id,omitempty"`
Topic string `json:"topic"`
Unsub bool `json:"unsub,omitempty"`
}
// MsgClientPub is client's request to publish data to topic subscribers {pub}.
type MsgClientPub struct {
Id string `json:"id,omitempty"`
Topic string `json:"topic"`
NoEcho bool `json:"noecho,omitempty"`
Head map[string]any `json:"head,omitempty"`
Content any `json:"content"`
}
// MsgClientGet is a query of topic state {get}.
type MsgClientGet struct {
Id string `json:"id,omitempty"`
Topic string `json:"topic"`
MsgGetQuery
}
// MsgClientSet is an update of topic state {set}.
type MsgClientSet struct {
Id string `json:"id,omitempty"`
Topic string `json:"topic"`
MsgSetQuery
}
// MsgClientDel delete messages or topic {del}.
type MsgClientDel struct {
Id string `json:"id,omitempty"`
Topic string `json:"topic,omitempty"`
// What to delete:
// * "msg" to delete messages (default)
// * "topic" to delete the topic
// * "sub" to delete a subscription to topic.
// * "user" to delete or disable user.
// * "cred" to delete credential (email or phone)
What string `json:"what"`
// Delete messages with these IDs (either one by one or a set of ranges)
DelSeq []MsgRange `json:"delseq,omitempty"`
// User ID of the user or subscription to delete
User string `json:"user,omitempty"`
// Credential to delete
Cred *MsgCredClient `json:"cred,omitempty"`
// Request to hard-delete objects (i.e. delete messages for all users), if such option is available.
Hard bool `json:"hard,omitempty"`
}
// MsgClientNote is a client-generated notification for topic subscribers {note}.
type MsgClientNote struct {
// There is no Id -- server will not akn {ping} packets, they are "fire and forget"
Topic string `json:"topic"`
// what is being reported: "recv" - message received, "read" - message read, "kp" - typing notification
What string `json:"what"`
// Server-issued message ID being reported
SeqId int `json:"seq,omitempty"`
// Client's count of unread messages to report back to the server. Used in push notifications on iOS.
Unread int `json:"unread,omitempty"`
// Call event.
Event string `json:"event,omitempty"`
// Arbitrary json payload (used in video calls).
Payload json.RawMessage `json:"payload,omitempty"`
}
// MsgClientExtra is not a stand-alone message but extra data which augments the main payload.
type MsgClientExtra struct {
// Array of out-of-band attachments which have to be exempted from GC.
Attachments []string `json:"attachments,omitempty"`
// Alternative user ID set by the root user (obo = On Behalf Of).
AsUser string `json:"obo,omitempty"`
// Altered authentication level set by the root user.
AuthLevel string `json:"authlevel,omitempty"`
}
// ClientComMessage is a wrapper for client messages.
type ClientComMessage struct {
Hi *MsgClientHi `json:"hi"`
Acc *MsgClientAcc `json:"acc"`
Login *MsgClientLogin `json:"login"`
Sub *MsgClientSub `json:"sub"`
Leave *MsgClientLeave `json:"leave"`
Pub *MsgClientPub `json:"pub"`
Get *MsgClientGet `json:"get"`
Set *MsgClientSet `json:"set"`
Del *MsgClientDel `json:"del"`
Note *MsgClientNote `json:"note"`
// Optional data.
Extra *MsgClientExtra `json:"extra"`
// Internal fields, routed only within the cluster.
// Message ID denormalized
Id string `json:"-"`
// Un-routable (original) topic name denormalized from XXX.Topic.
Original string `json:"-"`
// Routable (expanded) topic name.
RcptTo string `json:"-"`
// Sender's UserId as string.
AsUser string `json:"-"`
// Sender's authentication level.
AuthLvl int `json:"-"`
// Denormalized 'what' field of meta messages (set, get, del).
MetaWhat int `json:"-"`
// Timestamp when this message was received by the server.
Timestamp time.Time `json:"-"`
// Originating session to send an aknowledgement to.
sess *Session
// The message is initialized (true) as opposite to being used as a wrapper for session.
init bool
}
/****************************************************************
* Server to client messages.
****************************************************************/
// MsgLastSeenInfo contains info on user's appearance online - when & user agent.
type MsgLastSeenInfo struct {
// Timestamp of user's last appearance online.
When *time.Time `json:"when,omitempty"`
// User agent of the device when the user was last online.
UserAgent string `json:"ua,omitempty"`
}
func (src *MsgLastSeenInfo) describe() string {
return "'" + src.UserAgent + "' @ " + src.When.String()
}
// MsgCredServer is an account credential such as email or phone number.
type MsgCredServer struct {
// Credential type, i.e. `email` or `tel`.
Method string `json:"meth,omitempty"`
// Credential value, i.e. `user@example.com` or `+18003287448`
Value string `json:"val,omitempty"`
// Indicates that the credential is validated.
Done bool `json:"done,omitempty"`
}
// MsgAccessMode is a definition of access mode.
type MsgAccessMode struct {
// Access mode requested by the user
Want string `json:"want,omitempty"`
// Access mode granted to the user by the admin
Given string `json:"given,omitempty"`
// Cumulative access mode want & given
Mode string `json:"mode,omitempty"`
}
func (src *MsgAccessMode) describe() string {
var s string
if src.Want != "" {
s = "w=" + src.Want
}
if src.Given != "" {
s += " g=" + src.Given
}
if src.Mode != "" {
s += " m=" + src.Mode
}
return strings.TrimSpace(s)
}
// MsgTopicDesc is a topic description, S2C in Meta message.
type MsgTopicDesc struct {
CreatedAt *time.Time `json:"created,omitempty"`
UpdatedAt *time.Time `json:"updated,omitempty"`
// Timestamp of the last message
TouchedAt *time.Time `json:"touched,omitempty"`
// Account state, 'me' topic only.
State string `json:"state,omitempty"`
// If the group topic is online.
Online bool `json:"online,omitempty"`
// If the topic can be accessed as a channel
IsChan bool `json:"chan,omitempty"`
// P2P other user's last online timestamp & user agent
LastSeen *MsgLastSeenInfo `json:"seen,omitempty"`
DefaultAcs *MsgDefaultAcsMode `json:"defacs,omitempty"`
// Actual access mode
Acs *MsgAccessMode `json:"acs,omitempty"`
// Max message ID
SeqId int `json:"seq,omitempty"`
ReadSeqId int `json:"read,omitempty"`
RecvSeqId int `json:"recv,omitempty"`
// Id of the last delete operation as seen by the requesting user
DelId int `json:"clear,omitempty"`
Public any `json:"public,omitempty"`
Trusted any `json:"trusted,omitempty"`
// Per-subscription private data
Private any `json:"private,omitempty"`
}
func (src *MsgTopicDesc) describe() string {
var s string
if src.State != "" {
s = " state=" + src.State
}
s += " online=" + strconv.FormatBool(src.Online)
if src.Acs != nil {
s += " acs={" + src.Acs.describe() + "}"
}
if src.SeqId != 0 {
s += " seq=" + strconv.Itoa(src.SeqId)
}
if src.ReadSeqId != 0 {
s += " read=" + strconv.Itoa(src.ReadSeqId)
}
if src.RecvSeqId != 0 {
s += " recv=" + strconv.Itoa(src.RecvSeqId)
}
if src.DelId != 0 {
s += " clear=" + strconv.Itoa(src.DelId)
}
if src.Public != nil {
s += " pub='...'"
}
if src.Trusted != nil {
s += " trst='...'"
}
if src.Private != nil {
s += " priv='...'"
}
return s
}
// MsgTopicSub is topic subscription details, sent in Meta message.
type MsgTopicSub struct {
// Fields common to all subscriptions
// Timestamp when the subscription was last updated
UpdatedAt *time.Time `json:"updated,omitempty"`
// Timestamp when the subscription was deleted
DeletedAt *time.Time `json:"deleted,omitempty"`
// If the subscriber/topic is online
Online bool `json:"online,omitempty"`
// Access mode. Topic admins receive the full info, non-admins receive just the cumulative mode
// Acs.Mode = want & given. The field is not a pointer because at least one value is always assigned.
Acs MsgAccessMode `json:"acs,omitempty"`
// ID of the message reported by the given user as read
ReadSeqId int `json:"read,omitempty"`
// ID of the message reported by the given user as received
RecvSeqId int `json:"recv,omitempty"`
// Topic's public data
Public any `json:"public,omitempty"`
// Topic's trusted public data
Trusted any `json:"trusted,omitempty"`
// User's own private data per topic
Private any `json:"private,omitempty"`
// Response to non-'me' topic
// Uid of the subscribed user
User string `json:"user,omitempty"`
// The following sections makes sense only in context of getting
// user's own subscriptions ('me' topic response)
// Topic name of this subscription
Topic string `json:"topic,omitempty"`
// Timestamp of the last message in the topic.
TouchedAt *time.Time `json:"touched,omitempty"`
// ID of the last {data} message in a topic
SeqId int `json:"seq,omitempty"`
// Id of the latest Delete operation
DelId int `json:"clear,omitempty"`
// P2P topics in 'me' {get subs} response:
// Other user's last online timestamp & user agent
LastSeen *MsgLastSeenInfo `json:"seen,omitempty"`
}
func (src *MsgTopicSub) describe() string {
s := src.Topic + ":" + src.User + " online=" + strconv.FormatBool(src.Online) + " acs=" + src.Acs.describe()
if src.SeqId != 0 {
s += " seq=" + strconv.Itoa(src.SeqId)
}
if src.ReadSeqId != 0 {
s += " read=" + strconv.Itoa(src.ReadSeqId)
}
if src.RecvSeqId != 0 {
s += " recv=" + strconv.Itoa(src.RecvSeqId)
}
if src.DelId != 0 {
s += " clear=" + strconv.Itoa(src.DelId)
}
if src.Public != nil {
s += " pub='...'"
}
if src.Trusted != nil {
s += " trst='...'"
}
if src.Private != nil {
s += " priv='...'"
}
if src.LastSeen != nil {
s += " seen={" + src.LastSeen.describe() + "}"
}
return s
}
// MsgDelValues describes request to delete messages.
type MsgDelValues struct {
DelId int `json:"clear,omitempty"`
DelSeq []MsgRange `json:"delseq,omitempty"`
}
// MsgServerCtrl is a server control message {ctrl}.
type MsgServerCtrl struct {
Id string `json:"id,omitempty"`
Topic string `json:"topic,omitempty"`
Params any `json:"params,omitempty"`
Code int `json:"code"`
Text string `json:"text,omitempty"`
Timestamp time.Time `json:"ts"`
}
// Deep-shallow copy.
func (src *MsgServerCtrl) copy() *MsgServerCtrl {
if src == nil {
return nil
}
dst := *src
return &dst
}
func (src *MsgServerCtrl) describe() string {
return src.Topic + " id=" + src.Id + " code=" + strconv.Itoa(src.Code) + " txt=" + src.Text
}
// MsgServerData is a server {data} message.
type MsgServerData struct {
Topic string `json:"topic"`
// ID of the user who originated the message as {pub}, could be empty if sent by the system
From string `json:"from,omitempty"`
Timestamp time.Time `json:"ts"`
DeletedAt *time.Time `json:"deleted,omitempty"`
SeqId int `json:"seq"`
Head map[string]any `json:"head,omitempty"`
Content any `json:"content"`
}
// Deep-shallow copy.
func (src *MsgServerData) copy() *MsgServerData {
if src == nil {
return nil
}
dst := *src
return &dst
}
func (src *MsgServerData) describe() string {
s := src.Topic + " from=" + src.From + " seq=" + strconv.Itoa(src.SeqId)
if src.DeletedAt != nil {
s += " deleted"
} else {
if src.Head != nil {
s += " head=..."
}
s += " content='...'"
}
return s
}
// MsgServerPres is presence notification {pres} (authoritative update).
type MsgServerPres struct {
Topic string `json:"topic"`
Src string `json:"src,omitempty"`
What string `json:"what"`
UserAgent string `json:"ua,omitempty"`
SeqId int `json:"seq,omitempty"`
DelId int `json:"clear,omitempty"`
DelSeq []MsgRange `json:"delseq,omitempty"`
AcsTarget string `json:"tgt,omitempty"`
AcsActor string `json:"act,omitempty"`
// Acs or a delta Acs. Need to marshal it to json under a name different than 'acs'
// to allow different handling on the client
Acs *MsgAccessMode `json:"dacs,omitempty"`
// UNroutable params. All marked with `json:"-"` to exclude from json marshaling.
// They are still serialized for intra-cluster communication.
// Flag to break the reply loop
WantReply bool `json:"-"`
// Additional access mode filters when sending to topic's online members. Both filter conditions must be true.
// send only to those who have this access mode.
FilterIn int `json:"-"`
// skip those who have this access mode.
FilterOut int `json:"-"`
// When sending to 'me', skip sessions subscribed to this topic.
SkipTopic string `json:"-"`
// Send to sessions of a single user only.
SingleUser string `json:"-"`
// Exclude sessions of a single user.
ExcludeUser string `json:"-"`
}
// Deep-shallow copy.
func (src *MsgServerPres) copy() *MsgServerPres {
if src == nil {
return nil
}
dst := *src
return &dst
}
func (src *MsgServerPres) describe() string {
s := src.Topic
if src.Src != "" {
s += " src=" + src.Src
}
if src.What != "" {
s += " what=" + src.What
}
if src.UserAgent != "" {
s += " ua=" + src.UserAgent
}
if src.SeqId != 0 {
s += " seq=" + strconv.Itoa(src.SeqId)
}
if src.DelId != 0 {
s += " clear=" + strconv.Itoa(src.DelId)
}
if src.DelSeq != nil {
s += " delseq"
}
if src.AcsTarget != "" {
s += " tgt=" + src.AcsTarget
}
if src.AcsActor != "" {
s += " actor=" + src.AcsActor
}
if src.Acs != nil {
s += " dacs=" + src.Acs.describe()
}
return s
}
// MsgServerMeta is a topic metadata {meta} update.
type MsgServerMeta struct {
Id string `json:"id,omitempty"`
Topic string `json:"topic"`
Timestamp *time.Time `json:"ts,omitempty"`
// Topic description
Desc *MsgTopicDesc `json:"desc,omitempty"`
// Subscriptions as an array of objects
Sub []MsgTopicSub `json:"sub,omitempty"`
// Delete ID and the ranges of IDs of deleted messages
Del *MsgDelValues `json:"del,omitempty"`
// User discovery tags
Tags []string `json:"tags,omitempty"`
// Account credentials, 'me' only.
Cred []*MsgCredServer `json:"cred,omitempty"`
// Auxiliary data
Aux map[string]any `json:"aux,omitempty"`
}
// Deep-shallow copy of meta message. Deep copy of Id and Topic fields, shallow copy of payload.
func (src *MsgServerMeta) copy() *MsgServerMeta {
if src == nil {
return nil
}
dst := *src
return &dst
}
func (src *MsgServerMeta) describe() string {
s := src.Topic + " id=" + src.Id
if src.Desc != nil {
s += " desc={" + src.Desc.describe() + "}"
}
if src.Sub != nil {
var x []string
for _, sub := range src.Sub {
x = append(x, sub.describe())
}
s += " sub=[{" + strings.Join(x, "},{") + "}]"
}
if src.Del != nil {
x, _ := json.Marshal(src.Del)
s += " del={" + string(x) + "}"
}
if src.Tags != nil {
s += " tags=[" + strings.Join(src.Tags, ",") + "]"
}
if src.Cred != nil {
x, _ := json.Marshal(src.Cred)
s += " cred=[" + string(x) + "]"
}
if src.Aux != nil {
x, _ := json.Marshal(src.Aux)
s += " aux=[" + string(x) + "]"
}
return s
}
// MsgServerInfo is the server-side copy of MsgClientNote with From and optionally Src added (non-authoritative).
type MsgServerInfo struct {
// Topic to send event to.
Topic string `json:"topic"`
// Topic where the even has occurred (set only when Topic='me').
Src string `json:"src,omitempty"`
// ID of the user who originated the message.
From string `json:"from,omitempty"`
// The event being reported: "rcpt" - message received, "read" - message read, "kp" - typing notification, "call" - video call.
What string `json:"what"`
// Server-issued message ID being reported.
SeqId int `json:"seq,omitempty"`
// Call event.
Event string `json:"event,omitempty"`
// Arbitrary json payload (used by video calls).
Payload json.RawMessage `json:"payload,omitempty"`
// UNroutable params. All marked with `json:"-"` to exclude from json marshaling.
// They are still serialized for intra-cluster communication.
// When sending to 'me', skip sessions subscribed to this topic.
SkipTopic string `json:"-"`
}
// Deep copy.
func (src *MsgServerInfo) copy() *MsgServerInfo {
if src == nil {
return nil
}
dst := *src
return &dst
}
// Basic description.
func (src *MsgServerInfo) describe() string {
s := src.Topic
if src.Src != "" {
s += " src=" + src.Src
}
s += " what=" + src.What + " from=" + src.From
if src.SeqId > 0 {
s += " seq=" + strconv.Itoa(src.SeqId)
}
if len(src.Payload) > 0 {
s += " payload=<..." + strconv.Itoa(len(src.Payload)) + " bytes ...>"
}
return s
}
// ServerComMessage is a wrapper for server-side messages.
type ServerComMessage struct {
Ctrl *MsgServerCtrl `json:"ctrl,omitempty"`
Data *MsgServerData `json:"data,omitempty"`
Meta *MsgServerMeta `json:"meta,omitempty"`
Pres *MsgServerPres `json:"pres,omitempty"`
Info *MsgServerInfo `json:"info,omitempty"`
// Internal fields.
// MsgServerData has no Id field, copying it here for use in {ctrl} aknowledgements
Id string `json:"-"`
// Routable (expanded) name of the topic.
RcptTo string `json:"-"`
// User ID of the sender of the original message.
AsUser string `json:"-"`
// Timestamp for consistency of timestamps in {ctrl} messages
// (corresponds to originating client message receipt timestamp).
Timestamp time.Time `json:"-"`
// Originating session to send an aknowledgement to. Could be nil.
sess *Session
// Session ID to skip when sendng packet to sessions. Used to skip sending to original session.
// Could be either empty.
SkipSid string `json:"-"`
// User id affected by this message.
uid types.Uid
}
// Deep-shallow copy of ServerComMessage. Deep copy of service fields,
// shallow copy of session and payload.
func (src *ServerComMessage) copy() *ServerComMessage {
if src == nil {
return nil
}
dst := &ServerComMessage{
Id: src.Id,
RcptTo: src.RcptTo,
AsUser: src.AsUser,
Timestamp: src.Timestamp,
sess: src.sess,
SkipSid: src.SkipSid,
uid: src.uid,
}
dst.Ctrl = src.Ctrl.copy()
dst.Data = src.Data.copy()
dst.Meta = src.Meta.copy()
dst.Pres = src.Pres.copy()
dst.Info = src.Info.copy()
return dst
}
func (src *ServerComMessage) describe() string {
if src == nil {
return "-"
}
switch {
case src.Ctrl != nil:
return "{ctrl " + src.Ctrl.describe() + "}"
case src.Data != nil:
return "{data " + src.Data.describe() + "}"
case src.Meta != nil:
return "{meta " + src.Meta.describe() + "}"
case src.Pres != nil:
return "{pres " + src.Pres.describe() + "}"
case src.Info != nil:
return "{info " + src.Info.describe() + "}"
default:
return "{nil}"
}
}
// Generators of server-side error messages {ctrl}.
// NoErr indicates successful completion (200).
func NoErr(id, topic string, ts time.Time) *ServerComMessage {
return NoErrParams(id, topic, ts, nil)
}
// NoErrExplicitTs indicates successful completion with explicit server and incoming request timestamps (200).
func NoErrExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return NoErrParamsExplicitTs(id, topic, serverTs, incomingReqTs, nil)
}
// NoErrReply indicates successful completion as a reply to a client message (200).
func NoErrReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return NoErrExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp)
}
// NoErrParams indicates successful completion with additional parameters (200).
func NoErrParams(id, topic string, ts time.Time, params any) *ServerComMessage {
return NoErrParamsExplicitTs(id, topic, ts, ts, params)
}
// NoErrParamsExplicitTs indicates successful completion with additional parameters
// and explicit server and incoming request timestamps (200).
func NoErrParamsExplicitTs(id, topic string, serverTs, incomingReqTs time.Time, params any) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusOK, // 200
Text: "ok",
Topic: topic,
Params: params,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// NoErrParamsReply indicates successful completion with additional parameters
// and explicit server and incoming request timestamps (200).
func NoErrParamsReply(msg *ClientComMessage, ts time.Time, params any) *ServerComMessage {
return NoErrParamsExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp, params)
}
// NoErrCreated indicated successful creation of an object (201).
func NoErrCreated(id, topic string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusCreated, // 201
Text: "created",
Topic: topic,
Timestamp: ts,
},
Id: id,
Timestamp: ts,
}
}
// NoErrAccepted indicates request was accepted but not processed yet (202).
func NoErrAccepted(id, topic string, ts time.Time) *ServerComMessage {
return NoErrAcceptedExplicitTs(id, topic, ts, ts)
}
// NoErrAcceptedExplicitTs indicates request was accepted but not processed yet
// with explicit server and incoming request timestamps (202).
func NoErrAcceptedExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusAccepted, // 202
Text: "accepted",
Topic: topic,
Timestamp: serverTs,
}, Id: id,
Timestamp: incomingReqTs,
}
}
// NoContentParams indicates request was processed but resulted in no content (204).
func NoContentParams(id, topic string, serverTs, incomingReqTs time.Time, params any) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusNoContent, // 204
Text: "no content",
Topic: topic,
Params: params,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// NoContentParamsReply indicates request was processed but resulted in no content
// in response to a client request (204).
func NoContentParamsReply(msg *ClientComMessage, ts time.Time, params any) *ServerComMessage {
return NoContentParams(msg.Id, msg.Original, ts, msg.Timestamp, params)
}
// NoErrEvicted indicates that the user was disconnected from topic for no fault of the user (205).
func NoErrEvicted(id, topic string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusResetContent, // 205
Text: "evicted",
Topic: topic,
Timestamp: ts,
}, Id: id,
}
}
// NoErrShutdown means user was disconnected from topic because system shutdown is in progress (205).
func NoErrShutdown(ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Code: http.StatusResetContent, // 205
Text: "server shutdown",
Timestamp: ts,
},
}
}
// NoErrDeliveredParams means requested content has been delivered (208).
func NoErrDeliveredParams(id, topic string, ts time.Time, params any) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusAlreadyReported, // 208
Text: "delivered",
Topic: topic,
Params: params,
Timestamp: ts,
},
Id: id,
}
}
// 3xx
// InfoValidateCredentials requires user to confirm credentials before going forward (300).
func InfoValidateCredentials(id string, ts time.Time) *ServerComMessage {
return InfoValidateCredentialsExplicitTs(id, ts, ts)
}
// InfoValidateCredentialsExplicitTs requires user to confirm credentials before going forward
// with explicit server and incoming request timestamps (300).
func InfoValidateCredentialsExplicitTs(id string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusMultipleChoices, // 300
Text: "validate credentials",
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// InfoChallenge requires user to respond to presented challenge before login can be completed (300).
func InfoChallenge(id string, ts time.Time, challenge []byte) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusMultipleChoices, // 300
Text: "challenge",
Params: map[string]any{"challenge": challenge},
Timestamp: ts,
},
Id: id,
Timestamp: ts,
}
}
// InfoAuthReset is sent in response to request to reset authentication when it was completed
// but login was not performed (301).
func InfoAuthReset(id string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusMovedPermanently, // 301
Text: "auth reset",
Timestamp: ts,
},
Id: id,
Timestamp: ts,
}
}
// InfoUseOther is a response to a subscription request redirecting client to another topic (303).
func InfoUseOther(id, topic, other string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusSeeOther, // 303
Text: "use other",
Topic: topic,
Params: map[string]string{"topic": other},
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// InfoUseOtherReply is a response to a subscription request redirecting client to another topic (303).
func InfoUseOtherReply(msg *ClientComMessage, other string, ts time.Time) *ServerComMessage {
return InfoUseOther(msg.Id, msg.Original, other, ts, msg.Timestamp)
}
// InfoAlreadySubscribed response means request to subscribe was ignored because user is already subscribed (304).
func InfoAlreadySubscribed(id, topic string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusNotModified, // 304
Text: "already subscribed",
Topic: topic,
Timestamp: ts,
},
Id: id, Timestamp: ts,
}
}
// InfoNotJoined response means request to leave was ignored because user was not subscribed (304).
func InfoNotJoined(id, topic string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusNotModified, // 304
Text: "not joined",
Topic: topic,
Timestamp: ts,
},
Id: id,
Timestamp: ts,
}
}
// InfoNoAction response means request was ignored because the object was already in the desired state
// with explicit server and incoming request timestamps (304).
func InfoNoAction(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusNotModified, // 304
Text: "no action",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// InfoNoActionReply response means request was ignored because the object was already in the desired state
// in response to a client request (304).
func InfoNoActionReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return InfoNoAction(msg.Id, msg.Original, ts, msg.Timestamp)
}
// InfoNotModified response means update request was a noop (304).
func InfoNotModified(id, topic string, ts time.Time) *ServerComMessage {
return InfoNotModifiedExplicitTs(id, topic, ts, ts)
}
// InfoNotModifiedReply response means update request was a noop
// in response to a client request (304).
func InfoNotModifiedReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return InfoNotModifiedExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp)
}
// InfoNotModifiedExplicitTs response means update request was a noop
// with explicit server and incoming request timestamps (304).
func InfoNotModifiedExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusNotModified, // 304
Text: "not modified",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// InfoFound redirects to a new resource (307).
func InfoFound(id, topic string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusTemporaryRedirect, // 307
Text: "found",
Topic: topic,
Timestamp: ts,
},
Id: id,
Timestamp: ts,
}
}
// 4xx Errors
// ErrMalformed request malformed (400).
func ErrMalformed(id, topic string, ts time.Time) *ServerComMessage {
return ErrMalformedExplicitTs(id, topic, ts, ts)
}
// ErrMalformedReply request malformed
// in response to a client request (400).
func ErrMalformedReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrMalformedExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrMalformedExplicitTs request malformed with explicit server and incoming request timestamps (400).
func ErrMalformedExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusBadRequest, // 400
Text: "malformed",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrAuthRequired authentication required - user must authenticate first (401).
func ErrAuthRequired(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusUnauthorized, // 401
Text: "authentication required",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrAuthRequiredReply authentication required - user must authenticate first
// in response to a client request (401).
func ErrAuthRequiredReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrAuthRequired(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrAuthFailed authentication failed
// with explicit server and incoming request timestamps (401).
func ErrAuthFailed(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusUnauthorized, // 401
Text: "authentication failed",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrAuthUnknownScheme authentication scheme is unrecognized or invalid (401).
func ErrAuthUnknownScheme(id, topic string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusUnauthorized, // 401
Text: "unknown authentication scheme",
Topic: topic,
Timestamp: ts,
},
Id: id,
Timestamp: ts,
}
}
// ErrPermissionDenied user is authenticated but operation is not permitted (403).
func ErrPermissionDenied(id, topic string, ts time.Time) *ServerComMessage {
return ErrPermissionDeniedExplicitTs(id, topic, ts, ts)
}
// ErrPermissionDeniedExplicitTs user is authenticated but operation is not permitted
// with explicit server and incoming request timestamps (403).
func ErrPermissionDeniedExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusForbidden, // 403
Text: "permission denied",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrPermissionDeniedReply user is authenticated but operation is not permitted
// with explicit server and incoming request timestamps in response to a client request (403).
func ErrPermissionDeniedReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrPermissionDeniedExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrAPIKeyRequired valid API key is required (403).
func ErrAPIKeyRequired(ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Code: http.StatusForbidden,
Text: "valid API key required",
Timestamp: ts,
},
}
}
// ErrSessionNotFound valid API key is required (403).
func ErrSessionNotFound(ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Code: http.StatusForbidden,
Text: "invalid or expired session",
Timestamp: ts,
},
}
}
// ErrTopicNotFound topic is not found
// with explicit server and incoming request timestamps (404).
func ErrTopicNotFound(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusNotFound,
Text: "topic not found", // 404
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrTopicNotFoundReply topic is not found
// with explicit server and incoming request timestamps
// in response to a client request (404).
func ErrTopicNotFoundReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrTopicNotFound(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrUserNotFound user is not found
// with explicit server and incoming request timestamps (404).
func ErrUserNotFound(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusNotFound, // 404
Text: "user not found",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrUserNotFoundReply user is not found
// with explicit server and incoming request timestamps in response to a client request (404).
func ErrUserNotFoundReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrUserNotFound(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrNotFound is an error for missing objects other than user or topic (404).
func ErrNotFound(id, topic string, ts time.Time) *ServerComMessage {
return ErrNotFoundExplicitTs(id, topic, ts, ts)
}
// ErrNotFoundExplicitTs is an error for missing objects other than user or topic
// with explicit server and incoming request timestamps (404).
func ErrNotFoundExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusNotFound, // 404
Text: "not found",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrNotFoundReply is an error for missing objects other than user or topic
// with explicit server and incoming request timestamps in response to a client request (404).
func ErrNotFoundReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrNotFoundExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrOperationNotAllowed a valid operation is not permitted in this context (405).
func ErrOperationNotAllowed(id, topic string, ts time.Time) *ServerComMessage {
return ErrOperationNotAllowedExplicitTs(id, topic, ts, ts)
}
// ErrOperationNotAllowedExplicitTs a valid operation is not permitted in this context
// with explicit server and incoming request timestamps (405).
func ErrOperationNotAllowedExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusMethodNotAllowed, // 405
Text: "operation or method not allowed",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrOperationNotAllowedReply a valid operation is not permitted in this context
// with explicit server and incoming request timestamps (405).
func ErrOperationNotAllowedReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrOperationNotAllowedExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrInvalidResponse indicates that the client's response in invalid
// with explicit server and incoming request timestamps (406).
func ErrInvalidResponse(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusNotAcceptable, // 406
Text: "invalid response",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrAlreadyAuthenticated invalid attempt to authenticate an already authenticated session
// Switching users is not supported (409).
func ErrAlreadyAuthenticated(id, topic string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusConflict, // 409
Text: "already authenticated",
Topic: topic,
Timestamp: ts,
},
Id: id,
Timestamp: ts,
}
}
// ErrDuplicateCredential attempt to create a duplicate credential
// with explicit server and incoming request timestamps (409).
func ErrDuplicateCredential(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusConflict, // 409
Text: "duplicate credential",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrAttachFirst must attach to topic first in response to a client message (409).
func ErrAttachFirst(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: msg.Id,
Code: http.StatusConflict, // 409
Text: "must attach first",
Topic: msg.Original,
Timestamp: ts,
},
Id: msg.Id,
Timestamp: msg.Timestamp,
}
}
// ErrAlreadyExists the object already exists (409).
func ErrAlreadyExists(id, topic string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusConflict, // 409
Text: "already exists",
Topic: topic,
Timestamp: ts,
},
Id: id,
Timestamp: ts,
}
}
// ErrCommandOutOfSequence invalid sequence of comments, i.e. attempt to {sub} before {hi} (409).
func ErrCommandOutOfSequence(id, unused string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusConflict, // 409
Text: "command out of sequence",
Timestamp: ts,
},
Id: id,
Timestamp: ts,
}
}
// ErrGone topic deleted or user banned (410).
func ErrGone(id, topic string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusGone, // 410
Text: "gone",
Topic: topic,
Timestamp: ts,
},
Id: id,
Timestamp: ts,
}
}
// ErrTooLarge packet or request size exceeded the limit (413).
func ErrTooLarge(id, topic string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusRequestEntityTooLarge, // 413
Text: "too large",
Topic: topic,
Timestamp: ts,
},
Id: id,
Timestamp: ts,
}
}
// ErrPolicy request violates a policy (e.g. password is too weak or too many subscribers) (422).
func ErrPolicy(id, topic string, ts time.Time) *ServerComMessage {
return ErrPolicyExplicitTs(id, topic, ts, ts)
}
// ErrPolicyExplicitTs request violates a policy (e.g. password is too weak or too many subscribers)
// with explicit server and incoming request timestamps (422).
func ErrPolicyExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusUnprocessableEntity, // 422
Text: "policy violation",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrPolicyReply request violates a policy (e.g. password is too weak or too many subscribers)
// with explicit server and incoming request timestamps in response to a client request (422).
func ErrPolicyReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrPolicyExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrCallBusyExplicitTs indicates a "busy" reply to a video call request (486).
func ErrCallBusyExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: 486, // Busy here.
Text: "busy here",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrCallBusyReply indicates a "busy" reply in response to a video call request (486)
func ErrCallBusyReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrCallBusyExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrUnknown database or other server error (500).
func ErrUnknown(id, topic string, ts time.Time) *ServerComMessage {
return ErrUnknownExplicitTs(id, topic, ts, ts)
}
// ErrUnknownExplicitTs database or other server error with explicit server and incoming request timestamps (500).
func ErrUnknownExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusInternalServerError, // 500
Text: "internal error",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrUnknownReply database or other server error in response to a client request (500).
func ErrUnknownReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrUnknownExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrNotImplemented feature not implemented with explicit server and incoming request timestamps (501).
// TODO: consider changing status code to 4XX.
func ErrNotImplemented(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusNotImplemented, // 501
Text: "not implemented",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrNotImplementedReply feature not implemented error in response to a client request (501).
func ErrNotImplementedReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrNotImplemented(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrClusterUnreachableReply in-cluster communication has failed error as response to a client request (502).
func ErrClusterUnreachableReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrClusterUnreachableExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrClusterUnreachable in-cluster communication has failed error with explicit server and
// incoming request timestamps (502).
func ErrClusterUnreachableExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusBadGateway, // 502
Text: "cluster unreachable",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrServiceUnavailableReply server overloaded error in response to a client request (503).
func ErrServiceUnavailableReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrServiceUnavailableExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrServiceUnavailableExplicitTs server overloaded error with explicit server and
// incoming request timestamps (503).
func ErrServiceUnavailableExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusServiceUnavailable, // 503
Text: "service unavailable",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrLocked operation rejected because the topic is being deleted (503).
func ErrLocked(id, topic string, ts time.Time) *ServerComMessage {
return ErrLockedExplicitTs(id, topic, ts, ts)
}
// ErrLockedReply operation rejected because the topic is being deleted in response
// to a client request (503).
func ErrLockedReply(msg *ClientComMessage, ts time.Time) *ServerComMessage {
return ErrLockedExplicitTs(msg.Id, msg.Original, ts, msg.Timestamp)
}
// ErrLockedExplicitTs operation rejected because the topic is being deleted
// with explicit server and incoming request timestamps (503).
func ErrLockedExplicitTs(id, topic string, serverTs, incomingReqTs time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusServiceUnavailable, // 503
Text: "locked",
Topic: topic,
Timestamp: serverTs,
},
Id: id,
Timestamp: incomingReqTs,
}
}
// ErrVersionNotSupported invalid (too low) protocol version (505).
func ErrVersionNotSupported(id string, ts time.Time) *ServerComMessage {
return &ServerComMessage{
Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusHTTPVersionNotSupported, // 505
Text: "version not supported",
Timestamp: ts,
},
Id: id,
Timestamp: ts,
}
}