Internet Kill Switch by Firewall (OpenVPN + iptables)

タイトルの通り. このPythonプログラムはopenvpnの設定ファイルの

remote <hostname> <port>

remote <ip address> <port>

に書き換える.

また, 書き換えたVPNサーバのIPアドレスへのアクセスを許可するiptablesのOUTPUTチェインの追加ルールを./rules.txtに保存する. iptables-restoreで読み込める形にしてるので, このルールを追加しその他のルールを適切に設定すれば, ファイアウォールキルスイッチを実現できる.

プログラム

使用は自己責任で

何をやってるかわからない人は実行しないほうがよいです.

Use at your own risk

Don't execute it unless you understand what it does.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import socket
import ipaddress

def main():
    from argparse import ArgumentParser
    parser = ArgumentParser()
    parser.add_argument('-d', '--dry-run', action='store_true')
    args = parser.parse_args()

    path = '/etc/openvpn/client/'
    filenames = [f for f in os.listdir(path)
                 if os.path.isfile(path + f) and (f.endswith('.conf') or f.endswith('.ovpn'))]

    targets = []
    for filename in filenames:
        lines = None
        with open(path + filename, 'r') as f:
            lines = [line.strip() for line in f.readlines()]
        found = False
        port = 0
        addr = ''
        proto = ''
        for i, line in enumerate(lines):
            if not line:
                continue
            linesplit = line.split()
            if linesplit[0] == 'remote':
                port = linesplit[2]
                try:
                    addr = socket.gethostbyname(linesplit[1])
                    ipaddress.ip_address(addr)
                except:
                    print('cannot get ip address of {} in {}'.format(linesplit[1], filename))
                    break
                found = True
                lines[i] = 'remote {} {}'.format(addr, port)
            elif linesplit[0] == 'proto':
                proto = linesplit[1]
        if found:
            if not args.dry_run:
                with open(path + filename, 'w') as f:
                    f.writelines('{}\n'.format('\n'.join(lines)))
            targets.append({
                'addr': addr,
                'port': port,
                'proto': proto,
                'comment': 'VPN {}'.format(' '.join(filename.rsplit('.', 1)[0].split('_'))),
            })

    rules = []
    # iptables rules
    for target in targets:
        rules.append('# {}'.format(target['comment']))
        rules.append('-A OUTPUT -p {} -m {} -d {}/32 --dport {} -j ACCEPT'.format(target['proto'], target['proto'], target['addr'], target['port']))

    f = None
    if not args.dry_run:
        try:
            f = open('rules.txt', 'w')
        except OSError:
            print('failed to create a rule file.')
            print('write to stdout')
    try:
        print(*rules, sep='\n', file=f)
    except IOError:
        print('failed to write to the rule file')
    if f is not None and not args.dry_run:
        f.close()

if __name__ == '__main__':
    main()

おまけ

動作確認はしていないので注意. 統合する気はありません.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys

def main():
    with open('./rules.txt') as f:
        vpn_rules = f.readlines()
    fw = '/etc/iptables/iptables.rules'
    with open(fw) as f:
        rules = f.readlines()
    start = -1
    end = -1
    for i, rule in enumerate(rules):
        if rule.startswith('## BEGIN VPN'):
            start = i
        elif rule.startswith('## END VPN'):
            end = i
            break
    if start >= end or end == -1:
        sys.exit(1)
    rules = rules[:start + 1] + vpn_rules + rules[end:]                                                

    print(*rules, sep='')

    yn = input('write to file?[y/N]: ')
    if yn == 'y':
        with open(fw, 'w') as f:
            f.writelines(rules)

if __name__ == '__main__':
    main()

安全でないストリーム暗号ARCFOUR

はじめに

Wikipediaを見てみたらARCFOURのアルゴリズムが単純だったので実装してみた. 一応言っておくが, 暗号アルゴリズムの実装は素人がやるものではない.

参考記事: https://en.wikipedia.org/wiki/RC4

記事に書いてある通りに実装した.

ソースコード

/* This program is in the public domain */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>

#define MAX_KEY_LEN 256
#define S_LEN 256

uint8_t key[MAX_KEY_LEN + 1];
uint8_t S[S_LEN];

void swap(uint8_t *a, uint8_t *b)
{
    uint8_t t = *a;
    *a = *b;
    *b = t;
}

char *readline(char *s, size_t size)
{
    if (fgets(s, size, stdin) == NULL)
        return NULL;
    if (strchr(s, '\n') != NULL)
        s[strlen(s) - 1] = '\0';
    else
        while (getchar() != '\n')
            ;
    return s;
}

/* key-scheduling algorithm */
void KSA(size_t key_len)
{
    size_t i, j;
    for (i = 0; i < S_LEN; i++)
        S[i] = i;
    j = 0;
    for (i = 0; i < S_LEN; i++) {
        j = (j + S[i] + key[i % key_len]) % S_LEN;
        swap(&S[i], &S[j]);
    }
}

/* pseudo-random generation algorithm (PRGA) */
uint8_t PRGA(void)
{
    /* static variables! */
    static size_t i, j;
    i = (i + 1) % S_LEN;
    j = (j + S[i]) % S_LEN;
    swap(&S[i], &S[j]);
    return S[(S[i] + S[j]) % S_LEN];
}

int main(int argc, char **argv)
{
    FILE *fr, *fw;
    uint8_t buf;
    size_t key_len;

    if (argc != 3) {
        fprintf(stderr, "specify files\n");
        return 1;
    }
    if (!readline((char *)key, MAX_KEY_LEN + 1)) {
        fprintf(stderr, "failed to read key");
        return 1;
    }
    key_len = strlen((char *)key);
    KSA(key_len);
    if ((fr = fopen(argv[1], "rb")) == NULL) {
        fprintf(stderr, "failed to open file");
        return 1;
    }
    if ((fw = fopen(argv[2], "wb")) == NULL) {
        fprintf(stderr, "failed to open file");
        fclose(fr);
        return 1;
    }
    for (;;) {
        fread(&buf, sizeof(buf), 1, fr);
        if (feof(fr))
            break;
        if (ferror(fr)) {
            fprintf(stderr, "failed to read\n");
            break;
        }
        buf ^= PRGA();
        if (fwrite(&buf, sizeof(buf), 1, fw) < 1) {
            fprintf(stderr, "failed to write\n");
            break;
        }
    }
    fclose(fr);
    fclose(fw);
    return 0;
}

動作テスト

テキストファイルtest.txtを暗号化する. 暗号化すると文字化けするので16進数で表示する.

$ hexdump test.txt                                                                
0000000 83e3 e390 bc83 83e3 e381 a383 83e3 e3ab                                                         
0000010 a983 82e3 e3a4 9083 83e3 e3bc ae81 9ce6                                                         
0000020 e388 8e83 bee7 e58e 8e85 81e3 e3a7 9981                                                         
0000030 80e3 0a82 81e3 e3bf a881 83e3 e3a9 b882                                                         
0000040 81e3 e8ae b3a9 b4e7 e3b0 af81 81e3 e393                                                         
0000050 a181 82e3 ef89 81bc 86e2 6892 7474 3a70                                                         
0000060 2f2f 7774 6670 6a2e 2f70 694d 6f74 7354                                                         
0000070 6b75 6e69 0a6f 4d44 81e3 e5af 8b80 88e5                                                         
0000080 e3a5 a781 81e3 e3af aa81 81e3 408f 696e                                                         
0000090 696a 6173 6a6e 5f69 7061 e370 be81 81e3                                                         
00000a0 e3a7 8a81 a1e9 e398 8481 87e8 e3b4 9781                                                         
00000b0 81e3 e3be 9981 80e3 0a82 83e3 e395 a182                                                         
00000c0 83e3 e3b3 a282 83e3 e3bc 8883 81e3 20af                                                         
00000d0 e323 bf81 81e3 e3a8 8281 83e3 e3bc a881                                                         
00000e0 e320 a781 99e7 e8ba 8ba6 81e3 e397 be81                                                         
00000f0 81e3 ef99 81bc 000a
00000f7

これをキーVirtual YouTuberで暗号化してtest.txt.encに保存.

