MythTV  master
dsmccbiop.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) David C.J. Matthews 2005, 2006
3  * Derived from libdsmcc by Richard Palmer
4  */
5 #include <cstdlib>
6 #include <cstring>
7 
8 #include "dsmccbiop.h"
9 #include "dsmccreceiver.h"
10 #include "dsmcccache.h"
11 #include "dsmccobjcarousel.h"
12 #include "dsmcc.h"
13 
14 #include "mythlogging.h"
15 
17 {
18  if (m_id)
19  free(m_id);
20  if (m_kind)
21  free(m_kind);
22 }
23 
24 int BiopNameComp::Process(const unsigned char *data)
25 {
26  int off = 0;
27 
28  m_id_len = data[off++];
29  m_id = (char*) malloc(m_id_len);
30  memcpy(m_id, data + off, m_id_len);
31 
32  off += m_id_len;
33  m_kind_len = data[off++];
34  m_kind = (char*) malloc(m_kind_len);
35  memcpy(m_kind, data + off, m_kind_len);
36 
37  off += m_kind_len;
38 
39  return off;
40 }
41 
43 {
44  delete[] m_comps;
45 }
46 
47 int BiopName::Process(const unsigned char *data)
48 {
49  int off = 0;
50  m_comp_count = data[0];
51 
52  if (m_comp_count != 1)
53  LOG(VB_DSMCC, LOG_WARNING, "[biop] Expected one name");
54 
55  off++;
57 
58  for (int i = 0; i < m_comp_count; i++)
59  {
60  int ret = m_comps[i].Process(data + off);
61  if (ret <= 0)
62  return ret;
63  off += ret;
64  }
65 
66  return off;
67 }
68 
69 int BiopBinding::Process(const unsigned char *data)
70 {
71  int off = 0, ret;
72  ret = m_name.Process(data);
73 
74  if (ret > 0)
75  off += ret;
76  else
77  return ret; // Error
78 
79  m_binding_type = data[off++];
80  ret = m_ior.Process(data + off);
81 
82  if (ret > 0)
83  off += ret;
84  else
85  return ret; // Error
86 
87  m_objinfo_len = (data[off] << 8) | data[off + 1];
88  off += 2;
89 
90  if (m_objinfo_len > 0)
91  {
92  m_objinfo = (char*) malloc(m_objinfo_len);
93  memcpy(m_objinfo, data + off, m_objinfo_len);
94  }
95  else
96  m_objinfo = nullptr;
97 
98  off += m_objinfo_len;
99 
100  return off;
101 }
102 
104 {
105  free(m_objinfo);
106 }
107 
109  unsigned char *data, unsigned long *curp)
110 {
111  // Parse header
112  if (! ProcessMsgHdr(data, curp))
113  {
114  LOG(VB_DSMCC, LOG_ERR,
115  "[biop] Invalid biop header, dropping rest of module");
116 
117  /* not valid, skip rest of data */
118  return false;
119  }
120 
121  // Handle each message type
122  if (strcmp(m_objkind, "fil") == 0)
123  {
124  LOG(VB_DSMCC, LOG_DEBUG, "[biop] Processing file");
125  return ProcessFile(cachep, filecache, data, curp);
126  }
127  if (strcmp(m_objkind, "dir") == 0)
128  {
129  LOG(VB_DSMCC, LOG_DEBUG, "[biop] Processing directory");
130  return ProcessDir(false, cachep, filecache, data, curp);
131  }
132  if (strcmp(m_objkind, "srg") == 0)
133  {
134  LOG(VB_DSMCC, LOG_DEBUG, "[biop] Processing gateway");
135  return ProcessDir(true, cachep, filecache, data, curp);
136  }
137 
138  /* Error */
139  LOG(VB_DSMCC, LOG_WARNING, QString("Unknown or unsupported format %1%2%3%4")
140  .arg(m_objkind[0]).arg(m_objkind[1])
141  .arg(m_objkind[2]).arg(m_objkind[3]));
142  return false;
143 }
144 
146 {
147  free(m_objinfo);
148  free(m_objkind);
149 }
150 
151 bool BiopMessage::ProcessMsgHdr(const unsigned char *data, unsigned long *curp)
152 {
153  const unsigned char *buf = data + (*curp);
154  int off = 0;
155 
156  if (buf[off] !='B' || buf[off +1] !='I' || buf[off +2] !='O' || buf[off +3] !='P')
157  {
158  LOG(VB_DSMCC, LOG_WARNING, "BiopMessage - invalid header");
159  return false;
160  }
161  off += 4;
162 
163  m_version_major = buf[off++];
164  m_version_minor = buf[off++];
165  if (m_version_major != 1 || m_version_minor != 0)
166  {
167  LOG(VB_DSMCC, LOG_WARNING, "BiopMessage invalid version");
168  return false;
169  }
170 
171  if (buf[off++] != 0)
172  {
173  LOG(VB_DSMCC, LOG_WARNING, "BiopMessage invalid byte order");
174  return false;
175  }
176  if (buf[off++] != 0)
177  {
178  LOG(VB_DSMCC, LOG_WARNING, "BiopMessage invalid message type");
179  return false;
180  }
181 
182  m_message_size = COMBINE32(buf, off);
183  off += 4;
184 
185  uint nObjLen = buf[off++];
186  m_objkey = DSMCCCacheKey((const char*)buf + off, nObjLen);
187  off += nObjLen;
188 
189  m_objkind_len = COMBINE32(buf, off);
190  off += 4;
191  m_objkind = (char*) malloc(m_objkind_len);
192  memcpy(m_objkind, buf + off, m_objkind_len);
193  off += m_objkind_len;
194 
195  m_objinfo_len = buf[off] << 8 | buf[off + 1];
196  off += 2;
197  m_objinfo = (char*) malloc(m_objinfo_len);
198  memcpy(m_objinfo, buf + off, m_objinfo_len);
199  off += m_objinfo_len;
200 
201  (*curp) += off;
202 
203  return true;
204 }
205 
206 
216  bool isSrg, DSMCCCacheModuleData *cachep, DSMCCCache *filecache,
217  const unsigned char *data, unsigned long *curp)
218 {
219  int off = 0;
220  const unsigned char * const buf = data + (*curp);
221 
222  if (m_objinfo_len)
223  LOG(VB_DSMCC, LOG_WARNING, "[biop] ProcessDir non-zero objectInfo_length");
224 
225  const unsigned serviceContextList_count = buf[off++];
226  if (serviceContextList_count)
227  {
228  // TODO Handle serviceContextList for service gateway
229  LOG(VB_DSMCC, LOG_WARNING, QString("[biop] ProcessDir serviceContextList count %1")
230  .arg(serviceContextList_count));
231  return false; // Error
232  }
233 
234  unsigned long msgbody_len = COMBINE32(buf, off);
235  off += 4;
236  int const start = off;
237 
238  unsigned int bindings_count = buf[off] << 8 | buf[off + 1];
239  off += 2;
240 
241  DSMCCCacheReference ref(cachep->CarouselId(), cachep->ModuleId(),
242  cachep->StreamId(), m_objkey);
243  DSMCCCacheDir *pDir = isSrg ? filecache->Srg(ref) : filecache->Directory(ref);
244 
245  for (uint i = 0; i < bindings_count; i++)
246  {
247  BiopBinding binding;
248  int ret = binding.Process(buf + off);
249  if (ret > 0)
250  off += ret;
251  else
252  return false; // Error
253 
254  if (binding.m_name.m_comp_count != 1)
255  LOG(VB_DSMCC, LOG_WARNING, "[biop] ProcessDir nameComponents != 1");
256 
257  if (binding.m_binding_type != 1 && binding.m_binding_type != 2)
258  LOG(VB_DSMCC, LOG_WARNING, "[biop] ProcessDir invalid BindingType");
259 
260  // Process any taps in this binding.
261  binding.m_ior.AddTap(filecache->m_Dsmcc);
262 
263  if (pDir && binding.m_name.m_comp_count >= 1)
264  {
265  if (strcmp("fil", binding.m_name.m_comps[0].m_kind) == 0)
266  DSMCCCache::AddFileInfo(pDir, &binding);
267  else if (strcmp("dir", binding.m_name.m_comps[0].m_kind) == 0)
268  DSMCCCache::AddDirInfo(pDir, &binding);
269  else
270  LOG(VB_DSMCC, LOG_WARNING, QString("[biop] ProcessDir unknown kind %1")
271  .arg(binding.m_name.m_comps[0].m_kind));
272  }
273  }
274 
275  if ((unsigned)(off - start) != msgbody_len)
276  LOG(VB_DSMCC, LOG_WARNING, "[biop] ProcessDir incorrect msgbody_len");
277 
278  (*curp) += off;
279 
280  return true;
281 }
282 
284  unsigned char *data, unsigned long *curp)
285 {
286  int off = 0;
287  const unsigned char *buf = data + (*curp);
288  unsigned long msgbody_len;
289  unsigned long content_len;
290 
291  if (m_objinfo_len != 8)
292  LOG(VB_DSMCC, LOG_WARNING, QString("[biop] ProcessFile objectInfo_length = %1")
293  .arg(m_objinfo_len));
294 
295  const unsigned serviceContextList_count = buf[off++];
296  if (serviceContextList_count)
297  {
298  LOG(VB_DSMCC, LOG_WARNING,
299  QString("[biop] ProcessFile Unexpected serviceContextList_count %1")
300  .arg(serviceContextList_count));
301  return false; // Error
302  }
303 
304  msgbody_len = COMBINE32(buf, off);
305  off += 4;
306  content_len = COMBINE32(buf, off);
307  off += 4;
308  if (content_len + 4 != msgbody_len)
309  LOG(VB_DSMCC, LOG_WARNING, "[biop] ProcessFile incorrect msgbody_len");
310 
311  (*curp) += off;
312 
313  DSMCCCacheReference ref(cachep->CarouselId(), cachep->ModuleId(),
314  cachep->StreamId(), m_objkey);
315 
316  QByteArray filedata = QByteArray((const char *)data+(*curp), content_len);
317  filecache->CacheFileData(ref, filedata);
318 
319  (*curp) += content_len;
320  return true;
321 }
322 
323 void ModuleDescriptorData::Process(const unsigned char *data, int length)
324 {
325  while (length > 0)
326  {
327  unsigned char tag = *data++;
328  unsigned char len = *data++;
329  length -= 2;
330 
331  // Tags:
332  // case 0x01: // Type
333  // case 0x02: // Name
334  // case 0x03: // Info
335  // case 0x04: // Modlink
336  // case 0x05: // CRC
337  // case 0x06: // Location
338  // case 0x07: // DLtime
339  // case 0x08: // Grouplink
340  // case 0x09: // Compressed.
341  if (tag == 0x09)
342  {
343  // Skip the method.
344  m_isCompressed = true;
345  m_originalSize = COMBINE32(data, 1);
346  }
347 
348  length -= len;
349  data += len;
350  }
351 }
352 
353 int BiopModuleInfo::Process(const unsigned char *data)
354 {
355  int off;
356  m_mod_timeout = COMBINE32(data, 0);
357  m_block_timeout = COMBINE32(data, 4);
358  m_min_blocktime = COMBINE32(data, 8);
359 
360  m_taps_count = data[12];
361  off = 13;
362 
363  LOG(VB_DSMCC, LOG_DEBUG, QString("[Biop] "
364  "ModuleTimeout %1 BlockTimeout %2 MinBlockTime %3 Taps %4")
366  .arg(m_taps_count));
367 
368  if (m_taps_count > 0)
369  {
370  /* only 1 allowed TODO - may not be first though ? */
371  int ret = m_tap.Process(data + off);
372  if (ret <= 0)
373  return ret;
374  off += ret;
375  }
376 
377  unsigned userinfo_len = data[off++];
378 
379  if (userinfo_len > 0)
380  {
381  m_descriptorData.Process(data + off, userinfo_len);
382  off += userinfo_len;
383  }
384  return off;
385 
386 }
387 
388 int BiopTap::Process(const unsigned char *data)
389 {
390  int off=0;
391 
392  m_id = (data[off] << 8) | data[off + 1]; // Ignored
393  off += 2;
394  m_use = (data[off] << 8) | data[off + 1];
395  off += 2;
396  m_assoc_tag = (data[off] << 8) | data[off + 1];
397  off += 2;
398  m_selector_len = data[off++];
399  m_selector_data = (char*) malloc(m_selector_len);
400  memcpy(m_selector_data, data + off, m_selector_len);
401  if (m_use == 0x0016) // BIOP_DELIVERY_PARA_USE
402  {
403  unsigned selector_type = (data[off] << 8) | data[off + 1];
404  if (m_selector_len >= 10 && selector_type == 0x0001)
405  {
406  off += 2;
407  unsigned long transactionId = COMBINE32(data, off);
408  off += 4;
409  unsigned long timeout = COMBINE32(data, off);
410  LOG(VB_DSMCC, LOG_DEBUG, QString("[biop] BIOP_DELIVERY_PARA_USE tag %1 id 0x%2 timeout %3uS")
411  .arg(m_assoc_tag).arg(transactionId,0,16).arg(timeout));
412  off += 4;
413  m_selector_len -= 10;
414  }
415  }
416 
417  off += m_selector_len;
418  return off;
419 }
420 
421 int BiopConnbinder::Process(const unsigned char *data)
422 {
423  int off = 0;
424 
425  m_component_tag = COMBINE32(data, 0);
426  if (0x49534F40 != m_component_tag)
427  {
428  LOG(VB_DSMCC, LOG_WARNING, "[biop] Invalid Connbinder tag");
429  return 0;
430  }
431  off += 4;
432  m_component_data_len = data[off++];
433  m_taps_count = data[off++];
434  if (m_taps_count > 0)
435  {
436  /* UKProfile - only first tap read */
437  int ret = m_tap.Process(data + off);
438 #if 0
439  LOG(VB_GENERAL, LOG_DEBUG, QString("Binder - assoc_tag %1")
440  .arg(m_tap.m_assoc_tag));
441 #endif
442  if (ret > 0)
443  off += ret;
444  /* else TODO error */
445  }
446 
447  return off;
448 }
449 
450 int BiopObjLocation::Process(const unsigned char *data)
451 {
452  int off = 0;
453 
454  m_component_tag = COMBINE32(data, 0);
455  if (0x49534F50 != m_component_tag)
456  {
457  LOG(VB_DSMCC, LOG_WARNING, "[biop] Invalid ObjectLocation tag");
458  return 0;
459  }
460  off += 4;
461 
462  m_component_data_len = data[off++];
463  m_Reference.m_nCarouselId = COMBINE32(data, off);
464 
465  off += 4;
466 
467  m_Reference.m_nModuleId = (data[off] << 8) | data[off + 1];
468  off += 2;
469 
470  m_version_major = data[off++];
471  m_version_minor = data[off++];
472  if (1 != m_version_major || 0 != m_version_minor)
473  {
474  LOG(VB_DSMCC, LOG_WARNING, "[biop] Invalid ObjectLocation version");
475  return 0;
476  }
477 
478  uint objKeyLen = data[off++]; /* <= 4 */
479  m_Reference.m_Key = DSMCCCacheKey((char*)data + off, objKeyLen);
480  off += objKeyLen;
481  return off;
482 }
483 
484 // A Lite profile body is used to refer to an object referenced through
485 // a different PMT, We don't support that, at least at the moment.
486 int ProfileBodyLite::Process(const unsigned char * /*data*/)
487 {
488  LOG(VB_DSMCC, LOG_WARNING, "Found LiteProfileBody - Not Implemented Yet");
489  return 0;
490 }
491 
492 int ProfileBodyFull::Process(const unsigned char *data)
493 {
494  int off = 0, ret;
495 
496  m_data_len = COMBINE32(data, off);
497  off += 4;
498 
499  /* bit order */
500  if (data[off++] != 0)
501  {
502  LOG(VB_DSMCC, LOG_WARNING, "[biop] ProfileBody invalid byte order");
503  return 0;
504  }
505 
506  m_lite_components_count = data[off++];
507  if (m_lite_components_count < 2)
508  {
509  LOG(VB_DSMCC, LOG_WARNING, "[biop] ProfileBody invalid components_count");
510  return 0;
511  }
512 
513  ret = m_obj_loc.Process(data + off);
514  if (ret <= 0)
515  return ret;
516  off += ret;
517 
518  ret = m_dsm_conn.Process(data + off);
519  if (ret <= 0)
520  return ret;
521  off += ret;
522 
524 
525  /* UKProfile - ignore anything else */
526 
527  return off;
528 }
529 
530 int BiopIor::Process(const unsigned char *data)
531 {
532  int off = 0, ret;
533  m_type_id_len = COMBINE32(data, 0);
534  m_type_id = (char*) malloc(m_type_id_len);
535  off += 4;
536  memcpy(m_type_id, data + off, m_type_id_len);
537  off += m_type_id_len;
538 
539  m_tagged_profiles_count = COMBINE32(data, off);
540  if (m_tagged_profiles_count < 1)
541  {
542  LOG(VB_DSMCC, LOG_WARNING, "[biop] IOR missing taggedProfile");
543  return 0;
544  }
545  off += 4;
546 
547  m_profile_id_tag = COMBINE32(data, off);
548  off += 4;
549 
550  if (m_profile_id_tag == 0x49534F06) // profile_id_tag == 0x49534F06
551  {
553  ret = m_profile_body->Process(data + off);
554  if (ret <= 0)
555  return ret;
556  off += ret;
557  }
558  else if(m_profile_id_tag == 0x49534F05) // profile_id_tag == 0x49534F05
559  {
561  ret = m_profile_body->Process(data + off);
562  if (ret <= 0)
563  return ret;
564  off += ret;
565  }
566  else
567  {
568  /* UKProfile - receiver may ignore other profiles */
569  LOG(VB_DSMCC, LOG_WARNING, QString("[biop] Unknown Ior profile 0x%1")
570  .arg(m_profile_id_tag, 0, 16));
571  return 0;
572  }
573 
574  return off;
575 }
576 
577 // An IOR may refer to other streams. We may have to add taps for them.
578 void BiopIor::AddTap(Dsmcc *pStatus)
579 {
581  if (ref != nullptr)
582  pStatus->AddTap(ref->m_nStreamTag, ref->m_nCarouselId);
583 }
584 
586 {
587  free(m_selector_data);
588 }
char * m_kind
Definition: dsmccbiop.h:29
BiopObjLocation m_obj_loc
Definition: dsmccbiop.h:108
unsigned short m_id
Definition: dsmccbiop.h:52
int Process(const unsigned char *)
Definition: dsmccbiop.cpp:450
unsigned int m_objinfo_len
Definition: dsmccbiop.h:193
ProfileBody * m_profile_body
Definition: dsmccbiop.h:145
bool ProcessFile(DSMCCCacheModuleData *cachep, DSMCCCache *cache, unsigned char *data, unsigned long *curp)
Definition: dsmccbiop.cpp:283
BiopTap m_tap
Definition: dsmccbiop.h:221
int Process(const unsigned char *Data)
Definition: dsmccbiop.cpp:353
unsigned long m_originalSize
Definition: dsmccbiop.h:209
void AddTap(Dsmcc *pStatus)
Definition: dsmccbiop.cpp:578
Definition: dsmcc.h:75
Dsmcc * m_Dsmcc
Definition: dsmcccache.h:132
unsigned long m_type_id_len
Definition: dsmccbiop.h:141
int Process(const unsigned char *)
Definition: dsmccbiop.cpp:24
char * m_objinfo
Definition: dsmccbiop.h:162
unsigned short m_nModuleId
Definition: dsmcccache.h:60
DSMCCCacheDir * Srg(const DSMCCCacheReference &ref)
Definition: dsmcccache.cpp:133
unsigned long m_block_timeout
Definition: dsmccbiop.h:218
unsigned char m_id_len
Definition: dsmccbiop.h:26
char * m_type_id
Definition: dsmccbiop.h:142
void CacheFileData(const DSMCCCacheReference &ref, const QByteArray &data)
Definition: dsmcccache.cpp:179
char m_binding_type
Definition: dsmccbiop.h:159
unsigned long CarouselId(void) const
char * m_objkind
Definition: dsmccbiop.h:197
unsigned short m_use
Definition: dsmccbiop.h:53
virtual DSMCCCacheReference * GetReference()=0
char * m_objinfo
Definition: dsmccbiop.h:194
int Process(const unsigned char *)
Definition: dsmccbiop.cpp:388
unsigned short m_selector_len
Definition: dsmccbiop.h:56
unsigned long m_min_blocktime
Definition: dsmccbiop.h:219
DSMCCCacheKey m_objkey
Definition: dsmccbiop.h:191
#define COMBINE32(data, idx)
Definition: dsmcc.h:117
static void AddFileInfo(DSMCCCacheDir *dir, const BiopBinding *)
Definition: dsmcccache.cpp:206
unsigned char m_version_major
Definition: dsmccbiop.h:188
int Process(const unsigned char *)
Definition: dsmccbiop.cpp:47
unsigned short m_nStreamTag
Definition: dsmcccache.h:61
unsigned char m_version_minor
Definition: dsmccbiop.h:189
unsigned char m_taps_count
Definition: dsmccbiop.h:220
DSMCCCacheReference m_Reference
Definition: dsmccbiop.h:84
unsigned int m_objinfo_len
Definition: dsmccbiop.h:161
unsigned int uint
Definition: compat.h:140
unsigned short ModuleId(void) const
bool Process(DSMCCCacheModuleData *cachep, DSMCCCache *cache, unsigned char *data, unsigned long *curp)
Definition: dsmccbiop.cpp:108
BiopTap m_tap
Definition: dsmccbiop.h:69
char m_lite_components_count
Definition: dsmccbiop.h:107
static void AddDirInfo(DSMCCCacheDir *dir, const BiopBinding *)
Definition: dsmcccache.cpp:223
ObjCarousel * AddTap(unsigned short componentTag, unsigned carouselId)
Add a tap.
Definition: dsmcc.cpp:60
unsigned char m_comp_count
Definition: dsmccbiop.h:40
ModuleDescriptorData m_descriptorData
Definition: dsmccbiop.h:223
DSMCCCacheModuleData contains information about a module and holds the blocks for a partly completed ...
unsigned char m_component_data_len
Definition: dsmccbiop.h:67
DSMCCCacheDir * Directory(const DSMCCCacheReference &ref)
Definition: dsmcccache.cpp:156
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
unsigned long m_tagged_profiles_count
Definition: dsmccbiop.h:143
unsigned short m_assoc_tag
Definition: dsmccbiop.h:55
BiopIor m_ior
Definition: dsmccbiop.h:160
unsigned char m_taps_count
Definition: dsmccbiop.h:68
The object carousel is transmitted as a directed graph.
Definition: dsmcccache.h:94
DSMCCCacheKey m_Key
Definition: dsmcccache.h:62
unsigned char m_kind_len
Definition: dsmccbiop.h:27
int Process(const unsigned char *data)
Definition: dsmccbiop.cpp:69
unsigned int m_message_size
Definition: dsmccbiop.h:190
unsigned long m_nCarouselId
Definition: dsmcccache.h:59
char m_component_data_len
Definition: dsmccbiop.h:81
char m_version_minor
Definition: dsmccbiop.h:83
unsigned long m_data_len
Definition: dsmccbiop.h:105
unsigned long m_objkind_len
Definition: dsmccbiop.h:192
int Process(const unsigned char *)
Definition: dsmccbiop.cpp:421
virtual int Process(const unsigned char *)=0
int Process(const unsigned char *) override
Definition: dsmccbiop.cpp:486
int Process(const unsigned char *)
Definition: dsmccbiop.cpp:530
void Process(const unsigned char *data, int length)
Definition: dsmccbiop.cpp:323
bool ProcessDir(bool isSrg, DSMCCCacheModuleData *cachep, DSMCCCache *cache, const unsigned char *data, unsigned long *curp)
Process a Directory message.
Definition: dsmccbiop.cpp:215
bool ProcessMsgHdr(const unsigned char *data, unsigned long *curp)
Definition: dsmccbiop.cpp:151
unsigned long m_component_tag
Definition: dsmccbiop.h:66
BiopName m_name
Definition: dsmccbiop.h:158
char * m_id
Definition: dsmccbiop.h:28
unsigned long m_profile_id_tag
Definition: dsmccbiop.h:144
char m_version_major
Definition: dsmccbiop.h:82
BiopNameComp * m_comps
Definition: dsmccbiop.h:41
unsigned long m_mod_timeout
Definition: dsmccbiop.h:217
unsigned short StreamId(void) const
unsigned long m_component_tag
Definition: dsmccbiop.h:80
int Process(const unsigned char *) override
Definition: dsmccbiop.cpp:492
char * m_selector_data
Definition: dsmccbiop.h:57
BiopConnbinder m_dsm_conn
Definition: dsmccbiop.h:112