text:
'use strict';

const packageInfo = require('../../package.json');
const EventEmitter = require('events').EventEmitter;
const net = require('net');
const tls = require('tls');
const os = require('os');
const crypto = require('crypto');
const DataStream = require('./data-stream');
const PassThrough = require('stream').PassThrough;
const shared = require('../shared');

// default timeout values in ms
const CONNECTION_TIMEOUT = 2 * 60 * 1000; // how much to wait for the connection to be established
const SOCKET_TIMEOUT = 10 * 60 * 1000; // how much to wait for socket inactivity before disconnecting the client
const GREETING_TIMEOUT = 30 * 1000; // how much to wait after connection is established but SMTP greeting is not receieved

/**
 * Generates a SMTP connection object
 *
 * Optional options object takes the following possible properties:
 *
 *  * **port** - is the port to connect to (defaults to 587 or 465)
 *  * **host** - is the hostname or IP address to connect to (defaults to 'localhost')
 *  * **secure** - use SSL
 *  * **ignoreTLS** - ignore server support for STARTTLS
 *  * **requireTLS** - forces the client to use STARTTLS
 *  * **name** - the name of the client server
 *  * **localAddress** - outbound address to bind to (see: http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener)
 *  * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 10000)
 *  * **connectionTimeout** - how many milliseconds to wait for the connection to establish
 *  * **socketTimeout** - Time of inactivity until the connection is closed (defaults to 1 hour)
 *  * **lmtp** - if true, uses LMTP instead of SMTP protocol
 *  * **logger** - bunyan compatible logger interface
 *  * **debug** - if true pass SMTP traffic to the logger
 *  * **tls** - options for createCredentials
 *  * **socket** - existing socket to use instead of creating a new one (see: http://nodejs.org/api/net.html#net_class_net_socket)
 *  * **secured** - boolean indicates that the provided socket has already been upgraded to tls
 *
 * @constructor
 * @namespace SMTP Client module
 * @param {Object} [options] Option properties
 */
class SMTPConnection extends EventEmitter {
    constructor(options) {
        super(options);

        this.id = crypto.randomBytes(8).toString('base64').replace(/\W/g, '');
        this.stage = 'init';

        this.options = options || {};

        this.secureConnection = !!this.options.secure;
        this.alreadySecured = !!this.options.secured;

        this.port = this.options.port || (this.secureConnection ? 465 : 587);
        this.host = this.options.host || 'localhost';

        if (typeof this.options.secure === 'undefined' && this.port === 465) {
            // if secure option is not set but port is 465, then default to secure
            this.secureConnection = true;
        }

        this.name = this.options.name || this._getHostname();

        this.logger = shared.getLogger(this.options, {
            component: this.options.component || 'smtp-connection',
            sid: this.id
        });

        /**
         * Expose version nr, just for the reference
         * @type {String}
         */
        this.version = packageInfo.version;

        /**
         * If true, then the user is authenticated
         * @type {Boolean}
         */
        this.authenticated = false;

        /**
         * If set to true, this instance is no longer active
         * @private
         */
        this.destroyed = false;

        /**
         * Defines if the current connection is secure or not. If not,
         * STARTTLS can be used if available
         * @private
         */
        this.secure = !!this.secureConnection;

        /**
         * Store incomplete messages coming from the server
         * @private
         */
        this._remainder = '';

        /**
         * Unprocessed responses from the server
         * @type {Array}
         */
        this._responseQueue = [];

        this.lastServerResponse = false;

        /**
         * The socket connecting to the server
         * @publick
         */
        this._socket = false;

        /**
         * Lists supported auth mechanisms
         * @private
         */
        this._supportedAuth = [];

        /**
         * Includes current envelope (from, to)
         * @private
         */
        this._envelope = false;

        /**
         * Lists supported extensions
         * @private
         */
        this._supportedExtensions = [];

        /**
         * Defines the maximum allowed size for a single message
         * @private
         */
        this._maxAllowedSize = 0;

        /**
         * Function queue to run if a data chunk comes from the server
         * @private
         */
        this._responseActions = [];
        this._recipientQueue = [];

        /**
         * Timeout variable for waiting the greeting
         * @private
         */
        this._greetingTimeout = false;

        /**
         * Timeout variable for waiting the connection to start
         * @private
         */
        this._connectionTimeout = false;

        /**
         * If the socket is deemed already closed
         * @private
         */
        this._destroyed = false;

        /**
         * If the socket is already being closed
         * @private
         */
        this._closing = false;
    }

    /**
     * Creates a connection to a SMTP server and sets up connection
     * listener
     */
    connect(connectCallback) {
        if (typeof connectCallback === 'function') {
            this.once('connect', () => {
                this.logger.debug({
                    tnx: 'smtp'
                }, 'SMTP handshake finished');
                connectCallback();
            });
        }

        let opts = {
            port: this.port,
            host: this.host
        };

        if (this.options.localAddress) {
            opts.localAddress = this.options.localAddress;
        }

        if (this.options.connection) {
            // connection is already opened
            this._socket = this.options.connection;
            if (this.secureConnection && !this.alreadySecured) {
                setImmediate(() => this._upgradeConnection(err => {
                    if (err) {
                        this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'CONN');
                        return;
                    }
                    this._onConnect();
                }));
            } else {
                setImmediate(() => this._onConnect());
            }
        } else if (this.options.socket) {
            // socket object is set up but not yet connected
            this._socket = this.options.socket;
            try {
                this._socket.connect(this.port, this.host, () => {
                    this._socket.setKeepAlive(true);
                    this._onConnect();
                });
            } catch (E) {
                return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
            }
        } else if (this.secureConnection) {
            // connect using tls
            if (this.options.tls) {
                Object.keys(this.options.tls).forEach(key => {
                    opts[key] = this.options.tls[key];
                });
            }
            try {
                this._socket = tls.connect(this.port, this.host, opts, () => {
                    this._socket.setKeepAlive(true);
                    this._onConnect();
                });
            } catch (E) {
                return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
            }
        } else {
            // connect using plaintext
            try {
                this._socket = net.connect(opts, () => {
                    this._socket.setKeepAlive(true);
                    this._onConnect();
                });
            } catch (E) {
                return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
            }
        }

        this._connectionTimeout = setTimeout(() => {
            this._onError('Connection timeout', 'ETIMEDOUT', false, 'CONN');
        }, this.options.connectionTimeout || CONNECTION_TIMEOUT);

