1 // Copyright (c) 2015-2018 Josh Blum
2 // SPDX-License-Identifier: BSL-1.0
3 
4 #include "SoapySocketDefs.hpp"
5 #include "SoapyURLUtils.hpp"
6 #include <cstring> //memset
7 #include <string>
8 #include <cassert>
9 
SockAddrData(void)10 SockAddrData::SockAddrData(void)
11 {
12     return;
13 }
14 
SockAddrData(const struct sockaddr * addr,const int addrlen)15 SockAddrData::SockAddrData(const struct sockaddr *addr, const int addrlen)
16 {
17     _storage.resize(addrlen);
18     std::memcpy(_storage.data(), addr, addrlen);
19 }
20 
addr(void) const21 const struct sockaddr *SockAddrData::addr(void) const
22 {
23     return (const struct sockaddr *)_storage.data();
24 }
25 
addrlen(void) const26 size_t SockAddrData::addrlen(void) const
27 {
28     return _storage.size();
29 }
30 
SoapyURL(void)31 SoapyURL::SoapyURL(void)
32 {
33     return;
34 }
35 
SoapyURL(const std::string & scheme,const std::string & node,const std::string & service)36 SoapyURL::SoapyURL(const std::string &scheme, const std::string &node, const std::string &service):
37     _scheme(scheme),
38     _node(node),
39     _service(service)
40 {
41     return;
42 }
43 
SoapyURL(const std::string & url)44 SoapyURL::SoapyURL(const std::string &url)
45 {
46     //extract the scheme
47     std::string urlRest = url;
48     auto schemeEnd = url.find("://");
49     if (schemeEnd != std::string::npos)
50     {
51         _scheme = url.substr(0, schemeEnd);
52         urlRest = url.substr(schemeEnd+3);
53     }
54 
55     //extract node name and service port
56     bool inBracket = false;
57     bool inService = false;
58     for (size_t i = 0; i < urlRest.size(); i++)
59     {
60         const char ch = urlRest[i];
61         if (inBracket and ch == ']')
62         {
63             inBracket = false;
64             continue;
65         }
66         if (not inBracket and ch == '[')
67         {
68             inBracket = true;
69             continue;
70         }
71         if (inBracket)
72         {
73             _node += ch;
74             continue;
75         }
76         if (inService)
77         {
78             _service += ch;
79             continue;
80         }
81         if (not inService and ch == ':')
82         {
83             inService = true;
84             continue;
85         }
86         if (not inService)
87         {
88             _node += ch;
89             continue;
90         }
91     }
92 }
93 
SoapyURL(const struct sockaddr * addr)94 SoapyURL::SoapyURL(const struct sockaddr *addr)
95 {
96     char *s = NULL;
97     switch(addr->sa_family)
98     {
99         case AF_INET: {
100             auto *addr_in = (const struct sockaddr_in *)addr;
101             s = (char *)malloc(INET_ADDRSTRLEN);
102             inet_ntop(AF_INET, (void *)&(addr_in->sin_addr), s, INET_ADDRSTRLEN);
103             _node = s;
104             _service = std::to_string(ntohs(addr_in->sin_port));
105             break;
106         }
107         case AF_INET6: {
108             auto *addr_in6 = (const struct sockaddr_in6 *)addr;
109             s = (char *)malloc(INET6_ADDRSTRLEN);
110             inet_ntop(AF_INET6, (void *)&(addr_in6->sin6_addr), s, INET6_ADDRSTRLEN);
111             _node = s;
112             //support scoped address node
113             if (addr_in6->sin6_scope_id != 0)
114             {
115                 _node += "%" + std::to_string(addr_in6->sin6_scope_id);
116             }
117             _service = std::to_string(ntohs(addr_in6->sin6_port));
118             break;
119         }
120         default:
121             break;
122     }
123     free(s);
124 }
125 
SoapyURL(const SockAddrData & addr)126 SoapyURL::SoapyURL(const SockAddrData &addr)
127 {
128     *this = SoapyURL(addr.addr());
129 }
130 
toSockAddr(SockAddrData & addr) const131 std::string SoapyURL::toSockAddr(SockAddrData &addr) const
132 {
133     SockAddrData result;
134 
135     //unspecified service, cant continue
136     if (_service.empty()) return "service not specified";
137 
138     //configure the hint
139     struct addrinfo hints, *servinfo = NULL;
140     std::memset(&hints, 0, sizeof(hints));
141     hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6
142     hints.ai_socktype = this->getType();
143 
144     //get address info
145     int ret = getaddrinfo(_node.c_str(), _service.c_str(), &hints, &servinfo);
146     if (ret != 0) return gai_strerror(ret);
147 
148     //iterate through possible matches
149     struct addrinfo *p = NULL;
150     for (p = servinfo; p != NULL; p = p->ai_next)
151     {
152         //eliminate unsupported family types
153         if (p->ai_family != AF_INET and p->ai_family != AF_INET6) continue;
154 
155         //found a match
156         assert(p->ai_family == p->ai_addr->sa_family);
157         addr = SockAddrData(p->ai_addr, p->ai_addrlen);
158         break;
159     }
160 
161     //cleanup
162     freeaddrinfo(servinfo);
163 
164     //no results
165     if (p == NULL) return "no lookup results";
166 
167     return ""; //OK
168 }
169 
toString(void) const170 std::string SoapyURL::toString(void) const
171 {
172     std::string url;
173 
174     //add the scheme
175     if (not _scheme.empty()) url += _scheme + "://";
176 
177     //add the node with ipv6 escape brackets
178     if (_node.find(":") != std::string::npos) url += "[" + _node + "]";
179     else url += _node;
180 
181     //and the service
182     if (not _service.empty()) url += ":" + _service;
183 
184     return url;
185 }
186 
getScheme(void) const187 std::string SoapyURL::getScheme(void) const
188 {
189     return _scheme;
190 }
191 
getNode(void) const192 std::string SoapyURL::getNode(void) const
193 {
194     return _node;
195 }
196 
getService(void) const197 std::string SoapyURL::getService(void) const
198 {
199     return _service;
200 }
201 
setScheme(const std::string & scheme)202 void SoapyURL::setScheme(const std::string &scheme)
203 {
204     _scheme = scheme;
205 }
206 
setNode(const std::string & node)207 void SoapyURL::setNode(const std::string &node)
208 {
209     _node = node;
210 }
211 
setService(const std::string & service)212 void SoapyURL::setService(const std::string &service)
213 {
214     _service = service;
215 }
216 
getType(void) const217 int SoapyURL::getType(void) const
218 {
219     if (_scheme == "tcp") return SOCK_STREAM;
220     if (_scheme == "udp") return SOCK_DGRAM;
221     return SOCK_STREAM; //assume
222 }
223