text:
/*
 * NE2K (NE2000) NIC
 *
 * See http://www.ti.com/lit/ds/symlink/dp8390d.pdf
 */

/*global ArrayBuffer, DataView, define, Uint8Array */
define([
    "js/util",
    "js/IODevice/NE2K/ASIC",
    "js/IODevice/NE2K/EthernetDevice",
    "js/Networking/Frame/Ethernet",
    "js/Exception",
    "js/IODevice",
    "js/Networking/Address/MAC",
    "js/Promise",
    "js/Register"
], function (
    util,
    ASIC,
    EthernetDevice,
    EthernetFrame,
    Exception,
    IODevice,
    MACAddress,
    Promise,
    Register
) {
    "use strict";

    var BASE_IO_ADDRESS_OPTION = "ioAddress",
        IRQ_OPTION = "irq",
        NE2K_MEMORY_SIZE = 32 * 1024,
        NE2K_MEMORY_START = 16 * 1024,
        NE2K_MEMORY_END = NE2K_MEMORY_START + NE2K_MEMORY_SIZE,
        MAC_ADDRESS = "macAddress";

    function NE2K(system, io, memory, options) {
        IODevice.call(this, "NE2K", system, io, memory, options);

        this.asic = new ASIC(this);
        this.boundaryPointer = new Register(1);
        this.bytesToTransmit = new Register(2);
        this.crcErrorTally = new Register(1);
        this.currentPage = new Register(1);
        this.ethernetDevice = new EthernetDevice(this);
        this.fifo = new Register(1);
        this.frameAlignmentErrorTally = new Register(1);
        // Base I/O address
        this.ioAddress = this.options[BASE_IO_ADDRESS_OPTION] || 0x300;
        this.irq = this.options[IRQ_OPTION] || 3;
        this.localDMAAddress = new Register(2);
        this.romMACAddress = MACAddress.createFromHexString(this.options[MAC_ADDRESS] || "00:12:34:56:78:90");
        this.macAddress = this.romMACAddress.copy();
        this.macAddressData = new DataView(new ArrayBuffer(32));
        this.memory = new DataView(new ArrayBuffer(NE2K_MEMORY_SIZE));
        this.missedPacketErrorTally = new Register(1);
        this.multicastHash = new Uint8Array(8);
        this.numberOfCollisions = new Register(1);
        this.pageStart = new Register(1);
        this.pageStop = new Register(1);
        this.remoteByteCount = new Register(2);
        this.remoteDMAAddress = new Register(2);
        this.remoteStartAddress = new Register(2);
        this.transmitPageStart = new Register(1);
        this.transmitTimer = system.createTimer();

        // Command Register (@ 0x00)
        this.cr = {
            initiatePacketTransmission: null,
            remoteDMACommand: new Register(1),
            selectedPage: new Register(1),
            startRequested: null,
            stopRequested: null
        };

        // Data Configuration Register
        this.dcr = {
            autoRemoveRXPackets: null, // AR
            endian: null,              // BOS
            fifoSize: new Register(1), // FT0, FT1
            longAddress: null,         // LAS
            loopbackSelect: null,      // LS
            wordSize: null             // WTS
        };

        // Interrupt Mask Register
        this.imr = {
            enablePacketReceiveInterrupt: null,       // PRXE
            enablePacketTransmitInterrupt: null,      // PTXE
            enablePacketReceiveErrorInterrupt: null,  // RXEE
            enablePacketTransmitErrorInterrupt: null, // TXEE
            enableOverwriteWarnInterrupt: null,       // OVWE
            enableCounterOverflowInterrupt: null,     // CNTE
            enableRemoteDMACompleteInterrupt: null    // RDCE
            // D7 is reserved
        };

        // Interrupt Status Register
        this.isr = {
            counterOverflow: null,                      // CNT
            packetReceivedWithNoErrors: null,           // PRX
            packetReceivedWithOneOrMoreErrors: null,    // RXE
            packetTransmittedWithNoErrors: null,        // PTX
            packetTransmittedWithOneOrMoreErrors: null, // TXE
            receiveBufferResourcesExhausted: null,      // OVW
            remoteDMAComplete: null,                    // RDC
            resetStatus: null                           // RST
        };

        // Receive Configuration Register
        this.rcr = {
            acceptEthernetBroadcast: null,        // AB
            acceptPacketsWithReceiveErrors: null, // SEP
            acceptUnder64ByteRunts: null,         // AR
            checkMulticastHashArray: null,        // AM
            monitorOnly: null,                    // MON
            promiscuousMode: null                 // PRO
            // D6, D7 are reserved
        };

        // Receive Status Register
        this.rsr = {
            badCRCDetected: null,              // CRC
            deferred: null,                    // DFR - Collision active
            disabled: null,                    // DIS - Set when in monitor mode
            fifoOverrun: null,                 // FO
            frameAlignmentError: null,         // FAE
            missedPacketError: null,           // MPA
            multiMatch: null,                  // PHY - Unicast or multicast/broadcast address match
            receiveCompleteWithoutError: null  // PRX
        };

        // Transmit Configuration Register
        this.tcr = {
            allowTransmissionDisableByExternalMCast: null, // ATD
            collisionPriority: null,                       // OFST
            crcDisable: null,                              // CRC
            loopbackControl: new Register(1)               // LB0, LB1
        };

        // Transmit Status Register
        this.tsr = {
            carrierSenseLost: null,                 // CRS
            fifoUnderrun: null,                     // FU
            noTransmissionCDHeartbeat: null,        // CDH
            outOfWindowCollision: null,             // OWC
            transmissionAborted: null,              // ABT
            transmissionCollided: null,             // COL
            transmissionCompleteWithoutError: null  // PTX
        };
    }

    util.inherit(NE2K).from(IODevice);

    util.extend(NE2K.prototype, {
        getIOPorts: function () {
            var description = "NE2000 NIC",
                ne2k = this,
                port,
                ports = {};

            for (port = ne2k.ioAddress ; port <= ne2k.ioAddress + 0x0F; ++port) {
                ports[port] = { description: description, allowedIOLengths: {1: true, 2: true} };
            }

            ports[ne2k.ioAddress + 0x10] = { description: description, allowedIOLengths: {1: true, 2: true} };
            ports[ne2k.ioAddress + 0x1F] = { description: description, allowedIOLengths: {1: true} };

            return ports;
        },

        getPluginData: function () {
            var ne2k = this;

            return {
                getEthernetDevice: function () {
                    return ne2k.ethernetDevice;
                }
            };
        },

        init: function () {
            var index,
                ne2k = this,
                promise = new Promise();

            util.each(ne2k.macAddress.toHexString().split(":"), function (octet, index) {
                octet = parseInt(octet, 16);

                // Initialise the MAC address area by doubling the octets up
                ne2k.macAddressData.setUint8(index * 2, octet);
                ne2k.macAddressData.setUint8(index * 2 + 1, octet);
            });

            // Add NE2K signature
            for (index = 12; index < 32; index++) {
                ne2k.macAddressData.setUint8(index, 0x57);
            }

            ne2k.transmitTimer.on("elapse", function () {
                ne2k.cr.initiatePacketTransmission = false;
                ne2k.tsr.transmissionCompleteWithoutError = true;

                // Generate an interrupt if not masked and not one in progress
                if (ne2k.imr.enablePacketTransmitInterrupt && !ne2k.isr.packetTransmittedWithNoErrors) {
                    ne2k.isr.packetTransmittedWithNoErrors = true;
                    ne2k.raiseIRQ();
                }
            });

            promise.resolve();

            return promise;
        },

        ioRead: function (port, length) {
            var ne2k = this,
                offset = port - ne2k.ioAddress;

            if (offset >= 0x10) {
                return ne2k.asic.read(offset - 0x10, length);
            }

            if (offset === 0x00) {
                return ne2k.readCR();
            }

            if (ne2k.cr.selectedPage.get() === 0) {
                return ne2k.readPage0(offset, length);
            }
            if (ne2k.cr.selectedPage.get() === 1) {
                return ne2k.readPage1(offset, length);
            }
            if (ne2k.cr.selectedPage.get() === 2) {
                return ne2k.readPage2(offset, length);
            }
            if (ne2k.cr.selectedPage.get() === 3) {
                return ne2k.readPage3(offset, length);
            }

            ne2k.system.debug("NE2K.ioRead() :: Unknown CR.selectedPage " + util.hexify(ne2k.cr.selectedPage.get()));
            return 0xFF;
        },

        ioWrite: function (port, value, length) {
            var ne2k = this,
                offset = port - ne2k.ioAddress;

            /*
             * The high 16 bytes of I/O space are for the NE2000 ASIC -
             * the low 16 bytes are for the DS8390, with the current
             * page being selected by the PS0, PS1 registers in the
             * Command Register (CR)
             */
            if (offset >= 0x10) {
                ne2k.asic.write(offset - 0x10, value, length);
            } else if (offset === 0) {
                ne2k.writeCR(value);
            } else {
                if (ne2k.cr.selectedPage.get() === 0) {
                    ne2k.writePage0(offset, value, length);
                } else if (ne2k.cr.selectedPage.get() === 1) {
                    ne2k.writePage1(offset, value, length);
                } else if (ne2k.cr.selectedPage.get() === 2) {
                    ne2k.writePage2(offset, value, length);
                } else if (ne2k.cr.selectedPage.get() === 3) {
                    ne2k.writePage3(offset, value, length);
                } else {
                    ne2k.system.debug("NE2K.ioWrite() :: Unknown CR.selectedPage " + util.hexify(ne2k.cr.selectedPage.get()));
                }
            }
        },

        lowerIRQ: function () {
            var ne2k = this;

            ne2k.system.lowerIRQ(ne2k.irq);
        },

        raiseIRQ: function () {
            var ne2k = this;

            ne2k.system.raiseIRQ(ne2k.irq);
        },

        readChipMemoryDMA: function (length) {
            var ne2k = this,
                address = ne2k.remoteDMAAddress.get();

            /*jshint bitwise: false */
            if (length === 2 && (address & 1)) {
                throw new Exception("NE2K.readChipMemoryDMA() :: Unaligned chip memory word read");
            }

            // Read-only MAC address
            if (address >= 0 && address <= 31) {
                if (length === 1) {
                    return ne2k.macAddressData.getUint8(address);
                }
                if (length === 2) {
                    return ne2k.macAddressData.getUint16(address, true);
                }
                if (length === 4) {
                    return ne2k.macAddressData.getUint32(address, true);
                }
            }

            if (address >= NE2K_MEMORY_START && address < NE2K_MEMORY_END) {
                if (length === 1) {
                    return ne2k.memory.getUint8(address - NE2K_MEMORY_START);
                }
                if (length === 2) {
                    return ne2k.memory.getUint16(address - NE2K_MEMORY_START, true);
                }
                if (length === 4) {
                    return ne2k.memory.getUint32(address - NE2K_MEMORY_START, true);
                }
            }

            util.debug("NE2K.readChipMemoryDMA() :: Out-of-bounds chip memory read: " + util.hexify(address));
            return 0xFF;
        },

        readCR: function () {
            var ne2k = this;

            /*jshint bitwise: false */
            return (
                ((ne2k.cr.selectedPage.get() & 3) << 6) |
                ((ne2k.cr.remoteDMACommand.get() & 7) << 3) |
                (ne2k.cr.initiatePacketTransmission << 2) |
                (ne2k.cr.startRequested << 1) |
                (ne2k.cr.stopRequested)
            );
        },

        readPage0: function (offset, length) {
            /*jshint bitwise: false */
            var ne2k = this;

            if (length > 1) {
                throw new Exception("NE2K.readPage0() :: Length too long: " + util.hexify(length));
            }

            switch (offset) {
            case 0x1: // CLDA0
                return ne2k.localDMAAddress.get() & 0xFF;
            case 0x2: // CLDA1
                return (ne2k.localDMAAddress.get() >>> 8) & 0xFF;
            case 0x3: // BNRY
                return ne2k.boundaryPointer.get();
            case 0x4: // TSR
                return (
                    (ne2k.tsr.outOfWindowCollision << 7) |
                    (ne2k.tsr.noTransmissionCDHeartbeat << 6) |
                    (ne2k.tsr.fifoUnderrun << 5) |
                    (ne2k.tsr.carrierSenseLost << 4) |
                    (ne2k.tsr.transmissionAborted << 3) |
                    (ne2k.tsr.transmissionCollided << 2) |
                    (ne2k.tsr.transmissionCompleteWithoutError)
                );
            case 0x5: // NCR
                return ne2k.numberOfCollisions.get();
            case 0x6: // FIFO
                // NB: Reading FIFO is only valid in loopback mode
                return ne2k.fifo.get();
            case 0x7: // ISR
                return (
                    (ne2k.isr.resetStatus << 7) |
                    (ne2k.isr.remoteDMAComplete << 6) |
                    (ne2k.isr.counterOverflow << 5) |
                    (ne2k.isr.receiveBufferResourcesExhausted << 4) |
                    (ne2k.isr.packetTransmittedWithOneOrMoreErrors << 3) |
                    (ne2k.isr.packetReceivedWithOneOrMoreErrors << 2) |
                    (ne2k.isr.packetTransmittedWithNoErrors << 1) |
                    (ne2k.isr.packetReceivedWithNoErrors)
                );
            case 0x8: // CRDA0
                return ne2k.remoteDMAAddress.get() & 0xFF;
            case 0x9: // CRDA1
                return (ne2k.remoteDMAAddress.get() >>> 8) & 0xFF;
            case 0xa: // RTL8029ID0 (reserved)
                return 0xFF;
            case 0xb: // RTL8029ID1 (reserved)
                return 0xFF;
            case 0xc: // RSR
                return (
                    (ne2k.rsr.deferred << 7) |
                    (ne2k.rsr.disabled << 6) |
                    (ne2k.rsr.multiMatch << 5) |
                    (ne2k.rsr.missedPacketError << 4) |
                    (ne2k.rsr.fifoOverrun << 3) |
                    (ne2k.rsr.frameAlignmentError << 2) |
                    (ne2k.rsr.badCRCDetected << 1) |
                    (ne2k.rsr.receiveCompleteWithoutError)
                );
            case 0xd: // CNTR0
                return this.frameAlignmentErrorTally.get();
            case 0xe: // CNTR1
                return this.crcErrorTally.get();
            case 0xf: // CNTR2
                return this.missedPacketErrorTally.get();
            default:
                throw new Exception("NE2K.readPage0() :: Register out of range: " + util.hexify(offset));
            }
        },

        readPage1: function (offset, length) {
            /*jshint bitwise: false */
            var ne2k = this;

            if (length > 1) {
                throw new Exception("NE2K.readPage1() :: Length too long: " + util.hexify(length));
            }

            switch (offset) {
            case 0x1: // PAR0-5
            case 0x2:
            case 0x3:
            case 0x4:
            case 0x5:
            case 0x6:
                return ne2k.macAddress.getOctet(offset - 1);
            case 0x7: // CURR
                return ne2k.currentPage.get();
            case 0x8: // MAR0-7
            case 0x9:
            case 0xA:
            case 0xB:
            case 0xC:
            case 0xD:
            case 0xE:
            case 0xF:
                return ne2k.multicastHash[offset - 8];
            default:
                throw new Exception("NE2K.readPage1() :: Register out of range: " + util.hexify(offset));
            }
        },

        receiveFrame: function (buffer, offset, length) {
            /*jshint bitwise: false */
            var availablePages,
                endBytes,
                ethernetFrame,
                multicastIndex,
                ne2k = this,
                nextPage,
                packetHeader = new Uint8Array(4),
                requiredPages,
                startOffset,
                wrapped = false;

            // Ensure we are in a mode able to receive frames
            if (ne2k.stopRequested || ne2k.pageStart.get() === 0 || (!ne2k.dcr.loopbackSelect && ne2k.tcr.loopbackControl.get() !== 0)) {
                return;
            }

            // Work out how many 256-byte pages the frame would occupy
            // - Add 4 bytes for the header we will prepend to the packet in NE2K memory
            requiredPages = Math.ceil((length + 4) / 256);

            if (ne2k.currentPage.get() < ne2k.boundaryPointer.get()) {
                availablePages = ne2k.boundaryPointer.get() - ne2k.currentPage.get();
            } else {
                availablePages = (ne2k.pageStop.get() - ne2k.pageStart.get()) - (ne2k.currentPage.get() - ne2k.boundaryPointer.get());
                wrapped = true;
            }

            // Avoid getting into an overflow condition by not attempting partial receives
            if (availablePages < requiredPages) {
                // Just drop the frame
                return;
            }

            if (length < 60 && !ne2k.rcr.acceptUnder64ByteRunts) {
                util.debug("NE2K.receiveFrame() :: Rejected small frame with length " + util.hexify(length));
            }

            ethernetFrame = new EthernetFrame(buffer, offset, length);

            // Do hardware (MAC) address filtering if we're not in promiscuous mode
            if (!ne2k.rcr.promiscuousMode) {
                // Accept ethernet broadcast frames, unless...
                if (ethernetFrame.getDestinationMAC().isBroadcast()) {
                    // ... they have been disabled in hardware
                    if (!ne2k.rcr.acceptEthernetBroadcast) {
                        return;
                    }
                // Check for applicable multicast, unless...
                } else if (new Uint8Array(buffer, offset)[0] & 1) {
                    // ... it has been disabled in hardware
                    if (!ne2k.rcr.checkMulticastHashArray) {
                        return;
                    }

                    multicastIndex = ethernetFrame.getDestinationMAC().getMulticastIndex();

                    if (!(ne2k.multicastHash[multicastIndex >>> 3] & (1 << (multicastIndex & 0x7)))) {
                        return;
                    }
                // Otherwise we need an exact MAC address match for us to receive the frame
                } else if (ethernetFrame.getDestinationMAC().toHexString() !== ne2k.macAddress.toHexString()) {
                    return;
                }
            } else {
                util.debug("NE2K.receiveFrame() :: Promiscuous receive");
            }

            nextPage = ne2k.currentPage.get() + requiredPages;

            if (nextPage >= ne2k.pageStop.get()) {
                nextPage -= (ne2k.pageStop.get() - ne2k.pageStart.get());
            }

            // Build the packet header
            packetHeader[0] = 1; // Receive status
            if (new Uint8Array(buffer, offset)[0] & 1) {
                packetHeader[0] |= 0x20; // Add "multicast packet" status
            }
            packetHeader[1] = nextPage; // Pointer to next page
            packetHeader[2] = (length + 4) & 0xFF; // Low-byte of length
            packetHeader[3] = (length + 4) >>> 8; // High-byte of length

            // Copy packet header and payload into memory
            startOffset = ne2k.currentPage.get() * 256 - NE2K_MEMORY_START;

            // Packet fits into contiguous pages
            if ((nextPage > ne2k.currentPage.get()) || ((ne2k.currentPage.get() + requiredPages) === ne2k.pageStop.get())) {
                new Uint8Array(ne2k.memory.buffer, startOffset, 4).set(packetHeader);
                new Uint8Array(ne2k.memory.buffer, startOffset + 4, length).set(new Uint8Array(buffer, offset, length));
            } else {
                endBytes = (ne2k.pageStop.get() - ne2k.currentPage.get()) * 256;

                new Uint8Array(ne2k.memory.buffer, startOffset, 4).set(packetHeader);
                new Uint8Array(ne2k.memory.buffer, startOffset + 4, length).set(new Uint8Array(buffer, offset, endBytes - 4));

                startOffset = ne2k.pageStart.get() * 256 - NE2K_MEMORY_START;
                new Uint8Array(ne2k.memory.buffer, startOffset).set(new Uint8Array(buffer, offset + endBytes - 4, length - endBytes + 8));
            }

            ne2k.currentPage.set(nextPage);

            ne2k.rsr.receiveCompleteWithoutError = true;
            ne2k.rsr.multiMatch = !!(new Uint8Array(buffer, offset)[0] & 1);

            ne2k.isr.packetReceivedWithNoErrors = true;

            if (ne2k.imr.enablePacketReceiveInterrupt) {
                ne2k.raiseIRQ();
            }
        },

        reset: function () {
            var ne2k = this;

            function clearBuffer(buffer) {
                new Uint8Array(buffer).set(new Uint8Array(buffer.byteLength), 0);
            }

            ne2k.cr.initiatePacketTransmission = false;
            ne2k.cr.remoteDMACommand.set(4);
            ne2k.cr.selectedPage.clear();
            ne2k.cr.startRequested = false;
            ne2k.cr.stopRequested = true;

            ne2k.dcr.autoRemoveRXPackets = false;
            ne2k.dcr.endian = false;
            ne2k.dcr.fifoSize.clear();
            ne2k.dcr.longAddress = false;
            ne2k.dcr.loopbackSelect = false;
            ne2k.dcr.wordSize = false;

            ne2k.imr.enablePacketReceiveInterrupt = false;
            ne2k.imr.enablePacketTransmitInterrupt = false;
            ne2k.imr.enablePacketReceiveErrorInterrupt = false;
            ne2k.imr.enablePacketTransmitErrorInterrupt = false;
            ne2k.imr.enableOverwriteWarnInterrupt = false;
            ne2k.imr.enableCounterOverflowInterrupt = false;
            ne2k.imr.enableRemoteDMACompleteInterrupt = false;

            ne2k.isr.counterOverflow = false;
            ne2k.isr.packetReceivedWithNoErrors = false;
            ne2k.isr.packetReceivedWithOneOrMoreErrors = false;
            ne2k.isr.packetTransmittedWithNoErrors = false;
            ne2k.isr.packetTransmittedWithOneOrMoreErrors = false;
            ne2k.isr.receiveBufferResourcesExhausted = false;
            ne2k.isr.remoteDMAComplete = false;
            ne2k.isr.resetStatus = true;

            ne2k.rcr.acceptEthernetBroadcast = false;
            ne2k.rcr.acceptPacketsWithReceiveErrors = false;
            ne2k.rcr.acceptUnder64ByteRunts = false;
            ne2k.rcr.checkMulticastHashArray = false;
            ne2k.rcr.monitorOnly = false;
            ne2k.rcr.promiscuousMode = false;

            ne2k.rsr.badCRCDetected = false;
            ne2k.rsr.deferred = false;
            ne2k.rsr.disabled = false;
            ne2k.rsr.fifoOverrun = false;
            ne2k.rsr.frameAlignmentError = false;
            ne2k.rsr.missedPacketError = false;
            ne2k.rsr.multiMatch = false;
            ne2k.rsr.receiveCompleteWithoutError = false;

            ne2k.tcr.allowTransmissionDisableByExternalMCast = false;
            ne2k.tcr.collisionPriority = false;
            ne2k.tcr.crcDisable = false;
            ne2k.tcr.loopbackControl.clear();

            ne2k.tsr.carrierSenseLost = false;
            ne2k.tsr.fifoUnderrun = false;
            ne2k.tsr.noTransmissionCDHeartbeat = false;
            ne2k.tsr.outOfWindowCollision = false;
            ne2k.tsr.transmissionAborted = false;
            ne2k.tsr.transmissionCompleteWithoutError = false;
            ne2k.tsr.transmissionCollided = false;

            ne2k.boundaryPointer.clear();
            ne2k.bytesToTransmit.clear();
            ne2k.crcErrorTally.clear();
            ne2k.currentPage.clear();
            ne2k.fifo.clear();
            ne2k.frameAlignmentErrorTally.clear();
            ne2k.localDMAAddress.clear();
            // Clear memory
            clearBuffer(ne2k.memory.buffer);
            ne2k.missedPacketErrorTally.clear();
            ne2k.macAddress.setZero();
            clearBuffer(ne2k.multicastHash.buffer);
            ne2k.numberOfCollisions.clear();
            ne2k.pageStart.clear();
            ne2k.pageStop.clear();
            ne2k.remoteByteCount.clear();
            ne2k.remoteDMAAddress.clear();
            ne2k.remoteStartAddress.clear();
            ne2k.transmitPageStart.clear();

            return ne2k;
        },

        writeChipMemoryDMA: function (value, length) {
            var ne2k = this,
                address = ne2k.remoteDMAAddress.get();

            /*jshint bitwise: false */
            if (length === 2 && (address & 1)) {
                throw new Exception("NE2K.writeChipMemoryDMA() :: Unaligned chip memory word write");
            }

            if (address >= NE2K_MEMORY_START && address < NE2K_MEMORY_END) {
                if (length === 1) {
                    ne2k.memory.setUint8(address - NE2K_MEMORY_START, value);
                    return;
                }
                if (length === 2) {
                    ne2k.memory.setUint16(address - NE2K_MEMORY_START, value, true);
                    return;
                }
                if (length === 4) {
                    ne2k.memory.setUint32(address - NE2K_MEMORY_START, value, true);
                    return;
                }
            }

            util.debug("NE2K.writeChipMemoryDMA() :: Out-of-bounds chip memory write: " + util.hexify(address));
        },

        writeCR: function (value) {
            /*jshint bitwise: false */
            var ne2k = this;

            // Validate remote DMA
            if ((value & 0x38) === 0) {
                // DMA command 4 is a safe default
                value |= 0x20;
            }

            // Check for software reset
            if (value & 0x01) {
                ne2k.isr.resetStatus = true;
                ne2k.cr.stopRequested = true;
            } else {
                ne2k.cr.stopRequested = false;
            }

            ne2k.cr.remoteDMACommand.set((value & 0x38) >>> 3);

            // If start command issued, the RST bit in the ISR must be cleared
            if ((value & 0x02) && !ne2k.cr.startRequested) {
                ne2k.isr.resetStatus = false;
            }

            ne2k.cr.startRequested = !!(value & 0x02);
            ne2k.cr.selectedPage.set((value & 0xC0) >>> 6);

            // Check for send-packet command
            if (ne2k.cr.remoteDMACommand.get() === 3) {
                // Set up DMA read from receive ring
                ne2k.remoteDMAAddress.set(ne2k.boundaryPointer.get() * 256);
                ne2k.remoteStartAddress.set(ne2k.remoteDMAAddress.get());
                // Remember to read little-endian!
                ne2k.remoteByteCount.set(ne2k.memory.getUint16(ne2k.boundaryPointer.get() * 256 + 2 - NE2K_MEMORY_START, true));
            }

            // Check for start-transmission
            if (value & 0x04) {
                if (ne2k.tcr.loopbackControl.get()) {
                    if (ne2k.tcr.loopbackControl.get() !== 1) {
                        util.info("NE2K.writeCR() :: Loop mode " + ne2k.tcr.loopbackControl.get() + " not supported");
                    } else {
                        ne2k.receiveFrame(ne2k.memory, ne2k.transmitPageStart.get() * 256 - NE2K_MEMORY_START, ne2k.bytesToTransmit.get());
                    }
                } else {
                    if (ne2k.cr.stopRequested || !ne2k.cr.startRequested) {
                        // Allow for Solaris9 probe
                        if (ne2k.bytesToTransmit.get() === 0) {
                            return;
                        }

                        throw new Exception("NE2K.writeCR() :: Transmission start, but device is in reset");
                    }

                    if (ne2k.bytesToTransmit.get() === 0) {
                        throw new Exception("NE2K.writeCR() :: Transmission start, but no bytes to transmit");
                    }

                    ne2k.cr.initiatePacketTransmission = true;
                    ne2k.ethernetDevice.sendFrame(ne2k.memory.buffer, ne2k.transmitPageStart.get() * 256 - NE2K_MEMORY_START, ne2k.bytesToTransmit.get());

                    if (ne2k.transmitTimer.isActive()) {
                        util.problem("NE2K.writeCR() :: CR write, but transmit timer is still active");
                    }

                    /*
                     * Schedule a timer to trigger a tx-complete interrupt.
                     * The number of microseconds is the bit-time / 10.
                     * The bit-time is the preamble+SFD (64 bits), the
                     * inter-frame gap (96 bits), the CRC (4 bytes), and
                     * the number of bits in the frame (bytesToTransmit * 8).
                     */
                    ne2k.transmitTimer.triggerAfterMicroseconds((64 + 96 + 4 * 8 + ne2k.bytesToTransmit.get() * 8) / 10);
                }
            }

            /*
             * Linux probes for an interrupt by setting up a remote-DMA read
             * of 0 bytes with remote-DMA completion interrupts enabled.
             * Detect this here
             */
            if (ne2k.cr.remoteDMACommand.get() === 0x01 && ne2k.cr.startRequested && ne2k.remoteByteCount.get() === 0) {
                ne2k.isr.remoteDMAComplete = true;
                if (ne2k.imr.enableRemoteDMACompleteInterrupt) {
                    ne2k.raiseIRQ();
                }
            }
        },

        writePage0: function (offset, value, length) {
            /*jshint bitwise: false */
            var isrState,
                ne2k = this;

            // Break up word writes into bytes
            if (length === 2) {
                ne2k.writePage0(offset, value & 0xFF, 1);
                if (offset < 0xF) {
                    ne2k.writePage0(offset + 1, (value >>> 8) & 0xFF, 1);
                }

                return;
            }

            switch (offset) {
            case 0x1: // PSTART
                ne2k.pageStart.set(value);
                break;
            case 0x2: // PSTOP
                ne2k.pageStop.set(value);
                break;
            case 0x3: // BNRY
                ne2k.boundaryPointer.set(value);
                break;
            case 0x4: // TPSR
                ne2k.transmitPageStart.set(value);
                break;
            case 0x5: // TBCR0
                // Set low byte
                ne2k.bytesToTransmit.set((ne2k.bytesToTransmit.get() & 0xFF00) | (value & 0xFF));
                break;
            case 0x6: // TBCR1
                // Set high byte
                ne2k.bytesToTransmit.set((ne2k.bytesToTransmit.get() & 0x00FF) | ((value & 0xFF) << 8));
                break;
            case 0x7: // ISR
                // Clear RST bit as it is read-only
                value &= 0x7f;

                // Lower any flags that have their bit set in the value
                if (value & 0x01) {
                    ne2k.isr.packetReceivedWithNoErrors = false;
                }
                if (value & 0x02) {
                    ne2k.isr.packetTransmittedWithNoErrors = false;
                }
                if (value & 0x04) {
                    ne2k.isr.packetReceivedWithOneOrMoreErrors = false;
                }
                if (value & 0x08) {
                    ne2k.isr.packetTransmittedWithOneOrMoreErrors = false;
                }
                if (value & 0x10) {
                    ne2k.isr.receiveBufferResourcesExhausted = false;
                }
                if (value & 0x20) {
                    ne2k.isr.counterOverflow = false;
                }
                if (value & 0x40) {
                    ne2k.isr.remoteDMAComplete = false;
                }

                // Lower IRQ if none of the statuses are high
                if (!(
                    (ne2k.isr.remoteDMAComplete && ne2k.imr.enableRemoteDMACompleteInterrupt) ||
                    (ne2k.isr.counterOverflow && ne2k.imr.enableCounterOverflowInterrupt) ||
                    (ne2k.isr.receiveBufferResourcesExhausted && ne2k.imr.enableOverwriteWarnInterrupt) ||
                    (ne2k.isr.packetTransmittedWithOneOrMoreErrors && ne2k.imr.enablePacketTransmitErrorInterrupt) ||
                    (ne2k.isr.packetReceivedWithOneOrMoreErrors && ne2k.imr.enablePacketReceiveErrorInterrupt) ||
                    (ne2k.isr.packetTransmittedWithNoErrors && ne2k.imr.enablePacketTransmitInterrupt) ||
                    (ne2k.isr.packetReceivedWithNoErrors && ne2k.imr.enablePacketReceiveInterrupt)
                )) {
                    ne2k.lowerIRQ();
                }
                break;
            case 0x8: // RSAR0
                // Set low byte
                ne2k.remoteStartAddress.set((ne2k.remoteStartAddress.get() & 0xFF00) | (value & 0xFF));
                ne2k.remoteDMAAddress.set(ne2k.remoteStartAddress.get());
                break;
            case 0x9: // RSAR1
                // Set high byte
                ne2k.remoteStartAddress.set((ne2k.remoteStartAddress.get() & 0x00FF) | ((value & 0xFF) << 8));
                ne2k.remoteDMAAddress.set(ne2k.remoteStartAddress.get());
                break;
            case 0xA: // RBCR0
                // Set low byte
                ne2k.remoteByteCount.set((ne2k.remoteByteCount.get() & 0xFF00) | (value & 0xFF));
                break;
            case 0xB: // RBCR1
                // Set high byte
                ne2k.remoteByteCount.set((ne2k.remoteByteCount.get() & 0x00FF) | ((value & 0xFF) << 8));
                break;
            case 0xC: // RCR
                ne2k.rcr.acceptPacketsWithReceiveErrors = !!(value & 0x01);
                ne2k.rcr.acceptUnder64ByteRunts = !!(value & 0x02);
                ne2k.rcr.acceptEthernetBroadcast = !!(value & 0x04);
                ne2k.rcr.checkMulticastHashArray = !!(value & 0x08);
                ne2k.rcr.promiscuousMode = !!(value & 0x10);
                ne2k.rcr.monitorOnly = !!(value & 0x20);
                break;
            case 0xD: // TCR
                // Test Loop Mode (not supported)
                if (value & 0x06) {
                    ne2k.tcr.loopbackControl.set((value & 0x06) >>> 1);
                } else {
                    ne2k.tcr.loopbackControl.clear();
                }

                if (value & 0x01) {
                    throw new Exception("NE2K.writePage0() :: Inhibit-CRC mode not supported");
                }

                if (value & 0x08) {
                    throw new Exception("NE2K.writePage0() :: Auto-transmit disable not supported");
                }

                // Allow collision-offset to be set, but it is not used
                ne2k.tcr.collisionPriority = !!(value & 0x08);
                break;
            case 0xE: // DCR
                // Loopback mode is not support yet
                if (!(value & 0x08)) {
                    throw new Exception("NE2K.writePage0() :: DCR write - loopback mode selected but not supported");
                }

                ne2k.dcr.wordSize = !!(value & 0x01);
                ne2k.dcr.endian = !!(value & 0x02);
                ne2k.dcr.longAddress = !!(value & 0x04);
                ne2k.dcr.loopbackSelect = !!(value & 0x08);
                ne2k.dcr.autoRemoveRXPackets = !!(value & 0x10);
                ne2k.dcr.fifoSize.set((value & 0x50) >>> 5);
                break;
            case 0xF: // IMR
                ne2k.imr.enablePacketReceiveInterrupt = !!(value & 0x01);
                ne2k.imr.enablePacketTransmitInterrupt = !!(value & 0x02);
                ne2k.imr.enablePacketReceiveErrorInterrupt = !!(value & 0x04);
                ne2k.imr.enablePacketTransmitErrorInterrupt = !!(value & 0x08);
                ne2k.imr.enableOverwriteWarnInterrupt = !!(value & 0x10);
                ne2k.imr.enableCounterOverflowInterrupt = !!(value & 0x20);
                ne2k.imr.enableRemoteDMACompleteInterrupt = !!(value & 0x40);

                isrState = (
                    (ne2k.isr.remoteDMAComplete << 6) |
                    (ne2k.isr.counterOverflow << 5) |
                    (ne2k.isr.receiveBufferResourcesExhausted << 4) |
                    (ne2k.isr.packetTransmittedWithOneOrMoreErrors << 3) |
                    (ne2k.isr.packetReceivedWithOneOrMoreErrors << 2) |
                    (ne2k.isr.packetTransmittedWithNoErrors << 1) |
                    (ne2k.isr.packetReceivedWithNoErrors)
                );

                if (((value & isrState) & 0x7f) === 0) {
                    ne2k.lowerIRQ();
                } else {
                    ne2k.raiseIRQ();
                }
                break;
            default:
                throw new Exception("NE2K.writePage0() :: Register out of range: " + util.hexify(offset));
            }
        },

        writePage1: function (offset, value) {
            /*jshint bitwise: false */
            var ne2k = this;

            switch (offset) {
            case 0x1: // PAR0-5
            case 0x2:
            case 0x3:
            case 0x4:
            case 0x5:
            case 0x6:
                ne2k.macAddress.setOctet(offset - 1, value);
                if (offset === 6) {
                    util.info("NE2K.writePage1() :: MAC address set to " + ne2k.macAddress.toHexString());
                }
                break;
            case 0x7: // CURR
                ne2k.currentPage.set(value);
                break;
            case 0x8: // MAR0-7
            case 0x9:
            case 0xA:
            case 0xB:
            case 0xC:
            case 0xD:
            case 0xE:
            case 0xF:
                ne2k.multicastHash[offset - 8] = value;
                break;
            default:
                throw new Exception("NE2K.writePage1() :: Register out of range: " + util.hexify(offset));
            }
        }
    });

    return NE2K;
});


