페이스북에서 멜로망스 멜로망스 하도 난리길래 들어봤다.



널 잊어보려 계속 노력하고 있어
괜히 더 즐거운 척 시간을 보내는 날 넌 모르겠지

행복한 척하며 지내다 보면 언젠가
너를 잊게 될 거라 믿으며 살고 있는 날 모르겠지

내가 노력해봐도 너 때문이라서 
너는 더 짙어져 가고

혼자 남겨진 시간을 보내는 게 이젠
내겐 너무 두려운 일이 돼버렸단 걸 넌 모르겠지

네 꿈을 꾸는 밤이 오는 게 난 두려워
이젠 내게 밤이 무서워졌다는 걸 너는 모르겠지

내가 노력해봐도 너 때문이라 너는 더
짙어져 가고
너를 그리고
노력할수록 너는 계속 커져만 가겠지

짙어져 가는
너를 붙잡고
생각할수록 너는 계속 커져만 가겠지

널 잊어보려 항상 노력하고 있어
괜히 더 즐거운 척 살아보고 있는 날 넌 모르겠지


솔직히 멜로망스 음악들이 팍 꽂힐 정도로 좋은지는 잘 모르겠다.

그냥 내가 막 좋아할만한 스타일이 아닌 것 같음


근데 이 노래는 좋다.

이것만 계속 듣는 중 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 흐흐


네 꿈을 꾸는 밤이 오는 게 난 두려워

이젠 내게 밤이 무서워졌다는 걸 너는 모르겠지


'Motivation > Music' 카테고리의 다른 글

연애소설 _ 에픽하이 (Epik high)  (0) 2017.10.29
사랑이 다른 사랑으로 잊혀지네 _ 하림  (0) 2017.09.28
서울 이곳은 _ 로이킴  (0) 2017.09.27
달리기 _ 옥상달빛  (0) 2017.09.25
보호해줘 _ 옥상달빛  (0) 2017.09.25

에픽하이 신곡이 나왔따!!

갓픽하이



우리 한때 자석 같았다는 건
한쪽만 등을 돌리면 멀어진다는 거였네.

가진 게 없던 내게
네가 준 상처 덕분에
나도 주인공이 돼보네
in a sad love story.

별 볼 일 없던 내게
네가 준 이별 덕분에
나도 주인공이 돼보네
in a sad love story.
In this sad love story.

잊을 때도 됐는데
기억에 살만 붙어서 미련만 커지네.
되돌아보면
가슴을 찢어지게 하는데
하필 전부 명장면이네.
기억나?
캄캄한 영화관.
너와 내 두 손이 처음 포개졌던 날.
감사했어.
한평생 무수한 걸 짓고 무너뜨렸을 네 손이
내 손에 정착한 것을.

기억나?
네가 가족사를 들려준 밤.
그건 나만 아는 너 한 조각 주고픈 마음.
비가 와 이불 밑에서
넌 내 몸을 지붕 삼아 이 세상의 모든 비를 피했어.
다 기억나, 네가 없는 첫 아침도.
잘 참다 끝내 무너진 그 순간을.
한참 울었거든 샤워실에서,
비누에 붙은 너의 머리카락을 떼며.

가진 게 없던 내게
네가 준 상처 덕분에
나도 주인공이 돼보네
in a sad love story.

별 볼 일 없던 내게
네가 준 이별 덕분에
나도 주인공이 돼보네
in a sad love story.
In this sad love...

가랑비 같은 슬픔이라 위로했지만
여전히 젖은 얼굴로 잠에서 깨.
계절은 무심코 변하고 앞만 보는데
난 서성이네 여태
시간도 버리고 간 기억뿐인 네 옆에.
잊지 못해. 술기운에 이끌려 마주했었던 둘의 첫날밤.
사실 술 한잔 부딪히기도 전에 취했지.
우리가 마신 건 운명인 것 같아.

너무나 빠르게도 깨어난 우리.
한때는 죽고 못 살 것만 같던 날들이
전쟁 같은 매일이 돼.
죽일 듯 서로를 바라보며 맞이하게 된 눈물의 끝.
필연이라 믿던 첫 만남부터
악연이라며 돌아선 마지막까지도
우린 서로 마주 보는 거울이었지.
서로가 던진 눈빛에 깨질 때까지도.

가진 게 없던 내게
네가 준 상처 덕분에
나도 주인공이 돼보네
in a sad love story.

별 볼 일 없던 내게
네가 준 이별 덕분에
나도 한소절 가져보네
in a sad love story.
In this sad love...

나에게만 특별한 얘기.
참 진부하죠?
나만 이런 게 아닌 건 알지만
내가 이런 걸.

줄 게 없었던 내게
남겨준 상처 덕분에
나도 누군가에게 주네
나 닮은 sad story.

