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
|
||||
**/*.bak
|
||||
**/*.tmp
|
||||
data/config.json
|
||||
data/whitelist.json
|
||||
data/blacklist.json
|
||||
buny-jabber-bot
|
||||
buny-jabber-bot.exe
|
||||
/data/config.json
|
||||
/data/whitelist.json
|
||||
/data/blacklist.json
|
||||
/buny-jabber-bot
|
||||
/buny-jabber-bot.exe
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
run:
|
||||
tests: false
|
||||
skip-dirs:
|
||||
- data
|
||||
- vendor
|
||||
modules-download-mode: readonly
|
||||
allow-parallel-runners: true
|
||||
go: '1.21'
|
||||
go: '1.24'
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- data
|
||||
- vendor
|
||||
output:
|
||||
sort-results: true
|
||||
linters-settings:
|
||||
@@ -34,13 +35,12 @@ linters:
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- exhaustruct
|
||||
- exportloopref
|
||||
- copyloopvar
|
||||
- ginkgolinter
|
||||
- gci
|
||||
- gocheckcompilerdirectives
|
||||
- gocritic
|
||||
- godot
|
||||
- goerr113
|
||||
- err113
|
||||
- gofmt
|
||||
- goimports
|
||||
- gosec
|
||||
|
||||
18
Design.txt
18
Design.txt
@@ -1,12 +1,5 @@
|
||||
Oсобенности реализации клиентской части.
|
||||
|
||||
Общие положения
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
Nick для room-ов един и не может задаваться на per-conference основе. На этом завязаны отсылка ping-ов и определение
|
||||
pong-ов, как минимум. Хотя формально джойниться можно с любым ником :) но что-то не хочется потом распутывать этот
|
||||
клубок.
|
||||
|
||||
|
||||
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
|
||||
|
||||
BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -pgo=auto
|
||||
FILELIST=collection.go types.go globals.go lib.go event_parser.go buny.go commands.go main.go norm_string.go bf.go
|
||||
|
||||
BINARY=buny-jabber-bot
|
||||
BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath
|
||||
MYNAME=buny-jabber-bot
|
||||
BINARY=$(MYNAME)
|
||||
TEST1=xml-software-version-query
|
||||
|
||||
# На windows имя бинарника может зависеть не только от платформы, но и от выбранной цели, для linux-а суффикс .exe
|
||||
# не нужен
|
||||
@@ -33,21 +33,21 @@ ifeq ($(OS),Windows_NT)
|
||||
# вариант с powershell на windows
|
||||
ifeq ($(SHELL),sh.exe)
|
||||
SET CGO_ENABLED=0
|
||||
go build ${BUILDOPTS} -o ${BINARY} ${FILELIST}
|
||||
go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME}
|
||||
else
|
||||
# вариант с jetbrains golang на windows
|
||||
CGO_ENABLED=0
|
||||
go build ${BUILDOPTS} -o ${BINARY} ${FILELIST}
|
||||
go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME}
|
||||
endif
|
||||
# вариант с bash/git (windows) и bash (linux)
|
||||
else
|
||||
CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ${FILELIST}
|
||||
CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME}
|
||||
endif
|
||||
|
||||
|
||||
## Удаляем бинарник средствами go
|
||||
clean:
|
||||
go clean
|
||||
$(RM) ${BINARY}
|
||||
|
||||
|
||||
## Служебный таргет, для целей разработки. Обновляет завендоренные либы, брутальным образом.
|
||||
@@ -68,4 +68,21 @@ endif
|
||||
go mod tidy
|
||||
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:
|
||||
|
||||
35
README.md
35
README.md
@@ -1,20 +1,31 @@
|
||||
# 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 не работает.
|
||||
|
||||
Имеет возможность заносить пользователей в бан-лист, согласно заданным в чёрном списке правилам: по совпадению с
|
||||
регулярными выражениями в nick-е или jid-е злодея, а также по регулярным выражениям характерных фраз. Правила можно
|
||||
настроить как глобально, для всех комнат, где присутствует бот, так и для каждой комнаты отдельно. При изменении списка
|
||||
правил бота не надо перезапускать, достаточно отдать ему команду rehash либо в приват, либо прям в чатике.
|
||||
без поддержки механизма start tls. (Что-то в гошке или на стороне сервера при выборе StartTLS не работает.)
|
||||
* Имеет возможность заносить пользователей в бан-лист, согласно заданным в чёрном списке правилам:
|
||||
- По совпадению с регулярными выражениями в nick-е или jid-е злодея
|
||||
- По регулярным выражениям характерных фраз.
|
||||
- По названию и/или версии и, если есть, ос злодея.
|
||||
- Правила можно настроить как глобально, для всех комнат, где присутствует бот, так и для каждой комнаты отдельно.
|
||||
- При изменении списка правил бота не надо перезапускать, достаточно отдать ему команду rehash либо в приват, либо
|
||||
прям в чатике.
|
||||
* Есть настройка заходить в разные комнаты под разными никами.
|
||||
|
||||
## Что он не может?
|
||||
|
||||
@@ -46,7 +57,7 @@ make
|
||||
|
||||
Почему нельзя оставить комнату анонимной? А тут всё просто, в банлист заносится реальный 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
|
||||
chown -R nobody:nogroup /run/$SVCNAME
|
||||
}
|
||||
|
||||
|
||||
# vim: set ft=sh noet ai ts=4 sw=4 sts=4:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"room_name": "",
|
||||
|
||||
# Записывать ли в reason в банлисте комнаты сообщение вида autoban at yyyy.MM.dd hh:mm:ss.
|
||||
# Если не задано, то false
|
||||
# Если не задано, то false.
|
||||
"reason_enable": false,
|
||||
|
||||
# Список регулярок JID-ов, которых надо банить.
|
||||
@@ -25,6 +25,14 @@
|
||||
# Список регулярок, запрещённых к упоминанию в чяте.
|
||||
"phrase_re": [
|
||||
"^Exterminate.$"
|
||||
],
|
||||
|
||||
"user_agent": [
|
||||
{
|
||||
"software": "BadUserAgent",
|
||||
"version": "0.666.0",
|
||||
"os": "DeathStarOS"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -71,6 +71,10 @@
|
||||
# Название канала, обязательное поле
|
||||
"name" : "my_channel@conference.jabber.tld",
|
||||
|
||||
# Под каким ником бот зайдёт конкретно на этот канал. Если не задано, будет использован ник, заданный в
|
||||
# общих настройках Jabber, выше по конфигу.
|
||||
"nick" : "sailormoon",
|
||||
|
||||
# Настройки байесовского классификатора. В виду вероятности ложного срабатывания возможно, имеет смысл
|
||||
# воздержаться от использования. Зависит от использования и от словарей.
|
||||
"bayes": {
|
||||
@@ -186,8 +190,9 @@
|
||||
# command sign символ-суффикс команды, например !rehash или !help. По-умолчанию "!".
|
||||
"csign" : "!",
|
||||
|
||||
# Многословность логов. Если не задано, то info. Debug - ДЕЙСТВИТЕЛЬНО вербозный уровень логгирования.
|
||||
# Есть ещё trace :)
|
||||
# Многословность логов. Если не задано, то info.
|
||||
# Есть разные уровни trace, debug, debug, info, warn, error, fatal.
|
||||
# Практически задействованы только trace, debug, info.
|
||||
"loglevel": "debug",
|
||||
|
||||
# Если не задан, логи идут в 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
|
||||
|
||||
go 1.21
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/eleksir/go-xmpp v1.6.0
|
||||
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
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
golang.org/x/crypto v0.38.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/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/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/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
||||
github.com/jbrukh/bayesian v0.0.0-20200318221351-d726b684ca4a h1:gbdjhSslIoRRiSSLCP3kKuLmqAJGmhnPVhIyf6Dbw34=
|
||||
github.com/jbrukh/bayesian v0.0.0-20200318221351-d726b684ca4a/go.mod h1:SELxwZQq/mPnfPCR2mchLmT4TQaPJvYtLcCtDWSM7vM=
|
||||
github.com/jbrukh/bayesian v0.0.0-20231117143245-13ae6f916c7a h1:zSc8tkEGo49/E6cQ9o00si0L9Q/Lu1Hsg8JWKCvzEDU=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
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.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
||||
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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs=
|
||||
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package jabber
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -15,10 +15,12 @@ const (
|
||||
goodDictionaryFile string = "./data/good_dictionary.txt"
|
||||
badDictionaryFile string = "./data/bad_dictionary.txt"
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
@@ -96,3 +98,5 @@ func feed(c *bayesian.Classifier, filename string, bc bayesian.Class) error {
|
||||
|
||||
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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/eleksir/go-xmpp"
|
||||
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
|
||||
|
||||
// Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном
|
||||
// списке, заносим пидора в список outcast-ов
|
||||
if v.JID != "" {
|
||||
// На всякий случай: себя никогда не баним, явным образом
|
||||
if v.JID == talk.JID() {
|
||||
if v.JID == j.Talk.JID() {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
goodJid := strings.SplitN(v.JID, "/", 2)[0]
|
||||
|
||||
// Обрабатываем правила белого списка
|
||||
for _, good := range whiteList.Whitelist {
|
||||
for _, good := range j.WhiteList.Whitelist {
|
||||
// Глобальный белый список
|
||||
if good.RoomName == "" {
|
||||
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]
|
||||
evilNick := ""
|
||||
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 {
|
||||
for _, bEntry := range blackList.Blacklist {
|
||||
for _, bEntry := range j.BlackList.Blacklist {
|
||||
// Обработаем правила глобального чёрного списка
|
||||
if bEntry.RoomName == "" {
|
||||
for _, jidRegexp := range bEntry.JidRe {
|
||||
@@ -83,9 +98,9 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
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(
|
||||
"Hammer falls on %s (%s): jid matches with global blacklist entry: %s",
|
||||
v.From,
|
||||
@@ -93,7 +108,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
jidRegexp,
|
||||
)
|
||||
|
||||
id, err := squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf(
|
||||
@@ -102,7 +117,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -133,7 +148,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
)
|
||||
|
||||
// Баним именно jid
|
||||
id, err := squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf(
|
||||
@@ -142,7 +157,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -167,9 +182,9 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
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(
|
||||
"Hammer falls on %s (%s): jid matches with room blacklist entry: %s",
|
||||
v.From,
|
||||
@@ -177,7 +192,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
jidRegexp,
|
||||
)
|
||||
|
||||
id, err := squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf(
|
||||
@@ -186,7 +201,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -218,7 +233,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
)
|
||||
|
||||
// Баним именно jid
|
||||
id, err := squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf(
|
||||
@@ -227,7 +242,7 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -245,9 +260,9 @@ func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
return err
|
||||
}
|
||||
|
||||
// bunyChat производит проверку сообщений участников чата по списку забаненных фраз и в случае нахождения запрещённого
|
||||
// BunyChat производит проверку сообщений участников чата по списку забаненных фраз и в случае нахождения запрещённого
|
||||
// шаблона банит участника чата.
|
||||
func bunyChat(v xmpp.Chat) error {
|
||||
func (j *Jabber) BunyChat(v xmpp.Chat) error {
|
||||
var (
|
||||
room = (strings.SplitN(v.Remote, "/", 2))[0]
|
||||
// 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 {
|
||||
// Перебирём правила чёрных списков.
|
||||
for _, bEntry := range blackList.Blacklist {
|
||||
for _, bEntry := range j.BlackList.Blacklist {
|
||||
// Обработаем правила глобального чёрного списка
|
||||
if bEntry.RoomName == "" {
|
||||
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)
|
||||
|
||||
if re.MatchString(v.Text) {
|
||||
realJID := getRealJIDfromNick(v.Remote)
|
||||
realJID := j.GetRealJIDfromNick(v.Remote)
|
||||
|
||||
log.Warnf(
|
||||
"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,
|
||||
)
|
||||
|
||||
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(
|
||||
"unable to ban user: id=%s, err=%w",
|
||||
id,
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(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)
|
||||
|
||||
if re.MatchString(v.Text) {
|
||||
realJID := getRealJIDfromNick(v.Remote)
|
||||
realJID := j.GetRealJIDfromNick(v.Remote)
|
||||
|
||||
log.Warnf(
|
||||
"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,
|
||||
)
|
||||
|
||||
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(
|
||||
"unable to ban user: id=%s, err=%w",
|
||||
id,
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(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 {
|
||||
// Нормализуем строку и вырежем из неё пробелы
|
||||
normPhrase := strings.ReplaceAll(nString(v.Text), " ", "")
|
||||
@@ -357,8 +372,8 @@ func bunyChat(v xmpp.Chat) error {
|
||||
normPhraseUpper := strings.ReplaceAll(nStringUpper(v.Text), " ", "")
|
||||
|
||||
if normPhrase == normPhraseUpper {
|
||||
realJID := getRealJIDfromNick(v.Remote)
|
||||
id, err := squash(room, realJID, false, v.Type)
|
||||
realJID := j.GetRealJIDfromNick(v.Remote)
|
||||
id, err := j.Squash(room, realJID, false, v.Type)
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf(
|
||||
@@ -367,7 +382,7 @@ func bunyChat(v xmpp.Chat) error {
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,74 +396,208 @@ func bunyChat(v xmpp.Chat) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// squash банит указанный jid в указанной комнате.
|
||||
// reasonEnable указывает, надо ли писать дату автобана в банлисте в поле reason (это единственная причина, в которую
|
||||
// умеет бот).
|
||||
func squash(room, jid string, reasonEnable bool, vType string) (string, error) {
|
||||
// BunySoftwareVersion производит проверку по чёрному списку версий и названий клинтского ПО.
|
||||
func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) error {
|
||||
var err error
|
||||
|
||||
if v.From != "" {
|
||||
// На всякий случай: себя никогда не баним, явным образом
|
||||
if v.From == j.Talk.JID() {
|
||||
return err
|
||||
}
|
||||
|
||||
room := strings.SplitN(v.From, "/", 2)[0]
|
||||
|
||||
var (
|
||||
id string
|
||||
err error
|
||||
presenceJSONStrings []string
|
||||
)
|
||||
|
||||
if config.Jabber.BanPhrasesEnable {
|
||||
phrase := randomPhrase(config.Jabber.BanPhrases)
|
||||
|
||||
if _, err = 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
|
||||
}
|
||||
if presenceJSONInterface, present := j.RoomPresences.Get(room); present {
|
||||
presenceJSONStrings = InterfaceToStringSlice(presenceJSONInterface)
|
||||
}
|
||||
|
||||
// https://xmpp.org/extensions/xep-0045.html#ban баним вот таким сообщением
|
||||
ban := "<item affiliation='outcast' jid='" + jid + "'>"
|
||||
log.Debugf("Looking up %s in presence db", v.From)
|
||||
|
||||
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 />"
|
||||
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
|
||||
}
|
||||
|
||||
ban += "</item>"
|
||||
// На всякий случай.
|
||||
if p.Role == "moderator" {
|
||||
log.Infof("Skipping useragent blacklist check for %s because his role is %s", v.From, p.Role)
|
||||
|
||||
// Выжидаем некоторое время перед баном. А то можно настолько рано забанить, что сервер не внесёт злодея в банлист
|
||||
// комнаты и пришлёт affiliation: none вместо affiliation: outcast.
|
||||
if config.Jabber.BanDelay > 0 {
|
||||
time.Sleep(time.Duration(config.Jabber.BanDelay) * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
if id, err = talk.RawInformationQuery(
|
||||
talk.JID(),
|
||||
room,
|
||||
"ban1",
|
||||
xmpp.IQTypeSet,
|
||||
"http://jabber.org/protocol/muc#admin",
|
||||
ban,
|
||||
); err != nil {
|
||||
err = fmt.Errorf(
|
||||
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 id, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package jabber
|
||||
|
||||
import (
|
||||
"sync"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package jabber
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -10,28 +10,30 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func cmd(v xmpp.Chat) error {
|
||||
// Cmd парсит команды из чятика.
|
||||
func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
var err error
|
||||
|
||||
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 (
|
||||
answer string
|
||||
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 {
|
||||
chosenOneTalks = true
|
||||
}
|
||||
}
|
||||
|
||||
if chosenOneTalks {
|
||||
answer = fmt.Sprintf("%sпомощь - этот список команд\n", config.CSign)
|
||||
answer += fmt.Sprintf("%shelp - this commands list\n", config.CSign)
|
||||
answer += fmt.Sprintf("%srehash - reload white and black lists", config.CSign)
|
||||
answer = fmt.Sprintf("%sпомощь - этот список команд\n", j.C.CSign)
|
||||
answer += fmt.Sprintf("%shelp - this commands list\n", j.C.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 {
|
||||
answer = "Ничем помочь не могу. Луна не светит на тебя."
|
||||
}
|
||||
@@ -42,7 +44,7 @@ func cmd(v xmpp.Chat) error {
|
||||
dest = (strings.SplitN(v.Remote, "/", 2))[0]
|
||||
}
|
||||
|
||||
if _, err := talk.Send(
|
||||
if _, err := j.Talk.Send(
|
||||
xmpp.Chat{ //nolint:exhaustruct
|
||||
Remote: dest,
|
||||
Text: strings.TrimSpace(answer),
|
||||
@@ -54,7 +56,7 @@ func cmd(v xmpp.Chat) error {
|
||||
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-ом, а также из
|
||||
* чятика.
|
||||
* Groupchat мы можем определить по v.Type (groupchat) и тогда понятно что делать.
|
||||
@@ -71,9 +73,9 @@ func cmd(v xmpp.Chat) error {
|
||||
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 {
|
||||
chosenOneTalks = true
|
||||
}
|
||||
@@ -82,15 +84,22 @@ func cmd(v xmpp.Chat) error {
|
||||
if !chosenOneTalks {
|
||||
log.Infof(
|
||||
"Command %srehash given by non-bot_master user %s(%s), ignoring",
|
||||
config.CSign,
|
||||
j.C.CSign,
|
||||
realJID,
|
||||
v.Remote,
|
||||
)
|
||||
|
||||
var msg xmpp.Chat
|
||||
msg.Remote = v.Remote
|
||||
msg.Type = v.Type
|
||||
msg.Text = "Ничем помочь не могу. Луна не светит на тебя."
|
||||
|
||||
_, err := j.Talk.Send(msg)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := readWhitelist(); err != nil {
|
||||
if err := j.ReadWhitelist(); err != nil {
|
||||
listsLoaded = false
|
||||
|
||||
var msg xmpp.Chat
|
||||
@@ -98,12 +107,12 @@ func cmd(v xmpp.Chat) error {
|
||||
msg.Type = "chat" // private message
|
||||
msg.Text = fmt.Sprint(err)
|
||||
|
||||
if _, err := talk.Send(msg); err != nil {
|
||||
if _, err := j.Talk.Send(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := readBlacklist(); err != nil {
|
||||
if err := j.ReadBlacklist(); err != nil {
|
||||
listsLoaded = false
|
||||
|
||||
var msg xmpp.Chat
|
||||
@@ -111,7 +120,7 @@ func cmd(v xmpp.Chat) error {
|
||||
msg.Type = "chat" // private message
|
||||
msg.Text = fmt.Sprint(err)
|
||||
|
||||
if _, err := talk.Send(msg); err != nil {
|
||||
if _, err := j.Talk.Send(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -124,7 +133,7 @@ func cmd(v xmpp.Chat) error {
|
||||
msg.Type = v.Type
|
||||
msg.Text = "Сделано"
|
||||
|
||||
if _, err := talk.Send(msg); err != nil {
|
||||
if _, err := j.Talk.Send(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -132,16 +141,16 @@ func cmd(v xmpp.Chat) error {
|
||||
room := (strings.SplitN(v.Remote, "/", 2))[0]
|
||||
|
||||
// Реагировать на приватную команду "из комнаты", только если бот в комнате
|
||||
if slices.Contains(roomsConnected, room) {
|
||||
if slices.Contains(j.RoomsConnected, room) {
|
||||
var (
|
||||
chosenOneTalks = false
|
||||
listsLoaded = true
|
||||
)
|
||||
|
||||
// Реагировать на 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 {
|
||||
chosenOneTalks = true
|
||||
}
|
||||
@@ -150,15 +159,22 @@ func cmd(v xmpp.Chat) error {
|
||||
if !chosenOneTalks {
|
||||
log.Infof(
|
||||
"Command %srehash given by non-bot_master user %s(%s), ignoring",
|
||||
config.CSign,
|
||||
j.C.CSign,
|
||||
realJID,
|
||||
v.Remote,
|
||||
)
|
||||
|
||||
var msg xmpp.Chat
|
||||
msg.Remote = v.Remote
|
||||
msg.Type = v.Type
|
||||
msg.Text = "Ничем помочь не могу. Луна не светит на тебя."
|
||||
|
||||
_, err := j.Talk.Send(msg)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := readWhitelist(); err != nil {
|
||||
if err := j.ReadWhitelist(); err != nil {
|
||||
listsLoaded = false
|
||||
|
||||
var msg xmpp.Chat
|
||||
@@ -166,12 +182,12 @@ func cmd(v xmpp.Chat) error {
|
||||
msg.Type = v.Type
|
||||
msg.Text = fmt.Sprint(err)
|
||||
|
||||
if _, err := talk.Send(msg); err != nil {
|
||||
if _, err := j.Talk.Send(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := readBlacklist(); err != nil {
|
||||
if err := j.ReadBlacklist(); err != nil {
|
||||
listsLoaded = false
|
||||
|
||||
var msg xmpp.Chat
|
||||
@@ -179,7 +195,7 @@ func cmd(v xmpp.Chat) error {
|
||||
msg.Type = v.Type
|
||||
msg.Text = fmt.Sprint(err)
|
||||
|
||||
if _, err := talk.Send(msg); err != nil {
|
||||
if _, err := j.Talk.Send(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -190,7 +206,7 @@ func cmd(v xmpp.Chat) error {
|
||||
msg.Type = v.Type
|
||||
msg.Text = "Сделано"
|
||||
|
||||
if _, err := talk.Send(msg); err != nil {
|
||||
if _, err := j.Talk.Send(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -201,9 +217,34 @@ func cmd(v xmpp.Chat) error {
|
||||
}
|
||||
|
||||
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:
|
||||
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 (
|
||||
"encoding/json"
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// parseEvent парсит ивенты, прилетающие из модуля xmpp (изменения presence, фразы участников, ошибки).
|
||||
func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
lastServerActivity = time.Now().Unix()
|
||||
// ParseEvent парсит ивенты, прилетающие из модуля xmpp (изменения presence, фразы участников, ошибки).
|
||||
func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
j.LastServerActivity = time.Now().Unix()
|
||||
|
||||
switch v := e.(type) {
|
||||
// Сообщение в чяти
|
||||
@@ -33,32 +33,35 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
// Групповой чятик
|
||||
case "groupchat":
|
||||
log.Debugf("Message from public chat: %s", v.Text)
|
||||
// room := strings.SplitN(v.Remote, "/", 2)[0]
|
||||
nick := strings.SplitN(v.Remote, "/", 2)[1]
|
||||
var (
|
||||
room = strings.SplitN(v.Remote, "/", 2)[0]
|
||||
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")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err := cmd(v); err != nil {
|
||||
gTomb.Kill(err)
|
||||
if err := j.Cmd(v); err != nil {
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err := bunyChat(v); err != nil {
|
||||
gTomb.Kill(err)
|
||||
if err := j.BunyChat(v); err != nil {
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
lastActivity = lastServerActivity
|
||||
j.LastActivity = j.LastServerActivity
|
||||
|
||||
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 - это если мы работаем через ростер
|
||||
log.Debugf("Private message: %s", v.Text)
|
||||
|
||||
if err := cmd(v); err != nil {
|
||||
gTomb.Kill(err)
|
||||
if err := j.Cmd(v); err != nil {
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
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")
|
||||
|
||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||
if slices.Contains(roomsConnected, muc) {
|
||||
lastMucActivity.Set(muc, lastServerActivity)
|
||||
if slices.Contains(j.RoomsConnected, muc) {
|
||||
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
iqStruct jabberSimpleIqGetQuery
|
||||
iqStruct SimpleIqGetQuery
|
||||
parseError bool
|
||||
)
|
||||
|
||||
@@ -116,10 +119,10 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
case "jabber:iq:version":
|
||||
log.Infof("Got IQ get request for version from %s", v.From)
|
||||
|
||||
if id, err := talk.IqVersionResponse(
|
||||
if id, err := j.Talk.IqVersionResponse(
|
||||
v,
|
||||
"buny-jabber-bot",
|
||||
"1.dev",
|
||||
j.C.ExeName,
|
||||
j.C.Version,
|
||||
(strings.SplitN(runtime.GOOS, "/", 2))[0],
|
||||
); err != nil {
|
||||
err := fmt.Errorf(
|
||||
@@ -128,7 +131,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -139,14 +142,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
case "urn:xmpp:ping":
|
||||
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(
|
||||
"unable to send pong to jabber server: id=%s, err=%w",
|
||||
id,
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -158,14 +161,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
case "jabber:iq:last":
|
||||
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(
|
||||
"unable to send last activity time to jabber server: id=%s, err=%w",
|
||||
id,
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -181,10 +184,12 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
answer += "<feature var=\"urn:xmpp:time\" />"
|
||||
answer += "<feature var=\"urn:xmpp:ping\" />"
|
||||
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/muc\" />"
|
||||
answer += "</query>"
|
||||
|
||||
if id, err := talk.RawInformation(
|
||||
if id, err := j.Talk.RawInformation(
|
||||
v.To,
|
||||
v.From,
|
||||
v.ID,
|
||||
@@ -197,7 +202,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -208,7 +213,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
case xmpp.XMPPNS_DISCO_ITEMS:
|
||||
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,
|
||||
"http://jabber.org/protocol/disco#info",
|
||||
"http://jabber.org/protocol/commands",
|
||||
@@ -219,7 +224,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -239,21 +244,21 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
// Попробуем распарсить входящий запрос как urn:xmpp:time
|
||||
if parseError {
|
||||
var iqStruct jabberTimeIqGetQuery
|
||||
var iqStruct TimeIqGetQuery
|
||||
|
||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||
switch iqStruct.Xmlns {
|
||||
case "urn:xmpp:time":
|
||||
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(
|
||||
"unable to send urn:xmpp:time to jabber server: id=%s, err=%w",
|
||||
id,
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -273,14 +278,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
// Попробуем распарсить входящий запрос как http://jabber.org/protocol/pubsub
|
||||
if parseError {
|
||||
var iqStruct jabberPubsubIQGetQuery
|
||||
var iqStruct PubsubIQGetQuery
|
||||
|
||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||
switch iqStruct.Xmlns {
|
||||
case xmpp.XMPPNS_PUBSUB:
|
||||
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,
|
||||
"http://jabber.org/protocol/pubsub#errors",
|
||||
"subscribe",
|
||||
@@ -290,7 +295,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -311,19 +316,19 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
// Предполагается, что такие респонсы должны приходить только для пинга без серверной оптимизации.
|
||||
// Хотя идейно мы не поддерживаем работу без серверной оптимизации, но на пинг ответим, нам несложно.
|
||||
if parseError {
|
||||
var iqStruct jabberIqPing
|
||||
var iqStruct IqPing
|
||||
|
||||
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)
|
||||
|
||||
if id, err := talk.PingResponse(v); err != nil {
|
||||
if id, err := j.Talk.PingResponse(v); err != nil {
|
||||
err := fmt.Errorf(
|
||||
"unable to send pong to jabber server: id=%s, err=%w",
|
||||
id,
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -340,28 +345,28 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
case xmpp.IQTypeResult:
|
||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||
if slices.Contains(roomsConnected, muc) {
|
||||
lastMucActivity.Set(muc, lastServerActivity)
|
||||
if slices.Contains(j.RoomsConnected, muc) {
|
||||
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
// Похоже на 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)
|
||||
serverPingTimestampRx = time.Now().Unix() //nolint:wsl
|
||||
j.ServerPingTimestampRx = time.Now().Unix() //nolint:wsl
|
||||
|
||||
// Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован
|
||||
case v.To == talk.JID() && string(v.Query) == "<XMLElement></XMLElement>":
|
||||
mucNameMatch := slices.Contains(roomsConnected, v.From)
|
||||
// Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован.
|
||||
case v.To == j.Talk.JID() && string(v.Query) == "<XMLElement></XMLElement>":
|
||||
mucNameMatch := slices.Contains(j.RoomsConnected, v.From)
|
||||
|
||||
if mucNameMatch {
|
||||
log.Debugf("Got server-optimized MUC pong answer (xep-0410) from %s to %s", v.From, v.To)
|
||||
} else {
|
||||
mucNickMatch := false
|
||||
|
||||
for _, room := range roomsConnected {
|
||||
mucNick := fmt.Sprintf("%s/%s", room, config.Jabber.Nick)
|
||||
for _, room := range j.RoomsConnected {
|
||||
mucNick := fmt.Sprintf("%s/%s", room, j.GetBotNickFromRoomConfig(room))
|
||||
|
||||
if v.From == mucNick {
|
||||
mucNickMatch = true
|
||||
@@ -372,21 +377,25 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
if mucNickMatch {
|
||||
log.Debugf("Got 2-nd stage MUC pong answer (xep-0410) from %s to %s", v.From, v.To)
|
||||
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ
|
||||
} else {
|
||||
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ
|
||||
log.Infof("Got unknown pong from %s to %s", v.From, v.To)
|
||||
log.Debug(spew.Sdump(e))
|
||||
}
|
||||
}
|
||||
|
||||
// Подтверждение бана
|
||||
case v.To == talk.JID() && v.ID == "ban1":
|
||||
case v.To == j.Talk.JID() && v.ID == "ban1":
|
||||
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
|
||||
|
||||
break
|
||||
@@ -396,7 +405,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
// Формально, ответ должен парситься как пустой result и совпадать с типом jabberSimpleIqGetQuery
|
||||
// Ответ приходит с 2 xmlns, похоже, он парсится неправильно в go-xmpp.
|
||||
// TODO: после исправления go-xmpp, надо исправить это тут
|
||||
var iqStruct jabberSimpleIqGetQuery
|
||||
var iqStruct SimpleIqGetQuery
|
||||
|
||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||
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))
|
||||
}
|
||||
|
||||
// Ответ с результатом адресован нам.
|
||||
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:
|
||||
log.Info("Got an IQ result. Dunno how deal with it, discarding")
|
||||
log.Debug(spew.Sdump(e))
|
||||
@@ -424,14 +468,14 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
// Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем
|
||||
case xmpp.IQTypeSet:
|
||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||
if slices.Contains(roomsConnected, muc) {
|
||||
lastMucActivity.Set(muc, lastServerActivity)
|
||||
if slices.Contains(j.RoomsConnected, muc) {
|
||||
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Got an IQ request for set something. Answer not implemented")
|
||||
|
||||
if id, err := talk.ErrorNotImplemented(
|
||||
if id, err := j.Talk.ErrorNotImplemented(
|
||||
v,
|
||||
"http://jabber.org/protocol/commands",
|
||||
xmpp.IQTypeSet,
|
||||
@@ -442,7 +486,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -454,18 +498,18 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
// Если сервер не хочет пинговаться и отвечает ошибкой на пинг, то наверно он не умеет в пинги,
|
||||
// хотя если мы его пингуем, значит он анонсировал такой capability. Вот, засранец!
|
||||
var (
|
||||
iqPingStruct jabberIqPing
|
||||
iqPingStruct IqPing
|
||||
parseError = true
|
||||
)
|
||||
|
||||
if err := xml.Unmarshal(v.Query, &iqPingStruct); err == nil {
|
||||
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 += "fallback to keepalive whitespace pings"
|
||||
log.Error(msg)
|
||||
|
||||
serverCapsList.Set("urn:xmpp:ping", false)
|
||||
j.ServerCapsList.Set("urn:xmpp:ping", false)
|
||||
} else {
|
||||
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 и мы не в комнате, предполагается, что надо бы заджойниться
|
||||
if parseError {
|
||||
var iqErrorCancelNotAcceptable jabberIqErrorCancelNotAcceptable
|
||||
var iqErrorCancelNotAcceptable IqErrorCancelNotAcceptable
|
||||
|
||||
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]
|
||||
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(
|
||||
"Got Iq error message from: %s to: %s. Looks like i'm not in MUC anymore",
|
||||
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(
|
||||
"looks like connection to server also lost err=%w",
|
||||
err,
|
||||
)
|
||||
|
||||
gTomb.Kill(err)
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -535,8 +581,8 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
// Смена статуса участника
|
||||
case xmpp.Presence:
|
||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||
if slices.Contains(roomsConnected, muc) {
|
||||
lastMucActivity.Set(muc, lastServerActivity)
|
||||
if slices.Contains(j.RoomsConnected, muc) {
|
||||
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,17 +594,17 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
// Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать.
|
||||
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")
|
||||
|
||||
if slices.Contains(roomsConnected, v.From) {
|
||||
go joinMuc(v.From)
|
||||
if slices.Contains(j.RoomsConnected, v.From) {
|
||||
go j.JoinMuc(v.From)
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s",
|
||||
v.Type, v.From, v.Show, v.Status)
|
||||
log.Errorf(spew.Sdump(v))
|
||||
log.Error(spew.Sdump(v))
|
||||
}
|
||||
} else {
|
||||
log.Infof(
|
||||
@@ -577,19 +623,20 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
// Это наш собственный Presence
|
||||
if v.Show == "" && v.Status == "" {
|
||||
if nick == config.Jabber.Nick {
|
||||
roomsConnected = append(roomsConnected, room)
|
||||
if nick == j.GetBotNickFromRoomConfig(room) {
|
||||
j.RoomsConnected = append(j.RoomsConnected, room)
|
||||
// На всякий случай дедуплицируем список комнат, к которым мы заджойнились.
|
||||
sort.Strings(roomsConnected)
|
||||
slices.Compact(roomsConnected)
|
||||
sort.Strings(j.RoomsConnected)
|
||||
slices.Compact(j.RoomsConnected)
|
||||
}
|
||||
}
|
||||
|
||||
switch v.Role {
|
||||
// Участник ушёл
|
||||
case "none":
|
||||
if presenceJSONInterface, present := roomPresences.Get(room); present {
|
||||
presenceJSONStrings := interfaceToStringSlice(presenceJSONInterface)
|
||||
if presenceJSONInterface, present := j.RoomPresences.Get(room); present {
|
||||
presenceJSONStrings := InterfaceToStringSlice(presenceJSONInterface)
|
||||
|
||||
var newPresenceJSONStrings []string
|
||||
|
||||
for _, presenceJSONstring := range presenceJSONStrings {
|
||||
@@ -603,23 +650,25 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
newPresenceJSONStrings = append(newPresenceJSONStrings, presenceJSONstring)
|
||||
}
|
||||
|
||||
roomPresences.Set(room, newPresenceJSONStrings)
|
||||
j.RoomPresences.Set(room, newPresenceJSONStrings)
|
||||
}
|
||||
// Участник пришёл
|
||||
default:
|
||||
var presenceJSONStrings []string
|
||||
var newPresenceJSONStrings []string
|
||||
var presenceJSONBytes []byte
|
||||
var (
|
||||
presenceJSONStrings []string
|
||||
newPresenceJSONStrings []string
|
||||
presenceJSONBytes []byte
|
||||
)
|
||||
|
||||
if presenceJSONInterface, present := roomPresences.Get(room); present {
|
||||
presenceJSONStrings = interfaceToStringSlice(presenceJSONInterface)
|
||||
if presenceJSONInterface, present := j.RoomPresences.Get(room); present {
|
||||
presenceJSONStrings = InterfaceToStringSlice(presenceJSONInterface)
|
||||
}
|
||||
|
||||
for _, presenceJSONString := range presenceJSONStrings {
|
||||
var p xmpp.Presence
|
||||
_ = json.Unmarshal([]byte(presenceJSONString), &p)
|
||||
|
||||
// Если находим, что у нас уже есть клиент с таким же From, то есть полным nick-ом (для grouchat)
|
||||
// Если находим, что у нас уже есть клиент с таким же From, то есть полным nick-ом (для groupchat)
|
||||
// просто замещаем его.
|
||||
if p.From == v.From {
|
||||
continue
|
||||
@@ -630,16 +679,16 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
presenceJSONBytes, _ = json.Marshal(v) //nolint:errchkjson
|
||||
newPresenceJSONStrings = append(newPresenceJSONStrings, string(presenceJSONBytes))
|
||||
roomPresences.Set(room, newPresenceJSONStrings)
|
||||
j.RoomPresences.Set(room, newPresenceJSONStrings)
|
||||
}
|
||||
|
||||
// Проверяем, а не злодей ли зашёл? Сделать это мы можем, только если мы находимся в комнате.
|
||||
// По правилам, мы можем что-то делать, только после того, как нам прилетит наш собственный presence, это
|
||||
// значит, что мы вошли в комнату.
|
||||
if slices.Contains(roomsConnected, room) {
|
||||
if slices.Contains(j.RoomsConnected, room) {
|
||||
if v.Affiliation != "outcast" {
|
||||
if err := bunyPresense(v); err != nil {
|
||||
gTomb.Kill(err)
|
||||
if err := j.BunyPresense(v); err != nil {
|
||||
j.GTomb.Kill(err)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -650,8 +699,8 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
// Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info"
|
||||
case xmpp.DiscoResult:
|
||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||
if slices.Contains(roomsConnected, muc) {
|
||||
lastMucActivity.Set(muc, lastServerActivity)
|
||||
if slices.Contains(j.RoomsConnected, muc) {
|
||||
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,10 +712,10 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
// Конкретно сейчас нас интересует только поддержка c2s ping
|
||||
for _, feature := range v.Features {
|
||||
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":
|
||||
mucCaps := make(map[string]bool)
|
||||
@@ -676,7 +725,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
mucCaps[feature] = true //nolint:wsl
|
||||
}
|
||||
|
||||
mucCapsList.Set(v.From, mucCaps)
|
||||
j.MucCapsList.Set(v.From, mucCaps)
|
||||
|
||||
case "pubsub":
|
||||
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 (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Переменные для простенького нормализатора текста.
|
||||
var (
|
||||
// Знаки препинания раз.
|
||||
pMarks = []string{".", ",", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "{", "}", "<", ">", "[", "]", "\\"}
|
||||
|
||||
// Знаки препинания два-с.
|
||||
pMarks2 = []string{"-", "_", "+", "=", ":", ";", "'", "`", "~", "\""}
|
||||
|
||||
// Символы новой строки.
|
||||
newLines = []string{"\n", "\r", "\n\r", "\r\n"}
|
||||
)
|
||||
|
||||
// Нормализует текстовый буфер. Убирает начальные и конечные пробельные символы, заменяет сносы строк пробелами, удаляет
|
||||
// знаки препинания, схлопывает повторяющиеся пробелы в один символ.
|
||||
func nString(buf string) string {
|
||||
@@ -32,7 +44,7 @@ func nString(buf string) string {
|
||||
}
|
||||
|
||||
// Обёртка для nString, возвращает нормализованную строку в нижнем регистре.
|
||||
func nStringLower(buf string) string {
|
||||
func nStringLower(buf string) string { //nolint:unused
|
||||
return strings.ToLower(nString(buf))
|
||||
}
|
||||
|
||||
@@ -40,3 +52,5 @@ func nStringLower(buf string) string {
|
||||
func nStringUpper(buf string) string {
|
||||
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:
|
||||
|
||||
```shell
|
||||
go get github.com/navossoc/bayesian
|
||||
go get github.com/jbrukh/bayesian
|
||||
go install !$
|
||||
```
|
||||
|
||||
@@ -26,7 +26,7 @@ go install !$
|
||||
|
||||
## 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:
|
||||
|
||||
```go
|
||||
import "github.com/navossoc/bayesian"
|
||||
import "github.com/jbrukh/bayesian"
|
||||
|
||||
const (
|
||||
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`)
|
||||
|
||||
```go
|
||||
import "github.com/navossoc/bayesian"
|
||||
import "github.com/jbrukh/bayesian"
|
||||
|
||||
const (
|
||||
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
|
||||
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
|
||||
in the documentation and/or other materials provided with the
|
||||
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
|
||||
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
|
||||
pbkdf2.Key.
|
||||
*/
|
||||
package pbkdf2 // import "golang.org/x/crypto/pbkdf2"
|
||||
package pbkdf2
|
||||
|
||||
import (
|
||||
"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
|
||||
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
|
||||
in the documentation and/or other materials provided with the
|
||||
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
|
||||
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
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
"cmp"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// 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
|
||||
// comparison stops at the first unequal pair.
|
||||
// Floating point NaNs are not considered equal.
|
||||
//
|
||||
//go:fix inline
|
||||
func Equal[S ~[]E, E comparable](s1, s2 S) bool {
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
for i := range s1 {
|
||||
if s1[i] != s2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return slices.Equal(s1, s2)
|
||||
}
|
||||
|
||||
// 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
|
||||
// increasing index order, and the comparison stops at the first index
|
||||
// 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 {
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
for i, v1 := range s1 {
|
||||
v2 := s2[i]
|
||||
if !eq(v1, v2) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return slices.EqualFunc(s1, s2, eq)
|
||||
}
|
||||
|
||||
// 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
|
||||
// considered less than the longer one.
|
||||
// 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 {
|
||||
if i >= len(s2) {
|
||||
return +1
|
||||
}
|
||||
v2 := s2[i]
|
||||
if c := cmpCompare(v1, v2); c != 0 {
|
||||
return c
|
||||
}
|
||||
}
|
||||
if len(s1) < len(s2) {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
//
|
||||
//go:fix inline
|
||||
func Compare[S ~[]E, E cmp.Ordered](s1, s2 S) int {
|
||||
return slices.Compare(s1, s2)
|
||||
}
|
||||
|
||||
// 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
|
||||
// returns 0 the result is 0 if len(s1) == len(s2), -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 {
|
||||
for i, v1 := range s1 {
|
||||
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
|
||||
return slices.CompareFunc(s1, s2, cmp)
|
||||
}
|
||||
|
||||
// Index returns the index of the first occurrence of v in s,
|
||||
// or -1 if not present.
|
||||
//
|
||||
//go:fix inline
|
||||
func Index[S ~[]E, E comparable](s S, v E) int {
|
||||
for i := range s {
|
||||
if v == s[i] {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
return slices.Index(s, v)
|
||||
}
|
||||
|
||||
// IndexFunc returns the first index i satisfying f(s[i]),
|
||||
// or -1 if none do.
|
||||
//
|
||||
//go:fix inline
|
||||
func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
|
||||
for i := range s {
|
||||
if f(s[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
return slices.IndexFunc(s, f)
|
||||
}
|
||||
|
||||
// Contains reports whether v is present in s.
|
||||
//
|
||||
//go:fix inline
|
||||
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
|
||||
// element e of s satisfies f(e).
|
||||
//
|
||||
//go:fix inline
|
||||
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,
|
||||
@@ -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].
|
||||
// Insert panics if i is out of range.
|
||||
// This function is O(len(s) + len(v)).
|
||||
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
|
||||
m := len(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
|
||||
//go:fix inline
|
||||
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
|
||||
return slices.Insert(s, i, v...)
|
||||
}
|
||||
|
||||
// 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 is O(len(s)-j), so if many items must be deleted, it is better to
|
||||
// Delete panics if j > len(s) or s[i:j] is not a valid slice of s.
|
||||
// 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.
|
||||
// Delete might not modify the elements s[len(s)-(j-i):len(s)]. If those
|
||||
// elements contain pointers you might consider zeroing those elements so that
|
||||
// objects they reference can be garbage collected.
|
||||
// Delete zeroes the elements s[len(s)-(j-i):len(s)].
|
||||
//
|
||||
//go:fix inline
|
||||
func Delete[S ~[]E, E any](s S, i, j int) S {
|
||||
_ = s[i:j] // bounds check
|
||||
|
||||
return append(s[:i], s[j:]...)
|
||||
return slices.Delete(s, i, j)
|
||||
}
|
||||
|
||||
// DeleteFunc removes any elements from s for which del returns true,
|
||||
// returning the modified slice.
|
||||
// When DeleteFunc removes m elements, it might not modify the elements
|
||||
// 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.
|
||||
// DeleteFunc zeroes the elements between the new length and the original length.
|
||||
//
|
||||
//go:fix inline
|
||||
func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
|
||||
i := IndexFunc(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]
|
||||
return slices.DeleteFunc(s, del)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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 {
|
||||
_ = s[i:j] // verify that i:j is a valid subslice
|
||||
|
||||
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
|
||||
return slices.Replace(s, i, j, v...)
|
||||
}
|
||||
|
||||
// Clone returns a copy of the slice.
|
||||
// The elements are copied using assignment, so this is a shallow clone.
|
||||
//
|
||||
//go:fix inline
|
||||
func Clone[S ~[]E, E any](s S) S {
|
||||
// Preserve nil in case it matters.
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return append(S([]E{}), s...)
|
||||
return slices.Clone(s)
|
||||
}
|
||||
|
||||
// Compact replaces consecutive runs of equal elements with a single copy.
|
||||
// This is like the uniq command found on Unix.
|
||||
// Compact modifies the contents of the slice s and returns the modified slice,
|
||||
// which may have a smaller length.
|
||||
// When Compact discards m elements in total, it might not modify the elements
|
||||
// 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.
|
||||
// Compact zeroes the elements between the new length and the original length.
|
||||
//
|
||||
//go:fix inline
|
||||
func Compact[S ~[]E, E comparable](s S) S {
|
||||
if len(s) < 2 {
|
||||
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]
|
||||
return slices.Compact(s)
|
||||
}
|
||||
|
||||
// CompactFunc is like [Compact] but uses an equality function to compare elements.
|
||||
// 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 {
|
||||
if len(s) < 2 {
|
||||
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]
|
||||
return slices.CompactFunc(s, eq)
|
||||
}
|
||||
|
||||
// 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
|
||||
// to the slice without another allocation. If n is negative or too large to
|
||||
// allocate the memory, Grow panics.
|
||||
//
|
||||
//go:fix inline
|
||||
func Grow[S ~[]E, E any](s S, n int) S {
|
||||
if n < 0 {
|
||||
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
|
||||
return slices.Grow(s, n)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return s[:len(s):len(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")
|
||||
return slices.Clip(s)
|
||||
}
|
||||
|
||||
// Reverse reverses the elements of the slice in place.
|
||||
//
|
||||
//go:fix inline
|
||||
func Reverse[S ~[]E, E any](s S) {
|
||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
slices.Reverse(s)
|
||||
}
|
||||
|
||||
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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:generate go run $GOROOT/src/sort/gen_sort_variants.go -exp
|
||||
|
||||
package slices
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
"cmp"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// Sort sorts a slice of any ordered type in ascending order.
|
||||
// When sorting floating-point numbers, NaNs are ordered before other values.
|
||||
func Sort[S ~[]E, E constraints.Ordered](x S) {
|
||||
n := len(x)
|
||||
pdqsortOrdered(x, 0, n, bits.Len(uint(n)))
|
||||
//
|
||||
//go:fix inline
|
||||
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
|
||||
// function. This sort is not guaranteed to be stable.
|
||||
// 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.
|
||||
// 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) {
|
||||
n := len(x)
|
||||
pdqsortCmpFunc(x, 0, n, bits.Len(uint(n)), cmp)
|
||||
slices.SortFunc(x, cmp)
|
||||
}
|
||||
|
||||
// SortStableFunc sorts the slice x while keeping the original order of equal
|
||||
// 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) {
|
||||
stableCmpFunc(x, len(x), cmp)
|
||||
slices.SortStableFunc(x, cmp)
|
||||
}
|
||||
|
||||
// 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-- {
|
||||
if cmpLess(x[i], x[i-1]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
//
|
||||
//go:fix inline
|
||||
func IsSorted[S ~[]E, E cmp.Ordered](x S) bool {
|
||||
return slices.IsSorted(x)
|
||||
}
|
||||
|
||||
// IsSortedFunc reports whether x is sorted in ascending order, with cmp as the
|
||||
// comparison function as defined by [SortFunc].
|
||||
//
|
||||
//go:fix inline
|
||||
func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool {
|
||||
for i := len(x) - 1; i > 0; i-- {
|
||||
if cmp(x[i], x[i-1]) < 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return slices.IsSortedFunc(x, cmp)
|
||||
}
|
||||
|
||||
// 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
|
||||
// forces the output to be NaN).
|
||||
func Min[S ~[]E, E constraints.Ordered](x S) E {
|
||||
if len(x) < 1 {
|
||||
panic("slices.Min: empty list")
|
||||
}
|
||||
m := x[0]
|
||||
for i := 1; i < len(x); i++ {
|
||||
m = min(m, x[i])
|
||||
}
|
||||
return m
|
||||
//
|
||||
//go:fix inline
|
||||
func Min[S ~[]E, E cmp.Ordered](x S) E {
|
||||
return slices.Min(x)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
if len(x) < 1 {
|
||||
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
|
||||
return slices.MinFunc(x, cmp)
|
||||
}
|
||||
|
||||
// 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
|
||||
// forces the output to be NaN).
|
||||
func Max[S ~[]E, E constraints.Ordered](x S) E {
|
||||
if len(x) < 1 {
|
||||
panic("slices.Max: empty list")
|
||||
}
|
||||
m := x[0]
|
||||
for i := 1; i < len(x); i++ {
|
||||
m = max(m, x[i])
|
||||
}
|
||||
return m
|
||||
//
|
||||
//go:fix inline
|
||||
func Max[S ~[]E, E cmp.Ordered](x S) E {
|
||||
return slices.Max(x)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
if len(x) < 1 {
|
||||
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
|
||||
return slices.MaxFunc(x, cmp)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
func BinarySearch[S ~[]E, E constraints.Ordered](x S, target E) (int, bool) {
|
||||
// Inlining is faster than calling BinarySearchFunc with a lambda.
|
||||
n := len(x)
|
||||
// Define x[-1] < target and x[n] >= 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)))
|
||||
//
|
||||
//go:fix inline
|
||||
func BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) {
|
||||
return slices.BinarySearch(x, target)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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.
|
||||
//
|
||||
//go:fix inline
|
||||
func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) {
|
||||
n := len(x)
|
||||
// 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
|
||||
return slices.BinarySearchFunc(x, target, cmp)
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
in the documentation and/or other materials provided with the
|
||||
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
|
||||
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.
|
||||
|
||||
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.
|
||||
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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos) && go1.9
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
|
||||
// +build go1.9
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||
|
||||
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.
|
||||
|
||||
//go:build gc
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build (freebsd || netbsd || openbsd) && gc
|
||||
// +build freebsd netbsd openbsd
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build (darwin || dragonfly || freebsd || netbsd || openbsd) && gc
|
||||
// +build darwin dragonfly freebsd netbsd openbsd
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build (freebsd || netbsd || openbsd) && gc
|
||||
// +build freebsd netbsd openbsd
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build (darwin || freebsd || netbsd || openbsd) && gc
|
||||
// +build darwin freebsd netbsd openbsd
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build (darwin || freebsd || netbsd || openbsd) && gc
|
||||
// +build darwin freebsd netbsd openbsd
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build (darwin || freebsd || netbsd || openbsd) && gc
|
||||
// +build darwin freebsd netbsd openbsd
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build gc
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build gc
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build gc
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build linux && arm64 && gc
|
||||
// +build linux
|
||||
// +build arm64
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build linux && loong64 && gc
|
||||
// +build linux
|
||||
// +build loong64
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build linux && (mips64 || mips64le) && gc
|
||||
// +build linux
|
||||
// +build mips64 mips64le
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build linux && (mips || mipsle) && gc
|
||||
// +build linux
|
||||
// +build mips mipsle
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build linux && (ppc64 || ppc64le) && gc
|
||||
// +build linux
|
||||
// +build ppc64 ppc64le
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build riscv64 && gc
|
||||
// +build riscv64
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build linux && s390x && gc
|
||||
// +build linux
|
||||
// +build s390x
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build gc
|
||||
// +build gc
|
||||
|
||||
#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.
|
||||
|
||||
//go:build gc
|
||||
// +build gc
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
|
||||
572
vendor/golang.org/x/sys/unix/asm_zos_s390x.s
generated
vendored
572
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.
|
||||
|
||||
//go:build zos && s390x && gc
|
||||
// +build zos
|
||||
// +build s390x
|
||||
// +build gc
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
#define PSALAA 1208(R0)
|
||||
#define GTAB64(x) 80(x)
|
||||
#define LCA64(x) 88(x)
|
||||
#define CAA(x) 8(x)
|
||||
#define EDCHPXV(x) 1016(x) // in the CAA
|
||||
#define SAVSTACK_ASYNC(x) 336(x) // in the LCA
|
||||
#define CAA(x) 8(x)
|
||||
#define CEECAATHDID(x) 976(x) // in the CAA
|
||||
#define EDCHPXV(x) 1016(x) // in the CAA
|
||||
#define GOCB(x) 1104(x) // in the CAA
|
||||
|
||||
// SS_*, where x=SAVSTACK_ASYNC
|
||||
#define SS_LE(x) 0(x)
|
||||
@@ -22,7 +21,30 @@
|
||||
#define SS_ERRNO(x) 16(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
|
||||
|
||||
// Call Instructions
|
||||
#define LE_CALL BYTE $0x0D; BYTE $0x76 // BL R7, R6
|
||||
#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)
|
||||
@@ -38,7 +60,7 @@ TEXT addrerrno<>(SB),NOSPLIT|NOFRAME,$0-0
|
||||
// Get __errno FuncDesc.
|
||||
MOVD CAA(R8), R9
|
||||
MOVD EDCHPXV(R9), R9
|
||||
ADD $(0x156*16), R9
|
||||
ADD $(__errno), R9
|
||||
LMG 0(R9), R5, R6
|
||||
|
||||
// Switch to saved LE stack.
|
||||
@@ -55,296 +77,6 @@ TEXT addrerrno<>(SB),NOSPLIT|NOFRAME,$0-0
|
||||
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
|
||||
|
||||
// func svcCall(fnptr unsafe.Pointer, argv *unsafe.Pointer, dsa *uint64)
|
||||
TEXT ·svcCall(SB), NOSPLIT, $0
|
||||
BL runtime·save_g(SB) // Save g and stack pointer
|
||||
@@ -374,8 +106,7 @@ TEXT ·svcLoad(SB),NOSPLIT,$0
|
||||
MOVD name+0(FP), R0 // Move SVC args into registers
|
||||
MOVD $0x80000000, R1
|
||||
MOVD $0, R15
|
||||
BYTE $0x0A // SVC 08 LOAD
|
||||
BYTE $0x08
|
||||
SVC_LOAD
|
||||
MOVW R15, R3 // Save return code from SVC
|
||||
MOVD R2, R15 // Restore go stack pointer
|
||||
CMP R3, $0 // Check SVC return code
|
||||
@@ -383,18 +114,18 @@ TEXT ·svcLoad(SB),NOSPLIT,$0
|
||||
|
||||
MOVD $-2, R3 // Reset last bit of entry point to zero
|
||||
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
|
||||
BNE done
|
||||
|
||||
MOVD R15, R2 // Save go stack pointer
|
||||
MOVD $0, R15 // Move SVC args into registers (entry point still in r0 from SVC 08)
|
||||
BYTE $0x0A // SVC 09 DELETE
|
||||
BYTE $0x09
|
||||
SVC_DELETE
|
||||
MOVD R2, R15 // Restore go stack pointer
|
||||
|
||||
error:
|
||||
MOVD $0, addr+8(FP) // Return 0 on failure
|
||||
MOVD $0, ret+8(FP) // Return 0 on failure
|
||||
|
||||
done:
|
||||
XOR R0, R0 // Reset r0 to 0
|
||||
RET
|
||||
@@ -403,13 +134,12 @@ done:
|
||||
TEXT ·svcUnload(SB), NOSPLIT, $0
|
||||
MOVD R15, R2 // Save go stack pointer
|
||||
MOVD name+0(FP), R0 // Move SVC args into registers
|
||||
MOVD addr+8(FP), R15
|
||||
BYTE $0x0A // SVC 09
|
||||
BYTE $0x09
|
||||
MOVD fnptr+8(FP), R15
|
||||
SVC_DELETE
|
||||
XOR R0, R0 // Reset r0 to 0
|
||||
MOVD R15, R1 // Save SVC return code
|
||||
MOVD R2, R15 // Restore go stack pointer
|
||||
MOVD R1, rc+0(FP) // Return SVC return code
|
||||
MOVD R1, ret+16(FP) // Return SVC return code
|
||||
RET
|
||||
|
||||
// func gettid() uint64
|
||||
@@ -420,7 +150,233 @@ TEXT ·gettid(SB), NOSPLIT, $0
|
||||
|
||||
// Get CEECAATHDID
|
||||
MOVD CAA(R8), R9
|
||||
MOVD 0x3D0(R9), R9
|
||||
MOVD CEECAATHDID(R9), R9
|
||||
MOVD R9, ret+0(FP)
|
||||
|
||||
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.
|
||||
|
||||
//go:build freebsd
|
||||
// +build freebsd
|
||||
|
||||
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.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
|
||||
|
||||
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.
|
||||
|
||||
//go:build aix && ppc
|
||||
// +build aix,ppc
|
||||
|
||||
// Functions to access/create device major and minor numbers matching the
|
||||
// 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.
|
||||
|
||||
//go:build aix && ppc64
|
||||
// +build aix,ppc64
|
||||
|
||||
// Functions to access/create device major and minor numbers matching the
|
||||
// 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.
|
||||
|
||||
//go:build zos && s390x
|
||||
// +build zos,s390x
|
||||
|
||||
// Functions to access/create device major and minor numbers matching the
|
||||
// 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.
|
||||
|
||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
|
||||
|
||||
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.
|
||||
//
|
||||
//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
|
||||
|
||||
|
||||
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.
|
||||
//
|
||||
//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
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user