I2Cバス上のMITM


9

I2Cバスで選択したスレーブ応答を変更できるモジュールを設計しようとしています。これは元のバス構成です(わかりやすくするためにプルアップと電源接続は示していません)。

ここに画像の説明を入力してください このバスには2つのデバイスしかなく、100kHzしかありません。コントローラーMCU(I2Cマスター)およびRFIDカードリーダー(I2Cスレーブ)NXP PN512。コントローラーのファームウェアを変更したり、I2Cバスのトランザクションを変更したりできません。良い点は、コントローラーが2種類のトランザクションのみを送信することです。

Master (Write Register) - <s><address+W><register number><data><p> Master (Read Register) - <s><address+W><register number><p><s><address+R><data><p>

私がしたいことは、マスターレジスタの読み取り中に選択したデータバイトを自分のバイトで置き換えることです。MCUが読み取りたいレジスタ番号をUART(921.6kbaud)経由でPCに送信できます。C / C ++またはPythonで処理できます。値を置き換える必要のあるレジスター番号を受け取ったら、偽のバイトをデバイスに送り返すことができます。コントローラーに送り返して、元のカードの応答を置き換えます。

最初に、I2Cバスを2つのバスに分割しました。 ここに画像の説明を入力してください

Arduino Nanoを試し、その後クロックストレッチを使用してCPLDを試しました。MCUコントローラーに面したATmega328ハードウェアI2Cは、前の停止サイクルの5usより前に開始シーケンスが生成されることがあったため、追いつくことができませんでした。したがって、時々、AVRは読み取りトランザクションをNAKしていました。CPLDは、バスストレッチがMCUで無効になっていることが判明した停止/開始速度を処理できました。

マスターレジスタの読み取りは、その後に読み取りが続くと確信しているので、1バイトの書き込みを検出することで、読み取りを「予測」できるというアイデアを思いつきました。次のリードサイクルアドレス書き込み中に、スレーブからバイトを取り込むのに十分な時間があったようです。それはうまくいきませんでした。バストランザクションは最初(約最初の5秒間)は正常に見えましたが、コントローラーはバス上のすべての通信を停止し、タグの読み取りと直接通信していないことを検出したかのように処理していました。

カードリーダーは、マスターへの割り込みを生成することもできます。IRQは、タイマーまたはイベントベースです。この問題は、バスに本来導入されていた遅延が原因であると考えています。私は間違っていたかもしれませんが、別の「ゼロ遅延」設計を思いつきました。 ここに画像の説明を入力してください

私が考えているのは、SDAラインを切断し、SCLラインをマスターとスレーブの間に接続したままにすることだけです。このようにして、データラインのバイトをどちらの方向でも置き換えることができます。バスサイクルに基づいてSDAラインの方向を制御する必要があるため、設計がより複雑であることがわかりました。バストランザクションを処理し、16進バイトをUART経由でコンピューターに送信するVHDLコードを次に示します。コンピューターからのバイトの受信はまだ実装されていません。

library ieee; 
use ieee.std_logic_1164.all; 
use ieee.numeric_std.all; 

entity I2C_Sniffer is 
port ( 
 clk : in std_logic;

 scl_master : in std_logic; 
 sda_master : inout std_logic;
 sda_slave  : inout std_logic;

 tx : out std_logic

); 
end entity I2C_Sniffer; 

architecture arch of I2C_Sniffer is
 signal clkDiv: std_logic_vector(7 downto 0) := (others => '0');

 type I2C_STATE is (I2C_IDLE, I2C_MASTER_WRITE, I2C_SLAVE_ACK, I2C_MASTER_READ, I2C_MASTER_ACK);
 signal i2cState: I2C_STATE := I2C_IDLE;

 type I2C_BUS_DIR is (MASTER_TO_SLAVE, SLAVE_TO_MASTER);
 signal i2cBusDir: I2C_BUS_DIR := MASTER_TO_SLAVE;

 signal i2cRxData: std_logic_vector(7 downto 0);
 signal i2cCntr: integer range 0 to 8 := 0;

 signal i2cAddr: std_logic := '1';
 signal i2cCmd: std_logic := '0';

 signal scl_d: std_logic := '1';
 signal scl: std_logic := '1';
 signal sda_d: std_logic := '1';
 signal sda: std_logic := '1';

 --Strobes for SCL edges and Start/Stop bits
 signal start_strobe : std_logic := '0';
 signal stop_strobe : std_logic := '0';
 signal scl_rising_strobe : std_logic := '0';
 signal scl_falling_strobe : std_logic := '0';

 type UART_STATE is (UART_IDLE, UART_START, UART_DATA, UART_STOP);
 signal uartState: UART_STATE := UART_IDLE;

 signal uartTxRdy: std_logic := '0';
 signal uartTxData: std_logic_vector(7 downto 0);
 signal uartCntr: integer range 0 to 8 := 0;