        this._socket.on('error', err => {
            this._onError(err, 'ECONNECTION', false, 'CONN');
        });
    }

    /**
     * Sends QUIT
     */
    quit() {
        this._sendCommand('QUIT');
        this._responseActions.push(this.close);
    }

    /**
     * Closes the connection to the server
     */
    close() {
        clearTimeout(this._connectionTimeout);
        clearTimeout(this._greetingTimeout);
        this._responseActions = [];

        // allow to run this function only once
        if (this._closing) {
            return;
        }
        this._closing = true;

        let closeMethod = 'end';

        if (this.stage === 'init') {
            // Close the socket immediately when connection timed out
            closeMethod = 'destroy';
        }

        this.logger.debug({
            tnx: 'smtp'
        }, 'Closing connection to the server using "%s"', closeMethod);

        let socket = this._socket && this._socket.socket || this._socket;

        if (socket && !socket.destroyed) {
            try {
                this._socket[closeMethod]();
            } catch (E) {
                // just ignore
            }
        }

        this._destroy();
    }

    /**
     * Authenticate user
     */
    login(authData, callback) {
        this._auth = authData || {};

        // Select SASL authentication method
        this._authMethod = (this._auth.method || '').toString().trim().toUpperCase() || false;
        if (!this._authMethod && this._auth.oauth2 && !this._auth.credentials) {
            this._authMethod = 'XOAUTH2';
        } else if (!this._authMethod || (this._authMethod === 'XOAUTH2' && !this._auth.oauth2)) {
            // use first supported
            this._authMethod = (this._supportedAuth[0] || 'PLAIN').toUpperCase().trim();
        }

        if (this._authMethod !== 'XOAUTH2' && !this._auth.credentials) {
            if (this._auth.user && this._auth.pass) {
                this._auth.credentials = {
                    user: this._auth.user,
                    pass: this._auth.pass
                };
            } else {
                return callback(this._formatError('Missing credentials for "' + this._authMethod + '"', 'EAUTH', false, 'API'));
            }
        }

        switch (this._authMethod) {
            case 'XOAUTH2':
                this._handleXOauth2Token(false, callback);
                return;
            case 'LOGIN':
                this._responseActions.push(str => {
                    this._actionAUTH_LOGIN_USER(str, callback);
                });
                this._sendCommand('AUTH LOGIN');
                return;
            case 'PLAIN':
                this._responseActions.push(str => {
                    this._actionAUTHComplete(str, callback);
                });
                this._sendCommand('AUTH PLAIN ' + new Buffer(
                    //this._auth.user+'\u0000'+
                    '\u0000' + // skip authorization identity as it causes problems with some servers
                    this._auth.credentials.user + '\u0000' +
                    this._auth.credentials.pass, 'utf-8').toString('base64'));
                return;
            case 'CRAM-MD5':
                this._responseActions.push(str => {
                    this._actionAUTH_CRAM_MD5(str, callback);
                });
                this._sendCommand('AUTH CRAM-MD5');
                return;
        }

        return callback(this._formatError('Unknown authentication method "' + this._authMethod + '"', 'EAUTH', false, 'API'));
    }

    /**
     * Sends a message
     *
     * @param {Object} envelope Envelope object, {from: addr, to: [addr]}
     * @param {Object} message String, Buffer or a Stream
     * @param {Function} callback Callback to return once sending is completed
     */
    send(envelope, message, done) {
        if (!message) {
            return done(this._formatError('Empty message', 'EMESSAGE', false, 'API'));
        }

        // reject larger messages than allowed
        if (this._maxAllowedSize && envelope.size > this._maxAllowedSize) {
            return setImmediate(() => {
                done(this._formatError('Message size larger than allowed ' + this._maxAllowedSize, 'EMESSAGE', false, 'MAIL FROM'));
            });
        }

        // ensure that callback is only called once
        let returned = false;
        let callback = function () {
            if (returned) {
                return;
            }
            returned = true;

            done(...arguments);
        };

        if (typeof message.on === 'function') {
            message.on('error', err => callback(this._formatError(err, 'ESTREAM', false, 'API')));
        }

        this._setEnvelope(envelope, (err, info) => {
            if (err) {
                return callback(err);
            }
            let stream = this._createSendStream((err, str) => {
                if (err) {
                    return callback(err);
                }
                info.response = str;
                return callback(null, info);
            });
            if (typeof message.pipe === 'function') {
                message.pipe(stream);
            } else {
                stream.write(message);
                stream.end();
            }

        });
    }

    /**
     * Resets connection state
     *
     * @param {Function} callback Callback to return once connection is reset
     */
    reset(callback) {
        this._sendCommand('RSET');
        this._responseActions.push(str => {
            if (str.charAt(0) !== '2') {
                return callback(this._formatError('Could not reset session state:\n' + str, 'EPROTOCOL', str, 'RSET'));
            }
            this._envelope = false;
            return callback(null, true);
        });
    }

    /**
     * Connection listener that is run when the connection to
     * the server is opened
     *
     * @event
     */
    _onConnect() {
        clearTimeout(this._connectionTimeout);

        this.logger.info({
            tnx: 'network',
            localAddress: this._socket.localAddress,
            localPort: this._socket.localPort,
            remoteAddress: this._socket.remoteAddress,
            remotePort: this._socket.remotePort
        }, '%s established to %s:%s', this.secure ? 'Secure connection' : 'Connection', this._socket.remoteAddress, this._socket.remotePort);

        if (this._destroyed) {
            // Connection was established after we already had canceled it
            this.close();
            return;
        }

        this.stage = 'connected';

        // clear existing listeners for the socket
        this._socket.removeAllListeners('data');
        this._socket.removeAllListeners('timeout');
        this._socket.removeAllListeners('close');
        this._socket.removeAllListeners('end');

        this._socket.on('data', chunk => this._onData(chunk));
        this._socket.once('close', errored => this._onClose(errored));
        this._socket.once('end', () => this._onEnd());

        this._socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT);
        this._socket.on('timeout', () => this._onTimeout());

        this._greetingTimeout = setTimeout(() => {
            // if still waiting for greeting, give up
            if (this._socket && !this._destroyed && this._responseActions[0] === this._actionGreeting) {
                this._onError('Greeting never received', 'ETIMEDOUT', false, 'CONN');
            }
        }, this.options.greetingTimeout || GREETING_TIMEOUT);

        this._responseActions.push(this._actionGreeting);

        // we have a 'data' listener set up so resume socket if it was paused
        this._socket.resume();
    }

    /**
     * 'data' listener for data coming from the server
     *
     * @event
     * @param {Buffer} chunk Data chunk coming from the server
     */
    _onData(chunk) {
        if (this._destroyed || !chunk || !chunk.length) {
            return;
        }

        let data = (chunk || '').toString('binary');
        let lines = (this._remainder + data).split(/\r?\n/);
        let lastline;

        this._remainder = lines.pop();

        for (let i = 0, len = lines.length; i < len; i++) {
            if (this._responseQueue.length) {
                lastline = this._responseQueue[this._responseQueue.length - 1];
                if (/^\d+\-/.test(lastline.split('\n').pop())) {
                    this._responseQueue[this._responseQueue.length - 1] += '\n' + lines[i];
                    continue;
                }
            }
            this._responseQueue.push(lines[i]);
        }

        this._processResponse();
    }

    /**
     * 'error' listener for the socket
     *
     * @event
     * @param {Error} err Error object
     * @param {String} type Error name
     */
    _onError(err, type, data, command) {
        clearTimeout(this._connectionTimeout);
        clearTimeout(this._greetingTimeout);

        if (this._destroyed) {
            // just ignore, already closed
            // this might happen when a socket is canceled because of reached timeout
            // but the socket timeout error itself receives only after
            return;
        }

        err = this._formatError(err, type, data, command);

        let entry = {
            err
        };
        if (type) {
            entry.errorType = type;
        }
        if (data) {
            entry.errorData = data;
        }
        if (command) {
            entry.command = command;
        }

        this.logger.error(data, err.message);

        this.emit('error', err);
        this.close();
    }

    _formatError(message, type, response, command) {
        let err;

        if (/Error\]$/i.test(Object.prototype.toString.call(message))) {
            err = message;
        } else {
            err = new Error(message);
        }

        if (type && type !== 'Error') {
            err.code = type;
        }

        if (response) {
            err.response = response;
            err.message += ': ' + response;
        }

        let responseCode = typeof response === 'string' && Number((response.match(/^\d+/) || [])[0]) || false;
        if (responseCode) {
            err.responseCode = responseCode;
        }

        if (command) {
            err.command = command;
        }

        return err;
    }

    /**
     * 'close' listener for the socket
     *
     * @event
     */
    _onClose() {
        this.logger.info({
            tnx: 'network'
        }, 'Connection closed');

        if (this.upgrading && !this._destroyed) {
            return this._onError(new Error('Connection closed unexpectedly'), 'ETLS', false, 'CONN');
        } else if (![this._actionGreeting, this.close].includes(this._responseActions[0]) && !this._destroyed) {
            return this._onError(new Error('Connection closed unexpectedly'), 'ECONNECTION', false, 'CONN');
        }

        this._destroy();
    }

    /**
     * 'end' listener for the socket
     *
     * @event
     */
    _onEnd() {
        this._destroy();
    }

    /**
     * 'timeout' listener for the socket
     *
     * @event
     */
    _onTimeout() {
        return this._onError(new Error('Timeout'), 'ETIMEDOUT', false, 'CONN');
    }

    /**
     * Destroys the client, emits 'end'
     */
    _destroy() {
        if (this._destroyed) {
            return;
        }
        this._destroyed = true;
        this.emit('end');
    }

    /**
     * Upgrades the connection to TLS
     *
     * @param {Function} callback Callback function to run when the connection
     *        has been secured
     */
    _upgradeConnection(callback) {
        // do not remove all listeners or it breaks node v0.10 as there's
        // apparently a 'finish' event set that would be cleared as well

        // we can safely keep 'error', 'end', 'close' etc. events
        this._socket.removeAllListeners('data'); // incoming data is going to be gibberish from this point onwards
        this._socket.removeAllListeners('timeout'); // timeout will be re-set for the new socket object

        let socketPlain = this._socket;
        let opts = {
            socket: this._socket,
            host: this.host
        };

        Object.keys(this.options.tls || {}).forEach(key => {
            opts[key] = this.options.tls[key];
        });

        this.upgrading = true;
        this._socket = tls.connect(opts, () => {
            this.secure = true;
            this.upgrading = false;
            this._socket.on('data', chunk => this._onData(chunk));

            socketPlain.removeAllListeners('close');
            socketPlain.removeAllListeners('end');

            return callback(null, true);
        });

        this._socket.on('error', err => this._onError(err, 'ESOCKET', false, 'CONN'));
        this._socket.once('close', errored => this._onClose(errored));
        this._socket.once('end', () => this._onEnd());

        this._socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT); // 10 min.
        this._socket.on('timeout', () => this._onTimeout());

        // resume in case the socket was paused
        socketPlain.resume();
    }

    /**
     * Processes queued responses from the server
     *
     * @param {Boolean} force If true, ignores _processing flag
     */
    _processResponse() {
        if (!this._responseQueue.length) {
            return false;
        }

        let str = this.lastServerResponse = (this._responseQueue.shift() || '').toString();

        if (/^\d+\-/.test(str.split('\n').pop())) {
            // keep waiting for the final part of multiline response
            return;
        }

        if (this.options.debug || this.options.transactionLog) {
            this.logger.debug({
                tnx: 'server'
            }, str.replace(/\r?\n$/, ''));
        }

        if (!str.trim()) { // skip unexpected empty lines
            setImmediate(() => this._processResponse(true));
        }

        let action = this._responseActions.shift();

        if (typeof action === 'function') {
            action.call(this, str);
            setImmediate(() => this._processResponse(true));
        } else {
            return this._onError(new Error('Unexpected Response'), 'EPROTOCOL', str, 'CONN');
        }
    }

    /**
     * Send a command to the server, append \r\n
     *
     * @param {String} str String to be sent to the server
     */
    _sendCommand(str) {
        if (this._destroyed) {
            // Connection already closed, can't send any more data
            return;
        }

        if (this._socket.destroyed) {
            return this.close();
        }

        if (this.options.debug || this.options.transactionLog) {
            this.logger.debug({
                tnx: 'client'
            }, (str || '').toString().replace(/\r?\n$/, ''));
        }

        this._socket.write(new Buffer(str + '\r\n', 'utf-8'));
    }

    /**
     * Initiates a new message by submitting envelope data, starting with
     * MAIL FROM: command
     *
     * @param {Object} envelope Envelope object in the form of
     *        {from:'...', to:['...']}
     *        or
     *        {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
     */
    _setEnvelope(envelope, callback) {
        let args = [];
        let useSmtpUtf8 = false;

        this._envelope = envelope || {};
        this._envelope.from = (this._envelope.from && this._envelope.from.address || this._envelope.from || '').toString().trim();

        this._envelope.to = [].concat(this._envelope.to || []).map(to => (to && to.address || to || '').toString().trim());

        if (!this._envelope.to.length) {
            return callback(this._formatError('No recipients defined', 'EENVELOPE', false, 'API'));
        }

        if (this._envelope.from && /[\r\n<>]/.test(this._envelope.from)) {
            return callback(this._formatError('Invalid sender ' + JSON.stringify(this._envelope.from), 'EENVELOPE', false, 'API'));
        }

        // check if the sender address uses only ASCII characters,
        // otherwise require usage of SMTPUTF8 extension
        if (/[\x80-\uFFFF]/.test(this._envelope.from)) {
            useSmtpUtf8 = true;
        }

        for (let i = 0, len = this._envelope.to.length; i < len; i++) {
            if (!this._envelope.to[i] || /[\r\n<>]/.test(this._envelope.to[i])) {
                return callback(this._formatError('Invalid recipient ' + JSON.stringify(this._envelope.to[i]), 'EENVELOPE', false, 'API'));
            }

            // check if the recipients addresses use only ASCII characters,
            // otherwise require usage of SMTPUTF8 extension
            if (/[\x80-\uFFFF]/.test(this._envelope.to[i])) {
                useSmtpUtf8 = true;
            }
        }

        // clone the recipients array for latter manipulation
        this._envelope.rcptQueue = JSON.parse(JSON.stringify(this._envelope.to || []));
        this._envelope.rejected = [];
        this._envelope.rejectedErrors = [];
        this._envelope.accepted = [];

        if (this._envelope.dsn) {
            try {
                this._envelope.dsn = this._setDsnEnvelope(this._envelope.dsn);
            } catch (err) {
                return callback(this._formatError('Invalid DSN ' + err.message, 'EENVELOPE', false, 'API'));
            }
        }

        this._responseActions.push(str => {
            this._actionMAIL(str, callback);
        });

        // If the server supports SMTPUTF8 and the envelope includes an internationalized
        // email address then append SMTPUTF8 keyword to the MAIL FROM command
        if (useSmtpUtf8 && this._supportedExtensions.includes('SMTPUTF8')) {
            args.push('SMTPUTF8');
            this._usingSmtpUtf8 = true;
        }

        // If the server supports 8BITMIME and the message might contain non-ascii bytes
        // then append the 8BITMIME keyword to the MAIL FROM command
        if (this._envelope.use8BitMime && this._supportedExtensions.includes('8BITMIME')) {
            args.push('BODY=8BITMIME');
            this._using8BitMime = true;
        }

        if (this._envelope.size && this._supportedExtensions.includes('SIZE')) {
            args.push('SIZE=' + this._envelope.size);
        }

        // If the server supports DSN and the envelope includes an DSN prop
        // then append DSN params to the MAIL FROM command
        if (this._envelope.dsn && this._supportedExtensions.includes('DSN')) {
            if (this._envelope.dsn.ret) {
                args.push('RET=' + shared.encodeXText(this._envelope.dsn.ret));
            }
            if (this._envelope.dsn.envid) {
                args.push('ENVID=' + shared.encodeXText(this._envelope.dsn.envid));
            }
        }

        this._sendCommand('MAIL FROM:<' + (this._envelope.from) + '>' + (args.length ? ' ' + args.join(' ') : ''));
    }

    _setDsnEnvelope(params) {
        let ret = (params.ret || params.return || '').toString().toUpperCase() || null;
        if (ret) {
            switch (ret) {
                case 'HDRS':
                case 'HEADERS':
                    ret = 'HDRS';
                    break;
                case 'FULL':
                case 'BODY':
                    ret = 'full';
                    break;
            }
        }

        if (ret && !['FULL', 'HDRS'].includes(ret)) {
            throw new Error('ret: ' + JSON.stringify(ret));
        }

        let envid = (params.envid || params.id || '').toString() || null;

        let notify = params.notify || null;
        if (notify) {
            if (typeof notify === 'string') {
                notify = notify.split(',');
            }
            notify = notify.map(n => n.trim().toUpperCase());
            let validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
            let invaliNotify = notify.filter(n => !validNotify.includes(n));
            if (invaliNotify.length || (notify.length > 1 && notify.includes('NEVER'))) {
                throw new Error('notify: ' + JSON.stringify(notify.join(',')));
            }
            notify = notify.join(',');
        }

        let orcpt = (params.orcpt || params.recipient).toString() || null;
        if (orcpt && orcpt.indexOf(';') < 0) {
            orcpt = 'rfc822;' + orcpt;
        }

        return {
            ret,
            envid,
            notify,
            orcpt
        };
    }

    _getDsnRcptToArgs() {
        let args = [];
        // If the server supports DSN and the envelope includes an DSN prop
        // then append DSN params to the RCPT TO command
        if (this._envelope.dsn && this._supportedExtensions.includes('DSN')) {
            if (this._envelope.dsn.notify) {
                args.push('NOTIFY=' + shared.encodeXText(this._envelope.dsn.notify));
            }
            if (this._envelope.dsn.orcpt) {
                args.push('ORCPT=' + shared.encodeXText(this._envelope.dsn.orcpt));
            }
        }
        return (args.length ? ' ' + args.join(' ') : '');
    }

    _createSendStream(callback) {
        let dataStream = new DataStream();
        let logStream;

        if (this.options.lmtp) {
            this._envelope.accepted.forEach((recipient, i) => {
                let final = i === this._envelope.accepted.length - 1;
                this._responseActions.push(str => {
                    this._actionLMTPStream(recipient, final, str, callback);
                });
            });
        } else {
            this._responseActions.push(str => {
                this._actionSMTPStream(str, callback);
            });
        }

        dataStream.pipe(this._socket, {
            end: false
        });

        if (this.options.debug) {
            logStream = new PassThrough();
            logStream.on('readable', () => {
                let chunk;
                while ((chunk = logStream.read())) {
                    this.logger.debug({
                        tnx: 'message'
                    }, chunk.toString('binary').replace(/\r?\n$/, ''));
                }
            });
            dataStream.pipe(logStream);
        }

        dataStream.once('end', () => {
            this.logger.info({
                tnx: 'message',
                inByteCount: dataStream.inByteCount,
                outByteCount: dataStream.outByteCount
            }, '<%s bytes encoded mime message (source size %s bytes)>', dataStream.outByteCount, dataStream.inByteCount);
        });

        return dataStream;
    }

    /** ACTIONS **/

    /**
     * Will be run after the connection is created and the server sends
     * a greeting. If the incoming message starts with 220 initiate
     * SMTP session by sending EHLO command
     *
     * @param {String} str Message from the server
     */
    _actionGreeting(str) {
        clearTimeout(this._greetingTimeout);

        if (str.substr(0, 3) !== '220') {
            this._onError(new Error('Invalid greeting from server:\n' + str), 'EPROTOCOL', str, 'CONN');
            return;
        }

        if (this.options.lmtp) {
            this._responseActions.push(this._actionLHLO);
            this._sendCommand('LHLO ' + this.name);
        } else {
            this._responseActions.push(this._actionEHLO);
            this._sendCommand('EHLO ' + this.name);
        }
    }

    /**
     * Handles server response for LHLO command. If it yielded in
     * error, emit 'error', otherwise treat this as an EHLO response
     *
     * @param {String} str Message from the server
     */
    _actionLHLO(str) {
        if (str.charAt(0) !== '2') {
            this._onError(new Error('Invalid response for LHLO:\n' + str), 'EPROTOCOL', str, 'LHLO');
            return;
        }

        this._actionEHLO(str);
    }

    /**
     * Handles server response for EHLO command. If it yielded in
     * error, try HELO instead, otherwise initiate TLS negotiation
     * if STARTTLS is supported by the server or move into the
     * authentication phase.
     *
     * @param {String} str Message from the server
     */
    _actionEHLO(str) {
        let match;

        if (str.substr(0, 3) === '421') {
            this._onError(new Error('Server terminates connection:\n' + str), 'ECONNECTION', str, 'EHLO');
            return;
        }

        if (str.charAt(0) !== '2') {
            if (this.options.requireTLS) {
                this._onError(new Error('EHLO failed but HELO does not support required STARTTLS:\n' + str), 'ECONNECTION', str, 'EHLO');
                return;
            }

            // Try HELO instead
            this._responseActions.push(this._actionHELO);
            this._sendCommand('HELO ' + this.name);
            return;
        }

        // Detect if the server supports STARTTLS
        if (!this.secure && !this.options.ignoreTLS && (/[ \-]STARTTLS\b/mi.test(str) || this.options.requireTLS)) {
            this._sendCommand('STARTTLS');
            this._responseActions.push(this._actionSTARTTLS);
            return;
        }

        // Detect if the server supports SMTPUTF8
        if (/[ \-]SMTPUTF8\b/mi.test(str)) {
            this._supportedExtensions.push('SMTPUTF8');
        }

        // Detect if the server supports DSN
        if (/[ \-]DSN\b/mi.test(str)) {
            this._supportedExtensions.push('DSN');
        }

        // Detect if the server supports 8BITMIME
        if (/[ \-]8BITMIME\b/mi.test(str)) {
            this._supportedExtensions.push('8BITMIME');
        }

        // Detect if the server supports PIPELINING
        if (/[ \-]PIPELINING\b/mi.test(str)) {
            this._supportedExtensions.push('PIPELINING');
        }

        // Detect if the server supports PLAIN auth
        if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)PLAIN/i.test(str)) {
            this._supportedAuth.push('PLAIN');
        }

        // Detect if the server supports LOGIN auth
        if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)LOGIN/i.test(str)) {
            this._supportedAuth.push('LOGIN');
        }

        // Detect if the server supports CRAM-MD5 auth
        if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)CRAM-MD5/i.test(str)) {
            this._supportedAuth.push('CRAM-MD5');
        }

        // Detect if the server supports XOAUTH2 auth
        if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)XOAUTH2/i.test(str)) {
            this._supportedAuth.push('XOAUTH2');
        }

        // Detect if the server supports SIZE extensions (and the max allowed size)
        if ((match = str.match(/[ \-]SIZE(?:[ \t]+(\d+))?/mi))) {
            this._supportedExtensions.push('SIZE');
            this._maxAllowedSize = Number(match[1]) || 0;
        }

        this.emit('connect');
    }

    /**
     * Handles server response for HELO command. If it yielded in
     * error, emit 'error', otherwise move into the authentication phase.
     *
     * @param {String} str Message from the server
     */
    _actionHELO(str) {
        if (str.charAt(0) !== '2') {
            this._onError(new Error('Invalid response for EHLO/HELO:\n' + str), 'EPROTOCOL', str, 'HELO');
            return;
        }

        this.emit('connect');
    }

    /**
     * Handles server response for STARTTLS command. If there's an error
     * try HELO instead, otherwise initiate TLS upgrade. If the upgrade
     * succeedes restart the EHLO
     *
     * @param {String} str Message from the server
     */
    _actionSTARTTLS(str) {
        if (str.charAt(0) !== '2') {
            if (this.options.opportunisticTLS) {
                this.logger.info({
                    tnx: 'smtp'
                }, 'Failed STARTTLS upgrade, continuing unencrypted');
                return this.emit('connect');
            }
            this._onError(new Error('Error upgrading connection with STARTTLS'), 'ETLS', str, 'STARTTLS');
            return;
        }

        this._upgradeConnection((err, secured) => {
            if (err) {
                this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'STARTTLS');
                return;
            }

            this.logger.info({
                tnx: 'smtp'
            }, 'Connection upgraded with STARTTLS');

            if (secured) {
                // restart session
                if (this.options.lmtp) {
                    this._responseActions.push(this._actionLHLO);
                    this._sendCommand('LHLO ' + this.name);
                } else {
                    this._responseActions.push(this._actionEHLO);
                    this._sendCommand('EHLO ' + this.name);
                }
            } else {
                this.emit('connect');
            }
        });
    }

    /**
     * Handle the response for AUTH LOGIN command. We are expecting
     * '334 VXNlcm5hbWU6' (base64 for 'Username:'). Data to be sent as
     * response needs to be base64 encoded username. We do not need
     * exact match but settle with 334 response in general as some
     * hosts invalidly use a longer message than VXNlcm5hbWU6
     *
     * @param {String} str Message from the server
     */
    _actionAUTH_LOGIN_USER(str, callback) {
        if (!/^334[ \-]/.test(str)) { // expecting '334 VXNlcm5hbWU6'
            callback(this._formatError('Invalid login sequence while waiting for "334 VXNlcm5hbWU6"', 'EAUTH', str, 'AUTH LOGIN'));
            return;
        }

        this._responseActions.push(str => {
            this._actionAUTH_LOGIN_PASS(str, callback);
        });

        this._sendCommand(new Buffer(this._auth.credentials.user + '', 'utf-8').toString('base64'));
    }

    /**
     * Handle the response for AUTH CRAM-MD5 command. We are expecting
     * '334 <challenge string>'. Data to be sent as response needs to be
     * base64 decoded challenge string, MD5 hashed using the password as
     * a HMAC key, prefixed by the username and a space, and finally all
     * base64 encoded again.
     *
     * @param {String} str Message from the server
     */
    _actionAUTH_CRAM_MD5(str, callback) {
        let challengeMatch = str.match(/^334\s+(.+)$/);
        let challengeString = '';

        if (!challengeMatch) {
            return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH CRAM-MD5'));
        } else {
            challengeString = challengeMatch[1];
        }

        // Decode from base64
        let base64decoded = new Buffer(challengeString, 'base64').toString('ascii'),
            hmac_md5 = crypto.createHmac('md5', this._auth.credentials.pass);

        hmac_md5.update(base64decoded);

        let hex_hmac = hmac_md5.digest('hex');
        let prepended = this._auth.credentials.user + ' ' + hex_hmac;

        this._responseActions.push(str => {
            this._actionAUTH_CRAM_MD5_PASS(str, callback);
        });

        this._sendCommand(new Buffer(prepended).toString('base64'));
    }

    /**
     * Handles the response to CRAM-MD5 authentication, if there's no error,
     * the user can be considered logged in. Start waiting for a message to send
     *
     * @param {String} str Message from the server
     */
    _actionAUTH_CRAM_MD5_PASS(str, callback) {
        if (!str.match(/^235\s+/)) {
            return callback(this._formatError('Invalid login sequence while waiting for "235"', 'EAUTH', str, 'AUTH CRAM-MD5'));
        }

        this.logger.info({
            tnx: 'smtp',
            username: this._auth.user,
            action: 'authenticated',
            method: this._authMethod
        }, 'User %s authenticated', JSON.stringify(this._auth.user));
        this.authenticated = true;
        callback(null, true);
    }

    /**
     * Handle the response for AUTH LOGIN command. We are expecting
     * '334 UGFzc3dvcmQ6' (base64 for 'Password:'). Data to be sent as
     * response needs to be base64 encoded password.
     *
     * @param {String} str Message from the server
     */
    _actionAUTH_LOGIN_PASS(str, callback) {
        if (!/^334[ \-]/.test(str)) { // expecting '334 UGFzc3dvcmQ6'
            return callback(this._formatError('Invalid login sequence while waiting for "334 UGFzc3dvcmQ6"', 'EAUTH', str, 'AUTH LOGIN'));
        }

        this._responseActions.push(str => {
            this._actionAUTHComplete(str, callback);
        });

        this._sendCommand(new Buffer(this._auth.credentials.pass + '', 'utf-8').toString('base64'));
    }

    /**
     * Handles the response for authentication, if there's no error,
     * the user can be considered logged in. Start waiting for a message to send
     *
     * @param {String} str Message from the server
     */
    _actionAUTHComplete(str, isRetry, callback) {
        if (!callback && typeof isRetry === 'function') {
            callback = isRetry;
            isRetry = false;
        }

        if (str.substr(0, 3) === '334') {
            this._responseActions.push(str => {
                if (isRetry || this._authMethod !== 'XOAUTH2') {
                    this._actionAUTHComplete(str, true, callback);
                } else {
                    // fetch a new OAuth2 access token
                    setImmediate(() => this._handleXOauth2Token(true, callback));
                }
            });
            this._sendCommand('');
            return;
        }

        if (str.charAt(0) !== '2') {
            this.logger.info({
                tnx: 'smtp',
                username: this._auth.user,
                action: 'authfail',
                method: this._authMethod
            }, 'User %s failed to authenticate', JSON.stringify(this._auth.user));
            return callback(this._formatError('Invalid login', 'EAUTH', str, 'AUTH ' + this._authMethod));
        }

        this.logger.info({
            tnx: 'smtp',
            username: this._auth.user,
            action: 'authenticated',
            method: this._authMethod
        }, 'User %s authenticated', JSON.stringify(this._auth.user));
        this.authenticated = true;
        callback(null, true);
    }

    /**
     * Handle response for a MAIL FROM: command
     *
     * @param {String} str Message from the server
     */
    _actionMAIL(str, callback) {
        let message, curRecipient;
        if (Number(str.charAt(0)) !== 2) {
            if (this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.from)) {
                message = 'Internationalized mailbox name not allowed';
            } else {
                message = 'Mail command failed';
            }
            return callback(this._formatError(message, 'EENVELOPE', str, 'MAIL FROM'));
        }

        if (!this._envelope.rcptQueue.length) {
            return callback(this._formatError('Can\'t send mail - no recipients defined', 'EENVELOPE', false, 'API'));
        } else {
            this._recipientQueue = [];

            if (this._supportedExtensions.includes('PIPELINING')) {
                while (this._envelope.rcptQueue.length) {
                    curRecipient = this._envelope.rcptQueue.shift();
                    this._recipientQueue.push(curRecipient);
                    this._responseActions.push(str => {
                        this._actionRCPT(str, callback);
                    });
                    this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
                }
            } else {
                curRecipient = this._envelope.rcptQueue.shift();
                this._recipientQueue.push(curRecipient);
                this._responseActions.push(str => {
                    this._actionRCPT(str, callback);
                });
                this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
            }
        }
    }

    /**
     * Handle response for a RCPT TO: command
     *
     * @param {String} str Message from the server
     */
    _actionRCPT(str, callback) {
        let message, err, curRecipient = this._recipientQueue.shift();
        if (Number(str.charAt(0)) !== 2) {
            // this is a soft error
            if (this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(curRecipient)) {
                message = 'Internationalized mailbox name not allowed';
            } else {
                message = 'Recipient command failed';
            }
            this._envelope.rejected.push(curRecipient);
            // store error for the failed recipient
            err = this._formatError(message, 'EENVELOPE', str, 'RCPT TO');
            err.recipient = curRecipient;
            this._envelope.rejectedErrors.push(err);
        } else {
            this._envelope.accepted.push(curRecipient);
        }

        if (!this._envelope.rcptQueue.length && !this._recipientQueue.length) {
            if (this._envelope.rejected.length < this._envelope.to.length) {
                this._responseActions.push(str => {
                    this._actionDATA(str, callback);
                });
                this._sendCommand('DATA');
            } else {
                err = this._formatError('Can\'t send mail - all recipients were rejected', 'EENVELOPE', str, 'RCPT TO');
                err.rejected = this._envelope.rejected;
                err.rejectedErrors = this._envelope.rejectedErrors;
                return callback(err);
            }
        } else if (this._envelope.rcptQueue.length) {
            curRecipient = this._envelope.rcptQueue.shift();
            this._recipientQueue.push(curRecipient);
            this._responseActions.push(str => {
                this._actionRCPT(str, callback);
            });
            this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
        }
    }

    /**
     * Handle response for a DATA command
     *
     * @param {String} str Message from the server
     */
    _actionDATA(str, callback) {
        // response should be 354 but according to this issue https://github.com/eleith/emailjs/issues/24
        // some servers might use 250 instead, so lets check for 2 or 3 as the first digit
        if (!/^[23]/.test(str)) {
            return callback(this._formatError('Data command failed', 'EENVELOPE', str, 'DATA'));
        }

        let response = {
            accepted: this._envelope.accepted,
            rejected: this._envelope.rejected
        };

        if (this._envelope.rejectedErrors.length) {
            response.rejectedErrors = this._envelope.rejectedErrors;
        }

        callback(null, response);
    }

    /**
     * Handle response for a DATA stream when using SMTP
     * We expect a single response that defines if the sending succeeded or failed
     *
     * @param {String} str Message from the server
     */
    _actionSMTPStream(str, callback) {
        if (Number(str.charAt(0)) !== 2) {
            // Message failed
            return callback(this._formatError('Message failed', 'EMESSAGE', str, 'DATA'));
        } else {
            // Message sent succesfully
            return callback(null, str);
        }
    }

    /**
     * Handle response for a DATA stream
     * We expect a separate response for every recipient. All recipients can either
     * succeed or fail separately
     *
     * @param {String} recipient The recipient this response applies to
     * @param {Boolean} final Is this the final recipient?
     * @param {String} str Message from the server
     */
    _actionLMTPStream(recipient, final, str, callback) {
        let err;
        if (Number(str.charAt(0)) !== 2) {
            // Message failed
            err = this._formatError('Message failed for recipient ' + recipient, 'EMESSAGE', str, 'DATA');
            err.recipient = recipient;
            this._envelope.rejected.push(recipient);
            this._envelope.rejectedErrors.push(err);
            for (let i = 0, len = this._envelope.accepted.length; i < len; i++) {
                if (this._envelope.accepted[i] === recipient) {
                    this._envelope.accepted.splice(i, 1);
                }
            }
        }
        if (final) {
            return callback(null, str);
        }
    }

    _handleXOauth2Token(isRetry, callback) {
        this._auth.oauth2.getToken(isRetry, (err, accessToken) => {
            if (err) {
                this.logger.info({
                    tnx: 'smtp',
                    username: this._auth.user,
                    action: 'authfail',
                    method: this._authMethod
                }, 'User %s failed to authenticate', JSON.stringify(this._auth.user));
                return callback(this._formatError(err, 'EAUTH', false, 'AUTH XOAUTH2'));
            }
            this._responseActions.push(str => {
                this._actionAUTHComplete(str, isRetry, callback);
            });
            this._sendCommand('AUTH XOAUTH2 ' + this._auth.oauth2.buildXOAuth2Token(accessToken));
        });
    }

    _getHostname() {
        // defaul hostname is machine hostname or [IP]
        let defaultHostname = os.hostname() || '';

        // ignore if not FQDN
        if (defaultHostname.indexOf('.') < 0) {
            defaultHostname = '[127.0.0.1]';
        }

        // IP should be enclosed in []
        if (defaultHostname.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
            defaultHostname = '[' + defaultHostname + ']';
        }

        return defaultHostname;
    }
}

