Skip to content

Commit

Permalink
Merge pull request #99 from cbcrc:feat/support_no_messageId
Browse files Browse the repository at this point in the history
feat: support for no messageId in non-strict mode
  • Loading branch information
nytamin committed Apr 2, 2024
2 parents 8d6e3ee + 8e2743f commit 6795ef6
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 9 deletions.
9 changes: 6 additions & 3 deletions packages/connector/src/MosConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ export class MosConnection extends EventEmitter implements IMosConnection {
this._conf.mosID,
connectionOptions.primary.timeout,
connectionOptions.primary.heartbeatInterval,
this._debug
this._debug,
this.mosTypes.strict
)
let secondary = null
this._ncsConnections[connectionOptions.primary.host] = primary
Expand Down Expand Up @@ -128,7 +129,8 @@ export class MosConnection extends EventEmitter implements IMosConnection {
this._conf.mosID,
connectionOptions.secondary.timeout,
connectionOptions.secondary.heartbeatInterval,
this._debug
this._debug,
this.mosTypes.strict
)
this._ncsConnections[connectionOptions.secondary.host] = secondary
secondary.on('rawMessage', (type: string, message: string) => {
Expand Down Expand Up @@ -459,7 +461,8 @@ export class MosConnection extends EventEmitter implements IMosConnection {
this._conf.mosID,
undefined,
undefined,
this._debug
this._debug,
this.mosTypes.strict
)
this._ncsConnections[remoteAddress] = primary

Expand Down
59 changes: 59 additions & 0 deletions packages/connector/src/__tests__/Profile0-non-strict.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {
checkMessageSnapshot,
clearMocks,
decode,
DEFAULT_TIMEOUT,
delay,
doBeforeAll,
encode,
getMessageId,
Expand Down Expand Up @@ -104,6 +106,41 @@ describe('Profile 0 - non strict', () => {
expect(socketMockUpper).toBeTruthy()
expect(serverSocketMockLower).toBeTruthy()
})
test('send heartbeats - no messageId', async () => {
socketMockLower.setAutoReplyToHeartBeat(false) // Handle heartbeat manually
socketMockUpper.setAutoReplyToHeartBeat(false) // Handle heartbeat manually

const heartbeatCount = {
upper: 0,
lower: 0,
}

const mockReply = (portType: 'upper' | 'lower') => {
return (data: any) => {
const str = decode(data)

if (str.match(/<heartbeat/)) {
const repl = getXMLReply('', xmlData.heartbeat)
heartbeatCount[portType]++
return encode(repl)
} else throw new Error('Mock: Unhandled message: ' + str)
}
}

for (let i = 0; i < 100; i++) {
socketMockUpper.mockAddReply(mockReply('upper'))
socketMockLower.mockAddReply(mockReply('lower'))
}

// During this time, there should have been sent a few heartbeats to the server:
await delay(DEFAULT_TIMEOUT * 4.5)
expect(heartbeatCount.upper).toBeGreaterThanOrEqual(4)
expect(heartbeatCount.lower).toBeGreaterThanOrEqual(4)
expect(mosDevice.getConnectionStatus()).toMatchObject({ PrimaryConnected: true })

socketMockLower.setAutoReplyToHeartBeat(true) // reset
socketMockUpper.setAutoReplyToHeartBeat(true) // reset
})
test('requestMachineInfo - missing <time>', async () => {
// Prepare mock server response:
const mockReply = jest.fn((data) => {
Expand Down Expand Up @@ -152,6 +189,28 @@ describe('Profile 0 - non strict', () => {
expect(returnedMachineInfo).toMatchObject(replyMessage)
// expect(returnedMachineInfo.opTime).toBeUndefined()
})
test('requestMachineInfo - empty <time>, no messageId', async () => {
// Prepare mock server response:
const mockReply = jest.fn((_data) => {
const replyMessage = xmlData.machineInfo.replace(/<time>.*<\/time>/, '<time></time>')
const repl = getXMLReply('', replyMessage)
return encode(repl)
})
socketMockLower.mockAddReply(mockReply)
if (!xmlApiData.mosObj.ID) throw new Error('xmlApiData.mosObj.ID not set')

const returnedMachineInfo: IMOSListMachInfo = await mosDevice.requestMachineInfo()
expect(mockReply).toHaveBeenCalledTimes(1)
const msg = decode(mockReply.mock.calls[0][0])
expect(msg).toMatch(/<reqMachInfo\/>/)
checkMessageSnapshot(msg)

const replyMessage = { ...xmlApiData.machineInfoReply }
replyMessage.time = mosTypes.mosTime.fallback()

expect(returnedMachineInfo).toMatchObject(replyMessage)
// expect(returnedMachineInfo.opTime).toBeUndefined()
})
test('requestMachineInfo - bad formatted <time>', async () => {
// Prepare mock server response:
const mockReply = jest.fn((data) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ exports[`Profile 0 - non strict requestMachineInfo - empty <time> 1`] = `
</mos>"
`;
exports[`Profile 0 - non strict requestMachineInfo - empty <time>, no messageId 1`] = `
"<mos>
<ncsID>their.mos.id</ncsID>
<mosID>our.mos.id</mosID>
<messageID>xx</messageID>
<reqMachInfo/>
</mos>"
`;
exports[`Profile 0 - non strict requestMachineInfo - missing <opTime> 1`] = `
"<mos>
<ncsID>their.mos.id</ncsID>
Expand Down
4 changes: 2 additions & 2 deletions packages/connector/src/__tests__/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ export function getXMLReply(
ourMosId?: string,
theirMosId?: string
): string {
//add field only if messageId exist
if(messageId) messageId = '<messageID>' + messageId + '</messageID>'
return (
'<mos>' +
'<mosID>' +
Expand All @@ -128,9 +130,7 @@ export function getXMLReply(
'<ncsID>' +
(theirMosId || 'their.mos.id') +
'</ncsID>' +
'<messageID>' +
messageId +
'</messageID>' +
content +
'</mos>\r\n'
)
Expand Down
14 changes: 12 additions & 2 deletions packages/connector/src/connection/NCSServerConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class NCSServerConnection extends EventEmitter implements INCSServerConne
private _timeout: number
private _mosID: string
private _debug: boolean
private _strict = false
private _disposed = false

private _clients: { [clientID: string]: ClientDescription } = {}
Expand All @@ -45,7 +46,8 @@ export class NCSServerConnection extends EventEmitter implements INCSServerConne
mosID: string,
timeout: number | undefined,
heartbeatsInterval: number | undefined,
debug: boolean
debug: boolean,
strict: boolean
) {
super()
this._id = id
Expand All @@ -55,13 +57,21 @@ export class NCSServerConnection extends EventEmitter implements INCSServerConne
this._mosID = mosID
this._connected = false
this._debug = debug ?? false
this._strict = strict ?? false
}
get timeout(): number {
return this._timeout
}
/** Create a MOS client, which talks to */
createClient(clientID: string, port: number, clientDescription: ConnectionType, useHeartbeats: boolean): void {
const client = new MosSocketClient(this._host, port, clientDescription, this._timeout, this._debug)
const client = new MosSocketClient(
this._host,
port,
clientDescription,
this._timeout,
this._debug,
this._strict
)
this.debugTrace('registerOutgoingConnection', clientID)

this._clients[clientID] = {
Expand Down
42 changes: 40 additions & 2 deletions packages/connector/src/connection/mosSocketClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class MosSocketClient extends EventEmitter {
private _reconnectDelay = 3000
private _reconnectAttempts = 0
private _debug: boolean
private _strict: boolean

private _description: string
private _client: Socket | undefined
Expand Down Expand Up @@ -48,13 +49,14 @@ export class MosSocketClient extends EventEmitter {
private messageParser: MosMessageParser

/** */
constructor(host: string, port: number, description: string, timeout: number, debug: boolean) {
constructor(host: string, port: number, description: string, timeout: number, debug: boolean, strict: boolean) {
super()
this._host = host
this._port = port
this._description = description
this._commandTimeout = timeout || DEFAULT_COMMAND_TIMEOUT
this._debug = debug ?? false
this._strict = strict ?? false

this.messageParser = new MosMessageParser(description)
this.messageParser.debug = this._debug
Expand Down Expand Up @@ -390,7 +392,7 @@ export class MosSocketClient extends EventEmitter {
}

private _handleMessage(parsedData: any, messageString: string) {
const messageId = parsedData.mos.messageID
const messageId = this._getMessageId(parsedData, messageString)
if (messageId) {
const sentMessage = this._sentMessage || this._lingeringMessage
if (sentMessage) {
Expand Down Expand Up @@ -441,6 +443,42 @@ export class MosSocketClient extends EventEmitter {
this.processQueue()
}

private _getMessageId(parsedData: any, messageString: string): string | undefined {
// If there is a messageID, just return it:
if (typeof parsedData.mos.messageID === 'string' && parsedData.mos.messageID !== '')
return parsedData.mos.messageID

if (this._strict) {
this.debugTrace(`Reply with no messageId: ${messageString}. Try non-strict mode.`)
return undefined
} else {
// In non-strict mode: handle special cases:

// <heartbeat> response doesn't contain messageId (compliant with MOS version 2.8)
// we can assume it's the same as our sent message:
if (
this._sentMessage &&
this._sentMessage.msg.toString().search('<heartbeat>') >= 0 &&
parsedData.mos.heartbeat
) {
return `${this._sentMessage.msg.messageID}`
}

// <reqMachInfo> response doesn't contain messageId (compliant with MOS version 2.8)
// we can assume it's the same as our sent message:
if (
this._sentMessage &&
this._sentMessage.msg.toString().search('<reqMachInfo/>') >= 0 &&
parsedData.mos.listMachInfo
) {
return `${this._sentMessage.msg.messageID}`
} else {
this.debugTrace(`Invalid reply with no messageId in non-strict mode: ${messageString}`)
return undefined
}
}
}

/** */
private _onError(error: Error) {
// dispatch error!!!!!
Expand Down

0 comments on commit 6795ef6

Please sign in to comment.