| responseParser.js | |
|---|---|
| Author: H.Ristaumethods for parsing the DB response. requires:functions: | var rowParser = require(__dirname + '/rowParser.js');
var responseParser =  function () {
  
 | 
| internal vars for the parsing result | 
  this.error = {sqlErrNo: null, message: ''},
  this.result = new Array(),
  this.isConnected = null
};
var p = responseParser.prototype;
 | 
| function parseErrorMessageParameter:
 parsing of an error message | 
p.parseErrorMessage= function (data, offset) {
  this.error.sqlErrNo = data.readUInt32LE(offset, false);
  offset+=4;
  
 | 
| ignor state | 
  offset++;
  
 | 
| ignor security level | 
  offset++;
  var messageLen = data.readUInt16LE(offset, false)*2;
  offset+=2;
  this.error.message = data.toString('ucs2', offset, offset+messageLen);
  console.log(this.error);
  
}
 | 
| function parseInfoMessageAppears by using print statements in SQL-Batch or procedure Parameter:
 Parsing/ignoring of an info message. | 
p.parseInfoMessage= function (data, offset) {
  var messageLen = data.readUInt16LE(offset, false);
  offset+=2;
  
 | 
| calculate the new offset and return it | 
  return (offset+messageLen);
}
 | 
| function parseLoginResponseParameter:
 return
 Parsing the response of a login request. Sets the isConnected flag in according to the parsing result | 
p.parseLoginResponse = function (data,len){
  var notLastDone = true;
  var offset = 8;
  var token = data[offset];
  var tokenLen = data.readUInt16LE(++offset, false);
  offset+=2;
  
 | 
| loop over data buffer while token isn't done and not end of data | 
  while ( notLastDone && (offset <= len-3) ) {
    
 | 
| token handling | 
    switch (token) {
      
 | 
| environment change | 
      case 0xe3:
        break;
      
 | 
| error message | 
      case 0xaa:
        this.isConnected = false;
        this.parseErrorMessage(data, offset);
        return (false);
        break;
      
 | 
| info message | 
      case 0xab:
        break;
      
 | 
| login acknowlegment | 
      case 0xad:
        this.isConnected = true;
        return (true);
      
 | 
| done | 
      case 0xfd:
        var ret = this.parseDone(data, offset-2);
        
 | 
| done token has a const length | 
        tokenLen = 6;;
        notLastDone = ret.moreDone;
        break;
    }
    if ( notLastDone && (offset <= len-3) ) {
      
 | 
| read next token and calculate offset | 
      offset+=tokenLen;
      token = data[offset];
      tokenLen = data.readUInt16LE(++offset, false);
      offset+=2;
    }
  }
};
 | 
| function parseDoneParameter:
 return
 Parsing a done message. Checks if this is the last done message. | 
p.parseDone = function(data, offset)
{
  var fdState = data.readUInt16LE(offset, false);
  var ret = { moreDone: false,
              offset: offset }
  ret.offset+=8;
  ret.moreDone = (fdState & 0x01) == 0x01;
  return (ret);
}
 | 
| function parseResultSetParameter:
 return
 Parsing a complete resultset. First the meta data (structure) of the resultset will be analysed, after that the data rows will be parsed | 
