Compare commits
19 Commits
old_master
...
v0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ebd2b48d1 | ||
|
|
0ed74ed812 | ||
|
|
f9dd1acc45 | ||
|
|
21b5f59c1c | ||
|
|
073138bde4 | ||
|
|
fb19f8056a | ||
|
|
678d830f8c | ||
|
|
17fe947df9 | ||
|
|
0264e675a4 | ||
|
|
27f79905f4 | ||
|
|
0773c29359 | ||
|
|
c803682b8b | ||
|
|
9a9516e80d | ||
|
|
ea6e562928 | ||
|
|
420ed7d932 | ||
|
|
b690b54891 | ||
|
|
40c574faf7 | ||
|
|
31490b0922 | ||
|
|
d88f08479d |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -5,8 +5,8 @@
|
|||||||
*.tmp
|
*.tmp
|
||||||
**/*.bak
|
**/*.bak
|
||||||
**/*.tmp
|
**/*.tmp
|
||||||
data/config.json
|
/data/config.json
|
||||||
data/whitelist.json
|
/data/whitelist.json
|
||||||
data/blacklist.json
|
/data/blacklist.json
|
||||||
buny-jabber-bot
|
/buny-jabber-bot
|
||||||
buny-jabber-bot.exe
|
/buny-jabber-bot.exe
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
run:
|
run:
|
||||||
tests: false
|
tests: false
|
||||||
skip-dirs:
|
|
||||||
- data
|
|
||||||
- vendor
|
|
||||||
modules-download-mode: readonly
|
modules-download-mode: readonly
|
||||||
allow-parallel-runners: true
|
allow-parallel-runners: true
|
||||||
go: '1.21'
|
go: '1.24'
|
||||||
|
issues:
|
||||||
|
exclude-dirs:
|
||||||
|
- data
|
||||||
|
- vendor
|
||||||
output:
|
output:
|
||||||
sort-results: true
|
sort-results: true
|
||||||
linters-settings:
|
linters-settings:
|
||||||
@@ -34,13 +35,12 @@ linters:
|
|||||||
- errorlint
|
- errorlint
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exhaustruct
|
- exhaustruct
|
||||||
- exportloopref
|
- copyloopvar
|
||||||
- ginkgolinter
|
- ginkgolinter
|
||||||
- gci
|
|
||||||
- gocheckcompilerdirectives
|
- gocheckcompilerdirectives
|
||||||
- gocritic
|
- gocritic
|
||||||
- godot
|
- godot
|
||||||
- goerr113
|
- err113
|
||||||
- gofmt
|
- gofmt
|
||||||
- goimports
|
- goimports
|
||||||
- gosec
|
- gosec
|
||||||
|
|||||||
18
Design.txt
18
Design.txt
@@ -1,12 +1,5 @@
|
|||||||
Oсобенности реализации клиентской части.
|
Oсобенности реализации клиентской части.
|
||||||
|
|
||||||
Общие положения
|
|
||||||
------------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Nick для room-ов един и не может задаваться на per-conference основе. На этом завязаны отсылка ping-ов и определение
|
|
||||||
pong-ов, как минимум. Хотя формально джойниться можно с любым ником :) но что-то не хочется потом распутывать этот
|
|
||||||
клубок.
|
|
||||||
|
|
||||||
|
|
||||||
XEP-0410 MUC Self-Ping (Schrödinger's Chat)
|
XEP-0410 MUC Self-Ping (Schrödinger's Chat)
|
||||||
------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -67,3 +60,14 @@ Cуществует несколько видов пинга:
|
|||||||
------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------
|
||||||
Бот не читает историю, когда присоединяется к чатам, поэтому если его какое-то время не было в комнате и кто-то произнёс
|
Бот не читает историю, когда присоединяется к чатам, поэтому если его какое-то время не было в комнате и кто-то произнёс
|
||||||
непотребную фразу, ретроспективного бана не будет.
|
непотребную фразу, ретроспективного бана не будет.
|
||||||
|
|
||||||
|
|
||||||
|
Проблема реконнекта
|
||||||
|
------------------------------------------------------------------------------------------------------------------------
|
||||||
|
На текущий момент решена не совсем чисто. Скорее всего, при реконнекте утекают ресурсы, связанные с jabber-соединением.
|
||||||
|
Проблема в том, что приходится пересоздавать основной "объект" со всеми его полями.
|
||||||
|
|
||||||
|
Кроме того, sig handler фактичеки не завершается, потому что в него не приходит ни одного сигнала и формально остаётся
|
||||||
|
висеть на channel-е навечно.
|
||||||
|
|
||||||
|
А ещё есть пингеры каналов и пингеры сервера. Их бы тоже завершать явным образом.
|
||||||
|
|||||||
33
Makefile
33
Makefile
@@ -1,9 +1,9 @@
|
|||||||
#!/usr/bin/env gmake -f
|
#!/usr/bin/env gmake -f
|
||||||
|
|
||||||
BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -pgo=auto
|
BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath
|
||||||
FILELIST=collection.go types.go globals.go lib.go event_parser.go buny.go commands.go main.go norm_string.go bf.go
|
MYNAME=buny-jabber-bot
|
||||||
|
BINARY=$(MYNAME)
|
||||||
BINARY=buny-jabber-bot
|
TEST1=xml-software-version-query
|
||||||
|
|
||||||
# На windows имя бинарника может зависеть не только от платформы, но и от выбранной цели, для linux-а суффикс .exe
|
# На windows имя бинарника может зависеть не только от платформы, но и от выбранной цели, для linux-а суффикс .exe
|
||||||
# не нужен
|
# не нужен
|
||||||
@@ -33,21 +33,21 @@ ifeq ($(OS),Windows_NT)
|
|||||||
# вариант с powershell на windows
|
# вариант с powershell на windows
|
||||||
ifeq ($(SHELL),sh.exe)
|
ifeq ($(SHELL),sh.exe)
|
||||||
SET CGO_ENABLED=0
|
SET CGO_ENABLED=0
|
||||||
go build ${BUILDOPTS} -o ${BINARY} ${FILELIST}
|
go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME}
|
||||||
else
|
else
|
||||||
# вариант с jetbrains golang на windows
|
# вариант с jetbrains golang на windows
|
||||||
CGO_ENABLED=0
|
CGO_ENABLED=0
|
||||||
go build ${BUILDOPTS} -o ${BINARY} ${FILELIST}
|
go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME}
|
||||||
endif
|
endif
|
||||||
# вариант с bash/git (windows) и bash (linux)
|
# вариант с bash/git (windows) и bash (linux)
|
||||||
else
|
else
|
||||||
CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ${FILELIST}
|
CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME}
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
## Удаляем бинарник средствами go
|
## Удаляем бинарник средствами go
|
||||||
clean:
|
clean:
|
||||||
go clean
|
$(RM) ${BINARY}
|
||||||
|
|
||||||
|
|
||||||
## Служебный таргет, для целей разработки. Обновляет завендоренные либы, брутальным образом.
|
## Служебный таргет, для целей разработки. Обновляет завендоренные либы, брутальным образом.
|
||||||
@@ -68,4 +68,21 @@ endif
|
|||||||
go mod tidy
|
go mod tidy
|
||||||
go mod vendor
|
go mod vendor
|
||||||
|
|
||||||
|
test1:
|
||||||
|
# Ну и дальше просто билдим бинарник средствами гошки
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
# вариант с powershell на windows
|
||||||
|
ifeq ($(SHELL),sh.exe)
|
||||||
|
SET CGO_ENABLED=0
|
||||||
|
go build ${BUILDOPTS} -o ${TEST1} ./cmd/${TEST1}.exe
|
||||||
|
else
|
||||||
|
# вариант с jetbrains golang на windows
|
||||||
|
CGO_ENABLED=0
|
||||||
|
go build ${BUILDOPTS} -o ${TEST1} ./cmd/${TEST1}.exe
|
||||||
|
endif
|
||||||
|
# вариант с bash/git (windows) и bash (linux)
|
||||||
|
else
|
||||||
|
CGO_ENABLED=0 go build ${BUILDOPTS} -o ${TEST1} ./cmd/${TEST1}
|
||||||
|
endif
|
||||||
|
|
||||||
# vim: set ft=make noet ai ts=4 sw=4 sts=4:
|
# vim: set ft=make noet ai ts=4 sw=4 sts=4:
|
||||||
|
|||||||
35
README.md
35
README.md
@@ -1,20 +1,31 @@
|
|||||||
# Buny Jabber Bot
|
# Buny Jabber Bot
|
||||||
|
|
||||||
Простой бот, работающий по протоколу xmpp. Его задача - банить абьюзеров чата.
|
<table style="border-collapse: collapse; border: none;">
|
||||||
|
<tr style="border: none;">
|
||||||
Этот бот автоматизирует уборку человеческой грязи. Самый важный вопрос, который следует себе задать перед его
|
<td rowspan=2 style="border: none;"><img src="assets/avatar.png" alt="Бани" style="max-width: none; min-width:100%"></td>
|
||||||
использованием - а не забаню ли я кого-то лишнего?
|
<td style="border: none;">Простой бот, работающий по протоколу xmpp. Его задача - банить абьюзеров чата. При его
|
||||||
|
создании не стояло задачи сделать что-то красивое в архитектурном или каком-то другом плане.</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="border: none;">
|
||||||
|
<td style="border: none;">Этот бот автоматизирует уборку человеческой грязи. Самый важный вопрос, который следует себе
|
||||||
|
задать перед его использованием - а не забаню ли я кого-то лишнего?
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
## Что он может?
|
## Что он может?
|
||||||
|
|
||||||
Имеет возможность соединяться с сервером по протоколу xmpp как с шифрованием, так и без. (Пока что) Шифрование
|
* Имеет возможность соединяться с сервером по протоколу xmpp как с шифрованием, так и без. (Пока что) Шифрование
|
||||||
работает по классическим механикам (шифрованный канал связи, без возможности коммуникации в незашифрованном виде),
|
работает по классическим механикам (шифрованный канал связи, без возможности коммуникации в незашифрованном виде),
|
||||||
без поддержки механизма start tls. Что-то в гошке или на стороне сервера при выборе StartTLS не работает.
|
без поддержки механизма start tls. (Что-то в гошке или на стороне сервера при выборе StartTLS не работает.)
|
||||||
|
* Имеет возможность заносить пользователей в бан-лист, согласно заданным в чёрном списке правилам:
|
||||||
Имеет возможность заносить пользователей в бан-лист, согласно заданным в чёрном списке правилам: по совпадению с
|
- По совпадению с регулярными выражениями в nick-е или jid-е злодея
|
||||||
регулярными выражениями в nick-е или jid-е злодея, а также по регулярным выражениям характерных фраз. Правила можно
|
- По регулярным выражениям характерных фраз.
|
||||||
настроить как глобально, для всех комнат, где присутствует бот, так и для каждой комнаты отдельно. При изменении списка
|
- По названию и/или версии и, если есть, ос злодея.
|
||||||
правил бота не надо перезапускать, достаточно отдать ему команду rehash либо в приват, либо прям в чатике.
|
- Правила можно настроить как глобально, для всех комнат, где присутствует бот, так и для каждой комнаты отдельно.
|
||||||
|
- При изменении списка правил бота не надо перезапускать, достаточно отдать ему команду rehash либо в приват, либо
|
||||||
|
прям в чатике.
|
||||||
|
* Есть настройка заходить в разные комнаты под разными никами.
|
||||||
|
|
||||||
## Что он не может?
|
## Что он не может?
|
||||||
|
|
||||||
@@ -46,7 +57,7 @@ make
|
|||||||
|
|
||||||
Почему нельзя оставить комнату анонимной? А тут всё просто, в банлист заносится реальный jid пользователя, а не его ник.
|
Почему нельзя оставить комнату анонимной? А тут всё просто, в банлист заносится реальный jid пользователя, а не его ник.
|
||||||
Соответственно, бот должен видеть этот самый реальный jid. В анонимной комнате бот не сможет увидеть реальный jid.
|
Соответственно, бот должен видеть этот самый реальный jid. В анонимной комнате бот не сможет увидеть реальный jid.
|
||||||
А автокик - это конечно хорошо, но есть нюанс: можно, например, гадить ником, поэтому бот в автокик не умеет.
|
А автокик - это, конечно, хорошо, но есть нюанс: можно, например, гадить ником, поэтому бот в автокик не умеет.
|
||||||
|
|
||||||
## Как работает бан?
|
## Как работает бан?
|
||||||
|
|
||||||
|
|||||||
BIN
assets/avatar.png
Normal file
BIN
assets/avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
162
cmd/buny-jabber-bot/main.go
Normal file
162
cmd/buny-jabber-bot/main.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"buny-jabber-bot/internal/jabber"
|
||||||
|
|
||||||
|
"github.com/eleksir/go-xmpp"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"gopkg.in/tomb.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed version
|
||||||
|
var version string
|
||||||
|
|
||||||
|
// main - фактичеcки, начало и основное тело программы.
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for {
|
||||||
|
var j = jabber.Jabber{ //nolint:exhaustruct
|
||||||
|
SigChan: make(chan os.Signal, 1),
|
||||||
|
GTomb: tomb.Tomb{},
|
||||||
|
RoomsConnected: make([]string, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetFormatter(&log.TextFormatter{ //nolint:exhaustruct
|
||||||
|
DisableQuote: true,
|
||||||
|
DisableLevelTruncation: false,
|
||||||
|
DisableColors: true,
|
||||||
|
FullTimestamp: true,
|
||||||
|
TimestampFormat: "2006-01-02 15:04:05",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := j.ReadConfig(); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
j.C.Version = version
|
||||||
|
|
||||||
|
// no panic
|
||||||
|
switch j.C.Loglevel {
|
||||||
|
case "fatal":
|
||||||
|
log.SetLevel(log.FatalLevel)
|
||||||
|
case "error":
|
||||||
|
log.SetLevel(log.ErrorLevel)
|
||||||
|
case "warn":
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
case "info":
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
case "debug":
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
case "trace":
|
||||||
|
log.SetLevel(log.TraceLevel)
|
||||||
|
default:
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Откроем лог и скормим его логгеру.
|
||||||
|
if j.C.Log != "" {
|
||||||
|
logfile, err := os.OpenFile(j.C.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to open log file %s: %s", j.C.Log, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetOutput(logfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
j.C.ExeName, err = os.Executable()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("Unable to find my executable: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle pricky windows case. But honestly i do not expect it runs on windows.
|
||||||
|
if regexp.MustCompile(".[Ee][Xx][Ee]$").MatchString(j.C.ExeName) {
|
||||||
|
j.C.ExeName = j.C.ExeName[:len(j.C.ExeName)-4]
|
||||||
|
|
||||||
|
if j.C.ExeName == "" {
|
||||||
|
j.C.ExeName = "buny-jabber-bot"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Service %s v%s starting", j.C.ExeName, j.C.Version)
|
||||||
|
|
||||||
|
myLogLevel := log.GetLevel()
|
||||||
|
log.Warnf("Loglevel set to %v", myLogLevel)
|
||||||
|
|
||||||
|
verboseClient := false
|
||||||
|
|
||||||
|
if myLogLevel == log.TraceLevel {
|
||||||
|
verboseClient = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Самое время вгрузить белые и чёрные списки, если что - получим ошибку в лог.
|
||||||
|
if err := j.ReadWhitelist(); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := j.ReadBlacklist(); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// github.com/mattn/go-xmpp пишет в stdio, нам этого не надо, ловим выхлоп его в logrus с уровнем trace.
|
||||||
|
xmpp.DebugWriter = log.WithFields(log.Fields{"logger": "stdlib"}).WriterLevel(log.TraceLevel)
|
||||||
|
|
||||||
|
// Хэндлер сигналов.
|
||||||
|
j.GTomb.Go(func() error { return j.SigHandler() }) //nolint: gocritic
|
||||||
|
|
||||||
|
signal.Notify(j.SigChan, os.Interrupt)
|
||||||
|
|
||||||
|
j.Options = &xmpp.Options{ //nolint:exhaustruct
|
||||||
|
Host: fmt.Sprintf("%s:%d", j.C.Jabber.Server, j.C.Jabber.Port),
|
||||||
|
User: j.C.Jabber.User,
|
||||||
|
Password: j.C.Jabber.Password,
|
||||||
|
Resource: j.C.Jabber.Resource,
|
||||||
|
NoTLS: !j.C.Jabber.Ssl,
|
||||||
|
StartTLS: j.C.Jabber.StartTLS,
|
||||||
|
TLSConfig: &tls.Config{ //nolint:exhaustruct
|
||||||
|
ServerName: j.C.Jabber.Server,
|
||||||
|
InsecureSkipVerify: !j.C.Jabber.SslVerify, //nolint:gosec
|
||||||
|
},
|
||||||
|
InsecureAllowUnencryptedAuth: j.C.Jabber.InsecureAllowUnencryptedAuth,
|
||||||
|
Debug: verboseClient,
|
||||||
|
Session: false,
|
||||||
|
Status: "xa",
|
||||||
|
StatusMessage: jabber.RandomPhrase(j.C.Jabber.StartupStatus),
|
||||||
|
DialTimeout: time.Duration(j.C.Jabber.ConnectionTimeout) * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Враппер основной программы. Фактически на каждой серьёзной ошибке, например, сетевой, mainLoop и некоторые
|
||||||
|
// вспомогательные корутины прекращает свою работу. Здесь мы это ловим и запускаем их снова. Задача в том, чтобы
|
||||||
|
// запустить все асинхронные процессы заново, с чистого листа, но уже с распарсенным конфигом.
|
||||||
|
|
||||||
|
// Устанавливаем соединение и гребём события, посылаемые сервером - основной и вспомогательные циклы программы.
|
||||||
|
j.GTomb.Go(func() error { return j.MyLoop() }) //nolint: gocritic
|
||||||
|
|
||||||
|
// Ловим первый же kill и не дождаемся остальных, хотя формально надо бы.
|
||||||
|
<-j.GTomb.Dying()
|
||||||
|
|
||||||
|
// Если у нас wire error, то вызов .Close() повлечёт за собой ошибку. А если у нас не wire error, то по ходу мы
|
||||||
|
// получим утечку сокетов.
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(j.C.Jabber.ReconnectDelay) * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
1
cmd/buny-jabber-bot/version
Normal file
1
cmd/buny-jabber-bot/version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0.2
|
||||||
@@ -18,3 +18,6 @@ start_pre() {
|
|||||||
mkdir -p /run/$SVCNAME
|
mkdir -p /run/$SVCNAME
|
||||||
chown -R nobody:nogroup /run/$SVCNAME
|
chown -R nobody:nogroup /run/$SVCNAME
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# vim: set ft=sh noet ai ts=4 sw=4 sts=4:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"room_name": "",
|
"room_name": "",
|
||||||
|
|
||||||
# Записывать ли в reason в банлисте комнаты сообщение вида autoban at yyyy.MM.dd hh:mm:ss.
|
# Записывать ли в reason в банлисте комнаты сообщение вида autoban at yyyy.MM.dd hh:mm:ss.
|
||||||
# Если не задано, то false
|
# Если не задано, то false.
|
||||||
"reason_enable": false,
|
"reason_enable": false,
|
||||||
|
|
||||||
# Список регулярок JID-ов, которых надо банить.
|
# Список регулярок JID-ов, которых надо банить.
|
||||||
@@ -22,10 +22,18 @@
|
|||||||
"^Buzzing_Wasp$"
|
"^Buzzing_Wasp$"
|
||||||
],
|
],
|
||||||
|
|
||||||
# Список регулярок, запрещённых к упоминанию в чяте.
|
# Список регулярок, запрещённых к упоминанию в чяте.
|
||||||
"phrase_re": [
|
"phrase_re": [
|
||||||
"^Exterminate.$"
|
"^Exterminate.$"
|
||||||
]
|
],
|
||||||
|
|
||||||
|
"user_agent": [
|
||||||
|
{
|
||||||
|
"software": "BadUserAgent",
|
||||||
|
"version": "0.666.0",
|
||||||
|
"os": "DeathStarOS"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,10 @@
|
|||||||
# Название канала, обязательное поле
|
# Название канала, обязательное поле
|
||||||
"name" : "my_channel@conference.jabber.tld",
|
"name" : "my_channel@conference.jabber.tld",
|
||||||
|
|
||||||
|
# Под каким ником бот зайдёт конкретно на этот канал. Если не задано, будет использован ник, заданный в
|
||||||
|
# общих настройках Jabber, выше по конфигу.
|
||||||
|
"nick" : "sailormoon",
|
||||||
|
|
||||||
# Настройки байесовского классификатора. В виду вероятности ложного срабатывания возможно, имеет смысл
|
# Настройки байесовского классификатора. В виду вероятности ложного срабатывания возможно, имеет смысл
|
||||||
# воздержаться от использования. Зависит от использования и от словарей.
|
# воздержаться от использования. Зависит от использования и от словарей.
|
||||||
"bayes": {
|
"bayes": {
|
||||||
@@ -186,8 +190,9 @@
|
|||||||
# command sign символ-суффикс команды, например !rehash или !help. По-умолчанию "!".
|
# command sign символ-суффикс команды, например !rehash или !help. По-умолчанию "!".
|
||||||
"csign" : "!",
|
"csign" : "!",
|
||||||
|
|
||||||
# Многословность логов. Если не задано, то info. Debug - ДЕЙСТВИТЕЛЬНО вербозный уровень логгирования.
|
# Многословность логов. Если не задано, то info.
|
||||||
# Есть ещё trace :)
|
# Есть разные уровни trace, debug, debug, info, warn, error, fatal.
|
||||||
|
# Практически задействованы только trace, debug, info.
|
||||||
"loglevel": "debug",
|
"loglevel": "debug",
|
||||||
|
|
||||||
# Если не задан, логи идут в STDOUT
|
# Если не задан, логи идут в STDOUT
|
||||||
|
|||||||
90
globals.go
90
globals.go
@@ -1,90 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/eleksir/go-xmpp"
|
|
||||||
"github.com/jbrukh/bayesian"
|
|
||||||
"gopkg.in/tomb.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Bad bayesian.Class = "Bad"
|
|
||||||
Good bayesian.Class = "Good"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config - это у нас глобальная штука :) .
|
|
||||||
var config myConfig
|
|
||||||
|
|
||||||
// Списки тех, кого мы не баним никогда.
|
|
||||||
var whiteList myWhiteList
|
|
||||||
|
|
||||||
// Списки злодеев, которых мы баним.
|
|
||||||
var blackList myBlackList
|
|
||||||
|
|
||||||
// Ставится в true, если мы получили сигнал на выключение.
|
|
||||||
var shutdown = false
|
|
||||||
|
|
||||||
// Чтобы не организовывать драку за установку коннекта.
|
|
||||||
var connecting = false
|
|
||||||
|
|
||||||
// Глобальное состояние соединения.
|
|
||||||
var isConnected = false
|
|
||||||
|
|
||||||
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
|
|
||||||
var sigChan = make(chan os.Signal, 1)
|
|
||||||
|
|
||||||
// Основной инстанс xmpp-клиента.
|
|
||||||
var talk *xmpp.Client
|
|
||||||
|
|
||||||
// Опции подключения к xmpp-серверу.
|
|
||||||
var options *xmpp.Options
|
|
||||||
|
|
||||||
// Список комнат, в которых находится бот.
|
|
||||||
var roomsConnected []string
|
|
||||||
|
|
||||||
// Время последней активности, нужно для jabber:iq:last.
|
|
||||||
var lastActivity int64
|
|
||||||
|
|
||||||
// Время последней активности, нужно для c2s пингов - посылаем пинги, только если давненько ничего не приходило с
|
|
||||||
// сервера.
|
|
||||||
var lastServerActivity int64
|
|
||||||
|
|
||||||
// Время последней активности MUC-ов, нужно для пингов - посылаем пинги, только если давненько ничего не приходило из
|
|
||||||
// muc-ов.
|
|
||||||
var lastMucActivity *Collection
|
|
||||||
|
|
||||||
// Получен ли ответ на запрос disco#info к серверу.
|
|
||||||
var serverCapsQueried bool
|
|
||||||
|
|
||||||
// sync.Map-ка с капабилити сервера.
|
|
||||||
var serverCapsList *Collection
|
|
||||||
|
|
||||||
// sync.Map-ка с комнатами и их capability.
|
|
||||||
var mucCapsList *Collection
|
|
||||||
|
|
||||||
// Время, когда был отправлен c2s ping.
|
|
||||||
var serverPingTimestampTx int64
|
|
||||||
|
|
||||||
// Время, когда был принят s2c pong.
|
|
||||||
var serverPingTimestampRx int64
|
|
||||||
|
|
||||||
// Объектик для хранения стейта утилизатора горутинок.
|
|
||||||
var gTomb tomb.Tomb
|
|
||||||
|
|
||||||
// sync.Map-ка со списком участников конференций (в json-формате, согласно структуре xmpp.Presence, "room".[]json).
|
|
||||||
var roomPresences *Collection
|
|
||||||
|
|
||||||
// Переменные для простенького нормализатора текста.
|
|
||||||
var (
|
|
||||||
// Знаки препинания раз.
|
|
||||||
pMarks = []string{".", ",", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "{", "}", "<", ">", "[", "]", "\\"}
|
|
||||||
|
|
||||||
// Знаки препинания два-с.
|
|
||||||
pMarks2 = []string{"-", "_", "+", "=", ":", ";", "'", "`", "~", "\""}
|
|
||||||
|
|
||||||
// Символы новой строки.
|
|
||||||
newLines = []string{"\n", "\r", "\n\r", "\r\n"}
|
|
||||||
)
|
|
||||||
|
|
||||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
|
||||||
16
go.mod
16
go.mod
@@ -1,19 +1,21 @@
|
|||||||
module buny-jabber-bot
|
module buny-jabber-bot
|
||||||
|
|
||||||
go 1.21
|
go 1.24.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/eleksir/go-xmpp v1.6.0
|
github.com/eleksir/go-xmpp v1.6.0
|
||||||
github.com/hjson/hjson-go v3.3.0+incompatible
|
github.com/hjson/hjson-go v3.3.0+incompatible
|
||||||
github.com/jbrukh/bayesian v0.0.0-20200318221351-d726b684ca4a
|
github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/crypto v0.14.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
golang.org/x/sys v0.13.0 // indirect
|
golang.org/x/crypto v0.38.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/net v0.40.0 // indirect
|
||||||
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
|
golang.org/x/text v0.25.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
28
go.sum
28
go.sum
@@ -3,10 +3,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/eleksir/go-xmpp v1.6.0 h1:bkGr0YVczJWpkyk/IHhZb2ewv+NeHLvdNfds0TFgqao=
|
github.com/eleksir/go-xmpp v1.6.0 h1:bkGr0YVczJWpkyk/IHhZb2ewv+NeHLvdNfds0TFgqao=
|
||||||
github.com/eleksir/go-xmpp v1.6.0/go.mod h1:Ii6Wemk+yuGQOF+Glt0syjvtcPvdaBLstuNp/+rRJrg=
|
github.com/eleksir/go-xmpp v1.6.0/go.mod h1:Ii6Wemk+yuGQOF+Glt0syjvtcPvdaBLstuNp/+rRJrg=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hjson/hjson-go v3.3.0+incompatible h1:Rqr+Ya+0aCJMjaE4s8E9YKvuJLuLVpEvz4ONum52vnI=
|
github.com/hjson/hjson-go v3.3.0+incompatible h1:Rqr+Ya+0aCJMjaE4s8E9YKvuJLuLVpEvz4ONum52vnI=
|
||||||
github.com/hjson/hjson-go v3.3.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
github.com/hjson/hjson-go v3.3.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
||||||
github.com/jbrukh/bayesian v0.0.0-20200318221351-d726b684ca4a h1:gbdjhSslIoRRiSSLCP3kKuLmqAJGmhnPVhIyf6Dbw34=
|
github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a h1:zSc8tkEGo49/E6cQ9o00si0L9Q/Lu1Hsg8JWKCvzEDU=
|
||||||
github.com/jbrukh/bayesian v0.0.0-20200318221351-d726b684ca4a/go.mod h1:SELxwZQq/mPnfPCR2mchLmT4TQaPJvYtLcCtDWSM7vM=
|
github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a/go.mod h1:SELxwZQq/mPnfPCR2mchLmT4TQaPJvYtLcCtDWSM7vM=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
@@ -14,17 +16,19 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||||
|
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||||
|
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package jabber
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -12,13 +12,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
goodDictionaryFile string = "./data/good_dictionary.txt"
|
goodDictionaryFile string = "./data/good_dictionary.txt"
|
||||||
badDictionaryFile string = "./data/bad_dictionary.txt"
|
badDictionaryFile string = "./data/bad_dictionary.txt"
|
||||||
dataFile string = "./data/data.bin"
|
dataFile string = "./data/data.bin"
|
||||||
|
Bad bayesian.Class = "Bad"
|
||||||
|
Good bayesian.Class = "Good"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Выдаёт "очки похожести" для данной фразы.
|
// Выдаёт "очки похожести" для данной фразы.
|
||||||
func checkPhrase(s string) error {
|
func checkPhrase(s string) error { //nolint: unused
|
||||||
classifier, err := bayesian.NewClassifierFromFile(dataFile)
|
classifier, err := bayesian.NewClassifierFromFile(dataFile)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -35,7 +37,7 @@ func checkPhrase(s string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Выучивает слова из предопределённых словарей.
|
// Выучивает слова из предопределённых словарей.
|
||||||
func learn() error {
|
func learn() error { //nolint: unused
|
||||||
classifier := bayesian.NewClassifier(Bad, Good)
|
classifier := bayesian.NewClassifier(Bad, Good)
|
||||||
|
|
||||||
if err := feed(classifier, goodDictionaryFile, Good); err != nil {
|
if err := feed(classifier, goodDictionaryFile, Good); err != nil {
|
||||||
@@ -54,7 +56,7 @@ func learn() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Дополняет заданный класс фразами из указанного файла.
|
// Дополняет заданный класс фразами из указанного файла.
|
||||||
func feed(c *bayesian.Classifier, filename string, bc bayesian.Class) error {
|
func feed(c *bayesian.Classifier, filename string, bc bayesian.Class) error { //nolint:unused
|
||||||
fh, err := os.Open(filename)
|
fh, err := os.Open(filename)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -96,3 +98,5 @@ func feed(c *bayesian.Classifier, filename string, bc bayesian.Class) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
206
internal/jabber/black-whilte_lists.go
Normal file
206
internal/jabber/black-whilte_lists.go
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
package jabber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/hjson/hjson-go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadWhitelist читает и валидирует белые списки пользователей.
|
||||||
|
func (j *Jabber) ReadWhitelist() error {
|
||||||
|
var (
|
||||||
|
whitelistLoaded = false
|
||||||
|
err error
|
||||||
|
executablePath string
|
||||||
|
)
|
||||||
|
|
||||||
|
executablePath, err = os.Executable()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("unable to get current executable path: %w", err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistJSONPath := fmt.Sprintf("%s/data/whitelist.json", filepath.Dir(executablePath))
|
||||||
|
|
||||||
|
locations := []string{
|
||||||
|
"~/.buny-jabber-bot-whitelist.json",
|
||||||
|
"~/buny-jabber-bot-whitelist.json",
|
||||||
|
"/etc/buny-jabber-bot-whitelist.json",
|
||||||
|
whitelistJSONPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, location := range locations {
|
||||||
|
fileInfo, err := os.Stat(location)
|
||||||
|
|
||||||
|
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Файл белого списка длинноват для белого списка, попробуем следующего кандидата
|
||||||
|
if fileInfo.Size() > 2097152 {
|
||||||
|
log.Warnf("Whitelist file %s is too long for whitelist, skipping", location)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := os.ReadFile(location)
|
||||||
|
|
||||||
|
// Не удалось прочитать, попробуем следующего кандидата
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip reading whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||||
|
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||||
|
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
||||||
|
var (
|
||||||
|
sampleWhitelist MyWhiteList
|
||||||
|
tmp map[string]interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
|
|
||||||
|
// Не удалось распарсить - попробуем следующего кандидата
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpJSON, err := json.Marshal(tmp)
|
||||||
|
|
||||||
|
// Не удалось преобразовать map-ку в json
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(tmpJSON, &sampleWhitelist); err != nil {
|
||||||
|
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
j.WhiteList = sampleWhitelist
|
||||||
|
whitelistLoaded = true
|
||||||
|
|
||||||
|
log.Infof("Using %s as whiteList file", location)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !whitelistLoaded {
|
||||||
|
return errors.New("whitelist was not loaded") //nolint:goerr113
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadBlacklist читает и валидирует чёрные списки пользователей.
|
||||||
|
func (j *Jabber) ReadBlacklist() error {
|
||||||
|
var (
|
||||||
|
blacklistLoaded = false
|
||||||
|
err error
|
||||||
|
executablePath string
|
||||||
|
)
|
||||||
|
|
||||||
|
executablePath, err = os.Executable()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("unable to get current executable path: %w", err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistJSONPath := fmt.Sprintf("%s/data/blacklist.json", filepath.Dir(executablePath))
|
||||||
|
|
||||||
|
locations := []string{
|
||||||
|
"~/.bunyPresense-jabber-bot-blacklist.json",
|
||||||
|
"~/bunyPresense-jabber-bot-blacklist.json",
|
||||||
|
"/etc/bunyPresense-jabber-bot-blacklist.json",
|
||||||
|
whitelistJSONPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, location := range locations {
|
||||||
|
fileInfo, err := os.Stat(location)
|
||||||
|
|
||||||
|
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Файл чёрного списка длинноват для чёрного списка, попробуем следующего кандидата
|
||||||
|
if fileInfo.Size() > 16777216 {
|
||||||
|
log.Warnf("Blacklist file %s is too long for blacklist, skipping", location)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := os.ReadFile(location)
|
||||||
|
|
||||||
|
// Не удалось прочитать, попробуем следующего кандидата
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip reading blacklist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||||
|
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||||
|
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
||||||
|
var (
|
||||||
|
sampleBlacklist MyBlackList
|
||||||
|
tmp map[string]interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
|
|
||||||
|
// Не удалось распарсить - попробуем следующего кандидата
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing blacklist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpJSON, err := json.Marshal(tmp)
|
||||||
|
|
||||||
|
// Не удалось преобразовать map-ку в json
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing blacklist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(tmpJSON, &sampleBlacklist); err != nil {
|
||||||
|
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
j.BlackList = sampleBlacklist
|
||||||
|
blacklistLoaded = true
|
||||||
|
|
||||||
|
log.Infof("Using %s as blacklist file", location)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !blacklistLoaded {
|
||||||
|
return errors.New("blacklist was not loaded") //nolint:goerr113
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
package main
|
package jabber
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/eleksir/go-xmpp"
|
"github.com/eleksir/go-xmpp"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// bunyPresense производит проверку по бело-чёрным спискам. Если presence пришёл от злодея (из чёрного списка), то
|
// BunyPresense производит проверку по бело-чёрным спискам. Если presence пришёл от злодея (из чёрного списка), то
|
||||||
// отправляет его в бан.
|
// отправляет его в бан.
|
||||||
func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном
|
// Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном
|
||||||
// списке, заносим пидора в список outcast-ов
|
// списке, заносим пидора в список outcast-ов
|
||||||
if v.JID != "" {
|
if v.JID != "" {
|
||||||
// На всякий случай: себя никогда не баним, явным образом
|
// На всякий случай: себя никогда не баним, явным образом
|
||||||
if v.JID == talk.JID() {
|
if v.JID == j.Talk.JID() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
goodJid := strings.SplitN(v.JID, "/", 2)[0]
|
goodJid := strings.SplitN(v.JID, "/", 2)[0]
|
||||||
|
|
||||||
// Обрабатываем правила белого списка
|
// Обрабатываем правила белого списка
|
||||||
for _, good := range whiteList.Whitelist {
|
for _, good := range j.WhiteList.Whitelist {
|
||||||
// Глобальный белый список
|
// Глобальный белый список
|
||||||
if good.RoomName == "" {
|
if good.RoomName == "" {
|
||||||
for _, person := range good.Jid {
|
for _, person := range good.Jid {
|
||||||
@@ -55,6 +55,21 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
id, err := j.QuerySoftwareVersion(v.From)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"unable to query user software and version: jid=%s id=%s, err=%w",
|
||||||
|
v.JID,
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
evilJid := strings.SplitN(v.JID, "/", 2)[0]
|
evilJid := strings.SplitN(v.JID, "/", 2)[0]
|
||||||
evilNick := ""
|
evilNick := ""
|
||||||
evilNicks := strings.SplitN(v.From, "/", 2)
|
evilNicks := strings.SplitN(v.From, "/", 2)
|
||||||
@@ -65,9 +80,9 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Обрабатываем правила чёрного списка
|
// Обрабатываем правила чёрного списка
|
||||||
for _, cRoom := range roomsConnected {
|
for _, cRoom := range j.RoomsConnected {
|
||||||
if cRoom == room {
|
if cRoom == room {
|
||||||
for _, bEntry := range blackList.Blacklist {
|
for _, bEntry := range j.BlackList.Blacklist {
|
||||||
// Обработаем правила глобального чёрного списка
|
// Обработаем правила глобального чёрного списка
|
||||||
if bEntry.RoomName == "" {
|
if bEntry.RoomName == "" {
|
||||||
for _, jidRegexp := range bEntry.JidRe {
|
for _, jidRegexp := range bEntry.JidRe {
|
||||||
@@ -83,9 +98,9 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Checking jid %s vs global blacklist regex %s", evilJid, jidRegexp)
|
log.Debugf("Checking jid %s vs global blacklist regex %s", v.JID, jidRegexp)
|
||||||
|
|
||||||
if re.MatchString(evilJid) {
|
if re.MatchString(v.JID) {
|
||||||
log.Warnf(
|
log.Warnf(
|
||||||
"Hammer falls on %s (%s): jid matches with global blacklist entry: %s",
|
"Hammer falls on %s (%s): jid matches with global blacklist entry: %s",
|
||||||
v.From,
|
v.From,
|
||||||
@@ -93,7 +108,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
jidRegexp,
|
jidRegexp,
|
||||||
)
|
)
|
||||||
|
|
||||||
id, err := squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
@@ -102,7 +117,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -133,7 +148,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Баним именно jid
|
// Баним именно jid
|
||||||
id, err := squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
@@ -142,7 +157,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -167,9 +182,9 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Checking jid %s vs room %s blacklist regex %s", evilJid, room, jidRegexp)
|
log.Debugf("Checking jid %s vs room %s blacklist regex %s", v.JID, room, jidRegexp)
|
||||||
|
|
||||||
if re.MatchString(evilJid) {
|
if re.MatchString(v.JID) {
|
||||||
log.Warnf(
|
log.Warnf(
|
||||||
"Hammer falls on %s (%s): jid matches with room blacklist entry: %s",
|
"Hammer falls on %s (%s): jid matches with room blacklist entry: %s",
|
||||||
v.From,
|
v.From,
|
||||||
@@ -177,7 +192,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
jidRegexp,
|
jidRegexp,
|
||||||
)
|
)
|
||||||
|
|
||||||
id, err := squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
@@ -186,7 +201,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -218,7 +233,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Баним именно jid
|
// Баним именно jid
|
||||||
id, err := squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
@@ -227,7 +242,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -245,9 +260,9 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// bunyChat производит проверку сообщений участников чата по списку забаненных фраз и в случае нахождения запрещённого
|
// BunyChat производит проверку сообщений участников чата по списку забаненных фраз и в случае нахождения запрещённого
|
||||||
// шаблона банит участника чата.
|
// шаблона банит участника чата.
|
||||||
func bunyChat(v xmpp.Chat) error {
|
func (j *Jabber) BunyChat(v xmpp.Chat) error {
|
||||||
var (
|
var (
|
||||||
room = (strings.SplitN(v.Remote, "/", 2))[0]
|
room = (strings.SplitN(v.Remote, "/", 2))[0]
|
||||||
// nick = (strings.SplitN(v.Remote, "/", 2))[1]
|
// nick = (strings.SplitN(v.Remote, "/", 2))[1]
|
||||||
@@ -255,10 +270,10 @@ func bunyChat(v xmpp.Chat) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Действовать мы можем только в рамках тех комнат, где явно присуствуем.
|
// Действовать мы можем только в рамках тех комнат, где явно присуствуем.
|
||||||
for _, cRoom := range roomsConnected {
|
for _, cRoom := range j.RoomsConnected {
|
||||||
if cRoom == room {
|
if cRoom == room {
|
||||||
// Перебирём правила чёрных списков.
|
// Перебирём правила чёрных списков.
|
||||||
for _, bEntry := range blackList.Blacklist {
|
for _, bEntry := range j.BlackList.Blacklist {
|
||||||
// Обработаем правила глобального чёрного списка
|
// Обработаем правила глобального чёрного списка
|
||||||
if bEntry.RoomName == "" {
|
if bEntry.RoomName == "" {
|
||||||
for _, phraseRegexp := range bEntry.PhraseRe {
|
for _, phraseRegexp := range bEntry.PhraseRe {
|
||||||
@@ -277,7 +292,7 @@ func bunyChat(v xmpp.Chat) error {
|
|||||||
log.Debugf("Checking phrase %s vs room %s blacklist regex %s", v.Text, room, phraseRegexp)
|
log.Debugf("Checking phrase %s vs room %s blacklist regex %s", v.Text, room, phraseRegexp)
|
||||||
|
|
||||||
if re.MatchString(v.Text) {
|
if re.MatchString(v.Text) {
|
||||||
realJID := getRealJIDfromNick(v.Remote)
|
realJID := j.GetRealJIDfromNick(v.Remote)
|
||||||
|
|
||||||
log.Warnf(
|
log.Warnf(
|
||||||
"Hammer falls on %s (%s): phrase matches with global blacklist entry: %s vs %s",
|
"Hammer falls on %s (%s): phrase matches with global blacklist entry: %s vs %s",
|
||||||
@@ -287,14 +302,14 @@ func bunyChat(v xmpp.Chat) error {
|
|||||||
phraseRegexp,
|
phraseRegexp,
|
||||||
)
|
)
|
||||||
|
|
||||||
if id, err := squash(room, realJID, bEntry.ReasonEnable, v.Type); err != nil {
|
if id, err := j.Squash(room, realJID, bEntry.ReasonEnable, v.Type); err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
"unable to ban user: id=%s, err=%w",
|
"unable to ban user: id=%s, err=%w",
|
||||||
id,
|
id,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -320,7 +335,7 @@ func bunyChat(v xmpp.Chat) error {
|
|||||||
log.Debugf("Checking phrase %s vs room %s blacklist regex %s", v.Text, room, phraseRegexp)
|
log.Debugf("Checking phrase %s vs room %s blacklist regex %s", v.Text, room, phraseRegexp)
|
||||||
|
|
||||||
if re.MatchString(v.Text) {
|
if re.MatchString(v.Text) {
|
||||||
realJID := getRealJIDfromNick(v.Remote)
|
realJID := j.GetRealJIDfromNick(v.Remote)
|
||||||
|
|
||||||
log.Warnf(
|
log.Warnf(
|
||||||
"Hammer falls on %s (%s): phrase matches with room blacklist entry: %s vs %s",
|
"Hammer falls on %s (%s): phrase matches with room blacklist entry: %s vs %s",
|
||||||
@@ -330,14 +345,14 @@ func bunyChat(v xmpp.Chat) error {
|
|||||||
phraseRegexp,
|
phraseRegexp,
|
||||||
)
|
)
|
||||||
|
|
||||||
if id, err := squash(room, realJID, bEntry.ReasonEnable, v.Type); err != nil {
|
if id, err := j.Squash(room, realJID, bEntry.ReasonEnable, v.Type); err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
"unable to ban user: id=%s, err=%w",
|
"unable to ban user: id=%s, err=%w",
|
||||||
id,
|
id,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -347,7 +362,7 @@ func bunyChat(v xmpp.Chat) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Если включено, проверяем фразу на КАПС.
|
// Если включено, проверяем фразу на КАПС.
|
||||||
for _, channel := range config.Jabber.Channels {
|
for _, channel := range j.C.Jabber.Channels {
|
||||||
if channel.Name == room && channel.AllCaps.Enabled {
|
if channel.Name == room && channel.AllCaps.Enabled {
|
||||||
// Нормализуем строку и вырежем из неё пробелы
|
// Нормализуем строку и вырежем из неё пробелы
|
||||||
normPhrase := strings.ReplaceAll(nString(v.Text), " ", "")
|
normPhrase := strings.ReplaceAll(nString(v.Text), " ", "")
|
||||||
@@ -357,8 +372,8 @@ func bunyChat(v xmpp.Chat) error {
|
|||||||
normPhraseUpper := strings.ReplaceAll(nStringUpper(v.Text), " ", "")
|
normPhraseUpper := strings.ReplaceAll(nStringUpper(v.Text), " ", "")
|
||||||
|
|
||||||
if normPhrase == normPhraseUpper {
|
if normPhrase == normPhraseUpper {
|
||||||
realJID := getRealJIDfromNick(v.Remote)
|
realJID := j.GetRealJIDfromNick(v.Remote)
|
||||||
id, err := squash(room, realJID, false, v.Type)
|
id, err := j.Squash(room, realJID, false, v.Type)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
@@ -367,7 +382,7 @@ func bunyChat(v xmpp.Chat) error {
|
|||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,74 +396,208 @@ func bunyChat(v xmpp.Chat) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// squash банит указанный jid в указанной комнате.
|
// BunySoftwareVersion производит проверку по чёрному списку версий и названий клинтского ПО.
|
||||||
// reasonEnable указывает, надо ли писать дату автобана в банлисте в поле reason (это единственная причина, в которую
|
func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) error {
|
||||||
// умеет бот).
|
var err error
|
||||||
func squash(room, jid string, reasonEnable bool, vType string) (string, error) {
|
|
||||||
var (
|
|
||||||
id string
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if config.Jabber.BanPhrasesEnable {
|
if v.From != "" {
|
||||||
phrase := randomPhrase(config.Jabber.BanPhrases)
|
// На всякий случай: себя никогда не баним, явным образом
|
||||||
|
if v.From == j.Talk.JID() {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if _, err = talk.Send(
|
room := strings.SplitN(v.From, "/", 2)[0]
|
||||||
xmpp.Chat{ //nolint:exhaustruct
|
|
||||||
Remote: room,
|
|
||||||
Text: phrase,
|
|
||||||
Type: vType,
|
|
||||||
},
|
|
||||||
); err != nil {
|
|
||||||
err = fmt.Errorf("unable to send phrase to room %s: %w", room, err)
|
|
||||||
|
|
||||||
// Здесь возвращаем nil, т.к. за нас ошибку залоггирует код выше
|
var (
|
||||||
return id, err
|
presenceJSONStrings []string
|
||||||
|
)
|
||||||
|
|
||||||
|
if presenceJSONInterface, present := j.RoomPresences.Get(room); present {
|
||||||
|
presenceJSONStrings = InterfaceToStringSlice(presenceJSONInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Looking up %s in presence db", v.From)
|
||||||
|
|
||||||
|
for _, presenceJSONString := range presenceJSONStrings {
|
||||||
|
var p xmpp.Presence
|
||||||
|
_ = json.Unmarshal([]byte(presenceJSONString), &p)
|
||||||
|
|
||||||
|
if p.Affiliation == "admin" || p.Affiliation == "owner" {
|
||||||
|
log.Infof("Skipping useragent blacklist check for %s because his affiliation is %s", v.From, p.Affiliation)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// На всякий случай.
|
||||||
|
if p.Role == "moderator" {
|
||||||
|
log.Infof("Skipping useragent blacklist check for %s because his role is %s", v.From, p.Role)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.From == p.From {
|
||||||
|
log.Errorf("Found jid of %s in presence db: %s", v.From, p.JID)
|
||||||
|
|
||||||
|
// Lookup in global blacklist.
|
||||||
|
for _, cRoom := range j.RoomsConnected {
|
||||||
|
if cRoom == room {
|
||||||
|
for _, bEntry := range j.BlackList.Blacklist {
|
||||||
|
// Обработаем правила глобального чёрного списка
|
||||||
|
if bEntry.RoomName == "" {
|
||||||
|
for _, useragent := range bEntry.UserAgent {
|
||||||
|
switch {
|
||||||
|
case useragent.Name != "" && useragent.Version != "" && useragent.Os != "":
|
||||||
|
if ver.Name == useragent.Name && ver.Version == useragent.Version && ver.Os == useragent.Os {
|
||||||
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"unable to ban user: id=%s, err=%w",
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
j.GTomb.Kill(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case useragent.Name != "" && useragent.Version != "":
|
||||||
|
if ver.Name == useragent.Name && ver.Version == useragent.Version {
|
||||||
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"unable to ban user: id=%s, err=%w",
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
j.GTomb.Kill(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case useragent.Name != "":
|
||||||
|
if ver.Name == useragent.Name {
|
||||||
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"unable to ban user: id=%s, err=%w",
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
j.GTomb.Kill(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case useragent.Version != "":
|
||||||
|
if ver.Version == useragent.Version {
|
||||||
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"unable to ban user: id=%s, err=%w",
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
j.GTomb.Kill(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bEntry.RoomName == room {
|
||||||
|
for _, useragent := range bEntry.UserAgent {
|
||||||
|
switch {
|
||||||
|
case useragent.Name != "" && useragent.Version != "" && useragent.Os != "":
|
||||||
|
if ver.Name == useragent.Name && ver.Version == useragent.Version && ver.Os == useragent.Os {
|
||||||
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"unable to ban user: id=%s, err=%w",
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
j.GTomb.Kill(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case useragent.Name != "" && useragent.Version != "":
|
||||||
|
if ver.Name == useragent.Name && ver.Version == useragent.Version {
|
||||||
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"unable to ban user: id=%s, err=%w",
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
j.GTomb.Kill(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case useragent.Name != "":
|
||||||
|
if ver.Name == useragent.Name {
|
||||||
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"unable to ban user: id=%s, err=%w",
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
j.GTomb.Kill(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case useragent.Version != "":
|
||||||
|
if ver.Version == useragent.Version {
|
||||||
|
id, err := j.Squash(room, p.JID, bEntry.ReasonEnable, v.Type)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"unable to ban user: id=%s, err=%w",
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
j.GTomb.Kill(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://xmpp.org/extensions/xep-0045.html#ban баним вот таким сообщением
|
return err
|
||||||
ban := "<item affiliation='outcast' jid='" + jid + "'>"
|
|
||||||
|
|
||||||
if reasonEnable {
|
|
||||||
var t = time.Now()
|
|
||||||
ban += fmt.Sprintf(
|
|
||||||
"<reason>autoban at %04d.%02d.%02d %02d:%02d:%02d</reason>",
|
|
||||||
t.Year(),
|
|
||||||
t.Month(),
|
|
||||||
t.Day(),
|
|
||||||
t.Hour(),
|
|
||||||
t.Minute(),
|
|
||||||
t.Second(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ban += "<reason />"
|
|
||||||
}
|
|
||||||
|
|
||||||
ban += "</item>"
|
|
||||||
|
|
||||||
// Выжидаем некоторое время перед баном. А то можно настолько рано забанить, что сервер не внесёт злодея в банлист
|
|
||||||
// комнаты и пришлёт affiliation: none вместо affiliation: outcast.
|
|
||||||
if config.Jabber.BanDelay > 0 {
|
|
||||||
time.Sleep(time.Duration(config.Jabber.BanDelay) * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
if id, err = talk.RawInformationQuery(
|
|
||||||
talk.JID(),
|
|
||||||
room,
|
|
||||||
"ban1",
|
|
||||||
xmpp.IQTypeSet,
|
|
||||||
"http://jabber.org/protocol/muc#admin",
|
|
||||||
ban,
|
|
||||||
); err != nil {
|
|
||||||
err = fmt.Errorf(
|
|
||||||
"unable to ban user: id=%s, err=%w",
|
|
||||||
id,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return id, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package jabber
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package jabber
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -10,28 +10,30 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmd(v xmpp.Chat) error {
|
// Cmd парсит команды из чятика.
|
||||||
|
func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case v.Text == fmt.Sprintf("%shelp", config.CSign) || v.Text == fmt.Sprintf("%sпомощь", config.CSign):
|
case v.Text == fmt.Sprintf("%shelp", j.C.CSign) || v.Text == fmt.Sprintf("%sпомощь", j.C.CSign):
|
||||||
var (
|
var (
|
||||||
answer string
|
answer string
|
||||||
chosenOneTalks = false
|
chosenOneTalks = false
|
||||||
)
|
)
|
||||||
|
|
||||||
realJID := getRealJIDfromNick(v.Remote)
|
realJID := j.GetRealJIDfromNick(v.Remote)
|
||||||
|
|
||||||
for _, master := range config.Jabber.BotMasters {
|
for _, master := range j.C.Jabber.BotMasters {
|
||||||
if (strings.SplitN(realJID, "/", 2))[0] == master {
|
if (strings.SplitN(realJID, "/", 2))[0] == master {
|
||||||
chosenOneTalks = true
|
chosenOneTalks = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if chosenOneTalks {
|
if chosenOneTalks {
|
||||||
answer = fmt.Sprintf("%sпомощь - этот список команд\n", config.CSign)
|
answer = fmt.Sprintf("%sпомощь - этот список команд\n", j.C.CSign)
|
||||||
answer += fmt.Sprintf("%shelp - this commands list\n", config.CSign)
|
answer += fmt.Sprintf("%shelp - this commands list\n", j.C.CSign)
|
||||||
answer += fmt.Sprintf("%srehash - reload white and black lists", config.CSign)
|
answer += fmt.Sprintf("%srehash - reload white and black lists (available to bot admins only)\n", j.C.CSign)
|
||||||
|
answer += fmt.Sprintf("%sver|%sversion - prints version of software", j.C.CSign, j.C.CSign)
|
||||||
} else {
|
} else {
|
||||||
answer = "Ничем помочь не могу. Луна не светит на тебя."
|
answer = "Ничем помочь не могу. Луна не светит на тебя."
|
||||||
}
|
}
|
||||||
@@ -42,7 +44,7 @@ func cmd(v xmpp.Chat) error {
|
|||||||
dest = (strings.SplitN(v.Remote, "/", 2))[0]
|
dest = (strings.SplitN(v.Remote, "/", 2))[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := talk.Send(
|
if _, err := j.Talk.Send(
|
||||||
xmpp.Chat{ //nolint:exhaustruct
|
xmpp.Chat{ //nolint:exhaustruct
|
||||||
Remote: dest,
|
Remote: dest,
|
||||||
Text: strings.TrimSpace(answer),
|
Text: strings.TrimSpace(answer),
|
||||||
@@ -54,7 +56,7 @@ func cmd(v xmpp.Chat) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case v.Text == fmt.Sprintf("%srehash", config.CSign): //nolint:wsl
|
case v.Text == fmt.Sprintf("%srehash", j.C.CSign): //nolint:wsl
|
||||||
/* Эта команда может прилетать из приватной беседы с realjid-ом, из приватной беседы с chat-nick-ом, а также из
|
/* Эта команда может прилетать из приватной беседы с realjid-ом, из приватной беседы с chat-nick-ом, а также из
|
||||||
* чятика.
|
* чятика.
|
||||||
* Groupchat мы можем определить по v.Type (groupchat) и тогда понятно что делать.
|
* Groupchat мы можем определить по v.Type (groupchat) и тогда понятно что делать.
|
||||||
@@ -71,9 +73,9 @@ func cmd(v xmpp.Chat) error {
|
|||||||
listsLoaded = true
|
listsLoaded = true
|
||||||
)
|
)
|
||||||
|
|
||||||
realJID := getRealJIDfromNick(v.Remote)
|
realJID := j.GetRealJIDfromNick(v.Remote)
|
||||||
|
|
||||||
for _, master := range config.Jabber.BotMasters {
|
for _, master := range j.C.Jabber.BotMasters {
|
||||||
if (strings.SplitN(realJID, "/", 2))[0] == master {
|
if (strings.SplitN(realJID, "/", 2))[0] == master {
|
||||||
chosenOneTalks = true
|
chosenOneTalks = true
|
||||||
}
|
}
|
||||||
@@ -82,15 +84,22 @@ func cmd(v xmpp.Chat) error {
|
|||||||
if !chosenOneTalks {
|
if !chosenOneTalks {
|
||||||
log.Infof(
|
log.Infof(
|
||||||
"Command %srehash given by non-bot_master user %s(%s), ignoring",
|
"Command %srehash given by non-bot_master user %s(%s), ignoring",
|
||||||
config.CSign,
|
j.C.CSign,
|
||||||
realJID,
|
realJID,
|
||||||
v.Remote,
|
v.Remote,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var msg xmpp.Chat
|
||||||
|
msg.Remote = v.Remote
|
||||||
|
msg.Type = v.Type
|
||||||
|
msg.Text = "Ничем помочь не могу. Луна не светит на тебя."
|
||||||
|
|
||||||
|
_, err := j.Talk.Send(msg)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := readWhitelist(); err != nil {
|
if err := j.ReadWhitelist(); err != nil {
|
||||||
listsLoaded = false
|
listsLoaded = false
|
||||||
|
|
||||||
var msg xmpp.Chat
|
var msg xmpp.Chat
|
||||||
@@ -98,12 +107,12 @@ func cmd(v xmpp.Chat) error {
|
|||||||
msg.Type = "chat" // private message
|
msg.Type = "chat" // private message
|
||||||
msg.Text = fmt.Sprint(err)
|
msg.Text = fmt.Sprint(err)
|
||||||
|
|
||||||
if _, err := talk.Send(msg); err != nil {
|
if _, err := j.Talk.Send(msg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := readBlacklist(); err != nil {
|
if err := j.ReadBlacklist(); err != nil {
|
||||||
listsLoaded = false
|
listsLoaded = false
|
||||||
|
|
||||||
var msg xmpp.Chat
|
var msg xmpp.Chat
|
||||||
@@ -111,7 +120,7 @@ func cmd(v xmpp.Chat) error {
|
|||||||
msg.Type = "chat" // private message
|
msg.Type = "chat" // private message
|
||||||
msg.Text = fmt.Sprint(err)
|
msg.Text = fmt.Sprint(err)
|
||||||
|
|
||||||
if _, err := talk.Send(msg); err != nil {
|
if _, err := j.Talk.Send(msg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,7 +133,7 @@ func cmd(v xmpp.Chat) error {
|
|||||||
msg.Type = v.Type
|
msg.Type = v.Type
|
||||||
msg.Text = "Сделано"
|
msg.Text = "Сделано"
|
||||||
|
|
||||||
if _, err := talk.Send(msg); err != nil {
|
if _, err := j.Talk.Send(msg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,16 +141,16 @@ func cmd(v xmpp.Chat) error {
|
|||||||
room := (strings.SplitN(v.Remote, "/", 2))[0]
|
room := (strings.SplitN(v.Remote, "/", 2))[0]
|
||||||
|
|
||||||
// Реагировать на приватную команду "из комнаты", только если бот в комнате
|
// Реагировать на приватную команду "из комнаты", только если бот в комнате
|
||||||
if slices.Contains(roomsConnected, room) {
|
if slices.Contains(j.RoomsConnected, room) {
|
||||||
var (
|
var (
|
||||||
chosenOneTalks = false
|
chosenOneTalks = false
|
||||||
listsLoaded = true
|
listsLoaded = true
|
||||||
)
|
)
|
||||||
|
|
||||||
// Реагировать на rehash только если собеседник является bot master-ом
|
// Реагировать на rehash только если собеседник является bot master-ом
|
||||||
realJID := getRealJIDfromNick(v.Remote)
|
realJID := j.GetRealJIDfromNick(v.Remote)
|
||||||
|
|
||||||
for _, master := range config.Jabber.BotMasters {
|
for _, master := range j.C.Jabber.BotMasters {
|
||||||
if (strings.SplitN(realJID, "/", 2))[0] == master {
|
if (strings.SplitN(realJID, "/", 2))[0] == master {
|
||||||
chosenOneTalks = true
|
chosenOneTalks = true
|
||||||
}
|
}
|
||||||
@@ -150,15 +159,22 @@ func cmd(v xmpp.Chat) error {
|
|||||||
if !chosenOneTalks {
|
if !chosenOneTalks {
|
||||||
log.Infof(
|
log.Infof(
|
||||||
"Command %srehash given by non-bot_master user %s(%s), ignoring",
|
"Command %srehash given by non-bot_master user %s(%s), ignoring",
|
||||||
config.CSign,
|
j.C.CSign,
|
||||||
realJID,
|
realJID,
|
||||||
v.Remote,
|
v.Remote,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var msg xmpp.Chat
|
||||||
|
msg.Remote = v.Remote
|
||||||
|
msg.Type = v.Type
|
||||||
|
msg.Text = "Ничем помочь не могу. Луна не светит на тебя."
|
||||||
|
|
||||||
|
_, err := j.Talk.Send(msg)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := readWhitelist(); err != nil {
|
if err := j.ReadWhitelist(); err != nil {
|
||||||
listsLoaded = false
|
listsLoaded = false
|
||||||
|
|
||||||
var msg xmpp.Chat
|
var msg xmpp.Chat
|
||||||
@@ -166,12 +182,12 @@ func cmd(v xmpp.Chat) error {
|
|||||||
msg.Type = v.Type
|
msg.Type = v.Type
|
||||||
msg.Text = fmt.Sprint(err)
|
msg.Text = fmt.Sprint(err)
|
||||||
|
|
||||||
if _, err := talk.Send(msg); err != nil {
|
if _, err := j.Talk.Send(msg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := readBlacklist(); err != nil {
|
if err := j.ReadBlacklist(); err != nil {
|
||||||
listsLoaded = false
|
listsLoaded = false
|
||||||
|
|
||||||
var msg xmpp.Chat
|
var msg xmpp.Chat
|
||||||
@@ -179,7 +195,7 @@ func cmd(v xmpp.Chat) error {
|
|||||||
msg.Type = v.Type
|
msg.Type = v.Type
|
||||||
msg.Text = fmt.Sprint(err)
|
msg.Text = fmt.Sprint(err)
|
||||||
|
|
||||||
if _, err := talk.Send(msg); err != nil {
|
if _, err := j.Talk.Send(msg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,7 +206,7 @@ func cmd(v xmpp.Chat) error {
|
|||||||
msg.Type = v.Type
|
msg.Type = v.Type
|
||||||
msg.Text = "Сделано"
|
msg.Text = "Сделано"
|
||||||
|
|
||||||
if _, err := talk.Send(msg); err != nil {
|
if _, err := j.Talk.Send(msg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,9 +217,34 @@ func cmd(v xmpp.Chat) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
case v.Text == fmt.Sprintf("%sver", j.C.CSign) || v.Text == fmt.Sprintf("%sversion", j.C.CSign):
|
||||||
|
var (
|
||||||
|
answer = fmt.Sprintf("Version %s", j.C.Version)
|
||||||
|
)
|
||||||
|
|
||||||
|
dest := v.Remote
|
||||||
|
|
||||||
|
if v.Type == "groupchat" {
|
||||||
|
dest = (strings.SplitN(v.Remote, "/", 2))[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := j.Talk.Send(
|
||||||
|
xmpp.Chat{ //nolint:exhaustruct
|
||||||
|
Remote: dest,
|
||||||
|
Text: strings.TrimSpace(answer),
|
||||||
|
Type: v.Type,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
err = fmt.Errorf("unable to send message to %s: %w", v.Remote, err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package jabber
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -15,9 +15,9 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// parseEvent парсит ивенты, прилетающие из модуля xmpp (изменения presence, фразы участников, ошибки).
|
// ParseEvent парсит ивенты, прилетающие из модуля xmpp (изменения presence, фразы участников, ошибки).
|
||||||
func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||||
lastServerActivity = time.Now().Unix()
|
j.LastServerActivity = time.Now().Unix()
|
||||||
|
|
||||||
switch v := e.(type) {
|
switch v := e.(type) {
|
||||||
// Сообщение в чяти
|
// Сообщение в чяти
|
||||||
@@ -33,32 +33,35 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
// Групповой чятик
|
// Групповой чятик
|
||||||
case "groupchat":
|
case "groupchat":
|
||||||
log.Debugf("Message from public chat: %s", v.Text)
|
var (
|
||||||
// room := strings.SplitN(v.Remote, "/", 2)[0]
|
room = strings.SplitN(v.Remote, "/", 2)[0]
|
||||||
nick := strings.SplitN(v.Remote, "/", 2)[1]
|
nick = strings.SplitN(v.Remote, "/", 2)[1]
|
||||||
|
)
|
||||||
|
|
||||||
if nick == config.Jabber.Nick {
|
log.Debugf("Message from public chat: %s", v.Text)
|
||||||
|
|
||||||
|
if nick == j.GetBotNickFromRoomConfig(room) {
|
||||||
log.Debug("Skipping message from myself")
|
log.Debug("Skipping message from myself")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cmd(v); err != nil {
|
if err := j.Cmd(v); err != nil {
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bunyChat(v); err != nil {
|
if err := j.BunyChat(v); err != nil {
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lastActivity = lastServerActivity
|
j.LastActivity = j.LastServerActivity
|
||||||
|
|
||||||
if muc, _ := strings.CutSuffix(v.Remote, "/"); muc != "" {
|
if muc, _ := strings.CutSuffix(v.Remote, "/"); muc != "" {
|
||||||
lastMucActivity.Set(muc, lastServerActivity)
|
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Приватный чятик
|
// Приватный чятик
|
||||||
@@ -68,13 +71,13 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
// eleksir@jabber.ru/array.lan - это если мы работаем через ростер
|
// eleksir@jabber.ru/array.lan - это если мы работаем через ростер
|
||||||
log.Debugf("Private message: %s", v.Text)
|
log.Debugf("Private message: %s", v.Text)
|
||||||
|
|
||||||
if err := cmd(v); err != nil {
|
if err := j.Cmd(v); err != nil {
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lastActivity = lastServerActivity
|
j.LastActivity = j.LastServerActivity
|
||||||
|
|
||||||
// Внезапно, ошибки. По идее они должны ассоциироваться с отправляемыми сообщениями, но по ходу это
|
// Внезапно, ошибки. По идее они должны ассоциироваться с отправляемыми сообщениями, но по ходу это
|
||||||
// не реализовано, поэтому мы получаем ошибки в форме отдельного чятика
|
// не реализовано, поэтому мы получаем ошибки в форме отдельного чятика
|
||||||
@@ -99,13 +102,13 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
log.Debug("Looks like IQ get query")
|
log.Debug("Looks like IQ get query")
|
||||||
|
|
||||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
if slices.Contains(roomsConnected, muc) {
|
if slices.Contains(j.RoomsConnected, muc) {
|
||||||
lastMucActivity.Set(muc, lastServerActivity)
|
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
iqStruct jabberSimpleIqGetQuery
|
iqStruct SimpleIqGetQuery
|
||||||
parseError bool
|
parseError bool
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -116,10 +119,10 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
case "jabber:iq:version":
|
case "jabber:iq:version":
|
||||||
log.Infof("Got IQ get request for version from %s", v.From)
|
log.Infof("Got IQ get request for version from %s", v.From)
|
||||||
|
|
||||||
if id, err := talk.IqVersionResponse(
|
if id, err := j.Talk.IqVersionResponse(
|
||||||
v,
|
v,
|
||||||
"buny-jabber-bot",
|
j.C.ExeName,
|
||||||
"1.dev",
|
j.C.Version,
|
||||||
(strings.SplitN(runtime.GOOS, "/", 2))[0],
|
(strings.SplitN(runtime.GOOS, "/", 2))[0],
|
||||||
); err != nil {
|
); err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
@@ -128,7 +131,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -139,14 +142,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
case "urn:xmpp:ping":
|
case "urn:xmpp:ping":
|
||||||
log.Infof("Got IQ get request for pong from %s", v.From)
|
log.Infof("Got IQ get request for pong from %s", v.From)
|
||||||
|
|
||||||
if id, err := talk.PingResponse(v); err != nil {
|
if id, err := j.Talk.PingResponse(v); err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
"unable to send pong to jabber server: id=%s, err=%w",
|
"unable to send pong to jabber server: id=%s, err=%w",
|
||||||
id,
|
id,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -158,14 +161,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
case "jabber:iq:last":
|
case "jabber:iq:last":
|
||||||
log.Infof("Got IQ get last activity time request from %s", v.From)
|
log.Infof("Got IQ get last activity time request from %s", v.From)
|
||||||
|
|
||||||
if id, err := talk.JabberIqLastResponse(v, lastActivity); err != nil {
|
if id, err := j.Talk.JabberIqLastResponse(v, j.LastActivity); err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
"unable to send last activity time to jabber server: id=%s, err=%w",
|
"unable to send last activity time to jabber server: id=%s, err=%w",
|
||||||
id,
|
id,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -181,10 +184,12 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
answer += "<feature var=\"urn:xmpp:time\" />"
|
answer += "<feature var=\"urn:xmpp:time\" />"
|
||||||
answer += "<feature var=\"urn:xmpp:ping\" />"
|
answer += "<feature var=\"urn:xmpp:ping\" />"
|
||||||
answer += "<feature var=\"jabber:iq:last\" />"
|
answer += "<feature var=\"jabber:iq:last\" />"
|
||||||
|
answer += "<feature var=\"http://jabber.org/protocol/caps\" />"
|
||||||
answer += "<feature var=\"http://jabber.org/protocol/disco#info\" />"
|
answer += "<feature var=\"http://jabber.org/protocol/disco#info\" />"
|
||||||
|
answer += "<feature var=\"http://jabber.org/protocol/muc\" />"
|
||||||
answer += "</query>"
|
answer += "</query>"
|
||||||
|
|
||||||
if id, err := talk.RawInformation(
|
if id, err := j.Talk.RawInformation(
|
||||||
v.To,
|
v.To,
|
||||||
v.From,
|
v.From,
|
||||||
v.ID,
|
v.ID,
|
||||||
@@ -197,7 +202,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -208,7 +213,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
case xmpp.XMPPNS_DISCO_ITEMS:
|
case xmpp.XMPPNS_DISCO_ITEMS:
|
||||||
log.Infof("Got IQ get disco#items request from %s, answer service unavailable", v.From)
|
log.Infof("Got IQ get disco#items request from %s, answer service unavailable", v.From)
|
||||||
|
|
||||||
if _, err := talk.ErrorServiceUnavailable(
|
if _, err := j.Talk.ErrorServiceUnavailable(
|
||||||
v,
|
v,
|
||||||
"http://jabber.org/protocol/disco#info",
|
"http://jabber.org/protocol/disco#info",
|
||||||
"http://jabber.org/protocol/commands",
|
"http://jabber.org/protocol/commands",
|
||||||
@@ -219,7 +224,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -239,21 +244,21 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
// Попробуем распарсить входящий запрос как urn:xmpp:time
|
// Попробуем распарсить входящий запрос как urn:xmpp:time
|
||||||
if parseError {
|
if parseError {
|
||||||
var iqStruct jabberTimeIqGetQuery
|
var iqStruct TimeIqGetQuery
|
||||||
|
|
||||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||||
switch iqStruct.Xmlns {
|
switch iqStruct.Xmlns {
|
||||||
case "urn:xmpp:time":
|
case "urn:xmpp:time":
|
||||||
log.Infof("Got IQ get time request from %s", v.From)
|
log.Infof("Got IQ get time request from %s", v.From)
|
||||||
|
|
||||||
if id, err := talk.UrnXMPPTimeResponse(v, "+00:00"); err != nil {
|
if id, err := j.Talk.UrnXMPPTimeResponse(v, "+00:00"); err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
"unable to send urn:xmpp:time to jabber server: id=%s, err=%w",
|
"unable to send urn:xmpp:time to jabber server: id=%s, err=%w",
|
||||||
id,
|
id,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -273,14 +278,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
// Попробуем распарсить входящий запрос как http://jabber.org/protocol/pubsub
|
// Попробуем распарсить входящий запрос как http://jabber.org/protocol/pubsub
|
||||||
if parseError {
|
if parseError {
|
||||||
var iqStruct jabberPubsubIQGetQuery
|
var iqStruct PubsubIQGetQuery
|
||||||
|
|
||||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||||
switch iqStruct.Xmlns {
|
switch iqStruct.Xmlns {
|
||||||
case xmpp.XMPPNS_PUBSUB:
|
case xmpp.XMPPNS_PUBSUB:
|
||||||
log.Infof("Got IQ get pubsub request from %s, answer feature unimplemented", v.From)
|
log.Infof("Got IQ get pubsub request from %s, answer feature unimplemented", v.From)
|
||||||
|
|
||||||
if id, err := talk.ErrorNotImplemented(
|
if id, err := j.Talk.ErrorNotImplemented(
|
||||||
v,
|
v,
|
||||||
"http://jabber.org/protocol/pubsub#errors",
|
"http://jabber.org/protocol/pubsub#errors",
|
||||||
"subscribe",
|
"subscribe",
|
||||||
@@ -290,7 +295,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -311,19 +316,19 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
// Предполагается, что такие респонсы должны приходить только для пинга без серверной оптимизации.
|
// Предполагается, что такие респонсы должны приходить только для пинга без серверной оптимизации.
|
||||||
// Хотя идейно мы не поддерживаем работу без серверной оптимизации, но на пинг ответим, нам несложно.
|
// Хотя идейно мы не поддерживаем работу без серверной оптимизации, но на пинг ответим, нам несложно.
|
||||||
if parseError {
|
if parseError {
|
||||||
var iqStruct jabberIqPing
|
var iqStruct IqPing
|
||||||
|
|
||||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||||
log.Debugf("Got IQ get request (actually, response) for MUC Self-Ping from %s", v.From)
|
log.Debugf("Got IQ get request (actually, response) for MUC Self-Ping from %s", v.From)
|
||||||
|
|
||||||
if id, err := talk.PingResponse(v); err != nil {
|
if id, err := j.Talk.PingResponse(v); err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
"unable to send pong to jabber server: id=%s, err=%w",
|
"unable to send pong to jabber server: id=%s, err=%w",
|
||||||
id,
|
id,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -340,28 +345,28 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
case xmpp.IQTypeResult:
|
case xmpp.IQTypeResult:
|
||||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
if slices.Contains(roomsConnected, muc) {
|
if slices.Contains(j.RoomsConnected, muc) {
|
||||||
lastMucActivity.Set(muc, lastServerActivity)
|
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// Похоже на pong от сервера (по стандарту в ответе нету query, но go-xmpp нам подсовывает это)
|
// Похоже на pong от сервера (по стандарту в ответе нету query, но go-xmpp нам подсовывает это)
|
||||||
case v.From == config.Jabber.Server && v.To == talk.JID() && string(v.Query) == "<XMLElement></XMLElement>":
|
case v.From == j.C.Jabber.Server && v.To == j.Talk.JID() && string(v.Query) == "<XMLElement></XMLElement>":
|
||||||
log.Debugf("Got S2C pong answer from %s to %s", v.From, v.To)
|
log.Debugf("Got S2C pong answer from %s to %s", v.From, v.To)
|
||||||
serverPingTimestampRx = time.Now().Unix() //nolint:wsl
|
j.ServerPingTimestampRx = time.Now().Unix() //nolint:wsl
|
||||||
|
|
||||||
// Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован
|
// Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован.
|
||||||
case v.To == talk.JID() && string(v.Query) == "<XMLElement></XMLElement>":
|
case v.To == j.Talk.JID() && string(v.Query) == "<XMLElement></XMLElement>":
|
||||||
mucNameMatch := slices.Contains(roomsConnected, v.From)
|
mucNameMatch := slices.Contains(j.RoomsConnected, v.From)
|
||||||
|
|
||||||
if mucNameMatch {
|
if mucNameMatch {
|
||||||
log.Debugf("Got server-optimized MUC pong answer (xep-0410) from %s to %s", v.From, v.To)
|
log.Debugf("Got server-optimized MUC pong answer (xep-0410) from %s to %s", v.From, v.To)
|
||||||
} else {
|
} else {
|
||||||
mucNickMatch := false
|
mucNickMatch := false
|
||||||
|
|
||||||
for _, room := range roomsConnected {
|
for _, room := range j.RoomsConnected {
|
||||||
mucNick := fmt.Sprintf("%s/%s", room, config.Jabber.Nick)
|
mucNick := fmt.Sprintf("%s/%s", room, j.GetBotNickFromRoomConfig(room))
|
||||||
|
|
||||||
if v.From == mucNick {
|
if v.From == mucNick {
|
||||||
mucNickMatch = true
|
mucNickMatch = true
|
||||||
@@ -372,21 +377,25 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
if mucNickMatch {
|
if mucNickMatch {
|
||||||
log.Debugf("Got 2-nd stage MUC pong answer (xep-0410) from %s to %s", v.From, v.To)
|
log.Debugf("Got 2-nd stage MUC pong answer (xep-0410) from %s to %s", v.From, v.To)
|
||||||
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ
|
|
||||||
} else {
|
} else {
|
||||||
|
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ
|
||||||
log.Infof("Got unknown pong from %s to %s", v.From, v.To)
|
log.Infof("Got unknown pong from %s to %s", v.From, v.To)
|
||||||
log.Debug(spew.Sdump(e))
|
log.Debug(spew.Sdump(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Подтверждение бана
|
// Подтверждение бана
|
||||||
case v.To == talk.JID() && v.ID == "ban1":
|
case v.To == j.Talk.JID() && v.ID == "ban1":
|
||||||
mucNickMatch := false
|
mucNickMatch := false
|
||||||
|
|
||||||
for _, room := range roomsConnected {
|
// Если ник, которому предназначается сообщение, совпадает с ником, из конфига бота (глобального или для
|
||||||
mucNick := fmt.Sprintf("%s/%s", room, config.Jabber.Nick)
|
// комнаты), то считаем, что мы есть в этой комнате. Потому что в противном случае в roomsConnected не
|
||||||
|
// будет искомой комнаты и мы не сможем составить ник бота, чтобы сравнить его с тем, кому адресовано
|
||||||
|
// уведомление.
|
||||||
|
for _, room := range j.RoomsConnected {
|
||||||
|
mucNick := fmt.Sprintf("%s/%s", room, j.GetBotNickFromRoomConfig(room))
|
||||||
|
|
||||||
if v.From == mucNick {
|
if v.To == mucNick {
|
||||||
mucNickMatch = true
|
mucNickMatch = true
|
||||||
|
|
||||||
break
|
break
|
||||||
@@ -396,7 +405,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
// Формально, ответ должен парситься как пустой result и совпадать с типом jabberSimpleIqGetQuery
|
// Формально, ответ должен парситься как пустой result и совпадать с типом jabberSimpleIqGetQuery
|
||||||
// Ответ приходит с 2 xmlns, похоже, он парсится неправильно в go-xmpp.
|
// Ответ приходит с 2 xmlns, похоже, он парсится неправильно в go-xmpp.
|
||||||
// TODO: после исправления go-xmpp, надо исправить это тут
|
// TODO: после исправления go-xmpp, надо исправить это тут
|
||||||
var iqStruct jabberSimpleIqGetQuery
|
var iqStruct SimpleIqGetQuery
|
||||||
|
|
||||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||||
if iqStruct.Xmlns == "http://jabber.org/protocol/muc#admin" && iqStruct.Text == "" {
|
if iqStruct.Xmlns == "http://jabber.org/protocol/muc#admin" && iqStruct.Text == "" {
|
||||||
@@ -416,6 +425,41 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
log.Debug(spew.Sdump(e))
|
log.Debug(spew.Sdump(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ответ с результатом адресован нам.
|
||||||
|
case v.To == j.Talk.JID():
|
||||||
|
var softwareVersion IqResultSoftwareVersion
|
||||||
|
if err := xml.Unmarshal(v.Query, &softwareVersion); err == nil {
|
||||||
|
if softwareVersion.Os == "" {
|
||||||
|
log.Infof(
|
||||||
|
"Recieved software version query result for %s: software=%s version=%s",
|
||||||
|
v.From,
|
||||||
|
softwareVersion.Name,
|
||||||
|
softwareVersion.Version,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log.Infof(
|
||||||
|
"Recieved software version query result for %s: software=%s version=%s os=%s",
|
||||||
|
v.From,
|
||||||
|
softwareVersion.Name,
|
||||||
|
softwareVersion.Version,
|
||||||
|
softwareVersion.Os,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = j.BunySoftwareVersion(v, softwareVersion); err != nil {
|
||||||
|
log.Errorf("Unable to query client software version: %s", err)
|
||||||
|
|
||||||
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Got an IQ result. Dunno how deal with it, discarding")
|
||||||
|
log.Debug(spew.Sdump(e))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Info("Got an IQ result. Dunno how deal with it, discarding")
|
log.Info("Got an IQ result. Dunno how deal with it, discarding")
|
||||||
log.Debug(spew.Sdump(e))
|
log.Debug(spew.Sdump(e))
|
||||||
@@ -424,14 +468,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
// Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем
|
// Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем
|
||||||
case xmpp.IQTypeSet:
|
case xmpp.IQTypeSet:
|
||||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
if slices.Contains(roomsConnected, muc) {
|
if slices.Contains(j.RoomsConnected, muc) {
|
||||||
lastMucActivity.Set(muc, lastServerActivity)
|
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Got an IQ request for set something. Answer not implemented")
|
log.Info("Got an IQ request for set something. Answer not implemented")
|
||||||
|
|
||||||
if id, err := talk.ErrorNotImplemented(
|
if id, err := j.Talk.ErrorNotImplemented(
|
||||||
v,
|
v,
|
||||||
"http://jabber.org/protocol/commands",
|
"http://jabber.org/protocol/commands",
|
||||||
xmpp.IQTypeSet,
|
xmpp.IQTypeSet,
|
||||||
@@ -442,7 +486,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -454,18 +498,18 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
// Если сервер не хочет пинговаться и отвечает ошибкой на пинг, то наверно он не умеет в пинги,
|
// Если сервер не хочет пинговаться и отвечает ошибкой на пинг, то наверно он не умеет в пинги,
|
||||||
// хотя если мы его пингуем, значит он анонсировал такой capability. Вот, засранец!
|
// хотя если мы его пингуем, значит он анонсировал такой capability. Вот, засранец!
|
||||||
var (
|
var (
|
||||||
iqPingStruct jabberIqPing
|
iqPingStruct IqPing
|
||||||
parseError = true
|
parseError = true
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := xml.Unmarshal(v.Query, &iqPingStruct); err == nil {
|
if err := xml.Unmarshal(v.Query, &iqPingStruct); err == nil {
|
||||||
if iqPingStruct.Xmlns == "urn:xmpp:ping" {
|
if iqPingStruct.Xmlns == "urn:xmpp:ping" {
|
||||||
if v.From == config.Jabber.Server {
|
if v.From == j.C.Jabber.Server {
|
||||||
msg := "Server announced that it can answer c2s ping, but gives us an error to such query, "
|
msg := "Server announced that it can answer c2s ping, but gives us an error to such query, "
|
||||||
msg += "fallback to keepalive whitespace pings"
|
msg += "fallback to keepalive whitespace pings"
|
||||||
log.Error(msg)
|
log.Error(msg)
|
||||||
|
|
||||||
serverCapsList.Set("urn:xmpp:ping", false)
|
j.ServerCapsList.Set("urn:xmpp:ping", false)
|
||||||
} else {
|
} else {
|
||||||
log.Errorf("Got 'ping unsupported' message from: %s to: %s", v.From, v.To)
|
log.Errorf("Got 'ping unsupported' message from: %s to: %s", v.From, v.To)
|
||||||
}
|
}
|
||||||
@@ -479,27 +523,29 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
// Это у нас пинг xep-0410 и мы не в комнате, предполагается, что надо бы заджойниться
|
// Это у нас пинг xep-0410 и мы не в комнате, предполагается, что надо бы заджойниться
|
||||||
if parseError {
|
if parseError {
|
||||||
var iqErrorCancelNotAcceptable jabberIqErrorCancelNotAcceptable
|
var iqErrorCancelNotAcceptable IqErrorCancelNotAcceptable
|
||||||
|
|
||||||
if err := xml.Unmarshal(v.Query, &iqErrorCancelNotAcceptable); err == nil {
|
if err := xml.Unmarshal(v.Query, &iqErrorCancelNotAcceptable); err == nil {
|
||||||
if v.To == talk.JID() {
|
if v.To == j.Talk.JID() {
|
||||||
nick := strings.SplitN(v.From, "/", 2)[1]
|
nick := strings.SplitN(v.From, "/", 2)[1]
|
||||||
|
room := strings.SplitN(v.From, "/", 2)[0]
|
||||||
|
|
||||||
if slices.Contains(roomsConnected, iqErrorCancelNotAcceptable.By) && nick == config.Jabber.Nick {
|
if slices.Contains(j.RoomsConnected, iqErrorCancelNotAcceptable.By) &&
|
||||||
|
nick == j.GetBotNickFromRoomConfig(room) {
|
||||||
log.Errorf(
|
log.Errorf(
|
||||||
"Got Iq error message from: %s to: %s. Looks like i'm not in MUC anymore",
|
"Got Iq error message from: %s to: %s. Looks like i'm not in MUC anymore",
|
||||||
v.From, v.To,
|
v.From, v.To,
|
||||||
)
|
)
|
||||||
|
|
||||||
time.Sleep(time.Duration(config.Jabber.MucRejoinDelay) * time.Second)
|
time.Sleep(time.Duration(j.C.Jabber.MucRejoinDelay) * time.Second)
|
||||||
|
|
||||||
if _, err := talk.JoinMUCNoHistory(iqErrorCancelNotAcceptable.By, nick); err != nil {
|
if _, err := j.Talk.JoinMUCNoHistory(iqErrorCancelNotAcceptable.By, nick); err != nil {
|
||||||
err := fmt.Errorf(
|
err := fmt.Errorf(
|
||||||
"looks like connection to server also lost err=%w",
|
"looks like connection to server also lost err=%w",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -535,8 +581,8 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
// Смена статуса участника
|
// Смена статуса участника
|
||||||
case xmpp.Presence:
|
case xmpp.Presence:
|
||||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
if slices.Contains(roomsConnected, muc) {
|
if slices.Contains(j.RoomsConnected, muc) {
|
||||||
lastMucActivity.Set(muc, lastServerActivity)
|
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,17 +594,17 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
// Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать.
|
// Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать.
|
||||||
if v.Type == "unavailable" {
|
if v.Type == "unavailable" {
|
||||||
// Считаем, что мы больше не в комнате, поэтому не знаем, кто там есть
|
// Считаем, что мы больше не в комнате, поэтому не знаем, кто там есть
|
||||||
roomPresences.Delete(v.From)
|
j.RoomPresences.Delete(v.From)
|
||||||
|
|
||||||
log.Error("Presence notification - looks like another instance of client leaves room")
|
log.Error("Presence notification - looks like another instance of client leaves room")
|
||||||
|
|
||||||
if slices.Contains(roomsConnected, v.From) {
|
if slices.Contains(j.RoomsConnected, v.From) {
|
||||||
go joinMuc(v.From)
|
go j.JoinMuc(v.From)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s",
|
log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s",
|
||||||
v.Type, v.From, v.Show, v.Status)
|
v.Type, v.From, v.Show, v.Status)
|
||||||
log.Errorf(spew.Sdump(v))
|
log.Error(spew.Sdump(v))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Infof(
|
log.Infof(
|
||||||
@@ -577,19 +623,20 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
// Это наш собственный Presence
|
// Это наш собственный Presence
|
||||||
if v.Show == "" && v.Status == "" {
|
if v.Show == "" && v.Status == "" {
|
||||||
if nick == config.Jabber.Nick {
|
if nick == j.GetBotNickFromRoomConfig(room) {
|
||||||
roomsConnected = append(roomsConnected, room)
|
j.RoomsConnected = append(j.RoomsConnected, room)
|
||||||
// На всякий случай дедуплицируем список комнат, к которым мы заджойнились.
|
// На всякий случай дедуплицируем список комнат, к которым мы заджойнились.
|
||||||
sort.Strings(roomsConnected)
|
sort.Strings(j.RoomsConnected)
|
||||||
slices.Compact(roomsConnected)
|
slices.Compact(j.RoomsConnected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v.Role {
|
switch v.Role {
|
||||||
// Участник ушёл
|
// Участник ушёл
|
||||||
case "none":
|
case "none":
|
||||||
if presenceJSONInterface, present := roomPresences.Get(room); present {
|
if presenceJSONInterface, present := j.RoomPresences.Get(room); present {
|
||||||
presenceJSONStrings := interfaceToStringSlice(presenceJSONInterface)
|
presenceJSONStrings := InterfaceToStringSlice(presenceJSONInterface)
|
||||||
|
|
||||||
var newPresenceJSONStrings []string
|
var newPresenceJSONStrings []string
|
||||||
|
|
||||||
for _, presenceJSONstring := range presenceJSONStrings {
|
for _, presenceJSONstring := range presenceJSONStrings {
|
||||||
@@ -603,23 +650,25 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
newPresenceJSONStrings = append(newPresenceJSONStrings, presenceJSONstring)
|
newPresenceJSONStrings = append(newPresenceJSONStrings, presenceJSONstring)
|
||||||
}
|
}
|
||||||
|
|
||||||
roomPresences.Set(room, newPresenceJSONStrings)
|
j.RoomPresences.Set(room, newPresenceJSONStrings)
|
||||||
}
|
}
|
||||||
// Участник пришёл
|
// Участник пришёл
|
||||||
default:
|
default:
|
||||||
var presenceJSONStrings []string
|
var (
|
||||||
var newPresenceJSONStrings []string
|
presenceJSONStrings []string
|
||||||
var presenceJSONBytes []byte
|
newPresenceJSONStrings []string
|
||||||
|
presenceJSONBytes []byte
|
||||||
|
)
|
||||||
|
|
||||||
if presenceJSONInterface, present := roomPresences.Get(room); present {
|
if presenceJSONInterface, present := j.RoomPresences.Get(room); present {
|
||||||
presenceJSONStrings = interfaceToStringSlice(presenceJSONInterface)
|
presenceJSONStrings = InterfaceToStringSlice(presenceJSONInterface)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, presenceJSONString := range presenceJSONStrings {
|
for _, presenceJSONString := range presenceJSONStrings {
|
||||||
var p xmpp.Presence
|
var p xmpp.Presence
|
||||||
_ = json.Unmarshal([]byte(presenceJSONString), &p)
|
_ = json.Unmarshal([]byte(presenceJSONString), &p)
|
||||||
|
|
||||||
// Если находим, что у нас уже есть клиент с таким же From, то есть полным nick-ом (для grouchat)
|
// Если находим, что у нас уже есть клиент с таким же From, то есть полным nick-ом (для groupchat)
|
||||||
// просто замещаем его.
|
// просто замещаем его.
|
||||||
if p.From == v.From {
|
if p.From == v.From {
|
||||||
continue
|
continue
|
||||||
@@ -630,16 +679,16 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
|
|
||||||
presenceJSONBytes, _ = json.Marshal(v) //nolint:errchkjson
|
presenceJSONBytes, _ = json.Marshal(v) //nolint:errchkjson
|
||||||
newPresenceJSONStrings = append(newPresenceJSONStrings, string(presenceJSONBytes))
|
newPresenceJSONStrings = append(newPresenceJSONStrings, string(presenceJSONBytes))
|
||||||
roomPresences.Set(room, newPresenceJSONStrings)
|
j.RoomPresences.Set(room, newPresenceJSONStrings)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, а не злодей ли зашёл? Сделать это мы можем, только если мы находимся в комнате.
|
// Проверяем, а не злодей ли зашёл? Сделать это мы можем, только если мы находимся в комнате.
|
||||||
// По правилам, мы можем что-то делать, только после того, как нам прилетит наш собственный presence, это
|
// По правилам, мы можем что-то делать, только после того, как нам прилетит наш собственный presence, это
|
||||||
// значит, что мы вошли в комнату.
|
// значит, что мы вошли в комнату.
|
||||||
if slices.Contains(roomsConnected, room) {
|
if slices.Contains(j.RoomsConnected, room) {
|
||||||
if v.Affiliation != "outcast" {
|
if v.Affiliation != "outcast" {
|
||||||
if err := bunyPresense(v); err != nil {
|
if err := j.BunyPresense(v); err != nil {
|
||||||
gTomb.Kill(err)
|
j.GTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -650,8 +699,8 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
// Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info"
|
// Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info"
|
||||||
case xmpp.DiscoResult:
|
case xmpp.DiscoResult:
|
||||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
if slices.Contains(roomsConnected, muc) {
|
if slices.Contains(j.RoomsConnected, muc) {
|
||||||
lastMucActivity.Set(muc, lastServerActivity)
|
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,10 +712,10 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
// Конкретно сейчас нас интересует только поддержка c2s ping
|
// Конкретно сейчас нас интересует только поддержка c2s ping
|
||||||
for _, feature := range v.Features {
|
for _, feature := range v.Features {
|
||||||
log.Debugf("Server %s announced that it supports feature: %s", v.From, feature)
|
log.Debugf("Server %s announced that it supports feature: %s", v.From, feature)
|
||||||
serverCapsList.Set(feature, true)
|
j.ServerCapsList.Set(feature, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverCapsQueried = true
|
j.ServerCapsQueried = true
|
||||||
|
|
||||||
case "conference":
|
case "conference":
|
||||||
mucCaps := make(map[string]bool)
|
mucCaps := make(map[string]bool)
|
||||||
@@ -676,7 +725,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
mucCaps[feature] = true //nolint:wsl
|
mucCaps[feature] = true //nolint:wsl
|
||||||
}
|
}
|
||||||
|
|
||||||
mucCapsList.Set(v.From, mucCaps)
|
j.MucCapsList.Set(v.From, mucCaps)
|
||||||
|
|
||||||
case "pubsub":
|
case "pubsub":
|
||||||
log.Debugf("PubSub component %s reply to disco#info, skipping", v.From)
|
log.Debugf("PubSub component %s reply to disco#info, skipping", v.From)
|
||||||
82
internal/jabber/main_loop.go
Normal file
82
internal/jabber/main_loop.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package jabber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MyLoop - основной цикл программы.
|
||||||
|
func (j *Jabber) MyLoop() error {
|
||||||
|
for {
|
||||||
|
// Зададим начальное значение глобальным переменным
|
||||||
|
j.ServerPingTimestampRx = 0
|
||||||
|
j.ServerPingTimestampTx = 0
|
||||||
|
j.RoomsConnected = make([]string, 1)
|
||||||
|
j.LastActivity = 0
|
||||||
|
j.LastServerActivity = 0
|
||||||
|
j.LastMucActivity = NewCollection()
|
||||||
|
j.ServerCapsQueried = false
|
||||||
|
j.ServerCapsList = NewCollection()
|
||||||
|
j.MucCapsList = NewCollection()
|
||||||
|
j.ServerPingTimestampTx = 0
|
||||||
|
j.ServerPingTimestampRx = 0
|
||||||
|
j.RoomPresences = NewCollection()
|
||||||
|
|
||||||
|
// Установим коннект
|
||||||
|
if err := j.EstablishConnection(); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
j.ServerPingTimestampRx = time.Now().Unix() // Считаем, что если коннект запустился, то первый пинг успешен.
|
||||||
|
|
||||||
|
// Тыкаем сервер палочкой, проверяем, что коннект жив и вываливаемся из mainLoop, если он не жив.
|
||||||
|
j.GTomb.Go(
|
||||||
|
func() error {
|
||||||
|
err := j.ProbeServerLiveness()
|
||||||
|
log.Error(err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Тыкаем muc-и палочкой, проверяем, что они живы и вываливаемся из mainLoop, если пинги пропали.
|
||||||
|
// Если пинги до комнаты пропали, то это фактически значит, что либо сервер потерял связь с MUC-компонентом,
|
||||||
|
// либо у нас какой-то wire error.
|
||||||
|
// j.GTomb.Go() тут зовётся внутри на каждую комнату, если у нас ошибка, эта горутинка рано или поздно выйдет.
|
||||||
|
go j.ProbeMUCLiveness()
|
||||||
|
|
||||||
|
// Гребём ивенты...
|
||||||
|
for {
|
||||||
|
// Стриггерилось завершение работы приложения, или соединение не установлено (порвалось, например)
|
||||||
|
// грести не надо
|
||||||
|
if j.Shutdown {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !j.IsConnected {
|
||||||
|
// Tight loop - это наверно не очень хорошо, думаю, ничего страшного не будет, если мы поспим 100мс.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вынимаем ивент из "провода"
|
||||||
|
chat, err := j.Talk.Recv()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to get recieve data from socket: %s", err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
j.ParseEvent(chat)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
@@ -1,10 +1,22 @@
|
|||||||
package main
|
package jabber
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Переменные для простенького нормализатора текста.
|
||||||
|
var (
|
||||||
|
// Знаки препинания раз.
|
||||||
|
pMarks = []string{".", ",", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "{", "}", "<", ">", "[", "]", "\\"}
|
||||||
|
|
||||||
|
// Знаки препинания два-с.
|
||||||
|
pMarks2 = []string{"-", "_", "+", "=", ":", ";", "'", "`", "~", "\""}
|
||||||
|
|
||||||
|
// Символы новой строки.
|
||||||
|
newLines = []string{"\n", "\r", "\n\r", "\r\n"}
|
||||||
|
)
|
||||||
|
|
||||||
// Нормализует текстовый буфер. Убирает начальные и конечные пробельные символы, заменяет сносы строк пробелами, удаляет
|
// Нормализует текстовый буфер. Убирает начальные и конечные пробельные символы, заменяет сносы строк пробелами, удаляет
|
||||||
// знаки препинания, схлопывает повторяющиеся пробелы в один символ.
|
// знаки препинания, схлопывает повторяющиеся пробелы в один символ.
|
||||||
func nString(buf string) string {
|
func nString(buf string) string {
|
||||||
@@ -32,7 +44,7 @@ func nString(buf string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Обёртка для nString, возвращает нормализованную строку в нижнем регистре.
|
// Обёртка для nString, возвращает нормализованную строку в нижнем регистре.
|
||||||
func nStringLower(buf string) string {
|
func nStringLower(buf string) string { //nolint:unused
|
||||||
return strings.ToLower(nString(buf))
|
return strings.ToLower(nString(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,3 +52,5 @@ func nStringLower(buf string) string {
|
|||||||
func nStringUpper(buf string) string {
|
func nStringUpper(buf string) string {
|
||||||
return strings.ToUpper(nString(buf))
|
return strings.ToUpper(nString(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
36
internal/jabber/query_version.go
Normal file
36
internal/jabber/query_version.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package jabber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/eleksir/go-xmpp"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QuerySoftwareVersion запрошивает версию и название клиента.
|
||||||
|
func (j *Jabber) QuerySoftwareVersion(jid string) (string, error) {
|
||||||
|
var (
|
||||||
|
id string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if id, err = j.Talk.RawInformationQuery(
|
||||||
|
j.Talk.JID(),
|
||||||
|
jid,
|
||||||
|
uuid.New().String(),
|
||||||
|
xmpp.IQTypeGet,
|
||||||
|
"jabber:iq:version",
|
||||||
|
"",
|
||||||
|
); err != nil {
|
||||||
|
err = fmt.Errorf(
|
||||||
|
"unable to query software version of jid=%s err=%w",
|
||||||
|
jid,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log.Infof("Query software version of %s", jid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
280
internal/jabber/read_config.go
Normal file
280
internal/jabber/read_config.go
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
package jabber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/hjson/hjson-go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге
|
||||||
|
// нет.
|
||||||
|
func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
executablePath string
|
||||||
|
configLoaded = false
|
||||||
|
)
|
||||||
|
|
||||||
|
executablePath, err = os.Executable()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("unable to get current executable path: %w", err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
||||||
|
|
||||||
|
locations := []string{
|
||||||
|
"~/.buny-jabber-bot.json",
|
||||||
|
"~/buny-jabber-bot.json",
|
||||||
|
"/etc/buny-jabber-bot.json",
|
||||||
|
configJSONPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, location := range locations {
|
||||||
|
fileInfo, err := os.Stat(location)
|
||||||
|
|
||||||
|
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём.
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата.
|
||||||
|
if fileInfo.Size() > 2777216 {
|
||||||
|
log.Warnf("Config file %s is too long for config, skipping", location)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := os.ReadFile(location)
|
||||||
|
|
||||||
|
// Не удалось прочитать, попробуем следующего кандидата.
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip reading config file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||||
|
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||||
|
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
||||||
|
var (
|
||||||
|
sampleConfig MyConfig
|
||||||
|
tmp map[string]interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
|
|
||||||
|
// Не удалось распарсить - попробуем следующего кандидата.
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpJSON, err := json.Marshal(tmp)
|
||||||
|
|
||||||
|
// Не удалось преобразовать map-ку в json
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(tmpJSON, &sampleConfig); err != nil {
|
||||||
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Значения для Jabber-клиента
|
||||||
|
if sampleConfig.Jabber.Server == "" {
|
||||||
|
log.Error("Jabber server is not defined in config, using localhost")
|
||||||
|
sampleConfig.Jabber.Server = "localhost" //nolint:wsl
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.Port == 0 {
|
||||||
|
sampleConfig.Jabber.Port = 5222
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.Ssl {
|
||||||
|
if !sampleConfig.Jabber.StartTLS {
|
||||||
|
sampleConfig.Jabber.Port = 5223
|
||||||
|
|
||||||
|
log.Info("Jabber port is not defined in config, using 5223")
|
||||||
|
} else {
|
||||||
|
log.Info("Jabber port is not defined in config, using 5222")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sampleConfig.Jabber.Ssl {
|
||||||
|
sampleConfig.Jabber.StartTLS = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sampleConfig.Jabber.Ssl || !sampleConfig.Jabber.StartTLS {
|
||||||
|
sampleConfig.Jabber.SslVerify = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.ConnectionTimeout == 0 {
|
||||||
|
sampleConfig.Jabber.ConnectionTimeout = 10
|
||||||
|
|
||||||
|
log.Info("Jabber server connection_timeout not defined in config, using 10 seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.ReconnectDelay == 0 {
|
||||||
|
sampleConfig.Jabber.ReconnectDelay = 3
|
||||||
|
|
||||||
|
log.Info("Jabber server reconnect_delay not defined in config, using 3 seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.ServerPingDelay == 0 {
|
||||||
|
sampleConfig.Jabber.ServerPingDelay = 60
|
||||||
|
|
||||||
|
log.Info("Jabber server_ping_delay not defined in config, using 60 seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.MucPingDelay == 0 {
|
||||||
|
sampleConfig.Jabber.MucPingDelay = 900
|
||||||
|
|
||||||
|
log.Info("Jabber muc_ping_delay not defined in config, using 900 seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.MucRejoinDelay == 0 {
|
||||||
|
sampleConfig.Jabber.MucRejoinDelay = 3
|
||||||
|
|
||||||
|
log.Info("Jabber muc_rejoin_delay not defined in config, using 3 seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.PingSplayDelay == 0 {
|
||||||
|
sampleConfig.Jabber.PingSplayDelay = 3
|
||||||
|
|
||||||
|
log.Info("Jabber ping_splay_delay not defined in config, using 3 seconds")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.Nick == "" {
|
||||||
|
return errors.New("jabber nick is not defined in config, quitting") //nolint:goerr113
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.Resource == "" {
|
||||||
|
sampleConfig.Jabber.Resource = "bunyPresense bot"
|
||||||
|
|
||||||
|
log.Info("Jabber resource not defined in config, using bunyPresense bot")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.User == "" {
|
||||||
|
sampleConfig.Jabber.User = fmt.Sprintf("%s@%s", sampleConfig.Jabber.Nick, sampleConfig.Jabber.Server)
|
||||||
|
|
||||||
|
log.Infof("Jabber user not defined in config, guessing, it can be %s", sampleConfig.Jabber.User)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если sampleConfig.Jabber.Password не задан, то авторизации не будет
|
||||||
|
|
||||||
|
// Если не задано ни одного мастера, то бот сам себе мастер
|
||||||
|
if len(sampleConfig.Jabber.BotMasters) == 0 {
|
||||||
|
sampleConfig.Jabber.BotMasters[0] = sampleConfig.Jabber.User
|
||||||
|
}
|
||||||
|
|
||||||
|
// Нам бот нужен в каких-то чат-румах, а не "просто так"
|
||||||
|
if len(sampleConfig.Jabber.Channels) < 1 {
|
||||||
|
return errors.New("no jabber channels/rooms defined in config, quitting") //nolint:goerr113
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, channel := range sampleConfig.Jabber.Channels {
|
||||||
|
if channel.Name == "" {
|
||||||
|
return errors.New("no \"name\" entry in jabber channel config") //nolint:goerr113
|
||||||
|
}
|
||||||
|
|
||||||
|
if channel.Nick == "" {
|
||||||
|
sampleConfig.Jabber.Channels[n].Nick = sampleConfig.Jabber.Nick
|
||||||
|
}
|
||||||
|
|
||||||
|
// channel.Password может быть пустым, тогда пароля нет
|
||||||
|
// channel.Bayes.Enabled будет false, если не проставлен
|
||||||
|
|
||||||
|
if channel.Bayes.MinLength < 40 {
|
||||||
|
channel.Bayes.MinLength = 40
|
||||||
|
}
|
||||||
|
|
||||||
|
if channel.Bayes.MinWords < 8 {
|
||||||
|
channel.Bayes.MinWords = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
switch channel.Bayes.DefaultAction {
|
||||||
|
case "kick":
|
||||||
|
case "ban":
|
||||||
|
case "devoice":
|
||||||
|
default:
|
||||||
|
channel.Bayes.DefaultAction = "log"
|
||||||
|
}
|
||||||
|
|
||||||
|
// channel.AllCaps.Enabled будет false, если не указан
|
||||||
|
if channel.AllCaps.MinLength < 10 {
|
||||||
|
channel.AllCaps.MinLength = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
switch channel.AllCaps.DefaultAction {
|
||||||
|
case "kick":
|
||||||
|
case "ban":
|
||||||
|
case "devoice":
|
||||||
|
default:
|
||||||
|
channel.AllCaps.DefaultAction = "log"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если список фраз с которыми стартует бот пустой, вносим в него 1 запись с пустой строкой
|
||||||
|
if len(sampleConfig.Jabber.StartupStatus) == 0 {
|
||||||
|
sampleConfig.Jabber.StartupStatus[0] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если список статусов, с которыми работает бот пустой, вносим в него 1 запись с пустой строкой
|
||||||
|
if len(sampleConfig.Jabber.RuntimeStatus.Text) == 0 {
|
||||||
|
sampleConfig.Jabber.RuntimeStatus.Text[0] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если sampleConfig.Jabber.RuntimeStatus.RotationTime не задан, то он равен 0
|
||||||
|
// Если sampleConfig.Jabber.RuntimeStatus.RotationSplayTime не задан, то он равен 0
|
||||||
|
|
||||||
|
// Если sampleConfig.Jabber.BanDelay не задан, то он равен 0
|
||||||
|
// Если sampleConfig.Jabber.BanPhrasesEnable не задан, то он false
|
||||||
|
|
||||||
|
// Если список фраз, с которыми банят пустой, то вносим в него одну позицию с пустой строкой
|
||||||
|
if len(sampleConfig.Jabber.BanPhrases) == 0 {
|
||||||
|
sampleConfig.Jabber.BanPhrases[0] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.CSign == "" {
|
||||||
|
sampleConfig.CSign = "!"
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Loglevel == "" {
|
||||||
|
sampleConfig.Loglevel = "info"
|
||||||
|
|
||||||
|
log.Info("loglevel not defined in config, using info")
|
||||||
|
}
|
||||||
|
|
||||||
|
// sampleConfig.Log = "" if not set
|
||||||
|
|
||||||
|
j.C = sampleConfig
|
||||||
|
configLoaded = true
|
||||||
|
|
||||||
|
log.Infof("Using %s as config file", location)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !configLoaded {
|
||||||
|
return errors.New("config was not loaded") //nolint:goerr113
|
||||||
|
}
|
||||||
|
|
||||||
|
return err //nolint:wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
62
internal/jabber/sig_handler.go
Normal file
62
internal/jabber/sig_handler.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package jabber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/eleksir/go-xmpp"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (j *Jabber) SigHandler() error {
|
||||||
|
log.Debug("Installing signal handler")
|
||||||
|
|
||||||
|
for s := range j.SigChan {
|
||||||
|
switch s {
|
||||||
|
case syscall.SIGINT:
|
||||||
|
log.Infoln("Got SIGINT, quitting")
|
||||||
|
case syscall.SIGTERM:
|
||||||
|
log.Infoln("Got SIGTERM, quitting")
|
||||||
|
case syscall.SIGQUIT:
|
||||||
|
log.Infoln("Got SIGQUIT, quitting")
|
||||||
|
|
||||||
|
// Заходим на новую итерацию, если у нас "неинтересный" сигнал.
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Чтобы не срать в логи ошибками, проставим shutdown state приложения в true.
|
||||||
|
j.Shutdown = true
|
||||||
|
|
||||||
|
if j.IsConnected && !j.Shutdown {
|
||||||
|
log.Debug("Try to set our presence to Unavailable and status to Offline")
|
||||||
|
|
||||||
|
// Вот тут понадобится коллекция известных пользователей, чтобы им разослать presence, что бот свалил в
|
||||||
|
// offline. Пока за неимением лучшего сообщим об этом самим себе.
|
||||||
|
for _, room := range j.RoomsConnected {
|
||||||
|
if _, err := j.Talk.SendPresence(
|
||||||
|
xmpp.Presence{ //nolint:exhaustruct
|
||||||
|
To: room,
|
||||||
|
Status: "Offline",
|
||||||
|
Type: "unavailable",
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
log.Infof("Unable to send presence to jabber server: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// И закрываем соединение.
|
||||||
|
log.Infoln("Closing connection to jabber server")
|
||||||
|
|
||||||
|
if err := j.Talk.Close(); err != nil {
|
||||||
|
log.Infof("Unable to close connection to jabber server: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
80
internal/jabber/squash.go
Normal file
80
internal/jabber/squash.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package jabber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/eleksir/go-xmpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Squash банит указанный jid в указанной комнате.
|
||||||
|
// reasonEnable указывает, надо ли писать дату автобана в банлисте в поле reason (это единственная причина, в которую
|
||||||
|
// умеет бот).
|
||||||
|
func (j *Jabber) Squash(room, jid string, reasonEnable bool, vType string) (string, error) {
|
||||||
|
var (
|
||||||
|
id string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if j.C.Jabber.BanPhrasesEnable {
|
||||||
|
phrase := RandomPhrase(j.C.Jabber.BanPhrases)
|
||||||
|
|
||||||
|
if _, err = j.Talk.Send(
|
||||||
|
xmpp.Chat{ //nolint:exhaustruct
|
||||||
|
Remote: room,
|
||||||
|
Text: phrase,
|
||||||
|
Type: vType,
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
err = fmt.Errorf("unable to send phrase to room %s: %w", room, err)
|
||||||
|
|
||||||
|
// Здесь возвращаем nil, т.к. за нас ошибку залоггирует код выше.
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://xmpp.org/extensions/xep-0045.html#ban баним вот таким сообщением
|
||||||
|
ban := "<item affiliation='outcast' jid='" + jid + "'>"
|
||||||
|
|
||||||
|
if reasonEnable {
|
||||||
|
var t = time.Now()
|
||||||
|
ban += fmt.Sprintf(
|
||||||
|
"<reason>autoban at %04d.%02d.%02d %02d:%02d:%02d</reason>",
|
||||||
|
t.Year(),
|
||||||
|
t.Month(),
|
||||||
|
t.Day(),
|
||||||
|
t.Hour(),
|
||||||
|
t.Minute(),
|
||||||
|
t.Second(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ban += "<reason />"
|
||||||
|
}
|
||||||
|
|
||||||
|
ban += "</item>"
|
||||||
|
|
||||||
|
// Выжидаем некоторое время перед баном. А то можно настолько рано забанить, что сервер не внесёт злодея в банлист
|
||||||
|
// комнаты и пришлёт affiliation: none вместо affiliation: outcast.
|
||||||
|
if j.C.Jabber.BanDelay > 0 {
|
||||||
|
time.Sleep(time.Duration(j.C.Jabber.BanDelay) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
if id, err = j.Talk.RawInformationQuery(
|
||||||
|
j.Talk.JID(),
|
||||||
|
room,
|
||||||
|
"ban1",
|
||||||
|
xmpp.IQTypeSet,
|
||||||
|
"http://jabber.org/protocol/muc#admin",
|
||||||
|
ban,
|
||||||
|
); err != nil {
|
||||||
|
err = fmt.Errorf(
|
||||||
|
"unable to ban user: id=%s, err=%w",
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
215
internal/jabber/types.go
Normal file
215
internal/jabber/types.go
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
package jabber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/eleksir/go-xmpp"
|
||||||
|
"gopkg.in/tomb.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MyConfig прототип структурки с конфигом.
|
||||||
|
type MyConfig struct {
|
||||||
|
Jabber struct {
|
||||||
|
Server string `json:"server,omitempty"`
|
||||||
|
Port int `json:"port,omitempty"`
|
||||||
|
Ssl bool `json:"ssl,omitempty"`
|
||||||
|
StartTLS bool `json:"starttls,omitempty"`
|
||||||
|
SslVerify bool `json:"ssl_verify,omitempty"`
|
||||||
|
InsecureAllowUnencryptedAuth bool `json:"insecureallowunencryptedauth,omitempty"`
|
||||||
|
ConnectionTimeout int64 `json:"connection_timeout,omitempty"`
|
||||||
|
ReconnectDelay int64 `json:"reconnect_delay,omitempty"`
|
||||||
|
ServerPingDelay int64 `json:"server_ping_delay,omitempty"`
|
||||||
|
MucPingDelay int64 `json:"muc_ping_delay,omitempty"`
|
||||||
|
MucRejoinDelay int64 `json:"muc_rejoin_delay,omitempty"`
|
||||||
|
PingSplayDelay int64 `json:"ping_splay_delay,omitempty"`
|
||||||
|
Nick string `json:"nick,omitempty"`
|
||||||
|
Resource string `json:"resource,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
BotMasters []string `json:"bot_masters,omitempty"`
|
||||||
|
Channels []struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Nick string `json:"nick,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
Bayes struct {
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
MinWords int64 `json:"min_words,omitempty"`
|
||||||
|
MinLength int `json:"min_length,omitempty"`
|
||||||
|
DefaultAction string `json:"default_action,omitempty"`
|
||||||
|
} `json:"bayes,omitempty"`
|
||||||
|
AllCaps struct {
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
MinLength int `json:"min_length,omitempty"`
|
||||||
|
DefaultAction string `json:"default_action,omitempty"`
|
||||||
|
} `json:"all_caps,omitempty"`
|
||||||
|
} `json:"channels"`
|
||||||
|
StartupStatus []string `json:"startup_status,omitempty"`
|
||||||
|
RuntimeStatus struct {
|
||||||
|
Text []string `json:"text,omitempty"`
|
||||||
|
RotationTime int64 `json:"rotation_time,omitempty"`
|
||||||
|
RotationSplayTime int64 `json:"rotation_splay_time,omitempty"`
|
||||||
|
} `json:"runtime_status,omitempty"`
|
||||||
|
BanDelay int64 `json:"ban_delay,omitempty"`
|
||||||
|
BanPhrasesEnable bool `json:"ban_phrases_enable,omitempty"`
|
||||||
|
BanPhrases []string `json:"ban_phrases,omitempty"`
|
||||||
|
} `json:"jabber,omitempty"`
|
||||||
|
|
||||||
|
CSign string `json:"csign,omitempty"`
|
||||||
|
Loglevel string `json:"loglevel,omitempty"`
|
||||||
|
Log string `json:"log,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
ExeName string `json:"exe_name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MyWhiteList прототип структурки с белым списком jid-ов.
|
||||||
|
type MyWhiteList struct {
|
||||||
|
Whitelist []struct {
|
||||||
|
RoomName string `json:"room_name,omitempty"`
|
||||||
|
Jid []string `json:"jid,omitempty"`
|
||||||
|
WipeBans bool `json:"wipe_bans,omitempty"`
|
||||||
|
} `json:"whitelist,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MyBlackList прототип структурки с чёрным списком jid-ов.
|
||||||
|
type MyBlackList struct {
|
||||||
|
Blacklist []struct {
|
||||||
|
RoomName string `json:"room_name,omitempty"`
|
||||||
|
ReasonEnable bool `json:"reason_enable,omitempty"`
|
||||||
|
JidRe []string `json:"jid_re,omitempty"`
|
||||||
|
NickRe []string `json:"nick_re,omitempty"`
|
||||||
|
PhraseRe []string `json:"phrase_re,omitempty"`
|
||||||
|
UserAgent []struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
Os string `json:"os,omitempty"`
|
||||||
|
} `json:"user_agent,omitempty"`
|
||||||
|
} `json:"blacklist,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jabber основная структура-объект, содержащая стейты и проч.
|
||||||
|
type Jabber struct {
|
||||||
|
// C - конфиг, как он распарсился из конфиг-файла.
|
||||||
|
C MyConfig
|
||||||
|
|
||||||
|
// WhiteList - структурка с безусловно разрешёнными jid-ами
|
||||||
|
WhiteList MyWhiteList
|
||||||
|
|
||||||
|
// BlackList - структурка с запрещёнными по регуляркам фразами, никами, jid-ами.
|
||||||
|
BlackList MyBlackList
|
||||||
|
|
||||||
|
// Опции подключения к xmpp-серверу.
|
||||||
|
Options *xmpp.Options
|
||||||
|
|
||||||
|
// gTomb пул активных горутин.
|
||||||
|
GTomb tomb.Tomb
|
||||||
|
|
||||||
|
// Talk основная структурка xmpp-клиента.
|
||||||
|
Talk *xmpp.Client
|
||||||
|
|
||||||
|
// sync.Map-ка с капабилити сервера.
|
||||||
|
ServerCapsList *Collection
|
||||||
|
|
||||||
|
// ServerCapsQueried показывает, были ли запрошены capabilities сервера.
|
||||||
|
ServerCapsQueried bool
|
||||||
|
|
||||||
|
// Время последней активности, нужно для c2s пингов - посылаем пинги, только если давненько ничего не приходило с
|
||||||
|
// сервера.
|
||||||
|
LastServerActivity int64
|
||||||
|
|
||||||
|
// Время, когда был отправлен c2s ping.
|
||||||
|
ServerPingTimestampTx int64
|
||||||
|
|
||||||
|
// Время, когда был принят s2c pong.
|
||||||
|
ServerPingTimestampRx int64
|
||||||
|
|
||||||
|
// Время последней активности, нужно для jabber:iq:last.
|
||||||
|
LastActivity int64
|
||||||
|
|
||||||
|
// sync.Map-ка с комнатами и их capability.
|
||||||
|
MucCapsList *Collection
|
||||||
|
|
||||||
|
// Время последней активности MUC-ов, нужно для пингов - посылаем пинги, только если давненько ничего не приходило из
|
||||||
|
// muc-ов.
|
||||||
|
LastMucActivity *Collection
|
||||||
|
|
||||||
|
// Список комнат, в которых находится бот.
|
||||||
|
RoomsConnected []string
|
||||||
|
|
||||||
|
// sync.Map-ка со списком участников конференций (в json-формате, согласно структуре xmpp.Presence, "room".[]json).
|
||||||
|
RoomPresences *Collection
|
||||||
|
|
||||||
|
// Канал, по котором приходят сообщения о том, что ОС отправила некие сигналы процессу.
|
||||||
|
SigChan chan os.Signal
|
||||||
|
|
||||||
|
// Индиктор того, что процесс завершается.
|
||||||
|
Shutdown bool
|
||||||
|
|
||||||
|
// Индиктор того, что соединение установлено.
|
||||||
|
IsConnected bool
|
||||||
|
|
||||||
|
// Индикатор того, что соединение в процессе достукивания до сервера.
|
||||||
|
Connecting bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleIqGetQuery прототип структурки для разбора запросов xmpp discovery query, например,
|
||||||
|
// https://xmpp.org/extensions/xep-0030.html#example-18 .
|
||||||
|
type SimpleIqGetQuery struct {
|
||||||
|
XMLName xml.Name `xml:"query"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
Node string `xml:"node,attr,omitempty"` // для xmlns="http://jabber.org/protocol/disco#items"
|
||||||
|
}
|
||||||
|
|
||||||
|
// PubsubIQGetQuery прототип структурки для разбора запросов xmpp pubsub.
|
||||||
|
type PubsubIQGetQuery struct {
|
||||||
|
XMLName xml.Name `xml:"pubsub"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
Items struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
MaxItems string `xml:"max_items,attr"`
|
||||||
|
} `xml:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeIqGetQuery прототип структурки для разбора IQ запросов на локальное время клиента,
|
||||||
|
// https://xmpp.org/extensions/xep-0202.html
|
||||||
|
type TimeIqGetQuery struct {
|
||||||
|
// <time xmlns="urn:xmpp:time"/>
|
||||||
|
XMLName xml.Name `xml:"time"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IqPing прототип структурки для разбора IQ запросов на пинг клиента, https://xmpp.org/extensions/xep-0199.html
|
||||||
|
type IqPing struct {
|
||||||
|
XMLName xml.Name `xml:"ping"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IqErrorCancelNotAcceptable прототип структурки для разбора IQ ответов, когда сервис (сервер, клиент, etc) не
|
||||||
|
// может или не хочет принимать наш iq-запрос https://xmpp.org/extensions/xep-0099.html
|
||||||
|
type IqErrorCancelNotAcceptable struct {
|
||||||
|
XMLName xml.Name `xml:"error"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
By string `xml:"by,attr"`
|
||||||
|
NotAcceptable struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
} `xml:"not-acceptable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IqResultSoftwareVersion прототип структурки для разбора IQ ответов на запрос о названии и версии клиентского ПО.
|
||||||
|
type IqResultSoftwareVersion struct {
|
||||||
|
XMLName xml.Name `xml:"query"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
Name string `xml:"name"`
|
||||||
|
Version string `xml:"version"`
|
||||||
|
Os string `xml:"os,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
435
internal/jabber/util.go
Normal file
435
internal/jabber/util.go
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
package jabber
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/eleksir/go-xmpp"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandomPhrase выдаёт одну рандомную фразу из даденного списка фраз.
|
||||||
|
func RandomPhrase(list []string) string {
|
||||||
|
phrase := ""
|
||||||
|
|
||||||
|
if listLen := len(list); listLen > 0 {
|
||||||
|
phrase = list[rand.Intn(listLen)]
|
||||||
|
}
|
||||||
|
|
||||||
|
return phrase
|
||||||
|
}
|
||||||
|
|
||||||
|
// InterfaceToStringSlice превращает данный интерфейс в слайс строк.
|
||||||
|
// Если может, конечно :) .
|
||||||
|
func InterfaceToStringSlice(iface interface{}) []string {
|
||||||
|
var mySlice []string
|
||||||
|
|
||||||
|
// А теперь мы начинаем дурдом, нам надо превратить ёбанный interface{} в []string
|
||||||
|
// Поскольку interface{} может быть чем угодно, перестрахуемся
|
||||||
|
if reflect.TypeOf(iface).Kind() == reflect.Slice {
|
||||||
|
shit := reflect.ValueOf(iface)
|
||||||
|
|
||||||
|
for i := 0; i < shit.Len(); i++ {
|
||||||
|
mySlice = append(mySlice, fmt.Sprint(shit.Index(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mySlice
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRealJIDfromNick достаёт из запомненных presence-ов по даденному nick-у real jid с resource-ом. Nick должен
|
||||||
|
// содержать имя конфы, откуда участник.
|
||||||
|
func (j *Jabber) GetRealJIDfromNick(fullNick string) string {
|
||||||
|
var p xmpp.Presence
|
||||||
|
|
||||||
|
room := (strings.SplitN(fullNick, "/", 2))[0]
|
||||||
|
|
||||||
|
// Достанем presence участника
|
||||||
|
presenceJSONInterface, present := j.RoomPresences.Get(room)
|
||||||
|
|
||||||
|
// Никого нет дома
|
||||||
|
if !present {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
presenceJSONStrings := InterfaceToStringSlice(presenceJSONInterface)
|
||||||
|
|
||||||
|
for _, presepresenceJSONString := range presenceJSONStrings {
|
||||||
|
_ = json.Unmarshal([]byte(presepresenceJSONString), &p)
|
||||||
|
|
||||||
|
if p.From == fullNick {
|
||||||
|
return p.JID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBotNickFromRoomConfig достаёт из настроек комнаты короткий ник бота, либо берёт значение из конфига, если ник в
|
||||||
|
// настройках комнаты не задан. Короткий ник не содержит название комнаты.
|
||||||
|
func (j *Jabber) GetBotNickFromRoomConfig(room string) string {
|
||||||
|
for _, roomStruct := range j.C.Jabber.Channels {
|
||||||
|
if roomStruct.Name == room {
|
||||||
|
return roomStruct.Nick
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return j.C.Jabber.Nick
|
||||||
|
}
|
||||||
|
|
||||||
|
// RotateStatus периодически изменяет статус бота в MUC-е согласно настройкам из кофига.
|
||||||
|
func (j *Jabber) RotateStatus(room string) error {
|
||||||
|
for {
|
||||||
|
// TODO: Переделать на ticker-ы
|
||||||
|
totalSleepTime := time.Duration(j.C.Jabber.RuntimeStatus.RotationTime) * time.Second
|
||||||
|
totalSleepTime += time.Duration(j.C.Jabber.RuntimeStatus.RotationSplayTime) * time.Second
|
||||||
|
|
||||||
|
for {
|
||||||
|
status := RandomPhrase(j.C.Jabber.RuntimeStatus.Text)
|
||||||
|
log.Debugf("Set status for MUC: %s to: %s", room, status)
|
||||||
|
|
||||||
|
var p xmpp.Presence
|
||||||
|
|
||||||
|
if room != "" {
|
||||||
|
p = xmpp.Presence{ //nolint:exhaustruct
|
||||||
|
To: room,
|
||||||
|
Status: status,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p = xmpp.Presence{ //nolint:exhaustruct
|
||||||
|
Status: status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := j.Talk.SendPresence(p); err != nil {
|
||||||
|
return fmt.Errorf("unable to send presence to MUC %s: %w", room, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если мы не хотим ротировать, то цикл нам тут не нужен, просто выходим.
|
||||||
|
if j.C.Jabber.RuntimeStatus.RotationTime == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(totalSleepTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinMuc джойнится к конференциям/каналам/комнатам в джаббере.
|
||||||
|
func (j *Jabber) JoinMuc(room string) error {
|
||||||
|
log.Debugf("Sending disco#info from %s to %s", j.Talk.JID(), room)
|
||||||
|
|
||||||
|
if _, err := j.Talk.DiscoverInfo(j.Talk.JID(), room); err != nil {
|
||||||
|
return fmt.Errorf("unable to send disco#info to MUC %s: %w", room, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ждём, пока muc нам вернёт список фичей.
|
||||||
|
for i := 0; i < (20 * int(j.C.Jabber.ConnectionTimeout)); i++ {
|
||||||
|
var (
|
||||||
|
myRoom interface{}
|
||||||
|
supported bool
|
||||||
|
exist bool
|
||||||
|
)
|
||||||
|
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
if myRoom, exist = j.MucCapsList.Get(room); !exist {
|
||||||
|
// Пока не задискаверилась.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if supported, exist = myRoom.(map[string]bool)["muc_unsecured"]; exist {
|
||||||
|
if supported {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Unable to join to password-protected room. Don't know how to enter passwords :)")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Заходим в конфу, наконец-то
|
||||||
|
if _, err := j.Talk.JoinMUCNoHistory(room, j.GetBotNickFromRoomConfig(room)); err != nil {
|
||||||
|
return fmt.Errorf("unable to join to MUC: %s, %w", room, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Joining to MUC: %s", room)
|
||||||
|
|
||||||
|
// Ждём, когда прилетит presence из комнаты, тогда мы точно знаем, что мы вошли.
|
||||||
|
entered := false
|
||||||
|
|
||||||
|
for i := 0; i < (20 * int(j.C.Jabber.ConnectionTimeout)); i++ {
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
if slices.Contains(j.RoomsConnected, room) {
|
||||||
|
entered = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !entered {
|
||||||
|
log.Errorf(
|
||||||
|
"Unable to enter to MUC %s, join timeout after %d seconds (server does not return my presence for this room)",
|
||||||
|
room,
|
||||||
|
20*int(j.C.Jabber.ConnectionTimeout)+1,
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вот теперь точно можно слать статус.
|
||||||
|
log.Infof("Joined to MUC: %s", room)
|
||||||
|
|
||||||
|
j.GTomb.Go(func() error { return j.RotateStatus(room) })
|
||||||
|
|
||||||
|
// Время проверить участников на предмет злобности
|
||||||
|
namesInterface, present := j.RoomPresences.Get(room)
|
||||||
|
|
||||||
|
// Если room есть в списке presence-ов, то фигачим. room там должен быть, просто обязан.
|
||||||
|
if present {
|
||||||
|
for _, name := range InterfaceToStringSlice(namesInterface) {
|
||||||
|
var v xmpp.Presence
|
||||||
|
|
||||||
|
_ = json.Unmarshal([]byte(name), &v)
|
||||||
|
log.Infof("Fake presence forged for %s just for on-enter check", name)
|
||||||
|
// Оно там внутри всё само обработает, если вдруг возникнет wire error, то зарекконетится.
|
||||||
|
_ = j.BunyPresense(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EstablishConnection устанавливает соединение с jabber-сервером.
|
||||||
|
func (j *Jabber) EstablishConnection() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if j.Connecting && !j.IsConnected {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проставляем глобальные переменные.
|
||||||
|
j.Connecting = true
|
||||||
|
j.IsConnected = false
|
||||||
|
j.RoomsConnected = make([]string, 0)
|
||||||
|
|
||||||
|
log.Debugf("Establishing connection to %s", j.Options.Host)
|
||||||
|
j.Talk, err = j.Options.NewClient()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to connect to %s: %w", j.Options.Host, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// По идее keepalive должен же проходить только, если мы уже на сервере, так?
|
||||||
|
if _, err := j.Talk.SendKeepAlive(); err != nil {
|
||||||
|
return fmt.Errorf("try to send initial KeepAlive, got error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Connected")
|
||||||
|
|
||||||
|
// Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили
|
||||||
|
for _, roomStruct := range j.C.Jabber.Channels {
|
||||||
|
room := roomStruct.Name
|
||||||
|
|
||||||
|
j.GTomb.Go(func() error { return j.JoinMuc(room) })
|
||||||
|
}
|
||||||
|
|
||||||
|
j.GTomb.Go(func() error { return j.RotateStatus("") })
|
||||||
|
|
||||||
|
j.LastActivity = time.Now().Unix()
|
||||||
|
j.Connecting = false
|
||||||
|
j.IsConnected = true
|
||||||
|
|
||||||
|
log.Debugf("Sending disco#info to %s", j.C.Jabber.Server)
|
||||||
|
|
||||||
|
_, err = j.Talk.DiscoverInfo(j.Talk.JID(), j.C.Jabber.Server)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to send disco#info to jabber server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProbeServerLiveness проверяет живость соединения с сервером. Для многих серверов обязательная штука, без которой
|
||||||
|
// они выкидывают (дисконнектят) клиента через некоторое время неактивности.
|
||||||
|
func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
||||||
|
for {
|
||||||
|
for {
|
||||||
|
if j.Shutdown {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sleepTime := time.Duration(j.C.Jabber.ServerPingDelay) * 1000 * time.Millisecond
|
||||||
|
sleepTime += time.Duration(rand.Int63n(1000*j.C.Jabber.PingSplayDelay)) * time.Millisecond
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
|
||||||
|
if !j.IsConnected {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay,
|
||||||
|
// в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay
|
||||||
|
// if (time.Now().Unix() - lastServerActivity) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
if j.ServerCapsQueried { // Сервер ответил на disco#info
|
||||||
|
var (
|
||||||
|
value interface{}
|
||||||
|
exist bool
|
||||||
|
)
|
||||||
|
|
||||||
|
value, exist = j.ServerCapsList.Get("urn:xmpp:ping")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// Сервер анонсировал, что умеет в c2s пинги
|
||||||
|
case exist && value.(bool):
|
||||||
|
// Таймаут c2s пинга. Возьмём сумму задержки между пингами, добавим таймаут коннекта и добавим
|
||||||
|
// максимальную корректировку разброса.
|
||||||
|
txTimeout := j.C.Jabber.ServerPingDelay + j.C.Jabber.ConnectionTimeout
|
||||||
|
txTimeout += j.C.Jabber.PingSplayDelay
|
||||||
|
rxTimeout := txTimeout
|
||||||
|
|
||||||
|
rxTimeAgo := time.Now().Unix() - j.ServerPingTimestampRx
|
||||||
|
|
||||||
|
if j.ServerPingTimestampTx > 0 { // Первая пуля от нас ушла...
|
||||||
|
switch {
|
||||||
|
// Давненько мы не получали понгов от сервера, вероятно, соединение с сервером утеряно?
|
||||||
|
case rxTimeAgo > (rxTimeout * 2):
|
||||||
|
err := fmt.Errorf( //nolint:goerr113
|
||||||
|
"stall connection detected. No c2s pong for %d seconds",
|
||||||
|
rxTimeAgo,
|
||||||
|
)
|
||||||
|
|
||||||
|
return err
|
||||||
|
|
||||||
|
// По-умолчанию, мы отправляем c2s пинг
|
||||||
|
default:
|
||||||
|
log.Debugf("Sending c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server)
|
||||||
|
|
||||||
|
if err := j.Talk.PingC2S(j.Talk.JID(), j.C.Jabber.Server); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
j.ServerPingTimestampTx = time.Now().Unix()
|
||||||
|
}
|
||||||
|
} else { // Первая пуля пока не вылетела, отправляем
|
||||||
|
log.Debugf("Sending first c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server)
|
||||||
|
|
||||||
|
if err := j.Talk.PingC2S(j.Talk.JID(), j.C.Jabber.Server); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
j.ServerPingTimestampTx = time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сервер не анонсировал, что умеет в c2s пинги
|
||||||
|
default:
|
||||||
|
log.Debug("Sending keepalive whitespace ping")
|
||||||
|
|
||||||
|
if _, err := j.Talk.SendKeepAlive(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // Сервер не ответил на disco#info
|
||||||
|
log.Debug("Sending keepalive whitespace ping")
|
||||||
|
|
||||||
|
if _, err := j.Talk.SendKeepAlive(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProbeMUCLiveness Пингует MUC-и, нужно для проверки, что клиент ещё находится в MUC-е.
|
||||||
|
func (j *Jabber) ProbeMUCLiveness() { //nolint:gocognit
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for {
|
||||||
|
for _, room := range j.RoomsConnected {
|
||||||
|
var (
|
||||||
|
exist bool
|
||||||
|
lastActivityTS interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Если записи про комнату нету, то пинговать её бессмысленно.
|
||||||
|
if lastActivityTS, exist = j.LastMucActivity.Get(room); !exist {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если время последней активности в чятике не превысило
|
||||||
|
// j.C.Jabber.ServerPingDelay + j.C.Jabber.PingSplayDelay, ничего не пингуем.
|
||||||
|
if (time.Now().Unix() - lastActivityTS.(int64)) < (j.C.Jabber.ServerPingDelay + j.C.Jabber.PingSplayDelay) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Пинг MUC-а по сценарию без серверной оптимизации мы реализовывать не будем. Это как-то не надёжно.
|
||||||
|
j.GTomb.Go(
|
||||||
|
func() {
|
||||||
|
// Небольшая рандомная задержка перед пингом комнаты
|
||||||
|
sleepTime := time.Duration(rand.Int63n(1000*j.C.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
|
||||||
|
if e := j.Talk.PingS2S(talk.JID(), room+"/"+j.GetBotNickFromRoomConfig(room)); e != nil {
|
||||||
|
err = e
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
var roomMap interface{}
|
||||||
|
|
||||||
|
roomMap, exist = j.MucCapsList.Get(room)
|
||||||
|
|
||||||
|
// Пинги комнаты проводим, только если она записана, как прошедшая disco#info и поддерживающая
|
||||||
|
// Server Optimization.
|
||||||
|
if exist && roomMap.(map[string]bool)["http://jabber.org/protocol/muc#self-ping-optimization"] {
|
||||||
|
j.GTomb.Go(
|
||||||
|
func() error {
|
||||||
|
// Небольшая рандомная задержка перед пингом комнаты.
|
||||||
|
sleepTime := time.Duration(rand.Int63n(1000*j.C.Jabber.PingSplayDelay)) * time.Millisecond
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
|
||||||
|
log.Debugf("Sending MUC ping from %s to %s", j.Talk.JID(), room)
|
||||||
|
|
||||||
|
if e := j.Talk.PingS2S(j.Talk.JID(), room); e != nil {
|
||||||
|
err = fmt.Errorf("unable to ping MUC %s: %w", room, e)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(j.C.Jabber.MucPingDelay) * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
961
lib.go
961
lib.go
@@ -1,961 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/eleksir/go-xmpp"
|
|
||||||
"github.com/hjson/hjson-go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
// sigHandler Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения.
|
|
||||||
func sigHandler() {
|
|
||||||
log.Debug("Installing signal handler")
|
|
||||||
|
|
||||||
for s := range sigChan {
|
|
||||||
switch s {
|
|
||||||
case syscall.SIGINT:
|
|
||||||
log.Infoln("Got SIGINT, quitting")
|
|
||||||
case syscall.SIGTERM:
|
|
||||||
log.Infoln("Got SIGTERM, quitting")
|
|
||||||
case syscall.SIGQUIT:
|
|
||||||
log.Infoln("Got SIGQUIT, quitting")
|
|
||||||
|
|
||||||
// Заходим на новую итерацию, если у нас "неинтересный" сигнал.
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Чтобы не срать в логи ошибками, проставим shutdown state приложения в true.
|
|
||||||
shutdown = true
|
|
||||||
|
|
||||||
if isConnected && !shutdown {
|
|
||||||
log.Debug("Try to set our presence to Unavailable and status to Offline")
|
|
||||||
|
|
||||||
// Вот тут понадобится коллекция известных пользователей, чтобы им разослать presence, что бот свалил в offline
|
|
||||||
// Пока за неимением лучшего сообщим об этом самим себе.
|
|
||||||
for _, room := range roomsConnected {
|
|
||||||
if _, err := talk.SendPresence(
|
|
||||||
xmpp.Presence{ //nolint:exhaustruct
|
|
||||||
To: room,
|
|
||||||
Status: "Offline",
|
|
||||||
Type: "unavailable",
|
|
||||||
},
|
|
||||||
); err != nil {
|
|
||||||
log.Infof("Unable to send presence to jabber server: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// И закрываем соединение.
|
|
||||||
log.Debugf("Closing connection to jabber server")
|
|
||||||
|
|
||||||
if err := talk.Close(); err != nil {
|
|
||||||
log.Infof("Unable to close connection to jabber server: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// readConfig читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге
|
|
||||||
// нет.
|
|
||||||
func readConfig() error { //nolint:gocognit,gocyclo
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
executablePath string
|
|
||||||
configLoaded = false
|
|
||||||
)
|
|
||||||
|
|
||||||
executablePath, err = os.Executable()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err := fmt.Errorf("unable to get current executable path: %w", err)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
|
||||||
|
|
||||||
locations := []string{
|
|
||||||
"~/.bunyPresense-jabber-bot.json",
|
|
||||||
"~/bunyPresense-jabber-bot.json",
|
|
||||||
"/etc/bunyPresense-jabber-bot.json",
|
|
||||||
configJSONPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, location := range locations {
|
|
||||||
fileInfo, err := os.Stat(location)
|
|
||||||
|
|
||||||
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём.
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата.
|
|
||||||
if fileInfo.Size() > 65535 {
|
|
||||||
log.Warnf("Config file %s is too long for config, skipping", location)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := os.ReadFile(location)
|
|
||||||
|
|
||||||
// Не удалось прочитать, попробуем следующего кандидата.
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Skip reading config file %s: %s", location, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
|
||||||
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
|
||||||
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
|
||||||
var (
|
|
||||||
sampleConfig myConfig
|
|
||||||
tmp map[string]interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
err = hjson.Unmarshal(buf, &tmp)
|
|
||||||
|
|
||||||
// Не удалось распарсить - попробуем следующего кандидата.
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpJSON, err := json.Marshal(tmp)
|
|
||||||
|
|
||||||
// Не удалось преобразовать map-ку в json
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(tmpJSON, &sampleConfig); err != nil {
|
|
||||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Значения для Jabber-клиента
|
|
||||||
if sampleConfig.Jabber.Server == "" {
|
|
||||||
log.Error("Jabber server is not defined in config, using localhost")
|
|
||||||
sampleConfig.Jabber.Server = "localhost" //nolint:wsl
|
|
||||||
}
|
|
||||||
|
|
||||||
if sampleConfig.Jabber.Port == 0 {
|
|
||||||
sampleConfig.Jabber.Port = 5222
|
|
||||||
|
|
||||||
if sampleConfig.Jabber.Ssl {
|
|
||||||
if !sampleConfig.Jabber.StartTLS {
|
|
||||||
sampleConfig.Jabber.Port = 5223
|
|
||||||
|
|
||||||
log.Info("Jabber port is not defined in config, using 5223")
|
|
||||||
} else {
|
|
||||||
log.Info("Jabber port is not defined in config, using 5222")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sampleConfig.Jabber.Ssl {
|
|
||||||
sampleConfig.Jabber.StartTLS = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sampleConfig.Jabber.Ssl || !sampleConfig.Jabber.StartTLS {
|
|
||||||
sampleConfig.Jabber.SslVerify = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан
|
|
||||||
|
|
||||||
if sampleConfig.Jabber.ConnectionTimeout == 0 {
|
|
||||||
sampleConfig.Jabber.ConnectionTimeout = 10
|
|
||||||
|
|
||||||
log.Info("Jabber server connection_timeout not defined in config, using 10 seconds")
|
|
||||||
}
|
|
||||||
|
|
||||||
if sampleConfig.Jabber.ReconnectDelay == 0 {
|
|
||||||
sampleConfig.Jabber.ReconnectDelay = 3
|
|
||||||
|
|
||||||
log.Info("Jabber server reconnect_delay not defined in config, using 3 seconds")
|
|
||||||
}
|
|
||||||
|
|
||||||
if sampleConfig.Jabber.ServerPingDelay == 0 {
|
|
||||||
sampleConfig.Jabber.ServerPingDelay = 60
|
|
||||||
|
|
||||||
log.Info("Jabber server_ping_delay not defined in config, using 60 seconds")
|
|
||||||
}
|
|
||||||
|
|
||||||
if sampleConfig.Jabber.MucPingDelay == 0 {
|
|
||||||
sampleConfig.Jabber.MucPingDelay = 900
|
|
||||||
|
|
||||||
log.Info("Jabber muc_ping_delay not defined in config, using 900 seconds")
|
|
||||||
}
|
|
||||||
|
|
||||||
if sampleConfig.Jabber.MucRejoinDelay == 0 {
|
|
||||||
sampleConfig.Jabber.MucRejoinDelay = 3
|
|
||||||
|
|
||||||
log.Info("Jabber muc_rejoin_delay not defined in config, using 3 seconds")
|
|
||||||
}
|
|
||||||
|
|
||||||
if sampleConfig.Jabber.PingSplayDelay == 0 {
|
|
||||||
sampleConfig.Jabber.PingSplayDelay = 3
|
|
||||||
|
|
||||||
log.Info("Jabber ping_splay_delay not defined in config, using 3 seconds")
|
|
||||||
}
|
|
||||||
|
|
||||||
if sampleConfig.Jabber.Nick == "" {
|
|
||||||
return errors.New("jabber nick is not defined in config, quitting") //nolint:goerr113
|
|
||||||
}
|
|
||||||
|
|
||||||
if sampleConfig.Jabber.Resource == "" {
|
|
||||||
sampleConfig.Jabber.Resource = "bunyPresense bot"
|
|
||||||
|
|
||||||
log.Info("Jabber resource not defined in config, using bunyPresense bot")
|
|
||||||
}
|
|
||||||
|
|
||||||
if sampleConfig.Jabber.User == "" {
|
|
||||||
sampleConfig.Jabber.User = fmt.Sprintf("%s@%s", sampleConfig.Jabber.Nick, sampleConfig.Jabber.Server)
|
|
||||||
|
|
||||||
log.Infof("Jabber user not defined in config, guessing, it can be %s", sampleConfig.Jabber.User)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если sampleConfig.Jabber.Password не задан, то авторизации не будет
|
|
||||||
|
|
||||||
// Если не задано ни одного мастера, то бот сам себе мастер
|
|
||||||
if len(sampleConfig.Jabber.BotMasters) == 0 {
|
|
||||||
sampleConfig.Jabber.BotMasters[0] = sampleConfig.Jabber.User
|
|
||||||
}
|
|
||||||
|
|
||||||
// Нам бот нужен в каких-то чат-румах, а не "просто так"
|
|
||||||
if len(sampleConfig.Jabber.Channels) < 1 {
|
|
||||||
return errors.New("no jabber channels/rooms defined in config, quitting") //nolint:goerr113
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, channel := range sampleConfig.Jabber.Channels {
|
|
||||||
if channel.Name == "" {
|
|
||||||
return errors.New("no \"name\" entry in jabber channel config")
|
|
||||||
}
|
|
||||||
|
|
||||||
// channel.Password может быть пустым, тогда пароля нет
|
|
||||||
// channel.Bayes.Enabled будет false, если не проставлен
|
|
||||||
|
|
||||||
if channel.Bayes.MinLength < 40 {
|
|
||||||
channel.Bayes.MinLength = 40
|
|
||||||
}
|
|
||||||
|
|
||||||
if channel.Bayes.MinWords < 8 {
|
|
||||||
channel.Bayes.MinWords = 8
|
|
||||||
}
|
|
||||||
|
|
||||||
switch channel.Bayes.DefaultAction {
|
|
||||||
case "kick":
|
|
||||||
case "ban":
|
|
||||||
case "devoice":
|
|
||||||
default:
|
|
||||||
channel.Bayes.DefaultAction = "log"
|
|
||||||
}
|
|
||||||
|
|
||||||
// channel.AllCaps.Enabled будет false, если не указан
|
|
||||||
if channel.AllCaps.MinLength < 10 {
|
|
||||||
channel.AllCaps.MinLength = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
switch channel.AllCaps.DefaultAction {
|
|
||||||
case "kick":
|
|
||||||
case "ban":
|
|
||||||
case "devoice":
|
|
||||||
default:
|
|
||||||
channel.AllCaps.DefaultAction = "log"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если список фраз с которыми стартует бот пустой, вносим в него 1 запись с пустой строкой
|
|
||||||
if len(sampleConfig.Jabber.StartupStatus) == 0 {
|
|
||||||
sampleConfig.Jabber.StartupStatus[0] = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если список статусов, с которыми работает бот пустой, вносим в него 1 запись с пустой строкой
|
|
||||||
if len(sampleConfig.Jabber.RuntimeStatus.Text) == 0 {
|
|
||||||
sampleConfig.Jabber.RuntimeStatus.Text[0] = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если sampleConfig.Jabber.RuntimeStatus.RotationTime не задан, то он равен 0
|
|
||||||
// Если sampleConfig.Jabber.RuntimeStatus.RotationSplayTime не задан, то он равен 0
|
|
||||||
|
|
||||||
// Если sampleConfig.Jabber.BanDelay не задан, то он равен 0
|
|
||||||
// Если sampleConfig.Jabber.BanPhrasesEnable не задан, то он false
|
|
||||||
|
|
||||||
// Если список фраз, с которыми банят пустой, то вносим в него одну позицию с пустой строкой
|
|
||||||
if len(sampleConfig.Jabber.BanPhrases) == 0 {
|
|
||||||
sampleConfig.Jabber.BanPhrases[0] = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if sampleConfig.CSign == "" {
|
|
||||||
sampleConfig.CSign = "!"
|
|
||||||
}
|
|
||||||
|
|
||||||
if sampleConfig.Loglevel == "" {
|
|
||||||
sampleConfig.Loglevel = "info"
|
|
||||||
|
|
||||||
log.Info("loglevel not defined in config, using info")
|
|
||||||
}
|
|
||||||
|
|
||||||
// sampleConfig.Log = "" if not set
|
|
||||||
|
|
||||||
config = sampleConfig
|
|
||||||
configLoaded = true
|
|
||||||
log.Infof("Using %s as config file", location) //nolint:wsl
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !configLoaded {
|
|
||||||
return errors.New("config was not loaded!") //nolint:goerr113
|
|
||||||
}
|
|
||||||
|
|
||||||
return err //nolint:wrapcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
// readWhitelist читает и валидирует белые списки пользователей.
|
|
||||||
func readWhitelist() error {
|
|
||||||
var (
|
|
||||||
whitelistLoaded = false
|
|
||||||
err error
|
|
||||||
executablePath string
|
|
||||||
)
|
|
||||||
|
|
||||||
executablePath, err = os.Executable()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("unable to get current executable path: %w", err)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
whitelistJSONPath := fmt.Sprintf("%s/data/whitelist.json", filepath.Dir(executablePath))
|
|
||||||
|
|
||||||
locations := []string{
|
|
||||||
"~/.bunyPresense-jabber-bot-whitelist.json",
|
|
||||||
"~/bunyPresense-jabber-bot-whitelist.json",
|
|
||||||
"/etc/bunyPresense-jabber-bot-whitelist.json",
|
|
||||||
whitelistJSONPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, location := range locations {
|
|
||||||
fileInfo, err := os.Stat(location)
|
|
||||||
|
|
||||||
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Файл белого списка длинноват для белого списка, попробуем следующего кандидата
|
|
||||||
if fileInfo.Size() > 2097152 {
|
|
||||||
log.Warnf("Whitelist file %s is too long for whitelist, skipping", location)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := os.ReadFile(location)
|
|
||||||
|
|
||||||
// Не удалось прочитать, попробуем следующего кандидата
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Skip reading whitelist file %s: %s", location, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
|
||||||
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
|
||||||
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
|
||||||
var (
|
|
||||||
sampleWhitelist myWhiteList
|
|
||||||
tmp map[string]interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
err = hjson.Unmarshal(buf, &tmp)
|
|
||||||
|
|
||||||
// Не удалось распарсить - попробуем следующего кандидата
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpJSON, err := json.Marshal(tmp)
|
|
||||||
|
|
||||||
// Не удалось преобразовать map-ку в json
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(tmpJSON, &sampleWhitelist); err != nil {
|
|
||||||
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
whiteList = sampleWhitelist
|
|
||||||
whitelistLoaded = true
|
|
||||||
|
|
||||||
log.Infof("Using %s as whiteList file", location)
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !whitelistLoaded {
|
|
||||||
return errors.New("whitelist was not loaded!") //nolint:goerr113
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// readBlacklist читает и валидирует чёрные списки пользователей.
|
|
||||||
func readBlacklist() error {
|
|
||||||
var (
|
|
||||||
blacklistLoaded = false
|
|
||||||
err error
|
|
||||||
executablePath string
|
|
||||||
)
|
|
||||||
|
|
||||||
executablePath, err = os.Executable()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("unable to get current executable path: %w", err)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
whitelistJSONPath := fmt.Sprintf("%s/data/blacklist.json", filepath.Dir(executablePath))
|
|
||||||
|
|
||||||
locations := []string{
|
|
||||||
"~/.bunyPresense-jabber-bot-blacklist.json",
|
|
||||||
"~/bunyPresense-jabber-bot-blacklist.json",
|
|
||||||
"/etc/bunyPresense-jabber-bot-blacklist.json",
|
|
||||||
whitelistJSONPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, location := range locations {
|
|
||||||
fileInfo, err := os.Stat(location)
|
|
||||||
|
|
||||||
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Файл чёрного списка длинноват для чёрного списка, попробуем следующего кандидата
|
|
||||||
if fileInfo.Size() > 16777216 {
|
|
||||||
log.Warnf("Blacklist file %s is too long for blacklist, skipping", location)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := os.ReadFile(location)
|
|
||||||
|
|
||||||
// Не удалось прочитать, попробуем следующего кандидата
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Skip reading blacklist file %s: %s", location, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
|
||||||
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
|
||||||
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
|
||||||
var (
|
|
||||||
sampleBlacklist myBlackList
|
|
||||||
tmp map[string]interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
err = hjson.Unmarshal(buf, &tmp)
|
|
||||||
|
|
||||||
// Не удалось распарсить - попробуем следующего кандидата
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Skip parsing blacklist file %s: %s", location, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpJSON, err := json.Marshal(tmp)
|
|
||||||
|
|
||||||
// Не удалось преобразовать map-ку в json
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Skip parsing blacklist file %s: %s", location, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(tmpJSON, &sampleBlacklist); err != nil {
|
|
||||||
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
blackList = sampleBlacklist
|
|
||||||
blacklistLoaded = true
|
|
||||||
|
|
||||||
log.Infof("Using %s as blacklist file", location)
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !blacklistLoaded {
|
|
||||||
return errors.New("blacklist was not loaded!")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// establishConnection устанавливает соединение с jabber-сервером.
|
|
||||||
func establishConnection() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if connecting && !isConnected {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проставляем глобальные переменные.
|
|
||||||
connecting = true
|
|
||||||
isConnected = false
|
|
||||||
roomsConnected = make([]string, 0)
|
|
||||||
|
|
||||||
log.Debugf("Establishing connection to %s", options.Host)
|
|
||||||
talk, err = options.NewClient()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// По идее keepalive должен же проходить только, если мы уже на сервере, так?
|
|
||||||
if _, err := talk.SendKeepAlive(); err != nil {
|
|
||||||
log.Errorf("Try to send initial KeepAlive, got error: %s", err)
|
|
||||||
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Connected")
|
|
||||||
|
|
||||||
// Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили
|
|
||||||
for _, roomStruct := range config.Jabber.Channels {
|
|
||||||
room := roomStruct.Name
|
|
||||||
go joinMuc(room)
|
|
||||||
}
|
|
||||||
|
|
||||||
go RotateStatus("")
|
|
||||||
|
|
||||||
lastActivity = time.Now().Unix()
|
|
||||||
connecting = false
|
|
||||||
isConnected = true
|
|
||||||
|
|
||||||
log.Debugf("Sending disco#info to %s", config.Jabber.Server)
|
|
||||||
|
|
||||||
_, err = talk.DiscoverInfo(talk.JID(), config.Jabber.Server)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Infof("Unable to send disco#info to jabber server: %s", err)
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// joinMu джойнится к конференциям/каналам/комнатам в джаббере.
|
|
||||||
func joinMuc(room string) {
|
|
||||||
log.Debugf("Sending disco#info from %s to %s", talk.JID(), room)
|
|
||||||
|
|
||||||
if _, err := talk.DiscoverInfo(talk.JID(), room); err != nil {
|
|
||||||
log.Infof("Unable to send disco#info to MUC %s: %s", room, err)
|
|
||||||
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ждём, пока muc нам вернёт список фичей.
|
|
||||||
for i := 0; i < (20 * int(config.Jabber.ConnectionTimeout)); i++ {
|
|
||||||
var (
|
|
||||||
myRoom interface{}
|
|
||||||
supported bool
|
|
||||||
exist bool
|
|
||||||
)
|
|
||||||
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
|
|
||||||
if myRoom, exist = mucCapsList.Get(room); !exist {
|
|
||||||
// Пока не задискаверилась
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if supported, exist = myRoom.(map[string]bool)["muc_unsecured"]; exist {
|
|
||||||
if supported {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Unable to join to password-protected room. Don't know how to enter passwords :)")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := talk.JoinMUCNoHistory(room, config.Jabber.Nick); err != nil {
|
|
||||||
log.Errorf("Unable to join to MUC: %s", room)
|
|
||||||
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Joining to MUC: %s", room)
|
|
||||||
|
|
||||||
// Ждём, когда прилетит presence из комнаты, тогда мы точно знаем, что мы вошли.
|
|
||||||
entered := false
|
|
||||||
|
|
||||||
for i := 0; i < (20 * int(config.Jabber.ConnectionTimeout)); i++ {
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
|
|
||||||
if slices.Contains(roomsConnected, room) {
|
|
||||||
entered = true
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !entered {
|
|
||||||
log.Errorf(
|
|
||||||
"Unable to enter to MUC %s, join timeout after %d seconds (server does not return my presence for this room)",
|
|
||||||
room,
|
|
||||||
20*int(config.Jabber.ConnectionTimeout)+1,
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Вот теперь точно можно слать статус.
|
|
||||||
log.Infof("Joined to MUC: %s", room)
|
|
||||||
|
|
||||||
go RotateStatus(room)
|
|
||||||
|
|
||||||
// Время проверить участников на предмет злобности
|
|
||||||
namesInterface, present := roomPresences.Get(room)
|
|
||||||
|
|
||||||
// Если room есть в списке presence-ов, то фигачим. room там должен быть, просто обязан.
|
|
||||||
if present {
|
|
||||||
for _, name := range interfaceToStringSlice(namesInterface) {
|
|
||||||
var v xmpp.Presence
|
|
||||||
|
|
||||||
_ = json.Unmarshal([]byte(name), &v)
|
|
||||||
log.Infof("Fake presence forged for %s just for on-enter check", name)
|
|
||||||
// Оно там внутри всё само обработает, если вдруг возникнет wire error, то зарекконетится.
|
|
||||||
_ = bunyPresense(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// probeServerLiveness проверяет живость соединения с сервером. Для многих серверов обязательная штука, без которой
|
|
||||||
// они выкидывают клиента через некоторое время неактивности.
|
|
||||||
func probeServerLiveness() { //nolint:gocognit
|
|
||||||
defer gTomb.Done()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-gTomb.Dying():
|
|
||||||
return
|
|
||||||
|
|
||||||
default:
|
|
||||||
for {
|
|
||||||
if shutdown {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sleepTime := time.Duration(config.Jabber.ServerPingDelay) * 1000 * time.Millisecond
|
|
||||||
sleepTime += time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond
|
|
||||||
time.Sleep(sleepTime)
|
|
||||||
|
|
||||||
if !isConnected {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay,
|
|
||||||
// в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay
|
|
||||||
// if (time.Now().Unix() - lastServerActivity) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
|
|
||||||
if serverCapsQueried { // Сервер ответил на disco#info
|
|
||||||
var (
|
|
||||||
value interface{}
|
|
||||||
exist bool
|
|
||||||
)
|
|
||||||
|
|
||||||
value, exist = serverCapsList.Get("urn:xmpp:ping")
|
|
||||||
|
|
||||||
switch {
|
|
||||||
// Сервер анонсировал, что умеет в c2s пинги
|
|
||||||
case exist && value.(bool):
|
|
||||||
// Таймаут c2s пинга. Возьмём сумму задержки между пингами, добавим таймаут коннекта и добавим
|
|
||||||
// максимальную корректировку разброса.
|
|
||||||
txTimeout := config.Jabber.ServerPingDelay + config.Jabber.ConnectionTimeout
|
|
||||||
txTimeout += config.Jabber.PingSplayDelay
|
|
||||||
rxTimeout := txTimeout
|
|
||||||
|
|
||||||
rxTimeAgo := time.Now().Unix() - serverPingTimestampRx
|
|
||||||
|
|
||||||
if serverPingTimestampTx > 0 { // Первая пуля от нас ушла...
|
|
||||||
switch {
|
|
||||||
// Давненько мы не получали понгов от сервера, вероятно, соединение с сервером утеряно?
|
|
||||||
case rxTimeAgo > (rxTimeout * 2):
|
|
||||||
err := fmt.Errorf( //nolint:goerr113
|
|
||||||
"stall connection detected. No c2s pong for %d seconds",
|
|
||||||
rxTimeAgo,
|
|
||||||
)
|
|
||||||
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
// По-умолчанию, мы отправляем c2s пинг
|
|
||||||
default:
|
|
||||||
log.Debugf("Sending c2s ping from %s to %s", talk.JID(), config.Jabber.Server)
|
|
||||||
|
|
||||||
if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil {
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
serverPingTimestampTx = time.Now().Unix()
|
|
||||||
}
|
|
||||||
} else { // Первая пуля пока не вылетела, отправляем
|
|
||||||
log.Debugf("Sending first c2s ping from %s to %s", talk.JID(), config.Jabber.Server)
|
|
||||||
|
|
||||||
if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil {
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
serverPingTimestampTx = time.Now().Unix()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сервер не анонсировал, что умеет в c2s пинги
|
|
||||||
default:
|
|
||||||
log.Debug("Sending keepalive whitespace ping")
|
|
||||||
|
|
||||||
if _, err := talk.SendKeepAlive(); err != nil {
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // Сервер не ответил на disco#info
|
|
||||||
log.Debug("Sending keepalive whitespace ping")
|
|
||||||
|
|
||||||
if _, err := talk.SendKeepAlive(); err != nil {
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// probeMUCLiveness Пингует MUC-и, нужно для проверки, что клиент ещё находится в MUC-е.
|
|
||||||
func probeMUCLiveness() { //nolint:gocognit
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-gTomb.Dying():
|
|
||||||
return
|
|
||||||
|
|
||||||
default:
|
|
||||||
for {
|
|
||||||
for _, room := range roomsConnected {
|
|
||||||
var (
|
|
||||||
exist bool
|
|
||||||
lastActivityTS interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Если записи про комнату нету, то пинговать её бессмысленно.
|
|
||||||
if lastActivityTS, exist = lastMucActivity.Get(room); !exist {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если время последней активности в чятике не превысило
|
|
||||||
// config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay, ничего не пингуем.
|
|
||||||
if (time.Now().Unix() - lastActivityTS.(int64)) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Пинг MUC-а по сценарию без серверной оптимизации мы реализовывать не будем. Это как-то не надёжно.
|
|
||||||
go func(room string) {
|
|
||||||
// Небольшая рандомная задержка перед пингом комнаты
|
|
||||||
sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec
|
|
||||||
time.Sleep(sleepTime)
|
|
||||||
|
|
||||||
if err := talk.PingS2S(talk.JID(), room+"/"+config.Jabber.Nick); err != nil {
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}(room)
|
|
||||||
*/
|
|
||||||
|
|
||||||
var roomMap interface{}
|
|
||||||
|
|
||||||
roomMap, exist = mucCapsList.Get(room)
|
|
||||||
|
|
||||||
// Пинги комнаты проводим, только если она записана, как прошедшая disco#info и поддерживающая
|
|
||||||
// Server Optimization.
|
|
||||||
if exist && roomMap.(map[string]bool)["http://jabber.org/protocol/muc#self-ping-optimization"] {
|
|
||||||
go func(room string) {
|
|
||||||
// Небольшая рандомная задержка перед пингом комнаты.
|
|
||||||
sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond
|
|
||||||
time.Sleep(sleepTime)
|
|
||||||
|
|
||||||
log.Debugf("Sending MUC ping from %s to %s", talk.JID(), room)
|
|
||||||
|
|
||||||
if err := talk.PingS2S(talk.JID(), room); err != nil {
|
|
||||||
err := fmt.Errorf("unable to ping MUC %s: %w", room, err)
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}(room)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Duration(config.Jabber.MucPingDelay) * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RotateStatus периодически изменяет статус бота в MUC-е согласно настройкам из кофига.
|
|
||||||
func RotateStatus(room string) {
|
|
||||||
defer gTomb.Done()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-gTomb.Dying():
|
|
||||||
return
|
|
||||||
|
|
||||||
default:
|
|
||||||
// TODO: Переделать на ticker-ы
|
|
||||||
totalSleepTime := time.Duration(config.Jabber.RuntimeStatus.RotationTime) * time.Second
|
|
||||||
totalSleepTime += time.Duration(config.Jabber.RuntimeStatus.RotationSplayTime) * time.Second
|
|
||||||
|
|
||||||
for {
|
|
||||||
status := randomPhrase(config.Jabber.RuntimeStatus.Text)
|
|
||||||
log.Debugf("Set status for MUC: %s to: %s", room, status)
|
|
||||||
|
|
||||||
var p xmpp.Presence
|
|
||||||
|
|
||||||
if room != "" {
|
|
||||||
p = xmpp.Presence{ //nolint:exhaustruct
|
|
||||||
To: room,
|
|
||||||
Status: status,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p = xmpp.Presence{ //nolint:exhaustruct
|
|
||||||
Status: status,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := talk.SendPresence(p); err != nil {
|
|
||||||
log.Infof("Unable to send presence to MUC %s: %s", room, err)
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если мы не хотим ротировать, то цикл нам тут не нужен, просто выходим.
|
|
||||||
if config.Jabber.RuntimeStatus.RotationTime == 0 {
|
|
||||||
gTomb.Done()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(totalSleepTime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// randomPhrase Выдаёт одну рандомную фразу из даденного списка фраз.
|
|
||||||
func randomPhrase(list []string) string {
|
|
||||||
phrase := ""
|
|
||||||
|
|
||||||
if listLen := len(list); listLen > 0 {
|
|
||||||
phrase = list[rand.Intn(listLen)]
|
|
||||||
}
|
|
||||||
|
|
||||||
return phrase
|
|
||||||
}
|
|
||||||
|
|
||||||
// interfaceToStringSlice превращает данный интерфейс в слайс строк.
|
|
||||||
// Если может, конечно :) .
|
|
||||||
func interfaceToStringSlice(iface interface{}) []string {
|
|
||||||
var mySlice []string
|
|
||||||
|
|
||||||
// А теперь мы начинаем дурдом, нам надо превратить ёбанный interface{} в []string
|
|
||||||
// Поскольку interface{} может быть чем угодно, перестрахуемся
|
|
||||||
if reflect.TypeOf(iface).Kind() == reflect.Slice {
|
|
||||||
shit := reflect.ValueOf(iface)
|
|
||||||
|
|
||||||
for i := 0; i < shit.Len(); i++ {
|
|
||||||
mySlice = append(mySlice, fmt.Sprint(shit.Index(i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mySlice
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRealJIDfromNick достаёт из запомненных presence-ов по даденному nick-у real jid с resource-ом. Nick должен
|
|
||||||
// содержать имя конфы, откуда участник.
|
|
||||||
func getRealJIDfromNick(fullNick string) string {
|
|
||||||
var p xmpp.Presence
|
|
||||||
|
|
||||||
room := (strings.SplitN(fullNick, "/", 2))[0]
|
|
||||||
|
|
||||||
// Достанем presence участника
|
|
||||||
presenceJSONInterface, present := roomPresences.Get(room)
|
|
||||||
|
|
||||||
// Никого нет дома
|
|
||||||
if !present {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
presenceJSONStrings := interfaceToStringSlice(presenceJSONInterface)
|
|
||||||
|
|
||||||
for _, presepresenceJSONString := range presenceJSONStrings {
|
|
||||||
_ = json.Unmarshal([]byte(presepresenceJSONString), &p)
|
|
||||||
|
|
||||||
if p.From == fullNick {
|
|
||||||
return p.JID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
|
||||||
211
main.go
211
main.go
@@ -1,211 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/eleksir/go-xmpp"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gopkg.in/tomb.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.SetFormatter(&log.TextFormatter{ //nolint:exhaustruct
|
|
||||||
DisableQuote: true,
|
|
||||||
DisableLevelTruncation: false,
|
|
||||||
DisableColors: true,
|
|
||||||
FullTimestamp: true,
|
|
||||||
TimestampFormat: "2006-01-02 15:04:05",
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := readConfig(); err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// no panic
|
|
||||||
switch config.Loglevel {
|
|
||||||
case "fatal":
|
|
||||||
log.SetLevel(log.FatalLevel)
|
|
||||||
case "error":
|
|
||||||
log.SetLevel(log.ErrorLevel)
|
|
||||||
case "warn":
|
|
||||||
log.SetLevel(log.WarnLevel)
|
|
||||||
case "info":
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
case "debug":
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
case "trace":
|
|
||||||
log.SetLevel(log.TraceLevel)
|
|
||||||
default:
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Откроем лог и скормим его логгеру
|
|
||||||
if config.Log != "" {
|
|
||||||
logfile, err := os.OpenFile(config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Unable to open log file %s: %s", config.Log, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetOutput(logfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
myLogLevel := log.GetLevel()
|
|
||||||
log.Warnf("Loglevel set to %v", myLogLevel)
|
|
||||||
|
|
||||||
verboseClient := false
|
|
||||||
|
|
||||||
if myLogLevel == log.TraceLevel {
|
|
||||||
verboseClient = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Самое время вгрузить белые и чёрные списки, если что - получим ошибку в лог.
|
|
||||||
if err := readWhitelist(); err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := readBlacklist(); err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// github.com/mattn/go-xmpp пишет в stdio, нам этого не надо, ловим выхлоп его в logrus с уровнем trace
|
|
||||||
xmpp.DebugWriter = log.WithFields(log.Fields{"logger": "stdlib"}).WriterLevel(log.TraceLevel)
|
|
||||||
|
|
||||||
// Хэндлер сигналов не надо трогать, он нужен для завершения программы целиком
|
|
||||||
go sigHandler()
|
|
||||||
signal.Notify(sigChan, os.Interrupt)
|
|
||||||
|
|
||||||
for {
|
|
||||||
options = &xmpp.Options{ //nolint:exhaustruct
|
|
||||||
Host: fmt.Sprintf("%s:%d", config.Jabber.Server, config.Jabber.Port),
|
|
||||||
User: config.Jabber.User,
|
|
||||||
Password: config.Jabber.Password,
|
|
||||||
Resource: config.Jabber.Resource,
|
|
||||||
NoTLS: !config.Jabber.Ssl,
|
|
||||||
StartTLS: config.Jabber.StartTLS,
|
|
||||||
TLSConfig: &tls.Config{ //nolint:exhaustruct
|
|
||||||
ServerName: config.Jabber.Server,
|
|
||||||
InsecureSkipVerify: !config.Jabber.SslVerify, //nolint:gosec
|
|
||||||
},
|
|
||||||
InsecureAllowUnencryptedAuth: config.Jabber.InsecureAllowUnencryptedAuth,
|
|
||||||
Debug: verboseClient,
|
|
||||||
Session: false,
|
|
||||||
Status: "xa",
|
|
||||||
StatusMessage: randomPhrase(config.Jabber.StartupStatus),
|
|
||||||
DialTimeout: time.Duration(config.Jabber.ConnectionTimeout) * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Через tomb попробуем сделать выход горутинок управляемым
|
|
||||||
gTomb = tomb.Tomb{}
|
|
||||||
|
|
||||||
// Устанавливаем соединение и гребём события, посылаемые сервером
|
|
||||||
myLoop()
|
|
||||||
|
|
||||||
log.Error(gTomb.Wait())
|
|
||||||
// Если у нас wire error, то вызов .Close() повлечёт за собой ошибку, но мы вынуждены звать .Close(), чтоб
|
|
||||||
// закрыть tls контекст и почистить всё что связанно с прерванным соединением.
|
|
||||||
_ = talk.Close()
|
|
||||||
|
|
||||||
time.Sleep(time.Duration(config.Jabber.ReconnectDelay) * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func myLoop() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-gTomb.Dying():
|
|
||||||
gTomb.Done()
|
|
||||||
return //nolint:nlreturn
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Зададим начальное значение глобальным переменным
|
|
||||||
serverPingTimestampRx = 0
|
|
||||||
serverPingTimestampTx = 0
|
|
||||||
roomsConnected = make([]string, 1)
|
|
||||||
lastActivity = 0
|
|
||||||
lastServerActivity = 0
|
|
||||||
lastMucActivity = NewCollection()
|
|
||||||
serverCapsQueried = false
|
|
||||||
serverCapsList = NewCollection()
|
|
||||||
mucCapsList = NewCollection()
|
|
||||||
serverPingTimestampTx = 0
|
|
||||||
serverPingTimestampRx = 0
|
|
||||||
roomPresences = NewCollection()
|
|
||||||
|
|
||||||
// Установим коннект
|
|
||||||
establishConnection()
|
|
||||||
|
|
||||||
serverPingTimestampRx = time.Now().Unix() // Считаем, что если коннект запустился, то первый пинг успешен
|
|
||||||
|
|
||||||
// Тыкаем сервер палочкой, проверяем, что коннект жив и переустанавливаем его, если он не жив
|
|
||||||
go probeServerLiveness()
|
|
||||||
|
|
||||||
// Тыкаем muc-и палочкой, проверяем, что они живы и пере-заходим в них, если пинги пропали
|
|
||||||
go probeMUCLiveness()
|
|
||||||
|
|
||||||
// Гребём сообщения...
|
|
||||||
for {
|
|
||||||
// Стриггерилось завершение работы приложения, или соединение не установлено (порвалось, например)
|
|
||||||
// грести не надо
|
|
||||||
if shutdown {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isConnected {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
chat, err := talk.Recv()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Unable to get events from server: %s", err)
|
|
||||||
|
|
||||||
// Попробуем скрасить печальные будни generic-ошибок...
|
|
||||||
switch {
|
|
||||||
// Стрим не читается, он закрылся с той стороны во время чтения
|
|
||||||
case errors.Is(err, io.EOF):
|
|
||||||
err = fmt.Errorf("tcp stream closed while reading, err=%w", err)
|
|
||||||
|
|
||||||
// Пытаемся читать закрытый сокет
|
|
||||||
case errors.Is(err, net.ErrClosed):
|
|
||||||
err = fmt.Errorf("unable to read closed socket, err=%w", err)
|
|
||||||
|
|
||||||
// Не смогли записать в сокет
|
|
||||||
case errors.Is(err, net.ErrWriteToConnected):
|
|
||||||
err = fmt.Errorf("unable to write to socket, err=%w", err)
|
|
||||||
|
|
||||||
// Не сетевая проблема
|
|
||||||
default:
|
|
||||||
// Это уже что-то странное.
|
|
||||||
// Вероятно, ошибка парсинга xml. Собственно, баг сервера, тут мы ничего поделать не можем
|
|
||||||
err = fmt.Errorf("error during parsing received message, err=%w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
gTomb.Kill(err)
|
|
||||||
|
|
||||||
// Не забываем выходить на ошибке :(
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parseEvent(chat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
|
||||||
128
types.go
128
types.go
@@ -1,128 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// myConfig прототип структурки с конфигом.
|
|
||||||
type myConfig struct {
|
|
||||||
Jabber struct {
|
|
||||||
Server string `json:"server,omitempty"`
|
|
||||||
Port int `json:"port,omitempty"`
|
|
||||||
Ssl bool `json:"ssl,omitempty"`
|
|
||||||
StartTLS bool `json:"starttls,omitempty"`
|
|
||||||
SslVerify bool `json:"ssl_verify,omitempty"`
|
|
||||||
InsecureAllowUnencryptedAuth bool `json:"insecureallowunencryptedauth,omitempty"`
|
|
||||||
ConnectionTimeout int64 `json:"connection_timeout,omitempty"`
|
|
||||||
ReconnectDelay int64 `json:"reconnect_delay,omitempty"`
|
|
||||||
ServerPingDelay int64 `json:"server_ping_delay,omitempty"`
|
|
||||||
MucPingDelay int64 `json:"muc_ping_delay,omitempty"`
|
|
||||||
MucRejoinDelay int64 `json:"muc_rejoin_delay,omitempty"`
|
|
||||||
PingSplayDelay int64 `json:"ping_splay_delay,omitempty"`
|
|
||||||
Nick string `json:"nick,omitempty"`
|
|
||||||
Resource string `json:"resource,omitempty"`
|
|
||||||
User string `json:"user,omitempty"`
|
|
||||||
Password string `json:"password,omitempty"`
|
|
||||||
BotMasters []string `json:"bot_masters,omitempty"`
|
|
||||||
Channels []struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Password string `json:"password,omitempty"`
|
|
||||||
Bayes struct {
|
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
|
||||||
MinWords int64 `json:"min_words,omitempty"`
|
|
||||||
MinLength int `json:"min_length,omitempty"`
|
|
||||||
DefaultAction string `json:"default_action,omitempty"`
|
|
||||||
} `json:"bayes,omitempty"`
|
|
||||||
AllCaps struct {
|
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
|
||||||
MinLength int `json:"min_length,omitempty"`
|
|
||||||
DefaultAction string `json:"default_action,omitempty"`
|
|
||||||
} `json:"all_caps,omitempty"`
|
|
||||||
} `json:"channels"`
|
|
||||||
StartupStatus []string `json:"startup_status,omitempty"`
|
|
||||||
RuntimeStatus struct {
|
|
||||||
Text []string `json:"text,omitempty"`
|
|
||||||
RotationTime int64 `json:"rotation_time,omitempty"`
|
|
||||||
RotationSplayTime int64 `json:"rotation_splay_time,omitempty"`
|
|
||||||
} `json:"runtime_status,omitempty"`
|
|
||||||
BanDelay int64 `json:"ban_delay,omitempty"`
|
|
||||||
BanPhrasesEnable bool `json:"ban_phrases_enable,omitempty"`
|
|
||||||
BanPhrases []string `json:"ban_phrases,omitempty"`
|
|
||||||
} `json:"jabber,omitempty"`
|
|
||||||
|
|
||||||
CSign string `json:"csign,omitempty"`
|
|
||||||
Loglevel string `json:"loglevel,omitempty"`
|
|
||||||
Log string `json:"log,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// myWhiteList прототип структурки с белым списком jid-ов.
|
|
||||||
type myWhiteList struct {
|
|
||||||
Whitelist []struct {
|
|
||||||
RoomName string `json:"room_name,omitempty"`
|
|
||||||
Jid []string `json:"jid,omitempty"`
|
|
||||||
WipeBans bool `json:"wipe_bans,omitempty"`
|
|
||||||
} `json:"whitelist,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// myBlackList прототип структурки с чёрным списком jid-ов.
|
|
||||||
type myBlackList struct {
|
|
||||||
Blacklist []struct {
|
|
||||||
RoomName string `json:"room_name,omitempty"`
|
|
||||||
ReasonEnable bool `json:"reason_enable,omitempty"`
|
|
||||||
JidRe []string `json:"jid_re,omitempty"`
|
|
||||||
NickRe []string `json:"nick_re,omitempty"`
|
|
||||||
PhraseRe []string `json:"phrase_re,omitempty"`
|
|
||||||
} `json:"blacklist,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// jabberSimpleIqGetQuery прототип структурки для разбора запросов xmpp discovery query, например,
|
|
||||||
// https://xmpp.org/extensions/xep-0030.html#example-18 .
|
|
||||||
type jabberSimpleIqGetQuery struct {
|
|
||||||
XMLName xml.Name `xml:"query"`
|
|
||||||
Text string `xml:",chardata"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
Node string `xml:"node,attr,omitempty"` // для xmlns="http://jabber.org/protocol/disco#items"
|
|
||||||
}
|
|
||||||
|
|
||||||
// jabberPubsubIQGetQuery прототип структурки для разбора запросов xmpp pubsub.
|
|
||||||
type jabberPubsubIQGetQuery struct {
|
|
||||||
XMLName xml.Name `xml:"pubsub"`
|
|
||||||
Text string `xml:",chardata"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
Items struct {
|
|
||||||
Text string `xml:",chardata"`
|
|
||||||
Node string `xml:"node,attr"`
|
|
||||||
MaxItems string `xml:"max_items,attr"`
|
|
||||||
} `xml:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// jabberTimeIqGetQuery прототип структурки для разбора IQ запросов на локальное время клиента,
|
|
||||||
// https://xmpp.org/extensions/xep-0202.html
|
|
||||||
type jabberTimeIqGetQuery struct {
|
|
||||||
// <time xmlns="urn:xmpp:time"/>
|
|
||||||
XMLName xml.Name `xml:"time"`
|
|
||||||
Text string `xml:",chardata"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// jabberIqPing прототип структурки для разбора IQ запросов на пинг клиента, https://xmpp.org/extensions/xep-0199.html
|
|
||||||
type jabberIqPing struct {
|
|
||||||
XMLName xml.Name `xml:"ping"`
|
|
||||||
Text string `xml:",chardata"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// jabberIqErrorCancelNotAcceptable прототип структурки для разбора IQ ответов, когда сервис (сервер, клиент, etc) не
|
|
||||||
// может или не хочет принимать наш iq-запрос https://xmpp.org/extensions/xep-0099.html
|
|
||||||
type jabberIqErrorCancelNotAcceptable struct {
|
|
||||||
XMLName xml.Name `xml:"error"`
|
|
||||||
Text string `xml:",chardata"`
|
|
||||||
Type string `xml:"type,attr"`
|
|
||||||
By string `xml:"by,attr"`
|
|
||||||
NotAcceptable struct {
|
|
||||||
Text string `xml:",chardata"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
} `xml:"not-acceptable"`
|
|
||||||
}
|
|
||||||
|
|
||||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
|
||||||
41
vendor/github.com/google/uuid/CHANGELOG.md
generated
vendored
Normal file
41
vendor/github.com/google/uuid/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [1.6.0](https://github.com/google/uuid/compare/v1.5.0...v1.6.0) (2024-01-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add Max UUID constant ([#149](https://github.com/google/uuid/issues/149)) ([c58770e](https://github.com/google/uuid/commit/c58770eb495f55fe2ced6284f93c5158a62e53e3))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix typo in version 7 uuid documentation ([#153](https://github.com/google/uuid/issues/153)) ([016b199](https://github.com/google/uuid/commit/016b199544692f745ffc8867b914129ecb47ef06))
|
||||||
|
* Monotonicity in UUIDv7 ([#150](https://github.com/google/uuid/issues/150)) ([a2b2b32](https://github.com/google/uuid/commit/a2b2b32373ff0b1a312b7fdf6d38a977099698a6))
|
||||||
|
|
||||||
|
## [1.5.0](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) (2023-12-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Validate UUID without creating new UUID ([#141](https://github.com/google/uuid/issues/141)) ([9ee7366](https://github.com/google/uuid/commit/9ee7366e66c9ad96bab89139418a713dc584ae29))
|
||||||
|
|
||||||
|
## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4))
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior)
|
||||||
|
|
||||||
|
## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Use .EqualFold() to parse urn prefixed UUIDs ([#118](https://github.com/google/uuid/issues/118)) ([574e687](https://github.com/google/uuid/commit/574e6874943741fb99d41764c705173ada5293f0))
|
||||||
|
|
||||||
|
## Changelog
|
||||||
26
vendor/github.com/google/uuid/CONTRIBUTING.md
generated
vendored
Normal file
26
vendor/github.com/google/uuid/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# How to contribute
|
||||||
|
|
||||||
|
We definitely welcome patches and contribution to this project!
|
||||||
|
|
||||||
|
### Tips
|
||||||
|
|
||||||
|
Commits must be formatted according to the [Conventional Commits Specification](https://www.conventionalcommits.org).
|
||||||
|
|
||||||
|
Always try to include a test case! If it is not possible or not necessary,
|
||||||
|
please explain why in the pull request description.
|
||||||
|
|
||||||
|
### Releasing
|
||||||
|
|
||||||
|
Commits that would precipitate a SemVer change, as described in the Conventional
|
||||||
|
Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action)
|
||||||
|
to create a release candidate pull request. Once submitted, `release-please`
|
||||||
|
will create a release.
|
||||||
|
|
||||||
|
For tips on how to work with `release-please`, see its documentation.
|
||||||
|
|
||||||
|
### Legal requirements
|
||||||
|
|
||||||
|
In order to protect both you and ourselves, you will need to sign the
|
||||||
|
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||||
|
|
||||||
|
You may have already signed it for other Google projects.
|
||||||
9
vendor/github.com/google/uuid/CONTRIBUTORS
generated
vendored
Normal file
9
vendor/github.com/google/uuid/CONTRIBUTORS
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
Paul Borman <borman@google.com>
|
||||||
|
bmatsuo
|
||||||
|
shawnps
|
||||||
|
theory
|
||||||
|
jboverfelt
|
||||||
|
dsymonds
|
||||||
|
cd1
|
||||||
|
wallclockbuilder
|
||||||
|
dansouza
|
||||||
27
vendor/github.com/google/uuid/LICENSE
generated
vendored
Normal file
27
vendor/github.com/google/uuid/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
21
vendor/github.com/google/uuid/README.md
generated
vendored
Normal file
21
vendor/github.com/google/uuid/README.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# uuid
|
||||||
|
The uuid package generates and inspects UUIDs based on
|
||||||
|
[RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122)
|
||||||
|
and DCE 1.1: Authentication and Security Services.
|
||||||
|
|
||||||
|
This package is based on the github.com/pborman/uuid package (previously named
|
||||||
|
code.google.com/p/go-uuid). It differs from these earlier packages in that
|
||||||
|
a UUID is a 16 byte array rather than a byte slice. One loss due to this
|
||||||
|
change is the ability to represent an invalid UUID (vs a NIL UUID).
|
||||||
|
|
||||||
|
###### Install
|
||||||
|
```sh
|
||||||
|
go get github.com/google/uuid
|
||||||
|
```
|
||||||
|
|
||||||
|
###### Documentation
|
||||||
|
[](https://pkg.go.dev/github.com/google/uuid)
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the package can be viewed online without
|
||||||
|
installing this package by using the GoDoc site here:
|
||||||
|
http://pkg.go.dev/github.com/google/uuid
|
||||||
80
vendor/github.com/google/uuid/dce.go
generated
vendored
Normal file
80
vendor/github.com/google/uuid/dce.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Domain represents a Version 2 domain
|
||||||
|
type Domain byte
|
||||||
|
|
||||||
|
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||||
|
const (
|
||||||
|
Person = Domain(0)
|
||||||
|
Group = Domain(1)
|
||||||
|
Org = Domain(2)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||||
|
//
|
||||||
|
// The domain should be one of Person, Group or Org.
|
||||||
|
// On a POSIX system the id should be the users UID for the Person
|
||||||
|
// domain and the users GID for the Group. The meaning of id for
|
||||||
|
// the domain Org or on non-POSIX systems is site defined.
|
||||||
|
//
|
||||||
|
// For a given domain/id pair the same token may be returned for up to
|
||||||
|
// 7 minutes and 10 seconds.
|
||||||
|
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
|
||||||
|
uuid, err := NewUUID()
|
||||||
|
if err == nil {
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||||
|
uuid[9] = byte(domain)
|
||||||
|
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||||
|
}
|
||||||
|
return uuid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||||
|
// domain with the id returned by os.Getuid.
|
||||||
|
//
|
||||||
|
// NewDCESecurity(Person, uint32(os.Getuid()))
|
||||||
|
func NewDCEPerson() (UUID, error) {
|
||||||
|
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||||
|
// domain with the id returned by os.Getgid.
|
||||||
|
//
|
||||||
|
// NewDCESecurity(Group, uint32(os.Getgid()))
|
||||||
|
func NewDCEGroup() (UUID, error) {
|
||||||
|
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain returns the domain for a Version 2 UUID. Domains are only defined
|
||||||
|
// for Version 2 UUIDs.
|
||||||
|
func (uuid UUID) Domain() Domain {
|
||||||
|
return Domain(uuid[9])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
|
||||||
|
// UUIDs.
|
||||||
|
func (uuid UUID) ID() uint32 {
|
||||||
|
return binary.BigEndian.Uint32(uuid[0:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Domain) String() string {
|
||||||
|
switch d {
|
||||||
|
case Person:
|
||||||
|
return "Person"
|
||||||
|
case Group:
|
||||||
|
return "Group"
|
||||||
|
case Org:
|
||||||
|
return "Org"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Domain%d", int(d))
|
||||||
|
}
|
||||||
12
vendor/github.com/google/uuid/doc.go
generated
vendored
Normal file
12
vendor/github.com/google/uuid/doc.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package uuid generates and inspects UUIDs.
|
||||||
|
//
|
||||||
|
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
|
||||||
|
// Services.
|
||||||
|
//
|
||||||
|
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
|
||||||
|
// maps or compared directly.
|
||||||
|
package uuid
|
||||||
59
vendor/github.com/google/uuid/hash.go
generated
vendored
Normal file
59
vendor/github.com/google/uuid/hash.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Well known namespace IDs and UUIDs
|
||||||
|
var (
|
||||||
|
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
||||||
|
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
||||||
|
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
||||||
|
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
||||||
|
Nil UUID // empty UUID, all zeros
|
||||||
|
|
||||||
|
// The Max UUID is special form of UUID that is specified to have all 128 bits set to 1.
|
||||||
|
Max = UUID{
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewHash returns a new UUID derived from the hash of space concatenated with
|
||||||
|
// data generated by h. The hash should be at least 16 byte in length. The
|
||||||
|
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||||
|
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||||
|
// NewMD5 and NewSHA1.
|
||||||
|
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||||
|
h.Reset()
|
||||||
|
h.Write(space[:]) //nolint:errcheck
|
||||||
|
h.Write(data) //nolint:errcheck
|
||||||
|
s := h.Sum(nil)
|
||||||
|
var uuid UUID
|
||||||
|
copy(uuid[:], s)
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||||
|
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||||
|
// supplied name space and data. It is the same as calling:
|
||||||
|
//
|
||||||
|
// NewHash(md5.New(), space, data, 3)
|
||||||
|
func NewMD5(space UUID, data []byte) UUID {
|
||||||
|
return NewHash(md5.New(), space, data, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||||
|
// supplied name space and data. It is the same as calling:
|
||||||
|
//
|
||||||
|
// NewHash(sha1.New(), space, data, 5)
|
||||||
|
func NewSHA1(space UUID, data []byte) UUID {
|
||||||
|
return NewHash(sha1.New(), space, data, 5)
|
||||||
|
}
|
||||||
38
vendor/github.com/google/uuid/marshal.go
generated
vendored
Normal file
38
vendor/github.com/google/uuid/marshal.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// MarshalText implements encoding.TextMarshaler.
|
||||||
|
func (uuid UUID) MarshalText() ([]byte, error) {
|
||||||
|
var js [36]byte
|
||||||
|
encodeHex(js[:], uuid)
|
||||||
|
return js[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||||
|
func (uuid *UUID) UnmarshalText(data []byte) error {
|
||||||
|
id, err := ParseBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*uuid = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||||
|
func (uuid UUID) MarshalBinary() ([]byte, error) {
|
||||||
|
return uuid[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||||
|
func (uuid *UUID) UnmarshalBinary(data []byte) error {
|
||||||
|
if len(data) != 16 {
|
||||||
|
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||||
|
}
|
||||||
|
copy(uuid[:], data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
90
vendor/github.com/google/uuid/node.go
generated
vendored
Normal file
90
vendor/github.com/google/uuid/node.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
nodeMu sync.Mutex
|
||||||
|
ifname string // name of interface being used
|
||||||
|
nodeID [6]byte // hardware for version 1 UUIDs
|
||||||
|
zeroID [6]byte // nodeID with only 0's
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeInterface returns the name of the interface from which the NodeID was
|
||||||
|
// derived. The interface "user" is returned if the NodeID was set by
|
||||||
|
// SetNodeID.
|
||||||
|
func NodeInterface() string {
|
||||||
|
defer nodeMu.Unlock()
|
||||||
|
nodeMu.Lock()
|
||||||
|
return ifname
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||||
|
// If name is "" then the first usable interface found will be used or a random
|
||||||
|
// Node ID will be generated. If a named interface cannot be found then false
|
||||||
|
// is returned.
|
||||||
|
//
|
||||||
|
// SetNodeInterface never fails when name is "".
|
||||||
|
func SetNodeInterface(name string) bool {
|
||||||
|
defer nodeMu.Unlock()
|
||||||
|
nodeMu.Lock()
|
||||||
|
return setNodeInterface(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setNodeInterface(name string) bool {
|
||||||
|
iname, addr := getHardwareInterface(name) // null implementation for js
|
||||||
|
if iname != "" && addr != nil {
|
||||||
|
ifname = iname
|
||||||
|
copy(nodeID[:], addr)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found no interfaces with a valid hardware address. If name
|
||||||
|
// does not specify a specific interface generate a random Node ID
|
||||||
|
// (section 4.1.6)
|
||||||
|
if name == "" {
|
||||||
|
ifname = "random"
|
||||||
|
randomBits(nodeID[:])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||||
|
// if not already set.
|
||||||
|
func NodeID() []byte {
|
||||||
|
defer nodeMu.Unlock()
|
||||||
|
nodeMu.Lock()
|
||||||
|
if nodeID == zeroID {
|
||||||
|
setNodeInterface("")
|
||||||
|
}
|
||||||
|
nid := nodeID
|
||||||
|
return nid[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||||
|
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||||
|
// Node ID is not set.
|
||||||
|
func SetNodeID(id []byte) bool {
|
||||||
|
if len(id) < 6 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer nodeMu.Unlock()
|
||||||
|
nodeMu.Lock()
|
||||||
|
copy(nodeID[:], id)
|
||||||
|
ifname = "user"
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||||
|
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||||
|
func (uuid UUID) NodeID() []byte {
|
||||||
|
var node [6]byte
|
||||||
|
copy(node[:], uuid[10:])
|
||||||
|
return node[:]
|
||||||
|
}
|
||||||
12
vendor/github.com/google/uuid/node_js.go
generated
vendored
Normal file
12
vendor/github.com/google/uuid/node_js.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build js
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
// getHardwareInterface returns nil values for the JS version of the code.
|
||||||
|
// This removes the "net" dependency, because it is not used in the browser.
|
||||||
|
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
|
||||||
|
func getHardwareInterface(name string) (string, []byte) { return "", nil }
|
||||||
33
vendor/github.com/google/uuid/node_net.go
generated
vendored
Normal file
33
vendor/github.com/google/uuid/node_net.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !js
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
var interfaces []net.Interface // cached list of interfaces
|
||||||
|
|
||||||
|
// getHardwareInterface returns the name and hardware address of interface name.
|
||||||
|
// If name is "" then the name and hardware address of one of the system's
|
||||||
|
// interfaces is returned. If no interfaces are found (name does not exist or
|
||||||
|
// there are no interfaces) then "", nil is returned.
|
||||||
|
//
|
||||||
|
// Only addresses of at least 6 bytes are returned.
|
||||||
|
func getHardwareInterface(name string) (string, []byte) {
|
||||||
|
if interfaces == nil {
|
||||||
|
var err error
|
||||||
|
interfaces, err = net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ifs := range interfaces {
|
||||||
|
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||||
|
return ifs.Name, ifs.HardwareAddr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
118
vendor/github.com/google/uuid/null.go
generated
vendored
Normal file
118
vendor/github.com/google/uuid/null.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
// Copyright 2021 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var jsonNull = []byte("null")
|
||||||
|
|
||||||
|
// NullUUID represents a UUID that may be null.
|
||||||
|
// NullUUID implements the SQL driver.Scanner interface so
|
||||||
|
// it can be used as a scan destination:
|
||||||
|
//
|
||||||
|
// var u uuid.NullUUID
|
||||||
|
// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&u)
|
||||||
|
// ...
|
||||||
|
// if u.Valid {
|
||||||
|
// // use u.UUID
|
||||||
|
// } else {
|
||||||
|
// // NULL value
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
type NullUUID struct {
|
||||||
|
UUID UUID
|
||||||
|
Valid bool // Valid is true if UUID is not NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the SQL driver.Scanner interface.
|
||||||
|
func (nu *NullUUID) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
nu.UUID, nu.Valid = Nil, false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := nu.UUID.Scan(value)
|
||||||
|
if err != nil {
|
||||||
|
nu.Valid = false
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nu.Valid = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements the driver Valuer interface.
|
||||||
|
func (nu NullUUID) Value() (driver.Value, error) {
|
||||||
|
if !nu.Valid {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// Delegate to UUID Value function
|
||||||
|
return nu.UUID.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||||
|
func (nu NullUUID) MarshalBinary() ([]byte, error) {
|
||||||
|
if nu.Valid {
|
||||||
|
return nu.UUID[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||||
|
func (nu *NullUUID) UnmarshalBinary(data []byte) error {
|
||||||
|
if len(data) != 16 {
|
||||||
|
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||||
|
}
|
||||||
|
copy(nu.UUID[:], data)
|
||||||
|
nu.Valid = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText implements encoding.TextMarshaler.
|
||||||
|
func (nu NullUUID) MarshalText() ([]byte, error) {
|
||||||
|
if nu.Valid {
|
||||||
|
return nu.UUID.MarshalText()
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonNull, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||||
|
func (nu *NullUUID) UnmarshalText(data []byte) error {
|
||||||
|
id, err := ParseBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
nu.Valid = false
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nu.UUID = id
|
||||||
|
nu.Valid = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Marshaler.
|
||||||
|
func (nu NullUUID) MarshalJSON() ([]byte, error) {
|
||||||
|
if nu.Valid {
|
||||||
|
return json.Marshal(nu.UUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonNull, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler.
|
||||||
|
func (nu *NullUUID) UnmarshalJSON(data []byte) error {
|
||||||
|
if bytes.Equal(data, jsonNull) {
|
||||||
|
*nu = NullUUID{}
|
||||||
|
return nil // valid null UUID
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(data, &nu.UUID)
|
||||||
|
nu.Valid = err == nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
59
vendor/github.com/google/uuid/sql.go
generated
vendored
Normal file
59
vendor/github.com/google/uuid/sql.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scan implements sql.Scanner so UUIDs can be read from databases transparently.
|
||||||
|
// Currently, database types that map to string and []byte are supported. Please
|
||||||
|
// consult database-specific driver documentation for matching types.
|
||||||
|
func (uuid *UUID) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case nil:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case string:
|
||||||
|
// if an empty UUID comes from a table, we return a null UUID
|
||||||
|
if src == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// see Parse for required string format
|
||||||
|
u, err := Parse(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Scan: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*uuid = u
|
||||||
|
|
||||||
|
case []byte:
|
||||||
|
// if an empty UUID comes from a table, we return a null UUID
|
||||||
|
if len(src) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes a simple slice of bytes if 16 bytes
|
||||||
|
// otherwise attempts to parse
|
||||||
|
if len(src) != 16 {
|
||||||
|
return uuid.Scan(string(src))
|
||||||
|
}
|
||||||
|
copy((*uuid)[:], src)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements sql.Valuer so that UUIDs can be written to databases
|
||||||
|
// transparently. Currently, UUIDs map to strings. Please consult
|
||||||
|
// database-specific driver documentation for matching types.
|
||||||
|
func (uuid UUID) Value() (driver.Value, error) {
|
||||||
|
return uuid.String(), nil
|
||||||
|
}
|
||||||
134
vendor/github.com/google/uuid/time.go
generated
vendored
Normal file
134
vendor/github.com/google/uuid/time.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||||
|
// 1582.
|
||||||
|
type Time int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||||
|
unix = 2440587 // Julian day of 1 Jan 1970
|
||||||
|
epoch = unix - lillian // Days between epochs
|
||||||
|
g1582 = epoch * 86400 // seconds between epochs
|
||||||
|
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
timeMu sync.Mutex
|
||||||
|
lasttime uint64 // last time we returned
|
||||||
|
clockSeq uint16 // clock sequence for this run
|
||||||
|
|
||||||
|
timeNow = time.Now // for testing
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||||
|
// epoch of 1 Jan 1970.
|
||||||
|
func (t Time) UnixTime() (sec, nsec int64) {
|
||||||
|
sec = int64(t - g1582ns100)
|
||||||
|
nsec = (sec % 10000000) * 100
|
||||||
|
sec /= 10000000
|
||||||
|
return sec, nsec
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||||
|
// clock sequence as well as adjusting the clock sequence as needed. An error
|
||||||
|
// is returned if the current time cannot be determined.
|
||||||
|
func GetTime() (Time, uint16, error) {
|
||||||
|
defer timeMu.Unlock()
|
||||||
|
timeMu.Lock()
|
||||||
|
return getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTime() (Time, uint16, error) {
|
||||||
|
t := timeNow()
|
||||||
|
|
||||||
|
// If we don't have a clock sequence already, set one.
|
||||||
|
if clockSeq == 0 {
|
||||||
|
setClockSequence(-1)
|
||||||
|
}
|
||||||
|
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||||
|
|
||||||
|
// If time has gone backwards with this clock sequence then we
|
||||||
|
// increment the clock sequence
|
||||||
|
if now <= lasttime {
|
||||||
|
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
|
||||||
|
}
|
||||||
|
lasttime = now
|
||||||
|
return Time(now), clockSeq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClockSequence returns the current clock sequence, generating one if not
|
||||||
|
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||||
|
//
|
||||||
|
// The uuid package does not use global static storage for the clock sequence or
|
||||||
|
// the last time a UUID was generated. Unless SetClockSequence is used, a new
|
||||||
|
// random clock sequence is generated the first time a clock sequence is
|
||||||
|
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
|
||||||
|
func ClockSequence() int {
|
||||||
|
defer timeMu.Unlock()
|
||||||
|
timeMu.Lock()
|
||||||
|
return clockSequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
func clockSequence() int {
|
||||||
|
if clockSeq == 0 {
|
||||||
|
setClockSequence(-1)
|
||||||
|
}
|
||||||
|
return int(clockSeq & 0x3fff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||||
|
// -1 causes a new sequence to be generated.
|
||||||
|
func SetClockSequence(seq int) {
|
||||||
|
defer timeMu.Unlock()
|
||||||
|
timeMu.Lock()
|
||||||
|
setClockSequence(seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setClockSequence(seq int) {
|
||||||
|
if seq == -1 {
|
||||||
|
var b [2]byte
|
||||||
|
randomBits(b[:]) // clock sequence
|
||||||
|
seq = int(b[0])<<8 | int(b[1])
|
||||||
|
}
|
||||||
|
oldSeq := clockSeq
|
||||||
|
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||||
|
if oldSeq != clockSeq {
|
||||||
|
lasttime = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||||
|
// uuid. The time is only defined for version 1, 2, 6 and 7 UUIDs.
|
||||||
|
func (uuid UUID) Time() Time {
|
||||||
|
var t Time
|
||||||
|
switch uuid.Version() {
|
||||||
|
case 6:
|
||||||
|
time := binary.BigEndian.Uint64(uuid[:8]) // Ignore uuid[6] version b0110
|
||||||
|
t = Time(time)
|
||||||
|
case 7:
|
||||||
|
time := binary.BigEndian.Uint64(uuid[:8])
|
||||||
|
t = Time((time>>16)*10000 + g1582ns100)
|
||||||
|
default: // forward compatible
|
||||||
|
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||||
|
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||||
|
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||||
|
t = Time(time)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClockSequence returns the clock sequence encoded in uuid.
|
||||||
|
// The clock sequence is only well defined for version 1 and 2 UUIDs.
|
||||||
|
func (uuid UUID) ClockSequence() int {
|
||||||
|
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
|
||||||
|
}
|
||||||
43
vendor/github.com/google/uuid/util.go
generated
vendored
Normal file
43
vendor/github.com/google/uuid/util.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// randomBits completely fills slice b with random data.
|
||||||
|
func randomBits(b []byte) {
|
||||||
|
if _, err := io.ReadFull(rander, b); err != nil {
|
||||||
|
panic(err.Error()) // rand should never fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||||
|
var xvalues = [256]byte{
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
// xtob converts hex characters x1 and x2 into a byte.
|
||||||
|
func xtob(x1, x2 byte) (byte, bool) {
|
||||||
|
b1 := xvalues[x1]
|
||||||
|
b2 := xvalues[x2]
|
||||||
|
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||||
|
}
|
||||||
365
vendor/github.com/google/uuid/uuid.go
generated
vendored
Normal file
365
vendor/github.com/google/uuid/uuid.go
generated
vendored
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
// Copyright 2018 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||||
|
// 4122.
|
||||||
|
type UUID [16]byte
|
||||||
|
|
||||||
|
// A Version represents a UUID's version.
|
||||||
|
type Version byte
|
||||||
|
|
||||||
|
// A Variant represents a UUID's variant.
|
||||||
|
type Variant byte
|
||||||
|
|
||||||
|
// Constants returned by Variant.
|
||||||
|
const (
|
||||||
|
Invalid = Variant(iota) // Invalid UUID
|
||||||
|
RFC4122 // The variant specified in RFC4122
|
||||||
|
Reserved // Reserved, NCS backward compatibility.
|
||||||
|
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||||
|
Future // Reserved for future definition.
|
||||||
|
)
|
||||||
|
|
||||||
|
const randPoolSize = 16 * 16
|
||||||
|
|
||||||
|
var (
|
||||||
|
rander = rand.Reader // random function
|
||||||
|
poolEnabled = false
|
||||||
|
poolMu sync.Mutex
|
||||||
|
poolPos = randPoolSize // protected with poolMu
|
||||||
|
pool [randPoolSize]byte // protected with poolMu
|
||||||
|
)
|
||||||
|
|
||||||
|
type invalidLengthError struct{ len int }
|
||||||
|
|
||||||
|
func (err invalidLengthError) Error() string {
|
||||||
|
return fmt.Sprintf("invalid UUID length: %d", err.len)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInvalidLengthError is matcher function for custom error invalidLengthError
|
||||||
|
func IsInvalidLengthError(err error) bool {
|
||||||
|
_, ok := err.(invalidLengthError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse decodes s into a UUID or returns an error if it cannot be parsed. Both
|
||||||
|
// the standard UUID forms defined in RFC 4122
|
||||||
|
// (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) are decoded. In addition,
|
||||||
|
// Parse accepts non-standard strings such as the raw hex encoding
|
||||||
|
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx and 38 byte "Microsoft style" encodings,
|
||||||
|
// e.g. {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. Only the middle 36 bytes are
|
||||||
|
// examined in the latter case. Parse should not be used to validate strings as
|
||||||
|
// it parses non-standard encodings as indicated above.
|
||||||
|
func Parse(s string) (UUID, error) {
|
||||||
|
var uuid UUID
|
||||||
|
switch len(s) {
|
||||||
|
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
case 36:
|
||||||
|
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
case 36 + 9:
|
||||||
|
if !strings.EqualFold(s[:9], "urn:uuid:") {
|
||||||
|
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||||
|
}
|
||||||
|
s = s[9:]
|
||||||
|
|
||||||
|
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||||
|
case 36 + 2:
|
||||||
|
s = s[1:]
|
||||||
|
|
||||||
|
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
case 32:
|
||||||
|
var ok bool
|
||||||
|
for i := range uuid {
|
||||||
|
uuid[i], ok = xtob(s[i*2], s[i*2+1])
|
||||||
|
if !ok {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uuid, nil
|
||||||
|
default:
|
||||||
|
return uuid, invalidLengthError{len(s)}
|
||||||
|
}
|
||||||
|
// s is now at least 36 bytes long
|
||||||
|
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
for i, x := range [16]int{
|
||||||
|
0, 2, 4, 6,
|
||||||
|
9, 11,
|
||||||
|
14, 16,
|
||||||
|
19, 21,
|
||||||
|
24, 26, 28, 30, 32, 34,
|
||||||
|
} {
|
||||||
|
v, ok := xtob(s[x], s[x+1])
|
||||||
|
if !ok {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
uuid[i] = v
|
||||||
|
}
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
|
||||||
|
func ParseBytes(b []byte) (UUID, error) {
|
||||||
|
var uuid UUID
|
||||||
|
switch len(b) {
|
||||||
|
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
if !bytes.EqualFold(b[:9], []byte("urn:uuid:")) {
|
||||||
|
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
|
||||||
|
}
|
||||||
|
b = b[9:]
|
||||||
|
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||||
|
b = b[1:]
|
||||||
|
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
var ok bool
|
||||||
|
for i := 0; i < 32; i += 2 {
|
||||||
|
uuid[i/2], ok = xtob(b[i], b[i+1])
|
||||||
|
if !ok {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uuid, nil
|
||||||
|
default:
|
||||||
|
return uuid, invalidLengthError{len(b)}
|
||||||
|
}
|
||||||
|
// s is now at least 36 bytes long
|
||||||
|
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
for i, x := range [16]int{
|
||||||
|
0, 2, 4, 6,
|
||||||
|
9, 11,
|
||||||
|
14, 16,
|
||||||
|
19, 21,
|
||||||
|
24, 26, 28, 30, 32, 34,
|
||||||
|
} {
|
||||||
|
v, ok := xtob(b[x], b[x+1])
|
||||||
|
if !ok {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
uuid[i] = v
|
||||||
|
}
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustParse is like Parse but panics if the string cannot be parsed.
|
||||||
|
// It simplifies safe initialization of global variables holding compiled UUIDs.
|
||||||
|
func MustParse(s string) UUID {
|
||||||
|
uuid, err := Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(`uuid: Parse(` + s + `): ` + err.Error())
|
||||||
|
}
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
|
||||||
|
// does not have a length of 16. The bytes are copied from the slice.
|
||||||
|
func FromBytes(b []byte) (uuid UUID, err error) {
|
||||||
|
err = uuid.UnmarshalBinary(b)
|
||||||
|
return uuid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must returns uuid if err is nil and panics otherwise.
|
||||||
|
func Must(uuid UUID, err error) UUID {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate returns an error if s is not a properly formatted UUID in one of the following formats:
|
||||||
|
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||||
|
// It returns an error if the format is invalid, otherwise nil.
|
||||||
|
func Validate(s string) error {
|
||||||
|
switch len(s) {
|
||||||
|
// Standard UUID format
|
||||||
|
case 36:
|
||||||
|
|
||||||
|
// UUID with "urn:uuid:" prefix
|
||||||
|
case 36 + 9:
|
||||||
|
if !strings.EqualFold(s[:9], "urn:uuid:") {
|
||||||
|
return fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||||
|
}
|
||||||
|
s = s[9:]
|
||||||
|
|
||||||
|
// UUID enclosed in braces
|
||||||
|
case 36 + 2:
|
||||||
|
if s[0] != '{' || s[len(s)-1] != '}' {
|
||||||
|
return fmt.Errorf("invalid bracketed UUID format")
|
||||||
|
}
|
||||||
|
s = s[1 : len(s)-1]
|
||||||
|
|
||||||
|
// UUID without hyphens
|
||||||
|
case 32:
|
||||||
|
for i := 0; i < len(s); i += 2 {
|
||||||
|
_, ok := xtob(s[i], s[i+1])
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return invalidLengthError{len(s)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for standard UUID format
|
||||||
|
if len(s) == 36 {
|
||||||
|
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||||
|
return errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
for _, x := range []int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} {
|
||||||
|
if _, ok := xtob(s[x], s[x+1]); !ok {
|
||||||
|
return errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
// , or "" if uuid is invalid.
|
||||||
|
func (uuid UUID) String() string {
|
||||||
|
var buf [36]byte
|
||||||
|
encodeHex(buf[:], uuid)
|
||||||
|
return string(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// URN returns the RFC 2141 URN form of uuid,
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||||
|
func (uuid UUID) URN() string {
|
||||||
|
var buf [36 + 9]byte
|
||||||
|
copy(buf[:], "urn:uuid:")
|
||||||
|
encodeHex(buf[9:], uuid)
|
||||||
|
return string(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeHex(dst []byte, uuid UUID) {
|
||||||
|
hex.Encode(dst, uuid[:4])
|
||||||
|
dst[8] = '-'
|
||||||
|
hex.Encode(dst[9:13], uuid[4:6])
|
||||||
|
dst[13] = '-'
|
||||||
|
hex.Encode(dst[14:18], uuid[6:8])
|
||||||
|
dst[18] = '-'
|
||||||
|
hex.Encode(dst[19:23], uuid[8:10])
|
||||||
|
dst[23] = '-'
|
||||||
|
hex.Encode(dst[24:], uuid[10:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant returns the variant encoded in uuid.
|
||||||
|
func (uuid UUID) Variant() Variant {
|
||||||
|
switch {
|
||||||
|
case (uuid[8] & 0xc0) == 0x80:
|
||||||
|
return RFC4122
|
||||||
|
case (uuid[8] & 0xe0) == 0xc0:
|
||||||
|
return Microsoft
|
||||||
|
case (uuid[8] & 0xe0) == 0xe0:
|
||||||
|
return Future
|
||||||
|
default:
|
||||||
|
return Reserved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns the version of uuid.
|
||||||
|
func (uuid UUID) Version() Version {
|
||||||
|
return Version(uuid[6] >> 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
if v > 15 {
|
||||||
|
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("VERSION_%d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Variant) String() string {
|
||||||
|
switch v {
|
||||||
|
case RFC4122:
|
||||||
|
return "RFC4122"
|
||||||
|
case Reserved:
|
||||||
|
return "Reserved"
|
||||||
|
case Microsoft:
|
||||||
|
return "Microsoft"
|
||||||
|
case Future:
|
||||||
|
return "Future"
|
||||||
|
case Invalid:
|
||||||
|
return "Invalid"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("BadVariant%d", int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRand sets the random number generator to r, which implements io.Reader.
|
||||||
|
// If r.Read returns an error when the package requests random data then
|
||||||
|
// a panic will be issued.
|
||||||
|
//
|
||||||
|
// Calling SetRand with nil sets the random number generator to the default
|
||||||
|
// generator.
|
||||||
|
func SetRand(r io.Reader) {
|
||||||
|
if r == nil {
|
||||||
|
rander = rand.Reader
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rander = r
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableRandPool enables internal randomness pool used for Random
|
||||||
|
// (Version 4) UUID generation. The pool contains random bytes read from
|
||||||
|
// the random number generator on demand in batches. Enabling the pool
|
||||||
|
// may improve the UUID generation throughput significantly.
|
||||||
|
//
|
||||||
|
// Since the pool is stored on the Go heap, this feature may be a bad fit
|
||||||
|
// for security sensitive applications.
|
||||||
|
//
|
||||||
|
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
||||||
|
// only be called when there is no possibility that New or any other
|
||||||
|
// UUID Version 4 generation function will be called concurrently.
|
||||||
|
func EnableRandPool() {
|
||||||
|
poolEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableRandPool disables the randomness pool if it was previously
|
||||||
|
// enabled with EnableRandPool.
|
||||||
|
//
|
||||||
|
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
||||||
|
// only be called when there is no possibility that New or any other
|
||||||
|
// UUID Version 4 generation function will be called concurrently.
|
||||||
|
func DisableRandPool() {
|
||||||
|
poolEnabled = false
|
||||||
|
defer poolMu.Unlock()
|
||||||
|
poolMu.Lock()
|
||||||
|
poolPos = randPoolSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// UUIDs is a slice of UUID types.
|
||||||
|
type UUIDs []UUID
|
||||||
|
|
||||||
|
// Strings returns a string slice containing the string form of each UUID in uuids.
|
||||||
|
func (uuids UUIDs) Strings() []string {
|
||||||
|
var uuidStrs = make([]string, len(uuids))
|
||||||
|
for i, uuid := range uuids {
|
||||||
|
uuidStrs[i] = uuid.String()
|
||||||
|
}
|
||||||
|
return uuidStrs
|
||||||
|
}
|
||||||
44
vendor/github.com/google/uuid/version1.go
generated
vendored
Normal file
44
vendor/github.com/google/uuid/version1.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||||
|
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||||
|
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||||
|
// be set NewUUID returns nil. If clock sequence has not been set by
|
||||||
|
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||||
|
// return the current NewUUID returns nil and an error.
|
||||||
|
//
|
||||||
|
// In most cases, New should be used.
|
||||||
|
func NewUUID() (UUID, error) {
|
||||||
|
var uuid UUID
|
||||||
|
now, seq, err := GetTime()
|
||||||
|
if err != nil {
|
||||||
|
return uuid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeLow := uint32(now & 0xffffffff)
|
||||||
|
timeMid := uint16((now >> 32) & 0xffff)
|
||||||
|
timeHi := uint16((now >> 48) & 0x0fff)
|
||||||
|
timeHi |= 0x1000 // Version 1
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(uuid[0:], timeLow)
|
||||||
|
binary.BigEndian.PutUint16(uuid[4:], timeMid)
|
||||||
|
binary.BigEndian.PutUint16(uuid[6:], timeHi)
|
||||||
|
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||||
|
|
||||||
|
nodeMu.Lock()
|
||||||
|
if nodeID == zeroID {
|
||||||
|
setNodeInterface("")
|
||||||
|
}
|
||||||
|
copy(uuid[10:], nodeID[:])
|
||||||
|
nodeMu.Unlock()
|
||||||
|
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
76
vendor/github.com/google/uuid/version4.go
generated
vendored
Normal file
76
vendor/github.com/google/uuid/version4.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// New creates a new random UUID or panics. New is equivalent to
|
||||||
|
// the expression
|
||||||
|
//
|
||||||
|
// uuid.Must(uuid.NewRandom())
|
||||||
|
func New() UUID {
|
||||||
|
return Must(NewRandom())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewString creates a new random UUID and returns it as a string or panics.
|
||||||
|
// NewString is equivalent to the expression
|
||||||
|
//
|
||||||
|
// uuid.New().String()
|
||||||
|
func NewString() string {
|
||||||
|
return Must(NewRandom()).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRandom returns a Random (Version 4) UUID.
|
||||||
|
//
|
||||||
|
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||||
|
// package.
|
||||||
|
//
|
||||||
|
// Uses the randomness pool if it was enabled with EnableRandPool.
|
||||||
|
//
|
||||||
|
// A note about uniqueness derived from the UUID Wikipedia entry:
|
||||||
|
//
|
||||||
|
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||||
|
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
||||||
|
// means the probability is about 0.00000000006 (6 × 10−11),
|
||||||
|
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||||
|
// year and having one duplicate.
|
||||||
|
func NewRandom() (UUID, error) {
|
||||||
|
if !poolEnabled {
|
||||||
|
return NewRandomFromReader(rander)
|
||||||
|
}
|
||||||
|
return newRandomFromPool()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader.
|
||||||
|
func NewRandomFromReader(r io.Reader) (UUID, error) {
|
||||||
|
var uuid UUID
|
||||||
|
_, err := io.ReadFull(r, uuid[:])
|
||||||
|
if err != nil {
|
||||||
|
return Nil, err
|
||||||
|
}
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||||
|
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRandomFromPool() (UUID, error) {
|
||||||
|
var uuid UUID
|
||||||
|
poolMu.Lock()
|
||||||
|
if poolPos == randPoolSize {
|
||||||
|
_, err := io.ReadFull(rander, pool[:])
|
||||||
|
if err != nil {
|
||||||
|
poolMu.Unlock()
|
||||||
|
return Nil, err
|
||||||
|
}
|
||||||
|
poolPos = 0
|
||||||
|
}
|
||||||
|
copy(uuid[:], pool[poolPos:(poolPos+16)])
|
||||||
|
poolPos += 16
|
||||||
|
poolMu.Unlock()
|
||||||
|
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||||
|
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
56
vendor/github.com/google/uuid/version6.go
generated
vendored
Normal file
56
vendor/github.com/google/uuid/version6.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2023 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
// UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality.
|
||||||
|
// It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs.
|
||||||
|
// Systems that do not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
|
||||||
|
//
|
||||||
|
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#uuidv6
|
||||||
|
//
|
||||||
|
// NewV6 returns a Version 6 UUID based on the current NodeID and clock
|
||||||
|
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||||
|
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||||
|
// be set NewV6 set NodeID is random bits automatically . If clock sequence has not been set by
|
||||||
|
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||||
|
// return the current NewV6 returns Nil and an error.
|
||||||
|
func NewV6() (UUID, error) {
|
||||||
|
var uuid UUID
|
||||||
|
now, seq, err := GetTime()
|
||||||
|
if err != nil {
|
||||||
|
return uuid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| time_high |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| time_mid | time_low_and_version |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|clk_seq_hi_res | clk_seq_low | node (0-1) |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| node (2-5) |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
*/
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint64(uuid[0:], uint64(now))
|
||||||
|
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||||
|
|
||||||
|
uuid[6] = 0x60 | (uuid[6] & 0x0F)
|
||||||
|
uuid[8] = 0x80 | (uuid[8] & 0x3F)
|
||||||
|
|
||||||
|
nodeMu.Lock()
|
||||||
|
if nodeID == zeroID {
|
||||||
|
setNodeInterface("")
|
||||||
|
}
|
||||||
|
copy(uuid[10:], nodeID[:])
|
||||||
|
nodeMu.Unlock()
|
||||||
|
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
104
vendor/github.com/google/uuid/version7.go
generated
vendored
Normal file
104
vendor/github.com/google/uuid/version7.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
// Copyright 2023 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UUID version 7 features a time-ordered value field derived from the widely
|
||||||
|
// implemented and well known Unix Epoch timestamp source,
|
||||||
|
// the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded.
|
||||||
|
// As well as improved entropy characteristics over versions 1 or 6.
|
||||||
|
//
|
||||||
|
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#name-uuid-version-7
|
||||||
|
//
|
||||||
|
// Implementations SHOULD utilize UUID version 7 over UUID version 1 and 6 if possible.
|
||||||
|
//
|
||||||
|
// NewV7 returns a Version 7 UUID based on the current time(Unix Epoch).
|
||||||
|
// Uses the randomness pool if it was enabled with EnableRandPool.
|
||||||
|
// On error, NewV7 returns Nil and an error
|
||||||
|
func NewV7() (UUID, error) {
|
||||||
|
uuid, err := NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
return uuid, err
|
||||||
|
}
|
||||||
|
makeV7(uuid[:])
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewV7FromReader returns a Version 7 UUID based on the current time(Unix Epoch).
|
||||||
|
// it use NewRandomFromReader fill random bits.
|
||||||
|
// On error, NewV7FromReader returns Nil and an error.
|
||||||
|
func NewV7FromReader(r io.Reader) (UUID, error) {
|
||||||
|
uuid, err := NewRandomFromReader(r)
|
||||||
|
if err != nil {
|
||||||
|
return uuid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
makeV7(uuid[:])
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6])
|
||||||
|
// uuid[8] already has the right version number (Variant is 10)
|
||||||
|
// see function NewV7 and NewV7FromReader
|
||||||
|
func makeV7(uuid []byte) {
|
||||||
|
/*
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| unix_ts_ms |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| unix_ts_ms | ver | rand_a (12 bit seq) |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|var| rand_b |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| rand_b |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
*/
|
||||||
|
_ = uuid[15] // bounds check
|
||||||
|
|
||||||
|
t, s := getV7Time()
|
||||||
|
|
||||||
|
uuid[0] = byte(t >> 40)
|
||||||
|
uuid[1] = byte(t >> 32)
|
||||||
|
uuid[2] = byte(t >> 24)
|
||||||
|
uuid[3] = byte(t >> 16)
|
||||||
|
uuid[4] = byte(t >> 8)
|
||||||
|
uuid[5] = byte(t)
|
||||||
|
|
||||||
|
uuid[6] = 0x70 | (0x0F & byte(s>>8))
|
||||||
|
uuid[7] = byte(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lastV7time is the last time we returned stored as:
|
||||||
|
//
|
||||||
|
// 52 bits of time in milliseconds since epoch
|
||||||
|
// 12 bits of (fractional nanoseconds) >> 8
|
||||||
|
var lastV7time int64
|
||||||
|
|
||||||
|
const nanoPerMilli = 1000000
|
||||||
|
|
||||||
|
// getV7Time returns the time in milliseconds and nanoseconds / 256.
|
||||||
|
// The returned (milli << 12 + seq) is guarenteed to be greater than
|
||||||
|
// (milli << 12 + seq) returned by any previous call to getV7Time.
|
||||||
|
func getV7Time() (milli, seq int64) {
|
||||||
|
timeMu.Lock()
|
||||||
|
defer timeMu.Unlock()
|
||||||
|
|
||||||
|
nano := timeNow().UnixNano()
|
||||||
|
milli = nano / nanoPerMilli
|
||||||
|
// Sequence number is between 0 and 3906 (nanoPerMilli>>8)
|
||||||
|
seq = (nano - milli*nanoPerMilli) >> 8
|
||||||
|
now := milli<<12 + seq
|
||||||
|
if now <= lastV7time {
|
||||||
|
now = lastV7time + 1
|
||||||
|
milli = now >> 12
|
||||||
|
seq = now & 0xfff
|
||||||
|
}
|
||||||
|
lastV7time = now
|
||||||
|
return milli, seq
|
||||||
|
}
|
||||||
8
vendor/github.com/jbrukh/bayesian/README.md
generated
vendored
8
vendor/github.com/jbrukh/bayesian/README.md
generated
vendored
@@ -18,7 +18,7 @@ This is meant to be an low-entry barrier Go library for basic Bayesian classific
|
|||||||
Using the go command:
|
Using the go command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go get github.com/navossoc/bayesian
|
go get github.com/jbrukh/bayesian
|
||||||
go install !$
|
go install !$
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ go install !$
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
See the GoPkgDoc documentation [here](https://godoc.org/github.com/navossoc/bayesian).
|
See the GoPkgDoc documentation [here](https://godoc.org/github.com/jbrukh/bayesian).
|
||||||
|
|
||||||
------------
|
------------
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ To use the classifier, first you must create some classes
|
|||||||
and train it:
|
and train it:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/navossoc/bayesian"
|
import "github.com/jbrukh/bayesian"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Good bayesian.Class = "Good"
|
Good bayesian.Class = "Good"
|
||||||
@@ -85,7 +85,7 @@ and train it and you need to call ConvertTermsFreqToTfIdf() AFTER training
|
|||||||
and before calling classification methods such as `LogScores`, `SafeProbScores`, and `ProbScores`)
|
and before calling classification methods such as `LogScores`, `SafeProbScores`, and `ProbScores`)
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/navossoc/bayesian"
|
import "github.com/jbrukh/bayesian"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Good bayesian.Class = "Good"
|
Good bayesian.Class = "Good"
|
||||||
|
|||||||
4
vendor/golang.org/x/crypto/LICENSE
generated
vendored
4
vendor/golang.org/x/crypto/LICENSE
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
|
|||||||
copyright notice, this list of conditions and the following disclaimer
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
in the documentation and/or other materials provided with the
|
in the documentation and/or other materials provided with the
|
||||||
distribution.
|
distribution.
|
||||||
* Neither the name of Google Inc. nor the names of its
|
* Neither the name of Google LLC nor the names of its
|
||||||
contributors may be used to endorse or promote products derived from
|
contributors may be used to endorse or promote products derived from
|
||||||
this software without specific prior written permission.
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
|||||||
2
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
2
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
@@ -16,7 +16,7 @@ Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
|||||||
choose, you can pass the `New` functions from the different SHA packages to
|
choose, you can pass the `New` functions from the different SHA packages to
|
||||||
pbkdf2.Key.
|
pbkdf2.Key.
|
||||||
*/
|
*/
|
||||||
package pbkdf2 // import "golang.org/x/crypto/pbkdf2"
|
package pbkdf2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
|
|||||||
4
vendor/golang.org/x/exp/LICENSE
generated
vendored
4
vendor/golang.org/x/exp/LICENSE
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
|
|||||||
copyright notice, this list of conditions and the following disclaimer
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
in the documentation and/or other materials provided with the
|
in the documentation and/or other materials provided with the
|
||||||
distribution.
|
distribution.
|
||||||
* Neither the name of Google Inc. nor the names of its
|
* Neither the name of Google LLC nor the names of its
|
||||||
contributors may be used to endorse or promote products derived from
|
contributors may be used to endorse or promote products derived from
|
||||||
this software without specific prior written permission.
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
|||||||
50
vendor/golang.org/x/exp/constraints/constraints.go
generated
vendored
50
vendor/golang.org/x/exp/constraints/constraints.go
generated
vendored
@@ -1,50 +0,0 @@
|
|||||||
// Copyright 2021 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package constraints defines a set of useful constraints to be used
|
|
||||||
// with type parameters.
|
|
||||||
package constraints
|
|
||||||
|
|
||||||
// Signed is a constraint that permits any signed integer type.
|
|
||||||
// If future releases of Go add new predeclared signed integer types,
|
|
||||||
// this constraint will be modified to include them.
|
|
||||||
type Signed interface {
|
|
||||||
~int | ~int8 | ~int16 | ~int32 | ~int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsigned is a constraint that permits any unsigned integer type.
|
|
||||||
// If future releases of Go add new predeclared unsigned integer types,
|
|
||||||
// this constraint will be modified to include them.
|
|
||||||
type Unsigned interface {
|
|
||||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Integer is a constraint that permits any integer type.
|
|
||||||
// If future releases of Go add new predeclared integer types,
|
|
||||||
// this constraint will be modified to include them.
|
|
||||||
type Integer interface {
|
|
||||||
Signed | Unsigned
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float is a constraint that permits any floating-point type.
|
|
||||||
// If future releases of Go add new predeclared floating-point types,
|
|
||||||
// this constraint will be modified to include them.
|
|
||||||
type Float interface {
|
|
||||||
~float32 | ~float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complex is a constraint that permits any complex numeric type.
|
|
||||||
// If future releases of Go add new predeclared complex numeric types,
|
|
||||||
// this constraint will be modified to include them.
|
|
||||||
type Complex interface {
|
|
||||||
~complex64 | ~complex128
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ordered is a constraint that permits any ordered type: any type
|
|
||||||
// that supports the operators < <= >= >.
|
|
||||||
// If future releases of Go add new ordered types,
|
|
||||||
// this constraint will be modified to include them.
|
|
||||||
type Ordered interface {
|
|
||||||
Integer | Float | ~string
|
|
||||||
}
|
|
||||||
44
vendor/golang.org/x/exp/slices/cmp.go
generated
vendored
44
vendor/golang.org/x/exp/slices/cmp.go
generated
vendored
@@ -1,44 +0,0 @@
|
|||||||
// Copyright 2023 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package slices
|
|
||||||
|
|
||||||
import "golang.org/x/exp/constraints"
|
|
||||||
|
|
||||||
// min is a version of the predeclared function from the Go 1.21 release.
|
|
||||||
func min[T constraints.Ordered](a, b T) T {
|
|
||||||
if a < b || isNaN(a) {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// max is a version of the predeclared function from the Go 1.21 release.
|
|
||||||
func max[T constraints.Ordered](a, b T) T {
|
|
||||||
if a > b || isNaN(a) {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmpLess is a copy of cmp.Less from the Go 1.21 release.
|
|
||||||
func cmpLess[T constraints.Ordered](x, y T) bool {
|
|
||||||
return (isNaN(x) && !isNaN(y)) || x < y
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmpCompare is a copy of cmp.Compare from the Go 1.21 release.
|
|
||||||
func cmpCompare[T constraints.Ordered](x, y T) int {
|
|
||||||
xNaN := isNaN(x)
|
|
||||||
yNaN := isNaN(y)
|
|
||||||
if xNaN && yNaN {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if xNaN || x < y {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if yNaN || x > y {
|
|
||||||
return +1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
445
vendor/golang.org/x/exp/slices/slices.go
generated
vendored
445
vendor/golang.org/x/exp/slices/slices.go
generated
vendored
@@ -6,9 +6,8 @@
|
|||||||
package slices
|
package slices
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"unsafe"
|
"cmp"
|
||||||
|
"slices"
|
||||||
"golang.org/x/exp/constraints"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Equal reports whether two slices are equal: the same length and all
|
// Equal reports whether two slices are equal: the same length and all
|
||||||
@@ -16,16 +15,10 @@ import (
|
|||||||
// Otherwise, the elements are compared in increasing index order, and the
|
// Otherwise, the elements are compared in increasing index order, and the
|
||||||
// comparison stops at the first unequal pair.
|
// comparison stops at the first unequal pair.
|
||||||
// Floating point NaNs are not considered equal.
|
// Floating point NaNs are not considered equal.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func Equal[S ~[]E, E comparable](s1, s2 S) bool {
|
func Equal[S ~[]E, E comparable](s1, s2 S) bool {
|
||||||
if len(s1) != len(s2) {
|
return slices.Equal(s1, s2)
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := range s1 {
|
|
||||||
if s1[i] != s2[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EqualFunc reports whether two slices are equal using an equality
|
// EqualFunc reports whether two slices are equal using an equality
|
||||||
@@ -33,17 +26,10 @@ func Equal[S ~[]E, E comparable](s1, s2 S) bool {
|
|||||||
// EqualFunc returns false. Otherwise, the elements are compared in
|
// EqualFunc returns false. Otherwise, the elements are compared in
|
||||||
// increasing index order, and the comparison stops at the first index
|
// increasing index order, and the comparison stops at the first index
|
||||||
// for which eq returns false.
|
// for which eq returns false.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool {
|
func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool {
|
||||||
if len(s1) != len(s2) {
|
return slices.EqualFunc(s1, s2, eq)
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, v1 := range s1 {
|
|
||||||
v2 := s2[i]
|
|
||||||
if !eq(v1, v2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare compares the elements of s1 and s2, using [cmp.Compare] on each pair
|
// Compare compares the elements of s1 and s2, using [cmp.Compare] on each pair
|
||||||
@@ -53,20 +39,10 @@ func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) boo
|
|||||||
// If both slices are equal until one of them ends, the shorter slice is
|
// If both slices are equal until one of them ends, the shorter slice is
|
||||||
// considered less than the longer one.
|
// considered less than the longer one.
|
||||||
// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
|
// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
|
||||||
func Compare[S ~[]E, E constraints.Ordered](s1, s2 S) int {
|
//
|
||||||
for i, v1 := range s1 {
|
//go:fix inline
|
||||||
if i >= len(s2) {
|
func Compare[S ~[]E, E cmp.Ordered](s1, s2 S) int {
|
||||||
return +1
|
return slices.Compare(s1, s2)
|
||||||
}
|
|
||||||
v2 := s2[i]
|
|
||||||
if c := cmpCompare(v1, v2); c != 0 {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(s1) < len(s2) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompareFunc is like [Compare] but uses a custom comparison function on each
|
// CompareFunc is like [Compare] but uses a custom comparison function on each
|
||||||
@@ -74,53 +50,41 @@ func Compare[S ~[]E, E constraints.Ordered](s1, s2 S) int {
|
|||||||
// The result is the first non-zero result of cmp; if cmp always
|
// The result is the first non-zero result of cmp; if cmp always
|
||||||
// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
|
// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
|
||||||
// and +1 if len(s1) > len(s2).
|
// and +1 if len(s1) > len(s2).
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int {
|
func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int {
|
||||||
for i, v1 := range s1 {
|
return slices.CompareFunc(s1, s2, cmp)
|
||||||
if i >= len(s2) {
|
|
||||||
return +1
|
|
||||||
}
|
|
||||||
v2 := s2[i]
|
|
||||||
if c := cmp(v1, v2); c != 0 {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(s1) < len(s2) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index returns the index of the first occurrence of v in s,
|
// Index returns the index of the first occurrence of v in s,
|
||||||
// or -1 if not present.
|
// or -1 if not present.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func Index[S ~[]E, E comparable](s S, v E) int {
|
func Index[S ~[]E, E comparable](s S, v E) int {
|
||||||
for i := range s {
|
return slices.Index(s, v)
|
||||||
if v == s[i] {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndexFunc returns the first index i satisfying f(s[i]),
|
// IndexFunc returns the first index i satisfying f(s[i]),
|
||||||
// or -1 if none do.
|
// or -1 if none do.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
|
func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
|
||||||
for i := range s {
|
return slices.IndexFunc(s, f)
|
||||||
if f(s[i]) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains reports whether v is present in s.
|
// Contains reports whether v is present in s.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func Contains[S ~[]E, E comparable](s S, v E) bool {
|
func Contains[S ~[]E, E comparable](s S, v E) bool {
|
||||||
return Index(s, v) >= 0
|
return slices.Contains(s, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainsFunc reports whether at least one
|
// ContainsFunc reports whether at least one
|
||||||
// element e of s satisfies f(e).
|
// element e of s satisfies f(e).
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool {
|
func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool {
|
||||||
return IndexFunc(s, f) >= 0
|
return slices.ContainsFunc(s, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert inserts the values v... into s at index i,
|
// Insert inserts the values v... into s at index i,
|
||||||
@@ -130,370 +94,89 @@ func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool {
|
|||||||
// and r[i+len(v)] == value originally at r[i].
|
// and r[i+len(v)] == value originally at r[i].
|
||||||
// Insert panics if i is out of range.
|
// Insert panics if i is out of range.
|
||||||
// This function is O(len(s) + len(v)).
|
// This function is O(len(s) + len(v)).
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
|
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
|
||||||
m := len(v)
|
return slices.Insert(s, i, v...)
|
||||||
if m == 0 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
n := len(s)
|
|
||||||
if i == n {
|
|
||||||
return append(s, v...)
|
|
||||||
}
|
|
||||||
if n+m > cap(s) {
|
|
||||||
// Use append rather than make so that we bump the size of
|
|
||||||
// the slice up to the next storage class.
|
|
||||||
// This is what Grow does but we don't call Grow because
|
|
||||||
// that might copy the values twice.
|
|
||||||
s2 := append(s[:i], make(S, n+m-i)...)
|
|
||||||
copy(s2[i:], v)
|
|
||||||
copy(s2[i+m:], s[i:])
|
|
||||||
return s2
|
|
||||||
}
|
|
||||||
s = s[:n+m]
|
|
||||||
|
|
||||||
// before:
|
|
||||||
// s: aaaaaaaabbbbccccccccdddd
|
|
||||||
// ^ ^ ^ ^
|
|
||||||
// i i+m n n+m
|
|
||||||
// after:
|
|
||||||
// s: aaaaaaaavvvvbbbbcccccccc
|
|
||||||
// ^ ^ ^ ^
|
|
||||||
// i i+m n n+m
|
|
||||||
//
|
|
||||||
// a are the values that don't move in s.
|
|
||||||
// v are the values copied in from v.
|
|
||||||
// b and c are the values from s that are shifted up in index.
|
|
||||||
// d are the values that get overwritten, never to be seen again.
|
|
||||||
|
|
||||||
if !overlaps(v, s[i+m:]) {
|
|
||||||
// Easy case - v does not overlap either the c or d regions.
|
|
||||||
// (It might be in some of a or b, or elsewhere entirely.)
|
|
||||||
// The data we copy up doesn't write to v at all, so just do it.
|
|
||||||
|
|
||||||
copy(s[i+m:], s[i:])
|
|
||||||
|
|
||||||
// Now we have
|
|
||||||
// s: aaaaaaaabbbbbbbbcccccccc
|
|
||||||
// ^ ^ ^ ^
|
|
||||||
// i i+m n n+m
|
|
||||||
// Note the b values are duplicated.
|
|
||||||
|
|
||||||
copy(s[i:], v)
|
|
||||||
|
|
||||||
// Now we have
|
|
||||||
// s: aaaaaaaavvvvbbbbcccccccc
|
|
||||||
// ^ ^ ^ ^
|
|
||||||
// i i+m n n+m
|
|
||||||
// That's the result we want.
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// The hard case - v overlaps c or d. We can't just shift up
|
|
||||||
// the data because we'd move or clobber the values we're trying
|
|
||||||
// to insert.
|
|
||||||
// So instead, write v on top of d, then rotate.
|
|
||||||
copy(s[n:], v)
|
|
||||||
|
|
||||||
// Now we have
|
|
||||||
// s: aaaaaaaabbbbccccccccvvvv
|
|
||||||
// ^ ^ ^ ^
|
|
||||||
// i i+m n n+m
|
|
||||||
|
|
||||||
rotateRight(s[i:], m)
|
|
||||||
|
|
||||||
// Now we have
|
|
||||||
// s: aaaaaaaavvvvbbbbcccccccc
|
|
||||||
// ^ ^ ^ ^
|
|
||||||
// i i+m n n+m
|
|
||||||
// That's the result we want.
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes the elements s[i:j] from s, returning the modified slice.
|
// Delete removes the elements s[i:j] from s, returning the modified slice.
|
||||||
// Delete panics if s[i:j] is not a valid slice of s.
|
// Delete panics if j > len(s) or s[i:j] is not a valid slice of s.
|
||||||
// Delete is O(len(s)-j), so if many items must be deleted, it is better to
|
// Delete is O(len(s)-i), so if many items must be deleted, it is better to
|
||||||
// make a single call deleting them all together than to delete one at a time.
|
// make a single call deleting them all together than to delete one at a time.
|
||||||
// Delete might not modify the elements s[len(s)-(j-i):len(s)]. If those
|
// Delete zeroes the elements s[len(s)-(j-i):len(s)].
|
||||||
// elements contain pointers you might consider zeroing those elements so that
|
//
|
||||||
// objects they reference can be garbage collected.
|
//go:fix inline
|
||||||
func Delete[S ~[]E, E any](s S, i, j int) S {
|
func Delete[S ~[]E, E any](s S, i, j int) S {
|
||||||
_ = s[i:j] // bounds check
|
return slices.Delete(s, i, j)
|
||||||
|
|
||||||
return append(s[:i], s[j:]...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteFunc removes any elements from s for which del returns true,
|
// DeleteFunc removes any elements from s for which del returns true,
|
||||||
// returning the modified slice.
|
// returning the modified slice.
|
||||||
// When DeleteFunc removes m elements, it might not modify the elements
|
// DeleteFunc zeroes the elements between the new length and the original length.
|
||||||
// s[len(s)-m:len(s)]. If those elements contain pointers you might consider
|
//
|
||||||
// zeroing those elements so that objects they reference can be garbage
|
//go:fix inline
|
||||||
// collected.
|
|
||||||
func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
|
func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
|
||||||
i := IndexFunc(s, del)
|
return slices.DeleteFunc(s, del)
|
||||||
if i == -1 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
// Don't start copying elements until we find one to delete.
|
|
||||||
for j := i + 1; j < len(s); j++ {
|
|
||||||
if v := s[j]; !del(v) {
|
|
||||||
s[i] = v
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s[:i]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace replaces the elements s[i:j] by the given v, and returns the
|
// Replace replaces the elements s[i:j] by the given v, and returns the
|
||||||
// modified slice. Replace panics if s[i:j] is not a valid slice of s.
|
// modified slice. Replace panics if s[i:j] is not a valid slice of s.
|
||||||
|
// When len(v) < (j-i), Replace zeroes the elements between the new length and the original length.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func Replace[S ~[]E, E any](s S, i, j int, v ...E) S {
|
func Replace[S ~[]E, E any](s S, i, j int, v ...E) S {
|
||||||
_ = s[i:j] // verify that i:j is a valid subslice
|
return slices.Replace(s, i, j, v...)
|
||||||
|
|
||||||
if i == j {
|
|
||||||
return Insert(s, i, v...)
|
|
||||||
}
|
|
||||||
if j == len(s) {
|
|
||||||
return append(s[:i], v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
tot := len(s[:i]) + len(v) + len(s[j:])
|
|
||||||
if tot > cap(s) {
|
|
||||||
// Too big to fit, allocate and copy over.
|
|
||||||
s2 := append(s[:i], make(S, tot-i)...) // See Insert
|
|
||||||
copy(s2[i:], v)
|
|
||||||
copy(s2[i+len(v):], s[j:])
|
|
||||||
return s2
|
|
||||||
}
|
|
||||||
|
|
||||||
r := s[:tot]
|
|
||||||
|
|
||||||
if i+len(v) <= j {
|
|
||||||
// Easy, as v fits in the deleted portion.
|
|
||||||
copy(r[i:], v)
|
|
||||||
if i+len(v) != j {
|
|
||||||
copy(r[i+len(v):], s[j:])
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are expanding (v is bigger than j-i).
|
|
||||||
// The situation is something like this:
|
|
||||||
// (example has i=4,j=8,len(s)=16,len(v)=6)
|
|
||||||
// s: aaaaxxxxbbbbbbbbyy
|
|
||||||
// ^ ^ ^ ^
|
|
||||||
// i j len(s) tot
|
|
||||||
// a: prefix of s
|
|
||||||
// x: deleted range
|
|
||||||
// b: more of s
|
|
||||||
// y: area to expand into
|
|
||||||
|
|
||||||
if !overlaps(r[i+len(v):], v) {
|
|
||||||
// Easy, as v is not clobbered by the first copy.
|
|
||||||
copy(r[i+len(v):], s[j:])
|
|
||||||
copy(r[i:], v)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a situation where we don't have a single place to which
|
|
||||||
// we can copy v. Parts of it need to go to two different places.
|
|
||||||
// We want to copy the prefix of v into y and the suffix into x, then
|
|
||||||
// rotate |y| spots to the right.
|
|
||||||
//
|
|
||||||
// v[2:] v[:2]
|
|
||||||
// | |
|
|
||||||
// s: aaaavvvvbbbbbbbbvv
|
|
||||||
// ^ ^ ^ ^
|
|
||||||
// i j len(s) tot
|
|
||||||
//
|
|
||||||
// If either of those two destinations don't alias v, then we're good.
|
|
||||||
y := len(v) - (j - i) // length of y portion
|
|
||||||
|
|
||||||
if !overlaps(r[i:j], v) {
|
|
||||||
copy(r[i:j], v[y:])
|
|
||||||
copy(r[len(s):], v[:y])
|
|
||||||
rotateRight(r[i:], y)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
if !overlaps(r[len(s):], v) {
|
|
||||||
copy(r[len(s):], v[:y])
|
|
||||||
copy(r[i:j], v[y:])
|
|
||||||
rotateRight(r[i:], y)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we know that v overlaps both x and y.
|
|
||||||
// That means that the entirety of b is *inside* v.
|
|
||||||
// So we don't need to preserve b at all; instead we
|
|
||||||
// can copy v first, then copy the b part of v out of
|
|
||||||
// v to the right destination.
|
|
||||||
k := startIdx(v, s[j:])
|
|
||||||
copy(r[i:], v)
|
|
||||||
copy(r[i+len(v):], r[i+k:])
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone returns a copy of the slice.
|
// Clone returns a copy of the slice.
|
||||||
// The elements are copied using assignment, so this is a shallow clone.
|
// The elements are copied using assignment, so this is a shallow clone.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func Clone[S ~[]E, E any](s S) S {
|
func Clone[S ~[]E, E any](s S) S {
|
||||||
// Preserve nil in case it matters.
|
return slices.Clone(s)
|
||||||
if s == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return append(S([]E{}), s...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compact replaces consecutive runs of equal elements with a single copy.
|
// Compact replaces consecutive runs of equal elements with a single copy.
|
||||||
// This is like the uniq command found on Unix.
|
// This is like the uniq command found on Unix.
|
||||||
// Compact modifies the contents of the slice s and returns the modified slice,
|
// Compact modifies the contents of the slice s and returns the modified slice,
|
||||||
// which may have a smaller length.
|
// which may have a smaller length.
|
||||||
// When Compact discards m elements in total, it might not modify the elements
|
// Compact zeroes the elements between the new length and the original length.
|
||||||
// s[len(s)-m:len(s)]. If those elements contain pointers you might consider
|
//
|
||||||
// zeroing those elements so that objects they reference can be garbage collected.
|
//go:fix inline
|
||||||
func Compact[S ~[]E, E comparable](s S) S {
|
func Compact[S ~[]E, E comparable](s S) S {
|
||||||
if len(s) < 2 {
|
return slices.Compact(s)
|
||||||
return s
|
|
||||||
}
|
|
||||||
i := 1
|
|
||||||
for k := 1; k < len(s); k++ {
|
|
||||||
if s[k] != s[k-1] {
|
|
||||||
if i != k {
|
|
||||||
s[i] = s[k]
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s[:i]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompactFunc is like [Compact] but uses an equality function to compare elements.
|
// CompactFunc is like [Compact] but uses an equality function to compare elements.
|
||||||
// For runs of elements that compare equal, CompactFunc keeps the first one.
|
// For runs of elements that compare equal, CompactFunc keeps the first one.
|
||||||
|
// CompactFunc zeroes the elements between the new length and the original length.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
|
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
|
||||||
if len(s) < 2 {
|
return slices.CompactFunc(s, eq)
|
||||||
return s
|
|
||||||
}
|
|
||||||
i := 1
|
|
||||||
for k := 1; k < len(s); k++ {
|
|
||||||
if !eq(s[k], s[k-1]) {
|
|
||||||
if i != k {
|
|
||||||
s[i] = s[k]
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s[:i]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grow increases the slice's capacity, if necessary, to guarantee space for
|
// Grow increases the slice's capacity, if necessary, to guarantee space for
|
||||||
// another n elements. After Grow(n), at least n elements can be appended
|
// another n elements. After Grow(n), at least n elements can be appended
|
||||||
// to the slice without another allocation. If n is negative or too large to
|
// to the slice without another allocation. If n is negative or too large to
|
||||||
// allocate the memory, Grow panics.
|
// allocate the memory, Grow panics.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func Grow[S ~[]E, E any](s S, n int) S {
|
func Grow[S ~[]E, E any](s S, n int) S {
|
||||||
if n < 0 {
|
return slices.Grow(s, n)
|
||||||
panic("cannot be negative")
|
|
||||||
}
|
|
||||||
if n -= cap(s) - len(s); n > 0 {
|
|
||||||
// TODO(https://go.dev/issue/53888): Make using []E instead of S
|
|
||||||
// to workaround a compiler bug where the runtime.growslice optimization
|
|
||||||
// does not take effect. Revert when the compiler is fixed.
|
|
||||||
s = append([]E(s)[:cap(s)], make([]E, n)...)[:len(s)]
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
|
// Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func Clip[S ~[]E, E any](s S) S {
|
func Clip[S ~[]E, E any](s S) S {
|
||||||
return s[:len(s):len(s)]
|
return slices.Clip(s)
|
||||||
}
|
|
||||||
|
|
||||||
// Rotation algorithm explanation:
|
|
||||||
//
|
|
||||||
// rotate left by 2
|
|
||||||
// start with
|
|
||||||
// 0123456789
|
|
||||||
// split up like this
|
|
||||||
// 01 234567 89
|
|
||||||
// swap first 2 and last 2
|
|
||||||
// 89 234567 01
|
|
||||||
// join first parts
|
|
||||||
// 89234567 01
|
|
||||||
// recursively rotate first left part by 2
|
|
||||||
// 23456789 01
|
|
||||||
// join at the end
|
|
||||||
// 2345678901
|
|
||||||
//
|
|
||||||
// rotate left by 8
|
|
||||||
// start with
|
|
||||||
// 0123456789
|
|
||||||
// split up like this
|
|
||||||
// 01 234567 89
|
|
||||||
// swap first 2 and last 2
|
|
||||||
// 89 234567 01
|
|
||||||
// join last parts
|
|
||||||
// 89 23456701
|
|
||||||
// recursively rotate second part left by 6
|
|
||||||
// 89 01234567
|
|
||||||
// join at the end
|
|
||||||
// 8901234567
|
|
||||||
|
|
||||||
// TODO: There are other rotate algorithms.
|
|
||||||
// This algorithm has the desirable property that it moves each element exactly twice.
|
|
||||||
// The triple-reverse algorithm is simpler and more cache friendly, but takes more writes.
|
|
||||||
// The follow-cycles algorithm can be 1-write but it is not very cache friendly.
|
|
||||||
|
|
||||||
// rotateLeft rotates b left by n spaces.
|
|
||||||
// s_final[i] = s_orig[i+r], wrapping around.
|
|
||||||
func rotateLeft[E any](s []E, r int) {
|
|
||||||
for r != 0 && r != len(s) {
|
|
||||||
if r*2 <= len(s) {
|
|
||||||
swap(s[:r], s[len(s)-r:])
|
|
||||||
s = s[:len(s)-r]
|
|
||||||
} else {
|
|
||||||
swap(s[:len(s)-r], s[r:])
|
|
||||||
s, r = s[len(s)-r:], r*2-len(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func rotateRight[E any](s []E, r int) {
|
|
||||||
rotateLeft(s, len(s)-r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// swap swaps the contents of x and y. x and y must be equal length and disjoint.
|
|
||||||
func swap[E any](x, y []E) {
|
|
||||||
for i := 0; i < len(x); i++ {
|
|
||||||
x[i], y[i] = y[i], x[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// overlaps reports whether the memory ranges a[0:len(a)] and b[0:len(b)] overlap.
|
|
||||||
func overlaps[E any](a, b []E) bool {
|
|
||||||
if len(a) == 0 || len(b) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
elemSize := unsafe.Sizeof(a[0])
|
|
||||||
if elemSize == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// TODO: use a runtime/unsafe facility once one becomes available. See issue 12445.
|
|
||||||
// Also see crypto/internal/alias/alias.go:AnyOverlap
|
|
||||||
return uintptr(unsafe.Pointer(&a[0])) <= uintptr(unsafe.Pointer(&b[len(b)-1]))+(elemSize-1) &&
|
|
||||||
uintptr(unsafe.Pointer(&b[0])) <= uintptr(unsafe.Pointer(&a[len(a)-1]))+(elemSize-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// startIdx returns the index in haystack where the needle starts.
|
|
||||||
// prerequisite: the needle must be aliased entirely inside the haystack.
|
|
||||||
func startIdx[E any](haystack, needle []E) int {
|
|
||||||
p := &needle[0]
|
|
||||||
for i := range haystack {
|
|
||||||
if p == &haystack[i] {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: what if the overlap is by a non-integral number of Es?
|
|
||||||
panic("needle not found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse reverses the elements of the slice in place.
|
// Reverse reverses the elements of the slice in place.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func Reverse[S ~[]E, E any](s S) {
|
func Reverse[S ~[]E, E any](s S) {
|
||||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
slices.Reverse(s)
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
166
vendor/golang.org/x/exp/slices/sort.go
generated
vendored
166
vendor/golang.org/x/exp/slices/sort.go
generated
vendored
@@ -2,143 +2,103 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:generate go run $GOROOT/src/sort/gen_sort_variants.go -exp
|
|
||||||
|
|
||||||
package slices
|
package slices
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/bits"
|
"cmp"
|
||||||
|
"slices"
|
||||||
"golang.org/x/exp/constraints"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sort sorts a slice of any ordered type in ascending order.
|
// Sort sorts a slice of any ordered type in ascending order.
|
||||||
// When sorting floating-point numbers, NaNs are ordered before other values.
|
// When sorting floating-point numbers, NaNs are ordered before other values.
|
||||||
func Sort[S ~[]E, E constraints.Ordered](x S) {
|
//
|
||||||
n := len(x)
|
//go:fix inline
|
||||||
pdqsortOrdered(x, 0, n, bits.Len(uint(n)))
|
func Sort[S ~[]E, E cmp.Ordered](x S) {
|
||||||
|
slices.Sort(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortFunc sorts the slice x in ascending order as determined by the cmp
|
// SortFunc sorts the slice x in ascending order as determined by the cmp
|
||||||
// function. This sort is not guaranteed to be stable.
|
// function. This sort is not guaranteed to be stable.
|
||||||
// cmp(a, b) should return a negative number when a < b, a positive number when
|
// cmp(a, b) should return a negative number when a < b, a positive number when
|
||||||
// a > b and zero when a == b.
|
// a > b and zero when a == b or when a is not comparable to b in the sense
|
||||||
|
// of the formal definition of Strict Weak Ordering.
|
||||||
//
|
//
|
||||||
// SortFunc requires that cmp is a strict weak ordering.
|
// SortFunc requires that cmp is a strict weak ordering.
|
||||||
// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.
|
// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.
|
||||||
|
// To indicate 'uncomparable', return 0 from the function.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
|
func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
|
||||||
n := len(x)
|
slices.SortFunc(x, cmp)
|
||||||
pdqsortCmpFunc(x, 0, n, bits.Len(uint(n)), cmp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortStableFunc sorts the slice x while keeping the original order of equal
|
// SortStableFunc sorts the slice x while keeping the original order of equal
|
||||||
// elements, using cmp to compare elements in the same way as [SortFunc].
|
// elements, using cmp to compare elements in the same way as [SortFunc].
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
|
func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
|
||||||
stableCmpFunc(x, len(x), cmp)
|
slices.SortStableFunc(x, cmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSorted reports whether x is sorted in ascending order.
|
// IsSorted reports whether x is sorted in ascending order.
|
||||||
func IsSorted[S ~[]E, E constraints.Ordered](x S) bool {
|
//
|
||||||
for i := len(x) - 1; i > 0; i-- {
|
//go:fix inline
|
||||||
if cmpLess(x[i], x[i-1]) {
|
func IsSorted[S ~[]E, E cmp.Ordered](x S) bool {
|
||||||
return false
|
return slices.IsSorted(x)
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSortedFunc reports whether x is sorted in ascending order, with cmp as the
|
// IsSortedFunc reports whether x is sorted in ascending order, with cmp as the
|
||||||
// comparison function as defined by [SortFunc].
|
// comparison function as defined by [SortFunc].
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool {
|
func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool {
|
||||||
for i := len(x) - 1; i > 0; i-- {
|
return slices.IsSortedFunc(x, cmp)
|
||||||
if cmp(x[i], x[i-1]) < 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Min returns the minimal value in x. It panics if x is empty.
|
// Min returns the minimal value in x. It panics if x is empty.
|
||||||
// For floating-point numbers, Min propagates NaNs (any NaN value in x
|
// For floating-point numbers, Min propagates NaNs (any NaN value in x
|
||||||
// forces the output to be NaN).
|
// forces the output to be NaN).
|
||||||
func Min[S ~[]E, E constraints.Ordered](x S) E {
|
//
|
||||||
if len(x) < 1 {
|
//go:fix inline
|
||||||
panic("slices.Min: empty list")
|
func Min[S ~[]E, E cmp.Ordered](x S) E {
|
||||||
}
|
return slices.Min(x)
|
||||||
m := x[0]
|
|
||||||
for i := 1; i < len(x); i++ {
|
|
||||||
m = min(m, x[i])
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MinFunc returns the minimal value in x, using cmp to compare elements.
|
// MinFunc returns the minimal value in x, using cmp to compare elements.
|
||||||
// It panics if x is empty. If there is more than one minimal element
|
// It panics if x is empty. If there is more than one minimal element
|
||||||
// according to the cmp function, MinFunc returns the first one.
|
// according to the cmp function, MinFunc returns the first one.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
|
func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
|
||||||
if len(x) < 1 {
|
return slices.MinFunc(x, cmp)
|
||||||
panic("slices.MinFunc: empty list")
|
|
||||||
}
|
|
||||||
m := x[0]
|
|
||||||
for i := 1; i < len(x); i++ {
|
|
||||||
if cmp(x[i], m) < 0 {
|
|
||||||
m = x[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max returns the maximal value in x. It panics if x is empty.
|
// Max returns the maximal value in x. It panics if x is empty.
|
||||||
// For floating-point E, Max propagates NaNs (any NaN value in x
|
// For floating-point E, Max propagates NaNs (any NaN value in x
|
||||||
// forces the output to be NaN).
|
// forces the output to be NaN).
|
||||||
func Max[S ~[]E, E constraints.Ordered](x S) E {
|
//
|
||||||
if len(x) < 1 {
|
//go:fix inline
|
||||||
panic("slices.Max: empty list")
|
func Max[S ~[]E, E cmp.Ordered](x S) E {
|
||||||
}
|
return slices.Max(x)
|
||||||
m := x[0]
|
|
||||||
for i := 1; i < len(x); i++ {
|
|
||||||
m = max(m, x[i])
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxFunc returns the maximal value in x, using cmp to compare elements.
|
// MaxFunc returns the maximal value in x, using cmp to compare elements.
|
||||||
// It panics if x is empty. If there is more than one maximal element
|
// It panics if x is empty. If there is more than one maximal element
|
||||||
// according to the cmp function, MaxFunc returns the first one.
|
// according to the cmp function, MaxFunc returns the first one.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
|
func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
|
||||||
if len(x) < 1 {
|
return slices.MaxFunc(x, cmp)
|
||||||
panic("slices.MaxFunc: empty list")
|
|
||||||
}
|
|
||||||
m := x[0]
|
|
||||||
for i := 1; i < len(x); i++ {
|
|
||||||
if cmp(x[i], m) > 0 {
|
|
||||||
m = x[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BinarySearch searches for target in a sorted slice and returns the position
|
// BinarySearch searches for target in a sorted slice and returns the position
|
||||||
// where target is found, or the position where target would appear in the
|
// where target is found, or the position where target would appear in the
|
||||||
// sort order; it also returns a bool saying whether the target is really found
|
// sort order; it also returns a bool saying whether the target is really found
|
||||||
// in the slice. The slice must be sorted in increasing order.
|
// in the slice. The slice must be sorted in increasing order.
|
||||||
func BinarySearch[S ~[]E, E constraints.Ordered](x S, target E) (int, bool) {
|
//
|
||||||
// Inlining is faster than calling BinarySearchFunc with a lambda.
|
//go:fix inline
|
||||||
n := len(x)
|
func BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) {
|
||||||
// Define x[-1] < target and x[n] >= target.
|
return slices.BinarySearch(x, target)
|
||||||
// Invariant: x[i-1] < target, x[j] >= target.
|
|
||||||
i, j := 0, n
|
|
||||||
for i < j {
|
|
||||||
h := int(uint(i+j) >> 1) // avoid overflow when computing h
|
|
||||||
// i ≤ h < j
|
|
||||||
if cmpLess(x[h], target) {
|
|
||||||
i = h + 1 // preserves x[i-1] < target
|
|
||||||
} else {
|
|
||||||
j = h // preserves x[j] >= target
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// i == j, x[i-1] < target, and x[j] (= x[i]) >= target => answer is i.
|
|
||||||
return i, i < n && (x[i] == target || (isNaN(x[i]) && isNaN(target)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BinarySearchFunc works like [BinarySearch], but uses a custom comparison
|
// BinarySearchFunc works like [BinarySearch], but uses a custom comparison
|
||||||
@@ -148,48 +108,8 @@ func BinarySearch[S ~[]E, E constraints.Ordered](x S, target E) (int, bool) {
|
|||||||
// or a positive number if the slice element follows the target.
|
// or a positive number if the slice element follows the target.
|
||||||
// cmp must implement the same ordering as the slice, such that if
|
// cmp must implement the same ordering as the slice, such that if
|
||||||
// cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice.
|
// cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) {
|
func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) {
|
||||||
n := len(x)
|
return slices.BinarySearchFunc(x, target, cmp)
|
||||||
// Define cmp(x[-1], target) < 0 and cmp(x[n], target) >= 0 .
|
|
||||||
// Invariant: cmp(x[i - 1], target) < 0, cmp(x[j], target) >= 0.
|
|
||||||
i, j := 0, n
|
|
||||||
for i < j {
|
|
||||||
h := int(uint(i+j) >> 1) // avoid overflow when computing h
|
|
||||||
// i ≤ h < j
|
|
||||||
if cmp(x[h], target) < 0 {
|
|
||||||
i = h + 1 // preserves cmp(x[i - 1], target) < 0
|
|
||||||
} else {
|
|
||||||
j = h // preserves cmp(x[j], target) >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// i == j, cmp(x[i-1], target) < 0, and cmp(x[j], target) (= cmp(x[i], target)) >= 0 => answer is i.
|
|
||||||
return i, i < n && cmp(x[i], target) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortedHint int // hint for pdqsort when choosing the pivot
|
|
||||||
|
|
||||||
const (
|
|
||||||
unknownHint sortedHint = iota
|
|
||||||
increasingHint
|
|
||||||
decreasingHint
|
|
||||||
)
|
|
||||||
|
|
||||||
// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf
|
|
||||||
type xorshift uint64
|
|
||||||
|
|
||||||
func (r *xorshift) Next() uint64 {
|
|
||||||
*r ^= *r << 13
|
|
||||||
*r ^= *r >> 17
|
|
||||||
*r ^= *r << 5
|
|
||||||
return uint64(*r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextPowerOfTwo(length int) uint {
|
|
||||||
return 1 << bits.Len(uint(length))
|
|
||||||
}
|
|
||||||
|
|
||||||
// isNaN reports whether x is a NaN without requiring the math package.
|
|
||||||
// This will always return false if T is not floating-point.
|
|
||||||
func isNaN[T constraints.Ordered](x T) bool {
|
|
||||||
return x != x
|
|
||||||
}
|
}
|
||||||
|
|||||||
479
vendor/golang.org/x/exp/slices/zsortanyfunc.go
generated
vendored
479
vendor/golang.org/x/exp/slices/zsortanyfunc.go
generated
vendored
@@ -1,479 +0,0 @@
|
|||||||
// Code generated by gen_sort_variants.go; DO NOT EDIT.
|
|
||||||
|
|
||||||
// Copyright 2022 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package slices
|
|
||||||
|
|
||||||
// insertionSortCmpFunc sorts data[a:b] using insertion sort.
|
|
||||||
func insertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
|
|
||||||
for i := a + 1; i < b; i++ {
|
|
||||||
for j := i; j > a && (cmp(data[j], data[j-1]) < 0); j-- {
|
|
||||||
data[j], data[j-1] = data[j-1], data[j]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// siftDownCmpFunc implements the heap property on data[lo:hi].
|
|
||||||
// first is an offset into the array where the root of the heap lies.
|
|
||||||
func siftDownCmpFunc[E any](data []E, lo, hi, first int, cmp func(a, b E) int) {
|
|
||||||
root := lo
|
|
||||||
for {
|
|
||||||
child := 2*root + 1
|
|
||||||
if child >= hi {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if child+1 < hi && (cmp(data[first+child], data[first+child+1]) < 0) {
|
|
||||||
child++
|
|
||||||
}
|
|
||||||
if !(cmp(data[first+root], data[first+child]) < 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data[first+root], data[first+child] = data[first+child], data[first+root]
|
|
||||||
root = child
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func heapSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
|
|
||||||
first := a
|
|
||||||
lo := 0
|
|
||||||
hi := b - a
|
|
||||||
|
|
||||||
// Build heap with greatest element at top.
|
|
||||||
for i := (hi - 1) / 2; i >= 0; i-- {
|
|
||||||
siftDownCmpFunc(data, i, hi, first, cmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop elements, largest first, into end of data.
|
|
||||||
for i := hi - 1; i >= 0; i-- {
|
|
||||||
data[first], data[first+i] = data[first+i], data[first]
|
|
||||||
siftDownCmpFunc(data, lo, i, first, cmp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pdqsortCmpFunc sorts data[a:b].
|
|
||||||
// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort.
|
|
||||||
// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf
|
|
||||||
// C++ implementation: https://github.com/orlp/pdqsort
|
|
||||||
// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/
|
|
||||||
// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort.
|
|
||||||
func pdqsortCmpFunc[E any](data []E, a, b, limit int, cmp func(a, b E) int) {
|
|
||||||
const maxInsertion = 12
|
|
||||||
|
|
||||||
var (
|
|
||||||
wasBalanced = true // whether the last partitioning was reasonably balanced
|
|
||||||
wasPartitioned = true // whether the slice was already partitioned
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
length := b - a
|
|
||||||
|
|
||||||
if length <= maxInsertion {
|
|
||||||
insertionSortCmpFunc(data, a, b, cmp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to heapsort if too many bad choices were made.
|
|
||||||
if limit == 0 {
|
|
||||||
heapSortCmpFunc(data, a, b, cmp)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the last partitioning was imbalanced, we need to breaking patterns.
|
|
||||||
if !wasBalanced {
|
|
||||||
breakPatternsCmpFunc(data, a, b, cmp)
|
|
||||||
limit--
|
|
||||||
}
|
|
||||||
|
|
||||||
pivot, hint := choosePivotCmpFunc(data, a, b, cmp)
|
|
||||||
if hint == decreasingHint {
|
|
||||||
reverseRangeCmpFunc(data, a, b, cmp)
|
|
||||||
// The chosen pivot was pivot-a elements after the start of the array.
|
|
||||||
// After reversing it is pivot-a elements before the end of the array.
|
|
||||||
// The idea came from Rust's implementation.
|
|
||||||
pivot = (b - 1) - (pivot - a)
|
|
||||||
hint = increasingHint
|
|
||||||
}
|
|
||||||
|
|
||||||
// The slice is likely already sorted.
|
|
||||||
if wasBalanced && wasPartitioned && hint == increasingHint {
|
|
||||||
if partialInsertionSortCmpFunc(data, a, b, cmp) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probably the slice contains many duplicate elements, partition the slice into
|
|
||||||
// elements equal to and elements greater than the pivot.
|
|
||||||
if a > 0 && !(cmp(data[a-1], data[pivot]) < 0) {
|
|
||||||
mid := partitionEqualCmpFunc(data, a, b, pivot, cmp)
|
|
||||||
a = mid
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
mid, alreadyPartitioned := partitionCmpFunc(data, a, b, pivot, cmp)
|
|
||||||
wasPartitioned = alreadyPartitioned
|
|
||||||
|
|
||||||
leftLen, rightLen := mid-a, b-mid
|
|
||||||
balanceThreshold := length / 8
|
|
||||||
if leftLen < rightLen {
|
|
||||||
wasBalanced = leftLen >= balanceThreshold
|
|
||||||
pdqsortCmpFunc(data, a, mid, limit, cmp)
|
|
||||||
a = mid + 1
|
|
||||||
} else {
|
|
||||||
wasBalanced = rightLen >= balanceThreshold
|
|
||||||
pdqsortCmpFunc(data, mid+1, b, limit, cmp)
|
|
||||||
b = mid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// partitionCmpFunc does one quicksort partition.
|
|
||||||
// Let p = data[pivot]
|
|
||||||
// Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot.
|
|
||||||
// On return, data[newpivot] = p
|
|
||||||
func partitionCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int, alreadyPartitioned bool) {
|
|
||||||
data[a], data[pivot] = data[pivot], data[a]
|
|
||||||
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
|
|
||||||
|
|
||||||
for i <= j && (cmp(data[i], data[a]) < 0) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
for i <= j && !(cmp(data[j], data[a]) < 0) {
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
if i > j {
|
|
||||||
data[j], data[a] = data[a], data[j]
|
|
||||||
return j, true
|
|
||||||
}
|
|
||||||
data[i], data[j] = data[j], data[i]
|
|
||||||
i++
|
|
||||||
j--
|
|
||||||
|
|
||||||
for {
|
|
||||||
for i <= j && (cmp(data[i], data[a]) < 0) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
for i <= j && !(cmp(data[j], data[a]) < 0) {
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
if i > j {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
data[i], data[j] = data[j], data[i]
|
|
||||||
i++
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
data[j], data[a] = data[a], data[j]
|
|
||||||
return j, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// partitionEqualCmpFunc partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot].
|
|
||||||
// It assumed that data[a:b] does not contain elements smaller than the data[pivot].
|
|
||||||
func partitionEqualCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int) {
|
|
||||||
data[a], data[pivot] = data[pivot], data[a]
|
|
||||||
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
|
|
||||||
|
|
||||||
for {
|
|
||||||
for i <= j && !(cmp(data[a], data[i]) < 0) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
for i <= j && (cmp(data[a], data[j]) < 0) {
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
if i > j {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
data[i], data[j] = data[j], data[i]
|
|
||||||
i++
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// partialInsertionSortCmpFunc partially sorts a slice, returns true if the slice is sorted at the end.
|
|
||||||
func partialInsertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) bool {
|
|
||||||
const (
|
|
||||||
maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted
|
|
||||||
shortestShifting = 50 // don't shift any elements on short arrays
|
|
||||||
)
|
|
||||||
i := a + 1
|
|
||||||
for j := 0; j < maxSteps; j++ {
|
|
||||||
for i < b && !(cmp(data[i], data[i-1]) < 0) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == b {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if b-a < shortestShifting {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
data[i], data[i-1] = data[i-1], data[i]
|
|
||||||
|
|
||||||
// Shift the smaller one to the left.
|
|
||||||
if i-a >= 2 {
|
|
||||||
for j := i - 1; j >= 1; j-- {
|
|
||||||
if !(cmp(data[j], data[j-1]) < 0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
data[j], data[j-1] = data[j-1], data[j]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Shift the greater one to the right.
|
|
||||||
if b-i >= 2 {
|
|
||||||
for j := i + 1; j < b; j++ {
|
|
||||||
if !(cmp(data[j], data[j-1]) < 0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
data[j], data[j-1] = data[j-1], data[j]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// breakPatternsCmpFunc scatters some elements around in an attempt to break some patterns
|
|
||||||
// that might cause imbalanced partitions in quicksort.
|
|
||||||
func breakPatternsCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
|
|
||||||
length := b - a
|
|
||||||
if length >= 8 {
|
|
||||||
random := xorshift(length)
|
|
||||||
modulus := nextPowerOfTwo(length)
|
|
||||||
|
|
||||||
for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ {
|
|
||||||
other := int(uint(random.Next()) & (modulus - 1))
|
|
||||||
if other >= length {
|
|
||||||
other -= length
|
|
||||||
}
|
|
||||||
data[idx], data[a+other] = data[a+other], data[idx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// choosePivotCmpFunc chooses a pivot in data[a:b].
|
|
||||||
//
|
|
||||||
// [0,8): chooses a static pivot.
|
|
||||||
// [8,shortestNinther): uses the simple median-of-three method.
|
|
||||||
// [shortestNinther,∞): uses the Tukey ninther method.
|
|
||||||
func choosePivotCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) (pivot int, hint sortedHint) {
|
|
||||||
const (
|
|
||||||
shortestNinther = 50
|
|
||||||
maxSwaps = 4 * 3
|
|
||||||
)
|
|
||||||
|
|
||||||
l := b - a
|
|
||||||
|
|
||||||
var (
|
|
||||||
swaps int
|
|
||||||
i = a + l/4*1
|
|
||||||
j = a + l/4*2
|
|
||||||
k = a + l/4*3
|
|
||||||
)
|
|
||||||
|
|
||||||
if l >= 8 {
|
|
||||||
if l >= shortestNinther {
|
|
||||||
// Tukey ninther method, the idea came from Rust's implementation.
|
|
||||||
i = medianAdjacentCmpFunc(data, i, &swaps, cmp)
|
|
||||||
j = medianAdjacentCmpFunc(data, j, &swaps, cmp)
|
|
||||||
k = medianAdjacentCmpFunc(data, k, &swaps, cmp)
|
|
||||||
}
|
|
||||||
// Find the median among i, j, k and stores it into j.
|
|
||||||
j = medianCmpFunc(data, i, j, k, &swaps, cmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch swaps {
|
|
||||||
case 0:
|
|
||||||
return j, increasingHint
|
|
||||||
case maxSwaps:
|
|
||||||
return j, decreasingHint
|
|
||||||
default:
|
|
||||||
return j, unknownHint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// order2CmpFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a.
|
|
||||||
func order2CmpFunc[E any](data []E, a, b int, swaps *int, cmp func(a, b E) int) (int, int) {
|
|
||||||
if cmp(data[b], data[a]) < 0 {
|
|
||||||
*swaps++
|
|
||||||
return b, a
|
|
||||||
}
|
|
||||||
return a, b
|
|
||||||
}
|
|
||||||
|
|
||||||
// medianCmpFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c.
|
|
||||||
func medianCmpFunc[E any](data []E, a, b, c int, swaps *int, cmp func(a, b E) int) int {
|
|
||||||
a, b = order2CmpFunc(data, a, b, swaps, cmp)
|
|
||||||
b, c = order2CmpFunc(data, b, c, swaps, cmp)
|
|
||||||
a, b = order2CmpFunc(data, a, b, swaps, cmp)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// medianAdjacentCmpFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a.
|
|
||||||
func medianAdjacentCmpFunc[E any](data []E, a int, swaps *int, cmp func(a, b E) int) int {
|
|
||||||
return medianCmpFunc(data, a-1, a, a+1, swaps, cmp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reverseRangeCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
|
|
||||||
i := a
|
|
||||||
j := b - 1
|
|
||||||
for i < j {
|
|
||||||
data[i], data[j] = data[j], data[i]
|
|
||||||
i++
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func swapRangeCmpFunc[E any](data []E, a, b, n int, cmp func(a, b E) int) {
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
data[a+i], data[b+i] = data[b+i], data[a+i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stableCmpFunc[E any](data []E, n int, cmp func(a, b E) int) {
|
|
||||||
blockSize := 20 // must be > 0
|
|
||||||
a, b := 0, blockSize
|
|
||||||
for b <= n {
|
|
||||||
insertionSortCmpFunc(data, a, b, cmp)
|
|
||||||
a = b
|
|
||||||
b += blockSize
|
|
||||||
}
|
|
||||||
insertionSortCmpFunc(data, a, n, cmp)
|
|
||||||
|
|
||||||
for blockSize < n {
|
|
||||||
a, b = 0, 2*blockSize
|
|
||||||
for b <= n {
|
|
||||||
symMergeCmpFunc(data, a, a+blockSize, b, cmp)
|
|
||||||
a = b
|
|
||||||
b += 2 * blockSize
|
|
||||||
}
|
|
||||||
if m := a + blockSize; m < n {
|
|
||||||
symMergeCmpFunc(data, a, m, n, cmp)
|
|
||||||
}
|
|
||||||
blockSize *= 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// symMergeCmpFunc merges the two sorted subsequences data[a:m] and data[m:b] using
|
|
||||||
// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
|
|
||||||
// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
|
|
||||||
// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
|
|
||||||
// Computer Science, pages 714-723. Springer, 2004.
|
|
||||||
//
|
|
||||||
// Let M = m-a and N = b-n. Wolog M < N.
|
|
||||||
// The recursion depth is bound by ceil(log(N+M)).
|
|
||||||
// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
|
|
||||||
// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
|
|
||||||
//
|
|
||||||
// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
|
|
||||||
// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
|
|
||||||
// in the paper carries through for Swap operations, especially as the block
|
|
||||||
// swapping rotate uses only O(M+N) Swaps.
|
|
||||||
//
|
|
||||||
// symMerge assumes non-degenerate arguments: a < m && m < b.
|
|
||||||
// Having the caller check this condition eliminates many leaf recursion calls,
|
|
||||||
// which improves performance.
|
|
||||||
func symMergeCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) {
|
|
||||||
// Avoid unnecessary recursions of symMerge
|
|
||||||
// by direct insertion of data[a] into data[m:b]
|
|
||||||
// if data[a:m] only contains one element.
|
|
||||||
if m-a == 1 {
|
|
||||||
// Use binary search to find the lowest index i
|
|
||||||
// such that data[i] >= data[a] for m <= i < b.
|
|
||||||
// Exit the search loop with i == b in case no such index exists.
|
|
||||||
i := m
|
|
||||||
j := b
|
|
||||||
for i < j {
|
|
||||||
h := int(uint(i+j) >> 1)
|
|
||||||
if cmp(data[h], data[a]) < 0 {
|
|
||||||
i = h + 1
|
|
||||||
} else {
|
|
||||||
j = h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Swap values until data[a] reaches the position before i.
|
|
||||||
for k := a; k < i-1; k++ {
|
|
||||||
data[k], data[k+1] = data[k+1], data[k]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid unnecessary recursions of symMerge
|
|
||||||
// by direct insertion of data[m] into data[a:m]
|
|
||||||
// if data[m:b] only contains one element.
|
|
||||||
if b-m == 1 {
|
|
||||||
// Use binary search to find the lowest index i
|
|
||||||
// such that data[i] > data[m] for a <= i < m.
|
|
||||||
// Exit the search loop with i == m in case no such index exists.
|
|
||||||
i := a
|
|
||||||
j := m
|
|
||||||
for i < j {
|
|
||||||
h := int(uint(i+j) >> 1)
|
|
||||||
if !(cmp(data[m], data[h]) < 0) {
|
|
||||||
i = h + 1
|
|
||||||
} else {
|
|
||||||
j = h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Swap values until data[m] reaches the position i.
|
|
||||||
for k := m; k > i; k-- {
|
|
||||||
data[k], data[k-1] = data[k-1], data[k]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mid := int(uint(a+b) >> 1)
|
|
||||||
n := mid + m
|
|
||||||
var start, r int
|
|
||||||
if m > mid {
|
|
||||||
start = n - b
|
|
||||||
r = mid
|
|
||||||
} else {
|
|
||||||
start = a
|
|
||||||
r = m
|
|
||||||
}
|
|
||||||
p := n - 1
|
|
||||||
|
|
||||||
for start < r {
|
|
||||||
c := int(uint(start+r) >> 1)
|
|
||||||
if !(cmp(data[p-c], data[c]) < 0) {
|
|
||||||
start = c + 1
|
|
||||||
} else {
|
|
||||||
r = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end := n - start
|
|
||||||
if start < m && m < end {
|
|
||||||
rotateCmpFunc(data, start, m, end, cmp)
|
|
||||||
}
|
|
||||||
if a < start && start < mid {
|
|
||||||
symMergeCmpFunc(data, a, start, mid, cmp)
|
|
||||||
}
|
|
||||||
if mid < end && end < b {
|
|
||||||
symMergeCmpFunc(data, mid, end, b, cmp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rotateCmpFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
|
|
||||||
// Data of the form 'x u v y' is changed to 'x v u y'.
|
|
||||||
// rotate performs at most b-a many calls to data.Swap,
|
|
||||||
// and it assumes non-degenerate arguments: a < m && m < b.
|
|
||||||
func rotateCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) {
|
|
||||||
i := m - a
|
|
||||||
j := b - m
|
|
||||||
|
|
||||||
for i != j {
|
|
||||||
if i > j {
|
|
||||||
swapRangeCmpFunc(data, m-i, m, j, cmp)
|
|
||||||
i -= j
|
|
||||||
} else {
|
|
||||||
swapRangeCmpFunc(data, m-i, m+j-i, i, cmp)
|
|
||||||
j -= i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// i == j
|
|
||||||
swapRangeCmpFunc(data, m-i, m, i, cmp)
|
|
||||||
}
|
|
||||||
481
vendor/golang.org/x/exp/slices/zsortordered.go
generated
vendored
481
vendor/golang.org/x/exp/slices/zsortordered.go
generated
vendored
@@ -1,481 +0,0 @@
|
|||||||
// Code generated by gen_sort_variants.go; DO NOT EDIT.
|
|
||||||
|
|
||||||
// Copyright 2022 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package slices
|
|
||||||
|
|
||||||
import "golang.org/x/exp/constraints"
|
|
||||||
|
|
||||||
// insertionSortOrdered sorts data[a:b] using insertion sort.
|
|
||||||
func insertionSortOrdered[E constraints.Ordered](data []E, a, b int) {
|
|
||||||
for i := a + 1; i < b; i++ {
|
|
||||||
for j := i; j > a && cmpLess(data[j], data[j-1]); j-- {
|
|
||||||
data[j], data[j-1] = data[j-1], data[j]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// siftDownOrdered implements the heap property on data[lo:hi].
|
|
||||||
// first is an offset into the array where the root of the heap lies.
|
|
||||||
func siftDownOrdered[E constraints.Ordered](data []E, lo, hi, first int) {
|
|
||||||
root := lo
|
|
||||||
for {
|
|
||||||
child := 2*root + 1
|
|
||||||
if child >= hi {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if child+1 < hi && cmpLess(data[first+child], data[first+child+1]) {
|
|
||||||
child++
|
|
||||||
}
|
|
||||||
if !cmpLess(data[first+root], data[first+child]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data[first+root], data[first+child] = data[first+child], data[first+root]
|
|
||||||
root = child
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func heapSortOrdered[E constraints.Ordered](data []E, a, b int) {
|
|
||||||
first := a
|
|
||||||
lo := 0
|
|
||||||
hi := b - a
|
|
||||||
|
|
||||||
// Build heap with greatest element at top.
|
|
||||||
for i := (hi - 1) / 2; i >= 0; i-- {
|
|
||||||
siftDownOrdered(data, i, hi, first)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop elements, largest first, into end of data.
|
|
||||||
for i := hi - 1; i >= 0; i-- {
|
|
||||||
data[first], data[first+i] = data[first+i], data[first]
|
|
||||||
siftDownOrdered(data, lo, i, first)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pdqsortOrdered sorts data[a:b].
|
|
||||||
// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort.
|
|
||||||
// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf
|
|
||||||
// C++ implementation: https://github.com/orlp/pdqsort
|
|
||||||
// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/
|
|
||||||
// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort.
|
|
||||||
func pdqsortOrdered[E constraints.Ordered](data []E, a, b, limit int) {
|
|
||||||
const maxInsertion = 12
|
|
||||||
|
|
||||||
var (
|
|
||||||
wasBalanced = true // whether the last partitioning was reasonably balanced
|
|
||||||
wasPartitioned = true // whether the slice was already partitioned
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
length := b - a
|
|
||||||
|
|
||||||
if length <= maxInsertion {
|
|
||||||
insertionSortOrdered(data, a, b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to heapsort if too many bad choices were made.
|
|
||||||
if limit == 0 {
|
|
||||||
heapSortOrdered(data, a, b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the last partitioning was imbalanced, we need to breaking patterns.
|
|
||||||
if !wasBalanced {
|
|
||||||
breakPatternsOrdered(data, a, b)
|
|
||||||
limit--
|
|
||||||
}
|
|
||||||
|
|
||||||
pivot, hint := choosePivotOrdered(data, a, b)
|
|
||||||
if hint == decreasingHint {
|
|
||||||
reverseRangeOrdered(data, a, b)
|
|
||||||
// The chosen pivot was pivot-a elements after the start of the array.
|
|
||||||
// After reversing it is pivot-a elements before the end of the array.
|
|
||||||
// The idea came from Rust's implementation.
|
|
||||||
pivot = (b - 1) - (pivot - a)
|
|
||||||
hint = increasingHint
|
|
||||||
}
|
|
||||||
|
|
||||||
// The slice is likely already sorted.
|
|
||||||
if wasBalanced && wasPartitioned && hint == increasingHint {
|
|
||||||
if partialInsertionSortOrdered(data, a, b) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probably the slice contains many duplicate elements, partition the slice into
|
|
||||||
// elements equal to and elements greater than the pivot.
|
|
||||||
if a > 0 && !cmpLess(data[a-1], data[pivot]) {
|
|
||||||
mid := partitionEqualOrdered(data, a, b, pivot)
|
|
||||||
a = mid
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
mid, alreadyPartitioned := partitionOrdered(data, a, b, pivot)
|
|
||||||
wasPartitioned = alreadyPartitioned
|
|
||||||
|
|
||||||
leftLen, rightLen := mid-a, b-mid
|
|
||||||
balanceThreshold := length / 8
|
|
||||||
if leftLen < rightLen {
|
|
||||||
wasBalanced = leftLen >= balanceThreshold
|
|
||||||
pdqsortOrdered(data, a, mid, limit)
|
|
||||||
a = mid + 1
|
|
||||||
} else {
|
|
||||||
wasBalanced = rightLen >= balanceThreshold
|
|
||||||
pdqsortOrdered(data, mid+1, b, limit)
|
|
||||||
b = mid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// partitionOrdered does one quicksort partition.
|
|
||||||
// Let p = data[pivot]
|
|
||||||
// Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot.
|
|
||||||
// On return, data[newpivot] = p
|
|
||||||
func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int, alreadyPartitioned bool) {
|
|
||||||
data[a], data[pivot] = data[pivot], data[a]
|
|
||||||
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
|
|
||||||
|
|
||||||
for i <= j && cmpLess(data[i], data[a]) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
for i <= j && !cmpLess(data[j], data[a]) {
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
if i > j {
|
|
||||||
data[j], data[a] = data[a], data[j]
|
|
||||||
return j, true
|
|
||||||
}
|
|
||||||
data[i], data[j] = data[j], data[i]
|
|
||||||
i++
|
|
||||||
j--
|
|
||||||
|
|
||||||
for {
|
|
||||||
for i <= j && cmpLess(data[i], data[a]) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
for i <= j && !cmpLess(data[j], data[a]) {
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
if i > j {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
data[i], data[j] = data[j], data[i]
|
|
||||||
i++
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
data[j], data[a] = data[a], data[j]
|
|
||||||
return j, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// partitionEqualOrdered partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot].
|
|
||||||
// It assumed that data[a:b] does not contain elements smaller than the data[pivot].
|
|
||||||
func partitionEqualOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int) {
|
|
||||||
data[a], data[pivot] = data[pivot], data[a]
|
|
||||||
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
|
|
||||||
|
|
||||||
for {
|
|
||||||
for i <= j && !cmpLess(data[a], data[i]) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
for i <= j && cmpLess(data[a], data[j]) {
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
if i > j {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
data[i], data[j] = data[j], data[i]
|
|
||||||
i++
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// partialInsertionSortOrdered partially sorts a slice, returns true if the slice is sorted at the end.
|
|
||||||
func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool {
|
|
||||||
const (
|
|
||||||
maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted
|
|
||||||
shortestShifting = 50 // don't shift any elements on short arrays
|
|
||||||
)
|
|
||||||
i := a + 1
|
|
||||||
for j := 0; j < maxSteps; j++ {
|
|
||||||
for i < b && !cmpLess(data[i], data[i-1]) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == b {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if b-a < shortestShifting {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
data[i], data[i-1] = data[i-1], data[i]
|
|
||||||
|
|
||||||
// Shift the smaller one to the left.
|
|
||||||
if i-a >= 2 {
|
|
||||||
for j := i - 1; j >= 1; j-- {
|
|
||||||
if !cmpLess(data[j], data[j-1]) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
data[j], data[j-1] = data[j-1], data[j]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Shift the greater one to the right.
|
|
||||||
if b-i >= 2 {
|
|
||||||
for j := i + 1; j < b; j++ {
|
|
||||||
if !cmpLess(data[j], data[j-1]) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
data[j], data[j-1] = data[j-1], data[j]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// breakPatternsOrdered scatters some elements around in an attempt to break some patterns
|
|
||||||
// that might cause imbalanced partitions in quicksort.
|
|
||||||
func breakPatternsOrdered[E constraints.Ordered](data []E, a, b int) {
|
|
||||||
length := b - a
|
|
||||||
if length >= 8 {
|
|
||||||
random := xorshift(length)
|
|
||||||
modulus := nextPowerOfTwo(length)
|
|
||||||
|
|
||||||
for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ {
|
|
||||||
other := int(uint(random.Next()) & (modulus - 1))
|
|
||||||
if other >= length {
|
|
||||||
other -= length
|
|
||||||
}
|
|
||||||
data[idx], data[a+other] = data[a+other], data[idx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// choosePivotOrdered chooses a pivot in data[a:b].
|
|
||||||
//
|
|
||||||
// [0,8): chooses a static pivot.
|
|
||||||
// [8,shortestNinther): uses the simple median-of-three method.
|
|
||||||
// [shortestNinther,∞): uses the Tukey ninther method.
|
|
||||||
func choosePivotOrdered[E constraints.Ordered](data []E, a, b int) (pivot int, hint sortedHint) {
|
|
||||||
const (
|
|
||||||
shortestNinther = 50
|
|
||||||
maxSwaps = 4 * 3
|
|
||||||
)
|
|
||||||
|
|
||||||
l := b - a
|
|
||||||
|
|
||||||
var (
|
|
||||||
swaps int
|
|
||||||
i = a + l/4*1
|
|
||||||
j = a + l/4*2
|
|
||||||
k = a + l/4*3
|
|
||||||
)
|
|
||||||
|
|
||||||
if l >= 8 {
|
|
||||||
if l >= shortestNinther {
|
|
||||||
// Tukey ninther method, the idea came from Rust's implementation.
|
|
||||||
i = medianAdjacentOrdered(data, i, &swaps)
|
|
||||||
j = medianAdjacentOrdered(data, j, &swaps)
|
|
||||||
k = medianAdjacentOrdered(data, k, &swaps)
|
|
||||||
}
|
|
||||||
// Find the median among i, j, k and stores it into j.
|
|
||||||
j = medianOrdered(data, i, j, k, &swaps)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch swaps {
|
|
||||||
case 0:
|
|
||||||
return j, increasingHint
|
|
||||||
case maxSwaps:
|
|
||||||
return j, decreasingHint
|
|
||||||
default:
|
|
||||||
return j, unknownHint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// order2Ordered returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a.
|
|
||||||
func order2Ordered[E constraints.Ordered](data []E, a, b int, swaps *int) (int, int) {
|
|
||||||
if cmpLess(data[b], data[a]) {
|
|
||||||
*swaps++
|
|
||||||
return b, a
|
|
||||||
}
|
|
||||||
return a, b
|
|
||||||
}
|
|
||||||
|
|
||||||
// medianOrdered returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c.
|
|
||||||
func medianOrdered[E constraints.Ordered](data []E, a, b, c int, swaps *int) int {
|
|
||||||
a, b = order2Ordered(data, a, b, swaps)
|
|
||||||
b, c = order2Ordered(data, b, c, swaps)
|
|
||||||
a, b = order2Ordered(data, a, b, swaps)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// medianAdjacentOrdered finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a.
|
|
||||||
func medianAdjacentOrdered[E constraints.Ordered](data []E, a int, swaps *int) int {
|
|
||||||
return medianOrdered(data, a-1, a, a+1, swaps)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reverseRangeOrdered[E constraints.Ordered](data []E, a, b int) {
|
|
||||||
i := a
|
|
||||||
j := b - 1
|
|
||||||
for i < j {
|
|
||||||
data[i], data[j] = data[j], data[i]
|
|
||||||
i++
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func swapRangeOrdered[E constraints.Ordered](data []E, a, b, n int) {
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
data[a+i], data[b+i] = data[b+i], data[a+i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stableOrdered[E constraints.Ordered](data []E, n int) {
|
|
||||||
blockSize := 20 // must be > 0
|
|
||||||
a, b := 0, blockSize
|
|
||||||
for b <= n {
|
|
||||||
insertionSortOrdered(data, a, b)
|
|
||||||
a = b
|
|
||||||
b += blockSize
|
|
||||||
}
|
|
||||||
insertionSortOrdered(data, a, n)
|
|
||||||
|
|
||||||
for blockSize < n {
|
|
||||||
a, b = 0, 2*blockSize
|
|
||||||
for b <= n {
|
|
||||||
symMergeOrdered(data, a, a+blockSize, b)
|
|
||||||
a = b
|
|
||||||
b += 2 * blockSize
|
|
||||||
}
|
|
||||||
if m := a + blockSize; m < n {
|
|
||||||
symMergeOrdered(data, a, m, n)
|
|
||||||
}
|
|
||||||
blockSize *= 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using
|
|
||||||
// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
|
|
||||||
// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
|
|
||||||
// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
|
|
||||||
// Computer Science, pages 714-723. Springer, 2004.
|
|
||||||
//
|
|
||||||
// Let M = m-a and N = b-n. Wolog M < N.
|
|
||||||
// The recursion depth is bound by ceil(log(N+M)).
|
|
||||||
// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
|
|
||||||
// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
|
|
||||||
//
|
|
||||||
// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
|
|
||||||
// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
|
|
||||||
// in the paper carries through for Swap operations, especially as the block
|
|
||||||
// swapping rotate uses only O(M+N) Swaps.
|
|
||||||
//
|
|
||||||
// symMerge assumes non-degenerate arguments: a < m && m < b.
|
|
||||||
// Having the caller check this condition eliminates many leaf recursion calls,
|
|
||||||
// which improves performance.
|
|
||||||
func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) {
|
|
||||||
// Avoid unnecessary recursions of symMerge
|
|
||||||
// by direct insertion of data[a] into data[m:b]
|
|
||||||
// if data[a:m] only contains one element.
|
|
||||||
if m-a == 1 {
|
|
||||||
// Use binary search to find the lowest index i
|
|
||||||
// such that data[i] >= data[a] for m <= i < b.
|
|
||||||
// Exit the search loop with i == b in case no such index exists.
|
|
||||||
i := m
|
|
||||||
j := b
|
|
||||||
for i < j {
|
|
||||||
h := int(uint(i+j) >> 1)
|
|
||||||
if cmpLess(data[h], data[a]) {
|
|
||||||
i = h + 1
|
|
||||||
} else {
|
|
||||||
j = h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Swap values until data[a] reaches the position before i.
|
|
||||||
for k := a; k < i-1; k++ {
|
|
||||||
data[k], data[k+1] = data[k+1], data[k]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid unnecessary recursions of symMerge
|
|
||||||
// by direct insertion of data[m] into data[a:m]
|
|
||||||
// if data[m:b] only contains one element.
|
|
||||||
if b-m == 1 {
|
|
||||||
// Use binary search to find the lowest index i
|
|
||||||
// such that data[i] > data[m] for a <= i < m.
|
|
||||||
// Exit the search loop with i == m in case no such index exists.
|
|
||||||
i := a
|
|
||||||
j := m
|
|
||||||
for i < j {
|
|
||||||
h := int(uint(i+j) >> 1)
|
|
||||||
if !cmpLess(data[m], data[h]) {
|
|
||||||
i = h + 1
|
|
||||||
} else {
|
|
||||||
j = h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Swap values until data[m] reaches the position i.
|
|
||||||
for k := m; k > i; k-- {
|
|
||||||
data[k], data[k-1] = data[k-1], data[k]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mid := int(uint(a+b) >> 1)
|
|
||||||
n := mid + m
|
|
||||||
var start, r int
|
|
||||||
if m > mid {
|
|
||||||
start = n - b
|
|
||||||
r = mid
|
|
||||||
} else {
|
|
||||||
start = a
|
|
||||||
r = m
|
|
||||||
}
|
|
||||||
p := n - 1
|
|
||||||
|
|
||||||
for start < r {
|
|
||||||
c := int(uint(start+r) >> 1)
|
|
||||||
if !cmpLess(data[p-c], data[c]) {
|
|
||||||
start = c + 1
|
|
||||||
} else {
|
|
||||||
r = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end := n - start
|
|
||||||
if start < m && m < end {
|
|
||||||
rotateOrdered(data, start, m, end)
|
|
||||||
}
|
|
||||||
if a < start && start < mid {
|
|
||||||
symMergeOrdered(data, a, start, mid)
|
|
||||||
}
|
|
||||||
if mid < end && end < b {
|
|
||||||
symMergeOrdered(data, mid, end, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
|
|
||||||
// Data of the form 'x u v y' is changed to 'x v u y'.
|
|
||||||
// rotate performs at most b-a many calls to data.Swap,
|
|
||||||
// and it assumes non-degenerate arguments: a < m && m < b.
|
|
||||||
func rotateOrdered[E constraints.Ordered](data []E, a, m, b int) {
|
|
||||||
i := m - a
|
|
||||||
j := b - m
|
|
||||||
|
|
||||||
for i != j {
|
|
||||||
if i > j {
|
|
||||||
swapRangeOrdered(data, m-i, m, j)
|
|
||||||
i -= j
|
|
||||||
} else {
|
|
||||||
swapRangeOrdered(data, m-i, m+j-i, i)
|
|
||||||
j -= i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// i == j
|
|
||||||
swapRangeOrdered(data, m-i, m, i)
|
|
||||||
}
|
|
||||||
27
vendor/golang.org/x/net/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/net/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google LLC nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
22
vendor/golang.org/x/net/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/net/PATENTS
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
||||||
144
vendor/golang.org/x/net/context/context.go
generated
vendored
Normal file
144
vendor/golang.org/x/net/context/context.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package context defines the Context type, which carries deadlines,
|
||||||
|
// cancellation signals, and other request-scoped values across API boundaries
|
||||||
|
// and between processes.
|
||||||
|
// As of Go 1.7 this package is available in the standard library under the
|
||||||
|
// name [context], and migrating to it can be done automatically with [go fix].
|
||||||
|
//
|
||||||
|
// Incoming requests to a server should create a [Context], and outgoing
|
||||||
|
// calls to servers should accept a Context. The chain of function
|
||||||
|
// calls between them must propagate the Context, optionally replacing
|
||||||
|
// it with a derived Context created using [WithCancel], [WithDeadline],
|
||||||
|
// [WithTimeout], or [WithValue].
|
||||||
|
//
|
||||||
|
// Programs that use Contexts should follow these rules to keep interfaces
|
||||||
|
// consistent across packages and enable static analysis tools to check context
|
||||||
|
// propagation:
|
||||||
|
//
|
||||||
|
// Do not store Contexts inside a struct type; instead, pass a Context
|
||||||
|
// explicitly to each function that needs it. This is discussed further in
|
||||||
|
// https://go.dev/blog/context-and-structs. The Context should be the first
|
||||||
|
// parameter, typically named ctx:
|
||||||
|
//
|
||||||
|
// func DoSomething(ctx context.Context, arg Arg) error {
|
||||||
|
// // ... use ctx ...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Do not pass a nil [Context], even if a function permits it. Pass [context.TODO]
|
||||||
|
// if you are unsure about which Context to use.
|
||||||
|
//
|
||||||
|
// Use context Values only for request-scoped data that transits processes and
|
||||||
|
// APIs, not for passing optional parameters to functions.
|
||||||
|
//
|
||||||
|
// The same Context may be passed to functions running in different goroutines;
|
||||||
|
// Contexts are safe for simultaneous use by multiple goroutines.
|
||||||
|
//
|
||||||
|
// See https://go.dev/blog/context for example code for a server that uses
|
||||||
|
// Contexts.
|
||||||
|
//
|
||||||
|
// [go fix]: https://go.dev/cmd/go#hdr-Update_packages_to_use_new_APIs
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context" // standard library's context, as of Go 1.7
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Context carries a deadline, a cancellation signal, and other values across
|
||||||
|
// API boundaries.
|
||||||
|
//
|
||||||
|
// Context's methods may be called by multiple goroutines simultaneously.
|
||||||
|
type Context = context.Context
|
||||||
|
|
||||||
|
// Canceled is the error returned by [Context.Err] when the context is canceled
|
||||||
|
// for some reason other than its deadline passing.
|
||||||
|
var Canceled = context.Canceled
|
||||||
|
|
||||||
|
// DeadlineExceeded is the error returned by [Context.Err] when the context is canceled
|
||||||
|
// due to its deadline passing.
|
||||||
|
var DeadlineExceeded = context.DeadlineExceeded
|
||||||
|
|
||||||
|
// Background returns a non-nil, empty Context. It is never canceled, has no
|
||||||
|
// values, and has no deadline. It is typically used by the main function,
|
||||||
|
// initialization, and tests, and as the top-level Context for incoming
|
||||||
|
// requests.
|
||||||
|
func Background() Context {
|
||||||
|
return background
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
||||||
|
// it's unclear which Context to use or it is not yet available (because the
|
||||||
|
// surrounding function has not yet been extended to accept a Context
|
||||||
|
// parameter).
|
||||||
|
func TODO() Context {
|
||||||
|
return todo
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
background = context.Background()
|
||||||
|
todo = context.TODO()
|
||||||
|
)
|
||||||
|
|
||||||
|
// A CancelFunc tells an operation to abandon its work.
|
||||||
|
// A CancelFunc does not wait for the work to stop.
|
||||||
|
// A CancelFunc may be called by multiple goroutines simultaneously.
|
||||||
|
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||||
|
type CancelFunc = context.CancelFunc
|
||||||
|
|
||||||
|
// WithCancel returns a derived context that points to the parent context
|
||||||
|
// but has a new Done channel. The returned context's Done channel is closed
|
||||||
|
// when the returned cancel function is called or when the parent context's
|
||||||
|
// Done channel is closed, whichever happens first.
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this [Context] complete.
|
||||||
|
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||||
|
return context.WithCancel(parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDeadline returns a derived context that points to the parent context
|
||||||
|
// but has the deadline adjusted to be no later than d. If the parent's
|
||||||
|
// deadline is already earlier than d, WithDeadline(parent, d) is semantically
|
||||||
|
// equivalent to parent. The returned [Context.Done] channel is closed when
|
||||||
|
// the deadline expires, when the returned cancel function is called,
|
||||||
|
// or when the parent context's Done channel is closed, whichever happens first.
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this [Context] complete.
|
||||||
|
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
|
||||||
|
return context.WithDeadline(parent, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||||
|
//
|
||||||
|
// Canceling this context releases resources associated with it, so code should
|
||||||
|
// call cancel as soon as the operations running in this [Context] complete:
|
||||||
|
//
|
||||||
|
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||||
|
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||||
|
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||||
|
// return slowOperation(ctx)
|
||||||
|
// }
|
||||||
|
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||||
|
return context.WithTimeout(parent, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithValue returns a derived context that points to the parent Context.
|
||||||
|
// In the derived context, the value associated with key is val.
|
||||||
|
//
|
||||||
|
// Use context Values only for request-scoped data that transits processes and
|
||||||
|
// APIs, not for passing optional parameters to functions.
|
||||||
|
//
|
||||||
|
// The provided key must be comparable and should not be of type
|
||||||
|
// string or any other built-in type to avoid collisions between
|
||||||
|
// packages using context. Users of WithValue should define their own
|
||||||
|
// types for keys. To avoid allocating when assigning to an
|
||||||
|
// interface{}, context keys often have concrete type
|
||||||
|
// struct{}. Alternatively, exported context key variables' static
|
||||||
|
// type should be a pointer or interface.
|
||||||
|
func WithValue(parent Context, key, val interface{}) Context {
|
||||||
|
return context.WithValue(parent, key, val)
|
||||||
|
}
|
||||||
4
vendor/golang.org/x/sys/LICENSE
generated
vendored
4
vendor/golang.org/x/sys/LICENSE
generated
vendored
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
|
|||||||
copyright notice, this list of conditions and the following disclaimer
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
in the documentation and/or other materials provided with the
|
in the documentation and/or other materials provided with the
|
||||||
distribution.
|
distribution.
|
||||||
* Neither the name of Google Inc. nor the names of its
|
* Neither the name of Google LLC nor the names of its
|
||||||
contributors may be used to endorse or promote products derived from
|
contributors may be used to endorse or promote products derived from
|
||||||
this software without specific prior written permission.
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
|||||||
2
vendor/golang.org/x/sys/unix/README.md
generated
vendored
2
vendor/golang.org/x/sys/unix/README.md
generated
vendored
@@ -156,7 +156,7 @@ from the generated architecture-specific files listed below, and merge these
|
|||||||
into a common file for each OS.
|
into a common file for each OS.
|
||||||
|
|
||||||
The merge is performed in the following steps:
|
The merge is performed in the following steps:
|
||||||
1. Construct the set of common code that is idential in all architecture-specific files.
|
1. Construct the set of common code that is identical in all architecture-specific files.
|
||||||
2. Write this common code to the merged file.
|
2. Write this common code to the merged file.
|
||||||
3. Remove the common code from all architecture-specific files.
|
3. Remove the common code from all architecture-specific files.
|
||||||
|
|
||||||
|
|||||||
4
vendor/golang.org/x/sys/unix/aliases.go
generated
vendored
4
vendor/golang.org/x/sys/unix/aliases.go
generated
vendored
@@ -2,9 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos) && go1.9
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
|
|
||||||
// +build go1.9
|
|
||||||
|
|
||||||
package unix
|
package unix
|
||||||
|
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/asm_aix_ppc64.s
generated
vendored
1
vendor/golang.org/x/sys/unix/asm_aix_ppc64.s
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build gc
|
//go:build gc
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
2
vendor/golang.org/x/sys/unix/asm_bsd_386.s
generated
vendored
2
vendor/golang.org/x/sys/unix/asm_bsd_386.s
generated
vendored
@@ -3,8 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build (freebsd || netbsd || openbsd) && gc
|
//go:build (freebsd || netbsd || openbsd) && gc
|
||||||
// +build freebsd netbsd openbsd
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
2
vendor/golang.org/x/sys/unix/asm_bsd_amd64.s
generated
vendored
2
vendor/golang.org/x/sys/unix/asm_bsd_amd64.s
generated
vendored
@@ -3,8 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build (darwin || dragonfly || freebsd || netbsd || openbsd) && gc
|
//go:build (darwin || dragonfly || freebsd || netbsd || openbsd) && gc
|
||||||
// +build darwin dragonfly freebsd netbsd openbsd
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
2
vendor/golang.org/x/sys/unix/asm_bsd_arm.s
generated
vendored
2
vendor/golang.org/x/sys/unix/asm_bsd_arm.s
generated
vendored
@@ -3,8 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build (freebsd || netbsd || openbsd) && gc
|
//go:build (freebsd || netbsd || openbsd) && gc
|
||||||
// +build freebsd netbsd openbsd
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
2
vendor/golang.org/x/sys/unix/asm_bsd_arm64.s
generated
vendored
2
vendor/golang.org/x/sys/unix/asm_bsd_arm64.s
generated
vendored
@@ -3,8 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build (darwin || freebsd || netbsd || openbsd) && gc
|
//go:build (darwin || freebsd || netbsd || openbsd) && gc
|
||||||
// +build darwin freebsd netbsd openbsd
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
2
vendor/golang.org/x/sys/unix/asm_bsd_ppc64.s
generated
vendored
2
vendor/golang.org/x/sys/unix/asm_bsd_ppc64.s
generated
vendored
@@ -3,8 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build (darwin || freebsd || netbsd || openbsd) && gc
|
//go:build (darwin || freebsd || netbsd || openbsd) && gc
|
||||||
// +build darwin freebsd netbsd openbsd
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
2
vendor/golang.org/x/sys/unix/asm_bsd_riscv64.s
generated
vendored
2
vendor/golang.org/x/sys/unix/asm_bsd_riscv64.s
generated
vendored
@@ -3,8 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build (darwin || freebsd || netbsd || openbsd) && gc
|
//go:build (darwin || freebsd || netbsd || openbsd) && gc
|
||||||
// +build darwin freebsd netbsd openbsd
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/asm_linux_386.s
generated
vendored
1
vendor/golang.org/x/sys/unix/asm_linux_386.s
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build gc
|
//go:build gc
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/asm_linux_amd64.s
generated
vendored
1
vendor/golang.org/x/sys/unix/asm_linux_amd64.s
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build gc
|
//go:build gc
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/asm_linux_arm.s
generated
vendored
1
vendor/golang.org/x/sys/unix/asm_linux_arm.s
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build gc
|
//go:build gc
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
3
vendor/golang.org/x/sys/unix/asm_linux_arm64.s
generated
vendored
3
vendor/golang.org/x/sys/unix/asm_linux_arm64.s
generated
vendored
@@ -3,9 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build linux && arm64 && gc
|
//go:build linux && arm64 && gc
|
||||||
// +build linux
|
|
||||||
// +build arm64
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
3
vendor/golang.org/x/sys/unix/asm_linux_loong64.s
generated
vendored
3
vendor/golang.org/x/sys/unix/asm_linux_loong64.s
generated
vendored
@@ -3,9 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build linux && loong64 && gc
|
//go:build linux && loong64 && gc
|
||||||
// +build linux
|
|
||||||
// +build loong64
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
3
vendor/golang.org/x/sys/unix/asm_linux_mips64x.s
generated
vendored
3
vendor/golang.org/x/sys/unix/asm_linux_mips64x.s
generated
vendored
@@ -3,9 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build linux && (mips64 || mips64le) && gc
|
//go:build linux && (mips64 || mips64le) && gc
|
||||||
// +build linux
|
|
||||||
// +build mips64 mips64le
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
3
vendor/golang.org/x/sys/unix/asm_linux_mipsx.s
generated
vendored
3
vendor/golang.org/x/sys/unix/asm_linux_mipsx.s
generated
vendored
@@ -3,9 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build linux && (mips || mipsle) && gc
|
//go:build linux && (mips || mipsle) && gc
|
||||||
// +build linux
|
|
||||||
// +build mips mipsle
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
3
vendor/golang.org/x/sys/unix/asm_linux_ppc64x.s
generated
vendored
3
vendor/golang.org/x/sys/unix/asm_linux_ppc64x.s
generated
vendored
@@ -3,9 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build linux && (ppc64 || ppc64le) && gc
|
//go:build linux && (ppc64 || ppc64le) && gc
|
||||||
// +build linux
|
|
||||||
// +build ppc64 ppc64le
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
2
vendor/golang.org/x/sys/unix/asm_linux_riscv64.s
generated
vendored
2
vendor/golang.org/x/sys/unix/asm_linux_riscv64.s
generated
vendored
@@ -3,8 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build riscv64 && gc
|
//go:build riscv64 && gc
|
||||||
// +build riscv64
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
3
vendor/golang.org/x/sys/unix/asm_linux_s390x.s
generated
vendored
3
vendor/golang.org/x/sys/unix/asm_linux_s390x.s
generated
vendored
@@ -3,9 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build linux && s390x && gc
|
//go:build linux && s390x && gc
|
||||||
// +build linux
|
|
||||||
// +build s390x
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/asm_openbsd_mips64.s
generated
vendored
1
vendor/golang.org/x/sys/unix/asm_openbsd_mips64.s
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build gc
|
//go:build gc
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/asm_solaris_amd64.s
generated
vendored
1
vendor/golang.org/x/sys/unix/asm_solaris_amd64.s
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build gc
|
//go:build gc
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|||||||
678
vendor/golang.org/x/sys/unix/asm_zos_s390x.s
generated
vendored
678
vendor/golang.org/x/sys/unix/asm_zos_s390x.s
generated
vendored
@@ -3,18 +3,17 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build zos && s390x && gc
|
//go:build zos && s390x && gc
|
||||||
// +build zos
|
|
||||||
// +build s390x
|
|
||||||
// +build gc
|
|
||||||
|
|
||||||
#include "textflag.h"
|
#include "textflag.h"
|
||||||
|
|
||||||
#define PSALAA 1208(R0)
|
#define PSALAA 1208(R0)
|
||||||
#define GTAB64(x) 80(x)
|
#define GTAB64(x) 80(x)
|
||||||
#define LCA64(x) 88(x)
|
#define LCA64(x) 88(x)
|
||||||
|
#define SAVSTACK_ASYNC(x) 336(x) // in the LCA
|
||||||
#define CAA(x) 8(x)
|
#define CAA(x) 8(x)
|
||||||
#define EDCHPXV(x) 1016(x) // in the CAA
|
#define CEECAATHDID(x) 976(x) // in the CAA
|
||||||
#define SAVSTACK_ASYNC(x) 336(x) // in the LCA
|
#define EDCHPXV(x) 1016(x) // in the CAA
|
||||||
|
#define GOCB(x) 1104(x) // in the CAA
|
||||||
|
|
||||||
// SS_*, where x=SAVSTACK_ASYNC
|
// SS_*, where x=SAVSTACK_ASYNC
|
||||||
#define SS_LE(x) 0(x)
|
#define SS_LE(x) 0(x)
|
||||||
@@ -22,394 +21,125 @@
|
|||||||
#define SS_ERRNO(x) 16(x)
|
#define SS_ERRNO(x) 16(x)
|
||||||
#define SS_ERRNOJR(x) 20(x)
|
#define SS_ERRNOJR(x) 20(x)
|
||||||
|
|
||||||
#define LE_CALL BYTE $0x0D; BYTE $0x76; // BL R7, R6
|
// Function Descriptor Offsets
|
||||||
|
#define __errno 0x156*16
|
||||||
|
#define __err2ad 0x16C*16
|
||||||
|
|
||||||
TEXT ·clearErrno(SB),NOSPLIT,$0-0
|
// Call Instructions
|
||||||
BL addrerrno<>(SB)
|
#define LE_CALL BYTE $0x0D; BYTE $0x76 // BL R7, R6
|
||||||
MOVD $0, 0(R3)
|
#define SVC_LOAD BYTE $0x0A; BYTE $0x08 // SVC 08 LOAD
|
||||||
|
#define SVC_DELETE BYTE $0x0A; BYTE $0x09 // SVC 09 DELETE
|
||||||
|
|
||||||
|
DATA zosLibVec<>(SB)/8, $0
|
||||||
|
GLOBL zosLibVec<>(SB), NOPTR, $8
|
||||||
|
|
||||||
|
TEXT ·initZosLibVec(SB), NOSPLIT|NOFRAME, $0-0
|
||||||
|
MOVW PSALAA, R8
|
||||||
|
MOVD LCA64(R8), R8
|
||||||
|
MOVD CAA(R8), R8
|
||||||
|
MOVD EDCHPXV(R8), R8
|
||||||
|
MOVD R8, zosLibVec<>(SB)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·GetZosLibVec(SB), NOSPLIT|NOFRAME, $0-0
|
||||||
|
MOVD zosLibVec<>(SB), R8
|
||||||
|
MOVD R8, ret+0(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·clearErrno(SB), NOSPLIT, $0-0
|
||||||
|
BL addrerrno<>(SB)
|
||||||
|
MOVD $0, 0(R3)
|
||||||
RET
|
RET
|
||||||
|
|
||||||
// Returns the address of errno in R3.
|
// Returns the address of errno in R3.
|
||||||
TEXT addrerrno<>(SB),NOSPLIT|NOFRAME,$0-0
|
TEXT addrerrno<>(SB), NOSPLIT|NOFRAME, $0-0
|
||||||
// Get library control area (LCA).
|
// Get library control area (LCA).
|
||||||
MOVW PSALAA, R8
|
MOVW PSALAA, R8
|
||||||
MOVD LCA64(R8), R8
|
MOVD LCA64(R8), R8
|
||||||
|
|
||||||
// Get __errno FuncDesc.
|
// Get __errno FuncDesc.
|
||||||
MOVD CAA(R8), R9
|
MOVD CAA(R8), R9
|
||||||
MOVD EDCHPXV(R9), R9
|
MOVD EDCHPXV(R9), R9
|
||||||
ADD $(0x156*16), R9
|
ADD $(__errno), R9
|
||||||
LMG 0(R9), R5, R6
|
LMG 0(R9), R5, R6
|
||||||
|
|
||||||
// Switch to saved LE stack.
|
// Switch to saved LE stack.
|
||||||
MOVD SAVSTACK_ASYNC(R8), R9
|
MOVD SAVSTACK_ASYNC(R8), R9
|
||||||
MOVD 0(R9), R4
|
MOVD 0(R9), R4
|
||||||
MOVD $0, 0(R9)
|
MOVD $0, 0(R9)
|
||||||
|
|
||||||
// Call __errno function.
|
// Call __errno function.
|
||||||
LE_CALL
|
LE_CALL
|
||||||
NOPH
|
NOPH
|
||||||
|
|
||||||
// Switch back to Go stack.
|
// Switch back to Go stack.
|
||||||
XOR R0, R0 // Restore R0 to $0.
|
XOR R0, R0 // Restore R0 to $0.
|
||||||
MOVD R4, 0(R9) // Save stack pointer.
|
MOVD R4, 0(R9) // Save stack pointer.
|
||||||
RET
|
|
||||||
|
|
||||||
TEXT ·syscall_syscall(SB),NOSPLIT,$0-56
|
|
||||||
BL runtime·entersyscall(SB)
|
|
||||||
MOVD a1+8(FP), R1
|
|
||||||
MOVD a2+16(FP), R2
|
|
||||||
MOVD a3+24(FP), R3
|
|
||||||
|
|
||||||
// Get library control area (LCA).
|
|
||||||
MOVW PSALAA, R8
|
|
||||||
MOVD LCA64(R8), R8
|
|
||||||
|
|
||||||
// Get function.
|
|
||||||
MOVD CAA(R8), R9
|
|
||||||
MOVD EDCHPXV(R9), R9
|
|
||||||
MOVD trap+0(FP), R5
|
|
||||||
SLD $4, R5
|
|
||||||
ADD R5, R9
|
|
||||||
LMG 0(R9), R5, R6
|
|
||||||
|
|
||||||
// Restore LE stack.
|
|
||||||
MOVD SAVSTACK_ASYNC(R8), R9
|
|
||||||
MOVD 0(R9), R4
|
|
||||||
MOVD $0, 0(R9)
|
|
||||||
|
|
||||||
// Call function.
|
|
||||||
LE_CALL
|
|
||||||
NOPH
|
|
||||||
XOR R0, R0 // Restore R0 to $0.
|
|
||||||
MOVD R4, 0(R9) // Save stack pointer.
|
|
||||||
|
|
||||||
MOVD R3, r1+32(FP)
|
|
||||||
MOVD R0, r2+40(FP)
|
|
||||||
MOVD R0, err+48(FP)
|
|
||||||
MOVW R3, R4
|
|
||||||
CMP R4, $-1
|
|
||||||
BNE done
|
|
||||||
BL addrerrno<>(SB)
|
|
||||||
MOVWZ 0(R3), R3
|
|
||||||
MOVD R3, err+48(FP)
|
|
||||||
done:
|
|
||||||
BL runtime·exitsyscall(SB)
|
|
||||||
RET
|
|
||||||
|
|
||||||
TEXT ·syscall_rawsyscall(SB),NOSPLIT,$0-56
|
|
||||||
MOVD a1+8(FP), R1
|
|
||||||
MOVD a2+16(FP), R2
|
|
||||||
MOVD a3+24(FP), R3
|
|
||||||
|
|
||||||
// Get library control area (LCA).
|
|
||||||
MOVW PSALAA, R8
|
|
||||||
MOVD LCA64(R8), R8
|
|
||||||
|
|
||||||
// Get function.
|
|
||||||
MOVD CAA(R8), R9
|
|
||||||
MOVD EDCHPXV(R9), R9
|
|
||||||
MOVD trap+0(FP), R5
|
|
||||||
SLD $4, R5
|
|
||||||
ADD R5, R9
|
|
||||||
LMG 0(R9), R5, R6
|
|
||||||
|
|
||||||
// Restore LE stack.
|
|
||||||
MOVD SAVSTACK_ASYNC(R8), R9
|
|
||||||
MOVD 0(R9), R4
|
|
||||||
MOVD $0, 0(R9)
|
|
||||||
|
|
||||||
// Call function.
|
|
||||||
LE_CALL
|
|
||||||
NOPH
|
|
||||||
XOR R0, R0 // Restore R0 to $0.
|
|
||||||
MOVD R4, 0(R9) // Save stack pointer.
|
|
||||||
|
|
||||||
MOVD R3, r1+32(FP)
|
|
||||||
MOVD R0, r2+40(FP)
|
|
||||||
MOVD R0, err+48(FP)
|
|
||||||
MOVW R3, R4
|
|
||||||
CMP R4, $-1
|
|
||||||
BNE done
|
|
||||||
BL addrerrno<>(SB)
|
|
||||||
MOVWZ 0(R3), R3
|
|
||||||
MOVD R3, err+48(FP)
|
|
||||||
done:
|
|
||||||
RET
|
|
||||||
|
|
||||||
TEXT ·syscall_syscall6(SB),NOSPLIT,$0-80
|
|
||||||
BL runtime·entersyscall(SB)
|
|
||||||
MOVD a1+8(FP), R1
|
|
||||||
MOVD a2+16(FP), R2
|
|
||||||
MOVD a3+24(FP), R3
|
|
||||||
|
|
||||||
// Get library control area (LCA).
|
|
||||||
MOVW PSALAA, R8
|
|
||||||
MOVD LCA64(R8), R8
|
|
||||||
|
|
||||||
// Get function.
|
|
||||||
MOVD CAA(R8), R9
|
|
||||||
MOVD EDCHPXV(R9), R9
|
|
||||||
MOVD trap+0(FP), R5
|
|
||||||
SLD $4, R5
|
|
||||||
ADD R5, R9
|
|
||||||
LMG 0(R9), R5, R6
|
|
||||||
|
|
||||||
// Restore LE stack.
|
|
||||||
MOVD SAVSTACK_ASYNC(R8), R9
|
|
||||||
MOVD 0(R9), R4
|
|
||||||
MOVD $0, 0(R9)
|
|
||||||
|
|
||||||
// Fill in parameter list.
|
|
||||||
MOVD a4+32(FP), R12
|
|
||||||
MOVD R12, (2176+24)(R4)
|
|
||||||
MOVD a5+40(FP), R12
|
|
||||||
MOVD R12, (2176+32)(R4)
|
|
||||||
MOVD a6+48(FP), R12
|
|
||||||
MOVD R12, (2176+40)(R4)
|
|
||||||
|
|
||||||
// Call function.
|
|
||||||
LE_CALL
|
|
||||||
NOPH
|
|
||||||
XOR R0, R0 // Restore R0 to $0.
|
|
||||||
MOVD R4, 0(R9) // Save stack pointer.
|
|
||||||
|
|
||||||
MOVD R3, r1+56(FP)
|
|
||||||
MOVD R0, r2+64(FP)
|
|
||||||
MOVD R0, err+72(FP)
|
|
||||||
MOVW R3, R4
|
|
||||||
CMP R4, $-1
|
|
||||||
BNE done
|
|
||||||
BL addrerrno<>(SB)
|
|
||||||
MOVWZ 0(R3), R3
|
|
||||||
MOVD R3, err+72(FP)
|
|
||||||
done:
|
|
||||||
BL runtime·exitsyscall(SB)
|
|
||||||
RET
|
|
||||||
|
|
||||||
TEXT ·syscall_rawsyscall6(SB),NOSPLIT,$0-80
|
|
||||||
MOVD a1+8(FP), R1
|
|
||||||
MOVD a2+16(FP), R2
|
|
||||||
MOVD a3+24(FP), R3
|
|
||||||
|
|
||||||
// Get library control area (LCA).
|
|
||||||
MOVW PSALAA, R8
|
|
||||||
MOVD LCA64(R8), R8
|
|
||||||
|
|
||||||
// Get function.
|
|
||||||
MOVD CAA(R8), R9
|
|
||||||
MOVD EDCHPXV(R9), R9
|
|
||||||
MOVD trap+0(FP), R5
|
|
||||||
SLD $4, R5
|
|
||||||
ADD R5, R9
|
|
||||||
LMG 0(R9), R5, R6
|
|
||||||
|
|
||||||
// Restore LE stack.
|
|
||||||
MOVD SAVSTACK_ASYNC(R8), R9
|
|
||||||
MOVD 0(R9), R4
|
|
||||||
MOVD $0, 0(R9)
|
|
||||||
|
|
||||||
// Fill in parameter list.
|
|
||||||
MOVD a4+32(FP), R12
|
|
||||||
MOVD R12, (2176+24)(R4)
|
|
||||||
MOVD a5+40(FP), R12
|
|
||||||
MOVD R12, (2176+32)(R4)
|
|
||||||
MOVD a6+48(FP), R12
|
|
||||||
MOVD R12, (2176+40)(R4)
|
|
||||||
|
|
||||||
// Call function.
|
|
||||||
LE_CALL
|
|
||||||
NOPH
|
|
||||||
XOR R0, R0 // Restore R0 to $0.
|
|
||||||
MOVD R4, 0(R9) // Save stack pointer.
|
|
||||||
|
|
||||||
MOVD R3, r1+56(FP)
|
|
||||||
MOVD R0, r2+64(FP)
|
|
||||||
MOVD R0, err+72(FP)
|
|
||||||
MOVW R3, R4
|
|
||||||
CMP R4, $-1
|
|
||||||
BNE done
|
|
||||||
BL ·rrno<>(SB)
|
|
||||||
MOVWZ 0(R3), R3
|
|
||||||
MOVD R3, err+72(FP)
|
|
||||||
done:
|
|
||||||
RET
|
|
||||||
|
|
||||||
TEXT ·syscall_syscall9(SB),NOSPLIT,$0
|
|
||||||
BL runtime·entersyscall(SB)
|
|
||||||
MOVD a1+8(FP), R1
|
|
||||||
MOVD a2+16(FP), R2
|
|
||||||
MOVD a3+24(FP), R3
|
|
||||||
|
|
||||||
// Get library control area (LCA).
|
|
||||||
MOVW PSALAA, R8
|
|
||||||
MOVD LCA64(R8), R8
|
|
||||||
|
|
||||||
// Get function.
|
|
||||||
MOVD CAA(R8), R9
|
|
||||||
MOVD EDCHPXV(R9), R9
|
|
||||||
MOVD trap+0(FP), R5
|
|
||||||
SLD $4, R5
|
|
||||||
ADD R5, R9
|
|
||||||
LMG 0(R9), R5, R6
|
|
||||||
|
|
||||||
// Restore LE stack.
|
|
||||||
MOVD SAVSTACK_ASYNC(R8), R9
|
|
||||||
MOVD 0(R9), R4
|
|
||||||
MOVD $0, 0(R9)
|
|
||||||
|
|
||||||
// Fill in parameter list.
|
|
||||||
MOVD a4+32(FP), R12
|
|
||||||
MOVD R12, (2176+24)(R4)
|
|
||||||
MOVD a5+40(FP), R12
|
|
||||||
MOVD R12, (2176+32)(R4)
|
|
||||||
MOVD a6+48(FP), R12
|
|
||||||
MOVD R12, (2176+40)(R4)
|
|
||||||
MOVD a7+56(FP), R12
|
|
||||||
MOVD R12, (2176+48)(R4)
|
|
||||||
MOVD a8+64(FP), R12
|
|
||||||
MOVD R12, (2176+56)(R4)
|
|
||||||
MOVD a9+72(FP), R12
|
|
||||||
MOVD R12, (2176+64)(R4)
|
|
||||||
|
|
||||||
// Call function.
|
|
||||||
LE_CALL
|
|
||||||
NOPH
|
|
||||||
XOR R0, R0 // Restore R0 to $0.
|
|
||||||
MOVD R4, 0(R9) // Save stack pointer.
|
|
||||||
|
|
||||||
MOVD R3, r1+80(FP)
|
|
||||||
MOVD R0, r2+88(FP)
|
|
||||||
MOVD R0, err+96(FP)
|
|
||||||
MOVW R3, R4
|
|
||||||
CMP R4, $-1
|
|
||||||
BNE done
|
|
||||||
BL addrerrno<>(SB)
|
|
||||||
MOVWZ 0(R3), R3
|
|
||||||
MOVD R3, err+96(FP)
|
|
||||||
done:
|
|
||||||
BL runtime·exitsyscall(SB)
|
|
||||||
RET
|
|
||||||
|
|
||||||
TEXT ·syscall_rawsyscall9(SB),NOSPLIT,$0
|
|
||||||
MOVD a1+8(FP), R1
|
|
||||||
MOVD a2+16(FP), R2
|
|
||||||
MOVD a3+24(FP), R3
|
|
||||||
|
|
||||||
// Get library control area (LCA).
|
|
||||||
MOVW PSALAA, R8
|
|
||||||
MOVD LCA64(R8), R8
|
|
||||||
|
|
||||||
// Get function.
|
|
||||||
MOVD CAA(R8), R9
|
|
||||||
MOVD EDCHPXV(R9), R9
|
|
||||||
MOVD trap+0(FP), R5
|
|
||||||
SLD $4, R5
|
|
||||||
ADD R5, R9
|
|
||||||
LMG 0(R9), R5, R6
|
|
||||||
|
|
||||||
// Restore LE stack.
|
|
||||||
MOVD SAVSTACK_ASYNC(R8), R9
|
|
||||||
MOVD 0(R9), R4
|
|
||||||
MOVD $0, 0(R9)
|
|
||||||
|
|
||||||
// Fill in parameter list.
|
|
||||||
MOVD a4+32(FP), R12
|
|
||||||
MOVD R12, (2176+24)(R4)
|
|
||||||
MOVD a5+40(FP), R12
|
|
||||||
MOVD R12, (2176+32)(R4)
|
|
||||||
MOVD a6+48(FP), R12
|
|
||||||
MOVD R12, (2176+40)(R4)
|
|
||||||
MOVD a7+56(FP), R12
|
|
||||||
MOVD R12, (2176+48)(R4)
|
|
||||||
MOVD a8+64(FP), R12
|
|
||||||
MOVD R12, (2176+56)(R4)
|
|
||||||
MOVD a9+72(FP), R12
|
|
||||||
MOVD R12, (2176+64)(R4)
|
|
||||||
|
|
||||||
// Call function.
|
|
||||||
LE_CALL
|
|
||||||
NOPH
|
|
||||||
XOR R0, R0 // Restore R0 to $0.
|
|
||||||
MOVD R4, 0(R9) // Save stack pointer.
|
|
||||||
|
|
||||||
MOVD R3, r1+80(FP)
|
|
||||||
MOVD R0, r2+88(FP)
|
|
||||||
MOVD R0, err+96(FP)
|
|
||||||
MOVW R3, R4
|
|
||||||
CMP R4, $-1
|
|
||||||
BNE done
|
|
||||||
BL addrerrno<>(SB)
|
|
||||||
MOVWZ 0(R3), R3
|
|
||||||
MOVD R3, err+96(FP)
|
|
||||||
done:
|
|
||||||
RET
|
RET
|
||||||
|
|
||||||
// func svcCall(fnptr unsafe.Pointer, argv *unsafe.Pointer, dsa *uint64)
|
// func svcCall(fnptr unsafe.Pointer, argv *unsafe.Pointer, dsa *uint64)
|
||||||
TEXT ·svcCall(SB),NOSPLIT,$0
|
TEXT ·svcCall(SB), NOSPLIT, $0
|
||||||
BL runtime·save_g(SB) // Save g and stack pointer
|
BL runtime·save_g(SB) // Save g and stack pointer
|
||||||
MOVW PSALAA, R8
|
MOVW PSALAA, R8
|
||||||
MOVD LCA64(R8), R8
|
MOVD LCA64(R8), R8
|
||||||
MOVD SAVSTACK_ASYNC(R8), R9
|
MOVD SAVSTACK_ASYNC(R8), R9
|
||||||
MOVD R15, 0(R9)
|
MOVD R15, 0(R9)
|
||||||
|
|
||||||
MOVD argv+8(FP), R1 // Move function arguments into registers
|
MOVD argv+8(FP), R1 // Move function arguments into registers
|
||||||
MOVD dsa+16(FP), g
|
MOVD dsa+16(FP), g
|
||||||
MOVD fnptr+0(FP), R15
|
MOVD fnptr+0(FP), R15
|
||||||
|
|
||||||
BYTE $0x0D // Branch to function
|
BYTE $0x0D // Branch to function
|
||||||
BYTE $0xEF
|
BYTE $0xEF
|
||||||
|
|
||||||
BL runtime·load_g(SB) // Restore g and stack pointer
|
BL runtime·load_g(SB) // Restore g and stack pointer
|
||||||
MOVW PSALAA, R8
|
MOVW PSALAA, R8
|
||||||
MOVD LCA64(R8), R8
|
MOVD LCA64(R8), R8
|
||||||
MOVD SAVSTACK_ASYNC(R8), R9
|
MOVD SAVSTACK_ASYNC(R8), R9
|
||||||
MOVD 0(R9), R15
|
MOVD 0(R9), R15
|
||||||
|
|
||||||
RET
|
RET
|
||||||
|
|
||||||
// func svcLoad(name *byte) unsafe.Pointer
|
// func svcLoad(name *byte) unsafe.Pointer
|
||||||
TEXT ·svcLoad(SB),NOSPLIT,$0
|
TEXT ·svcLoad(SB), NOSPLIT, $0
|
||||||
MOVD R15, R2 // Save go stack pointer
|
MOVD R15, R2 // Save go stack pointer
|
||||||
MOVD name+0(FP), R0 // Move SVC args into registers
|
MOVD name+0(FP), R0 // Move SVC args into registers
|
||||||
MOVD $0x80000000, R1
|
MOVD $0x80000000, R1
|
||||||
MOVD $0, R15
|
MOVD $0, R15
|
||||||
BYTE $0x0A // SVC 08 LOAD
|
SVC_LOAD
|
||||||
BYTE $0x08
|
MOVW R15, R3 // Save return code from SVC
|
||||||
MOVW R15, R3 // Save return code from SVC
|
MOVD R2, R15 // Restore go stack pointer
|
||||||
MOVD R2, R15 // Restore go stack pointer
|
CMP R3, $0 // Check SVC return code
|
||||||
CMP R3, $0 // Check SVC return code
|
BNE error
|
||||||
BNE error
|
|
||||||
|
|
||||||
MOVD $-2, R3 // Reset last bit of entry point to zero
|
MOVD $-2, R3 // Reset last bit of entry point to zero
|
||||||
AND R0, R3
|
AND R0, R3
|
||||||
MOVD R3, addr+8(FP) // Return entry point returned by SVC
|
MOVD R3, ret+8(FP) // Return entry point returned by SVC
|
||||||
CMP R0, R3 // Check if last bit of entry point was set
|
CMP R0, R3 // Check if last bit of entry point was set
|
||||||
BNE done
|
BNE done
|
||||||
|
|
||||||
MOVD R15, R2 // Save go stack pointer
|
MOVD R15, R2 // Save go stack pointer
|
||||||
MOVD $0, R15 // Move SVC args into registers (entry point still in r0 from SVC 08)
|
MOVD $0, R15 // Move SVC args into registers (entry point still in r0 from SVC 08)
|
||||||
BYTE $0x0A // SVC 09 DELETE
|
SVC_DELETE
|
||||||
BYTE $0x09
|
MOVD R2, R15 // Restore go stack pointer
|
||||||
MOVD R2, R15 // Restore go stack pointer
|
|
||||||
|
|
||||||
error:
|
error:
|
||||||
MOVD $0, addr+8(FP) // Return 0 on failure
|
MOVD $0, ret+8(FP) // Return 0 on failure
|
||||||
|
|
||||||
done:
|
done:
|
||||||
XOR R0, R0 // Reset r0 to 0
|
XOR R0, R0 // Reset r0 to 0
|
||||||
RET
|
RET
|
||||||
|
|
||||||
// func svcUnload(name *byte, fnptr unsafe.Pointer) int64
|
// func svcUnload(name *byte, fnptr unsafe.Pointer) int64
|
||||||
TEXT ·svcUnload(SB),NOSPLIT,$0
|
TEXT ·svcUnload(SB), NOSPLIT, $0
|
||||||
MOVD R15, R2 // Save go stack pointer
|
MOVD R15, R2 // Save go stack pointer
|
||||||
MOVD name+0(FP), R0 // Move SVC args into registers
|
MOVD name+0(FP), R0 // Move SVC args into registers
|
||||||
MOVD addr+8(FP), R15
|
MOVD fnptr+8(FP), R15
|
||||||
BYTE $0x0A // SVC 09
|
SVC_DELETE
|
||||||
BYTE $0x09
|
XOR R0, R0 // Reset r0 to 0
|
||||||
XOR R0, R0 // Reset r0 to 0
|
MOVD R15, R1 // Save SVC return code
|
||||||
MOVD R15, R1 // Save SVC return code
|
MOVD R2, R15 // Restore go stack pointer
|
||||||
MOVD R2, R15 // Restore go stack pointer
|
MOVD R1, ret+16(FP) // Return SVC return code
|
||||||
MOVD R1, rc+0(FP) // Return SVC return code
|
|
||||||
RET
|
RET
|
||||||
|
|
||||||
// func gettid() uint64
|
// func gettid() uint64
|
||||||
@@ -420,7 +150,233 @@ TEXT ·gettid(SB), NOSPLIT, $0
|
|||||||
|
|
||||||
// Get CEECAATHDID
|
// Get CEECAATHDID
|
||||||
MOVD CAA(R8), R9
|
MOVD CAA(R8), R9
|
||||||
MOVD 0x3D0(R9), R9
|
MOVD CEECAATHDID(R9), R9
|
||||||
MOVD R9, ret+0(FP)
|
MOVD R9, ret+0(FP)
|
||||||
|
|
||||||
RET
|
RET
|
||||||
|
|
||||||
|
//
|
||||||
|
// Call LE function, if the return is -1
|
||||||
|
// errno and errno2 is retrieved
|
||||||
|
//
|
||||||
|
TEXT ·CallLeFuncWithErr(SB), NOSPLIT, $0
|
||||||
|
MOVW PSALAA, R8
|
||||||
|
MOVD LCA64(R8), R8
|
||||||
|
MOVD CAA(R8), R9
|
||||||
|
MOVD g, GOCB(R9)
|
||||||
|
|
||||||
|
// Restore LE stack.
|
||||||
|
MOVD SAVSTACK_ASYNC(R8), R9 // R9-> LE stack frame saving address
|
||||||
|
MOVD 0(R9), R4 // R4-> restore previously saved stack frame pointer
|
||||||
|
|
||||||
|
MOVD parms_base+8(FP), R7 // R7 -> argument array
|
||||||
|
MOVD parms_len+16(FP), R8 // R8 number of arguments
|
||||||
|
|
||||||
|
// arg 1 ---> R1
|
||||||
|
CMP R8, $0
|
||||||
|
BEQ docall
|
||||||
|
SUB $1, R8
|
||||||
|
MOVD 0(R7), R1
|
||||||
|
|
||||||
|
// arg 2 ---> R2
|
||||||
|
CMP R8, $0
|
||||||
|
BEQ docall
|
||||||
|
SUB $1, R8
|
||||||
|
ADD $8, R7
|
||||||
|
MOVD 0(R7), R2
|
||||||
|
|
||||||
|
// arg 3 --> R3
|
||||||
|
CMP R8, $0
|
||||||
|
BEQ docall
|
||||||
|
SUB $1, R8
|
||||||
|
ADD $8, R7
|
||||||
|
MOVD 0(R7), R3
|
||||||
|
|
||||||
|
CMP R8, $0
|
||||||
|
BEQ docall
|
||||||
|
MOVD $2176+16, R6 // starting LE stack address-8 to store 4th argument
|
||||||
|
|
||||||
|
repeat:
|
||||||
|
ADD $8, R7
|
||||||
|
MOVD 0(R7), R0 // advance arg pointer by 8 byte
|
||||||
|
ADD $8, R6 // advance LE argument address by 8 byte
|
||||||
|
MOVD R0, (R4)(R6*1) // copy argument from go-slice to le-frame
|
||||||
|
SUB $1, R8
|
||||||
|
CMP R8, $0
|
||||||
|
BNE repeat
|
||||||
|
|
||||||
|
docall:
|
||||||
|
MOVD funcdesc+0(FP), R8 // R8-> function descriptor
|
||||||
|
LMG 0(R8), R5, R6
|
||||||
|
MOVD $0, 0(R9) // R9 address of SAVSTACK_ASYNC
|
||||||
|
LE_CALL // balr R7, R6 (return #1)
|
||||||
|
NOPH
|
||||||
|
MOVD R3, ret+32(FP)
|
||||||
|
CMP R3, $-1 // compare result to -1
|
||||||
|
BNE done
|
||||||
|
|
||||||
|
// retrieve errno and errno2
|
||||||
|
MOVD zosLibVec<>(SB), R8
|
||||||
|
ADD $(__errno), R8
|
||||||
|
LMG 0(R8), R5, R6
|
||||||
|
LE_CALL // balr R7, R6 __errno (return #3)
|
||||||
|
NOPH
|
||||||
|
MOVWZ 0(R3), R3
|
||||||
|
MOVD R3, err+48(FP)
|
||||||
|
MOVD zosLibVec<>(SB), R8
|
||||||
|
ADD $(__err2ad), R8
|
||||||
|
LMG 0(R8), R5, R6
|
||||||
|
LE_CALL // balr R7, R6 __err2ad (return #2)
|
||||||
|
NOPH
|
||||||
|
MOVW (R3), R2 // retrieve errno2
|
||||||
|
MOVD R2, errno2+40(FP) // store in return area
|
||||||
|
|
||||||
|
done:
|
||||||
|
MOVD R4, 0(R9) // Save stack pointer.
|
||||||
|
RET
|
||||||
|
|
||||||
|
//
|
||||||
|
// Call LE function, if the return is 0
|
||||||
|
// errno and errno2 is retrieved
|
||||||
|
//
|
||||||
|
TEXT ·CallLeFuncWithPtrReturn(SB), NOSPLIT, $0
|
||||||
|
MOVW PSALAA, R8
|
||||||
|
MOVD LCA64(R8), R8
|
||||||
|
MOVD CAA(R8), R9
|
||||||
|
MOVD g, GOCB(R9)
|
||||||
|
|
||||||
|
// Restore LE stack.
|
||||||
|
MOVD SAVSTACK_ASYNC(R8), R9 // R9-> LE stack frame saving address
|
||||||
|
MOVD 0(R9), R4 // R4-> restore previously saved stack frame pointer
|
||||||
|
|
||||||
|
MOVD parms_base+8(FP), R7 // R7 -> argument array
|
||||||
|
MOVD parms_len+16(FP), R8 // R8 number of arguments
|
||||||
|
|
||||||
|
// arg 1 ---> R1
|
||||||
|
CMP R8, $0
|
||||||
|
BEQ docall
|
||||||
|
SUB $1, R8
|
||||||
|
MOVD 0(R7), R1
|
||||||
|
|
||||||
|
// arg 2 ---> R2
|
||||||
|
CMP R8, $0
|
||||||
|
BEQ docall
|
||||||
|
SUB $1, R8
|
||||||
|
ADD $8, R7
|
||||||
|
MOVD 0(R7), R2
|
||||||
|
|
||||||
|
// arg 3 --> R3
|
||||||
|
CMP R8, $0
|
||||||
|
BEQ docall
|
||||||
|
SUB $1, R8
|
||||||
|
ADD $8, R7
|
||||||
|
MOVD 0(R7), R3
|
||||||
|
|
||||||
|
CMP R8, $0
|
||||||
|
BEQ docall
|
||||||
|
MOVD $2176+16, R6 // starting LE stack address-8 to store 4th argument
|
||||||
|
|
||||||
|
repeat:
|
||||||
|
ADD $8, R7
|
||||||
|
MOVD 0(R7), R0 // advance arg pointer by 8 byte
|
||||||
|
ADD $8, R6 // advance LE argument address by 8 byte
|
||||||
|
MOVD R0, (R4)(R6*1) // copy argument from go-slice to le-frame
|
||||||
|
SUB $1, R8
|
||||||
|
CMP R8, $0
|
||||||
|
BNE repeat
|
||||||
|
|
||||||
|
docall:
|
||||||
|
MOVD funcdesc+0(FP), R8 // R8-> function descriptor
|
||||||
|
LMG 0(R8), R5, R6
|
||||||
|
MOVD $0, 0(R9) // R9 address of SAVSTACK_ASYNC
|
||||||
|
LE_CALL // balr R7, R6 (return #1)
|
||||||
|
NOPH
|
||||||
|
MOVD R3, ret+32(FP)
|
||||||
|
CMP R3, $0 // compare result to 0
|
||||||
|
BNE done
|
||||||
|
|
||||||
|
// retrieve errno and errno2
|
||||||
|
MOVD zosLibVec<>(SB), R8
|
||||||
|
ADD $(__errno), R8
|
||||||
|
LMG 0(R8), R5, R6
|
||||||
|
LE_CALL // balr R7, R6 __errno (return #3)
|
||||||
|
NOPH
|
||||||
|
MOVWZ 0(R3), R3
|
||||||
|
MOVD R3, err+48(FP)
|
||||||
|
MOVD zosLibVec<>(SB), R8
|
||||||
|
ADD $(__err2ad), R8
|
||||||
|
LMG 0(R8), R5, R6
|
||||||
|
LE_CALL // balr R7, R6 __err2ad (return #2)
|
||||||
|
NOPH
|
||||||
|
MOVW (R3), R2 // retrieve errno2
|
||||||
|
MOVD R2, errno2+40(FP) // store in return area
|
||||||
|
XOR R2, R2
|
||||||
|
MOVWZ R2, (R3) // clear errno2
|
||||||
|
|
||||||
|
done:
|
||||||
|
MOVD R4, 0(R9) // Save stack pointer.
|
||||||
|
RET
|
||||||
|
|
||||||
|
//
|
||||||
|
// function to test if a pointer can be safely dereferenced (content read)
|
||||||
|
// return 0 for succces
|
||||||
|
//
|
||||||
|
TEXT ·ptrtest(SB), NOSPLIT, $0-16
|
||||||
|
MOVD arg+0(FP), R10 // test pointer in R10
|
||||||
|
|
||||||
|
// set up R2 to point to CEECAADMC
|
||||||
|
BYTE $0xE3; BYTE $0x20; BYTE $0x04; BYTE $0xB8; BYTE $0x00; BYTE $0x17 // llgt 2,1208
|
||||||
|
BYTE $0xB9; BYTE $0x17; BYTE $0x00; BYTE $0x22 // llgtr 2,2
|
||||||
|
BYTE $0xA5; BYTE $0x26; BYTE $0x7F; BYTE $0xFF // nilh 2,32767
|
||||||
|
BYTE $0xE3; BYTE $0x22; BYTE $0x00; BYTE $0x58; BYTE $0x00; BYTE $0x04 // lg 2,88(2)
|
||||||
|
BYTE $0xE3; BYTE $0x22; BYTE $0x00; BYTE $0x08; BYTE $0x00; BYTE $0x04 // lg 2,8(2)
|
||||||
|
BYTE $0x41; BYTE $0x22; BYTE $0x03; BYTE $0x68 // la 2,872(2)
|
||||||
|
|
||||||
|
// set up R5 to point to the "shunt" path which set 1 to R3 (failure)
|
||||||
|
BYTE $0xB9; BYTE $0x82; BYTE $0x00; BYTE $0x33 // xgr 3,3
|
||||||
|
BYTE $0xA7; BYTE $0x55; BYTE $0x00; BYTE $0x04 // bras 5,lbl1
|
||||||
|
BYTE $0xA7; BYTE $0x39; BYTE $0x00; BYTE $0x01 // lghi 3,1
|
||||||
|
|
||||||
|
// if r3 is not zero (failed) then branch to finish
|
||||||
|
BYTE $0xB9; BYTE $0x02; BYTE $0x00; BYTE $0x33 // lbl1 ltgr 3,3
|
||||||
|
BYTE $0xA7; BYTE $0x74; BYTE $0x00; BYTE $0x08 // brc b'0111',lbl2
|
||||||
|
|
||||||
|
// stomic store shunt address in R5 into CEECAADMC
|
||||||
|
BYTE $0xE3; BYTE $0x52; BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x24 // stg 5,0(2)
|
||||||
|
|
||||||
|
// now try reading from the test pointer in R10, if it fails it branches to the "lghi" instruction above
|
||||||
|
BYTE $0xE3; BYTE $0x9A; BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x04 // lg 9,0(10)
|
||||||
|
|
||||||
|
// finish here, restore 0 into CEECAADMC
|
||||||
|
BYTE $0xB9; BYTE $0x82; BYTE $0x00; BYTE $0x99 // lbl2 xgr 9,9
|
||||||
|
BYTE $0xE3; BYTE $0x92; BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x24 // stg 9,0(2)
|
||||||
|
MOVD R3, ret+8(FP) // result in R3
|
||||||
|
RET
|
||||||
|
|
||||||
|
//
|
||||||
|
// function to test if a untptr can be loaded from a pointer
|
||||||
|
// return 1: the 8-byte content
|
||||||
|
// 2: 0 for success, 1 for failure
|
||||||
|
//
|
||||||
|
// func safeload(ptr uintptr) ( value uintptr, error uintptr)
|
||||||
|
TEXT ·safeload(SB), NOSPLIT, $0-24
|
||||||
|
MOVD ptr+0(FP), R10 // test pointer in R10
|
||||||
|
MOVD $0x0, R6
|
||||||
|
BYTE $0xE3; BYTE $0x20; BYTE $0x04; BYTE $0xB8; BYTE $0x00; BYTE $0x17 // llgt 2,1208
|
||||||
|
BYTE $0xB9; BYTE $0x17; BYTE $0x00; BYTE $0x22 // llgtr 2,2
|
||||||
|
BYTE $0xA5; BYTE $0x26; BYTE $0x7F; BYTE $0xFF // nilh 2,32767
|
||||||
|
BYTE $0xE3; BYTE $0x22; BYTE $0x00; BYTE $0x58; BYTE $0x00; BYTE $0x04 // lg 2,88(2)
|
||||||
|
BYTE $0xE3; BYTE $0x22; BYTE $0x00; BYTE $0x08; BYTE $0x00; BYTE $0x04 // lg 2,8(2)
|
||||||
|
BYTE $0x41; BYTE $0x22; BYTE $0x03; BYTE $0x68 // la 2,872(2)
|
||||||
|
BYTE $0xB9; BYTE $0x82; BYTE $0x00; BYTE $0x33 // xgr 3,3
|
||||||
|
BYTE $0xA7; BYTE $0x55; BYTE $0x00; BYTE $0x04 // bras 5,lbl1
|
||||||
|
BYTE $0xA7; BYTE $0x39; BYTE $0x00; BYTE $0x01 // lghi 3,1
|
||||||
|
BYTE $0xB9; BYTE $0x02; BYTE $0x00; BYTE $0x33 // lbl1 ltgr 3,3
|
||||||
|
BYTE $0xA7; BYTE $0x74; BYTE $0x00; BYTE $0x08 // brc b'0111',lbl2
|
||||||
|
BYTE $0xE3; BYTE $0x52; BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x24 // stg 5,0(2)
|
||||||
|
BYTE $0xE3; BYTE $0x6A; BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x04 // lg 6,0(10)
|
||||||
|
BYTE $0xB9; BYTE $0x82; BYTE $0x00; BYTE $0x99 // lbl2 xgr 9,9
|
||||||
|
BYTE $0xE3; BYTE $0x92; BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x24 // stg 9,0(2)
|
||||||
|
MOVD R6, value+8(FP) // result in R6
|
||||||
|
MOVD R3, error+16(FP) // error in R3
|
||||||
|
RET
|
||||||
|
|||||||
36
vendor/golang.org/x/sys/unix/auxv.go
generated
vendored
Normal file
36
vendor/golang.org/x/sys/unix/auxv.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2025 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos)
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:linkname runtime_getAuxv runtime.getAuxv
|
||||||
|
func runtime_getAuxv() []uintptr
|
||||||
|
|
||||||
|
// Auxv returns the ELF auxiliary vector as a sequence of key/value pairs.
|
||||||
|
// The returned slice is always a fresh copy, owned by the caller.
|
||||||
|
// It returns an error on non-ELF platforms, or if the auxiliary vector cannot be accessed,
|
||||||
|
// which happens in some locked-down environments and build modes.
|
||||||
|
func Auxv() ([][2]uintptr, error) {
|
||||||
|
vec := runtime_getAuxv()
|
||||||
|
vecLen := len(vec)
|
||||||
|
|
||||||
|
if vecLen == 0 {
|
||||||
|
return nil, syscall.ENOENT
|
||||||
|
}
|
||||||
|
|
||||||
|
if vecLen%2 != 0 {
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]uintptr, vecLen)
|
||||||
|
copy(result, vec)
|
||||||
|
return unsafe.Slice((*[2]uintptr)(unsafe.Pointer(&result[0])), vecLen/2), nil
|
||||||
|
}
|
||||||
13
vendor/golang.org/x/sys/unix/auxv_unsupported.go
generated
vendored
Normal file
13
vendor/golang.org/x/sys/unix/auxv_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2025 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos)
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func Auxv() ([][2]uintptr, error) {
|
||||||
|
return nil, syscall.ENOTSUP
|
||||||
|
}
|
||||||
657
vendor/golang.org/x/sys/unix/bpxsvc_zos.go
generated
vendored
Normal file
657
vendor/golang.org/x/sys/unix/bpxsvc_zos.go
generated
vendored
Normal file
@@ -0,0 +1,657 @@
|
|||||||
|
// Copyright 2024 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build zos
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
func bpxcall(plist []unsafe.Pointer, bpx_offset int64)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
func A2e([]byte)
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
func E2a([]byte)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BPX4STA = 192 // stat
|
||||||
|
BPX4FST = 104 // fstat
|
||||||
|
BPX4LST = 132 // lstat
|
||||||
|
BPX4OPN = 156 // open
|
||||||
|
BPX4CLO = 72 // close
|
||||||
|
BPX4CHR = 500 // chattr
|
||||||
|
BPX4FCR = 504 // fchattr
|
||||||
|
BPX4LCR = 1180 // lchattr
|
||||||
|
BPX4CTW = 492 // cond_timed_wait
|
||||||
|
BPX4GTH = 1056 // __getthent
|
||||||
|
BPX4PTQ = 412 // pthread_quiesc
|
||||||
|
BPX4PTR = 320 // ptrace
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
//options
|
||||||
|
//byte1
|
||||||
|
BPX_OPNFHIGH = 0x80
|
||||||
|
//byte2
|
||||||
|
BPX_OPNFEXEC = 0x80
|
||||||
|
//byte3
|
||||||
|
BPX_O_NOLARGEFILE = 0x08
|
||||||
|
BPX_O_LARGEFILE = 0x04
|
||||||
|
BPX_O_ASYNCSIG = 0x02
|
||||||
|
BPX_O_SYNC = 0x01
|
||||||
|
//byte4
|
||||||
|
BPX_O_CREXCL = 0xc0
|
||||||
|
BPX_O_CREAT = 0x80
|
||||||
|
BPX_O_EXCL = 0x40
|
||||||
|
BPX_O_NOCTTY = 0x20
|
||||||
|
BPX_O_TRUNC = 0x10
|
||||||
|
BPX_O_APPEND = 0x08
|
||||||
|
BPX_O_NONBLOCK = 0x04
|
||||||
|
BPX_FNDELAY = 0x04
|
||||||
|
BPX_O_RDWR = 0x03
|
||||||
|
BPX_O_RDONLY = 0x02
|
||||||
|
BPX_O_WRONLY = 0x01
|
||||||
|
BPX_O_ACCMODE = 0x03
|
||||||
|
BPX_O_GETFL = 0x0f
|
||||||
|
|
||||||
|
//mode
|
||||||
|
// byte1 (file type)
|
||||||
|
BPX_FT_DIR = 1
|
||||||
|
BPX_FT_CHARSPEC = 2
|
||||||
|
BPX_FT_REGFILE = 3
|
||||||
|
BPX_FT_FIFO = 4
|
||||||
|
BPX_FT_SYMLINK = 5
|
||||||
|
BPX_FT_SOCKET = 6
|
||||||
|
//byte3
|
||||||
|
BPX_S_ISUID = 0x08
|
||||||
|
BPX_S_ISGID = 0x04
|
||||||
|
BPX_S_ISVTX = 0x02
|
||||||
|
BPX_S_IRWXU1 = 0x01
|
||||||
|
BPX_S_IRUSR = 0x01
|
||||||
|
//byte4
|
||||||
|
BPX_S_IRWXU2 = 0xc0
|
||||||
|
BPX_S_IWUSR = 0x80
|
||||||
|
BPX_S_IXUSR = 0x40
|
||||||
|
BPX_S_IRWXG = 0x38
|
||||||
|
BPX_S_IRGRP = 0x20
|
||||||
|
BPX_S_IWGRP = 0x10
|
||||||
|
BPX_S_IXGRP = 0x08
|
||||||
|
BPX_S_IRWXOX = 0x07
|
||||||
|
BPX_S_IROTH = 0x04
|
||||||
|
BPX_S_IWOTH = 0x02
|
||||||
|
BPX_S_IXOTH = 0x01
|
||||||
|
|
||||||
|
CW_INTRPT = 1
|
||||||
|
CW_CONDVAR = 32
|
||||||
|
CW_TIMEOUT = 64
|
||||||
|
|
||||||
|
PGTHA_NEXT = 2
|
||||||
|
PGTHA_CURRENT = 1
|
||||||
|
PGTHA_FIRST = 0
|
||||||
|
PGTHA_LAST = 3
|
||||||
|
PGTHA_PROCESS = 0x80
|
||||||
|
PGTHA_CONTTY = 0x40
|
||||||
|
PGTHA_PATH = 0x20
|
||||||
|
PGTHA_COMMAND = 0x10
|
||||||
|
PGTHA_FILEDATA = 0x08
|
||||||
|
PGTHA_THREAD = 0x04
|
||||||
|
PGTHA_PTAG = 0x02
|
||||||
|
PGTHA_COMMANDLONG = 0x01
|
||||||
|
PGTHA_THREADFAST = 0x80
|
||||||
|
PGTHA_FILEPATH = 0x40
|
||||||
|
PGTHA_THDSIGMASK = 0x20
|
||||||
|
// thread quiece mode
|
||||||
|
QUIESCE_TERM int32 = 1
|
||||||
|
QUIESCE_FORCE int32 = 2
|
||||||
|
QUIESCE_QUERY int32 = 3
|
||||||
|
QUIESCE_FREEZE int32 = 4
|
||||||
|
QUIESCE_UNFREEZE int32 = 5
|
||||||
|
FREEZE_THIS_THREAD int32 = 6
|
||||||
|
FREEZE_EXIT int32 = 8
|
||||||
|
QUIESCE_SRB int32 = 9
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pgtha struct {
|
||||||
|
Pid uint32 // 0
|
||||||
|
Tid0 uint32 // 4
|
||||||
|
Tid1 uint32
|
||||||
|
Accesspid byte // C
|
||||||
|
Accesstid byte // D
|
||||||
|
Accessasid uint16 // E
|
||||||
|
Loginname [8]byte // 10
|
||||||
|
Flag1 byte // 18
|
||||||
|
Flag1b2 byte // 19
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bpxystat_t struct { // DSECT BPXYSTAT
|
||||||
|
St_id [4]uint8 // 0
|
||||||
|
St_length uint16 // 0x4
|
||||||
|
St_version uint16 // 0x6
|
||||||
|
St_mode uint32 // 0x8
|
||||||
|
St_ino uint32 // 0xc
|
||||||
|
St_dev uint32 // 0x10
|
||||||
|
St_nlink uint32 // 0x14
|
||||||
|
St_uid uint32 // 0x18
|
||||||
|
St_gid uint32 // 0x1c
|
||||||
|
St_size uint64 // 0x20
|
||||||
|
St_atime uint32 // 0x28
|
||||||
|
St_mtime uint32 // 0x2c
|
||||||
|
St_ctime uint32 // 0x30
|
||||||
|
St_rdev uint32 // 0x34
|
||||||
|
St_auditoraudit uint32 // 0x38
|
||||||
|
St_useraudit uint32 // 0x3c
|
||||||
|
St_blksize uint32 // 0x40
|
||||||
|
St_createtime uint32 // 0x44
|
||||||
|
St_auditid [4]uint32 // 0x48
|
||||||
|
St_res01 uint32 // 0x58
|
||||||
|
Ft_ccsid uint16 // 0x5c
|
||||||
|
Ft_flags uint16 // 0x5e
|
||||||
|
St_res01a [2]uint32 // 0x60
|
||||||
|
St_res02 uint32 // 0x68
|
||||||
|
St_blocks uint32 // 0x6c
|
||||||
|
St_opaque [3]uint8 // 0x70
|
||||||
|
St_visible uint8 // 0x73
|
||||||
|
St_reftime uint32 // 0x74
|
||||||
|
St_fid uint64 // 0x78
|
||||||
|
St_filefmt uint8 // 0x80
|
||||||
|
St_fspflag2 uint8 // 0x81
|
||||||
|
St_res03 [2]uint8 // 0x82
|
||||||
|
St_ctimemsec uint32 // 0x84
|
||||||
|
St_seclabel [8]uint8 // 0x88
|
||||||
|
St_res04 [4]uint8 // 0x90
|
||||||
|
// end of version 1
|
||||||
|
_ uint32 // 0x94
|
||||||
|
St_atime64 uint64 // 0x98
|
||||||
|
St_mtime64 uint64 // 0xa0
|
||||||
|
St_ctime64 uint64 // 0xa8
|
||||||
|
St_createtime64 uint64 // 0xb0
|
||||||
|
St_reftime64 uint64 // 0xb8
|
||||||
|
_ uint64 // 0xc0
|
||||||
|
St_res05 [16]uint8 // 0xc8
|
||||||
|
// end of version 2
|
||||||
|
}
|
||||||
|
|
||||||
|
type BpxFilestatus struct {
|
||||||
|
Oflag1 byte
|
||||||
|
Oflag2 byte
|
||||||
|
Oflag3 byte
|
||||||
|
Oflag4 byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type BpxMode struct {
|
||||||
|
Ftype byte
|
||||||
|
Mode1 byte
|
||||||
|
Mode2 byte
|
||||||
|
Mode3 byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thr attribute structure for extended attributes
|
||||||
|
type Bpxyatt_t struct { // DSECT BPXYATT
|
||||||
|
Att_id [4]uint8
|
||||||
|
Att_version uint16
|
||||||
|
Att_res01 [2]uint8
|
||||||
|
Att_setflags1 uint8
|
||||||
|
Att_setflags2 uint8
|
||||||
|
Att_setflags3 uint8
|
||||||
|
Att_setflags4 uint8
|
||||||
|
Att_mode uint32
|
||||||
|
Att_uid uint32
|
||||||
|
Att_gid uint32
|
||||||
|
Att_opaquemask [3]uint8
|
||||||
|
Att_visblmaskres uint8
|
||||||
|
Att_opaque [3]uint8
|
||||||
|
Att_visibleres uint8
|
||||||
|
Att_size_h uint32
|
||||||
|
Att_size_l uint32
|
||||||
|
Att_atime uint32
|
||||||
|
Att_mtime uint32
|
||||||
|
Att_auditoraudit uint32
|
||||||
|
Att_useraudit uint32
|
||||||
|
Att_ctime uint32
|
||||||
|
Att_reftime uint32
|
||||||
|
// end of version 1
|
||||||
|
Att_filefmt uint8
|
||||||
|
Att_res02 [3]uint8
|
||||||
|
Att_filetag uint32
|
||||||
|
Att_res03 [8]uint8
|
||||||
|
// end of version 2
|
||||||
|
Att_atime64 uint64
|
||||||
|
Att_mtime64 uint64
|
||||||
|
Att_ctime64 uint64
|
||||||
|
Att_reftime64 uint64
|
||||||
|
Att_seclabel [8]uint8
|
||||||
|
Att_ver3res02 [8]uint8
|
||||||
|
// end of version 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func BpxOpen(name string, options *BpxFilestatus, mode *BpxMode) (rv int32, rc int32, rn int32) {
|
||||||
|
if len(name) < 1024 {
|
||||||
|
var namebuf [1024]byte
|
||||||
|
sz := int32(copy(namebuf[:], name))
|
||||||
|
A2e(namebuf[:sz])
|
||||||
|
var parms [7]unsafe.Pointer
|
||||||
|
parms[0] = unsafe.Pointer(&sz)
|
||||||
|
parms[1] = unsafe.Pointer(&namebuf[0])
|
||||||
|
parms[2] = unsafe.Pointer(options)
|
||||||
|
parms[3] = unsafe.Pointer(mode)
|
||||||
|
parms[4] = unsafe.Pointer(&rv)
|
||||||
|
parms[5] = unsafe.Pointer(&rc)
|
||||||
|
parms[6] = unsafe.Pointer(&rn)
|
||||||
|
bpxcall(parms[:], BPX4OPN)
|
||||||
|
return rv, rc, rn
|
||||||
|
}
|
||||||
|
return -1, -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func BpxClose(fd int32) (rv int32, rc int32, rn int32) {
|
||||||
|
var parms [4]unsafe.Pointer
|
||||||
|
parms[0] = unsafe.Pointer(&fd)
|
||||||
|
parms[1] = unsafe.Pointer(&rv)
|
||||||
|
parms[2] = unsafe.Pointer(&rc)
|
||||||
|
parms[3] = unsafe.Pointer(&rn)
|
||||||
|
bpxcall(parms[:], BPX4CLO)
|
||||||
|
return rv, rc, rn
|
||||||
|
}
|
||||||
|
|
||||||
|
func BpxFileFStat(fd int32, st *Bpxystat_t) (rv int32, rc int32, rn int32) {
|
||||||
|
st.St_id = [4]uint8{0xe2, 0xe3, 0xc1, 0xe3}
|
||||||
|
st.St_version = 2
|
||||||
|
stat_sz := uint32(unsafe.Sizeof(*st))
|
||||||
|
var parms [6]unsafe.Pointer
|
||||||
|
parms[0] = unsafe.Pointer(&fd)
|
||||||
|
parms[1] = unsafe.Pointer(&stat_sz)
|
||||||
|
parms[2] = unsafe.Pointer(st)
|
||||||
|
parms[3] = unsafe.Pointer(&rv)
|
||||||
|
parms[4] = unsafe.Pointer(&rc)
|
||||||
|
parms[5] = unsafe.Pointer(&rn)
|
||||||
|
bpxcall(parms[:], BPX4FST)
|
||||||
|
return rv, rc, rn
|
||||||
|
}
|
||||||
|
|
||||||
|
func BpxFileStat(name string, st *Bpxystat_t) (rv int32, rc int32, rn int32) {
|
||||||
|
if len(name) < 1024 {
|
||||||
|
var namebuf [1024]byte
|
||||||
|
sz := int32(copy(namebuf[:], name))
|
||||||
|
A2e(namebuf[:sz])
|
||||||
|
st.St_id = [4]uint8{0xe2, 0xe3, 0xc1, 0xe3}
|
||||||
|
st.St_version = 2
|
||||||
|
stat_sz := uint32(unsafe.Sizeof(*st))
|
||||||
|
var parms [7]unsafe.Pointer
|
||||||
|
parms[0] = unsafe.Pointer(&sz)
|
||||||
|
parms[1] = unsafe.Pointer(&namebuf[0])
|
||||||
|
parms[2] = unsafe.Pointer(&stat_sz)
|
||||||
|
parms[3] = unsafe.Pointer(st)
|
||||||
|
parms[4] = unsafe.Pointer(&rv)
|
||||||
|
parms[5] = unsafe.Pointer(&rc)
|
||||||
|
parms[6] = unsafe.Pointer(&rn)
|
||||||
|
bpxcall(parms[:], BPX4STA)
|
||||||
|
return rv, rc, rn
|
||||||
|
}
|
||||||
|
return -1, -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func BpxFileLStat(name string, st *Bpxystat_t) (rv int32, rc int32, rn int32) {
|
||||||
|
if len(name) < 1024 {
|
||||||
|
var namebuf [1024]byte
|
||||||
|
sz := int32(copy(namebuf[:], name))
|
||||||
|
A2e(namebuf[:sz])
|
||||||
|
st.St_id = [4]uint8{0xe2, 0xe3, 0xc1, 0xe3}
|
||||||
|
st.St_version = 2
|
||||||
|
stat_sz := uint32(unsafe.Sizeof(*st))
|
||||||
|
var parms [7]unsafe.Pointer
|
||||||
|
parms[0] = unsafe.Pointer(&sz)
|
||||||
|
parms[1] = unsafe.Pointer(&namebuf[0])
|
||||||
|
parms[2] = unsafe.Pointer(&stat_sz)
|
||||||
|
parms[3] = unsafe.Pointer(st)
|
||||||
|
parms[4] = unsafe.Pointer(&rv)
|
||||||
|
parms[5] = unsafe.Pointer(&rc)
|
||||||
|
parms[6] = unsafe.Pointer(&rn)
|
||||||
|
bpxcall(parms[:], BPX4LST)
|
||||||
|
return rv, rc, rn
|
||||||
|
}
|
||||||
|
return -1, -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func BpxChattr(path string, attr *Bpxyatt_t) (rv int32, rc int32, rn int32) {
|
||||||
|
if len(path) >= 1024 {
|
||||||
|
return -1, -1, -1
|
||||||
|
}
|
||||||
|
var namebuf [1024]byte
|
||||||
|
sz := int32(copy(namebuf[:], path))
|
||||||
|
A2e(namebuf[:sz])
|
||||||
|
attr_sz := uint32(unsafe.Sizeof(*attr))
|
||||||
|
var parms [7]unsafe.Pointer
|
||||||
|
parms[0] = unsafe.Pointer(&sz)
|
||||||
|
parms[1] = unsafe.Pointer(&namebuf[0])
|
||||||
|
parms[2] = unsafe.Pointer(&attr_sz)
|
||||||
|
parms[3] = unsafe.Pointer(attr)
|
||||||
|
parms[4] = unsafe.Pointer(&rv)
|
||||||
|
parms[5] = unsafe.Pointer(&rc)
|
||||||
|
parms[6] = unsafe.Pointer(&rn)
|
||||||
|
bpxcall(parms[:], BPX4CHR)
|
||||||
|
return rv, rc, rn
|
||||||
|
}
|
||||||
|
|
||||||
|
func BpxLchattr(path string, attr *Bpxyatt_t) (rv int32, rc int32, rn int32) {
|
||||||
|
if len(path) >= 1024 {
|
||||||
|
return -1, -1, -1
|
||||||
|
}
|
||||||
|
var namebuf [1024]byte
|
||||||
|
sz := int32(copy(namebuf[:], path))
|
||||||
|
A2e(namebuf[:sz])
|
||||||
|
attr_sz := uint32(unsafe.Sizeof(*attr))
|
||||||
|
var parms [7]unsafe.Pointer
|
||||||
|
parms[0] = unsafe.Pointer(&sz)
|
||||||
|
parms[1] = unsafe.Pointer(&namebuf[0])
|
||||||
|
parms[2] = unsafe.Pointer(&attr_sz)
|
||||||
|
parms[3] = unsafe.Pointer(attr)
|
||||||
|
parms[4] = unsafe.Pointer(&rv)
|
||||||
|
parms[5] = unsafe.Pointer(&rc)
|
||||||
|
parms[6] = unsafe.Pointer(&rn)
|
||||||
|
bpxcall(parms[:], BPX4LCR)
|
||||||
|
return rv, rc, rn
|
||||||
|
}
|
||||||
|
|
||||||
|
func BpxFchattr(fd int32, attr *Bpxyatt_t) (rv int32, rc int32, rn int32) {
|
||||||
|
attr_sz := uint32(unsafe.Sizeof(*attr))
|
||||||
|
var parms [6]unsafe.Pointer
|
||||||
|
parms[0] = unsafe.Pointer(&fd)
|
||||||
|
parms[1] = unsafe.Pointer(&attr_sz)
|
||||||
|
parms[2] = unsafe.Pointer(attr)
|
||||||
|
parms[3] = unsafe.Pointer(&rv)
|
||||||
|
parms[4] = unsafe.Pointer(&rc)
|
||||||
|
parms[5] = unsafe.Pointer(&rn)
|
||||||
|
bpxcall(parms[:], BPX4FCR)
|
||||||
|
return rv, rc, rn
|
||||||
|
}
|
||||||
|
|
||||||
|
func BpxCondTimedWait(sec uint32, nsec uint32, events uint32, secrem *uint32, nsecrem *uint32) (rv int32, rc int32, rn int32) {
|
||||||
|
var parms [8]unsafe.Pointer
|
||||||
|
parms[0] = unsafe.Pointer(&sec)
|
||||||
|
parms[1] = unsafe.Pointer(&nsec)
|
||||||
|
parms[2] = unsafe.Pointer(&events)
|
||||||
|
parms[3] = unsafe.Pointer(secrem)
|
||||||
|
parms[4] = unsafe.Pointer(nsecrem)
|
||||||
|
parms[5] = unsafe.Pointer(&rv)
|
||||||
|
parms[6] = unsafe.Pointer(&rc)
|
||||||
|
parms[7] = unsafe.Pointer(&rn)
|
||||||
|
bpxcall(parms[:], BPX4CTW)
|
||||||
|
return rv, rc, rn
|
||||||
|
}
|
||||||
|
func BpxGetthent(in *Pgtha, outlen *uint32, out unsafe.Pointer) (rv int32, rc int32, rn int32) {
|
||||||
|
var parms [7]unsafe.Pointer
|
||||||
|
inlen := uint32(26) // nothing else will work. Go says Pgtha is 28-byte because of alignment, but Pgtha is "packed" and must be 26-byte
|
||||||
|
parms[0] = unsafe.Pointer(&inlen)
|
||||||
|
parms[1] = unsafe.Pointer(&in)
|
||||||
|
parms[2] = unsafe.Pointer(outlen)
|
||||||
|
parms[3] = unsafe.Pointer(&out)
|
||||||
|
parms[4] = unsafe.Pointer(&rv)
|
||||||
|
parms[5] = unsafe.Pointer(&rc)
|
||||||
|
parms[6] = unsafe.Pointer(&rn)
|
||||||
|
bpxcall(parms[:], BPX4GTH)
|
||||||
|
return rv, rc, rn
|
||||||
|
}
|
||||||
|
func ZosJobname() (jobname string, err error) {
|
||||||
|
var pgtha Pgtha
|
||||||
|
pgtha.Pid = uint32(Getpid())
|
||||||
|
pgtha.Accesspid = PGTHA_CURRENT
|
||||||
|
pgtha.Flag1 = PGTHA_PROCESS
|
||||||
|
var out [256]byte
|
||||||
|
var outlen uint32
|
||||||
|
outlen = 256
|
||||||
|
rv, rc, rn := BpxGetthent(&pgtha, &outlen, unsafe.Pointer(&out[0]))
|
||||||
|
if rv == 0 {
|
||||||
|
gthc := []byte{0x87, 0xa3, 0x88, 0x83} // 'gthc' in ebcdic
|
||||||
|
ix := bytes.Index(out[:], gthc)
|
||||||
|
if ix == -1 {
|
||||||
|
err = fmt.Errorf("BPX4GTH: gthc return data not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jn := out[ix+80 : ix+88] // we didn't declare Pgthc, but jobname is 8-byte at offset 80
|
||||||
|
E2a(jn)
|
||||||
|
jobname = string(bytes.TrimRight(jn, " "))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("BPX4GTH: rc=%d errno=%d reason=code=0x%x", rv, rc, rn)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func Bpx4ptq(code int32, data string) (rv int32, rc int32, rn int32) {
|
||||||
|
var userdata [8]byte
|
||||||
|
var parms [5]unsafe.Pointer
|
||||||
|
copy(userdata[:], data+" ")
|
||||||
|
A2e(userdata[:])
|
||||||
|
parms[0] = unsafe.Pointer(&code)
|
||||||
|
parms[1] = unsafe.Pointer(&userdata[0])
|
||||||
|
parms[2] = unsafe.Pointer(&rv)
|
||||||
|
parms[3] = unsafe.Pointer(&rc)
|
||||||
|
parms[4] = unsafe.Pointer(&rn)
|
||||||
|
bpxcall(parms[:], BPX4PTQ)
|
||||||
|
return rv, rc, rn
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
PT_TRACE_ME = 0 // Debug this process
|
||||||
|
PT_READ_I = 1 // Read a full word
|
||||||
|
PT_READ_D = 2 // Read a full word
|
||||||
|
PT_READ_U = 3 // Read control info
|
||||||
|
PT_WRITE_I = 4 //Write a full word
|
||||||
|
PT_WRITE_D = 5 //Write a full word
|
||||||
|
PT_CONTINUE = 7 //Continue the process
|
||||||
|
PT_KILL = 8 //Terminate the process
|
||||||
|
PT_READ_GPR = 11 // Read GPR, CR, PSW
|
||||||
|
PT_READ_FPR = 12 // Read FPR
|
||||||
|
PT_READ_VR = 13 // Read VR
|
||||||
|
PT_WRITE_GPR = 14 // Write GPR, CR, PSW
|
||||||
|
PT_WRITE_FPR = 15 // Write FPR
|
||||||
|
PT_WRITE_VR = 16 // Write VR
|
||||||
|
PT_READ_BLOCK = 17 // Read storage
|
||||||
|
PT_WRITE_BLOCK = 19 // Write storage
|
||||||
|
PT_READ_GPRH = 20 // Read GPRH
|
||||||
|
PT_WRITE_GPRH = 21 // Write GPRH
|
||||||
|
PT_REGHSET = 22 // Read all GPRHs
|
||||||
|
PT_ATTACH = 30 // Attach to a process
|
||||||
|
PT_DETACH = 31 // Detach from a process
|
||||||
|
PT_REGSET = 32 // Read all GPRs
|
||||||
|
PT_REATTACH = 33 // Reattach to a process
|
||||||
|
PT_LDINFO = 34 // Read loader info
|
||||||
|
PT_MULTI = 35 // Multi process mode
|
||||||
|
PT_LD64INFO = 36 // RMODE64 Info Area
|
||||||
|
PT_BLOCKREQ = 40 // Block request
|
||||||
|
PT_THREAD_INFO = 60 // Read thread info
|
||||||
|
PT_THREAD_MODIFY = 61
|
||||||
|
PT_THREAD_READ_FOCUS = 62
|
||||||
|
PT_THREAD_WRITE_FOCUS = 63
|
||||||
|
PT_THREAD_HOLD = 64
|
||||||
|
PT_THREAD_SIGNAL = 65
|
||||||
|
PT_EXPLAIN = 66
|
||||||
|
PT_EVENTS = 67
|
||||||
|
PT_THREAD_INFO_EXTENDED = 68
|
||||||
|
PT_REATTACH2 = 71
|
||||||
|
PT_CAPTURE = 72
|
||||||
|
PT_UNCAPTURE = 73
|
||||||
|
PT_GET_THREAD_TCB = 74
|
||||||
|
PT_GET_ALET = 75
|
||||||
|
PT_SWAPIN = 76
|
||||||
|
PT_EXTENDED_EVENT = 98
|
||||||
|
PT_RECOVER = 99 // Debug a program check
|
||||||
|
PT_GPR0 = 0 // General purpose register 0
|
||||||
|
PT_GPR1 = 1 // General purpose register 1
|
||||||
|
PT_GPR2 = 2 // General purpose register 2
|
||||||
|
PT_GPR3 = 3 // General purpose register 3
|
||||||
|
PT_GPR4 = 4 // General purpose register 4
|
||||||
|
PT_GPR5 = 5 // General purpose register 5
|
||||||
|
PT_GPR6 = 6 // General purpose register 6
|
||||||
|
PT_GPR7 = 7 // General purpose register 7
|
||||||
|
PT_GPR8 = 8 // General purpose register 8
|
||||||
|
PT_GPR9 = 9 // General purpose register 9
|
||||||
|
PT_GPR10 = 10 // General purpose register 10
|
||||||
|
PT_GPR11 = 11 // General purpose register 11
|
||||||
|
PT_GPR12 = 12 // General purpose register 12
|
||||||
|
PT_GPR13 = 13 // General purpose register 13
|
||||||
|
PT_GPR14 = 14 // General purpose register 14
|
||||||
|
PT_GPR15 = 15 // General purpose register 15
|
||||||
|
PT_FPR0 = 16 // Floating point register 0
|
||||||
|
PT_FPR1 = 17 // Floating point register 1
|
||||||
|
PT_FPR2 = 18 // Floating point register 2
|
||||||
|
PT_FPR3 = 19 // Floating point register 3
|
||||||
|
PT_FPR4 = 20 // Floating point register 4
|
||||||
|
PT_FPR5 = 21 // Floating point register 5
|
||||||
|
PT_FPR6 = 22 // Floating point register 6
|
||||||
|
PT_FPR7 = 23 // Floating point register 7
|
||||||
|
PT_FPR8 = 24 // Floating point register 8
|
||||||
|
PT_FPR9 = 25 // Floating point register 9
|
||||||
|
PT_FPR10 = 26 // Floating point register 10
|
||||||
|
PT_FPR11 = 27 // Floating point register 11
|
||||||
|
PT_FPR12 = 28 // Floating point register 12
|
||||||
|
PT_FPR13 = 29 // Floating point register 13
|
||||||
|
PT_FPR14 = 30 // Floating point register 14
|
||||||
|
PT_FPR15 = 31 // Floating point register 15
|
||||||
|
PT_FPC = 32 // Floating point control register
|
||||||
|
PT_PSW = 40 // PSW
|
||||||
|
PT_PSW0 = 40 // Left half of the PSW
|
||||||
|
PT_PSW1 = 41 // Right half of the PSW
|
||||||
|
PT_CR0 = 42 // Control register 0
|
||||||
|
PT_CR1 = 43 // Control register 1
|
||||||
|
PT_CR2 = 44 // Control register 2
|
||||||
|
PT_CR3 = 45 // Control register 3
|
||||||
|
PT_CR4 = 46 // Control register 4
|
||||||
|
PT_CR5 = 47 // Control register 5
|
||||||
|
PT_CR6 = 48 // Control register 6
|
||||||
|
PT_CR7 = 49 // Control register 7
|
||||||
|
PT_CR8 = 50 // Control register 8
|
||||||
|
PT_CR9 = 51 // Control register 9
|
||||||
|
PT_CR10 = 52 // Control register 10
|
||||||
|
PT_CR11 = 53 // Control register 11
|
||||||
|
PT_CR12 = 54 // Control register 12
|
||||||
|
PT_CR13 = 55 // Control register 13
|
||||||
|
PT_CR14 = 56 // Control register 14
|
||||||
|
PT_CR15 = 57 // Control register 15
|
||||||
|
PT_GPRH0 = 58 // GP High register 0
|
||||||
|
PT_GPRH1 = 59 // GP High register 1
|
||||||
|
PT_GPRH2 = 60 // GP High register 2
|
||||||
|
PT_GPRH3 = 61 // GP High register 3
|
||||||
|
PT_GPRH4 = 62 // GP High register 4
|
||||||
|
PT_GPRH5 = 63 // GP High register 5
|
||||||
|
PT_GPRH6 = 64 // GP High register 6
|
||||||
|
PT_GPRH7 = 65 // GP High register 7
|
||||||
|
PT_GPRH8 = 66 // GP High register 8
|
||||||
|
PT_GPRH9 = 67 // GP High register 9
|
||||||
|
PT_GPRH10 = 68 // GP High register 10
|
||||||
|
PT_GPRH11 = 69 // GP High register 11
|
||||||
|
PT_GPRH12 = 70 // GP High register 12
|
||||||
|
PT_GPRH13 = 71 // GP High register 13
|
||||||
|
PT_GPRH14 = 72 // GP High register 14
|
||||||
|
PT_GPRH15 = 73 // GP High register 15
|
||||||
|
PT_VR0 = 74 // Vector register 0
|
||||||
|
PT_VR1 = 75 // Vector register 1
|
||||||
|
PT_VR2 = 76 // Vector register 2
|
||||||
|
PT_VR3 = 77 // Vector register 3
|
||||||
|
PT_VR4 = 78 // Vector register 4
|
||||||
|
PT_VR5 = 79 // Vector register 5
|
||||||
|
PT_VR6 = 80 // Vector register 6
|
||||||
|
PT_VR7 = 81 // Vector register 7
|
||||||
|
PT_VR8 = 82 // Vector register 8
|
||||||
|
PT_VR9 = 83 // Vector register 9
|
||||||
|
PT_VR10 = 84 // Vector register 10
|
||||||
|
PT_VR11 = 85 // Vector register 11
|
||||||
|
PT_VR12 = 86 // Vector register 12
|
||||||
|
PT_VR13 = 87 // Vector register 13
|
||||||
|
PT_VR14 = 88 // Vector register 14
|
||||||
|
PT_VR15 = 89 // Vector register 15
|
||||||
|
PT_VR16 = 90 // Vector register 16
|
||||||
|
PT_VR17 = 91 // Vector register 17
|
||||||
|
PT_VR18 = 92 // Vector register 18
|
||||||
|
PT_VR19 = 93 // Vector register 19
|
||||||
|
PT_VR20 = 94 // Vector register 20
|
||||||
|
PT_VR21 = 95 // Vector register 21
|
||||||
|
PT_VR22 = 96 // Vector register 22
|
||||||
|
PT_VR23 = 97 // Vector register 23
|
||||||
|
PT_VR24 = 98 // Vector register 24
|
||||||
|
PT_VR25 = 99 // Vector register 25
|
||||||
|
PT_VR26 = 100 // Vector register 26
|
||||||
|
PT_VR27 = 101 // Vector register 27
|
||||||
|
PT_VR28 = 102 // Vector register 28
|
||||||
|
PT_VR29 = 103 // Vector register 29
|
||||||
|
PT_VR30 = 104 // Vector register 30
|
||||||
|
PT_VR31 = 105 // Vector register 31
|
||||||
|
PT_PSWG = 106 // PSWG
|
||||||
|
PT_PSWG0 = 106 // Bytes 0-3
|
||||||
|
PT_PSWG1 = 107 // Bytes 4-7
|
||||||
|
PT_PSWG2 = 108 // Bytes 8-11 (IA high word)
|
||||||
|
PT_PSWG3 = 109 // Bytes 12-15 (IA low word)
|
||||||
|
)
|
||||||
|
|
||||||
|
func Bpx4ptr(request int32, pid int32, addr unsafe.Pointer, data unsafe.Pointer, buffer unsafe.Pointer) (rv int32, rc int32, rn int32) {
|
||||||
|
var parms [8]unsafe.Pointer
|
||||||
|
parms[0] = unsafe.Pointer(&request)
|
||||||
|
parms[1] = unsafe.Pointer(&pid)
|
||||||
|
parms[2] = unsafe.Pointer(&addr)
|
||||||
|
parms[3] = unsafe.Pointer(&data)
|
||||||
|
parms[4] = unsafe.Pointer(&buffer)
|
||||||
|
parms[5] = unsafe.Pointer(&rv)
|
||||||
|
parms[6] = unsafe.Pointer(&rc)
|
||||||
|
parms[7] = unsafe.Pointer(&rn)
|
||||||
|
bpxcall(parms[:], BPX4PTR)
|
||||||
|
return rv, rc, rn
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyU8(val uint8, dest []uint8) int {
|
||||||
|
if len(dest) < 1 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
dest[0] = val
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyU8Arr(src, dest []uint8) int {
|
||||||
|
if len(dest) < len(src) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
for i, v := range src {
|
||||||
|
dest[i] = v
|
||||||
|
}
|
||||||
|
return len(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyU16(val uint16, dest []uint16) int {
|
||||||
|
if len(dest) < 1 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
dest[0] = val
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyU32(val uint32, dest []uint32) int {
|
||||||
|
if len(dest) < 1 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
dest[0] = val
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyU32Arr(src, dest []uint32) int {
|
||||||
|
if len(dest) < len(src) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
for i, v := range src {
|
||||||
|
dest[i] = v
|
||||||
|
}
|
||||||
|
return len(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyU64(val uint64, dest []uint64) int {
|
||||||
|
if len(dest) < 1 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
dest[0] = val
|
||||||
|
return 1
|
||||||
|
}
|
||||||
192
vendor/golang.org/x/sys/unix/bpxsvc_zos.s
generated
vendored
Normal file
192
vendor/golang.org/x/sys/unix/bpxsvc_zos.s
generated
vendored
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
// Copyright 2024 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "go_asm.h"
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// function to call USS assembly language services
|
||||||
|
//
|
||||||
|
// doc: https://www.ibm.com/support/knowledgecenter/en/SSLTBW_3.1.0/com.ibm.zos.v3r1.bpxb100/bit64env.htm
|
||||||
|
//
|
||||||
|
// arg1 unsafe.Pointer array that ressembles an OS PLIST
|
||||||
|
//
|
||||||
|
// arg2 function offset as in
|
||||||
|
// doc: https://www.ibm.com/support/knowledgecenter/en/SSLTBW_3.1.0/com.ibm.zos.v3r1.bpxb100/bpx2cr_List_of_offsets.htm
|
||||||
|
//
|
||||||
|
// func bpxcall(plist []unsafe.Pointer, bpx_offset int64)
|
||||||
|
|
||||||
|
TEXT ·bpxcall(SB), NOSPLIT|NOFRAME, $0
|
||||||
|
MOVD plist_base+0(FP), R1 // r1 points to plist
|
||||||
|
MOVD bpx_offset+24(FP), R2 // r2 offset to BPX vector table
|
||||||
|
MOVD R14, R7 // save r14
|
||||||
|
MOVD R15, R8 // save r15
|
||||||
|
MOVWZ 16(R0), R9
|
||||||
|
MOVWZ 544(R9), R9
|
||||||
|
MOVWZ 24(R9), R9 // call vector in r9
|
||||||
|
ADD R2, R9 // add offset to vector table
|
||||||
|
MOVWZ (R9), R9 // r9 points to entry point
|
||||||
|
BYTE $0x0D // BL R14,R9 --> basr r14,r9
|
||||||
|
BYTE $0xE9 // clobbers 0,1,14,15
|
||||||
|
MOVD R8, R15 // restore 15
|
||||||
|
JMP R7 // return via saved return address
|
||||||
|
|
||||||
|
// func A2e(arr [] byte)
|
||||||
|
// code page conversion from 819 to 1047
|
||||||
|
TEXT ·A2e(SB), NOSPLIT|NOFRAME, $0
|
||||||
|
MOVD arg_base+0(FP), R2 // pointer to arry of characters
|
||||||
|
MOVD arg_len+8(FP), R3 // count
|
||||||
|
XOR R0, R0
|
||||||
|
XOR R1, R1
|
||||||
|
BYTE $0xA7; BYTE $0x15; BYTE $0x00; BYTE $0x82 // BRAS 1,(2+(256/2))
|
||||||
|
|
||||||
|
// ASCII -> EBCDIC conversion table:
|
||||||
|
BYTE $0x00; BYTE $0x01; BYTE $0x02; BYTE $0x03
|
||||||
|
BYTE $0x37; BYTE $0x2d; BYTE $0x2e; BYTE $0x2f
|
||||||
|
BYTE $0x16; BYTE $0x05; BYTE $0x15; BYTE $0x0b
|
||||||
|
BYTE $0x0c; BYTE $0x0d; BYTE $0x0e; BYTE $0x0f
|
||||||
|
BYTE $0x10; BYTE $0x11; BYTE $0x12; BYTE $0x13
|
||||||
|
BYTE $0x3c; BYTE $0x3d; BYTE $0x32; BYTE $0x26
|
||||||
|
BYTE $0x18; BYTE $0x19; BYTE $0x3f; BYTE $0x27
|
||||||
|
BYTE $0x1c; BYTE $0x1d; BYTE $0x1e; BYTE $0x1f
|
||||||
|
BYTE $0x40; BYTE $0x5a; BYTE $0x7f; BYTE $0x7b
|
||||||
|
BYTE $0x5b; BYTE $0x6c; BYTE $0x50; BYTE $0x7d
|
||||||
|
BYTE $0x4d; BYTE $0x5d; BYTE $0x5c; BYTE $0x4e
|
||||||
|
BYTE $0x6b; BYTE $0x60; BYTE $0x4b; BYTE $0x61
|
||||||
|
BYTE $0xf0; BYTE $0xf1; BYTE $0xf2; BYTE $0xf3
|
||||||
|
BYTE $0xf4; BYTE $0xf5; BYTE $0xf6; BYTE $0xf7
|
||||||
|
BYTE $0xf8; BYTE $0xf9; BYTE $0x7a; BYTE $0x5e
|
||||||
|
BYTE $0x4c; BYTE $0x7e; BYTE $0x6e; BYTE $0x6f
|
||||||
|
BYTE $0x7c; BYTE $0xc1; BYTE $0xc2; BYTE $0xc3
|
||||||
|
BYTE $0xc4; BYTE $0xc5; BYTE $0xc6; BYTE $0xc7
|
||||||
|
BYTE $0xc8; BYTE $0xc9; BYTE $0xd1; BYTE $0xd2
|
||||||
|
BYTE $0xd3; BYTE $0xd4; BYTE $0xd5; BYTE $0xd6
|
||||||
|
BYTE $0xd7; BYTE $0xd8; BYTE $0xd9; BYTE $0xe2
|
||||||
|
BYTE $0xe3; BYTE $0xe4; BYTE $0xe5; BYTE $0xe6
|
||||||
|
BYTE $0xe7; BYTE $0xe8; BYTE $0xe9; BYTE $0xad
|
||||||
|
BYTE $0xe0; BYTE $0xbd; BYTE $0x5f; BYTE $0x6d
|
||||||
|
BYTE $0x79; BYTE $0x81; BYTE $0x82; BYTE $0x83
|
||||||
|
BYTE $0x84; BYTE $0x85; BYTE $0x86; BYTE $0x87
|
||||||
|
BYTE $0x88; BYTE $0x89; BYTE $0x91; BYTE $0x92
|
||||||
|
BYTE $0x93; BYTE $0x94; BYTE $0x95; BYTE $0x96
|
||||||
|
BYTE $0x97; BYTE $0x98; BYTE $0x99; BYTE $0xa2
|
||||||
|
BYTE $0xa3; BYTE $0xa4; BYTE $0xa5; BYTE $0xa6
|
||||||
|
BYTE $0xa7; BYTE $0xa8; BYTE $0xa9; BYTE $0xc0
|
||||||
|
BYTE $0x4f; BYTE $0xd0; BYTE $0xa1; BYTE $0x07
|
||||||
|
BYTE $0x20; BYTE $0x21; BYTE $0x22; BYTE $0x23
|
||||||
|
BYTE $0x24; BYTE $0x25; BYTE $0x06; BYTE $0x17
|
||||||
|
BYTE $0x28; BYTE $0x29; BYTE $0x2a; BYTE $0x2b
|
||||||
|
BYTE $0x2c; BYTE $0x09; BYTE $0x0a; BYTE $0x1b
|
||||||
|
BYTE $0x30; BYTE $0x31; BYTE $0x1a; BYTE $0x33
|
||||||
|
BYTE $0x34; BYTE $0x35; BYTE $0x36; BYTE $0x08
|
||||||
|
BYTE $0x38; BYTE $0x39; BYTE $0x3a; BYTE $0x3b
|
||||||
|
BYTE $0x04; BYTE $0x14; BYTE $0x3e; BYTE $0xff
|
||||||
|
BYTE $0x41; BYTE $0xaa; BYTE $0x4a; BYTE $0xb1
|
||||||
|
BYTE $0x9f; BYTE $0xb2; BYTE $0x6a; BYTE $0xb5
|
||||||
|
BYTE $0xbb; BYTE $0xb4; BYTE $0x9a; BYTE $0x8a
|
||||||
|
BYTE $0xb0; BYTE $0xca; BYTE $0xaf; BYTE $0xbc
|
||||||
|
BYTE $0x90; BYTE $0x8f; BYTE $0xea; BYTE $0xfa
|
||||||
|
BYTE $0xbe; BYTE $0xa0; BYTE $0xb6; BYTE $0xb3
|
||||||
|
BYTE $0x9d; BYTE $0xda; BYTE $0x9b; BYTE $0x8b
|
||||||
|
BYTE $0xb7; BYTE $0xb8; BYTE $0xb9; BYTE $0xab
|
||||||
|
BYTE $0x64; BYTE $0x65; BYTE $0x62; BYTE $0x66
|
||||||
|
BYTE $0x63; BYTE $0x67; BYTE $0x9e; BYTE $0x68
|
||||||
|
BYTE $0x74; BYTE $0x71; BYTE $0x72; BYTE $0x73
|
||||||
|
BYTE $0x78; BYTE $0x75; BYTE $0x76; BYTE $0x77
|
||||||
|
BYTE $0xac; BYTE $0x69; BYTE $0xed; BYTE $0xee
|
||||||
|
BYTE $0xeb; BYTE $0xef; BYTE $0xec; BYTE $0xbf
|
||||||
|
BYTE $0x80; BYTE $0xfd; BYTE $0xfe; BYTE $0xfb
|
||||||
|
BYTE $0xfc; BYTE $0xba; BYTE $0xae; BYTE $0x59
|
||||||
|
BYTE $0x44; BYTE $0x45; BYTE $0x42; BYTE $0x46
|
||||||
|
BYTE $0x43; BYTE $0x47; BYTE $0x9c; BYTE $0x48
|
||||||
|
BYTE $0x54; BYTE $0x51; BYTE $0x52; BYTE $0x53
|
||||||
|
BYTE $0x58; BYTE $0x55; BYTE $0x56; BYTE $0x57
|
||||||
|
BYTE $0x8c; BYTE $0x49; BYTE $0xcd; BYTE $0xce
|
||||||
|
BYTE $0xcb; BYTE $0xcf; BYTE $0xcc; BYTE $0xe1
|
||||||
|
BYTE $0x70; BYTE $0xdd; BYTE $0xde; BYTE $0xdb
|
||||||
|
BYTE $0xdc; BYTE $0x8d; BYTE $0x8e; BYTE $0xdf
|
||||||
|
|
||||||
|
retry:
|
||||||
|
WORD $0xB9931022 // TROO 2,2,b'0001'
|
||||||
|
BVS retry
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func e2a(arr [] byte)
|
||||||
|
// code page conversion from 1047 to 819
|
||||||
|
TEXT ·E2a(SB), NOSPLIT|NOFRAME, $0
|
||||||
|
MOVD arg_base+0(FP), R2 // pointer to arry of characters
|
||||||
|
MOVD arg_len+8(FP), R3 // count
|
||||||
|
XOR R0, R0
|
||||||
|
XOR R1, R1
|
||||||
|
BYTE $0xA7; BYTE $0x15; BYTE $0x00; BYTE $0x82 // BRAS 1,(2+(256/2))
|
||||||
|
|
||||||
|
// EBCDIC -> ASCII conversion table:
|
||||||
|
BYTE $0x00; BYTE $0x01; BYTE $0x02; BYTE $0x03
|
||||||
|
BYTE $0x9c; BYTE $0x09; BYTE $0x86; BYTE $0x7f
|
||||||
|
BYTE $0x97; BYTE $0x8d; BYTE $0x8e; BYTE $0x0b
|
||||||
|
BYTE $0x0c; BYTE $0x0d; BYTE $0x0e; BYTE $0x0f
|
||||||
|
BYTE $0x10; BYTE $0x11; BYTE $0x12; BYTE $0x13
|
||||||
|
BYTE $0x9d; BYTE $0x0a; BYTE $0x08; BYTE $0x87
|
||||||
|
BYTE $0x18; BYTE $0x19; BYTE $0x92; BYTE $0x8f
|
||||||
|
BYTE $0x1c; BYTE $0x1d; BYTE $0x1e; BYTE $0x1f
|
||||||
|
BYTE $0x80; BYTE $0x81; BYTE $0x82; BYTE $0x83
|
||||||
|
BYTE $0x84; BYTE $0x85; BYTE $0x17; BYTE $0x1b
|
||||||
|
BYTE $0x88; BYTE $0x89; BYTE $0x8a; BYTE $0x8b
|
||||||
|
BYTE $0x8c; BYTE $0x05; BYTE $0x06; BYTE $0x07
|
||||||
|
BYTE $0x90; BYTE $0x91; BYTE $0x16; BYTE $0x93
|
||||||
|
BYTE $0x94; BYTE $0x95; BYTE $0x96; BYTE $0x04
|
||||||
|
BYTE $0x98; BYTE $0x99; BYTE $0x9a; BYTE $0x9b
|
||||||
|
BYTE $0x14; BYTE $0x15; BYTE $0x9e; BYTE $0x1a
|
||||||
|
BYTE $0x20; BYTE $0xa0; BYTE $0xe2; BYTE $0xe4
|
||||||
|
BYTE $0xe0; BYTE $0xe1; BYTE $0xe3; BYTE $0xe5
|
||||||
|
BYTE $0xe7; BYTE $0xf1; BYTE $0xa2; BYTE $0x2e
|
||||||
|
BYTE $0x3c; BYTE $0x28; BYTE $0x2b; BYTE $0x7c
|
||||||
|
BYTE $0x26; BYTE $0xe9; BYTE $0xea; BYTE $0xeb
|
||||||
|
BYTE $0xe8; BYTE $0xed; BYTE $0xee; BYTE $0xef
|
||||||
|
BYTE $0xec; BYTE $0xdf; BYTE $0x21; BYTE $0x24
|
||||||
|
BYTE $0x2a; BYTE $0x29; BYTE $0x3b; BYTE $0x5e
|
||||||
|
BYTE $0x2d; BYTE $0x2f; BYTE $0xc2; BYTE $0xc4
|
||||||
|
BYTE $0xc0; BYTE $0xc1; BYTE $0xc3; BYTE $0xc5
|
||||||
|
BYTE $0xc7; BYTE $0xd1; BYTE $0xa6; BYTE $0x2c
|
||||||
|
BYTE $0x25; BYTE $0x5f; BYTE $0x3e; BYTE $0x3f
|
||||||
|
BYTE $0xf8; BYTE $0xc9; BYTE $0xca; BYTE $0xcb
|
||||||
|
BYTE $0xc8; BYTE $0xcd; BYTE $0xce; BYTE $0xcf
|
||||||
|
BYTE $0xcc; BYTE $0x60; BYTE $0x3a; BYTE $0x23
|
||||||
|
BYTE $0x40; BYTE $0x27; BYTE $0x3d; BYTE $0x22
|
||||||
|
BYTE $0xd8; BYTE $0x61; BYTE $0x62; BYTE $0x63
|
||||||
|
BYTE $0x64; BYTE $0x65; BYTE $0x66; BYTE $0x67
|
||||||
|
BYTE $0x68; BYTE $0x69; BYTE $0xab; BYTE $0xbb
|
||||||
|
BYTE $0xf0; BYTE $0xfd; BYTE $0xfe; BYTE $0xb1
|
||||||
|
BYTE $0xb0; BYTE $0x6a; BYTE $0x6b; BYTE $0x6c
|
||||||
|
BYTE $0x6d; BYTE $0x6e; BYTE $0x6f; BYTE $0x70
|
||||||
|
BYTE $0x71; BYTE $0x72; BYTE $0xaa; BYTE $0xba
|
||||||
|
BYTE $0xe6; BYTE $0xb8; BYTE $0xc6; BYTE $0xa4
|
||||||
|
BYTE $0xb5; BYTE $0x7e; BYTE $0x73; BYTE $0x74
|
||||||
|
BYTE $0x75; BYTE $0x76; BYTE $0x77; BYTE $0x78
|
||||||
|
BYTE $0x79; BYTE $0x7a; BYTE $0xa1; BYTE $0xbf
|
||||||
|
BYTE $0xd0; BYTE $0x5b; BYTE $0xde; BYTE $0xae
|
||||||
|
BYTE $0xac; BYTE $0xa3; BYTE $0xa5; BYTE $0xb7
|
||||||
|
BYTE $0xa9; BYTE $0xa7; BYTE $0xb6; BYTE $0xbc
|
||||||
|
BYTE $0xbd; BYTE $0xbe; BYTE $0xdd; BYTE $0xa8
|
||||||
|
BYTE $0xaf; BYTE $0x5d; BYTE $0xb4; BYTE $0xd7
|
||||||
|
BYTE $0x7b; BYTE $0x41; BYTE $0x42; BYTE $0x43
|
||||||
|
BYTE $0x44; BYTE $0x45; BYTE $0x46; BYTE $0x47
|
||||||
|
BYTE $0x48; BYTE $0x49; BYTE $0xad; BYTE $0xf4
|
||||||
|
BYTE $0xf6; BYTE $0xf2; BYTE $0xf3; BYTE $0xf5
|
||||||
|
BYTE $0x7d; BYTE $0x4a; BYTE $0x4b; BYTE $0x4c
|
||||||
|
BYTE $0x4d; BYTE $0x4e; BYTE $0x4f; BYTE $0x50
|
||||||
|
BYTE $0x51; BYTE $0x52; BYTE $0xb9; BYTE $0xfb
|
||||||
|
BYTE $0xfc; BYTE $0xf9; BYTE $0xfa; BYTE $0xff
|
||||||
|
BYTE $0x5c; BYTE $0xf7; BYTE $0x53; BYTE $0x54
|
||||||
|
BYTE $0x55; BYTE $0x56; BYTE $0x57; BYTE $0x58
|
||||||
|
BYTE $0x59; BYTE $0x5a; BYTE $0xb2; BYTE $0xd4
|
||||||
|
BYTE $0xd6; BYTE $0xd2; BYTE $0xd3; BYTE $0xd5
|
||||||
|
BYTE $0x30; BYTE $0x31; BYTE $0x32; BYTE $0x33
|
||||||
|
BYTE $0x34; BYTE $0x35; BYTE $0x36; BYTE $0x37
|
||||||
|
BYTE $0x38; BYTE $0x39; BYTE $0xb3; BYTE $0xdb
|
||||||
|
BYTE $0xdc; BYTE $0xd9; BYTE $0xda; BYTE $0x9f
|
||||||
|
|
||||||
|
retry:
|
||||||
|
WORD $0xB9931022 // TROO 2,2,b'0001'
|
||||||
|
BVS retry
|
||||||
|
RET
|
||||||
1
vendor/golang.org/x/sys/unix/cap_freebsd.go
generated
vendored
1
vendor/golang.org/x/sys/unix/cap_freebsd.go
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build freebsd
|
//go:build freebsd
|
||||||
// +build freebsd
|
|
||||||
|
|
||||||
package unix
|
package unix
|
||||||
|
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/constants.go
generated
vendored
1
vendor/golang.org/x/sys/unix/constants.go
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
|
|
||||||
|
|
||||||
package unix
|
package unix
|
||||||
|
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/dev_aix_ppc.go
generated
vendored
1
vendor/golang.org/x/sys/unix/dev_aix_ppc.go
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build aix && ppc
|
//go:build aix && ppc
|
||||||
// +build aix,ppc
|
|
||||||
|
|
||||||
// Functions to access/create device major and minor numbers matching the
|
// Functions to access/create device major and minor numbers matching the
|
||||||
// encoding used by AIX.
|
// encoding used by AIX.
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/dev_aix_ppc64.go
generated
vendored
1
vendor/golang.org/x/sys/unix/dev_aix_ppc64.go
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build aix && ppc64
|
//go:build aix && ppc64
|
||||||
// +build aix,ppc64
|
|
||||||
|
|
||||||
// Functions to access/create device major and minor numbers matching the
|
// Functions to access/create device major and minor numbers matching the
|
||||||
// encoding used AIX.
|
// encoding used AIX.
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/dev_zos.go
generated
vendored
1
vendor/golang.org/x/sys/unix/dev_zos.go
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build zos && s390x
|
//go:build zos && s390x
|
||||||
// +build zos,s390x
|
|
||||||
|
|
||||||
// Functions to access/create device major and minor numbers matching the
|
// Functions to access/create device major and minor numbers matching the
|
||||||
// encoding used by z/OS.
|
// encoding used by z/OS.
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/dirent.go
generated
vendored
1
vendor/golang.org/x/sys/unix/dirent.go
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
|
|
||||||
|
|
||||||
package unix
|
package unix
|
||||||
|
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/endian_big.go
generated
vendored
1
vendor/golang.org/x/sys/unix/endian_big.go
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
//go:build armbe || arm64be || m68k || mips || mips64 || mips64p32 || ppc || ppc64 || s390 || s390x || shbe || sparc || sparc64
|
//go:build armbe || arm64be || m68k || mips || mips64 || mips64p32 || ppc || ppc64 || s390 || s390x || shbe || sparc || sparc64
|
||||||
// +build armbe arm64be m68k mips mips64 mips64p32 ppc ppc64 s390 s390x shbe sparc sparc64
|
|
||||||
|
|
||||||
package unix
|
package unix
|
||||||
|
|
||||||
|
|||||||
1
vendor/golang.org/x/sys/unix/endian_little.go
generated
vendored
1
vendor/golang.org/x/sys/unix/endian_little.go
generated
vendored
@@ -3,7 +3,6 @@
|
|||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
//go:build 386 || amd64 || amd64p32 || alpha || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || nios2 || ppc64le || riscv || riscv64 || sh
|
//go:build 386 || amd64 || amd64p32 || alpha || arm || arm64 || loong64 || mipsle || mips64le || mips64p32le || nios2 || ppc64le || riscv || riscv64 || sh
|
||||||
// +build 386 amd64 amd64p32 alpha arm arm64 loong64 mipsle mips64le mips64p32le nios2 ppc64le riscv riscv64 sh
|
|
||||||
|
|
||||||
package unix
|
package unix
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user