+static inline unsigned char fromhex(unsigned c)
+{
+ c&=0xDF;
+ return(c<'A'?c-('0'&0xDF):(c-('A'&0xDF))+0xA);
+}
+
+static int teldecode(char *text,unsigned char *bin,size_t digits)
+{
+unsigned char b;
+int r=0,i;
+
+ for (i=0;i<digits;text++,i++) {
+ if (!(i&1)) b=*bin;
+ else b=(*bin++)>>4;
+ b&=0x0F;
+ if (b<=0x09)
+ *text=b+'0';
+ else {
+ *text='?';
+ r++;
+ }
+ }
+ *text='\0';
+ return(r);
+}
+
+static void sctsparse(unsigned char *bin,const char *pduline,int offs)
+{
+#define DIGIT2BIN(v) (((v)&0x0F)*10+(((v)>>4)&0x0F))
+int i;
+
+ receive_time=-1;
+ memset(&tm,0,sizeof(tm)); /* may be redundant */
+ for (i=0;i<NELEM(timeparse);offs++,i++) {
+ if ((*bin&0x0F)>0x09 || (*bin&0xF0)>0x90) {
+ error(_("Invalid value of \"%s\" at offset %d in: %s"),
+ timeparse[i].name,offs,pduline);
+ return;
+ }
+ GETTIME(i)=DIGIT2BIN(*bin);
+ bin++;
+ }
+ maketime(pduline);
+
+#undef DIGIT2BIN
+}
+
+static void receive_pdu(char *pduline)
+{
+unsigned char pdu[140+0x100],*pdup,*pdue,oalen,inreg;
+char text[160+1],*textp,*s,*pdulinescan;
+size_t pdulinel=strlen(pduline),want;
+size_t udl,udlb;
+int inb,outb,xb;
+
+ d2("receive_pdu: %s\n",pduline);
+ if (pdulinel>2*sizeof(pdu))
+ { error(_("PDU too long (%d/2) to be valid: %s"),pdulinel,pduline); return; }
+ if (pdulinel&1)
+ { error(_("PDU length odd (%d): %s"),pdulinel,pduline); return; }
+ if (pdulinel<2*13)
+ { error(_("PDU length %d too small (min. 2*%d): %s"),pdulinel,13,pduline); return; }
+ for (pdup=pdu,pdulinescan=pduline;*pdulinescan;pdulinescan+=2) {
+ if (!isxdigit(pdulinescan[0]) || !(isxdigit(pdulinescan[1])))
+ { error(_("Invalid hex byte: %c%c on byte %d in: %s"),
+ pdulinescan[0],pdulinescan[1],pdup-pdu,pduline); return; }
+ *pdup++=(fromhex(pdulinescan[0])<<4)|fromhex(pdulinescan[1]);
+ }
+ pdue=pdup;
+ free(receive_smsc);
+ if (*pdu<=1) {
+ receive_smsc=NULL;
+ }
+ else {
+ if (*pdu>10)
+ { error(_("SMSC length too large (%d, max. %d): %s"),*pdu,10,pduline); return; }
+ chk(receive_smsc=malloc(1+2*(*pdu)+1));
+ s=receive_smsc;
+ if (pdu[1]==ADDR_INT) *s++='+';
+ else {
+ if (pdu[1]!=ADDR_NAT)
+ error(_("Unknown address type 0x%02X of %s, ignoring in PDU: %s"),pdu[1],_("SMSC"),pduline); return;
+ }
+ if (teldecode(s,pdu+2,2*(*pdu-1)-((pdu[1+(*pdu)]&0xF0)==0xF0)))
+ error(_("Some digits unrecognized in %s \"%s\", ignoring in PDU: %s"),_("SMSC"),receive_smsc,pduline);
+ }
+ pdup=pdu+1+(*pdu);
+ if (*pdup&0x03) /* PDU type */
+ error(_("Unrecognized PDU type 0x%02X at offset %d, dropping: %s"),*pdup,pdup-pdu,pduline);
+ pdup++;
+ free(receive_number);
+ if ((oalen=*pdup++)>2*0x10) /* OA len */
+ { error(_("Originating number too large (0x%X, max. 2*0x%X): %s"),oalen,0x10,pduline); return; }
+ if (pdup+(want=1+(oalen+1)/2+10)>pdue)
+ { error(_("PDU length too short (want %d, is %d): %s"),(pdup-pdu)+want,pdue-pdu,pduline); return; }
+ chk(receive_number=malloc(1+2*(*pdup)+1));
+ s=receive_number;
+ if (*pdup==ADDR_INT) *s++='+';
+ else {
+ if (*pdup!=ADDR_NAT)
+ error(_("Unknown address type 0x%02X of %s, ignoring in PDU: %s"),*pdup,_("originating number"),pduline); return;
+ }
+ pdup++;
+ if (teldecode(s,pdup,oalen))
+ error(_("Some digits unrecognized in %s \"%s\", ignoring in PDU at offset %d: %s"),
+ _("originating number"),receive_number,pdup-pdu,pduline);
+ pdup+=(oalen+1)/2;
+ if (*pdup) /* PID */
+ error(_("PID number %02X unsupported, ignoring: %s"),*pdup,pduline);
+ pdup++;
+ if (*pdup) { /* DCS */
+ if ((*pdup&0xF4)==0xF4)
+ { error(_("DCS 0x%02X indicates 8-bit data, unsupported, dropping: %s"),*pdup,pduline); return; }
+ error(_("DCS 0x%02X unsupported, will attempt decoding: %s"),*pdup,pduline);
+ }
+ pdup++;
+ sctsparse(pdup,pduline,pdup-pdu);
+ pdup+=7;
+ /* UDL */
+ udl=*pdup++;
+ if (pdue-pdup>140) {
+ error(_("PDU data (%d) exceed maximum length of %d bytes, cut: %s"),
+ pdue-pdup,140,pduline);
+ pdue=pdup+140;
+ }
+ udlb=(udl*7+7)/8;
+ if (pdup+udlb>pdue) {
+size_t udl1,udlb1;
+
+ udlb1=pdue-pdup;
+ udl1=(udlb1*8)/7;
+ error(_("PDU data length (%d/7->%d/8) longer than data (%d), cut to %d/7->%d/8: %s"),
+ udl,udlb,pdue-pdup,udl1,udlb1,pduline);
+ udl=udl1; udlb=udlb1;
+ }
+ else
+ assert(pdup+udlb==pdue); /* should be checked by 'PDU length too short' above */
+ textp=text;
+ inb=outb=0;
+ inreg=0; /* GCC happiness */
+ while (udl) {
+ if (!inb) {
+ inreg=*pdup++;
+ inb=8;
+ }
+ if (!outb) {
+ assert(textp<text+160);
+ *textp=0x00;
+ outb=7;
+ }
+ xb=MIN(inb,outb);
+#if 0
+ d4("inb=%d,outb=%d,xb=%d\n",inb,outb,xb);
+#endif
+ *textp|=((inreg>>(unsigned)(8-inb))&((1<<xb)-1))<<(unsigned)(7-outb);
+ inb-=xb; outb-=xb;
+ if (!outb) {
+ *textp=charconv_recv(*textp,textp-text);
+ textp++;
+ udl--;
+ }
+ }
+ *textp=0;
+ receive_text(text);
+}
+