Skip to content

Commit

Permalink
Add support for XEP-0202: Entity Time (ortuman#150)
Browse files Browse the repository at this point in the history
  • Loading branch information
ortuman authored Apr 21, 2021
1 parent 8193487 commit 12b4a37
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ docker run --name=jackal \
- [XEP-0190: Best Practice for Closing Idle Streams](https://xmpp.org/extensions/xep-0190.html) *1.1*
- [XEP-0191: Blocking Command](https://xmpp.org/extensions/xep-0191.html) *1.3*
- [XEP-0199: XMPP Ping](https://xmpp.org/extensions/xep-0199.html) *2.0*
- [XEP-0202: Entity Time](https://xmpp.org/extensions/xep-0202.html) *2.0*
- [XEP-0220: Server Dialback](https://xmpp.org/extensions/xep-0220.html) *1.1.1*
- [XEP-0237: Roster Versioning](https://xmpp.org/extensions/xep-0237.html) *1.3*
- [XEP-0280: Message Carbons](https://xmpp.org/extensions/xep-0280.html) *0.13.3*
Expand Down
2 changes: 1 addition & 1 deletion cmd/jackal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ type s2sOutConfig struct {
}

type modulesConfig struct {
Enabled []string `fig:"enabled" default:"[roster,offline,last,disco,private,vcard,version,caps,blocklist,ping,carbons]"`
Enabled []string `fig:"enabled" default:"[roster,offline,last,disco,private,vcard,version,caps,blocklist,ping,time,carbons]"`

Offline struct {
QueueSize int `fig:"queue_size" default:"200"`
Expand Down
6 changes: 6 additions & 0 deletions cmd/jackal/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/ortuman/jackal/module/xep0115"
"github.com/ortuman/jackal/module/xep0191"
"github.com/ortuman/jackal/module/xep0199"
"github.com/ortuman/jackal/module/xep0202"
"github.com/ortuman/jackal/module/xep0280"
"github.com/ortuman/jackal/util/stringmatcher"
stringsutil "github.com/ortuman/jackal/util/strings"
Expand Down Expand Up @@ -96,6 +97,11 @@ func initModules(a *serverApp, cfg modulesConfig) error {
})
mods = append(mods, ping)
}
// time
if stringsutil.StringSliceContains(xep0202.ModuleName, cfg.Enabled) {
time := xep0202.New(a.router)
mods = append(mods, time)
}
// carbons
if stringsutil.StringSliceContains(xep0280.ModuleName, cfg.Enabled) {
carbons := xep0280.New(a.hosts, a.router, a.resMng, a.sonar)
Expand Down
22 changes: 22 additions & 0 deletions module/xep0202/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2021 The jackal Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xep0202

import "github.com/ortuman/jackal/router"

//go:generate moq -out router.mock_test.go . globalRouter:routerMock
type globalRouter interface {
router.Router
}
114 changes: 114 additions & 0 deletions module/xep0202/time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2021 The jackal Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xep0202

import (
"context"
"time"

"github.com/jackal-xmpp/stravaganza/v2"
stanzaerror "github.com/jackal-xmpp/stravaganza/v2/errors/stanza"
"github.com/ortuman/jackal/log"
"github.com/ortuman/jackal/router"
xmpputil "github.com/ortuman/jackal/util/xmpp"
)

const timeNamespace = "urn:xmpp:time"

const (
// ModuleName represents time module name.
ModuleName = "time"

// XEPNumber represents time XEP number.
XEPNumber = "0202"
)

// Time represents a last activity (XEP-0202) module type.
type Time struct {
router router.Router
tmFn func() time.Time
}

// New returns a new initialized Time instance.
func New(
router router.Router,
) *Time {
return &Time{
router: router,
tmFn: time.Now,
}
}

// Name returns time module name.
func (m *Time) Name() string { return ModuleName }

// StreamFeature returns time module stream feature.
func (m *Time) StreamFeature(_ context.Context, _ string) (stravaganza.Element, error) {
return nil, nil
}

// ServerFeatures returns time server disco features.
func (m *Time) ServerFeatures(_ context.Context) ([]string, error) {
return []string{timeNamespace}, nil
}

// AccountFeatures returns time account disco features.
func (m *Time) AccountFeatures(_ context.Context) ([]string, error) {
return nil, nil
}

// Start starts time module.
func (m *Time) Start(_ context.Context) error {
log.Infow("Started time module", "xep", XEPNumber)
return nil
}

// Stop stops time module.
func (m *Time) Stop(_ context.Context) error {
log.Infow("Stopped time module", "xep", XEPNumber)
return nil
}

// MatchesNamespace tells whether namespace matches time module.
func (m *Time) MatchesNamespace(namespace string, serverTarget bool) bool {
if !serverTarget {
return false
}
return namespace == timeNamespace
}

// ProcessIQ process a time iq.
func (m *Time) ProcessIQ(ctx context.Context, iq *stravaganza.IQ) error {
switch {
case iq.IsGet() && iq.ChildNamespace("time", timeNamespace) != nil:
m.reportServerTime(ctx, iq)
return nil
default:
_, _ = m.router.Route(ctx, xmpputil.MakeErrorStanza(iq, stanzaerror.BadRequest))
return nil
}
}

func (m *Time) reportServerTime(ctx context.Context, iq *stravaganza.IQ) {
tm := m.tmFn()

resIQ := xmpputil.MakeResultIQ(iq, stravaganza.NewBuilder("time").
WithAttribute(stravaganza.Namespace, timeNamespace).
WithChild(stravaganza.NewBuilder("tzo").WithText(tm.Format("-07:00")).Build()).
WithChild(stravaganza.NewBuilder("utc").WithText(tm.UTC().Format("2006-01-02T15:04:05Z")).Build()).
Build(),
)
_, _ = m.router.Route(ctx, resIQ)
}
72 changes: 72 additions & 0 deletions module/xep0202/time_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2021 The jackal Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package xep0202

import (
"context"
"testing"
"time"

"github.com/google/uuid"
"github.com/jackal-xmpp/stravaganza/v2"
"github.com/jackal-xmpp/stravaganza/v2/jid"
"github.com/stretchr/testify/require"
)

func TestTime_GetTime(t *testing.T) {
// given
routerMock := &routerMock{}

var respStanzas []stravaganza.Stanza
routerMock.RouteFunc = func(ctx context.Context, stanza stravaganza.Stanza) ([]jid.JID, error) {
respStanzas = append(respStanzas, stanza)
return nil, nil
}
m := &Time{
router: routerMock,
tmFn: func() time.Time {
return time.Date(1984, 01, 03, 00, 00, 00, 00, time.UTC)
},
}

// when
iq, _ := stravaganza.NewIQBuilder().
WithAttribute(stravaganza.ID, uuid.New().String()).
WithAttribute(stravaganza.Type, stravaganza.GetType).
WithAttribute(stravaganza.From, "ortuman@jackal.im/chamber").
WithAttribute(stravaganza.To, "jackal.im").
WithChild(
stravaganza.NewBuilder("time").
WithAttribute(stravaganza.Namespace, timeNamespace).
Build(),
).
BuildIQ()
_ = m.ProcessIQ(context.Background(), iq)

// then
require.Len(t, respStanzas, 1)

require.Equal(t, "iq", respStanzas[0].Name())
require.Equal(t, stravaganza.ResultType, respStanzas[0].Attribute(stravaganza.Type))

tm := respStanzas[0].ChildNamespace("time", timeNamespace)
require.NotNil(t, tm)

tzo := tm.Child("tzo")
utc := tm.Child("utc")

require.Equal(t, "+00:00", tzo.Text())
require.Equal(t, "1984-01-03T00:00:00Z", utc.Text())
}
2 changes: 1 addition & 1 deletion util/xmpp/xmpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func MakeDelayMessage(stanza stravaganza.Stanza, stamp time.Time, from, text str
stravaganza.NewBuilder("delay").
WithAttribute(stravaganza.Namespace, "urn:xmpp:delay").
WithAttribute(stravaganza.From, from).
WithAttribute("stamp", stamp.UTC().Format(time.RFC3339)).
WithAttribute("stamp", stamp.UTC().Format("2006-01-02T15:04:05Z")).
WithText(text).
Build(),
)
Expand Down

0 comments on commit 12b4a37

Please sign in to comment.