Commit 1ea9317d authored by captainwong's avatar captainwong

update

parent e9111ac9
......@@ -451,6 +451,27 @@ AdemcoParseResult ademcoParseDataSegment(const uint8_t* packet, int packet_len,
return RESULT_ERROR;
}
int ademcoXDataConvert(AdemcoXDataSegment* xdata, AdemcoXDataLengthFormat xlf)
{
if (!xdata) { return ADEMCO_ERR; }
if (xdata->lenghFormat == xlf) { return ADEMCO_OK; }
int len = ademcoXDataGetValidContentLen(xdata);
uint8_t raw[ADEMCO_PACKET_XDATA_MAX_LEN];
if (xlf == TWO_HEX) { // convert from TWO_HEX to FOUR_DECIMAL
if (len + 6 > ADEMCO_PACKET_XDATA_MAX_LEN) {
return ADEMCO_ERR;
}
memcpy(raw, ademcoXDataGetValidContentAddr(xdata), len);
ademcoMakeXData(xdata, FOUR_DECIMAL, AdemcoXDataTransform_as_is, raw, len);
} else {
memcpy(raw, ademcoXDataGetValidContentAddr(xdata), len);
ademcoMakeXData(xdata, TWO_HEX, AdemcoXDataTransform_as_is, raw, len);
}
return ADEMCO_OK;
}
int ademcoDataSegmentToCongwinFe100(uint8_t* fe100, const AdemcoDataSegment* dataSegment)
{
if (dataSegment->raw_len == ADEMCO_PACKET_DATA_SEGMENT_EMPTY_LEN) {
......@@ -547,6 +568,15 @@ int ademcoXDataGetValidContentLen(const AdemcoXDataSegment* xdata)
}
}
int ademcoXDataCopy(AdemcoXDataSegment* dst, const AdemcoXDataSegment* src)
{
if (dst && src && src->raw_len > 0) {
memcpy(dst, src, sizeof(AdemcoXDataSegment));
return src->raw_len;
}
return 0;
}
int ademcoMakeXData(AdemcoXDataSegment* xdata, AdemcoXDataLengthFormat xlf, AdemcoXDataTransform xtr, const uint8_t* content, int len)
{
uint8_t transformed[ADEMCO_PACKET_XDATA_MAX_LEN];
......@@ -700,7 +730,7 @@ int ademcoMakeNakPacket(uint8_t* buff, int len, uint16_t seq, const char* acct,
}
int ademcoMakeHbPacket(uint8_t* dst_buff, int len, uint16_t seq, const char* acct, AdemcoId ademcoId,
AdemcoEvent ademcoEvent, AdemcoGG gg, AdemcoZone zone, AdemcoXDataSegment* xdata)
AdemcoEvent ademcoEvent, AdemcoGG gg, AdemcoZone zone, const AdemcoXDataSegment* xdata)
{
char buff[ADEMCO_PACKET_MAX_LEN];
char* p = buff;
......@@ -754,12 +784,16 @@ int ademcoMakeHbPacket(uint8_t* dst_buff, int len, uint16_t seq, const char* acc
static void copyAcct2AdemcoPacket(AdemcoPacket* pkt, const char* acct)
{
if (acct) {
int len = strlen(acct);
if (len > ADEMCO_PACKET_ACCT_MAX_LEN) {
len = ADEMCO_PACKET_ACCT_MAX_LEN;
}
memcpy(pkt->acct, acct, len);
pkt->acct[len] = '\0';
} else {
pkt->acct[0] = '\0';
}
}
int ademcoMakeNullPacket2(AdemcoPacket* pkt, uint16_t seq, const char* acct, AdemcoId ademcoId)
......@@ -787,7 +821,7 @@ int ademcoMakeNakPacket2(AdemcoPacket* pkt, uint16_t seq, const char* acct, Adem
}
int ademcoMakeHbPacket2(AdemcoPacket* pkt, uint16_t seq, const char* acct, AdemcoId ademcoId,
AdemcoEvent ademcoEvent, AdemcoGG gg, AdemcoZone zone, AdemcoXDataSegment* xdata)
AdemcoEvent ademcoEvent, AdemcoGG gg, AdemcoZone zone, const AdemcoXDataSegment* xdata)
{
pkt->seq = seq;
copyAcct2AdemcoPacket(pkt, acct);
......
......@@ -348,8 +348,10 @@ typedef struct AdemcoPacket {
}AdemcoPacket;
// Functions
//////////////////////// Functions ////////////////////////
void ademcoPrint(const uint8_t* buff, int len);
//////////////////////// AdemcoEvent functions
//! 是否主机状态事件
ADEMCO_EXPORT_SYMBOL int ademcoIsMachineStatusEvent(AdemcoEvent ademcoEvent);
//! 是否主机类型事件
......@@ -363,11 +365,16 @@ ADEMCO_EXPORT_SYMBOL AdemcoEvent ademcoGetExceptionEventByResumeEvent(AdemcoEven
ADEMCO_EXPORT_SYMBOL const char* ademcoEventToString(AdemcoEvent ademcoEvent);
ADEMCO_EXPORT_SYMBOL const char* ademcoEventToStringChinese(AdemcoEvent ademcoEvent);
//////////////////////// AdemcoDataSegment functions ////////////////////////
ADEMCO_EXPORT_SYMBOL int ademcoAppendDataSegment(uint8_t* packet, AdemcoId ademcoId, AdemcoEvent ademcoEvent, AdemcoGG gg, AdemcoZone zone);
ADEMCO_EXPORT_SYMBOL int ademcoAppendDataSegment2(AdemcoDataSegment* dataSegment, AdemcoId ademcoId, AdemcoEvent ademcoEvent, AdemcoGG gg, AdemcoZone zone);
ADEMCO_EXPORT_SYMBOL AdemcoParseResult ademcoParseDataSegment(const uint8_t* packet, int packet_len, AdemcoDataSegment* dataSegment);
// return 0 for empty packet, CONGWIN_FE100_PACKET_LEN for non-empty packet
ADEMCO_EXPORT_SYMBOL int ademcoDataSegmentToCongwinFe100(uint8_t* fe100, const AdemcoDataSegment* dataSegment);
//////////////////////// AdemcoXDataSegment functions ////////////////////////
// return ADEMCO_OK for success, ADEMCO_ERR for failed
ADEMCO_EXPORT_SYMBOL int ademcoXDataConvert(AdemcoXDataSegment* xdata, AdemcoXDataLengthFormat xlf);
#ifndef SWIG
// get valid content address of xdata (except [len])
ADEMCO_EXPORT_SYMBOL const uint8_t* ademcoXDataGetValidContentAddr(const AdemcoXDataSegment* xdata);
......@@ -376,9 +383,12 @@ ADEMCO_EXPORT_SYMBOL const uint8_t* ademcoXDataGetValidContentAddr(const AdemcoX
ADEMCO_EXPORT_SYMBOL int ademcoXDataGetValidContentLen(const AdemcoXDataSegment* xdata);
// return 0 if xdata's valid content is exactly the same as [buf, buf+buf_len)
ADEMCO_EXPORT_SYMBOL int ademcoXDataMemcmp(const AdemcoXDataSegment* xdata, const void* buf, int buf_len);
// copy xdata content from src to dst, return copied length = src.raw_len
ADEMCO_EXPORT_SYMBOL int ademcoXDataCopy(AdemcoXDataSegment* dst, const AdemcoXDataSegment* src);
// return ADEMCO_OK for success, return ADEMCO_ERR for len is too long
ADEMCO_EXPORT_SYMBOL int ademcoMakeXData(AdemcoXDataSegment* xdata, AdemcoXDataLengthFormat xlf, AdemcoXDataTransform xtr, const uint8_t* content, int len);
//////////////////////// AdemcoPacketId functions ////////////////////////
ADEMCO_EXPORT_SYMBOL int isAdemcoPacketId(const char* standard, const char* id, int len);
ADEMCO_EXPORT_SYMBOL AdemcoPacketId getAdemcoPacketId(const char* id, int len);
ADEMCO_EXPORT_SYMBOL const char* admecoPacketIdToString(AdemcoPacketId id);
......@@ -394,14 +404,14 @@ ADEMCO_EXPORT_SYMBOL int ademcoMakeNullPacket(uint8_t* buff, int len, uint16_t s
ADEMCO_EXPORT_SYMBOL int ademcoMakeAckPacket(uint8_t* buff, int len, uint16_t seq, const char* acct, AdemcoId ademcoId);
ADEMCO_EXPORT_SYMBOL int ademcoMakeNakPacket(uint8_t* buff, int len, uint16_t seq, const char* acct, AdemcoId ademcoId);
ADEMCO_EXPORT_SYMBOL int ademcoMakeHbPacket(uint8_t* buff, int len, uint16_t seq, const char* acct, AdemcoId ademcoId,
AdemcoEvent ademcoEvent, AdemcoGG gg, AdemcoZone zone, AdemcoXDataSegment* xdata);
AdemcoEvent ademcoEvent, AdemcoGG gg, AdemcoZone zone, const AdemcoXDataSegment* xdata);
// like upper funcs, store buff and len to pkt->raw, pkt->raw_len
ADEMCO_EXPORT_SYMBOL int ademcoMakeNullPacket2(AdemcoPacket* pkt, uint16_t seq, const char* acct, AdemcoId ademcoId);
ADEMCO_EXPORT_SYMBOL int ademcoMakeAckPacket2(AdemcoPacket* pkt, uint16_t seq, const char* acct, AdemcoId ademcoId);
ADEMCO_EXPORT_SYMBOL int ademcoMakeNakPacket2(AdemcoPacket* pkt, uint16_t seq, const char* acct, AdemcoId ademcoId);
ADEMCO_EXPORT_SYMBOL int ademcoMakeHbPacket2(AdemcoPacket* pkt, uint16_t seq, const char* acct, AdemcoId ademcoId,
AdemcoEvent ademcoEvent, AdemcoGG gg, AdemcoZone zone, AdemcoXDataSegment* xdata);
AdemcoEvent ademcoEvent, AdemcoGG gg, AdemcoZone zone, const AdemcoXDataSegment* xdata);
ADEMCO_EXPORT_SYMBOL AdemcoParseResult ademcoPacketParse(const uint8_t* buff, int len, AdemcoPacket* pkt, int* cb_commited);
......
......@@ -487,7 +487,13 @@ int hbCheckSum(const uint8_t* data, int len)
return sum == *p;
}
HbComRequestType hbComParseRequest(const uint8_t* data, int len)
#define copy_data_to_com \
if (cd) { \
cd->len = len; \
memcpy(cd->data, data, len); \
}
HbComRequestType hbComParseRequest(const uint8_t* data, int len, HbComData* cd)
{
do {
if (len < 5) { break; }
......@@ -501,36 +507,42 @@ HbComRequestType hbComParseRequest(const uint8_t* data, int len)
{
if (len != HbComReq_A0_len) { break; }
if (memcmp(HbComReq_A0_data, data, len) != 0) { break; }
copy_data_to_com;
return HbComReq_A0;
}
case 0xA1: // EB AB 3F A1 76
{
if (len != HbComReq_A1_len) { break; }
if (memcmp(HbComReq_A1_data, data, len) != 0) { break; }
copy_data_to_com;
return HbComReq_A1;
}
case 0xA2: // EB AB 3F A2 77
{
if (len != HbComReq_A2_len) { break; }
if (memcmp(HbComReq_A2_data, data, len) != 0) { break; }
copy_data_to_com;
return HbComReq_A2;
}
case 0xA5: // EB AB 3F A5 7A
{
if (len != HbComReq_A5_len) { break; }
if (memcmp(HbComReq_A5_data, data, len) != 0) { break; }
copy_data_to_com;
return HbComReq_A5;
}
case 0xAC: // EB AB 3F AC 81
{
if (len != HbComReq_AC_len) { break; }
if (memcmp(HbComReq_AC_data, data, len) != 0) { break; }
copy_data_to_com;
return HbComReq_AC;
}
case 0xAD: // EB AB 3F AD 82
{
if (len != HbComReq_AD_len) { break; }
if (memcmp(HbComReq_AD_data, data, len) != 0) { break; }
copy_data_to_com;
return HbComReq_AD;
}
......@@ -543,6 +555,7 @@ HbComRequestType hbComParseRequest(const uint8_t* data, int len)
{
if (data[2] != 0x3F) { break; }
if (len == HbComReq_RD_acct_len && memcmp(data, HbComReq_RD_acct_data, len) == 0) {
copy_data_to_com;
return HbComReq_RD_acct;
}
break;
......@@ -552,11 +565,20 @@ HbComRequestType hbComParseRequest(const uint8_t* data, int len)
if (data[2] != 0x3F) { break; }
if (data[3] == 0x09 && data[4] == 0xA3 && len == HbComReq_A3_len) { // EB CB 3F 09 A3 P1 P2 P3 SUM
if (hbCheckSum(data, len)) { return HbComReq_A3; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComReq_A3;
}
} else if (data[3] == 0x0F && data[4] == 0x4D && len == HbComReq_WR_acct_len) {
if (hbCheckSum(data, len)) { return HbComReq_WR_acct; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComReq_WR_acct;
}
} else if (data[3] == 0x0E && data[4] == 0xA7 && len == HbComReq_A7_len) { // EB CB 3F 0E A7 H1 M1 H2 M2 H3 M3 H4 M4 SUM
if (hbCheckSum(data, len)) { return HbComReq_A7; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComReq_A7;
}
}
/*else if (data[3] == 0x08 && data[4] == 0xA9 && len == A9_len) {
......@@ -565,7 +587,10 @@ HbComRequestType hbComParseRequest(const uint8_t* data, int len)
}*/
else if (data[3] == 0x08 && data[4] == 0xAA && len == HbComReq_AA_len) { // EB CB 3F 08 AA P1 P2 SUM
if (hbCheckSum(data, len)) { return HbComReq_AA; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComReq_AA;
}
}
/*else if (data[3] == 0x08 && data[4] == 0xAE && len == AE_len) {
......@@ -574,6 +599,7 @@ HbComRequestType hbComParseRequest(const uint8_t* data, int len)
}*/
else if (data[3] == 0x06 && data[4] == 0xB0 && len == HbComReq_B0_len && memcmp(HbComReq_B0_data, data, len) == 0) { // EB CB 3F 06 B0 AB
copy_data_to_com;
return HbComReq_B0;
}
}
......@@ -582,13 +608,13 @@ HbComRequestType hbComParseRequest(const uint8_t* data, int len)
return HbComReq_Invalid;
}
ADEMCO_EXPORT_SYMBOL HbComRequestType hbComParseXDataRequest(const AdemcoXDataSegment* xdata)
ADEMCO_EXPORT_SYMBOL HbComRequestType hbComParseXDataRequest(const AdemcoXDataSegment* xdata, HbComData* cd)
{
if (!xdata) { return HbComReq_Invalid; }
return hbComParseRequest(ademcoXDataGetValidContentAddr(xdata), ademcoXDataGetValidContentLen(xdata));
return hbComParseRequest(ademcoXDataGetValidContentAddr(xdata), ademcoXDataGetValidContentLen(xdata), cd);
}
HbComResponseType hbComParseResponse(const uint8_t* data, int len)
HbComResponseType hbComParseResponse(const uint8_t* data, int len, HbComData* cd)
{
do {
if (len < 7) { break; } // 所有的 response ,长度最小为 7
......@@ -597,67 +623,98 @@ HbComResponseType hbComParseResponse(const uint8_t* data, int len)
case 0xA0: // EB BA 3F 07 P0 A0 P1 P2 P3 SUM
{
if (len != HbComResp_A0_len) { break; }
if (hbCheckSum(data, len)) { return HbComResp_A0; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComResp_A0;
}
break;
}
case 0xA2: // EB BA 3F PN P0 A2 [Z, P]xN P1 SUM
{
if (len != data[3]) { break; }
if (hbCheckSum(data, len)) { return HbComResp_A2; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComResp_A2;
}
break;
}
case 0xA3:
{
if (len != HbComResp_A3_len) { break; }
if (hbCheckSum(data, len)) { return HbComResp_A3; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComResp_A3;
}
break;
}
case 0xA4:
{
if (len != HbComResp_A4_len) { break; }
if (hbCheckSum(data, len)) { return HbComResp_A4; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComResp_A4;
}
break;
}
case 0xA6:
{
if (len != HbComResp_A6_len) { break; }
if (hbCheckSum(data, len)) { return HbComResp_A6; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComResp_A6;
}
break;
}
case 0xA7:
{
if (len != HbComResp_A7_len) { break; }
if (hbCheckSum(data, len)) { return HbComResp_A7; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComResp_A7;
}
break;
}
case 0xA8:
{
if (len != HbComResp_A8_len) { break; }
if (hbCheckSum(data, len)) { return HbComResp_A8; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComResp_A8;
}
break;
}
case 0xA9:
{
// TODO
break;
}
case 0xAB:
{
if (len != HbComResp_AB_len) { break; }
if (hbCheckSum(data, len)) { return HbComResp_AB; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComResp_AB;
}
break;
}
case 0xAD: // EB BA 3F PN P0 AD P1 DATA P2 SUM
{
if (len != data[3]) { break; }
if (hbCheckSum(data, len)) { return HbComResp_AD; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComResp_AD;
}
break;
}
case 0xAF: // TODO
{
......@@ -668,15 +725,24 @@ HbComResponseType hbComParseResponse(const uint8_t* data, int len)
case 0xB1: // EB BA 3F 08 P0 B1 P1 SUM
{
if (len != HbComResp_B1_len) { break; }
if (hbCheckSum(data, len)) { return HbComResp_B1; }
if (hbCheckSum(data, len)) {
copy_data_to_com;
return HbComResp_B1;
}
break;
}
}
} while (0);
return HbComResp_Invalid;
}
HbComResponseType hbComParseXDataResponse(const AdemcoXDataSegment* xdata, HbComData* cd)
{
if (!xdata) { return HbComReq_Invalid; }
return hbComParseResponse(ademcoXDataGetValidContentAddr(xdata), ademcoXDataGetValidContentLen(xdata), cd);
}
void hbComMakeReqA0_getMachineStatus(HbComData* data)
{
memcpy(data->data, HbComReq_A0_data, HbComReq_A0_len);
......@@ -769,6 +835,31 @@ void hbComMakeReqWR_acct(HbComData* data, const char* acct)
hbSum(data->data, data->len);
}
void hbComResp_A2_Iter_init(HbComResp_A2_Iter* iter, const HbComData* com)
{
if (!iter || !com)return;
memcpy(&iter->com, com, sizeof(HbComData));
iter->i = 0;
if (com->len > HbComResp_A2_len_min) {
iter->total = (com->len - HbComResp_A2_len_min) / 2;
iter->p1 = (com->data[com->len - 2] == 0xFF) ? HbComResp_A2_p1_nomore : HbComResp_A2_p1_more;
} else {
iter->total = 0;
iter->p1 = HbComResp_A2_p1_nomore;
}
}
HbComResp_A2_p1 hbComResp_A2_Iter_next(HbComResp_A2_Iter* iter, HbZoneAndProperty* zp)
{
if (iter->i == iter->total) {
return HbComResp_A2_p1_nomore;
}
zp->zone = iter->com.data[6 + iter->i * 2];
zp->prop = (HbZoneProperty)iter->com.data[7 + iter->i * 2];
iter->i++;
return HbComResp_A2_p1_more;
}
void hbComMakeRespA0_getMachineStatus(HbComData* data, HbMachineStatus status, HbMachineType type)
{
memcpy(data->data, HbComResp_A0_head, 6);
......
......@@ -104,6 +104,11 @@ typedef enum HbZoneProperty {
HZP_INVALID = -1,
}HbZoneProperty;
typedef struct HbZoneAndProperty {
AdemcoZone zone;
HbZoneProperty prop;
}HbZoneAndProperty;
typedef struct HbMachineTimePoint {
uint8_t hour;
uint8_t minute;
......@@ -185,6 +190,11 @@ typedef enum HbComResp_A2_p1 {
HbComResp_A2_p1_more = 0x00, // 还有更多防区
}HbComResp_A2_p1;
#define HbComResp_A2_head "\xEB\xBA\x3F\x08\xCC\xA2"
typedef struct HbComResp_A2_Iter {
HbComData com;
int i, total;
HbComResp_A2_p1 p1;
}HbComResp_A2_Iter;
//! 索要更多主机防区 EB AB 3F A2 77
//! 仅应在收到ZoneResponse的param非0xFF时发送,以索要全部防区
......@@ -301,6 +311,11 @@ typedef enum HbCom_3sec_which {
//! 三区段主机状态回应 EB BA 3F 08 P0 B1 P1 SUM
#define HbComResp_B1_len 8
#define HbComResp_B1_head "\xEB\xBA\x3F\x08\xCC\xB1"
#define hbComResp_B1_status(d) ((((d)[6] >> 6) & 0x03) == HbCom_3sec_arm ? HMS_ARM : HMS_DISARM)
#define hbComResp_B1_status1(d) ((((d)[6] >> 4) & 0x03) == HbCom_3sec_arm ? HMS_ARM : HMS_DISARM)
#define hbComResp_B1_status2(d) ((((d)[6] >> 2) & 0x03) == HbCom_3sec_arm ? HMS_ARM : HMS_DISARM)
#define hbComResp_B1_status3(d) ((((d)[6]) & 0x03) == HbCom_3sec_arm ? HMS_ARM : HMS_DISARM)
//! 读取主机账号
#define HbComReq_RD_acct_len 7
......@@ -386,9 +401,10 @@ ADEMCO_EXPORT_SYMBOL void hbSum(uint8_t* data, int len);
// 校验和是否正确, return 0 for incorrect, otherwise correct
ADEMCO_EXPORT_SYMBOL int hbCheckSum(const uint8_t* data, int len);
ADEMCO_EXPORT_SYMBOL HbComRequestType hbComParseRequest(const uint8_t* buff, int len);
ADEMCO_EXPORT_SYMBOL HbComRequestType hbComParseXDataRequest(const AdemcoXDataSegment* xdata);
ADEMCO_EXPORT_SYMBOL HbComResponseType hbComParseResponse(const uint8_t* buff, int len);
ADEMCO_EXPORT_SYMBOL HbComRequestType hbComParseRequest(const uint8_t* buff, int len, HbComData* cd);
ADEMCO_EXPORT_SYMBOL HbComRequestType hbComParseXDataRequest(const AdemcoXDataSegment* xdata, HbComData* cd);
ADEMCO_EXPORT_SYMBOL HbComResponseType hbComParseResponse(const uint8_t* buff, int len, HbComData* cd);
ADEMCO_EXPORT_SYMBOL HbComResponseType hbComParseXDataResponse(const AdemcoXDataSegment* xdata, HbComData* cd);
ADEMCO_EXPORT_SYMBOL void hbComMakeReqA0_getMachineStatus(HbComData* data);
ADEMCO_EXPORT_SYMBOL void hbComMakeReqA1_getMachineZones(HbComData* data);
......@@ -407,6 +423,9 @@ ADEMCO_EXPORT_SYMBOL void hbComMakeReqB0_get3SectionMachineStatus(HbComData* dat
ADEMCO_EXPORT_SYMBOL void hbComMakeReqRD_acct(HbComData* data);
ADEMCO_EXPORT_SYMBOL void hbComMakeReqWR_acct(HbComData* data, const char* acct);
ADEMCO_EXPORT_SYMBOL void hbComResp_A2_Iter_init(HbComResp_A2_Iter* iter, const HbComData* com);
ADEMCO_EXPORT_SYMBOL HbComResp_A2_p1 hbComResp_A2_Iter_next(HbComResp_A2_Iter* iter, HbZoneAndProperty* zp);
ADEMCO_EXPORT_SYMBOL void hbComMakeRespA0_getMachineStatus(HbComData* data, HbMachineStatus status, HbMachineType type);
// zones and props length is count
// if count is 0, p1 wil be set to HbComResp_A2_p1_nomore
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment