Beispiel #1
0
        public ReaderService(IReaderLowLevel reader)
        {
            _reader = reader;

            _reader.Initialize();

#if CHATTY_READER
            Trace.Listeners.Add(new TextWriterTraceListener(CHATTY_OUTPUT_FILE));
#endif

            /*
             * NOTE: from https://www.dataloggerinc.com/downloads/caenrfid/CAEN_RFID_API_UserMan_rev_04.pdf
             */
            /*
             * {
             *  double Gain = 8.0;
             *  double Loss = 1.5;
             *  double ERPPower = 1500.0;
             *  int OutPower;
             *  OutPower = (int)(ERPPower / Math.Pow(10, ((Gain - Loss - 2.14) / 10)));
             *  M3Client.reader.SetPower(OutPower);
             *
             *  Console.WriteLine("Set effective radiate power to {0} mW", ERPPower);
             * }
             * {
             *  double Gain = 8.0;
             *  double Loss = 1.5;
             *  double ERPPower;
             *  int OutPower;
             *  OutPower = M3Client.reader.GetPower();
             *  ERPPower = ((double)OutPower) * ((double)Math.Pow(10, ((Gain - Loss - 2.14) / 10)));
             *  Console.WriteLine("Current effective radiate power, mW: {0}", ERPPower);
             * }*/
        }
Beispiel #2
0
        /// <summary>
        /// Reads EPC URI from a tag. May return either an identity URI or a raw URI.
        /// </summary>
        /// <param name="reader">low-level reader</param>
        /// <param name="tag">the singulated tag</param>
        /// <param name="uri">output URI</param>
        /// <returns>true if able to obtain the URI</returns>
        public static bool ReadEPCURI(this IReaderLowLevel reader, object tag, out string uri)
        {
            DecodeError err;

            TagEPCURI_SGTIN sgtin;

            err = ReadEPC_SGTIN(reader, tag, out sgtin);
            if (err == DecodeError.None)
            {
                uri = sgtin.Identity.URI;
                return(true);
            }
            else
            {
                TagRaw raw;
                err = ReadEPC_Raw(reader, tag, out raw);
                if (err == DecodeError.None)
                {
                    var builder = new StringBuilder();
                    raw.GetURI(builder);
                    uri = builder.ToString();
                    return(true);
                }
            }

            uri = null;
            return(false);
        }
Beispiel #3
0
        /// <summary>
        /// Read the first 4 bytes of a properly formatted EPC memory bank, and try to tease out
        /// the "protocol control" info
        /// </summary>
        /// <param name="reader">Reference to the reader that is used to obtain the TID memory bank contents</param>
        /// <param name="tag">Reference to the singulated tag (reader-specific datum)</param>
        /// <param name="pc">Decoded Protocol Control information</param>
        /// <returns>Error or success</returns>
        public static DecodeError ReadEPC_PC(this IReaderLowLevel reader, object tag, out EPC_PC pc)
        {
            pc = default(EPC_PC);

            int start = 0;
            int count = 4;

            byte[] epc;
            var    rr = reader.ReadBytes(tag, 0x01, start, count, out epc);

            if (!rr)
            {
                return(DecodeError.IO);
            }

            // extract CRC-16, protocol control
            pc.Crc16 = (short)((epc[0] << 8) | epc[1]);
            // represents the number of 16-bit words comprising the PC field and the EPC field
            // bits[0x10..0x14]
            pc.PC_EPC_Length = (short)(((epc[2] >> 3) & 0x1F) * 2); // full length of PC+EPC data, in bytes
            pc.UMI           = (epc[2] & 0x4) != 0;                 // indicates if user memory bank is present
            pc.XI            = (epc[2] & 0x2) != 0;                 // indicates if XPC is present
            // toggle (bit 0x17)
            // 0: bits 0x18-0x1F contain attribute bits and the remainder of EPC bank contains a binary encoded EPC
            // 1: bits 0x18-0x1F contain ISO AFI, and the remainder of EPC bank contains UII
            pc.Toggle = (epc[2] & 0x1) != 0;
            // bits 0x18-0x1F
            pc.Attribs = epc[3]; // bits that may guide the handling of physical object to which the tag is affixed (Gen2 v1.x tags only)

            return(DecodeError.None);
        }
Beispiel #4
0
 public void Shutdown()
 {
     if (_reader != null)
     {
         _reader.Dispose();
         _reader = null;
     }
 }
