package cc.blynk.server.core.model.auth;
import cc.blynk.server.core.protocol.model.messages.ResponseWithBodyMessage;
import cc.blynk.server.core.session.HardwareStateHolder;
import cc.blynk.server.core.stats.metrics.InstanceLoadMeter;
import cc.blynk.server.handlers.BaseSimpleChannelInboundHandler;
import cc.blynk.utils.ArrayUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoop;
import io.netty.util.internal.ConcurrentSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.HashSet;
import java.util.Set;
import static cc.blynk.server.core.protocol.enums.Command.RESPONSE;
import static cc.blynk.server.core.protocol.enums.Response.DEVICE_WENT_OFFLINE;
import static cc.blynk.utils.BlynkByteBufUtil.makeUTF8StringMessage;
import static cc.blynk.utils.StateHolderUtil.*;
import static cc.blynk.utils.StringUtils.DEVICE_SEPARATOR;
import static cc.blynk.utils.StringUtils.prependDashIdAndDeviceId;
/**
* The Blynk Project.
* Created by Dmitriy Dumanskiy.
* Created on 2/1/2015.
* <p>
* DefaultChannelGroup.java too complicated. so doing in simple way for now.
*/
public class Session {
private static final Logger log = LogManager.getLogger(Session.class);
public final EventLoop initialEventLoop;
public final Set<Channel> appChannels = new ConcurrentSet<>();
public final Set<Channel> hardwareChannels = new ConcurrentSet<>();
private final ChannelFutureListener appRemover = future -> removeAppChannel(future.channel());
private final ChannelFutureListener hardRemover = future -> removeHardChannel(future.channel());
public Session(EventLoop initialEventLoop) {
this.initialEventLoop = initialEventLoop;
}
private static int getRequestRate(Set<Channel> channels) {
double sum = 0;
for (Channel c : channels) {
BaseSimpleChannelInboundHandler handler = c.pipeline().get(BaseSimpleChannelInboundHandler.class);
if (handler != null) {
InstanceLoadMeter loadMeter = handler.getQuotaMeter();
sum += loadMeter.getOneMinuteRateNoTick();
}
}
return (int) sum;
}
public static boolean needSync(Channel channel, String sharedToken) {
BaseSimpleChannelInboundHandler appHandler = channel.pipeline().get(BaseSimpleChannelInboundHandler.class);
return appHandler != null && appHandler.getState().contains(sharedToken);
}
public void addAppChannel(Channel appChannel) {
if (appChannels.add(appChannel)) {
appChannel.closeFuture().addListener(appRemover);
}
}
public void removeAppChannel(Channel appChannel) {
if (appChannels.remove(appChannel)) {
appChannel.closeFuture().removeListener(appRemover);
}
}
public void addHardChannel(Channel hardChannel) {
if (hardwareChannels.add(hardChannel)) {
hardChannel.closeFuture().addListener(hardRemover);
}
}
public void removeHardChannel(Channel hardChannel) {
if (hardwareChannels.remove(hardChannel)) {
hardChannel.closeFuture().removeListener(hardRemover);
}
}
private Set<Channel> filter(int activeDashId, int[] deviceIds) {
final Set<Channel> targetChannels = new HashSet<>();
for (Channel channel : hardwareChannels) {
final HardwareStateHolder hardwareState = getHardState(channel);
if (hardwareState.dashId == activeDashId &&
(deviceIds.length == 0 || ArrayUtil.contains(deviceIds, hardwareState.deviceId))) {
targetChannels.add(channel);
}
}
return targetChannels;
}
private Set<Channel> filter(int activeDashId, int deviceId) {
final Set<Channel> targetChannels = new HashSet<>();
for (Channel channel : hardwareChannels) {
if (isSameDashAndDeviceId(channel, activeDashId, deviceId)) {
targetChannels.add(channel);
}
}
return targetChannels;
}
public boolean sendMessageToHardware(int activeDashId, short cmd, int msgId, String body, int deviceId) {
return hardwareChannels.size() == 0 || sendMessageToHardware(filter(activeDashId, deviceId), cmd, msgId, body);
}
public boolean sendMessageToHardware(int activeDashId, short cmd, int msgId, String body, int... deviceIds) {
return hardwareChannels.size() == 0 || sendMessageToHardware(filter(activeDashId, deviceIds), cmd, msgId, body);
}
private boolean sendMessageToHardware(Set<Channel> targetChannels, short cmd, int msgId, String body) {
final int channelsNum = targetChannels.size();
if (channelsNum == 0) {
return true; // -> no active hardware
}
send(targetChannels, channelsNum, cmd, msgId, body);
return false; // -> there is active hardware
}
public boolean isHardwareConnected() {
return hardwareChannels.size() > 0;
}
public boolean isHardwareConnected(int dashId, int deviceId) {
for (Channel channel : hardwareChannels) {
if (isSameDashAndDeviceId(channel, dashId, deviceId)) {
return true;
}
}
return false;
}
public boolean isHardwareConnected(int dashId) {
for (Channel channel : hardwareChannels) {
if (isSameDash(channel, dashId)) {
return true;
}
}
return false;
}
public void sendOfflineMessageToApps(int dashId) {
if (isAppConnected()) {
log.trace("Sending device offline message.");
ResponseWithBodyMessage response = new ResponseWithBodyMessage(0, RESPONSE, DEVICE_WENT_OFFLINE, dashId);
for (Channel appChannel : appChannels) {
if (appChannel.isWritable()) {
appChannel.writeAndFlush(response, appChannel.voidPromise());
}
}
}
}
public void sendToApps(short cmd, int msgId, int dashId, int deviceId) {
final int targetsNum = appChannels.size();
if (targetsNum == 0) {
return;
}
send(appChannels, targetsNum, cmd, msgId, "" + dashId + DEVICE_SEPARATOR + deviceId);
}
public void sendToApps(short cmd, int msgId, int dashId, int deviceId, String body) {
final int targetsNum = appChannels.size();
if (targetsNum == 0) {
return;
}
send(appChannels, targetsNum, cmd, msgId, prependDashIdAndDeviceId(dashId, deviceId, body));
}
private void send(Set<Channel> targets, int targetsNum, short cmd, int msgId, String body) {
ByteBuf msg = makeUTF8StringMessage(cmd, msgId, body);
if (targetsNum > 1) {
msg.retain(targetsNum - 1).markReaderIndex();
}
for (Channel channel : targets) {
if (channel.isWritable()) {
log.trace("Sending {} to channel {}", body, channel);
channel.writeAndFlush(msg, channel.voidPromise());
}
if (msg.refCnt() > 0) {
msg.resetReaderIndex();
}
}
}
public void sendToSharedApps(Channel sendingChannel, String sharedToken, short cmd, int msgId, String body) {
final Set<Channel> targetChannels = new HashSet<>();
for (Channel channel : appChannels) {
if (channel != sendingChannel && channel.isWritable() && needSync(channel, sharedToken)) {
targetChannels.add(channel);
}
}
final int channelsNum = targetChannels.size();
if (channelsNum == 0) {
return;
}
send(targetChannels, channelsNum, cmd, msgId, body);
}
public boolean isAppConnected() {
return appChannels.size() > 0;
}
public int getAppRequestRate() {
return getRequestRate(appChannels);
}
public int getHardRequestRate() {
return getRequestRate(hardwareChannels);
}
public void closeHardwareChannelByDeviceId(int dashId, int deviceId) {
for (Channel channel : hardwareChannels) {
if (isSameDashAndDeviceId(channel, dashId, deviceId)) {
channel.close();
}
}
}
public void closeHardwareChannelByDashId(int dashId) {
for (Channel channel : hardwareChannels) {
if (isSameDash(channel, dashId)) {
channel.close();
}
}
}
public void closeAll() {
hardwareChannels.forEach(io.netty.channel.Channel::close);
appChannels.forEach(io.netty.channel.Channel::close);
}
}