p.parseResultSet = function (data, offset, resultSetCount){
  var cols = data.readUInt16LE(++offset, false);
  offset+=2;
  offset+=2; //userType
  offset+=2; //flags
  var colType = data[offset];
  var result;
  
 | 
| initialize the object for the resultset | 
  var resultSet = { colData: new Array(),  rows: new Array(), outputParams: new Array(), returnStatus: null };
  this.result[resultSetCount] = resultSet;
  
 | 
| initialize the object for the return | 
  var ret = { offset:0, resultSetCount:0, nextResultSet: false};
  
 | 
| parse meta data | 
  for  ( var i=0; i<cols; i++){ //parse all cols
    result = rowParser.parseColMetaData(colType, data, offset);
    offset = result.newOffset;
    this.result[resultSetCount].colData[i] = { value: result.value,
                                               precision : result.precision ? result.precision : null,
                                               scale: result.scale ? result.scale : null,
                                               type: colType };
    offset+=4;
    colType = data[offset];
  }
    offset-=4;
  var token = data[offset];
  var rowParsed = false;
  
 | 
| parse data rows | 
  while (!rowParsed) {
    
 | 
| token handling | 
    switch (token) {
      
 | 
| error message | 
      case 0xaa:
        var tokenLen = data.readUInt16LE(++offset, false);
        this.parseErrorMessage(data, offset);
        rowParsed = false;
      break;
      
 | 
| info message | 
      case 0xab:
        offset = this.parseInfoMessage(data, offset);
        rowParsed = false;
        token=data[offset];
        offset++;
      break;
      
 | 
| next result set | 
      case 0x81:
        ret.offset = --offset;
        ret.resultSetCount = ++resultSetCount;
        ret.nextResultSet = true;
        rowParsed = true;
      break;
      
 | 
| row | 
      case 0xd1:
        var res = rowParser.parseRowData(this.result[resultSetCount].colData, data, offset);
        this.result[resultSetCount].rows = res.rows;
        if (res.error) {
          this.error = res.error;
        }
        offset = res.offset;
        token=data[offset];
        offset++;
      break;
      
 | 
| returnStatus | 
      case 0x79:
        this.result[resultSetCount].returnStatus = data.readInt32LE(offset, false);
        offset+=4;
        token=data[offset];
        offset++;
      break;
      
 | 
| Order Token will be ignored | 
      case 0xA9:
        var tokenLen = data.readUInt16LE(++offset, false);
        offset +=2;
        offset += tokenLen;
        token = data[offset];
      break;
      
 | 
| doneInProc | 
      case 0xff:
      
 | 
| doneProc | 
      case 0xfe:
        //only info
        var doneRes = this.parseDone(data, offset);
        offset = doneRes.offset;
        if(doneRes.moreDone){
          token=data[offset];
          offset++;
        }
        else{
          rowParsed = true;
        }
      break;
      
 | 
| done | 
      case 0xfd:
        var fdState = data.readUInt16LE(offset, false);
        offset+=2;
        if( (fdState & 0x01) == 0x01) {
          offset += 6;
          token=data[offset];
          offset++;
        }
        else {
          rowParsed = true;
        }
      break;
      //empty resultSet: fdState=1 and Token=0x00
      case 0x00:
        rowParsed = true;
      break;
      
 | 
| unknown token | 
      default:
        console.log('token ignored:'+token);
        var tokenLen = data.readUInt16LE(++offset, false);
        offset +=2;
        offset += tokenLen;
        token = data[offset];
    }
  }
  return (ret);
}
 | 
| function parseSqlBatchResponseParameter:
 Parsing a response of a sql batch request. | 