Beispiel #5
0
        /// <summary>
        /// Decode the EPC memory bank, assuming it is in raw format. Ref: EPC TDS 1.9, 15.2.1.
        /// </summary>
        /// <param name="reader">low-level reader</param>
        /// <param name="tag">the singulated tag</param>
        /// <param name="uri">output Raw URI</param>
        /// <returns>Success or failure</returns>
        public static DecodeError ReadEPC_Raw(this IReaderLowLevel reader, object tag, out TagRaw uri)
        {
            uri = default(TagRaw);

            EPC_PC pc;
            var    rr = ReadEPC_PC(reader, tag, out pc);

            if (rr != DecodeError.None)
            {
                return(rr);
            }

            uri.PC = pc;
            var pc_epc_len = pc.PC_EPC_Length;

            // read full length of EPC (12 bytes)
            // NOTE: this is much more efficient to do than reading by tiny 4-byte chunks!
            byte[] epc_payload = new byte[pc_epc_len];
            {
                const int chunkSize = 4;
                int       sizeRead  = 0;
                while (sizeRead < pc_epc_len)
                {
                    byte[] chunk;
                    if (!reader.ReadBytes(tag, 0x01, 4 + sizeRead, chunkSize, out chunk))
                    {
                        return(DecodeError.IO);
                    }
                    Buffer.BlockCopy(chunk, 0, epc_payload, sizeRead, chunkSize);
                    sizeRead += chunkSize;
                }
            }

            // bits[0x20..0x20+N]
            uri.V = epc_payload;

            return(DecodeError.None);
        }
        /// <summary>
        /// Encode the Tag EPC URI in SGTIN scheme into tag EPC memory bank.
        /// </summary>
        /// <param name="reader">Reference to the reader</param>
        /// <param name="tag">Reference to the singulated tag (reader-specific datum)</param>
        /// <param name="uri">The Tag URI</param>
        /// <returns>True if succeeded</returns>
        public static bool WriteEPC(this IReaderLowLevel reader, object tag, TagEPCURI_SGTIN uri)
        {
            // try to encode the EPC Tag URI

            // see 14.5.1, table 14-2
            // algorithm: 14.3.3
            byte[] res_control = new byte[4];
            byte[] res         = new byte[12];

            res_control[0] = 0; // CRC-16
            res_control[1] = 0; // CRC-16

            /*
             * The number of bits, N, in the EPC binary encoding determined
             * in Step 2 above, divided by 16, and rounded up to the next
             * higher integer if N was not a multiple of 16
             */
            var nbits      = 12 * 8; // full length of PC+EPC data, in bits
            int nbit_value = (nbits + 15) / 16;
            var UMI        = true;   // indicates if user memory bank is present
            var XI         = false;  // indicates if XPC is present
            var toggle     = false;  // indicates if bits 0x18-0x1F contain attribute bits and the remainder of EPC bank contains a binary encoded EPC

            res_control[2] =
//            var pc_epc_len = ((epc[2] >> 3) & 0x1F) * 2; // full length of PC+EPC data, in bytes
                (byte)((nbit_value << 3)
                       | (UMI? 0x4 : 0)
                       | (XI? 0x2 : 0)
                       | (toggle? 0x1 : 0));
            res_control[3] = 0;

            //Console.WriteLine("Control: {0}", BitConverter.ToString(res_control));

            // SGTIN-96

            // EPC header 1 byte = 00110000 (bin)
            // filter 3 bits (3 bit integer)
            // partition 3 bits (3 bit integer, see below)
            // GS1 Company Prefix 20-40 bits
            // Item Ref 24-4 bits
            // Serial 38 bits

            // write the header
            res[0] = 0x30;
            // write the filter
            res[1] = (byte)(res[1] | ((uri.Filter & 0x7) << 5));

            var  uri0      = uri.Identity.URI;
            byte partition = 0;
            var  C         = uri.Identity.GS1CompanyPrefix;
            var  D         = uri.Identity.ItemRef;
            // value P (partition value, 3 bits)
            // value C (of M-bit width)
            var Cdigits = uri.Identity.GS1CompanyPrefixLength;
            // value D (of N-bit width)
            var Ddigits = uri.Identity.ItemRefLength;

            // NOTE: Cdigits + Ddigits must be 13
            var sum = Cdigits + Ddigits;

            // there's a complication
            // a GTIN might be:
            // - GTIN-8 (8 digits)
            // - GTIN-12 (12 digits)
            // - GTIN-13 (13 digits)
            // - GTIN-14 (14 digits)
            // first of all, how to convert GTIN-8 to GTIN-12?
            // what about anything else? should we use strings instead of long integers?

            /*
             * see: https://www.gs1us.org/resources/standards/company-prefix
             * - GS1 Company Prefix is a string of digits
             * - GS1 Company Prefix Length is between 7 and 11 digits
             * - GS1 Company Prefix Length provides capacity in emitting fresh GTINs
             *   - prefix length = 7, capacity = 100 000
             *   - prefix length = 8, capacity = 10 000
             *
             * A GS1 Company Prefix and an Item Reference Number comprise a GTIN.
             *
             * http://www.envioag.com/wp-content/uploads/2013/05/GTIN_070313.pdf
             */

            if (Cdigits == 12 && Ddigits == 1)
            {
                partition = 0;
                // new SGTINPartInfo() { bitsM = 40, digitsL = 12, bitsN = 4, digits = 1 },
                res[1] |= (byte)((C >> 38) & 0x3);
                res[2] |= (byte)((C >> 30) & 0xFF);
                res[3] |= (byte)((C >> 22) & 0xFF);
                res[4] |= (byte)((C >> 14) & 0xFF);
                res[5] |= (byte)((C >> 6) & 0xFF);
                res[6] |= (byte)(((C >> 0) & 0x3F) << 2);

                res[6] |= (byte)((D >> 2) & 0x3);
                res[7] |= (byte)(((D >> 0) & 0x3) << 6);
            }
            else if (Cdigits == 11 && Ddigits == 2)
            {
                // 1
                return(false);
            }
            else if (Cdigits == 10 && Ddigits == 3)
            {
                partition = 2;
                res[1]   |= ((byte)((C >> 32) & 0x03));
                res[2]   |= ((byte)((C >> 24) & 0xFF));
                res[3]   |= ((byte)((C >> 16) & 0xFF));
                res[4]   |= ((byte)((C >> 8) & 0xFF));
                res[5]   |= ((byte)((C >> 0) & 0xFF));

                res[6] |= (byte)(D >> 2);
                res[7] |= (byte)(((D >> 0) & 0x03) << 6);
            }
            else if (Cdigits == 9 && Ddigits == 4)
            {
                // 3
                return(false);
            }
            else if (Cdigits == 8 && Ddigits == 5)
            {
                // 4
                return(false);
            }
            else if (Cdigits == 7 && Ddigits == 6)
            {
                partition = 5;
                res[1]   |= ((byte)((C >> 22) & 0x03));
                res[2]   |= ((byte)((C >> 14) & 0xFF));
                res[3]   |= ((byte)((C >> 6) & 0xFF));
                res[4]   |= ((byte)(((C >> 0) & 0x3F) << 2));

                res[4] |= ((byte)((D >> 18) & 0x03));
                res[5] |= ((byte)((D >> 10) & 0xFF));
                res[6] |= ((byte)((D >> 2) & 0xFF));
                res[7] |= ((byte)(((D >> 0) & 0x03) << 6));
            }
            else if (Cdigits == 6 && Ddigits == 7)
            {
                // 6
                return(false);
            }
            else
            {
                // unknown partition
                return(false);
            }

            res[1] = (byte)(res[1] | ((partition & 0x7) << 2));

            var S = uri.Identity.SerialNr;

            res[7]  = (byte)(res[7] | (byte)((S >> 32) & 0x3F));
            res[8]  = (byte)(res[8] | (byte)((S >> 24) & 0xFF));
            res[9]  = (byte)(res[9] | (byte)((S >> 16) & 0xFF));
            res[10] = (byte)(res[10] | (byte)((S >> 8) & 0xFF));
            res[11] = (byte)(res[11] | (byte)((S >> 0) & 0xFF));

            /*
             * var res_hexstring = BitConverter.ToString(res);
             * Console.WriteLine("result of WriteEPC: {0}", res_hexstring);
             */

            if (reader.ChunkSize > 0)
            {
                // try writing in blocks of ChunkSize
                var csize = reader.ChunkSize;
                var chunk = new byte[csize];
                var beg   = 0;
                var ofs   = 4;
                while (beg < res.Length)
                {
                    Buffer.BlockCopy(res, beg, chunk, 0, csize);
                    if (!reader.WriteBytes(tag, 0x01, ofs, csize, chunk))
                    {
                        return(false);
                    }
                    ofs += csize;
                    beg += csize;
                }
                return(true);
            }
            else
            {
                // skip the first 4 bytes
                return(reader.WriteBytes(tag, 0x01, 4, res.Length, res));
            }
        }
