LoginSignup
0
0

Fortran で Base64 変換

Last updated at Posted at 2023-06-15

要旨

Fortan で base64 の encode/decode を行うルーチンを作った。 Bit 演算を使わないことで Big/Little endian に依らずどちらでもいけるようにした(つもり)。

Base64 とは

Base64 についてはネットで調べてもらった方が早いと思われますが、簡単に言えばバイナリを含むデータを ASCII の可読文字に変換して、可読文字しか使えないようなメールなどでのバイナリファイルの扱いを可能にするものです。

基本的に 3byte = 24bit を 4 つの 6bit に分割して、その 6bit に対して 2^6 = 64 の可読文字を割り当てるものです。24bit からの端数の扱いにひと工夫があって埋草に更に 1 文字用いるので 65 種の ASCII 文字に変換され、全体のサイズは 4/3 倍に膨らみます。

詳細は以下で

Fortan での実装

アルゴリズムは単純ですが、ビットをより分けねばなりません。
bit 演算を用いるには、一度整数に変換しなければなりませんが、そうなると Endian の問題が生じます。1 byte 整数を用いることも考えられますますが、今度は処理系がサポートしているかの問題が生じます。

そこでここでは bit 演算を用いずに、achar/iachar による文字と整数の変換と算術演算での bit 切り出しを行うことにしました。結果的に少し冗長なプログラムになっています。

プログラム

変換・逆変換とも、文字列を引数とし文字列を返す関数として実装しました。

チェックのため 10~10^7 文字の非可読文字を含む文字列を乱数で生成し、base64 にencode、続いて decode を行い、元の文字列が復元されたかを調べています。

    module base64_m
        implicit none

        character, parameter :: b64(0:63) = &
                   transfer('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', ' ', size = 64)
    contains


        function b64enc(text) result(res)
            character(len = *), intent(in) :: text
            character(len = :), allocatable :: res
            integer :: i, n, ires, nres,  m1, m2, m3, k1, k2, k3, k4
            n = len(text) 
            nres = ceiling(n / 3.0) * 4
            allocate(character(len = nres):: res)
            ires = 1 
            do i = 1, (n / 3) * 3, 3
                 m1 = iachar(text(i  :i  ))
                 m2 = iachar(text(i+1:i+1))
                 m3 = iachar(text(i+2:i+2))
                 k1 = m1 /  4                    
                 k2 = m2 / 16 + mod(m1,  4) * 16    
                 k3 = m3 / 64 + mod(m2, 16) *  4 
                 k4 =           mod(m3, 64)     
                 res(ires:ires + 3) = b64(k1) // b64(k2) // b64(k3) // b64(k4) 
                 ires = ires + 4 
            end do
            ! 
            select case(mod(n, 3))
            case(1)
                 m1 = iachar(text(i:i))
                 k1 = m1 / 4          
                 k2 = mod(m1, 4) * 16
                 res(ires:ires+3) = b64(k1) // b64(k2) // '=='
            case(2)   
                 m1 = iachar(text(i   :i ))
                 m2 = iachar(text(i+1:i+1))
                 k1 = m1 / 4                   
                 k2 = m2 / 16 + mod(m1, 4) * 16
                 k3 =           mod(m2, 16) * 4
                 res(ires:ires+3) = b64(k1) // b64(k2) // b64(k3) // '='
            case default
            end select
        end function b64enc

        function b64dec(text) result(res)
            character(*), intent(in) :: text
            character(:), allocatable :: res
            integer :: i, j, n, no, m1, m2, m3, m4, k1, k2, k3
            n = len(text)
            no = (n / 4) * 3
            if (text(n  :n  ) == '=') no = no - 1
            if (text(n-1:n-1) == '=') no = no - 1
            allocate(character(len = no):: res)
            j = 1
           do i = 1, n-4, 4 
                m1 = findloc(b64, text(i  :i  ), dim = 1) - 1
                m2 = findloc(b64, text(i+1:i+1), dim = 1) - 1
                m3 = findloc(b64, text(i+2:i+2), dim = 1) - 1
                m4 = findloc(b64, text(i+3:i+3), dim = 1) - 1
                k1 =     m1       * 4 + m2 / 16
                k2 = mod(m2, 16) * 16 + m3 /  4
                k3 = mod(m3,  4) * 64 + m4
                res(j  :j  ) = achar(k1)
                res(j+1:j+1) = achar(k2)
                res(j+2:j+2) = achar(k3)  
                j = j + 3 
            end do
            !
            m1 = findloc(b64, text(i  :i  ), dim = 1) - 1
            m2 = findloc(b64, text(i+1:i+1), dim = 1) - 1
            k1 = m1 * 4 + m2 / 16
            res(j:j) = achar(k1)
            if (text(i+2:i+2) == '=') return
            m3 = findloc(b64, text(i+2:i+2), dim = 1) - 1
            k2 = mod(m2, 16) * 16 + m3 / 4
            res(j+1:j+1) = achar(k2)
            if (text(i+3:i+3) == '=') return
            m4 = findloc(b64, text(i+3:i+3), dim = 1) - 1
            k3 = mod(m3,  4) * 64 + m4
            res(j+2:j+2) = achar(k3)  
         end function b64dec

    end module base64_m


    program base64enc
        use base64_m
        implicit none
        character(:), allocatable :: buff, buff64, buff64d
        integer :: i, j, k
        real, allocatable :: x(:)

        call random_seed()
        do i = 0, 7
            k = 10**i 
            allocate(x(k))
            allocate(character(len = k)::buff)
            call random_number(x)
            forall(j=1:k) buff(j:j) = achar( int( 256*x(j) ) )
            buff64 = b64enc(buff)
            buff64d = b64dec(buff64)
            if (buff == buff64d) then 
                print *, k, 'OK: '!, buff64
            else
                print *, k, '*** ', buff64
                stop
            end if
            deallocate(x, buff, buff64, buff64d)
        end do 
    end program base64enc

実行結果

           1 OK: 
          10 OK: 
         100 OK: 
        1000 OK: 
       10000 OK: 
      100000 OK: 
     1000000 OK: 
    10000000 OK: 

10^7 byte ~ 10Mbyte の時、encode/decode に 2 秒程度かかっています。

ここには示しませんが適当な文字列を変換し、ネット上のオンライン base64 変換サイトでの結果との比較も行って確認しています。

まとめ

Fortran で base64 encode/decode のプログラムを書いた。Endian に依らない実装にした(つもりだが、little endian でしか確認していない)。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0