/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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.
 */
#include "common/commands/wifi_relay/cmd.h"

namespace cvd {

Cmd::Cmd() : msg_(nlmsg_alloc()) {}

Cmd::Cmd(nlmsghdr* h) : msg_(nlmsg_convert(h)) {}

Cmd::Cmd(nl_msg* h) {
  nlmsg_get(h);
  msg_ = h;
}

Cmd::~Cmd() {
  for (auto& msg : responses_) {
    nlmsg_free(msg);
  }
  nlmsg_free(msg_);
}

bool Cmd::OnResponse(nl_msg* msg) {
  // nlmsg_get increases refcount on msg, but does not return the msg
  // so we can't exactly use it as an argument to unique_ptr.
  nlmsg_get(msg);
  responses_.emplace_back(msg);
  auto hdr = nlmsg_hdr(msg);

  // Kernel documentation seems to be a bit misleading on this topic saying:
  //
  //     In multipart messages (multiple nlmsghdr headers with associated
  //     payload in one byte stream) the first and all following headers have
  //     the NLM_F_MULTI flag set, except for the last header which has the type
  //     NLMSG_DONE.
  //
  // In theory, that would make processing multi-part messages simple, but in
  // practice this does not seem to be true. Specifying exit criteria solely on
  // NLM_F_MULTI flag setting will block some, if not all calls that dump
  // NL80211 wifi interfaces for example.
  if (!(hdr->nlmsg_flags & NLM_F_MULTI) || (hdr->nlmsg_type == NLMSG_DONE) ||
      (hdr->nlmsg_type == NLMSG_ERROR)) {
    std::lock_guard<std::mutex> lock(ready_mutex_);
    ready_signal_.notify_all();
    return true;
  }

  return false;
}

const std::vector<nl_msg*> Cmd::Responses() const {
  WaitComplete();
  return responses_;
}

void Cmd::WaitComplete() const {
  std::unique_lock<std::mutex> lock(ready_mutex_);
  ready_signal_.wait(lock, [this]() { return responses_.size() > 0; });
}

}  // namespace cvd