void processMsg(SessionMetaData sessionMd, String msg) throws IOException {
        WebSocketSessionRef sessionRef = sessionMd.sessionRef;
        WsCommandsWrapper cmdsWrapper;
        try {
            switch (sessionRef.getSessionType()) {
                case GENERAL:
                    cmdsWrapper = JacksonUtil.fromString(msg, WsCommandsWrapper.class);
                    break;
                case TELEMETRY:
                    cmdsWrapper = JacksonUtil.fromString(msg, TelemetryCmdsWrapper.class).toCommonCmdsWrapper();
                    break;
                case NOTIFICATIONS:
                    cmdsWrapper = JacksonUtil.fromString(msg, NotificationCmdsWrapper.class).toCommonCmdsWrapper();
                    break;
                default:
                    return;
            }
        } catch (Exception e) {
            log.debug("{} Failed to decode subscription cmd: {}", sessionRef, e.getMessage(), e);
            if (sessionRef.getSecurityCtx() != null) {
                webSocketService.sendError(sessionRef, 1, SubscriptionErrorCode.BAD_REQUEST, "Failed to parse the payload");
            } else {
                close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage()));
            }
            return;
        }

        if (sessionRef.getSecurityCtx() != null) {
            log.trace("{} Processing {}", sessionRef, msg);
            webSocketService.handleCommands(sessionRef, cmdsWrapper);
        } else {
            AuthCmd authCmd = cmdsWrapper.getAuthCmd();
            if (authCmd == null) {
                close(sessionRef, CloseStatus.POLICY_VIOLATION.withReason("Auth cmd is missing"));
                return;
            }
            log.trace("{} Authenticating session", sessionRef);
            SecurityUser securityCtx;
            try {
                securityCtx = authenticationProvider.authenticate(authCmd.getToken());
            } catch (Exception e) {
                close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage()));
                return;
            }
            sessionRef.setSecurityCtx(securityCtx);
            pendingSessions.invalidate(sessionMd.session.getId());
            establishSession(sessionMd.session, sessionRef, sessionMd);

            webSocketService.handleCommands(sessionRef, cmdsWrapper);
        }
    }
@Test
    void sendHandler_onMsg_allProcessed() throws Exception {
        Deque<String> msgs = new ConcurrentLinkedDeque<>();
        doAnswer(inv -> msgs.add(inv.getArgument(1))).when(wsHandler).processMsg(any(), any());
        for (int i = 0; i < 100; i++) {
            String msg = String.valueOf(i);
            executor.submit(() -> {
                try {
                    Thread.sleep(new Random().nextInt(50));
                    sendHandler.onMsg(msg);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        executor.shutdown();
        executor.awaitTermination(5, TimeUnit.SECONDS);

        assertThat(msgs).map(Integer::parseInt).doesNotHaveDuplicates().hasSize(100);
    }