• Познакомьтесь с пентестом веб-приложений на практике в нашем новом бесплатном курсе

    «Анализ защищенности веб-приложений»

    🔥 Записаться бесплатно!

  • CTF с учебными материалами Codeby Games

    Обучение кибербезопасности в игровой форме. Более 200 заданий по Active Directory, OSINT, PWN, Веб, Стеганографии, Реверс-инжинирингу, Форензике и Криптографии. Школа CTF с бесплатными курсами по всем категориям.

Comport

  • Автор темы igorpromen2009
  • Дата начала
I

igorpromen2009

Доброго времени суток! Если кто-нибудь знает, где можно раздобыть компонент для работы СОМ-портом в С++Builder, помогите ссылочкой. Спасибо
 
D

DarkKnight

Дл Com-порта впринципи ни какой компонент - не нужен :) Не так функциональность ;-) Расскажи, что нужно именно реализовать, я тебе помогу...
 
I

igorpromen2009

Передать данные в виде кодограмм по 96 бит с компа на комп.
 
D

DarkKnight

Вот принцип работы, не самый лучший, но самый понятный и продуктивный...
Распихиваешь или по потокам или по TTimer....
И проблема решина... Если что не ясно напиши объясню...
P.S.>Сорь за консоль, дома Builder не установлен, винду сносил недавно..

C++:
#include <iostream>
#include <Windows.h>
#define CreateFile CreateFileA
typedef _DCB TDCB;

//Для наглядности в консоли перегрузил вывод...
std::ostream& operator<<(std::ostream& out, const TDCB &Object)
{
out<<"BaudRate : "<<Object.BaudRate<<std::endl;
out<<"ByteSize : "<<(int)Object.ByteSize<<std::endl;
out<<"Parity : "<<(int)Object.Parity<<std::endl;
out<<"StopBits : "<<(int)Object.StopBits<<std::endl;
return out;
}

void main (void)
{
setlocale(LC_ALL,"Russian");
//Открытие порта для ассинхронного режима
HANDLE Port = CreateFile("COM1",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);
GetLastError(); //Этим можем получить ошибку, если что то пошло не так

TDCB lpDCB; // Структура для COM-Portа
if (!GetCommState(Port,&lpDCB)) //Проверим состояние
{
std::cout<<"Не удалось открыть COM-порт";
return;
}
std::cout<<"СOM-Port открыт, текущие настройки :"<<std::endl;
std::cout<<lpDCB;
//Теперь заполним требуемые настройки
lpDCB.BaudRate = 9600; //Скорость
lpDCB.ByteSize = 8; //Число битов данных в передаваемых и принимаемых байтах
lpDCB.Parity = 0; //Контроль четности 0-отсутствие бита четности, 1-дополнить до нечетности, 2-дополнить до четностиж 3- бит четности всегда 1; 4- бит четности всегда 0
lpDCB.StopBits = 2; //Число стоповых битов 0-один бит, 1-полтора бита, 2-два бита

if (!SetCommState(Port,&lpDCB))
{
std::cout<<"Не удалось сменить настройки порта"<<std::endl;
return;
}
std::cout<<"Настройки СOM-Port Изменены, текущие настройки :"<<std::endl;

_COMMTIMEOUTS lpCT; //Настройка таймаутов
GetCommTimeouts(Port,&lpCT); //Пролучить текущие настройки таймаутов
lpCT.ReadIntervalTimeout =100; //Заполнение, но это я тебе сразу скажу - очень много
lpCT.ReadTotalTimeoutConstant = 200;
lpCT.ReadTotalTimeoutMultiplier = 200;
lpCT.WriteTotalTimeoutConstant = 200;
lpCT.WriteTotalTimeoutMultiplier = 200;
SetCommTimeouts(Port,&lpCT); //Применим новые настройки таймаутов


char *S="Привет МедведЪ";
DWORD n;

FlushFileBuffers(Port);


if (!WriteFile(Port,S,strlen(S),&n,NULL))
{
std::cout<<"Что то не так";
}

std::cout<<"В Com-порт записано" <<(int)n<<" байт";

DWORD lpEvtMask;
SetCommMask(Port,EV_RXCHAR); //Определим маску событий (поступление данных в порт)
char buffer[100]; //Буфер для считывания
memset(buffer,0,100); //Обнулим его
WaitCommEvent(Port,&lpEvtMask,NULL); //Ожидаем поступление того что можно считать

if (ReadFile(Port,buffer,100,&n,NULL)) //Считаем
{
std::cout<<"Что то не так";
};
std::cout<<n<<buffer;


CloseHandle(Port); //Закроем порт
}
 
