Infinity Keyboard Kit 製作記(開封編)

MassdropInfinity Keyboard Kitを購入し、先日自宅に届きました。今回は、Massdrop 、Infinity Keyboard Kitの紹介と、その開封の様子を紹介します。

Massdropとはなにか

Massdropは、マニアックなPC用キーボードやオーディオ機器を手に入れたいときに有用な、オンラインコミュニティサイトです。 MassdropのHow It Worksによると、以下の様にあります。

Massdropは、いくつかのコミュニティ(オーディオ、エレクトロニクスからキルティングや料理まで)のオタクたちに、互いに繋がり、お気に入りのプロダクトやアクティビティについてディスカッションし、共にプロダクトを購入するための場を提供するオンラインコミュニティです。

コミュニティで購入希望者が一定数集まったところで発注をかけるので、商品を安く購入できたり、ニッチすぎて小売店に出ないようなものが買えたりします。
How It Worksにあるように、商品についてのディスカッションの掲示板もあります。基本英語なので、読むことに徹しておりますが。

Infinity Keyboard Kitとはなにか

Infinity Keyboard Kitは、その名の通り、キーボードのキットです。他の特徴は、

といった感じです。
気になる方は、こちらを参照ください。今は購入者の募集はしていませんが、人気商品のようなのできっと再募集があると思います。

開封!

ここからは写真でお届けします。


届いた箱の外観。はるばる米国よりやってきました。


中身が緩衝材にしっかり包まれるように、丁寧に梱包されていました。安心感あります。


内容物。

  • 上段左:外装ケース | 上段右:大型キーのスタビライザー
  • 中段左:キースイッチを支持するプレート | 中段右:miniUSBケーブル
  • 下段左:回路基盤 | 下段右:キースイッチ

キースイッチはCherry MXの茶軸を選択しました。実は初めてのメカニカルスイッチ。
キートップも同時に注文できますが、今回は見送りしました。別途調達するつもりです。


回路基盤のアップ。この基盤に、キースイッチを一つひとつはんだ付けします。


外装ケースのアップ。写真では伝わりにくいですが、アルミ製で重量感があり、質感も安っぽさはありません。これはうれしい誤算でした。

私の性癖のせいでしょうが、はじめてiPadを開封するときよりもわくわくしてしまいました。早くこのキーボードで文字打ちしたいです。
組立て始めたら経過をレポートします。ではまた。

CXFで生成したスタブにequals()やhashCode()が実装されるようにする

CXFを使ってSOAPのサービスを構築する際、多くの場合、wsdl2Javaを利用してWSDLからスタブを生成(XMLからJavaへのデシリアライズ)することになると思います。 このとき、単にwsdl2javaを実行するだけでは、生成されたクラスにはequals()やhashCode()などのユーティリティが実装されていません。

そこで、CXFのwsdl2javaで、equals()やhashCode()が実装されたスタブを生成する方法を調べてみました。

基本方針

Javaコードへのデシリアライズにおいて、JAXB2 Basics Pluginをプラグインする事によって、equals()やhashCode()などのユーティリティが生成されるようにします。 mavenやantからwsdl2javaを実行する際に、JAXB2 Basics Pluginをプラグインする手順を以下に記します。

mavenでやる場合

mavenでやる方法は、Using JAXB2 Basics Plugins with CXF に詳しいです。「A fragment of pom.xml/build/plugins」にあるように、pom.xmlに追記すればよい模様。

ant でやる場合

ant でやる場合、antからJavaコマンドでWSDLToJavaをたたいてやるわけですが、このときクラスパスにJAXB2 Basics Pluginプラグインを加えておくと、ユーティリティを追加するためのオプションが使えるようになります。 targetタグの記述例を記します。"-xjc-XhashCode"、"-xjc-Xequals"が該当のオプションです。

<!-- equals()とhashCode()を追加する場合 -->
<target name="wsdl2java.stub"
    <java classname="org.apache.cxf.tools.wsdlto.WSDLToJava" fork="true">
        <arg value="-xjc-XhashCode" />
        <arg value="-xjc-Xequals" />
        <arg value="-verbose" />
        <arg value="-validate" />
        <arg value="-mark-generated" />
        <arg value="-d" />
        <arg value="${path_dist}" />
        <arg value="-p" />
        <arg value="http://www.example.jp/2014/12/example/ws=jp.example.ws" />
        <arg value="-p" />
        <arg value="http://www.example.jp/2014/12/example/ws/types=jp.example.ws.types" />
        <arg value="${path_wsdl}" />
        <classpath>
            <path refid="${classpath}" />
        </classpath>
    </java>
</target>