module.exports = SMTPConnection;


text:
package examples

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strconv"

	"github.com/moov-io/ach"
)

// Example_enrRead reads a ENR file
func Example_enrRead() {
	f, err := os.Open(filepath.Join("testdata", "enr-read.ach"))
	if err != nil {
		log.Fatal(err)
	}
	r := ach.NewReader(f)
	achFile, err := r.Read()
	if err != nil {
		panic(fmt.Sprintf("Issue reading file: %+v \n", err))
	}
	// ensure we have a validated file structure
	if achFile.Validate(); err != nil {
		fmt.Printf("Could not validate entire read file: %v", err)
	}
	// If you trust the file but it's formatting is off building will probably resolve the malformed file.
	if err := achFile.Create(); err != nil {
		fmt.Printf("Could not create file with read properties: %v", err)
	}

	fmt.Printf("Total Amount: %s", strconv.Itoa(achFile.Batches[0].GetEntries()[0].Amount)+"\n")
	fmt.Printf("SEC Code: %s", achFile.Batches[0].GetHeader().StandardEntryClassCode+"\n")
	fmt.Printf("Payment Related Information: %s", achFile.Batches[0].GetEntries()[0].Addenda05[0].String()+"\n")

	// Output:
	// Total Amount: 0
	// SEC Code: ENR
	// Payment Related Information: 70522*12200004*3*123987654321*777777777*DOE*JOHN*1\                                00010000001
}