I

igorpromen2009

Спасибо! Но вот что за проблема: как сделать, чтобы при обрабатывании ожидания WaitCommEvent (а отчего еще ж, вроде) не подвисала прога?
 
D

DarkKnight

OVERLAPPED-структура и повешанное событие на нее...
А дальше таймером состояние проверять...
Щас поздно уже... я завтра напишу....
 
I

igorpromen2009

Спасибо, только вот есть два вопроса: 1. Как можно сделать передачу побитно, допустим 96 бит? 2.Как сделать, чтобы прога не подвисала после обработки WaitCommEvent(вродею...)?


Добавлено: Простите, что-то глючит :YES:
 
D

DarkKnight

Вот смотри законченый (рабочий) пример...
Место моего потока используй например таймер 500мс (TTimer) и никаких ожиданий не будет...
P.S. про 96 бит щас напишу (подредактирую пример). Просто только щас заметил..

C++:
#include <iostream>
#include <Windows.h>
//#include <Error.h>
#define CreateFile CreateFileA
typedef _DCB TDCB;
using namespace std;
//Для наглядности в консоли перегрузил вывод...
std::ostream& operator<<(std::ostream& out, const TDCB &Object)
{
out<<"BaudRate : "<<Object.BaudRate<<std::endl;
out<<"ByteSize : "<<(int)Object.ByteSize<<std::endl;
out<<"Parity : "<<(int)Object.Parity<<std::endl;
out<<"StopBits : "<<(int)Object.StopBits<<std::endl;
return out;
}

OVERLAPPED overlapped; //OVERLAPPED структура
HANDLE Port;
HANDLE Port2;
/*
ДЛЯ НАГЛЯДНОСТИ ЛОКАЛЬНО НА КОМПЕ СОЕДИНЯЮ ЧЕРЕЗ НУЛЬ-МОДЕМНЫЙ КАБЕЛЬ COM1(На мат. плате)
И COM2 (дополнительный контроллер)
*/

//Функция настройки COM-PORT
bool SetSettingCom(char *NameComPort,HANDLE hPort)
{
TDCB lpDCB; // Структура для COM-Portа
if (!GetCommState(hPort,&lpDCB)) //Проверим состояние
{
std::cout<<"Не удалось открыть COM-порт "<<NameComPort;
return false;
}
std::cout<<NameComPort<<"СOM-Port открыт, текущие настройки :"<<std::endl;
std::cout<<lpDCB;
//Теперь заполним требуемые настройки
lpDCB.BaudRate = 9600; //Скорость
lpDCB.ByteSize = 8; //Число битов данных в передаваемых и принимаемых байтах
lpDCB.Parity = 0; //Контроль четности 0-отсутствие бита четности, 1-дополнить до нечетности, 2-дополнить до четностиж 3- бит четности всегда 1; 4- бит четности всегда 0
lpDCB.StopBits = 2; //Число стоповых битов 0-один бит, 1-полтора бита, 2-два бита


lpDCB.fBinary = TRUE; //включаем двоичный режим обмена

if (!SetCommState(hPort,&lpDCB))
{
std::cout<<"Не удалось сменить настройки порта "<<NameComPort<<std::endl;
return false;
}
std::cout<<"Настройки СOM-Port "<<NameComPort<<" Изменены, текущие настройки :"<<std::endl;

_COMMTIMEOUTS lpCT; //Настройка таймаутов
GetCommTimeouts(hPort,&lpCT); //Пролучить текущие настройки таймаутов
lpCT.ReadIntervalTimeout =100; //Заполнение, но это я тебе сразу скажу - очень много
lpCT.ReadTotalTimeoutConstant = 200;
lpCT.ReadTotalTimeoutMultiplier = 200;
lpCT.WriteTotalTimeoutConstant = 200;
lpCT.WriteTotalTimeoutMultiplier = 200;
SetCommTimeouts(hPort,&lpCT); //Применим новые настройки таймаутов
std::cout<<std::endl<<"--------------------------"<<std::endl;
return true;
}

