641 lines
20 KiB
C
641 lines
20 KiB
C
|
||
//=============================================================================
|
||
// MODULE: ccmp.c
|
||
//
|
||
// Description:
|
||
//
|
||
// Bloodhound Parser DLL for the Cluster Control Message Protocol
|
||
//
|
||
// Modification History
|
||
//
|
||
// Mike Massa 03/21/97 Created
|
||
//=============================================================================
|
||
|
||
#include "precomp.h"
|
||
#pragma hdrstop
|
||
|
||
|
||
//
|
||
// Constants
|
||
//
|
||
#define ClusterDefaultMaxNodes 16 // from clusdef.h
|
||
#define ClusterMinNodeId 1 // from clusdef.h
|
||
|
||
#define MAX_CLUSTER_SIZE ClusterDefaultMaxNodes
|
||
|
||
#define BYTEL 8 // number of bits in a uint8
|
||
#define BYTES_IN_CLUSTER ((MAX_CLUSTER_SIZE + BYTEL - 1) / BYTEL)
|
||
|
||
#define BYTE(cluster, node) ( (cluster)[(node) / BYTEL] ) // byte# in array
|
||
#define BIT(node) ( (node) % BYTEL ) // bit# in byte
|
||
|
||
typedef UCHAR cluster_t [BYTES_IN_CLUSTER];
|
||
typedef SHORT node_t;
|
||
|
||
typedef union _CX_CLUSTERSCREEN {
|
||
ULONG UlongScreen;
|
||
cluster_t ClusterScreen;
|
||
} CX_CLUSTERSCREEN;
|
||
|
||
//
|
||
// converts external node number to internal
|
||
//
|
||
|
||
#define LOWEST_NODENUM ((node_t)ClusterMinNodeId) // starting node number
|
||
#define INT_NODE(ext_node) ((node_t)(ext_node - LOWEST_NODENUM))
|
||
#define EXT_NODE(int_node) ((node_t)(int_node + LOWEST_NODENUM))
|
||
|
||
#define CnpClusterScreenMember(c, i) \
|
||
((BOOLEAN)((BYTE(c,i) >> (BYTEL-1-BIT(i))) & 1))
|
||
|
||
#define CnpClusterScreenInsert(c, i) \
|
||
(BYTE(c, i) |= (1 << (BYTEL-1-BIT(i))))
|
||
|
||
#define CnpClusterScreenDelete(c, i) \
|
||
(BYTE(c, i) &= ~(1 << (BYTEL-1-BIT(i))))
|
||
|
||
|
||
//
|
||
// Types
|
||
//
|
||
typedef enum {
|
||
CcmpInvalidMsgType = 0,
|
||
CcmpHeartbeatMsgType = 1,
|
||
CcmpPoisonMsgType = 2,
|
||
CcmpMembershipMsgType = 3,
|
||
CcmpMcastHeartbeatMsgType = 4
|
||
} CCMP_MSG_TYPE;
|
||
|
||
typedef enum {
|
||
CcmpInvalidMsgCode = 0
|
||
} CCMP_MSG_CODE;
|
||
|
||
typedef struct {
|
||
ULONG SeqNumber;
|
||
ULONG AckNumber;
|
||
} CCMP_HEARTBEAT_MSG, *PCCMP_HEARTBEAT_MSG;
|
||
|
||
typedef struct {
|
||
ULONG NodeCount;
|
||
CX_CLUSTERSCREEN McastTargetNodes;
|
||
} CCMP_MCAST_HEARTBEAT_HEADER, *PCCMP_MCAST_HEARTBEAT_MSG;
|
||
|
||
typedef struct _CX_HB_NODE_INFO {
|
||
ULONG SeqNumber;
|
||
ULONG AckNumber;
|
||
} CX_HB_NODE_INFO, *PCX_HB_NODE_INFO;
|
||
|
||
typedef struct {
|
||
ULONG SeqNumber;
|
||
} CCMP_POISON_MSG, *PCCMP_POISON_MSG;
|
||
|
||
typedef struct {
|
||
UCHAR Type;
|
||
UCHAR Code;
|
||
USHORT Checksum;
|
||
|
||
union {
|
||
CCMP_HEARTBEAT_MSG Heartbeat;
|
||
CCMP_POISON_MSG Poison;
|
||
CCMP_MCAST_HEARTBEAT_HEADER McastHeartbeat;
|
||
} Message;
|
||
|
||
} CCMP_HEADER, *PCCMP_HEADER;
|
||
|
||
//
|
||
// Data
|
||
//
|
||
LPSTR HeartbeatTypeString = "Heartbeat";
|
||
LPSTR MembershipTypeString = "Membership";
|
||
LPSTR PoisonTypeString = "Poison";
|
||
LPSTR UnknownTypeString = "Unknown";
|
||
|
||
//=============================================================================
|
||
// Forward references.
|
||
//=============================================================================
|
||
|
||
VOID WINAPIV CcmpFormatSummary(LPPROPERTYINST lpPropertyInst);
|
||
|
||
DWORD WINAPIV CcmpFormatMcastNodeInfo(LPPROPERTYINST lpPropertyInst);
|
||
|
||
DWORD WINAPIV CcmpFormatMcastNodeData(LPPROPERTYINST lpPropertyInst);
|
||
|
||
LABELED_BYTE lbCcmpPacketTypes[] =
|
||
{
|
||
{
|
||
CcmpHeartbeatMsgType,
|
||
"Heartbeat"
|
||
},
|
||
|
||
{
|
||
CcmpPoisonMsgType,
|
||
"Poison"
|
||
},
|
||
|
||
{
|
||
CcmpMembershipMsgType,
|
||
"Membership"
|
||
},
|
||
|
||
{
|
||
CcmpMcastHeartbeatMsgType,
|
||
"Multicast Heartbeat"
|
||
},
|
||
};
|
||
|
||
#define NUM_CCMP_PACKET_TYPES (sizeof(lbCcmpPacketTypes) / sizeof(LABELED_BYTE))
|
||
|
||
SET sCcmpPacketTypes =
|
||
{
|
||
NUM_CCMP_PACKET_TYPES,
|
||
lbCcmpPacketTypes
|
||
};
|
||
|
||
|
||
//=============================================================================
|
||
// CCMP database.
|
||
//=============================================================================
|
||
|
||
#define CCMP_SUMMARY 0
|
||
#define CCMP_TYPE 1
|
||
#define CCMP_CODE 2
|
||
#define CCMP_RESERVED 3
|
||
#define CCMP_HB_SEQ_NUMBER 4
|
||
#define CCMP_HB_ACK_NUMBER 5
|
||
#define CCMP_POISON_SEQ_NUMBER 6
|
||
#define CCMP_MCASTHB_NODE_COUNT 7
|
||
#define CCMP_MCASTHB_NODE_DATA 8
|
||
#define CCMP_MCASTHB_NODE_INFO 9
|
||
#define CCMP_MCASTHB_NODE_MASK 10
|
||
|
||
PROPERTYINFO CcmpDatabase[] =
|
||
{
|
||
{ // CCMP_SUMMARY 0
|
||
0,0,
|
||
"Summary",
|
||
"Summary of the CCMP packet",
|
||
PROP_TYPE_SUMMARY,
|
||
PROP_QUAL_NONE,
|
||
NULL,
|
||
132,
|
||
CcmpFormatSummary},
|
||
|
||
{ // CCMP_TYPE 1
|
||
0,0,
|
||
"Type",
|
||
"Type of CCMP packet",
|
||
PROP_TYPE_BYTE,
|
||
PROP_QUAL_LABELED_SET,
|
||
&sCcmpPacketTypes,
|
||
FMT_STR_SIZE,
|
||
FormatPropertyInstance},
|
||
|
||
{ // CCMP_CODE 2
|
||
0,0,
|
||
"Code",
|
||
"Identifying code (Type Specific)",
|
||
PROP_TYPE_BYTE,
|
||
PROP_QUAL_NONE,
|
||
NULL,
|
||
80,
|
||
FormatPropertyInstance},
|
||
|
||
{ // CCMP_RESERVED 3
|
||
0,0,
|
||
"Reserved",
|
||
"Reserved field",
|
||
PROP_TYPE_WORD,
|
||
PROP_QUAL_NONE,
|
||
NULL,
|
||
80,
|
||
FormatPropertyInstance},
|
||
|
||
{ // CCMP_HB_SEQ_NUMBER 4
|
||
0,0,
|
||
"Sequence Number",
|
||
"Sequence number identifying this heartbeat",
|
||
PROP_TYPE_DWORD,
|
||
PROP_QUAL_NONE,
|
||
NULL,
|
||
80,
|
||
FormatPropertyInstance},
|
||
|
||
{ // CCMP_HB_ACK_NUMBER 5
|
||
0,0,
|
||
"Acknowledgement Number",
|
||
"Acknowledgement of the last heartbeat received from the destination",
|
||
PROP_TYPE_DWORD,
|
||
PROP_QUAL_NONE,
|
||
NULL,
|
||
80,
|
||
FormatPropertyInstance},
|
||
|
||
{ // CCMP_POISON_SEQ_NUMBER 6
|
||
0,0,
|
||
"Sequence Number",
|
||
"Sequence number identifying this poison packet",
|
||
PROP_TYPE_DWORD,
|
||
PROP_QUAL_NONE,
|
||
NULL,
|
||
80,
|
||
FormatPropertyInstance},
|
||
|
||
{ // CCMP_MCASTHB_NODE_COUNT 7
|
||
0,0,
|
||
"Node Count",
|
||
"Maximum number of nodes for which this message contains data",
|
||
PROP_TYPE_WORD,
|
||
PROP_QUAL_NONE,
|
||
NULL,
|
||
80,
|
||
FormatPropertyInstance},
|
||
|
||
{ // CCMP_MCASTHB_NODE_DATA 8
|
||
0,0,
|
||
"Multicast Node Data",
|
||
"Array of heartbeat sequence and acknowledgement numbers",
|
||
PROP_TYPE_SUMMARY,
|
||
PROP_QUAL_NONE,
|
||
NULL,
|
||
80,
|
||
CcmpFormatMcastNodeData},
|
||
|
||
{ // CCMP_MCASTHB_NODE_INFO 9
|
||
0,0,
|
||
"Multicast Node Info",
|
||
"Heartbeat sequence and acknowledgement number for a target node",
|
||
PROP_TYPE_SUMMARY,
|
||
PROP_QUAL_NONE,
|
||
NULL,
|
||
80,
|
||
CcmpFormatMcastNodeInfo},
|
||
|
||
{ // CCMP_MCASTHB_NODE_MASK 10
|
||
0,0,
|
||
"Multicast Target Node Mask",
|
||
"Bitmask of nodes for which this heartbeat message contains data",
|
||
PROP_TYPE_DWORD,
|
||
PROP_QUAL_NONE,
|
||
NULL,
|
||
80,
|
||
FormatPropertyInstance},
|
||
|
||
};
|
||
|
||
DWORD nCcmpProperties = ((sizeof CcmpDatabase) / PROPERTYINFO_SIZE);
|
||
|
||
|
||
//=============================================================================
|
||
// FUNCTION: CcmpRegister()
|
||
//
|
||
// Modification History
|
||
//
|
||
// Steve Hiskey 07/07/94 Created
|
||
//=============================================================================
|
||
|
||
VOID WINAPI CcmpRegister(HPROTOCOL hCcmpProtocol)
|
||
{
|
||
register DWORD i;
|
||
|
||
//=========================================================================
|
||
// Create the property database.
|
||
//=========================================================================
|
||
|
||
CreatePropertyDatabase(hCcmpProtocol, nCcmpProperties);
|
||
|
||
for(i = 0; i < nCcmpProperties; ++i)
|
||
{
|
||
AddProperty(hCcmpProtocol, &CcmpDatabase[i]);
|
||
}
|
||
|
||
}
|
||
|
||
//=============================================================================
|
||
// FUNCTION: Deregister()
|
||
//
|
||
// Modification History
|
||
//
|
||
// Steve Hiskey 07/07/94 Created
|
||
//=============================================================================
|
||
|
||
VOID WINAPI CcmpDeregister(HPROTOCOL hCcmpProtocol)
|
||
{
|
||
DestroyPropertyDatabase(hCcmpProtocol);
|
||
}
|
||
|
||
//=============================================================================
|
||
// FUNCTION: CcmpRecognizeFrame()
|
||
//
|
||
// Modification History
|
||
//
|
||
// Steve Hiskey 07/07/94 Created
|
||
//=============================================================================
|
||
|
||
LPBYTE WINAPI CcmpRecognizeFrame(HFRAME hFrame, //... frame handle.
|
||
LPBYTE MacFrame, //... Frame pointer.
|
||
LPBYTE MyFrame, //... Relative pointer.
|
||
DWORD MacType, //... MAC type.
|
||
DWORD BytesLeft, //... Bytes left.
|
||
HPROTOCOL hPreviousProtocol, //... Previous protocol or NULL if none.
|
||
DWORD nPreviousProtocolOffset, //... Offset of previous protocol.
|
||
LPDWORD ProtocolStatusCode, //... Pointer to return status code in.
|
||
LPHPROTOCOL hNextProtocol, //... Next protocol to call (optional).
|
||
LPDWORD InstData) //... Next protocol instance data.
|
||
{
|
||
CCMP_HEADER UNALIGNED * ccmpHeader = (CCMP_HEADER UNALIGNED *) MyFrame;
|
||
LPBYTE lpNextByte = (LPBYTE) (ccmpHeader + 1);
|
||
|
||
if (ccmpHeader->Type == CcmpMcastHeartbeatMsgType) {
|
||
lpNextByte += (ccmpHeader->Message.McastHeartbeat.NodeCount * sizeof(CX_HB_NODE_INFO));
|
||
*ProtocolStatusCode = PROTOCOL_STATUS_CLAIMED;
|
||
} else {
|
||
#ifdef SSP_DECODE
|
||
*hNextProtocol = GetProtocolFromName("SSP");
|
||
*ProtocolStatusCode = PROTOCOL_STATUS_NEXT_PROTOCOL;
|
||
#else
|
||
*ProtocolStatusCode = PROTOCOL_STATUS_CLAIMED;
|
||
#endif
|
||
}
|
||
|
||
return lpNextByte;
|
||
}
|
||
|
||
//=============================================================================
|
||
// FUNCTION: CcmpAttachProperties()
|
||
//
|
||
// Modification History
|
||
//
|
||
// Steve Hiskey 07/07/94 Created
|
||
//=============================================================================
|
||
|
||
LPBYTE WINAPI CcmpAttachProperties(HFRAME hFrame,
|
||
LPBYTE Frame,
|
||
LPBYTE MyFrame,
|
||
DWORD MacType,
|
||
DWORD BytesLeft,
|
||
HPROTOCOL hPreviousProtocol,
|
||
DWORD nPreviousProtocolOffset,
|
||
DWORD InstData)
|
||
{
|
||
CCMP_HEADER UNALIGNED * ccmpHeader = (CCMP_HEADER UNALIGNED *) MyFrame;
|
||
|
||
AttachPropertyInstance(hFrame,
|
||
CcmpDatabase[CCMP_SUMMARY].hProperty,
|
||
#ifdef SSP_DECODE
|
||
sizeof(CCMP_HEADER),
|
||
#else
|
||
BytesLeft,
|
||
#endif
|
||
ccmpHeader,
|
||
0, 0, 0);
|
||
|
||
AttachPropertyInstance(hFrame,
|
||
CcmpDatabase[CCMP_TYPE].hProperty,
|
||
sizeof(BYTE),
|
||
&(ccmpHeader->Type),
|
||
0, 1, 0);
|
||
|
||
AttachPropertyInstance(hFrame,
|
||
CcmpDatabase[CCMP_CODE].hProperty,
|
||
sizeof(BYTE),
|
||
&(ccmpHeader->Code),
|
||
0, 1, 0);
|
||
|
||
AttachPropertyInstance(hFrame,
|
||
CcmpDatabase[CCMP_RESERVED].hProperty,
|
||
sizeof(WORD),
|
||
&(ccmpHeader->Checksum),
|
||
0, 1, 0);
|
||
|
||
if (ccmpHeader->Type == CcmpHeartbeatMsgType) {
|
||
|
||
AttachPropertyInstance(hFrame,
|
||
CcmpDatabase[CCMP_HB_SEQ_NUMBER].hProperty,
|
||
sizeof(DWORD),
|
||
&(ccmpHeader->Message.Heartbeat.SeqNumber),
|
||
0, 1, 0);
|
||
|
||
AttachPropertyInstance(hFrame,
|
||
CcmpDatabase[CCMP_HB_ACK_NUMBER].hProperty,
|
||
sizeof(DWORD),
|
||
&(ccmpHeader->Message.Heartbeat.AckNumber),
|
||
0, 1, 0);
|
||
|
||
} else if (ccmpHeader->Type == CcmpPoisonMsgType) {
|
||
|
||
AttachPropertyInstance(hFrame,
|
||
CcmpDatabase[CCMP_POISON_SEQ_NUMBER].hProperty,
|
||
sizeof(DWORD),
|
||
&(ccmpHeader->Message.Poison.SeqNumber),
|
||
0, 1, 0);
|
||
|
||
} else if (ccmpHeader->Type == CcmpMcastHeartbeatMsgType) {
|
||
|
||
CX_HB_NODE_INFO UNALIGNED * nodeInfo;
|
||
DWORD i;
|
||
|
||
//
|
||
// Header
|
||
//
|
||
AttachPropertyInstance(hFrame,
|
||
CcmpDatabase[CCMP_MCASTHB_NODE_COUNT].hProperty,
|
||
sizeof(DWORD),
|
||
&(ccmpHeader->Message.McastHeartbeat.NodeCount),
|
||
0, 1, 0);
|
||
|
||
AttachPropertyInstance(hFrame,
|
||
CcmpDatabase[CCMP_MCASTHB_NODE_MASK].hProperty,
|
||
sizeof(DWORD),
|
||
&(ccmpHeader->Message.McastHeartbeat.McastTargetNodes.UlongScreen),
|
||
0, 1, 0);
|
||
|
||
//
|
||
// Format the heartbeat data.
|
||
//
|
||
|
||
nodeInfo = (CX_HB_NODE_INFO UNALIGNED *)(ccmpHeader + 1);
|
||
|
||
AttachPropertyInstance(hFrame,
|
||
CcmpDatabase[CCMP_MCASTHB_NODE_DATA].hProperty,
|
||
sizeof(nodeInfo[0]) * ccmpHeader->Message.McastHeartbeat.NodeCount,
|
||
&(nodeInfo[0]),
|
||
0, 1, 0);
|
||
|
||
for (i = ClusterMinNodeId;
|
||
i < (DWORD) EXT_NODE(ccmpHeader->Message.McastHeartbeat.NodeCount);
|
||
i++) {
|
||
|
||
if (CnpClusterScreenMember(
|
||
ccmpHeader->Message.McastHeartbeat.McastTargetNodes.ClusterScreen,
|
||
INT_NODE(i)
|
||
)) {
|
||
AttachPropertyInstanceEx(hFrame,
|
||
CcmpDatabase[CCMP_MCASTHB_NODE_INFO].hProperty,
|
||
sizeof(nodeInfo[INT_NODE(i)]),
|
||
&(nodeInfo[INT_NODE(i)]),
|
||
sizeof(i),
|
||
&i,
|
||
0, 2, 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
//==============================================================================
|
||
// FUNCTION: CcmpFormatMcastNodeData()
|
||
//
|
||
// Modification History
|
||
//
|
||
// David Dion 04/10/2001 Created
|
||
//==============================================================================
|
||
|
||
DWORD WINAPIV CcmpFormatMcastNodeData(LPPROPERTYINST lpPropertyInst)
|
||
{
|
||
wsprintf( lpPropertyInst->szPropertyText,
|
||
"Node Data:"
|
||
);
|
||
|
||
return NMERR_SUCCESS;
|
||
}
|
||
|
||
|
||
//==============================================================================
|
||
// FUNCTION: CcmpFormatMcastNodeInfo()
|
||
//
|
||
// Modification History
|
||
//
|
||
// David Dion 04/10/2001 Created
|
||
//==============================================================================
|
||
|
||
DWORD WINAPIV CcmpFormatMcastNodeInfo(LPPROPERTYINST lpPropertyInst)
|
||
{
|
||
DWORD Length;
|
||
LPPROPERTYINSTEX lpPropertyInstEx = lpPropertyInst->lpPropertyInstEx;
|
||
CX_HB_NODE_INFO UNALIGNED * nodeInfo = lpPropertyInstEx->lpData;
|
||
DWORD node = (lpPropertyInstEx->Dword[0]);
|
||
|
||
Length = wsprintf( lpPropertyInst->szPropertyText,
|
||
"Node %u Heartbeat: Seq = %u (0x%x); Ack = %u (0x%x)",
|
||
node,
|
||
nodeInfo->SeqNumber,
|
||
nodeInfo->SeqNumber,
|
||
nodeInfo->AckNumber,
|
||
nodeInfo->AckNumber
|
||
);
|
||
|
||
return NMERR_SUCCESS;
|
||
}
|
||
|
||
|
||
//==============================================================================
|
||
// FUNCTION: CcmpFormatSummary()
|
||
//
|
||
// Modification History
|
||
//
|
||
// Steve Hiskey 07/07/94 Created
|
||
//==============================================================================
|
||
|
||
VOID WINAPIV CcmpFormatSummary(LPPROPERTYINST lpPropertyInst)
|
||
{
|
||
LPSTR typeString;
|
||
LPSTR SummaryStr;
|
||
DWORD Length;
|
||
CCMP_HEADER UNALIGNED * ccmpHeader =
|
||
(CCMP_HEADER UNALIGNED *) lpPropertyInst->lpData;
|
||
|
||
|
||
if (ccmpHeader->Type == CcmpHeartbeatMsgType) {
|
||
Length = wsprintf( lpPropertyInst->szPropertyText,
|
||
"Heartbeat: Seq = %u (0x%x); Ack = %u (0x%x)",
|
||
ccmpHeader->Message.Heartbeat.SeqNumber,
|
||
ccmpHeader->Message.Heartbeat.SeqNumber,
|
||
ccmpHeader->Message.Heartbeat.AckNumber,
|
||
ccmpHeader->Message.Heartbeat.AckNumber
|
||
);
|
||
}
|
||
else if (ccmpHeader->Type == CcmpPoisonMsgType) {
|
||
Length = wsprintf( lpPropertyInst->szPropertyText,
|
||
"Poison: Seq = %u (0x%x)",
|
||
ccmpHeader->Message.Poison.SeqNumber,
|
||
ccmpHeader->Message.Poison.SeqNumber
|
||
);
|
||
}
|
||
else if (ccmpHeader->Type == CcmpMembershipMsgType) {
|
||
Length = wsprintf( lpPropertyInst->szPropertyText,
|
||
"Membership"
|
||
);
|
||
}
|
||
else if (ccmpHeader->Type == CcmpMcastHeartbeatMsgType) {
|
||
CX_HB_NODE_INFO UNALIGNED * nodeInfo;
|
||
DWORD i;
|
||
LPSTR strbuf = lpPropertyInst->szPropertyText;
|
||
|
||
nodeInfo = (CX_HB_NODE_INFO UNALIGNED *)(ccmpHeader + 1);
|
||
|
||
Length = wsprintf( strbuf,
|
||
"Multicast Heartbeat: "
|
||
);
|
||
for (i = ClusterMinNodeId;
|
||
i < (DWORD) EXT_NODE(ccmpHeader->Message.McastHeartbeat.NodeCount);
|
||
i++) {
|
||
|
||
if (CnpClusterScreenMember(
|
||
ccmpHeader->Message.McastHeartbeat.McastTargetNodes.ClusterScreen,
|
||
INT_NODE(i)
|
||
)) {
|
||
strbuf = (LPSTR)((PUCHAR)strbuf + Length);
|
||
Length = wsprintf( strbuf,
|
||
"(N%u: S%u, A%u) ",
|
||
i,
|
||
nodeInfo[INT_NODE(i)].SeqNumber,
|
||
nodeInfo[INT_NODE(i)].AckNumber
|
||
);
|
||
}
|
||
}
|
||
|
||
}
|
||
else {
|
||
Length = wsprintf( lpPropertyInst->szPropertyText,
|
||
"Unknown CCMP message type: %u",
|
||
ccmpHeader->Type
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
//==============================================================================
|
||
// FUNCTION: CcmpFormatProperties()
|
||
//
|
||
// Modification History
|
||
//
|
||
// Steve Hiskey 07/07/94 Created
|
||
//==============================================================================
|
||
|
||
DWORD WINAPI CcmpFormatProperties(HFRAME hFrame,
|
||
LPBYTE MacFrame,
|
||
LPBYTE FrameData,
|
||
DWORD nPropertyInsts,
|
||
LPPROPERTYINST p)
|
||
{
|
||
//=========================================================================
|
||
// Format each property in the property instance table.
|
||
//
|
||
// The property-specific instance data was used to store the address of a
|
||
// property-specific formatting function so all we do here is call each
|
||
// function via the instance data pointer.
|
||
//=========================================================================
|
||
|
||
while (nPropertyInsts--)
|
||
{
|
||
((FORMAT) p->lpPropertyInfo->InstanceData)(p);
|
||
|
||
p++;
|
||
}
|
||
|
||
return NMERR_SUCCESS;
|
||
}
|
||
|