begin

 CLK_DIV: process (clk)
 begin
   if rising_edge(clk) then
     clkDiv <= std_logic_vector(unsigned(clkDiv) + 1);
   end if;
 end process;

I2C_STROBES: process (clk)
begin
  if rising_edge(clk) then
    --Pipelined SDA and SCL signals

    scl_d <= scl_master;
    scl <= scl_d;

    scl_rising_strobe <= '0';
    if scl = '0' and scl_d = '1' then
      scl_rising_strobe <= '1';
    end if;

    scl_falling_strobe <= '0';
    if scl = '1' and scl_d = '0' then
      scl_falling_strobe <= '1';
    end if;

    if i2cBusDir = MASTER_TO_SLAVE then
      sda_d <= sda_master;
      sda <= sda_d;
    else
      sda_d <= sda_slave;
      sda <= sda_d;
    end if;

    start_strobe <= '0';
    if sda_d = '0' and sda = '1' and scl = '1' and scl_d = '1' then
      start_strobe <= '1';
    end if;

    stop_strobe <= '0';
    if sda_d = '1' and sda = '0' and scl = '1' and scl_d = '1' then
      stop_strobe <= '1';
    end if;
  end if;
end process;

BUS_DIR: process(sda_master, sda_slave, i2cBusDir)
begin 
  if i2cBusDir = MASTER_TO_SLAVE then
    sda_slave <= sda_master;
    sda_master <= 'Z';
  else
    sda_master <= sda_slave;
    sda_slave <= 'Z';
  end if;
end process;

