summaryrefslogtreecommitdiffstats
path: root/lib/jcode.rb
blob: 78422f296f99e3e51fed669214201b3f1dab3732 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# jcode.rb - ruby code to handle japanese (EUC/SJIS) string

if $VERBOSE && $KCODE == "NONE"
  warn "Warning: $KCODE is NONE."
end

$vsave, $VERBOSE = $VERBOSE, false
class String
  warn "feel free for some warnings:\n" if $VERBOSE

  def _regex_quote(str)
    str.gsub(/(\\[\[\]\-\\])|\\(.)|([\[\]\\])/) do
      $1 || $2 || '\\' + $3
    end
  end
  private :_regex_quote

  PATTERN_SJIS = '[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc]'
  PATTERN_EUC = '[\xa1-\xfe][\xa1-\xfe]'
  PATTERN_UTF8 = '[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf]'

  RE_SJIS = Regexp.new(PATTERN_SJIS, 0, 'n')
  RE_EUC = Regexp.new(PATTERN_EUC, 0, 'n')
  RE_UTF8 = Regexp.new(PATTERN_UTF8, 0, 'n')

  SUCC = {}
  SUCC['s'] = Hash.new(1)
  for i in 0 .. 0x3f
    SUCC['s'][i.chr] = 0x40 - i
  end
  SUCC['s']["\x7e"] = 0x80 - 0x7e
  SUCC['s']["\xfd"] = 0x100 - 0xfd
  SUCC['s']["\xfe"] = 0x100 - 0xfe
  SUCC['s']["\xff"] = 0x100 - 0xff
  SUCC['e'] = Hash.new(1)
  for i in 0 .. 0xa0
    SUCC['e'][i.chr] = 0xa1 - i
  end
  SUCC['e']["\xfe"] = 2
  SUCC['u'] = Hash.new(1)
  for i in 0 .. 0x7f
    SUCC['u'][i.chr] = 0x80 - i
  end
  SUCC['u']["\xbf"] = 0x100 - 0xbf

  def mbchar?
    case $KCODE[0]
    when ?s, ?S
      self =~ RE_SJIS
    when ?e, ?E
      self =~ RE_EUC
    when ?u, ?U
      self =~ RE_UTF8
    else
      nil
    end
  end

  def end_regexp
    case $KCODE[0]
    when ?s, ?S
      /#{PATTERN_SJIS}$/on
    when ?e, ?E
      /#{PATTERN_EUC}$/on
    when ?u, ?U
      /#{PATTERN_UTF8}$/on
    else
      /.$/on
    end
  end

  alias original_succ! succ!
  private :original_succ!

  alias original_succ succ
  private :original_succ

  def succ!
    reg = end_regexp
    if  $KCODE != 'NONE' && self =~ reg
      succ_table = SUCC[$KCODE[0,1].downcase]
      begin
	self[-1] += succ_table[self[-1]]
	self[-2] += 1 if self[-1] == 0
      end while self !~ reg
      self
    else
      original_succ!
    end
  end

  def succ
    str = self.dup
    str.succ! or str
  end

  private

  def _expand_ch str
    a = []
    str.scan(/(?:\\(.)|([^\\]))-(?:\\(.)|([^\\]))|(?:\\(.)|(.))/m) do
      from = $1 || $2
      to = $3 || $4
      one = $5 || $6
      if one
	a.push one
      elsif from.length != to.length
	next
      elsif from.length == 1
	from[0].upto(to[0]) { |c| a.push c.chr }
      else
	from.upto(to) { |c| a.push c }
      end
    end
    a
  end

  def expand_ch_hash from, to
    h = {}
    afrom = _expand_ch(from)
    ato = _expand_ch(to)
    afrom.each_with_index do |x,i| h[x] = ato[i] || ato[-1] end
    h
  end

  HashCache = {}
  TrPatternCache = {}
  DeletePatternCache = {}
  SqueezePatternCache = {}

  public

  def tr!(from, to)
    return nil if from == ""
    return self.delete!(from) if to == ""

    pattern = TrPatternCache[from] ||= /[#{_regex_quote(from)}]/
    if from[0] == ?^
      last = /.$/.match(to)[0]
      self.gsub!(pattern, last)
    else
      h = HashCache[from + "1-0" + to] ||= expand_ch_hash(from, to)
      self.gsub!(pattern) do |c| h[c] end
    end
  end

  def tr(from, to)
    (str = self.dup).tr!(from, to) or str
  end

  def delete!(del)
    return nil if del == ""
    self.gsub!(DeletePatternCache[del] ||= /[#{_regex_quote(del)}]+/, '')
  end

  def delete(del)
    (str = self.dup).delete!(del) or str
  end

  def squeeze!(del=nil)
    return nil if del == ""
    pattern =
      if del
	SqueezePatternCache[del] ||= /([#{_regex_quote(del)}])\1+/
      else
	/(.|\n)\1+/
      end
    self.gsub!(pattern, '\1')
  end

  def squeeze(del=nil)
    (str = self.dup).squeeze!(del) or str
  end

  def tr_s!(from, to)
    return self.delete!(from) if to.length == 0

    pattern = SqueezePatternCache[from] ||= /([#{_regex_quote(from)}])\1*/
    if from[0] == ?^
      last = /.$/.match(to)[0]
      self.gsub!(pattern, last)
    else
      h = HashCache[from + "1-0" + to] ||= expand_ch_hash(from, to)
      self.gsub!(pattern) do h[$1] end
    end
  end

  def tr_s(from, to)
    (str = self.dup).tr_s!(from,to) or str
  end

  def chop!
    self.gsub!(/(?:.|\r?\n)\z/, '')
  end

  def chop
    (str = self.dup).chop! or str
  end

  def jlength
    self.gsub(/[^\Wa-zA-Z_\d]/, ' ').length
  end
  alias jsize jlength

  def jcount(str)
    self.delete("^#{str}").jlength
  end

  def each_char
    if block_given?
      scan(/./m) do |x|
        yield x
      end
    else
      scan(/./m)
    end
  end

end
$VERBOSE = $vsave
span> #include "tcpsrv.h" #include "obj.h" #include "glbl.h" #include "netstrms.h" #include "netstrm.h" #include "nssel.h" #include "errmsg.h" MODULE_TYPE_LIB /* defines */ #define TCPSESS_MAX_DEFAULT 200 /* default for nbr of tcp sessions if no number is given */ #define TCPLSTN_MAX_DEFAULT 20 /* default for nbr of listeners */ /* static data */ DEFobjStaticHelpers DEFobjCurrIf(conf) DEFobjCurrIf(glbl) DEFobjCurrIf(tcps_sess) DEFobjCurrIf(errmsg) DEFobjCurrIf(net) DEFobjCurrIf(netstrms) DEFobjCurrIf(netstrm) DEFobjCurrIf(nssel) /* configure TCP listener settings. This is called during command * line parsing. The argument following -t is supplied as an argument. * The format of this argument is * "<port-to-use>, <nbr-of-sessions>" * Typically, there is no whitespace between port and session number. * (but it may be...). * NOTE: you can not use dbgprintf() in here - the dbgprintf() system is * not yet initilized when this function is called. * rgerhards, 2007-06-21 * The port in cOptarg is handed over to us - the caller MUST NOT free it! * rgerhards, 2008-03-20 */ static void configureTCPListen(tcpsrv_t *pThis, char *cOptarg) { register int i; register char *pArg = cOptarg; assert(cOptarg != NULL); ISOBJ_TYPE_assert(pThis, tcpsrv); /* extract port */ i = 0; while(isdigit((int) *pArg)) { i = i * 10 + *pArg++ - '0'; } if(pThis->TCPLstnPort != NULL) { free(pThis->TCPLstnPort); pThis->TCPLstnPort = NULL; } if( i >= 0 && i <= 65535) { pThis->TCPLstnPort = cOptarg; } else { errmsg.LogError(0, NO_ERRCODE, "Invalid TCP listen port %s - changed to 514.\n", cOptarg); } } /* Initialize the session table * returns 0 if OK, somewhat else otherwise */ static rsRetVal TCPSessTblInit(tcpsrv_t *pThis) { DEFiRet; ISOBJ_TYPE_assert(pThis, tcpsrv); assert(pThis->pSessions == NULL); dbgprintf("Allocating buffer for %d TCP sessions.\n", pThis->iSessMax); if((pThis->pSessions = (tcps_sess_t **) calloc(pThis->iSessMax, sizeof(tcps_sess_t *))) == NULL) { dbgprintf("Error: TCPSessInit() could not alloc memory for TCP session table.\n"); ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } finalize_it: RETiRet; } /* find a free spot in the session table. If the table * is full, -1 is returned, else the index of the free * entry (0 or higher). */ static int TCPSessTblFindFreeSpot(tcpsrv_t *pThis) { register int i; ISOBJ_TYPE_assert(pThis, tcpsrv); for(i = 0 ; i < pThis->iSessMax ; ++i) { if(pThis->pSessions[i] == NULL) break; } return((i < pThis->iSessMax) ? i : -1); } /* Get the next session index. Free session tables entries are * skipped. This function is provided the index of the last * session entry, or -1 if no previous entry was obtained. It * returns the index of the next session or -1, if there is no * further entry in the table. Please note that the initial call * might as well return -1, if there is no session at all in the * session table. */ static int TCPSessGetNxtSess(tcpsrv_t *pThis, int iCurr) { register int i; BEGINfunc ISOBJ_TYPE_assert(pThis, tcpsrv); assert(pThis->pSessions != NULL); for(i = iCurr + 1 ; i < pThis->iSessMax ; ++i) { if(pThis->pSessions[i] != NULL) break; } ENDfunc return((i < pThis->iSessMax) ? i : -1); } /* De-Initialize TCP listner sockets. * This function deinitializes everything, including freeing the * session table. No TCP listen receive operations are permitted * unless the subsystem is reinitialized. * rgerhards, 2007-06-21 */ static void deinit_tcp_listener(tcpsrv_t *pThis) { int i; ISOBJ_TYPE_assert(pThis, tcpsrv); if(pThis->pSessions != NULL) { /* close all TCP connections! */ i = TCPSessGetNxtSess(pThis, -1); while(i != -1) { tcps_sess.Destruct(&pThis->pSessions[i]); /* now get next... */ i = TCPSessGetNxtSess(pThis, i); } /* we are done with the session table - so get rid of it... */ free(pThis->pSessions); pThis->pSessions = NULL; /* just to make sure... */ } if(pThis->TCPLstnPort != NULL) free(pThis->TCPLstnPort); /* finally close our listen streams */ for(i = 0 ; i < pThis->iLstnMax ; ++i) { netstrm.Destruct(pThis->ppLstn + i); } } /* add a listen socket to our listen socket array. This is a callback * invoked from the netstrm class. -- rgerhards, 2008-04-23 */ static rsRetVal addTcpLstn(void *pUsr, netstrm_t *pLstn) { tcpsrv_t *pThis = (tcpsrv_t*) pUsr; DEFiRet; ISOBJ_TYPE_assert(pThis, tcpsrv); ISOBJ_TYPE_assert(pLstn, netstrm); if(pThis->iLstnMax >= TCPLSTN_MAX_DEFAULT) ABORT_FINALIZE(RS_RET_MAX_LSTN_REACHED); pThis->ppLstn[pThis->iLstnMax] = pLstn; ++pThis->iLstnMax; finalize_it: RETiRet; } /* Initialize TCP sockets (for listener) and listens on them */ static rsRetVal create_tcp_socket(tcpsrv_t *pThis) { DEFiRet; uchar *TCPLstnPort; ISOBJ_TYPE_assert(pThis, tcpsrv); if(!strcmp((char*)pThis->TCPLstnPort, "0")) TCPLstnPort = (uchar*)"514"; // TODO: we need to enable the caller to set a port (based on who is // using this, 514 may be totally unsuitable... --- rgerhards, 2008-04-22 /* use default - we can not do service db update, because there is * no IANA-assignment for syslog/tcp. In the long term, we might * re-use RFC 3195 port of 601, but that would probably break to * many existing configurations. * rgerhards, 2007-06-28 */ else TCPLstnPort = (uchar*)pThis->TCPLstnPort; /* TODO: add capability to specify local listen address! */ CHKiRet(netstrm.LstnInit(pThis->pNS, (void*)pThis, addTcpLstn, TCPLstnPort, NULL, pThis->iSessMax)); /* OK, we had success. Now it is also time to * initialize our connections */ if(TCPSessTblInit(pThis) != 0) { /* OK, we are in some trouble - we could not initialize the * session table, so we can not continue. We need to free all * we have assigned so far, because we can not really use it... */ errmsg.LogError(0, RS_RET_ERR, "Could not initialize TCP session table, suspending TCP message reception."); ABORT_FINALIZE(RS_RET_ERR); } finalize_it: RETiRet; } /* Accept new TCP connection; make entry in session table. If there * is no more space left in the connection table, the new TCP * connection is immediately dropped. * ppSess has a pointer to the newly created session, if it succeds. * If it does not succeed, no session is created and ppSess is * undefined. If the user has provided an OnSessAccept Callback, * this one is executed immediately after creation of the * session object, so that it can do its own initialization. * rgerhards, 2008-03-02 */ static rsRetVal SessAccept(tcpsrv_t *pThis, tcps_sess_t **ppSess, netstrm_t *pStrm) { DEFiRet; tcps_sess_t *pSess = NULL; netstrm_t *pNewStrm = NULL; int iSess = -1; struct sockaddr_storage *addr; uchar *fromHostFQDN = NULL; uchar *fromHostIP = NULL; ISOBJ_TYPE_assert(pThis, tcpsrv); CHKiRet(netstrm.AcceptConnReq(pStrm, &pNewStrm)); /* Add to session list */ iSess = TCPSessTblFindFreeSpot(pThis); if(iSess == -1) { errno = 0; errmsg.LogError(0, RS_RET_MAX_SESS_REACHED, "too many tcp sessions - dropping incoming request"); ABORT_FINALIZE(RS_RET_MAX_SESS_REACHED); } else { /* we found a free spot and can construct our session object */ CHKiRet(tcps_sess.Construct(&pSess)); CHKiRet(tcps_sess.SetTcpsrv(pSess, pThis)); } /* OK, we have a "good" index... */ /* get the host name */ CHKiRet(netstrm.GetRemoteHName(pNewStrm, &fromHostFQDN)); CHKiRet(netstrm.GetRemoteIP(pNewStrm, &fromHostIP)); CHKiRet(netstrm.GetRemAddr(pNewStrm, &addr)); /* TODO: check if we need to strip the domain name here -- rgerhards, 2008-04-24 */ /* Here we check if a host is permitted to send us messages. If it isn't, we do not further * process the message but log a warning (if we are configured to do this). * rgerhards, 2005-09-26 */ if(!pThis->pIsPermittedHost((struct sockaddr*) addr, (char*) fromHostFQDN, pThis->pUsr, pSess->pUsr)) { dbgprintf("%s is not an allowed sender\n", fromHostFQDN); if(glbl.GetOption_DisallowWarning()) { errno = 0; errmsg.LogError(0, RS_RET_HOST_NOT_PERMITTED, "TCP message from disallowed sender %s discarded", fromHostFQDN); } ABORT_FINALIZE(RS_RET_HOST_NOT_PERMITTED); } /* OK, we have an allowed sender, so let's continue, what * means we can finally fill in the session object. */ CHKiRet(tcps_sess.SetHost(pSess, fromHostFQDN)); fromHostFQDN = NULL; /* we handed this string over */ CHKiRet(tcps_sess.SetHostIP(pSess, fromHostIP)); fromHostIP = NULL; /* we handed this string over */ CHKiRet(tcps_sess.SetStrm(pSess, pNewStrm)); pNewStrm = NULL; /* prevent it from being freed in error handler, now done in tcps_sess! */ CHKiRet(tcps_sess.SetMsgIdx(pSess, 0)); CHKiRet(tcps_sess.ConstructFinalize(pSess)); /* check if we need to call our callback */ if(pThis->pOnSessAccept != NULL) { CHKiRet(pThis->pOnSessAccept(pThis, pSess)); } *ppSess = pSess; pThis->pSessions[iSess] = pSess; pSess = NULL; /* this is now also handed over */ finalize_it: if(iRet != RS_RET_OK) { if(pSess != NULL) tcps_sess.Destruct(&pSess); if(fromHostFQDN != NULL) free(fromHostFQDN); if(fromHostIP != NULL) free(fromHostIP); if(pNewStrm != NULL) netstrm.Destruct(&pNewStrm); } RETiRet; } static void RunCancelCleanup(void *arg) { nssel_t **ppSel = (nssel_t**) arg; if(*ppSel != NULL) nssel.Destruct(ppSel); } /* This function is called to gather input. */ #pragma GCC diagnostic ignored "-Wempty-body" static rsRetVal Run(tcpsrv_t *pThis) { DEFiRet; int nfds; int i; int iTCPSess; int bIsReady; tcps_sess_t *pNewSess; nssel_t *pSel; ssize_t iRcvd; ISOBJ_TYPE_assert(pThis, tcpsrv); /* this is an endless loop - it is terminated by the framework canelling * this thread. Thus, we also need to instantiate a cancel cleanup handler * to prevent us from leaking anything. -- rgerharsd, 20080-04-24 */ pthread_cleanup_push(RunCancelCleanup, (void*) &pSel); while(1) { CHKiRet(nssel.Construct(&pSel)); // TODO: set driver CHKiRet(nssel.ConstructFinalize(pSel)); /* Add the TCP listen sockets to the list of read descriptors. */ for(i = 0 ; i < pThis->iLstnMax ; ++i) { CHKiRet(nssel.Add(pSel, pThis->ppLstn[i], NSDSEL_RD)); } /* do the sessions */ iTCPSess = TCPSessGetNxtSess(pThis, -1); while(iTCPSess != -1) { /* TODO: access to pNsd is NOT really CLEAN, use method... */ CHKiRet(nssel.Add(pSel, pThis->pSessions[iTCPSess]->pStrm, NSDSEL_RD)); /* now get next... */ iTCPSess = TCPSessGetNxtSess(pThis, iTCPSess); } /* wait for io to become ready */ CHKiRet(nssel.Wait(pSel, &nfds)); for(i = 0 ; i < pThis->iLstnMax ; ++i) { CHKiRet(nssel.IsReady(pSel, pThis->ppLstn[i], NSDSEL_RD, &bIsReady, &nfds)); if(bIsReady) { dbgprintf("New connect on NSD %p.\n", pThis->ppLstn[i]); SessAccept(pThis, &pNewSess, pThis->ppLstn[i]); --nfds; /* indicate we have processed one */ } } /* now check the sessions */ iTCPSess = TCPSessGetNxtSess(pThis, -1); while(nfds && iTCPSess != -1) { CHKiRet(nssel.IsReady(pSel, pThis->pSessions[iTCPSess]->pStrm, NSDSEL_RD, &bIsReady, &nfds)); if(bIsReady) { char buf[8*1024]; /* reception buffer - may hold a partial or multiple messages */ dbgprintf("netstream %p with new data\n", pThis->pSessions[iTCPSess]->pStrm); /* Receive message */ iRet = pThis->pRcvData(pThis->pSessions[iTCPSess], buf, sizeof(buf), &iRcvd); switch(iRet) { case RS_RET_CLOSED: pThis->pOnRegularClose(pThis->pSessions[iTCPSess]); tcps_sess.Destruct(&pThis->pSessions[iTCPSess]); break; case RS_RET_RETRY: /* we simply ignore retry - this is not an error, but we also have not received anything */ break; case RS_RET_OK: /* valid data received, process it! */ if(tcps_sess.DataRcvd(pThis->pSessions[iTCPSess], buf, iRcvd) != RS_RET_OK) { /* in this case, something went awfully wrong. * We are instructed to terminate the session. */ errmsg.LogError(0, NO_ERRCODE, "Tearing down TCP Session %d - see " "previous messages for reason(s)\n", iTCPSess); pThis->pOnErrClose(pThis->pSessions[iTCPSess]); tcps_sess.Destruct(&pThis->pSessions[iTCPSess]); } break; default: errno = 0; errmsg.LogError(0, iRet, "netstream session %p will be closed due to error\n", pThis->pSessions[iTCPSess]->pStrm); pThis->pOnErrClose(pThis->pSessions[iTCPSess]); tcps_sess.Destruct(&pThis->pSessions[iTCPSess]); break; } --nfds; /* indicate we have processed one */ } iTCPSess = TCPSessGetNxtSess(pThis, iTCPSess); } CHKiRet(nssel.Destruct(&pSel)); finalize_it: /* this is a very special case - this time only we do not exit the function, * because that would not help us either. So we simply retry it. Let's see * if that actually is a better idea. Exiting the loop wasn't we always * crashed, which made sense (the rest of the engine was not prepared for * that) -- rgerhards, 2008-05-19 */ /*EMPTY*/; } /* note that this point is usually not reached */ pthread_cleanup_pop(0); /* remove cleanup handler */ RETiRet; } #pragma GCC diagnostic warning "-Wempty-body" /* Standard-Constructor */ BEGINobjConstruct(tcpsrv) /* be sure to specify the object type also in END macro! */ pThis->iSessMax = TCPSESS_MAX_DEFAULT; /* TODO: useful default ;) */ ENDobjConstruct(tcpsrv) /* ConstructionFinalizer */ static rsRetVal tcpsrvConstructFinalize(tcpsrv_t *pThis) { DEFiRet; ISOBJ_TYPE_assert(pThis, tcpsrv); /* prepare network stream subsystem */ CHKiRet(netstrms.Construct(&pThis->pNS)); CHKiRet(netstrms.SetDrvrMode(pThis->pNS, pThis->iDrvrMode)); if(pThis->pszDrvrAuthMode != NULL) CHKiRet(netstrms.SetDrvrAuthMode(pThis->pNS, pThis->pszDrvrAuthMode)); if(pThis->pPermPeers != NULL) CHKiRet(netstrms.SetDrvrPermPeers(pThis->pNS, pThis->pPermPeers)); // TODO: set driver! CHKiRet(netstrms.ConstructFinalize(pThis->pNS)); /* set up listeners */ CHKmalloc(pThis->ppLstn = calloc(TCPLSTN_MAX_DEFAULT, sizeof(netstrm_t*))); iRet = pThis->OpenLstnSocks(pThis); finalize_it: if(iRet != RS_RET_OK) { if(pThis->pNS != NULL) netstrms.Destruct(&pThis->pNS); } RETiRet; } /* destructor for the tcpsrv object */ BEGINobjDestruct(tcpsrv) /* be sure to specify the object type also in END and CODESTART macros! */ CODESTARTobjDestruct(tcpsrv) if(pThis->OnDestruct != NULL) pThis->OnDestruct(pThis->pUsr); deinit_tcp_listener(pThis); if(pThis->pNS != NULL) netstrms.Destruct(&pThis->pNS); if(pThis->pszDrvrAuthMode != NULL) free(pThis->pszDrvrAuthMode); if(pThis->ppLstn != NULL) free(pThis->ppLstn); ENDobjDestruct(tcpsrv) /* debugprint for the tcpsrv object */ BEGINobjDebugPrint(tcpsrv) /* be sure to specify the object type also in END and CODESTART macros! */ CODESTARTobjDebugPrint(tcpsrv) ENDobjDebugPrint(tcpsrv) /* set functions */ static rsRetVal SetCBIsPermittedHost(tcpsrv_t *pThis, int (*pCB)(struct sockaddr *addr, char *fromHostFQDN, void*, void*)) { DEFiRet; pThis->pIsPermittedHost = pCB; RETiRet; } static rsRetVal SetCBRcvData(tcpsrv_t *pThis, rsRetVal (*pRcvData)(tcps_sess_t*, char*, size_t, ssize_t*)) { DEFiRet; pThis->pRcvData = pRcvData; RETiRet; } static rsRetVal SetCBOnListenDeinit(tcpsrv_t *pThis, int (*pCB)(void*)) { DEFiRet; pThis->pOnListenDeinit = pCB; RETiRet; } static rsRetVal SetCBOnSessAccept(tcpsrv_t *pThis, rsRetVal (*pCB)(tcpsrv_t*, tcps_sess_t*)) { DEFiRet;