/*
 * Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#ifndef MODBUS_PRIVATE_H
#define MODBUS_PRIVATE_H

// clang-format off
#include <stdint.h>
#include <sys/time.h>
// clang-format on
#include <config.h>
#include <sys/types.h>

#include "modbus.h"

MODBUS_BEGIN_DECLS

/*
 * 这并非真正的最小长度（真正的最小长度是 RTU 模式下的"报告从站 ID"消息，仅 4 字节），
 * 但它是一个在 RTU 或 TCP 通信中读取多个值或写入单个值时非常方便使用的尺寸。
 * 取以下两者中的最大值：
 * - HEADER_LENGTH_TCP (7) + 功能码 (1) + 地址 (2) + 数量 (2)
 * - HEADER_LENGTH_RTU (1) + 功能码 (1) + 地址 (2) + 数量 (2) + CRC (2)
 */
#define _MIN_REQ_LENGTH 12

/*
 * 报告从站 ID 的功能码子码。
 * 该值用于 modbus_report_slave_id() 函数内部，以区分标准的"报告从站 ID"请求
 * 与其他功能码。
 */
#define _REPORT_SLAVE_ID 180

/*
 * Modbus 异常响应的固定长度。
 * 异常响应帧 = 从站地址(1) + 功能码(最高位置1, 1字节) + 异常码(1) + CRC(2)
 * 在 RTU 模式下最小为 5 字节，TCP 模式下加上 MBAP 头共 12 字节。
 */
#define _MODBUS_EXCEPTION_RSP_LENGTH 5

/* 超时时间（单位：微秒），默认 0.5 秒 */
#define _RESPONSE_TIMEOUT 500000
/*
 * 字节间超时时间（单位：微秒），默认 0.5 秒。
 * 在 RTU 模式下，如果两个字节之间的间隔超过此值，则认为一帧消息已结束。
 */
#define _BYTE_TIMEOUT     500000

/*
 * 后端类型枚举。
 * libmodbus 支持两种传输后端：RTU（串行链路）和 TCP（以太网）。
 * 该枚举用于标识当前 modbus_t 上下文使用的后端类型。
 */
typedef enum {
    _MODBUS_BACKEND_TYPE_RTU = 0,
    _MODBUS_BACKEND_TYPE_TCP
} modbus_backend_type_t;

/*
 *  Modbus 客户端/服务器通信模型示意：
 *
 *  ---------- 请求(Request)    指示(Indication) ----------
 *  | 客户端  | ---------------------->| 服务器  |
 *  ---------- 确认(Confirmation) 响应(Response) ----------
 *
 * msg_type_t 枚举用于标识当前消息处于通信模型的哪个阶段，
 * 主要用于内部函数 _modbus_receive_msg() 的消息类型参数。
 */
typedef enum {
    /* 服务器端的请求消息（即服务器收到来自客户端的请求） */
    MSG_INDICATION,
    /* 客户端的请求消息（即客户端等待来自服务器的响应） */
    MSG_CONFIRMATION
} msg_type_t;

/*
 * sft_t 结构体将从站地址(slave)、功能码(function)和事务ID(t_id)
 * 组合在一起，减少了函数调用的参数数量，从而优化执行速度（约提升 37%）。
 */
typedef struct _sft {
    int slave;
    int function;
    int t_id;
} sft_t;

/*
 * modbus_backend_t 是后端（RTU/TCP）的虚函数表结构体。
 * 每种传输后端（RTU、TCP）提供自己的一组函数指针实现，
 * 通过此结构体实现多态，使得上层代码无需关心底层传输细节。
 */
typedef struct _modbus_backend {
    unsigned int backend_type;               /* 后端类型标识 */
    unsigned int header_length;              /* 协议头长度（RTU:1, TCP:7） */
    unsigned int checksum_length;            /* 校验和长度（RTU:2, TCP:0） */
    unsigned int max_adu_length;             /* 最大 ADU 长度 */
    int (*set_slave)(modbus_t *ctx, int slave); /* 设置从站地址 */
    int (*build_request_basis)(              /* 构建请求帧基础部分 */
        modbus_t *ctx, int function, int addr, int nb, uint8_t *req);
    int (*build_response_basis)(sft_t *sft, uint8_t *rsp); /* 构建响应帧基础部分 */
    int (*get_response_tid)(const uint8_t *req); /* 提取事务ID（TCP使用） */
    int (*send_msg_pre)(uint8_t *req, int req_length); /* 发送前的预处理 */
    ssize_t (*send)(modbus_t *ctx, const uint8_t *req, int req_length); /* 发送数据 */
    int (*receive)(modbus_t *ctx, uint8_t *req); /* 接收数据（旧接口） */
    ssize_t (*recv)(modbus_t *ctx, uint8_t *rsp, int rsp_length); /* 接收数据（新接口） */
    int (*check_integrity)(modbus_t *ctx, uint8_t *msg, const int msg_length); /* 完整性校验 */
    int (*pre_check_confirmation)(modbus_t *ctx,    /* 收到响应后的初步校验 */
                                  const uint8_t *req,
                                  const uint8_t *rsp,
                                  int rsp_length);
    int (*connect)(modbus_t *ctx);                 /* 建立连接 */
    unsigned int (*is_connected)(modbus_t *ctx);   /* 检查连接状态 */
    void (*close)(modbus_t *ctx);                  /* 关闭连接 */
    int (*flush)(modbus_t *ctx);                   /* 清空缓冲区 */
    int (*select)(modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length); /* 多路复用等待 */
    void (*free)(modbus_t *ctx);                   /* 释放后端私有数据 */
} modbus_backend_t;

/*
 * struct _modbus — libmodbus 的核心上下文结构体。
 * 每个 Modbus 连接（无论是 RTU 还是 TCP）都由一个 modbus_t 实例表示，
 * 该结构体包含了连接的所有配置参数和运行时状态。
 */
struct _modbus {
    /* 从站地址 */
    int slave;
    /* 套接字或文件描述符 */
    int s;
    int debug;                     /* 调试标志 */
    int error_recovery;            /* 错误恢复模式 */
    int quirks;                    /* 兼容性标志位（用于处理非标准设备的行为差异） */
    struct timeval response_timeout;   /* 响应超时时间 */
    struct timeval byte_timeout;       /* 字节间超时时间 */
    struct timeval indication_timeout; /* 指示超时时间（服务器端等待请求） */
    const modbus_backend_t *backend;   /* 后端虚函数表指针 */
    void *backend_data;                /* 后端私有数据（如 modbus_rtu_t 或 modbus_tcp_t） */
};

void _modbus_init_common(modbus_t *ctx);
void _error_print(modbus_t *ctx, const char *context);
int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type);

/*
 * 如果目标平台未提供 strlcpy 函数，则声明本地的 strlcpy 实现。
 * strlcpy 是 BSD 风格的字符串拷贝函数，保证目标缓冲区以 '\0' 结尾，
 * 且不会发生缓冲区溢出。
 */
#ifndef HAVE_STRLCPY
size_t strlcpy(char *dest, const char *src, size_t dest_size);
#endif

MODBUS_END_DECLS

#endif /* MODBUS_PRIVATE_H */