redpajama_set_name: RedPajamaGithub


text:
package itti.com.pl.ontology.common.dto;

public class Topic extends MetadataObject{

}


text:
package com.java110.code.back;

import com.java110.code.util.FileUtilBase;
import com.java110.utils.util.DateUtil;

import java.util.Map;

/**
 *
 */
public class GeneratorDtoBean extends BaseGenerator {

    /**
     * 拼装 查询数量
     *
     * @param data        数据
     * @param fileContext 文件内容
     * @return filContext 数据
     */
    private String dealVariableAndGetSet(Data data, String fileContext) {

        Map<String, String> params = data.getParams();

        String variable = "";
        String variableGetSet = "";

        for (String key : params.keySet()) {
            if ("operate".equals(key) || "bId".equals(key) || "statusCd".equals(key)|| "createTime".equals(key)) {
                continue;
            }
            variable += "private String " + key + ";\n";

            variableGetSet += "public String get" + toUpperCaseFirstOne(key) + "() {\n"
                    + "        return " + key + ";\n"
                    + "    }\n";
            variableGetSet += "public void set" + toUpperCaseFirstOne(key) + "(String " + key + ") {\n"
                    + "        this." + key + " = " + key + ";\n"
                    + "    }\n";

        }

        fileContext = fileContext.replace("$beanVariable$", variable);
        fileContext = fileContext.replace("$beanVariableGetSet$", variableGetSet);

        return fileContext;
    }

