Compare commits

...
Author SHA1 Message Date
Timothy Makram Ghatas d60dd6a5a0 Merge branch 'master' into generate_c2profiles 2024-05-20 23:26:37 +02:00
Tim Makram Ghatas 8737bba4c3 enforce unique startsession extensions 2024-05-20 23:25:10 +02:00
Tim Makram Ghatas 97716d2b9e only stager extensions need to be unique 2024-05-20 23:01:38 +02:00
Tim Makram Ghatas 844a6f8d25 check there are sufficient unused extensions in the provided file 2024-05-20 22:58:09 +02:00
Tim Makram Ghatas b6f2c1b4bb fix documentation for generate command 2024-05-20 22:23:56 +02:00
Ronan Kervella 848704eb3c Merge pull request #1695 from BishopFox/0bafdependabot/go_modules/github.com/fatih/color-1.17.0
Bump github.com/fatih/color from 1.16.0 to 1.17.0
2024-05-20 11:01:48 -07:00
dependabot[bot] c3571b001a Bump github.com/fatih/color from 1.16.0 to 1.17.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.16.0 to 1.17.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.16.0...v1.17.0)

---
updated-dependencies:
- dependency-name: github.com/fatih/color
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-05-20 03:37:24 +00:00
Tim Makram Ghatas cf6682dff7 allow generating c2profiles using a file containing one url per line. Fix autocomplete for export c2profile name 2024-05-19 13:18:26 +02:00
Joe 2a1453ee37 Merge pull request #1691 from BishopFox/v1.6.0/certificate-management
View Certificate Information
2024-05-17 12:46:13 -05:00
Raf a9f06fc299 Merge branch 'master' into v1.6.0/certificate-management 2024-05-17 09:39:30 -04:00
Raf 5839c59682 Changing help text for certificates command 2024-05-17 09:35:54 -04:00
Raf 860810c148 Adding filters to certificate command 2024-05-17 09:33:24 -04:00
Ronan Kervella d92f079f97 Merge pull request #1690 from w33ts/patch-1
Fixed command typo in Getting Started.md
2024-05-17 13:19:58 +02:00
w33ts 4315a3fce3 Fixed command typo in Getting Started.md
There is a command that says "systemctrl" instead of "systemctl". Yup, I'm *that* guy now.

