295 lines
20 KiB
C
295 lines
20 KiB
C
/*
|
||
* TCP内部的子站连接协调:串行衔接Receiver、Link和SendManager,不执行socket I/O。
|
||
* 本头文件供TcpServer实现使用;业务通过IEC104_TcpServer.h接入。
|
||
* 下文的调用方指TcpServer;对象及借用存储由其管理,内部帧和记录由其转为业务事件。
|
||
*/
|
||
#ifndef IEC104_CONNECTION_H
|
||
#define IEC104_CONNECTION_H
|
||
|
||
#include "IEC104_Receiver.h"
|
||
#include "IEC104_SendManager.h"
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/* 按具体值分支;只有SEND_READY提供发送字节,FRAME_WRITTEN提供完成结果。 */
|
||
typedef enum {
|
||
IEC104_CONNECTION_FATAL = -2, /* 已冻结连接;停止I/O,读取记录后Close */
|
||
IEC104_CONNECTION_ERROR = -1, /* 参数、身份、调用阶段或时钟非法 */
|
||
IEC104_CONNECTION_EMPTY = 0, /* 无可查询项或无待发送业务 */
|
||
IEC104_CONNECTION_OK = 1, /* 本次操作成功;发送进度接口中表示尚未整帧写完 */
|
||
IEC104_CONNECTION_WAIT = 2, /* 接收请求未受理,或发送门控/容量阻塞 */
|
||
IEC104_CONNECTION_FRAME_READY = 3, /* I帧已完成链路处理,等待TcpServer复制为事件并释放 */
|
||
IEC104_CONNECTION_SEND_READY = 4, /* 当前帧的剩余字节可交给实际传输 */
|
||
IEC104_CONNECTION_FRAME_WRITTEN = 5 /* 整帧写完及对应完成处理均已提交 */
|
||
} IEC104_ConnectionResult_t;
|
||
|
||
typedef enum {
|
||
IEC104_CONNECTION_UNINITIALIZED = 0, /* 零值标记;仍须成功Init后才能调用运行接口 */
|
||
IEC104_CONNECTION_ACTIVE, /* TCP由TcpServer管理;不表示已经STARTED */
|
||
IEC104_CONNECTION_FAILED, /* 保留接收原文、发送记录及ASDU队列供回收 */
|
||
IEC104_CONNECTION_CLOSED /* 借用已解除;ASDU队列仍由TcpServer持有 */
|
||
} IEC104_ConnectionPhase_t;
|
||
|
||
typedef enum {
|
||
IEC104_CONNECTION_FAULT_NONE = 0, /* 尚未登记故障 */
|
||
IEC104_CONNECTION_FAULT_FRAME, /* 起始字节、APCI或ASDU最低长度非法 */
|
||
IEC104_CONNECTION_FAULT_PROTOCOL, /* 序号、ACK或接收阶段不符合协议 */
|
||
IEC104_CONNECTION_FAULT_INTERNAL, /* 模块状态或交接失败 */
|
||
IEC104_CONNECTION_FAULT_TRANSPORT, /* 发送失败或外部传输故障;正常有序EOF由TcpServer收尾 */
|
||
IEC104_CONNECTION_FAULT_LOCAL_SEND_TIMEOUT, /* 当前APDU排队及写入总期限到达 */
|
||
IEC104_CONNECTION_FAULT_I_ACK_TIMEOUT, /* 已完整发出的I帧等待对端ACK达到t1 */
|
||
IEC104_CONNECTION_FAULT_TESTFR_TIMEOUT, /* 本端TESTFR_ACT已发完,等待CON达到t1 */
|
||
IEC104_CONNECTION_FAULT_STOP_TIMEOUT /* STOP已受理,确认及对应CON发送未在总期限内完成 */
|
||
} IEC104_ConnectionFault_t;
|
||
|
||
/* 内部连接配置的类型别名;配置值由公共Types定义,与TcpServer使用同一布局。 */
|
||
typedef IEC104_ProtocolConfig_t IEC104_ConnectionConfig_t;
|
||
|
||
/* TcpServer借用原文至匹配的Release或Close;代次与receive_serial共同标识这次内部读取。 */
|
||
typedef struct {
|
||
IEC104_ReceivedFrame_t frame; /* 完整APDU/APCI;仅交付已受理的I帧 */
|
||
uint64_t receive_serial; /* 本连接内不复用,拒绝迟到的重复Release */
|
||
} IEC104_ConnectionReceivedFrame_t;
|
||
|
||
/* 无指针的观察快照;CLOSED后的链路字段是收尾时的历史值,不用这些字段自行组合收发门控。 */
|
||
typedef struct {
|
||
uint64_t connection_generation; /* 本快照所属连接代次 */
|
||
IEC104_ConnectionPhase_t phase; /* Connection运行、故障冻结或已关闭阶段 */
|
||
IEC104_ConnectionFault_t fault; /* 首次故障原因;正常关闭仍可为NONE */
|
||
IEC104_DataTransferState_t dt_state; /* 受理START/STOP后立即更新的数据传输状态 */
|
||
IEC104_DtProcedure_t dt_procedure; /* 子站仅IDLE或STOP_DRAINING,CON进度由发送缓冲维护 */
|
||
IEC104_TestfrState_t testfr_state; /* 本端主动探测的发送/等待CON进度 */
|
||
uint16_t send_record_count; /* 全部未消费I帧记录数,包括已经ACK的回执 */
|
||
uint8_t has_received_i_frame; /* TcpServer可Peek,复制为事件后显式Release */
|
||
uint8_t has_unhandled_frame; /* WAIT或故障时保留的尚未受理帧 */
|
||
} IEC104_ConnectionStatus_t;
|
||
|
||
/*
|
||
* 仅供TcpServer内部的启停通知适配;不是业务回调,不得重入修改接口,可只读查询状态。
|
||
* can_accept只读查询启停通知容量;notify在实际状态改变后、请求处理返回前复制通知。
|
||
* 两个函数必须同时设置或同时为空;为空时独立Connection不产生通知背压。
|
||
*/
|
||
typedef struct {
|
||
void *context;
|
||
int (*can_accept)(void *context);
|
||
int (*notify)(void *context, IEC104_DataTransferState_t state);
|
||
} IEC104_ConnectionTransferObserver_t;
|
||
|
||
/*
|
||
* TcpServer内部持有,完整布局用于静态存储;全部字段由接口维护,无malloc、无锁、无业务回调。
|
||
* Init后地址固定,不能复制/移动:Manager引用本结构内唯一的Link和SendBuffer。
|
||
* 同一连接的所有调用必须串行;不要再直接调用内含对象的修改接口。
|
||
* Link和SendBuffer的运行期修改统一经Manager;Connection只读Link,发送期限也通过Manager查询。
|
||
* ASDU队列独立持有以支持重连恢复;对象、数组及输入/输出参数存储不得重叠。
|
||
*/
|
||
typedef struct {
|
||
IEC104_Receiver_t receiver; /* 组装半帧并保留完整APDU;I帧由TcpServer复制为事件后释放 */
|
||
IEC104_LinkState_t link_state; /* 本连接唯一协议状态,收发共用序号和启停进度 */
|
||
IEC104_SendBuffer_t send_buffer; /* 当前I帧及控制FIFO,保存实际写入进度 */
|
||
IEC104_SendManager_t send_manager; /* 关联ASDU、发送帧和ACK记录,引用本对象内的Link/Buffer */
|
||
IEC104_ConnectionConfig_t config; /* Init时复制的配置,运行期间保持不变 */
|
||
IEC104_ConnectionTransferObserver_t transfer_observer; /* 内部通知容量及变化交接 */
|
||
IEC104_SendFrameId_t stop_con_frame_id; /* 当前STOP对应的最后一帧CON,拒绝旧完成结束新期限 */
|
||
uint64_t connection_generation; /* 非零且跨连接不复用,所有外部事件均须匹配 */
|
||
uint64_t last_now_ms; /* 串行事件的单调时刻,不接受倒退 */
|
||
uint64_t last_activity_ms; /* 完整合法APDU到达或本地整帧完成;反向启停CON除外 */
|
||
uint64_t received_at_ms; /* 当前完整接收帧到达时刻,WAIT不续期 */
|
||
uint64_t receive_serial; /* 每次成帧递增;耗尽时结束连接 */
|
||
uint64_t rx_ack_started_ms; /* 当前连续未确认接收区间的最早起点 */
|
||
uint64_t testfr_started_ms; /* 本次TESTFR_ACT完整写出的时刻 */
|
||
uint64_t stop_started_ms; /* 本轮STOP原成帧时刻;受理时启用计时,受理前WAIT也计入总期限 */
|
||
IEC104_ConnectionPhase_t phase; /* Connection生命周期,独立于Link的STARTED/STOPPED */
|
||
IEC104_ConnectionFault_t fault; /* 只登记首次故障;Close保留该值供查询 */
|
||
uint8_t rx_link_applied; /* I帧已交给Manager,不得因重复Peek/驱动再交一次 */
|
||
uint8_t rx_ack_timer_active; /* 0/1:存在尚未全部确认的接收区间,rx_ack_started_ms有效 */
|
||
uint8_t testfr_timer_active; /* 0/1:本次ACT已完整发送,testfr_started_ms有效 */
|
||
uint8_t stop_timer_active; /* 0/1:STOP已受理、未取消且CON尚未完成,stop_started_ms有效 */
|
||
uint8_t receive_batch_active; /* 有界接收批次内暂缓对端确认超时,下一次Tick结束批次 */
|
||
uint8_t receive_batch_pending; /* 固定输入快照尚未处理完,下一轮必须立即续收 */
|
||
} IEC104_Connection_t;
|
||
|
||
/* 写入Link默认值;本地发送和停止期限默认等于默认t1,HIGH严格优先。NULL无操作。 */
|
||
extern void IEC104_Connection_Config_Default(IEC104_ConnectionConfig_t *config);
|
||
|
||
/*
|
||
* 初始化新子站连接,初始STOPPED;generation非零且跨连接不复用,now_ms允许0。
|
||
* queue已Init/Rebind到该代次且无待ACK条目;可有待发NORMAL,成功时清空HIGH。
|
||
* control_capacity>=1,record_capacity为1..32767;config不可为NULL。
|
||
* 配置须满足0<t2_ms<t1_ms;不满足时返回ERROR,不改connection或队列。
|
||
* OK后对象地址及借用存储保持有效;失败不改connection或队列。
|
||
* Manager负责校验并在最终地址初始化Link/Buffer;成功后仅提交其余连接字段,不修补Manager内部指针。
|
||
* 仅用于首次使用或Close后的对象;重新Init前必须结束旧I/O和所有借用。
|
||
*/
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Init(
|
||
IEC104_Connection_t *connection, IEC104_AsduQueue_t *queue,
|
||
IEC104_SendBufferControlFrame_t *control_frames, uint16_t control_capacity,
|
||
IEC104_IFrameSendRecordStorage_t *records, uint16_t record_capacity,
|
||
const IEC104_ConnectionConfig_t *config, uint64_t connection_generation, uint64_t now_ms);
|
||
|
||
/*
|
||
* Init后、首次Feed前安装内部启停通知适配;observer复制入连接,context须持续有效。
|
||
* 参数非法或已经接收过APDU返回ERROR,不改变适配器;不执行通知或推进协议。
|
||
*/
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Set_Transfer_Observer(
|
||
IEC104_Connection_t *connection, uint64_t connection_generation,
|
||
const IEC104_ConnectionTransferObserver_t *observer);
|
||
|
||
/*
|
||
* 可选:开始一轮有界的非阻塞接收,暂缓I帧ACK和TESTFR_CON的t1判定。
|
||
* 用于先处理socket中已经到达的多帧输入,再检查对端是否仍欠确认;不重置期限。
|
||
* 同一轮必须调用Tick或Yield_Receive_Batch结束批次;重复Begin返回ERROR。
|
||
* 期间仍检查本地发送和STOP总期限,格式/协议错误仍立即失败;Close也会结束批次。
|
||
* 未调用本接口时,Feed仍即时检查全部期限;发送准备/回报仅检查本地发送和STOP期限。
|
||
*/
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Begin_Receive_Batch(
|
||
IEC104_Connection_t *connection, uint64_t connection_generation);
|
||
|
||
/* 仅供调度预算让出:结束当前轮次,但已到达的有限输入快照尚未全部处理。
|
||
* 不改任何期限起点,暂缓I_ACK/TESTFR判定;本地发送、STOP及控制推进仍有效。
|
||
* Get_Wait_Time返回0,调用方必须立即续收,且不得把后来到达的输入加入旧快照。
|
||
* 快照耗尽、实际容量背压或接收无进展时必须调用Tick恢复完整期限检查。
|
||
* 不能用于等待业务消费或socket可读;仅ACTIVE且已Begin时可调用。 */
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Yield_Receive_Batch(
|
||
IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t now_ms);
|
||
|
||
/*
|
||
* 最多消费至一帧末尾,consumed_length必填;非NULL时始终写入实际消费量。
|
||
* 半帧或已受理S/U返回OK;I帧返回FRAME_READY;控制槽或启停通知容量不足返回WAIT并保留当前帧。
|
||
* 后两者重入时消费0字节。保留未消费尾部;Release/驱动解除阻塞后再Feed尾部。
|
||
* 长度为0时data可为NULL,用于重试当前帧。格式/协议错误冻结连接并返回FATAL。
|
||
* 格式合法的STARTDT_CON/STOPDT_CON消费后忽略,不应答、不改变启停或确认期限、不刷新t3;
|
||
* 不占业务通知或控制发送槽,处理后仍照常检查期限并推进已有控制义务。
|
||
* 链路受理不代表ASDU业务执行成功;ASDU内容由业务解析。无效参数不消费输入。
|
||
*/
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Feed(
|
||
IEC104_Connection_t *connection, uint64_t connection_generation,
|
||
const uint8_t *data, uint32_t length, uint64_t now_ms, uint32_t *consumed_length);
|
||
|
||
/* FRAME_READY时写只读视图;无业务帧EMPTY,尚未受理WAIT。FAILED仍可读取已受理I帧。 */
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Peek_Received_Frame(
|
||
const IEC104_Connection_t *connection, uint64_t connection_generation,
|
||
IEC104_ConnectionReceivedFrame_t *frame);
|
||
|
||
/* TcpServer成功复制I_FRAME事件后释放匹配I帧,OK/ERROR;允许FAILED回收,不再次提交链路事件。 */
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Release_Received_Frame(
|
||
IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t receive_serial);
|
||
|
||
/*
|
||
* 提交当前连接的应答;仅接收批次之外、同代次ACTIVE连接可用,不执行socket I/O。
|
||
* 两条路径均由Manager按实际状态准入:仅STARTED+IDLE时接手。
|
||
* 其他阶段在容量检查前返回ERROR且不入HIGH;普通路径先Drive,再检查准入。
|
||
* SEND_READY已生成I帧并写head_info;OK已复制到HIGH;两者都不再借用输入原文。
|
||
* WAIT尚未接手,ERROR参数/阶段/容量非法,FATAL连接失败;准备后的故障不能直接重放原文。
|
||
* 到期且准入允许时仅入HIGH,由Process先读取待处理ACK再检查期限;未到期时统一推进控制流程。
|
||
* 受理新STOPDT_ACT时,HIGH待发应答作废且无逐条回执;重复STARTDT_ACT不清理HIGH。
|
||
* 服务级NORMAL直接进入TcpServer持有的ASDU队列;应答的HIGH顺序、窗口及公平配额由Manager保持。
|
||
*/
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Submit_Response(
|
||
IEC104_Connection_t *connection, uint64_t connection_generation,
|
||
const uint8_t *asdu, uint16_t asdu_length, uint64_t now_ms,
|
||
IEC104_SendBufferHeadInfo_t *head_info);
|
||
|
||
/* 不排队变体:仅SEND_READY接手;两级待发数据、窗口/缓冲背压均返回WAIT。
|
||
* 到期协议工作也先返回WAIT,由正常接收/Process处理后重试,不提前判远端超时。
|
||
* WAIT不保存原文、不编号;其余参数、会话及输出约定沿用Submit_Response。 */
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Try_Submit_Response(
|
||
IEC104_Connection_t *connection, uint64_t connection_generation,
|
||
const uint8_t *asdu, uint16_t asdu_length, uint64_t now_ms,
|
||
IEC104_SendBufferHeadInfo_t *head_info);
|
||
|
||
/*
|
||
* 推进接收重试、控制应答及本地发送/STOP期限,再取得剩余发送字节;仅SEND_READY写head_info。
|
||
* 不在此处报告I帧ACK或TESTFR_CON超时故障;调用方须按Get_Wait_Time及时接收并Tick。
|
||
* 任一确认t1已到时只取得已有帧;无已有帧返回WAIT,不选择HIGH/NORMAL、不分配N(S)。
|
||
* 有效确认解除所有已到期等待后重新准入,不重置原期限;接收批次内外均适用。
|
||
* 无当前帧时才选ASDU并编号。RX的WAIT不阻止已有帧发送,避免控制槽满时互等。
|
||
* pending_data借用至该帧完成或Close;一次只能有一个未回报的写操作,禁止APDU交错。
|
||
*/
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Prepare_Next_Send(
|
||
IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t now_ms,
|
||
IEC104_SendBufferHeadInfo_t *head_info);
|
||
|
||
/* 内部EOF收尾查询:仅在Prepare_Next_Send返回WAIT、转移就绪事件后使用。
|
||
* 记录容量已满、队首可回收,且启停/k窗口和上次准备时刻的确认t1均允许新I帧时,record_wait=1。
|
||
* 表示当前等待可通过转移RECORD解除;其他限制为0,不把还需新ACK/STARTDT的等待误判为可恢复。
|
||
* 只读,不推进协议或预留序号;仅OK写输出,ERROR/FATAL沿用连接查询约定。 */
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Get_Record_Wait(
|
||
const IEC104_Connection_t *connection, uint64_t connection_generation, uint8_t *record_wait);
|
||
|
||
/*
|
||
* 回报实际写入增量,0表示暂不可写;OK尚未写完,FRAME_WRITTEN才写completion。
|
||
* now_ms使用实际写入后的时刻;记录及计时照常更新,对端确认t1由后续接收/Tick判定。
|
||
* 本地发送和STOP期限仍检查;不得通过回报发送进度替代接收及Tick调度。
|
||
* 完成后自动重试保留接收帧/控制流程。FATAL可能发生在已登记写入之后,不能重放增量。
|
||
* 先回报本地已知完成,再Feed覆盖该帧的ACK;不允许用对端ACK推定写入结果。
|
||
*/
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Handle_Send_Progress(
|
||
IEC104_Connection_t *connection, IEC104_SendFrameId_t frame_id,
|
||
uint16_t bytes_written_now, uint64_t now_ms, IEC104_LocalSendCompletion_t *completion);
|
||
|
||
/* 最终写失败:匹配当前帧时提交失败并冻结,返回FATAL;错身份ERROR,不影响当前帧。 */
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Handle_Send_Failure(
|
||
IEC104_Connection_t *connection, IEC104_SendFrameId_t frame_id);
|
||
|
||
/*
|
||
* TcpServer在Poll中调用,结束可选接收批次并检查全部期限;会排队到期的S/TESTFR。
|
||
* 不创建系统定时器、不准备新I帧。固定输入快照耗尽或实际背压时必须调用Tick。
|
||
* 返回OK、接收FRAME_READY/WAIT或FATAL;参数、阶段、旧代次或时钟非法返回ERROR。
|
||
* 所有now_ms须为同一单调时钟且按调用顺序不倒退,时间戳0有效,是否计时由active标志区分。
|
||
* t2保留连续未确认区间的最早起点;只确认区间前缀时不续期,剩余部分可能提前确认。
|
||
*/
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Tick(
|
||
IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t now_ms);
|
||
|
||
/*
|
||
* 供TcpServer在接收批次结束且已推进协议后,查询下一次驱动前可等待的毫秒数。
|
||
* max_wait_ms是调用方本轮允许的上限,0表示不等待,UINT32_MAX仍是有限毫秒数。
|
||
* OK才写wait_ms,范围0..max_wait_ms;到期返回0,没有更早期限则返回max_wait_ms。
|
||
* 覆盖本地发送、I帧/TESTFR的t1、STOP总期限及当前可准备控制帧的t2/t3。
|
||
* 与超时检查、发送准入和控制推进共用即时期限视图,不另存或改写各帧的原始起点。
|
||
* S/TESTFR已在途或控制槽满时不反复被旧t2/t3唤醒,仍受发送及其他活动期限约束。
|
||
* 只读查询,不更新时刻、不续期、不排队或冻结连接;实际处理仍由Tick/收发驱动。
|
||
* 参数、代次、时钟、内部状态非法或接收批次未结束返回ERROR;FAILED返回FATAL。
|
||
*/
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Get_Wait_Time(
|
||
const IEC104_Connection_t *connection, uint64_t connection_generation, uint64_t now_ms,
|
||
uint32_t max_wait_ms, uint32_t *wait_ms);
|
||
|
||
/* 供TcpServer查询内部回执并转换为RECORD事件;允许ACTIVE或FAILED,OK才写record。 */
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Peek_Confirmed_Record(
|
||
const IEC104_Connection_t *connection, uint64_t connection_generation,
|
||
IEC104_IFrameSendRecord_t *record);
|
||
/* TcpServer成功复制RECORD事件后移除匹配队首回执;允许FAILED回收,不再次释放ASDU原文。 */
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Remove_Confirmed_Record(
|
||
IEC104_Connection_t *connection, IEC104_SendFrameId_t frame_id);
|
||
|
||
/* 按交接顺序读取记录,供故障回收;OK写副本,越界EMPTY。Close后不能再读取。 */
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Get_Record_By_Offset(
|
||
const IEC104_Connection_t *connection, uint64_t connection_generation,
|
||
uint16_t offset, IEC104_IFrameSendRecord_t *record);
|
||
|
||
/* 返回数值状态,允许FAILED/CLOSED;OK写status,参数、旧代次或模块状态非法时ERROR不改输出。 */
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Get_Status(
|
||
const IEC104_Connection_t *connection, uint64_t connection_generation,
|
||
IEC104_ConnectionStatus_t *status);
|
||
|
||
/* EOF或外部传输失败时冻结;ACTIVE/FAILED下同代次返回FATAL并保留首次原因。
|
||
* 旧代次、未初始化或CLOSED返回ERROR;不关闭socket、提交当前帧Complete或清空记录。 */
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Handle_Transport_Closed(
|
||
IEC104_Connection_t *connection, uint64_t connection_generation);
|
||
|
||
/*
|
||
* 先结束旧I/O、结束字节借用、读取所需记录,再Close;同代次重复Close返回OK。
|
||
* 清理Receiver/Manager/SendBuffer,保留外部ASDU队列;不执行socket关闭或隐式Rebind。
|
||
* 重连顺序:Close -> Queue.Rebind(新代次) -> Connection.Init -> 新STARTDT。
|
||
*/
|
||
extern IEC104_ConnectionResult_t IEC104_Connection_Close(
|
||
IEC104_Connection_t *connection, uint64_t connection_generation);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
#endif /* IEC104_CONNECTION_H */
|