Skip to content

Commit

Permalink
Implement bridge-device functionality for onboarding non-OCF complian…
Browse files Browse the repository at this point in the history
…t devices

Implement a bridge-device service facilitating access to bridged devices, incorporating extensions beyond the OCF specification for local access. Notable distinctions include the use of a single opened UDP (potentially DTLS in the future) for Plgd bridged devices, contrasting with OCF bridged devices that utilize individual UDP/DTLS/TCP/TCP-TLS for both IPv4 and IPv6.

* Key Points:
  - Access devices by including the query parameter `di=<deviceID>` in the request.
  - The discovery resource (`/oic/res`) without the `di` parameter provides links for all devices. Each link contains a deviceID, specifying the required value for the `di` query parameter.
  - Accessing another device without the `di` parameter results in failure.

* Features:
  - Enables setting a Certificate Authority (CA) for cloud connection for each bridged device.
  - Implements a cloud connector for each bridged device with certificate validation for cloud connections.
  - Supports custom loggers.
  - Provides an API for configuring update/get/observe handlers for resources.
  - Supports both IPv4 and IPv6.

---------

Co-authored-by: Daniel Adam <daniel.adam1922@protonmail.com>
  • Loading branch information
jkralik and Danielius1922 authored Feb 3, 2024
1 parent e22e783 commit 1107a03
Show file tree
Hide file tree
Showing 150 changed files with 9,826 additions and 648 deletions.
4 changes: 3 additions & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
ignore:
- "client/app/app.go"
- "cmd/ocfclient/ocfclient.go"
- "cmd/bridge-device/*.go"
- "cmd/ocfclient/*.go"
- "**/main.go"
- "**/test/**/*.go"
- "**/*.pb.go"
- "**/*.pb.gw.go"
- "**/*_test.go"
Expand Down
6 changes: 0 additions & 6 deletions .github/workflows/build-publish-cfg.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ on:
description: Name of the container
type: string
required: true
directory:
description: Directory of service
type: string
required: true
file:
description: Dockerfile to build
type: string
Expand Down Expand Up @@ -102,7 +98,6 @@ jobs:
platforms: linux/amd64,linux/arm64
builder: ${{ steps.buildx.outputs.name }}
build-args: |
DIRECTORY=${{ inputs.directory }}
NAME=${{ inputs.name }}
COMMIT_DATE=${{ steps.build-args.outputs.commit_date }}
SHORT_COMMIT=${{ steps.build-args.outputs.short_commit }}
Expand All @@ -123,7 +118,6 @@ jobs:
platforms: linux/amd64,linux/arm64
builder: ${{ steps.buildx.outputs.name }}
build-args: |
DIRECTORY=${{ inputs.directory }}
NAME=${{ inputs.name }}
COMMIT_DATE=${{ steps.build-args.outputs.commit_date }}
SHORT_COMMIT=${{ steps.build-args.outputs.short_commit }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build-publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ jobs:
matrix:
include:
- name: test-cloud-server
directory: test/cloud-server
file: test/cloud-server/Dockerfile
- name: bridge-device
file: cmd/bridge-device/Dockerfile
uses: ./.github/workflows/build-publish-cfg.yaml
with:
name: ${{ matrix.name }}
directory: ${{ matrix.directory }}
file: ${{ matrix.file }}

21 changes: 15 additions & 6 deletions .github/workflows/test-with-cfg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ on:
name:
type: string
required: true
bridgetest-enabled:
type: boolean
required: false
default: false
coverage:
type: boolean
required: false
Expand All @@ -22,10 +26,10 @@ on:
type: string
required: false
default: ""
unittest-args:
type: string
unittest-enabled:
type: boolean
required: false
default: ""
default: false
jobs:
test:
runs-on: ubuntu-latest
Expand All @@ -51,11 +55,16 @@ jobs:
if: ${{ failure() }}
run: docker logs -t devsim-net-host && cat .tmp/devsim-net-host/0.log

# Run after integration tests, because they first clean-up the output directory
# Run after integration tests, because they always clean-up the output directory
- name: Run bridge tests
if: ${{ inputs.bridgetest-enabled }}
run: |
make test-bridge
- name: Run unit tests
if: ${{ inputs.coverage }}
if: ${{ inputs.unittest-enabled }}
run: |
make unit-test ${{ inputs.unittest-args }}
make unit-test
- name: Get output file name
if: ${{ inputs.coverage }}
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ jobs:
matrix:
include:
- name: cloud-server-debug
bridgetest-enabled: true
coverage: true
tag: ghcr.io/iotivity/iotivity-lite/cloud-server-debug:vnext
unittest-enabled: true
- name: cloud-server-debug-sha384
test-args: CERT_TOOL_SIGN_ALG=ECDSA-SHA384 CERT_TOOL_ELLIPTIC_CURVE=P384
coverage: true
Expand All @@ -37,9 +39,11 @@ jobs:
uses: ./.github/workflows/test-with-cfg.yml
with:
name: ${{ matrix.name }}
bridgetest-enabled: ${{ matrix.bridgetest-enabled || false }}
coverage: ${{ matrix.coverage }}
tag: ${{ matrix.tag }}
test-args: ${{ matrix.test-args }}
unittest-enabled: ${{ matrix.unittest-enabled || false }}

analysis:
name: SonarCloud and codecov analysis
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ vendor/
.vscode/
.tmp/
debug

cmd/bridge-device/bridge-device
cmd/ocfclient/ocfclient
test/bridge-device/bridge-device
8 changes: 6 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"-race",
"-v",
"-cover",
"-coverpkg=./...",
],
"go.testEnvVars": {
"ROOT_CA_CRT": "${workspaceFolder}/.tmp/pki_certs/cloudca.pem",
Expand All @@ -13,9 +14,12 @@
"MFG_KEY": "${workspaceFolder}/.tmp/pki_certs/mfgkey.pem",
"IDENTITY_CRT": "${workspaceFolder}/.tmp/pki_certs/identitycrt.pem",
"IDENTITY_KEY": "${workspaceFolder}/.tmp/pki_certs/identitykey.pem",
"COAP_CRT": "${workspaceFolder}/.tmp/pki_certs/coapcrt.pem",
"COAP_KEY": "${workspaceFolder}/.tmp/pki_certs/coapkey.pem",
"CLOUD_SID": "adebc667-1f2b-41e3-bf5c-6d6eabc68cc6",
},
"files.watcherExclude": {
"**/plgd-dev/device/v2/**": true
},
"**/plgd-dev/device/v2/**": true
},
"go.testTimeout": "600s",
}
131 changes: 117 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ SHELL = /bin/bash
SERVICE_NAME = cloud-server-test
VERSION_TAG = vnext-$(shell git rev-parse --short=7 --verify HEAD)
SIMULATOR_NAME_SUFFIX ?= $(shell hostname)
USER_ID := $(shell id -u)
GROUP_ID := $(shell id -g)
TMP_PATH = $(shell pwd)/.tmp
CERT_PATH = $(TMP_PATH)/pki_certs
CLOUD_SID ?= adebc667-1f2b-41e3-bf5c-6d6eabc68cc6
DEVSIM_NET_HOST_PATH = $(shell pwd)/.tmp/devsim-net-host
CERT_TOOL_IMAGE ?= ghcr.io/plgd-dev/hub/cert-tool:vnext
# supported values: ECDSA-SHA256, ECDSA-SHA384, ECDSA-SHA512
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:vnext-pr1202

