2015年3月26日木曜日

mosquitto / lib / util_mosq.c の mosquitto_topic_matches_sub の Java 実装を作ってみました

概要

mosquittoで実装されているACLのチェック機構がC言語で実装されていたのでJavaで書きなおしてみました
ソースファイル全体ではなくACLをチェックする一部のメソッドだけ書きなおしています

環境

  • mosquitto 1.4
  • Java 1.8.0_25

ソースコード

package test;

public class Test {

    public static void main(String[] args) {
        Test t = new Test();
        String acl, topic;
        acl = "aaa"; topic = "aaa"; // -> true
        System.out.println(t.checkMatchingACLWithTopic(acl, topic));
        acl = "#"; topic = "test"; // -> true
        System.out.println(t.checkMatchingACLWithTopic(acl, topic));
        acl = "+/aaa"; topic = "aaa/aaa"; // -> true
        System.out.println(t.checkMatchingACLWithTopic(acl, topic));
        acl = ""; topic = "test"; // -> false
        System.out.println(t.checkMatchingACLWithTopic(acl, topic));
        acl = "test"; topic = "test2"; // -> false
        System.out.println(t.checkMatchingACLWithTopic(acl, topic));
        acl = "+"; topic = "test"; // -> true
        System.out.println(t.checkMatchingACLWithTopic(acl, topic));
        acl = "a/+/c"; topic = "a/b/c"; // -> true
        System.out.println(t.checkMatchingACLWithTopic(acl, topic));
        acl = "a/+/+"; topic = "a/b"; // -> false
        System.out.println(t.checkMatchingACLWithTopic(acl, topic));
    }

    /**
    * ACL とアクセスしたいトピックがマッチするかチェックする
    * これは mosquitto/lib/util_mosq.c の mosquitto_topic_matches_sub を Java に 書き換えたものである
    *
    * @param sub トピックに対するユーザのACL情報
    * @param topic アクセスしたいトピック
    * @return trueならばACLの中にアクセスしたトピックのパターンが含まれている
    */
    public boolean checkMatchingACLWithTopic(String sub, String topic) {
        int slen, tlen;
        int spos, tpos;
        boolean multiLevelWildCard = false;

        if (sub == null && topic == null) {
            return false;
        }
        slen = sub.length();
        tlen = topic.length();
        if (slen >= 0 && tlen >= 0) {
            if ((sub.startsWith("$") && !topic.startsWith("$")) || (!sub.startsWith("$") && topic.startsWith("$"))) {
                // $始まりだった場合
                return false;
            }
        }
        spos = 0;
        tpos = 0;
        // sub, topic ともに全文字列をチェックする
        while (spos < slen && tpos < tlen) {
            if(sub.charAt(spos) == topic.charAt(tpos)) {
                if (tpos == tlen -1) {
                    if (spos == slen - 3 && sub.charAt(spos + 1) == '/' && sub.charAt(spos + 2) == '#') {
                        // # で 終わる sub だから true を返却
                        return true;
                    }
                }
                spos++;
                tpos++;
                if (spos == slen && tpos == tlen) {
                    // お互い最後の文字だった場合
                    return true;
                } else if (tpos == tlen && spos == slen - 1 && sub.charAt(spos) == '+') {
                    // topic が最後の文字で sub もあと2文字で今の文字が「+」だった場合
                    return true;
                }
            } else {
                if (sub.charAt(spos) == '+') {
                    spos++;
                    // sub が「+」の場合
                    while (tpos < tlen && topic.charAt(tpos) != '/') {
                        // 次の / 移行の topic に移動する(ACLで + なのでその階層はなんでも許可するため)
                        tpos++;
                    }
                    // 移動した結果どちらも最終文字だった場合
                    if (tpos == tlen && spos == slen) {
                        return true;
                    }
                } else if (sub.charAt(spos) == '#') {
                    // sub が「#」ワイルドカードの場合
                    multiLevelWildCard = true;
                    if (spos + 1 != slen) {
                        // sub が最後から1文字目でない場合(要するに # のあとに何かを指定しちゃっている場合)
                        return false;
                    } else {
                        // # のあとに何も指定していない場合はすべてを許可する
                        return true;
                    }
                } else {
                    // それ以外の文字の場合はマッチしない
                    return false;
                }
            }
        }
        // 最終的に # が使われていないかつ sub または topic の文字列が最後まで達していない場合
        if (multiLevelWildCard == false && (tpos < tlen || spos < slen)) {
            return false;
        }
        // 該当がない場合はエラーにする
        return false;
    }

}

一応CJavaで同じテストを実施して同じ結果になることは確認しています
mosquittoでは更にこれをコールしている上位のメソッドがあってこのメソッドの返り値を見て更に ACC という値と比較して Topic へのアクセス可否を判断していました

0 件のコメント:

コメントを投稿