logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma

flake_id.ex (5212B)


      1 # Pleroma: A lightweight social networking server
      2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
      3 # SPDX-License-Identifier: AGPL-3.0-only
      4 
      5 defmodule Pleroma.FlakeId do
      6   @moduledoc """
      7   Flake is a decentralized, k-ordered id generation service.
      8 
      9   Adapted from:
     10 
     11   * [flaky](https://github.com/nirvana/flaky), released under the terms of the Truly Free License,
     12   * [Flake](https://github.com/boundary/flake), Copyright 2012, Boundary, Apache License, Version 2.0
     13   """
     14 
     15   @type t :: binary
     16 
     17   use Ecto.Type
     18   use GenServer
     19   require Logger
     20   alias __MODULE__
     21   import Kernel, except: [to_string: 1]
     22 
     23   defstruct node: nil, time: 0, sq: 0
     24 
     25   @doc "Converts a binary Flake to a String"
     26   def to_string(<<0::integer-size(64), id::integer-size(64)>>) do
     27     Kernel.to_string(id)
     28   end
     29 
     30   def to_string(<<_::integer-size(64), _::integer-size(48), _::integer-size(16)>> = flake) do
     31     encode_base62(flake)
     32   end
     33 
     34   def to_string(s), do: s
     35 
     36   def from_string(int) when is_integer(int) do
     37     from_string(Kernel.to_string(int))
     38   end
     39 
     40   for i <- [-1, 0] do
     41     def from_string(unquote(i)), do: <<0::integer-size(128)>>
     42     def from_string(unquote(Kernel.to_string(i))), do: <<0::integer-size(128)>>
     43   end
     44 
     45   def from_string(<<_::integer-size(128)>> = flake), do: flake
     46 
     47   def from_string(string) when is_binary(string) and byte_size(string) < 18 do
     48     case Integer.parse(string) do
     49       {id, ""} -> <<0::integer-size(64), id::integer-size(64)>>
     50       _ -> nil
     51     end
     52   end
     53 
     54   def from_string(string) do
     55     string |> decode_base62 |> from_integer
     56   end
     57 
     58   def to_integer(<<integer::integer-size(128)>>), do: integer
     59 
     60   def from_integer(integer) do
     61     <<_time::integer-size(64), _node::integer-size(48), _seq::integer-size(16)>> =
     62       <<integer::integer-size(128)>>
     63   end
     64 
     65   @doc "Generates a Flake"
     66   @spec get :: binary
     67   def get, do: to_string(:gen_server.call(:flake, :get))
     68 
     69   # checks that ID is is valid FlakeID
     70   #
     71   @spec is_flake_id?(String.t()) :: boolean
     72   def is_flake_id?(id), do: is_flake_id?(String.to_charlist(id), true)
     73   defp is_flake_id?([c | cs], true) when c >= ?0 and c <= ?9, do: is_flake_id?(cs, true)
     74   defp is_flake_id?([c | cs], true) when c >= ?A and c <= ?Z, do: is_flake_id?(cs, true)
     75   defp is_flake_id?([c | cs], true) when c >= ?a and c <= ?z, do: is_flake_id?(cs, true)
     76   defp is_flake_id?([], true), do: true
     77   defp is_flake_id?(_, _), do: false
     78 
     79   # -- Ecto.Type API
     80   @impl Ecto.Type
     81   def type, do: :uuid
     82 
     83   @impl Ecto.Type
     84   def cast(value) do
     85     {:ok, FlakeId.to_string(value)}
     86   end
     87 
     88   @impl Ecto.Type
     89   def load(value) do
     90     {:ok, FlakeId.to_string(value)}
     91   end
     92 
     93   @impl Ecto.Type
     94   def dump(value) do
     95     {:ok, FlakeId.from_string(value)}
     96   end
     97 
     98   def autogenerate, do: get()
     99 
    100   # -- GenServer API
    101   def start_link(_) do
    102     :gen_server.start_link({:local, :flake}, __MODULE__, [], [])
    103   end
    104 
    105   @impl GenServer
    106   def init([]) do
    107     {:ok, %FlakeId{node: worker_id(), time: time()}}
    108   end
    109 
    110   @impl GenServer
    111   def handle_call(:get, _from, state) do
    112     {flake, new_state} = get(time(), state)
    113     {:reply, flake, new_state}
    114   end
    115 
    116   # Matches when the calling time is the same as the state time. Incr. sq
    117   defp get(time, %FlakeId{time: time, node: node, sq: seq}) do
    118     new_state = %FlakeId{time: time, node: node, sq: seq + 1}
    119     {gen_flake(new_state), new_state}
    120   end
    121 
    122   # Matches when the times are different, reset sq
    123   defp get(newtime, %FlakeId{time: time, node: node}) when newtime > time do
    124     new_state = %FlakeId{time: newtime, node: node, sq: 0}
    125     {gen_flake(new_state), new_state}
    126   end
    127 
    128   # Error when clock is running backwards
    129   defp get(newtime, %FlakeId{time: time}) when newtime < time do
    130     {:error, :clock_running_backwards}
    131   end
    132 
    133   defp gen_flake(%FlakeId{time: time, node: node, sq: seq}) do
    134     <<time::integer-size(64), node::integer-size(48), seq::integer-size(16)>>
    135   end
    136 
    137   defp nthchar_base62(n) when n <= 9, do: ?0 + n
    138   defp nthchar_base62(n) when n <= 35, do: ?A + n - 10
    139   defp nthchar_base62(n), do: ?a + n - 36
    140 
    141   defp encode_base62(<<integer::integer-size(128)>>) do
    142     integer
    143     |> encode_base62([])
    144     |> List.to_string()
    145   end
    146 
    147   defp encode_base62(int, acc) when int < 0, do: encode_base62(-int, acc)
    148   defp encode_base62(int, []) when int == 0, do: '0'
    149   defp encode_base62(int, acc) when int == 0, do: acc
    150 
    151   defp encode_base62(int, acc) do
    152     r = rem(int, 62)
    153     id = div(int, 62)
    154     acc = [nthchar_base62(r) | acc]
    155     encode_base62(id, acc)
    156   end
    157 
    158   defp decode_base62(s) do
    159     decode_base62(String.to_charlist(s), 0)
    160   end
    161 
    162   defp decode_base62([c | cs], acc) when c >= ?0 and c <= ?9,
    163     do: decode_base62(cs, 62 * acc + (c - ?0))
    164 
    165   defp decode_base62([c | cs], acc) when c >= ?A and c <= ?Z,
    166     do: decode_base62(cs, 62 * acc + (c - ?A + 10))
    167 
    168   defp decode_base62([c | cs], acc) when c >= ?a and c <= ?z,
    169     do: decode_base62(cs, 62 * acc + (c - ?a + 36))
    170 
    171   defp decode_base62([], acc), do: acc
    172 
    173   defp time do
    174     {mega_seconds, seconds, micro_seconds} = :erlang.timestamp()
    175     1_000_000_000 * mega_seconds + seconds * 1000 + :erlang.trunc(micro_seconds / 1000)
    176   end
    177 
    178   defp worker_id do
    179     <<worker::integer-size(48)>> = :crypto.strong_rand_bytes(6)
    180     worker
    181   end
    182 end