diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f4c3a73..dd1aa2b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -15,7 +15,7 @@ jobs: id-token: write steps: - name: Check out the repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Log in to the Container registry uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e80ed59..5dd56da 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,11 +18,12 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 with: go-version: stable - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v8 with: - version: v1.60 + version: v2.4 + args: --timeout=1m diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d907d0a..4fb7eee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,12 +13,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go-version: ["1.23"] + go-version: ["1.24.7", "1.25.1"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} - name: Install dependencies diff --git a/.golangci.yml b/.golangci.yml index 44496c9..d7f06f9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,53 +1,46 @@ -run: - timeout: 30s - -output: - sort-results: true - sort-order: - - file +version: "2" linters: + default: all + disable: + # Annoying linters + - depguard # Needs configuration + - ireturn # Forbids returning interface values + - nlreturn # Requires a newline before return statements + - exhaustruct # Requires all struct fields to be initialized + - nonamedreturns # Forbids named return values + - wsl_v5 # Enforces particular statement grouping using blank lines + - noinlineerr # Forbids inline error handling + - varnamelen # Forbids short variable names + - err113 # Forbids dynamic error declaration without wrapping + - nilnil # Forbids `return nil, nil` as not idiomatic + - paralleltest # Requires parallel tests + - tagliatelle # Enforces a specific struct tag casing + - testpackage # Requires tests to be in a package named `*_test` + - funlen # Enforces function length limits + - godox # Forbids TODO/FIXME comments + - promlinter # Enforces a naming convention + # Others + - wsl # deprecated (since v2.2.0) due to: new major version. Replaced by wsl_v5. + - cyclop # We already use gocyclo + - musttag # Too many false positives + - mnd # None of the numbers should be magic + - lll # Line width is enforced with exceptions + settings: + gocyclo: + min-complexity: 12 + nestif: + min-complexity: 6 + +formatters: enable: - - bodyclose - - containedctx - - contextcheck - - copyloopvar - - decorder - - dupl - - dupword - - durationcheck - - errchkjson - - errname - - errorlint - - gochecknoglobals - - goconst - goimports - - gocritic - - gocyclo - - gosec - - makezero - - mirror - - nestif - - nilerr - - perfsprint - - prealloc - - revive - - stylecheck - - unconvert - - unused - - usestdlibvars - - wastedassign - - whitespace -linters-settings: - gocyclo: - min-complexity: 12 - nestif: - min-complexity: 6 +output: + sort-order: + - file + - linter + show-stats: true -issues: - include: - # don't suppress missing exported method/function parameters - - EXC0012 - # don't suppress missing package level comment - - EXC0015 +run: + timeout: 5m diff --git a/cmd/convert/main.go b/cmd/convert/main.go index ca6ff34..2102db6 100644 --- a/cmd/convert/main.go +++ b/cmd/convert/main.go @@ -1,6 +1,7 @@ // This package allows to convert datamodels into a CSV format that is supported // by this simulator. -// nolint:gochecknoglobals +// +//nolint:gochecknoglobals,gosec package main import ( @@ -63,7 +64,11 @@ func save(path string, params []datamodel.Parameter) error { if err != nil { return fmt.Errorf("create destination file: %s", path) } - defer fd.Close() + defer func() { + if err := fd.Close(); err != nil { + log.Error("Failed to close file", log.Cause(err), log.F{"path": path}) + } + }() slices.SortFunc(params, func(a, b datamodel.Parameter) int { return cmp.Compare(strings.ToLower(a.Path), strings.ToLower(b.Path)) @@ -88,8 +93,6 @@ func save(path string, params []datamodel.Parameter) error { return nil } -// FIXME: cyclo complexity is too high, rewrite it -// nolint:gocyclo,musttag func convertGetParameterValuesResponse(b []byte) []datamodel.Parameter { var gpv struct { XMLName xml.Name `xml:"GetParameterValuesResponse"` diff --git a/datamodel/common.go b/datamodel/common.go index f4c735c..53d92ab 100644 --- a/datamodel/common.go +++ b/datamodel/common.go @@ -75,7 +75,8 @@ func (dm *DataModel) UDPConnectionRequestAddress() Parameter { return p } -// SetConnectionRequestURL sets UDP connection request address to the given value. +// SetUDPConnectionRequestAddress sets UDP connection request address to the +// given value. func (dm *DataModel) SetUDPConnectionRequestAddress(val string) { dm.SetValue(pathUDPConnectionRequestAddress, val) } diff --git a/datamodel/datamodel.go b/datamodel/datamodel.go index 350e8f4..05a1881 100644 --- a/datamodel/datamodel.go +++ b/datamodel/datamodel.go @@ -219,7 +219,6 @@ func (dm *DataModel) DeleteObject(name string) { // ParameterNames returns all subparameters in the given path. If nextLevel is // set to true the list of parameters goes one level deeper. -// nolint:nestif func (dm *DataModel) ParameterNames(path string, nextLevel bool) []Parameter { var reg *regexp.Regexp if path == "" { @@ -381,6 +380,8 @@ func (dm *DataModel) prefixedPath(path string) string { return path } return tr181Prefix + path + case unknownVersion: + fallthrough default: return path } diff --git a/datamodel/datamodel_test.go b/datamodel/datamodel_test.go index 7222a40..d6b5ce6 100644 --- a/datamodel/datamodel_test.go +++ b/datamodel/datamodel_test.go @@ -151,7 +151,7 @@ func TestGetValueFormatGen(t *testing.T) { })) param, ok := dm.GetValue(path2) assert.True(t, ok) - assert.Equal(t, "", param.GetValue()) + assert.Empty(t, param.GetValue()) }) } @@ -277,14 +277,14 @@ func TestAddObject(t *testing.T) { dm := New(newState()) dm.SetValue("Device.DeviceInfo.Description", "Residential Gateway") _, err := dm.AddObject("Device.DeviceInfo.Description") - assert.NotNil(t, err) + require.Error(t, err) assert.Equal(t, errors.New("parent is not an object"), err) } func TestAddObjectNonExistentParent(t *testing.T) { dm := New(newState()) _, err := dm.AddObject("Device.NonExistent.Parent") - assert.NotNil(t, err) + require.Error(t, err) assert.Equal(t, errors.New("parent object doesn't exist"), err) } @@ -292,7 +292,7 @@ func TestAddObjectParentNotObject(t *testing.T) { dm := New(newState()) dm.SetValue("Device.DeviceInfo", "Some Value") _, err := dm.AddObject("Device.DeviceInfo") - assert.NotNil(t, err) + require.Error(t, err) assert.Equal(t, errors.New("parent is not an object"), err) } @@ -320,7 +320,7 @@ func TestParameterNamesEmptyPath(t *testing.T) { }, })) params := dm.ParameterNames("", true) - assert.Len(t, params, 0) + assert.Empty(t, params) } func TestParameterNamesNoMatch(t *testing.T) { @@ -332,7 +332,7 @@ func TestParameterNamesNoMatch(t *testing.T) { }, })) params := dm.ParameterNames("Device.Ethernet", true) - assert.Len(t, params, 0) + assert.Empty(t, params) } func TestParameterNamesNextLevel(t *testing.T) { diff --git a/datamodel/noise/generator.go b/datamodel/noise/generator.go index b8a6aca..5b68416 100644 --- a/datamodel/noise/generator.go +++ b/datamodel/noise/generator.go @@ -1,6 +1,8 @@ // Package noise provides algorithms to generate sequences of values that // simulate sensor readings with various patterns, including random walk, // piecewise linear, sine wave with noise, and Perlin noise. +// +//nolint:gosec package noise import ( @@ -18,8 +20,6 @@ import ( func RandomWalk(startValue, minValue, maxValue, step float64) Generator { prevValue := startValue return func() float64 { - // nolint:gosec - // It's okay to use the default random number generator here. change := (rand.Float64()*2 - 1) * step newValue := clamp(prevValue+change, minValue, maxValue) prevValue = newValue @@ -39,8 +39,6 @@ func PiecewiseLinear(startValue, minValue, maxValue, step float64) Generator { if i%20 == 0 { direction *= -1 } - // nolint:gosec - // It's okay to use the default random number generator here. change := direction*step + (rand.Float64()*2-1)*(step/2) newValue := clamp(prevValue+change, minValue, maxValue) prevValue = newValue @@ -67,8 +65,6 @@ func PiecewiseLinear(startValue, minValue, maxValue, step float64) Generator { func SineWithNoise(offset, amplitude, frequency, phase, noiseScale float64) Generator { i := 0 return func() float64 { - // nolint:gosec - // It's okay to use the default random number generator here. value := offset + amplitude*math.Sin(frequency*float64(i)+phase) + rand.Float64()*noiseScale i++ return value @@ -87,10 +83,11 @@ func SineWithNoise(offset, amplitude, frequency, phase, noiseScale float64) Gene // - scale: A scaling factor to adjust the amplitude of the noise. // - offset: A constant value to be added to the generated noise values. func PerlinNoise(offset, alpha, beta float64, scale float64) Generator { + const noiseFactor = 0.1 p := perlin.NewPerlin(alpha, beta, 3, time.Now().UnixNano()) i := 0 return func() float64 { - value := p.Noise1D(float64(i) * 0.1) + value := p.Noise1D(float64(i) * noiseFactor) i++ return offset + scale*value } @@ -110,8 +107,6 @@ func TrendWithNoise(startValue, step, noiseScale float64) func() float64 { prevValue := startValue return func() float64 { newValue := prevValue + step - // nolint:gosec - // It's okay to use the default random number generator here. noise := (rand.Float64()*2 - 1) * noiseScale if step < 0 { newValue = min(newValue, newValue+noise) diff --git a/datamodel/noise/generator_test.go b/datamodel/noise/generator_test.go index 87bf3c7..3867f18 100644 --- a/datamodel/noise/generator_test.go +++ b/datamodel/noise/generator_test.go @@ -21,7 +21,7 @@ const ( func TestRandomWalkBounds(t *testing.T) { gen := RandomWalk(startValue, minValue, maxValue, step) - for i := 0; i < 100; i++ { + for range 100 { value := gen() if value < minValue || value > maxValue { t.Errorf("Value out of bounds: got %v, want between %v and %v", value, minValue, maxValue) @@ -31,7 +31,7 @@ func TestRandomWalkBounds(t *testing.T) { func TestPiecewiseLinearBounds(t *testing.T) { gen := PiecewiseLinear(startValue, minValue, maxValue, step) - for i := 0; i < 100; i++ { + for range 100 { value := gen() if value < minValue || value > maxValue { t.Errorf("Value out of bounds: got %v, want between %v and %v", value, minValue, maxValue) @@ -41,7 +41,7 @@ func TestPiecewiseLinearBounds(t *testing.T) { func TestSineWithNoiseBounds(t *testing.T) { gen := SineWithNoise(offset, amplitude, frequency, phase, noiseScale) - for i := 0; i < 100; i++ { + for range 100 { value := gen() // Since sine wave values range between -amplitude and +amplitude, we add noiseScale to the bounds if value < -amplitude-noiseScale || value > amplitude+noiseScale { @@ -52,7 +52,7 @@ func TestSineWithNoiseBounds(t *testing.T) { func TestPerlinNoiseBounds(t *testing.T) { gen := PerlinNoise(offset, alpha, beta, scale) - for i := 0; i < 100; i++ { + for range 100 { value := gen() // Perlin noise values are typically between -1 and 1, scaled and offset if value < -scale+offset || value > scale+offset { @@ -63,7 +63,7 @@ func TestPerlinNoiseBounds(t *testing.T) { func TestTrendWithNoiseBounds(t *testing.T) { gen := TrendWithNoise(startValue, step, noiseScale) - for i := 0; i < 100; i++ { + for range 100 { value := gen() // Since the trend can go indefinitely, we only check that the noise does not exceed the noiseScale if step >= 0 && value < startValue || step < 0 && value > startValue { @@ -74,21 +74,21 @@ func TestTrendWithNoiseBounds(t *testing.T) { func BenchmarkRandomWalk(b *testing.B) { gen := RandomWalk(startValue, minValue, maxValue, step) - for i := 0; i < b.N; i++ { + for b.Loop() { gen() } } func BenchmarkPiecewiseLinear(b *testing.B) { gen := PiecewiseLinear(startValue, minValue, maxValue, step) - for i := 0; i < b.N; i++ { + for b.Loop() { gen() } } func BenchmarkSineWithNoise(b *testing.B) { gen := SineWithNoise(offset, amplitude, frequency, phase, noiseScale) - for i := 0; i < b.N; i++ { + for b.Loop() { gen() } } @@ -96,14 +96,14 @@ func BenchmarkSineWithNoise(b *testing.B) { func BenchmarkPerlinNoise(b *testing.B) { gen := PerlinNoise(offset, alpha, beta, scale) b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { gen() } } func BenchmarkTrendWithNoise(b *testing.B) { gen := TrendWithNoise(startValue, step, noiseScale) - for i := 0; i < b.N; i++ { + for b.Loop() { gen() } } diff --git a/datamodel/noise/parser_test.go b/datamodel/noise/parser_test.go index 209870e..1fde58d 100644 --- a/datamodel/noise/parser_test.go +++ b/datamodel/noise/parser_test.go @@ -190,10 +190,10 @@ func TestGenerator(t *testing.T) { t.Run(tt.name, func(t *testing.T) { gen, err := tt.fn.Generator() if tt.err != "" { - assert.ErrorContains(t, err, tt.err) + require.ErrorContains(t, err, tt.err) assert.Nil(t, gen) } else { - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, gen) } }) diff --git a/datamodel/notification.go b/datamodel/notification.go index d15093a..4cf3044 100644 --- a/datamodel/notification.go +++ b/datamodel/notification.go @@ -43,6 +43,8 @@ func (dm *DataModel) ForcedInformParameters() []string { "RootDataModelVersion", "ManagementServer.AliasBasedAddressing", ) + case unknownVersion: + fallthrough default: return common } diff --git a/datamodel/parameter.go b/datamodel/parameter.go index b4b5574..a2f1c68 100644 --- a/datamodel/parameter.go +++ b/datamodel/parameter.go @@ -34,7 +34,7 @@ func NormalizeParameters(params map[string]Parameter) { } // Name returns parameter name. -func (p Parameter) Name() string { +func (p *Parameter) Name() string { tokens := strings.Split(p.Path, ".") return tokens[len(tokens)-1] } @@ -42,7 +42,7 @@ func (p Parameter) Name() string { // GetValue returns a parameter value. If the parameter has a generator function // it will be used to produce a value, otherwise the value from the parameter // will be returned. -func (p Parameter) GetValue() string { +func (p *Parameter) GetValue() string { if p.gen != nil { switch rpc.NoXSD(p.Type) { case rpc.TypeInt, rpc.TypeLong: @@ -61,7 +61,7 @@ func (p Parameter) GetValue() string { } // Encode converts a parameter into RPC ParameterValue structure. -func (p Parameter) Encode() rpc.ParameterValueEncoder { +func (p *Parameter) Encode() rpc.ParameterValueEncoder { return rpc.ParameterValueEncoder{ Name: p.Path, Value: rpc.ValueEncoder{ @@ -117,7 +117,7 @@ func (p *Parameter) initGenerator() error { return nil } -// TODO: implement value ranges +// TODO: implement value ranges. func normalizeValue(td *typeDef, name, val string) string { val = strings.TrimSpace(val) switch td.name { diff --git a/datamodel/parameter_test.go b/datamodel/parameter_test.go index 90fdf6a..dd55ec3 100644 --- a/datamodel/parameter_test.go +++ b/datamodel/parameter_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNormalizeParameters(t *testing.T) { @@ -68,7 +69,7 @@ func TestNormalizeParameters(t *testing.T) { param := params["Device.DeviceInfo.DeviceCategory"] assert.Equal(t, "xsd:string", param.Type) - assert.Equal(t, "", param.GetValue()) + assert.Empty(t, param.GetValue()) param = params["Device.DeviceInfo.DeviceImageNumberOfEntries"] assert.Equal(t, "xsd:unsignedInt", param.Type) @@ -81,7 +82,7 @@ func TestNormalizeParameters(t *testing.T) { param = params["Device.DeviceInfo.MemoryStatus"] assert.Equal(t, "Device.DeviceInfo.MemoryStatus", param.Path) assert.Equal(t, "object", param.Type) - assert.Equal(t, "", param.GetValue()) + assert.Empty(t, param.GetValue()) param = params["Device.DeviceInfo.MemoryStatus.Free"] assert.Equal(t, "xsd:unsignedInt", param.Type) @@ -203,7 +204,7 @@ Parameter,Object,Writable,Value,Type Device.DeviceInfo.ProcessStatus.CPUUsage,false,true,"perlinNoise(offset=50, alpha=2, beta=2, scale=40) as xsd:int",sim:generator `)) params, err := LoadDataModel(fd) - assert.NoError(t, err) + require.NoError(t, err) param := params["Device.DeviceInfo.ProcessStatus.CPUUsage"] val := param.GetValue() assert.Equal(t, "50", val) diff --git a/datamodel/state.go b/datamodel/state.go index ae47c5b..fde70d0 100644 --- a/datamodel/state.go +++ b/datamodel/state.go @@ -9,9 +9,9 @@ import ( // deletions, and default values. It uses a read-write mutex to ensure thread- // safe access and modifications. type State struct { - Bootstrapped bool - Changes map[string]Parameter - Deleted map[string]struct{} + Bootstrapped bool `json:"Bootstrapped"` + Changes map[string]Parameter `json:"Changes"` + Deleted map[string]struct{} `json:"Deleted"` defaults map[string]Parameter lock sync.RWMutex } diff --git a/datamodel/storage.go b/datamodel/storage.go index d215fe1..f1aac30 100644 --- a/datamodel/storage.go +++ b/datamodel/storage.go @@ -8,6 +8,8 @@ import ( "io" "os" "strconv" + + "github.com/localhots/blip/noctx/log" ) // LoadState loads the state from the specified file path. If the file path @@ -21,6 +23,8 @@ func LoadState(filePath string) (*State, error) { return newState(), nil } + // Assume the file is trusted + //nolint:gosec b, err := os.ReadFile(filePath) if err != nil && !os.IsNotExist(err) { return nil, fmt.Errorf("read state file: %w", err) @@ -36,11 +40,17 @@ func LoadState(filePath string) (*State, error) { // LoadDataModelFile loads the data model from the specified file path. func LoadDataModelFile(filePath string) (map[string]Parameter, error) { + // Assume the file is trusted + //nolint:gosec fd, err := os.Open(filePath) if err != nil { return nil, fmt.Errorf("read datamodel file: %w", err) } - defer fd.Close() + defer func() { + if err := fd.Close(); err != nil { + log.Error("Failed to close datamodel file", log.Cause(err), log.F{"path": filePath}) + } + }() return LoadDataModel(fd) } @@ -57,7 +67,7 @@ func LoadDataModel(r io.Reader) (map[string]Parameter, error) { var headerRead bool for { f, err := csvr.Read() - // nolint:errorlint + //nolint:errorlint if err == io.EOF { break } diff --git a/datamodel/type_def.go b/datamodel/type_def.go index 7b2d0bd..530a579 100644 --- a/datamodel/type_def.go +++ b/datamodel/type_def.go @@ -57,6 +57,16 @@ func parseTypeDef(str string) (*typeDef, error) { return td.normalize(), nil } +func (td *typeDef) String() string { + if td.min != nil { + return fmt.Sprintf("%s(%d:%d)", rpc.XSD(td.name), *td.min, *td.max) + } + if td.max != nil { + return fmt.Sprintf("%s(%d)", rpc.XSD(td.name), *td.max) + } + return rpc.XSD(td.name) +} + func (td *typeDef) normalize() *typeDef { switch td.name { case rpc.TypeBase64, @@ -86,13 +96,3 @@ func (td *typeDef) normalize() *typeDef { } return td } - -func (td typeDef) String() string { - if td.min != nil { - return fmt.Sprintf("%s(%d:%d)", rpc.XSD(td.name), *td.min, *td.max) - } - if td.max != nil { - return fmt.Sprintf("%s(%d)", rpc.XSD(td.name), *td.max) - } - return rpc.XSD(td.name) -} diff --git a/datamodel/type_def_test.go b/datamodel/type_def_test.go index 55c362d..652fa6d 100644 --- a/datamodel/type_def_test.go +++ b/datamodel/type_def_test.go @@ -53,26 +53,22 @@ func TestParseTypeDef(t *testing.T) { }) t.Run("invalid+", func(t *testing.T) { td, err := parseTypeDef("invalid+") - require.Error(t, err) - assert.EqualError(t, err, "invalid type definition") + require.EqualError(t, err, "invalid type definition") require.Nil(t, td) }) t.Run("invalid(:50)", func(t *testing.T) { td, err := parseTypeDef("invalid(:50)") - require.Error(t, err) - assert.EqualError(t, err, "invalid type definition") + require.EqualError(t, err, "invalid type definition") require.Nil(t, td) }) t.Run("invalid(x:50)", func(t *testing.T) { td, err := parseTypeDef("invalid(x:50)") - require.Error(t, err) - assert.EqualError(t, err, "invalid type definition") + require.EqualError(t, err, "invalid type definition") require.Nil(t, td) }) t.Run("int(0:9999999999999999999999)", func(t *testing.T) { td, err := parseTypeDef("int(0:9999999999999999999999)") - require.Error(t, err) - assert.EqualError(t, err, `parse type max: strconv.Atoi: parsing "9999999999999999999999": value out of range`) + require.EqualError(t, err, `parse type max: strconv.Atoi: parsing "9999999999999999999999": value out of range`) require.Nil(t, td) }) } diff --git a/go.mod b/go.mod index fc5744c..8707a77 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/localhots/SimulaTR69 -go 1.23.4 - -toolchain go1.24.6 +go 1.24.7 require ( github.com/aquilax/go-perlin v1.1.0 diff --git a/rpc/decoder.go b/rpc/decoder.go index 4d08f4c..135c349 100644 --- a/rpc/decoder.go +++ b/rpc/decoder.go @@ -1,4 +1,4 @@ -// nolint:revive +//nolint:revive package rpc import ( @@ -62,10 +62,10 @@ type SetParameterValuesRequest struct { ParameterKey string } -func (r SetParameterValuesRequest) Debug(logger *blip.Logger) { - logger.Info(context.TODO(), "Received message", log.F{"method": "SetParameterValues"}) +func (r SetParameterValuesRequest) Debug(ctx context.Context, logger *blip.Logger) { + logger.Info(ctx, "Received message", log.F{"method": "SetParameterValues"}) for _, v := range r.ParameterList.ParameterValues { - logger.Debug(context.TODO(), "SetParameterValues", log.F{ + logger.Debug(ctx, "SetParameterValues", log.F{ "name": v.Name, "type": v.Value.Type, "value": v.Value.Value, @@ -77,10 +77,10 @@ type GetParameterValuesRequest struct { ParameterNames ParameterNames } -func (r GetParameterValuesRequest) Debug(logger *blip.Logger) { - logger.Info(context.TODO(), "Received message", log.F{"method": "GetParameterValues"}) +func (r GetParameterValuesRequest) Debug(ctx context.Context, logger *blip.Logger) { + logger.Info(ctx, "Received message", log.F{"method": "GetParameterValues"}) for _, name := range r.ParameterNames.Names { - logger.Debug(context.TODO(), "GetParameterValues", log.F{"name": name}) + logger.Debug(ctx, "GetParameterValues", log.F{"name": name}) } } @@ -89,9 +89,9 @@ type GetParameterNamesRequest struct { NextLevel bool } -func (r GetParameterNamesRequest) Debug(logger *blip.Logger) { - logger.Info(context.TODO(), "Received message", log.F{"method": "GetParameterNames"}) - logger.Debug(context.TODO(), "GetParameterNames", log.F{ +func (r GetParameterNamesRequest) Debug(ctx context.Context, logger *blip.Logger) { + logger.Info(ctx, "Received message", log.F{"method": "GetParameterNames"}) + logger.Debug(ctx, "GetParameterNames", log.F{ "name": r.ParameterPath, "next_level": r.NextLevel, }) @@ -104,10 +104,10 @@ type SetParameterAttributesRequest struct { } } -func (r SetParameterAttributesRequest) Debug(logger *blip.Logger) { - logger.Info(context.TODO(), "Received message", log.F{"method": "SetParameterAttributes"}) +func (r SetParameterAttributesRequest) Debug(ctx context.Context, logger *blip.Logger) { + logger.Info(ctx, "Received message", log.F{"method": "SetParameterAttributes"}) for _, attr := range r.ParameterList.ParameterAttributes { - logger.Debug(context.TODO(), "SetParameterAttributes", log.F{ + logger.Debug(ctx, "SetParameterAttributes", log.F{ "name": attr.Name, "notification": int(attr.Notification), "notification_change": attr.NotificationChange, @@ -121,10 +121,10 @@ type GetParameterAttributesRequest struct { ParameterNames ParameterNames } -func (r GetParameterAttributesRequest) Debug(logger *blip.Logger) { - logger.Info(context.TODO(), "Received message", log.F{"method": "GetParameterAttributes"}) +func (r GetParameterAttributesRequest) Debug(ctx context.Context, logger *blip.Logger) { + logger.Info(ctx, "Received message", log.F{"method": "GetParameterAttributes"}) for _, path := range r.ParameterNames.Names { - logger.Debug(context.TODO(), "GetParameterAttributes", log.F{"name": path}) + logger.Debug(ctx, "GetParameterAttributes", log.F{"name": path}) } } @@ -133,9 +133,9 @@ type AddObjectRequest struct { ParameterKey string } -func (r AddObjectRequest) Debug(logger *blip.Logger) { - logger.Info(context.TODO(), "Received message", log.F{"method": "AddObject"}) - logger.Debug(context.TODO(), "AddObjectRequest", log.F{"path": r.ObjectName}) +func (r AddObjectRequest) Debug(ctx context.Context, logger *blip.Logger) { + logger.Info(ctx, "Received message", log.F{"method": "AddObject"}) + logger.Debug(ctx, "AddObjectRequest", log.F{"path": r.ObjectName}) } type DeleteObjectRequest struct { @@ -143,9 +143,9 @@ type DeleteObjectRequest struct { ParameterKey string } -func (r DeleteObjectRequest) Debug(logger *blip.Logger) { - logger.Info(context.TODO(), "Received message", log.F{"method": "DeleteObject"}) - logger.Debug(context.TODO(), "DeleteObjectRequest", log.F{"path": r.ObjectName}) +func (r DeleteObjectRequest) Debug(ctx context.Context, logger *blip.Logger) { + logger.Info(ctx, "Received message", log.F{"method": "DeleteObject"}) + logger.Debug(ctx, "DeleteObjectRequest", log.F{"path": r.ObjectName}) } type RebootRequest struct { @@ -165,9 +165,9 @@ type DownloadRequest struct { FailureURL string } -func (r DownloadRequest) Debug(logger *blip.Logger) { - logger.Info(context.TODO(), "Received message", log.F{"method": "Download"}) - logger.Debug(context.TODO(), "DownloadRequest", log.F{ +func (r DownloadRequest) Debug(ctx context.Context, logger *blip.Logger) { + logger.Info(ctx, "Received message", log.F{"method": "Download"}) + logger.Debug(ctx, "DownloadRequest", log.F{ "file_type": r.FileType, "url": r.URL, "file_size": r.FileSize, @@ -178,9 +178,9 @@ type UploadRequest struct { CommandKey string } -func (r UploadRequest) Debug(logger *blip.Logger) { - logger.Info(context.TODO(), "Received message", log.F{"method": "Upload"}) - logger.Debug(context.TODO(), "UploadRequest", log.F{"command_key": r.CommandKey}) +func (r UploadRequest) Debug(ctx context.Context, logger *blip.Logger) { + logger.Info(ctx, "Received message", log.F{"method": "Upload"}) + logger.Debug(ctx, "UploadRequest", log.F{"command_key": r.CommandKey}) } type ScheduleInformRequest struct { @@ -253,7 +253,7 @@ func Decode(b []byte) (*EnvelopeDecoder, error) { return &env, nil } -// nolint:gocyclo +//nolint:gocyclo func (env EnvelopeDecoder) Method() string { switch { case env.Body.GetRPCMethods != nil: diff --git a/rpc/decoder_test.go b/rpc/decoder_test.go index 86af59e..7178143 100644 --- a/rpc/decoder_test.go +++ b/rpc/decoder_test.go @@ -62,7 +62,7 @@ func TestDecodeGetParameterNamesRequest(t *testing.T) { pn := env.Body.GetParameterNames assert.Equal(t, "Device.", pn.ParameterPath) - assert.Equal(t, false, pn.NextLevel) + assert.False(t, pn.NextLevel) } func TestDecodeSetParameterAttributesRequest(t *testing.T) { @@ -75,9 +75,9 @@ func TestDecodeSetParameterAttributesRequest(t *testing.T) { pa := pl.ParameterAttributes[0] assert.Equal(t, "Device.DeviceSummary", pa.Name) - assert.Equal(t, true, pa.NotificationChange) + assert.True(t, pa.NotificationChange) assert.Equal(t, AttributeNotificationPassive, pa.Notification) - assert.Equal(t, true, pa.AccessListChange) + assert.True(t, pa.AccessListChange) assert.Equal(t, ArrayType(XSD(TypeString), 1), pa.AccessList.ArrayType) require.Len(t, pa.AccessList.Values, 1) assert.Equal(t, "Subscriber", pa.AccessList.Values[0]) diff --git a/rpc/encoder.go b/rpc/encoder.go index 821e5e8..44d8437 100644 --- a/rpc/encoder.go +++ b/rpc/encoder.go @@ -1,4 +1,4 @@ -// nolint:revive +//nolint:revive package rpc import ( @@ -50,7 +50,6 @@ type BodyEncoder struct { // Payloads // -// nolint:stylecheck type InformRequestEncoder struct { DeviceId DeviceID Event EventEncoder @@ -230,7 +229,7 @@ func NewEnvelope(id string) *EnvelopeEncoder { } } -// nolint:gocyclo +//nolint:gocyclo func (ee *EnvelopeEncoder) Method() string { switch { case ee == nil: @@ -280,15 +279,15 @@ func (ee *EnvelopeEncoder) WithFaultMsg(fault FaultCode, msg string) *EnvelopeEn return ee } -func (ee EnvelopeEncoder) Encode() ([]byte, error) { +func (ee *EnvelopeEncoder) Encode() ([]byte, error) { return ee.encode(false) } -func (ee EnvelopeEncoder) EncodePretty() ([]byte, error) { +func (ee *EnvelopeEncoder) EncodePretty() ([]byte, error) { return ee.encode(true) } -func (ee EnvelopeEncoder) encode(pretty bool) ([]byte, error) { +func (ee *EnvelopeEncoder) encode(pretty bool) ([]byte, error) { buf := bytes.Buffer{} if _, err := buf.WriteString(xml.Header); err != nil { return nil, fmt.Errorf("write xml header: %w", err) diff --git a/rpc/encoder_test.go b/rpc/encoder_test.go index 03df33d..99df4b2 100644 --- a/rpc/encoder_test.go +++ b/rpc/encoder_test.go @@ -191,7 +191,7 @@ func TestEncodeDeleteObjectResponse(t *testing.T) { } b, err := env.EncodePretty() - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, string(deleteObjectResponseTestData), string(b)) } diff --git a/rpc/fault.go b/rpc/fault.go index d4e5301..9f07193 100644 --- a/rpc/fault.go +++ b/rpc/fault.go @@ -1,4 +1,4 @@ -// nolint:revive +//nolint:revive package rpc //go:generate go run golang.org/x/tools/cmd/stringer@latest -type=FaultCode -trimprefix=Fault -output=fault_string.go diff --git a/rpc/rpc.go b/rpc/rpc.go index 35d605d..36056cc 100644 --- a/rpc/rpc.go +++ b/rpc/rpc.go @@ -1,4 +1,4 @@ -// nolint:revive +//nolint:revive package rpc import ( diff --git a/rpc/rpc_test.go b/rpc/rpc_test.go index 01d69bb..479982a 100644 --- a/rpc/rpc_test.go +++ b/rpc/rpc_test.go @@ -4,7 +4,7 @@ import _ "embed" var ( // - // Requests + // Requests. // //go:embed test_data/get_rpc_methods_request.xml @@ -56,7 +56,7 @@ var ( autonomousTransferCompleteRequestTestData []byte // - // Responses + // Responses. // //go:embed test_data/inform_response.xml diff --git a/simulator/config.go b/simulator/config.go index 5a4fa7b..890d78c 100644 --- a/simulator/config.go +++ b/simulator/config.go @@ -10,7 +10,8 @@ import ( ) // Config is a global configuration store. -// nolint:gochecknoglobals +// +//nolint:gochecknoglobals var Config struct { // LogLevel controls how verbose the levels are. Supported values: trace, // debug, info, warn, error, fatal, panic. diff --git a/simulator/download.go b/simulator/download.go index 01cb804..36e6b44 100644 --- a/simulator/download.go +++ b/simulator/download.go @@ -14,7 +14,7 @@ import ( "github.com/localhots/SimulaTR69/rpc" ) -func (s *Simulator) handleDownload(envID string, r *rpc.DownloadRequest) *rpc.EnvelopeEncoder { +func (s *Simulator) handleDownload(ctx context.Context, envID string, r *rpc.DownloadRequest) *rpc.EnvelopeEncoder { resp := rpc.NewEnvelope(envID) resp.Body.DownloadResponse = &rpc.DownloadResponseEncoder{ Status: rpc.DownloadNotCompleted, @@ -29,7 +29,7 @@ func (s *Simulator) handleDownload(envID string, r *rpc.DownloadRequest) *rpc.En StartTime: time.Now().UTC().Format(time.RFC3339), Fault: &rpc.FaultStruct{}, } - err := s.upgradeFirmware(r) + err := s.upgradeFirmware(ctx, r) tcr.CompleteTime = time.Now().UTC().Format(time.RFC3339) if err != nil { tcr.Fault = &rpc.FaultStruct{ @@ -44,9 +44,9 @@ func (s *Simulator) handleDownload(envID string, r *rpc.DownloadRequest) *rpc.En s.pendingEvents <- rpc.EventTransferComplete return func() taskFn { - s.logger.Debug(context.TODO(), "Simulating firmware upgrade", log.F{"delay": Config.UpgradeDelay}) + s.logger.Debug(ctx, "Simulating firmware upgrade", log.F{"delay": Config.UpgradeDelay}) s.pretendOfflineFor(Config.UpgradeDelay) - s.logger.Debug(context.TODO(), "Starting up") + s.logger.Debug(ctx, "Starting up") s.pendingEvents <- rpc.EventBoot return nil } @@ -55,8 +55,8 @@ func (s *Simulator) handleDownload(envID string, r *rpc.DownloadRequest) *rpc.En return resp } -func (s *Simulator) upgradeFirmware(r *rpc.DownloadRequest) error { - req, err := http.NewRequest(http.MethodGet, r.URL, nil) +func (s *Simulator) upgradeFirmware(ctx context.Context, r *rpc.DownloadRequest) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, r.URL, nil) if err != nil { return fmt.Errorf("create new request: %w", err) } @@ -64,7 +64,7 @@ func (s *Simulator) upgradeFirmware(r *rpc.DownloadRequest) error { req.SetBasicAuth(r.Username, r.Password) } - s.logger.Debug(context.TODO(), "Downloading file", log.F{"url": r.URL}) + s.logger.Debug(ctx, "Downloading file", log.F{"url": r.URL}) hresp, err := http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("make request: %w", err) @@ -72,7 +72,11 @@ func (s *Simulator) upgradeFirmware(r *rpc.DownloadRequest) error { if hresp.Body == nil { return errors.New("empty download") } - defer hresp.Body.Close() + defer func() { + if err := hresp.Body.Close(); err != nil { + s.logger.Error(ctx, "Failed to close response body", log.Cause(err)) + } + }() b, err := io.ReadAll(hresp.Body) if err != nil { return fmt.Errorf("download: %w", err) @@ -82,7 +86,7 @@ func (s *Simulator) upgradeFirmware(r *rpc.DownloadRequest) error { return nil } - s.logger.Debug(context.TODO(), "Parsing firmware file") + s.logger.Debug(ctx, "Parsing firmware file") var ver struct { Version string `json:"version"` } @@ -93,7 +97,7 @@ func (s *Simulator) upgradeFirmware(r *rpc.DownloadRequest) error { return errors.New("incompatible firmware") } - s.logger.Info(context.TODO(), "Upgrading firmware", log.F{"version": ver.Version}) + s.logger.Info(ctx, "Upgrading firmware", log.F{"version": ver.Version}) s.dm.SetFirmwareVersion(ver.Version) return nil } diff --git a/simulator/factory_reset.go b/simulator/factory_reset.go index bbabb98..5ed5f7d 100644 --- a/simulator/factory_reset.go +++ b/simulator/factory_reset.go @@ -8,12 +8,12 @@ import ( "github.com/localhots/SimulaTR69/rpc" ) -func (s *Simulator) handleFactoryReset(envID string) *rpc.EnvelopeEncoder { +func (s *Simulator) handleFactoryReset(ctx context.Context, envID string) *rpc.EnvelopeEncoder { resp := rpc.NewEnvelope(envID) resp.Body.FactoryResetResponse = &rpc.FactoryResetResponseEncoder{} s.tasks <- func() taskFn { - s.logger.Debug(context.TODO(), "Simulating factory reset", log.F{"delay": Config.UpgradeDelay}) + s.logger.Debug(ctx, "Simulating factory reset", log.F{"delay": Config.UpgradeDelay}) s.pretendOfflineFor(Config.UpgradeDelay) s.dm.Reset() @@ -23,7 +23,7 @@ func (s *Simulator) handleFactoryReset(envID string) *rpc.EnvelopeEncoder { s.dm.SetSerialNumber(Config.SerialNumber) } - s.logger.Debug(context.TODO(), "Starting up") + s.logger.Debug(ctx, "Starting up") s.pendingEvents <- rpc.EventBootstrap return nil } diff --git a/simulator/get_rpc_methods.go b/simulator/get_rpc_methods.go index 213f2b4..5e801cc 100644 --- a/simulator/get_rpc_methods.go +++ b/simulator/get_rpc_methods.go @@ -8,11 +8,11 @@ import ( "github.com/localhots/SimulaTR69/rpc" ) -func (s *Simulator) handleGetRPCMethods(envID string) *rpc.EnvelopeEncoder { - s.logger.Info(context.TODO(), "Received message", log.F{"method": "GetRPCMethods"}) +func (s *Simulator) handleGetRPCMethods(ctx context.Context, envID string) *rpc.EnvelopeEncoder { + s.logger.Info(ctx, "Received message", log.F{"method": "GetRPCMethods"}) methods := rpc.SupportedMethods() for _, m := range methods { - s.logger.Debug(context.TODO(), "GetRPCMethodsResponse", log.F{"method": m}) + s.logger.Debug(ctx, "GetRPCMethodsResponse", log.F{"method": m}) } resp := rpc.NewEnvelope(envID) resp.Body.GetRPCMethodsResponse = &rpc.GetRPCMethodsResponseEncoder{ diff --git a/simulator/inform.go b/simulator/inform.go index e999eaa..3fa6102 100644 --- a/simulator/inform.go +++ b/simulator/inform.go @@ -28,11 +28,11 @@ type ( func (s *Simulator) periodicInform(ctx context.Context) { for !s.stopped() { if !s.dm.PeriodicInformEnabled() { - s.logger.Info(context.TODO(), "Periodic inform disabled") + s.logger.Info(ctx, "Periodic inform disabled") } delay := time.Until(s.nextInformTime()) - s.logger.Info(context.TODO(), "Scheduling next Inform request", log.F{ + s.logger.Info(ctx, "Scheduling next Inform request", log.F{ "delay": delay.Truncate(time.Millisecond).String(), }) @@ -48,9 +48,9 @@ func (s *Simulator) periodicInform(ctx context.Context) { } // Run all available tasks after session is finished - s.logger.Debug(context.TODO(), "Start processing tasks") + s.logger.Debug(ctx, "Start processing tasks") s.processTasks() - s.logger.Debug(context.TODO(), "Finished processing tasks") + s.logger.Debug(ctx, "Finished processing tasks") } } @@ -72,7 +72,7 @@ func (s *Simulator) startSession(ctx context.Context, handler sessionHandler) { // Allow only one session at a time if ok := s.sessionMux.TryLock(); !ok { - s.logger.Warn(context.TODO(), "Session in progress, dropping request") + s.logger.Warn(ctx, "Session in progress, dropping request") return } defer s.sessionMux.Unlock() @@ -80,16 +80,16 @@ func (s *Simulator) startSession(ctx context.Context, handler sessionHandler) { s.metrics.SessionsAttempted.Inc() u, err := url.Parse(Config.ACSURL) if err != nil { - s.logger.Error(context.TODO(), "Failed to parse ACS URL", log.Cause(err)) + s.logger.Error(ctx, "Failed to parse ACS URL", log.Cause(err)) return } - s.logger.Info(context.TODO(), "Connecting to ACS", log.F{"acs_url": Config.ACSURL}) + s.logger.Info(ctx, "Connecting to ACS", log.F{"acs_url": Config.ACSURL}) connectionStartTime := time.Now() client, closeFn, err := newClient(u.Hostname(), tcpPort(u)) s.metrics.ConnectionLatency.Observe(float64(time.Since(connectionStartTime).Milliseconds())) if err != nil { - s.logger.Error(context.TODO(), "Failed to connect to ACS", log.Cause(err)) + s.logger.Error(ctx, "Failed to connect to ACS", log.Cause(err)) s.metrics.RequestFailures.Inc() s.dm.IncrRetryAttempts() return @@ -100,7 +100,6 @@ func (s *Simulator) startSession(ctx context.Context, handler sessionHandler) { handler(ctx, &client) } -// nolint:gocyclo func (s *Simulator) informHandler(ctx context.Context, client *http.Client) { s.logger.Info(ctx, "Starting inform") informEnv := s.makeInformEnvelope() @@ -140,7 +139,7 @@ pendingRequests: s.metrics.RequestFailures.Inc() return } - nextEnv = s.handleEnvelope(acsResponseEnv) + nextEnv = s.handleEnvelope(ctx, acsResponseEnv) default: break pendingRequests } @@ -157,7 +156,7 @@ pendingRequests: break } - nextEnv = s.handleEnvelope(acsRequestEnv) + nextEnv = s.handleEnvelope(ctx, acsRequestEnv) if nextEnv == nil { break } @@ -174,7 +173,7 @@ pendingRequests: } func (s *Simulator) send(ctx context.Context, client *http.Client, env *rpc.EnvelopeEncoder) (*rpc.EnvelopeDecoder, error) { - s.pretendToBeSlow() + s.pretendToBeSlow(ctx) s.logger.Debug(ctx, "Sending request to ACS", log.F{"method": env.Method()}) resp, err := s.request(ctx, client, env) @@ -197,7 +196,7 @@ func (s *Simulator) send(ctx context.Context, client *http.Client, env *rpc.Enve return nil, nil } - logPrettyXML(s.logger, "Response from ACS", b) + logPrettyXML(ctx, s.logger, "Response from ACS", b) acsRequestEnv, err := rpc.Decode(b) if err != nil { return nil, fmt.Errorf("decode envelope: %w", err) @@ -249,12 +248,12 @@ func (s *Simulator) makeInformEnvelope() *rpc.EnvelopeEncoder { func (s *Simulator) request(ctx context.Context, client *http.Client, env *rpc.EnvelopeEncoder) (*http.Response, error) { var buf io.Reader if env != nil { - s.debugEnvelope(env) + s.debugEnvelope(ctx, env) b, err := env.EncodePretty() if err != nil { return nil, fmt.Errorf("encode envelope: %w", err) } - logPrettyXML(s.logger, "Request from ACS", b) + logPrettyXML(ctx, s.logger, "Request from ACS", b) buf = bytes.NewBuffer(b) } else { s.logger.Info(ctx, "Sending empty POST request") @@ -308,7 +307,7 @@ func (s *Simulator) processTasks() { } } -func (s *Simulator) debugEnvelope(env *rpc.EnvelopeEncoder) { +func (s *Simulator) debugEnvelope(ctx context.Context, env *rpc.EnvelopeEncoder) { fields := log.F{"method": env.Method()} if env.Body.Inform != nil { fields["events"] = s.dm.PendingEvents() @@ -318,17 +317,17 @@ func (s *Simulator) debugEnvelope(env *rpc.EnvelopeEncoder) { fields["code"] = f.FaultCode.String() fields["error"] = f.FaultString } - s.logger.Info(context.TODO(), "Sending envelope", fields) + s.logger.Info(ctx, "Sending envelope", fields) gpn := env.Body.GetParameterNamesResponse gpv := env.Body.GetParameterValuesResponse switch { case gpn != nil && len(gpn.ParameterList.Parameters) > 100: - s.logger.Debug(context.TODO(), "Sending all parameter names") + s.logger.Debug(ctx, "Sending all parameter names") case gpv != nil && len(gpv.ParameterList.ParameterValues) > 100: - s.logger.Debug(context.TODO(), "Sending all parameter values") + s.logger.Debug(ctx, "Sending all parameter values") default: - s.logger.Debug(context.TODO(), "Request to ACS") + s.logger.Debug(ctx, "Request to ACS") } } @@ -346,7 +345,7 @@ func newClient(host, port string) (http.Client, func() error, error) { return conn, nil }, TLSClientConfig: &tls.Config{ - // nolint:gosec + //nolint:gosec InsecureSkipVerify: !Config.ACSVerifyTLS, }, } @@ -403,6 +402,6 @@ func calcInformTime( return periodicInformTime.Add(time.Duration(intervalsElapsed) * periodicInformInterval) } -func logPrettyXML(logger *blip.Logger, msg string, x []byte) { - logger.Trace(context.TODO(), msg+"\n"+prettyXML(x)) +func logPrettyXML(ctx context.Context, logger *blip.Logger, msg string, x []byte) { + logger.Trace(ctx, msg+"\n"+prettyXML(x)) } diff --git a/simulator/metrics/metrics.go b/simulator/metrics/metrics.go index 900a4ea..b56eea1 100644 --- a/simulator/metrics/metrics.go +++ b/simulator/metrics/metrics.go @@ -35,18 +35,20 @@ type Metrics struct { // prometheus.CounterVec is a struct, not an interface. We can't reimplement it // so instead a custom interface is defined. -// nolint:revive,stylecheck +// +//nolint:revive type prometheus_CounterVec interface { prometheus.Collector - With(prometheus.Labels) prometheus.Counter + With(l prometheus.Labels) prometheus.Counter } // prometheus.HistogramVec is a struct, not an interface. We can't reimplement // it so instead a custom interface is defined. -// nolint:revive,stylecheck +// +//nolint:revive type prometheus_HistogramVec interface { prometheus.Collector - With(prometheus.Labels) prometheus.Observer + With(l prometheus.Labels) prometheus.Observer } // New creates and registers a new Metrics instance with the provided diff --git a/simulator/reboot.go b/simulator/reboot.go index 0acfbf0..48bc08d 100644 --- a/simulator/reboot.go +++ b/simulator/reboot.go @@ -8,16 +8,16 @@ import ( "github.com/localhots/SimulaTR69/rpc" ) -func (s *Simulator) handleReboot(envID string, r *rpc.RebootRequest) *rpc.EnvelopeEncoder { - s.logger.Info(context.TODO(), "Received message", log.F{"method": "Reboot"}) +func (s *Simulator) handleReboot(ctx context.Context, envID string, r *rpc.RebootRequest) *rpc.EnvelopeEncoder { + s.logger.Info(ctx, "Received message", log.F{"method": "Reboot"}) resp := rpc.NewEnvelope(envID) resp.Body.RebootResponse = &rpc.RebootResponseEncoder{} s.dm.SetCommandKey(r.CommandKey) s.tasks <- func() taskFn { - s.logger.Debug(context.TODO(), "Simulating reboot", log.F{"delay": Config.RebootDelay}) + s.logger.Debug(ctx, "Simulating reboot", log.F{"delay": Config.RebootDelay}) s.pretendOfflineFor(Config.RebootDelay) - s.logger.Debug(context.TODO(), "Starting up") + s.logger.Debug(ctx, "Starting up") s.pendingEvents <- rpc.EventBoot return nil } diff --git a/simulator/server.go b/simulator/server.go index 750b14c..d572368 100644 --- a/simulator/server.go +++ b/simulator/server.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "net/url" + "strconv" "strings" "time" @@ -18,7 +19,7 @@ import ( type server interface { listenPort() int url() string - stop(context.Context) error + stop(ctx context.Context) error } type crParams struct { @@ -44,7 +45,7 @@ type httpServer struct { logger *blip.Logger } -func newHTTPServer(h crHandlerFn, logger *blip.Logger) (server, error) { +func newHTTPServer(ctx context.Context, h crHandlerFn, logger *blip.Logger) (server, error) { var err error if Config.Host == "" { Config.Host, err = getIP() @@ -53,30 +54,35 @@ func newHTTPServer(h crHandlerFn, logger *blip.Logger) (server, error) { } } + // Linter demands the ListenConfig must be used. + //nolint:noctx listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", Config.Host, Config.Port)) if err != nil { return nil, fmt.Errorf("create TCP listener: %w", err) } - // Config.Port can be set to 0 in order to bind to a random available port. - port := listener.Addr().(*net.TCPAddr).Port + // Config.Port can be set to 0 in order to bind to a random available addr. + addr, ok := listener.Addr().(*net.TCPAddr) + if !ok { + return nil, fmt.Errorf("get TCP addr: %w", err) + } mux := http.NewServeMux() s := &httpServer{ httpServer: &http.Server{ - Addr: fmt.Sprintf("%s:%d", Config.Host, port), + Addr: fmt.Sprintf("%s:%d", Config.Host, addr.Port), Handler: mux, ReadTimeout: 5 * time.Second, WriteTimeout: 5 * time.Second, }, handler: h, - port: port, + port: addr.Port, logger: logger, } mux.HandleFunc("/cwmp", s.handleConnectionRequest) go func() { if err := s.httpServer.Serve(listener); !errors.Is(err, http.ErrServerClosed) { - logger.Error(context.TODO(), "Server error", log.Cause(err)) + logger.Error(ctx, "Server error", log.Cause(err)) } }() @@ -84,7 +90,7 @@ func newHTTPServer(h crHandlerFn, logger *blip.Logger) (server, error) { } func (s *httpServer) handleConnectionRequest(w http.ResponseWriter, r *http.Request) { - s.logger.Info(context.TODO(), "Received HTTP connection request", log.F{ + s.logger.Info(r.Context(), "Received HTTP connection request", log.F{ "remote_addr": r.RemoteAddr, "method": r.Method, "url": r.URL.String(), @@ -112,17 +118,20 @@ func (s *httpServer) listenPort() int { } func (s *httpServer) url() string { - return fmt.Sprintf("http://%s:%d/cwmp", Config.Host, s.port) + return fmt.Sprintf("http://%s/cwmp", net.JoinHostPort(Config.Host, strconv.Itoa(s.port))) } func (s *httpServer) stop(ctx context.Context) error { - return s.httpServer.Shutdown(ctx) + if err := s.httpServer.Shutdown(ctx); err != nil { + return fmt.Errorf("shutdown HTTP server: %w", err) + } + return nil } func getIP() (string, error) { addrs, err := net.InterfaceAddrs() if err != nil { - return "", err + return "", fmt.Errorf("get interface addresses: %w", err) } for _, addr := range addrs { ipNet, ok := addr.(*net.IPNet) diff --git a/simulator/simulator.go b/simulator/simulator.go index 6c7388e..f50e958 100644 --- a/simulator/simulator.go +++ b/simulator/simulator.go @@ -93,7 +93,7 @@ func (s *Simulator) SetArtificialLatency(d time.Duration) { // Start starts the simulator and initiates an inform session. func (s *Simulator) Start(ctx context.Context) error { if Config.ConnReqEnableHTTP { - srv, err := newHTTPServer(s.handleConnectionRequest, s.logger) + srv, err := newHTTPServer(ctx, s.handleConnectionRequest, s.logger) if err != nil { return fmt.Errorf("start connection request server: %w", err) } @@ -180,92 +180,92 @@ func (s *Simulator) handleConnectionRequest(_ context.Context, params crParams) return nil } -// nolint:gocyclo -func (s *Simulator) handleEnvelope(env *rpc.EnvelopeDecoder) *rpc.EnvelopeEncoder { +//nolint:gocyclo +func (s *Simulator) handleEnvelope(ctx context.Context, env *rpc.EnvelopeDecoder) *rpc.EnvelopeEncoder { s.metrics.MethodCalls.With(prometheus.Labels{"method": env.Method()}).Inc() envID := env.Header.ID.Value switch { case env.Body.GetRPCMethods != nil: - return s.handleGetRPCMethods(envID) + return s.handleGetRPCMethods(ctx, envID) case env.Body.SetParameterValues != nil: - env.Body.SetParameterValues.Debug(s.logger) + env.Body.SetParameterValues.Debug(ctx, s.logger) return s.handleSetParameterValues(envID, env.Body.SetParameterValues) case env.Body.GetParameterValues != nil: - env.Body.GetParameterValues.Debug(s.logger) + env.Body.GetParameterValues.Debug(ctx, s.logger) return s.handleGetParameterValues(envID, env.Body.GetParameterValues) case env.Body.GetParameterNames != nil: - env.Body.GetParameterNames.Debug(s.logger) + env.Body.GetParameterNames.Debug(ctx, s.logger) return s.handleGetParameterNames(envID, env.Body.GetParameterNames) case env.Body.SetParameterAttributes != nil: - env.Body.SetParameterAttributes.Debug(s.logger) + env.Body.SetParameterAttributes.Debug(ctx, s.logger) return s.handleSetParameterAttributes(envID, env.Body.SetParameterAttributes) case env.Body.GetParameterAttributes != nil: - env.Body.GetParameterAttributes.Debug(s.logger) + env.Body.GetParameterAttributes.Debug(ctx, s.logger) return s.handleGetParameterAttributes(envID, env.Body.GetParameterAttributes) case env.Body.AddObject != nil: - env.Body.AddObject.Debug(s.logger) + env.Body.AddObject.Debug(ctx, s.logger) return s.handleAddObject(envID, env.Body.AddObject) case env.Body.DeleteObject != nil: - env.Body.DeleteObject.Debug(s.logger) + env.Body.DeleteObject.Debug(ctx, s.logger) return s.handleDeleteObject(envID, env.Body.DeleteObject) case env.Body.Reboot != nil: - return s.handleReboot(envID, env.Body.Reboot) + return s.handleReboot(ctx, envID, env.Body.Reboot) case env.Body.Download != nil: - env.Body.Download.Debug(s.logger) - return s.handleDownload(envID, env.Body.Download) + env.Body.Download.Debug(ctx, s.logger) + return s.handleDownload(ctx, envID, env.Body.Download) case env.Body.Upload != nil: - env.Body.Upload.Debug(s.logger) + env.Body.Upload.Debug(ctx, s.logger) return s.handleUpload(envID, env.Body.Upload) case env.Body.FactoryReset != nil: - s.logger.Info(context.TODO(), "Received message", log.F{"method": "FactoryReset"}) - return s.handleFactoryReset(envID) + s.logger.Info(ctx, "Received message", log.F{"method": "FactoryReset"}) + return s.handleFactoryReset(ctx, envID) case env.Body.GetQueuedTransfers != nil: - return s.handleGetQueuedTransfers(envID) + return s.handleGetQueuedTransfers(ctx, envID) case env.Body.GetAllQueuedTransfers != nil: - return s.handleGetAllQueuedTransfers(envID) + return s.handleGetAllQueuedTransfers(ctx, envID) case env.Body.ScheduleInform != nil: - return s.handleScheduleInform(envID) + return s.handleScheduleInform(ctx, envID) case env.Body.SetVouchers != nil: - return s.handleSetVouchers(envID) + return s.handleSetVouchers(ctx, envID) case env.Body.GetOptions != nil: - return s.handleGetOptions(envID) + return s.handleGetOptions(ctx, envID) case env.Body.Fault != nil: - return s.handleFault(envID, env.Body.Fault) + return s.handleFault(ctx, envID, env.Body.Fault) case env.Body.TransferCompleteResponse != nil: return nil default: - s.logger.Warn(context.TODO(), "Unknown method", log.F{"env_id": envID}) + s.logger.Warn(ctx, "Unknown method", log.F{"env_id": envID}) return rpc.NewEnvelope(envID).WithFault(rpc.FaultMethodNotSupported) } } -func (s *Simulator) handleGetQueuedTransfers(envID string) *rpc.EnvelopeEncoder { - s.logger.Info(context.TODO(), "Received message", log.F{"method": "GetQueuedTransfers"}) +func (s *Simulator) handleGetQueuedTransfers(ctx context.Context, envID string) *rpc.EnvelopeEncoder { + s.logger.Info(ctx, "Received message", log.F{"method": "GetQueuedTransfers"}) return rpc.NewEnvelope(envID).WithFault(rpc.FaultMethodNotSupported) } -func (s *Simulator) handleGetAllQueuedTransfers(envID string) *rpc.EnvelopeEncoder { - s.logger.Info(context.TODO(), "Received message", log.F{"method": "GetAllQueuedTransfers"}) +func (s *Simulator) handleGetAllQueuedTransfers(ctx context.Context, envID string) *rpc.EnvelopeEncoder { + s.logger.Info(ctx, "Received message", log.F{"method": "GetAllQueuedTransfers"}) return rpc.NewEnvelope(envID).WithFault(rpc.FaultMethodNotSupported) } -func (s *Simulator) handleScheduleInform(envID string) *rpc.EnvelopeEncoder { - s.logger.Info(context.TODO(), "Received message", log.F{"method": "ScheduleInform"}) +func (s *Simulator) handleScheduleInform(ctx context.Context, envID string) *rpc.EnvelopeEncoder { + s.logger.Info(ctx, "Received message", log.F{"method": "ScheduleInform"}) return rpc.NewEnvelope(envID).WithFault(rpc.FaultMethodNotSupported) } -func (s *Simulator) handleSetVouchers(envID string) *rpc.EnvelopeEncoder { - s.logger.Info(context.TODO(), "Received message", log.F{"method": "SetVouchers"}) +func (s *Simulator) handleSetVouchers(ctx context.Context, envID string) *rpc.EnvelopeEncoder { + s.logger.Info(ctx, "Received message", log.F{"method": "SetVouchers"}) return rpc.NewEnvelope(envID).WithFault(rpc.FaultMethodNotSupported) } -func (s *Simulator) handleGetOptions(envID string) *rpc.EnvelopeEncoder { - s.logger.Info(context.TODO(), "Received message", log.F{"method": "GetOptions"}) +func (s *Simulator) handleGetOptions(ctx context.Context, envID string) *rpc.EnvelopeEncoder { + s.logger.Info(ctx, "Received message", log.F{"method": "GetOptions"}) return rpc.NewEnvelope(envID).WithFault(rpc.FaultMethodNotSupported) } -func (s *Simulator) handleFault(envID string, r *rpc.FaultPayload) *rpc.EnvelopeEncoder { - s.logger.Error(context.TODO(), "ACS fault", log.F{ +func (s *Simulator) handleFault(ctx context.Context, envID string, r *rpc.FaultPayload) *rpc.EnvelopeEncoder { + s.logger.Error(ctx, "ACS fault", log.F{ "env_id": envID, "code": r.Detail.Fault.FaultCode.String(), "string": r.Detail.Fault.FaultString, @@ -280,11 +280,12 @@ func (s *Simulator) pretendOfflineFor(dur time.Duration) { time.Sleep(dur) } -func (s *Simulator) pretendToBeSlow() { +func (s *Simulator) pretendToBeSlow(ctx context.Context) { if s.artificialLatency > 0 { - // nolint:gosec + // It's fine to use non cryptographic randomness here. + //nolint:gosec delay := time.Duration(rand.Int63n(int64(s.artificialLatency))).Round(time.Millisecond) - s.logger.Debug(context.TODO(), "Simulating slow response", log.F{"delay": delay.String()}) + s.logger.Debug(ctx, "Simulating slow response", log.F{"delay": delay.String()}) time.Sleep(delay) } }