text:
package org.odata4j.core;

/**
 * Useful constants.
 */
public class ODataConstants {

  private ODataConstants() {}

  public static final String TEXT_PLAIN = "text/plain";
  public static final String TEXT_PLAIN_CHARSET_UTF8 = TEXT_PLAIN
      + ";charset=" + Charsets.Lower.UTF_8;

  public static final String APPLICATION_ATOM_XML = "application/atom+xml";
  public static final String APPLICATION_ATOM_XML_CHARSET_UTF8 = APPLICATION_ATOM_XML
      + ";charset=" + Charsets.Lower.UTF_8;

  public static final String APPLICATION_ATOMSVC_XML = "application/atomsvc+xml";
  public static final String APPLICATION_ATOMSVC_XML_CHARSET_UTF8 = APPLICATION_ATOMSVC_XML
      + ";charset=" + Charsets.Lower.UTF_8;

  public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
  public static final String APPLICATION_XML = "application/xml";
  public static final String APPLICATION_XML_CHARSET_UTF8 = "application/xml;charset="
      + Charsets.Lower.UTF_8;
  public static final String TEXT_JAVASCRIPT_CHARSET_UTF8 = "text/javascript;charset="
      + Charsets.Lower.UTF_8;
  public static final String APPLICATION_JAVASCRIPT = "application/json";
  public static final String APPLICATION_JAVASCRIPT_VERBOSE = "application/json;odata=verbose";
  public static final String APPLICATION_JAVASCRIPT_CHARSET_UTF8 = APPLICATION_JAVASCRIPT
      + ";charset=" + Charsets.Lower.UTF_8;
  public static final String APPLICATION_JAVASCRIPT_VERBOSE_CHARSET_UTF8 = APPLICATION_JAVASCRIPT_VERBOSE
      + ";charset=" + Charsets.Lower.UTF_8;
  public static final String APPLICATION_HTTP = "application/http";
  public static final String APPLICATION_BINARY = "application/binary";

  public static final ODataVersion DATA_SERVICE_VERSION = ODataVersion.V3;
  public static final String DATA_SERVICE_VERSION_HEADER = DATA_SERVICE_VERSION.asString;

  public static final String BINARY = "binary";

  public static final String CDATA_TAG_START = "<![CDATA[";
  public static final String CDATA_TAG_END = "]]>";
  public static final String ERROR_TEXT = " @*#*@ Error While Reading Data @*#*@ : ";

  public static final String NaN_value = "NaN";

  /** Common http header names. */
  public static class Headers {
    public static final String X_HTTP_METHOD = "X-HTTP-METHOD";
    public static final String DATA_SERVICE_VERSION = "DataServiceVersion";
    public static final String CONTENT_TYPE = "Content-Type";
    public static final String USER_AGENT = "User-Agent";
    public static final String LOCATION = "Location";
    public static final String SLUG = "Slug";
    public static final String IF_MATCH = "If-Match";
    public static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
    public static final String CONTENT_LENGTH = "Content-Length";
  }

  public static final int COPY_BUFFER_SIZE = 8 * 1024;
  /**
   * The default buffer size after which we push the contents to file while creating the response
   */
  public static final Integer DEFAULT_BUFFER_THRESHOLD_LIMIT = 64 * 1024 * 1024;
  public static final String ODATA_WRITER_THRESHOLD_SIZE = "odata4j.writer.buffer.threshold";
  public static final String JERSEY_CLIENT_CHUNKED_ENCODING_SIZE = "jersey.config.client.chunkedEncodingSize";
  public static final String ODATA_STREAM_BUFFER_SIZE = "odata4j.stream.buffer.size";

  /** Common character sets. */
  public static class Charsets {
    /** Common character sets. (UPPER-CASE) */
    public static class Upper {
      public static final String UTF_8 = "UTF-8";
      public static final String ISO_8859_1 = "ISO-8859-1"; // latin1
      public static final String ISO_8859_15 = "ISO-8859-15"; // latin9
    }

    /** Common character sets. (lower-case) */
    public static class Lower {
      public static final String UTF_8 = "utf-8";
      public static final String ISO_8859_1 = "iso-8859-1";
      public static final String ISO_8859_15 = "iso-8859-15";
    }
  }
}


text:
[clappr](../../index.md) / [io.clappr.player.plugin.control](../index.md) / [MediaControl](index.md) / [modalPanelViewKey](./modal-panel-view-key.md)

# modalPanelViewKey

`const val modalPanelViewKey: `[`String`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html)

redpajama_set_name: RedPajamaGithub


text:
package org.pentaho.di.trans.steps.webservices.wsdl;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.List;

/**
 * Some DOM utility methods.
 */
public final class DomUtils {

    /**
     *
Returns the first child element with the given name. Returns
     * null if not found.

     *
     * @param parent    parent element
     * @param localName name of the child element
     * @return child element, null if not found.
     */
    protected static Element getChildElementByName(Element parent, String localName) {
        NodeList children = parent.getChildNodes();

        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) node;
                if (element.getLocalName().equals(localName)) {
                    return element;
                }
            }
        }
        return null;
    }

    /**
     *
Returns a list of child elements with the given
     * name. Returns an empty list if there are no such child
     * elements.

     *
     * @param parent    parent element
     * @param localName Local name of the child element
     * @return child elements
     */
    protected static List<Element> getChildElementsByName(Element parent, String localName) {
        List<Element> elements = new ArrayList<Element>();

        NodeList children = parent.getChildNodes();

        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) node;
                if (element.getLocalName().equals(localName)) {
                    elements.add(element);
                }
            }
        }
        return elements;
    }
}


text:
// Defines Function and Calls (producing return)
// ============================================================
var crd;
var geocoder;
var latlngStr = '', lat = 0.0, lng = 0.0;
var latlng;
var placeName='';

var id, target = {};
var options = {
    enableHighAccuracy: true,
    timeout: 5000,
    maximumAge: 0
};

$(document).ready(function() {
    target = {
        'latitude': $("#targLat").text(),
        'longitude': $("#targLong").text()
    };
    geocoder = new google.maps.Geocoder();
    // setInterval("getLocation()", 5000);
    getLocation();
});

function getLocation() {
    if (navigator.geolocation) {
        // navigator.geolocation.getCurrentPosition(success, error, options);
        id = navigator.geolocation.watchPosition(success, error, options);
    } else {
        $("#lalo").text('Geolocation switched off or not supported.');
    }
}
function success(pos) {
    var crd = pos.coords;
    $("#accuracy").text(Math.round(crd.accuracy) + ' m');
    // are we there yet?
    // parseInt is ROUGH!
    if (parseInt(target.latitude) === parseInt(crd.latitude) &&
            parseInt(target.longitude) === parseInt(crd.longitude)) {
        console.log('Congratulations, you reached the target');
        navigator.geolocation.clearWatch(id);
    }
    // adding layers of data
    latlng = new google.maps.LatLng(crd.latitude, crd.longitude);
    geocoder.geocode({'latLng': latlng}, function(results, status) {
        placeName = results[0].formatted_address;
        $("#placeName").text(placeName);

        res = {'lat': crd.latitude, 'long': crd.longitude, 'place': placeName,
            'target': target
        };
        // $("#lalo").text(JSON.stringify(res) + $.now());

        // sends it back for database handling
        $.getJSON('./_multilocs', res, function(data) {
            $("#result").text(data.dists + ' km');
        });
    });
}
function error(err) {
    console.warn('ERROR(' + err.code + '): ' + err.message);
    /*
    switch(err.code) {
        case err.PERMISSION_DENIED:
            console.log("permission denied");
            break;
        case err.POSITION_UNAVAILABLE:
            console.log("Location information is unavailable.");
            break;
        case err.TIMEOUT:
            console.log("The request to get user location timed out.");
            break;
        case err.UNKNOWN_ERROR:
            console.log("An unknown error occurred.");
            break;
    }
    */
}
/*
function from_LaLo_to_Place(pos) {
    var crd = pos.coords;
    console.log('More or less ' + crd.accuracy + ' meters.');
    latlng = new google.maps.LatLng(crd.latitude, crd.longitude);
    geocoder.geocode({'latLng': latlng}, function(results, status) {
        placeName = results[0].formatted_address;
        $("#placeName").text(placeName);

        res = {'lat': crd.latitude, 'long': crd.longitude, 'place': placeName};
        $("#lalo").text(JSON.stringify(res) + $.now());

        // sends it back for database handling
        $.getJSON('./_multilocs', res, function(data) {
            $("#result").text(data.result);
        });
    });
}
*/


text:
revision = '2b059ec04b'
down_revision = '3a8a83ce74f'

from alembic import op
import sqlalchemy as sa

def upgrade():
    op.add_column('person', sa.Column('first_name', sa.Text(), nullable=True))
    op.add_column('person', sa.Column('last_name', sa.Text(), nullable=True))

def downgrade():
    op.drop_column('person', 'last_name')
    op.drop_column('person', 'first_name')