//Функция-поток считывания из Сom
DWORD WINAPI ThreadProcPortRead(LPVOID Parametr)
{
DWORD lpEvtMask; //Наша маска
DWORD signal = -1; //Флаг сиглального состояния
char buffer[100]; //Буфер для считывания
OVERLAPPED over; //Оверлаппед-структура (т.к. идет перекрытие )
COMSTAT curstat;
DWORD temp,n;

SetCommMask(Port2,EV_RXCHAR); //Определим маску событий (поступление данных в порт (байта))	
FlushFileBuffers(Port2); // Почистим Порт


over.hEvent = CreateEvent(NULL, true, true, NULL); //Создадим событие, т.к. используем ассинхронное считывание (перекрываемые функции)

while(1) //Бесконечный цикл
{

memset(buffer,0,100); //Обнулим буфферную переменную
WaitCommEvent(Port2,&lpEvtMask,&over); //Ожидаем поступление того что можно считать

signal = WaitForSingleObject(over.hEvent,-1); //усыпить поток до прихода байта (что бы время проца не занимал)
if(signal == WAIT_OBJECT_0) //если событие прихода байта произошло
{
if(GetOverlappedResult(Port2, &over, &temp, true)) //проверяем, успешно ли завершилась перекрываемая операция WaitCommEvent
if((lpEvtMask&EV_RXCHAR)!=0) //если произошло именно событие прихода байта
{
temp=-1; 
ClearCommError(Port2, &temp, &curstat); //Опросим порт и заполним структуру COMSTAT
if (temp == 0) //Если не было ошибок
{
int btr = curstat.cbInQue; //получить количество принятых байтов 
if(btr) //если в буфере порта есть непрочитанные байты
{
ReadFile(Port2, buffer, btr, &n, &overlapped); //прочитать байты из порта в буфер
std::cout<<endl<<"Tread <считанно "<<btr<<"байт> :"<<buffer<<endl; //Вывод статистики
}
}
}
}
}
}

int Title(void)
{
int Result;
cout<<"\t\tМЕНЮ УПРАВЛЕНИЕ COM"<<endl;
cout<<"1. Записать строку в COM-порт"<<endl;
cout<<"2.Выход из системы"<<endl;
cout<<endl<<"Сделайте выбор [] : ";
cin>>Result;
return Result;
}
void main (void)
{
setlocale(LC_ALL,"Russian");
char S[128]; //Буферная переменная для отправки
DWORD n; //Заглушка
DWORD signal=0; //Сигнальный флаг

//Открытие портов для ассинхронного режима
Port = CreateFile("COM1",GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL); 
Port2 = CreateFile("COM2",GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,FILE_FLAG_OVERLAPPED, NULL);
GetLastError(); //Этим можем получить ошибку, если что то пошло не так
//Настройки портов:
SetSettingCom("COM1",Port);
SetSettingCom("COM2",Port2);

//Запуск потока считывание
CreateThread(NULL,0,&ThreadProcPortRead,NULL,0,NULL); //Запустим процесс на считывание
overlapped.hEvent = CreateEvent(NULL, true, true, NULL); //Создадим событие, т.к. ввод у нас асинхронный, понадобится для перекрываемых функций

int isMenu = -1;
while (isMenu != 2)
{
isMenu = Title();

switch (isMenu)
{
case 1: 
cout<<endl;
cout<<"Введите строку для отправки :"<<endl<<">";
getchar(); //Уберем последний символ возврата корретки
gets(S); //Прочитаем строку
WriteFile(Port,S,strlen(S),&n,&overlapped); //Запишим в порт
signal = WaitForSingleObject(overlapped.hEvent, -1); //приостановим поток пока не заваршится перекрываемая операция
if (GetOverlappedResult(Port, &overlapped, &n, true)) //Если все прошло на зачет
{
std::cout<<">>В Com-порт записано " <<(int)n<<" байт"<<endl;
}
break;
default: break;
}
}

CloseHandle(Port); //Закроем порт
CloseHandle(Port2); //Закроем порт
}
 
