1 /*******************************************************************************
2   Copyright(c) 2017 Jasem Mutlaq. All rights reserved.
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License version 2 as published by the Free Software Foundation.
7 
8  This library is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  Library General Public License for more details.
12 
13  You should have received a copy of the GNU Library General Public License
14  along with this library; see the file COPYING.LIB.  If not, write to
15  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  Boston, MA 02110-1301, USA.
17 *******************************************************************************/
18 
19 #include "connectiontcp.h"
20 
21 #include "indilogger.h"
22 #include "indistandardproperty.h"
23 
24 #include <cerrno>
25 #include <netdb.h>
26 #include <cstring>
27 #include <unistd.h>
28 
29 #ifdef __FreeBSD__
30 #include <arpa/inet.h>
31 #include <netinet/in.h>
32 #include <sys/socket.h>
33 #endif
34 
35 namespace Connection
36 {
37 extern const char *CONNECTION_TAB;
38 
TCP(INDI::DefaultDevice * dev)39 TCP::TCP(INDI::DefaultDevice *dev) : Interface(dev, CONNECTION_TCP)
40 {
41     char defaultHostname[MAXINDINAME] = {0};
42     char defaultPort[MAXINDINAME] = {0};
43 
44     // Try to load the port from the config file. If that fails, use default port.
45     IUGetConfigText(dev->getDeviceName(), INDI::SP::DEVICE_ADDRESS, "ADDRESS", defaultHostname, MAXINDINAME);
46     IUGetConfigText(dev->getDeviceName(), INDI::SP::DEVICE_ADDRESS, "PORT", defaultPort, MAXINDINAME);
47 
48     // Address/Port
49     IUFillText(&AddressT[0], "ADDRESS", "Address", defaultHostname);
50     IUFillText(&AddressT[1], "PORT", "Port", defaultPort);
51     IUFillTextVector(&AddressTP, AddressT, 2, getDeviceName(), "DEVICE_ADDRESS", "Server", CONNECTION_TAB,
52                      IP_RW, 60, IPS_IDLE);
53 
54     IUFillSwitch(&TcpUdpS[0], "TCP", "TCP", ISS_ON);
55     IUFillSwitch(&TcpUdpS[1], "UDP", "UDP", ISS_OFF);
56     IUFillSwitchVector(&TcpUdpSP, TcpUdpS, 2, getDeviceName(), "CONNECTION_TYPE", "Connection Type",
57                        CONNECTION_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
58 
59 }
60 
ISNewText(const char * dev,const char * name,char * texts[],char * names[],int n)61 bool TCP::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
62 {
63     if (!strcmp(dev, m_Device->getDeviceName()))
64     {
65         // TCP Server settings
66         if (!strcmp(name, AddressTP.name))
67         {
68             IUUpdateText(&AddressTP, texts, names, n);
69             AddressTP.s = IPS_OK;
70             IDSetText(&AddressTP, nullptr);
71             return true;
72         }
73     }
74 
75     return false;
76 }
77 
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)78 bool TCP::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
79 {
80     if (!strcmp(dev, m_Device->getDeviceName()))
81     {
82         if (!strcmp(name, TcpUdpSP.name))
83         {
84             IUUpdateSwitch(&TcpUdpSP, states, names, n);
85             TcpUdpSP.s = IPS_OK;
86 
87             IDSetSwitch(&TcpUdpSP, nullptr);
88 
89             return true;
90         }
91     }
92 
93     return false;
94 }
95 
Connect()96 bool TCP::Connect()
97 {
98     if (AddressT[0].text == nullptr || AddressT[0].text[0] == '\0' || AddressT[1].text == nullptr ||
99             AddressT[1].text[0] == '\0')
100     {
101         LOG_ERROR("Error! Server address is missing or invalid.");
102         return false;
103     }
104 
105     const char *hostname = AddressT[0].text;
106     const char *port     = AddressT[1].text;
107 
108     LOGF_INFO("Connecting to %s@%s ...", hostname, port);
109 
110     if (m_Device->isSimulation() == false)
111     {
112         struct sockaddr_in serv_addr;
113         struct hostent *hp = nullptr;
114         int ret            = 0;
115 
116         struct timeval ts;
117         ts.tv_sec  = SOCKET_TIMEOUT;
118         ts.tv_usec = 0;
119 
120         if (sockfd != -1)
121             close(sockfd);
122 
123         // Lookup host name or IPv4 address
124         hp = gethostbyname(hostname);
125         if (!hp)
126         {
127             LOG_ERROR("Failed to lookup IP Address or hostname.");
128             return false;
129         }
130 
131         memset(&serv_addr, 0, sizeof(serv_addr));
132         serv_addr.sin_family      = AF_INET;
133         serv_addr.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr_list[0]))->s_addr;
134         serv_addr.sin_port        = htons(atoi(port));
135 
136         int socketType = 0;
137         if (TcpUdpS[0].s == ISS_ON)
138         {
139             socketType = SOCK_STREAM;
140         }
141         else
142         {
143             socketType = SOCK_DGRAM;
144         }
145 
146         if ((sockfd = socket(AF_INET, socketType, 0)) < 0)
147         {
148             LOG_ERROR("Failed to create socket.");
149             return false;
150         }
151 
152         // Connect to the mount
153         if ((ret = ::connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0)
154         {
155             LOGF_ERROR("Failed to connect to mount %s@%s: %s.", hostname, port, strerror(errno));
156             close(sockfd);
157             sockfd = -1;
158             return false;
159         }
160 
161         // Set the socket receiving and sending timeouts
162         setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&ts, sizeof(struct timeval));
163         setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&ts, sizeof(struct timeval));
164     }
165 
166     PortFD = sockfd;
167 
168     LOG_DEBUG("Connection successful, attempting handshake...");
169     bool rc = Handshake();
170 
171     if (rc)
172     {
173         LOGF_INFO("%s is online.", getDeviceName());
174         m_Device->saveConfig(true, "DEVICE_ADDRESS");
175         m_Device->saveConfig(true, "CONNECTION_TYPE");
176     }
177     else
178         LOG_DEBUG("Handshake failed.");
179 
180     return rc;
181 }
182 
Disconnect()183 bool TCP::Disconnect()
184 {
185     if (sockfd > 0)
186     {
187         close(sockfd);
188         sockfd = PortFD = -1;
189     }
190 
191     return true;
192 }
193 
Activated()194 void TCP::Activated()
195 {
196     m_Device->defineText(&AddressTP);
197     m_Device->defineSwitch(&TcpUdpSP);
198     m_Device->loadConfig(true, "DEVICE_ADDRESS");
199     m_Device->loadConfig(true, "CONNECTION_TYPE");
200 }
201 
Deactivated()202 void TCP::Deactivated()
203 {
204     m_Device->deleteProperty(AddressTP.name);
205     m_Device->deleteProperty(TcpUdpSP.name);
206 }
207 
saveConfigItems(FILE * fp)208 bool TCP::saveConfigItems(FILE *fp)
209 {
210     IUSaveConfigText(fp, &AddressTP);
211     IUSaveConfigSwitch(fp, &TcpUdpSP);
212 
213     return true;
214 }
215 
setDefaultHost(const char * addressHost)216 void TCP::setDefaultHost(const char *addressHost)
217 {
218     IUSaveText(&AddressT[0], addressHost);
219 }
220 
setDefaultPort(uint32_t addressPort)221 void TCP::setDefaultPort(uint32_t addressPort)
222 {
223     char portStr[8];
224     snprintf(portStr, 8, "%d", addressPort);
225     IUSaveText(&AddressT[1], portStr);
226 }
227 
setConnectionType(int type)228 void TCP::setConnectionType(int type)
229 {
230     IUResetSwitch(&TcpUdpSP);
231     TcpUdpS[type].s = ISS_ON;
232     IDSetSwitch(&TcpUdpSP, nullptr);
233 }
234 }
235