text:
/*
Name: 			Pages / Locked Screen - Examples
Written by: 	Okler Themes - (http://www.okler.net)
Theme Version: 	1.3.0
*/

(function($) {

	'use strict';

	var $document,
		idleTime;

	$document = $(document);

	$(function() {
		$.idleTimer( 10000 ); // ms

		$document.on( 'idle.idleTimer', function() {
			// if you don't want the modal
			// you can make a redirect here
			LockScreen.show();
		});
	});

}).apply( this, [ jQuery ]);

redpajama_set_name: RedPajamaGithub


text:
<?php

/**
 * Describes a single field with its validation rules.
 *
 * @param string $name Name of the field
 * @param Validator $validator Validator for which the field is instantiated
 */

namespace Validator;

class Field
{
	const RULE_BUILTIN	=	'builtin'
		, RULE_CLOSURE	=	'closure'
		, RULE_FUNCTION	=	'function'
		, RULE_PHP		=	'php';

	protected $_name;
	protected $_rules	=	[ ];
	protected $_validator;

	protected $_error;
	protected $_error_forced	=	false;

	public function __construct($name, \Validator $validator)
	{
		$this->_name		=	$name;
		$this->_validator	=	$validator;
	}

	/**
	 * Add a validation rule.
	 *
	 * @param mixed $rule Validation rule
	 */
	public function rule($rule)
	{
		$this->_rules[]	=	$rule;
	}

