public void imm_handleTimedNotification(YPktStreamHead data)
        {
            uint    pos  = 0;
            YDevice ydev = _yctx._yHash.imm_getDevice(SerialNumber);

            if (ydev == null)
            {
                // device has not been registered;
                return;
            }

            while (pos < data.Len)
            {
                int  funYdx = data.imm_GetByte(pos) & 0xf;
                bool isAvg  = (data.imm_GetByte(pos) & 0x80) != 0;
                uint len    = (uint)(1 + ((data.imm_GetByte(pos) >> 4) & 0x7));
                pos++;
                if (data.Len < pos + len)
                {
                    _yctx._Log("drop invalid timedNotification");
                    return;
                }

                if (funYdx == 0xf)
                {
                    byte[] intData = new byte[len];
                    for (uint i = 0; i < len; i++)
                    {
                        intData[i] = data.imm_GetByte(pos + i);
                    }

                    ydev.imm_setDeviceTime(intData);
                }
                else
                {
                    YPEntry yp = imm_getYPEntryFromYdx(funYdx);
                    if (yp != null)
                    {
                        List <int> report = new List <int>((int)(len + 1));
                        report.Add(isAvg ? 1 : 0);
                        for (uint i = 0; i < len; i++)
                        {
                            int b = data.imm_GetByte(pos + i) & 0xff;
                            report.Add(b);
                        }

                        _hub.imm_handleTimedNotification(yp.Serial, yp.FuncId, ydev.imm_getDeviceTime(), report);
                    }
                }

                pos += len;
            }
        }
        public void handleTimedNotificationV2(YPktStreamHead data)
        {
            uint    pos  = 0;
            YDevice ydev = _yctx._yHash.imm_getDevice(SerialNumber);

            if (ydev == null)
            {
                // device has not been registered;
                return;
            }

            while (pos < data.Len)
            {
                int  funYdx   = data.imm_GetByte(pos) & 0xf;
                uint extralen = (uint)((data.imm_GetByte(pos) >> 4) & 0xf);
                uint len      = extralen + 1;
                pos++; // consume generic header
                if (funYdx == 0xf)
                {
                    byte[] intData = new byte[len];
                    for (uint i = 0; i < len; i++)
                    {
                        intData[i] = data.imm_GetByte(pos + i);
                    }

                    ydev.imm_setDeviceTime(intData);
                }
                else
                {
                    YPEntry yp = imm_getYPEntryFromYdx(funYdx);
                    if (yp != null)
                    {
                        List <int> report = new List <int>((int)(len + 1));
                        report.Add(2);
                        for (uint i = 0; i < len; i++)
                        {
                            int b = data.imm_GetByte(pos + i) & 0xff;
                            report.Add(b);
                        }

                        _hub.imm_handleTimedNotification(yp.Serial, yp.FuncId, ydev.imm_getDeviceTime(), report);
                    }
                }

                pos += len;
            }
        }
        private void imm_handleNotifcation(YPktStreamHead ystream)
        {
            string functionId;
            int    firstByte = ystream.imm_GetByte(0);
            bool   isV2      = ystream.StreamType == YGenericHub.YSTREAM_NOTICE_V2;

            if (isV2 || firstByte <= NOTIFY_1STBYTE_MAXTINY || firstByte >= NOTIFY_1STBYTE_MINSMALL)
            {
                int     funcvalType = (firstByte >> NOTIFY_V2_TYPE_OFS) & NOTIFY_V2_TYPE_MASK;
                int     funydx      = firstByte & NOTIFY_V2_FUNYDX_MASK;
                YPEntry ypEntry     = imm_getYPEntryFromYdx(funydx);
                if (ypEntry != null)
                {
                    if (ypEntry.Index == funydx)
                    {
                        if (funcvalType == YGenericHub.NOTIFY_V2_FLUSHGROUP)
                        {
                            // not yet used by devices
                        }
                        else
                        {
                            if ((firstByte & NOTIFY_V2_IS_SMALL_FLAG) != 0)
                            {
                                // added on 2015-02-25, remove code below when confirmed dead code
                                throw new YAPI_Exception(YAPI.IO_ERROR, "Hub Should not fwd notification");
                            }

                            int    len  = (int)ystream.Len;
                            byte[] data = new byte[len];
                            ystream.imm_CopyData(data, 0);
                            string funcval = YGenericHub.imm_decodePubVal(funcvalType, data, 1, len - 1);
                            _hub.imm_handleValueNotification(SerialNumber, ypEntry.FuncId, funcval);
                        }
                    }
                }
            }
            else
            {
                string serial = ystream.imm_GetString(0, YAPI.YOCTO_SERIAL_LEN);
                if (SerialNumber == null)
                {
                    SerialNumber = serial;
                }

                uint p    = YAPI.YOCTO_SERIAL_LEN;
                int  type = ystream.imm_GetByte(p++);
                switch (type)
                {
                case NOTIFY_PKT_NAME:
                    _logicalname = ystream.imm_GetString(p, YAPI.YOCTO_LOGICAL_LEN);
                    _beacon      = ystream.imm_GetByte(p + YAPI.YOCTO_LOGICAL_LEN);
                    break;

                case NOTIFY_PKT_PRODNAME:
                    _product = ystream.imm_GetString(p, YAPI.YOCTO_PRODUCTNAME_LEN);
                    break;

                case NOTIFY_PKT_CHILD:
                    break;

                case NOTIFY_PKT_FIRMWARE:
                    _firmware = ystream.imm_GetString(p, YAPI.YOCTO_FIRMWARE_LEN);
                    p        += YAPI.YOCTO_FIRMWARE_LEN;
                    p        += 2;
                    _deviceid = (ushort)(ystream.imm_GetByte(p) + (ystream.imm_GetByte(p + 1) << 8));
                    break;

                case NOTIFY_PKT_FUNCNAME:
                    functionId = ystream.imm_GetString(p, YAPI.YOCTO_FUNCTION_LEN);
                    p         += YAPI.YOCTO_FUNCTION_LEN;
                    string funcname = ystream.imm_GetString(p, YAPI.YOCTO_LOGICAL_LEN);
                    if (!_usbYP.ContainsKey(functionId))
                    {
                        _usbYP[functionId] = new YPEntry(serial, functionId, YPEntry.BaseClass.Function);
                    }

                    _usbYP[functionId].LogicalName = funcname;
                    break;

                case NOTIFY_PKT_FUNCVAL:
                    functionId = ystream.imm_GetString(p, YAPI.YOCTO_FUNCTION_LEN);
                    p         += YAPI.YOCTO_FUNCTION_LEN;
                    string funcval = ystream.imm_GetString(p, YAPI.YOCTO_PUBVAL_SIZE);
                    _hub.imm_handleValueNotification(serial, functionId, funcval);
                    break;

                case NOTIFY_PKT_STREAMREADY:
                    _devState = DevState.StreamReadyReceived;
                    _wp       = new WPEntry(_logicalname, _product, _deviceid, "", _beacon, SerialNumber);
                    _yctx._Log("Device " + SerialNumber + " ready.\n");
                    _currentTask.SetResult(true);
                    break;

                case NOTIFY_PKT_LOG:
                    //FIXME: handle log notification
                    break;

                case NOTIFY_PKT_FUNCNAMEYDX:
                    functionId = ystream.imm_GetString(p, YAPI.YOCTO_FUNCTION_LEN - 1);
                    p         += YAPI.YOCTO_FUNCTION_LEN - 1;
                    byte funclass = ystream.imm_GetByte(p++);
                    funcname = ystream.imm_GetString(p, YAPI.YOCTO_LOGICAL_LEN);
                    p       += YAPI.YOCTO_LOGICAL_LEN;
                    byte funydx = ystream.imm_GetByte(p);
                    if (!_usbYP.ContainsKey(functionId))
                    {
                        _usbYP[functionId] = new YPEntry(serial, functionId, YPEntry.BaseClass.forByte(funclass));
                    }

                    // update ydx
                    _usbYP[functionId].Index       = funydx;
                    _usbYP[functionId].LogicalName = funcname;
                    break;

                case NOTIFY_PKT_PRODINFO:
                    break;

                default:
                    //fixme: Find why this happening on my dev computer
                    throw new YAPI_Exception(YAPI.IO_ERROR, "Invalid Notification");
                }
            }
        }
        internal async void OnInputReportEvent(HidDevice sender, HidInputReportReceivedEventArgs args)
        {
            if (_devState == DevState.Detected || _devState == DevState.IOError)
            {
                // drop all packet until reset has been sent
                return;
            }

            try {
                byte[] bb  = args.Report.Data.ToArray();
                long   ofs = 1; //skip first byte that is not part of the packet
                List <YPktStreamHead> streams = new List <YPktStreamHead>();
                while (ofs < bb.Length)
                {
                    YPktStreamHead s = YPktStreamHead.imm_Decode(ofs, bb);
                    if (s == null)
                    {
                        break;
                    }

                    //Debug.WriteLine(s.ToString());
                    streams.Add(s);
                    ofs += s.Len + 2;
                }

                YPktStreamHead streamHead = streams[0];
                switch (_devState)
                {
                case DevState.ResetSend:
                    if (streamHead.PktType != YUSBPkt.YPKT_CONF || streamHead.StreamType != YUSBPkt.USB_CONF_RESET)
                    {
                        return;
                    }

                    byte low    = streamHead.imm_GetByte(0);
                    uint hig    = streamHead.imm_GetByte(1);
                    uint devapi = (hig << 8) + low;
                    _devVersion = devapi;
                    if (imm_CheckVersionCompatibility(devapi) < 0)
                    {
                        return;
                    }

                    await Start(_pktAckDelay);

                    break;

                case DevState.StartSend:
                    if (streamHead.PktType != YUSBPkt.YPKT_CONF || streamHead.StreamType != YUSBPkt.USB_CONF_START)
                    {
                        return;
                    }

                    if (_devVersion >= YUSBPkt.YPKT_USB_VERSION_BCD)
                    {
                        _pktAckDelay = streamHead.imm_GetByte(1);
                    }
                    else
                    {
                        _pktAckDelay = 0;
                    }

                    _lastpktno = streamHead.PktNumber;
                    _devState  = DevState.StartReceived;
                    break;

                case DevState.StreamReadyReceived:
                case DevState.StartReceived:
                    if (_devState == DevState.StreamReadyReceived || _devState == DevState.StartReceived)
                    {
                        if (_pktAckDelay > 0 && _lastpktno == streamHead.PktNumber)
                        {
                            //late retry : drop it since we already have the packet.
                            return;
                        }

                        uint expectedPktNo = (_lastpktno + 1) & 7;
                        if (streamHead.PktNumber != expectedPktNo)
                        {
                            String message = "Missing packet (look of pkt " + expectedPktNo + " but get " + streamHead.PktNumber + ")";
                            _yctx._Log(message + "\n");
                            _yctx._Log("Set YAPI.RESEND_MISSING_PKT on YAPI.InitAPI()\n");
                            _devState = DevState.IOError;
                            _watcher.imm_removeUsableDevice(this);
                            return;
                        }

                        _lastpktno = streamHead.PktNumber;
                        await streamHandler(streams);
                        await checkMetaUTC();
                    }

                    break;

                default:
                    return;
                }
            } catch (YAPI_Exception ex) {
                _yctx._Log(ex.Message + "\n");
                _yctx._Log("Set YAPI.RESEND_MISSING_PKT on YAPI.InitAPI()\n");
                _devState = DevState.IOError;
                _watcher.imm_removeUsableDevice(this);
                if (_currentTask != null)
                {
                    _currentTask.SetException(ex);
                }
            }
        }