19 Commits

Author SHA1 Message Date
eleksir
3ebd2b48d1 Implement ban by software version xep-0092 2025-05-29 23:56:19 +03:00
eleksir
0ed74ed812 Implement proper version command 2025-05-27 22:09:56 +03:00
eleksir
f9dd1acc45 Update deps, update config of golangci-lint, fix some golangci-lint warnings 2025-05-27 20:54:22 +03:00
eleksir
21b5f59c1c Hopefully fix xmpp server communication error handling, but fix is kinda dirty 2024-12-07 20:12:28 +03:00
eleksir
073138bde4 Properly wrap error, add more informative error message body 2024-12-07 19:03:51 +03:00
eleksir
fb19f8056a Use tomb v2, update deps, fix hang on stale connection
- Use tomb v2 to properly spawn and reap goroutines (hopefully)
- Update deps
- Fix hang on stale connection due to improper goroutine management
2024-11-16 10:41:00 +03:00
eleksir
678d830f8c Fix .gitignore and return cmd/buny-jabber-bot/main.go 2024-11-15 23:56:22 +03:00
eleksir
17fe947df9 Add two caps that are somewhat implemented http://jabber.org/protocol/{caps,muc} 2024-10-21 21:35:34 +03:00
eleksir
0264e675a4 Refactor layout to go-style, update deps 2024-10-21 19:47:11 +03:00
eleksir
27f79905f4 Update vendored libs, min go version now 1.22 2024-07-24 23:10:32 +03:00
eleksir
0773c29359 Fix typo in README.md 2023-11-24 02:36:53 +03:00
eleksir
c803682b8b Update README.md, Design.txt and some comments here and there 2023-11-24 02:29:56 +03:00
eleksir
9a9516e80d Properly detect ban event source (wether we are in src room or not) 2023-11-24 01:23:26 +03:00
eleksir
ea6e562928 Fix existing comments, add some new 2023-11-24 01:12:51 +03:00
eleksir
420ed7d932 Do not exclude resource while checking evil jid against blacklist 2023-11-13 22:22:06 +03:00
eleksir
b690b54891 Be more verbose about closing of jabber connection 2023-11-12 10:41:30 +03:00
eleksir
40c574faf7 Do not use defer, notify gTomb before closing goroutines. Explicitly. 2023-11-12 10:36:08 +03:00
eleksir
31490b0922 Add per-chat nick setting, update README.md 2023-11-05 20:09:17 +03:00
eleksir
d88f08479d Move read config stuff to separate file 2023-11-05 16:00:05 +03:00
394 changed files with 17983 additions and 8223 deletions

10
.gitignore vendored
View File

@@ -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

View File

@@ -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

View File

@@ -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-е навечно.
А ещё есть пингеры каналов и пингеры сервера. Их бы тоже завершать явным образом.

View File

@@ -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:

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

162
cmd/buny-jabber-bot/main.go Normal file
View 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: */

View File

@@ -0,0 +1 @@
0.2

View File

@@ -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:

View File

@@ -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"
}
]
}
]

View File

@@ -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

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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: */

View 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: */

View File

@@ -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: */

View File

@@ -1,4 +1,4 @@
package main
package jabber
import (
"sync"

View File

@@ -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: */

View File

@@ -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)

View 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: */

View File

@@ -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: */

View 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
}

View 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: */

View 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
View 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
View 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
View 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
View File

@@ -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
View File

@@ -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
View File

@@ -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
View 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
View 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
View 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
View 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
View 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
[![Go Reference](https://pkg.go.dev/badge/github.com/google/uuid.svg)](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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}

View File

@@ -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
View File

@@ -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.

View File

@@ -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
View File

@@ -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.

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)).
//
//go:fix inline
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
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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
View 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
View 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
View 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
View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gc
// +build gc
#include "textflag.h"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gc
// +build gc
#include "textflag.h"

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gc
// +build gc
#include "textflag.h"

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gc
// +build gc
#include "textflag.h"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -3,8 +3,6 @@
// license that can be found in the LICENSE file.
//go:build riscv64 && gc
// +build riscv64
// +build gc
#include "textflag.h"

View File

@@ -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"

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gc
// +build gc
#include "textflag.h"

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gc
// +build gc
#include "textflag.h"

View File

@@ -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,15 +21,38 @@
#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
TEXT ·clearErrno(SB),NOSPLIT,$0-0
// 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)
MOVD $0, 0(R3)
RET
// Returns the address of errno in R3.
TEXT addrerrno<>(SB),NOSPLIT|NOFRAME,$0-0
TEXT addrerrno<>(SB), NOSPLIT|NOFRAME, $0-0
// Get library control area (LCA).
MOVW PSALAA, R8
MOVD LCA64(R8), R8
@@ -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,298 +77,8 @@ 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
TEXT ·svcCall(SB), NOSPLIT, $0
BL runtime·save_g(SB) // Save g and stack pointer
MOVW PSALAA, R8
MOVD LCA64(R8), R8
@@ -369,13 +101,12 @@ TEXT ·svcCall(SB),NOSPLIT,$0
RET
// func svcLoad(name *byte) unsafe.Pointer
TEXT ·svcLoad(SB),NOSPLIT,$0
TEXT ·svcLoad(SB), NOSPLIT, $0
MOVD R15, R2 // Save go stack pointer
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,33 +114,32 @@ 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
// func svcUnload(name *byte, fnptr unsafe.Pointer) int64
TEXT ·svcUnload(SB),NOSPLIT,$0
TEXT ·svcUnload(SB), NOSPLIT, $0
MOVD R15, R2 // Save go stack pointer
MOVD 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
View 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
View 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
View 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
View 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

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build freebsd
// +build freebsd
package unix

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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