Beispiel #7
0
        /// <summary>
        /// Decode the TID memory bank of a chip.
        /// </summary>
        /// <param name="reader">Reference to the reader that is used to obtain the TID memory bank contents</param>
        /// <param name="tag">Reference to the singulated tag (reader-specific datum)</param>
        /// <param name="tid">The decoded contents</param>
        /// <returns>Error or success</returns>
        public static DecodeError ReadTID(this IReaderLowLevel reader, object tag, out TID tid)
        {
            tid = default(TID);

            if (tag == null)
            {
                return(DecodeError.IO);
            }

            /*
             * TDS 1.5
             * - TID shall contain 0xE2 @ 0x00-0x07 (allocation class identifier)
             * - TID shall contain 12-bit Tag mask designer identifier MDID @ 0x08-0x13
             * - TID shall contain 12-bit Tag Model Number (TMN) @ 0x14-0x1F
             * - bit 0x08 is the XTID bit: if set to 0, the bank contains only
             *    allocation class identifier, MDID, and TMN
             *    A value of zero indicates a short TID in which
             *    the values beyond address 0x1F are not defined. A value of one indicates an Extended Tag
             *    Identification (XTID) in which the memory locations beyond 0x1F contain additional data
             *    as specified in Section 16.2.
             *
             * what's in TMN?
             * - if MDID is Impinj:
             *   - 0001011xxxxx Monza 6 family tag
             *   - 0001001100xx Monza 5 family tag
             *   - 0001000xxxxx Monza 4 family tag
             *
             * examples (bytes 0-8 of TID)
             * also: refer to http://www.kentraub.net/tools/tagxlate/TIDDecoder.html
             * E2-80-11-05-20-00-55-01 -- singular tag, Monza
             * E2-80-11-05-20-00-55-01-21-28-08-98
             *    (Monza 4QT, according to: Monza TID Memory Maps for Self-Serialization)
             *    MDID 801
             *      vendor: Impinj
             *    TMN 105 (binary 0001 0000 0101)
             *        000 100 000 101
             *        ^^^ ^^^ MCS identifier (3 bits), product (3 bits)
             *                ^^^ MCS product (unused)
             *                    ^^^ version
             *    manufacturer-assigned serial number: 550121280898 (hex)
             *    STID URI: urn:epc:stid:x801.x105.x550121280898
             *    already serialized!
             * E2-80-11-05-20-00-55-81 -- one of the twin tags, don't know which one exactly
             *    premature end of TID
             * E2-00-34-12-01-3B-F0-00 (screw-on tags)
             * E2-00-34-12-01-38-F0-00 (screw-on tags)
             *        ^ MCS identifier!
             *    MDID 003, TMN 412 (binary 0100 0001 0010)
             *      010 000 010 010
             *      ^^^ MCS identifier (vendor code?)
             *          ^^^ vendor code
             *              ^^^ product
             *                  ^^^ version
             * what about MCS?
             * - http://www.emmicroelectronic.com/sites/default/files/public/products/datasheets/an604004_0.pdf
             * - http://www.alientechnology.com/wp-content/uploads/white-paper-Alien-Technology-Higgs-4-Serialization.pdf
             * - http://www.nxp.com/documents/other/249820.pdf
             * - MCS serial number is
             *   MCS header (3 bits), MCS product (3 bits), IC serial (32 bits)
             *   where to get that data?
             *   break up TMN as (from MSB to LSB):
             *   - MCS identifier (6 bits)
             *     - highest bits: unused
             *     - lowest bits: vendor code
             *   - MCS product (3 bits)
             *   - version (3 bits)
             */
            {
                // read 12-byte TID (6-word, where word = 2 bytes)
                // 12 byte = 96
                // 64 bits of UTID (8 bytes)

                // read 4 bytes of TID first
                // if bit 0x08 is set, read XTID

                byte[] shortTID;
                var    r0 = reader.ReadBytes(tag, 0x02, 0, 4, out shortTID);
                if (!r0)
                {
                    return(DecodeError.IO);
                }
                if (shortTID[0] != 0xE2)
                {
                    return(DecodeError.Invalid); // we are not prepared to handle custom TID tags
                }
                var MDID = shortTID[1] << 4 | ((shortTID[2] >> 4) & 0xF);
                var TMN  = ((shortTID[2] & 0xF) << 8) | shortTID[3];

                var shortTID_hex = BitConverter.ToString(shortTID);

                // is XTID set?
                var XTID = (shortTID[1] & 0x80) > 0;
                // no: short tag
                // yes: long tag
                // - memory locations 0x20 to 0x2F contain 16-bit XTID header
                //   that specifies what info is present at 0x30 and above

                Debug.WriteLine(string.Format("MDID: {0:X} TMN: {1:X}", MDID, TMN));
                if (XTID)
                {
                    // read XTID header segment
                    byte[] XTID_header;
                    var    r1 = reader.ReadBytes(tag, 0x02, 4, 2, out XTID_header);
                    if (!r1)
                    {
                        return(DecodeError.IO);
                    }

                    //var XTID_header_hex = BitConverter.ToString(XTID_header);

                    var XTID_header_word        = (short)((XTID_header[0] << 8) | (XTID_header[1] & 0xFF));
                    var extended_header_present = IsBitSet(XTID_header_word, 0);
                    var reserved = !IsBitSet(XTID_header_word, 1) &&
                                   !IsBitSet(XTID_header_word, 2) &&
                                   !IsBitSet(XTID_header_word, 3) &&
                                   !IsBitSet(XTID_header_word, 4) &&
                                   !IsBitSet(XTID_header_word, 5) &&
                                   !IsBitSet(XTID_header_word, 6) &&
                                   !IsBitSet(XTID_header_word, 7) &&
                                   !IsBitSet(XTID_header_word, 8) &&
                                   !IsBitSet(XTID_header_word, 9); // TDS 1.9: these should be 0
                    var has_user_mem_and_block_permalock_segment = IsBitSet(XTID_header_word, 10);
                    var has_blockwrite_and_blockerase_segment    = IsBitSet(XTID_header_word, 11);
                    var has_optional_command_support_segment     = IsBitSet(XTID_header_word, 12);

                    var serial_size = (XTID_header_word >> 13) & 0x7;
                    // if serialization is non-zero, specifies that XTID includes
                    // a unique serial number, whose length is in bits is 48 + 16(N-1),
                    // where N is the value of this field;
                    // otherwise, specifies that XTID does not include a unique serial number

                    Debug.WriteLine(string.Format("serial size: {0}", serial_size));
                    if (serial_size > 0)
                    {
                        var    size = (48 + 16 * (serial_size - 1)) / 8;
                        byte[] XTID_serial_segment;

                        var r2 = reader.ReadBytes(tag, 0x02, 6, size, out XTID_serial_segment);
                        if (!r2)
                        {
                            return(DecodeError.IO);
                        }

                        var XTID_serial_hex = BitConverter.ToString(XTID_serial_segment);
                        Debug.WriteLine(string.Format("serial segment: {0}", XTID_serial_hex));

                        tid.MDID   = MDID;
                        tid.TMN    = TMN;
                        tid.Serial = XTID_serial_segment;

                        var STID_uri = "urn:epc:stid:";
                        STID_uri    += "x" + MDID.ToString("X3"); // as 3-character hex (upcase)
                        STID_uri    += ".x" + TMN.ToString("X3"); // as 3-character hex (upcase)
                        STID_uri    += ".x" + XTID_serial_hex.Replace("-", "");
                        tid.STID_URI = STID_uri;

                        // TODO: STID -> EPC SGTIN, and write it down

                        Debug.WriteLine(string.Format("STID URI: {0}", STID_uri));
                        //System.Windows.Forms.MessageBox.Show(STID_uri);
                        return(DecodeError.None);
                    }
                }
                else
                {
                    // maybe it's Alien Higgs-3?
                    // refer to: https://www.rfid-alliance.com/RFIDshop/Alien-Technology-Higgs-3-IC-Datasheet.pdf
                    if (MDID == 0x3) // manufacturer: Alien
                    {
                        var model     = (TMN >> 8) & 0xF;
                        var rev_major = (TMN >> 4) & 0xF;
                        var rev_minor = TMN & 0xF;

                        // e.g. model=4 (Higgs), Major=1, minor=2
                        Debug.WriteLine(string.Format("Alien: model {0:X}, major rev. {1}, minor rev. {2}", model, rev_major, rev_minor));
                        if (model == 4 && rev_major == 1 && rev_minor == 2)
                        {
                            byte[] UTID;
                            var    r2 = reader.ReadBytes(tag, 0x02, 4, 8, out UTID);
                            if (!r2)
                            {
                                return(DecodeError.IO);
                            }

                            tid.MDID   = MDID;
                            tid.TMN    = TMN;
                            tid.Serial = UTID;

                            var UTID_hex = BitConverter.ToString(UTID);

                            // indeed, we have UTID here
                            // but how do we obtain SGTIN from it?
                            //var UTID_hex = BitConverter.ToString(UTID);
                            //Console.WriteLine("Alien: {0}", UTID_hex);
                            return(DecodeError.None);
                        }
                    }
                    else if (MDID == 0x6)
                    {
                        // NXP
                        // e.g. G2XM: http://www.nxp.com/documents/data_sheet/SL3ICS1002_1202.pdf
                        // used in e.g. HID IN TAG 500 UHF
                        var version = (TMN >> 4) & 0x3F;
                        // NOTE: whenever the 32 bit serial is exceeded the
                        // subversion is incremented by 1,
                        // so we include it into the serial!
                        var subversion = TMN & 0x0F;

                        byte[] UTID;
                        var    r2 = reader.ReadBytes(tag, 0x02, 4, 4, out UTID);
                        if (!r2)
                        {
                            return(DecodeError.IO);
                        }

                        byte[] serial = new byte[5];
                        serial[0] = (byte)subversion;
                        Buffer.BlockCopy(UTID, 0, serial, 1, 4);

                        tid.MDID   = MDID;
                        tid.TMN    = TMN;
                        tid.Serial = serial;

                        return(DecodeError.None);
                    }
                    else
                    {
                        // unknown manufacturer's tag
                        // see the list here: http://www.gs1.org/epcglobal/standards/mdid
                        tid.MDID   = MDID;
                        tid.TMN    = TMN;
                        tid.Serial = null;
                        return(DecodeError.None);
                    }
                }
            }

            return(DecodeError.Invalid);
        }