$ ./arc4 test.txt test.txt.enc
Virtual YouTuber
$ hexdump test.txt.enc
0000000 ee83 d240 22d7 820f afa8 e854 2757 303f
0000010 bfed 3ebb f9a3 b055 29da b054 e975 17c2
0000020 1211 d87e 97b4 bae8 a525 3d65 7256 05e6
0000030 138a 697d c071 a7a5 e715 75be 49de 8e4c
0000040 bf07 bfff b0a4 cc27 8498 0dbd 77ea 67b1
0000050 670a 2daa a470 ab84 251f 8129 9885 41c2
0000060 afd8 57e4 2552 39ff 18db 327e 0745 399f
0000070 5c34 b6e2 c6d2 93ef c14b d111 da67 b225
0000080 fb26 fc24 d760 15fa 71e7 dce4 b55f 6d71
0000090 3355 874c e3ef 105b 423d ca88 4d9b 58ec
00000a0 f162 ef70 510a 8faa 6ef5 d474 0a03 1598
00000b0 4a35 7574 494e 2117 b50c 9898 7e58 1a4a
00000c0 93b2 4c73 4116 2b17 b2ab 4290 9d2b 107f
00000d0 1f7e d8be e6ef 0bfe 2012 f740 4595 8a8d
00000e0 ff29 84a3 a444 7c0e fbae 2515 f988 2ce9
00000f0 dcd7 7249 e9ec 002a
00000f7

test.txt.encを復号してtest.txt.enc.decに保存.

$ ./arc4 test.txt.enc test.txt.enc.dec                                            
Virtual YouTuber
$ hexdump test.txt.enc.dec                                                       
0000000 83e3 e390 bc83 83e3 e381 a383 83e3 e3ab
0000010 a983 82e3 e3a4 9083 83e3 e3bc ae81 9ce6
0000020 e388 8e83 bee7 e58e 8e85 81e3 e3a7 9981
0000030 80e3 0a82 81e3 e3bf a881 83e3 e3a9 b882
0000040 81e3 e8ae b3a9 b4e7 e3b0 af81 81e3 e393
0000050 a181 82e3 ef89 81bc 86e2 6892 7474 3a70
0000060 2f2f 7774 6670 6a2e 2f70 694d 6f74 7354
0000070 6b75 6e69 0a6f 4d44 81e3 e5af 8b80 88e5
0000080 e3a5 a781 81e3 e3af aa81 81e3 408f 696e
0000090 696a 6173 6a6e 5f69 7061 e370 be81 81e3
00000a0 e3a7 8a81 a1e9 e398 8481 87e8 e3b4 9781
00000b0 81e3 e3be 9981 80e3 0a82 83e3 e395 a182
00000c0 83e3 e3b3 a282 83e3 e3bc 8883 81e3 20af
00000d0 e323 bf81 81e3 e3a8 8281 83e3 e3bc a881
00000e0 e320 a781 99e7 e8ba 8ba6 81e3 e397 be81
00000f0 81e3 ef99 81bc 000a
00000f7

もとに戻った.

$ sha256sum test.txt test.txt.enc.dec
0e288309792d64d4b14af3db9e847de9e0c32526f2f42b9254580f86648fc3e6  test.txt
0e288309792d64d4b14af3db9e847de9e0c32526f2f42b9254580f86648fc3e6  test.txt.enc.dec

おまけ

Rustの練習で書いてみた. まったく感覚がわからない.

use std::env;
use std::fs::File;
use std::io::{self, BufReader, BufWriter, Read, Write};
use std::process;

const MAX_KEY_LEN: usize = 256;
const S_LEN: usize = 256;

struct Arc4 {
    s: [u8; S_LEN],
    i: usize,
    j: usize,
}

// key-scheduling algorithm
fn ksa(key: &[u8]) -> Arc4 {
    let mut a = Arc4 {
        s: [0; S_LEN],
        i: 0,
        j: 0,
    };
    for i in 0..S_LEN {
        a.s[i] = i as u8;
    }
    let mut j: usize = 0;
    let key_len = key.len();
    for i in 0..S_LEN {
        j = (j + usize::from(a.s[i]) + usize::from(key[i % key_len])) % S_LEN;
        a.s.swap(i, j);
    }
    a
}