default: build

Expand All @@ -28,21 +32,58 @@ build-testcontainer:

build: build-testcontainer

ROOT_CA_CRT = $(CERT_PATH)/cloudca.pem
ROOT_CA_KEY = $(CERT_PATH)/cloudcakey.pem
INTERMEDIATE_CA_CRT = $(CERT_PATH)/intermediatecacrt.pem
INTERMEDIATE_CA_KEY = $(CERT_PATH)/intermediatecakey.pem
MFG_CRT = $(CERT_PATH)/mfgcrt.pem
MFG_KEY = $(CERT_PATH)/mfgkey.pem
COAP_CRT = $(CERT_PATH)/coapcrt.pem
COAP_KEY = $(CERT_PATH)/coapkey.pem

certificates:
mkdir -p $(CERT_PATH)
chmod 0777 $(CERT_PATH)
docker pull $(CERT_TOOL_IMAGE)
docker run --rm -v $(CERT_PATH):/out $(CERT_TOOL_IMAGE) --outCert=/out/cloudca.pem --outKey=/out/cloudcakey.pem \
--cert.subject.cn="ca" --cert.signatureAlgorithm=$(CERT_TOOL_SIGN_ALG) --cert.ellipticCurve=$(CERT_TOOL_ELLIPTIC_CURVE) \
--cmd.generateRootCA
docker run --rm -v $(CERT_PATH):/out $(CERT_TOOL_IMAGE) --signerCert=/out/cloudca.pem --signerKey=/out/cloudcakey.pem \
--outCert=/out/intermediatecacrt.pem --outKey=/out/intermediatecakey.pem --cert.basicConstraints.maxPathLen=0 \
--cert.subject.cn="intermediateCA" --cert.signatureAlgorithm=$(CERT_TOOL_SIGN_ALG) \
--cert.ellipticCurve=$(CERT_TOOL_ELLIPTIC_CURVE) --cmd.generateIntermediateCA
docker run --rm -v $(CERT_PATH):/out $(CERT_TOOL_IMAGE) --signerCert=/out/intermediatecacrt.pem \
--signerKey=/out/intermediatecakey.pem --outCert=/out/mfgcrt.pem --outKey=/out/mfgkey.pem --cert.san.domain=localhost \
--cert.san.ip=127.0.0.1 --cert.subject.cn="mfg" --cert.signatureAlgorithm=$(CERT_TOOL_SIGN_ALG) \
--cert.ellipticCurve=$(CERT_TOOL_ELLIPTIC_CURVE) --cmd.generateCertificate