Signed-off-by: w33ts <[email protected]>
2024-05-16 21:50:34 -05:00
Joe 5d142acb86 Merge pull request #1681 from BishopFox/1c54dependabot/go_modules/golang.org/x/net-0.25.0
Bump golang.org/x/net from 0.24.0 to 0.25.0
2024-05-16 14:43:36 -05:00
Raf 0ee50c3150 Adding ID to certificates output, adjusting for narrow terms 2024-05-16 13:37:30 -04:00
Joe c0840eb9d6 Merge branch 'master' into 1c54dependabot/go_modules/golang.org/x/net-0.25.0 2024-05-16 10:54:09 -05:00
Joe 8b3e3cd6ab Merge pull request #1682 from BishopFox/5a34dependabot/go_modules/github.com/cheggaaa/pb/v3-3.1.5
Bump github.com/cheggaaa/pb/v3 from 3.1.2 to 3.1.5
2024-05-16 10:53:39 -05:00
Joe ffd2fa6e8b Merge pull request #1686 from BishopFox/fix-install-script
Fixing install script for Arch
2024-05-16 10:53:23 -05:00
Joe 130b5f9706 Merge pull request #1687 from BishopFox/v1.6.0/database-implant-table-fixes
Improving PostgreSQL Support
2024-05-16 10:53:06 -05:00
Raf 549a54cf4b Initial components of the certificates command
This commit adds a command (certificates) to the client that retrieves selected certificate information from the database.
2024-05-16 11:32:34 -04:00
Raf e2d11c40dd Fixed poor declaration of DB models for migration 2024-05-14 11:49:44 -04:00
Raf 9749cd8e58 Merge branch 'master' into v1.6.0/database-implant-table-fixes 2024-05-14 10:42:22 -04:00
Raf 4082378313 Adding nil check to listener deletion to prevent panic 2024-05-14 10:23:42 -04:00
Raf df65d7fe11 More thorough deletion of jobs from the DB when they are killed 2024-05-14 10:22:00 -04:00
Raf 99ce1cfb7a Fixing FK constraint while deleting profiles 2024-05-14 09:07:06 -04:00
Raf 5f2abac8d0 Database model fixes for non-SQLite databases, part 2
Changing implant generation without an associated profile to insert NULL into the database instead of the zero GUID
2024-05-13 15:43:04 -04:00
dependabot[bot] f265214006 Bump github.com/cheggaaa/pb/v3 from 3.1.2 to 3.1.5
Bumps [github.com/cheggaaa/pb/v3](https://github.com/cheggaaa/pb) from 3.1.2 to 3.1.5.
- [Commits](https://github.com/cheggaaa/pb/compare/v3.1.2...v3.1.5)

---
updated-dependencies:
- dependency-name: github.com/cheggaaa/pb/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-05-13 17:14:40 +00:00
dependabot[bot] 23bdc7053d Bump golang.org/x/net from 0.24.0 to 0.25.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.24.0 to 0.25.0.
- [Commits](https://github.com/golang/net/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-05-13 17:14:34 +00:00
Joe 9510a5f0c1 Merge pull request #1685 from BishopFox/47b6dependabot/go_modules/google.golang.org/protobuf-1.34.1
Bump google.golang.org/protobuf from 1.34.0 to 1.34.1
2024-05-13 12:13:32 -05:00
Joe 72ea11790f Merge pull request #1683 from BishopFox/e97ddependabot/go_modules/golang.org/x/crypto-0.23.0
Bump golang.org/x/crypto from 0.22.0 to 0.23.0
2024-05-13 12:13:19 -05:00
Raf d651e0888f Database model fixes for non-SQLite databases, part 1
Changing database migrations to occur in sequence instead of all at once for more accurate error reporting in the log
Changing HostUUID in the models.Host struct to be unique to fix a constraint issue
Changing models.HttpC2Header to contain pointers to ServerConfigIDs and ImplantConfigIDs so that NULL values can be inserted into the database when a type of config does not apply.
2024-05-13 12:55:53 -04:00
Raf f2bb628883 Fixing install script for Arch 2024-05-13 09:15:20 -04:00
dependabot[bot] 8e5ff45306 Bump google.golang.org/protobuf from 1.34.0 to 1.34.1
Bumps google.golang.org/protobuf from 1.34.0 to 1.34.1.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-05-13 08:03:08 +00:00
Ronan Kervella 9dbd5a1c92 Merge branch 'master' into e97ddependabot/go_modules/golang.org/x/crypto-0.23.0 2024-05-13 10:02:18 +02:00
Ronan Kervella 6115702268 Merge pull request #1684 from BishopFox/527ddependabot/go_modules/modernc.org/sqlite-1.29.9
Bump modernc.org/sqlite from 1.29.8 to 1.29.9
2024-05-13 10:01:48 +02:00
dependabot[bot] ed1739c8d2 Bump modernc.org/sqlite from 1.29.8 to 1.29.9
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.29.8 to 1.29.9.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.29.8...v1.29.9)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-05-13 03:08:40 +00:00
dependabot[bot] 88da1fe868 Bump golang.org/x/crypto from 0.22.0 to 0.23.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.22.0 to 0.23.0.
- [Commits](https://github.com/golang/crypto/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-05-13 03:08:17 +00:00
Joe b520eaf6e6 Merge pull request #1679 from BishopFox/v1.6.0/fix-console-hang
Fix #1668
2024-05-10 09:31:13 -05:00
rkervella 635086364e Fix #1668 2024-05-10 11:46:31 +02:00
Ronan Kervella 4bfdb7bcee Merge pull request #1676 from BishopFox/v1.6.0/package-output
New Output Handler for Extensions and Aliases
2024-05-08 09:46:49 +02:00
Ronan Kervella 7e4ba5d3e7 Merge branch 'master' into v1.6.0/package-output 2024-05-07 17:20:47 +02:00
Ronan Kervella c642bfcacb Merge pull request #1675 from BishopFox/xforwardedfor_fix
extract original host ip if forwarded by one or more hosts
2024-05-07 17:20:17 +02:00
Timothy Makram Ghatas a424412edd Merge branch 'master' into xforwardedfor_fix 2024-05-07 15:34:08 +02:00
Raf 4d15774c9b Merge branch 'master' into v1.6.0/package-output 2024-05-07 09:05:14 -04:00
Raf 51c45a48e8 Adding error handling for bad ingestion 2024-05-07 09:04:55 -04:00
Tim Makram Ghatas 94f01c8230 added default case if only one forwarding ip exists 2024-05-07 14:41:51 +02:00
Raf 0307bf16eb Adding a new output handler for extensions and aliases that deserializes JSON output into tables 2024-05-07 08:30:17 -04:00
Tim Makram Ghatas 1a73128163 extract original host ip if forwarded by one or more hosts 2024-05-07 12:31:01 +02:00
Joe eff371f6b7 Merge pull request #1671 from BishopFox/d2cbdependabot/go_modules/github.com/stretchr/testify-1.9.0
Bump github.com/stretchr/testify from 1.8.4 to 1.9.0
2024-05-06 10:37:28 -05:00
Joe 3a48d8480e Merge pull request #1674 from BishopFox/f9f5dependabot/go_modules/golang.org/x/text-0.15.0
Bump golang.org/x/text from 0.14.0 to 0.15.0
2024-05-06 10:37:16 -05:00
Joe 5cd83392e0 Merge pull request #1672 from BishopFox/7227dependabot/go_modules/github.com/glebarez/sqlite-1.11.0
Bump github.com/glebarez/sqlite from 1.10.0 to 1.11.0
2024-05-06 10:37:06 -05:00
Joe fffaca3945 Merge pull request #1670 from BishopFox/7890dependabot/go_modules/github.com/ncruces/go-sqlite3-0.15.0
Bump github.com/ncruces/go-sqlite3 from 0.7.2 to 0.15.0
2024-05-06 10:36:54 -05:00
dependabot[bot] fae797eff0 Bump github.com/ncruces/go-sqlite3 from 0.7.2 to 0.15.0
Bumps [github.com/ncruces/go-sqlite3](https://github.com/ncruces/go-sqlite3) from 0.7.2 to 0.15.0.
- [Release notes](https://github.com/ncruces/go-sqlite3/releases)
- [Commits](https://github.com/ncruces/go-sqlite3/compare/v0.7.2...v0.15.0)

---
updated-dependencies:
- dependency-name: github.com/ncruces/go-sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-05-06 08:14:52 +00:00
dependabot[bot] 64a7095f5f Bump github.com/stretchr/testify from 1.8.4 to 1.9.0
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.4 to 1.9.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.4...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-05-06 08:13:52 +00:00
dependabot[bot] 82909ad1d9 Bump github.com/glebarez/sqlite from 1.10.0 to 1.11.0
Bumps [github.com/glebarez/sqlite](https://github.com/glebarez/sqlite) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/glebarez/sqlite/releases)
- [Commits](https://github.com/glebarez/sqlite/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/glebarez/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-05-06 08:13:37 +00:00
Ronan Kervella e2e9bc18dc Merge branch 'master' into f9f5dependabot/go_modules/golang.org/x/text-0.15.0 2024-05-06 01:12:43 -07:00
Ronan Kervella 5bed9d4e90 Merge pull request #1673 from BishopFox/c6e8dependabot/go_modules/github.com/jedib0t/go-pretty/v6-6.5.9
Bump github.com/jedib0t/go-pretty/v6 from 6.5.8 to 6.5.9
2024-05-06 01:12:20 -07:00
dependabot[bot] 60e87f9e08 Bump golang.org/x/text from 0.14.0 to 0.15.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.14.0 to 0.15.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-05-06 03:45:35 +00:00
dependabot[bot] d4c37f45d2 Bump github.com/jedib0t/go-pretty/v6 from 6.5.8 to 6.5.9
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.5.8 to 6.5.9.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.5.8...v6.5.9)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-05-06 03:45:25 +00:00
Joe 79d22bafbf Merge pull request #1669 from ngn13/wg-config-fix
fix: add missing newline
2024-05-05 10:56:55 -05:00
ngn 6396a64878 add missing newline
Signed-off-by: ngn <[email protected]>
2024-05-04 23:41:34 +00:00
Joe 335797e661 Merge pull request #1667 from BishopFox/docs/community-guides
Docs/community guides
2024-05-03 21:13:39 -05:00
moloch-- d767bd019c Add youtube embed component 2024-05-03 19:13:02 -07:00
moloch-- 590f9c3f96 Add youtube embed component 2024-05-03 18:59:42 -07:00
Joe cff899aa49 Merge pull request #1646 from testwill/close_resource
fix: close resource
2024-05-03 12:17:46 -05:00
Joe ca2dc9d267 Merge pull request #1665 from BishopFox/c2profile_update_bugfix
C2profile update bugfix
2024-05-03 12:17:19 -05:00
Ronan Kervella afdd77c642 Merge branch 'master' into close_resource 2024-05-03 09:21:54 -07:00
Ronan Kervella 8a5970c3da Merge branch 'master' into c2profile_update_bugfix 2024-05-03 09:21:40 -07:00
Ronan Kervella 2eb2ac8121 Merge pull request #1659 from C-Sto/bugfix-extensions
fix extension registration, run, and load flows
2024-05-03 09:21:29 -07:00
Ronan Kervella daf7d404b1 Merge branch 'master' into c2profile_update_bugfix 2024-05-03 09:21:23 -07:00
Ronan Kervella 3b308920f4 Merge branch 'master' into close_resource 2024-05-03 08:34:23 -07:00
Ronan Kervella c73b183f74 Merge branch 'master' into bugfix-extensions 2024-05-03 08:32:44 -07:00
Ronan Kervella 7564312f5e Merge pull request #1666 from BishopFox/v1.6.0/fix-alias-hostproc
Fix Default Linux Host Process for Aliases
2024-05-03 08:32:22 -07:00
Raf 6134b78afb Fix default linux host process for aliases 2024-05-03 08:24:32 -04:00
Ronan Kervella 3db9946608 Merge branch 'master' into bugfix-extensions 2024-05-02 14:11:02 -07:00
Joe ef09058498 Merge pull request #1664 from BishopFox/v1.6.0/ls-rootdir
Adding root directory entry to `ls` command
2024-05-02 16:06:46 -05:00
Tim Makram Ghatas 331faeb8ef fix commandline paramameters for profile staging 2024-05-02 20:30:33 +02:00
Tim Makram Ghatas 4ae195e3eb fix c2 profile update issue with headers, cookies and segments 2024-05-02 19:09:46 +02:00
Ronan Kervella f8f8ba5864 Merge branch 'master' into bugfix-extensions 2024-05-02 09:59:25 -07:00
Raf 216e3b9759 Adds an entry in the directory listing returned by ls for the root directory ('.'). 2024-05-02 12:12:32 -04:00
Joe f1bc5074f9 Merge pull request #1663 from BishopFox/docs/compile-from-source
Add to index
2024-05-01 21:40:10 -05:00
Joe 1b5d3436eb Merge pull request #1662 from BishopFox/docs/compile-from-source
Improve compile from source docs
2024-05-01 21:23:20 -05:00
Joe 9397fcb4ae Merge pull request #1661 from BishopFox/fix_stage_encoder
fix merge error for stage encoders
2024-05-01 20:11:35 -05:00
Joe 04c6c3f800 Merge pull request #1658 from C-Sto/bugfix-extensionnoargs
fix: handle noargs case
2024-05-01 20:08:17 -05:00
C-Sto 7842a6c4c6 add init back in, handle nils/zero values more elegantly 2024-05-02 08:49:21 +08:00
Tim Makram Ghatas 6c815a4432 fix merge error for stage encoders 2024-05-02 02:06:54 +02:00
C-Sto 81358e31bb fix extension registration, run, and load flows 2024-05-01 12:31:19 +08:00
C-Sto 7bea772dbd fix: handle noargs case 2024-05-01 12:15:33 +08:00
guangwu 7fe0b295f5 fix: close resource
Signed-off-by: guangwu <[email protected]>
2024-04-25 20:54:30 +08:00
204 changed files with 18499 additions and 8330 deletions

No files matched your search

+19
View File
@@ -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)
}
}
}
+72 -19
View File
@@ -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())
+214 -8
View File
@@ -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
}
+25 -1
View File
@@ -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)
+181
View File
@@ -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())
}
+51
View File
@@ -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,
}
}
+143 -48
View File
@@ -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, " ")
}
+22
View File
@@ -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)
+9
View File
@@ -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
+2
View File
@@ -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
+1 -1
View File
@@ -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
+2
View File
@@ -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)
+1 -1
View File
@@ -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") {
+2
View File
@@ -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.
+254
View File
@@ -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
}
+6
View File
@@ -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
+22
View File
@@ -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>
);
}
+1 -2
View File
@@ -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}
+2 -2
View File
@@ -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
+14 -14
View File
@@ -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
+28 -28
View File
@@ -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=
+30 -6
View File
@@ -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) {
+15 -2
View File
@@ -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
View File
@@ -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,
},
+21
View File
@@ -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;
}
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
File diff suppressed because it is too large. Load diff
+3
View File
@@ -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);
File diff suppressed because it is too large. Load diff
+8 -5
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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
}
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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
+13 -5
View File
@@ -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
View File
@@ -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
+5 -1
View File
@@ -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
+5 -1
View File
@@ -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)
+5
View File
@@ -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
+102
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -4,84 +4,108 @@
[![Go Report](https://goreportcard.com/badge/github.com/ncruces/go-sqlite3)](https://goreportcard.com/report/github.com/ncruces/go-sqlite3)
[![Go Coverage](https://github.com/ncruces/go-sqlite3/wiki/coverage.svg)](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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
Binary file not shown.
+52 -5
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,6 @@
go 1.21
use (
.
./gormlite
)
+8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,2 @@
// Package osutil implements operating system utility functions.
package osutil
+236
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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