Skip to content

Commit

Permalink
Add provisioning example using HTTP.
Browse files Browse the repository at this point in the history
  • Loading branch information
aka-mj committed Nov 3, 2022
1 parent 3bba9e4 commit 6ad61b0
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 7 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,15 @@ func main() {
}
```

[cmd/iothub-service](https://github.com/dangeroushobo/iothub/blob/master/cmd/iothub-service) and [cmd/iothub-device](https://github.com/dangeroushobo/iothub/blob/master/cmd/iothub-device) are reference implementations of almost all available features.
[cmd/iothub-service](https://github.com/dangeroushobo/iothub/blob/master/cmd/iothub-service) and [cmd/iothub-device](https://github.com/dangeroushobo/iothub/blob/master/cmd/iothub-device) are reference implementations of almost all available features.

## Provision Device

See `_examples/provision`.

## CLI

The project provides two command line utilities: `iothub-device` and `iothub-sevice`. First is for using it on IoT devices and the second manages and interacts with them.
The project provides two command line utilities: `iothub-device` and `iothub-sevice`. First is for using it on IoT devices and the second manages and interacts with them.

You can perform operations like publishing, subscribing to events and feedback, registering and invoking direct methods and so on straight from the command line.

Expand Down
200 changes: 200 additions & 0 deletions _examples/provision/provision.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// This is a working example to provision a device via the HTTP API
//
// Example run (expects you have a x509 certificate and private key for your device):
// go run provision.go -cert device-cert.pem -pkey private-key.pem -scopeID "0ne000XXXXX" -deviceID "mydevice-1"
//
package main

import (
"bytes"
"crypto/tls"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"net/http/httputil"
"time"

"go.uber.org/zap"
)

const (
DpsProvisioningUrl string = "https://global.azure-devices-provisioning.net"
DpsProvisioningApiVersion string = "api-version=2019-03-31"
)

type ProvClient struct {
c http.Client
scopeId string
deviceId string
log *zap.SugaredLogger
}

func NewProvClient(slog *zap.SugaredLogger, scopeID string, deviceId string, certFile string, privKeyFile string) (*ProvClient, error) {
cert, err := tls.LoadX509KeyPair(certFile, privKeyFile)
if err != nil {
return nil, err
}
p := new(ProvClient)
p.log = slog.Named("ProvClient")
p.deviceId = deviceId
p.scopeId = scopeID
p.c = http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
Renegotiation: tls.RenegotiateFreelyAsClient,
Certificates: []tls.Certificate{cert},
},
},
}
return p, nil
}

type ProvReplyJSON struct {
OpId string `json:"operationId"`
Status string `json:"status"`
}

type ProvStatusReply struct {
OpId string `json:"operationId"`
Status string `json:"status"`
RegState struct {
X509 struct {
EnrollmentGroupId string `json:"enrollmentGroupId"`
} `json:"x509"`
RegId string `json:"registrationId"`
CreatedDateTimeUtc string `json:"createdDateTimeUtc"`
AssignedHub string `json:"assignedHub"`
DeviceId string `json:"deviceId"`
Status string `json:"status"`
Substatus string `json:"substatus"`
LastUpdatedDateTimeUtc string `json:"lastUpdatedDateTimeUtc"`
} `json:"registrationState"`
}

func provisioningRequestBody(deviceId string) io.Reader {
type reqBody struct {
RegId string `json:"registrationId"`
}

// Create JSON for request body
reqJSON, _ := json.Marshal(reqBody{
RegId: deviceId,
})

return bytes.NewBuffer(reqJSON)
}

func (p *ProvClient) SendProvisioningRequest() (ProvReplyJSON, error) {
url := fmt.Sprintf("%s/%s/registrations/%s/register?%s", DpsProvisioningUrl, p.scopeId, p.deviceId, DpsProvisioningApiVersion)
p.log.Debugw("Provisioning Request", "url", url)

var reply ProvReplyJSON

// Create HTTP POST request
req, err := http.NewRequest("PUT", url, provisioningRequestBody(p.deviceId))
if err != nil {
return reply, err
}

req.Header.Add("Content-Type", "application/json")
req.Header.Add("Content-Encoding", "utf-8")

reqDump, _ := httputil.DumpRequest(req, true)
p.log.Infow("Request", "dump", string(reqDump))

// Send request
resp, err := p.c.Do(req)
if err != nil {
return reply, err
}
fmt.Printf("\n%+v\n", p.c)
p.log.Debugw("Reply", "error", err, "status", resp.Status)
if resp.StatusCode != 202 {
return reply, fmt.Errorf("Got non 202 return code")
}

// Read response
body, err := io.ReadAll(resp.Body)
if err != nil {
return reply, err
}

json.Unmarshal(body, &reply)

return reply, err
}

func (p *ProvClient) ProvisioningStatus(operationId string) (ProvStatusReply, error) {
url := fmt.Sprintf("%s/%s/registrations/%s/operations/%s?%s", DpsProvisioningUrl, p.scopeId, p.deviceId, operationId, DpsProvisioningApiVersion)
p.log.Debugw("Provisioning Status", "url", url)

var reply ProvStatusReply

// Create HTTP GET request
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return reply, err
}

req.Header.Add("Content-Type", "application/json")
req.Header.Add("Content-Encoding", "utf-8")

// Send request
resp, err := p.c.Do(req)
if err != nil {
return reply, err
}
if resp.StatusCode != 200 {
return reply, fmt.Errorf("Got non 200 return code")
}

// Read response
body, err := io.ReadAll(resp.Body)
if err != nil {
return reply, err
}

json.Unmarshal(body, &reply)

return reply, err
}

func main() {
var (
optScopeID string
optDeviceID string
optCert string
optPrivateKey string
)

flag.StringVar(&optScopeID, "scopeID", "", "Scope ID")
flag.StringVar(&optDeviceID, "deviceID", "", "Device ID in the cloud")
flag.StringVar(&optCert, "cert", "cert.pem", "x509 Certificate")
flag.StringVar(&optPrivateKey, "pkey", "private-key.pem", "Private Key")
flag.Parse()

slog := zap.NewExample().Sugar()
defer slog.Sync()

// Create client
client, _ := NewProvClient(slog, optScopeID, optDeviceID, optCert, optPrivateKey)
// Send provisioning request to DPS
rep, _ := client.SendProvisioningRequest()
// Check on provisioning status, give up after 10 seconds
var count uint
for count < 10 {
if status, err := client.ProvisioningStatus(rep.OpId); err != nil {
slog.Errorw("Failed to get provisioning status", "error", err)
} else {
slog.Infow("Provisioning Status", "reply", status)
if status.Status == "assigned" {
slog.Info("Success")
break
}
}
count++
time.Sleep(time.Second)
}
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ require (
github.com/gorilla/websocket v1.5.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.23.0 // indirect
golang.org/x/net v0.1.0 // indirect
golang.org/x/sync v0.1.0 // indirect
)
12 changes: 7 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ github.com/Azure/go-amqp v0.17.0 h1:HHXa3149nKrI0IZwyM7DRcRy5810t9ZICDutn4BYzj4=
github.com/Azure/go-amqp v0.17.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y=
github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4=
github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
Expand All @@ -25,10 +23,14 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20211020060615-d418f374d309 h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI=
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down

0 comments on commit 6ad61b0

Please sign in to comment.