// pseudo-random generation algorithm (PRGA)
fn prga(a: &mut Arc4) -> u8 {
    a.i = (a.i + 1) % S_LEN;
    a.j = (a.j + usize::from(a.s[a.i])) % S_LEN;
    a.s.swap(a.i, a.j);
    a.s[(usize::from(a.s[a.i]) + usize::from(a.s[a.j])) % S_LEN]
}

fn main() {
    if env::args().len() != 3 {
        println!("specify files");
        process::exit(1);
    }
    let mut key_input = String::new();
    io::stdin().read_line(&mut key_input).expect("failed to read input from stdin");
    let key_input_trimmed = key_input.trim();
    match key_input_trimmed.len() {
        1...MAX_KEY_LEN => {},
        _ => {
            println!("the key length must be 1-{} bytes (8 - {} bits)", MAX_KEY_LEN, MAX_KEY_LEN * 8);
            process::exit(1);
        }
    }
    let mut a = ksa(key_input_trimmed.as_bytes());
    let args: Vec<String> = env::args().skip(1).collect();
    let fr = File::open(&args[0]).expect("failed to open a file");
    let mut reader = BufReader::new(fr);
    let fw = File::create(&args[1]).expect("failed to create a file");
    let mut writer = BufWriter::new(fw);
    let mut buf = vec![];
    reader.read_to_end(&mut buf).expect("failed to read a file");
    for b in buf.iter_mut() {
        *b ^= prga(&mut a);
    }
    writer.write_all(&buf).expect("failed to write to a file");
}

TweetDeckのサムネイルを小さいサイズの画像にするブックマークレット

通信速度制限を喰らっているときにTweetDeckの画像抽出カラムの流速が速いととてもつらいのでそれを若干緩和させるブックマークレットを書いた. JavaScriptをまともに触るのは初めてなので苦戦. Web系まったくわからん.

Content Security PolicyのアレでこれをFirefoxで実行するにはabout:configからsecurity.csp.enablefalseにする必要がある. 常用する場合は*monkeyとかを使えばよさそう.

javascript:(()=>Array.from(document.getElementsByClassName('chirp-container')).forEach(e=>(new MutationObserver((r,o)=>r.forEach(s=>Array.from(s.addedNodes).forEach(n=>Array.from(n.getElementsByTagName('a')).filter(a=>a.hasAttribute('rel')&&a.getAttribute('rel')==='mediaPreview').forEach(a=>a.style.backgroundImage=a.style.backgroundImage.replace(/name=.*$/,'name=tiny")')))))).observe(e,{childList:true})))();

How to Play an Encrypted Video File Directly with openssl and mpv

opensslmpv を使用して暗号化された動画ファイルを直接再生する方法

やむを得ない事情により, 復号したファイルを保存できない場合があるかもしれない. opensslmpv を使用することで, 暗号化された動画を直接再生することができる.

Example

Assume that your file vvv.enc is encrypted with AES256-CBC with salt, and the passphrase is wholesome.

You can decrypt it and play the movie with the following command:

$ openssl enc -d -aes-256-cbc -salt -pass pass:wholesome -in vvv.enc | mpv --force-seekable -

Linux Kernel の黒魔術マクロ: __is_constexpr(x)

__is_constexpr(x)Linux kernel 4.17 で導入されたマクロで、与えられた式が定数式であるかを 式を評価せずに 判定する。

/*
 * This returns a constant expression while determining if an argument is
 * a constant expression, most importantly without evaluating the argument.
 * Glory to Martin Uecker <Martin.Uecker@med.uni-goettingen.de>
 */
#define __is_constexpr(x) \
        (sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))

kernel/git/torvalds/linux.git - Linux kernel source tree

Linus さんも「それはアイデアとは言わねえ。天才かマジで病んだ頭脳のどっちかだわ」(適当訳) とべた褒めしている。

LKML: Linus Torvalds: Re: detecting integer constant expressions in macros

サンプルコード:

#include <stdio.h>

#define __is_constexpr(x) \
        (sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))