docker run \
--rm -v $(CERT_PATH):/out \
--user $(USER_ID):$(GROUP_ID) \
$(CERT_TOOL_IMAGE) \
--outCert=/out/cloudca.pem --outKey=/out/cloudcakey.pem \
--cert.subject.cn="ca" --cert.signatureAlgorithm=$(CERT_TOOL_SIGN_ALG) --cert.ellipticCurve=$(CERT_TOOL_ELLIPTIC_CURVE) \
--cmd.generateRootCA

docker run \
--rm -v $(CERT_PATH):/out \
--user $(USER_ID):$(GROUP_ID) \
$(CERT_TOOL_IMAGE) \
--signerCert=/out/cloudca.pem --signerKey=/out/cloudcakey.pem \
--outCert=/out/intermediatecacrt.pem --outKey=/out/intermediatecakey.pem \
--cert.basicConstraints.maxPathLen=0 --cert.subject.cn="intermediateCA" \
--cert.ellipticCurve=$(CERT_TOOL_ELLIPTIC_CURVE) --cert.signatureAlgorithm=$(CERT_TOOL_SIGN_ALG) \
--cmd.generateIntermediateCA

docker run \
--rm -v $(CERT_PATH):/out \
--user $(USER_ID):$(GROUP_ID) \
$(CERT_TOOL_IMAGE) \
--signerCert=/out/intermediatecacrt.pem --signerKey=/out/intermediatecakey.pem \
--outCert=/out/mfgcrt.pem --outKey=/out/mfgkey.pem --cert.san.domain=localhost \
--cert.san.ip=127.0.0.1 --cert.subject.cn="mfg" \
--cert.signatureAlgorithm=$(CERT_TOOL_SIGN_ALG) --cert.ellipticCurve=$(CERT_TOOL_ELLIPTIC_CURVE) \
--cmd.generateCertificate

docker run \
--rm -v $(CERT_PATH):/out \
--user $(USER_ID):$(GROUP_ID) \
${CERT_TOOL_IMAGE} \
--signerCert=/out/cloudca.pem --signerKey=/out/cloudcakey.pem \
--outCert=/out/coapcrt.pem --outKey=/out/coapkey.pem \
--cert.san.ip=127.0.0.1 --cert.san.domain=localhost \
--cert.signatureAlgorithm=$(CERT_TOOL_SIGN_ALG) --cert.ellipticCurve=$(CERT_TOOL_ELLIPTIC_CURVE) \
--cmd.generateCertificate --cert.subject.cn=uuid:$(CLOUD_SID)

sudo chown -R $(shell whoami) $(CERT_PATH)
chmod -R 0777 $(CERT_PATH)

Expand All @@ -64,8 +105,15 @@ env: clean certificates

