mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
90
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d60dd6a5a0 | ||
|
|
8737bba4c3 | ||
|
|
97716d2b9e | ||
|
|
844a6f8d25 | ||
|
|
b6f2c1b4bb | ||
|
|
848704eb3c | ||
|
|
c3571b001a | ||
|
|
cf6682dff7 | ||
|
|
2a1453ee37 | ||
|
|
a9f06fc299 | ||
|
|
5839c59682 | ||
|
|
860810c148 | ||
|
|
d92f079f97 | ||
|
|
4315a3fce3 | ||
|
|
5d142acb86 | ||
|
|
0ee50c3150 | ||
|
|
c0840eb9d6 | ||
|
|
8b3e3cd6ab | ||
|
|
ffd2fa6e8b | ||
|
|
130b5f9706 | ||
|
|
549a54cf4b | ||
|
|
e2d11c40dd | ||
|
|
9749cd8e58 | ||
|
|
4082378313 | ||
|
|
df65d7fe11 | ||
|
|
99ce1cfb7a | ||
|
|
5f2abac8d0 | ||
|
|
f265214006 | ||
|
|
23bdc7053d | ||
|
|
9510a5f0c1 | ||
|
|
72ea11790f | ||
|
|
d651e0888f | ||
|
|
f2bb628883 | ||
|
|
8e5ff45306 | ||
|
|
9dbd5a1c92 | ||
|
|
6115702268 | ||
|
|
ed1739c8d2 | ||
|
|
88da1fe868 | ||
|
|
b520eaf6e6 | ||
|
|
635086364e | ||
|
|
4bfdb7bcee | ||
|
|
7e4ba5d3e7 | ||
|
|
c642bfcacb | ||
|
|
a424412edd | ||
|
|
4d15774c9b | ||
|
|
51c45a48e8 | ||
|
|
94f01c8230 | ||
|
|
0307bf16eb | ||
|
|
1a73128163 | ||
|
|
eff371f6b7 | ||
|
|
3a48d8480e | ||
|
|
5cd83392e0 | ||
|
|
fffaca3945 | ||
|
|
fae797eff0 | ||
|
|
64a7095f5f | ||
|
|
82909ad1d9 | ||
|
|
e2e9bc18dc | ||
|
|
5bed9d4e90 | ||
|
|
60e87f9e08 | ||
|
|
d4c37f45d2 | ||
|
|
79d22bafbf | ||
|
|
6396a64878 | ||
|
|
335797e661 | ||
|
|
d767bd019c | ||
|
|
590f9c3f96 | ||
|
|
cff899aa49 | ||
|
|
ca2dc9d267 | ||
|
|
afdd77c642 | ||
|
|
8a5970c3da | ||
|
|
2eb2ac8121 | ||
|
|
daf7d404b1 | ||
|
|
3b308920f4 | ||
|
|
c73b183f74 | ||
|
|
7564312f5e | ||
|
|
6134b78afb | ||
|
|
3db9946608 | ||
|
|
ef09058498 | ||
|
|
331faeb8ef | ||
|
|
4ae195e3eb | ||
|
|
f8f8ba5864 | ||
|
|
216e3b9759 | ||
|
|
f1bc5074f9 | ||
|
|
1b5d3436eb | ||
|
|
9397fcb4ae | ||
|
|
04c6c3f800 | ||
|
|
7842a6c4c6 | ||
|
|
6c815a4432 | ||
|
|
81358e31bb | ||
|
|
7bea772dbd | ||
|
|
7fe0b295f5 |
No files matched your search
@@ -19,7 +19,9 @@ package cli
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
"github.com/bishopfox/sliver/client/command"
|
||||
@@ -28,6 +30,7 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
)
|
||||
|
||||
// consoleCmd generates the console with required pre/post runners.
|
||||
@@ -70,6 +73,9 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait for any connection state changes and exit if the connection is lost.
|
||||
go handleConnectionLost(ln)
|
||||
|
||||
return console.StartClient(con, rpc, command.ServerCommands(con, nil), command.SliverCommands(con), run)
|
||||
}
|
||||
|
||||
@@ -84,3 +90,16 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
|
||||
|
||||
return pre, post
|
||||
}
|
||||
|
||||
func handleConnectionLost(ln *grpc.ClientConn) {
|
||||
currentState := ln.GetState()
|
||||
// currentState should be "Ready" when the connection is established.
|
||||
if ln.WaitForStateChange(context.Background(), currentState) {
|
||||
newState := ln.GetState()
|
||||
// newState will be "Idle" if the connection is lost.
|
||||
if newState == connectivity.Idle {
|
||||
fmt.Println("\nLost connection to server. Exiting now.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/packages"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
@@ -57,7 +58,7 @@ var (
|
||||
|
||||
defaultHostProc = map[string]string{
|
||||
"windows": windowsDefaultHostProc,
|
||||
"linux": windowsDefaultHostProc,
|
||||
"linux": linuxDefaultHostProc,
|
||||
"darwin": macosDefaultHostProc,
|
||||
}
|
||||
)
|
||||
@@ -85,12 +86,13 @@ type AliasManifest struct {
|
||||
Help string `json:"help"`
|
||||
LongHelp string `json:"long_help"`
|
||||
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
AllowArgs bool `json:"allow_args"`
|
||||
DefaultArgs string `json:"default_args"`
|
||||
Files []*AliasFile `json:"files"`
|
||||
IsReflective bool `json:"is_reflective"`
|
||||
IsAssembly bool `json:"is_assembly"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
AllowArgs bool `json:"allow_args"`
|
||||
DefaultArgs string `json:"default_args"`
|
||||
Files []*AliasFile `json:"files"`
|
||||
IsReflective bool `json:"is_reflective"`
|
||||
IsAssembly bool `json:"is_assembly"`
|
||||
Schema *packages.OutputSchema `json:"schema"`
|
||||
|
||||
RootPath string `json:"-"`
|
||||
ArmoryName string `json:"-"`
|
||||
@@ -239,6 +241,13 @@ func ParseAliasManifest(data []byte) (*AliasManifest, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if alias.Schema != nil {
|
||||
if !packages.IsValidSchemaType(alias.Schema.Name) {
|
||||
return nil, fmt.Errorf("%s is not a valid schema type", alias.Schema.Name)
|
||||
}
|
||||
alias.Schema.IngestColumns()
|
||||
}
|
||||
|
||||
return alias, nil
|
||||
}
|
||||
|
||||
@@ -387,11 +396,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintAssemblyOutput(cmd.Name(), executeAssemblyResp, outFilePath, con)
|
||||
PrintAssemblyOutput(cmd.Name(), aliasManifest.Schema, executeAssemblyResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(executeAssemblyResp.Response)
|
||||
} else {
|
||||
PrintAssemblyOutput(cmd.Name(), executeAssemblyResp, outFilePath, con)
|
||||
PrintAssemblyOutput(cmd.Name(), aliasManifest.Schema, executeAssemblyResp, outFilePath, con)
|
||||
}
|
||||
|
||||
} else if aliasManifest.IsReflective {
|
||||
@@ -426,11 +435,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSpawnDLLOutput(cmd.Name(), spawnDllResp, outFilePath, con)
|
||||
PrintSpawnDLLOutput(cmd.Name(), aliasManifest.Schema, spawnDllResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(spawnDllResp.Response)
|
||||
} else {
|
||||
PrintSpawnDLLOutput(cmd.Name(), spawnDllResp, outFilePath, con)
|
||||
PrintSpawnDLLOutput(cmd.Name(), aliasManifest.Schema, spawnDllResp, outFilePath, con)
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -466,18 +475,44 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintSideloadOutput(cmd.Name(), sideloadResp, outFilePath, con)
|
||||
PrintSideloadOutput(cmd.Name(), aliasManifest.Schema, sideloadResp, outFilePath, con)
|
||||
})
|
||||
con.PrintAsyncResponse(sideloadResp.Response)
|
||||
} else {
|
||||
PrintSideloadOutput(cmd.Name(), sideloadResp, outFilePath, con)
|
||||
PrintSideloadOutput(cmd.Name(), aliasManifest.Schema, sideloadResp, outFilePath, con)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getOutputWithSchema(schema *packages.OutputSchema, result string) string {
|
||||
if schema == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
outputSchema := packages.GetNewPackageOutput(schema.Name)
|
||||
if outputSchema == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
err := outputSchema.IngestData([]byte(result), schema.Columns(), schema.GroupBy)
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
return outputSchema.CreateTable()
|
||||
}
|
||||
|
||||
// PrintSpawnDLLOutput - Prints the output of a spawn dll command.
|
||||
func PrintSpawnDLLOutput(cmdName string, spawnDllResp *sliverpb.SpawnDll, outFilePath *os.File, con *console.SliverClient) {
|
||||
con.PrintInfof("%s output:\n%s", cmdName, spawnDllResp.GetResult())
|
||||
func PrintSpawnDLLOutput(cmdName string, schema *packages.OutputSchema, spawnDllResp *sliverpb.SpawnDll, outFilePath *os.File, con *console.SliverClient) {
|
||||
var result string
|
||||
|
||||
if schema != nil {
|
||||
result = getOutputWithSchema(schema, spawnDllResp.GetResult())
|
||||
} else {
|
||||
result = spawnDllResp.GetResult()
|
||||
}
|
||||
con.PrintInfof("%s output:\n%s", cmdName, result)
|
||||
|
||||
// Output the raw result to the file
|
||||
if outFilePath != nil {
|
||||
outFilePath.WriteString(spawnDllResp.GetResult())
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
@@ -485,8 +520,17 @@ func PrintSpawnDLLOutput(cmdName string, spawnDllResp *sliverpb.SpawnDll, outFil
|
||||
}
|
||||
|
||||
// PrintSideloadOutput - Prints the output of a sideload command.
|
||||
func PrintSideloadOutput(cmdName string, sideloadResp *sliverpb.Sideload, outFilePath *os.File, con *console.SliverClient) {
|
||||
con.PrintInfof("%s output:\n%s", cmdName, sideloadResp.GetResult())
|
||||
func PrintSideloadOutput(cmdName string, schema *packages.OutputSchema, sideloadResp *sliverpb.Sideload, outFilePath *os.File, con *console.SliverClient) {
|
||||
var result string
|
||||
|
||||
if schema != nil {
|
||||
result = getOutputWithSchema(schema, sideloadResp.GetResult())
|
||||
} else {
|
||||
result = sideloadResp.GetResult()
|
||||
}
|
||||
con.PrintInfof("%s output:\n%s", cmdName, result)
|
||||
|
||||
// Output the raw result to the file
|
||||
if outFilePath != nil {
|
||||
outFilePath.WriteString(sideloadResp.GetResult())
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
@@ -494,8 +538,17 @@ func PrintSideloadOutput(cmdName string, sideloadResp *sliverpb.Sideload, outFil
|
||||
}
|
||||
|
||||
// PrintAssemblyOutput - Prints the output of an execute-assembly command.
|
||||
func PrintAssemblyOutput(cmdName string, execAsmResp *sliverpb.ExecuteAssembly, outFilePath *os.File, con *console.SliverClient) {
|
||||
con.PrintInfof("%s output:\n%s", cmdName, string(execAsmResp.GetOutput()))
|
||||
func PrintAssemblyOutput(cmdName string, schema *packages.OutputSchema, execAsmResp *sliverpb.ExecuteAssembly, outFilePath *os.File, con *console.SliverClient) {
|
||||
var result string
|
||||
|
||||
if schema != nil {
|
||||
result = getOutputWithSchema(schema, string(execAsmResp.GetOutput()))
|
||||
} else {
|
||||
result = string(execAsmResp.GetOutput())
|
||||
}
|
||||
con.PrintInfof("%s output:\n%s", cmdName, result)
|
||||
|
||||
// Output the raw result to the file
|
||||
if outFilePath != nil {
|
||||
outFilePath.Write(execAsmResp.GetOutput())
|
||||
con.PrintInfof("Output saved to %s\n", outFilePath.Name())
|
||||
|
||||
@@ -21,8 +21,13 @@ package c2profiles
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
@@ -154,7 +159,13 @@ func ExportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
||||
return
|
||||
}
|
||||
|
||||
jsonProfile, err := C2ConfigToJSON(profileName, profile)
|
||||
config, err := C2ConfigToJSON(profileName, profile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonProfile, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
@@ -169,8 +180,96 @@ func ExportC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
||||
con.Println(profileName, "C2 profile exported to ", filepath)
|
||||
}
|
||||
|
||||
func GenerateC2ProfileCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
|
||||
// load template to use as starting point
|
||||
template, err := cmd.Flags().GetString("template")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
profileName, _ := cmd.Flags().GetString("name")
|
||||
if profileName == "" {
|
||||
con.PrintErrorf("Invalid c2 profile name\n")
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := con.Rpc.GetHTTPC2ProfileByName(context.Background(), &clientpb.C2ProfileReq{Name: template})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
c2Profiles, err := con.Rpc.GetHTTPC2Profiles(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var extensions []string
|
||||
for _, c2profile := range c2Profiles.Configs {
|
||||
confProfile, err := con.Rpc.GetHTTPC2ProfileByName(context.Background(), &clientpb.C2ProfileReq{Name: c2profile.Name})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
extensions = append(extensions, confProfile.ImplantConfig.StagerFileExtension)
|
||||
extensions = append(extensions, confProfile.ImplantConfig.StartSessionFileExtension)
|
||||
}
|
||||
|
||||
config, err := C2ConfigToJSON(profileName, profile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// read urls files and replace segments
|
||||
filepath, err := cmd.Flags().GetString("file")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
urlsFile, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
fileContent, err := io.ReadAll(urlsFile)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
urls := strings.Split(string(fileContent), "\n")
|
||||
|
||||
jsonProfile, err := updateC2Profile(extensions, config, urls)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// save or display config
|
||||
importC2Profile, err := cmd.Flags().GetBool("import")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if importC2Profile {
|
||||
httpC2ConfigReq := clientpb.HTTPC2ConfigReq{C2Config: C2ConfigToProtobuf(profileName, jsonProfile)}
|
||||
_, err = con.Rpc.SaveHTTPC2Profile(context.Background(), &httpC2ConfigReq)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.Println("C2 profile generated and saved as ", profileName)
|
||||
} else {
|
||||
PrintC2Profiles(profile, con)
|
||||
}
|
||||
}
|
||||
|
||||
// convert protobuf to json
|
||||
func C2ConfigToJSON(profileName string, profile *clientpb.HTTPC2Config) ([]byte, error) {
|
||||
func C2ConfigToJSON(profileName string, profile *clientpb.HTTPC2Config) (*assets.HTTPC2Config, error) {
|
||||
implantConfig := assets.HTTPC2ImplantConfig{
|
||||
UserAgent: profile.ImplantConfig.UserAgent,
|
||||
ChromeBaseVersion: int(profile.ImplantConfig.ChromeBaseVersion),
|
||||
@@ -278,12 +377,7 @@ func C2ConfigToJSON(profileName string, profile *clientpb.HTTPC2Config) ([]byte,
|
||||
ServerConfig: serverConfig,
|
||||
}
|
||||
|
||||
jsonConfig, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jsonConfig, nil
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// convert json to protobuf
|
||||
@@ -598,3 +692,115 @@ func selectC2Profile(c2profiles []*clientpb.HTTPC2Config) string {
|
||||
|
||||
return c2profile
|
||||
}
|
||||
|
||||
func updateC2Profile(usedExtensions []string, template *assets.HTTPC2Config, urls []string) (*assets.HTTPC2Config, error) {
|
||||
// update the template with the urls
|
||||
|
||||
var (
|
||||
paths []string
|
||||
filenames []string
|
||||
extensions []string
|
||||
filteredExtensions []string
|
||||
)
|
||||
|
||||
for _, urlPath := range urls {
|
||||
parsedURL, err := url.Parse(urlPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing URL:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
dir, file := path.Split(parsedURL.Path)
|
||||
dir = strings.Trim(dir, "/")
|
||||
if dir != "" {
|
||||
paths = append(paths, strings.Split(dir, "/")...)
|
||||
}
|
||||
|
||||
if file != "" {
|
||||
fileName := strings.TrimSuffix(file, filepath.Ext(file))
|
||||
filenames = append(filenames, fileName)
|
||||
ext := strings.TrimPrefix(filepath.Ext(file), ".")
|
||||
if ext != "" {
|
||||
extensions = append(extensions, ext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(extensions)
|
||||
extensions = slices.Compact(extensions)
|
||||
|
||||
for _, extension := range extensions {
|
||||
if !slices.Contains(usedExtensions, extension) {
|
||||
filteredExtensions = append(filteredExtensions, extension)
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(paths)
|
||||
paths = slices.Compact(paths)
|
||||
|
||||
slices.Sort(filenames)
|
||||
filenames = slices.Compact(filenames)
|
||||
|
||||
// 5 is arbitrarily used as a minimum value, it only has to be 5 for the extensions, the others can be lower
|
||||
if len(filteredExtensions) < 5 {
|
||||
return nil, fmt.Errorf("got %d unused extensions, need at least 5", len(filteredExtensions))
|
||||
}
|
||||
|
||||
if len(paths) < 5 {
|
||||
return nil, fmt.Errorf("got %d paths need at least 5", len(paths))
|
||||
}
|
||||
|
||||
if len(filenames) < 5 {
|
||||
return nil, fmt.Errorf("got %d paths need at least 5", len(filenames))
|
||||
}
|
||||
|
||||
// shuffle extensions
|
||||
for i := len(extensions) - 1; i > 0; i-- {
|
||||
j := rand.Intn(i + 1)
|
||||
extensions[i], extensions[j] = extensions[j], extensions[i]
|
||||
}
|
||||
|
||||
template.ImplantConfig.PollFileExt = extensions[0]
|
||||
template.ImplantConfig.StagerFileExt = extensions[1]
|
||||
template.ImplantConfig.StartSessionFileExt = extensions[2]
|
||||
template.ImplantConfig.SessionFileExt = extensions[3]
|
||||
template.ImplantConfig.CloseFileExt = extensions[4]
|
||||
|
||||
// randomly distribute the paths and filenames into the different segment types
|
||||
template.ImplantConfig.CloseFiles = []string{}
|
||||
template.ImplantConfig.SessionFiles = []string{}
|
||||
template.ImplantConfig.PollFiles = []string{}
|
||||
template.ImplantConfig.StagerFiles = []string{}
|
||||
template.ImplantConfig.ClosePaths = []string{}
|
||||
template.ImplantConfig.SessionPaths = []string{}
|
||||
template.ImplantConfig.PollPaths = []string{}
|
||||
template.ImplantConfig.StagerPaths = []string{}
|
||||
|
||||
for _, path := range paths {
|
||||
switch rand.Intn(4) {
|
||||
case 0:
|
||||
template.ImplantConfig.PollPaths = append(template.ImplantConfig.PollPaths, path)
|
||||
case 1:
|
||||
template.ImplantConfig.SessionPaths = append(template.ImplantConfig.SessionPaths, path)
|
||||
case 2:
|
||||
template.ImplantConfig.ClosePaths = append(template.ImplantConfig.ClosePaths, path)
|
||||
case 3:
|
||||
template.ImplantConfig.StagerPaths = append(template.ImplantConfig.StagerPaths, path)
|
||||
}
|
||||
}
|
||||
|
||||
for _, filename := range filenames {
|
||||
switch rand.Intn(4) {
|
||||
case 0:
|
||||
template.ImplantConfig.PollFiles = append(template.ImplantConfig.PollFiles, filename)
|
||||
case 1:
|
||||
template.ImplantConfig.SessionFiles = append(template.ImplantConfig.SessionFiles, filename)
|
||||
case 2:
|
||||
template.ImplantConfig.CloseFiles = append(template.ImplantConfig.CloseFiles, filename)
|
||||
case 3:
|
||||
template.ImplantConfig.StagerFiles = append(template.ImplantConfig.StagerFiles, filename)
|
||||
}
|
||||
}
|
||||
|
||||
return template, nil
|
||||
}
|
||||
@@ -39,7 +39,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind(consts.ExportC2ProfileStr, false, exportC2ProfileCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("file", "f", "", "Path to file to export C2 configuration to")
|
||||
f.StringP("name", "n", consts.DefaultC2Profile, "HTTP C2 Profile name")
|
||||
|
||||
})
|
||||
flags.BindFlagCompletions(exportC2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["name"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
|
||||
C2ProfileCmd := &cobra.Command{
|
||||
@@ -58,6 +60,28 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.BindFlagCompletions(C2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["name"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
|
||||
generateC2ProfileCmd := &cobra.Command{
|
||||
Use: consts.C2GenerateStr,
|
||||
Short: "Generate a C2 Profile from a list of urls",
|
||||
Long: help.GetHelpFor([]string{consts.C2ProfileStr + "." + consts.C2GenerateStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
GenerateC2ProfileCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
|
||||
flags.Bind(consts.GenerateStr, false, generateC2ProfileCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("file", "f", "", "Path to file containing URL list, /hello/there.txt one per line")
|
||||
f.BoolP("import", "i", false, "Import the generated profile after creation")
|
||||
f.StringP("name", "n", "", "HTTP C2 Profile name to save C2Profile as")
|
||||
f.StringP("template", "t", consts.DefaultC2Profile, "HTTP C2 Profile to use as a template for the new profile")
|
||||
})
|
||||
|
||||
flags.BindFlagCompletions(generateC2ProfileCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["template"] = generate.HTTPC2Completer(con)
|
||||
})
|
||||
|
||||
C2ProfileCmd.AddCommand(generateC2ProfileCmd)
|
||||
C2ProfileCmd.AddCommand(importC2ProfileCmd)
|
||||
C2ProfileCmd.AddCommand(exportC2ProfileCmd)
|
||||
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const (
|
||||
timeFormat = "2006-01-02 15:04:05 UTC-0700"
|
||||
)
|
||||
|
||||
// Defining the transport filters
|
||||
const (
|
||||
MTLSTransport uint32 = 1 << iota
|
||||
HTTPSTransport
|
||||
AllTransports
|
||||
)
|
||||
|
||||
// Defining the role filters
|
||||
const (
|
||||
// Provide some separation between the options so that we do not have duplicate combinations
|
||||
ServerRole uint32 = 8 << iota
|
||||
ImplantRole
|
||||
AllRoles
|
||||
)
|
||||
|
||||
func CertificateInfoCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
// Since we are sending this value in a protobuf, we will give it a fixed bit size
|
||||
// 32 is the smallest we can go
|
||||
var chosenOptions uint32
|
||||
|
||||
if cmd.Flags().Changed("mtls") {
|
||||
if cmd.Flags().Changed("https") {
|
||||
chosenOptions = AllTransports
|
||||
} else {
|
||||
chosenOptions = MTLSTransport
|
||||
}
|
||||
} else if cmd.Flags().Changed("https") {
|
||||
chosenOptions = HTTPSTransport
|
||||
} else {
|
||||
chosenOptions = AllTransports
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("server") {
|
||||
if cmd.Flags().Changed("implant") {
|
||||
chosenOptions |= AllRoles
|
||||
} else {
|
||||
chosenOptions |= ServerRole
|
||||
}
|
||||
} else if cmd.Flags().Changed("implant") {
|
||||
chosenOptions |= ImplantRole
|
||||
} else {
|
||||
chosenOptions |= AllRoles
|
||||
}
|
||||
|
||||
request := &clientpb.CertificatesReq{
|
||||
CategoryFilters: chosenOptions,
|
||||
}
|
||||
|
||||
request.CN, _ = cmd.Flags().GetString("cn")
|
||||
|
||||
// Ask the server for information about certificates
|
||||
certificateInfo, err := con.Rpc.GetCertificateInfo(context.Background(), request)
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not get certificate information from database: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
printCertificateInfo(con, certificateInfo.Info)
|
||||
}
|
||||
|
||||
func checkCertExpiry(expiryTime time.Time) string {
|
||||
if expiryTime.Before(time.Now()) || expiryTime.Equal(time.Now()) {
|
||||
return console.Bold + console.Red
|
||||
}
|
||||
|
||||
// One week is 168 hours - this is bad
|
||||
if expiryTime.Before(time.Now().Add(168 * time.Hour)) {
|
||||
return console.Bold + console.Red
|
||||
}
|
||||
|
||||
// One month is approximately 730 hours - this is a warning
|
||||
if expiryTime.Before(time.Now().Add(730 * time.Hour)) {
|
||||
return console.Bold + console.Orange
|
||||
}
|
||||
|
||||
return console.Normal
|
||||
}
|
||||
|
||||
func printCertificateInfo(con *console.SliverClient, certData []*clientpb.CertificateData) {
|
||||
// Get the terminal width
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
}
|
||||
|
||||
if len(certData) == 0 {
|
||||
con.PrintWarnf("There are no certificates in the database matching the given parameters.\n")
|
||||
return
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
wideTermWidth := con.Settings.SmallTermWidth < width
|
||||
|
||||
if wideTermWidth {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Common Name",
|
||||
"Creation Time",
|
||||
"Certificate Type",
|
||||
"Key Algorithm",
|
||||
"Validity Start",
|
||||
"Expires",
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Common Name",
|
||||
"Expires",
|
||||
})
|
||||
}
|
||||
|
||||
for _, cert := range certData {
|
||||
rowColor := console.Normal
|
||||
|
||||
expiry, err := time.Parse(timeFormat, cert.ValidityExpiry)
|
||||
// This should not error out, but if it does, the row will not be colored
|
||||
if err == nil {
|
||||
rowColor = checkCertExpiry(expiry)
|
||||
}
|
||||
if wideTermWidth {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CreationTime),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.Type),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.KeyAlgorithm),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityStart),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, cert.ValidityExpiry),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tw.SortBy([]table.SortBy{{Name: "Expires", Mode: table.Dsc}})
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package certificates
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
certificatesCmd := &cobra.Command{
|
||||
Use: consts.CertificatesStr,
|
||||
Short: "Certificate management",
|
||||
Long: help.GetHelpFor([]string{consts.CertificatesStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CertificateInfoCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.GenericHelpGroup,
|
||||
}
|
||||
flags.Bind(consts.CertificatesStr, false, certificatesCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("mtls", "m", false, "Show MTLS certificates")
|
||||
f.BoolP("https", "p", false, "Show HTTPS certificates")
|
||||
f.BoolP("implant", "i", false, "Show implant certificates")
|
||||
f.BoolP("server", "s", false, "Show server certificates")
|
||||
f.StringP("cn", "c", "", "Show certificate information for a provided common name")
|
||||
})
|
||||
|
||||
return []*cobra.Command{
|
||||
certificatesCmd,
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/client/packages"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
@@ -92,13 +93,15 @@ type ExtensionManifest struct {
|
||||
}
|
||||
|
||||
type ExtCommand struct {
|
||||
CommandName string `json:"command_name"`
|
||||
Help string `json:"help"`
|
||||
LongHelp string `json:"long_help"`
|
||||
Files []*extensionFile `json:"files"`
|
||||
Arguments []*extensionArgument `json:"arguments"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
DependsOn string `json:"depends_on"`
|
||||
CommandName string `json:"command_name"`
|
||||
Help string `json:"help"`
|
||||
LongHelp string `json:"long_help"`
|
||||
Files []*extensionFile `json:"files"`
|
||||
Arguments []*extensionArgument `json:"arguments"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
DependsOn string `json:"depends_on"`
|
||||
Init string `json:"init"`
|
||||
Schema *packages.OutputSchema `json:"schema"`
|
||||
|
||||
Manifest *ExtensionManifest
|
||||
}
|
||||
@@ -200,6 +203,7 @@ func convertOldManifest(old *ExtensionManifest_) *ExtensionManifest {
|
||||
Entrypoint: old.Entrypoint,
|
||||
Files: old.Files,
|
||||
Arguments: old.Arguments,
|
||||
Schema: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -227,9 +231,13 @@ func ParseExtensionManifest(data []byte) (*ExtensionManifest, error) {
|
||||
//yes, ok, lets jigger it to a new manifest
|
||||
extManifest = convertOldManifest(oldmanifest)
|
||||
}
|
||||
//pass ref to manifest to each command
|
||||
//pass ref to manifest to each command and initialize output schema if applicable
|
||||
for i := range extManifest.ExtCommand {
|
||||
extManifest.ExtCommand[i].Manifest = extManifest
|
||||
command := extManifest.ExtCommand[i]
|
||||
command.Manifest = extManifest
|
||||
if command.Schema != nil {
|
||||
command.Schema.IngestColumns()
|
||||
}
|
||||
}
|
||||
return extManifest, validManifest(extManifest)
|
||||
}
|
||||
@@ -262,6 +270,11 @@ func validManifest(manifest *ExtensionManifest) error {
|
||||
if extManifest.Help == "" {
|
||||
return errors.New("missing `help` field in extension manifest")
|
||||
}
|
||||
if extManifest.Schema != nil {
|
||||
if !packages.IsValidSchemaType(extManifest.Schema.Name) {
|
||||
return fmt.Errorf("%s is not a valid schema type", extManifest.Schema.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -371,38 +384,50 @@ func loadExtension(goos string, goarch string, checkCache bool, ext *ExtCommand,
|
||||
}
|
||||
extensionList = extList.Names
|
||||
}
|
||||
depLoaded := false
|
||||
//extensionList contains all *implant* loaded extensions. Is a sha256 sum of the relevant file.
|
||||
|
||||
//get the file hash to compare against implant extensions later
|
||||
toberunfilepath, err := ext.getFileForTarget(goos, goarch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//we need to check the dependent if it's a bof
|
||||
if ext.DependsOn != "" {
|
||||
//verify extension is in loaded list
|
||||
if extension, found := loadedExtensions[ext.DependsOn]; found {
|
||||
toberunfilepath, err = extension.getFileForTarget(goos, goarch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// handle error
|
||||
return fmt.Errorf("attempted to load non-existing extension: %s", ext.DependsOn)
|
||||
}
|
||||
}
|
||||
|
||||
//todo, maybe cache these values somewhere
|
||||
toberunfiledata, err := os.ReadFile(toberunfilepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(toberunfiledata) == 0 {
|
||||
//read an empty file, bail out
|
||||
return errors.New("read empty extension file content")
|
||||
}
|
||||
bd := sha256.Sum256(toberunfiledata)
|
||||
toberunhash := hex.EncodeToString(bd[:])
|
||||
|
||||
for _, extName := range extensionList {
|
||||
if !depLoaded {
|
||||
if extN, ok := loadedExtensions[cmd.Name()]; ok && extName == extN.DependsOn {
|
||||
depLoaded = true
|
||||
}
|
||||
}
|
||||
//check if the file suffixes are the same (should only be relevant to the same extension package), and don't load the file if it's already there
|
||||
extN := loadedExtensions[cmd.Name()]
|
||||
if extN != nil {
|
||||
//confirm we haven't already loaded the file
|
||||
for _, extf := range extN.Files {
|
||||
if strings.HasSuffix(binPath, extf.Path) {
|
||||
//file already exists, no need to load it
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if extN, ok := loadedExtensions[cmd.Name()]; ok && extN.CommandName == extName {
|
||||
//check if extension we are trying to run (ext.CommandName or ext.DependsOn, for bofs) is already loaded
|
||||
if extName == toberunhash {
|
||||
//exists on the other side, exit early
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Extension not found, let's load it
|
||||
if filepath.Ext(binPath) == ".o" {
|
||||
// BOFs are not loaded by the DLL loader, but we make sure the loader itself is loaded
|
||||
// Auto load the coff loader if we have it
|
||||
if !depLoaded {
|
||||
if errLoad := loadDep(goos, goarch, ext.DependsOn, cmd, con); errLoad != nil {
|
||||
return errLoad
|
||||
}
|
||||
}
|
||||
return nil
|
||||
// BOFs are not loaded by the DLL loader, but we need to load the loader (more load more good)
|
||||
if ext.DependsOn != "" {
|
||||
return loadDep(goos, goarch, ext.DependsOn, cmd, con)
|
||||
}
|
||||
binData, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
@@ -414,17 +439,45 @@ func loadExtension(goos string, goarch string, checkCache bool, ext *ExtCommand,
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerExtension(goos string, _ *ExtCommand, binData []byte, cmd *cobra.Command, con *console.SliverClient) error {
|
||||
func registerExtension(goos string, ext *ExtCommand, binData []byte, cmd *cobra.Command, con *console.SliverClient) error {
|
||||
//set extension name to a hash of the data to avoid loading more than one instance
|
||||
bd := sha256.Sum256(binData)
|
||||
name := hex.EncodeToString(bd[:])
|
||||
sess, beac := con.ActiveTarget.GetInteractive()
|
||||
ctrl := make(chan bool)
|
||||
//first time run of an extension will require some waiting depending on the size
|
||||
if sess != nil {
|
||||
msg := fmt.Sprintf("Sending %s to implant ...", ext.CommandName)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
}
|
||||
//don't block if we are in beacon mode
|
||||
if beac != nil && sess == nil {
|
||||
go func() {
|
||||
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
|
||||
Name: name,
|
||||
Data: binData,
|
||||
OS: goos,
|
||||
Init: ext.Init,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error registering extension: %s\n", err)
|
||||
}
|
||||
if registerResp.Response != nil && registerResp.Response.Err != "" {
|
||||
con.PrintErrorf("Error registering extension: %s\n", errors.New(registerResp.Response.Err))
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
//session mode (hopefully)
|
||||
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
|
||||
Name: name,
|
||||
Data: binData,
|
||||
OS: goos,
|
||||
//Init: ext.Init, ?
|
||||
Name: name,
|
||||
Data: binData,
|
||||
OS: goos,
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -525,6 +578,33 @@ func runExtensionCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
}
|
||||
bd := sha256.Sum256(extdata)
|
||||
name := hex.EncodeToString(bd[:])
|
||||
if isBOF {
|
||||
//if we are using a bof, we are actually calling the coffloader extension - so get the file from dep ref and use that shasum
|
||||
|
||||
if extension, found := loadedExtensions[ext.DependsOn]; found {
|
||||
dep, err := extension.getFileForTarget(goos, goarch)
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not get file for extension %s", ext.DependsOn)
|
||||
return
|
||||
}
|
||||
depdata, err := os.ReadFile(dep)
|
||||
if err != nil {
|
||||
con.PrintErrorf("dep read file error: %s\n", err)
|
||||
return
|
||||
}
|
||||
if len(depdata) == 0 {
|
||||
con.PrintErrorf("read empty file: %s\n", dep)
|
||||
return
|
||||
}
|
||||
bd = sha256.Sum256(depdata)
|
||||
name = hex.EncodeToString(bd[:])
|
||||
} else {
|
||||
// handle error
|
||||
con.PrintErrorf("attempted to load non-existing extension: %s", ext.DependsOn)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
callExtResp, err := con.Rpc.CallExtension(context.Background(), &sliverpb.CallExtensionReq{
|
||||
Name: name,
|
||||
Export: entryPoint,
|
||||
@@ -545,23 +625,38 @@ func runExtensionCmd(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
con.PrintErrorf("Failed to decode call ext response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExtOutput(extName, ext.CommandName, callExtResp, con)
|
||||
PrintExtOutput(extName, ext.CommandName, ext.Schema, callExtResp, con)
|
||||
})
|
||||
con.PrintAsyncResponse(callExtResp.Response)
|
||||
} else {
|
||||
PrintExtOutput(extName, ext.CommandName, callExtResp, con)
|
||||
PrintExtOutput(extName, ext.CommandName, ext.Schema, callExtResp, con)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintExtOutput - Print the ext execution output.
|
||||
func PrintExtOutput(extName string, commandName string, callExtension *sliverpb.CallExtension, con *console.SliverClient) {
|
||||
func PrintExtOutput(extName string, commandName string, outputSchema *packages.OutputSchema, callExtension *sliverpb.CallExtension, con *console.SliverClient) {
|
||||
if extName == commandName {
|
||||
con.PrintInfof("Successfully executed %s", extName)
|
||||
con.PrintInfof("Successfully executed %s\n", extName)
|
||||
} else {
|
||||
con.PrintInfof("Successfully executed %s (%s)", commandName, extName)
|
||||
con.PrintInfof("Successfully executed %s (%s)\n", commandName, extName)
|
||||
}
|
||||
if 0 < len(string(callExtension.Output)) {
|
||||
con.PrintInfof("Got output:\n%s", callExtension.Output)
|
||||
if outputSchema == nil {
|
||||
con.PrintInfof("Got output:\n%s", callExtension.Output)
|
||||
} else {
|
||||
// Get output schema
|
||||
schema := packages.GetNewPackageOutput(outputSchema.Name)
|
||||
if schema != nil {
|
||||
ingestErr := schema.IngestData(callExtension.Output, outputSchema.Columns(), outputSchema.GroupBy)
|
||||
if ingestErr != nil {
|
||||
con.PrintInfof("Got output:\n%s", callExtension.Output)
|
||||
} else {
|
||||
con.Printf("%s\n", schema.CreateTable())
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("Got output:\n%s", callExtension.Output)
|
||||
}
|
||||
}
|
||||
}
|
||||
if callExtension.Response != nil && callExtension.Response.Err != "" {
|
||||
con.PrintErrorf(callExtension.Response.Err)
|
||||
@@ -579,7 +674,7 @@ func getExtArgs(_ *cobra.Command, args []string, _ string, ext *ExtCommand) ([]b
|
||||
missingRequiredArgs := make([]string, 0)
|
||||
|
||||
// If we have an extension that expects a single string, but more than one has been parsed, combine them
|
||||
if len(ext.Arguments) == 1 && strings.Contains(ext.Arguments[0].Type, "string") {
|
||||
if len(ext.Arguments) == 1 && strings.Contains(ext.Arguments[0].Type, "string") && len(args) > 0 {
|
||||
// The loop below will only read the first element of args because ext.Arguments is 1
|
||||
args[0] = strings.Join(args, " ")
|
||||
}
|
||||
|
||||
@@ -184,6 +184,28 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
profilesCmd.AddCommand(profilesNewCmd)
|
||||
|
||||
profilesStageCmd := &cobra.Command{
|
||||
Use: consts.StageStr,
|
||||
Short: "Generate implant from a profile and encode or encrypt it",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Long: help.GetHelpFor([]string{consts.ProfilesStr, consts.StageStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ProfilesStageCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("profiles", false, profilesStageCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("save", "s", "", "directory/file to the binary to")
|
||||
f.StringP("name", "n", "", "Implant name")
|
||||
f.StringP("aes-encrypt-key", "k", "", "AES Encryption Key")
|
||||
f.StringP("aes-encrypt-iv", "i", "", "AES Encryption IV")
|
||||
f.StringP("rc4-encrypt-key", "r", "", "RC4 encryption key")
|
||||
f.BoolP("prepend-size", "p", false, "Prepend stage size")
|
||||
f.StringP("compress", "c", "", "Compress stage (zlib, gzip, deflate9 or deflate)")
|
||||
})
|
||||
|
||||
carapace.Gen(profilesStageCmd).PositionalCompletion(ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesStageCmd)
|
||||
|
||||
// Session flags and completions.
|
||||
coreImplantFlags("session", profilesNewCmd)
|
||||
compileImplantFlags("session", profilesNewCmd)
|
||||
|
||||
@@ -108,6 +108,7 @@ var (
|
||||
// Profiles
|
||||
consts.ProfilesStr + sep + consts.NewStr: newProfileHelp,
|
||||
consts.ProfilesStr + sep + consts.GenerateStr: generateProfileHelp,
|
||||
consts.ProfilesStr + sep + consts.StageStr: generateProfileStageHelp,
|
||||
|
||||
// Reactions
|
||||
consts.ReactionStr: reactionHelp,
|
||||
@@ -121,6 +122,7 @@ var (
|
||||
|
||||
// HTTP C2
|
||||
consts.C2ProfileStr: c2ProfilesHelp,
|
||||
consts.C2ProfileStr + sep + consts.C2GenerateStr: c2GenerateHelp,
|
||||
}
|
||||
|
||||
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||
@@ -249,6 +251,9 @@ settings. Generated implants will still have per-binary certificates/obfuscation
|
||||
generateProfileHelp = `[[.Bold]]Command:[[.Normal]] generate [name] <options>
|
||||
[[.Bold]]About:[[.Normal]] Generate an implant from a saved profile (see 'profiles new --help').`
|
||||
|
||||
generateProfileStageHelp = `[[.Bold]]Command:[[.Normal]] stage [name] <options>
|
||||
[[.Bold]]About:[[.Normal]] Generate and encrypt or encode an implant from a saved profile (see 'profiles stage --help').`
|
||||
|
||||
msfHelp = `[[.Bold]]Command:[[.Normal]] msf [--lhost] <options>
|
||||
[[.Bold]]About:[[.Normal]] Execute a metasploit payload in the current process.`
|
||||
|
||||
@@ -1292,6 +1297,10 @@ Sliver uses the same hash identifiers as Hashcat (use the #):
|
||||
C2ProfileImportStr = `[[.Bold]]Command:[[.Normal]] Import
|
||||
[[.Bold]]About:[[.Normal]] Load custom HTTP C2 profiles.
|
||||
`
|
||||
c2GenerateHelp = `[[.Bold]]Command:[[.Normal]] C2 Profile generate
|
||||
[[.Bold]]About:[[.Normal]] Generate C2 profile using a file containing urls.
|
||||
Optionaly import profile or use another profile as a base template for the new profile.
|
||||
`
|
||||
|
||||
grepHelp = `[[.Bold]]Command:[[.Normal]] grep [flags / options] <search pattern> <path>
|
||||
[[.Bold]]About:[[.Normal]] Search a file or path for a search pattern
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/beacons"
|
||||
"github.com/bishopfox/sliver/client/command/builders"
|
||||
"github.com/bishopfox/sliver/client/command/c2profiles"
|
||||
"github.com/bishopfox/sliver/client/command/certificates"
|
||||
"github.com/bishopfox/sliver/client/command/crack"
|
||||
"github.com/bishopfox/sliver/client/command/creds"
|
||||
"github.com/bishopfox/sliver/client/command/exit"
|
||||
@@ -93,6 +94,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
|
||||
operators.Commands,
|
||||
creds.Commands,
|
||||
crack.Commands,
|
||||
certificates.Commands,
|
||||
)
|
||||
|
||||
// C2 Network
|
||||
|
||||
@@ -227,7 +227,7 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
con.PrintErrorf("Failed to decode task response: %s\n", err)
|
||||
return
|
||||
}
|
||||
extensions.PrintExtOutput("", "", callExtension, con)
|
||||
extensions.PrintExtOutput("", "", nil, callExtension, con)
|
||||
|
||||
// ---------------------
|
||||
// Exec commands
|
||||
|
||||
@@ -264,11 +264,13 @@ func downloadAsset(client *http.Client, asset *version.Asset, saveTo string) err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer writer.Close()
|
||||
|
||||
resp, err := client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
bar := pb.Full.Start64(limit)
|
||||
barReader := bar.NewProxyReader(resp.Body)
|
||||
|
||||
@@ -88,7 +88,7 @@ func WGConfigCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
|
||||
save, _ := cmd.Flags().GetString("save")
|
||||
if save == "" {
|
||||
con.PrintInfof("New client config:")
|
||||
con.PrintInfof("New client config:\n")
|
||||
con.Println(output.String())
|
||||
} else {
|
||||
if !strings.HasSuffix(save, ".conf") {
|
||||
|
||||
@@ -133,6 +133,7 @@ const (
|
||||
TasksStr = "tasks"
|
||||
CancelStr = "cancel"
|
||||
GenerateStr = "generate"
|
||||
C2GenerateStr = "generate"
|
||||
RegenerateStr = "regenerate"
|
||||
CompilerInfoStr = "info"
|
||||
MsfStagerStr = "msf-stager"
|
||||
@@ -146,6 +147,7 @@ const (
|
||||
C2ProfileStr = "c2profiles"
|
||||
ImportC2ProfileStr = "import"
|
||||
ExportC2ProfileStr = "export"
|
||||
CertificatesStr = "certificates"
|
||||
|
||||
// Generic.
|
||||
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const (
|
||||
KeyValueSchemaName = "key-value"
|
||||
GroupedKeyValueSchemaName = "grouped-key-value"
|
||||
)
|
||||
|
||||
var validSchemas []string = []string{KeyValueSchemaName, GroupedKeyValueSchemaName}
|
||||
|
||||
type OutputSchema struct {
|
||||
Name string `json:"name"`
|
||||
RawColumns string `json:"columns"`
|
||||
GroupBy string `json:"group_by"`
|
||||
cols []string `json:"-"`
|
||||
}
|
||||
|
||||
func (schema *OutputSchema) IngestColumns() {
|
||||
schema.cols = strings.Split(schema.RawColumns, ",")
|
||||
}
|
||||
|
||||
func (schema *OutputSchema) Columns() []string {
|
||||
return schema.cols
|
||||
}
|
||||
|
||||
type PackageOutput interface {
|
||||
// The names of columns followed by any other strings needed, like the data itself
|
||||
// Each type that implements this interface will have to make sure the correct number
|
||||
// of strings are provided
|
||||
IngestData([]byte, []string, ...string) error
|
||||
// Renders the data into a table
|
||||
CreateTable() string
|
||||
Name() string
|
||||
}
|
||||
|
||||
func IsValidSchemaType(name string) bool {
|
||||
return slices.Contains(validSchemas, name)
|
||||
}
|
||||
|
||||
func GetNewPackageOutput(name string) PackageOutput {
|
||||
switch name {
|
||||
case KeyValueSchemaName:
|
||||
return &KeyValuePairs{}
|
||||
case GroupedKeyValueSchemaName:
|
||||
return &GroupedKeyValuePairs{}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
This output schema is suitable for data that is an array of key-value pairs
|
||||
*/
|
||||
type KeyValueData map[string]string
|
||||
type KeyValuePairs struct {
|
||||
columnOrder []string
|
||||
data []KeyValueData
|
||||
// This field is optional
|
||||
columnToGroupBy string
|
||||
}
|
||||
|
||||
/*
|
||||
This output schema is suitable for one or more key-values pairs grouped
|
||||
by a group name. This schema assumes the data is already grouped and
|
||||
does not perform the grouping itself.
|
||||
*/
|
||||
type GroupedKeyValuePairs struct {
|
||||
groupedBy string
|
||||
columnOrder []string
|
||||
data map[string][]KeyValueData
|
||||
}
|
||||
|
||||
func (kvd *KeyValueData) UnmarshalJSON(data []byte) error {
|
||||
var raw map[string]interface{}
|
||||
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*kvd = make(KeyValueData, len(raw))
|
||||
for key, value := range raw {
|
||||
key = strings.ToLower(key)
|
||||
switch v := value.(type) {
|
||||
case float64:
|
||||
(*kvd)[key] = strconv.FormatFloat(v, 'f', -1, 64)
|
||||
case string:
|
||||
// Newlines and extra whitespace mess up the table
|
||||
(*kvd)[key] = strings.TrimSpace(v)
|
||||
case bool:
|
||||
(*kvd)[key] = strconv.FormatBool(v)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type %T", v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kvp *KeyValuePairs) IngestData(data []byte, order []string, arguments ...string) error {
|
||||
if len(arguments) > 1 {
|
||||
// Only an optional a column to group by
|
||||
return fmt.Errorf("unexpected number of arguments - expected only an optional column name to group by")
|
||||
}
|
||||
groupByColumn := ""
|
||||
if len(arguments) == 1 {
|
||||
groupByColumn = arguments[0]
|
||||
}
|
||||
err := json.Unmarshal(data, &kvp.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(kvp.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the wrong columns were provided, there will be empty cells
|
||||
kvp.columnOrder = order
|
||||
// If an invalid column name is supplied, it will have no effect
|
||||
kvp.columnToGroupBy = groupByColumn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kvp *KeyValuePairs) CreateTable() string {
|
||||
if len(kvp.data) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
t := table.NewWriter()
|
||||
|
||||
header := table.Row{}
|
||||
for _, column := range kvp.columnOrder {
|
||||
header = append(header, column)
|
||||
}
|
||||
|
||||
t.AppendHeader(header)
|
||||
for _, dataRow := range kvp.data {
|
||||
tableRow := table.Row{}
|
||||
|
||||
for _, column := range kvp.columnOrder {
|
||||
column = strings.ToLower(column)
|
||||
tableRow = append(tableRow, dataRow[column])
|
||||
}
|
||||
t.AppendRow(tableRow)
|
||||
}
|
||||
|
||||
t.SetColumnConfigs([]table.ColumnConfig{{Name: kvp.columnToGroupBy, AutoMerge: true}})
|
||||
if kvp.columnToGroupBy != "" {
|
||||
t.SortBy([]table.SortBy{{Name: kvp.columnToGroupBy, Mode: table.Asc}})
|
||||
} else {
|
||||
t.SortBy([]table.SortBy{{Number: 1, Mode: table.Asc}})
|
||||
}
|
||||
|
||||
return t.Render()
|
||||
}
|
||||
|
||||
func (kvp *KeyValuePairs) Name() string {
|
||||
return KeyValueSchemaName
|
||||
}
|
||||
|
||||
func (gkvp *GroupedKeyValuePairs) IngestData(data []byte, order []string, arguments ...string) error {
|
||||
if len(arguments) != 1 {
|
||||
return fmt.Errorf("unexpected number of arguments - a grouped by column name")
|
||||
}
|
||||
|
||||
groupedBy := arguments[0]
|
||||
|
||||
err := json.Unmarshal(data, &gkvp.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(gkvp.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the wrong columns were provided, there will be empty cells
|
||||
gkvp.columnOrder = order
|
||||
// If an invalid grouped by column is provided, the table will not render correctly,
|
||||
// but there will not be any panics
|
||||
gkvp.groupedBy = groupedBy
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gkvp *GroupedKeyValuePairs) CreateTable() string {
|
||||
if len(gkvp.data) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
t := table.NewWriter()
|
||||
|
||||
header := table.Row{}
|
||||
for _, column := range gkvp.columnOrder {
|
||||
header = append(header, column)
|
||||
}
|
||||
|
||||
t.AppendHeader(header)
|
||||
groupColumn := strings.ToLower(gkvp.groupedBy)
|
||||
for groupName, groupData := range gkvp.data {
|
||||
for _, group := range groupData {
|
||||
tableRow := table.Row{}
|
||||
for _, column := range gkvp.columnOrder {
|
||||
column = strings.ToLower(column)
|
||||
if column == groupColumn {
|
||||
tableRow = append(tableRow, groupName)
|
||||
} else {
|
||||
tableRow = append(tableRow, group[column])
|
||||
}
|
||||
}
|
||||
t.AppendRow(tableRow)
|
||||
}
|
||||
}
|
||||
|
||||
t.SetColumnConfigs([]table.ColumnConfig{{Name: gkvp.groupedBy, AutoMerge: true}})
|
||||
|
||||
if gkvp.groupedBy != "" {
|
||||
t.SortBy([]table.SortBy{{Name: gkvp.groupedBy, Mode: table.Asc}})
|
||||
} else {
|
||||
t.SortBy([]table.SortBy{{Number: 1, Mode: table.Asc}})
|
||||
}
|
||||
|
||||
return t.Render()
|
||||
}
|
||||
|
||||
func (gkvp *GroupedKeyValuePairs) Name() string {
|
||||
return GroupedKeyValueSchemaName
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { useRouter } from "next/router";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import AsciinemaPlayer from "./asciinema";
|
||||
import Youtube from "./youtube";
|
||||
|
||||
export type MarkdownProps = {
|
||||
key?: string;
|
||||
@@ -131,6 +132,11 @@ const MarkdownViewer = (props: MarkdownProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (lang === "youtube") {
|
||||
const embedId = (children as string) || "";
|
||||
return <Youtube embedId={embedId.trim()} />;
|
||||
}
|
||||
|
||||
if (lang === "asciinema") {
|
||||
const asciiCast: MarkdownAsciiCast = JSON.parse(
|
||||
children as string
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
export type YoutubeProps = {
|
||||
className?: string;
|
||||
|
||||
width?: number;
|
||||
height?: number;
|
||||
|
||||
embedId: string;
|
||||
};
|
||||
|
||||
export default function Youtube(props: YoutubeProps) {
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<iframe
|
||||
width={`${props.width ? props.width : 640}`}
|
||||
height={`${props.height ? props.height : 360}`}
|
||||
src={`https://youtu.be/embed/${props.embedId}`}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import Document, { Head, Html, Main, NextScript } from "next/document";
|
||||
import React from "react";
|
||||
|
||||
// This generates the https: and wss: "connect-src" directives based on the above backends list so its a little easier to edit.
|
||||
const CSP = `default-src 'none'; script-src 'self' 'unsafe-eval'; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; connect-src 'self'`;
|
||||
const CSP = `default-src 'none'; script-src 'self' 'unsafe-eval'; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; frame-src https://youtu.be; connect-src 'self'`;
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps(ctx: any) {
|
||||
@@ -17,7 +17,6 @@ class MyDocument extends Document {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<title>Sliver Docs</title>
|
||||
<meta httpEquiv="Content-Security-Policy" content={CSP} />
|
||||
</Head>
|
||||
<body>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
### Videos
|
||||
|
||||
- https://youtu.be/3R6WKUgN0K4?t=456
|
||||
- https://www.youtube.com/watch?v=QO_1UMaiWHk
|
||||
- https://www.youtube.com/watch?v=izMMmOaLn9g
|
||||
- https://www.youtube.com/watch?v=qIbrozlf2wM
|
||||
- https://www.youtube.com/watch?v=CKfjLnEMfvI
|
||||
- https://youtu.be/watch?v=QO_1UMaiWHk
|
||||
- https://youtu.be/watch?v=izMMmOaLn9g
|
||||
- https://youtu.be/watch?v=qIbrozlf2wM
|
||||
- https://youtu.be/watch?v=CKfjLnEMfvI
|
||||
|
||||
### Blogs
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ For a Linux server, you can also use the one liner installation `curl https://sl
|
||||
{"src": "/asciinema/install-1.cast", "cols": "132"}
|
||||
```
|
||||
|
||||
If you install Sliver via the one liner, you can check that the server service is running using `systemctl status sliver`. Note that the Sliver service is not configured to start automatically on boot by default (i.e., if you reboot the server you'll need to start the service again using `systemctrl start sliver`):
|
||||
If you install Sliver via the one liner, you can check that the server service is running using `systemctl status sliver`. Note that the Sliver service is not configured to start automatically on boot by default (i.e., if you reboot the server you'll need to start the service again using `systemctl start sliver`):
|
||||
|
||||
```asciinema
|
||||
{"src": "/asciinema/service-status-1.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}
|
||||
|
||||
@@ -22,8 +22,8 @@ elif command -v yum &> /dev/null; then # Redhat-based OS (Fedora, CentOS, RHEL)
|
||||
INSTALLER=(yum -y)
|
||||
elif command -v pacman &>/dev/null; then # Arch-based (Manjaro, Garuda, Blackarch)
|
||||
echo "Installing dependencies using pacman..."
|
||||
pacman -S mingw-w64-gcc mingw-w64-binutils mingw-w64-headers
|
||||
INSTALLER=(pacman -S)
|
||||
pacman --noconfirm -S mingw-w64-gcc mingw-w64-binutils mingw-w64-headers
|
||||
INSTALLER=(pacman --noconfirm -S)
|
||||
else
|
||||
echo "Unsupported OS, exiting"
|
||||
exit
|
||||
|
||||
@@ -15,17 +15,17 @@ require (
|
||||
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0
|
||||
github.com/Ne0nd0g/go-clr v1.0.3
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/cheggaaa/pb/v3 v3.1.2
|
||||
github.com/cheggaaa/pb/v3 v3.1.5
|
||||
github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071
|
||||
github.com/chromedp/chromedp v0.9.5
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/glebarez/sqlite v1.10.0
|
||||
github.com/fatih/color v1.17.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.8
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
|
||||
github.com/klauspost/compress v1.17.8
|
||||
github.com/lesnuages/go-winio v0.4.19
|
||||
@@ -34,36 +34,36 @@ require (
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/moloch--/asciicast v0.1.0
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e
|
||||
github.com/ncruces/go-sqlite3 v0.7.2
|
||||
github.com/ncruces/go-sqlite3 v0.15.0
|
||||
github.com/reeflective/console v0.1.15
|
||||
github.com/reeflective/readline v1.0.14
|
||||
github.com/rsteube/carapace v0.46.3-0.20231214181515-27e49f3c3b69
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tetratelabs/wazero v1.7.1
|
||||
github.com/things-go/go-socks5 v0.0.5
|
||||
github.com/ulikunitz/xz v0.5.12
|
||||
github.com/xlab/treeprint v1.2.0
|
||||
github.com/yiya1989/sshkrb5 v0.0.1
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/term v0.19.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/term v0.20.0
|
||||
golang.org/x/text v0.15.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
google.golang.org/grpc v1.63.2
|
||||
google.golang.org/protobuf v1.34.0
|
||||
google.golang.org/protobuf v1.34.1
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8
|
||||
gorm.io/driver/mysql v1.5.6
|
||||
gorm.io/driver/postgres v1.5.7
|
||||
gorm.io/driver/sqlite v1.5.5
|
||||
gorm.io/gorm v1.25.10
|
||||
gvisor.dev/gvisor v0.0.0-20240306221502-ee1e1f6070e3
|
||||
modernc.org/sqlite v1.29.8
|
||||
modernc.org/sqlite v1.29.9
|
||||
tailscale.com v1.64.2
|
||||
)
|
||||
|
||||
@@ -150,7 +150,7 @@ require (
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/ncruces/julianday v0.1.5 // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
|
||||
@@ -86,8 +86,8 @@ github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx
|
||||
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cheggaaa/pb/v3 v3.1.2 h1:FIxT3ZjOj9XJl0U4o2XbEhjFfZl7jCVCDOGq1ZAB7wQ=
|
||||
github.com/cheggaaa/pb/v3 v3.1.2/go.mod h1:SNjnd0yKcW+kw0brSusraeDd5Bf1zBfxAzTL2ss3yQ4=
|
||||
github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk=
|
||||
github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6jM60XI=
|
||||
github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071 h1:RdCf9hH3xq5vJifrjGB7zQlFkdRB3pAppcX2helDq2U=
|
||||
github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
|
||||
@@ -129,8 +129,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
|
||||
@@ -143,8 +143,8 @@ github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
|
||||
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
@@ -221,8 +221,8 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
|
||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.8 h1:8BCzJdSvUbaDuRba4YVh+SKMGcAAKdkcF3SVFbrHAtQ=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.8/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
|
||||
github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
@@ -303,12 +303,12 @@ github.com/moloch--/asciicast v0.1.0 h1:eBOJwuFKSk447s/kPs9MWsc4kAl5HmuKIDLDYD6/
|
||||
github.com/moloch--/asciicast v0.1.0/go.mod h1:OckO16UDLgxVLclrCnbocL1ix15Br/8Xv/caBoYq98o=
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxck+NqAwBx8JDlnHYm4orOQBbs4BDQ=
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
|
||||
github.com/ncruces/go-sqlite3 v0.7.2 h1:K7jU4rnUxFdUsbEL+B0Xc+VexLTEwGSO6Qh91Qh4hYc=
|
||||
github.com/ncruces/go-sqlite3 v0.7.2/go.mod h1:t3dP4AP9rJddU+ffFv0h6fWyeOCEhjxrYc1nsYG7aQI=
|
||||
github.com/ncruces/go-sqlite3 v0.15.0 h1:C+SIrcYsAIR5GUYWmCnif6x81n6BS9y75vYcQynuGNU=
|
||||
github.com/ncruces/go-sqlite3 v0.15.0/go.mod h1:kHHYmFmK4G2VFFoIovEg9BEQ8BP+D81y4ESHXnzJV/w=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ncruces/julianday v0.1.5 h1:hDJ9ejiMp3DHsoZ5KW4c1lwfMjbARS7u/gbYcd0FBZk=
|
||||
github.com/ncruces/julianday v0.1.5/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
@@ -358,8 +358,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||
@@ -426,8 +426,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
@@ -454,8 +454,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
|
||||
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
|
||||
@@ -497,18 +497,18 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -551,8 +551,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
|
||||
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8 h1:5UtTowJZTz1j7NxVzDGKTz6Lm9IWm8DDF6b7a2wq9VY=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.8/go.mod h1:CaHjv79TCgAvXMSFJSVgonHXYWxnhzI3eoHtnX5UgUo=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -614,8 +614,8 @@ modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.29.8 h1:nGKglNx9K5v0As+zF0/Gcl1kMkmaU1XynYyq92PbsC8=
|
||||
modernc.org/sqlite v1.29.8/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk=
|
||||
modernc.org/sqlite v1.29.9 h1:9RhNMklxJs+1596GNuAX+O/6040bvOwacTxuFcRuQow=
|
||||
modernc.org/sqlite v1.29.9/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -155,7 +155,7 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
path, filter := determineDirPathFilter(targetPath)
|
||||
|
||||
dir, files, err := getDirList(path)
|
||||
dir, rootDirEntry, files, err := getDirList(path)
|
||||
|
||||
// Convert directory listing to protobuf
|
||||
timezone, offset := time.Now().Zone()
|
||||
@@ -166,6 +166,19 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
dirList.Exists = false
|
||||
}
|
||||
dirList.Files = []*sliverpb.FileInfo{}
|
||||
rootDirInfo, err := rootDirEntry.Info()
|
||||
if err == nil && filter == "" {
|
||||
// We should not get an error because we created the DirEntry object from the FileInfo object
|
||||
dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
|
||||
Name: ".", // Cannot use the name from the FileInfo / DirEntry because that is the name of the directory
|
||||
Size: rootDirInfo.Size(),
|
||||
ModTime: rootDirInfo.ModTime().Unix(),
|
||||
Mode: rootDirInfo.Mode().String(),
|
||||
Uid: getUid(rootDirInfo),
|
||||
Gid: getGid(rootDirInfo),
|
||||
IsDir: rootDirInfo.IsDir(),
|
||||
})
|
||||
}
|
||||
|
||||
var match bool = false
|
||||
var linkPath string = ""
|
||||
@@ -226,19 +239,30 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func getDirList(target string) (string, []fs.DirEntry, error) {
|
||||
func getDirList(target string) (string, fs.DirEntry, []fs.DirEntry, error) {
|
||||
dir, err := filepath.Abs(target)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("dir list failed to construct path %s", err)
|
||||
// {{end}}
|
||||
return "", nil, err
|
||||
return "", nil, nil, err
|
||||
}
|
||||
if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
if rootInfo, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
/*
|
||||
We could place the entry for the directory itself
|
||||
at the beginning of the returned slice of DirEntry
|
||||
objects, but then it is not clear if that is the
|
||||
root directory or a directory / file in the root
|
||||
directory with the same name as the root directory.
|
||||
|
||||
Using WalkDir is not great here because you cannot
|
||||
tell it to not be recursive, so we will be wasting
|
||||
cycles telling it to skip directories and files
|
||||
*/
|
||||
files, err := os.ReadDir(dir)
|
||||
return dir, files, err
|
||||
return dir, fs.FileInfoToDirEntry(rootInfo), files, err
|
||||
}
|
||||
return dir, []fs.DirEntry{}, errors.New("directory does not exist")
|
||||
return dir, nil, []fs.DirEntry{}, errors.New("directory does not exist")
|
||||
}
|
||||
|
||||
func rmHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
@@ -31,8 +31,8 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/procdump"
|
||||
"github.com/bishopfox/sliver/implant/sliver/mount"
|
||||
"github.com/bishopfox/sliver/implant/sliver/procdump"
|
||||
"github.com/bishopfox/sliver/implant/sliver/taskrunner"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
@@ -190,7 +190,7 @@ func memfilesListHandler(_ []byte, resp RPCResponse) {
|
||||
|
||||
pid := os.Getpid()
|
||||
path := fmt.Sprintf("/proc/%d/fd/", pid)
|
||||
dir, files, err := getDirList(path)
|
||||
dir, rootDirEntry, files, err := getDirList(path)
|
||||
|
||||
// Convert directory listing to protobuf
|
||||
timezone, offset := time.Now().Zone()
|
||||
@@ -201,6 +201,19 @@ func memfilesListHandler(_ []byte, resp RPCResponse) {
|
||||
dirList.Exists = false
|
||||
}
|
||||
dirList.Files = []*sliverpb.FileInfo{}
|
||||
rootDirInfo, err := rootDirEntry.Info()
|
||||
if err == nil {
|
||||
// We should not get an error because we created the DirEntry object from the FileInfo object
|
||||
dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
|
||||
Name: ".", // Cannot use the name from the FileInfo / DirEntry because that is the name of the directory
|
||||
Size: rootDirInfo.Size(),
|
||||
ModTime: rootDirInfo.ModTime().Unix(),
|
||||
Mode: rootDirInfo.Mode().String(),
|
||||
Uid: getUid(rootDirInfo),
|
||||
Gid: getGid(rootDirInfo),
|
||||
IsDir: rootDirInfo.IsDir(),
|
||||
})
|
||||
}
|
||||
|
||||
for _, dirEntry := range files {
|
||||
//log.Printf("File: %s\n", dirEntry.Name())
|
||||
|
||||
+540
-280
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v5.26.1
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v4.25.3
|
||||
// source: clientpb/client.proto
|
||||
|
||||
package clientpb
|
||||
@@ -11022,6 +11022,203 @@ func (x *ResourceID) GetValue() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
type CertificatesReq struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
CategoryFilters uint32 `protobuf:"varint,1,opt,name=CategoryFilters,proto3" json:"CategoryFilters,omitempty"`
|
||||
CN string `protobuf:"bytes,2,opt,name=CN,proto3" json:"CN,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CertificatesReq) Reset() {
|
||||
*x = CertificatesReq{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_clientpb_client_proto_msgTypes[119]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CertificatesReq) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CertificatesReq) ProtoMessage() {}
|
||||
|
||||
func (x *CertificatesReq) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_clientpb_client_proto_msgTypes[119]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CertificatesReq.ProtoReflect.Descriptor instead.
|
||||
func (*CertificatesReq) Descriptor() ([]byte, []int) {
|
||||
return file_clientpb_client_proto_rawDescGZIP(), []int{119}
|
||||
}
|
||||
|
||||
func (x *CertificatesReq) GetCategoryFilters() uint32 {
|
||||
if x != nil {
|
||||
return x.CategoryFilters
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *CertificatesReq) GetCN() string {
|
||||
if x != nil {
|
||||
return x.CN
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CertificateData struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
CN string `protobuf:"bytes,1,opt,name=CN,proto3" json:"CN,omitempty"`
|
||||
CreationTime string `protobuf:"bytes,2,opt,name=CreationTime,proto3" json:"CreationTime,omitempty"`
|
||||
ValidityStart string `protobuf:"bytes,3,opt,name=ValidityStart,proto3" json:"ValidityStart,omitempty"`
|
||||
ValidityExpiry string `protobuf:"bytes,4,opt,name=ValidityExpiry,proto3" json:"ValidityExpiry,omitempty"`
|
||||
Type string `protobuf:"bytes,5,opt,name=Type,proto3" json:"Type,omitempty"`
|
||||
KeyAlgorithm string `protobuf:"bytes,6,opt,name=KeyAlgorithm,proto3" json:"KeyAlgorithm,omitempty"`
|
||||
ID string `protobuf:"bytes,7,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CertificateData) Reset() {
|
||||
*x = CertificateData{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_clientpb_client_proto_msgTypes[120]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CertificateData) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CertificateData) ProtoMessage() {}
|
||||
|
||||
func (x *CertificateData) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_clientpb_client_proto_msgTypes[120]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CertificateData.ProtoReflect.Descriptor instead.
|
||||
func (*CertificateData) Descriptor() ([]byte, []int) {
|
||||
return file_clientpb_client_proto_rawDescGZIP(), []int{120}
|
||||
}
|
||||
|
||||
func (x *CertificateData) GetCN() string {
|
||||
if x != nil {
|
||||
return x.CN
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CertificateData) GetCreationTime() string {
|
||||
if x != nil {
|
||||
return x.CreationTime
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CertificateData) GetValidityStart() string {
|
||||
if x != nil {
|
||||
return x.ValidityStart
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CertificateData) GetValidityExpiry() string {
|
||||
if x != nil {
|
||||
return x.ValidityExpiry
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CertificateData) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CertificateData) GetKeyAlgorithm() string {
|
||||
if x != nil {
|
||||
return x.KeyAlgorithm
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CertificateData) GetID() string {
|
||||
if x != nil {
|
||||
return x.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CertificateInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Info []*CertificateData `protobuf:"bytes,1,rep,name=info,proto3" json:"info,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CertificateInfo) Reset() {
|
||||
*x = CertificateInfo{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_clientpb_client_proto_msgTypes[121]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CertificateInfo) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CertificateInfo) ProtoMessage() {}
|
||||
|
||||
func (x *CertificateInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_clientpb_client_proto_msgTypes[121]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CertificateInfo.ProtoReflect.Descriptor instead.
|
||||
func (*CertificateInfo) Descriptor() ([]byte, []int) {
|
||||
return file_clientpb_client_proto_rawDescGZIP(), []int{121}
|
||||
}
|
||||
|
||||
func (x *CertificateInfo) GetInfo() []*CertificateData {
|
||||
if x != nil {
|
||||
return x.Info
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_clientpb_client_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_clientpb_client_proto_rawDesc = []byte{
|
||||
@@ -12565,224 +12762,247 @@ var file_clientpb_client_proto_rawDesc = []byte{
|
||||
0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||
0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x5b,
|
||||
0x0a, 0x0c, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x0e,
|
||||
0x0a, 0x0a, 0x53, 0x48, 0x41, 0x52, 0x45, 0x44, 0x5f, 0x4c, 0x49, 0x42, 0x10, 0x00, 0x12, 0x0d,
|
||||
0x0a, 0x09, 0x53, 0x48, 0x45, 0x4c, 0x4c, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x01, 0x12, 0x0e, 0x0a,
|
||||
0x0a, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a,
|
||||
0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x48,
|
||||
0x49, 0x52, 0x44, 0x5f, 0x50, 0x41, 0x52, 0x54, 0x59, 0x10, 0x04, 0x2a, 0x2d, 0x0a, 0x0d, 0x53,
|
||||
0x74, 0x61, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03,
|
||||
0x54, 0x43, 0x50, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, 0x12,
|
||||
0x09, 0x0a, 0x05, 0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x02, 0x2a, 0x2d, 0x0a, 0x08, 0x46, 0x69,
|
||||
0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x4f, 0x5f, 0x46, 0x49, 0x4c,
|
||||
0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x01, 0x12,
|
||||
0x08, 0x0a, 0x04, 0x54, 0x45, 0x58, 0x54, 0x10, 0x02, 0x2a, 0x30, 0x0a, 0x10, 0x53, 0x68, 0x65,
|
||||
0x6c, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x12, 0x08, 0x0a,
|
||||
0x04, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x48, 0x49, 0x4b, 0x41,
|
||||
0x54, 0x41, 0x5f, 0x47, 0x41, 0x5f, 0x4e, 0x41, 0x49, 0x10, 0x01, 0x2a, 0x35, 0x0a, 0x11, 0x48,
|
||||
0x54, 0x54, 0x50, 0x43, 0x32, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65,
|
||||
0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x4c, 0x4c, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45,
|
||||
0x53, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, 0x45,
|
||||
0x10, 0x02, 0x2a, 0x98, 0x13, 0x0a, 0x08, 0x48, 0x61, 0x73, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12,
|
||||
0x07, 0x0a, 0x03, 0x4d, 0x44, 0x35, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x03, 0x4d, 0x44, 0x34, 0x10,
|
||||
0x84, 0x07, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x48, 0x41, 0x31, 0x10, 0x64, 0x12, 0x0d, 0x0a, 0x08,
|
||||
0x53, 0x48, 0x41, 0x32, 0x5f, 0x32, 0x32, 0x34, 0x10, 0x94, 0x0a, 0x12, 0x0d, 0x0a, 0x08, 0x53,
|
||||
0x48, 0x41, 0x32, 0x5f, 0x32, 0x35, 0x36, 0x10, 0xf8, 0x0a, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x48,
|
||||
0x41, 0x32, 0x5f, 0x33, 0x38, 0x34, 0x10, 0xb0, 0x54, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x48, 0x41,
|
||||
0x32, 0x5f, 0x35, 0x31, 0x32, 0x10, 0xa4, 0x0d, 0x12, 0x0e, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33,
|
||||
0x5f, 0x32, 0x32, 0x34, 0x10, 0x94, 0x87, 0x01, 0x12, 0x0e, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33,
|
||||
0x5f, 0x32, 0x35, 0x36, 0x10, 0xf8, 0x87, 0x01, 0x12, 0x0e, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33,
|
||||
0x5f, 0x33, 0x38, 0x34, 0x10, 0xdc, 0x88, 0x01, 0x12, 0x0e, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33,
|
||||
0x5f, 0x35, 0x31, 0x32, 0x10, 0xc0, 0x89, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x52, 0x49, 0x50, 0x45,
|
||||
0x4d, 0x44, 0x5f, 0x31, 0x36, 0x30, 0x10, 0xf0, 0x2e, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x4c, 0x41,
|
||||
0x4b, 0x45, 0x32, 0x42, 0x5f, 0x32, 0x35, 0x36, 0x10, 0xd8, 0x04, 0x12, 0x1a, 0x0a, 0x15, 0x47,
|
||||
0x4f, 0x53, 0x54, 0x5f, 0x52, 0x5f, 0x33, 0x32, 0x5f, 0x31, 0x31, 0x5f, 0x32, 0x30, 0x31, 0x32,
|
||||
0x5f, 0x32, 0x35, 0x36, 0x10, 0xb4, 0x5b, 0x12, 0x1a, 0x0a, 0x15, 0x47, 0x4f, 0x53, 0x54, 0x5f,
|
||||
0x52, 0x5f, 0x33, 0x32, 0x5f, 0x31, 0x31, 0x5f, 0x32, 0x30, 0x31, 0x32, 0x5f, 0x35, 0x31, 0x32,
|
||||
0x10, 0x98, 0x5c, 0x12, 0x14, 0x0a, 0x0f, 0x47, 0x4f, 0x53, 0x54, 0x5f, 0x52, 0x5f, 0x33, 0x34,
|
||||
0x5f, 0x31, 0x31, 0x5f, 0x39, 0x34, 0x10, 0xf4, 0x35, 0x12, 0x09, 0x0a, 0x03, 0x47, 0x50, 0x47,
|
||||
0x10, 0xf2, 0x84, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x48, 0x41, 0x4c, 0x46, 0x5f, 0x4d, 0x44, 0x35,
|
||||
0x10, 0xec, 0x27, 0x12, 0x10, 0x0a, 0x0a, 0x4b, 0x45, 0x43, 0x43, 0x41, 0x4b, 0x5f, 0x32, 0x32,
|
||||
0x34, 0x10, 0xa4, 0x8a, 0x01, 0x12, 0x10, 0x0a, 0x0a, 0x4b, 0x45, 0x43, 0x43, 0x41, 0x4b, 0x5f,
|
||||
0x32, 0x35, 0x36, 0x10, 0x88, 0x8b, 0x01, 0x12, 0x10, 0x0a, 0x0a, 0x4b, 0x45, 0x43, 0x43, 0x41,
|
||||
0x4b, 0x5f, 0x33, 0x38, 0x34, 0x10, 0xec, 0x8b, 0x01, 0x12, 0x10, 0x0a, 0x0a, 0x4b, 0x45, 0x43,
|
||||
0x43, 0x41, 0x4b, 0x5f, 0x35, 0x31, 0x32, 0x10, 0xd0, 0x8c, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x57,
|
||||
0x48, 0x49, 0x52, 0x4c, 0x50, 0x4f, 0x4f, 0x4c, 0x10, 0xd4, 0x2f, 0x12, 0x0c, 0x0a, 0x07, 0x53,
|
||||
0x49, 0x50, 0x48, 0x41, 0x53, 0x48, 0x10, 0xf4, 0x4e, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x44, 0x35,
|
||||
0x5f, 0x55, 0x54, 0x46, 0x31, 0x36, 0x4c, 0x45, 0x10, 0x46, 0x12, 0x11, 0x0a, 0x0c, 0x53, 0x48,
|
||||
0x41, 0x31, 0x5f, 0x55, 0x54, 0x46, 0x31, 0x36, 0x4c, 0x45, 0x10, 0xaa, 0x01, 0x12, 0x13, 0x0a,
|
||||
0x0e, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x5f, 0x55, 0x54, 0x46, 0x31, 0x36, 0x4c, 0x45, 0x10,
|
||||
0xbe, 0x0b, 0x12, 0x13, 0x0a, 0x0e, 0x53, 0x48, 0x41, 0x33, 0x38, 0x34, 0x5f, 0x55, 0x54, 0x46,
|
||||
0x31, 0x36, 0x4c, 0x45, 0x10, 0xf6, 0x54, 0x12, 0x13, 0x0a, 0x0e, 0x53, 0x48, 0x41, 0x35, 0x31,
|
||||
0x32, 0x5f, 0x55, 0x54, 0x46, 0x31, 0x36, 0x4c, 0x45, 0x10, 0xea, 0x0d, 0x12, 0x18, 0x0a, 0x13,
|
||||
0x42, 0x4c, 0x41, 0x4b, 0x45, 0x32, 0x42, 0x5f, 0x35, 0x31, 0x32, 0x5f, 0x50, 0x57, 0x5f, 0x53,
|
||||
0x41, 0x4c, 0x54, 0x10, 0xe2, 0x04, 0x12, 0x18, 0x0a, 0x13, 0x42, 0x4c, 0x41, 0x4b, 0x45, 0x32,
|
||||
0x42, 0x5f, 0x35, 0x31, 0x32, 0x5f, 0x53, 0x41, 0x4c, 0x54, 0x5f, 0x50, 0x57, 0x10, 0xec, 0x04,
|
||||
0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x44, 0x35, 0x5f, 0x50, 0x57, 0x5f, 0x53, 0x41, 0x4c, 0x54, 0x10,
|
||||
0x0a, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x44, 0x35, 0x5f, 0x53, 0x41, 0x4c, 0x54, 0x5f, 0x50, 0x57,
|
||||
0x10, 0x14, 0x12, 0x15, 0x0a, 0x10, 0x4d, 0x44, 0x35, 0x5f, 0x53, 0x41, 0x4c, 0x54, 0x5f, 0x50,
|
||||
0x57, 0x5f, 0x53, 0x41, 0x4c, 0x54, 0x10, 0xd8, 0x1d, 0x12, 0x14, 0x0a, 0x0f, 0x4d, 0x44, 0x35,
|
||||
0x5f, 0x53, 0x41, 0x4c, 0x54, 0x5f, 0x4d, 0x44, 0x35, 0x5f, 0x50, 0x57, 0x10, 0xfe, 0x1c, 0x12,
|
||||
0x0a, 0x0a, 0x05, 0x43, 0x52, 0x43, 0x33, 0x32, 0x10, 0xec, 0x59, 0x12, 0x0c, 0x0a, 0x06, 0x43,
|
||||
0x52, 0x43, 0x33, 0x32, 0x43, 0x10, 0xfc, 0xd9, 0x01, 0x12, 0x10, 0x0a, 0x0a, 0x43, 0x52, 0x43,
|
||||
0x36, 0x34, 0x4a, 0x6f, 0x6e, 0x65, 0x73, 0x10, 0xe0, 0xda, 0x01, 0x12, 0x11, 0x0a, 0x0b, 0x4a,
|
||||
0x41, 0x56, 0x41, 0x5f, 0x4f, 0x42, 0x4a, 0x45, 0x43, 0x54, 0x10, 0x8c, 0x92, 0x01, 0x12, 0x0c,
|
||||
0x0a, 0x06, 0x4d, 0x55, 0x52, 0x4d, 0x55, 0x52, 0x10, 0xe4, 0xc8, 0x01, 0x12, 0x0d, 0x0a, 0x07,
|
||||
0x4d, 0x55, 0x52, 0x4d, 0x55, 0x52, 0x33, 0x10, 0x98, 0xd9, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x54,
|
||||
0x48, 0x52, 0x45, 0x45, 0x5f, 0x44, 0x45, 0x53, 0x10, 0x94, 0x6e, 0x12, 0x08, 0x0a, 0x03, 0x44,
|
||||
0x45, 0x53, 0x10, 0xb0, 0x6d, 0x12, 0x11, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f, 0x31, 0x32, 0x38,
|
||||
0x5f, 0x45, 0x43, 0x42, 0x10, 0xa1, 0xce, 0x01, 0x12, 0x11, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f,
|
||||
0x31, 0x39, 0x32, 0x5f, 0x45, 0x43, 0x42, 0x10, 0xa2, 0xce, 0x01, 0x12, 0x11, 0x0a, 0x0b, 0x41,
|
||||
0x45, 0x53, 0x5f, 0x32, 0x35, 0x36, 0x5f, 0x45, 0x43, 0x42, 0x10, 0xa3, 0xce, 0x01, 0x12, 0x0f,
|
||||
0x0a, 0x0a, 0x43, 0x48, 0x41, 0x5f, 0x43, 0x48, 0x41, 0x5f, 0x32, 0x30, 0x10, 0xa8, 0x78, 0x12,
|
||||
0x1f, 0x0a, 0x1a, 0x4c, 0x49, 0x4e, 0x55, 0x58, 0x5f, 0x4b, 0x45, 0x52, 0x4e, 0x45, 0x4c, 0x5f,
|
||||
0x43, 0x52, 0x59, 0x50, 0x54, 0x4f, 0x5f, 0x41, 0x50, 0x49, 0x5f, 0x32, 0x34, 0x10, 0xa4, 0x71,
|
||||
0x12, 0x0c, 0x0a, 0x07, 0x53, 0x4b, 0x49, 0x50, 0x5f, 0x33, 0x32, 0x10, 0xb4, 0x74, 0x12, 0x14,
|
||||
0x0a, 0x0f, 0x50, 0x42, 0x4b, 0x44, 0x46, 0x32, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x5f, 0x4d, 0x44,
|
||||
0x35, 0x10, 0xfc, 0x5c, 0x12, 0x15, 0x0a, 0x10, 0x50, 0x42, 0x4b, 0x44, 0x46, 0x32, 0x5f, 0x48,
|
||||
0x4d, 0x41, 0x43, 0x5f, 0x53, 0x48, 0x41, 0x31, 0x10, 0xe0, 0x5d, 0x12, 0x17, 0x0a, 0x12, 0x50,
|
||||
0x42, 0x4b, 0x44, 0x46, 0x32, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x5f, 0x53, 0x48, 0x41, 0x32, 0x35,
|
||||
0x36, 0x10, 0x94, 0x55, 0x12, 0x17, 0x0a, 0x12, 0x50, 0x42, 0x4b, 0x44, 0x46, 0x32, 0x5f, 0x48,
|
||||
0x4d, 0x41, 0x43, 0x5f, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x10, 0xc4, 0x5e, 0x12, 0x0b, 0x0a,
|
||||
0x06, 0x53, 0x43, 0x52, 0x59, 0x50, 0x54, 0x10, 0xc4, 0x45, 0x12, 0x0b, 0x0a, 0x06, 0x50, 0x48,
|
||||
0x50, 0x41, 0x53, 0x53, 0x10, 0x90, 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x54, 0x41, 0x43, 0x41, 0x43,
|
||||
0x53, 0x5f, 0x50, 0x4c, 0x55, 0x53, 0x10, 0xe4, 0x7d, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x49, 0x50,
|
||||
0x5f, 0x44, 0x49, 0x47, 0x45, 0x53, 0x54, 0x10, 0x88, 0x59, 0x12, 0x0c, 0x0a, 0x07, 0x49, 0x4b,
|
||||
0x45, 0x5f, 0x4d, 0x44, 0x35, 0x10, 0xb4, 0x29, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x4b, 0x45, 0x5f,
|
||||
0x53, 0x48, 0x41, 0x31, 0x10, 0x98, 0x2a, 0x12, 0x19, 0x0a, 0x13, 0x53, 0x4e, 0x4d, 0x50, 0x5f,
|
||||
0x56, 0x33, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x5f, 0x4d, 0x44, 0x35, 0x5f, 0x39, 0x36, 0x10, 0x8c,
|
||||
0xc4, 0x01, 0x12, 0x22, 0x0a, 0x1c, 0x53, 0x4e, 0x4d, 0x50, 0x5f, 0x56, 0x33, 0x5f, 0x48, 0x4d,
|
||||
0x41, 0x43, 0x5f, 0x4d, 0x44, 0x35, 0x5f, 0x39, 0x36, 0x5f, 0x5f, 0x53, 0x48, 0x41, 0x31, 0x5f,
|
||||
0x39, 0x36, 0x10, 0xa8, 0xc3, 0x01, 0x12, 0x1a, 0x0a, 0x14, 0x53, 0x4e, 0x4d, 0x50, 0x5f, 0x56,
|
||||
0x33, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x5f, 0x53, 0x48, 0x41, 0x31, 0x5f, 0x39, 0x36, 0x10, 0xf0,
|
||||
0xc4, 0x01, 0x12, 0x1d, 0x0a, 0x17, 0x53, 0x4e, 0x4d, 0x50, 0x5f, 0x56, 0x33, 0x5f, 0x48, 0x4d,
|
||||
0x41, 0x43, 0x5f, 0x53, 0x48, 0x41, 0x32, 0x32, 0x34, 0x5f, 0x31, 0x32, 0x38, 0x10, 0xcc, 0xd0,
|
||||
0x01, 0x12, 0x1d, 0x0a, 0x17, 0x53, 0x4e, 0x4d, 0x50, 0x5f, 0x56, 0x33, 0x5f, 0x48, 0x4d, 0x41,
|
||||
0x43, 0x5f, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x5f, 0x31, 0x39, 0x32, 0x10, 0xb0, 0xd1, 0x01,
|
||||
0x12, 0x1d, 0x0a, 0x17, 0x53, 0x4e, 0x4d, 0x50, 0x5f, 0x56, 0x33, 0x5f, 0x48, 0x4d, 0x41, 0x43,
|
||||
0x5f, 0x53, 0x48, 0x41, 0x33, 0x38, 0x34, 0x5f, 0x32, 0x35, 0x36, 0x10, 0x94, 0xd2, 0x01, 0x12,
|
||||
0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x4b,
|
||||
0x0a, 0x0f, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65,
|
||||
0x71, 0x12, 0x28, 0x0a, 0x0f, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x46, 0x69, 0x6c,
|
||||
0x74, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x43, 0x61, 0x74, 0x65,
|
||||
0x67, 0x6f, 0x72, 0x79, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x43,
|
||||
0x4e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x43, 0x4e, 0x22, 0xdb, 0x01, 0x0a, 0x0f,
|
||||
0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x43, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x43, 0x4e, 0x12,
|
||||
0x22, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x69, 0x74, 0x79, 0x53,
|
||||
0x74, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x56, 0x61, 0x6c, 0x69,
|
||||
0x64, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x56, 0x61, 0x6c,
|
||||
0x69, 0x64, 0x69, 0x74, 0x79, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x69, 0x74, 0x79, 0x45, 0x78, 0x70, 0x69, 0x72,
|
||||
0x79, 0x12, 0x12, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x4b, 0x65, 0x79, 0x41, 0x6c, 0x67, 0x6f,
|
||||
0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x4b, 0x65, 0x79,
|
||||
0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18,
|
||||
0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x22, 0x40, 0x0a, 0x0f, 0x43, 0x65, 0x72,
|
||||
0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2d, 0x0a, 0x04,
|
||||
0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6c, 0x69,
|
||||
0x65, 0x6e, 0x74, 0x70, 0x62, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||
0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x2a, 0x5b, 0x0a, 0x0c, 0x4f,
|
||||
0x75, 0x74, 0x70, 0x75, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x0e, 0x0a, 0x0a, 0x53,
|
||||
0x48, 0x41, 0x52, 0x45, 0x44, 0x5f, 0x4c, 0x49, 0x42, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53,
|
||||
0x48, 0x45, 0x4c, 0x4c, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x45, 0x58,
|
||||
0x45, 0x43, 0x55, 0x54, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45,
|
||||
0x52, 0x56, 0x49, 0x43, 0x45, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x48, 0x49, 0x52, 0x44,
|
||||
0x5f, 0x50, 0x41, 0x52, 0x54, 0x59, 0x10, 0x04, 0x2a, 0x2d, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x67,
|
||||
0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x07, 0x0a, 0x03, 0x54, 0x43, 0x50,
|
||||
0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05,
|
||||
0x48, 0x54, 0x54, 0x50, 0x53, 0x10, 0x02, 0x2a, 0x2d, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x54,
|
||||
0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x4f, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x00,
|
||||
0x12, 0x0a, 0x0a, 0x06, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04,
|
||||
0x54, 0x45, 0x58, 0x54, 0x10, 0x02, 0x2a, 0x30, 0x0a, 0x10, 0x53, 0x68, 0x65, 0x6c, 0x6c, 0x63,
|
||||
0x6f, 0x64, 0x65, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x4f,
|
||||
0x4e, 0x45, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x48, 0x49, 0x4b, 0x41, 0x54, 0x41, 0x5f,
|
||||
0x47, 0x41, 0x5f, 0x4e, 0x41, 0x49, 0x10, 0x01, 0x2a, 0x35, 0x0a, 0x11, 0x48, 0x54, 0x54, 0x50,
|
||||
0x43, 0x32, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a,
|
||||
0x04, 0x50, 0x4f, 0x4c, 0x4c, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x53, 0x53, 0x49,
|
||||
0x4f, 0x4e, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x02, 0x2a,
|
||||
0x98, 0x13, 0x0a, 0x08, 0x48, 0x61, 0x73, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03,
|
||||
0x4d, 0x44, 0x35, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x03, 0x4d, 0x44, 0x34, 0x10, 0x84, 0x07, 0x12,
|
||||
0x08, 0x0a, 0x04, 0x53, 0x48, 0x41, 0x31, 0x10, 0x64, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x48, 0x41,
|
||||
0x32, 0x5f, 0x32, 0x32, 0x34, 0x10, 0x94, 0x0a, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x32,
|
||||
0x5f, 0x32, 0x35, 0x36, 0x10, 0xf8, 0x0a, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x32, 0x5f,
|
||||
0x33, 0x38, 0x34, 0x10, 0xb0, 0x54, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x32, 0x5f, 0x35,
|
||||
0x31, 0x32, 0x10, 0xa4, 0x0d, 0x12, 0x0e, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33, 0x5f, 0x32, 0x32,
|
||||
0x34, 0x10, 0x94, 0x87, 0x01, 0x12, 0x0e, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33, 0x5f, 0x32, 0x35,
|
||||
0x36, 0x10, 0xf8, 0x87, 0x01, 0x12, 0x0e, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33, 0x5f, 0x33, 0x38,
|
||||
0x34, 0x10, 0xdc, 0x88, 0x01, 0x12, 0x0e, 0x0a, 0x08, 0x53, 0x48, 0x41, 0x33, 0x5f, 0x35, 0x31,
|
||||
0x32, 0x10, 0xc0, 0x89, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x52, 0x49, 0x50, 0x45, 0x4d, 0x44, 0x5f,
|
||||
0x31, 0x36, 0x30, 0x10, 0xf0, 0x2e, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x4c, 0x41, 0x4b, 0x45, 0x32,
|
||||
0x42, 0x5f, 0x32, 0x35, 0x36, 0x10, 0xd8, 0x04, 0x12, 0x1a, 0x0a, 0x15, 0x47, 0x4f, 0x53, 0x54,
|
||||
0x5f, 0x52, 0x5f, 0x33, 0x32, 0x5f, 0x31, 0x31, 0x5f, 0x32, 0x30, 0x31, 0x32, 0x5f, 0x32, 0x35,
|
||||
0x36, 0x10, 0xb4, 0x5b, 0x12, 0x1a, 0x0a, 0x15, 0x47, 0x4f, 0x53, 0x54, 0x5f, 0x52, 0x5f, 0x33,
|
||||
0x32, 0x5f, 0x31, 0x31, 0x5f, 0x32, 0x30, 0x31, 0x32, 0x5f, 0x35, 0x31, 0x32, 0x10, 0x98, 0x5c,
|
||||
0x12, 0x14, 0x0a, 0x0f, 0x47, 0x4f, 0x53, 0x54, 0x5f, 0x52, 0x5f, 0x33, 0x34, 0x5f, 0x31, 0x31,
|
||||
0x5f, 0x39, 0x34, 0x10, 0xf4, 0x35, 0x12, 0x09, 0x0a, 0x03, 0x47, 0x50, 0x47, 0x10, 0xf2, 0x84,
|
||||
0x01, 0x12, 0x0d, 0x0a, 0x08, 0x48, 0x41, 0x4c, 0x46, 0x5f, 0x4d, 0x44, 0x35, 0x10, 0xec, 0x27,
|
||||
0x12, 0x10, 0x0a, 0x0a, 0x4b, 0x45, 0x43, 0x43, 0x41, 0x4b, 0x5f, 0x32, 0x32, 0x34, 0x10, 0xa4,
|
||||
0x8a, 0x01, 0x12, 0x10, 0x0a, 0x0a, 0x4b, 0x45, 0x43, 0x43, 0x41, 0x4b, 0x5f, 0x32, 0x35, 0x36,
|
||||
0x10, 0x88, 0x8b, 0x01, 0x12, 0x10, 0x0a, 0x0a, 0x4b, 0x45, 0x43, 0x43, 0x41, 0x4b, 0x5f, 0x33,
|
||||
0x38, 0x34, 0x10, 0xec, 0x8b, 0x01, 0x12, 0x10, 0x0a, 0x0a, 0x4b, 0x45, 0x43, 0x43, 0x41, 0x4b,
|
||||
0x5f, 0x35, 0x31, 0x32, 0x10, 0xd0, 0x8c, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x57, 0x48, 0x49, 0x52,
|
||||
0x4c, 0x50, 0x4f, 0x4f, 0x4c, 0x10, 0xd4, 0x2f, 0x12, 0x0c, 0x0a, 0x07, 0x53, 0x49, 0x50, 0x48,
|
||||
0x41, 0x53, 0x48, 0x10, 0xf4, 0x4e, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x44, 0x35, 0x5f, 0x55, 0x54,
|
||||
0x46, 0x31, 0x36, 0x4c, 0x45, 0x10, 0x46, 0x12, 0x11, 0x0a, 0x0c, 0x53, 0x48, 0x41, 0x31, 0x5f,
|
||||
0x55, 0x54, 0x46, 0x31, 0x36, 0x4c, 0x45, 0x10, 0xaa, 0x01, 0x12, 0x13, 0x0a, 0x0e, 0x53, 0x48,
|
||||
0x41, 0x32, 0x35, 0x36, 0x5f, 0x55, 0x54, 0x46, 0x31, 0x36, 0x4c, 0x45, 0x10, 0xbe, 0x0b, 0x12,
|
||||
0x13, 0x0a, 0x0e, 0x53, 0x48, 0x41, 0x33, 0x38, 0x34, 0x5f, 0x55, 0x54, 0x46, 0x31, 0x36, 0x4c,
|
||||
0x45, 0x10, 0xf6, 0x54, 0x12, 0x13, 0x0a, 0x0e, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x5f, 0x55,
|
||||
0x54, 0x46, 0x31, 0x36, 0x4c, 0x45, 0x10, 0xea, 0x0d, 0x12, 0x18, 0x0a, 0x13, 0x42, 0x4c, 0x41,
|
||||
0x4b, 0x45, 0x32, 0x42, 0x5f, 0x35, 0x31, 0x32, 0x5f, 0x50, 0x57, 0x5f, 0x53, 0x41, 0x4c, 0x54,
|
||||
0x10, 0xe2, 0x04, 0x12, 0x18, 0x0a, 0x13, 0x42, 0x4c, 0x41, 0x4b, 0x45, 0x32, 0x42, 0x5f, 0x35,
|
||||
0x31, 0x32, 0x5f, 0x53, 0x41, 0x4c, 0x54, 0x5f, 0x50, 0x57, 0x10, 0xec, 0x04, 0x12, 0x0f, 0x0a,
|
||||
0x0b, 0x4d, 0x44, 0x35, 0x5f, 0x50, 0x57, 0x5f, 0x53, 0x41, 0x4c, 0x54, 0x10, 0x0a, 0x12, 0x0f,
|
||||
0x0a, 0x0b, 0x4d, 0x44, 0x35, 0x5f, 0x53, 0x41, 0x4c, 0x54, 0x5f, 0x50, 0x57, 0x10, 0x14, 0x12,
|
||||
0x15, 0x0a, 0x10, 0x4d, 0x44, 0x35, 0x5f, 0x53, 0x41, 0x4c, 0x54, 0x5f, 0x50, 0x57, 0x5f, 0x53,
|
||||
0x41, 0x4c, 0x54, 0x10, 0xd8, 0x1d, 0x12, 0x14, 0x0a, 0x0f, 0x4d, 0x44, 0x35, 0x5f, 0x53, 0x41,
|
||||
0x4c, 0x54, 0x5f, 0x4d, 0x44, 0x35, 0x5f, 0x50, 0x57, 0x10, 0xfe, 0x1c, 0x12, 0x0a, 0x0a, 0x05,
|
||||
0x43, 0x52, 0x43, 0x33, 0x32, 0x10, 0xec, 0x59, 0x12, 0x0c, 0x0a, 0x06, 0x43, 0x52, 0x43, 0x33,
|
||||
0x32, 0x43, 0x10, 0xfc, 0xd9, 0x01, 0x12, 0x10, 0x0a, 0x0a, 0x43, 0x52, 0x43, 0x36, 0x34, 0x4a,
|
||||
0x6f, 0x6e, 0x65, 0x73, 0x10, 0xe0, 0xda, 0x01, 0x12, 0x11, 0x0a, 0x0b, 0x4a, 0x41, 0x56, 0x41,
|
||||
0x5f, 0x4f, 0x42, 0x4a, 0x45, 0x43, 0x54, 0x10, 0x8c, 0x92, 0x01, 0x12, 0x0c, 0x0a, 0x06, 0x4d,
|
||||
0x55, 0x52, 0x4d, 0x55, 0x52, 0x10, 0xe4, 0xc8, 0x01, 0x12, 0x0d, 0x0a, 0x07, 0x4d, 0x55, 0x52,
|
||||
0x4d, 0x55, 0x52, 0x33, 0x10, 0x98, 0xd9, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x54, 0x48, 0x52, 0x45,
|
||||
0x45, 0x5f, 0x44, 0x45, 0x53, 0x10, 0x94, 0x6e, 0x12, 0x08, 0x0a, 0x03, 0x44, 0x45, 0x53, 0x10,
|
||||
0xb0, 0x6d, 0x12, 0x11, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f, 0x31, 0x32, 0x38, 0x5f, 0x45, 0x43,
|
||||
0x42, 0x10, 0xa1, 0xce, 0x01, 0x12, 0x11, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f, 0x31, 0x39, 0x32,
|
||||
0x5f, 0x45, 0x43, 0x42, 0x10, 0xa2, 0xce, 0x01, 0x12, 0x11, 0x0a, 0x0b, 0x41, 0x45, 0x53, 0x5f,
|
||||
0x32, 0x35, 0x36, 0x5f, 0x45, 0x43, 0x42, 0x10, 0xa3, 0xce, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x43,
|
||||
0x48, 0x41, 0x5f, 0x43, 0x48, 0x41, 0x5f, 0x32, 0x30, 0x10, 0xa8, 0x78, 0x12, 0x1f, 0x0a, 0x1a,
|
||||
0x4c, 0x49, 0x4e, 0x55, 0x58, 0x5f, 0x4b, 0x45, 0x52, 0x4e, 0x45, 0x4c, 0x5f, 0x43, 0x52, 0x59,
|
||||
0x50, 0x54, 0x4f, 0x5f, 0x41, 0x50, 0x49, 0x5f, 0x32, 0x34, 0x10, 0xa4, 0x71, 0x12, 0x0c, 0x0a,
|
||||
0x07, 0x53, 0x4b, 0x49, 0x50, 0x5f, 0x33, 0x32, 0x10, 0xb4, 0x74, 0x12, 0x14, 0x0a, 0x0f, 0x50,
|
||||
0x42, 0x4b, 0x44, 0x46, 0x32, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x5f, 0x4d, 0x44, 0x35, 0x10, 0xfc,
|
||||
0x5c, 0x12, 0x15, 0x0a, 0x10, 0x50, 0x42, 0x4b, 0x44, 0x46, 0x32, 0x5f, 0x48, 0x4d, 0x41, 0x43,
|
||||
0x5f, 0x53, 0x48, 0x41, 0x31, 0x10, 0xe0, 0x5d, 0x12, 0x17, 0x0a, 0x12, 0x50, 0x42, 0x4b, 0x44,
|
||||
0x46, 0x32, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x5f, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x10, 0x94,
|
||||
0x55, 0x12, 0x17, 0x0a, 0x12, 0x50, 0x42, 0x4b, 0x44, 0x46, 0x32, 0x5f, 0x48, 0x4d, 0x41, 0x43,
|
||||
0x5f, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x10, 0xc4, 0x5e, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x43,
|
||||
0x52, 0x59, 0x50, 0x54, 0x10, 0xc4, 0x45, 0x12, 0x0b, 0x0a, 0x06, 0x50, 0x48, 0x50, 0x41, 0x53,
|
||||
0x53, 0x10, 0x90, 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x54, 0x41, 0x43, 0x41, 0x43, 0x53, 0x5f, 0x50,
|
||||
0x4c, 0x55, 0x53, 0x10, 0xe4, 0x7d, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x49, 0x50, 0x5f, 0x44, 0x49,
|
||||
0x47, 0x45, 0x53, 0x54, 0x10, 0x88, 0x59, 0x12, 0x0c, 0x0a, 0x07, 0x49, 0x4b, 0x45, 0x5f, 0x4d,
|
||||
0x44, 0x35, 0x10, 0xb4, 0x29, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x4b, 0x45, 0x5f, 0x53, 0x48, 0x41,
|
||||
0x31, 0x10, 0x98, 0x2a, 0x12, 0x19, 0x0a, 0x13, 0x53, 0x4e, 0x4d, 0x50, 0x5f, 0x56, 0x33, 0x5f,
|
||||
0x48, 0x4d, 0x41, 0x43, 0x5f, 0x4d, 0x44, 0x35, 0x5f, 0x39, 0x36, 0x10, 0x8c, 0xc4, 0x01, 0x12,
|
||||
0x22, 0x0a, 0x1c, 0x53, 0x4e, 0x4d, 0x50, 0x5f, 0x56, 0x33, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x5f,
|
||||
0x4d, 0x44, 0x35, 0x5f, 0x39, 0x36, 0x5f, 0x5f, 0x53, 0x48, 0x41, 0x31, 0x5f, 0x39, 0x36, 0x10,
|
||||
0xa8, 0xc3, 0x01, 0x12, 0x1a, 0x0a, 0x14, 0x53, 0x4e, 0x4d, 0x50, 0x5f, 0x56, 0x33, 0x5f, 0x48,
|
||||
0x4d, 0x41, 0x43, 0x5f, 0x53, 0x48, 0x41, 0x31, 0x5f, 0x39, 0x36, 0x10, 0xf0, 0xc4, 0x01, 0x12,
|
||||
0x1d, 0x0a, 0x17, 0x53, 0x4e, 0x4d, 0x50, 0x5f, 0x56, 0x33, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x5f,
|
||||
0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x5f, 0x33, 0x38, 0x34, 0x10, 0xa4, 0xd5, 0x01, 0x12, 0x15,
|
||||
0x0a, 0x10, 0x57, 0x50, 0x41, 0x5f, 0x45, 0x41, 0x50, 0x4f, 0x4c, 0x5f, 0x50, 0x42, 0x4b, 0x44,
|
||||
0x46, 0x32, 0x10, 0xc4, 0x13, 0x12, 0x12, 0x0a, 0x0d, 0x57, 0x50, 0x41, 0x5f, 0x45, 0x41, 0x50,
|
||||
0x4f, 0x4c, 0x5f, 0x50, 0x4d, 0x4b, 0x10, 0xc5, 0x13, 0x12, 0x1c, 0x0a, 0x16, 0x57, 0x50, 0x41,
|
||||
0x5f, 0x50, 0x42, 0x4b, 0x44, 0x46, 0x32, 0x5f, 0x50, 0x4d, 0x4b, 0x49, 0x44, 0x5f, 0x45, 0x41,
|
||||
0x50, 0x4f, 0x4c, 0x10, 0xf0, 0xab, 0x01, 0x12, 0x19, 0x0a, 0x13, 0x57, 0x50, 0x41, 0x5f, 0x50,
|
||||
0x4d, 0x4b, 0x5f, 0x50, 0x4d, 0x4b, 0x49, 0x44, 0x5f, 0x45, 0x41, 0x50, 0x4f, 0x4c, 0x10, 0xf1,
|
||||
0xab, 0x01, 0x12, 0x16, 0x0a, 0x10, 0x57, 0x50, 0x41, 0x5f, 0x50, 0x4d, 0x4b, 0x49, 0x44, 0x5f,
|
||||
0x50, 0x42, 0x4b, 0x44, 0x46, 0x32, 0x10, 0xa0, 0x83, 0x01, 0x12, 0x13, 0x0a, 0x0d, 0x57, 0x50,
|
||||
0x41, 0x5f, 0x50, 0x4d, 0x4b, 0x49, 0x44, 0x5f, 0x50, 0x4d, 0x4b, 0x10, 0xa1, 0x83, 0x01, 0x12,
|
||||
0x19, 0x0a, 0x14, 0x49, 0x50, 0x4d, 0x49, 0x32, 0x5f, 0x50, 0x41, 0x4b, 0x50, 0x5f, 0x48, 0x4d,
|
||||
0x41, 0x43, 0x5f, 0x53, 0x48, 0x41, 0x31, 0x10, 0x84, 0x39, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x52,
|
||||
0x41, 0x4d, 0x5f, 0x4d, 0x44, 0x35, 0x10, 0xd8, 0x4f, 0x12, 0x09, 0x0a, 0x03, 0x4a, 0x57, 0x54,
|
||||
0x10, 0xf4, 0x80, 0x01, 0x12, 0x0e, 0x0a, 0x08, 0x52, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x5f, 0x33,
|
||||
0x10, 0x90, 0xe4, 0x01, 0x12, 0x19, 0x0a, 0x13, 0x4b, 0x45, 0x52, 0x42, 0x45, 0x52, 0x4f, 0x53,
|
||||
0x5f, 0x31, 0x37, 0x5f, 0x54, 0x47, 0x53, 0x5f, 0x52, 0x45, 0x50, 0x10, 0x90, 0x99, 0x01, 0x12,
|
||||
0x19, 0x0a, 0x13, 0x4b, 0x45, 0x52, 0x42, 0x45, 0x52, 0x4f, 0x53, 0x5f, 0x31, 0x37, 0x5f, 0x50,
|
||||
0x52, 0x45, 0x41, 0x55, 0x54, 0x48, 0x10, 0xd8, 0x9a, 0x01, 0x12, 0x14, 0x0a, 0x0e, 0x4b, 0x45,
|
||||
0x52, 0x42, 0x45, 0x52, 0x4f, 0x53, 0x5f, 0x31, 0x37, 0x5f, 0x44, 0x42, 0x10, 0x80, 0xe1, 0x01,
|
||||
0x12, 0x19, 0x0a, 0x13, 0x4b, 0x45, 0x52, 0x42, 0x45, 0x52, 0x4f, 0x53, 0x5f, 0x31, 0x38, 0x5f,
|
||||
0x54, 0x47, 0x53, 0x5f, 0x52, 0x45, 0x50, 0x10, 0xf4, 0x99, 0x01, 0x12, 0x19, 0x0a, 0x13, 0x4b,
|
||||
0x45, 0x52, 0x42, 0x45, 0x52, 0x4f, 0x53, 0x5f, 0x31, 0x38, 0x5f, 0x50, 0x52, 0x45, 0x41, 0x55,
|
||||
0x54, 0x48, 0x10, 0xbc, 0x9b, 0x01, 0x12, 0x14, 0x0a, 0x0e, 0x4b, 0x45, 0x52, 0x42, 0x45, 0x52,
|
||||
0x4f, 0x53, 0x5f, 0x31, 0x38, 0x5f, 0x44, 0x42, 0x10, 0xe4, 0xe1, 0x01, 0x12, 0x1f, 0x0a, 0x1a,
|
||||
0x4b, 0x45, 0x52, 0x42, 0x45, 0x52, 0x4f, 0x53, 0x5f, 0x32, 0x33, 0x5f, 0x53, 0x41, 0x5f, 0x52,
|
||||
0x45, 0x51, 0x5f, 0x50, 0x52, 0x45, 0x41, 0x55, 0x54, 0x48, 0x10, 0xcc, 0x3a, 0x12, 0x18, 0x0a,
|
||||
0x13, 0x4b, 0x45, 0x52, 0x42, 0x45, 0x52, 0x4f, 0x53, 0x5f, 0x32, 0x33, 0x5f, 0x54, 0x47, 0x53,
|
||||
0x5f, 0x52, 0x45, 0x50, 0x10, 0xac, 0x66, 0x12, 0x18, 0x0a, 0x12, 0x4b, 0x45, 0x52, 0x42, 0x45,
|
||||
0x52, 0x4f, 0x53, 0x5f, 0x32, 0x33, 0x5f, 0x41, 0x53, 0x5f, 0x52, 0x45, 0x50, 0x10, 0x98, 0x8e,
|
||||
0x01, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x45, 0x54, 0x5f, 0x4e, 0x54, 0x4c, 0x4d, 0x5f, 0x56, 0x31,
|
||||
0x10, 0xfc, 0x2a, 0x12, 0x14, 0x0a, 0x0e, 0x4e, 0x45, 0x54, 0x5f, 0x4e, 0x54, 0x4c, 0x4d, 0x5f,
|
||||
0x56, 0x31, 0x5f, 0x4e, 0x54, 0x10, 0xf8, 0xd2, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x45, 0x54,
|
||||
0x5f, 0x4e, 0x54, 0x4c, 0x4d, 0x5f, 0x56, 0x32, 0x10, 0xe0, 0x2b, 0x12, 0x14, 0x0a, 0x0e, 0x4e,
|
||||
0x45, 0x54, 0x5f, 0x4e, 0x54, 0x4c, 0x4d, 0x5f, 0x56, 0x32, 0x5f, 0x4e, 0x54, 0x10, 0xdc, 0xd3,
|
||||
0x01, 0x12, 0x0b, 0x0a, 0x05, 0x46, 0x4c, 0x41, 0x53, 0x4b, 0x10, 0xac, 0xe3, 0x01, 0x12, 0x0f,
|
||||
0x0a, 0x0a, 0x49, 0x53, 0x43, 0x53, 0x49, 0x5f, 0x43, 0x48, 0x41, 0x50, 0x10, 0xc0, 0x25, 0x12,
|
||||
0x09, 0x0a, 0x04, 0x52, 0x41, 0x43, 0x46, 0x10, 0xb4, 0x42, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x49,
|
||||
0x58, 0x5f, 0x53, 0x4d, 0x44, 0x35, 0x10, 0x9c, 0x31, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x49, 0x58,
|
||||
0x5f, 0x53, 0x53, 0x48, 0x41, 0x31, 0x10, 0xac, 0x34, 0x12, 0x10, 0x0a, 0x0b, 0x41, 0x49, 0x58,
|
||||
0x5f, 0x53, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x10, 0x80, 0x32, 0x12, 0x10, 0x0a, 0x0b, 0x41,
|
||||
0x49, 0x58, 0x5f, 0x53, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x10, 0xe4, 0x32, 0x12, 0x07, 0x0a,
|
||||
0x02, 0x4c, 0x4d, 0x10, 0xb8, 0x17, 0x12, 0x0d, 0x0a, 0x07, 0x51, 0x4e, 0x58, 0x5f, 0x4d, 0x44,
|
||||
0x35, 0x10, 0xb8, 0x94, 0x01, 0x12, 0x10, 0x0a, 0x0a, 0x51, 0x4e, 0x58, 0x5f, 0x53, 0x48, 0x41,
|
||||
0x32, 0x35, 0x36, 0x10, 0x9c, 0x95, 0x01, 0x12, 0x10, 0x0a, 0x0a, 0x51, 0x4e, 0x58, 0x5f, 0x53,
|
||||
0x48, 0x41, 0x35, 0x31, 0x32, 0x10, 0x80, 0x96, 0x01, 0x12, 0x19, 0x0a, 0x14, 0x44, 0x50, 0x41,
|
||||
0x50, 0x49, 0x5f, 0x56, 0x31, 0x5f, 0x43, 0x54, 0x58, 0x5f, 0x31, 0x5f, 0x41, 0x4e, 0x44, 0x5f,
|
||||
0x32, 0x10, 0xc4, 0x77, 0x12, 0x13, 0x0a, 0x0e, 0x44, 0x50, 0x41, 0x50, 0x49, 0x5f, 0x56, 0x31,
|
||||
0x5f, 0x43, 0x54, 0x58, 0x5f, 0x33, 0x10, 0xce, 0x77, 0x12, 0x19, 0x0a, 0x14, 0x44, 0x50, 0x41,
|
||||
0x50, 0x49, 0x5f, 0x56, 0x32, 0x5f, 0x43, 0x54, 0x58, 0x5f, 0x31, 0x5f, 0x41, 0x4e, 0x44, 0x5f,
|
||||
0x32, 0x10, 0x9c, 0x7c, 0x12, 0x13, 0x0a, 0x0e, 0x44, 0x50, 0x41, 0x50, 0x49, 0x5f, 0x56, 0x32,
|
||||
0x5f, 0x43, 0x54, 0x58, 0x5f, 0x33, 0x10, 0xa6, 0x7c, 0x12, 0x0b, 0x0a, 0x06, 0x47, 0x52, 0x55,
|
||||
0x42, 0x5f, 0x32, 0x10, 0xa0, 0x38, 0x12, 0x12, 0x0a, 0x0d, 0x4d, 0x53, 0x5f, 0x41, 0x5a, 0x55,
|
||||
0x52, 0x45, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x80, 0x64, 0x12, 0x0f, 0x0a, 0x0a, 0x42, 0x53,
|
||||
0x44, 0x49, 0x5f, 0x43, 0x52, 0x59, 0x50, 0x54, 0x10, 0xf0, 0x60, 0x12, 0x09, 0x0a, 0x04, 0x4e,
|
||||
0x54, 0x4c, 0x4d, 0x10, 0xe8, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x41, 0x44, 0x4d, 0x49, 0x4e,
|
||||
0x32, 0x10, 0xac, 0x4d, 0x12, 0x14, 0x0a, 0x0f, 0x53, 0x41, 0x4d, 0x53, 0x55, 0x4e, 0x47, 0x5f,
|
||||
0x41, 0x4e, 0x44, 0x52, 0x4f, 0x49, 0x44, 0x10, 0xa8, 0x2d, 0x12, 0x17, 0x0a, 0x11, 0x57, 0x49,
|
||||
0x4e, 0x44, 0x4f, 0x57, 0x53, 0x5f, 0x48, 0x45, 0x4c, 0x4c, 0x4f, 0x5f, 0x50, 0x49, 0x4e, 0x10,
|
||||
0xc4, 0xdb, 0x01, 0x12, 0x12, 0x0a, 0x0d, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x5f, 0x50,
|
||||
0x48, 0x4f, 0x4e, 0x45, 0x10, 0xe8, 0x6b, 0x12, 0x12, 0x0a, 0x0d, 0x43, 0x49, 0x53, 0x43, 0x4f,
|
||||
0x5f, 0x41, 0x53, 0x41, 0x5f, 0x4d, 0x44, 0x35, 0x10, 0xea, 0x12, 0x12, 0x1c, 0x0a, 0x17, 0x43,
|
||||
0x49, 0x53, 0x43, 0x4f, 0x5f, 0x49, 0x4f, 0x53, 0x5f, 0x50, 0x42, 0x4b, 0x44, 0x46, 0x32, 0x5f,
|
||||
0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x10, 0xf0, 0x47, 0x12, 0x15, 0x0a, 0x10, 0x43, 0x49, 0x53,
|
||||
0x43, 0x4f, 0x5f, 0x49, 0x4f, 0x53, 0x5f, 0x53, 0x43, 0x52, 0x59, 0x50, 0x54, 0x10, 0xd4, 0x48,
|
||||
0x12, 0x12, 0x0a, 0x0d, 0x43, 0x49, 0x53, 0x43, 0x4f, 0x5f, 0x50, 0x49, 0x58, 0x5f, 0x4d, 0x44,
|
||||
0x35, 0x10, 0xe0, 0x12, 0x12, 0x1a, 0x0a, 0x15, 0x43, 0x49, 0x54, 0x52, 0x49, 0x58, 0x5f, 0x4e,
|
||||
0x45, 0x54, 0x53, 0x43, 0x41, 0x4c, 0x45, 0x52, 0x5f, 0x53, 0x48, 0x41, 0x31, 0x10, 0xa4, 0x3f,
|
||||
0x12, 0x1d, 0x0a, 0x17, 0x43, 0x49, 0x54, 0x52, 0x49, 0x58, 0x5f, 0x4e, 0x45, 0x54, 0x53, 0x43,
|
||||
0x41, 0x4c, 0x45, 0x52, 0x5f, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x10, 0xb8, 0xad, 0x01, 0x12,
|
||||
0x08, 0x0a, 0x03, 0x44, 0x43, 0x43, 0x10, 0xcc, 0x08, 0x12, 0x09, 0x0a, 0x04, 0x44, 0x43, 0x43,
|
||||
0x32, 0x10, 0xb4, 0x10, 0x12, 0x0f, 0x0a, 0x0a, 0x4d, 0x41, 0x43, 0x4f, 0x53, 0x5f, 0x31, 0x30,
|
||||
0x5f, 0x38, 0x10, 0xbc, 0x37, 0x12, 0x0c, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44,
|
||||
0x10, 0x8f, 0x4e, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x43, 0x52, 0x59, 0x50, 0x54, 0x5f, 0x55, 0x4e,
|
||||
0x49, 0x58, 0x10, 0x80, 0x19, 0x12, 0x16, 0x0a, 0x11, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x5f,
|
||||
0x43, 0x52, 0x59, 0x50, 0x54, 0x5f, 0x55, 0x4e, 0x49, 0x58, 0x10, 0x88, 0x0e, 0x2a, 0x32, 0x0a,
|
||||
0x06, 0x53, 0x74, 0x61, 0x74, 0x65, 0x73, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x44, 0x4c, 0x45, 0x10,
|
||||
0x00, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x52, 0x41, 0x43, 0x4b, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12,
|
||||
0x10, 0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10,
|
||||
0x02, 0x2a, 0x3c, 0x0a, 0x0e, 0x43, 0x72, 0x61, 0x63, 0x6b, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x12, 0x0f, 0x0a, 0x0b, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45,
|
||||
0x53, 0x53, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45,
|
||||
0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a,
|
||||
0x94, 0x01, 0x0a, 0x0f, 0x43, 0x72, 0x61, 0x63, 0x6b, 0x41, 0x74, 0x74, 0x61, 0x63, 0x6b, 0x4d,
|
||||
0x6f, 0x64, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x52, 0x41, 0x49, 0x47, 0x48, 0x54, 0x10,
|
||||
0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4f, 0x4d, 0x42, 0x49, 0x4e, 0x41, 0x54, 0x49, 0x4f, 0x4e,
|
||||
0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x52, 0x55, 0x54, 0x45, 0x46, 0x4f, 0x52, 0x43, 0x45,
|
||||
0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x48, 0x59, 0x42, 0x52, 0x49, 0x44, 0x5f, 0x57, 0x4f, 0x52,
|
||||
0x44, 0x4c, 0x49, 0x53, 0x54, 0x5f, 0x4d, 0x41, 0x53, 0x4b, 0x10, 0x06, 0x12, 0x18, 0x0a, 0x14,
|
||||
0x48, 0x59, 0x42, 0x52, 0x49, 0x44, 0x5f, 0x4d, 0x41, 0x53, 0x4b, 0x5f, 0x57, 0x4f, 0x52, 0x44,
|
||||
0x4c, 0x49, 0x53, 0x54, 0x10, 0x07, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x53, 0x53, 0x4f, 0x43, 0x49,
|
||||
0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x09, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x41, 0x54,
|
||||
0x54, 0x41, 0x43, 0x4b, 0x10, 0x0a, 0x2a, 0x44, 0x0a, 0x0d, 0x43, 0x72, 0x61, 0x63, 0x6b, 0x45,
|
||||
0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x41, 0x4c,
|
||||
0x49, 0x44, 0x5f, 0x45, 0x4e, 0x43, 0x4f, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0f, 0x0a,
|
||||
0x0b, 0x49, 0x53, 0x4f, 0x5f, 0x38, 0x38, 0x35, 0x39, 0x5f, 0x31, 0x35, 0x10, 0x01, 0x12, 0x0c,
|
||||
0x0a, 0x08, 0x55, 0x54, 0x46, 0x5f, 0x33, 0x32, 0x4c, 0x45, 0x10, 0x02, 0x2a, 0x90, 0x01, 0x0a,
|
||||
0x12, 0x43, 0x72, 0x61, 0x63, 0x6b, 0x4f, 0x75, 0x74, 0x66, 0x69, 0x6c, 0x65, 0x46, 0x6f, 0x72,
|
||||
0x6d, 0x61, 0x74, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x46,
|
||||
0x4f, 0x52, 0x4d, 0x41, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x41, 0x53, 0x48, 0x5f,
|
||||
0x53, 0x41, 0x4c, 0x54, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x4c, 0x41, 0x49, 0x4e, 0x10,
|
||||
0x02, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x45, 0x58, 0x5f, 0x50, 0x4c, 0x41, 0x49, 0x4e, 0x10, 0x03,
|
||||
0x12, 0x0d, 0x0a, 0x09, 0x43, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x50, 0x4f, 0x53, 0x10, 0x04, 0x12,
|
||||
0x16, 0x0a, 0x12, 0x54, 0x49, 0x4d, 0x45, 0x53, 0x54, 0x41, 0x4d, 0x50, 0x5f, 0x41, 0x42, 0x53,
|
||||
0x4f, 0x4c, 0x55, 0x54, 0x45, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x54, 0x49, 0x4d, 0x45, 0x53,
|
||||
0x54, 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x4c, 0x41, 0x54, 0x49, 0x56, 0x45, 0x10, 0x06, 0x2a,
|
||||
0x63, 0x0a, 0x14, 0x43, 0x72, 0x61, 0x63, 0x6b, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64,
|
||||
0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x18, 0x49, 0x4e, 0x56, 0x41, 0x4c,
|
||||
0x49, 0x44, 0x5f, 0x57, 0x4f, 0x52, 0x4b, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x46,
|
||||
0x49, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x0b,
|
||||
0x0a, 0x07, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x48,
|
||||
0x49, 0x47, 0x48, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x49, 0x47, 0x48, 0x54, 0x4d, 0x41,
|
||||
0x52, 0x45, 0x10, 0x04, 0x2a, 0x4e, 0x0a, 0x0d, 0x43, 0x72, 0x61, 0x63, 0x6b, 0x46, 0x69, 0x6c,
|
||||
0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44,
|
||||
0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x57, 0x4f, 0x52, 0x44, 0x4c,
|
||||
0x49, 0x53, 0x54, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x55, 0x4c, 0x45, 0x53, 0x10, 0x02,
|
||||
0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x41, 0x52, 0x4b, 0x4f, 0x56, 0x5f, 0x48, 0x43, 0x53, 0x54, 0x41,
|
||||
0x54, 0x32, 0x10, 0x03, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73, 0x68, 0x6f, 0x70, 0x66, 0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69,
|
||||
0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x63, 0x6c, 0x69,
|
||||
0x65, 0x6e, 0x74, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x53, 0x48, 0x41, 0x32, 0x32, 0x34, 0x5f, 0x31, 0x32, 0x38, 0x10, 0xcc, 0xd0, 0x01, 0x12, 0x1d,
|
||||
0x0a, 0x17, 0x53, 0x4e, 0x4d, 0x50, 0x5f, 0x56, 0x33, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x5f, 0x53,
|
||||
0x48, 0x41, 0x32, 0x35, 0x36, 0x5f, 0x31, 0x39, 0x32, 0x10, 0xb0, 0xd1, 0x01, 0x12, 0x1d, 0x0a,
|
||||
0x17, 0x53, 0x4e, 0x4d, 0x50, 0x5f, 0x56, 0x33, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x5f, 0x53, 0x48,
|
||||
0x41, 0x33, 0x38, 0x34, 0x5f, 0x32, 0x35, 0x36, 0x10, 0x94, 0xd2, 0x01, 0x12, 0x1d, 0x0a, 0x17,
|
||||
0x53, 0x4e, 0x4d, 0x50, 0x5f, 0x56, 0x33, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x5f, 0x53, 0x48, 0x41,
|
||||
0x35, 0x31, 0x32, 0x5f, 0x33, 0x38, 0x34, 0x10, 0xa4, 0xd5, 0x01, 0x12, 0x15, 0x0a, 0x10, 0x57,
|
||||
0x50, 0x41, 0x5f, 0x45, 0x41, 0x50, 0x4f, 0x4c, 0x5f, 0x50, 0x42, 0x4b, 0x44, 0x46, 0x32, 0x10,
|
||||
0xc4, 0x13, 0x12, 0x12, 0x0a, 0x0d, 0x57, 0x50, 0x41, 0x5f, 0x45, 0x41, 0x50, 0x4f, 0x4c, 0x5f,
|
||||
0x50, 0x4d, 0x4b, 0x10, 0xc5, 0x13, 0x12, 0x1c, 0x0a, 0x16, 0x57, 0x50, 0x41, 0x5f, 0x50, 0x42,
|
||||
0x4b, 0x44, 0x46, 0x32, 0x5f, 0x50, 0x4d, 0x4b, 0x49, 0x44, 0x5f, 0x45, 0x41, 0x50, 0x4f, 0x4c,
|
||||
0x10, 0xf0, 0xab, 0x01, 0x12, 0x19, 0x0a, 0x13, 0x57, 0x50, 0x41, 0x5f, 0x50, 0x4d, 0x4b, 0x5f,
|
||||
0x50, 0x4d, 0x4b, 0x49, 0x44, 0x5f, 0x45, 0x41, 0x50, 0x4f, 0x4c, 0x10, 0xf1, 0xab, 0x01, 0x12,
|
||||
0x16, 0x0a, 0x10, 0x57, 0x50, 0x41, 0x5f, 0x50, 0x4d, 0x4b, 0x49, 0x44, 0x5f, 0x50, 0x42, 0x4b,
|
||||
0x44, 0x46, 0x32, 0x10, 0xa0, 0x83, 0x01, 0x12, 0x13, 0x0a, 0x0d, 0x57, 0x50, 0x41, 0x5f, 0x50,
|
||||
0x4d, 0x4b, 0x49, 0x44, 0x5f, 0x50, 0x4d, 0x4b, 0x10, 0xa1, 0x83, 0x01, 0x12, 0x19, 0x0a, 0x14,
|
||||
0x49, 0x50, 0x4d, 0x49, 0x32, 0x5f, 0x50, 0x41, 0x4b, 0x50, 0x5f, 0x48, 0x4d, 0x41, 0x43, 0x5f,
|
||||
0x53, 0x48, 0x41, 0x31, 0x10, 0x84, 0x39, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x52, 0x41, 0x4d, 0x5f,
|
||||
0x4d, 0x44, 0x35, 0x10, 0xd8, 0x4f, 0x12, 0x09, 0x0a, 0x03, 0x4a, 0x57, 0x54, 0x10, 0xf4, 0x80,
|
||||
0x01, 0x12, 0x0e, 0x0a, 0x08, 0x52, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x5f, 0x33, 0x10, 0x90, 0xe4,
|
||||
0x01, 0x12, 0x19, 0x0a, 0x13, 0x4b, 0x45, 0x52, 0x42, 0x45, 0x52, 0x4f, 0x53, 0x5f, 0x31, 0x37,
|
||||
0x5f, 0x54, 0x47, 0x53, 0x5f, 0x52, 0x45, 0x50, 0x10, 0x90, 0x99, 0x01, 0x12, 0x19, 0x0a, 0x13,
|
||||
0x4b, 0x45, 0x52, 0x42, 0x45, 0x52, 0x4f, 0x53, 0x5f, 0x31, 0x37, 0x5f, 0x50, 0x52, 0x45, 0x41,
|
||||
0x55, 0x54, 0x48, 0x10, 0xd8, 0x9a, 0x01, 0x12, 0x14, 0x0a, 0x0e, 0x4b, 0x45, 0x52, 0x42, 0x45,
|
||||
0x52, 0x4f, 0x53, 0x5f, 0x31, 0x37, 0x5f, 0x44, 0x42, 0x10, 0x80, 0xe1, 0x01, 0x12, 0x19, 0x0a,
|
||||
0x13, 0x4b, 0x45, 0x52, 0x42, 0x45, 0x52, 0x4f, 0x53, 0x5f, 0x31, 0x38, 0x5f, 0x54, 0x47, 0x53,
|
||||
0x5f, 0x52, 0x45, 0x50, 0x10, 0xf4, 0x99, 0x01, 0x12, 0x19, 0x0a, 0x13, 0x4b, 0x45, 0x52, 0x42,
|
||||
0x45, 0x52, 0x4f, 0x53, 0x5f, 0x31, 0x38, 0x5f, 0x50, 0x52, 0x45, 0x41, 0x55, 0x54, 0x48, 0x10,
|
||||
0xbc, 0x9b, 0x01, 0x12, 0x14, 0x0a, 0x0e, 0x4b, 0x45, 0x52, 0x42, 0x45, 0x52, 0x4f, 0x53, 0x5f,
|
||||
0x31, 0x38, 0x5f, 0x44, 0x42, 0x10, 0xe4, 0xe1, 0x01, 0x12, 0x1f, 0x0a, 0x1a, 0x4b, 0x45, 0x52,
|
||||
0x42, 0x45, 0x52, 0x4f, 0x53, 0x5f, 0x32, 0x33, 0x5f, 0x53, 0x41, 0x5f, 0x52, 0x45, 0x51, 0x5f,
|
||||
0x50, 0x52, 0x45, 0x41, 0x55, 0x54, 0x48, 0x10, 0xcc, 0x3a, 0x12, 0x18, 0x0a, 0x13, 0x4b, 0x45,
|
||||
0x52, 0x42, 0x45, 0x52, 0x4f, 0x53, 0x5f, 0x32, 0x33, 0x5f, 0x54, 0x47, 0x53, 0x5f, 0x52, 0x45,
|
||||
0x50, 0x10, 0xac, 0x66, 0x12, 0x18, 0x0a, 0x12, 0x4b, 0x45, 0x52, 0x42, 0x45, 0x52, 0x4f, 0x53,
|
||||
0x5f, 0x32, 0x33, 0x5f, 0x41, 0x53, 0x5f, 0x52, 0x45, 0x50, 0x10, 0x98, 0x8e, 0x01, 0x12, 0x10,
|
||||
0x0a, 0x0b, 0x4e, 0x45, 0x54, 0x5f, 0x4e, 0x54, 0x4c, 0x4d, 0x5f, 0x56, 0x31, 0x10, 0xfc, 0x2a,
|
||||
0x12, 0x14, 0x0a, 0x0e, 0x4e, 0x45, 0x54, 0x5f, 0x4e, 0x54, 0x4c, 0x4d, 0x5f, 0x56, 0x31, 0x5f,
|
||||
0x4e, 0x54, 0x10, 0xf8, 0xd2, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x45, 0x54, 0x5f, 0x4e, 0x54,
|
||||
0x4c, 0x4d, 0x5f, 0x56, 0x32, 0x10, 0xe0, 0x2b, 0x12, 0x14, 0x0a, 0x0e, 0x4e, 0x45, 0x54, 0x5f,
|
||||
0x4e, 0x54, 0x4c, 0x4d, 0x5f, 0x56, 0x32, 0x5f, 0x4e, 0x54, 0x10, 0xdc, 0xd3, 0x01, 0x12, 0x0b,
|
||||
0x0a, 0x05, 0x46, 0x4c, 0x41, 0x53, 0x4b, 0x10, 0xac, 0xe3, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x49,
|
||||
0x53, 0x43, 0x53, 0x49, 0x5f, 0x43, 0x48, 0x41, 0x50, 0x10, 0xc0, 0x25, 0x12, 0x09, 0x0a, 0x04,
|
||||
0x52, 0x41, 0x43, 0x46, 0x10, 0xb4, 0x42, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x49, 0x58, 0x5f, 0x53,
|
||||
0x4d, 0x44, 0x35, 0x10, 0x9c, 0x31, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x49, 0x58, 0x5f, 0x53, 0x53,
|
||||
0x48, 0x41, 0x31, 0x10, 0xac, 0x34, 0x12, 0x10, 0x0a, 0x0b, 0x41, 0x49, 0x58, 0x5f, 0x53, 0x53,
|
||||
0x48, 0x41, 0x32, 0x35, 0x36, 0x10, 0x80, 0x32, 0x12, 0x10, 0x0a, 0x0b, 0x41, 0x49, 0x58, 0x5f,
|
||||
0x53, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x10, 0xe4, 0x32, 0x12, 0x07, 0x0a, 0x02, 0x4c, 0x4d,
|
||||
0x10, 0xb8, 0x17, 0x12, 0x0d, 0x0a, 0x07, 0x51, 0x4e, 0x58, 0x5f, 0x4d, 0x44, 0x35, 0x10, 0xb8,
|
||||
0x94, 0x01, 0x12, 0x10, 0x0a, 0x0a, 0x51, 0x4e, 0x58, 0x5f, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36,
|
||||
0x10, 0x9c, 0x95, 0x01, 0x12, 0x10, 0x0a, 0x0a, 0x51, 0x4e, 0x58, 0x5f, 0x53, 0x48, 0x41, 0x35,
|
||||
0x31, 0x32, 0x10, 0x80, 0x96, 0x01, 0x12, 0x19, 0x0a, 0x14, 0x44, 0x50, 0x41, 0x50, 0x49, 0x5f,
|
||||
0x56, 0x31, 0x5f, 0x43, 0x54, 0x58, 0x5f, 0x31, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x32, 0x10, 0xc4,
|
||||
0x77, 0x12, 0x13, 0x0a, 0x0e, 0x44, 0x50, 0x41, 0x50, 0x49, 0x5f, 0x56, 0x31, 0x5f, 0x43, 0x54,
|
||||
0x58, 0x5f, 0x33, 0x10, 0xce, 0x77, 0x12, 0x19, 0x0a, 0x14, 0x44, 0x50, 0x41, 0x50, 0x49, 0x5f,
|
||||
0x56, 0x32, 0x5f, 0x43, 0x54, 0x58, 0x5f, 0x31, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x32, 0x10, 0x9c,
|
||||
0x7c, 0x12, 0x13, 0x0a, 0x0e, 0x44, 0x50, 0x41, 0x50, 0x49, 0x5f, 0x56, 0x32, 0x5f, 0x43, 0x54,
|
||||
0x58, 0x5f, 0x33, 0x10, 0xa6, 0x7c, 0x12, 0x0b, 0x0a, 0x06, 0x47, 0x52, 0x55, 0x42, 0x5f, 0x32,
|
||||
0x10, 0xa0, 0x38, 0x12, 0x12, 0x0a, 0x0d, 0x4d, 0x53, 0x5f, 0x41, 0x5a, 0x55, 0x52, 0x45, 0x5f,
|
||||
0x53, 0x59, 0x4e, 0x43, 0x10, 0x80, 0x64, 0x12, 0x0f, 0x0a, 0x0a, 0x42, 0x53, 0x44, 0x49, 0x5f,
|
||||
0x43, 0x52, 0x59, 0x50, 0x54, 0x10, 0xf0, 0x60, 0x12, 0x09, 0x0a, 0x04, 0x4e, 0x54, 0x4c, 0x4d,
|
||||
0x10, 0xe8, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x41, 0x44, 0x4d, 0x49, 0x4e, 0x32, 0x10, 0xac,
|
||||
0x4d, 0x12, 0x14, 0x0a, 0x0f, 0x53, 0x41, 0x4d, 0x53, 0x55, 0x4e, 0x47, 0x5f, 0x41, 0x4e, 0x44,
|
||||
0x52, 0x4f, 0x49, 0x44, 0x10, 0xa8, 0x2d, 0x12, 0x17, 0x0a, 0x11, 0x57, 0x49, 0x4e, 0x44, 0x4f,
|
||||
0x57, 0x53, 0x5f, 0x48, 0x45, 0x4c, 0x4c, 0x4f, 0x5f, 0x50, 0x49, 0x4e, 0x10, 0xc4, 0xdb, 0x01,
|
||||
0x12, 0x12, 0x0a, 0x0d, 0x57, 0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x5f, 0x50, 0x48, 0x4f, 0x4e,
|
||||
0x45, 0x10, 0xe8, 0x6b, 0x12, 0x12, 0x0a, 0x0d, 0x43, 0x49, 0x53, 0x43, 0x4f, 0x5f, 0x41, 0x53,
|
||||
0x41, 0x5f, 0x4d, 0x44, 0x35, 0x10, 0xea, 0x12, 0x12, 0x1c, 0x0a, 0x17, 0x43, 0x49, 0x53, 0x43,
|
||||
0x4f, 0x5f, 0x49, 0x4f, 0x53, 0x5f, 0x50, 0x42, 0x4b, 0x44, 0x46, 0x32, 0x5f, 0x53, 0x48, 0x41,
|
||||
0x32, 0x35, 0x36, 0x10, 0xf0, 0x47, 0x12, 0x15, 0x0a, 0x10, 0x43, 0x49, 0x53, 0x43, 0x4f, 0x5f,
|
||||
0x49, 0x4f, 0x53, 0x5f, 0x53, 0x43, 0x52, 0x59, 0x50, 0x54, 0x10, 0xd4, 0x48, 0x12, 0x12, 0x0a,
|
||||
0x0d, 0x43, 0x49, 0x53, 0x43, 0x4f, 0x5f, 0x50, 0x49, 0x58, 0x5f, 0x4d, 0x44, 0x35, 0x10, 0xe0,
|
||||
0x12, 0x12, 0x1a, 0x0a, 0x15, 0x43, 0x49, 0x54, 0x52, 0x49, 0x58, 0x5f, 0x4e, 0x45, 0x54, 0x53,
|
||||
0x43, 0x41, 0x4c, 0x45, 0x52, 0x5f, 0x53, 0x48, 0x41, 0x31, 0x10, 0xa4, 0x3f, 0x12, 0x1d, 0x0a,
|
||||
0x17, 0x43, 0x49, 0x54, 0x52, 0x49, 0x58, 0x5f, 0x4e, 0x45, 0x54, 0x53, 0x43, 0x41, 0x4c, 0x45,
|
||||
0x52, 0x5f, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x10, 0xb8, 0xad, 0x01, 0x12, 0x08, 0x0a, 0x03,
|
||||
0x44, 0x43, 0x43, 0x10, 0xcc, 0x08, 0x12, 0x09, 0x0a, 0x04, 0x44, 0x43, 0x43, 0x32, 0x10, 0xb4,
|
||||
0x10, 0x12, 0x0f, 0x0a, 0x0a, 0x4d, 0x41, 0x43, 0x4f, 0x53, 0x5f, 0x31, 0x30, 0x5f, 0x38, 0x10,
|
||||
0xbc, 0x37, 0x12, 0x0c, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x8f, 0x4e,
|
||||
0x12, 0x10, 0x0a, 0x0b, 0x42, 0x43, 0x52, 0x59, 0x50, 0x54, 0x5f, 0x55, 0x4e, 0x49, 0x58, 0x10,
|
||||
0x80, 0x19, 0x12, 0x16, 0x0a, 0x11, 0x53, 0x48, 0x41, 0x35, 0x31, 0x32, 0x5f, 0x43, 0x52, 0x59,
|
||||
0x50, 0x54, 0x5f, 0x55, 0x4e, 0x49, 0x58, 0x10, 0x88, 0x0e, 0x2a, 0x32, 0x0a, 0x06, 0x53, 0x74,
|
||||
0x61, 0x74, 0x65, 0x73, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x44, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x0c,
|
||||
0x0a, 0x08, 0x43, 0x52, 0x41, 0x43, 0x4b, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c,
|
||||
0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x2a, 0x3c,
|
||||
0x0a, 0x0e, 0x43, 0x72, 0x61, 0x63, 0x6b, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x12, 0x0f, 0x0a, 0x0b, 0x49, 0x4e, 0x5f, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10,
|
||||
0x00, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4d, 0x50, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x01,
|
||||
0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a, 0x94, 0x01, 0x0a,
|
||||
0x0f, 0x43, 0x72, 0x61, 0x63, 0x6b, 0x41, 0x74, 0x74, 0x61, 0x63, 0x6b, 0x4d, 0x6f, 0x64, 0x65,
|
||||
0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x52, 0x41, 0x49, 0x47, 0x48, 0x54, 0x10, 0x00, 0x12, 0x0f,
|
||||
0x0a, 0x0b, 0x43, 0x4f, 0x4d, 0x42, 0x49, 0x4e, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12,
|
||||
0x0e, 0x0a, 0x0a, 0x42, 0x52, 0x55, 0x54, 0x45, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x10, 0x03, 0x12,
|
||||
0x18, 0x0a, 0x14, 0x48, 0x59, 0x42, 0x52, 0x49, 0x44, 0x5f, 0x57, 0x4f, 0x52, 0x44, 0x4c, 0x49,
|
||||
0x53, 0x54, 0x5f, 0x4d, 0x41, 0x53, 0x4b, 0x10, 0x06, 0x12, 0x18, 0x0a, 0x14, 0x48, 0x59, 0x42,
|
||||
0x52, 0x49, 0x44, 0x5f, 0x4d, 0x41, 0x53, 0x4b, 0x5f, 0x57, 0x4f, 0x52, 0x44, 0x4c, 0x49, 0x53,
|
||||
0x54, 0x10, 0x07, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x53, 0x53, 0x4f, 0x43, 0x49, 0x41, 0x54, 0x49,
|
||||
0x4f, 0x4e, 0x10, 0x09, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x41, 0x54, 0x54, 0x41, 0x43,
|
||||
0x4b, 0x10, 0x0a, 0x2a, 0x44, 0x0a, 0x0d, 0x43, 0x72, 0x61, 0x63, 0x6b, 0x45, 0x6e, 0x63, 0x6f,
|
||||
0x64, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f,
|
||||
0x45, 0x4e, 0x43, 0x4f, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x49, 0x53,
|
||||
0x4f, 0x5f, 0x38, 0x38, 0x35, 0x39, 0x5f, 0x31, 0x35, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x55,
|
||||
0x54, 0x46, 0x5f, 0x33, 0x32, 0x4c, 0x45, 0x10, 0x02, 0x2a, 0x90, 0x01, 0x0a, 0x12, 0x43, 0x72,
|
||||
0x61, 0x63, 0x6b, 0x4f, 0x75, 0x74, 0x66, 0x69, 0x6c, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74,
|
||||
0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x46, 0x4f, 0x52, 0x4d,
|
||||
0x41, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x48, 0x41, 0x53, 0x48, 0x5f, 0x53, 0x41, 0x4c,
|
||||
0x54, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x4c, 0x41, 0x49, 0x4e, 0x10, 0x02, 0x12, 0x0d,
|
||||
0x0a, 0x09, 0x48, 0x45, 0x58, 0x5f, 0x50, 0x4c, 0x41, 0x49, 0x4e, 0x10, 0x03, 0x12, 0x0d, 0x0a,
|
||||
0x09, 0x43, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x50, 0x4f, 0x53, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12,
|
||||
0x54, 0x49, 0x4d, 0x45, 0x53, 0x54, 0x41, 0x4d, 0x50, 0x5f, 0x41, 0x42, 0x53, 0x4f, 0x4c, 0x55,
|
||||
0x54, 0x45, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x54, 0x49, 0x4d, 0x45, 0x53, 0x54, 0x41, 0x4d,
|
||||
0x50, 0x5f, 0x52, 0x45, 0x4c, 0x41, 0x54, 0x49, 0x56, 0x45, 0x10, 0x06, 0x2a, 0x63, 0x0a, 0x14,
|
||||
0x43, 0x72, 0x61, 0x63, 0x6b, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x72, 0x6f,
|
||||
0x66, 0x69, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x18, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f,
|
||||
0x57, 0x4f, 0x52, 0x4b, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x50, 0x52, 0x4f, 0x46, 0x49, 0x4c, 0x45,
|
||||
0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44,
|
||||
0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x49, 0x47, 0x48,
|
||||
0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x49, 0x47, 0x48, 0x54, 0x4d, 0x41, 0x52, 0x45, 0x10,
|
||||
0x04, 0x2a, 0x4e, 0x0a, 0x0d, 0x43, 0x72, 0x61, 0x63, 0x6b, 0x46, 0x69, 0x6c, 0x65, 0x54, 0x79,
|
||||
0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x54, 0x59,
|
||||
0x50, 0x45, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x57, 0x4f, 0x52, 0x44, 0x4c, 0x49, 0x53, 0x54,
|
||||
0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x55, 0x4c, 0x45, 0x53, 0x10, 0x02, 0x12, 0x12, 0x0a,
|
||||
0x0e, 0x4d, 0x41, 0x52, 0x4b, 0x4f, 0x56, 0x5f, 0x48, 0x43, 0x53, 0x54, 0x41, 0x54, 0x32, 0x10,
|
||||
0x03, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
|
||||
0x62, 0x69, 0x73, 0x68, 0x6f, 0x70, 0x66, 0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72,
|
||||
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
|
||||
0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -12798,7 +13018,7 @@ func file_clientpb_client_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_clientpb_client_proto_enumTypes = make([]protoimpl.EnumInfo, 13)
|
||||
var file_clientpb_client_proto_msgTypes = make([]protoimpl.MessageInfo, 130)
|
||||
var file_clientpb_client_proto_msgTypes = make([]protoimpl.MessageInfo, 133)
|
||||
var file_clientpb_client_proto_goTypes = []interface{}{
|
||||
(OutputFormat)(0), // 0: clientpb.OutputFormat
|
||||
(StageProtocol)(0), // 1: clientpb.StageProtocol
|
||||
@@ -12932,20 +13152,23 @@ var file_clientpb_client_proto_goTypes = []interface{}{
|
||||
(*MonitoringProviders)(nil), // 129: clientpb.MonitoringProviders
|
||||
(*MonitoringProvider)(nil), // 130: clientpb.MonitoringProvider
|
||||
(*ResourceID)(nil), // 131: clientpb.ResourceID
|
||||
nil, // 132: clientpb.TrafficEncoderMap.EncodersEntry
|
||||
nil, // 133: clientpb.ImplantBuilds.ConfigsEntry
|
||||
nil, // 134: clientpb.ImplantBuilds.ResourceIDsEntry
|
||||
nil, // 135: clientpb.ImplantBuilds.StagedEntry
|
||||
nil, // 136: clientpb.WebsiteAddContent.ContentsEntry
|
||||
nil, // 137: clientpb.Website.ContentsEntry
|
||||
nil, // 138: clientpb.Host.ExtensionDataEntry
|
||||
nil, // 139: clientpb.ShellcodeEncoderMap.EncodersEntry
|
||||
nil, // 140: clientpb.CrackSyncStatus.ProgressEntry
|
||||
nil, // 141: clientpb.CrackBenchmark.BenchmarksEntry
|
||||
nil, // 142: clientpb.Crackstation.BenchmarksEntry
|
||||
(*commonpb.File)(nil), // 143: commonpb.File
|
||||
(*commonpb.Request)(nil), // 144: commonpb.Request
|
||||
(*commonpb.Response)(nil), // 145: commonpb.Response
|
||||
(*CertificatesReq)(nil), // 132: clientpb.CertificatesReq
|
||||
(*CertificateData)(nil), // 133: clientpb.CertificateData
|
||||
(*CertificateInfo)(nil), // 134: clientpb.CertificateInfo
|
||||
nil, // 135: clientpb.TrafficEncoderMap.EncodersEntry
|
||||
nil, // 136: clientpb.ImplantBuilds.ConfigsEntry
|
||||
nil, // 137: clientpb.ImplantBuilds.ResourceIDsEntry
|
||||
nil, // 138: clientpb.ImplantBuilds.StagedEntry
|
||||
nil, // 139: clientpb.WebsiteAddContent.ContentsEntry
|
||||
nil, // 140: clientpb.Website.ContentsEntry
|
||||
nil, // 141: clientpb.Host.ExtensionDataEntry
|
||||
nil, // 142: clientpb.ShellcodeEncoderMap.EncodersEntry
|
||||
nil, // 143: clientpb.CrackSyncStatus.ProgressEntry
|
||||
nil, // 144: clientpb.CrackBenchmark.BenchmarksEntry
|
||||
nil, // 145: clientpb.Crackstation.BenchmarksEntry
|
||||
(*commonpb.File)(nil), // 146: commonpb.File
|
||||
(*commonpb.Request)(nil), // 147: commonpb.Request
|
||||
(*commonpb.Response)(nil), // 148: commonpb.Response
|
||||
}
|
||||
var file_clientpb_client_proto_depIdxs = []int32{
|
||||
16, // 0: clientpb.Beacons.Beacons:type_name -> clientpb.Beacon
|
||||
@@ -12953,18 +13176,18 @@ var file_clientpb_client_proto_depIdxs = []int32{
|
||||
31, // 2: clientpb.ImplantConfig.ImplantBuilds:type_name -> clientpb.ImplantBuild
|
||||
21, // 3: clientpb.ImplantConfig.C2:type_name -> clientpb.ImplantC2
|
||||
0, // 4: clientpb.ImplantConfig.Format:type_name -> clientpb.OutputFormat
|
||||
143, // 5: clientpb.ImplantConfig.Assets:type_name -> commonpb.File
|
||||
143, // 6: clientpb.TrafficEncoder.Wasm:type_name -> commonpb.File
|
||||
132, // 7: clientpb.TrafficEncoderMap.Encoders:type_name -> clientpb.TrafficEncoderMap.EncodersEntry
|
||||
146, // 5: clientpb.ImplantConfig.Assets:type_name -> commonpb.File
|
||||
146, // 6: clientpb.TrafficEncoder.Wasm:type_name -> commonpb.File
|
||||
135, // 7: clientpb.TrafficEncoderMap.Encoders:type_name -> clientpb.TrafficEncoderMap.EncodersEntry
|
||||
23, // 8: clientpb.TrafficEncoderTests.Encoder:type_name -> clientpb.TrafficEncoder
|
||||
25, // 9: clientpb.TrafficEncoderTests.Tests:type_name -> clientpb.TrafficEncoderTest
|
||||
22, // 10: clientpb.ExternalImplantConfig.Config:type_name -> clientpb.ImplantConfig
|
||||
31, // 11: clientpb.ExternalImplantConfig.Build:type_name -> clientpb.ImplantBuild
|
||||
106, // 12: clientpb.ExternalImplantConfig.HTTPC2:type_name -> clientpb.HTTPC2Config
|
||||
143, // 13: clientpb.ExternalImplantBinary.File:type_name -> commonpb.File
|
||||
133, // 14: clientpb.ImplantBuilds.Configs:type_name -> clientpb.ImplantBuilds.ConfigsEntry
|
||||
134, // 15: clientpb.ImplantBuilds.ResourceIDs:type_name -> clientpb.ImplantBuilds.ResourceIDsEntry
|
||||
135, // 16: clientpb.ImplantBuilds.staged:type_name -> clientpb.ImplantBuilds.StagedEntry
|
||||
146, // 13: clientpb.ExternalImplantBinary.File:type_name -> commonpb.File
|
||||
136, // 14: clientpb.ImplantBuilds.Configs:type_name -> clientpb.ImplantBuilds.ConfigsEntry
|
||||
137, // 15: clientpb.ImplantBuilds.ResourceIDs:type_name -> clientpb.ImplantBuilds.ResourceIDsEntry
|
||||
138, // 16: clientpb.ImplantBuilds.staged:type_name -> clientpb.ImplantBuilds.StagedEntry
|
||||
0, // 17: clientpb.CompilerTarget.Format:type_name -> clientpb.OutputFormat
|
||||
32, // 18: clientpb.Compiler.Targets:type_name -> clientpb.CompilerTarget
|
||||
33, // 19: clientpb.Compiler.CrossCompilers:type_name -> clientpb.CrossCompiler
|
||||
@@ -12978,25 +13201,25 @@ var file_clientpb_client_proto_depIdxs = []int32{
|
||||
51, // 27: clientpb.ListenerJob.DNSConf:type_name -> clientpb.DNSListenerReq
|
||||
52, // 28: clientpb.ListenerJob.HTTPConf:type_name -> clientpb.HTTPListenerReq
|
||||
48, // 29: clientpb.ListenerJob.MultiConf:type_name -> clientpb.MultiplayerListenerReq
|
||||
144, // 30: clientpb.NamedPipesReq.Request:type_name -> commonpb.Request
|
||||
145, // 31: clientpb.NamedPipes.Response:type_name -> commonpb.Response
|
||||
144, // 32: clientpb.TCPPivotReq.Request:type_name -> commonpb.Request
|
||||
145, // 33: clientpb.TCPPivot.Response:type_name -> commonpb.Response
|
||||
147, // 30: clientpb.NamedPipesReq.Request:type_name -> commonpb.Request
|
||||
148, // 31: clientpb.NamedPipes.Response:type_name -> commonpb.Response
|
||||
147, // 32: clientpb.TCPPivotReq.Request:type_name -> commonpb.Request
|
||||
148, // 33: clientpb.TCPPivot.Response:type_name -> commonpb.Response
|
||||
15, // 34: clientpb.Sessions.Sessions:type_name -> clientpb.Session
|
||||
22, // 35: clientpb.GenerateReq.Config:type_name -> clientpb.ImplantConfig
|
||||
143, // 36: clientpb.Generate.File:type_name -> commonpb.File
|
||||
144, // 37: clientpb.MSFReq.Request:type_name -> commonpb.Request
|
||||
144, // 38: clientpb.MSFRemoteReq.Request:type_name -> commonpb.Request
|
||||
146, // 36: clientpb.Generate.File:type_name -> commonpb.File
|
||||
147, // 37: clientpb.MSFReq.Request:type_name -> commonpb.Request
|
||||
147, // 38: clientpb.MSFRemoteReq.Request:type_name -> commonpb.Request
|
||||
1, // 39: clientpb.StagerListenerReq.Protocol:type_name -> clientpb.StageProtocol
|
||||
1, // 40: clientpb.MsfStagerReq.Protocol:type_name -> clientpb.StageProtocol
|
||||
143, // 41: clientpb.MsfStager.File:type_name -> commonpb.File
|
||||
146, // 41: clientpb.MsfStager.File:type_name -> commonpb.File
|
||||
22, // 42: clientpb.GetSystemReq.Config:type_name -> clientpb.ImplantConfig
|
||||
144, // 43: clientpb.GetSystemReq.Request:type_name -> commonpb.Request
|
||||
147, // 43: clientpb.GetSystemReq.Request:type_name -> commonpb.Request
|
||||
22, // 44: clientpb.MigrateReq.Config:type_name -> clientpb.ImplantConfig
|
||||
3, // 45: clientpb.MigrateReq.Encoder:type_name -> clientpb.ShellcodeEncoder
|
||||
144, // 46: clientpb.MigrateReq.Request:type_name -> commonpb.Request
|
||||
144, // 47: clientpb.CreateTunnelReq.Request:type_name -> commonpb.Request
|
||||
144, // 48: clientpb.CloseTunnelReq.Request:type_name -> commonpb.Request
|
||||
147, // 46: clientpb.MigrateReq.Request:type_name -> commonpb.Request
|
||||
147, // 47: clientpb.CreateTunnelReq.Request:type_name -> commonpb.Request
|
||||
147, // 48: clientpb.CloseTunnelReq.Request:type_name -> commonpb.Request
|
||||
15, // 49: clientpb.PivotGraphEntry.Session:type_name -> clientpb.Session
|
||||
75, // 50: clientpb.PivotGraphEntry.Children:type_name -> clientpb.PivotGraphEntry
|
||||
75, // 51: clientpb.PivotGraph.Children:type_name -> clientpb.PivotGraphEntry
|
||||
@@ -13005,23 +13228,23 @@ var file_clientpb_client_proto_depIdxs = []int32{
|
||||
42, // 54: clientpb.Event.Job:type_name -> clientpb.Job
|
||||
77, // 55: clientpb.Event.Client:type_name -> clientpb.Client
|
||||
80, // 56: clientpb.Operators.Operators:type_name -> clientpb.Operator
|
||||
136, // 57: clientpb.WebsiteAddContent.Contents:type_name -> clientpb.WebsiteAddContent.ContentsEntry
|
||||
137, // 58: clientpb.Website.Contents:type_name -> clientpb.Website.ContentsEntry
|
||||
139, // 57: clientpb.WebsiteAddContent.Contents:type_name -> clientpb.WebsiteAddContent.ContentsEntry
|
||||
140, // 58: clientpb.Website.Contents:type_name -> clientpb.Website.ContentsEntry
|
||||
84, // 59: clientpb.Websites.Websites:type_name -> clientpb.Website
|
||||
2, // 60: clientpb.Loot.FileType:type_name -> clientpb.FileType
|
||||
143, // 61: clientpb.Loot.File:type_name -> commonpb.File
|
||||
146, // 61: clientpb.Loot.File:type_name -> commonpb.File
|
||||
87, // 62: clientpb.AllLoot.Loot:type_name -> clientpb.Loot
|
||||
89, // 63: clientpb.Host.IOCs:type_name -> clientpb.IOC
|
||||
138, // 64: clientpb.Host.ExtensionData:type_name -> clientpb.Host.ExtensionDataEntry
|
||||
141, // 64: clientpb.Host.ExtensionData:type_name -> clientpb.Host.ExtensionDataEntry
|
||||
91, // 65: clientpb.AllHosts.Hosts:type_name -> clientpb.Host
|
||||
144, // 66: clientpb.DllHijackReq.Request:type_name -> commonpb.Request
|
||||
145, // 67: clientpb.DllHijack.Response:type_name -> commonpb.Response
|
||||
144, // 68: clientpb.BackdoorReq.Request:type_name -> commonpb.Request
|
||||
145, // 69: clientpb.Backdoor.Response:type_name -> commonpb.Response
|
||||
147, // 66: clientpb.DllHijackReq.Request:type_name -> commonpb.Request
|
||||
148, // 67: clientpb.DllHijack.Response:type_name -> commonpb.Response
|
||||
147, // 68: clientpb.BackdoorReq.Request:type_name -> commonpb.Request
|
||||
148, // 69: clientpb.Backdoor.Response:type_name -> commonpb.Response
|
||||
3, // 70: clientpb.ShellcodeEncodeReq.Encoder:type_name -> clientpb.ShellcodeEncoder
|
||||
144, // 71: clientpb.ShellcodeEncodeReq.Request:type_name -> commonpb.Request
|
||||
145, // 72: clientpb.ShellcodeEncode.Response:type_name -> commonpb.Response
|
||||
139, // 73: clientpb.ShellcodeEncoderMap.Encoders:type_name -> clientpb.ShellcodeEncoderMap.EncodersEntry
|
||||
147, // 71: clientpb.ShellcodeEncodeReq.Request:type_name -> commonpb.Request
|
||||
148, // 72: clientpb.ShellcodeEncode.Response:type_name -> commonpb.Response
|
||||
142, // 73: clientpb.ShellcodeEncoderMap.Encoders:type_name -> clientpb.ShellcodeEncoderMap.EncodersEntry
|
||||
22, // 74: clientpb.ExternalGenerateReq.Config:type_name -> clientpb.ImplantConfig
|
||||
102, // 75: clientpb.Builders.Builders:type_name -> clientpb.Builder
|
||||
32, // 76: clientpb.Builder.Targets:type_name -> clientpb.CompilerTarget
|
||||
@@ -13041,10 +13264,10 @@ var file_clientpb_client_proto_depIdxs = []int32{
|
||||
120, // 90: clientpb.Crackstations.Crackstations:type_name -> clientpb.Crackstation
|
||||
6, // 91: clientpb.CrackstationStatus.State:type_name -> clientpb.States
|
||||
117, // 92: clientpb.CrackstationStatus.Syncing:type_name -> clientpb.CrackSyncStatus
|
||||
140, // 93: clientpb.CrackSyncStatus.Progress:type_name -> clientpb.CrackSyncStatus.ProgressEntry
|
||||
141, // 94: clientpb.CrackBenchmark.Benchmarks:type_name -> clientpb.CrackBenchmark.BenchmarksEntry
|
||||
143, // 93: clientpb.CrackSyncStatus.Progress:type_name -> clientpb.CrackSyncStatus.ProgressEntry
|
||||
144, // 94: clientpb.CrackBenchmark.Benchmarks:type_name -> clientpb.CrackBenchmark.BenchmarksEntry
|
||||
124, // 95: clientpb.CrackTask.Command:type_name -> clientpb.CrackCommand
|
||||
142, // 96: clientpb.Crackstation.Benchmarks:type_name -> clientpb.Crackstation.BenchmarksEntry
|
||||
145, // 96: clientpb.Crackstation.Benchmarks:type_name -> clientpb.Crackstation.BenchmarksEntry
|
||||
121, // 97: clientpb.Crackstation.CUDA:type_name -> clientpb.CUDABackendInfo
|
||||
123, // 98: clientpb.Crackstation.Metal:type_name -> clientpb.MetalBackendInfo
|
||||
122, // 99: clientpb.Crackstation.OpenCL:type_name -> clientpb.OpenCLBackendInfo
|
||||
@@ -13058,18 +13281,19 @@ var file_clientpb_client_proto_depIdxs = []int32{
|
||||
12, // 107: clientpb.CrackFile.Type:type_name -> clientpb.CrackFileType
|
||||
128, // 108: clientpb.CrackFile.Chunks:type_name -> clientpb.CrackFileChunk
|
||||
130, // 109: clientpb.MonitoringProviders.providers:type_name -> clientpb.MonitoringProvider
|
||||
23, // 110: clientpb.TrafficEncoderMap.EncodersEntry.value:type_name -> clientpb.TrafficEncoder
|
||||
22, // 111: clientpb.ImplantBuilds.ConfigsEntry.value:type_name -> clientpb.ImplantConfig
|
||||
131, // 112: clientpb.ImplantBuilds.ResourceIDsEntry.value:type_name -> clientpb.ResourceID
|
||||
81, // 113: clientpb.WebsiteAddContent.ContentsEntry.value:type_name -> clientpb.WebContent
|
||||
81, // 114: clientpb.Website.ContentsEntry.value:type_name -> clientpb.WebContent
|
||||
90, // 115: clientpb.Host.ExtensionDataEntry.value:type_name -> clientpb.ExtensionData
|
||||
3, // 116: clientpb.ShellcodeEncoderMap.EncodersEntry.value:type_name -> clientpb.ShellcodeEncoder
|
||||
117, // [117:117] is the sub-list for method output_type
|
||||
117, // [117:117] is the sub-list for method input_type
|
||||
117, // [117:117] is the sub-list for extension type_name
|
||||
117, // [117:117] is the sub-list for extension extendee
|
||||
0, // [0:117] is the sub-list for field type_name
|
||||
133, // 110: clientpb.CertificateInfo.info:type_name -> clientpb.CertificateData
|
||||
23, // 111: clientpb.TrafficEncoderMap.EncodersEntry.value:type_name -> clientpb.TrafficEncoder
|
||||
22, // 112: clientpb.ImplantBuilds.ConfigsEntry.value:type_name -> clientpb.ImplantConfig
|
||||
131, // 113: clientpb.ImplantBuilds.ResourceIDsEntry.value:type_name -> clientpb.ResourceID
|
||||
81, // 114: clientpb.WebsiteAddContent.ContentsEntry.value:type_name -> clientpb.WebContent
|
||||
81, // 115: clientpb.Website.ContentsEntry.value:type_name -> clientpb.WebContent
|
||||
90, // 116: clientpb.Host.ExtensionDataEntry.value:type_name -> clientpb.ExtensionData
|
||||
3, // 117: clientpb.ShellcodeEncoderMap.EncodersEntry.value:type_name -> clientpb.ShellcodeEncoder
|
||||
118, // [118:118] is the sub-list for method output_type
|
||||
118, // [118:118] is the sub-list for method input_type
|
||||
118, // [118:118] is the sub-list for extension type_name
|
||||
118, // [118:118] is the sub-list for extension extendee
|
||||
0, // [0:118] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_clientpb_client_proto_init() }
|
||||
@@ -14506,6 +14730,42 @@ func file_clientpb_client_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_clientpb_client_proto_msgTypes[119].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CertificatesReq); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_clientpb_client_proto_msgTypes[120].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CertificateData); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_clientpb_client_proto_msgTypes[121].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CertificateInfo); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
@@ -14513,7 +14773,7 @@ func file_clientpb_client_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_clientpb_client_proto_rawDesc,
|
||||
NumEnums: 13,
|
||||
NumMessages: 130,
|
||||
NumMessages: 133,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -1308,4 +1308,25 @@ message ResourceID {
|
||||
string Type = 2;
|
||||
string Name = 3;
|
||||
uint64 Value = 4;
|
||||
}
|
||||
|
||||
// [ Certificates ] ----------------------------------------
|
||||
|
||||
message CertificatesReq {
|
||||
uint32 CategoryFilters = 1;
|
||||
string CN = 2;
|
||||
}
|
||||
|
||||
message CertificateData {
|
||||
string CN = 1;
|
||||
string CreationTime = 2;
|
||||
string ValidityStart = 3;
|
||||
string ValidityExpiry = 4;
|
||||
string Type = 5;
|
||||
string KeyAlgorithm = 6;
|
||||
string ID = 7;
|
||||
}
|
||||
|
||||
message CertificateInfo {
|
||||
repeated CertificateData info = 1;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v5.26.1
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v4.25.3
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v5.26.1
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v4.25.3
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
|
||||
+990
-981
File diff suppressed because it is too large.
Load diff
@@ -106,6 +106,9 @@ service SliverRPC {
|
||||
rpc BuilderTrigger(clientpb.Event) returns (commonpb.Empty);
|
||||
rpc Builders(commonpb.Empty) returns (clientpb.Builders);
|
||||
|
||||
// *** Certificates ***
|
||||
rpc GetCertificateInfo(clientpb.CertificatesReq) returns (clientpb.CertificateInfo);
|
||||
|
||||
// *** Crackstation ***
|
||||
rpc CrackstationRegister(clientpb.Crackstation)
|
||||
returns (stream clientpb.Event);
|
||||
|
||||
+396
-542
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v5.26.1
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v4.25.3
|
||||
// source: sliverpb/sliver.proto
|
||||
|
||||
package sliverpb
|
||||
@@ -173,7 +173,8 @@ func (PeerFailureType) EnumDescriptor() ([]byte, []int) {
|
||||
}
|
||||
|
||||
// Envelope - Used to encode implant<->server messages since we
|
||||
// cannot use gRPC due to the various transports used.
|
||||
//
|
||||
// cannot use gRPC due to the various transports used.
|
||||
type Envelope struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -746,7 +747,8 @@ func (x *CloseSession) GetRequest() *commonpb.Request {
|
||||
}
|
||||
|
||||
// Ping - Not ICMP, just sends a rount trip message to an implant to
|
||||
// see if it's still responding.
|
||||
//
|
||||
// see if it's still responding.
|
||||
type Ping struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -3684,7 +3686,8 @@ func (x *CurrentTokenOwner) GetResponse() *commonpb.Response {
|
||||
}
|
||||
|
||||
// InvokeGetSystemReq - Implant-side version of GetSystemReq, this message
|
||||
// contains the .Data based on the client's req.Config
|
||||
//
|
||||
// contains the .Data based on the client's req.Config
|
||||
type InvokeGetSystemReq struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
||||
+10
-1
@@ -810,7 +810,16 @@ func newHTTPSessionID() string {
|
||||
func getRemoteAddr(req *http.Request) string {
|
||||
ipAddress := req.Header.Get("X-Real-Ip")
|
||||
if ipAddress == "" {
|
||||
ipAddress = req.Header.Get("X-Forwarded-For")
|
||||
xForwardedFor := req.Header.Get("X-Forwarded-For")
|
||||
if xForwardedFor != "" {
|
||||
ips := strings.Split(xForwardedFor, ",")
|
||||
if len(ips) > 0 {
|
||||
// Extracts original client ip address
|
||||
ipAddress = strings.TrimSpace(ips[0])
|
||||
} else {
|
||||
ipAddress = xForwardedFor
|
||||
}
|
||||
}
|
||||
}
|
||||
if ipAddress == "" {
|
||||
return req.RemoteAddr
|
||||
|
||||
@@ -58,6 +58,7 @@ var (
|
||||
ErrNonUniqueFileExt = errors.New("implant config must specify unique file extensions")
|
||||
ErrQueryParamNameLen = errors.New("implant config url query parameter names must be 3 or more characters")
|
||||
ErrDuplicateStageExt = errors.New("stager extension is already used in another C2 profile")
|
||||
ErrDuplicateStartSessionExt = errors.New("start session extension is already used in another C2 profile")
|
||||
ErrDuplicateC2ProfileName = errors.New("C2 Profile name is already in use")
|
||||
ErrUserAgentIllegalCharacters = errors.New("user agent cannot contain the ` character")
|
||||
|
||||
|
||||
+201
-7
@@ -27,6 +27,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -258,7 +259,7 @@ func ImplantProfileByName(name string) (*clientpb.ImplantProfile, error) {
|
||||
return nil, err
|
||||
}
|
||||
err = Session().Where(models.ImplantConfig{
|
||||
ImplantProfileID: profile.ID,
|
||||
ImplantProfileID: &profile.ID,
|
||||
}).First(&config).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -332,6 +333,31 @@ func SearchStageExtensions(stagerExtension string, profileName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// used to prevent duplicate start session extensions
|
||||
func SearchStartSessionExtensions(StartSessionFileExt string, profileName string) error {
|
||||
c2Config := models.HttpC2ImplantConfig{}
|
||||
err := Session().Where(&models.HttpC2ImplantConfig{
|
||||
StartSessionFileExtension: StartSessionFileExt,
|
||||
}).Find(&c2Config).Error
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c2Config.StartSessionFileExtension != "" && profileName != "" {
|
||||
httpC2Config := models.HttpC2Config{}
|
||||
err = Session().Where(&models.HttpC2Config{ID: c2Config.HttpC2ConfigID}).Find(&httpC2Config).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if httpC2Config.Name == profileName {
|
||||
return nil
|
||||
}
|
||||
return configs.ErrDuplicateStartSessionExt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadHTTPC2ConfigByName(name string) (*clientpb.HTTPC2Config, error) {
|
||||
if len(name) < 1 {
|
||||
return nil, ErrRecordNotFound
|
||||
@@ -367,7 +393,7 @@ func LoadHTTPC2ConfigByName(name string) (*clientpb.HTTPC2Config, error) {
|
||||
// load headers
|
||||
c2ImplantHeaders := []models.HttpC2Header{}
|
||||
err = Session().Where(&models.HttpC2Header{
|
||||
HttpC2ImplantConfigID: c2ImplantConfig.ID,
|
||||
HttpC2ImplantConfigID: &c2ImplantConfig.ID,
|
||||
}).Find(&c2ImplantHeaders).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -397,7 +423,7 @@ func LoadHTTPC2ConfigByName(name string) (*clientpb.HTTPC2Config, error) {
|
||||
// load headers
|
||||
c2ServerHeaders := []models.HttpC2Header{}
|
||||
err = Session().Where(&models.HttpC2Header{
|
||||
HttpC2ServerConfigID: c2ServerConfig.ID,
|
||||
HttpC2ServerConfigID: &c2ServerConfig.ID,
|
||||
}).Find(&c2ServerHeaders).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -436,20 +462,74 @@ func SaveHTTPC2Config(httpC2Config *clientpb.HTTPC2Config) error {
|
||||
func HTTPC2ConfigUpdate(newConf *clientpb.HTTPC2Config, oldConf *clientpb.HTTPC2Config) error {
|
||||
clientID, _ := uuid.FromString(oldConf.ImplantConfig.ID)
|
||||
c2Config := models.HTTPC2ConfigFromProtobuf(newConf)
|
||||
err := Session().Where(&models.ImplantConfig{
|
||||
|
||||
err := Session().Where(&models.HttpC2PathSegment{
|
||||
HttpC2ImplantConfigID: clientID,
|
||||
}).Delete(&models.HttpC2PathSegment{})
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
|
||||
err = Session().Where(&models.ImplantConfig{
|
||||
ID: clientID,
|
||||
}).Updates(c2Config.ImplantConfig)
|
||||
if err != nil {
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
|
||||
for _, segment := range c2Config.ImplantConfig.PathSegments {
|
||||
segment.HttpC2ImplantConfigID = clientID
|
||||
err = Session().Clauses(clause.OnConflict{
|
||||
UpdateAll: true,
|
||||
}).Create(&segment)
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
}
|
||||
|
||||
serverID, _ := uuid.FromString(oldConf.ServerConfig.ID)
|
||||
|
||||
err = Session().Where(&models.HttpC2Cookie{
|
||||
HttpC2ServerConfigID: serverID,
|
||||
}).Delete(&models.HttpC2Cookie{})
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
|
||||
err = Session().Where(&models.HttpC2Header{
|
||||
HttpC2ServerConfigID: &serverID,
|
||||
}).Delete(&models.HttpC2Header{})
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
|
||||
serverID, _ := uuid.FromString(oldConf.ImplantConfig.ID)
|
||||
err = Session().Where(&models.HttpC2ServerConfig{
|
||||
ID: serverID,
|
||||
}).Updates(c2Config.ServerConfig)
|
||||
if err != nil {
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
|
||||
for _, cookie := range c2Config.ServerConfig.Cookies {
|
||||
cookie.HttpC2ServerConfigID = serverID
|
||||
err = Session().Clauses(clause.OnConflict{
|
||||
UpdateAll: true,
|
||||
}).Create(&cookie)
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
}
|
||||
|
||||
for _, header := range c2Config.ServerConfig.Headers {
|
||||
header.HttpC2ServerConfigID = &serverID
|
||||
err = Session().Clauses(clause.OnConflict{
|
||||
UpdateAll: true,
|
||||
}).Create(&header)
|
||||
if err.Error != nil {
|
||||
return err.Error
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -537,6 +617,35 @@ func ListenerJobs() ([]*clientpb.ListenerJob, error) {
|
||||
}
|
||||
|
||||
func DeleteListener(JobID uint32) error {
|
||||
// Determine which type of listener this is and delete its record from the corresponding table
|
||||
var deleteErr error
|
||||
// JobID is unique so if the JobID exists, it is the only record in the table with that JobID
|
||||
listener := &models.ListenerJob{}
|
||||
result := Session().Where(&models.ListenerJob{JobID: JobID}).First(&listener)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if listener == nil {
|
||||
return fmt.Errorf("Job ID %d not found in database", JobID)
|
||||
}
|
||||
listenerID := listener.ID
|
||||
switch listener.Type {
|
||||
case constants.HttpStr, constants.HttpsStr:
|
||||
deleteErr = Session().Where(&models.HTTPListener{ListenerJobID: listenerID}).Delete(&models.HTTPListener{}).Error
|
||||
case constants.DnsStr:
|
||||
deleteErr = Session().Where(&models.DNSListener{ListenerJobID: listenerID}).Delete(&models.DNSListener{}).Error
|
||||
case constants.MtlsStr:
|
||||
deleteErr = Session().Where(&models.MtlsListener{ListenerJobID: listenerID}).Delete(&models.MtlsListener{}).Error
|
||||
case constants.WGStr:
|
||||
deleteErr = Session().Where(&models.WGListener{ListenerJobID: listenerID}).Delete(&models.WGListener{}).Error
|
||||
case constants.MultiplayerModeStr:
|
||||
deleteErr = Session().Where(&models.MultiplayerListener{ListenerJobID: listenerID}).Delete(&models.MultiplayerListener{}).Error
|
||||
}
|
||||
|
||||
if deleteErr != nil {
|
||||
return deleteErr
|
||||
}
|
||||
|
||||
return Session().Where(&models.ListenerJob{JobID: JobID}).Delete(&models.ListenerJob{}).Error
|
||||
}
|
||||
|
||||
@@ -587,6 +696,9 @@ func DeleteProfile(name string) error {
|
||||
|
||||
uuid, _ := uuid.FromString(profile.Config.ID)
|
||||
|
||||
// delete linked ImplantC2
|
||||
err = Session().Where(&models.ImplantC2{ImplantConfigID: uuid}).Delete(&models.ImplantC2{}).Error
|
||||
|
||||
// delete linked ImplantConfig
|
||||
err = Session().Where(&models.ImplantConfig{ID: uuid}).Delete(&models.ImplantConfig{}).Error
|
||||
|
||||
@@ -1304,3 +1416,85 @@ func SaveResourceID(r *clientpb.ResourceID) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Certificates
|
||||
func GetCertificateInfo(categoryOptions uint32, cn string) ([]*models.Certificate, error) {
|
||||
// Enumerate the options for transport and role
|
||||
// First, defining the transport filters
|
||||
const (
|
||||
MTLSTransport uint32 = 1 << iota
|
||||
HTTPSTransport
|
||||
AllTransports
|
||||
)
|
||||
|
||||
// Now, defining the role filters
|
||||
const (
|
||||
ServerRole uint32 = 8 << iota
|
||||
ImplantRole
|
||||
AllRoles
|
||||
)
|
||||
|
||||
certInfo := []*models.Certificate{}
|
||||
dbSession := Session()
|
||||
var err error
|
||||
|
||||
switch categoryOptions {
|
||||
case MTLSTransport | ServerRole:
|
||||
if cn != "" {
|
||||
err = dbSession.Where(&models.Certificate{CommonName: cn, CAType: "mtls-server"}).Find(&certInfo).Error
|
||||
} else {
|
||||
err = dbSession.Where(&models.Certificate{CAType: "mtls-server"}).Find(&certInfo).Error
|
||||
}
|
||||
case (MTLSTransport | ImplantRole), (AllTransports | ImplantRole):
|
||||
if cn != "" {
|
||||
err = dbSession.Where(&models.Certificate{CommonName: cn, CAType: "mtls-implant"}).Find(&certInfo).Error
|
||||
} else {
|
||||
err = dbSession.Where(&models.Certificate{CAType: "mtls-implant"}).Find(&certInfo).Error
|
||||
}
|
||||
case MTLSTransport | AllRoles:
|
||||
if cn != "" {
|
||||
err = dbSession.Where(&models.Certificate{CommonName: cn}).Where(
|
||||
dbSession.Where(&models.Certificate{CAType: "mtls-server"}).Or(
|
||||
&models.Certificate{CAType: "mtls-implant"},
|
||||
),
|
||||
).Find(&certInfo).Error
|
||||
} else {
|
||||
err = dbSession.Where(dbSession.Where(&models.Certificate{CAType: "mtls-server"}).Or(
|
||||
&models.Certificate{CAType: "mtls-implant"})).Find(&certInfo).Error
|
||||
}
|
||||
case HTTPSTransport | ImplantRole:
|
||||
// This does not currently exist (HTTPS certs are not issued for implants), so return nothing
|
||||
return certInfo, nil
|
||||
case (HTTPSTransport | ServerRole), (HTTPSTransport | AllRoles):
|
||||
if cn != "" {
|
||||
err = dbSession.Where(&models.Certificate{CommonName: cn, CAType: "https"}).Find(&certInfo).Error
|
||||
} else {
|
||||
err = dbSession.Where(&models.Certificate{CAType: "https"}).Find(&certInfo).Error
|
||||
}
|
||||
case AllTransports | ServerRole:
|
||||
if cn != "" {
|
||||
err = dbSession.Where(&models.Certificate{CommonName: cn}).Where(
|
||||
dbSession.Where(&models.Certificate{CAType: "mtls-server"}).Or(
|
||||
&models.Certificate{CAType: "https"},
|
||||
),
|
||||
).Find(&certInfo).Error
|
||||
} else {
|
||||
err = dbSession.Where(dbSession.Where(&models.Certificate{CAType: "mtls-server"}).Or(
|
||||
&models.Certificate{CAType: "https"},
|
||||
),
|
||||
).Find(&certInfo).Error
|
||||
}
|
||||
case AllRoles | AllTransports:
|
||||
if cn != "" {
|
||||
err = dbSession.Where(&models.Certificate{CommonName: cn}).Find(&certInfo).Error
|
||||
} else {
|
||||
err = dbSession.Where(&models.Certificate{}).Find(&certInfo).Error
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Processing of the data can occur at the caller. It does not need to happen here.
|
||||
return certInfo, nil
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
// Host - Represents a host machine
|
||||
type Host struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
HostUUID uuid.UUID `gorm:"type:uuid;"`
|
||||
HostUUID uuid.UUID `gorm:"type:uuid;unique"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
|
||||
Hostname string
|
||||
|
||||
@@ -203,9 +203,9 @@ func (h *HttpC2Cookie) ToProtobuf() *clientpb.HTTPC2Cookie {
|
||||
|
||||
// HttpC2Header - HTTP C2 Header (server and implant)
|
||||
type HttpC2Header struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
HttpC2ServerConfigID uuid.UUID `gorm:"type:uuid;"`
|
||||
HttpC2ImplantConfigID uuid.UUID `gorm:"type:uuid;"`
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
HttpC2ServerConfigID *uuid.UUID `gorm:"type:uuid;"`
|
||||
HttpC2ImplantConfigID *uuid.UUID `gorm:"type:uuid;"`
|
||||
|
||||
Method string
|
||||
Name string
|
||||
|
||||
@@ -144,7 +144,7 @@ func ImplantBuildFromProtobuf(ib *clientpb.ImplantBuild) *ImplantBuild {
|
||||
// ImplantConfig - An implant build configuration
|
||||
type ImplantConfig struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
ImplantProfileID uuid.UUID
|
||||
ImplantProfileID *uuid.UUID
|
||||
|
||||
ImplantBuilds []ImplantBuild
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
@@ -237,9 +237,8 @@ func (ic *ImplantConfig) ToProtobuf() *clientpb.ImplantConfig {
|
||||
implantBuilds = append(implantBuilds, implantBuild.ToProtobuf())
|
||||
}
|
||||
config := &clientpb.ImplantConfig{
|
||||
ID: ic.ID.String(),
|
||||
ImplantBuilds: implantBuilds,
|
||||
ImplantProfileID: ic.ImplantProfileID.String(),
|
||||
ID: ic.ID.String(),
|
||||
ImplantBuilds: implantBuilds,
|
||||
|
||||
IsBeacon: ic.IsBeacon,
|
||||
BeaconInterval: ic.BeaconInterval,
|
||||
@@ -286,6 +285,11 @@ func (ic *ImplantConfig) ToProtobuf() *clientpb.ImplantConfig {
|
||||
IncludeWG: ic.IncludeWG,
|
||||
IncludeTCP: ic.IncludeTCP,
|
||||
}
|
||||
|
||||
if ic.ImplantProfileID != nil {
|
||||
config.ImplantProfileID = ic.ImplantProfileID.String()
|
||||
}
|
||||
|
||||
// Copy Canary Domains
|
||||
config.CanaryDomains = []string{}
|
||||
for _, canaryDomain := range ic.CanaryDomains {
|
||||
@@ -432,7 +436,11 @@ func ImplantConfigFromProtobuf(pbConfig *clientpb.ImplantConfig) *ImplantConfig
|
||||
cfg.ID = id
|
||||
cfg.ImplantBuilds = implantBuilds
|
||||
profileID, _ := uuid.FromString(pbConfig.ImplantProfileID)
|
||||
cfg.ImplantProfileID = profileID
|
||||
if profileID == uuid.Nil {
|
||||
cfg.ImplantProfileID = nil
|
||||
} else {
|
||||
cfg.ImplantProfileID = &profileID
|
||||
}
|
||||
|
||||
cfg.IsBeacon = pbConfig.IsBeacon
|
||||
cfg.BeaconInterval = pbConfig.BeaconInterval
|
||||
|
||||
+21
-14
@@ -50,7 +50,14 @@ func newDBClient() *gorm.DB {
|
||||
panic(fmt.Sprintf("Unknown DB Dialect: '%s'", dbConfig.Dialect))
|
||||
}
|
||||
|
||||
err := dbClient.AutoMigrate(
|
||||
// We cannot pass all of these into AutoMigrate at once because if one fails, subsequent models will not be created
|
||||
var allDBModels []interface{} = append(make([]interface{}, 0),
|
||||
&models.HttpC2Header{},
|
||||
&models.HttpC2ServerConfig{},
|
||||
&models.HttpC2ImplantConfig{},
|
||||
&models.HttpC2Config{},
|
||||
&models.HttpC2URLParameter{},
|
||||
&models.HttpC2PathSegment{},
|
||||
&models.Beacon{},
|
||||
&models.BeaconTask{},
|
||||
&models.DNSCanary{},
|
||||
@@ -62,30 +69,25 @@ func newDBClient() *gorm.DB {
|
||||
&models.CrackFileChunk{},
|
||||
&models.Certificate{},
|
||||
&models.Host{},
|
||||
&models.HttpC2Config{},
|
||||
&models.HttpC2ImplantConfig{},
|
||||
&models.HttpC2ServerConfig{},
|
||||
&models.HttpC2Header{},
|
||||
&models.HttpC2PathSegment{},
|
||||
&models.KeyValue{},
|
||||
&models.WGKeys{},
|
||||
&models.WGPeer{},
|
||||
&models.ResourceID{},
|
||||
&models.HttpC2Cookie{},
|
||||
&models.HttpC2URLParameter{},
|
||||
&models.IOC{},
|
||||
&models.ExtensionData{},
|
||||
&models.ImplantBuild{},
|
||||
&models.ImplantProfile{},
|
||||
&models.ImplantConfig{},
|
||||
&models.ImplantBuild{},
|
||||
&models.ImplantC2{},
|
||||
&models.EncoderAsset{},
|
||||
&models.KeyExHistory{},
|
||||
&models.KeyValue{},
|
||||
&models.CanaryDomain{},
|
||||
&models.Loot{},
|
||||
&models.Credential{},
|
||||
&models.Operator{},
|
||||
&models.Website{},
|
||||
&models.WebContent{},
|
||||
&models.WGKeys{},
|
||||
&models.WGPeer{},
|
||||
&models.ListenerJob{},
|
||||
&models.HTTPListener{},
|
||||
&models.DNSListener{},
|
||||
@@ -94,10 +96,15 @@ func newDBClient() *gorm.DB {
|
||||
&models.MtlsListener{},
|
||||
&models.DnsDomain{},
|
||||
&models.MonitoringProvider{},
|
||||
&models.ResourceID{},
|
||||
)
|
||||
if err != nil {
|
||||
clientLog.Error(err)
|
||||
|
||||
var err error
|
||||
|
||||
for _, model := range allDBModels {
|
||||
err = dbClient.AutoMigrate(model)
|
||||
if err != nil {
|
||||
clientLog.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get generic database object sql.DB to use its functions
|
||||
|
||||
@@ -77,7 +77,11 @@ func ImplantConfigSave(config *clientpb.ImplantConfig) (*clientpb.ImplantConfig,
|
||||
|
||||
} else {
|
||||
id, _ := uuid.FromString(dbConfig.ImplantProfileID)
|
||||
modelConfig.ImplantProfileID = id
|
||||
if id == uuid.Nil {
|
||||
modelConfig.ImplantProfileID = nil
|
||||
} else {
|
||||
modelConfig.ImplantProfileID = &id
|
||||
}
|
||||
|
||||
// this avoids gorm saving duplicate c2 objects ...
|
||||
tempC2 := modelConfig.C2
|
||||
|
||||
@@ -54,7 +54,11 @@ func SaveImplantProfile(pbProfile *clientpb.ImplantProfile) (*clientpb.ImplantPr
|
||||
profile.ImplantConfig.ID = configID
|
||||
|
||||
profileID, _ := uuid.FromString(dbProfile.ID)
|
||||
profile.ImplantConfig.ImplantProfileID = profileID
|
||||
if profileID == uuid.Nil {
|
||||
profile.ImplantConfig.ImplantProfileID = nil
|
||||
} else {
|
||||
profile.ImplantConfig.ImplantProfileID = &profileID
|
||||
}
|
||||
|
||||
for _, c2 := range dbProfile.Config.C2 {
|
||||
id, _ := uuid.FromString(c2.ID)
|
||||
|
||||
@@ -68,6 +68,11 @@ func (rpc *Server) SaveHTTPC2Profile(ctx context.Context, req *clientpb.HTTPC2Co
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.SearchStartSessionExtensions(req.C2Config.ImplantConfig.StartSessionFileExtension, profileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpC2Config, err := db.LoadHTTPC2ConfigByName(req.C2Config.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const (
|
||||
timeFormat = "2006-01-02 15:04:05 UTC-0700"
|
||||
)
|
||||
|
||||
func convertDatabaseRecordToProtobuf(record *models.Certificate) *clientpb.CertificateData {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
certData := &clientpb.CertificateData{}
|
||||
// These values are default until we parse the cert
|
||||
certData.ValidityStart = "Unknown (could not parse certificate)"
|
||||
certData.ValidityExpiry = "Unknown (could not parse certificate)"
|
||||
|
||||
certData.CN = record.CommonName
|
||||
certData.ID = record.ID.String()
|
||||
certData.CreationTime = record.CreatedAt.Format(timeFormat)
|
||||
switch record.CAType {
|
||||
case certs.MtlsImplantCA:
|
||||
certData.Type = "MTLS (Implant)"
|
||||
case certs.MtlsServerCA:
|
||||
certData.Type = "MTLS (Server)"
|
||||
case certs.HTTPSCA:
|
||||
certData.Type = "HTTPS"
|
||||
default:
|
||||
certData.Type = record.CAType
|
||||
}
|
||||
|
||||
switch record.KeyType {
|
||||
case certs.ECCKey:
|
||||
certData.KeyAlgorithm = "ECC"
|
||||
case certs.RSAKey:
|
||||
certData.KeyAlgorithm = "RSA"
|
||||
default:
|
||||
certData.KeyAlgorithm = strings.ToUpper(record.KeyType)
|
||||
}
|
||||
|
||||
// To get the validity period, we need to parse the certificate information
|
||||
pemBlock, _ := pem.Decode([]byte(record.CertificatePEM))
|
||||
if pemBlock == nil {
|
||||
return certData
|
||||
}
|
||||
|
||||
certificate, err := x509.ParseCertificate(pemBlock.Bytes)
|
||||
if err != nil {
|
||||
return certData
|
||||
}
|
||||
|
||||
certData.ValidityStart = certificate.NotBefore.Format(timeFormat)
|
||||
certData.ValidityExpiry = certificate.NotAfter.Format(timeFormat)
|
||||
|
||||
return certData
|
||||
}
|
||||
|
||||
func (rpc *Server) GetCertificateInfo(ctx context.Context, req *clientpb.CertificatesReq) (*clientpb.CertificateInfo, error) {
|
||||
certInfo := clientpb.CertificateInfo{}
|
||||
|
||||
certInfoDB, err := db.GetCertificateInfo(req.CategoryFilters, req.CN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certInfo.Info = make([]*clientpb.CertificateData, len(certInfoDB))
|
||||
|
||||
for idx, record := range certInfoDB {
|
||||
certInfo.Info[idx] = convertDatabaseRecordToProtobuf(record)
|
||||
}
|
||||
|
||||
return &certInfo, nil
|
||||
}
|
||||
+12
-2
@@ -1,3 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package pb
|
||||
@@ -5,6 +6,7 @@ package pb
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/cheggaaa/pb/v3/termutil"
|
||||
)
|
||||
@@ -24,17 +26,25 @@ func (p *Pool) print(first bool) bool {
|
||||
}
|
||||
coords.X = 0
|
||||
|
||||
err = termutil.SetCursorPos(coords)
|
||||
err = termutil.SetCursorPos(coords)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
cols, err := termutil.TerminalWidth()
|
||||
if err != nil {
|
||||
cols = defaultBarWidth
|
||||
}
|
||||
isFinished := true
|
||||
for _, bar := range p.bars {
|
||||
if !bar.IsFinished() {
|
||||
isFinished = false
|
||||
}
|
||||
out += fmt.Sprintf("\r%s\n", bar.String())
|
||||
result := bar.String()
|
||||
if r := cols - CellCount(result); r > 0 {
|
||||
result += strings.Repeat(" ", r)
|
||||
}
|
||||
out += fmt.Sprintf("\r%s\n", result)
|
||||
}
|
||||
if p.Output != nil {
|
||||
fmt.Fprint(p.Output, out)
|
||||
|
||||
+7
-1
@@ -1,3 +1,4 @@
|
||||
//go:build linux || darwin || freebsd || netbsd || openbsd || solaris || dragonfly || plan9 || aix
|
||||
// +build linux darwin freebsd netbsd openbsd solaris dragonfly plan9 aix
|
||||
|
||||
package pb
|
||||
@@ -5,6 +6,7 @@ package pb
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cheggaaa/pb/v3/termutil"
|
||||
)
|
||||
@@ -31,7 +33,11 @@ func (p *Pool) print(first bool) bool {
|
||||
isFinished = false
|
||||
}
|
||||
bar.SetWidth(cols)
|
||||
out += fmt.Sprintf("\r%s\n", bar.String())
|
||||
result := bar.String()
|
||||
if r := cols - CellCount(result); r > 0 {
|
||||
result += strings.Repeat(" ", r)
|
||||
}
|
||||
out += fmt.Sprintf("\r%s\n", result)
|
||||
}
|
||||
if p.Output != nil {
|
||||
fmt.Fprint(p.Output, out)
|
||||
|
||||
+1
-2
@@ -138,9 +138,8 @@ func lockEcho() (err error) {
|
||||
}
|
||||
|
||||
newState := oldState
|
||||
const ENABLE_ECHO_INPUT = 0x0004
|
||||
const ENABLE_LINE_INPUT = 0x0002
|
||||
newState = newState & (^(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT))
|
||||
newState = newState & (^ENABLE_LINE_INPUT)
|
||||
if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(newState), 0); e != 0 {
|
||||
err = fmt.Errorf("Can't set terminal settings: %v", e)
|
||||
return
|
||||
|
||||
+9
-4
@@ -269,7 +269,7 @@ func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, c.wrap(fmt.Sprint(a...)))
|
||||
return fmt.Fprintln(w, c.wrap(sprintln(a...)))
|
||||
}
|
||||
|
||||
// Println formats using the default formats for its operands and writes to
|
||||
@@ -278,7 +278,7 @@ func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(Output, c.wrap(fmt.Sprint(a...)))
|
||||
return fmt.Fprintln(Output, c.wrap(sprintln(a...)))
|
||||
}
|
||||
|
||||
// Sprint is just like Print, but returns a string instead of printing it.
|
||||
@@ -288,7 +288,7 @@ func (c *Color) Sprint(a ...interface{}) string {
|
||||
|
||||
// Sprintln is just like Println, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(c.Sprint(a...))
|
||||
return c.wrap(sprintln(a...)) + "\n"
|
||||
}
|
||||
|
||||
// Sprintf is just like Printf, but returns a string instead of printing it.
|
||||
@@ -370,7 +370,7 @@ func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return fmt.Sprintln(c.Sprint(a...))
|
||||
return c.wrap(sprintln(a...)) + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,3 +648,8 @@ func HiCyanString(format string, a ...interface{}) string { return colorString(f
|
||||
func HiWhiteString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiWhite, a...)
|
||||
}
|
||||
|
||||
// sprintln is a helper function to format a string with fmt.Sprintln and trim the trailing newline.
|
||||
func sprintln(a ...interface{}) string {
|
||||
return strings.TrimSuffix(fmt.Sprintln(a...), "\n")
|
||||
}
|
||||
+19
-26
@@ -13,7 +13,8 @@ import (
|
||||
|
||||
var (
|
||||
sqliteSeparator = "`|\"|'|\t"
|
||||
indexRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)CREATE(?: UNIQUE)? INDEX [%v]?[\w\d-]+[%v]? ON (.*)$`, sqliteSeparator, sqliteSeparator))
|
||||
uniqueRegexp = regexp.MustCompile(fmt.Sprintf(`^CONSTRAINT [%v]?[\w-]+[%v]? UNIQUE (.*)$`, sqliteSeparator, sqliteSeparator))
|
||||
indexRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)CREATE(?: UNIQUE)? INDEX [%v]?[\w\d-]+[%v]?(?s:.*?)ON (.*)$`, sqliteSeparator, sqliteSeparator))
|
||||
tableRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)(CREATE TABLE [%v]?[\w\d-]+[%v]?)(?:\s*\((.*)\))?`, sqliteSeparator, sqliteSeparator))
|
||||
separatorRegexp = regexp.MustCompile(fmt.Sprintf("[%v]", sqliteSeparator))
|
||||
columnsRegexp = regexp.MustCompile(fmt.Sprintf(`[(,][%v]?(\w+)[%v]?`, sqliteSeparator, sqliteSeparator))
|
||||
@@ -103,11 +104,24 @@ func parseDDL(strs ...string) (*ddl, error) {
|
||||
|
||||
for _, f := range result.fields {
|
||||
fUpper := strings.ToUpper(f)
|
||||
if strings.HasPrefix(fUpper, "CHECK") ||
|
||||
strings.HasPrefix(fUpper, "CONSTRAINT") {
|
||||
if strings.HasPrefix(fUpper, "CHECK") {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(fUpper, "CONSTRAINT") {
|
||||
matches := uniqueRegexp.FindStringSubmatch(f)
|
||||
if len(matches) > 0 {
|
||||
if columns := getAllColumns(matches[1]); len(columns) == 1 {
|
||||
for idx, column := range result.columns {
|
||||
if column.NameValue.String == columns[0] {
|
||||
column.UniqueValue = sql.NullBool{Bool: true, Valid: true}
|
||||
result.columns[idx] = column
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(fUpper, "PRIMARY KEY") {
|
||||
for _, name := range getAllColumns(f) {
|
||||
for idx, column := range result.columns {
|
||||
@@ -159,14 +173,7 @@ func parseDDL(strs ...string) (*ddl, error) {
|
||||
}
|
||||
}
|
||||
} else if matches := indexRegexp.FindStringSubmatch(str); len(matches) > 0 {
|
||||
for _, column := range getAllColumns(matches[1]) {
|
||||
for idx, c := range result.columns {
|
||||
if c.NameValue.String == column {
|
||||
c.UniqueValue = sql.NullBool{Bool: strings.ToUpper(strings.Fields(str)[1]) == "UNIQUE", Valid: true}
|
||||
result.columns[idx] = c
|
||||
}
|
||||
}
|
||||
}
|
||||
// don't report Unique by UniqueIndex
|
||||
} else {
|
||||
return nil, errors.New("invalid DDL")
|
||||
}
|
||||
@@ -268,20 +275,6 @@ func (d *ddl) getColumns() []string {
|
||||
return res
|
||||
}
|
||||
|
||||
func (d *ddl) alterColumn(name, sql string) bool {
|
||||
reg := regexp.MustCompile("^(`|'|\"| )" + regexp.QuoteMeta(name) + "(`|'|\"| ) .*?$")
|
||||
|
||||
for i := 0; i < len(d.fields); i++ {
|
||||
if reg.MatchString(d.fields[i]) {
|
||||
d.fields[i] = sql
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
d.fields = append(d.fields, sql)
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *ddl) removeColumn(name string) bool {
|
||||
reg := regexp.MustCompile("^(`|'|\"| )" + regexp.QuoteMeta(name) + "(`|'|\"| ) .*?$")
|
||||
|
||||
|
||||
+64
-40
@@ -79,14 +79,28 @@ func (m Migrator) AlterColumn(value interface{}, name string) error {
|
||||
return m.RunWithoutForeignKey(func() error {
|
||||
return m.recreateTable(value, nil, func(ddl *ddl, stmt *gorm.Statement) (*ddl, []interface{}, error) {
|
||||
if field := stmt.Schema.LookUpField(name); field != nil {
|
||||
if ddl.alterColumn(field.DBName, fmt.Sprintf("`%s` ?", field.DBName)) {
|
||||
return nil, nil, fmt.Errorf("field `%s` not found in origin ddl, ddl= '%s'", name, ddl.compile())
|
||||
var sqlArgs []interface{}
|
||||
for i, f := range ddl.fields {
|
||||
if matches := columnRegexp.FindStringSubmatch(f); len(matches) > 1 && matches[1] == field.DBName {
|
||||
ddl.fields[i] = fmt.Sprintf("`%v` ?", field.DBName)
|
||||
sqlArgs = []interface{}{m.FullDataTypeOf(field)}
|
||||
// table created by old version might look like `CREATE TABLE ? (? varchar(10) UNIQUE)`.
|
||||
// FullDataTypeOf doesn't contain UNIQUE, so we need to add unique constraint.
|
||||
if strings.Contains(strings.ToUpper(matches[3]), " UNIQUE") {
|
||||
uniName := m.DB.NamingStrategy.UniqueName(stmt.Table, field.DBName)
|
||||
uni, _ := m.GuessConstraintInterfaceAndTable(stmt, uniName)
|
||||
if uni != nil {
|
||||
uniSQL, uniArgs := uni.Build()
|
||||
ddl.addConstraint(uniName, uniSQL)
|
||||
sqlArgs = append(sqlArgs, uniArgs...)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ddl, []interface{}{m.FullDataTypeOf(field)}, nil
|
||||
return ddl, sqlArgs, nil
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("failed to alter field with name `%s`", name)
|
||||
return nil, nil, fmt.Errorf("failed to alter field with name %v", name)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -153,7 +167,7 @@ func (m Migrator) DropColumn(value interface{}, name string) error {
|
||||
|
||||
func (m Migrator) CreateConstraint(value interface{}, name string) error {
|
||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
|
||||
constraint, table := m.GuessConstraintInterfaceAndTable(stmt, name)
|
||||
|
||||
return m.recreateTable(value, &table,
|
||||
func(ddl *ddl, stmt *gorm.Statement) (*ddl, []interface{}, error) {
|
||||
@@ -164,12 +178,8 @@ func (m Migrator) CreateConstraint(value interface{}, name string) error {
|
||||
)
|
||||
|
||||
if constraint != nil {
|
||||
constraintName = constraint.Name
|
||||
constraintSql, constraintValues = buildConstraint(constraint)
|
||||
} else if chk != nil {
|
||||
constraintName = chk.Name
|
||||
constraintSql = "CONSTRAINT ? CHECK (?)"
|
||||
constraintValues = []interface{}{clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint}}
|
||||
constraintName = constraint.GetName()
|
||||
constraintSql, constraintValues = constraint.Build()
|
||||
} else {
|
||||
return nil, nil, nil
|
||||
}
|
||||
@@ -182,11 +192,9 @@ func (m Migrator) CreateConstraint(value interface{}, name string) error {
|
||||
|
||||
func (m Migrator) DropConstraint(value interface{}, name string) error {
|
||||
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
|
||||
constraint, table := m.GuessConstraintInterfaceAndTable(stmt, name)
|
||||
if constraint != nil {
|
||||
name = constraint.Name
|
||||
} else if chk != nil {
|
||||
name = chk.Name
|
||||
name = constraint.GetName()
|
||||
}
|
||||
|
||||
return m.recreateTable(value, &table,
|
||||
@@ -200,11 +208,9 @@ func (m Migrator) DropConstraint(value interface{}, name string) error {
|
||||
func (m Migrator) HasConstraint(value interface{}, name string) bool {
|
||||
var count int64
|
||||
m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
constraint, chk, table := m.GuessConstraintAndTable(stmt, name)
|
||||
constraint, table := m.GuessConstraintInterfaceAndTable(stmt, name)
|
||||
if constraint != nil {
|
||||
name = constraint.Name
|
||||
} else if chk != nil {
|
||||
name = chk.Name
|
||||
name = constraint.GetName()
|
||||
}
|
||||
|
||||
m.DB.Raw(
|
||||
@@ -317,26 +323,44 @@ func (m Migrator) DropIndex(value interface{}, name string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func buildConstraint(constraint *schema.Constraint) (sql string, results []interface{}) {
|
||||
sql = "CONSTRAINT ? FOREIGN KEY ? REFERENCES ??"
|
||||
if constraint.OnDelete != "" {
|
||||
sql += " ON DELETE " + constraint.OnDelete
|
||||
}
|
||||
type Index struct {
|
||||
Seq int
|
||||
Name string
|
||||
Unique bool
|
||||
Origin string
|
||||
Partial bool
|
||||
}
|
||||
|
||||
if constraint.OnUpdate != "" {
|
||||
sql += " ON UPDATE " + constraint.OnUpdate
|
||||
}
|
||||
|
||||
var foreignKeys, references []interface{}
|
||||
for _, field := range constraint.ForeignKeys {
|
||||
foreignKeys = append(foreignKeys, clause.Column{Name: field.DBName})
|
||||
}
|
||||
|
||||
for _, field := range constraint.References {
|
||||
references = append(references, clause.Column{Name: field.DBName})
|
||||
}
|
||||
results = append(results, clause.Table{Name: constraint.Name}, foreignKeys, clause.Table{Name: constraint.ReferenceSchema.Table}, references)
|
||||
return
|
||||
// GetIndexes return Indexes []gorm.Index and execErr error,
|
||||
// See the [doc]
|
||||
//
|
||||
// [doc]: https://www.sqlite.org/pragma.html#pragma_index_list
|
||||
func (m Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) {
|
||||
indexes := make([]gorm.Index, 0)
|
||||
err := m.RunWithValue(value, func(stmt *gorm.Statement) error {
|
||||
rst := make([]*Index, 0)
|
||||
if err := m.DB.Debug().Raw("SELECT * FROM PRAGMA_index_list(?)", stmt.Table).Scan(&rst).Error; err != nil { // alias `PRAGMA index_list(?)`
|
||||
return err
|
||||
}
|
||||
for _, index := range rst {
|
||||
if index.Origin == "u" { // skip the index was created by a UNIQUE constraint
|
||||
continue
|
||||
}
|
||||
var columns []string
|
||||
if err := m.DB.Raw("SELECT name FROM PRAGMA_index_info(?)", index.Name).Scan(&columns).Error; err != nil { // alias `PRAGMA index_info(?)`
|
||||
return err
|
||||
}
|
||||
indexes = append(indexes, &migrator.Index{
|
||||
TableName: stmt.Table,
|
||||
NameValue: index.Name,
|
||||
ColumnList: columns,
|
||||
PrimaryKeyValue: sql.NullBool{Bool: index.Origin == "pk", Valid: true}, // The exceptions are INTEGER PRIMARY KEY
|
||||
UniqueValue: sql.NullBool{Bool: index.Unique, Valid: true},
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return indexes, err
|
||||
}
|
||||
|
||||
func (m Migrator) getRawDDL(table string) (string, error) {
|
||||
|
||||
+2
-2
@@ -7,9 +7,9 @@ Pretty-print tables into ASCII/Unicode strings.
|
||||
- Add Header(s) and Footer(s) (`AppendHeader`/`AppendFooter`)
|
||||
- Add a Separator manually after any Row (`AppendSeparator`)
|
||||
- Auto Index Rows (1, 2, 3 ...) and Columns (A, B, C, ...) (`SetAutoIndex`)
|
||||
- Auto Merge (_not supported in CSV/HTML/Markdown/TSV modes_)
|
||||
- Auto Merge (_not supported in CSV/Markdown/TSV modes_)
|
||||
- Cells in a Row (`RowConfig.AutoMerge`)
|
||||
- Columns (`ColumnConfig.AutoMerge`)
|
||||
- Columns (`ColumnConfig.AutoMerge`) (_not supported in HTML mode_)
|
||||
- Limit the length of
|
||||
- Rows (`SetAllowedRowLength`)
|
||||
- Columns (`ColumnConfig.Width*`)
|
||||
|
||||
+26
-3
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"html"
|
||||
"strings"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -113,9 +115,9 @@ func (t *Table) htmlRenderColumn(out *strings.Builder, colStr string) {
|
||||
out.WriteString(colStr)
|
||||
}
|
||||
|
||||
func (t *Table) htmlRenderColumnAttributes(out *strings.Builder, colIdx int, hint renderHint) {
|
||||
func (t *Table) htmlRenderColumnAttributes(out *strings.Builder, colIdx int, hint renderHint, alignOverride text.Align) {
|
||||
// determine the HTML "align"/"valign" property values
|
||||
align := t.getAlign(colIdx, hint).HTMLProperty()
|
||||
align := alignOverride.HTMLProperty()
|
||||
vAlign := t.getVAlign(colIdx, hint).HTMLProperty()
|
||||
// determine the HTML "class" property values for the colors
|
||||
class := t.getColumnColors(colIdx, hint).HTMLProperty()
|
||||
@@ -158,11 +160,31 @@ func (t *Table) htmlRenderRow(out *strings.Builder, row rowStr, hint renderHint)
|
||||
t.htmlRenderColumnAutoIndex(out, hint)
|
||||
}
|
||||
|
||||
align := t.getAlign(colIdx, hint)
|
||||
rowConfig := t.getRowConfig(hint)
|
||||
extraColumnsRendered := 0
|
||||
if rowConfig.AutoMerge && !hint.isSeparatorRow {
|
||||
// get the real row to consider all lines in each column instead of just
|
||||
// looking at the current "line"
|
||||
rowUnwrapped := t.getRow(hint.rowNumber-1, hint)
|
||||
for idx := colIdx + 1; idx < len(rowUnwrapped); idx++ {
|
||||
if rowUnwrapped[colIdx] != rowUnwrapped[idx] {
|
||||
break
|
||||
}
|
||||
align = rowConfig.getAutoMergeAlign()
|
||||
extraColumnsRendered++
|
||||
}
|
||||
}
|
||||
|
||||
colStr, colTagName := t.htmlGetColStrAndTag(row, colIdx, hint)
|
||||
// write the row
|
||||
out.WriteString(" <")
|
||||
out.WriteString(colTagName)
|
||||
t.htmlRenderColumnAttributes(out, colIdx, hint)
|
||||
t.htmlRenderColumnAttributes(out, colIdx, hint, align)
|
||||
if extraColumnsRendered > 0 {
|
||||
out.WriteString(" colspan=")
|
||||
out.WriteString(fmt.Sprint(extraColumnsRendered + 1))
|
||||
}
|
||||
out.WriteString(">")
|
||||
if len(colStr) == 0 {
|
||||
out.WriteString(t.style.HTML.EmptyColumn)
|
||||
@@ -172,6 +194,7 @@ func (t *Table) htmlRenderRow(out *strings.Builder, row rowStr, hint renderHint)
|
||||
out.WriteString("</")
|
||||
out.WriteString(colTagName)
|
||||
out.WriteString(">\n")
|
||||
colIdx += extraColumnsRendered
|
||||
}
|
||||
out.WriteString(" </tr>\n")
|
||||
}
|
||||
|
||||
+87
-63
@@ -4,84 +4,108 @@
|
||||
[](https://goreportcard.com/report/github.com/ncruces/go-sqlite3)
|
||||
[](https://github.com/ncruces/go-sqlite3/wiki/Test-coverage-report)
|
||||
|
||||
Go module `github.com/ncruces/go-sqlite3` wraps a [WASM](https://webassembly.org/) build of [SQLite](https://sqlite.org/),
|
||||
and uses [wazero](https://wazero.io/) to provide `cgo`-free SQLite bindings.
|
||||
Go module `github.com/ncruces/go-sqlite3` is a `cgo`-free [SQLite](https://sqlite.org/) wrapper.\
|
||||
It provides a [`database/sql`](https://pkg.go.dev/database/sql) compatible driver,
|
||||
as well as direct access to most of the [C SQLite API](https://sqlite.org/cintro.html).
|
||||
|
||||
- Package [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3)
|
||||
wraps the [C SQLite API](https://www.sqlite.org/cintro.html)
|
||||
([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-package)).
|
||||
- Package [`github.com/ncruces/go-sqlite3/driver`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver)
|
||||
provides a [`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||
([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)).
|
||||
- Package [`github.com/ncruces/go-sqlite3/embed`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/embed)
|
||||
embeds a build of SQLite into your application.
|
||||
- Package [`github.com/ncruces/go-sqlite3/vfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs)
|
||||
wraps the [C SQLite VFS API](https://www.sqlite.org/vfs.html) and provides a pure Go implementation.
|
||||
- Package [`github.com/ncruces/go-sqlite3/gormlite`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/gormlite)
|
||||
provides a [GORM](https://gorm.io) driver.
|
||||
It wraps a [Wasm](https://webassembly.org/) [build](embed/) of SQLite,
|
||||
and uses [wazero](https://wazero.io/) as the runtime.\
|
||||
Go, wazero and [`x/sys`](https://pkg.go.dev/golang.org/x/sys) are the _only_ runtime dependencies [^1].
|
||||
|
||||
### Packages
|
||||
|
||||
- [`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3)
|
||||
wraps the [C SQLite API](https://sqlite.org/cintro.html)
|
||||
([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3#example-package)).
|
||||
- [`github.com/ncruces/go-sqlite3/driver`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver)
|
||||
provides a [`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||
([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)).
|
||||
- [`github.com/ncruces/go-sqlite3/embed`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/embed)
|
||||
embeds a build of SQLite into your application.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs)
|
||||
wraps the [C SQLite VFS API](https://sqlite.org/vfs.html) and provides a pure Go implementation.
|
||||
- [`github.com/ncruces/go-sqlite3/gormlite`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/gormlite)
|
||||
provides a [GORM](https://gorm.io) driver.
|
||||
|
||||
### Extensions
|
||||
|
||||
- [`github.com/ncruces/go-sqlite3/ext/array`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/array)
|
||||
provides the [`array`](https://sqlite.org/carray.html) table-valued function.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/blobio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/blobio)
|
||||
simplifies [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html).
|
||||
- [`github.com/ncruces/go-sqlite3/ext/csv`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/csv)
|
||||
reads [comma-separated values](https://sqlite.org/csv.html).
|
||||
- [`github.com/ncruces/go-sqlite3/ext/fileio`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/fileio)
|
||||
reads, writes and lists files.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/hash`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/hash)
|
||||
provides cryptographic hash functions.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/lines`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/lines)
|
||||
reads data [line-by-line](https://github.com/asg017/sqlite-lines).
|
||||
- [`github.com/ncruces/go-sqlite3/ext/pivot`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/pivot)
|
||||
creates [pivot tables](https://github.com/jakethaw/pivot_vtab).
|
||||
- [`github.com/ncruces/go-sqlite3/ext/statement`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/statement)
|
||||
creates [parameterized views](https://github.com/0x09/sqlite-statement-vtab).
|
||||
- [`github.com/ncruces/go-sqlite3/ext/stats`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/stats)
|
||||
provides [statistics](https://www.oreilly.com/library/view/sql-in-a/9780596155322/ch04s02.html) functions.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/unicode`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/unicode)
|
||||
provides [Unicode aware](https://sqlite.org/src/dir/ext/icu) functions.
|
||||
- [`github.com/ncruces/go-sqlite3/ext/zorder`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/ext/zorder)
|
||||
maps multidimensional data to one dimension.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs/memdb`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/memdb)
|
||||
implements an in-memory VFS.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs/readervfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/readervfs)
|
||||
implements a VFS for immutable databases.
|
||||
- [`github.com/ncruces/go-sqlite3/vfs/adiantum`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs/adiantum)
|
||||
wraps a VFS to offer encryption at rest.
|
||||
|
||||
### Advanced features
|
||||
|
||||
- [incremental BLOB I/O](https://sqlite.org/c3ref/blob_open.html)
|
||||
- [nested transactions](https://sqlite.org/lang_savepoint.html)
|
||||
- [custom functions](https://sqlite.org/c3ref/create_function.html)
|
||||
- [virtual tables](https://sqlite.org/vtab.html)
|
||||
- [custom VFSes](https://sqlite.org/vfs.html)
|
||||
- [online backup](https://sqlite.org/backup.html)
|
||||
- [JSON support](https://sqlite.org/json1.html)
|
||||
- [math functions](https://sqlite.org/lang_mathfunc.html)
|
||||
- [full-text search](https://sqlite.org/fts5.html)
|
||||
- [geospatial search](https://sqlite.org/geopoly.html)
|
||||
- [encryption at rest](vfs/adiantum/README.md)
|
||||
- [and more…](embed/README.md)
|
||||
|
||||
### Caveats
|
||||
|
||||
This module replaces the SQLite [OS Interface](https://www.sqlite.org/vfs.html)
|
||||
(aka VFS) with a [pure Go](vfs/) implementation.
|
||||
This has benefits, but also comes with some drawbacks.
|
||||
This module replaces the SQLite [OS Interface](https://sqlite.org/vfs.html)
|
||||
(aka VFS) with a [pure Go](vfs/) implementation,
|
||||
which has advantages and disadvantages.
|
||||
|
||||
#### Write-Ahead Logging
|
||||
Read more about the Go VFS design [here](vfs/README.md).
|
||||
|
||||
Because WASM does not support shared memory,
|
||||
[WAL](https://www.sqlite.org/wal.html) support is [limited](https://www.sqlite.org/wal.html#noshm).
|
||||
### Testing
|
||||
|
||||
To work around this limitation, SQLite is compiled with
|
||||
[`SQLITE_DEFAULT_LOCKING_MODE=1`](https://www.sqlite.org/compile.html#default_locking_mode),
|
||||
making `EXCLUSIVE` the default locking mode.
|
||||
For non-WAL databases, `NORMAL` locking mode can be activated with
|
||||
[`PRAGMA locking_mode=NORMAL`](https://www.sqlite.org/pragma.html#pragma_locking_mode).
|
||||
This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3/wiki/Test-coverage-report).
|
||||
It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and
|
||||
[wazero's](https://tetrate.io/blog/introducing-wazero-from-tetrate/#:~:text=Rock%2Dsolid%20test%20approach) thorough testing.
|
||||
|
||||
Because connection pooling is incompatible with `EXCLUSIVE` locking mode,
|
||||
the `database/sql` driver defaults to `NORMAL` locking mode.
|
||||
To open WAL databases, or use `EXCLUSIVE` locking mode,
|
||||
disable connection pooling by calling
|
||||
[`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns).
|
||||
Every commit is [tested](.github/workflows/test.yml) on
|
||||
Linux (amd64/arm64/386/riscv64), macOS (amd64/arm64), Windows, FreeBSD and illumos.
|
||||
The Go VFS is tested by running SQLite's
|
||||
[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c).
|
||||
|
||||
#### POSIX Advisory Locks
|
||||
### Performance
|
||||
|
||||
POSIX advisory locks, which SQLite uses, are
|
||||
[broken by design](https://www.sqlite.org/src/artifact/90c4fa?ln=1073-1161).
|
||||
Perfomance of the [`database/sql`](https://pkg.go.dev/database/sql) driver is
|
||||
[competitive](https://github.com/cvilsmeier/go-sqlite-bench) with alternatives.
|
||||
|
||||
On Linux, macOS and illumos, this module uses
|
||||
[OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
|
||||
to synchronize access to database files.
|
||||
OFD locks are fully compatible with process-associated POSIX advisory locks.
|
||||
|
||||
On BSD Unixes, this module uses
|
||||
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2).
|
||||
BSD locks may _not_ be compatible with process-associated POSIX advisory locks.
|
||||
|
||||
#### Testing
|
||||
|
||||
The pure Go VFS is tested by running an unmodified build of SQLite's
|
||||
[mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c)
|
||||
on Linux, macOS and Windows.
|
||||
Performance is tested by running
|
||||
The Wasm and VFS layers are also tested by running SQLite's
|
||||
[speedtest1](https://github.com/sqlite/sqlite/blob/master/test/speedtest1.c).
|
||||
|
||||
### Roadmap
|
||||
|
||||
- [ ] advanced SQLite features
|
||||
- [x] nested transactions
|
||||
- [x] incremental BLOB I/O
|
||||
- [x] online backup
|
||||
- [ ] session extension
|
||||
- [ ] custom VFSes
|
||||
- [x] custom VFS API
|
||||
- [x] in-memory VFS
|
||||
- [x] read-only VFS, wrapping an [`io.ReaderAt`](https://pkg.go.dev/io#ReaderAt)
|
||||
- [ ] cloud-based VFS, based on [Cloud Backed SQLite](https://sqlite.org/cloudsqlite/doc/trunk/www/index.wiki)
|
||||
- [ ] custom SQL functions
|
||||
|
||||
### Alternatives
|
||||
|
||||
- [`modernc.org/sqlite`](https://pkg.go.dev/modernc.org/sqlite)
|
||||
- [`crawshaw.io/sqlite`](https://pkg.go.dev/crawshaw.io/sqlite)
|
||||
- [`github.com/mattn/go-sqlite3`](https://pkg.go.dev/github.com/mattn/go-sqlite3)
|
||||
- [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite)
|
||||
- [`github.com/zombiezen/go-sqlite`](https://pkg.go.dev/github.com/zombiezen/go-sqlite)
|
||||
|
||||
[^1]: anything else you find in `go.mod` is either a test dependency,
|
||||
or needed by one of the extensions.
|
||||
+18
-18
@@ -2,7 +2,7 @@ package sqlite3
|
||||
|
||||
// Backup is an handle to an ongoing online backup operation.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/backup.html
|
||||
// https://sqlite.org/c3ref/backup.html
|
||||
type Backup struct {
|
||||
c *Conn
|
||||
handle uint32
|
||||
@@ -15,7 +15,7 @@ type Backup struct {
|
||||
// and blocks until the entire backup is complete.
|
||||
// Use [Conn.BackupInit] for incremental backup.
|
||||
//
|
||||
// https://www.sqlite.org/backup.html
|
||||
// https://sqlite.org/backup.html
|
||||
func (src *Conn) Backup(srcDB, dstURI string) error {
|
||||
b, err := src.BackupInit(srcDB, dstURI)
|
||||
if err != nil {
|
||||
@@ -31,7 +31,7 @@ func (src *Conn) Backup(srcDB, dstURI string) error {
|
||||
// Restore opens the SQLite database file srcURI,
|
||||
// and blocks until the entire restore is complete.
|
||||
//
|
||||
// https://www.sqlite.org/backup.html
|
||||
// https://sqlite.org/backup.html
|
||||
func (dst *Conn) Restore(dstDB, srcURI string) error {
|
||||
src, err := dst.openDB(srcURI, OPEN_READONLY|OPEN_URI)
|
||||
if err != nil {
|
||||
@@ -52,7 +52,7 @@ func (dst *Conn) Restore(dstDB, srcURI string) error {
|
||||
// then initializes a backup that copies the contents of srcDB on the src connection
|
||||
// to the "main" database in dstURI.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupinit
|
||||
func (src *Conn) BackupInit(srcDB, dstURI string) (*Backup, error) {
|
||||
dst, err := src.openDB(dstURI, OPEN_READWRITE|OPEN_CREATE|OPEN_URI)
|
||||
if err != nil {
|
||||
@@ -62,7 +62,7 @@ func (src *Conn) BackupInit(srcDB, dstURI string) (*Backup, error) {
|
||||
}
|
||||
|
||||
func (c *Conn) backupInit(dst uint32, dstName string, src uint32, srcName string) (*Backup, error) {
|
||||
defer c.arena.reset()
|
||||
defer c.arena.mark()()
|
||||
dstPtr := c.arena.string(dstName)
|
||||
srcPtr := c.arena.string(srcName)
|
||||
|
||||
@@ -71,13 +71,13 @@ func (c *Conn) backupInit(dst uint32, dstName string, src uint32, srcName string
|
||||
other = src
|
||||
}
|
||||
|
||||
r := c.call(c.api.backupInit,
|
||||
r := c.call("sqlite3_backup_init",
|
||||
uint64(dst), uint64(dstPtr),
|
||||
uint64(src), uint64(srcPtr))
|
||||
if r == 0 {
|
||||
defer c.closeDB(other)
|
||||
r = c.call(c.api.errcode, uint64(dst))
|
||||
return nil, c.module.error(r, dst)
|
||||
r = c.call("sqlite3_errcode", uint64(dst))
|
||||
return nil, c.sqlite.error(r, dst)
|
||||
}
|
||||
|
||||
return &Backup{
|
||||
@@ -91,13 +91,13 @@ func (c *Conn) backupInit(dst uint32, dstName string, src uint32, srcName string
|
||||
//
|
||||
// It is safe to close a nil, zero or closed Backup.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish
|
||||
func (b *Backup) Close() error {
|
||||
if b == nil || b.handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := b.c.call(b.c.api.backupFinish, uint64(b.handle))
|
||||
r := b.c.call("sqlite3_backup_finish", uint64(b.handle))
|
||||
b.c.closeDB(b.otherc)
|
||||
b.handle = 0
|
||||
return b.c.error(r)
|
||||
@@ -106,9 +106,9 @@ func (b *Backup) Close() error {
|
||||
// Step copies up to nPage pages between the source and destination databases.
|
||||
// If nPage is negative, all remaining source pages are copied.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupstep
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupstep
|
||||
func (b *Backup) Step(nPage int) (done bool, err error) {
|
||||
r := b.c.call(b.c.api.backupStep, uint64(b.handle), uint64(nPage))
|
||||
r := b.c.call("sqlite3_backup_step", uint64(b.handle), uint64(nPage))
|
||||
if r == _DONE {
|
||||
return true, nil
|
||||
}
|
||||
@@ -118,17 +118,17 @@ func (b *Backup) Step(nPage int) (done bool, err error) {
|
||||
// Remaining returns the number of pages still to be backed up
|
||||
// at the conclusion of the most recent [Backup.Step].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining
|
||||
func (b *Backup) Remaining() int {
|
||||
r := b.c.call(b.c.api.backupRemaining, uint64(b.handle))
|
||||
return int(r)
|
||||
r := b.c.call("sqlite3_backup_remaining", uint64(b.handle))
|
||||
return int(int32(r))
|
||||
}
|
||||
|
||||
// PageCount returns the total number of pages in the source database
|
||||
// at the conclusion of the most recent [Backup.Step].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount
|
||||
// https://sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount
|
||||
func (b *Backup) PageCount() int {
|
||||
r := b.c.call(b.c.api.backupPageCount, uint64(b.handle))
|
||||
return int(r)
|
||||
r := b.c.call("sqlite3_backup_pagecount", uint64(b.handle))
|
||||
return int(int32(r))
|
||||
}
|
||||
+30
-27
@@ -15,7 +15,7 @@ type ZeroBlob int64
|
||||
//
|
||||
// It implements [io.ReadWriteSeeker] for incremental BLOB I/O.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob.html
|
||||
// https://sqlite.org/c3ref/blob.html
|
||||
type Blob struct {
|
||||
c *Conn
|
||||
bytes int64
|
||||
@@ -27,10 +27,10 @@ var _ io.ReadWriteSeeker = &Blob{}
|
||||
|
||||
// OpenBlob opens a BLOB for incremental I/O.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_open.html
|
||||
// https://sqlite.org/c3ref/blob_open.html
|
||||
func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob, error) {
|
||||
c.checkInterrupt()
|
||||
defer c.arena.reset()
|
||||
defer c.arena.mark()()
|
||||
blobPtr := c.arena.new(ptrlen)
|
||||
dbPtr := c.arena.string(db)
|
||||
tablePtr := c.arena.string(table)
|
||||
@@ -41,7 +41,7 @@ func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob,
|
||||
flags = 1
|
||||
}
|
||||
|
||||
r := c.call(c.api.blobOpen, uint64(c.handle),
|
||||
r := c.call("sqlite3_blob_open", uint64(c.handle),
|
||||
uint64(dbPtr), uint64(tablePtr), uint64(columnPtr),
|
||||
uint64(row), flags, uint64(blobPtr))
|
||||
|
||||
@@ -51,7 +51,7 @@ func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob,
|
||||
|
||||
blob := Blob{c: c}
|
||||
blob.handle = util.ReadUint32(c.mod, blobPtr)
|
||||
blob.bytes = int64(c.call(c.api.blobBytes, uint64(blob.handle)))
|
||||
blob.bytes = int64(c.call("sqlite3_blob_bytes", uint64(blob.handle)))
|
||||
return &blob, nil
|
||||
}
|
||||
|
||||
@@ -59,13 +59,13 @@ func (c *Conn) OpenBlob(db, table, column string, row int64, write bool) (*Blob,
|
||||
//
|
||||
// It is safe to close a nil, zero or closed Blob.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_close.html
|
||||
// https://sqlite.org/c3ref/blob_close.html
|
||||
func (b *Blob) Close() error {
|
||||
if b == nil || b.handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := b.c.call(b.c.api.blobClose, uint64(b.handle))
|
||||
r := b.c.call("sqlite3_blob_close", uint64(b.handle))
|
||||
|
||||
b.handle = 0
|
||||
return b.c.error(r)
|
||||
@@ -73,14 +73,14 @@ func (b *Blob) Close() error {
|
||||
|
||||
// Size returns the size of the BLOB in bytes.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_bytes.html
|
||||
// https://sqlite.org/c3ref/blob_bytes.html
|
||||
func (b *Blob) Size() int64 {
|
||||
return b.bytes
|
||||
}
|
||||
|
||||
// Read implements the [io.Reader] interface.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_read.html
|
||||
// https://sqlite.org/c3ref/blob_read.html
|
||||
func (b *Blob) Read(p []byte) (n int, err error) {
|
||||
if b.offset >= b.bytes {
|
||||
return 0, io.EOF
|
||||
@@ -92,10 +92,10 @@ func (b *Blob) Read(p []byte) (n int, err error) {
|
||||
want = avail
|
||||
}
|
||||
|
||||
defer b.c.arena.reset()
|
||||
defer b.c.arena.mark()()
|
||||
ptr := b.c.arena.new(uint64(want))
|
||||
|
||||
r := b.c.call(b.c.api.blobRead, uint64(b.handle),
|
||||
r := b.c.call("sqlite3_blob_read", uint64(b.handle),
|
||||
uint64(ptr), uint64(want), uint64(b.offset))
|
||||
err = b.c.error(r)
|
||||
if err != nil {
|
||||
@@ -112,23 +112,23 @@ func (b *Blob) Read(p []byte) (n int, err error) {
|
||||
|
||||
// WriteTo implements the [io.WriterTo] interface.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_read.html
|
||||
// https://sqlite.org/c3ref/blob_read.html
|
||||
func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
|
||||
if b.offset >= b.bytes {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
want := int64(1024 * 1024)
|
||||
avail := b.bytes - b.offset
|
||||
want := int64(65536)
|
||||
if want > avail {
|
||||
want = avail
|
||||
}
|
||||
|
||||
ptr := b.c.new(uint64(want))
|
||||
defer b.c.free(ptr)
|
||||
defer b.c.arena.mark()()
|
||||
ptr := b.c.arena.new(uint64(want))
|
||||
|
||||
for want > 0 {
|
||||
r := b.c.call(b.c.api.blobRead, uint64(b.handle),
|
||||
r := b.c.call("sqlite3_blob_read", uint64(b.handle),
|
||||
uint64(ptr), uint64(want), uint64(b.offset))
|
||||
err = b.c.error(r)
|
||||
if err != nil {
|
||||
@@ -156,12 +156,12 @@ func (b *Blob) WriteTo(w io.Writer) (n int64, err error) {
|
||||
|
||||
// Write implements the [io.Writer] interface.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_write.html
|
||||
// https://sqlite.org/c3ref/blob_write.html
|
||||
func (b *Blob) Write(p []byte) (n int, err error) {
|
||||
defer b.c.arena.reset()
|
||||
defer b.c.arena.mark()()
|
||||
ptr := b.c.arena.bytes(p)
|
||||
|
||||
r := b.c.call(b.c.api.blobWrite, uint64(b.handle),
|
||||
r := b.c.call("sqlite3_blob_write", uint64(b.handle),
|
||||
uint64(ptr), uint64(len(p)), uint64(b.offset))
|
||||
err = b.c.error(r)
|
||||
if err != nil {
|
||||
@@ -173,10 +173,13 @@ func (b *Blob) Write(p []byte) (n int, err error) {
|
||||
|
||||
// ReadFrom implements the [io.ReaderFrom] interface.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_write.html
|
||||
// https://sqlite.org/c3ref/blob_write.html
|
||||
func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
want := int64(1024 * 1024)
|
||||
avail := b.bytes - b.offset
|
||||
want := int64(65536)
|
||||
if l, ok := r.(*io.LimitedReader); ok && want > l.N {
|
||||
want = l.N
|
||||
}
|
||||
if want > avail {
|
||||
want = avail
|
||||
}
|
||||
@@ -184,14 +187,14 @@ func (b *Blob) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
want = 1
|
||||
}
|
||||
|
||||
ptr := b.c.new(uint64(want))
|
||||
defer b.c.free(ptr)
|
||||
defer b.c.arena.mark()()
|
||||
ptr := b.c.arena.new(uint64(want))
|
||||
|
||||
for {
|
||||
mem := util.View(b.c.mod, ptr, uint64(want))
|
||||
m, err := r.Read(mem[:want])
|
||||
if m > 0 {
|
||||
r := b.c.call(b.c.api.blobWrite, uint64(b.handle),
|
||||
r := b.c.call("sqlite3_blob_write", uint64(b.handle),
|
||||
uint64(ptr), uint64(m), uint64(b.offset))
|
||||
err := b.c.error(r)
|
||||
if err != nil {
|
||||
@@ -238,10 +241,10 @@ func (b *Blob) Seek(offset int64, whence int) (int64, error) {
|
||||
|
||||
// Reopen moves a BLOB handle to a new row of the same database table.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/blob_reopen.html
|
||||
// https://sqlite.org/c3ref/blob_reopen.html
|
||||
func (b *Blob) Reopen(row int64) error {
|
||||
err := b.c.error(b.c.call(b.c.api.blobReopen, uint64(b.handle), uint64(row)))
|
||||
b.bytes = int64(b.c.call(b.c.api.blobBytes, uint64(b.handle)))
|
||||
err := b.c.error(b.c.call("sqlite3_blob_reopen", uint64(b.handle), uint64(row)))
|
||||
b.bytes = int64(b.c.call("sqlite3_blob_bytes", uint64(b.handle)))
|
||||
b.offset = 0
|
||||
return err
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// Config makes configuration changes to a database connection.
|
||||
// Only boolean configuration options are supported.
|
||||
// Called with no arg reads the current configuration value,
|
||||
// called with one arg sets and returns the new value.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_config.html
|
||||
func (c *Conn) Config(op DBConfig, arg ...bool) (bool, error) {
|
||||
defer c.arena.mark()()
|
||||
argsPtr := c.arena.new(2 * ptrlen)
|
||||
|
||||
var flag int
|
||||
switch {
|
||||
case len(arg) == 0:
|
||||
flag = -1
|
||||
case arg[0]:
|
||||
flag = 1
|
||||
}
|
||||
|
||||
util.WriteUint32(c.mod, argsPtr+0*ptrlen, uint32(flag))
|
||||
util.WriteUint32(c.mod, argsPtr+1*ptrlen, argsPtr)
|
||||
|
||||
r := c.call("sqlite3_db_config", uint64(c.handle),
|
||||
uint64(op), uint64(argsPtr))
|
||||
return util.ReadUint32(c.mod, argsPtr) != 0, c.error(r)
|
||||
}
|
||||
|
||||
// ConfigLog sets up the error logging callback for the connection.
|
||||
//
|
||||
// https://sqlite.org/errlog.html
|
||||
func (c *Conn) ConfigLog(cb func(code ExtendedErrorCode, msg string)) error {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
r := c.call("sqlite3_config_log_go", enable)
|
||||
if err := c.error(r); err != nil {
|
||||
return err
|
||||
}
|
||||
c.log = cb
|
||||
return nil
|
||||
}
|
||||
|
||||
func logCallback(ctx context.Context, mod api.Module, _, iCode, zMsg uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.log != nil {
|
||||
msg := util.ReadString(mod, zMsg, _MAX_LENGTH)
|
||||
c.log(xErrorCode(iCode), msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Limit allows the size of various constructs to be
|
||||
// limited on a connection by connection basis.
|
||||
//
|
||||
// https://sqlite.org/c3ref/limit.html
|
||||
func (c *Conn) Limit(id LimitCategory, value int) int {
|
||||
r := c.call("sqlite3_limit", uint64(c.handle), uint64(id), uint64(value))
|
||||
return int(int32(r))
|
||||
}
|
||||
|
||||
// SetAuthorizer registers an authorizer callback with the database connection.
|
||||
//
|
||||
// https://sqlite.org/c3ref/set_authorizer.html
|
||||
func (c *Conn) SetAuthorizer(cb func(action AuthorizerActionCode, name3rd, name4th, schema, nameInner string) AuthorizerReturnCode) error {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
r := c.call("sqlite3_set_authorizer_go", uint64(c.handle), enable)
|
||||
if err := c.error(r); err != nil {
|
||||
return err
|
||||
}
|
||||
c.authorizer = cb
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func authorizerCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zName3rd, zName4th, zSchema, zNameInner uint32) (rc AuthorizerReturnCode) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.authorizer != nil {
|
||||
var name3rd, name4th, schema, nameInner string
|
||||
if zName3rd != 0 {
|
||||
name3rd = util.ReadString(mod, zName3rd, _MAX_NAME)
|
||||
}
|
||||
if zName4th != 0 {
|
||||
name4th = util.ReadString(mod, zName4th, _MAX_NAME)
|
||||
}
|
||||
if zSchema != 0 {
|
||||
schema = util.ReadString(mod, zSchema, _MAX_NAME)
|
||||
}
|
||||
if zNameInner != 0 {
|
||||
nameInner = util.ReadString(mod, zNameInner, _MAX_NAME)
|
||||
}
|
||||
rc = c.authorizer(action, name3rd, name4th, schema, nameInner)
|
||||
}
|
||||
return rc
|
||||
}
|
||||
|
||||
// WalCheckpoint checkpoints a WAL database.
|
||||
//
|
||||
// https://sqlite.org/c3ref/wal_checkpoint_v2.html
|
||||
func (c *Conn) WalCheckpoint(schema string, mode CheckpointMode) (nLog, nCkpt int, err error) {
|
||||
defer c.arena.mark()()
|
||||
nLogPtr := c.arena.new(ptrlen)
|
||||
nCkptPtr := c.arena.new(ptrlen)
|
||||
schemaPtr := c.arena.string(schema)
|
||||
r := c.call("sqlite3_wal_checkpoint_v2",
|
||||
uint64(c.handle), uint64(schemaPtr), uint64(mode),
|
||||
uint64(nLogPtr), uint64(nCkptPtr))
|
||||
nLog = int(int32(util.ReadUint32(c.mod, nLogPtr)))
|
||||
nCkpt = int(int32(util.ReadUint32(c.mod, nCkptPtr)))
|
||||
return nLog, nCkpt, c.error(r)
|
||||
}
|
||||
|
||||
// WalAutoCheckpoint configures WAL auto-checkpoints.
|
||||
//
|
||||
// https://sqlite.org/c3ref/wal_autocheckpoint.html
|
||||
func (c *Conn) WalAutoCheckpoint(pages int) error {
|
||||
r := c.call("sqlite3_wal_autocheckpoint", uint64(c.handle), uint64(pages))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// WalHook registers a callback function to be invoked
|
||||
// each time data is committed to a database in WAL mode.
|
||||
//
|
||||
// https://sqlite.org/c3ref/wal_hook.html
|
||||
func (c *Conn) WalHook(cb func(db *Conn, schema string, pages int) error) {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
c.call("sqlite3_wal_hook_go", uint64(c.handle), enable)
|
||||
c.wal = cb
|
||||
}
|
||||
|
||||
func walCallback(ctx context.Context, mod api.Module, _, pDB, zSchema uint32, pages int32) (rc uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.wal != nil {
|
||||
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
||||
err := c.wal(c, schema, int(pages))
|
||||
_, rc = errorCode(err, ERROR)
|
||||
}
|
||||
return rc
|
||||
}
|
||||
|
||||
// AutoVacuumPages registers a autovacuum compaction amount callback.
|
||||
//
|
||||
// https://sqlite.org/c3ref/autovacuum_pages.html
|
||||
func (c *Conn) AutoVacuumPages(cb func(schema string, dbPages, freePages, bytesPerPage uint) uint) error {
|
||||
funcPtr := util.AddHandle(c.ctx, cb)
|
||||
r := c.call("sqlite3_autovacuum_pages_go", uint64(c.handle), uint64(funcPtr))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
func autoVacuumCallback(ctx context.Context, mod api.Module, pApp, zSchema, nDbPage, nFreePage, nBytePerPage uint32) uint32 {
|
||||
fn := util.GetHandle(ctx, pApp).(func(schema string, dbPages, freePages, bytesPerPage uint) uint)
|
||||
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
||||
return uint32(fn(schema, uint(nDbPage), uint(nFreePage), uint(nBytePerPage)))
|
||||
}
|
||||
+191
-134
@@ -2,29 +2,35 @@ package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// Conn is a database connection handle.
|
||||
// A Conn is not safe for concurrent use by multiple goroutines.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/sqlite3.html
|
||||
// https://sqlite.org/c3ref/sqlite3.html
|
||||
type Conn struct {
|
||||
*module
|
||||
*sqlite
|
||||
|
||||
interrupt context.Context
|
||||
waiter chan struct{}
|
||||
pending *Stmt
|
||||
arena arena
|
||||
interrupt context.Context
|
||||
pending *Stmt
|
||||
busy func(int) bool
|
||||
log func(xErrorCode, string)
|
||||
collation func(*Conn, string)
|
||||
authorizer func(AuthorizerActionCode, string, string, string, string) AuthorizerReturnCode
|
||||
update func(AuthorizerActionCode, string, string, int64)
|
||||
commit func() bool
|
||||
rollback func()
|
||||
wal func(*Conn, string, int) error
|
||||
arena arena
|
||||
|
||||
handle uint32
|
||||
}
|
||||
@@ -39,9 +45,9 @@ func Open(filename string) (*Conn, error) {
|
||||
// If none of the required flags is used, a combination of [OPEN_READWRITE] and [OPEN_CREATE] is used.
|
||||
// If a URI filename is used, PRAGMA statements to execute can be specified using "_pragma":
|
||||
//
|
||||
// sqlite3.Open("file:demo.db?_pragma=busy_timeout(10000)&_pragma=locking_mode(normal)")
|
||||
// sqlite3.Open("file:demo.db?_pragma=busy_timeout(10000)")
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/open.html
|
||||
// https://sqlite.org/c3ref/open.html
|
||||
func OpenFlags(filename string, flags OpenFlag) (*Conn, error) {
|
||||
if flags&(OPEN_READONLY|OPEN_READWRITE|OPEN_CREATE) == 0 {
|
||||
flags |= OPEN_READWRITE | OPEN_CREATE
|
||||
@@ -49,21 +55,22 @@ func OpenFlags(filename string, flags OpenFlag) (*Conn, error) {
|
||||
return newConn(filename, flags)
|
||||
}
|
||||
|
||||
type connKey struct{}
|
||||
|
||||
func newConn(filename string, flags OpenFlag) (conn *Conn, err error) {
|
||||
mod, err := instantiateModule()
|
||||
sqlite, err := instantiateSQLite()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if conn == nil {
|
||||
mod.close()
|
||||
} else {
|
||||
runtime.SetFinalizer(conn, util.Finalizer[Conn](3))
|
||||
sqlite.close()
|
||||
}
|
||||
}()
|
||||
|
||||
c := &Conn{module: mod}
|
||||
c := &Conn{sqlite: sqlite}
|
||||
c.arena = c.newArena(1024)
|
||||
c.ctx = context.WithValue(c.ctx, connKey{}, c)
|
||||
c.handle, err = c.openDB(filename, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -72,15 +79,15 @@ func newConn(filename string, flags OpenFlag) (conn *Conn, err error) {
|
||||
}
|
||||
|
||||
func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) {
|
||||
defer c.arena.reset()
|
||||
defer c.arena.mark()()
|
||||
connPtr := c.arena.new(ptrlen)
|
||||
namePtr := c.arena.string(filename)
|
||||
|
||||
flags |= OPEN_EXRESCODE
|
||||
r := c.call(c.api.open, uint64(namePtr), uint64(connPtr), uint64(flags), 0)
|
||||
r := c.call("sqlite3_open_v2", uint64(namePtr), uint64(connPtr), uint64(flags), 0)
|
||||
|
||||
handle := util.ReadUint32(c.mod, connPtr)
|
||||
if err := c.module.error(r, handle); err != nil {
|
||||
if err := c.sqlite.error(r, handle); err != nil {
|
||||
c.closeDB(handle)
|
||||
return 0, err
|
||||
}
|
||||
@@ -92,28 +99,26 @@ func (c *Conn) openDB(filename string, flags OpenFlag) (uint32, error) {
|
||||
for _, p := range query["_pragma"] {
|
||||
pragmas.WriteString(`PRAGMA `)
|
||||
pragmas.WriteString(p)
|
||||
pragmas.WriteByte(';')
|
||||
pragmas.WriteString(`;`)
|
||||
}
|
||||
}
|
||||
|
||||
c.arena.reset()
|
||||
pragmaPtr := c.arena.string(pragmas.String())
|
||||
r := c.call(c.api.exec, uint64(handle), uint64(pragmaPtr), 0, 0, 0)
|
||||
if err := c.module.error(r, handle, pragmas.String()); err != nil {
|
||||
if errors.Is(err, ERROR) {
|
||||
if pragmas.Len() != 0 {
|
||||
pragmaPtr := c.arena.string(pragmas.String())
|
||||
r := c.call("sqlite3_exec", uint64(handle), uint64(pragmaPtr), 0, 0, 0)
|
||||
if err := c.sqlite.error(r, handle, pragmas.String()); err != nil {
|
||||
err = fmt.Errorf("sqlite3: invalid _pragma: %w", err)
|
||||
c.closeDB(handle)
|
||||
return 0, err
|
||||
}
|
||||
c.closeDB(handle)
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
c.call("sqlite3_progress_handler_go", uint64(handle), 100)
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
func (c *Conn) closeDB(handle uint32) {
|
||||
r := c.call(c.api.closeZombie, uint64(handle))
|
||||
if err := c.module.error(r, handle); err != nil {
|
||||
r := c.call("sqlite3_close_v2", uint64(handle))
|
||||
if err := c.sqlite.error(r, handle); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -126,37 +131,35 @@ func (c *Conn) closeDB(handle uint32) {
|
||||
//
|
||||
// It is safe to close a nil, zero or closed Conn.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/close.html
|
||||
// https://sqlite.org/c3ref/close.html
|
||||
func (c *Conn) Close() error {
|
||||
if c == nil || c.handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.SetInterrupt(context.Background())
|
||||
c.pending.Close()
|
||||
c.pending = nil
|
||||
|
||||
r := c.call(c.api.close, uint64(c.handle))
|
||||
r := c.call("sqlite3_close", uint64(c.handle))
|
||||
if err := c.error(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.handle = 0
|
||||
runtime.SetFinalizer(c, nil)
|
||||
return c.module.close()
|
||||
return c.close()
|
||||
}
|
||||
|
||||
// Exec is a convenience function that allows an application to run
|
||||
// multiple statements of SQL without having to use a lot of code.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/exec.html
|
||||
// https://sqlite.org/c3ref/exec.html
|
||||
func (c *Conn) Exec(sql string) error {
|
||||
c.checkInterrupt()
|
||||
defer c.arena.reset()
|
||||
defer c.arena.mark()()
|
||||
sqlPtr := c.arena.string(sql)
|
||||
|
||||
r := c.call(c.api.exec, uint64(c.handle), uint64(sqlPtr), 0, 0, 0)
|
||||
return c.error(r)
|
||||
r := c.call("sqlite3_exec", uint64(c.handle), uint64(sqlPtr), 0, 0, 0)
|
||||
return c.error(r, sql)
|
||||
}
|
||||
|
||||
// Prepare calls [Conn.PrepareFlags] with no flags.
|
||||
@@ -169,25 +172,26 @@ func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) {
|
||||
// If the input text contains no SQL (if the input is an empty string or a comment),
|
||||
// both stmt and err will be nil.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/prepare.html
|
||||
// https://sqlite.org/c3ref/prepare.html
|
||||
func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) {
|
||||
if emptyStatement(sql) {
|
||||
return nil, "", nil
|
||||
if len(sql) > _MAX_SQL_LENGTH {
|
||||
return nil, "", TOOBIG
|
||||
}
|
||||
|
||||
defer c.arena.reset()
|
||||
defer c.arena.mark()()
|
||||
stmtPtr := c.arena.new(ptrlen)
|
||||
tailPtr := c.arena.new(ptrlen)
|
||||
sqlPtr := c.arena.string(sql)
|
||||
|
||||
r := c.call(c.api.prepare, uint64(c.handle),
|
||||
r := c.call("sqlite3_prepare_v3", uint64(c.handle),
|
||||
uint64(sqlPtr), uint64(len(sql)+1), uint64(flags),
|
||||
uint64(stmtPtr), uint64(tailPtr))
|
||||
|
||||
stmt = &Stmt{c: c}
|
||||
stmt.handle = util.ReadUint32(c.mod, stmtPtr)
|
||||
i := util.ReadUint32(c.mod, tailPtr)
|
||||
tail = sql[i-sqlPtr:]
|
||||
if sql := sql[util.ReadUint32(c.mod, tailPtr)-sqlPtr:]; sql != "" {
|
||||
tail = sql
|
||||
}
|
||||
|
||||
if err := c.error(r, sql); err != nil {
|
||||
return nil, "", err
|
||||
@@ -195,36 +199,108 @@ func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail str
|
||||
if stmt.handle == 0 {
|
||||
return nil, "", nil
|
||||
}
|
||||
return
|
||||
return stmt, tail, nil
|
||||
}
|
||||
|
||||
// DBName returns the schema name for n-th database on the database connection.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_name.html
|
||||
func (c *Conn) DBName(n int) string {
|
||||
r := c.call("sqlite3_db_name", uint64(c.handle), uint64(n))
|
||||
|
||||
ptr := uint32(r)
|
||||
if ptr == 0 {
|
||||
return ""
|
||||
}
|
||||
return util.ReadString(c.mod, ptr, _MAX_NAME)
|
||||
}
|
||||
|
||||
// Filename returns the filename for a database.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_filename.html
|
||||
func (c *Conn) Filename(schema string) *vfs.Filename {
|
||||
var ptr uint32
|
||||
if schema != "" {
|
||||
defer c.arena.mark()()
|
||||
ptr = c.arena.string(schema)
|
||||
}
|
||||
|
||||
r := c.call("sqlite3_db_filename", uint64(c.handle), uint64(ptr))
|
||||
return vfs.OpenFilename(c.ctx, c.mod, uint32(r), vfs.OPEN_MAIN_DB)
|
||||
}
|
||||
|
||||
// ReadOnly determines if a database is read-only.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_readonly.html
|
||||
func (c *Conn) ReadOnly(schema string) (ro bool, ok bool) {
|
||||
var ptr uint32
|
||||
if schema != "" {
|
||||
defer c.arena.mark()()
|
||||
ptr = c.arena.string(schema)
|
||||
}
|
||||
r := c.call("sqlite3_db_readonly", uint64(c.handle), uint64(ptr))
|
||||
return int32(r) > 0, int32(r) < 0
|
||||
}
|
||||
|
||||
// GetAutocommit tests the connection for auto-commit mode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/get_autocommit.html
|
||||
// https://sqlite.org/c3ref/get_autocommit.html
|
||||
func (c *Conn) GetAutocommit() bool {
|
||||
r := c.call(c.api.autocommit, uint64(c.handle))
|
||||
r := c.call("sqlite3_get_autocommit", uint64(c.handle))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
// LastInsertRowID returns the rowid of the most recent successful INSERT
|
||||
// on the database connection.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/last_insert_rowid.html
|
||||
// https://sqlite.org/c3ref/last_insert_rowid.html
|
||||
func (c *Conn) LastInsertRowID() int64 {
|
||||
r := c.call(c.api.lastRowid, uint64(c.handle))
|
||||
r := c.call("sqlite3_last_insert_rowid", uint64(c.handle))
|
||||
return int64(r)
|
||||
}
|
||||
|
||||
// SetLastInsertRowID allows the application to set the value returned by
|
||||
// [Conn.LastInsertRowID].
|
||||
//
|
||||
// https://sqlite.org/c3ref/set_last_insert_rowid.html
|
||||
func (c *Conn) SetLastInsertRowID(id int64) {
|
||||
c.call("sqlite3_set_last_insert_rowid", uint64(c.handle), uint64(id))
|
||||
}
|
||||
|
||||
// Changes returns the number of rows modified, inserted or deleted
|
||||
// by the most recently completed INSERT, UPDATE or DELETE statement
|
||||
// on the database connection.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/changes.html
|
||||
// https://sqlite.org/c3ref/changes.html
|
||||
func (c *Conn) Changes() int64 {
|
||||
r := c.call(c.api.changes, uint64(c.handle))
|
||||
r := c.call("sqlite3_changes64", uint64(c.handle))
|
||||
return int64(r)
|
||||
}
|
||||
|
||||
// TotalChanges returns the number of rows modified, inserted or deleted
|
||||
// by all INSERT, UPDATE or DELETE statements completed
|
||||
// since the database connection was opened.
|
||||
//
|
||||
// https://sqlite.org/c3ref/total_changes.html
|
||||
func (c *Conn) TotalChanges() int64 {
|
||||
r := c.call("sqlite3_total_changes64", uint64(c.handle))
|
||||
return int64(r)
|
||||
}
|
||||
|
||||
// ReleaseMemory frees memory used by a database connection.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_release_memory.html
|
||||
func (c *Conn) ReleaseMemory() error {
|
||||
r := c.call("sqlite3_db_release_memory", uint64(c.handle))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// GetInterrupt gets the context set with [Conn.SetInterrupt],
|
||||
// or nil if none was set.
|
||||
func (c *Conn) GetInterrupt() context.Context {
|
||||
return c.interrupt
|
||||
}
|
||||
|
||||
// SetInterrupt interrupts a long-running query when a context is done.
|
||||
//
|
||||
// Subsequent uses of the connection will return [INTERRUPT]
|
||||
@@ -238,108 +314,89 @@ func (c *Conn) Changes() int64 {
|
||||
//
|
||||
// SetInterrupt returns the old context assigned to the connection.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/interrupt.html
|
||||
// https://sqlite.org/c3ref/interrupt.html
|
||||
func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) {
|
||||
// Is a waiter running?
|
||||
if c.waiter != nil {
|
||||
c.waiter <- struct{}{} // Cancel the waiter.
|
||||
<-c.waiter // Wait for it to finish.
|
||||
c.waiter = nil
|
||||
// Is it the same context?
|
||||
if ctx == c.interrupt {
|
||||
return ctx
|
||||
}
|
||||
// Reset the pending statement.
|
||||
if c.pending != nil {
|
||||
c.pending.Reset()
|
||||
|
||||
// A busy SQL statement prevents SQLite from ignoring an interrupt
|
||||
// that comes before any other statements are started.
|
||||
if c.pending == nil {
|
||||
c.pending, _, _ = c.Prepare(`WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x FROM c) SELECT x FROM c`)
|
||||
}
|
||||
|
||||
old = c.interrupt
|
||||
c.interrupt = ctx
|
||||
if ctx == nil || ctx.Done() == nil {
|
||||
return old
|
||||
|
||||
if old != nil && old.Done() != nil && (ctx == nil || ctx.Err() == nil) {
|
||||
c.pending.Reset()
|
||||
}
|
||||
|
||||
// Creating an uncompleted SQL statement prevents SQLite from ignoring
|
||||
// an interrupt that comes before any other statements are started.
|
||||
if c.pending == nil {
|
||||
c.pending, _, _ = c.Prepare(`SELECT 1 UNION ALL SELECT 2`)
|
||||
if ctx != nil && ctx.Done() != nil {
|
||||
c.pending.Step()
|
||||
}
|
||||
c.pending.Step()
|
||||
|
||||
// Don't create the goroutine if we're already interrupted.
|
||||
// This happens frequently while restoring to a previously interrupted state.
|
||||
if c.checkInterrupt() {
|
||||
return old
|
||||
}
|
||||
|
||||
waiter := make(chan struct{})
|
||||
c.waiter = waiter
|
||||
go func() {
|
||||
select {
|
||||
case <-waiter: // Waiter was cancelled.
|
||||
break
|
||||
|
||||
case <-ctx.Done(): // Done was closed.
|
||||
const isInterruptedOffset = 280
|
||||
buf := util.View(c.mod, c.handle+isInterruptedOffset, 4)
|
||||
(*atomic.Uint32)(unsafe.Pointer(&buf[0])).Store(1)
|
||||
// Wait for the next call to SetInterrupt.
|
||||
<-waiter
|
||||
}
|
||||
|
||||
// Signal that the waiter has finished.
|
||||
waiter <- struct{}{}
|
||||
}()
|
||||
return old
|
||||
}
|
||||
|
||||
func (c *Conn) checkInterrupt() bool {
|
||||
if c.interrupt == nil || c.interrupt.Err() == nil {
|
||||
return false
|
||||
func (c *Conn) checkInterrupt() {
|
||||
if c.interrupt != nil && c.interrupt.Err() != nil {
|
||||
c.call("sqlite3_interrupt", uint64(c.handle))
|
||||
}
|
||||
const isInterruptedOffset = 280
|
||||
buf := util.View(c.mod, c.handle+isInterruptedOffset, 4)
|
||||
(*atomic.Uint32)(unsafe.Pointer(&buf[0])).Store(1)
|
||||
return true
|
||||
}
|
||||
|
||||
// Pragma executes a PRAGMA statement and returns any results.
|
||||
//
|
||||
// https://www.sqlite.org/pragma.html
|
||||
func (c *Conn) Pragma(str string) ([]string, error) {
|
||||
stmt, _, err := c.Prepare(`PRAGMA ` + str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func progressCallback(ctx context.Context, mod api.Module, pDB uint32) (interrupt uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.interrupt != nil {
|
||||
if c.interrupt.Err() != nil {
|
||||
interrupt = 1
|
||||
}
|
||||
}
|
||||
defer stmt.Close()
|
||||
return interrupt
|
||||
}
|
||||
|
||||
var pragmas []string
|
||||
for stmt.Step() {
|
||||
pragmas = append(pragmas, stmt.ColumnText(0))
|
||||
// BusyTimeout sets a busy timeout.
|
||||
//
|
||||
// https://sqlite.org/c3ref/busy_timeout.html
|
||||
func (c *Conn) BusyTimeout(timeout time.Duration) error {
|
||||
ms := min((timeout+time.Millisecond-1)/time.Millisecond, math.MaxInt32)
|
||||
r := c.call("sqlite3_busy_timeout", uint64(c.handle), uint64(ms))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// BusyHandler registers a callback to handle [BUSY] errors.
|
||||
//
|
||||
// https://sqlite.org/c3ref/busy_handler.html
|
||||
func (c *Conn) BusyHandler(cb func(count int) (retry bool)) error {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
return pragmas, stmt.Close()
|
||||
r := c.call("sqlite3_busy_handler_go", uint64(c.handle), enable)
|
||||
if err := c.error(r); err != nil {
|
||||
return err
|
||||
}
|
||||
c.busy = cb
|
||||
return nil
|
||||
}
|
||||
|
||||
func busyCallback(ctx context.Context, mod api.Module, pDB uint32, count int32) (retry uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.busy != nil {
|
||||
if c.busy(int(count)) {
|
||||
retry = 1
|
||||
}
|
||||
}
|
||||
return retry
|
||||
}
|
||||
|
||||
func (c *Conn) error(rc uint64, sql ...string) error {
|
||||
return c.module.error(rc, c.handle, sql...)
|
||||
return c.sqlite.error(rc, c.handle, sql...)
|
||||
}
|
||||
|
||||
// DriverConn is implemented by the SQLite [database/sql] driver connection.
|
||||
//
|
||||
// It can be used to access advanced SQLite features like
|
||||
// [savepoints], [online backup] and [incremental BLOB I/O].
|
||||
// It can be used to access SQLite features like [online backup].
|
||||
//
|
||||
// [savepoints]: https://www.sqlite.org/lang_savepoint.html
|
||||
// [online backup]: https://www.sqlite.org/backup.html
|
||||
// [incremental BLOB I/O]: https://www.sqlite.org/c3ref/blob_open.html
|
||||
// [online backup]: https://sqlite.org/backup.html
|
||||
type DriverConn interface {
|
||||
driver.Conn
|
||||
driver.ConnBeginTx
|
||||
driver.ExecerContext
|
||||
driver.ConnPrepareContext
|
||||
|
||||
SetInterrupt(ctx context.Context) (old context.Context)
|
||||
|
||||
Savepoint() Savepoint
|
||||
Backup(srcDB, dstURI string) error
|
||||
Restore(dstDB, srcURI string) error
|
||||
OpenBlob(db, table, column string, row int64, write bool) (*Blob, error)
|
||||
Raw() *Conn
|
||||
}
|
||||
+172
-11
@@ -9,16 +9,18 @@ const (
|
||||
|
||||
_UTF8 = 1
|
||||
|
||||
_MAX_STRING = 512 // Used for short strings: names, error messages…
|
||||
|
||||
_MAX_NAME = 1e6 // Self-imposed limit for most NUL terminated strings.
|
||||
_MAX_LENGTH = 1e9
|
||||
_MAX_SQL_LENGTH = 1e9
|
||||
_MAX_ALLOCATION_SIZE = 0x7ffffeff
|
||||
_MAX_FUNCTION_ARG = 100
|
||||
|
||||
ptrlen = 4
|
||||
)
|
||||
|
||||
// ErrorCode is a result code that [Error.Code] might return.
|
||||
//
|
||||
// https://www.sqlite.org/rescode.html
|
||||
// https://sqlite.org/rescode.html
|
||||
type ErrorCode uint8
|
||||
|
||||
const (
|
||||
@@ -54,7 +56,7 @@ const (
|
||||
|
||||
// ExtendedErrorCode is a result code that [Error.ExtendedCode] might return.
|
||||
//
|
||||
// https://www.sqlite.org/rescode.html
|
||||
// https://sqlite.org/rescode.html
|
||||
type (
|
||||
ExtendedErrorCode uint16
|
||||
xErrorCode = ExtendedErrorCode
|
||||
@@ -97,6 +99,7 @@ const (
|
||||
IOERR_ROLLBACK_ATOMIC ExtendedErrorCode = xErrorCode(IOERR) | (31 << 8)
|
||||
IOERR_DATA ExtendedErrorCode = xErrorCode(IOERR) | (32 << 8)
|
||||
IOERR_CORRUPTFS ExtendedErrorCode = xErrorCode(IOERR) | (33 << 8)
|
||||
IOERR_IN_PAGE ExtendedErrorCode = xErrorCode(IOERR) | (34 << 8)
|
||||
LOCKED_SHAREDCACHE ExtendedErrorCode = xErrorCode(LOCKED) | (1 << 8)
|
||||
LOCKED_VTAB ExtendedErrorCode = xErrorCode(LOCKED) | (2 << 8)
|
||||
BUSY_RECOVERY ExtendedErrorCode = xErrorCode(BUSY) | (1 << 8)
|
||||
@@ -139,7 +142,7 @@ const (
|
||||
|
||||
// OpenFlag is a flag for the [OpenFlags] function.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_open_autoproxy.html
|
||||
// https://sqlite.org/c3ref/c_open_autoproxy.html
|
||||
type OpenFlag uint32
|
||||
|
||||
const (
|
||||
@@ -158,7 +161,7 @@ const (
|
||||
|
||||
// PrepareFlag is a flag that can be passed to [Conn.PrepareFlags].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_prepare_normalize.html
|
||||
// https://sqlite.org/c3ref/c_prepare_normalize.html
|
||||
type PrepareFlag uint32
|
||||
|
||||
const (
|
||||
@@ -167,9 +170,167 @@ const (
|
||||
PREPARE_NO_VTAB PrepareFlag = 0x04
|
||||
)
|
||||
|
||||
// FunctionFlag is a flag that can be passed to
|
||||
// [Conn.CreateFunction] and [Conn.CreateWindowFunction].
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_deterministic.html
|
||||
type FunctionFlag uint32
|
||||
|
||||
const (
|
||||
DETERMINISTIC FunctionFlag = 0x000000800
|
||||
DIRECTONLY FunctionFlag = 0x000080000
|
||||
SUBTYPE FunctionFlag = 0x000100000
|
||||
INNOCUOUS FunctionFlag = 0x000200000
|
||||
RESULT_SUBTYPE FunctionFlag = 0x001000000
|
||||
)
|
||||
|
||||
// StmtStatus name counter values associated with the [Stmt.Status] method.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_stmtstatus_counter.html
|
||||
type StmtStatus uint32
|
||||
|
||||
const (
|
||||
STMTSTATUS_FULLSCAN_STEP StmtStatus = 1
|
||||
STMTSTATUS_SORT StmtStatus = 2
|
||||
STMTSTATUS_AUTOINDEX StmtStatus = 3
|
||||
STMTSTATUS_VM_STEP StmtStatus = 4
|
||||
STMTSTATUS_REPREPARE StmtStatus = 5
|
||||
STMTSTATUS_RUN StmtStatus = 6
|
||||
STMTSTATUS_FILTER_MISS StmtStatus = 7
|
||||
STMTSTATUS_FILTER_HIT StmtStatus = 8
|
||||
STMTSTATUS_MEMUSED StmtStatus = 99
|
||||
)
|
||||
|
||||
// DBConfig are the available database connection configuration options.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_dbconfig_defensive.html
|
||||
type DBConfig uint32
|
||||
|
||||
const (
|
||||
// DBCONFIG_MAINDBNAME DBConfig = 1000
|
||||
// DBCONFIG_LOOKASIDE DBConfig = 1001
|
||||
DBCONFIG_ENABLE_FKEY DBConfig = 1002
|
||||
DBCONFIG_ENABLE_TRIGGER DBConfig = 1003
|
||||
DBCONFIG_ENABLE_FTS3_TOKENIZER DBConfig = 1004
|
||||
DBCONFIG_ENABLE_LOAD_EXTENSION DBConfig = 1005
|
||||
DBCONFIG_NO_CKPT_ON_CLOSE DBConfig = 1006
|
||||
DBCONFIG_ENABLE_QPSG DBConfig = 1007
|
||||
DBCONFIG_TRIGGER_EQP DBConfig = 1008
|
||||
DBCONFIG_RESET_DATABASE DBConfig = 1009
|
||||
DBCONFIG_DEFENSIVE DBConfig = 1010
|
||||
DBCONFIG_WRITABLE_SCHEMA DBConfig = 1011
|
||||
DBCONFIG_LEGACY_ALTER_TABLE DBConfig = 1012
|
||||
DBCONFIG_DQS_DML DBConfig = 1013
|
||||
DBCONFIG_DQS_DDL DBConfig = 1014
|
||||
DBCONFIG_ENABLE_VIEW DBConfig = 1015
|
||||
DBCONFIG_LEGACY_FILE_FORMAT DBConfig = 1016
|
||||
DBCONFIG_TRUSTED_SCHEMA DBConfig = 1017
|
||||
DBCONFIG_STMT_SCANSTATUS DBConfig = 1018
|
||||
DBCONFIG_REVERSE_SCANORDER DBConfig = 1019
|
||||
)
|
||||
|
||||
// LimitCategory are the available run-time limit categories.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_limit_attached.html
|
||||
type LimitCategory uint32
|
||||
|
||||
const (
|
||||
LIMIT_LENGTH LimitCategory = 0
|
||||
LIMIT_SQL_LENGTH LimitCategory = 1
|
||||
LIMIT_COLUMN LimitCategory = 2
|
||||
LIMIT_EXPR_DEPTH LimitCategory = 3
|
||||
LIMIT_COMPOUND_SELECT LimitCategory = 4
|
||||
LIMIT_VDBE_OP LimitCategory = 5
|
||||
LIMIT_FUNCTION_ARG LimitCategory = 6
|
||||
LIMIT_ATTACHED LimitCategory = 7
|
||||
LIMIT_LIKE_PATTERN_LENGTH LimitCategory = 8
|
||||
LIMIT_VARIABLE_NUMBER LimitCategory = 9
|
||||
LIMIT_TRIGGER_DEPTH LimitCategory = 10
|
||||
LIMIT_WORKER_THREADS LimitCategory = 11
|
||||
)
|
||||
|
||||
// AuthorizerActionCode are the integer action codes
|
||||
// that the authorizer callback may be passed.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_alter_table.html
|
||||
type AuthorizerActionCode uint32
|
||||
|
||||
const (
|
||||
/***************************************************** 3rd ************ 4th ***********/
|
||||
AUTH_CREATE_INDEX AuthorizerActionCode = 1 /* Index Name Table Name */
|
||||
AUTH_CREATE_TABLE AuthorizerActionCode = 2 /* Table Name NULL */
|
||||
AUTH_CREATE_TEMP_INDEX AuthorizerActionCode = 3 /* Index Name Table Name */
|
||||
AUTH_CREATE_TEMP_TABLE AuthorizerActionCode = 4 /* Table Name NULL */
|
||||
AUTH_CREATE_TEMP_TRIGGER AuthorizerActionCode = 5 /* Trigger Name Table Name */
|
||||
AUTH_CREATE_TEMP_VIEW AuthorizerActionCode = 6 /* View Name NULL */
|
||||
AUTH_CREATE_TRIGGER AuthorizerActionCode = 7 /* Trigger Name Table Name */
|
||||
AUTH_CREATE_VIEW AuthorizerActionCode = 8 /* View Name NULL */
|
||||
AUTH_DELETE AuthorizerActionCode = 9 /* Table Name NULL */
|
||||
AUTH_DROP_INDEX AuthorizerActionCode = 10 /* Index Name Table Name */
|
||||
AUTH_DROP_TABLE AuthorizerActionCode = 11 /* Table Name NULL */
|
||||
AUTH_DROP_TEMP_INDEX AuthorizerActionCode = 12 /* Index Name Table Name */
|
||||
AUTH_DROP_TEMP_TABLE AuthorizerActionCode = 13 /* Table Name NULL */
|
||||
AUTH_DROP_TEMP_TRIGGER AuthorizerActionCode = 14 /* Trigger Name Table Name */
|
||||
AUTH_DROP_TEMP_VIEW AuthorizerActionCode = 15 /* View Name NULL */
|
||||
AUTH_DROP_TRIGGER AuthorizerActionCode = 16 /* Trigger Name Table Name */
|
||||
AUTH_DROP_VIEW AuthorizerActionCode = 17 /* View Name NULL */
|
||||
AUTH_INSERT AuthorizerActionCode = 18 /* Table Name NULL */
|
||||
AUTH_PRAGMA AuthorizerActionCode = 19 /* Pragma Name 1st arg or NULL */
|
||||
AUTH_READ AuthorizerActionCode = 20 /* Table Name Column Name */
|
||||
AUTH_SELECT AuthorizerActionCode = 21 /* NULL NULL */
|
||||
AUTH_TRANSACTION AuthorizerActionCode = 22 /* Operation NULL */
|
||||
AUTH_UPDATE AuthorizerActionCode = 23 /* Table Name Column Name */
|
||||
AUTH_ATTACH AuthorizerActionCode = 24 /* Filename NULL */
|
||||
AUTH_DETACH AuthorizerActionCode = 25 /* Database Name NULL */
|
||||
AUTH_ALTER_TABLE AuthorizerActionCode = 26 /* Database Name Table Name */
|
||||
AUTH_REINDEX AuthorizerActionCode = 27 /* Index Name NULL */
|
||||
AUTH_ANALYZE AuthorizerActionCode = 28 /* Table Name NULL */
|
||||
AUTH_CREATE_VTABLE AuthorizerActionCode = 29 /* Table Name Module Name */
|
||||
AUTH_DROP_VTABLE AuthorizerActionCode = 30 /* Table Name Module Name */
|
||||
AUTH_FUNCTION AuthorizerActionCode = 31 /* NULL Function Name */
|
||||
AUTH_SAVEPOINT AuthorizerActionCode = 32 /* Operation Savepoint Name */
|
||||
AUTH_COPY AuthorizerActionCode = 0 /* No longer used */
|
||||
AUTH_RECURSIVE AuthorizerActionCode = 33 /* NULL NULL */
|
||||
)
|
||||
|
||||
// AuthorizerReturnCode are the integer codes
|
||||
// that the authorizer callback may return.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_deny.html
|
||||
type AuthorizerReturnCode uint32
|
||||
|
||||
const (
|
||||
AUTH_OK AuthorizerReturnCode = 0
|
||||
AUTH_DENY AuthorizerReturnCode = 1 /* Abort the SQL statement with an error */
|
||||
AUTH_IGNORE AuthorizerReturnCode = 2 /* Don't allow access, but don't generate an error */
|
||||
)
|
||||
|
||||
// CheckpointMode are all the checkpoint mode values.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_checkpoint_full.html
|
||||
type CheckpointMode uint32
|
||||
|
||||
const (
|
||||
CHECKPOINT_PASSIVE CheckpointMode = 0 /* Do as much as possible w/o blocking */
|
||||
CHECKPOINT_FULL CheckpointMode = 1 /* Wait for writers, then checkpoint */
|
||||
CHECKPOINT_RESTART CheckpointMode = 2 /* Like FULL but wait for readers */
|
||||
CHECKPOINT_TRUNCATE CheckpointMode = 3 /* Like RESTART but also truncate WAL */
|
||||
)
|
||||
|
||||
// TxnState are the allowed return values from [Conn.TxnState].
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_txn_none.html
|
||||
type TxnState uint32
|
||||
|
||||
const (
|
||||
TXN_NONE TxnState = 0
|
||||
TXN_READ TxnState = 1
|
||||
TXN_WRITE TxnState = 2
|
||||
)
|
||||
|
||||
// Datatype is a fundamental datatype of SQLite.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_blob.html
|
||||
// https://sqlite.org/c3ref/c_blob.html
|
||||
type Datatype uint32
|
||||
|
||||
const (
|
||||
@@ -182,18 +343,18 @@ const (
|
||||
|
||||
// String implements the [fmt.Stringer] interface.
|
||||
func (t Datatype) String() string {
|
||||
const name = "INTEGERFLOATTEXTBLOBNULL"
|
||||
const name = "INTEGERFLOATEXTBLOBNULL"
|
||||
switch t {
|
||||
case INTEGER:
|
||||
return name[0:7]
|
||||
case FLOAT:
|
||||
return name[7:12]
|
||||
case TEXT:
|
||||
return name[12:16]
|
||||
return name[11:15]
|
||||
case BLOB:
|
||||
return name[16:20]
|
||||
return name[15:19]
|
||||
case NULL:
|
||||
return name[20:24]
|
||||
return name[19:23]
|
||||
}
|
||||
return strconv.FormatUint(uint64(t), 10)
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// Context is the context in which an SQL function executes.
|
||||
// An SQLite [Context] is in no way related to a Go [context.Context].
|
||||
//
|
||||
// https://sqlite.org/c3ref/context.html
|
||||
type Context struct {
|
||||
c *Conn
|
||||
handle uint32
|
||||
}
|
||||
|
||||
// Conn returns the database connection of the
|
||||
// [Conn.CreateFunction] or [Conn.CreateWindowFunction]
|
||||
// routines that originally registered the application defined function.
|
||||
//
|
||||
// https://sqlite.org/c3ref/context_db_handle.html
|
||||
func (ctx Context) Conn() *Conn {
|
||||
return ctx.c
|
||||
}
|
||||
|
||||
// SetAuxData saves metadata for argument n of the function.
|
||||
//
|
||||
// https://sqlite.org/c3ref/get_auxdata.html
|
||||
func (ctx Context) SetAuxData(n int, data any) {
|
||||
ptr := util.AddHandle(ctx.c.ctx, data)
|
||||
ctx.c.call("sqlite3_set_auxdata_go", uint64(ctx.handle), uint64(n), uint64(ptr))
|
||||
}
|
||||
|
||||
// GetAuxData returns metadata for argument n of the function.
|
||||
//
|
||||
// https://sqlite.org/c3ref/get_auxdata.html
|
||||
func (ctx Context) GetAuxData(n int) any {
|
||||
ptr := uint32(ctx.c.call("sqlite3_get_auxdata", uint64(ctx.handle), uint64(n)))
|
||||
return util.GetHandle(ctx.c.ctx, ptr)
|
||||
}
|
||||
|
||||
// ResultBool sets the result of the function to a bool.
|
||||
// SQLite does not have a separate boolean storage class.
|
||||
// Instead, boolean values are stored as integers 0 (false) and 1 (true).
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultBool(value bool) {
|
||||
var i int64
|
||||
if value {
|
||||
i = 1
|
||||
}
|
||||
ctx.ResultInt64(i)
|
||||
}
|
||||
|
||||
// ResultInt sets the result of the function to an int.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultInt(value int) {
|
||||
ctx.ResultInt64(int64(value))
|
||||
}
|
||||
|
||||
// ResultInt64 sets the result of the function to an int64.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultInt64(value int64) {
|
||||
ctx.c.call("sqlite3_result_int64",
|
||||
uint64(ctx.handle), uint64(value))
|
||||
}
|
||||
|
||||
// ResultFloat sets the result of the function to a float64.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultFloat(value float64) {
|
||||
ctx.c.call("sqlite3_result_double",
|
||||
uint64(ctx.handle), math.Float64bits(value))
|
||||
}
|
||||
|
||||
// ResultText sets the result of the function to a string.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultText(value string) {
|
||||
ptr := ctx.c.newString(value)
|
||||
ctx.c.call("sqlite3_result_text64",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(value)),
|
||||
uint64(ctx.c.freer), _UTF8)
|
||||
}
|
||||
|
||||
// ResultRawText sets the text result of the function to a []byte.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultRawText(value []byte) {
|
||||
ptr := ctx.c.newBytes(value)
|
||||
ctx.c.call("sqlite3_result_text64",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(value)),
|
||||
uint64(ctx.c.freer), _UTF8)
|
||||
}
|
||||
|
||||
// ResultBlob sets the result of the function to a []byte.
|
||||
// Returning a nil slice is the same as calling [Context.ResultNull].
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultBlob(value []byte) {
|
||||
ptr := ctx.c.newBytes(value)
|
||||
ctx.c.call("sqlite3_result_blob64",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(value)),
|
||||
uint64(ctx.c.freer))
|
||||
}
|
||||
|
||||
// ResultZeroBlob sets the result of the function to a zero-filled, length n BLOB.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultZeroBlob(n int64) {
|
||||
ctx.c.call("sqlite3_result_zeroblob64",
|
||||
uint64(ctx.handle), uint64(n))
|
||||
}
|
||||
|
||||
// ResultNull sets the result of the function to NULL.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultNull() {
|
||||
ctx.c.call("sqlite3_result_null",
|
||||
uint64(ctx.handle))
|
||||
}
|
||||
|
||||
// ResultTime sets the result of the function to a [time.Time].
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultTime(value time.Time, format TimeFormat) {
|
||||
if format == TimeFormatDefault {
|
||||
ctx.resultRFC3339Nano(value)
|
||||
return
|
||||
}
|
||||
switch v := format.Encode(value).(type) {
|
||||
case string:
|
||||
ctx.ResultText(v)
|
||||
case int64:
|
||||
ctx.ResultInt64(v)
|
||||
case float64:
|
||||
ctx.ResultFloat(v)
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx Context) resultRFC3339Nano(value time.Time) {
|
||||
const maxlen = uint64(len(time.RFC3339Nano)) + 5
|
||||
|
||||
ptr := ctx.c.new(maxlen)
|
||||
buf := util.View(ctx.c.mod, ptr, maxlen)
|
||||
buf = value.AppendFormat(buf[:0], time.RFC3339Nano)
|
||||
|
||||
ctx.c.call("sqlite3_result_text64",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(buf)),
|
||||
uint64(ctx.c.freer), _UTF8)
|
||||
}
|
||||
|
||||
// ResultPointer sets the result of the function to NULL, just like [Context.ResultNull],
|
||||
// except that it also associates ptr with that NULL value such that it can be retrieved
|
||||
// within an application-defined SQL function using [Value.Pointer].
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultPointer(ptr any) {
|
||||
valPtr := util.AddHandle(ctx.c.ctx, ptr)
|
||||
ctx.c.call("sqlite3_result_pointer_go", uint64(valPtr))
|
||||
}
|
||||
|
||||
// ResultJSON sets the result of the function to the JSON encoding of value.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultJSON(value any) {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
ctx.ResultError(err)
|
||||
return
|
||||
}
|
||||
ctx.ResultRawText(data)
|
||||
}
|
||||
|
||||
// ResultValue sets the result of the function to a copy of [Value].
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultValue(value Value) {
|
||||
if value.c != ctx.c {
|
||||
ctx.ResultError(MISUSE)
|
||||
return
|
||||
}
|
||||
ctx.c.call("sqlite3_result_value",
|
||||
uint64(ctx.handle), uint64(value.handle))
|
||||
}
|
||||
|
||||
// ResultError sets the result of the function an error.
|
||||
//
|
||||
// https://sqlite.org/c3ref/result_blob.html
|
||||
func (ctx Context) ResultError(err error) {
|
||||
if errors.Is(err, NOMEM) {
|
||||
ctx.c.call("sqlite3_result_error_nomem", uint64(ctx.handle))
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, TOOBIG) {
|
||||
ctx.c.call("sqlite3_result_error_toobig", uint64(ctx.handle))
|
||||
return
|
||||
}
|
||||
|
||||
msg, code := errorCode(err, _OK)
|
||||
if msg != "" {
|
||||
defer ctx.c.arena.mark()()
|
||||
ptr := ctx.c.arena.string(msg)
|
||||
ctx.c.call("sqlite3_result_error",
|
||||
uint64(ctx.handle), uint64(ptr), uint64(len(msg)))
|
||||
}
|
||||
if code != _OK {
|
||||
ctx.c.call("sqlite3_result_error_code",
|
||||
uint64(ctx.handle), uint64(code))
|
||||
}
|
||||
}
|
||||
|
||||
// VTabNoChange may return true if a column is being fetched as part
|
||||
// of an update during which the column value will not change.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vtab_nochange.html
|
||||
func (ctx Context) VTabNoChange() bool {
|
||||
r := ctx.c.call("sqlite3_vtab_nochange", uint64(ctx.handle))
|
||||
return r != 0
|
||||
}
|
||||
+269
-111
@@ -12,98 +12,199 @@
|
||||
//
|
||||
// sql.Open("sqlite3", "file:demo.db?_txlock=immediate")
|
||||
//
|
||||
// Possible values are: "deferred", "immediate", "exclusive".
|
||||
// A [read-only] transaction is always "deferred", regardless of "_txlock".
|
||||
//
|
||||
// The time encoding/decoding format can be specified using "_timefmt":
|
||||
//
|
||||
// sql.Open("sqlite3", "file:demo.db?_timefmt=sqlite")
|
||||
//
|
||||
// Possible values are: "auto" (the default), "sqlite", "rfc3339";
|
||||
// "auto" encodes as RFC 3339 and decodes any [format] supported by SQLite;
|
||||
// "sqlite" encodes as SQLite and decodes any [format] supported by SQLite;
|
||||
// "rfc3339" encodes and decodes RFC 3339 only.
|
||||
//
|
||||
// [PRAGMA] statements can be specified using "_pragma":
|
||||
//
|
||||
// sql.Open("sqlite3", "file:demo.db?_pragma=busy_timeout(10000)&_pragma=locking_mode(normal)")
|
||||
// sql.Open("sqlite3", "file:demo.db?_pragma=busy_timeout(10000)")
|
||||
//
|
||||
// If no PRAGMAs are specified, a busy timeout of 1 minute
|
||||
// and normal locking mode are used.
|
||||
// If no PRAGMAs are specified, a busy timeout of 1 minute is set.
|
||||
//
|
||||
// Order matters:
|
||||
// busy timeout and locking mode should be the first PRAGMAs set, in that order.
|
||||
//
|
||||
// [URI]: https://www.sqlite.org/uri.html
|
||||
// [PRAGMA]: https://www.sqlite.org/pragma.html
|
||||
// [TRANSACTION]: https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
|
||||
// [URI]: https://sqlite.org/uri.html
|
||||
// [PRAGMA]: https://sqlite.org/pragma.html
|
||||
// [format]: https://sqlite.org/lang_datefunc.html#time_values
|
||||
// [TRANSACTION]: https://sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
|
||||
// [read-only]: https://pkg.go.dev/database/sql#TxOptions
|
||||
package driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// This variable can be replaced with -ldflags:
|
||||
//
|
||||
// go build -ldflags="-X github.com/ncruces/go-sqlite3/driver.driverName=sqlite"
|
||||
var driverName = "sqlite3"
|
||||
|
||||
func init() {
|
||||
sql.Register("sqlite3", sqlite{})
|
||||
if driverName != "" {
|
||||
sql.Register(driverName, &SQLite{})
|
||||
}
|
||||
}
|
||||
|
||||
type sqlite struct{}
|
||||
|
||||
func (sqlite) Open(name string) (_ driver.Conn, err error) {
|
||||
var c conn
|
||||
c.Conn, err = sqlite3.Open(name)
|
||||
// Open opens the SQLite database specified by dataSourceName as a [database/sql.DB].
|
||||
//
|
||||
// The init function is called by the driver on new connections.
|
||||
// The [sqlite3.Conn] can be used to execute queries, register functions, etc.
|
||||
// Any error returned closes the connection and is returned to [database/sql].
|
||||
func Open(dataSourceName string, init func(*sqlite3.Conn) error) (*sql.DB, error) {
|
||||
c, err := (&SQLite{Init: init}).OpenConnector(dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sql.OpenDB(c), nil
|
||||
}
|
||||
|
||||
c.txBegin = "BEGIN"
|
||||
var pragmas []string
|
||||
// SQLite implements [database/sql/driver.Driver].
|
||||
type SQLite struct {
|
||||
// Init function is called by the driver on new connections.
|
||||
// The [sqlite3.Conn] can be used to execute queries, register functions, etc.
|
||||
// Any error returned closes the connection and is returned to [database/sql].
|
||||
Init func(*sqlite3.Conn) error
|
||||
}
|
||||
|
||||
// Open implements [database/sql/driver.Driver].
|
||||
func (d *SQLite) Open(name string) (driver.Conn, error) {
|
||||
c, err := d.newConnector(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Connect(context.Background())
|
||||
}
|
||||
|
||||
// OpenConnector implements [database/sql/driver.DriverContext].
|
||||
func (d *SQLite) OpenConnector(name string) (driver.Connector, error) {
|
||||
return d.newConnector(name)
|
||||
}
|
||||
|
||||
func (d *SQLite) newConnector(name string) (*connector, error) {
|
||||
c := connector{driver: d, name: name}
|
||||
|
||||
var txlock, timefmt string
|
||||
if strings.HasPrefix(name, "file:") {
|
||||
if _, after, ok := strings.Cut(name, "?"); ok {
|
||||
query, _ := url.ParseQuery(after)
|
||||
|
||||
switch s := query.Get("_txlock"); s {
|
||||
case "":
|
||||
c.txBegin = "BEGIN"
|
||||
case "deferred", "immediate", "exclusive":
|
||||
c.txBegin = "BEGIN " + s
|
||||
default:
|
||||
c.Close()
|
||||
return nil, fmt.Errorf("sqlite3: invalid _txlock: %s", s)
|
||||
query, err := url.ParseQuery(after)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pragmas = query["_pragma"]
|
||||
txlock = query.Get("_txlock")
|
||||
timefmt = query.Get("_timefmt")
|
||||
c.pragmas = query.Has("_pragma")
|
||||
}
|
||||
}
|
||||
if len(pragmas) == 0 {
|
||||
err := c.Conn.Exec(`
|
||||
PRAGMA busy_timeout=60000;
|
||||
PRAGMA locking_mode=normal;
|
||||
`)
|
||||
|
||||
switch txlock {
|
||||
case "":
|
||||
c.txBegin = "BEGIN"
|
||||
case "deferred", "immediate", "exclusive":
|
||||
c.txBegin = "BEGIN " + txlock
|
||||
default:
|
||||
return nil, fmt.Errorf("sqlite3: invalid _txlock: %s", txlock)
|
||||
}
|
||||
|
||||
switch timefmt {
|
||||
case "":
|
||||
c.tmRead = sqlite3.TimeFormatAuto
|
||||
c.tmWrite = sqlite3.TimeFormatDefault
|
||||
case "sqlite":
|
||||
c.tmRead = sqlite3.TimeFormatAuto
|
||||
c.tmWrite = sqlite3.TimeFormat3
|
||||
case "rfc3339":
|
||||
c.tmRead = sqlite3.TimeFormatDefault
|
||||
c.tmWrite = sqlite3.TimeFormatDefault
|
||||
default:
|
||||
c.tmRead = sqlite3.TimeFormat(timefmt)
|
||||
c.tmWrite = sqlite3.TimeFormat(timefmt)
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
type connector struct {
|
||||
driver *SQLite
|
||||
name string
|
||||
txBegin string
|
||||
tmRead sqlite3.TimeFormat
|
||||
tmWrite sqlite3.TimeFormat
|
||||
pragmas bool
|
||||
}
|
||||
|
||||
func (n *connector) Driver() driver.Driver {
|
||||
return n.driver
|
||||
}
|
||||
|
||||
func (n *connector) Connect(ctx context.Context) (_ driver.Conn, err error) {
|
||||
c := &conn{
|
||||
txBegin: n.txBegin,
|
||||
tmRead: n.tmRead,
|
||||
tmWrite: n.tmWrite,
|
||||
}
|
||||
|
||||
c.Conn, err = sqlite3.Open(n.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
c.reusable = true
|
||||
} else {
|
||||
s, _, err := c.Conn.Prepare(`
|
||||
SELECT * FROM
|
||||
PRAGMA_locking_mode,
|
||||
PRAGMA_query_only;
|
||||
`)
|
||||
}()
|
||||
|
||||
old := c.Conn.SetInterrupt(ctx)
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
|
||||
if !n.pragmas {
|
||||
err = c.Conn.BusyTimeout(60 * time.Second)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
if s.Step() {
|
||||
c.reusable = s.ColumnText(0) == "normal"
|
||||
c.readOnly = s.ColumnRawText(1)[0] // 0 or 1
|
||||
}
|
||||
if n.driver.Init != nil {
|
||||
err = n.driver.Init(c.Conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if n.pragmas || n.driver.Init != nil {
|
||||
s, _, err := c.Conn.Prepare(`PRAGMA query_only`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.Step() && s.ColumnBool(0) {
|
||||
c.readOnly = '1'
|
||||
} else {
|
||||
c.readOnly = '0'
|
||||
}
|
||||
err = s.Close()
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &c, nil
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
@@ -111,20 +212,21 @@ type conn struct {
|
||||
txBegin string
|
||||
txCommit string
|
||||
txRollback string
|
||||
reusable bool
|
||||
tmRead sqlite3.TimeFormat
|
||||
tmWrite sqlite3.TimeFormat
|
||||
readOnly byte
|
||||
}
|
||||
|
||||
var (
|
||||
// Ensure these interfaces are implemented:
|
||||
_ driver.ExecerContext = &conn{}
|
||||
_ driver.ConnBeginTx = &conn{}
|
||||
_ driver.Validator = &conn{}
|
||||
_ sqlite3.DriverConn = &conn{}
|
||||
_ driver.ConnPrepareContext = &conn{}
|
||||
_ driver.ExecerContext = &conn{}
|
||||
_ driver.ConnBeginTx = &conn{}
|
||||
_ sqlite3.DriverConn = &conn{}
|
||||
)
|
||||
|
||||
func (c *conn) IsValid() bool {
|
||||
return c.reusable
|
||||
func (c *conn) Raw() *sqlite3.Conn {
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
func (c *conn) Begin() (driver.Tx, error) {
|
||||
@@ -140,10 +242,10 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
|
||||
txBegin = `
|
||||
BEGIN deferred;
|
||||
PRAGMA query_only=on`
|
||||
c.txCommit = `
|
||||
c.txRollback = `
|
||||
ROLLBACK;
|
||||
PRAGMA query_only=` + string(c.readOnly)
|
||||
c.txRollback = c.txCommit
|
||||
c.txCommit = c.txRollback
|
||||
}
|
||||
|
||||
switch opts.Isolation {
|
||||
@@ -167,14 +269,20 @@ func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, e
|
||||
|
||||
func (c *conn) Commit() error {
|
||||
err := c.Conn.Exec(c.txCommit)
|
||||
if err != nil && !c.GetAutocommit() {
|
||||
if err != nil && !c.Conn.GetAutocommit() {
|
||||
c.Rollback()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) Rollback() error {
|
||||
return c.Conn.Exec(c.txRollback)
|
||||
err := c.Conn.Exec(c.txRollback)
|
||||
if errors.Is(err, sqlite3.INTERRUPT) {
|
||||
old := c.Conn.SetInterrupt(context.Background())
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
err = c.Conn.Exec(c.txRollback)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) Prepare(query string) (driver.Stmt, error) {
|
||||
@@ -190,19 +298,10 @@ func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, e
|
||||
return nil, err
|
||||
}
|
||||
if tail != "" {
|
||||
// Check if the tail contains any SQL.
|
||||
st, _, err := c.Conn.Prepare(tail)
|
||||
if err != nil {
|
||||
s.Close()
|
||||
return nil, err
|
||||
}
|
||||
if st != nil {
|
||||
s.Close()
|
||||
st.Close()
|
||||
return nil, util.TailErr
|
||||
}
|
||||
s.Close()
|
||||
return nil, util.TailErr
|
||||
}
|
||||
return &stmt{s, c.Conn}, nil
|
||||
return &stmt{Stmt: s, tmRead: c.tmRead, tmWrite: c.tmWrite}, nil
|
||||
}
|
||||
|
||||
func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||
@@ -211,6 +310,12 @@ func (c *conn) ExecContext(ctx context.Context, query string, args []driver.Name
|
||||
return nil, driver.ErrSkip
|
||||
}
|
||||
|
||||
if savept, ok := ctx.(*saveptCtx); ok {
|
||||
// Called from driver.Savepoint.
|
||||
savept.Savepoint = c.Conn.Savepoint()
|
||||
return resultRowsAffected(0), nil
|
||||
}
|
||||
|
||||
old := c.Conn.SetInterrupt(ctx)
|
||||
defer c.Conn.SetInterrupt(old)
|
||||
|
||||
@@ -222,9 +327,14 @@ func (c *conn) ExecContext(ctx context.Context, query string, args []driver.Name
|
||||
return newResult(c.Conn), nil
|
||||
}
|
||||
|
||||
func (c *conn) CheckNamedValue(arg *driver.NamedValue) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type stmt struct {
|
||||
Stmt *sqlite3.Stmt
|
||||
Conn *sqlite3.Conn
|
||||
*sqlite3.Stmt
|
||||
tmWrite sqlite3.TimeFormat
|
||||
tmRead sqlite3.TimeFormat
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -234,10 +344,6 @@ var (
|
||||
_ driver.NamedValueChecker = &stmt{}
|
||||
)
|
||||
|
||||
func (s *stmt) Close() error {
|
||||
return s.Stmt.Close()
|
||||
}
|
||||
|
||||
func (s *stmt) NumInput() int {
|
||||
n := s.Stmt.BindCount()
|
||||
for i := 1; i <= n; i++ {
|
||||
@@ -259,26 +365,35 @@ func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
}
|
||||
|
||||
func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||
// Use QueryContext to setup bindings.
|
||||
// No need to close rows: that simply resets the statement, exec does the same.
|
||||
_, err := s.QueryContext(ctx, args)
|
||||
err := s.setupBindings(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
old := s.Stmt.Conn().SetInterrupt(ctx)
|
||||
defer s.Stmt.Conn().SetInterrupt(old)
|
||||
|
||||
err = s.Stmt.Exec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newResult(s.Conn), nil
|
||||
return newResult(s.Stmt.Conn()), nil
|
||||
}
|
||||
|
||||
func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||
err := s.Stmt.ClearBindings()
|
||||
err := s.setupBindings(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rows{ctx: ctx, stmt: s}, nil
|
||||
}
|
||||
|
||||
func (s *stmt) setupBindings(args []driver.NamedValue) error {
|
||||
err := s.Stmt.ClearBindings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ids [3]int
|
||||
for _, arg := range args {
|
||||
@@ -310,7 +425,11 @@ func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driv
|
||||
case sqlite3.ZeroBlob:
|
||||
err = s.Stmt.BindZeroBlob(id, int64(a))
|
||||
case time.Time:
|
||||
err = s.Stmt.BindTime(id, a, sqlite3.TimeFormatDefault)
|
||||
err = s.Stmt.BindTime(id, a, s.tmWrite)
|
||||
case util.JSON:
|
||||
err = s.Stmt.BindJSON(id, a.Value)
|
||||
case util.PointerUnwrap:
|
||||
err = s.Stmt.BindPointer(id, util.UnwrapPointer(a))
|
||||
case nil:
|
||||
err = s.Stmt.BindNull(id)
|
||||
default:
|
||||
@@ -318,17 +437,18 @@ func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driv
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return &rows{ctx, s.Stmt, s.Conn}, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stmt) CheckNamedValue(arg *driver.NamedValue) error {
|
||||
switch arg.Value.(type) {
|
||||
case bool, int, int64, float64, string, []byte,
|
||||
sqlite3.ZeroBlob, time.Time, nil:
|
||||
time.Time, sqlite3.ZeroBlob,
|
||||
util.JSON, util.PointerUnwrap,
|
||||
nil:
|
||||
return nil
|
||||
default:
|
||||
return driver.ErrSkip
|
||||
@@ -367,27 +487,52 @@ func (r resultRowsAffected) RowsAffected() (int64, error) {
|
||||
}
|
||||
|
||||
type rows struct {
|
||||
ctx context.Context
|
||||
Stmt *sqlite3.Stmt
|
||||
Conn *sqlite3.Conn
|
||||
ctx context.Context
|
||||
*stmt
|
||||
names []string
|
||||
types []string
|
||||
}
|
||||
|
||||
func (r *rows) Close() error {
|
||||
r.Stmt.ClearBindings()
|
||||
return r.Stmt.Reset()
|
||||
}
|
||||
|
||||
func (r *rows) Columns() []string {
|
||||
count := r.Stmt.ColumnCount()
|
||||
columns := make([]string, count)
|
||||
for i := range columns {
|
||||
columns[i] = r.Stmt.ColumnName(i)
|
||||
if r.names == nil {
|
||||
count := r.Stmt.ColumnCount()
|
||||
r.names = make([]string, count)
|
||||
for i := range r.names {
|
||||
r.names[i] = r.Stmt.ColumnName(i)
|
||||
}
|
||||
}
|
||||
return columns
|
||||
return r.names
|
||||
}
|
||||
|
||||
func (r *rows) declType(index int) string {
|
||||
if r.types == nil {
|
||||
count := r.Stmt.ColumnCount()
|
||||
r.types = make([]string, count)
|
||||
for i := range r.types {
|
||||
r.types[i] = strings.ToUpper(r.Stmt.ColumnDeclType(i))
|
||||
}
|
||||
}
|
||||
return r.types[index]
|
||||
}
|
||||
|
||||
func (r *rows) ColumnTypeDatabaseTypeName(index int) string {
|
||||
decltype := r.declType(index)
|
||||
if len := len(decltype); len > 0 && decltype[len-1] == ')' {
|
||||
if i := strings.LastIndexByte(decltype, '('); i >= 0 {
|
||||
decltype = decltype[:i]
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(decltype)
|
||||
}
|
||||
|
||||
func (r *rows) Next(dest []driver.Value) error {
|
||||
old := r.Conn.SetInterrupt(r.ctx)
|
||||
defer r.Conn.SetInterrupt(old)
|
||||
old := r.Stmt.Conn().SetInterrupt(r.ctx)
|
||||
defer r.Stmt.Conn().SetInterrupt(old)
|
||||
|
||||
if !r.Stmt.Step() {
|
||||
if err := r.Stmt.Err(); err != nil {
|
||||
@@ -396,26 +541,39 @@ func (r *rows) Next(dest []driver.Value) error {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
data := unsafe.Slice((*any)(unsafe.SliceData(dest)), len(dest))
|
||||
err := r.Stmt.Columns(data)
|
||||
for i := range dest {
|
||||
switch r.Stmt.ColumnType(i) {
|
||||
case sqlite3.INTEGER:
|
||||
dest[i] = r.Stmt.ColumnInt64(i)
|
||||
case sqlite3.FLOAT:
|
||||
dest[i] = r.Stmt.ColumnFloat(i)
|
||||
case sqlite3.BLOB:
|
||||
dest[i] = r.Stmt.ColumnRawBlob(i)
|
||||
case sqlite3.TEXT:
|
||||
dest[i] = stringOrTime(r.Stmt.ColumnRawText(i))
|
||||
case sqlite3.NULL:
|
||||
if buf, ok := dest[i].([]byte); ok {
|
||||
dest[i] = buf[0:0]
|
||||
} else {
|
||||
dest[i] = nil
|
||||
if t, ok := r.decodeTime(i, dest[i]); ok {
|
||||
dest[i] = t
|
||||
continue
|
||||
}
|
||||
if s, ok := dest[i].(string); ok {
|
||||
t, ok := maybeTime(s)
|
||||
if ok {
|
||||
dest[i] = t
|
||||
}
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return r.Stmt.Err()
|
||||
func (r *rows) decodeTime(i int, v any) (_ time.Time, _ bool) {
|
||||
if r.tmRead == sqlite3.TimeFormatDefault {
|
||||
return
|
||||
}
|
||||
switch r.declType(i) {
|
||||
case "DATE", "TIME", "DATETIME", "TIMESTAMP":
|
||||
// maybe
|
||||
default:
|
||||
return
|
||||
}
|
||||
switch v.(type) {
|
||||
case int64, float64, string:
|
||||
// maybe
|
||||
default:
|
||||
return
|
||||
}
|
||||
t, err := r.tmRead.Decode(v)
|
||||
return t, err == nil
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
)
|
||||
|
||||
// Savepoint establishes a new transaction savepoint.
|
||||
//
|
||||
// https://sqlite.org/lang_savepoint.html
|
||||
func Savepoint(tx *sql.Tx) sqlite3.Savepoint {
|
||||
var ctx saveptCtx
|
||||
tx.ExecContext(&ctx, "")
|
||||
return ctx.Savepoint
|
||||
}
|
||||
|
||||
type saveptCtx struct{ sqlite3.Savepoint }
|
||||
|
||||
func (*saveptCtx) Deadline() (deadline time.Time, ok bool) { return }
|
||||
|
||||
func (*saveptCtx) Done() <-chan struct{} { return nil }
|
||||
|
||||
func (*saveptCtx) Err() error { return nil }
|
||||
|
||||
func (*saveptCtx) Value(key any) any { return nil }
|
||||
+9
-9
@@ -1,7 +1,6 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -9,23 +8,24 @@ import (
|
||||
// if it roundtrips back to the same string.
|
||||
// This way times can be persisted to, and recovered from, the database,
|
||||
// but if a string is needed, [database/sql] will recover the same string.
|
||||
func stringOrTime(text []byte) driver.Value {
|
||||
func maybeTime(text string) (_ time.Time, _ bool) {
|
||||
// Weed out (some) values that can't possibly be
|
||||
// [time.RFC3339Nano] timestamps.
|
||||
if len(text) < len("2006-01-02T15:04:05Z") {
|
||||
return string(text)
|
||||
return
|
||||
}
|
||||
if len(text) > len(time.RFC3339Nano) {
|
||||
return string(text)
|
||||
return
|
||||
}
|
||||
if text[4] != '-' || text[10] != 'T' || text[16] != ':' {
|
||||
return string(text)
|
||||
return
|
||||
}
|
||||
|
||||
// Slow path.
|
||||
date, err := time.Parse(time.RFC3339Nano, string(text))
|
||||
if err == nil && date.Format(time.RFC3339Nano) == string(text) {
|
||||
return date
|
||||
var buf [len(time.RFC3339Nano)]byte
|
||||
date, err := time.Parse(time.RFC3339Nano, text)
|
||||
if err == nil && text == string(date.AppendFormat(buf[:0], time.RFC3339Nano)) {
|
||||
return date, true
|
||||
}
|
||||
return string(text)
|
||||
return
|
||||
}
|
||||
+9
-7
@@ -1,16 +1,18 @@
|
||||
# Embeddable WASM build of SQLite
|
||||
# Embeddable Wasm build of SQLite
|
||||
|
||||
This folder includes an embeddable WASM build of SQLite 3.42.0 for use with
|
||||
This folder includes an embeddable Wasm build of SQLite 3.45.3 for use with
|
||||
[`github.com/ncruces/go-sqlite3`](https://pkg.go.dev/github.com/ncruces/go-sqlite3).
|
||||
|
||||
The following optional features are compiled in:
|
||||
- [math functions](https://www.sqlite.org/lang_mathfunc.html)
|
||||
- [FTS3/4](https://www.sqlite.org/fts3.html)/[5](https://www.sqlite.org/fts5.html)
|
||||
- [JSON](https://www.sqlite.org/json1.html)
|
||||
- [R*Tree](https://www.sqlite.org/rtree.html)
|
||||
- [GeoPoly](https://www.sqlite.org/geopoly.html)
|
||||
- [math functions](https://sqlite.org/lang_mathfunc.html)
|
||||
- [FTS5](https://sqlite.org/fts5.html)
|
||||
- [JSON](https://sqlite.org/json1.html)
|
||||
- [R*Tree](https://sqlite.org/rtree.html)
|
||||
- [GeoPoly](https://sqlite.org/geopoly.html)
|
||||
- [soundex](https://sqlite.org/lang_corefunc.html#soundex)
|
||||
- [base64](https://github.com/sqlite/sqlite/blob/master/ext/misc/base64.c)
|
||||
- [decimal](https://github.com/sqlite/sqlite/blob/master/ext/misc/decimal.c)
|
||||
- [ieee754](https://github.com/sqlite/sqlite/blob/master/ext/misc/ieee754.c)
|
||||
- [regexp](https://github.com/sqlite/sqlite/blob/master/ext/misc/regexp.c)
|
||||
- [series](https://github.com/sqlite/sqlite/blob/master/ext/misc/series.c)
|
||||
- [uint](https://github.com/sqlite/sqlite/blob/master/ext/misc/uint.c)
|
||||
|
||||
+10
-6
@@ -4,24 +4,28 @@ set -euo pipefail
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
|
||||
ROOT=../
|
||||
BINARYEN="$ROOT/tools/binaryen-version_113/bin"
|
||||
WASI_SDK="$ROOT/tools/wasi-sdk-20.0/bin"
|
||||
BINARYEN="$ROOT/tools/binaryen-version_117/bin"
|
||||
WASI_SDK="$ROOT/tools/wasi-sdk-22.0/bin"
|
||||
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -flto -g0 -O2 \
|
||||
"$WASI_SDK/clang" --target=wasm32-wasi -std=c17 -flto -g0 -O2 \
|
||||
-Wall -Wextra -Wno-unused-parameter \
|
||||
-o sqlite3.wasm "$ROOT/sqlite3/main.c" \
|
||||
-I"$ROOT/sqlite3" \
|
||||
-mexec-model=reactor \
|
||||
-mmutable-globals \
|
||||
-msimd128 -mmutable-globals \
|
||||
-mbulk-memory -mreference-types \
|
||||
-mnontrapping-fptoint -msign-ext \
|
||||
-fno-stack-protector -fno-stack-clash-protection \
|
||||
-Wl,--initial-memory=327680 \
|
||||
-Wl,--stack-first \
|
||||
-Wl,--import-undefined \
|
||||
-D_HAVE_SQLITE_CONFIG_H \
|
||||
$(awk '{print "-Wl,--export="$0}' exports.txt)
|
||||
|
||||
trap 'rm -f sqlite3.tmp' EXIT
|
||||
"$BINARYEN/wasm-ctor-eval" -g -c _initialize sqlite3.wasm -o sqlite3.tmp
|
||||
"$BINARYEN/wasm-opt" -g -O2 sqlite3.tmp -o sqlite3.wasm \
|
||||
--enable-multivalue --enable-mutable-globals \
|
||||
"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -O3 \
|
||||
sqlite3.tmp -o sqlite3.wasm \
|
||||
--enable-simd --enable-mutable-globals --enable-multivalue \
|
||||
--enable-bulk-memory --enable-reference-types \
|
||||
--enable-nontrapping-float-to-int --enable-sign-ext
|
||||
+116
-38
@@ -1,49 +1,127 @@
|
||||
aligned_alloc
|
||||
free
|
||||
malloc
|
||||
malloc_destructor
|
||||
sqlite3_errcode
|
||||
sqlite3_errstr
|
||||
sqlite3_errmsg
|
||||
sqlite3_error_offset
|
||||
sqlite3_open_v2
|
||||
sqlite3_close
|
||||
sqlite3_close_v2
|
||||
sqlite3_prepare_v3
|
||||
sqlite3_finalize
|
||||
sqlite3_reset
|
||||
sqlite3_step
|
||||
sqlite3_exec
|
||||
sqlite3_clear_bindings
|
||||
sqlite3_anycollseq_init
|
||||
sqlite3_autovacuum_pages_go
|
||||
sqlite3_backup_finish
|
||||
sqlite3_backup_init
|
||||
sqlite3_backup_pagecount
|
||||
sqlite3_backup_remaining
|
||||
sqlite3_backup_step
|
||||
sqlite3_bind_blob64
|
||||
sqlite3_bind_double
|
||||
sqlite3_bind_int64
|
||||
sqlite3_bind_null
|
||||
sqlite3_bind_parameter_count
|
||||
sqlite3_bind_parameter_index
|
||||
sqlite3_bind_parameter_name
|
||||
sqlite3_bind_null
|
||||
sqlite3_bind_int64
|
||||
sqlite3_bind_double
|
||||
sqlite3_bind_pointer_go
|
||||
sqlite3_bind_text64
|
||||
sqlite3_bind_blob64
|
||||
sqlite3_bind_value
|
||||
sqlite3_bind_zeroblob64
|
||||
sqlite3_column_count
|
||||
sqlite3_column_name
|
||||
sqlite3_column_type
|
||||
sqlite3_column_int64
|
||||
sqlite3_column_double
|
||||
sqlite3_column_text
|
||||
sqlite3_blob_bytes
|
||||
sqlite3_blob_close
|
||||
sqlite3_blob_open
|
||||
sqlite3_blob_read
|
||||
sqlite3_blob_reopen
|
||||
sqlite3_blob_write
|
||||
sqlite3_busy_handler_go
|
||||
sqlite3_busy_timeout
|
||||
sqlite3_changes64
|
||||
sqlite3_clear_bindings
|
||||
sqlite3_close
|
||||
sqlite3_close_v2
|
||||
sqlite3_collation_needed_go
|
||||
sqlite3_column_blob
|
||||
sqlite3_column_bytes
|
||||
sqlite3_blob_open
|
||||
sqlite3_blob_close
|
||||
sqlite3_blob_bytes
|
||||
sqlite3_blob_read
|
||||
sqlite3_blob_write
|
||||
sqlite3_blob_reopen
|
||||
sqlite3_backup_init
|
||||
sqlite3_backup_step
|
||||
sqlite3_backup_finish
|
||||
sqlite3_backup_remaining
|
||||
sqlite3_backup_pagecount
|
||||
sqlite3_uri_parameter
|
||||
sqlite3_uri_key
|
||||
sqlite3_changes64
|
||||
sqlite3_column_count
|
||||
sqlite3_column_decltype
|
||||
sqlite3_column_double
|
||||
sqlite3_column_int64
|
||||
sqlite3_column_name
|
||||
sqlite3_column_text
|
||||
sqlite3_column_type
|
||||
sqlite3_column_value
|
||||
sqlite3_columns_go
|
||||
sqlite3_commit_hook_go
|
||||
sqlite3_config_log_go
|
||||
sqlite3_create_aggregate_function_go
|
||||
sqlite3_create_collation_go
|
||||
sqlite3_create_function_go
|
||||
sqlite3_create_module_go
|
||||
sqlite3_create_window_function_go
|
||||
sqlite3_database_file_object
|
||||
sqlite3_db_config
|
||||
sqlite3_db_filename
|
||||
sqlite3_db_name
|
||||
sqlite3_db_readonly
|
||||
sqlite3_db_release_memory
|
||||
sqlite3_declare_vtab
|
||||
sqlite3_errcode
|
||||
sqlite3_errmsg
|
||||
sqlite3_error_offset
|
||||
sqlite3_errstr
|
||||
sqlite3_exec
|
||||
sqlite3_filename_database
|
||||
sqlite3_filename_journal
|
||||
sqlite3_filename_wal
|
||||
sqlite3_finalize
|
||||
sqlite3_get_autocommit
|
||||
sqlite3_get_auxdata
|
||||
sqlite3_interrupt
|
||||
sqlite3_last_insert_rowid
|
||||
sqlite3_get_autocommit
|
||||
sqlite3_limit
|
||||
sqlite3_open_v2
|
||||
sqlite3_overload_function
|
||||
sqlite3_prepare_v3
|
||||
sqlite3_progress_handler_go
|
||||
sqlite3_reset
|
||||
sqlite3_result_blob64
|
||||
sqlite3_result_double
|
||||
sqlite3_result_error
|
||||
sqlite3_result_error_code
|
||||
sqlite3_result_error_nomem
|
||||
sqlite3_result_error_toobig
|
||||
sqlite3_result_int64
|
||||
sqlite3_result_null
|
||||
sqlite3_result_pointer_go
|
||||
sqlite3_result_text64
|
||||
sqlite3_result_value
|
||||
sqlite3_result_zeroblob64
|
||||
sqlite3_rollback_hook_go
|
||||
sqlite3_set_authorizer_go
|
||||
sqlite3_set_auxdata_go
|
||||
sqlite3_set_last_insert_rowid
|
||||
sqlite3_step
|
||||
sqlite3_stmt_busy
|
||||
sqlite3_stmt_readonly
|
||||
sqlite3_stmt_status
|
||||
sqlite3_total_changes64
|
||||
sqlite3_txn_state
|
||||
sqlite3_update_hook_go
|
||||
sqlite3_uri_key
|
||||
sqlite3_uri_parameter
|
||||
sqlite3_value_blob
|
||||
sqlite3_value_bytes
|
||||
sqlite3_value_double
|
||||
sqlite3_value_dup
|
||||
sqlite3_value_free
|
||||
sqlite3_value_int64
|
||||
sqlite3_value_nochange
|
||||
sqlite3_value_numeric_type
|
||||
sqlite3_value_pointer_go
|
||||
sqlite3_value_text
|
||||
sqlite3_value_type
|
||||
sqlite3_vtab_collation
|
||||
sqlite3_vtab_config_go
|
||||
sqlite3_vtab_distinct
|
||||
sqlite3_vtab_in
|
||||
sqlite3_vtab_in_first
|
||||
sqlite3_vtab_in_next
|
||||
sqlite3_vtab_nochange
|
||||
sqlite3_vtab_on_conflict
|
||||
sqlite3_vtab_rhs_value
|
||||
sqlite3_wal_autocheckpoint
|
||||
sqlite3_wal_checkpoint_v2
|
||||
sqlite3_wal_hook_go
|
||||
BIN
Binary file not shown.
+52
-5
@@ -1,6 +1,7 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -9,7 +10,7 @@ import (
|
||||
|
||||
// Error wraps an SQLite Error Code.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/errcode.html
|
||||
// https://sqlite.org/c3ref/errcode.html
|
||||
type Error struct {
|
||||
str string
|
||||
msg string
|
||||
@@ -19,14 +20,14 @@ type Error struct {
|
||||
|
||||
// Code returns the primary error code for this error.
|
||||
//
|
||||
// https://www.sqlite.org/rescode.html
|
||||
// https://sqlite.org/rescode.html
|
||||
func (e *Error) Code() ErrorCode {
|
||||
return ErrorCode(e.code)
|
||||
}
|
||||
|
||||
// ExtendedCode returns the extended error code for this error.
|
||||
//
|
||||
// https://www.sqlite.org/rescode.html
|
||||
// https://sqlite.org/rescode.html
|
||||
func (e *Error) ExtendedCode() ExtendedErrorCode {
|
||||
return ExtendedErrorCode(e.code)
|
||||
}
|
||||
@@ -43,8 +44,7 @@ func (e *Error) Error() string {
|
||||
}
|
||||
|
||||
if e.msg != "" {
|
||||
b.WriteByte(':')
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(": ")
|
||||
b.WriteString(e.msg)
|
||||
}
|
||||
|
||||
@@ -68,6 +68,19 @@ func (e *Error) Is(err error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// As converts this error to an [ErrorCode] or [ExtendedErrorCode].
|
||||
func (e *Error) As(err any) bool {
|
||||
switch c := err.(type) {
|
||||
case *ErrorCode:
|
||||
*c = e.Code()
|
||||
return true
|
||||
case *ExtendedErrorCode:
|
||||
*c = e.ExtendedCode()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Temporary returns true for [BUSY] errors.
|
||||
func (e *Error) Temporary() bool {
|
||||
return e.Code() == BUSY
|
||||
@@ -104,6 +117,15 @@ func (e ExtendedErrorCode) Is(err error) bool {
|
||||
return ok && c == ErrorCode(e)
|
||||
}
|
||||
|
||||
// As converts this error to an [ErrorCode].
|
||||
func (e ExtendedErrorCode) As(err any) bool {
|
||||
c, ok := err.(*ErrorCode)
|
||||
if ok {
|
||||
*c = ErrorCode(e)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// Temporary returns true for [BUSY] errors.
|
||||
func (e ExtendedErrorCode) Temporary() bool {
|
||||
return ErrorCode(e) == BUSY
|
||||
@@ -113,3 +135,28 @@ func (e ExtendedErrorCode) Temporary() bool {
|
||||
func (e ExtendedErrorCode) Timeout() bool {
|
||||
return e == BUSY_TIMEOUT
|
||||
}
|
||||
|
||||
func errorCode(err error, def ErrorCode) (msg string, code uint32) {
|
||||
switch code := err.(type) {
|
||||
case nil:
|
||||
return "", _OK
|
||||
case ErrorCode:
|
||||
return "", uint32(code)
|
||||
case xErrorCode:
|
||||
return "", uint32(code)
|
||||
case *Error:
|
||||
return code.msg, uint32(code.code)
|
||||
}
|
||||
|
||||
var ecode ErrorCode
|
||||
var xcode xErrorCode
|
||||
switch {
|
||||
case errors.As(err, &xcode):
|
||||
code = uint32(xcode)
|
||||
case errors.As(err, &ecode):
|
||||
code = uint32(ecode)
|
||||
default:
|
||||
code = uint32(def)
|
||||
}
|
||||
return err.Error(), code
|
||||
}
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// CollationNeeded registers a callback to be invoked
|
||||
// whenever an unknown collation sequence is required.
|
||||
//
|
||||
// https://sqlite.org/c3ref/collation_needed.html
|
||||
func (c *Conn) CollationNeeded(cb func(db *Conn, name string)) error {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
r := c.call("sqlite3_collation_needed_go", uint64(c.handle), enable)
|
||||
if err := c.error(r); err != nil {
|
||||
return err
|
||||
}
|
||||
c.collation = cb
|
||||
return nil
|
||||
}
|
||||
|
||||
// AnyCollationNeeded uses [Conn.CollationNeeded] to register
|
||||
// a fake collating function for any unknown collating sequence.
|
||||
// The fake collating function works like BINARY.
|
||||
//
|
||||
// This can be used to load schemas that contain
|
||||
// one or more unknown collating sequences.
|
||||
func (c *Conn) AnyCollationNeeded() {
|
||||
c.call("sqlite3_anycollseq_init", uint64(c.handle), 0, 0)
|
||||
}
|
||||
|
||||
// CreateCollation defines a new collating sequence.
|
||||
//
|
||||
// https://sqlite.org/c3ref/create_collation.html
|
||||
func (c *Conn) CreateCollation(name string, fn func(a, b []byte) int) error {
|
||||
defer c.arena.mark()()
|
||||
namePtr := c.arena.string(name)
|
||||
funcPtr := util.AddHandle(c.ctx, fn)
|
||||
r := c.call("sqlite3_create_collation_go",
|
||||
uint64(c.handle), uint64(namePtr), uint64(funcPtr))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// CreateFunction defines a new scalar SQL function.
|
||||
//
|
||||
// https://sqlite.org/c3ref/create_function.html
|
||||
func (c *Conn) CreateFunction(name string, nArg int, flag FunctionFlag, fn ScalarFunction) error {
|
||||
defer c.arena.mark()()
|
||||
namePtr := c.arena.string(name)
|
||||
funcPtr := util.AddHandle(c.ctx, fn)
|
||||
r := c.call("sqlite3_create_function_go",
|
||||
uint64(c.handle), uint64(namePtr), uint64(nArg),
|
||||
uint64(flag), uint64(funcPtr))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// ScalarFunction is the type of a scalar SQL function.
|
||||
// Implementations must not retain arg.
|
||||
type ScalarFunction func(ctx Context, arg ...Value)
|
||||
|
||||
// CreateWindowFunction defines a new aggregate or aggregate window SQL function.
|
||||
// If fn returns a [WindowFunction], then an aggregate window function is created.
|
||||
// If fn returns an [io.Closer], it will be called to free resources.
|
||||
//
|
||||
// https://sqlite.org/c3ref/create_function.html
|
||||
func (c *Conn) CreateWindowFunction(name string, nArg int, flag FunctionFlag, fn func() AggregateFunction) error {
|
||||
defer c.arena.mark()()
|
||||
call := "sqlite3_create_aggregate_function_go"
|
||||
namePtr := c.arena.string(name)
|
||||
funcPtr := util.AddHandle(c.ctx, fn)
|
||||
if _, ok := fn().(WindowFunction); ok {
|
||||
call = "sqlite3_create_window_function_go"
|
||||
}
|
||||
r := c.call(call,
|
||||
uint64(c.handle), uint64(namePtr), uint64(nArg),
|
||||
uint64(flag), uint64(funcPtr))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
// AggregateFunction is the interface an aggregate function should implement.
|
||||
//
|
||||
// https://sqlite.org/appfunc.html
|
||||
type AggregateFunction interface {
|
||||
// Step is invoked to add a row to the current window.
|
||||
// The function arguments, if any, corresponding to the row being added, are passed to Step.
|
||||
// Implementations must not retain arg.
|
||||
Step(ctx Context, arg ...Value)
|
||||
|
||||
// Value is invoked to return the current (or final) value of the aggregate.
|
||||
Value(ctx Context)
|
||||
}
|
||||
|
||||
// WindowFunction is the interface an aggregate window function should implement.
|
||||
//
|
||||
// https://sqlite.org/windowfunctions.html
|
||||
type WindowFunction interface {
|
||||
AggregateFunction
|
||||
|
||||
// Inverse is invoked to remove the oldest presently aggregated result of Step from the current window.
|
||||
// The function arguments, if any, are those passed to Step for the row being removed.
|
||||
// Implementations must not retain arg.
|
||||
Inverse(ctx Context, arg ...Value)
|
||||
}
|
||||
|
||||
// OverloadFunction overloads a function for a virtual table.
|
||||
//
|
||||
// https://sqlite.org/c3ref/overload_function.html
|
||||
func (c *Conn) OverloadFunction(name string, nArg int) error {
|
||||
defer c.arena.mark()()
|
||||
namePtr := c.arena.string(name)
|
||||
r := c.call("sqlite3_overload_function",
|
||||
uint64(c.handle), uint64(namePtr), uint64(nArg))
|
||||
return c.error(r)
|
||||
}
|
||||
|
||||
func destroyCallback(ctx context.Context, mod api.Module, pApp uint32) {
|
||||
util.DelHandle(ctx, pApp)
|
||||
}
|
||||
|
||||
func collationCallback(ctx context.Context, mod api.Module, pArg, pDB, eTextRep, zName uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.collation != nil {
|
||||
name := util.ReadString(mod, zName, _MAX_NAME)
|
||||
c.collation(c, name)
|
||||
}
|
||||
}
|
||||
|
||||
func compareCallback(ctx context.Context, mod api.Module, pApp, nKey1, pKey1, nKey2, pKey2 uint32) uint32 {
|
||||
fn := util.GetHandle(ctx, pApp).(func(a, b []byte) int)
|
||||
return uint32(fn(util.View(mod, pKey1, uint64(nKey1)), util.View(mod, pKey2, uint64(nKey2))))
|
||||
}
|
||||
|
||||
func funcCallback(ctx context.Context, mod api.Module, pCtx, pApp, nArg, pArg uint32) {
|
||||
args := getFuncArgs()
|
||||
defer putFuncArgs(args)
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
fn := util.GetHandle(db.ctx, pApp).(ScalarFunction)
|
||||
callbackArgs(db, args[:nArg], pArg)
|
||||
fn(Context{db, pCtx}, args[:nArg]...)
|
||||
}
|
||||
|
||||
func stepCallback(ctx context.Context, mod api.Module, pCtx, pAgg, pApp, nArg, pArg uint32) {
|
||||
args := getFuncArgs()
|
||||
defer putFuncArgs(args)
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
callbackArgs(db, args[:nArg], pArg)
|
||||
fn, _ := callbackAggregate(db, pAgg, pApp)
|
||||
fn.Step(Context{db, pCtx}, args[:nArg]...)
|
||||
}
|
||||
|
||||
func finalCallback(ctx context.Context, mod api.Module, pCtx, pAgg, pApp uint32) {
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
fn, handle := callbackAggregate(db, pAgg, pApp)
|
||||
fn.Value(Context{db, pCtx})
|
||||
util.DelHandle(ctx, handle)
|
||||
}
|
||||
|
||||
func valueCallback(ctx context.Context, mod api.Module, pCtx, pAgg uint32) {
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
fn := util.GetHandle(db.ctx, pAgg).(AggregateFunction)
|
||||
fn.Value(Context{db, pCtx})
|
||||
}
|
||||
|
||||
func inverseCallback(ctx context.Context, mod api.Module, pCtx, pAgg, nArg, pArg uint32) {
|
||||
args := getFuncArgs()
|
||||
defer putFuncArgs(args)
|
||||
db := ctx.Value(connKey{}).(*Conn)
|
||||
callbackArgs(db, args[:nArg], pArg)
|
||||
fn := util.GetHandle(db.ctx, pAgg).(WindowFunction)
|
||||
fn.Inverse(Context{db, pCtx}, args[:nArg]...)
|
||||
}
|
||||
|
||||
func callbackAggregate(db *Conn, pAgg, pApp uint32) (AggregateFunction, uint32) {
|
||||
if pApp == 0 {
|
||||
handle := util.ReadUint32(db.mod, pAgg)
|
||||
return util.GetHandle(db.ctx, handle).(AggregateFunction), handle
|
||||
}
|
||||
|
||||
// We need to create the aggregate.
|
||||
fn := util.GetHandle(db.ctx, pApp).(func() AggregateFunction)()
|
||||
handle := util.AddHandle(db.ctx, fn)
|
||||
if pAgg != 0 {
|
||||
util.WriteUint32(db.mod, pAgg, handle)
|
||||
}
|
||||
return fn, handle
|
||||
}
|
||||
|
||||
func callbackArgs(db *Conn, arg []Value, pArg uint32) {
|
||||
for i := range arg {
|
||||
arg[i] = Value{
|
||||
c: db,
|
||||
handle: util.ReadUint32(db.mod, pArg+ptrlen*uint32(i)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var funcArgsPool sync.Pool
|
||||
|
||||
func putFuncArgs(p *[_MAX_FUNCTION_ARG]Value) {
|
||||
funcArgsPool.Put(p)
|
||||
}
|
||||
|
||||
func getFuncArgs() *[_MAX_FUNCTION_ARG]Value {
|
||||
if p := funcArgsPool.Get(); p == nil {
|
||||
return new([_MAX_FUNCTION_ARG]Value)
|
||||
} else {
|
||||
return p.(*[_MAX_FUNCTION_ARG]Value)
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
go 1.21
|
||||
|
||||
use (
|
||||
.
|
||||
./gormlite
|
||||
)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
//go:build unix
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func mmappedAllocator(cap, max uint64) experimental.LinearMemory {
|
||||
// Round up to the page size.
|
||||
rnd := uint64(unix.Getpagesize() - 1)
|
||||
max = (max + rnd) &^ rnd
|
||||
cap = (cap + rnd) &^ rnd
|
||||
|
||||
if max > math.MaxInt {
|
||||
// This ensures int(max) overflows to a negative value,
|
||||
// and unix.Mmap returns EINVAL.
|
||||
max = math.MaxUint64
|
||||
}
|
||||
// Reserve max bytes of address space, to ensure we won't need to move it.
|
||||
// A protected, private, anonymous mapping should not commit memory.
|
||||
b, err := unix.Mmap(-1, 0, int(max), unix.PROT_NONE, unix.MAP_PRIVATE|unix.MAP_ANON)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Commit the initial cap bytes of memory.
|
||||
err = unix.Mprotect(b[:cap], unix.PROT_READ|unix.PROT_WRITE)
|
||||
if err != nil {
|
||||
unix.Munmap(b)
|
||||
panic(err)
|
||||
}
|
||||
return &mmappedMemory{buf: b[:cap]}
|
||||
}
|
||||
|
||||
// The slice covers the entire mmapped memory:
|
||||
// - len(buf) is the already committed memory,
|
||||
// - cap(buf) is the reserved address space.
|
||||
type mmappedMemory struct {
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (m *mmappedMemory) Reallocate(size uint64) []byte {
|
||||
if com := uint64(len(m.buf)); com < size {
|
||||
// Round up to the page size.
|
||||
rnd := uint64(unix.Getpagesize() - 1)
|
||||
new := (size + rnd) &^ rnd
|
||||
|
||||
// Commit additional memory up to new bytes.
|
||||
err := unix.Mprotect(m.buf[com:new], unix.PROT_READ|unix.PROT_WRITE)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Update committed memory.
|
||||
m.buf = m.buf[:new]
|
||||
}
|
||||
// Limit returned capacity because bytes beyond
|
||||
// len(m.buf) have not yet been committed.
|
||||
return m.buf[:size:len(m.buf)]
|
||||
}
|
||||
|
||||
func (m *mmappedMemory) Free() {
|
||||
err := unix.Munmap(m.buf[:cap(m.buf)])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
m.buf = nil
|
||||
}
|
||||
+1
@@ -72,6 +72,7 @@ const (
|
||||
IOERR_ROLLBACK_ATOMIC = IOERR | (31 << 8)
|
||||
IOERR_DATA = IOERR | (32 << 8)
|
||||
IOERR_CORRUPTFS = IOERR | (33 << 8)
|
||||
IOERR_IN_PAGE = IOERR | (34 << 8)
|
||||
LOCKED_SHAREDCACHE = LOCKED | (1 << 8)
|
||||
LOCKED_VTAB = LOCKED | (2 << 8)
|
||||
BUSY_RECOVERY = BUSY | (1 << 8)
|
||||
|
||||
+3
-12
@@ -1,7 +1,6 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
@@ -15,14 +14,14 @@ const (
|
||||
OOMErr = ErrorString("sqlite3: out of memory")
|
||||
RangeErr = ErrorString("sqlite3: index out of range")
|
||||
NoNulErr = ErrorString("sqlite3: missing NUL terminator")
|
||||
NoGlobalErr = ErrorString("sqlite3: could not find global: ")
|
||||
NoFuncErr = ErrorString("sqlite3: could not find function: ")
|
||||
BinaryErr = ErrorString("sqlite3: no SQLite binary embed/set/loaded")
|
||||
NoBinaryErr = ErrorString("sqlite3: no SQLite binary embed/set/loaded")
|
||||
BadBinaryErr = ErrorString("sqlite3: invalid SQLite binary embed/set/loaded")
|
||||
TimeErr = ErrorString("sqlite3: invalid time value")
|
||||
WhenceErr = ErrorString("sqlite3: invalid whence")
|
||||
OffsetErr = ErrorString("sqlite3: invalid offset")
|
||||
TailErr = ErrorString("sqlite3: multiple statements")
|
||||
IsolationErr = ErrorString("sqlite3: unsupported isolation level")
|
||||
ValueErr = ErrorString("sqlite3: unsupported value")
|
||||
NoVFSErr = ErrorString("sqlite3: no such vfs: ")
|
||||
)
|
||||
|
||||
@@ -34,14 +33,6 @@ func AssertErr() ErrorString {
|
||||
return ErrorString(msg)
|
||||
}
|
||||
|
||||
func Finalizer[T any](skip int) func(*T) {
|
||||
msg := fmt.Sprintf("sqlite3: %T not closed", new(T))
|
||||
if _, file, line, ok := runtime.Caller(skip + 1); ok && skip >= 0 {
|
||||
msg += " (" + file + ":" + strconv.Itoa(line) + ")"
|
||||
}
|
||||
return func(*T) { panic(ErrorString(msg)) }
|
||||
}
|
||||
|
||||
func ErrorCodeString(rc uint32) string {
|
||||
switch rc {
|
||||
case ABORT_ROLLBACK:
|
||||
|
||||
+91
@@ -10,6 +10,84 @@ import (
|
||||
type i32 interface{ ~int32 | ~uint32 }
|
||||
type i64 interface{ ~int64 | ~uint64 }
|
||||
|
||||
type funcVI[T0 i32] func(context.Context, api.Module, T0)
|
||||
|
||||
func (fn funcVI[T0]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
fn(ctx, mod, T0(stack[0]))
|
||||
}
|
||||
|
||||
func ExportFuncVI[T0 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0)) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(funcVI[T0](fn),
|
||||
[]api.ValueType{api.ValueTypeI32}, nil).
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcVII[T0, T1 i32] func(context.Context, api.Module, T0, T1)
|
||||
|
||||
func (fn funcVII[T0, T1]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
fn(ctx, mod, T0(stack[0]), T1(stack[1]))
|
||||
}
|
||||
|
||||
func ExportFuncVII[T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1)) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(funcVII[T0, T1](fn),
|
||||
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, nil).
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcVIII[T0, T1, T2 i32] func(context.Context, api.Module, T0, T1, T2)
|
||||
|
||||
func (fn funcVIII[T0, T1, T2]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]))
|
||||
}
|
||||
|
||||
func ExportFuncVIII[T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2)) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(funcVIII[T0, T1, T2](fn),
|
||||
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil).
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcVIIII[T0, T1, T2, T3 i32] func(context.Context, api.Module, T0, T1, T2, T3)
|
||||
|
||||
func (fn funcVIIII[T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]))
|
||||
}
|
||||
|
||||
func ExportFuncVIIII[T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3)) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(funcVIIII[T0, T1, T2, T3](fn),
|
||||
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil).
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcVIIIII[T0, T1, T2, T3, T4 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4)
|
||||
|
||||
func (fn funcVIIIII[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))
|
||||
}
|
||||
|
||||
func ExportFuncVIIIII[T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4)) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(funcVIIIII[T0, T1, T2, T3, T4](fn),
|
||||
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, nil).
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcVIIIIJ[T0, T1, T2, T3 i32, T4 i64] func(context.Context, api.Module, T0, T1, T2, T3, T4)
|
||||
|
||||
func (fn funcVIIIIJ[T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))
|
||||
}
|
||||
|
||||
func ExportFuncVIIIIJ[T0, T1, T2, T3 i32, T4 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4)) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(funcVIIIIJ[T0, T1, T2, T3, T4](fn),
|
||||
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, nil).
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcII[TR, T0 i32] func(context.Context, api.Module, T0) TR
|
||||
|
||||
func (fn funcII[TR, T0]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
@@ -75,6 +153,19 @@ func ExportFuncIIIIII[TR, T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder,
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32] func(context.Context, api.Module, T0, T1, T2, T3, T4, T5) TR
|
||||
|
||||
func (fn funcIIIIIII[TR, T0, T1, T2, T3, T4, T5]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]), T5(stack[5])))
|
||||
}
|
||||
|
||||
func ExportFuncIIIIIII[TR, T0, T1, T2, T3, T4, T5 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4, T5) TR) {
|
||||
mod.NewFunctionBuilder().
|
||||
WithGoModuleFunction(funcIIIIIII[TR, T0, T1, T2, T3, T4, T5](fn),
|
||||
[]api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}).
|
||||
Export(name)
|
||||
}
|
||||
|
||||
type funcIIIIJ[TR, T0, T1, T2 i32, T3 i64] func(context.Context, api.Module, T0, T1, T2, T3) TR
|
||||
|
||||
func (fn funcIIIIJ[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
type handleState struct {
|
||||
handles []any
|
||||
holes int
|
||||
}
|
||||
|
||||
func (s *handleState) CloseNotify(ctx context.Context, exitCode uint32) {
|
||||
for _, h := range s.handles {
|
||||
if c, ok := h.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
s.handles = nil
|
||||
s.holes = 0
|
||||
}
|
||||
|
||||
func GetHandle(ctx context.Context, id uint32) any {
|
||||
if id == 0 {
|
||||
return nil
|
||||
}
|
||||
s := ctx.Value(moduleKey{}).(*moduleState)
|
||||
return s.handles[^id]
|
||||
}
|
||||
|
||||
func DelHandle(ctx context.Context, id uint32) error {
|
||||
if id == 0 {
|
||||
return nil
|
||||
}
|
||||
s := ctx.Value(moduleKey{}).(*moduleState)
|
||||
a := s.handles[^id]
|
||||
s.handles[^id] = nil
|
||||
s.holes++
|
||||
if c, ok := a.(io.Closer); ok {
|
||||
return c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddHandle(ctx context.Context, a any) (id uint32) {
|
||||
if a == nil {
|
||||
panic(NilErr)
|
||||
}
|
||||
s := ctx.Value(moduleKey{}).(*moduleState)
|
||||
|
||||
// Find an empty slot.
|
||||
if s.holes > cap(s.handles)-len(s.handles) {
|
||||
for id, h := range s.handles {
|
||||
if h == nil {
|
||||
s.holes--
|
||||
s.handles[id] = a
|
||||
return ^uint32(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new slot.
|
||||
s.handles = append(s.handles, a)
|
||||
return -uint32(len(s.handles))
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type JSON struct{ Value any }
|
||||
|
||||
func (j JSON) Scan(value any) error {
|
||||
var buf []byte
|
||||
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
buf = v
|
||||
case string:
|
||||
buf = unsafe.Slice(unsafe.StringData(v), len(v))
|
||||
case int64:
|
||||
buf = strconv.AppendInt(nil, v, 10)
|
||||
case float64:
|
||||
buf = strconv.AppendFloat(nil, v, 'g', -1, 64)
|
||||
case time.Time:
|
||||
buf = append(buf, '"')
|
||||
buf = v.AppendFormat(buf, time.RFC3339Nano)
|
||||
buf = append(buf, '"')
|
||||
case nil:
|
||||
buf = append(buf, "null"...)
|
||||
default:
|
||||
panic(AssertErr())
|
||||
}
|
||||
|
||||
return json.Unmarshal(buf, j.Value)
|
||||
}
|
||||
+24
@@ -14,6 +14,9 @@ func View(mod api.Module, ptr uint32, size uint64) []byte {
|
||||
if size > math.MaxUint32 {
|
||||
panic(RangeErr)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
buf, ok := mod.Memory().Read(ptr, uint32(size))
|
||||
if !ok {
|
||||
panic(RangeErr)
|
||||
@@ -21,6 +24,17 @@ func View(mod api.Module, ptr uint32, size uint64) []byte {
|
||||
return buf
|
||||
}
|
||||
|
||||
func ReadUint8(mod api.Module, ptr uint32) uint8 {
|
||||
if ptr == 0 {
|
||||
panic(NilErr)
|
||||
}
|
||||
v, ok := mod.Memory().ReadByte(ptr)
|
||||
if !ok {
|
||||
panic(RangeErr)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func ReadUint32(mod api.Module, ptr uint32) uint32 {
|
||||
if ptr == 0 {
|
||||
panic(NilErr)
|
||||
@@ -32,6 +46,16 @@ func ReadUint32(mod api.Module, ptr uint32) uint32 {
|
||||
return v
|
||||
}
|
||||
|
||||
func WriteUint8(mod api.Module, ptr uint32, v uint8) {
|
||||
if ptr == 0 {
|
||||
panic(NilErr)
|
||||
}
|
||||
ok := mod.Memory().WriteByte(ptr, v)
|
||||
if !ok {
|
||||
panic(RangeErr)
|
||||
}
|
||||
}
|
||||
|
||||
func WriteUint32(mod api.Module, ptr uint32, v uint32) {
|
||||
if ptr == 0 {
|
||||
panic(NilErr)
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
//go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_flock || sqlite3_noshm || sqlite3_nosys)
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func withMmappedAllocator(ctx context.Context) context.Context {
|
||||
return experimental.WithMemoryAllocator(ctx,
|
||||
experimental.MemoryAllocatorFunc(mmappedAllocator))
|
||||
}
|
||||
|
||||
type mmapState struct {
|
||||
regions []*MappedRegion
|
||||
}
|
||||
|
||||
func (s *mmapState) new(ctx context.Context, mod api.Module, size int32) *MappedRegion {
|
||||
// Find unused region.
|
||||
for _, r := range s.regions {
|
||||
if !r.used && r.size == size {
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate page aligned memmory.
|
||||
alloc := mod.ExportedFunction("aligned_alloc")
|
||||
stack := [2]uint64{
|
||||
uint64(unix.Getpagesize()),
|
||||
uint64(size),
|
||||
}
|
||||
if err := alloc.CallWithStack(ctx, stack[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if stack[0] == 0 {
|
||||
panic(OOMErr)
|
||||
}
|
||||
|
||||
// Save the newly allocated region.
|
||||
ptr := uint32(stack[0])
|
||||
buf := View(mod, ptr, uint64(size))
|
||||
addr := uintptr(unsafe.Pointer(&buf[0]))
|
||||
s.regions = append(s.regions, &MappedRegion{
|
||||
Ptr: ptr,
|
||||
addr: addr,
|
||||
size: size,
|
||||
})
|
||||
return s.regions[len(s.regions)-1]
|
||||
}
|
||||
|
||||
type MappedRegion struct {
|
||||
addr uintptr
|
||||
Ptr uint32
|
||||
size int32
|
||||
used bool
|
||||
}
|
||||
|
||||
func MapRegion(ctx context.Context, mod api.Module, f *os.File, offset int64, size int32, prot int) (*MappedRegion, error) {
|
||||
s := ctx.Value(moduleKey{}).(*moduleState)
|
||||
r := s.new(ctx, mod, size)
|
||||
err := r.mmap(f, offset, prot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *MappedRegion) Unmap() error {
|
||||
// We can't munmap the region, otherwise it could be remaped.
|
||||
// Instead, convert it to a protected, private, anonymous mapping.
|
||||
// If successful, it can be reused for a subsequent mmap.
|
||||
_, err := mmap(r.addr, uintptr(r.size),
|
||||
unix.PROT_NONE, unix.MAP_PRIVATE|unix.MAP_ANON|unix.MAP_FIXED,
|
||||
-1, 0)
|
||||
r.used = err != nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *MappedRegion) mmap(f *os.File, offset int64, prot int) error {
|
||||
_, err := mmap(r.addr, uintptr(r.size),
|
||||
prot, unix.MAP_SHARED|unix.MAP_FIXED,
|
||||
int(f.Fd()), offset)
|
||||
r.used = err == nil
|
||||
return err
|
||||
}
|
||||
|
||||
// We need the low level mmap for MAP_FIXED to work.
|
||||
// Bind the syscall version hoping that it is more stable.
|
||||
|
||||
//go:linkname mmap syscall.mmap
|
||||
func mmap(addr, length uintptr, prot, flag, fd int, pos int64) (*byte, error)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
//go:build !(darwin || linux) || !(amd64 || arm64 || riscv64) || sqlite3_flock || sqlite3_noshm || sqlite3_nosys
|
||||
|
||||
package util
|
||||
|
||||
import "context"
|
||||
|
||||
type mmapState struct{}
|
||||
|
||||
func withMmappedAllocator(ctx context.Context) context.Context {
|
||||
return ctx
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
)
|
||||
|
||||
type moduleKey struct{}
|
||||
type moduleState struct {
|
||||
mmapState
|
||||
handleState
|
||||
}
|
||||
|
||||
func NewContext(ctx context.Context) context.Context {
|
||||
state := new(moduleState)
|
||||
ctx = withMmappedAllocator(ctx)
|
||||
ctx = experimental.WithCloseNotifier(ctx, state)
|
||||
ctx = context.WithValue(ctx, moduleKey{}, state)
|
||||
return ctx
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package util
|
||||
|
||||
type Pointer[T any] struct{ Value T }
|
||||
|
||||
func (p Pointer[T]) unwrap() any { return p.Value }
|
||||
|
||||
type PointerUnwrap interface{ unwrap() any }
|
||||
|
||||
func UnwrapPointer(p PointerUnwrap) any {
|
||||
return p.unwrap()
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package util
|
||||
|
||||
import "reflect"
|
||||
|
||||
func ReflectType(v reflect.Value) reflect.Type {
|
||||
if v.Kind() != reflect.Invalid {
|
||||
return v.Type()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package sqlite3
|
||||
|
||||
import "github.com/ncruces/go-sqlite3/internal/util"
|
||||
|
||||
// JSON returns a value that can be used as an argument to
|
||||
// [database/sql.DB.Exec], [database/sql.Row.Scan] and similar methods to
|
||||
// store value as JSON, or decode JSON into value.
|
||||
// JSON should NOT be used with [BindJSON] or [ResultJSON].
|
||||
func JSON(value any) any {
|
||||
return util.JSON{Value: value}
|
||||
}
|
||||
-352
@@ -1,352 +0,0 @@
|
||||
// Package sqlite3 wraps the C SQLite API.
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// Configure SQLite WASM.
|
||||
//
|
||||
// Importing package embed initializes these
|
||||
// with an appropriate build of SQLite:
|
||||
//
|
||||
// import _ "github.com/ncruces/go-sqlite3/embed"
|
||||
var (
|
||||
Binary []byte // WASM binary to load.
|
||||
Path string // Path to load the binary from.
|
||||
)
|
||||
|
||||
var sqlite3 struct {
|
||||
runtime wazero.Runtime
|
||||
compiled wazero.CompiledModule
|
||||
err error
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func instantiateModule() (*module, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
sqlite3.once.Do(compileModule)
|
||||
if sqlite3.err != nil {
|
||||
return nil, sqlite3.err
|
||||
}
|
||||
|
||||
cfg := wazero.NewModuleConfig()
|
||||
|
||||
mod, err := sqlite3.runtime.InstantiateModule(ctx, sqlite3.compiled, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newModule(mod)
|
||||
}
|
||||
|
||||
func compileModule() {
|
||||
ctx := context.Background()
|
||||
sqlite3.runtime = wazero.NewRuntime(ctx)
|
||||
|
||||
env := vfs.ExportHostFunctions(sqlite3.runtime.NewHostModuleBuilder("env"))
|
||||
_, sqlite3.err = env.Instantiate(ctx)
|
||||
if sqlite3.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bin := Binary
|
||||
if bin == nil && Path != "" {
|
||||
bin, sqlite3.err = os.ReadFile(Path)
|
||||
if sqlite3.err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if bin == nil {
|
||||
sqlite3.err = util.BinaryErr
|
||||
return
|
||||
}
|
||||
|
||||
sqlite3.compiled, sqlite3.err = sqlite3.runtime.CompileModule(ctx, bin)
|
||||
}
|
||||
|
||||
type module struct {
|
||||
ctx context.Context
|
||||
mod api.Module
|
||||
vfs io.Closer
|
||||
api sqliteAPI
|
||||
arg [8]uint64
|
||||
}
|
||||
|
||||
func newModule(mod api.Module) (m *module, err error) {
|
||||
m = new(module)
|
||||
m.mod = mod
|
||||
m.ctx, m.vfs = vfs.NewContext(context.Background())
|
||||
|
||||
getFun := func(name string) api.Function {
|
||||
f := mod.ExportedFunction(name)
|
||||
if f == nil {
|
||||
err = util.NoFuncErr + util.ErrorString(name)
|
||||
return nil
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
getVal := func(name string) uint32 {
|
||||
g := mod.ExportedGlobal(name)
|
||||
if g == nil {
|
||||
err = util.NoGlobalErr + util.ErrorString(name)
|
||||
return 0
|
||||
}
|
||||
return util.ReadUint32(mod, uint32(g.Get()))
|
||||
}
|
||||
|
||||
m.api = sqliteAPI{
|
||||
free: getFun("free"),
|
||||
malloc: getFun("malloc"),
|
||||
destructor: getVal("malloc_destructor"),
|
||||
errcode: getFun("sqlite3_errcode"),
|
||||
errstr: getFun("sqlite3_errstr"),
|
||||
errmsg: getFun("sqlite3_errmsg"),
|
||||
erroff: getFun("sqlite3_error_offset"),
|
||||
open: getFun("sqlite3_open_v2"),
|
||||
close: getFun("sqlite3_close"),
|
||||
closeZombie: getFun("sqlite3_close_v2"),
|
||||
prepare: getFun("sqlite3_prepare_v3"),
|
||||
finalize: getFun("sqlite3_finalize"),
|
||||
reset: getFun("sqlite3_reset"),
|
||||
step: getFun("sqlite3_step"),
|
||||
exec: getFun("sqlite3_exec"),
|
||||
clearBindings: getFun("sqlite3_clear_bindings"),
|
||||
bindCount: getFun("sqlite3_bind_parameter_count"),
|
||||
bindIndex: getFun("sqlite3_bind_parameter_index"),
|
||||
bindName: getFun("sqlite3_bind_parameter_name"),
|
||||
bindNull: getFun("sqlite3_bind_null"),
|
||||
bindInteger: getFun("sqlite3_bind_int64"),
|
||||
bindFloat: getFun("sqlite3_bind_double"),
|
||||
bindText: getFun("sqlite3_bind_text64"),
|
||||
bindBlob: getFun("sqlite3_bind_blob64"),
|
||||
bindZeroBlob: getFun("sqlite3_bind_zeroblob64"),
|
||||
columnCount: getFun("sqlite3_column_count"),
|
||||
columnName: getFun("sqlite3_column_name"),
|
||||
columnType: getFun("sqlite3_column_type"),
|
||||
columnInteger: getFun("sqlite3_column_int64"),
|
||||
columnFloat: getFun("sqlite3_column_double"),
|
||||
columnText: getFun("sqlite3_column_text"),
|
||||
columnBlob: getFun("sqlite3_column_blob"),
|
||||
columnBytes: getFun("sqlite3_column_bytes"),
|
||||
blobOpen: getFun("sqlite3_blob_open"),
|
||||
blobClose: getFun("sqlite3_blob_close"),
|
||||
blobReopen: getFun("sqlite3_blob_reopen"),
|
||||
blobBytes: getFun("sqlite3_blob_bytes"),
|
||||
blobRead: getFun("sqlite3_blob_read"),
|
||||
blobWrite: getFun("sqlite3_blob_write"),
|
||||
backupInit: getFun("sqlite3_backup_init"),
|
||||
backupStep: getFun("sqlite3_backup_step"),
|
||||
backupFinish: getFun("sqlite3_backup_finish"),
|
||||
backupRemaining: getFun("sqlite3_backup_remaining"),
|
||||
backupPageCount: getFun("sqlite3_backup_pagecount"),
|
||||
changes: getFun("sqlite3_changes64"),
|
||||
lastRowid: getFun("sqlite3_last_insert_rowid"),
|
||||
autocommit: getFun("sqlite3_get_autocommit"),
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *module) close() error {
|
||||
err := m.mod.Close(m.ctx)
|
||||
m.vfs.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *module) error(rc uint64, handle uint32, sql ...string) error {
|
||||
if rc == _OK {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := Error{code: rc}
|
||||
|
||||
if err.Code() == NOMEM || err.ExtendedCode() == IOERR_NOMEM {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
|
||||
if r := m.call(m.api.errstr, rc); r != 0 {
|
||||
err.str = util.ReadString(m.mod, uint32(r), _MAX_STRING)
|
||||
}
|
||||
|
||||
if r := m.call(m.api.errmsg, uint64(handle)); r != 0 {
|
||||
err.msg = util.ReadString(m.mod, uint32(r), _MAX_STRING)
|
||||
}
|
||||
|
||||
if sql != nil {
|
||||
if r := m.call(m.api.erroff, uint64(handle)); r != math.MaxUint32 {
|
||||
err.sql = sql[0][r:]
|
||||
}
|
||||
}
|
||||
|
||||
switch err.msg {
|
||||
case err.str, "not an error":
|
||||
err.msg = ""
|
||||
}
|
||||
return &err
|
||||
}
|
||||
|
||||
func (m *module) call(fn api.Function, params ...uint64) uint64 {
|
||||
copy(m.arg[:], params)
|
||||
err := fn.CallWithStack(m.ctx, m.arg[:])
|
||||
if err != nil {
|
||||
// The module closed or panicked; release resources.
|
||||
m.vfs.Close()
|
||||
panic(err)
|
||||
}
|
||||
return m.arg[0]
|
||||
}
|
||||
|
||||
func (m *module) free(ptr uint32) {
|
||||
if ptr == 0 {
|
||||
return
|
||||
}
|
||||
m.call(m.api.free, uint64(ptr))
|
||||
}
|
||||
|
||||
func (m *module) new(size uint64) uint32 {
|
||||
if size > _MAX_ALLOCATION_SIZE {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
ptr := uint32(m.call(m.api.malloc, size))
|
||||
if ptr == 0 && size != 0 {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (m *module) newBytes(b []byte) uint32 {
|
||||
if b == nil {
|
||||
return 0
|
||||
}
|
||||
ptr := m.new(uint64(len(b)))
|
||||
util.WriteBytes(m.mod, ptr, b)
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (m *module) newString(s string) uint32 {
|
||||
ptr := m.new(uint64(len(s) + 1))
|
||||
util.WriteString(m.mod, ptr, s)
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (m *module) newArena(size uint64) arena {
|
||||
return arena{
|
||||
m: m,
|
||||
base: m.new(size),
|
||||
size: uint32(size),
|
||||
}
|
||||
}
|
||||
|
||||
type arena struct {
|
||||
m *module
|
||||
ptrs []uint32
|
||||
base uint32
|
||||
next uint32
|
||||
size uint32
|
||||
}
|
||||
|
||||
func (a *arena) free() {
|
||||
if a.m == nil {
|
||||
return
|
||||
}
|
||||
a.reset()
|
||||
a.m.free(a.base)
|
||||
a.m = nil
|
||||
}
|
||||
|
||||
func (a *arena) reset() {
|
||||
for _, ptr := range a.ptrs {
|
||||
a.m.free(ptr)
|
||||
}
|
||||
a.ptrs = nil
|
||||
a.next = 0
|
||||
}
|
||||
|
||||
func (a *arena) new(size uint64) uint32 {
|
||||
if size <= uint64(a.size-a.next) {
|
||||
ptr := a.base + a.next
|
||||
a.next += uint32(size)
|
||||
return ptr
|
||||
}
|
||||
ptr := a.m.new(size)
|
||||
a.ptrs = append(a.ptrs, ptr)
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (a *arena) bytes(b []byte) uint32 {
|
||||
if b == nil {
|
||||
return 0
|
||||
}
|
||||
ptr := a.new(uint64(len(b)))
|
||||
util.WriteBytes(a.m.mod, ptr, b)
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (a *arena) string(s string) uint32 {
|
||||
ptr := a.new(uint64(len(s) + 1))
|
||||
util.WriteString(a.m.mod, ptr, s)
|
||||
return ptr
|
||||
}
|
||||
|
||||
type sqliteAPI struct {
|
||||
free api.Function
|
||||
malloc api.Function
|
||||
errcode api.Function
|
||||
errstr api.Function
|
||||
errmsg api.Function
|
||||
erroff api.Function
|
||||
open api.Function
|
||||
close api.Function
|
||||
closeZombie api.Function
|
||||
prepare api.Function
|
||||
finalize api.Function
|
||||
reset api.Function
|
||||
step api.Function
|
||||
exec api.Function
|
||||
clearBindings api.Function
|
||||
bindNull api.Function
|
||||
bindCount api.Function
|
||||
bindIndex api.Function
|
||||
bindName api.Function
|
||||
bindInteger api.Function
|
||||
bindFloat api.Function
|
||||
bindText api.Function
|
||||
bindBlob api.Function
|
||||
bindZeroBlob api.Function
|
||||
columnCount api.Function
|
||||
columnName api.Function
|
||||
columnType api.Function
|
||||
columnInteger api.Function
|
||||
columnFloat api.Function
|
||||
columnText api.Function
|
||||
columnBlob api.Function
|
||||
columnBytes api.Function
|
||||
blobOpen api.Function
|
||||
blobClose api.Function
|
||||
blobReopen api.Function
|
||||
blobBytes api.Function
|
||||
blobRead api.Function
|
||||
blobWrite api.Function
|
||||
backupInit api.Function
|
||||
backupStep api.Function
|
||||
backupFinish api.Function
|
||||
backupRemaining api.Function
|
||||
backupPageCount api.Function
|
||||
changes api.Function
|
||||
lastRowid api.Function
|
||||
autocommit api.Function
|
||||
destructor uint32
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package sqlite3
|
||||
|
||||
import "github.com/ncruces/go-sqlite3/internal/util"
|
||||
|
||||
// Pointer returns a pointer to a value that can be used as an argument to
|
||||
// [database/sql.DB.Exec] and similar methods.
|
||||
// Pointer should NOT be used with [BindPointer] or [ResultPointer].
|
||||
//
|
||||
// https://sqlite.org/bindptr.html
|
||||
func Pointer[T any](value T) any {
|
||||
return util.Pointer[T]{Value: value}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// Quote escapes and quotes a value
|
||||
// making it safe to embed in SQL text.
|
||||
func Quote(value any) string {
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
return "NULL"
|
||||
case bool:
|
||||
if v {
|
||||
return "1"
|
||||
} else {
|
||||
return "0"
|
||||
}
|
||||
|
||||
case int:
|
||||
return strconv.Itoa(v)
|
||||
case int64:
|
||||
return strconv.FormatInt(v, 10)
|
||||
case float64:
|
||||
switch {
|
||||
case math.IsNaN(v):
|
||||
return "NULL"
|
||||
case math.IsInf(v, 1):
|
||||
return "9.0e999"
|
||||
case math.IsInf(v, -1):
|
||||
return "-9.0e999"
|
||||
}
|
||||
return strconv.FormatFloat(v, 'g', -1, 64)
|
||||
case time.Time:
|
||||
return "'" + v.Format(time.RFC3339Nano) + "'"
|
||||
|
||||
case string:
|
||||
if strings.IndexByte(v, 0) >= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
buf := make([]byte, 2+len(v)+strings.Count(v, "'"))
|
||||
buf[0] = '\''
|
||||
i := 1
|
||||
for _, b := range []byte(v) {
|
||||
if b == '\'' {
|
||||
buf[i] = b
|
||||
i += 1
|
||||
}
|
||||
buf[i] = b
|
||||
i += 1
|
||||
}
|
||||
buf[i] = '\''
|
||||
return unsafe.String(&buf[0], len(buf))
|
||||
|
||||
case []byte:
|
||||
buf := make([]byte, 3+2*len(v))
|
||||
buf[0] = 'x'
|
||||
buf[1] = '\''
|
||||
i := 2
|
||||
for _, b := range v {
|
||||
const hex = "0123456789ABCDEF"
|
||||
buf[i+0] = hex[b/16]
|
||||
buf[i+1] = hex[b%16]
|
||||
i += 2
|
||||
}
|
||||
buf[i] = '\''
|
||||
return unsafe.String(&buf[0], len(buf))
|
||||
|
||||
case ZeroBlob:
|
||||
if v > ZeroBlob(1e9-3)/2 {
|
||||
break
|
||||
}
|
||||
|
||||
buf := bytes.Repeat([]byte("0"), int(3+2*int64(v)))
|
||||
buf[0] = 'x'
|
||||
buf[1] = '\''
|
||||
buf[len(buf)-1] = '\''
|
||||
return unsafe.String(&buf[0], len(buf))
|
||||
}
|
||||
|
||||
panic(util.ValueErr)
|
||||
}
|
||||
|
||||
// QuoteIdentifier escapes and quotes an identifier
|
||||
// making it safe to embed in SQL text.
|
||||
func QuoteIdentifier(id string) string {
|
||||
if strings.IndexByte(id, 0) >= 0 {
|
||||
panic(util.ValueErr)
|
||||
}
|
||||
|
||||
buf := make([]byte, 2+len(id)+strings.Count(id, `"`))
|
||||
buf[0] = '"'
|
||||
i := 1
|
||||
for _, b := range []byte(id) {
|
||||
if b == '"' {
|
||||
buf[i] = b
|
||||
i += 1
|
||||
}
|
||||
buf[i] = b
|
||||
i += 1
|
||||
}
|
||||
buf[i] = '"'
|
||||
return unsafe.String(&buf[0], len(buf))
|
||||
}
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
// Package sqlite3 wraps the C SQLite API.
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"math/bits"
|
||||
"os"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// Configure SQLite Wasm.
|
||||
//
|
||||
// Importing package embed initializes [Binary]
|
||||
// with an appropriate build of SQLite:
|
||||
//
|
||||
// import _ "github.com/ncruces/go-sqlite3/embed"
|
||||
var (
|
||||
Binary []byte // Wasm binary to load.
|
||||
Path string // Path to load the binary from.
|
||||
|
||||
RuntimeConfig wazero.RuntimeConfig
|
||||
)
|
||||
|
||||
var instance struct {
|
||||
runtime wazero.Runtime
|
||||
compiled wazero.CompiledModule
|
||||
err error
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func compileSQLite() {
|
||||
if RuntimeConfig == nil {
|
||||
RuntimeConfig = wazero.NewRuntimeConfig()
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
instance.runtime = wazero.NewRuntimeWithConfig(ctx, RuntimeConfig)
|
||||
|
||||
env := instance.runtime.NewHostModuleBuilder("env")
|
||||
env = vfs.ExportHostFunctions(env)
|
||||
env = exportCallbacks(env)
|
||||
_, instance.err = env.Instantiate(ctx)
|
||||
if instance.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bin := Binary
|
||||
if bin == nil && Path != "" {
|
||||
bin, instance.err = os.ReadFile(Path)
|
||||
if instance.err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if bin == nil {
|
||||
instance.err = util.NoBinaryErr
|
||||
return
|
||||
}
|
||||
|
||||
instance.compiled, instance.err = instance.runtime.CompileModule(ctx, bin)
|
||||
}
|
||||
|
||||
type sqlite struct {
|
||||
ctx context.Context
|
||||
mod api.Module
|
||||
funcs struct {
|
||||
fn [32]api.Function
|
||||
id [32]*byte
|
||||
mask uint32
|
||||
}
|
||||
stack [8]uint64
|
||||
freer uint32
|
||||
}
|
||||
|
||||
func instantiateSQLite() (sqlt *sqlite, err error) {
|
||||
instance.once.Do(compileSQLite)
|
||||
if instance.err != nil {
|
||||
return nil, instance.err
|
||||
}
|
||||
|
||||
sqlt = new(sqlite)
|
||||
sqlt.ctx = util.NewContext(context.Background())
|
||||
|
||||
sqlt.mod, err = instance.runtime.InstantiateModule(sqlt.ctx,
|
||||
instance.compiled, wazero.NewModuleConfig().WithName(""))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
global := sqlt.mod.ExportedGlobal("malloc_destructor")
|
||||
if global == nil {
|
||||
return nil, util.BadBinaryErr
|
||||
}
|
||||
|
||||
sqlt.freer = util.ReadUint32(sqlt.mod, uint32(global.Get()))
|
||||
if sqlt.freer == 0 {
|
||||
return nil, util.BadBinaryErr
|
||||
}
|
||||
return sqlt, nil
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) close() error {
|
||||
return sqlt.mod.Close(sqlt.ctx)
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) error(rc uint64, handle uint32, sql ...string) error {
|
||||
if rc == _OK {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := Error{code: rc}
|
||||
|
||||
if err.Code() == NOMEM || err.ExtendedCode() == IOERR_NOMEM {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
|
||||
if r := sqlt.call("sqlite3_errstr", rc); r != 0 {
|
||||
err.str = util.ReadString(sqlt.mod, uint32(r), _MAX_NAME)
|
||||
}
|
||||
|
||||
if handle != 0 {
|
||||
if r := sqlt.call("sqlite3_errmsg", uint64(handle)); r != 0 {
|
||||
err.msg = util.ReadString(sqlt.mod, uint32(r), _MAX_LENGTH)
|
||||
}
|
||||
|
||||
if sql != nil {
|
||||
if r := sqlt.call("sqlite3_error_offset", uint64(handle)); r != math.MaxUint32 {
|
||||
err.sql = sql[0][r:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch err.msg {
|
||||
case err.str, "not an error":
|
||||
err.msg = ""
|
||||
}
|
||||
return &err
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) getfn(name string) api.Function {
|
||||
c := &sqlt.funcs
|
||||
p := unsafe.StringData(name)
|
||||
for i := range c.id {
|
||||
if c.id[i] == p {
|
||||
c.id[i] = nil
|
||||
c.mask &^= uint32(1) << i
|
||||
return c.fn[i]
|
||||
}
|
||||
}
|
||||
return sqlt.mod.ExportedFunction(name)
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) putfn(name string, fn api.Function) {
|
||||
c := &sqlt.funcs
|
||||
p := unsafe.StringData(name)
|
||||
i := bits.TrailingZeros32(^c.mask)
|
||||
if i < 32 {
|
||||
c.id[i] = p
|
||||
c.fn[i] = fn
|
||||
c.mask |= uint32(1) << i
|
||||
} else {
|
||||
c.id[0] = p
|
||||
c.fn[0] = fn
|
||||
c.mask = uint32(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) call(name string, params ...uint64) uint64 {
|
||||
copy(sqlt.stack[:], params)
|
||||
fn := sqlt.getfn(name)
|
||||
err := fn.CallWithStack(sqlt.ctx, sqlt.stack[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sqlt.putfn(name, fn)
|
||||
return sqlt.stack[0]
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) free(ptr uint32) {
|
||||
if ptr == 0 {
|
||||
return
|
||||
}
|
||||
sqlt.call("free", uint64(ptr))
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) new(size uint64) uint32 {
|
||||
if size > _MAX_ALLOCATION_SIZE {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
ptr := uint32(sqlt.call("malloc", size))
|
||||
if ptr == 0 && size != 0 {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) newBytes(b []byte) uint32 {
|
||||
if (*[0]byte)(b) == nil {
|
||||
return 0
|
||||
}
|
||||
ptr := sqlt.new(uint64(len(b)))
|
||||
util.WriteBytes(sqlt.mod, ptr, b)
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) newString(s string) uint32 {
|
||||
ptr := sqlt.new(uint64(len(s) + 1))
|
||||
util.WriteString(sqlt.mod, ptr, s)
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (sqlt *sqlite) newArena(size uint64) arena {
|
||||
// Ensure the arena's size is a multiple of 8.
|
||||
size = (size + 7) &^ 7
|
||||
return arena{
|
||||
sqlt: sqlt,
|
||||
size: uint32(size),
|
||||
base: sqlt.new(size),
|
||||
}
|
||||
}
|
||||
|
||||
type arena struct {
|
||||
sqlt *sqlite
|
||||
ptrs []uint32
|
||||
base uint32
|
||||
next uint32
|
||||
size uint32
|
||||
}
|
||||
|
||||
func (a *arena) free() {
|
||||
if a.sqlt == nil {
|
||||
return
|
||||
}
|
||||
for _, ptr := range a.ptrs {
|
||||
a.sqlt.free(ptr)
|
||||
}
|
||||
a.sqlt.free(a.base)
|
||||
a.sqlt = nil
|
||||
}
|
||||
|
||||
func (a *arena) mark() (reset func()) {
|
||||
ptrs := len(a.ptrs)
|
||||
next := a.next
|
||||
return func() {
|
||||
for _, ptr := range a.ptrs[ptrs:] {
|
||||
a.sqlt.free(ptr)
|
||||
}
|
||||
a.ptrs = a.ptrs[:ptrs]
|
||||
a.next = next
|
||||
}
|
||||
}
|
||||
|
||||
func (a *arena) new(size uint64) uint32 {
|
||||
// Align the next address, to 4 or 8 bytes.
|
||||
if size&7 != 0 {
|
||||
a.next = (a.next + 3) &^ 3
|
||||
} else {
|
||||
a.next = (a.next + 7) &^ 7
|
||||
}
|
||||
if size <= uint64(a.size-a.next) {
|
||||
ptr := a.base + a.next
|
||||
a.next += uint32(size)
|
||||
return ptr
|
||||
}
|
||||
ptr := a.sqlt.new(size)
|
||||
a.ptrs = append(a.ptrs, ptr)
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (a *arena) bytes(b []byte) uint32 {
|
||||
if (*[0]byte)(b) == nil {
|
||||
return 0
|
||||
}
|
||||
ptr := a.new(uint64(len(b)))
|
||||
util.WriteBytes(a.sqlt.mod, ptr, b)
|
||||
return ptr
|
||||
}
|
||||
|
||||
func (a *arena) string(s string) uint32 {
|
||||
ptr := a.new(uint64(len(s) + 1))
|
||||
util.WriteString(a.sqlt.mod, ptr, s)
|
||||
return ptr
|
||||
}
|
||||
|
||||
func exportCallbacks(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
|
||||
util.ExportFuncIII(env, "go_busy_handler", busyCallback)
|
||||
util.ExportFuncII(env, "go_progress_handler", progressCallback)
|
||||
util.ExportFuncII(env, "go_commit_hook", commitCallback)
|
||||
util.ExportFuncVI(env, "go_rollback_hook", rollbackCallback)
|
||||
util.ExportFuncVIIIIJ(env, "go_update_hook", updateCallback)
|
||||
util.ExportFuncIIIII(env, "go_wal_hook", walCallback)
|
||||
util.ExportFuncIIIIII(env, "go_autovacuum_pages", autoVacuumCallback)
|
||||
util.ExportFuncIIIIIII(env, "go_authorizer", authorizerCallback)
|
||||
util.ExportFuncVIII(env, "go_log", logCallback)
|
||||
util.ExportFuncVI(env, "go_destroy", destroyCallback)
|
||||
util.ExportFuncVIIII(env, "go_func", funcCallback)
|
||||
util.ExportFuncVIIIII(env, "go_step", stepCallback)
|
||||
util.ExportFuncVIII(env, "go_final", finalCallback)
|
||||
util.ExportFuncVII(env, "go_value", valueCallback)
|
||||
util.ExportFuncVIIII(env, "go_inverse", inverseCallback)
|
||||
util.ExportFuncVIIII(env, "go_collation_needed", collationCallback)
|
||||
util.ExportFuncIIIIII(env, "go_compare", compareCallback)
|
||||
util.ExportFuncIIIIII(env, "go_vtab_create", vtabModuleCallback(xCreate))
|
||||
util.ExportFuncIIIIII(env, "go_vtab_connect", vtabModuleCallback(xConnect))
|
||||
util.ExportFuncII(env, "go_vtab_disconnect", vtabDisconnectCallback)
|
||||
util.ExportFuncII(env, "go_vtab_destroy", vtabDestroyCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_best_index", vtabBestIndexCallback)
|
||||
util.ExportFuncIIIII(env, "go_vtab_update", vtabUpdateCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_rename", vtabRenameCallback)
|
||||
util.ExportFuncIIIII(env, "go_vtab_find_function", vtabFindFuncCallback)
|
||||
util.ExportFuncII(env, "go_vtab_begin", vtabBeginCallback)
|
||||
util.ExportFuncII(env, "go_vtab_sync", vtabSyncCallback)
|
||||
util.ExportFuncII(env, "go_vtab_commit", vtabCommitCallback)
|
||||
util.ExportFuncII(env, "go_vtab_rollback", vtabRollbackCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_savepoint", vtabSavepointCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_release", vtabReleaseCallback)
|
||||
util.ExportFuncIII(env, "go_vtab_rollback_to", vtabRollbackToCallback)
|
||||
util.ExportFuncIIIIII(env, "go_vtab_integrity", vtabIntegrityCallback)
|
||||
util.ExportFuncIII(env, "go_cur_open", cursorOpenCallback)
|
||||
util.ExportFuncII(env, "go_cur_close", cursorCloseCallback)
|
||||
util.ExportFuncIIIIII(env, "go_cur_filter", cursorFilterCallback)
|
||||
util.ExportFuncII(env, "go_cur_next", cursorNextCallback)
|
||||
util.ExportFuncII(env, "go_cur_eof", cursorEOFCallback)
|
||||
util.ExportFuncIIII(env, "go_cur_column", cursorColumnCallback)
|
||||
util.ExportFuncIII(env, "go_cur_rowid", cursorRowIDCallback)
|
||||
return env
|
||||
}
|
||||
+274
-102
@@ -1,7 +1,9 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
@@ -9,7 +11,7 @@ import (
|
||||
|
||||
// Stmt is a prepared statement object.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/stmt.html
|
||||
// https://sqlite.org/c3ref/stmt.html
|
||||
type Stmt struct {
|
||||
c *Conn
|
||||
err error
|
||||
@@ -20,33 +22,49 @@ type Stmt struct {
|
||||
//
|
||||
// It is safe to close a nil, zero or closed Stmt.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/finalize.html
|
||||
// https://sqlite.org/c3ref/finalize.html
|
||||
func (s *Stmt) Close() error {
|
||||
if s == nil || s.handle == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := s.c.call(s.c.api.finalize, uint64(s.handle))
|
||||
r := s.c.call("sqlite3_finalize", uint64(s.handle))
|
||||
|
||||
s.handle = 0
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// Conn returns the database connection to which the prepared statement belongs.
|
||||
//
|
||||
// https://sqlite.org/c3ref/db_handle.html
|
||||
func (s *Stmt) Conn() *Conn {
|
||||
return s.c
|
||||
}
|
||||
|
||||
// ReadOnly returns true if and only if the statement
|
||||
// makes no direct changes to the content of the database file.
|
||||
//
|
||||
// https://sqlite.org/c3ref/stmt_readonly.html
|
||||
func (s *Stmt) ReadOnly() bool {
|
||||
r := s.c.call("sqlite3_stmt_readonly", uint64(s.handle))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
// Reset resets the prepared statement object.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/reset.html
|
||||
// https://sqlite.org/c3ref/reset.html
|
||||
func (s *Stmt) Reset() error {
|
||||
r := s.c.call(s.c.api.reset, uint64(s.handle))
|
||||
r := s.c.call("sqlite3_reset", uint64(s.handle))
|
||||
s.err = nil
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// ClearBindings resets all bindings on the prepared statement.
|
||||
// Busy determines if a prepared statement has been reset.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/clear_bindings.html
|
||||
func (s *Stmt) ClearBindings() error {
|
||||
r := s.c.call(s.c.api.clearBindings, uint64(s.handle))
|
||||
return s.c.error(r)
|
||||
// https://sqlite.org/c3ref/stmt_busy.html
|
||||
func (s *Stmt) Busy() bool {
|
||||
r := s.c.call("sqlite3_stmt_busy", uint64(s.handle))
|
||||
return r != 0
|
||||
}
|
||||
|
||||
// Step evaluates the SQL statement.
|
||||
@@ -57,16 +75,17 @@ func (s *Stmt) ClearBindings() error {
|
||||
// If an error has occurred, Step returns false;
|
||||
// call [Stmt.Err] or [Stmt.Reset] to get the error.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/step.html
|
||||
// https://sqlite.org/c3ref/step.html
|
||||
func (s *Stmt) Step() bool {
|
||||
s.c.checkInterrupt()
|
||||
r := s.c.call(s.c.api.step, uint64(s.handle))
|
||||
if r == _ROW {
|
||||
return true
|
||||
}
|
||||
if r == _DONE {
|
||||
r := s.c.call("sqlite3_step", uint64(s.handle))
|
||||
switch r {
|
||||
case _ROW:
|
||||
s.err = nil
|
||||
} else {
|
||||
return true
|
||||
case _DONE:
|
||||
s.err = nil
|
||||
default:
|
||||
s.err = s.c.error(r)
|
||||
}
|
||||
return false
|
||||
@@ -75,7 +94,7 @@ func (s *Stmt) Step() bool {
|
||||
// Err gets the last error occurred during [Stmt.Step].
|
||||
// Err returns nil after [Stmt.Reset] is called.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/step.html
|
||||
// https://sqlite.org/c3ref/step.html
|
||||
func (s *Stmt) Err() error {
|
||||
return s.err
|
||||
}
|
||||
@@ -88,40 +107,64 @@ func (s *Stmt) Exec() error {
|
||||
return s.Reset()
|
||||
}
|
||||
|
||||
// Status monitors the performance characteristics of prepared statements.
|
||||
//
|
||||
// https://sqlite.org/c3ref/stmt_status.html
|
||||
func (s *Stmt) Status(op StmtStatus, reset bool) int {
|
||||
if op > STMTSTATUS_FILTER_HIT && op != STMTSTATUS_MEMUSED {
|
||||
return 0
|
||||
}
|
||||
var i uint64
|
||||
if reset {
|
||||
i = 1
|
||||
}
|
||||
r := s.c.call("sqlite3_stmt_status", uint64(s.handle),
|
||||
uint64(op), i)
|
||||
return int(int32(r))
|
||||
}
|
||||
|
||||
// ClearBindings resets all bindings on the prepared statement.
|
||||
//
|
||||
// https://sqlite.org/c3ref/clear_bindings.html
|
||||
func (s *Stmt) ClearBindings() error {
|
||||
r := s.c.call("sqlite3_clear_bindings", uint64(s.handle))
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// BindCount returns the number of SQL parameters in the prepared statement.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_parameter_count.html
|
||||
// https://sqlite.org/c3ref/bind_parameter_count.html
|
||||
func (s *Stmt) BindCount() int {
|
||||
r := s.c.call(s.c.api.bindCount,
|
||||
r := s.c.call("sqlite3_bind_parameter_count",
|
||||
uint64(s.handle))
|
||||
return int(r)
|
||||
return int(int32(r))
|
||||
}
|
||||
|
||||
// BindIndex returns the index of a parameter in the prepared statement
|
||||
// given its name.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_parameter_index.html
|
||||
// https://sqlite.org/c3ref/bind_parameter_index.html
|
||||
func (s *Stmt) BindIndex(name string) int {
|
||||
defer s.c.arena.reset()
|
||||
defer s.c.arena.mark()()
|
||||
namePtr := s.c.arena.string(name)
|
||||
r := s.c.call(s.c.api.bindIndex,
|
||||
r := s.c.call("sqlite3_bind_parameter_index",
|
||||
uint64(s.handle), uint64(namePtr))
|
||||
return int(r)
|
||||
return int(int32(r))
|
||||
}
|
||||
|
||||
// BindName returns the name of a parameter in the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_parameter_name.html
|
||||
// https://sqlite.org/c3ref/bind_parameter_name.html
|
||||
func (s *Stmt) BindName(param int) string {
|
||||
r := s.c.call(s.c.api.bindName,
|
||||
r := s.c.call("sqlite3_bind_parameter_name",
|
||||
uint64(s.handle), uint64(param))
|
||||
|
||||
ptr := uint32(r)
|
||||
if ptr == 0 {
|
||||
return ""
|
||||
}
|
||||
return util.ReadString(s.c.mod, ptr, _MAX_STRING)
|
||||
return util.ReadString(s.c.mod, ptr, _MAX_NAME)
|
||||
}
|
||||
|
||||
// BindBool binds a bool to the prepared statement.
|
||||
@@ -129,18 +172,19 @@ func (s *Stmt) BindName(param int) string {
|
||||
// SQLite does not have a separate boolean storage class.
|
||||
// Instead, boolean values are stored as integers 0 (false) and 1 (true).
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindBool(param int, value bool) error {
|
||||
var i int64
|
||||
if value {
|
||||
return s.BindInt64(param, 1)
|
||||
i = 1
|
||||
}
|
||||
return s.BindInt64(param, 0)
|
||||
return s.BindInt64(param, i)
|
||||
}
|
||||
|
||||
// BindInt binds an int to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindInt(param int, value int) error {
|
||||
return s.BindInt64(param, int64(value))
|
||||
}
|
||||
@@ -148,9 +192,9 @@ func (s *Stmt) BindInt(param int, value int) error {
|
||||
// BindInt64 binds an int64 to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindInt64(param int, value int64) error {
|
||||
r := s.c.call(s.c.api.bindInteger,
|
||||
r := s.c.call("sqlite3_bind_int64",
|
||||
uint64(s.handle), uint64(param), uint64(value))
|
||||
return s.c.error(r)
|
||||
}
|
||||
@@ -158,9 +202,9 @@ func (s *Stmt) BindInt64(param int, value int64) error {
|
||||
// BindFloat binds a float64 to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindFloat(param int, value float64) error {
|
||||
r := s.c.call(s.c.api.bindFloat,
|
||||
r := s.c.call("sqlite3_bind_double",
|
||||
uint64(s.handle), uint64(param), math.Float64bits(value))
|
||||
return s.c.error(r)
|
||||
}
|
||||
@@ -168,13 +212,32 @@ func (s *Stmt) BindFloat(param int, value float64) error {
|
||||
// BindText binds a string to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindText(param int, value string) error {
|
||||
if len(value) > _MAX_LENGTH {
|
||||
return TOOBIG
|
||||
}
|
||||
ptr := s.c.newString(value)
|
||||
r := s.c.call(s.c.api.bindText,
|
||||
r := s.c.call("sqlite3_bind_text64",
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(value)),
|
||||
uint64(s.c.api.destructor), _UTF8)
|
||||
uint64(s.c.freer), _UTF8)
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// BindRawText binds a []byte to the prepared statement as text.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindRawText(param int, value []byte) error {
|
||||
if len(value) > _MAX_LENGTH {
|
||||
return TOOBIG
|
||||
}
|
||||
ptr := s.c.newBytes(value)
|
||||
r := s.c.call("sqlite3_bind_text64",
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(value)),
|
||||
uint64(s.c.freer), _UTF8)
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
@@ -182,22 +245,25 @@ func (s *Stmt) BindText(param int, value string) error {
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
// Binding a nil slice is the same as calling [Stmt.BindNull].
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindBlob(param int, value []byte) error {
|
||||
if len(value) > _MAX_LENGTH {
|
||||
return TOOBIG
|
||||
}
|
||||
ptr := s.c.newBytes(value)
|
||||
r := s.c.call(s.c.api.bindBlob,
|
||||
r := s.c.call("sqlite3_bind_blob64",
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(value)),
|
||||
uint64(s.c.api.destructor))
|
||||
uint64(s.c.freer))
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// BindZeroBlob binds a zero-filled, length n BLOB to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindZeroBlob(param int, n int64) error {
|
||||
r := s.c.call(s.c.api.bindZeroBlob,
|
||||
r := s.c.call("sqlite3_bind_zeroblob64",
|
||||
uint64(s.handle), uint64(param), uint64(n))
|
||||
return s.c.error(r)
|
||||
}
|
||||
@@ -205,9 +271,9 @@ func (s *Stmt) BindZeroBlob(param int, n int64) error {
|
||||
// BindNull binds a NULL to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindNull(param int) error {
|
||||
r := s.c.call(s.c.api.bindNull,
|
||||
r := s.c.call("sqlite3_bind_null",
|
||||
uint64(s.handle), uint64(param))
|
||||
return s.c.error(r)
|
||||
}
|
||||
@@ -215,7 +281,7 @@ func (s *Stmt) BindNull(param int) error {
|
||||
// BindTime binds a [time.Time] to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/bind_blob.html
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindTime(param int, value time.Time, format TimeFormat) error {
|
||||
if format == TimeFormatDefault {
|
||||
return s.bindRFC3339Nano(param, value)
|
||||
@@ -234,71 +300,119 @@ func (s *Stmt) BindTime(param int, value time.Time, format TimeFormat) error {
|
||||
}
|
||||
|
||||
func (s *Stmt) bindRFC3339Nano(param int, value time.Time) error {
|
||||
const maxlen = uint64(len(time.RFC3339Nano))
|
||||
const maxlen = uint64(len(time.RFC3339Nano)) + 5
|
||||
|
||||
ptr := s.c.new(maxlen)
|
||||
buf := util.View(s.c.mod, ptr, maxlen)
|
||||
buf = value.AppendFormat(buf[:0], time.RFC3339Nano)
|
||||
|
||||
r := s.c.call(s.c.api.bindText,
|
||||
r := s.c.call("sqlite3_bind_text64",
|
||||
uint64(s.handle), uint64(param),
|
||||
uint64(ptr), uint64(len(buf)),
|
||||
uint64(s.c.api.destructor), _UTF8)
|
||||
uint64(s.c.freer), _UTF8)
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// BindPointer binds a NULL to the prepared statement, just like [Stmt.BindNull],
|
||||
// but it also associates ptr with that NULL value such that it can be retrieved
|
||||
// within an application-defined SQL function using [Value.Pointer].
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindPointer(param int, ptr any) error {
|
||||
valPtr := util.AddHandle(s.c.ctx, ptr)
|
||||
r := s.c.call("sqlite3_bind_pointer_go",
|
||||
uint64(s.handle), uint64(param), uint64(valPtr))
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// BindJSON binds the JSON encoding of value to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindJSON(param int, value any) error {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.BindRawText(param, data)
|
||||
}
|
||||
|
||||
// BindValue binds a copy of value to the prepared statement.
|
||||
// The leftmost SQL parameter has an index of 1.
|
||||
//
|
||||
// https://sqlite.org/c3ref/bind_blob.html
|
||||
func (s *Stmt) BindValue(param int, value Value) error {
|
||||
if value.c != s.c {
|
||||
return MISUSE
|
||||
}
|
||||
r := s.c.call("sqlite3_bind_value",
|
||||
uint64(s.handle), uint64(param), uint64(value.handle))
|
||||
return s.c.error(r)
|
||||
}
|
||||
|
||||
// ColumnCount returns the number of columns in a result set.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_count.html
|
||||
// https://sqlite.org/c3ref/column_count.html
|
||||
func (s *Stmt) ColumnCount() int {
|
||||
r := s.c.call(s.c.api.columnCount,
|
||||
r := s.c.call("sqlite3_column_count",
|
||||
uint64(s.handle))
|
||||
return int(r)
|
||||
return int(int32(r))
|
||||
}
|
||||
|
||||
// ColumnName returns the name of the result column.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_name.html
|
||||
// https://sqlite.org/c3ref/column_name.html
|
||||
func (s *Stmt) ColumnName(col int) string {
|
||||
r := s.c.call(s.c.api.columnName,
|
||||
r := s.c.call("sqlite3_column_name",
|
||||
uint64(s.handle), uint64(col))
|
||||
|
||||
ptr := uint32(r)
|
||||
if ptr == 0 {
|
||||
panic(util.OOMErr)
|
||||
}
|
||||
return util.ReadString(s.c.mod, ptr, _MAX_STRING)
|
||||
return util.ReadString(s.c.mod, ptr, _MAX_NAME)
|
||||
}
|
||||
|
||||
// ColumnType returns the initial [Datatype] of the result column.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnType(col int) Datatype {
|
||||
r := s.c.call(s.c.api.columnType,
|
||||
r := s.c.call("sqlite3_column_type",
|
||||
uint64(s.handle), uint64(col))
|
||||
return Datatype(r)
|
||||
}
|
||||
|
||||
// ColumnDeclType returns the declared datatype of the result column.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://sqlite.org/c3ref/column_decltype.html
|
||||
func (s *Stmt) ColumnDeclType(col int) string {
|
||||
r := s.c.call("sqlite3_column_decltype",
|
||||
uint64(s.handle), uint64(col))
|
||||
if r == 0 {
|
||||
return ""
|
||||
}
|
||||
return util.ReadString(s.c.mod, uint32(r), _MAX_NAME)
|
||||
}
|
||||
|
||||
// ColumnBool returns the value of the result column as a bool.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
// SQLite does not have a separate boolean storage class.
|
||||
// Instead, boolean values are retrieved as integers,
|
||||
// with 0 converted to false and any other value to true.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnBool(col int) bool {
|
||||
if i := s.ColumnInt64(col); i != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return s.ColumnInt64(col) != 0
|
||||
}
|
||||
|
||||
// ColumnInt returns the value of the result column as an int.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnInt(col int) int {
|
||||
return int(s.ColumnInt64(col))
|
||||
}
|
||||
@@ -306,9 +420,9 @@ func (s *Stmt) ColumnInt(col int) int {
|
||||
// ColumnInt64 returns the value of the result column as an int64.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnInt64(col int) int64 {
|
||||
r := s.c.call(s.c.api.columnInteger,
|
||||
r := s.c.call("sqlite3_column_int64",
|
||||
uint64(s.handle), uint64(col))
|
||||
return int64(r)
|
||||
}
|
||||
@@ -316,9 +430,9 @@ func (s *Stmt) ColumnInt64(col int) int64 {
|
||||
// ColumnFloat returns the value of the result column as a float64.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnFloat(col int) float64 {
|
||||
r := s.c.call(s.c.api.columnFloat,
|
||||
r := s.c.call("sqlite3_column_double",
|
||||
uint64(s.handle), uint64(col))
|
||||
return math.Float64frombits(r)
|
||||
}
|
||||
@@ -326,7 +440,7 @@ func (s *Stmt) ColumnFloat(col int) float64 {
|
||||
// ColumnTime returns the value of the result column as a [time.Time].
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnTime(col int, format TimeFormat) time.Time {
|
||||
var v any
|
||||
switch s.ColumnType(col) {
|
||||
@@ -351,7 +465,7 @@ func (s *Stmt) ColumnTime(col int, format TimeFormat) time.Time {
|
||||
// ColumnText returns the value of the result column as a string.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnText(col int) string {
|
||||
return string(s.ColumnRawText(col))
|
||||
}
|
||||
@@ -360,7 +474,7 @@ func (s *Stmt) ColumnText(col int) string {
|
||||
// the value of the result column as a []byte.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnBlob(col int, buf []byte) []byte {
|
||||
return append(buf, s.ColumnRawBlob(col)...)
|
||||
}
|
||||
@@ -370,22 +484,11 @@ func (s *Stmt) ColumnBlob(col int, buf []byte) []byte {
|
||||
// subsequent calls to [Stmt] methods.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnRawText(col int) []byte {
|
||||
r := s.c.call(s.c.api.columnText,
|
||||
r := s.c.call("sqlite3_column_text",
|
||||
uint64(s.handle), uint64(col))
|
||||
|
||||
ptr := uint32(r)
|
||||
if ptr == 0 {
|
||||
r = s.c.call(s.c.api.errcode, uint64(s.c.handle))
|
||||
s.err = s.c.error(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
r = s.c.call(s.c.api.columnBytes,
|
||||
uint64(s.handle), uint64(col))
|
||||
|
||||
return util.View(s.c.mod, ptr, r)
|
||||
return s.columnRawBytes(col, uint32(r))
|
||||
}
|
||||
|
||||
// ColumnRawBlob returns the value of the result column as a []byte.
|
||||
@@ -393,35 +496,104 @@ func (s *Stmt) ColumnRawText(col int) []byte {
|
||||
// subsequent calls to [Stmt] methods.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/column_blob.html
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnRawBlob(col int) []byte {
|
||||
r := s.c.call(s.c.api.columnBlob,
|
||||
r := s.c.call("sqlite3_column_blob",
|
||||
uint64(s.handle), uint64(col))
|
||||
return s.columnRawBytes(col, uint32(r))
|
||||
}
|
||||
|
||||
ptr := uint32(r)
|
||||
func (s *Stmt) columnRawBytes(col int, ptr uint32) []byte {
|
||||
if ptr == 0 {
|
||||
r = s.c.call(s.c.api.errcode, uint64(s.c.handle))
|
||||
r := s.c.call("sqlite3_errcode", uint64(s.c.handle))
|
||||
s.err = s.c.error(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
r = s.c.call(s.c.api.columnBytes,
|
||||
r := s.c.call("sqlite3_column_bytes",
|
||||
uint64(s.handle), uint64(col))
|
||||
|
||||
return util.View(s.c.mod, ptr, r)
|
||||
}
|
||||
|
||||
// Return true if stmt is an empty SQL statement.
|
||||
// This is used as an optimization.
|
||||
// It's OK to always return false here.
|
||||
func emptyStatement(stmt string) bool {
|
||||
for _, b := range []byte(stmt) {
|
||||
switch b {
|
||||
case ' ', '\n', '\r', '\t', '\v', '\f':
|
||||
case ';':
|
||||
default:
|
||||
return false
|
||||
// ColumnJSON parses the JSON-encoded value of the result column
|
||||
// and stores it in the value pointed to by ptr.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnJSON(col int, ptr any) error {
|
||||
var data []byte
|
||||
switch s.ColumnType(col) {
|
||||
case NULL:
|
||||
data = append(data, "null"...)
|
||||
case TEXT:
|
||||
data = s.ColumnRawText(col)
|
||||
case BLOB:
|
||||
data = s.ColumnRawBlob(col)
|
||||
case INTEGER:
|
||||
data = strconv.AppendInt(nil, s.ColumnInt64(col), 10)
|
||||
case FLOAT:
|
||||
data = strconv.AppendFloat(nil, s.ColumnFloat(col), 'g', -1, 64)
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
return json.Unmarshal(data, ptr)
|
||||
}
|
||||
|
||||
// ColumnValue returns the unprotected value of the result column.
|
||||
// The leftmost column of the result set has the index 0.
|
||||
//
|
||||
// https://sqlite.org/c3ref/column_blob.html
|
||||
func (s *Stmt) ColumnValue(col int) Value {
|
||||
r := s.c.call("sqlite3_column_value",
|
||||
uint64(s.handle), uint64(col))
|
||||
return Value{
|
||||
c: s.c,
|
||||
unprot: true,
|
||||
handle: uint32(r),
|
||||
}
|
||||
}
|
||||
|
||||
// Columns populates result columns into the provided slice.
|
||||
// The slice must have [Stmt.ColumnCount] length.
|
||||
//
|
||||
// [INTEGER] columns will be retrieved as int64 values,
|
||||
// [FLOAT] as float64, [NULL] as nil,
|
||||
// [TEXT] as string, and [BLOB] as []byte.
|
||||
// Any []byte are owned by SQLite and may be invalidated by
|
||||
// subsequent calls to [Stmt] methods.
|
||||
func (s *Stmt) Columns(dest []any) error {
|
||||
defer s.c.arena.mark()()
|
||||
count := uint64(len(dest))
|
||||
typePtr := s.c.arena.new(count)
|
||||
dataPtr := s.c.arena.new(8 * count)
|
||||
|
||||
r := s.c.call("sqlite3_columns_go",
|
||||
uint64(s.handle), count, uint64(typePtr), uint64(dataPtr))
|
||||
if err := s.c.error(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
types := util.View(s.c.mod, typePtr, count)
|
||||
for i := range dest {
|
||||
switch types[i] {
|
||||
case byte(INTEGER):
|
||||
dest[i] = int64(util.ReadUint64(s.c.mod, dataPtr+8*uint32(i)))
|
||||
continue
|
||||
case byte(FLOAT):
|
||||
dest[i] = util.ReadFloat64(s.c.mod, dataPtr+8*uint32(i))
|
||||
continue
|
||||
case byte(NULL):
|
||||
dest[i] = nil
|
||||
continue
|
||||
}
|
||||
ptr := util.ReadUint32(s.c.mod, dataPtr+8*uint32(i)+0)
|
||||
len := util.ReadUint32(s.c.mod, dataPtr+8*uint32(i)+4)
|
||||
buf := util.View(s.c.mod, ptr, uint64(len))
|
||||
if types[i] == byte(TEXT) {
|
||||
dest[i] = string(buf)
|
||||
} else {
|
||||
dest[i] = buf
|
||||
}
|
||||
}
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
+42
-28
@@ -15,12 +15,12 @@ import (
|
||||
// See the documentation for the [TimeFormatDefault] constant
|
||||
// for formats recognized by SQLite.
|
||||
//
|
||||
// https://www.sqlite.org/lang_datefunc.html
|
||||
// https://sqlite.org/lang_datefunc.html
|
||||
type TimeFormat string
|
||||
|
||||
// TimeFormats recognized by SQLite to encode/decode time values.
|
||||
//
|
||||
// https://www.sqlite.org/lang_datefunc.html
|
||||
// https://sqlite.org/lang_datefunc.html#time_values
|
||||
const (
|
||||
TimeFormatDefault TimeFormat = "" // time.RFC3339Nano
|
||||
|
||||
@@ -83,9 +83,9 @@ const (
|
||||
// a float64 for [TimeFormatJulianDay] and [TimeFormatUnixFrac],
|
||||
// or an int64 for the other numeric formats.
|
||||
//
|
||||
// https://www.sqlite.org/lang_datefunc.html
|
||||
// https://sqlite.org/lang_datefunc.html
|
||||
//
|
||||
// [collating sequence]: https://www.sqlite.org/datatype3.html#collating_sequences
|
||||
// [collating sequence]: https://sqlite.org/datatype3.html#collating_sequences
|
||||
func (f TimeFormat) Encode(t time.Time) any {
|
||||
switch f {
|
||||
// Numeric formats
|
||||
@@ -136,7 +136,7 @@ func (f TimeFormat) Encode(t time.Time) any {
|
||||
// Unix timestamps before 1980 and after 9999 may be misinterpreted as julian day numbers,
|
||||
// or have the wrong time unit.
|
||||
//
|
||||
// https://www.sqlite.org/lang_datefunc.html
|
||||
// https://sqlite.org/lang_datefunc.html
|
||||
func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
switch f {
|
||||
// Numeric formats
|
||||
@@ -164,9 +164,9 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
case float64:
|
||||
sec, frac := math.Modf(v)
|
||||
nsec := math.Floor(frac * 1e9)
|
||||
return time.Unix(int64(sec), int64(nsec)), nil
|
||||
return time.Unix(int64(sec), int64(nsec)).UTC(), nil
|
||||
case int64:
|
||||
return time.Unix(v, 0), nil
|
||||
return time.Unix(v, 0).UTC(), nil
|
||||
default:
|
||||
return time.Time{}, util.TimeErr
|
||||
}
|
||||
@@ -181,9 +181,9 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case float64:
|
||||
return time.UnixMilli(int64(math.Floor(v))), nil
|
||||
return time.UnixMilli(int64(math.Floor(v))).UTC(), nil
|
||||
case int64:
|
||||
return time.UnixMilli(int64(v)), nil
|
||||
return time.UnixMilli(int64(v)).UTC(), nil
|
||||
default:
|
||||
return time.Time{}, util.TimeErr
|
||||
}
|
||||
@@ -198,9 +198,9 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case float64:
|
||||
return time.UnixMicro(int64(math.Floor(v))), nil
|
||||
return time.UnixMicro(int64(math.Floor(v))).UTC(), nil
|
||||
case int64:
|
||||
return time.UnixMicro(int64(v)), nil
|
||||
return time.UnixMicro(int64(v)).UTC(), nil
|
||||
default:
|
||||
return time.Time{}, util.TimeErr
|
||||
}
|
||||
@@ -215,9 +215,9 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case float64:
|
||||
return time.Unix(0, int64(math.Floor(v))), nil
|
||||
return time.Unix(0, int64(math.Floor(v))).UTC(), nil
|
||||
case int64:
|
||||
return time.Unix(0, int64(v)), nil
|
||||
return time.Unix(0, int64(v)).UTC(), nil
|
||||
default:
|
||||
return time.Time{}, util.TimeErr
|
||||
}
|
||||
@@ -238,26 +238,16 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
}
|
||||
|
||||
dates := []TimeFormat{
|
||||
TimeFormat6TZ, TimeFormat6, TimeFormat3TZ, TimeFormat3,
|
||||
TimeFormat5TZ, TimeFormat5, TimeFormat2TZ, TimeFormat2,
|
||||
TimeFormat1,
|
||||
TimeFormat9, TimeFormat8,
|
||||
TimeFormat6, TimeFormat5,
|
||||
TimeFormat3, TimeFormat2, TimeFormat1,
|
||||
}
|
||||
for _, f := range dates {
|
||||
t, err := time.Parse(string(f), s)
|
||||
t, err := f.Decode(s)
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
|
||||
times := []TimeFormat{
|
||||
TimeFormat9TZ, TimeFormat9, TimeFormat8TZ, TimeFormat8,
|
||||
}
|
||||
for _, f := range times {
|
||||
t, err := time.Parse(string(f), s)
|
||||
if err == nil {
|
||||
return t.AddDate(2000, 0, 0), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case float64:
|
||||
@@ -314,7 +304,10 @@ func (f TimeFormat) Decode(v any) (time.Time, error) {
|
||||
return time.Time{}, util.TimeErr
|
||||
}
|
||||
t, err := f.parseRelaxed(s)
|
||||
return t.AddDate(2000, 0, 0), err
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return t.AddDate(2000, 0, 0), nil
|
||||
|
||||
default:
|
||||
s, ok := v.(string)
|
||||
@@ -338,3 +331,24 @@ func (f TimeFormat) parseRelaxed(s string) (time.Time, error) {
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Scanner returns a [database/sql.Scanner] that can be used as an argument to
|
||||
// [database/sql.Row.Scan] and similar methods to
|
||||
// decode a time value into dest using this format.
|
||||
func (f TimeFormat) Scanner(dest *time.Time) interface{ Scan(any) error } {
|
||||
return timeScanner{dest, f}
|
||||
}
|
||||
|
||||
type timeScanner struct {
|
||||
*time.Time
|
||||
TimeFormat
|
||||
}
|
||||
|
||||
func (s timeScanner) Scan(src any) error {
|
||||
var ok bool
|
||||
var err error
|
||||
if *s.Time, ok = src.(time.Time); !ok {
|
||||
*s.Time, err = s.Decode(src)
|
||||
}
|
||||
return err
|
||||
}
|
||||
-202
@@ -1,202 +0,0 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Tx is an in-progress database transaction.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
type Tx struct {
|
||||
c *Conn
|
||||
}
|
||||
|
||||
// Begin starts a deferred transaction.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
func (c *Conn) Begin() Tx {
|
||||
// BEGIN even if interrupted.
|
||||
err := c.txExecInterrupted(`BEGIN DEFERRED`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return Tx{c}
|
||||
}
|
||||
|
||||
// BeginImmediate starts an immediate transaction.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
func (c *Conn) BeginImmediate() (Tx, error) {
|
||||
err := c.Exec(`BEGIN IMMEDIATE`)
|
||||
if err != nil {
|
||||
return Tx{}, err
|
||||
}
|
||||
return Tx{c}, nil
|
||||
}
|
||||
|
||||
// BeginExclusive starts an exclusive transaction.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
func (c *Conn) BeginExclusive() (Tx, error) {
|
||||
err := c.Exec(`BEGIN EXCLUSIVE`)
|
||||
if err != nil {
|
||||
return Tx{}, err
|
||||
}
|
||||
return Tx{c}, nil
|
||||
}
|
||||
|
||||
// End calls either [Tx.Commit] or [Tx.Rollback]
|
||||
// depending on whether *error points to a nil or non-nil error.
|
||||
//
|
||||
// This is meant to be deferred:
|
||||
//
|
||||
// func doWork(conn *sqlite3.Conn) (err error) {
|
||||
// tx := conn.Begin()
|
||||
// defer tx.End(&err)
|
||||
//
|
||||
// // ... do work in the transaction
|
||||
// }
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
func (tx Tx) End(errp *error) {
|
||||
recovered := recover()
|
||||
if recovered != nil {
|
||||
defer panic(recovered)
|
||||
}
|
||||
|
||||
if *errp == nil && recovered == nil {
|
||||
// Success path.
|
||||
if tx.c.GetAutocommit() { // There is nothing to commit.
|
||||
return
|
||||
}
|
||||
*errp = tx.Commit()
|
||||
if *errp == nil {
|
||||
return
|
||||
}
|
||||
// Fall through to the error path.
|
||||
}
|
||||
|
||||
// Error path.
|
||||
if tx.c.GetAutocommit() { // There is nothing to rollback.
|
||||
return
|
||||
}
|
||||
err := tx.Rollback()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Commit commits the transaction.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
func (tx Tx) Commit() error {
|
||||
return tx.c.Exec(`COMMIT`)
|
||||
}
|
||||
|
||||
// Rollback rolls back the transaction,
|
||||
// even if the connection has been interrupted.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
func (tx Tx) Rollback() error {
|
||||
return tx.c.txExecInterrupted(`ROLLBACK`)
|
||||
}
|
||||
|
||||
// Savepoint is a marker within a transaction
|
||||
// that allows for partial rollback.
|
||||
//
|
||||
// https://www.sqlite.org/lang_savepoint.html
|
||||
type Savepoint struct {
|
||||
c *Conn
|
||||
name string
|
||||
}
|
||||
|
||||
// Savepoint establishes a new transaction savepoint.
|
||||
//
|
||||
// https://www.sqlite.org/lang_savepoint.html
|
||||
func (c *Conn) Savepoint() Savepoint {
|
||||
name := "sqlite3.Savepoint"
|
||||
var pc [1]uintptr
|
||||
if n := runtime.Callers(2, pc[:]); n > 0 {
|
||||
frames := runtime.CallersFrames(pc[:n])
|
||||
frame, _ := frames.Next()
|
||||
if frame.Function != "" {
|
||||
name = frame.Function
|
||||
}
|
||||
}
|
||||
// Names can be reused; this makes catching bugs more likely.
|
||||
name += "#" + strconv.Itoa(int(rand.Int31()))
|
||||
|
||||
err := c.txExecInterrupted(fmt.Sprintf("SAVEPOINT %q;", name))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return Savepoint{c: c, name: name}
|
||||
}
|
||||
|
||||
// Release releases the savepoint rolling back any changes
|
||||
// if *error points to a non-nil error.
|
||||
//
|
||||
// This is meant to be deferred:
|
||||
//
|
||||
// func doWork(conn *sqlite3.Conn) (err error) {
|
||||
// savept := conn.Savepoint()
|
||||
// defer savept.Release(&err)
|
||||
//
|
||||
// // ... do work in the transaction
|
||||
// }
|
||||
func (s Savepoint) Release(errp *error) {
|
||||
recovered := recover()
|
||||
if recovered != nil {
|
||||
defer panic(recovered)
|
||||
}
|
||||
|
||||
if *errp == nil && recovered == nil {
|
||||
// Success path.
|
||||
if s.c.GetAutocommit() { // There is nothing to commit.
|
||||
return
|
||||
}
|
||||
*errp = s.c.Exec(fmt.Sprintf("RELEASE %q;", s.name))
|
||||
if *errp == nil {
|
||||
return
|
||||
}
|
||||
// Fall through to the error path.
|
||||
}
|
||||
|
||||
// Error path.
|
||||
if s.c.GetAutocommit() { // There is nothing to rollback.
|
||||
return
|
||||
}
|
||||
// ROLLBACK and RELEASE even if interrupted.
|
||||
err := s.c.txExecInterrupted(fmt.Sprintf(`
|
||||
ROLLBACK TO %[1]q;
|
||||
RELEASE %[1]q;
|
||||
`, s.name))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Rollback rolls the transaction back to the savepoint,
|
||||
// even if the connection has been interrupted.
|
||||
// Rollback does not release the savepoint.
|
||||
//
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
func (s Savepoint) Rollback() error {
|
||||
// ROLLBACK even if interrupted.
|
||||
return s.c.txExecInterrupted(fmt.Sprintf("ROLLBACK TO %q;", s.name))
|
||||
}
|
||||
|
||||
func (c *Conn) txExecInterrupted(sql string) error {
|
||||
err := c.Exec(sql)
|
||||
if errors.Is(err, INTERRUPT) {
|
||||
old := c.SetInterrupt(context.Background())
|
||||
defer c.SetInterrupt(old)
|
||||
err = c.Exec(sql)
|
||||
}
|
||||
return err
|
||||
}
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// Txn is an in-progress database transaction.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
type Txn struct {
|
||||
c *Conn
|
||||
}
|
||||
|
||||
// Begin starts a deferred transaction.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (c *Conn) Begin() Txn {
|
||||
// BEGIN even if interrupted.
|
||||
err := c.txnExecInterrupted(`BEGIN DEFERRED`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return Txn{c}
|
||||
}
|
||||
|
||||
// BeginImmediate starts an immediate transaction.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (c *Conn) BeginImmediate() (Txn, error) {
|
||||
err := c.Exec(`BEGIN IMMEDIATE`)
|
||||
if err != nil {
|
||||
return Txn{}, err
|
||||
}
|
||||
return Txn{c}, nil
|
||||
}
|
||||
|
||||
// BeginExclusive starts an exclusive transaction.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (c *Conn) BeginExclusive() (Txn, error) {
|
||||
err := c.Exec(`BEGIN EXCLUSIVE`)
|
||||
if err != nil {
|
||||
return Txn{}, err
|
||||
}
|
||||
return Txn{c}, nil
|
||||
}
|
||||
|
||||
// End calls either [Txn.Commit] or [Txn.Rollback]
|
||||
// depending on whether *error points to a nil or non-nil error.
|
||||
//
|
||||
// This is meant to be deferred:
|
||||
//
|
||||
// func doWork(db *sqlite3.Conn) (err error) {
|
||||
// tx := db.Begin()
|
||||
// defer tx.End(&err)
|
||||
//
|
||||
// // ... do work in the transaction
|
||||
// }
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (tx Txn) End(errp *error) {
|
||||
recovered := recover()
|
||||
if recovered != nil {
|
||||
defer panic(recovered)
|
||||
}
|
||||
|
||||
if *errp == nil && recovered == nil {
|
||||
// Success path.
|
||||
if tx.c.GetAutocommit() { // There is nothing to commit.
|
||||
return
|
||||
}
|
||||
*errp = tx.Commit()
|
||||
if *errp == nil {
|
||||
return
|
||||
}
|
||||
// Fall through to the error path.
|
||||
}
|
||||
|
||||
// Error path.
|
||||
if tx.c.GetAutocommit() { // There is nothing to rollback.
|
||||
return
|
||||
}
|
||||
err := tx.Rollback()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Commit commits the transaction.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (tx Txn) Commit() error {
|
||||
return tx.c.Exec(`COMMIT`)
|
||||
}
|
||||
|
||||
// Rollback rolls back the transaction,
|
||||
// even if the connection has been interrupted.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (tx Txn) Rollback() error {
|
||||
return tx.c.txnExecInterrupted(`ROLLBACK`)
|
||||
}
|
||||
|
||||
// Savepoint is a marker within a transaction
|
||||
// that allows for partial rollback.
|
||||
//
|
||||
// https://sqlite.org/lang_savepoint.html
|
||||
type Savepoint struct {
|
||||
c *Conn
|
||||
name string
|
||||
}
|
||||
|
||||
// Savepoint establishes a new transaction savepoint.
|
||||
//
|
||||
// https://sqlite.org/lang_savepoint.html
|
||||
func (c *Conn) Savepoint() Savepoint {
|
||||
// Names can be reused; this makes catching bugs more likely.
|
||||
name := saveptName() + "_" + strconv.Itoa(int(rand.Int31()))
|
||||
|
||||
err := c.txnExecInterrupted(fmt.Sprintf("SAVEPOINT %q;", name))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return Savepoint{c: c, name: name}
|
||||
}
|
||||
|
||||
func saveptName() (name string) {
|
||||
defer func() {
|
||||
if name == "" {
|
||||
name = "sqlite3.Savepoint"
|
||||
}
|
||||
}()
|
||||
|
||||
var pc [8]uintptr
|
||||
n := runtime.Callers(3, pc[:])
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
frames := runtime.CallersFrames(pc[:n])
|
||||
frame, more := frames.Next()
|
||||
for more && (strings.HasPrefix(frame.Function, "database/sql.") ||
|
||||
strings.HasPrefix(frame.Function, "github.com/ncruces/go-sqlite3/driver.")) {
|
||||
frame, more = frames.Next()
|
||||
}
|
||||
return frame.Function
|
||||
}
|
||||
|
||||
// Release releases the savepoint rolling back any changes
|
||||
// if *error points to a non-nil error.
|
||||
//
|
||||
// This is meant to be deferred:
|
||||
//
|
||||
// func doWork(db *sqlite3.Conn) (err error) {
|
||||
// savept := db.Savepoint()
|
||||
// defer savept.Release(&err)
|
||||
//
|
||||
// // ... do work in the transaction
|
||||
// }
|
||||
func (s Savepoint) Release(errp *error) {
|
||||
recovered := recover()
|
||||
if recovered != nil {
|
||||
defer panic(recovered)
|
||||
}
|
||||
|
||||
if *errp == nil && recovered == nil {
|
||||
// Success path.
|
||||
if s.c.GetAutocommit() { // There is nothing to commit.
|
||||
return
|
||||
}
|
||||
*errp = s.c.Exec(fmt.Sprintf("RELEASE %q;", s.name))
|
||||
if *errp == nil {
|
||||
return
|
||||
}
|
||||
// Fall through to the error path.
|
||||
}
|
||||
|
||||
// Error path.
|
||||
if s.c.GetAutocommit() { // There is nothing to rollback.
|
||||
return
|
||||
}
|
||||
// ROLLBACK and RELEASE even if interrupted.
|
||||
err := s.c.txnExecInterrupted(fmt.Sprintf(`
|
||||
ROLLBACK TO %[1]q;
|
||||
RELEASE %[1]q;
|
||||
`, s.name))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Rollback rolls the transaction back to the savepoint,
|
||||
// even if the connection has been interrupted.
|
||||
// Rollback does not release the savepoint.
|
||||
//
|
||||
// https://sqlite.org/lang_transaction.html
|
||||
func (s Savepoint) Rollback() error {
|
||||
// ROLLBACK even if interrupted.
|
||||
return s.c.txnExecInterrupted(fmt.Sprintf("ROLLBACK TO %q;", s.name))
|
||||
}
|
||||
|
||||
func (c *Conn) txnExecInterrupted(sql string) error {
|
||||
err := c.Exec(sql)
|
||||
if errors.Is(err, INTERRUPT) {
|
||||
old := c.SetInterrupt(context.Background())
|
||||
defer c.SetInterrupt(old)
|
||||
err = c.Exec(sql)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// TxnState starts a deferred transaction.
|
||||
//
|
||||
// https://sqlite.org/c3ref/txn_state.html
|
||||
func (c *Conn) TxnState(schema string) TxnState {
|
||||
var ptr uint32
|
||||
if schema != "" {
|
||||
defer c.arena.mark()()
|
||||
ptr = c.arena.string(schema)
|
||||
}
|
||||
r := c.call("sqlite3_txn_state", uint64(c.handle), uint64(ptr))
|
||||
return TxnState(r)
|
||||
}
|
||||
|
||||
// CommitHook registers a callback function to be invoked
|
||||
// whenever a transaction is committed.
|
||||
// Return true to allow the commit operation to continue normally.
|
||||
//
|
||||
// https://sqlite.org/c3ref/commit_hook.html
|
||||
func (c *Conn) CommitHook(cb func() (ok bool)) {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
c.call("sqlite3_commit_hook_go", uint64(c.handle), enable)
|
||||
c.commit = cb
|
||||
}
|
||||
|
||||
// RollbackHook registers a callback function to be invoked
|
||||
// whenever a transaction is rolled back.
|
||||
//
|
||||
// https://sqlite.org/c3ref/commit_hook.html
|
||||
func (c *Conn) RollbackHook(cb func()) {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
c.call("sqlite3_rollback_hook_go", uint64(c.handle), enable)
|
||||
c.rollback = cb
|
||||
}
|
||||
|
||||
// UpdateHook registers a callback function to be invoked
|
||||
// whenever a row is updated, inserted or deleted in a rowid table.
|
||||
//
|
||||
// https://sqlite.org/c3ref/update_hook.html
|
||||
func (c *Conn) UpdateHook(cb func(action AuthorizerActionCode, schema, table string, rowid int64)) {
|
||||
var enable uint64
|
||||
if cb != nil {
|
||||
enable = 1
|
||||
}
|
||||
c.call("sqlite3_update_hook_go", uint64(c.handle), enable)
|
||||
c.update = cb
|
||||
}
|
||||
|
||||
func commitCallback(ctx context.Context, mod api.Module, pDB uint32) (rollback uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.commit != nil {
|
||||
if !c.commit() {
|
||||
rollback = 1
|
||||
}
|
||||
}
|
||||
return rollback
|
||||
}
|
||||
|
||||
func rollbackCallback(ctx context.Context, mod api.Module, pDB uint32) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.rollback != nil {
|
||||
c.rollback()
|
||||
}
|
||||
}
|
||||
|
||||
func updateCallback(ctx context.Context, mod api.Module, pDB uint32, action AuthorizerActionCode, zSchema, zTabName uint32, rowid uint64) {
|
||||
if c, ok := ctx.Value(connKey{}).(*Conn); ok && c.handle == pDB && c.update != nil {
|
||||
schema := util.ReadString(mod, zSchema, _MAX_NAME)
|
||||
table := util.ReadString(mod, zTabName, _MAX_NAME)
|
||||
c.update(action, schema, table, int64(rowid))
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
//go:build !windows
|
||||
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
// OpenFile behaves the same as [os.OpenFile],
|
||||
// except on Windows it sets [syscall.FILE_SHARE_DELETE].
|
||||
//
|
||||
// See: https://go.dev/issue/32088#issuecomment-502850674
|
||||
func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
|
||||
return os.OpenFile(name, flag, perm)
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
. "syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// OpenFile behaves the same as [os.OpenFile],
|
||||
// except on Windows it sets [syscall.FILE_SHARE_DELETE].
|
||||
//
|
||||
// See: https://go.dev/issue/32088#issuecomment-502850674
|
||||
func OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
|
||||
if name == "" {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: ENOENT}
|
||||
}
|
||||
r, e := syscallOpen(name, flag, uint32(perm.Perm()))
|
||||
if e != nil {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: e}
|
||||
}
|
||||
return os.NewFile(uintptr(r), name), nil
|
||||
}
|
||||
|
||||
// syscallOpen is a copy of [syscall.Open]
|
||||
// that uses [syscall.FILE_SHARE_DELETE].
|
||||
//
|
||||
// https://go.dev/src/syscall/syscall_windows.go
|
||||
func syscallOpen(path string, mode int, perm uint32) (fd Handle, err error) {
|
||||
if len(path) == 0 {
|
||||
return InvalidHandle, ERROR_FILE_NOT_FOUND
|
||||
}
|
||||
pathp, err := UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return InvalidHandle, err
|
||||
}
|
||||
var access uint32
|
||||
switch mode & (O_RDONLY | O_WRONLY | O_RDWR) {
|
||||
case O_RDONLY:
|
||||
access = GENERIC_READ
|
||||
case O_WRONLY:
|
||||
access = GENERIC_WRITE
|
||||
case O_RDWR:
|
||||
access = GENERIC_READ | GENERIC_WRITE
|
||||
}
|
||||
if mode&O_CREAT != 0 {
|
||||
access |= GENERIC_WRITE
|
||||
}
|
||||
if mode&O_APPEND != 0 {
|
||||
access &^= GENERIC_WRITE
|
||||
access |= FILE_APPEND_DATA
|
||||
}
|
||||
sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
|
||||
var sa *SecurityAttributes
|
||||
if mode&O_CLOEXEC == 0 {
|
||||
sa = makeInheritSa()
|
||||
}
|
||||
var createmode uint32
|
||||
switch {
|
||||
case mode&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL):
|
||||
createmode = CREATE_NEW
|
||||
case mode&(O_CREAT|O_TRUNC) == (O_CREAT | O_TRUNC):
|
||||
createmode = CREATE_ALWAYS
|
||||
case mode&O_CREAT == O_CREAT:
|
||||
createmode = OPEN_ALWAYS
|
||||
case mode&O_TRUNC == O_TRUNC:
|
||||
createmode = TRUNCATE_EXISTING
|
||||
default:
|
||||
createmode = OPEN_EXISTING
|
||||
}
|
||||
var attrs uint32 = FILE_ATTRIBUTE_NORMAL
|
||||
if perm&S_IWRITE == 0 {
|
||||
attrs = FILE_ATTRIBUTE_READONLY
|
||||
if createmode == CREATE_ALWAYS {
|
||||
const _ERROR_BAD_NETPATH = Errno(53)
|
||||
// We have been asked to create a read-only file.
|
||||
// If the file already exists, the semantics of
|
||||
// the Unix open system call is to preserve the
|
||||
// existing permissions. If we pass CREATE_ALWAYS
|
||||
// and FILE_ATTRIBUTE_READONLY to CreateFile,
|
||||
// and the file already exists, CreateFile will
|
||||
// change the file permissions.
|
||||
// Avoid that to preserve the Unix semantics.
|
||||
h, e := CreateFile(pathp, access, sharemode, sa, TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
|
||||
switch e {
|
||||
case ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, ERROR_PATH_NOT_FOUND:
|
||||
// File does not exist. These are the same
|
||||
// errors as Errno.Is checks for ErrNotExist.
|
||||
// Carry on to create the file.
|
||||
default:
|
||||
// Success or some different error.
|
||||
return h, e
|
||||
}
|
||||
}
|
||||
}
|
||||
if createmode == OPEN_EXISTING && access == GENERIC_READ {
|
||||
// Necessary for opening directory handles.
|
||||
attrs |= FILE_FLAG_BACKUP_SEMANTICS
|
||||
}
|
||||
if mode&O_SYNC != 0 {
|
||||
const _FILE_FLAG_WRITE_THROUGH = 0x80000000
|
||||
attrs |= _FILE_FLAG_WRITE_THROUGH
|
||||
}
|
||||
return CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
|
||||
}
|
||||
|
||||
func makeInheritSa() *SecurityAttributes {
|
||||
var sa SecurityAttributes
|
||||
sa.Length = uint32(unsafe.Sizeof(sa))
|
||||
sa.InheritHandle = 1
|
||||
return &sa
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
// FS implements [fs.FS], [fs.StatFS], and [fs.ReadFileFS]
|
||||
// using package [os].
|
||||
//
|
||||
// This filesystem does not respect [fs.ValidPath] rules,
|
||||
// and fails [testing/fstest.TestFS]!
|
||||
//
|
||||
// Still, it can be a useful tool to unify implementations
|
||||
// that can access either the [os] filesystem or an [fs.FS].
|
||||
// It's OK to use this to open files, but you should avoid
|
||||
// opening directories, resolving paths, or walking the file system.
|
||||
type FS struct{}
|
||||
|
||||
// Open implements [fs.FS].
|
||||
func (FS) Open(name string) (fs.File, error) {
|
||||
return OpenFile(name, os.O_RDONLY, 0)
|
||||
}
|
||||
|
||||
// ReadFileFS implements [fs.StatFS].
|
||||
func (FS) Stat(name string) (fs.FileInfo, error) {
|
||||
return os.Stat(name)
|
||||
}
|
||||
|
||||
// ReadFile implements [fs.ReadFileFS].
|
||||
func (FS) ReadFile(name string) ([]byte, error) {
|
||||
return os.ReadFile(name)
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package osutil implements operating system utility functions.
|
||||
package osutil
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// Value is any value that can be stored in a database table.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value.html
|
||||
type Value struct {
|
||||
c *Conn
|
||||
handle uint32
|
||||
unprot bool
|
||||
copied bool
|
||||
}
|
||||
|
||||
func (v Value) protected() uint64 {
|
||||
if v.unprot {
|
||||
panic(util.ValueErr)
|
||||
}
|
||||
return uint64(v.handle)
|
||||
}
|
||||
|
||||
// Dup makes a copy of the SQL value and returns a pointer to that copy.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_dup.html
|
||||
func (v Value) Dup() *Value {
|
||||
r := v.c.call("sqlite3_value_dup", uint64(v.handle))
|
||||
return &Value{
|
||||
c: v.c,
|
||||
copied: true,
|
||||
handle: uint32(r),
|
||||
}
|
||||
}
|
||||
|
||||
// Close frees an SQL value previously obtained by [Value.Dup].
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_dup.html
|
||||
func (dup *Value) Close() error {
|
||||
if !dup.copied {
|
||||
panic(util.ValueErr)
|
||||
}
|
||||
dup.c.call("sqlite3_value_free", uint64(dup.handle))
|
||||
dup.handle = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the initial datatype of the value.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Type() Datatype {
|
||||
r := v.c.call("sqlite3_value_type", v.protected())
|
||||
return Datatype(r)
|
||||
}
|
||||
|
||||
// Type returns the numeric datatype of the value.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) NumericType() Datatype {
|
||||
r := v.c.call("sqlite3_value_numeric_type", v.protected())
|
||||
return Datatype(r)
|
||||
}
|
||||
|
||||
// Bool returns the value as a bool.
|
||||
// SQLite does not have a separate boolean storage class.
|
||||
// Instead, boolean values are retrieved as integers,
|
||||
// with 0 converted to false and any other value to true.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Bool() bool {
|
||||
return v.Int64() != 0
|
||||
}
|
||||
|
||||
// Int returns the value as an int.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Int() int {
|
||||
return int(v.Int64())
|
||||
}
|
||||
|
||||
// Int64 returns the value as an int64.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Int64() int64 {
|
||||
r := v.c.call("sqlite3_value_int64", v.protected())
|
||||
return int64(r)
|
||||
}
|
||||
|
||||
// Float returns the value as a float64.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Float() float64 {
|
||||
r := v.c.call("sqlite3_value_double", v.protected())
|
||||
return math.Float64frombits(r)
|
||||
}
|
||||
|
||||
// Time returns the value as a [time.Time].
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Time(format TimeFormat) time.Time {
|
||||
var a any
|
||||
switch v.Type() {
|
||||
case INTEGER:
|
||||
a = v.Int64()
|
||||
case FLOAT:
|
||||
a = v.Float()
|
||||
case TEXT, BLOB:
|
||||
a = v.Text()
|
||||
case NULL:
|
||||
return time.Time{}
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
t, _ := format.Decode(a)
|
||||
return t
|
||||
}
|
||||
|
||||
// Text returns the value as a string.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Text() string {
|
||||
return string(v.RawText())
|
||||
}
|
||||
|
||||
// Blob appends to buf and returns
|
||||
// the value as a []byte.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) Blob(buf []byte) []byte {
|
||||
return append(buf, v.RawBlob()...)
|
||||
}
|
||||
|
||||
// RawText returns the value as a []byte.
|
||||
// The []byte is owned by SQLite and may be invalidated by
|
||||
// subsequent calls to [Value] methods.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) RawText() []byte {
|
||||
r := v.c.call("sqlite3_value_text", v.protected())
|
||||
return v.rawBytes(uint32(r))
|
||||
}
|
||||
|
||||
// RawBlob returns the value as a []byte.
|
||||
// The []byte is owned by SQLite and may be invalidated by
|
||||
// subsequent calls to [Value] methods.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) RawBlob() []byte {
|
||||
r := v.c.call("sqlite3_value_blob", v.protected())
|
||||
return v.rawBytes(uint32(r))
|
||||
}
|
||||
|
||||
func (v Value) rawBytes(ptr uint32) []byte {
|
||||
if ptr == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := v.c.call("sqlite3_value_bytes", v.protected())
|
||||
return util.View(v.c.mod, ptr, r)
|
||||
}
|
||||
|
||||
// Pointer gets the pointer associated with this value,
|
||||
// or nil if it has no associated pointer.
|
||||
func (v Value) Pointer() any {
|
||||
r := v.c.call("sqlite3_value_pointer_go", v.protected())
|
||||
return util.GetHandle(v.c.ctx, uint32(r))
|
||||
}
|
||||
|
||||
// JSON parses a JSON-encoded value
|
||||
// and stores the result in the value pointed to by ptr.
|
||||
func (v Value) JSON(ptr any) error {
|
||||
var data []byte
|
||||
switch v.Type() {
|
||||
case NULL:
|
||||
data = append(data, "null"...)
|
||||
case TEXT:
|
||||
data = v.RawText()
|
||||
case BLOB:
|
||||
data = v.RawBlob()
|
||||
case INTEGER:
|
||||
data = strconv.AppendInt(nil, v.Int64(), 10)
|
||||
case FLOAT:
|
||||
data = strconv.AppendFloat(nil, v.Float(), 'g', -1, 64)
|
||||
default:
|
||||
panic(util.AssertErr())
|
||||
}
|
||||
return json.Unmarshal(data, ptr)
|
||||
}
|
||||
|
||||
// NoChange returns true if and only if the value is unchanged
|
||||
// in a virtual table update operatiom.
|
||||
//
|
||||
// https://sqlite.org/c3ref/value_blob.html
|
||||
func (v Value) NoChange() bool {
|
||||
r := v.c.call("sqlite3_value_nochange", v.protected())
|
||||
return r != 0
|
||||
}
|
||||
|
||||
// InFirst returns the first element
|
||||
// on the right-hand side of an IN constraint.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vtab_in_first.html
|
||||
func (v Value) InFirst() (Value, error) {
|
||||
defer v.c.arena.mark()()
|
||||
valPtr := v.c.arena.new(ptrlen)
|
||||
r := v.c.call("sqlite3_vtab_in_first", uint64(v.handle), uint64(valPtr))
|
||||
if err := v.c.error(r); err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
return Value{
|
||||
c: v.c,
|
||||
handle: util.ReadUint32(v.c.mod, valPtr),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// InNext returns the next element
|
||||
// on the right-hand side of an IN constraint.
|
||||
//
|
||||
// https://sqlite.org/c3ref/vtab_in_first.html
|
||||
func (v Value) InNext() (Value, error) {
|
||||
defer v.c.arena.mark()()
|
||||
valPtr := v.c.arena.new(ptrlen)
|
||||
r := v.c.call("sqlite3_vtab_in_next", uint64(v.handle), uint64(valPtr))
|
||||
if err := v.c.error(r); err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
return Value{
|
||||
c: v.c,
|
||||
handle: util.ReadUint32(v.c.mod, valPtr),
|
||||
}, nil
|
||||
}
|
||||
+75
-5
@@ -1,9 +1,79 @@
|
||||
# Go SQLite VFS API
|
||||
|
||||
This package implements the SQLite [OS Interface](https://www.sqlite.org/vfs.html) (aka VFS).
|
||||
This package implements the SQLite [OS Interface](https://sqlite.org/vfs.html) (aka VFS).
|
||||
|
||||
It replaces the default VFS with a pure Go implementation,
|
||||
that is tested on Linux, macOS and Windows,
|
||||
but which should also work on illumos and the various BSDs.
|
||||
It replaces the default SQLite VFS with a **pure Go** implementation.
|
||||
|
||||
It also exposes interfaces that should allow you to implement your own custom VFSes.
|
||||
It also exposes [interfaces](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#VFS)
|
||||
that should allow you to implement your own custom VFSes.
|
||||
|
||||
Since it is a from scratch reimplementation,
|
||||
there are naturally some ways it deviates from the original.
|
||||
|
||||
The main differences are [file locking](#file-locking) and [WAL mode](write-ahead-logging) support.
|
||||
|
||||
### File Locking
|
||||
|
||||
POSIX advisory locks, which SQLite uses on Unix, are
|
||||
[broken by design](https://github.com/sqlite/sqlite/blob/b74eb0/src/os_unix.c#L1073-L1161).
|
||||
|
||||
On Linux and macOS, this module uses
|
||||
[OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
|
||||
to synchronize access to database files.
|
||||
OFD locks are fully compatible with POSIX advisory locks.
|
||||
|
||||
On BSD Unixes, this module uses
|
||||
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2).
|
||||
On BSD, these locks are fully compatible with POSIX advisory locks.
|
||||
However, concurrency is reduced with BSD locks
|
||||
(`BEGIN IMMEDIATE` behaves the same as `BEGIN EXCLUSIVE`).
|
||||
|
||||
On Windows, this module uses `LockFileEx` and `UnlockFileEx`,
|
||||
like SQLite.
|
||||
|
||||
On all other platforms, file locking is not supported, and you must use
|
||||
[`nolock=1`](https://sqlite.org/uri.html#urinolock)
|
||||
(or [`immutable=1`](https://sqlite.org/uri.html#uriimmutable))
|
||||
to open database files.\
|
||||
To use the [`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||
with `nolock=1` you must disable connection pooling by calling
|
||||
[`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns).
|
||||
|
||||
You can use [`vfs.SupportsFileLocking`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsFileLocking)
|
||||
to check if your platform supports file locking.
|
||||
|
||||
### Write-Ahead Logging
|
||||
|
||||
On 64-bit Linux and macOS, this module uses `mmap` to implement
|
||||
[shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index),
|
||||
like SQLite.
|
||||
|
||||
To allow `mmap` to work, each connection needs to reserve up to 4GB of address space.\
|
||||
To limit the amount of address space each connection needs,
|
||||
use [`WithMemoryLimitPages`](../tests/testcfg/testcfg.go).
|
||||
|
||||
On Windows and BSD, [WAL](https://sqlite.org/wal.html) support is
|
||||
[limited](https://sqlite.org/wal.html#noshm).
|
||||
`EXCLUSIVE` locking mode can be set to create, read, and write WAL databases.\
|
||||
To use `EXCLUSIVE` locking mode with the
|
||||
[`database/sql`](https://pkg.go.dev/database/sql) driver
|
||||
you must disable connection pooling by calling
|
||||
[`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns).
|
||||
|
||||
On all other platforms, where file locking is not supported, WAL mode does not work.
|
||||
|
||||
You can use [`vfs.SupportsSharedMemory`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsSharedMemory)
|
||||
to check if your platform supports shared memory.
|
||||
|
||||
### Batch-Atomic Write
|
||||
|
||||
On 64-bit Linux, this module supports [batch-atomic writes](https://sqlite.org/cgi/src/technote/714)
|
||||
with the F2FS filesystem.
|
||||
|
||||
### Build tags
|
||||
|
||||
The VFS can be customized with a few build tags:
|
||||
- `sqlite3_flock` forces the use of BSD locks; it can be used on macOS to test the BSD locking implementation.
|
||||
- `sqlite3_nosys` prevents importing [`x/sys`](https://pkg.go.dev/golang.org/x/sys);
|
||||
disables locking _and_ shared memory on all platforms.
|
||||
- `sqlite3_noshm` disables shared memory on all platforms.
|
||||
+87
-15
@@ -1,13 +1,18 @@
|
||||
// Package vfs wraps the C SQLite VFS API.
|
||||
package vfs
|
||||
|
||||
import "net/url"
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// A VFS defines the interface between the SQLite core and the underlying operating system.
|
||||
//
|
||||
// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/vfs.html
|
||||
// https://sqlite.org/c3ref/vfs.html
|
||||
type VFS interface {
|
||||
Open(name string, flags OpenFlag) (File, OpenFlag, error)
|
||||
Delete(name string, syncDir bool) error
|
||||
@@ -15,13 +20,13 @@ type VFS interface {
|
||||
FullPathname(name string) (string, error)
|
||||
}
|
||||
|
||||
// VFSParams extends VFS to with the ability to handle URI parameters
|
||||
// through the OpenParams method.
|
||||
// VFSFilename extends VFS with the ability to use Filename
|
||||
// objects for opening files.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/uri_boolean.html
|
||||
type VFSParams interface {
|
||||
// https://sqlite.org/c3ref/filename.html
|
||||
type VFSFilename interface {
|
||||
VFS
|
||||
OpenParams(name string, flags OpenFlag, params url.Values) (File, OpenFlag, error)
|
||||
OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error)
|
||||
}
|
||||
|
||||
// A File represents an open file in the OS interface layer.
|
||||
@@ -29,7 +34,7 @@ type VFSParams interface {
|
||||
// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite.
|
||||
// In particular, sqlite3.BUSY is necessary to correctly implement lock methods.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/io_methods.html
|
||||
// https://sqlite.org/c3ref/io_methods.html
|
||||
type File interface {
|
||||
Close() error
|
||||
ReadAt(p []byte, off int64) (n int, err error)
|
||||
@@ -47,16 +52,25 @@ type File interface {
|
||||
// FileLockState extends File to implement the
|
||||
// SQLITE_FCNTL_LOCKSTATE file control opcode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntllockstate
|
||||
type FileLockState interface {
|
||||
File
|
||||
LockState() LockLevel
|
||||
}
|
||||
|
||||
// FileChunkSize extends File to implement the
|
||||
// SQLITE_FCNTL_CHUNK_SIZE file control opcode.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlchunksize
|
||||
type FileChunkSize interface {
|
||||
File
|
||||
ChunkSize(size int)
|
||||
}
|
||||
|
||||
// FileSizeHint extends File to implement the
|
||||
// SQLITE_FCNTL_SIZE_HINT file control opcode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlsizehint
|
||||
type FileSizeHint interface {
|
||||
File
|
||||
SizeHint(size int64) error
|
||||
@@ -65,26 +79,45 @@ type FileSizeHint interface {
|
||||
// FileHasMoved extends File to implement the
|
||||
// SQLITE_FCNTL_HAS_MOVED file control opcode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlhasmoved
|
||||
type FileHasMoved interface {
|
||||
File
|
||||
HasMoved() (bool, error)
|
||||
}
|
||||
|
||||
// FileOverwrite extends File to implement the
|
||||
// SQLITE_FCNTL_OVERWRITE file control opcode.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntloverwrite
|
||||
type FileOverwrite interface {
|
||||
File
|
||||
Overwrite() error
|
||||
}
|
||||
|
||||
// FilePersistentWAL extends File to implement the
|
||||
// SQLITE_FCNTL_PERSIST_WAL file control opcode.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpersistwal
|
||||
type FilePersistentWAL interface {
|
||||
File
|
||||
PersistentWAL() bool
|
||||
SetPersistentWAL(bool)
|
||||
}
|
||||
|
||||
// FilePowersafeOverwrite extends File to implement the
|
||||
// SQLITE_FCNTL_POWERSAFE_OVERWRITE file control opcode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpowersafeoverwrite
|
||||
type FilePowersafeOverwrite interface {
|
||||
File
|
||||
PowersafeOverwrite() bool
|
||||
SetPowersafeOverwrite(bool)
|
||||
}
|
||||
|
||||
// FilePowersafeOverwrite extends File to implement the
|
||||
// FileCommitPhaseTwo extends File to implement the
|
||||
// SQLITE_FCNTL_COMMIT_PHASETWO file control opcode.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlcommitphasetwo
|
||||
type FileCommitPhaseTwo interface {
|
||||
File
|
||||
CommitPhaseTwo() error
|
||||
@@ -94,10 +127,49 @@ type FileCommitPhaseTwo interface {
|
||||
// SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE
|
||||
// and SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE file control opcodes.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlbeginatomicwrite
|
||||
type FileBatchAtomicWrite interface {
|
||||
File
|
||||
BeginAtomicWrite() error
|
||||
CommitAtomicWrite() error
|
||||
RollbackAtomicWrite() error
|
||||
}
|
||||
|
||||
// FilePragma extends File to implement the
|
||||
// SQLITE_FCNTL_PRAGMA file control opcode.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpragma
|
||||
type FilePragma interface {
|
||||
File
|
||||
Pragma(name, value string) (string, error)
|
||||
}
|
||||
|
||||
// FileCheckpoint extends File to implement the
|
||||
// SQLITE_FCNTL_CKPT_START and SQLITE_FCNTL_CKPT_DONE
|
||||
// file control opcodes.
|
||||
//
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlckptstart
|
||||
type FileCheckpoint interface {
|
||||
File
|
||||
CheckpointDone() error
|
||||
CheckpointStart() error
|
||||
}
|
||||
|
||||
// FileSharedMemory extends File to possibly implement
|
||||
// shared-memory for the WAL-index.
|
||||
// The same shared-memory instance must be returned
|
||||
// for the entire life of the file.
|
||||
// It's OK for SharedMemory to return nil.
|
||||
type FileSharedMemory interface {
|
||||
File
|
||||
SharedMemory() SharedMemory
|
||||
}
|
||||
|
||||
// SharedMemory is a shared-memory WAL-index implementation.
|
||||
// Use [NewSharedMemory] to create a shared-memory.
|
||||
type SharedMemory interface {
|
||||
shmMap(context.Context, api.Module, int32, int32, bool) (uint32, error)
|
||||
shmLock(int32, int32, _ShmFlag) error
|
||||
shmUnmap(bool)
|
||||
io.Closer
|
||||
}
|
||||
+28
-9
@@ -3,12 +3,15 @@ package vfs
|
||||
import "github.com/ncruces/go-sqlite3/internal/util"
|
||||
|
||||
const (
|
||||
_MAX_STRING = 512 // Used for short strings: names, error messages…
|
||||
_MAX_PATHNAME = 512
|
||||
_MAX_NAME = 1e6 // Self-imposed limit for most NUL terminated strings.
|
||||
_MAX_SQL_LENGTH = 1e9
|
||||
_MAX_PATHNAME = 1024
|
||||
_DEFAULT_SECTOR_SIZE = 4096
|
||||
|
||||
ptrlen = 4
|
||||
)
|
||||
|
||||
// https://www.sqlite.org/rescode.html
|
||||
// https://sqlite.org/rescode.html
|
||||
type _ErrorCode uint32
|
||||
|
||||
func (e _ErrorCode) Error() string {
|
||||
@@ -17,6 +20,7 @@ func (e _ErrorCode) Error() string {
|
||||
|
||||
const (
|
||||
_OK _ErrorCode = util.OK
|
||||
_ERROR _ErrorCode = util.ERROR
|
||||
_PERM _ErrorCode = util.PERM
|
||||
_BUSY _ErrorCode = util.BUSY
|
||||
_READONLY _ErrorCode = util.READONLY
|
||||
@@ -37,6 +41,10 @@ const (
|
||||
_IOERR_CHECKRESERVEDLOCK _ErrorCode = util.IOERR_CHECKRESERVEDLOCK
|
||||
_IOERR_LOCK _ErrorCode = util.IOERR_LOCK
|
||||
_IOERR_CLOSE _ErrorCode = util.IOERR_CLOSE
|
||||
_IOERR_SHMOPEN _ErrorCode = util.IOERR_SHMOPEN
|
||||
_IOERR_SHMSIZE _ErrorCode = util.IOERR_SHMSIZE
|
||||
_IOERR_SHMLOCK _ErrorCode = util.IOERR_SHMLOCK
|
||||
_IOERR_SHMMAP _ErrorCode = util.IOERR_SHMMAP
|
||||
_IOERR_SEEK _ErrorCode = util.IOERR_SEEK
|
||||
_IOERR_DELETE_NOENT _ErrorCode = util.IOERR_DELETE_NOENT
|
||||
_IOERR_BEGIN_ATOMIC _ErrorCode = util.IOERR_BEGIN_ATOMIC
|
||||
@@ -44,12 +52,13 @@ const (
|
||||
_IOERR_ROLLBACK_ATOMIC _ErrorCode = util.IOERR_ROLLBACK_ATOMIC
|
||||
_CANTOPEN_FULLPATH _ErrorCode = util.CANTOPEN_FULLPATH
|
||||
_CANTOPEN_ISDIR _ErrorCode = util.CANTOPEN_ISDIR
|
||||
_READONLY_CANTINIT _ErrorCode = util.READONLY_CANTINIT
|
||||
_OK_SYMLINK _ErrorCode = util.OK_SYMLINK
|
||||
)
|
||||
|
||||
// OpenFlag is a flag for the [VFS] Open method.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_open_autoproxy.html
|
||||
// https://sqlite.org/c3ref/c_open_autoproxy.html
|
||||
type OpenFlag uint32
|
||||
|
||||
const (
|
||||
@@ -78,7 +87,7 @@ const (
|
||||
|
||||
// AccessFlag is a flag for the [VFS] Access method.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_access_exists.html
|
||||
// https://sqlite.org/c3ref/c_access_exists.html
|
||||
type AccessFlag uint32
|
||||
|
||||
const (
|
||||
@@ -89,7 +98,7 @@ const (
|
||||
|
||||
// SyncFlag is a flag for the [File] Sync method.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_sync_dataonly.html
|
||||
// https://sqlite.org/c3ref/c_sync_dataonly.html
|
||||
type SyncFlag uint32
|
||||
|
||||
const (
|
||||
@@ -100,7 +109,7 @@ const (
|
||||
|
||||
// LockLevel is a value used with [File] Lock and Unlock methods.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_lock_exclusive.html
|
||||
// https://sqlite.org/c3ref/c_lock_exclusive.html
|
||||
type LockLevel uint32
|
||||
|
||||
const (
|
||||
@@ -146,7 +155,7 @@ const (
|
||||
|
||||
// DeviceCharacteristic is a flag retuned by the [File] DeviceCharacteristics method.
|
||||
//
|
||||
// https://www.sqlite.org/c3ref/c_iocap_atomic.html
|
||||
// https://sqlite.org/c3ref/c_iocap_atomic.html
|
||||
type DeviceCharacteristic uint32
|
||||
|
||||
const (
|
||||
@@ -167,7 +176,7 @@ const (
|
||||
IOCAP_BATCH_ATOMIC DeviceCharacteristic = 0x00004000
|
||||
)
|
||||
|
||||
// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
|
||||
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
|
||||
type _FcntlOpcode uint32
|
||||
|
||||
const (
|
||||
@@ -213,3 +222,13 @@ const (
|
||||
_FCNTL_CKSM_FILE _FcntlOpcode = 41
|
||||
_FCNTL_RESET_CACHE _FcntlOpcode = 42
|
||||
)
|
||||
|
||||
// https://sqlite.org/c3ref/c_shm_exclusive.html
|
||||
type _ShmFlag uint32
|
||||
|
||||
const (
|
||||
_SHM_UNLOCK _ShmFlag = 1
|
||||
_SHM_LOCK _ShmFlag = 2
|
||||
_SHM_SHARED _ShmFlag = 4
|
||||
_SHM_EXCLUSIVE _ShmFlag = 8
|
||||
)
|
||||
+32
-16
@@ -4,12 +4,12 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/util/osutil"
|
||||
)
|
||||
|
||||
type vfsOS struct{}
|
||||
@@ -69,10 +69,10 @@ func (vfsOS) Access(name string, flags AccessFlag) (bool, error) {
|
||||
}
|
||||
|
||||
func (vfsOS) Open(name string, flags OpenFlag) (File, OpenFlag, error) {
|
||||
return vfsOS{}.OpenParams(name, flags, nil)
|
||||
return nil, 0, _CANTOPEN
|
||||
}
|
||||
|
||||
func (vfsOS) OpenParams(name string, flags OpenFlag, params url.Values) (File, OpenFlag, error) {
|
||||
func (vfsOS) OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error) {
|
||||
var oflags int
|
||||
if flags&OPEN_EXCLUSIVE != 0 {
|
||||
oflags |= os.O_EXCL
|
||||
@@ -89,10 +89,10 @@ func (vfsOS) OpenParams(name string, flags OpenFlag, params url.Values) (File, O
|
||||
|
||||
var err error
|
||||
var f *os.File
|
||||
if name == "" {
|
||||
if name == nil {
|
||||
f, err = os.CreateTemp("", "*.db")
|
||||
} else {
|
||||
f, err = osOpenFile(name, oflags, 0666)
|
||||
f, err = osutil.OpenFile(name.String(), oflags, 0666)
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EISDIR) {
|
||||
@@ -101,7 +101,7 @@ func (vfsOS) OpenParams(name string, flags OpenFlag, params url.Values) (File, O
|
||||
return nil, flags, err
|
||||
}
|
||||
|
||||
if modeof := params.Get("modeof"); modeof != "" {
|
||||
if modeof := name.URIParameter("modeof"); modeof != "" {
|
||||
if err = osSetMode(f, modeof); err != nil {
|
||||
f.Close()
|
||||
return nil, flags, _IOERR_FSTAT
|
||||
@@ -118,17 +118,19 @@ func (vfsOS) OpenParams(name string, flags OpenFlag, params url.Values) (File, O
|
||||
syncDir: runtime.GOOS != "windows" &&
|
||||
flags&(OPEN_CREATE) != 0 &&
|
||||
flags&(OPEN_MAIN_JOURNAL|OPEN_SUPER_JOURNAL|OPEN_WAL) != 0,
|
||||
shm: NewSharedMemory(name.String()+"-shm", flags),
|
||||
}
|
||||
return &file, flags, nil
|
||||
}
|
||||
|
||||
type vfsFile struct {
|
||||
*os.File
|
||||
lockTimeout time.Duration
|
||||
lock LockLevel
|
||||
psow bool
|
||||
syncDir bool
|
||||
readOnly bool
|
||||
shm SharedMemory
|
||||
lock LockLevel
|
||||
readOnly bool
|
||||
keepWAL bool
|
||||
syncDir bool
|
||||
psow bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -136,9 +138,17 @@ var (
|
||||
_ FileLockState = &vfsFile{}
|
||||
_ FileHasMoved = &vfsFile{}
|
||||
_ FileSizeHint = &vfsFile{}
|
||||
_ FilePersistentWAL = &vfsFile{}
|
||||
_ FilePowersafeOverwrite = &vfsFile{}
|
||||
)
|
||||
|
||||
func (f *vfsFile) Close() error {
|
||||
if f.shm != nil {
|
||||
f.shm.Close()
|
||||
}
|
||||
return f.File.Close()
|
||||
}
|
||||
|
||||
func (f *vfsFile) Sync(flags SyncFlag) error {
|
||||
dataonly := (flags & SYNC_DATAONLY) != 0
|
||||
fullsync := (flags & 0x0f) == SYNC_FULL
|
||||
@@ -166,15 +176,19 @@ func (f *vfsFile) Size() (int64, error) {
|
||||
return f.Seek(0, io.SeekEnd)
|
||||
}
|
||||
|
||||
func (*vfsFile) SectorSize() int {
|
||||
func (f *vfsFile) SectorSize() int {
|
||||
return _DEFAULT_SECTOR_SIZE
|
||||
}
|
||||
|
||||
func (f *vfsFile) DeviceCharacteristics() DeviceCharacteristic {
|
||||
if f.psow {
|
||||
return IOCAP_POWERSAFE_OVERWRITE
|
||||
var res DeviceCharacteristic
|
||||
if osBatchAtomic(f.File) {
|
||||
res |= IOCAP_BATCH_ATOMIC
|
||||
}
|
||||
return 0
|
||||
if f.psow {
|
||||
res |= IOCAP_POWERSAFE_OVERWRITE
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (f *vfsFile) SizeHint(size int64) error {
|
||||
@@ -198,4 +212,6 @@ func (f *vfsFile) HasMoved() (bool, error) {
|
||||
|
||||
func (f *vfsFile) LockState() LockLevel { return f.lock }
|
||||
func (f *vfsFile) PowersafeOverwrite() bool { return f.psow }
|
||||
func (f *vfsFile) PersistentWAL() bool { return f.keepWAL }
|
||||
func (f *vfsFile) SetPowersafeOverwrite(psow bool) { f.psow = psow }
|
||||
func (f *vfsFile) SetPersistentWAL(keepWAL bool) { f.keepWAL = keepWAL }
|
||||
Loaded 100 of 204 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user