I2C: process(clk)
begin
    if rising_edge(clk) then
        uartTxRdy <= '0';

        case i2cState is
            when I2C_IDLE =>
                i2cBusDir <= MASTER_TO_SLAVE;

                if start_strobe = '1' then
                    i2cAddr <= '1';
                    i2cCntr <= 0;
                    i2cState <= I2C_MASTER_WRITE;
                end if;

            -- Master Write (Address/Data)
            when I2C_MASTER_WRITE =>
                i2cBusDir <= MASTER_TO_SLAVE;

                if stop_strobe = '1' then
                    i2cState <= I2C_IDLE;
                        uartTxData <= "00001010";
                        uartTxRdy <= '1';
                end if;

                if scl_rising_strobe = '1' then
                    if i2cCntr <= 7 then
                        i2cRxData(7 - i2cCntr) <= sda;
                        i2cCntr <= i2cCntr + 1;
                    end if;
                end if;

                if i2cCntr = 4 then
                    case i2cRxData(7 downto 4) is
                        when "0000" => uartTxData <= "00110000"; --0
                        when "0001" => uartTxData <= "00110001"; --1
                        when "0010" => uartTxData <= "00110010"; --2
                        when "0011" => uartTxData <= "00110011"; --3
                        when "0100" => uartTxData <= "00110100"; --4
                        when "0101" => uartTxData <= "00110101"; --5
                        when "0110" => uartTxData <= "00110110"; --6
                        when "0111" => uartTxData <= "00110111"; --7
                        when "1000" => uartTxData <= "00111000"; --8
                        when "1001" => uartTxData <= "00111001"; --9
                        when "1010" => uartTxData <= "01000001"; --A
                        when "1011" => uartTxData <= "01000010"; --B
                        when "1100" => uartTxData <= "01000011"; --C
                        when "1101" => uartTxData <= "01000100"; --D
                        when "1110" => uartTxData <= "01000101"; --E
                        when "1111" => uartTxData <= "01000110"; --F
                        when others => uartTxData <= "00111111"; --?
                    end case;
                    uartTxRdy <= '1';
                end if;

                if i2cCntr = 8 then
                    case i2cRxData(3 downto 0) is
                        when "0000" => uartTxData <= "00110000"; --0
                        when "0001" => uartTxData <= "00110001"; --1
                        when "0010" => uartTxData <= "00110010"; --2
                        when "0011" => uartTxData <= "00110011"; --3
                        when "0100" => uartTxData <= "00110100"; --4
                        when "0101" => uartTxData <= "00110101"; --5
                        when "0110" => uartTxData <= "00110110"; --6
                        when "0111" => uartTxData <= "00110111"; --7
                        when "1000" => uartTxData <= "00111000"; --8
                        when "1001" => uartTxData <= "00111001"; --9
                        when "1010" => uartTxData <= "01000001"; --A
                        when "1011" => uartTxData <= "01000010"; --B
                        when "1100" => uartTxData <= "01000011"; --C
                        when "1101" => uartTxData <= "01000100"; --D
                        when "1110" => uartTxData <= "01000101"; --E
                        when "1111" => uartTxData <= "01000110"; --F
                        when others => uartTxData <= "00111111"; --?
                    end case;
                    uartTxRdy <= '1';
                end if;

                if i2cCntr = 8 then
                    if scl_falling_strobe = '1' then
                        i2cState <= I2C_SLAVE_ACK;

                        if i2cAddr = '1' then
                            i2cCmd <= i2cRxData(0);
                            i2cAddr <= '0';
                        end if;
                    end if;
                end if;

            when I2C_SLAVE_ACK =>
                i2cBusDir <= SLAVE_TO_MASTER;

                if scl_falling_strobe = '1' then
                    i2cCntr <= 0;

                    if i2cCmd = '0' then
                        i2cState <= I2C_MASTER_WRITE;
                    else
                        i2cState <= I2C_MASTER_READ;
                    end if;
                end if;

            when I2C_MASTER_READ =>
                i2cBusDir <= SLAVE_TO_MASTER;

                if stop_strobe = '1' then
                    i2cState <= I2C_IDLE;
                        uartTxData <= "00001010";
                        uartTxRdy <= '1';
                end if;

                if scl_rising_strobe = '1' then
                    if i2cCntr <= 7 then
                        i2cRxData(7 - i2cCntr) <= sda;
                        i2cCntr <= i2cCntr + 1;
                    end if;
                end if;

                if i2cCntr = 4 then
                    case i2cRxData(7 downto 4) is
                        when "0000" => uartTxData <= "00110000"; --0
                        when "0001" => uartTxData <= "00110001"; --1
                        when "0010" => uartTxData <= "00110010"; --2
                        when "0011" => uartTxData <= "00110011"; --3
                        when "0100" => uartTxData <= "00110100"; --4
                        when "0101" => uartTxData <= "00110101"; --5
                        when "0110" => uartTxData <= "00110110"; --6
                        when "0111" => uartTxData <= "00110111"; --7
                        when "1000" => uartTxData <= "00111000"; --8
                        when "1001" => uartTxData <= "00111001"; --9
                        when "1010" => uartTxData <= "01000001"; --A
                        when "1011" => uartTxData <= "01000010"; --B
                        when "1100" => uartTxData <= "01000011"; --C
                        when "1101" => uartTxData <= "01000100"; --D
                        when "1110" => uartTxData <= "01000101"; --E
                        when "1111" => uartTxData <= "01000110"; --F
                        when others => uartTxData <= "00111111"; --?
                    end case;
                    uartTxRdy <= '1';
                end if;

                if i2cCntr = 8 then
                    case i2cRxData(3 downto 0) is
                        when "0000" => uartTxData <= "00110000"; --0
                        when "0001" => uartTxData <= "00110001"; --1
                        when "0010" => uartTxData <= "00110010"; --2
                        when "0011" => uartTxData <= "00110011"; --3
                        when "0100" => uartTxData <= "00110100"; --4
                        when "0101" => uartTxData <= "00110101"; --5
                        when "0110" => uartTxData <= "00110110"; --6
                        when "0111" => uartTxData <= "00110111"; --7
                        when "1000" => uartTxData <= "00111000"; --8
                        when "1001" => uartTxData <= "00111001"; --9
                        when "1010" => uartTxData <= "01000001"; --A
                        when "1011" => uartTxData <= "01000010"; --B
                        when "1100" => uartTxData <= "01000011"; --C
                        when "1101" => uartTxData <= "01000100"; --D
                        when "1110" => uartTxData <= "01000101"; --E
                        when "1111" => uartTxData <= "01000110"; --F
                        when others => uartTxData <= "00111111"; --?
                    end case;
                    uartTxRdy <= '1';
                end if;

                if i2cCntr = 8 and scl_falling_strobe = '1' then
                    i2cState <= I2C_MASTER_ACK;
                end if;

            when I2C_MASTER_ACK =>
                i2cBusDir <= MASTER_TO_SLAVE;
                if scl_falling_strobe = '1' then
                    i2cCntr <= 0;
                end if;

                if stop_strobe = '1' then
                    i2cState <= I2C_IDLE;
                    uartTxData <= "00001010"; -- \n
                    uartTxRdy <= '1';
                end if;
        end case;
    end if;