	/**
	 * Add multiple validation rules.
	 *
	 * @param array $rules Array of validation rules
	 */
	public function rules($rules)
	{
		$this->_rules	=	array_merge($this->_rules, $rules);
	}

	/**
	 * Run the validation rules for the field.
	 *
	 * @param array $values List of values (only the field will be searched for and used)
	 * @return bool Whether the field is correct or not
	 */
	public function run(array $values, $custom_rule = null)
	{
		if($this->_error && $this->_error_forced)
		{
			if($custom_rule)
				$error	=	true;
		}
		else
		{
			$error			=	false;
			$this->_error	=	null;

			$value		=	self::getValue($this->_name, $values);

			$rules	=	$custom_rule ? [ $custom_rule ] : $this->_rules;

			foreach($rules as $rule)
			{
				$rule_type		=	self::_getRuleType($rule);
				$display_error	=	$rule_type === self::RULE_BUILTIN ? $rule->displayError() : true;

				if($value !== null || ($rule_type === self::RULE_BUILTIN && ! empty($rule->accept_missing_value)))
				{
					try
					{
						switch($rule_type)
						{
							case self::RULE_BUILTIN:
								$rule->check($value, $values, $this->_validator);
							break;

							case self::RULE_CLOSURE:
								$rule($value, $values, $this->_validator);
							break;

							case self::RULE_FUNCTION:
								call_user_func($rule, $value, $values, $this->_validator);
							break;

							case self::RULE_PHP:
								if( ! call_user_func($rule, $value))
									throw new \Exception(\Validator\i18n::get('error_generic'));
							break;
						}

					}
					catch(\Exception $e)
					{
						if($display_error)
						{
							if($custom_rule)
								$error	=	true;
							else
								$this->error($e->getMessage(), $display_error);
						}

						break;
					}
				}
			}
		}

		return $custom_rule ? ! $error : $this->_error === null;
	}