クラスパスに入れる必要のある、プラグインのjarは、MavenRepositoryから探してきます。当たり前かもしれませんが、素のantでは依存モジュールの解決を自動ではやってくれません。

それにしても、CXFは本当に日本語の技術情報が少ないですね…。

'二次元のキーに対する値を保持するデータ構造'の実装方法(Java)

個人的に作成しているデータ集計用のプログラムで、二次元の表形式のデータ構造を利用したかったため、その実装方法を考えてみました。

実装方針を決める

軽くWebを探した限りでは、いずれのケースでも、JDKのCollection型を組み合わせて実現しています。組み合わせ方に関しては、以下2通りの方法を採るケースがほとんどのようです。

1.Mapの値として、さらにMapを持たせる:Map<R, Map<C, V>>
2.Mapのキーに、要素数 2のListを用いる:Map<List, V>

今回は、上記のいずれかを用いることにしました。これらををラップして、put() や get() でデータの出し入れができるようにします。 また、ラップするCollectionの実装型には、1. ではHashMapを、2.ではArrayListとHashMapを使います。

パフォーマンスを検討する

1.と2.のどちらかを選択するために、パフォーマンスを測って比較してみました。 キー、値はすべてString型にしています。 1,000 × 1,000 = 1,000,000 通りのキーの組み合わせに対して値を put/get する操作を1試行とし、100 試行の平均所要時間を出しました。 以下、計測結果です。

- put() [ms] get()[ms]
1.Map<R, Map<C, V>> 85 42
2.Map<List, V> 1753 1229


結論

1.Map<R, Map<C, V>>は、2.Map<List, V>に比べて、圧倒的に速いです(put で20分の1、get で30分の1の処理時間)。 というわけで、今回は1. を採用することにしました。


コード(付録)

インターフェース Table

/**
 * テーブルのインターフェース
 * テーブルは、二次元のキーに対する値を保持するデータ形式
 */
public interface Table<R, C, V> {


    /**
     * テーブルに値を追加する
     *
     * @param rowKey テーブルの行
     * @param columnKey テーブルの列
     * @param value
     */
    public void put(R rowKey, C columnKey, V value);


    /**
     * テーブルから値を取得する
     *
     * @param rowKey
     * @param columnKey
     * @return
     */
    public V get(R rowKey, C columnKey);
}

HashMapsTable(Tableを入れ子のHashMapを使って実装したもの)

import java.util.HashMap;
import java.util.Map;


/**
 * HashMapを入れ子にすることによりテーブルを実現する、Tableインターフェースの実装
 *
 * @param <R>
 * @param <C>
 * @param <V>
 */
public class HashMapsTable<R, C, V> implements Table<R, C, V> {


    private Map<R, Map<C, V>> table = new HashMap<R, Map<C, V>>();


    @Override
    public void put(R rowKey, C columnKey, V value) {
        Map<C, V> row = table.get(rowKey);
        if (row == null) {
            row = new HashMap<C, V>();
            table.put(rowKey, row);
        }
        row.put(columnKey, value);
    }


    @Override
    public V get(R rowKey, C columnKey) {
        Map<C, V> row = table.get(rowKey);
        if (row == null) {
            return null;
        }
        return row.get(columnKey);
    }


}

ArrayListAndHashMapTable(TableをArrayListをキーとしたHashMapを使って実装したもの)

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * ArrayListをキーとしたHashMapによりテーブルを実現する、Tableインターフェースの実装
 *
 * @param <R>
 * @param <C>
 * @param <V>
 */
public class ArrayListAndHashMapTable<R, C, V> implements Table<R, C, V> {


    private Map<List<Object>, V> table = new HashMap<List<Object>, V>();


    @Override
    public void put(R rowKey, C columnKey, V value) {
        List<Object> key = new ArrayList<Object>(2);
        key.add(0, rowKey);
        key.add(1, columnKey);
        table.put(key, value);
    }


    @Override
    public V get(R rowKey, C columnKey) {
        List<Object> key = new ArrayList<Object>(2);
        key.add(0, rowKey);
        key.add(1, columnKey);
        return table.get(key);
    }
}

パフォーマンス測定(メイン)

public class Test {