#define sq(x) ((x) * (x))

int main(void)
{
    int a = 2;

    if (__is_constexpr(sq(2)))
        puts("Yes");
    else
        puts("No");

    if (__is_constexpr(sq(a)))
        puts("Yes");
    else
        puts("No");

    return 0;
}

実行結果:

Yes
No

はい天才。

解説は暇があったら書くが、既にある:

c - Linux Kernel's __is_constexpr Macro - Stack Overflow

漢字変換を雑に実装してみる

一発変換と変換候補を出すだけ。 たぶん一番基本的なやつ。

真面目に日本語入力を実現するには形態素解析とかいろいろしなきゃいけないので自分には MURI。 ライブラリもあるけど興味もそこまでない。

Mozc 更新して。

コメント書けないのはアレだけど、Python だと JSON を標準ライブラリでサポートしてるので便利。

Wikipedia の Trie の記事を参考にした: https://en.wikipedia.org/wiki/Trie

ソースコード

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Copyright (C) 2018 Vanaestea

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

from collections import deque

class Node(object):
    def __init__(self):
        self.values = []
        self.children = {}

class Trie(object):
    def __init__(self, vocabulary=None):
        self.__root = Node()
        if vocabulary is not None:
            for key, values in vocabulary.items():
                self.insert(key, values)

    def insert(self, key, values):
        node = self.__root
        i = 0
        while i < len(key):
            if key[i] in node.children:
                node = node.children[key[i]]
                i += 1
            else:
                break

        while i < len(key):
            node.children[key[i]] = Node()
            node = node.children[key[i]]
            i += 1

        node.values.extend(values)

    def query(self, key):
        node = self.__root
        for char in key:
            if char in node.children:
                node = node.children[char]
            else:
                return list()
        return node.values

    def suggest(self, key, n=10):
        suggestions = []

        node = self.__root
        for char in key:
            if char in node.children:
                node = node.children[char]
            else:
                break

        if node is not self.__root:
            suggestions.extend(node.values)

            que = deque()
            que.append(node.children)
            while len(que) > 0:
                for child in que.popleft().values():
                    que.append(child.children)
                    suggestions.extend(child.values)
                    if len(suggestions) > n:
                        return suggestions

        return suggestions

vocabulary.json

{
    "い": ["胃", "異", "煎"],
    "いい": ["良い", "謂", "易々"],
    "いいん": ["委員", "医院"],
    "いいんちょう": ["委員長"]
}

使用例

$ python3
Python 3.5.3 (default, Jan 19 2017, 14:11:04)
[GCC 6.3.0 20170118] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from trie import Trie
>>> import json
>>> with open('vocabulary.json', 'r') as f:
...     vocabulary = json.load(f)
...
>>> trie = Trie(vocabulary)
>>> trie.query('いいんちょう')
['委員長']
>>> trie.query('い')
['胃', '異', '煎']
>>> trie.query('がっきゅういいん')
[]
>>> trie.suggest('いい')
['良い', '謂', '易々', '委員', '医院', '委員長']
>>>

Uniqfuck: Brainfuck Translator

Uniqfuck

Brainfuck プログラムと Brainfuck 派生言語プログラムを相互変換するプログラムを作った。 名前は Uniqfuck とした。 ソースコードは記事の最後に載せる。 ライセンスは MIT License とする。

使い方

Uniqfuck の使い方を簡単に説明する。 Python が読める方はソースを読んだほうがはやい。

必須入力ファイルはソースファイルと文法定義ファイルである。 文法定義は JSON で記述する。

例: Mukadefuck

使い方の例として新たな Brainfuck 派生言語を作った。 言語名は Mukadefuck とした。

文法定義: mukade.json

{
    "commands": {
        ">": "起立! 気を付け!",
        "<": "これがバーチャルYouTuberなんだよなあ……。",
        "+": "よいしょ。",
        "-": "わたくしで隠さなきゃ……!",
        ",": "クソ雑魚パンチやめてくださいwww",
        ".": "僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです!",
        "[": "ぜろ……。ぜろ……。ぜろ……。",
        "]": "おるやんけ! おるやんけ! どなたかおるやんけ!"
    },
    "special_characters": {
        "delimiter": " ",
        "r_delimiter": ""
    }
}