D

DarkKnight

А вот тоже самое но с 96-байтной структурой , но а вообще зачастую при таких структурированных данных считывают по-байтно, но таким способом как описал я немного быстрее...

C++:
#include <iostream>
#include <Windows.h>
//#include <Error.h>
#define CreateFile CreateFileA
typedef _DCB TDCB;
using namespace std;
//Для наглядности в консоли перегрузил вывод...
std::ostream& operator<<(std::ostream& out, const TDCB &Object)
{
out<<"BaudRate : "<<Object.BaudRate<<std::endl;
out<<"ByteSize : "<<(int)Object.ByteSize<<std::endl;
out<<"Parity : "<<(int)Object.Parity<<std::endl;
out<<"StopBits : "<<(int)Object.StopBits<<std::endl;
return out;
}

//Возьмем к примеру структуру 96 байт
struct MyStruct
{
int a; //4 байт
int c; //4 байт
char name[88]; //Еще 88 байта
};

OVERLAPPED overlapped; //OVERLAPPED структура
HANDLE Port;
HANDLE Port2;
/*
ДЛЯ НАГЛЯДНОСТИ ЛОКАЛЬНО НА КОМПЕ СОЕДИНЯЮ ЧЕРЕЗ НУЛЬ-МОДЕМНЫЙ КАБЕЛЬ COM1(На мат. плате)
И COM2 (дополнительный контроллер)
*/

//Функция настройки COM-PORT
bool SetSettingCom(char *NameComPort,HANDLE hPort)
{
TDCB lpDCB; // Структура для COM-Portа
if (!GetCommState(hPort,&lpDCB)) //Проверим состояние
{
std::cout<<"Не удалось открыть COM-порт "<<NameComPort;
return false;
}
std::cout<<NameComPort<<"СOM-Port открыт, текущие настройки :"<<std::endl;
std::cout<<lpDCB;
//Теперь заполним требуемые настройки
lpDCB.BaudRate = 9600; //Скорость
lpDCB.ByteSize = 8; //Число битов данных в передаваемых и принимаемых байтах
lpDCB.Parity = 0; //Контроль четности 0-отсутствие бита четности, 1-дополнить до нечетности, 2-дополнить до четностиж 3- бит четности всегда 1; 4- бит четности всегда 0
lpDCB.StopBits = 2; //Число стоповых битов 0-один бит, 1-полтора бита, 2-два бита


lpDCB.fBinary = TRUE; //включаем двоичный режим обмена

if (!SetCommState(hPort,&lpDCB))
{
std::cout<<"Не удалось сменить настройки порта "<<NameComPort<<std::endl;
return false;
}
std::cout<<"Настройки СOM-Port "<<NameComPort<<" Изменены, текущие настройки :"<<std::endl;

_COMMTIMEOUTS lpCT; //Настройка таймаутов
GetCommTimeouts(hPort,&lpCT); //Пролучить текущие настройки таймаутов
lpCT.ReadIntervalTimeout =100; //Заполнение, но это я тебе сразу скажу - очень много
lpCT.ReadTotalTimeoutConstant = 200;
lpCT.ReadTotalTimeoutMultiplier = 200;
lpCT.WriteTotalTimeoutConstant = 200;
lpCT.WriteTotalTimeoutMultiplier = 200;
SetCommTimeouts(hPort,&lpCT); //Применим новые настройки таймаутов
std::cout<<std::endl<<"--------------------------"<<std::endl;
return true;
}

