├── .travis.yml ├── scripts ├── move.sh ├── move.bat ├── copy_to_local_server.sh └── copy_to_azure_server.sh ├── server ├── twitter │ ├── src │ │ ├── test │ │ │ ├── resources │ │ │ │ └── twitter4j.properties │ │ │ └── java │ │ │ │ └── cc │ │ │ │ └── blynk │ │ │ │ └── server │ │ │ │ └── twitter │ │ │ │ └── TwitterWrapperTest.java │ │ └── main │ │ │ └── java │ │ │ └── cc │ │ │ └── blynk │ │ │ └── server │ │ │ ├── exceptions │ │ │ ├── TweetException.java │ │ │ └── TweetNotAuthorizedException.java │ │ │ ├── model │ │ │ └── TwitterAccessToken.java │ │ │ └── twitter │ │ │ └── TwitterWrapper.java │ └── pom.xml ├── push-notifications-android │ ├── src │ │ ├── test │ │ │ ├── resources │ │ │ │ └── gcm.properties │ │ │ └── java │ │ │ │ └── cc │ │ │ │ └── blynk │ │ │ │ └── server │ │ │ │ └── push │ │ │ │ └── GCMWrapperTest.java │ │ └── main │ │ │ ├── java │ │ │ └── cc │ │ │ │ └── blynk │ │ │ │ └── server │ │ │ │ └── push │ │ │ │ ├── response │ │ │ │ ├── enums │ │ │ │ │ ├── ControlType.java │ │ │ │ │ └── NACKError.java │ │ │ │ ├── ACKMessage.java │ │ │ │ ├── ControlMessage.java │ │ │ │ ├── NACKMessage.java │ │ │ │ └── ResponseMessageBase.java │ │ │ │ ├── exceptions │ │ │ │ └── PushException.java │ │ │ │ ├── request │ │ │ │ └── RequestMessage.java │ │ │ │ ├── GCMPacketExtension.java │ │ │ │ ├── LoggingConnectionListener.java │ │ │ │ ├── GCMWrapper.java │ │ │ │ └── MyPacketListener.java │ │ │ └── resources │ │ │ └── log4j2.xml │ └── pom.xml ├── tcp-server │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── cc │ │ │ │ │ └── blynk │ │ │ │ │ └── server │ │ │ │ │ ├── model │ │ │ │ │ ├── enums │ │ │ │ │ │ ├── State.java │ │ │ │ │ │ ├── PinType.java │ │ │ │ │ │ └── WidgetType.java │ │ │ │ │ ├── widgets │ │ │ │ │ │ ├── controls │ │ │ │ │ │ │ ├── SliderLarge.java │ │ │ │ │ │ │ ├── Knob.java │ │ │ │ │ │ │ ├── RGB.java │ │ │ │ │ │ │ ├── Gamepad.java │ │ │ │ │ │ │ ├── Keypad.java │ │ │ │ │ │ │ ├── FourWayArrow.java │ │ │ │ │ │ │ ├── RotaryKnob.java │ │ │ │ │ │ │ ├── TwoWayArrow.java │ │ │ │ │ │ │ ├── OneAxisJoystick.java │ │ │ │ │ │ │ ├── TwoAxisJoystick.java │ │ │ │ │ │ │ ├── Button.java │ │ │ │ │ │ │ └── Slider.java │ │ │ │ │ │ ├── inputs │ │ │ │ │ │ │ ├── GPS.java │ │ │ │ │ │ │ ├── Gyroscope.java │ │ │ │ │ │ │ ├── Microphone.java │ │ │ │ │ │ │ └── Accelerometer.java │ │ │ │ │ │ ├── others │ │ │ │ │ │ │ ├── RCT.java │ │ │ │ │ │ │ ├── Email.java │ │ │ │ │ │ │ ├── Eventor.java │ │ │ │ │ │ │ ├── SDCard.java │ │ │ │ │ │ │ ├── Twitter.java │ │ │ │ │ │ │ ├── Terminal.java │ │ │ │ │ │ │ ├── Notification.java │ │ │ │ │ │ │ └── Timer.java │ │ │ │ │ │ └── outputs │ │ │ │ │ │ │ ├── Gauge.java │ │ │ │ │ │ │ ├── Graph.java │ │ │ │ │ │ │ ├── LCDDisplay.java │ │ │ │ │ │ │ ├── LevelDisplay.java │ │ │ │ │ │ │ ├── LED.java │ │ │ │ │ │ │ └── Digit4Display.java │ │ │ │ │ └── auth │ │ │ │ │ │ ├── Stats.java │ │ │ │ │ │ ├── nio │ │ │ │ │ │ ├── ChannelState.java │ │ │ │ │ │ └── ChannelServer.java │ │ │ │ │ │ └── Session.java │ │ │ │ │ ├── dao │ │ │ │ │ ├── Storage.java │ │ │ │ │ ├── SessionsHolder.java │ │ │ │ │ ├── GraphInMemoryStorage.java │ │ │ │ │ └── UserRegistry.java │ │ │ │ │ ├── exceptions │ │ │ │ │ ├── NotAllowedException.java │ │ │ │ │ ├── UserNotRegistered.java │ │ │ │ │ ├── InvalidTokenException.java │ │ │ │ │ ├── UserAlreadyLoggedIn.java │ │ │ │ │ ├── UserNotAuthenticated.java │ │ │ │ │ ├── IllegalCommandException.java │ │ │ │ │ ├── DeviceNotInNetworkException.java │ │ │ │ │ ├── NoActiveDashboardException.java │ │ │ │ │ └── TweetBodyInvalidException.java │ │ │ │ │ ├── handlers │ │ │ │ │ ├── workflow │ │ │ │ │ │ ├── ClientChannelStateHandler.java │ │ │ │ │ │ ├── PingHandler.java │ │ │ │ │ │ ├── LoadProfileHandler.java │ │ │ │ │ │ ├── DeActivateDashboardHandler.java │ │ │ │ │ │ ├── GetTokenHandler.java │ │ │ │ │ │ ├── RefreshTokenHandler.java │ │ │ │ │ │ ├── TweetHandler.java │ │ │ │ │ │ ├── ActivateDashboardHandler.java │ │ │ │ │ │ ├── HardwareHandler.java │ │ │ │ │ │ └── SaveProfileHandler.java │ │ │ │ │ └── auth │ │ │ │ │ │ ├── RegisterHandler.java │ │ │ │ │ │ └── AppLoginHandler.java │ │ │ │ │ ├── workers │ │ │ │ │ ├── ShutdownHookWorker.java │ │ │ │ │ ├── ProfileSaverWorker.java │ │ │ │ │ └── PropertiesChangeWatcherWorker.java │ │ │ │ │ ├── utils │ │ │ │ │ ├── Finder.java │ │ │ │ │ └── JsonParser.java │ │ │ │ │ ├── core │ │ │ │ │ ├── hardware │ │ │ │ │ │ ├── HardwareServer.java │ │ │ │ │ │ ├── HardwareChannelInitializer.java │ │ │ │ │ │ └── HardwareHandlersHolder.java │ │ │ │ │ ├── application │ │ │ │ │ │ └── AppChannelInitializer.java │ │ │ │ │ └── BaseServer.java │ │ │ │ │ └── ArgumentsParser.java │ │ │ └── resources │ │ │ │ └── server.properties │ │ └── test │ │ │ ├── resources │ │ │ ├── json_test │ │ │ │ ├── user_profile_json_3.txt │ │ │ │ ├── user_profile_with_timer.txt │ │ │ │ ├── user_profile_json_2.txt │ │ │ │ ├── user_profile_json_4.txt │ │ │ │ ├── user_profile_json.txt │ │ │ │ └── user_profile_json_5.txt │ │ │ └── log4j2-test.xml │ │ │ └── java │ │ │ └── cc │ │ │ └── blynk │ │ │ └── server │ │ │ ├── TestBase.java │ │ │ ├── utils │ │ │ ├── EMailValidationTest.java │ │ │ └── FileManagerIntegrationTest.java │ │ │ ├── handlers │ │ │ └── workflow │ │ │ │ └── SaveProfileHandlerTest.java │ │ │ ├── workers │ │ │ ├── PropertiesChangeWatcherWorkerTest.java │ │ │ ├── ProfileSaverWorkerTest.java │ │ │ └── timer │ │ │ │ └── TimerWorkerTest.java │ │ │ ├── dao │ │ │ └── GraphInMemoryStorageTest.java │ │ │ └── model │ │ │ └── UserProfileCalcGraphPinsTest.java │ └── pom.xml ├── push-notifications-ios │ └── pom.xml └── pom.xml ├── integration-tests ├── src │ └── test │ │ ├── resources │ │ ├── twitter4j.properties │ │ ├── log4j2-test.xml │ │ ├── test-certs │ │ │ ├── test-server.crt │ │ │ └── test-server.pem │ │ ├── server.properties │ │ └── json_test │ │ │ ├── user_profile_json.txt │ │ │ └── user_profile_json_many_dashes.txt │ │ └── java │ │ └── cc │ │ └── blynk │ │ └── integration │ │ └── model │ │ ├── SimpleClientHandler.java │ │ ├── ClientPair.java │ │ ├── MockHolder.java │ │ └── TestHardClient.java └── pom.xml ├── client ├── src │ ├── main │ │ ├── resources │ │ │ ├── log4j2.xml │ │ │ └── test.crt │ │ └── java │ │ │ └── cc │ │ │ └── blynk │ │ │ └── client │ │ │ ├── enums │ │ │ └── ClientMode.java │ │ │ ├── handlers │ │ │ └── ClientReplayingMessageDecoder.java │ │ │ ├── CommandParser.java │ │ │ ├── core │ │ │ ├── HardwareClient.java │ │ │ └── AppClient.java │ │ │ ├── HexConvertor.java │ │ │ └── ClientLauncher.java │ └── test │ │ └── java │ │ └── cc │ │ └── blynk │ │ └── client │ │ └── ClientTest.java └── pom.xml ├── common ├── src │ ├── main │ │ └── java │ │ │ └── cc │ │ │ └── blynk │ │ │ └── common │ │ │ ├── utils │ │ │ ├── Config.java │ │ │ ├── ParseUtil.java │ │ │ ├── ReflectionUtil.java │ │ │ └── StringUtils.java │ │ │ ├── exceptions │ │ │ ├── UnsupportedCommandException.java │ │ │ └── BaseServerException.java │ │ │ ├── model │ │ │ └── messages │ │ │ │ ├── Message.java │ │ │ │ ├── protocol │ │ │ │ ├── PingMessage.java │ │ │ │ ├── hardware │ │ │ │ │ └── TweetMessage.java │ │ │ │ ├── appllication │ │ │ │ │ ├── LoginMessage.java │ │ │ │ │ ├── GetTokenMessage.java │ │ │ │ │ ├── RegisterMessage.java │ │ │ │ │ ├── LoadProfileMessage.java │ │ │ │ │ ├── SaveProfileMessage.java │ │ │ │ │ ├── RefreshTokenMessage.java │ │ │ │ │ ├── ActivateDashboardMessage.java │ │ │ │ │ └── DeActivateDashboardMessage.java │ │ │ │ └── HardwareMessage.java │ │ │ │ ├── ResponseMessage.java │ │ │ │ ├── MessageBase.java │ │ │ │ └── MessageFactory.java │ │ │ ├── handlers │ │ │ ├── encoders │ │ │ │ └── DeviceMessageEncoder.java │ │ │ ├── decoders │ │ │ │ └── ReplayingMessageDecoder.java │ │ │ └── DefaultExceptionHandler.java │ │ │ ├── enums │ │ │ ├── Command.java │ │ │ └── Response.java │ │ │ └── stats │ │ │ ├── metrics │ │ │ └── InstanceLoadMeter.java │ │ │ └── GlobalStats.java │ └── test │ │ └── java │ │ └── cc │ │ └── blynk │ │ └── common │ │ └── utils │ │ └── StringUtilsTest.java └── pom.xml └── license.txt /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | script: mvn test -------------------------------------------------------------------------------- /scripts/move.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HenrikDA/blynk-server/HEAD/scripts/move.sh -------------------------------------------------------------------------------- /scripts/move.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HenrikDA/blynk-server/HEAD/scripts/move.bat -------------------------------------------------------------------------------- /server/twitter/src/test/resources/twitter4j.properties: -------------------------------------------------------------------------------- 1 | debug=true 2 | oauth.consumerKey= 3 | oauth.consumerSecret= -------------------------------------------------------------------------------- /scripts/copy_to_local_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | scp ../build/server-0.1.jar pupkin@192.168.0.64:/home/pupkin/Dropbox/blynk_new -------------------------------------------------------------------------------- /scripts/copy_to_azure_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | scp ../build/server-0.1.jar azureuser@blynk-test-east.cloudapp.net:/home/azureuser -------------------------------------------------------------------------------- /server/push-notifications-android/src/test/resources/gcm.properties: -------------------------------------------------------------------------------- 1 | gcm.server=gcm.googleapis.com 2 | gcm.port=5235 3 | gcm.project.id=YOUR_PROJECT_ID_HERE 4 | gcm.api.key=YOUR_API_KEY_HERE -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/enums/State.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.enums; 2 | 3 | /** 4 | * User: ddumanskiy 5 | * Date: 21.11.13 6 | * Time: 13:44 7 | */ 8 | public enum State { 9 | 10 | ON, OFF 11 | } 12 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/resources/json_test/user_profile_json_3.txt: -------------------------------------------------------------------------------- 1 | {"dashBoards":[{"id":1,"boardType":"UNO","widgets":[{"y":0,"id":2948750487994149329,"type":"BUTTON","x":0,"label":"BUTTON"},{"y":6,"id":4396013099992412521,"type":"SLIDER","x":2,"label":"SLIDER"}]}]} -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/enums/PinType.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.enums; 2 | 3 | /** 4 | * User: ddumanskiy 5 | * Date: 10.12.13 6 | * Time: 10:15 7 | */ 8 | public enum PinType { 9 | 10 | NONE, 11 | DIGITAL, 12 | VIRTUAL, 13 | ANALOG 14 | 15 | } 16 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/controls/SliderLarge.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.controls; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 21.03.15. 7 | */ 8 | public class SliderLarge extends Slider { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/java/cc/blynk/server/push/response/enums/ControlType.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push.response.enums; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 2/8/2015. 7 | */ 8 | public enum ControlType { 9 | 10 | CONNECTION_DRAINING 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/inputs/GPS.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.inputs; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class GPS extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/others/RCT.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.others; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class RCT extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/controls/Knob.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.controls; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Knob extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/controls/RGB.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.controls; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class RGB extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/others/Email.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.others; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Email extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/others/Eventor.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.others; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Eventor extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/others/SDCard.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.others; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class SDCard extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/others/Twitter.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.others; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Twitter extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/outputs/Gauge.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.outputs; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Gauge extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/outputs/Graph.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.outputs; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Graph extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/controls/Gamepad.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.controls; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Gamepad extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/controls/Keypad.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.controls; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Keypad extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/inputs/Gyroscope.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.inputs; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Gyroscope extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/others/Terminal.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.others; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Terminal extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/controls/FourWayArrow.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.controls; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class FourWayArrow extends Widget { 11 | } 12 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/controls/RotaryKnob.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.controls; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class RotaryKnob extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/controls/TwoWayArrow.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.controls; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class TwoWayArrow extends Widget { 11 | } 12 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/inputs/Microphone.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.inputs; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Microphone extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/others/Notification.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.others; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Notification extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/outputs/LCDDisplay.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.outputs; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class LCDDisplay extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/inputs/Accelerometer.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.inputs; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Accelerometer extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/outputs/LevelDisplay.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.outputs; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class LevelDisplay extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/controls/OneAxisJoystick.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.controls; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class OneAxisJoystick extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/controls/TwoAxisJoystick.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.controls; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class TwoAxisJoystick extends Widget { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/outputs/LED.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.outputs; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class LED extends Widget { 11 | 12 | int color; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/dao/Storage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.dao; 2 | 3 | import cc.blynk.server.model.auth.User; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 2/18/2015. 9 | */ 10 | public interface Storage { 11 | 12 | public String store(User user, Integer dashId, String body, int msgId); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/controls/Button.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.controls; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Button extends Widget { 11 | 12 | public boolean pushMode; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/java/cc/blynk/server/push/exceptions/PushException.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push.exceptions; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 2/8/2015. 7 | */ 8 | public class PushException extends RuntimeException { 9 | 10 | public PushException(String message) { 11 | super(message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /integration-tests/src/test/resources/twitter4j.properties: -------------------------------------------------------------------------------- 1 | ## For local servers you should fill this fields by yourself 2 | ## Go here https://apps.twitter.com/ 3 | ## Create twitter app. 4 | ## paste here consumerKey and consumerSecret 5 | ## Access token will be populated from mobile app. 6 | ## Also you could read instruction here http://twitter4j.org/en/code-examples.html 7 | debug=true 8 | oauth.consumerKey= 9 | oauth.consumerSecret= -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/outputs/Digit4Display.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.outputs; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Digit4Display extends Widget { 11 | 12 | public int min; 13 | 14 | public int max; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/java/cc/blynk/server/TestBase.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server; 2 | 3 | import cc.blynk.common.utils.ServerProperties; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 2/12/2015. 9 | */ 10 | public class TestBase { 11 | 12 | public ServerProperties props = new ServerProperties(); 13 | public String dataFolder = props.getProperty("data.folder"); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/java/cc/blynk/server/push/response/ACKMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push.response; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 2/8/2015. 7 | */ 8 | public class ACKMessage extends ResponseMessageBase { 9 | 10 | @Override 11 | public String toString() { 12 | return "ACKMessage{" + super.toString() + "}"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/controls/Slider.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.controls; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Slider extends Widget { 11 | 12 | public boolean pwmMode; 13 | 14 | public int min; 15 | 16 | public int max; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/utils/Config.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.utils; 2 | 3 | import java.nio.charset.Charset; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 1/31/2015. 9 | */ 10 | public class Config { 11 | 12 | public static final Charset DEFAULT_CHARSET = Charset.defaultCharset(); 13 | 14 | public static final String SERVER_PROPERTIES_FILENAME = "server.properties"; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/resources/json_test/user_profile_with_timer.txt: -------------------------------------------------------------------------------- 1 | {"dashBoards":[{"boardType":"Arduino Mega","widgets":[{"pushMode":false,"type":"BUTTON","pinType":"DIGITAL","pin":13,"index":0,"id":2,"height":2,"width":2,"x":0,"y":0},{"stopValue":"dw\u00009\u00000","startValue":"dw\u00009\u00001","stopTime":-1,"startTime":65640,"type":"TIMER","label":"","pinType":"DIGITAL","pin":9,"index":43,"id":3,"height":1,"width":3,"x":3,"y":5}],"name":"NEW PROJECT","id":1}]} 2 | -------------------------------------------------------------------------------- /integration-tests/src/test/resources/log4j2-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/widgets/others/Timer.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.widgets.others; 2 | 3 | import cc.blynk.server.model.widgets.Widget; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 21.03.15. 9 | */ 10 | public class Timer extends Widget { 11 | 12 | public Long startTime; 13 | 14 | public String startValue; 15 | 16 | public Long stopTime; 17 | 18 | public String stopValue; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/exceptions/UnsupportedCommandException.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.exceptions; 2 | 3 | import cc.blynk.common.enums.Response; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 2/3/2015. 9 | */ 10 | public class UnsupportedCommandException extends BaseServerException { 11 | 12 | public UnsupportedCommandException(String message, int msgId) { 13 | super(message, msgId, Response.ILLEGAL_COMMAND); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /server/twitter/src/main/java/cc/blynk/server/exceptions/TweetException.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.exceptions; 2 | 3 | import cc.blynk.common.enums.Response; 4 | import cc.blynk.common.exceptions.BaseServerException; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/3/2015. 10 | */ 11 | public class TweetException extends BaseServerException { 12 | 13 | public TweetException(String message, int msgId) { 14 | super(message, msgId, Response.TWEET_EXCEPTION); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/exceptions/NotAllowedException.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.exceptions; 2 | 3 | import cc.blynk.common.enums.Response; 4 | import cc.blynk.common.exceptions.BaseServerException; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/23/2015. 10 | */ 11 | public class NotAllowedException extends BaseServerException { 12 | 13 | public NotAllowedException(String message, int msgId) { 14 | super(message, msgId, Response.NOT_ALLOWED); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/exceptions/UserNotRegistered.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.exceptions; 2 | 3 | import cc.blynk.common.enums.Response; 4 | import cc.blynk.common.exceptions.BaseServerException; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/3/2015. 10 | */ 11 | public class UserNotRegistered extends BaseServerException { 12 | 13 | public UserNotRegistered(String message, int msgId) { 14 | super(message, msgId, Response.USER_NOT_REGISTERED); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/exceptions/InvalidTokenException.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.exceptions; 2 | 3 | import cc.blynk.common.enums.Response; 4 | import cc.blynk.common.exceptions.BaseServerException; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/3/2015. 10 | */ 11 | public class InvalidTokenException extends BaseServerException { 12 | 13 | public InvalidTokenException(String message, int msgId) { 14 | super(message, msgId, Response.INVALID_TOKEN); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/exceptions/BaseServerException.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.exceptions; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 2/3/2015. 7 | */ 8 | public class BaseServerException extends RuntimeException { 9 | 10 | public final int msgId; 11 | public final int errorCode; 12 | 13 | public BaseServerException(String message, int msgId, int errorCode) { 14 | super(message); 15 | this.msgId = msgId; 16 | this.errorCode = errorCode; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/exceptions/UserAlreadyLoggedIn.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.exceptions; 2 | 3 | import cc.blynk.common.enums.Response; 4 | import cc.blynk.common.exceptions.BaseServerException; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/3/2015. 10 | */ 11 | public class UserAlreadyLoggedIn extends BaseServerException { 12 | 13 | public UserAlreadyLoggedIn(String message, int msgId) { 14 | super(message, msgId, Response.USER_ALREADY_LOGGED_IN); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/exceptions/UserNotAuthenticated.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.exceptions; 2 | 3 | import cc.blynk.common.enums.Response; 4 | import cc.blynk.common.exceptions.BaseServerException; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/3/2015. 10 | */ 11 | public class UserNotAuthenticated extends BaseServerException { 12 | 13 | public UserNotAuthenticated(String message, int msgId) { 14 | super(message, msgId, Response.USER_NOT_AUTHENTICATED); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/exceptions/IllegalCommandException.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.exceptions; 2 | 3 | import cc.blynk.common.enums.Response; 4 | import cc.blynk.common.exceptions.BaseServerException; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/3/2015. 10 | */ 11 | public class IllegalCommandException extends BaseServerException { 12 | 13 | public IllegalCommandException(String message, int msgId) { 14 | super(message, msgId, Response.ILLEGAL_COMMAND); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/exceptions/DeviceNotInNetworkException.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.exceptions; 2 | 3 | import cc.blynk.common.enums.Response; 4 | import cc.blynk.common.exceptions.BaseServerException; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/3/2015. 10 | */ 11 | public class DeviceNotInNetworkException extends BaseServerException { 12 | 13 | public DeviceNotInNetworkException(String message, int msgId) { 14 | super(message, msgId, Response.DEVICE_NOT_IN_NETWORK); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/exceptions/NoActiveDashboardException.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.exceptions; 2 | 3 | import cc.blynk.common.enums.Response; 4 | import cc.blynk.common.exceptions.BaseServerException; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/23/2015. 10 | */ 11 | public class NoActiveDashboardException extends BaseServerException { 12 | 13 | public NoActiveDashboardException(String message, int msgId) { 14 | super(message, msgId, Response.NO_ACTIVE_DASHBOARD); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/exceptions/TweetBodyInvalidException.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.exceptions; 2 | 3 | import cc.blynk.common.enums.Response; 4 | import cc.blynk.common.exceptions.BaseServerException; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/3/2015. 10 | */ 11 | public class TweetBodyInvalidException extends BaseServerException { 12 | 13 | public TweetBodyInvalidException(String message, int msgId) { 14 | super(message, msgId, Response.TWEET_BODY_INVALID_EXCEPTION); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/twitter/src/main/java/cc/blynk/server/exceptions/TweetNotAuthorizedException.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.exceptions; 2 | 3 | import cc.blynk.common.enums.Response; 4 | import cc.blynk.common.exceptions.BaseServerException; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/3/2015. 10 | */ 11 | public class TweetNotAuthorizedException extends BaseServerException { 12 | 13 | public TweetNotAuthorizedException(String message, int msgId) { 14 | super(message, msgId, Response.TWEET_NOT_AUTHORIZED_EXCEPTION); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/Message.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 2/1/2015. 7 | */ 8 | public abstract class Message extends MessageBase { 9 | 10 | public String body; 11 | 12 | public Message(int messageId, short command, int length, String body) { 13 | super(messageId, command, length); 14 | this.body = body; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return super.toString() + ", body='" + body + "'"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/src/main/java/cc/blynk/client/enums/ClientMode.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.client.enums; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 2/16/2015. 7 | */ 8 | public enum ClientMode { 9 | 10 | APP, HARDWARE; 11 | 12 | public static ClientMode parse(String val) { 13 | for (ClientMode clientMode : values()) { 14 | if (clientMode.name().equalsIgnoreCase(val)) { 15 | return clientMode; 16 | } 17 | } 18 | 19 | throw new RuntimeException("Wrong client mode. app and hardware only supported."); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/java/cc/blynk/server/push/response/ControlMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push.response; 2 | 3 | import cc.blynk.server.push.response.enums.ControlType; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 2/8/2015. 9 | */ 10 | public class ControlMessage extends ResponseMessageBase { 11 | 12 | public ControlType control_type; 13 | 14 | @Override 15 | public String toString() { 16 | return "ControlMessage{" + 17 | "control_type='" + control_type + "'," + 18 | super.toString() + "} "; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /integration-tests/src/test/java/cc/blynk/integration/model/SimpleClientHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.integration.model; 2 | 3 | import cc.blynk.common.model.messages.MessageBase; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 1/31/2015. 11 | */ 12 | public class SimpleClientHandler extends SimpleChannelInboundHandler { 13 | 14 | @Override 15 | public void channelRead0(ChannelHandlerContext ctx, MessageBase msg) throws Exception { 16 | //System.out.println(msg); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/protocol/PingMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages.protocol; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | 5 | import static cc.blynk.common.enums.Command.PING; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public class PingMessage extends Message { 13 | 14 | public PingMessage(int messageId, String body) { 15 | super(messageId, PING, body.length(), body); 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return "PingMessage{" + super.toString() + "}"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/protocol/hardware/TweetMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages.protocol.hardware; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | 5 | import static cc.blynk.common.enums.Command.TWEET; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public class TweetMessage extends Message { 13 | 14 | public TweetMessage(int messageId, String body) { 15 | super(messageId, TWEET, body.length(), body); 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return "TweetMessage{" + super.toString() + "}"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/protocol/appllication/LoginMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages.protocol.appllication; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | 5 | import static cc.blynk.common.enums.Command.LOGIN; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public class LoginMessage extends Message { 13 | 14 | public LoginMessage(int messageId, String body) { 15 | super(messageId, LOGIN, body.length(), body); 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return "LoginMessage{" + super.toString() + "}"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/test/java/cc/blynk/server/push/GCMWrapperTest.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push; 2 | 3 | import org.junit.Ignore; 4 | import org.junit.Test; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * The Blynk Project. 11 | * Created by Dmitriy Dumanskiy. 12 | * Created on 2/8/2015. 13 | */ 14 | public class GCMWrapperTest { 15 | 16 | @Test 17 | @Ignore 18 | public void testSendMessage() throws Exception { 19 | Map payload = new HashMap<>(); 20 | payload.put("Hello", "World"); 21 | new GCMWrapper().sendMessage("534534534534", payload); 22 | Thread.sleep(1000); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/protocol/appllication/GetTokenMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages.protocol.appllication; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | 5 | import static cc.blynk.common.enums.Command.GET_TOKEN; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public class GetTokenMessage extends Message { 13 | 14 | public GetTokenMessage(int messageId, String body) { 15 | super(messageId, GET_TOKEN, body.length(), body); 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return "GetTokenMessage{" + super.toString() + "}"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/protocol/appllication/RegisterMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages.protocol.appllication; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | 5 | import static cc.blynk.common.enums.Command.REGISTER; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public class RegisterMessage extends Message { 13 | 14 | public RegisterMessage(int messageId, String body) { 15 | super(messageId, REGISTER, body.length(), body); 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return "RegisterMessage{" + super.toString() + "}"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/twitter/src/main/java/cc/blynk/server/model/TwitterAccessToken.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 2/7/2015. 7 | */ 8 | public class TwitterAccessToken { 9 | 10 | private String token; 11 | private String tokenSecret; 12 | 13 | public TwitterAccessToken() { 14 | } 15 | 16 | public TwitterAccessToken(String token, String tokenSecret) { 17 | this.token = token; 18 | this.tokenSecret = tokenSecret; 19 | } 20 | 21 | public String getToken() { 22 | return token; 23 | } 24 | 25 | public String getTokenSecret() { 26 | return tokenSecret; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/protocol/appllication/LoadProfileMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages.protocol.appllication; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | 5 | import static cc.blynk.common.enums.Command.LOAD_PROFILE; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public class LoadProfileMessage extends Message { 13 | 14 | public LoadProfileMessage(int messageId, String body) { 15 | super(messageId, LOAD_PROFILE, body.length(), body); 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return "LoadProfileMessage{" + super.toString() + "}"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/protocol/appllication/SaveProfileMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages.protocol.appllication; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | 5 | import static cc.blynk.common.enums.Command.SAVE_PROFILE; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public class SaveProfileMessage extends Message { 13 | 14 | public SaveProfileMessage(int messageId, String body) { 15 | super(messageId, SAVE_PROFILE, body.length(), body); 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return "SaveProfileMessage{" + super.toString() + "}"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /integration-tests/src/test/java/cc/blynk/integration/model/ClientPair.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.integration.model; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 2/4/2015. 7 | */ 8 | public class ClientPair { 9 | 10 | public TestAppClient appClient; 11 | 12 | public TestHardClient hardwareClient; 13 | 14 | public String token; 15 | 16 | public ClientPair(TestAppClient appClient, TestHardClient hardwareClient, String token) { 17 | this.appClient = appClient; 18 | this.hardwareClient = hardwareClient; 19 | this.token = token; 20 | } 21 | 22 | public void stop() { 23 | appClient.stop(); 24 | hardwareClient.stop(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/java/cc/blynk/server/push/response/NACKMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push.response; 2 | 3 | import cc.blynk.server.push.response.enums.NACKError; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 2/8/2015. 9 | */ 10 | public class NACKMessage extends ResponseMessageBase { 11 | 12 | public NACKError error; 13 | 14 | public String error_description; 15 | 16 | 17 | @Override 18 | public String toString() { 19 | return "NACKMessage{" + 20 | "error='" + error + '\'' + 21 | ", error_description='" + error_description + "'," + 22 | super.toString() + "}"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/protocol/appllication/RefreshTokenMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages.protocol.appllication; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | 5 | import static cc.blynk.common.enums.Command.REFRESH_TOKEN; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public class RefreshTokenMessage extends Message { 13 | 14 | public RefreshTokenMessage(int messageId, String body) { 15 | super(messageId, REFRESH_TOKEN, body.length(), body); 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return "RefreshTokenMessage{" + super.toString() + "}"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/protocol/appllication/ActivateDashboardMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages.protocol.appllication; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | 5 | import static cc.blynk.common.enums.Command.ACTIVATE_DASHBOARD; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public class ActivateDashboardMessage extends Message { 13 | 14 | public ActivateDashboardMessage(int messageId, String body) { 15 | super(messageId, ACTIVATE_DASHBOARD, body.length(), body); 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return "ActivateDashboardMessage{" + super.toString() + "}"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/ResponseMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages; 2 | 3 | import cc.blynk.common.enums.Command; 4 | import cc.blynk.common.enums.Response; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/1/2015. 10 | */ 11 | public class ResponseMessage extends MessageBase { 12 | 13 | public ResponseMessage(int messageId, short command, int length) { 14 | super(messageId, command, length); 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return "ResponseMessage{id=" + id + 20 | ", command=" + Command.getNameByValue(command) + 21 | ", responseCode=" + Response.getNameByValue(length) + "}"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/protocol/appllication/DeActivateDashboardMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages.protocol.appllication; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | 5 | import static cc.blynk.common.enums.Command.DEACTIVATE_DASHBOARD; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public class DeActivateDashboardMessage extends Message { 13 | 14 | public DeActivateDashboardMessage(int messageId, String body) { 15 | super(messageId, DEACTIVATE_DASHBOARD, body.length(), body); 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return "DeActivateDashboardMessage{" + super.toString() + "}"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/twitter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | server 5 | cc.blynk 6 | 0.3-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | twitter 11 | 12 | 13 | 14 | 15 | org.twitter4j 16 | twitter4j-core 17 | 4.0.2 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | blynk 5 | cc.blynk 6 | 0.3-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | jar 11 | common 12 | 13 | 14 | 15 | com.codahale.metrics 16 | metrics-core 17 | 3.0.2 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /server/push-notifications-ios/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | server 5 | cc.blynk 6 | 0.3-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | push-notifications-ios 11 | 12 | 13 | 14 | com.notnoop.apns 15 | apns 16 | 1.0.0.Beta6 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/auth/Stats.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.auth; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 2/11/2015. 7 | */ 8 | //todo think about migration 9 | public class Stats { 10 | 11 | private final int[] commands; 12 | private final int[] exceptions; 13 | 14 | public Stats() { 15 | //21 - is because Commands max value is 20; 16 | this.commands = new int[21]; 17 | this.exceptions = new int[20]; 18 | } 19 | 20 | //do not expect to be incremented from different threads 21 | public void incr(short cmd) { 22 | commands[cmd]++; 23 | } 24 | 25 | public void incrException(int exceptionCode) { 26 | exceptions[exceptionCode]++; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/resources/json_test/user_profile_json_2.txt: -------------------------------------------------------------------------------- 1 | { 2 | "dashBoards":[ 3 | { 4 | "id":1, 5 | "boardType":"UNO", 6 | "widgets":[ 7 | { 8 | "x":0, 9 | "type":"SLIDER", 10 | "y":4, 11 | "id":9013884621491657959, 12 | "label":"SLIDER", 13 | "width":8, 14 | "height":1, 15 | "pin":-1 16 | }, 17 | { 18 | "x":0, 19 | "type":"BUTTON", 20 | "y":0, 21 | "id":8101118353106571483, 22 | "label":"BUTTON", 23 | "width":2, 24 | "height":2, 25 | "pin":-1 26 | } 27 | ] 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /server/twitter/src/test/java/cc/blynk/server/twitter/TwitterWrapperTest.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.twitter; 2 | 3 | import cc.blynk.server.exceptions.TweetException; 4 | import org.junit.Ignore; 5 | import org.junit.Test; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/7/2015. 11 | */ 12 | public class TwitterWrapperTest { 13 | 14 | @Test 15 | @Ignore 16 | public void testTweet() { 17 | String token = "PUT_YOUR_TOKEN_HERE"; 18 | String tokenSecret = "PUT_YOUR_TOKEN_SECRET_HERE"; 19 | new TwitterWrapper().tweet(token, tokenSecret, "Hello444", 1); 20 | } 21 | 22 | @Test(expected = TweetException.class) 23 | public void expectException() { 24 | new TwitterWrapper().tweet("", "", "Hello 123", 1); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/utils/ParseUtil.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.utils; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 1/31/2015. 7 | */ 8 | public final class ParseUtil { 9 | 10 | public static int parseInt(String intProperty) { 11 | try { 12 | return Integer.parseInt(intProperty); 13 | } catch (NumberFormatException nfe) { 14 | throw new RuntimeException(intProperty + " not a number. " + nfe.getMessage()); 15 | } 16 | } 17 | 18 | public static long parseLong(String longProperty) { 19 | try { 20 | return Integer.parseInt(longProperty); 21 | } catch (NumberFormatException nfe) { 22 | throw new RuntimeException(longProperty + " not a number. " + nfe.getMessage()); 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/auth/nio/ChannelState.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.auth.nio; 2 | 3 | import cc.blynk.server.model.auth.User; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.socket.nio.NioSocketChannel; 6 | 7 | import java.nio.channels.SocketChannel; 8 | 9 | /** 10 | * The Blynk Project. 11 | * Created by Dmitriy Dumanskiy. 12 | * Created on 2/17/2015. 13 | */ 14 | public class ChannelState extends NioSocketChannel { 15 | 16 | public boolean isHardwareChannel; 17 | 18 | /** 19 | * Used for hardware channels in order to have quick access to dashId of dashboard connect to this hardware channel. 20 | */ 21 | public Integer dashId; 22 | 23 | public User user; 24 | 25 | public ChannelState(Channel parent, SocketChannel socket) { 26 | super(parent, socket); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/enums/WidgetType.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.enums; 2 | 3 | /** 4 | * User: ddumanskiy 5 | * Date: 21.11.13 6 | * Time: 13:12 7 | */ 8 | public enum WidgetType { 9 | 10 | //controls 11 | BUTTON, 12 | SLIDER, 13 | SLIDER_LARGE, 14 | KNOB, 15 | ROTARY_KNOB, 16 | RGB, 17 | TWO_WAY_ARROW, 18 | FOUR_WAY_ARROW, 19 | ONE_AXIS_JOYSTICK, 20 | TWO_AXIS_JOYSTICK, 21 | GAMEPAD, 22 | KEYPAD, 23 | 24 | //outputs 25 | LED, 26 | DIGIT4_DISPLAY, //same as NUMERICAL_DISPLAY 27 | GAUGE, 28 | LCD_DISPLAY, 29 | GRAPH, 30 | LEVEL_DISPLAY, 31 | 32 | //inputs 33 | MICROPHONE, 34 | GYROSCOPE, 35 | ACCELEROMETER, 36 | GPS, 37 | 38 | //other 39 | TERMINAL, 40 | TWITTER, 41 | EMAIL, 42 | NOTIFICATION, 43 | SD_CARD, 44 | EVENTOR, 45 | RCT, 46 | TIMER 47 | 48 | } 49 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/java/cc/blynk/server/push/request/RequestMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push.request; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 2/8/2015. 9 | */ 10 | public class RequestMessage { 11 | 12 | public String to; 13 | 14 | public String message_id; 15 | 16 | public Map data; 17 | 18 | public long time_to_live; 19 | 20 | public boolean delay_while_idle; 21 | 22 | public boolean delivery_receipt_requested; 23 | 24 | public RequestMessage(String to, String message_id, Map data, long time_to_live, boolean delay_while_idle) { 25 | this.to = to; 26 | this.message_id = message_id; 27 | this.data = data; 28 | this.time_to_live = time_to_live; 29 | this.delay_while_idle = delay_while_idle; 30 | this.delivery_receipt_requested = false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/protocol/HardwareMessage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages.protocol; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | 5 | import static cc.blynk.common.enums.Command.HARDWARE_COMMAND; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public class HardwareMessage extends Message { 13 | 14 | public HardwareMessage(int messageId, String body) { 15 | super(messageId, HARDWARE_COMMAND, body.length(), body); 16 | } 17 | 18 | public static String attachTS(String body) { 19 | return body + '\0' + System.currentTimeMillis(); 20 | } 21 | 22 | public HardwareMessage updateMessageBody(String newBody) { 23 | this.body = newBody; 24 | this.length = newBody.length(); 25 | return this; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return "HardwareMessage{" + super.toString() + "}"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/push-notifications-android/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | server 5 | cc.blynk 6 | 0.3-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | push-notifications-android 11 | 12 | 13 | 14 | 15 | org.igniterealtime.smack 16 | smack-core 17 | 4.0.6 18 | 19 | 20 | 21 | org.igniterealtime.smack 22 | smack-tcp 23 | 4.0.6 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /client/src/main/java/cc/blynk/client/handlers/ClientReplayingMessageDecoder.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.client.handlers; 2 | 3 | import cc.blynk.common.handlers.decoders.ReplayingMessageDecoder; 4 | import io.netty.channel.ChannelHandlerContext; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | * The Blynk Project. 10 | * Created by Dmitriy Dumanskiy. 11 | * Created on 2/20/2015. 12 | */ 13 | public class ClientReplayingMessageDecoder extends ReplayingMessageDecoder { 14 | 15 | @Override 16 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 17 | throw new IOException("Server closed client connection."); 18 | } 19 | 20 | @Override 21 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 22 | //server goes down 23 | if (cause instanceof IOException) { 24 | ctx.close(); 25 | log.error("Client socket closed. Reason : {}", cause.getMessage()); 26 | //todo find better way 27 | System.exit(0); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/java/cc/blynk/server/push/response/enums/NACKError.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push.response.enums; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 2/8/2015. 7 | */ 8 | public enum NACKError { 9 | 10 | BAD_ACK("The ACK message is improperly formed."), 11 | BAD_REGISTRATION("The device has a registration ID, but it's invalid or expired."), 12 | CONNECTION_DRAINING("The message couldn't be processed because the connection is draining. "), 13 | DEVICE_UNREGISTERED("The device is not registered."), 14 | INTERNAL_SERVER_ERROR("The server encountered an error while trying to process the request."), 15 | INVALID_JSON("The JSON message payload is not valid."), 16 | DEVICE_MESSAGE_RATE_EXCEEDED("The rate of messages to a particular device is too high."), 17 | SERVICE_UNAVAILABLE("CCS is not currently able to process the message."); 18 | 19 | private String message; 20 | 21 | private NACKError(String message) { 22 | this.message = message; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/java/cc/blynk/server/push/response/ResponseMessageBase.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push.response; 2 | 3 | import com.fasterxml.jackson.annotation.JsonSubTypes; 4 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 5 | 6 | /** 7 | * The Blynk Project. 8 | * Created by Dmitriy Dumanskiy. 9 | * Created on 2/8/2015. 10 | */ 11 | @JsonTypeInfo( 12 | use = JsonTypeInfo.Id.NAME, 13 | include = JsonTypeInfo.As.PROPERTY, 14 | property = "message_type") 15 | @JsonSubTypes({ 16 | @JsonSubTypes.Type(value = ACKMessage.class, name = "ack"), 17 | @JsonSubTypes.Type(value = NACKMessage.class, name = "nack"), 18 | @JsonSubTypes.Type(value = NACKMessage.class, name = "control") 19 | }) 20 | public abstract class ResponseMessageBase { 21 | 22 | public String from; 23 | 24 | public String message_id; 25 | 26 | //public String message_type; 27 | 28 | 29 | @Override 30 | public String toString() { 31 | return "from='" + from + '\'' + 32 | ", message_id='" + message_id + '\''; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Blynk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/handlers/workflow/ClientChannelStateHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.workflow; 2 | 3 | import cc.blynk.server.dao.SessionsHolder; 4 | import cc.blynk.server.model.auth.nio.ChannelState; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | 9 | /** 10 | * The Blynk Project. 11 | * Created by Dmitriy Dumanskiy. 12 | * Created on 2/20/2015. 13 | * 14 | * Removes channel from session in case it became inactive (closed from client side). 15 | */ 16 | @ChannelHandler.Sharable 17 | public class ClientChannelStateHandler extends ChannelInboundHandlerAdapter { 18 | 19 | private final SessionsHolder sessionsHolder; 20 | 21 | public ClientChannelStateHandler(SessionsHolder sessionsHolder) { 22 | this.sessionsHolder = sessionsHolder; 23 | } 24 | 25 | @Override 26 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 27 | sessionsHolder.removeFromSession((ChannelState) ctx.channel()); 28 | super.channelInactive(ctx); 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /server/tcp-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | server 5 | cc.blynk 6 | 0.3-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | tcp-server 11 | 12 | 13 | 14 | 15 | commons-cli 16 | commons-cli 17 | 1.2 18 | 19 | 20 | 21 | cc.blynk 22 | twitter 23 | ${project.version} 24 | 25 | 26 | 27 | redis.clients 28 | jedis 29 | 2.6.2 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /integration-tests/src/test/resources/test-certs/test-server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDHjCCAgYCCQCZpSzX8AGvdDANBgkqhkiG9w0BAQsFADBRMQswCQYDVQQGEwJV 3 | QTETMBEGA1UECAwKU29tZS1TdGF0ZTENMAsGA1UEBwwES3lpdjEOMAwGA1UECgwF 4 | Qmx5bmsxDjAMBgNVBAMMBUJseW5rMB4XDTE1MDMxMDIzMzY0NVoXDTIwMDMwODIz 5 | MzY0NVowUTELMAkGA1UEBhMCVUExEzARBgNVBAgMClNvbWUtU3RhdGUxDTALBgNV 6 | BAcMBEt5aXYxDjAMBgNVBAoMBUJseW5rMQ4wDAYDVQQDDAVCbHluazCCASIwDQYJ 7 | KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKjAxLda35psH4EFkRWbnwySFf2yWcnK 8 | XPVzUrezqzTzH4qOHyUpLF0TlUdLBOAihAybLoOhsvEBFEdeqR/hZ0UUa7fQik6C 9 | jFt6C8w/IRKBbgrCjOS5yKdVTYVMZZnkFYVu0r93QA94g+mGvjSeyK1VIJFJRufJ 10 | KDojI1Z4/cdHnzDwXJBk3V9PWxLQgQlnTFHU6XQvvm69RHnos04VMROBhIIzSz1P 11 | DuGZeNTBaMOYFGFaFJPfH4Bkuv/EJtVgVJGqcY8cDupIfgd3YkPYIjTDA08vlrAr 12 | vDtY5sJqz/9uTJDTk5bdIcfHuxMhBZbtDgSnrqhg/xY14482Iz+PMgMCAwEAATAN 13 | BgkqhkiG9w0BAQsFAAOCAQEAb/4SHVNVU+wLvU8cESDewYykPk/f7U0mwrTgCzfl 14 | Gpjt1j8taRsg/nV02oOfVStHHUfH/pBFt0pOLgq/B2rfrjF6kRpVKoD/bSQkiXEz 15 | z1goVdE67s8pAUZGTj34Gdav1BIJ9gdzc4lqRbpfaVm1unpXxiOOxjzDuvksn1o3 16 | 5rI9VAhCQUZ4Cdi+uZzvADE7+Ls82CtkaaWaW4XLcKWOTCmjfFD07tgFRhiAUG2X 17 | oX/DviCn19MC0NIxprFvPuDXLJ9yTgrbk3IjbnfbVu+p7w1sxn7M4OlbR8MYfGzA 18 | l9DLfZlgB2lniqzRHiySoQEC511tB/yPgn0lZH70WV1GOg== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/resources/log4j2-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | %d{HH:mm:ss.SSS} %-5level - %msg%n 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/handlers/encoders/DeviceMessageEncoder.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.handlers.encoders; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | import cc.blynk.common.model.messages.MessageBase; 5 | import cc.blynk.common.utils.Config; 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.handler.codec.MessageToByteEncoder; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.Logger; 11 | 12 | /** 13 | * The Blynk Project. 14 | * Created by Dmitriy Dumanskiy. 15 | * Created on 2/1/2015. 16 | */ 17 | public class DeviceMessageEncoder extends MessageToByteEncoder { 18 | 19 | protected static final Logger log = LogManager.getLogger(DeviceMessageEncoder.class); 20 | 21 | @Override 22 | protected void encode(ChannelHandlerContext ctx, MessageBase message, ByteBuf out) throws Exception { 23 | out.writeByte(message.command); 24 | out.writeShort(message.id); 25 | out.writeShort(message.length); 26 | 27 | if (message.length > 0 && message instanceof Message) { 28 | out.writeBytes(((Message) message).body.getBytes(Config.DEFAULT_CHARSET)); 29 | } 30 | 31 | log.trace("Out {}", message); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/java/cc/blynk/server/push/GCMPacketExtension.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push; 2 | 3 | import org.jivesoftware.smack.packet.DefaultPacketExtension; 4 | import org.jivesoftware.smack.packet.Message; 5 | import org.jivesoftware.smack.packet.Packet; 6 | import org.jivesoftware.smack.util.StringUtils; 7 | 8 | /** 9 | * XMPP Packet Extension for GCM Cloud Connection Server. 10 | */ 11 | final class GCMPacketExtension extends DefaultPacketExtension { 12 | 13 | static final String GCM_ELEMENT_NAME = "gcm"; 14 | static final String GCM_NAMESPACE = "google:mobile:data"; 15 | 16 | private final String json; 17 | 18 | public GCMPacketExtension(String json) { 19 | super(GCM_ELEMENT_NAME, GCM_NAMESPACE); 20 | this.json = json; 21 | } 22 | 23 | public String getJson() { 24 | return json; 25 | } 26 | 27 | @Override 28 | public String toXML() { 29 | return String.format("<%s xmlns=\"%s\">%s", 30 | GCM_ELEMENT_NAME, GCM_NAMESPACE, 31 | StringUtils.escapeForXML(json), GCM_ELEMENT_NAME); 32 | } 33 | 34 | public Packet toPacket() { 35 | Message message = new Message(); 36 | message.addExtension(this); 37 | return message; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/handlers/workflow/PingHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.workflow; 2 | 3 | import cc.blynk.common.model.messages.protocol.PingMessage; 4 | import cc.blynk.common.utils.ServerProperties; 5 | import cc.blynk.server.dao.FileManager; 6 | import cc.blynk.server.dao.SessionsHolder; 7 | import cc.blynk.server.dao.UserRegistry; 8 | import cc.blynk.server.model.auth.User; 9 | import io.netty.channel.ChannelHandler; 10 | import io.netty.channel.ChannelHandlerContext; 11 | 12 | import static cc.blynk.common.enums.Response.OK; 13 | import static cc.blynk.common.model.messages.MessageFactory.produce; 14 | 15 | /** 16 | * The Blynk Project. 17 | * Created by Dmitriy Dumanskiy. 18 | * Created on 2/1/2015. 19 | * 20 | */ 21 | @ChannelHandler.Sharable 22 | public class PingHandler extends BaseSimpleChannelInboundHandler { 23 | 24 | public PingHandler(ServerProperties props, FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder) { 25 | super(props, fileManager, userRegistry, sessionsHolder); 26 | } 27 | 28 | @Override 29 | protected void messageReceived(ChannelHandlerContext ctx, User user, PingMessage message) throws Exception { 30 | ctx.channel().writeAndFlush(produce(message.id, OK)); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /common/src/test/java/cc/blynk/common/utils/StringUtilsTest.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.utils; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | import static org.junit.Assert.assertNotNull; 7 | 8 | /** 9 | * The Blynk Project. 10 | * Created by Dmitriy Dumanskiy. 11 | * Created on 2/18/2015. 12 | */ 13 | public class StringUtilsTest { 14 | 15 | @Test 16 | public void testCorrectFastSplit() { 17 | String in = "ar 1 2 3 4 5 6".replaceAll(" ", "\0"); 18 | 19 | String[] res = StringUtils.split(in, '\0'); 20 | assertEquals(7, res.length); 21 | assertEquals("ar", res[0]); 22 | assertEquals("1", res[1]); 23 | assertEquals("2", res[2]); 24 | assertEquals("3", res[3]); 25 | assertEquals("4", res[4]); 26 | assertEquals("5", res[5]); 27 | assertEquals("6", res[6]); 28 | } 29 | 30 | @Test 31 | public void testCorrectFastNewSplit() { 32 | String in = "ar 1 2 3 4 5 6".replaceAll(" ", "\0"); 33 | 34 | String res = StringUtils.split(in); 35 | assertNotNull(res); 36 | assertEquals("1", res); 37 | 38 | 39 | in = "ar 22222".replaceAll(" ", "\0"); 40 | res = StringUtils.split(in); 41 | assertNotNull(res); 42 | assertEquals("22222", res); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/auth/nio/ChannelServer.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.auth.nio; 2 | 3 | import io.netty.channel.socket.nio.NioServerSocketChannel; 4 | import io.netty.util.internal.logging.InternalLogger; 5 | import io.netty.util.internal.logging.InternalLoggerFactory; 6 | 7 | import java.nio.channels.SocketChannel; 8 | import java.util.List; 9 | 10 | /** 11 | * The Blynk Project. 12 | * Created by Dmitriy Dumanskiy. 13 | * Created on 2/17/2015. 14 | */ 15 | public class ChannelServer extends NioServerSocketChannel { 16 | 17 | private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelServer.class); 18 | 19 | @Override 20 | protected int doReadMessages(List buf) throws Exception { 21 | SocketChannel ch = javaChannel().accept(); 22 | 23 | try { 24 | if (ch != null) { 25 | buf.add(new ChannelState(this, ch)); 26 | return 1; 27 | } 28 | } catch (Throwable t) { 29 | logger.warn("Failed to create a new channel from an accepted socket.", t); 30 | 31 | try { 32 | ch.close(); 33 | } catch (Throwable t2) { 34 | logger.warn("Failed to close a socket.", t2); 35 | } 36 | } 37 | 38 | return 0; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client/src/main/resources/test.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDfjCCAmYCCQD0IGVOPMSODzANBgkqhkiG9w0BAQUFADCBgDELMAkGA1UEBhMC 3 | VUExCjAIBgNVBAgMAVwxDTALBgNVBAcMBEt5aXYxDjAMBgNVBAoMBUJseW5rMQww 4 | CgYDVQQLDANXZWIxFzAVBgNVBAMMDmNsb3VkLmJseW5rLmNjMR8wHQYJKoZIhvcN 5 | AQkBFhBkbWl0cml5QGJseW5rLmNjMB4XDTE1MDIxNTIxMjg1N1oXDTE2MDIxNTIx 6 | Mjg1N1owgYAxCzAJBgNVBAYTAlVBMQowCAYDVQQIDAFcMQ0wCwYDVQQHDARLeWl2 7 | MQ4wDAYDVQQKDAVCbHluazEMMAoGA1UECwwDV2ViMRcwFQYDVQQDDA5jbG91ZC5i 8 | bHluay5jYzEfMB0GCSqGSIb3DQEJARYQZG1pdHJpeUBibHluay5jYzCCASIwDQYJ 9 | KoZIhvcNAQEBBQADggEPADCCAQoCggEBALKSUC1+SIHV7zTKvh6xEZCwqu8hgWZN 10 | b8vnXQaXqmShVbBosTWp9ToVWUtOJPEgUxYh5WKyI/u8VVVmj5Ka7jaHOzkFhE2T 11 | usmWONr3UaQ7SMeskNViECapK5hLYnSriXM2Fjcz0PjFxx17uf18EpOPdsoIuZT2 12 | JcUtsD+kJQDK7iYkcHTqVaOVl9jfb+WDYaPBvDvQ2MY6buksv2anM6IikxQAoED/ 13 | 0D/mRNPft4QXmy78/z/f0r0QQsLeYqHN5xyhazzYlz+jzWD5vM4SjUOdFe5mYcFN 14 | GB85VZ60IRZaSLRw/YsR2edKCZftDSmgbupTMt6P/csNsscvjJUP1tcCAwEAATAN 15 | BgkqhkiG9w0BAQUFAAOCAQEAHWo2HJ8R3DxYlehxE+SWUb2ra6Y/4hhAAYzAat2o 16 | vVQzZmkmXF6rYYHUkM96JxRRFNvS6q8wfSyiXktpZwXn4Cvn7TcuMq/OBLjVBv24 17 | bc6hDhLd8yhsPTkmaDvKGO2BPHVZmM/jUxcLQfZ+/UCQM9cwzULfKVQ2oi626+48 18 | 6gRUupbEQWq1bL0kS80C0qPdjkweQBCZkotTIJKqSV1vgFDR3/V3qUKnSGq8kyEM 19 | tGRgUGuObXHK+zVZE5hz7PNOIybiKmkfZa/pahVUQE7wvQ07NTOZFNAQYFELv4Cj 20 | 9ClM64GH+E10ukvFKY8ikvxYwwVZD4WuneATusimkA0Kyg== 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/resources/json_test/user_profile_json_4.txt: -------------------------------------------------------------------------------- 1 | { 2 | "activeDashId" : 1, 3 | "dashBoards" : 4 | [ 5 | { 6 | "id":1, 7 | "name":"My Dashboard", 8 | "widgets" : [ 9 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"BUTTON", "pinType":"DIGITAL", "pin":1, "value":"1"}, 10 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER_LARGE", "pinType":"DIGITAL", "pin":2, "value":"1", "state":"ON"}, 11 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER_LARGE", "pinType":"ANALOG", "pin":3, "value":"0", "state":"OFF"}, 12 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER", "pinType":"VIRTUAL", "pin":4, "value":"244" }, 13 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"TIMER", "pinType":"DIGITAL", "pin":5, "value":"1"}, 14 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"LED", "pinType":"DIGITAL", "pin":6}, 15 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"DIGIT4_DISPLAY", "pinType":"DIGITAL", "pin":7} 16 | ], 17 | "boardType":"UNO" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /integration-tests/src/test/resources/server.properties: -------------------------------------------------------------------------------- 1 | app.ssl.enabled=true 2 | server.ssl.port=8283 3 | server.ssl.cert=/test-certs/test-server.crt 4 | server.ssl.key=/test-certs/test-server.pem 5 | server.ssl.key.pass=blynkawesome 6 | 7 | server.default.port=8282 8 | 9 | #by default System.getProperty("java.io.tmpdir")/blynk used 10 | data.folder= 11 | 12 | #defines maximum allowed number of user dashboards. Needed to limit possible number of tokens. 13 | #reloadable 14 | user.dashboard.max.limit=10 15 | 16 | #user is limited with 100 messages per second. 17 | #reloadable 18 | user.message.quota.limit=100 19 | #in case of consistent quota limit exceed during long term, sending warning response back to exceeding channel 20 | #for performance reason sending only 1 message within interval. In millis 21 | #reloadable 22 | user.message.quota.limit.exceeded.warning.period=2000 23 | 24 | #maximum size of user profile in kb's 25 | #reloadable 26 | user.profile.max.size=128 27 | 28 | #in memory storage limit for storing *read* values from hardware 29 | user.in.memory.storage.limit=1000 30 | 31 | #period in millis for saving all user DB to disk. 32 | profile.save.worker.period=100 33 | 34 | #properties for redis storage for user profile storage. 35 | redis.userprofile.storage.enabled=false 36 | redis.userprofile.host=localhost 37 | redis.userprofile.port=6379 38 | 39 | server.workers.threads=2 -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/handlers/workflow/LoadProfileHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.workflow; 2 | 3 | import cc.blynk.common.model.messages.protocol.appllication.LoadProfileMessage; 4 | import cc.blynk.common.utils.ServerProperties; 5 | import cc.blynk.server.dao.FileManager; 6 | import cc.blynk.server.dao.SessionsHolder; 7 | import cc.blynk.server.dao.UserRegistry; 8 | import cc.blynk.server.model.auth.User; 9 | import io.netty.channel.ChannelHandler; 10 | import io.netty.channel.ChannelHandlerContext; 11 | 12 | import static cc.blynk.common.model.messages.MessageFactory.produce; 13 | 14 | /** 15 | * The Blynk Project. 16 | * Created by Dmitriy Dumanskiy. 17 | * Created on 2/1/2015. 18 | * 19 | */ 20 | @ChannelHandler.Sharable 21 | public class LoadProfileHandler extends BaseSimpleChannelInboundHandler { 22 | 23 | public LoadProfileHandler(ServerProperties props, FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder) { 24 | super(props, fileManager, userRegistry, sessionsHolder); 25 | } 26 | 27 | @Override 28 | protected void messageReceived(ChannelHandlerContext ctx, User user, LoadProfileMessage message) throws Exception { 29 | String body = user.getUserProfile().toString(); 30 | ctx.writeAndFlush(produce(message.id, message.command, body)); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/workers/ShutdownHookWorker.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.workers; 2 | 3 | import cc.blynk.server.core.BaseServer; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | 7 | /** 8 | * Used to close and store all important info to disk. 9 | * 10 | * The Blynk Project. 11 | * Created by Dmitriy Dumanskiy. 12 | * Created on 25.03.15. 13 | */ 14 | public class ShutdownHookWorker implements Runnable { 15 | 16 | private final static Logger log = LogManager.getLogger(ShutdownHookWorker.class); 17 | 18 | private final BaseServer hardServer; 19 | private final BaseServer appServer; 20 | private final ProfileSaverWorker profileSaverWorker; 21 | 22 | public ShutdownHookWorker(BaseServer hardServer, BaseServer appServer, ProfileSaverWorker profileSaverWorker) { 23 | this.hardServer = hardServer; 24 | this.appServer = appServer; 25 | this.profileSaverWorker = profileSaverWorker; 26 | } 27 | 28 | @Override 29 | public void run() { 30 | log.info("Catch shutdown hook. Trying to save users and close threads."); 31 | 32 | profileSaverWorker.run(); 33 | 34 | if (hardServer != null) { 35 | hardServer.stop(); 36 | } 37 | 38 | if (appServer != null) { 39 | appServer.stop(); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/resources/server.properties: -------------------------------------------------------------------------------- 1 | app.ssl.enabled=false 2 | server.ssl.port=8443 3 | server.ssl.cert=/home/pupkin/server.crt 4 | server.ssl.key=/home/pupkin/server.pem 5 | server.ssl.key.pass= 6 | 7 | #default port for hardware devices 8 | server.default.port=8442 9 | 10 | #by default System.getProperty("java.io.tmpdir")/blynk used 11 | data.folder= 12 | 13 | #folder for logs. 14 | logs.folder=./logs 15 | 16 | #defines maximum allowed number of user dashboards. Needed to limit possible number of tokens. 17 | #reloadable 18 | user.dashboard.max.limit=10 19 | 20 | #user is limited with 100 messages per second. 21 | #reloadable 22 | user.message.quota.limit=100 23 | #in case of consistent quota limit exceed during long term, sending warning response back to exceeding channel 24 | #for performance reason sending only 1 message within interval. In millis 25 | #reloadable 26 | user.message.quota.limit.exceeded.warning.period=60000 27 | 28 | #maximum size of user profile in kb's 29 | #reloadable 30 | user.profile.max.size=128 31 | 32 | #in memory storage limit for storing *read* values from hardware 33 | user.in.memory.storage.limit=1000 34 | 35 | #period in millis for saving all user DB to disk. 36 | profile.save.worker.period=60000 37 | 38 | #properties for redis storage for user profile storage. 39 | redis.userprofile.storage.enabled=false 40 | redis.userprofile.host=localhost 41 | redis.userprofile.port=6379 -------------------------------------------------------------------------------- /integration-tests/src/test/java/cc/blynk/integration/model/MockHolder.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.integration.model; 2 | 3 | import cc.blynk.common.model.messages.MessageBase; 4 | import cc.blynk.common.model.messages.MessageFactory; 5 | 6 | import static org.mockito.Mockito.*; 7 | 8 | /** 9 | * The Blynk Project. 10 | * Created by Dmitriy Dumanskiy. 11 | * Created on 3/1/2015. 12 | */ 13 | public class MockHolder { 14 | 15 | private SimpleClientHandler mock; 16 | 17 | public MockHolder(SimpleClientHandler mock) { 18 | this.mock = mock; 19 | } 20 | 21 | public MockHolder check(int responseMessageCode) throws Exception { 22 | verify(mock).channelRead(any(), eq(MessageFactory.produce(1, responseMessageCode))); 23 | return this; 24 | } 25 | 26 | public MockHolder check(int times, int responseMessageCode) throws Exception { 27 | verify(mock, times(times)).channelRead(any(), eq(MessageFactory.produce(1, responseMessageCode))); 28 | return this; 29 | } 30 | 31 | public MockHolder check(MessageBase responseMessage) throws Exception { 32 | verify(mock).channelRead(any(), eq(responseMessage)); 33 | return this; 34 | } 35 | 36 | public MockHolder check(int times, MessageBase responseMessage) throws Exception { 37 | verify(mock, times(times)).channelRead(any(), eq(responseMessage)); 38 | return this; 39 | } 40 | 41 | } 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/src/main/java/cc/blynk/client/CommandParser.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.client; 2 | 3 | import static cc.blynk.common.enums.Command.*; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 2/1/2015. 9 | * 10 | * Convertor between user-friendly command and protocol command code 11 | */ 12 | public class CommandParser { 13 | 14 | public static Short parseCommand(String stringCommand) { 15 | switch (stringCommand.toLowerCase()) { 16 | case "hardware" : 17 | return HARDWARE_COMMAND; 18 | case "ping" : 19 | return PING; 20 | case "loadprofile" : 21 | return LOAD_PROFILE; 22 | case "saveprofile" : 23 | return SAVE_PROFILE; 24 | case "gettoken" : 25 | return GET_TOKEN; 26 | case "refreshtoken" : 27 | return REFRESH_TOKEN; 28 | case "login" : 29 | return LOGIN; 30 | case "activate" : 31 | return ACTIVATE_DASHBOARD; 32 | case "deactivate" : 33 | return DEACTIVATE_DASHBOARD; 34 | case "register" : 35 | return REGISTER; 36 | case "tweet" : 37 | return TWEET; 38 | 39 | default: 40 | throw new IllegalArgumentException("Unsupported command"); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/enums/Command.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.enums; 2 | 3 | import cc.blynk.common.utils.ReflectionUtil; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public final class Command { 13 | 14 | public static final short RESPONSE = 0; 15 | 16 | //mobile client command 17 | public static final short REGISTER = 1; 18 | public static final short LOGIN = 2; 19 | public static final short SAVE_PROFILE = 3; 20 | public static final short LOAD_PROFILE = 4; 21 | public static final short GET_TOKEN = 5; 22 | public static final short PING = 6; 23 | public static final short ACTIVATE_DASHBOARD = 7; 24 | public static final short DEACTIVATE_DASHBOARD = 8; 25 | public static final short REFRESH_TOKEN = 9; 26 | public static final short TWEET = 12; 27 | //------------------------------------------ 28 | 29 | //HARDWARE commands 30 | public static final short HARDWARE_COMMAND = 20; 31 | //------------------------------------------ 32 | 33 | //all this code just to make logging more user-friendly 34 | private static Map valuesName = ReflectionUtil.generateMapOfValueNameShort(Command.class); 35 | 36 | public static String getNameByValue(short val) { 37 | return valuesName.get(val); 38 | } 39 | //-------------------------------------------------------- 40 | 41 | } 42 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/handlers/workflow/DeActivateDashboardHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.workflow; 2 | 3 | import cc.blynk.common.model.messages.protocol.appllication.DeActivateDashboardMessage; 4 | import cc.blynk.common.utils.ServerProperties; 5 | import cc.blynk.server.dao.FileManager; 6 | import cc.blynk.server.dao.SessionsHolder; 7 | import cc.blynk.server.dao.UserRegistry; 8 | import cc.blynk.server.model.auth.User; 9 | import io.netty.channel.ChannelHandler; 10 | import io.netty.channel.ChannelHandlerContext; 11 | 12 | import static cc.blynk.common.enums.Response.OK; 13 | import static cc.blynk.common.model.messages.MessageFactory.produce; 14 | 15 | /** 16 | * The Blynk Project. 17 | * Created by Dmitriy Dumanskiy. 18 | * Created on 2/1/2015. 19 | * 20 | */ 21 | @ChannelHandler.Sharable 22 | public class DeActivateDashboardHandler extends BaseSimpleChannelInboundHandler { 23 | 24 | public DeActivateDashboardHandler(ServerProperties props, FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder) { 25 | super(props, fileManager, userRegistry, sessionsHolder); 26 | } 27 | 28 | @Override 29 | protected void messageReceived(ChannelHandlerContext ctx, User user, DeActivateDashboardMessage message) throws Exception { 30 | user.getUserProfile().setActiveDashId(null); 31 | 32 | ctx.writeAndFlush(produce(message.id, OK)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/enums/Response.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.enums; 2 | 3 | import cc.blynk.common.utils.ReflectionUtil; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/1/2015. 11 | */ 12 | public final class Response { 13 | 14 | public static final int OK = 200; 15 | public static final int TOO_MANY_REQUESTS_EXCEPTION = 1; 16 | public static final int ILLEGAL_COMMAND = 2; 17 | public static final int USER_NOT_REGISTERED = 3; 18 | public static final int USER_ALREADY_REGISTERED = 4; 19 | public static final int USER_NOT_AUTHENTICATED = 5; 20 | public static final int NOT_ALLOWED = 6; 21 | public static final int DEVICE_NOT_IN_NETWORK = 7; 22 | public static final int NO_ACTIVE_DASHBOARD = 8; 23 | public static final int INVALID_TOKEN = 9; 24 | public static final int USER_ALREADY_LOGGED_IN = 11; 25 | public static final int TWEET_EXCEPTION = 12; 26 | public static final int TWEET_BODY_INVALID_EXCEPTION = 13; 27 | public static final int TWEET_NOT_AUTHORIZED_EXCEPTION = 14; 28 | 29 | 30 | //all this code just to make logging more user-friendly 31 | private static Map valuesName = ReflectionUtil.generateMapOfValueNameInteger(Response.class); 32 | 33 | public static String getNameByValue(int val) { 34 | return valuesName.get(val); 35 | } 36 | //-------------------------------------------------------- 37 | } 38 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/resources/json_test/user_profile_json.txt: -------------------------------------------------------------------------------- 1 | { 2 | "activeDashId" : 1, 3 | "dashBoards" : 4 | [ 5 | { 6 | "id":1, 7 | "name":"My Dashboard", 8 | "widgets" : [ 9 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"BUTTON", "pinType":"DIGITAL", "pin":1, "value":"1"}, 10 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER_LARGE", "pinType":"DIGITAL", "pin":2, "value":"1", "state":"ON"}, 11 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER_LARGE", "pinType":"ANALOG", "pin":3, "value":"0", "state":"OFF"}, 12 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER", "pinType":"VIRTUAL", "pin":4, "value":"244" }, 13 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"TIMER", "pinType":"DIGITAL", "pin":5, "value":"1"}, 14 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"LED", "pinType":"DIGITAL", "pin":6}, 15 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"DIGIT4_DISPLAY", "pinType":"DIGITAL", "pin":7}, 16 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"GRAPH", "pinType":"DIGITAL", "pin":8} 17 | ], 18 | "boardType":"UNO" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /client/src/main/java/cc/blynk/client/core/HardwareClient.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.client.core; 2 | 3 | import cc.blynk.client.handlers.ClientReplayingMessageDecoder; 4 | import cc.blynk.common.handlers.encoders.DeviceMessageEncoder; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.ChannelPipeline; 7 | import io.netty.channel.socket.SocketChannel; 8 | 9 | import java.util.Random; 10 | 11 | /** 12 | * The Blynk Project. 13 | * Created by Dmitriy Dumanskiy. 14 | * Created on 11.03.15. 15 | */ 16 | public class HardwareClient extends BaseClient { 17 | 18 | public HardwareClient(String host, int port) { 19 | super(host, port, new Random()); 20 | log.info("Creating hardware client. Host : {}, port : {}", host, port); 21 | } 22 | 23 | public HardwareClient(String host, int port, Random msgIdGenerator) { 24 | super(host, port, msgIdGenerator); 25 | log.info("Creating hardware client. Host : {}, port : {}", host, port); 26 | } 27 | 28 | @Override 29 | public ChannelInitializer getChannelInitializer() { 30 | return new ChannelInitializer () { 31 | @Override 32 | public void initChannel(SocketChannel ch) throws Exception { 33 | ChannelPipeline pipeline = ch.pipeline(); 34 | pipeline.addLast(new ClientReplayingMessageDecoder()); 35 | pipeline.addLast(new DeviceMessageEncoder()); 36 | } 37 | }; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/utils/ReflectionUtil.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.utils; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * The Blynk Project. 9 | * Created by Dmitriy Dumanskiy. 10 | * Created on 2/4/2015. 11 | */ 12 | public class ReflectionUtil { 13 | 14 | /** 15 | * Used to generate map of class fields where key is field value and value is field name. 16 | */ 17 | public static Map generateMapOfValueNameInteger(Class clazz) { 18 | Map valuesName = new HashMap<>(); 19 | try { 20 | for (Field field : clazz.getFields()) { 21 | valuesName.put((Integer) field.get(int.class), field.getName()); 22 | } 23 | } catch (IllegalAccessException e) { 24 | e.printStackTrace(); 25 | } 26 | return valuesName; 27 | } 28 | 29 | /** 30 | * Used to generate map of class fields where key is field value and value is field name. 31 | */ 32 | public static Map generateMapOfValueNameShort(Class clazz) { 33 | Map valuesName = new HashMap<>(); 34 | try { 35 | for (Field field : clazz.getFields()) { 36 | valuesName.put((Short) field.get(short.class), field.getName()); 37 | } 38 | } catch (IllegalAccessException e) { 39 | e.printStackTrace(); 40 | } 41 | return valuesName; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /client/src/test/java/cc/blynk/client/ClientTest.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.client; 2 | 3 | import cc.blynk.client.core.AppClient; 4 | import cc.blynk.client.core.HardwareClient; 5 | import org.junit.Ignore; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.Mock; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import java.io.BufferedReader; 12 | 13 | import static cc.blynk.client.ClientLauncher.*; 14 | import static org.mockito.Mockito.when; 15 | 16 | /** 17 | * The Blynk Project. 18 | * Created by Dmitriy Dumanskiy. 19 | * Created on 11.03.15. 20 | */ 21 | @RunWith(MockitoJUnitRunner.class) 22 | public class ClientTest { 23 | 24 | @Mock 25 | public BufferedReader bufferedReader; 26 | 27 | @Test 28 | @Ignore 29 | public void testQuitApp() throws Exception { 30 | AppClient testAppClient = new AppClient(DEFAULT_HOST, DEFAULT_APPLICATION_PORT, false); 31 | when(bufferedReader.readLine()).thenReturn("quit"); 32 | testAppClient.start(bufferedReader); 33 | //verify(testAppClient.responseMock, never()).channelRead(any(), any()); 34 | } 35 | 36 | @Test 37 | @Ignore 38 | public void testQuitHard() throws Exception { 39 | HardwareClient testHardClient = new HardwareClient(DEFAULT_HOST, DEFAULT_HARDWARE_PORT); 40 | when(bufferedReader.readLine()).thenReturn("quit"); 41 | testHardClient.start(bufferedReader); 42 | //verify(testHardClient.responseMock, never()).channelRead(any(), any()); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.utils; 2 | 3 | /** 4 | * The Blynk Project. 5 | * Created by Dmitriy Dumanskiy. 6 | * Created on 2/18/2015. 7 | */ 8 | public class StringUtils { 9 | 10 | /** 11 | * Parses string similar to this : "xw 1 xxxx" 12 | */ 13 | private static final int startIndex = 3; 14 | 15 | /** 16 | * Fast split in java. This one is ~25% faster than s.split("\0"); 17 | * Copied from here https://gist.github.com/banthar/2923321 18 | * 19 | */ 20 | public static String[] split(String s, char delimeter) { 21 | int count = 1; 22 | 23 | for (int i = 0; i < s.length(); i++) 24 | if (s.charAt(i) == delimeter) 25 | count++; 26 | 27 | String[] array = new String[count]; 28 | 29 | int a = -1; 30 | int b = 0; 31 | 32 | for (int i = 0; i < count; i++) { 33 | 34 | while (b < s.length() && s.charAt(b) != delimeter) 35 | b++; 36 | 37 | array[i] = s.substring(a + 1, b); 38 | a = b; 39 | b++; 40 | 41 | } 42 | 43 | return array; 44 | } 45 | 46 | public static String split(String s) { 47 | int i = startIndex; 48 | while (i < s.length()) { 49 | if (s.charAt(i) == '\0') { 50 | return s.substring(startIndex, i); 51 | } 52 | i++; 53 | } 54 | 55 | return s.substring(startIndex, i); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/java/cc/blynk/server/push/LoggingConnectionListener.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | import org.jivesoftware.smack.ConnectionListener; 6 | import org.jivesoftware.smack.XMPPConnection; 7 | 8 | /** 9 | * The Blynk Project. 10 | * Created by Dmitriy Dumanskiy. 11 | * Created on 2/8/2015. 12 | */ 13 | final class LoggingConnectionListener implements ConnectionListener { 14 | 15 | private static final Logger log = LogManager.getLogger(LoggingConnectionListener.class); 16 | 17 | @Override 18 | public void connected(XMPPConnection xmppConnection) { 19 | log.info("Connected."); 20 | } 21 | 22 | @Override 23 | public void authenticated(XMPPConnection xmppConnection) { 24 | log.info("Authenticated."); 25 | } 26 | 27 | @Override 28 | public void reconnectionSuccessful() { 29 | log.info("Reconnecting.."); 30 | } 31 | 32 | @Override 33 | public void reconnectionFailed(Exception e) { 34 | log.info("Reconnection failed.. ", e); 35 | } 36 | 37 | @Override 38 | public void reconnectingIn(int seconds) { 39 | log.info("Reconnecting in {} secs", seconds); 40 | } 41 | 42 | @Override 43 | public void connectionClosedOnError(Exception e) { 44 | log.info("Connection closed on error."); 45 | } 46 | 47 | @Override 48 | public void connectionClosed() { 49 | log.info("Connection closed."); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /client/src/main/java/cc/blynk/client/HexConvertor.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.client; 2 | 3 | import cc.blynk.common.model.messages.Message; 4 | import cc.blynk.common.model.messages.MessageBase; 5 | import cc.blynk.common.model.messages.protocol.appllication.LoginMessage; 6 | import cc.blynk.common.utils.Config; 7 | 8 | import java.nio.ByteBuffer; 9 | 10 | public class HexConvertor { 11 | 12 | final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); 13 | 14 | public static void main(String args[]) { 15 | Message message = new LoginMessage(1, "username@example.com UserPassword"); 16 | System.out.println(messageToHex(message)); 17 | } 18 | 19 | 20 | public static String messageToHex(Message message) { 21 | ByteBuffer bb = ByteBuffer.allocate(MessageBase.HEADER_LENGTH + message.length); 22 | bb.put((byte) message.command); 23 | bb.putShort((short) message.id); 24 | bb.putShort((short) message.length); 25 | 26 | if (message.length > 0) { 27 | bb.put(message.body.getBytes(Config.DEFAULT_CHARSET)); 28 | } 29 | 30 | return bytesToHex(bb.array()); 31 | } 32 | 33 | public static String bytesToHex(byte[] bytes) { 34 | char[] hexChars = new char[bytes.length * 2]; 35 | for ( int j = 0; j < bytes.length; j++ ) { 36 | int v = bytes[j] & 0xFF; 37 | hexChars[j * 2] = hexArray[v >>> 4]; 38 | hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 39 | } 40 | return new String(hexChars); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /integration-tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | blynk 5 | cc.blynk 6 | 0.3-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | integration-tests 11 | 12 | 13 | 14 | cc.blynk 15 | common 16 | ${project.version} 17 | 18 | 19 | cc.blynk 20 | client 21 | ${project.version} 22 | 23 | 24 | cc.blynk 25 | tcp-server 26 | ${project.version} 27 | 28 | 29 | cc.blynk 30 | twitter 31 | ${project.version} 32 | 33 | 34 | commons-io 35 | commons-io 36 | 2.4 37 | test 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/java/cc/blynk/server/push/GCMWrapper.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push; 2 | 3 | import cc.blynk.common.utils.ServerProperties; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | 7 | import java.util.Map; 8 | 9 | import static cc.blynk.server.push.GCMSmackCcsClient.createRequest; 10 | 11 | /** 12 | * The Blynk Project. 13 | * Created by Dmitriy Dumanskiy. 14 | * Created on 2/8/2015. 15 | */ 16 | public class GCMWrapper { 17 | 18 | private static final Logger log = LogManager.getLogger(GCMSmackCcsClient.class); 19 | 20 | private static final String filePropertiesName = "gcm.properties"; 21 | private GCMSmackCcsClient ccsClient; 22 | 23 | public GCMWrapper() { 24 | ServerProperties props = new ServerProperties(filePropertiesName); 25 | 26 | this.ccsClient = new GCMSmackCcsClient(props.getProperty("gcm.server"), props.getIntProperty("gcm.port")); 27 | 28 | try { 29 | ccsClient.connect(props.getLongProperty("gcm.project.id"), props.getProperty("gcm.api.key")); 30 | } catch (Exception e) { 31 | log.error("Error connecting to google push server.", e); 32 | } 33 | } 34 | 35 | public void sendMessage(String toRegId, Map payload) throws Exception { 36 | final String messageId = GCMSmackCcsClient.generateUniqueMesageId(); 37 | final long timeToLive = 86400; 38 | 39 | String message = createRequest(toRegId, messageId, payload, timeToLive, true); 40 | ccsClient.sendDownstreamMessage(message); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /integration-tests/src/test/resources/json_test/user_profile_json.txt: -------------------------------------------------------------------------------- 1 | { 2 | "dashBoards" : 3 | [ 4 | { 5 | "id":1, 6 | "name":"My Dashboard", 7 | "widgets" : [ 8 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"BUTTON", "pinType":"DIGITAL", "pin":1, "value":"1"}, 9 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER_LARGE", "pinType":"DIGITAL", "pin":2, "value":"1", "state":"ON"}, 10 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER_LARGE", "pinType":"ANALOG", "pin":3, "value":"0", "state":"OFF"}, 11 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER", "pinType":"VIRTUAL", "pin":4, "value":"244" }, 12 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"TIMER", "pinType":"DIGITAL", "pin":5, "value":"1", "startTime":0}, 13 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"LED", "pinType":"DIGITAL", "pin":6}, 14 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"DIGIT4_DISPLAY", "pinType":"DIGITAL", "pin":7}, 15 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"GRAPH", "pinType":"DIGITAL", "pin":8} 16 | ], 17 | "settings" : {"boardType":"UNO", "someParam":"someValue"} 18 | } 19 | ], 20 | "graphPins":{"1":[8]}, 21 | "twitter" : { 22 | "token" : "123", 23 | "tokenSecret" : "123" 24 | } 25 | } -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/utils/Finder.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.utils; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | 6 | import java.io.IOException; 7 | import java.nio.file.*; 8 | import java.nio.file.attribute.BasicFileAttributes; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import static java.nio.file.FileVisitResult.CONTINUE; 13 | 14 | /** 15 | * Utility class to walk through all files within folder by pattern 16 | * 17 | * User: ddumanskiy 18 | * Date: 09.12.13 19 | * Time: 7:51 20 | */ 21 | public class Finder extends SimpleFileVisitor { 22 | 23 | private static final Logger log = LogManager.getLogger(Finder.class); 24 | 25 | private final PathMatcher matcher; 26 | private final List foundFiles = new ArrayList<>(); 27 | 28 | public Finder(String pattern) { 29 | matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern); 30 | } 31 | 32 | // Compares the glob pattern against 33 | // the file or directory name. 34 | private void find(Path file) { 35 | Path name = file.getFileName(); 36 | if (name != null && matcher.matches(name)) { 37 | foundFiles.add(file); 38 | } 39 | } 40 | 41 | // Invoke the pattern matching method on each file. 42 | @Override 43 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { 44 | find(file); 45 | return CONTINUE; 46 | } 47 | 48 | @Override 49 | public FileVisitResult visitFileFailed(Path file, IOException exc) { 50 | log.error(exc); 51 | return CONTINUE; 52 | } 53 | 54 | public List getFoundFiles() { 55 | return foundFiles; 56 | } 57 | } -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/MessageBase.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages; 2 | 3 | import cc.blynk.common.enums.Command; 4 | 5 | /** 6 | * The Blynk Project. 7 | * Created by Dmitriy Dumanskiy. 8 | * Created on 2/1/2015. 9 | * 10 | * Yes, I don't use getters and setters, inlining is not always works as expected. 11 | * 12 | * IMPORTANT : have in mind, in body we retrieve always unsigned bytes, shorts, while in java 13 | * is only signed types, so we require 2 times larger types. 14 | */ 15 | public abstract class MessageBase { 16 | 17 | //1 + 2 + 2 18 | public static final int HEADER_LENGTH = 5; 19 | 20 | public short command; 21 | 22 | public int id; 23 | 24 | public int length; 25 | 26 | public MessageBase(int id, short command, int length) { 27 | this.command = command; 28 | this.id = id; 29 | this.length = length; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "id=" + id + 35 | ", command=" + Command.getNameByValue(command) + 36 | ", length=" + length; 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | if (this == o) return true; 42 | if (o == null || getClass() != o.getClass()) return false; 43 | 44 | MessageBase that = (MessageBase) o; 45 | 46 | if (command != that.command) return false; 47 | if (id != that.id) return false; 48 | if (length != that.length) return false; 49 | 50 | return true; 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | int result = (int) command; 56 | result = 31 * result + id; 57 | result = 31 * result + length; 58 | return result; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/java/cc/blynk/server/utils/EMailValidationTest.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.utils; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertFalse; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | /** 9 | * User: ddumanskiy 10 | * Date: 8/11/13 11 | * Time: 6:43 PM 12 | */ 13 | public class EMailValidationTest { 14 | 15 | @Test 16 | public void testAllValid() { 17 | String[] mailList = new String[] { 18 | "1@mail.ru", 19 | "google@gmail.com", 20 | "dsasd234e021-0+@mail.ua", 21 | "ddd@yahoo.com", 22 | "mmmm@yahoo.com", 23 | "mmmmm-100@yahoo.com", 24 | "mmmmm.100@yahoo.com", 25 | "mmmm111@mmmm.com", 26 | "mmmm-100@mmmm.net", 27 | "mmmm.100@mmmm.com.au", 28 | "mmmm@1.com", 29 | "mmmm@gmail.com.com", 30 | "mmmm+100@gmail.com", 31 | "mmmm-100@yahoo-test.com" 32 | }; 33 | 34 | for (String email : mailList) { 35 | assertTrue(email, EMailValidator.isValid(email)); 36 | } 37 | } 38 | 39 | @Test 40 | public void testAllInValid() { 41 | String[] mailList = new String[] { 42 | "mmmm", 43 | "mmmm@.com.my", 44 | "mmmm123@.com", 45 | "mmmm123@.com.com", 46 | ".mmmm@mmmm.com", 47 | "mmmm()*@gmail.com", 48 | "mmmm..2002@gmail.com", 49 | "mmmm.@gmail.com", 50 | "mmmm@mmmm@gmail.com" 51 | }; 52 | 53 | for (String email : mailList) { 54 | assertFalse(email, EMailValidator.isValid(email)); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/java/cc/blynk/server/handlers/workflow/SaveProfileHandlerTest.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.workflow; 2 | 3 | import cc.blynk.common.model.messages.protocol.appllication.SaveProfileMessage; 4 | import cc.blynk.server.TestBase; 5 | import cc.blynk.server.exceptions.IllegalCommandException; 6 | import cc.blynk.server.exceptions.NotAllowedException; 7 | import org.junit.Test; 8 | 9 | import static cc.blynk.common.enums.Command.SAVE_PROFILE; 10 | import static cc.blynk.common.model.messages.MessageFactory.produce; 11 | 12 | /** 13 | * The Blynk Project. 14 | * Created by Dmitriy Dumanskiy. 15 | * Created on 2/26/2015. 16 | */ 17 | public class SaveProfileHandlerTest extends TestBase { 18 | 19 | private SaveProfileHandler saveProfileHandler = new SaveProfileHandler(props, null, null, null); 20 | 21 | @Test(expected = NotAllowedException.class) 22 | public void testTooBigUserProfile() throws Exception { 23 | StringBuilder tmp = new StringBuilder(); 24 | for (int i = 0; i < props.getIntProperty("user.profile.max.size") * 1024 + 1; i++) { 25 | tmp.append('a'); 26 | } 27 | 28 | SaveProfileMessage msg = (SaveProfileMessage) produce(1, SAVE_PROFILE, tmp.toString()); 29 | saveProfileHandler.messageReceived(null, null, msg); 30 | } 31 | 32 | @Test(expected = IllegalCommandException.class) 33 | public void testIllegalProfile() throws Exception { 34 | StringBuilder tmp = new StringBuilder(); 35 | for (int i = 0; i < props.getIntProperty("user.profile.max.size") * 1024; i++) { 36 | tmp.append('a'); 37 | } 38 | 39 | SaveProfileMessage msg = (SaveProfileMessage) produce(1, SAVE_PROFILE, tmp.toString()); 40 | saveProfileHandler.messageReceived(null, null, msg); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/java/cc/blynk/server/workers/PropertiesChangeWatcherWorkerTest.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.workers; 2 | 3 | import cc.blynk.server.handlers.workflow.BaseSimpleChannelInboundHandler; 4 | import org.junit.Ignore; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.Mock; 8 | import org.mockito.runners.MockitoJUnitRunner; 9 | 10 | import java.io.IOException; 11 | import java.io.OutputStream; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.nio.file.Paths; 15 | 16 | import static org.mockito.Mockito.*; 17 | 18 | /** 19 | * The Blynk Project. 20 | * Created by Dmitriy Dumanskiy. 21 | * Created on 2/26/2015. 22 | */ 23 | @RunWith(MockitoJUnitRunner.class) 24 | public class PropertiesChangeWatcherWorkerTest { 25 | 26 | @Mock 27 | private BaseSimpleChannelInboundHandler fakeHandler; 28 | 29 | @Test 30 | @Ignore 31 | public void testPropertiesChanged() throws IOException { 32 | Path tempDir = Files.createTempDirectory("tmp"); 33 | Path tmpFile = Files.createFile(Paths.get(tempDir.toString(), "temp.txt")); 34 | 35 | 36 | System.setProperty("user.dir", tempDir.toString()); 37 | new Thread( new PropertiesChangeWatcherWorker(tmpFile.getFileName().toString(), fakeHandler)).start(); 38 | 39 | 40 | try { 41 | Thread.sleep(1000); 42 | } catch (InterruptedException e) { 43 | } 44 | 45 | try (OutputStream outputStream = Files.newOutputStream(tmpFile)) { 46 | outputStream.write(new byte[1]); 47 | } catch (Exception e) { 48 | e.printStackTrace(); 49 | } 50 | 51 | //todo find out why it called 2 times 52 | verify(fakeHandler, timeout(5000).atLeastOnce()).updateProperties(any()); 53 | 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/handlers/workflow/GetTokenHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.workflow; 2 | 3 | import cc.blynk.common.model.messages.protocol.appllication.GetTokenMessage; 4 | import cc.blynk.common.utils.ServerProperties; 5 | import cc.blynk.server.dao.FileManager; 6 | import cc.blynk.server.dao.SessionsHolder; 7 | import cc.blynk.server.dao.UserRegistry; 8 | import cc.blynk.server.exceptions.NotAllowedException; 9 | import cc.blynk.server.model.auth.User; 10 | import io.netty.channel.ChannelHandler; 11 | import io.netty.channel.ChannelHandlerContext; 12 | 13 | import static cc.blynk.common.model.messages.MessageFactory.produce; 14 | 15 | /** 16 | * The Blynk Project. 17 | * Created by Dmitriy Dumanskiy. 18 | * Created on 2/1/2015. 19 | * 20 | */ 21 | @ChannelHandler.Sharable 22 | public class GetTokenHandler extends BaseSimpleChannelInboundHandler { 23 | 24 | public GetTokenHandler(ServerProperties props, FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder) { 25 | super(props, fileManager, userRegistry, sessionsHolder); 26 | } 27 | 28 | @Override 29 | protected void messageReceived(ChannelHandlerContext ctx, User user, GetTokenMessage message) throws Exception { 30 | String dashBoardIdString = message.body; 31 | 32 | int dashBoardId; 33 | try { 34 | dashBoardId = Integer.parseInt(dashBoardIdString); 35 | } catch (NumberFormatException ex) { 36 | throw new NotAllowedException(String.format("Dash board id '%s' not valid.", dashBoardIdString), message.id); 37 | } 38 | 39 | user.getUserProfile().validateDashId(dashBoardId, message.id); 40 | 41 | String token = userRegistry.getToken(user, dashBoardId); 42 | 43 | ctx.writeAndFlush(produce(message.id, message.command, token)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/core/hardware/HardwareServer.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.core.hardware; 2 | 3 | import cc.blynk.common.stats.GlobalStats; 4 | import cc.blynk.common.utils.ServerProperties; 5 | import cc.blynk.server.core.BaseServer; 6 | import cc.blynk.server.dao.FileManager; 7 | import cc.blynk.server.dao.SessionsHolder; 8 | import cc.blynk.server.dao.UserRegistry; 9 | import cc.blynk.server.handlers.workflow.BaseSimpleChannelInboundHandler; 10 | import io.netty.channel.ChannelInitializer; 11 | import io.netty.channel.socket.SocketChannel; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * The Blynk Project. 17 | * Created by Dmitriy Dumanskiy. 18 | * Created on 2/1/2015. 19 | */ 20 | public class HardwareServer extends BaseServer { 21 | 22 | private final HardwareHandlersHolder handlersHolder; 23 | private final ChannelInitializer channelInitializer; 24 | 25 | public HardwareServer(ServerProperties props, FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder, GlobalStats stats) { 26 | super(props.getIntProperty("server.default.port"), props); 27 | 28 | this.handlersHolder = new HardwareHandlersHolder(props, fileManager, userRegistry, sessionsHolder); 29 | this.channelInitializer = new HardwareChannelInitializer(sessionsHolder, stats, handlersHolder); 30 | 31 | log.info("Hardware server port {}.", port); 32 | } 33 | 34 | @Override 35 | public List getBaseHandlers() { 36 | return handlersHolder.getBaseHandlers(); 37 | } 38 | 39 | @Override 40 | public ChannelInitializer getChannelInitializer() { 41 | return channelInitializer; 42 | } 43 | 44 | @Override 45 | public void stop() { 46 | log.info("Shutting down default server..."); 47 | super.stop(); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/core/hardware/HardwareChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.core.hardware; 2 | 3 | import cc.blynk.common.handlers.decoders.ReplayingMessageDecoder; 4 | import cc.blynk.common.handlers.encoders.DeviceMessageEncoder; 5 | import cc.blynk.common.stats.GlobalStats; 6 | import cc.blynk.server.dao.SessionsHolder; 7 | import cc.blynk.server.handlers.workflow.ClientChannelStateHandler; 8 | import io.netty.channel.ChannelInitializer; 9 | import io.netty.channel.ChannelPipeline; 10 | import io.netty.channel.socket.SocketChannel; 11 | import io.netty.handler.timeout.ReadTimeoutHandler; 12 | 13 | /** 14 | * The Blynk Project. 15 | * Created by Dmitriy Dumanskiy. 16 | * Created on 11.03.15. 17 | */ 18 | final class HardwareChannelInitializer extends ChannelInitializer { 19 | 20 | private final SessionsHolder sessionsHolder; 21 | private final GlobalStats stats; 22 | private final HardwareHandlersHolder handlersHolder; 23 | 24 | public HardwareChannelInitializer(SessionsHolder sessionsHolder, GlobalStats stats, HardwareHandlersHolder handlersHolder) { 25 | this.sessionsHolder = sessionsHolder; 26 | this.stats = stats; 27 | this.handlersHolder = handlersHolder; 28 | } 29 | 30 | @Override 31 | protected void initChannel(SocketChannel ch) throws Exception { 32 | ChannelPipeline pipeline = ch.pipeline(); 33 | 34 | //non-sharable handlers 35 | //todo apply from hardware. 36 | pipeline.addLast(new ReadTimeoutHandler(15)); 37 | pipeline.addLast(new ClientChannelStateHandler(sessionsHolder)); 38 | pipeline.addLast(new ReplayingMessageDecoder(stats)); 39 | pipeline.addLast(new DeviceMessageEncoder()); 40 | 41 | //sharable business logic handlers initialized previously 42 | handlersHolder.getAllHandlers().forEach(pipeline::addLast); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/handlers/workflow/RefreshTokenHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.workflow; 2 | 3 | import cc.blynk.common.model.messages.protocol.appllication.RefreshTokenMessage; 4 | import cc.blynk.common.utils.ServerProperties; 5 | import cc.blynk.server.dao.FileManager; 6 | import cc.blynk.server.dao.SessionsHolder; 7 | import cc.blynk.server.dao.UserRegistry; 8 | import cc.blynk.server.exceptions.NotAllowedException; 9 | import cc.blynk.server.model.auth.User; 10 | import io.netty.channel.ChannelHandler; 11 | import io.netty.channel.ChannelHandlerContext; 12 | 13 | import static cc.blynk.common.model.messages.MessageFactory.produce; 14 | 15 | /** 16 | * The Blynk Project. 17 | * Created by Dmitriy Dumanskiy. 18 | * Created on 2/1/2015. 19 | * 20 | */ 21 | @ChannelHandler.Sharable 22 | public class RefreshTokenHandler extends BaseSimpleChannelInboundHandler { 23 | 24 | public RefreshTokenHandler(ServerProperties props, FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder) { 25 | super(props, fileManager, userRegistry, sessionsHolder); 26 | } 27 | 28 | @Override 29 | protected void messageReceived(ChannelHandlerContext ctx, User user, RefreshTokenMessage message) throws Exception { 30 | String dashBoardIdString = message.body; 31 | 32 | int dashBoardId; 33 | try { 34 | dashBoardId = Integer.parseInt(dashBoardIdString); 35 | } catch (NumberFormatException ex) { 36 | throw new NotAllowedException(String.format("Dash board id '%s' not valid.", dashBoardIdString), message.id); 37 | } 38 | 39 | user.getUserProfile().validateDashId(dashBoardId, message.id); 40 | 41 | String token = userRegistry.refreshToken(user, dashBoardId); 42 | 43 | ctx.writeAndFlush(produce(message.id, message.command, token)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /integration-tests/src/test/resources/test-certs/test-server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIE6TAbBgkqhkiG9w0BBQMwDgQIcEjbuxRT1cwCAggABIIEyGsmWTn0qs7DmPNE 3 | OAQzOUHNHnHnV66pvNaFNGn7LLWHxZa0F9KA+wSZ8/eWIgJJniAtSxBEzh4Eb6Um 4 | 28dq+AvkEb0hLKoQqLt5p/TmS9e0jJ+9eR0Cv0SZx13gPkOSXi4EoLmnCgbUw0Cu 5 | KSPEa17DsdXq0kt8PtO8XIznfXTVOdlWF5debKetEcTWW+9MXeU5Qz2WNSEKRhrD 6 | dmy25feYg6O6lR+3tJ5aJTCoGsep1Et6Hmcez+6qP2yd8jYEyluLa4L+tkMO2s3H 7 | TJY60+6/RbfQvUlzgvU0PFca+hlu6PUXGVMnujGAN7yyGBfkpswErpAQOf/deXtx 8 | Ub/UOOU2Virsm1Df13FCqK2637HECTanyzle6yH6TtjMuWRzBrkWKvc+7RDuPEW1 9 | p5WHrX/PKRPhYFXjXNnaGjcXFxCmC1aYN6DsaNvIPkovS8sFtOdc80l1rQidK1sa 10 | fsEvYey7s9v3btEs5hrp0+EyT80hY3jKjVpAXnGdCTtWGKC8qczd6dWHz2A6oM0o 11 | KI5L579jI9QcA3Wcym/+SX95idp0sXzlvnMgaPgmXCD0k5CiEUd7id6rCoMlRqGg 12 | 0sw/qAUkRSbGFLpE1aU45iuNCrv0tZvB64Ytf47k1KRrClxJUEHsJyitNDt5y0Vj 13 | 7BUcXSsESUqX/cT9KqQ1F583mVF83Ezi/YUSVJTSsmC1xkgDK1dY/MKMc3pbQhrd 14 | yZwqF/ZI2YpD3IFxSvFDS4/pvqCypqVUi4wvz5Ennsqk5Is0k/L0oDm4SjvKD0Qo 15 | crhwg05EVvkUlQeyVQgB6bIWf0hck27tSL/6pWMG4MjJqX6T17S/oFV1VZFVSSy9 16 | 0YF3kHb7aN4otTI+hh2H0DvoMXSyIcepNNbB+3ANEs/vv8v/qdPug+VHRaVuSUOl 17 | JPDpUhPPmeof+YPfCvqwcRO8mNsqTQlQJCRRlQqELETSjFgcndjWY7klzhBZWeJG 18 | 1nKdB31qCZqyQTSg112a7TP70E4xJlRKjefafVahAl0PSwxhJC6uwNZxlTy8YMhR 19 | yVbqyHtTjkuknL3gTxlqdAjsFiJnCwGNkA8RzWjCaSdaRcFzDtkKrmuQFrClXRYP 20 | 2V71KtfhoCllEcQQOlkZfojbVhP4L0IT6rgRV+DyTiQ5IeRX50u3toGHBmQl/XyX 21 | oFeAQ4cfaYMLGnAz1k1cJsrP+teTyZsyjQI/lohrDxjxyehFb7lHaqbOuDu2mCzJ 22 | TDj1olaBMw9KOjRNjtVkurTehp1ZNMYt5cG/Xr/FT+7auR2ZZ5IiiSOCddAfAstF 23 | 0gpUaUJCOkpYnIyTJ+zYM7+RAlSwXfUc3xE5eT6Th89YXtJ75pozwCJ9PSviltDJ 24 | YPy95MRbuZUqeBJi8OgqqarYJs0xPALcZDhmLLyOxY2/TDFjjZ+QbI+BrkUMHCLo 25 | rN/8c4S8XpgZWnaupxfWPFmcDgPoQb/fDLfIKQV8pO/6xqMlLVdt0R2k0Eft6Bcc 26 | HyNsBJlWiTsuOlhOU14nbhcbyxTPiZCzOy4hNyO8UZ8mbWw25dzuN1hk+iXv0rzH 27 | iZ4nge6RVeA35e1QXRv3yH5hMevEVa8hPtD1+nPNHgBje5ane0M/Ec/Ei8C5ByjL 28 | dPSZKJ8PZzxRhbga4A== 29 | -----END ENCRYPTED PRIVATE KEY----- 30 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/ArgumentsParser.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server; 2 | 3 | import cc.blynk.common.utils.ParseUtil; 4 | import org.apache.commons.cli.BasicParser; 5 | import org.apache.commons.cli.CommandLine; 6 | import org.apache.commons.cli.Options; 7 | import org.apache.commons.cli.ParseException; 8 | 9 | import java.util.Properties; 10 | 11 | /** 12 | * Simple class for program arguments parsing. 13 | * 14 | * The Blynk Project. 15 | * Created by Dmitriy Dumanskiy. 16 | * Created on 25.03.15. 17 | */ 18 | public class ArgumentsParser { 19 | 20 | private final Options options; 21 | 22 | ArgumentsParser() { 23 | options = new Options(); 24 | options.addOption("hardPort", true, "Hardware server port.") 25 | .addOption("appPort", true, "Application server port.") 26 | .addOption("workerThreads", true, "Server worker threads.") 27 | .addOption("disableAppSsl", false, "Disables SSL for app mode."); 28 | } 29 | 30 | 31 | void processArguments(String[] args, Properties serverProperties) throws ParseException { 32 | CommandLine cmd = new BasicParser().parse(options, args); 33 | 34 | String hardPort = cmd.getOptionValue("hardPort"); 35 | String appPort = cmd.getOptionValue("appPort"); 36 | String workerThreadsString = cmd.getOptionValue("workerThreads"); 37 | 38 | if (hardPort != null) { 39 | ParseUtil.parseInt(hardPort); 40 | serverProperties.put("server.default.port", hardPort); 41 | } 42 | if (appPort != null) { 43 | ParseUtil.parseInt(appPort); 44 | serverProperties.put("server.ssl.port", appPort); 45 | } 46 | if (workerThreadsString != null) { 47 | ParseUtil.parseInt(workerThreadsString); 48 | serverProperties.put("server.worker.threads", workerThreadsString); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/dao/SessionsHolder.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.dao; 2 | 3 | import cc.blynk.server.model.auth.Session; 4 | import cc.blynk.server.model.auth.User; 5 | import cc.blynk.server.model.auth.nio.ChannelState; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | import java.util.Map; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | /** 13 | * Holds session info related to specific user. 14 | * 15 | * The Blynk Project. 16 | * Created by Dmitriy Dumanskiy. 17 | * Created on 2/18/2015. 18 | */ 19 | public class SessionsHolder { 20 | 21 | private static final Logger log = LogManager.getLogger(SessionsHolder.class); 22 | 23 | private Map userSession = new ConcurrentHashMap<>(); 24 | 25 | public void addChannelToGroup(User user, ChannelState channel, int msgId) { 26 | Session session = getSessionByUser(user); 27 | session.addChannel(channel, msgId); 28 | } 29 | 30 | public void removeFromSession(ChannelState channel) { 31 | if (channel.user != null) { 32 | Session session = userSession.get(channel.user); 33 | if (session != null) { 34 | session.remove(channel); 35 | } 36 | } 37 | } 38 | 39 | public Map getUserSession() { 40 | return userSession; 41 | } 42 | 43 | //threadsafe 44 | private Session getSessionByUser(User user) { 45 | Session group = userSession.get(user); 46 | //only one side came 47 | if (group == null) { 48 | Session value = new Session(); 49 | group = userSession.putIfAbsent(user, value); 50 | if (group == null) { 51 | log.trace("Creating unique session for user: {}", user); 52 | return value; 53 | } 54 | } 55 | 56 | return group; 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /server/twitter/src/main/java/cc/blynk/server/twitter/TwitterWrapper.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.twitter; 2 | 3 | import cc.blynk.server.exceptions.TweetException; 4 | import cc.blynk.server.exceptions.TweetNotAuthorizedException; 5 | import cc.blynk.server.model.TwitterAccessToken; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | import twitter4j.Twitter; 9 | import twitter4j.TwitterException; 10 | import twitter4j.TwitterFactory; 11 | import twitter4j.auth.AccessToken; 12 | 13 | /** 14 | * The Blynk Project. 15 | * Created by Dmitriy Dumanskiy. 16 | * Created on 2/6/2015. 17 | */ 18 | public class TwitterWrapper { 19 | 20 | private static final Logger log = LogManager.getLogger(TwitterWrapper.class); 21 | 22 | // The factory instance is re-useable and thread safe. 23 | private final TwitterFactory factory = new TwitterFactory(); 24 | 25 | public void tweet(TwitterAccessToken twitterAccessToken, String message, int msgId) { 26 | if (twitterAccessToken == null || 27 | twitterAccessToken.getToken() == null || twitterAccessToken.getToken().equals("") || 28 | twitterAccessToken.getTokenSecret() == null || twitterAccessToken.getTokenSecret().equals("")) { 29 | throw new TweetNotAuthorizedException("User has no access token provided.", msgId); 30 | } 31 | tweet(twitterAccessToken.getToken(), twitterAccessToken.getTokenSecret(), message, msgId); 32 | } 33 | 34 | public void tweet(String token, String tokenSecret, String message, int msgId) { 35 | AccessToken accessToken = new AccessToken(token, tokenSecret); 36 | Twitter twitter = factory.getInstance(); 37 | twitter.setOAuthAccessToken(accessToken); 38 | try { 39 | twitter.updateStatus(message); 40 | } catch (TwitterException e) { 41 | throw new TweetException(e.getMessage(), msgId); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/handlers/workflow/TweetHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.workflow; 2 | 3 | import cc.blynk.common.model.messages.protocol.hardware.TweetMessage; 4 | import cc.blynk.common.utils.ServerProperties; 5 | import cc.blynk.server.dao.FileManager; 6 | import cc.blynk.server.dao.SessionsHolder; 7 | import cc.blynk.server.dao.UserRegistry; 8 | import cc.blynk.server.exceptions.TweetBodyInvalidException; 9 | import cc.blynk.server.model.auth.User; 10 | import cc.blynk.server.twitter.TwitterWrapper; 11 | import io.netty.channel.ChannelHandler; 12 | import io.netty.channel.ChannelHandlerContext; 13 | 14 | import static cc.blynk.common.enums.Response.OK; 15 | import static cc.blynk.common.model.messages.MessageFactory.produce; 16 | 17 | /** 18 | * The Blynk Project. 19 | * Created by Dmitriy Dumanskiy. 20 | * Created on 2/1/2015. 21 | * 22 | */ 23 | @ChannelHandler.Sharable 24 | public class TweetHandler extends BaseSimpleChannelInboundHandler { 25 | 26 | private final TwitterWrapper twitterWrapper; 27 | 28 | public TweetHandler(ServerProperties props, FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder, TwitterWrapper twitterWrapper) { 29 | super(props, fileManager, userRegistry, sessionsHolder); 30 | this.twitterWrapper = twitterWrapper; 31 | } 32 | 33 | @Override 34 | protected void messageReceived(ChannelHandlerContext ctx, User user, TweetMessage message) throws Exception { 35 | if (message.body == null || message.body.equals("") || message.body.length() > 140) { 36 | throw new TweetBodyInvalidException("Tweet message is empty or larger 140 chars", message.id); 37 | } 38 | twitterWrapper.tweet(user.getUserProfile().getTwitter(), message.body, message.id); 39 | log.debug("Tweet for user {}, with message : '{}', successfully was sent.", user.getName(), message.body); 40 | 41 | ctx.channel().writeAndFlush(produce(message.id, OK)); 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/handlers/workflow/ActivateDashboardHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.workflow; 2 | 3 | import cc.blynk.common.model.messages.protocol.appllication.ActivateDashboardMessage; 4 | import cc.blynk.common.utils.ServerProperties; 5 | import cc.blynk.server.dao.FileManager; 6 | import cc.blynk.server.dao.SessionsHolder; 7 | import cc.blynk.server.dao.UserRegistry; 8 | import cc.blynk.server.exceptions.IllegalCommandException; 9 | import cc.blynk.server.model.auth.User; 10 | import io.netty.channel.ChannelHandler; 11 | import io.netty.channel.ChannelHandlerContext; 12 | 13 | import static cc.blynk.common.enums.Response.OK; 14 | import static cc.blynk.common.model.messages.MessageFactory.produce; 15 | 16 | /** 17 | * The Blynk Project. 18 | * Created by Dmitriy Dumanskiy. 19 | * Created on 2/1/2015. 20 | * 21 | */ 22 | @ChannelHandler.Sharable 23 | public class ActivateDashboardHandler extends BaseSimpleChannelInboundHandler { 24 | 25 | public ActivateDashboardHandler(ServerProperties props, FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder) { 26 | super(props, fileManager, userRegistry, sessionsHolder); 27 | } 28 | 29 | @Override 30 | protected void messageReceived(ChannelHandlerContext ctx, User user, ActivateDashboardMessage message) throws Exception { 31 | String dashBoardIdString = message.body; 32 | 33 | int dashBoardId; 34 | try { 35 | dashBoardId = Integer.parseInt(dashBoardIdString); 36 | } catch (NumberFormatException ex) { 37 | throw new IllegalCommandException(String.format("Dash board id '%s' not valid.", dashBoardIdString), message.id); 38 | } 39 | 40 | log.debug("Activating dash {} for user {}", dashBoardIdString, user.getName()); 41 | user.getUserProfile().validateDashId(dashBoardId, message.id); 42 | user.getUserProfile().setActiveDashId(dashBoardId); 43 | 44 | ctx.writeAndFlush(produce(message.id, OK)); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | blynk 5 | cc.blynk 6 | 0.3-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | client 11 | 12 | 13 | 14 | 15 | maven-assembly-plugin 16 | 17 | client-${project.version} 18 | 19 | 20 | cc.blynk.client.ClientLauncher 21 | 22 | 23 | 24 | jar-with-dependencies 25 | 26 | false 27 | 28 | 29 | 30 | make-assembly 31 | package 32 | 33 | single 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | commons-cli 45 | commons-cli 46 | 1.2 47 | 48 | 49 | 50 | cc.blynk 51 | common 52 | ${project.version} 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /client/src/main/java/cc/blynk/client/ClientLauncher.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.client; 2 | 3 | import cc.blynk.client.core.AppClient; 4 | import cc.blynk.client.core.BaseClient; 5 | import cc.blynk.client.core.HardwareClient; 6 | import cc.blynk.client.enums.ClientMode; 7 | import cc.blynk.common.utils.ParseUtil; 8 | import org.apache.commons.cli.BasicParser; 9 | import org.apache.commons.cli.CommandLine; 10 | import org.apache.commons.cli.Options; 11 | import org.apache.commons.cli.ParseException; 12 | 13 | import java.io.BufferedReader; 14 | import java.io.InputStreamReader; 15 | 16 | /** 17 | * The Blynk Project. 18 | * Created by Dmitriy Dumanskiy. 19 | * Created on 11.03.15. 20 | */ 21 | public class ClientLauncher { 22 | 23 | protected static final String DEFAULT_HOST = "localhost"; 24 | protected static final int DEFAULT_HARDWARE_PORT = 8442; 25 | protected static final int DEFAULT_APPLICATION_PORT = 8443; 26 | 27 | private static final Options options = new Options(); 28 | 29 | static { 30 | options.addOption("host", true, "Server host or ip.") 31 | .addOption("port", true, "Port client should connect to.") 32 | .addOption("mode", true, "Client mode. 'hardware' or 'app'.") 33 | .addOption("disableAppSsl", false, "Disables SSL for app mode."); 34 | } 35 | 36 | public static void main(String[] args) throws ParseException { 37 | CommandLine cmd = new BasicParser().parse(options, args); 38 | 39 | ClientMode mode = ClientMode.parse(cmd.getOptionValue("mode", ClientMode.HARDWARE.name())); 40 | String host = cmd.getOptionValue("host", DEFAULT_HOST); 41 | int port = ParseUtil.parseInt(cmd.getOptionValue("port", 42 | (mode == ClientMode.APP ? String.valueOf(DEFAULT_APPLICATION_PORT) : String.valueOf(DEFAULT_HARDWARE_PORT))) 43 | ); 44 | boolean disableAppSsl = cmd.hasOption("disableAppSsl"); 45 | 46 | BaseClient baseClient = mode == ClientMode.APP ? new AppClient(host, port, disableAppSsl) : new HardwareClient(host, port); 47 | 48 | baseClient.start(new BufferedReader(new InputStreamReader(System.in))); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/java/cc/blynk/server/dao/GraphInMemoryStorageTest.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.dao; 2 | 3 | import cc.blynk.server.exceptions.IllegalCommandException; 4 | import cc.blynk.server.model.UserProfile; 5 | import cc.blynk.server.model.auth.User; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.Mock; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import static org.junit.Assert.*; 12 | import static org.mockito.Mockito.when; 13 | 14 | /** 15 | * The Blynk Project. 16 | * Created by Dmitriy Dumanskiy. 17 | * Created on 2/26/2015. 18 | */ 19 | @RunWith(MockitoJUnitRunner.class) 20 | public class GraphInMemoryStorageTest { 21 | 22 | private GraphInMemoryStorage storage = new GraphInMemoryStorage(1000); 23 | 24 | @Mock 25 | private User user; 26 | 27 | @Mock 28 | private UserProfile userProfile; 29 | 30 | @Test 31 | public void testNoActualStore() { 32 | String body = "ar 1".replaceAll(" ", "\0"); 33 | String result = storage.store(user, 1, body, 1); 34 | 35 | assertNotNull(result); 36 | assertEquals(body, result); 37 | 38 | } 39 | 40 | @Test(expected = IllegalCommandException.class) 41 | public void testWrongStoreCommand() { 42 | String body = "aw".replaceAll(" ", "\0"); 43 | storage.store(user, 1, body, 1); 44 | } 45 | 46 | @Test(expected = NumberFormatException.class) 47 | public void testStorePinNotANumber() { 48 | String body = "aw x".replaceAll(" ", "\0"); 49 | storage.store(user, 1, body, 1); 50 | } 51 | 52 | @Test 53 | public void testStoreCorrect() { 54 | when(user.getUserProfile()).thenReturn(userProfile); 55 | when(user.getName()).thenReturn("testUserName"); 56 | when(userProfile.hasGraphPin(1, (byte) 33)).thenReturn(true); 57 | 58 | String body = "aw 33".replaceAll(" ", "\0"); 59 | String result = storage.store(user, 1, body, 1); 60 | 61 | assertNotNull(result); 62 | assertTrue(result.startsWith(body)); 63 | //14 - ts length + '\0' separator 64 | assertEquals(body.length() + 14, result.length()); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/dao/GraphInMemoryStorage.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.dao; 2 | 3 | import cc.blynk.server.exceptions.IllegalCommandException; 4 | import cc.blynk.server.model.auth.User; 5 | 6 | import java.util.LinkedList; 7 | import java.util.Map; 8 | import java.util.Queue; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | import static cc.blynk.common.model.messages.protocol.HardwareMessage.attachTS; 12 | import static cc.blynk.common.utils.StringUtils.split; 13 | 14 | /** 15 | * The Blynk Project. 16 | * Created by Dmitriy Dumanskiy. 17 | * Created on 2/18/2015. 18 | * 19 | * todo redesign. right now it is not efficient at all 20 | */ 21 | public class GraphInMemoryStorage implements Storage { 22 | 23 | private final Map> userValues; 24 | private final int sizeLimit; 25 | 26 | public GraphInMemoryStorage(int sizeLimit) { 27 | this.userValues = new ConcurrentHashMap<>(); 28 | this.sizeLimit = sizeLimit; 29 | } 30 | 31 | @Override 32 | public String store(User user, Integer dashId, String body, int msgId) { 33 | if (body.charAt(1) == 'w') { 34 | if (body.length() < 4) { 35 | throw new IllegalCommandException("Hardware command body too short.", msgId); 36 | } 37 | String pinString = split(body); 38 | Byte pin = Byte.valueOf(pinString); 39 | if (user.getUserProfile().hasGraphPin(dashId, pin)) { 40 | body = attachTS(body); 41 | storeValue(user.getName(), dashId, body); 42 | } 43 | } 44 | return body; 45 | } 46 | 47 | private void storeValue(String userName, Integer dashId, String body) { 48 | //expecting same user always in same thread, so no concurrency 49 | String key = userName + dashId; 50 | Queue bodies = userValues.get(key); 51 | if (bodies == null) { 52 | bodies = new LinkedList<>(); 53 | userValues.put(key, bodies); 54 | } 55 | 56 | if (bodies.size() == sizeLimit) { 57 | bodies.poll(); 58 | } 59 | bodies.add(body); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/core/application/AppChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.core.application; 2 | 3 | import cc.blynk.common.handlers.decoders.ReplayingMessageDecoder; 4 | import cc.blynk.common.handlers.encoders.DeviceMessageEncoder; 5 | import cc.blynk.common.stats.GlobalStats; 6 | import cc.blynk.server.dao.SessionsHolder; 7 | import cc.blynk.server.handlers.workflow.ClientChannelStateHandler; 8 | import io.netty.channel.ChannelInitializer; 9 | import io.netty.channel.ChannelPipeline; 10 | import io.netty.channel.socket.SocketChannel; 11 | import io.netty.handler.ssl.SslContext; 12 | import io.netty.handler.timeout.ReadTimeoutHandler; 13 | 14 | /** 15 | * Application processing pipeline initializer. 16 | * 17 | * The Blynk Project. 18 | * Created by Dmitriy Dumanskiy. 19 | * Created on 11.03.15. 20 | */ 21 | final class AppChannelInitializer extends ChannelInitializer { 22 | 23 | private final SessionsHolder sessionsHolder; 24 | private final GlobalStats stats; 25 | private final AppHandlersHolder handlersHolder; 26 | private final SslContext sslCtx; 27 | 28 | public AppChannelInitializer(SessionsHolder sessionsHolder, GlobalStats stats, AppHandlersHolder handlersHolder, SslContext sslContext) { 29 | this.sessionsHolder = sessionsHolder; 30 | this.stats = stats; 31 | this.handlersHolder = handlersHolder; 32 | this.sslCtx = sslContext; 33 | } 34 | 35 | @Override 36 | protected void initChannel(SocketChannel ch) throws Exception { 37 | ChannelPipeline pipeline = ch.pipeline(); 38 | 39 | //todo apply from 40 | pipeline.addLast(new ReadTimeoutHandler(600)); 41 | 42 | if (sslCtx != null) { 43 | pipeline.addLast(sslCtx.newHandler(ch.alloc())); 44 | } 45 | 46 | //non-sharable handlers 47 | pipeline.addLast(new ClientChannelStateHandler(sessionsHolder)); 48 | pipeline.addLast(new ReplayingMessageDecoder(stats)); 49 | pipeline.addLast(new DeviceMessageEncoder()); 50 | 51 | //sharable business logic handlers initialized previously 52 | handlersHolder.getAllHandlers().forEach(pipeline::addLast); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/java/cc/blynk/server/model/UserProfileCalcGraphPinsTest.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model; 2 | 3 | import cc.blynk.server.utils.JsonParser; 4 | import org.junit.Test; 5 | 6 | import java.io.InputStream; 7 | 8 | import static org.junit.Assert.*; 9 | 10 | /** 11 | * The Blynk Project. 12 | * Created by Dmitriy Dumanskiy. 13 | * Created on 2/17/2015. 14 | */ 15 | public class UserProfileCalcGraphPinsTest { 16 | 17 | @Test 18 | public void testHas1Pin() { 19 | InputStream is = this.getClass().getResourceAsStream("/json_test/user_profile_json.txt"); 20 | 21 | UserProfile userProfile = JsonParser.parseProfile(is); 22 | String userProfileString = userProfile.toString(); 23 | 24 | assertNotNull(userProfileString); 25 | assertTrue(userProfileString.contains("dashBoards")); 26 | 27 | userProfile.calcGraphPins(); 28 | 29 | assertTrue(userProfile.hasGraphPin(1, (byte) 8)); 30 | } 31 | 32 | @Test 33 | public void testNoPins() { 34 | InputStream is = this.getClass().getResourceAsStream("/json_test/user_profile_json_4.txt"); 35 | 36 | UserProfile userProfile = JsonParser.parseProfile(is); 37 | String userProfileString = userProfile.toString(); 38 | 39 | assertNotNull(userProfileString); 40 | assertTrue(userProfileString.contains("dashBoards")); 41 | 42 | userProfile.calcGraphPins(); 43 | 44 | assertFalse(userProfile.hasGraphPin(1, (byte) 8)); 45 | } 46 | 47 | @Test 48 | public void testManyPins() { 49 | InputStream is = this.getClass().getResourceAsStream("/json_test/user_profile_json_5.txt"); 50 | 51 | UserProfile userProfile = JsonParser.parseProfile(is); 52 | String userProfileString = userProfile.toString(); 53 | 54 | assertNotNull(userProfileString); 55 | assertTrue(userProfileString.contains("dashBoards")); 56 | 57 | userProfile.calcGraphPins(); 58 | 59 | assertTrue(userProfile.hasGraphPin(1, (byte) 8)); 60 | assertTrue(userProfile.hasGraphPin(1, (byte) 9)); 61 | 62 | 63 | assertFalse(userProfile.hasGraphPin(2, (byte) 9)); 64 | assertTrue(userProfile.hasGraphPin(2, (byte) 8)); 65 | assertTrue(userProfile.hasGraphPin(2, (byte) 2)); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /client/src/main/java/cc/blynk/client/core/AppClient.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.client.core; 2 | 3 | import cc.blynk.client.handlers.ClientReplayingMessageDecoder; 4 | import cc.blynk.common.handlers.encoders.DeviceMessageEncoder; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.ChannelPipeline; 7 | import io.netty.channel.socket.SocketChannel; 8 | import io.netty.handler.ssl.SslContext; 9 | import io.netty.handler.ssl.util.InsecureTrustManagerFactory; 10 | 11 | import javax.net.ssl.SSLException; 12 | import java.util.Random; 13 | 14 | /** 15 | * The Blynk Project. 16 | * Created by Dmitriy Dumanskiy. 17 | * Created on 11.03.15. 18 | */ 19 | public class AppClient extends BaseClient { 20 | 21 | protected SslContext sslCtx; 22 | 23 | public AppClient(String host, int port, boolean disableAppSsl) { 24 | this(host, port, new Random(), disableAppSsl); 25 | } 26 | 27 | public AppClient(String host, int port, Random msgIdGenerator, boolean disableAppSsl) { 28 | super(host, port, msgIdGenerator); 29 | log.info("Creating app client. Host {}, sslPort : {}", host, port); 30 | 31 | if (!disableAppSsl) { 32 | //todo think how to simplify with real certs? 33 | //sslCtx = SslContext.newClientContext(getFileFromResources("/test.crt")); 34 | try { 35 | this.sslCtx = SslContext.newClientContext(InsecureTrustManagerFactory.INSTANCE); 36 | } catch (SSLException e) { 37 | log.error("Error initializing SSL context. Reason : {}", e.getMessage()); 38 | log.debug(e); 39 | throw new RuntimeException(); 40 | } 41 | } 42 | } 43 | 44 | @Override 45 | public ChannelInitializer getChannelInitializer() { 46 | return new ChannelInitializer() { 47 | @Override 48 | public void initChannel(SocketChannel ch) throws Exception { 49 | ChannelPipeline pipeline = ch.pipeline(); 50 | if (sslCtx != null) { 51 | pipeline.addLast(sslCtx.newHandler(ch.alloc(), host, port)); 52 | } 53 | pipeline.addLast(new ClientReplayingMessageDecoder()); 54 | pipeline.addLast(new DeviceMessageEncoder()); 55 | } 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/handlers/decoders/ReplayingMessageDecoder.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.handlers.decoders; 2 | 3 | import cc.blynk.common.enums.Command; 4 | import cc.blynk.common.handlers.DefaultExceptionHandler; 5 | import cc.blynk.common.model.messages.MessageBase; 6 | import cc.blynk.common.stats.GlobalStats; 7 | import cc.blynk.common.utils.Config; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.handler.codec.ReplayingDecoder; 11 | import org.apache.logging.log4j.LogManager; 12 | import org.apache.logging.log4j.Logger; 13 | 14 | import java.util.List; 15 | 16 | import static cc.blynk.common.model.messages.MessageFactory.produce; 17 | 18 | /** 19 | * The Blynk Project. 20 | * Created by Dmitriy Dumanskiy. 21 | * Created on 2/1/2015. 22 | */ 23 | //todo could be optimized with checkpoints if needed 24 | public class ReplayingMessageDecoder extends ReplayingDecoder implements DefaultExceptionHandler { 25 | 26 | protected static final Logger log = LogManager.getLogger(ReplayingMessageDecoder.class); 27 | 28 | private final GlobalStats stats; 29 | 30 | public ReplayingMessageDecoder() { 31 | this.stats = new GlobalStats(); 32 | } 33 | 34 | public ReplayingMessageDecoder(GlobalStats stats) { 35 | this.stats = stats; 36 | } 37 | 38 | @Override 39 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 40 | short command = in.readUnsignedByte(); 41 | int messageId = in.readUnsignedShort(); 42 | 43 | MessageBase message; 44 | if (command == Command.RESPONSE) { 45 | int responseCode = in.readUnsignedShort(); 46 | message = produce(messageId, responseCode); 47 | } else { 48 | int length = in.readUnsignedShort(); 49 | String messageBody = in.readSlice(length).toString(Config.DEFAULT_CHARSET); 50 | message = produce(messageId, command, messageBody); 51 | } 52 | 53 | log.trace("Incoming {}", message); 54 | 55 | stats.mark(); 56 | stats.mark(message.getClass()); 57 | 58 | out.add(message); 59 | } 60 | 61 | @Override 62 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 63 | handleGeneralException(ctx, cause); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/java/cc/blynk/server/utils/FileManagerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.utils; 2 | 3 | import cc.blynk.server.TestBase; 4 | import cc.blynk.server.dao.FileManager; 5 | import cc.blynk.server.model.auth.User; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.util.Map; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | import static org.junit.Assert.assertNotNull; 16 | 17 | /** 18 | * User: ddumanskiy 19 | * Date: 09.12.13 20 | * Time: 8:07 21 | */ 22 | public class FileManagerIntegrationTest extends TestBase { 23 | 24 | private User user1 = new User("name1", "pass1"); 25 | private User user2 = new User("name2", "pass2"); 26 | 27 | private FileManager fileManager = new FileManager(dataFolder); 28 | 29 | @Before 30 | public void cleanup() throws IOException { 31 | Path file; 32 | file = fileManager.generateFileName(user1.getName()); 33 | Files.deleteIfExists(file); 34 | 35 | file = fileManager.generateFileName(user2.getName()); 36 | Files.deleteIfExists(file); 37 | } 38 | 39 | @Test 40 | public void testGenerateFileName() { 41 | Path file = fileManager.generateFileName(user1.getName()); 42 | assertEquals("u_name1.user", file.getFileName().toString()); 43 | } 44 | 45 | @Test 46 | public void testCreationTempFile() throws IOException { 47 | fileManager.overrideUserFile(user1); 48 | //file existence ignored 49 | fileManager.overrideUserFile(user1); 50 | } 51 | 52 | @Test 53 | public void testReadListOfFiles() throws IOException { 54 | fileManager.overrideUserFile(user1); 55 | fileManager.overrideUserFile(user2); 56 | 57 | Map users = fileManager.deserialize(); 58 | assertNotNull(users); 59 | assertNotNull(users.get(user1.getName())); 60 | assertNotNull(users.get(user2.getName())); 61 | } 62 | 63 | @Test 64 | public void testOverrideFiles() throws IOException { 65 | fileManager.overrideUserFile(user1); 66 | fileManager.overrideUserFile(user1); 67 | 68 | Map users = fileManager.deserialize(); 69 | assertNotNull(users); 70 | assertNotNull(users.get(user1.getName())); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | blynk 5 | cc.blynk 6 | 0.3-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | server 11 | pom 12 | 13 | 14 | push-notifications-android 15 | push-notifications-ios 16 | twitter 17 | tcp-server 18 | 19 | 20 | 21 | 22 | 23 | 24 | maven-assembly-plugin 25 | 26 | server-${project.version} 27 | 28 | 29 | cc.blynk.server.ServerLauncher 30 | 31 | 32 | 33 | jar-with-dependencies 34 | 35 | false 36 | 37 | 38 | 39 | make-assembly 40 | package 41 | 42 | single 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | com.fasterxml.jackson.core 54 | jackson-databind 55 | 2.5.1 56 | 57 | 58 | 59 | cc.blynk 60 | common 61 | ${project.version} 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/core/hardware/HardwareHandlersHolder.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.core.hardware; 2 | 3 | import cc.blynk.common.utils.ServerProperties; 4 | import cc.blynk.server.dao.FileManager; 5 | import cc.blynk.server.dao.SessionsHolder; 6 | import cc.blynk.server.dao.UserRegistry; 7 | import cc.blynk.server.handlers.auth.HardwareLoginHandler; 8 | import cc.blynk.server.handlers.workflow.BaseSimpleChannelInboundHandler; 9 | import cc.blynk.server.handlers.workflow.HardwareHandler; 10 | import cc.blynk.server.handlers.workflow.PingHandler; 11 | import cc.blynk.server.handlers.workflow.TweetHandler; 12 | import cc.blynk.server.twitter.TwitterWrapper; 13 | import io.netty.channel.ChannelHandler; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * The Blynk Project. 20 | * Created by Dmitriy Dumanskiy. 21 | * Created on 2/27/2015. 22 | */ 23 | class HardwareHandlersHolder { 24 | 25 | //todo cover with tests. 26 | //sharable handlers 27 | private final HardwareLoginHandler hardwareLoginHandler; 28 | private final HardwareHandler hardwareHandler; 29 | private final PingHandler pingHandler; 30 | private final TweetHandler tweetHandler; 31 | 32 | public HardwareHandlersHolder(ServerProperties props, FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder) { 33 | this.hardwareLoginHandler = new HardwareLoginHandler(fileManager, userRegistry, sessionsHolder); 34 | this.hardwareHandler = new HardwareHandler(props, fileManager, userRegistry, sessionsHolder); 35 | this.pingHandler = new PingHandler(props, fileManager, userRegistry, sessionsHolder); 36 | this.tweetHandler = new TweetHandler(props, fileManager, userRegistry, sessionsHolder, new TwitterWrapper()); 37 | } 38 | 39 | public List getBaseHandlers() { 40 | return new ArrayList() { 41 | { 42 | add(hardwareHandler); 43 | add(pingHandler); 44 | add(tweetHandler); 45 | } 46 | }; 47 | } 48 | 49 | public List getAllHandlers() { 50 | return new ArrayList() { 51 | { 52 | add(hardwareLoginHandler); 53 | add(hardwareHandler); 54 | add(pingHandler); 55 | add(tweetHandler); 56 | } 57 | }; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/stats/metrics/InstanceLoadMeter.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.stats.metrics; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.atomic.AtomicLong; 5 | import java.util.concurrent.atomic.LongAdder; 6 | 7 | import static java.lang.Math.exp; 8 | 9 | /** 10 | * The Blynk Project. 11 | * Created by Dmitriy Dumanskiy. 12 | * Created on 16.03.15. 13 | */ 14 | public class InstanceLoadMeter { 15 | 16 | private static final long TICK_INTERVAL = TimeUnit.SECONDS.toMillis(1); 17 | private static final double TICK_INTERVAL_DOUBLE = (double) TICK_INTERVAL; 18 | 19 | private final AtomicLong lastTick; 20 | private final LongAdder uncounted = new LongAdder(); 21 | private final double alpha = 1 - exp(-1/60.0); 22 | private volatile boolean initialized = false; 23 | private volatile double rate = 0.0; 24 | 25 | public InstanceLoadMeter() { 26 | this.lastTick = new AtomicLong(System.currentTimeMillis()); 27 | } 28 | 29 | /** 30 | * Mark the occurrence of an event. 31 | */ 32 | public void mark() { 33 | mark(1); 34 | } 35 | 36 | /** 37 | * Mark the occurrence of a given number of events. 38 | * 39 | * @param n the number of events 40 | */ 41 | public void mark(long n) { 42 | tickIfNecessary(); 43 | uncounted.add(n); 44 | } 45 | 46 | private void tickIfNecessary() { 47 | final long oldTick = lastTick.get(); 48 | final long newTick = System.currentTimeMillis(); 49 | final long age = newTick - oldTick; 50 | if (age > TICK_INTERVAL) { 51 | final long newIntervalStartTick = newTick - age % TICK_INTERVAL; 52 | if (lastTick.compareAndSet(oldTick, newIntervalStartTick)) { 53 | final long requiredTicks = age / TICK_INTERVAL; 54 | for (long i = 0; i < requiredTicks; i++) { 55 | tick(); 56 | } 57 | } 58 | } 59 | } 60 | 61 | public double getOneMinuteRate() { 62 | tickIfNecessary(); 63 | return rate * TICK_INTERVAL_DOUBLE; 64 | } 65 | 66 | private void tick() { 67 | final long count = uncounted.sumThenReset(); 68 | final double instantRate = count / TICK_INTERVAL_DOUBLE; 69 | if (initialized) { 70 | rate += (alpha * (instantRate - rate)); 71 | } else { 72 | rate = instantRate; 73 | initialized = true; 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/model/messages/MessageFactory.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.model.messages; 2 | 3 | import cc.blynk.common.enums.Command; 4 | import cc.blynk.common.exceptions.BaseServerException; 5 | import cc.blynk.common.exceptions.UnsupportedCommandException; 6 | import cc.blynk.common.model.messages.protocol.HardwareMessage; 7 | import cc.blynk.common.model.messages.protocol.PingMessage; 8 | import cc.blynk.common.model.messages.protocol.appllication.*; 9 | import cc.blynk.common.model.messages.protocol.hardware.TweetMessage; 10 | 11 | import static cc.blynk.common.enums.Command.*; 12 | 13 | /** 14 | * The Blynk Project. 15 | * Created by Dmitriy Dumanskiy. 16 | * Created on 2/1/2015. 17 | */ 18 | public class MessageFactory { 19 | 20 | public static Message produce(int messageId, short command, String body) { 21 | switch (command) { 22 | case HARDWARE_COMMAND : 23 | return new HardwareMessage(messageId, body); 24 | case PING : 25 | return new PingMessage(messageId, body); 26 | case SAVE_PROFILE : 27 | return new SaveProfileMessage(messageId, body); 28 | case LOAD_PROFILE : 29 | return new LoadProfileMessage(messageId, body); 30 | case ACTIVATE_DASHBOARD : 31 | return new ActivateDashboardMessage(messageId, body); 32 | case DEACTIVATE_DASHBOARD : 33 | return new DeActivateDashboardMessage(messageId, body); 34 | case GET_TOKEN : 35 | return new GetTokenMessage(messageId, body); 36 | case REFRESH_TOKEN : 37 | return new RefreshTokenMessage(messageId, body); 38 | case LOGIN : 39 | return new LoginMessage(messageId, body); 40 | case TWEET : 41 | return new TweetMessage(messageId, body); 42 | case REGISTER : 43 | return new RegisterMessage(messageId, body); 44 | 45 | default: throw new UnsupportedCommandException(String.format("Command with code %d not supported message.", command), messageId); 46 | } 47 | } 48 | 49 | public static ResponseMessage produce(int messageId, int responseCode) { 50 | return new ResponseMessage(messageId, Command.RESPONSE, responseCode); 51 | } 52 | 53 | public static ResponseMessage produce(BaseServerException exception) { 54 | return new ResponseMessage(exception.msgId, Command.RESPONSE, exception.errorCode); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/core/BaseServer.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.core; 2 | 3 | import cc.blynk.common.utils.ServerProperties; 4 | import cc.blynk.server.core.hardware.HardwareServer; 5 | import cc.blynk.server.handlers.workflow.BaseSimpleChannelInboundHandler; 6 | import cc.blynk.server.model.auth.nio.ChannelServer; 7 | import io.netty.bootstrap.ServerBootstrap; 8 | import io.netty.channel.Channel; 9 | import io.netty.channel.ChannelFuture; 10 | import io.netty.channel.ChannelInitializer; 11 | import io.netty.channel.ChannelOption; 12 | import io.netty.channel.nio.NioEventLoopGroup; 13 | import io.netty.channel.socket.SocketChannel; 14 | import org.apache.logging.log4j.LogManager; 15 | import org.apache.logging.log4j.Logger; 16 | 17 | import java.util.List; 18 | 19 | /** 20 | * The Blynk Project. 21 | * Created by Dmitriy Dumanskiy. 22 | * Created on 3/10/2015. 23 | */ 24 | public abstract class BaseServer implements Runnable { 25 | 26 | protected static final Logger log = LogManager.getLogger(HardwareServer.class); 27 | protected final int port; 28 | private final int workerThreads; 29 | 30 | private Channel channel; 31 | 32 | protected BaseServer(int port, ServerProperties props) { 33 | this.port = port; 34 | this.workerThreads = props.getIntProperty("server.worker.threads", Runtime.getRuntime().availableProcessors()); 35 | } 36 | 37 | @Override 38 | public void run() { 39 | NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); 40 | NioEventLoopGroup workerGroup = new NioEventLoopGroup(workerThreads); 41 | ServerBootstrap b = new ServerBootstrap(); 42 | try { 43 | b.group(bossGroup, workerGroup) 44 | .channel(ChannelServer.class) 45 | .childOption(ChannelOption.SO_KEEPALIVE, true) 46 | .childHandler(getChannelInitializer()); 47 | 48 | ChannelFuture channelFuture = b.bind(port).sync(); 49 | 50 | this.channel = channelFuture.channel(); 51 | this.channel.closeFuture().sync(); 52 | } catch (Exception e) { 53 | log.error(e); 54 | } finally { 55 | bossGroup.shutdownGracefully(); 56 | workerGroup.shutdownGracefully(); 57 | } 58 | } 59 | 60 | public abstract List getBaseHandlers(); 61 | 62 | public abstract ChannelInitializer getChannelInitializer(); 63 | 64 | public void stop() { 65 | if (channel != null) { 66 | channel.close().awaitUninterruptibly(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /integration-tests/src/test/resources/json_test/user_profile_json_many_dashes.txt: -------------------------------------------------------------------------------- 1 | { 2 | "activeDashId" : 1, 3 | "dashBoards" : 4 | [ 5 | { 6 | "id":1, 7 | "name":"My Dashboard", 8 | "widgets" : [ 9 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"BUTTON", "pinType":"DIGITAL", "pin":1, "value":"1"}, 10 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER_LARGE", "pinType":"DIGITAL", "pin":2, "value":"1", "state":"ON"}, 11 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER_LARGE", "pinType":"ANALOG", "pin":3, "value":"0", "state":"OFF"}, 12 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER", "pinType":"VIRTUAL", "pin":4, "value":"244" }, 13 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"TIMER", "pinType":"DIGITAL", "pin":5, "value":"1", "startTime":0}, 14 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"LED", "pinType":"DIGITAL", "pin":6}, 15 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"DIGIT4_DISPLAY", "pinType":"DIGITAL", "pin":7}, 16 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"GRAPH", "pinType":"DIGITAL", "pin":8} 17 | ], 18 | "settings" : {"boardType":"UNO", "someParam":"someValue"} 19 | }, 20 | { 21 | "id":2, 22 | "name":"My Dashboard" 23 | }, 24 | { 25 | "id":3, 26 | "name":"My Dashboard" 27 | }, 28 | { 29 | "id":4, 30 | "name":"My Dashboard" 31 | }, 32 | { 33 | "id":5, 34 | "name":"My Dashboard" 35 | }, 36 | { 37 | "id":6, 38 | "name":"My Dashboard" 39 | }, 40 | { 41 | "id":7, 42 | "name":"My Dashboard" 43 | }, 44 | { 45 | "id":8, 46 | "name":"My Dashboard" 47 | }, 48 | { 49 | "id":9, 50 | "name":"My Dashboard" 51 | }, 52 | { 53 | "id":10, 54 | "name":"My Dashboard" 55 | }, 56 | { 57 | "id":11, 58 | "name":"My Dashboard" 59 | } 60 | ], 61 | "twitter" : { 62 | "token" : "123", 63 | "tokenSecret" : "123" 64 | } 65 | } -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/utils/JsonParser.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.utils; 2 | 3 | import cc.blynk.server.exceptions.IllegalCommandException; 4 | import cc.blynk.server.model.UserProfile; 5 | import cc.blynk.server.model.auth.User; 6 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 7 | import com.fasterxml.jackson.annotation.JsonInclude; 8 | import com.fasterxml.jackson.annotation.PropertyAccessor; 9 | import com.fasterxml.jackson.databind.DeserializationFeature; 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import org.apache.logging.log4j.LogManager; 12 | import org.apache.logging.log4j.Logger; 13 | 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | 17 | /** 18 | * User: ddumanskiy 19 | * Date: 21.11.13 20 | * Time: 15:31 21 | */ 22 | public final class JsonParser { 23 | 24 | private static final Logger log = LogManager.getLogger(JsonParser.class); 25 | 26 | //it is threadsafe 27 | private static ObjectMapper mapper = init(); 28 | 29 | private static ObjectMapper init() { 30 | return new ObjectMapper() 31 | .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 32 | .setSerializationInclusion(JsonInclude.Include.NON_NULL) 33 | .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) 34 | .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) 35 | .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); 36 | } 37 | 38 | public static String toJson(Object object) { 39 | try { 40 | return mapper.writeValueAsString(object); 41 | } catch (Exception e) { 42 | log.error("Error jsoning object."); 43 | log.error(e); 44 | } 45 | return "{}"; 46 | } 47 | 48 | public static User parseUser(String reader) throws IOException { 49 | User user = mapper.reader(User.class).readValue(reader); 50 | user.initQuota(); 51 | return user; 52 | } 53 | 54 | public static UserProfile parseProfile(String reader, int id) { 55 | try { 56 | return mapper.reader(UserProfile.class).readValue(reader); 57 | } catch (IOException e) { 58 | throw new IllegalCommandException("Error parsing user profile. Reason : " + e.getMessage(), id); 59 | } 60 | } 61 | 62 | //only for tests 63 | public static UserProfile parseProfile(InputStream reader) { 64 | try { 65 | return mapper.reader(UserProfile.class).readValue(reader); 66 | } catch (IOException e) { 67 | throw new IllegalCommandException("Error parsing user profile. Reason : " + e.getMessage(), 1); 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/stats/GlobalStats.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.stats; 2 | 3 | import cc.blynk.common.model.messages.ResponseMessage; 4 | import cc.blynk.common.model.messages.protocol.HardwareMessage; 5 | import cc.blynk.common.model.messages.protocol.PingMessage; 6 | import cc.blynk.common.model.messages.protocol.appllication.*; 7 | import cc.blynk.common.model.messages.protocol.hardware.TweetMessage; 8 | import com.codahale.metrics.Meter; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.Logger; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import java.util.concurrent.atomic.LongAdder; 15 | 16 | /** 17 | * The Blynk Project. 18 | * Created by Dmitriy Dumanskiy. 19 | * Created on 2/13/2015. 20 | */ 21 | public class GlobalStats { 22 | 23 | private static final Logger log = LogManager.getLogger(GlobalStats.class); 24 | private Meter incomeMessages; 25 | private Map, LongAdder> specificCounters; 26 | 27 | public GlobalStats() { 28 | this.incomeMessages = new Meter(); 29 | 30 | this.specificCounters = new HashMap<>(); 31 | specificCounters.put(GetTokenMessage.class, new LongAdder()); 32 | specificCounters.put(RefreshTokenMessage.class, new LongAdder()); 33 | specificCounters.put(HardwareMessage.class, new LongAdder()); 34 | specificCounters.put(LoadProfileMessage.class, new LongAdder()); 35 | specificCounters.put(LoginMessage.class, new LongAdder()); 36 | specificCounters.put(PingMessage.class, new LongAdder()); 37 | specificCounters.put(RegisterMessage.class, new LongAdder()); 38 | specificCounters.put(SaveProfileMessage.class, new LongAdder()); 39 | specificCounters.put(ActivateDashboardMessage.class, new LongAdder()); 40 | specificCounters.put(DeActivateDashboardMessage.class, new LongAdder()); 41 | specificCounters.put(TweetMessage.class, new LongAdder()); 42 | specificCounters.put(ResponseMessage.class, new LongAdder()); 43 | } 44 | 45 | public void mark(Class clazz) { 46 | specificCounters.get(clazz).increment(); 47 | } 48 | 49 | public void mark() { 50 | incomeMessages.mark(1); 51 | } 52 | 53 | public void log() { 54 | //do not log low traffic. it is not interesting =). 55 | if (incomeMessages.getOneMinuteRate() > 1) { 56 | log.debug("1 min rate : {}", String.format("%.2f", incomeMessages.getOneMinuteRate())); 57 | for (Map.Entry, LongAdder> counterEntry : specificCounters.entrySet()) { 58 | log.debug("{} : {}", counterEntry.getKey().getSimpleName(), counterEntry.getValue().sum()); 59 | } 60 | log.debug("--------------------------------------------------------------------------------------"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/workers/ProfileSaverWorker.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.workers; 2 | 3 | import cc.blynk.common.stats.GlobalStats; 4 | import cc.blynk.server.dao.FileManager; 5 | import cc.blynk.server.dao.JedisWrapper; 6 | import cc.blynk.server.dao.UserRegistry; 7 | import cc.blynk.server.model.auth.User; 8 | import org.apache.logging.log4j.LogManager; 9 | import org.apache.logging.log4j.Logger; 10 | 11 | import java.io.IOException; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.concurrent.Executors; 15 | import java.util.concurrent.ScheduledExecutorService; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | 19 | 20 | /** 21 | * The Blynk Project. 22 | * Created by Dmitriy Dumanskiy. 23 | * Created on 2/12/2015. 24 | */ 25 | public class ProfileSaverWorker implements Runnable { 26 | 27 | private static final Logger log = LogManager.getLogger(ProfileSaverWorker.class); 28 | 29 | //1 min 30 | private final UserRegistry userRegistry; 31 | private final FileManager fileManager; 32 | private final int periodInMillis; 33 | private final GlobalStats stats; 34 | private final ScheduledExecutorService scheduler; 35 | private final JedisWrapper jedisWrapper; 36 | private long lastStart; 37 | 38 | public ProfileSaverWorker(JedisWrapper jedisWrapper, UserRegistry userRegistry, FileManager fileManager, int periodInMillis, GlobalStats stats) { 39 | this.userRegistry = userRegistry; 40 | this.fileManager = fileManager; 41 | this.periodInMillis = periodInMillis; 42 | this.stats = stats; 43 | this.scheduler = Executors.newScheduledThreadPool(1); 44 | this.lastStart = System.currentTimeMillis(); 45 | this.jedisWrapper = jedisWrapper; 46 | } 47 | 48 | public void start() { 49 | scheduler.scheduleAtFixedRate(this, 1000, periodInMillis, TimeUnit.MILLISECONDS); 50 | } 51 | 52 | @Override 53 | public void run() { 54 | log.debug("Starting saving user db."); 55 | int count = 0; 56 | long newStart = System.currentTimeMillis(); 57 | 58 | List usersToSave = new ArrayList<>(); 59 | 60 | for (User user : userRegistry.getUsers().values()) { 61 | if (lastStart <= user.getLastModifiedTs()) { 62 | try { 63 | usersToSave.add(user); 64 | fileManager.overrideUserFile(user); 65 | count++; 66 | } catch (IOException e) { 67 | log.error("Error saving : {}.", user); 68 | } 69 | } 70 | } 71 | 72 | jedisWrapper.saveToRemoteStorage(usersToSave); 73 | 74 | lastStart = newStart; 75 | 76 | stats.log(); 77 | log.debug("Saving user db finished. Modified {} users.", count); 78 | } 79 | 80 | 81 | } 82 | -------------------------------------------------------------------------------- /server/push-notifications-android/src/main/java/cc/blynk/server/push/MyPacketListener.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.push; 2 | 3 | import cc.blynk.server.push.response.ACKMessage; 4 | import cc.blynk.server.push.response.ControlMessage; 5 | import cc.blynk.server.push.response.NACKMessage; 6 | import cc.blynk.server.push.response.ResponseMessageBase; 7 | import org.apache.logging.log4j.LogManager; 8 | import org.apache.logging.log4j.Logger; 9 | import org.jivesoftware.smack.PacketListener; 10 | import org.jivesoftware.smack.packet.Message; 11 | import org.jivesoftware.smack.packet.Packet; 12 | 13 | import static cc.blynk.server.push.GCMPacketExtension.GCM_NAMESPACE; 14 | 15 | /** 16 | * The Blynk Project. 17 | * Created by Dmitriy Dumanskiy. 18 | * Created on 2/8/2015. 19 | */ 20 | class MyPacketListener implements PacketListener { 21 | 22 | private static final Logger log = LogManager.getLogger(MyPacketListener.class); 23 | 24 | private GCMSmackCcsClient gcmSmackCcsClient; 25 | 26 | public MyPacketListener(GCMSmackCcsClient gcmSmackCcsClient) { 27 | this.gcmSmackCcsClient = gcmSmackCcsClient; 28 | } 29 | 30 | @Override 31 | public void processPacket(Packet packet) { 32 | log.debug("Incomming GCM respones : {}", packet.toXML()); 33 | Message incomingMessage = (Message) packet; 34 | GCMPacketExtension gcmPacket = (GCMPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE); 35 | String json = gcmPacket.getJson(); 36 | try { 37 | ResponseMessageBase responseMessageBase = GCMSmackCcsClient.mapper.readValue(json, ResponseMessageBase.class); 38 | log.debug("Incomming GCM respones : {}", responseMessageBase); 39 | // present for "ack"/"nack", null otherwise 40 | 41 | if (responseMessageBase instanceof ACKMessage) { 42 | gcmSmackCcsClient.handleAckReceipt((ACKMessage) responseMessageBase); 43 | } else if (responseMessageBase instanceof NACKMessage) { 44 | gcmSmackCcsClient.handleNackReceipt((NACKMessage) responseMessageBase); 45 | } else if (responseMessageBase instanceof ControlMessage) { 46 | gcmSmackCcsClient.handleControlMessage((ControlMessage) responseMessageBase); 47 | } else { 48 | log.error("Not implemented!!!"); 49 | /* 50 | // Normal upstream data message 51 | handleUpstreamMessage(jsonObject); 52 | 53 | // Send ACK to CCS 54 | String messageId = (String) jsonObject.get("message_id"); 55 | String from = (String) jsonObject.get("from"); 56 | String ack = createJsonAck(from, messageId); 57 | send(ack); 58 | */ 59 | } 60 | 61 | } catch (Exception e) { 62 | log.error("Failed to process packet", e); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/workers/PropertiesChangeWatcherWorker.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.workers; 2 | 3 | import cc.blynk.common.utils.Config; 4 | import cc.blynk.common.utils.ServerProperties; 5 | import cc.blynk.server.handlers.workflow.BaseSimpleChannelInboundHandler; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.*; 11 | import java.util.List; 12 | 13 | /** 14 | * The Blynk Project. 15 | * Created by Dmitriy Dumanskiy. 16 | * Created on 2/26/2015. 17 | */ 18 | public class PropertiesChangeWatcherWorker implements Runnable { 19 | 20 | private static final Logger log = LogManager.getLogger(PropertiesChangeWatcherWorker.class); 21 | 22 | private final String fileName; 23 | private final BaseSimpleChannelInboundHandler[] handlers; 24 | private final Path propsFileFolder; 25 | 26 | public PropertiesChangeWatcherWorker(String fileName, BaseSimpleChannelInboundHandler... handlers) { 27 | this.fileName = fileName; 28 | this.handlers = handlers; 29 | this.propsFileFolder = ServerProperties.getCurrentDir(); 30 | } 31 | 32 | public PropertiesChangeWatcherWorker(String fileName, List handlers) { 33 | this(fileName, handlers.toArray(new BaseSimpleChannelInboundHandler[handlers.size()])); 34 | } 35 | 36 | @Override 37 | public void run() { 38 | try { 39 | WatchService watchService = FileSystems.getDefault().newWatchService(); 40 | propsFileFolder.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); 41 | while (true) { 42 | WatchKey wk = watchService.take(); 43 | for (WatchEvent event : wk.pollEvents()) { 44 | Path changed = (Path) event.context(); 45 | Path changedFile = propsFileFolder.resolve(changed.toString()); 46 | if (changed.getFileName().toString().endsWith(fileName) && Files.exists(changedFile)) { 47 | log.info("Props file '{}' changed. Updating handler properties.", changedFile); 48 | for (BaseSimpleChannelInboundHandler handler : handlers) { 49 | ServerProperties changedProps = new ServerProperties(changed); 50 | handler.updateProperties(changedProps); 51 | } 52 | } 53 | } 54 | // reset the key 55 | boolean valid = wk.reset(); 56 | if (!valid) { 57 | log.info("Key has been not unregistered."); 58 | } 59 | } 60 | } catch (IOException | InterruptedException e) { 61 | log.warn("Error monitoring '{}' file. Reloadable properties are not enabled.", Config.SERVER_PROPERTIES_FILENAME, e); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/java/cc/blynk/server/workers/ProfileSaverWorkerTest.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.workers; 2 | 3 | import cc.blynk.common.stats.GlobalStats; 4 | import cc.blynk.server.dao.FileManager; 5 | import cc.blynk.server.dao.JedisWrapper; 6 | import cc.blynk.server.dao.UserRegistry; 7 | import cc.blynk.server.model.auth.User; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.Mock; 11 | import org.mockito.runners.MockitoJUnitRunner; 12 | 13 | import java.io.IOException; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | import static org.mockito.Mockito.*; 18 | 19 | /** 20 | * The Blynk Project. 21 | * Created by Dmitriy Dumanskiy. 22 | * Created on 3/3/2015. 23 | */ 24 | @RunWith(MockitoJUnitRunner.class) 25 | public class ProfileSaverWorkerTest { 26 | 27 | @Mock 28 | private UserRegistry userRegistry; 29 | 30 | @Mock 31 | private FileManager fileManager; 32 | 33 | @Mock 34 | private JedisWrapper jedisWrapper; 35 | 36 | @Mock 37 | private GlobalStats stats; 38 | 39 | @Test 40 | public void testCorrectProfilesAreSaved() throws IOException { 41 | ProfileSaverWorker profileSaverWorker = new ProfileSaverWorker(jedisWrapper, userRegistry, fileManager, 0 , stats); 42 | 43 | User user1 = new User("1", ""); 44 | User user2 = new User("2", ""); 45 | User user3 = new User("3", ""); 46 | User user4 = new User("4", ""); 47 | 48 | Map userMap = new HashMap<>(); 49 | userMap.put("1", user1); 50 | userMap.put("2", user2); 51 | userMap.put("3", user3); 52 | userMap.put("4", user4); 53 | 54 | when(userRegistry.getUsers()).thenReturn(userMap); 55 | profileSaverWorker.run(); 56 | 57 | verify(fileManager, times(4)).overrideUserFile(any()); 58 | verify(fileManager).overrideUserFile(user1); 59 | verify(fileManager).overrideUserFile(user2); 60 | verify(fileManager).overrideUserFile(user3); 61 | verify(fileManager).overrideUserFile(user4); 62 | verify(stats).log(); 63 | } 64 | 65 | @Test 66 | public void testNoProfileChanges() throws Exception { 67 | User user1 = new User("1", ""); 68 | User user2 = new User("2", ""); 69 | User user3 = new User("3", ""); 70 | User user4 = new User("4", ""); 71 | 72 | Map userMap = new HashMap<>(); 73 | userMap.put("1", user1); 74 | userMap.put("2", user2); 75 | userMap.put("3", user3); 76 | userMap.put("4", user4); 77 | 78 | Thread.sleep(1); 79 | 80 | ProfileSaverWorker profileSaverWorker = new ProfileSaverWorker(jedisWrapper, userRegistry, fileManager, 0 , stats); 81 | 82 | when(userRegistry.getUsers()).thenReturn(userMap); 83 | profileSaverWorker.run(); 84 | 85 | verifyNoMoreInteractions(fileManager); 86 | verify(stats).log(); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/java/cc/blynk/server/workers/timer/TimerWorkerTest.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.workers.timer; 2 | 3 | import cc.blynk.server.dao.SessionsHolder; 4 | import cc.blynk.server.dao.UserRegistry; 5 | import cc.blynk.server.model.DashBoard; 6 | import cc.blynk.server.model.UserProfile; 7 | import cc.blynk.server.model.auth.Session; 8 | import cc.blynk.server.model.auth.User; 9 | import cc.blynk.server.model.widgets.others.Timer; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.mockito.InjectMocks; 14 | import org.mockito.Mock; 15 | import org.mockito.Spy; 16 | import org.mockito.runners.MockitoJUnitRunner; 17 | 18 | import java.time.LocalDateTime; 19 | import java.time.ZoneId; 20 | import java.util.HashSet; 21 | import java.util.Set; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | 24 | import static org.mockito.Mockito.*; 25 | 26 | /** 27 | * The Blynk Project. 28 | * Created by Dmitriy Dumanskiy. 29 | * Created on 2/6/2015. 30 | */ 31 | @RunWith(MockitoJUnitRunner.class) 32 | public class TimerWorkerTest { 33 | 34 | @Mock 35 | private UserRegistry userRegistry; 36 | 37 | @Mock 38 | private SessionsHolder sessionsHolder; 39 | 40 | @Spy 41 | @InjectMocks 42 | private TimerWorker timerWorker; 43 | 44 | @Mock 45 | private User user; 46 | 47 | @Mock 48 | private UserProfile userProfile; 49 | 50 | private ConcurrentHashMap users = new ConcurrentHashMap<>(); 51 | 52 | private ConcurrentHashMap userSession = new ConcurrentHashMap<>(); 53 | 54 | private Set timers = new HashSet<>(); 55 | private Timer w = new Timer(); 56 | 57 | @Before 58 | public void init() { 59 | timers.add(w); 60 | } 61 | 62 | @Test 63 | public void testTimer() { 64 | //wait for start of a second 65 | long startDelay = 1001 - (System.currentTimeMillis() % 1000); 66 | try { 67 | Thread.sleep(startDelay); 68 | } catch (InterruptedException e) { 69 | } 70 | 71 | LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("UTC")); 72 | long curTime = localDateTime.getSecond() + localDateTime.getMinute() * 60 + localDateTime.getHour() * 3600; 73 | w.startTime = curTime; 74 | 75 | int userCount = 1000; 76 | for (int i = 0; i < userCount; i++) { 77 | users.put(String.valueOf(i), user); 78 | } 79 | 80 | when(userRegistry.getUsers()).thenReturn(users); 81 | when(sessionsHolder.getUserSession()).thenReturn(userSession); 82 | when(user.getUserProfile()).thenReturn(userProfile); 83 | when(userProfile.getDashBoards()).thenReturn(new DashBoard[] {}); 84 | when(userProfile.getDashboardTimerWidgets()).thenReturn(timers); 85 | 86 | timerWorker.run(); 87 | 88 | verify(timerWorker, times(1000)).timerTick(eq(curTime), eq(curTime)); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/handlers/workflow/HardwareHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.workflow; 2 | 3 | import cc.blynk.common.model.messages.protocol.HardwareMessage; 4 | import cc.blynk.common.utils.ServerProperties; 5 | import cc.blynk.server.dao.*; 6 | import cc.blynk.server.exceptions.DeviceNotInNetworkException; 7 | import cc.blynk.server.exceptions.NoActiveDashboardException; 8 | import cc.blynk.server.model.auth.Session; 9 | import cc.blynk.server.model.auth.User; 10 | import cc.blynk.server.model.auth.nio.ChannelState; 11 | import io.netty.channel.ChannelHandler; 12 | import io.netty.channel.ChannelHandlerContext; 13 | 14 | /** 15 | * The Blynk Project. 16 | * Created by Dmitriy Dumanskiy. 17 | * Created on 2/1/2015. 18 | * 19 | */ 20 | @ChannelHandler.Sharable 21 | public class HardwareHandler extends BaseSimpleChannelInboundHandler { 22 | 23 | private final Storage storage; 24 | 25 | public HardwareHandler(ServerProperties props, FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder) { 26 | super(props, fileManager, userRegistry, sessionsHolder); 27 | this.storage = new GraphInMemoryStorage(props.getIntProperty("user.in.memory.storage.limit")); 28 | } 29 | 30 | private static boolean pinModeMessage(String body) { 31 | return body != null && body.length() > 0 && body.charAt(0) == 'p'; 32 | } 33 | 34 | @Override 35 | protected void messageReceived(ChannelHandlerContext ctx, User user, HardwareMessage message) throws Exception { 36 | Session session = sessionsHolder.getUserSession().get(user); 37 | 38 | ChannelState channelState = (ChannelState) ctx.channel(); 39 | 40 | //todo 41 | //for hardware command do not wait for hardware response. 42 | if (channelState.isHardwareChannel) { 43 | //if message from hardware, check if it belongs to graph. so we need save it in that case 44 | String body = storage.store(user, channelState.dashId, message.body, message.id); 45 | Session.sendMessageTo(message.updateMessageBody(body), session.appChannels); 46 | } else { 47 | if (user.getUserProfile().getActiveDashId() == null) { 48 | throw new NoActiveDashboardException("No active dashboard.", message.id); 49 | } 50 | 51 | if (session.hardwareChannels.size() == 0) { 52 | if (pinModeMessage(message.body) && user.getUserProfile().isJustActivated()) { 53 | log.trace("No device and Pin Mode message catch. Remembering."); 54 | user.getUserProfile().setPinModeMessage(message); 55 | user.getUserProfile().setJustActivated(false); 56 | } 57 | throw new DeviceNotInNetworkException("No device in session.", message.id); 58 | } 59 | 60 | session.sendMessageToHardware(user.getUserProfile().getActiveDashId(), message); 61 | } 62 | 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /integration-tests/src/test/java/cc/blynk/integration/model/TestHardClient.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.integration.model; 2 | 3 | import cc.blynk.client.core.HardwareClient; 4 | import cc.blynk.common.handlers.decoders.ReplayingMessageDecoder; 5 | import cc.blynk.common.handlers.encoders.DeviceMessageEncoder; 6 | import io.netty.bootstrap.Bootstrap; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.ChannelPipeline; 9 | import io.netty.channel.nio.NioEventLoopGroup; 10 | import io.netty.channel.socket.SocketChannel; 11 | import io.netty.channel.socket.nio.NioSocketChannel; 12 | import org.mockito.Mockito; 13 | 14 | import java.io.BufferedReader; 15 | import java.util.Random; 16 | 17 | /** 18 | * The Blynk Project. 19 | * Created by Dmitriy Dumanskiy. 20 | * Created on 1/31/2015. 21 | */ 22 | public class TestHardClient extends HardwareClient { 23 | 24 | public final SimpleClientHandler responseMock; 25 | private ChannelPipeline pipeline; 26 | 27 | private int msgId; 28 | 29 | public TestHardClient(String host, int port) { 30 | super(host, port, Mockito.mock(Random.class)); 31 | Mockito.when(random.nextInt(Short.MAX_VALUE)).thenReturn(1); 32 | 33 | this.responseMock = Mockito.mock(SimpleClientHandler.class); 34 | this.msgId = 0; 35 | } 36 | 37 | @Override 38 | public void start(BufferedReader commandInputStream) { 39 | if (commandInputStream == null) { 40 | nioEventLoopGroup = new NioEventLoopGroup(); 41 | 42 | Bootstrap b = new Bootstrap(); 43 | b.group(nioEventLoopGroup).channel(NioSocketChannel.class).handler(getChannelInitializer()); 44 | 45 | try { 46 | // Start the connection attempt. 47 | this.channel = b.connect(host, port).sync().channel(); 48 | } catch (InterruptedException e) { 49 | log.error(e); 50 | } 51 | } else { 52 | super.start(commandInputStream); 53 | } 54 | } 55 | 56 | @Override 57 | public ChannelInitializer getChannelInitializer() { 58 | return new ChannelInitializer() { 59 | @Override 60 | protected void initChannel(SocketChannel ch) throws Exception { 61 | ChannelPipeline pipeline = ch.pipeline(); 62 | TestHardClient.this.pipeline = pipeline; 63 | 64 | pipeline.addLast(new ReplayingMessageDecoder()); 65 | pipeline.addLast(new DeviceMessageEncoder()); 66 | pipeline.addLast(responseMock); 67 | } 68 | }; 69 | } 70 | 71 | public TestHardClient send(String line) { 72 | send(produceMessageBaseOnUserInput(line, ++msgId)); 73 | return this; 74 | } 75 | 76 | public void reset() { 77 | Mockito.reset(responseMock); 78 | msgId = 0; 79 | } 80 | 81 | public void replace(SimpleClientHandler simpleClientHandler) { 82 | pipeline.removeLast(); 83 | pipeline.addLast(simpleClientHandler); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/handlers/auth/RegisterHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.auth; 2 | 3 | import cc.blynk.common.handlers.DefaultExceptionHandler; 4 | import cc.blynk.common.model.messages.protocol.appllication.RegisterMessage; 5 | import cc.blynk.server.dao.FileManager; 6 | import cc.blynk.server.dao.SessionsHolder; 7 | import cc.blynk.server.dao.UserRegistry; 8 | import cc.blynk.server.utils.EMailValidator; 9 | import io.netty.channel.ChannelHandler; 10 | import io.netty.channel.ChannelHandlerContext; 11 | import io.netty.channel.SimpleChannelInboundHandler; 12 | 13 | import static cc.blynk.common.enums.Response.*; 14 | import static cc.blynk.common.model.messages.MessageFactory.produce; 15 | 16 | /** 17 | * Process register message. 18 | * Divides input string by spaces on 2 parts: 19 | * "username" "password". 20 | * Checks if user not registered yet. If not - registering. 21 | * 22 | * For instance, incoming register message may be : "user@mail.ua my_password" 23 | * 24 | * The Blynk Project. 25 | * Created by Dmitriy Dumanskiy. 26 | * Created on 2/1/2015. 27 | */ 28 | @ChannelHandler.Sharable 29 | public class RegisterHandler extends SimpleChannelInboundHandler implements DefaultExceptionHandler { 30 | 31 | protected final FileManager fileManager; 32 | protected final UserRegistry userRegistry; 33 | protected final SessionsHolder sessionsHolder; 34 | 35 | public RegisterHandler(FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder) { 36 | this.fileManager = fileManager; 37 | this.userRegistry = userRegistry; 38 | this.sessionsHolder = sessionsHolder; 39 | } 40 | 41 | @Override 42 | protected void channelRead0(ChannelHandlerContext ctx, RegisterMessage message) throws Exception { 43 | String[] messageParts = message.body.split(" ", 2); 44 | 45 | //expecting message with 2 parts, described above in comment. 46 | if (messageParts.length != 2) { 47 | log.error("Register Handler. Wrong income message format. {}", message); 48 | ctx.writeAndFlush(produce(message.id, ILLEGAL_COMMAND)); 49 | return; 50 | } 51 | 52 | String userName = messageParts[0].toLowerCase(); 53 | String pass = messageParts[1]; 54 | log.info("Trying register user : {}", userName); 55 | 56 | if (!EMailValidator.isValid(userName)) { 57 | log.error("Register Handler. Wrong email: {}", userName); 58 | ctx.writeAndFlush(produce(message.id, ILLEGAL_COMMAND)); 59 | return; 60 | } 61 | 62 | if (userRegistry.isUserExists(userName)) { 63 | log.warn("User with name {} already exists.", userName); 64 | ctx.writeAndFlush(produce(message.id, USER_ALREADY_REGISTERED)); 65 | return; 66 | } 67 | 68 | userRegistry.createNewUser(userName, pass); 69 | 70 | log.info("Registered {}.", userName); 71 | 72 | ctx.writeAndFlush(produce(message.id, OK)); 73 | } 74 | 75 | @Override 76 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 77 | handleGeneralException(ctx, cause); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /server/tcp-server/src/test/resources/json_test/user_profile_json_5.txt: -------------------------------------------------------------------------------- 1 | { 2 | "activeDashId" : 1, 3 | "dashBoards" : 4 | [ 5 | { 6 | "id":1, 7 | "name":"My Dashboard", 8 | "widgets" : [ 9 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"BUTTON", "pinType":"DIGITAL", "pin":1, "value":"1"}, 10 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER_LARGE", "pinType":"DIGITAL", "pin":2, "value":"1", "state":"ON"}, 11 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER_LARGE", "pinType":"ANALOG", "pin":3, "value":"0", "state":"OFF"}, 12 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER", "pinType":"VIRTUAL", "pin":4, "value":"244" }, 13 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"TIMER", "pinType":"DIGITAL", "pin":5, "value":"1"}, 14 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"LED", "pinType":"DIGITAL", "pin":6}, 15 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"DIGIT4_DISPLAY", "pinType":"DIGITAL", "pin":7}, 16 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"GRAPH", "pinType":"DIGITAL", "pin":8}, 17 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"GRAPH", "pinType":"DIGITAL", "pin":9} 18 | ], 19 | "boardType":"UNO" 20 | }, 21 | 22 | { 23 | "id":2, 24 | "name":"My Dashboard", 25 | "widgets" : [ 26 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"BUTTON", "pinType":"DIGITAL", "pin":1, "value":"1"}, 27 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER_LARGE", "pinType":"DIGITAL", "pin":2, "value":"1", "state":"ON"}, 28 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER_LARGE", "pinType":"ANALOG", "pin":3, "value":"0", "state":"OFF"}, 29 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"SLIDER", "pinType":"VIRTUAL", "pin":4, "value":"244" }, 30 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"TIMER", "pinType":"DIGITAL", "pin":5, "value":"1"}, 31 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"LED", "pinType":"DIGITAL", "pin":6}, 32 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"DIGIT4_DISPLAY", "pinType":"DIGITAL", "pin":7}, 33 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"GRAPH", "pinType":"DIGITAL", "pin":8}, 34 | {"id":1, "x":1, "y":1, "dashBoardId":1, "label":"Some Text", "type":"GRAPH", "pinType":"DIGITAL", "pin":2} 35 | ], 36 | "boardType":"UNO" 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/dao/UserRegistry.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.dao; 2 | 3 | import cc.blynk.server.model.auth.User; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | 7 | import java.util.Map; 8 | import java.util.UUID; 9 | 10 | /** 11 | * Helper class for holding info regarding registered users and profiles. 12 | * 13 | * User: ddumanskiy 14 | * Date: 8/11/13 15 | * Time: 4:02 PM 16 | */ 17 | public class UserRegistry { 18 | 19 | private static final Logger log = LogManager.getLogger(UserRegistry.class); 20 | private Map users; 21 | //init user DB if possible 22 | 23 | public UserRegistry(Map users) { 24 | //reading DB to RAM. 25 | this.users = users; 26 | } 27 | 28 | public static Integer getDashIdByToken(User user, String token) { 29 | for (Map.Entry dashToken : user.getDashTokens().entrySet()) { 30 | if (dashToken.getValue().equals(token)) { 31 | return dashToken.getKey(); 32 | } 33 | } 34 | throw new RuntimeException("Error getting dashId for user. FIX/"); 35 | } 36 | 37 | private static String generateNewToken() { 38 | return UUID.randomUUID().toString().replace("-", ""); 39 | } 40 | 41 | public boolean isUserExists(String name) { 42 | return users.get(name) != null; 43 | } 44 | 45 | public User getByName(String name) { 46 | return users.get(name); 47 | } 48 | 49 | public Map getUsers() { 50 | return users; 51 | } 52 | 53 | //todo optimize 54 | public User getUserByToken(String token) { 55 | for (User user : users.values()) { 56 | for (String userToken : user.getDashTokens().values()) { 57 | if (userToken.equals(token)) { 58 | return user; 59 | } 60 | } 61 | } 62 | return null; 63 | } 64 | 65 | public String getToken(User user, Integer dashboardId) { 66 | Map dashTokens = user.getDashTokens(); 67 | String token = dashTokens.get(dashboardId); 68 | 69 | //if token not exists. generate new one 70 | if (token == null) { 71 | log.info("Token for user {} and dashId {} not generated yet.", user.getName(), dashboardId); 72 | token = generateNewToken(); 73 | user.putToken(dashboardId, token); 74 | log.info("Generated token for user {} and dashId {} is {}.", user.getName(), dashboardId, token); 75 | } else { 76 | log.info("Token for user {} and dashId {} generated already. Token {}", user.getName(), dashboardId, token); 77 | } 78 | 79 | return token; 80 | } 81 | 82 | public String refreshToken(User user, Integer dashboardId) { 83 | String token = generateNewToken(); 84 | user.putToken(dashboardId, token); 85 | log.info("Refreshed token for user {} and dashId {} is {}.", user.getName(), dashboardId, token); 86 | return token; 87 | } 88 | 89 | public User createNewUser(String userName, String pass) { 90 | User newUser = new User(userName, pass); 91 | users.put(userName, newUser); 92 | return newUser; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/model/auth/Session.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.model.auth; 2 | 3 | import cc.blynk.common.model.messages.MessageBase; 4 | import cc.blynk.server.exceptions.DeviceNotInNetworkException; 5 | import cc.blynk.server.exceptions.UserAlreadyLoggedIn; 6 | import cc.blynk.server.model.auth.nio.ChannelState; 7 | import io.netty.channel.Channel; 8 | import io.netty.channel.ChannelFuture; 9 | import io.netty.util.internal.ConcurrentSet; 10 | import org.apache.logging.log4j.LogManager; 11 | import org.apache.logging.log4j.Logger; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | /** 18 | * The Blynk Project. 19 | * Created by Dmitriy Dumanskiy. 20 | * Created on 2/1/2015. 21 | * 22 | * DefaultChannelGroup.java too complicated. so doing in simple way for now. 23 | * 24 | */ 25 | public class Session { 26 | 27 | private static final Logger log = LogManager.getLogger(Session.class); 28 | 29 | public final Set appChannels = new ConcurrentSet<>(); 30 | public final Set hardwareChannels = new ConcurrentSet<>(); 31 | 32 | public static List sendMessageTo(MessageBase message, Set channels) { 33 | if (channels.size() == 0) { 34 | throw new DeviceNotInNetworkException("No device in session.", message.id); 35 | } 36 | List futureList = new ArrayList<>(); 37 | for (Channel channel : channels) { 38 | log.trace("Sending {} to {}", message, channel); 39 | futureList.add(channel.writeAndFlush(message)); 40 | } 41 | return futureList; 42 | } 43 | 44 | public List sendMessageToHardware(Integer activeDashId, MessageBase message) { 45 | List futureList = new ArrayList<>(); 46 | for (Channel channel : hardwareChannels) { 47 | Integer dashId = ((ChannelState) channel).dashId; 48 | if (dashId.equals(activeDashId)) { 49 | log.trace("Sending {} to {}", message, channel); 50 | futureList.add(channel.writeAndFlush(message)); 51 | } 52 | } 53 | return futureList; 54 | } 55 | 56 | public void addChannel(ChannelState channel, int msgId) { 57 | if (channel.isHardwareChannel) { 58 | addChannel(hardwareChannels, channel, msgId); 59 | } else { 60 | addChannel(appChannels, channel, msgId); 61 | } 62 | } 63 | 64 | //todo not sure, but netty processes same channel in same thread, so no sync 65 | private void addChannel(Set channelSet, Channel channel, int msgId) { 66 | //if login from same channel again - do not allow. 67 | if (channelSet.contains(channel)) { 68 | throw new UserAlreadyLoggedIn("User already logged. Client problem. CHECK!", msgId); 69 | } 70 | channelSet.add(channel); 71 | } 72 | 73 | public List sendMessageToHardware(MessageBase message) { 74 | return sendMessageTo(message, hardwareChannels); 75 | } 76 | 77 | public void remove(ChannelState channelServer) { 78 | if (channelServer.isHardwareChannel) { 79 | hardwareChannels.remove(channelServer); 80 | } else { 81 | appChannels.remove(channelServer); 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/handlers/workflow/SaveProfileHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.workflow; 2 | 3 | import cc.blynk.common.model.messages.protocol.appllication.SaveProfileMessage; 4 | import cc.blynk.common.utils.ServerProperties; 5 | import cc.blynk.server.dao.FileManager; 6 | import cc.blynk.server.dao.SessionsHolder; 7 | import cc.blynk.server.dao.UserRegistry; 8 | import cc.blynk.server.exceptions.IllegalCommandException; 9 | import cc.blynk.server.exceptions.NotAllowedException; 10 | import cc.blynk.server.model.UserProfile; 11 | import cc.blynk.server.model.auth.User; 12 | import cc.blynk.server.utils.JsonParser; 13 | import io.netty.channel.ChannelHandler; 14 | import io.netty.channel.ChannelHandlerContext; 15 | 16 | import static cc.blynk.common.enums.Response.OK; 17 | import static cc.blynk.common.model.messages.MessageFactory.produce; 18 | 19 | /** 20 | * The Blynk Project. 21 | * Created by Dmitriy Dumanskiy. 22 | * Created on 2/1/2015. 23 | * 24 | */ 25 | @ChannelHandler.Sharable 26 | public class SaveProfileHandler extends BaseSimpleChannelInboundHandler { 27 | 28 | //I have to use volatile for reloadable props to be sure updated value will be visible by all threads 29 | private volatile int DASH_MAX_LIMIT; 30 | 31 | //I have to use volatile for reloadable props to be sure updated value will be visible by all threads 32 | private volatile int USER_PROFILE_MAX_SIZE; 33 | 34 | public SaveProfileHandler(ServerProperties props, FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder) { 35 | super(props, fileManager, userRegistry, sessionsHolder); 36 | updateProperties(props); 37 | } 38 | 39 | @Override 40 | protected void messageReceived(ChannelHandlerContext ctx, User user, SaveProfileMessage message) throws Exception { 41 | String userProfileString = message.body; 42 | 43 | //expecting message with 2 parts 44 | if (userProfileString == null || userProfileString.equals("")) { 45 | throw new IllegalCommandException("Save Profile Handler. Income profile message is empty.", message.id); 46 | } 47 | 48 | if (userProfileString.length() > USER_PROFILE_MAX_SIZE) { 49 | throw new NotAllowedException(String.format("User profile size is larger than %d bytes.", USER_PROFILE_MAX_SIZE), message.id); 50 | } 51 | 52 | log.debug("Trying to parse user profile : {}", userProfileString); 53 | UserProfile userProfile = JsonParser.parseProfile(userProfileString, message.id); 54 | 55 | if (userProfile.getDashBoards() != null && userProfile.getDashBoards().length > DASH_MAX_LIMIT) { 56 | throw new NotAllowedException( 57 | String.format("Not allowed to create more than %s dashboards.", DASH_MAX_LIMIT), message.id); 58 | } 59 | 60 | log.info("Trying save user profile."); 61 | 62 | userProfile.calcGraphPins(); 63 | 64 | user.setUserProfile(userProfile); 65 | ctx.writeAndFlush(produce(message.id, OK)); 66 | } 67 | 68 | @Override 69 | public void updateProperties(ServerProperties props) { 70 | super.updateProperties(props); 71 | this.DASH_MAX_LIMIT = props.getIntProperty("user.dashboard.max.limit"); 72 | this.USER_PROFILE_MAX_SIZE = props.getIntProperty("user.profile.max.size") * 1024; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /server/tcp-server/src/main/java/cc/blynk/server/handlers/auth/AppLoginHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.server.handlers.auth; 2 | 3 | import cc.blynk.common.handlers.DefaultExceptionHandler; 4 | import cc.blynk.common.model.messages.protocol.appllication.LoginMessage; 5 | import cc.blynk.server.dao.FileManager; 6 | import cc.blynk.server.dao.SessionsHolder; 7 | import cc.blynk.server.dao.UserRegistry; 8 | import cc.blynk.server.exceptions.IllegalCommandException; 9 | import cc.blynk.server.exceptions.UserNotAuthenticated; 10 | import cc.blynk.server.exceptions.UserNotRegistered; 11 | import cc.blynk.server.model.auth.User; 12 | import cc.blynk.server.model.auth.nio.ChannelState; 13 | import io.netty.channel.ChannelHandler; 14 | import io.netty.channel.ChannelHandlerContext; 15 | import io.netty.channel.SimpleChannelInboundHandler; 16 | 17 | import static cc.blynk.common.enums.Response.OK; 18 | import static cc.blynk.common.model.messages.MessageFactory.produce; 19 | 20 | /** 21 | * Handler responsible for managing hardware and apps login messages. 22 | * Initializes netty channel with a state tied with user. 23 | * 24 | * The Blynk Project. 25 | * Created by Dmitriy Dumanskiy. 26 | * Created on 2/1/2015. 27 | * 28 | */ 29 | @ChannelHandler.Sharable 30 | public class AppLoginHandler extends SimpleChannelInboundHandler implements DefaultExceptionHandler { 31 | 32 | protected final FileManager fileManager; 33 | protected final UserRegistry userRegistry; 34 | protected final SessionsHolder sessionsHolder; 35 | 36 | public AppLoginHandler(FileManager fileManager, UserRegistry userRegistry, SessionsHolder sessionsHolder) { 37 | this.fileManager = fileManager; 38 | this.userRegistry = userRegistry; 39 | this.sessionsHolder = sessionsHolder; 40 | } 41 | 42 | @Override 43 | protected void channelRead0(ChannelHandlerContext ctx, LoginMessage message) throws Exception { 44 | String[] messageParts = message.body.split(" ", 2); 45 | 46 | if (messageParts.length == 2) { 47 | appLogin(ctx, message.id, messageParts[0], messageParts[1]); 48 | } else { 49 | throw new IllegalCommandException("Wrong income message format.", message.id); 50 | } 51 | 52 | ctx.writeAndFlush(produce(message.id, OK)); 53 | } 54 | 55 | private void appLogin(ChannelHandlerContext ctx, int messageId, String username, String pass) { 56 | String userName = username.toLowerCase(); 57 | User user = userRegistry.getByName(userName); 58 | 59 | if (user == null) { 60 | throw new UserNotRegistered(String.format("User not registered. Username '%s', %s", userName, ctx.channel()), messageId); 61 | } 62 | 63 | if (!user.getPass().equals(pass)) { 64 | throw new UserNotAuthenticated(String.format("User credentials are wrong. Username '%s', %s", userName, ctx.channel()), messageId); 65 | } 66 | 67 | ChannelState channelState = (ChannelState) ctx.channel(); 68 | channelState.user = user; 69 | 70 | sessionsHolder.addChannelToGroup(user, channelState, messageId); 71 | 72 | log.info("Adding app channel with id {} to userGroup {}.", ctx.channel(), user.getName()); 73 | } 74 | 75 | @Override 76 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 77 | handleGeneralException(ctx, cause); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /common/src/main/java/cc/blynk/common/handlers/DefaultExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package cc.blynk.common.handlers; 2 | 3 | import cc.blynk.common.exceptions.BaseServerException; 4 | import cc.blynk.common.exceptions.UnsupportedCommandException; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.DecoderException; 7 | import io.netty.handler.ssl.NotSslRecordException; 8 | import io.netty.handler.timeout.ReadTimeoutException; 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.Logger; 11 | import org.apache.logging.log4j.ThreadContext; 12 | 13 | import javax.net.ssl.SSLException; 14 | 15 | import static cc.blynk.common.model.messages.MessageFactory.produce; 16 | 17 | /** 18 | * The Blynk Project. 19 | * Created by Dmitriy Dumanskiy. 20 | * Created on 2/11/2015. 21 | */ 22 | public interface DefaultExceptionHandler { 23 | 24 | static final Logger log = LogManager.getLogger(DefaultExceptionHandler.class); 25 | 26 | public default void handleGeneralException(ChannelHandlerContext ctx, Throwable cause) throws Exception { 27 | if (cause instanceof BaseServerException) { 28 | handleAppException(ctx, (BaseServerException) cause); 29 | } else { 30 | handleUnexpectedException(ctx, cause); 31 | } 32 | } 33 | 34 | public default void handleUnexpectedException(ChannelHandlerContext ctx, Throwable cause) throws Exception { 35 | if (cause instanceof ReadTimeoutException) { 36 | log.trace("Channel was inactive for a long period. Closing..."); 37 | //channel is already closed here by ReadTimeoutHandler 38 | } else if (cause instanceof DecoderException && cause.getCause() instanceof UnsupportedCommandException) { 39 | log.error("Input command is invalid. Closing socket.", cause.getMessage()); 40 | ctx.close(); 41 | } else if (cause instanceof NotSslRecordException) { 42 | log.error("Not secure connection attempt detected. {}.", cause.getMessage()); 43 | ctx.close(); 44 | } else if (cause instanceof SSLException) { 45 | log.error("SSL exception. {}.", cause.getMessage()); 46 | ctx.close(); 47 | } else { 48 | String errorMessage = cause.getMessage() == null ? "" : cause.getMessage(); 49 | 50 | //all this are expected when user goes offline without closing socket correctly... 51 | switch (errorMessage) { 52 | case "Connection reset by peer": 53 | case "No route to host": 54 | case "Connection timed out": 55 | log.debug("Client goes offline. Reason : {}", cause.getMessage()); 56 | break; 57 | default: 58 | log.error("Unexpected error!!!", cause); 59 | break; 60 | } 61 | } 62 | 63 | } 64 | 65 | public default void handleAppException(ChannelHandlerContext ctx, BaseServerException baseServerException) { 66 | //no need for stack trace for known exceptions 67 | log.error(baseServerException.getMessage()); 68 | try { 69 | //todo handle exception here? 70 | ctx.writeAndFlush(produce(baseServerException)); 71 | } finally { 72 | //cleanup logging context in case error happened. 73 | ThreadContext.clearMap(); 74 | } 75 | } 76 | 77 | } 78 | --------------------------------------------------------------------------------