Refactored various message-related operations into the Message class.

- Replaces the XPCMessage struct.
 - Messages are now read from a UDPSocket by the Message class.
 - Printing messages to the log is now handled by the Message class.
 - In the future, may extend this class with command-specific subclasses that encapsulate additional message parsing functionality.
This commit is contained in:
Jason Watkins
2015-04-11 09:12:47 -07:00
parent 2736cd86de
commit 547eb4de18
9 changed files with 363 additions and 303 deletions

166
xpcPlugin/Message.cpp Normal file
View File

@@ -0,0 +1,166 @@
//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the
//National Aeronautics and Space Administration. All Rights Reserved.
#include "Message.h"
#include "Log.h"
#include "xplaneConnect.h"
#include <iomanip>
#include <sstream>
#include <string>
namespace XPC
{
Message::Message() {}
Message Message::ReadFrom(UDPSocket& sock)
{
Message m;
int len = sock.Read(m.buffer, bufferSize, &m.source);
m.size = len < 0 ? 0 : len;
return m;
}
std::uint32_t Message::GetMagicNumber()
{
if (size < 4)
{
return 0;
}
return *((std::uint32_t*)buffer);
}
std::string Message::GetHead()
{
if (size < 4)
{
return "";
}
return std::string((char*)buffer, 4);
}
const std::uint8_t* Message::GetBuffer()
{
if (size == 0)
{
return nullptr;
}
return buffer;
}
std::size_t Message::GetSize()
{
return size;
}
struct sockaddr Message::GetSource()
{
return source;
}
void Message::PrintToLog()
{
#if LOG_VERBOSITY > 2
std::stringstream ss;
ss << "[DEBUG]";
// Dump raw bytes to string
ss << std::hex << std::setfill('0');
for (int i = 0; i < size; ++i)
{
ss << ' ' << std::setw(2) << static_cast<unsigned>(buffer[i]);
}
Log::WriteLine(ss.str());
ss.str("");
ss << "[" << GetHead() << "-DEBUG](" << buffer[4] << ")";
switch (GetMagicNumber())
{
case 0x434F4E4E: // CONN
case 0x57595054: // WYPT
case 0x54455854: // TEXT
{
Log::WriteLine(ss.str());
break;
}
case 0x4354524C: // CTRL
{
xpcCtrl ctrl = parseCTRL((char*)buffer);
ss << " (" << ctrl.pitch << " " << ctrl.roll << " " << ctrl.yaw << ") ";
ss << ctrl.throttle << " " << ctrl.gear << " " << ctrl.flaps;
Log::WriteLine(ss.str());
break;
}
case 0x44415441: // DATA
{
float dataRef[30][9];
short numCols = parseDATA((char*)buffer, size, dataRef);
ss << " (" << numCols << " lines)";
Log::WriteLine(ss.str());
for (int i = 0; i < numCols; ++i)
{
ss.str("");
ss << "\t#" << dataRef[i][0];
for (int j = 1; j < 9; ++j)
{
ss << " " << dataRef[i][j];
}
Log::WriteLine(ss.str());
}
break;
}
case 0x44524546: // DREF
{
Log::WriteLine(ss.str());
std::string dref((char*)buffer + 6, buffer[5]);
Log::FormatLine("-\tDREF (size %i) = %s", dref.length(), dref.c_str());
ss.str("");
int values = buffer[6 + buffer[5]];
ss << "\tValues(size " << values << ") =";
for (int i = 0; i < values; ++i)
{
ss << " " << *((float*)(buffer + values + 1 + sizeof(float) * i));
}
Log::WriteLine(ss.str());
break;
}
case 0x47455444: // GETD
{
Log::WriteLine(ss.str());
int cur = 6;
for (int i = 0; i < buffer[5]; ++i)
{
std::string dref((char*)buffer + cur + 1, buffer[cur]);
Log::FormatLine("\t#%i/%i (size:%i) %s", i + 1, buffer[5], dref.length(), dref);
cur += 1 + buffer[cur];
}
break;
}
case 0x504F5349: // POSI
{
float position[6] = { 0.0 };
short aircraft = 0;
float gear = -1.0;
aircraft = parsePOSI((char*)buffer, position, 6, &gear);
ss << ' ' << aircraft;
ss << " (" << position[0] << ' ' << position[1] << ' ' << position[2] << ") (";
ss << position[3] << ' ' << position[4] << ' ' << position[5] << ") ";
ss << gear;
Log::WriteLine(ss.str());
break;
}
case 0x53494D55: // SIMU
{
ss << ' ' << buffer[5];
Log::WriteLine(ss.str());
break;
}
default:
{
ss << " UNKNOWN HEADER";
Log::WriteLine(ss.str());
break;
}
}
#endif
}
}

