reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
GStreamerUtils.cxx
Go to the documentation of this file.
1#include "GStreamerUtils.hxx"
2
3#ifdef USE_GSTREAMER
4
5#include <regex>
6
7#include <glib.h>
8#include <gst/gst.h>
9#include <gst/rtp/gstrtcpbuffer.h>
10#include <glibmm.h>
11#include <gstreamermm.h>
12// In earlier versions of gstreamer, we needed this to gain access
13// to certain API types/methods but it is not currently required.
14//#include <gst/webrtc/gstwebrtcbin.h>
15
16#include "rutil/Logger.hxx"
17#include "rutil/Subsystem.hxx"
18
19#include "resip/stack/Tuple.hxx"
20
21using namespace resip;
22using namespace Glib;
23using namespace Gst;
24using namespace std;
25
26#define RESIPROCATE_SUBSYSTEM resip::GstSubsystem::GSTREAMER
27
28extern "C"
29{
30
33{
34 switch (level)
35 {
36 case GST_LEVEL_ERROR: return Log::Err;
37 case GST_LEVEL_WARNING: return Log::Warning;
38 case GST_LEVEL_FIXME: return Log::Info;
39 case GST_LEVEL_INFO: return Log::Info;
40 case GST_LEVEL_DEBUG: return Log::Debug;
41 case GST_LEVEL_LOG: return Log::Stack;
42 case GST_LEVEL_TRACE: return Log::Stack;
43 default: return Log::None;
44 }
45}
46
47void
49 const gchar *file,
50 const gchar *function, gint line, GObject *object,
52{
53 if (level > gst_debug_category_get_threshold (category) )
54 {
55 return;
56 }
57
59
60 if (level_ == Log::None)
61 {
62 return;
63 }
64
66 do
67 {
69 {
71 _resip_log_guard.asStream() << "[" << category->name << "]: ";
72 // FIXME - include the GObject *object with debug_object (object)
73 _resip_log_guard.asStream() << gst_debug_message_get (message);
74 }
75 }
76 while(false);
77}
78
79} // extern "C"
80
82resip::buildTestSource(const Data& streamName)
83{
85 if(streamName == "audio")
86 {
87 testSrc = Parse::create_bin("audiotestsrc is-live=true wave=red-noise ! audioconvert ! audioresample", true);
88 }
89 else if(streamName == "video")
90 {
91 testSrc = Parse::create_bin("videotestsrc is-live=true pattern=ball ! videoconvert", true);
92 }
93 else
94 {
95 ErrLog(<<"unknown stream name: " << streamName);
96 resip_assert(0);
97 }
98 return testSrc;
99}
100
102resip::getMediaTypeName(const RefPtr<Caps>& caps)
103{
104 if(!caps)
105 {
106 ErrLog(<<"caps is empty");
107 return "";
108 }
109
110 DebugLog(<<"getMediaTypeName: " << caps->to_string());
111
112 //return pad->get_stream_id();
113
114 string mediaType = caps->get_structure(0).get_name();
115 StackLog(<<"mediaType: " << mediaType);
116 if(regex_search(mediaType, regex("^video")))
117 {
118 return "video";
119 }
120 else if(regex_search(mediaType, regex("^audio")))
121 {
122 return "audio";
123 }
124 if(caps->get_structure(0).has_field("media"))
125 {
126 string mediaName;
127 caps->get_structure(0).get_field("media", mediaName);
128 StackLog(<<"mediaName: " << mediaName);
129 return mediaName.c_str();
130 }
131 return "";
132}
133
134int
135resip::getStreamIdFromPadName(const Glib::ustring padName)
136{
137 StackLog(<<"trying to extract streamId from pad name: " << padName);
138
139 // rtpbin
140 std::regex r("recv_rtp_src_(\\d+)_(\\d+)_(\\d+)");
141 std::smatch match;
142 const std::string& s = padName.raw();
143 if(std::regex_search(s.begin(), s.end(), match, r))
144 {
145 unsigned int streamId = atoi(match[1].str().c_str());
146 StackLog(<<"found streamId: " << streamId);
147 return streamId;
148 }
149
150 // webrtcbin
151 r = "src_(\\d+)";
152 if(std::regex_search(s.begin(), s.end(), match, r))
153 {
154 unsigned int streamId = atoi(match[1].str().c_str());
155 StackLog(<<"found streamId: " << streamId);
156 return streamId;
157 }
158
159 WarningLog(<<"pad name didn't match regex");
160 resip_assert(0);
161 return -1;
162}
163
164// FIXME - remove this, it is a temporary hack until
165// the code is consolidated in GstRtpManager
166Data
167resip::deduceKeyForPadName(const Glib::ustring padName)
168{
169 StackLog(<<"trying to extract pt from pad name");
170 std::regex r("recv_rtp_src_(\\d+)_(\\d+)_(\\d+)");
171 std::smatch match;
172 const std::string& s = padName.raw();
173 if (std::regex_search(s.begin(), s.end(), match, r))
174 {
175 unsigned int pt = atoi(match[3].str().c_str());
176 StackLog(<<"found payload type: " << pt);
177 switch(pt)
178 {
179 case 0: // FIXME
180 case 8:
181 return "audio";
182 break;
183 case 97:
184 return "video";
185 break;
186 default:
187 resip_assert(0);
188 break;
189 }
190 }
191
192 r = "src_(\\d+)";
193 if (std::regex_search(s.begin(), s.end(), match, r))
194 {
195 unsigned int srcId = atoi(match[1].str().c_str());
196 StackLog(<<"found srcId: " << srcId);
197 switch(srcId)
198 {
199 case 0: return "audio";
200 case 1: return "video";
201 default:
202 ErrLog(<<"unsupported srcId value: " << srcId);
203 resip_assert(0);
204 }
205 }
206
207 WarningLog(<<"pad name didn't match regex");
208 resip_assert(0);
209 return "";
210}
211
212void
213resip::storeGstreamerGraph(RefPtr<Gst::Bin>& bin, const resip::Data& fileNamePrefix)
214{
216}
217
218void
219resip::gstWebRTCSetDescription(Glib::RefPtr<Element> element, const gchar* signalName, GstWebRTCSessionDescription* gstwebrtcdesc)
220{
225}
226
228resip::createSdpContentsFromGstreamer(GstWebRTCSessionDescription *gSdp)
229{
230 gchar *text = gst_sdp_message_as_text (gSdp->sdp);
231 StackLog(<<"text from gst_sdp_message_as_text: " << text);
232 HeaderFieldValue hfv(text, strlen(text));
233 Mime type("application", "sdp");
234 SdpContents *result = dynamic_cast<SdpContents*>((new SdpContents(hfv, type))->clone());
235 g_free(text);
236 return result;
237}
238
239std::shared_ptr<GstWebRTCSessionDescription>
240resip::createGstWebRTCSessionDescriptionFromSdpContents(GstWebRTCSDPType sdpType, const SdpContents& sdp)
241{
242 std::ostringstream sdpBuf;
243 sdpBuf << sdp;
244
245 GstSDPMessage* _gstdesc = nullptr;
247 if(r != GST_SDP_OK || !_gstdesc)
248 {
249 ErrLog(<<"failed to create GstSDPMessage");
250 return nullptr;
251 }
254 std::shared_ptr<GstWebRTCSessionDescription> gstwebrtc(_gstwebrtc, [](GstWebRTCSessionDescription* p){gst_webrtc_session_description_free(p);});
255 return gstwebrtc;
256}
257
258void
259resip::addGstreamerKeyframeProbe(const Glib::RefPtr<Gst::Pad>& pad, std::function<void()> onKeyframeRequired)
260{
262 [onKeyframeRequired](const Glib::RefPtr<Gst::Pad>& pad, const Gst::PadProbeInfo& info)
263 {
264 DebugLog(<<"received event on video stream");
265 RefPtr<Gst::Event> event = info.get_event();
266 if(event &&
267 event->get_event_type() == EVENT_CUSTOM_UPSTREAM &&
268 event->has_name("GstForceKeyUnit"))
269 {
270 DebugLog(<<"gstreamer decoder wants a keyframe");
272 return PAD_PROBE_HANDLED;
273 }
274 return PAD_PROBE_PASS;
275 });
276}
277
278// logic for inserting the Tee elements in the RTCP flow and
279// creating ghost pads to access the RTCP flow from the application
280bool
282{
283 RefPtr<Caps> rtcpCaps = Gst::Caps::create_from_string("application/x-rtcp");
284 RefPtr<Gst::PadTemplate> padTemplate = PadTemplate::create("src_%", PAD_SRC, PAD_REQUEST, rtcpCaps);
285 while(it.next())
286 {
288 RefPtr<Pad> peer = src->get_peer();
289 if((src->get_name().find("rtcp") != ustring::npos) && peer)
290 {
291 RefPtr<Element> tee = ElementFactory::create_element("tee");
292 rtpbin->add(tee);
293 ustring proxyName = prefix + src->get_name();
294 RefPtr<Pad> ghost = GhostPad::create(tee->request_pad(padTemplate), proxyName);
295 rtpbin->add_pad(ghost);
296 src->unlink(peer);
297 RefPtr<Pad> teeSrc1 = tee->request_pad(padTemplate);
298 teeSrc1->link(peer);
299 src->link(tee->get_static_pad("sink"));
300
301 if(outerBin != rtpbin)
302 {
303 // rtpbin is nested inside webrtcbin
304 RefPtr<GhostPad> outerGhost = GhostPad::create(ghost, proxyName);
305 outerBin->add_pad(outerGhost);
306 }
307
308 tee->sync_state_with_parent();
309 }
310 }
311 return true;
312}
313
314// the internal RTPSession class
315// https://gstreamer.freedesktop.org/documentation/rtpmanager/RTPSession.html?gi-language=c#RTPSession
316struct RTPSession;
317
318// stub function to proxy signal from C-style handler to C++
319typedef std::function<void(GstBuffer*)> RxRTCPCallback;
320void
321rxRtcpStub(GstElement* session, GstBuffer* buffer, void *u)
322{
323 RxRTCPCallback &func = *static_cast<RxRTCPCallback*>(u);
324 func(buffer);
325}
326
327// stub function to proxy signal from C-style handler to C++
328typedef std::function<gboolean(GstBuffer*, gboolean)> TxRTCPCallback;
330txRtcpStub(GstElement* session, GstBuffer* buffer, gboolean early, void *u)
331{
332 TxRTCPCallback &func = *static_cast<TxRTCPCallback*>(u);
333 return func(buffer, early);
334}
335
336guint
338{
339 guint count, i;
340
341 stream << "\"report_blocks\":[";
342
344 for (i = 0; i < count; i++)
345 {
346 guint32 ssrc, exthighestseq, jitter, lsr, dlsr;
349
351 &packetslost, &exthighestseq, &jitter, &lsr, &dlsr);
352
353 stream << (i>0 ? ",":"") << "{"
354 << "\"source_ssrc\":" << ssrc << ","
355 << "\"highest_seq_no\":" << exthighestseq << ","
356 << "\"fraction_lost\":" << (uint32_t)fractionlost << ","
357 << "\"ia_jitter\":" << jitter << ","
358 << "\"packets_lost\":" << packetslost << ","
359 << "\"lsr\":" << lsr << ","
360 << "\"dlsr\":" << dlsr
361 << "}";
362 }
363
364 stream << "],\"report_count\":" << count;
365
366 return count;
367}
368
369void
371{
372 guint32 ssrc;
378
379 stream << "\"sender_information\":{"
380 << "\"ntp_timestamp_sec\":" << (ntptime >> 32) << ","
381 << "\"ntp_timestamp_usec\":" << (ntptime & 0xffff) << ","
382 << "\"octets\":" << octet_count << ","
383 << "\"rtp_timestamp\":" << rtptime << ","
384 << "\"packets\":" << packet_count
385 << "}";
386
387 stream << ",\"ssrc\":" << ssrc
388 << ",\"type\":" << 200;
389
390 stream << ",";
392}
393
394void
396{
398
400
401 stream << "\"ssrc\":" << senderssrc
402 << ",\"type\":" << 201;
403
404 stream << ",";
406}
407
408void
409onBuffer(GstBuffer *__buffer, const GenericIPAddress& source, const GenericIPAddress& destination, std::shared_ptr<HepAgent> hepAgent, const Data& correlationId)
410{
411 if(!__buffer)
412 {
413 ErrLog(<<"__buffer is NULL");
414 return;
415 }
416
417 gpointer iteratorState = nullptr;
418 GstMeta* _m;
420 {
421 //RefPtr<Gst::Meta> m = Meta::create(_m);
422 // we receive GstMeta on some packets using regular rtpbin/udpsrc/udpsink
423 // we don't receive any GstMeta from webrtcbin
424 StackLog(<<"found GstMeta on RTCP");
425 }
426
427 // Use reSIProcate's HepAgent code to extract values from RTCP
428
429 // This doesn't work right now, possibly because of issues in struct
430 // alignment or byte order. Therefore, we use the Gstreamer API to
431 // examine the RTCP buffer.
432 /*{
433 RefPtr<Buffer> buffer = Glib::wrap(__buffer, true);
434 gsize packetSize = buffer->get_size();
435 StackLog(<<"got an RTCP packet, packetSize = " << packetSize);
436 char* _buffer = new char[packetSize];
437 gsize extracted = buffer->extract(0, _buffer, packetSize);
438 StackLog(<<"extracted bytes: " << extracted);
439 Data packet(Data::Take, _buffer, packetSize);
440 // send to HEP
441 hepAgent->sendRTCP(TransportType::UDP, source, destination, packet, correlationId);
442 return;
443 }*/
444
445 // use Gstreamer RTCP code to extract values from RTCP
446
448 {
449 ErrLog(<<"_buffer validation failed");
450 return;
451 }
452 GstRTCPBuffer _rtcp = { NULL, };
454 if(!result)
455 {
456 ErrLog(<<"gst_rtcp_buffer_map failed");
457 //return;
458 }
459
462 while(more)
463 {
464 Data json;
465 DataStream stream(json);
466
467 stream << "{";
468
469 GstRTCPType type;
471 StackLog(<<"processing a GstRTCPPacket, type: " << type);
472 switch (type)
473 {
474 case GST_RTCP_TYPE_SR:
475 onSenderReport(stream, &_packet);
476 break;
477 case GST_RTCP_TYPE_RR:
478 onReceiverReport(stream, &_packet);
479 break;
480 default:
481 WarningLog(<<"unhandled RTCP packet type: " << type);
482 }
483
484 stream << "}";
485 stream.flush();
486
487 if(json.size() > 2)
488 {
489 StackLog(<< "created JSON for HOMER: " << json);
490 hepAgent->sendToHOMER<Data>(TransportType::UDP, source, destination, HepAgent::RTCP_JSON, json, correlationId);
491 }
492 else
493 {
494 ErrLog(<< "no JSON created");
495 }
496
498 }
499
501}
502
503bool
504resip::addGstreamerRtcpMonitoringPads(Glib::RefPtr<Gst::Bin> bin, std::shared_ptr<HepAgent> hepAgent, const RtcpPeerSpecVector& peerSpecs, const Data& correlationId)
505{
506 if(!hepAgent)
507 {
508 WarningLog(<<"HepAgent is NULL");
509 return false;
510 }
512 //if(bin->get_type() == gst_webrtc_bin_get_type())
513 if(bin->get_name() == "webrtcbin0")
514 {
515 rtpbin = RefPtr<Bin>::cast_dynamic(bin->get_element("rtpbin"));
516 }
517 else if(bin->get_name() == "rtpbin" || bin->get_name() == "rtpbin0")
518 {
519 rtpbin = bin;
520 }
521 if(!rtpbin)
522 {
523 ErrLog(<<"failed to find rtpbin");
524 return false;
525 }
526
527 // Obtain access to individual elements in the rtpbin and insert
528 // Tee (GstTee) elements to intercept RTCP flows.
529 /* RefPtr<Element> session = rtpbin->get_element("rtpsession0");
530 if(!rtpbin)
531 {
532 ErrLog(<<"failed to find rtpsession0");
533 return false;
534 }
535 RefPtr<Element> demux = rtpbin->get_element("rtpssrcdemux0");
536 if(!rtpbin)
537 {
538 ErrLog(<<"failed to find rtpssrcdemux0");
539 return false;
540 } */
541
542 // incoming RTCP
543 /* if(!addGstreamerRtcpMonitoringPadsInternal(bin, rtpbin, demux->iterate_src_pads(), "inbound_"))
544 {
545 ErrLog(<<"inbound linking failed");
546 return false;
547 }
548
549 // outgoing RTCP
550 if(!addGstreamerRtcpMonitoringPadsInternal(bin, rtpbin, session->iterate_src_pads(), "outbound_"))
551 {
552 ErrLog(<<"outbound linking failed");
553 return false;
554 }*/
555
556 StackLog(<<"peerSpecs.size(): " << peerSpecs.size());
557 resip_assert(peerSpecs.size() > 0);
558 guint sessionId = 0;
559 {
561 GenericIPAddress local = ps.local.toGenericIPAddress();
562 GenericIPAddress remote = ps.remote.toGenericIPAddress();
564 g_signal_emit_by_name (rtpbin->gobj(), "get-internal-session", sessionId, &_session);
565 if(_session)
566 {
567
568 // FIXME shared_ptr
570 {
571 DebugLog(<<"on-receiving-rtcp invoked");
573 });
574
576 {
577 DebugLog(<<"on-sending-rtcp invoked");
579 return TRUE;
580 });
581
582 // FIXME - need to wrap internal RTPSession type
583 /*RefPtr<Glib::Object> __session = Glib::wrap(G_OBJECT(_session), false);
584 auto s1 = Glib::signal_any<void,GstBuffer*>(__session, "on-receiving-rtcp").connect(rxcb);
585 auto s2 = Glib::signal_any<void,GstBuffer*, gboolean>(__session, "on-sending-rtcp").connect(txcb);*/
586 g_signal_connect_after (_session, "on-receiving-rtcp",
588 g_signal_connect_after (_session, "on-sending-rtcp",
590
591 DebugLog(<<"RTCP monitoring setup for session " << sessionId);
592 }
593 else
594 {
595 WarningLog(<<"failed to obtain RTPSession for sessionId " << sessionId);
596 }
597 }
598 return true;
599}
600
601void
602resip::linkGstreamerRtcpHomer(Glib::RefPtr<Gst::Bin> bin, Glib::RefPtr<Gst::Pad> pad, std::shared_ptr<HepAgent> hepAgent, const RtcpPeerSpecVector& peerSpecs, const Data& correlationId)
603{
604 // find index for tuple
605 // std::bind the incoming/outgoing
607 GenericIPAddress destination;
608 StackLog(<<"peerSpecs.size(): " << peerSpecs.size());
609 resip_assert(peerSpecs.size() > 0);
610 const RtcpPeerSpec& ps = peerSpecs.front();
611
612 // examine pad
613 // in or out?
614 Glib::ustring padName = pad->get_name();
615 bool inbound = true;
616 if(padName.substr(0, 9) == "outbound_")
617 {
618 inbound = false;
619 source = ps.local.toGenericIPAddress();
620 destination = ps.remote.toGenericIPAddress();
621 }
622 else if(padName.substr(0, 8) == "inbound_")
623 {
624 source = ps.remote.toGenericIPAddress();
625 destination = ps.local.toGenericIPAddress();
626 }
627 else
628 {
629 resip_assert(0); // FIXME
630 }
631 DebugLog(<<"RTCP adding probe for pad " << padName
632 << " source " << source
633 << " destination " << destination
634 << " direction " << (inbound ? "inbound" : "outbound"));
635
636 RefPtr<AppSink> appSink = AppSink::create();
637 bin->add(appSink);
638 appSink->property_emit_signals() = true;
639 appSink->signal_new_sample().connect([hepAgent, peerSpecs, pad, appSink, source, destination, correlationId]()
640 -> Gst::FlowReturn {
641 RefPtr<Sample> sample = appSink->pull_sample();
642 StackLog(<<"got an RTCP sample, caps = " << sample->get_caps()->to_string());
643 RefPtr<Buffer> buffer = sample->get_buffer();
644 if(!buffer)
645 {
646 ErrLog(<<"sample doesn't contain a buffer");
647 return FLOW_OK;
648 }
649 onBuffer(buffer->gobj(), source, destination, hepAgent, correlationId);
650 return FLOW_OK;
651 });
652 pad->link(appSink->get_static_pad("sink"));
653 appSink->sync_state_with_parent();
654}
655
656Tuple
658{
659 auto& c = m.getConnections().front();
660 TransportType tt = UDP; // FIXME - check for valid m.protocol()
661 return Tuple(c.getAddress(), m.firstRtcpPort(), tt);
662}
663
665resip::createRtcpPeerSpecs(const SdpContents& local, const SdpContents& remote)
666{
668
669 SdpContents::Session::MediumContainer::const_iterator iLocal = local.session().media().cbegin();
670 SdpContents::Session::MediumContainer::const_iterator iRemote = remote.session().media().cbegin();
671
672 while((iLocal != local.session().media().cend()) &&
673 (iRemote != remote.session().media().cend()))
674 {
675 result.push_back({
678 });
679
680 iLocal++;
681 iRemote++;
682 }
683
684 return result;
685}
686
687
688#endif
689
690/* ====================================================================
691 *
692 * Copyright (c) 2022, Software Freedom Institute https://softwarefreedom.institute
693 * Copyright (c) 2021-2022, Daniel Pocock https://danielpocock.com
694 *
695 * Redistribution and use in source and binary forms, with or without
696 * modification, are permitted provided that the following conditions
697 * are met:
698 *
699 * 1. Redistributions of source code must retain the above copyright
700 * notice, this list of conditions and the following disclaimer.
701 *
702 * 2. Redistributions in binary form must reproduce the above copyright
703 * notice, this list of conditions and the following disclaimer in
704 * the documentation and/or other materials provided with the
705 * distribution.
706 *
707 * 3. Neither the name of the author(s) nor the names of any contributors
708 * may be used to endorse or promote products derived from this software
709 * without specific prior written permission.
710 *
711 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTORS "AS IS" AND
712 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
713 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
714 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTORS BE LIABLE
715 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
716 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
717 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
718 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
719 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
720 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
721 * SUCH DAMAGE.
722 *
723 * ====================================================================
724 *
725 *
726 */
#define RESIPROCATE_SUBSYSTEM
static const Data pad("\0\0\0\0\0\0\0", 7)
#define DebugLog(args_)
Definition Logger.hxx:128
#define StackLog(args_)
Definition Logger.hxx:125
static bool genericLogCheckLevel(resip::Log::Level level, const resip::Subsystem &sub)
Definition Logger.hxx:144
#define WarningLog(args_)
Definition Logger.hxx:134
#define ErrLog(args_)
Definition Logger.hxx:137
#define resip_assert(x)
This file needs to work for both C and C++ code.
An iostream that operates on an existing Data.
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
Implementation for logging macros.
Definition Log.hxx:106
Represents the "media-type" element in the RFC 3261 grammar.
Definition Mime.hxx:20
process m= (media announcement) blocks
const std::list< Connection > getConnections() const
get a list of Connection objects, including the Session's c= line.
int firstRtcpPort() const
get the value of the first specified RTCP port or if no rtcp: attribute found, the default port() val...
Provides an interface to process and generate SDP bodies (MIME content-type application/sdp).
Class used to specify what sub-system given sections of code belong to, for use by the logging system...
Definition Subsystem.hxx:34
Represents a network address.
Definition Tuple.hxx:59
dcm! – add UnusedChecking(_enum) below;
TransportType
An enumeration of transport protocols.
unsigned int uint32_t
Definition stdint.h:85
Represents an IP-address and port (V4 or V6).
void list()
Definition userAdmin.cxx:66