    /**
     * 生成代码
     *
     * @param data 数据
     */
    public void generator(Data data) throws Exception {
        StringBuffer sb = readFile(this.getClass().getResource("/template/dto.txt").getFile());
        String fileContext = sb.toString();
        fileContext = fileContext.replace("store", toLowerCaseFirstOne(data.getName()))
                .replace("@@templateCode@@",data.getName())
                .replace("Store", toUpperCaseFirstOne(data.getName()))
                .replace("商户", data.getDesc())
                .replace("@@date@@", DateUtil.getNow(DateUtil.DATE_FORMATE_STRING_A));

        fileContext = dealVariableAndGetSet(data, fileContext);
        String writePath = this.getClass().getResource("/").getPath()
                + "out/back/dto/" + data.getName() + "/" + toUpperCaseFirstOne(data.getName()) + "Dto.java";

        writeFile(writePath,
                fileContext);
        //复制生成的文件到对应分区目录下
        if (data.isAutoMove()) {
            FileUtilBase.copyfile(writePath, "java110-bean\\src\\main\\java\\com\\java110\\dto\\" + data.getName() + "/" + toUpperCaseFirstOne(data.getName()) + "Dto.java");
        }
    }
}


text:
<?xml version="1.0" encoding="CP850"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

  <meta content="text/html; charset=CP850" http-equiv="Content-Type" />

  <title>Class: HTTPAccess2::Session::KeepAliveDisconnected</title>

  <link rel="stylesheet" href="../../rdoc.css" type="text/css" media="screen" />

          Home
          Classes
          Methods

In Files

lib/http-access2.rb

Parent

HTTPAccess2::Session::Error

Class/Module Index

        <form action="#" method="get" accept-charset="utf-8" class="initially-hidden">
        <fieldset>
          <legend>Quicksearch</legend>
          <input type="text" name="quicksearch" value=""
            class="quicksearch-field" />
        </fieldset>
        </form>

HTTPClient

HTTPClient::AuthBase

HTTPClient::AuthFilterBase

HTTPClient::BadResponseError

HTTPClient::BasicAuth

HTTPClient::ConfigurationError

HTTPClient::ConnectTimeoutError

HTTPClient::Connection

HTTPClient::CookieManager

HTTPClient::CookieManager::Error

HTTPClient::CookieManager::ErrorOverrideOK

HTTPClient::CookieManager::SpecialError

HTTPClient::DebugSocket

HTTPClient::DigestAuth

HTTPClient::IncludeClient

HTTPClient::JRubySSLSocket

HTTPClient::JRubySSLSocket::BrowserCompatHostnameVerifier

HTTPClient::JRubySSLSocket::JSSEVerifyCallback

HTTPClient::JRubySSLSocket::JavaCertificate

HTTPClient::JRubySSLSocket::KeyStoreLoader

HTTPClient::JRubySSLSocket::PEMUtils

HTTPClient::JRubySSLSocket::SSLStoreContext

HTTPClient::JRubySSLSocket::SystemTrustManagerFactory

HTTPClient::JRubySSLSocket::SystemTrustManagerFactory::SystemTrustManager

HTTPClient::JRubySSLSocket::TrustStoreLoader

HTTPClient::JRubySSLSocket::VerifyNoneTrustManagerFactory

HTTPClient::JRubySSLSocket::VerifyNoneTrustManagerFactory::VerifyNoneTrustManager

HTTPClient::JavaSocketWrap

HTTPClient::LoopBackSocket

HTTPClient::NegotiateAuth

HTTPClient::OAuth

HTTPClient::OAuth::Config

HTTPClient::ProxyAuth

HTTPClient::ProxyBasicAuth

HTTPClient::ProxyDigestAuth

HTTPClient::ReceiveTimeoutError

HTTPClient::SSLConfig

HTTPClient::SSLSocket

HTTPClient::SSPINegotiateAuth

HTTPClient::SendTimeoutError

HTTPClient::Session

HTTPClient::Session::BadResponse

HTTPClient::Session::LenientInflater

HTTPClient::SessionManager

HTTPClient::Site

HTTPClient::SocketWrap

HTTPClient::Timeout

HTTPClient::TimeoutError

HTTPClient::TimeoutScheduler

HTTPClient::TimeoutScheduler::Period

HTTPClient::Util

HTTPClient::Util::AddressableURI

HTTPClient::WWWAuth

HTTPClient::WebAgentSaver

HTTPAccess2

HTTPAccess2::Client

HTTPAccess2::Client::RetryableResponse

HTTPAccess2::Session

HTTPAccess2::Session::BadResponse

HTTPAccess2::Session::Error

HTTPAccess2::Session::InvalidState

HTTPAccess2::Session::KeepAliveDisconnected

HTTP

HTTP::Message

HTTP::Message::Array

HTTP::Message::Body

HTTP::Message::Body::Parts

HTTP::Message::Headers

HTTP::Status

WebAgent

WebAgent::Cookie

WebAgent::CookieManager

WebAgent::CookieUtils

WebAgent::SynchronizedArray

OpenSSL

OpenSSL::X509

OpenSSL::X509::Certificate

URI

URI::Generic

HexDump

JSONClient

OAuthClient

String

No matching classes.

HTTPAccess2::Session::KeepAliveDisconnected

[Validate]

Generated with the Darkfish
      Rdoc Generator 2.


text:
/************************************************************************/
/*																		*/
/*	MtdsCore.c	--	Low Level Support Functions For mtds.cpp			*/
/*																		*/
/************************************************************************/
/*	Author: 	Gene Apperson											*/
/*	Copyright 2016, Digilent, Inc. All rights reserved.					*/
/************************************************************************/
/*  Module Description: 												*/
/*																		*/
/*	This module contains utility support functions for the Digilent		*/
/*	Multi-Touch Display Shield library defined in mtds.cpp.				*/
/*																		*/
/************************************************************************/
/*  Revision History:													*/
/*																		*/
/* 2015/08/03(GeneApperson): Created									*/
/* 2015/10/05(GeneApperson): Renamed from MtdsUtil.cpp					*/
/* 2016-09-27(GeneApperson): Moved low level driver functions to new	*/
/*		module MtdsHal.cpp to support having a hardware abstraction		*/
/*		layer to make the library portable between platforms.			*/
/* 2016-09-27(GeneApperson): Changed conditional on __SIM__ to being	*/
/*		conditional on MPIDE as part of change to using a HAL layer		*/
/*		to make library portable between platforms.	This conditional	*/
/*		was really to put out debugging information when testing under	*/
/*		MPIDE															*/
/* 12/20/2017(atangzwj): Replaced delay functions with sleep            */
/*																		*/
/************************************************************************/

#define	OPT_BOARD_INTERNAL

/* ------------------------------------------------------------ */
/*				Include File Definitions						*/
/* ------------------------------------------------------------ */

#include	<stdint.h>

#include	"ProtoDefs.h"
#include	"mtds.h"
#include	"MtdsCore.h"
#include	"MtdsHal.h"
#include "sleep.h"

/* ------------------------------------------------------------ */
/*				Local Type Definitions							*/
/* ------------------------------------------------------------ */

//#define __MTDSDEBUG__
//#define __MTDSTRACE__

/* ------------------------------------------------------------ */
/*				Global Variables								*/
/* ------------------------------------------------------------ */

uint8_t	rgbMtdsRetVal[cbRetValInit];

CHDR	chdrMtdsCmd;
DHDR	dhdrMtdsData;

RHDR *	prhdrMtdsRet = (RHDR *)rgbMtdsRetVal;
DHDR *	pdhdrMtdsData = &dhdrMtdsData;

/* ------------------------------------------------------------ */
/*				Local Variables									*/
/* ------------------------------------------------------------ */

/* ------------------------------------------------------------ */
/*				Forward Declarations							*/
/* ------------------------------------------------------------ */

/* ------------------------------------------------------------ */
/*				Procedure Definitions							*/
/* ------------------------------------------------------------ */
/***	MtdsSendCmdPacket(cls, cmd, cbParam, pbParam)
**
**	Parameters:
**		cls		- command class
**		cmd		- command code
**		cbParam	- number of bytes of parameter data in command packet
**		pbParam	- pointer to parameter data to send
**
**	Return Values:
**		none
**
**	Errors:
**		none
**
**	Description:
**		Send the specified command packet to the shield.
*/
void MtdsSendCmdPacket(uint8_t cls, uint8_t cmd, uint16_t cbParam, uint8_t * pbParam) {

	/* Set up the packet header.
	*/
	chdrMtdsCmd.cls = cls;
	chdrMtdsCmd.cmd = cmd;
	chdrMtdsCmd.cb = cbParam;

	/* Send the packet.
	*/
	MtdsHalEnableSlave(true);

	MtdsSendData(sizeof(CHDR), (uint8_t *)&chdrMtdsCmd);

	if (cbParam > 0) {
		MtdsSendData(cbParam, pbParam);
	}

	MtdsHalEnableSlave(false);

}

/* ------------------------------------------------------------ */
/***	MtdsReadStatusPacket()
**
**	Parameters:
**		none
**
**	Return Values:
**		none
**
**	Errors:
**		Returns status code
**
**	Description:
**		Read a status packet into the status packet buffer.
*/

bool MtdsReadStatusPacket() {
	bool	fStat;

	fStat = true;
	MtdsHalEnableSlave(true);

	if (MtdsReadRhdr(rgbMtdsRetVal)) {
		/* We got a good status packet header. Read the rest of the packet.
		*/
		if (prhdrMtdsRet->cb != 0) {
			MtdsReadData(prhdrMtdsRet->cb, &rgbMtdsRetVal[sizeof(RHDR)]);
		}
	}
	else {
		/* We got an error reading the header. This means that the first real
		** byte that we got back wasn't the class byte for a status packet header,
		** we timed out, or there was an internal inconsistency in the packet header.
		** The cls byte of the RHDR structure contains the channel status byte
		** indicating what happened. Return failure and let the higher level code
		** figure out how to recover.
		*/
		fStat = false;
	}

	MtdsHalEnableSlave(false);

	return fStat;
}

/* ------------------------------------------------------------ */
/***	MtdsSendDataPacket(cmd, cb, pb)
**
**	Parameters:
**		cmd		- command value to put in packet header
**		cb		- number of bytes for packet payload
**		pb		- pointer to packet payload to send
**
**	Return Values:
**		none
**
**	Errors:
**		none
**
**	Description:
**		Send the specified data packet to the shield.
*/
void MtdsSendDataPacket(uint8_t cmd, uint16_t cb, uint8_t * pb) {

	/* Set up the packet header.
	*/
	dhdrMtdsData.cls = clsDataOut;
	dhdrMtdsData.cmd = cmd;
	dhdrMtdsData.cb = cb;

	/* Send the packet payload.
	*/
	MtdsHalEnableSlave(true);
	MtdsSendData(sizeof(DHDR), (uint8_t *)&dhdrMtdsData);
	MtdsSendData(cb, pb);
	MtdsHalEnableSlave(false);

}