57
xpcPlugin/Message.h Normal file
View File

@@ -0,0 +1,57 @@
//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the
//National Aeronautics and Space Administration. All Rights Reserved.
#ifndef XPC_MESSAGE_H
#define XPC_MESSAGE_H
#include "UDPSocket.h"
namespace XPC
{
/// Represents a message received from an XPC client.
///
/// \author Jason Watkins
/// \version 1.0
/// \since 1.0
/// \date Intial Version: 2015-04-11
/// \date Last Updated: 2015-04-11
class Message
{
public:
/// Reads a datagram from the specified socket and interprets it as a
/// message.
///
/// \param sock The socket to read from.
/// \returns A message parsed from the data read from sock. If no
/// data was read or an error occurs, returns a message
/// with the size set to 0.
static Message ReadFrom(UDPSocket& sock);
/// Gets the message header in binary form.
std::uint32_t GetMagicNumber();
/// Gets the message header.
std::string GetHead();
/// Gets the buffer underlying the message.
const std::uint8_t* GetBuffer();
/// Gets the size of the message in bytes.
std::size_t GetSize();
/// Gets the address this message was read from.
struct sockaddr GetSource();
/// Prints the contents of the message to the XPC log.
void PrintToLog();
private:
Message();
static const std::size_t bufferSize = 4096;
std::uint8_t buffer[bufferSize];
std::size_t size;
struct sockaddr source;
};
}
#endif

View File