	/**
	 * Indicates if the fields passed the validation or not.
	 *
	 * @param bool $displayable Whether to display non-displayable messages (for example, `DependsOn` error)
	 * @return bool
	 */
	public function hasError($displayable = false)
	{
		return $this->_error !== null
			&& ( ! $displayable || $this->_error->isDisplayable());
	}

	/**
	 * Return the first error for the field.
	 *
	 * @return \Validator\FieldError
	 */
	public function getError()
	{
		return $this->_error;
	}

	/**
	 * Push an error to the field
	 *
	 * @param string $message Error message
	 * @param bool $display_error Whether the error is displayable or not
	 */
	public function error($message, $display_error = true, $forced = false)
	{
		$this->_error			=	new \Validator\FieldError($message, $display_error);
		$this->_error_forced	=	$forced;

		$this->_validator->setHasError(true);
	}

	/**
	 * Clear the error (if any).
	 */
	public function clearError()
	{
		$this->_error			=	null;
		$this->_error_forced	=	false;
	}

	/**
	 * Check if a key exists in a form and returns it.
	 *
	 * @param string $key Key of the field to be searched
	 * @param array $values Array of values to search for the key within
	 * @return mixed Value
	 */
	public static function getValue($key, $values)
	{
		if(array_key_exists($key, $values) && $values[$key] !== null)
		{
			if($values[$key] === 'null')
				$ret	=	'';
			else
				$ret	=	is_string($values[$key]) ? trim($values[$key]) : $values[$key];
		}
		else
		{
			$ret	=	null;
		}

		return $ret;
	}

	/**
	 * Detect the type of a rule.
	 *
	 * @param mixed $rule
	 */
	protected static function _getRuleType($rule)
	{
		if(is_string($rule))
		{
			if(function_exists($rule))
				$ret	=	self::RULE_PHP;
			else
				$ret	=	self::RULE_FUNCTION;
		}
		else
		{
			if($rule instanceof \Validator\Validation_Abstract)
				$ret	=	self::RULE_BUILTIN;
			else if(is_callable($rule))
				$ret	=	self::RULE_CLOSURE;
			else
				throw new \Exception('Wrong rule type. Unable to run the validation.');
		}

		return $ret;
	}
}


text:
export default function() {
  let allCallbacks = [];
  let prevTime = 0;

  const now = () => {
    return prevTime;
  };

  const raf = cb => {
    allCallbacks.push(cb);
  };

  const defaultTimeInterval = 1000 / 60;
  const step = (ms = defaultTimeInterval) => {
    const allCallbacksBefore = allCallbacks;
    allCallbacks = [];

    allCallbacksBefore.forEach(cb => cb());

    prevTime += ms;
  };

  const manySteps = (num = 1) => {
    for (let i = 0; i < num; i++) {
      step();
    }
  };

  return {now, raf, step, manySteps};
}


text:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    android:padding="5dp" >

    <RelativeLayout
        android:id="@+id/header"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:id="@+id/author"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:singleLine="true"
            android:textColor="#1526ff"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/datetime"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:singleLine="true"
            android:textColor="#000000"
            android:textSize="14sp" />
    </RelativeLayout>

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/header"
        android:padding="5dp" >

        <Button
            android:id="@+id/image_button"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Show Image"
            android:textColor="#000000" />

        <RelativeLayout
            android:id="@+id/wrapper"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/image_button"
            android:background="#bbbbbb" >

            <ImageView
                android:id="@+id/image"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:adjustViewBounds="true"
                android:background="@drawable/grumpy_cat2" />
        </RelativeLayout>

        <TextView
            android:id="@+id/content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/wrapper"
            android:textColor="#000000"
            android:textSize="18sp" />
    </RelativeLayout>

</RelativeLayout>


text:
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;

/**
 * @author Oleg Cherednik
 * @since 04.08.2018
 */
public class Solution {

    static String isValid(String s) {
        int[] arr = new int[26];

        for (int i = 0; i < s.length(); i++)
            arr[s.charAt(i) - 'a']++;

        Map<Integer, Integer> map = new TreeMap<>(Comparator.reverseOrder());

        for (int count : arr)
            if (count > 0)
                map.compute(count, (key, value) -> value != null ? value + 1 : 1);

        if (map.size() == 1)
            return "YES";
        if (map.size() > 2)
            return "NO";

        Iterator<Integer> it = map.keySet().iterator();
        int key1 = it.next();
        int key2 = it.next();
        return key1 - 1 == key2 && map.get(key1) == 1 || map.get(key2) == 1 ? "YES" : "NO";
    }