    /*
     *  keyとvalue
     *  行、列のサイズをそれぞれ1000とし、全てのキーの組み合わせに対する値を用意する
     *  キー、値の型はすべてString
     */
    private static final int COLUMN_SIZE = 1000;
    private static final String[] COLUMN_KEYS = new String[COLUMN_SIZE];
    static {
        for (int i = 0; i < COLUMN_KEYS.length; i++) {
            COLUMN_KEYS[i] = "C" + i;
        }
    }
    private static final int ROW_SIZE = 1000;
    private static final String[] ROW_KEYS = new String[ROW_SIZE];
    static {
        for (int i = 0; i < ROW_KEYS.length; i++) {
            ROW_KEYS[i] = "R" + i;
        }
    }
    private static final String[][] VALUES =
            new String[COLUMN_KEYS.length][ROW_KEYS.length];
    static {
        for (int i = 0; i < COLUMN_KEYS.length; i++) {
            for (int j = 0; j < ROW_KEYS.length; j++) {
                VALUES[i][j] = COLUMN_KEYS[i] + ROW_KEYS[j];
            }
        }
    }


    private static final int TRIAL_NUMBER = 100;


    /**
     * main関数
     *
     * @param args
     */
    public static void main(String[] args) {
        long elapsed_put = 0, elapsed_get = 0;
        long mean_put = 0, mean_get = 0;


        System.out.println("----- Map-Map table. -----");
        for (int i = 0; i < TRIAL_NUMBER; i++) {
            Table<String, String, String> mm =
                    new HashMapsTable<String, String, String>();
            elapsed_put += testPut(mm, COLUMN_KEYS, ROW_KEYS, VALUES);
            elapsed_get += testGet(mm, COLUMN_KEYS, ROW_KEYS);
        }
        mean_put = elapsed_put / TRIAL_NUMBER;
        mean_get = elapsed_get / TRIAL_NUMBER;
        System.out.println("elapsed mean time of...");
        System.out.println("    put(): " + mean_put + " [ms]");
        System.out.println("    get(): " + mean_get + " [ms]");


        elapsed_put = 0;
        elapsed_get = 0;


        System.out.println("----- List-Map table. -----");
        for (int i = 0; i < TRIAL_NUMBER; i++) {
            Table<String, String, String> lm =
                    new ArrayListAndHashMapTable<String, String, String>();
            elapsed_put += testPut(lm, COLUMN_KEYS, ROW_KEYS, VALUES);
            elapsed_get += testGet(lm, COLUMN_KEYS, ROW_KEYS);
        }
        mean_put = elapsed_put / TRIAL_NUMBER;
        mean_get = elapsed_get / TRIAL_NUMBER;
        System.out.println("elapsed mean time of...");
        System.out.println("    put(): " + mean_put + " [ms]");
        System.out.println("    get(): " + mean_get + " [ms]");
    }




    /**
     * テーブルの全ての要素に値を設定する
     * 1,000 * 1,000 で、1,000,000回の処理
     */
    private static long testPut(Table<String, String, String> table,
            String[] c_keys, String[] r_keys, String[][] values) {
        if (c_keys == null || r_keys == null || values == null) {
            throw new NullPointerException();
        }
        if (c_keys.length != values.length
                || r_keys.length != values[0].length) {
            throw new IllegalArgumentException();
        }


        long begin = System.currentTimeMillis();
        for (int i = 0; i < c_keys.length; i++) {
            for (int j = 0; j < r_keys.length; j++) {
                table.put(c_keys[i], r_keys[j], values[i][j]);
            }
        }
        long end = System.currentTimeMillis();
        return end - begin;
    }


    /**
     * テーブルの全ての要素の値を取得する
     * 1,000 * 1,000 で、1,000,000回の処理
     */
    private static long testGet(Table<String, String, String> table,
            String[] c_keys, String[] r_keys) {
        if (c_keys == null || r_keys == null) {
            throw new NullPointerException();
        }


        long begin = System.currentTimeMillis();
        for (int i = 0; i < c_keys.length; i++) {
            for (int j = 0; j < r_keys.length; j++) {
                String value = table.get(c_keys[i], r_keys[j]);
//                System.out.print(value + " ");
            }
//            System.out.println();
        }
        long end = System.currentTimeMillis();
        return end - begin;
    }


}

TLSチャンネルID(IETFドラフト)翻訳記事 目次

TLSチャンネルIDは、GoogleのWebブラウザ ”Chrome”で実装され、テストされている技術で、Cookie を特定のチャンネル ID と暗号で結び付けることで、セッションが乗っ取られるリスクを低減するものです。

過去8回に分けて、TLSチャンネルIDのIETFドラフトの翻訳記事をアップしてきましたが、前回いよいよ完結し、この度目次をアップしておくことにしました。 どのくらい需要があるものかわからないですが、エンジニアのみなさんの参考になれば幸いです。

翻訳記事 目次

参考文献

TLSチャンネルID 翻訳 - 9. IANA Considerations