/* ------------------------------------------------------------ */
/***	MtdsReadDataPacket(cbMax, pb, pcbRes)
**
**	Parameters:
**		cbMax		- maximum number of bytes to read
**		pb			- pointer to buffer to receive the data
**		pcbRes		- pointer to variable to receive actual number of bytes read
**
**	Return Values:
**		none
**
**	Errors:
**		none
**
**	Description:
**		Read a data packet header and then read the packet payload
**		into the specified buffer.
*/

bool MtdsReadDataPacket(uint16_t cbMax, uint8_t * pb, uint16_t * pcbRes) {
	uint16_t	cbCur;
	uint16_t	cbRem;
	bool		fStat;

	fStat = true;
	*pcbRes = 0;
	MtdsHalEnableSlave(true);

	if (MtdsReadDhdr((uint8_t *)&dhdrMtdsData)) {
		/* We have the data in packet header. We need to read up to the requested amount
		** of data into the specified buffer.
		*/
		cbCur = dhdrMtdsData.cb;
		if (cbCur > cbMax) {
			cbCur = cbMax;
		}
		if (cbCur > 0) {
			MtdsReadData(cbCur, pb);
			*pcbRes = cbCur;
		}

		/* Discard any data from the data-in packet that won't fit in the return buffer.
		*/
		cbRem = dhdrMtdsData.cb - cbCur;
		if (cbRem > 0) {
			MtdsReadData(cbRem, 0);
		}
	}
	else {
		/* We got an error reading the header. This means that the first real
		** byte that we got back wasn't the class byte for a data packet header,
		** we timed out, or there was an internal inconsistency in the packet header.
		** The cls byte of the DHDR structure contains the channel status byte
		** indicating what happened. Return failure and let the higher level code
		** figure out how to recover.
		*/
		fStat = false;
	}

	MtdsHalEnableSlave(false);

	return fStat;

}

/* ------------------------------------------------------------ */
/***	MtdsReadRhdr(pb)
**
**	Parameters:
**		pb		- buffer to receive the data
**
**	Return Values:
**		none
**
**	Errors:
**		Returns true if successful, false if we get an error.
**
**	Description:
**		Read a status packet header from the shield.
*/

bool MtdsReadRhdr(uint8_t * pb) {
	uint8_t		bRcv;
	int			cbCur;
	RHDR *		prhdr;
	uint32_t	tmsStart;

	cbCur = 0;
	prhdr = (RHDR *)pb;

	/* It may take a while for the shield to process the read status command
	** and to begin sending the status packet. While it is processing the command
	** we will get back "chnStaBusy" bytes. We may also get some "chnStaIdle" bytes
	** before we get the actual status packet. We need to skip past those until
	** we get the actual status packet header.
	*/
	tmsStart = MtdsHalTmsElapsed();
	while (true) {
		usleep(tusPacketDelay);
		while (!MtdsHalSpiReady()) {
		}
		bRcv = MtdsHalPutSpiByte(chnCmdRead);
		if ((bRcv & mskPacketCls) == clsPacketSta) {
			break;
		}
		if ((MtdsHalTmsElapsed() - tmsStart) > tmsRhdrTimeout) {
#if defined(MPIDE)
#if defined(__MTDSTRACE__)
			Serial.println("!RdRhdr timeout");
#endif
#endif
			prhdr->cls = chnStaTimeout;
			return false;
		}
	}

	/* Stick the first byte in the buffer.
	*/
	*pb++ = bRcv;
	cbCur += 1;

	/* Check that we have the class byte for a status packet.
	*/
	if ((bRcv & mskPacketCls) != clsPacketSta) {
		/* We don't have the correct initial byte for a status packet.
		*/
#if defined(MPIDE)
#if defined(__MTDSTRACE__)
		Serial.print("!RdRhdr: ");
		Serial.println(bRcv, HEX);
#endif
#endif
		return false;
	}

	/* Read the rest of the header.
	*/
	while (cbCur < sizeof(RHDR)) {
		while (!MtdsHalSpiReady()) {
		}
		bRcv = MtdsHalPutSpiByte(chnCmdRead);
		*pb++ = bRcv;
		cbCur +=1;
	}

	if (prhdr->cb > cbRhdrDataMax) {
		/* The cb field is too large for this to be a valid header. Either the host
		** sent us a bad packet, or it got screwed up by a channel error.
		*/
		prhdr->cls = chnStaDataSize;
#if defined(MPIDE)
#if defined(__MTDSTRACE__)
		Serial.println("!RdRhdr data size");
#endif
#endif
		return false;
	}

	return true;

}

/* ------------------------------------------------------------ */
/***	MtdsReadDhdr(pb)
**
**	Parameters:
**		pb		- buffer to receive the data
**
**	Return Values:
**		none
**
**	Errors:
**		Returns true if successful, false if we get an error.
**
**	Description:
**		Read a data packet header from the shield.
*/

bool MtdsReadDhdr(uint8_t * pb) {
	uint8_t		bRcv;
	int			cbCur;
	DHDR *		pdhdr;
	uint32_t	tmsStart;

	cbCur = 0;
	pdhdr = (DHDR *)pb;

	/* It may take a while for the shield to process the command and to begin
	** sending the data packet.
	** While it is processing the command we will get back "chnStaBusy" bytes.
	** We need to skip past these. If the shield generated a data in packet, we
	** will get clsDataIn. If the shield terminated the command and didn't generate
	** a data packet, we will get chnStaDone.
	*/
	tmsStart = MtdsHalTmsElapsed();
	while (true) {
		usleep(tusPacketDelay);
		while (!MtdsHalSpiReady()) {
		}
		bRcv = MtdsHalPutSpiByte(chnCmdRead);
		if ((bRcv == clsDataIn) || (bRcv == chnStaDone)) {
			break;
		}
		if ((MtdsHalTmsElapsed() - tmsStart) > tmsDhdrTimeout) {
#if defined(MPIDE)
#if defined(__MTDSTRACE__)
			Serial.println("!RdDhdr timeout");
#endif
#endif
			pdhdr->cls = chnStaTimeout;
			return false;
		}
	}

	/* Stick the first byte in the buffer.
	*/
	*pb++ = bRcv;
	cbCur += 1;

	/* Check that we have the class byte for a data packet.
	*/
	if (bRcv != clsDataIn) {
		return false;
	}

	/* Read the rest of the header.
	*/
	while (cbCur < sizeof(DHDR)) {
		while (!MtdsHalSpiReady()) {
		}
		bRcv = MtdsHalPutSpiByte(chnCmdRead);
		*pb++ = bRcv;
		cbCur +=1;
	}

	if (pdhdr->cb > cbDhdrDataInMax) {
		/* The cb field is too large for this to be a valid header. Either the host
		** sent us a bad packet, or it got screwed up by a channel error.
		*/
		pdhdr->cls = chnStaDataSize;
#if defined(MPIDE)
#if defined(__MTDSTRACE__)
		Serial.println("!RdDhdr data size");
#endif
#endif
		return false;
	}

	return true;

}

/* ------------------------------------------------------------ */
/***	MtdsSendData(cb, pb)
**
**	Parameters:
**		cb		- number of bytes to send
**		pb		- pointer to the bytes to send
**
**	Return Values:
**		none
**
**	Errors:
**		none
**
**	Description:
**		This functions writes the specified number of bytes into the
**		transmit buffer to the shield. Because the shield uses the SPI
**		port in enhanced mode with FIFO, we can burst the data quickly.
**		However, we need to be careful to not overrun the FIFO, so we
**		burst it in bursts of a maximum of 8 bytes and then wait a bit.
*/

void MtdsSendData(uint16_t cb, uint8_t * pb) {
	int		cbCur;

	for (cbCur = 0; cbCur < cb; cbCur++) {
		while (!MtdsHalSpiReady()) {
		}
		MtdsHalPutSpiByte(*pb++);
	}

}

/* ------------------------------------------------------------ */
/***	MtdsReadData(cb, pb)
**
**	Parameters:
**		cb		- number of bytes to receive from shield
**		pb		- buffer to receive the data, pb==0 means to discard the data
**
**	Return Values:
**		none
**
**	Errors:
**		Returns true if successful, false if a time-out occurs.
**
**	Description:
**		Read the specified number of bytes from the shield.
*/

void MtdsReadData(uint16_t cb, uint8_t * pb) {
	int		cbCur;
	uint8_t	bRcv;

	cbCur = 0;

	while(cbCur < cb) {
		while (!MtdsHalSpiReady()) {
		}
		bRcv = MtdsHalPutSpiByte(chnCmdRead);
		if (pb != 0) {
			*pb++ = bRcv;
		}
		cbCur += 1;
	}

}

/* ------------------------------------------------------------ */
/***	MtdsWaitUntilShieldIdle()
**
**	Parameters:
**		none
**
**	Return Values:
**		none
**
**	Errors:
**		none
**
**	Description:
**		Wait until the shield indicates that it had finished executing the
**		last command.
*/

bool MtdsWaitUntilShieldIdle() {
	uint8_t		bTmp;
	uint32_t	tmsStart;

	tmsStart = MtdsHalTmsElapsed();
	while (true) {
		usleep(tusPacketDelay);
		while (!MtdsHalSpiReady()) {
		}
		MtdsHalEnableSlave(true);
		bTmp = MtdsHalPutSpiByte(chnCmdRead);
		MtdsHalEnableSlave(false);
		if (bTmp == chnStaIdle) {
			break;
		}
#if !defined(__MTDSDEBUG__)
		if ((MtdsHalTmsElapsed() - tmsStart) > tmsIdleTimeout) {
#if defined(MPIDE)
#if defined(__MTDSTRACE__)
			Serial.println("!WaitIdle timeout");
#endif
#endif
			return false;
		}
#endif
	}

	return true;
}

/* ------------------------------------------------------------ */
/***	MtdsWaitUntilShieldBusy()
**
**	Parameters:
**		none
**
**	Return Values:
**		none
**
**	Errors:
**		none
**
**	Description:
**		Wait until we get a chnStaBusy from the shield. This indicates that the
**		SPI FIFOs have been flushed of any previous characters that may have been
**		there prior to the transmission of the command packet.
*/

bool MtdsWaitUntilShieldBusy() {
	uint8_t		bTmp;
	uint32_t	tmsStart;

	tmsStart = MtdsHalTmsElapsed();
	while (true) {
		usleep(tusPacketDelay);
		while (!MtdsHalSpiReady()) {
		}
		MtdsHalEnableSlave(true);
		bTmp = MtdsHalPutSpiByte(chnCmdRead);
		MtdsHalEnableSlave(false);
		if (bTmp == chnStaBusy) {
			break;
		}
#if !defined(__MTDSDEBUG__)
		if ((MtdsHalTmsElapsed() - tmsStart) > tmsBusyTimeout) {
#if defined(MPIDE)
#if defined(__MTDSTRACE__)
			Serial.println("!WaitBusy timeout");
#endif
#endif
			return false;
		}
#endif
	}

	return true;
}