end process;


UART: process (clk, clkDiv(1), uartTxRdy)
begin
    if rising_edge(clk) then
        case uartState is
            when UART_IDLE =>
                if uartTxRdy = '1' then
                    uartState <= UART_START;
                end if;

            when UART_START =>
                if clkDiv(1 downto 0) = "00" then
                    tx <= '0';
                    uartState <= UART_DATA;
                    uartCntr <= 0;
                end if;

            when UART_DATA =>
                if clkDiv(1 downto 0) = "00" then
                    if uartCntr <= 7 then
                        uartCntr <= uartCntr + 1;
                        tx <= uartTxData(uartCntr);
                    else
                        tx <= '1';
                        uartState <= UART_STOP;
                    end if;
                end if;

            when UART_STOP =>
                if clkDiv(1 downto 0) = "00" then
                    tx <= '1';
                    uartState <= UART_IDLE;
                end if;
        end case;
    end if;
  end process;
end architecture arch;

以下は、SDAラインを制御するCPLDでキャプチャされたバス変換です。

レジスタ書き込み:

ここに画像の説明を入力してください

レジスター読み取り:

ここに画像の説明を入力してください

バスの方向が変わると、いくつかの不具合が見られます。これは、CPLDがバスの方向を変更するタイミングと、カードリーダーがACKを生成するタイミングの違いが原因です。ACKレベルは、SCLの立ち上がりエッジで安定しているようです。私が知る限り、それで十分です。

このことを行うと、コントローラーは、数秒以内にバスのアクティビティを中断するスプリットバスの場合と同じように動作します。私はまた、そのMCUをモックしてバストラフィックを生成するArduinoをテストしました。Arduinoも時々フリーズするようです。したがって、VHDLステートマシンで何らかの問題が発生し、状況によっては1つの状態から抜け出せなくなる可能性があります。何か案は?


あなたの質問は、とにかく私にはあまり明確ではありません。最初に言ってThere's only 2 devices on this bus running at 100kHz、それからThe hardware I2C was a slave and a bit banged I2C was a master on the card reader bus at 1Mbps。なぜ2つのバスがあるのですか?なぜ高速バスが必要なのですか?初期設計のスケッチを提供し、質問を明確にしてください。
SoreDakeNoKoto 2016

ええ、ごめんなさい。元のバスには、コントローラー(i2cマスター)とRFIDカード/タグリーダー(i2cスレーブ)しかありません。したがって、ポイントツーポイントであるため、I2Cアドレス指定について心配する必要はありません(マスターから送信されるすべてのパケットは、この1つのスレーブ用です)。私の最初のアプローチは、バスを2つのバスに分割し、コントローラー側ではi2cスレーブとして、RFIDリーダー側ではマスターとして機能することでした。
Alexxx

RIFDリーダーはより高速(1MHzまたはさらに高速)に対応しているので、RFIDリーダーからデータを読み取る間、コントローラー側のバス(バスストレッチ)を長時間保持しないようにしたいと思いました。登録。また、1バイトの書き込みを検出したときにバスストレッチがなければ、RIFDリーダーからバイトを読み取ってコントローラーに送信するための次の読み取りサイクルの時間はほとんどありません。
Alexxx