//Функция-поток считывания из Сom
DWORD WINAPI ThreadProcPortRead(LPVOID Parametr)
{
DWORD lpEvtMask; //Наша маска
DWORD signal = -1; //Флаг сиглального состояния
char buffer[100]; //Буфер для считывания
OVERLAPPED over; //Оверлаппед-структура (т.к. идет перекрытие )
COMSTAT curstat;
DWORD temp,n;

SetCommMask(Port2,EV_RXCHAR); //Определим маску событий (поступление данных в порт (байта))	
FlushFileBuffers(Port2); // Почистим Порт


over.hEvent = CreateEvent(NULL, true, true, NULL); //Создадим событие, т.к. используем ассинхронное считывание (перекрываемые функции)
char b[96];
//memset(b,0,96);
int cnt = 0;
while(1) //Бесконечный цикл
{

memset(buffer,0,100); //Обнулим буфферную переменную
WaitCommEvent(Port2,&lpEvtMask,&over); //Ожидаем поступление того что можно считать

signal = WaitForSingleObject(over.hEvent,-1); //усыпить поток до прихода байта (что бы время проца не занимал)
if(signal == WAIT_OBJECT_0) //если событие прихода байта произошло
{
if(GetOverlappedResult(Port2, &over, &temp, true)) //проверяем, успешно ли завершилась перекрываемая операция WaitCommEvent
if((lpEvtMask&EV_RXCHAR)!=0) //если произошло именно событие прихода байта
{
temp=-1; 
ClearCommError(Port2, &temp, &curstat); //Опросим порт и заполним структуру COMSTAT
if (temp == 0) //Если не было ошибок
{
int btr = curstat.cbInQue; //получить количество принятых байтов 
if(btr == 96) //если в буфере порта есть непрочитанные байты /*А именно 96 - наша структура
{
ReadFile(Port2, buffer, btr, &n, &overlapped); //прочитать байты из порта в буфер

MyStruct Sl;
memcpy(&Sl,buffer,96);
cout<<endl<<"НАША СТРУКТУРА :"<<endl<<"A: = "
<<Sl.a
<<endl<<"C: = "
<<Sl.c<<endl
<<"NAME: = "
<<Sl.name<<endl;

std::cout<<endl<<"Tread <считанно "<<btr<<"байт> :"<<buffer<<endl; //Вывод статистики
}
}
}
}
Sleep(500);
}
}

int Title(void)
{
int Result;
cout<<"\t\tМЕНЮ УПРАВЛЕНИЕ COM"<<endl;
cout<<"1. Записать строку в COM-порт"<<endl;
cout<<"4. Записать структуру в COM-порт (MyStruct - 96 байт)"<<endl;
cout<<"2.Выход из системы"<<endl;
cout<<endl<<"Сделайте выбор [] : ";
cin>>Result;
return Result;
}
void main (void)
{
setlocale(LC_ALL,"Russian");
char S[128]; //Буферная переменная для отправки
DWORD n; //Заглушка
DWORD signal=0; //Сигнальный флаг

//Открытие портов для ассинхронного режима
Port = CreateFile("COM1",GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL); 
Port2 = CreateFile("COM2",GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,FILE_FLAG_OVERLAPPED, NULL);
GetLastError(); //Этим можем получить ошибку, если что то пошло не так
//Настройки портов:
SetSettingCom("COM1",Port);
SetSettingCom("COM2",Port2);

//Запуск потока считывание
CreateThread(NULL,0,&ThreadProcPortRead,NULL,0,NULL); //Запустим процесс на считывание
overlapped.hEvent = CreateEvent(NULL, true, true, NULL); //Создадим событие, т.к. ввод у нас асинхронный, понадобится для перекрываемых функций

int isMenu = -1;
while (isMenu != 2)
{
isMenu = Title();

switch (isMenu)
{
case 1: 
cout<<endl;
cout<<"Введите строку для отправки :"<<endl<<">";
getchar(); //Уберем последний символ возврата корретки
gets(S); //Прочитаем строку
WriteFile(Port,S,strlen(S),&n,&overlapped); //Запишим в порт
signal = WaitForSingleObject(overlapped.hEvent, -1); //приостановим поток пока не заваршится перекрываемая операция
if (GetOverlappedResult(Port, &overlapped, &n, true)) //Если все прошло на зачет
{
std::cout<<">>В Com-порт записано " <<(int)n<<" байт"<<endl;
}
break;
case 4:
MyStruct St;
cout<<endl;
cout<<"Введите атрибуты структуры :"<<endl<<">";
cout<<"Введите A (int) : ";
getchar(); //Уберем последний символ возврата корретки
cin>>St.a;
cout<<"Введите C (int) : ";
getchar(); //Уберем последний символ возврата корретки
memset(St.name,0,88);
cin>>St.c;
cout<<"Введите Name (char[64]) : ";
getchar(); //Уберем последний символ возврата корретки
gets(St.name); //Прочитаем строку
cout<<sizeof(St);
WriteFile(Port,&St,sizeof(St),&n,&overlapped); //Запишим в порт
signal = WaitForSingleObject(overlapped.hEvent, -1); //приостановим поток пока не заваршится перекрываемая операция
if (GetOverlappedResult(Port, &overlapped, &n, true)) //Если все прошло на зачет
{
std::cout<<">>В Com-порт записано " <<(int)n<<" байт"<<endl;
}
break;
default: break;
}
}

CloseHandle(Port); //Закроем порт
CloseHandle(Port2); //Закроем порт
}

