/*
 * Copyright 2024 Google LLC
 *
 * 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 <stdint.h>
#include <string.h>

#include "console/pulse_llc.h"
#include "console/pulse_protocol_impl.h"

#include "console/pulse_internal.h"
#include "util/attributes.h"
#include "util/math.h"

#define LLC_INMSG_LINK_ESTABLISHMENT_REQUEST (1)
#define LLC_INMSG_LINK_CLOSE_REQUEST (3)
#define LLC_INMSG_ECHO_REQUEST (5)
#define LLC_INMSG_CHANGE_BAUD (7)

#define LLC_OUTMSG_LINK_OPENED (2)
#define LLC_OUTMSG_LINK_CLOSED (4)
#define LLC_OUTMSG_ECHO_REPLY (6)
#define LLC_OUTMSG_INVALID_LLC_MESSAGE (128)
#define LLC_OUTMSG_UNKNOWN_PROTOCOL_NUMBER (129)


static void prv_bad_packet_response(uint8_t type, uint8_t bad_id, void *body,
                                    size_t body_length);
static void prv_handle_change_baud(void *body, size_t body_length);


void pulse_llc_handler(void *packet, size_t length) {
  if (!length) {
    // Message too small; doesn't contain a type field.
    uint8_t *message = pulse_best_effort_send_begin(PULSE_PROTOCOL_LLC);
    *message = LLC_OUTMSG_INVALID_LLC_MESSAGE;
    pulse_best_effort_send(message, sizeof(uint8_t));
    return;
  }

  uint8_t type = *(uint8_t*)packet;
  switch (type) {
    case LLC_INMSG_LINK_ESTABLISHMENT_REQUEST:
      pulse_llc_send_link_opened_msg();
      return;
    case LLC_INMSG_LINK_CLOSE_REQUEST:
      pulse_end();
      return;
    case LLC_INMSG_ECHO_REQUEST:
      *(uint8_t*)packet = LLC_OUTMSG_ECHO_REPLY;

      uint8_t *message = pulse_best_effort_send_begin(PULSE_PROTOCOL_LLC);
      memcpy(message, packet, length);

      pulse_best_effort_send(message, length);
      return;
    case LLC_INMSG_CHANGE_BAUD:
      prv_handle_change_baud((char*)packet + 1, length - 1);
      return;
    default:
      prv_bad_packet_response(LLC_OUTMSG_INVALID_LLC_MESSAGE, type,
                              (char*)packet + 1, length - 1);
      return;
  }
}

void pulse_llc_link_state_handler(PulseLinkState link_state) {
}

void pulse_llc_send_link_opened_msg(void) {
  typedef struct PACKED Response {
    uint8_t type;
    uint8_t pulse_version;
    uint16_t mtu;
    uint16_t mru;
    uint8_t timeout;
  } Response;

  Response *response = pulse_best_effort_send_begin(PULSE_PROTOCOL_LLC);
  *response = (Response) {
    .type = LLC_OUTMSG_LINK_OPENED,
    .pulse_version = 1,
    .mtu = PULSE_MAX_SEND_SIZE + PULSE_MIN_FRAME_LENGTH,
    .mru = PULSE_MAX_RECEIVE_UNIT,
    .timeout = PULSE_KEEPALIVE_TIMEOUT_DECISECONDS
  };

  pulse_best_effort_send(response, sizeof(Response));
}

void pulse_llc_send_link_closed_msg(void) {
  uint8_t *link_close_response = pulse_best_effort_send_begin(
      PULSE_PROTOCOL_LLC);
  *link_close_response = LLC_OUTMSG_LINK_CLOSED;

  pulse_best_effort_send(link_close_response, sizeof(uint8_t));
}

static void prv_bad_packet_response(uint8_t type, uint8_t bad_id, void *body,
                                    size_t body_length) {
  typedef struct PACKED Response {
    uint8_t type;
    uint8_t bad_identifier;
    char body[8];
  } Response;

  Response *response = pulse_best_effort_send_begin(PULSE_PROTOCOL_LLC);
  *response = (Response) {
    .type = type,
    .bad_identifier = bad_id
  };

  body_length = MIN(sizeof(response->body), body_length);
  if (body_length) {
    memcpy(response->body, body, body_length);
  }

  pulse_best_effort_send(response, 2 + body_length);
}

void pulse_llc_unknown_protocol_handler(uint8_t protocol, void *packet,
                                        size_t length) {
  prv_bad_packet_response(LLC_OUTMSG_UNKNOWN_PROTOCOL_NUMBER, protocol, packet,
                          length);
}

void prv_handle_change_baud(void *body, size_t body_length) {
  if (body_length != sizeof(uint32_t)) {
    // Don't send a response; the client will have already changed its receiver
    // baud rate.
    return;
  }
  uint32_t *new_baud = body;
  pulse_change_baud_rate(*new_baud);
}
