From 3924cac8c955a96c5fab88ec13420441dff661a0 Mon Sep 17 00:00:00 2001 From: Wendel Hime <6754291+WendelHime@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:41:28 -0300 Subject: [PATCH] adding water version control (#1427) * feat: adding wasm downloader * fix: update tests package name * fix: generating wasm downloader mock * feat: adding magnet downloader * feat: adding case for using magnet downloader * fix: replacing magnetic references by magnet and also removing magnet skip * fix: downgrade torrent version to 1.53.3 to avoid errors happening at confluence - anacrolix/confluence needs a new tag and we need to update that at lantern/replica and then here at lantern/flashlight * fix: updating the way we're comparing hashes * chore: go mod tidy * feat: using DirectThenFronted proxy for fetching WASM and also update proxy vars * feat: fix test and print error message * feat: give life to the dial monster * feat: release the beast * fix: rename mocks.go to mocks_test.go * fix: pass empty rootCA at this moment * fix: removing hashsum parameter * fix: replacing http client parameter by package var so I can mock it for tests * fix: leaving mkdirtemp first parameter empty and set lantern-water-module dir name * fix: update torrent to latest version * fix: generating more mocks for torrent so we can unit test it without integration * chore: removing test that was fetching random torrent * chore: keeping torrent at v1.53.3 * fix: removing WithHTTPDownloader * fix: adding garmr-ulfr suggestions from http-proxy PR * feat: adding minimal version control for WASM files * feat: using filename/transport as index for mapped wasm files * feat: integrate version control with water wasm and replace direct downloader * chore: sending writer directly instead of creating a temp buffer * chore: sending config dir as parameter to water impl * chore: small refactor for organizing download step * feat: add support for WASM history * feat: update history after loading WASM correctly * chore: go mod tidy * chore: running go generate * fix: renaming versionControl to waterVersionControl * fix: removing VersionControl unused interface * fix: moving version control commit to GetWASM * fix: moving WASMDownloader initialization from version control to water impl and receive downloader for fetching wasm * fix: renaming structs to refer to water * fix: renaming configDir to dir * fix: updating mock test references * fix: removing water_impl responsibility for setting the water version control dir * fix: creating const at package level for outdated WASMs * fix: removing csv dependency and making it simpler * chore: go mod tidy * fix: replace os.IsNotExist * fix: grouping files to be deleted and delete them outside filepath.Walk * fix: creating water dir if it doesn't exist * fix: replacing os.Mkdir by os.MkdirAll --- chained/mocks_test.go | 111 +++++++++--------- chained/proxy.go | 14 ++- chained/torrent_reader_mock_test.go | 17 +-- chained/water_downloader.go | 26 ++--- chained/water_downloader_test.go | 22 ++-- chained/water_https_downloader.go | 2 +- chained/water_https_downloader_test.go | 2 +- chained/water_impl.go | 48 ++++---- chained/water_impl_test.go | 29 +++-- chained/water_magnet_downloader.go | 22 ++-- chained/water_magnet_downloader_test.go | 12 +- chained/water_version_control.go | 142 ++++++++++++++++++++++++ chained/water_version_control_test.go | 123 ++++++++++++++++++++ genconfig/mock_genconfig.go | 2 + go.mod | 8 -- go.sum | 21 ---- 16 files changed, 430 insertions(+), 171 deletions(-) create mode 100644 chained/water_version_control.go create mode 100644 chained/water_version_control_test.go diff --git a/chained/mocks_test.go b/chained/mocks_test.go index b706c0c94..bf26cd6e6 100644 --- a/chained/mocks_test.go +++ b/chained/mocks_test.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/getlantern/flashlight/v7/chained (interfaces: WASMDownloader,TorrentClient,TorrentInfo) +// Source: github.com/getlantern/flashlight/v7/chained (interfaces: waterWASMDownloader,torrentClient,torrentInfo) // // Generated by this command: // -// mockgen -package=chained -destination=mocks_test.go . WASMDownloader,TorrentClient,TorrentInfo +// mockgen -package=chained -destination=mocks_test.go . waterWASMDownloader,torrentClient,torrentInfo // // Package chained is a generated GoMock package. @@ -19,31 +19,32 @@ import ( gomock "go.uber.org/mock/gomock" ) -// MockWASMDownloader is a mock of WASMDownloader interface. -type MockWASMDownloader struct { +// MockwaterWASMDownloader is a mock of waterWASMDownloader interface. +type MockwaterWASMDownloader struct { ctrl *gomock.Controller - recorder *MockWASMDownloaderMockRecorder + recorder *MockwaterWASMDownloaderMockRecorder + isgomock struct{} } -// MockWASMDownloaderMockRecorder is the mock recorder for MockWASMDownloader. -type MockWASMDownloaderMockRecorder struct { - mock *MockWASMDownloader +// MockwaterWASMDownloaderMockRecorder is the mock recorder for MockwaterWASMDownloader. +type MockwaterWASMDownloaderMockRecorder struct { + mock *MockwaterWASMDownloader } -// NewMockWASMDownloader creates a new mock instance. -func NewMockWASMDownloader(ctrl *gomock.Controller) *MockWASMDownloader { - mock := &MockWASMDownloader{ctrl: ctrl} - mock.recorder = &MockWASMDownloaderMockRecorder{mock} +// NewMockwaterWASMDownloader creates a new mock instance. +func NewMockwaterWASMDownloader(ctrl *gomock.Controller) *MockwaterWASMDownloader { + mock := &MockwaterWASMDownloader{ctrl: ctrl} + mock.recorder = &MockwaterWASMDownloaderMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockWASMDownloader) EXPECT() *MockWASMDownloaderMockRecorder { +func (m *MockwaterWASMDownloader) EXPECT() *MockwaterWASMDownloaderMockRecorder { return m.recorder } // Close mocks base method. -func (m *MockWASMDownloader) Close() error { +func (m *MockwaterWASMDownloader) Close() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].(error) @@ -51,13 +52,13 @@ func (m *MockWASMDownloader) Close() error { } // Close indicates an expected call of Close. -func (mr *MockWASMDownloaderMockRecorder) Close() *gomock.Call { +func (mr *MockwaterWASMDownloaderMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockWASMDownloader)(nil).Close)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockwaterWASMDownloader)(nil).Close)) } // DownloadWASM mocks base method. -func (m *MockWASMDownloader) DownloadWASM(arg0 context.Context, arg1 io.Writer) error { +func (m *MockwaterWASMDownloader) DownloadWASM(arg0 context.Context, arg1 io.Writer) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DownloadWASM", arg0, arg1) ret0, _ := ret[0].(error) @@ -65,51 +66,52 @@ func (m *MockWASMDownloader) DownloadWASM(arg0 context.Context, arg1 io.Writer) } // DownloadWASM indicates an expected call of DownloadWASM. -func (mr *MockWASMDownloaderMockRecorder) DownloadWASM(arg0, arg1 any) *gomock.Call { +func (mr *MockwaterWASMDownloaderMockRecorder) DownloadWASM(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadWASM", reflect.TypeOf((*MockWASMDownloader)(nil).DownloadWASM), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadWASM", reflect.TypeOf((*MockwaterWASMDownloader)(nil).DownloadWASM), arg0, arg1) } -// MockTorrentClient is a mock of TorrentClient interface. -type MockTorrentClient struct { +// MocktorrentClient is a mock of torrentClient interface. +type MocktorrentClient struct { ctrl *gomock.Controller - recorder *MockTorrentClientMockRecorder + recorder *MocktorrentClientMockRecorder + isgomock struct{} } -// MockTorrentClientMockRecorder is the mock recorder for MockTorrentClient. -type MockTorrentClientMockRecorder struct { - mock *MockTorrentClient +// MocktorrentClientMockRecorder is the mock recorder for MocktorrentClient. +type MocktorrentClientMockRecorder struct { + mock *MocktorrentClient } -// NewMockTorrentClient creates a new mock instance. -func NewMockTorrentClient(ctrl *gomock.Controller) *MockTorrentClient { - mock := &MockTorrentClient{ctrl: ctrl} - mock.recorder = &MockTorrentClientMockRecorder{mock} +// NewMocktorrentClient creates a new mock instance. +func NewMocktorrentClient(ctrl *gomock.Controller) *MocktorrentClient { + mock := &MocktorrentClient{ctrl: ctrl} + mock.recorder = &MocktorrentClientMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockTorrentClient) EXPECT() *MockTorrentClientMockRecorder { +func (m *MocktorrentClient) EXPECT() *MocktorrentClientMockRecorder { return m.recorder } // AddMagnet mocks base method. -func (m *MockTorrentClient) AddMagnet(arg0 string) (TorrentInfo, error) { +func (m *MocktorrentClient) AddMagnet(arg0 string) (torrentInfo, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddMagnet", arg0) - ret0, _ := ret[0].(TorrentInfo) + ret0, _ := ret[0].(torrentInfo) ret1, _ := ret[1].(error) return ret0, ret1 } // AddMagnet indicates an expected call of AddMagnet. -func (mr *MockTorrentClientMockRecorder) AddMagnet(arg0 any) *gomock.Call { +func (mr *MocktorrentClientMockRecorder) AddMagnet(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMagnet", reflect.TypeOf((*MockTorrentClient)(nil).AddMagnet), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMagnet", reflect.TypeOf((*MocktorrentClient)(nil).AddMagnet), arg0) } // Close mocks base method. -func (m *MockTorrentClient) Close() []error { +func (m *MocktorrentClient) Close() []error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Close") ret0, _ := ret[0].([]error) @@ -117,36 +119,37 @@ func (m *MockTorrentClient) Close() []error { } // Close indicates an expected call of Close. -func (mr *MockTorrentClientMockRecorder) Close() *gomock.Call { +func (mr *MocktorrentClientMockRecorder) Close() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockTorrentClient)(nil).Close)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MocktorrentClient)(nil).Close)) } -// MockTorrentInfo is a mock of TorrentInfo interface. -type MockTorrentInfo struct { +// MocktorrentInfo is a mock of torrentInfo interface. +type MocktorrentInfo struct { ctrl *gomock.Controller - recorder *MockTorrentInfoMockRecorder + recorder *MocktorrentInfoMockRecorder + isgomock struct{} } -// MockTorrentInfoMockRecorder is the mock recorder for MockTorrentInfo. -type MockTorrentInfoMockRecorder struct { - mock *MockTorrentInfo +// MocktorrentInfoMockRecorder is the mock recorder for MocktorrentInfo. +type MocktorrentInfoMockRecorder struct { + mock *MocktorrentInfo } -// NewMockTorrentInfo creates a new mock instance. -func NewMockTorrentInfo(ctrl *gomock.Controller) *MockTorrentInfo { - mock := &MockTorrentInfo{ctrl: ctrl} - mock.recorder = &MockTorrentInfoMockRecorder{mock} +// NewMocktorrentInfo creates a new mock instance. +func NewMocktorrentInfo(ctrl *gomock.Controller) *MocktorrentInfo { + mock := &MocktorrentInfo{ctrl: ctrl} + mock.recorder = &MocktorrentInfoMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockTorrentInfo) EXPECT() *MockTorrentInfoMockRecorder { +func (m *MocktorrentInfo) EXPECT() *MocktorrentInfoMockRecorder { return m.recorder } // GotInfo mocks base method. -func (m *MockTorrentInfo) GotInfo() events.Done { +func (m *MocktorrentInfo) GotInfo() events.Done { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GotInfo") ret0, _ := ret[0].(events.Done) @@ -154,13 +157,13 @@ func (m *MockTorrentInfo) GotInfo() events.Done { } // GotInfo indicates an expected call of GotInfo. -func (mr *MockTorrentInfoMockRecorder) GotInfo() *gomock.Call { +func (mr *MocktorrentInfoMockRecorder) GotInfo() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GotInfo", reflect.TypeOf((*MockTorrentInfo)(nil).GotInfo)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GotInfo", reflect.TypeOf((*MocktorrentInfo)(nil).GotInfo)) } // NewReader mocks base method. -func (m *MockTorrentInfo) NewReader() torrent.Reader { +func (m *MocktorrentInfo) NewReader() torrent.Reader { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NewReader") ret0, _ := ret[0].(torrent.Reader) @@ -168,7 +171,7 @@ func (m *MockTorrentInfo) NewReader() torrent.Reader { } // NewReader indicates an expected call of NewReader. -func (mr *MockTorrentInfoMockRecorder) NewReader() *gomock.Call { +func (mr *MocktorrentInfoMockRecorder) NewReader() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewReader", reflect.TypeOf((*MockTorrentInfo)(nil).NewReader)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewReader", reflect.TypeOf((*MocktorrentInfo)(nil).NewReader)) } diff --git a/chained/proxy.go b/chained/proxy.go index 1bd8949fd..aed718410 100644 --- a/chained/proxy.go +++ b/chained/proxy.go @@ -7,6 +7,8 @@ import ( "math/rand" "net" "net/http" + "os" + "path/filepath" "strconv" "strings" "sync" @@ -18,7 +20,7 @@ import ( "github.com/getlantern/common/config" "github.com/getlantern/ema" - "github.com/getlantern/errors" + lerrors "github.com/getlantern/errors" "github.com/getlantern/mtime" "github.com/getlantern/netx" @@ -151,7 +153,7 @@ func extractParams(s *config.ProxyConfig) (addr, transport, network string, err forceProxy(s) } if s.Addr == "" { - err = errors.New("Empty addr") + err = lerrors.New("Empty addr") return } addr = s.Addr @@ -212,9 +214,13 @@ func createImpl(configDir, name, addr, transport string, s *config.ProxyConfig, case "algeneva": impl, err = newAlgenevaImpl(addr, s, reportDialCore) case "water": - impl, err = newWaterImpl(addr, s, reportDialCore) + waterDir := filepath.Join(configDir, "water") + if err := os.MkdirAll(waterDir, 0755); err != nil { + return nil, fmt.Errorf("failed to create water directory: %w", err) + } + impl, err = newWaterImpl(waterDir, addr, s, reportDialCore) default: - err = errors.New("Unknown transport: %v", transport).With("addr", addr).With("plugabble-transport", transport) + err = lerrors.New("Unknown transport: %v", transport).With("addr", addr).With("plugabble-transport", transport) } if err != nil { return nil, err diff --git a/chained/torrent_reader_mock_test.go b/chained/torrent_reader_mock_test.go index 3a60076c1..d7fdb4194 100644 --- a/chained/torrent_reader_mock_test.go +++ b/chained/torrent_reader_mock_test.go @@ -21,6 +21,7 @@ import ( type MockReader struct { ctrl *gomock.Controller recorder *MockReaderMockRecorder + isgomock struct{} } // MockReaderMockRecorder is the mock recorder for MockReader. @@ -55,18 +56,18 @@ func (mr *MockReaderMockRecorder) Close() *gomock.Call { } // Read mocks base method. -func (m *MockReader) Read(arg0 []byte) (int, error) { +func (m *MockReader) Read(p []byte) (int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Read", arg0) + ret := m.ctrl.Call(m, "Read", p) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // Read indicates an expected call of Read. -func (mr *MockReaderMockRecorder) Read(arg0 any) *gomock.Call { +func (mr *MockReaderMockRecorder) Read(p any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockReader)(nil).Read), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockReader)(nil).Read), p) } // ReadContext mocks base method. @@ -85,18 +86,18 @@ func (mr *MockReaderMockRecorder) ReadContext(arg0, arg1 any) *gomock.Call { } // Seek mocks base method. -func (m *MockReader) Seek(arg0 int64, arg1 int) (int64, error) { +func (m *MockReader) Seek(offset int64, whence int) (int64, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Seek", arg0, arg1) + ret := m.ctrl.Call(m, "Seek", offset, whence) ret0, _ := ret[0].(int64) ret1, _ := ret[1].(error) return ret0, ret1 } // Seek indicates an expected call of Seek. -func (mr *MockReaderMockRecorder) Seek(arg0, arg1 any) *gomock.Call { +func (mr *MockReaderMockRecorder) Seek(offset, whence any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Seek", reflect.TypeOf((*MockReader)(nil).Seek), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Seek", reflect.TypeOf((*MockReader)(nil).Seek), offset, whence) } // SetReadahead mocks base method. diff --git a/chained/water_downloader.go b/chained/water_downloader.go index dad8ab16b..7cb469b1d 100644 --- a/chained/water_downloader.go +++ b/chained/water_downloader.go @@ -1,7 +1,6 @@ package chained import ( - "bytes" "context" "errors" "io" @@ -9,10 +8,10 @@ import ( "strings" ) -//go:generate mockgen -package=chained -destination=mocks_test.go . WASMDownloader,TorrentClient,TorrentInfo +//go:generate mockgen -package=chained -destination=mocks_test.go . waterWASMDownloader,torrentClient,torrentInfo //go:generate mockgen -package=chained -destination=torrent_reader_mock_test.go github.com/anacrolix/torrent Reader -type WASMDownloader interface { +type waterWASMDownloader interface { DownloadWASM(context.Context, io.Writer) error Close() error } @@ -20,12 +19,12 @@ type WASMDownloader interface { type downloader struct { urls []string httpClient *http.Client - httpDownloader WASMDownloader - magnetDownloader WASMDownloader + httpDownloader waterWASMDownloader + magnetDownloader waterWASMDownloader } -// NewWASMDownloader creates a new WASMDownloader instance. -func NewWASMDownloader(urls []string, httpClient *http.Client) (WASMDownloader, error) { +// newWaterWASMDownloader creates a new WASMDownloader instance. +func newWaterWASMDownloader(urls []string, httpClient *http.Client) (waterWASMDownloader, error) { if len(urls) == 0 { return nil, log.Error("WASM downloader requires URLs to download but received empty list") } @@ -47,14 +46,7 @@ func (d *downloader) Close() error { func (d *downloader) DownloadWASM(ctx context.Context, w io.Writer) error { joinedErrs := errors.New("failed to download WASM from all URLs") for _, url := range d.urls { - tempBuffer := &bytes.Buffer{} - err := d.downloadWASM(ctx, tempBuffer, url) - if err != nil { - joinedErrs = errors.Join(joinedErrs, err) - continue - } - - _, err = tempBuffer.WriteTo(w) + err := d.downloadWASM(ctx, w, url) if err != nil { joinedErrs = errors.Join(joinedErrs, err) continue @@ -71,13 +63,13 @@ func (d *downloader) downloadWASM(ctx context.Context, w io.Writer, url string) switch { case strings.HasPrefix(url, "http://"), strings.HasPrefix(url, "https://"): if d.httpDownloader == nil { - d.httpDownloader = NewHTTPSDownloader(d.httpClient, url) + d.httpDownloader = newWaterHTTPSDownloader(d.httpClient, url) } return d.httpDownloader.DownloadWASM(ctx, w) case strings.HasPrefix(url, "magnet:?"): if d.magnetDownloader == nil { var err error - downloader, err := NewMagnetDownloader(ctx, url) + downloader, err := newWaterMagnetDownloader(ctx, url) if err != nil { return err } diff --git a/chained/water_downloader_test.go b/chained/water_downloader_test.go index 31ce37fc6..737012480 100644 --- a/chained/water_downloader_test.go +++ b/chained/water_downloader_test.go @@ -17,11 +17,11 @@ func TestNewWASMDownloader(t *testing.T) { name string givenURLs []string givenHTTPClient *http.Client - assert func(*testing.T, WASMDownloader, error) + assert func(*testing.T, waterWASMDownloader, error) }{ { name: "it should return an error when providing an empty list of URLs", - assert: func(t *testing.T, d WASMDownloader, err error) { + assert: func(t *testing.T, d waterWASMDownloader, err error) { assert.Error(t, err) assert.Nil(t, d) }, @@ -30,7 +30,7 @@ func TestNewWASMDownloader(t *testing.T) { name: "it should successfully return a wasm downloader", givenURLs: []string{"http://example.com"}, givenHTTPClient: http.DefaultClient, - assert: func(t *testing.T, wDownloader WASMDownloader, err error) { + assert: func(t *testing.T, wDownloader waterWASMDownloader, err error) { assert.NoError(t, err) d := wDownloader.(*downloader) assert.Equal(t, []string{"http://example.com"}, d.urls) @@ -40,7 +40,7 @@ func TestNewWASMDownloader(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - d, err := NewWASMDownloader(tt.givenURLs, tt.givenHTTPClient) + d, err := newWaterWASMDownloader(tt.givenURLs, tt.givenHTTPClient) tt.assert(t, d, err) }) } @@ -55,7 +55,7 @@ func TestDownloadWASM(t *testing.T) { givenHTTPClient *http.Client givenURLs []string givenWriter io.Writer - setupHTTPDownloader func(ctrl *gomock.Controller) WASMDownloader + setupHTTPDownloader func(ctrl *gomock.Controller) waterWASMDownloader assert func(*testing.T, io.Reader, error) }{ { @@ -76,8 +76,8 @@ func TestDownloadWASM(t *testing.T) { givenURLs: []string{ "http://example.com", }, - setupHTTPDownloader: func(ctrl *gomock.Controller) WASMDownloader { - httpDownloader := NewMockWASMDownloader(ctrl) + setupHTTPDownloader: func(ctrl *gomock.Controller) waterWASMDownloader { + httpDownloader := NewMockwaterWASMDownloader(ctrl) httpDownloader.EXPECT().DownloadWASM(ctx, gomock.Any()).Return(assert.AnError) return httpDownloader }, @@ -95,8 +95,8 @@ func TestDownloadWASM(t *testing.T) { givenURLs: []string{ "http://example.com", }, - setupHTTPDownloader: func(ctrl *gomock.Controller) WASMDownloader { - httpDownloader := NewMockWASMDownloader(ctrl) + setupHTTPDownloader: func(ctrl *gomock.Controller) waterWASMDownloader { + httpDownloader := NewMockwaterWASMDownloader(ctrl) httpDownloader.EXPECT().DownloadWASM(ctx, gomock.Any()).DoAndReturn( func(ctx context.Context, w io.Writer) error { _, err := w.Write([]byte(contentMessage)) @@ -114,7 +114,7 @@ func TestDownloadWASM(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var httpDownloader WASMDownloader + var httpDownloader waterWASMDownloader if tt.setupHTTPDownloader != nil { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -122,7 +122,7 @@ func TestDownloadWASM(t *testing.T) { } b := &bytes.Buffer{} - d, err := NewWASMDownloader(tt.givenURLs, tt.givenHTTPClient) + d, err := newWaterWASMDownloader(tt.givenURLs, tt.givenHTTPClient) require.NoError(t, err) if httpDownloader != nil { diff --git a/chained/water_https_downloader.go b/chained/water_https_downloader.go index d3dfae734..0b3f31157 100644 --- a/chained/water_https_downloader.go +++ b/chained/water_https_downloader.go @@ -12,7 +12,7 @@ type httpsDownloader struct { url string } -func NewHTTPSDownloader(client *http.Client, url string) WASMDownloader { +func newWaterHTTPSDownloader(client *http.Client, url string) waterWASMDownloader { return &httpsDownloader{cli: client, url: url} } diff --git a/chained/water_https_downloader_test.go b/chained/water_https_downloader_test.go index cabbf37e9..32514ea26 100644 --- a/chained/water_https_downloader_test.go +++ b/chained/water_https_downloader_test.go @@ -75,7 +75,7 @@ func TestHTTPSDownloadWASM(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b := new(bytes.Buffer) - err := NewHTTPSDownloader(tt.givenHTTPClient, tt.givenURL).DownloadWASM(ctx, b) + err := newWaterHTTPSDownloader(tt.givenHTTPClient, tt.givenURL).DownloadWASM(ctx, b) tt.assert(t, b, err) }) } diff --git a/chained/water_impl.go b/chained/water_impl.go index 2eaf8de5a..4a725ad5c 100644 --- a/chained/water_impl.go +++ b/chained/water_impl.go @@ -1,10 +1,10 @@ package chained import ( - "bytes" "context" "encoding/base64" "fmt" + "io" "log/slog" "net" "net/http" @@ -27,7 +27,7 @@ type waterImpl struct { var httpClient *http.Client -func newWaterImpl(addr string, pc *config.ProxyConfig, reportDialCore reportDialCoreFn) (*waterImpl, error) { +func newWaterImpl(dir, addr string, pc *config.ProxyConfig, reportDialCore reportDialCoreFn) (*waterImpl, error) { var wasm []byte b64WASM := ptSetting(pc, "water_wasm") @@ -39,40 +39,44 @@ func newWaterImpl(addr string, pc *config.ProxyConfig, reportDialCore reportDial } } + ctx := context.Background() wasmAvailableAt := ptSetting(pc, "water_available_at") + transport := ptSetting(pc, "water_transport") if wasm == nil && wasmAvailableAt != "" { - urls := strings.Split(wasmAvailableAt, ",") - b := new(bytes.Buffer) + vc := newWaterVersionControl(dir) cli := httpClient if cli == nil { - downloadTimeout := ptSetting(pc, "water_download_timeout") - timeout := 10 * time.Minute - if downloadTimeout != "" { - var err error - timeout, err = time.ParseDuration(downloadTimeout) - if err != nil { - return nil, fmt.Errorf("failed to parse download timeout: %w", err) - } - } - - cli = proxied.ChainedThenDirectThenFrontedClient(timeout, "") + cli = proxied.ChainedThenDirectThenFrontedClient(1*time.Minute, "") } - d, err := NewWASMDownloader(urls, cli) + downloader, err := newWaterWASMDownloader(strings.Split(wasmAvailableAt, ","), cli) if err != nil { - return nil, log.Errorf("failed to create wasm downloader: %s", err.Error()) + return nil, log.Errorf("failed to create wasm downloader: %w", err) } - if err = d.DownloadWASM(context.Background(), b); err != nil { - return nil, log.Errorf("failed to download wasm: %s", err.Error()) + + r, err := vc.GetWASM(ctx, transport, downloader) + if err != nil { + return nil, log.Errorf("failed to get wasm: %w", err) + } + defer r.Close() + + b, err := io.ReadAll(r) + if err != nil { + return nil, log.Errorf("failed to read wasm: %w", err) + } + + if len(b) == 0 { + return nil, log.Errorf("received empty wasm") } - wasm = b.Bytes() + + wasm = b } cfg := &water.Config{ TransportModuleBin: wasm, - OverrideLogger: slog.New(newLogHandler(log, ptSetting(pc, "water_transport"))), + OverrideLogger: slog.New(newLogHandler(log, transport)), } - dialer, err := water.NewDialerWithContext(context.Background(), cfg) + dialer, err := water.NewDialerWithContext(ctx, cfg) if err != nil { return nil, fmt.Errorf("failed to create dialer: %w", err) } diff --git a/chained/water_impl_test.go b/chained/water_impl_test.go index b6e5163c4..837b15ffc 100644 --- a/chained/water_impl_test.go +++ b/chained/water_impl_test.go @@ -8,6 +8,7 @@ import ( "io" "net" "net/http" + "os" "testing" "github.com/getlantern/common/config" @@ -28,6 +29,7 @@ func (f *roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { func TestNewWaterImpl(t *testing.T) { type params struct { + configDir string raddr string pc *config.ProxyConfig reportDialCore reportDialCoreFn @@ -40,6 +42,10 @@ func TestNewWaterImpl(t *testing.T) { b64WASM := base64.StdEncoding.EncodeToString(wantWASM) + configDir, err := os.MkdirTemp("", "water") + require.NoError(t, err) + defer os.RemoveAll(configDir) + var tests = []struct { name string givenParams params @@ -49,7 +55,8 @@ func TestNewWaterImpl(t *testing.T) { { name: "create new waterImpl with success", givenParams: params{ - raddr: "127.0.0.1", + configDir: configDir, + raddr: "127.0.0.1", pc: &config.ProxyConfig{ PluggableTransportSettings: map[string]string{ "water_wasm": b64WASM, @@ -81,10 +88,12 @@ func TestNewWaterImpl(t *testing.T) { { name: "create new waterImpl by downloading wasm", givenParams: params{ - raddr: "127.0.0.1", + configDir: configDir, + raddr: "127.0.0.1", pc: &config.ProxyConfig{ PluggableTransportSettings: map[string]string{ "water_available_at": "http://example.com/wasm.wasm,http://example2.com/wasm.wasm", + "water_transport": "plain.v1.tinygo.wasm", }, }, reportDialCore: func(op *ops.Op, dialCore func() (net.Conn, error)) (net.Conn, error) { @@ -114,7 +123,7 @@ func TestNewWaterImpl(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.setHTTPClient() - waterImpl, err := newWaterImpl(tt.givenParams.raddr, tt.givenParams.pc, tt.givenParams.reportDialCore) + waterImpl, err := newWaterImpl(tt.givenParams.configDir, tt.givenParams.raddr, tt.givenParams.pc, tt.givenParams.reportDialCore) tt.assert(t, waterImpl, err) }) } @@ -138,10 +147,15 @@ func TestWaterDialServer(t *testing.T) { testOp := ops.Begin("test") defer testOp.End() + configDir, err := os.MkdirTemp("", "water") + require.NoError(t, err) + defer os.RemoveAll(configDir) + var tests = []struct { name string givenOp *ops.Op givenCtx context.Context + givenConfigDir string givenReportDialCore reportDialCoreFn givenDialer water.Dialer givenAddr string @@ -149,9 +163,10 @@ func TestWaterDialServer(t *testing.T) { setHTTPClient func() }{ { - name: "should fail to dial when endpoint is not available", - givenOp: testOp, - givenCtx: ctx, + name: "should fail to dial when endpoint is not available", + givenOp: testOp, + givenCtx: ctx, + givenConfigDir: configDir, givenReportDialCore: func(op *ops.Op, dialCore func() (net.Conn, error)) (net.Conn, error) { assert.Equal(t, testOp, op) assert.NotNil(t, dialCore) @@ -179,7 +194,7 @@ func TestWaterDialServer(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.setHTTPClient() - waterImpl, err := newWaterImpl(tt.givenAddr, pc, tt.givenReportDialCore) + waterImpl, err := newWaterImpl(tt.givenConfigDir, tt.givenAddr, pc, tt.givenReportDialCore) require.NoError(t, err) conn, err := waterImpl.dialServer(tt.givenOp, tt.givenCtx) tt.assert(t, conn, err) diff --git a/chained/water_magnet_downloader.go b/chained/water_magnet_downloader.go index 9bc941671..bdb8531f4 100644 --- a/chained/water_magnet_downloader.go +++ b/chained/water_magnet_downloader.go @@ -11,13 +11,13 @@ import ( "github.com/anacrolix/torrent" ) -type magnetDownloader struct { +type waterMagnetDownloader struct { magnetURL string - client TorrentClient + client torrentClient } -// NewMagnetDownloader creates a new WASMDownloader instance. -func NewMagnetDownloader(ctx context.Context, magnetURL string) (WASMDownloader, error) { +// newWaterMagnetDownloader creates a new WASMDownloader instance. +func newWaterMagnetDownloader(ctx context.Context, magnetURL string) (waterWASMDownloader, error) { cfg, err := generateTorrentClientConfig(ctx) if err != nil { return nil, err @@ -27,13 +27,13 @@ func NewMagnetDownloader(ctx context.Context, magnetURL string) (WASMDownloader, if err != nil { return nil, log.Errorf("failed to create torrent client: %w", err) } - return &magnetDownloader{ + return &waterMagnetDownloader{ magnetURL: magnetURL, client: newTorrentCliWrapper(client), }, nil } -func (d *magnetDownloader) Close() error { +func (d *waterMagnetDownloader) Close() error { errs := d.client.Close() closeErr := errors.New("failed to close torrent client") allErrs := make([]error, len(errs)+1) @@ -55,7 +55,7 @@ func newTorrentCliWrapper(client *torrent.Client) *torrentCliWrapper { } } -func (t *torrentCliWrapper) AddMagnet(magnetURL string) (TorrentInfo, error) { +func (t *torrentCliWrapper) AddMagnet(magnetURL string) (torrentInfo, error) { return t.client.AddMagnet(magnetURL) } @@ -63,12 +63,12 @@ func (t *torrentCliWrapper) Close() []error { return t.client.Close() } -type TorrentClient interface { - AddMagnet(string) (TorrentInfo, error) +type torrentClient interface { + AddMagnet(string) (torrentInfo, error) Close() []error } -type TorrentInfo interface { +type torrentInfo interface { GotInfo() events.Done NewReader() torrent.Reader } @@ -95,7 +95,7 @@ func generateTorrentClientConfig(ctx context.Context) (*torrent.ClientConfig, er } // DownloadWASM downloads the WASM file from the given URL. -func (d *magnetDownloader) DownloadWASM(ctx context.Context, w io.Writer) error { +func (d *waterMagnetDownloader) DownloadWASM(ctx context.Context, w io.Writer) error { t, err := d.client.AddMagnet(d.magnetURL) if err != nil { return log.Errorf("failed to add magnet: %w", err) diff --git a/chained/water_magnet_downloader_test.go b/chained/water_magnet_downloader_test.go index 47229ae61..21e15e81a 100644 --- a/chained/water_magnet_downloader_test.go +++ b/chained/water_magnet_downloader_test.go @@ -17,17 +17,17 @@ func TestMagnetDownloadWASM(t *testing.T) { ctx := context.Background() var tests = []struct { name string - setup func(ctrl *gomock.Controller, downloader *magnetDownloader) WASMDownloader + setup func(ctrl *gomock.Controller, downloader *waterMagnetDownloader) waterWASMDownloader givenCtx context.Context givenMagnetURL string assert func(t *testing.T, r io.Reader, err error) }{ { name: "should return success when download is successful", - setup: func(ctrl *gomock.Controller, downloader *magnetDownloader) WASMDownloader { + setup: func(ctrl *gomock.Controller, downloader *waterMagnetDownloader) waterWASMDownloader { downloader.Close() - torrentClient := NewMockTorrentClient(ctrl) - torrentInfo := NewMockTorrentInfo(ctrl) + torrentClient := NewMocktorrentClient(ctrl) + torrentInfo := NewMocktorrentInfo(ctrl) torrentReader := NewMockReader(ctrl) // torrent reader receive Read calls from io.Copy, it should be able to store the message hello world @@ -60,14 +60,14 @@ func TestMagnetDownloadWASM(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - downloader, err := NewMagnetDownloader(tt.givenCtx, tt.givenMagnetURL) + downloader, err := newWaterMagnetDownloader(tt.givenCtx, tt.givenMagnetURL) require.NoError(t, err) defer downloader.Close() if tt.setup != nil { ctrl := gomock.NewController(t) defer ctrl.Finish() - tt.setup(ctrl, downloader.(*magnetDownloader)) + tt.setup(ctrl, downloader.(*waterMagnetDownloader)) } b := new(bytes.Buffer) diff --git a/chained/water_version_control.go b/chained/water_version_control.go new file mode 100644 index 000000000..e6ba4be41 --- /dev/null +++ b/chained/water_version_control.go @@ -0,0 +1,142 @@ +package chained + +import ( + "context" + "errors" + "io" + "io/fs" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" +) + +type waterVersionControl struct { + dir string +} + +type wasmInfo struct { + lastTimeLoaded time.Time + path string +} + +func newWaterVersionControl(dir string) *waterVersionControl { + return &waterVersionControl{ + dir: dir, + } +} + +// GetWASM returns the WASM file for the given transport +// Remember to Close the io.ReadCloser after using it +func (vc *waterVersionControl) GetWASM(ctx context.Context, transport string, downloader waterWASMDownloader) (io.ReadCloser, error) { + path := filepath.Join(vc.dir, transport+".wasm") + var f io.ReadCloser + f, err := os.Open(path) + if err != nil && !os.IsNotExist(err) { + return nil, log.Errorf("failed to open file %s: %w", path, err) + } + + if errors.Is(err, fs.ErrNotExist) || f == nil { + f, err = vc.downloadWASM(ctx, transport, downloader) + if err != nil { + return nil, log.Errorf("failed to download WASM file: %w", err) + } + } + + if err = vc.markUsed(transport); err != nil { + return nil, log.Errorf("failed to update WASM history: %w", err) + } + + return f, nil +} + +// Commit will update the history of the last time the WASM file was loaded +// and delete the outdated WASM files +func (vc *waterVersionControl) markUsed(transport string) error { + f, err := os.Create(filepath.Join(vc.dir, transport+".last-loaded")) + if err != nil { + return log.Errorf("failed to create file %s: %w", transport+".last-loaded", err) + } + defer f.Close() + + if _, err = f.WriteString(strconv.FormatInt(time.Now().UTC().Unix(), 10)); err != nil { + return log.Errorf("failed to write to file %s: %w", transport+".last-loaded", err) + } + if err = vc.cleanOutdated(); err != nil { + return log.Errorf("failed to clean outdated WASMs: %w", err) + } + return nil +} + +// unusedWASMsDeletedAfter is the time after which the WASM files are considered outdated +const unusedWASMsDeletedAfter = 7 * 24 * time.Hour + +func (vc *waterVersionControl) cleanOutdated() error { + wg := new(sync.WaitGroup) + filesToBeDeleted := make([]string, 0) + // walk through dir, load last-loaded and delete if older than unusedWASMsDeletedAfter + err := filepath.Walk(vc.dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return log.Errorf("failed to walk through dir: %w", err) + } + if info.IsDir() { + return nil + } + + if filepath.Ext(path) != ".last-loaded" { + return nil + } + + lastLoaded, err := os.ReadFile(path) + if err != nil { + return log.Errorf("failed to read file %s: %w", path, err) + } + + i, err := strconv.ParseInt(string(lastLoaded), 10, 64) + if err != nil { + return log.Errorf("failed to parse int: %w", err) + } + lastLoadedTime := time.Unix(i, 0) + if time.Since(lastLoadedTime) > unusedWASMsDeletedAfter { + filesToBeDeleted = append(filesToBeDeleted, path) + } + return nil + }) + for _, path := range filesToBeDeleted { + wg.Add(1) + go func() { + defer wg.Done() + transport := strings.TrimSuffix(filepath.Base(path), ".last-loaded") + if err = os.Remove(filepath.Join(vc.dir, transport+".wasm")); err != nil { + log.Errorf("failed to remove wasm file %s: %w", transport+".wasm", err) + return + } + if err = os.Remove(path); err != nil { + log.Errorf("failed to remove last-loaded file %s: %w", path, err) + return + } + }() + } + wg.Wait() + return err +} + +func (vc *waterVersionControl) downloadWASM(ctx context.Context, transport string, downloader waterWASMDownloader) (io.ReadCloser, error) { + outputPath := filepath.Join(vc.dir, transport+".wasm") + f, err := os.Create(outputPath) + if err != nil { + return nil, log.Errorf("failed to create file %s: %w", transport, err) + } + + if err = downloader.DownloadWASM(ctx, f); err != nil { + return nil, log.Errorf("failed to download wasm: %w", err) + } + + if _, err = f.Seek(0, 0); err != nil { + return nil, log.Errorf("failed to seek to the beginning of the file: %w", err) + } + + return f, nil +} diff --git a/chained/water_version_control_test.go b/chained/water_version_control_test.go new file mode 100644 index 000000000..18a1dde37 --- /dev/null +++ b/chained/water_version_control_test.go @@ -0,0 +1,123 @@ +package chained + +import ( + "context" + "io" + "os" + "path/filepath" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" +) + +func TestNewWaterVersionControl(t *testing.T) { + var tests = []struct { + name string + assert func(t *testing.T, dir string, r io.ReadCloser, err error) + setup func(t *testing.T, ctrl *gomock.Controller, dir string) waterWASMDownloader + }{ + { + name: "it should call downloadWASM when the file does not exist", + assert: func(t *testing.T, dir string, r io.ReadCloser, err error) { + assert.NoError(t, err) + assert.NotNil(t, r) + defer r.Close() + + b, err := io.ReadAll(r) + require.NoError(t, err) + assert.Equal(t, "test", string(b)) + + _, err = os.Stat(filepath.Join(dir, "test.wasm")) + assert.NoError(t, err) + + _, err = os.Stat(filepath.Join(dir, "test.last-loaded")) + assert.NoError(t, err) + }, + setup: func(t *testing.T, ctrl *gomock.Controller, _ string) waterWASMDownloader { + downloader := NewMockwaterWASMDownloader(ctrl) + downloader.EXPECT().DownloadWASM(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, w io.Writer) error { + assert.NotNil(t, ctx) + assert.NotNil(t, w) + _, err := w.Write([]byte("test")) + require.NoError(t, err) + return nil + }) + return downloader + }, + }, + { + name: "it should delete outdated WASM files after marking it as used", + assert: func(t *testing.T, dir string, r io.ReadCloser, err error) { + assert.NoError(t, err) + assert.NotNil(t, r) + defer r.Close() + + b, err := io.ReadAll(r) + require.NoError(t, err) + assert.Equal(t, "test", string(b)) + + _, err = os.Stat(filepath.Join(dir, "test.wasm")) + assert.NoError(t, err) + + _, err = os.Stat(filepath.Join(dir, "test.last-loaded")) + assert.NoError(t, err) + + // assert old-test does not exist + _, err = os.Stat(filepath.Join(dir, "old-test.wasm")) + assert.Error(t, os.ErrNotExist, err) + + _, err = os.Stat(filepath.Join(dir, "old-test.last-loaded")) + assert.Error(t, os.ErrNotExist, err) + + }, + setup: func(t *testing.T, ctrl *gomock.Controller, dir string) waterWASMDownloader { + // create test.wasm at dir + f, err := os.Create(filepath.Join(dir, "old-test.wasm")) + require.NoError(t, err) + _, err = f.WriteString("test") + require.NoError(t, err) + require.NoError(t, f.Close()) + + // create test.last-loaded at dir with time older than 7 days + f, err = os.Create(filepath.Join(dir, "old-test.last-loaded")) + require.NoError(t, err) + unixTime := time.Now().UTC().AddDate(0, 0, -8).Unix() + oldTime := strconv.FormatInt(unixTime, 10) + _, err = f.WriteString(oldTime) + require.NoError(t, err) + require.NoError(t, f.Close()) + + downloader := NewMockwaterWASMDownloader(ctrl) + downloader.EXPECT().DownloadWASM(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, w io.Writer) error { + assert.NotNil(t, ctx) + assert.NotNil(t, w) + _, err := w.Write([]byte("test")) + require.NoError(t, err) + return nil + }) + return downloader + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir, err := os.MkdirTemp("", "water") + require.NoError(t, err) + defer os.RemoveAll(dir) + + downloader := tt.setup(t, gomock.NewController(t), dir) + vc := newWaterVersionControl(dir) + require.NotNil(t, vc) + require.NotEmpty(t, vc.dir) + + ctx := context.Background() + r, err := vc.GetWASM(ctx, "test", downloader) + + tt.assert(t, dir, r, err) + }) + } +} diff --git a/genconfig/mock_genconfig.go b/genconfig/mock_genconfig.go index 3e587a47c..a9606e895 100644 --- a/genconfig/mock_genconfig.go +++ b/genconfig/mock_genconfig.go @@ -22,6 +22,7 @@ import ( type Mockverifier struct { ctrl *gomock.Controller recorder *MockverifierMockRecorder + isgomock struct{} } // MockverifierMockRecorder is the mock recorder for Mockverifier. @@ -59,6 +60,7 @@ func (mr *MockverifierMockRecorder) Vet(m, pool, testURL any) *gomock.Call { type MockcertGrabber struct { ctrl *gomock.Controller recorder *MockcertGrabberMockRecorder + isgomock struct{} } // MockcertGrabberMockRecorder is the mock recorder for MockcertGrabber. diff --git a/go.mod b/go.mod index 64d095d26..e7437b388 100644 --- a/go.mod +++ b/go.mod @@ -114,23 +114,15 @@ require ( github.com/Jigsaw-Code/outline-ss-server v1.5.0 // indirect github.com/anacrolix/dht/v2 v2.20.0 // indirect github.com/blang/vfs v1.0.0 // indirect - github.com/cespare/xxhash v1.1.0 // indirect github.com/coder/websocket v1.8.12 // indirect github.com/dchest/siphash v1.2.3 // indirect github.com/gaukas/wazerofs v0.1.0 // indirect github.com/getlantern/algeneva v0.0.0-20240222191137-2b4e88234f59 // indirect github.com/getlantern/lampshade v0.0.0-20201109225444-b06082e15f3a // indirect github.com/getlantern/withtimeout v0.0.0-20160829163843-511f017cd913 // indirect - github.com/klauspost/cpuid/v2 v2.2.3 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect - github.com/mr-tron/base58 v1.2.0 // indirect - github.com/multiformats/go-multihash v0.2.3 // indirect - github.com/multiformats/go-varint v0.0.6 // indirect - github.com/protolambda/ctxlock v0.1.0 // indirect github.com/tetratelabs/wazero v1.7.1 // indirect gitlab.com/yawning/edwards25519-extra.git v0.0.0-20211229043746-2f91fcc9fbdb // indirect gitlab.com/yawning/obfs4.git v0.0.0-20220204003609-77af0cba934d // indirect - lukechampine.com/blake3 v1.1.6 // indirect nhooyr.io/websocket v1.8.11 // indirect ) diff --git a/go.sum b/go.sum index 08467e922..48a13149e 100644 --- a/go.sum +++ b/go.sum @@ -13,7 +13,6 @@ github.com/Jigsaw-Code/outline-sdk v0.0.16 h1:WbHmv80FKDIpzEmR3GehTbq5CibYTLvcxI github.com/Jigsaw-Code/outline-sdk v0.0.16/go.mod h1:e1oQZbSdLJBBuHgfeQsgEkvkuyIePPwstUeZRGq0KO8= github.com/Jigsaw-Code/outline-ss-server v1.5.0 h1:Vz+iS0xR7i3PrLD82pzFFwZ9fsh6zrNawMeYERR8VTc= github.com/Jigsaw-Code/outline-ss-server v1.5.0/go.mod h1:KaebwBiCWDSkgsJrJIbGH0szON8CZq4LgQaFV8v3RM4= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OperatorFoundation/Replicant-go/Replicant/v3 v3.0.23 h1:g0kC1BDonLwNse78HRsudElKEDfXHusLQ9Nfekl/l0o= github.com/OperatorFoundation/Replicant-go/Replicant/v3 v3.0.23/go.mod h1:QVlygHzbNc/fX+OHurCRC0AFwISJAUQbPaqdEfAkUio= @@ -105,8 +104,6 @@ github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pm github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= github.com/anacrolix/torrent v1.53.3 h1:Abb3J8p/JWUjOzHaii8kixuDOGMXz+Oh8+hwc/zA2lA= github.com/anacrolix/torrent v1.53.3/go.mod h1:is8GNob5qDeZ5Kq+pKPiE2xqYUi1ms7IgSB+CftZETk= -github.com/anacrolix/torrent v1.57.1 h1:CS8rYfC2Oe15NPBhwCNs/3WBY6HiBCPDFpY+s9aFHbA= -github.com/anacrolix/torrent v1.57.1/go.mod h1:NNBg4lP2/us9Hp5+cLNcZRILM69cNoKIkqMGqr9AuR0= github.com/anacrolix/upnp v0.1.4 h1:+2t2KA6QOhm/49zeNyeVwDu1ZYS9dB9wfxyVvh/wk7U= github.com/anacrolix/upnp v0.1.4/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic= github.com/anacrolix/utp v0.1.0 h1:FOpQOmIwYsnENnz7tAGohA+r6iXpRjrq8ssKSre2Cp4= @@ -144,7 +141,6 @@ github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaq github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -566,10 +562,6 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= -github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= -github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/reedsolomon v1.9.9 h1:qCL7LZlv17xMixl55nq2/Oa1Y86nfO8EqDfv2GHND54= @@ -600,8 +592,6 @@ github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssn github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= -github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-server-timing v1.0.1 h1:f00/aIe8T3MrnLhQHu3tSWvnwc5GV/p5eutuu3hF/tE= @@ -617,15 +607,9 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.5.0 h1:2EkzeTSqBB4V4bJwWrt5gIIrZmpJBcoIRGS2kWLgzmk= github.com/montanaflynn/stats v0.5.0/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= -github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= -github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= -github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= -github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= -github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.2 h1:Cj0yZY6T1Zx1R7AhTbyGSALm44/Mmq+BAPc4B/p/d3M= @@ -756,8 +740,6 @@ github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4 github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/protolambda/ctxlock v0.1.0 h1:rCUY3+vRdcdZXqT07iXgyr744J2DU2LCBIXowYAjBCE= -github.com/protolambda/ctxlock v0.1.0/go.mod h1:vefhX6rIZH8rsg5ZpOJfEDYQOppZi19SfPiGOFrNnwM= github.com/quic-go/quic-go v0.41.0 h1:aD8MmHfgqTURWNJy48IYFg2OnxwHT3JL7ahGs73lb4k= github.com/quic-go/quic-go v0.41.0/go.mod h1:qCkNjqczPEvgsOnxZ0eCD14lv+B2LHlFAB++CNOh9hA= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -1063,7 +1045,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1179,8 +1160,6 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= -lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c= -lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=