バスストレッチングとは、スレーブがSCLラインをLowに保持して、データの準備がまだ整っていないことをマスターに知らせるI2Cクロックストレッチングを意味します。スレーブの準備ができると、スレーブはSCLラインを解放し、マスターはスレーブによって送信されたビットを読み取り続けます。
Alexxx 16

1
代わりに質問を編集することをお勧めします。まだ2つのバスが必要な理由を説明していません。I2Cを採用しているカードリーダーからデータを読み取ることだけが必要な場合は、それらを同じバスに接続して、MCUから読み取ってみませんか?クロックストレッチングは、通常、マスターへの応答が遅い場合にのみ、スレーブ側で意味をなします。スレーブの準備ができていない場合は、何もすることができません。ほとんどのアプリケーションでは、通常100〜400kHzで十分です。読み取ったデータまたは同様のデータに対して時間に敏感な操作を実行する必要がある場合に、より速い速度が必要になる唯一の理由。
SoreDakeNoKoto 2016

回答:


6

今までのようにカットシーハックを試みることは、まさにあなたが遭遇しているような症状で、トラブルを求めていると思います。あなたは基本的にごまかそうとしていますが、捕まらないように願っています。

あなたの説明によると、あなたが試していないのは、このカードリーダーのものの完全なエミュレーションです。あなたは実際にそれが何をするのか、どれほど複雑であるかを説明していませんが、マスターが何を送信しているかから判断すると、それほど複雑ではありません。

ハードウェアIICスレーブ機能を備えたマイクロコントローラーを使用します。それはマスターに接続されています。ファームウェアはカードリーダーをエミュレートします。マスターが読み取るのはレジスタのシーケンスだけなので、ファームウェアの他の部分はカードリーダーと完全に非同期で通信して、情報を取得し、制御します。これは、リセットラインとIRQラインも分離されていることも意味します。

正しく行われている場合、不正行為は発生していないため、これは機能する必要があります。カードリーダーは、コントローラーがコマンドを送信し、それがどのように使用されるかを正確に読み取るのを見ます。これには、IRQイベントへの応答が含まれます。

マスターは、リセットやIRQの動作など、本物と同じようにすべての操作をエミュレートするため、実際のカードリーダーと直接通信していると考えます。

これは、バスのハックに別のバイトをすばやくダーティにジャムするよりも作業のように聞こえるかもしれませんが、それほど速くなく、常にいくつかのタイミングの問題があるかもしれません。完全なエミュレーションにより、すべてのタイミング制約が解除されます。エミュレーションがカードリーダーの処理にまだ追いついていない場合は、まだ行われていないかのようにマスターに働きかけます。基本的に、エミュレーションがすべての面でイベントに応答する準備ができるまで、新しいことが何も起こらないふりをします。

これは、ファームウェアの2つの非同期部分があることを意味します。マスターに提示されるリーダーのIICエミュレーションと、すべての状態を内部メモリでライブに保つことができるフルカードリーダードライバー。

あなたは浮気していないので、これが正しく行われればうまくいくはずです。唯一のシステムレベルの問題は、マスターが既存のシステムよりもカードリーダーの動作を確認および発生させるのに多少の遅延があることです。これは「カードリーダー」にとって大したことのようには聞こえません。この遅延を考慮すると、最悪の場合数十ミリ秒になる可能性があります。それは確かに人間の時間スケールでは目立ちません。

エミュレータとカードリーダー間の通信は、現在使用されている100 kbits / sに限定されないことに注意してください。カードリーダーとハードウェアで可能な限り速く実行する必要があります。結局のところ、そのリンクではあなたがマスターになるので、あなたは時計を所有しています。ここでも、適切なファームウェアアーキテクチャと非同期タスクがあれば、これは問題になりません。実際、ドライバーはおそらくマスターがエミュレーターから取得するよりも頻繁に通信し、カードリーダーからより多くのデータを取得します。


答えてくれてありがとう。最初にそれを見ていると述べたとき、私は正確にいくつかを念頭に置いていました。かなり複雑そうなので、私はすぐにその考えを放棄しました。MCUがレジスタの書き込みと読み取りのみを行っている場合は簡単ですが、リーダーは独自のプロトコル(マルチバイトのコマンドと応答)を持つRFIDと通信します。その上、MCUはリーダーのIRQにいくつかのフラグを設定し、像を読み返します。したがって、数バイトだけを対象として残りをリーダーに任せる方がはるかに簡単であるように思われました。
Alexxx 16

