jspurefix
Version:
pure node js fix engine
89 lines (81 loc) • 3.88 kB
text/typescript
import { AsciiChars, AsciiEncoder, TimeFormatter } from '../../buffer/ascii'
import { MsgTransmitter } from '../msg-transmitter'
import { ILooseObject } from '../../collections/collection'
import { IContainedSet } from '../../dictionary/contained'
import { MessageDefinition } from '../../dictionary/definition'
import { IJsFixConfig } from '../../config'
import { IStandardHeader } from '../../types/FIX4.4/repo'
import { inject, injectable } from 'tsyringe'
import { DITokens } from '../../runtime/di-tokens'
import { ElasticBuffer } from '../../buffer'
export class AsciiMsgTransmitter extends MsgTransmitter {
public msgSeqNum: number
public time: Date
private readonly header: IContainedSet | undefined
private readonly trailer: IContainedSet | undefined
constructor ( public readonly config: IJsFixConfig) {
super(config.sessionContainer.resolve<ElasticBuffer>(DITokens.TransmitBuffer), config.definitions, config.description)
this.msgSeqNum = (config.description.LastSentSeqNum ?? 0) + 1 // adding 1 as this the next sequence # to use.
const buffer = this.buffer
const tf: TimeFormatter = new TimeFormatter(buffer)
this.encoder = new AsciiEncoder(buffer, config.definitions, tf,
config.delimiter ?? AsciiChars.Soh,
config.logDelimiter ?? AsciiChars.Pipe)
const components = config.definitions.component
this.header = components.get('StandardHeader')
this.trailer = components.get('StandardTrailer')
}
private checksum (): number {
const buffer = this.buffer
const encoder: AsciiEncoder = this.encoder as AsciiEncoder
let checksum: number = buffer.sum()
if (encoder.delimiter !== encoder.logDelimiter) {
const changes = encoder.tags.nextTagPos
checksum -= changes * encoder.logDelimiter
checksum += changes * encoder.delimiter
}
checksum = checksum % 256
return checksum
}
public encodeMessage (msgType: string, obj: ILooseObject): (ILooseObject | null) {
const encoder: AsciiEncoder = this.encoder as AsciiEncoder
const factory = this.config.factory
let headerProps: Partial<IStandardHeader> = {}
const { StandardHeader, ...bodyProps } = obj
if (StandardHeader) {
const { BeginString, BodyLength, MsgType, SenderCompID, SendingTime, TargetCompID, TargetSubID, ...hp } = StandardHeader
headerProps = hp // pick up any optional applied by application
headerProps.OrigSendingTime = SendingTime // when first sent
}
const msgSeqNum = this.msgSeqNum
const sendingTime = this.time || new Date()
const hdr: ILooseObject | null = factory?.header(msgType, msgSeqNum, sendingTime, headerProps) ?? null
// Only increment sequence number if this is not a duplicate message.
if (!hdr) return null
if (!headerProps.PossDupFlag) {
this.msgSeqNum++
}
const buffer = this.buffer
buffer.reset()
const msgDef: MessageDefinition | undefined = this.definitions.message.get(msgType)
if (!msgDef) {
this.emit('error', new Error(`ascii transmitter cannot find definition for ${msgType}`))
return null
}
const headerName: string = this.header?.name ?? 'header'
const trailerName: string = this.trailer?.name ?? 'trailer'
encoder.encode(hdr, headerName)
encoder.encode(bodyProps, msgDef.name)
const lenPos = encoder.bodyLengthPos
const bodyLength: number = Math.max(4, this.config.description.BodyLengthChars ?? 7)
const len = buffer.getPos() - encoder.msgTypePos
buffer.patchPaddedNumberAtPos(lenPos, len, bodyLength)
const checksum: number = this.checksum()
const trl: ILooseObject | null = factory?.trailer(checksum) ?? null
if (trl) {
encoder.encode(trl, trailerName)
}
return hdr
}
}