// Copyright 2021 The Pigweed Authors
//
// 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
//
//     https://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.
syntax = "proto3";

package pw.software_update;

import "google/protobuf/any.proto";
import "pw_protobuf_protos/common.proto";
import "pw_tokenizer_proto/options.proto";

option java_multiple_files = true;
option java_package = "dev.pigweed.pw_software_update";

message BundledUpdateState {
  enum Enum {
    UNKNOWN = 0;  // Not an expected state in the OTA system; only for proto.

    // Valid methods in this state: Start()
    //
    // Transition:
    //   Start() succeeds --> TRANSFERRING
    //   Start() fails    --> FINISHED
    INACTIVE = 1;

    // Valid methods in this state: GetStatus(), Abort().
    //
    // Transitions:
    //   Transfer completes --> TRANSFERRED
    //   Transfer fails     --> FINISHED
    //   Abort() called     --> ABORTING
    TRANSFERRING = 2;

    // Valid methods in this state: GetStatus(), Abort(), Verify().
    //
    // Transitions:
    //   Verify() called    --> VERIFYING
    //   Apply() called     --> VERIFYING
    //   Abort() called     --> ABORTING
    TRANSFERRED = 3;

    // Valid methods in this state: GetStatus(), Abort().
    //
    // Transitions:
    //   Verifying finished --> VERIFIED
    //   Verifying failed   --> FINISHED
    //   Abort() called     --> ABORTING
    VERIFYING = 4;

    // Valid methods in this state: GetStatus(), Abort(), Apply().
    //
    // Transitions:
    //   Apply() called --> APPLYING
    //   Abort() called --> ABORTING
    VERIFIED = 5;

    // Valid methods in this state: GetStatus().
    //
    // Transitions:
    //   Apply() finished --> FINISHED; may require persisting across a reboot.
    //   Apply() failed   --> FINISHED; with error set.
    APPLYING = 6;

    // Valid methods in this state: GetStatus().
    //
    // Transitions:
    //   Abort finishes --> FINISHED
    //   Abort fails    --> FINISHED
    ABORTING = 7;

    // Valid methods in this state: GetStatus(), Reset().
    //
    // Terminal state indicating a finished update; whether successful or
    // not. Additional termination information available in completion_state
    // and possibly note.
    //
    // Transitions:
    //   Reset() succeeds --> INACTIVE
    //   Reset() fails    --> FINISHED
    FINISHED = 8;
  }
}

message BundledUpdateResult {
  enum Enum {
    UNKNOWN = 0;
    SUCCESS = 1;
    UNKNOWN_ERROR = 2;
    ABORTED = 3;
    TRANSFER_FAILED = 4;
    VERIFY_FAILED = 5;
    APPLY_FAILED = 6;
  }
}

message BundledUpdateStatus {
  BundledUpdateState.Enum state = 1;

  optional BundledUpdateResult.Enum result = 2;

  // This is the percentage of estimated progress for the current update
  // state in hundreths of a percent. (e.g. 5.00% = 500u)
  optional uint32 current_state_progress_hundreth_percent = 3;

  // If present, the active transfer ID for the update.
  optional uint32 transfer_id = 4;

  // The name of the update bundle. Not present when in INACTIVE state. This is
  // useful for enabling resuming of transfers across reboots or disconnects,
  // without transferring an expensive manifest.
  optional string bundle_filename = 5;

  // Additional information related to the state may be provided here.
  // Examples: "Failed verifying: ml_model.bin", "Flash partition couldn't be
  // acquired and was busy", etc. Can provide more granular information than
  // just the completion result.
  optional bytes note = 6 [(tokenizer.format) = TOKENIZATION_OPTIONAL];

  // Custom application data.
  optional google.protobuf.Any extended_status = 7;
}

message StartRequest {
  // If present, the filename for the staged file. This should persist across
  // reboots, and will be returned from GetStatus() until either the update
  // applies or is aborted.
  optional string bundle_filename = 1;
}

// TODO: b/235273688 - Add documentation and details of the API contract.
service BundledUpdate {
  // TODO(keir): Offer GetCurrentManifest & GetStagedManifest() methods that
  // leverage pw_transfer to upload the manifest off the device, to enable host
  // logic.

  // Get current status of software update.
  rpc GetStatus(pw.protobuf.Empty) returns (BundledUpdateStatus);

  // Start a software update. Do any device-specific tasks needed to be ready
  // for update. Open pw_transfer channel used for staging bundle.
  // Returned status includes the transfer ID to use for bundle transfer.
  //
  // Precondition: Device update state must be INACTIVE.
  rpc Start(StartRequest) returns (BundledUpdateStatus);

  // Manually set the bundle status as transferred. It can be used to recover
  // a previously finished transfer or used when transfer happens on a side
  // channel without involvement of pw_transfer
  //
  // Precondition: Device update state must be INACTIVE or TRANSFERRING
  rpc SetTransferred(pw.protobuf.Empty) returns (BundledUpdateStatus);

  // Verify the bundle in the staging area. Returns immediately, but
  // verification runs asynchronously. Poll for completion with GetStatus().
  //
  // Precondition: Device update state must be TRANSFERRED.
  rpc Verify(pw.protobuf.Empty) returns (BundledUpdateStatus);

  // Apply the staged update. The update process will verify the staged bundle
  // or ensure that it was verified, apply the bundle, and in some cases reboot
  // the device. During this process, the device may be slow to respond.
  //
  // The RPC is async; callers must fetch status with GetStatus().
  //
  // Precondition: Device update state must be TRANSFERRED or VERIFIED.
  rpc Apply(pw.protobuf.Empty) returns (BundledUpdateStatus);

  // Abort any current software update in progress.
  // Precondition: Device update state must not be INACTIVE or FINISHED.
  rpc Abort(pw.protobuf.Empty) returns (BundledUpdateStatus);

  // Reset after a finished update. Device goes into INACTIVE state after.
  //
  // Precondition: Device update state must be FINISHED.
  rpc Reset(pw.protobuf.Empty) returns (BundledUpdateStatus);
}