また、バス全体を2つのバスに分割すると、カードリーダーとより高速で通信できます。ただし、SDAラインを切断する最新の設計では、SCLライン上のMCUから提供される100KHzのタイミングに固執する必要があります。
Alexxx 16

0

私はArduino NanoをMITMとして正しい方向に進んでいることをお勧めしますが、2つを使うのが最善だと思います。

ここに画像の説明を入力してください

NXP-PN512は3.4 Mhzのクロック速度で動作するので、リーダーと通信する右側のMCUには1.5〜2 MHz程度のものを使用することをお勧めします。
左側のMCUは100 kHzに設定されているため、トランザクションバイト(アドレス/レジスタ-WR)を認識したら、MCU間の8ビットパラレルバス(またはさらに広い)にコピーして、コマンドをリーダーに送信できます。低速I2Cチャネルで1クロック時間未満。リーダーからバイトを等しく受信することは、応答バスをセットアップするための十分な時間を与える遅いバス上のクロック時間よりも短い時間で達成されます。

ここでは、1バイトずつ変換するだけでなく、複数のバイトをNFC IDとして実際に変換する必要がある場合を想定しています(所要時間が短い)。

そのとき私が目にする主な問題は、変更をマップするためにPCとの間で複数のバイトをシリアル化する必要がある場合、タイミングがさらに重要になることです。マッピング変更アルゴリズム/テーブルを左側のMCUに組み込む方法があった場合、より良いアプローチのように思われますが、マルチバイ識別子マッピングの解決は依然として最大の課題です。

私が間違っていて、1つのカード識別子バイトをマップする必要があるだけなら、これでうまくいくかもしれません。

Arduinoを使用した初期のテストで、すべての割り込みがオフになっていることを確認しましたか(少なくともTWIのみが使用されています)?あなたがしなかったなら、これはあなたのタイミングを台無しにしたかもしれません。


1
2つの別個のマイクロが必要な理由がわかりません。多くのマイクロが2つのIICバスを同時に処理できます。マスターになる場合でもハードウェアを使用すると便利な場合がありますが、実際に必要なのはスレーブになるためのハードウェアだけです。2つのマイクロ間での通信は、同じマイクロで実行されている2つのタスクと比較して、不必要に複雑で低速に見えます。2つのマイクロが解決する問題を確認できません。
Olin Lathrop

@Olin Lathrop。ソフトウェア開発を簡素化します。デバッグなどがはるかに簡単になります。それは、自動車に1つ(提案するように単純な)大きなマルチプロセスプロセッサではなく、何百ものマイクロプロセッサが搭載されている理由です。複数のMCUを使用してもまったく問題はありません。コストは主に単機能ロジックチップよりも低く、機能の定義と開発が簡単です。この場合、ATMega328にはTWI割り込みベクトルが1つしかないため、I2Cの2つのチャネルをサポートすることは困難です。..しかし、それは確かに個人的な選択です。
Jack Creasey

この場合、複数のプロセッサにより複雑さが増し、追加の通信が必要になります。バスマスターの場合、IICに割り込みやハードウェアを使用する必要はありません。ただし、ハードウェアで2つのIICバスを独立して処理できるプロセッサはたくさんあります。ATMegaができず、2つのハードウェアIICを使用したい場合は、ATMegaを使用しないでください。
Olin Lathrop

@Olin Lathrop。同意しないことに同意しましょう。100 kHzを超えるIMOビットバッシングは重要ではありません。OPの問題は、データをシリアル化してPCに送信してマッピングアルゴリズムを実行するコストにタイミングの問題が伴うことです。
Jack Creasey

回答とコメントをありがとう。ATMega328 / Arduinoは、MCU I2Cタイミングのため、MCU側では使用できません。このMCUは、前回の停止後、4.7usよりも速い開始シーケンスを生成できます。ATMega328データシートの表32-10(tBUFパラメータ)を参照してください。起こっていたのは、Arduinoがi2c書き込みに続くすべてのi2c読み取りをNACKしていたことでした。これは明らかに既知の問題です。髪の毛を全部引っ張った後、オンラインでどこかでその情報を見つけました。これがCPLDに切り替えた理由です。
Alexxx 16
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.