@@ -63,6 +63,7 @@
// XPC Includes
#include "Log.h"
#include "Drawing.h"
#include "Message.h"
#include "UDPSocket.h"
#include "xpcPluginTools.h"
@@ -130,7 +131,7 @@ int handleDREF(char *buf);
int handleVIEW();
int handleDATA(char *buf, int buflen);
int handleTEXT(char *buf, int len);
short handleInput(struct XPCMessage * theMessage);
short handleInput(XPC::Message& msg);
char setPOSI(short aircraft, float pos[3]);
char setORIENT(short aircraft, float orient[3]);
@@ -239,7 +240,6 @@ float MyFlightLoopCallback( float inElapsedSinceLastCall,
#if (__APPLE__)
double diff_t;
#endif
XPCMessage theMessage;
counter++;
if (benchmarkingSwitch > 1)
@@ -255,8 +255,8 @@ float MyFlightLoopCallback( float inElapsedSinceLastCall,
start = (double)mach_absolute_time( ) * timeConvert;
#endif
}
readMessage(recvSocket, &theMessage);
result = handleInput(&theMessage);
XPC::Message msg = XPC::Message::ReadFrom(*recvSocket);
result = handleInput(msg);
if (benchmarkingSwitch > 0)
{
@@ -282,145 +282,143 @@ float MyFlightLoopCallback( float inElapsedSinceLastCall,
return -1;
}
short handleInput(struct XPCMessage * theMessage)
short handleInput(XPC::Message& msg)
{
int i;
char IP[16] = {0};
char IP[16] = { 0 };
unsigned short port = 0;
current_connection = -1;
if (theMessage->msglen > 0) // If message received
if (msg.GetSize() == 0)
{
if (LOG_VERBOSITY > 0)
// No message received
return 0;
}
msg.PrintToLog();
// Check for existing connection
port = getIP(msg.GetSource(), IP);
for (i = 0; i < number_of_connections; i++)
{
if (strcmp(connectionList[i].IP, IP) == 0 && port == connectionList[i].fromPort && port > 0)
{
#ifdef _WIN32
#else
printBufferToLog(*theMessage);
#endif
current_connection = i;
break;
}
// Check for existing connection
port = getIP(theMessage->recvaddr,IP);
for (i=0; i<number_of_connections; i++)
}
if (current_connection == -1) //SETUP NEW CONNECTION
{
if (number_of_connections >= MAXCONN) // Handle hitting MAXCONN (COME UP WITH A BETTER SOLUTION)
{
if (strcmp(connectionList[i].IP,IP) == 0 && port == connectionList[i].fromPort && port > 0)
{
current_connection=i;
break;
}
XPC::Log::WriteLine("[EXEC] Hit maximum number of connections-Removing old ones");
number_of_connections = 0;
}
if (current_connection == -1) //SETUP NEW CONNECTION
{
if (number_of_connections>=MAXCONN) // Handle hitting MAXCONN (COME UP WITH A BETTER SOLUTION)
{
XPC::Log::WriteLine("[EXEC] Hit maximum number of connections-Removing old ones");
number_of_connections = 0;
}
memcpy(connectionList[number_of_connections].IP,IP,16);
connectionList[number_of_connections].recPort = 49008;
connectionList[number_of_connections].fromPort = port;
current_connection = number_of_connections;
number_of_connections ++;
// Log Connection
XPC::Log::FormatLine("[EXEC] New Connection [%i]. IP=%s, port=%i", number_of_connections, IP, port);
}
if (strncmp(theMessage->head,"CONN",4)==0) // Header = CONN (Connection)
{
handleCONN(theMessage->msg);
}
else if (strncmp(theMessage->head,"SIMU",4)==0) // Header = SIMU
{
handleSIMU(theMessage->msg);
}
else if (strncmp(theMessage->head,"POSI",4)==0) // Header = POSI (Position)
{
handlePOSI(theMessage->msg);
}
else if (strncmp(theMessage->head,"CTRL",4)==0) // Header = CTRL (Control)
{
handleCTRL(theMessage->msg);
}
else if (strncmp(theMessage->head,"WYPT",4)==0) // Header = WYPT (Waypoint Draw)
{
handleWYPT(theMessage->msg, theMessage->msglen);
}
else if (strncmp(theMessage->head,"GETD",4)==0) // Header = GETD (Data Request)
{
handleGETD(theMessage->msg);
}
else if (strncmp(theMessage->head,"DREF",4)==0) // Header = DREF (By Data Ref) (this is slower than DATA)
{
handleDREF(theMessage->msg);
}
else if (strncmp(theMessage->head,"VIEW",4)==0) // Header = VIEW (Change View)
{
handleVIEW();
}
else if (strncmp(theMessage->head,"DATA",4)==0) // Header = DATA (UDP Data)
{
handleDATA(theMessage->msg, theMessage->msglen);
}
else if (strncmp(theMessage->head, "TEXT", 4) == 0) // Header = TEXT (Screen message)
{
handleTEXT(theMessage->msg, theMessage->msglen);
}
else if ((strncmp(theMessage->head,"DSEL",4)==0) || (strncmp(theMessage->head,"USEL",4)==0)) // Header = DSEL/USEL (Select UDP Send)
{
sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP
}
else if ((strncmp(theMessage->head,"DCOC",4)==0) || (strncmp(theMessage->head,"UCOC",4)==0))
{
sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP
}
else if ((strncmp(theMessage->head,"MOUS",4)==0) || (strncmp(theMessage->head,"CHAR",4)==0))
{
sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP
}
else if (strncmp(theMessage->head,"MENU",4)==0)
{
sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP
}
else if (strncmp(theMessage->head,"SOUN",4)==0)
{
sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP
}
else if ((strncmp(theMessage->head,"FAIL",4)==0) || (strncmp(theMessage->head,"RECO",4)==0))
{
sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP
}
else if (strncmp(theMessage->head,"PAPT",4)==0)
{
sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP
}
else if ((strncmp(theMessage->head,"VEHN",4)==0) || (strncmp(theMessage->head,"VEH1",4)==0) || (strncmp(theMessage->head,"VEHA",4)==0))
{
sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP
}
else if ((strncmp(theMessage->head,"OBJN",4)==0) || (strncmp(theMessage->head,"OBJL",4)==0))
{
sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP
}
else if ((strncmp(theMessage->head,"GSET",4)==0) || (strncmp(theMessage->head,"ISET",4)==0))
{
sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP
}
else if (strncmp(theMessage->head, "BOAT", 4) == 0)
{
sendBUF(theMessage->msg, theMessage->msglen); // Send to UDP
}
else
{ //unrecognized header
XPC::Log::FormatLine("[EXEC] ERROR: Command %s not recognized", theMessage->head);
}
current_connection = -1;
} // end if (buflen > 0)
return theMessage->msglen;
memcpy(connectionList[number_of_connections].IP, IP, 16);
connectionList[number_of_connections].recPort = 49008;
connectionList[number_of_connections].fromPort = port;
current_connection = number_of_connections;
number_of_connections++;
// Log Connection
XPC::Log::FormatLine("[EXEC] New Connection [%i]. IP=%s, port=%i", number_of_connections, IP, port);
}
std::string head = msg.GetHead();
if (head == "CONN") // Header = CONN (Connection)
{
handleCONN((char*)msg.GetBuffer());
}
else if (head == "SIMU") // Header = SIMU
{
handleSIMU((char*)msg.GetBuffer());
}
else if (head == "POSI") // Header = POSI (Position)
{
handlePOSI((char*)msg.GetBuffer());
}
else if (head == "CTRL") // Header = CTRL (Control)
{
handleCTRL((char*)msg.GetBuffer());
}
else if (head == "WYPT") // Header = WYPT (Waypoint Draw)
{
handleWYPT((char*)msg.GetBuffer(), msg.GetSize());
}
else if (head == "GETD") // Header = GETD (Data Request)
{
handleGETD((char*)msg.GetBuffer());
}
else if (head == "DREF") // Header = DREF (By Data Ref) (this is slower than DATA)
{
handleDREF((char*)msg.GetBuffer());
}
else if (head == "VIEW") // Header = VIEW (Change View)
{
handleVIEW();
}
else if (head == "DATA") // Header = DATA (UDP Data)
{
handleDATA((char*)msg.GetBuffer(), msg.GetSize());
}
else if (head == "TEXT") // Header = TEXT (Screen message)
{
handleTEXT((char*)msg.GetBuffer(), msg.GetSize());
}
else if ((head == "DSEL") || (head == "USEL")) // Header = DSEL/USEL (Select UDP Send)
{
sendBUF((char*)msg.GetBuffer(), msg.GetSize()); // Send to UDP
}
else if ((head == "DCOC") || (head == "UCOC"))
{
sendBUF((char*)msg.GetBuffer(), msg.GetSize()); // Send to UDP
}
else if ((head == "MOUS") || (head == "CHAR"))
{
sendBUF((char*)msg.GetBuffer(), msg.GetSize()); // Send to UDP
}
else if (head == "MENU")
{
sendBUF((char*)msg.GetBuffer(), msg.GetSize()); // Send to UDP
}
else if (head == "SOUN")
{
sendBUF((char*)msg.GetBuffer(), msg.GetSize()); // Send to UDP
}
else if ((head == "FAIL") || (head == "RECO"))
{
sendBUF((char*)msg.GetBuffer(), msg.GetSize()); // Send to UDP
}
else if (head == "PAPT")
{
sendBUF((char*)msg.GetBuffer(), msg.GetSize()); // Send to UDP
}
else if ((head == "VEHN") || head == "VEH1" || (head == "VEHA"))
{
sendBUF((char*)msg.GetBuffer(), msg.GetSize()); // Send to UDP
}
else if ((head == "OBJN") || (head == "OBJL"))
{
sendBUF((char*)msg.GetBuffer(), msg.GetSize()); // Send to UDP
}
else if ((head == "GSET") || (head == "ISET"))
{
sendBUF((char*)msg.GetBuffer(), msg.GetSize()); // Send to UDP
}
else if (head == "BOAT")
{
sendBUF((char*)msg.GetBuffer(), msg.GetSize()); // Send to UDP
}
else
{ //unrecognized header
XPC::Log::FormatLine("[EXEC] ERROR: Command %s not recognized", head);
}
current_connection = -1;
return 0;
}
void sendBUF( char buf[], int buflen)

Binary file not shown.

Binary file not shown.

View File

@@ -101,6 +101,7 @@
<ClInclude Include="..\..\C\src\xplaneConnect.h" />
<ClInclude Include="..\Drawing.h" />
<ClInclude Include="..\Log.h" />
<ClInclude Include="..\Message.h" />
<ClInclude Include="..\UDPSocket.h" />
<ClInclude Include="..\xpcPluginTools.h" />
</ItemGroup>
@@ -108,6 +109,7 @@
<ClCompile Include="..\..\C\src\xplaneConnect.c" />
<ClCompile Include="..\Drawing.cpp" />
<ClCompile Include="..\Log.cpp" />
<ClCompile Include="..\Message.cpp" />
<ClCompile Include="..\UDPSocket.cpp" />
<ClCompile Include="..\XPCPlugin.cpp" />
<ClCompile Include="..\xpcPluginTools.cpp" />

View File

@@ -30,6 +30,9 @@
<ClInclude Include="..\UDPSocket.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\Message.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\C\src\xplaneConnect.c">
@@ -50,6 +53,9 @@
<ClCompile Include="..\UDPSocket.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Message.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Library Include="..\SDK\Libraries\Win\XPLM.lib">

View File

@@ -36,22 +36,6 @@ XPLMDataRef XPLMDataRefs[134][8];
XPLMDataRef multiplayer[20][17];
XPLMDataRef AIswitch;
void readMessage(XPC::UDPSocket* socket, struct XPCMessage * pMessage)
{
pMessage->msglen = socket->Read((std::uint8_t*)pMessage->msg, 5000, &pMessage->recvaddr);
if ( pMessage->msglen <= 0 ) // No Message
{
pMessage->msg[0] = 0;
}
else
{
strncpy( pMessage->head, pMessage->msg, 4 );
}
return;
}
void buildXPLMDataRefs()
{
int i, j;
@@ -300,146 +284,6 @@ unsigned short getIP(struct sockaddr recvaddr, char *IP)
// DEBUGGING TOOLS
// --------------------------------
int printBufferToLog(struct XPCMessage & msg)
{
// Prints the entire buffer to the log (for debugging)
char logmsg[350] = "[DEBUG]";
int i;
for (i=0;i<msg.msglen && strlen(logmsg)<(sizeof(logmsg)-4);i++)
{
sprintf(logmsg,"%s %hhu",logmsg,msg.msg[i]);
}
XPC::Log::WriteLine(logmsg);
memset(logmsg,0,strlen(logmsg));
sprintf(logmsg,"[%s-DEBUG](%i)",msg.head,msg.msg[4]);
//Switch for header
if (strncmp(msg.head,"CONN",4)==0)
{// Header = CONN (Connection)
XPC::Log::WriteLine(logmsg);
}
else if (strncmp(msg.head,"SIMU",4)==0)
{// Header = SIMU
sprintf(logmsg,"%s %i",logmsg,msg.msg[5]);
XPC::Log::WriteLine(logmsg);
}
else if (strncmp(msg.head,"POSI",4)==0)
{// Header = POSI (Position)
float position[8] = {0.0};
float pos[3],orient[3];
short aircraft = 0;
float gear = -1.0;
// UPDATE LOG
aircraft = parsePOSI(msg.msg,position,6, &gear);
//ADD AIRCRAFT HANDLING
// Position
memcpy(pos,position,3*sizeof(float));
// Orientation
memcpy(orient,&position[3],3*sizeof(float));
sprintf(logmsg,"%s %i (%f %f %f) (%f %f %f) %f",logmsg,aircraft,pos[0],pos[1],pos[2],orient[0],orient[1],orient[2],gear);
XPC::Log::WriteLine(logmsg);
}
else if (strncmp(msg.head,"CTRL",4)==0)
{// Header = CTRL (Control)
xpcCtrl ctrl = parseCTRL(msg.msg);
sprintf(logmsg,"%s (%f %f %f) %f %hhi %f",logmsg, ctrl.pitch, ctrl.roll, ctrl.yaw, ctrl.throttle, ctrl.gear, ctrl.flaps);
XPC::Log::WriteLine(logmsg);
}
else if (strncmp(msg.head,"WYPT",4)==0)
{// Header = WYPT (Waypoint Draw)
XPC::Log::WriteLine(logmsg);
}
else if (strncmp(msg.head,"GETD",4)==0)
{// Header = GETD (Data Request)
char DREF[100];
int counter = 6;
XPC::Log::WriteLine(logmsg);
memset(logmsg,0,strlen(logmsg));
for (i=0;i<msg.msg[5];i++)
{
memcpy(DREF,&msg.msg[counter+1],msg.msg[counter]);
sprintf(logmsg,"\t#%i/%i (size:%i) %s",i+1,msg.msg[5],msg.msg[counter],DREF);
XPC::Log::WriteLine(logmsg);
memset(logmsg,0,strlen(logmsg));
memset(DREF,0,sizeof(DREF));
counter += msg.msg[counter]+1;
}
}
else if (strncmp(msg.head,"DREF",4)==0)
{// Header = DREF (By Data Ref) (this is slower than DATA)
char DREF[100]={0};
float tmp;
XPC::Log::WriteLine(logmsg);
memset(logmsg,0,strlen(logmsg));
memcpy(DREF,&msg.msg[6],msg.msg[5]);
sprintf(logmsg,"-\tDREF (size %i)= %s",msg.msg[5],DREF);
XPC::Log::WriteLine(logmsg);
memset(logmsg,0,strlen(logmsg));
sprintf(logmsg,"-\tValues(Size %i)=",msg.msg[6+msg.msg[5]]);
for (i=0;i<msg.msg[6+msg.msg[5]];i++)
{
memcpy(&tmp,&msg.msg[7+msg.msg[5]+sizeof(float)*i],sizeof(float));
sprintf(logmsg,"%s %f",logmsg,tmp);
}
XPC::Log::WriteLine(logmsg);
}
else if (strncmp(msg.head,"VIEW",4)==0)
{// Header = VIEW (Change View)
XPC::Log::WriteLine(logmsg);
}
else if (strncmp(msg.head,"DATA",4)==0)
{// Header = DATA (UDP Data)
int j;
short totalColumns = ((msg.msglen-5)/36);
float dataRef[30][9];
sprintf(logmsg,"%s (%i lines)",logmsg,totalColumns);
XPC::Log::WriteLine(logmsg);
memset(logmsg,0,strlen(logmsg));
totalColumns = parseDATA(msg.msg, msg.msg[4], dataRef);
for (i=0; i<totalColumns; i++)
{
sprintf(logmsg,"\t#%i: ",(int) dataRef[i][0]);
for (j=1; j<9; j++)
{
sprintf(logmsg,"%s %f",logmsg,dataRef[i][j]);
if (dataRef[i][j]!=dataRef[i][j]) sprintf(logmsg,"%s (not a number)",logmsg);
}
XPC::Log::WriteLine(logmsg);
memset(logmsg,0,strlen(logmsg));
}
}
else
{
return -1; //unrecognized header
}
return 0;
}
int test(const char *buffer)
{

View File

@@ -19,17 +19,6 @@
extern XPLMDataRef multiplayer[20][17];
extern XPLMDataRef AIswitch;
struct XPCMessage
{
short connectionID;
char head[5];
char msg[5000];
int msglen;
struct sockaddr recvaddr;
};
void readMessage(XPC::UDPSocket* socket, struct XPCMessage * pMessage);
void buildXPLMDataRefs(void);
int almostequal(float arg1, float arg2, float tol);
@@ -39,8 +28,6 @@
int test(int buffer);
unsigned short getIP(struct sockaddr recvaddr, char *IP);
int printBufferToLog(struct XPCMessage & msg);
int fmini(int a, int b);