TLSチャンネルIDの翻訳シリーズの第8回です。今回が最終回です。「9. IANA Considerations」の翻訳結果を投稿します。

シリーズの過去の記事は、 以下を参照ください。
第1回:「TLSチャンネルID 翻訳 - 1. introduction」
第2回:「TLSチャンネルID 翻訳 - 2. Why not client certificates」
第3回:「TLSチャンネルID 翻訳 - 3. Requirements Notation / 4. Channel ID Client Keys」
第4回:「TLSチャンネルID 翻訳 - 5. Channel ID Extention」
第5回:「TLSチャンネルID 翻訳 - 6. Security Considerations」
第6回:「TLSチャンネルID 翻訳 - 7. Use Cases」
第7回:「TLSチャンネルID 翻訳 - 8. Privacy Considerations」


9. IANAについての考慮事項

9. IANA Considerations

This document requires IANA to update its registry of TLS extensions to assign an entry referred to here as "channel_id".

このドキュメントは、IANAに対して、TLS拡張の登録簿を、"channel_id"と表される項目を追加するように更新することを求める。

This document also requires IANA to update its registry of TLS handshake types to assign an entry referred to here as "encrypted_extensions".

また、このドキュメントはIANAに対して、TLSハンドシェイクの種類の登録簿を、"encrypted_extensions"と表される項目を追加するように更新することを求める。

(「10. リファレンス」以下の翻訳は省略)

TLSチャンネルID 翻訳 - 8. Privacy Considerations

TLSチャンネルIDの翻訳シリーズの第7回です。「8. Privacy Considerations」の翻訳結果を投稿します。
このシリーズも、もう今回と、最終回を残すのみとなってきました。

シリーズの過去の記事は、 以下を参照ください。
第1回:「TLSチャンネルID 翻訳 - 1. introduction」
第2回:「TLSチャンネルID 翻訳 - 2. Why not client certificates」
第3回:「TLSチャンネルID 翻訳 - 3. Requirements Notation / 4. Channel ID Client Keys」
第4回:「TLSチャンネルID 翻訳 - 5. Channel ID Extention」
第5回:「TLSチャンネルID 翻訳 - 6. Security Considerations」
第6回:「TLSチャンネルID 翻訳 - 7. Use Cases」

ー ー ー

8. プライバシーについての考慮事項

8. Privacy Considerations

The TLS layer does its part in protecting user privacy by transmitting the Channel ID public key under encryption. Higher levels of the stack must ensure that the same Channel ID is not used with different servers in such a way as to provide a linkable identifier. For example, a user-agent must use different Channel IDs for communicating with different servers. Because channel-bound cookies are an important use case for TLS Channel ID, and cookies can be set on top-level domains, it is RECOMMENDED that user-agents use the same Channel ID for servers within the same top-level domain, and different Channel IDs for different top-level domains. User-agents must also ensure that Channel ID state can be reset by the user in the same way as other identifiers, i.e. cookies.

TLS層は、チャンネルIDの公開鍵を暗号化の下で転送することにより、ユーザーのプライバシーの保護の一部を担っている。プロトコルスタックの上位の層は、linkable identifierを提供するのと同じように、異なるサーバーに対して同じチャンネルIDが使われないことを保証しなければならない。例えば、user-agentは、異なるサーバーと対話するときに、異なるチャンネルIDを使わなければならない。なぜならば、チャンネルと結びつけられたcookieTLSチャンネルIDの重要なユースケースであり、cookieトップレベルドメインに対して設定される可能性があるので、user-agentは、同じチャンネルIDを同じトップレベルドメインのサーバー群に対して用い、異なるチャンネルIDを異なるトップレベルドメインに対して用いることが推奨される(RECOMMENDED)。また、user-agentは、cookieのような他の識別子と同様に、チャンネルIDの状態がユーザーによってリセットできることを保証しなければならない。

However, there are some security concerns that could result in the disclosure of a client's Channel ID to a network attacker. This is covered in the Security Considerations section.

しかしながら、クライアントのチャンネルIDがネットワーク上の攻撃者に漏洩する結果になるのと、同じセキュリティ上の考慮事項がある。これは、セキュリティ上の考慮事項のセクションで説明されている。

Clients that share an IP address can be disambiguated through their Channel IDs. This is analogous to protocols that use cookies (e.g., HTTP), which also allow disambiguation of user-agents behind proxies.

IPアドレスを共有するクライアントは、それらのチャンネルIDにより識別することができる。これは、HTTPのようなcookieを用いるプロトコルに類似しており、これらはプロキシの背後にあるuserーagentの識別を可能にしている。