Brainfuck -> Mukadefuck

"Hello, world!" を出力する Brainfuck プログラムを Mukadefuck に変換してみる。 ソースコードニコニコ大百科から引用した。

helloworld.brainfuck

+++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+
++.>-.------------.<++++++++.--------.+++.------.--------.>+.

出典: http://dic.nicovideo.jp/a/brainfuck

次のコマンドを実行すると

$ ./uniqfuck.py -o helloworld.mukadefuck mukade.json helloworld.brainfuck

次のファイルが得られる。

helloworld.mukadefuck

よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 ぜろ……。ぜろ……。ぜろ……。 起立! 気を付け! よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 起立! 気を付け! よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 起立! 気を付け! よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 これがバーチャルYouTuberなんだよなあ……。 これがバーチャルYouTuberなんだよなあ……。 これがバーチャルYouTuberなんだよなあ……。 わたくしで隠さなきゃ……! おるやんけ! おるやんけ! どなたかおるやんけ! 起立! 気を付け! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! 起立! 気を付け! よいしょ。 よいしょ。 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! よいしょ。 よいしょ。 よいしょ。 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! 起立! 気を付け! わたくしで隠さなきゃ……! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! これがバーチャルYouTuberなんだよなあ……。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 よいしょ。 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! よいしょ。 よいしょ。 よいしょ。 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! わたくしで隠さなきゃ……! 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです! 起立! 気を付け! よいしょ。 僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです!

Mukadefuck -> Brainfuck

'X' を 8 個出力する Mukadefuck プログラムを Brainfuck に変換してみる。

x8.mukadefuck

# 'X' を 8 個出力するプログラムを作りましょう。

# ASCII コードの 'X' に対応する数値 88 を 3 番地に用意します。
# まずカウントダウン用に 0 番地に 8 を用意します。
「よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。」

# ここから 0 番地の値が 0 になるまでループします。
「ぜろ……。ぜろ……。ぜろ……。」
    # 2 番地に移動します。
    「起立! 気を付け! 起立! 気を付け!」
    # カウントダウン用 2 番地に 11 を用意します (8 * 11 = 88)。
    「よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。よいしょ。」
    # 2 番地が 0 になるまでループします。
    「ぜろ……。ぜろ……。ぜろ……。」
        # 3 番地に移動し、1 加えます。
        # ここに 88 が用意されます。
        「起立! 気を付け! よいしょ。」
        # 2 番地に移動し、1 引きます。
        「これがバーチャルYouTuberなんだよなあ……。わたくしで隠さなきゃ……!」
    # 2 番地が 0 であれば、次に進みます。
    「おるやんけ! おるやんけ! どなたかおるやんけ!」

    # 1 番地に移動し 1 加えます。
    # なぜかというと、'X' を 8 回表示するために、
    # カウントダウン用の 8 を用意する必要があるからです
    # (「僕はね音楽なんて(略)」を 8 回書くと大変)。
    # ここのループは 8 回繰り返されるので 8 が得られます。
    「これがバーチャルYouTuberなんだよなあ……。よいしょ。」
    # 0 番地に移動して 1 引きます。
    「これがバーチャルYouTuberなんだよなあ……。わたくしで隠さなきゃ……!」
# 0 番地が 0 であれば、次に進みます。
「おるやんけ! おるやんけ! どなたかおるやんけ!」

# 1 番地に移動します (1 番地の値は 8 です)。
「起立! 気を付け!」
# ここから 1 番地の値が 0 になるまでループします。
「ぜろ……。ぜろ……。ぜろ……。」
    # 3 番地まで移動します。
    「起立! 気を付け!起立! 気を付け!」
    # 3 番地の値 88、つまり 'X' を出力します。
    「僕はね音楽なんてどうでもよくて君のことが好きなんやけどでもあのそのだから楽器を握るんじゃなくて君の手を握りたいけどだけれどもだけれでも僕はもうこうやって音楽を奏でて君に言葉を伝えるその術しか持ってないから僕は君の為に歌うもぼ僕のために歌いたいんです!」
    # 1 番地に移動して、1 引きます。
    「これがバーチャルYouTuberなんだよなあ……。これがバーチャルYouTuberなんだよなあ……。わたくしで隠さなきゃ……!」