다 처음이었던 내게
네가 준 두려움 덕분에
난 영원히
in a sad love story.
In this sad love story.

우리 한때 자석 같았다는 건
한쪽만 등을 돌리면 멀어진다는 거였네.

우리 한때 자석 같았다는 건
한쪽만 등을 돌리면 남이 된다는 거였네.

서울 하늘엔 별 하나 없네.


랩이라 가사가 너무 길다.


그래도 좋다. 아이유 목소리 역시나 너무 좋고

에픽하이는 가사를 정말 너무 잘 쓴다.


나만 이런 게 아닌 건 알지만

내가 이런 걸.


'Motivation > Music' 카테고리의 다른 글

짙어져 _ 멜로망스  (0) 2017.10.29
사랑이 다른 사랑으로 잊혀지네 _ 하림  (0) 2017.09.28
서울 이곳은 _ 로이킴  (0) 2017.09.27
달리기 _ 옥상달빛  (0) 2017.09.25
보호해줘 _ 옥상달빛  (0) 2017.09.25

부제는 

Fragment 에서 호출한 startActivityForResult 의 결과값이 Fragment 의 onActivityResult 로 안 와요 ㅠㅠ 


오늘도 질문 받아주다가 새로운 지식을 얻었다.



일단, 이전 버전의 fragment 는 잘 모르겠고

andorid.support.v4.app.Fragment 를 이용했다면 분명, Fragment에서도 startActivityForResult가 잘 동작해야 한다.


그런데 이게 fragment 내에 정의된 onActivityResult로 안 온다?! 옴마나?!


싶을 때는 해당 fragment의 상위 activity 코드를 열어보자.


만약, Activity에 onActivityResult method가 override 되어있다면 (사실 만약이 아니고 무조건 그럴 거임)

해당 onActivityResult 코드에 super.onActivityResult 가 빠져있는지 확인해보자. (아마 빠져있을 거임 ㅎㅎ)



알아보니 startActivityResult의 결과는,

먼저 Activity 의 onActivityResult 를 거쳐서 fragment 의 onActivityResult 로 넘어오는 것 같다. (노확실 영어못함)

그리고 그 fragment로 넘겨주는 부분이 Activity 의 onActivityResult 에 정의되어 있는 듯.


그러니 fragment 의 상위 activity 가 onActivityResult를 아예 override 하고 있지 않거나, super.onActivityResult 를 제대로 호출해주고 있다면

fragment 까지 잘 넘어가겟지만,

그렇지 않다면 아예 fragment 의 onActivityResult로 넘어갈 기회조차 잃는 것..



method override 하면서 super.~ 를 지울 때에는 막 지우지말고 역할이 뭔지 판단 후에 지우는 습관을 들이는 것이 좋을 것 같다.


는 나도 잘 못하지만 ㅎㅎㅎ

흐흐.. 이 날은 비가 오는데다 차도 빨리 휙 트는 바람에
사진이 이따위로 찍혔다.

덕분에 이 날의 큰물꼬기는 매우 아련했다.
내 카메라에 차마 제대로 담기지 못하고
공기중의 무수한 빗방울 너머로 흐릿하게 남은 큰물꼬기...★

근데 ㄹㅇ루다가 원래 빅피쉬가 어느 위치에 있는지 모르면
이 사진에서는 파란 형태조차 찾기 힘들 것 같다 ㅋㅋㅋㅋ

참나 이딴 사진도 기록이라고,, 흐흫

'Egg > Big Fish' 카테고리의 다른 글

[17.10.30] Daily Big fish  (0) 2017.11.28
[17.10.27] Daily Big fish  (0) 2017.11.28
[17.10.26] Daily Big fish  (0) 2017.11.27
[17.10.25] Daily Big fish  (0) 2017.11.27
[17.10.23] Daily Big fish  (0) 2017.10.26

처음 찍은 빅피쉬
맑은 날 선명한 빅피쉬
앞에 사진 찍고 있는 사람도 있는 것 같다.
선명해서 그런가 당당해보이는 모습이다 (멍멍)

셔틀에서 항상 보이는 빅피쉬를
매일 아침 찍어서 감상을 올릴 거야.

근데 지금까지 찍은 사진 중에 이게 제일 잘 나왔다.. 선명
이 이후론 다 망했다...

'Egg > Big Fish' 카테고리의 다른 글

[17.10.30] Daily Big fish  (0) 2017.11.28
[17.10.27] Daily Big fish  (0) 2017.11.28
[17.10.26] Daily Big fish  (0) 2017.11.27
[17.10.25] Daily Big fish  (0) 2017.11.27
[17.10.24] Daily Big fish  (0) 2017.10.27