p.parseSqlBatchResponse = function (data,len){
  var offset = 8;
  var token = data[offset];
  var resultSetCount = 0;
  
 | 
| loop over data buffer while not end of data | 
  while ( (offset <= len-3) && (offset > 0) ) {
    switch (token) {
      case 0xe3:
        tokenLen = data.readUInt16LE(++offset, false);
        offset+=2;
        offset+=tokenLen;
        token = data[offset];
      break;
      
 | 
| returnStatus | 
      case 0x79:
        var resultSet = { colData: new Array(),  rows: new Array(), outputParams: new Array(), returnStatus: null };
        this.result[resultSetCount] = resultSet;
        this.result[resultSetCount++].returnStatus = data.readInt32LE(++offset, false);
        offset+=4;
        token=data[++offset];
      break;
      
 | 
| resultSet | 
      case 0x81:
        ret = this.parseResultSet(data, offset, resultSetCount);
        offset = ret.offset;
        resultSetCount = ret.resultSetCount;
        if (ret.nextResultSet) {
          token = data[offset];
        }
      break;
      
 | 
| info message | 
      case 0xab:
        tokenLen = data.readUInt16LE(++offset, false);
        offset+=2;
        offset+=tokenLen;
        token = data[offset];
      break;
      
 | 
| error message | 
      case 0xaa:
        var tokenLen = data.readUInt16LE(++offset, false);
        var ret;
        offset+=2;
        this.parseErrorMessage(data, offset);
        return (true);
      break;
      
 | 
| login acknowlegment | 
      case 0xad:
        this.isConnected = true;
        return (true);
      break;
      
 | 
| doneInProc | 
      case 0xff:
      
 | 
| done | 
      case 0xfd:
        var fdState = data.readUInt16LE(++offset, false);
        offset+=2;
        if( (fdState & 0x01) == 0x01) {
          offset += 6;
          token=data[offset];
        }
        else {
          offset += 6;
          token=data[offset];
        }
      break;
      default:
        token=data[offset];
        offset++;
      break;
    }
  } //while
};
 | 
| function concatPacketsParameter:
 return
 concat the chunks of a response to a big parsable packet. | 
p.concatPackets = function(packetArr) {
  var hLen = 0;
  
 | 
| calculate the length of hBuf | 
  for (var i=0; i < packetArr.length; i++) {
    hLen += packetArr[i].length;
  }
  hLen -= (packetArr.length-1)*8;
  
 | 
| init hBuf | 
  var hBuf = new Buffer(hLen);
  var hOffset = 0;
  
 | 
| copy the first chunk komplete with header | 
  packetArr[0].copy(hBuf, hOffset );
  hOffset += packetArr[0].length;
  
 | 
| copy all other chunks without header | 
  for (var i=1; i < packetArr.length; i++) {
    packetArr[i].copy(hBuf, hOffset, 8 );
    hOffset += packetArr[i].length-8;
  }
  return (hBuf);
}
 | 
| function parseDbResponseParameter:
 return
 General method for parsing all DB responses. Wrapper for parseLoginResponse and parseSqlBatchResponse | 
p.parseDbResponse = function (packetArr){
  
 | 
| read the headerType | 
  var headerType = packetArr[0][0];
  
 | 
| read the packet length | 
  var len = packetArr[0].readUInt16BE(2, true);
  
 | 
| read the token | 
  var token = packetArr[0][8];
  var ret = true;
  
 | 
| accept only packets with headerType=4 | 
  if (headerType == 0x04) {
    
 | 
| token handling | 
    switch (token) {
      
 | 
| LoginResponse | 
      case 0xad :
      case 0xe3 :
        if (this.isConnected){
          ret = this.parseSqlBatchResponse(packetArr[0], packetArr[0].length);
        } else {
          ret = this.parseLoginResponse(packetArr[0],len);
        }
      break;
      
 | 
| error message | 
      case 0xaa:
        var offset = 8;
        var tokenLen = packetArr[0].readUInt16LE(++offset, false);
        offset+=2;
        this.parseErrorMessage(packetArr[0], offset);
        ret = false;
        break;
      
 | 
| done | 
      case 0xfd :
      case 0xff :
      
 | 
| SqlBatchResponse | 
      case 0x81 :
      
 | 
| returnStatus | 
      case 0x79 :
        var hBuf = this.concatPackets(packetArr);
      //console.log('responseData:'+hBuf.length);
        ret = this.parseSqlBatchResponse(hBuf, hBuf.length);
      break;
      
 | 
| Unrecognized token | 
      default:
        throw new Error("Unrecognized token " + token);
    }
  }
  else
  {
    throw new Error("Unrecognized headerType " + headerType);
  }
  
 | 
| copying results to returnig object | 
  var parseResult = { result : this.result,
                      error : this.error,
                      isConnected : this.isConnected,
                      ret : ret
                    };
  return (parseResult);
};
module.exports = responseParser;
 |