From 17709136f7622ccb9855a22cd59ce220163c3185 Mon Sep 17 00:00:00 2001 From: Daniel Adam Date: Fri, 26 Apr 2024 14:03:41 +0200 Subject: [PATCH 1/7] bridge: updates for tests --- Makefile | 46 ++- bridge/device/thingDescription/manager.go | 128 --------- .../thingDescription/thingDescription.go | 181 ++++++++++++ .../thingDescription/ocfResources.go | 31 ++ .../thingDescription/ocfResources.jsonld | 183 +++++++++++- .../thingDescription/resource_test.go | 21 +- bridge/test/test.go | 270 +++++------------- cmd/bridge-device/bridge-device.jsonld | 33 +-- cmd/bridge-device/device.go | 120 ++++++++ cmd/bridge-device/{ => device}/config.go | 28 +- cmd/bridge-device/device/device.go | 83 ++++++ cmd/bridge-device/main.go | 196 ++----------- test/bridge-device/main.go | 129 --------- 13 files changed, 772 insertions(+), 677 deletions(-) create mode 100644 bridge/device/thingDescription/thingDescription.go create mode 100644 cmd/bridge-device/device.go rename cmd/bridge-device/{ => device}/config.go (77%) create mode 100644 cmd/bridge-device/device/device.go delete mode 100644 test/bridge-device/main.go diff --git a/Makefile b/Makefile index 2a7f6bd6..6056eb6e 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,8 @@ CERT_TOOL_SIGN_ALG ?= ECDSA-SHA256 # supported values: P256, P384, P521 CERT_TOOL_ELLIPTIC_CURVE ?= P256 DEVSIM_IMAGE ?= ghcr.io/iotivity/iotivity-lite/cloud-server-discovery-resource-observable-debug:vnext -HUB_TEST_DEVICE_IMAGE = ghcr.io/plgd-dev/hub/test-cloud-server:main +# HUB_TEST_DEVICE_IMAGE = ghcr.io/plgd-dev/hub/test-cloud-server:main +HUB_TEST_DEVICE_IMAGE = ghcr.io/plgd-dev/hub/test-cloud-server:vnext-pr1274 default: build @@ -123,7 +124,34 @@ test: env build-testcontainer -v $(TMP_PATH):/tmp \ $(SERVICE_NAME):$(VERSION_TAG) -test.parallel 1 -test.v -test.coverprofile=/tmp/coverage.txt -test-bridge: +test-bridge/clean: + pkill -KILL bridge-device || : + rm -rf $(TMP_PATH)/bridge || : + +define SET-BRIDGE-DEVICE-CONFIG + yq -i '.apis.coap.externalAddresses=["127.0.0.1:15683","[::1]:15683"]' $(1) + yq -i '.cloud.enabled=true' $(1) + yq -i '.cloud.cloudID="$(CLOUD_SID)"' $(1) + yq -i '.cloud.tls.caPoolPath="$(2)/data/certs/root_ca.crt"' $(1) + yq -i '.cloud.tls.keyPath="$(2)/data/certs/external/coap-gateway.key"' $(1) + yq -i '.cloud.tls.certPath="$(2)/data/certs/external/coap-gateway.crt"' $(1) + yq -i '.numGeneratedBridgedDevices=3' $(1) + yq -i '.numResourcesPerDevice=0' $(1) + yq -i '.thingDescription.enabled=true' $(1) + yq -i '.thingDescription.file="$(2)/bridge/bridge-device.jsonld"' $(1) +endef + +# config-docker.yaml -> copy of configuration with paths valid inside docker container +# config-test.yaml -> copy of configuration with paths valid on host machine +test-bridge/env: test-bridge/clean + mkdir -p $(TMP_PATH)/bridge + cp ./cmd/bridge-device/bridge-device.jsonld $(TMP_PATH)/bridge + cp ./cmd/bridge-device/config.yaml $(TMP_PATH)/bridge/config-docker.yaml + $(call SET-BRIDGE-DEVICE-CONFIG,$(TMP_PATH)/bridge/config-docker.yaml,) + cp $(TMP_PATH)/bridge/config-docker.yaml $(TMP_PATH)/bridge/config-test.yaml + $(call SET-BRIDGE-DEVICE-CONFIG,$(TMP_PATH)/bridge/config-test.yaml,$(TMP_PATH)) + +test-bridge: test-bridge/env sudo rm -rf $(TMP_PATH)/data || : mkdir -p $(TMP_PATH)/data # pull image @@ -141,15 +169,10 @@ test-bridge: $(HUB_TEST_DEVICE_IMAGE) # start device - rm -rf $(TMP_PATH)/bridge || : - mkdir -p $(TMP_PATH)/bridge - go build -C ./test/bridge-device -cover -o ./bridge-device + go build -C ./cmd/bridge-device -cover -o ./bridge-device pkill -KILL bridge-device || : - CLOUD_SID=$(CLOUD_SID) CA_POOL=$(TMP_PATH)/data/certs/root_ca.crt \ - CERT_FILE=$(TMP_PATH)/data/certs/external/coap-gateway.crt \ - KEY_FILE=$(TMP_PATH)/data/certs/external/coap-gateway.key \ GOCOVERDIR=$(TMP_PATH)/bridge \ - ./test/bridge-device/bridge-device & + ./cmd/bridge-device/bridge-device -config $(TMP_PATH)/bridge/config-test.yaml & # run tests docker run \ @@ -161,9 +184,11 @@ test-bridge: --env COAP_GATEWAY_CLOUD_ID="$(CLOUD_SID)" \ --env TEST_DEVICE_NAME="bridged-device-0" \ --env TEST_DEVICE_TYPE="bridged" \ + --env TEST_BRIDGE_DEVICE_CONFIG="/bridge/config-docker.yaml" \ --env GRPC_GATEWAY_TEST_DISABLED=1 \ --env IOTIVITY_LITE_TEST_RUN="(TestOffboard|TestOffboardWithoutSignIn|TestOffboardWithRepeat|TestRepublishAfterRefresh)$$" \ -v $(TMP_PATH):/tmp \ + -v $(TMP_PATH)/bridge:/bridge \ -v $(TMP_PATH)/data:/data \ $(HUB_TEST_DEVICE_IMAGE) @@ -175,11 +200,10 @@ test-bridge: done go tool covdata textfmt -i=$(TMP_PATH)/bridge -o $(TMP_PATH)/bridge.coverage.txt -clean: +clean: test-bridge/clean docker rm -f devsim-net-host || : docker rm -f hub-device-tests-environment || : docker rm -f hub-device-tests || : - pkill -KILL bridge-device || : sudo rm -rf .tmp/* .PHONY: build-testcontainer build certificates clean env test unit-test diff --git a/bridge/device/thingDescription/manager.go b/bridge/device/thingDescription/manager.go index 12a5973c..1ad59ff4 100644 --- a/bridge/device/thingDescription/manager.go +++ b/bridge/device/thingDescription/manager.go @@ -1,35 +1,19 @@ package thingDescription import ( - "net/url" "reflect" "sync/atomic" - "github.com/fredbi/uri" "github.com/google/uuid" "github.com/plgd-dev/device/v2/bridge/net" "github.com/plgd-dev/device/v2/bridge/resources" "github.com/plgd-dev/device/v2/pkg/eventloop" "github.com/plgd-dev/device/v2/schema" - "github.com/plgd-dev/go-coap/v3/message" "github.com/plgd-dev/go-coap/v3/message/pool" "github.com/plgd-dev/go-coap/v3/pkg/sync" "github.com/web-of-things-open-source/thingdescription-go/thingDescription" ) -var ( - SecurityNoSec = "nosec_sc" - SecurityDefinitions = map[string]thingDescription.SecurityScheme{ - SecurityNoSec: { - Scheme: "nosec", - }, - } - HTTPSWWWW3Org2022WotTdV11 = thingDescription.HTTPSWWWW3Org2022WotTdV11 - Context = thingDescription.ThingContext{ - Enum: &HTTPSWWWW3Org2022WotTdV11, - } -) - // Resource to avoid import cycle also it is same as in Device package to avoid wrapping it type Resource = interface { Close() @@ -117,115 +101,3 @@ func (t *Manager) NotifySubscriptions(td thingDescription.ThingDescription) { default: } } - -func supportedOperationToTDOperation(ops resources.SupportedOperation) []string { - tdOps := make([]string, 0, 3) - if ops.HasOperation(resources.SupportedOperationRead) { - tdOps = append(tdOps, string(thingDescription.Readproperty)) - } - if ops.HasOperation(resources.SupportedOperationWrite) { - tdOps = append(tdOps, string(thingDescription.Writeproperty)) - } - if ops.HasOperation(resources.SupportedOperationObserve) { - tdOps = append(tdOps, string(thingDescription.Observeproperty), string(thingDescription.Unobserveproperty)) - } - if len(tdOps) == 0 { - return nil - } - return tdOps -} - -func boolToPtr(v bool) *bool { - if !v { - return nil - } - return &v -} - -func stringToPtr(v string) *string { - if v == "" { - return nil - } - return &v -} - -func createForms(deviceID uuid.UUID, href string, supportedOperations resources.SupportedOperation, setForm bool) []thingDescription.FormElementProperty { - if !setForm { - return nil - } - ops := supportedOperationToTDOperation(supportedOperations) - if len(ops) > 0 { - hrefStr := href - if deviceID != uuid.Nil { - hrefStr += "?di=" + deviceID.String() - } - href, err := url.Parse(hrefStr) - if err == nil { - return []thingDescription.FormElementProperty{ - { - ContentType: stringToPtr(message.AppCBOR.String()), - Op: &thingDescription.FormElementPropertyOp{ - StringArray: ops, - }, - Href: *href, - }, - } - } - } - return nil -} - -func PatchPropertyElement(prop thingDescription.PropertyElement, deviceID uuid.UUID, resource Resource, setForm bool) thingDescription.PropertyElement { - ops := resource.SupportsOperations() - observable := ops.HasOperation(resources.SupportedOperationObserve) - isReadOnly := ops.HasOperation(resources.SupportedOperationRead) && !ops.HasOperation(resources.SupportedOperationWrite) - isWriteOnly := ops.HasOperation(resources.SupportedOperationWrite) && !ops.HasOperation(resources.SupportedOperationRead) - resourceTypes := resource.GetResourceTypes() - - prop.Type = &thingDescription.TypeDeclaration{ - StringArray: resourceTypes, - } - prop.Observable = boolToPtr(observable) - prop.ReadOnly = boolToPtr(isReadOnly) - prop.WriteOnly = boolToPtr(isWriteOnly) - prop.Observable = boolToPtr(observable) - prop.Forms = createForms(deviceID, resource.GetHref(), ops, setForm) - return prop -} - -func PatchThingDescription(td thingDescription.ThingDescription, device Device, endpoint string, getPropertyElement func(resourceHref string, resource Resource) (thingDescription.PropertyElement, bool)) thingDescription.ThingDescription { - if td.Context == nil { - td.Context = &Context - } - id, err := uri.Parse("urn:uuid:" + device.GetID().String()) - if err == nil { - td.ID = id - } - td.Title = device.GetName() - if endpoint != "" { - // base - u, err := url.Parse(endpoint) - if err == nil { - td.Base = *u - } - // security - td.Security = &thingDescription.TypeDeclaration{ - String: &SecurityNoSec, - } - // securityDefinitions - td.SecurityDefinitions = SecurityDefinitions - } - - device.Range(func(resourceHref string, resource Resource) bool { - pe, ok := getPropertyElement(resourceHref, resource) - if !ok { - return true - } - if td.Properties == nil { - td.Properties = make(map[string]thingDescription.PropertyElement) - } - td.Properties[resourceHref] = pe - return true - }) - return td -} diff --git a/bridge/device/thingDescription/thingDescription.go b/bridge/device/thingDescription/thingDescription.go new file mode 100644 index 00000000..30353948 --- /dev/null +++ b/bridge/device/thingDescription/thingDescription.go @@ -0,0 +1,181 @@ +package thingDescription + +import ( + "net/url" + + "github.com/fredbi/uri" + "github.com/google/uuid" + "github.com/plgd-dev/device/v2/bridge/resources" + "github.com/web-of-things-open-source/thingdescription-go/thingDescription" +) + +var ( + SecurityNoSec = "nosec_sc" + SecurityDefinitions = map[string]thingDescription.SecurityScheme{ + SecurityNoSec: { + Scheme: "nosec", + }, + } + HTTPSWWWW3Org2022WotTdV11 = thingDescription.HTTPSWWWW3Org2022WotTdV11 + Context = thingDescription.ThingContext{ + Enum: &HTTPSWWWW3Org2022WotTdV11, + } +) + +func SupportedOperationToTDOperations(ops resources.SupportedOperation) []string { + tdOps := make([]string, 0, 3) + type translationItem struct { + resourceOp resources.SupportedOperation + tdOps []string + } + translationTable := []translationItem{ + {resources.SupportedOperationRead, []string{string(thingDescription.Readproperty)}}, + {resources.SupportedOperationWrite, []string{string(thingDescription.Writeproperty)}}, + {resources.SupportedOperationObserve, []string{string(thingDescription.Observeproperty), string(thingDescription.Unobserveproperty)}}, + } + for _, t := range translationTable { + if ops.HasOperation(t.resourceOp) { + tdOps = append(tdOps, t.tdOps...) + } + } + if len(tdOps) == 0 { + return nil + } + return tdOps +} + +func BoolToPtr(v bool) *bool { + if !v { + return nil + } + return &v +} + +func StringToPtr(v string) *string { + if v == "" { + return nil + } + return &v +} + +type PropertyElementOperations struct { + ReadOnly bool + WriteOnly bool + Observable bool +} + +func toPropertyElementOperations(ops resources.SupportedOperation) PropertyElementOperations { + return PropertyElementOperations{ + Observable: ops.HasOperation(resources.SupportedOperationObserve), + ReadOnly: ops.HasOperation(resources.SupportedOperationRead) && !ops.HasOperation(resources.SupportedOperationWrite), + WriteOnly: ops.HasOperation(resources.SupportedOperationWrite) && !ops.HasOperation(resources.SupportedOperationRead), + } +} + +func GetPropertyElementOperations(pe thingDescription.PropertyElement) PropertyElementOperations { + isNotNilAndTrue := func(val *bool) bool { + return val != nil && *val + } + return PropertyElementOperations{ + ReadOnly: isNotNilAndTrue(pe.ReadOnly), + WriteOnly: isNotNilAndTrue(pe.WriteOnly), + Observable: isNotNilAndTrue(pe.Observable), + } +} + +func (p PropertyElementOperations) ToSupportedOperations() resources.SupportedOperation { + var ops resources.SupportedOperation + if p.Observable { + ops |= resources.SupportedOperationObserve + } + if p.ReadOnly { + return ops | resources.SupportedOperationRead + } + if p.WriteOnly { + return ops | resources.SupportedOperationWrite + } + return ops | resources.SupportedOperationRead | resources.SupportedOperationWrite +} + +func PatchPropertyElement(prop thingDescription.PropertyElement, types []string, setForm bool, deviceID uuid.UUID, href string, ops resources.SupportedOperation, contentType string) (thingDescription.PropertyElement, error) { + if len(types) > 0 { + prop.Type = &thingDescription.TypeDeclaration{ + StringArray: types, + } + } + propOps := toPropertyElementOperations(ops) + prop.Observable = BoolToPtr(propOps.Observable) + prop.ReadOnly = BoolToPtr(propOps.ReadOnly) + prop.WriteOnly = BoolToPtr(propOps.WriteOnly) + if !setForm { + return prop, nil + } + opsStrs := SupportedOperationToTDOperations(ops) + if len(opsStrs) == 0 { + return prop, nil + } + var hrefUri *url.URL + if len(href) > 0 { + hrefStr := href + if deviceID != uuid.Nil { + hrefStr += "?di=" + deviceID.String() + } + var err error + hrefUri, err = url.Parse(hrefStr) + if err != nil { + return thingDescription.PropertyElement{}, err + } + } + form := thingDescription.FormElementProperty{ + ContentType: StringToPtr(contentType), + Op: &thingDescription.FormElementPropertyOp{ + StringArray: opsStrs, + }, + } + if hrefUri != nil { + form.Href = *hrefUri + } + prop.Forms = []thingDescription.FormElementProperty{form} + return prop, nil +} + +func GetThingDescriptionID(deviceID string) (uri.URI, error) { + return uri.Parse("urn:uuid:" + deviceID) +} + +func PatchThingDescription(td thingDescription.ThingDescription, device Device, endpoint string, getPropertyElement func(resourceHref string, resource Resource) (thingDescription.PropertyElement, bool)) thingDescription.ThingDescription { + if td.Context == nil { + td.Context = &Context + } + id, err := GetThingDescriptionID(device.GetID().String()) + if err == nil { + td.ID = id + } + td.Title = device.GetName() + if endpoint != "" { + // base + u, err := url.Parse(endpoint) + if err == nil { + td.Base = *u + } + // security + td.Security = &thingDescription.TypeDeclaration{ + String: &SecurityNoSec, + } + // securityDefinitions + td.SecurityDefinitions = SecurityDefinitions + } + + device.Range(func(resourceHref string, resource Resource) bool { + pe, ok := getPropertyElement(resourceHref, resource) + if !ok { + return true + } + if td.Properties == nil { + td.Properties = make(map[string]thingDescription.PropertyElement) + } + td.Properties[resourceHref] = pe + return true + }) + return td +} diff --git a/bridge/resources/thingDescription/ocfResources.go b/bridge/resources/thingDescription/ocfResources.go index 63008024..cdd1817e 100644 --- a/bridge/resources/thingDescription/ocfResources.go +++ b/bridge/resources/thingDescription/ocfResources.go @@ -3,6 +3,12 @@ package thingDescription import ( _ "embed" + "github.com/google/uuid" + bridgeTD "github.com/plgd-dev/device/v2/bridge/device/thingDescription" + schemaCloud "github.com/plgd-dev/device/v2/schema/cloud" + schemaCredential "github.com/plgd-dev/device/v2/schema/credential" + schemaDevice "github.com/plgd-dev/device/v2/schema/device" + schemaMaintenance "github.com/plgd-dev/device/v2/schema/maintenance" "github.com/web-of-things-open-source/thingdescription-go/thingDescription" ) @@ -32,3 +38,28 @@ func GetOCFResourcePropertyElement(resourceHref string) (thingDescription.Proper } return prop, true } + +func patchResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, resourceTypes []string, resourceHref, contentType string) (thingDescription.PropertyElement, error) { + propOps := bridgeTD.GetPropertyElementOperations(pe) + return bridgeTD.PatchPropertyElement(pe, resourceTypes, true, deviceID, resourceHref, propOps.ToSupportedOperations(), contentType) +} + +func PatchDeviceResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL, contentType string, deviceType string) (thingDescription.PropertyElement, error) { + var types []string + if deviceType != "" { + types = []string{schemaDevice.ResourceType, deviceType} + } + return patchResourcePropertyElement(pe, deviceID, types, baseURL+schemaDevice.ResourceURI, contentType) +} + +func PatchMaintenanceResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL, contentType string) (thingDescription.PropertyElement, error) { + return patchResourcePropertyElement(pe, deviceID, []string{schemaMaintenance.ResourceType}, baseURL+schemaMaintenance.ResourceURI, contentType) +} + +func PatchCloudResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL, contentType string) (thingDescription.PropertyElement, error) { + return patchResourcePropertyElement(pe, deviceID, []string{schemaCloud.ResourceType}, baseURL+schemaCloud.ResourceURI, contentType) +} + +func PatchCredentialResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL, contentType string) (thingDescription.PropertyElement, error) { + return patchResourcePropertyElement(pe, deviceID, []string{schemaCredential.ResourceType}, baseURL+schemaCredential.ResourceURI, contentType) +} diff --git a/bridge/resources/thingDescription/ocfResources.jsonld b/bridge/resources/thingDescription/ocfResources.jsonld index 673dba8e..c50409cc 100644 --- a/bridge/resources/thingDescription/ocfResources.jsonld +++ b/bridge/resources/thingDescription/ocfResources.jsonld @@ -7,6 +7,7 @@ "properties": { "/oic/d": { "title": "Device Information", + "readOnly": true, "type": "object", "properties": { "piid": { @@ -26,7 +27,10 @@ "readOnly": true, "format": "uuid" } - } + }, + "@type": [ + "oic.wk.d" + ] }, "/oic/mnt": { "title": "Maintenance", @@ -36,7 +40,10 @@ "title": "Factory Reset", "type": "boolean" } - } + }, + "@type": [ + "oic.wk.mnt" + ] }, "/CoapCloudConfResURI": { "title": "CoapCloudConfResURI", @@ -76,7 +83,177 @@ "title": "Last error code", "type": "integer" } - } + }, + "@type": [ + "oic.r.coapcloudconf" + ] + }, + "/oic/sec/cred": { + "title": "Credentials", + "type": "object", + "properties": { + "creds": { + "title": "Credentials", + "type": "array", + "items": { + "type": "object", + "properties": { + "credid": { + "title": "Credential ID", + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "subjectuuid": { + "title": "Subject UUID", + "type": "string", + "format": "uuid" + }, + "roleid": { + "title": "Role ID", + "type": "object", + "properties": { + "role": { + "title": "Role", + "type": "string", + "readOnly": true + }, + "authority": { + "title": "Authority", + "type": "string", + "readOnly": true + } + }, + "required": [ + "role" + ] + }, + "credtype": { + "title": "Credential Type", + "type": "integer", + "enum": [ + "0", + "1", + "2", + "4", + "8", + "16", + "32", + "64", + "128", + "256" + ] + }, + "credusage": { + "title": "Credential Usage", + "type": "string", + "enum": [ + "oic.sec.cred.trustca", + "oic.sec.cred.cert", + "oic.sec.cred.rolecert", + "oic.sec.cred.mfgtrustca", + "oic.sec.cred.mfgtrustanchor" + ] + }, + "publicdata": { + "title": "Public Data", + "type": "object", + "properties": { + "data": { + "title": "Data", + "type": "string" + }, + "encoding": { + "title": "Encoding format", + "type": "string", + "enum": [ + "oic.sec.encoding.jwt", + "oic.sec.encoding.cwt", + "oic.sec.encoding.base64", + "oic.sec.encoding.uri", + "oic.sec.encoding.pem", + "oic.sec.encoding.der", + "oic.sec.encoding.raw" + ] + } + } + }, + "privatedata": { + "title": "Private Data", + "type": "object", + "properties": { + "data": { + "title": "Data", + "type": "string", + "writeOnly": true + }, + "encoding": { + "title": "Encoding format", + "type": "string", + "enum": [ + "oic.sec.encoding.jwt", + "oic.sec.encoding.cwt", + "oic.sec.encoding.base64", + "oic.sec.encoding.uri", + "oic.sec.encoding.handle", + "oic.sec.encoding.raw" + ] + }, + "handle": { + "title": "Handle", + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + }, + "required": [ + "encoding" + ] + }, + "oscore": { + "title": "OSCORE Configuration", + "type": "object", + "properties": { + "senderid": { + "title": "Sender ID", + "type": "string" + }, + "recipientid": { + "title": "Recipient ID", + "type": "string" + }, + "ssn": { + "title": "Sender Sequence Number", + "type": "integer", + "readOnly": true + }, + "desc": { + "title": "Security Context Description", + "type": "string" + } + } + } + }, + "required": [ + "credid", + "subjectuuid", + "credtype" + ] + } + }, + "rowneruuid": { + "title": "Resource Owner ID", + "type": "string", + "format": "uuid" + } + }, + "required": [ + "creds", + "rowneruuid" + ], + "@type": [ + "oic.r.cred" + ] } } } \ No newline at end of file diff --git a/bridge/resources/thingDescription/resource_test.go b/bridge/resources/thingDescription/resource_test.go index 9764247c..eb770820 100644 --- a/bridge/resources/thingDescription/resource_test.go +++ b/bridge/resources/thingDescription/resource_test.go @@ -25,9 +25,8 @@ import ( "testing" "time" - "github.com/fredbi/uri" "github.com/google/uuid" - "github.com/plgd-dev/device/v2/bridge/device/thingDescription" + bridgeDeviceTD "github.com/plgd-dev/device/v2/bridge/device/thingDescription" thingDescriptionResource "github.com/plgd-dev/device/v2/bridge/resources/thingDescription" "github.com/plgd-dev/device/v2/bridge/service" bridgeTest "github.com/plgd-dev/device/v2/bridge/test" @@ -98,8 +97,8 @@ func getEndpoint(t *testing.T, c *client.Client, deviceID string) string { } func getPatchedTD(td wotTD.ThingDescription, d service.Device, epURI string) wotTD.ThingDescription { - return thingDescription.PatchThingDescription(td, d, epURI, func(resourceHref string, resource thingDescription.Resource) (wotTD.PropertyElement, bool) { - return bridgeTest.GetPropertyElement(td, d, epURI, resourceHref, resource) + return bridgeDeviceTD.PatchThingDescription(td, d, epURI, func(resourceHref string, resource bridgeDeviceTD.Resource) (wotTD.PropertyElement, bool) { + return bridgeTest.GetPropertyElement(td, d, epURI, resourceHref, resource, message.AppCBOR.String()) }) } @@ -108,8 +107,8 @@ func TestGetThingDescription(t *testing.T) { t.Cleanup(func() { _ = s.Shutdown() }) - deviceID := uuid.New().String() - d := bridgeTest.NewBridgedDevice(t, s, deviceID, true, true, true) + deviceID := uuid.New() + d := bridgeTest.NewBridgedDevice(t, s, deviceID.String(), true, true, true) defer func() { s.DeleteAndCloseDevice(d.GetID()) }() @@ -127,7 +126,7 @@ func TestGetThingDescription(t *testing.T) { require.NoError(t, errC) }() - td, err := bridgeTest.ThingDescription(true, true) + td, err := bridgeTest.ThingDescription(deviceID, "", true, true) require.NoError(t, err) epURI := getEndpoint(t, c, d.GetID().String()) td = getPatchedTD(td, d, epURI) @@ -201,10 +200,10 @@ func TestObserveThingDescription(t *testing.T) { _ = s.Shutdown() }) - deviceID := uuid.New().String() - td, err := bridgeTest.ThingDescription(true, true) + deviceID := uuid.New() + td, err := bridgeTest.ThingDescription(deviceID, "", true, true) require.NoError(t, err) - d := bridgeTest.NewBridgedDeviceWithThingDescription(t, s, deviceID, true, true, &td) + d := bridgeTest.NewBridgedDeviceWithThingDescription(t, s, deviceID.String(), true, true, &td) defer func() { s.DeleteAndCloseDevice(d.GetID()) }() @@ -244,7 +243,7 @@ func TestObserveThingDescription(t *testing.T) { base, err := url.Parse("http://localhost:8080") require.NoError(t, err) - id, err := uri.Parse("urn:uuid:" + deviceID) + id, err := bridgeDeviceTD.GetThingDescriptionID(deviceID.String()) require.NoError(t, err) td2 := wotTD.ThingDescription{ Base: *base, diff --git a/bridge/test/test.go b/bridge/test/test.go index 8b9ea736..341942fc 100644 --- a/bridge/test/test.go +++ b/bridge/test/test.go @@ -22,19 +22,24 @@ import ( "context" "crypto/tls" "crypto/x509" - "encoding/json" + "errors" + "net/url" "testing" "github.com/google/uuid" "github.com/plgd-dev/device/v2/bridge/device" "github.com/plgd-dev/device/v2/bridge/device/cloud" - "github.com/plgd-dev/device/v2/bridge/device/thingDescription" + bridgeDeviceTD "github.com/plgd-dev/device/v2/bridge/device/thingDescription" + thingDescriptionResource "github.com/plgd-dev/device/v2/bridge/resources/thingDescription" "github.com/plgd-dev/device/v2/bridge/service" "github.com/plgd-dev/device/v2/pkg/log" "github.com/plgd-dev/device/v2/schema" schemaCloud "github.com/plgd-dev/device/v2/schema/cloud" "github.com/plgd-dev/device/v2/schema/credential" + schemaDevice "github.com/plgd-dev/device/v2/schema/device" + schemaMaintenance "github.com/plgd-dev/device/v2/schema/maintenance" "github.com/plgd-dev/device/v2/test" + "github.com/plgd-dev/go-coap/v3/message" "github.com/stretchr/testify/require" wotTD "github.com/web-of-things-open-source/thingdescription-go/thingDescription" ) @@ -110,21 +115,22 @@ func makeDeviceConfig(id uuid.UUID, cloudEnabled bool, credentialEnabled bool) d return cfg } -func GetPropertyElement(td wotTD.ThingDescription, device thingDescription.Device, endpoint string, resourceHref string, resource thingDescription.Resource) (wotTD.PropertyElement, bool) { +func GetPropertyElement(td wotTD.ThingDescription, device bridgeDeviceTD.Device, endpoint string, resourceHref string, resource bridgeDeviceTD.Resource, contentType string) (wotTD.PropertyElement, bool) { propElement, ok := td.Properties[resourceHref] if !ok { return wotTD.PropertyElement{}, false } - propElement = thingDescription.PatchPropertyElement(propElement, device.GetID(), resource, endpoint != "") - return propElement, true + propElement, err := bridgeDeviceTD.PatchPropertyElement(propElement, resource.GetResourceTypes(), endpoint != "", device.GetID(), resource.GetHref(), + resource.SupportsOperations(), contentType) + return propElement, err == nil } func NewBridgedDevice(t *testing.T, s *service.Service, id string, cloudEnabled, credentialEnabled, thingDescriptionEnabled bool, opts ...device.Option) service.Device { - u, err := uuid.Parse(id) + deviceID, err := uuid.Parse(id) require.NoError(t, err) - cfg := makeDeviceConfig(u, cloudEnabled, credentialEnabled) + cfg := makeDeviceConfig(deviceID, cloudEnabled, credentialEnabled) if thingDescriptionEnabled { - td, err := ThingDescription(cloudEnabled, credentialEnabled) + td, err := ThingDescription(deviceID, "", cloudEnabled, credentialEnabled) require.NoError(t, err) return NewBridgedDeviceWithThingDescription(t, s, id, cloudEnabled, credentialEnabled, &td, opts...) } @@ -141,9 +147,9 @@ func NewBridgedDeviceWithThingDescription(t *testing.T, s *service.Service, id s if len(endpoints) > 0 { endpoint = endpoints[0].URI } - newTD := thingDescription.PatchThingDescription(*td, device, endpoint, - func(resourceHref string, resource thingDescription.Resource) (wotTD.PropertyElement, bool) { - return GetPropertyElement(*td, device, endpoint, resourceHref, resource) + newTD := bridgeDeviceTD.PatchThingDescription(*td, device, endpoint, + func(resourceHref string, resource bridgeDeviceTD.Resource) (wotTD.PropertyElement, bool) { + return GetPropertyElement(*td, device, endpoint, resourceHref, resource, message.AppCBOR.String()) }) return &newTD })) @@ -151,204 +157,76 @@ func NewBridgedDeviceWithThingDescription(t *testing.T, s *service.Service, id s return NewBridgedDeviceWithConfig(t, s, cfg, opts...) } -func ThingDescription(cloudEnabled, credentialEnabled bool) (wotTD.ThingDescription, error) { - type ThingDescription struct { - Context string `json:"@context"` - Type []string `json:"@type"` - ID string `json:"id"` - Properties map[string]interface{} `json:"properties"` +func getOCFResourcesProperties(deviceID uuid.UUID, baseURL string, cloudEnabled, credentialEnabled bool) (map[string]wotTD.PropertyElement, error) { + properties := make(map[string]wotTD.PropertyElement) + deviceResource, ok := thingDescriptionResource.GetOCFResourcePropertyElement(schemaDevice.ResourceURI) + if !ok { + return nil, errors.New("device resource not found") + } + deviceResource, err := thingDescriptionResource.PatchDeviceResourcePropertyElement(deviceResource, deviceID, baseURL, message.AppCBOR.String(), "") + if err != nil { + return nil, err } + properties[schemaDevice.ResourceURI] = deviceResource - td := ThingDescription{ - Context: "https://www.w3.org/2019/wot/td/v1", - Type: []string{"Thing"}, - ID: "urn:uuid:bridge", - Properties: map[string]interface{}{ - "/oic/d": map[string]interface{}{ - "title": "Device Information", - "type": "object", - "properties": map[string]interface{}{ - "piid": map[string]interface{}{ - "title": "Protocol Interface ID", - "type": "string", - "readOnly": true, - "format": "uuid", - }, - "n": map[string]interface{}{ - "title": "Device Name", - "type": "string", - "readOnly": true, - }, - "di": map[string]interface{}{ - "title": "Device ID", - "type": "string", - "readOnly": true, - "format": "uuid", - }, - }, - }, - "/oic/mnt": map[string]interface{}{ - "title": "Maintenance", - "type": "object", - "properties": map[string]interface{}{ - "fr": map[string]interface{}{ - "title": "Factory Reset", - "type": "boolean", - }, - }, - }, - }, + maintenanceResource, ok := thingDescriptionResource.GetOCFResourcePropertyElement(schemaMaintenance.ResourceURI) + if !ok { + return nil, errors.New("maintenance resource not found") + } + properties[schemaMaintenance.ResourceURI] = maintenanceResource + maintenanceResource, err = thingDescriptionResource.PatchMaintenanceResourcePropertyElement(maintenanceResource, deviceID, baseURL, message.AppCBOR.String()) + if err != nil { + return nil, err } + properties[schemaMaintenance.ResourceURI] = maintenanceResource if cloudEnabled { - td.Properties[schemaCloud.ResourceURI] = map[string]interface{}{ - "title": "CoapCloudConfResURI", - "type": "object", - "properties": map[string]interface{}{ - "apn": map[string]interface{}{ - "title": "Authorization provider name", - "type": "string", - }, - "cis": map[string]interface{}{ - "title": "Cloud interface server", - "type": "string", - "format": "uri", - }, - "sid": map[string]interface{}{ - "title": "Cloud ID", - "type": "string", - "format": "uuid", - }, - "at": map[string]interface{}{ - "title": "Access token", - "type": "string", - }, - "cps": map[string]interface{}{ - "title": "Provisioning status", - "type": "string", - "enum": []schemaCloud.ProvisioningStatus{ - schemaCloud.ProvisioningStatus_UNINITIALIZED, - schemaCloud.ProvisioningStatus_READY_TO_REGISTER, - schemaCloud.ProvisioningStatus_REGISTERING, - schemaCloud.ProvisioningStatus_REGISTERED, - schemaCloud.ProvisioningStatus_FAILED, - }, - }, - "clec": map[string]interface{}{ - "title": "Last error code", - "type": "integer", - }, - }, + cloudResource, ok := thingDescriptionResource.GetOCFResourcePropertyElement(schemaCloud.ResourceURI) + if !ok { + return nil, errors.New("cloud resource not found") + } + cloudResource, err = thingDescriptionResource.PatchCloudResourcePropertyElement(cloudResource, deviceID, baseURL, message.AppCBOR.String()) + if err != nil { + return nil, err } + properties[schemaCloud.ResourceURI] = cloudResource } if credentialEnabled { - td.Properties[credential.ResourceURI] = map[string]interface{}{ - "title": "Credential", - "type": "object", - "properties": map[string]interface{}{ - "credid": map[string]interface{}{ - "title": "Credential ID", - "type": "integer", - }, - "credtype": map[string]interface{}{ - "title": "Credential Type", - "type": "integer", - "enum": []int{ - int(credential.CredentialType_EMPTY), - int(credential.CredentialType_SYMMETRIC_PAIR_WISE), - int(credential.CredentialType_SYMMETRIC_GROUP), - int(credential.CredentialType_ASYMMETRIC_SIGNING), - int(credential.CredentialType_ASYMMETRIC_SIGNING_WITH_CERTIFICATE), - int(credential.CredentialType_PIN_OR_PASSWORD), - int(credential.CredentialType_ASYMMETRIC_ENCRYPTION_KEY), - }, - }, - "subjectuuid": map[string]interface{}{ - "title": "Subject UUID", - "type": "string", - }, - "credusage": map[string]interface{}{ - "title": "Credential Usage", - "type": "string", - "enum": []credential.CredentialUsage{ - credential.CredentialUsage_TRUST_CA, - credential.CredentialUsage_CERT, - credential.CredentialUsage_ROLE_CERT, - credential.CredentialUsage_MFG_TRUST_CA, - credential.CredentialUsage_MFG_CERT, - }, - }, - "privatedata": map[string]interface{}{ - "title": "Private Data", - "type": "object", - "properties": map[string]interface{}{ - "data": map[string]interface{}{ - "title": "Data", - "type": "string", - }, - "encoding": map[string]interface{}{ - "title": "Encoding", - "type": "string", - "enum": []credential.CredentialPrivateDataEncoding{ - credential.CredentialPrivateDataEncoding_JWT, - credential.CredentialPrivateDataEncoding_CWT, - credential.CredentialPrivateDataEncoding_BASE64, - credential.CredentialPrivateDataEncoding_URI, - credential.CredentialPrivateDataEncoding_HANDLE, - credential.CredentialPrivateDataEncoding_RAW, - }, - }, - }, - }, - "publicdata": map[string]interface{}{ - "title": "Public Data", - "type": "object", - "properties": map[string]interface{}{ - "data": map[string]interface{}{ - "title": "Data", - "type": "string", - }, - "encoding": map[string]interface{}{ - "title": "Encoding", - "type": "string", - "enum": []credential.CredentialPublicDataEncoding{ - credential.CredentialPublicDataEncoding_JWT, - credential.CredentialPublicDataEncoding_CWT, - credential.CredentialPublicDataEncoding_BASE64, - credential.CredentialPublicDataEncoding_URI, - credential.CredentialPublicDataEncoding_PEM, - credential.CredentialPublicDataEncoding_DER, - credential.CredentialPublicDataEncoding_RAW, - }, - }, - }, - }, - "roleid": map[string]interface{}{ - "title": "Role ID", - "type": "object", - "properties": map[string]interface{}{ - "authority": map[string]interface{}{ - "title": "Authority", - "type": "string", - }, - "role": map[string]interface{}{ - "title": "Role", - "type": "string", - }, - }, - }, - "tag": map[string]interface{}{ - "title": "Tag", - "type": "string", - }, - }, + credentialResource, ok := thingDescriptionResource.GetOCFResourcePropertyElement(credential.ResourceURI) + if !ok { + return nil, errors.New("credential resource not found") + } + credentialResource, err = thingDescriptionResource.PatchCredentialResourcePropertyElement(credentialResource, deviceID, baseURL, message.AppCBOR.String()) + if err != nil { + return nil, err + } + properties[credential.ResourceURI] = credentialResource + } + return properties, nil +} + +func ThingDescription(deviceID uuid.UUID, baseURL string, cloudEnabled, credentialEnabled bool) (wotTD.ThingDescription, error) { + td := wotTD.ThingDescription{} + td.Context = &bridgeDeviceTD.Context + td.Type = &wotTD.TypeDeclaration{StringArray: []string{"Thing"}} + id, err := bridgeDeviceTD.GetThingDescriptionID(deviceID.String()) + if err != nil { + return wotTD.ThingDescription{}, err + } + td.ID = id + if baseURL != "" { + base, errP := url.Parse(baseURL) + if errP != nil { + return wotTD.ThingDescription{}, errP } + td.Base = *base } - tdJson, err := json.Marshal(td) + properties, err := getOCFResourcesProperties(deviceID, baseURL, cloudEnabled, credentialEnabled) if err != nil { return wotTD.ThingDescription{}, err } - return wotTD.UnmarshalThingDescription(tdJson) + td.Properties = properties + return td, nil } diff --git a/cmd/bridge-device/bridge-device.jsonld b/cmd/bridge-device/bridge-device.jsonld index 245902d7..547b123c 100644 --- a/cmd/bridge-device/bridge-device.jsonld +++ b/cmd/bridge-device/bridge-device.jsonld @@ -4,36 +4,5 @@ "Thing" ], "id": "urn:uuid:bridge", - "properties": { - "/test/0": { - "title": "Test Resource", - "type": "object", - "properties": { - "Name": { - "title": "Name", - "type": "string" - } - } - }, - "/test/1": { - "title": "Test Resource", - "type": "object", - "properties": { - "Name": { - "title": "Name", - "type": "string" - } - } - }, - "/test/2": { - "title": "Test Resource", - "type": "object", - "properties": { - "Name": { - "title": "Name", - "type": "string" - } - } - } - } + "properties": {} } \ No newline at end of file diff --git a/cmd/bridge-device/device.go b/cmd/bridge-device/device.go new file mode 100644 index 00000000..9f2a6308 --- /dev/null +++ b/cmd/bridge-device/device.go @@ -0,0 +1,120 @@ +package main + +import ( + "bytes" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/plgd-dev/device/v2/bridge/net" + "github.com/plgd-dev/device/v2/bridge/resources" + "github.com/plgd-dev/device/v2/bridge/service" + bridgeDevice "github.com/plgd-dev/device/v2/cmd/bridge-device/device" + "github.com/plgd-dev/device/v2/pkg/codec/cbor" + codecOcf "github.com/plgd-dev/device/v2/pkg/codec/ocf" + "github.com/plgd-dev/device/v2/schema/interfaces" + "github.com/plgd-dev/go-coap/v3/message" + "github.com/plgd-dev/go-coap/v3/message/codes" + "github.com/plgd-dev/go-coap/v3/message/pool" + coapSync "github.com/plgd-dev/go-coap/v3/pkg/sync" +) + +type resourceData struct { + Name string `json:"name,omitempty"` +} + +type resourceDataSync struct { + resourceData + lock sync.Mutex +} + +func (r *resourceDataSync) setName(name string) { + r.lock.Lock() + defer r.lock.Unlock() + r.Name = name +} + +func (r *resourceDataSync) copy() resourceData { + r.lock.Lock() + defer r.lock.Unlock() + return resourceData{ + Name: r.Name, + } +} + +func addResources(d service.Device, numResources int) { + if numResources <= 0 { + return + } + obsWatcher := coapSync.NewMap[uint64, func()]() + for i := 0; i < numResources; i++ { + addResource(d, i, obsWatcher) + } + go func() { + // notify observers every 500ms + for { + time.Sleep(time.Millisecond * 500) + obsWatcher.Range(func(_ uint64, h func()) bool { + h() + return true + }) + } + }() +} + +func addResource(d service.Device, idx int, obsWatcher *coapSync.Map[uint64, func()]) { + rds := resourceDataSync{ + resourceData: resourceData{ + Name: fmt.Sprintf("test-%v", idx), + }, + } + + resHandler := func(req *net.Request) (*pool.Message, error) { + resp := pool.NewMessage(req.Context()) + switch req.Code() { + case codes.GET: + resp.SetCode(codes.Content) + case codes.POST: + resp.SetCode(codes.Changed) + default: + return nil, fmt.Errorf("invalid method %v", req.Code()) + } + resp.SetContentFormat(message.AppOcfCbor) + data, err := cbor.Encode(rds.copy()) + if err != nil { + return nil, err + } + resp.SetBody(bytes.NewReader(data)) + return resp, nil + } + + resPostHandler := func(req *net.Request) (*pool.Message, error) { + codec := codecOcf.VNDOCFCBORCodec{} + var newData resourceData + err := codec.Decode(req.Message, &newData) + if err != nil { + return nil, err + } + rds.setName(newData.Name) + return resHandler(req) + } + + var subID atomic.Uint64 + res := resources.NewResource(bridgeDevice.GetTestResourceHref(idx), resHandler, resPostHandler, []string{bridgeDevice.TestResourceType}, []string{interfaces.OC_IF_BASELINE, interfaces.OC_IF_RW}) + res.SetObserveHandler(d.GetLoop(), func(req *net.Request, handler func(msg *pool.Message, err error)) (cancel func(), err error) { + sub := subID.Add(1) + obsWatcher.Store(sub, func() { + resp, err := resHandler(req) + if err != nil { + handler(nil, err) + return + } + handler(resp, nil) + }) + return func() { + obsWatcher.Delete(sub) + }, nil + }) + d.AddResources(res) +} diff --git a/cmd/bridge-device/config.go b/cmd/bridge-device/device/config.go similarity index 77% rename from cmd/bridge-device/config.go rename to cmd/bridge-device/device/config.go index c1162365..4cec9f97 100644 --- a/cmd/bridge-device/config.go +++ b/cmd/bridge-device/device/config.go @@ -1,10 +1,14 @@ -package main +package device import ( "errors" + "fmt" + "os" + "path/filepath" "github.com/plgd-dev/device/v2/bridge/service" "github.com/plgd-dev/device/v2/pkg/log" + "gopkg.in/yaml.v3" ) type TLSConfig struct { @@ -30,6 +34,7 @@ type LogConfig struct { type CloudConfig struct { Enabled bool `yaml:"enabled" json:"enabled" description:"enable cloud connection"` + CloudID string `yaml:"cloudID" json:"cloudID" description:"cloud id"` TLS TLSConfig `yaml:"tls" json:"tls"` } @@ -68,3 +73,24 @@ func (c *Config) Validate() error { } return nil } + +func LoadConfig(configFile string) (Config, error) { + // Sanitize the configFile variable to ensure it only contains a valid file path + configFile = filepath.Clean(configFile) + f, err := os.Open(configFile) + if err != nil { + return Config{}, fmt.Errorf("failed to load config from %s: %w", configFile, err) + } + defer func() { + _ = f.Close() + }() + var cfg Config + err = yaml.NewDecoder(f).Decode(&cfg) + if err != nil { + return Config{}, fmt.Errorf("failed to decode config: %w", err) + } + if err = cfg.Validate(); err != nil { + return Config{}, err + } + return cfg, nil +} diff --git a/cmd/bridge-device/device/device.go b/cmd/bridge-device/device/device.go new file mode 100644 index 00000000..3d5c879f --- /dev/null +++ b/cmd/bridge-device/device/device.go @@ -0,0 +1,83 @@ +package device + +import ( + "os" + "path/filepath" + "strconv" + + "github.com/google/uuid" + bridgeTD "github.com/plgd-dev/device/v2/bridge/device/thingDescription" + "github.com/web-of-things-open-source/thingdescription-go/thingDescription" +) + +const ( + DeviceResourceType = "oic.d.virtual" + TestResourcePropertyKey = "my-property" + TestResourceType = "x.plgd.test" +) + +func GetTestResourceHref(id int) string { + return "/test/" + strconv.Itoa(id) +} + +func GetPropertyDescriptionForTestResource() thingDescription.PropertyElement { + objectType := thingDescription.Object + stringType := thingDescription.String + return thingDescription.PropertyElement{ + Type: &thingDescription.TypeDeclaration{ + StringArray: []string{TestResourceType}, + }, + Title: bridgeTD.StringToPtr("Test Property"), + PropertyElementType: &objectType, + Properties: &thingDescription.Properties{ + DataSchemaMap: map[string]thingDescription.DataSchema{ + "Name": { + Title: bridgeTD.StringToPtr("Name"), + DataSchemaType: &stringType, + }, + }, + }, + Observable: bridgeTD.BoolToPtr(true), + } +} + +func PatchTestResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, href, contentType string) (thingDescription.PropertyElement, error) { + propOps := bridgeTD.GetPropertyElementOperations(pe) + return bridgeTD.PatchPropertyElement(pe, []string{TestResourceType}, true, deviceID, href, + propOps.ToSupportedOperations(), contentType) +} + +func GetAdditionalProperties() map[string]interface{} { + return map[string]interface{}{ + TestResourcePropertyKey: "my-value", + } +} + +func GetDataSchemaForAdditionalProperties() map[string]thingDescription.DataSchema { + dsm := map[string]thingDescription.DataSchema{} + stringType := thingDescription.String + readOnly := true + dsm[TestResourcePropertyKey] = thingDescription.DataSchema{ + DataSchemaType: &stringType, + ReadOnly: &readOnly, + } + return dsm +} + +func GetThingDescription(path string, numResources int) (thingDescription.ThingDescription, error) { + tdJson, err := os.ReadFile(filepath.Clean(path)) + if err != nil { + return thingDescription.ThingDescription{}, err + } + td, err := thingDescription.UnmarshalThingDescription(tdJson) + if err != nil { + return thingDescription.ThingDescription{}, err + } + if td.Properties == nil { + td.Properties = make(map[string]thingDescription.PropertyElement) + } + for i := 0; i < numResources; i++ { + td.Properties[GetTestResourceHref(i)] = GetPropertyDescriptionForTestResource() + } + return td, nil +} diff --git a/cmd/bridge-device/main.go b/cmd/bridge-device/main.go index 62ebb795..fa2115d3 100644 --- a/cmd/bridge-device/main.go +++ b/cmd/bridge-device/main.go @@ -1,7 +1,6 @@ package main import ( - "bytes" "context" "crypto/tls" "crypto/x509" @@ -10,156 +9,24 @@ import ( "fmt" "os" "os/signal" - "path/filepath" - "sync" - "sync/atomic" "syscall" - "time" "github.com/google/uuid" "github.com/plgd-dev/device/v2/bridge/device" "github.com/plgd-dev/device/v2/bridge/device/cloud" "github.com/plgd-dev/device/v2/bridge/device/thingDescription" - "github.com/plgd-dev/device/v2/bridge/net" - "github.com/plgd-dev/device/v2/bridge/resources" thingDescriptionResource "github.com/plgd-dev/device/v2/bridge/resources/thingDescription" "github.com/plgd-dev/device/v2/bridge/service" - "github.com/plgd-dev/device/v2/pkg/codec/cbor" - codecOcf "github.com/plgd-dev/device/v2/pkg/codec/ocf" + bridgeDevice "github.com/plgd-dev/device/v2/cmd/bridge-device/device" "github.com/plgd-dev/device/v2/pkg/log" pkgX509 "github.com/plgd-dev/device/v2/pkg/security/x509" "github.com/plgd-dev/device/v2/schema" deviceResource "github.com/plgd-dev/device/v2/schema/device" - "github.com/plgd-dev/device/v2/schema/interfaces" "github.com/plgd-dev/go-coap/v3/message" - "github.com/plgd-dev/go-coap/v3/message/codes" - "github.com/plgd-dev/go-coap/v3/message/pool" - coapSync "github.com/plgd-dev/go-coap/v3/pkg/sync" wotTD "github.com/web-of-things-open-source/thingdescription-go/thingDescription" - "gopkg.in/yaml.v3" ) -const myPropertyKey = "my-property" - -func loadConfig(configFile string) (Config, error) { - // Sanitize the configFile variable to ensure it only contains a valid file path - configFile = filepath.Clean(configFile) - f, err := os.Open(configFile) - if err != nil { - return Config{}, err - } - defer func() { - _ = f.Close() - }() - var cfg Config - err = yaml.NewDecoder(f).Decode(&cfg) - if err != nil { - return Config{}, err - } - if err = cfg.Validate(); err != nil { - return Config{}, err - } - return cfg, nil -} - -type resourceData struct { - Name string `json:"name,omitempty"` -} - -type resourceDataSync struct { - resourceData - lock sync.Mutex -} - -func (r *resourceDataSync) setName(name string) { - r.lock.Lock() - defer r.lock.Unlock() - r.Name = name -} - -func (r *resourceDataSync) copy() resourceData { - r.lock.Lock() - defer r.lock.Unlock() - return resourceData{ - Name: r.Name, - } -} - -func addResources(d service.Device, numResources int) { - if numResources <= 0 { - return - } - obsWatcher := coapSync.NewMap[uint64, func()]() - for i := 0; i < numResources; i++ { - addResource(d, i, obsWatcher) - } - go func() { - // notify observers every 500ms - for { - time.Sleep(time.Millisecond * 500) - obsWatcher.Range(func(_ uint64, h func()) bool { - h() - return true - }) - } - }() -} - -func addResource(d service.Device, idx int, obsWatcher *coapSync.Map[uint64, func()]) { - rds := resourceDataSync{ - resourceData: resourceData{ - Name: fmt.Sprintf("test-%v", idx), - }, - } - - resHandler := func(req *net.Request) (*pool.Message, error) { - resp := pool.NewMessage(req.Context()) - switch req.Code() { - case codes.GET: - resp.SetCode(codes.Content) - case codes.POST: - resp.SetCode(codes.Changed) - default: - return nil, fmt.Errorf("invalid method %v", req.Code()) - } - resp.SetContentFormat(message.AppOcfCbor) - data, err := cbor.Encode(rds.copy()) - if err != nil { - return nil, err - } - resp.SetBody(bytes.NewReader(data)) - return resp, nil - } - - var subID atomic.Uint64 - res := resources.NewResource(fmt.Sprintf("/test/%d", idx), resHandler, func(req *net.Request) (*pool.Message, error) { - codec := codecOcf.VNDOCFCBORCodec{} - var newData resourceData - err := codec.Decode(req.Message, &newData) - if err != nil { - return nil, err - } - rds.setName(newData.Name) - return resHandler(req) - }, []string{"x.plgd.test"}, []string{interfaces.OC_IF_BASELINE, interfaces.OC_IF_RW}) - res.SetObserveHandler(d.GetLoop(), func(req *net.Request, handler func(msg *pool.Message, err error)) (cancel func(), err error) { - sub := subID.Add(1) - obsWatcher.Store(sub, func() { - resp, err := resHandler(req) - if err != nil { - handler(nil, err) - return - } - handler(resp, nil) - }) - return func() { - obsWatcher.Delete(sub) - }, nil - }) - d.AddResources(res) -} - -func getCloudTLS(cfg CloudConfig, credentialEnabled bool) (cloud.CAPool, *tls.Certificate, error) { +func getCloudTLS(cfg bridgeDevice.CloudConfig, credentialEnabled bool) (cloud.CAPool, *tls.Certificate, error) { var ca []*x509.Certificate var err error if cfg.TLS.CAPoolPath == "" && !credentialEnabled { @@ -202,7 +69,7 @@ func handleSignals(s *service.Service) { } } -func getCloudOpts(cfg Config) ([]device.Option, error) { +func getCloudOpts(cfg bridgeDevice.Config) ([]device.Option, error) { caPool, cert, err := getCloudTLS(cfg.Cloud, cfg.Credential.Enabled) if err != nil { return nil, err @@ -216,12 +83,26 @@ func getCloudOpts(cfg Config) ([]device.Option, error) { return opts, nil } -func getTDOpts(cfg Config) ([]device.Option, error) { - tdJson, err := os.ReadFile(cfg.ThingDescription.File) - if err != nil { - return nil, err +func patchPropertyElement(td wotTD.ThingDescription, dev *device.Device, endpoint string, resourceHref string, resource thingDescription.Resource) (wotTD.PropertyElement, bool) { + propElement, ok := td.Properties[resourceHref] + if !ok { + propElement, ok = thingDescriptionResource.GetOCFResourcePropertyElement(resourceHref) + if ok && resourceHref == deviceResource.ResourceURI && propElement.Properties != nil && propElement.Properties.DataSchemaMap != nil { + addProps := bridgeDevice.GetDataSchemaForAdditionalProperties() + for key, prop := range addProps { + propElement.Properties.DataSchemaMap[key] = prop + } + } } - td, err := wotTD.UnmarshalThingDescription(tdJson) + if !ok { + return wotTD.PropertyElement{}, false + } + propElement, err := thingDescription.PatchPropertyElement(propElement, resource.GetResourceTypes(), endpoint != "", dev.GetID(), resource.GetHref(), resource.SupportsOperations(), message.AppCBOR.String()) + return propElement, err == nil +} + +func getTDOpts(cfg bridgeDevice.Config) ([]device.Option, error) { + td, err := bridgeDevice.GetThingDescription(cfg.ThingDescription.File, cfg.NumResourcesPerDevice) if err != nil { return nil, err } @@ -231,35 +112,15 @@ func getTDOpts(cfg Config) ([]device.Option, error) { endpoint = endpoints[0].URI } newTD := thingDescription.PatchThingDescription(td, dev, endpoint, func(resourceHref string, resource thingDescription.Resource) (wotTD.PropertyElement, bool) { - propElement, ok := td.Properties[resourceHref] - if !ok { - propElement, ok = thingDescriptionResource.GetOCFResourcePropertyElement(resourceHref) - if ok && resourceHref == deviceResource.ResourceURI && propElement.Properties != nil && propElement.Properties.DataSchemaMap != nil { - stringType := wotTD.String - readOnly := true - propElement.Properties.DataSchemaMap[myPropertyKey] = wotTD.DataSchema{ - DataSchemaType: &stringType, - ReadOnly: &readOnly, - } - } - } - if !ok { - return wotTD.PropertyElement{}, false - } - propElement = thingDescription.PatchPropertyElement(propElement, dev.GetID(), resource, endpoint != "") - return propElement, true + return patchPropertyElement(td, dev, endpoint, resourceHref, resource) }) return &newTD })}, nil } -func getOpts(cfg Config) ([]device.Option, error) { +func getOpts(cfg bridgeDevice.Config) ([]device.Option, error) { opts := []device.Option{ - device.WithGetAdditionalPropertiesForResponse(func() map[string]interface{} { - return map[string]interface{}{ - myPropertyKey: "my-value", - } - }), + device.WithGetAdditionalPropertiesForResponse(bridgeDevice.GetAdditionalProperties), } if cfg.Cloud.Enabled { cloudOpts, err := getCloudOpts(cfg) @@ -281,7 +142,7 @@ func getOpts(cfg Config) ([]device.Option, error) { func main() { configFile := flag.String("config", "config.yaml", "path to config file") flag.Parse() - cfg, err := loadConfig(*configFile) + cfg, err := bridgeDevice.LoadConfig(*configFile) if err != nil { panic(err) } @@ -299,12 +160,15 @@ func main() { newDevice := func(id uuid.UUID, piid uuid.UUID) (service.Device, error) { return device.New(device.Config{ Name: fmt.Sprintf("bridged-device-%d", i), - ResourceTypes: []string{"oic.d.virtual"}, + ResourceTypes: []string{bridgeDevice.DeviceResourceType}, ID: id, ProtocolIndependentID: piid, MaxMessageSize: cfg.Config.API.CoAP.MaxMessageSize, Cloud: device.CloudConfig{ Enabled: cfg.Cloud.Enabled, + Config: cloud.Config{ + CloudID: cfg.Cloud.CloudID, + }, }, Credential: device.CredentialConfig{ Enabled: cfg.Credential.Enabled, diff --git a/test/bridge-device/main.go b/test/bridge-device/main.go deleted file mode 100644 index 327f5e6f..00000000 --- a/test/bridge-device/main.go +++ /dev/null @@ -1,129 +0,0 @@ -package main - -import ( - "crypto/tls" - "crypto/x509" - "fmt" - "os" - "os/signal" - "syscall" - - "github.com/google/uuid" - "github.com/plgd-dev/device/v2/bridge/device" - "github.com/plgd-dev/device/v2/bridge/device/cloud" - "github.com/plgd-dev/device/v2/bridge/net" - "github.com/plgd-dev/device/v2/bridge/service" - "github.com/plgd-dev/device/v2/pkg/log" - pkgX509 "github.com/plgd-dev/device/v2/pkg/security/x509" -) - -const ( - numGeneratedBridgedDevices = 3 -) - -func testConfig() service.Config { - return service.Config{ - API: service.APIConfig{ - CoAP: service.CoAPConfig{ - ID: uuid.New().String(), - Config: net.Config{ - ExternalAddresses: []string{"127.0.0.1:15683", "[::1]:15683"}, - MaxMessageSize: 2097152, - }, - }, - }, - } -} - -func getCloudTLS() (cloud.CAPool, *tls.Certificate, error) { - caPath := os.Getenv("CA_POOL") - fmt.Printf("Loading CA(%s)\n", caPath) - ca, err := pkgX509.ReadPemCertificates(caPath) - if err != nil { - return cloud.CAPool{}, nil, fmt.Errorf("cannot load ca: %w", err) - } - caPool := cloud.MakeCAPool(func() []*x509.Certificate { - return ca - }, false) - - certPath := os.Getenv("CERT_FILE") - keyPath := os.Getenv("KEY_FILE") - if keyPath != "" && certPath != "" { - fmt.Printf("Loading certificate(%s) and key(%s)\n", certPath, keyPath) - cert, err := tls.LoadX509KeyPair(certPath, keyPath) - if err != nil { - return cloud.CAPool{}, nil, fmt.Errorf("cannot load cert: %w", err) - } - return caPool, &cert, nil - } - return caPool, nil, nil -} - -func handleSignals(s *service.Service) { - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) - - for sig := range sigCh { - switch sig { - case syscall.SIGINT: - os.Exit(0) - return - case syscall.SIGTERM: - _ = s.Shutdown() - return - } - } -} - -func main() { - cfg := testConfig() - if err := cfg.Validate(); err != nil { - panic(err) - } - s, err := service.New(cfg, service.WithLogger(log.NewStdLogger(log.LevelDebug))) - if err != nil { - panic(err) - } - - opts := []device.Option{} - caPool, cert, errC := getCloudTLS() - if errC != nil { - panic(errC) - } - opts = append(opts, device.WithCAPool(caPool)) - if cert != nil { - opts = append(opts, device.WithGetCertificates(func(string) []tls.Certificate { - return []tls.Certificate{*cert} - })) - } - - for i := 0; i < numGeneratedBridgedDevices; i++ { - newDevice := func(id uuid.UUID, piid uuid.UUID) (service.Device, error) { - return device.New(device.Config{ - Name: fmt.Sprintf("bridged-device-%d", i), - ResourceTypes: []string{"oic.d.virtual"}, - ID: id, - ProtocolIndependentID: piid, - MaxMessageSize: cfg.API.CoAP.MaxMessageSize, - Cloud: device.CloudConfig{ - Enabled: true, - Config: cloud.Config{ - CloudID: os.Getenv("CLOUD_SID"), - }, - }, - }, append(opts, device.WithLogger(device.NewLogger(id, log.LevelDebug)))...) - } - d, errC := s.CreateDevice(uuid.New(), newDevice) - if errC == nil { - d.Init() - } - } - - go func() { - handleSignals(s) - }() - - if err = s.Serve(); err != nil { - panic(err) - } -} From ed9e6617b84a2ca0afd99d536b3de8822462ca48 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Fri, 10 May 2024 09:31:40 +0000 Subject: [PATCH 2/7] udpate github.com/web-of-things-open-source/thingdescription-go --- bridge/resources/thingDescription/resource.go | 2 +- go.mod | 7 +++++-- go.sum | 10 ++++++++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/bridge/resources/thingDescription/resource.go b/bridge/resources/thingDescription/resource.go index b7563329..02c8841f 100644 --- a/bridge/resources/thingDescription/resource.go +++ b/bridge/resources/thingDescription/resource.go @@ -21,9 +21,9 @@ package thingDescription import ( "bytes" "context" - "encoding/json" "errors" + "github.com/go-json-experiment/json" "github.com/plgd-dev/device/v2/bridge/net" "github.com/plgd-dev/device/v2/bridge/resources" "github.com/plgd-dev/device/v2/schema" diff --git a/go.mod b/go.mod index 99a02b7f..6b7bb53c 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,13 @@ module github.com/plgd-dev/device/v2 -go 1.20 +go 1.21 + +toolchain go1.22.0 require ( github.com/fredbi/uri v1.1.0 github.com/fxamacker/cbor/v2 v2.6.0 + github.com/go-json-experiment/json v0.0.0-20240418180308-af2d5061e6c2 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 @@ -17,7 +20,7 @@ require ( github.com/plgd-dev/kit/v2 v2.0.0-20211006190727-057b33161b90 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240417084639-a2aca7547975 + github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510090525-772cd4ad3459 go.uber.org/atomic v1.11.0 golang.org/x/sync v0.7.0 google.golang.org/grpc v1.63.2 diff --git a/go.sum b/go.sum index 93fddea2..898e1f20 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8= github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -26,6 +27,8 @@ github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrt github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-acme/lego v2.7.2+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= +github.com/go-json-experiment/json v0.0.0-20240418180308-af2d5061e6c2 h1:lhCu2IkNoFfDdcjHos2ZtLdAsyxLZbkpijNzhvvM6BY= +github.com/go-json-experiment/json v0.0.0-20240418180308-af2d5061e6c2/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ocf/go-coap/v2 v2.0.4-0.20200728125043-f38b86f047a7/go.mod h1:X9wVKcaOSx7wBxKcvrWgMQq1R2DNeA7NBLW2osIb8TM= @@ -52,7 +55,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -99,6 +104,7 @@ github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/plgd-dev/go-coap/v2 v2.0.4-0.20200819112225-8eb712b901bc/go.mod h1:+tCi9Q78H/orWRtpVWyBgrr4vKFo2zYtbbxUllerBp4= github.com/plgd-dev/go-coap/v2 v2.4.1-0.20210517130748-95c37ac8e1fa/go.mod h1:rA7fc7ar+B/qa+Q0hRqv7yj/EMtIlmo1l7vkQGSrHPU= github.com/plgd-dev/go-coap/v3 v3.3.4 h1:clDLFOXXmXfhZqB0eSk6WJs2iYfjC2J22Ixwu5MHiO0= @@ -133,8 +139,8 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFXQnxHGXy6E= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240417084639-a2aca7547975 h1:OSKuqET6tBjpWdlN4/ZevP26nhHeUrhVb8jO8NfRKJA= -github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240417084639-a2aca7547975/go.mod h1:OPqnw2lEwlmWVFbBZlpNreJqoqDKNuEr0b8zwYj8WyU= +github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510090525-772cd4ad3459 h1:5JIsujthXmSrKC318Y6UJRyQ2yysV0cuen70fL9LW3U= +github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510090525-772cd4ad3459/go.mod h1:L/jWuWf+v7rmuFykpUP/runRXTnnA0QdGGgou8vzPrw= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From f9605804b8326d8fb91641ccdcf1102553a2d46f Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Fri, 10 May 2024 09:56:22 +0000 Subject: [PATCH 3/7] upgrade golang to 1.22 --- .github/workflows/build-with-cfg.yml | 2 +- .github/workflows/builds.yml | 4 ++-- .github/workflows/golangci-lint.yml | 4 ++-- .github/workflows/test-with-cfg.yml | 4 ++-- .golangci.yml | 2 +- README.md | 2 +- bridge/resources/thingDescription/resource_test.go | 10 ++++++---- cmd/bridge-device/Dockerfile | 2 +- go.mod | 2 +- test/cloud-server/Dockerfile | 2 +- 10 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-with-cfg.yml b/.github/workflows/build-with-cfg.yml index 26f35b8e..93d66ed1 100644 --- a/.github/workflows/build-with-cfg.yml +++ b/.github/workflows/build-with-cfg.yml @@ -23,7 +23,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ inputs.go-version || '^1.20' }} + go-version: ${{ inputs.go-version || '^1.22' }} check-latest: true - run: | diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 47ca9cf8..afe37239 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -17,8 +17,8 @@ jobs: matrix: include: # test build of oldest supported go version - - name: go1.20 - go-version: "~1.20" + - name: go1.22 + go-version: "~1.22" uses: ./.github/workflows/build-with-cfg.yml with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index a105b2d0..65807147 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -16,10 +16,10 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Set up Go 1.20+ + - name: Set up Go 1.22+ uses: actions/setup-go@v5 with: - go-version: "^1.20" # The Go version to download (if necessary) and use. + go-version: "^1.22" # The Go version to download (if necessary) and use. check-latest: true cache: false diff --git a/.github/workflows/test-with-cfg.yml b/.github/workflows/test-with-cfg.yml index 6864075b..b7e27b10 100644 --- a/.github/workflows/test-with-cfg.yml +++ b/.github/workflows/test-with-cfg.yml @@ -37,10 +37,10 @@ jobs: - name: Shallow checkout uses: actions/checkout@v4 - - name: Set up Go 1.20+ + - name: Set up Go 1.22+ uses: actions/setup-go@v5 with: - go-version: "^1.20" + go-version: "^1.22" check-latest: true - run: go version diff --git a/.golangci.yml b/.golangci.yml index 05d872c0..694207db 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -163,4 +163,4 @@ issues: # fix: true run: - go: "1.20" + go: "1.22" diff --git a/README.md b/README.md index d62a3fd9..d98a596b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The **client** enables interaction with devices in a local network: ## Requirements -- Go 1.20 or higher +- Go 1.22 or higher ## Installation OCF Client diff --git a/bridge/resources/thingDescription/resource_test.go b/bridge/resources/thingDescription/resource_test.go index eb770820..82716b80 100644 --- a/bridge/resources/thingDescription/resource_test.go +++ b/bridge/resources/thingDescription/resource_test.go @@ -25,6 +25,7 @@ import ( "testing" "time" + v2json "github.com/go-json-experiment/json" "github.com/google/uuid" bridgeDeviceTD "github.com/plgd-dev/device/v2/bridge/device/thingDescription" thingDescriptionResource "github.com/plgd-dev/device/v2/bridge/resources/thingDescription" @@ -77,10 +78,10 @@ func (JSONCodec) Decode(m *pool.Message, v interface{}) error { func getThingDescription(t *testing.T, data interface{}) wotTD.ThingDescription { tdMap, ok := data.(map[interface{}]interface{}) require.True(t, ok) - jsonData, err := json.Encode(tdMap) + jsonData, err := v2json.Marshal(tdMap) require.NoError(t, err) td := wotTD.ThingDescription{} - err = json.Decode(jsonData, &td) + err = v2json.Unmarshal(jsonData, &td) require.NoError(t, err) return td } @@ -246,8 +247,9 @@ func TestObserveThingDescription(t *testing.T) { id, err := bridgeDeviceTD.GetThingDescriptionID(deviceID.String()) require.NoError(t, err) td2 := wotTD.ThingDescription{ - Base: *base, - ID: id, + Base: *base, + ID: id, + SecurityDefinitions: map[string]wotTD.SecurityScheme{}, } d.GetThingDescriptionManager().NotifySubscriptions(td2) n, err = h.WaitForNotification(ctx) diff --git a/cmd/bridge-device/Dockerfile b/cmd/bridge-device/Dockerfile index 843d84b3..4ffb9df1 100644 --- a/cmd/bridge-device/Dockerfile +++ b/cmd/bridge-device/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM golang:1.20.14-alpine AS build +FROM golang:1.22.3-alpine AS build RUN apk add --no-cache curl git build-base WORKDIR $GOPATH/src/github.com/plgd-dev/device COPY go.mod go.sum ./ diff --git a/go.mod b/go.mod index 6b7bb53c..396e2a8c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/plgd-dev/device/v2 -go 1.21 +go 1.22 toolchain go1.22.0 diff --git a/test/cloud-server/Dockerfile b/test/cloud-server/Dockerfile index 59e6fbb2..f0a435f2 100644 --- a/test/cloud-server/Dockerfile +++ b/test/cloud-server/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20.14-alpine AS build +FROM golang:1.22.3-alpine AS build RUN apk add --no-cache curl git build-base WORKDIR $GOPATH/src/github.com/plgd-dev/device COPY go.mod go.sum ./ From e10f602b7f77ceab8e18d948d0c528cd13852ef1 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Fri, 10 May 2024 12:14:46 +0000 Subject: [PATCH 4/7] set forms for coap additional fields --- .../thingDescription/thingDescription.go | 50 +++++++++++++++---- .../thingDescription/ocfResources.go | 11 ++-- .../thingDescription/resource_test.go | 2 +- bridge/test/test.go | 12 ++--- cmd/bridge-device/device/device.go | 3 +- cmd/bridge-device/main.go | 2 +- go.mod | 2 +- go.sum | 4 +- 8 files changed, 58 insertions(+), 28 deletions(-) diff --git a/bridge/device/thingDescription/thingDescription.go b/bridge/device/thingDescription/thingDescription.go index 30353948..f770f185 100644 --- a/bridge/device/thingDescription/thingDescription.go +++ b/bridge/device/thingDescription/thingDescription.go @@ -1,11 +1,13 @@ package thingDescription import ( + "net/http" "net/url" "github.com/fredbi/uri" "github.com/google/uuid" "github.com/plgd-dev/device/v2/bridge/resources" + "github.com/plgd-dev/go-coap/v3/message" "github.com/web-of-things-open-source/thingdescription-go/thingDescription" ) @@ -97,7 +99,42 @@ func (p PropertyElementOperations) ToSupportedOperations() resources.SupportedOp return ops | resources.SupportedOperationRead | resources.SupportedOperationWrite } -func PatchPropertyElement(prop thingDescription.PropertyElement, types []string, setForm bool, deviceID uuid.UUID, href string, ops resources.SupportedOperation, contentType string) (thingDescription.PropertyElement, error) { +func createForm(hrefUri *url.URL, covMethod string, op thingDescription.StickyDescription, contentType message.MediaType) thingDescription.FormElementProperty { + additionalFields := map[string]interface{}{ + "cov:method": covMethod, + "cov:accept": float64(contentType), + } + ops := []string{string(op)} + if op == thingDescription.Observeproperty { + additionalFields["subprotocol"] = "cov:observe" + ops = append(ops, string(thingDescription.Unobserveproperty)) + } + + return thingDescription.FormElementProperty{ + ContentType: StringToPtr(contentType.String()), + Href: *hrefUri, + Op: &thingDescription.FormElementPropertyOp{ + StringArray: ops, + }, + AdditionalFields: additionalFields, + } +} + +func SetForms(hrefUri *url.URL, ops resources.SupportedOperation, contentType message.MediaType) []thingDescription.FormElementProperty { + forms := make([]thingDescription.FormElementProperty, 0, 3) + if ops.HasOperation(resources.SupportedOperationWrite) { + forms = append(forms, createForm(hrefUri, http.MethodPost, thingDescription.Writeproperty, contentType)) + } + if ops.HasOperation(resources.SupportedOperationRead) { + forms = append(forms, createForm(hrefUri, http.MethodGet, thingDescription.Readproperty, contentType)) + } + if ops.HasOperation(resources.SupportedOperationObserve) { + forms = append(forms, createForm(hrefUri, http.MethodGet, thingDescription.Observeproperty, contentType)) + } + return forms +} + +func PatchPropertyElement(prop thingDescription.PropertyElement, types []string, setForm bool, deviceID uuid.UUID, href string, ops resources.SupportedOperation, contentType message.MediaType) (thingDescription.PropertyElement, error) { if len(types) > 0 { prop.Type = &thingDescription.TypeDeclaration{ StringArray: types, @@ -126,16 +163,7 @@ func PatchPropertyElement(prop thingDescription.PropertyElement, types []string, return thingDescription.PropertyElement{}, err } } - form := thingDescription.FormElementProperty{ - ContentType: StringToPtr(contentType), - Op: &thingDescription.FormElementPropertyOp{ - StringArray: opsStrs, - }, - } - if hrefUri != nil { - form.Href = *hrefUri - } - prop.Forms = []thingDescription.FormElementProperty{form} + prop.Forms = SetForms(hrefUri, ops, contentType) return prop, nil } diff --git a/bridge/resources/thingDescription/ocfResources.go b/bridge/resources/thingDescription/ocfResources.go index cdd1817e..ca704490 100644 --- a/bridge/resources/thingDescription/ocfResources.go +++ b/bridge/resources/thingDescription/ocfResources.go @@ -9,6 +9,7 @@ import ( schemaCredential "github.com/plgd-dev/device/v2/schema/credential" schemaDevice "github.com/plgd-dev/device/v2/schema/device" schemaMaintenance "github.com/plgd-dev/device/v2/schema/maintenance" + "github.com/plgd-dev/go-coap/v3/message" "github.com/web-of-things-open-source/thingdescription-go/thingDescription" ) @@ -39,12 +40,12 @@ func GetOCFResourcePropertyElement(resourceHref string) (thingDescription.Proper return prop, true } -func patchResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, resourceTypes []string, resourceHref, contentType string) (thingDescription.PropertyElement, error) { +func patchResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, resourceTypes []string, resourceHref string, contentType message.MediaType) (thingDescription.PropertyElement, error) { propOps := bridgeTD.GetPropertyElementOperations(pe) return bridgeTD.PatchPropertyElement(pe, resourceTypes, true, deviceID, resourceHref, propOps.ToSupportedOperations(), contentType) } -func PatchDeviceResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL, contentType string, deviceType string) (thingDescription.PropertyElement, error) { +func PatchDeviceResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL string, contentType message.MediaType, deviceType string) (thingDescription.PropertyElement, error) { var types []string if deviceType != "" { types = []string{schemaDevice.ResourceType, deviceType} @@ -52,14 +53,14 @@ func PatchDeviceResourcePropertyElement(pe thingDescription.PropertyElement, dev return patchResourcePropertyElement(pe, deviceID, types, baseURL+schemaDevice.ResourceURI, contentType) } -func PatchMaintenanceResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL, contentType string) (thingDescription.PropertyElement, error) { +func PatchMaintenanceResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL string, contentType message.MediaType) (thingDescription.PropertyElement, error) { return patchResourcePropertyElement(pe, deviceID, []string{schemaMaintenance.ResourceType}, baseURL+schemaMaintenance.ResourceURI, contentType) } -func PatchCloudResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL, contentType string) (thingDescription.PropertyElement, error) { +func PatchCloudResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL string, contentType message.MediaType) (thingDescription.PropertyElement, error) { return patchResourcePropertyElement(pe, deviceID, []string{schemaCloud.ResourceType}, baseURL+schemaCloud.ResourceURI, contentType) } -func PatchCredentialResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL, contentType string) (thingDescription.PropertyElement, error) { +func PatchCredentialResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL string, contentType message.MediaType) (thingDescription.PropertyElement, error) { return patchResourcePropertyElement(pe, deviceID, []string{schemaCredential.ResourceType}, baseURL+schemaCredential.ResourceURI, contentType) } diff --git a/bridge/resources/thingDescription/resource_test.go b/bridge/resources/thingDescription/resource_test.go index 82716b80..6b182d87 100644 --- a/bridge/resources/thingDescription/resource_test.go +++ b/bridge/resources/thingDescription/resource_test.go @@ -99,7 +99,7 @@ func getEndpoint(t *testing.T, c *client.Client, deviceID string) string { func getPatchedTD(td wotTD.ThingDescription, d service.Device, epURI string) wotTD.ThingDescription { return bridgeDeviceTD.PatchThingDescription(td, d, epURI, func(resourceHref string, resource bridgeDeviceTD.Resource) (wotTD.PropertyElement, bool) { - return bridgeTest.GetPropertyElement(td, d, epURI, resourceHref, resource, message.AppCBOR.String()) + return bridgeTest.GetPropertyElement(td, d, epURI, resourceHref, resource, message.AppCBOR) }) } diff --git a/bridge/test/test.go b/bridge/test/test.go index 341942fc..8edcf8c9 100644 --- a/bridge/test/test.go +++ b/bridge/test/test.go @@ -115,7 +115,7 @@ func makeDeviceConfig(id uuid.UUID, cloudEnabled bool, credentialEnabled bool) d return cfg } -func GetPropertyElement(td wotTD.ThingDescription, device bridgeDeviceTD.Device, endpoint string, resourceHref string, resource bridgeDeviceTD.Resource, contentType string) (wotTD.PropertyElement, bool) { +func GetPropertyElement(td wotTD.ThingDescription, device bridgeDeviceTD.Device, endpoint string, resourceHref string, resource bridgeDeviceTD.Resource, contentType message.MediaType) (wotTD.PropertyElement, bool) { propElement, ok := td.Properties[resourceHref] if !ok { return wotTD.PropertyElement{}, false @@ -149,7 +149,7 @@ func NewBridgedDeviceWithThingDescription(t *testing.T, s *service.Service, id s } newTD := bridgeDeviceTD.PatchThingDescription(*td, device, endpoint, func(resourceHref string, resource bridgeDeviceTD.Resource) (wotTD.PropertyElement, bool) { - return GetPropertyElement(*td, device, endpoint, resourceHref, resource, message.AppCBOR.String()) + return GetPropertyElement(*td, device, endpoint, resourceHref, resource, message.AppCBOR) }) return &newTD })) @@ -163,7 +163,7 @@ func getOCFResourcesProperties(deviceID uuid.UUID, baseURL string, cloudEnabled, if !ok { return nil, errors.New("device resource not found") } - deviceResource, err := thingDescriptionResource.PatchDeviceResourcePropertyElement(deviceResource, deviceID, baseURL, message.AppCBOR.String(), "") + deviceResource, err := thingDescriptionResource.PatchDeviceResourcePropertyElement(deviceResource, deviceID, baseURL, message.AppCBOR, "") if err != nil { return nil, err } @@ -174,7 +174,7 @@ func getOCFResourcesProperties(deviceID uuid.UUID, baseURL string, cloudEnabled, return nil, errors.New("maintenance resource not found") } properties[schemaMaintenance.ResourceURI] = maintenanceResource - maintenanceResource, err = thingDescriptionResource.PatchMaintenanceResourcePropertyElement(maintenanceResource, deviceID, baseURL, message.AppCBOR.String()) + maintenanceResource, err = thingDescriptionResource.PatchMaintenanceResourcePropertyElement(maintenanceResource, deviceID, baseURL, message.AppCBOR) if err != nil { return nil, err } @@ -185,7 +185,7 @@ func getOCFResourcesProperties(deviceID uuid.UUID, baseURL string, cloudEnabled, if !ok { return nil, errors.New("cloud resource not found") } - cloudResource, err = thingDescriptionResource.PatchCloudResourcePropertyElement(cloudResource, deviceID, baseURL, message.AppCBOR.String()) + cloudResource, err = thingDescriptionResource.PatchCloudResourcePropertyElement(cloudResource, deviceID, baseURL, message.AppCBOR) if err != nil { return nil, err } @@ -197,7 +197,7 @@ func getOCFResourcesProperties(deviceID uuid.UUID, baseURL string, cloudEnabled, if !ok { return nil, errors.New("credential resource not found") } - credentialResource, err = thingDescriptionResource.PatchCredentialResourcePropertyElement(credentialResource, deviceID, baseURL, message.AppCBOR.String()) + credentialResource, err = thingDescriptionResource.PatchCredentialResourcePropertyElement(credentialResource, deviceID, baseURL, message.AppCBOR) if err != nil { return nil, err } diff --git a/cmd/bridge-device/device/device.go b/cmd/bridge-device/device/device.go index 3d5c879f..e64519d3 100644 --- a/cmd/bridge-device/device/device.go +++ b/cmd/bridge-device/device/device.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" bridgeTD "github.com/plgd-dev/device/v2/bridge/device/thingDescription" + "github.com/plgd-dev/go-coap/v3/message" "github.com/web-of-things-open-source/thingdescription-go/thingDescription" ) @@ -41,7 +42,7 @@ func GetPropertyDescriptionForTestResource() thingDescription.PropertyElement { } } -func PatchTestResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, href, contentType string) (thingDescription.PropertyElement, error) { +func PatchTestResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, href string, contentType message.MediaType) (thingDescription.PropertyElement, error) { propOps := bridgeTD.GetPropertyElementOperations(pe) return bridgeTD.PatchPropertyElement(pe, []string{TestResourceType}, true, deviceID, href, propOps.ToSupportedOperations(), contentType) diff --git a/cmd/bridge-device/main.go b/cmd/bridge-device/main.go index fa2115d3..bb63d54c 100644 --- a/cmd/bridge-device/main.go +++ b/cmd/bridge-device/main.go @@ -97,7 +97,7 @@ func patchPropertyElement(td wotTD.ThingDescription, dev *device.Device, endpoin if !ok { return wotTD.PropertyElement{}, false } - propElement, err := thingDescription.PatchPropertyElement(propElement, resource.GetResourceTypes(), endpoint != "", dev.GetID(), resource.GetHref(), resource.SupportsOperations(), message.AppCBOR.String()) + propElement, err := thingDescription.PatchPropertyElement(propElement, resource.GetResourceTypes(), endpoint != "", dev.GetID(), resource.GetHref(), resource.SupportsOperations(), message.AppCBOR) return propElement, err == nil } diff --git a/go.mod b/go.mod index 396e2a8c..b99f7a5c 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/plgd-dev/kit/v2 v2.0.0-20211006190727-057b33161b90 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510090525-772cd4ad3459 + github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510130416-741fef736e1e go.uber.org/atomic v1.11.0 golang.org/x/sync v0.7.0 google.golang.org/grpc v1.63.2 diff --git a/go.sum b/go.sum index 898e1f20..08cc6127 100644 --- a/go.sum +++ b/go.sum @@ -139,8 +139,8 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFXQnxHGXy6E= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510090525-772cd4ad3459 h1:5JIsujthXmSrKC318Y6UJRyQ2yysV0cuen70fL9LW3U= -github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510090525-772cd4ad3459/go.mod h1:L/jWuWf+v7rmuFykpUP/runRXTnnA0QdGGgou8vzPrw= +github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510130416-741fef736e1e h1:blQyU8WqqyRcBmaAPLiU5cTg9BSQu04CJZ/ffEzgI1s= +github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510130416-741fef736e1e/go.mod h1:L/jWuWf+v7rmuFykpUP/runRXTnnA0QdGGgou8vzPrw= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From 774e3a74e801601948245ce7750d8e5fa99152e8 Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Fri, 10 May 2024 13:42:26 +0000 Subject: [PATCH 5/7] allow to custom form properties --- .../thingDescription/thingDescription.go | 68 +++++++++++++------ .../thingDescription/ocfResources.go | 20 +++--- bridge/resources/thingDescription/resource.go | 5 +- bridge/test/test.go | 16 +++-- cmd/bridge-device/config.yaml | 4 +- cmd/bridge-device/device/device.go | 6 +- cmd/bridge-device/main.go | 6 +- 7 files changed, 81 insertions(+), 44 deletions(-) diff --git a/bridge/device/thingDescription/thingDescription.go b/bridge/device/thingDescription/thingDescription.go index f770f185..b5b8f077 100644 --- a/bridge/device/thingDescription/thingDescription.go +++ b/bridge/device/thingDescription/thingDescription.go @@ -1,6 +1,7 @@ package thingDescription import ( + "errors" "net/http" "net/url" @@ -99,9 +100,20 @@ func (p PropertyElementOperations) ToSupportedOperations() resources.SupportedOp return ops | resources.SupportedOperationRead | resources.SupportedOperationWrite } -func createForm(hrefUri *url.URL, covMethod string, op thingDescription.StickyDescription, contentType message.MediaType) thingDescription.FormElementProperty { +type CreateFormFunc func(hrefUri *url.URL, op thingDescription.StickyDescription, contentType message.MediaType) (thingDescription.FormElementProperty, bool) + +func CreateCOAPForm(hrefUri *url.URL, op thingDescription.StickyDescription, contentType message.MediaType) (thingDescription.FormElementProperty, bool) { + methods := map[thingDescription.StickyDescription]string{ + thingDescription.Readproperty: http.MethodGet, + thingDescription.Writeproperty: http.MethodPost, + thingDescription.Observeproperty: http.MethodGet, + } + method, ok := methods[op] + if !ok { + return thingDescription.FormElementProperty{}, false + } additionalFields := map[string]interface{}{ - "cov:method": covMethod, + "cov:method": method, "cov:accept": float64(contentType), } ops := []string{string(op)} @@ -117,24 +129,46 @@ func createForm(hrefUri *url.URL, covMethod string, op thingDescription.StickyDe StringArray: ops, }, AdditionalFields: additionalFields, - } + }, true } -func SetForms(hrefUri *url.URL, ops resources.SupportedOperation, contentType message.MediaType) []thingDescription.FormElementProperty { +type CreateFormsFunc func(hrefUri *url.URL, ops resources.SupportedOperation, contentType message.MediaType) []thingDescription.FormElementProperty + +func CreateCOAPForms(hrefUri *url.URL, ops resources.SupportedOperation, contentType message.MediaType) []thingDescription.FormElementProperty { forms := make([]thingDescription.FormElementProperty, 0, 3) if ops.HasOperation(resources.SupportedOperationWrite) { - forms = append(forms, createForm(hrefUri, http.MethodPost, thingDescription.Writeproperty, contentType)) + form, ok := CreateCOAPForm(hrefUri, thingDescription.Writeproperty, contentType) + if ok { + forms = append(forms, form) + } } if ops.HasOperation(resources.SupportedOperationRead) { - forms = append(forms, createForm(hrefUri, http.MethodGet, thingDescription.Readproperty, contentType)) + form, ok := CreateCOAPForm(hrefUri, thingDescription.Readproperty, contentType) + if ok { + forms = append(forms, form) + } } if ops.HasOperation(resources.SupportedOperationObserve) { - forms = append(forms, createForm(hrefUri, http.MethodGet, thingDescription.Observeproperty, contentType)) + form, ok := CreateCOAPForm(hrefUri, thingDescription.Observeproperty, contentType) + if ok { + forms = append(forms, form) + } } return forms } -func PatchPropertyElement(prop thingDescription.PropertyElement, types []string, setForm bool, deviceID uuid.UUID, href string, ops resources.SupportedOperation, contentType message.MediaType) (thingDescription.PropertyElement, error) { +func GetPropertyHref(deviceID uuid.UUID, href string) (*url.URL, error) { + if len(href) == 0 { + return nil, errors.New("href is empty") + } + hrefStr := href + if deviceID != uuid.Nil { + hrefStr += "?di=" + deviceID.String() + } + return url.Parse(hrefStr) +} + +func PatchPropertyElement(prop thingDescription.PropertyElement, types []string, deviceID uuid.UUID, href string, ops resources.SupportedOperation, contentType message.MediaType, createForms CreateFormsFunc) (thingDescription.PropertyElement, error) { if len(types) > 0 { prop.Type = &thingDescription.TypeDeclaration{ StringArray: types, @@ -144,26 +178,18 @@ func PatchPropertyElement(prop thingDescription.PropertyElement, types []string, prop.Observable = BoolToPtr(propOps.Observable) prop.ReadOnly = BoolToPtr(propOps.ReadOnly) prop.WriteOnly = BoolToPtr(propOps.WriteOnly) - if !setForm { + if createForms == nil { return prop, nil } opsStrs := SupportedOperationToTDOperations(ops) if len(opsStrs) == 0 { return prop, nil } - var hrefUri *url.URL - if len(href) > 0 { - hrefStr := href - if deviceID != uuid.Nil { - hrefStr += "?di=" + deviceID.String() - } - var err error - hrefUri, err = url.Parse(hrefStr) - if err != nil { - return thingDescription.PropertyElement{}, err - } + hrefUri, err := GetPropertyHref(deviceID, href) + if err != nil { + return thingDescription.PropertyElement{}, err } - prop.Forms = SetForms(hrefUri, ops, contentType) + prop.Forms = createForms(hrefUri, ops, contentType) return prop, nil } diff --git a/bridge/resources/thingDescription/ocfResources.go b/bridge/resources/thingDescription/ocfResources.go index ca704490..9cdd7def 100644 --- a/bridge/resources/thingDescription/ocfResources.go +++ b/bridge/resources/thingDescription/ocfResources.go @@ -40,27 +40,27 @@ func GetOCFResourcePropertyElement(resourceHref string) (thingDescription.Proper return prop, true } -func patchResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, resourceTypes []string, resourceHref string, contentType message.MediaType) (thingDescription.PropertyElement, error) { +func patchResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, resourceTypes []string, resourceHref string, contentType message.MediaType, createForms bridgeTD.CreateFormsFunc) (thingDescription.PropertyElement, error) { propOps := bridgeTD.GetPropertyElementOperations(pe) - return bridgeTD.PatchPropertyElement(pe, resourceTypes, true, deviceID, resourceHref, propOps.ToSupportedOperations(), contentType) + return bridgeTD.PatchPropertyElement(pe, resourceTypes, deviceID, resourceHref, propOps.ToSupportedOperations(), contentType, createForms) } -func PatchDeviceResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL string, contentType message.MediaType, deviceType string) (thingDescription.PropertyElement, error) { +func PatchDeviceResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL string, contentType message.MediaType, deviceType string, createForms bridgeTD.CreateFormsFunc) (thingDescription.PropertyElement, error) { var types []string if deviceType != "" { types = []string{schemaDevice.ResourceType, deviceType} } - return patchResourcePropertyElement(pe, deviceID, types, baseURL+schemaDevice.ResourceURI, contentType) + return patchResourcePropertyElement(pe, deviceID, types, baseURL+schemaDevice.ResourceURI, contentType, createForms) } -func PatchMaintenanceResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL string, contentType message.MediaType) (thingDescription.PropertyElement, error) { - return patchResourcePropertyElement(pe, deviceID, []string{schemaMaintenance.ResourceType}, baseURL+schemaMaintenance.ResourceURI, contentType) +func PatchMaintenanceResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL string, contentType message.MediaType, createForms bridgeTD.CreateFormsFunc) (thingDescription.PropertyElement, error) { + return patchResourcePropertyElement(pe, deviceID, []string{schemaMaintenance.ResourceType}, baseURL+schemaMaintenance.ResourceURI, contentType, createForms) } -func PatchCloudResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL string, contentType message.MediaType) (thingDescription.PropertyElement, error) { - return patchResourcePropertyElement(pe, deviceID, []string{schemaCloud.ResourceType}, baseURL+schemaCloud.ResourceURI, contentType) +func PatchCloudResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL string, contentType message.MediaType, createForms bridgeTD.CreateFormsFunc) (thingDescription.PropertyElement, error) { + return patchResourcePropertyElement(pe, deviceID, []string{schemaCloud.ResourceType}, baseURL+schemaCloud.ResourceURI, contentType, createForms) } -func PatchCredentialResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL string, contentType message.MediaType) (thingDescription.PropertyElement, error) { - return patchResourcePropertyElement(pe, deviceID, []string{schemaCredential.ResourceType}, baseURL+schemaCredential.ResourceURI, contentType) +func PatchCredentialResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, baseURL string, contentType message.MediaType, createForms bridgeTD.CreateFormsFunc) (thingDescription.PropertyElement, error) { + return patchResourcePropertyElement(pe, deviceID, []string{schemaCredential.ResourceType}, baseURL+schemaCredential.ResourceURI, contentType, createForms) } diff --git a/bridge/resources/thingDescription/resource.go b/bridge/resources/thingDescription/resource.go index 02c8841f..1ce9155b 100644 --- a/bridge/resources/thingDescription/resource.go +++ b/bridge/resources/thingDescription/resource.go @@ -53,7 +53,10 @@ type Resource struct { } func (r *Resource) createMessage(request *net.Request, thingDescription *thingDescription.ThingDescription) (*pool.Message, error) { - dataJson, err := json.Marshal(thingDescription) + if thingDescription == nil { + return resources.CreateErrorResponse(request.Context(), codes.NotFound, errors.New("thing description not found")) + } + dataJson, err := thingDescription.MarshalJSON() if err != nil { return resources.CreateErrorResponse(request.Context(), codes.InternalServerError, err) } diff --git a/bridge/test/test.go b/bridge/test/test.go index 8edcf8c9..eed3e3e1 100644 --- a/bridge/test/test.go +++ b/bridge/test/test.go @@ -120,8 +120,12 @@ func GetPropertyElement(td wotTD.ThingDescription, device bridgeDeviceTD.Device, if !ok { return wotTD.PropertyElement{}, false } - propElement, err := bridgeDeviceTD.PatchPropertyElement(propElement, resource.GetResourceTypes(), endpoint != "", device.GetID(), resource.GetHref(), - resource.SupportsOperations(), contentType) + var f bridgeDeviceTD.CreateFormsFunc + if endpoint != "" { + f = bridgeDeviceTD.CreateCOAPForms + } + propElement, err := bridgeDeviceTD.PatchPropertyElement(propElement, resource.GetResourceTypes(), device.GetID(), resource.GetHref(), + resource.SupportsOperations(), contentType, f) return propElement, err == nil } @@ -163,7 +167,7 @@ func getOCFResourcesProperties(deviceID uuid.UUID, baseURL string, cloudEnabled, if !ok { return nil, errors.New("device resource not found") } - deviceResource, err := thingDescriptionResource.PatchDeviceResourcePropertyElement(deviceResource, deviceID, baseURL, message.AppCBOR, "") + deviceResource, err := thingDescriptionResource.PatchDeviceResourcePropertyElement(deviceResource, deviceID, baseURL, message.AppCBOR, "", bridgeDeviceTD.CreateCOAPForms) if err != nil { return nil, err } @@ -174,7 +178,7 @@ func getOCFResourcesProperties(deviceID uuid.UUID, baseURL string, cloudEnabled, return nil, errors.New("maintenance resource not found") } properties[schemaMaintenance.ResourceURI] = maintenanceResource - maintenanceResource, err = thingDescriptionResource.PatchMaintenanceResourcePropertyElement(maintenanceResource, deviceID, baseURL, message.AppCBOR) + maintenanceResource, err = thingDescriptionResource.PatchMaintenanceResourcePropertyElement(maintenanceResource, deviceID, baseURL, message.AppCBOR, bridgeDeviceTD.CreateCOAPForms) if err != nil { return nil, err } @@ -185,7 +189,7 @@ func getOCFResourcesProperties(deviceID uuid.UUID, baseURL string, cloudEnabled, if !ok { return nil, errors.New("cloud resource not found") } - cloudResource, err = thingDescriptionResource.PatchCloudResourcePropertyElement(cloudResource, deviceID, baseURL, message.AppCBOR) + cloudResource, err = thingDescriptionResource.PatchCloudResourcePropertyElement(cloudResource, deviceID, baseURL, message.AppCBOR, bridgeDeviceTD.CreateCOAPForms) if err != nil { return nil, err } @@ -197,7 +201,7 @@ func getOCFResourcesProperties(deviceID uuid.UUID, baseURL string, cloudEnabled, if !ok { return nil, errors.New("credential resource not found") } - credentialResource, err = thingDescriptionResource.PatchCredentialResourcePropertyElement(credentialResource, deviceID, baseURL, message.AppCBOR) + credentialResource, err = thingDescriptionResource.PatchCredentialResourcePropertyElement(credentialResource, deviceID, baseURL, message.AppCBOR, bridgeDeviceTD.CreateCOAPForms) if err != nil { return nil, err } diff --git a/cmd/bridge-device/config.yaml b/cmd/bridge-device/config.yaml index 098c4e00..846338b3 100644 --- a/cmd/bridge-device/config.yaml +++ b/cmd/bridge-device/config.yaml @@ -3,8 +3,8 @@ apis: id: 8f596b43-29c0-4147-8b40-e99268ab30f7 name: "my-bridge-example" externalAddresses: - - "127.0.0.1:15683" - - "[::1]:15683" + - "127.0.0.1:35683" + - "[::1]:35683" maxMessageSize: 2097152 log: level: "info" diff --git a/cmd/bridge-device/device/device.go b/cmd/bridge-device/device/device.go index e64519d3..e82b8cb9 100644 --- a/cmd/bridge-device/device/device.go +++ b/cmd/bridge-device/device/device.go @@ -42,10 +42,10 @@ func GetPropertyDescriptionForTestResource() thingDescription.PropertyElement { } } -func PatchTestResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, href string, contentType message.MediaType) (thingDescription.PropertyElement, error) { +func PatchTestResourcePropertyElement(pe thingDescription.PropertyElement, deviceID uuid.UUID, href string, contentType message.MediaType, createForms bridgeTD.CreateFormsFunc) (thingDescription.PropertyElement, error) { propOps := bridgeTD.GetPropertyElementOperations(pe) - return bridgeTD.PatchPropertyElement(pe, []string{TestResourceType}, true, deviceID, href, - propOps.ToSupportedOperations(), contentType) + return bridgeTD.PatchPropertyElement(pe, []string{TestResourceType}, deviceID, href, + propOps.ToSupportedOperations(), contentType, createForms) } func GetAdditionalProperties() map[string]interface{} { diff --git a/cmd/bridge-device/main.go b/cmd/bridge-device/main.go index bb63d54c..96b45a08 100644 --- a/cmd/bridge-device/main.go +++ b/cmd/bridge-device/main.go @@ -97,7 +97,11 @@ func patchPropertyElement(td wotTD.ThingDescription, dev *device.Device, endpoin if !ok { return wotTD.PropertyElement{}, false } - propElement, err := thingDescription.PatchPropertyElement(propElement, resource.GetResourceTypes(), endpoint != "", dev.GetID(), resource.GetHref(), resource.SupportsOperations(), message.AppCBOR) + var f thingDescription.CreateFormsFunc + if endpoint != "" { + f = thingDescription.CreateCOAPForms + } + propElement, err := thingDescription.PatchPropertyElement(propElement, resource.GetResourceTypes(), dev.GetID(), resource.GetHref(), resource.SupportsOperations(), message.AppCBOR, f) return propElement, err == nil } From f3ecdcfafda76fd5397a4cfc9cd04c90a688801e Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Tue, 14 May 2024 06:11:22 +0000 Subject: [PATCH 6/7] set device image to main --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6056eb6e..aa94ceec 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,7 @@ CERT_TOOL_SIGN_ALG ?= ECDSA-SHA256 # supported values: P256, P384, P521 CERT_TOOL_ELLIPTIC_CURVE ?= P256 DEVSIM_IMAGE ?= ghcr.io/iotivity/iotivity-lite/cloud-server-discovery-resource-observable-debug:vnext -# HUB_TEST_DEVICE_IMAGE = ghcr.io/plgd-dev/hub/test-cloud-server:main -HUB_TEST_DEVICE_IMAGE = ghcr.io/plgd-dev/hub/test-cloud-server:vnext-pr1274 +HUB_TEST_DEVICE_IMAGE = ghcr.io/plgd-dev/hub/test-cloud-server:main default: build From bbcdc05e5652aa9191918f65381bff20bc174bae Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Tue, 14 May 2024 06:13:49 +0000 Subject: [PATCH 7/7] upgrade thingdescription-go --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b99f7a5c..32b28f3d 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/plgd-dev/kit/v2 v2.0.0-20211006190727-057b33161b90 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510130416-741fef736e1e + github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240513190706-79b5f39190eb go.uber.org/atomic v1.11.0 golang.org/x/sync v0.7.0 google.golang.org/grpc v1.63.2 diff --git a/go.sum b/go.sum index 08cc6127..16d8f348 100644 --- a/go.sum +++ b/go.sum @@ -141,6 +141,8 @@ github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFX github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510130416-741fef736e1e h1:blQyU8WqqyRcBmaAPLiU5cTg9BSQu04CJZ/ffEzgI1s= github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510130416-741fef736e1e/go.mod h1:L/jWuWf+v7rmuFykpUP/runRXTnnA0QdGGgou8vzPrw= +github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240513190706-79b5f39190eb h1:6qIx0S8XOgCXkIMWew2dET+0Fcw2qTYZsDJ9LT8xQXg= +github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240513190706-79b5f39190eb/go.mod h1:L/jWuWf+v7rmuFykpUP/runRXTnnA0QdGGgou8vzPrw= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=