Skip to content

Commit

Permalink
Token parsing modified to use a ReadableTrackingBuffer, removing the …
Browse files Browse the repository at this point in the history
…need to track the position within the buffer within the token parsing.

This is a complex and wide ranging change, that significantly simplifies token parsing, and makes it easier to read and maintain.
Any attempt to read past the end of the available buffer results in an exception being thrown. This may have a small runtime overhead, but it's worth it because of the simplified code.

Functions added to ReadableTrackingBuffer for more data types.
Functions added to WritableTrackingBuffer for more data types.
Many unit tests converted to use WritableTrackingBuffer, to avoid them needing to track the position withing the buffer.
  • Loading branch information
pekim committed Dec 31, 2011
1 parent 9c97cc6 commit f550296
Show file tree
Hide file tree
Showing 21 changed files with 469 additions and 621 deletions.
59 changes: 14 additions & 45 deletions lib/token/colmetadata-token-parser.coffee
Expand Up @@ -3,82 +3,52 @@
TYPE = require('./data-type').TYPE
sprintf = require('sprintf').sprintf

parser = (buffer, position) ->
startPosition = position

if buffer.length - position < 2
return false
columnCount = buffer.readUInt16LE(position)
position += 2
parser = (buffer) ->
columnCount = buffer.readUInt16LE()

columns = []
for c in [1..columnCount]
if buffer.length - position < 4 + 2 + 1
return false

userType = buffer.readUInt32LE(position)
position += 4

flags = buffer.readUInt16LE(position)
position +=2

typeNumber = buffer.readUInt8(position)
userType = buffer.readUInt32LE()
flags = buffer.readUInt16LE()
typeNumber = buffer.readUInt8()
type = TYPE[typeNumber]
position++


if !type
error = sprintf('Unrecognised data type 0x%02X at offset 0x%04X', typeNumber, (position - 1))
error = sprintf('Unrecognised data type 0x%02X at offset 0x%04X', typeNumber, (buffer.position - 1))
break

#console.log(type)

if type.fixedLength
dataLength = type.dataLength
else if type.variableLength
if buffer.length - position < type.dataLengthLength
return false

switch type.dataLengthLength
when 1
dataLength = buffer.readUInt8(position)
dataLength = buffer.readUInt8()
when 2
dataLength = buffer.readUInt16LE(position)
dataLength = buffer.readUInt16LE()
when 4
dataLength = buffer.readUInt32LE(position)
dataLength = buffer.readUInt32LE()
else
error = "Unrecognised dataLengthLength for type #{type}"
break
position += type.dataLengthLength

if type.hasPrecision
precision = buffer.readUInt8(position)
position++
precision = buffer.readUInt8()
else
precision = undefined

if type.hasScale
scale = buffer.readUInt8(position)
position++
scale = buffer.readUInt8()
else
scale = undefined

if type.hasCollation
if buffer.length - position < 5
return false
collation = Array.prototype.slice.call(buffer, position, position + 5)
position += 5
collation = buffer.readBuffer(5)
else
collation = undefined

if buffer.length - position < 1
return false
colNameLength = buffer.readUInt8(position) * 2
position++

if buffer.length - position < colNameLength
return false
colName = buffer.toString('ucs-2', position, position + colNameLength)
position += colNameLength
colName = buffer.readBVarchar()