unit-test: certificates
mkdir -p $(TMP_PATH)
go test -race -v ./schema/... -covermode=atomic -coverprofile=$(TMP_PATH)/schema.coverage.txt
ROOT_CA_CRT="$(CERT_PATH)/cloudca.pem" ROOT_CA_KEY="$(CERT_PATH)/cloudcakey.pem" go test -race -v ./pkg/... -covermode=atomic -coverprofile=$(TMP_PATH)/pkg.coverage.txt
ROOT_CA_CRT="$(ROOT_CA_CRT)" ROOT_CA_KEY="$(ROOT_CA_KEY)" \
MFG_CRT="$(MFG_CRT)" MFG_KEY="$(MFG_KEY)" \
INTERMEDIATE_CA_CRT="$(INTERMEDIATE_CA_CRT)" INTERMEDIATE_CA_KEY=$(INTERMEDIATE_CA_KEY) \
COAP_CRT="$(COAP_CRT)" COAP_KEY="$(COAP_KEY)" \
CLOUD_SID=$(CLOUD_SID) \
go test -race -parallel 1 -v ./bridge/... -coverpkg=./... -covermode=atomic -coverprofile=$(TMP_PATH)/bridge.unit.coverage.txt
go test -race -v ./schema/... -covermode=atomic -coverprofile=$(TMP_PATH)/schema.unit.coverage.txt
ROOT_CA_CRT="$(ROOT_CA_CRT)" ROOT_CA_KEY="$(ROOT_CA_KEY)" \
go test -race -v ./pkg/... -covermode=atomic -coverprofile=$(TMP_PATH)/pkg.unit.coverage.txt

test: env build-testcontainer
docker run \
Expand All @@ -75,8 +123,63 @@ test: env build-testcontainer
-v $(TMP_PATH):/tmp \
$(SERVICE_NAME):$(VERSION_TAG) -test.parallel 1 -test.v -test.coverprofile=/tmp/coverage.txt

test-bridge:
sudo rm -rf $(TMP_PATH)/data || :
mkdir -p $(TMP_PATH)/data
# pull image
docker pull $(HUB_TEST_DEVICE_IMAGE)
# prepare environment
docker run \
--rm \
--network=host \
--name hub-device-tests-environment \
--env PREPARE_ENV=true \
--env RUN=false \
--env COAP_GATEWAY_CLOUD_ID="$(CLOUD_SID)" \
-v $(TMP_PATH):/tmp \
-v $(TMP_PATH)/data:/data \
$(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
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 &

# run tests
docker run \
--rm \
--network=host \
--name hub-device-tests \
--env PREPARE_ENV=false \
--env RUN=true \
--env COAP_GATEWAY_CLOUD_ID="$(CLOUD_SID)" \
--env TEST_DEVICE_NAME="bridged-device-0" \
--env TEST_DEVICE_TYPE="bridged" \
--env GRPC_GATEWAY_TEST_DISABLED=1 \
--env IOTIVITY_LITE_TEST_RUN="(TestOffboard|TestOffboardWithoutSignIn|TestOffboardWithRepeat|TestRepublishAfterRefresh)$$" \
-v $(TMP_PATH):/tmp \
-v $(TMP_PATH)/data:/data \
$(HUB_TEST_DEVICE_IMAGE)

# stop device
pkill -TERM bridge-device || :
while pgrep -x bridge-device > /dev/null; do \
echo "waiting for bridge-device to exit"; \
sleep 1; \
done
go tool covdata textfmt -i=$(TMP_PATH)/bridge -o $(TMP_PATH)/bridge.coverage.txt

clean:
docker rm -f devsim-net-host || true
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
5 changes: 5 additions & 0 deletions bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# TODO

- Set RootCAs to the cloud connection
- Unit tests
- Set logger to package and propagate to package
35 changes: 35 additions & 0 deletions bridge/device/cloud/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/****************************************************************************
*
* Copyright (c) 2023 plgd.dev s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License"),
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
****************************************************************************/

package cloud

import (
"github.com/plgd-dev/device/v2/schema/cloud"
)

type Configuration struct {
ResourceTypes []string `yaml:"-" json:"rt"`
Interfaces []string `yaml:"-" json:"if"`
Name string `yaml:"-" json:"n"`
AuthorizationProvider string `yaml:"authorizationProvider" json:"apn"`
CloudID string `yaml:"cloudID" json:"sid"`
URL string `yaml:"cloudEndpoint" json:"cis"`
LastErrorCode int `yaml:"-" json:"clec"`
ProvisioningStatus cloud.ProvisioningStatus `yaml:"-" json:"cps"`
AuthorizationCode string `yaml:"-" json:"-"`
}
Loading

0 comments on commit 1107a03

Please sign in to comment.