diff --git a/chained/logger.go b/chained/logger.go deleted file mode 100644 index 2e9d15393..000000000 --- a/chained/logger.go +++ /dev/null @@ -1,160 +0,0 @@ -package chained - -import ( - "context" - "fmt" - "log/slog" - "strings" - - "github.com/getlantern/golog" -) - -// slogHandler is a Handler that implements the slog.Handler interface -// and writes log records to a golog.Logger. -type slogHandler struct { - logger golog.Logger - prefix string - minLevel slog.Level - opts slog.HandlerOptions - attrs string - groups []string -} - -func newLogHandler(logger golog.Logger, prefix string) *slogHandler { - return &slogHandler{logger: logger, prefix: prefix} -} - -// Enabled reports whether the handler handles records at the given level. -// The handler ignores records whose level is lower. -// It is called early, before any arguments are processed, -// to save effort if the log event should be discarded. -// If called from a Logger method, the first argument is the context -// passed to that method, or context.Background() if nil was passed -// or the method does not take a context. -// The context is passed so Enabled can use its values -// to make a decision. -func (h *slogHandler) Enabled(_ context.Context, level slog.Level) bool { - minLevel := slog.LevelDebug - if h.opts.Level != nil { - minLevel = h.opts.Level.Level() - } - return level >= minLevel -} - -// Handle handles the Record. -// It will only be called when Enabled returns true. -// The Context argument is as for Enabled. -// It is present solely to provide Handlers access to the context's values. -// Canceling the context should not affect record processing. -// (Among other things, log messages may be necessary to debug a -// cancellation-related problem.) -// -// Handle methods that produce output should observe the following rules: -// - If r.Time is the zero time, ignore the time. -// - If r.PC is zero, ignore it. -// - Attr's values should be resolved. -// - If an Attr's key and value are both the zero value, ignore the Attr. -// This can be tested with attr.Equal(Attr{}). -// - If a group's key is empty, inline the group's Attrs. -// - If a group has no Attrs (even if it has a non-empty key), -// ignore it. -func (h *slogHandler) Handle(ctx context.Context, record slog.Record) error { - if !h.Enabled(ctx, record.Level) { - return nil - } - - messageBuilder := new(strings.Builder) - messageBuilder.WriteString(record.Level.String()) - messageBuilder.WriteString(" ") - messageBuilder.WriteString(h.prefix) - messageBuilder.WriteString(": ") - messageBuilder.WriteString(record.Message) - messageBuilder.WriteString(" ") - record.Attrs(func(attr slog.Attr) bool { - messageBuilder.WriteString(attr.Key) - messageBuilder.WriteString("=") - messageBuilder.WriteString(attr.Value.String()) - messageBuilder.WriteString(" ") - return true - }) - - messageBuilder.WriteString(h.attrs) - message := messageBuilder.String() - - switch record.Level { - case slog.LevelDebug, slog.LevelInfo, slog.LevelWarn: - h.logger.Debug(message) - case slog.LevelError: - err := h.logger.Error(message) - if err != nil { - return err - } - default: - return fmt.Errorf("unsupported log level: %v", record.Level) - } - return nil -} - -// WithAttrs returns a new Handler whose attributes consist of -// both the receiver's attributes and the arguments. -// The Handler owns the slice: it may retain, modify or discard it. -func (h *slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { - h.attrs = parseAttrs(attrs) - return h -} - -func parseAttrs(attrs []slog.Attr) string { - attrsBuilder := new(strings.Builder) - for _, attr := range attrs { - attrsBuilder.WriteString(attr.Key) - attrsBuilder.WriteString(":") - switch attr.Value.Kind() { - case slog.KindString, slog.KindAny: - attrsBuilder.WriteString(attr.Value.String()) - case slog.KindInt64: - fmt.Fprintf(attrsBuilder, "%d", attr.Value.Int64()) - case slog.KindUint64: - fmt.Fprintf(attrsBuilder, "%d", attr.Value.Uint64()) - case slog.KindFloat64: - fmt.Fprintf(attrsBuilder, "%f", attr.Value.Float64()) - case slog.KindBool: - fmt.Fprintf(attrsBuilder, "%t", attr.Value.Bool()) - case slog.KindTime: - attrsBuilder.WriteString(attr.Value.Time().String()) - case slog.KindDuration: - attrsBuilder.WriteString(attr.Value.Duration().String()) - case slog.KindLogValuer: - attrsBuilder.WriteString(attr.Value.LogValuer().LogValue().String()) - case slog.KindGroup: - attrsBuilder.WriteString("{") - attrsBuilder.WriteString(parseAttrs(attr.Value.Group())) - attrsBuilder.WriteString("}") - } - attrsBuilder.WriteString(" ") - } - return attrsBuilder.String() -} - -// WithGroup returns a new Handler with the given group appended to -// the receiver's existing groups. -// The keys of all subsequent attributes, whether added by With or in a -// Record, should be qualified by the sequence of group names. -// -// How this qualification happens is up to the Handler, so long as -// this Handler's attribute keys differ from those of another Handler -// with a different sequence of group names. -// -// A Handler should treat WithGroup as starting a Group of Attrs that ends -// at the end of the log event. That is, -// -// logger.WithGroup("s").LogAttrs(ctx, level, msg, slog.Int("a", 1), slog.Int("b", 2)) -// -// should behave like -// -// logger.LogAttrs(ctx, level, msg, slog.Group("s", slog.Int("a", 1), slog.Int("b", 2))) -// -// If the name is empty, WithGroup returns the receiver. -func (h *slogHandler) WithGroup(name string) slog.Handler { - // TODO: Implement WithGroup - return h -} diff --git a/chained/mocks_test.go b/chained/mocks_test.go deleted file mode 100644 index bf26cd6e6..000000000 --- a/chained/mocks_test.go +++ /dev/null @@ -1,177 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/getlantern/flashlight/v7/chained (interfaces: waterWASMDownloader,torrentClient,torrentInfo) -// -// Generated by this command: -// -// mockgen -package=chained -destination=mocks_test.go . waterWASMDownloader,torrentClient,torrentInfo -// - -// Package chained is a generated GoMock package. -package chained - -import ( - context "context" - io "io" - reflect "reflect" - - events "github.com/anacrolix/chansync/events" - torrent "github.com/anacrolix/torrent" - gomock "go.uber.org/mock/gomock" -) - -// MockwaterWASMDownloader is a mock of waterWASMDownloader interface. -type MockwaterWASMDownloader struct { - ctrl *gomock.Controller - recorder *MockwaterWASMDownloaderMockRecorder - isgomock struct{} -} - -// MockwaterWASMDownloaderMockRecorder is the mock recorder for MockwaterWASMDownloader. -type MockwaterWASMDownloaderMockRecorder struct { - mock *MockwaterWASMDownloader -} - -// 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 *MockwaterWASMDownloader) EXPECT() *MockwaterWASMDownloaderMockRecorder { - return m.recorder -} - -// Close mocks base method. -func (m *MockwaterWASMDownloader) Close() error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Close") - ret0, _ := ret[0].(error) - return ret0 -} - -// Close indicates an expected call of Close. -func (mr *MockwaterWASMDownloaderMockRecorder) Close() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockwaterWASMDownloader)(nil).Close)) -} - -// DownloadWASM mocks base method. -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) - return ret0 -} - -// DownloadWASM indicates an expected call of DownloadWASM. -func (mr *MockwaterWASMDownloaderMockRecorder) DownloadWASM(arg0, arg1 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - 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 { - ctrl *gomock.Controller - recorder *MocktorrentClientMockRecorder - isgomock struct{} -} - -// 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} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MocktorrentClient) EXPECT() *MocktorrentClientMockRecorder { - return m.recorder -} - -// AddMagnet mocks base method. -func (m *MocktorrentClient) AddMagnet(arg0 string) (torrentInfo, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddMagnet", arg0) - 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 { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMagnet", reflect.TypeOf((*MocktorrentClient)(nil).AddMagnet), arg0) -} - -// Close mocks base method. -func (m *MocktorrentClient) Close() []error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Close") - ret0, _ := ret[0].([]error) - return ret0 -} - -// Close indicates an expected call of Close. -func (mr *MocktorrentClientMockRecorder) Close() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MocktorrentClient)(nil).Close)) -} - -// MocktorrentInfo is a mock of torrentInfo interface. -type MocktorrentInfo struct { - ctrl *gomock.Controller - recorder *MocktorrentInfoMockRecorder - isgomock struct{} -} - -// 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} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MocktorrentInfo) EXPECT() *MocktorrentInfoMockRecorder { - return m.recorder -} - -// GotInfo mocks base method. -func (m *MocktorrentInfo) GotInfo() events.Done { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GotInfo") - ret0, _ := ret[0].(events.Done) - return ret0 -} - -// GotInfo indicates an expected call of GotInfo. -func (mr *MocktorrentInfoMockRecorder) GotInfo() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GotInfo", reflect.TypeOf((*MocktorrentInfo)(nil).GotInfo)) -} - -// NewReader mocks base method. -func (m *MocktorrentInfo) NewReader() torrent.Reader { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewReader") - ret0, _ := ret[0].(torrent.Reader) - return ret0 -} - -// NewReader indicates an expected call of NewReader. -func (mr *MocktorrentInfoMockRecorder) NewReader() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewReader", reflect.TypeOf((*MocktorrentInfo)(nil).NewReader)) -} diff --git a/chained/torrent_reader_mock_test.go b/chained/torrent_reader_mock_test.go deleted file mode 100644 index d7fdb4194..000000000 --- a/chained/torrent_reader_mock_test.go +++ /dev/null @@ -1,137 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/anacrolix/torrent (interfaces: Reader) -// -// Generated by this command: -// -// mockgen -package=chained -destination=torrent_reader_mock_test.go github.com/anacrolix/torrent Reader -// - -// Package chained is a generated GoMock package. -package chained - -import ( - context "context" - reflect "reflect" - - torrent "github.com/anacrolix/torrent" - gomock "go.uber.org/mock/gomock" -) - -// MockReader is a mock of Reader interface. -type MockReader struct { - ctrl *gomock.Controller - recorder *MockReaderMockRecorder - isgomock struct{} -} - -// MockReaderMockRecorder is the mock recorder for MockReader. -type MockReaderMockRecorder struct { - mock *MockReader -} - -// NewMockReader creates a new mock instance. -func NewMockReader(ctrl *gomock.Controller) *MockReader { - mock := &MockReader{ctrl: ctrl} - mock.recorder = &MockReaderMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockReader) EXPECT() *MockReaderMockRecorder { - return m.recorder -} - -// Close mocks base method. -func (m *MockReader) Close() error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Close") - ret0, _ := ret[0].(error) - return ret0 -} - -// Close indicates an expected call of Close. -func (mr *MockReaderMockRecorder) Close() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockReader)(nil).Close)) -} - -// Read mocks base method. -func (m *MockReader) Read(p []byte) (int, error) { - m.ctrl.T.Helper() - 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(p any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockReader)(nil).Read), p) -} - -// ReadContext mocks base method. -func (m *MockReader) ReadContext(arg0 context.Context, arg1 []byte) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReadContext", arg0, arg1) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ReadContext indicates an expected call of ReadContext. -func (mr *MockReaderMockRecorder) ReadContext(arg0, arg1 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadContext", reflect.TypeOf((*MockReader)(nil).ReadContext), arg0, arg1) -} - -// Seek mocks base method. -func (m *MockReader) Seek(offset int64, whence int) (int64, error) { - m.ctrl.T.Helper() - 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(offset, whence any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Seek", reflect.TypeOf((*MockReader)(nil).Seek), offset, whence) -} - -// SetReadahead mocks base method. -func (m *MockReader) SetReadahead(arg0 int64) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SetReadahead", arg0) -} - -// SetReadahead indicates an expected call of SetReadahead. -func (mr *MockReaderMockRecorder) SetReadahead(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadahead", reflect.TypeOf((*MockReader)(nil).SetReadahead), arg0) -} - -// SetReadaheadFunc mocks base method. -func (m *MockReader) SetReadaheadFunc(arg0 torrent.ReadaheadFunc) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SetReadaheadFunc", arg0) -} - -// SetReadaheadFunc indicates an expected call of SetReadaheadFunc. -func (mr *MockReaderMockRecorder) SetReadaheadFunc(arg0 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadaheadFunc", reflect.TypeOf((*MockReader)(nil).SetReadaheadFunc), arg0) -} - -// SetResponsive mocks base method. -func (m *MockReader) SetResponsive() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SetResponsive") -} - -// SetResponsive indicates an expected call of SetResponsive. -func (mr *MockReaderMockRecorder) SetResponsive() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetResponsive", reflect.TypeOf((*MockReader)(nil).SetResponsive)) -} diff --git a/chained/water_downloader.go b/chained/water_downloader.go deleted file mode 100644 index 7cb469b1d..000000000 --- a/chained/water_downloader.go +++ /dev/null @@ -1,82 +0,0 @@ -package chained - -import ( - "context" - "errors" - "io" - "net/http" - "strings" -) - -//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 waterWASMDownloader interface { - DownloadWASM(context.Context, io.Writer) error - Close() error -} - -type downloader struct { - urls []string - httpClient *http.Client - httpDownloader waterWASMDownloader - magnetDownloader waterWASMDownloader -} - -// 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") - } - return &downloader{ - urls: urls, - httpClient: httpClient, - }, nil -} - -func (d *downloader) Close() error { - if d.magnetDownloader != nil { - return d.magnetDownloader.Close() - } - return nil -} - -// DownloadWASM downloads the WASM file from the given URLs, verifies the hash -// sum and writes the file to the given writer. -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 { - err := d.downloadWASM(ctx, w, url) - if err != nil { - joinedErrs = errors.Join(joinedErrs, err) - continue - } - - return nil - } - return joinedErrs -} - -// downloadWASM checks what kind of URL was given and downloads the WASM file -// from the URL. It can be a HTTPS URL or a magnet link. -func (d *downloader) downloadWASM(ctx context.Context, w io.Writer, url string) error { - switch { - case strings.HasPrefix(url, "http://"), strings.HasPrefix(url, "https://"): - if d.httpDownloader == nil { - 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 := newWaterMagnetDownloader(ctx, url) - if err != nil { - return err - } - d.magnetDownloader = downloader - } - return d.magnetDownloader.DownloadWASM(ctx, w) - default: - return log.Errorf("unsupported protocol: %s", url) - } -} diff --git a/chained/water_downloader_test.go b/chained/water_downloader_test.go deleted file mode 100644 index 737012480..000000000 --- a/chained/water_downloader_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package chained - -import ( - "bytes" - "context" - "io" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - gomock "go.uber.org/mock/gomock" -) - -func TestNewWASMDownloader(t *testing.T) { - var tests = []struct { - name string - givenURLs []string - givenHTTPClient *http.Client - 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 waterWASMDownloader, err error) { - assert.Error(t, err) - assert.Nil(t, d) - }, - }, - { - name: "it should successfully return a wasm downloader", - givenURLs: []string{"http://example.com"}, - givenHTTPClient: http.DefaultClient, - 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) - assert.Equal(t, http.DefaultClient, d.httpClient) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - d, err := newWaterWASMDownloader(tt.givenURLs, tt.givenHTTPClient) - tt.assert(t, d, err) - }) - } -} - -func TestDownloadWASM(t *testing.T) { - ctx := context.Background() - - contentMessage := "content" - var tests = []struct { - name string - givenHTTPClient *http.Client - givenURLs []string - givenWriter io.Writer - setupHTTPDownloader func(ctrl *gomock.Controller) waterWASMDownloader - assert func(*testing.T, io.Reader, error) - }{ - { - name: "udp urls are unsupported", - givenURLs: []string{ - "udp://example.com", - }, - assert: func(t *testing.T, r io.Reader, err error) { - b, berr := io.ReadAll(r) - require.NoError(t, berr) - assert.Empty(t, b) - assert.Error(t, err) - assert.ErrorContains(t, err, "unsupported protocol") - }, - }, - { - name: "http download error", - givenURLs: []string{ - "http://example.com", - }, - setupHTTPDownloader: func(ctrl *gomock.Controller) waterWASMDownloader { - httpDownloader := NewMockwaterWASMDownloader(ctrl) - httpDownloader.EXPECT().DownloadWASM(ctx, gomock.Any()).Return(assert.AnError) - return httpDownloader - }, - assert: func(t *testing.T, r io.Reader, err error) { - b, berr := io.ReadAll(r) - require.NoError(t, berr) - assert.Empty(t, b) - assert.Error(t, err) - assert.ErrorContains(t, err, assert.AnError.Error()) - assert.ErrorContains(t, err, "failed to download WASM from all URLs") - }, - }, - { - name: "success", - givenURLs: []string{ - "http://example.com", - }, - 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)) - return err - }) - return httpDownloader - }, - assert: func(t *testing.T, r io.Reader, err error) { - b, berr := io.ReadAll(r) - require.NoError(t, berr) - assert.NoError(t, err) - assert.Equal(t, contentMessage, string(b)) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var httpDownloader waterWASMDownloader - if tt.setupHTTPDownloader != nil { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - httpDownloader = tt.setupHTTPDownloader(ctrl) - } - - b := &bytes.Buffer{} - d, err := newWaterWASMDownloader(tt.givenURLs, tt.givenHTTPClient) - require.NoError(t, err) - - if httpDownloader != nil { - d.(*downloader).httpDownloader = httpDownloader - } - err = d.DownloadWASM(ctx, b) - tt.assert(t, b, err) - }) - } -} diff --git a/chained/water_https_downloader.go b/chained/water_https_downloader.go deleted file mode 100644 index 0b3f31157..000000000 --- a/chained/water_https_downloader.go +++ /dev/null @@ -1,43 +0,0 @@ -package chained - -import ( - "context" - "fmt" - "io" - "net/http" -) - -type httpsDownloader struct { - cli *http.Client - url string -} - -func newWaterHTTPSDownloader(client *http.Client, url string) waterWASMDownloader { - return &httpsDownloader{cli: client, url: url} -} - -func (d *httpsDownloader) Close() error { - return nil -} - -func (d *httpsDownloader) DownloadWASM(ctx context.Context, w io.Writer) error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, d.url, http.NoBody) - if err != nil { - return fmt.Errorf("failed to create a new HTTP request: %w", err) - } - resp, err := d.cli.Do(req) - if err != nil { - return fmt.Errorf("failed to send a HTTP request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("failed to download WASM file: %s", resp.Status) - } - - _, err = io.Copy(w, resp.Body) - if err != nil { - return fmt.Errorf("failed to write the WASM file: %w", err) - } - return nil -} diff --git a/chained/water_https_downloader_test.go b/chained/water_https_downloader_test.go deleted file mode 100644 index 32514ea26..000000000 --- a/chained/water_https_downloader_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package chained - -import ( - "bytes" - "context" - "io" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestHTTPSDownloadWASM(t *testing.T) { - ctx := context.Background() - var tests = []struct { - name string - givenHTTPClient *http.Client - givenURL string - assert func(*testing.T, io.Reader, error) - }{ - { - name: "sending request successfully", - givenHTTPClient: &http.Client{ - Transport: &roundTripFunc{ - f: func(req *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString("wasm")), - }, nil - }, - }, - }, - givenURL: "https://example.com/wasm.wasm", - assert: func(t *testing.T, r io.Reader, err error) { - assert.NoError(t, err) - b, err := io.ReadAll(r) - require.NoError(t, err) - assert.Equal(t, "wasm", string(b)) - }, - }, - { - name: "when receiving an error from the HTTP client, it should return an error", - givenHTTPClient: &http.Client{ - Transport: &roundTripFunc{ - f: func(req *http.Request) (*http.Response, error) { - return nil, assert.AnError - }, - }, - }, - givenURL: "https://example.com/wasm.wasm", - assert: func(t *testing.T, r io.Reader, err error) { - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to send a HTTP request") - }, - }, - { - name: "when the HTTP status code is not 200, it should return an error", - givenHTTPClient: &http.Client{ - Transport: &roundTripFunc{ - f: func(req *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusNotFound, - }, nil - }, - }, - }, - givenURL: "https://example.com/wasm.wasm", - assert: func(t *testing.T, r io.Reader, err error) { - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to download WASM file") - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := new(bytes.Buffer) - 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 df7b8b5ae..49e5e1445 100644 --- a/chained/water_impl.go +++ b/chained/water_impl.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "fmt" "io" - "log/slog" "net" "net/http" "strings" @@ -17,6 +16,10 @@ import ( "github.com/getlantern/flashlight/v7/proxied" "github.com/refraction-networking/water" _ "github.com/refraction-networking/water/transport/v1" + + waterDialer "github.com/getlantern/lantern-water/dialer" + waterDownloader "github.com/getlantern/lantern-water/downloader" + waterVC "github.com/getlantern/lantern-water/version_control" ) type waterImpl struct { @@ -66,7 +69,11 @@ func newWaterImpl(dir, addr string, pc *config.ProxyConfig, reportDialCore repor return } - d.dialer, err = createDialer(ctx, wasm, transport) + d.dialer, err = waterDialer.NewDialer(ctx, waterDialer.DialerParameters{ + Logger: log, + Transport: transport, + WASM: wasm, + }) if err != nil { d.setErrLoadingWASM(log.Errorf("failed to create dialer: %w", err)) return @@ -79,7 +86,6 @@ func newWaterImpl(dir, addr string, pc *config.ProxyConfig, reportDialCore repor if wasmAvailableAt != "" { go func() { log.Debugf("Loading WASM for %q. If not available, it should try to download from the following URLs: %+v. The file should be available at: %q", transport, strings.Split(wasmAvailableAt, ","), dir) - r, err := d.loadWASM(ctx, transport, dir, wasmAvailableAt) if err != nil { d.setErrLoadingWASM(log.Errorf("failed to read wasm: %w", err)) @@ -94,7 +100,11 @@ func newWaterImpl(dir, addr string, pc *config.ProxyConfig, reportDialCore repor log.Debugf("received wasm with %d bytes", len(b)) - d.dialer, err = createDialer(ctx, b, transport) + d.dialer, err = waterDialer.NewDialer(ctx, waterDialer.DialerParameters{ + Logger: log, + Transport: transport, + WASM: b, + }) if err != nil { d.setErrLoadingWASM(log.Errorf("failed to create dialer: %w", err)) return @@ -162,12 +172,12 @@ func (d *waterImpl) loadWASM(ctx context.Context, transport string, dir string, m.Lock() defer m.Unlock() - vc := newWaterVersionControl(dir) + vc := waterVC.NewWaterVersionControl(dir, log) cli := waterHTTPClient if cli == nil { cli = proxied.ChainedThenDirectThenFrontedClient(1*time.Minute, "") } - downloader, err := newWaterWASMDownloader(strings.Split(wasmAvailableAt, ","), cli) + downloader, err := waterDownloader.NewWASMDownloader(strings.Split(wasmAvailableAt, ","), cli) if err != nil { return nil, log.Errorf("failed to create wasm downloader: %w", err) } @@ -178,19 +188,6 @@ func (d *waterImpl) loadWASM(ctx context.Context, transport string, dir string, return r, nil } -func createDialer(ctx context.Context, wasm []byte, transport string) (water.Dialer, error) { - cfg := &water.Config{ - TransportModuleBin: wasm, - OverrideLogger: slog.New(newLogHandler(log, transport)), - } - - dialer, err := water.NewDialerWithContext(ctx, cfg) - if err != nil { - return nil, log.Errorf("failed to create dialer: %w", err) - } - return dialer, nil -} - func (d *waterImpl) dialServer(op *ops.Op, ctx context.Context) (net.Conn, error) { return d.reportDialCore(op, func() (net.Conn, error) { // if dialer is not ready, wait until WASM is downloaded or context timeout diff --git a/chained/water_magnet_downloader.go b/chained/water_magnet_downloader.go deleted file mode 100644 index bdb8531f4..000000000 --- a/chained/water_magnet_downloader.go +++ /dev/null @@ -1,115 +0,0 @@ -package chained - -import ( - "context" - "errors" - "io" - "net" - "os" - - "github.com/anacrolix/chansync/events" - "github.com/anacrolix/torrent" -) - -type waterMagnetDownloader struct { - magnetURL string - client torrentClient -} - -// 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 - } - - client, err := torrent.NewClient(cfg) - if err != nil { - return nil, log.Errorf("failed to create torrent client: %w", err) - } - return &waterMagnetDownloader{ - magnetURL: magnetURL, - client: newTorrentCliWrapper(client), - }, nil -} - -func (d *waterMagnetDownloader) Close() error { - errs := d.client.Close() - closeErr := errors.New("failed to close torrent client") - allErrs := make([]error, len(errs)+1) - allErrs[0] = closeErr - for i, err := range errs { - allErrs[i+1] = err - } - closeErr = errors.Join(allErrs...) - return closeErr -} - -type torrentCliWrapper struct { - client *torrent.Client -} - -func newTorrentCliWrapper(client *torrent.Client) *torrentCliWrapper { - return &torrentCliWrapper{ - client: client, - } -} - -func (t *torrentCliWrapper) AddMagnet(magnetURL string) (torrentInfo, error) { - return t.client.AddMagnet(magnetURL) -} - -func (t *torrentCliWrapper) Close() []error { - return t.client.Close() -} - -type torrentClient interface { - AddMagnet(string) (torrentInfo, error) - Close() []error -} - -type torrentInfo interface { - GotInfo() events.Done - NewReader() torrent.Reader -} - -func dialContext(ctx context.Context, network, addr string) (net.Conn, error) { - select { - case <-ctx.Done(): - return nil, log.Errorf("context complete: %w", ctx.Err()) - default: - return new(net.Dialer).DialContext(ctx, network, addr) - } -} - -func generateTorrentClientConfig(ctx context.Context) (*torrent.ClientConfig, error) { - cfg := torrent.NewDefaultClientConfig() - path, err := os.MkdirTemp("", "lantern-water-module") - if err != nil { - return nil, log.Errorf("failed to create temp dir: %w", err) - } - cfg.DataDir = path - cfg.HTTPDialContext = dialContext - cfg.TrackerDialContext = dialContext - return cfg, nil -} - -// DownloadWASM downloads the WASM file from the given URL. -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) - } - - select { - case <-t.GotInfo(): - case <-ctx.Done(): - return log.Errorf("context complete: %w", ctx.Err()) - } - - _, err = io.Copy(w, t.NewReader()) - if err != nil { - return log.Errorf("failed to copy torrent reader to writer: %w", err) - } - return nil -} diff --git a/chained/water_magnet_downloader_test.go b/chained/water_magnet_downloader_test.go deleted file mode 100644 index 21e15e81a..000000000 --- a/chained/water_magnet_downloader_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package chained - -import ( - "bytes" - "context" - "io" - "testing" - - events "github.com/anacrolix/chansync/events" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - gomock "go.uber.org/mock/gomock" -) - -// TestMagnetDownloadWASM run a integration test for validating if the magnet downloader is working as expected. -func TestMagnetDownloadWASM(t *testing.T) { - ctx := context.Background() - var tests = []struct { - name string - 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 *waterMagnetDownloader) waterWASMDownloader { - downloader.Close() - 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 - // and finish the Read call (along with the io.Copy) - torrentReader.EXPECT().Read(gomock.Any()).DoAndReturn(func(p []byte) (int, error) { - copy(p, []byte("hello world")) - return len(p), io.EOF - }).AnyTimes() - - torrentClient.EXPECT().AddMagnet(downloader.magnetURL).Return(torrentInfo, nil).Times(1) - done := make(chan struct{}) - // send done - torrentInfo.EXPECT().GotInfo().DoAndReturn(func() events.Done { - defer func() { - close(done) - }() - return done - }).Times(1) - torrentInfo.EXPECT().NewReader().Return(torrentReader).AnyTimes() - torrentClient.EXPECT().Close().Return(nil).AnyTimes() - downloader.client = torrentClient - return downloader - }, - givenCtx: ctx, - givenMagnetURL: "", - assert: func(t *testing.T, r io.Reader, err error) { - assert.NoError(t, err) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - 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.(*waterMagnetDownloader)) - } - - b := new(bytes.Buffer) - err = downloader.DownloadWASM(ctx, b) - tt.assert(t, b, err) - }) - } -} diff --git a/chained/water_version_control.go b/chained/water_version_control.go deleted file mode 100644 index 342c0a478..000000000 --- a/chained/water_version_control.go +++ /dev/null @@ -1,178 +0,0 @@ -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) { - // This function implements the following steps: - // 1. Check if the WASM file exists - // 2. If it does not exist, download it - // 3. If it exists, check if it was loaded correctly by checking if the last-loaded file existis - // 4. If it was not loaded correctly, download it again - // 5. If it was loaded correctly, return the file and mark the file as loaded - path := filepath.Join(vc.dir, transport+".wasm") - log.Debugf("trying to load file %q", path) - 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 { - if f != nil { - f.Close() - } - response, err := vc.downloadWASM(ctx, transport, downloader) - if err != nil { - return nil, log.Errorf("failed to download WASM file: %w", err) - } - - return response, nil - } - - lastLoaded, err := os.Open(filepath.Join(vc.dir, transport+".last-loaded")) - if err != nil && !os.IsNotExist(err) { - return nil, log.Errorf("failed to open file %s: %w", transport+".last-loaded", err) - } - - if errors.Is(err, fs.ErrNotExist) { - log.Debugf("%q WASM file exists but never loaded correctly, downloading again", transport) - response, err := vc.downloadWASM(ctx, transport, downloader) - if err != nil { - return nil, log.Errorf("failed to download WASM file: %w", err) - } - return response, nil - } - defer lastLoaded.Close() - - _, err = f.Seek(0, 0) - if err != nil { - return nil, log.Errorf("failed to seek file at the beginning: %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 { - log.Debugf("deleting file: %q", path) - 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") - log.Debugf("downloading WASM file and writing at %q", outputPath) - 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) - } - - if err = vc.markUsed(transport); err != nil { - return nil, log.Errorf("failed to update WASM history: %w", err) - } - - return f, nil -} diff --git a/chained/water_version_control_test.go b/chained/water_version_control_test.go deleted file mode 100644 index 18a1dde37..000000000 --- a/chained/water_version_control_test.go +++ /dev/null @@ -1,123 +0,0 @@ -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/go.mod b/go.mod index 77b288da6..4d3aecef1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/getlantern/flashlight/v7 -go 1.22.4 +go 1.22.9 replace github.com/keighl/mandrill => github.com/getlantern/mandrill v0.0.0-20221004112352-e7c04248adcb @@ -42,6 +42,7 @@ require ( github.com/getlantern/kcpwrapper v0.0.0-20230327091313-c12d7c17c6de github.com/getlantern/keyman v0.0.0-20230503155501-4e864ca2175b github.com/getlantern/lantern-algeneva v0.0.0-20240930181006-6d3c00db1d5d + github.com/getlantern/lantern-water v0.0.0-20241217184729-97b2bf6add4a github.com/getlantern/mockconn v0.0.0-20200818071412-cb30d065a848 github.com/getlantern/mtime v0.0.0-20200417132445-23682092d1f7 github.com/getlantern/multipath v0.0.0-20230510135141-717ed305ef50 @@ -66,7 +67,6 @@ require ( github.com/getlantern/uuid v1.2.0 github.com/getlantern/waitforserver v1.0.1 github.com/getlantern/yaml v0.0.0-20190801163808-0c9bb1ebf426 - github.com/golang/protobuf v1.5.3 github.com/hashicorp/golang-lru v0.5.4 github.com/jaffee/commandeer v0.6.0 github.com/keighl/mandrill v0.0.0-20170605120353-1775dd4b3b41 @@ -79,7 +79,7 @@ require ( github.com/sagernet/sing v0.6.0-alpha.18 github.com/samber/lo v1.38.1 github.com/shadowsocks/go-shadowsocks2 v0.1.5 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/vulcand/oxy v1.4.2 github.com/xtaci/smux v1.5.24 go.opentelemetry.io/otel v1.19.0 @@ -111,6 +111,7 @@ require ( github.com/getsentry/sentry-go v0.20.0 // indirect github.com/go-llsqlite/crawshaw v0.5.1 // indirect github.com/gofrs/uuid/v5 v5.3.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/tetratelabs/wazero v1.7.1 // indirect github.com/vishvananda/netns v0.0.1 // indirect github.com/wlynxg/anet v0.0.3 // indirect @@ -129,7 +130,7 @@ require ( github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alextanhongpin/go-bandit v0.0.0-20191125130111-30de60d69bae - github.com/anacrolix/chansync v0.4.1-0.20240627045151-1aa1ac392fe8 + github.com/anacrolix/chansync v0.4.1-0.20240627045151-1aa1ac392fe8 // indirect github.com/anacrolix/confluence v1.15.0 // indirect github.com/anacrolix/envpprof v1.3.0 // indirect github.com/anacrolix/generics v0.0.3-0.20240902042256-7fb2702ef0ca // indirect @@ -143,7 +144,7 @@ require ( github.com/anacrolix/squirrel v0.6.4 // indirect github.com/anacrolix/stm v0.4.1-0.20221221005312-96d17df0e496 // indirect github.com/anacrolix/sync v0.5.1 // indirect - github.com/anacrolix/torrent v1.53.3 + github.com/anacrolix/torrent v1.53.3 // indirect github.com/anacrolix/upnp v0.1.4 // indirect github.com/anacrolix/utp v0.1.0 // indirect github.com/andybalholm/brotli v1.0.6 // indirect @@ -282,7 +283,7 @@ require ( go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.19.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect - go.uber.org/mock v0.4.0 + go.uber.org/mock v0.5.0 go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.29.0 // indirect diff --git a/go.sum b/go.sum index e12ed910d..1cfdf8f0b 100644 --- a/go.sum +++ b/go.sum @@ -319,6 +319,8 @@ github.com/getlantern/lampshade v0.0.0-20201109225444-b06082e15f3a h1:z7G1v79GB1 github.com/getlantern/lampshade v0.0.0-20201109225444-b06082e15f3a/go.mod h1:cGOfTjvllC9bcwS7cVW6tGT6fXc8Dki384uFjm7XBnw= github.com/getlantern/lantern-algeneva v0.0.0-20240930181006-6d3c00db1d5d h1:ACBwPR4du54Qw+X5ajsbMqOFR8euGZRdMGkvTDS7I60= github.com/getlantern/lantern-algeneva v0.0.0-20240930181006-6d3c00db1d5d/go.mod h1:bDcK4RWBjBO+bBPLTCmaEyYK+n0/w0TrzvSCJOQmkgk= +github.com/getlantern/lantern-water v0.0.0-20241217184729-97b2bf6add4a h1:3i8teo3y32raoP3g0crSwEVjeZvCxjoEIGVbtBhttIw= +github.com/getlantern/lantern-water v0.0.0-20241217184729-97b2bf6add4a/go.mod h1:x9/DBDfoFtVrbFsMuhDQ44kKZc9S4fF8s54IOpQC78A= github.com/getlantern/mandrill v0.0.0-20221004112352-e7c04248adcb h1:oyEMOT9jn4bzKyivF2sVBogsXyL8fBCK7HIT/P6h64Y= github.com/getlantern/mandrill v0.0.0-20221004112352-e7c04248adcb/go.mod h1:gz4iIB+vPk8hWxkAnnZSudQuIpBMnW7i89eHl9Fl+I8= github.com/getlantern/measured v0.0.0-20230919230611-3d9e3776a6cd h1:pDfqh9yd58OW9vQzv4U+q6G+LfbNXVhbWcBWmC5Dkm4= @@ -804,8 +806,9 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/cpu v0.0.8 h1:va6GebSxedVdR5XEyPJD49t94p5ZsjWO6Wh/PfbmZnc= github.com/templexxx/cpu v0.0.8/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= @@ -894,8 +897,8 @@ go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=