[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

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


경축축 이제 진짜 드디어 마지막 글이다!!


이제 Command Type 을 추가하는 게 끝났다.


하지만 거기까지 해서는


Authentication 을 위한 Packet 흐름


1. Connect Packet (Device -> Server)

2. Authenticate Packet (Server -> Device)

3. Authenticate Packet (Device -> Server)

4. Connect ACK Packet (Server -> Device)

에서의 네번째 단계가 아마 안 될 거다...





#Connect ACK Packet 받기


세번째 글에서 stream.pipe 란 애를 요주의라고 했었다.


그니까!! 

3단계까지 무사히 마쳤으니

Server 에서 authenticate packet 을 받아서 처리할 수 있는 상태가 되었다.


그래서 처리를 해주고, authentication 이 완료되면 Connect ACK Packet 을 device 에 보내줘야한다.

그래야 비로소 connection 이 완료되니까!


근데!!!!!! 지금 상태에선 Device 가 그놈의 Connect ACK Packet 을 못 받는다!!!!!


대체 왜인지 알기 위해 진짜 디버깅을 엄청 했다

Wire shark 도 켜서 막 패킷 캡쳐해보고... 별짓을 다했는데

확실해진 건 Server -> Device 로 가는 Connect ACK Packet 이 확실히 보내졌다는 거다.


그럼 문제가 뭘까?

Device 쪽 코드에서 받아서 뭔가 처리를 하는데 생긴 문제일텐데,

stream 에서 데이터를 아예 못받나? 싶어서

client 코드 들어가서 

this.stream.on('data', function(data) {
  console.log(data);
});


해봤더니 넘나 잘 나옴!!

심지어 Wire shark 에서 캡쳐한 Packet 이랑 byte array 도 일치한다 ㅠㅠ


그럼 뭐가 문제일까...

하다보니 문득 위의 this.stream 이 readable stream 이란 것과,

실제로 받은 packet 의 처리는 stream.pipe 에 destination 으로 설정됐던 writable stream 에서 한다는 생각이 스쳐갔다.



그럼 왜인지는 몰라도 저 pipe 가 한번만 동작하고 있는 건 아닐까?

싶어서 코드를 수정해봤다

/*
writable._write = function (buf, enc, done) {
  completeParse = done;
  parser.parse(buf);
  process();
};

this.stream.pipe(writable);
*/

this.stream.on('data', function(buf) {
  completeParse = function(err) {
    if (err) {
      console.log(err);
      return;
    }
  };
  parser.parse(buf);
  process();
});


원래 있던 pipe 관련 코드들을 다 주석처리하고

저렇게 직접 readable stream 에서 읽은 data buffer 를 처리하도록 해줬더니.............

세상에 넘나 잘 된다


대체 왜 pipe 가 한번만 동작하고 막힌 건지는 모르겠다.

어딘가에서 막힌 것 같은데 그것까지 찾아볼 생각도 없고...

기나긴 삽질에 지쳐 그럴 자신도 없다 ㅠㅠ




그래도 이제 authentication 을 위한 모든 준비가 끝났다!

암호화만 하면 된다. ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ



나말고 읽을 사람이 있을지 의문이지만

혹시 이 대장정을 다 읽으셨다면


수고하셨습니다!!

[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 추가하기 완료!!!



흐하항행복해

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

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


아 근데 글 하나 더 있다. ㅎ

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

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



하잇!

아 이어서 정리하려니까 넘 힘들다

언젠가는 내가 이걸 다시 보고 공부를 할까?

왜 자꾸 부질 없는 것 같지... 흑흑


그래도 내가 공부한 거 정리하는 거니까 마저 열씨미 해야징 ㅠㅠ





#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)


2단계를 하고 있었다.

저번 글에서는 #4. mqtt-packet (generate) 에서 모듈 열어보고, authenticate type packet 을 generate 하는 코드 추가해주고 테스트까지 해봤다.


그럼 2단계인 Server -> Device 에서

"Server -" 정도 까지 된 거다.

남은 "> Device" 부분을 이 글에서 볼 거고!



이제 Server 측에서 authenticate type packet 을 보내는 걸 했으니!

Device 에서 받아야한다. 

해당 Packet 을 제대로 받으려면, Authenticate type 을 parsing 할 수 있어야겠지?


Device 쪽 코드에서는 mqtt library 를 쓴다.

그러니 이번에는 mqtt library를 열어볼 거야



#5. mqtt (receive)


Device 에서는 mqtt library를 이용해 client 를 생성하고,Server 에 Connect 한다.


그리고 client 에다 관련 event들을 handling 하는 callback function 들을 달아주는데,

우리는 authenticate type 의 packet 에 대한 event 도 받아야 한다!

그러려면 일단 event를 emit 하는 부분을 찾아봐야 하지 않을까?!


(mqtt/lib/client.js)

MqttClient.prototype._handleConnack = function (packet) {
  var rc = packet.returnCode,
    // TODO: move to protocol
    errors = [
      '',
      'Unacceptable protocol version',
      'Identifier rejected',
      'Server unavailable',
      'Bad username or password',
      'Not authorized'
    ];

  clearTimeout(this.connackTimer);

  if (0 === rc) {
    this.reconnecting = false;
    this.emit('connect', packet);
  } else if (0 < rc) {
    this.emit('error',
        new Error('Connection refused: ' + errors[rc]));
  }
};

그래서 evnet를 emit 하는 부분을 찾아보았다!

저거 말고도 많은데, 일단 connect event 를 emit 하는 부분을 찾아봤더니, 저렇게 되어있었다.


그럼 다음 수순은 뭘까?

저 function 을 호출하는 부분을 찾아야겠지!?


(mqtt/lib/client.js)

MqttClient.prototype._handlePacket = function (packet, done) {
  this.emit('packetreceive', packet);

  switch (packet.cmd) {
    case 'publish':
      this._handlePublish(packet, done);
      break;
    case 'authenticate':
      this.emit('authenticate', packet);
      break;
    case 'puback':
    case 'pubrec':
    case 'pubcomp':
    case 'suback':
    case 'unsuback':
      this._handleAck(packet);
      done();
      break;
    case 'pubrel':
      this._handlePubrel(packet, done);
      break;
    case 'connack':
      this._handleConnack(packet);
      done();
      break;
    case 'pingresp':
      this._handlePingresp(packet);
      done();
      break;
    default:
      // do nothing
      // maybe we should do an error handling
      // or just log it
      break;
  }
};


짜잔!


이제 packet type 에 따라 event를 발생시켜주는 부분을 알았으니,

저기다 authenticate event 를 emit 하는 코드를 추가해주면

client 에서 authenticate event 를 handling 할 수 있을 거야!!


그래서 authenticate case 를 추가해줬다.


이럼 끝일까?


젠젠,, ㅠ


Packet Parsing 이 남았다.

기껏 authenticate packet 을 전송하는 거랑, 해당 event 를 처리할 수 있도록 준비까지 다 마쳐놨으니!

제대로 authenticate packet 타입을 처리해줘야 한다.


parsing 을 어디서 하는지 알아보자.


일단 저 _handlePacket function이 packet 이란 parameter 를 받는 것으로 보아,

어딘가에서 packet을 parsing 해서 _handlePacket function 을 호출해주겠지?



(mqtt/lib/client.js) 요 파일 많이많이 본다

function process () {
  var packet = packets.shift(),
    done = completeParse;
  if (packet) {
    that._handlePacket(packet, process);
  } else {
    completeParse = null;
    done();
  }
}


여기다!

근데 packet 을 packets 라는 변수에서 꺼내오는 걸 보니

packet 은 이 function 이 호출되기 전에 이미 처리되어 packets 에 저장되는 것 같다.

그럼 또 이 process function 이 호출되는 곳을 봐야한다.



(mqtt/lib/client.js)

writable._write = function (buf, enc, done) {
  console.log(buf)
  completeParse = done;
  parser.parse(buf);
  process();
};

this.stream.pipe(writable);

요기 있지!!


여길 보면 stream.pipe 란 애가 있는데, 요주의..

아마 나중에 또 등장할 거다. 한 마지막편쯤에..


어쨌든 저 pipe 란 애의 원래 역할은, 

readable stream 에 pipe 로 writable stream 을 연결해주면,

readable stream 이 읽어들인 data buffer 를 writable stream 으로 토스해준다.

그리고 그 buffer 를 처리해주는 부분이 writable._write 로 정의된 저 function 인 것 같다.

자세한 건 document 참조


여튼 그렇게 읽어들인 buffer 를 처리하는 부분에

내가 찾던 키워드가 있다!


parser.parse(buf)...


저 parser 는 또 뭘까 해서 보니

2편에 나왔던 그 아이다

mqtt-packet..




#6. mqtt-packet (parse)


그럼 다시 봐야지 뭐..


(mqtt-packet/parser.js)

Parser.prototype._parsePayload = function () {
  var result = false

  // Do we have a payload? Do we have enough data to complete the payload?
  // PINGs have no payload
  if (this.packet.length === 0 || this._list.length >= this.packet.length) {

    this._pos = 0

    switch (this.packet.cmd) {
      case 'connect':
        this._parseConnect()
        break
      case 'authenticate':
        this._parseAuth()
        break
      case 'connack':
        this._parseConnack()
        break
      case 'publish':
        this._parsePublish()
        break
      case 'puback':
      case 'pubrec':
      case 'pubrel':
      case 'pubcomp':
        this._parseMessageId()
        break
      case 'subscribe':
        this._parseSubscribe()
        break
      case 'suback':
        this._parseSuback()
        break
      case 'unsubscribe':
        this._parseUnsubscribe()
        break
      case 'unsuback':
        this._parseUnsuback()
        break
      case 'pingreq':
      case 'pingresp':
      case 'disconnect':
        // these are empty, nothing to do
        break
      default:
        this._emitError(new Error('not supported'))
    }

    result = true
  }

  return result
}

흐흐 나 코드 따라가는 것 좀 잘 하는 것 같다.


여튼 여기 등장! 빠밤

그럼 여기다 또 언제나 그래왔듯 authenticate case 를 추가해주면 된다.


근데 또 못보던 function 이 은근슬쩍 등장했다.

그렇다. 만들어줘야한다.

진짜 개귀찮다. 내가 왜 이 짓을 시작했지

역시 코딩은 삽질의 연속이다

인정? 어인정~ 동의? 어 보감~ 동휘? 어 보검~~~~~~~~~~~~~~~



(mqtt-packet/parser.js)

Parser.prototype._parseAuth = function() {
  var protocolId // constants id
    , clientId // Client id
    , password // Password
    , username // Username
    , flags = {}
    , packet = this.packet

  // Parse constants id
  protocolId = this._parseString()
  if (protocolId === null)
    return this._emitError(new Error('cannot parse protocol id'))

  if (protocolId != 'MQTT' && protocolId != 'MQIsdp') {

    return this._emitError(new Error('invalid protocol id'))
  }

  packet.protocolId = protocolId

  // Parse constants version number
  if(this._pos >= this._list.length)
    return this._emitError(new Error('packet too short'))

  packet.protocolVersion = this._list.readUInt8(this._pos)

  if(packet.protocolVersion != 3 && packet.protocolVersion != 4) {

    return this._emitError(new Error('invalid protocol version'))
  }

  this._pos++
  if(this._pos >= this._list.length)
    return this._emitError(new Error('packet too short'))

  // Parse connect flags
  flags.username  = (this._list.readUInt8(this._pos) & constants.USERNAME_MASK)
  flags.password  = (this._list.readUInt8(this._pos) & constants.PASSWORD_MASK)

  packet.clean = (this._list.readUInt8(this._pos) & constants.CLEAN_SESSION_MASK) !== 0
  this._pos++

  // Parse keepalive
  packet.keepalive = this._parseNum()
  if(packet.keepalive === -1)
    return this._emitError(new Error('packet too short'))

  // Parse client ID
  clientId = this._parseString()
  if(clientId === null)
    return this._emitError(new Error('packet too short'))
  packet.clientId = clientId

  // Parse username
  if (flags.username) {
    username = this._parseString()
    if(username === null)
      return this._emitError(new Error('cannot parse username'))
    packet.username = username
  }

  // Parse password
  if(flags.password) {
    password = this._parseBuffer()
    if(password === null)
      return this._emitError(new Error('cannot parse username'))
    packet.password = password
  }

  return packet
}


이번에도 역시 _parseConnect 를 참고했다.

참고하되 저번에 만들어둔 Authenticate Generate Function 을 꼭 고려하면서 짜깁기해야한다!

그때 넣었던 데이터들 잘 참고해서 고쳐줘야함!


요까지 해주면 device 에서 authenticate packet 을 받는 것도 매우 잘 된다!


아래는 받는 코드

client.on('authenticate', function(packet) {
  console.log(packet);
});


아 근데 주의할 점이 있다.


혹여나 (가 아니고 대개 그렇겠지만)

Server process 를 돌리는 project directory 와

Device process 를 돌리는 project directory 가 다르다면

각각 node_modules directory 에서 적합한 library 를 수정해줘야 하는데,


지금까지 한 Authenticate Packet (Server -> Device) 단계에서

Server / Device 둘 모두에서 고쳐줘야 하는 부분이 있다.


constants.js !!


authenticate 타입을

Server 에선 추가해줬는데 Device 에서 안 해줬거나

혹은 그 반대의 상황이어도

보내거나 받는 게 제대로 안 된다.


꼭 확인!


ㅠㅠ 다음편엔 3단계로 넘어갈 예정.

정리 넘 힘들당 

너무 길어질 것 같아서 잘랐다.


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



요기서 Connect 를 받지 않은 채로 Server - Device 간 통신을 어찌할지에 대해 언급만 하고 끝났다.


사실 더 많은 삽질을 했었는데, (Mosca, mqtt 라이브러리의 stream에 직접 접근한다거나..)

아무래도 라이브러리 내부적으로 처리하는 것들이 많다보니, 그렇게 직접 접근해서 데이터를 갖다 박아버리는 방식은 적합하지 않은 것 같았다.

가는 건 되는데 오는 게 안 된다거나, 보내고 받고 보냈는데 마지막 받는 걸 못한다거나..


그래서 라이브러리 자체 로직을 따르기로 했다.

Mqtt packet 을 generating, parsing 하는 부분 코드를 참조해서 Authentication 을 위한 Packet Type 을 만들어버리는 방법으로!




#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)


니까!!

2 단계인 Server -> Device 부터 보자.



#1. Mosca Authenticate


Mosca 에서 제공하는 Authentication 기능과 적절히 섞어서 사용할 거기 때문에,

Mosca 의 authenticate function 을 먼저 봐야 한다.


var authenticate = function(client, username, password, callback) {
  if (/* authentication success */)
    callback(null, true);
  else /* authentication failed */
    callback(null, false);
};


Mosca 의 기본 authentication 기능을 이용하려면, 위와 같이 authenticate function 을 내 입맛대로 정의해준 뒤에

Mosca Server setup 단계에서 아래와 같이 설정해주면 된다.


function setup() {
  server.authenticate = authenticate;
  /* authorizePublish, authorizeSubscribe 도 필요하면 셋팅해주면 된다. */
};

server.on('ready', setup);

당연한 얘기지만 setup function 을 server.on 에다 바로 정의해도 된다.


여튼 Mosca 의 Authentication 은 저렇게 쓰는 건데, 우린 저 단계에서 callback 으로 success parameter 를 넘기기 전에 device 와 통신을 해야한다.



그러기 위해서는 일단 Mosca 에서 Mqtt Packet 을 어떤 식으로 보내는지부터 봐야한다.




#2. Mosca authenticate function callback


Packet 전송을 어디서 어떻게 하는지 해당 코드를 찾는 가장 확실한 방법은

현재로선 위 authenticate function 에서 호출하도록 되어있는 callback 을 보는 것이다.

true, false 에 따라 Connect ACK Packet 을 보낼지 말지 결정할 거 아냐?

그래서 따라가봤다.


(mosca/lib/client.js)

that.server.authenticate(this, packet.username, packet.password,
                           function(err, verdict) {

    if (err) {
      logger.info({ username: packet.username }, "authentication error");
      client.connack({
        returnCode: 4
      });
      client.stream.end();
      return;
    }

    if (!verdict) {
      logger.info({ username: packet.username }, "authentication denied");
      client.connack({
        returnCode: 5
      });
      client.stream.end();
      return;
    }

    that.keepalive = packet.keepalive;
    that.will = packet.will;

    that.clean = packet.clean;

    if (that.id in that.server.clients){
      that.server.clients[that.id].close(completeConnection, "new connection request");
    } else {
      completeConnection();
    }
  });


요것이 해당코드!

저길 보면 client.connack(~) 하는 부분이 실제 Connect ACK Packet 을 전송하는 부분이라 볼 수 있다. (이름부터가.. ConnectACK -> CONNACK)


엥 근데 client? 

하고 client 가 뭔지 찾아올라가봤더니,

대체 mosca 에서 왜 이런 네이밍을 한 건지 모르겠지만, 


var that = this, logger, client = this.connection;

이란 부분이 있다.


그렇다면 저 connection 이란 애가 실제 Packet 전송을 담당한다는 건데.

쟤가 바로 'mqtt-connection' 이라는 module 이다.




#3. mqtt-connection


그럼 이제 이 모듈도 봐야 돼..

node_modules 에 있는 mqtt-connection directory 를 보면

mqtt-connection/connection.js 라는 파일이 있다.


걔를 열고 좀 내려보면

['connect',
  'connack',
  'publish',
  'puback',
  'pubrec',
  'pubrel',
  'pubcomp',
  'subscribe',
  'suback',
  'unsubscribe',
  'unsuback',
  'pingreq',
  'pingresp',
  'disconnect'].forEach(function(cmd) {
    Connection.prototype[cmd] = function(opts, cb) {
      opts = opts || {}
      opts.cmd = cmd;

      // flush the buffer if needed
      // UGLY hack, we should listen for the 'drain' event
      // and start writing again, but this works too
      this.write(opts)
      if (cb)
        setImmediate(cb)
    }
  });

이런 코드가 있는데,

저기 줄줄이 나열된 애들이 mqtt protocol 에서 원래 쓰는? command 들이다!


각 command 마다 

Connection.prototype.connect

뭐 이런 식으로 function 을 만들어주는 코드인데,

우리는 authenticate 를 쓸 거니까 저 배열에 은근슬쩍 'authenticate' 를 끼워주면 된다.


['connect',
  'connack',
  'publish',
  'puback',
  'pubrec',
  'pubrel',
  'pubcomp',
  'subscribe',
  'suback',
  'unsubscribe',
  'unsuback',
  'pingreq',
  'pingresp',
  'disconnect',
  'authenticate'].forEach(function(cmd) {
    Connection.prototype[cmd] = function(opts, cb) {
      opts = opts || {}
      opts.cmd = cmd;

      // flush the buffer if needed
      // UGLY hack, we should listen for the 'drain' event
      // and start writing again, but this works too
      this.write(opts)
      if (cb)
        setImmediate(cb)
    }
  });

이렇게!! 거의 다른그림찾기 수준,, ㅎㅎ


여튼 이렇게 해두고

지금은 [2. Authenticate Packet (Server -> Device)] 를 보고 있는 거니까!

서버에서 보내는 상황!


그럼 authenticate type 에 맞게 MQTT Packet 을 generate 해줘야겠지?



(mqtt-connection/lib/generateStream.js)

var through   = require('through2')
  , generate  = require('mqtt-packet').generate
  , empty     = new Buffer(0)

function generateStream() {
  var stream  = through.obj(process)

  function process(chunk, enc, cb) {
    var packet = empty;

    try {
      packet = generate(chunk)
    } catch(err) {
      this.emit('error', err)
      return;
    }

    this.push(packet)
    cb()
  }

  return stream
}

module.exports = generateStream;


응?

generateStream.js 를 보니,,

얘 정작 Packet generate 하는 데에는 또 다른 모듈을 쓴다!! ㅠㅠ

봐야할 것 ++..


그럼 이번엔 mqtt-packet 이란 모듈을 한번 보자.




#4. mqtt-packet (generate)


요 라이브러리도 코드를 열어보면, 딱 앗 얘가 generate 하는 애구나!! 싶은 파일이 있다. (이름이 generate.js 거든..)

근데 generate.js 를 보기 전에!!

파일 목록을 찬찬히 보다보면 거슬리는 놈이 하나 있다.


constants.js!!


이름부터가 뭔가 상수관리를 할 것 같은, 아주 중요해보이는 아이니까

얘를 먼저 한번 보자.

/* Command code => mnemonic */
module.exports.types = {
  0: 'reserved' /* -> authenticate */,
  1: 'connect',
  2: 'connack',
  3: 'publish',
  4: 'puback',
  5: 'pubrec',
  6: 'pubrel',
  7: 'pubcomp',
  8: 'subscribe',
  9: 'suback',
  10: 'unsubscribe',
  11: 'unsuback',
  12: 'pingreq',
  13: 'pingresp',
  14: 'disconnect',
  15: 'reserved'
};

호엑 역시 중요한 칭구였어

Packet Command 관련 상수들이 관리되는 파일인 것 같다.


1~14 까지는 다 원래 중요하게 쓰이는 Command 들이니 건드리지 말고

아래 위로 껴있는 reserved 중 하나를 authenticate 로 바꿔보자.

사실 reserved 가 무슨 역할인지는 아직 모른다.


0, 15 둘 중 아무거나 바꿔도 된다. 둘 다 테스트 해봤는데 잘 됨!

근데 신기한 건, 모든 과정을 끝내고 테스트하면서 Packet Capture를 떠보면, Authenticate Packet 도 Reserve 로 잡힌다.

MQTT Protocol 약속인가봐...

약속된 값을 건드렸는데도 문제가 없음에 감사하며 ㅠㅠ



constants.js 를 무사히 수정했다면 다시 원래 목적인 generate.js 를 보자.



(mqtt-packet/generate.js)

function generate(packet) {

  switch (packet.cmd) {
    case 'connect':
      return connect(packet)
    case 'authenticate':
      return authenticate(packet)
    case 'connack':
      return connack(packet)
    case 'publish':
      return publish(packet)
    case 'puback':
    case 'pubrec':
    case 'pubrel':
    case 'pubcomp':
    case 'unsuback':
      return confirmation(packet)
    case 'subscribe':
      return subscribe(packet)
    case 'suback':
      return suback(packet)
    case 'unsubscribe':
      return unsubscribe(packet)
    case 'pingreq':
    case 'pingresp':
    case 'disconnect':
      return emptyPacket(packet)
    default:
      throw new Error('unknown command')
  }
}

일단 파일을 열자마자 7번째 줄에 이런 코드가 있을 거다.

저기서 case 문들 중 아무 곳에나 'authenticate' case 를 끼워넣자.

나는 connect 다음에다 끼워넣었다.


근데 내가 적은 코드를 자세히 보니

return authenticate(packet)??

저게 원래 있던 function일까?


그럴리가.. 직접 추가해줘야한다.


이미 라이브러리에 포함되어 있는

다른 관련 function 코드를 참고해서 만들면 된다.


나는 username과 password를 포함한다는 점에서,

connect 와 비슷한 점이 많은 것 같아 connect function 을 복붙해서 고쳤다.


필요한 정보는 남기고, 불필요한 정보는 지우고,

필요한지 불필요한지 잘모르겠는 건 일단 남기고 ㅎㅎㅎㅎ


그렇게 짜깁기로 완성한 authenticate function code ▼

function authenticate(opts) {
  var opts = opts || {}
    , cmd = opts.cmd
    , protocolId = opts.protocolId || 'MQTT'
    , protocolVersion = opts.protocolVersion || 4
    , clean = opts.clean
    , keepalive = opts.keepalive || 0
    , clientId = opts.clientId || ""
    , username = opts.username
    , password = opts.password

  if (clean === undefined) {
    clean = true
  }

  var length = 0

  // Must be a string and non-falsy
  if (!protocolId ||
     (typeof protocolId !== "string" && !Buffer.isBuffer(protocolId))) {
    throw new Error('Invalid protocol id')
  } else {
    length += protocolId.length + 2
  }

  // Must be a 1 byte number
  if (!protocolVersion ||
      'number' !== typeof protocolVersion ||
      protocolVersion > 255 ||
      protocolVersion < 0) {

    throw new Error('Invalid protocol version')
  } else {
    length += 1
  }

  // ClientId might be omitted in 3.1.1, but only if cleanSession is set to 1
  if ((typeof clientId === "string" || Buffer.isBuffer(clientId)) &&
     (clientId || protocolVersion == 4) &&
     (clientId || clean)) {

    length += clientId.length + 2
  } else {

    if(protocolVersion < 4) {

      throw new Error('clientId must be supplied before 3.1.1');
    }

    if(clean == 0) {

      throw new Error('clientId must be given if cleanSession set to 0');
    }
  }

  // Must be a two byte number
  if ('number' !== typeof keepalive ||
      keepalive < 0 ||
      keepalive > 65535) {
    throw new Error('Invalid keepalive')
  } else {
    length += 2
  }

  // Connect flags
  length += 1

  // Username
  if (username) {
    if (username.length) {
      length += Buffer.byteLength(username) + 2
    } else {
      throw new Error('Invalid username')
    }
  }

  // Password
  if (password) {
    if (password.length) {
      length += byteLength(password) + 2
    } else {
      throw new Error('Invalid password')
    }
  }

  var buffer = new Buffer(1 + calcLengthLength(length) + length)
    , pos = 0

  // Generate header
  buffer.writeUInt8(protocol.codes[cmd] << protocol.CMD_SHIFT, pos++, true)

  // Generate length
  pos += writeLength(buffer, pos, length)

  // Generate protocol ID
  pos += writeStringOrBuffer(buffer, pos, protocolId)
  buffer.writeUInt8(protocolVersion, pos++, true)

  // Connect flags
  var flags = 0
  flags |= username ? protocol.USERNAME_MASK : 0
  flags |= password ? protocol.PASSWORD_MASK : 0
  flags |= clean ? protocol.CLEAN_SESSION_MASK : 0

  buffer.writeUInt8(flags, pos++, true)

  // Keepalive
  pos += writeNumber(buffer, pos, keepalive)

  // Client ID
  pos += writeStringOrBuffer(buffer, pos, clientId)

  // Username and password
  if (username)
    pos += writeStringOrBuffer(buffer, pos, username)

  if (password)
    pos += writeStringOrBuffer(buffer, pos, password)

  return buffer
}


이제 여기까지 했으면

authenticate type packet 을 generate 하는 것부터, 

mqtt-connection 라이브러리에 해당 타입(authenticate)의 패킷을 전송하는 function도 추가가 되었다.



이제 보내기만 하면 되지!!

var secretKey = +new Date();

client.connection.authenticate({
  clientId: client.id,
  username: "",
  password: secretKey.toString()
});

이게 실제로 보내는 코드다.

필요한대로 활용하면 된다.

나는 이 코드를 authenticate function 안에 넣어뒀다.


으으 이제 Device 에서 authenticate type Packet 을 받는 부분을 손봐야 한다.


원래 Server -> Device 부분을 한 글에 끝내버리려고 했는데...........

넘 길어져버려서 이쯤에서 끊어야 할 것 같다

여기서 더 쓰면 나중에 내가 다시 보기 힘들 듯 ㅎㅎ


안녕!!

Error:Execution failed for task ':app:mergeDebugResources'. > Error: Some file crunching failed, see logs for details

라는 에러가 떴다.


see logs for details 라길래 확인해봤더니

어쩌구저쩌구내나인패치경로~~ 9.png malformed. 라고 나와있었다.


구글링을 해봤더니

YOUR_SDK_PATH/tools/draw9patch 라는 실행파일을 실행해서

저 프로그램으로 해당 나인패치 파일을 열었다가 다시 저장하면 된단다.


잘됨..


이딴 오류는 왜 있는 거야 대체 

+ Recent posts