/* ------------------------------------------------------------ */
/***	MtdsWaitUntilShieldDone()
**
**	Parameters:
**		none
**
**	Return Values:
**		none
**
**	Errors:
**		none
**
**	Description:
**		Wait until the shield indicates that it had finished executing the
**		last command.
*/

bool MtdsWaitUntilShieldDone() {
	uint8_t		bTmp;
	uint32_t	tmsStart;

	tmsStart = MtdsHalTmsElapsed();
	while (true) {
		usleep(tusPacketDelay);
		while (!MtdsHalSpiReady()) {
		}
		MtdsHalEnableSlave(true);
		bTmp = MtdsHalPutSpiByte(chnCmdRead);
		MtdsHalEnableSlave(false);
		if (bTmp == chnStaDone) {
			break;
		}
#if !defined(__MTDSDEBUG__)
		if ((MtdsHalTmsElapsed() - tmsStart) > tmsIdleTimeout) {
#if defined(MPIDE)
#if defined(__MTDSTRACE__)
			Serial.println("!WaitDone timeout");
#endif
#endif
			return false;
		}
#endif
	}

	return true;
}

/* ------------------------------------------------------------ */
/***	MtdsWaitUntilShieldReady()
**
**	Parameters:
**		none
**
**	Return Values:
**		Returns the ending condition.
**
**	Errors:
**		none
**
**	Description:
**		Wait until the shield indicates that it is ready to accept a
**		data out packet. The shield will return chnStaReady when it is ready
**		to accept a data packet.
**		Normally this function returns when the shield has sent chnStaReady. However,
**		it can terminate because the shield aborted the command by returning
**		chnStaAbort, or we timed out waiting for the shield to become ready or
**		abort.
**		This function returns the character that caused the end condition, or 0 if
**		it timed out.
*/

uint8_t MtdsWaitUntilShieldReady() {
	uint8_t	bTmp;
	uint32_t	tmsStart;

	tmsStart = MtdsHalTmsElapsed();
	while (true) {
		usleep(tusPacketDelay);
		while (!MtdsHalSpiReady()) {
		}
		MtdsHalEnableSlave(true);
		bTmp = MtdsHalPutSpiByte(chnCmdRead);
		MtdsHalEnableSlave(false);
		if ((bTmp == chnStaReady) || (bTmp == chnStaAbort)) {
			/* We are waiting for the shield to indicate that it is ready for a
			** data packet, or that it wants to abort the command.
			*/
			break;
		}
#if !defined(__MTDSDEBUG__)
		if ((MtdsHalTmsElapsed() - tmsStart) > tmsReadyTimeout) {
			/* We timed out waiting for the expected response.
			*/
#if defined(MPIDE)
#if defined(__MTDSTRACE__)
			Serial.println("!WaitReady timeout");
#endif
#endif
			return chnStaTimeout;
		}
#endif
	}

	return bTmp;
}

/* ------------------------------------------------------------ */
/***	MtdsWaitUntilShieldSync()
**
**	Parameters:
**		none
**
**	Return Values:
**		none
**
**	Errors:
**		none
**
**	Description:
**		Wait until the shield indicates that it is ready to accept a
**		data out packet. It will return chnStaReady when it is ready
**		to accept a data packet.
*/

bool MtdsWaitUntilShieldSync() {
	uint8_t	bTmp;
	uint32_t	tmsStart;

	tmsStart = MtdsHalTmsElapsed();
	while (true) {
		usleep(tusPacketDelay);
		while (!MtdsHalSpiReady()) {
		}
		MtdsHalEnableSlave(true);
		bTmp = MtdsHalPutSpiByte(chnCmdRead);
		MtdsHalEnableSlave(false);
		if (bTmp == chnStaSync) {
			break;
		}
#if !defined(__MTDSDEBUG__)
		if ((MtdsHalTmsElapsed() - tmsStart) > tmsSyncTimeout) {
#if defined(MPIDE)
#if defined(__MTDSTRACE__)
			Serial.println("!WaitSync timeout");
#endif
#endif
			return false;
		}
#endif
	}

	return true;
}

/* ------------------------------------------------------------ */
/***	MtdsResumeChannel()
**
**	Parameters:
**		none
**
**	Return Values:
**		none
**
**	Errors:
**		Returns true if successful, false if it times out waiting for shield
**
**	Description:
**		Following an abort of the command by the shield, we need to send
**		chnCmdResume until it goes to the idle state.
*/

bool MtdsResumeChannel() {
	uint8_t	bTmp;
	uint32_t	tmsStart;

	tmsStart = MtdsHalTmsElapsed();

	while (true) {
		usleep(tusPacketDelay);
		while (!MtdsHalSpiReady()) {
		}
		MtdsHalEnableSlave(true);
		bTmp = MtdsHalPutSpiByte(chnCmdResume);
		MtdsHalEnableSlave(false);
		if (bTmp == chnStaDone) {
			break;
		}
#if !defined(__MTDSDEBUG__)
		if ((MtdsHalTmsElapsed() - tmsStart) > tmsResumeTimeout) {
#if defined(MPIDE)
#if defined(__MTDSTRACE__)
			Serial.println("!WaitResume timeout");
#endif
#endif
			return false;
		}
#endif
	}

	return true;
}

/* ------------------------------------------------------------ */
/***	MtdsBeginUpdate()
**
**	Parameters:
**		none
**
**	Return Values:
**		none
**
**	Errors:
**		Returns chnStaSuccess if successful, other channel status code if not
**
**	Description:
**		This issues the channel command to the shield firmware to
**		launch the firmware update process.
*/

uint8_t MtdsBeginUpdate() {
	uint8_t		bTmp;
	uint32_t	tmsStart;

	tmsStart = MtdsHalTmsElapsed();

	while (true) {
		usleep(tusPacketDelay);
		while (!MtdsHalSpiReady()) {
		}
		MtdsHalEnableSlave(true);
		bTmp = MtdsHalPutSpiByte(chnCmdUpdate);
		MtdsHalEnableSlave(false);
		if (bTmp == chnStaUpdate) {
			break;
		}
		if ((bTmp == chnStaNak) || (bTmp == chnStaAbort) ) {
			return bTmp;
		}

#if !defined(__MTDSDEBUG__)
		if ((MtdsHalTmsElapsed() - tmsStart) > tmsUpdateTimeout) {
#if defined(MPIDE)
#if defined(__MTDSTRACE__)
			Serial.println("!WaitUpdate timeout");
#endif
#endif
			return chnStaTimeout;
		}
#endif
	}

	return chnStaSuccess;

}

/* ------------------------------------------------------------ */
/***	MtdsQueryUpdate()
**
**	Parameters:
**		none
**
**	Return Values:
**		Returns update state code returned by the display device
**
**	Errors:
**		Errors are indicated by the update status code returned
**
**	Description:
**		Send chnCmdRead to the display device and get the returned
**		character.
*/

uint8_t	MtdsQueryUpdate() {
	uint8_t		bTmp;
	uint8_t		sta;

	usleep(tusPacketDelay);
	while (!MtdsHalSpiReady()) {
	}
	MtdsHalEnableSlave(true);
	bTmp = MtdsHalPutSpiByte(chnCmdRead);
	MtdsHalEnableSlave(false);

	if (bTmp == chnStaNak) {
		/* The firmware updater got a fatal error. The update failed and we
		** can't recover.
		*/
		sta = staUpdateError;
	}
	else if (bTmp == chnStaAbort) {
		/* The firmware updater was unable to start the update process, but
		** the current firmware image is still intact. Tell the updater to
		** resume the current firmware image. The updater will transfer control
		** to the reset entry point for the firmware image. We send chnCmdResume
		** until we get back chnStaStartup. This indicates that the device firmware
		** is doing its reset initialization.
		*/
		while (true) {
			usleep(tusPacketDelay);
			while (!MtdsHalSpiReady()) {
			}
			MtdsHalEnableSlave(true);
			bTmp = MtdsHalPutSpiByte(chnCmdResume);
			MtdsHalEnableSlave(false);
			if (bTmp == chnStaStartup) {
				break;
			}
		}
		sta = staUpdateAbort;
	}
	else if (bTmp == chnStaStartup) {
		/* The firmware updater has completed the update and transferred control
		** to the beginning of the new firmware image. It is in the process of
		** initializing itself.
		*/
		sta = staUpdateSuccess;
	}
	else {
		/* The updater is still working.
		*/
		sta = staUpdateBusy;
	}

	return sta;

}

/* ------------------------------------------------------------ */
/***	ProcName
**
**	Parameters:
**
**	Return Values:
**
**	Errors:
**
**	Description:
**
*/

/* ------------------------------------------------------------ */
/***	ProcName
**
**	Parameters:
**
**	Return Values:
**
**	Errors:
**
**	Description:
**
*/

/* ------------------------------------------------------------ */

/************************************************************************/


text:
package eu.appbucket.queue.web.controller2;

import eu.appbucket.queue.core.domain.queue.QueueDetails;
import eu.appbucket.queue.core.domain.queue.QueueInfo;
import eu.appbucket.queue.core.domain.ticket.TicketEstimation;
import eu.appbucket.queue.core.domain.ticket.TicketUpdate;
import eu.appbucket.queue.core.service.QueueService;
import eu.appbucket.queue.core.service2.TicketService;
import eu.appbucket.queue.web.domain.ticket.TicketInput;
import eu.appbucket.queue.web.domain.ticket.TicketStatus;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.Date;

@Controller(value = "ticketController2")
public class TicketController {

	private static final Logger LOGGER = Logger.getLogger(TicketController.class);
	private QueueService queueService;
	private TicketService ticketService;

	@Autowired
	public void setQueueService(QueueService queueService) {
		this.queueService = queueService;
	}

	@Autowired
	public void setTicketService2(TicketService ticketService) {
		this.ticketService = ticketService;
	}

    @RequestMapping(value = {"queues/{queueId}/tickets/{ticketId}", "v2/queues/{queueId}/tickets/{ticketId}"}, method = RequestMethod.POST)
    @ResponseBody
    public TicketStatus postTicketUpdate(@PathVariable int queueId, @PathVariable int ticketId,
                                         @RequestBody TicketInput ticketInput) {
        LOGGER.info("postTicketUpdate - queueId: " + queueId + ", ticketId: " + ticketId + ", ticketInput: " + ticketInput);
        saveUserInput(queueId, ticketId, ticketInput);
        TicketStatus ticketStatus = getTicketStatus(queueId, ticketId);
        LOGGER.info("postTicketUpdate - waiting time: " + ticketStatus.getWaitingTime());
        return ticketStatus;
    }

    private void saveUserInput(int queueId, int ticketId, TicketInput ticketInput) {
        QueueInfo queueInfo = queueService.getQueueInfoByQueueId(queueId);
        TicketUpdate ticketUpdate = TicketUpdate.fromTicketInputAndQueueInfo(ticketId, ticketInput, queueInfo);
        ticketService.persistTicketUpdate(ticketUpdate);
    }