columns.push(
userType: userType
Expand All @@ -98,7 +68,6 @@ parser = (buffer, position) ->
else
token =
name: 'COLMETADATA'
length: position - startPosition
event: 'columnMetadata'
columns: columns

Expand Down
39 changes: 13 additions & 26 deletions lib/token/done-token-parser.coffee
Expand Up @@ -10,11 +10,8 @@ STATUS =
ATTN: 0x0020
SRVERROR: 0x0100

parser = (buffer, position) ->
if buffer.length - position < 2
return false
status = buffer.readUInt16LE(position)
position += 2
parser = (buffer) ->
status = buffer.readUInt16LE()

more = !!(status & STATUS.MORE)
sqlError = !!(status & STATUS.ERROR)
Expand All @@ -23,25 +20,15 @@ parser = (buffer, position) ->
attention = !!(status & STATUS.ATTN)
serverError = !!(status & STATUS.SRVERROR)

if buffer.length - position < 2
return false
curCmd = buffer.readUInt16LE(position)
position += 2
curCmd = buffer.readUInt16LE()

if rowCountValid
if buffer.length - position < 8
return false

# If rowCount > 53 bits then rowCount will be incorrect (because Javascript uses IEEE_754 for number representation).
rowCountLow = buffer.readUInt32LE(position)
position += 4
rowCountHigh = buffer.readUInt32LE(position)
position += 4
rowCount = rowCountLow + (0x100000000 * rowCountHigh)
# If rowCount > 53 bits then rowCount will be incorrect (because Javascript uses IEEE_754 for number representation).
rowCount = buffer.readUInt64LE()
if !rowCountValid
rowCount = undefined

token =
name: 'DONE'
length: 2 + 2 + 8
event: 'done'
more: more
sqlError: sqlError
Expand All @@ -50,22 +37,22 @@ parser = (buffer, position) ->
rowCount: rowCount
curCmd: curCmd

doneParser = (buffer, position) ->
token = parser(buffer, position)
doneParser = (buffer) ->
token = parser(buffer)
token.name = 'DONE'
token.event = 'done'

token

doneInProcParser = (buffer, position) ->
token = parser(buffer, position)
doneInProcParser = (buffer) ->
token = parser(buffer)
token.name = 'DONEINPROC'
token.event = 'doneInProc'

token

doneProcParser = (buffer, position) ->
token = parser(buffer, position)
doneProcParser = (buffer) ->
token = parser(buffer)
token.name = 'DONEPROC'
token.event = 'doneProc'

Expand Down
44 changes: 11 additions & 33 deletions lib/token/env-change-token-parser.coffee
Expand Up @@ -27,51 +27,30 @@ types =
17:
name: 'TXN_ENDED'

module.exports = (buffer, position) ->
if buffer.length - position < 3
# Not long enough to contain length and type bytes.
return false

length = buffer.readUInt16LE(position)
position += 2
if (buffer.length - position < length)
# Not long enough for the extracted length
return false

typeNumber = buffer.readUInt8(position)
position++
module.exports = (buffer) ->
length = buffer.readUInt16LE()
typeNumber = buffer.readUInt8()
type = types[typeNumber]

if type
switch type.name
when 'DATABASE', 'LANGUAGE', 'CHARSET', 'PACKET_SIZE'
valueLength = buffer.readUInt8(position) * 2
position++
newValue = buffer.toString('ucs-2', position, position + valueLength)
position += valueLength

valueLength = buffer.readUInt8(position) * 2
position++
oldValue = buffer.toString('ucs-2', position, position + valueLength)
position += valueLength
newValue = buffer.readBVarchar()
oldValue = buffer.readBVarchar()
when 'SQL_COLLATION'
valueLength = buffer.readUInt8(position)
position++
newValue = buffer.slice(position, position + valueLength)
position += valueLength
valueLength = buffer.readUInt8()
newValue = buffer.readBuffer(valueLength)

valueLength = buffer.readUInt8(position)
position++
oldValue = buffer.slice(position, position + valueLength)
position += valueLength
valueLength = buffer.readUInt8()
oldValue = buffer.readBuffer(valueLength)
else
error = "Unsupported ENVCHANGE type #{typeNumber} #{type.name} at offset #{position}"
error = "Unsupported ENVCHANGE type #{typeNumber} #{type.name} at offset #{buffer.position - 1}"

if type.name == 'PACKET_SIZE'
newValue = parseInt(newValue)
oldValue = parseInt(oldValue)
else
error = "Unsupported ENVCHANGE type #{typeNumber}"
error = "Unsupported ENVCHANGE type #{typeNumber} at offset #{buffer.position - 1}"

if error
token =
Expand All @@ -80,7 +59,6 @@ module.exports = (buffer, position) ->
else
token =
name: 'ENVCHANGE'
length: length + 2
type: type.name
event: type.event
oldValue: oldValue
Expand Down
55 changes: 13 additions & 42 deletions lib/token/infoerror-token-parser.coffee
@@ -1,45 +1,16 @@
# s2.2.7.9, s2.2.7.10

parser = (buffer, position) ->
if buffer.length - position < 3
# Not long enough to contain length and type bytes.
return false

length = buffer.readUInt16LE(position)
position += 2
if (buffer.length - position < length)
# Not long enough for the extracted length
return false

number = buffer.readUInt32LE(position)
position += 4

state = buffer.readUInt8(position)
position++

class_ = buffer.readUInt8(position)
position++

valueLength = buffer.readUInt16LE(position) * 2
position += 2
message = buffer.toString('ucs-2', position, position + valueLength)
position += valueLength

valueLength = buffer.readUInt8(position) * 2
position++
serverName = buffer.toString('ucs-2', position, position + valueLength)
position += valueLength

valueLength = buffer.readUInt8(position) * 2
position++
procName = buffer.toString('ucs-2', position, position + valueLength)
position += valueLength

lineNumber = buffer.readUInt32LE(position)
position += 4
parser = (buffer) ->
length = buffer.readUInt16LE()
number = buffer.readUInt32LE()
state = buffer.readUInt8()
class_ = buffer.readUInt8()
message = buffer.readUsVarchar()
serverName = buffer.readBVarchar()
procName = buffer.readBVarchar()
lineNumber = buffer.readUInt32LE()

token =
length: length + 2
number: number
state: state
class: class_
Expand All @@ -48,15 +19,15 @@ parser = (buffer, position) ->
procName: procName
lineNumber: lineNumber

infoParser = (buffer, position) ->
token = parser(buffer, position)
infoParser = (buffer) ->
token = parser(buffer)
token.name = 'INFO'
token.event = 'infoMessage'

token

errorParser = (buffer, position) ->
token = parser(buffer, position)
errorParser = (buffer) ->
token = parser(buffer)
token.name = 'ERROR'
token.event = 'errorMessage'

Expand Down
42 changes: 14 additions & 28 deletions lib/token/loginack-token-parser.coffee
Expand Up @@ -6,39 +6,26 @@ interfaces =
0: 'SQL_DFLT'
1: 'SQL_TSQL'

parser = (buffer, position) ->
if buffer.length - position < 3
# Not long enough to contain length and type bytes.
return false

length = buffer.readUInt16LE(position)
position += 2
if (buffer.length - position < length)
# Not long enough for the extracted length
return false

interfaceNumber = buffer.readUInt8(position)
parser = (buffer) ->
length = buffer.readUInt16LE()

interfaceNumber = buffer.readUInt8()
interface = interfaces[interfaceNumber]
if !interface
error = "Unknown LOGINACK Interface #{interfaceNumber} at offset #{position}"
position++
error = "Unknown LOGINACK Interface #{interfaceNumber} at offset #{buffer.position}"

tdsVersionNumber = buffer.readUInt32BE(position)
tdsVersionNumber = buffer.readUInt32BE()
tdsVersion = versions[tdsVersionNumber]
if !tdsVersion
error = "Unknown LOGINACK TDSVersion #{tdsVersionNumber} at offset #{position}"
position += 4

valueLength = buffer.readUInt8(position) * 2
position++
progName = buffer.toString('ucs-2', position, position + valueLength)
position += valueLength

error = "Unknown LOGINACK TDSVersion #{tdsVersionNumber} at offset #{buffer.position}"

progName = buffer.readBVarchar()

progVersion =
major: buffer.readUInt8(position++)
minor: buffer.readUInt8(position++)
buildNumHi: buffer.readUInt8(position++)
buildNumLow: buffer.readUInt8(position++)
major: buffer.readUInt8()
minor: buffer.readUInt8()
buildNumHi: buffer.readUInt8()
buildNumLow: buffer.readUInt8()

if error
token =
Expand All @@ -47,7 +34,6 @@ parser = (buffer, position) ->
else
token =
name: 'LOGINACK'
length: length + 2
event: 'loginack'
interface: interface
tdsVersion: tdsVersion
Expand Down

0 comments on commit f550296

Please sign in to comment.