Добавлено: Ну а вот таким образом будет совмещатся и строковая и структурная передача
C++:
	int cnt = 0;
[b]	MyStruct Sl;
char *ptr = (char*)&Sl;[/b]
while(1) //Бесконечный цикл
{

memset(buffer,0,100); //Обнулим буфферную переменную
WaitCommEvent(Port2,&lpEvtMask,&over); //Ожидаем поступление того что можно считать

signal = WaitForSingleObject(over.hEvent,-1); //усыпить поток до прихода байта (что бы время проца не занимал)
if(signal == WAIT_OBJECT_0) //если событие прихода байта произошло
{
if(GetOverlappedResult(Port2, &over, &temp, true)) //проверяем, успешно ли завершилась перекрываемая операция WaitCommEvent
if((lpEvtMask&EV_RXCHAR)!=0) //если произошло именно событие прихода байта
{
temp=-1; 
ClearCommError(Port2, &temp, &curstat); //Опросим порт и заполним структуру COMSTAT
if (temp == 0) //Если не было ошибок
{
int btr = curstat.cbInQue; //получить количество принятых байтов 
if(btr) //если в буфере порта есть непрочитанные байты /*А именно 96 - наша структура
{
ReadFile(Port2, buffer, btr, &n, &overlapped); //прочитать байты из порта в буфер
[b]							if (cnt < 96)
{
memcpy(ptr,buffer,(int)btr);
ptr+=(int)btr;
cnt+=(int)btr;
}
if (cnt == 96)
{
cout<<endl<<"НАША СТРУКТУРА :"<<endl<<"A: = "
<<Sl.a
<<endl<<"C: = "
<<Sl.c<<endl
<<"NAME: = "
<<Sl.name<<endl;
cnt = 0;
ptr = (char*)&Sl;
}[/b]

std::cout<<endl<<"Tread <считанно "<<btr<<"байт> :"<<buffer<<endl; //Вывод статистики
}
}
}
}
 
D

DarkKnight

И кстати спасибо за вопрос, правда очень понравился.... Редко на форуме код приходится такой большой писать :)
 
I

igorpromen2009

Здравствуйте. Набрал прогу приблизительно как Вы описали, проверял на компе с Win98, порт открывает, настраивает, записывает... Но потом по непонятным причинам настройки порта перестали устанавливатся(( но запись в порт проходила... Попробовал написанную прогу на других компах с ХР, и так получилось, что в них прога не открывает порт, не настривает его и не записывает, а операция чтения просто подвешивает систему?!?!?! не знаю что и делать.... Может есть специфика работы с ХР или в портах нюанс?
 
D

DarkKnight

Здравствуйте. Набрал прогу приблизительно как Вы описали, проверял на компе с Win98, порт открывает, настраивает, записывает... Но потом по непонятным причинам настройки порта перестали устанавливатся(( но запись в порт проходила... Попробовал написанную прогу на других компах с ХР, и так получилось, что в них прога не открывает порт, не настривает его и не записывает, а операция чтения просто подвешивает систему?!?!?! не знаю что и делать.... Может есть специфика работы с ХР или в портах нюанс?
Нет, причина явно не в этом, я пишу и тестирую любую программу по Win 7 + Win Vista, я обязательно гляну код после праздников, но пока что думаю, что какая то либо другая программа задействует ком-порт, в этом и идет ущербность ПО... Но это я на первый взгяд вам сказал, еще раз повторюсь, что после празников обязательно гляну код.....
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!