Channel ID has been designed to provide privacy equivalent to that of cookies. User-agents SHOULD continue to meet this design goal at higher layers of the protocol stack. For example, if a user indicates that they would like to block third-party cookies (or if the user-agent has some sort of policy around when it blocks thirdparty cookies by default), then the user agent SHOULD NOT use Channel ID on third-party connections (or other connections through which the user-agent would refuse to send or accept cookies).

チャンネルIDはcookieと同等のプライバシーを提供するように設計されている。userーagentは、プロトコルスタックの上位層において、この設計の目標を達成し続けるべきである(SHOULD)。例えば、もしユーザーがサードパーティーのcookieをブロックしたいと表明したら(あるいは、userーagentがデフォルトでサードパーティーのcookieをブロックするというポリシーを持っていたら)、userーagentは、サードパーティーとのコネクションに(あるいは、userーagentがcookieを送信したり受け入れたりするのを拒否するような他のコネクションに)チャンネルIDを使用するべきではない(SHOULD NOT)。

TLSチャンネルID 翻訳 - 7. Use Cases

TLSチャンネルIDの翻訳シリーズの第6回です。「7. Use Cases」の翻訳結果を投稿します。

シリーズの過去の記事は、 以下を参照ください。
第1回:「TLSチャンネルID 翻訳 - 1. introduction」
第2回:「TLSチャンネルID 翻訳 - 2. Why not client certificates」
第3回:「TLSチャンネルID 翻訳 - 3. Requirements Notation / 4. Channel ID Client Keys」
第4回:「TLSチャンネルID 翻訳 - 5. Channel ID Extention」
第5回:「TLSチャンネルID 翻訳 - 6. Security Considerations」


7. ユースケース

7. Use Cases

7. 1. チャンネルに結びつけたCookie

7.1. Channel-Bound Cookies

An HTTP application on the server can channel-bind its cookies by associating them with the Channel ID of the user-agent that the cookies are being set on. The server MAY then choose to consider cookies sent from the user-agent invalid if the Channel ID associated with the cookie does not match the Channel ID used by the user-agent when it sends the cookie back to the server.

サーバーで動作するHTTPのアプリケーションは、cookieがセットされたuserーagentのChannel IDを、(cookieに)関連づけることによって、cookiechannel-bindにすることができる。サーバーは、cookieに紐付けられたChannel IDが、cookieをサーバーに送り返してきたuserーagentで使用されているChannel IDと一致しない場合に、userーagentから送られたcookieを無効と見なしてもよい(MAY)。

Such a mismatch could occur when the cookie has been obtained from the legitimate user-agent and is now being sent by a client not in possession of the legitimate user-agent's Channel ID private key. The mismatch can also occur if the legitimate user-agent has changed the Channel ID it is using for the server, presumably due to the user requesting a Channel ID reset through the user-agent's user interface (see Section 8). Such a user intervention is analogous to the user's removal of cookies from the user-agent, but instead of removing cookies, the cookies are being rendered invalid (in the eyes of the server).

このような不整合は、cookieが正規のuserーagentから取得され、正規のuserーagentのChannel IDの秘密鍵を保持していないクライアントから(そのcookieが)送付された場合に起こりうる。この不整合は、正規のuserーagentが、おそらくユーザーがuserーagentのインターフェースからリセットを要求することによって、サーバーに対して使用しているChannel IDを変更した場合にも起こりうる(Section 8を参照)。このようなユーザーの介入は、ユーザーがuserーagentからcookieを削除することに類似しているが、cookieを削除する代わりに、(サーバーの視点では)cookieが無効として扱われる。

7. 2. チャンネルに結びつけたOAuthトークン

7.2. Channel-Bound OAuth Tokens

Similarly to cookies, a server may choose to channel-bind OAuth tokens (or any other kind of authorization tokens) to the clients to which they are issued. The mechanism on the server remains the same (it associates the OAuth token with the client's Channel ID either by storing this information in a database, or by suitably encoding the information in the OAuth token itself), but the application-level protocol may be different: In addition to HTTP, OAuth tokens are used in protocols such as IMAP and XMPP.

cookieと同じように、サーバーは、発行先のクライアントに対するchannel-bindなOAuthトークン(または、他の任意の認可トークン)を選択してもよい。サーバー上の機構は同じ(データベースにこの情報*1を保存するか、OAuthトークン自身の中にしかるべき方法でエンコードすることによって、OAuthトークンをクライアントのChannel IDに紐付ける)であるが、アプリケーションレベルのプロトコルは異なるかもしれない:HTTPに加えて、OAuthトークンはIMAPXMPPのようなプロトコルでも用いられる。

*1:「この情報」は、Channel ID を指しているものと思われる。