Beispiel #8
0
        /// <summary>
        /// Decode the EPC memory bank, which is assumed to be SGTIN-192, and return the Tag URI.
        /// </summary>
        /// <param name="reader">Reference to the reader that is used to obtain the TID memory bank contents</param>
        /// <param name="tag">Reference to the singulated tag (reader-specific datum)</param>
        /// <param name="uri">The resulting Tag URI</param>
        /// <returns>Success or failure</returns>
        public static DecodeError ReadEPC_SGTIN(this IReaderLowLevel reader, object tag, out TagEPCURI_SGTIN uri)
        {
            uri = default(TagEPCURI_SGTIN);

            // see also: http://www.rfidjournal.net/masterPresentations/rfid_live2012/np/traub_apr3_230_ITProf.pdf
            // what about the EPC?
            // bit 0x17 of EPC is the toggle
            // - we need to use SGTIN-96, and encode it
            //   - pure identity URI (e.g. urn:epc:id:sgtin:0614141.100743.401)
            //     - independent of RFID, so this is what the business apps should use
            //   - tag URI (e.g. urn:epc:tag:sgtin-96:3.0614141.100743.401) <-- contains control info of EPC memory bank as well
            //     - used when reading from a tag where the control info is of interest
            //     - used when writing to the EPC memory bank of an RFID tag, in order to fully specify the contents to be written
            //     - there exists 1:1 mapping between tag URI and binary representation
            // - what's the barcode equivalent?
            //   barcode:
            //      (01) 1 0614141 12345 2 (21) 401
            //      ^^^^ not included
            //   pure identity URI:
            // urn:epc:id:sgtin:0614141.112345.401
            // - also, need to learn how to encode SGTIN-96 to binary form and back!
            //
            // also, we might want to use the GS1 GIAI schema
            // - http://www.gs1.org/docs/idkeys/GS1_GIAI_Executive_Summary.pdf
            // - 1 2 ... n           n+1 n+2  ... <= 30
            //   GS1 company prefix| individual asset reference
            //   (numeric)         | (alphanumeric)
            //
            // what's in SGTIN? e.g. urn:epc:id:sgtin:0614141.112345.400
            // urn:epc:id:sgtin:CompanyPrefix.ItemRefAndIndicator.SerialNumber
            // - company prefix (assigned by GS1 to a managing entity or its delegates)
            // - item ref and indicator (assigned by the managing entity to a particular object class)
            // - serial number (assigned by the managing entity to an individual object)
            // what's in GIAI? e.g. urn:epc:id:giai:0614141.12345400
            // urn:epc:id:giai:CompanyPrefix.IndividulAssetReference
            // - company prefix (assigned by GS1 to a managing entity)
            // - individual asset reference (assigned uniquely by the managing entity to a specific asset)

            /*
             * how to assign?
             *
             * what's in the EPC
             * - 2 bytes of CRC (we don't usually write these, right?)
             * - 2 bytes: length (5 bits), UMI (1 bit), XI (1 bit), T (1 bit), attribs (8 bits)
             *     - length: nr of words for PC and EPC, combined
             * - 2 bytes: EPC header (1 byte), filter (3 bits), partition (3 bits), GTIN (2 bits)
             * - 2 bytes: GTIN
             * - 2 bytes: GTIN
             * - 2 bytes: GTIN (10 bits), serial (6 bits)
             * - 2 bytes: serial
             * - 2 bytes: serial
             * 16 bytes in total
             */

            // see also: http://www.rfidjournal.net/masterPresentations/rfid_live2012/np/traub_apr3_230_ITProf.pdf
            // what about the EPC?
            // bit 0x17 of EPC is the toggle
            // - we need to use SGTIN-96, and encode it
            //   - pure identity URI (e.g. urn:epc:id:sgtin:0614141.100743.401)
            //     - independent of RFID, so this is what the business apps should use
            //   - tag URI (e.g. urn:epc:tag:sgtin-96:3.0614141.100743.401) <-- contains control info of EPC memory bank as well
            //     - used when reading from a tag where the control info is of interest
            //     - used when writing to the EPC memory bank of an RFID tag, in order to fully specify the contents to be written
            //     - there exists 1:1 mapping between tag URI and binary representation
            // - what's the barcode equivalent?
            //   barcode:
            //      (01) 1 0614141 12345 2 (21) 401
            //      ^^^^ not included
            //   pure identity URI:
            // urn:epc:id:sgtin:0614141.112345.401
            // - also, need to learn how to encode SGTIN-96 to binary form and back!
            //
            // also, we might want to use the GS1 GIAI schema
            // - http://www.gs1.org/docs/idkeys/GS1_GIAI_Executive_Summary.pdf
            // - 1 2 ... n           n+1 n+2  ... <= 30
            //   GS1 company prefix| individual asset reference
            //   (numeric)         | (alphanumeric)
            //
            // what's in SGTIN? e.g. urn:epc:id:sgtin:0614141.112345.400
            // urn:epc:id:sgtin:CompanyPrefix.ItemRefAndIndicator.SerialNumber
            // - company prefix (assigned by GS1 to a managing entity or its delegates)
            // - item ref and indicator (assigned by the managing entity to a particular object class)
            // - serial number (assigned by the managing entity to an individual object)
            // what's in GIAI? e.g. urn:epc:id:giai:0614141.12345400
            // urn:epc:id:giai:CompanyPrefix.IndividulAssetReference
            // - company prefix (assigned by GS1 to a managing entity)
            // - individual asset reference (assigned uniquely by the managing entity to a specific asset)

            /*
             * how to assign?
             *
             * what's in the EPC
             * - 2 bytes of CRC (we don't usually write these, right?)
             * - 2 bytes: length (5 bits), UMI (1 bit), XI (1 bit), T (1 bit), attribs (8 bits)
             *     - length: nr of words for PC and EPC, combined
             * - 2 bytes: EPC header (1 byte), filter (3 bits), partition (3 bits), GTIN (2 bits)
             * - 2 bytes: GTIN
             * - 2 bytes: GTIN
             * - 2 bytes: GTIN (10 bits), serial (6 bits)
             * - 2 bytes: serial
             * - 2 bytes: serial
             * 16 bytes in total
             */

            EPC_PC pc;
            var    rr = ReadEPC_PC(reader, tag, out pc);

            if (rr != DecodeError.None)
            {
                return(rr);
            }

            var crc16      = pc.Crc16;
            var pc_epc_len = pc.PC_EPC_Length;
            var UMI        = pc.UMI;
            var XI         = pc.XI;
            var toggle     = pc.Toggle;
            var attribs    = pc.Attribs;

            // read full length of EPC (12 bytes or more)
            byte[] epc_payload = new byte[pc_epc_len];
            {
                const int chunkSize = 4;
                int       sizeRead  = 0;
                while (sizeRead < pc_epc_len)
                {
                    byte[] chunk;
                    if (!reader.ReadBytes(tag, 0x01, 4 + sizeRead, chunkSize, out chunk))
                    {
                        return(DecodeError.IO);
                    }
                    Buffer.BlockCopy(chunk, 0, epc_payload, sizeRead, chunkSize);
                    sizeRead += chunkSize;
                }
            }

            //var epc_payload_hex = BitConverter.ToString(epc_payload);
            //System.Windows.Forms.MessageBox.Show(BitConverter.ToString(epc_payload));

            // SGTIN-96
            var epc_header = epc_payload[0]; // must be 0x30 if that's SGTIN-96!

            if (epc_header != 0x30)
            {
                return(DecodeError.Invalid); // NOT SGTIN-96!
            }
            if (pc_epc_len < 10 /*96 bits*/)
            {
                return(DecodeError.Invalid); // wrong length of EPC! (see table 14.1 in EPC TDS 1.9)
            }
            var filter    = (epc_payload[1] >> 5) & 0x7;
            var partition = (epc_payload[1] >> 2) & 0x7;

            // 14.4.3
            var C       = 0L; // company prefix
            var Cdigits = 0;
            var D       = 0L; // indicator/item reference
            var Ddigits = 0;
            var S       = 0L;

            // Consider these M bits to be an unsigned binary integer, C.
            // The value of C must be less than 10^L, where L is the value
            // specified in the “GS1 Company Prefix Digits (L)” column of
            // the matching partition table row.
            //
            // There are N bits remaining in the input bit string, where
            // N is the value specified in the “Other Field Bits” column
            // of the matching partition table row. Consider these N bits
            // to be an unsigned binary integer, D. The value of D must be
            // less than 10^K, where K is the value specified in the
            // “Other Field Digits (K)” column of the matching partition
            // table row. Note that if K = 0, then the value of D must be zero.
            switch (partition)
            {
            case 0:
                // new SGTINPartInfo() { bitsM = 40, digitsL = 12, bitsN = 4, digits = 1 },
                C      |= ((long)(epc_payload[1] & 0x3) << 38);
                C      |= ((long)(epc_payload[2] & 0xFF) << 30);
                C      |= ((long)(epc_payload[3] & 0xFF) << 22);
                C      |= ((long)(epc_payload[4] & 0xFF) << 14);
                C      |= ((long)(epc_payload[5] & 0xFF) << 6);
                C      |= ((long)(((epc_payload[6] >> 2) & 0x3F)) << 0);
                Cdigits = 12;
                if ((ulong)C >= IntPow10((byte)Cdigits))
                {
                    return(DecodeError.Invalid);    // something is wrong with it!
                }
                D       = ((((epc_payload[6] & 0x3) << 2) | ((epc_payload[7] >> 6) & 0x3)) & 0xFF) << 0;
                Ddigits = 1;
                if ((ulong)D >= IntPow10((byte)1))
                {
                    return(DecodeError.Invalid);
                }
                break;

            /*
             *                              case 1:
             *                                  //{1, new SGTINPartInfo() { bitsM = 37, digitsL = 11, bitsN = 7, digits = 2 }},
             *                                  C |= ((long)(epc_payload[1] & 0x03) << 35);
             *                                  C |= ((long)(epc_payload[2] & 0xFF) << 27);
             *                                  C |= ((long)(epc_payload[3] & 0xFF) << 19);
             *                                  C |= ((long)(epc_payload[4] & 0xFF) << 11);
             *                                  C |= ((long)(epc_payload[5] & 0xFF) << 3);
             *                                  C |= ((long)(((epc_payload[6] >> 5) & 0x07)) << 0);
             *                                  Cdigits = 11;
             *                                  if ((ulong)C >= IntPow10((byte)Cdigits))
             *                                      continue; // something is wrong with it!
             *
             *                                  D |= ((long)(epc_payload[6] & 0x07) << 3); // 3 bits
             *                                  D |= ((((long)epc_payload[7] >> 4) & 0x0F) << 0); // 4 bits
             *                                  Ddigits = 2;
             *                                  if ((ulong)D >= IntPow10((byte)Ddigits))
             *                                      continue;
             *
             *                                  break;*/
            case 2:
                //{2, new SGTINPartInfo() { bitsM = 34, digitsL = 10, bitsN = 10, digits = 3 }},
                C      |= ((long)(epc_payload[1] & 0x03) << 32);
                C      |= ((long)(epc_payload[2] & 0xFF) << 24);
                C      |= ((long)(epc_payload[3] & 0xFF) << 16);
                C      |= ((long)(epc_payload[4] & 0xFF) << 8);
                C      |= ((long)(epc_payload[5] & 0xFF) << 0);
                Cdigits = 10;
                if ((ulong)C >= IntPow10((byte)Cdigits))
                {
                    return(DecodeError.Invalid);    // something is wrong with it!
                }
                D      |= ((long)epc_payload[6] << 2);
                D      |= ((long)((epc_payload[7] >> 6) & 0x03) << 0);
                Ddigits = 3;
                if ((ulong)D >= IntPow10((byte)Ddigits))
                {
                    return(DecodeError.Invalid);
                }
                break;

            /*
             * case 3:
             *  // {3, new SGTINPartInfo() { bitsM = 30, digitsL = 9, bitsN = 14, digits = 4 }},
             *  break;
             * case 4:
             * //            {4, new SGTINPartInfo() { bitsM = 30, digitsL = 9, bitsN = 14, digits = 4 }},
             */
            case 5:
                // {5, new SGTINPartInfo() { bitsM = 24, digitsL = 7, bitsN = 20, digits = 6 }},
                C      |= ((long)(epc_payload[1] & 0x03) << 22);
                C      |= ((long)(epc_payload[2] & 0xFF) << 14);
                C      |= ((long)(epc_payload[3] & 0xFF) << 6);
                C      |= ((((long)epc_payload[4] >> 2) & 0x3F) << 0);
                Cdigits = 7;
                if ((ulong)C >= IntPow10((byte)Cdigits))
                {
                    return(DecodeError.Invalid);    // something is wrong with it!
                }
                D      |= ((long)(epc_payload[4] & 0x03) << 18);
                D      |= ((long)(epc_payload[5] & 0xFF) << 10);
                D      |= ((long)(epc_payload[6] & 0xFF) << 2);
                D      |= ((long)((epc_payload[7] >> 6) & 0x03) << 0);
                Ddigits = 6;
                if ((ulong)D >= IntPow10((byte)Ddigits))
                {
                    return(DecodeError.Invalid);
                }
                break;

            /*
             * case 6:
             * //{6, new SGTINPartInfo() { bitsM = 20, digitsL = 6, bitsN = 24, digits = 7}},
             * C |= ((long)(epc_payload[1] & 0x03) << 18);
             * C |= ((long)(epc_payload[2] & 0xFF) << 10);
             * C |= ((long)(epc_payload[3] & 0xFF) << 2);
             * C |= ((((long)epc_payload[4] >> 2) & 0x3F) << 0);
             * Cdigits = 6;
             * if ((ulong)C >= IntPow10((byte)Cdigits))
             *  continue; // something is wrong with it!
             *
             * D |= ((long)(epc_payload[4] & 0x03) << 22);
             * D |= ((long)(epc_payload[5] & 0xFF) << 14);
             * D |= ((long)(epc_payload[6] & 0xFF) << 6);
             * D |= ((long)((epc_payload[7] >> 2) & 0x03) << 0);
             * Ddigits = 7;
             * if ((ulong)D >= IntPow10((byte)Ddigits))
             *  continue; // something is wrong with it!
             * break;*/

            default:
                return(DecodeError.Invalid);    // unable to parse this partition
            }

            // serial: 38 bits
            // NOTE: must be less than 274,877,906,944
            // 6 bits, and then some more
            S |= ((long)(epc_payload[7] & 0x3F) << 32);
            S |= ((long)(epc_payload[8] & 0xFF) << 24);
            S |= ((long)(epc_payload[9] & 0xFF) << 16);
            S |= ((long)(epc_payload[10] & 0xFF) << 8);
            S |= ((long)(epc_payload[11] & 0xFF) << 0);

            uri.Filter   = (byte)filter;
            uri.Identity = new IdentityEPCURI_SGTIN()
            {
                GS1CompanyPrefix       = C,
                ItemRef                = D,
                GS1CompanyPrefixLength = Cdigits,
                ItemRefLength          = Ddigits,
                SerialNr               = S
            };

            return(DecodeError.None);
        }