[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #1

[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #2

[MQTT / Mosca] Mqtt Packet 에 Command Type 추가하기 (for authentication) #3



흐흐흐 드디어 네번째 글이다

정말 정리하기 넘 힘들군..


그래도 이번엔 드디어 3단계로 넘어간다!!

경축경축!!!



#MQTT Packet Command 추가하기



Authentication 을 위한 Packet 흐름


1. Connect Packet (Device -> Server)

2. Authenticate Packet (Server -> Device)

3. Authenticate Packet (Device -> Server)

4. Connect ACK Packet (Server -> Device)


크으으 이제 무려 3단계다


2단계에서 워낙 삽질을 많이 해서

3단계는 좀 수월하게 할 수 있다.



일단 2단계에서의 접근과 동일하게,

이번엔 Device 에서 Packet 을 generate 하는 부분을 봐야 되니까!

Device 쪽 mqtt 라이브러리를 봐야한다.



#5. mqtt (send)


내가 원하는 건

publish 할 때 처럼!!

그냥 client.authenticate(~) 해서 authenticate 요청을 날려버리는 거다!


그러니 publish function 이 어떻게 정의되어 있는지부터 한번 보자.


(mqtt/lib/client.js)

/**
 * publish - publish <message> to <topic>
 *
 * @param {String} topic - topic to publish to
 * @param {String, Buffer} message - message to publish
 * @param {Object} [opts] - publish options, includes:
 *    {Number} qos - qos level to publish on
 *    {Boolean} retain - whether or not to retain the message
 * @param {Function} [callback] - function(err){}
 *    called when publish succeeds or fails
 * @returns {MqttClient} this - for chaining
 * @api public
 *
 * @example client.publish('topic', 'message');
 * @example
 *     client.publish('topic', 'message', {qos: 1, retain: true});
 * @example client.publish('topic', 'message', console.log);
 */
MqttClient.prototype.publish = function (topic, message, opts, callback) {
  var packet;

  // .publish(topic, payload, cb);
  if ('function' === typeof opts) {
    callback = opts;
    opts = null;
  }

  // Default opts
  if (!opts) {
    opts = {qos: 0, retain: false};
  }

  if (this._checkDisconnecting(callback)) {
    return this;
  }

  packet = {
    cmd: 'publish',
    topic: topic,
    payload: message,
    qos: opts.qos,
    retain: opts.retain,
    messageId: this._nextId()
  };

  switch (opts.qos) {
    case 1:
    case 2:

      // Add to callbacks
      this.outgoing[packet.messageId] = callback || nop;
      this._sendPacket(packet);
      break;
    default:
      this._sendPacket(packet, callback);
      break;
  }

  return this;
};


안녕? publish function 아!

나는 너를 복붙해다가, 겁나 고쳐댈거야!


말 그대로다.

그냥 얘 복붙해서 맘에 들도록 짜깁기하면 된다.

2편에서 generate 할 때 그랬던 것처럼!



짜깁기 하여 완성된 코드▼

MqttClient.prototype.authenticate = function (clientId, username, secretKey, callback) {
  var packet;

  if (this._checkDisconnecting(callback)) {
    return this;
  }

  packet = {
    cmd: 'authenticate',
    keepalive: 1,
    qos: 0,
    retain: false,
    clientId: clientId,
    username: username,
    password: secretKey
  };

  this._sendPacket(packet, callback);

  return this;
};

비교적 매우 심플하다.

왜냐면 별 처리를 안 했거든..


근데 여기서는 packet에 들어갈 정보들만 명시해준 다음,

_sendPacket 이란 function 으로 넘겨준다.


그럼 저 function 이 뭘 하는 앤지 정도는 살펴봐야겠지?

저기서 막 또 cmd case 별로 나눠서 뭔 짓 해주면 어떡해..


보러 가자!

/**
 * _sendPacket - send or queue a packet
 * @param {String} type - packet type (see `protocol`)
 * @param {Object} packet - packet options
 * @param {Function} cb - callback when the packet is sent
 * @api private
 */
MqttClient.prototype._sendPacket = function (packet, cb) {
  if (!this.connected) {
    if (((packet.qos || 0) === 0 && this.queueQoSZero) || packet.cmd !== 'publish') {
      this.queue.push({ packet: packet, cb: cb })
    } else if (packet.qos > 0) {
      this.outgoingStore.put(packet, function (err) {
        if (err) {
          return cb && cb(err)
        }
      })
    } else if (cb) {
      cb(new Error('No connection to broker'))
    }

    return
  }

  // When sending a packet, reschedule the ping timer
  this._shiftPingInterval()

  if (packet.cmd !== 'publish') {
    sendPacket(this, packet, cb)
    return
  }

  switch (packet.qos) {
    case 2:
    case 1:
      storeAndSend(this, packet, cb)
      break
    /**
     * no need of case here since it will be caught by default
     * and jshint comply that before default it must be a break
     * anyway it will result in -1 evaluation
     */
    case 0:
      /* falls through */
    default:
      sendPacket(this, packet, cb)
      break
  }
}


거봐 확인 안 했으면 어쩔뻔 했어


나는 authenticate packet 을 client 가 connect 되기 전에 요청한다.

근데 저 function 에 따르면, connected 상태에 따라 처리해주는 부분이 있어버려서,

제대로 sendPacket 까지 도달하지 못하는 것 같다. (실제로 이 function 을 처리 안 해주고 그냥 돌리면 에러가 난다. 테스트 해봄)



이 코드를 수정하는 데에는 여러가지 방법이 있겠지만,

코드 하나하나 뜯어보고 무슨 의미인지 알아내기엔 이미 삽질을 너무 많이 해서

그냥 가장 간단한 방법으로 고쳤다.

/**
 * _sendPacket - send or queue a packet
 * @param {String} type - packet type (see `protocol`)
 * @param {Object} packet - packet options
 * @param {Function} cb - callback when the packet is sent
 * @api private
 */
MqttClient.prototype._sendPacket = function (packet, cb) {
  if (!this.connected && packet.cmd !== 'authenticate') {
    if (0 < packet.qos || 'publish' !== packet.cmd || this.queueQoSZero) {
      this.queue.push({ packet: packet, cb: cb });
    } else if (cb) {
      cb(new Error('No connection to broker'));
    }

    return;
  }

  // When sending a packet, reschedule the ping timer
  this._shiftPingInterval();

  switch (packet.qos) {
    case 2:
    case 1:
      storeAndSend(this, packet, cb);
      break;
    /**
     * no need of case here since it will be caught by default
     * and jshint comply that before default it must be a break
     * anyway it will result in -1 evaluation
     */
    case 0:
      /* falls through */
    default:
      sendPacket(this, packet, cb);
      break;
  }
};

다른 그림찾기 넘 고난이도일까봐 bold 처리 했다.


저렇게 그냥 단순히 cmd가 authenticate일 경우에는 connected 가 false 여도 다음 로직으로 넘어가도록 해버렸다.



이제 여기까지 하면!!

거의 다 됐는데!!!



이전편처럼 project directory 가 구분되어 있는 사람들은

Deivce project 쪽 mqtt-packet library 에서도 authenticate type packet generate 하는 부분을 다시 추가해줘야 한다.


근데 나만 그런 건지는 모르겠는데,

npm install mosca 로 설치된 Server project 쪽 mqtt-packet library

npm install mqtt 로 설치된 Device project 쪽 mqtt-packet library 가 

코드 구성이 조금 상이하다.


주의할 것.. 아마 버전 차이인 것 같다.


npm install mqtt 로 설치된 mqtt-packet library 에서는 

generate 하는 코드를 mqtt-packet/writeToStream.js 파일에서 찾을 수 있다.



방식이 좀 다르긴 한데

stream 이란 두번째 parameter 가 추가되었다.


그래도 이전에 했던 것처럼 connect 복붙해서 적당히 수정하면 된다.

진짜 똑같이 수정하면 됨!! 괜히 이상한 부분만 안 건드리면..


대신 진짜진짜 주의할 부분이 있다!!!!

나도 원래는 같은 project directory 에서 Server, Device process 둘 다 돌리면서 테스트 하고 분리했었는데,

이걸 안 건드려서 한참 삽질을 또 했다.


(Device side mqtt-packet/constants.js)

/* Authenticate */
protocol.AUTHENTICATE_HEADER = Buffer.from([protocol.codes['authenticate'] << protocol.CMD_SHIFT])

저 파일을 열고 밑으로 스크롤 죽죽 내려보면

HEADER 를 generate 해서 명시해두는 부분이 있는데,

이게 Server side 에서 쓰는 mqtt-packet library 버전이랑 달라서 ㅠㅠ

Server 에서는 그냥 connect function 적당히 수정하면 HEADER parsing 도 같이 처리됐었는데

여기선 따로 저렇게 constants.js 파일에서 관리한다.

꼭꼭 명시해줘야됨..


저걸 모르고 그냥 돌렸더니 세상에

authenticate 형식으로 잘 파싱해놓고

보낼 때 connect로 보내버린다.


저 코드를 constants.js 에 추가해줬다면

다시 writeToStream.js 파일로 돌아와서 복붙해서 짜깁기해둔 authenticate parsing function 에다가

  // Generate header
  stream.write(protocol.AUTHENTICATE_HEADER)

라고 수정해주면 된다.




여기까지하면 device에서 authenticate packet 을 보내는 것도 잘 된다!



Server 에서 저 authenticate packet 을 parsing 해서 받는 부분은

Device 에서 받을 때와 동일한 파일들을 동일하게 수정해주면 된다.

다행히 이 부분은 버전별 차이가 없다 ㅠㅠ 다행






길고 긴 여정이 끝났다!


이렇게 하면 

Mqtt Packet 에 Command Type 추가하기 완료!!!



흐하항행복해

이제 추가한 타입을 멋대로 가져다 쓰면 된다

이벤트도 받고 패킷도 쏘고~~~ 앗싸링!!


아 근데 글 하나 더 있다. ㅎ

+ Recent posts