-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #58 from xmidt-org/mocktr181
Mocktr181
- Loading branch information
Showing
9 changed files
with
57,043 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,3 +41,4 @@ internal/adapters/libparodus/cmd/example/example | |
dist/* | ||
|
||
cmd/xmidt-agent/dist/* | ||
lint.sh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package mocktr181 | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"strings" | ||
|
||
"github.com/xmidt-org/wrp-go/v3" | ||
"github.com/xmidt-org/xmidt-agent/internal/wrpkit" | ||
) | ||
|
||
var ( | ||
ErrInvalidInput = fmt.Errorf("invalid input") | ||
ErrInvalidFileInput = fmt.Errorf("misconfigured file input") | ||
ErrUnableToReadFile = fmt.Errorf("unable to read file") | ||
ErrInvalidPayload = fmt.Errorf("invalid request payload") | ||
ErrInvalidResponsePayload = fmt.Errorf("invalid response payload") | ||
) | ||
|
||
// Option is a functional option type for WS. | ||
type Option interface { | ||
apply(*Handler) error | ||
} | ||
|
||
type optionFunc func(*Handler) error | ||
|
||
func (f optionFunc) apply(c *Handler) error { | ||
return f(c) | ||
} | ||
|
||
type Handler struct { | ||
egress wrpkit.Handler | ||
source string | ||
filePath string | ||
parameters []MockParameter | ||
enabled bool | ||
} | ||
|
||
type MockParameter struct { | ||
Name string | ||
Value string | ||
Access string | ||
DataType int // add json labels here | ||
Attributes map[string]interface{} | ||
Delay int | ||
} | ||
|
||
type MockParameters struct { | ||
Parameters []MockParameter | ||
} | ||
|
||
type Tr181Payload struct { | ||
Command string `json:"command"` | ||
Names []string `json:"names"` | ||
Parameters []Parameter `json:"parameters"` | ||
} | ||
|
||
type Parameters struct { | ||
Parameters []Parameter | ||
} | ||
|
||
type Parameter struct { | ||
Name string `json:"name"` | ||
Value string `json:"value"` | ||
DataType int `json:"dataType"` | ||
Attributes map[string]interface{} `json:"attributes"` | ||
} | ||
|
||
// New creates a new instance of the Handler struct. The parameter egress is | ||
// the handler that will be called to send the response. The parameter source is the source to use in | ||
// the response message. | ||
func New(egress wrpkit.Handler, source string, opts ...Option) (*Handler, error) { | ||
// TODO - load config from file system | ||
|
||
h := Handler{ | ||
egress: egress, | ||
source: source, | ||
} | ||
|
||
for _, opt := range opts { | ||
if opt != nil { | ||
if err := opt.apply(&h); err != nil { | ||
return nil, err | ||
} | ||
} | ||
} | ||
|
||
parameters, err := h.loadFile() | ||
if err != nil { | ||
return nil, errors.Join(ErrUnableToReadFile, err) | ||
} | ||
|
||
h.parameters = parameters | ||
|
||
if h.egress == nil || h.source == "" { | ||
return nil, ErrInvalidInput | ||
} | ||
|
||
return &h, nil | ||
} | ||
|
||
func (h Handler) Enabled() bool { | ||
return h.enabled | ||
} | ||
|
||
// HandleWrp is called to process a tr181 command | ||
func (h Handler) HandleWrp(msg wrp.Message) error { | ||
payload := new(Tr181Payload) | ||
var commandErr error | ||
err := json.Unmarshal(msg.Payload, &payload) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var payloadResponse []byte | ||
var statusCode int64 | ||
|
||
command := payload.Command | ||
|
||
switch command { | ||
case "GET": | ||
statusCode, payloadResponse, commandErr = h.get(payload.Names) | ||
|
||
case "SET": | ||
statusCode = h.set(payload.Parameters) | ||
|
||
default: | ||
// currently only get and set are implemented for existing mocktr181 | ||
statusCode = http.StatusOK | ||
} | ||
|
||
response := msg | ||
response.Destination = msg.Source | ||
response.Source = h.source | ||
response.ContentType = "text/plain" | ||
response.Payload = payloadResponse | ||
|
||
response.Status = &statusCode | ||
|
||
err = h.egress.HandleWrp(response) | ||
|
||
return errors.Join(err, commandErr) | ||
} | ||
|
||
func (h Handler) get(names []string) (int64, []byte, error) { | ||
result := []Parameter{} | ||
|
||
for _, name := range names { | ||
for _, mockParameter := range h.parameters { | ||
if strings.HasPrefix(mockParameter.Name, name) { | ||
result = append(result, Parameter{ | ||
Name: mockParameter.Name, | ||
Value: mockParameter.Value, | ||
DataType: mockParameter.DataType, | ||
Attributes: mockParameter.Attributes, | ||
}) | ||
} | ||
} | ||
} | ||
|
||
payload, err := json.Marshal(result) | ||
if err != nil { | ||
return http.StatusInternalServerError, payload, errors.Join(ErrInvalidResponsePayload, err) | ||
} | ||
|
||
return http.StatusOK, payload, nil | ||
} | ||
|
||
func (h Handler) set(parameters []Parameter) int64 { | ||
for _, parameter := range parameters { | ||
for _, mockParameter := range h.parameters { | ||
if strings.HasPrefix(mockParameter.Name, parameter.Name) { | ||
if mockParameter.Access == "rw" { | ||
mockParameter.Value = parameter.Value | ||
mockParameter.DataType = parameter.DataType | ||
mockParameter.Attributes = parameter.Attributes | ||
} | ||
} | ||
} | ||
} | ||
|
||
return http.StatusAccepted | ||
} | ||
|
||
func (h Handler) loadFile() ([]MockParameter, error) { | ||
jsonFile, err := os.Open(h.filePath) | ||
if err != nil { | ||
return nil, errors.Join(ErrUnableToReadFile, err) | ||
} | ||
defer jsonFile.Close() | ||
|
||
var parameters []MockParameter | ||
byteValue, _ := io.ReadAll(jsonFile) | ||
err = json.Unmarshal(byteValue, ¶meters) | ||
if err != nil { | ||
return nil, errors.Join(ErrInvalidFileInput, err) | ||
} | ||
|
||
return parameters, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package mocktr181 | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"github.com/xmidt-org/wrp-go/v3" | ||
"github.com/xmidt-org/xmidt-agent/internal/wrpkit" | ||
) | ||
|
||
func TestHandler_HandleWrp(t *testing.T) { | ||
tests := []struct { | ||
description string | ||
nextResult error | ||
nextCallCount int | ||
egressResult error | ||
egressCallCount int | ||
msg wrp.Message | ||
expectedErr error | ||
validate func(*assert.Assertions, wrp.Message, *Handler) error | ||
}{ | ||
{ | ||
description: "get success with multiple results", | ||
egressCallCount: 1, | ||
expectedErr: nil, | ||
msg: wrp.Message{ | ||
Type: wrp.SimpleEventMessageType, | ||
Source: "dns:tr1d1um.example.com/service/ignored", | ||
Destination: "event:event_1/ignored", | ||
Payload: []byte("{\"command\":\"GET\",\"names\":[\"Device.DeviceInfo.\"]}"), | ||
}, | ||
validate: func(a *assert.Assertions, msg wrp.Message, h *Handler) error { | ||
a.Equal(int64(http.StatusOK), *msg.Status) | ||
var result []Parameters | ||
err := json.Unmarshal(msg.Payload, &result) | ||
a.NoError(err) | ||
a.Equal(102, len(result)) | ||
a.True(h.Enabled()) | ||
return nil | ||
}, | ||
}, { | ||
description: "set, success", | ||
egressCallCount: 1, | ||
msg: wrp.Message{ | ||
Type: wrp.SimpleEventMessageType, | ||
Source: "dns:tr1d1um.example.com/service/ignored", | ||
Destination: "event:event_1/ignored", | ||
Payload: []byte("{\"command\":\"SET\",\"parameters\":[{\"name\":\"Device.WiFi.Radio.10000.Name\",\"dataType\":0,\"value\":\"anothername\",\"attributes\":{\"notify\":0}}]}"), | ||
}, | ||
validate: func(a *assert.Assertions, msg wrp.Message, h *Handler) error { | ||
a.Equal(int64(http.StatusAccepted), *msg.Status) | ||
a.True(h.Enabled()) | ||
|
||
return nil | ||
}, | ||
}, { | ||
description: "set, read only", | ||
egressCallCount: 1, | ||
msg: wrp.Message{ | ||
Type: wrp.SimpleEventMessageType, | ||
Source: "dns:tr1d1um.example.com/service/ignored", | ||
Destination: "event:event_1/ignored", | ||
Payload: []byte("{\"command\":\"SET\",\"parameters\":[{\"name\":\"Device.Bridging.MaxBridgeEntries\",\"dataType\":0,\"value\":\"anothername\",\"attributes\":{\"notify\":0}}]}"), | ||
}, | ||
validate: func(a *assert.Assertions, msg wrp.Message, h *Handler) error { | ||
a.Equal(int64(http.StatusAccepted), *msg.Status) | ||
a.True(h.Enabled()) | ||
|
||
return nil | ||
}, | ||
}, | ||
} | ||
for _, tc := range tests { | ||
t.Run(tc.description, func(t *testing.T) { | ||
assert := assert.New(t) | ||
require := require.New(t) | ||
var h *Handler | ||
|
||
egressCallCount := 0 | ||
egress := wrpkit.HandlerFunc(func(msg wrp.Message) error { | ||
egressCallCount++ | ||
if tc.validate != nil { | ||
assert.NoError(tc.validate(assert, msg, h)) | ||
} | ||
return tc.egressResult | ||
}) | ||
|
||
mockDefaults := []Option{ | ||
FilePath("mock_tr181_test.json"), | ||
Enabled(true), | ||
} | ||
|
||
h, err := New(egress, "some-source", mockDefaults...) | ||
require.NoError(err) | ||
|
||
err = h.HandleWrp(tc.msg) | ||
assert.ErrorIs(err, tc.expectedErr) | ||
|
||
assert.Equal(tc.egressCallCount, egressCallCount) | ||
}) | ||
} | ||
} |
Oops, something went wrong.