text: // Copyright 2019 The Fuchsia Authors
  //
  // Use of this source code is governed by a MIT-style
  // license that can be found in the LICENSE file or at
  // https://opensource.org/licenses/MIT
  
  #include <object/clock_dispatcher.h>
  
  #include "priv.h"
  
  namespace {
  constexpr uint64_t GetArgsVersion(uint64_t options) {
    return (options & ZX_CLOCK_ARGS_VERSION_MASK) >> ZX_CLOCK_ARGS_VERSION_SHIFT;
  }
  }  // namespace
  
  zx_status_t sys_clock_create(uint64_t options, user_in_ptr<const void> user_args,
                               user_out_handle* clock_out) {
    KernelHandle<ClockDispatcher> clock_handle;
    zx_clock_create_args_v1_t args{};
    zx_rights_t rights;
    zx_status_t result;
  
    // Extract the creation arguments based on the version signalled in options.
    switch (GetArgsVersion(options)) {
      // v0 implies "just use the defaults".  No args structure should have been
      // passed.  Just set our local v1 args structure to the default backstop
      // time of 0.
      case 0:
        if (user_args) {
          return ZX_ERR_INVALID_ARGS;
        }
        args.backstop_time = 0;
        break;
  
      // Extract the user args from the v1 structure.  They will be sanity checked
      // during the dispatcher's static Create
      case 1:
        result = user_args.reinterpret<const zx_clock_create_args_v1_t>().copy_from_user(&args);
        if (result != ZX_OK) {
          return result;
        }
        break;
  
      default:
        return ZX_ERR_INVALID_ARGS;
    }
  
    result = ClockDispatcher::Create(options, args, &clock_handle, &rights);
    if (result == ZX_OK) {
      result = clock_out->make(ktl::move(clock_handle), rights);
    }
  
    return result;
  }
  
  zx_status_t sys_clock_read(zx_handle_t clock_handle, user_out_ptr<zx_time_t> user_now) {
    auto up = ProcessDispatcher::GetCurrent();
    fbl::RefPtr<ClockDispatcher> clock;
    zx_status_t status =
        up->handle_table().GetDispatcherWithRights(clock_handle, ZX_RIGHT_READ, &clock);
    if (status != ZX_OK) {
      return status;
    }
  
    zx_time_t now{};
    status = clock->Read(&now);
    if (status != ZX_OK) {
      return status;
    }
  
    return user_now.copy_to_user(now);
  }
  
  zx_status_t sys_clock_get_details(zx_handle_t clock_handle, uint64_t options,
                                    user_out_ptr<void> user_details) {
    // Currently, the only version of the details structure defined is V1.  If the
    // user failed to provide a buffer, or signaled a different version of the
    // structure, then it is an error.
    zx_clock_details_v1_t details{};
    if ((options != ZX_CLOCK_ARGS_VERSION(1)) || !user_details) {
      return ZX_ERR_INVALID_ARGS;
    }
  
    auto up = ProcessDispatcher::GetCurrent();
    fbl::RefPtr<ClockDispatcher> clock;
    zx_status_t status =
        up->handle_table().GetDispatcherWithRights(clock_handle, ZX_RIGHT_READ, &clock);
    if (status != ZX_OK) {
      return status;
    }
  
    status = clock->GetDetails(&details);
    if (status != ZX_OK) {
      return status;
    }
  
    return user_details.reinterpret<zx_clock_details_v1>().copy_to_user(details);
  }
  
  zx_status_t sys_clock_update(zx_handle_t clock_handle, uint64_t options,
                               user_in_ptr<const void> user_args) {
    // Currently, there are only 2 versions of the update structure defined; V1
    // and V2.  If the user failed to provide a buffer, or signaled any other
    // version of the structure, then it is an error.
    if (!user_args) {
      return ZX_ERR_INVALID_ARGS;
    }
  
    union {
      zx_clock_update_args_v1_t v1;
      zx_clock_update_args_v2_t v2;
    } args;
  
    zx_status_t status;
    const uint64_t version = GetArgsVersion(options);
    int32_t rate_adjust;
    switch (version) {
      case 1:
        status = user_args.reinterpret<const zx_clock_update_args_v1_t>().copy_from_user(&args.v1);
        rate_adjust = args.v1.rate_adjust;
        break;
      case 2:
        status = user_args.reinterpret<const zx_clock_update_args_v2_t>().copy_from_user(&args.v2);
        rate_adjust = args.v2.rate_adjust;
        break;
      default:
        return ZX_ERR_INVALID_ARGS;
    }
  
    if (status != ZX_OK) {
      return status;
    }
  
    // Before going further, perform basic sanity checks of the update arguments.
    //
    // Only the defined options may be present in the request, and at least one of
    // them must be specified.
    options = options & ~ZX_CLOCK_ARGS_VERSION_MASK;
    if ((options & ~ZX_CLOCK_UPDATE_OPTIONS_ALL) || !(options & ZX_CLOCK_UPDATE_OPTIONS_ALL)) {
      return ZX_ERR_INVALID_ARGS;
    }
  
    // The PPM adjustment must be within the legal range.
    if ((options & ZX_CLOCK_UPDATE_OPTION_RATE_ADJUST_VALID) &&
        ((rate_adjust < ZX_CLOCK_UPDATE_MIN_RATE_ADJUST) ||
         (rate_adjust > ZX_CLOCK_UPDATE_MAX_RATE_ADJUST))) {
      return ZX_ERR_INVALID_ARGS;
    }
  
    auto up = ProcessDispatcher::GetCurrent();
    fbl::RefPtr<ClockDispatcher> clock;
    status = up->handle_table().GetDispatcherWithRights(clock_handle, ZX_RIGHT_WRITE, &clock);
    if (status != ZX_OK) {
      return status;
    }
  
    if (version == 1) {
      return clock->Update(options, args.v1);
    } else {
      return clock->Update(options, args.v2);
    }
  }
  