# 1 番地が 0 であればプログラムは終了します。
「おるやんけ! おるやんけ! どなたかおるやんけ!」

# これでプログラムは終了です。楽しんでいただけましたか?
# 気を付け! 着席!
# 以上、Mukadefuck のプログラムの例をお送りしました。

'-r' オプションを付けて実行すると

$ ./uniqfuck.py  -r -o x8.brainfuck mukade.json x8.mukadefuck

次のファイルが得られる。

x8.brainfuck

++++++++[>>+++++++++++[>+<-]<+<-]>[>>.<<-]

ソースコード

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Copyright (C) 2018 Vanaestea

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import sys
import argparse
import json

def is_uniquely_decodable(codewords):
    def get_residual_words(prefixes, words):
        residual_words = set()
        if len(prefixes) == 0:
            return words

        for prefix in prefixes:
            for word in words:
                if word.find(prefix) == 0:
                    residual_words.add(word[len(prefix):])

        return residual_words

    s = [set()]
    for word in get_residual_words(codewords, codewords):
        s[0].add(word)
    s[0].remove('')

    i = 0
    while True:
        if len(s[i]) == 0:
            return True

        for word in s[i]:
            if word == '' or word in codewords:
                return False

        for t in s[:i]:
            if t == s[i]:
                return True

        s.append(set())
        for word in get_residual_words(codewords, s[i]):
            s[i + 1].add(word)
        for word in get_residual_words(s[i], codewords):
            s[i + 1].add(word)

        i += 1


def main():
    parser = argparse.ArgumentParser(description='Uniqfuck: Meta(?) Brainfuck')
    parser.add_argument('-r', '--reverse', action='store_true', help='convert source code to Brainfuck code')
    parser.add_argument('-s', '--stdout', action='store_true', help='set output to stdout')
    parser.add_argument('-i', '--stdin', action='store_true', help='read source code from stdin')
    parser.add_argument('-o', '--output', nargs='?', default='a.out.txt', help='set output file name')
    parser.add_argument('grammar', metavar='grammar_file', nargs='?', help='grammar definition file')
    parser.add_argument('source', metavar='src_file', nargs='?', help='source file')

    args = parser.parse_args()

    reverse = args.reverse
    stdout = args.stdout
    stdin = args.stdin
    output = args.output
    if args.grammar is None or stdin == False and args.source is None:
        print('must specify files')
        sys.exit()
    grammar = args.grammar
    source = args.source

    commands = None
    special_characters = None
    with open(grammar, 'r') as f:
        grammar_dict = json.load(f)
        commands = grammar_dict['commands']
        special_characters = grammar_dict['special_characters']

    if is_uniquely_decodable(set(v for v in commands.values())) == False:
        print('not uniquely decodable')
        sys.exit()

    if reverse == True:
        commands = dict(((v, k) for k, v in commands.items()))

    source_code = None
    if stdin is True:
        source_code = ''.join(sys.stdin.readlines())
    else:
        with open(source, 'r') as f:
            source_code = ''.join(f.readlines())

    compiled_code = None
    if reverse == True:
        commands_lengths = sorted([len(k) for k in commands.keys()], reverse=True)
        compiled_commands = []
        i = 0

        while i + commands_lengths[-1] < len(source_code):
            for length in commands_lengths:
                if i + length > len(source_code):
                    continue
                command = source_code[i:i + length]
                if command in commands:
                    i += length
                    compiled_commands.append(commands[command])
                    break
            else:
                i += 1
        compiled_code = special_characters['r_delimiter'].join(compiled_commands)
    else:
        compiled_code = special_characters['delimiter'].join(commands[command] for command in source_code if command in commands)

    if stdout == True:
        print(compiled_code)
    else:
        with open(output, 'w') as f:
            f.write(''.join([compiled_code, '\n']))

if __name__ == '__main__':
    main()