github.com/arduino/arduino-cloud-cli@v0.0.0-20240517070944-e7a449561083/command/device/provision.go (about)

     1  // This file is part of arduino-cloud-cli.
     2  //
     3  // Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published
     7  // by the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    17  
    18  package device
    19  
    20  import (
    21  	"context"
    22  	"encoding/hex"
    23  	"fmt"
    24  	"path/filepath"
    25  	"strconv"
    26  	"time"
    27  
    28  	"github.com/arduino/arduino-cloud-cli/arduino"
    29  	"github.com/arduino/arduino-cloud-cli/internal/binary"
    30  	"github.com/arduino/arduino-cloud-cli/internal/serial"
    31  	"github.com/arduino/go-paths-helper"
    32  	iotclient "github.com/arduino/iot-client-go"
    33  	"github.com/sirupsen/logrus"
    34  )
    35  
    36  // downloadProvisioningFile downloads and returns the absolute path
    37  // of the provisioning binary corresponding to the passed fqbn.
    38  func downloadProvisioningFile(ctx context.Context, fqbn string) (string, error) {
    39  	index, err := binary.LoadIndex(ctx)
    40  	if err != nil {
    41  		return "", err
    42  	}
    43  	bin := index.FindProvisionBin(fqbn)
    44  	if bin == nil {
    45  		return "", fmt.Errorf("provisioning binary for board %s not found", fqbn)
    46  	}
    47  	bytes, err := binary.Download(ctx, bin)
    48  	if err != nil {
    49  		return "", fmt.Errorf("downloading provisioning binary: %w", err)
    50  	}
    51  
    52  	// Save provision binary always in the same temporary folder to
    53  	// avoid wasting user's storage.
    54  	filename := filepath.Base(bin.URL)
    55  	path := paths.TempDir().Join("cloud-cli").Join(filename)
    56  	path.Parent().MkdirAll()
    57  	if err = path.WriteFile(bytes); err != nil {
    58  		return "", fmt.Errorf("writing provisioning binary: %w", err)
    59  	}
    60  	p, err := path.Abs()
    61  	if err != nil {
    62  		return "", fmt.Errorf("cannot retrieve absolute path of downloaded binary: %w", err)
    63  	}
    64  	return p.String(), nil
    65  }
    66  
    67  type certificateCreator interface {
    68  	CertificateCreate(ctx context.Context, id, csr string) (*iotclient.ArduinoCompressedv2, error)
    69  }
    70  
    71  // provision is responsible for running the provisioning
    72  // procedures for boards with crypto-chip.
    73  type provision struct {
    74  	arduino.Commander
    75  	cert  certificateCreator
    76  	ser   *serial.Serial
    77  	board *board
    78  	id    string
    79  }
    80  
    81  // run provisioning procedure for boards with crypto-chip.
    82  func (p provision) run(ctx context.Context) error {
    83  	bin, err := downloadProvisioningFile(ctx, p.board.fqbn)
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	// Try to upload the provisioning sketch
    89  	logrus.Infof("%s\n", "Uploading provisioning sketch on the board")
    90  	if err = sleepCtx(ctx, 500*time.Millisecond); err != nil {
    91  		return err
    92  	}
    93  	errMsg := "Error while uploading the provisioning sketch"
    94  	err = retry(ctx, 5, time.Millisecond*1000, errMsg, func() error {
    95  		//serialutils.Reset(dev.port, true, nil)
    96  		return p.UploadBin(ctx, p.board.fqbn, bin, p.board.address, p.board.protocol)
    97  	})
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	// Try to connect to board through the serial port
   103  	logrus.Infof("%s\n", "Connecting to the board through serial port")
   104  	if err = sleepCtx(ctx, 1500*time.Millisecond); err != nil {
   105  		return err
   106  	}
   107  	p.ser = serial.NewSerial()
   108  	errMsg = "Error while connecting to the board"
   109  	err = retry(ctx, 5, time.Millisecond*1000, errMsg, func() error {
   110  		return p.ser.Connect(p.board.address)
   111  	})
   112  	if err != nil {
   113  		return err
   114  	}
   115  	defer p.ser.Close()
   116  	logrus.Infof("%s\n\n", "Connected to the board")
   117  
   118  	// Wait some time before using the serial port
   119  	if err = sleepCtx(ctx, 2000*time.Millisecond); err != nil {
   120  		return err
   121  	}
   122  
   123  	// Send configuration commands to the board
   124  	if err = p.configBoard(ctx); err != nil {
   125  		return err
   126  	}
   127  
   128  	logrus.Infof("%s\n\n", "Device provisioning successful")
   129  	return nil
   130  }
   131  
   132  func (p provision) configBoard(ctx context.Context) error {
   133  	logrus.Info("Receiving the certificate")
   134  	csr, err := p.ser.SendReceive(ctx, serial.CSR, []byte(p.id))
   135  	if err != nil {
   136  		return err
   137  	}
   138  	cert, err := p.cert.CertificateCreate(ctx, p.id, string(csr))
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	logrus.Info("Requesting begin storage")
   144  	if err = p.ser.Send(ctx, serial.BeginStorage, nil); err != nil {
   145  		return err
   146  	}
   147  
   148  	s := strconv.Itoa(cert.NotBefore.Year())
   149  	logrus.Info("Sending year: ", s)
   150  	if err = p.ser.Send(ctx, serial.SetYear, []byte(s)); err != nil {
   151  		return err
   152  	}
   153  
   154  	s = fmt.Sprintf("%02d", int(cert.NotBefore.Month()))
   155  	logrus.Info("Sending month: ", s)
   156  	if err = p.ser.Send(ctx, serial.SetMonth, []byte(s)); err != nil {
   157  		return err
   158  	}
   159  
   160  	s = fmt.Sprintf("%02d", cert.NotBefore.Day())
   161  	logrus.Info("Sending day: ", s)
   162  	if err = p.ser.Send(ctx, serial.SetDay, []byte(s)); err != nil {
   163  		return err
   164  	}
   165  
   166  	s = fmt.Sprintf("%02d", cert.NotBefore.Hour())
   167  	logrus.Info("Sending hour: ", s)
   168  	if err = p.ser.Send(ctx, serial.SetHour, []byte(s)); err != nil {
   169  		return err
   170  	}
   171  
   172  	s = strconv.Itoa(31)
   173  	logrus.Info("Sending validity: ", s)
   174  	if err = p.ser.Send(ctx, serial.SetValidity, []byte(s)); err != nil {
   175  		return err
   176  	}
   177  
   178  	logrus.Info("Sending certificate serial")
   179  	b, err := hex.DecodeString(cert.Serial)
   180  	if err != nil {
   181  		return fmt.Errorf("decoding certificate serial: %w", err)
   182  	}
   183  	if err = p.ser.Send(ctx, serial.SetCertSerial, b); err != nil {
   184  		return err
   185  	}
   186  
   187  	logrus.Info("Sending certificate authority key")
   188  	b, err = hex.DecodeString(cert.AuthorityKeyIdentifier)
   189  	if err != nil {
   190  		return fmt.Errorf("decoding certificate authority key id: %w", err)
   191  	}
   192  	if err = p.ser.Send(ctx, serial.SetAuthKey, b); err != nil {
   193  		return err
   194  	}
   195  
   196  	logrus.Info("Sending certificate signature")
   197  	b, err = hex.DecodeString(cert.SignatureAsn1X + cert.SignatureAsn1Y)
   198  	if err != nil {
   199  		err = fmt.Errorf("decoding certificate signature: %w", err)
   200  		return err
   201  	}
   202  	if err = p.ser.Send(ctx, serial.SetSignature, b); err != nil {
   203  		return err
   204  	}
   205  
   206  	if err := sleepCtx(ctx, 1*time.Second); err != nil {
   207  		return err
   208  	}
   209  
   210  	logrus.Info("Requesting end storage")
   211  	if err = p.ser.Send(ctx, serial.EndStorage, nil); err != nil {
   212  		return err
   213  	}
   214  
   215  	if err := sleepCtx(ctx, 2*time.Second); err != nil {
   216  		return err
   217  	}
   218  
   219  	logrus.Info("Requesting certificate reconstruction")
   220  	if err = p.ser.Send(ctx, serial.ReconstructCert, nil); err != nil {
   221  		return err
   222  	}
   223  
   224  	return nil
   225  }
   226  
   227  func retry(ctx context.Context, tries int, sleep time.Duration, errMsg string, fun func() error) error {
   228  	if err := ctx.Err(); err != nil {
   229  		return err
   230  	}
   231  
   232  	var err error
   233  	for n := 0; n < tries; n++ {
   234  		err = fun()
   235  		if err == nil {
   236  			break
   237  		}
   238  		logrus.Warningf("%s: %s: %s", errMsg, err.Error(), "\nTrying again...")
   239  		if err := sleepCtx(ctx, sleep); err != nil {
   240  			return err
   241  		}
   242  	}
   243  	return err
   244  }
   245  
   246  func sleepCtx(ctx context.Context, tm time.Duration) error {
   247  	select {
   248  	case <-ctx.Done():
   249  		return ctx.Err()
   250  	case <-time.After(tm):
   251  		return nil
   252  	}
   253  }