    private TicketStatus getTicketStatus(int queueId, int ticketId) {
        QueueInfo queueInfo = queueService.getQueueInfoByQueueId(queueId);
        QueueDetails queueDetails = queueService.getQueueDetailsByQueueId(queueId);
        TicketEstimation ticketEstimation = ticketService.getTicketEstimation(queueInfo, queueDetails, ticketId);
        TicketStatus ticketStatus = TicketStatus.fromTicketEstimation(ticketEstimation);
        LOGGER.info("getTicketStatus - waiting time in milliseconds: " + ticketStatus.getWaitingTime() +
                ", waiting time: " + new Date(ticketStatus.getWaitingTime()));
        return ticketStatus;
    }

	@RequestMapping(value = {"queues/{queueId}/tickets/{ticketId}", "v2/queues/{queueId}/tickets/{ticketId}"}, method = RequestMethod.GET)
	@ResponseBody
	public TicketStatus getTicketStats(@PathVariable int queueId, @PathVariable int ticketId) {
		LOGGER.info("getTicketStats - queueId: " + queueId + ", ticketId: " + ticketId);
        TicketStatus ticketStatus = getTicketStatus(queueId, ticketId);
        LOGGER.info("getTicketStats - waiting time: " + ticketStatus.getWaitingTime());
		return ticketStatus;
	}

}


text:
import unittest
import sys

from os import path

from mock import Mock, MagicMock, patch

from twisted.python import log
from twisted.protocols.amp import AMP
from twisted.protocols.policies import TimeoutMixin
from twisted.test.proto_helpers import StringTransport
from twisted.internet import defer, reactor, ssl
from twisted.internet.protocol import Factory,Protocol, ServerFactory, ClientFactory
from twisted.internet import defer, protocol
from twisted.trial import unittest
from twisted.protocols.amp import AMP

sys.path.append(path.abspath(path.join(path.dirname(__file__), "..")))
import misc
from gs_interface import GroundStationInterface
from client_amp import Client, ClientReconnectFactory, CtxFactory
from client_ui import  SatNetUI
from threads import Threads
from ampCommands import Login, StartRemote
from errors import SlotErrorNotification

class MockServerFactory(Factory):
    active_protocols = {'xabi':'xabiprotocol'}
    active_connections = {}

class ServerProtocol(AMP):
    def connectionLost(self, *a):
        self.factory.onConnectionLost.callback(self)

    def login(self, sUsername, sPassword):

        if sUsername in self.factory.active_protocols:
            log.msg('Client already logged in, renewing...')
            # What's the point of logging an user already logged?
            return {'bAuthenticated': False}
        else:
            self.username = sUsername
            self.password = sPassword
            self.factory.active_protocols[sUsername] = None

        # To-do. Mock function call.
        # self.rpc = rpcrequests.SatnetRPC(sUsername, sPassword)
        self.rpc = Mock
        self.rpc.testing = MagicMock(return_value=True)

        self.factory.active_protocols[sUsername] = self

        log.msg('Connection made!, clients = ' + str(
            len(self.factory.active_protocols))
        )

        return {'bAuthenticated': True}

    Login.responder(login)

    def decode_user(self, slot):
        """Decodes the information of the client
        :param slot:
        :return:
        """
        if not slot:
            err_msg = 'No operational slots for the user'
            log.err(err_msg)
            raise SlotErrorNotification(err_msg)

        gs_user = slot['gs_username']
        sc_user = slot['sc_username']

        client_a = sc_user
        client_b = gs_user
        if gs_user == self.username:
            client_a = gs_user
            client_b = sc_user

        return gs_user, sc_user, client_a, client_b

    def check_slot_ownership(self, gs_user, sc_user):
        """Checks if this slot has not been assigned to this user
        :param gs_user: Username of the groundstation user
        :param sc_user: Username of the spacecraft user
        """

        # print '>>> gs_user = ' + str(gs_user)
        # print '>>> sc_user = ' + str(sc_user)
        # print '>>> self.username = ' + str(self.username)

        if gs_user != self.username and sc_user != self.username:
            err_msg = 'This slot has not been assigned to this user'
            log.err(err_msg)
            raise SlotErrorNotification(err_msg)

    def start_remote_user(self):
        """Start Remote
        This function implements the checks to be executed after a START REMOTE
        command coming from a user.
        """

        rpcrequests = Mock()
        rpcrequests.RPC_TEST_USER_GS = 'test-user-gs'
        rpcrequests.RPC_TEST_USER_SC = 'test-user-sc'

        if self.rpc.testing:
            slot = {
                'id': -1,
                'gs_username': rpcrequests.RPC_TEST_USER_GS,
                'sc_username': rpcrequests.RPC_TEST_USER_SC,
                'ending_time': None
            }
        else:
            slot = self.rpc.get_next_slot(self.username)

        gs_user, sc_user, client_a, client_c = self.decode_user(slot)
        self.check_slot_ownership(gs_user, sc_user)

        return slot, gs_user, sc_user, client_a, client_c

    def i_start_remote(self):
        """RPC Handler
        This function processes the remote request through the StartRemote AMP
        command.
        FIXME iSlotId is no longer necessary...
        :param iSlotId:
        """
        log.msg('(' + self.username + ') --------- Start Remote ---------')
        slot, gs_user, sc_user, client_a, client_c = self.start_remote_user()
        return self.create_connection(
            slot['ending_time'], slot['id'], client_a, client_c
        )

    StartRemote.responder(i_start_remote)

class ClientProtocol(AMP):
    # TODO Complete description
    def connectionMade(self):
        """ Override connection made method

        @return:
        """
        self.factory.protoInstance = self
        self.factory.onConnectionMade.callback(self)

    # TODO Complete description
    def connectionLost(self, *a):
        """

        @param a:
        @return:
        """
        self.factory.onConnectionLost.callback(self)

class TestConnectionProcessIntegrated(unittest.TestCase):
    def setUp(self):
        self.CONNECTION_INFO = {'username': 'satnet_admin', 'password': 'pass', 'udpipsend': '172.19.51.145',
                                'baudrate': '500000', 'name': 'Universidade de Vigo', 'parameters': 'yes',
                                'tcpportsend': '1234', 'tcpipsend': '127.0.0.1', 'udpipreceive': '127.0.0.1',
                                'attempts': '10', 'serverip': '172.19.51.143', 'serialport': '/dev/ttyUSB0',
                                'tcpportreceive': 4321, 'connection': 'udp', 'udpportreceive': 57008,
                                'serverport': 25345, 'reconnection': 'no', 'udpportsend': '57009',
                                'tcpipreceive': '127.0.0.1'}
        self.gsi = GroundStationInterface(self.CONNECTION_INFO, 'Vigo', AMP)
        self.threads = Threads(self.CONNECTION_INFO, self.gsi)

        self.serverDisconnected = defer.Deferred()
        self.serverPort = self._listenServer(self.serverDisconnected)
        connected = defer.Deferred()
        self.clientDisconnected = defer.Deferred()
        self.clientConnection = self._connectClient(connected, self.clientDisconnected)
        return connected

    def _listenServer(self, d):
        from twisted.internet import reactor
        f = MockServerFactory()
        f.onConnectionLost = d
        f.protocol = ServerProtocol

        return reactor.listenSSL(1234, f,
                                 contextFactory=ssl.DefaultOpenSSLContextFactory('../../key/server.pem',
                                                                                '../../key/public.pem'))

    def _connectClient(self, d1, d2):
        from twisted.internet import reactor
        # self.factory = ClientReconnectFactory(self.CONNECTION_INFO, self.gsi, self.threads)
        self.factory = protocol.ClientFactory()
        self.factory.protocol = ClientProtocol
        # self.factory.protocol = ClientProtocol(self.CONNECTION_INFO, self.gsi, self.threads)
        self.factory.onConnectionMade = d1
        self.factory.onConnectionLost = d2
        return reactor.connectSSL('127.0.0.1', 1234, self.factory, CtxFactory())

    def tearDown(self):
        d = defer.maybeDeferred(self.serverPort.stopListening)
        self.clientConnection.disconnect()
        return defer.gatherResults([d, self.clientDisconnected,
                                    self.serverDisconnected])

    # FIXME Fix test and complete description
    def _test_loginRightUsernameRightPasswordRightConnection(self):
        """

        @return:
        """
        d = self.factory.protoInstance.callRemote(Login, sUsername='sgongar',
                                                  sPassword='sgongarpass')
        d.addCallback(lambda res : self.assertTrue(res['bAuthenticated']))
        return d

    # FIXME Fix test and complete description
    def _test_loginWrongUsernameRightPasswordRightConnection(self):
        """

        @return:
        """
        d = self.factory.protoInstance.callRemote(Login, sUsername='xabi',
                                                  sPassword='xabipass')
        d.addCallback(lambda res : self.assertFalse(res['bAuthenticated']))
        return d

    # TODO Complete description
    def test_wrongUsernamestartRemoteFailed(self):
        """

        @return:
        """
        d = self.factory.protoInstance.callRemote(Login, sUsername='sgongar',
                                                  sPassword='sgongarpass')
        d.addCallback(lambda l : self.factory.protoInstance.callRemote(StartRemote))

        def checkError(result):
            self.assertEqual(result.message,
                             'This slot has not been assigned to this user')
        return self.assertFailure(d, SlotErrorNotification).addCallback(checkError)


text:
An example database-connected web application,
 with server-side software written in Python. Represents a partial implementation of [Salad System Requirements](https://github.com/gwu-business/salad-system-requirements).

Features:

 + Populates the database with menu data.
 + Stores in the database information captured from a web form.
 + Produces menu page using data from the database.

![a home page with text that reads 'welcome regional manager'](images/screenshots/0-welcome.png)

![a menu page featuring a table with a row per menu item](images/screenshots/1-menu.png)

![a web form for entering new menu items](images/screenshots/2-new-menu-item-form.png)

![another look at the menu page, which this time includes the recently-added menu item](images/screenshots/3-menu-updated.png)

## Usage

Download the repository.

```` sh
git clone git@github.com:gwu-business/salad-system-py.git
cd salad-system-py
````

Create a new local database
 and populate it with menu data (requires mysql).

```` sh
# if your root user does not have a password: run these commands without the -p flag, or press enter when prompted for a password ...
cat database/create.sql | mysql -u root -p
mysql -uroot -p salad_db < database/migrate.sql
mysql -uroot -p salad_db < database/populate.sql
````

Install python package dependencies (requires python and pip).

```` sh
pip install -r software/requirements.txt
````

Create another menu item record, this time using a python script.

```` sh
python software/add_menu_item.py
````

Start a local web server.

```` sh
python software/start_local_web_server.py
````

Visit [localhost:8818](localhost:8818) in a browser
  to view the menu
  and create new menu items.

## Contributing

[Issues](https://github.com/gwu-business/salad-system-py/issues) and [Pull Requests](https://github.com/gwu-business/salad-system-py/pulls) welcome.

## [License](LICENSE.md)