    private static final Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        String s = scanner.nextLine();

        String result = isValid(s);

        System.out.println(result);

        scanner.close();
    }
}


text:
<?php

class Amasty_Customerattr_Block_Adminhtml_Customer_Relation_Edit extends Mage_Adminhtml_Block_Widget_Form_Container
{
    public function __construct()
    {
        $this->_blockGroup = 'amcustomerattr';
        $this->_objectId = 'attribute_id';
        $this->_controller = 'adminhtml_customer_relation';

		$this->_addButton('add_new_relation', array(
                'label'     => Mage::helper('amcustomerattr')->__('New Relation'),
                'onclick'   => 'newRelation()',
                'class' => 'add'
            ), 0, 2);

		$this->_addButton('add_new_attribute', array(
                'label'     => Mage::helper('amcustomerattr')->__('New Attribute'),
                'onclick'   => 'newAttribute()',
                'class' => 'add'
            ), 0, 1);

		$this->_updateButton('save', 'label', Mage::helper('amcustomerattr')->__('Save Relation'), 0, 3);

		$this->_formScripts[] = " function newAttribute(){ setLocation('" . $this->getUrl('*/adminhtml_manage/new'). "');} ";
		$this->_formScripts[] = " function newRelation(){ setLocation('" . $this->getUrl('*/adminhtml_relation/new'). "');} ";

        $this->_formScripts[] = "
            function amcustomerattr_load_attr_options()
            {

            	new Ajax.Request('" . $this->getUrl('*/*/options', array('isAjax'=>true)) ."?attributeId=' + $('attribute_id').value, {
	                parameters: {
    				},
	                onSuccess: function(transport) {
						var data = transport.responseText;
						if(!data.isJSON()) {
							return;
						}
						data = data.evalJSON();

	                    $('option_id').update(data.options);
	                    $('dependend_attribute_id').update(data.dependent);
	                },
	                onFailure: function() {

	                }

	            });
            }
            obj = document.getElementById('attribute_id');
            Event.observe(obj,'change', amcustomerattr_load_attr_options);

        ";

        parent::__construct();
        $this->_removeButton('reset');
    }

    public function getHeaderText()
    {
        if (Mage::registry('entity_relation')->getId()) {
            $frontendLabel = Mage::registry('entity_relation')->getName();
            if (is_array($frontendLabel)) {
                $frontendLabel = $frontendLabel[0];
            }
            return Mage::helper('catalog')->__('Edit Relation "%s"', $this->htmlEscape($frontendLabel));
        }
        else {
            return Mage::helper('catalog')->__('New Relation');
        }
    }

    public function getValidationUrl()
    {
        return $this->getUrl('*/*/validate', array('_current'=>true));
    }

    public function getSaveUrl()
    {
        return $this->getUrl('*/*/save', array('_current'=>true, 'back'=>null));
    }
}


text:
Docker deployment
=================

There's a [Docker][docker] environment provided for reliable production
deployment of horta-hell. This document describes how to deploy horta-hell into
a local Docker container.

If you want to publish your own Docker package, [consult the
documentation][docs/docker-publish].

Prerequisites
-------------

The only prerequisite is [Docker][docker] itself.

Building the image
------------------

First, build the horta-hell according to [the build instructions][manual/build]
its manual and copy the resulting `horta-hell.jar` into `src/docker` directory.
After that, issue the command

```console
$ docker build -t=codingteam/horta-hell .
```

Configuring horta-hell
----------------------

You should mount the volume containing `horta.properties` to the `/data`
mountpoint in the container.

Please take a look at the [documentation][manual] to know more about the
configuration files. There is an example file bundled with horta-hell.

⚠ Please use only UNIX line endings (plain `\n`) in the `horta.properties`!

It's recommended to store the database on the same `/data` volume.

Running the container
---------------------

Here's an example script for running the container.

```console
$ cd horta-foundation
$ cp -f /path/to/build/horta-hell-assembly.jar ./horta-hell.jar
$ docker build -t=codingteam/horta-hell .
$ docker stop horta-hell # in case it already exists
$ docker rm horta-hell
$ docker run -d --name horta-hell -v /path/to/local/horta/configuration/directory:/data codingteam/horta-hell
```

Updating the packages
---------------------

The recommended way of upgrading is to [rebuild and recreate the whole
container][so-docker-upgrade]. Make sure your data is stored on an external data
volume, so it's fully preserved on container rebuild!

[docs/docker-publish]: How-to-publish-Docker-image.md
[manual]: ../README.md
[manual/build]: ../README.md#building

[docker]: https://www.docker.com/
[so-docker-upgrade]: http://stackoverflow.com/questions/26734402


text:
Data-Channel Group Chat
===========

This is a complete Group Chat app based on Data-Channel to send messages.
This example is built on the top of Bistri WebRTC API.


text:
package org.apache.camel.component.kubernetes.cluster.utils;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.kubernetes.api.model.PodListBuilder;
import io.fabric8.kubernetes.client.server.mock.KubernetesMockServer;
import io.fabric8.mockwebserver.utils.ResponseProvider;
import okhttp3.Headers;
import okhttp3.mockwebserver.RecordedRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A Test server to interact with Kubernetes for locking on a ConfigMap.
 */
public class LockTestServer extends KubernetesMockServer {

    private static final Logger LOG = LoggerFactory.getLogger(LockTestServer.class);

    private boolean refuseRequests;

    private Long delayRequests;

    private Set<String> pods;

    public LockTestServer(ConfigMapLockSimulator lockSimulator) {
        this(lockSimulator, Collections.emptySet());
    }

    public LockTestServer(ConfigMapLockSimulator lockSimulator, Collection<String> initialPods) {

        this.pods = new TreeSet<>(initialPods);

        expect().get().withPath("/api/v1/namespaces/test/configmaps/" + lockSimulator.getConfigMapName()).andReply(new ResponseProvider<Object>() {
            ThreadLocal<Integer> responseCode = new ThreadLocal<>();

            private Headers headers = new Headers.Builder().build();

            @Override
            public int getStatusCode() {
                return responseCode.get();
            }

            @Override
            public Object getBody(RecordedRequest recordedRequest) {
                delayIfNecessary();
                if (refuseRequests) {
                    responseCode.set(500);
                    return "";
                }

                ConfigMap map = lockSimulator.getConfigMap();
                if (map != null) {
                    responseCode.set(200);
                    return map;
                } else {
                    responseCode.set(404);
                    return "";
                }
            }

            @Override
            public Headers getHeaders() {
                return headers;
            }

            @Override
            public void setHeaders(Headers headers) {
                this.headers = headers;
            }
        }).always();

        expect().post().withPath("/api/v1/namespaces/test/configmaps").andReply(new ResponseProvider<Object>() {
            ThreadLocal<Integer> responseCode = new ThreadLocal<>();

            private Headers headers = new Headers.Builder().build();

            @Override
            public int getStatusCode() {
                return responseCode.get();
            }

            @Override
            public Object getBody(RecordedRequest recordedRequest) {
                delayIfNecessary();
                if (refuseRequests) {
                    responseCode.set(500);
                    return "";
                }

                ConfigMap map = convert(recordedRequest);
                if (map == null || map.getMetadata() == null || !lockSimulator.getConfigMapName().equals(map.getMetadata().getName())) {
                    throw new IllegalArgumentException("Illegal configMap received");
                }

                boolean done = lockSimulator.setConfigMap(map, true);
                if (done) {
                    responseCode.set(201);
                    return lockSimulator.getConfigMap();
                } else {
                    responseCode.set(500);
                    return "";
                }
            }

            @Override
            public Headers getHeaders() {
                return headers;
            }

            @Override
            public void setHeaders(Headers headers) {
                this.headers = headers;
            }
        }).always();

        expect().put().withPath("/api/v1/namespaces/test/configmaps/" + lockSimulator.getConfigMapName()).andReply(new ResponseProvider<Object>() {
            ThreadLocal<Integer> responseCode = new ThreadLocal<>();

            private Headers headers = new Headers.Builder().build();

            @Override
            public int getStatusCode() {
                return responseCode.get();
            }

            @Override
            public Object getBody(RecordedRequest recordedRequest) {
                delayIfNecessary();
                if (refuseRequests) {
                    responseCode.set(500);
                    return "";
                }

                ConfigMap map = convert(recordedRequest);

                boolean done = lockSimulator.setConfigMap(map, false);
                if (done) {
                    responseCode.set(200);
                    return lockSimulator.getConfigMap();
                } else {
                    responseCode.set(409);
                    return "";
                }
            }

            @Override
            public Headers getHeaders() {
                return headers;
            }

            @Override
            public void setHeaders(Headers headers) {
                this.headers = headers;
            }
        }).always();

        // Other resources
        expect().get().withPath("/api/v1/namespaces/test/pods")
            .andReply(200,
            request -> new PodListBuilder().withNewMetadata().withResourceVersion("1").and().withItems(getCurrentPods()
            .stream().map(name -> new PodBuilder().withNewMetadata().withName(name).and().build()).collect(Collectors.toList())).build())
            .always();
    }

    public boolean isRefuseRequests() {
        return refuseRequests;
    }

    public void setRefuseRequests(boolean refuseRequests) {
        this.refuseRequests = refuseRequests;
    }

    public synchronized Collection<String> getCurrentPods() {
        return new TreeSet<>(this.pods);
    }

    public synchronized void removePod(String pod) {
        this.pods.remove(pod);
    }

    public synchronized void addPod(String pod) {
        this.pods.add(pod);
    }

    public Long getDelayRequests() {
        return delayRequests;
    }

    public void setDelayRequests(Long delayRequests) {
        this.delayRequests = delayRequests;
    }

    private void delayIfNecessary() {
        if (delayRequests != null) {
            try {
                Thread.sleep(delayRequests);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private ConfigMap convert(RecordedRequest request) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            return mapper.readValue(request.getBody().readByteArray(), ConfigMap.class);
        } catch (IOException e) {
            throw new IllegalArgumentException("Erroneous data", e);
        }
    }

}


text:
title: UPnP Device Architecture
date: 2013-01-25 14:55:26.000000000 +05:30
published: true
categories:
 - Articles
 - Tutorial
tags:
 - UPNP
 - Networking

excerpt: "An article that provides an in-depth understanding about the UPNP protocol and its foundational technologies like HTTP, XML, SOAP etc"
---

{% include base_path %}

This article has been migrated from my [original post](https://embeddedinn.wordpress.com/tutorials/upnp-device-architecture/){:target="_blank"}  at [embeddedinn.wordpress.com](http://embeddedinn.wordpress.com){:target="_blank"}.
{: .notice--info}

## 1. Motivation For UPnP

{% include toc title="Table of contents" icon="file-text" %}

USB provides a universal solution to the PC plug and play problem. It has minimal configurations to be done by the user, user (generally) doesn't have to reboot or make configuration changes are to add a new device. At the same time, the USB architecture is is highly hierarchical .

Currently, numerous pervasive and cross platform technologies like (TCP/IP, HTTP, XML, UDP etc) are available. So , the question is,  why not integrate them all to get a "Foundation of the Connected Home"

## 2. Introduction

UPnP targets at universal digital, open home networking platform that requires

-    Zero configuration
-    Reuse existing technologies so that we don't re-invent the wheel and  support from standard systems that already has most of these technology stacks in place.

UPnP was introduced by Microsoft Corporation at the Consumer Electronics Show in January of 1999. It is currently maintained by the [UPnP forum](http://openconnectivity.org/upnp){:target="_blank"}.

## 3. Key Features

Major features provided by UPnP are

- Device connectivity
    Devices can join and leave the UPnP network transparently, advertise their services, discover other devices and services, send events, and control other devices.
- Ad-Hoc networking
    No dedicated network infrastructure have to be out in place for enabeling UPnP home network. Networks are configured on the fly without manual intervention. thus, it is a "Zero configuration network"
- Standard based Architecture
    All foundation technologies used in UPnP are  existing and proven. Thus, realizing UPnP in any platform is extremely simple. This also reduces the time-to-market.
- Platform and medium independence
    UPnP can be implemented over any medium for which an IP stack is available. Thus, UPnP can be used to aggregate devices connected through phone lines, power lines, RF , Ethernet, WiFi (ya, it is RF again:) )  etc
- Programmatic and manual device control
    Devices in the UPnP network can be controlled programatically as well as manually

## 4. Building Blocks

Basic abstractions of the UPnP device architecture includes 3 major components . They are

-    Devices
-    Services
-    Control points

Service is a unit of functionality implemented by a device. there can be  0 or more services present in a device.

Specifics about service implementations are defined by a separate UPnP forum working committee and are not part of the device architecture.

A service description will define "actions" ,input and output parameters , return value etc and will include mandatory device services. For example, an audio rendering device, such as a CD player, might have a service that provides the ability to play, stop, and pause audio content.

A "Control point" invokes the services provided by a device. They can control the device and request updates on state change of a service.

We will visit each of  these topics in depth in later sections. However, first we need to get an understanding of the underlying technologies used in UPnP.

## 5.Groundwork

In this section, we will try and get a quick glance of the underlying technologies used in UPnP. This include

This section introduces

-    URI
-    IP Multicast
-    HTTP
-    HTTP over UDP
-    XML

### 5.1 Uniform Resource Identifier

The world wide web stores information (web pages, images, data, video, songs etc) in the form of resources. A URI is a compact string used to identify a resource. URI folloes syntax specified in rfc2396. URI is further classified into URL and URN

A Uniform Resource Locator (URL)  identifies a resource by location rather than by name or some other identifier. Thus the content in this location can keep changeing. A good illustration of this is the arbitrary URL – [http://example.com/quote-for-the-day](http://example.com/quote-for-the-day){:target="_blank"}. Each day, the quote in this page changes but the URL remains the same and points to the same internet resource that generates the quote for the day.

A Uniform resource names (URN) is a unique and persistent identifier of a resource. It cannot be reused even after the resource cease to exist

Note: [http://example.com](http://example.com){:target="_blank"} domain is established by IANA to be used for illustrative examples in documents.

### 5.2 IP Multicast

A sender sends a multicast message to a multicst address (<IPAddress>:<port>) and all the members of this  "host group" receives data from the sender. Any device interested in listening to multicasts join the group with an ICMP message and the router ensures that the device receives the message. Sender need not be a member of this group.

Multicast addresses are class D internet addresses. i.e range of addresses from `224.0.0.0` to `239.255.255.255` (with reservations)

UPnP control points and devices use the multicast address `239.255.255.250`, and port 1900.Any source can send data to all UPnP devices and control points on a local network with this address. Thus UPnP network requires support of multicast capable IP routers.

Reach of multicast is controlled by using the Time To Live (TTL) field of IPv4 headers.TTL is decremented by 1 for each hop (router). A packet is transmitted only if TTL > 0.

Reach of multicast can also be restricted with administrative scope of IP multicast. `239.0.0.0` to `239.255.255.255` is administratively scoped IPv4 multicasts address space. (Termed IPv4 Local Scope). Network administrators partition this space and limit multicast range. Generally `239.255.255.250` falls within the IPv4 Local Scope and is the smallest of the administrative scopes.

### 5.3 HTTP

#### 5.3.1 HTTP 1.0

Hyper Test Transfer Protocol 1.0 is a simple request-response protocol. The current HTTP 1.1 is built on top of this protocol. The major features of this protocol are discussed below.

In HTTP 1.0, connection is closed by server after each request. This makes it a stateless protocol. States are stored in "cookies" and are presented to the server with each request.

##### Request structure:

The initial line of the request specifies the  `METHOD` `(GET,PUT,POST etc)`. This is followed by more `header` lines specifying variables and values and an optional message body (file, query data etc). Below are some of the components of an HTTP request-response.

1. Initial request line:

    Format: `<method name> <request path> <http version>`

    `Eg: GET /path/to/file/index.html HTTP/1.0`

2. Header lines:

    Provides information about request, response or object sent in message body.Format is

    `header-name: Value`

3. Response code:

    Response code will indicate outcome of action

    ```
    HTTP/1.0 200 OK
    or
    HTTP/1.0 404 Not Found
    ```

4. Message body:

    In response, this is used to return requested resource to client or explanation text of an error

    An HTTP 1.0 transaction to  retrieve file at URL `http://www.example.com/path1/file1.html` has the following steps:

    First open a socket at `http://www.example.com` at port `80` (default http port)
    Send the following string through the socket

```
    GET /path1/file1.html HTTP/1.0
    From : someuser@example.com
    User-Agent : HTTPTool/1.0
    (blank line)
```

   Server responds with something like

```
    HTTP/1.0 200 OK
    Date: Fri, 20 Nov 2002 23:59:59 GMT
    Content-Type: text/html
    Content-Length: 1354
    <html>
    <body>
    <h1>Sample Header Text</h1>
    (more file contents)
    .
    .
    .
    </body>
    </html>
```

#### 5.3.2 HTTP 1.1

The major improvements brought in by HTTP 1.1 are discussed here.

HTTP 1.1 supports multiple transactions over persistent connection by default. Requests are sent in a pipelined queue.This is applicable to the responses also. Once the transactions are done, the client uses "connection: close" header to denote end of connection. It can also be used by server to denote that connection is being terminated.

`HTTP/1.1 100 Continue` is used in slow connections to indicate data is on the way :smile:

HTTP 1.1 has support for data caching. All responses contain a GMT time stamp with the `Date:`  header. `If-Modified-Since` or `If-Unmodified-Since` headers can be used to request newer version of the resource.

HTTP 1.1 Chunked encoding allows response to be sent even before total length is known. This mechanism uses uses the `Transfer-Encoding` header set to `chunked`

Sample of chunked encoding transfer

```
HTTP/1.1 200 OK
Date: Fri, Dec 2002 23:59:59 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
1a: ignore-stuff-here                             |size of chunk in hex
abcdefghijklmnopqrstuvwxyz                        |chunk data
10                                                |size of chunk in hex
1234567890abcdef                                  |chunk data
0                                                 |0 to denote end of chunks
some-footer: some value
another-footer: another value
(blank line)
```

The footers should be considered as if they were sent before the data

HTTP 1.1 allows multiple domains to be served from same ip address. This is called multi-homing and is facilitated by the `HOST` header. eg

```
GET /path/file.html HTTP/1.1
Host: http://www.host1.com
(blank line)
```

HTTP 1.1 also accepts absolute URL requests like `GET http://www.intoast.com/path/file.html HTTP/1.2`

Summary of HTTP 1.1 server requirements are:

-    Host header with each request
-    Accept response with chunked data
-    Support persistent connections or use Connection: close with each request
-    Handle 10 continue response (by ignoring it)

Summary of HTTP 1.1 client requirements are:

-    Accept absolute URL
-    Accept request with chunked data
-    Include Date: header in each response

#### 5.3.3 HTTP Extension framework

HTTP provides a mechanism to address private headers and requests by extending a single HTTP message. This feature allows to defines few HTTP header fields to denote extensions.

### 5.4 HTTP over UDP

UPnP Exploits the connection less model of UDP and uses it primarily for sending HTTP multicast messages.

-    HTTPMU for multicast
-    HTTPU for UDP unicast

UPnP introduces 3 new headers to resolve issues arising from HTTP over UDP.

1. MX request header

    To avoid chances of large number of responses coming back to the sender (Host) at the same time, HTTP over UDP uses a new header which denotes the number of seconds the multicast UDP HTTP resource can wait before it can send a response initiated by a multicast request.

    The resource generates a random number between 0 and MX and waits for that much time to send response. Thus, if multiple responses are there, they will be spread out between 0 and MX.

2. Sequence (S) general header

    The Sequence header is used to associate a unique URI with a request-response pair. It is Generated at request time and uses the same URI used in response

3. Alternate (AL) general header

    This header is used for multiple "Location:"  header transmission

Request-URI in case of MulticastHTTP means the recipient and not the requested resource.  Thus the "*" request URI means, send to all.
`Eg: M-SEARCH "*" HTTP/1.1`

### 5.5 Extensible Markup Language (XML)

XML is a documentation language used to specify structure of data and how various elements relate. An XML document contains markup and character data. Markup gives the document its structure while character data is the actual content of the document. An XML document type definition (DTD) or XML schema (template) taht specifies teh constraints of data in teh document.

A typical XML document will have a prologue, root and misc part.

The Prologue contains XML declaration, processing instruction, comments, whitespace and a document type declaration. A sample declatation will look like:
```
<?xml version="1.0″ encoding="UTF-8″ standalone="no"?>
```

Here `standalone` means no external dependencies

An XML DOM or document object model can be considered as a A programmatic view of the xml document as trees and nodes. DOM APIs are used to access and manipulate XML docs.

![UPNP dom]({{ base_path }}/images/posts/upnp/upnp_dom.jpg){: .align-center}

## 6. UPnP Protocol Stack

![UPNP stack]({{ base_path }}/images/posts/upnp/upnp_stack.jpg){: .align-center}

UPnP protocol stack

The figure shows an IP based protocol stack . (even though UPnP can be implemented over any medium/base protocol supporting socket based communication). Further discussion assumes an IP based protocol stack.

Basic abstractions of the UPnP device architecture includes three major components.

-    Devices,
-    Services
-    Control points

A physical device may have more than one root device. A root device can have multiple embedded devices. Thus UPnP provides a very flexible logical arrangement.

A device will have a state table that maintains state variables. All operations on devices are based on the states of these state variables.

There are 6 phases of operation for any UPnP device. they are

1.    Addressing
2.    Discovery
3.    Description
4.    Control
5.    Eventing
6.    Presentation

first we will have a breif description each phase. We will visit each in detail later

### 7.1 Addressing

A UPnP device aquires an address using either DHCP or AutoIP as soon as it joins the network. This enables Adhoc , Zero Configuration networking.

### 7.2 Discovery

As soon as a UPnP device gets an address, it searches for presence of other UPnP services in the network. It also announces presence to other devices present in the network. This is done using Simple Service Discovery Protocol (SSDP) over HTTPMU

### 7.3 Description

Once a device identifies other devices available in the network, it obtains service details of required devices in the network. The device should also provide its capability details to other devices requesting it.

### 7.4 Control

A Control point can  invoke the functionality provided by a service via SOAP messages. Simple Object Access Protocol (SOAP) is a Microsoft technology used for cross platform RPCs. Once a device service receives a message, it has to act upon it

### 7.5 Eventing

UPnP follows a publisher-subscriber model to notify monitoring/interested control points about change in the state variable. General Event Notification Architecture (GENA) is used for event notifications.

### 7.6 Presentation

UPnP devices also provide a browser based, manual interface. It is HTML based and the presentation URL is part of device descriptor document (DDD) that is provided as part of the description process.

Now, let us visit each of the stages in detail.

## 8. Addressing

DHCP allows two modes of Addressing

-   DHCP – which requires dedicated infrastructure (a DHCP server)
-   Auto IP  – a true AdHoc mechanism with lots of limitations on network reach

### 8.1. Dynamic Host Control Protocol (DHCP)

For DHCP, a server sits and allocates IP addresses to all newly arriving devices. It relies on UDP based transport and the server receive requests on port 67. The response is posted to the hosts on port 68.

DHCP alows three allocation modes

- Automatic – where the IP address is allocated from a pool of addresses upon request by a client
- Manual – where the admin configures the IP of each host and the DHCP server conveys it to hosts on request
- Dynamic – where the DHCP server "Leases" an IP to the host for a limited period of time after which the device has to renew the IP.

![UPNP dhcpFlow]({{ base_path }}/images/posts/upnp/upnp_dhcpFlow.jpg){: .align-center}

Basic DHCP flow

### 8.2 AutoIP

First and foremost, AutoIP is Not a DHCP replacement. Rather, it is a temporary solution when the DHCP server is down. AutoIP address assignment happens in two major steps.

1. IP Selection

    The device selects a candidate IP address within the non-routable address range (`169.254/16`) with default class B subnet mask `255.255.0.0`. The first and last 256 addresses in this range are reserved and MUST NOT be used.

    > The Non-routable address range (`169.254/16`) do not cross network gateways and are known as `LINKLOCAL` net range of IPs

    The selection algorithm is implementation dependent and is not defined by AutoIP. However, it is recommended to use device's or control point's Ethernet hardware MAC address as seed for randomization.

2. ARP

    Send out an Address Resolution Protocol (ARP) probe for the chosen address. If no one responds, then it is yours :smile:

    ARP is generally used to determine the MAC address of an IP address holder. Here it is used in an unusual way. Recommended probing pattern is four times at two-second intervals.

![UPNP autoIPFlow]({{ base_path }}/images/posts/upnp/upnp_autoip.jpg){: .align-center}

AutoIP sequence

AutoIP addressed devices will not be visible beyond the local network segment. IP packets whose source or destination addresses are in the `169.254/16` range will not be sent to any router for forwarding. In case this has to be by-passed, senders must ARP for the destination address and then send the packets directly to the destination on the same link. Devices and control points MAY assume that all `169.254/16` destination addresses are on-link and directly reachable.

### 8.3 UPnP device Addressing

Steps in UPnP Device Addressing are as follows:

1. If DHCP is available, get it from there.
2. Else, use Auto-IP to get the IP
3. If DHCP becomes available afterwards, get IP from there

Suggested recheck period for DHCP server availability is 5 minutes. However, the current IP address should be relinquished only after the current connections are over.

Once addressed, do a DNS registration if required. DNS addresses can be preconfigured or obtained from DHCP server.

## 9. Discovery

Once addressed, a device is ready to provide services to other devices in the network and control point, can start utilizing the available services. Discovery allows control points to search for devices and services matching a search criteria.

Devices advertise and inform. Control points discover and select. All this is done using simple service discovery protocol (ssdp)

### 9.1. Simple service discovery protocol (SSDP)

SSDP is a protocol for small networks that runs over HTTPMU. It facilitates a mesh model where there is no central store of information . However, it gets ugly as number increase.

UPnP adds two concepts to generic SSDP. They are

-    Service type
-    Unique Service Name (USN)

USN is a URI to identify functionality of a resource. It is a 128 bit Universally Unique Identifier (UUID). Its generated based on the host network address, a timestamp and a randomly generated component. (In linux based systems the uuidgen utility can be used to generate a uuid.)

    service type: "printer"
    USN: "uuid: 2fac1234-31f8-11b4-a222-08002b34c003"

There are two types of SSDP requests.

- Discovery request:
    Sent by a device to know the devices that have come in before itself on a per service basis.

- Presence announcement:
    Sent only at the time of joining the network and leaving the network

As a result of this model, neither server nor client will have to send out steady stream of messages

#### 9.1.1. SSDP Discovery request

![UPNP ssdp header]({{ base_path }}/images/posts/upnp/ssdp_discover.jpg){: .align-center}

SSDP Device Discovery header template

The headers in ssdp are

-    **M-SEARCH**: Search request broadcast method
-    **ST**      : Header to specify search target
-    **MX**      : Response delay limit to avoid cluttering as described in HTTP over UDP
-    **MAN**     : Always set to ssdp:discover

SSDP supports very simple searches without logical expressions, name-value pairs etc. Legal search type (ST) values are:

-  ***ssdp:all***         : to search all UPnP devices
-  ***upnp:rootdevice***  : only root devices . Embedded devices will not respond
-  ***uuid:device-uuid*** : search a device by vendor supplied unique id
-  ***urn:schemas-upnp-org:device:deviceType- version*** : locates all devices of a given type (as defined by working committee)
-  ***urn:schemas-upnp-org:service:serviceType- version*** : locate service of a given type

All SSDP requests are sent to `239.255.255.250` on the default port `1900`. However this can be overridden with `SEARCHPORT.UPNP.ORG` header.

SSDP start line will always be one of the following

- ***NOTIFY * HTTP/1.1***: In case of advertisement
- ***M-SEARCH * HTTP/1.1***: In case of search
- ***HTTP/1.1 200 OK***: In case of response

SSDP also supports additional proprietary headers. Some of the defined headers include:

    BOOTID.UPNP.ORG
    CONFIGID.UPNP.ORG
    NEXTBOOTID.UPNP.ORG
    SEARCHPORT.UPNP.ORG

Domain (vendor) specific header format will be of the form

    myheader.example.com: "some value"

#### 9.1.2. SSDP Discovery response

SSDP response is a unicast reply to the sender of search broadcast. It starts with HTTP success response followed by few headers. The response is sent to the same port that made the request.

![UPNP ssdp response]({{ base_path }}/images/posts/upnp/upnp_ssdp_response.jpg){: .align-center}

SSDP response

SSDP response includes:

|    Cache-control          | This header is part of HTTP cache control settings. UPnP uses max-age for this field.|
|    Date (recommended)     | Response generation timestamp|
|    Ext                    | Confirms that man header in request (ssdp:discover) was understood|
|    Location               | URL to the device description document of the root device|
|    Server                 | concatenation of OS name, OS version, Product name and product version|
|    ST                     | Same as that specified in the discovery request|
|    USN                    | Unique service name. It takes different value formats depending on ST. |

Some sample USN values are:

        uuid:device-UUID:upnp-rootdevice
        uuid: device-UUID
        uuid:device-UUID:urn:schemas-upnp-org: device:deviceType:ver
        uuid:device-UUID :urn:schemas-upnp-org:device:serviceType:ver
        urn:domain-name:device:deviceType:ver
        urn:domain-name:service:serviceType:ver

USN+ST provide unique identification to the service.

#### 9.1.3. Presence Announcement (Advertisement)

When a UPNP device is joining, leaving, having location change or expiration info change, it has to send an advertisement to let other devices be aware of the changes that are happening.

The presence announcement is sent To standard multicast address (`239.255.255.250:1900`) where all control points listen.

Presence announcement uses (General Event Notification Architecture)GENA which relies on the NOTIFY method over HTTPU/HTTPMU.

There are three kinds of advertisement.

-    Device available : ssdp:alive
-    Device unavailable : ssdp:byebye
-    Update : ssdp:update

Advertisement sends info about root, embedded devices, and how to find them. So, if a root device has
d embedded devices, `s` embedded services and `k` distinct service types, There will be `3+2d+k` advertisement messages.

UPnP 1.1 adds 4 new header fields to SSDP

- BOOTID.UPNP.ORG
    A non-negative 31-bit integer, ASCII encoded, decimal, without leading 0s which is increased each time a device (re)joins the network and sends an initial announce. It can also be the same as the field value of the `NEXTBOOTID.UPNP.ORG` header field in the last sent SSDP update message.Otherwise same value is used in all repeat announcements, search responses, update messages and eventually bye-bye messages. A convenient mechanism is to set this field value to the epoch time of initial announcement

- NEXTBOOTID.UPNP.ORG
    Indicates the field value of the `BOOTID.UPNP.ORG` header field that a multi-homed device intends to use in future announcements after adding a new UPnPenabled interface

- CONFIGID.UPNP.ORG
    A non-negative 31-bit integer, ASCII encoded, decimal, without leading 0s that represents the configuration number of a root device. The configuration of a root device consists of the following information:
    - The Device Descriptor Document of the root device and all its embedded devices,SCPDs of all the contained services
    - Control points can parse this header field to detect whether they need to send new description query messages.

   the values ranges from 0 to `16777215` (224-1) and higher numbers are reserved for future use

- SEARCHPORT.UPNP.ORG (optional)

#### 9.1.3.2. ssdp:alive advertisement

![UPNP ssdp alive]({{ base_path }}/images/posts/upnp/upnp_ssdp_alive.jpg){: .align-center}

SSDP Alive Advertisement

This notification is sent when joining a network with one of the following notification type :

-    `upnp:rootdevice`
-    `uuid:device-UUID`
-    `urn:schemas-upnp-org:device: deviceType:ver`
-    `urn:schemas-upnp-org:service: serviceType:ver`
-    `urn:domain-name:device:deviceType:ver`
-    `urn:domain-name:service:serviceType:ver`

USN can be one of

-   `uuid:device-UUID::upnp:Rootdevice`
-   `uuid:device-UUID`
-   `uuid:device-UUID::urn:schemas-upnp-org:device: deviceType:ver`
-   `uuid:device-UUID::urn:schemas-upnp-org:service: serviceType:ver`
-   `uuid:device-UUID::urn:domain-name:device:deviceType:ver`
-   `uuid:device-UUID::urn:domain-name:service:serviceType:ver`

#### 9.1.3.2. ssdp:byebye advertisement

This notification when the device is leaving the network

![UPNP ssdp byby]({{ base_path }}/images/posts/upnp/upnp_ssdp_byby.jpg){: .align-center}

ssdp byebye

#### 9.1.3.3.  ssdp:update

This notification is sent when a UPnP enabled interface is added or removed from the multi-homed device. This is applicable to all root and embedded devices. The `BOOTID.UPNP.ORG` header is increased when ssdp:update is sent.

![UPNP ssdp update]({{ base_path }}/images/posts/upnp/upnp_ssdp_update.jpg){: .align-center}

ssdp update

## 10. Description

Control points gets the device and service descriptor URIs from discovery phase. Now, the  next step is to read and understand the required descriptor documents.

Descriptor documents are written in XML following the Schema provided by UPnP forum.

Descriptors come in two logical parts

-    Device Descriptor
-    Service Descriptor

These XMLs are retrieved using HTTP GET method.

### 10.1 Device Descriptor Document

Devicve descriptor document describes physical and logical containers. It contains the device identifiers and list of services provided by the device.

![UPNP ddd]({{ base_path }}/images/posts/upnp/upnp_ddd.jpg){: .align-center}

Device Description Document

The DDD spsecfies a UDN which is the unique device name and a UPC (universal product code). UPC is a 12 digit all-numeric code ID for consumer package.

The DDD also contains list of services and the service description document(SCPD) URL. Specific requests to the service can be sent to the "control URL" and event subscriptions are sent to the "EventSub URL".

### 10.2. Service Descriptor Document (SCPD)

![UPNP scpd]({{ base_path }}/images/posts/upnp/upnp_scpd.jpg){: .align-center}

SCPD document

The SCPD primarily contains the list of all actions the service provides. It also contains an argument list.

One or more arguments may be marked as return value and each argument corresponds to a state variable.

The service state table describes the state variables. The sendEvents attribute denotes eventing status of the state variable. If it is set to  "yes", an event will be sent when the state variable changes. The allowedValueList and allowedValueRange determine the legal values of the state variables.

### 10.3. Retrieving Descriptors

![UPNP retrive]({{ base_path }}/images/posts/upnp/retrival.jpg){: .align-center}

Retrieving descriptors

First get device descriptor, parse it to get service descriptor URL, then get requires service descriptors.

## 11. Control

When services are distributed over a system, one component cannot directly access data in another component or invoke services directly. So, we have to use a client-server message passing like mechanism where each component is a mini-server. Thus devices in the network can make simple calls to a network based remote entity as if it is a local component.However, this requires components to meet the following minimum requirements

- A message based protocol with request, response, error invoking etc.
- A Platform independent data representation to package argument parameters
  - Either predefined types or mechanism to pass type info
- An Optional security feature

In case of UPnP, service requests are made to the controlURL sub element of the service element in the device descriptor. As long as the discovery advertisement has not expired, service can be assumed to be available.

UPnP uses Simple Object Access Protocol (SOAP) for controlling other device services.

### 11.1. Simple Object Access Protocol (SOAP)

SOAP is an XML based mechanism for web based (over HTTP) messaging and remote procedure call mechanism. It consists of 4 main parts.

- SOAP envelope
    An XML schema based infrastructure defining processing rules of the message. It is the outermost container of SOAP messages

- SOAP encoding rule
    An XML schema for defining user defined data types

- SOAP bindings
    Conventions for using different transport protocols that Generally uses HTTP

- SOAP RPC representation
    For representing RPC calls and responses

Any system capable of packing and unpacking XML can use SOAP since tt is platform independence and it can pass complex messages and data.

SOAP message envelope `<envelope>` has two child elements –

- `<Header>` [optional]
    It contains auxiliary information for authentication, transactions and payments. It can contain unlimited number of child elements.

- `<Body>` [required]
    Contains core of messages

#### 11.1.1. SOAP over HTTP

A UPnP SOAP message is sent as an HTTP POST to the control URI of the service obtained from descriptor document. The Content type will be set to "text/xml".

SOAP introduces a a new header "SOAPAction" into HTTP. It contains a URI that indicates intention of the soap request. This URI need not point an actual web resource since it is used by the recieving web server to route the request. An empty string `("")` implies, the intent can be known from the HTTP request URI. No value means no indication about the intent.

![UPNP soap request]({{ base_path }}/images/posts/upnp/soap-request.jpg){: .align-center}

Sample of a SOAP Message over HTTP

SOAP Uses HTTP Extension framework and introduces the MAN header and modifies POST to M-POST. M-POST requires HTTP server to read and understand the `URI` in `MAN`. First request is sent without `MAN`. If it fails with `405(method not allowed)`, then retries with `M-POST`. If the retry fails, then the whole request fails.

![UPNP soap response]({{ base_path }}/images/posts/upnp/soap-response.jpg){: .align-center}

Soap Response

SOAP HTTP response is also a XML document where the `<Body>` contains the encoded result instead of the method call. Void methods omit `<return>` in the body

### 11.2. UPnP Action Request and Response over SOAP

Action request is sent to the service control URI with the action name, argument name and argument values as described in the service descriptor document.

![UPNP soap action request]({{ base_path }}/images/posts/upnp/soap-action-request.jpg){: .align-center}

UPnP action request over SOAP

![UPNP soap scpd]({{ base_path }}/images/posts/upnp/scpd-argument.jpg){: .align-center}

SCPD XML showing action and argument list

Upon receiving the request, the service has to respond within 30 seconds.

![UPNP soap response]({{ base_path }}/images/posts/upnp/soap_response.jpg){: .align-center}

SOAP Response with HTTP Header

If large amounts of data has to be transferred, it is recommended to do an out of band transfer, i.e. publish URL as part of SOAP and later get it using HTTP GET. Error response follows a similar schema with error values.

## 12. Eventing

UPnP eventing uses General Event Notification Architecture (GENA) over HTTP. It follows a subscriber-publisher model where the services are publishers and control points are subscribers.

Eventing begins with a subscription request from the control point to the event subscription URL of the service.  The publisher (service) responds with a subscription ID and duration of subscription.

GENA brings in 3 new HTTP methods:

- `SUBSCRIBE`
- `UNSUBSCRIBE`
- `NOTIFY`

It also introduces the following HTTP headers:

-    `CALLBACK` : Used by subscriber to register notification URL
-    `NT` : Notification type
-    `NTS` : Notification sub type
-    `SID` : Subscription ID generated by publisher, used by both parties

If a service state variable is marked as evented in the SCPD , control point can subscribe to notifications in case there is a change int eh state variable value.

![UPNP evented]({{ base_path }}/images/posts/upnp/evented.jpg){: .align-center}

Evented state variable

GENA uses XML based event messages whic includes state variable name and UPnP template language for eventing. An initial event message is sent upon subscription which includes all subscribed state variable names and states. The publisher maintains an event notification counter known as "Event key" that is incremented and embedded into each event notification.

![UPNP event schema]({{ base_path }}/images/posts/upnp/eventing_schema.jpg){: .align-center}

UPnP eventing Schema

### 12.1 Event Subscription

A common event subscription is made to all evented state variables in the service. The event subscription request will also contain the CALLBACK header which specifies the URL to which event notifications are to be sent.

![UPNP sub req]({{ base_path }}/images/posts/upnp/subscription.jpg){: .align-center}

Event Subscription request

the event subscription response will include the HTTP response code and the subscription `UUID` (`SID`) (if success).

![UPNP sub response]({{ base_path }}/images/posts/upnp/sub_response.jpg){: .align-center}

Subscription Response

The subscriber can send a renewal or cancellation request that wil be identified by the published by the SID.

![UPNP sub renew ]({{ base_path }}/images/posts/upnp/sub_renew.jpg){: .align-center}

subscription renewal request

![UPNP sub cancel]({{ base_path }}/images/posts/upnp/sub_cancel.jpg){: .align-center}

Subscription Cancellation

### 12.2 Event Message

![UPNP event message]({{ base_path }}/images/posts/upnp/event_message.jpg){: .align-center}

Unicast event message

Generally event notifications are sent to the registered callback URL as a unicast. The receiver acknowledges the notification with the appropriate HTTP response. However, there is also an alternate mechanism to publish events as a multicast to `239.255.255.246:7900`.

![UPNP event message multicast]({{ base_path }}/images/posts/upnp/event_multicast.jpg){: .align-center}

Multicast event message

## 13. Presentation

UPnP also has provision for a browser based control of the device . The Presentation URL is part of Device Descriptor Document.

## 14. Summing up

![UPNP arch]({{ base_path }}/images/posts/upnp/intelsdk.jpg){: .align-center}

Intel SDK – libupnp

Every UPnP device/control point have a specialized HTTP server Assisted by an XML Parser. They process and route the messages. Messages are routed into the corresponding Protocol stack for processing

Broadcasts are done over UDP at standard ports


text:
<!DOCTYPE html>

  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
  <meta content="text/html; charset=UTF-8" http-equiv="content-type">

  <title>Campus map</title>

  <link rel="stylesheet" href="../css/lib/jquery.mobile-1.3.1.min.css"/>
  <link rel="stylesheet" href="../css/SU-ThemeA-B.css"/>
  <link rel="stylesheet" href="../css/SU-beta.css"/>
  <link rel="stylesheet" href="css/mapapp.css"/>

      No results available


text:
Composant pour séléctionner une date sur un calendrier et l'affficher dans un champ texte. Implémente NgModel.

### Utilisation
> Ne pas oublier d'importer le `DatePickerModule` dans les `imports` de votre module concerné !

  - Implémentation (crée un champ texte qui, au click fait apparaitre un *date-selector* contenu dans un *dropdown*) :

```html
<deja-date-picker [disableDates]="[0, 6]" [(ngModel)]="votreModel"></deja-date-picker>
```

#### Note
 - Vous pouvez ajouter la propriété `disabled` comme sur tout les champs input.

### Propriétés

    Nom
    Type
    Defaut
    Description

    dateMax
    Date
    null
    Date maximum sélectionnable dans le calendrier

    dateMin
    Date
    null
    Date minimum sélectionnable dans le calendrier

    disabled
    boolean
    false
    Désactive le datepicker

    disableDates
    (DaysOfWeek | Date)[]
    null
    Désactive les dates correspondantes. Prend un tableau qui contient des nombres (de 0 à 6 pour désactiver les jours) et/ou des dates pour désactiver une date specifique. TODO : prendre une fonction 8promise / observable) pour récupérer les disablesDates depuis un service.

    dropdownAlignment
    string
    left right top bottom
    Voir la documentation du *DejaDropdownComponent* pour plus d'informations

    dropdownContainerId
    string
    null
    Voir la documentation du *DejaDropdownComponent* pour plus d'informations

    format
    string
    null
    Le format de la date à afficher dans le champ texte. Par défaut ce aui est affiché est YYYY-MM-DD + HH:mm si c'est aussi un timepicker

    ownerAlignment
    string
    left bottom
    Voir la documentation du *DejaDropdownComponent* pour plus d'informations

    placeholder
    string
    Date
    Le placeholder du champ texte du datepicker


text:
using System.Linq;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.AspNetCore.Identity.UI.Areas.Identity.Filters;

internal sealed class ExternalLoginsPageFilter<TUser> : IAsyncPageFilter where TUser : class
{
    public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
    {
        var result = await next();
        if (result.Result is PageResult page)
        {
            var signInManager = context.HttpContext.RequestServices.GetRequiredService<SignInManager<TUser>>();
            var schemes = await signInManager.GetExternalAuthenticationSchemesAsync();
            var hasExternalLogins = schemes.Any();

            page.ViewData["ManageNav.HasExternalLogins"] = hasExternalLogins;
        }
    }

    public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
    {
        return Task.CompletedTask;
    }
}