1 //
2 // Copyright 2014 Ettus Research LLC
3 // Copyright 2018 Ettus Research, a National Instruments Company
4 //
5 // SPDX-License-Identifier: GPL-3.0-or-later
6 //
7 
8 #pragma once
9 
10 #include <uhd/exception.hpp>
11 #include <uhd/types/device_addr.hpp>
12 #include <uhd/utils/cast.hpp>
13 #include <unordered_map>
14 #include <boost/algorithm/string.hpp>
15 #include <boost/assign/list_of.hpp>
16 #include <boost/format.hpp>
17 #include <sstream>
18 #include <string>
19 #include <vector>
20 
21 namespace uhd { namespace usrp {
22 
23 /*!
24  * constrained_device_args_t provides a base and utilities to
25  * map key=value pairs passed in through the device creation
26  * args interface (device_addr_t).
27  *
28  * Inherit from this class to create typed device specific
29  * arguments and use the base class methods to handle parsing
30  * the device_addr or any key=value string to populate the args
31  *
32  * This file contains a library of different types of args the
33  * the user can pass in. The library can be extended to support
34  * non-intrinsic types by the client.
35  *
36  */
37 class constrained_device_args_t
38 {
39 public: // Types
40     /*!
41      * Base argument type. All other arguments inherit from this.
42      */
43     class generic_arg
44     {
45     public:
generic_arg(const std::string & key)46         generic_arg(const std::string& key) : _key(key) {}
key() const47         inline const std::string& key() const
48         {
49             return _key;
50         }
51         inline virtual std::string to_string() const = 0;
52 
53     private:
54         std::string _key;
55     };
56 
57     /*!
58      * String argument type. Can be case sensitive or insensitive
59      */
60     template <bool case_sensitive>
61     class str_arg : public generic_arg
62     {
63     public:
str_arg(const std::string & name,const std::string & default_value)64         str_arg(const std::string& name, const std::string& default_value)
65             : generic_arg(name)
66         {
67             set(default_value);
68         }
69 
set(const std::string & value)70         inline void set(const std::string& value)
71         {
72             _value = case_sensitive ? value : boost::algorithm::to_lower_copy(value);
73         }
get() const74         inline const std::string& get() const
75         {
76             return _value;
77         }
parse(const std::string & str_rep)78         inline void parse(const std::string& str_rep)
79         {
80             set(str_rep);
81         }
to_string() const82         inline virtual std::string to_string() const
83         {
84             return key() + "=" + get();
85         }
operator ==(const std::string & rhs) const86         inline bool operator==(const std::string& rhs) const
87         {
88             return get() == boost::algorithm::to_lower_copy(rhs);
89         }
90 
91     private:
92         std::string _value;
93     };
94     typedef str_arg<false> str_ci_arg;
95     typedef str_arg<true> str_cs_arg;
96 
97     /*!
98      * Numeric argument type. The template type data_t allows the
99      * client to constrain the type of the number.
100      */
101     template <typename data_t>
102     class num_arg : public generic_arg
103     {
104     public:
num_arg(const std::string & name,const data_t default_value)105         num_arg(const std::string& name, const data_t default_value) : generic_arg(name)
106         {
107             set(default_value);
108         }
109 
set(const data_t value)110         inline void set(const data_t value)
111         {
112             _value = value;
113         }
get() const114         inline const data_t get() const
115         {
116             return _value;
117         }
parse(const std::string & str_rep)118         inline void parse(const std::string& str_rep)
119         {
120             try {
121                 _value = boost::lexical_cast<data_t>(str_rep);
122             } catch (std::exception& ex) {
123                 throw uhd::value_error(
124                     str(boost::format("Error parsing numeric parameter %s: %s.") % key()
125                         % ex.what()));
126             }
127         }
to_string() const128         inline virtual std::string to_string() const
129         {
130             return key() + "=" + std::to_string(get());
131         }
132 
133     private:
134         data_t _value;
135     };
136 
137     /*!
138      * Enumeration argument type. The template type enum_t allows the
139      * client to use their own enum and specify a string mapping for
140      * the values of the enum
141      */
142     template <typename enum_t>
143     class enum_arg : public generic_arg
144     {
145     public:
enum_arg(const std::string & name,const enum_t default_value,const std::unordered_map<std::string,enum_t> & values)146         enum_arg(const std::string& name,
147             const enum_t default_value,
148             const std::unordered_map<std::string, enum_t>& values)
149             : generic_arg(name), _str_values(_enum_map_to_lowercase<enum_t>(values))
150         {
151             set(default_value);
152         }
set(const enum_t value)153         inline void set(const enum_t value)
154         {
155             _value = value;
156         }
get() const157         inline const enum_t get() const
158         {
159             return _value;
160         }
parse(const std::string & str_rep,const bool assert_invalid=true)161         inline void parse(const std::string& str_rep, const bool assert_invalid = true)
162         {
163             const std::string str_rep_lowercase =
164                 boost::algorithm::to_lower_copy(str_rep);
165             if (_str_values.count(str_rep_lowercase) == 0) {
166                 if (assert_invalid) {
167                     std::string valid_values_str = "";
168                     for (const auto& value : _str_values) {
169                         valid_values_str +=
170                             (valid_values_str.empty() ? "" : ", ") + value.first;
171                     }
172                     throw uhd::value_error(
173                         str(boost::format("Invalid device arg value: %s=%s (Valid: {%s})")
174                             % key() % str_rep % valid_values_str));
175                 } else {
176                     return;
177                 }
178             }
179 
180             set(_str_values.at(str_rep_lowercase));
181         }
to_string() const182         inline virtual std::string to_string() const
183         {
184             std::string repr;
185             for (const auto& value : _str_values) {
186                 if (value.second == _value) {
187                     repr = value.first;
188                     break;
189                 }
190             }
191 
192             UHD_ASSERT_THROW(!repr.empty());
193             return key() + "=" + repr;
194         }
195 
196     private:
197         enum_t _value;
198         const std::unordered_map<std::string, enum_t> _str_values;
199     };
200 
201     /*!
202      * Boolean argument type.
203      */
204     class bool_arg : public generic_arg
205     {
206     public:
bool_arg(const std::string & name,const bool default_value)207         bool_arg(const std::string& name, const bool default_value) : generic_arg(name)
208         {
209             set(default_value);
210         }
211 
set(const bool value)212         inline void set(const bool value)
213         {
214             _value = value;
215         }
get() const216         inline bool get() const
217         {
218             return _value;
219         }
parse(const std::string & str_rep)220         inline void parse(const std::string& str_rep)
221         {
222             try {
223                 if (str_rep.empty()) {
224                     // If str_rep is empty, the flag is interpreted as set
225                     _value = true;
226                 } else {
227                     _value = uhd::cast::from_str<bool>(str_rep);
228                 }
229             } catch (std::exception& ex) {
230                 throw uhd::value_error(
231                     str(boost::format("Error parsing boolean parameter %s: %s.")
232                         % key() % ex.what()));
233             }
234         }
to_string() const235         inline virtual std::string to_string() const
236         {
237             return key() + "=" + (get() ? "true" : "false");
238         }
239 
240     private:
241         bool _value;
242     };
243 
244 public: // Methods
constrained_device_args_t()245     constrained_device_args_t() {}
~constrained_device_args_t()246     virtual ~constrained_device_args_t() {}
247 
parse(const std::string & str_args)248     void parse(const std::string& str_args)
249     {
250         device_addr_t dev_args(str_args);
251         _parse(dev_args);
252     }
253 
parse(const device_addr_t & dev_args)254     void parse(const device_addr_t& dev_args)
255     {
256         _parse(dev_args);
257     }
258 
259     inline virtual std::string to_string() const = 0;
260 
261     template <typename arg_type>
parse_arg_default(const device_addr_t & dev_args,arg_type & constrained_arg)262     void parse_arg_default(const device_addr_t& dev_args, arg_type& constrained_arg)
263     {
264         if (dev_args.has_key(constrained_arg.key())) {
265             constrained_arg.parse(dev_args[constrained_arg.key()]);
266         }
267     }
268 
269 protected: // Methods
270     // Override _parse to provide an implementation to parse all
271     // client specific device args
272     virtual void _parse(const device_addr_t& dev_args) = 0;
273 
274     /*!
275      * Utility: Ensure that the value of the device arg is between min and max
276      */
277     template <typename num_data_t>
_enforce_range(const num_arg<num_data_t> & arg,const num_data_t & min,const num_data_t & max)278     static inline void _enforce_range(
279         const num_arg<num_data_t>& arg, const num_data_t& min, const num_data_t& max)
280     {
281         if (arg.get() > max || arg.get() < min) {
282             throw uhd::value_error(str(
283                 boost::format("Invalid device arg value: %s (Minimum: %s, Maximum: %s)")
284                 % arg.to_string() % std::to_string(min) % std::to_string(max)));
285         }
286     }
287 
288     /*!
289      * Utility: Ensure that the value of the device arg is is contained in valid_values
290      */
291     template <typename arg_t, typename data_t>
_enforce_discrete(const arg_t & arg,const std::vector<data_t> & valid_values)292     static inline void _enforce_discrete(
293         const arg_t& arg, const std::vector<data_t>& valid_values)
294     {
295         bool match = false;
296         for (const data_t& val : valid_values) {
297             if (val == arg.get()) {
298                 match = true;
299                 break;
300             }
301         }
302         if (!match) {
303             std::string valid_values_str;
304             for (size_t i = 0; i < valid_values.size(); i++) {
305                 std::stringstream valid_values_ss;
306                 valid_values_ss << ((i == 0) ? "" : ", ") << valid_values[i];
307                 throw uhd::value_error(
308                     str(boost::format("Invalid device arg value: %s (Valid: {%s})")
309                         % arg.to_string() % valid_values_ss.str()));
310             }
311         }
312     }
313 
314     //! Helper for enum_arg: Create a new map where keys are converted to
315     //  lowercase.
316     template <typename enum_t>
_enum_map_to_lowercase(const std::unordered_map<std::string,enum_t> & in_map)317     static std::unordered_map<std::string, enum_t> _enum_map_to_lowercase(
318         const std::unordered_map<std::string, enum_t>& in_map)
319     {
320         std::unordered_map<std::string, enum_t> new_map;
321         for (const auto& str_to_enum : in_map) {
322             new_map.insert(std::pair<std::string, enum_t>(
323                 boost::algorithm::to_lower_copy(str_to_enum.first), str_to_enum.second));
324         }
325         return new_map;
326     }
327 };
328 }} // namespace uhd::usrp
329