text:
class ClientTrackedDeviceProvider : public vr::IClientTrackedDeviceProvider
{
public:
  EVRInitError Init(EClientDriverMode eDriverMode, IDriverLog * pDriverLog, vr::IClientDriverHost * pDriverHost, const char * pchUserDriverConfigDir, const char * pchDriverInstallDir) override;
  void Cleanup() override;
  bool BIsHmdPresent(const char * pchUserConfigDir) override;
  EVRInitError SetDisplayId(const char * pchDisplayId) override;
  HiddenAreaMesh_t GetHiddenAreaMesh(EVREye eEye, EHiddenAreaMeshType type) override;
  uint32_t GetMCImage(uint32_t * pImgWidth, uint32_t * pImgHeight, uint32_t * pChannels, void * pDataBuffer, uint32_t unBufferLen) override;

private:
  vr::IClientDriverHost * host;
  vr::EClientDriverMode driverMode;
};


text:
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.enterprise.sessionmanager;

import java.io.File;

/**
 * This object contains the internal settings used by the SessionManager
 * for storage and maintenance of the back-end data.
 *
 * Settings are controlled by:
 * - platform customization (e.g. whether to use Chubby as the back-end storage
 *   (clusters), or whether to use files as the back-end (oneway/mini)
 * - admin console settings (e.g. length of a session timeout)
 *
 * TODO - this initial implementation is a place-holder.
 * In the final version,
 * settings will be read from a settings file maintained by a combination of
 * system customization scripts and the admin console.  If parsing that file
 * is expensive, this class will abstract the automatic caching of the parsing.
 *
 */
class Settings {

  // Internal data storage (protected to facilitate overrides by testing)
  protected String prefix;
  protected int timeoutSecs;

  private static final String PREFIX1 = "/mnt/rtcache/";
  private static final String PREFIX2 = "/tmp/";
  static final String BASEDIR = "session_manager/";

  // Temporary constructor - set the settings here for now
  Settings () {
    /* prefix directory: session data contains user passwords etc, so from a
     * security perspective, it's important to us that sessions be wiped out
     * upon machine-down.  Therefore, we really want session data to be stored
     * in a ramdisk.  On GSA's in the 5.0 time-frame, the best available ramdisk
     * is a tempfs system on /mnt/rtcache.  However, this directory doesn't
     * exists on non-GSA's (e.g. developer boxes, the unit testing framework,
     * etc).  So- we check if /mnt/rtcache exists, and if so, we place the
     * session manager store under there.  Otherwise, we fall back to /tmp
     */
    String prefixToUse = PREFIX1;
    File fileTest = new File(prefixToUse);
    try {
      if (!fileTest.exists()) {
        prefixToUse = PREFIX2;
      }
    } catch (SecurityException e) {
      prefixToUse = PREFIX2;
    }

    prefix = prefixToUse + BASEDIR;

    timeoutSecs = 30 * 60;                    // 30 minutes
  }

  /**
   * retrieve the filename prefix used for this sessionmanager.
   * Something like /ls/sessionmanager/ or /tmp/sessionmanager/.
   *
   * @return    the filename prefix; with a trailing slash
   */
  public String getPrefix() {
    return prefix;
  }

  /**
   * retrieve the amount of time to wait after the last access (read or write)
   * to a session before it is eligible for garbage collection
   *
   * @return    number of seconds since last access when a session is expired
   */
  public int getSessionTimeout() {
    return timeoutSecs;
  }

}


text:
ZendFramework1-App-Skeleton
===========================

Composer Zend Framework 1.* App Skeletton with composer dependencies manager


text:
package org.mifos.application.master.business;

import org.mifos.customers.api.DataTransferObject;

public class PaymentTypeDto implements DataTransferObject {

    private Short paymentTypeId;
    private String paymentTypeValue;

    public Short getPaymentTypeId() {
        return paymentTypeId;
    }

    public void setPaymentTypeId(Short paymentTypeId) {
        this.paymentTypeId = paymentTypeId;
    }

    public String getPaymentTypeValue() {
        return paymentTypeValue;
    }

    public void setPaymentTypeValue(String paymentTypeValue) {
        this.paymentTypeValue = paymentTypeValue;
    }

}


text:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

	<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=utf-8">
	<TITLE></TITLE>
	<META NAME="GENERATOR" CONTENT="OpenOffice.org 3.0  (Unix)">
	<META NAME="AUTHOR" CONTENT="Bela Ban">
	<META NAME="CREATED" CONTENT="20080904;10594200">
	<META NAME="CHANGEDBY" CONTENT="Bela Ban">
	<META NAME="CHANGED" CONTENT="20090121;11214300">
	<META NAME="Info 1" CONTENT="">
	<META NAME="Info 2" CONTENT="">
	<META NAME="Info 3" CONTENT="">
	<META NAME="Info 4" CONTENT="">

A simple clustered task distribution system

Bela Ban

Sept 2008

This article will introduce the basic
concepts of JGroups ([1]) and
then implement a task distribution system (on top of JGroups), where
tasks can be placed into the cluster and are executed by worker
nodes.

I'll show that worker nodes can be
added at run time to add more processing power, or taken down when we
don't have much load. Plus, tasks assigned to workers who
subsequently crash are automatically reassigned to live nodes.

We could have implemented this with JMS
queues. However, when we have a lot of load, the JMS server tends to
become the bottleneck. In our decentralized solution, every node in
the cluster can be both a master (who submits tasks) and a slave (who
executes the tasks and returns the results).

JGroups

Overview

JGroups is a clustering library. Applications can use JGroups to
join a cluster, send messages to the cluster nodes, get notifications
when other nodes join or leave (including crashes), and leave a
cluster.

Its task is the reliable sending of messages within a cluster. Its
scope is much smaller than JMS; JGroups doesn't know about queues,
topics and transactions, but only about message sending.

The main feature of JGroups is the protocol stack and the
resulting configuration flexibility. Applications can pick the
properties they would like in a cluster by simply editing an XML
file.

For example, an application can add compression by simply adding
the COMPRESS protocol to the configuration.

Or it can remove fragmentation because its messages will always be
smaller than 65K (over UDP), or because it uses TCP as transport.

Another application might add encryption and authentication, so
messages are encrypted and only nodes which present a valid X.509
certificate can join the cluster.

Applications are even free to write their own protocols (or extend
an existing one), and add them to the configuration. It might be
useful for example, to add a protocol which keeps track of all
messages sent and received over a cluster, for auditing or statistics
purposes.

The architecture of JGroups is shown in fig. 1.

The main API for clients is a Channel (see below) which is used to
send and receive messages. When a message is sent, it is passed down
the protocol stack. The stack is a list of protocols, and each
protocol gets a chance to do something with the message.

For example, a fragmentation protocol might check the size of the
message. If the message is greater than the configured size, it might
fragment it into multiple smaller messages and send those down the
stack.

Illustration
	1: JGroups architecture

On the receive side, the fragmentation protocol would queue the
fragments, until all have been received, then assemble them into the
original message and pass it up.

The protocols shipped with JGroups can be divided into the
following categories:

Transport: sending and receiving of messages. UDP uses IP
	multicasting and/or UDP datagrams. TCP uses TCP connections.

Discovery: initial discovery of nodes

Merging: after a network partition heals, this merges the
	sub-clusters back into one

Failure detection: monitoring of cluster nodes and
	notifications of potential crashes or hangs

Reliable delivery: makes sure a message is not lost, received
	only once, and received in the order in which a sender sent it. This
	is done through assigning sequence numbers to each message and
	through retransmission in case of a missing message.

Stability: nodes have to buffer all messages (for potential
	retransmission). The stability protocol makes sure that periodically
	(or based on accumulated size), messages that have been received by
	all cluster nodes are purged so they can be garbage collected.

Group membership: keeps track of the nodes in a cluster, and
	notifies the application of node joins and leaves (including
	crashes)

Flow control: makes sure that a sender cannot send messages
	faster than the receivers can process them, over a longer time. This
	is necessary to prevent out-of-memory situations. Flow control is a
	counter part to stability.

Fragmentation: fragments large messages into smaller ones and
	re-assembles them at the receivers

State transfer: makes sure that the shared state of a cluster
	(e.g. all HTTP sessions) is transferred correctly to a new node

Compression: compresses messages and uncompresses them at the
	receivers

Encryption: encrypts messages

Authentication: prevents unauthorized node from joining a
	cluster

API

The main API is org.jgroups.JChannel:

public class JChannel
extends Channel {

    public JChannel(String
properties) throws ChannelException;

    public void
setReceiver(Receiver r);

    public void
connect(String cluster_name) throws ChannelException;

    public void
send(Message msg) throws ChannelException;

    public View getView();

    public Address
getLocalAddress();

    public void
disconnect();

    public void close();

}

We join a cluster by creating a channel
and calling connect():

Channel ch=new
JChannel("/home/bela/udp.xml");

ch.setReceiver(new
ReceiverAdapter() {

    public void
receive(Message msg) {}

});

ch.connect("demo-cluster");

This creates a channel with a protocol
stack defined in /home/bela/udp.xml. If an application needs
different properties, it would modify udp.xml and pass the modified
XML file to the constructor.

Then we set a Receiver, which has
callbacks that are invoked when messages are received. Finally, we
join cluster "demo-cluster". All channels with the same
configuration and the same cluster name (argument of connect()) will
join the same cluster.

A node can send and receive messages
once it has joined a cluster.

The Receiver interface has 2 methods
we're interested in:

void receive(Message msg);

void viewAccepted(View
new_view);

The receive() callback is invoked
whenever a message is received1.
Its argument is an org.jgroups.Message:

public class Message
implements Streamable {

    protected Address
dest_addr=null;

    protected Address
src_addr=null;

    private byte[]
buf=null;

    public byte[]
getBuffer();

    public void
setBuffer(byte[] b);

}

A message has the address of the
destination (dest_addr) and sender (src_addr) and a payload (buf). An
Address is an opaque class identifying a node uniquely within a
cluster2.
A destination address of null means that the message is to be sent to
all cluster nodes (a multicast), a non-null destination address means
to send the message to a single receiver (a unicast).

When receiving a message, the
application can call getBuffer() to retrieve the byte[] buffer and
then unmarshal it into data meaningful to the application.

The viewAccepted() callback is invoked
when a node joins or leaves. Its only parameter is a View, which is
essentially a list of addresses. Views are received by all cluster
nodes in exactly the same order, so when we have a cluster {A,B,C},
then the nodes have the following views:

A: {A,B,C}

B: {A,B,C}

C: {A,B,C}

If we had a new node D joining, then
the view would become {A,B,C,D}. If B crashed, then everyone would
install {A,C,D}. As we can see, the nodes in a view are ordered
according to join time.

The oldest node is always first.

The current view can also be retrieved
from a channel by calling Channel.getView().

Let's now discuss the remaining methods
of JChannel.

The send() method takes a Message and
sends it to all cluster nodes if the message's destination is null,
or to a single node if the destination is non-null. The application
needs to marshal their data to a byte[] buffer and set place it into
the message via Message.setBuffer().

To retrieve the current view, we can
use Channel.getView() and to fetch the local address of a node, we
call Channel.getLocalAddress().

The disconnect() method leaves a
cluster and close() destroys the channel. A closed channel cannot be
opened again. Invoking close() also disconnects a channel if not
already disconnected.

Calling disconnect() will install a new
view in all cluster nodes, and viewAccepted() will be invoked in all
receivers.

As we will see in building our task
distribution system, the use of JGroups allows us to focus on the
system at hand, and not have to worry about clustering issues, as
JGroups does the heavy lifting. The main features used in our system
are membership management (we need to know who the cluster nodes are,
and when a node joins or leaves) and reliable messaging (for task
sending). Plus, by modifying the protocol configuration, we can adapt
the transport to our needs.

Task distribution system

The idea is very simple: we have a cluster of nodes, and each node
can submit tasks to be executed by some other node in the cluster. So
every node is a peer, in the sense that it can both submit and
handle tasks. In a real life application, clients would
connect to any of the nodes, e.g. via TCP or RMI, and submit
tasks to that node, which would then distribute it to some other node
(or handle it itself).

When submitting a task, we choose a random integer which is then
mapped to the rank of a node in the cluster (int mod N where N is the
cluster size). The rank is the position of a node in the view, and
since the view is the same in all nodes, the rank identifies the node
uniquely.

The task is then multicast (EXECUTE) across the cluster. Every
node adds the task to a hash map consisting of tasks and their
submitters' (JGroups) addresses.

Every node now compares the rank shipped with the task to its own
rank. It it doesn't match, nothing is done. If it matches, the node
needs to process the task. It does so and returns the result to the
submitter.

When the submitter receives the response (RESULT), it multicasts a
REMOVE message across the cluster. Upon reception of REMOVE(T), every
node removes T from its hash map

If a node X crashes (or leaves gracefully), we know which tasks
were assigned to it by looking up the tasks in the hash map, keyed by
X. All tasks which are still present in the hash map have not yet
been processed and need to be re-executed, this time by a different
node. This is done by comparing the rank shipped with the task to the
node's rank and executing it if a node's own rank matches it.

If a master M crashes after having submitted a few tasks but not
yet having received the results, the slaves remove all tasks
submitted by M, because M won't need the results anymore3.

Fig. 2 shows how task distribution in a cluster looks like.

The cluster consists of nodes A, B, C and D. Clients can access
any one of them. A task submitted for example to B by a client might
assign 23 to the task. B then multicasts an EXECUTE(23, TASK) message
to all nodes in the cluster, and every node adds task #23 to its
cache.

However, the only node processing task #23 is A (to which 23
happens to map to), which then sends the result as a RESULT(23, OBJ)
to B. B returns the result OBJ to the client and multicasts a
REMOVE(23) message to the cluster, which causes all nodes to remove
task #23 from their caches.

Had A crashed during processing of task #23, some other node would
have taken over, processed the result and sent it back to B.

Illustration
	2: Task distribution in a cluster

Code

Let's look at how we implement this4.
We'll write a Server class which has the main() loop and accepts
requests which are to be distributed across the cluster. This mimics
real clients sending requests to any cluster node. We can then start
a number of Server processes, across the cluster or on the same host
(for the demo).

First, we need an ID (ClusterID) which is unique across the
cluster and which is used to determine whether a node accepts a task.
An instance of ClusterID is created for every task. The class looks
as follows:

public class
ClusterID implements Streamable {

    private Address
creator;

    private  int id;

    private static
int next_id=1;

    public static
synchronized ClusterID create(Address addr) {

        return new
ClusterID(addr, next_id++);

    }

}

Implementations of Streamable let JGroups marshal and unmarshal
objects more efficiently.

A ClusterID has the address of the node which created it and an ID
which is incremented on each create() call. If we only used IDs,
because every node could potentially submit tasks, we might end up
with node A submitting task #23 and node C submitting task #23, and
this would lead to issues with task entries getting overwritten in
the cache hash map Prefixing the ID with its creator would yield
A::23 and C::23, which are 2 different tasks.

Then, we define the Master and Slaves interfaces:

public interface Master {

    Object submit(Task
task, long timeout) throws Exception;

}

public interface Slave {

    Object handle(Task
task);

}

These interfaces are implemented by our Server class which does
the bulk of the work.

The submit() method takes a task (see below) and a timeout. It can
throw an exception or return a result. Note that both the Task
subclasses and the result have to be serializable or Streamable,
because they are potentially sent across the network to get executed.

The handle(Task t) method is invoked at the slave, which is the
worker node which decides that it will handle the task. It typically
uses the data shipped with a task (subclass) and returns an object
which needs to be serializable because in most cases, it will be sent
back to the submitter via the network.

Next, we define the Task:

public interface Task
extends Serializable {

    public abstract Object
execute();

}

A task contains all of the necessary data
that is shipped to a slave. The execute() method then uses that data
to perform the processing and returns a result which is sent back to
the master who submitted that task.

Now that we've defined all the ancillary classes and interfaces,
let's start writing the Server:

public class Server extends
ReceiverAdapter implements Master, Slave {

    private String
props="udp.xml";

    private Channel ch;

    private final
ConcurrentMap<ClusterID,Entry> tasks;

    private final
ExecutorService thread_pool=Executors.newCachedThreadPool();

    private View view;

    private int rank=-1;

    private int
cluster_size=-1;

    public void start()
throws Exception {

        ch=new
JChannel(props);

        ch.setReceiver(this);

        ch.connect("dzone-demo");

    }

    public void stop() {

        thread_pool.shutdown();

        ch.close();

    }

Server implements the Master and Slave interfaces, which means
that a Server can act both as a client (Master) and server (Slave).
So the name "Server" is actually somewhat of a misnomer, as this
is clearly more of a peer than a server !

Next, we need a few instance variables. For instance, we need a
JGroups channel (ch) which needs to be configured with properties
(props) defining the configuration of the protocol stack.

We also need a thread pool (thread_pool) to execute the tasks we
receive to process. Here, we chose the a simple pool which creates
new threads when needed and removes threads that have been idle for
more than 60 seconds.

The 'tasks' hash map is the cache for received tasks. It is keyed
by ClusterId and the values are Entry instances (see below).

The view, rank and cluster_size variables are needed to determine
whether or not to process a received task. More on this later.

In start(), we create a JChannel based on the properties passed to
server and connect it, which causes it to join the cluster. Also, we
set a Receiver, which means that we'll get receive(Message) and
viewAccepted(View) callbacks whenever a message or view change is
received.

In stop(), we shut down the thread pool and close the channel,
causing this node to leave the cluster gracefully. Everybody else
connected to this cluster will get a view change (viewAccepted()
callback) notifying them of the termination of this node.

The Entry class (inner class of Server) is shown below:

private static class Entry
{

    private final Task
task;

    private final Address
submitter;

    private final
Promise<Object> promise=new Promise<Object>();

}

It is a wrapper for the task, the address of the submitter and the
promise (similar to a Future) used to block on until the result has
been received. The address of the submitter of the task is needed to
send the result back. This is necessary when a node other than the
originally assigned one takes over and processes a task.

Another inner class of Server is Request, which is used to send
requests and responses between submitters (masters) and slaves:

    public static class
Request implements Streamable {

        static enum Type
{EXECUTE, RESULT, REMOVE};

        private Type type;

        private Task task;

        private ClusterID
id;

        private Object
result;

    }

A Request also implements Streamable (implementation not shown)
which allows for more efficient marshalling. We're sending 3 types of
requests around:

EXECUTE: multicast by the submitter to all nodes of the
	cluster. It contains the task and a ClusterID, generated by the
	submitter and used by the slave to determine whether or not to
	accept the task. Note that only one slave in the entire
	cluster will handle a given task.

RESULT: contains the ClusterID and an Object (can be null if
	nothing is returned, e.g. calling a void method). This is unicast
	from the slave to the master which submitted the task.

REMOVE: only contains the ClusterID and is multicast by the
	submitter of a task T after the result for T has been received.
	Everyone removes the task from their cache upon reception of this
	message.

Now that we have all the bits and pieces in place, it is actually
time to look at the submit() method:

    public Object
submit(Task task, long timeout) throws Exception {

        ClusterID
id=ClusterID.create(ch.getLocalAddress());

        try {

            Request req=new
Request(Request.Type.EXECUTE, task, id, null);

            byte[]
buf=Util.streamableToByteBuffer(req);

            Entry entry=new
Entry(task, ch.getLocalAddress());

            tasks.put(id,
entry);

            ch.send(new
Message(null, null, buf));

            return
entry.promise.getResultWithTimeout(timeout);

        }

        catch(Exception ex)
{

            tasks.remove(id);
// remove it again

            throw ex;

        }

    }

This is an implementation of the Master interface. It generates a
ClusterId and constructs a Request instance of type EXECUTE,
containing the task and the ClusterID.

Util.streamableToByteBuffer() is a helper method provided by
JGroups, which takes a Streamable object and marshals it into a
byte[] buffer. Remember that JGroups can only ship byte[] buffers
over the wire.

Then we add the task to our local cache, create a message with
destination of null (= multicast) and the marshalled request.

Finally we block on the promise of Entry until a result has been
received or we get an exception (e.g. a timeout).

The Slave part for handling of received tasks is simple:

    public Object
handle(Task task) {

        return
task.execute();

    }

We simply take the task and call execute() on it.

In the Server.start() method, we created a JChannel and set the
Receiver to be the Server itself. We implement 2 methods:
receive(Message) and viewAccepted(View). The receive() method is
shown below:

public void receive(Message
msg) {

    Request
req=(Request)Util.streamableFromByteBuffer(Request.class,
msg.getBuffer());

    switch(req.type) {

        case EXECUTE:

            handleExecute(req.id,
msg.getSrc(), req.task);

            break;

        case RESULT:

            Entry
entry=tasks.get(req.id);

            entry.promise.setResult(req.result);

            multicastRemoveRequest(req.id);

            break;

        case REMOVE:

            tasks.remove(req.id);

            break;

    }

}

The receive() method handles all requests and responses. Upon
reception of a message, we need to grab its byte[] buffer, unmarshal
it into a Request and then handle the request. We use the JGroups
helper method Util.streamableFromByteBuffer() to do this.

On reception of EXECUTE, we call handleExecute(), passing the
ClusterID, the submitter and the task to it.

On reception of RESULT (sent by a slave), we set the result in the
promise, releasing the blocked submitter of the task. Then, we
multicast a REMOVE request.

On reception of REMOVE, we simply remove the task from our cache.

The handleExecute() method checks if a node should accept the task
and, if yes, passes it to a thread pool to execute:

    private void
handleExecute(ClusterID id, Address sender, Task task) {

        tasks.putIfAbsent(id,
new Entry(task, sender));

        int
index=id.getId() % cluster_size;

        if(index != rank)

            return;

        thread_pool.execute(new
Handler(id, sender, task);

    }

First, we add the task to our tasks cache, keyed by the
ClusterID5.
Then we take the ClusterID's ID, modulo the number of nodes in the
cluster. This is the rank of the node which should execute the task.
If it matches our own rank, we create a Handler and pass it to the
thread pool to be executed on a separate thread, otherwise we return
from handleExecute().

The Handler class is shown below:

    private class Handler
implements Runnable {

        final ClusterID id;

        final Address
sender;

        final Task task;

        public void run() {

            Object
result=null;

            try {

                result=handle(task);

            }

            catch(Throwable
t) {

                result=t;

            }

            Request
response=new Request(Request.Type.RESULT, null, id, result);

            byte[]
buf=Util.streamableToByteBuffer(response);

            ch.send(new
Message(sender, null, buf));

        }

    }

It executes the task against the Slave interface (handle() method)
and stores the result. If there is an exception, then the exception
(which is serializable by default) is stored as result instead.

Then a Response object is created from the result.
Util.streamableToByteBuffer() is called to generate a byte[] buffer
from the response, which is then placed into a Message and sent via
the channel to the original submitter of the task.

Our code is now almost complete. The only thing missing is the
handling of membership changes. Remember that we need to resubmit
tasks from failed nodes, or from nodes who left gracefully, to other
nodes. This is done in viewAccepted(View):

    public void
viewAccepted(View view) {

        List<Address>
left_members=Util.leftMembers(this.view, view);

        this.view=view;

        Address
local_addr=ch.getLocalAddress();

        cluster_size=view.size();

        Vector<Address>
mbrs=view.getMembers();

        for(int i=0; i <
mbrs.size(); i++) {

            Address
tmp=mbrs.get(i);

            if(tmp.equals(local_addr))
{

                rank=i;

                break;

            }

        }

        if(left_members !=
null && !left_members.isEmpty()) {

            for(Address
mbr: left_members)

                handleLeftMember(mbr);

        }

    }

First, we determine which members left between the new and
previous views. This is done with Util.leftMembers(), which returns a
list of addresses of nodes which left between 2 views.

Then we set the local address (Channel.getLocalAddress()),
cluster_size and view.

Next, the rank is computed by iterating through the new membership
and comparing each element against the local address. On a match, our
rank is the iteration counter. For example, if we have a membership
of {A,B,C,D,E}, and we're C, then our rank will be 2.

Finally, we need to determine whether any nodes left since the
previous view, and whether there are any tasks to take over from
them. This is done by iterating through all left members (if there
are any) and calling handleLeftMember(), which is shown below:

private void
handleLeftMember(Address mbr) {

    for(Map.Entry<ClusterID,Entry>
entry: tasks.entrySet()) {

        ClusterID
id=entry.getKey();

        int
index=id.getId() % cluster_size;

        if(index != rank)

            return;

        Entry
val=entry.getValue();

        if(mbr.equals(val.submitter))
{

            continue;

        }

        execute(id,
val.submitter, val.task);

    }

}

This method iterates through all cache entries and compares the ID
(modulo cluster size) to our own rank. If it matches, we execute the
task (unless the submitter itself left, in which case we drop the
task)6.

Note that both rank and cluster_size might change on receiving a
new view. Both assignments need to happen before
handleLeftMember() is called, as this method uses the 2 variables.
For example, if we have cluster {A,B,C,D,E}, then C has rank=2 and D
has rank=3. If C crashes, D's and E's ranks change: D's rank is now
2. This means that D will process all of the tasks that C was
processing and which hadn't completed by the time C crashed
(otherwise C would have removed them).

The code above has a deficiency though: assuming we have a cluster
of {A,B,C,D} and A crashes, then the ranks of B, C and D change: B
from 1 to 0, C from 2 to 1 and D from 3 to 2.

This means that B, C and D will now execute tasks which were
already being worked on by other nodes. For example, C will
re-execute D's tasks and B will re-execute C's tasks. This is not
incorrect, as the submitter of a task will remove the task when
completed. So, when receiving a result R from a slave for a task
which was already completed and therefore removed, the submitter just
drops R.

This is not wrong, but leads to spurious and unneeded processing.
A better way to define the rank would be to use consistent
hashing ([2]), which minimizes changes to the rank and therefore
re-execution of tasks already being worked on by other nodes.

Our code is now complete. The last thing to do is to write the
driver code, which we also add to Server:

    public static void
main(String[] args) throws Exception {

        Server server=new
Server(props);

        server.start();

        loop(server);

    }

    private static void
loop(Server server) {

        boolean
looping=true;

        while(looping) {

            int
key=Util.keyPress("[1] Submit [2] Submit long running task [q]
Quit");

            switch(key) {

                case '1':

                    Task
task=new Task() {

                        public
Object execute() {

                            return
new Date();

                        }

                    };

                    log("<==
result = " + server.submit(task, 30000));

                    break;

                case '2':

                    task=new
Task() {

                        public
Object execute() {

                            Util.sleep(15000);

                            return
new Date();

                        }

                    };

                    log("<==
result = " + server.submit(task, 30000));

                    break;

            }

        }

        server.stop();

    }

The main() method creates a Server and starts it. The loop()
method waits for a key press and then submits a short running (on
'1') or long running (on '2') task. The task simply returns a new
Date with the current time. The long running task sleep for 15
seconds before returning the date. When 'q' is pressed, we stop the
server gracefully and return.

Demo

Let's see whether this thing works ! The demo JAR can be
downloaded here.

Let's start some instances and submit some tasks. To start an
instance, we run:

<TT>[linux]/home/bela/JGroupsArticles/dist$
java -Djgroups.bind_addr=192.168.1.5 -jar demo.jar </TT>

<TT>-------------------------------------------------------</TT>

<TT>GMS:
address is 192.168.1.5:33795</TT>

<TT>-------------------------------------------------------</TT>

<TT>view:
[192.168.1.5:33795|0] [192.168.1.5:33795]</TT>

<TT>my
rank is 0</TT>

<TT>[1]
Submit [2] Submit long running task [3] Info [q] Quit</TT>

Replace the IP address set with -Djgroups.bind_addr with the one
of a valid NIC. If you don't set this property, JGroups picks a
random NIC.

We can see that we're the first node in the cluster, our local
address is 192.168.1.5:33795 and our rank is 0.

When we submit a task, we see that it is executed by our self,
since we're the only node in the cluster:

<TT>[1]
Submit [2] Submit long running task [3] Info [q] Quit</TT>

<TT>1</TT>

<TT>==>
submitting 192.168.1.5:33795::1</TT>

<TT>executing
192.168.1.5:33795::1</TT>

<TT><==
result = Tue Sep 09 12:38:55 CEST 2008</TT>

Let's start a second instance:

<TT>[linux]/home/bela/JGroupsArticles/dist$
java -Djgroups.bind_addr=192.168.1.5 -jar demo.jar </TT>

<TT>-------------------------------------------------------</TT>

<TT>GMS: address is
192.168.1.5:41232</TT>

<TT>-------------------------------------------------------</TT>

<TT>view:
[192.168.1.5:33795|1] [192.168.1.5:33795, 192.168.1.5:41232]</TT>

<TT>my rank is 1</TT>

<TT>[1] Submit [2] Submit long
running task [3] Info [q] Quit</TT>

We can see that the view now has 2 members: 192.168.1.5:33795 with
rank=0 and 192.168.1.5:41232 (the second instance started) with
rank=1. Note that for this demo, we start all instances as separate
processes on the same host, but of course we would place those
processes on different hosts in real life.

If we now go back to the first instance and submit 2 tasks, we can
see that they are assigned to both instances:

<TT>1</TT>

<TT>==>
submitting 192.168.1.5:33795::2</TT>

<TT>executing
192.168.1.5:33795::2</TT>

<TT><==
result = Tue Sep 09 12:43:48 CEST 2008</TT>

<TT>[1]
Submit [2] Submit long running task [3] Info [q] Quit</TT>

<TT>1</TT>

<TT>==>
submitting 192.168.1.5:33795::3</TT>

<TT><==
result = Tue Sep 09 12:43:49 CEST 2008</TT>

Task #2 was executed by our self, but task #3 was executed by the
second instance (this can be verified by looking at the output of the
second instance).

Let's now start a third instance:

<TT>[linux]/home/bela/JGroupsArticles/dist$
java -Djgroups.bind_addr=192.168.1.5 -jar demo.jar </TT>

<TT>-------------------------------------------------------</TT>

<TT>GMS:
address is 192.168.1.5:45532</TT>

<TT>-------------------------------------------------------</TT>

<TT>view:
[192.168.1.5:33795|2] [192.168.1.5:33795, 192.168.1.5:41232,
192.168.1.5:45532]</TT>

<TT>my
rank is 2</TT>

<TT>[1]
Submit [2] Submit long running task [3] Info [q] Quit</TT>

We see that the cluster now has 3 nodes, and the rank of the
freshly started instance is 2.

Now we'll submit a long running task T and - before T completes -
kill the node which is processing T.

Let's submit that task on the third instance:

<TT>[1]
Submit [2] Submit long running task [3] Info [q] Quit</TT>

<TT>2</TT>

<TT>==>
submitting 192.168.1.5:45532::1</TT>

Because the second instance has rank=1, task #1 from
192.168.1.5:45532 is executed on that instance.

Before the 15 seconds elapse, let's kill the second instance.
After a few seconds, the output of the third instance shows the
following:

<TT>view:
[192.168.1.5:33795|3] [192.168.1.5:33795, 192.168.1.5:45532]</TT>

<TT>my
rank is 1</TT>

<TT>****
taking over task 192.168.1.5:45532::1 from 192.168.1.5:41232
(submitted by 192.168.1.5:45532)</TT>

<TT>executing
192.168.1.5:45532::1</TT>

<TT>sleeping
for 15 secs...</TT>

<TT>done</TT>

<TT><==
result = Tue Sep 09 12:55:10 CEST 2008</TT>

This might be somewhat surprising, but correct. Let's see what's
happening.

First we get a view change, the new view is <TT>192.168.1.5:33795,
192.168.1.5:45532. This means that the third instance has now rank=1,
which is exactly the rank the killed instance had. Therefore when
task #1 is reassigned, it is the third node 192.168.1.5:41232 which
executes #1.</TT>

<TT>This happens
to be the same node as the submitter, but that's okay: since we only
have 2 node left in our cluster, there is a 50% chance of the
submitter processing its own task. If we had more nodes in the
cluster, the likelihood of a submitter processing its own task would
decrease</TT><TT>7</TT><TT>.</TT>

Conclusion

We implemented a simple, highly decentralized, clustered task
distribution system in roughly 500 lines of code and 5 classes. The
system is failure resilient, because all nodes are peers and there's
no central server.

All peers are equal (every peer can act as both master and slave)
and tasks are grabbed by a node based on an ID assigned by the
submitter (master).

Crashes or graceful termination of nodes doesn't lead to missing
tasks, as the system re-balances itself and assigns orphaned tasks to
another node in the cluster.

The system is so small because it runs on top of JGroups. Had we
written it without JGroups, we would have had to implement the
following functionality ourselves:

Cluster membership and failure detection: we need to know
	when the membership changes, and all nodes in a cluster need to get
	these views in exactly the same order

Simulcasting (with UDP datagrams): fragmentation (if a task
	or result is greater than 65K) and retransmission (UDP datagrams are
	lost), plus suppression of duplicate messages  (we cannot receive
	the same task multiple times !).

Simple switching between transports (UDP, TCP), and
	configuration/customization of the transport: e.g. adding
	compression or encryption

The current task distribution is far from complete (after all,
this is just a demo of what can be done with JGroups !); possible
further improvements include:

Implementation java.util.concurrent.ExecutorService. This
	would extend the in-VM thread pool to become a clustered thread
	pool, where tasks are executed not only by threads in the same JVM,
	but also by threads on different hosts. This would allow masters to
	submit tasks (for example a collection of tasks) and to wait for
	their completion later. In our current solution, the thread
	of the caller of submit() is blocked until a timeout occurs or the
	result becomes available.

Not all nodes store the task, but only a subset of the nodes.
	When a node X crashes, we ask everyone for the tasks assigned to X,
	and these are returned by the nodes who stored them.

Use random numbers to create ClusterIDs rather than
	monotonically increasing ints. Currently, we use a round robin ID
	allocation scheme. While this is pretty good at distributing all
	tasks evenly, it might be better in some cases to assign weights to
	individual cluster nodes, according to number of cores, memory etc.
	Thus, tasks could be assigned more optimally, whereas in the current
	solution we assign all tasks evenly, which means slower hosts get
	the same number of tasks as faster hosts. We should probably
	externalize the policy which creates the IDs and/or picks the nodes,
	so it can be replaced.

Download

The full demo code can be downloaded here
([3]).

Links

[1] http://www.jgroups.org

[2]
http://www.spiteful.com/2008/03/17/programmers-toolbox-part-3-consistent-hashing

[3]
http://www.jgroups.org/javagroupsnew/docs/taskdistribution/demo.jar

1Note
	that receive() can be invoked concurrently, when we receive messages
	from different senders. It therefore needs to be reentrant.

2In
	JGroups, it is the IP address and port of a node by default

3This
	could of course be changed to have any submitted task be
	executed even if its original submitter crashed

4All
	code examples have been edited for brevity, e.g. no exception or
	error handling is shown.

5This
	is only done if we're not the submitter of the task, because in this
	case the task will already be in the cache.

6For
	the purpose of the demo. This could also be changed, as it probably
	makes sense in some cases to execute a task even if the submitter
	crashed.

7We
	could of course also change the system, such that a submitter never
	executes its own tasks (unless there are no other nodes in the
	cluster).


text:
package com.mxgraph.examples;

import java.awt.Dimension;
import java.awt.Graphics;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;

import com.mxgraph.util.mxCellRenderer;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.view.mxGraph;

public class GraphControl extends JComponent
{

	/**
	 *
	 */
	private static final long serialVersionUID = -7470544285363165479L;

	mxGraph graph;

	Icon buffer;

	public GraphControl(mxGraph graph)
	{
		this.graph = graph;
		this.graph.getModel().addListener(mxEvent.CHANGE,
				new mxIEventListener()
				{
					public void invoke(Object source, mxEventObject evt)
					{
						GraphControl.this.graphModelChanged();
					}
				});
	}

	public mxGraph getGraph()
	{
		return graph;
	}

	public void paint(Graphics g)
	{
		super.paint(g);
		paintBuffer(g);

	}

	void paintBuffer(Graphics g)
	{
		Icon icon = getBuffer();
		icon.paintIcon(this, g, 0, 0);
	}

	public Icon getBuffer()
	{
		if (buffer == null)
		{
			updateBuffer();
		}
		return buffer;
	}

	protected void updateBuffer()
	{
		buffer = createBuffer();
		int width = buffer.getIconWidth() + 1;
		int height = buffer.getIconHeight() + 1;
		Dimension d = new Dimension(width, height);

		if (!getPreferredSize().equals(d))
		{
			setPreferredSize(d);
			revalidate();
		}
	}

	protected Icon createBuffer()
	{
		return new ImageIcon(mxCellRenderer.createBufferedImage(graph, null,
				graph.getView().getScale(), null, true, null));
	}

	protected void clearBuffer()
	{
		buffer = null;
	}

	public void refreshBuffer()
	{
		clearBuffer();
		repaint();
	}

	protected void graphModelChanged()
	{
		refreshBuffer();
	}

}


text:
- Table of Contents
{:toc}

## Version 1.6.0

The 1.6.0 release of MCUboot adds support for the PSOC6 platform,
X25519 encrypted images, rollback protection, hardware keys, and a
shared boot record to communicate boot attestation information to
later boot stages.  There are bug fixes, and associated imgtool
updates as well.

### About this release

- Initial support for the Cypress PSOC6 plaformt.  This platform
  builds using the Cypress SDK, which has been added as submodules.
- CBOR decoding in serial recovery replaced by code generated from a
  CDDL description.
- Add support for X25519 encrypted images.
- Add rollback protection.  There is support for a HW rollback counter
  (which must be provided as part of the platform), as well as a SW
  solution that protects against some types of rollback.
- Add an optional boot record in shared memory to communicate boot
  attributes to later-run code.
- Add support for hardware keys.
- Various fixes to work with the latest Zephyr version.

### Security issues addressed

- CVE-2020-7595 "xmlStringLenDecodeEntities in parser.c in libxml2
  2.9.10 has an infinite loop in a certain end-of-file situation." Fix
  by updating a dependency in documentation generation.

### Zephyr-RTOS Compatibility

This release of MCUboot works the Zephyr "master" at the time of the
release.  It was tested as of has 1a89ca1238.  When Zephyr v2.3.0 is
released, there will be a possible 1.6.1 or similar release of Zephyr
if needed to address any issues.  There also may be branch releases of
MCUboot specifically for the current version of Zephyr, e.g.
v1.6.0-zephyr-2.2.1.

## Version 1.5.0

The 1.5.0 release of MCUboot adds support for encrypted images using
ECIES with secp256r1 as an Elliptic Curve alternative to RSA-OAEP. A
new swap method was added which allows for upgrades without using a
scratch partition. There are also lots of bug fixes, extra simulator
testing coverage and some imgtool updates.

### About this release

- TLVs were updated to use 16-bit lengths (from previous 8). This
  should work with no changes for little-endian targets, but will
  break compatibility with big-endian targets.
- A benchmark framework was added to Zephyr
- ed25519 signature validation can now build without using mbedTLS
  by relying on a bundled tinycrypt based sha-512 implementation.
- imgtool was updated to correctly detect trailer overruns by image.
- Encrypted image TLVs can be saved in swap metadata during a swap
  upgrade instead of the plain AES key.
- imgtool can dump private keys in C format (getpriv command), which
  can be added as decryption keys. Optionally can remove superfluous
  fields from the ASN1 by passing it `--minimal`.
- Lots of other smaller bugs fixes.
- Added downgrade prevention feature (available when the overwrite-based
  image update strategy is used)

### Known issues

- TLV size change breaks compatibility with big-endian targets.

## Version 1.4.0

The 1.4.0 release of MCUboot primarily adds support for multi-image
booting.  With this release, MCUboot can manage two images that can be
updated independently.  With this, it also supports additions to the
TLV that allow these dependencies to be specified.

Multi-image support adds backward-incompatible changes to the format
of the images: specifically adding support for protected TLV entries.
If multiple images and dependencies are not used, the images will be
compatible with previous releases of MCUboot.

### About this release

- Fixed CVE-2019-5477, and CVE-2019-16892.  These fix issue with
  dependencies used in the generation of the documentation on github.
- Numerous code cleanups and refactorings
- Documentation updates for multi-image features
- Update imgtool.py to support the new features
- Updated the mbed TLS submodule to current stable version 2.16.3
- Moved the mbed TLS submodule from within sim/mcuboot-sys to ext.
  This will make it easier for other board supports to use this code.
- Added some additional overflow and bound checks to data in the image
  header, and TLV data.
- Add a `-x` (or `--hex_addr`) flag to imgtool to set the base address
  written to a hex-format image.  This allows the image to be flashed
  at an offset, without having to use additional tools to modify the
  image.

## Version 1.3.1

The 1.3.1 release of MCUboot consists mostly of small bug fixes and updates.
There are no breaking changes in functionality. This release should work with
Mynewt 1.6.0 and up, and any Zephyr `master` after sha
f51e3c296040f73bca0e8fe1051d5ee63ce18e0d.

### About this release

- Fixed a revert interruption bug
- Added ed25519 signing support
- Added RSA-3072 signing support
- Allow ec256 to run on CC310 interface
- Some preparation work was done to allow for multi image support, which
  should land in 1.4.0. This includes a simulator update for testing
  multi-images, and a new name for slot0/slot1 which are now called
  "primary slot" and "secondary slot".
- Other minor bugfixes and improvements

## Version 1.3.0

The 1.3.0 release of MCUboot brings in many fixes and updates.  There
are no breaking changes in functionality.  Many of the changes are
refactorings that will make the code easier to maintain going forward.
In addition, support has been added for encrypted images.  See [the
docs](encrypted_images.md) for more information.

### About this release

- Modernize the Zephyr build scripts.
- Add a `ptest` utility to help run the simulator in different
  configurations.
- Migrate the simulator to Rust 2018 edition.  The sim now requires at
  least Rust 1.32 to build.
- Simulator cleanups.  The simulator code is now built the same way
  for every configuration, and queries the MCUboot code for how it was
  compiled.
- Abstract logging in MCUboot.  This was needed to support the new
  logging system used in Zephyr.
- Add multiple flash support.  Allows slot1/scratch to be stored in an
  external flash device.
- Add support for [encrypted images](encrypted_images.md).
- Add support for flash devices that read as '0' when erased.
- Add support to Zephyr for the `nrf52840_pca10059`.  This board
  supports serial recovery over USB with CDC ACM.
- imgtool is now also available as a python package on pypi.org.
- Add an option to erase flash pages progressively during recovery to
  avoid possible timeouts (required especially by serial recovery
  using USB with CDC ACM).
- imgtool: big-endian support
- imgtool: saves in intel-hex format when output filename has `.hex`
  extension; otherwise saves in binary format.

## Version 1.2.0

The 1.2.0 release of MCUboot brings a lot of fixes/updates, where much of the
changes were on the boot serial functionality and imgtool utility. There are
no breaking changes in MCUBoot functionality, but some of the CLI parameters
in imgtool were changed (either removed or added or updated).

### About this release

- imgtool accepts .hex formatted input
- Logging system is now configurable
- Most Zephyr configuration has been switched to Kconfig
- Build system accepts .pem files in build system to autogenerate required
  key arrays used internally
- Zephyr build switched to using built-in flash_map and TinyCBOR modules
- Serial boot has substantially decreased in space usage after refactorings
- Serial boot build doesn't require newlib-c anymore on Zephyr
- imgtool updates:
  + "create" subcommand can be used as an alias for "sign"
  + To allow imgtool to always perform the check that firmware does not
    overflow the status area, `--slot-size` was added and `--pad` was updated
    to act as a flag parameter.
  + `--overwrite-only` can be passed if not using swap upgrades
  + `--max-sectors` can be used to adjust the maximum amount of sectors that
    a swap can handle; this value must also be configured for the bootloader
  + `--pad-header` substitutes `--included-header` with reverted semantics,
    so it's not required for firmware built by Zephyr build system

### Known issues

None

## Version 1.1.0

The 1.1.0 release of MCUboot brings a lot of fixes/updates to its
inner workings, specially to its testing infrastructure which now
enables a more thorough quality assurance of many of the available
options. As expected of the 1.x.x release cycle, no breaking changes
were made. From the tooling perpective the main addition is
newt/imgtool support for password protected keys.

### About this release

- serial recovery functionality support under Zephyr
- simulator: lots of refactors were applied, which result in the
  simulator now leveraging the Rust testing infrastructure; testing
  of ecdsa (secp256r1) was added
- imgtool: removed PKCS1.5 support, added support for password
  protected keys
- tinycrypt 0.2.8 and the mbed-tls ASN1 parser are now bundled with
  mcuboot (eg secp256r1 is now free of external dependencies!)
- Overwrite-only mode was updated to erase/copy only sectors that
  actually store firmware
- A lot of small code and documentation fixes and updates.

### Known issues

None

## Version 1.0.0

The 1.0.0 release of MCUboot introduces a format change.  It is
important to either use the `imgtool.py` also from this release, or
pass the `-2` to recent versions of the `newt` tool in order to
generate image headers with the new format.  There should be no
incompatible format changes throughout the 1.x.y release series.

### About this release

- Header format change.  This change was made to move all of the
  information about signatures out of the header and into the TLV
  block appended to the image.  This allows
  - The signature to be replaced without changing the image.
  - Multiple signatures to be applied.  This can be used, for example,
    to sign an image with two algorithms, to support different
    bootloader configurations based on these image.
  - The public key is referred to by its SHA1 hash (or a prefix of the
    hash), instead of an index that has to be maintained with the
    bootloader.
  - Allow new types of signatures in the future.
- Support for PKCS#1 v1.5 signatures has been dropped.  All RSA
  signatures should be made with PSS.  The tools have been changed to
  reflect this.
- The source for Tinycrypt has been placed in the MCUboot tree.  A
  recent version of Tinycrypt introduced breaking API changes.  To
  allow MCUboot to work across various platforms, we stop using the
  Tinycrypt bundled with the OS platform, and use our own version.  A
  future release of MCUboot will update the Tinycrypt version.
- Support for some new targets:
  - Nordic nRF51 and nRF52832 dev kits
  - Hexiwear K64
- Clearer sample applications have been added under `samples`.
- Test plans for [zephyr](testplan-zephyr.md), and
  [mynewt](testplan-mynewt.md).
- The simulator is now able to test RSA signatures.
- There is an unimplemented `load_addr` header for future support for
  RAM loading in the bootloader.
- Numerous documentation.

### Known issues

None

## Version 0.9.0

This is the first release of MCUboot, a secure bootloader for 32-bit MCUs.
It is designed to be operating system-agnostic and works over any transport -
wired or wireless. It is also hardware independent, and relies  on hardware
porting layers from the operating system it works with. For the first release,
we have support for three open source operating systems: Apache Mynewt, Zephyr
and RIOT.

### About this release

- This release supports building with and running Apache Mynewt and Zephyr
  targets.
- RIOT is supported as a running target.
- Image integrity is provided with SHA256.
- Image originator authenticity is provided supporting the following
  signature algorithms:
  - RSA 2048 and RSA PKCS#1 v1.5 or v2.1
  - Elliptic curve DSA with secp224r1 and secp256r1
- Two firmware upgrade algorithms are provided:
  - An overwrite only which upgrades slot 0 with the image in slot 1.
  - A swapping upgrade which enables image test, allowing for rollback to a
    previous known good image.
- Supports both mbed-TLS and tinycrypt as backend crypto libraries. One of them
  must be defined and the chosen signing algorithm will require a particular
  library according to this list:
  - RSA 2048 needs mbed TLS
  - ECDSA secp224r1 needs mbed TLS
  - ECDSA secp256r1 needs tinycrypt as well as the ASN.1 code from mbed TLS
    (so still needs that present).

### Known issues

- The image header and TLV formats are planned to change with release 1.0:
  https://runtimeco.atlassian.net/browse/MCUB-66


text:
from django.db.models import Q
from django.shortcuts import render, HttpResponse
from django.views.generic import View
from pure_pagination import Paginator, PageNotAnInteger

from operation.models import UserFavorite, CourseComments, UserCourse
from utils.mixin_utils import LoginRequiredMixin
from .models import Course, CourseResource, Video

# Create your views here.

class CourseListView(View):
    """课程列表"""

    def get(self, request):
        all_courses = Course.objects.all().order_by('-add_time')  # 所有课程
        hot_courses = Course.objects.all().order_by('-click_num')[:3]  # 热门课程推荐,根据点击数取值

        search_keywords = request.GET.get('keywords', '')  # 课程搜索
        if search_keywords:
            all_courses = all_courses.filter(
                Q(name__icontains=search_keywords) | Q(desc__icontains=search_keywords) | Q(
                    detail__icontains=search_keywords))

        sort = request.GET.get('sort', '')  # 排序
        if sort:
            if sort == "students":  # 参与人数
                all_courses = all_courses.order_by('-students')
            elif sort == "hot":  # 热门
                all_courses = all_courses.order_by('-click_num')

        # 对课程进行分页
        try:  # 获取当前页
            page = request.GET.get('page', 1)
        except PageNotAnInteger:
            page = 1

        p = Paginator(all_courses, 3, request=request)  # 进行分页
        courses = p.page(page)  # 获取指定分页的课程

        return render(request, 'course-list.html', {
            'all_courses': courses,
            'sort': sort,
            'hot_courses': hot_courses,
        })

class CourseDetailView(View):
    """课程详情页"""

    def get(self, request, course_id):
        course = Course.objects.get(id=int(course_id))  # 获取当前的课程
        course.click_num += 1  # 增加课程点击数
        course.save()  # 保存到数据库中

        tag = course.tag
        relate_courses = []
        if tag:
            relate_courses = Course.objects.filter(tag=tag)[:1]
        # 课程收藏和机构收藏
        has_fav_course = False
        has_fav_org = False
        if request.user.is_authenticated():
            if UserFavorite.objects.filter(user=request.user, fav_id=course.id, fav_type=1):
                has_fav_course = True
            if UserFavorite.objects.filter(user=request.user, fav_id=course.course_org.id, fav_type=2):
                has_fav_org = True
        return render(request, 'course-detail.html', {
            'course': course,
            'relate_courses': relate_courses,
            'has_fav_course': has_fav_course,
            'has_fav_org': has_fav_org
        })

class CourseInfoView(LoginRequiredMixin, View):
    """课程章节信息"""

    def get(self, request, course_id):
        course = Course.objects.get(id=int(course_id))  # 获取课程链接

        user_courses = UserCourse.objects.filter(user=request.user, course=course)  # 查询用户是否已经关联该课程
        if not user_courses:
            user_course = UserCourse(user=request.user, course=course)  # 学习记录
            user_course.save()
            course.students += 1  # 学习人数+1
            course.save()  # 保存到数据库中
        resources = CourseResource.objects.filter(course=course)  # 课程资源

        user_cousers = UserCourse.objects.filter(course=course)  # 获取当前课程学习的所有用户
        user_ids = [user_couser.user.id for user_couser in user_cousers]  # 所有的用户ID
        all_user_courses = UserCourse.objects.filter(user_id__in=user_ids)  # 用户学过的所有课程
        course_ids = [user_couser.course.id for user_couser in all_user_courses]  # 取出所有课程ID
        relate_courses = Course.objects.filter(id__in=course_ids).order_by('-click_num')[:5]  # 获取学过该用户的其他的所有课程

        return render(request, 'course-video.html', {
            'course': course,
            'resources': resources,
            'relate_courses': relate_courses
        })

class CourseCommentView(LoginRequiredMixin, View):
    """课程评论"""

    def get(self, request, course_id):
        course = Course.objects.get(id=int(course_id))  # 获取当前课程
        resources = CourseResource.objects.filter(course=course)  # 当前课程的资源
        all_comments = CourseComments.objects.filter(course=course).order_by('-add_time')  # 当前课程的所有评论
        user_cousers = UserCourse.objects.filter(course=course)  # 获取当前课程学习的所有用户
        user_ids = [user_couser.user.id for user_couser in user_cousers]  # 所有的用户ID
        all_user_courses = UserCourse.objects.filter(user_id__in=user_ids)  # 用户学过的所有课程
        course_ids = [user_couser.course.id for user_couser in all_user_courses]  # 取出所有课程ID
        relate_courses = Course.objects.filter(id__in=course_ids).order_by('-click_nums')[:5]  # 获取学过该用户的其他的所有课程
        return render(request, 'course-comment.html', {
            'course': course,
            'resources': resources,
            'relate_courses': relate_courses,
            'all_comments': all_comments
        })

class AddCommentView(View):
    """添加课程评论"""

    def post(self, request):
        if not request.user.is_authenticated():
            """判断用户登录状态"""
            return HttpResponse('{"status":"fail","msg":"用户未登录"}', content_type='application/json')

        course_id = int(request.POST.get('course_id', 0))  # 获取课程ID
        comments = request.POST.get('comments', "")  # 获取评论信息
        if course_id > 0 and comments:  # 如果课程ID和评论信息都是合法的
            course_comments = CourseComments()  # 获取评论对象
            course = Course.objects.get(id=int(course_id))  # 获取课程
            course_comments.course = course  # 课程
            course_comments.comments = comments  # 评论
            course_comments.user = request.user  # 评论的用户
            course_comments.save()
            return HttpResponse('{"status":"success","msg":"添加成功"}', content_type='application/json')
        else:
            return HttpResponse('{"status":"fail","msg":"添加失败"}', content_type='application/json')

class VideoPlayView(LoginRequiredMixin, View):
    """视频播放页面"""

    def get(self, request, video_id):
        video = Video.objects.get(id=int(video_id))  # 获取对应的课程
        course = video.lesson.course  # 获取课程
        user_courses = UserCourse.objects.filter(user=request.user, course=course)  # 查询用户是否已经关联该课程
        if user_courses:
            user_course = UserCourse(user=request.user, course=course)  # 用户可课程做关联
            user_course.save()
            course.students += 1  # 学习人数+1
            course.save()  # 保存到数据库中
        user_cousers = UserCourse.objects.filter(course=course)  # 获取当前课程学习的所有用户
        user_ids = [user_couser.user.id for user_couser in user_cousers]  # 所有的用户ID
        all_user_courses = UserCourse.objects.filter(user_id__in=user_ids)  # 用户学过的所有课程
        course_ids = [user_couser.course.id for user_couser in all_user_courses]  # 取出所有课程ID
        relate_courses = Course.objects.filter(id__in=course_ids).order_by('-click_num')[:5]  # 获取学过该用户的其他的所有课程
        resources = CourseResource.objects.filter(course=course)  # 课程资源
        return render(request, 'course-play.html', {
            'course': course,
            'resources': resources,
            'relate_courses': relate_courses,
            'video': video
        })


text:
package net.geocentral.geometria.action;

public interface GActionWithHelp extends GAction {

    public void setHelpId(String helpId);

    public String getHelpId();
}


text:
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from __future__ import unicode_literals

import os
import unittest

from pymatgen.analysis.path_finder import NEBPathfinder, ChgcarPotential
from pymatgen.io.vasp import Poscar, Chgcar, Element

__author__ = 'Ziqin (Shaun) Rong'
__version__ = '0.1'
__maintainer__ = 'Ziqin (Shaun) Rong'
__email__ = 'rongzq08@gmail.com'

class PathFinderTest(unittest.TestCase):
    """
    Uses Li migration in LiFePO4
    """
    def test_image_num(self):
        module_dir = os.path.dirname(os.path.abspath(__file__))
        test_file_dir = os.path.join(module_dir, "..", "..", "..", "test_files",
                                     "path_finder")
        start_s = Poscar.from_file(os.path.join(test_file_dir, 'LFP_POSCAR_s')).structure
        end_s = Poscar.from_file(os.path.join(test_file_dir, 'LFP_POSCAR_e')).structure
        chg = Chgcar.from_file(os.path.join(test_file_dir, 'LFP_CHGCAR.gz'))
        moving_cation_specie = Element('Li')
        relax_sites = []
        for site_i, site in enumerate(start_s.sites):
            if site.specie == moving_cation_specie:
                relax_sites.append(site_i)
        pf = NEBPathfinder(start_s, end_s, relax_sites=relax_sites,
                           v=ChgcarPotential(chg).get_v(), n_images=(8 * 3))
        images = []
        for i, image in enumerate(pf.images):
            if i % 3 == 0:
                images.append(image)
        self.assertEqual(len(images), 9)

if __name__ == '__main__':
    unittest.main()


text:
<?php

namespace Doctrine\Tests\OXM\Types;

use Doctrine\OXM\Types\Type,
    Doctrine\OXM\Types\DateTimeType;

class DateTimeTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var \Doctrine\OXM\Types\DateTimeType
     */
    protected $_type;

    protected function setUp()
    {
        $this->_type = Type::getType('datetime');
    }

    public function testName()
    {
        $this->assertEquals('datetime', $this->_type->getName());
    }

    public function testDateTimeConvertsToXmlValue()
    {
        $date = new \DateTime('1985-09-01 10:10:10');

        $expected = '1985-09-01 10:10:10';
        $actual = $this->_type->convertToXmlValue($date);

        $this->assertEquals($expected, $actual);
    }

    public function testDateTimeConvertsToPHPValue()
    {
        // Birthday of jwage and also birthday of Doctrine. Send him a present ;)
        $date = $this->_type->convertToPHPValue('1985-09-01 00:00:00');
        $this->assertInstanceOf('\DateTime', $date);
        $this->assertEquals('1985-09-01 00:00:00', $date->format('Y-m-d H:i:s'));
    }

    public function testInvalidDateTimeFormatConversion()
    {
        $this->setExpectedException('Doctrine\OXM\Types\ConversionException');
        $this->_type->convertToPHPValue('abcdefg');
    }

    public function testNullConversion()
    {
        $this->assertNull($this->_type->convertToPHPValue(null));
    }
}


text:
from google.cloud import talent_v4beta1

def sample_update_job():
    # Create a client
    client = talent_v4beta1.JobServiceClient()

    # Initialize request argument(s)
    job = talent_v4beta1.Job()
    job.company = "company_value"
    job.requisition_id = "requisition_id_value"
    job.title = "title_value"
    job.description = "description_value"

    request = talent_v4beta1.UpdateJobRequest(
        job=job,
    )

    # Make the request
    response = client.update_job(request=request)

    # Handle the response
    print(response)

# [END jobs_v4beta1_generated_JobService_UpdateJob_sync]


text:
namespace Python.Runtime
{
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using Python.Runtime.Codecs;

    /// <summary>
    /// Defines <see cref="PyObject"/> conversion to CLR types (unmarshalling)
    /// </summary>
    public interface IPyObjectDecoder
    {
        /// <summary>
        /// Checks if this decoder can decode from <paramref name="objectType"/> to <paramref name="targetType"/>
        /// </summary>
        bool CanDecode(PyObject objectType, Type targetType);
        /// <summary>
        /// Attempts do decode <paramref name="pyObj"/> into a variable of specified type
        /// </summary>
        /// <typeparam name="T">CLR type to decode into</typeparam>
        /// <param name="pyObj">Object to decode</param>
        /// <param name="value">The variable, that will receive decoding result</param>
        /// <returns></returns>
        bool TryDecode<T>(PyObject pyObj, out T value);
    }

    /// <summary>
    /// Defines conversion from CLR objects into Python objects (e.g. <see cref="PyObject"/>) (marshalling)
    /// </summary>
    public interface IPyObjectEncoder
    {
        /// <summary>
        /// Checks if encoder can encode CLR objects of specified type
        /// </summary>
        bool CanEncode(Type type);
        /// <summary>
        /// Attempts to encode CLR object <paramref name="value"/> into Python object
        /// </summary>
        PyObject TryEncode(object value);
    }

    /// <summary>
    /// This class allows to register additional marshalling codecs.
    /// <para>Python.NET will pick suitable encoder/decoder registered first</para>
    /// </summary>
    public static class PyObjectConversions
    {
        static readonly DecoderGroup decoders = new DecoderGroup();
        static readonly EncoderGroup encoders = new EncoderGroup();

        /// <summary>
        /// Registers specified encoder (marshaller)
        /// <para>Python.NET will pick suitable encoder/decoder registered first</para>
        /// </summary>
        public static void RegisterEncoder(IPyObjectEncoder encoder)
        {
            if (encoder == null) throw new ArgumentNullException(nameof(encoder));

            lock (encoders)
            {
                encoders.Add(encoder);
            }
        }

        /// <summary>
        /// Registers specified decoder (unmarshaller)
        /// <para>Python.NET will pick suitable encoder/decoder registered first</para>
        /// </summary>
        public static void RegisterDecoder(IPyObjectDecoder decoder)
        {
            if (decoder == null) throw new ArgumentNullException(nameof(decoder));

            lock (decoders)
            {
                decoders.Add(decoder);
            }
        }

        #region Encoding
        internal static PyObject TryEncode(object obj, Type type)
        {
            if (obj == null) throw new ArgumentNullException(nameof(obj));
            if (type == null) throw new ArgumentNullException(nameof(type));

            if (clrToPython.Count == 0)
            {
                return null;
            }

            IPyObjectEncoder[] availableEncoders;
            if (!clrToPython.TryGetValue(type, out availableEncoders))
            {
                availableEncoders = GetEncoders(type);
                clrToPython[type] = availableEncoders;
            }

            foreach (var encoder in availableEncoders)
            {
                var result = encoder.TryEncode(obj);
                if (result != null) return result;
            }

            return null;
        }

        static readonly Dictionary<Type, IPyObjectEncoder[]> clrToPython = new();

        static IPyObjectEncoder[] GetEncoders(Type type)
        {
            lock (encoders)
            {
                return encoders.GetEncoders(type).ToArray();
            }
        }
        #endregion

        #region Decoding
        static readonly ConcurrentDictionary<TypePair, Converter.TryConvertFromPythonDelegate>
            pythonToClr = new ConcurrentDictionary<TypePair, Converter.TryConvertFromPythonDelegate>();
        internal static bool TryDecode(IntPtr pyHandle, IntPtr pyType, Type targetType, out object result)
        {
            if (pyHandle == IntPtr.Zero) throw new ArgumentNullException(nameof(pyHandle));
            if (pyType == IntPtr.Zero) throw new ArgumentNullException(nameof(pyType));
            if (targetType == null) throw new ArgumentNullException(nameof(targetType));

            var decoder = pythonToClr.GetOrAdd(new TypePair(pyType, targetType), pair => GetDecoder(pair.PyType, pair.ClrType));
            result = null;
            if (decoder == null) return false;
            return decoder.Invoke(pyHandle, out result);
        }

        static Converter.TryConvertFromPythonDelegate GetDecoder(IntPtr sourceType, Type targetType)
        {
            IPyObjectDecoder decoder;
            using (var pyType = new PyObject(Runtime.SelfIncRef(sourceType)))
            {
                lock (decoders)
                {
                    decoder = decoders.GetDecoder(pyType, targetType);
                    if (decoder == null) return null;
                }
            }

            var decode = genericDecode.MakeGenericMethod(targetType);

            bool TryDecode(IntPtr pyHandle, out object result)
            {
                var pyObj = new PyObject(Runtime.SelfIncRef(pyHandle));
                var @params = new object[] { pyObj, null };
                bool success = (bool)decode.Invoke(decoder, @params);
                if (!success)
                {
                    pyObj.Dispose();
                }

                result = @params[1];
                return success;
            }

            return TryDecode;
        }

        static readonly MethodInfo genericDecode = typeof(IPyObjectDecoder).GetMethod(nameof(IPyObjectDecoder.TryDecode));

        #endregion

        internal static void Reset()
        {
            lock (encoders)
                lock (decoders)
                {
                    clrToPython.Clear();
                    pythonToClr.Clear();
                    encoders.Clear();
                    decoders.Clear();
                }
        }

        struct TypePair : IEquatable<TypePair>
        {
            internal readonly IntPtr PyType;
            internal readonly Type ClrType;

            public TypePair(IntPtr pyType, Type clrType)
            {
                this.PyType = pyType;
                this.ClrType = clrType;
            }

            public override int GetHashCode()
                => this.ClrType.GetHashCode() ^ this.PyType.GetHashCode();

            public bool Equals(TypePair other)
                => this.PyType == other.PyType && this.ClrType == other.ClrType;

            public override bool Equals(object obj) => obj is TypePair other && this.Equals(other);
        }
    }
}